From c57797df0f21eee1f57332d7a31d351ead08573d Mon Sep 17 00:00:00 2001 From: hthieu1110 Date: Thu, 19 Dec 2024 21:24:40 +0700 Subject: [PATCH 01/14] chore: remove Get prefixes from GetCallerAt, GetOrigSend, GetOrigCaller --- .../porting-solidity-to-gno/porting-5.gno | 4 +- .../porting-solidity-to-gno/porting-8.gno | 6 +-- .../write-simple-dapp/poll-2.gno | 2 +- docs/concepts/effective-gno.md | 8 +-- docs/how-to-guides/porting-solidity-to-gno.md | 10 ++-- docs/how-to-guides/write-simple-dapp.md | 2 +- docs/reference/stdlibs/std/chain.md | 12 ++--- .../p/demo/gnorkle/gnorkle/instance.gno | 2 +- .../demo/grc/grc1155/basic_grc1155_token.gno | 20 +++---- .../grc/grc1155/basic_grc1155_token_test.gno | 6 +-- .../gno.land/p/demo/microblog/microblog.gno | 2 +- examples/gno.land/p/demo/simpledao/dao.gno | 6 +-- .../p/demo/subscription/lifetime/lifetime.gno | 2 +- .../demo/subscription/recurring/recurring.gno | 2 +- .../gno.land/p/demo/todolist/todolist.gno | 2 +- .../p/demo/todolist/todolist_test.gno | 2 +- examples/gno.land/r/demo/banktest/README.md | 4 +- .../gno.land/r/demo/banktest/banktest.gno | 4 +- examples/gno.land/r/demo/boards/public.gno | 14 ++--- .../gno.land/r/demo/disperse/disperse.gno | 2 +- examples/gno.land/r/demo/foo1155/foo1155.gno | 8 +-- examples/gno.land/r/demo/groups/public.gno | 8 +-- .../gno.land/r/demo/groups/z_1_a_filetest.gno | 2 +- .../gno.land/r/demo/groups/z_2_a_filetest.gno | 2 +- .../gno.land/r/demo/keystore/keystore.gno | 12 ++--- .../gno.land/r/demo/microblog/microblog.gno | 2 +- .../r/demo/releases_example/example.gno | 4 +- examples/gno.land/r/demo/tests/tests.gno | 2 +- .../r/demo/todolist/todolist_test.gno | 2 +- examples/gno.land/r/demo/users/users.gno | 10 ++-- .../gno.land/r/demo/users/z_10_filetest.gno | 2 +- .../gno.land/r/demo/users/z_11_filetest.gno | 2 +- .../gno.land/r/demo/users/z_11b_filetest.gno | 2 +- .../gno.land/r/demo/users/z_2_filetest.gno | 2 +- .../gno.land/r/demo/users/z_3_filetest.gno | 2 +- .../gno.land/r/demo/users/z_4_filetest.gno | 2 +- .../gno.land/r/demo/users/z_5_filetest.gno | 2 +- .../gno.land/r/demo/users/z_6_filetest.gno | 2 +- .../gno.land/r/demo/users/z_7_filetest.gno | 2 +- .../gno.land/r/demo/users/z_7b_filetest.gno | 2 +- .../gno.land/r/demo/users/z_8_filetest.gno | 2 +- .../gno.land/r/demo/users/z_9_filetest.gno | 2 +- examples/gno.land/r/demo/wugnot/wugnot.gno | 2 +- examples/gno.land/r/gnoland/blog/admin.gno | 10 ++-- examples/gno.land/r/gnoland/blog/gnoblog.gno | 2 +- .../gno.land/r/gnoland/blog/gnoblog_test.gno | 2 +- examples/gno.land/r/gnoland/faucet/admin.gno | 2 +- examples/gno.land/r/gnoland/faucet/faucet.gno | 2 +- .../gno.land/r/gnoland/ghverify/contract.gno | 6 +-- .../r/gnoland/ghverify/contract_test.gno | 2 +- examples/gno.land/r/gnoland/pages/admin.gno | 8 +-- .../r/gnoland/valopers/v2/valopers.gno | 2 +- .../r/matijamarjanovic/home/config.gno | 4 +- .../gno.land/r/matijamarjanovic/home/home.gno | 6 +-- examples/gno.land/r/moul/config/config.gno | 2 +- examples/gno.land/r/moul/present/admin.gno | 8 +-- examples/gno.land/r/stefann/home/home.gno | 4 +- .../upgrade_b/v1/v1.gno | 2 +- .../upgrade_b/v2/v2.gno | 2 +- .../nir1218_evaluation_proposal/committee.gno | 14 ++--- gno.land/cmd/gnoland/testdata/initctx.txtar | 2 +- .../cmd/gnoland/testdata/issue_1786.txtar | 8 +-- .../cmd/gnoland/testdata/issue_2283.txtar | 2 +- .../testdata/issue_2283_cacheTypes.txtar | 2 +- gno.land/genesis/genesis_txs.jsonl | 2 +- gno.land/pkg/sdk/vm/handler_test.go | 2 +- gno.land/pkg/sdk/vm/keeper_test.go | 24 ++++----- gnovm/stdlibs/std/native.gno | 6 +-- gnovm/stdlibs/std/native.go | 6 +-- gnovm/tests/files/std10.gno | 4 +- gnovm/tests/files/std11.gno | 2 +- gnovm/tests/files/std2.gno | 2 +- gnovm/tests/files/std3.gno | 4 +- gnovm/tests/files/std4.gno | 2 +- gnovm/tests/files/std5.gno | 6 +-- gnovm/tests/files/std6.gno | 4 +- gnovm/tests/files/std7.gno | 6 +-- gnovm/tests/files/std8.gno | 10 ++-- gnovm/tests/files/zrealm_initctx.gno | 2 +- gnovm/tests/files/zrealm_std0.gno | 2 +- gnovm/tests/files/zrealm_std1.gno | 4 +- gnovm/tests/files/zrealm_std2.gno | 4 +- gnovm/tests/stdlibs/std/std.gno | 2 +- gnovm/tests/stdlibs/std/std.go | 6 +-- .../test5.gno.land/genesis_txs.jsonl | 54 +++++++++---------- 85 files changed, 218 insertions(+), 218 deletions(-) diff --git a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-5.gno b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-5.gno index 43f0b43b397..c1dfc2ae03e 100644 --- a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-5.gno +++ b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-5.gno @@ -3,7 +3,7 @@ func Bid() { panic("Exceeded auction end block") } - sentCoins := std.GetOrigSend() + sentCoins := std.OrigSend() if len(sentCoins) != 1 { panic("Send only one type of coin") } @@ -23,7 +23,7 @@ func Bid() { } // Update the top bidder address - highestBidder = std.GetOrigCaller() + highestBidder = std.OrigCaller() // Update the top bid amount highestBid = sentAmount } diff --git a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-8.gno b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-8.gno index 7cb6bbd8d90..f1bda3ec56d 100644 --- a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-8.gno +++ b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-8.gno @@ -1,15 +1,15 @@ func Withdraw() { // Query the return amount to non-highest bidders - amount, _ := pendingReturns.Get(std.GetOrigCaller().String()) + amount, _ := pendingReturns.Get(std.OrigCaller().String()) if amount > 0 { // If there's an amount, reset the amount first, - pendingReturns.Set(std.GetOrigCaller().String(), 0) + pendingReturns.Set(std.OrigCaller().String(), 0) // Return the exceeded amount banker := std.GetBanker(std.BankerTypeRealmSend) pkgAddr := std.GetOrigPkgAddr() - banker.SendCoins(pkgAddr, std.GetOrigCaller(), std.Coins{{"ugnot", amount.(int64)}}) + banker.SendCoins(pkgAddr, std.OrigCaller(), std.Coins{{"ugnot", amount.(int64)}}) } } diff --git a/docs/assets/how-to-guides/write-simple-dapp/poll-2.gno b/docs/assets/how-to-guides/write-simple-dapp/poll-2.gno index c7dbaedfbb2..0521eb12cb2 100644 --- a/docs/assets/how-to-guides/write-simple-dapp/poll-2.gno +++ b/docs/assets/how-to-guides/write-simple-dapp/poll-2.gno @@ -40,7 +40,7 @@ func NewPoll(title, description string, deadline int64) string { // yes - true, no - false func Vote(id string, vote bool) string { // get txSender - txSender := std.GetOrigCaller() + txSender := std.OrigCaller() // get specific Poll from AVL tree pollRaw, exists := polls.Get(id) diff --git a/docs/concepts/effective-gno.md b/docs/concepts/effective-gno.md index 45872dd1d63..2b11ca693e6 100644 --- a/docs/concepts/effective-gno.md +++ b/docs/concepts/effective-gno.md @@ -169,10 +169,10 @@ var ( func init() { created = time.Now() - // std.GetOrigCaller in the context of realm initialisation is, + // std.OrigCaller in the context of realm initialisation is, // of course, the publisher of the realm :) // This can be better than hardcoding an admin address as a constant. - admin = std.GetOrigCaller() + admin = std.OrigCaller() // list is already initialized, so it will already contain "foo", "bar" and // the current time as existing items. list = append(list, admin.String()) @@ -498,10 +498,10 @@ One strategy is to look at the caller with `std.PrevRealm()`, which could be the EOA (Externally Owned Account), or the preceding realm in the call stack. Another approach is to look specifically at the EOA. For this, you should call -`std.GetOrigCaller()`, which returns the public address of the account that +`std.OrigCaller()`, which returns the public address of the account that signed the transaction. -TODO: explain when to use `std.GetOrigCaller`. +TODO: explain when to use `std.OrigCaller`. Internally, this call will look at the frame stack, which is basically the stack of callers including all the functions, anonymous functions, other realms, and diff --git a/docs/how-to-guides/porting-solidity-to-gno.md b/docs/how-to-guides/porting-solidity-to-gno.md index 85c426c4c83..7b57cf388c5 100644 --- a/docs/how-to-guides/porting-solidity-to-gno.md +++ b/docs/how-to-guides/porting-solidity-to-gno.md @@ -353,7 +353,7 @@ func Bid() { panic("Exceeded auction end block") } - sentCoins := std.GetOrigSend() + sentCoins := std.OrigSend() if len(sentCoins) != 1 { panic("Send only one type of coin") } @@ -373,7 +373,7 @@ func Bid() { } // Update the top bidder address - highestBidder = std.GetOrigCaller() + highestBidder = std.OrigCaller() // Update the top bid amount highestBid = sentAmount } @@ -466,17 +466,17 @@ function withdraw() external returns (bool) { ```go func Withdraw() { // Query the return amount to non-highest bidders - amount, _ := pendingReturns.Get(std.GetOrigCaller().String()) + amount, _ := pendingReturns.Get(std.OrigCaller().String()) if amount > 0 { // If there's an amount, reset the amount first, - pendingReturns.Set(std.GetOrigCaller().String(), 0) + pendingReturns.Set(std.OrigCaller().String(), 0) // Return the exceeded amount banker := std.GetBanker(std.BankerTypeRealmSend) pkgAddr := std.GetOrigPkgAddr() - banker.SendCoins(pkgAddr, std.GetOrigCaller(), std.Coins{{"ugnot", amount.(int64)}}) + banker.SendCoins(pkgAddr, std.OrigCaller(), std.Coins{{"ugnot", amount.(int64)}}) } } ``` diff --git a/docs/how-to-guides/write-simple-dapp.md b/docs/how-to-guides/write-simple-dapp.md index f844f8ab7c8..50c3ffe337a 100644 --- a/docs/how-to-guides/write-simple-dapp.md +++ b/docs/how-to-guides/write-simple-dapp.md @@ -174,7 +174,7 @@ func NewPoll(title, description string, deadline int64) string { // yes - true, no - false func Vote(id string, vote bool) string { // get txSender - txSender := std.GetOrigCaller() + txSender := std.OrigCaller() // get specific Poll from AVL tree pollRaw, exists := polls.Get(id) diff --git a/docs/reference/stdlibs/std/chain.md b/docs/reference/stdlibs/std/chain.md index 6a1da6483fd..38abaac217a 100644 --- a/docs/reference/stdlibs/std/chain.md +++ b/docs/reference/stdlibs/std/chain.md @@ -77,27 +77,27 @@ height := std.GetHeight() ``` --- -## GetOrigSend +## OrigSend ```go -func GetOrigSend() Coins +func OrigSend() Coins ``` Returns the `Coins` that were sent along with the calling transaction. #### Usage ```go -coinsSent := std.GetOrigSend() +coinsSent := std.OrigSend() ``` --- -## GetOrigCaller +## OrigCaller ```go -func GetOrigCaller() Address +func OrigCaller() Address ``` Returns the original signer of the transaction. #### Usage ```go -caller := std.GetOrigCaller() +caller := std.OrigCaller() ``` --- diff --git a/examples/gno.land/p/demo/gnorkle/gnorkle/instance.gno b/examples/gno.land/p/demo/gnorkle/gnorkle/instance.gno index eea4782909e..9d33f71ccd2 100644 --- a/examples/gno.land/p/demo/gnorkle/gnorkle/instance.gno +++ b/examples/gno.land/p/demo/gnorkle/gnorkle/instance.gno @@ -106,7 +106,7 @@ type PostMessageHandler interface { // TODO: Consider further message types that could allow administrative action such as modifying // a feed's whitelist without the owner of this oracle having to maintain a reference to it. func (i *Instance) HandleMessage(msg string, postHandler PostMessageHandler) (string, error) { - caller := string(std.GetOrigCaller()) + caller := string(std.OrigCaller()) funcType, msg := message.ParseFunc(msg) diff --git a/examples/gno.land/p/demo/grc/grc1155/basic_grc1155_token.gno b/examples/gno.land/p/demo/grc/grc1155/basic_grc1155_token.gno index f152ee90e79..8b03a99a645 100644 --- a/examples/gno.land/p/demo/grc/grc1155/basic_grc1155_token.gno +++ b/examples/gno.land/p/demo/grc/grc1155/basic_grc1155_token.gno @@ -62,7 +62,7 @@ func (s *basicGRC1155Token) SetApprovalForAll(operator std.Address, approved boo return ErrInvalidAddress } - caller := std.GetOrigCaller() + caller := std.OrigCaller() return s.setApprovalForAll(caller, operator, approved) } @@ -85,7 +85,7 @@ func (s *basicGRC1155Token) IsApprovedForAll(owner, operator std.Address) bool { // contract recipients are aware of the GRC1155 protocol to prevent // tokens from being forever locked. func (s *basicGRC1155Token) SafeTransferFrom(from, to std.Address, tid TokenID, amount uint64) error { - caller := std.GetOrigCaller() + caller := std.OrigCaller() if !s.IsApprovedForAll(caller, from) { return ErrCallerIsNotOwnerOrApproved } @@ -108,7 +108,7 @@ func (s *basicGRC1155Token) SafeTransferFrom(from, to std.Address, tid TokenID, // contract recipients are aware of the GRC1155 protocol to prevent // tokens from being forever locked. func (s *basicGRC1155Token) SafeBatchTransferFrom(from, to std.Address, batch []TokenID, amounts []uint64) error { - caller := std.GetOrigCaller() + caller := std.OrigCaller() if !s.IsApprovedForAll(caller, from) { return ErrCallerIsNotOwnerOrApproved } @@ -130,7 +130,7 @@ func (s *basicGRC1155Token) SafeBatchTransferFrom(from, to std.Address, batch [] // Creates `amount` tokens of token type `id`, and assigns them to `to`. Also checks that // contract recipients are using GRC1155 protocol. func (s *basicGRC1155Token) SafeMint(to std.Address, tid TokenID, amount uint64) error { - caller := std.GetOrigCaller() + caller := std.OrigCaller() err := s.mintBatch(to, []TokenID{tid}, []uint64{amount}) if err != nil { @@ -149,7 +149,7 @@ func (s *basicGRC1155Token) SafeMint(to std.Address, tid TokenID, amount uint64) // Batch version of `SafeMint()`. Also checks that // contract recipients are using GRC1155 protocol. func (s *basicGRC1155Token) SafeBatchMint(to std.Address, batch []TokenID, amounts []uint64) error { - caller := std.GetOrigCaller() + caller := std.OrigCaller() err := s.mintBatch(to, batch, amounts) if err != nil { @@ -167,7 +167,7 @@ func (s *basicGRC1155Token) SafeBatchMint(to std.Address, batch []TokenID, amoun // Destroys `amount` tokens of token type `id` from `from`. func (s *basicGRC1155Token) Burn(from std.Address, tid TokenID, amount uint64) error { - caller := std.GetOrigCaller() + caller := std.OrigCaller() err := s.burnBatch(from, []TokenID{tid}, []uint64{amount}) if err != nil { @@ -181,7 +181,7 @@ func (s *basicGRC1155Token) Burn(from std.Address, tid TokenID, amount uint64) e // Batch version of `Burn()` func (s *basicGRC1155Token) BatchBurn(from std.Address, batch []TokenID, amounts []uint64) error { - caller := std.GetOrigCaller() + caller := std.OrigCaller() err := s.burnBatch(from, batch, amounts) if err != nil { @@ -225,7 +225,7 @@ func (s *basicGRC1155Token) safeBatchTransferFrom(from, to std.Address, batch [] return ErrCannotTransferToSelf } - caller := std.GetOrigCaller() + caller := std.OrigCaller() s.beforeTokenTransfer(caller, from, to, batch, amounts) for i := 0; i < len(batch); i++ { @@ -265,7 +265,7 @@ func (s *basicGRC1155Token) mintBatch(to std.Address, batch []TokenID, amounts [ return ErrInvalidAddress } - caller := std.GetOrigCaller() + caller := std.OrigCaller() s.beforeTokenTransfer(caller, zeroAddress, to, batch, amounts) for i := 0; i < len(batch); i++ { @@ -294,7 +294,7 @@ func (s *basicGRC1155Token) burnBatch(from std.Address, batch []TokenID, amounts return ErrInvalidAddress } - caller := std.GetOrigCaller() + caller := std.OrigCaller() s.beforeTokenTransfer(caller, from, zeroAddress, batch, amounts) for i := 0; i < len(batch); i++ { diff --git a/examples/gno.land/p/demo/grc/grc1155/basic_grc1155_token_test.gno b/examples/gno.land/p/demo/grc/grc1155/basic_grc1155_token_test.gno index 2fef3431b43..4c1bf5d3828 100644 --- a/examples/gno.land/p/demo/grc/grc1155/basic_grc1155_token_test.gno +++ b/examples/gno.land/p/demo/grc/grc1155/basic_grc1155_token_test.gno @@ -91,7 +91,7 @@ func TestSetApprovalForAll(t *testing.T) { dummy := NewBasicGRC1155Token(dummyURI) uassert.True(t, dummy != nil, "should not be nil") - caller := std.GetOrigCaller() + caller := std.OrigCaller() addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") isApprovedForAll := dummy.IsApprovedForAll(caller, addr) @@ -114,7 +114,7 @@ func TestSafeTransferFrom(t *testing.T) { dummy := NewBasicGRC1155Token(dummyURI) uassert.True(t, dummy != nil, "should not be nil") - caller := std.GetOrigCaller() + caller := std.OrigCaller() addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") tid := TokenID("1") @@ -145,7 +145,7 @@ func TestSafeBatchTransferFrom(t *testing.T) { dummy := NewBasicGRC1155Token(dummyURI) uassert.True(t, dummy != nil, "should not be nil") - caller := std.GetOrigCaller() + caller := std.OrigCaller() addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") tid1 := TokenID("1") diff --git a/examples/gno.land/p/demo/microblog/microblog.gno b/examples/gno.land/p/demo/microblog/microblog.gno index f6d6709f20b..fbc8510ea46 100644 --- a/examples/gno.land/p/demo/microblog/microblog.gno +++ b/examples/gno.land/p/demo/microblog/microblog.gno @@ -48,7 +48,7 @@ func (m *Microblog) GetPages() []*Page { } func (m *Microblog) NewPost(text string) error { - author := std.GetOrigCaller() + author := std.OrigCaller() _, found := m.Pages.Get(author.String()) if !found { // make a new page for the new author diff --git a/examples/gno.land/p/demo/simpledao/dao.gno b/examples/gno.land/p/demo/simpledao/dao.gno index 837f64a41d6..8728d825433 100644 --- a/examples/gno.land/p/demo/simpledao/dao.gno +++ b/examples/gno.land/p/demo/simpledao/dao.gno @@ -56,7 +56,7 @@ func (s *SimpleDAO) Propose(request dao.ProposalRequest) (uint64, error) { var ( caller = getDAOCaller() - sentCoins = std.GetOrigSend() // Get the sent coins, if any + sentCoins = std.OrigSend() // Get the sent coins, if any canCoverFee = sentCoins.AmountOf("ugnot") >= minProposalFee.Amount ) @@ -168,7 +168,7 @@ func (s *SimpleDAO) VoteOnProposal(id uint64, option dao.VoteOption) error { func (s *SimpleDAO) ExecuteProposal(id uint64) error { var ( caller = getDAOCaller() - sentCoins = std.GetOrigSend() // Get the sent coins, if any + sentCoins = std.OrigSend() // Get the sent coins, if any canCoverFee = sentCoins.AmountOf("ugnot") >= minExecuteFee.Amount ) @@ -219,5 +219,5 @@ func (s *SimpleDAO) ExecuteProposal(id uint64) error { // However, the current MsgRun context does not persist escaping the main() scope. // Until a better solution is developed, this enables proposals to be made through a package deployment + init() func getDAOCaller() std.Address { - return std.GetOrigCaller() + return std.OrigCaller() } diff --git a/examples/gno.land/p/demo/subscription/lifetime/lifetime.gno b/examples/gno.land/p/demo/subscription/lifetime/lifetime.gno index be661e70129..f89d2a8be7e 100644 --- a/examples/gno.land/p/demo/subscription/lifetime/lifetime.gno +++ b/examples/gno.land/p/demo/subscription/lifetime/lifetime.gno @@ -26,7 +26,7 @@ func NewLifetimeSubscription(amount int64) *LifetimeSubscription { // processSubscription handles the subscription process for a given receiver. func (ls *LifetimeSubscription) processSubscription(receiver std.Address) error { - amount := std.GetOrigSend() + amount := std.OrigSend() if amount.AmountOf("ugnot") != ls.amount { return ErrAmt diff --git a/examples/gno.land/p/demo/subscription/recurring/recurring.gno b/examples/gno.land/p/demo/subscription/recurring/recurring.gno index 8f116009aa6..da2cdc66b9e 100644 --- a/examples/gno.land/p/demo/subscription/recurring/recurring.gno +++ b/examples/gno.land/p/demo/subscription/recurring/recurring.gno @@ -43,7 +43,7 @@ func (rs *RecurringSubscription) HasValidSubscription(addr std.Address) error { // processSubscription processes the payment for a given receiver and renews or adds their subscription. func (rs *RecurringSubscription) processSubscription(receiver std.Address) error { - amount := std.GetOrigSend() + amount := std.OrigSend() if amount.AmountOf("ugnot") != rs.amount { return ErrAmt diff --git a/examples/gno.land/p/demo/todolist/todolist.gno b/examples/gno.land/p/demo/todolist/todolist.gno index a675344655f..aed3be308ba 100644 --- a/examples/gno.land/p/demo/todolist/todolist.gno +++ b/examples/gno.land/p/demo/todolist/todolist.gno @@ -22,7 +22,7 @@ func NewTodoList(title string) *TodoList { return &TodoList{ Title: title, Tasks: avl.NewTree(), - Owner: std.GetOrigCaller(), + Owner: std.OrigCaller(), } } diff --git a/examples/gno.land/p/demo/todolist/todolist_test.gno b/examples/gno.land/p/demo/todolist/todolist_test.gno index 85836e2a17f..224f6afba47 100644 --- a/examples/gno.land/p/demo/todolist/todolist_test.gno +++ b/examples/gno.land/p/demo/todolist/todolist_test.gno @@ -13,7 +13,7 @@ func TestNewTodoList(t *testing.T) { uassert.Equal(t, title, todoList.GetTodolistTitle()) uassert.Equal(t, 0, len(todoList.GetTasks())) - uassert.Equal(t, std.GetOrigCaller().String(), todoList.GetTodolistOwner().String()) + uassert.Equal(t, std.OrigCaller().String(), todoList.GetTodolistOwner().String()) } func TestNewTask(t *testing.T) { diff --git a/examples/gno.land/r/demo/banktest/README.md b/examples/gno.land/r/demo/banktest/README.md index 944757f9b12..888a53b0742 100644 --- a/examples/gno.land/r/demo/banktest/README.md +++ b/examples/gno.land/r/demo/banktest/README.md @@ -44,7 +44,7 @@ This means that calls to functions defined within this package are encapsulated // Deposit will take the coins (to the realm's pkgaddr) or return them to user. func Deposit(returnDenom string, returnAmount int64) string { std.AssertOriginCall() - caller := std.GetOrigCaller() + caller := std.OrigCaller() send := std.Coins{{returnDenom, returnAmount}} ``` @@ -54,7 +54,7 @@ This is the beginning of the definition of the contract function named "Deposit" // record activity act := &activity{ caller: caller, - sent: std.GetOrigSend(), + sent: std.OrigSend(), returned: send, time: time.Now(), } diff --git a/examples/gno.land/r/demo/banktest/banktest.gno b/examples/gno.land/r/demo/banktest/banktest.gno index 29c479dd025..b2d735431a9 100644 --- a/examples/gno.land/r/demo/banktest/banktest.gno +++ b/examples/gno.land/r/demo/banktest/banktest.gno @@ -24,12 +24,12 @@ var latest [10]*activity // Deposit will take the coins (to the realm's pkgaddr) or return them to user. func Deposit(returnDenom string, returnAmount int64) string { std.AssertOriginCall() - caller := std.GetOrigCaller() + caller := std.OrigCaller() send := std.Coins{{returnDenom, returnAmount}} // record activity act := &activity{ caller: caller, - sent: std.GetOrigSend(), + sent: std.OrigSend(), returned: send, time: time.Now(), } diff --git a/examples/gno.land/r/demo/boards/public.gno b/examples/gno.land/r/demo/boards/public.gno index 1d26126fcb2..4798a11d8f4 100644 --- a/examples/gno.land/r/demo/boards/public.gno +++ b/examples/gno.land/r/demo/boards/public.gno @@ -21,7 +21,7 @@ func CreateBoard(name string) BoardID { panic("invalid non-user call") } bid := incGetBoardID() - caller := std.GetOrigCaller() + caller := std.OrigCaller() if usernameOf(caller) == "" { panic("unauthorized") } @@ -34,7 +34,7 @@ func CreateBoard(name string) BoardID { } func checkAnonFee() bool { - sent := std.GetOrigSend() + sent := std.OrigSend() anonFeeCoin := std.NewCoin("ugnot", int64(gDefaultAnonFee)) if len(sent) == 1 && sent[0].IsGTE(anonFeeCoin) { return true @@ -46,7 +46,7 @@ func CreateThread(bid BoardID, title string, body string) PostID { if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { panic("invalid non-user call") } - caller := std.GetOrigCaller() + caller := std.OrigCaller() if usernameOf(caller) == "" { if !checkAnonFee() { panic("please register, otherwise minimum fee " + strconv.Itoa(gDefaultAnonFee) + " is required if anonymous") @@ -64,7 +64,7 @@ func CreateReply(bid BoardID, threadid, postid PostID, body string) PostID { if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { panic("invalid non-user call") } - caller := std.GetOrigCaller() + caller := std.OrigCaller() if usernameOf(caller) == "" { if !checkAnonFee() { panic("please register, otherwise minimum fee " + strconv.Itoa(gDefaultAnonFee) + " is required if anonymous") @@ -94,7 +94,7 @@ func CreateRepost(bid BoardID, postid PostID, title string, body string, dstBoar if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { panic("invalid non-user call") } - caller := std.GetOrigCaller() + caller := std.OrigCaller() if usernameOf(caller) == "" { // TODO: allow with gDefaultAnonFee payment. if !checkAnonFee() { @@ -124,7 +124,7 @@ func DeletePost(bid BoardID, threadid, postid PostID, reason string) { if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { panic("invalid non-user call") } - caller := std.GetOrigCaller() + caller := std.OrigCaller() board := getBoard(bid) if board == nil { panic("board not exist") @@ -156,7 +156,7 @@ func EditPost(bid BoardID, threadid, postid PostID, title, body string) { if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { panic("invalid non-user call") } - caller := std.GetOrigCaller() + caller := std.OrigCaller() board := getBoard(bid) if board == nil { panic("board not exist") diff --git a/examples/gno.land/r/demo/disperse/disperse.gno b/examples/gno.land/r/demo/disperse/disperse.gno index 0dc833dda95..2ac73c6d566 100644 --- a/examples/gno.land/r/demo/disperse/disperse.gno +++ b/examples/gno.land/r/demo/disperse/disperse.gno @@ -13,7 +13,7 @@ var realmAddr = std.CurrentRealm().Addr() // The function will send out the coins to the addresses and return the leftover coins to the caller // if there are any to return func DisperseUgnot(addresses []std.Address, coins std.Coins) { - coinSent := std.GetOrigSend() + coinSent := std.OrigSend() caller := std.PrevRealm().Addr() banker := std.GetBanker(std.BankerTypeOrigSend) diff --git a/examples/gno.land/r/demo/foo1155/foo1155.gno b/examples/gno.land/r/demo/foo1155/foo1155.gno index 2bd3b7a84c0..c2f86a76619 100644 --- a/examples/gno.land/r/demo/foo1155/foo1155.gno +++ b/examples/gno.land/r/demo/foo1155/foo1155.gno @@ -82,7 +82,7 @@ func BatchTransferFrom(from, to pusers.AddressOrName, batch []grc1155.TokenID, a // Admin func Mint(to pusers.AddressOrName, tid grc1155.TokenID, amount uint64) { - caller := std.GetOrigCaller() + caller := std.OrigCaller() assertIsAdmin(caller) err := foo.SafeMint(users.Resolve(to), tid, amount) if err != nil { @@ -91,7 +91,7 @@ func Mint(to pusers.AddressOrName, tid grc1155.TokenID, amount uint64) { } func MintBatch(to pusers.AddressOrName, batch []grc1155.TokenID, amounts []uint64) { - caller := std.GetOrigCaller() + caller := std.OrigCaller() assertIsAdmin(caller) err := foo.SafeBatchMint(users.Resolve(to), batch, amounts) if err != nil { @@ -100,7 +100,7 @@ func MintBatch(to pusers.AddressOrName, batch []grc1155.TokenID, amounts []uint6 } func Burn(from pusers.AddressOrName, tid grc1155.TokenID, amount uint64) { - caller := std.GetOrigCaller() + caller := std.OrigCaller() assertIsAdmin(caller) err := foo.Burn(users.Resolve(from), tid, amount) if err != nil { @@ -109,7 +109,7 @@ func Burn(from pusers.AddressOrName, tid grc1155.TokenID, amount uint64) { } func BurnBatch(from pusers.AddressOrName, batch []grc1155.TokenID, amounts []uint64) { - caller := std.GetOrigCaller() + caller := std.OrigCaller() assertIsAdmin(caller) err := foo.BatchBurn(users.Resolve(from), batch, amounts) if err != nil { diff --git a/examples/gno.land/r/demo/groups/public.gno b/examples/gno.land/r/demo/groups/public.gno index 33e7dbdcf35..2d3d164eae7 100644 --- a/examples/gno.land/r/demo/groups/public.gno +++ b/examples/gno.land/r/demo/groups/public.gno @@ -19,7 +19,7 @@ func GetGroupIDFromName(name string) (GroupID, bool) { func CreateGroup(name string) GroupID { std.AssertOriginCall() - caller := std.GetOrigCaller() + caller := std.OrigCaller() usernameOf(caller) url := "/r/demo/groups:" + name group := newGroup(url, name, caller) @@ -31,7 +31,7 @@ func CreateGroup(name string) GroupID { func AddMember(gid GroupID, address string, weight int, metadata string) MemberID { std.AssertOriginCall() - caller := std.GetOrigCaller() + caller := std.OrigCaller() usernameOf(caller) group := getGroup(gid) if !group.HasPermission(caller, EditPermission) { @@ -52,7 +52,7 @@ func AddMember(gid GroupID, address string, weight int, metadata string) MemberI func DeleteGroup(gid GroupID) { std.AssertOriginCall() - caller := std.GetOrigCaller() + caller := std.OrigCaller() group := getGroup(gid) if !group.HasPermission(caller, DeletePermission) { panic("unauthorized to delete group") @@ -62,7 +62,7 @@ func DeleteGroup(gid GroupID) { func DeleteMember(gid GroupID, mid MemberID) { std.AssertOriginCall() - caller := std.GetOrigCaller() + caller := std.OrigCaller() group := getGroup(gid) if !group.HasPermission(caller, DeletePermission) { panic("unauthorized to delete member") diff --git a/examples/gno.land/r/demo/groups/z_1_a_filetest.gno b/examples/gno.land/r/demo/groups/z_1_a_filetest.gno index 71da1b966ec..5949f97dfa6 100644 --- a/examples/gno.land/r/demo/groups/z_1_a_filetest.gno +++ b/examples/gno.land/r/demo/groups/z_1_a_filetest.gno @@ -16,7 +16,7 @@ var gid groups.GroupID const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") func main() { - caller := std.GetOrigCaller() // main + caller := std.OrigCaller() // main users.Register("", "gnouser0", "my profile 1") std.TestSetOrigCaller(admin) diff --git a/examples/gno.land/r/demo/groups/z_2_a_filetest.gno b/examples/gno.land/r/demo/groups/z_2_a_filetest.gno index 0c482e1b52f..9254d13ac55 100644 --- a/examples/gno.land/r/demo/groups/z_2_a_filetest.gno +++ b/examples/gno.land/r/demo/groups/z_2_a_filetest.gno @@ -16,7 +16,7 @@ var gid groups.GroupID const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") func main() { - caller := std.GetOrigCaller() // main + caller := std.OrigCaller() // main users.Register("", "gnouser0", "my profile 1") std.TestSetOrigCaller(admin) diff --git a/examples/gno.land/r/demo/keystore/keystore.gno b/examples/gno.land/r/demo/keystore/keystore.gno index 5c76ccd90f8..c76923089b1 100644 --- a/examples/gno.land/r/demo/keystore/keystore.gno +++ b/examples/gno.land/r/demo/keystore/keystore.gno @@ -33,14 +33,14 @@ type KeyStore struct { // Set will set a value to a key // requires write-access (original caller must be caller) func Set(k, v string) string { - origOwner := std.GetOrigCaller() + origOwner := std.OrigCaller() return set(origOwner.String(), k, v) } // set (private) will set a key to value // requires write-access (original caller must be caller) func set(owner, k, v string) string { - origOwner := std.GetOrigCaller() + origOwner := std.OrigCaller() if origOwner.String() != owner { return StatusNoWriteAccess } @@ -62,14 +62,14 @@ func set(owner, k, v string) string { // Remove removes a key // requires write-access (original owner must be caller) func Remove(k string) string { - origOwner := std.GetOrigCaller() + origOwner := std.OrigCaller() return remove(origOwner.String(), k) } // remove (private) removes a key // requires write-access (original owner must be caller) func remove(owner, k string) string { - origOwner := std.GetOrigCaller() + origOwner := std.OrigCaller() if origOwner.String() != owner { return StatusNoWriteAccess } @@ -94,7 +94,7 @@ func remove(owner, k string) string { // Get returns a value for a key // read-only func Get(k string) string { - origOwner := std.GetOrigCaller() + origOwner := std.OrigCaller() return remove(origOwner.String(), k) } @@ -116,7 +116,7 @@ func get(owner, k string) string { // Size returns size of database // read-only func Size() string { - origOwner := std.GetOrigCaller() + origOwner := std.OrigCaller() return size(origOwner.String()) } diff --git a/examples/gno.land/r/demo/microblog/microblog.gno b/examples/gno.land/r/demo/microblog/microblog.gno index 1c3cd5e7d68..725c0e2ad31 100644 --- a/examples/gno.land/r/demo/microblog/microblog.gno +++ b/examples/gno.land/r/demo/microblog/microblog.gno @@ -91,7 +91,7 @@ func NewPost(text string) string { } func Register(name, profile string) string { - caller := std.GetOrigCaller() // main + caller := std.OrigCaller() // main users.Register(caller, name, profile) return "OK" } diff --git a/examples/gno.land/r/demo/releases_example/example.gno b/examples/gno.land/r/demo/releases_example/example.gno index a2713687d28..3f5c39d7236 100644 --- a/examples/gno.land/r/demo/releases_example/example.gno +++ b/examples/gno.land/r/demo/releases_example/example.gno @@ -17,7 +17,7 @@ func init() { } func NewRelease(name, url, notes string) { - caller := std.GetOrigCaller() + caller := std.OrigCaller() if caller != admin { panic("restricted area") } @@ -25,7 +25,7 @@ func NewRelease(name, url, notes string) { } func UpdateAdmin(address std.Address) { - caller := std.GetOrigCaller() + caller := std.OrigCaller() if caller != admin { panic("restricted area") } diff --git a/examples/gno.land/r/demo/tests/tests.gno b/examples/gno.land/r/demo/tests/tests.gno index e7fde94ea08..b0c4c3dbae6 100644 --- a/examples/gno.land/r/demo/tests/tests.gno +++ b/examples/gno.land/r/demo/tests/tests.gno @@ -21,7 +21,7 @@ func CurrentRealmPath() string { return std.CurrentRealm().PkgPath() } -var initOrigCaller = std.GetOrigCaller() +var initOrigCaller = std.OrigCaller() func InitOrigCaller() std.Address { return initOrigCaller diff --git a/examples/gno.land/r/demo/todolist/todolist_test.gno b/examples/gno.land/r/demo/todolist/todolist_test.gno index 6446732df3e..999ec0a8098 100644 --- a/examples/gno.land/r/demo/todolist/todolist_test.gno +++ b/examples/gno.land/r/demo/todolist/todolist_test.gno @@ -26,7 +26,7 @@ func TestNewTodoList(t *testing.T) { uassert.Equal(t, title, tdl.Title, "title does not match") uassert.Equal(t, 1, tlid, "tlid does not match") - uassert.Equal(t, tdl.Owner.String(), std.GetOrigCaller().String(), "owner does not match") + uassert.Equal(t, tdl.Owner.String(), std.OrigCaller().String(), "owner does not match") uassert.Equal(t, 0, len(tdl.GetTasks()), "Expected no tasks in the todo list") } diff --git a/examples/gno.land/r/demo/users/users.gno b/examples/gno.land/r/demo/users/users.gno index 8547a6e60e0..5205a6d3332 100644 --- a/examples/gno.land/r/demo/users/users.gno +++ b/examples/gno.land/r/demo/users/users.gno @@ -35,11 +35,11 @@ func Register(inviter std.Address, name string, profile string) { std.AssertOriginCall() // assert invited or paid. caller := std.GetCallerAt(2) - if caller != std.GetOrigCaller() { + if caller != std.OrigCaller() { panic("should not happen") // because std.AssertOrigCall(). } - sentCoins := std.GetOrigSend() + sentCoins := std.OrigSend() minCoin := std.NewCoin("ugnot", minFee) if inviter == "" { @@ -119,7 +119,7 @@ func Invite(invitee string) { std.AssertOriginCall() // get caller/inviter. caller := std.GetCallerAt(2) - if caller != std.GetOrigCaller() { + if caller != std.OrigCaller() { panic("should not happen") // because std.AssertOrigCall(). } lines := strings.Split(invitee, "\n") @@ -158,7 +158,7 @@ func GrantInvites(invites string) { std.AssertOriginCall() // assert admin. caller := std.GetCallerAt(2) - if caller != std.GetOrigCaller() { + if caller != std.OrigCaller() { panic("should not happen") // because std.AssertOrigCall(). } if caller != admin { @@ -278,7 +278,7 @@ func AdminAddRestrictedName(name string) { // assert CallTx call. std.AssertOriginCall() // get caller - caller := std.GetOrigCaller() + caller := std.OrigCaller() // assert admin if caller != admin { panic("unauthorized") diff --git a/examples/gno.land/r/demo/users/z_10_filetest.gno b/examples/gno.land/r/demo/users/z_10_filetest.gno index afeecffcc42..c2d7d9f67f1 100644 --- a/examples/gno.land/r/demo/users/z_10_filetest.gno +++ b/examples/gno.land/r/demo/users/z_10_filetest.gno @@ -11,7 +11,7 @@ import ( const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") func init() { - caller := std.GetOrigCaller() // main + caller := std.OrigCaller() // main test2 := testutils.TestAddress("test2") // as admin, invite gnouser and test2 std.TestSetOrigCaller(admin) diff --git a/examples/gno.land/r/demo/users/z_11_filetest.gno b/examples/gno.land/r/demo/users/z_11_filetest.gno index 27c7e9813da..b8da48e3887 100644 --- a/examples/gno.land/r/demo/users/z_11_filetest.gno +++ b/examples/gno.land/r/demo/users/z_11_filetest.gno @@ -11,7 +11,7 @@ import ( const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") func main() { - caller := std.GetOrigCaller() // main + caller := std.OrigCaller() // main std.TestSetOrigCaller(admin) users.AdminAddRestrictedName("superrestricted") diff --git a/examples/gno.land/r/demo/users/z_11b_filetest.gno b/examples/gno.land/r/demo/users/z_11b_filetest.gno index be508963911..350952f3a8d 100644 --- a/examples/gno.land/r/demo/users/z_11b_filetest.gno +++ b/examples/gno.land/r/demo/users/z_11b_filetest.gno @@ -11,7 +11,7 @@ import ( const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") func main() { - caller := std.GetOrigCaller() // main + caller := std.OrigCaller() // main std.TestSetOrigCaller(admin) // add restricted name users.AdminAddRestrictedName("superrestricted") diff --git a/examples/gno.land/r/demo/users/z_2_filetest.gno b/examples/gno.land/r/demo/users/z_2_filetest.gno index c1b92790f8b..9d25bbd989b 100644 --- a/examples/gno.land/r/demo/users/z_2_filetest.gno +++ b/examples/gno.land/r/demo/users/z_2_filetest.gno @@ -12,7 +12,7 @@ import ( const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") func main() { - caller := std.GetOrigCaller() // main + caller := std.OrigCaller() // main users.Register("", "gnouser", "my profile") // as admin, grant invites to gnouser std.TestSetOrigCaller(admin) diff --git a/examples/gno.land/r/demo/users/z_3_filetest.gno b/examples/gno.land/r/demo/users/z_3_filetest.gno index 5402235e03d..15bbea39f35 100644 --- a/examples/gno.land/r/demo/users/z_3_filetest.gno +++ b/examples/gno.land/r/demo/users/z_3_filetest.gno @@ -12,7 +12,7 @@ import ( const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") func main() { - caller := std.GetOrigCaller() // main + caller := std.OrigCaller() // main users.Register("", "gnouser", "my profile") // as admin, grant invites to gnouser std.TestSetOrigCaller(admin) diff --git a/examples/gno.land/r/demo/users/z_4_filetest.gno b/examples/gno.land/r/demo/users/z_4_filetest.gno index 613fadf9625..30d582a1fb6 100644 --- a/examples/gno.land/r/demo/users/z_4_filetest.gno +++ b/examples/gno.land/r/demo/users/z_4_filetest.gno @@ -12,7 +12,7 @@ import ( const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") func main() { - caller := std.GetOrigCaller() // main + caller := std.OrigCaller() // main users.Register("", "gnouser", "my profile") // as admin, grant invites to gnouser std.TestSetOrigCaller(admin) diff --git a/examples/gno.land/r/demo/users/z_5_filetest.gno b/examples/gno.land/r/demo/users/z_5_filetest.gno index 6465cc9c378..6c921f7eb8a 100644 --- a/examples/gno.land/r/demo/users/z_5_filetest.gno +++ b/examples/gno.land/r/demo/users/z_5_filetest.gno @@ -12,7 +12,7 @@ import ( const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") func main() { - caller := std.GetOrigCaller() // main + caller := std.OrigCaller() // main users.Register("", "gnouser", "my profile") // as admin, grant invites to gnouser std.TestSetOrigCaller(admin) diff --git a/examples/gno.land/r/demo/users/z_6_filetest.gno b/examples/gno.land/r/demo/users/z_6_filetest.gno index 919088088a2..329c52cdda4 100644 --- a/examples/gno.land/r/demo/users/z_6_filetest.gno +++ b/examples/gno.land/r/demo/users/z_6_filetest.gno @@ -9,7 +9,7 @@ import ( const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") func main() { - caller := std.GetOrigCaller() + caller := std.OrigCaller() // as admin, grant invites to unregistered user. std.TestSetOrigCaller(admin) users.GrantInvites(caller.String() + ":1") diff --git a/examples/gno.land/r/demo/users/z_7_filetest.gno b/examples/gno.land/r/demo/users/z_7_filetest.gno index 1d3c9e3a917..a68ab255f24 100644 --- a/examples/gno.land/r/demo/users/z_7_filetest.gno +++ b/examples/gno.land/r/demo/users/z_7_filetest.gno @@ -12,7 +12,7 @@ import ( const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") func main() { - caller := std.GetOrigCaller() // main + caller := std.OrigCaller() // main users.Register("", "gnouser", "my profile") // as admin, grant invites to gnouser std.TestSetOrigCaller(admin) diff --git a/examples/gno.land/r/demo/users/z_7b_filetest.gno b/examples/gno.land/r/demo/users/z_7b_filetest.gno index 09c15bb135d..7fdf9fd1daf 100644 --- a/examples/gno.land/r/demo/users/z_7b_filetest.gno +++ b/examples/gno.land/r/demo/users/z_7b_filetest.gno @@ -12,7 +12,7 @@ import ( const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") func main() { - caller := std.GetOrigCaller() // main + caller := std.OrigCaller() // main users.Register("", "gnouser", "my profile") // as admin, grant invites to gnouser std.TestSetOrigCaller(admin) diff --git a/examples/gno.land/r/demo/users/z_8_filetest.gno b/examples/gno.land/r/demo/users/z_8_filetest.gno index 78fada74a71..01c45cb7ec6 100644 --- a/examples/gno.land/r/demo/users/z_8_filetest.gno +++ b/examples/gno.land/r/demo/users/z_8_filetest.gno @@ -12,7 +12,7 @@ import ( const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") func main() { - caller := std.GetOrigCaller() // main + caller := std.OrigCaller() // main users.Register("", "gnouser", "my profile") // as admin, grant invites to gnouser std.TestSetOrigCaller(admin) diff --git a/examples/gno.land/r/demo/users/z_9_filetest.gno b/examples/gno.land/r/demo/users/z_9_filetest.gno index c73c685aebd..35a60722a26 100644 --- a/examples/gno.land/r/demo/users/z_9_filetest.gno +++ b/examples/gno.land/r/demo/users/z_9_filetest.gno @@ -10,7 +10,7 @@ import ( const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") func main() { - caller := std.GetOrigCaller() // main + caller := std.OrigCaller() // main test2 := testutils.TestAddress("test2") // as admin, invite gnouser and test2 std.TestSetOrigCaller(admin) diff --git a/examples/gno.land/r/demo/wugnot/wugnot.gno b/examples/gno.land/r/demo/wugnot/wugnot.gno index 09538b860ca..a573d4696fe 100644 --- a/examples/gno.land/r/demo/wugnot/wugnot.gno +++ b/examples/gno.land/r/demo/wugnot/wugnot.gno @@ -25,7 +25,7 @@ func init() { func Deposit() { caller := std.PrevRealm().Addr() - sent := std.GetOrigSend() + sent := std.OrigSend() amount := sent.AmountOf("ugnot") require(uint64(amount) >= ugnotMinDeposit, ufmt.Sprintf("Deposit below minimum: %d/%d ugnot.", amount, ugnotMinDeposit)) diff --git a/examples/gno.land/r/gnoland/blog/admin.gno b/examples/gno.land/r/gnoland/blog/admin.gno index 87d465449f3..e8bfaff9af4 100644 --- a/examples/gno.land/r/gnoland/blog/admin.gno +++ b/examples/gno.land/r/gnoland/blog/admin.gno @@ -17,7 +17,7 @@ var ( ) func init() { - // adminAddr = std.GetOrigCaller() // FIXME: find a way to use this from the main's genesis. + // adminAddr = std.OrigCaller() // FIXME: find a way to use this from the main's genesis. adminAddr = "g1manfred47kzduec920z88wfr64ylksmdcedlf5" // @moul } @@ -53,7 +53,7 @@ func NewPostExecutor(slug, title, body, publicationDate, authors, tags string) d func ModAddPost(slug, title, body, publicationDate, authors, tags string) { assertIsModerator() - caller := std.GetOrigCaller() + caller := std.OrigCaller() addPost(caller, slug, title, body, publicationDate, authors, tags) } @@ -120,14 +120,14 @@ func isCommenter(addr std.Address) bool { } func assertIsAdmin() { - caller := std.GetOrigCaller() + caller := std.OrigCaller() if !isAdmin(caller) { panic("access restricted.") } } func assertIsModerator() { - caller := std.GetOrigCaller() + caller := std.OrigCaller() if isAdmin(caller) || isModerator(caller) { return } @@ -135,7 +135,7 @@ func assertIsModerator() { } func assertIsCommenter() { - caller := std.GetOrigCaller() + caller := std.OrigCaller() if isAdmin(caller) || isModerator(caller) || isCommenter(caller) { return } diff --git a/examples/gno.land/r/gnoland/blog/gnoblog.gno b/examples/gno.land/r/gnoland/blog/gnoblog.gno index d2a163543e5..ab40da125e0 100644 --- a/examples/gno.land/r/gnoland/blog/gnoblog.gno +++ b/examples/gno.land/r/gnoland/blog/gnoblog.gno @@ -15,7 +15,7 @@ func AddComment(postSlug, comment string) { assertIsCommenter() assertNotInPause() - caller := std.GetOrigCaller() + caller := std.OrigCaller() err := b.GetPost(postSlug).AddComment(caller, comment) checkErr(err) } diff --git a/examples/gno.land/r/gnoland/blog/gnoblog_test.gno b/examples/gno.land/r/gnoland/blog/gnoblog_test.gno index b4658db4fb5..9d1ecd798d7 100644 --- a/examples/gno.land/r/gnoland/blog/gnoblog_test.gno +++ b/examples/gno.land/r/gnoland/blog/gnoblog_test.gno @@ -9,7 +9,7 @@ import ( func TestPackage(t *testing.T) { std.TestSetOrigCaller(std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5")) - author := std.GetOrigCaller() + author := std.OrigCaller() // by default, no posts. { diff --git a/examples/gno.land/r/gnoland/faucet/admin.gno b/examples/gno.land/r/gnoland/faucet/admin.gno index 37108059b74..2da0b6f42be 100644 --- a/examples/gno.land/r/gnoland/faucet/admin.gno +++ b/examples/gno.land/r/gnoland/faucet/admin.gno @@ -79,7 +79,7 @@ func AdminRemoveController(addr std.Address) string { } func assertIsAdmin() error { - caller := std.GetOrigCaller() + caller := std.OrigCaller() if caller != gAdminAddr { return errors.New("restricted for admin") } diff --git a/examples/gno.land/r/gnoland/faucet/faucet.gno b/examples/gno.land/r/gnoland/faucet/faucet.gno index 908b86d4aaf..c4f47fb9208 100644 --- a/examples/gno.land/r/gnoland/faucet/faucet.gno +++ b/examples/gno.land/r/gnoland/faucet/faucet.gno @@ -81,7 +81,7 @@ func Render(_ string) string { } func assertIsController() error { - caller := std.GetOrigCaller() + caller := std.OrigCaller() ok := gControllers.Has(caller.String()) if !ok { diff --git a/examples/gno.land/r/gnoland/ghverify/contract.gno b/examples/gno.land/r/gnoland/ghverify/contract.gno index 3b8f7fcbbe1..38567620a6b 100644 --- a/examples/gno.land/r/gnoland/ghverify/contract.gno +++ b/examples/gno.land/r/gnoland/ghverify/contract.gno @@ -16,7 +16,7 @@ const ( ) var ( - ownerAddress = std.GetOrigCaller() + ownerAddress = std.OrigCaller() oracle *gnorkle.Instance postHandler postGnorkleMessageHandler @@ -70,7 +70,7 @@ func (h postGnorkleMessageHandler) Handle(i *gnorkle.Instance, funcType message. // RequestVerification creates a new static feed with a single task that will // instruct an agent to verify the github handle / gno address pair. func RequestVerification(githubHandle string) { - gnoAddress := string(std.GetOrigCaller()) + gnoAddress := string(std.OrigCaller()) if err := oracle.AddFeeds( static.NewSingleValueFeed( gnoAddress, @@ -102,7 +102,7 @@ func GnorkleEntrypoint(message string) string { // SetOwner transfers ownership of the contract to the given address. func SetOwner(owner std.Address) { - if ownerAddress != std.GetOrigCaller() { + if ownerAddress != std.OrigCaller() { panic("only the owner can set a new owner") } diff --git a/examples/gno.land/r/gnoland/ghverify/contract_test.gno b/examples/gno.land/r/gnoland/ghverify/contract_test.gno index 5c0be0afcb1..a6a40c02e81 100644 --- a/examples/gno.land/r/gnoland/ghverify/contract_test.gno +++ b/examples/gno.land/r/gnoland/ghverify/contract_test.gno @@ -8,7 +8,7 @@ import ( ) func TestVerificationLifecycle(t *testing.T) { - defaultAddress := std.GetOrigCaller() + defaultAddress := std.OrigCaller() user1Address := std.Address(testutils.TestAddress("user 1")) user2Address := std.Address(testutils.TestAddress("user 2")) diff --git a/examples/gno.land/r/gnoland/pages/admin.gno b/examples/gno.land/r/gnoland/pages/admin.gno index 71050f4ef57..894e76a41e7 100644 --- a/examples/gno.land/r/gnoland/pages/admin.gno +++ b/examples/gno.land/r/gnoland/pages/admin.gno @@ -14,7 +14,7 @@ var ( ) func init() { - // adminAddr = std.GetOrigCaller() // FIXME: find a way to use this from the main's genesis. + // adminAddr = std.OrigCaller() // FIXME: find a way to use this from the main's genesis. adminAddr = "g1manfred47kzduec920z88wfr64ylksmdcedlf5" // @moul } @@ -41,7 +41,7 @@ func AdminRemoveModerator(addr std.Address) { func ModAddPost(slug, title, body, publicationDate, authors, tags string) { assertIsModerator() - caller := std.GetOrigCaller() + caller := std.OrigCaller() tagList := strings.Split(tags, ",") authorList := strings.Split(authors, ",") @@ -69,14 +69,14 @@ func isModerator(addr std.Address) bool { } func assertIsAdmin() { - caller := std.GetOrigCaller() + caller := std.OrigCaller() if !isAdmin(caller) { panic("access restricted.") } } func assertIsModerator() { - caller := std.GetOrigCaller() + caller := std.OrigCaller() if isAdmin(caller) || isModerator(caller) { return } diff --git a/examples/gno.land/r/gnoland/valopers/v2/valopers.gno b/examples/gno.land/r/gnoland/valopers/v2/valopers.gno index d88ea4b872f..93bc24c1aa0 100644 --- a/examples/gno.land/r/gnoland/valopers/v2/valopers.gno +++ b/examples/gno.land/r/gnoland/valopers/v2/valopers.gno @@ -148,7 +148,7 @@ func GovDAOProposal(address std.Address) { ) // Make sure the valoper is the caller - if std.GetOrigCaller() != address { + if std.OrigCaller() != address { panic(errValoperNotCaller) } diff --git a/examples/gno.land/r/matijamarjanovic/home/config.gno b/examples/gno.land/r/matijamarjanovic/home/config.gno index 2a9669c0b58..49d62077daf 100644 --- a/examples/gno.land/r/matijamarjanovic/home/config.gno +++ b/examples/gno.land/r/matijamarjanovic/home/config.gno @@ -48,7 +48,7 @@ func SetBackup(newAddress std.Address) error { } func checkAuthorized() error { - caller := std.GetOrigCaller() + caller := std.OrigCaller() if caller != mainAddr && caller != backupAddr { return errorUnauthorized } @@ -57,7 +57,7 @@ func checkAuthorized() error { } func AssertAuthorized() { - caller := std.GetOrigCaller() + caller := std.OrigCaller() if caller != mainAddr && caller != backupAddr { panic(errorUnauthorized) } diff --git a/examples/gno.land/r/matijamarjanovic/home/home.gno b/examples/gno.land/r/matijamarjanovic/home/home.gno index 3757324108a..78ac494da0e 100644 --- a/examples/gno.land/r/matijamarjanovic/home/home.gno +++ b/examples/gno.land/r/matijamarjanovic/home/home.gno @@ -71,21 +71,21 @@ func maxOfThree(a, b, c int64) int64 { } func VoteModern() { - ugnotAmount := std.GetOrigSend().AmountOf("ugnot") + ugnotAmount := std.OrigSend().AmountOf("ugnot") votes := ugnotAmount modernVotes += votes updateCurrentTheme() } func VoteClassic() { - ugnotAmount := std.GetOrigSend().AmountOf("ugnot") + ugnotAmount := std.OrigSend().AmountOf("ugnot") votes := ugnotAmount classicVotes += votes updateCurrentTheme() } func VoteMinimal() { - ugnotAmount := std.GetOrigSend().AmountOf("ugnot") + ugnotAmount := std.OrigSend().AmountOf("ugnot") votes := ugnotAmount minimalVotes += votes updateCurrentTheme() diff --git a/examples/gno.land/r/moul/config/config.gno b/examples/gno.land/r/moul/config/config.gno index a4f24411747..203f7ce680b 100644 --- a/examples/gno.land/r/moul/config/config.gno +++ b/examples/gno.land/r/moul/config/config.gno @@ -14,7 +14,7 @@ func UpdateAddr(newAddr std.Address) { } func AssertIsAdmin() { - if std.GetOrigCaller() != addr { + if std.OrigCaller() != addr { panic("restricted area") } } diff --git a/examples/gno.land/r/moul/present/admin.gno b/examples/gno.land/r/moul/present/admin.gno index ab99b1725c5..9e32cfa7624 100644 --- a/examples/gno.land/r/moul/present/admin.gno +++ b/examples/gno.land/r/moul/present/admin.gno @@ -14,7 +14,7 @@ var ( ) func init() { - // adminAddr = std.GetOrigCaller() // FIXME: find a way to use this from the main's genesis. + // adminAddr = std.OrigCaller() // FIXME: find a way to use this from the main's genesis. adminAddr = "g1manfred47kzduec920z88wfr64ylksmdcedlf5" } @@ -41,7 +41,7 @@ func AdminRemoveModerator(addr std.Address) { func ModAddPost(slug, title, body, publicationDate, authors, tags string) { assertIsModerator() - caller := std.GetOrigCaller() + caller := std.OrigCaller() tagList := strings.Split(tags, ",") authorList := strings.Split(authors, ",") @@ -69,14 +69,14 @@ func isModerator(addr std.Address) bool { } func assertIsAdmin() { - caller := std.GetOrigCaller() + caller := std.OrigCaller() if !isAdmin(caller) { panic("access restricted.") } } func assertIsModerator() { - caller := std.GetOrigCaller() + caller := std.OrigCaller() if isAdmin(caller) || isModerator(caller) { return } diff --git a/examples/gno.land/r/stefann/home/home.gno b/examples/gno.land/r/stefann/home/home.gno index 9586f377311..2d3a736793d 100644 --- a/examples/gno.land/r/stefann/home/home.gno +++ b/examples/gno.land/r/stefann/home/home.gno @@ -131,8 +131,8 @@ func UpdateMaxSponsors(newMax int) { } func Donate() { - address := std.GetOrigCaller() - amount := std.GetOrigSend() + address := std.OrigCaller() + amount := std.OrigSend() if amount.AmountOf("ugnot") == 0 { panic("Donation must include GNOT") diff --git a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v1/v1.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v1/v1.gno index 1298b2539be..f7a1d828e38 100644 --- a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v1/v1.gno +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v1/v1.gno @@ -25,7 +25,7 @@ func SetNextVersion(addr string) { std.AssertOriginCall() // assert admin. caller := std.GetCallerAt(2) - if caller != std.GetOrigCaller() { + if caller != std.OrigCaller() { panic("should not happen") // because std.AssertOrigCall(). } if caller != admin { diff --git a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v2/v2.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v2/v2.gno index bf30ee1acab..3b7b18ab1d1 100644 --- a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v2/v2.gno +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v2/v2.gno @@ -29,7 +29,7 @@ func SetNextVersion(addr string) { std.AssertOriginCall() // assert admin. caller := std.GetCallerAt(2) - if caller != std.GetOrigCaller() { + if caller != std.OrigCaller() { panic("should not happen") // because std.AssertOrigCall(). } if caller != admin { diff --git a/examples/gno.land/r/x/nir1218_evaluation_proposal/committee.gno b/examples/gno.land/r/x/nir1218_evaluation_proposal/committee.gno index 1ec801bb971..316099db3dc 100644 --- a/examples/gno.land/r/x/nir1218_evaluation_proposal/committee.gno +++ b/examples/gno.land/r/x/nir1218_evaluation_proposal/committee.gno @@ -36,7 +36,7 @@ func (c *Committee) DismissMembers(members []std.Address) []std.Address { func (c *Committee) AddCategory(name string, criteria []string) bool { // TODO error handling - if !c.isMember(std.GetOrigCaller()) { + if !c.isMember(std.OrigCaller()) { return false } category := NewCategory(name, criteria) @@ -45,7 +45,7 @@ func (c *Committee) AddCategory(name string, criteria []string) bool { } func (c *Committee) ApproveCategory(name string, option string) bool { - if !c.isMember(std.GetOrigCaller()) { + if !c.isMember(std.OrigCaller()) { return false } @@ -58,8 +58,8 @@ func (c *Committee) ApproveCategory(name string, option string) bool { return false } - vote := NewVote(std.GetOrigCaller(), option) - category.votes.Set(std.GetOrigCaller().String(), vote) + vote := NewVote(std.OrigCaller(), option) + category.votes.Set(std.OrigCaller().String(), vote) category.Tally() // TODO Add threshold factor for a category approval @@ -81,7 +81,7 @@ func (c *Committee) ApproveCategory(name string, option string) bool { // TODO error handling func (c *Committee) AddContribution(pr *PullRequest, contributor std.Address) (contributionId int, ok bool) { - if !c.isMember(std.GetOrigCaller()) { + if !c.isMember(std.OrigCaller()) { return -1, false } // Check the category of the PR matches a category this committee evaluates @@ -95,7 +95,7 @@ func (c *Committee) AddContribution(pr *PullRequest, contributor std.Address) (c // TODO error handling func (c *Committee) ApproveContribution(id int, option string) bool { - if !c.isMember(std.GetOrigCaller()) { + if !c.isMember(std.OrigCaller()) { return false } @@ -109,7 +109,7 @@ func (c *Committee) ApproveContribution(id int, option string) bool { return false } - vote := NewVote(std.GetOrigCaller(), option) + vote := NewVote(std.OrigCaller(), option) contribution.votes = append(contribution.votes, vote) contribution.Tally() diff --git a/gno.land/cmd/gnoland/testdata/initctx.txtar b/gno.land/cmd/gnoland/testdata/initctx.txtar index 9210268e66f..64811459365 100644 --- a/gno.land/cmd/gnoland/testdata/initctx.txtar +++ b/gno.land/cmd/gnoland/testdata/initctx.txtar @@ -18,7 +18,7 @@ var orig = std.Address("orig") var prev = std.Address("prev") func init() { - orig = std.GetOrigCaller() + orig = std.OrigCaller() prev = std.PrevRealm().Addr() } diff --git a/gno.land/cmd/gnoland/testdata/issue_1786.txtar b/gno.land/cmd/gnoland/testdata/issue_1786.txtar index 0e66a882a6d..1c60e84f8fa 100644 --- a/gno.land/cmd/gnoland/testdata/issue_1786.txtar +++ b/gno.land/cmd/gnoland/testdata/issue_1786.txtar @@ -50,7 +50,7 @@ import ( ) func ProxyWrap() { - sent := std.GetOrigSend() + sent := std.OrigSend() ugnotSent := uint64(sent.AmountOf("ugnot")) if ugnotSent == 0 { @@ -64,7 +64,7 @@ func ProxyWrap() { wugnot.Deposit() // `proxywugnot` has ugnot // SEND WUGNOT: PROXY_WUGNOT -> USER - wugnot.Transfer(pusers.AddressOrName(std.GetOrigCaller()), ugnotSent) + wugnot.Transfer(pusers.AddressOrName(std.OrigCaller()), ugnotSent) } func ProxyUnwrap(wugnotAmount uint64) { @@ -73,12 +73,12 @@ func ProxyUnwrap(wugnotAmount uint64) { } // SEND WUGNOT: USER -> PROXY_WUGNOT - wugnot.TransferFrom(pusers.AddressOrName(std.GetOrigCaller()), pusers.AddressOrName(std.CurrentRealm().Addr()), wugnotAmount) + wugnot.TransferFrom(pusers.AddressOrName(std.OrigCaller()), pusers.AddressOrName(std.CurrentRealm().Addr()), wugnotAmount) // UNWRAP IT wugnot.Withdraw(wugnotAmount) // SEND GNOT: PROXY_WUGNOT -> USER banker := std.GetBanker(std.BankerTypeRealmSend) - banker.SendCoins(std.CurrentRealm().Addr(), std.GetOrigCaller(), std.Coins{{"ugnot", int64(wugnotAmount)}}) + banker.SendCoins(std.CurrentRealm().Addr(), std.OrigCaller(), std.Coins{{"ugnot", int64(wugnotAmount)}}) } diff --git a/gno.land/cmd/gnoland/testdata/issue_2283.txtar b/gno.land/cmd/gnoland/testdata/issue_2283.txtar index 653a4dd79b0..4f3e51c3e29 100644 --- a/gno.land/cmd/gnoland/testdata/issue_2283.txtar +++ b/gno.land/cmd/gnoland/testdata/issue_2283.txtar @@ -66,7 +66,7 @@ import ( }, { "Name": "post.gno", - "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n)\n\ntype PostID uint64\n\nfunc (pid PostID) String() string {\n\treturn strconv.Itoa(int(pid))\n}\n\nfunc (pid *PostID) FromJSON(ast *ujson.JSONASTNode) {\n\tval, err := strconv.Atoi(ast.Value)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t*pid = PostID(val)\n}\n\nfunc (pid PostID) ToJSON() string {\n\treturn strconv.Itoa(int(pid))\n}\n\ntype Reaction struct {\n\ticon string\n\tcount uint64\n}\n\nvar Categories []string = []string{\n\t\"Reaction\",\n\t\"Comment\",\n\t\"Normal\",\n\t\"Article\",\n\t\"Picture\",\n\t\"Audio\",\n\t\"Video\",\n}\n\ntype Post struct {\n\tid PostID\n\tparentID PostID\n\tfeedID FeedID\n\tcategory uint64\n\tmetadata string\n\treactions avl.Tree // icon -> count\n\tcomments avl.Tree // Post.id -> *Post\n\tcreator std.Address\n\ttipAmount uint64\n\tdeleted bool\n\tcommentsCount uint64\n\n\tcreatedAt int64\n\tupdatedAt int64\n\tdeletedAt int64\n}\n\nfunc newPost(feed *Feed, id PostID, creator std.Address, parentID PostID, category uint64, metadata string) *Post {\n\treturn &Post{\n\t\tid: id,\n\t\tparentID: parentID,\n\t\tfeedID: feed.id,\n\t\tcategory: category,\n\t\tmetadata: metadata,\n\t\treactions: avl.Tree{},\n\t\tcreator: creator,\n\t\tcreatedAt: time.Now().Unix(),\n\t}\n}\n\nfunc (post *Post) String() string {\n\treturn post.ToJSON()\n}\n\nfunc (post *Post) Update(category uint64, metadata string) {\n\tpost.category = category\n\tpost.metadata = metadata\n\tpost.updatedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Delete() {\n\tpost.deleted = true\n\tpost.deletedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Tip(from std.Address, to std.Address) {\n\treceivedCoins := std.GetOrigSend()\n\tamount := receivedCoins[0].Amount\n\n\tbanker := std.GetBanker(std.BankerTypeOrigSend)\n\t// banker := std.GetBanker(std.BankerTypeRealmSend)\n\tcoinsToSend := std.Coins{std.Coin{Denom: \"ugnot\", Amount: amount}}\n\tpkgaddr := std.GetOrigPkgAddr()\n\n\tbanker.SendCoins(pkgaddr, to, coinsToSend)\n\n\t// Update tip amount\n\tpost.tipAmount += uint64(amount)\n}\n\n// Always remove reaction if count = 0\nfunc (post *Post) React(icon string, up bool) {\n\tcount_, ok := post.reactions.Get(icon)\n\tcount := 0\n\n\tif ok {\n\t\tcount = count_.(int)\n\t}\n\n\tif up {\n\t\tcount++\n\t} else {\n\t\tcount--\n\t}\n\n\tif count <= 0 {\n\t\tpost.reactions.Remove(icon)\n\t} else {\n\t\tpost.reactions.Set(icon, count)\n\t}\n}\n\nfunc (post *Post) Render() string {\n\treturn post.metadata\n}\n\nfunc (post *Post) FromJSON(jsonData string) {\n\tast := ujson.TokenizeAndParse(jsonData)\n\tast.ParseObject([]*ujson.ParseKV{\n\t\t{Key: \"id\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.id = PostID(pid)\n\t\t}},\n\t\t{Key: \"parentID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.parentID = PostID(pid)\n\t\t}},\n\t\t{Key: \"feedID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tfid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.feedID = FeedID(fid)\n\t\t}},\n\t\t{Key: \"category\", Value: &post.category},\n\t\t{Key: \"metadata\", Value: &post.metadata},\n\t\t{Key: \"reactions\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\treactions := avl.NewTree()\n\t\t\tfor _, child := range node.ObjectChildren {\n\t\t\t\treactionCount := child.Value\n\t\t\t\treactions.Set(child.Key, reactionCount)\n\t\t\t}\n\t\t\tpost.reactions = *reactions\n\t\t}},\n\t\t{Key: \"commentsCount\", Value: &post.commentsCount},\n\t\t{Key: \"creator\", Value: &post.creator},\n\t\t{Key: \"tipAmount\", Value: &post.tipAmount},\n\t\t{Key: \"deleted\", Value: &post.deleted},\n\t\t{Key: \"createdAt\", Value: &post.createdAt},\n\t\t{Key: \"updatedAt\", Value: &post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: &post.deletedAt},\n\t})\n}\n\nfunc (post *Post) ToJSON() string {\n\treactionsKV := []ujson.FormatKV{}\n\tpost.reactions.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tcount := value.(int)\n\t\tdata := ujson.FormatKV{Key: key, Value: count}\n\t\treactionsKV = append(reactionsKV, data)\n\t\treturn false\n\t})\n\treactions := ujson.FormatObject(reactionsKV)\n\n\tpostJSON := ujson.FormatObject([]ujson.FormatKV{\n\t\t{Key: \"id\", Value: uint64(post.id)},\n\t\t{Key: \"parentID\", Value: uint64(post.parentID)},\n\t\t{Key: \"feedID\", Value: uint64(post.feedID)},\n\t\t{Key: \"category\", Value: post.category},\n\t\t{Key: \"metadata\", Value: post.metadata},\n\t\t{Key: \"reactions\", Value: reactions, Raw: true},\n\t\t{Key: \"creator\", Value: post.creator},\n\t\t{Key: \"tipAmount\", Value: post.tipAmount},\n\t\t{Key: \"deleted\", Value: post.deleted},\n\t\t{Key: \"commentsCount\", Value: post.commentsCount},\n\t\t{Key: \"createdAt\", Value: post.createdAt},\n\t\t{Key: \"updatedAt\", Value: post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: post.deletedAt},\n\t})\n\treturn postJSON\n}\n" + "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n)\n\ntype PostID uint64\n\nfunc (pid PostID) String() string {\n\treturn strconv.Itoa(int(pid))\n}\n\nfunc (pid *PostID) FromJSON(ast *ujson.JSONASTNode) {\n\tval, err := strconv.Atoi(ast.Value)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t*pid = PostID(val)\n}\n\nfunc (pid PostID) ToJSON() string {\n\treturn strconv.Itoa(int(pid))\n}\n\ntype Reaction struct {\n\ticon string\n\tcount uint64\n}\n\nvar Categories []string = []string{\n\t\"Reaction\",\n\t\"Comment\",\n\t\"Normal\",\n\t\"Article\",\n\t\"Picture\",\n\t\"Audio\",\n\t\"Video\",\n}\n\ntype Post struct {\n\tid PostID\n\tparentID PostID\n\tfeedID FeedID\n\tcategory uint64\n\tmetadata string\n\treactions avl.Tree // icon -> count\n\tcomments avl.Tree // Post.id -> *Post\n\tcreator std.Address\n\ttipAmount uint64\n\tdeleted bool\n\tcommentsCount uint64\n\n\tcreatedAt int64\n\tupdatedAt int64\n\tdeletedAt int64\n}\n\nfunc newPost(feed *Feed, id PostID, creator std.Address, parentID PostID, category uint64, metadata string) *Post {\n\treturn &Post{\n\t\tid: id,\n\t\tparentID: parentID,\n\t\tfeedID: feed.id,\n\t\tcategory: category,\n\t\tmetadata: metadata,\n\t\treactions: avl.Tree{},\n\t\tcreator: creator,\n\t\tcreatedAt: time.Now().Unix(),\n\t}\n}\n\nfunc (post *Post) String() string {\n\treturn post.ToJSON()\n}\n\nfunc (post *Post) Update(category uint64, metadata string) {\n\tpost.category = category\n\tpost.metadata = metadata\n\tpost.updatedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Delete() {\n\tpost.deleted = true\n\tpost.deletedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Tip(from std.Address, to std.Address) {\n\treceivedCoins := std.OrigSend()\n\tamount := receivedCoins[0].Amount\n\n\tbanker := std.GetBanker(std.BankerTypeOrigSend)\n\t// banker := std.GetBanker(std.BankerTypeRealmSend)\n\tcoinsToSend := std.Coins{std.Coin{Denom: \"ugnot\", Amount: amount}}\n\tpkgaddr := std.GetOrigPkgAddr()\n\n\tbanker.SendCoins(pkgaddr, to, coinsToSend)\n\n\t// Update tip amount\n\tpost.tipAmount += uint64(amount)\n}\n\n// Always remove reaction if count = 0\nfunc (post *Post) React(icon string, up bool) {\n\tcount_, ok := post.reactions.Get(icon)\n\tcount := 0\n\n\tif ok {\n\t\tcount = count_.(int)\n\t}\n\n\tif up {\n\t\tcount++\n\t} else {\n\t\tcount--\n\t}\n\n\tif count <= 0 {\n\t\tpost.reactions.Remove(icon)\n\t} else {\n\t\tpost.reactions.Set(icon, count)\n\t}\n}\n\nfunc (post *Post) Render() string {\n\treturn post.metadata\n}\n\nfunc (post *Post) FromJSON(jsonData string) {\n\tast := ujson.TokenizeAndParse(jsonData)\n\tast.ParseObject([]*ujson.ParseKV{\n\t\t{Key: \"id\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.id = PostID(pid)\n\t\t}},\n\t\t{Key: \"parentID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.parentID = PostID(pid)\n\t\t}},\n\t\t{Key: \"feedID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tfid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.feedID = FeedID(fid)\n\t\t}},\n\t\t{Key: \"category\", Value: &post.category},\n\t\t{Key: \"metadata\", Value: &post.metadata},\n\t\t{Key: \"reactions\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\treactions := avl.NewTree()\n\t\t\tfor _, child := range node.ObjectChildren {\n\t\t\t\treactionCount := child.Value\n\t\t\t\treactions.Set(child.Key, reactionCount)\n\t\t\t}\n\t\t\tpost.reactions = *reactions\n\t\t}},\n\t\t{Key: \"commentsCount\", Value: &post.commentsCount},\n\t\t{Key: \"creator\", Value: &post.creator},\n\t\t{Key: \"tipAmount\", Value: &post.tipAmount},\n\t\t{Key: \"deleted\", Value: &post.deleted},\n\t\t{Key: \"createdAt\", Value: &post.createdAt},\n\t\t{Key: \"updatedAt\", Value: &post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: &post.deletedAt},\n\t})\n}\n\nfunc (post *Post) ToJSON() string {\n\treactionsKV := []ujson.FormatKV{}\n\tpost.reactions.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tcount := value.(int)\n\t\tdata := ujson.FormatKV{Key: key, Value: count}\n\t\treactionsKV = append(reactionsKV, data)\n\t\treturn false\n\t})\n\treactions := ujson.FormatObject(reactionsKV)\n\n\tpostJSON := ujson.FormatObject([]ujson.FormatKV{\n\t\t{Key: \"id\", Value: uint64(post.id)},\n\t\t{Key: \"parentID\", Value: uint64(post.parentID)},\n\t\t{Key: \"feedID\", Value: uint64(post.feedID)},\n\t\t{Key: \"category\", Value: post.category},\n\t\t{Key: \"metadata\", Value: post.metadata},\n\t\t{Key: \"reactions\", Value: reactions, Raw: true},\n\t\t{Key: \"creator\", Value: post.creator},\n\t\t{Key: \"tipAmount\", Value: post.tipAmount},\n\t\t{Key: \"deleted\", Value: post.deleted},\n\t\t{Key: \"commentsCount\", Value: post.commentsCount},\n\t\t{Key: \"createdAt\", Value: post.createdAt},\n\t\t{Key: \"updatedAt\", Value: post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: post.deletedAt},\n\t})\n\treturn postJSON\n}\n" }, { "Name": "public.gno", diff --git a/gno.land/cmd/gnoland/testdata/issue_2283_cacheTypes.txtar b/gno.land/cmd/gnoland/testdata/issue_2283_cacheTypes.txtar index 95bd48c0144..412a1734f2b 100644 --- a/gno.land/cmd/gnoland/testdata/issue_2283_cacheTypes.txtar +++ b/gno.land/cmd/gnoland/testdata/issue_2283_cacheTypes.txtar @@ -60,7 +60,7 @@ stdout OK! }, { "Name": "post.gno", - "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n)\n\ntype PostID uint64\n\nfunc (pid PostID) String() string {\n\treturn strconv.Itoa(int(pid))\n}\n\nfunc (pid *PostID) FromJSON(ast *ujson.JSONASTNode) {\n\tval, err := strconv.Atoi(ast.Value)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t*pid = PostID(val)\n}\n\nfunc (pid PostID) ToJSON() string {\n\treturn strconv.Itoa(int(pid))\n}\n\ntype Reaction struct {\n\ticon string\n\tcount uint64\n}\n\nvar Categories []string = []string{\n\t\"Reaction\",\n\t\"Comment\",\n\t\"Normal\",\n\t\"Article\",\n\t\"Picture\",\n\t\"Audio\",\n\t\"Video\",\n}\n\ntype Post struct {\n\tid PostID\n\tparentID PostID\n\tfeedID FeedID\n\tcategory uint64\n\tmetadata string\n\treactions avl.Tree // icon -> count\n\tcomments avl.Tree // Post.id -> *Post\n\tcreator std.Address\n\ttipAmount uint64\n\tdeleted bool\n\tcommentsCount uint64\n\n\tcreatedAt int64\n\tupdatedAt int64\n\tdeletedAt int64\n}\n\nfunc newPost(feed *Feed, id PostID, creator std.Address, parentID PostID, category uint64, metadata string) *Post {\n\treturn &Post{\n\t\tid: id,\n\t\tparentID: parentID,\n\t\tfeedID: feed.id,\n\t\tcategory: category,\n\t\tmetadata: metadata,\n\t\treactions: avl.Tree{},\n\t\tcreator: creator,\n\t\tcreatedAt: time.Now().Unix(),\n\t}\n}\n\nfunc (post *Post) String() string {\n\treturn post.ToJSON()\n}\n\nfunc (post *Post) Update(category uint64, metadata string) {\n\tpost.category = category\n\tpost.metadata = metadata\n\tpost.updatedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Delete() {\n\tpost.deleted = true\n\tpost.deletedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Tip(from std.Address, to std.Address) {\n\treceivedCoins := std.GetOrigSend()\n\tamount := receivedCoins[0].Amount\n\n\tbanker := std.GetBanker(std.BankerTypeOrigSend)\n\t// banker := std.GetBanker(std.BankerTypeRealmSend)\n\tcoinsToSend := std.Coins{std.Coin{Denom: \"ugnot\", Amount: amount}}\n\tpkgaddr := std.GetOrigPkgAddr()\n\n\tbanker.SendCoins(pkgaddr, to, coinsToSend)\n\n\t// Update tip amount\n\tpost.tipAmount += uint64(amount)\n}\n\n// Always remove reaction if count = 0\nfunc (post *Post) React(icon string, up bool) {\n\tcount_, ok := post.reactions.Get(icon)\n\tcount := 0\n\n\tif ok {\n\t\tcount = count_.(int)\n\t}\n\n\tif up {\n\t\tcount++\n\t} else {\n\t\tcount--\n\t}\n\n\tif count <= 0 {\n\t\tpost.reactions.Remove(icon)\n\t} else {\n\t\tpost.reactions.Set(icon, count)\n\t}\n}\n\nfunc (post *Post) Render() string {\n\treturn post.metadata\n}\n\nfunc (post *Post) FromJSON(jsonData string) {\n\tast := ujson.TokenizeAndParse(jsonData)\n\tast.ParseObject([]*ujson.ParseKV{\n\t\t{Key: \"id\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.id = PostID(pid)\n\t\t}},\n\t\t{Key: \"parentID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.parentID = PostID(pid)\n\t\t}},\n\t\t{Key: \"feedID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tfid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.feedID = FeedID(fid)\n\t\t}},\n\t\t{Key: \"category\", Value: &post.category},\n\t\t{Key: \"metadata\", Value: &post.metadata},\n\t\t{Key: \"reactions\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\treactions := avl.NewTree()\n\t\t\tfor _, child := range node.ObjectChildren {\n\t\t\t\treactionCount := child.Value\n\t\t\t\treactions.Set(child.Key, reactionCount)\n\t\t\t}\n\t\t\tpost.reactions = *reactions\n\t\t}},\n\t\t{Key: \"commentsCount\", Value: &post.commentsCount},\n\t\t{Key: \"creator\", Value: &post.creator},\n\t\t{Key: \"tipAmount\", Value: &post.tipAmount},\n\t\t{Key: \"deleted\", Value: &post.deleted},\n\t\t{Key: \"createdAt\", Value: &post.createdAt},\n\t\t{Key: \"updatedAt\", Value: &post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: &post.deletedAt},\n\t})\n}\n\nfunc (post *Post) ToJSON() string {\n\treactionsKV := []ujson.FormatKV{}\n\tpost.reactions.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tcount := value.(int)\n\t\tdata := ujson.FormatKV{Key: key, Value: count}\n\t\treactionsKV = append(reactionsKV, data)\n\t\treturn false\n\t})\n\treactions := ujson.FormatObject(reactionsKV)\n\n\tpostJSON := ujson.FormatObject([]ujson.FormatKV{\n\t\t{Key: \"id\", Value: uint64(post.id)},\n\t\t{Key: \"parentID\", Value: uint64(post.parentID)},\n\t\t{Key: \"feedID\", Value: uint64(post.feedID)},\n\t\t{Key: \"category\", Value: post.category},\n\t\t{Key: \"metadata\", Value: post.metadata},\n\t\t{Key: \"reactions\", Value: reactions, Raw: true},\n\t\t{Key: \"creator\", Value: post.creator},\n\t\t{Key: \"tipAmount\", Value: post.tipAmount},\n\t\t{Key: \"deleted\", Value: post.deleted},\n\t\t{Key: \"commentsCount\", Value: post.commentsCount},\n\t\t{Key: \"createdAt\", Value: post.createdAt},\n\t\t{Key: \"updatedAt\", Value: post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: post.deletedAt},\n\t})\n\treturn postJSON\n}\n" + "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n)\n\ntype PostID uint64\n\nfunc (pid PostID) String() string {\n\treturn strconv.Itoa(int(pid))\n}\n\nfunc (pid *PostID) FromJSON(ast *ujson.JSONASTNode) {\n\tval, err := strconv.Atoi(ast.Value)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t*pid = PostID(val)\n}\n\nfunc (pid PostID) ToJSON() string {\n\treturn strconv.Itoa(int(pid))\n}\n\ntype Reaction struct {\n\ticon string\n\tcount uint64\n}\n\nvar Categories []string = []string{\n\t\"Reaction\",\n\t\"Comment\",\n\t\"Normal\",\n\t\"Article\",\n\t\"Picture\",\n\t\"Audio\",\n\t\"Video\",\n}\n\ntype Post struct {\n\tid PostID\n\tparentID PostID\n\tfeedID FeedID\n\tcategory uint64\n\tmetadata string\n\treactions avl.Tree // icon -> count\n\tcomments avl.Tree // Post.id -> *Post\n\tcreator std.Address\n\ttipAmount uint64\n\tdeleted bool\n\tcommentsCount uint64\n\n\tcreatedAt int64\n\tupdatedAt int64\n\tdeletedAt int64\n}\n\nfunc newPost(feed *Feed, id PostID, creator std.Address, parentID PostID, category uint64, metadata string) *Post {\n\treturn &Post{\n\t\tid: id,\n\t\tparentID: parentID,\n\t\tfeedID: feed.id,\n\t\tcategory: category,\n\t\tmetadata: metadata,\n\t\treactions: avl.Tree{},\n\t\tcreator: creator,\n\t\tcreatedAt: time.Now().Unix(),\n\t}\n}\n\nfunc (post *Post) String() string {\n\treturn post.ToJSON()\n}\n\nfunc (post *Post) Update(category uint64, metadata string) {\n\tpost.category = category\n\tpost.metadata = metadata\n\tpost.updatedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Delete() {\n\tpost.deleted = true\n\tpost.deletedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Tip(from std.Address, to std.Address) {\n\treceivedCoins := std.OrigSend()\n\tamount := receivedCoins[0].Amount\n\n\tbanker := std.GetBanker(std.BankerTypeOrigSend)\n\t// banker := std.GetBanker(std.BankerTypeRealmSend)\n\tcoinsToSend := std.Coins{std.Coin{Denom: \"ugnot\", Amount: amount}}\n\tpkgaddr := std.GetOrigPkgAddr()\n\n\tbanker.SendCoins(pkgaddr, to, coinsToSend)\n\n\t// Update tip amount\n\tpost.tipAmount += uint64(amount)\n}\n\n// Always remove reaction if count = 0\nfunc (post *Post) React(icon string, up bool) {\n\tcount_, ok := post.reactions.Get(icon)\n\tcount := 0\n\n\tif ok {\n\t\tcount = count_.(int)\n\t}\n\n\tif up {\n\t\tcount++\n\t} else {\n\t\tcount--\n\t}\n\n\tif count <= 0 {\n\t\tpost.reactions.Remove(icon)\n\t} else {\n\t\tpost.reactions.Set(icon, count)\n\t}\n}\n\nfunc (post *Post) Render() string {\n\treturn post.metadata\n}\n\nfunc (post *Post) FromJSON(jsonData string) {\n\tast := ujson.TokenizeAndParse(jsonData)\n\tast.ParseObject([]*ujson.ParseKV{\n\t\t{Key: \"id\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.id = PostID(pid)\n\t\t}},\n\t\t{Key: \"parentID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.parentID = PostID(pid)\n\t\t}},\n\t\t{Key: \"feedID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tfid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.feedID = FeedID(fid)\n\t\t}},\n\t\t{Key: \"category\", Value: &post.category},\n\t\t{Key: \"metadata\", Value: &post.metadata},\n\t\t{Key: \"reactions\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\treactions := avl.NewTree()\n\t\t\tfor _, child := range node.ObjectChildren {\n\t\t\t\treactionCount := child.Value\n\t\t\t\treactions.Set(child.Key, reactionCount)\n\t\t\t}\n\t\t\tpost.reactions = *reactions\n\t\t}},\n\t\t{Key: \"commentsCount\", Value: &post.commentsCount},\n\t\t{Key: \"creator\", Value: &post.creator},\n\t\t{Key: \"tipAmount\", Value: &post.tipAmount},\n\t\t{Key: \"deleted\", Value: &post.deleted},\n\t\t{Key: \"createdAt\", Value: &post.createdAt},\n\t\t{Key: \"updatedAt\", Value: &post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: &post.deletedAt},\n\t})\n}\n\nfunc (post *Post) ToJSON() string {\n\treactionsKV := []ujson.FormatKV{}\n\tpost.reactions.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tcount := value.(int)\n\t\tdata := ujson.FormatKV{Key: key, Value: count}\n\t\treactionsKV = append(reactionsKV, data)\n\t\treturn false\n\t})\n\treactions := ujson.FormatObject(reactionsKV)\n\n\tpostJSON := ujson.FormatObject([]ujson.FormatKV{\n\t\t{Key: \"id\", Value: uint64(post.id)},\n\t\t{Key: \"parentID\", Value: uint64(post.parentID)},\n\t\t{Key: \"feedID\", Value: uint64(post.feedID)},\n\t\t{Key: \"category\", Value: post.category},\n\t\t{Key: \"metadata\", Value: post.metadata},\n\t\t{Key: \"reactions\", Value: reactions, Raw: true},\n\t\t{Key: \"creator\", Value: post.creator},\n\t\t{Key: \"tipAmount\", Value: post.tipAmount},\n\t\t{Key: \"deleted\", Value: post.deleted},\n\t\t{Key: \"commentsCount\", Value: post.commentsCount},\n\t\t{Key: \"createdAt\", Value: post.createdAt},\n\t\t{Key: \"updatedAt\", Value: post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: post.deletedAt},\n\t})\n\treturn postJSON\n}\n" }, { "Name": "public.gno", diff --git a/gno.land/genesis/genesis_txs.jsonl b/gno.land/genesis/genesis_txs.jsonl index 9027d51c0ac..c8e5be67d8a 100644 --- a/gno.land/genesis/genesis_txs.jsonl +++ b/gno.land/genesis/genesis_txs.jsonl @@ -10,7 +10,7 @@ {"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateBoard","args":["testboard"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"vzlSxEFh5jOkaSdv3rsV91v/OJKEF2qSuoCpri1u5tRWq62T7xr3KHRCF5qFnn4aQX/yE8g8f/Y//WPOCUGhJw=="}],"memo":""}} {"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","Hello World","This is a demo of Gno smart contract programming. This document was\nconstructed by Gno onto a smart contract hosted on the data Realm \nname [\"gno.land/r/demo/boards\"](https://gno.land/r/demo/boards/)\n([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)).\n\n## Starting the `gnoland` node node/validator.\n\nNOTE: Where you see `--remote %%REMOTE%%` here, that flag can be replaced\nwith `--remote localhost:26657` for local testnets.\n\n### build gnoland.\n\n```bash\ngit clone git@github.com:gnolang/gno.git\ncd ./gno\nmake \n```\n\n### add test account.\n\n```bash\n./build/gnokey add test1 --recover\n```\n\nUse this mnemonic:\n\u003e source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast\n\n### start gnoland validator node.\n\n```bash\n./build/gnoland\n```\n\n(This can be reset with `make reset`).\n\n### start gnoland web server (optional).\n\n```bash\ngo run ./gnoland/website\n```\n\n## Signing and broadcasting transactions.\n\n### publish the \"gno.land/p/demo/avl\" package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/p/demo/avl\" --pkgdir \"examples/gno.land/p/demo/avl\" --deposit 100ugnot --gas-fee 1ugnot --gas-wanted 2000000 \u003e addpkg.avl.unsigned.txt\n./build/gnokey query \"auth/accounts/g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\"\n./build/gnokey sign test1 --txpath addpkg.avl.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 0 \u003e addpkg.avl.signed.txt\n./build/gnokey broadcast addpkg.avl.signed.txt --remote %%REMOTE%%\n```\n\n### publish the \"gno.land/r/demo/boards\" realm package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/r/demo/boards\" --pkgdir \"examples/gno.land/r/demo/boards\" --deposit 100ugnot --gas-fee 1ugnot --gas-wanted 300000000 \u003e addpkg.boards.unsigned.txt\n./build/gnokey sign test1 --txpath addpkg.boards.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 1 \u003e addpkg.boards.signed.txt\n./build/gnokey broadcast addpkg.boards.signed.txt --remote %%REMOTE%%\n```\n\n### create a board with a smart contract call.\n\n```bash\n./build/gnokey maketx call test1 --pkgpath \"gno.land/r/demo/boards\" --func CreateBoard --args \"testboard\" --gas-fee 1ugnot --gas-wanted 2000000 \u003e createboard.unsigned.txt\n./build/gnokey sign test1 --txpath createboard.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 2 \u003e createboard.signed.txt\n./build/gnokey broadcast createboard.signed.txt --remote %%REMOTE%%\n```\nNext, query for the permanent board ID by querying (you need this to create a new post):\n\n```bash\n./build/gnokey query \"vm/qeval\" --data \"gno.land/r/demo/boards.GetBoardIDFromName(\\\"testboard\\\")\"\n```\n\n### create a post of a board with a smart contract call.\n\n```bash\n./build/gnokey maketx call test1 --pkgpath \"gno.land/r/demo/boards\" --func CreatePost --args 1 --args \"Hello World\" --args#file \"./examples/gno.land/r/demo/boards/README.md\" --gas-fee 1ugnot --gas-wanted 2000000 \u003e createpost.unsigned.txt\n./build/gnokey sign test1 --txpath createpost.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 3 \u003e createpost.signed.txt\n./build/gnokey broadcast createpost.signed.txt --remote %%REMOTE%%\n```\n\n### create a comment to a post.\n\n```bash\n./build/gnokey maketx call test1 --pkgpath \"gno.land/r/demo/boards\" --func CreateReply --args 1 --args 1 --args \"A comment\" --gas-fee 1ugnot --gas-wanted 2000000 \u003e createcomment.unsigned.txt\n./build/gnokey sign test1 --txpath createcomment.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 4 \u003e createcomment.signed.txt\n./build/gnokey broadcast createcomment.signed.txt --remote %%REMOTE%%\n```\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards:testboard/1\"\n```\n\n### render page with optional path expression.\n\nThe contents of `https://gno.land/r/demo/boards:` and `https://gno.land/r/demo/boards:testboard` are rendered by calling\nthe `Render(path string)` function like so:\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards:testboard\"\n```\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"V43B1waFxhzheW9TfmCpjLdrC4dC1yjUGES5y3J6QsNar6hRpNz4G1thzWmWK7xXhg8u1PCIpxLxGczKQYhuPw=="}],"memo":""}} {"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","NFT example","NFT's are all the rage these days, for various reasons.\n\nI read over EIP-721 which appears to be the de-facto NFT standard on Ethereum. Then, made a sample implementation of EIP-721 (let's here called GRC-721). The implementation isn't complete, but it demonstrates the main functionality.\n\n - [EIP-721](https://eips.ethereum.org/EIPS/eip-721)\n - [gno.land/r/demo/nft/nft.gno](https://gno.land/r/demo/nft/nft.gno)\n - [zrealm_nft3.gno test](https://github.com/gnolang/gno/blob/master/examples/gno.land/r/demo/nft/z_3_filetest.gno)\n\nIn short, this demonstrates how to implement Ethereum contract interfaces in gno.land; by using only standard Go language features.\n\nPlease leave a comment ([guide](https://gno.land/r/demo/boards:testboard/1)).\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"ZXfrTiHxPFQL8uSm+Tv7WXIHPMca9okhm94RAlC6YgNbB1VHQYYpoP4w+cnL3YskVzGrOZxensXa9CAZ+cNNeg=="}],"memo":""}} -{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","Simple echo example with coins","This is a simple test realm contract that demonstrates how to use the banker.\n\nSee [gno.land/r/demo/banktest/banktest.gno](/r/demo/banktest/banktest.gno) to see the original contract code.\n\nThis article will go through each line to explain how it works.\n\n```go\npackage banktest\n```\n\nThis package is locally named \"banktest\" (could be anything).\n\n```go\nimport (\n\t\"std\"\n)\n```\n\nThe \"std\" package is defined by the gno code in stdlibs/std/. \u003c/br\u003e\nSelf explanatory; and you'll see more usage from std later.\n\n```go\ntype activity struct {\n\tcaller std.Address\n\tsent std.Coins\n\treturned std.Coins\n\ttime std.Time\n}\n\nfunc (act *activity) String() string {\n\treturn act.caller.String() + \" \" +\n\t\tact.sent.String() + \" sent, \" +\n\t\tact.returned.String() + \" returned, at \" +\n\t\tstd.FormatTimestamp(act.time, \"2006-01-02 3:04pm MST\")\n}\n\nvar latest [10]*activity\n```\n\nThis is just maintaining a list of recent activity to this contract.\nNotice that the \"latest\" variable is defined \"globally\" within\nthe context of the realm with path \"gno.land/r/demo/banktest\".\n\nThis means that calls to functions defined within this package\nare encapsulated within this \"data realm\", where the data is \nmutated based on transactions that can potentially cross many\nrealm and non-realm packge boundaries (in the call stack).\n\n```go\n// Deposit will take the coins (to the realm's pkgaddr) or return them to user.\nfunc Deposit(returnDenom string, returnAmount int64) string {\n\tstd.AssertOriginCall()\n\tcaller := std.GetOrigCaller()\n\tsend := std.Coins{{returnDenom, returnAmount}}\n```\n\nThis is the beginning of the definition of the contract function named\n\"Deposit\". `std.AssertOriginCall() asserts that this function was called by a\ngno transactional Message. The caller is the user who signed off on this\ntransactional message. Send is the amount of deposit sent along with this\nmessage.\n\n```go\n\t// record activity\n\tact := \u0026activity{\n\t\tcaller: caller,\n\t\tsent: std.GetOrigSend(),\n\t\treturned: send,\n\t\ttime: std.GetTimestamp(),\n\t}\n\tfor i := len(latest) - 2; i \u003e= 0; i-- {\n\t\tlatest[i+1] = latest[i] // shift by +1.\n\t}\n\tlatest[0] = act\n```\n\nUpdating the \"latest\" array for viewing at gno.land/r/demo/banktest: (w/ trailing colon).\n\n```go\n\t// return if any.\n\tif returnAmount \u003e 0 {\n```\n\nIf the user requested the return of coins...\n\n```go\n\t\tbanker := std.GetBanker(std.BankerTypeOrigSend)\n```\n\nuse a std.Banker instance to return any deposited coins to the original sender.\n\n```go\n\t\tpkgaddr := std.GetOrigPkgAddr()\n\t\t// TODO: use std.Coins constructors, this isn't generally safe.\n\t\tbanker.SendCoins(pkgaddr, caller, send)\n\t\treturn \"returned!\"\n```\n\nNotice that each realm package has an associated Cosmos address.\n\n\nFinally, the results are rendered via an ABCI query call when you visit [/r/demo/banktest:](/r/demo/banktest:).\n\n```go\nfunc Render(path string) string {\n\t// get realm coins.\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tcoins := banker.GetCoins(std.GetOrigPkgAddr())\n\n\t// render\n\tres := \"\"\n\tres += \"## recent activity\\n\"\n\tres += \"\\n\"\n\tfor _, act := range latest {\n\t\tif act == nil {\n\t\t\tbreak\n\t\t}\n\t\tres += \" * \" + act.String() + \"\\n\"\n\t}\n\tres += \"\\n\"\n\tres += \"## total deposits\\n\"\n\tres += coins.String()\n\treturn res\n}\n```\n\nYou can call this contract yourself, by vistiing [/r/demo/banktest](/r/demo/banktest) and the [quickstart guide](/r/demo/boards:testboard/4).\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"iZX/llZlNTdZMLv1goCTgK2bWqzT8enlTq56wMTCpVxJGA0BTvuEM5Nnt9vrnlG6Taqj2GuTrmEnJBkDFTmt9g=="}],"memo":""}} +{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","Simple echo example with coins","This is a simple test realm contract that demonstrates how to use the banker.\n\nSee [gno.land/r/demo/banktest/banktest.gno](/r/demo/banktest/banktest.gno) to see the original contract code.\n\nThis article will go through each line to explain how it works.\n\n```go\npackage banktest\n```\n\nThis package is locally named \"banktest\" (could be anything).\n\n```go\nimport (\n\t\"std\"\n)\n```\n\nThe \"std\" package is defined by the gno code in stdlibs/std/. \u003c/br\u003e\nSelf explanatory; and you'll see more usage from std later.\n\n```go\ntype activity struct {\n\tcaller std.Address\n\tsent std.Coins\n\treturned std.Coins\n\ttime std.Time\n}\n\nfunc (act *activity) String() string {\n\treturn act.caller.String() + \" \" +\n\t\tact.sent.String() + \" sent, \" +\n\t\tact.returned.String() + \" returned, at \" +\n\t\tstd.FormatTimestamp(act.time, \"2006-01-02 3:04pm MST\")\n}\n\nvar latest [10]*activity\n```\n\nThis is just maintaining a list of recent activity to this contract.\nNotice that the \"latest\" variable is defined \"globally\" within\nthe context of the realm with path \"gno.land/r/demo/banktest\".\n\nThis means that calls to functions defined within this package\nare encapsulated within this \"data realm\", where the data is \nmutated based on transactions that can potentially cross many\nrealm and non-realm packge boundaries (in the call stack).\n\n```go\n// Deposit will take the coins (to the realm's pkgaddr) or return them to user.\nfunc Deposit(returnDenom string, returnAmount int64) string {\n\tstd.AssertOriginCall()\n\tcaller := std.OrigCaller()\n\tsend := std.Coins{{returnDenom, returnAmount}}\n```\n\nThis is the beginning of the definition of the contract function named\n\"Deposit\". `std.AssertOriginCall() asserts that this function was called by a\ngno transactional Message. The caller is the user who signed off on this\ntransactional message. Send is the amount of deposit sent along with this\nmessage.\n\n```go\n\t// record activity\n\tact := \u0026activity{\n\t\tcaller: caller,\n\t\tsent: std.OrigSend(),\n\t\treturned: send,\n\t\ttime: std.GetTimestamp(),\n\t}\n\tfor i := len(latest) - 2; i \u003e= 0; i-- {\n\t\tlatest[i+1] = latest[i] // shift by +1.\n\t}\n\tlatest[0] = act\n```\n\nUpdating the \"latest\" array for viewing at gno.land/r/demo/banktest: (w/ trailing colon).\n\n```go\n\t// return if any.\n\tif returnAmount \u003e 0 {\n```\n\nIf the user requested the return of coins...\n\n```go\n\t\tbanker := std.GetBanker(std.BankerTypeOrigSend)\n```\n\nuse a std.Banker instance to return any deposited coins to the original sender.\n\n```go\n\t\tpkgaddr := std.GetOrigPkgAddr()\n\t\t// TODO: use std.Coins constructors, this isn't generally safe.\n\t\tbanker.SendCoins(pkgaddr, caller, send)\n\t\treturn \"returned!\"\n```\n\nNotice that each realm package has an associated Cosmos address.\n\n\nFinally, the results are rendered via an ABCI query call when you visit [/r/demo/banktest:](/r/demo/banktest:).\n\n```go\nfunc Render(path string) string {\n\t// get realm coins.\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tcoins := banker.GetCoins(std.GetOrigPkgAddr())\n\n\t// render\n\tres := \"\"\n\tres += \"## recent activity\\n\"\n\tres += \"\\n\"\n\tfor _, act := range latest {\n\t\tif act == nil {\n\t\t\tbreak\n\t\t}\n\t\tres += \" * \" + act.String() + \"\\n\"\n\t}\n\tres += \"\\n\"\n\tres += \"## total deposits\\n\"\n\tres += coins.String()\n\treturn res\n}\n```\n\nYou can call this contract yourself, by vistiing [/r/demo/banktest](/r/demo/banktest) and the [quickstart guide](/r/demo/boards:testboard/4).\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"iZX/llZlNTdZMLv1goCTgK2bWqzT8enlTq56wMTCpVxJGA0BTvuEM5Nnt9vrnlG6Taqj2GuTrmEnJBkDFTmt9g=="}],"memo":""}} {"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","TASK: Describe in your words","Describe in an essay (250+ words), on your favorite medium, why you are interested in gno.land and gnolang.\n\nReply here with a URL link to your written piece as a comment, for rewards.\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AmG6kzznyo1uNqWPAYU6wDpsmzQKDaEOrVRaZ08vOyX0"},"signature":"4HBNtrta8HdeHj4JTN56PBTRK8GOe31NMRRXDiyYtjozuyRdWfOGEsGjGgHWcoBUJq6DepBgD4FetdqfhZ6TNQ=="}],"memo":""}} {"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1manfred47kzduec920z88wfr64ylksmdcedlf5","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","Getting Started","This is a demo of Gno smart contract programming. This document was\nconstructed by Gno onto a smart contract hosted on the data Realm\nname [\"gno.land/r/demo/boards\"](https://gno.land/r/demo/boards/)\n([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)).\n\n\n\n## Build `gnokey`, create your account, and interact with Gno.\n\nNOTE: Where you see `--remote %%REMOTE%%` here, that flag can be replaced\nwith `--remote localhost:26657` for local testnets.\n\n### Build `gnokey`.\n\n```bash\ngit clone git@github.com:gnolang/gno.git\ncd ./gno\nmake\n```\n\n### Generate a seed/mnemonic code.\n\n```bash\n./build/gnokey generate\n```\n\nNOTE: You can generate 24 words with any good bip39 generator.\n\n### Create a new account using your mnemonic.\n\n```bash\n./build/gnokey add KEYNAME --recover\n```\n\nNOTE: `KEYNAME` is your key identifier, and should be changed.\n\n### Verify that you can see your account locally.\n\n```bash\n./build/gnokey list\n```\n\n## Interact with the blockchain:\n\n### Get your current balance, account number, and sequence number.\n\n```bash\n./build/gnokey query auth/accounts/ACCOUNT_ADDR --remote %%REMOTE%%\n```\n\nNOTE: you can retrieve your `ACCOUNT_ADDR` with `./build/gnokey list`.\n\n### Acquire testnet tokens using the official faucet.\n\nGo to https://gno.land/faucet\n\n### Create a board with a smart contract call.\n\nNOTE: `BOARDNAME` will be the slug of the board, and should be changed.\n\n```bash\n./build/gnokey maketx call KEYNAME --pkgpath \"gno.land/r/demo/boards\" --func \"CreateBoard\" --args \"BOARDNAME\" --gas-fee \"1000000ugnot\" --gas-wanted \"2000000\" --broadcast=true --chainid %%CHAINID%% --remote %%REMOTE%%\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateBoard\n\nNext, query for the permanent board ID by querying (you need this to create a new post):\n\n```bash\n./build/gnokey query \"vm/qeval\" --data \"gno.land/r/demo/boards.GetBoardIDFromName(\\\"BOARDNAME\\\")\" --remote %%REMOTE%%\n```\n\n### Create a post of a board with a smart contract call.\n\nNOTE: If a board was created successfully, your SEQUENCE_NUMBER would have increased.\n\n```bash\n./build/gnokey maketx call KEYNAME --pkgpath \"gno.land/r/demo/boards\" --func \"CreateThread\" --args BOARD_ID --args \"Hello gno.land\" --args\\#file \"./examples/gno.land/r/demo/boards/example_post.md\" --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast=true --chainid %%CHAINID%% --remote %%REMOTE%%\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateThread\n\n### Create a comment to a post.\n\n```bash\n./build/gnokey maketx call KEYNAME --pkgpath \"gno.land/r/demo/boards\" --func \"CreateReply\" --args \"BOARD_ID\" --args \"1\" --args \"1\" --args \"Nice to meet you too.\" --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast=true --chainid %%CHAINID%% --remote %%REMOTE%%\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateReply\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards:BOARDNAME/1\" --remote %%REMOTE%%\n```\n\n### Render page with optional path expression.\n\nThe contents of `https://gno.land/r/demo/boards:` and `https://gno.land/r/demo/boards:gnolang` are rendered by calling\nthe `Render(path string)` function like so:\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards:gnolang\"\n```\n\n## Starting a local `gnoland` node:\n\n### Add test account.\n\n```bash\n./build/gnokey add test1 --recover\n```\n\nUse this mneonic:\n\u003e source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast\n\n### Start `gnoland` node.\n\n```bash\n./build/gnoland\n```\n\nNOTE: This can be reset with `make reset`\n\n### Publish the \"gno.land/p/demo/avl\" package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/p/demo/avl\" --pkgdir \"examples/gno.land/p/demo/avl\" --deposit 100000000ugnot --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast=true --chainid %%CHAINID%% --remote localhost:26657\n```\n\n### Publish the \"gno.land/r/demo/boards\" realm package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/r/demo/boards\" --pkgdir \"examples/gno.land/r/demo/boards\" --deposit 100000000ugnot --gas-fee 1000000ugnot --gas-wanted 300000000 --broadcast=true --chainid %%CHAINID%% --remote localhost:26657\n```\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AnK+a6mcFDjY6b/v6p7r8QFW1M1PgIoQxBgrwOoyY7v3"},"signature":"sHjOGXZEi9wt2FSXFHmkDDoVQyepvFHKRDDU0zgedHYnCYPx5/YndyihsDD5Y2Z7/RgNYBh4JlJwDMGFNStzBQ=="}],"memo":""}} {"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1manfred47kzduec920z88wfr64ylksmdcedlf5","send":"","pkg_path":"gno.land/r/gnoland/blog","func":"ModAddPost","args":["post1","First post","Lorem Ipsum","2022-05-20T13:17:22Z","","tag1,tag2"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AnK+a6mcFDjY6b/v6p7r8QFW1M1PgIoQxBgrwOoyY7v3"},"signature":"sHjOGXZEi9wt2FSXFHmkDDoVQyepvFHKRDDU0zgedHYnCYPx5/YndyihsDD5Y2Z7/RgNYBh4JlJwDMGFNStzBQ=="}],"memo":""}} diff --git a/gno.land/pkg/sdk/vm/handler_test.go b/gno.land/pkg/sdk/vm/handler_test.go index 0d238deed1f..d159d992c50 100644 --- a/gno.land/pkg/sdk/vm/handler_test.go +++ b/gno.land/pkg/sdk/vm/handler_test.go @@ -120,7 +120,7 @@ import "std" import "time" var _ = time.RFC3339 -func caller() std.Address { return std.GetOrigCaller() } +func caller() std.Address { return std.OrigCaller() } var GetHeight = std.GetHeight var sl = []int{1,2,3,4,5} func fn() func(string) string { return Echo } diff --git a/gno.land/pkg/sdk/vm/keeper_test.go b/gno.land/pkg/sdk/vm/keeper_test.go index f8144988c44..427f46afbdc 100644 --- a/gno.land/pkg/sdk/vm/keeper_test.go +++ b/gno.land/pkg/sdk/vm/keeper_test.go @@ -128,9 +128,9 @@ func init() { } func Echo(msg string) string { - addr := std.GetOrigCaller() + addr := std.OrigCaller() pkgAddr := std.GetOrigPkgAddr() - send := std.GetOrigSend() + send := std.OrigSend() banker := std.GetBanker(std.BankerTypeOrigSend) banker.SendCoins(pkgAddr, addr, send) // send back return "echo:"+msg @@ -172,13 +172,13 @@ import "std" var admin std.Address func init() { - admin = std.GetOrigCaller() + admin = std.OrigCaller() } func Echo(msg string) string { - addr := std.GetOrigCaller() + addr := std.OrigCaller() pkgAddr := std.GetOrigPkgAddr() - send := std.GetOrigSend() + send := std.OrigSend() banker := std.GetBanker(std.BankerTypeOrigSend) banker.SendCoins(pkgAddr, addr, send) // send back return "echo:"+msg @@ -227,7 +227,7 @@ func init() { } func Echo(msg string) string { - addr := std.GetOrigCaller() + addr := std.OrigCaller() pkgAddr := std.GetOrigPkgAddr() send := std.Coins{{"ugnot", 10000000}} banker := std.GetBanker(std.BankerTypeOrigSend) @@ -271,7 +271,7 @@ func init() { } func Echo(msg string) string { - addr := std.GetOrigCaller() + addr := std.OrigCaller() pkgAddr := std.GetOrigPkgAddr() send := std.Coins{{"ugnot", 10000000}} banker := std.GetBanker(std.BankerTypeRealmSend) @@ -315,7 +315,7 @@ func init() { } func Echo(msg string) string { - addr := std.GetOrigCaller() + addr := std.OrigCaller() pkgAddr := std.GetOrigPkgAddr() send := std.Coins{{"ugnot", 10000000}} banker := std.GetBanker(std.BankerTypeRealmSend) @@ -412,13 +412,13 @@ import "std" var admin std.Address func init() { - admin = std.GetOrigCaller() + admin = std.OrigCaller() } func Echo(msg string) string { - addr := std.GetOrigCaller() + addr := std.OrigCaller() pkgAddr := std.GetOrigPkgAddr() - send := std.GetOrigSend() + send := std.OrigSend() banker := std.GetBanker(std.BankerTypeOrigSend) banker.SendCoins(pkgAddr, addr, send) // send back return "echo:"+msg @@ -500,7 +500,7 @@ package main import "std" func main() { - addr := std.GetOrigCaller() + addr := std.OrigCaller() println("hello world!", addr) } `}, diff --git a/gnovm/stdlibs/std/native.gno b/gnovm/stdlibs/std/native.gno index 0dcde1148e1..98565237518 100644 --- a/gnovm/stdlibs/std/native.gno +++ b/gnovm/stdlibs/std/native.gno @@ -14,7 +14,7 @@ func GetChainID() string // injected func GetChainDomain() string // injected func GetHeight() int64 // injected -func GetOrigSend() Coins { +func OrigSend() Coins { den, amt := origSend() coins := make(Coins, len(den)) for i := range coins { @@ -23,7 +23,7 @@ func GetOrigSend() Coins { return coins } -func GetOrigCaller() Address { +func OrigCaller() Address { return Address(origCaller()) } @@ -41,7 +41,7 @@ func GetOrigPkgAddr() Address { return Address(origPkgAddr()) } -func GetCallerAt(n int) Address { +func CallerAt(n int) Address { return Address(callerAt(n)) } diff --git a/gnovm/stdlibs/std/native.go b/gnovm/stdlibs/std/native.go index fb181d9be31..4f65eaa92a5 100644 --- a/gnovm/stdlibs/std/native.go +++ b/gnovm/stdlibs/std/native.go @@ -82,10 +82,10 @@ func X_origPkgAddr(m *gno.Machine) string { func X_callerAt(m *gno.Machine, n int) string { if n <= 0 { - m.Panic(typedString("GetCallerAt requires positive arg")) + m.Panic(typedString("CallerAt requires positive arg")) return "" } - // Add 1 to n to account for the GetCallerAt (gno fn) frame. + // Add 1 to n to account for the CallerAt (gno fn) frame. n++ if n > m.NumFrames() { // NOTE: the last frame's LastPackage @@ -95,7 +95,7 @@ func X_callerAt(m *gno.Machine, n int) string { return "" } if n == m.NumFrames() { - // This makes it consistent with GetOrigCaller. + // This makes it consistent with OrigCaller. ctx := GetContext(m) return string(ctx.OrigCaller) } diff --git a/gnovm/tests/files/std10.gno b/gnovm/tests/files/std10.gno index 7caf534c5ca..b549387feaa 100644 --- a/gnovm/tests/files/std10.gno +++ b/gnovm/tests/files/std10.gno @@ -7,8 +7,8 @@ func main() { // assert panic is recoverable println(recover()) }() - std.GetCallerAt(0) + std.CallerAt(0) } // Output: -// GetCallerAt requires positive arg +// CallerAt requires positive arg diff --git a/gnovm/tests/files/std11.gno b/gnovm/tests/files/std11.gno index ce02fa11ec3..70668fe443b 100644 --- a/gnovm/tests/files/std11.gno +++ b/gnovm/tests/files/std11.gno @@ -7,7 +7,7 @@ func main() { // assert panic is recoverable println(recover()) }() - std.GetCallerAt(42) + std.CallerAt(42) } // Output: diff --git a/gnovm/tests/files/std2.gno b/gnovm/tests/files/std2.gno index fe218f8b34a..6698b9ae976 100644 --- a/gnovm/tests/files/std2.gno +++ b/gnovm/tests/files/std2.gno @@ -3,7 +3,7 @@ package main import "std" func main() { - caller := std.GetOrigCaller() + caller := std.OrigCaller() println(caller) } diff --git a/gnovm/tests/files/std3.gno b/gnovm/tests/files/std3.gno index e8d4bc31a12..d002a46b683 100644 --- a/gnovm/tests/files/std3.gno +++ b/gnovm/tests/files/std3.gno @@ -6,8 +6,8 @@ import ( ) func main() { - caller := std.GetOrigCaller() - caller2 := std.GetOrigCaller() + caller := std.OrigCaller() + caller2 := std.OrigCaller() cmp := bytes.Compare([]byte(caller), []byte(caller2)) println(cmp) } diff --git a/gnovm/tests/files/std4.gno b/gnovm/tests/files/std4.gno index d09bc6251c0..aef32507b08 100644 --- a/gnovm/tests/files/std4.gno +++ b/gnovm/tests/files/std4.gno @@ -5,7 +5,7 @@ import ( ) func main() { - caller1 := std.GetCallerAt(1) + caller1 := std.CallerAt(1) println(caller1) } diff --git a/gnovm/tests/files/std5.gno b/gnovm/tests/files/std5.gno index e339d7a6364..b941978f805 100644 --- a/gnovm/tests/files/std5.gno +++ b/gnovm/tests/files/std5.gno @@ -5,9 +5,9 @@ import ( ) func main() { - caller1 := std.GetCallerAt(1) + caller1 := std.CallerAt(1) println(caller1) - caller2 := std.GetCallerAt(2) + caller2 := std.CallerAt(2) println(caller2) } @@ -15,7 +15,7 @@ func main() { // panic: frame not found // callerAt(n) // gonative:std.callerAt -// std.GetCallerAt(2) +// std.CallerAt(2) // std/native.gno:45 // main() // main/files/std5.gno:10 diff --git a/gnovm/tests/files/std6.gno b/gnovm/tests/files/std6.gno index 20943f47d28..27c64503b13 100644 --- a/gnovm/tests/files/std6.gno +++ b/gnovm/tests/files/std6.gno @@ -3,9 +3,9 @@ package main import "std" func inner() { - caller1 := std.GetCallerAt(1) + caller1 := std.CallerAt(1) println(caller1) - caller2 := std.GetCallerAt(2) + caller2 := std.CallerAt(2) println(caller2) } diff --git a/gnovm/tests/files/std7.gno b/gnovm/tests/files/std7.gno index 9d602cc2039..ce767fe59e9 100644 --- a/gnovm/tests/files/std7.gno +++ b/gnovm/tests/files/std7.gno @@ -7,11 +7,11 @@ import ( ) func inner() { - caller1 := std.GetCallerAt(1) + caller1 := std.CallerAt(1) println(caller1) - caller2 := std.GetCallerAt(2) + caller2 := std.CallerAt(2) println(caller2) - caller3 := std.GetCallerAt(3) + caller3 := std.CallerAt(3) println(caller3) } diff --git a/gnovm/tests/files/std8.gno b/gnovm/tests/files/std8.gno index ee717bf16be..8b8bca7e8d7 100644 --- a/gnovm/tests/files/std8.gno +++ b/gnovm/tests/files/std8.gno @@ -7,13 +7,13 @@ import ( ) func inner() { - caller1 := std.GetCallerAt(1) + caller1 := std.CallerAt(1) println(caller1) - caller2 := std.GetCallerAt(2) + caller2 := std.CallerAt(2) println(caller2) - caller3 := std.GetCallerAt(3) + caller3 := std.CallerAt(3) println(caller3) - caller4 := std.GetCallerAt(4) + caller4 := std.CallerAt(4) println(caller4) } @@ -25,7 +25,7 @@ func main() { // panic: frame not found // callerAt(n) // gonative:std.callerAt -// std.GetCallerAt(4) +// std.CallerAt(4) // std/native.gno:45 // fn() // main/files/std8.gno:16 diff --git a/gnovm/tests/files/zrealm_initctx.gno b/gnovm/tests/files/zrealm_initctx.gno index 2fda65e7681..2ff1034f9ae 100644 --- a/gnovm/tests/files/zrealm_initctx.gno +++ b/gnovm/tests/files/zrealm_initctx.gno @@ -10,7 +10,7 @@ var addr = std.Address("test") var addrInit = std.Address("addrInit") func init() { - addr = std.GetOrigCaller() + addr = std.OrigCaller() addrInit = tests.InitOrigCaller() } diff --git a/gnovm/tests/files/zrealm_std0.gno b/gnovm/tests/files/zrealm_std0.gno index 3f6bdae2537..02672de6937 100644 --- a/gnovm/tests/files/zrealm_std0.gno +++ b/gnovm/tests/files/zrealm_std0.gno @@ -6,7 +6,7 @@ import ( ) func main() { - caller := std.GetOrigCaller() + caller := std.OrigCaller() println(caller) } diff --git a/gnovm/tests/files/zrealm_std1.gno b/gnovm/tests/files/zrealm_std1.gno index d75a2c60b71..01da962f949 100644 --- a/gnovm/tests/files/zrealm_std1.gno +++ b/gnovm/tests/files/zrealm_std1.gno @@ -8,14 +8,14 @@ import ( var aset *std.AddressList func init() { - caller := std.GetOrigCaller() + caller := std.OrigCaller() aset = std.NewAddressList() aset.AddAddress(caller) } func main() { println(*aset) - caller := std.GetOrigCaller() + caller := std.OrigCaller() err := aset.AddAddress(caller) println("error:", err) has := aset.HasAddress(caller) diff --git a/gnovm/tests/files/zrealm_std2.gno b/gnovm/tests/files/zrealm_std2.gno index 810210c6160..2d73e9c94c6 100644 --- a/gnovm/tests/files/zrealm_std2.gno +++ b/gnovm/tests/files/zrealm_std2.gno @@ -9,14 +9,14 @@ import ( var aset std.AddressSet func init() { - caller := std.GetOrigCaller() + caller := std.OrigCaller() aset = std.NewAddressList() aset.AddAddress(caller) } func main() { println(*(aset.(*std.AddressList))) - caller := std.GetOrigCaller() + caller := std.OrigCaller() err := aset.AddAddress(caller) println("error:", err) has := aset.HasAddress(caller) diff --git a/gnovm/tests/stdlibs/std/std.gno b/gnovm/tests/stdlibs/std/std.gno index dcb5a64dbb3..f2d67bef40d 100644 --- a/gnovm/tests/stdlibs/std/std.gno +++ b/gnovm/tests/stdlibs/std/std.gno @@ -25,7 +25,7 @@ func TestIssueCoins(addr Address, coins Coins) { testIssueCoins(string(addr), denom, amt) } -// GetCallerAt calls callerAt, which we overwrite +// CallerAt calls callerAt, which we overwrite func callerAt(n int) string // native bindings diff --git a/gnovm/tests/stdlibs/std/std.go b/gnovm/tests/stdlibs/std/std.go index 675194b252f..cd2ac4fae66 100644 --- a/gnovm/tests/stdlibs/std/std.go +++ b/gnovm/tests/stdlibs/std/std.go @@ -71,10 +71,10 @@ func TestSkipHeights(m *gno.Machine, count int64) { func X_callerAt(m *gno.Machine, n int) string { if n <= 0 { - m.Panic(typedString("GetCallerAt requires positive arg")) + m.Panic(typedString("CallerAt requires positive arg")) return "" } - // Add 1 to n to account for the GetCallerAt (gno fn) frame. + // Add 1 to n to account for the CallerAt (gno fn) frame. n++ if n > m.NumFrames()-1 { // NOTE: the last frame's LastPackage @@ -84,7 +84,7 @@ func X_callerAt(m *gno.Machine, n int) string { return "" } if n == m.NumFrames()-1 { - // This makes it consistent with GetOrigCaller and TestSetOrigCaller. + // This makes it consistent with OrigCaller and TestSetOrigCaller. ctx := m.Context.(*TestExecContext) return string(ctx.OrigCaller) } diff --git a/misc/deployments/test5.gno.land/genesis_txs.jsonl b/misc/deployments/test5.gno.land/genesis_txs.jsonl index 7d03fddc523..744c82d6abe 100755 --- a/misc/deployments/test5.gno.land/genesis_txs.jsonl +++ b/misc/deployments/test5.gno.land/genesis_txs.jsonl @@ -24,21 +24,21 @@ {"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"feed","path":"gno.land/p/demo/gnorkle/feed","files":[{"name":"errors.gno","body":"package feed\n\nimport \"errors\"\n\nvar ErrUndefined = errors.New(\"undefined feed\")\n"},{"name":"task.gno","body":"package feed\n\n// Task is a unit of work that can be part of a `Feed` definition. Tasks\n// are executed by agents.\ntype Task interface {\n\tMarshalJSON() ([]byte, error)\n}\n"},{"name":"type.gno","body":"package feed\n\n// Type indicates the type of a feed.\ntype Type int\n\nconst (\n\t// TypeStatic indicates a feed cannot be changed once the first value is committed.\n\tTypeStatic Type = iota\n\t// TypeContinuous indicates a feed can continuously ingest values and will publish\n\t// a new value on request using the values it has ingested.\n\tTypeContinuous\n\t// TypePeriodic indicates a feed can accept one or more values within a certain period\n\t// and will proceed to commit these values at the end up each period to produce an\n\t// aggregate value before starting a new period.\n\tTypePeriodic\n)\n"},{"name":"value.gno","body":"package feed\n\nimport \"time\"\n\n// Value represents a value published by a feed. The `Time` is when the value was published.\ntype Value struct {\n\tString string\n\tTime time.Time\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":"ingester","path":"gno.land/p/demo/gnorkle/ingester","files":[{"name":"errors.gno","body":"package ingester\n\nimport \"errors\"\n\nvar ErrUndefined = errors.New(\"ingester undefined\")\n"},{"name":"type.gno","body":"package ingester\n\n// Type indicates an ingester type.\ntype Type int\n\nconst (\n\t// TypeSingle indicates an ingester that can only ingest a single within a given period or no period.\n\tTypeSingle Type = iota\n\t// TypeMulti indicates an ingester that can ingest multiple within a given period or no period\n\tTypeMulti\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":"message","path":"gno.land/p/demo/gnorkle/message","files":[{"name":"parse.gno","body":"package message\n\nimport \"strings\"\n\n// ParseFunc parses a raw message and returns the message function\n// type extracted from the remainder of the message.\nfunc ParseFunc(rawMsg string) (FuncType, string) {\n\tfuncType, remainder := parseFirstToken(rawMsg)\n\treturn FuncType(funcType), remainder\n}\n\n// ParseID parses a raw message and returns the ID extracted from\n// the remainder of the message.\nfunc ParseID(rawMsg string) (string, string) {\n\treturn parseFirstToken(rawMsg)\n}\n\nfunc parseFirstToken(rawMsg string) (string, string) {\n\tmsgParts := strings.SplitN(rawMsg, \",\", 2)\n\tif len(msgParts) \u003c 2 {\n\t\treturn msgParts[0], \"\"\n\t}\n\n\treturn msgParts[0], msgParts[1]\n}\n"},{"name":"parse_test.gno","body":"package message_test\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/gnorkle/message\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestParseFunc(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput string\n\t\texpFuncType message.FuncType\n\t\texpRemainder string\n\t}{\n\t\t{\n\t\t\tname: \"empty\",\n\t\t},\n\t\t{\n\t\t\tname: \"func only\",\n\t\t\tinput: \"ingest\",\n\t\t\texpFuncType: message.FuncTypeIngest,\n\t\t},\n\t\t{\n\t\t\tname: \"func with short remainder\",\n\t\t\tinput: \"commit,asdf\",\n\t\t\texpFuncType: message.FuncTypeCommit,\n\t\t\texpRemainder: \"asdf\",\n\t\t},\n\t\t{\n\t\t\tname: \"func with long remainder\",\n\t\t\tinput: \"request,hello,world,goodbye\",\n\t\t\texpFuncType: message.FuncTypeRequest,\n\t\t\texpRemainder: \"hello,world,goodbye\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tfuncType, remainder := message.ParseFunc(tt.input)\n\n\t\t\tuassert.Equal(t, string(tt.expFuncType), string(funcType))\n\t\t\tuassert.Equal(t, tt.expRemainder, remainder)\n\t\t})\n\t}\n}\n"},{"name":"type.gno","body":"package message\n\n// FuncType is the type of function that is being called by the agent.\ntype FuncType string\n\nconst (\n\t// FuncTypeIngest means the agent is sending data for ingestion.\n\tFuncTypeIngest FuncType = \"ingest\"\n\t// FuncTypeCommit means the agent is requesting a feed commit the transitive data\n\t// being held by its ingester.\n\tFuncTypeCommit FuncType = \"commit\"\n\t// FuncTypeRequest means the agent is requesting feed definitions for all those\n\t// that it is whitelisted to provide data for.\n\tFuncTypeRequest FuncType = \"request\"\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":"gnorkle","path":"gno.land/p/demo/gnorkle/gnorkle","files":[{"name":"feed.gno","body":"package gnorkle\n\nimport (\n\t\"gno.land/p/demo/gnorkle/feed\"\n\t\"gno.land/p/demo/gnorkle/message\"\n)\n\n// Feed is an abstraction used by a gnorkle `Instance` to ingest data from\n// agents and provide data feeds to consumers.\ntype Feed interface {\n\tID() string\n\tType() feed.Type\n\tValue() (value feed.Value, dataType string, consumable bool)\n\tIngest(funcType message.FuncType, rawMessage, providerAddress string) error\n\tMarshalJSON() ([]byte, error)\n\tTasks() []feed.Task\n\tIsActive() bool\n}\n\n// FeedWithWhitelist associates a `Whitelist` with a `Feed`.\ntype FeedWithWhitelist struct {\n\tFeed\n\tWhitelist\n}\n"},{"name":"ingester.gno","body":"package gnorkle\n\nimport \"gno.land/p/demo/gnorkle/ingester\"\n\n// Ingester is the abstraction that allows a `Feed` to ingest data from agents\n// and commit it to storage using zero or more intermediate aggregation steps.\ntype Ingester interface {\n\tType() ingester.Type\n\tIngest(value, providerAddress string) (canAutoCommit bool, err error)\n\tCommitValue(storage Storage, providerAddress string) error\n}\n"},{"name":"instance.gno","body":"package gnorkle\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/gnorkle/agent\"\n\t\"gno.land/p/demo/gnorkle/feed\"\n\t\"gno.land/p/demo/gnorkle/message\"\n)\n\n// Instance is a single instance of an oracle.\ntype Instance struct {\n\tfeeds *avl.Tree\n\twhitelist agent.Whitelist\n}\n\n// NewInstance creates a new instance of an oracle.\nfunc NewInstance() *Instance {\n\treturn \u0026Instance{\n\t\tfeeds: avl.NewTree(),\n\t}\n}\n\nfunc assertValidID(id string) error {\n\tif len(id) == 0 {\n\t\treturn errors.New(\"feed ids cannot be empty\")\n\t}\n\n\tif strings.Contains(id, \",\") {\n\t\treturn errors.New(\"feed ids cannot contain commas\")\n\t}\n\n\treturn nil\n}\n\nfunc (i *Instance) assertFeedDoesNotExist(id string) error {\n\tif i.feeds.Has(id) {\n\t\treturn errors.New(\"feed already exists\")\n\t}\n\n\treturn nil\n}\n\n// AddFeeds adds feeds to the instance with empty whitelists.\nfunc (i *Instance) AddFeeds(feeds ...Feed) error {\n\tfor _, feed := range feeds {\n\t\tif err := assertValidID(feed.ID()); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err := i.assertFeedDoesNotExist(feed.ID()); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\ti.feeds.Set(\n\t\t\tfeed.ID(),\n\t\t\tFeedWithWhitelist{\n\t\t\t\tWhitelist: new(agent.Whitelist),\n\t\t\t\tFeed: feed,\n\t\t\t},\n\t\t)\n\t}\n\n\treturn nil\n}\n\n// AddFeedsWithWhitelists adds feeds to the instance with the given whitelists.\nfunc (i *Instance) AddFeedsWithWhitelists(feeds ...FeedWithWhitelist) error {\n\tfor _, feed := range feeds {\n\t\tif err := i.assertFeedDoesNotExist(feed.ID()); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := assertValidID(feed.ID()); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\ti.feeds.Set(\n\t\t\tfeed.ID(),\n\t\t\tFeedWithWhitelist{\n\t\t\t\tWhitelist: feed.Whitelist,\n\t\t\t\tFeed: feed,\n\t\t\t},\n\t\t)\n\t}\n\n\treturn nil\n}\n\n// RemoveFeed removes a feed from the instance.\nfunc (i *Instance) RemoveFeed(id string) {\n\ti.feeds.Remove(id)\n}\n\n// PostMessageHandler is a type that allows for post-processing of feed state after a feed\n// ingests a message from an agent.\ntype PostMessageHandler interface {\n\tHandle(i *Instance, funcType message.FuncType, feed Feed) error\n}\n\n// HandleMessage handles a message from an agent and routes to either the logic that returns\n// feed definitions or the logic that allows a feed to ingest a message.\n//\n// TODO: Consider further message types that could allow administrative action such as modifying\n// a feed's whitelist without the owner of this oracle having to maintain a reference to it.\nfunc (i *Instance) HandleMessage(msg string, postHandler PostMessageHandler) (string, error) {\n\tcaller := string(std.GetOrigCaller())\n\n\tfuncType, msg := message.ParseFunc(msg)\n\n\tswitch funcType {\n\tcase message.FuncTypeRequest:\n\t\treturn i.GetFeedDefinitions(caller)\n\n\tdefault:\n\t\tid, msg := message.ParseID(msg)\n\t\tif err := assertValidID(id); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\tfeedWithWhitelist, err := i.getFeedWithWhitelist(id)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\tif !addressIsWhitelisted(\u0026i.whitelist, feedWithWhitelist, caller, nil) {\n\t\t\treturn \"\", errors.New(\"caller not whitelisted\")\n\t\t}\n\n\t\tif err := feedWithWhitelist.Ingest(funcType, msg, caller); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\tif postHandler != nil {\n\t\t\tpostHandler.Handle(i, funcType, feedWithWhitelist)\n\t\t}\n\t}\n\n\treturn \"\", nil\n}\n\nfunc (i *Instance) getFeed(id string) (Feed, error) {\n\tuntypedFeed, ok := i.feeds.Get(id)\n\tif !ok {\n\t\treturn nil, errors.New(\"invalid ingest id: \" + id)\n\t}\n\n\tfeed, ok := untypedFeed.(Feed)\n\tif !ok {\n\t\treturn nil, errors.New(\"invalid feed type\")\n\t}\n\n\treturn feed, nil\n}\n\nfunc (i *Instance) getFeedWithWhitelist(id string) (FeedWithWhitelist, error) {\n\tuntypedFeedWithWhitelist, ok := i.feeds.Get(id)\n\tif !ok {\n\t\treturn FeedWithWhitelist{}, errors.New(\"invalid ingest id: \" + id)\n\t}\n\n\tfeedWithWhitelist, ok := untypedFeedWithWhitelist.(FeedWithWhitelist)\n\tif !ok {\n\t\treturn FeedWithWhitelist{}, errors.New(\"invalid feed with whitelist type\")\n\t}\n\n\treturn feedWithWhitelist, nil\n}\n\n// GetFeedValue returns the most recently published value of a feed along with a string\n// representation of the value's type and boolean indicating whether the value is\n// okay for consumption.\nfunc (i *Instance) GetFeedValue(id string) (feed.Value, string, bool, error) {\n\tfoundFeed, err := i.getFeed(id)\n\tif err != nil {\n\t\treturn feed.Value{}, \"\", false, err\n\t}\n\n\tvalue, valueType, consumable := foundFeed.Value()\n\treturn value, valueType, consumable, nil\n}\n\n// GetFeedDefinitions returns a JSON string representing the feed definitions for which the given\n// agent address is whitelisted to provide values for ingestion.\nfunc (i *Instance) GetFeedDefinitions(forAddress string) (string, error) {\n\tinstanceHasAddressWhitelisted := !i.whitelist.HasDefinition() || i.whitelist.HasAddress(forAddress)\n\n\tbuf := new(strings.Builder)\n\tbuf.WriteString(\"[\")\n\tfirst := true\n\tvar err error\n\n\t// The boolean value returned by this callback function indicates whether to stop iterating.\n\ti.feeds.Iterate(\"\", \"\", func(_ string, value interface{}) bool {\n\t\tfeedWithWhitelist, ok := value.(FeedWithWhitelist)\n\t\tif !ok {\n\t\t\terr = errors.New(\"invalid feed type\")\n\t\t\treturn true\n\t\t}\n\n\t\t// Don't give agents the ability to try to publish to inactive feeds.\n\t\tif !feedWithWhitelist.IsActive() {\n\t\t\treturn false\n\t\t}\n\n\t\t// Skip feeds the address is not whitelisted for.\n\t\tif !addressIsWhitelisted(\u0026i.whitelist, feedWithWhitelist, forAddress, \u0026instanceHasAddressWhitelisted) {\n\t\t\treturn false\n\t\t}\n\n\t\tvar taskBytes []byte\n\t\tif taskBytes, err = feedWithWhitelist.Feed.MarshalJSON(); err != nil {\n\t\t\treturn true\n\t\t}\n\n\t\t// Guard against any tasks that shouldn't be returned; maybe they are not active because they have\n\t\t// already been completed.\n\t\tif len(taskBytes) == 0 {\n\t\t\treturn false\n\t\t}\n\n\t\tif !first {\n\t\t\tbuf.WriteString(\",\")\n\t\t}\n\n\t\tfirst = false\n\t\tbuf.Write(taskBytes)\n\t\treturn false\n\t})\n\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tbuf.WriteString(\"]\")\n\treturn buf.String(), nil\n}\n"},{"name":"storage.gno","body":"package gnorkle\n\nimport \"gno.land/p/demo/gnorkle/feed\"\n\n// Storage defines how published feed values should be read\n// and written.\ntype Storage interface {\n\tPut(value string) error\n\tGetLatest() feed.Value\n\tGetHistory() []feed.Value\n}\n"},{"name":"whitelist.gno","body":"package gnorkle\n\n// Whitelist is used to manage which agents are allowed to interact.\ntype Whitelist interface {\n\tClearAddresses()\n\tAddAddresses(addresses []string)\n\tRemoveAddress(address string)\n\tHasDefinition() bool\n\tHasAddress(address string) bool\n}\n\n// ClearWhitelist clears the whitelist of the instance or feed depending on the feed ID.\nfunc (i *Instance) ClearWhitelist(feedID string) error {\n\tif feedID == \"\" {\n\t\ti.whitelist.ClearAddresses()\n\t\treturn nil\n\t}\n\n\tfeedWithWhitelist, err := i.getFeedWithWhitelist(feedID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfeedWithWhitelist.ClearAddresses()\n\treturn nil\n}\n\n// AddToWhitelist adds the given addresses to the whitelist of the instance or feed depending on the feed ID.\nfunc (i *Instance) AddToWhitelist(feedID string, addresses []string) error {\n\tif feedID == \"\" {\n\t\ti.whitelist.AddAddresses(addresses)\n\t\treturn nil\n\t}\n\n\tfeedWithWhitelist, err := i.getFeedWithWhitelist(feedID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfeedWithWhitelist.AddAddresses(addresses)\n\treturn nil\n}\n\n// RemoveFromWhitelist removes the given address from the whitelist of the instance or feed depending on the feed ID.\nfunc (i *Instance) RemoveFromWhitelist(feedID string, address string) error {\n\tif feedID == \"\" {\n\t\ti.whitelist.RemoveAddress(address)\n\t\treturn nil\n\t}\n\n\tfeedWithWhitelist, err := i.getFeedWithWhitelist(feedID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfeedWithWhitelist.RemoveAddress(address)\n\treturn nil\n}\n\n// addressWhiteListed returns true if:\n// - the feed has a white list and the address is whitelisted, or\n// - the feed has no white list and the instance has a white list and the address is whitelisted, or\n// - the feed has no white list and the instance has no white list.\nfunc addressIsWhitelisted(instanceWhitelist, feedWhitelist Whitelist, address string, instanceWhitelistedOverride *bool) bool {\n\t// A feed whitelist takes priority, so it will return false if the feed has a whitelist and the caller is\n\t// not a part of it. An empty whitelist defers to the instance whitelist.\n\tif feedWhitelist != nil {\n\t\tif feedWhitelist.HasDefinition() \u0026\u0026 !feedWhitelist.HasAddress(address) {\n\t\t\treturn false\n\t\t}\n\n\t\t// Getting to this point means that one of the following is true:\n\t\t// - the feed has no defined whitelist (so it can't possibly have the address whitelisted)\n\t\t// - the feed has a defined whitelist and the caller is a part of it\n\t\t//\n\t\t// In this case, we can be sure that the boolean indicating whether the feed has this address whitelisted\n\t\t// is equivalent to the boolean indicating whether the feed has a defined whitelist.\n\t\tif feedWhitelist.HasDefinition() {\n\t\t\treturn true\n\t\t}\n\t}\n\n\tif instanceWhitelistedOverride != nil {\n\t\treturn *instanceWhitelistedOverride\n\t}\n\n\t// We were unable able to determine whether this address is allowed after looking at the feed whitelist,\n\t// so fall back to the instance whitelist. A complete absence of values in the instance whitelist means\n\t// that the instance has no whitelist so we can return true because everything is allowed by default.\n\tif instanceWhitelist == nil || !instanceWhitelist.HasDefinition() {\n\t\treturn true\n\t}\n\n\t// The instance whitelist is defined so if the address is present then it is allowed.\n\treturn instanceWhitelist.HasAddress(address)\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":"gnorkle","path":"gno.land/p/demo/gnorkle/gnorkle","files":[{"name":"feed.gno","body":"package gnorkle\n\nimport (\n\t\"gno.land/p/demo/gnorkle/feed\"\n\t\"gno.land/p/demo/gnorkle/message\"\n)\n\n// Feed is an abstraction used by a gnorkle `Instance` to ingest data from\n// agents and provide data feeds to consumers.\ntype Feed interface {\n\tID() string\n\tType() feed.Type\n\tValue() (value feed.Value, dataType string, consumable bool)\n\tIngest(funcType message.FuncType, rawMessage, providerAddress string) error\n\tMarshalJSON() ([]byte, error)\n\tTasks() []feed.Task\n\tIsActive() bool\n}\n\n// FeedWithWhitelist associates a `Whitelist` with a `Feed`.\ntype FeedWithWhitelist struct {\n\tFeed\n\tWhitelist\n}\n"},{"name":"ingester.gno","body":"package gnorkle\n\nimport \"gno.land/p/demo/gnorkle/ingester\"\n\n// Ingester is the abstraction that allows a `Feed` to ingest data from agents\n// and commit it to storage using zero or more intermediate aggregation steps.\ntype Ingester interface {\n\tType() ingester.Type\n\tIngest(value, providerAddress string) (canAutoCommit bool, err error)\n\tCommitValue(storage Storage, providerAddress string) error\n}\n"},{"name":"instance.gno","body":"package gnorkle\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/gnorkle/agent\"\n\t\"gno.land/p/demo/gnorkle/feed\"\n\t\"gno.land/p/demo/gnorkle/message\"\n)\n\n// Instance is a single instance of an oracle.\ntype Instance struct {\n\tfeeds *avl.Tree\n\twhitelist agent.Whitelist\n}\n\n// NewInstance creates a new instance of an oracle.\nfunc NewInstance() *Instance {\n\treturn \u0026Instance{\n\t\tfeeds: avl.NewTree(),\n\t}\n}\n\nfunc assertValidID(id string) error {\n\tif len(id) == 0 {\n\t\treturn errors.New(\"feed ids cannot be empty\")\n\t}\n\n\tif strings.Contains(id, \",\") {\n\t\treturn errors.New(\"feed ids cannot contain commas\")\n\t}\n\n\treturn nil\n}\n\nfunc (i *Instance) assertFeedDoesNotExist(id string) error {\n\tif i.feeds.Has(id) {\n\t\treturn errors.New(\"feed already exists\")\n\t}\n\n\treturn nil\n}\n\n// AddFeeds adds feeds to the instance with empty whitelists.\nfunc (i *Instance) AddFeeds(feeds ...Feed) error {\n\tfor _, feed := range feeds {\n\t\tif err := assertValidID(feed.ID()); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err := i.assertFeedDoesNotExist(feed.ID()); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\ti.feeds.Set(\n\t\t\tfeed.ID(),\n\t\t\tFeedWithWhitelist{\n\t\t\t\tWhitelist: new(agent.Whitelist),\n\t\t\t\tFeed: feed,\n\t\t\t},\n\t\t)\n\t}\n\n\treturn nil\n}\n\n// AddFeedsWithWhitelists adds feeds to the instance with the given whitelists.\nfunc (i *Instance) AddFeedsWithWhitelists(feeds ...FeedWithWhitelist) error {\n\tfor _, feed := range feeds {\n\t\tif err := i.assertFeedDoesNotExist(feed.ID()); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := assertValidID(feed.ID()); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\ti.feeds.Set(\n\t\t\tfeed.ID(),\n\t\t\tFeedWithWhitelist{\n\t\t\t\tWhitelist: feed.Whitelist,\n\t\t\t\tFeed: feed,\n\t\t\t},\n\t\t)\n\t}\n\n\treturn nil\n}\n\n// RemoveFeed removes a feed from the instance.\nfunc (i *Instance) RemoveFeed(id string) {\n\ti.feeds.Remove(id)\n}\n\n// PostMessageHandler is a type that allows for post-processing of feed state after a feed\n// ingests a message from an agent.\ntype PostMessageHandler interface {\n\tHandle(i *Instance, funcType message.FuncType, feed Feed) error\n}\n\n// HandleMessage handles a message from an agent and routes to either the logic that returns\n// feed definitions or the logic that allows a feed to ingest a message.\n//\n// TODO: Consider further message types that could allow administrative action such as modifying\n// a feed's whitelist without the owner of this oracle having to maintain a reference to it.\nfunc (i *Instance) HandleMessage(msg string, postHandler PostMessageHandler) (string, error) {\n\tcaller := string(std.OrigCaller())\n\n\tfuncType, msg := message.ParseFunc(msg)\n\n\tswitch funcType {\n\tcase message.FuncTypeRequest:\n\t\treturn i.GetFeedDefinitions(caller)\n\n\tdefault:\n\t\tid, msg := message.ParseID(msg)\n\t\tif err := assertValidID(id); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\tfeedWithWhitelist, err := i.getFeedWithWhitelist(id)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\tif !addressIsWhitelisted(\u0026i.whitelist, feedWithWhitelist, caller, nil) {\n\t\t\treturn \"\", errors.New(\"caller not whitelisted\")\n\t\t}\n\n\t\tif err := feedWithWhitelist.Ingest(funcType, msg, caller); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\tif postHandler != nil {\n\t\t\tpostHandler.Handle(i, funcType, feedWithWhitelist)\n\t\t}\n\t}\n\n\treturn \"\", nil\n}\n\nfunc (i *Instance) getFeed(id string) (Feed, error) {\n\tuntypedFeed, ok := i.feeds.Get(id)\n\tif !ok {\n\t\treturn nil, errors.New(\"invalid ingest id: \" + id)\n\t}\n\n\tfeed, ok := untypedFeed.(Feed)\n\tif !ok {\n\t\treturn nil, errors.New(\"invalid feed type\")\n\t}\n\n\treturn feed, nil\n}\n\nfunc (i *Instance) getFeedWithWhitelist(id string) (FeedWithWhitelist, error) {\n\tuntypedFeedWithWhitelist, ok := i.feeds.Get(id)\n\tif !ok {\n\t\treturn FeedWithWhitelist{}, errors.New(\"invalid ingest id: \" + id)\n\t}\n\n\tfeedWithWhitelist, ok := untypedFeedWithWhitelist.(FeedWithWhitelist)\n\tif !ok {\n\t\treturn FeedWithWhitelist{}, errors.New(\"invalid feed with whitelist type\")\n\t}\n\n\treturn feedWithWhitelist, nil\n}\n\n// GetFeedValue returns the most recently published value of a feed along with a string\n// representation of the value's type and boolean indicating whether the value is\n// okay for consumption.\nfunc (i *Instance) GetFeedValue(id string) (feed.Value, string, bool, error) {\n\tfoundFeed, err := i.getFeed(id)\n\tif err != nil {\n\t\treturn feed.Value{}, \"\", false, err\n\t}\n\n\tvalue, valueType, consumable := foundFeed.Value()\n\treturn value, valueType, consumable, nil\n}\n\n// GetFeedDefinitions returns a JSON string representing the feed definitions for which the given\n// agent address is whitelisted to provide values for ingestion.\nfunc (i *Instance) GetFeedDefinitions(forAddress string) (string, error) {\n\tinstanceHasAddressWhitelisted := !i.whitelist.HasDefinition() || i.whitelist.HasAddress(forAddress)\n\n\tbuf := new(strings.Builder)\n\tbuf.WriteString(\"[\")\n\tfirst := true\n\tvar err error\n\n\t// The boolean value returned by this callback function indicates whether to stop iterating.\n\ti.feeds.Iterate(\"\", \"\", func(_ string, value interface{}) bool {\n\t\tfeedWithWhitelist, ok := value.(FeedWithWhitelist)\n\t\tif !ok {\n\t\t\terr = errors.New(\"invalid feed type\")\n\t\t\treturn true\n\t\t}\n\n\t\t// Don't give agents the ability to try to publish to inactive feeds.\n\t\tif !feedWithWhitelist.IsActive() {\n\t\t\treturn false\n\t\t}\n\n\t\t// Skip feeds the address is not whitelisted for.\n\t\tif !addressIsWhitelisted(\u0026i.whitelist, feedWithWhitelist, forAddress, \u0026instanceHasAddressWhitelisted) {\n\t\t\treturn false\n\t\t}\n\n\t\tvar taskBytes []byte\n\t\tif taskBytes, err = feedWithWhitelist.Feed.MarshalJSON(); err != nil {\n\t\t\treturn true\n\t\t}\n\n\t\t// Guard against any tasks that shouldn't be returned; maybe they are not active because they have\n\t\t// already been completed.\n\t\tif len(taskBytes) == 0 {\n\t\t\treturn false\n\t\t}\n\n\t\tif !first {\n\t\t\tbuf.WriteString(\",\")\n\t\t}\n\n\t\tfirst = false\n\t\tbuf.Write(taskBytes)\n\t\treturn false\n\t})\n\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tbuf.WriteString(\"]\")\n\treturn buf.String(), nil\n}\n"},{"name":"storage.gno","body":"package gnorkle\n\nimport \"gno.land/p/demo/gnorkle/feed\"\n\n// Storage defines how published feed values should be read\n// and written.\ntype Storage interface {\n\tPut(value string) error\n\tGetLatest() feed.Value\n\tGetHistory() []feed.Value\n}\n"},{"name":"whitelist.gno","body":"package gnorkle\n\n// Whitelist is used to manage which agents are allowed to interact.\ntype Whitelist interface {\n\tClearAddresses()\n\tAddAddresses(addresses []string)\n\tRemoveAddress(address string)\n\tHasDefinition() bool\n\tHasAddress(address string) bool\n}\n\n// ClearWhitelist clears the whitelist of the instance or feed depending on the feed ID.\nfunc (i *Instance) ClearWhitelist(feedID string) error {\n\tif feedID == \"\" {\n\t\ti.whitelist.ClearAddresses()\n\t\treturn nil\n\t}\n\n\tfeedWithWhitelist, err := i.getFeedWithWhitelist(feedID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfeedWithWhitelist.ClearAddresses()\n\treturn nil\n}\n\n// AddToWhitelist adds the given addresses to the whitelist of the instance or feed depending on the feed ID.\nfunc (i *Instance) AddToWhitelist(feedID string, addresses []string) error {\n\tif feedID == \"\" {\n\t\ti.whitelist.AddAddresses(addresses)\n\t\treturn nil\n\t}\n\n\tfeedWithWhitelist, err := i.getFeedWithWhitelist(feedID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfeedWithWhitelist.AddAddresses(addresses)\n\treturn nil\n}\n\n// RemoveFromWhitelist removes the given address from the whitelist of the instance or feed depending on the feed ID.\nfunc (i *Instance) RemoveFromWhitelist(feedID string, address string) error {\n\tif feedID == \"\" {\n\t\ti.whitelist.RemoveAddress(address)\n\t\treturn nil\n\t}\n\n\tfeedWithWhitelist, err := i.getFeedWithWhitelist(feedID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfeedWithWhitelist.RemoveAddress(address)\n\treturn nil\n}\n\n// addressWhiteListed returns true if:\n// - the feed has a white list and the address is whitelisted, or\n// - the feed has no white list and the instance has a white list and the address is whitelisted, or\n// - the feed has no white list and the instance has no white list.\nfunc addressIsWhitelisted(instanceWhitelist, feedWhitelist Whitelist, address string, instanceWhitelistedOverride *bool) bool {\n\t// A feed whitelist takes priority, so it will return false if the feed has a whitelist and the caller is\n\t// not a part of it. An empty whitelist defers to the instance whitelist.\n\tif feedWhitelist != nil {\n\t\tif feedWhitelist.HasDefinition() \u0026\u0026 !feedWhitelist.HasAddress(address) {\n\t\t\treturn false\n\t\t}\n\n\t\t// Getting to this point means that one of the following is true:\n\t\t// - the feed has no defined whitelist (so it can't possibly have the address whitelisted)\n\t\t// - the feed has a defined whitelist and the caller is a part of it\n\t\t//\n\t\t// In this case, we can be sure that the boolean indicating whether the feed has this address whitelisted\n\t\t// is equivalent to the boolean indicating whether the feed has a defined whitelist.\n\t\tif feedWhitelist.HasDefinition() {\n\t\t\treturn true\n\t\t}\n\t}\n\n\tif instanceWhitelistedOverride != nil {\n\t\treturn *instanceWhitelistedOverride\n\t}\n\n\t// We were unable able to determine whether this address is allowed after looking at the feed whitelist,\n\t// so fall back to the instance whitelist. A complete absence of values in the instance whitelist means\n\t// that the instance has no whitelist so we can return true because everything is allowed by default.\n\tif instanceWhitelist == nil || !instanceWhitelist.HasDefinition() {\n\t\treturn true\n\t}\n\n\t// The instance whitelist is defined so if the address is present then it is allowed.\n\treturn instanceWhitelist.HasAddress(address)\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":"storage","path":"gno.land/p/demo/gnorkle/storage","files":[{"name":"errors.gno","body":"package storage\n\nimport \"errors\"\n\nvar ErrUndefined = errors.New(\"undefined storage\")\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":"simple","path":"gno.land/p/demo/gnorkle/storage/simple","files":[{"name":"storage.gno","body":"package simple\n\nimport (\n\t\"time\"\n\n\t\"gno.land/p/demo/gnorkle/feed\"\n\t\"gno.land/p/demo/gnorkle/storage\"\n)\n\n// Storage is simple, bounded storage for published feed values.\ntype Storage struct {\n\tvalues []feed.Value\n\tmaxValues uint\n}\n\n// NewStorage creates a new Storage with the given maximum number of values.\n// If maxValues is 0, the storage is bounded to a size of one. If this is not desirable,\n// then don't provide a value of 0.\nfunc NewStorage(maxValues uint) *Storage {\n\tif maxValues == 0 {\n\t\tmaxValues = 1\n\t}\n\n\treturn \u0026Storage{\n\t\tmaxValues: maxValues,\n\t}\n}\n\n// Put adds a new value to the storage. If the storage is full, the oldest value\n// is removed. If maxValues is 0, the storage is bounded to a size of one.\nfunc (s *Storage) Put(value string) error {\n\tif s == nil {\n\t\treturn storage.ErrUndefined\n\t}\n\n\ts.values = append(s.values, feed.Value{String: value, Time: time.Now()})\n\tif uint(len(s.values)) \u003e s.maxValues {\n\t\ts.values = s.values[1:]\n\t}\n\n\treturn nil\n}\n\n// GetLatest returns the most recently added value, or an empty value if none exist.\nfunc (s Storage) GetLatest() feed.Value {\n\tif len(s.values) == 0 {\n\t\treturn feed.Value{}\n\t}\n\n\treturn s.values[len(s.values)-1]\n}\n\n// GetHistory returns all values in the storage, from oldest to newest.\nfunc (s Storage) GetHistory() []feed.Value {\n\treturn s.values\n}\n"},{"name":"storage_test.gno","body":"package simple_test\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/gnorkle/storage\"\n\t\"gno.land/p/demo/gnorkle/storage/simple\"\n\t\"gno.land/p/demo/uassert\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestStorage(t *testing.T) {\n\tvar undefinedStorage *simple.Storage\n\terr := undefinedStorage.Put(\"\")\n\tuassert.ErrorIs(t, err, storage.ErrUndefined, \"expected storage.ErrUndefined on undefined storage\")\n\n\ttests := []struct {\n\t\tname string\n\t\tvaluesToPut []string\n\t\texpLatestValueString string\n\t\texpLatestValueTimeIsZero bool\n\t\texpHistoricalValueStrings []string\n\t}{\n\t\t{\n\t\t\tname: \"empty\",\n\t\t\texpLatestValueTimeIsZero: true,\n\t\t},\n\t\t{\n\t\t\tname: \"one value\",\n\t\t\tvaluesToPut: []string{\"one\"},\n\t\t\texpLatestValueString: \"one\",\n\t\t\texpHistoricalValueStrings: []string{\"one\"},\n\t\t},\n\t\t{\n\t\t\tname: \"two values\",\n\t\t\tvaluesToPut: []string{\"one\", \"two\"},\n\t\t\texpLatestValueString: \"two\",\n\t\t\texpHistoricalValueStrings: []string{\"one\", \"two\"},\n\t\t},\n\t\t{\n\t\t\tname: \"three values\",\n\t\t\tvaluesToPut: []string{\"one\", \"two\", \"three\"},\n\t\t\texpLatestValueString: \"three\",\n\t\t\texpHistoricalValueStrings: []string{\"two\", \"three\"},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tsimpleStorage := simple.NewStorage(2)\n\t\t\tfor _, value := range tt.valuesToPut {\n\t\t\t\terr := simpleStorage.Put(value)\n\t\t\t\turequire.NoError(t, err, \"unexpected error putting value in storage\")\n\t\t\t}\n\n\t\t\tlatestValue := simpleStorage.GetLatest()\n\t\t\tuassert.Equal(t, tt.expLatestValueString, latestValue.String)\n\t\t\tuassert.Equal(t, tt.expLatestValueTimeIsZero, latestValue.Time.IsZero())\n\n\t\t\thistoricalValues := simpleStorage.GetHistory()\n\t\t\turequire.Equal(t, len(tt.expHistoricalValueStrings), len(historicalValues), \"historical values length does not match\")\n\n\t\t\tfor i, expValue := range tt.expHistoricalValueStrings {\n\t\t\t\tuassert.Equal(t, historicalValues[i].String, expValue)\n\t\t\t\turequire.False(t, historicalValues[i].Time.IsZero(), ufmt.Sprintf(\"unexpeced zero time for historical value at index %d\", i))\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":"single","path":"gno.land/p/demo/gnorkle/ingesters/single","files":[{"name":"ingester.gno","body":"package single\n\nimport (\n\t\"gno.land/p/demo/gnorkle/gnorkle\"\n\t\"gno.land/p/demo/gnorkle/ingester\"\n)\n\n// ValueIngester is an ingester that ingests a single value.\ntype ValueIngester struct {\n\tvalue string\n}\n\n// Type returns the type of the ingester.\nfunc (i *ValueIngester) Type() ingester.Type {\n\treturn ingester.TypeSingle\n}\n\n// Ingest ingests a value provided by the given agent address.\nfunc (i *ValueIngester) Ingest(value, providerAddress string) (bool, error) {\n\tif i == nil {\n\t\treturn false, ingester.ErrUndefined\n\t}\n\n\ti.value = value\n\treturn true, nil\n}\n\n// CommitValue commits the ingested value to the given storage instance.\nfunc (i *ValueIngester) CommitValue(valueStorer gnorkle.Storage, providerAddress string) error {\n\tif i == nil {\n\t\treturn ingester.ErrUndefined\n\t}\n\n\treturn valueStorer.Put(i.value)\n}\n"},{"name":"ingester_test.gno","body":"package single_test\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/gnorkle/ingester\"\n\t\"gno.land/p/demo/gnorkle/ingesters/single\"\n\t\"gno.land/p/demo/gnorkle/storage/simple\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestValueIngester(t *testing.T) {\n\tstorage := simple.NewStorage(1)\n\n\tvar undefinedIngester *single.ValueIngester\n\t_, err := undefinedIngester.Ingest(\"asdf\", \"gno11111\")\n\tuassert.ErrorIs(t, err, ingester.ErrUndefined, \"undefined ingester call to Ingest should return ingester.ErrUndefined\")\n\n\terr = undefinedIngester.CommitValue(storage, \"gno11111\")\n\tuassert.ErrorIs(t, err, ingester.ErrUndefined, \"undefined ingester call to CommitValue should return ingester.ErrUndefined\")\n\n\tvar valueIngester single.ValueIngester\n\ttyp := valueIngester.Type()\n\tuassert.Equal(t, int(ingester.TypeSingle), int(typ), \"single value ingester should return type ingester.TypeSingle\")\n\n\tingestValue := \"value\"\n\tautocommit, err := valueIngester.Ingest(ingestValue, \"gno11111\")\n\tuassert.True(t, autocommit, \"single value ingester should return autocommit true\")\n\tuassert.NoError(t, err)\n\n\terr = valueIngester.CommitValue(storage, \"gno11111\")\n\tuassert.NoError(t, err)\n\n\tlatestValue := storage.GetLatest()\n\tuassert.Equal(t, ingestValue, latestValue.String)\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":"static","path":"gno.land/p/demo/gnorkle/feeds/static","files":[{"name":"feed.gno","body":"package static\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"errors\"\n\n\t\"gno.land/p/demo/gnorkle/feed\"\n\t\"gno.land/p/demo/gnorkle/gnorkle\"\n\t\"gno.land/p/demo/gnorkle/ingesters/single\"\n\t\"gno.land/p/demo/gnorkle/message\"\n\t\"gno.land/p/demo/gnorkle/storage/simple\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Feed is a static feed.\ntype Feed struct {\n\tid string\n\tisLocked bool\n\tvalueDataType string\n\tingester gnorkle.Ingester\n\tstorage gnorkle.Storage\n\ttasks []feed.Task\n}\n\n// NewFeed creates a new static feed.\nfunc NewFeed(\n\tid string,\n\tvalueDataType string,\n\tingester gnorkle.Ingester,\n\tstorage gnorkle.Storage,\n\ttasks ...feed.Task,\n) *Feed {\n\treturn \u0026Feed{\n\t\tid: id,\n\t\tvalueDataType: valueDataType,\n\t\tingester: ingester,\n\t\tstorage: storage,\n\t\ttasks: tasks,\n\t}\n}\n\n// NewSingleValueFeed is a convenience function for creating a static feed\n// that autocommits a value after a single ingestion.\nfunc NewSingleValueFeed(\n\tid string,\n\tvalueDataType string,\n\ttasks ...feed.Task,\n) *Feed {\n\treturn NewFeed(\n\t\tid,\n\t\tvalueDataType,\n\t\t\u0026single.ValueIngester{},\n\t\tsimple.NewStorage(1),\n\t\ttasks...,\n\t)\n}\n\n// ID returns the feed's ID.\nfunc (f Feed) ID() string {\n\treturn f.id\n}\n\n// Type returns the feed's type.\nfunc (f Feed) Type() feed.Type {\n\treturn feed.TypeStatic\n}\n\n// Ingest ingests a message into the feed. It either adds the value to the ingester's\n// pending values or commits the value to the storage.\nfunc (f *Feed) Ingest(funcType message.FuncType, msg, providerAddress string) error {\n\tif f == nil {\n\t\treturn feed.ErrUndefined\n\t}\n\n\tif f.isLocked {\n\t\treturn errors.New(\"feed locked\")\n\t}\n\n\tswitch funcType {\n\tcase message.FuncTypeIngest:\n\t\t// Autocommit the ingester's value if it's a single value ingester\n\t\t// because this is a static feed and this is the only value it will ever have.\n\t\tif canAutoCommit, err := f.ingester.Ingest(msg, providerAddress); canAutoCommit \u0026\u0026 err == nil {\n\t\t\tif err := f.ingester.CommitValue(f.storage, providerAddress); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tf.isLocked = true\n\t\t} else if err != nil {\n\t\t\treturn err\n\t\t}\n\n\tcase message.FuncTypeCommit:\n\t\tif err := f.ingester.CommitValue(f.storage, providerAddress); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tf.isLocked = true\n\n\tdefault:\n\t\treturn errors.New(\"invalid message function \" + string(funcType))\n\t}\n\n\treturn nil\n}\n\n// Value returns the feed's latest value, it's data type, and whether or not it can\n// be safely consumed. In this case it uses `f.isLocked` because, this being a static\n// feed, it will only ever have one value; once that value is committed the feed is locked\n// and there is a valid, non-empty value to consume.\nfunc (f Feed) Value() (feed.Value, string, bool) {\n\treturn f.storage.GetLatest(), f.valueDataType, f.isLocked\n}\n\n// MarshalJSON marshals the components of the feed that are needed for\n// an agent to execute tasks and send values for ingestion.\nfunc (f Feed) MarshalJSON() ([]byte, error) {\n\tbuf := new(bytes.Buffer)\n\tw := bufio.NewWriter(buf)\n\n\tw.Write([]byte(\n\t\t`{\"id\":\"` + f.id +\n\t\t\t`\",\"type\":\"` + ufmt.Sprintf(\"%d\", int(f.Type())) +\n\t\t\t`\",\"value_type\":\"` + f.valueDataType +\n\t\t\t`\",\"tasks\":[`),\n\t)\n\n\tfirst := true\n\tfor _, task := range f.tasks {\n\t\tif !first {\n\t\t\tw.WriteString(\",\")\n\t\t}\n\n\t\ttaskJSON, err := task.MarshalJSON()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tw.Write(taskJSON)\n\t\tfirst = false\n\t}\n\n\tw.Write([]byte(\"]}\"))\n\tw.Flush()\n\n\treturn buf.Bytes(), nil\n}\n\n// Tasks returns the feed's tasks. This allows task consumers to extract task\n// contents without having to marshal the entire feed.\nfunc (f Feed) Tasks() []feed.Task {\n\treturn f.tasks\n}\n\n// IsActive returns true if the feed is accepting ingestion requests from agents.\nfunc (f Feed) IsActive() bool {\n\treturn !f.isLocked\n}\n"},{"name":"feed_test.gno","body":"package static_test\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/gnorkle/feed\"\n\t\"gno.land/p/demo/gnorkle/feeds/static\"\n\t\"gno.land/p/demo/gnorkle/gnorkle\"\n\t\"gno.land/p/demo/gnorkle/ingester\"\n\t\"gno.land/p/demo/gnorkle/message\"\n\t\"gno.land/p/demo/gnorkle/storage/simple\"\n\t\"gno.land/p/demo/uassert\"\n\t\"gno.land/p/demo/urequire\"\n)\n\ntype mockIngester struct {\n\tcanAutoCommit bool\n\tingestErr error\n\tcommitErr error\n\tvalue string\n\tproviderAddress string\n}\n\nfunc (i mockIngester) Type() ingester.Type {\n\treturn ingester.Type(0)\n}\n\nfunc (i *mockIngester) Ingest(value, providerAddress string) (bool, error) {\n\tif i.ingestErr != nil {\n\t\treturn false, i.ingestErr\n\t}\n\n\ti.value = value\n\ti.providerAddress = providerAddress\n\treturn i.canAutoCommit, nil\n}\n\nfunc (i *mockIngester) CommitValue(storage gnorkle.Storage, providerAddress string) error {\n\tif i.commitErr != nil {\n\t\treturn i.commitErr\n\t}\n\n\treturn storage.Put(i.value)\n}\n\nfunc TestNewSingleValueFeed(t *testing.T) {\n\tstaticFeed := static.NewSingleValueFeed(\"1\", \"\")\n\n\tuassert.Equal(t, \"1\", staticFeed.ID())\n\tuassert.Equal(t, int(feed.TypeStatic), int(staticFeed.Type()))\n}\n\nfunc TestFeed_Ingest(t *testing.T) {\n\tvar undefinedFeed *static.Feed\n\terr := undefinedFeed.Ingest(\"\", \"\", \"\")\n\tuassert.ErrorIs(t, err, feed.ErrUndefined)\n\n\ttests := []struct {\n\t\tname string\n\t\tingester *mockIngester\n\t\tverifyIsLocked bool\n\t\tdoCommit bool\n\t\tfuncType message.FuncType\n\t\tmsg string\n\t\tproviderAddress string\n\t\texpFeedValueString string\n\t\texpErrText string\n\t\texpIsActive bool\n\t}{\n\t\t{\n\t\t\tname: \"func invalid error\",\n\t\t\tingester: \u0026mockIngester{},\n\t\t\tfuncType: message.FuncType(\"derp\"),\n\t\t\texpErrText: \"invalid message function derp\",\n\t\t\texpIsActive: true,\n\t\t},\n\t\t{\n\t\t\tname: \"func ingest ingest error\",\n\t\t\tingester: \u0026mockIngester{\n\t\t\t\tingestErr: errors.New(\"ingest error\"),\n\t\t\t},\n\t\t\tfuncType: message.FuncTypeIngest,\n\t\t\texpErrText: \"ingest error\",\n\t\t\texpIsActive: true,\n\t\t},\n\t\t{\n\t\t\tname: \"func ingest commit error\",\n\t\t\tingester: \u0026mockIngester{\n\t\t\t\tcommitErr: errors.New(\"commit error\"),\n\t\t\t\tcanAutoCommit: true,\n\t\t\t},\n\t\t\tfuncType: message.FuncTypeIngest,\n\t\t\texpErrText: \"commit error\",\n\t\t\texpIsActive: true,\n\t\t},\n\t\t{\n\t\t\tname: \"func commit commit error\",\n\t\t\tingester: \u0026mockIngester{\n\t\t\t\tcommitErr: errors.New(\"commit error\"),\n\t\t\t\tcanAutoCommit: true,\n\t\t\t},\n\t\t\tfuncType: message.FuncTypeCommit,\n\t\t\texpErrText: \"commit error\",\n\t\t\texpIsActive: true,\n\t\t},\n\t\t{\n\t\t\tname: \"only ingest\",\n\t\t\tingester: \u0026mockIngester{},\n\t\t\tfuncType: message.FuncTypeIngest,\n\t\t\tmsg: \"still active feed\",\n\t\t\tproviderAddress: \"gno1234\",\n\t\t\texpIsActive: true,\n\t\t},\n\t\t{\n\t\t\tname: \"ingest autocommit\",\n\t\t\tingester: \u0026mockIngester{canAutoCommit: true},\n\t\t\tfuncType: message.FuncTypeIngest,\n\t\t\tmsg: \"still active feed\",\n\t\t\tproviderAddress: \"gno1234\",\n\t\t\texpFeedValueString: \"still active feed\",\n\t\t\tverifyIsLocked: true,\n\t\t},\n\t\t{\n\t\t\tname: \"commit no value\",\n\t\t\tingester: \u0026mockIngester{},\n\t\t\tfuncType: message.FuncTypeCommit,\n\t\t\tmsg: \"shouldn't be stored\",\n\t\t\tverifyIsLocked: true,\n\t\t},\n\t\t{\n\t\t\tname: \"ingest then commmit\",\n\t\t\tingester: \u0026mockIngester{},\n\t\t\tfuncType: message.FuncTypeIngest,\n\t\t\tmsg: \"blahblah\",\n\t\t\tdoCommit: true,\n\t\t\texpFeedValueString: \"blahblah\",\n\t\t\tverifyIsLocked: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tstaticFeed := static.NewFeed(\n\t\t\t\t\"1\",\n\t\t\t\t\"string\",\n\t\t\t\ttt.ingester,\n\t\t\t\tsimple.NewStorage(1),\n\t\t\t\tnil,\n\t\t\t)\n\n\t\t\tvar errText string\n\t\t\tif err := staticFeed.Ingest(tt.funcType, tt.msg, tt.providerAddress); err != nil {\n\t\t\t\terrText = err.Error()\n\t\t\t}\n\n\t\t\turequire.Equal(t, tt.expErrText, errText)\n\n\t\t\tif tt.doCommit {\n\t\t\t\terr := staticFeed.Ingest(message.FuncTypeCommit, \"\", \"\")\n\t\t\t\turequire.NoError(t, err, \"follow up commit failed\")\n\t\t\t}\n\n\t\t\tif tt.verifyIsLocked {\n\t\t\t\terrText = \"\"\n\t\t\t\tif err := staticFeed.Ingest(tt.funcType, tt.msg, tt.providerAddress); err != nil {\n\t\t\t\t\terrText = err.Error()\n\t\t\t\t}\n\n\t\t\t\turequire.Equal(t, \"feed locked\", errText)\n\t\t\t}\n\n\t\t\tuassert.Equal(t, tt.providerAddress, tt.ingester.providerAddress)\n\n\t\t\tfeedValue, dataType, isLocked := staticFeed.Value()\n\t\t\tuassert.Equal(t, tt.expFeedValueString, feedValue.String)\n\t\t\tuassert.Equal(t, \"string\", dataType)\n\t\t\tuassert.Equal(t, tt.verifyIsLocked, isLocked)\n\t\t\tuassert.Equal(t, tt.expIsActive, staticFeed.IsActive())\n\t\t})\n\t}\n}\n\ntype mockTask struct {\n\terr error\n\tvalue string\n}\n\nfunc (t mockTask) MarshalJSON() ([]byte, error) {\n\tif t.err != nil {\n\t\treturn nil, t.err\n\t}\n\n\treturn []byte(`{\"value\":\"` + t.value + `\"}`), nil\n}\n\nfunc TestFeed_Tasks(t *testing.T) {\n\tid := \"99\"\n\tvalueDataType := \"int\"\n\n\ttests := []struct {\n\t\tname string\n\t\ttasks []feed.Task\n\t\texpErrText string\n\t\texpJSON string\n\t}{\n\t\t{\n\t\t\tname: \"no tasks\",\n\t\t\texpJSON: `{\"id\":\"99\",\"type\":\"0\",\"value_type\":\"int\",\"tasks\":[]}`,\n\t\t},\n\t\t{\n\t\t\tname: \"marshal error\",\n\t\t\ttasks: []feed.Task{\n\t\t\t\tmockTask{err: errors.New(\"marshal error\")},\n\t\t\t},\n\t\t\texpErrText: \"marshal error\",\n\t\t},\n\t\t{\n\t\t\tname: \"one task\",\n\t\t\ttasks: []feed.Task{\n\t\t\t\tmockTask{value: \"single\"},\n\t\t\t},\n\t\t\texpJSON: `{\"id\":\"99\",\"type\":\"0\",\"value_type\":\"int\",\"tasks\":[{\"value\":\"single\"}]}`,\n\t\t},\n\t\t{\n\t\t\tname: \"two tasks\",\n\t\t\ttasks: []feed.Task{\n\t\t\t\tmockTask{value: \"first\"},\n\t\t\t\tmockTask{value: \"second\"},\n\t\t\t},\n\t\t\texpJSON: `{\"id\":\"99\",\"type\":\"0\",\"value_type\":\"int\",\"tasks\":[{\"value\":\"first\"},{\"value\":\"second\"}]}`,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tstaticFeed := static.NewSingleValueFeed(\n\t\t\t\tid,\n\t\t\t\tvalueDataType,\n\t\t\t\ttt.tasks...,\n\t\t\t)\n\n\t\t\turequire.Equal(t, len(tt.tasks), len(staticFeed.Tasks()))\n\n\t\t\tvar errText string\n\t\t\tjson, err := staticFeed.MarshalJSON()\n\t\t\tif err != nil {\n\t\t\t\terrText = err.Error()\n\t\t\t}\n\n\t\t\turequire.Equal(t, tt.expErrText, errText)\n\t\t\turequire.Equal(t, tt.expJSON, string(json))\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":"exts","path":"gno.land/p/demo/grc/exts","files":[{"name":"token_metadata.gno","body":"package exts\n\ntype TokenMetadata interface {\n\t// Returns the name of the token.\n\tGetName() string\n\n\t// Returns the symbol of the token, usually a shorter version of the\n\t// name.\n\tGetSymbol() string\n\n\t// Returns the decimals places of the token.\n\tGetDecimals() uint\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":"grc1155","path":"gno.land/p/demo/grc/grc1155","files":[{"name":"README.md","body":"# GRC-1155 Spec: Multi Token Standard\n\nGRC1155 is a specification for managing multiple tokens based on Gnoland. The name and design is based on Ethereum's ERC1155 standard.\n\n## See also:\n\n[ERC-1155 Spec][erc-1155]\n\n[erc-1155]: https://eips.ethereum.org/EIPS/eip-1155"},{"name":"basic_grc1155_token.gno","body":"package grc1155\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype basicGRC1155Token struct {\n\turi string\n\tbalances avl.Tree // \"TokenId:Address\" -\u003e uint64\n\toperatorApprovals avl.Tree // \"OwnerAddress:OperatorAddress\" -\u003e bool\n}\n\nvar _ IGRC1155 = (*basicGRC1155Token)(nil)\n\n// Returns new basic GRC1155 token\nfunc NewBasicGRC1155Token(uri string) *basicGRC1155Token {\n\treturn \u0026basicGRC1155Token{\n\t\turi: uri,\n\t\tbalances: avl.Tree{},\n\t\toperatorApprovals: avl.Tree{},\n\t}\n}\n\nfunc (s *basicGRC1155Token) Uri() string { return s.uri }\n\n// BalanceOf returns the input address's balance of the token type requested\nfunc (s *basicGRC1155Token) BalanceOf(addr std.Address, tid TokenID) (uint64, error) {\n\tif !isValidAddress(addr) {\n\t\treturn 0, ErrInvalidAddress\n\t}\n\n\tkey := string(tid) + \":\" + addr.String()\n\tbalance, found := s.balances.Get(key)\n\tif !found {\n\t\treturn 0, nil\n\t}\n\n\treturn balance.(uint64), nil\n}\n\n// BalanceOfBatch returns the balance of multiple account/token pairs\nfunc (s *basicGRC1155Token) BalanceOfBatch(owners []std.Address, batch []TokenID) ([]uint64, error) {\n\tif len(owners) != len(batch) {\n\t\treturn nil, ErrMismatchLength\n\t}\n\n\tbalanceOfBatch := make([]uint64, len(owners))\n\n\tfor i := 0; i \u003c len(owners); i++ {\n\t\tbalanceOfBatch[i], _ = s.BalanceOf(owners[i], batch[i])\n\t}\n\n\treturn balanceOfBatch, nil\n}\n\n// SetApprovalForAll can approve the operator to operate on all tokens\nfunc (s *basicGRC1155Token) SetApprovalForAll(operator std.Address, approved bool) error {\n\tif !isValidAddress(operator) {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tcaller := std.GetOrigCaller()\n\treturn s.setApprovalForAll(caller, operator, approved)\n}\n\n// IsApprovedForAll returns true if operator is the owner or is approved for all by the owner.\n// Otherwise, returns false\nfunc (s *basicGRC1155Token) IsApprovedForAll(owner, operator std.Address) bool {\n\tif operator == owner {\n\t\treturn true\n\t}\n\tkey := owner.String() + \":\" + operator.String()\n\t_, found := s.operatorApprovals.Get(key)\n\tif !found {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// Safely transfers `tokenId` token from `from` to `to`, checking that\n// contract recipients are aware of the GRC1155 protocol to prevent\n// tokens from being forever locked.\nfunc (s *basicGRC1155Token) SafeTransferFrom(from, to std.Address, tid TokenID, amount uint64) error {\n\tcaller := std.GetOrigCaller()\n\tif !s.IsApprovedForAll(caller, from) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.safeBatchTransferFrom(from, to, []TokenID{tid}, []uint64{amount})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.doSafeTransferAcceptanceCheck(caller, from, to, tid, amount) {\n\t\treturn ErrTransferToRejectedOrNonGRC1155Receiver\n\t}\n\n\temit(\u0026TransferSingleEvent{caller, from, to, tid, amount})\n\n\treturn nil\n}\n\n// Safely transfers a `batch` of tokens from `from` to `to`, checking that\n// contract recipients are aware of the GRC1155 protocol to prevent\n// tokens from being forever locked.\nfunc (s *basicGRC1155Token) SafeBatchTransferFrom(from, to std.Address, batch []TokenID, amounts []uint64) error {\n\tcaller := std.GetOrigCaller()\n\tif !s.IsApprovedForAll(caller, from) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.safeBatchTransferFrom(from, to, batch, amounts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.doSafeBatchTransferAcceptanceCheck(caller, from, to, batch, amounts) {\n\t\treturn ErrTransferToRejectedOrNonGRC1155Receiver\n\t}\n\n\temit(\u0026TransferBatchEvent{caller, from, to, batch, amounts})\n\n\treturn nil\n}\n\n// Creates `amount` tokens of token type `id`, and assigns them to `to`. Also checks that\n// contract recipients are using GRC1155 protocol.\nfunc (s *basicGRC1155Token) SafeMint(to std.Address, tid TokenID, amount uint64) error {\n\tcaller := std.GetOrigCaller()\n\n\terr := s.mintBatch(to, []TokenID{tid}, []uint64{amount})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.doSafeTransferAcceptanceCheck(caller, zeroAddress, to, tid, amount) {\n\t\treturn ErrTransferToRejectedOrNonGRC1155Receiver\n\t}\n\n\temit(\u0026TransferSingleEvent{caller, zeroAddress, to, tid, amount})\n\n\treturn nil\n}\n\n// Batch version of `SafeMint()`. Also checks that\n// contract recipients are using GRC1155 protocol.\nfunc (s *basicGRC1155Token) SafeBatchMint(to std.Address, batch []TokenID, amounts []uint64) error {\n\tcaller := std.GetOrigCaller()\n\n\terr := s.mintBatch(to, batch, amounts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.doSafeBatchTransferAcceptanceCheck(caller, zeroAddress, to, batch, amounts) {\n\t\treturn ErrTransferToRejectedOrNonGRC1155Receiver\n\t}\n\n\temit(\u0026TransferBatchEvent{caller, zeroAddress, to, batch, amounts})\n\n\treturn nil\n}\n\n// Destroys `amount` tokens of token type `id` from `from`.\nfunc (s *basicGRC1155Token) Burn(from std.Address, tid TokenID, amount uint64) error {\n\tcaller := std.GetOrigCaller()\n\n\terr := s.burnBatch(from, []TokenID{tid}, []uint64{amount})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\temit(\u0026TransferSingleEvent{caller, from, zeroAddress, tid, amount})\n\n\treturn nil\n}\n\n// Batch version of `Burn()`\nfunc (s *basicGRC1155Token) BatchBurn(from std.Address, batch []TokenID, amounts []uint64) error {\n\tcaller := std.GetOrigCaller()\n\n\terr := s.burnBatch(from, batch, amounts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\temit(\u0026TransferBatchEvent{caller, from, zeroAddress, batch, amounts})\n\n\treturn nil\n}\n\n/* Helper methods */\n\n// Helper for SetApprovalForAll(): approve `operator` to operate on all of `owner` tokens\nfunc (s *basicGRC1155Token) setApprovalForAll(owner, operator std.Address, approved bool) error {\n\tif owner == operator {\n\t\treturn nil\n\t}\n\n\tkey := owner.String() + \":\" + operator.String()\n\tif approved {\n\t\ts.operatorApprovals.Set(key, approved)\n\t} else {\n\t\ts.operatorApprovals.Remove(key)\n\t}\n\n\temit(\u0026ApprovalForAllEvent{owner, operator, approved})\n\n\treturn nil\n}\n\n// Helper for SafeTransferFrom() and SafeBatchTransferFrom()\nfunc (s *basicGRC1155Token) safeBatchTransferFrom(from, to std.Address, batch []TokenID, amounts []uint64) error {\n\tif len(batch) != len(amounts) {\n\t\treturn ErrMismatchLength\n\t}\n\tif !isValidAddress(from) || !isValidAddress(to) {\n\t\treturn ErrInvalidAddress\n\t}\n\tif from == to {\n\t\treturn ErrCannotTransferToSelf\n\t}\n\n\tcaller := std.GetOrigCaller()\n\ts.beforeTokenTransfer(caller, from, to, batch, amounts)\n\n\tfor i := 0; i \u003c len(batch); i++ {\n\t\ttid := batch[i]\n\t\tamount := amounts[i]\n\t\tfromBalance, err := s.BalanceOf(from, tid)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif fromBalance \u003c amount {\n\t\t\treturn ErrInsufficientBalance\n\t\t}\n\t\ttoBalance, err := s.BalanceOf(to, tid)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfromBalance -= amount\n\t\ttoBalance += amount\n\t\tfromBalanceKey := string(tid) + \":\" + from.String()\n\t\ttoBalanceKey := string(tid) + \":\" + to.String()\n\t\ts.balances.Set(fromBalanceKey, fromBalance)\n\t\ts.balances.Set(toBalanceKey, toBalance)\n\t}\n\n\ts.afterTokenTransfer(caller, from, to, batch, amounts)\n\n\treturn nil\n}\n\n// Helper for SafeMint() and SafeBatchMint()\nfunc (s *basicGRC1155Token) mintBatch(to std.Address, batch []TokenID, amounts []uint64) error {\n\tif len(batch) != len(amounts) {\n\t\treturn ErrMismatchLength\n\t}\n\tif !isValidAddress(to) {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tcaller := std.GetOrigCaller()\n\ts.beforeTokenTransfer(caller, zeroAddress, to, batch, amounts)\n\n\tfor i := 0; i \u003c len(batch); i++ {\n\t\ttid := batch[i]\n\t\tamount := amounts[i]\n\t\ttoBalance, err := s.BalanceOf(to, tid)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ttoBalance += amount\n\t\ttoBalanceKey := string(tid) + \":\" + to.String()\n\t\ts.balances.Set(toBalanceKey, toBalance)\n\t}\n\n\ts.afterTokenTransfer(caller, zeroAddress, to, batch, amounts)\n\n\treturn nil\n}\n\n// Helper for Burn() and BurnBatch()\nfunc (s *basicGRC1155Token) burnBatch(from std.Address, batch []TokenID, amounts []uint64) error {\n\tif len(batch) != len(amounts) {\n\t\treturn ErrMismatchLength\n\t}\n\tif !isValidAddress(from) {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tcaller := std.GetOrigCaller()\n\ts.beforeTokenTransfer(caller, from, zeroAddress, batch, amounts)\n\n\tfor i := 0; i \u003c len(batch); i++ {\n\t\ttid := batch[i]\n\t\tamount := amounts[i]\n\t\tfromBalance, err := s.BalanceOf(from, tid)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif fromBalance \u003c amount {\n\t\t\treturn ErrBurnAmountExceedsBalance\n\t\t}\n\t\tfromBalance -= amount\n\t\tfromBalanceKey := string(tid) + \":\" + from.String()\n\t\ts.balances.Set(fromBalanceKey, fromBalance)\n\t}\n\n\ts.afterTokenTransfer(caller, from, zeroAddress, batch, amounts)\n\n\treturn nil\n}\n\nfunc (s *basicGRC1155Token) setUri(newUri string) {\n\ts.uri = newUri\n\temit(\u0026UpdateURIEvent{newUri})\n}\n\nfunc (s *basicGRC1155Token) beforeTokenTransfer(operator, from, to std.Address, batch []TokenID, amounts []uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicGRC1155Token) afterTokenTransfer(operator, from, to std.Address, batch []TokenID, amounts []uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicGRC1155Token) doSafeTransferAcceptanceCheck(operator, from, to std.Address, tid TokenID, amount uint64) bool {\n\t// TODO: Implementation\n\treturn true\n}\n\nfunc (s *basicGRC1155Token) doSafeBatchTransferAcceptanceCheck(operator, from, to std.Address, batch []TokenID, amounts []uint64) bool {\n\t// TODO: Implementation\n\treturn true\n}\n\nfunc (s *basicGRC1155Token) RenderHome() (str string) {\n\tstr += ufmt.Sprintf(\"# URI:%s\\n\", s.uri)\n\n\treturn\n}\n"},{"name":"basic_grc1155_token_test.gno","body":"package grc1155\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nconst dummyURI = \"ipfs://xyz\"\n\nfunc TestNewBasicGRC1155Token(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n}\n\nfunc TestUri(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\tuassert.Equal(t, dummyURI, dummy.Uri())\n}\n\nfunc TestBalanceOf(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\tbalanceZeroAddressOfToken1, err := dummy.BalanceOf(zeroAddress, tid1)\n\tuassert.Error(t, err, \"should result in error\")\n\n\tbalanceAddr1OfToken1, err := dummy.BalanceOf(addr1, tid1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(0), balanceAddr1OfToken1)\n\n\tdummy.mintBatch(addr1, []TokenID{tid1, tid2}, []uint64{10, 100})\n\tdummy.mintBatch(addr2, []TokenID{tid1}, []uint64{20})\n\n\tbalanceAddr1OfToken1, err = dummy.BalanceOf(addr1, tid1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tbalanceAddr1OfToken2, err := dummy.BalanceOf(addr1, tid2)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tbalanceAddr2OfToken1, err := dummy.BalanceOf(addr2, tid1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tuassert.Equal(t, uint64(10), balanceAddr1OfToken1)\n\tuassert.Equal(t, uint64(100), balanceAddr1OfToken2)\n\tuassert.Equal(t, uint64(20), balanceAddr2OfToken1)\n}\n\nfunc TestBalanceOfBatch(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{addr1, addr2}, []TokenID{tid1, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(0), balanceBatch[0])\n\tuassert.Equal(t, uint64(0), balanceBatch[1])\n\n\tdummy.mintBatch(addr1, []TokenID{tid1}, []uint64{10})\n\tdummy.mintBatch(addr2, []TokenID{tid2}, []uint64{20})\n\n\tbalanceBatch, err = dummy.BalanceOfBatch([]std.Address{addr1, addr2}, []TokenID{tid1, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(10), balanceBatch[0])\n\tuassert.Equal(t, uint64(20), balanceBatch[1])\n}\n\nfunc TestIsApprovedForAll(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(addr1, addr2)\n\tuassert.False(t, isApprovedForAll)\n}\n\nfunc TestSetApprovalForAll(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.GetOrigCaller()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(caller, addr)\n\tuassert.False(t, isApprovedForAll)\n\n\terr := dummy.SetApprovalForAll(addr, true)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tisApprovedForAll = dummy.IsApprovedForAll(caller, addr)\n\tuassert.True(t, isApprovedForAll)\n\n\terr = dummy.SetApprovalForAll(addr, false)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tisApprovedForAll = dummy.IsApprovedForAll(caller, addr)\n\tuassert.False(t, isApprovedForAll)\n}\n\nfunc TestSafeTransferFrom(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.GetOrigCaller()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\ttid := TokenID(\"1\")\n\n\tdummy.mintBatch(caller, []TokenID{tid}, []uint64{100})\n\n\terr := dummy.SafeTransferFrom(caller, zeroAddress, tid, 10)\n\tuassert.Error(t, err, \"should result in error\")\n\n\terr = dummy.SafeTransferFrom(caller, addr, tid, 160)\n\tuassert.Error(t, err, \"should result in error\")\n\n\terr = dummy.SafeTransferFrom(caller, addr, tid, 60)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check balance of caller after transfer\n\tbalanceOfCaller, err := dummy.BalanceOf(caller, tid)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(40), balanceOfCaller)\n\n\t// Check balance of addr after transfer\n\tbalanceOfAddr, err := dummy.BalanceOf(addr, tid)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(60), balanceOfAddr)\n}\n\nfunc TestSafeBatchTransferFrom(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.GetOrigCaller()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\tdummy.mintBatch(caller, []TokenID{tid1, tid2}, []uint64{10, 100})\n\n\terr := dummy.SafeBatchTransferFrom(caller, zeroAddress, []TokenID{tid1, tid2}, []uint64{4, 60})\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.SafeBatchTransferFrom(caller, addr, []TokenID{tid1, tid2}, []uint64{40, 60})\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.SafeBatchTransferFrom(caller, addr, []TokenID{tid1}, []uint64{40, 60})\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.SafeBatchTransferFrom(caller, addr, []TokenID{tid1, tid2}, []uint64{4, 60})\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{caller, addr, caller, addr}, []TokenID{tid1, tid1, tid2, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check token1's balance of caller after batch transfer\n\tuassert.Equal(t, uint64(6), balanceBatch[0])\n\n\t// Check token1's balance of addr after batch transfer\n\tuassert.Equal(t, uint64(4), balanceBatch[1])\n\n\t// Check token2's balance of caller after batch transfer\n\tuassert.Equal(t, uint64(40), balanceBatch[2])\n\n\t// Check token2's balance of addr after batch transfer\n\tuassert.Equal(t, uint64(60), balanceBatch[3])\n}\n\nfunc TestSafeMint(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\terr := dummy.SafeMint(zeroAddress, tid1, 100)\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.SafeMint(addr1, tid1, 100)\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.SafeMint(addr1, tid2, 200)\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.SafeMint(addr2, tid1, 50)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{addr1, addr2, addr1}, []TokenID{tid1, tid1, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\t// Check token1's balance of addr1 after mint\n\tuassert.Equal(t, uint64(100), balanceBatch[0])\n\t// Check token1's balance of addr2 after mint\n\tuassert.Equal(t, uint64(50), balanceBatch[1])\n\t// Check token2's balance of addr1 after mint\n\tuassert.Equal(t, uint64(200), balanceBatch[2])\n}\n\nfunc TestSafeBatchMint(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\terr := dummy.SafeBatchMint(zeroAddress, []TokenID{tid1, tid2}, []uint64{100, 200})\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.SafeBatchMint(addr1, []TokenID{tid1, tid2}, []uint64{100, 200})\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.SafeBatchMint(addr2, []TokenID{tid1, tid2}, []uint64{300, 400})\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{addr1, addr2, addr1, addr2}, []TokenID{tid1, tid1, tid2, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\t// Check token1's balance of addr1 after batch mint\n\tuassert.Equal(t, uint64(100), balanceBatch[0])\n\t// Check token1's balance of addr2 after batch mint\n\tuassert.Equal(t, uint64(300), balanceBatch[1])\n\t// Check token2's balance of addr1 after batch mint\n\tuassert.Equal(t, uint64(200), balanceBatch[2])\n\t// Check token2's balance of addr2 after batch mint\n\tuassert.Equal(t, uint64(400), balanceBatch[3])\n}\n\nfunc TestBurn(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\tdummy.mintBatch(addr, []TokenID{tid1, tid2}, []uint64{100, 200})\n\terr := dummy.Burn(zeroAddress, tid1, uint64(60))\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.Burn(addr, tid1, uint64(160))\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.Burn(addr, tid1, uint64(60))\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.Burn(addr, tid2, uint64(60))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{addr, addr}, []TokenID{tid1, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check token1's balance of addr after burn\n\tuassert.Equal(t, uint64(40), balanceBatch[0])\n\t// Check token2's balance of addr after burn\n\tuassert.Equal(t, uint64(140), balanceBatch[1])\n}\n\nfunc TestBatchBurn(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\tdummy.mintBatch(addr, []TokenID{tid1, tid2}, []uint64{100, 200})\n\terr := dummy.BatchBurn(zeroAddress, []TokenID{tid1, tid2}, []uint64{60, 60})\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.BatchBurn(addr, []TokenID{tid1, tid2}, []uint64{160, 60})\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.BatchBurn(addr, []TokenID{tid1, tid2}, []uint64{60, 60})\n\tuassert.NoError(t, err, \"should not result in error\")\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{addr, addr}, []TokenID{tid1, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check token1's balance of addr after batch burn\n\tuassert.Equal(t, uint64(40), balanceBatch[0])\n\t// Check token2's balance of addr after batch burn\n\tuassert.Equal(t, uint64(140), balanceBatch[1])\n}\n"},{"name":"errors.gno","body":"package grc1155\n\nimport \"errors\"\n\nvar (\n\tErrInvalidAddress = errors.New(\"invalid address\")\n\tErrMismatchLength = errors.New(\"accounts and ids length mismatch\")\n\tErrCannotTransferToSelf = errors.New(\"cannot send transfer to self\")\n\tErrTransferToRejectedOrNonGRC1155Receiver = errors.New(\"transfer to rejected or non GRC1155Receiver implementer\")\n\tErrCallerIsNotOwnerOrApproved = errors.New(\"caller is not token owner or approved\")\n\tErrInsufficientBalance = errors.New(\"insufficient balance for transfer\")\n\tErrBurnAmountExceedsBalance = errors.New(\"burn amount exceeds balance\")\n)\n"},{"name":"igrc1155.gno","body":"package grc1155\n\nimport \"std\"\n\ntype IGRC1155 interface {\n\tSafeTransferFrom(from, to std.Address, tid TokenID, amount uint64) error\n\tSafeBatchTransferFrom(from, to std.Address, batch []TokenID, amounts []uint64) error\n\tBalanceOf(owner std.Address, tid TokenID) (uint64, error)\n\tBalanceOfBatch(owners []std.Address, batch []TokenID) ([]uint64, error)\n\tSetApprovalForAll(operator std.Address, approved bool) error\n\tIsApprovedForAll(owner, operator std.Address) bool\n}\n\ntype TokenID string\n\ntype TransferSingleEvent struct {\n\tOperator std.Address\n\tFrom std.Address\n\tTo std.Address\n\tTokenID TokenID\n\tAmount uint64\n}\n\ntype TransferBatchEvent struct {\n\tOperator std.Address\n\tFrom std.Address\n\tTo std.Address\n\tBatch []TokenID\n\tAmounts []uint64\n}\n\ntype ApprovalForAllEvent struct {\n\tOwner std.Address\n\tOperator std.Address\n\tApproved bool\n}\n\ntype UpdateURIEvent struct {\n\tURI string\n}\n"},{"name":"util.gno","body":"package grc1155\n\nimport (\n\t\"std\"\n)\n\nconst zeroAddress std.Address = \"\"\n\nfunc isValidAddress(addr std.Address) bool {\n\tif !addr.IsValid() {\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc emit(event interface{}) {\n\t// TODO: setup a pubsub system here?\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":"grc1155","path":"gno.land/p/demo/grc/grc1155","files":[{"name":"README.md","body":"# GRC-1155 Spec: Multi Token Standard\n\nGRC1155 is a specification for managing multiple tokens based on Gnoland. The name and design is based on Ethereum's ERC1155 standard.\n\n## See also:\n\n[ERC-1155 Spec][erc-1155]\n\n[erc-1155]: https://eips.ethereum.org/EIPS/eip-1155"},{"name":"basic_grc1155_token.gno","body":"package grc1155\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype basicGRC1155Token struct {\n\turi string\n\tbalances avl.Tree // \"TokenId:Address\" -\u003e uint64\n\toperatorApprovals avl.Tree // \"OwnerAddress:OperatorAddress\" -\u003e bool\n}\n\nvar _ IGRC1155 = (*basicGRC1155Token)(nil)\n\n// Returns new basic GRC1155 token\nfunc NewBasicGRC1155Token(uri string) *basicGRC1155Token {\n\treturn \u0026basicGRC1155Token{\n\t\turi: uri,\n\t\tbalances: avl.Tree{},\n\t\toperatorApprovals: avl.Tree{},\n\t}\n}\n\nfunc (s *basicGRC1155Token) Uri() string { return s.uri }\n\n// BalanceOf returns the input address's balance of the token type requested\nfunc (s *basicGRC1155Token) BalanceOf(addr std.Address, tid TokenID) (uint64, error) {\n\tif !isValidAddress(addr) {\n\t\treturn 0, ErrInvalidAddress\n\t}\n\n\tkey := string(tid) + \":\" + addr.String()\n\tbalance, found := s.balances.Get(key)\n\tif !found {\n\t\treturn 0, nil\n\t}\n\n\treturn balance.(uint64), nil\n}\n\n// BalanceOfBatch returns the balance of multiple account/token pairs\nfunc (s *basicGRC1155Token) BalanceOfBatch(owners []std.Address, batch []TokenID) ([]uint64, error) {\n\tif len(owners) != len(batch) {\n\t\treturn nil, ErrMismatchLength\n\t}\n\n\tbalanceOfBatch := make([]uint64, len(owners))\n\n\tfor i := 0; i \u003c len(owners); i++ {\n\t\tbalanceOfBatch[i], _ = s.BalanceOf(owners[i], batch[i])\n\t}\n\n\treturn balanceOfBatch, nil\n}\n\n// SetApprovalForAll can approve the operator to operate on all tokens\nfunc (s *basicGRC1155Token) SetApprovalForAll(operator std.Address, approved bool) error {\n\tif !isValidAddress(operator) {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tcaller := std.OrigCaller()\n\treturn s.setApprovalForAll(caller, operator, approved)\n}\n\n// IsApprovedForAll returns true if operator is the owner or is approved for all by the owner.\n// Otherwise, returns false\nfunc (s *basicGRC1155Token) IsApprovedForAll(owner, operator std.Address) bool {\n\tif operator == owner {\n\t\treturn true\n\t}\n\tkey := owner.String() + \":\" + operator.String()\n\t_, found := s.operatorApprovals.Get(key)\n\tif !found {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// Safely transfers `tokenId` token from `from` to `to`, checking that\n// contract recipients are aware of the GRC1155 protocol to prevent\n// tokens from being forever locked.\nfunc (s *basicGRC1155Token) SafeTransferFrom(from, to std.Address, tid TokenID, amount uint64) error {\n\tcaller := std.OrigCaller()\n\tif !s.IsApprovedForAll(caller, from) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.safeBatchTransferFrom(from, to, []TokenID{tid}, []uint64{amount})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.doSafeTransferAcceptanceCheck(caller, from, to, tid, amount) {\n\t\treturn ErrTransferToRejectedOrNonGRC1155Receiver\n\t}\n\n\temit(\u0026TransferSingleEvent{caller, from, to, tid, amount})\n\n\treturn nil\n}\n\n// Safely transfers a `batch` of tokens from `from` to `to`, checking that\n// contract recipients are aware of the GRC1155 protocol to prevent\n// tokens from being forever locked.\nfunc (s *basicGRC1155Token) SafeBatchTransferFrom(from, to std.Address, batch []TokenID, amounts []uint64) error {\n\tcaller := std.OrigCaller()\n\tif !s.IsApprovedForAll(caller, from) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.safeBatchTransferFrom(from, to, batch, amounts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.doSafeBatchTransferAcceptanceCheck(caller, from, to, batch, amounts) {\n\t\treturn ErrTransferToRejectedOrNonGRC1155Receiver\n\t}\n\n\temit(\u0026TransferBatchEvent{caller, from, to, batch, amounts})\n\n\treturn nil\n}\n\n// Creates `amount` tokens of token type `id`, and assigns them to `to`. Also checks that\n// contract recipients are using GRC1155 protocol.\nfunc (s *basicGRC1155Token) SafeMint(to std.Address, tid TokenID, amount uint64) error {\n\tcaller := std.OrigCaller()\n\n\terr := s.mintBatch(to, []TokenID{tid}, []uint64{amount})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.doSafeTransferAcceptanceCheck(caller, zeroAddress, to, tid, amount) {\n\t\treturn ErrTransferToRejectedOrNonGRC1155Receiver\n\t}\n\n\temit(\u0026TransferSingleEvent{caller, zeroAddress, to, tid, amount})\n\n\treturn nil\n}\n\n// Batch version of `SafeMint()`. Also checks that\n// contract recipients are using GRC1155 protocol.\nfunc (s *basicGRC1155Token) SafeBatchMint(to std.Address, batch []TokenID, amounts []uint64) error {\n\tcaller := std.OrigCaller()\n\n\terr := s.mintBatch(to, batch, amounts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.doSafeBatchTransferAcceptanceCheck(caller, zeroAddress, to, batch, amounts) {\n\t\treturn ErrTransferToRejectedOrNonGRC1155Receiver\n\t}\n\n\temit(\u0026TransferBatchEvent{caller, zeroAddress, to, batch, amounts})\n\n\treturn nil\n}\n\n// Destroys `amount` tokens of token type `id` from `from`.\nfunc (s *basicGRC1155Token) Burn(from std.Address, tid TokenID, amount uint64) error {\n\tcaller := std.OrigCaller()\n\n\terr := s.burnBatch(from, []TokenID{tid}, []uint64{amount})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\temit(\u0026TransferSingleEvent{caller, from, zeroAddress, tid, amount})\n\n\treturn nil\n}\n\n// Batch version of `Burn()`\nfunc (s *basicGRC1155Token) BatchBurn(from std.Address, batch []TokenID, amounts []uint64) error {\n\tcaller := std.OrigCaller()\n\n\terr := s.burnBatch(from, batch, amounts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\temit(\u0026TransferBatchEvent{caller, from, zeroAddress, batch, amounts})\n\n\treturn nil\n}\n\n/* Helper methods */\n\n// Helper for SetApprovalForAll(): approve `operator` to operate on all of `owner` tokens\nfunc (s *basicGRC1155Token) setApprovalForAll(owner, operator std.Address, approved bool) error {\n\tif owner == operator {\n\t\treturn nil\n\t}\n\n\tkey := owner.String() + \":\" + operator.String()\n\tif approved {\n\t\ts.operatorApprovals.Set(key, approved)\n\t} else {\n\t\ts.operatorApprovals.Remove(key)\n\t}\n\n\temit(\u0026ApprovalForAllEvent{owner, operator, approved})\n\n\treturn nil\n}\n\n// Helper for SafeTransferFrom() and SafeBatchTransferFrom()\nfunc (s *basicGRC1155Token) safeBatchTransferFrom(from, to std.Address, batch []TokenID, amounts []uint64) error {\n\tif len(batch) != len(amounts) {\n\t\treturn ErrMismatchLength\n\t}\n\tif !isValidAddress(from) || !isValidAddress(to) {\n\t\treturn ErrInvalidAddress\n\t}\n\tif from == to {\n\t\treturn ErrCannotTransferToSelf\n\t}\n\n\tcaller := std.OrigCaller()\n\ts.beforeTokenTransfer(caller, from, to, batch, amounts)\n\n\tfor i := 0; i \u003c len(batch); i++ {\n\t\ttid := batch[i]\n\t\tamount := amounts[i]\n\t\tfromBalance, err := s.BalanceOf(from, tid)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif fromBalance \u003c amount {\n\t\t\treturn ErrInsufficientBalance\n\t\t}\n\t\ttoBalance, err := s.BalanceOf(to, tid)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfromBalance -= amount\n\t\ttoBalance += amount\n\t\tfromBalanceKey := string(tid) + \":\" + from.String()\n\t\ttoBalanceKey := string(tid) + \":\" + to.String()\n\t\ts.balances.Set(fromBalanceKey, fromBalance)\n\t\ts.balances.Set(toBalanceKey, toBalance)\n\t}\n\n\ts.afterTokenTransfer(caller, from, to, batch, amounts)\n\n\treturn nil\n}\n\n// Helper for SafeMint() and SafeBatchMint()\nfunc (s *basicGRC1155Token) mintBatch(to std.Address, batch []TokenID, amounts []uint64) error {\n\tif len(batch) != len(amounts) {\n\t\treturn ErrMismatchLength\n\t}\n\tif !isValidAddress(to) {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tcaller := std.OrigCaller()\n\ts.beforeTokenTransfer(caller, zeroAddress, to, batch, amounts)\n\n\tfor i := 0; i \u003c len(batch); i++ {\n\t\ttid := batch[i]\n\t\tamount := amounts[i]\n\t\ttoBalance, err := s.BalanceOf(to, tid)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ttoBalance += amount\n\t\ttoBalanceKey := string(tid) + \":\" + to.String()\n\t\ts.balances.Set(toBalanceKey, toBalance)\n\t}\n\n\ts.afterTokenTransfer(caller, zeroAddress, to, batch, amounts)\n\n\treturn nil\n}\n\n// Helper for Burn() and BurnBatch()\nfunc (s *basicGRC1155Token) burnBatch(from std.Address, batch []TokenID, amounts []uint64) error {\n\tif len(batch) != len(amounts) {\n\t\treturn ErrMismatchLength\n\t}\n\tif !isValidAddress(from) {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tcaller := std.OrigCaller()\n\ts.beforeTokenTransfer(caller, from, zeroAddress, batch, amounts)\n\n\tfor i := 0; i \u003c len(batch); i++ {\n\t\ttid := batch[i]\n\t\tamount := amounts[i]\n\t\tfromBalance, err := s.BalanceOf(from, tid)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif fromBalance \u003c amount {\n\t\t\treturn ErrBurnAmountExceedsBalance\n\t\t}\n\t\tfromBalance -= amount\n\t\tfromBalanceKey := string(tid) + \":\" + from.String()\n\t\ts.balances.Set(fromBalanceKey, fromBalance)\n\t}\n\n\ts.afterTokenTransfer(caller, from, zeroAddress, batch, amounts)\n\n\treturn nil\n}\n\nfunc (s *basicGRC1155Token) setUri(newUri string) {\n\ts.uri = newUri\n\temit(\u0026UpdateURIEvent{newUri})\n}\n\nfunc (s *basicGRC1155Token) beforeTokenTransfer(operator, from, to std.Address, batch []TokenID, amounts []uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicGRC1155Token) afterTokenTransfer(operator, from, to std.Address, batch []TokenID, amounts []uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicGRC1155Token) doSafeTransferAcceptanceCheck(operator, from, to std.Address, tid TokenID, amount uint64) bool {\n\t// TODO: Implementation\n\treturn true\n}\n\nfunc (s *basicGRC1155Token) doSafeBatchTransferAcceptanceCheck(operator, from, to std.Address, batch []TokenID, amounts []uint64) bool {\n\t// TODO: Implementation\n\treturn true\n}\n\nfunc (s *basicGRC1155Token) RenderHome() (str string) {\n\tstr += ufmt.Sprintf(\"# URI:%s\\n\", s.uri)\n\n\treturn\n}\n"},{"name":"basic_grc1155_token_test.gno","body":"package grc1155\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nconst dummyURI = \"ipfs://xyz\"\n\nfunc TestNewBasicGRC1155Token(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n}\n\nfunc TestUri(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\tuassert.Equal(t, dummyURI, dummy.Uri())\n}\n\nfunc TestBalanceOf(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\tbalanceZeroAddressOfToken1, err := dummy.BalanceOf(zeroAddress, tid1)\n\tuassert.Error(t, err, \"should result in error\")\n\n\tbalanceAddr1OfToken1, err := dummy.BalanceOf(addr1, tid1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(0), balanceAddr1OfToken1)\n\n\tdummy.mintBatch(addr1, []TokenID{tid1, tid2}, []uint64{10, 100})\n\tdummy.mintBatch(addr2, []TokenID{tid1}, []uint64{20})\n\n\tbalanceAddr1OfToken1, err = dummy.BalanceOf(addr1, tid1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tbalanceAddr1OfToken2, err := dummy.BalanceOf(addr1, tid2)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tbalanceAddr2OfToken1, err := dummy.BalanceOf(addr2, tid1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tuassert.Equal(t, uint64(10), balanceAddr1OfToken1)\n\tuassert.Equal(t, uint64(100), balanceAddr1OfToken2)\n\tuassert.Equal(t, uint64(20), balanceAddr2OfToken1)\n}\n\nfunc TestBalanceOfBatch(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{addr1, addr2}, []TokenID{tid1, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(0), balanceBatch[0])\n\tuassert.Equal(t, uint64(0), balanceBatch[1])\n\n\tdummy.mintBatch(addr1, []TokenID{tid1}, []uint64{10})\n\tdummy.mintBatch(addr2, []TokenID{tid2}, []uint64{20})\n\n\tbalanceBatch, err = dummy.BalanceOfBatch([]std.Address{addr1, addr2}, []TokenID{tid1, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(10), balanceBatch[0])\n\tuassert.Equal(t, uint64(20), balanceBatch[1])\n}\n\nfunc TestIsApprovedForAll(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(addr1, addr2)\n\tuassert.False(t, isApprovedForAll)\n}\n\nfunc TestSetApprovalForAll(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.OrigCaller()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(caller, addr)\n\tuassert.False(t, isApprovedForAll)\n\n\terr := dummy.SetApprovalForAll(addr, true)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tisApprovedForAll = dummy.IsApprovedForAll(caller, addr)\n\tuassert.True(t, isApprovedForAll)\n\n\terr = dummy.SetApprovalForAll(addr, false)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tisApprovedForAll = dummy.IsApprovedForAll(caller, addr)\n\tuassert.False(t, isApprovedForAll)\n}\n\nfunc TestSafeTransferFrom(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.OrigCaller()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\ttid := TokenID(\"1\")\n\n\tdummy.mintBatch(caller, []TokenID{tid}, []uint64{100})\n\n\terr := dummy.SafeTransferFrom(caller, zeroAddress, tid, 10)\n\tuassert.Error(t, err, \"should result in error\")\n\n\terr = dummy.SafeTransferFrom(caller, addr, tid, 160)\n\tuassert.Error(t, err, \"should result in error\")\n\n\terr = dummy.SafeTransferFrom(caller, addr, tid, 60)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check balance of caller after transfer\n\tbalanceOfCaller, err := dummy.BalanceOf(caller, tid)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(40), balanceOfCaller)\n\n\t// Check balance of addr after transfer\n\tbalanceOfAddr, err := dummy.BalanceOf(addr, tid)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(60), balanceOfAddr)\n}\n\nfunc TestSafeBatchTransferFrom(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.OrigCaller()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\tdummy.mintBatch(caller, []TokenID{tid1, tid2}, []uint64{10, 100})\n\n\terr := dummy.SafeBatchTransferFrom(caller, zeroAddress, []TokenID{tid1, tid2}, []uint64{4, 60})\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.SafeBatchTransferFrom(caller, addr, []TokenID{tid1, tid2}, []uint64{40, 60})\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.SafeBatchTransferFrom(caller, addr, []TokenID{tid1}, []uint64{40, 60})\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.SafeBatchTransferFrom(caller, addr, []TokenID{tid1, tid2}, []uint64{4, 60})\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{caller, addr, caller, addr}, []TokenID{tid1, tid1, tid2, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check token1's balance of caller after batch transfer\n\tuassert.Equal(t, uint64(6), balanceBatch[0])\n\n\t// Check token1's balance of addr after batch transfer\n\tuassert.Equal(t, uint64(4), balanceBatch[1])\n\n\t// Check token2's balance of caller after batch transfer\n\tuassert.Equal(t, uint64(40), balanceBatch[2])\n\n\t// Check token2's balance of addr after batch transfer\n\tuassert.Equal(t, uint64(60), balanceBatch[3])\n}\n\nfunc TestSafeMint(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\terr := dummy.SafeMint(zeroAddress, tid1, 100)\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.SafeMint(addr1, tid1, 100)\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.SafeMint(addr1, tid2, 200)\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.SafeMint(addr2, tid1, 50)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{addr1, addr2, addr1}, []TokenID{tid1, tid1, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\t// Check token1's balance of addr1 after mint\n\tuassert.Equal(t, uint64(100), balanceBatch[0])\n\t// Check token1's balance of addr2 after mint\n\tuassert.Equal(t, uint64(50), balanceBatch[1])\n\t// Check token2's balance of addr1 after mint\n\tuassert.Equal(t, uint64(200), balanceBatch[2])\n}\n\nfunc TestSafeBatchMint(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\terr := dummy.SafeBatchMint(zeroAddress, []TokenID{tid1, tid2}, []uint64{100, 200})\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.SafeBatchMint(addr1, []TokenID{tid1, tid2}, []uint64{100, 200})\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.SafeBatchMint(addr2, []TokenID{tid1, tid2}, []uint64{300, 400})\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{addr1, addr2, addr1, addr2}, []TokenID{tid1, tid1, tid2, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\t// Check token1's balance of addr1 after batch mint\n\tuassert.Equal(t, uint64(100), balanceBatch[0])\n\t// Check token1's balance of addr2 after batch mint\n\tuassert.Equal(t, uint64(300), balanceBatch[1])\n\t// Check token2's balance of addr1 after batch mint\n\tuassert.Equal(t, uint64(200), balanceBatch[2])\n\t// Check token2's balance of addr2 after batch mint\n\tuassert.Equal(t, uint64(400), balanceBatch[3])\n}\n\nfunc TestBurn(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\tdummy.mintBatch(addr, []TokenID{tid1, tid2}, []uint64{100, 200})\n\terr := dummy.Burn(zeroAddress, tid1, uint64(60))\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.Burn(addr, tid1, uint64(160))\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.Burn(addr, tid1, uint64(60))\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.Burn(addr, tid2, uint64(60))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{addr, addr}, []TokenID{tid1, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check token1's balance of addr after burn\n\tuassert.Equal(t, uint64(40), balanceBatch[0])\n\t// Check token2's balance of addr after burn\n\tuassert.Equal(t, uint64(140), balanceBatch[1])\n}\n\nfunc TestBatchBurn(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\tdummy.mintBatch(addr, []TokenID{tid1, tid2}, []uint64{100, 200})\n\terr := dummy.BatchBurn(zeroAddress, []TokenID{tid1, tid2}, []uint64{60, 60})\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.BatchBurn(addr, []TokenID{tid1, tid2}, []uint64{160, 60})\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.BatchBurn(addr, []TokenID{tid1, tid2}, []uint64{60, 60})\n\tuassert.NoError(t, err, \"should not result in error\")\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{addr, addr}, []TokenID{tid1, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check token1's balance of addr after batch burn\n\tuassert.Equal(t, uint64(40), balanceBatch[0])\n\t// Check token2's balance of addr after batch burn\n\tuassert.Equal(t, uint64(140), balanceBatch[1])\n}\n"},{"name":"errors.gno","body":"package grc1155\n\nimport \"errors\"\n\nvar (\n\tErrInvalidAddress = errors.New(\"invalid address\")\n\tErrMismatchLength = errors.New(\"accounts and ids length mismatch\")\n\tErrCannotTransferToSelf = errors.New(\"cannot send transfer to self\")\n\tErrTransferToRejectedOrNonGRC1155Receiver = errors.New(\"transfer to rejected or non GRC1155Receiver implementer\")\n\tErrCallerIsNotOwnerOrApproved = errors.New(\"caller is not token owner or approved\")\n\tErrInsufficientBalance = errors.New(\"insufficient balance for transfer\")\n\tErrBurnAmountExceedsBalance = errors.New(\"burn amount exceeds balance\")\n)\n"},{"name":"igrc1155.gno","body":"package grc1155\n\nimport \"std\"\n\ntype IGRC1155 interface {\n\tSafeTransferFrom(from, to std.Address, tid TokenID, amount uint64) error\n\tSafeBatchTransferFrom(from, to std.Address, batch []TokenID, amounts []uint64) error\n\tBalanceOf(owner std.Address, tid TokenID) (uint64, error)\n\tBalanceOfBatch(owners []std.Address, batch []TokenID) ([]uint64, error)\n\tSetApprovalForAll(operator std.Address, approved bool) error\n\tIsApprovedForAll(owner, operator std.Address) bool\n}\n\ntype TokenID string\n\ntype TransferSingleEvent struct {\n\tOperator std.Address\n\tFrom std.Address\n\tTo std.Address\n\tTokenID TokenID\n\tAmount uint64\n}\n\ntype TransferBatchEvent struct {\n\tOperator std.Address\n\tFrom std.Address\n\tTo std.Address\n\tBatch []TokenID\n\tAmounts []uint64\n}\n\ntype ApprovalForAllEvent struct {\n\tOwner std.Address\n\tOperator std.Address\n\tApproved bool\n}\n\ntype UpdateURIEvent struct {\n\tURI string\n}\n"},{"name":"util.gno","body":"package grc1155\n\nimport (\n\t\"std\"\n)\n\nconst zeroAddress std.Address = \"\"\n\nfunc isValidAddress(addr std.Address) bool {\n\tif !addr.IsValid() {\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc emit(event interface{}) {\n\t// TODO: setup a pubsub system here?\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":"banker.gno","body":"package grc20\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Banker implements a token banker with admin privileges.\n//\n// The Banker is intended to be used in two main ways:\n// 1. as a temporary object used to make the initial minting, then deleted.\n// 2. preserved in an unexported variable to support conditional administrative\n// tasks protected by the contract.\ntype Banker struct {\n\tname string\n\tsymbol string\n\tdecimals uint\n\ttotalSupply uint64\n\tbalances avl.Tree // std.Address(owner) -\u003e uint64\n\tallowances avl.Tree // string(owner+\":\"+spender) -\u003e uint64\n\ttoken *token // to share the same pointer\n}\n\nfunc NewBanker(name, symbol string, decimals uint) *Banker {\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\tb := Banker{\n\t\tname: name,\n\t\tsymbol: symbol,\n\t\tdecimals: decimals,\n\t}\n\tt := \u0026token{banker: \u0026b}\n\tb.token = t\n\treturn \u0026b\n}\n\nfunc (b Banker) Token() Token { return b.token } // Token returns a grc20 safe-object implementation.\nfunc (b Banker) GetName() string { return b.name }\nfunc (b Banker) GetSymbol() string { return b.symbol }\nfunc (b Banker) GetDecimals() uint { return b.decimals }\nfunc (b Banker) TotalSupply() uint64 { return b.totalSupply }\nfunc (b Banker) KnownAccounts() int { return b.balances.Size() }\n\nfunc (b *Banker) Mint(address std.Address, amount uint64) error {\n\tif !address.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\n\t// TODO: check for overflow\n\n\tb.totalSupply += amount\n\tcurrentBalance := b.BalanceOf(address)\n\tnewBalance := currentBalance + amount\n\n\tb.balances.Set(string(address), newBalance)\n\n\tstd.Emit(\n\t\tMintEvent,\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\nfunc (b *Banker) Burn(address std.Address, amount uint64) error {\n\tif !address.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\t// TODO: check for overflow\n\n\tcurrentBalance := b.BalanceOf(address)\n\tif currentBalance \u003c amount {\n\t\treturn ErrInsufficientBalance\n\t}\n\n\tb.totalSupply -= amount\n\tnewBalance := currentBalance - amount\n\n\tb.balances.Set(string(address), newBalance)\n\n\tstd.Emit(\n\t\tBurnEvent,\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\nfunc (b Banker) BalanceOf(address std.Address) uint64 {\n\tbalance, found := b.balances.Get(address.String())\n\tif !found {\n\t\treturn 0\n\t}\n\treturn balance.(uint64)\n}\n\nfunc (b *Banker) 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 := b.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\tb.allowances.Remove(key)\n\t} else {\n\t\tb.allowances.Set(key, newAllowance)\n\t}\n\n\treturn nil\n}\n\nfunc (b *Banker) 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\ttoBalance := b.BalanceOf(to)\n\tfromBalance := b.BalanceOf(from)\n\n\tif fromBalance \u003c amount {\n\t\treturn ErrInsufficientBalance\n\t}\n\n\tnewToBalance := toBalance + amount\n\tnewFromBalance := fromBalance - amount\n\n\tb.balances.Set(string(to), newToBalance)\n\tb.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\nfunc (b *Banker) TransferFrom(spender, from, to std.Address, amount uint64) error {\n\tif err := b.SpendAllowance(from, spender, amount); err != nil {\n\t\treturn err\n\t}\n\treturn b.Transfer(from, to, amount)\n}\n\nfunc (b *Banker) Allowance(owner, spender std.Address) uint64 {\n\tallowance, found := b.allowances.Get(allowanceKey(owner, spender))\n\tif !found {\n\t\treturn 0\n\t}\n\treturn allowance.(uint64)\n}\n\nfunc (b *Banker) Approve(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\tb.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\nfunc (b *Banker) RenderHome() string {\n\tstr := \"\"\n\tstr += ufmt.Sprintf(\"# %s ($%s)\\n\\n\", b.name, b.symbol)\n\tstr += ufmt.Sprintf(\"* **Decimals**: %d\\n\", b.decimals)\n\tstr += ufmt.Sprintf(\"* **Total supply**: %d\\n\", b.totalSupply)\n\tstr += ufmt.Sprintf(\"* **Known accounts**: %d\\n\", b.KnownAccounts())\n\treturn str\n}\n\nfunc allowanceKey(owner, spender std.Address) string {\n\treturn owner.String() + \":\" + spender.String()\n}\n"},{"name":"banker_test.gno","body":"package grc20\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestBankerImpl(t *testing.T) {\n\tdummy := NewBanker(\"Dummy\", \"DUMMY\", 4)\n\turequire.False(t, dummy == nil, \"dummy should not be nil\")\n}\n\nfunc TestAllowance(t *testing.T) {\n\tvar (\n\t\towner = testutils.TestAddress(\"owner\")\n\t\tspender = testutils.TestAddress(\"spender\")\n\t\tdest = testutils.TestAddress(\"dest\")\n\t)\n\n\tb := NewBanker(\"Dummy\", \"DUMMY\", 6)\n\turequire.NoError(t, b.Mint(owner, 100000000))\n\turequire.NoError(t, b.Approve(owner, spender, 5000000))\n\turequire.Error(t, b.TransferFrom(spender, owner, dest, 10000000), ErrInsufficientAllowance.Error(), \"should not be able to transfer more than approved\")\n\n\ttests := []struct {\n\t\tspend uint64\n\t\texp uint64\n\t}{\n\t\t{3, 4999997},\n\t\t{999997, 4000000},\n\t\t{4000000, 0},\n\t}\n\n\tfor _, tt := range tests {\n\t\tb0 := b.BalanceOf(dest)\n\t\turequire.NoError(t, b.TransferFrom(spender, owner, dest, tt.spend))\n\t\ta := b.Allowance(owner, spender)\n\t\turequire.Equal(t, a, tt.exp, ufmt.Sprintf(\"allowance exp: %d, got %d\", tt.exp, a))\n\t\tb := b.BalanceOf(dest)\n\t\texpB := b0 + tt.spend\n\t\turequire.Equal(t, b, expB, ufmt.Sprintf(\"balance exp: %d, got %d\", expB, b))\n\t}\n\n\turequire.Error(t, b.TransferFrom(spender, owner, dest, 1), \"no allowance\")\n\tkey := allowanceKey(owner, spender)\n\turequire.False(t, b.allowances.Has(key), \"allowance should be removed\")\n\turequire.Equal(t, b.Allowance(owner, spender), uint64(0), \"allowance should be 0\")\n}\n"},{"name":"token.gno","body":"package grc20\n\nimport (\n\t\"std\"\n)\n\n// token implements the Token interface.\n//\n// It is generated with Banker.Token().\n// It can safely be exposed publicly.\ntype token struct {\n\tbanker *Banker\n}\n\n// var _ Token = (*token)(nil)\nfunc (t *token) GetName() string { return t.banker.name }\nfunc (t *token) GetSymbol() string { return t.banker.symbol }\nfunc (t *token) GetDecimals() uint { return t.banker.decimals }\nfunc (t *token) TotalSupply() uint64 { return t.banker.totalSupply }\n\nfunc (t *token) BalanceOf(owner std.Address) uint64 {\n\treturn t.banker.BalanceOf(owner)\n}\n\nfunc (t *token) Transfer(to std.Address, amount uint64) error {\n\tcaller := std.PrevRealm().Addr()\n\treturn t.banker.Transfer(caller, to, amount)\n}\n\nfunc (t *token) Allowance(owner, spender std.Address) uint64 {\n\treturn t.banker.Allowance(owner, spender)\n}\n\nfunc (t *token) Approve(spender std.Address, amount uint64) error {\n\tcaller := std.PrevRealm().Addr()\n\treturn t.banker.Approve(caller, spender, amount)\n}\n\nfunc (t *token) TransferFrom(from, to std.Address, amount uint64) error {\n\tspender := std.PrevRealm().Addr()\n\tif err := t.banker.SpendAllowance(from, spender, amount); err != nil {\n\t\treturn err\n\t}\n\treturn t.banker.Transfer(from, to, amount)\n}\n"},{"name":"token_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/ufmt\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestUserTokenImpl(t *testing.T) {\n\tbank := NewBanker(\"Dummy\", \"DUMMY\", 4)\n\ttok := bank.Token()\n\t_ = tok\n}\n\nfunc TestUserApprove(t *testing.T) {\n\towner := testutils.TestAddress(\"owner\")\n\tspender := testutils.TestAddress(\"spender\")\n\tdest := testutils.TestAddress(\"dest\")\n\n\tbank := NewBanker(\"Dummy\", \"DUMMY\", 6)\n\ttok := bank.Token()\n\n\t// Set owner as the original caller\n\tstd.TestSetOrigCaller(owner)\n\t// Mint 100000000 tokens for owner\n\turequire.NoError(t, bank.Mint(owner, 100000000))\n\n\t// Approve spender to spend 5000000 tokens\n\turequire.NoError(t, tok.Approve(spender, 5000000))\n\n\t// Set spender as the original caller\n\tstd.TestSetOrigCaller(spender)\n\t// Try to transfer 10000000 tokens from owner to dest, should fail because it exceeds allowance\n\turequire.Error(t,\n\t\ttok.TransferFrom(owner, dest, 10000000),\n\t\tErrInsufficientAllowance.Error(),\n\t\t\"should not be able to transfer more than approved\",\n\t)\n\n\t// Define a set of test data with spend amount and expected remaining allowance\n\ttests := []struct {\n\t\tspend uint64 // Spend amount\n\t\texp uint64 // Remaining allowance\n\t}{\n\t\t{3, 4999997},\n\t\t{999997, 4000000},\n\t\t{4000000, 0},\n\t}\n\n\t// perform transfer operation,and check if allowance and balance are correct\n\tfor _, tt := range tests {\n\t\tb0 := tok.BalanceOf(dest)\n\t\t// Perform transfer from owner to dest\n\t\turequire.NoError(t, tok.TransferFrom(owner, dest, tt.spend))\n\t\ta := tok.Allowance(owner, spender)\n\t\t// Check if allowance equals expected value\n\t\turequire.True(t, a == tt.exp, ufmt.Sprintf(\"allowance exp: %d,got %d\", tt.exp, a))\n\n\t\t// Get dest current balance\n\t\tb := tok.BalanceOf(dest)\n\t\t// Calculate expected balance ,should be initial balance plus transfer amount\n\t\texpB := b0 + tt.spend\n\t\t// Check if balance equals expected value\n\t\turequire.True(t, b == expB, ufmt.Sprintf(\"balance exp: %d,got %d\", expB, b))\n\t}\n\n\t// Try to transfer one token from owner to dest ,should fail because no allowance left\n\turequire.Error(t, tok.TransferFrom(owner, dest, 1), ErrInsufficientAllowance.Error(), \"no allowance\")\n}\n"},{"name":"types.gno","body":"package grc20\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/grc/exts\"\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)\n\ntype Token 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 the risk\n\t// that someone may use both the old and the new allowance by unfortunate\n\t// transaction ordering. One possible solution to mitigate this race\n\t// condition is to first reduce the spender's allowance to 0 and set the\n\t// 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\nconst (\n\tMintEvent = \"Mint\"\n\tBurnEvent = \"Burn\"\n\tTransferEvent = \"Transfer\"\n\tApprovalEvent = \"Approval\"\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":"grc721","path":"gno.land/p/demo/grc/grc721","files":[{"name":"basic_nft.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype basicNFT struct {\n\tname string\n\tsymbol string\n\towners avl.Tree // tokenId -\u003e OwnerAddress\n\tbalances avl.Tree // OwnerAddress -\u003e TokenCount\n\ttokenApprovals avl.Tree // TokenId -\u003e ApprovedAddress\n\ttokenURIs avl.Tree // TokenId -\u003e URIs\n\toperatorApprovals avl.Tree // \"OwnerAddress:OperatorAddress\" -\u003e bool\n}\n\n// Returns new basic NFT\nfunc NewBasicNFT(name string, symbol string) *basicNFT {\n\treturn \u0026basicNFT{\n\t\tname: name,\n\t\tsymbol: symbol,\n\n\t\towners: avl.Tree{},\n\t\tbalances: avl.Tree{},\n\t\ttokenApprovals: avl.Tree{},\n\t\ttokenURIs: avl.Tree{},\n\t\toperatorApprovals: avl.Tree{},\n\t}\n}\n\nfunc (s *basicNFT) Name() string { return s.name }\nfunc (s *basicNFT) Symbol() string { return s.symbol }\nfunc (s *basicNFT) TokenCount() uint64 { return uint64(s.owners.Size()) }\n\n// BalanceOf returns balance of input address\nfunc (s *basicNFT) BalanceOf(addr std.Address) (uint64, error) {\n\tif err := isValidAddress(addr); err != nil {\n\t\treturn 0, err\n\t}\n\n\tbalance, found := s.balances.Get(addr.String())\n\tif !found {\n\t\treturn 0, nil\n\t}\n\n\treturn balance.(uint64), nil\n}\n\n// OwnerOf returns owner of input token id\nfunc (s *basicNFT) OwnerOf(tid TokenID) (std.Address, error) {\n\towner, found := s.owners.Get(string(tid))\n\tif !found {\n\t\treturn \"\", ErrInvalidTokenId\n\t}\n\n\treturn owner.(std.Address), nil\n}\n\n// TokenURI returns the URI of input token id\nfunc (s *basicNFT) TokenURI(tid TokenID) (string, error) {\n\turi, found := s.tokenURIs.Get(string(tid))\n\tif !found {\n\t\treturn \"\", ErrInvalidTokenId\n\t}\n\n\treturn uri.(string), nil\n}\n\nfunc (s *basicNFT) SetTokenURI(tid TokenID, tURI TokenURI) (bool, error) {\n\t// check for invalid TokenID\n\tif !s.exists(tid) {\n\t\treturn false, ErrInvalidTokenId\n\t}\n\n\t// check for the right owner\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn false, ErrCallerIsNotOwner\n\t}\n\ts.tokenURIs.Set(string(tid), string(tURI))\n\treturn true, nil\n}\n\n// IsApprovedForAll returns true if operator is approved for all by the owner.\n// Otherwise, returns false\nfunc (s *basicNFT) IsApprovedForAll(owner, operator std.Address) bool {\n\tkey := owner.String() + \":\" + operator.String()\n\t_, found := s.operatorApprovals.Get(key)\n\tif !found {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// Approve approves the input address for particular token\nfunc (s *basicNFT) Approve(to std.Address, tid TokenID) error {\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner == to {\n\t\treturn ErrApprovalToCurrentOwner\n\t}\n\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner \u0026\u0026 !s.IsApprovedForAll(owner, caller) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\ts.tokenApprovals.Set(string(tid), to.String())\n\tevent := ApprovalEvent{owner, to, tid}\n\temit(\u0026event)\n\n\treturn nil\n}\n\n// GetApproved return the approved address for token\nfunc (s *basicNFT) GetApproved(tid TokenID) (std.Address, error) {\n\taddr, found := s.tokenApprovals.Get(string(tid))\n\tif !found {\n\t\treturn zeroAddress, ErrTokenIdNotHasApproved\n\t}\n\n\treturn std.Address(addr.(string)), nil\n}\n\n// SetApprovalForAll can approve the operator to operate on all tokens\nfunc (s *basicNFT) SetApprovalForAll(operator std.Address, approved bool) error {\n\tif err := isValidAddress(operator); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tcaller := std.PrevRealm().Addr()\n\treturn s.setApprovalForAll(caller, operator, approved)\n}\n\n// Safely transfers `tokenId` token from `from` to `to`, checking that\n// contract recipients are aware of the GRC721 protocol to prevent\n// tokens from being forever locked.\nfunc (s *basicNFT) SafeTransferFrom(from, to std.Address, tid TokenID) error {\n\tcaller := std.PrevRealm().Addr()\n\tif !s.isApprovedOrOwner(caller, tid) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.transfer(from, to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.checkOnGRC721Received(from, to, tid) {\n\t\treturn ErrTransferToNonGRC721Receiver\n\t}\n\n\treturn nil\n}\n\n// Transfers `tokenId` token from `from` to `to`.\nfunc (s *basicNFT) TransferFrom(from, to std.Address, tid TokenID) error {\n\tcaller := std.PrevRealm().Addr()\n\tif !s.isApprovedOrOwner(caller, tid) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.transfer(from, to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// Mints `tokenId` and transfers it to `to`.\nfunc (s *basicNFT) Mint(to std.Address, tid TokenID) error {\n\treturn s.mint(to, tid)\n}\n\n// Mints `tokenId` and transfers it to `to`. Also checks that\n// contract recipients are using GRC721 protocol\nfunc (s *basicNFT) SafeMint(to std.Address, tid TokenID) error {\n\terr := s.mint(to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.checkOnGRC721Received(zeroAddress, to, tid) {\n\t\treturn ErrTransferToNonGRC721Receiver\n\t}\n\n\treturn nil\n}\n\nfunc (s *basicNFT) Burn(tid TokenID) error {\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ts.beforeTokenTransfer(owner, zeroAddress, tid, 1)\n\n\ts.tokenApprovals.Remove(string(tid))\n\tbalance, err := s.BalanceOf(owner)\n\tif err != nil {\n\t\treturn err\n\t}\n\tbalance -= 1\n\ts.balances.Set(owner.String(), balance)\n\ts.owners.Remove(string(tid))\n\n\tevent := TransferEvent{owner, zeroAddress, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(owner, zeroAddress, tid, 1)\n\n\treturn nil\n}\n\n/* Helper methods */\n\n// Helper for SetApprovalForAll()\nfunc (s *basicNFT) setApprovalForAll(owner, operator std.Address, approved bool) error {\n\tif owner == operator {\n\t\treturn ErrApprovalToCurrentOwner\n\t}\n\n\tkey := owner.String() + \":\" + operator.String()\n\ts.operatorApprovals.Set(key, approved)\n\n\tevent := ApprovalForAllEvent{owner, operator, approved}\n\temit(\u0026event)\n\n\treturn nil\n}\n\n// Helper for TransferFrom() and SafeTransferFrom()\nfunc (s *basicNFT) transfer(from, to std.Address, tid TokenID) error {\n\tif err := isValidAddress(from); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\tif err := isValidAddress(to); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tif from == to {\n\t\treturn ErrCannotTransferToSelf\n\t}\n\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner != from {\n\t\treturn ErrTransferFromIncorrectOwner\n\t}\n\n\ts.beforeTokenTransfer(from, to, tid, 1)\n\n\t// Check that tokenId was not transferred by `beforeTokenTransfer`\n\towner, err = s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner != from {\n\t\treturn ErrTransferFromIncorrectOwner\n\t}\n\n\ts.tokenApprovals.Remove(string(tid))\n\tfromBalance, err := s.BalanceOf(from)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance, err := s.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfromBalance -= 1\n\ttoBalance += 1\n\ts.balances.Set(from.String(), fromBalance)\n\ts.balances.Set(to.String(), toBalance)\n\ts.owners.Set(string(tid), to)\n\n\tevent := TransferEvent{from, to, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(from, to, tid, 1)\n\n\treturn nil\n}\n\n// Helper for Mint() and SafeMint()\nfunc (s *basicNFT) mint(to std.Address, tid TokenID) error {\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\tif s.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ts.beforeTokenTransfer(zeroAddress, to, tid, 1)\n\n\t// Check that tokenId was not minted by `beforeTokenTransfer`\n\tif s.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ttoBalance, err := s.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance += 1\n\ts.balances.Set(to.String(), toBalance)\n\ts.owners.Set(string(tid), to)\n\n\tevent := TransferEvent{zeroAddress, to, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(zeroAddress, to, tid, 1)\n\n\treturn nil\n}\n\nfunc (s *basicNFT) isApprovedOrOwner(addr std.Address, tid TokenID) bool {\n\towner, found := s.owners.Get(string(tid))\n\tif !found {\n\t\treturn false\n\t}\n\n\tif addr == owner.(std.Address) || s.IsApprovedForAll(owner.(std.Address), addr) {\n\t\treturn true\n\t}\n\n\t_, err := s.GetApproved(tid)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// Checks if token id already exists\nfunc (s *basicNFT) exists(tid TokenID) bool {\n\t_, found := s.owners.Get(string(tid))\n\treturn found\n}\n\nfunc (s *basicNFT) beforeTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicNFT) afterTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicNFT) checkOnGRC721Received(from, to std.Address, tid TokenID) bool {\n\t// TODO: Implementation\n\treturn true\n}\n\nfunc (s *basicNFT) RenderHome() (str string) {\n\tstr += ufmt.Sprintf(\"# %s ($%s)\\n\\n\", s.name, s.symbol)\n\tstr += ufmt.Sprintf(\"* **Total supply**: %d\\n\", s.TokenCount())\n\tstr += ufmt.Sprintf(\"* **Known accounts**: %d\\n\", s.balances.Size())\n\n\treturn\n}\n"},{"name":"basic_nft_test.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\tdummyNFTName = \"DummyNFT\"\n\tdummyNFTSymbol = \"DNFT\"\n)\n\nfunc TestNewBasicNFT(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n}\n\nfunc TestName(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tname := dummy.Name()\n\tuassert.Equal(t, dummyNFTName, name)\n}\n\nfunc TestSymbol(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tsymbol := dummy.Symbol()\n\tuassert.Equal(t, dummyNFTSymbol, symbol)\n}\n\nfunc TestTokenCount(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcount := dummy.TokenCount()\n\tuassert.Equal(t, uint64(0), count)\n\n\tdummy.mint(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\", TokenID(\"1\"))\n\tdummy.mint(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\", TokenID(\"2\"))\n\n\tcount = dummy.TokenCount()\n\tuassert.Equal(t, uint64(2), count)\n}\n\nfunc TestBalanceOf(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\tbalanceAddr1, err := dummy.BalanceOf(addr1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(0), balanceAddr1)\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\tdummy.mint(addr1, TokenID(\"2\"))\n\tdummy.mint(addr2, TokenID(\"3\"))\n\n\tbalanceAddr1, err = dummy.BalanceOf(addr1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tbalanceAddr2, err := dummy.BalanceOf(addr2)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tuassert.Equal(t, uint64(2), balanceAddr1)\n\tuassert.Equal(t, uint64(1), balanceAddr2)\n}\n\nfunc TestOwnerOf(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\towner, err := dummy.OwnerOf(TokenID(\"invalid\"))\n\tuassert.Error(t, err, \"should not result in error\")\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\tdummy.mint(addr2, TokenID(\"2\"))\n\n\t// Checking for token id \"1\"\n\towner, err = dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr1.String(), owner.String())\n\n\t// Checking for token id \"2\"\n\towner, err = dummy.OwnerOf(TokenID(\"2\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr2.String(), owner.String())\n}\n\nfunc TestIsApprovedForAll(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(addr1, addr2)\n\tuassert.False(t, isApprovedForAll)\n}\n\nfunc TestSetApprovalForAll(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(caller, addr)\n\tuassert.False(t, isApprovedForAll)\n\n\terr := dummy.SetApprovalForAll(addr, true)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tisApprovedForAll = dummy.IsApprovedForAll(caller, addr)\n\tuassert.True(t, isApprovedForAll)\n}\n\nfunc TestGetApproved(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tapprovedAddr, err := dummy.GetApproved(TokenID(\"invalid\"))\n\tuassert.Error(t, err, \"should result in error\")\n}\n\nfunc TestApprove(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\n\t_, err := dummy.GetApproved(TokenID(\"1\"))\n\tuassert.Error(t, err, \"should result in error\")\n\n\terr = dummy.Approve(addr, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tapprovedAddr, err := dummy.GetApproved(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should result in error\")\n\tuassert.Equal(t, addr.String(), approvedAddr.String())\n}\n\nfunc TestTransferFrom(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\tdummy.mint(caller, TokenID(\"2\"))\n\n\terr := dummy.TransferFrom(caller, addr, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should result in error\")\n\n\t// Check balance of caller after transfer\n\tbalanceOfCaller, err := dummy.BalanceOf(caller)\n\tuassert.NoError(t, err, \"should result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfCaller)\n\n\t// Check balance of addr after transfer\n\tbalanceOfAddr, err := dummy.BalanceOf(addr)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfAddr)\n\n\t// Check Owner of transferred Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should result in error\")\n\tuassert.Equal(t, addr.String(), owner.String())\n}\n\nfunc TestSafeTransferFrom(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\tdummy.mint(caller, TokenID(\"2\"))\n\n\terr := dummy.SafeTransferFrom(caller, addr, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check balance of caller after transfer\n\tbalanceOfCaller, err := dummy.BalanceOf(caller)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfCaller)\n\n\t// Check balance of addr after transfer\n\tbalanceOfAddr, err := dummy.BalanceOf(addr)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfAddr)\n\n\t// Check Owner of transferred Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr.String(), owner.String())\n}\n\nfunc TestMint(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\terr := dummy.Mint(addr1, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.Mint(addr1, TokenID(\"2\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.Mint(addr2, TokenID(\"3\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Try minting duplicate token id\n\terr = dummy.Mint(addr2, TokenID(\"1\"))\n\tuassert.Error(t, err, \"should not result in error\")\n\n\t// Check Owner of Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr1.String(), owner.String())\n}\n\nfunc TestBurn(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(addr, TokenID(\"1\"))\n\tdummy.mint(addr, TokenID(\"2\"))\n\n\terr := dummy.Burn(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check Owner of Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.Error(t, err, \"should result in error\")\n}\n\nfunc TestSetTokenURI(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\ttokenURI := \"http://example.com/token\"\n\n\tstd.TestSetOrigCaller(std.Address(addr1)) // addr1\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\t_, derr := dummy.SetTokenURI(TokenID(\"1\"), TokenURI(tokenURI))\n\tuassert.NoError(t, derr, \"should not result in error\")\n\n\t// Test case: Invalid token ID\n\t_, err := dummy.SetTokenURI(TokenID(\"3\"), TokenURI(tokenURI))\n\tuassert.ErrorIs(t, err, ErrInvalidTokenId)\n\n\tstd.TestSetOrigCaller(std.Address(addr2)) // addr2\n\n\t_, cerr := dummy.SetTokenURI(TokenID(\"1\"), TokenURI(tokenURI)) // addr2 trying to set URI for token 1\n\tuassert.ErrorIs(t, cerr, ErrCallerIsNotOwner)\n\n\t// Test case: Retrieving TokenURI\n\tstd.TestSetOrigCaller(std.Address(addr1)) // addr1\n\n\tdummyTokenURI, err := dummy.TokenURI(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"TokenURI error\")\n\tuassert.Equal(t, string(tokenURI), string(dummyTokenURI))\n}\n"},{"name":"errors.gno","body":"package grc721\n\nimport \"errors\"\n\nvar (\n\tErrInvalidTokenId = errors.New(\"invalid token id\")\n\tErrInvalidAddress = errors.New(\"invalid address\")\n\tErrTokenIdNotHasApproved = errors.New(\"token id not approved for anyone\")\n\tErrApprovalToCurrentOwner = errors.New(\"approval to current owner\")\n\tErrCallerIsNotOwner = errors.New(\"caller is not token owner\")\n\tErrCallerNotApprovedForAll = errors.New(\"caller is not approved for all\")\n\tErrCannotTransferToSelf = errors.New(\"cannot send transfer to self\")\n\tErrTransferFromIncorrectOwner = errors.New(\"transfer from incorrect owner\")\n\tErrTransferToNonGRC721Receiver = errors.New(\"transfer to non GRC721Receiver implementer\")\n\tErrCallerIsNotOwnerOrApproved = errors.New(\"caller is not token owner or approved\")\n\tErrTokenIdAlreadyExists = errors.New(\"token id already exists\")\n\n\t// ERC721Royalty\n\tErrInvalidRoyaltyPercentage = errors.New(\"invalid royalty percentage\")\n\tErrInvalidRoyaltyPaymentAddress = errors.New(\"invalid royalty paymentAddress\")\n\tErrCannotCalculateRoyaltyAmount = errors.New(\"cannot calculate royalty amount\")\n)\n"},{"name":"grc721_metadata.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n// metadataNFT represents an NFT with metadata extensions.\ntype metadataNFT struct {\n\t*basicNFT // Embedded basicNFT struct for basic NFT functionality\n\textensions *avl.Tree // AVL tree for storing metadata extensions\n}\n\n// Ensure that metadataNFT implements the IGRC721MetadataOnchain interface.\nvar _ IGRC721MetadataOnchain = (*metadataNFT)(nil)\n\n// NewNFTWithMetadata creates a new basic NFT with metadata extensions.\nfunc NewNFTWithMetadata(name string, symbol string) *metadataNFT {\n\t// Create a new basic NFT\n\tnft := NewBasicNFT(name, symbol)\n\n\t// Return a metadataNFT with basicNFT embedded and an empty AVL tree for extensions\n\treturn \u0026metadataNFT{\n\t\tbasicNFT: nft,\n\t\textensions: avl.NewTree(),\n\t}\n}\n\n// SetTokenMetadata sets metadata for a given token ID.\nfunc (s *metadataNFT) SetTokenMetadata(tid TokenID, metadata Metadata) error {\n\t// Check if the caller is the owner of the token\n\towner, err := s.basicNFT.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn ErrCallerIsNotOwner\n\t}\n\n\t// Set the metadata for the token ID in the extensions AVL tree\n\ts.extensions.Set(string(tid), metadata)\n\treturn nil\n}\n\n// TokenMetadata retrieves metadata for a given token ID.\nfunc (s *metadataNFT) TokenMetadata(tid TokenID) (Metadata, error) {\n\t// Retrieve metadata from the extensions AVL tree\n\tmetadata, found := s.extensions.Get(string(tid))\n\tif !found {\n\t\treturn Metadata{}, ErrInvalidTokenId\n\t}\n\n\treturn metadata.(Metadata), nil\n}\n\n// mint mints a new token and assigns it to the specified address.\nfunc (s *metadataNFT) mint(to std.Address, tid TokenID) error {\n\t// Check if the address is valid\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\t// Check if the token ID already exists\n\tif s.basicNFT.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ts.basicNFT.beforeTokenTransfer(zeroAddress, to, tid, 1)\n\n\t// Check if the token ID was minted by beforeTokenTransfer\n\tif s.basicNFT.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\t// Increment balance of the recipient address\n\ttoBalance, err := s.basicNFT.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance += 1\n\ts.basicNFT.balances.Set(to.String(), toBalance)\n\n\t// Set owner of the token ID to the recipient address\n\ts.basicNFT.owners.Set(string(tid), to)\n\n\t// Emit transfer event\n\tevent := TransferEvent{zeroAddress, to, tid}\n\temit(\u0026event)\n\n\ts.basicNFT.afterTokenTransfer(zeroAddress, to, tid, 1)\n\n\treturn nil\n}\n"},{"name":"grc721_metadata_test.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestSetMetadata(t *testing.T) {\n\t// Create a new dummy NFT with metadata\n\tdummy := NewNFTWithMetadata(dummyNFTName, dummyNFTSymbol)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n\n\t// Define addresses for testing purposes\n\taddr1 := testutils.TestAddress(\"alice\")\n\taddr2 := testutils.TestAddress(\"bob\")\n\n\t// Define metadata attributes\n\tname := \"test\"\n\tdescription := \"test\"\n\timage := \"test\"\n\timageData := \"test\"\n\texternalURL := \"test\"\n\tattributes := []Trait{}\n\tbackgroundColor := \"test\"\n\tanimationURL := \"test\"\n\tyoutubeURL := \"test\"\n\n\t// Set the original caller to addr1\n\tstd.TestSetOrigCaller(addr1) // addr1\n\n\t// Mint a new token for addr1\n\tdummy.mint(addr1, TokenID(\"1\"))\n\n\t// Set metadata for token 1\n\tderr := dummy.SetTokenMetadata(TokenID(\"1\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if there was an error setting metadata\n\tuassert.NoError(t, derr, \"Should not result in error\")\n\n\t// Test case: Invalid token ID\n\terr := dummy.SetTokenMetadata(TokenID(\"3\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if the error returned matches the expected error\n\tuassert.ErrorIs(t, err, ErrInvalidTokenId)\n\n\t// Set the original caller to addr2\n\tstd.TestSetOrigCaller(addr2) // addr2\n\n\t// Try to set metadata for token 1 from addr2 (should fail)\n\tcerr := dummy.SetTokenMetadata(TokenID(\"1\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if the error returned matches the expected error\n\tuassert.ErrorIs(t, cerr, ErrCallerIsNotOwner)\n\n\t// Set the original caller back to addr1\n\tstd.TestSetOrigCaller(addr1) // addr1\n\n\t// Retrieve metadata for token 1\n\tdummyMetadata, err := dummy.TokenMetadata(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"Metadata error\")\n\n\t// Check if metadata attributes match expected values\n\tuassert.Equal(t, image, dummyMetadata.Image)\n\tuassert.Equal(t, imageData, dummyMetadata.ImageData)\n\tuassert.Equal(t, externalURL, dummyMetadata.ExternalURL)\n\tuassert.Equal(t, description, dummyMetadata.Description)\n\tuassert.Equal(t, name, dummyMetadata.Name)\n\tuassert.Equal(t, len(attributes), len(dummyMetadata.Attributes))\n\tuassert.Equal(t, backgroundColor, dummyMetadata.BackgroundColor)\n\tuassert.Equal(t, animationURL, dummyMetadata.AnimationURL)\n\tuassert.Equal(t, youtubeURL, dummyMetadata.YoutubeURL)\n}\n"},{"name":"grc721_royalty.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n// royaltyNFT represents a non-fungible token (NFT) with royalty functionality.\ntype royaltyNFT struct {\n\t*metadataNFT // Embedding metadataNFT for NFT functionality\n\ttokenRoyaltyInfo *avl.Tree // AVL tree to store royalty information for each token\n\tmaxRoyaltyPercentage uint64 // maxRoyaltyPercentage represents the maximum royalty percentage that can be charged every sale\n}\n\n// Ensure that royaltyNFT implements the IGRC2981 interface.\nvar _ IGRC2981 = (*royaltyNFT)(nil)\n\n// NewNFTWithRoyalty creates a new royalty NFT with the specified name, symbol, and royalty calculator.\nfunc NewNFTWithRoyalty(name string, symbol string) *royaltyNFT {\n\t// Create a new NFT with metadata\n\tnft := NewNFTWithMetadata(name, symbol)\n\n\treturn \u0026royaltyNFT{\n\t\tmetadataNFT: nft,\n\t\ttokenRoyaltyInfo: avl.NewTree(),\n\t\tmaxRoyaltyPercentage: 100,\n\t}\n}\n\n// SetTokenRoyalty sets the royalty information for a specific token ID.\nfunc (r *royaltyNFT) SetTokenRoyalty(tid TokenID, royaltyInfo RoyaltyInfo) error {\n\t// Validate the payment address\n\tif err := isValidAddress(royaltyInfo.PaymentAddress); err != nil {\n\t\treturn ErrInvalidRoyaltyPaymentAddress\n\t}\n\n\t// Check if royalty percentage exceeds maxRoyaltyPercentage\n\tif royaltyInfo.Percentage \u003e r.maxRoyaltyPercentage {\n\t\treturn ErrInvalidRoyaltyPercentage\n\t}\n\n\t// Check if the caller is the owner of the token\n\towner, err := r.metadataNFT.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn ErrCallerIsNotOwner\n\t}\n\n\t// Set royalty information for the token\n\tr.tokenRoyaltyInfo.Set(string(tid), royaltyInfo)\n\n\treturn nil\n}\n\n// RoyaltyInfo returns the royalty information for the given token ID and sale price.\nfunc (r *royaltyNFT) RoyaltyInfo(tid TokenID, salePrice uint64) (std.Address, uint64, error) {\n\t// Retrieve royalty information for the token\n\tval, found := r.tokenRoyaltyInfo.Get(string(tid))\n\tif !found {\n\t\treturn \"\", 0, ErrInvalidTokenId\n\t}\n\n\troyaltyInfo := val.(RoyaltyInfo)\n\n\t// Calculate royalty amount\n\troyaltyAmount, _ := r.calculateRoyaltyAmount(salePrice, royaltyInfo.Percentage)\n\n\treturn royaltyInfo.PaymentAddress, royaltyAmount, nil\n}\n\nfunc (r *royaltyNFT) calculateRoyaltyAmount(salePrice, percentage uint64) (uint64, error) {\n\troyaltyAmount := (salePrice * percentage) / 100\n\treturn royaltyAmount, nil\n}\n"},{"name":"grc721_royalty_test.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestSetTokenRoyalty(t *testing.T) {\n\tdummy := NewNFTWithRoyalty(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := testutils.TestAddress(\"alice\")\n\taddr2 := testutils.TestAddress(\"bob\")\n\n\tpaymentAddress := testutils.TestAddress(\"john\")\n\tpercentage := uint64(10) // 10%\n\n\tsalePrice := uint64(1000)\n\texpectRoyaltyAmount := uint64(100)\n\n\tstd.TestSetOrigCaller(addr1) // addr1\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\n\tderr := dummy.SetTokenRoyalty(TokenID(\"1\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tuassert.NoError(t, derr, \"Should not result in error\")\n\n\t// Test case: Invalid token ID\n\terr := dummy.SetTokenRoyalty(TokenID(\"3\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tuassert.ErrorIs(t, derr, ErrInvalidTokenId)\n\n\tstd.TestSetOrigCaller(addr2) // addr2\n\n\tcerr := dummy.SetTokenRoyalty(TokenID(\"1\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tuassert.ErrorIs(t, cerr, ErrCallerIsNotOwner)\n\n\t// Test case: Invalid payment address\n\taerr := dummy.SetTokenRoyalty(TokenID(\"4\"), RoyaltyInfo{\n\t\tPaymentAddress: std.Address(\"###\"), // invalid address\n\t\tPercentage: percentage,\n\t})\n\tuassert.ErrorIs(t, aerr, ErrInvalidRoyaltyPaymentAddress)\n\n\t// Test case: Invalid percentage\n\tperr := dummy.SetTokenRoyalty(TokenID(\"5\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: uint64(200), // over maxRoyaltyPercentage\n\t})\n\tuassert.ErrorIs(t, perr, ErrInvalidRoyaltyPercentage)\n\n\t// Test case: Retrieving Royalty Info\n\tstd.TestSetOrigCaller(addr1) // addr1\n\n\tdummyPaymentAddress, dummyRoyaltyAmount, rerr := dummy.RoyaltyInfo(TokenID(\"1\"), salePrice)\n\tuassert.NoError(t, rerr, \"RoyaltyInfo error\")\n\tuassert.Equal(t, paymentAddress, dummyPaymentAddress)\n\tuassert.Equal(t, expectRoyaltyAmount, dummyRoyaltyAmount)\n}\n"},{"name":"igrc721.gno","body":"package grc721\n\nimport \"std\"\n\ntype IGRC721 interface {\n\tBalanceOf(owner std.Address) (uint64, error)\n\tOwnerOf(tid TokenID) (std.Address, error)\n\tSetTokenURI(tid TokenID, tURI TokenURI) (bool, error)\n\tSafeTransferFrom(from, to std.Address, tid TokenID) error\n\tTransferFrom(from, to std.Address, tid TokenID) error\n\tApprove(approved std.Address, tid TokenID) error\n\tSetApprovalForAll(operator std.Address, approved bool) error\n\tGetApproved(tid TokenID) (std.Address, error)\n\tIsApprovedForAll(owner, operator std.Address) bool\n}\n\ntype (\n\tTokenID string\n\tTokenURI string\n)\n\ntype TransferEvent struct {\n\tFrom std.Address\n\tTo std.Address\n\tTokenID TokenID\n}\n\ntype ApprovalEvent struct {\n\tOwner std.Address\n\tApproved std.Address\n\tTokenID TokenID\n}\n\ntype ApprovalForAllEvent struct {\n\tOwner std.Address\n\tOperator std.Address\n\tApproved bool\n}\n"},{"name":"igrc721_metadata.gno","body":"package grc721\n\n// IGRC721CollectionMetadata describes basic information about an NFT collection.\ntype IGRC721CollectionMetadata interface {\n\tName() string // Name returns the name of the collection.\n\tSymbol() string // Symbol returns the symbol of the collection.\n}\n\n// IGRC721Metadata follows the Ethereum standard\ntype IGRC721Metadata interface {\n\tIGRC721CollectionMetadata\n\tTokenURI(tid TokenID) (string, error) // TokenURI returns the URI of a specific token.\n}\n\n// IGRC721Metadata follows the OpenSea metadata standard\ntype IGRC721MetadataOnchain interface {\n\tIGRC721CollectionMetadata\n\tTokenMetadata(tid TokenID) (Metadata, error)\n}\n\ntype Trait struct {\n\tDisplayType string\n\tTraitType string\n\tValue string\n}\n\n// see: https://docs.opensea.io/docs/metadata-standards\ntype Metadata struct {\n\tImage string // URL to the image of the item. Can be any type of image (including SVGs, which will be cached into PNGs by OpenSea), IPFS or Arweave URLs or paths. We recommend using a minimum 3000 x 3000 image.\n\tImageData string // Raw SVG image data, if you want to generate images on the fly (not recommended). Only use this if you're not including the image parameter.\n\tExternalURL string // URL that will appear below the asset's image on OpenSea and will allow users to leave OpenSea and view the item on your site.\n\tDescription string // Human-readable description of the item. Markdown is supported.\n\tName string // Name of the item.\n\tAttributes []Trait // Attributes for the item, which will show up on the OpenSea page for the item.\n\tBackgroundColor string // Background color of the item on OpenSea. Must be a six-character hexadecimal without a pre-pended #\n\tAnimationURL string // URL to a multimedia attachment for the item. Supported file extensions: GLTF, GLB, WEBM, MP4, M4V, OGV, OGG, MP3, WAV, OGA, HTML (for rich experiences and interactive NFTs using JavaScript canvas, WebGL, etc.). Scripts and relative paths within the HTML page are now supported. Access to browser extensions is not supported.\n\tYoutubeURL string // URL to a YouTube video (only used if animation_url is not provided).\n}\n"},{"name":"igrc721_royalty.gno","body":"package grc721\n\nimport \"std\"\n\n// IGRC2981 follows the Ethereum standard\ntype IGRC2981 interface {\n\t// RoyaltyInfo retrieves royalty information for a tokenID and salePrice.\n\t// It returns the payment address, royalty amount, and an error if any.\n\tRoyaltyInfo(tokenID TokenID, salePrice uint64) (std.Address, uint64, error)\n}\n\n// RoyaltyInfo represents royalty information for a token.\ntype RoyaltyInfo struct {\n\tPaymentAddress std.Address // PaymentAddress is the address where royalty payment should be sent.\n\tPercentage uint64 // Percentage is the royalty percentage. It indicates the percentage of royalty to be paid for each sale. For example : Percentage = 10 =\u003e 10%\n}\n"},{"name":"util.gno","body":"package grc721\n\nimport (\n\t\"std\"\n)\n\nvar zeroAddress = std.Address(\"\")\n\nfunc isValidAddress(addr std.Address) error {\n\tif !addr.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\treturn nil\n}\n\nfunc emit(event interface{}) {\n\t// TODO: setup a pubsub system here?\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":"grc777","path":"gno.land/p/demo/grc/grc777","files":[{"name":"dummy_test.gno","body":"package grc777\n\nimport (\n\t\"std\"\n\t\"testing\"\n)\n\ntype dummyImpl struct{}\n\n// FIXME: this should fail.\nvar _ IGRC777 = (*dummyImpl)(nil)\n\nfunc TestInterface(t *testing.T) {\n\tvar dummy IGRC777 = \u0026dummyImpl{}\n}\n\nfunc (impl *dummyImpl) GetName() string { panic(\"not implemented\") }\nfunc (impl *dummyImpl) GetSymbol() string { panic(\"not implemented\") }\nfunc (impl *dummyImpl) GetDecimals() uint { panic(\"not implemented\") }\nfunc (impl *dummyImpl) Granularity() (granularity uint64) { panic(\"not implemented\") }\nfunc (impl *dummyImpl) TotalSupply() (supply uint64) { panic(\"not implemented\") }\nfunc (impl *dummyImpl) BalanceOf(address std.Address) uint64 { panic(\"not implemented\") }\nfunc (impl *dummyImpl) Burn(amount uint64, data []byte) { panic(\"not implemented\") }\nfunc (impl *dummyImpl) AuthorizeOperator(operator std.Address) { panic(\"not implemented\") }\nfunc (impl *dummyImpl) RevokeOperator(operators std.Address) { panic(\"not implemented\") }\nfunc (impl *dummyImpl) DefaultOperators() []std.Address { panic(\"not implemented\") }\nfunc (impl *dummyImpl) Send(recipient std.Address, amount uint64, data []byte) {\n\tpanic(\"not implemented\")\n}\n\nfunc (impl *dummyImpl) IsOperatorFor(operator, tokenHolder std.Address) bool {\n\tpanic(\"not implemented\")\n}\n\nfunc (impl *dummyImpl) OperatorSend(sender, recipient std.Address, amount uint64, data, operatorData []byte) {\n\tpanic(\"not implemented\")\n}\n\nfunc (impl *dummyImpl) OperatorBurn(account std.Address, amount uint64, data, operatorData []byte) {\n\tpanic(\"not implemented\")\n}\n"},{"name":"igrc777.gno","body":"package grc777\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/grc/exts\"\n)\n\n// TODO: use big.Int or a custom uint64 instead of uint64\n\ntype IGRC777 interface {\n\texts.TokenMetadata\n\n\t// Returns the smallest part of the token that is not divisible. This\n\t// means all token operations (creation, movement and destruction) must\n\t// have amounts that are a multiple of this number.\n\t//\n\t// For most token contracts, this value will equal 1.\n\tGranularity() (granularity uint64)\n\n\t// Returns the amount of tokens in existence.\n\tTotalSupply() (supply uint64)\n\n\t// Returns the amount of tokens owned by an account (`owner`).\n\tBalanceOf(address std.Address) uint64\n\n\t// Moves `amount` tokens from the caller's account to `recipient`.\n\t//\n\t// If send or receive hooks are registered for the caller and `recipient`,\n\t// the corresponding functions will be called with `data` and empty\n\t// `operatorData`. See {IERC777Sender} and {IERC777Recipient}.\n\t//\n\t// Emits a {Sent} event.\n\t//\n\t// Requirements\n\t//\n\t// - the caller must have at least `amount` tokens.\n\t// - `recipient` cannot be the zero address.\n\t// - if `recipient` is a contract, it must implement the {IERC777Recipient}\n\t// interface.\n\tSend(recipient std.Address, amount uint64, data []byte)\n\n\t// Destroys `amount` tokens from the caller's account, reducing the\n\t// total supply.\n\t//\n\t// If a send hook is registered for the caller, the corresponding function\n\t// will be called with `data` and empty `operatorData`. See {IERC777Sender}.\n\t//\n\t// Emits a {Burned} event.\n\t//\n\t// Requirements\n\t//\n\t// - the caller must have at least `amount` tokens.\n\tBurn(amount uint64, data []byte)\n\n\t// Returns true if an account is an operator of `tokenHolder`.\n\t// Operators can send and burn tokens on behalf of their owners. All\n\t// accounts are their own operator.\n\t//\n\t// See {operatorSend} and {operatorBurn}.\n\tIsOperatorFor(operator, tokenHolder std.Address) bool\n\n\t// Make an account an operator of the caller.\n\t//\n\t// See {isOperatorFor}.\n\t//\n\t// Emits an {AuthorizedOperator} event.\n\t//\n\t// Requirements\n\t//\n\t// - `operator` cannot be calling address.\n\tAuthorizeOperator(operator std.Address)\n\n\t// Revoke an account's operator status for the caller.\n\t//\n\t// See {isOperatorFor} and {defaultOperators}.\n\t//\n\t// Emits a {RevokedOperator} event.\n\t//\n\t// Requirements\n\t//\n\t// - `operator` cannot be calling address.\n\tRevokeOperator(operators std.Address)\n\n\t// Returns the list of default operators. These accounts are operators\n\t// for all token holders, even if {authorizeOperator} was never called on\n\t// them.\n\t//\n\t// This list is immutable, but individual holders may revoke these via\n\t// {revokeOperator}, in which case {isOperatorFor} will return false.\n\tDefaultOperators() []std.Address\n\n\t// Moves `amount` tokens from `sender` to `recipient`. The caller must\n\t// be an operator of `sender`.\n\t//\n\t// If send or receive hooks are registered for `sender` and `recipient`,\n\t// the corresponding functions will be called with `data` and\n\t// `operatorData`. See {IERC777Sender} and {IERC777Recipient}.\n\t//\n\t// Emits a {Sent} event.\n\t//\n\t// Requirements\n\t//\n\t// - `sender` cannot be the zero address.\n\t// - `sender` must have at least `amount` tokens.\n\t// - the caller must be an operator for `sender`.\n\t// - `recipient` cannot be the zero address.\n\t// - if `recipient` is a contract, it must implement the {IERC777Recipient}\n\t// interface.\n\tOperatorSend(sender, recipient std.Address, amount uint64, data, operatorData []byte)\n\n\t// Destroys `amount` tokens from `account`, reducing the total supply.\n\t// The caller must be an operator of `account`.\n\t//\n\t// If a send hook is registered for `account`, the corresponding function\n\t// will be called with `data` and `operatorData`. See {IERC777Sender}.\n\t//\n\t// Emits a {Burned} event.\n\t//\n\t// Requirements\n\t//\n\t// - `account` cannot be the zero address.\n\t// - `account` must have at least `amount` tokens.\n\t// - the caller must be an operator for `account`.\n\tOperatorBurn(account std.Address, amount uint64, data, operatorData []byte)\n}\n\n// Emitted when `amount` tokens are created by `operator` and assigned to `to`.\n//\n// Note that some additional user `data` and `operatorData` can be logged in the event.\ntype MintedEvent struct {\n\tOperator std.Address\n\tTo std.Address\n\tAmount uint64\n\tData []byte\n\tOperatorData []byte\n}\n\n// Emitted when `operator` destroys `amount` tokens from `account`.\n//\n// Note that some additional user `data` and `operatorData` can be logged in the event.\ntype BurnedEvent struct {\n\tOperator std.Address\n\tFrom std.Address\n\tAmount uint64\n\tData []byte\n\tOperatorData []byte\n}\n\n// Emitted when `operator` is made operator for `tokenHolder`\ntype AuthorizedOperatorEvent struct {\n\tOperator std.Address\n\tTokenHolder std.Address\n}\n\n// Emitted when `operator` is revoked its operator status for `tokenHolder`.\ntype RevokedOperatorEvent struct {\n\tOperator std.Address\n\tTokenHolder std.Address\n}\n\ntype SentEvent struct {\n\tOperator std.Address\n\tFrom std.Address\n\tTo std.Address\n\tAmount uint64\n\tData []byte\n\tOperatorData []byte\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":"rat","path":"gno.land/p/demo/rat","files":[{"name":"maths.gno","body":"package rat\n\nconst (\n\tintSize = 32 \u003c\u003c (^uint(0) \u003e\u003e 63) // 32 or 64\n\n\tMaxInt = 1\u003c\u003c(intSize-1) - 1\n\tMinInt = -1 \u003c\u003c (intSize - 1)\n\tMaxInt8 = 1\u003c\u003c7 - 1\n\tMinInt8 = -1 \u003c\u003c 7\n\tMaxInt16 = 1\u003c\u003c15 - 1\n\tMinInt16 = -1 \u003c\u003c 15\n\tMaxInt32 = 1\u003c\u003c31 - 1\n\tMinInt32 = -1 \u003c\u003c 31\n\tMaxInt64 = 1\u003c\u003c63 - 1\n\tMinInt64 = -1 \u003c\u003c 63\n\tMaxUint = 1\u003c\u003cintSize - 1\n\tMaxUint8 = 1\u003c\u003c8 - 1\n\tMaxUint16 = 1\u003c\u003c16 - 1\n\tMaxUint32 = 1\u003c\u003c32 - 1\n\tMaxUint64 = 1\u003c\u003c64 - 1\n)\n"},{"name":"rat.gno","body":"package rat\n\n//----------------------------------------\n// Rat fractions\n\n// represents a fraction.\ntype Rat struct {\n\tX int32\n\tY int32 // must be positive\n}\n\nfunc NewRat(x, y int32) Rat {\n\tif y \u003c= 0 {\n\t\tpanic(\"invalid std.Rat denominator\")\n\t}\n\treturn Rat{X: x, Y: y}\n}\n\nfunc (r1 Rat) IsValid() bool {\n\tif r1.Y \u003c= 0 {\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc (r1 Rat) Cmp(r2 Rat) int {\n\tif !r1.IsValid() {\n\t\tpanic(\"invalid std.Rat left operand\")\n\t}\n\tif !r2.IsValid() {\n\t\tpanic(\"invalid std.Rat right operand\")\n\t}\n\tvar p1, p2 int64\n\tp1 = int64(r1.X) * int64(r2.Y)\n\tp2 = int64(r1.Y) * int64(r2.X)\n\tif p1 \u003c p2 {\n\t\treturn -1\n\t} else if p1 == p2 {\n\t\treturn 0\n\t} else {\n\t\treturn 1\n\t}\n}\n\n//func (r1 Rat) Plus(r2 Rat) Rat {\n// XXX\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":"txlink","path":"gno.land/p/moul/txlink","files":[{"name":"txlink.gno","body":"// Package txlink provides utilities for creating transaction-related links\n// compatible with Gnoweb, Gnobro, and other clients within the Gno ecosystem.\n//\n// This package is optimized for generating lightweight transaction links with\n// flexible arguments, allowing users to build dynamic links that integrate\n// seamlessly with various Gno clients.\n//\n// The primary function, URL, is designed to produce markdown links for\n// transaction functions in the current \"relative realm\". By specifying a custom\n// Realm, you can generate links that either use the current realm path or a\n// fully qualified path for another realm.\n//\n// This package is a streamlined alternative to helplink, providing similar\n// functionality for transaction links without the full feature set of helplink.\npackage txlink\n\nimport (\n\t\"std\"\n\t\"strings\"\n)\n\nconst chainDomain = \"gno.land\" // XXX: std.ChainDomain (#2911)\n\n// URL returns a URL for the specified function with optional key-value\n// arguments, for the current realm.\nfunc URL(fn string, args ...string) string {\n\treturn Realm(\"\").URL(fn, args...)\n}\n\n// Realm represents a specific realm for generating tx links.\ntype Realm string\n\n// prefix returns the URL prefix for the realm.\nfunc (r Realm) prefix() string {\n\t// relative\n\tif r == \"\" {\n\t\tcurPath := std.CurrentRealm().PkgPath()\n\t\treturn strings.TrimPrefix(curPath, chainDomain)\n\t}\n\n\t// local realm -\u003e /realm\n\trealm := string(r)\n\tif strings.Contains(realm, chainDomain) {\n\t\treturn strings.TrimPrefix(realm, chainDomain)\n\t}\n\n\t// remote realm -\u003e https://remote.land/realm\n\treturn \"https://\" + string(r)\n}\n\n// URL returns a URL for the specified function with optional key-value\n// arguments.\nfunc (r Realm) URL(fn string, args ...string) string {\n\t// Start with the base query\n\turl := r.prefix() + \"$help\u0026func=\" + fn\n\n\t// Check if args length is even\n\tif len(args)%2 != 0 {\n\t\t// If not even, we can choose to handle the error here.\n\t\t// For example, we can just return the URL without appending\n\t\t// more args.\n\t\treturn url\n\t}\n\n\t// Append key-value pairs to the URL\n\tfor i := 0; i \u003c len(args); i += 2 {\n\t\tkey := args[i]\n\t\tvalue := args[i+1]\n\t\t// XXX: escape keys and args\n\t\turl += \"\u0026\" + key + \"=\" + value\n\t}\n\n\treturn url\n}\n"},{"name":"txlink_test.gno","body":"package txlink\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestURL(t *testing.T) {\n\ttests := []struct {\n\t\tfn string\n\t\targs []string\n\t\twant string\n\t\trealm Realm\n\t}{\n\t\t{\"foo\", []string{\"bar\", \"1\", \"baz\", \"2\"}, \"$help\u0026func=foo\u0026bar=1\u0026baz=2\", \"\"},\n\t\t{\"testFunc\", []string{\"key\", \"value\"}, \"$help\u0026func=testFunc\u0026key=value\", \"\"},\n\t\t{\"noArgsFunc\", []string{}, \"$help\u0026func=noArgsFunc\", \"\"},\n\t\t{\"oddArgsFunc\", []string{\"key\"}, \"$help\u0026func=oddArgsFunc\", \"\"},\n\t\t{\"foo\", []string{\"bar\", \"1\", \"baz\", \"2\"}, \"/r/lorem/ipsum$help\u0026func=foo\u0026bar=1\u0026baz=2\", \"gno.land/r/lorem/ipsum\"},\n\t\t{\"testFunc\", []string{\"key\", \"value\"}, \"/r/lorem/ipsum$help\u0026func=testFunc\u0026key=value\", \"gno.land/r/lorem/ipsum\"},\n\t\t{\"noArgsFunc\", []string{}, \"/r/lorem/ipsum$help\u0026func=noArgsFunc\", \"gno.land/r/lorem/ipsum\"},\n\t\t{\"oddArgsFunc\", []string{\"key\"}, \"/r/lorem/ipsum$help\u0026func=oddArgsFunc\", \"gno.land/r/lorem/ipsum\"},\n\t\t{\"foo\", []string{\"bar\", \"1\", \"baz\", \"2\"}, \"https://gno.world/r/lorem/ipsum$help\u0026func=foo\u0026bar=1\u0026baz=2\", \"gno.world/r/lorem/ipsum\"},\n\t\t{\"testFunc\", []string{\"key\", \"value\"}, \"https://gno.world/r/lorem/ipsum$help\u0026func=testFunc\u0026key=value\", \"gno.world/r/lorem/ipsum\"},\n\t\t{\"noArgsFunc\", []string{}, \"https://gno.world/r/lorem/ipsum$help\u0026func=noArgsFunc\", \"gno.world/r/lorem/ipsum\"},\n\t\t{\"oddArgsFunc\", []string{\"key\"}, \"https://gno.world/r/lorem/ipsum$help\u0026func=oddArgsFunc\", \"gno.world/r/lorem/ipsum\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\ttitle := tt.fn\n\t\tt.Run(title, func(t *testing.T) {\n\t\t\tgot := tt.realm.URL(tt.fn, tt.args...)\n\t\t\turequire.Equal(t, tt.want, got)\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":"users","path":"gno.land/p/demo/users","files":[{"name":"types.gno","body":"package users\n\ntype AddressOrName string\n\nfunc (aon AddressOrName) IsName() bool {\n\treturn aon != \"\" \u0026\u0026 aon[0] == '@'\n}\n\nfunc (aon AddressOrName) GetName() (string, bool) {\n\tif len(aon) \u003e= 2 \u0026\u0026 aon[0] == '@' {\n\t\treturn string(aon[1:]), true\n\t}\n\treturn \"\", false\n}\n"},{"name":"users.gno","body":"package users\n\nimport (\n\t\"std\"\n\t\"strconv\"\n)\n\n//----------------------------------------\n// Types\n\ntype User struct {\n\tAddress std.Address\n\tName string\n\tProfile string\n\tNumber int\n\tInvites int\n\tInviter std.Address\n}\n\nfunc (u *User) Render() string {\n\tstr := \"## user \" + u.Name + \"\\n\" +\n\t\t\"\\n\" +\n\t\t\" * address = \" + string(u.Address) + \"\\n\" +\n\t\t\" * \" + strconv.Itoa(u.Invites) + \" invites\\n\"\n\tif u.Inviter != \"\" {\n\t\tstr = str + \" * invited by \" + string(u.Inviter) + \"\\n\"\n\t}\n\tstr = str + \"\\n\" +\n\t\tu.Profile + \"\\n\"\n\treturn str\n}\n"},{"name":"users_test.gno","body":"package users\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":"users","path":"gno.land/r/demo/users","files":[{"name":"preregister.gno","body":"package users\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/users\"\n)\n\n// pre-restricted names\nvar preRestrictedNames = []string{\n\t\"bitcoin\", \"cosmos\", \"newtendermint\", \"ethereum\",\n}\n\n// pre-registered users\nvar preRegisteredUsers = []struct {\n\tName string\n\tAddress std.Address\n}{\n\t// system name\n\t{\"archives\", \"g1xlnyjrnf03ju82v0f98ruhpgnquk28knmjfe5k\"}, // -\u003e @r_archives\n\t{\"demo\", \"g13ek2zz9qurzynzvssyc4sthwppnruhnp0gdz8n\"}, // -\u003e @r_demo\n\t{\"gno\", \"g19602kd9tfxrfd60sgreadt9zvdyyuudcyxsz8a\"}, // -\u003e @r_gno\n\t{\"gnoland\", \"g1g3lsfxhvaqgdv4ccemwpnms4fv6t3aq3p5z6u7\"}, // -\u003e @r_gnoland\n\t{\"gnolang\", \"g1yjlnm3z2630gg5mryjd79907e0zx658wxs9hnd\"}, // -\u003e @r_gnolang\n\t{\"gov\", \"g1g73v2anukg4ej7axwqpthsatzrxjsh0wk797da\"}, // -\u003e @r_gov\n\t{\"nt\", \"g15ge0ae9077eh40erwrn2eq0xw6wupwqthpv34l\"}, // -\u003e @r_nt\n\t{\"sys\", \"g1r929wt2qplfawe4lvqv9zuwfdcz4vxdun7qh8l\"}, // -\u003e @r_sys\n\t{\"x\", \"g164sdpew3c2t3rvxj3kmfv7c7ujlvcw2punzzuz\"}, // -\u003e @r_x\n\n\t// test1 user\n\t{\"test1\", \"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\"}, // -\u003e @test1\n\n\t// Onbloc\n\t{\"gnoswap\", \"g1lmvrrrr4er2us84h2732sru76c9zl2nvknha8c\"}, // -\u003e @r_gnoswap\n\t{\"onbloc\", \"g12vx7dn3dqq89mz550zwunvg4qw6epq73d9csay\"}, // -\u003e @r_onbloc\n\n\t// Dragos\n\t{\"flippando\", \"g1z82x8mxa0pz5s9u7csy6zya4x0ut9uw6p7d8dk\"}, // -\u003e @r_flippando\n\t{\"zentasktic\", \"g1paxgmwy2wzhx0l6qvav2p8thvphc5c030xz35c\"}, // -\u003e @r_zentasktic\n}\n\nfunc init() {\n\t// add pre-registered users\n\tfor _, res := range preRegisteredUsers {\n\t\t// assert not already registered.\n\t\t_, ok := name2User.Get(res.Name)\n\t\tif ok {\n\t\t\tpanic(\"name already registered\")\n\t\t}\n\n\t\t_, ok = addr2User.Get(res.Address.String())\n\t\tif ok {\n\t\t\tpanic(\"address already registered\")\n\t\t}\n\n\t\tcounter++\n\t\tuser := \u0026users.User{\n\t\t\tAddress: res.Address,\n\t\t\tName: res.Name,\n\t\t\tProfile: \"\",\n\t\t\tNumber: counter,\n\t\t\tInvites: int(0),\n\t\t\tInviter: admin,\n\t\t}\n\t\tname2User.Set(res.Name, user)\n\t\taddr2User.Set(res.Address.String(), user)\n\t}\n\n\t// add pre-restricted names\n\tfor _, name := range preRestrictedNames {\n\t\tif _, ok := name2User.Get(name); ok {\n\t\t\tpanic(\"name already registered\")\n\t\t}\n\n\t\trestricted.Set(name, true)\n\t}\n}\n"},{"name":"users.gno","body":"package users\n\nimport (\n\t\"regexp\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/avl/pager\"\n\t\"gno.land/p/demo/avlhelpers\"\n\t\"gno.land/p/demo/users\"\n)\n\n//----------------------------------------\n// State\n\nvar (\n\tadmin std.Address = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\" // @moul\n\n\trestricted avl.Tree // Name -\u003e true - restricted name\n\tname2User avl.Tree // Name -\u003e *users.User\n\taddr2User avl.Tree // std.Address -\u003e *users.User\n\tinvites avl.Tree // string(inviter+\":\"+invited) -\u003e true\n\tcounter int // user id counter\n\tminFee int64 = 20 * 1_000_000 // minimum gnot must be paid to register.\n\tmaxFeeMult int64 = 10 // maximum multiples of minFee accepted.\n)\n\n//----------------------------------------\n// Top-level functions\n\nfunc Register(inviter std.Address, name string, profile string) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// assert invited or paid.\n\tcaller := std.GetCallerAt(2)\n\tif caller != std.GetOrigCaller() {\n\t\tpanic(\"should not happen\") // because std.AssertOrigCall().\n\t}\n\n\tsentCoins := std.GetOrigSend()\n\tminCoin := std.NewCoin(\"ugnot\", minFee)\n\n\tif inviter == \"\" {\n\t\t// banker := std.GetBanker(std.BankerTypeOrigSend)\n\t\tif len(sentCoins) == 1 \u0026\u0026 sentCoins[0].IsGTE(minCoin) {\n\t\t\tif sentCoins[0].Amount \u003e minFee*maxFeeMult {\n\t\t\t\tpanic(\"payment must not be greater than \" + strconv.Itoa(int(minFee*maxFeeMult)))\n\t\t\t} else {\n\t\t\t\t// ok\n\t\t\t}\n\t\t} else {\n\t\t\tpanic(\"payment must not be less than \" + strconv.Itoa(int(minFee)))\n\t\t}\n\t} else {\n\t\tinvitekey := inviter.String() + \":\" + caller.String()\n\t\t_, ok := invites.Get(invitekey)\n\t\tif !ok {\n\t\t\tpanic(\"invalid invitation\")\n\t\t}\n\t\tinvites.Remove(invitekey)\n\t}\n\n\t// assert not already registered.\n\t_, ok := name2User.Get(name)\n\tif ok {\n\t\tpanic(\"name already registered: \" + name)\n\t}\n\t_, ok = addr2User.Get(caller.String())\n\tif ok {\n\t\tpanic(\"address already registered: \" + caller.String())\n\t}\n\n\tisInviterAdmin := inviter == admin\n\n\t// check for restricted name\n\tif _, isRestricted := restricted.Get(name); isRestricted {\n\t\t// only address invite by the admin can register restricted name\n\t\tif !isInviterAdmin {\n\t\t\tpanic(\"restricted name: \" + name)\n\t\t}\n\n\t\trestricted.Remove(name)\n\t}\n\n\t// assert name is valid.\n\t// admin inviter can bypass name restriction\n\tif !isInviterAdmin \u0026\u0026 !reName.MatchString(name) {\n\t\tpanic(\"invalid name: \" + name + \" (must be at least 6 characters, lowercase alphanumeric with underscore)\")\n\t}\n\n\t// remainder of fees go toward invites.\n\tinvites := int(0)\n\tif len(sentCoins) == 1 {\n\t\tif sentCoins[0].Denom == \"ugnot\" \u0026\u0026 sentCoins[0].Amount \u003e= minFee {\n\t\t\tinvites = int(sentCoins[0].Amount / minFee)\n\t\t\tif inviter == \"\" \u0026\u0026 invites \u003e 0 {\n\t\t\t\tinvites -= 1\n\t\t\t}\n\t\t}\n\t}\n\t// register.\n\tcounter++\n\tuser := \u0026users.User{\n\t\tAddress: caller,\n\t\tName: name,\n\t\tProfile: profile,\n\t\tNumber: counter,\n\t\tInvites: invites,\n\t\tInviter: inviter,\n\t}\n\tname2User.Set(name, user)\n\taddr2User.Set(caller.String(), user)\n}\n\nfunc Invite(invitee string) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// get caller/inviter.\n\tcaller := std.GetCallerAt(2)\n\tif caller != std.GetOrigCaller() {\n\t\tpanic(\"should not happen\") // because std.AssertOrigCall().\n\t}\n\tlines := strings.Split(invitee, \"\\n\")\n\tif caller == admin {\n\t\t// nothing to do, all good\n\t} else {\n\t\t// ensure has invites.\n\t\tuserI, ok := addr2User.Get(caller.String())\n\t\tif !ok {\n\t\t\tpanic(\"user unknown\")\n\t\t}\n\t\tuser := userI.(*users.User)\n\t\tif user.Invites \u003c= 0 {\n\t\t\tpanic(\"user has no invite tokens\")\n\t\t}\n\t\tuser.Invites -= len(lines)\n\t\tif user.Invites \u003c 0 {\n\t\t\tpanic(\"user has insufficient invite tokens\")\n\t\t}\n\t}\n\t// for each line...\n\tfor _, line := range lines {\n\t\tif line == \"\" {\n\t\t\tcontinue // file bodies have a trailing newline.\n\t\t} else if strings.HasPrefix(line, `//`) {\n\t\t\tcontinue // comment\n\t\t}\n\t\t// record invite.\n\t\tinvitekey := string(caller) + \":\" + string(line)\n\t\tinvites.Set(invitekey, true)\n\t}\n}\n\nfunc GrantInvites(invites string) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// assert admin.\n\tcaller := std.GetCallerAt(2)\n\tif caller != std.GetOrigCaller() {\n\t\tpanic(\"should not happen\") // because std.AssertOrigCall().\n\t}\n\tif caller != admin {\n\t\tpanic(\"unauthorized\")\n\t}\n\t// for each line...\n\tlines := strings.Split(invites, \"\\n\")\n\tfor _, line := range lines {\n\t\tif line == \"\" {\n\t\t\tcontinue // file bodies have a trailing newline.\n\t\t} else if strings.HasPrefix(line, `//`) {\n\t\t\tcontinue // comment\n\t\t}\n\t\t// parse name and invites.\n\t\tvar name string\n\t\tvar invites int\n\t\tparts := strings.Split(line, \":\")\n\t\tif len(parts) == 1 { // short for :1.\n\t\t\tname = parts[0]\n\t\t\tinvites = 1\n\t\t} else if len(parts) == 2 {\n\t\t\tname = parts[0]\n\t\t\tinvites_, err := strconv.Atoi(parts[1])\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t\tinvites = int(invites_)\n\t\t} else {\n\t\t\tpanic(\"should not happen\")\n\t\t}\n\t\t// give invites.\n\t\tuserI, ok := name2User.Get(name)\n\t\tif !ok {\n\t\t\t// maybe address.\n\t\t\tuserI, ok = addr2User.Get(name)\n\t\t\tif !ok {\n\t\t\t\tpanic(\"invalid user \" + name)\n\t\t\t}\n\t\t}\n\t\tuser := userI.(*users.User)\n\t\tuser.Invites += invites\n\t}\n}\n\n// Any leftover fees go toward invitations.\nfunc SetMinFee(newMinFee int64) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// assert admin caller.\n\tcaller := std.GetCallerAt(2)\n\tif caller != admin {\n\t\tpanic(\"unauthorized\")\n\t}\n\t// update global variables.\n\tminFee = newMinFee\n}\n\n// This helps prevent fat finger accidents.\nfunc SetMaxFeeMultiple(newMaxFeeMult int64) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// assert admin caller.\n\tcaller := std.GetCallerAt(2)\n\tif caller != admin {\n\t\tpanic(\"unauthorized\")\n\t}\n\t// update global variables.\n\tmaxFeeMult = newMaxFeeMult\n}\n\n//----------------------------------------\n// Exposed public functions\n\nfunc GetUserByName(name string) *users.User {\n\tuserI, ok := name2User.Get(name)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn userI.(*users.User)\n}\n\nfunc GetUserByAddress(addr std.Address) *users.User {\n\tuserI, ok := addr2User.Get(addr.String())\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn userI.(*users.User)\n}\n\n// unlike GetUserByName, input must be \"@\" prefixed for names.\nfunc GetUserByAddressOrName(input users.AddressOrName) *users.User {\n\tname, isName := input.GetName()\n\tif isName {\n\t\treturn GetUserByName(name)\n\t}\n\treturn GetUserByAddress(std.Address(input))\n}\n\n// Get a list of user names starting from the given prefix. Limit the\n// number of results to maxResults. (This can be used for a name search tool.)\nfunc ListUsersByPrefix(prefix string, maxResults int) []string {\n\treturn avlhelpers.ListByteStringKeysByPrefix(name2User, prefix, maxResults)\n}\n\nfunc Resolve(input users.AddressOrName) std.Address {\n\tname, isName := input.GetName()\n\tif !isName {\n\t\treturn std.Address(input) // TODO check validity\n\t}\n\n\tuser := GetUserByName(name)\n\treturn user.Address\n}\n\n// Add restricted name to the list\nfunc AdminAddRestrictedName(name string) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// get caller\n\tcaller := std.GetOrigCaller()\n\t// assert admin\n\tif caller != admin {\n\t\tpanic(\"unauthorized\")\n\t}\n\n\tif user := GetUserByName(name); user != nil {\n\t\tpanic(\"already registered name\")\n\t}\n\n\t// register restricted name\n\n\trestricted.Set(name, true)\n}\n\n//----------------------------------------\n// Constants\n\n// NOTE: name length must be clearly distinguishable from a bech32 address.\nvar reName = regexp.MustCompile(`^[a-z]+[_a-z0-9]{5,16}$`)\n\n//----------------------------------------\n// Render main page\n\nfunc Render(fullPath string) string {\n\tpath, _ := splitPathAndQuery(fullPath)\n\tif path == \"\" {\n\t\treturn renderHome(fullPath)\n\t} else if len(path) \u003e= 38 { // 39? 40?\n\t\tif path[:2] != \"g1\" {\n\t\t\treturn \"invalid address \" + path\n\t\t}\n\t\tuser := GetUserByAddress(std.Address(path))\n\t\tif user == nil {\n\t\t\t// TODO: display basic information about account.\n\t\t\treturn \"unknown address \" + path\n\t\t}\n\t\treturn user.Render()\n\t} else {\n\t\tuser := GetUserByName(path)\n\t\tif user == nil {\n\t\t\treturn \"unknown username \" + path\n\t\t}\n\t\treturn user.Render()\n\t}\n}\n\nfunc renderHome(path string) string {\n\tdoc := \"\"\n\n\tpage := pager.NewPager(\u0026name2User, 50).MustGetPageByPath(path)\n\n\tfor _, item := range page.Items {\n\t\tuser := item.Value.(*users.User)\n\t\tdoc += \" * [\" + user.Name + \"](/r/demo/users:\" + user.Name + \")\\n\"\n\t}\n\tdoc += \"\\n\"\n\tdoc += page.Selector()\n\treturn doc\n}\n\nfunc splitPathAndQuery(fullPath string) (string, string) {\n\tparts := strings.SplitN(fullPath, \"?\", 2)\n\tpath := parts[0]\n\tqueryString := \"\"\n\tif len(parts) \u003e 1 {\n\t\tqueryString = \"?\" + parts[1]\n\t}\n\treturn path, queryString\n}\n"},{"name":"users_test.gno","body":"package users\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestPreRegisteredTest1(t *testing.T) {\n\tnames := ListUsersByPrefix(\"test1\", 1)\n\tuassert.Equal(t, len(names), 1)\n\tuassert.Equal(t, names[0], \"test1\")\n}\n"},{"name":"z_0_b_filetest.gno","body":"package main\n\n// SEND: 19900000ugnot\n\nimport (\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Error:\n// payment must not be less than 20000000\n"},{"name":"z_0_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tstd.TestSetOrigSend(std.Coins{std.NewCoin(\"dontcare\", 1)}, nil)\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Error:\n// incompatible coin denominations: dontcare, ugnot\n"},{"name":"z_10_filetest.gno","body":"// PKGPATH: gno.land/r/demo/users_test\npackage users_test\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc init() {\n\tcaller := std.GetOrigCaller() // main\n\ttest2 := testutils.TestAddress(\"test2\")\n\t// as admin, invite gnouser and test2\n\tstd.TestSetOrigCaller(admin)\n\tusers.Invite(caller.String() + \"\\n\" + test2.String())\n\t// register as caller\n\tstd.TestSetOrigCaller(caller)\n\tusers.Register(admin, \"gnouser\", \"my profile\")\n}\n\nfunc main() {\n\t// register as test2\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOrigCaller(test2)\n\tusers.Register(admin, \"test222\", \"my profile 2\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_11_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.GetOrigCaller() // main\n\tstd.TestSetOrigCaller(admin)\n\tusers.AdminAddRestrictedName(\"superrestricted\")\n\n\t// test restricted name\n\tstd.TestSetOrigCaller(caller)\n\tusers.Register(\"\", \"superrestricted\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Error:\n// restricted name: superrestricted\n"},{"name":"z_11b_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.GetOrigCaller() // main\n\tstd.TestSetOrigCaller(admin)\n\t// add restricted name\n\tusers.AdminAddRestrictedName(\"superrestricted\")\n\t// grant invite to caller\n\tusers.Invite(caller.String())\n\t// set back caller\n\tstd.TestSetOrigCaller(caller)\n\t// register restricted name with admin invite\n\tusers.Register(admin, \"superrestricted\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_12_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"alicia\", \"my profile\")\n\n\t{\n\t\t// Normal usage\n\t\tnames := users.ListUsersByPrefix(\"a\", 1)\n\t\tprintln(\"# names: \" + strconv.Itoa(len(names)))\n\t\tprintln(\"name: \" + names[0])\n\t}\n\n\t{\n\t\t// Empty prefix: match all\n\t\tnames := users.ListUsersByPrefix(\"\", 1)\n\t\tprintln(\"# names: \" + strconv.Itoa(len(names)))\n\t\tprintln(\"name: \" + names[0])\n\t}\n\n\t{\n\t\t// The prefix is before \"alicia\"\n\t\tnames := users.ListUsersByPrefix(\"alich\", 1)\n\t\tprintln(\"# names: \" + strconv.Itoa(len(names)))\n\t}\n\n\t{\n\t\t// The prefix is after the last name\n\t\tnames := users.ListUsersByPrefix(\"y\", 10)\n\t\tprintln(\"# names: \" + strconv.Itoa(len(names)))\n\t}\n\n\t// More tests are in p/demo/avlhelpers\n}\n\n// Output:\n// # names: 1\n// name: alicia\n// # names: 1\n// name: alicia\n// # names: 0\n// # names: 0\n"},{"name":"z_1_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_2_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.GetOrigCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test1)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_3_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.GetOrigCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test1)\n\tstd.TestSetOrigSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_4_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.GetOrigCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\ttest2 := testutils.TestAddress(\"test2\")\n\tusers.Invite(test1.String())\n\t// switch to test2 (not test1)\n\tstd.TestSetOrigCaller(test2)\n\tstd.TestSetOrigSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\tprintln(\"done\")\n}\n\n// Error:\n// invalid invitation\n"},{"name":"z_5_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.GetOrigCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test1)\n\tstd.TestSetOrigSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\tprintln(users.Render(\"\"))\n\tprintln(\"========================================\")\n\tprintln(users.Render(\"?page=2\"))\n\tprintln(\"========================================\")\n\tprintln(users.Render(\"gnouser\"))\n\tprintln(\"========================================\")\n\tprintln(users.Render(\"satoshi\"))\n\tprintln(\"========================================\")\n\tprintln(users.Render(\"badname\"))\n}\n\n// Output:\n// * [archives](/r/demo/users:archives)\n// * [demo](/r/demo/users:demo)\n// * [gno](/r/demo/users:gno)\n// * [gnoland](/r/demo/users:gnoland)\n// * [gnolang](/r/demo/users:gnolang)\n// * [gnouser](/r/demo/users:gnouser)\n// * [gov](/r/demo/users:gov)\n// * [nt](/r/demo/users:nt)\n// * [satoshi](/r/demo/users:satoshi)\n// * [sys](/r/demo/users:sys)\n// * [test1](/r/demo/users:test1)\n// * [x](/r/demo/users:x)\n//\n//\n// ========================================\n//\n//\n// ========================================\n// ## user gnouser\n//\n// * address = g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n// * 9 invites\n//\n// my profile\n//\n// ========================================\n// ## user satoshi\n//\n// * address = g1w3jhxap3ta047h6lta047h6lta047h6l4mfnm7\n// * 0 invites\n// * invited by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// my other profile\n//\n// ========================================\n// unknown username badname\n"},{"name":"z_6_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.GetOrigCaller()\n\t// as admin, grant invites to unregistered user.\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\tprintln(\"done\")\n}\n\n// Error:\n// invalid user g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n"},{"name":"z_7_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.GetOrigCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test1)\n\tstd.TestSetOrigSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\t// as admin, grant invites to gnouser(again) and satoshi.\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\\n\" + test1.String() + \":1\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_7b_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.GetOrigCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\\n\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test1)\n\tstd.TestSetOrigSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\t// as admin, grant invites to gnouser(again) and satoshi.\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\\n\" + test1.String() + \":1\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_8_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.GetOrigCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test1)\n\tstd.TestSetOrigSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\t// as admin, grant invites to gnouser(again) and nonexistent user.\n\tstd.TestSetOrigCaller(admin)\n\ttest2 := testutils.TestAddress(\"test2\")\n\tusers.GrantInvites(caller.String() + \":1\\n\" + test2.String() + \":1\")\n\tprintln(\"done\")\n}\n\n// Error:\n// invalid user g1w3jhxapjta047h6lta047h6lta047h6laqcyu4\n"},{"name":"z_9_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.GetOrigCaller() // main\n\ttest2 := testutils.TestAddress(\"test2\")\n\t// as admin, invite gnouser and test2\n\tstd.TestSetOrigCaller(admin)\n\tusers.Invite(caller.String() + \"\\n\" + test2.String())\n\t// register as caller\n\tstd.TestSetOrigCaller(caller)\n\tusers.Register(admin, \"gnouser\", \"my profile\")\n\t// register as test2\n\tstd.TestSetOrigCaller(test2)\n\tusers.Register(admin, \"test222\", \"my profile 2\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\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":"boards","path":"gno.land/r/demo/boards","files":[{"name":"README.md","body":"This is a demo of Gno smart contract programming. This document was\nconstructed by Gno onto a smart contract hosted on the data Realm\nname [\"gno.land/r/demo/boards\"](https://gno.land/r/demo/boards/)\n([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)).\n\n\n\n## Build `gnokey`, create your account, and interact with Gno.\n\nNOTE: Where you see `-remote localhost:26657` here, that flag can be replaced\nwith `-remote gno.land:26657` if you have $GNOT on the testnet.\n(To use the testnet, also replace `-chainid dev` with `-chainid portal-loop` .)\n\n### Build `gnokey` (and other tools).\n\n```bash\ngit clone git@github.com:gnolang/gno.git\ncd gno/gno.land\nmake build\n```\n\n### Generate a seed/mnemonic code.\n\n```bash\n./build/gnokey generate\n```\n\nNOTE: You can generate 24 words with any good bip39 generator.\n\n### Create a new account using your mnemonic.\n\n```bash\n./build/gnokey add -recover KEYNAME\n```\n\nNOTE: `KEYNAME` is your key identifier, and should be changed.\n\n### Verify that you can see your account locally.\n\n```bash\n./build/gnokey list\n```\n\nTake note of your `addr` which looks something like `g17sphqax3kasjptdkmuqvn740u8dhtx4kxl6ljf` .\nYou will use this as your `ACCOUNT_ADDR`.\n\n## Interact with the blockchain.\n\n### Add $GNOT for your account.\n\nBefore starting the `gnoland` node for the first time, your new account can be given $GNOT in the node genesis.\nEdit the file `gno.land/genesis/genesis_balances.txt` and add the following line (simlar to the others), using\nyour `ACCOUNT_ADDR` and `KEYNAME`\n\n`ACCOUNT_ADDR=10000000000ugnot # @KEYNAME`\n\n### Alternative: Run a faucet to add $GNOT.\n\nInstead of editing `gno.land/genesis/genesis_balances.txt`, a more general solution (with more steps)\nis to run a local \"faucet\" and use the web browser to add $GNOT. (This can be done at any time.)\nSee this page: https://github.com/gnolang/gno/blob/master/contribs/gnofaucet/README.md\n\n\n### Start the `gnoland` node.\n\n```bash\n./build/gnoland start\n```\n\nNOTE: The node already has the \"boards\" realm.\n\nLeave this running in the terminal. In a new terminal, cd to the same folder `gno/gno.land` .\n\n### Get your current balance, account number, and sequence number.\n\n```bash\n./build/gnokey query auth/accounts/ACCOUNT_ADDR -remote localhost:26657\n```\n\n### Register a board username with a smart contract call.\n\nThe `USERNAME` for posting can different than your `KEYNAME`. It is internally linked to your `ACCOUNT_ADDR`. It must be at least 6 characters, lowercase alphanumeric with underscore.\n\n```bash\n./build/gnokey maketx call -pkgpath \"gno.land/r/demo/users\" -func \"Register\" -args \"\" -args \"USERNAME\" -args \"Profile description\" -gas-fee \"10000000ugnot\" -gas-wanted \"2000000\" -send \"200000000ugnot\" -broadcast -chainid dev -remote 127.0.0.1:26657 KEYNAME\n```\n\nInteractive documentation: https://gno.land/r/demo/users$help\u0026func=Register\n\n### Create a board with a smart contract call.\n\n```bash\n./build/gnokey maketx call -pkgpath \"gno.land/r/demo/boards\" -func \"CreateBoard\" -args \"BOARDNAME\" -gas-fee \"1000000ugnot\" -gas-wanted \"10000000\" -broadcast -chainid dev -remote localhost:26657 KEYNAME\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateBoard\n\nNext, query for the permanent board ID by querying (you need this to create a new post):\n\n```bash\n./build/gnokey query \"vm/qeval\" -data 'gno.land/r/demo/boards.GetBoardIDFromName(\"BOARDNAME\")' -remote localhost:26657\n```\n\n### Create a post of a board with a smart contract call.\n\nNOTE: If a board was created successfully, your SEQUENCE_NUMBER would have increased.\n\n```bash\n./build/gnokey maketx call -pkgpath \"gno.land/r/demo/boards\" -func \"CreateThread\" -args BOARD_ID -args \"Hello gno.land\" -args \"Text of the post\" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid dev -remote localhost:26657 KEYNAME\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateThread\n\n### Create a comment to a post.\n\n```bash\n./build/gnokey maketx call -pkgpath \"gno.land/r/demo/boards\" -func \"CreateReply\" -args BOARD_ID -args \"1\" -args \"1\" -args \"Nice to meet you too.\" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid dev -remote localhost:26657 KEYNAME\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateReply\n\n```bash\n./build/gnokey query \"vm/qrender\" -data \"gno.land/r/demo/boards:BOARDNAME/1\" -remote localhost:26657\n```\n\n### Render page with optional path expression.\n\nThe contents of `https://gno.land/r/demo/boards:` and `https://gno.land/r/demo/boards:gnolang` are rendered by calling\nthe `Render(path string)` function like so:\n\n```bash\n./build/gnokey query \"vm/qrender\" -data \"gno.land/r/demo/boards:gnolang\"\n```\n## View the board in the browser.\n\n### Start the web server.\n\n```bash\n./build/gnoweb\n```\n\nThis should print something like `Running on http://127.0.0.1:8888` . Leave this running in the terminal.\n\n### View in the browser\n\nIn your browser, navigate to the printed address http://127.0.0.1:8888 .\nTo see you post, click on the package `/r/demo/boards` .\n"},{"name":"board.gno","body":"package boards\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/moul/txlink\"\n)\n\n//----------------------------------------\n// Board\n\ntype BoardID uint64\n\nfunc (bid BoardID) String() string {\n\treturn strconv.Itoa(int(bid))\n}\n\ntype Board struct {\n\tid BoardID // only set for public boards.\n\turl string\n\tname string\n\tcreator std.Address\n\tthreads avl.Tree // Post.id -\u003e *Post\n\tpostsCtr uint64 // increments Post.id\n\tcreatedAt time.Time\n\tdeleted avl.Tree // TODO reserved for fast-delete.\n}\n\nfunc newBoard(id BoardID, url string, name string, creator std.Address) *Board {\n\tif !reName.MatchString(name) {\n\t\tpanic(\"invalid name: \" + name)\n\t}\n\texists := gBoardsByName.Has(name)\n\tif exists {\n\t\tpanic(\"board already exists\")\n\t}\n\treturn \u0026Board{\n\t\tid: id,\n\t\turl: url,\n\t\tname: name,\n\t\tcreator: creator,\n\t\tthreads: avl.Tree{},\n\t\tcreatedAt: time.Now(),\n\t\tdeleted: avl.Tree{},\n\t}\n}\n\n/* TODO support this once we figure out how to ensure URL correctness.\n// A private board is not tracked by gBoards*,\n// but must be persisted by the caller's realm.\n// Private boards have 0 id and does not ping\n// back the remote board on reposts.\nfunc NewPrivateBoard(url string, name string, creator std.Address) *Board {\n\treturn newBoard(0, url, name, creator)\n}\n*/\n\nfunc (board *Board) IsPrivate() bool {\n\treturn board.id == 0\n}\n\nfunc (board *Board) GetThread(pid PostID) *Post {\n\tpidkey := postIDKey(pid)\n\tpostI, exists := board.threads.Get(pidkey)\n\tif !exists {\n\t\treturn nil\n\t}\n\treturn postI.(*Post)\n}\n\nfunc (board *Board) AddThread(creator std.Address, title string, body string) *Post {\n\tpid := board.incGetPostID()\n\tpidkey := postIDKey(pid)\n\tthread := newPost(board, pid, creator, title, body, pid, 0, 0)\n\tboard.threads.Set(pidkey, thread)\n\treturn thread\n}\n\n// NOTE: this can be potentially very expensive for threads with many replies.\n// TODO: implement optional fast-delete where thread is simply moved.\nfunc (board *Board) DeleteThread(pid PostID) {\n\tpidkey := postIDKey(pid)\n\t_, removed := board.threads.Remove(pidkey)\n\tif !removed {\n\t\tpanic(\"thread does not exist with id \" + pid.String())\n\t}\n}\n\nfunc (board *Board) HasPermission(addr std.Address, perm Permission) bool {\n\tif board.creator == addr {\n\t\tswitch perm {\n\t\tcase EditPermission:\n\t\t\treturn true\n\t\tcase DeletePermission:\n\t\t\treturn true\n\t\tdefault:\n\t\t\treturn false\n\t\t}\n\t}\n\treturn false\n}\n\n// Renders the board for display suitable as plaintext in\n// console. This is suitable for demonstration or tests,\n// but not for prod.\nfunc (board *Board) RenderBoard() string {\n\tstr := \"\"\n\tstr += \"\\\\[[post](\" + board.GetPostFormURL() + \")]\\n\\n\"\n\tif board.threads.Size() \u003e 0 {\n\t\tboard.threads.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tif str != \"\" {\n\t\t\t\tstr += \"----------------------------------------\\n\"\n\t\t\t}\n\t\t\tstr += value.(*Post).RenderSummary() + \"\\n\"\n\t\t\treturn false\n\t\t})\n\t}\n\treturn str\n}\n\nfunc (board *Board) incGetPostID() PostID {\n\tboard.postsCtr++\n\treturn PostID(board.postsCtr)\n}\n\nfunc (board *Board) GetURLFromThreadAndReplyID(threadID, replyID PostID) string {\n\tif replyID == 0 {\n\t\treturn board.url + \"/\" + threadID.String()\n\t} else {\n\t\treturn board.url + \"/\" + threadID.String() + \"/\" + replyID.String()\n\t}\n}\n\nfunc (board *Board) GetPostFormURL() string {\n\treturn txlink.URL(\"CreateThread\", \"bid\", board.id.String())\n}\n"},{"name":"boards.gno","body":"package boards\n\nimport (\n\t\"regexp\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n//----------------------------------------\n// Realm (package) state\n\nvar (\n\tgBoards avl.Tree // id -\u003e *Board\n\tgBoardsCtr int // increments Board.id\n\tgBoardsByName avl.Tree // name -\u003e *Board\n\tgDefaultAnonFee = 100000000 // minimum fee required if anonymous\n)\n\n//----------------------------------------\n// Constants\n\nvar reName = regexp.MustCompile(`^[a-z]+[_a-z0-9]{2,29}$`)\n"},{"name":"misc.gno","body":"package boards\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/r/demo/users\"\n)\n\n//----------------------------------------\n// private utility methods\n// XXX ensure these cannot be called from public.\n\nfunc getBoard(bid BoardID) *Board {\n\tbidkey := boardIDKey(bid)\n\tboard_, exists := gBoards.Get(bidkey)\n\tif !exists {\n\t\treturn nil\n\t}\n\tboard := board_.(*Board)\n\treturn board\n}\n\nfunc incGetBoardID() BoardID {\n\tgBoardsCtr++\n\treturn BoardID(gBoardsCtr)\n}\n\nfunc padLeft(str string, length int) string {\n\tif len(str) \u003e= length {\n\t\treturn str\n\t} else {\n\t\treturn strings.Repeat(\" \", length-len(str)) + str\n\t}\n}\n\nfunc padZero(u64 uint64, length int) string {\n\tstr := strconv.Itoa(int(u64))\n\tif len(str) \u003e= length {\n\t\treturn str\n\t} else {\n\t\treturn strings.Repeat(\"0\", length-len(str)) + str\n\t}\n}\n\nfunc boardIDKey(bid BoardID) string {\n\treturn padZero(uint64(bid), 10)\n}\n\nfunc postIDKey(pid PostID) string {\n\treturn padZero(uint64(pid), 10)\n}\n\nfunc indentBody(indent string, body string) string {\n\tlines := strings.Split(body, \"\\n\")\n\tres := \"\"\n\tfor i, line := range lines {\n\t\tif i \u003e 0 {\n\t\t\tres += \"\\n\"\n\t\t}\n\t\tres += indent + line\n\t}\n\treturn res\n}\n\n// NOTE: length must be greater than 3.\nfunc summaryOf(str string, length int) string {\n\tlines := strings.SplitN(str, \"\\n\", 2)\n\tline := lines[0]\n\tif len(line) \u003e length {\n\t\tline = line[:(length-3)] + \"...\"\n\t} else if len(lines) \u003e 1 {\n\t\t// len(line) \u003c= 80\n\t\tline = line + \"...\"\n\t}\n\treturn line\n}\n\nfunc displayAddressMD(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user == nil {\n\t\treturn \"[\" + addr.String() + \"](/r/demo/users:\" + addr.String() + \")\"\n\t} else {\n\t\treturn \"[@\" + user.Name + \"](/r/demo/users:\" + user.Name + \")\"\n\t}\n}\n\nfunc usernameOf(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user == nil {\n\t\treturn \"\"\n\t}\n\treturn user.Name\n}\n"},{"name":"post.gno","body":"package boards\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/moul/txlink\"\n)\n\n//----------------------------------------\n// Post\n\n// NOTE: a PostID is relative to the board.\ntype PostID uint64\n\nfunc (pid PostID) String() string {\n\treturn strconv.Itoa(int(pid))\n}\n\n// A Post is a \"thread\" or a \"reply\" depending on context.\n// A thread is a Post of a Board that holds other replies.\ntype Post struct {\n\tboard *Board\n\tid PostID\n\tcreator std.Address\n\ttitle string // optional\n\tbody string\n\treplies avl.Tree // Post.id -\u003e *Post\n\trepliesAll avl.Tree // Post.id -\u003e *Post (all replies, for top-level posts)\n\treposts avl.Tree // Board.id -\u003e Post.id\n\tthreadID PostID // original Post.id\n\tparentID PostID // parent Post.id (if reply or repost)\n\trepostBoard BoardID // original Board.id (if repost)\n\tcreatedAt time.Time\n\tupdatedAt time.Time\n}\n\nfunc newPost(board *Board, id PostID, creator std.Address, title, body string, threadID, parentID PostID, repostBoard BoardID) *Post {\n\treturn \u0026Post{\n\t\tboard: board,\n\t\tid: id,\n\t\tcreator: creator,\n\t\ttitle: title,\n\t\tbody: body,\n\t\treplies: avl.Tree{},\n\t\trepliesAll: avl.Tree{},\n\t\treposts: avl.Tree{},\n\t\tthreadID: threadID,\n\t\tparentID: parentID,\n\t\trepostBoard: repostBoard,\n\t\tcreatedAt: time.Now(),\n\t}\n}\n\nfunc (post *Post) IsThread() bool {\n\treturn post.parentID == 0\n}\n\nfunc (post *Post) GetPostID() PostID {\n\treturn post.id\n}\n\nfunc (post *Post) AddReply(creator std.Address, body string) *Post {\n\tboard := post.board\n\tpid := board.incGetPostID()\n\tpidkey := postIDKey(pid)\n\treply := newPost(board, pid, creator, \"\", body, post.threadID, post.id, 0)\n\tpost.replies.Set(pidkey, reply)\n\tif post.threadID == post.id {\n\t\tpost.repliesAll.Set(pidkey, reply)\n\t} else {\n\t\tthread := board.GetThread(post.threadID)\n\t\tthread.repliesAll.Set(pidkey, reply)\n\t}\n\treturn reply\n}\n\nfunc (post *Post) Update(title string, body string) {\n\tpost.title = title\n\tpost.body = body\n\tpost.updatedAt = time.Now()\n}\n\nfunc (thread *Post) GetReply(pid PostID) *Post {\n\tpidkey := postIDKey(pid)\n\treplyI, ok := thread.repliesAll.Get(pidkey)\n\tif !ok {\n\t\treturn nil\n\t} else {\n\t\treturn replyI.(*Post)\n\t}\n}\n\nfunc (post *Post) AddRepostTo(creator std.Address, title, body string, dst *Board) *Post {\n\tif !post.IsThread() {\n\t\tpanic(\"cannot repost non-thread post\")\n\t}\n\tpid := dst.incGetPostID()\n\tpidkey := postIDKey(pid)\n\trepost := newPost(dst, pid, creator, title, body, pid, post.id, post.board.id)\n\tdst.threads.Set(pidkey, repost)\n\tif !dst.IsPrivate() {\n\t\tbidkey := boardIDKey(dst.id)\n\t\tpost.reposts.Set(bidkey, pid)\n\t}\n\treturn repost\n}\n\nfunc (thread *Post) DeletePost(pid PostID) {\n\tif thread.id == pid {\n\t\tpanic(\"should not happen\")\n\t}\n\tpidkey := postIDKey(pid)\n\tpostI, removed := thread.repliesAll.Remove(pidkey)\n\tif !removed {\n\t\tpanic(\"post not found in thread\")\n\t}\n\tpost := postI.(*Post)\n\tif post.parentID != thread.id {\n\t\tparent := thread.GetReply(post.parentID)\n\t\tparent.replies.Remove(pidkey)\n\t} else {\n\t\tthread.replies.Remove(pidkey)\n\t}\n}\n\nfunc (post *Post) HasPermission(addr std.Address, perm Permission) bool {\n\tif post.creator == addr {\n\t\tswitch perm {\n\t\tcase EditPermission:\n\t\t\treturn true\n\t\tcase DeletePermission:\n\t\t\treturn true\n\t\tdefault:\n\t\t\treturn false\n\t\t}\n\t}\n\t// post notes inherit permissions of the board.\n\treturn post.board.HasPermission(addr, perm)\n}\n\nfunc (post *Post) GetSummary() string {\n\treturn summaryOf(post.body, 80)\n}\n\nfunc (post *Post) GetURL() string {\n\tif post.IsThread() {\n\t\treturn post.board.GetURLFromThreadAndReplyID(\n\t\t\tpost.id, 0)\n\t} else {\n\t\treturn post.board.GetURLFromThreadAndReplyID(\n\t\t\tpost.threadID, post.id)\n\t}\n}\n\nfunc (post *Post) GetReplyFormURL() string {\n\treturn txlink.URL(\"CreateReply\",\n\t\t\"bid\", post.board.id.String(),\n\t\t\"threadid\", post.threadID.String(),\n\t\t\"postid\", post.id.String(),\n\t)\n}\n\nfunc (post *Post) GetRepostFormURL() string {\n\treturn txlink.URL(\"CreateRepost\",\n\t\t\"bid\", post.board.id.String(),\n\t\t\"postid\", post.id.String(),\n\t)\n}\n\nfunc (post *Post) GetDeleteFormURL() string {\n\treturn txlink.URL(\"DeletePost\",\n\t\t\"bid\", post.board.id.String(),\n\t\t\"threadid\", post.threadID.String(),\n\t\t\"postid\", post.id.String(),\n\t)\n}\n\nfunc (post *Post) RenderSummary() string {\n\tif post.repostBoard != 0 {\n\t\tdstBoard := getBoard(post.repostBoard)\n\t\tif dstBoard == nil {\n\t\t\tpanic(\"repostBoard does not exist\")\n\t\t}\n\t\tthread := dstBoard.GetThread(PostID(post.parentID))\n\t\tif thread == nil {\n\t\t\treturn \"reposted post does not exist\"\n\t\t}\n\t\treturn \"Repost: \" + post.GetSummary() + \"\\n\" + thread.RenderSummary()\n\t}\n\tstr := \"\"\n\tif post.title != \"\" {\n\t\tstr += \"## [\" + summaryOf(post.title, 80) + \"](\" + post.GetURL() + \")\\n\"\n\t\tstr += \"\\n\"\n\t}\n\tstr += post.GetSummary() + \"\\n\"\n\tstr += \"\\\\- \" + displayAddressMD(post.creator) + \",\"\n\tstr += \" [\" + post.createdAt.Format(\"2006-01-02 3:04pm MST\") + \"](\" + post.GetURL() + \")\"\n\tstr += \" \\\\[[x](\" + post.GetDeleteFormURL() + \")]\"\n\tstr += \" (\" + strconv.Itoa(post.replies.Size()) + \" replies)\"\n\tstr += \" (\" + strconv.Itoa(post.reposts.Size()) + \" reposts)\" + \"\\n\"\n\treturn str\n}\n\nfunc (post *Post) RenderPost(indent string, levels int) string {\n\tif post == nil {\n\t\treturn \"nil post\"\n\t}\n\tstr := \"\"\n\tif post.title != \"\" {\n\t\tstr += indent + \"# \" + post.title + \"\\n\"\n\t\tstr += indent + \"\\n\"\n\t}\n\tstr += indentBody(indent, post.body) + \"\\n\" // TODO: indent body lines.\n\tstr += indent + \"\\\\- \" + displayAddressMD(post.creator) + \", \"\n\tstr += \"[\" + post.createdAt.Format(\"2006-01-02 3:04pm (MST)\") + \"](\" + post.GetURL() + \")\"\n\tstr += \" \\\\[[reply](\" + post.GetReplyFormURL() + \")]\"\n\tif post.IsThread() {\n\t\tstr += \" \\\\[[repost](\" + post.GetRepostFormURL() + \")]\"\n\t}\n\tstr += \" \\\\[[x](\" + post.GetDeleteFormURL() + \")]\\n\"\n\tif levels \u003e 0 {\n\t\tif post.replies.Size() \u003e 0 {\n\t\t\tpost.replies.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\t\tstr += indent + \"\\n\"\n\t\t\t\tstr += value.(*Post).RenderPost(indent+\"\u003e \", levels-1)\n\t\t\t\treturn false\n\t\t\t})\n\t\t}\n\t} else {\n\t\tif post.replies.Size() \u003e 0 {\n\t\t\tstr += indent + \"\\n\"\n\t\t\tstr += indent + \"_[see all \" + strconv.Itoa(post.replies.Size()) + \" replies](\" + post.GetURL() + \")_\\n\"\n\t\t}\n\t}\n\treturn str\n}\n\n// render reply and link to context thread\nfunc (post *Post) RenderInner() string {\n\tif post.IsThread() {\n\t\tpanic(\"unexpected thread\")\n\t}\n\tthreadID := post.threadID\n\t// replyID := post.id\n\tparentID := post.parentID\n\tstr := \"\"\n\tstr += \"_[see thread](\" + post.board.GetURLFromThreadAndReplyID(\n\t\tthreadID, 0) + \")_\\n\\n\"\n\tthread := post.board.GetThread(post.threadID)\n\tvar parent *Post\n\tif thread.id == parentID {\n\t\tparent = thread\n\t} else {\n\t\tparent = thread.GetReply(parentID)\n\t}\n\tstr += parent.RenderPost(\"\", 0)\n\tstr += \"\\n\"\n\tstr += post.RenderPost(\"\u003e \", 5)\n\treturn str\n}\n"},{"name":"public.gno","body":"package boards\n\nimport (\n\t\"std\"\n\t\"strconv\"\n)\n\n//----------------------------------------\n// Public facing functions\n\nfunc GetBoardIDFromName(name string) (BoardID, bool) {\n\tboardI, exists := gBoardsByName.Get(name)\n\tif !exists {\n\t\treturn 0, false\n\t}\n\treturn boardI.(*Board).id, true\n}\n\nfunc CreateBoard(name string) BoardID {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tbid := incGetBoardID()\n\tcaller := std.GetOrigCaller()\n\tif usernameOf(caller) == \"\" {\n\t\tpanic(\"unauthorized\")\n\t}\n\turl := \"/r/demo/boards:\" + name\n\tboard := newBoard(bid, url, name, caller)\n\tbidkey := boardIDKey(bid)\n\tgBoards.Set(bidkey, board)\n\tgBoardsByName.Set(name, board)\n\treturn board.id\n}\n\nfunc checkAnonFee() bool {\n\tsent := std.GetOrigSend()\n\tanonFeeCoin := std.NewCoin(\"ugnot\", int64(gDefaultAnonFee))\n\tif len(sent) == 1 \u0026\u0026 sent[0].IsGTE(anonFeeCoin) {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc CreateThread(bid BoardID, title string, body string) PostID {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.GetOrigCaller()\n\tif usernameOf(caller) == \"\" {\n\t\tif !checkAnonFee() {\n\t\t\tpanic(\"please register, otherwise minimum fee \" + strconv.Itoa(gDefaultAnonFee) + \" is required if anonymous\")\n\t\t}\n\t}\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"board not exist\")\n\t}\n\tthread := board.AddThread(caller, title, body)\n\treturn thread.id\n}\n\nfunc CreateReply(bid BoardID, threadid, postid PostID, body string) PostID {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.GetOrigCaller()\n\tif usernameOf(caller) == \"\" {\n\t\tif !checkAnonFee() {\n\t\t\tpanic(\"please register, otherwise minimum fee \" + strconv.Itoa(gDefaultAnonFee) + \" is required if anonymous\")\n\t\t}\n\t}\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"board not exist\")\n\t}\n\tthread := board.GetThread(threadid)\n\tif thread == nil {\n\t\tpanic(\"thread not exist\")\n\t}\n\tif postid == threadid {\n\t\treply := thread.AddReply(caller, body)\n\t\treturn reply.id\n\t} else {\n\t\tpost := thread.GetReply(postid)\n\t\treply := post.AddReply(caller, body)\n\t\treturn reply.id\n\t}\n}\n\n// If dstBoard is private, does not ping back.\n// If board specified by bid is private, panics.\nfunc CreateRepost(bid BoardID, postid PostID, title string, body string, dstBoardID BoardID) PostID {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.GetOrigCaller()\n\tif usernameOf(caller) == \"\" {\n\t\t// TODO: allow with gDefaultAnonFee payment.\n\t\tif !checkAnonFee() {\n\t\t\tpanic(\"please register, otherwise minimum fee \" + strconv.Itoa(gDefaultAnonFee) + \" is required if anonymous\")\n\t\t}\n\t}\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"src board not exist\")\n\t}\n\tif board.IsPrivate() {\n\t\tpanic(\"cannot repost from a private board\")\n\t}\n\tdst := getBoard(dstBoardID)\n\tif dst == nil {\n\t\tpanic(\"dst board not exist\")\n\t}\n\tthread := board.GetThread(postid)\n\tif thread == nil {\n\t\tpanic(\"thread not exist\")\n\t}\n\trepost := thread.AddRepostTo(caller, title, body, dst)\n\treturn repost.id\n}\n\nfunc DeletePost(bid BoardID, threadid, postid PostID, reason string) {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.GetOrigCaller()\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"board not exist\")\n\t}\n\tthread := board.GetThread(threadid)\n\tif thread == nil {\n\t\tpanic(\"thread not exist\")\n\t}\n\tif postid == threadid {\n\t\t// delete thread\n\t\tif !thread.HasPermission(caller, DeletePermission) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t\tboard.DeleteThread(threadid)\n\t} else {\n\t\t// delete thread's post\n\t\tpost := thread.GetReply(postid)\n\t\tif post == nil {\n\t\t\tpanic(\"post not exist\")\n\t\t}\n\t\tif !post.HasPermission(caller, DeletePermission) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t\tthread.DeletePost(postid)\n\t}\n}\n\nfunc EditPost(bid BoardID, threadid, postid PostID, title, body string) {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.GetOrigCaller()\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"board not exist\")\n\t}\n\tthread := board.GetThread(threadid)\n\tif thread == nil {\n\t\tpanic(\"thread not exist\")\n\t}\n\tif postid == threadid {\n\t\t// edit thread\n\t\tif !thread.HasPermission(caller, EditPermission) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t\tthread.Update(title, body)\n\t} else {\n\t\t// edit thread's post\n\t\tpost := thread.GetReply(postid)\n\t\tif post == nil {\n\t\t\tpanic(\"post not exist\")\n\t\t}\n\t\tif !post.HasPermission(caller, EditPermission) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t\tpost.Update(title, body)\n\t}\n}\n"},{"name":"render.gno","body":"package boards\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n)\n\n//----------------------------------------\n// Render functions\n\nfunc RenderBoard(bid BoardID) string {\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\treturn \"missing board\"\n\t}\n\treturn board.RenderBoard()\n}\n\nfunc Render(path string) string {\n\tif path == \"\" {\n\t\tstr := \"These are all the boards of this realm:\\n\\n\"\n\t\tgBoards.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tboard := value.(*Board)\n\t\t\tstr += \" * [\" + board.url + \"](\" + board.url + \")\\n\"\n\t\t\treturn false\n\t\t})\n\t\treturn str\n\t}\n\tparts := strings.Split(path, \"/\")\n\tif len(parts) == 1 {\n\t\t// /r/demo/boards:BOARD_NAME\n\t\tname := parts[0]\n\t\tboardI, exists := gBoardsByName.Get(name)\n\t\tif !exists {\n\t\t\treturn \"board does not exist: \" + name\n\t\t}\n\t\treturn boardI.(*Board).RenderBoard()\n\t} else if len(parts) == 2 {\n\t\t// /r/demo/boards:BOARD_NAME/THREAD_ID\n\t\tname := parts[0]\n\t\tboardI, exists := gBoardsByName.Get(name)\n\t\tif !exists {\n\t\t\treturn \"board does not exist: \" + name\n\t\t}\n\t\tpid, err := strconv.Atoi(parts[1])\n\t\tif err != nil {\n\t\t\treturn \"invalid thread id: \" + parts[1]\n\t\t}\n\t\tboard := boardI.(*Board)\n\t\tthread := board.GetThread(PostID(pid))\n\t\tif thread == nil {\n\t\t\treturn \"thread does not exist with id: \" + parts[1]\n\t\t}\n\t\treturn thread.RenderPost(\"\", 5)\n\t} else if len(parts) == 3 {\n\t\t// /r/demo/boards:BOARD_NAME/THREAD_ID/REPLY_ID\n\t\tname := parts[0]\n\t\tboardI, exists := gBoardsByName.Get(name)\n\t\tif !exists {\n\t\t\treturn \"board does not exist: \" + name\n\t\t}\n\t\tpid, err := strconv.Atoi(parts[1])\n\t\tif err != nil {\n\t\t\treturn \"invalid thread id: \" + parts[1]\n\t\t}\n\t\tboard := boardI.(*Board)\n\t\tthread := board.GetThread(PostID(pid))\n\t\tif thread == nil {\n\t\t\treturn \"thread does not exist with id: \" + parts[1]\n\t\t}\n\t\trid, err := strconv.Atoi(parts[2])\n\t\tif err != nil {\n\t\t\treturn \"invalid reply id: \" + parts[2]\n\t\t}\n\t\treply := thread.GetReply(PostID(rid))\n\t\tif reply == nil {\n\t\t\treturn \"reply does not exist with id: \" + parts[2]\n\t\t}\n\t\treturn reply.RenderInner()\n\t} else {\n\t\treturn \"unrecognized path \" + path\n\t}\n}\n"},{"name":"role.gno","body":"package boards\n\ntype Permission string\n\nconst (\n\tDeletePermission Permission = \"role:delete\"\n\tEditPermission Permission = \"role:edit\"\n)\n"},{"name":"z_0_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\nimport (\n\t\"gno.land/r/demo/boards\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid := boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// unauthorized\n"},{"name":"z_0_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 19900000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid = boards.CreateBoard(\"test_board\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// payment must not be less than 20000000\n"},{"name":"z_0_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tboards.CreateThread(1, \"First Post (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// board not exist\n"},{"name":"z_0_d_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateReply(bid, 0, 0, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// thread not exist\n"},{"name":"z_0_e_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tboards.CreateReply(bid, 0, 0, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// board not exist\n"},{"name":"z_0_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 20000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid := boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Output:\n// \\[[post](/r/demo/boards$help\u0026func=CreateThread\u0026bid=1)]\n//\n// ----------------------------------------\n// ## [First Post (title)](/r/demo/boards:test_board/1)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/1) \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)] (0 replies) (0 reposts)\n//\n// ----------------------------------------\n// ## [Second Post (title)](/r/demo/boards:test_board/2)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/2) \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)] (1 replies) (0 reposts)\n"},{"name":"z_10_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// boardId 2 not exist\n\tboards.DeletePost(2, pid, pid, \"\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// board not exist\n"},{"name":"z_10_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// pid of 2 not exist\n\tboards.DeletePost(bid, 2, 2, \"\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// thread not exist\n"},{"name":"z_10_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"First reply of the First post\\n\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.DeletePost(bid, pid, rid, \"\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e First reply of the First post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n//\n// ----------------------------------------------------\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n"},{"name":"z_10_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.DeletePost(bid, pid, pid, \"\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// ----------------------------------------------------\n// thread does not exist with id: 1\n"},{"name":"z_11_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// board 2 not exist\n\tboards.EditPost(2, pid, pid, \"Edited: First Post in (title)\", \"Edited: Body of the first post. (body)\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// board not exist\n"},{"name":"z_11_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// thread 2 not exist\n\tboards.EditPost(bid, 2, pid, \"Edited: First Post in (title)\", \"Edited: Body of the first post. (body)\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// thread not exist\n"},{"name":"z_11_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// post 2 not exist\n\tboards.EditPost(bid, pid, 2, \"Edited: First Post in (title)\", \"Edited: Body of the first post. (body)\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// post not exist\n"},{"name":"z_11_d_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"First reply of the First post\\n\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.EditPost(bid, pid, rid, \"\", \"Edited: First reply of the First post\\n\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e First reply of the First post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n//\n// ----------------------------------------------------\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e Edited: First reply of the First post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n"},{"name":"z_11_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.EditPost(bid, pid, pid, \"Edited: First Post in (title)\", \"Edited: Body of the first post. (body)\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// ----------------------------------------------------\n// # Edited: First Post in (title)\n//\n// Edited: Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n"},{"name":"z_12_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// create a post via registered user\n\tbid1 := boards.CreateBoard(\"test_board1\")\n\tpid := boards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tbid2 := boards.CreateBoard(\"test_board2\")\n\n\t// create a repost via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOrigCaller(test2)\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\n\trid := boards.CreateRepost(bid1, pid, \"\", \"Check this out\", bid2)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board1\"))\n}\n\n// Error:\n// please register, otherwise minimum fee 100000000 is required if anonymous\n"},{"name":"z_12_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid1 := boards.CreateBoard(\"test_board1\")\n\tpid := boards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tbid2 := boards.CreateBoard(\"test_board2\")\n\n\t// create a repost to a non-existing board\n\trid := boards.CreateRepost(5, pid, \"\", \"Check this out\", bid2)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board1\"))\n}\n\n// Error:\n// src board not exist\n"},{"name":"z_12_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid1 := boards.CreateBoard(\"test_board1\")\n\tboards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tbid2 := boards.CreateBoard(\"test_board2\")\n\n\t// create a repost to a non-existing thread\n\trid := boards.CreateRepost(bid1, 5, \"\", \"Check this out\", bid2)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board1\"))\n}\n\n// Error:\n// thread not exist\n"},{"name":"z_12_d_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid1 := boards.CreateBoard(\"test_board1\")\n\tpid := boards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tboards.CreateBoard(\"test_board2\")\n\n\t// create a repost to a non-existing destination board\n\trid := boards.CreateRepost(bid1, pid, \"\", \"Check this out\", 5)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board1\"))\n}\n\n// Error:\n// dst board not exist\n"},{"name":"z_12_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid1 boards.BoardID\n\tbid2 boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid1 = boards.CreateBoard(\"test_board1\")\n\tpid = boards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tbid2 = boards.CreateBoard(\"test_board2\")\n}\n\nfunc main() {\n\trid := boards.CreateRepost(bid1, pid, \"\", \"Check this out\", bid2)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board2\"))\n}\n\n// Output:\n// 1\n// \\[[post](/r/demo/boards$help\u0026func=CreateThread\u0026bid=2)]\n//\n// ----------------------------------------\n// Repost: Check this out\n// ## [First Post (title)](/r/demo/boards:test_board1/1)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board1/1) \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)] (0 replies) (1 reposts)\n"},{"name":"z_1_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar board *boards.Board\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\t_ = boards.CreateBoard(\"test_board_1\")\n\t_ = boards.CreateBoard(\"test_board_2\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"\"))\n}\n\n// Output:\n// These are all the boards of this realm:\n//\n// * [/r/demo/boards:test_board_1](/r/demo/boards:test_board_1)\n// * [/r/demo/boards:test_board_2](/r/demo/boards:test_board_2)\n"},{"name":"z_2_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n"},{"name":"z_3_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n}\n\nfunc main() {\n\trid := boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// 3\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n"},{"name":"z_4_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid := boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n\tprintln(rid)\n}\n\nfunc main() {\n\trid2 := boards.CreateReply(bid, pid, pid, \"Second reply of the second post\")\n\tprintln(rid2)\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// 3\n// 4\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n//\n// \u003e Second reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=4)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=4)]\n\n// Realm:\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/boards\"]\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111\",\n// \"ModTime\": \"123\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"68663c8895d37d479e417c11e21badfe21345c61\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:112\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Post\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"3f34ac77289aa1d5f9a2f8b6d083138325816fb0\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {},\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"94a6665a44bac6ede7f3e3b87173e537b12f9532\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"bc8e5b4e782a0bbc4ac9689681f119beb7b34d59\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"9957eadbc91dd32f33b0d815e041a32dbdea0671\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131]={\n// \"Fields\": [\n// {\n// \"N\": \"AAAAgJSeXbo=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"65536\"\n// }\n// },\n// {\n// \"N\": \"AbSNdvQQIhE=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"1024\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Location\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"336074805fc853987abe6f7fe3ad97a6a6f3077a:2\"\n// },\n// \"Index\": \"182\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"65536\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"1024\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Location\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Board\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:84\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"N\": \"BAAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.PostID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"std.Address\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"Second reply of the second post\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"f91e355bd19240f0f3350a7fa0e6a82b72225916\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"9ee9c4117be283fc51ffcc5ecd65b75ecef5a9dd\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"eb768b0140a5fe95f9c58747f0960d647dacfd42\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.PostID\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.PostID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.BoardID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Time\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"0fd3352422af0a56a77ef2c9e88f479054e3d51f\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Time\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"bed4afa8ffdbbf775451c947fc68b27a345ce32a\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132\"\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\",\n// \"IsEscaped\": true,\n// \"ModTime\": \"0\",\n// \"RefCount\": \"2\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Post\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"c45bbd47a46681a63af973db0ec2180922e4a8ae\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\"\n// }\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120\",\n// \"ModTime\": \"134\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"dc1f011553dc53e7a846049e08cc77fa35ea6a51\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:121\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Post\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"96b86b4585c7f1075d7794180a5581f72733a7ab\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {},\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"32274e1f28fb2b97d67a1262afd362d370de7faa\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"c2cfd6aec36a462f35bf02e5bf4a127aa1bb7ac2\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"5cb875179e86d32c517322af7a323b2a5f3e6cc5\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\"\n// }\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:85]={\n// \"Fields\": [\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.BoardID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"/r/demo/boards:test_board\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"test_board\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"std.Address\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"a416a751c3a45a1e5cba11e737c51340b081e372\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:86\"\n// }\n// },\n// {\n// \"N\": \"BAAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"65536\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Time\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"36299fccbc13f2a84c4629fad4cb940f0bd4b1c6\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:87\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"af6ed0268f99b7f369329094eb6dfaea7812708b\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:88\"\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:85\",\n// \"ModTime\": \"121\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:84\",\n// \"RefCount\": \"1\"\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"9809329dc1ddc5d3556f7a8fa3c2cebcbf65560b\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106\",\n// \"ModTime\": \"121\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:105\",\n// \"RefCount\": \"1\"\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"ceae9a1c4ed28bb51062e6ccdccfad0caafd1c4f\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107\",\n// \"ModTime\": \"121\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:105\",\n// \"RefCount\": \"1\"\n// }\n// }\n// switchrealm[\"gno.land/r/demo/boards\"]\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/boards\"]\n// switchrealm[\"gno.land/r/demo/boards_test\"]\n"},{"name":"z_5_b_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// create board via registered user\n\tbid := boards.CreateBoard(\"test_board\")\n\n\t// create post via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOrigCaller(test2)\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\n\tpid := boards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// please register, otherwise minimum fee 100000000 is required if anonymous\n"},{"name":"z_5_c_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// create board via registered user\n\tbid := boards.CreateBoard(\"test_board\")\n\n\t// create post via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOrigCaller(test2)\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 101000000}}, nil)\n\n\tpid := boards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the first post\")\n\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post (title)\n//\n// Body of the first post. (body)\n// \\- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/r/demo/users:g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e Reply of the first post\n// \u003e \\- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/r/demo/users:g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n"},{"name":"z_5_d_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// create board via registered user\n\tbid := boards.CreateBoard(\"test_board\")\n\tpid := boards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\n\t// create reply via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOrigCaller(test2)\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\tboards.CreateReply(bid, pid, pid, \"Reply of the first post\")\n\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// please register, otherwise minimum fee 100000000 is required if anonymous\n"},{"name":"z_5_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid := boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\trid2 := boards.CreateReply(bid, pid, pid, \"Second reply of the second post\\n\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n//\n// \u003e Second reply of the second post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=4)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=4)]\n"},{"name":"z_6_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tboards.CreateReply(bid, pid, pid, \"Second reply of the second post\\n\")\n\tboards.CreateReply(bid, pid, rid, \"First reply of the first reply\\n\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n// \u003e\n// \u003e \u003e First reply of the first reply\n// \u003e \u003e\n// \u003e \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/5) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=5)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=5)]\n//\n// \u003e Second reply of the second post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=4)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=4)]\n"},{"name":"z_7_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc init() {\n\t// register\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\t// create board and post\n\tbid := boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Output:\n// \\[[post](/r/demo/boards$help\u0026func=CreateThread\u0026bid=1)]\n//\n// ----------------------------------------\n// ## [First Post (title)](/r/demo/boards:test_board/1)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/1) \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)] (0 replies) (0 reposts)\n"},{"name":"z_8_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tboards.CreateReply(bid, pid, pid, \"Second reply of the second post\\n\")\n\trid2 := boards.CreateReply(bid, pid, rid, \"First reply of the first reply\\n\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid)) + \"/\" + strconv.Itoa(int(rid2))))\n}\n\n// Output:\n// _[see thread](/r/demo/boards:test_board/2)_\n//\n// Reply of the second post\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n//\n// _[see all 1 replies](/r/demo/boards:test_board/2/3)_\n//\n// \u003e First reply of the first reply\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/5) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=5)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=5)]\n"},{"name":"z_9_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar dstBoard boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tdstBoard = boards.CreateBoard(\"dst_board\")\n\n\tboards.CreateRepost(0, 0, \"First Post in (title)\", \"Body of the first post. (body)\", dstBoard)\n}\n\nfunc main() {\n}\n\n// Error:\n// src board not exist\n"},{"name":"z_9_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tsrcBoard boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tsrcBoard = boards.CreateBoard(\"first_board\")\n\tpid = boards.CreateThread(srcBoard, \"First Post in (title)\", \"Body of the first post. (body)\")\n\n\tboards.CreateRepost(srcBoard, pid, \"First Post in (title)\", \"Body of the first post. (body)\", 0)\n}\n\nfunc main() {\n}\n\n// Error:\n// dst board not exist\n"},{"name":"z_9_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tfirstBoard boards.BoardID\n\tsecondBoard boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tfirstBoard = boards.CreateBoard(\"first_board\")\n\tsecondBoard = boards.CreateBoard(\"second_board\")\n\tpid = boards.CreateThread(firstBoard, \"First Post in (title)\", \"Body of the first post. (body)\")\n\n\tboards.CreateRepost(firstBoard, pid, \"First Post in (title)\", \"Body of the first post. (body)\", secondBoard)\n}\n\nfunc main() {\n\tprintln(boards.Render(\"second_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:second_board/1/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=2\u0026threadid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=2\u0026threadid=1\u0026postid=1)]\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":"users","path":"gno.land/r/demo/users","files":[{"name":"preregister.gno","body":"package users\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/users\"\n)\n\n// pre-restricted names\nvar preRestrictedNames = []string{\n\t\"bitcoin\", \"cosmos\", \"newtendermint\", \"ethereum\",\n}\n\n// pre-registered users\nvar preRegisteredUsers = []struct {\n\tName string\n\tAddress std.Address\n}{\n\t// system name\n\t{\"archives\", \"g1xlnyjrnf03ju82v0f98ruhpgnquk28knmjfe5k\"}, // -\u003e @r_archives\n\t{\"demo\", \"g13ek2zz9qurzynzvssyc4sthwppnruhnp0gdz8n\"}, // -\u003e @r_demo\n\t{\"gno\", \"g19602kd9tfxrfd60sgreadt9zvdyyuudcyxsz8a\"}, // -\u003e @r_gno\n\t{\"gnoland\", \"g1g3lsfxhvaqgdv4ccemwpnms4fv6t3aq3p5z6u7\"}, // -\u003e @r_gnoland\n\t{\"gnolang\", \"g1yjlnm3z2630gg5mryjd79907e0zx658wxs9hnd\"}, // -\u003e @r_gnolang\n\t{\"gov\", \"g1g73v2anukg4ej7axwqpthsatzrxjsh0wk797da\"}, // -\u003e @r_gov\n\t{\"nt\", \"g15ge0ae9077eh40erwrn2eq0xw6wupwqthpv34l\"}, // -\u003e @r_nt\n\t{\"sys\", \"g1r929wt2qplfawe4lvqv9zuwfdcz4vxdun7qh8l\"}, // -\u003e @r_sys\n\t{\"x\", \"g164sdpew3c2t3rvxj3kmfv7c7ujlvcw2punzzuz\"}, // -\u003e @r_x\n\n\t// test1 user\n\t{\"test1\", \"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\"}, // -\u003e @test1\n\n\t// Onbloc\n\t{\"gnoswap\", \"g1lmvrrrr4er2us84h2732sru76c9zl2nvknha8c\"}, // -\u003e @r_gnoswap\n\t{\"onbloc\", \"g12vx7dn3dqq89mz550zwunvg4qw6epq73d9csay\"}, // -\u003e @r_onbloc\n\n\t// Dragos\n\t{\"flippando\", \"g1z82x8mxa0pz5s9u7csy6zya4x0ut9uw6p7d8dk\"}, // -\u003e @r_flippando\n\t{\"zentasktic\", \"g1paxgmwy2wzhx0l6qvav2p8thvphc5c030xz35c\"}, // -\u003e @r_zentasktic\n}\n\nfunc init() {\n\t// add pre-registered users\n\tfor _, res := range preRegisteredUsers {\n\t\t// assert not already registered.\n\t\t_, ok := name2User.Get(res.Name)\n\t\tif ok {\n\t\t\tpanic(\"name already registered\")\n\t\t}\n\n\t\t_, ok = addr2User.Get(res.Address.String())\n\t\tif ok {\n\t\t\tpanic(\"address already registered\")\n\t\t}\n\n\t\tcounter++\n\t\tuser := \u0026users.User{\n\t\t\tAddress: res.Address,\n\t\t\tName: res.Name,\n\t\t\tProfile: \"\",\n\t\t\tNumber: counter,\n\t\t\tInvites: int(0),\n\t\t\tInviter: admin,\n\t\t}\n\t\tname2User.Set(res.Name, user)\n\t\taddr2User.Set(res.Address.String(), user)\n\t}\n\n\t// add pre-restricted names\n\tfor _, name := range preRestrictedNames {\n\t\tif _, ok := name2User.Get(name); ok {\n\t\t\tpanic(\"name already registered\")\n\t\t}\n\n\t\trestricted.Set(name, true)\n\t}\n}\n"},{"name":"users.gno","body":"package users\n\nimport (\n\t\"regexp\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/avl/pager\"\n\t\"gno.land/p/demo/avlhelpers\"\n\t\"gno.land/p/demo/users\"\n)\n\n//----------------------------------------\n// State\n\nvar (\n\tadmin std.Address = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\" // @moul\n\n\trestricted avl.Tree // Name -\u003e true - restricted name\n\tname2User avl.Tree // Name -\u003e *users.User\n\taddr2User avl.Tree // std.Address -\u003e *users.User\n\tinvites avl.Tree // string(inviter+\":\"+invited) -\u003e true\n\tcounter int // user id counter\n\tminFee int64 = 20 * 1_000_000 // minimum gnot must be paid to register.\n\tmaxFeeMult int64 = 10 // maximum multiples of minFee accepted.\n)\n\n//----------------------------------------\n// Top-level functions\n\nfunc Register(inviter std.Address, name string, profile string) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// assert invited or paid.\n\tcaller := std.GetCallerAt(2)\n\tif caller != std.OrigCaller() {\n\t\tpanic(\"should not happen\") // because std.AssertOrigCall().\n\t}\n\n\tsentCoins := std.OrigSend()\n\tminCoin := std.NewCoin(\"ugnot\", minFee)\n\n\tif inviter == \"\" {\n\t\t// banker := std.GetBanker(std.BankerTypeOrigSend)\n\t\tif len(sentCoins) == 1 \u0026\u0026 sentCoins[0].IsGTE(minCoin) {\n\t\t\tif sentCoins[0].Amount \u003e minFee*maxFeeMult {\n\t\t\t\tpanic(\"payment must not be greater than \" + strconv.Itoa(int(minFee*maxFeeMult)))\n\t\t\t} else {\n\t\t\t\t// ok\n\t\t\t}\n\t\t} else {\n\t\t\tpanic(\"payment must not be less than \" + strconv.Itoa(int(minFee)))\n\t\t}\n\t} else {\n\t\tinvitekey := inviter.String() + \":\" + caller.String()\n\t\t_, ok := invites.Get(invitekey)\n\t\tif !ok {\n\t\t\tpanic(\"invalid invitation\")\n\t\t}\n\t\tinvites.Remove(invitekey)\n\t}\n\n\t// assert not already registered.\n\t_, ok := name2User.Get(name)\n\tif ok {\n\t\tpanic(\"name already registered: \" + name)\n\t}\n\t_, ok = addr2User.Get(caller.String())\n\tif ok {\n\t\tpanic(\"address already registered: \" + caller.String())\n\t}\n\n\tisInviterAdmin := inviter == admin\n\n\t// check for restricted name\n\tif _, isRestricted := restricted.Get(name); isRestricted {\n\t\t// only address invite by the admin can register restricted name\n\t\tif !isInviterAdmin {\n\t\t\tpanic(\"restricted name: \" + name)\n\t\t}\n\n\t\trestricted.Remove(name)\n\t}\n\n\t// assert name is valid.\n\t// admin inviter can bypass name restriction\n\tif !isInviterAdmin \u0026\u0026 !reName.MatchString(name) {\n\t\tpanic(\"invalid name: \" + name + \" (must be at least 6 characters, lowercase alphanumeric with underscore)\")\n\t}\n\n\t// remainder of fees go toward invites.\n\tinvites := int(0)\n\tif len(sentCoins) == 1 {\n\t\tif sentCoins[0].Denom == \"ugnot\" \u0026\u0026 sentCoins[0].Amount \u003e= minFee {\n\t\t\tinvites = int(sentCoins[0].Amount / minFee)\n\t\t\tif inviter == \"\" \u0026\u0026 invites \u003e 0 {\n\t\t\t\tinvites -= 1\n\t\t\t}\n\t\t}\n\t}\n\t// register.\n\tcounter++\n\tuser := \u0026users.User{\n\t\tAddress: caller,\n\t\tName: name,\n\t\tProfile: profile,\n\t\tNumber: counter,\n\t\tInvites: invites,\n\t\tInviter: inviter,\n\t}\n\tname2User.Set(name, user)\n\taddr2User.Set(caller.String(), user)\n}\n\nfunc Invite(invitee string) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// get caller/inviter.\n\tcaller := std.GetCallerAt(2)\n\tif caller != std.OrigCaller() {\n\t\tpanic(\"should not happen\") // because std.AssertOrigCall().\n\t}\n\tlines := strings.Split(invitee, \"\\n\")\n\tif caller == admin {\n\t\t// nothing to do, all good\n\t} else {\n\t\t// ensure has invites.\n\t\tuserI, ok := addr2User.Get(caller.String())\n\t\tif !ok {\n\t\t\tpanic(\"user unknown\")\n\t\t}\n\t\tuser := userI.(*users.User)\n\t\tif user.Invites \u003c= 0 {\n\t\t\tpanic(\"user has no invite tokens\")\n\t\t}\n\t\tuser.Invites -= len(lines)\n\t\tif user.Invites \u003c 0 {\n\t\t\tpanic(\"user has insufficient invite tokens\")\n\t\t}\n\t}\n\t// for each line...\n\tfor _, line := range lines {\n\t\tif line == \"\" {\n\t\t\tcontinue // file bodies have a trailing newline.\n\t\t} else if strings.HasPrefix(line, `//`) {\n\t\t\tcontinue // comment\n\t\t}\n\t\t// record invite.\n\t\tinvitekey := string(caller) + \":\" + string(line)\n\t\tinvites.Set(invitekey, true)\n\t}\n}\n\nfunc GrantInvites(invites string) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// assert admin.\n\tcaller := std.GetCallerAt(2)\n\tif caller != std.OrigCaller() {\n\t\tpanic(\"should not happen\") // because std.AssertOrigCall().\n\t}\n\tif caller != admin {\n\t\tpanic(\"unauthorized\")\n\t}\n\t// for each line...\n\tlines := strings.Split(invites, \"\\n\")\n\tfor _, line := range lines {\n\t\tif line == \"\" {\n\t\t\tcontinue // file bodies have a trailing newline.\n\t\t} else if strings.HasPrefix(line, `//`) {\n\t\t\tcontinue // comment\n\t\t}\n\t\t// parse name and invites.\n\t\tvar name string\n\t\tvar invites int\n\t\tparts := strings.Split(line, \":\")\n\t\tif len(parts) == 1 { // short for :1.\n\t\t\tname = parts[0]\n\t\t\tinvites = 1\n\t\t} else if len(parts) == 2 {\n\t\t\tname = parts[0]\n\t\t\tinvites_, err := strconv.Atoi(parts[1])\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t\tinvites = int(invites_)\n\t\t} else {\n\t\t\tpanic(\"should not happen\")\n\t\t}\n\t\t// give invites.\n\t\tuserI, ok := name2User.Get(name)\n\t\tif !ok {\n\t\t\t// maybe address.\n\t\t\tuserI, ok = addr2User.Get(name)\n\t\t\tif !ok {\n\t\t\t\tpanic(\"invalid user \" + name)\n\t\t\t}\n\t\t}\n\t\tuser := userI.(*users.User)\n\t\tuser.Invites += invites\n\t}\n}\n\n// Any leftover fees go toward invitations.\nfunc SetMinFee(newMinFee int64) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// assert admin caller.\n\tcaller := std.GetCallerAt(2)\n\tif caller != admin {\n\t\tpanic(\"unauthorized\")\n\t}\n\t// update global variables.\n\tminFee = newMinFee\n}\n\n// This helps prevent fat finger accidents.\nfunc SetMaxFeeMultiple(newMaxFeeMult int64) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// assert admin caller.\n\tcaller := std.GetCallerAt(2)\n\tif caller != admin {\n\t\tpanic(\"unauthorized\")\n\t}\n\t// update global variables.\n\tmaxFeeMult = newMaxFeeMult\n}\n\n//----------------------------------------\n// Exposed public functions\n\nfunc GetUserByName(name string) *users.User {\n\tuserI, ok := name2User.Get(name)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn userI.(*users.User)\n}\n\nfunc GetUserByAddress(addr std.Address) *users.User {\n\tuserI, ok := addr2User.Get(addr.String())\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn userI.(*users.User)\n}\n\n// unlike GetUserByName, input must be \"@\" prefixed for names.\nfunc GetUserByAddressOrName(input users.AddressOrName) *users.User {\n\tname, isName := input.GetName()\n\tif isName {\n\t\treturn GetUserByName(name)\n\t}\n\treturn GetUserByAddress(std.Address(input))\n}\n\n// Get a list of user names starting from the given prefix. Limit the\n// number of results to maxResults. (This can be used for a name search tool.)\nfunc ListUsersByPrefix(prefix string, maxResults int) []string {\n\treturn avlhelpers.ListByteStringKeysByPrefix(name2User, prefix, maxResults)\n}\n\nfunc Resolve(input users.AddressOrName) std.Address {\n\tname, isName := input.GetName()\n\tif !isName {\n\t\treturn std.Address(input) // TODO check validity\n\t}\n\n\tuser := GetUserByName(name)\n\treturn user.Address\n}\n\n// Add restricted name to the list\nfunc AdminAddRestrictedName(name string) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// get caller\n\tcaller := std.OrigCaller()\n\t// assert admin\n\tif caller != admin {\n\t\tpanic(\"unauthorized\")\n\t}\n\n\tif user := GetUserByName(name); user != nil {\n\t\tpanic(\"already registered name\")\n\t}\n\n\t// register restricted name\n\n\trestricted.Set(name, true)\n}\n\n//----------------------------------------\n// Constants\n\n// NOTE: name length must be clearly distinguishable from a bech32 address.\nvar reName = regexp.MustCompile(`^[a-z]+[_a-z0-9]{5,16}$`)\n\n//----------------------------------------\n// Render main page\n\nfunc Render(fullPath string) string {\n\tpath, _ := splitPathAndQuery(fullPath)\n\tif path == \"\" {\n\t\treturn renderHome(fullPath)\n\t} else if len(path) \u003e= 38 { // 39? 40?\n\t\tif path[:2] != \"g1\" {\n\t\t\treturn \"invalid address \" + path\n\t\t}\n\t\tuser := GetUserByAddress(std.Address(path))\n\t\tif user == nil {\n\t\t\t// TODO: display basic information about account.\n\t\t\treturn \"unknown address \" + path\n\t\t}\n\t\treturn user.Render()\n\t} else {\n\t\tuser := GetUserByName(path)\n\t\tif user == nil {\n\t\t\treturn \"unknown username \" + path\n\t\t}\n\t\treturn user.Render()\n\t}\n}\n\nfunc renderHome(path string) string {\n\tdoc := \"\"\n\n\tpage := pager.NewPager(\u0026name2User, 50).MustGetPageByPath(path)\n\n\tfor _, item := range page.Items {\n\t\tuser := item.Value.(*users.User)\n\t\tdoc += \" * [\" + user.Name + \"](/r/demo/users:\" + user.Name + \")\\n\"\n\t}\n\tdoc += \"\\n\"\n\tdoc += page.Selector()\n\treturn doc\n}\n\nfunc splitPathAndQuery(fullPath string) (string, string) {\n\tparts := strings.SplitN(fullPath, \"?\", 2)\n\tpath := parts[0]\n\tqueryString := \"\"\n\tif len(parts) \u003e 1 {\n\t\tqueryString = \"?\" + parts[1]\n\t}\n\treturn path, queryString\n}\n"},{"name":"users_test.gno","body":"package users\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestPreRegisteredTest1(t *testing.T) {\n\tnames := ListUsersByPrefix(\"test1\", 1)\n\tuassert.Equal(t, len(names), 1)\n\tuassert.Equal(t, names[0], \"test1\")\n}\n"},{"name":"z_0_b_filetest.gno","body":"package main\n\n// SEND: 19900000ugnot\n\nimport (\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Error:\n// payment must not be less than 20000000\n"},{"name":"z_0_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tstd.TestSetOrigSend(std.Coins{std.NewCoin(\"dontcare\", 1)}, nil)\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Error:\n// incompatible coin denominations: dontcare, ugnot\n"},{"name":"z_10_filetest.gno","body":"// PKGPATH: gno.land/r/demo/users_test\npackage users_test\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc init() {\n\tcaller := std.OrigCaller() // main\n\ttest2 := testutils.TestAddress(\"test2\")\n\t// as admin, invite gnouser and test2\n\tstd.TestSetOrigCaller(admin)\n\tusers.Invite(caller.String() + \"\\n\" + test2.String())\n\t// register as caller\n\tstd.TestSetOrigCaller(caller)\n\tusers.Register(admin, \"gnouser\", \"my profile\")\n}\n\nfunc main() {\n\t// register as test2\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOrigCaller(test2)\n\tusers.Register(admin, \"test222\", \"my profile 2\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_11_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OrigCaller() // main\n\tstd.TestSetOrigCaller(admin)\n\tusers.AdminAddRestrictedName(\"superrestricted\")\n\n\t// test restricted name\n\tstd.TestSetOrigCaller(caller)\n\tusers.Register(\"\", \"superrestricted\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Error:\n// restricted name: superrestricted\n"},{"name":"z_11b_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OrigCaller() // main\n\tstd.TestSetOrigCaller(admin)\n\t// add restricted name\n\tusers.AdminAddRestrictedName(\"superrestricted\")\n\t// grant invite to caller\n\tusers.Invite(caller.String())\n\t// set back caller\n\tstd.TestSetOrigCaller(caller)\n\t// register restricted name with admin invite\n\tusers.Register(admin, \"superrestricted\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_12_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"alicia\", \"my profile\")\n\n\t{\n\t\t// Normal usage\n\t\tnames := users.ListUsersByPrefix(\"a\", 1)\n\t\tprintln(\"# names: \" + strconv.Itoa(len(names)))\n\t\tprintln(\"name: \" + names[0])\n\t}\n\n\t{\n\t\t// Empty prefix: match all\n\t\tnames := users.ListUsersByPrefix(\"\", 1)\n\t\tprintln(\"# names: \" + strconv.Itoa(len(names)))\n\t\tprintln(\"name: \" + names[0])\n\t}\n\n\t{\n\t\t// The prefix is before \"alicia\"\n\t\tnames := users.ListUsersByPrefix(\"alich\", 1)\n\t\tprintln(\"# names: \" + strconv.Itoa(len(names)))\n\t}\n\n\t{\n\t\t// The prefix is after the last name\n\t\tnames := users.ListUsersByPrefix(\"y\", 10)\n\t\tprintln(\"# names: \" + strconv.Itoa(len(names)))\n\t}\n\n\t// More tests are in p/demo/avlhelpers\n}\n\n// Output:\n// # names: 1\n// name: alicia\n// # names: 1\n// name: alicia\n// # names: 0\n// # names: 0\n"},{"name":"z_1_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_2_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OrigCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test1)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_3_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OrigCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test1)\n\tstd.TestSetOrigSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_4_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OrigCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\ttest2 := testutils.TestAddress(\"test2\")\n\tusers.Invite(test1.String())\n\t// switch to test2 (not test1)\n\tstd.TestSetOrigCaller(test2)\n\tstd.TestSetOrigSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\tprintln(\"done\")\n}\n\n// Error:\n// invalid invitation\n"},{"name":"z_5_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OrigCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test1)\n\tstd.TestSetOrigSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\tprintln(users.Render(\"\"))\n\tprintln(\"========================================\")\n\tprintln(users.Render(\"?page=2\"))\n\tprintln(\"========================================\")\n\tprintln(users.Render(\"gnouser\"))\n\tprintln(\"========================================\")\n\tprintln(users.Render(\"satoshi\"))\n\tprintln(\"========================================\")\n\tprintln(users.Render(\"badname\"))\n}\n\n// Output:\n// * [archives](/r/demo/users:archives)\n// * [demo](/r/demo/users:demo)\n// * [gno](/r/demo/users:gno)\n// * [gnoland](/r/demo/users:gnoland)\n// * [gnolang](/r/demo/users:gnolang)\n// * [gnouser](/r/demo/users:gnouser)\n// * [gov](/r/demo/users:gov)\n// * [nt](/r/demo/users:nt)\n// * [satoshi](/r/demo/users:satoshi)\n// * [sys](/r/demo/users:sys)\n// * [test1](/r/demo/users:test1)\n// * [x](/r/demo/users:x)\n//\n//\n// ========================================\n//\n//\n// ========================================\n// ## user gnouser\n//\n// * address = g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n// * 9 invites\n//\n// my profile\n//\n// ========================================\n// ## user satoshi\n//\n// * address = g1w3jhxap3ta047h6lta047h6lta047h6l4mfnm7\n// * 0 invites\n// * invited by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// my other profile\n//\n// ========================================\n// unknown username badname\n"},{"name":"z_6_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OrigCaller()\n\t// as admin, grant invites to unregistered user.\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\tprintln(\"done\")\n}\n\n// Error:\n// invalid user g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n"},{"name":"z_7_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OrigCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test1)\n\tstd.TestSetOrigSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\t// as admin, grant invites to gnouser(again) and satoshi.\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\\n\" + test1.String() + \":1\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_7b_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OrigCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\\n\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test1)\n\tstd.TestSetOrigSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\t// as admin, grant invites to gnouser(again) and satoshi.\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\\n\" + test1.String() + \":1\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_8_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OrigCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test1)\n\tstd.TestSetOrigSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\t// as admin, grant invites to gnouser(again) and nonexistent user.\n\tstd.TestSetOrigCaller(admin)\n\ttest2 := testutils.TestAddress(\"test2\")\n\tusers.GrantInvites(caller.String() + \":1\\n\" + test2.String() + \":1\")\n\tprintln(\"done\")\n}\n\n// Error:\n// invalid user g1w3jhxapjta047h6lta047h6lta047h6laqcyu4\n"},{"name":"z_9_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OrigCaller() // main\n\ttest2 := testutils.TestAddress(\"test2\")\n\t// as admin, invite gnouser and test2\n\tstd.TestSetOrigCaller(admin)\n\tusers.Invite(caller.String() + \"\\n\" + test2.String())\n\t// register as caller\n\tstd.TestSetOrigCaller(caller)\n\tusers.Register(admin, \"gnouser\", \"my profile\")\n\t// register as test2\n\tstd.TestSetOrigCaller(test2)\n\tusers.Register(admin, \"test222\", \"my profile 2\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\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":"boards","path":"gno.land/r/demo/boards","files":[{"name":"README.md","body":"This is a demo of Gno smart contract programming. This document was\nconstructed by Gno onto a smart contract hosted on the data Realm\nname [\"gno.land/r/demo/boards\"](https://gno.land/r/demo/boards/)\n([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)).\n\n\n\n## Build `gnokey`, create your account, and interact with Gno.\n\nNOTE: Where you see `-remote localhost:26657` here, that flag can be replaced\nwith `-remote gno.land:26657` if you have $GNOT on the testnet.\n(To use the testnet, also replace `-chainid dev` with `-chainid portal-loop` .)\n\n### Build `gnokey` (and other tools).\n\n```bash\ngit clone git@github.com:gnolang/gno.git\ncd gno/gno.land\nmake build\n```\n\n### Generate a seed/mnemonic code.\n\n```bash\n./build/gnokey generate\n```\n\nNOTE: You can generate 24 words with any good bip39 generator.\n\n### Create a new account using your mnemonic.\n\n```bash\n./build/gnokey add -recover KEYNAME\n```\n\nNOTE: `KEYNAME` is your key identifier, and should be changed.\n\n### Verify that you can see your account locally.\n\n```bash\n./build/gnokey list\n```\n\nTake note of your `addr` which looks something like `g17sphqax3kasjptdkmuqvn740u8dhtx4kxl6ljf` .\nYou will use this as your `ACCOUNT_ADDR`.\n\n## Interact with the blockchain.\n\n### Add $GNOT for your account.\n\nBefore starting the `gnoland` node for the first time, your new account can be given $GNOT in the node genesis.\nEdit the file `gno.land/genesis/genesis_balances.txt` and add the following line (simlar to the others), using\nyour `ACCOUNT_ADDR` and `KEYNAME`\n\n`ACCOUNT_ADDR=10000000000ugnot # @KEYNAME`\n\n### Alternative: Run a faucet to add $GNOT.\n\nInstead of editing `gno.land/genesis/genesis_balances.txt`, a more general solution (with more steps)\nis to run a local \"faucet\" and use the web browser to add $GNOT. (This can be done at any time.)\nSee this page: https://github.com/gnolang/gno/blob/master/contribs/gnofaucet/README.md\n\n\n### Start the `gnoland` node.\n\n```bash\n./build/gnoland start\n```\n\nNOTE: The node already has the \"boards\" realm.\n\nLeave this running in the terminal. In a new terminal, cd to the same folder `gno/gno.land` .\n\n### Get your current balance, account number, and sequence number.\n\n```bash\n./build/gnokey query auth/accounts/ACCOUNT_ADDR -remote localhost:26657\n```\n\n### Register a board username with a smart contract call.\n\nThe `USERNAME` for posting can different than your `KEYNAME`. It is internally linked to your `ACCOUNT_ADDR`. It must be at least 6 characters, lowercase alphanumeric with underscore.\n\n```bash\n./build/gnokey maketx call -pkgpath \"gno.land/r/demo/users\" -func \"Register\" -args \"\" -args \"USERNAME\" -args \"Profile description\" -gas-fee \"10000000ugnot\" -gas-wanted \"2000000\" -send \"200000000ugnot\" -broadcast -chainid dev -remote 127.0.0.1:26657 KEYNAME\n```\n\nInteractive documentation: https://gno.land/r/demo/users$help\u0026func=Register\n\n### Create a board with a smart contract call.\n\n```bash\n./build/gnokey maketx call -pkgpath \"gno.land/r/demo/boards\" -func \"CreateBoard\" -args \"BOARDNAME\" -gas-fee \"1000000ugnot\" -gas-wanted \"10000000\" -broadcast -chainid dev -remote localhost:26657 KEYNAME\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateBoard\n\nNext, query for the permanent board ID by querying (you need this to create a new post):\n\n```bash\n./build/gnokey query \"vm/qeval\" -data 'gno.land/r/demo/boards.GetBoardIDFromName(\"BOARDNAME\")' -remote localhost:26657\n```\n\n### Create a post of a board with a smart contract call.\n\nNOTE: If a board was created successfully, your SEQUENCE_NUMBER would have increased.\n\n```bash\n./build/gnokey maketx call -pkgpath \"gno.land/r/demo/boards\" -func \"CreateThread\" -args BOARD_ID -args \"Hello gno.land\" -args \"Text of the post\" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid dev -remote localhost:26657 KEYNAME\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateThread\n\n### Create a comment to a post.\n\n```bash\n./build/gnokey maketx call -pkgpath \"gno.land/r/demo/boards\" -func \"CreateReply\" -args BOARD_ID -args \"1\" -args \"1\" -args \"Nice to meet you too.\" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid dev -remote localhost:26657 KEYNAME\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateReply\n\n```bash\n./build/gnokey query \"vm/qrender\" -data \"gno.land/r/demo/boards:BOARDNAME/1\" -remote localhost:26657\n```\n\n### Render page with optional path expression.\n\nThe contents of `https://gno.land/r/demo/boards:` and `https://gno.land/r/demo/boards:gnolang` are rendered by calling\nthe `Render(path string)` function like so:\n\n```bash\n./build/gnokey query \"vm/qrender\" -data \"gno.land/r/demo/boards:gnolang\"\n```\n## View the board in the browser.\n\n### Start the web server.\n\n```bash\n./build/gnoweb\n```\n\nThis should print something like `Running on http://127.0.0.1:8888` . Leave this running in the terminal.\n\n### View in the browser\n\nIn your browser, navigate to the printed address http://127.0.0.1:8888 .\nTo see you post, click on the package `/r/demo/boards` .\n"},{"name":"board.gno","body":"package boards\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/moul/txlink\"\n)\n\n//----------------------------------------\n// Board\n\ntype BoardID uint64\n\nfunc (bid BoardID) String() string {\n\treturn strconv.Itoa(int(bid))\n}\n\ntype Board struct {\n\tid BoardID // only set for public boards.\n\turl string\n\tname string\n\tcreator std.Address\n\tthreads avl.Tree // Post.id -\u003e *Post\n\tpostsCtr uint64 // increments Post.id\n\tcreatedAt time.Time\n\tdeleted avl.Tree // TODO reserved for fast-delete.\n}\n\nfunc newBoard(id BoardID, url string, name string, creator std.Address) *Board {\n\tif !reName.MatchString(name) {\n\t\tpanic(\"invalid name: \" + name)\n\t}\n\texists := gBoardsByName.Has(name)\n\tif exists {\n\t\tpanic(\"board already exists\")\n\t}\n\treturn \u0026Board{\n\t\tid: id,\n\t\turl: url,\n\t\tname: name,\n\t\tcreator: creator,\n\t\tthreads: avl.Tree{},\n\t\tcreatedAt: time.Now(),\n\t\tdeleted: avl.Tree{},\n\t}\n}\n\n/* TODO support this once we figure out how to ensure URL correctness.\n// A private board is not tracked by gBoards*,\n// but must be persisted by the caller's realm.\n// Private boards have 0 id and does not ping\n// back the remote board on reposts.\nfunc NewPrivateBoard(url string, name string, creator std.Address) *Board {\n\treturn newBoard(0, url, name, creator)\n}\n*/\n\nfunc (board *Board) IsPrivate() bool {\n\treturn board.id == 0\n}\n\nfunc (board *Board) GetThread(pid PostID) *Post {\n\tpidkey := postIDKey(pid)\n\tpostI, exists := board.threads.Get(pidkey)\n\tif !exists {\n\t\treturn nil\n\t}\n\treturn postI.(*Post)\n}\n\nfunc (board *Board) AddThread(creator std.Address, title string, body string) *Post {\n\tpid := board.incGetPostID()\n\tpidkey := postIDKey(pid)\n\tthread := newPost(board, pid, creator, title, body, pid, 0, 0)\n\tboard.threads.Set(pidkey, thread)\n\treturn thread\n}\n\n// NOTE: this can be potentially very expensive for threads with many replies.\n// TODO: implement optional fast-delete where thread is simply moved.\nfunc (board *Board) DeleteThread(pid PostID) {\n\tpidkey := postIDKey(pid)\n\t_, removed := board.threads.Remove(pidkey)\n\tif !removed {\n\t\tpanic(\"thread does not exist with id \" + pid.String())\n\t}\n}\n\nfunc (board *Board) HasPermission(addr std.Address, perm Permission) bool {\n\tif board.creator == addr {\n\t\tswitch perm {\n\t\tcase EditPermission:\n\t\t\treturn true\n\t\tcase DeletePermission:\n\t\t\treturn true\n\t\tdefault:\n\t\t\treturn false\n\t\t}\n\t}\n\treturn false\n}\n\n// Renders the board for display suitable as plaintext in\n// console. This is suitable for demonstration or tests,\n// but not for prod.\nfunc (board *Board) RenderBoard() string {\n\tstr := \"\"\n\tstr += \"\\\\[[post](\" + board.GetPostFormURL() + \")]\\n\\n\"\n\tif board.threads.Size() \u003e 0 {\n\t\tboard.threads.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tif str != \"\" {\n\t\t\t\tstr += \"----------------------------------------\\n\"\n\t\t\t}\n\t\t\tstr += value.(*Post).RenderSummary() + \"\\n\"\n\t\t\treturn false\n\t\t})\n\t}\n\treturn str\n}\n\nfunc (board *Board) incGetPostID() PostID {\n\tboard.postsCtr++\n\treturn PostID(board.postsCtr)\n}\n\nfunc (board *Board) GetURLFromThreadAndReplyID(threadID, replyID PostID) string {\n\tif replyID == 0 {\n\t\treturn board.url + \"/\" + threadID.String()\n\t} else {\n\t\treturn board.url + \"/\" + threadID.String() + \"/\" + replyID.String()\n\t}\n}\n\nfunc (board *Board) GetPostFormURL() string {\n\treturn txlink.URL(\"CreateThread\", \"bid\", board.id.String())\n}\n"},{"name":"boards.gno","body":"package boards\n\nimport (\n\t\"regexp\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n//----------------------------------------\n// Realm (package) state\n\nvar (\n\tgBoards avl.Tree // id -\u003e *Board\n\tgBoardsCtr int // increments Board.id\n\tgBoardsByName avl.Tree // name -\u003e *Board\n\tgDefaultAnonFee = 100000000 // minimum fee required if anonymous\n)\n\n//----------------------------------------\n// Constants\n\nvar reName = regexp.MustCompile(`^[a-z]+[_a-z0-9]{2,29}$`)\n"},{"name":"misc.gno","body":"package boards\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/r/demo/users\"\n)\n\n//----------------------------------------\n// private utility methods\n// XXX ensure these cannot be called from public.\n\nfunc getBoard(bid BoardID) *Board {\n\tbidkey := boardIDKey(bid)\n\tboard_, exists := gBoards.Get(bidkey)\n\tif !exists {\n\t\treturn nil\n\t}\n\tboard := board_.(*Board)\n\treturn board\n}\n\nfunc incGetBoardID() BoardID {\n\tgBoardsCtr++\n\treturn BoardID(gBoardsCtr)\n}\n\nfunc padLeft(str string, length int) string {\n\tif len(str) \u003e= length {\n\t\treturn str\n\t} else {\n\t\treturn strings.Repeat(\" \", length-len(str)) + str\n\t}\n}\n\nfunc padZero(u64 uint64, length int) string {\n\tstr := strconv.Itoa(int(u64))\n\tif len(str) \u003e= length {\n\t\treturn str\n\t} else {\n\t\treturn strings.Repeat(\"0\", length-len(str)) + str\n\t}\n}\n\nfunc boardIDKey(bid BoardID) string {\n\treturn padZero(uint64(bid), 10)\n}\n\nfunc postIDKey(pid PostID) string {\n\treturn padZero(uint64(pid), 10)\n}\n\nfunc indentBody(indent string, body string) string {\n\tlines := strings.Split(body, \"\\n\")\n\tres := \"\"\n\tfor i, line := range lines {\n\t\tif i \u003e 0 {\n\t\t\tres += \"\\n\"\n\t\t}\n\t\tres += indent + line\n\t}\n\treturn res\n}\n\n// NOTE: length must be greater than 3.\nfunc summaryOf(str string, length int) string {\n\tlines := strings.SplitN(str, \"\\n\", 2)\n\tline := lines[0]\n\tif len(line) \u003e length {\n\t\tline = line[:(length-3)] + \"...\"\n\t} else if len(lines) \u003e 1 {\n\t\t// len(line) \u003c= 80\n\t\tline = line + \"...\"\n\t}\n\treturn line\n}\n\nfunc displayAddressMD(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user == nil {\n\t\treturn \"[\" + addr.String() + \"](/r/demo/users:\" + addr.String() + \")\"\n\t} else {\n\t\treturn \"[@\" + user.Name + \"](/r/demo/users:\" + user.Name + \")\"\n\t}\n}\n\nfunc usernameOf(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user == nil {\n\t\treturn \"\"\n\t}\n\treturn user.Name\n}\n"},{"name":"post.gno","body":"package boards\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/moul/txlink\"\n)\n\n//----------------------------------------\n// Post\n\n// NOTE: a PostID is relative to the board.\ntype PostID uint64\n\nfunc (pid PostID) String() string {\n\treturn strconv.Itoa(int(pid))\n}\n\n// A Post is a \"thread\" or a \"reply\" depending on context.\n// A thread is a Post of a Board that holds other replies.\ntype Post struct {\n\tboard *Board\n\tid PostID\n\tcreator std.Address\n\ttitle string // optional\n\tbody string\n\treplies avl.Tree // Post.id -\u003e *Post\n\trepliesAll avl.Tree // Post.id -\u003e *Post (all replies, for top-level posts)\n\treposts avl.Tree // Board.id -\u003e Post.id\n\tthreadID PostID // original Post.id\n\tparentID PostID // parent Post.id (if reply or repost)\n\trepostBoard BoardID // original Board.id (if repost)\n\tcreatedAt time.Time\n\tupdatedAt time.Time\n}\n\nfunc newPost(board *Board, id PostID, creator std.Address, title, body string, threadID, parentID PostID, repostBoard BoardID) *Post {\n\treturn \u0026Post{\n\t\tboard: board,\n\t\tid: id,\n\t\tcreator: creator,\n\t\ttitle: title,\n\t\tbody: body,\n\t\treplies: avl.Tree{},\n\t\trepliesAll: avl.Tree{},\n\t\treposts: avl.Tree{},\n\t\tthreadID: threadID,\n\t\tparentID: parentID,\n\t\trepostBoard: repostBoard,\n\t\tcreatedAt: time.Now(),\n\t}\n}\n\nfunc (post *Post) IsThread() bool {\n\treturn post.parentID == 0\n}\n\nfunc (post *Post) GetPostID() PostID {\n\treturn post.id\n}\n\nfunc (post *Post) AddReply(creator std.Address, body string) *Post {\n\tboard := post.board\n\tpid := board.incGetPostID()\n\tpidkey := postIDKey(pid)\n\treply := newPost(board, pid, creator, \"\", body, post.threadID, post.id, 0)\n\tpost.replies.Set(pidkey, reply)\n\tif post.threadID == post.id {\n\t\tpost.repliesAll.Set(pidkey, reply)\n\t} else {\n\t\tthread := board.GetThread(post.threadID)\n\t\tthread.repliesAll.Set(pidkey, reply)\n\t}\n\treturn reply\n}\n\nfunc (post *Post) Update(title string, body string) {\n\tpost.title = title\n\tpost.body = body\n\tpost.updatedAt = time.Now()\n}\n\nfunc (thread *Post) GetReply(pid PostID) *Post {\n\tpidkey := postIDKey(pid)\n\treplyI, ok := thread.repliesAll.Get(pidkey)\n\tif !ok {\n\t\treturn nil\n\t} else {\n\t\treturn replyI.(*Post)\n\t}\n}\n\nfunc (post *Post) AddRepostTo(creator std.Address, title, body string, dst *Board) *Post {\n\tif !post.IsThread() {\n\t\tpanic(\"cannot repost non-thread post\")\n\t}\n\tpid := dst.incGetPostID()\n\tpidkey := postIDKey(pid)\n\trepost := newPost(dst, pid, creator, title, body, pid, post.id, post.board.id)\n\tdst.threads.Set(pidkey, repost)\n\tif !dst.IsPrivate() {\n\t\tbidkey := boardIDKey(dst.id)\n\t\tpost.reposts.Set(bidkey, pid)\n\t}\n\treturn repost\n}\n\nfunc (thread *Post) DeletePost(pid PostID) {\n\tif thread.id == pid {\n\t\tpanic(\"should not happen\")\n\t}\n\tpidkey := postIDKey(pid)\n\tpostI, removed := thread.repliesAll.Remove(pidkey)\n\tif !removed {\n\t\tpanic(\"post not found in thread\")\n\t}\n\tpost := postI.(*Post)\n\tif post.parentID != thread.id {\n\t\tparent := thread.GetReply(post.parentID)\n\t\tparent.replies.Remove(pidkey)\n\t} else {\n\t\tthread.replies.Remove(pidkey)\n\t}\n}\n\nfunc (post *Post) HasPermission(addr std.Address, perm Permission) bool {\n\tif post.creator == addr {\n\t\tswitch perm {\n\t\tcase EditPermission:\n\t\t\treturn true\n\t\tcase DeletePermission:\n\t\t\treturn true\n\t\tdefault:\n\t\t\treturn false\n\t\t}\n\t}\n\t// post notes inherit permissions of the board.\n\treturn post.board.HasPermission(addr, perm)\n}\n\nfunc (post *Post) GetSummary() string {\n\treturn summaryOf(post.body, 80)\n}\n\nfunc (post *Post) GetURL() string {\n\tif post.IsThread() {\n\t\treturn post.board.GetURLFromThreadAndReplyID(\n\t\t\tpost.id, 0)\n\t} else {\n\t\treturn post.board.GetURLFromThreadAndReplyID(\n\t\t\tpost.threadID, post.id)\n\t}\n}\n\nfunc (post *Post) GetReplyFormURL() string {\n\treturn txlink.URL(\"CreateReply\",\n\t\t\"bid\", post.board.id.String(),\n\t\t\"threadid\", post.threadID.String(),\n\t\t\"postid\", post.id.String(),\n\t)\n}\n\nfunc (post *Post) GetRepostFormURL() string {\n\treturn txlink.URL(\"CreateRepost\",\n\t\t\"bid\", post.board.id.String(),\n\t\t\"postid\", post.id.String(),\n\t)\n}\n\nfunc (post *Post) GetDeleteFormURL() string {\n\treturn txlink.URL(\"DeletePost\",\n\t\t\"bid\", post.board.id.String(),\n\t\t\"threadid\", post.threadID.String(),\n\t\t\"postid\", post.id.String(),\n\t)\n}\n\nfunc (post *Post) RenderSummary() string {\n\tif post.repostBoard != 0 {\n\t\tdstBoard := getBoard(post.repostBoard)\n\t\tif dstBoard == nil {\n\t\t\tpanic(\"repostBoard does not exist\")\n\t\t}\n\t\tthread := dstBoard.GetThread(PostID(post.parentID))\n\t\tif thread == nil {\n\t\t\treturn \"reposted post does not exist\"\n\t\t}\n\t\treturn \"Repost: \" + post.GetSummary() + \"\\n\" + thread.RenderSummary()\n\t}\n\tstr := \"\"\n\tif post.title != \"\" {\n\t\tstr += \"## [\" + summaryOf(post.title, 80) + \"](\" + post.GetURL() + \")\\n\"\n\t\tstr += \"\\n\"\n\t}\n\tstr += post.GetSummary() + \"\\n\"\n\tstr += \"\\\\- \" + displayAddressMD(post.creator) + \",\"\n\tstr += \" [\" + post.createdAt.Format(\"2006-01-02 3:04pm MST\") + \"](\" + post.GetURL() + \")\"\n\tstr += \" \\\\[[x](\" + post.GetDeleteFormURL() + \")]\"\n\tstr += \" (\" + strconv.Itoa(post.replies.Size()) + \" replies)\"\n\tstr += \" (\" + strconv.Itoa(post.reposts.Size()) + \" reposts)\" + \"\\n\"\n\treturn str\n}\n\nfunc (post *Post) RenderPost(indent string, levels int) string {\n\tif post == nil {\n\t\treturn \"nil post\"\n\t}\n\tstr := \"\"\n\tif post.title != \"\" {\n\t\tstr += indent + \"# \" + post.title + \"\\n\"\n\t\tstr += indent + \"\\n\"\n\t}\n\tstr += indentBody(indent, post.body) + \"\\n\" // TODO: indent body lines.\n\tstr += indent + \"\\\\- \" + displayAddressMD(post.creator) + \", \"\n\tstr += \"[\" + post.createdAt.Format(\"2006-01-02 3:04pm (MST)\") + \"](\" + post.GetURL() + \")\"\n\tstr += \" \\\\[[reply](\" + post.GetReplyFormURL() + \")]\"\n\tif post.IsThread() {\n\t\tstr += \" \\\\[[repost](\" + post.GetRepostFormURL() + \")]\"\n\t}\n\tstr += \" \\\\[[x](\" + post.GetDeleteFormURL() + \")]\\n\"\n\tif levels \u003e 0 {\n\t\tif post.replies.Size() \u003e 0 {\n\t\t\tpost.replies.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\t\tstr += indent + \"\\n\"\n\t\t\t\tstr += value.(*Post).RenderPost(indent+\"\u003e \", levels-1)\n\t\t\t\treturn false\n\t\t\t})\n\t\t}\n\t} else {\n\t\tif post.replies.Size() \u003e 0 {\n\t\t\tstr += indent + \"\\n\"\n\t\t\tstr += indent + \"_[see all \" + strconv.Itoa(post.replies.Size()) + \" replies](\" + post.GetURL() + \")_\\n\"\n\t\t}\n\t}\n\treturn str\n}\n\n// render reply and link to context thread\nfunc (post *Post) RenderInner() string {\n\tif post.IsThread() {\n\t\tpanic(\"unexpected thread\")\n\t}\n\tthreadID := post.threadID\n\t// replyID := post.id\n\tparentID := post.parentID\n\tstr := \"\"\n\tstr += \"_[see thread](\" + post.board.GetURLFromThreadAndReplyID(\n\t\tthreadID, 0) + \")_\\n\\n\"\n\tthread := post.board.GetThread(post.threadID)\n\tvar parent *Post\n\tif thread.id == parentID {\n\t\tparent = thread\n\t} else {\n\t\tparent = thread.GetReply(parentID)\n\t}\n\tstr += parent.RenderPost(\"\", 0)\n\tstr += \"\\n\"\n\tstr += post.RenderPost(\"\u003e \", 5)\n\treturn str\n}\n"},{"name":"public.gno","body":"package boards\n\nimport (\n\t\"std\"\n\t\"strconv\"\n)\n\n//----------------------------------------\n// Public facing functions\n\nfunc GetBoardIDFromName(name string) (BoardID, bool) {\n\tboardI, exists := gBoardsByName.Get(name)\n\tif !exists {\n\t\treturn 0, false\n\t}\n\treturn boardI.(*Board).id, true\n}\n\nfunc CreateBoard(name string) BoardID {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tbid := incGetBoardID()\n\tcaller := std.OrigCaller()\n\tif usernameOf(caller) == \"\" {\n\t\tpanic(\"unauthorized\")\n\t}\n\turl := \"/r/demo/boards:\" + name\n\tboard := newBoard(bid, url, name, caller)\n\tbidkey := boardIDKey(bid)\n\tgBoards.Set(bidkey, board)\n\tgBoardsByName.Set(name, board)\n\treturn board.id\n}\n\nfunc checkAnonFee() bool {\n\tsent := std.OrigSend()\n\tanonFeeCoin := std.NewCoin(\"ugnot\", int64(gDefaultAnonFee))\n\tif len(sent) == 1 \u0026\u0026 sent[0].IsGTE(anonFeeCoin) {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc CreateThread(bid BoardID, title string, body string) PostID {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.OrigCaller()\n\tif usernameOf(caller) == \"\" {\n\t\tif !checkAnonFee() {\n\t\t\tpanic(\"please register, otherwise minimum fee \" + strconv.Itoa(gDefaultAnonFee) + \" is required if anonymous\")\n\t\t}\n\t}\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"board not exist\")\n\t}\n\tthread := board.AddThread(caller, title, body)\n\treturn thread.id\n}\n\nfunc CreateReply(bid BoardID, threadid, postid PostID, body string) PostID {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.OrigCaller()\n\tif usernameOf(caller) == \"\" {\n\t\tif !checkAnonFee() {\n\t\t\tpanic(\"please register, otherwise minimum fee \" + strconv.Itoa(gDefaultAnonFee) + \" is required if anonymous\")\n\t\t}\n\t}\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"board not exist\")\n\t}\n\tthread := board.GetThread(threadid)\n\tif thread == nil {\n\t\tpanic(\"thread not exist\")\n\t}\n\tif postid == threadid {\n\t\treply := thread.AddReply(caller, body)\n\t\treturn reply.id\n\t} else {\n\t\tpost := thread.GetReply(postid)\n\t\treply := post.AddReply(caller, body)\n\t\treturn reply.id\n\t}\n}\n\n// If dstBoard is private, does not ping back.\n// If board specified by bid is private, panics.\nfunc CreateRepost(bid BoardID, postid PostID, title string, body string, dstBoardID BoardID) PostID {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.OrigCaller()\n\tif usernameOf(caller) == \"\" {\n\t\t// TODO: allow with gDefaultAnonFee payment.\n\t\tif !checkAnonFee() {\n\t\t\tpanic(\"please register, otherwise minimum fee \" + strconv.Itoa(gDefaultAnonFee) + \" is required if anonymous\")\n\t\t}\n\t}\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"src board not exist\")\n\t}\n\tif board.IsPrivate() {\n\t\tpanic(\"cannot repost from a private board\")\n\t}\n\tdst := getBoard(dstBoardID)\n\tif dst == nil {\n\t\tpanic(\"dst board not exist\")\n\t}\n\tthread := board.GetThread(postid)\n\tif thread == nil {\n\t\tpanic(\"thread not exist\")\n\t}\n\trepost := thread.AddRepostTo(caller, title, body, dst)\n\treturn repost.id\n}\n\nfunc DeletePost(bid BoardID, threadid, postid PostID, reason string) {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.OrigCaller()\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"board not exist\")\n\t}\n\tthread := board.GetThread(threadid)\n\tif thread == nil {\n\t\tpanic(\"thread not exist\")\n\t}\n\tif postid == threadid {\n\t\t// delete thread\n\t\tif !thread.HasPermission(caller, DeletePermission) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t\tboard.DeleteThread(threadid)\n\t} else {\n\t\t// delete thread's post\n\t\tpost := thread.GetReply(postid)\n\t\tif post == nil {\n\t\t\tpanic(\"post not exist\")\n\t\t}\n\t\tif !post.HasPermission(caller, DeletePermission) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t\tthread.DeletePost(postid)\n\t}\n}\n\nfunc EditPost(bid BoardID, threadid, postid PostID, title, body string) {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.OrigCaller()\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"board not exist\")\n\t}\n\tthread := board.GetThread(threadid)\n\tif thread == nil {\n\t\tpanic(\"thread not exist\")\n\t}\n\tif postid == threadid {\n\t\t// edit thread\n\t\tif !thread.HasPermission(caller, EditPermission) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t\tthread.Update(title, body)\n\t} else {\n\t\t// edit thread's post\n\t\tpost := thread.GetReply(postid)\n\t\tif post == nil {\n\t\t\tpanic(\"post not exist\")\n\t\t}\n\t\tif !post.HasPermission(caller, EditPermission) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t\tpost.Update(title, body)\n\t}\n}\n"},{"name":"render.gno","body":"package boards\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n)\n\n//----------------------------------------\n// Render functions\n\nfunc RenderBoard(bid BoardID) string {\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\treturn \"missing board\"\n\t}\n\treturn board.RenderBoard()\n}\n\nfunc Render(path string) string {\n\tif path == \"\" {\n\t\tstr := \"These are all the boards of this realm:\\n\\n\"\n\t\tgBoards.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tboard := value.(*Board)\n\t\t\tstr += \" * [\" + board.url + \"](\" + board.url + \")\\n\"\n\t\t\treturn false\n\t\t})\n\t\treturn str\n\t}\n\tparts := strings.Split(path, \"/\")\n\tif len(parts) == 1 {\n\t\t// /r/demo/boards:BOARD_NAME\n\t\tname := parts[0]\n\t\tboardI, exists := gBoardsByName.Get(name)\n\t\tif !exists {\n\t\t\treturn \"board does not exist: \" + name\n\t\t}\n\t\treturn boardI.(*Board).RenderBoard()\n\t} else if len(parts) == 2 {\n\t\t// /r/demo/boards:BOARD_NAME/THREAD_ID\n\t\tname := parts[0]\n\t\tboardI, exists := gBoardsByName.Get(name)\n\t\tif !exists {\n\t\t\treturn \"board does not exist: \" + name\n\t\t}\n\t\tpid, err := strconv.Atoi(parts[1])\n\t\tif err != nil {\n\t\t\treturn \"invalid thread id: \" + parts[1]\n\t\t}\n\t\tboard := boardI.(*Board)\n\t\tthread := board.GetThread(PostID(pid))\n\t\tif thread == nil {\n\t\t\treturn \"thread does not exist with id: \" + parts[1]\n\t\t}\n\t\treturn thread.RenderPost(\"\", 5)\n\t} else if len(parts) == 3 {\n\t\t// /r/demo/boards:BOARD_NAME/THREAD_ID/REPLY_ID\n\t\tname := parts[0]\n\t\tboardI, exists := gBoardsByName.Get(name)\n\t\tif !exists {\n\t\t\treturn \"board does not exist: \" + name\n\t\t}\n\t\tpid, err := strconv.Atoi(parts[1])\n\t\tif err != nil {\n\t\t\treturn \"invalid thread id: \" + parts[1]\n\t\t}\n\t\tboard := boardI.(*Board)\n\t\tthread := board.GetThread(PostID(pid))\n\t\tif thread == nil {\n\t\t\treturn \"thread does not exist with id: \" + parts[1]\n\t\t}\n\t\trid, err := strconv.Atoi(parts[2])\n\t\tif err != nil {\n\t\t\treturn \"invalid reply id: \" + parts[2]\n\t\t}\n\t\treply := thread.GetReply(PostID(rid))\n\t\tif reply == nil {\n\t\t\treturn \"reply does not exist with id: \" + parts[2]\n\t\t}\n\t\treturn reply.RenderInner()\n\t} else {\n\t\treturn \"unrecognized path \" + path\n\t}\n}\n"},{"name":"role.gno","body":"package boards\n\ntype Permission string\n\nconst (\n\tDeletePermission Permission = \"role:delete\"\n\tEditPermission Permission = \"role:edit\"\n)\n"},{"name":"z_0_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\nimport (\n\t\"gno.land/r/demo/boards\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid := boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// unauthorized\n"},{"name":"z_0_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 19900000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid = boards.CreateBoard(\"test_board\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// payment must not be less than 20000000\n"},{"name":"z_0_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tboards.CreateThread(1, \"First Post (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// board not exist\n"},{"name":"z_0_d_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateReply(bid, 0, 0, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// thread not exist\n"},{"name":"z_0_e_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tboards.CreateReply(bid, 0, 0, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// board not exist\n"},{"name":"z_0_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 20000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid := boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Output:\n// \\[[post](/r/demo/boards$help\u0026func=CreateThread\u0026bid=1)]\n//\n// ----------------------------------------\n// ## [First Post (title)](/r/demo/boards:test_board/1)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/1) \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)] (0 replies) (0 reposts)\n//\n// ----------------------------------------\n// ## [Second Post (title)](/r/demo/boards:test_board/2)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/2) \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)] (1 replies) (0 reposts)\n"},{"name":"z_10_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// boardId 2 not exist\n\tboards.DeletePost(2, pid, pid, \"\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// board not exist\n"},{"name":"z_10_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// pid of 2 not exist\n\tboards.DeletePost(bid, 2, 2, \"\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// thread not exist\n"},{"name":"z_10_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"First reply of the First post\\n\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.DeletePost(bid, pid, rid, \"\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e First reply of the First post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n//\n// ----------------------------------------------------\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n"},{"name":"z_10_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.DeletePost(bid, pid, pid, \"\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// ----------------------------------------------------\n// thread does not exist with id: 1\n"},{"name":"z_11_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// board 2 not exist\n\tboards.EditPost(2, pid, pid, \"Edited: First Post in (title)\", \"Edited: Body of the first post. (body)\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// board not exist\n"},{"name":"z_11_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// thread 2 not exist\n\tboards.EditPost(bid, 2, pid, \"Edited: First Post in (title)\", \"Edited: Body of the first post. (body)\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// thread not exist\n"},{"name":"z_11_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// post 2 not exist\n\tboards.EditPost(bid, pid, 2, \"Edited: First Post in (title)\", \"Edited: Body of the first post. (body)\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// post not exist\n"},{"name":"z_11_d_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"First reply of the First post\\n\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.EditPost(bid, pid, rid, \"\", \"Edited: First reply of the First post\\n\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e First reply of the First post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n//\n// ----------------------------------------------------\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e Edited: First reply of the First post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n"},{"name":"z_11_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.EditPost(bid, pid, pid, \"Edited: First Post in (title)\", \"Edited: Body of the first post. (body)\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// ----------------------------------------------------\n// # Edited: First Post in (title)\n//\n// Edited: Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n"},{"name":"z_12_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// create a post via registered user\n\tbid1 := boards.CreateBoard(\"test_board1\")\n\tpid := boards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tbid2 := boards.CreateBoard(\"test_board2\")\n\n\t// create a repost via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOrigCaller(test2)\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\n\trid := boards.CreateRepost(bid1, pid, \"\", \"Check this out\", bid2)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board1\"))\n}\n\n// Error:\n// please register, otherwise minimum fee 100000000 is required if anonymous\n"},{"name":"z_12_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid1 := boards.CreateBoard(\"test_board1\")\n\tpid := boards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tbid2 := boards.CreateBoard(\"test_board2\")\n\n\t// create a repost to a non-existing board\n\trid := boards.CreateRepost(5, pid, \"\", \"Check this out\", bid2)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board1\"))\n}\n\n// Error:\n// src board not exist\n"},{"name":"z_12_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid1 := boards.CreateBoard(\"test_board1\")\n\tboards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tbid2 := boards.CreateBoard(\"test_board2\")\n\n\t// create a repost to a non-existing thread\n\trid := boards.CreateRepost(bid1, 5, \"\", \"Check this out\", bid2)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board1\"))\n}\n\n// Error:\n// thread not exist\n"},{"name":"z_12_d_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid1 := boards.CreateBoard(\"test_board1\")\n\tpid := boards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tboards.CreateBoard(\"test_board2\")\n\n\t// create a repost to a non-existing destination board\n\trid := boards.CreateRepost(bid1, pid, \"\", \"Check this out\", 5)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board1\"))\n}\n\n// Error:\n// dst board not exist\n"},{"name":"z_12_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid1 boards.BoardID\n\tbid2 boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid1 = boards.CreateBoard(\"test_board1\")\n\tpid = boards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tbid2 = boards.CreateBoard(\"test_board2\")\n}\n\nfunc main() {\n\trid := boards.CreateRepost(bid1, pid, \"\", \"Check this out\", bid2)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board2\"))\n}\n\n// Output:\n// 1\n// \\[[post](/r/demo/boards$help\u0026func=CreateThread\u0026bid=2)]\n//\n// ----------------------------------------\n// Repost: Check this out\n// ## [First Post (title)](/r/demo/boards:test_board1/1)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board1/1) \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)] (0 replies) (1 reposts)\n"},{"name":"z_1_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar board *boards.Board\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\t_ = boards.CreateBoard(\"test_board_1\")\n\t_ = boards.CreateBoard(\"test_board_2\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"\"))\n}\n\n// Output:\n// These are all the boards of this realm:\n//\n// * [/r/demo/boards:test_board_1](/r/demo/boards:test_board_1)\n// * [/r/demo/boards:test_board_2](/r/demo/boards:test_board_2)\n"},{"name":"z_2_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n"},{"name":"z_3_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n}\n\nfunc main() {\n\trid := boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// 3\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n"},{"name":"z_4_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid := boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n\tprintln(rid)\n}\n\nfunc main() {\n\trid2 := boards.CreateReply(bid, pid, pid, \"Second reply of the second post\")\n\tprintln(rid2)\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// 3\n// 4\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n//\n// \u003e Second reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=4)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=4)]\n\n// Realm:\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/boards\"]\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111\",\n// \"ModTime\": \"123\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"68663c8895d37d479e417c11e21badfe21345c61\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:112\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Post\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"3f34ac77289aa1d5f9a2f8b6d083138325816fb0\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {},\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"94a6665a44bac6ede7f3e3b87173e537b12f9532\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"bc8e5b4e782a0bbc4ac9689681f119beb7b34d59\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"9957eadbc91dd32f33b0d815e041a32dbdea0671\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131]={\n// \"Fields\": [\n// {\n// \"N\": \"AAAAgJSeXbo=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"65536\"\n// }\n// },\n// {\n// \"N\": \"AbSNdvQQIhE=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"1024\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Location\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"336074805fc853987abe6f7fe3ad97a6a6f3077a:2\"\n// },\n// \"Index\": \"182\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"65536\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"1024\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Location\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Board\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:84\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"N\": \"BAAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.PostID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"std.Address\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"Second reply of the second post\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"f91e355bd19240f0f3350a7fa0e6a82b72225916\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"9ee9c4117be283fc51ffcc5ecd65b75ecef5a9dd\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"eb768b0140a5fe95f9c58747f0960d647dacfd42\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.PostID\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.PostID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.BoardID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Time\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"0fd3352422af0a56a77ef2c9e88f479054e3d51f\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Time\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"bed4afa8ffdbbf775451c947fc68b27a345ce32a\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132\"\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\",\n// \"IsEscaped\": true,\n// \"ModTime\": \"0\",\n// \"RefCount\": \"2\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Post\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"c45bbd47a46681a63af973db0ec2180922e4a8ae\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\"\n// }\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120\",\n// \"ModTime\": \"134\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"dc1f011553dc53e7a846049e08cc77fa35ea6a51\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:121\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Post\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"96b86b4585c7f1075d7794180a5581f72733a7ab\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {},\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"32274e1f28fb2b97d67a1262afd362d370de7faa\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"c2cfd6aec36a462f35bf02e5bf4a127aa1bb7ac2\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"5cb875179e86d32c517322af7a323b2a5f3e6cc5\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\"\n// }\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:85]={\n// \"Fields\": [\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.BoardID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"/r/demo/boards:test_board\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"test_board\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"std.Address\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"a416a751c3a45a1e5cba11e737c51340b081e372\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:86\"\n// }\n// },\n// {\n// \"N\": \"BAAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"65536\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Time\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"36299fccbc13f2a84c4629fad4cb940f0bd4b1c6\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:87\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"af6ed0268f99b7f369329094eb6dfaea7812708b\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:88\"\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:85\",\n// \"ModTime\": \"121\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:84\",\n// \"RefCount\": \"1\"\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"9809329dc1ddc5d3556f7a8fa3c2cebcbf65560b\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106\",\n// \"ModTime\": \"121\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:105\",\n// \"RefCount\": \"1\"\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"ceae9a1c4ed28bb51062e6ccdccfad0caafd1c4f\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107\",\n// \"ModTime\": \"121\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:105\",\n// \"RefCount\": \"1\"\n// }\n// }\n// switchrealm[\"gno.land/r/demo/boards\"]\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/boards\"]\n// switchrealm[\"gno.land/r/demo/boards_test\"]\n"},{"name":"z_5_b_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// create board via registered user\n\tbid := boards.CreateBoard(\"test_board\")\n\n\t// create post via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOrigCaller(test2)\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\n\tpid := boards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// please register, otherwise minimum fee 100000000 is required if anonymous\n"},{"name":"z_5_c_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// create board via registered user\n\tbid := boards.CreateBoard(\"test_board\")\n\n\t// create post via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOrigCaller(test2)\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 101000000}}, nil)\n\n\tpid := boards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the first post\")\n\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post (title)\n//\n// Body of the first post. (body)\n// \\- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/r/demo/users:g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e Reply of the first post\n// \u003e \\- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/r/demo/users:g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n"},{"name":"z_5_d_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// create board via registered user\n\tbid := boards.CreateBoard(\"test_board\")\n\tpid := boards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\n\t// create reply via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOrigCaller(test2)\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\tboards.CreateReply(bid, pid, pid, \"Reply of the first post\")\n\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// please register, otherwise minimum fee 100000000 is required if anonymous\n"},{"name":"z_5_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid := boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\trid2 := boards.CreateReply(bid, pid, pid, \"Second reply of the second post\\n\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n//\n// \u003e Second reply of the second post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=4)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=4)]\n"},{"name":"z_6_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tboards.CreateReply(bid, pid, pid, \"Second reply of the second post\\n\")\n\tboards.CreateReply(bid, pid, rid, \"First reply of the first reply\\n\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n// \u003e\n// \u003e \u003e First reply of the first reply\n// \u003e \u003e\n// \u003e \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/5) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=5)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=5)]\n//\n// \u003e Second reply of the second post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=4)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=4)]\n"},{"name":"z_7_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc init() {\n\t// register\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\t// create board and post\n\tbid := boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Output:\n// \\[[post](/r/demo/boards$help\u0026func=CreateThread\u0026bid=1)]\n//\n// ----------------------------------------\n// ## [First Post (title)](/r/demo/boards:test_board/1)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/1) \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)] (0 replies) (0 reposts)\n"},{"name":"z_8_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tboards.CreateReply(bid, pid, pid, \"Second reply of the second post\\n\")\n\trid2 := boards.CreateReply(bid, pid, rid, \"First reply of the first reply\\n\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid)) + \"/\" + strconv.Itoa(int(rid2))))\n}\n\n// Output:\n// _[see thread](/r/demo/boards:test_board/2)_\n//\n// Reply of the second post\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n//\n// _[see all 1 replies](/r/demo/boards:test_board/2/3)_\n//\n// \u003e First reply of the first reply\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/5) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=5)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=5)]\n"},{"name":"z_9_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar dstBoard boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tdstBoard = boards.CreateBoard(\"dst_board\")\n\n\tboards.CreateRepost(0, 0, \"First Post in (title)\", \"Body of the first post. (body)\", dstBoard)\n}\n\nfunc main() {\n}\n\n// Error:\n// src board not exist\n"},{"name":"z_9_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tsrcBoard boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tsrcBoard = boards.CreateBoard(\"first_board\")\n\tpid = boards.CreateThread(srcBoard, \"First Post in (title)\", \"Body of the first post. (body)\")\n\n\tboards.CreateRepost(srcBoard, pid, \"First Post in (title)\", \"Body of the first post. (body)\", 0)\n}\n\nfunc main() {\n}\n\n// Error:\n// dst board not exist\n"},{"name":"z_9_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tfirstBoard boards.BoardID\n\tsecondBoard boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tfirstBoard = boards.CreateBoard(\"first_board\")\n\tsecondBoard = boards.CreateBoard(\"second_board\")\n\tpid = boards.CreateThread(firstBoard, \"First Post in (title)\", \"Body of the first post. (body)\")\n\n\tboards.CreateRepost(firstBoard, pid, \"First Post in (title)\", \"Body of the first post. (body)\", secondBoard)\n}\n\nfunc main() {\n\tprintln(boards.Render(\"second_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:second_board/1/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=2\u0026threadid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=2\u0026threadid=1\u0026postid=1)]\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":"groups","path":"gno.land/p/demo/groups","files":[{"name":"groups.gno","body":"package groups\n\nimport \"gno.land/r/demo/boards\"\n\n// TODO implement something and test.\ntype Group struct {\n\tBoard *boards.Board\n}\n"},{"name":"vote_set.gno","body":"package groups\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/demo/rat\"\n)\n\n//----------------------------------------\n// VoteSet\n\ntype VoteSet interface {\n\t// number of present votes in set.\n\tSize() int\n\t// add or update vote for voter.\n\tSetVote(voter std.Address, value string) error\n\t// count the number of votes for value.\n\tCountVotes(value string) int\n}\n\n//----------------------------------------\n// VoteList\n\ntype Vote struct {\n\tVoter std.Address\n\tValue string\n}\n\ntype VoteList []Vote\n\nfunc NewVoteList() *VoteList {\n\treturn \u0026VoteList{}\n}\n\nfunc (vlist *VoteList) Size() int {\n\treturn len(*vlist)\n}\n\nfunc (vlist *VoteList) SetVote(voter std.Address, value string) error {\n\t// TODO optimize with binary algorithm\n\tfor i, vote := range *vlist {\n\t\tif vote.Voter == voter {\n\t\t\t// update vote\n\t\t\t(*vlist)[i] = Vote{\n\t\t\t\tVoter: voter,\n\t\t\t\tValue: value,\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t}\n\t*vlist = append(*vlist, Vote{\n\t\tVoter: voter,\n\t\tValue: value,\n\t})\n\treturn nil\n}\n\nfunc (vlist *VoteList) CountVotes(target string) int {\n\t// TODO optimize with binary algorithm\n\tvar count int\n\tfor _, vote := range *vlist {\n\t\tif vote.Value == target {\n\t\t\tcount++\n\t\t}\n\t}\n\treturn count\n}\n\n//----------------------------------------\n// Committee\n\ntype Committee struct {\n\tQuorum rat.Rat\n\tThreshold rat.Rat\n\tAddresses std.AddressSet\n}\n\n//----------------------------------------\n// VoteSession\n// NOTE: this seems a bit too formal and\n// complicated vs what might be possible;\n// something simpler, more informal.\n\ntype SessionStatus int\n\nconst (\n\tSessionNew SessionStatus = iota\n\tSessionStarted\n\tSessionCompleted\n\tSessionCanceled\n)\n\ntype VoteSession struct {\n\tName string\n\tCreator std.Address\n\tBody string\n\tStart time.Time\n\tDeadline time.Time\n\tStatus SessionStatus\n\tCommittee *Committee\n\tVotes VoteSet\n\tChoices []string\n\tResult string\n}\n"},{"name":"z_1_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\nimport (\n\t\"gno.land/p/demo/groups\"\n\t\"gno.land/p/demo/testutils\"\n)\n\nvar vset groups.VoteSet\n\nfunc init() {\n\taddr1 := testutils.TestAddress(\"test1\")\n\taddr2 := testutils.TestAddress(\"test2\")\n\tvset = groups.NewVoteList()\n\tvset.SetVote(addr1, \"yes\")\n\tvset.SetVote(addr2, \"yes\")\n}\n\nfunc main() {\n\tprintln(vset.Size())\n\tprintln(\"yes:\", vset.CountVotes(\"yes\"))\n\tprintln(\"no:\", vset.CountVotes(\"no\"))\n}\n\n// Output:\n// 2\n// yes: 2\n// no: 0\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":"uint256","path":"gno.land/p/demo/uint256","files":[{"name":"LICENSE","body":"BSD 3-Clause License\n\nCopyright 2020 uint256 Authors\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this\n list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright notice,\n this list of conditions and the following disclaimer in the documentation\n and/or other materials provided with the distribution.\n\n3. Neither the name of the copyright holder nor the names of its\n contributors may be used to endorse or promote products derived from\n this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"},{"name":"README.md","body":"# Fixed size 256-bit math library\n\nThis is a library specialized at replacing the `big.Int` library for math based on 256-bit types.\n\noriginal repository: [uint256](\u003chttps://github.com/holiman/uint256/tree/master\u003e)\n"},{"name":"arithmetic.gno","body":"// arithmetic provides arithmetic operations for Uint objects.\n// This includes basic binary operations such as addition, subtraction, multiplication, division, and modulo operations\n// as well as overflow checks, and negation. These functions are essential for numeric\n// calculations using 256-bit unsigned integers.\npackage uint256\n\nimport (\n\t\"math/bits\"\n)\n\n// Add sets z to the sum x+y\nfunc (z *Uint) Add(x, y *Uint) *Uint {\n\tvar carry uint64\n\tz.arr[0], carry = bits.Add64(x.arr[0], y.arr[0], 0)\n\tz.arr[1], carry = bits.Add64(x.arr[1], y.arr[1], carry)\n\tz.arr[2], carry = bits.Add64(x.arr[2], y.arr[2], carry)\n\tz.arr[3], _ = bits.Add64(x.arr[3], y.arr[3], carry)\n\treturn z\n}\n\n// AddOverflow sets z to the sum x+y, and returns z and whether overflow occurred\nfunc (z *Uint) AddOverflow(x, y *Uint) (*Uint, bool) {\n\tvar carry uint64\n\tz.arr[0], carry = bits.Add64(x.arr[0], y.arr[0], 0)\n\tz.arr[1], carry = bits.Add64(x.arr[1], y.arr[1], carry)\n\tz.arr[2], carry = bits.Add64(x.arr[2], y.arr[2], carry)\n\tz.arr[3], carry = bits.Add64(x.arr[3], y.arr[3], carry)\n\treturn z, carry != 0\n}\n\n// Sub sets z to the difference x-y\nfunc (z *Uint) Sub(x, y *Uint) *Uint {\n\tvar carry uint64\n\tz.arr[0], carry = bits.Sub64(x.arr[0], y.arr[0], 0)\n\tz.arr[1], carry = bits.Sub64(x.arr[1], y.arr[1], carry)\n\tz.arr[2], carry = bits.Sub64(x.arr[2], y.arr[2], carry)\n\tz.arr[3], _ = bits.Sub64(x.arr[3], y.arr[3], carry)\n\treturn z\n}\n\n// SubOverflow sets z to the difference x-y and returns z and true if the operation underflowed\nfunc (z *Uint) SubOverflow(x, y *Uint) (*Uint, bool) {\n\tvar carry uint64\n\tz.arr[0], carry = bits.Sub64(x.arr[0], y.arr[0], 0)\n\tz.arr[1], carry = bits.Sub64(x.arr[1], y.arr[1], carry)\n\tz.arr[2], carry = bits.Sub64(x.arr[2], y.arr[2], carry)\n\tz.arr[3], carry = bits.Sub64(x.arr[3], y.arr[3], carry)\n\treturn z, carry != 0\n}\n\n// Neg returns -x mod 2^256.\nfunc (z *Uint) Neg(x *Uint) *Uint {\n\treturn z.Sub(new(Uint), x)\n}\n\n// commented out for possible overflow\n// Mul sets z to the product x*y\nfunc (z *Uint) Mul(x, y *Uint) *Uint {\n\tvar (\n\t\tres Uint\n\t\tcarry uint64\n\t\tres1, res2, res3 uint64\n\t)\n\n\tcarry, res.arr[0] = bits.Mul64(x.arr[0], y.arr[0])\n\tcarry, res1 = umulHop(carry, x.arr[1], y.arr[0])\n\tcarry, res2 = umulHop(carry, x.arr[2], y.arr[0])\n\tres3 = x.arr[3]*y.arr[0] + carry\n\n\tcarry, res.arr[1] = umulHop(res1, x.arr[0], y.arr[1])\n\tcarry, res2 = umulStep(res2, x.arr[1], y.arr[1], carry)\n\tres3 = res3 + x.arr[2]*y.arr[1] + carry\n\n\tcarry, res.arr[2] = umulHop(res2, x.arr[0], y.arr[2])\n\tres3 = res3 + x.arr[1]*y.arr[2] + carry\n\n\tres.arr[3] = res3 + x.arr[0]*y.arr[3]\n\n\treturn z.Set(\u0026res)\n}\n\n// MulOverflow sets z to the product x*y, and returns z and whether overflow occurred\nfunc (z *Uint) MulOverflow(x, y *Uint) (*Uint, bool) {\n\tp := umul(x, y)\n\tcopy(z.arr[:], p[:4])\n\treturn z, (p[4] | p[5] | p[6] | p[7]) != 0\n}\n\n// commented out for possible overflow\n// Div sets z to the quotient x/y for returns z.\n// If y == 0, z is set to 0\nfunc (z *Uint) Div(x, y *Uint) *Uint {\n\tif y.IsZero() || y.Gt(x) {\n\t\treturn z.Clear()\n\t}\n\tif x.Eq(y) {\n\t\treturn z.SetOne()\n\t}\n\t// Shortcut some cases\n\tif x.IsUint64() {\n\t\treturn z.SetUint64(x.Uint64() / y.Uint64())\n\t}\n\n\t// At this point, we know\n\t// x/y ; x \u003e y \u003e 0\n\n\tvar quot Uint\n\tudivrem(quot.arr[:], x.arr[:], y)\n\treturn z.Set(\u0026quot)\n}\n\n// MulMod calculates the modulo-m multiplication of x and y and\n// returns z.\n// If m == 0, z is set to 0 (OBS: differs from the big.Int)\nfunc (z *Uint) MulMod(x, y, m *Uint) *Uint {\n\tif x.IsZero() || y.IsZero() || m.IsZero() {\n\t\treturn z.Clear()\n\t}\n\tp := umul(x, y)\n\n\tif m.arr[3] != 0 {\n\t\tmu := Reciprocal(m)\n\t\tr := reduce4(p, m, mu)\n\t\treturn z.Set(\u0026r)\n\t}\n\n\tvar (\n\t\tpl Uint\n\t\tph Uint\n\t)\n\n\tpl = Uint{arr: [4]uint64{p[0], p[1], p[2], p[3]}}\n\tph = Uint{arr: [4]uint64{p[4], p[5], p[6], p[7]}}\n\n\t// If the multiplication is within 256 bits use Mod().\n\tif ph.IsZero() {\n\t\treturn z.Mod(\u0026pl, m)\n\t}\n\n\tvar quot [8]uint64\n\trem := udivrem(quot[:], p[:], m)\n\treturn z.Set(\u0026rem)\n}\n\n// Mod sets z to the modulus x%y for y != 0 and returns z.\n// If y == 0, z is set to 0 (OBS: differs from the big.Uint)\nfunc (z *Uint) Mod(x, y *Uint) *Uint {\n\tif x.IsZero() || y.IsZero() {\n\t\treturn z.Clear()\n\t}\n\tswitch x.Cmp(y) {\n\tcase -1:\n\t\t// x \u003c y\n\t\tcopy(z.arr[:], x.arr[:])\n\t\treturn z\n\tcase 0:\n\t\t// x == y\n\t\treturn z.Clear() // They are equal\n\t}\n\n\t// At this point:\n\t// x != 0\n\t// y != 0\n\t// x \u003e y\n\n\t// Shortcut trivial case\n\tif x.IsUint64() {\n\t\treturn z.SetUint64(x.Uint64() % y.Uint64())\n\t}\n\n\tvar quot Uint\n\t*z = udivrem(quot.arr[:], x.arr[:], y)\n\treturn z\n}\n\n// DivMod sets z to the quotient x div y and m to the modulus x mod y and returns the pair (z, m) for y != 0.\n// If y == 0, both z and m are set to 0 (OBS: differs from the big.Int)\nfunc (z *Uint) DivMod(x, y, m *Uint) (*Uint, *Uint) {\n\tif y.IsZero() {\n\t\treturn z.Clear(), m.Clear()\n\t}\n\tvar quot Uint\n\t*m = udivrem(quot.arr[:], x.arr[:], y)\n\t*z = quot\n\treturn z, m\n}\n\n// Exp sets z = base**exponent mod 2**256, and returns z.\nfunc (z *Uint) Exp(base, exponent *Uint) *Uint {\n\tres := Uint{arr: [4]uint64{1, 0, 0, 0}}\n\tmultiplier := *base\n\texpBitLen := exponent.BitLen()\n\n\tcurBit := 0\n\tword := exponent.arr[0]\n\tfor ; curBit \u003c expBitLen \u0026\u0026 curBit \u003c 64; curBit++ {\n\t\tif word\u00261 == 1 {\n\t\t\tres.Mul(\u0026res, \u0026multiplier)\n\t\t}\n\t\tmultiplier.squared()\n\t\tword \u003e\u003e= 1\n\t}\n\n\tword = exponent.arr[1]\n\tfor ; curBit \u003c expBitLen \u0026\u0026 curBit \u003c 128; curBit++ {\n\t\tif word\u00261 == 1 {\n\t\t\tres.Mul(\u0026res, \u0026multiplier)\n\t\t}\n\t\tmultiplier.squared()\n\t\tword \u003e\u003e= 1\n\t}\n\n\tword = exponent.arr[2]\n\tfor ; curBit \u003c expBitLen \u0026\u0026 curBit \u003c 192; curBit++ {\n\t\tif word\u00261 == 1 {\n\t\t\tres.Mul(\u0026res, \u0026multiplier)\n\t\t}\n\t\tmultiplier.squared()\n\t\tword \u003e\u003e= 1\n\t}\n\n\tword = exponent.arr[3]\n\tfor ; curBit \u003c expBitLen \u0026\u0026 curBit \u003c 256; curBit++ {\n\t\tif word\u00261 == 1 {\n\t\t\tres.Mul(\u0026res, \u0026multiplier)\n\t\t}\n\t\tmultiplier.squared()\n\t\tword \u003e\u003e= 1\n\t}\n\treturn z.Set(\u0026res)\n}\n\nfunc (z *Uint) squared() {\n\tvar (\n\t\tres Uint\n\t\tcarry0, carry1, carry2 uint64\n\t\tres1, res2 uint64\n\t)\n\n\tcarry0, res.arr[0] = bits.Mul64(z.arr[0], z.arr[0])\n\tcarry0, res1 = umulHop(carry0, z.arr[0], z.arr[1])\n\tcarry0, res2 = umulHop(carry0, z.arr[0], z.arr[2])\n\n\tcarry1, res.arr[1] = umulHop(res1, z.arr[0], z.arr[1])\n\tcarry1, res2 = umulStep(res2, z.arr[1], z.arr[1], carry1)\n\n\tcarry2, res.arr[2] = umulHop(res2, z.arr[0], z.arr[2])\n\n\tres.arr[3] = 2*(z.arr[0]*z.arr[3]+z.arr[1]*z.arr[2]) + carry0 + carry1 + carry2\n\n\tz.Set(\u0026res)\n}\n\n// udivrem divides u by d and produces both quotient and remainder.\n// The quotient is stored in provided quot - len(u)-len(d)+1 words.\n// It loosely follows the Knuth's division algorithm (sometimes referenced as \"schoolbook\" division) using 64-bit words.\n// See Knuth, Volume 2, section 4.3.1, Algorithm D.\nfunc udivrem(quot, u []uint64, d *Uint) (rem Uint) {\n\tvar dLen int\n\tfor i := len(d.arr) - 1; i \u003e= 0; i-- {\n\t\tif d.arr[i] != 0 {\n\t\t\tdLen = i + 1\n\t\t\tbreak\n\t\t}\n\t}\n\n\tshift := uint(bits.LeadingZeros64(d.arr[dLen-1]))\n\n\tvar dnStorage Uint\n\tdn := dnStorage.arr[:dLen]\n\tfor i := dLen - 1; i \u003e 0; i-- {\n\t\tdn[i] = (d.arr[i] \u003c\u003c shift) | (d.arr[i-1] \u003e\u003e (64 - shift))\n\t}\n\tdn[0] = d.arr[0] \u003c\u003c shift\n\n\tvar uLen int\n\tfor i := len(u) - 1; i \u003e= 0; i-- {\n\t\tif u[i] != 0 {\n\t\t\tuLen = i + 1\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif uLen \u003c dLen {\n\t\tcopy(rem.arr[:], u)\n\t\treturn rem\n\t}\n\n\tvar unStorage [9]uint64\n\tun := unStorage[:uLen+1]\n\tun[uLen] = u[uLen-1] \u003e\u003e (64 - shift)\n\tfor i := uLen - 1; i \u003e 0; i-- {\n\t\tun[i] = (u[i] \u003c\u003c shift) | (u[i-1] \u003e\u003e (64 - shift))\n\t}\n\tun[0] = u[0] \u003c\u003c shift\n\n\t// TODO: Skip the highest word of numerator if not significant.\n\n\tif dLen == 1 {\n\t\tr := udivremBy1(quot, un, dn[0])\n\t\trem.SetUint64(r \u003e\u003e shift)\n\t\treturn rem\n\t}\n\n\tudivremKnuth(quot, un, dn)\n\n\tfor i := 0; i \u003c dLen-1; i++ {\n\t\trem.arr[i] = (un[i] \u003e\u003e shift) | (un[i+1] \u003c\u003c (64 - shift))\n\t}\n\trem.arr[dLen-1] = un[dLen-1] \u003e\u003e shift\n\n\treturn rem\n}\n\n// umul computes full 256 x 256 -\u003e 512 multiplication.\nfunc umul(x, y *Uint) [8]uint64 {\n\tvar (\n\t\tres [8]uint64\n\t\tcarry, carry4, carry5, carry6 uint64\n\t\tres1, res2, res3, res4, res5 uint64\n\t)\n\n\tcarry, res[0] = bits.Mul64(x.arr[0], y.arr[0])\n\tcarry, res1 = umulHop(carry, x.arr[1], y.arr[0])\n\tcarry, res2 = umulHop(carry, x.arr[2], y.arr[0])\n\tcarry4, res3 = umulHop(carry, x.arr[3], y.arr[0])\n\n\tcarry, res[1] = umulHop(res1, x.arr[0], y.arr[1])\n\tcarry, res2 = umulStep(res2, x.arr[1], y.arr[1], carry)\n\tcarry, res3 = umulStep(res3, x.arr[2], y.arr[1], carry)\n\tcarry5, res4 = umulStep(carry4, x.arr[3], y.arr[1], carry)\n\n\tcarry, res[2] = umulHop(res2, x.arr[0], y.arr[2])\n\tcarry, res3 = umulStep(res3, x.arr[1], y.arr[2], carry)\n\tcarry, res4 = umulStep(res4, x.arr[2], y.arr[2], carry)\n\tcarry6, res5 = umulStep(carry5, x.arr[3], y.arr[2], carry)\n\n\tcarry, res[3] = umulHop(res3, x.arr[0], y.arr[3])\n\tcarry, res[4] = umulStep(res4, x.arr[1], y.arr[3], carry)\n\tcarry, res[5] = umulStep(res5, x.arr[2], y.arr[3], carry)\n\tres[7], res[6] = umulStep(carry6, x.arr[3], y.arr[3], carry)\n\n\treturn res\n}\n\n// umulStep computes (hi * 2^64 + lo) = z + (x * y) + carry.\nfunc umulStep(z, x, y, carry uint64) (hi, lo uint64) {\n\thi, lo = bits.Mul64(x, y)\n\tlo, carry = bits.Add64(lo, carry, 0)\n\thi, _ = bits.Add64(hi, 0, carry)\n\tlo, carry = bits.Add64(lo, z, 0)\n\thi, _ = bits.Add64(hi, 0, carry)\n\treturn hi, lo\n}\n\n// umulHop computes (hi * 2^64 + lo) = z + (x * y)\nfunc umulHop(z, x, y uint64) (hi, lo uint64) {\n\thi, lo = bits.Mul64(x, y)\n\tlo, carry := bits.Add64(lo, z, 0)\n\thi, _ = bits.Add64(hi, 0, carry)\n\treturn hi, lo\n}\n\n// udivremBy1 divides u by single normalized word d and produces both quotient and remainder.\n// The quotient is stored in provided quot.\nfunc udivremBy1(quot, u []uint64, d uint64) (rem uint64) {\n\treciprocal := reciprocal2by1(d)\n\trem = u[len(u)-1] // Set the top word as remainder.\n\tfor j := len(u) - 2; j \u003e= 0; j-- {\n\t\tquot[j], rem = udivrem2by1(rem, u[j], d, reciprocal)\n\t}\n\treturn rem\n}\n\n// udivremKnuth implements the division of u by normalized multiple word d from the Knuth's division algorithm.\n// The quotient is stored in provided quot - len(u)-len(d) words.\n// Updates u to contain the remainder - len(d) words.\nfunc udivremKnuth(quot, u, d []uint64) {\n\tdh := d[len(d)-1]\n\tdl := d[len(d)-2]\n\treciprocal := reciprocal2by1(dh)\n\n\tfor j := len(u) - len(d) - 1; j \u003e= 0; j-- {\n\t\tu2 := u[j+len(d)]\n\t\tu1 := u[j+len(d)-1]\n\t\tu0 := u[j+len(d)-2]\n\n\t\tvar qhat, rhat uint64\n\t\tif u2 \u003e= dh { // Division overflows.\n\t\t\tqhat = ^uint64(0)\n\t\t\t// TODO: Add \"qhat one to big\" adjustment (not needed for correctness, but helps avoiding \"add back\" case).\n\t\t} else {\n\t\t\tqhat, rhat = udivrem2by1(u2, u1, dh, reciprocal)\n\t\t\tph, pl := bits.Mul64(qhat, dl)\n\t\t\tif ph \u003e rhat || (ph == rhat \u0026\u0026 pl \u003e u0) {\n\t\t\t\tqhat--\n\t\t\t\t// TODO: Add \"qhat one to big\" adjustment (not needed for correctness, but helps avoiding \"add back\" case).\n\t\t\t}\n\t\t}\n\n\t\t// Multiply and subtract.\n\t\tborrow := subMulTo(u[j:], d, qhat)\n\t\tu[j+len(d)] = u2 - borrow\n\t\tif u2 \u003c borrow { // Too much subtracted, add back.\n\t\t\tqhat--\n\t\t\tu[j+len(d)] += addTo(u[j:], d)\n\t\t}\n\n\t\tquot[j] = qhat // Store quotient digit.\n\t}\n}\n\n// isBitSet returns true if bit n-th is set, where n = 0 is LSB.\n// The n must be \u003c= 255.\nfunc (z *Uint) isBitSet(n uint) bool {\n\treturn (z.arr[n/64] \u0026 (1 \u003c\u003c (n % 64))) != 0\n}\n\n// addTo computes x += y.\n// Requires len(x) \u003e= len(y).\nfunc addTo(x, y []uint64) uint64 {\n\tvar carry uint64\n\tfor i := 0; i \u003c len(y); i++ {\n\t\tx[i], carry = bits.Add64(x[i], y[i], carry)\n\t}\n\treturn carry\n}\n\n// subMulTo computes x -= y * multiplier.\n// Requires len(x) \u003e= len(y).\nfunc subMulTo(x, y []uint64, multiplier uint64) uint64 {\n\tvar borrow uint64\n\tfor i := 0; i \u003c len(y); i++ {\n\t\ts, carry1 := bits.Sub64(x[i], borrow, 0)\n\t\tph, pl := bits.Mul64(y[i], multiplier)\n\t\tt, carry2 := bits.Sub64(s, pl, 0)\n\t\tx[i] = t\n\t\tborrow = ph + carry1 + carry2\n\t}\n\treturn borrow\n}\n\n// reciprocal2by1 computes \u003c^d, ^0\u003e / d.\nfunc reciprocal2by1(d uint64) uint64 {\n\treciprocal, _ := bits.Div64(^d, ^uint64(0), d)\n\treturn reciprocal\n}\n\n// udivrem2by1 divides \u003cuh, ul\u003e / d and produces both quotient and remainder.\n// It uses the provided d's reciprocal.\n// Implementation ported from https://github.com/chfast/intx and is based on\n// \"Improved division by invariant integers\", Algorithm 4.\nfunc udivrem2by1(uh, ul, d, reciprocal uint64) (quot, rem uint64) {\n\tqh, ql := bits.Mul64(reciprocal, uh)\n\tql, carry := bits.Add64(ql, ul, 0)\n\tqh, _ = bits.Add64(qh, uh, carry)\n\tqh++\n\n\tr := ul - qh*d\n\n\tif r \u003e ql {\n\t\tqh--\n\t\tr += d\n\t}\n\n\tif r \u003e= d {\n\t\tqh++\n\t\tr -= d\n\t}\n\n\treturn qh, r\n}\n"},{"name":"arithmetic_test.gno","body":"package uint256\n\nimport (\n\t\"testing\"\n)\n\ntype binOp2Test struct {\n\tx, y, want string\n}\n\nfunc TestAdd(t *testing.T) {\n\ttests := []binOp2Test{\n\t\t{\"0\", \"1\", \"1\"},\n\t\t{\"1\", \"0\", \"1\"},\n\t\t{\"1\", \"1\", \"2\"},\n\t\t{\"1\", \"3\", \"4\"},\n\t\t{\"10\", \"10\", \"20\"},\n\t\t{\"18446744073709551615\", \"18446744073709551615\", \"36893488147419103230\"}, // uint64 overflow\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\ty := MustFromDecimal(tt.y)\n\n\t\twant := MustFromDecimal(tt.want)\n\t\tgot := new(Uint).Add(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Add(%s, %s) = %v, want %v\", tt.x, tt.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestAddOverflow(t *testing.T) {\n\ttests := []struct {\n\t\tx, y string\n\t\twant string\n\t\toverflow bool\n\t}{\n\t\t{\"0\", \"1\", \"1\", false},\n\t\t{\"1\", \"0\", \"1\", false},\n\t\t{\"1\", \"1\", \"2\", false},\n\t\t{\"10\", \"10\", \"20\", false},\n\t\t{\"18446744073709551615\", \"18446744073709551615\", \"36893488147419103230\", false}, // uint64 overflow, but not Uint256 overflow\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"1\", \"0\", true}, // 2^256 - 1 + 1, should overflow\n\t\t{\"57896044618658097711785492504343953926634992332820282019728792003956564819967\", \"57896044618658097711785492504343953926634992332820282019728792003956564819968\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\", false}, // (2^255 - 1) + 2^255, no overflow\n\t\t{\"57896044618658097711785492504343953926634992332820282019728792003956564819967\", \"57896044618658097711785492504343953926634992332820282019728792003956564819969\", \"0\", true}, // (2^255 - 1) + (2^255 + 1), should overflow\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\ty := MustFromDecimal(tt.y)\n\t\twant, _ := FromDecimal(tt.want)\n\n\t\tgot, overflow := new(Uint).AddOverflow(x, y)\n\n\t\tif got.Cmp(want) != 0 || overflow != tt.overflow {\n\t\t\tt.Errorf(\"AddOverflow(%s, %s) = (%s, %v), want (%s, %v)\",\n\t\t\t\ttt.x, tt.y, got.ToString(), overflow, tt.want, tt.overflow)\n\t\t}\n\t}\n}\n\nfunc TestSub(t *testing.T) {\n\ttests := []binOp2Test{\n\t\t{\"1\", \"0\", \"1\"},\n\t\t{\"1\", \"1\", \"0\"},\n\t\t{\"10\", \"10\", \"0\"},\n\t\t{\"31337\", \"1337\", \"30000\"},\n\t\t{\"2\", \"3\", twoPow256Sub1}, // underflow\n\t}\n\n\tfor _, tc := range tests {\n\t\tx := MustFromDecimal(tc.x)\n\t\ty := MustFromDecimal(tc.y)\n\n\t\twant := MustFromDecimal(tc.want)\n\n\t\tgot := new(Uint).Sub(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\n\t\t\t\t\"Sub(%s, %s) = %v, want %v\",\n\t\t\t\ttc.x, tc.y, got.ToString(), want.ToString(),\n\t\t\t)\n\t\t}\n\t}\n}\n\nfunc TestSubOverflow(t *testing.T) {\n\ttests := []struct {\n\t\tx, y string\n\t\twant string\n\t\toverflow bool\n\t}{\n\t\t{\"1\", \"0\", \"1\", false},\n\t\t{\"1\", \"1\", \"0\", false},\n\t\t{\"10\", \"10\", \"0\", false},\n\t\t{\"31337\", \"1337\", \"30000\", false},\n\t\t{\"0\", \"1\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\", true}, // 0 - 1, should underflow\n\t\t{\"57896044618658097711785492504343953926634992332820282019728792003956564819968\", \"1\", \"57896044618658097711785492504343953926634992332820282019728792003956564819967\", false}, // 2^255 - 1, no underflow\n\t\t{\"57896044618658097711785492504343953926634992332820282019728792003956564819968\", \"57896044618658097711785492504343953926634992332820282019728792003956564819969\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\", true}, // 2^255 - (2^255 + 1), should underflow\n\t}\n\n\tfor _, tc := range tests {\n\t\tx := MustFromDecimal(tc.x)\n\t\ty := MustFromDecimal(tc.y)\n\t\twant := MustFromDecimal(tc.want)\n\n\t\tgot, overflow := new(Uint).SubOverflow(x, y)\n\n\t\tif got.Cmp(want) != 0 || overflow != tc.overflow {\n\t\t\tt.Errorf(\n\t\t\t\t\"SubOverflow(%s, %s) = (%s, %v), want (%s, %v)\",\n\t\t\t\ttc.x, tc.y, got.ToString(), overflow, tc.want, tc.overflow,\n\t\t\t)\n\t\t}\n\t}\n}\n\nfunc TestMul(t *testing.T) {\n\ttests := []binOp2Test{\n\t\t{\"1\", \"0\", \"0\"},\n\t\t{\"1\", \"1\", \"1\"},\n\t\t{\"10\", \"10\", \"100\"},\n\t\t{\"18446744073709551615\", \"2\", \"36893488147419103230\"}, // uint64 overflow\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\ty := MustFromDecimal(tt.y)\n\t\twant := MustFromDecimal(tt.want)\n\t\tgot := new(Uint).Mul(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Mul(%s, %s) = %v, want %v\", tt.x, tt.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestMulOverflow(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty string\n\t\twantZ string\n\t\twantOver bool\n\t}{\n\t\t{\"0x1\", \"0x1\", \"0x1\", false},\n\t\t{\"0x0\", \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0x0\", false},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0x2\", \"0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe\", true},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0x1\", true},\n\t\t{\"0x8000000000000000000000000000000000000000000000000000000000000000\", \"0x2\", \"0x0\", true},\n\t\t{\"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0x2\", \"0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe\", false},\n\t\t{\"0x100000000000000000\", \"0x100000000000000000\", \"0x10000000000000000000000000000000000\", false},\n\t\t{\"0x10000000000000000000000000000000\", \"0x10000000000000000000000000000000\", \"0x100000000000000000000000000000000000000000000000000000000000000\", false},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromHex(tt.x)\n\t\ty := MustFromHex(tt.y)\n\t\twantZ := MustFromHex(tt.wantZ)\n\n\t\tgotZ, gotOver := new(Uint).MulOverflow(x, y)\n\n\t\tif gotZ.Neq(wantZ) {\n\t\t\tt.Errorf(\n\t\t\t\t\"MulOverflow(%s, %s) = %s, want %s\",\n\t\t\t\ttt.x, tt.y, gotZ.ToString(), wantZ.ToString(),\n\t\t\t)\n\t\t}\n\t\tif gotOver != tt.wantOver {\n\t\t\tt.Errorf(\"MulOverflow(%s, %s) = %v, want %v\", tt.x, tt.y, gotOver, tt.wantOver)\n\t\t}\n\t}\n}\n\nfunc TestDiv(t *testing.T) {\n\ttests := []binOp2Test{\n\t\t{\"31337\", \"3\", \"10445\"},\n\t\t{\"31337\", \"0\", \"0\"},\n\t\t{\"0\", \"31337\", \"0\"},\n\t\t{\"1\", \"1\", \"1\"},\n\t\t{\"1000000000000000000\", \"3\", \"333333333333333333\"},\n\t\t{twoPow256Sub1, \"2\", \"57896044618658097711785492504343953926634992332820282019728792003956564819967\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\ty := MustFromDecimal(tt.y)\n\t\twant := MustFromDecimal(tt.want)\n\n\t\tgot := new(Uint).Div(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Div(%s, %s) = %v, want %v\", tt.x, tt.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestMod(t *testing.T) {\n\ttests := []binOp2Test{\n\t\t{\"31337\", \"3\", \"2\"},\n\t\t{\"31337\", \"0\", \"0\"},\n\t\t{\"0\", \"31337\", \"0\"},\n\t\t{\"2\", \"31337\", \"2\"},\n\t\t{\"1\", \"1\", \"0\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"2\", \"1\"}, // 2^256 - 1 mod 2\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"3\", \"0\"}, // 2^256 - 1 mod 3\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"57896044618658097711785492504343953926634992332820282019728792003956564819968\", \"57896044618658097711785492504343953926634992332820282019728792003956564819967\"}, // 2^256 - 1 mod 2^255\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\ty := MustFromDecimal(tt.y)\n\t\twant := MustFromDecimal(tt.want)\n\n\t\tgot := new(Uint).Mod(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Mod(%s, %s) = %v, want %v\", tt.x, tt.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestMulMod(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty string\n\t\tm string\n\t\twant string\n\t}{\n\t\t{\"0x1\", \"0x1\", \"0x2\", \"0x1\"},\n\t\t{\"0x10\", \"0x10\", \"0x7\", \"0x4\"},\n\t\t{\"0x100\", \"0x100\", \"0x17\", \"0x9\"},\n\t\t{\"0x31337\", \"0x31337\", \"0x31338\", \"0x1\"},\n\t\t{\"0x0\", \"0x31337\", \"0x31338\", \"0x0\"},\n\t\t{\"0x31337\", \"0x0\", \"0x31338\", \"0x0\"},\n\t\t{\"0x2\", \"0x3\", \"0x5\", \"0x1\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0x0\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe\", \"0x1\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0xffffffffffffffffffffffffffffffff\", \"0x0\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromHex(tt.x)\n\t\ty := MustFromHex(tt.y)\n\t\tm := MustFromHex(tt.m)\n\t\twant := MustFromHex(tt.want)\n\n\t\tgot := new(Uint).MulMod(x, y, m)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\n\t\t\t\t\"MulMod(%s, %s, %s) = %s, want %s\",\n\t\t\t\ttt.x, tt.y, tt.m, got.ToString(), want.ToString(),\n\t\t\t)\n\t\t}\n\t}\n}\n\nfunc TestDivMod(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty string\n\t\twantDiv string\n\t\twantMod string\n\t}{\n\t\t{\"1\", \"1\", \"1\", \"0\"},\n\t\t{\"10\", \"10\", \"1\", \"0\"},\n\t\t{\"100\", \"10\", \"10\", \"0\"},\n\t\t{\"31337\", \"3\", \"10445\", \"2\"},\n\t\t{\"31337\", \"0\", \"0\", \"0\"},\n\t\t{\"0\", \"31337\", \"0\", \"0\"},\n\t\t{\"2\", \"31337\", \"0\", \"2\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\ty := MustFromDecimal(tt.y)\n\t\twantDiv := MustFromDecimal(tt.wantDiv)\n\t\twantMod := MustFromDecimal(tt.wantMod)\n\n\t\tgotDiv := new(Uint)\n\t\tgotMod := new(Uint)\n\t\tgotDiv.DivMod(x, y, gotMod)\n\n\t\tfor i := range gotDiv.arr {\n\t\t\tif gotDiv.arr[i] != wantDiv.arr[i] {\n\t\t\t\tt.Errorf(\"DivMod(%s, %s) got Div %v, want Div %v\", tt.x, tt.y, gotDiv, wantDiv)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfor i := range gotMod.arr {\n\t\t\tif gotMod.arr[i] != wantMod.arr[i] {\n\t\t\t\tt.Errorf(\"DivMod(%s, %s) got Mod %v, want Mod %v\", tt.x, tt.y, gotMod, wantMod)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestNeg(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant string\n\t}{\n\t\t{\"31337\", \"115792089237316195423570985008687907853269984665640564039457584007913129608599\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129608599\", \"31337\"},\n\t\t{\"0\", \"0\"},\n\t\t{\"2\", \"115792089237316195423570985008687907853269984665640564039457584007913129639934\"},\n\t\t{\"1\", twoPow256Sub1},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\twant := MustFromDecimal(tt.want)\n\n\t\tgot := new(Uint).Neg(x)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Neg(%s) = %v, want %v\", tt.x, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestExp(t *testing.T) {\n\ttests := []binOp2Test{\n\t\t{\"31337\", \"3\", \"30773171189753\"},\n\t\t{\"31337\", \"0\", \"1\"},\n\t\t{\"0\", \"31337\", \"0\"},\n\t\t{\"1\", \"1\", \"1\"},\n\t\t{\"2\", \"3\", \"8\"},\n\t\t{\"2\", \"64\", \"18446744073709551616\"},\n\t\t{\"2\", \"128\", \"340282366920938463463374607431768211456\"},\n\t\t{\"2\", \"255\", \"57896044618658097711785492504343953926634992332820282019728792003956564819968\"},\n\t\t{\"2\", \"256\", \"0\"}, // overflow\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\ty := MustFromDecimal(tt.y)\n\t\twant := MustFromDecimal(tt.want)\n\n\t\tgot := new(Uint).Exp(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\n\t\t\t\t\"Exp(%s, %s) = %v, want %v\",\n\t\t\t\ttt.x, tt.y, got.ToString(), want.ToString(),\n\t\t\t)\n\t\t}\n\t}\n}\n\nfunc TestExp_LargeExponent(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tbase string\n\t\texponent string\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname: \"2^129\",\n\t\t\tbase: \"2\",\n\t\t\texponent: \"680564733841876926926749214863536422912\",\n\t\t\texpected: \"0\",\n\t\t},\n\t\t{\n\t\t\tname: \"2^193\",\n\t\t\tbase: \"2\",\n\t\t\texponent: \"12379400392853802746563808384000000000000000000\",\n\t\t\texpected: \"0\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tbase := MustFromDecimal(tt.base)\n\t\t\texponent := MustFromDecimal(tt.exponent)\n\t\t\texpected := MustFromDecimal(tt.expected)\n\n\t\t\tresult := new(Uint).Exp(base, exponent)\n\n\t\t\tif result.Neq(expected) {\n\t\t\t\tt.Errorf(\n\t\t\t\t\t\"Test %s failed. Expected %s, got %s\",\n\t\t\t\t\ttt.name, expected.ToString(), result.ToString(),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n}\n"},{"name":"bits_table.gno","body":"// Copyright 2017 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n// Code generated by go run make_tables.go. DO NOT EDIT.\n\npackage uint256\n\nconst ntz8tab = \"\" +\n\t\"\\x08\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x05\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x06\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x05\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x07\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x05\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x06\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x05\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\"\n\nconst pop8tab = \"\" +\n\t\"\\x00\\x01\\x01\\x02\\x01\\x02\\x02\\x03\\x01\\x02\\x02\\x03\\x02\\x03\\x03\\x04\" +\n\t\"\\x01\\x02\\x02\\x03\\x02\\x03\\x03\\x04\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\" +\n\t\"\\x01\\x02\\x02\\x03\\x02\\x03\\x03\\x04\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\" +\n\t\"\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\" +\n\t\"\\x01\\x02\\x02\\x03\\x02\\x03\\x03\\x04\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\" +\n\t\"\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\" +\n\t\"\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\" +\n\t\"\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\\x04\\x05\\x05\\x06\\x05\\x06\\x06\\x07\" +\n\t\"\\x01\\x02\\x02\\x03\\x02\\x03\\x03\\x04\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\" +\n\t\"\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\" +\n\t\"\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\" +\n\t\"\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\\x04\\x05\\x05\\x06\\x05\\x06\\x06\\x07\" +\n\t\"\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\" +\n\t\"\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\\x04\\x05\\x05\\x06\\x05\\x06\\x06\\x07\" +\n\t\"\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\\x04\\x05\\x05\\x06\\x05\\x06\\x06\\x07\" +\n\t\"\\x04\\x05\\x05\\x06\\x05\\x06\\x06\\x07\\x05\\x06\\x06\\x07\\x06\\x07\\x07\\x08\"\n\nconst rev8tab = \"\" +\n\t\"\\x00\\x80\\x40\\xc0\\x20\\xa0\\x60\\xe0\\x10\\x90\\x50\\xd0\\x30\\xb0\\x70\\xf0\" +\n\t\"\\x08\\x88\\x48\\xc8\\x28\\xa8\\x68\\xe8\\x18\\x98\\x58\\xd8\\x38\\xb8\\x78\\xf8\" +\n\t\"\\x04\\x84\\x44\\xc4\\x24\\xa4\\x64\\xe4\\x14\\x94\\x54\\xd4\\x34\\xb4\\x74\\xf4\" +\n\t\"\\x0c\\x8c\\x4c\\xcc\\x2c\\xac\\x6c\\xec\\x1c\\x9c\\x5c\\xdc\\x3c\\xbc\\x7c\\xfc\" +\n\t\"\\x02\\x82\\x42\\xc2\\x22\\xa2\\x62\\xe2\\x12\\x92\\x52\\xd2\\x32\\xb2\\x72\\xf2\" +\n\t\"\\x0a\\x8a\\x4a\\xca\\x2a\\xaa\\x6a\\xea\\x1a\\x9a\\x5a\\xda\\x3a\\xba\\x7a\\xfa\" +\n\t\"\\x06\\x86\\x46\\xc6\\x26\\xa6\\x66\\xe6\\x16\\x96\\x56\\xd6\\x36\\xb6\\x76\\xf6\" +\n\t\"\\x0e\\x8e\\x4e\\xce\\x2e\\xae\\x6e\\xee\\x1e\\x9e\\x5e\\xde\\x3e\\xbe\\x7e\\xfe\" +\n\t\"\\x01\\x81\\x41\\xc1\\x21\\xa1\\x61\\xe1\\x11\\x91\\x51\\xd1\\x31\\xb1\\x71\\xf1\" +\n\t\"\\x09\\x89\\x49\\xc9\\x29\\xa9\\x69\\xe9\\x19\\x99\\x59\\xd9\\x39\\xb9\\x79\\xf9\" +\n\t\"\\x05\\x85\\x45\\xc5\\x25\\xa5\\x65\\xe5\\x15\\x95\\x55\\xd5\\x35\\xb5\\x75\\xf5\" +\n\t\"\\x0d\\x8d\\x4d\\xcd\\x2d\\xad\\x6d\\xed\\x1d\\x9d\\x5d\\xdd\\x3d\\xbd\\x7d\\xfd\" +\n\t\"\\x03\\x83\\x43\\xc3\\x23\\xa3\\x63\\xe3\\x13\\x93\\x53\\xd3\\x33\\xb3\\x73\\xf3\" +\n\t\"\\x0b\\x8b\\x4b\\xcb\\x2b\\xab\\x6b\\xeb\\x1b\\x9b\\x5b\\xdb\\x3b\\xbb\\x7b\\xfb\" +\n\t\"\\x07\\x87\\x47\\xc7\\x27\\xa7\\x67\\xe7\\x17\\x97\\x57\\xd7\\x37\\xb7\\x77\\xf7\" +\n\t\"\\x0f\\x8f\\x4f\\xcf\\x2f\\xaf\\x6f\\xef\\x1f\\x9f\\x5f\\xdf\\x3f\\xbf\\x7f\\xff\"\n\nconst len8tab = \"\" +\n\t\"\\x00\\x01\\x02\\x02\\x03\\x03\\x03\\x03\\x04\\x04\\x04\\x04\\x04\\x04\\x04\\x04\" +\n\t\"\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\" +\n\t\"\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\" +\n\t\"\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\" +\n\t\"\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\" +\n\t\"\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\" +\n\t\"\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\" +\n\t\"\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\"\n"},{"name":"bitwise.gno","body":"// bitwise contains bitwise operations for Uint instances.\n// This file includes functions to perform bitwise AND, OR, XOR, and NOT operations, as well as bit shifting.\n// These operations are crucial for manipulating individual bits within a 256-bit unsigned integer.\npackage uint256\n\n// Or sets z = x | y and returns z.\nfunc (z *Uint) Or(x, y *Uint) *Uint {\n\tz.arr[0] = x.arr[0] | y.arr[0]\n\tz.arr[1] = x.arr[1] | y.arr[1]\n\tz.arr[2] = x.arr[2] | y.arr[2]\n\tz.arr[3] = x.arr[3] | y.arr[3]\n\treturn z\n}\n\n// And sets z = x \u0026 y and returns z.\nfunc (z *Uint) And(x, y *Uint) *Uint {\n\tz.arr[0] = x.arr[0] \u0026 y.arr[0]\n\tz.arr[1] = x.arr[1] \u0026 y.arr[1]\n\tz.arr[2] = x.arr[2] \u0026 y.arr[2]\n\tz.arr[3] = x.arr[3] \u0026 y.arr[3]\n\treturn z\n}\n\n// Not sets z = ^x and returns z.\nfunc (z *Uint) Not(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = ^x.arr[3], ^x.arr[2], ^x.arr[1], ^x.arr[0]\n\treturn z\n}\n\n// AndNot sets z = x \u0026^ y and returns z.\nfunc (z *Uint) AndNot(x, y *Uint) *Uint {\n\tz.arr[0] = x.arr[0] \u0026^ y.arr[0]\n\tz.arr[1] = x.arr[1] \u0026^ y.arr[1]\n\tz.arr[2] = x.arr[2] \u0026^ y.arr[2]\n\tz.arr[3] = x.arr[3] \u0026^ y.arr[3]\n\treturn z\n}\n\n// Xor sets z = x ^ y and returns z.\nfunc (z *Uint) Xor(x, y *Uint) *Uint {\n\tz.arr[0] = x.arr[0] ^ y.arr[0]\n\tz.arr[1] = x.arr[1] ^ y.arr[1]\n\tz.arr[2] = x.arr[2] ^ y.arr[2]\n\tz.arr[3] = x.arr[3] ^ y.arr[3]\n\treturn z\n}\n\n// Lsh sets z = x \u003c\u003c n and returns z.\nfunc (z *Uint) Lsh(x *Uint, n uint) *Uint {\n\t// n % 64 == 0\n\tif n\u00260x3f == 0 {\n\t\tswitch n {\n\t\tcase 0:\n\t\t\treturn z.Set(x)\n\t\tcase 64:\n\t\t\treturn z.lsh64(x)\n\t\tcase 128:\n\t\t\treturn z.lsh128(x)\n\t\tcase 192:\n\t\t\treturn z.lsh192(x)\n\t\tdefault:\n\t\t\treturn z.Clear()\n\t\t}\n\t}\n\tvar a, b uint64\n\t// Big swaps first\n\tswitch {\n\tcase n \u003e 192:\n\t\tif n \u003e 256 {\n\t\t\treturn z.Clear()\n\t\t}\n\t\tz.lsh192(x)\n\t\tn -= 192\n\t\tgoto sh192\n\tcase n \u003e 128:\n\t\tz.lsh128(x)\n\t\tn -= 128\n\t\tgoto sh128\n\tcase n \u003e 64:\n\t\tz.lsh64(x)\n\t\tn -= 64\n\t\tgoto sh64\n\tdefault:\n\t\tz.Set(x)\n\t}\n\n\t// remaining shifts\n\ta = z.arr[0] \u003e\u003e (64 - n)\n\tz.arr[0] = z.arr[0] \u003c\u003c n\n\nsh64:\n\tb = z.arr[1] \u003e\u003e (64 - n)\n\tz.arr[1] = (z.arr[1] \u003c\u003c n) | a\n\nsh128:\n\ta = z.arr[2] \u003e\u003e (64 - n)\n\tz.arr[2] = (z.arr[2] \u003c\u003c n) | b\n\nsh192:\n\tz.arr[3] = (z.arr[3] \u003c\u003c n) | a\n\n\treturn z\n}\n\n// Rsh sets z = x \u003e\u003e n and returns z.\nfunc (z *Uint) Rsh(x *Uint, n uint) *Uint {\n\t// n % 64 == 0\n\tif n\u00260x3f == 0 {\n\t\tswitch n {\n\t\tcase 0:\n\t\t\treturn z.Set(x)\n\t\tcase 64:\n\t\t\treturn z.rsh64(x)\n\t\tcase 128:\n\t\t\treturn z.rsh128(x)\n\t\tcase 192:\n\t\t\treturn z.rsh192(x)\n\t\tdefault:\n\t\t\treturn z.Clear()\n\t\t}\n\t}\n\tvar a, b uint64\n\t// Big swaps first\n\tswitch {\n\tcase n \u003e 192:\n\t\tif n \u003e 256 {\n\t\t\treturn z.Clear()\n\t\t}\n\t\tz.rsh192(x)\n\t\tn -= 192\n\t\tgoto sh192\n\tcase n \u003e 128:\n\t\tz.rsh128(x)\n\t\tn -= 128\n\t\tgoto sh128\n\tcase n \u003e 64:\n\t\tz.rsh64(x)\n\t\tn -= 64\n\t\tgoto sh64\n\tdefault:\n\t\tz.Set(x)\n\t}\n\n\t// remaining shifts\n\ta = z.arr[3] \u003c\u003c (64 - n)\n\tz.arr[3] = z.arr[3] \u003e\u003e n\n\nsh64:\n\tb = z.arr[2] \u003c\u003c (64 - n)\n\tz.arr[2] = (z.arr[2] \u003e\u003e n) | a\n\nsh128:\n\ta = z.arr[1] \u003c\u003c (64 - n)\n\tz.arr[1] = (z.arr[1] \u003e\u003e n) | b\n\nsh192:\n\tz.arr[0] = (z.arr[0] \u003e\u003e n) | a\n\n\treturn z\n}\n\n// SRsh (Signed/Arithmetic right shift)\n// considers z to be a signed integer, during right-shift\n// and sets z = x \u003e\u003e n and returns z.\nfunc (z *Uint) SRsh(x *Uint, n uint) *Uint {\n\t// If the MSB is 0, SRsh is same as Rsh.\n\tif !x.isBitSet(255) {\n\t\treturn z.Rsh(x, n)\n\t}\n\tif n%64 == 0 {\n\t\tswitch n {\n\t\tcase 0:\n\t\t\treturn z.Set(x)\n\t\tcase 64:\n\t\t\treturn z.srsh64(x)\n\t\tcase 128:\n\t\t\treturn z.srsh128(x)\n\t\tcase 192:\n\t\t\treturn z.srsh192(x)\n\t\tdefault:\n\t\t\treturn z.SetAllOne()\n\t\t}\n\t}\n\tvar a uint64 = MaxUint64 \u003c\u003c (64 - n%64)\n\t// Big swaps first\n\tswitch {\n\tcase n \u003e 192:\n\t\tif n \u003e 256 {\n\t\t\treturn z.SetAllOne()\n\t\t}\n\t\tz.srsh192(x)\n\t\tn -= 192\n\t\tgoto sh192\n\tcase n \u003e 128:\n\t\tz.srsh128(x)\n\t\tn -= 128\n\t\tgoto sh128\n\tcase n \u003e 64:\n\t\tz.srsh64(x)\n\t\tn -= 64\n\t\tgoto sh64\n\tdefault:\n\t\tz.Set(x)\n\t}\n\n\t// remaining shifts\n\tz.arr[3], a = (z.arr[3]\u003e\u003en)|a, z.arr[3]\u003c\u003c(64-n)\n\nsh64:\n\tz.arr[2], a = (z.arr[2]\u003e\u003en)|a, z.arr[2]\u003c\u003c(64-n)\n\nsh128:\n\tz.arr[1], a = (z.arr[1]\u003e\u003en)|a, z.arr[1]\u003c\u003c(64-n)\n\nsh192:\n\tz.arr[0] = (z.arr[0] \u003e\u003e n) | a\n\n\treturn z\n}\n\nfunc (z *Uint) lsh64(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = x.arr[2], x.arr[1], x.arr[0], 0\n\treturn z\n}\n\nfunc (z *Uint) lsh128(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = x.arr[1], x.arr[0], 0, 0\n\treturn z\n}\n\nfunc (z *Uint) lsh192(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = x.arr[0], 0, 0, 0\n\treturn z\n}\n\nfunc (z *Uint) rsh64(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, x.arr[3], x.arr[2], x.arr[1]\n\treturn z\n}\n\nfunc (z *Uint) rsh128(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, 0, x.arr[3], x.arr[2]\n\treturn z\n}\n\nfunc (z *Uint) rsh192(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, 0, 0, x.arr[3]\n\treturn z\n}\n\nfunc (z *Uint) srsh64(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = MaxUint64, x.arr[3], x.arr[2], x.arr[1]\n\treturn z\n}\n\nfunc (z *Uint) srsh128(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = MaxUint64, MaxUint64, x.arr[3], x.arr[2]\n\treturn z\n}\n\nfunc (z *Uint) srsh192(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = MaxUint64, MaxUint64, MaxUint64, x.arr[3]\n\treturn z\n}\n"},{"name":"bitwise_test.gno","body":"package uint256\n\nimport \"testing\"\n\ntype logicOpTest struct {\n\tname string\n\tx Uint\n\ty Uint\n\twant Uint\n}\n\nfunc TestOr(t *testing.T) {\n\ttests := []logicOpTest{\n\t\t{\n\t\t\tname: \"all zeros\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tres := new(Uint).Or(\u0026tt.x, \u0026tt.y)\n\t\t\tif *res != tt.want {\n\t\t\t\tt.Errorf(\n\t\t\t\t\t\"Or(%s, %s) = %s, want %s\",\n\t\t\t\t\ttt.x.ToString(), tt.y.ToString(), res.ToString(), (tt.want).ToString(),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestAnd(t *testing.T) {\n\ttests := []logicOpTest{\n\t\t{\n\t\t\tname: \"all zeros\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 2\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 3\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand zero\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}},\n\t\t\twant: Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tres := new(Uint).And(\u0026tt.x, \u0026tt.y)\n\t\t\tif *res != tt.want {\n\t\t\t\tt.Errorf(\n\t\t\t\t\t\"And(%s, %s) = %s, want %s\",\n\t\t\t\t\ttt.x.ToString(), tt.y.ToString(), res.ToString(), (tt.want).ToString(),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNot(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tx Uint\n\t\twant Uint\n\t}{\n\t\t{\n\t\t\tname: \"all zeros\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tres := new(Uint).Not(\u0026tt.x)\n\t\t\tif *res != tt.want {\n\t\t\t\tt.Errorf(\n\t\t\t\t\t\"Not(%s) = %s, want %s\",\n\t\t\t\t\ttt.x.ToString(), res.ToString(), (tt.want).ToString(),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestAndNot(t *testing.T) {\n\ttests := []logicOpTest{\n\t\t{\n\t\t\tname: \"all zeros\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 2\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 3\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand zero\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}},\n\t\t\twant: Uint{arr: [4]uint64{0xAAAAAAAAAAAAAAAA, 0x5555555555555555, 0x0000000000000000, ^uint64(0)}},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tres := new(Uint).AndNot(\u0026tt.x, \u0026tt.y)\n\t\t\tif *res != tt.want {\n\t\t\t\tt.Errorf(\n\t\t\t\t\t\"AndNot(%s, %s) = %s, want %s\",\n\t\t\t\t\ttt.x.ToString(), tt.y.ToString(), res.ToString(), (tt.want).ToString(),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestXor(t *testing.T) {\n\ttests := []logicOpTest{\n\t\t{\n\t\t\tname: \"all zeros\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 2\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 3\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand zero\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF}},\n\t\t\twant: Uint{arr: [4]uint64{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}},\n\t\t\twant: Uint{arr: [4]uint64{0xAAAAAAAAAAAAAAAA, 0x5555555555555555, 0x0000000000000000, ^uint64(0)}},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tres := new(Uint).Xor(\u0026tt.x, \u0026tt.y)\n\t\t\tif *res != tt.want {\n\t\t\t\tt.Errorf(\n\t\t\t\t\t\"Xor(%s, %s) = %s, want %s\",\n\t\t\t\t\ttt.x.ToString(), tt.y.ToString(), res.ToString(), (tt.want).ToString(),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestLsh(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty uint\n\t\twant string\n\t}{\n\t\t{\"0\", 0, \"0\"},\n\t\t{\"0\", 1, \"0\"},\n\t\t{\"0\", 64, \"0\"},\n\t\t{\"1\", 0, \"1\"},\n\t\t{\"1\", 1, \"2\"},\n\t\t{\"1\", 64, \"18446744073709551616\"},\n\t\t{\"1\", 128, \"340282366920938463463374607431768211456\"},\n\t\t{\"1\", 192, \"6277101735386680763835789423207666416102355444464034512896\"},\n\t\t{\"1\", 255, \"57896044618658097711785492504343953926634992332820282019728792003956564819968\"},\n\t\t{\"1\", 256, \"0\"},\n\t\t{\"31337\", 0, \"31337\"},\n\t\t{\"31337\", 1, \"62674\"},\n\t\t{\"31337\", 64, \"578065619037836218990592\"},\n\t\t{\"31337\", 128, \"10663428532201448629551770073089320442396672\"},\n\t\t{\"31337\", 192, \"196705537081812415096322133155058642481399512563169449530621952\"},\n\t\t{\"31337\", 193, \"393411074163624830192644266310117284962799025126338899061243904\"},\n\t\t{\"31337\", 255, \"57896044618658097711785492504343953926634992332820282019728792003956564819968\"},\n\t\t{\"31337\", 256, \"0\"},\n\t\t// 64 \u003c n \u003c 128\n\t\t{\"1\", 65, \"36893488147419103232\"},\n\t\t{\"31337\", 100, \"39724366859352024754702188346867712\"},\n\n\t\t// 128 \u003c n \u003c 192\n\t\t{\"1\", 129, \"680564733841876926926749214863536422912\"},\n\t\t{\"31337\", 150, \"44725660946326664792723507424638829088826130956288\"},\n\n\t\t// 192 \u003c n \u003c 256\n\t\t{\"1\", 193, \"12554203470773361527671578846415332832204710888928069025792\"},\n\t\t{\"31337\", 200, \"50356617492943978264658466087695012475238275216171379079839219712\"},\n\n\t\t// n \u003e 256\n\t\t{\"1\", 257, \"0\"},\n\t\t{\"31337\", 300, \"0\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\twant := MustFromDecimal(tt.want)\n\n\t\tgot := new(Uint).Lsh(x, tt.y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Lsh(%s, %d) = %s, want %s\", tt.x, tt.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestRsh(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty uint\n\t\twant string\n\t}{\n\t\t{\"0\", 0, \"0\"},\n\t\t{\"0\", 1, \"0\"},\n\t\t{\"0\", 64, \"0\"},\n\t\t{\"1\", 0, \"1\"},\n\t\t{\"1\", 1, \"0\"},\n\t\t{\"1\", 64, \"0\"},\n\t\t{\"1\", 128, \"0\"},\n\t\t{\"1\", 192, \"0\"},\n\t\t{\"1\", 255, \"0\"},\n\t\t{\"57896044618658097711785492504343953926634992332820282019728792003956564819968\", 255, \"1\"},\n\t\t{\"6277101735386680763835789423207666416102355444464034512896\", 192, \"1\"},\n\t\t{\"340282366920938463463374607431768211456\", 128, \"1\"},\n\t\t{\"18446744073709551616\", 64, \"1\"},\n\t\t{\"393411074163624830192644266310117284962799025126338899061243904\", 193, \"31337\"},\n\t\t{\"196705537081812415096322133155058642481399512563169449530621952\", 192, \"31337\"},\n\t\t{\"10663428532201448629551770073089320442396672\", 128, \"31337\"},\n\t\t{\"578065619037836218990592\", 64, \"31337\"},\n\t\t{twoPow256Sub1, 256, \"0\"},\n\t\t// outliers\n\t\t{\"340282366920938463463374607431768211455\", 129, \"0\"},\n\t\t{\"18446744073709551615\", 65, \"0\"},\n\t\t{twoPow256Sub1, 1, \"57896044618658097711785492504343953926634992332820282019728792003956564819967\"},\n\n\t\t// n \u003e 256\n\t\t{\"1\", 257, \"0\"},\n\t\t{\"31337\", 300, \"0\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\n\t\twant := MustFromDecimal(tt.want)\n\t\tgot := new(Uint).Rsh(x, tt.y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Rsh(%s, %d) = %s, want %s\", tt.x, tt.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestSRsh(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty uint\n\t\twant string\n\t}{\n\t\t// Positive numbers (behaves like Rsh)\n\t\t{\"0x0\", 0, \"0x0\"},\n\t\t{\"0x0\", 1, \"0x0\"},\n\t\t{\"0x1\", 0, \"0x1\"},\n\t\t{\"0x1\", 1, \"0x0\"},\n\t\t{\"0x31337\", 0, \"0x31337\"},\n\t\t{\"0x31337\", 4, \"0x3133\"},\n\t\t{\"0x31337\", 8, \"0x313\"},\n\t\t{\"0x31337\", 16, \"0x3\"},\n\t\t{\"0x10000000000000000\", 64, \"0x1\"}, // 2^64 \u003e\u003e 64\n\n\t\t// // Numbers with MSB set (negative numbers in two's complement)\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 0, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 1, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 4, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 64, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 128, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 192, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 255, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\n\t\t// Large positive number close to max value\n\t\t{\"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 1, \"0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 2, \"0x1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 64, \"0x7fffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 128, \"0x7fffffffffffffffffffffffffffffff\"},\n\t\t{\"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 192, \"0x7fffffffffffffff\"},\n\t\t{\"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 255, \"0x0\"},\n\n\t\t// Specific cases\n\t\t{\"0x8000000000000000000000000000000000000000000000000000000000000000\", 1, \"0xc000000000000000000000000000000000000000000000000000000000000000\"},\n\t\t{\"0x8000000000000000000000000000000000000000000000000000000000000001\", 1, \"0xc000000000000000000000000000000000000000000000000000000000000000\"},\n\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 65, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 127, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 129, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 193, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\n\t\t// n \u003e 256\n\t\t{\"0x1\", 257, \"0x0\"},\n\t\t{\"0x31337\", 300, \"0x0\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromHex(tt.x)\n\t\twant := MustFromHex(tt.want)\n\n\t\tgot := new(Uint).SRsh(x, tt.y)\n\n\t\tif !got.Eq(want) {\n\t\t\tt.Errorf(\"SRsh(%s, %d) = %s, want %s\", tt.x, tt.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n"},{"name":"cmp.gno","body":"// cmp (or, comparisons) includes methods for comparing Uint instances.\n// These comparison functions cover a range of operations including equality checks, less than/greater than\n// evaluations, and specialized comparisons such as signed greater than. These are fundamental for logical\n// decision making based on Uint values.\npackage uint256\n\nimport (\n\t\"math/bits\"\n)\n\n// Cmp compares z and x and returns:\n//\n//\t-1 if z \u003c x\n//\t 0 if z == x\n//\t+1 if z \u003e x\nfunc (z *Uint) Cmp(x *Uint) (r int) {\n\t// z \u003c x \u003c=\u003e z - x \u003c 0 i.e. when subtraction overflows.\n\td0, carry := bits.Sub64(z.arr[0], x.arr[0], 0)\n\td1, carry := bits.Sub64(z.arr[1], x.arr[1], carry)\n\td2, carry := bits.Sub64(z.arr[2], x.arr[2], carry)\n\td3, carry := bits.Sub64(z.arr[3], x.arr[3], carry)\n\tif carry == 1 {\n\t\treturn -1\n\t}\n\tif d0|d1|d2|d3 == 0 {\n\t\treturn 0\n\t}\n\treturn 1\n}\n\n// IsZero returns true if z == 0\nfunc (z *Uint) IsZero() bool {\n\treturn (z.arr[0] | z.arr[1] | z.arr[2] | z.arr[3]) == 0\n}\n\n// Sign returns:\n//\n//\t-1 if z \u003c 0\n//\t 0 if z == 0\n//\t+1 if z \u003e 0\n//\n// Where z is interpreted as a two's complement signed number\nfunc (z *Uint) Sign() int {\n\tif z.IsZero() {\n\t\treturn 0\n\t}\n\tif z.arr[3] \u003c 0x8000000000000000 {\n\t\treturn 1\n\t}\n\treturn -1\n}\n\n// LtUint64 returns true if z is smaller than n\nfunc (z *Uint) LtUint64(n uint64) bool {\n\treturn z.arr[0] \u003c n \u0026\u0026 (z.arr[1]|z.arr[2]|z.arr[3]) == 0\n}\n\n// GtUint64 returns true if z is larger than n\nfunc (z *Uint) GtUint64(n uint64) bool {\n\treturn z.arr[0] \u003e n || (z.arr[1]|z.arr[2]|z.arr[3]) != 0\n}\n\n// Lt returns true if z \u003c x\nfunc (z *Uint) Lt(x *Uint) bool {\n\t// z \u003c x \u003c=\u003e z - x \u003c 0 i.e. when subtraction overflows.\n\t_, carry := bits.Sub64(z.arr[0], x.arr[0], 0)\n\t_, carry = bits.Sub64(z.arr[1], x.arr[1], carry)\n\t_, carry = bits.Sub64(z.arr[2], x.arr[2], carry)\n\t_, carry = bits.Sub64(z.arr[3], x.arr[3], carry)\n\n\treturn carry != 0\n}\n\n// Gt returns true if z \u003e x\nfunc (z *Uint) Gt(x *Uint) bool {\n\treturn x.Lt(z)\n}\n\n// Lte returns true if z \u003c= x\nfunc (z *Uint) Lte(x *Uint) bool {\n\tcond1 := z.Lt(x)\n\tcond2 := z.Eq(x)\n\n\tif cond1 || cond2 {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// Gte returns true if z \u003e= x\nfunc (z *Uint) Gte(x *Uint) bool {\n\tcond1 := z.Gt(x)\n\tcond2 := z.Eq(x)\n\n\tif cond1 || cond2 {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// Eq returns true if z == x\nfunc (z *Uint) Eq(x *Uint) bool {\n\treturn (z.arr[0] == x.arr[0]) \u0026\u0026 (z.arr[1] == x.arr[1]) \u0026\u0026 (z.arr[2] == x.arr[2]) \u0026\u0026 (z.arr[3] == x.arr[3])\n}\n\n// Neq returns true if z != x\nfunc (z *Uint) Neq(x *Uint) bool {\n\treturn !z.Eq(x)\n}\n\n// Sgt interprets z and x as signed integers, and returns\n// true if z \u003e x\nfunc (z *Uint) Sgt(x *Uint) bool {\n\tzSign := z.Sign()\n\txSign := x.Sign()\n\n\tswitch {\n\tcase zSign \u003e= 0 \u0026\u0026 xSign \u003c 0:\n\t\treturn true\n\tcase zSign \u003c 0 \u0026\u0026 xSign \u003e= 0:\n\t\treturn false\n\tdefault:\n\t\treturn z.Gt(x)\n\t}\n}\n"},{"name":"cmp_test.gno","body":"package uint256\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestSign(t *testing.T) {\n\ttests := []struct {\n\t\tinput *Uint\n\t\texpected int\n\t}{\n\t\t{\n\t\t\tinput: NewUint(0),\n\t\t\texpected: 0,\n\t\t},\n\t\t{\n\t\t\tinput: NewUint(1),\n\t\t\texpected: 1,\n\t\t},\n\t\t{\n\t\t\tinput: NewUint(0x7fffffffffffffff),\n\t\t\texpected: 1,\n\t\t},\n\t\t{\n\t\t\tinput: NewUint(0x8000000000000000),\n\t\t\texpected: 1,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.input.ToString(), func(t *testing.T) {\n\t\t\tresult := tt.input.Sign()\n\t\t\tif result != tt.expected {\n\t\t\t\tt.Errorf(\"Sign() = %d; want %d\", result, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCmp(t *testing.T) {\n\ttests := []struct {\n\t\tx, y string\n\t\twant int\n\t}{\n\t\t{\"0\", \"0\", 0},\n\t\t{\"0\", \"1\", -1},\n\t\t{\"1\", \"0\", 1},\n\t\t{\"1\", \"1\", 0},\n\t\t{\"10\", \"10\", 0},\n\t\t{\"10\", \"11\", -1},\n\t\t{\"11\", \"10\", 1},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx := MustFromDecimal(tc.x)\n\t\ty := MustFromDecimal(tc.y)\n\n\t\tgot := x.Cmp(y)\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Cmp(%s, %s) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestIsZero(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant bool\n\t}{\n\t\t{\"0\", true},\n\t\t{\"1\", false},\n\t\t{\"10\", false},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\n\t\tgot := x.IsZero()\n\t\tif got != tt.want {\n\t\t\tt.Errorf(\"IsZero(%s) = %v, want %v\", tt.x, got, tt.want)\n\t\t}\n\t}\n}\n\nfunc TestLtUint64(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty uint64\n\t\twant bool\n\t}{\n\t\t{\"0\", 1, true},\n\t\t{\"1\", 0, false},\n\t\t{\"10\", 10, false},\n\t\t{\"0xffffffffffffffff\", 0, false},\n\t\t{\"0x10000000000000000\", 10000000000000000, false},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx := parseTestString(t, tc.x)\n\n\t\tgot := x.LtUint64(tc.y)\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"LtUint64(%s, %d) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestUint_GtUint64(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tz string\n\t\tn uint64\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"z \u003e n\",\n\t\t\tz: \"1\",\n\t\t\tn: 0,\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"z \u003c n\",\n\t\t\tz: \"18446744073709551615\",\n\t\t\tn: 0xFFFFFFFFFFFFFFFF,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"z == n\",\n\t\t\tz: \"18446744073709551615\",\n\t\t\tn: 0xFFFFFFFFFFFFFFFF,\n\t\t\twant: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tz := MustFromDecimal(tt.z)\n\n\t\t\tif got := z.GtUint64(tt.n); got != tt.want {\n\t\t\t\tt.Errorf(\"Uint.GtUint64() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSGT(t *testing.T) {\n\tx := MustFromHex(\"0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe\")\n\ty := MustFromHex(\"0x0\")\n\tactual := x.Sgt(y)\n\tif actual {\n\t\tt.Fatalf(\"Expected %v false\", actual)\n\t}\n\n\tx = MustFromHex(\"0x0\")\n\ty = MustFromHex(\"0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe\")\n\tactual = x.Sgt(y)\n\tif !actual {\n\t\tt.Fatalf(\"Expected %v true\", actual)\n\t}\n}\n\nfunc TestEq(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty string\n\t\twant bool\n\t}{\n\t\t{\"0xffffffffffffffff\", \"18446744073709551615\", true},\n\t\t{\"0x10000000000000000\", \"18446744073709551616\", true},\n\t\t{\"0\", \"0\", true},\n\t\t{twoPow256Sub1, twoPow256Sub1, true},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := parseTestString(t, tt.x)\n\n\t\ty, err := FromDecimal(tt.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.Eq(y)\n\n\t\tif got != tt.want {\n\t\t\tt.Errorf(\"Eq(%s, %s) = %v, want %v\", tt.x, tt.y, got, tt.want)\n\t\t}\n\t}\n}\n\nfunc TestUint_Lte(t *testing.T) {\n\ttests := []struct {\n\t\tz, x string\n\t\twant bool\n\t}{\n\t\t{\"10\", \"20\", true},\n\t\t{\"20\", \"10\", false},\n\t\t{\"10\", \"10\", true},\n\t\t{\"0\", \"0\", true},\n\t}\n\n\tfor _, tt := range tests {\n\t\tz, err := FromDecimal(tt.z)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\t\tx, err := FromDecimal(tt.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\t\tif got := z.Lte(x); got != tt.want {\n\t\t\tt.Errorf(\"Uint.Lte(%v, %v) = %v, want %v\", tt.z, tt.x, got, tt.want)\n\t\t}\n\t}\n}\n\nfunc TestUint_Gte(t *testing.T) {\n\ttests := []struct {\n\t\tz, x string\n\t\twant bool\n\t}{\n\t\t{\"20\", \"10\", true},\n\t\t{\"10\", \"20\", false},\n\t\t{\"10\", \"10\", true},\n\t\t{\"0\", \"0\", true},\n\t}\n\n\tfor _, tt := range tests {\n\t\tz := parseTestString(t, tt.z)\n\t\tx := parseTestString(t, tt.x)\n\n\t\tif got := z.Gte(x); got != tt.want {\n\t\t\tt.Errorf(\"Uint.Gte(%v, %v) = %v, want %v\", tt.z, tt.x, got, tt.want)\n\t\t}\n\t}\n}\n\nfunc parseTestString(_ *testing.T, s string) *Uint {\n\tvar x *Uint\n\n\tif strings.HasPrefix(s, \"0x\") {\n\t\tx = MustFromHex(s)\n\t} else {\n\t\tx = MustFromDecimal(s)\n\t}\n\n\treturn x\n}\n"},{"name":"conversion.gno","body":"// conversions contains methods for converting Uint instances to other types and vice versa.\n// This includes conversions to and from basic types such as uint64 and int32, as well as string representations\n// and byte slices. Additionally, it covers marshaling and unmarshaling for JSON and other text formats.\npackage uint256\n\nimport (\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// Uint64 returns the lower 64-bits of z\nfunc (z *Uint) Uint64() uint64 {\n\treturn z.arr[0]\n}\n\n// Uint64WithOverflow returns the lower 64-bits of z and bool whether overflow occurred\nfunc (z *Uint) Uint64WithOverflow() (uint64, bool) {\n\treturn z.arr[0], (z.arr[1] | z.arr[2] | z.arr[3]) != 0\n}\n\n// SetUint64 sets z to the value x\nfunc (z *Uint) SetUint64(x uint64) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, 0, 0, x\n\treturn z\n}\n\n// IsUint64 reports whether z can be represented as a uint64.\nfunc (z *Uint) IsUint64() bool {\n\treturn (z.arr[1] | z.arr[2] | z.arr[3]) == 0\n}\n\n// Dec returns the decimal representation of z.\nfunc (z *Uint) Dec() string {\n\tif z.IsZero() {\n\t\treturn \"0\"\n\t}\n\tif z.IsUint64() {\n\t\treturn strconv.FormatUint(z.Uint64(), 10)\n\t}\n\n\t// The max uint64 value being 18446744073709551615, the largest\n\t// power-of-ten below that is 10000000000000000000.\n\t// When we do a DivMod using that number, the remainder that we\n\t// get back is the lower part of the output.\n\t//\n\t// The ascii-output of remainder will never exceed 19 bytes (since it will be\n\t// below 10000000000000000000).\n\t//\n\t// Algorithm example using 100 as divisor\n\t//\n\t// 12345 % 100 = 45 (rem)\n\t// 12345 / 100 = 123 (quo)\n\t// -\u003e output '45', continue iterate on 123\n\tvar (\n\t\t// out is 98 bytes long: 78 (max size of a string without leading zeroes,\n\t\t// plus slack so we can copy 19 bytes every iteration).\n\t\t// We init it with zeroes, because when strconv appends the ascii representations,\n\t\t// it will omit leading zeroes.\n\t\tout = []byte(\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\")\n\t\tdivisor = NewUint(10000000000000000000) // 20 digits\n\t\ty = new(Uint).Set(z) // copy to avoid modifying z\n\t\tpos = len(out) // position to write to\n\t\tbuf = make([]byte, 0, 19) // buffer to write uint64:s to\n\t)\n\tfor {\n\t\t// Obtain Q and R for divisor\n\t\tvar quot Uint\n\t\trem := udivrem(quot.arr[:], y.arr[:], divisor)\n\t\ty.Set(\u0026quot) // Set Q for next loop\n\t\t// Convert the R to ascii representation\n\t\tbuf = strconv.AppendUint(buf[:0], rem.Uint64(), 10)\n\t\t// Copy in the ascii digits\n\t\tcopy(out[pos-len(buf):], buf)\n\t\tif y.IsZero() {\n\t\t\tbreak\n\t\t}\n\t\t// Move 19 digits left\n\t\tpos -= 19\n\t}\n\t// skip leading zeroes by only using the 'used size' of buf\n\treturn string(out[pos-len(buf):])\n}\n\nfunc (z *Uint) Scan(src interface{}) error {\n\tif src == nil {\n\t\tz.Clear()\n\t\treturn nil\n\t}\n\n\tswitch src := src.(type) {\n\tcase string:\n\t\treturn z.scanScientificFromString(src)\n\tcase []byte:\n\t\treturn z.scanScientificFromString(string(src))\n\t}\n\treturn errors.New(\"default // unsupported type: can't convert to uint256.Uint\")\n}\n\nfunc (z *Uint) scanScientificFromString(src string) error {\n\tif len(src) == 0 {\n\t\tz.Clear()\n\t\treturn nil\n\t}\n\n\tidx := strings.IndexByte(src, 'e')\n\tif idx == -1 {\n\t\treturn z.SetFromDecimal(src)\n\t}\n\tif err := z.SetFromDecimal(src[:idx]); err != nil {\n\t\treturn err\n\t}\n\tif src[(idx+1):] == \"0\" {\n\t\treturn nil\n\t}\n\texp := new(Uint)\n\tif err := exp.SetFromDecimal(src[(idx + 1):]); err != nil {\n\t\treturn err\n\t}\n\tif exp.GtUint64(77) { // 10**78 is larger than 2**256\n\t\treturn ErrBig256Range\n\t}\n\texp.Exp(NewUint(10), exp)\n\tif _, overflow := z.MulOverflow(z, exp); overflow {\n\t\treturn ErrBig256Range\n\t}\n\treturn nil\n}\n\n// ToString returns the decimal string representation of z. It returns an empty string if z is nil.\n// OBS: doesn't exist from holiman's uint256\nfunc (z *Uint) ToString() string {\n\tif z == nil {\n\t\treturn \"\"\n\t}\n\n\treturn z.Dec()\n}\n\n// MarshalJSON implements json.Marshaler.\n// MarshalJSON marshals using the 'decimal string' representation. This is _not_ compatible\n// with big.Uint: big.Uint marshals into JSON 'native' numeric format.\n//\n// The JSON native format is, on some platforms, (e.g. javascript), limited to 53-bit large\n// integer space. Thus, U256 uses string-format, which is not compatible with\n// big.int (big.Uint refuses to unmarshal a string representation).\nfunc (z *Uint) MarshalJSON() ([]byte, error) {\n\treturn []byte(`\"` + z.Dec() + `\"`), nil\n}\n\n// UnmarshalJSON implements json.Unmarshaler. UnmarshalJSON accepts either\n// - Quoted string: either hexadecimal OR decimal\n// - Not quoted string: only decimal\nfunc (z *Uint) UnmarshalJSON(input []byte) error {\n\tif len(input) \u003c 2 || input[0] != '\"' || input[len(input)-1] != '\"' {\n\t\t// if not quoted, it must be decimal\n\t\treturn z.fromDecimal(string(input))\n\t}\n\treturn z.UnmarshalText(input[1 : len(input)-1])\n}\n\n// MarshalText implements encoding.TextMarshaler\n// MarshalText marshals using the decimal representation (compatible with big.Uint)\nfunc (z *Uint) MarshalText() ([]byte, error) {\n\treturn []byte(z.Dec()), nil\n}\n\n// UnmarshalText implements encoding.TextUnmarshaler. This method\n// can unmarshal either hexadecimal or decimal.\n// - For hexadecimal, the input _must_ be prefixed with 0x or 0X\nfunc (z *Uint) UnmarshalText(input []byte) error {\n\tif len(input) \u003e= 2 \u0026\u0026 input[0] == '0' \u0026\u0026 (input[1] == 'x' || input[1] == 'X') {\n\t\treturn z.fromHex(string(input))\n\t}\n\treturn z.fromDecimal(string(input))\n}\n\n// SetBytes interprets buf as the bytes of a big-endian unsigned\n// integer, sets z to that value, and returns z.\n// If buf is larger than 32 bytes, the last 32 bytes is used.\nfunc (z *Uint) SetBytes(buf []byte) *Uint {\n\tswitch l := len(buf); l {\n\tcase 0:\n\t\tz.Clear()\n\tcase 1:\n\t\tz.SetBytes1(buf)\n\tcase 2:\n\t\tz.SetBytes2(buf)\n\tcase 3:\n\t\tz.SetBytes3(buf)\n\tcase 4:\n\t\tz.SetBytes4(buf)\n\tcase 5:\n\t\tz.SetBytes5(buf)\n\tcase 6:\n\t\tz.SetBytes6(buf)\n\tcase 7:\n\t\tz.SetBytes7(buf)\n\tcase 8:\n\t\tz.SetBytes8(buf)\n\tcase 9:\n\t\tz.SetBytes9(buf)\n\tcase 10:\n\t\tz.SetBytes10(buf)\n\tcase 11:\n\t\tz.SetBytes11(buf)\n\tcase 12:\n\t\tz.SetBytes12(buf)\n\tcase 13:\n\t\tz.SetBytes13(buf)\n\tcase 14:\n\t\tz.SetBytes14(buf)\n\tcase 15:\n\t\tz.SetBytes15(buf)\n\tcase 16:\n\t\tz.SetBytes16(buf)\n\tcase 17:\n\t\tz.SetBytes17(buf)\n\tcase 18:\n\t\tz.SetBytes18(buf)\n\tcase 19:\n\t\tz.SetBytes19(buf)\n\tcase 20:\n\t\tz.SetBytes20(buf)\n\tcase 21:\n\t\tz.SetBytes21(buf)\n\tcase 22:\n\t\tz.SetBytes22(buf)\n\tcase 23:\n\t\tz.SetBytes23(buf)\n\tcase 24:\n\t\tz.SetBytes24(buf)\n\tcase 25:\n\t\tz.SetBytes25(buf)\n\tcase 26:\n\t\tz.SetBytes26(buf)\n\tcase 27:\n\t\tz.SetBytes27(buf)\n\tcase 28:\n\t\tz.SetBytes28(buf)\n\tcase 29:\n\t\tz.SetBytes29(buf)\n\tcase 30:\n\t\tz.SetBytes30(buf)\n\tcase 31:\n\t\tz.SetBytes31(buf)\n\tdefault:\n\t\tz.SetBytes32(buf[l-32:])\n\t}\n\treturn z\n}\n\n// SetBytes1 is identical to SetBytes(in[:1]), but panics is input is too short\nfunc (z *Uint) SetBytes1(in []byte) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = uint64(in[0])\n\treturn z\n}\n\n// SetBytes2 is identical to SetBytes(in[:2]), but panics is input is too short\nfunc (z *Uint) SetBytes2(in []byte) *Uint {\n\t_ = in[1] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = uint64(binary.BigEndian.Uint16(in[0:2]))\n\treturn z\n}\n\n// SetBytes3 is identical to SetBytes(in[:3]), but panics is input is too short\nfunc (z *Uint) SetBytes3(in []byte) *Uint {\n\t_ = in[2] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = uint64(binary.BigEndian.Uint16(in[1:3])) | uint64(in[0])\u003c\u003c16\n\treturn z\n}\n\n// SetBytes4 is identical to SetBytes(in[:4]), but panics is input is too short\nfunc (z *Uint) SetBytes4(in []byte) *Uint {\n\t_ = in[3] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = uint64(binary.BigEndian.Uint32(in[0:4]))\n\treturn z\n}\n\n// SetBytes5 is identical to SetBytes(in[:5]), but panics is input is too short\nfunc (z *Uint) SetBytes5(in []byte) *Uint {\n\t_ = in[4] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = bigEndianUint40(in[0:5])\n\treturn z\n}\n\n// SetBytes6 is identical to SetBytes(in[:6]), but panics is input is too short\nfunc (z *Uint) SetBytes6(in []byte) *Uint {\n\t_ = in[5] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = bigEndianUint48(in[0:6])\n\treturn z\n}\n\n// SetBytes7 is identical to SetBytes(in[:7]), but panics is input is too short\nfunc (z *Uint) SetBytes7(in []byte) *Uint {\n\t_ = in[6] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = bigEndianUint56(in[0:7])\n\treturn z\n}\n\n// SetBytes8 is identical to SetBytes(in[:8]), but panics is input is too short\nfunc (z *Uint) SetBytes8(in []byte) *Uint {\n\t_ = in[7] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = binary.BigEndian.Uint64(in[0:8])\n\treturn z\n}\n\n// SetBytes9 is identical to SetBytes(in[:9]), but panics is input is too short\nfunc (z *Uint) SetBytes9(in []byte) *Uint {\n\t_ = in[8] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = uint64(in[0])\n\tz.arr[0] = binary.BigEndian.Uint64(in[1:9])\n\treturn z\n}\n\n// SetBytes10 is identical to SetBytes(in[:10]), but panics is input is too short\nfunc (z *Uint) SetBytes10(in []byte) *Uint {\n\t_ = in[9] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = uint64(binary.BigEndian.Uint16(in[0:2]))\n\tz.arr[0] = binary.BigEndian.Uint64(in[2:10])\n\treturn z\n}\n\n// SetBytes11 is identical to SetBytes(in[:11]), but panics is input is too short\nfunc (z *Uint) SetBytes11(in []byte) *Uint {\n\t_ = in[10] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = uint64(binary.BigEndian.Uint16(in[1:3])) | uint64(in[0])\u003c\u003c16\n\tz.arr[0] = binary.BigEndian.Uint64(in[3:11])\n\treturn z\n}\n\n// SetBytes12 is identical to SetBytes(in[:12]), but panics is input is too short\nfunc (z *Uint) SetBytes12(in []byte) *Uint {\n\t_ = in[11] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = uint64(binary.BigEndian.Uint32(in[0:4]))\n\tz.arr[0] = binary.BigEndian.Uint64(in[4:12])\n\treturn z\n}\n\n// SetBytes13 is identical to SetBytes(in[:13]), but panics is input is too short\nfunc (z *Uint) SetBytes13(in []byte) *Uint {\n\t_ = in[12] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = bigEndianUint40(in[0:5])\n\tz.arr[0] = binary.BigEndian.Uint64(in[5:13])\n\treturn z\n}\n\n// SetBytes14 is identical to SetBytes(in[:14]), but panics is input is too short\nfunc (z *Uint) SetBytes14(in []byte) *Uint {\n\t_ = in[13] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = bigEndianUint48(in[0:6])\n\tz.arr[0] = binary.BigEndian.Uint64(in[6:14])\n\treturn z\n}\n\n// SetBytes15 is identical to SetBytes(in[:15]), but panics is input is too short\nfunc (z *Uint) SetBytes15(in []byte) *Uint {\n\t_ = in[14] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = bigEndianUint56(in[0:7])\n\tz.arr[0] = binary.BigEndian.Uint64(in[7:15])\n\treturn z\n}\n\n// SetBytes16 is identical to SetBytes(in[:16]), but panics is input is too short\nfunc (z *Uint) SetBytes16(in []byte) *Uint {\n\t_ = in[15] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = binary.BigEndian.Uint64(in[0:8])\n\tz.arr[0] = binary.BigEndian.Uint64(in[8:16])\n\treturn z\n}\n\n// SetBytes17 is identical to SetBytes(in[:17]), but panics is input is too short\nfunc (z *Uint) SetBytes17(in []byte) *Uint {\n\t_ = in[16] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = uint64(in[0])\n\tz.arr[1] = binary.BigEndian.Uint64(in[1:9])\n\tz.arr[0] = binary.BigEndian.Uint64(in[9:17])\n\treturn z\n}\n\n// SetBytes18 is identical to SetBytes(in[:18]), but panics is input is too short\nfunc (z *Uint) SetBytes18(in []byte) *Uint {\n\t_ = in[17] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = uint64(binary.BigEndian.Uint16(in[0:2]))\n\tz.arr[1] = binary.BigEndian.Uint64(in[2:10])\n\tz.arr[0] = binary.BigEndian.Uint64(in[10:18])\n\treturn z\n}\n\n// SetBytes19 is identical to SetBytes(in[:19]), but panics is input is too short\nfunc (z *Uint) SetBytes19(in []byte) *Uint {\n\t_ = in[18] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = uint64(binary.BigEndian.Uint16(in[1:3])) | uint64(in[0])\u003c\u003c16\n\tz.arr[1] = binary.BigEndian.Uint64(in[3:11])\n\tz.arr[0] = binary.BigEndian.Uint64(in[11:19])\n\treturn z\n}\n\n// SetBytes20 is identical to SetBytes(in[:20]), but panics is input is too short\nfunc (z *Uint) SetBytes20(in []byte) *Uint {\n\t_ = in[19] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = uint64(binary.BigEndian.Uint32(in[0:4]))\n\tz.arr[1] = binary.BigEndian.Uint64(in[4:12])\n\tz.arr[0] = binary.BigEndian.Uint64(in[12:20])\n\treturn z\n}\n\n// SetBytes21 is identical to SetBytes(in[:21]), but panics is input is too short\nfunc (z *Uint) SetBytes21(in []byte) *Uint {\n\t_ = in[20] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = bigEndianUint40(in[0:5])\n\tz.arr[1] = binary.BigEndian.Uint64(in[5:13])\n\tz.arr[0] = binary.BigEndian.Uint64(in[13:21])\n\treturn z\n}\n\n// SetBytes22 is identical to SetBytes(in[:22]), but panics is input is too short\nfunc (z *Uint) SetBytes22(in []byte) *Uint {\n\t_ = in[21] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = bigEndianUint48(in[0:6])\n\tz.arr[1] = binary.BigEndian.Uint64(in[6:14])\n\tz.arr[0] = binary.BigEndian.Uint64(in[14:22])\n\treturn z\n}\n\n// SetBytes23 is identical to SetBytes(in[:23]), but panics is input is too short\nfunc (z *Uint) SetBytes23(in []byte) *Uint {\n\t_ = in[22] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = bigEndianUint56(in[0:7])\n\tz.arr[1] = binary.BigEndian.Uint64(in[7:15])\n\tz.arr[0] = binary.BigEndian.Uint64(in[15:23])\n\treturn z\n}\n\n// SetBytes24 is identical to SetBytes(in[:24]), but panics is input is too short\nfunc (z *Uint) SetBytes24(in []byte) *Uint {\n\t_ = in[23] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = binary.BigEndian.Uint64(in[0:8])\n\tz.arr[1] = binary.BigEndian.Uint64(in[8:16])\n\tz.arr[0] = binary.BigEndian.Uint64(in[16:24])\n\treturn z\n}\n\n// SetBytes25 is identical to SetBytes(in[:25]), but panics is input is too short\nfunc (z *Uint) SetBytes25(in []byte) *Uint {\n\t_ = in[24] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = uint64(in[0])\n\tz.arr[2] = binary.BigEndian.Uint64(in[1:9])\n\tz.arr[1] = binary.BigEndian.Uint64(in[9:17])\n\tz.arr[0] = binary.BigEndian.Uint64(in[17:25])\n\treturn z\n}\n\n// SetBytes26 is identical to SetBytes(in[:26]), but panics is input is too short\nfunc (z *Uint) SetBytes26(in []byte) *Uint {\n\t_ = in[25] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = uint64(binary.BigEndian.Uint16(in[0:2]))\n\tz.arr[2] = binary.BigEndian.Uint64(in[2:10])\n\tz.arr[1] = binary.BigEndian.Uint64(in[10:18])\n\tz.arr[0] = binary.BigEndian.Uint64(in[18:26])\n\treturn z\n}\n\n// SetBytes27 is identical to SetBytes(in[:27]), but panics is input is too short\nfunc (z *Uint) SetBytes27(in []byte) *Uint {\n\t_ = in[26] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = uint64(binary.BigEndian.Uint16(in[1:3])) | uint64(in[0])\u003c\u003c16\n\tz.arr[2] = binary.BigEndian.Uint64(in[3:11])\n\tz.arr[1] = binary.BigEndian.Uint64(in[11:19])\n\tz.arr[0] = binary.BigEndian.Uint64(in[19:27])\n\treturn z\n}\n\n// SetBytes28 is identical to SetBytes(in[:28]), but panics is input is too short\nfunc (z *Uint) SetBytes28(in []byte) *Uint {\n\t_ = in[27] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = uint64(binary.BigEndian.Uint32(in[0:4]))\n\tz.arr[2] = binary.BigEndian.Uint64(in[4:12])\n\tz.arr[1] = binary.BigEndian.Uint64(in[12:20])\n\tz.arr[0] = binary.BigEndian.Uint64(in[20:28])\n\treturn z\n}\n\n// SetBytes29 is identical to SetBytes(in[:29]), but panics is input is too short\nfunc (z *Uint) SetBytes29(in []byte) *Uint {\n\t_ = in[23] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = bigEndianUint40(in[0:5])\n\tz.arr[2] = binary.BigEndian.Uint64(in[5:13])\n\tz.arr[1] = binary.BigEndian.Uint64(in[13:21])\n\tz.arr[0] = binary.BigEndian.Uint64(in[21:29])\n\treturn z\n}\n\n// SetBytes30 is identical to SetBytes(in[:30]), but panics is input is too short\nfunc (z *Uint) SetBytes30(in []byte) *Uint {\n\t_ = in[29] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = bigEndianUint48(in[0:6])\n\tz.arr[2] = binary.BigEndian.Uint64(in[6:14])\n\tz.arr[1] = binary.BigEndian.Uint64(in[14:22])\n\tz.arr[0] = binary.BigEndian.Uint64(in[22:30])\n\treturn z\n}\n\n// SetBytes31 is identical to SetBytes(in[:31]), but panics is input is too short\nfunc (z *Uint) SetBytes31(in []byte) *Uint {\n\t_ = in[30] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = bigEndianUint56(in[0:7])\n\tz.arr[2] = binary.BigEndian.Uint64(in[7:15])\n\tz.arr[1] = binary.BigEndian.Uint64(in[15:23])\n\tz.arr[0] = binary.BigEndian.Uint64(in[23:31])\n\treturn z\n}\n\n// SetBytes32 sets z to the value of the big-endian 256-bit unsigned integer in.\nfunc (z *Uint) SetBytes32(in []byte) *Uint {\n\t_ = in[31] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = binary.BigEndian.Uint64(in[0:8])\n\tz.arr[2] = binary.BigEndian.Uint64(in[8:16])\n\tz.arr[1] = binary.BigEndian.Uint64(in[16:24])\n\tz.arr[0] = binary.BigEndian.Uint64(in[24:32])\n\treturn z\n}\n\n// Utility methods that are \"missing\" among the bigEndian.UintXX methods.\n\n// bigEndianUint40 returns the uint64 value represented by the 5 bytes in big-endian order.\nfunc bigEndianUint40(b []byte) uint64 {\n\t_ = b[4] // bounds check hint to compiler; see golang.org/issue/14808\n\treturn uint64(b[4]) | uint64(b[3])\u003c\u003c8 | uint64(b[2])\u003c\u003c16 | uint64(b[1])\u003c\u003c24 |\n\t\tuint64(b[0])\u003c\u003c32\n}\n\n// bigEndianUint56 returns the uint64 value represented by the 7 bytes in big-endian order.\nfunc bigEndianUint56(b []byte) uint64 {\n\t_ = b[6] // bounds check hint to compiler; see golang.org/issue/14808\n\treturn uint64(b[6]) | uint64(b[5])\u003c\u003c8 | uint64(b[4])\u003c\u003c16 | uint64(b[3])\u003c\u003c24 |\n\t\tuint64(b[2])\u003c\u003c32 | uint64(b[1])\u003c\u003c40 | uint64(b[0])\u003c\u003c48\n}\n\n// bigEndianUint48 returns the uint64 value represented by the 6 bytes in big-endian order.\nfunc bigEndianUint48(b []byte) uint64 {\n\t_ = b[5] // bounds check hint to compiler; see golang.org/issue/14808\n\treturn uint64(b[5]) | uint64(b[4])\u003c\u003c8 | uint64(b[3])\u003c\u003c16 | uint64(b[2])\u003c\u003c24 |\n\t\tuint64(b[1])\u003c\u003c32 | uint64(b[0])\u003c\u003c40\n}\n"},{"name":"conversion_test.gno","body":"package uint256\n\nimport \"testing\"\n\nfunc TestIsUint64(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant bool\n\t}{\n\t\t{\"0x0\", true},\n\t\t{\"0x1\", true},\n\t\t{\"0x10\", true},\n\t\t{\"0xffffffffffffffff\", true},\n\t\t{\"0x10000000000000000\", false},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromHex(tt.x)\n\t\tgot := x.IsUint64()\n\n\t\tif got != tt.want {\n\t\t\tt.Errorf(\"IsUint64(%s) = %v, want %v\", tt.x, got, tt.want)\n\t\t}\n\t}\n}\n\nfunc TestDec(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tz Uint\n\t\twant string\n\t}{\n\t\t{\n\t\t\tname: \"zero\",\n\t\t\tz: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: \"0\",\n\t\t},\n\t\t{\n\t\t\tname: \"less than 20 digits\",\n\t\t\tz: Uint{arr: [4]uint64{1234567890, 0, 0, 0}},\n\t\t\twant: \"1234567890\",\n\t\t},\n\t\t{\n\t\t\tname: \"max possible value\",\n\t\t\tz: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: twoPow256Sub1,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresult := tt.z.Dec()\n\t\t\tif result != tt.want {\n\t\t\t\tt.Errorf(\"Dec(%v) = %s, want %s\", tt.z, result, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUint_Scan(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput interface{}\n\t\twant *Uint\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"nil\",\n\t\t\tinput: nil,\n\t\t\twant: NewUint(0),\n\t\t},\n\t\t{\n\t\t\tname: \"valid scientific notation\",\n\t\t\tinput: \"1e4\",\n\t\t\twant: NewUint(10000),\n\t\t},\n\t\t{\n\t\t\tname: \"valid decimal string\",\n\t\t\tinput: \"12345\",\n\t\t\twant: NewUint(12345),\n\t\t},\n\t\t{\n\t\t\tname: \"valid byte slice\",\n\t\t\tinput: []byte(\"12345\"),\n\t\t\twant: NewUint(12345),\n\t\t},\n\t\t{\n\t\t\tname: \"invalid string\",\n\t\t\tinput: \"invalid\",\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"out of range\",\n\t\t\tinput: \"115792089237316195423570985008687907853269984665640564039457584007913129639936\", // 2^256\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"unsupported type\",\n\t\t\tinput: 123,\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tz := new(Uint)\n\t\t\terr := z.Scan(tt.input)\n\n\t\t\tif tt.wantErr {\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Errorf(\"Scan() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"Scan() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\t}\n\t\t\t\tif !z.Eq(tt.want) {\n\t\t\t\t\tt.Errorf(\"Scan() = %v, want %v\", z, tt.want)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSetBytes(t *testing.T) {\n\ttests := []struct {\n\t\tinput []byte\n\t\texpected string\n\t}{\n\t\t{[]byte{}, \"0\"},\n\t\t{[]byte{0x01}, \"1\"},\n\t\t{[]byte{0x12, 0x34}, \"4660\"},\n\t\t{[]byte{0x12, 0x34, 0x56}, \"1193046\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78}, \"305419896\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a}, \"78187493530\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc}, \"20015998343868\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde}, \"5124095576030430\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}, \"1311768467463790320\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12}, \"335812727670730321938\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34}, \"85968058283706962416180\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56}, \"22007822920628982378542166\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78}, \"5634002667681019488906794616\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a}, \"1442304682926340989160139421850\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc}, \"369229998829143293224995691993788\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde}, \"94522879700260683065598897150409950\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}, \"24197857203266734864793317670504947440\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12}, \"6194651444036284125387089323649266544658\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34}, \"1585830769673288736099094866854212235432500\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56}, \"405972677036361916441368285914678332270720086\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78}, \"103929005321308650608990281194157653061304342136\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a}, \"26605825362255014555901511985704359183693911586970\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc}, \"6811091292737283726310787068340315951025641366264508\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde}, \"1743639370940744633935561489495120883462564189763714270\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}, \"446371678960830626287503741310750946166416432579510853360\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12}, \"114271149813972640329600957775552242218602606740354778460178\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34}, \"29253414352376995924377845190541374007962267325530823285805620\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56}, \"7488874074208510956640728368778591746038340435335890761166238806\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78}, \"1917151762997378804900026462407319486985815151445988034858557134456\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a}, \"490790851327328974054406774376273788668368678770172936923790626420890\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc}, \"125642457939796217357928134240326089899102381765164271852490400363748028\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde}, \"32164469232587831643629602365523479014170209731882053594237542493119495390\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}, \"8234104123542484900769178205574010627627573691361805720124810878238590820080\"},\n\t\t// over 32 bytes (last 32 bytes are used)\n\t\t{append([]byte{0xff}, []byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}...), \"8234104123542484900769178205574010627627573691361805720124810878238590820080\"},\n\t}\n\n\tfor _, test := range tests {\n\t\tz := new(Uint)\n\t\tz.SetBytes(test.input)\n\t\texpected := MustFromDecimal(test.expected)\n\t\tif z.Cmp(expected) != 0 {\n\t\t\tt.Errorf(\"SetBytes(%x) = %s, expected %s\", test.input, z.ToString(), test.expected)\n\t\t}\n\t}\n}\n"},{"name":"error.gno","body":"package uint256\n\nimport (\n\t\"errors\"\n)\n\nvar (\n\tErrEmptyString = errors.New(\"empty hex string\")\n\tErrSyntax = errors.New(\"invalid hex string\")\n\tErrRange = errors.New(\"number out of range\")\n\tErrMissingPrefix = errors.New(\"hex string without 0x prefix\")\n\tErrEmptyNumber = errors.New(\"hex string \\\"0x\\\"\")\n\tErrLeadingZero = errors.New(\"hex number with leading zero digits\")\n\tErrBig256Range = errors.New(\"hex number \u003e 256 bits\")\n\tErrBadBufferLength = errors.New(\"bad ssz buffer length\")\n\tErrBadEncodedLength = errors.New(\"bad ssz encoded length\")\n\tErrInvalidBase = errors.New(\"invalid base\")\n\tErrInvalidBitSize = errors.New(\"invalid bit size\")\n)\n\ntype u256Error struct {\n\tfn string // function name\n\tinput string\n\terr error\n}\n\nfunc (e *u256Error) Error() string {\n\treturn e.fn + \": \" + e.input + \": \" + e.err.Error()\n}\n\nfunc (e *u256Error) Unwrap() error {\n\treturn e.err\n}\n\nfunc errEmptyString(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrEmptyString}\n}\n\nfunc errSyntax(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrSyntax}\n}\n\nfunc errMissingPrefix(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrMissingPrefix}\n}\n\nfunc errEmptyNumber(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrEmptyNumber}\n}\n\nfunc errLeadingZero(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrLeadingZero}\n}\n\nfunc errRange(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrRange}\n}\n\nfunc errBig256Range(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrBig256Range}\n}\n\nfunc errBadBufferLength(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrBadBufferLength}\n}\n\nfunc errInvalidBase(fn string, base int) error {\n\treturn \u0026u256Error{fn: fn, input: string(base), err: ErrInvalidBase}\n}\n\nfunc errInvalidBitSize(fn string, bitSize int) error {\n\treturn \u0026u256Error{fn: fn, input: string(bitSize), err: ErrInvalidBitSize}\n}\n"},{"name":"mod.gno","body":"package uint256\n\nimport (\n\t\"math/bits\"\n)\n\n// Some utility functions\n\n// Reciprocal computes a 320-bit value representing 1/m\n//\n// Notes:\n// - specialized for m.arr[3] != 0, hence limited to 2^192 \u003c= m \u003c 2^256\n// - returns zero if m.arr[3] == 0\n// - starts with a 32-bit division, refines with newton-raphson iterations\nfunc Reciprocal(m *Uint) (mu [5]uint64) {\n\tif m.arr[3] == 0 {\n\t\treturn mu\n\t}\n\n\ts := bits.LeadingZeros64(m.arr[3]) // Replace with leadingZeros(m) for general case\n\tp := 255 - s // floor(log_2(m)), m\u003e0\n\n\t// 0 or a power of 2?\n\n\t// Check if at least one bit is set in m.arr[2], m.arr[1] or m.arr[0],\n\t// or at least two bits in m.arr[3]\n\n\tif m.arr[0]|m.arr[1]|m.arr[2]|(m.arr[3]\u0026(m.arr[3]-1)) == 0 {\n\n\t\tmu[4] = ^uint64(0) \u003e\u003e uint(p\u002663)\n\t\tmu[3] = ^uint64(0)\n\t\tmu[2] = ^uint64(0)\n\t\tmu[1] = ^uint64(0)\n\t\tmu[0] = ^uint64(0)\n\n\t\treturn mu\n\t}\n\n\t// Maximise division precision by left-aligning divisor\n\n\tvar (\n\t\ty Uint // left-aligned copy of m\n\t\tr0 uint32 // estimate of 2^31/y\n\t)\n\n\ty.Lsh(m, uint(s)) // 1/2 \u003c y \u003c 1\n\n\t// Extract most significant 32 bits\n\n\tyh := uint32(y.arr[3] \u003e\u003e 32)\n\n\tif yh == 0x80000000 { // Avoid overflow in division\n\t\tr0 = 0xffffffff\n\t} else {\n\t\tr0, _ = bits.Div32(0x80000000, 0, yh)\n\t}\n\n\t// First iteration: 32 -\u003e 64\n\n\tt1 := uint64(r0) // 2^31/y\n\tt1 *= t1 // 2^62/y^2\n\tt1, _ = bits.Mul64(t1, y.arr[3]) // 2^62/y^2 * 2^64/y / 2^64 = 2^62/y\n\n\tr1 := uint64(r0) \u003c\u003c 32 // 2^63/y\n\tr1 -= t1 // 2^63/y - 2^62/y = 2^62/y\n\tr1 *= 2 // 2^63/y\n\n\tif (r1 | (y.arr[3] \u003c\u003c 1)) == 0 {\n\t\tr1 = ^uint64(0)\n\t}\n\n\t// Second iteration: 64 -\u003e 128\n\n\t// square: 2^126/y^2\n\ta2h, a2l := bits.Mul64(r1, r1)\n\n\t// multiply by y: e2h:e2l:b2h = 2^126/y^2 * 2^128/y / 2^128 = 2^126/y\n\tb2h, _ := bits.Mul64(a2l, y.arr[2])\n\tc2h, c2l := bits.Mul64(a2l, y.arr[3])\n\td2h, d2l := bits.Mul64(a2h, y.arr[2])\n\te2h, e2l := bits.Mul64(a2h, y.arr[3])\n\n\tb2h, c := bits.Add64(b2h, c2l, 0)\n\te2l, c = bits.Add64(e2l, c2h, c)\n\te2h, _ = bits.Add64(e2h, 0, c)\n\n\t_, c = bits.Add64(b2h, d2l, 0)\n\te2l, c = bits.Add64(e2l, d2h, c)\n\te2h, _ = bits.Add64(e2h, 0, c)\n\n\t// subtract: t2h:t2l = 2^127/y - 2^126/y = 2^126/y\n\tt2l, b := bits.Sub64(0, e2l, 0)\n\tt2h, _ := bits.Sub64(r1, e2h, b)\n\n\t// double: r2h:r2l = 2^127/y\n\tr2l, c := bits.Add64(t2l, t2l, 0)\n\tr2h, _ := bits.Add64(t2h, t2h, c)\n\n\tif (r2h | r2l | (y.arr[3] \u003c\u003c 1)) == 0 {\n\t\tr2h = ^uint64(0)\n\t\tr2l = ^uint64(0)\n\t}\n\n\t// Third iteration: 128 -\u003e 192\n\n\t// square r2 (keep 256 bits): 2^190/y^2\n\ta3h, a3l := bits.Mul64(r2l, r2l)\n\tb3h, b3l := bits.Mul64(r2l, r2h)\n\tc3h, c3l := bits.Mul64(r2h, r2h)\n\n\ta3h, c = bits.Add64(a3h, b3l, 0)\n\tc3l, c = bits.Add64(c3l, b3h, c)\n\tc3h, _ = bits.Add64(c3h, 0, c)\n\n\ta3h, c = bits.Add64(a3h, b3l, 0)\n\tc3l, c = bits.Add64(c3l, b3h, c)\n\tc3h, _ = bits.Add64(c3h, 0, c)\n\n\t// multiply by y: q = 2^190/y^2 * 2^192/y / 2^192 = 2^190/y\n\n\tx0 := a3l\n\tx1 := a3h\n\tx2 := c3l\n\tx3 := c3h\n\n\tvar q0, q1, q2, q3, q4, t0 uint64\n\n\tq0, _ = bits.Mul64(x2, y.arr[0])\n\tq1, t0 = bits.Mul64(x3, y.arr[0])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, _ = bits.Add64(q1, 0, c)\n\n\tt1, _ = bits.Mul64(x1, y.arr[1])\n\tq0, c = bits.Add64(q0, t1, 0)\n\tq2, t0 = bits.Mul64(x3, y.arr[1])\n\tq1, c = bits.Add64(q1, t0, c)\n\tq2, _ = bits.Add64(q2, 0, c)\n\n\tt1, t0 = bits.Mul64(x2, y.arr[1])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tq2, _ = bits.Add64(q2, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, y.arr[2])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tq3, t0 = bits.Mul64(x3, y.arr[2])\n\tq2, c = bits.Add64(q2, t0, c)\n\tq3, _ = bits.Add64(q3, 0, c)\n\n\tt1, _ = bits.Mul64(x0, y.arr[2])\n\tq0, c = bits.Add64(q0, t1, 0)\n\tt1, t0 = bits.Mul64(x2, y.arr[2])\n\tq1, c = bits.Add64(q1, t0, c)\n\tq2, c = bits.Add64(q2, t1, c)\n\tq3, _ = bits.Add64(q3, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, y.arr[3])\n\tq1, c = bits.Add64(q1, t0, 0)\n\tq2, c = bits.Add64(q2, t1, c)\n\tq4, t0 = bits.Mul64(x3, y.arr[3])\n\tq3, c = bits.Add64(q3, t0, c)\n\tq4, _ = bits.Add64(q4, 0, c)\n\n\tt1, t0 = bits.Mul64(x0, y.arr[3])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tt1, t0 = bits.Mul64(x2, y.arr[3])\n\tq2, c = bits.Add64(q2, t0, c)\n\tq3, c = bits.Add64(q3, t1, c)\n\tq4, _ = bits.Add64(q4, 0, c)\n\n\t// subtract: t3 = 2^191/y - 2^190/y = 2^190/y\n\t_, b = bits.Sub64(0, q0, 0)\n\t_, b = bits.Sub64(0, q1, b)\n\tt3l, b := bits.Sub64(0, q2, b)\n\tt3m, b := bits.Sub64(r2l, q3, b)\n\tt3h, _ := bits.Sub64(r2h, q4, b)\n\n\t// double: r3 = 2^191/y\n\tr3l, c := bits.Add64(t3l, t3l, 0)\n\tr3m, c := bits.Add64(t3m, t3m, c)\n\tr3h, _ := bits.Add64(t3h, t3h, c)\n\n\t// Fourth iteration: 192 -\u003e 320\n\n\t// square r3\n\n\ta4h, a4l := bits.Mul64(r3l, r3l)\n\tb4h, b4l := bits.Mul64(r3l, r3m)\n\tc4h, c4l := bits.Mul64(r3l, r3h)\n\td4h, d4l := bits.Mul64(r3m, r3m)\n\te4h, e4l := bits.Mul64(r3m, r3h)\n\tf4h, f4l := bits.Mul64(r3h, r3h)\n\n\tb4h, c = bits.Add64(b4h, c4l, 0)\n\te4l, c = bits.Add64(e4l, c4h, c)\n\te4h, _ = bits.Add64(e4h, 0, c)\n\n\ta4h, c = bits.Add64(a4h, b4l, 0)\n\td4l, c = bits.Add64(d4l, b4h, c)\n\td4h, c = bits.Add64(d4h, e4l, c)\n\tf4l, c = bits.Add64(f4l, e4h, c)\n\tf4h, _ = bits.Add64(f4h, 0, c)\n\n\ta4h, c = bits.Add64(a4h, b4l, 0)\n\td4l, c = bits.Add64(d4l, b4h, c)\n\td4h, c = bits.Add64(d4h, e4l, c)\n\tf4l, c = bits.Add64(f4l, e4h, c)\n\tf4h, _ = bits.Add64(f4h, 0, c)\n\n\t// multiply by y\n\n\tx1, x0 = bits.Mul64(d4h, y.arr[0])\n\tx3, x2 = bits.Mul64(f4h, y.arr[0])\n\tt1, t0 = bits.Mul64(f4l, y.arr[0])\n\tx1, c = bits.Add64(x1, t0, 0)\n\tx2, c = bits.Add64(x2, t1, c)\n\tx3, _ = bits.Add64(x3, 0, c)\n\n\tt1, t0 = bits.Mul64(d4h, y.arr[1])\n\tx1, c = bits.Add64(x1, t0, 0)\n\tx2, c = bits.Add64(x2, t1, c)\n\tx4, t0 := bits.Mul64(f4h, y.arr[1])\n\tx3, c = bits.Add64(x3, t0, c)\n\tx4, _ = bits.Add64(x4, 0, c)\n\tt1, t0 = bits.Mul64(d4l, y.arr[1])\n\tx0, c = bits.Add64(x0, t0, 0)\n\tx1, c = bits.Add64(x1, t1, c)\n\tt1, t0 = bits.Mul64(f4l, y.arr[1])\n\tx2, c = bits.Add64(x2, t0, c)\n\tx3, c = bits.Add64(x3, t1, c)\n\tx4, _ = bits.Add64(x4, 0, c)\n\n\tt1, t0 = bits.Mul64(a4h, y.arr[2])\n\tx0, c = bits.Add64(x0, t0, 0)\n\tx1, c = bits.Add64(x1, t1, c)\n\tt1, t0 = bits.Mul64(d4h, y.arr[2])\n\tx2, c = bits.Add64(x2, t0, c)\n\tx3, c = bits.Add64(x3, t1, c)\n\tx5, t0 := bits.Mul64(f4h, y.arr[2])\n\tx4, c = bits.Add64(x4, t0, c)\n\tx5, _ = bits.Add64(x5, 0, c)\n\tt1, t0 = bits.Mul64(d4l, y.arr[2])\n\tx1, c = bits.Add64(x1, t0, 0)\n\tx2, c = bits.Add64(x2, t1, c)\n\tt1, t0 = bits.Mul64(f4l, y.arr[2])\n\tx3, c = bits.Add64(x3, t0, c)\n\tx4, c = bits.Add64(x4, t1, c)\n\tx5, _ = bits.Add64(x5, 0, c)\n\n\tt1, t0 = bits.Mul64(a4h, y.arr[3])\n\tx1, c = bits.Add64(x1, t0, 0)\n\tx2, c = bits.Add64(x2, t1, c)\n\tt1, t0 = bits.Mul64(d4h, y.arr[3])\n\tx3, c = bits.Add64(x3, t0, c)\n\tx4, c = bits.Add64(x4, t1, c)\n\tx6, t0 := bits.Mul64(f4h, y.arr[3])\n\tx5, c = bits.Add64(x5, t0, c)\n\tx6, _ = bits.Add64(x6, 0, c)\n\tt1, t0 = bits.Mul64(a4l, y.arr[3])\n\tx0, c = bits.Add64(x0, t0, 0)\n\tx1, c = bits.Add64(x1, t1, c)\n\tt1, t0 = bits.Mul64(d4l, y.arr[3])\n\tx2, c = bits.Add64(x2, t0, c)\n\tx3, c = bits.Add64(x3, t1, c)\n\tt1, t0 = bits.Mul64(f4l, y.arr[3])\n\tx4, c = bits.Add64(x4, t0, c)\n\tx5, c = bits.Add64(x5, t1, c)\n\tx6, _ = bits.Add64(x6, 0, c)\n\n\t// subtract\n\t_, b = bits.Sub64(0, x0, 0)\n\t_, b = bits.Sub64(0, x1, b)\n\tr4l, b := bits.Sub64(0, x2, b)\n\tr4k, b := bits.Sub64(0, x3, b)\n\tr4j, b := bits.Sub64(r3l, x4, b)\n\tr4i, b := bits.Sub64(r3m, x5, b)\n\tr4h, _ := bits.Sub64(r3h, x6, b)\n\n\t// Multiply candidate for 1/4y by y, with full precision\n\n\tx0 = r4l\n\tx1 = r4k\n\tx2 = r4j\n\tx3 = r4i\n\tx4 = r4h\n\n\tq1, q0 = bits.Mul64(x0, y.arr[0])\n\tq3, q2 = bits.Mul64(x2, y.arr[0])\n\tq5, q4 := bits.Mul64(x4, y.arr[0])\n\n\tt1, t0 = bits.Mul64(x1, y.arr[0])\n\tq1, c = bits.Add64(q1, t0, 0)\n\tq2, c = bits.Add64(q2, t1, c)\n\tt1, t0 = bits.Mul64(x3, y.arr[0])\n\tq3, c = bits.Add64(q3, t0, c)\n\tq4, c = bits.Add64(q4, t1, c)\n\tq5, _ = bits.Add64(q5, 0, c)\n\n\tt1, t0 = bits.Mul64(x0, y.arr[1])\n\tq1, c = bits.Add64(q1, t0, 0)\n\tq2, c = bits.Add64(q2, t1, c)\n\tt1, t0 = bits.Mul64(x2, y.arr[1])\n\tq3, c = bits.Add64(q3, t0, c)\n\tq4, c = bits.Add64(q4, t1, c)\n\tq6, t0 := bits.Mul64(x4, y.arr[1])\n\tq5, c = bits.Add64(q5, t0, c)\n\tq6, _ = bits.Add64(q6, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, y.arr[1])\n\tq2, c = bits.Add64(q2, t0, 0)\n\tq3, c = bits.Add64(q3, t1, c)\n\tt1, t0 = bits.Mul64(x3, y.arr[1])\n\tq4, c = bits.Add64(q4, t0, c)\n\tq5, c = bits.Add64(q5, t1, c)\n\tq6, _ = bits.Add64(q6, 0, c)\n\n\tt1, t0 = bits.Mul64(x0, y.arr[2])\n\tq2, c = bits.Add64(q2, t0, 0)\n\tq3, c = bits.Add64(q3, t1, c)\n\tt1, t0 = bits.Mul64(x2, y.arr[2])\n\tq4, c = bits.Add64(q4, t0, c)\n\tq5, c = bits.Add64(q5, t1, c)\n\tq7, t0 := bits.Mul64(x4, y.arr[2])\n\tq6, c = bits.Add64(q6, t0, c)\n\tq7, _ = bits.Add64(q7, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, y.arr[2])\n\tq3, c = bits.Add64(q3, t0, 0)\n\tq4, c = bits.Add64(q4, t1, c)\n\tt1, t0 = bits.Mul64(x3, y.arr[2])\n\tq5, c = bits.Add64(q5, t0, c)\n\tq6, c = bits.Add64(q6, t1, c)\n\tq7, _ = bits.Add64(q7, 0, c)\n\n\tt1, t0 = bits.Mul64(x0, y.arr[3])\n\tq3, c = bits.Add64(q3, t0, 0)\n\tq4, c = bits.Add64(q4, t1, c)\n\tt1, t0 = bits.Mul64(x2, y.arr[3])\n\tq5, c = bits.Add64(q5, t0, c)\n\tq6, c = bits.Add64(q6, t1, c)\n\tq8, t0 := bits.Mul64(x4, y.arr[3])\n\tq7, c = bits.Add64(q7, t0, c)\n\tq8, _ = bits.Add64(q8, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, y.arr[3])\n\tq4, c = bits.Add64(q4, t0, 0)\n\tq5, c = bits.Add64(q5, t1, c)\n\tt1, t0 = bits.Mul64(x3, y.arr[3])\n\tq6, c = bits.Add64(q6, t0, c)\n\tq7, c = bits.Add64(q7, t1, c)\n\tq8, _ = bits.Add64(q8, 0, c)\n\n\t// Final adjustment\n\n\t// subtract q from 1/4\n\t_, b = bits.Sub64(0, q0, 0)\n\t_, b = bits.Sub64(0, q1, b)\n\t_, b = bits.Sub64(0, q2, b)\n\t_, b = bits.Sub64(0, q3, b)\n\t_, b = bits.Sub64(0, q4, b)\n\t_, b = bits.Sub64(0, q5, b)\n\t_, b = bits.Sub64(0, q6, b)\n\t_, b = bits.Sub64(0, q7, b)\n\t_, b = bits.Sub64(uint64(1)\u003c\u003c62, q8, b)\n\n\t// decrement the result\n\tx0, t := bits.Sub64(r4l, 1, 0)\n\tx1, t = bits.Sub64(r4k, 0, t)\n\tx2, t = bits.Sub64(r4j, 0, t)\n\tx3, t = bits.Sub64(r4i, 0, t)\n\tx4, _ = bits.Sub64(r4h, 0, t)\n\n\t// commit the decrement if the subtraction underflowed (reciprocal was too large)\n\tif b != 0 {\n\t\tr4h, r4i, r4j, r4k, r4l = x4, x3, x2, x1, x0\n\t}\n\n\t// Shift to correct bit alignment, truncating excess bits\n\n\tp = (p \u0026 63) - 1\n\n\tx0, c = bits.Add64(r4l, r4l, 0)\n\tx1, c = bits.Add64(r4k, r4k, c)\n\tx2, c = bits.Add64(r4j, r4j, c)\n\tx3, c = bits.Add64(r4i, r4i, c)\n\tx4, _ = bits.Add64(r4h, r4h, c)\n\n\tif p \u003c 0 {\n\t\tr4h, r4i, r4j, r4k, r4l = x4, x3, x2, x1, x0\n\t\tp = 0 // avoid negative shift below\n\t}\n\n\t{\n\t\tr := uint(p) // right shift\n\t\tl := uint(64 - r) // left shift\n\n\t\tx0 = (r4l \u003e\u003e r) | (r4k \u003c\u003c l)\n\t\tx1 = (r4k \u003e\u003e r) | (r4j \u003c\u003c l)\n\t\tx2 = (r4j \u003e\u003e r) | (r4i \u003c\u003c l)\n\t\tx3 = (r4i \u003e\u003e r) | (r4h \u003c\u003c l)\n\t\tx4 = (r4h \u003e\u003e r)\n\t}\n\n\tif p \u003e 0 {\n\t\tr4h, r4i, r4j, r4k, r4l = x4, x3, x2, x1, x0\n\t}\n\n\tmu[0] = r4l\n\tmu[1] = r4k\n\tmu[2] = r4j\n\tmu[3] = r4i\n\tmu[4] = r4h\n\n\treturn mu\n}\n\n// reduce4 computes the least non-negative residue of x modulo m\n//\n// requires a four-word modulus (m.arr[3] \u003e 1) and its inverse (mu)\nfunc reduce4(x [8]uint64, m *Uint, mu [5]uint64) (z Uint) {\n\t// NB: Most variable names in the comments match the pseudocode for\n\t// \tBarrett reduction in the Handbook of Applied Cryptography.\n\n\t// q1 = x/2^192\n\n\tx0 := x[3]\n\tx1 := x[4]\n\tx2 := x[5]\n\tx3 := x[6]\n\tx4 := x[7]\n\n\t// q2 = q1 * mu; q3 = q2 / 2^320\n\n\tvar q0, q1, q2, q3, q4, q5, t0, t1, c uint64\n\n\tq0, _ = bits.Mul64(x3, mu[0])\n\tq1, t0 = bits.Mul64(x4, mu[0])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, _ = bits.Add64(q1, 0, c)\n\n\tt1, _ = bits.Mul64(x2, mu[1])\n\tq0, c = bits.Add64(q0, t1, 0)\n\tq2, t0 = bits.Mul64(x4, mu[1])\n\tq1, c = bits.Add64(q1, t0, c)\n\tq2, _ = bits.Add64(q2, 0, c)\n\n\tt1, t0 = bits.Mul64(x3, mu[1])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tq2, _ = bits.Add64(q2, 0, c)\n\n\tt1, t0 = bits.Mul64(x2, mu[2])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tq3, t0 = bits.Mul64(x4, mu[2])\n\tq2, c = bits.Add64(q2, t0, c)\n\tq3, _ = bits.Add64(q3, 0, c)\n\n\tt1, _ = bits.Mul64(x1, mu[2])\n\tq0, c = bits.Add64(q0, t1, 0)\n\tt1, t0 = bits.Mul64(x3, mu[2])\n\tq1, c = bits.Add64(q1, t0, c)\n\tq2, c = bits.Add64(q2, t1, c)\n\tq3, _ = bits.Add64(q3, 0, c)\n\n\tt1, _ = bits.Mul64(x0, mu[3])\n\tq0, c = bits.Add64(q0, t1, 0)\n\tt1, t0 = bits.Mul64(x2, mu[3])\n\tq1, c = bits.Add64(q1, t0, c)\n\tq2, c = bits.Add64(q2, t1, c)\n\tq4, t0 = bits.Mul64(x4, mu[3])\n\tq3, c = bits.Add64(q3, t0, c)\n\tq4, _ = bits.Add64(q4, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, mu[3])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tt1, t0 = bits.Mul64(x3, mu[3])\n\tq2, c = bits.Add64(q2, t0, c)\n\tq3, c = bits.Add64(q3, t1, c)\n\tq4, _ = bits.Add64(q4, 0, c)\n\n\tt1, t0 = bits.Mul64(x0, mu[4])\n\t_, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tt1, t0 = bits.Mul64(x2, mu[4])\n\tq2, c = bits.Add64(q2, t0, c)\n\tq3, c = bits.Add64(q3, t1, c)\n\tq5, t0 = bits.Mul64(x4, mu[4])\n\tq4, c = bits.Add64(q4, t0, c)\n\tq5, _ = bits.Add64(q5, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, mu[4])\n\tq1, c = bits.Add64(q1, t0, 0)\n\tq2, c = bits.Add64(q2, t1, c)\n\tt1, t0 = bits.Mul64(x3, mu[4])\n\tq3, c = bits.Add64(q3, t0, c)\n\tq4, c = bits.Add64(q4, t1, c)\n\tq5, _ = bits.Add64(q5, 0, c)\n\n\t// Drop the fractional part of q3\n\n\tq0 = q1\n\tq1 = q2\n\tq2 = q3\n\tq3 = q4\n\tq4 = q5\n\n\t// r1 = x mod 2^320\n\n\tx0 = x[0]\n\tx1 = x[1]\n\tx2 = x[2]\n\tx3 = x[3]\n\tx4 = x[4]\n\n\t// r2 = q3 * m mod 2^320\n\n\tvar r0, r1, r2, r3, r4 uint64\n\n\tr4, r3 = bits.Mul64(q0, m.arr[3])\n\t_, t0 = bits.Mul64(q1, m.arr[3])\n\tr4, _ = bits.Add64(r4, t0, 0)\n\n\tt1, r2 = bits.Mul64(q0, m.arr[2])\n\tr3, c = bits.Add64(r3, t1, 0)\n\t_, t0 = bits.Mul64(q2, m.arr[2])\n\tr4, _ = bits.Add64(r4, t0, c)\n\n\tt1, t0 = bits.Mul64(q1, m.arr[2])\n\tr3, c = bits.Add64(r3, t0, 0)\n\tr4, _ = bits.Add64(r4, t1, c)\n\n\tt1, r1 = bits.Mul64(q0, m.arr[1])\n\tr2, c = bits.Add64(r2, t1, 0)\n\tt1, t0 = bits.Mul64(q2, m.arr[1])\n\tr3, c = bits.Add64(r3, t0, c)\n\tr4, _ = bits.Add64(r4, t1, c)\n\n\tt1, t0 = bits.Mul64(q1, m.arr[1])\n\tr2, c = bits.Add64(r2, t0, 0)\n\tr3, c = bits.Add64(r3, t1, c)\n\t_, t0 = bits.Mul64(q3, m.arr[1])\n\tr4, _ = bits.Add64(r4, t0, c)\n\n\tt1, r0 = bits.Mul64(q0, m.arr[0])\n\tr1, c = bits.Add64(r1, t1, 0)\n\tt1, t0 = bits.Mul64(q2, m.arr[0])\n\tr2, c = bits.Add64(r2, t0, c)\n\tr3, c = bits.Add64(r3, t1, c)\n\t_, t0 = bits.Mul64(q4, m.arr[0])\n\tr4, _ = bits.Add64(r4, t0, c)\n\n\tt1, t0 = bits.Mul64(q1, m.arr[0])\n\tr1, c = bits.Add64(r1, t0, 0)\n\tr2, c = bits.Add64(r2, t1, c)\n\tt1, t0 = bits.Mul64(q3, m.arr[0])\n\tr3, c = bits.Add64(r3, t0, c)\n\tr4, _ = bits.Add64(r4, t1, c)\n\n\t// r = r1 - r2\n\n\tvar b uint64\n\n\tr0, b = bits.Sub64(x0, r0, 0)\n\tr1, b = bits.Sub64(x1, r1, b)\n\tr2, b = bits.Sub64(x2, r2, b)\n\tr3, b = bits.Sub64(x3, r3, b)\n\tr4, b = bits.Sub64(x4, r4, b)\n\n\t// if r\u003c0 then r+=m\n\n\tif b != 0 {\n\t\tr0, c = bits.Add64(r0, m.arr[0], 0)\n\t\tr1, c = bits.Add64(r1, m.arr[1], c)\n\t\tr2, c = bits.Add64(r2, m.arr[2], c)\n\t\tr3, c = bits.Add64(r3, m.arr[3], c)\n\t\tr4, _ = bits.Add64(r4, 0, c)\n\t}\n\n\t// while (r\u003e=m) r-=m\n\n\tfor {\n\t\t// q = r - m\n\t\tq0, b = bits.Sub64(r0, m.arr[0], 0)\n\t\tq1, b = bits.Sub64(r1, m.arr[1], b)\n\t\tq2, b = bits.Sub64(r2, m.arr[2], b)\n\t\tq3, b = bits.Sub64(r3, m.arr[3], b)\n\t\tq4, b = bits.Sub64(r4, 0, b)\n\n\t\t// if borrow break\n\t\tif b != 0 {\n\t\t\tbreak\n\t\t}\n\n\t\t// r = q\n\t\tr4, r3, r2, r1, r0 = q4, q3, q2, q1, q0\n\t}\n\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = r3, r2, r1, r0\n\n\treturn z\n}\n"},{"name":"uint256.gno","body":"// Ported from https://github.com/holiman/uint256\n// This package provides a 256-bit unsigned integer type, Uint256, and associated functions.\npackage uint256\n\nimport (\n\t\"errors\"\n\t\"math/bits\"\n\t\"strconv\"\n)\n\nconst (\n\tMaxUint64 = 1\u003c\u003c64 - 1\n\tuintSize = 32 \u003c\u003c (^uint(0) \u003e\u003e 63)\n)\n\n// Uint is represented as an array of 4 uint64, in little-endian order,\n// so that Uint[3] is the most significant, and Uint[0] is the least significant\ntype Uint struct {\n\tarr [4]uint64\n}\n\n// NewUint returns a new initialized Uint.\nfunc NewUint(val uint64) *Uint {\n\tz := \u0026Uint{arr: [4]uint64{val, 0, 0, 0}}\n\treturn z\n}\n\n// Zero returns a new Uint initialized to zero.\nfunc Zero() *Uint {\n\treturn NewUint(0)\n}\n\n// One returns a new Uint initialized to one.\nfunc One() *Uint {\n\treturn NewUint(1)\n}\n\n// SetAllOne sets all the bits of z to 1\nfunc (z *Uint) SetAllOne() *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = MaxUint64, MaxUint64, MaxUint64, MaxUint64\n\treturn z\n}\n\n// Set sets z to x and returns z.\nfunc (z *Uint) Set(x *Uint) *Uint {\n\t*z = *x\n\n\treturn z\n}\n\n// SetOne sets z to 1\nfunc (z *Uint) SetOne() *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, 0, 0, 1\n\treturn z\n}\n\nconst twoPow256Sub1 = \"115792089237316195423570985008687907853269984665640564039457584007913129639935\"\n\n// SetFromDecimal sets z from the given string, interpreted as a decimal number.\n// OBS! This method is _not_ strictly identical to the (*big.Uint).SetString(..., 10) method.\n// Notable differences:\n// - This method does not accept underscore input, e.g. \"100_000\",\n// - This method does not accept negative zero as valid, e.g \"-0\",\n// - (this method does not accept any negative input as valid))\nfunc (z *Uint) SetFromDecimal(s string) (err error) {\n\t// Remove max one leading +\n\tif len(s) \u003e 0 \u0026\u0026 s[0] == '+' {\n\t\ts = s[1:]\n\t}\n\t// Remove any number of leading zeroes\n\tif len(s) \u003e 0 \u0026\u0026 s[0] == '0' {\n\t\tvar i int\n\t\tvar c rune\n\t\tfor i, c = range s {\n\t\t\tif c != '0' {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\ts = s[i:]\n\t}\n\tif len(s) \u003c len(twoPow256Sub1) {\n\t\treturn z.fromDecimal(s)\n\t}\n\tif len(s) == len(twoPow256Sub1) {\n\t\tif s \u003e twoPow256Sub1 {\n\t\t\treturn ErrBig256Range\n\t\t}\n\t\treturn z.fromDecimal(s)\n\t}\n\treturn ErrBig256Range\n}\n\n// FromDecimal is a convenience-constructor to create an Uint from a\n// decimal (base 10) string. Numbers larger than 256 bits are not accepted.\nfunc FromDecimal(decimal string) (*Uint, error) {\n\tvar z Uint\n\tif err := z.SetFromDecimal(decimal); err != nil {\n\t\treturn nil, err\n\t}\n\treturn \u0026z, nil\n}\n\n// MustFromDecimal is a convenience-constructor to create an Uint from a\n// decimal (base 10) string.\n// Returns a new Uint and panics if any error occurred.\nfunc MustFromDecimal(decimal string) *Uint {\n\tvar z Uint\n\tif err := z.SetFromDecimal(decimal); err != nil {\n\t\tpanic(err)\n\t}\n\treturn \u0026z\n}\n\n// multipliers holds the values that are needed for fromDecimal\nvar multipliers = [5]*Uint{\n\tnil, // represents first round, no multiplication needed\n\t{[4]uint64{10000000000000000000, 0, 0, 0}}, // 10 ^ 19\n\t{[4]uint64{687399551400673280, 5421010862427522170, 0, 0}}, // 10 ^ 38\n\t{[4]uint64{5332261958806667264, 17004971331911604867, 2938735877055718769, 0}}, // 10 ^ 57\n\t{[4]uint64{0, 8607968719199866880, 532749306367912313, 1593091911132452277}}, // 10 ^ 76\n}\n\n// fromDecimal is a helper function to only ever be called via SetFromDecimal\n// this function takes a string and chunks it up, calling ParseUint on it up to 5 times\n// these chunks are then multiplied by the proper power of 10, then added together.\nfunc (z *Uint) fromDecimal(bs string) error {\n\t// first clear the input\n\tz.Clear()\n\t// the maximum value of uint64 is 18446744073709551615, which is 20 characters\n\t// one less means that a string of 19 9's is always within the uint64 limit\n\tvar (\n\t\tnum uint64\n\t\terr error\n\t\tremaining = len(bs)\n\t)\n\tif remaining == 0 {\n\t\treturn errors.New(\"EOF\")\n\t}\n\t// We proceed in steps of 19 characters (nibbles), from least significant to most significant.\n\t// This means that the first (up to) 19 characters do not need to be multiplied.\n\t// In the second iteration, our slice of 19 characters needs to be multipleied\n\t// by a factor of 10^19. Et cetera.\n\tfor i, mult := range multipliers {\n\t\tif remaining \u003c= 0 {\n\t\t\treturn nil // Done\n\t\t} else if remaining \u003e 19 {\n\t\t\tnum, err = strconv.ParseUint(bs[remaining-19:remaining], 10, 64)\n\t\t} else {\n\t\t\t// Final round\n\t\t\tnum, err = strconv.ParseUint(bs, 10, 64)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t// add that number to our running total\n\t\tif i == 0 {\n\t\t\tz.SetUint64(num)\n\t\t} else {\n\t\t\tbase := NewUint(num)\n\t\t\tz.Add(z, base.Mul(base, mult))\n\t\t}\n\t\t// Chop off another 19 characters\n\t\tif remaining \u003e 19 {\n\t\t\tbs = bs[0 : remaining-19]\n\t\t}\n\t\tremaining -= 19\n\t}\n\treturn nil\n}\n\n// Byte sets z to the value of the byte at position n,\n// with 'z' considered as a big-endian 32-byte integer\n// if 'n' \u003e 32, f is set to 0\n// Example: f = '5', n=31 =\u003e 5\nfunc (z *Uint) Byte(n *Uint) *Uint {\n\t// in z, z.arr[0] is the least significant\n\tif number, overflow := n.Uint64WithOverflow(); !overflow {\n\t\tif number \u003c 32 {\n\t\t\tnumber := z.arr[4-1-number/8]\n\t\t\toffset := (n.arr[0] \u0026 0x7) \u003c\u003c 3 // 8*(n.d % 8)\n\t\t\tz.arr[0] = (number \u0026 (0xff00000000000000 \u003e\u003e offset)) \u003e\u003e (56 - offset)\n\t\t\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\t\t\treturn z\n\t\t}\n\t}\n\n\treturn z.Clear()\n}\n\n// BitLen returns the number of bits required to represent z\nfunc (z *Uint) BitLen() int {\n\tswitch {\n\tcase z.arr[3] != 0:\n\t\treturn 192 + bits.Len64(z.arr[3])\n\tcase z.arr[2] != 0:\n\t\treturn 128 + bits.Len64(z.arr[2])\n\tcase z.arr[1] != 0:\n\t\treturn 64 + bits.Len64(z.arr[1])\n\tdefault:\n\t\treturn bits.Len64(z.arr[0])\n\t}\n}\n\n// ByteLen returns the number of bytes required to represent z\nfunc (z *Uint) ByteLen() int {\n\treturn (z.BitLen() + 7) / 8\n}\n\n// Clear sets z to 0\nfunc (z *Uint) Clear() *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, 0, 0, 0\n\treturn z\n}\n\nconst (\n\t// hextable = \"0123456789abcdef\"\n\tbintable = \"\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\a\\b\\t\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\n\\v\\f\\r\\x0e\\x0f\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\n\\v\\f\\r\\x0e\\x0f\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\"\n\tbadNibble = 0xff\n)\n\n// SetFromHex sets z from the given string, interpreted as a hexadecimal number.\n// OBS! This method is _not_ strictly identical to the (*big.Int).SetString(..., 16) method.\n// Notable differences:\n// - This method _require_ \"0x\" or \"0X\" prefix.\n// - This method does not accept zero-prefixed hex, e.g. \"0x0001\"\n// - This method does not accept underscore input, e.g. \"100_000\",\n// - This method does not accept negative zero as valid, e.g \"-0x0\",\n// - (this method does not accept any negative input as valid)\nfunc (z *Uint) SetFromHex(hex string) error {\n\treturn z.fromHex(hex)\n}\n\n// fromHex is the internal implementation of parsing a hex-string.\nfunc (z *Uint) fromHex(hex string) error {\n\tif err := checkNumberS(hex); err != nil {\n\t\treturn err\n\t}\n\tif len(hex) \u003e 66 {\n\t\treturn ErrBig256Range\n\t}\n\tz.Clear()\n\tend := len(hex)\n\tfor i := 0; i \u003c 4; i++ {\n\t\tstart := end - 16\n\t\tif start \u003c 2 {\n\t\t\tstart = 2\n\t\t}\n\t\tfor ri := start; ri \u003c end; ri++ {\n\t\t\tnib := bintable[hex[ri]]\n\t\t\tif nib == badNibble {\n\t\t\t\treturn ErrSyntax\n\t\t\t}\n\t\t\tz.arr[i] = z.arr[i] \u003c\u003c 4\n\t\t\tz.arr[i] += uint64(nib)\n\t\t}\n\t\tend = start\n\t}\n\treturn nil\n}\n\n// FromHex is a convenience-constructor to create an Uint from\n// a hexadecimal string. The string is required to be '0x'-prefixed\n// Numbers larger than 256 bits are not accepted.\nfunc FromHex(hex string) (*Uint, error) {\n\tvar z Uint\n\tif err := z.fromHex(hex); err != nil {\n\t\treturn nil, err\n\t}\n\treturn \u0026z, nil\n}\n\n// MustFromHex is a convenience-constructor to create an Uint from\n// a hexadecimal string.\n// Returns a new Uint and panics if any error occurred.\nfunc MustFromHex(hex string) *Uint {\n\tvar z Uint\n\tif err := z.fromHex(hex); err != nil {\n\t\tpanic(err)\n\t}\n\treturn \u0026z\n}\n\n// Clone creates a new Uint identical to z\nfunc (z *Uint) Clone() *Uint {\n\tvar x Uint\n\tx.arr[0] = z.arr[0]\n\tx.arr[1] = z.arr[1]\n\tx.arr[2] = z.arr[2]\n\tx.arr[3] = z.arr[3]\n\n\treturn \u0026x\n}\n"},{"name":"uint256_test.gno","body":"package uint256\n\nimport (\n\t\"testing\"\n)\n\nfunc TestSetAllOne(t *testing.T) {\n\tz := Zero()\n\tz.SetAllOne()\n\tif z.ToString() != twoPow256Sub1 {\n\t\tt.Errorf(\"Expected all ones, got %s\", z.ToString())\n\t}\n}\n\nfunc TestByte(t *testing.T) {\n\ttests := []struct {\n\t\tinput string\n\t\tposition uint64\n\t\texpected byte\n\t}{\n\t\t{\"0x1000000000000000000000000000000000000000000000000000000000000000\", 0, 16},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 0, 255},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 31, 255},\n\t}\n\n\tfor i, tt := range tests {\n\t\tz, _ := FromHex(tt.input)\n\t\tn := NewUint(tt.position)\n\t\tresult := z.Byte(n)\n\n\t\tif result.arr[0] != uint64(tt.expected) {\n\t\t\tt.Errorf(\"Test case %d failed. Input: %s, Position: %d, Expected: %d, Got: %d\",\n\t\t\t\ti, tt.input, tt.position, tt.expected, result.arr[0])\n\t\t}\n\n\t\t// check other array elements are 0\n\t\tif result.arr[1] != 0 || result.arr[2] != 0 || result.arr[3] != 0 {\n\t\t\tt.Errorf(\"Test case %d failed. Non-zero values in upper bytes\", i)\n\t\t}\n\t}\n\n\t// overflow\n\tz, _ := FromHex(\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\")\n\tn := NewUint(32)\n\tresult := z.Byte(n)\n\n\tif !result.IsZero() {\n\t\tt.Errorf(\"Expected zero for position \u003e= 32, got %v\", result)\n\t}\n}\n\nfunc TestBitLen(t *testing.T) {\n\ttests := []struct {\n\t\tinput string\n\t\texpected int\n\t}{\n\t\t{\"0x0\", 0},\n\t\t{\"0x1\", 1},\n\t\t{\"0xff\", 8},\n\t\t{\"0x100\", 9},\n\t\t{\"0xffff\", 16},\n\t\t{\"0x10000\", 17},\n\t\t{\"0xffffffffffffffff\", 64},\n\t\t{\"0x10000000000000000\", 65},\n\t\t{\"0xffffffffffffffffffffffffffffffff\", 128},\n\t\t{\"0x100000000000000000000000000000000\", 129},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 256},\n\t}\n\n\tfor i, tt := range tests {\n\t\tz, _ := FromHex(tt.input)\n\t\tresult := z.BitLen()\n\n\t\tif result != tt.expected {\n\t\t\tt.Errorf(\"Test case %d failed. Input: %s, Expected: %d, Got: %d\",\n\t\t\t\ti, tt.input, tt.expected, result)\n\t\t}\n\t}\n}\n\nfunc TestByteLen(t *testing.T) {\n\ttests := []struct {\n\t\tinput string\n\t\texpected int\n\t}{\n\t\t{\"0x0\", 0},\n\t\t{\"0x1\", 1},\n\t\t{\"0xff\", 1},\n\t\t{\"0x100\", 2},\n\t\t{\"0xffff\", 2},\n\t\t{\"0x10000\", 3},\n\t\t{\"0xffffffffffffffff\", 8},\n\t\t{\"0x10000000000000000\", 9},\n\t\t{\"0xffffffffffffffffffffffffffffffff\", 16},\n\t\t{\"0x100000000000000000000000000000000\", 17},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 32},\n\t}\n\n\tfor i, tt := range tests {\n\t\tz, _ := FromHex(tt.input)\n\t\tresult := z.ByteLen()\n\n\t\tif result != tt.expected {\n\t\t\tt.Errorf(\"Test case %d failed. Input: %s, Expected: %d, Got: %d\",\n\t\t\t\ti, tt.input, tt.expected, result)\n\t\t}\n\t}\n}\n\nfunc TestClone(t *testing.T) {\n\ttests := []struct {\n\t\tinput string\n\t\texpected string\n\t}{\n\t\t{\"0x1\", \"1\"},\n\t\t{\"0x100\", \"256\"},\n\t\t{\"0x10000000000000000\", \"18446744073709551616\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tz, _ := FromHex(tt.input)\n\t\tresult := z.Clone()\n\t\tif result.ToString() != tt.expected {\n\t\t\tt.Errorf(\"Test %s failed. Expected %s, got %s\", tt.input, tt.expected, result.ToString())\n\t\t}\n\t}\n}\n"},{"name":"utils.gno","body":"package uint256\n\nfunc checkNumberS(input string) error {\n\tconst fn = \"UnmarshalText\"\n\tl := len(input)\n\tif l == 0 {\n\t\treturn errEmptyString(fn, input)\n\t}\n\tif l \u003c 2 || input[0] != '0' ||\n\t\t(input[1] != 'x' \u0026\u0026 input[1] != 'X') {\n\t\treturn errMissingPrefix(fn, input)\n\t}\n\tif l == 2 {\n\t\treturn errEmptyNumber(fn, input)\n\t}\n\tif len(input) \u003e 3 \u0026\u0026 input[2] == '0' {\n\t\treturn errLeadingZero(fn, input)\n\t}\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_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"int256","path":"gno.land/p/demo/int256","files":[{"name":"LICENSE","body":"MIT License\n\nCopyright (c) 2023 Trịnh Đức Bảo Linh(Kevin)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."},{"name":"README.md","body":"# Fixed size signed 256-bit math library\n\n1. This is a library specialized at replacing the big.Int library for math based on signed 256-bit types.\n2. It uses [uint256](https://github.com/gnolang/gno/tree/master/examples/gno.land/p/demo/uint256) as the underlying type.\n\nported from [mempooler/int256](https://github.com/mempooler/int256)\n"},{"name":"absolute.gno","body":"package int256\n\nimport \"gno.land/p/demo/uint256\"\n\n// Abs returns |z|\nfunc (z *Int) Abs() *uint256.Uint {\n\treturn z.abs.Clone()\n}\n\n// AbsGt returns true if |z| \u003e x, where x is a uint256\nfunc (z *Int) AbsGt(x *uint256.Uint) bool {\n\treturn z.abs.Gt(x)\n}\n\n// AbsLt returns true if |z| \u003c x, where x is a uint256\nfunc (z *Int) AbsLt(x *uint256.Uint) bool {\n\treturn z.abs.Lt(x)\n}\n"},{"name":"absolute_test.gno","body":"package int256\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uint256\"\n)\n\nfunc TestAbs(t *testing.T) {\n\ttests := []struct {\n\t\tx, want string\n\t}{\n\t\t{\"0\", \"0\"},\n\t\t{\"1\", \"1\"},\n\t\t{\"-1\", \"1\"},\n\t\t{\"-2\", \"2\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.Abs()\n\n\t\tif got.ToString() != tc.want {\n\t\t\tt.Errorf(\"Abs(%s) = %v, want %v\", tc.x, got.ToString(), tc.want)\n\t\t}\n\t}\n}\n\nfunc TestAbsGt(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"0\", \"false\"},\n\t\t{\"1\", \"0\", \"true\"},\n\t\t{\"-1\", \"0\", \"true\"},\n\t\t{\"-1\", \"1\", \"false\"},\n\t\t{\"-2\", \"1\", \"true\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"0\", \"true\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"1\", \"true\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"false\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := uint256.FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.AbsGt(y)\n\n\t\tif got != (tc.want == \"true\") {\n\t\t\tt.Errorf(\"AbsGt(%s, %s) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestAbsLt(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"0\", \"false\"},\n\t\t{\"1\", \"0\", \"false\"},\n\t\t{\"-1\", \"0\", \"false\"},\n\t\t{\"-1\", \"1\", \"false\"},\n\t\t{\"-2\", \"1\", \"false\"},\n\t\t{\"-5\", \"10\", \"true\"},\n\t\t{\"31330\", \"31337\", \"true\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"0\", \"false\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"1\", \"false\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"false\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := uint256.FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.AbsLt(y)\n\n\t\tif got != (tc.want == \"true\") {\n\t\t\tt.Errorf(\"AbsLt(%s, %s) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n"},{"name":"arithmetic.gno","body":"package int256\n\nimport \"gno.land/p/demo/uint256\"\n\nfunc (z *Int) Add(x, y *Int) *Int {\n\tz.initiateAbs()\n\n\tif x.neg == y.neg {\n\t\t// If both numbers have the same sign, add their absolute values\n\t\tz.abs.Add(x.abs, y.abs)\n\t\tz.neg = x.neg\n\t} else {\n\t\tswitch x.abs.Cmp(y.abs) {\n\t\tcase 1: // x \u003e y\n\t\t\tz.abs.Sub(x.abs, y.abs)\n\t\t\tz.neg = x.neg\n\t\tcase -1: // x \u003c y\n\t\t\tz.abs.Sub(y.abs, x.abs)\n\t\t\tz.neg = y.neg\n\t\tcase 0: // x == y\n\t\t\tz.abs = uint256.NewUint(0)\n\t\t}\n\t}\n\n\treturn z\n}\n\n// AddUint256 set z to the sum x + y, where y is a uint256, and returns z\nfunc (z *Int) AddUint256(x *Int, y *uint256.Uint) *Int {\n\tif x.neg {\n\t\tif x.abs.Gt(y) {\n\t\t\tz.abs.Sub(x.abs, y)\n\t\t\tz.neg = true\n\t\t} else {\n\t\t\tz.abs.Sub(y, x.abs)\n\t\t\tz.neg = false\n\t\t}\n\t} else {\n\t\tz.abs.Add(x.abs, y)\n\t\tz.neg = false\n\t}\n\treturn z\n}\n\n// Sets z to the sum x + y, where z and x are uint256s and y is an int256.\nfunc AddDelta(z, x *uint256.Uint, y *Int) {\n\tif y.neg {\n\t\tz.Sub(x, y.abs)\n\t} else {\n\t\tz.Add(x, y.abs)\n\t}\n}\n\n// Sets z to the sum x + y, where z and x are uint256s and y is an int256.\nfunc AddDeltaOverflow(z, x *uint256.Uint, y *Int) bool {\n\tvar overflow bool\n\tif y.neg {\n\t\t_, overflow = z.SubOverflow(x, y.abs)\n\t} else {\n\t\t_, overflow = z.AddOverflow(x, y.abs)\n\t}\n\treturn overflow\n}\n\n// Sub sets z to the difference x-y and returns z.\nfunc (z *Int) Sub(x, y *Int) *Int {\n\tz.initiateAbs()\n\n\tif x.neg != y.neg {\n\t\t// If sign are different, add the absolute values\n\t\tz.abs.Add(x.abs, y.abs)\n\t\tz.neg = x.neg\n\t} else {\n\t\tswitch x.abs.Cmp(y.abs) {\n\t\tcase 1: // x \u003e y\n\t\t\tz.abs.Sub(x.abs, y.abs)\n\t\t\tz.neg = x.neg\n\t\tcase -1: // x \u003c y\n\t\t\tz.abs.Sub(y.abs, x.abs)\n\t\t\tz.neg = !x.neg\n\t\tcase 0: // x == y\n\t\t\tz.abs = uint256.NewUint(0)\n\t\t}\n\t}\n\n\t// Ensure zero is always positive\n\tif z.abs.IsZero() {\n\t\tz.neg = false\n\t}\n\treturn z\n}\n\n// SubUint256 set z to the difference x - y, where y is a uint256, and returns z\nfunc (z *Int) SubUint256(x *Int, y *uint256.Uint) *Int {\n\tif x.neg {\n\t\tz.abs.Add(x.abs, y)\n\t\tz.neg = true\n\t} else {\n\t\tif x.abs.Lt(y) {\n\t\t\tz.abs.Sub(y, x.abs)\n\t\t\tz.neg = true\n\t\t} else {\n\t\t\tz.abs.Sub(x.abs, y)\n\t\t\tz.neg = false\n\t\t}\n\t}\n\treturn z\n}\n\n// Mul sets z to the product x*y and returns z.\nfunc (z *Int) Mul(x, y *Int) *Int {\n\tz.initiateAbs()\n\n\tz.abs = z.abs.Mul(x.abs, y.abs)\n\tz.neg = x.neg != y.neg \u0026\u0026 !z.abs.IsZero() // 0 has no sign\n\treturn z\n}\n\n// MulUint256 sets z to the product x*y, where y is a uint256, and returns z\nfunc (z *Int) MulUint256(x *Int, y *uint256.Uint) *Int {\n\tz.abs.Mul(x.abs, y)\n\tif z.abs.IsZero() {\n\t\tz.neg = false\n\t} else {\n\t\tz.neg = x.neg\n\t}\n\treturn z\n}\n\n// Div sets z to the quotient x/y for y != 0 and returns z.\nfunc (z *Int) Div(x, y *Int) *Int {\n\tz.initiateAbs()\n\n\tif y.abs.IsZero() {\n\t\tpanic(\"division by zero\")\n\t}\n\n\tz.abs.Div(x.abs, y.abs)\n\tz.neg = (x.neg != y.neg) \u0026\u0026 !z.abs.IsZero() // 0 has no sign\n\n\treturn z\n}\n\n// DivUint256 sets z to the quotient x/y, where y is a uint256, and returns z\n// If y == 0, z is set to 0\nfunc (z *Int) DivUint256(x *Int, y *uint256.Uint) *Int {\n\tz.abs.Div(x.abs, y)\n\tif z.abs.IsZero() {\n\t\tz.neg = false\n\t} else {\n\t\tz.neg = x.neg\n\t}\n\treturn z\n}\n\n// Quo sets z to the quotient x/y for y != 0 and returns z.\n// If y == 0, a division-by-zero run-time panic occurs.\n// OBS: differs from mempooler int256, we need to panic manually if y == 0\n// Quo implements truncated division (like Go); see QuoRem for more details.\nfunc (z *Int) Quo(x, y *Int) *Int {\n\tif y.IsZero() {\n\t\tpanic(\"division by zero\")\n\t}\n\n\tz.initiateAbs()\n\n\tz.abs = z.abs.Div(x.abs, y.abs)\n\tz.neg = !(z.abs.IsZero()) \u0026\u0026 x.neg != y.neg // 0 has no sign\n\treturn z\n}\n\n// Rem sets z to the remainder x%y for y != 0 and returns z.\n// If y == 0, a division-by-zero run-time panic occurs.\n// OBS: differs from mempooler int256, we need to panic manually if y == 0\n// Rem implements truncated modulus (like Go); see QuoRem for more details.\nfunc (z *Int) Rem(x, y *Int) *Int {\n\tif y.IsZero() {\n\t\tpanic(\"division by zero\")\n\t}\n\n\tz.initiateAbs()\n\n\tz.abs.Mod(x.abs, y.abs)\n\tz.neg = z.abs.Sign() \u003e 0 \u0026\u0026 x.neg // 0 has no sign\n\treturn z\n}\n\n// Mod sets z to the modulus x%y for y != 0 and returns z.\n// If y == 0, z is set to 0 (OBS: differs from the big.Int)\nfunc (z *Int) Mod(x, y *Int) *Int {\n\tif x.neg {\n\t\tz.abs.Div(x.abs, y.abs)\n\t\tz.abs.Add(z.abs, one)\n\t\tz.abs.Mul(z.abs, y.abs)\n\t\tz.abs.Sub(z.abs, x.abs)\n\t\tz.abs.Mod(z.abs, y.abs)\n\t} else {\n\t\tz.abs.Mod(x.abs, y.abs)\n\t}\n\tz.neg = false\n\treturn z\n}\n"},{"name":"arithmetic_test.gno","body":"package int256\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uint256\"\n)\n\nfunc TestAdd(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"1\", \"1\"},\n\t\t{\"1\", \"0\", \"1\"},\n\t\t{\"1\", \"1\", \"2\"},\n\t\t{\"1\", \"2\", \"3\"},\n\t\t// NEGATIVE\n\t\t{\"-1\", \"1\", \"0\"},\n\t\t{\"1\", \"-1\", \"0\"},\n\t\t{\"3\", \"-3\", \"0\"},\n\t\t{\"-1\", \"-1\", \"-2\"},\n\t\t{\"-1\", \"-2\", \"-3\"},\n\t\t{\"-1\", \"3\", \"2\"},\n\t\t{\"3\", \"-1\", \"2\"},\n\t\t// OVERFLOW\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"1\", \"0\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.Add(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Add(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestAddUint256(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"1\", \"1\"},\n\t\t{\"1\", \"0\", \"1\"},\n\t\t{\"1\", \"1\", \"2\"},\n\t\t{\"1\", \"2\", \"3\"},\n\t\t{\"-1\", \"1\", \"0\"},\n\t\t{\"-1\", \"3\", \"2\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639934\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"1\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"115792089237316195423570985008687907853269984665640564039457584007913129639934\", \"-1\"},\n\t\t// OVERFLOW\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"0\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := uint256.FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.AddUint256(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"AddUint256(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestAddDelta(t *testing.T) {\n\ttests := []struct {\n\t\tz, x, y, want string\n\t}{\n\t\t{\"0\", \"0\", \"0\", \"0\"},\n\t\t{\"0\", \"0\", \"1\", \"1\"},\n\t\t{\"0\", \"1\", \"0\", \"1\"},\n\t\t{\"0\", \"1\", \"1\", \"2\"},\n\t\t{\"1\", \"2\", \"3\", \"5\"},\n\t\t{\"5\", \"10\", \"-3\", \"7\"},\n\t\t// underflow\n\t\t{\"1\", \"2\", \"-3\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tz, err := uint256.FromDecimal(tc.z)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tx, err := uint256.FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := uint256.FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tAddDelta(z, x, y)\n\n\t\tif z.Neq(want) {\n\t\t\tt.Errorf(\"AddDelta(%s, %s, %s) = %v, want %v\", tc.z, tc.x, tc.y, z.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestAddDeltaOverflow(t *testing.T) {\n\ttests := []struct {\n\t\tz, x, y string\n\t\twant bool\n\t}{\n\t\t{\"0\", \"0\", \"0\", false},\n\t\t// underflow\n\t\t{\"1\", \"2\", \"-3\", true},\n\t}\n\n\tfor _, tc := range tests {\n\t\tz, err := uint256.FromDecimal(tc.z)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tx, err := uint256.FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tresult := AddDeltaOverflow(z, x, y)\n\t\tif result != tc.want {\n\t\t\tt.Errorf(\"AddDeltaOverflow(%s, %s, %s) = %v, want %v\", tc.z, tc.x, tc.y, result, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestSub(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"1\", \"0\", \"1\"},\n\t\t{\"1\", \"1\", \"0\"},\n\t\t{\"-1\", \"1\", \"-2\"},\n\t\t{\"1\", \"-1\", \"2\"},\n\t\t{\"-1\", \"-1\", \"0\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"0\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"0\", \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\"},\n\t\t{x: \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", y: \"1\", want: \"0\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.Sub(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Sub(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestSubUint256(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"1\", \"-1\"},\n\t\t{\"1\", \"0\", \"1\"},\n\t\t{\"1\", \"1\", \"0\"},\n\t\t{\"1\", \"2\", \"-1\"},\n\t\t{\"-1\", \"1\", \"-2\"},\n\t\t{\"-1\", \"3\", \"-4\"},\n\t\t// underflow\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"1\", \"-0\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"2\", \"-1\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"3\", \"-2\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := uint256.FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.SubUint256(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"SubUint256(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestMul(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"5\", \"3\", \"15\"},\n\t\t{\"-5\", \"3\", \"-15\"},\n\t\t{\"5\", \"-3\", \"-15\"},\n\t\t{\"0\", \"3\", \"0\"},\n\t\t{\"3\", \"0\", \"0\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.Mul(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Mul(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestMulUint256(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"1\", \"0\"},\n\t\t{\"1\", \"0\", \"0\"},\n\t\t{\"1\", \"1\", \"1\"},\n\t\t{\"1\", \"2\", \"2\"},\n\t\t{\"-1\", \"1\", \"-1\"},\n\t\t{\"-1\", \"3\", \"-3\"},\n\t\t{\"3\", \"4\", \"12\"},\n\t\t{\"-3\", \"4\", \"-12\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639934\", \"2\", \"-115792089237316195423570985008687907853269984665640564039457584007913129639932\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639934\", \"2\", \"115792089237316195423570985008687907853269984665640564039457584007913129639932\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := uint256.FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.MulUint256(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"MulUint256(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestDiv(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, expected string\n\t}{\n\t\t{\"1\", \"1\", \"1\"},\n\t\t{\"0\", \"1\", \"0\"},\n\t\t{\"-1\", \"1\", \"-1\"},\n\t\t{\"1\", \"-1\", \"-1\"},\n\t\t{\"-1\", \"-1\", \"1\"},\n\t\t{\"-6\", \"3\", \"-2\"},\n\t\t{\"10\", \"-2\", \"-5\"},\n\t\t{\"-10\", \"3\", \"-3\"},\n\t\t{\"7\", \"3\", \"2\"},\n\t\t{\"-7\", \"3\", \"-2\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"2\", \"57896044618658097711785492504343953926634992332820282019728792003956564819967\"}, // Max uint256 / 2\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.x+\"/\"+tt.y, func(t *testing.T) {\n\t\t\tx := MustFromDecimal(tt.x)\n\t\t\ty := MustFromDecimal(tt.y)\n\t\t\tresult := Zero().Div(x, y)\n\t\t\tif result.ToString() != tt.expected {\n\t\t\t\tt.Errorf(\"Div(%s, %s) = %s, want %s\", tt.x, tt.y, result.ToString(), tt.expected)\n\t\t\t}\n\t\t\tif result.abs.IsZero() \u0026\u0026 result.neg {\n\t\t\t\tt.Errorf(\"Div(%s, %s) resulted in negative zero\", tt.x, tt.y)\n\t\t\t}\n\t\t})\n\t}\n\n\tt.Run(\"Division by zero\", func(t *testing.T) {\n\t\tdefer func() {\n\t\t\tif r := recover(); r == nil {\n\t\t\t\tt.Errorf(\"Div(1, 0) did not panic\")\n\t\t\t}\n\t\t}()\n\t\tx := MustFromDecimal(\"1\")\n\t\ty := MustFromDecimal(\"0\")\n\t\tZero().Div(x, y)\n\t})\n}\n\nfunc TestDivUint256(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"1\", \"0\"},\n\t\t{\"1\", \"0\", \"0\"},\n\t\t{\"1\", \"1\", \"1\"},\n\t\t{\"1\", \"2\", \"0\"},\n\t\t{\"-1\", \"1\", \"-1\"},\n\t\t{\"-1\", \"3\", \"0\"},\n\t\t{\"4\", \"3\", \"1\"},\n\t\t{\"25\", \"5\", \"5\"},\n\t\t{\"25\", \"4\", \"6\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639934\", \"2\", \"-57896044618658097711785492504343953926634992332820282019728792003956564819967\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639934\", \"2\", \"57896044618658097711785492504343953926634992332820282019728792003956564819967\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := uint256.FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.DivUint256(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"DivUint256(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestQuo(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"1\", \"0\"},\n\t\t{\"0\", \"-1\", \"0\"},\n\t\t{\"10\", \"1\", \"10\"},\n\t\t{\"10\", \"-1\", \"-10\"},\n\t\t{\"-10\", \"1\", \"-10\"},\n\t\t{\"-10\", \"-1\", \"10\"},\n\t\t{\"10\", \"-3\", \"-3\"},\n\t\t{\"10\", \"3\", \"3\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.Quo(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Quo(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestRem(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"1\", \"0\"},\n\t\t{\"0\", \"-1\", \"0\"},\n\t\t{\"10\", \"1\", \"0\"},\n\t\t{\"10\", \"-1\", \"0\"},\n\t\t{\"-10\", \"1\", \"0\"},\n\t\t{\"-10\", \"-1\", \"0\"},\n\t\t{\"10\", \"3\", \"1\"},\n\t\t{\"10\", \"-3\", \"1\"},\n\t\t{\"-10\", \"3\", \"-1\"},\n\t\t{\"-10\", \"-3\", \"-1\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.Rem(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Rem(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestMod(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"1\", \"0\"},\n\t\t{\"0\", \"-1\", \"0\"},\n\t\t{\"10\", \"0\", \"0\"},\n\t\t{\"10\", \"1\", \"0\"},\n\t\t{\"10\", \"-1\", \"0\"},\n\t\t{\"-10\", \"0\", \"0\"},\n\t\t{\"-10\", \"1\", \"0\"},\n\t\t{\"-10\", \"-1\", \"0\"},\n\t\t{\"10\", \"3\", \"1\"},\n\t\t{\"10\", \"-3\", \"1\"},\n\t\t{\"-10\", \"3\", \"2\"},\n\t\t{\"-10\", \"-3\", \"2\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.Mod(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Mod(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n"},{"name":"bitwise.gno","body":"package int256\n\nimport (\n\t\"gno.land/p/demo/uint256\"\n)\n\n// Or sets z = x | y and returns z.\nfunc (z *Int) Or(x, y *Int) *Int {\n\tif x.neg == y.neg {\n\t\tif x.neg {\n\t\t\t// (-x) | (-y) == ^(x-1) | ^(y-1) == ^((x-1) \u0026 (y-1)) == -(((x-1) \u0026 (y-1)) + 1)\n\t\t\tx1 := new(uint256.Uint).Sub(x.abs, one)\n\t\t\ty1 := new(uint256.Uint).Sub(y.abs, one)\n\t\t\tz.abs = z.abs.Add(z.abs.And(x1, y1), one)\n\t\t\tz.neg = true // z cannot be zero if x and y are negative\n\t\t\treturn z\n\t\t}\n\n\t\t// x | y == x | y\n\t\tz.abs = z.abs.Or(x.abs, y.abs)\n\t\tz.neg = false\n\t\treturn z\n\t}\n\n\t// x.neg != y.neg\n\tif x.neg {\n\t\tx, y = y, x // | is symmetric\n\t}\n\n\t// x | (-y) == x | ^(y-1) == ^((y-1) \u0026^ x) == -(^((y-1) \u0026^ x) + 1)\n\ty1 := new(uint256.Uint).Sub(y.abs, one)\n\tz.abs = z.abs.Add(z.abs.AndNot(y1, x.abs), one)\n\tz.neg = true // z cannot be zero if one of x or y is negative\n\n\treturn z\n}\n\n// And sets z = x \u0026 y and returns z.\nfunc (z *Int) And(x, y *Int) *Int {\n\tif x.neg == y.neg {\n\t\tif x.neg {\n\t\t\t// (-x) \u0026 (-y) == ^(x-1) \u0026 ^(y-1) == ^((x-1) | (y-1)) == -(((x-1) | (y-1)) + 1)\n\t\t\tx1 := new(uint256.Uint).Sub(x.abs, one)\n\t\t\ty1 := new(uint256.Uint).Sub(y.abs, one)\n\t\t\tz.abs = z.abs.Add(z.abs.Or(x1, y1), one)\n\t\t\tz.neg = true // z cannot be zero if x and y are negative\n\t\t\treturn z\n\t\t}\n\n\t\t// x \u0026 y == x \u0026 y\n\t\tz.abs = z.abs.And(x.abs, y.abs)\n\t\tz.neg = false\n\t\treturn z\n\t}\n\n\t// x.neg != y.neg\n\t// REF: https://cs.opensource.google/go/go/+/refs/tags/go1.22.1:src/math/big/int.go;l=1192-1202;drc=d57303e65f00b84b528ee682747dbe1fd3316d30\n\tif x.neg {\n\t\tx, y = y, x // \u0026 is symmetric\n\t}\n\n\t// x \u0026 (-y) == x \u0026 ^(y-1) == x \u0026^ (y-1)\n\ty1 := new(uint256.Uint).Sub(y.abs, uint256.One())\n\tz.abs = z.abs.AndNot(x.abs, y1)\n\tz.neg = false\n\treturn z\n}\n\n// Rsh sets z = x \u003e\u003e n and returns z.\n// OBS: Different from original implementation it was using math.Big\nfunc (z *Int) Rsh(x *Int, n uint) *Int {\n\tif !x.neg {\n\t\tz.abs.Rsh(x.abs, n)\n\t\tz.neg = x.neg\n\t\treturn z\n\t}\n\n\t// REF: https://cs.opensource.google/go/go/+/refs/tags/go1.22.1:src/math/big/int.go;l=1118-1126;drc=d57303e65f00b84b528ee682747dbe1fd3316d30\n\tt := NewInt(0).Sub(FromUint256(x.abs), NewInt(1))\n\tt = t.Rsh(t, n)\n\n\t_tmp := t.Add(t, NewInt(1))\n\tz.abs = _tmp.Abs()\n\tz.neg = true\n\n\treturn z\n}\n\n// Lsh sets z = x \u003c\u003c n and returns z.\nfunc (z *Int) Lsh(x *Int, n uint) *Int {\n\tz.abs.Lsh(x.abs, n)\n\tz.neg = x.neg\n\treturn z\n}\n"},{"name":"bitwise_test.gno","body":"package int256\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uint256\"\n)\n\nfunc TestOr(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tx, y, want Int\n\t}{\n\t\t{\n\t\t\tname: \"all zeroes\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t},\n\t\t{\n\t\t\tname: \"all ones\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand all ones\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := New()\n\t\t\tgot.Or(\u0026tc.x, \u0026tc.y)\n\n\t\t\tif got.Neq(\u0026tc.want) {\n\t\t\t\tt.Errorf(\"Or(%v, %v) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestAnd(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tx, y, want Int\n\t}{\n\t\t{\n\t\t\tname: \"all zeroes\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t},\n\t\t{\n\t\t\tname: \"all ones\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 2\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 3\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand zero\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand all ones\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}}, neg: false},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := New()\n\t\t\tgot.And(\u0026tc.x, \u0026tc.y)\n\n\t\t\tif got.Neq(\u0026tc.want) {\n\t\t\t\tt.Errorf(\"And(%v, %v) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestRsh(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\tn uint\n\t\twant string\n\t}{\n\t\t{\"1024\", 0, \"1024\"},\n\t\t{\"1024\", 1, \"512\"},\n\t\t{\"1024\", 2, \"256\"},\n\t\t{\"1024\", 10, \"1\"},\n\t\t{\"1024\", 11, \"0\"},\n\t\t{\"18446744073709551615\", 0, \"18446744073709551615\"},\n\t\t{\"18446744073709551615\", 1, \"9223372036854775807\"},\n\t\t{\"18446744073709551615\", 62, \"3\"},\n\t\t{\"18446744073709551615\", 63, \"1\"},\n\t\t{\"18446744073709551615\", 64, \"0\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", 0, \"115792089237316195423570985008687907853269984665640564039457584007913129639935\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", 1, \"57896044618658097711785492504343953926634992332820282019728792003956564819967\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", 128, \"340282366920938463463374607431768211455\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", 255, \"1\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", 256, \"0\"},\n\t\t{\"-1024\", 0, \"-1024\"},\n\t\t{\"-1024\", 1, \"-512\"},\n\t\t{\"-1024\", 2, \"-256\"},\n\t\t{\"-1024\", 10, \"-1\"},\n\t\t{\"-1024\", 10, \"-1\"},\n\t\t{\"-9223372036854775808\", 0, \"-9223372036854775808\"},\n\t\t{\"-9223372036854775808\", 1, \"-4611686018427387904\"},\n\t\t{\"-9223372036854775808\", 62, \"-2\"},\n\t\t{\"-9223372036854775808\", 63, \"-1\"},\n\t\t{\"-9223372036854775808\", 64, \"-1\"},\n\t\t{\"-57896044618658097711785492504343953926634992332820282019728792003956564819968\", 0, \"-57896044618658097711785492504343953926634992332820282019728792003956564819968\"},\n\t\t{\"-57896044618658097711785492504343953926634992332820282019728792003956564819968\", 1, \"-28948022309329048855892746252171976963317496166410141009864396001978282409984\"},\n\t\t{\"-57896044618658097711785492504343953926634992332820282019728792003956564819968\", 253, \"-4\"},\n\t\t{\"-57896044618658097711785492504343953926634992332820282019728792003956564819968\", 254, \"-2\"},\n\t\t{\"-57896044618658097711785492504343953926634992332820282019728792003956564819968\", 255, \"-1\"},\n\t\t{\"-57896044618658097711785492504343953926634992332820282019728792003956564819968\", 256, \"-1\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.Rsh(x, tc.n)\n\n\t\tif got.ToString() != tc.want {\n\t\t\tt.Errorf(\"Rsh(%s, %d) = %v, want %v\", tc.x, tc.n, got.ToString(), tc.want)\n\t\t}\n\t}\n}\n\nfunc TestLsh(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\tn uint\n\t\twant string\n\t}{\n\t\t{\"1\", 0, \"1\"},\n\t\t{\"1\", 1, \"2\"},\n\t\t{\"1\", 2, \"4\"},\n\t\t{\"2\", 0, \"2\"},\n\t\t{\"2\", 1, \"4\"},\n\t\t{\"2\", 2, \"8\"},\n\t\t{\"-2\", 0, \"-2\"},\n\t\t{\"-4\", 0, \"-4\"},\n\t\t{\"-8\", 0, \"-8\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.Lsh(x, tc.n)\n\n\t\tif got.ToString() != tc.want {\n\t\t\tt.Errorf(\"Lsh(%s, %d) = %v, want %v\", tc.x, tc.n, got.ToString(), tc.want)\n\t\t}\n\t}\n}\n"},{"name":"cmp.gno","body":"package int256\n\n// Eq returns true if z == x\nfunc (z *Int) Eq(x *Int) bool {\n\treturn (z.neg == x.neg) \u0026\u0026 z.abs.Eq(x.abs)\n}\n\n// Neq returns true if z != x\nfunc (z *Int) Neq(x *Int) bool {\n\treturn !z.Eq(x)\n}\n\n// Cmp compares x and y and returns:\n//\n//\t-1 if x \u003c y\n//\t 0 if x == y\n//\t+1 if x \u003e y\nfunc (z *Int) Cmp(x *Int) (r int) {\n\t// x cmp y == x cmp y\n\t// x cmp (-y) == x\n\t// (-x) cmp y == y\n\t// (-x) cmp (-y) == -(x cmp y)\n\tswitch {\n\tcase z == x:\n\t\t// nothing to do\n\tcase z.neg == x.neg:\n\t\tr = z.abs.Cmp(x.abs)\n\t\tif z.neg {\n\t\t\tr = -r\n\t\t}\n\tcase z.neg:\n\t\tr = -1\n\tdefault:\n\t\tr = 1\n\t}\n\treturn\n}\n\n// IsZero returns true if z == 0\nfunc (z *Int) IsZero() bool {\n\treturn z.abs.IsZero()\n}\n\n// IsNeg returns true if z \u003c 0\nfunc (z *Int) IsNeg() bool {\n\treturn z.neg\n}\n\n// Lt returns true if z \u003c x\nfunc (z *Int) Lt(x *Int) bool {\n\tif z.neg {\n\t\tif x.neg {\n\t\t\treturn z.abs.Gt(x.abs)\n\t\t} else {\n\t\t\treturn true\n\t\t}\n\t} else {\n\t\tif x.neg {\n\t\t\treturn false\n\t\t} else {\n\t\t\treturn z.abs.Lt(x.abs)\n\t\t}\n\t}\n}\n\n// Gt returns true if z \u003e x\nfunc (z *Int) Gt(x *Int) bool {\n\tif z.neg {\n\t\tif x.neg {\n\t\t\treturn z.abs.Lt(x.abs)\n\t\t} else {\n\t\t\treturn false\n\t\t}\n\t} else {\n\t\tif x.neg {\n\t\t\treturn true\n\t\t} else {\n\t\t\treturn z.abs.Gt(x.abs)\n\t\t}\n\t}\n}\n\n// Clone creates a new Int identical to z\nfunc (z *Int) Clone() *Int {\n\treturn \u0026Int{z.abs.Clone(), z.neg}\n}\n"},{"name":"cmp_test.gno","body":"package int256\n\nimport \"testing\"\n\nfunc TestEq(t *testing.T) {\n\ttests := []struct {\n\t\tx, y string\n\t\twant bool\n\t}{\n\t\t{\"0\", \"0\", true},\n\t\t{\"0\", \"1\", false},\n\t\t{\"1\", \"0\", false},\n\t\t{\"-1\", \"0\", false},\n\t\t{\"0\", \"-1\", false},\n\t\t{\"1\", \"1\", true},\n\t\t{\"-1\", \"-1\", true},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", false},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", true},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.Eq(y)\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Eq(%s, %s) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestNeq(t *testing.T) {\n\ttests := []struct {\n\t\tx, y string\n\t\twant bool\n\t}{\n\t\t{\"0\", \"0\", false},\n\t\t{\"0\", \"1\", true},\n\t\t{\"1\", \"0\", true},\n\t\t{\"-1\", \"0\", true},\n\t\t{\"0\", \"-1\", true},\n\t\t{\"1\", \"1\", false},\n\t\t{\"-1\", \"-1\", false},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", true},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", false},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.Neq(y)\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Neq(%s, %s) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestCmp(t *testing.T) {\n\ttests := []struct {\n\t\tx, y string\n\t\twant int\n\t}{\n\t\t{\"0\", \"0\", 0},\n\t\t{\"0\", \"1\", -1},\n\t\t{\"1\", \"0\", 1},\n\t\t{\"-1\", \"0\", -1},\n\t\t{\"0\", \"-1\", 1},\n\t\t{\"1\", \"1\", 0},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", 1},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.Cmp(y)\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Cmp(%s, %s) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestIsZero(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant bool\n\t}{\n\t\t{\"0\", true},\n\t\t{\"-0\", true},\n\t\t{\"1\", false},\n\t\t{\"-1\", false},\n\t\t{\"10\", false},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.IsZero()\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"IsZero(%s) = %v, want %v\", tc.x, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestIsNeg(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant bool\n\t}{\n\t\t{\"0\", false},\n\t\t{\"-0\", true}, // TODO: should this be false?\n\t\t{\"1\", false},\n\t\t{\"-1\", true},\n\t\t{\"10\", false},\n\t\t{\"-10\", true},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.IsNeg()\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"IsNeg(%s) = %v, want %v\", tc.x, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestLt(t *testing.T) {\n\ttests := []struct {\n\t\tx, y string\n\t\twant bool\n\t}{\n\t\t{\"0\", \"0\", false},\n\t\t{\"0\", \"1\", true},\n\t\t{\"1\", \"0\", false},\n\t\t{\"-1\", \"0\", true},\n\t\t{\"0\", \"-1\", false},\n\t\t{\"1\", \"1\", false},\n\t\t{\"-1\", \"-1\", false},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", false},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.Lt(y)\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Lt(%s, %s) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestGt(t *testing.T) {\n\ttests := []struct {\n\t\tx, y string\n\t\twant bool\n\t}{\n\t\t{\"0\", \"0\", false},\n\t\t{\"0\", \"1\", false},\n\t\t{\"1\", \"0\", true},\n\t\t{\"-1\", \"0\", false},\n\t\t{\"0\", \"-1\", true},\n\t\t{\"1\", \"1\", false},\n\t\t{\"-1\", \"-1\", false},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", true},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.Gt(y)\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Gt(%s, %s) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestClone(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t}{\n\t\t{\"0\"},\n\t\t{\"-0\"},\n\t\t{\"1\"},\n\t\t{\"-1\"},\n\t\t{\"10\"},\n\t\t{\"-10\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty := x.Clone()\n\n\t\tif x.Cmp(y) != 0 {\n\t\t\tt.Errorf(\"Clone(%s) = %v, want %v\", tc.x, y, x)\n\t\t}\n\t}\n}\n"},{"name":"conversion.gno","body":"package int256\n\nimport \"gno.land/p/demo/uint256\"\n\n// SetInt64 sets z to x and returns z.\nfunc (z *Int) SetInt64(x int64) *Int {\n\tz.initiateAbs()\n\n\tneg := false\n\tif x \u003c 0 {\n\t\tneg = true\n\t\tx = -x\n\t}\n\tif z.abs == nil {\n\t\tpanic(\"abs is nil\")\n\t}\n\tz.abs = z.abs.SetUint64(uint64(x))\n\tz.neg = neg\n\treturn z\n}\n\n// SetUint64 sets z to x and returns z.\nfunc (z *Int) SetUint64(x uint64) *Int {\n\tz.initiateAbs()\n\n\tif z.abs == nil {\n\t\tpanic(\"abs is nil\")\n\t}\n\tz.abs = z.abs.SetUint64(x)\n\tz.neg = false\n\treturn z\n}\n\n// Uint64 returns the lower 64-bits of z\nfunc (z *Int) Uint64() uint64 {\n\treturn z.abs.Uint64()\n}\n\n// Int64 returns the lower 64-bits of z\nfunc (z *Int) Int64() int64 {\n\t_abs := z.abs.Clone()\n\n\tif z.neg {\n\t\treturn -int64(_abs.Uint64())\n\t}\n\treturn int64(_abs.Uint64())\n}\n\n// Neg sets z to -x and returns z.)\nfunc (z *Int) Neg(x *Int) *Int {\n\tz.abs.Set(x.abs)\n\tif z.abs.IsZero() {\n\t\tz.neg = false\n\t} else {\n\t\tz.neg = !x.neg\n\t}\n\treturn z\n}\n\n// Set sets z to x and returns z.\nfunc (z *Int) Set(x *Int) *Int {\n\tz.abs.Set(x.abs)\n\tz.neg = x.neg\n\treturn z\n}\n\n// SetFromUint256 converts a uint256.Uint to Int and sets the value to z.\nfunc (z *Int) SetUint256(x *uint256.Uint) *Int {\n\tz.abs.Set(x)\n\tz.neg = false\n\treturn z\n}\n\n// OBS, differs from original mempooler int256\n// ToString returns the decimal representation of z.\nfunc (z *Int) ToString() string {\n\tif z == nil {\n\t\tpanic(\"int256: nil pointer to ToString()\")\n\t}\n\n\tt := z.abs.Dec()\n\tif z.neg {\n\t\treturn \"-\" + t\n\t}\n\n\treturn t\n}\n"},{"name":"conversion_test.gno","body":"package int256\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uint256\"\n)\n\nfunc TestSetInt64(t *testing.T) {\n\ttests := []struct {\n\t\tx int64\n\t\twant string\n\t}{\n\t\t{0, \"0\"},\n\t\t{1, \"1\"},\n\t\t{-1, \"-1\"},\n\t\t{9223372036854775807, \"9223372036854775807\"},\n\t\t{-9223372036854775808, \"-9223372036854775808\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tvar z Int\n\t\tz.SetInt64(tc.x)\n\n\t\tgot := z.ToString()\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"SetInt64(%d) = %s, want %s\", tc.x, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestSetUint64(t *testing.T) {\n\ttests := []struct {\n\t\tx uint64\n\t\twant string\n\t}{\n\t\t{0, \"0\"},\n\t\t{1, \"1\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tvar z Int\n\t\tz.SetUint64(tc.x)\n\n\t\tgot := z.ToString()\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"SetUint64(%d) = %s, want %s\", tc.x, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestUint64(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant uint64\n\t}{\n\t\t{\"0\", 0},\n\t\t{\"1\", 1},\n\t\t{\"9223372036854775807\", 9223372036854775807},\n\t\t{\"9223372036854775808\", 9223372036854775808},\n\t\t{\"18446744073709551615\", 18446744073709551615},\n\t\t{\"18446744073709551616\", 0},\n\t\t{\"18446744073709551617\", 1},\n\t\t{\"-1\", 1},\n\t\t{\"-18446744073709551615\", 18446744073709551615},\n\t\t{\"-18446744073709551616\", 0},\n\t\t{\"-18446744073709551617\", 1},\n\t}\n\n\tfor _, tc := range tests {\n\t\tz := MustFromDecimal(tc.x)\n\n\t\tgot := z.Uint64()\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Uint64(%s) = %d, want %d\", tc.x, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestInt64(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant int64\n\t}{\n\t\t{\"0\", 0},\n\t\t{\"1\", 1},\n\t\t{\"9223372036854775807\", 9223372036854775807},\n\t\t{\"18446744073709551616\", 0},\n\t\t{\"18446744073709551617\", 1},\n\t\t{\"-1\", -1},\n\t\t{\"-9223372036854775808\", -9223372036854775808},\n\t}\n\n\tfor _, tc := range tests {\n\t\tz := MustFromDecimal(tc.x)\n\n\t\tgot := z.Int64()\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Uint64(%s) = %d, want %d\", tc.x, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestNeg(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant string\n\t}{\n\t\t{\"0\", \"0\"},\n\t\t{\"1\", \"-1\"},\n\t\t{\"-1\", \"1\"},\n\t\t{\"9223372036854775807\", \"-9223372036854775807\"},\n\t\t{\"-18446744073709551615\", \"18446744073709551615\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tz := MustFromDecimal(tc.x)\n\t\tz.Neg(z)\n\n\t\tgot := z.ToString()\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Neg(%s) = %s, want %s\", tc.x, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestSet(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant string\n\t}{\n\t\t{\"0\", \"0\"},\n\t\t{\"1\", \"1\"},\n\t\t{\"-1\", \"-1\"},\n\t\t{\"9223372036854775807\", \"9223372036854775807\"},\n\t\t{\"-18446744073709551615\", \"-18446744073709551615\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tz := MustFromDecimal(tc.x)\n\t\tz.Set(z)\n\n\t\tgot := z.ToString()\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Set(%s) = %s, want %s\", tc.x, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestSetUint256(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant string\n\t}{\n\t\t{\"0\", \"0\"},\n\t\t{\"1\", \"1\"},\n\t\t{\"9223372036854775807\", \"9223372036854775807\"},\n\t\t{\"18446744073709551615\", \"18446744073709551615\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tgot := New()\n\n\t\tz := uint256.MustFromDecimal(tc.x)\n\t\tgot.SetUint256(z)\n\n\t\tif got.ToString() != tc.want {\n\t\t\tt.Errorf(\"SetUint256(%s) = %s, want %s\", tc.x, got.ToString(), tc.want)\n\t\t}\n\t}\n}\n\nfunc TestToString(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tsetup func() *Int\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname: \"Zero from subtraction\",\n\t\t\tsetup: func() *Int {\n\t\t\t\tminusThree := MustFromDecimal(\"-3\")\n\t\t\t\tthree := MustFromDecimal(\"3\")\n\t\t\t\treturn Zero().Add(minusThree, three)\n\t\t\t},\n\t\t\texpected: \"0\",\n\t\t},\n\t\t{\n\t\t\tname: \"Zero from right shift\",\n\t\t\tsetup: func() *Int {\n\t\t\t\treturn Zero().Rsh(One(), 1234)\n\t\t\t},\n\t\t\texpected: \"0\",\n\t\t},\n\t\t{\n\t\t\tname: \"Positive number\",\n\t\t\tsetup: func() *Int {\n\t\t\t\treturn MustFromDecimal(\"42\")\n\t\t\t},\n\t\t\texpected: \"42\",\n\t\t},\n\t\t{\n\t\t\tname: \"Negative number\",\n\t\t\tsetup: func() *Int {\n\t\t\t\treturn MustFromDecimal(\"-42\")\n\t\t\t},\n\t\t\texpected: \"-42\",\n\t\t},\n\t\t{\n\t\t\tname: \"Large positive number\",\n\t\t\tsetup: func() *Int {\n\t\t\t\treturn MustFromDecimal(\"115792089237316195423570985008687907853269984665640564039457584007913129639935\")\n\t\t\t},\n\t\t\texpected: \"115792089237316195423570985008687907853269984665640564039457584007913129639935\",\n\t\t},\n\t\t{\n\t\t\tname: \"Large negative number\",\n\t\t\tsetup: func() *Int {\n\t\t\t\treturn MustFromDecimal(\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\")\n\t\t\t},\n\t\t\texpected: \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tz := tt.setup()\n\t\t\tresult := z.ToString()\n\t\t\tif result != tt.expected {\n\t\t\t\tt.Errorf(\"ToString() = %s, want %s\", result, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n"},{"name":"int256.gno","body":"// This package provides a 256-bit signed integer type, Int, and associated functions.\npackage int256\n\nimport (\n\t\"gno.land/p/demo/uint256\"\n)\n\nvar one = uint256.NewUint(1)\n\ntype Int struct {\n\tabs *uint256.Uint\n\tneg bool\n}\n\n// Zero returns a new Int set to 0.\nfunc Zero() *Int {\n\treturn NewInt(0)\n}\n\n// One returns a new Int set to 1.\nfunc One() *Int {\n\treturn NewInt(1)\n}\n\n// Sign returns:\n//\n//\t-1 if x \u003c 0\n//\t 0 if x == 0\n//\t+1 if x \u003e 0\nfunc (z *Int) Sign() int {\n\tz.initiateAbs()\n\n\tif z.abs.IsZero() {\n\t\treturn 0\n\t}\n\tif z.neg {\n\t\treturn -1\n\t}\n\treturn 1\n}\n\n// New returns a new Int set to 0.\nfunc New() *Int {\n\treturn \u0026Int{\n\t\tabs: new(uint256.Uint),\n\t}\n}\n\n// NewInt allocates and returns a new Int set to x.\nfunc NewInt(x int64) *Int {\n\treturn New().SetInt64(x)\n}\n\n// FromDecimal returns a new Int from a decimal string.\n// Returns a new Int and an error if the string is not a valid decimal.\nfunc FromDecimal(s string) (*Int, error) {\n\treturn new(Int).SetString(s)\n}\n\n// MustFromDecimal returns a new Int from a decimal string.\n// Panics if the string is not a valid decimal.\nfunc MustFromDecimal(s string) *Int {\n\tz, err := FromDecimal(s)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn z\n}\n\n// SetString sets s to the value of z and returns z and a boolean indicating success.\nfunc (z *Int) SetString(s string) (*Int, error) {\n\tneg := false\n\t// Remove max one leading +\n\tif len(s) \u003e 0 \u0026\u0026 s[0] == '+' {\n\t\tneg = false\n\t\ts = s[1:]\n\t}\n\n\tif len(s) \u003e 0 \u0026\u0026 s[0] == '-' {\n\t\tneg = true\n\t\ts = s[1:]\n\t}\n\tvar (\n\t\tabs *uint256.Uint\n\t\terr error\n\t)\n\tabs, err = uint256.FromDecimal(s)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn \u0026Int{\n\t\tabs,\n\t\tneg,\n\t}, nil\n}\n\n// FromUint256 is a convenience-constructor from uint256.Uint.\n// Returns a new Int and whether overflow occurred.\n// OBS: If u is `nil`, this method returns `nil, false`\nfunc FromUint256(x *uint256.Uint) *Int {\n\tif x == nil {\n\t\treturn nil\n\t}\n\tz := Zero()\n\n\tz.SetUint256(x)\n\treturn z\n}\n\n// OBS, differs from original mempooler int256\n// NilToZero sets z to 0 and return it if it's nil, otherwise it returns z\nfunc (z *Int) NilToZero() *Int {\n\tif z == nil {\n\t\treturn NewInt(0)\n\t}\n\treturn z\n}\n\n// initiateAbs sets default value for `z` or `z.abs` value if is nil\n// OBS: differs from mempooler int256. It checks not only `z.abs` but also `z`\nfunc (z *Int) initiateAbs() {\n\tif z == nil || z.abs == nil {\n\t\tz.abs = new(uint256.Uint)\n\t}\n}\n"},{"name":"int256_test.gno","body":"// ported from github.com/mempooler/int256\npackage int256\n\nimport \"testing\"\n\nfunc TestSign(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant int\n\t}{\n\t\t{\"0\", 0},\n\t\t{\"1\", 1},\n\t\t{\"-1\", -1},\n\t}\n\n\tfor _, tc := range tests {\n\t\tz := MustFromDecimal(tc.x)\n\t\tgot := z.Sign()\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Sign(%s) = %d, want %d\", tc.x, got, tc.want)\n\t\t}\n\t}\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}} @@ -49,24 +49,24 @@ {"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"seqid","path":"gno.land/p/demo/seqid","files":[{"name":"README.md","body":"# seqid\n\n```\npackage seqid // import \"gno.land/p/demo/seqid\"\n\nPackage seqid provides a simple way to have sequential IDs which will be ordered\ncorrectly when inserted in an AVL tree.\n\nSample usage:\n\n var id seqid.ID\n var users avl.Tree\n\n func NewUser() {\n \tusers.Set(id.Next().Binary(), \u0026User{ ... })\n }\n\nTYPES\n\ntype ID uint64\n An ID is a simple sequential ID generator.\n\nfunc FromBinary(b string) (ID, bool)\n FromBinary creates a new ID from the given string.\n\nfunc (i ID) Binary() string\n Binary returns a big-endian binary representation of the ID, suitable to be\n used as an AVL key.\n\nfunc (i *ID) Next() ID\n Next advances the ID i. It will panic if increasing ID would overflow.\n\nfunc (i *ID) TryNext() (ID, bool)\n TryNext increases i by 1 and returns its value. It returns true if\n successful, or false if the increment would result in an overflow.\n```\n"},{"name":"seqid.gno","body":"// Package seqid provides a simple way to have sequential IDs which will be\n// ordered correctly when inserted in an AVL tree.\n//\n// Sample usage:\n//\n//\tvar id seqid.ID\n//\tvar users avl.Tree\n//\n//\tfunc NewUser() {\n//\t\tusers.Set(id.Next().String(), \u0026User{ ... })\n//\t}\npackage seqid\n\nimport (\n\t\"encoding/binary\"\n\n\t\"gno.land/p/demo/cford32\"\n)\n\n// An ID is a simple sequential ID generator.\ntype ID uint64\n\n// Next advances the ID i.\n// It will panic if increasing ID would overflow.\nfunc (i *ID) Next() ID {\n\tnext, ok := i.TryNext()\n\tif !ok {\n\t\tpanic(\"seqid: next ID overflows uint64\")\n\t}\n\treturn next\n}\n\nconst maxID ID = 1\u003c\u003c64 - 1\n\n// TryNext increases i by 1 and returns its value.\n// It returns true if successful, or false if the increment would result in\n// an overflow.\nfunc (i *ID) TryNext() (ID, bool) {\n\tif *i == maxID {\n\t\t// Addition will overflow.\n\t\treturn 0, false\n\t}\n\t*i++\n\treturn *i, true\n}\n\n// Binary returns a big-endian binary representation of the ID,\n// suitable to be used as an AVL key.\nfunc (i ID) Binary() string {\n\tbuf := make([]byte, 8)\n\tbinary.BigEndian.PutUint64(buf, uint64(i))\n\treturn string(buf)\n}\n\n// String encodes i using cford32's compact encoding. For more information,\n// see the documentation for package [gno.land/p/demo/cford32].\n//\n// The result of String will be a 7-byte string for IDs [0,2^34), and a\n// 13-byte string for all values following that. All generated string IDs\n// follow the same lexicographic order as their number values; that is, for any\n// two IDs (x, y) such that x \u003c y, x.String() \u003c y.String().\n// As such, this string representation is suitable to be used as an AVL key.\nfunc (i ID) String() string {\n\treturn string(cford32.PutCompact(uint64(i)))\n}\n\n// FromBinary creates a new ID from the given string, expected to be a binary\n// big-endian encoding of an ID (such as that of [ID.Binary]).\n// The second return value is true if the conversion was successful.\nfunc FromBinary(b string) (ID, bool) {\n\tif len(b) != 8 {\n\t\treturn 0, false\n\t}\n\treturn ID(binary.BigEndian.Uint64([]byte(b))), true\n}\n\n// FromString creates a new ID from the given string, expected to be a string\n// representation using cford32, such as that returned by [ID.String].\n//\n// The encoding scheme used by cford32 allows the same ID to have many\n// different representations (though the one returned by [ID.String] is only\n// one, deterministic and safe to be used in AVL). The encoding scheme is\n// \"human-centric\" and is thus case insensitive, and maps some ambiguous\n// characters to be the same, ie. L = I = 1, O = 0. For this reason, when\n// parsing user input to retrieve a key (encoded as a string), always sanitize\n// it first using FromString, then run String(), instead of using the user's\n// input directly.\nfunc FromString(b string) (ID, error) {\n\tn, err := cford32.Uint64([]byte(b))\n\treturn ID(n), err\n}\n"},{"name":"seqid_test.gno","body":"package seqid\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestID(t *testing.T) {\n\tvar i ID\n\n\tfor j := 0; j \u003c 100; j++ {\n\t\ti.Next()\n\t}\n\tif i != 100 {\n\t\tt.Fatalf(\"invalid: wanted %d got %d\", 100, i)\n\t}\n}\n\nfunc TestID_Overflow(t *testing.T) {\n\ti := ID(maxID)\n\n\tdefer func() {\n\t\terr := recover()\n\t\tif !strings.Contains(fmt.Sprint(err), \"next ID overflows\") {\n\t\t\tt.Errorf(\"did not overflow\")\n\t\t}\n\t}()\n\n\ti.Next()\n}\n\nfunc TestID_Binary(t *testing.T) {\n\tvar i ID\n\tprev := i.Binary()\n\n\tfor j := 0; j \u003c 1000; j++ {\n\t\tcur := i.Next().Binary()\n\t\tif cur \u003c= prev {\n\t\t\tt.Fatalf(\"cur %x \u003e prev %x\", cur, prev)\n\t\t}\n\t\tprev = cur\n\t}\n}\n\nfunc TestID_String(t *testing.T) {\n\tvar i ID\n\tprev := i.String()\n\n\tfor j := 0; j \u003c 1000; j++ {\n\t\tcur := i.Next().String()\n\t\tif cur \u003c= prev {\n\t\t\tt.Fatalf(\"cur %s \u003e prev %s\", cur, prev)\n\t\t}\n\t\tprev = cur\n\t}\n\n\t// Test for when cford32 switches over to the long encoding.\n\ti = 1\u003c\u003c34 - 512\n\tfor j := 0; j \u003c 1024; j++ {\n\t\tcur := i.Next().String()\n\t\t// println(cur)\n\t\tif cur \u003c= prev {\n\t\t\tt.Fatalf(\"cur %s \u003e prev %s\", cur, prev)\n\t\t}\n\t\tprev = cur\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":"memeland","path":"gno.land/p/demo/memeland","files":[{"name":"memeland.gno","body":"package memeland\n\nimport (\n\t\"sort\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/seqid\"\n)\n\nconst (\n\tDATE_CREATED = \"DATE_CREATED\"\n\tUPVOTES = \"UPVOTES\"\n)\n\ntype Post struct {\n\tID string\n\tData string\n\tAuthor std.Address\n\tTimestamp time.Time\n\tUpvoteTracker *avl.Tree // address \u003e struct{}{}\n}\n\ntype Memeland struct {\n\t*ownable.Ownable\n\tPosts []*Post\n\tMemeCounter seqid.ID\n}\n\nfunc NewMemeland() *Memeland {\n\treturn \u0026Memeland{\n\t\tOwnable: ownable.New(),\n\t\tPosts: make([]*Post, 0),\n\t}\n}\n\n// PostMeme - Adds a new post\nfunc (m *Memeland) PostMeme(data string, timestamp int64) string {\n\tif data == \"\" || timestamp \u003c= 0 {\n\t\tpanic(\"timestamp or data cannot be empty\")\n\t}\n\n\t// Generate ID\n\tid := m.MemeCounter.Next().String()\n\n\tnewPost := \u0026Post{\n\t\tID: id,\n\t\tData: data,\n\t\tAuthor: std.PrevRealm().Addr(),\n\t\tTimestamp: time.Unix(timestamp, 0),\n\t\tUpvoteTracker: avl.NewTree(),\n\t}\n\n\tm.Posts = append(m.Posts, newPost)\n\treturn id\n}\n\nfunc (m *Memeland) Upvote(id string) string {\n\tpost := m.getPost(id)\n\tif post == nil {\n\t\tpanic(\"post with specified ID does not exist\")\n\t}\n\n\tcaller := std.PrevRealm().Addr().String()\n\n\tif _, exists := post.UpvoteTracker.Get(caller); exists {\n\t\tpanic(\"user has already upvoted this post\")\n\t}\n\n\tpost.UpvoteTracker.Set(caller, struct{}{})\n\n\treturn \"upvote successful\"\n}\n\n// GetPostsInRange returns a JSON string of posts within the given timestamp range, supporting pagination\nfunc (m *Memeland) GetPostsInRange(startTimestamp, endTimestamp int64, page, pageSize int, sortBy string) string {\n\tif len(m.Posts) == 0 {\n\t\treturn \"[]\"\n\t}\n\n\tif page \u003c 1 {\n\t\tpanic(\"page number cannot be less than 1\")\n\t}\n\n\t// No empty pages\n\tif pageSize \u003c 1 {\n\t\tpanic(\"page size cannot be less than 1\")\n\t}\n\n\t// No pages larger than 10\n\tif pageSize \u003e 10 {\n\t\tpanic(\"page size cannot be larger than 10\")\n\t}\n\n\t// Need to pass in a sort parameter\n\tif sortBy == \"\" {\n\t\tpanic(\"sort order cannot be empty\")\n\t}\n\n\tvar filteredPosts []*Post\n\n\tstart := time.Unix(startTimestamp, 0)\n\tend := time.Unix(endTimestamp, 0)\n\n\t// Filtering posts\n\tfor _, p := range m.Posts {\n\t\tif !p.Timestamp.Before(start) \u0026\u0026 !p.Timestamp.After(end) {\n\t\t\tfilteredPosts = append(filteredPosts, p)\n\t\t}\n\t}\n\n\tswitch sortBy {\n\t// Sort by upvote descending\n\tcase UPVOTES:\n\t\tdateSorter := PostSorter{\n\t\t\tPosts: filteredPosts,\n\t\t\tLessF: func(i, j int) bool {\n\t\t\t\treturn filteredPosts[i].UpvoteTracker.Size() \u003e filteredPosts[j].UpvoteTracker.Size()\n\t\t\t},\n\t\t}\n\t\tsort.Sort(dateSorter)\n\tcase DATE_CREATED:\n\t\t// Sort by timestamp, beginning with newest\n\t\tdateSorter := PostSorter{\n\t\t\tPosts: filteredPosts,\n\t\t\tLessF: func(i, j int) bool {\n\t\t\t\treturn filteredPosts[i].Timestamp.After(filteredPosts[j].Timestamp)\n\t\t\t},\n\t\t}\n\t\tsort.Sort(dateSorter)\n\tdefault:\n\t\tpanic(\"sort order can only be \\\"UPVOTES\\\" or \\\"DATE_CREATED\\\"\")\n\t}\n\n\t// Pagination\n\tstartIndex := (page - 1) * pageSize\n\tendIndex := startIndex + pageSize\n\n\t// If page does not contain any posts\n\tif startIndex \u003e= len(filteredPosts) {\n\t\treturn \"[]\"\n\t}\n\n\t// If page contains fewer posts than the page size\n\tif endIndex \u003e len(filteredPosts) {\n\t\tendIndex = len(filteredPosts)\n\t}\n\n\t// Return JSON representation of paginated and sorted posts\n\treturn PostsToJSONString(filteredPosts[startIndex:endIndex])\n}\n\n// RemovePost allows the owner to remove a post with a specific ID\nfunc (m *Memeland) RemovePost(id string) string {\n\tif id == \"\" {\n\t\tpanic(\"id cannot be empty\")\n\t}\n\n\tif err := m.CallerIsOwner(); err != nil {\n\t\tpanic(err)\n\t}\n\n\tfor i, post := range m.Posts {\n\t\tif post.ID == id {\n\t\t\tm.Posts = append(m.Posts[:i], m.Posts[i+1:]...)\n\t\t\treturn id\n\t\t}\n\t}\n\n\tpanic(\"post with specified id does not exist\")\n}\n\n// PostsToJSONString converts a slice of Post structs into a JSON string\nfunc PostsToJSONString(posts []*Post) string {\n\tvar sb strings.Builder\n\tsb.WriteString(\"[\")\n\n\tfor i, post := range posts {\n\t\tif i \u003e 0 {\n\t\t\tsb.WriteString(\",\")\n\t\t}\n\n\t\tsb.WriteString(PostToJSONString(post))\n\t}\n\tsb.WriteString(\"]\")\n\n\treturn sb.String()\n}\n\n// PostToJSONString returns a Post formatted as a JSON string\nfunc PostToJSONString(post *Post) string {\n\tvar sb strings.Builder\n\n\tsb.WriteString(\"{\")\n\tsb.WriteString(`\"id\":\"` + post.ID + `\",`)\n\tsb.WriteString(`\"data\":\"` + escapeString(post.Data) + `\",`)\n\tsb.WriteString(`\"author\":\"` + escapeString(post.Author.String()) + `\",`)\n\tsb.WriteString(`\"timestamp\":\"` + strconv.Itoa(int(post.Timestamp.Unix())) + `\",`)\n\tsb.WriteString(`\"upvotes\":` + strconv.Itoa(post.UpvoteTracker.Size()))\n\tsb.WriteString(\"}\")\n\n\treturn sb.String()\n}\n\n// escapeString escapes quotes in a string for JSON compatibility.\nfunc escapeString(s string) string {\n\treturn strings.ReplaceAll(s, `\"`, `\\\"`)\n}\n\nfunc (m *Memeland) getPost(id string) *Post {\n\tfor _, p := range m.Posts {\n\t\tif p.ID == id {\n\t\t\treturn p\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// PostSorter is a flexible sorter for the *Post slice\ntype PostSorter struct {\n\tPosts []*Post\n\tLessF func(i, j int) bool\n}\n\nfunc (p PostSorter) Len() int {\n\treturn len(p.Posts)\n}\n\nfunc (p PostSorter) Swap(i, j int) {\n\tp.Posts[i], p.Posts[j] = p.Posts[j], p.Posts[i]\n}\n\nfunc (p PostSorter) Less(i, j int) bool {\n\treturn p.LessF(i, j)\n}\n"},{"name":"memeland_test.gno","body":"package memeland\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\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 TestPostMeme(t *testing.T) {\n\tm := NewMemeland()\n\tid := m.PostMeme(\"Test meme data\", time.Now().Unix())\n\tuassert.NotEqual(t, \"\", string(id), \"Expected valid ID, got empty string\")\n}\n\nfunc TestGetPostsInRangePagination(t *testing.T) {\n\tm := NewMemeland()\n\tnow := time.Now()\n\n\tnumOfPosts := 5\n\tvar memeData []string\n\tfor i := 1; i \u003c= numOfPosts; i++ {\n\t\t// Prepare meme data\n\t\tnextTime := now.Add(time.Duration(i) * time.Minute)\n\t\tdata := ufmt.Sprintf(\"Meme #%d\", i)\n\t\tmemeData = append(memeData, data)\n\n\t\tm.PostMeme(data, nextTime.Unix())\n\t}\n\n\t// Get timestamps\n\tbeforeEarliest := now.Add(-1 * time.Minute)\n\tafterLatest := now.Add(time.Duration(numOfPosts)*time.Minute + time.Minute)\n\n\ttestCases := []struct {\n\t\tpage int\n\t\tpageSize int\n\t\texpectedNumOfPosts int\n\t}{\n\t\t{page: 1, pageSize: 1, expectedNumOfPosts: 1}, // one per page\n\t\t{page: 2, pageSize: 1, expectedNumOfPosts: 1}, // one on second page\n\t\t{page: 1, pageSize: numOfPosts, expectedNumOfPosts: numOfPosts}, // all posts on single page\n\t\t{page: 12, pageSize: 1, expectedNumOfPosts: 0}, // empty page\n\t\t{page: 1, pageSize: numOfPosts + 1, expectedNumOfPosts: numOfPosts}, // page with fewer posts than its size\n\t\t{page: 5, pageSize: numOfPosts / 5, expectedNumOfPosts: 1}, // evenly distribute posts per page\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(ufmt.Sprintf(\"Page%d_Size%d\", tc.page, tc.pageSize), func(t *testing.T) {\n\t\t\tresult := m.GetPostsInRange(beforeEarliest.Unix(), afterLatest.Unix(), tc.page, tc.pageSize, \"DATE_CREATED\")\n\n\t\t\t// Count posts by how many times id: shows up in JSON string\n\t\t\tpostCount := strings.Count(result, `\"id\":\"`)\n\t\t\tuassert.Equal(t, tc.expectedNumOfPosts, postCount)\n\t\t})\n\t}\n}\n\nfunc TestGetPostsInRangeByTimestamp(t *testing.T) {\n\tm := NewMemeland()\n\tnow := time.Now()\n\n\tnumOfPosts := 5\n\tvar memeData []string\n\tfor i := 1; i \u003c= numOfPosts; i++ {\n\t\t// Prepare meme data\n\t\tnextTime := now.Add(time.Duration(i) * time.Minute)\n\t\tdata := ufmt.Sprintf(\"Meme #%d\", i)\n\t\tmemeData = append(memeData, data)\n\n\t\tm.PostMeme(data, nextTime.Unix())\n\t}\n\n\t// Get timestamps\n\tbeforeEarliest := now.Add(-1 * time.Minute)\n\tafterLatest := now.Add(time.Duration(numOfPosts)*time.Minute + time.Minute)\n\n\t// Default sort is by addition order/timestamp\n\tjsonStr := m.GetPostsInRange(\n\t\tbeforeEarliest.Unix(), // start at earliest post\n\t\tafterLatest.Unix(), // end at latest post\n\t\t1, // first page\n\t\tnumOfPosts, // all memes on the page\n\t\t\"DATE_CREATED\", // sort by newest first\n\t)\n\n\tuassert.NotEmpty(t, jsonStr, \"Expected non-empty JSON string, got empty string\")\n\n\t// Count the number of posts returned in the JSON string as a rudimentary check for correct pagination/filtering\n\tpostCount := strings.Count(jsonStr, `\"id\":\"`)\n\tuassert.Equal(t, uint64(m.MemeCounter), uint64(postCount))\n\n\t// Check if data is there\n\tfor _, expData := range memeData {\n\t\tcheck := strings.Contains(jsonStr, expData)\n\t\tuassert.True(t, check, ufmt.Sprintf(\"Expected %s in the JSON string, but counld't find it\", expData))\n\t}\n\n\t// Check if ordering is correct, sort by created date\n\tfor i := 0; i \u003c len(memeData)-2; i++ {\n\t\tcheck := strings.Index(jsonStr, memeData[i]) \u003e= strings.Index(jsonStr, memeData[i+1])\n\t\tuassert.True(t, check, ufmt.Sprintf(\"Expected %s to be before %s, but was at %d, and %d\", memeData[i], memeData[i+1], i, i+1))\n\t}\n}\n\nfunc TestGetPostsInRangeByUpvote(t *testing.T) {\n\tm := NewMemeland()\n\tnow := time.Now()\n\n\tmemeData1 := \"Meme #1\"\n\tmemeData2 := \"Meme #2\"\n\n\t// Create posts at specific times for testing\n\tid1 := m.PostMeme(memeData1, now.Unix())\n\tid2 := m.PostMeme(memeData2, now.Add(time.Minute).Unix())\n\n\tm.Upvote(id1)\n\tm.Upvote(id2)\n\n\t// Change caller so avoid double upvote panic\n\tstd.TestSetOrigCaller(testutils.TestAddress(\"alice\"))\n\tm.Upvote(id1)\n\n\t// Final upvote count:\n\t// Meme #1 - 2 upvote\n\t// Meme #2 - 1 upvotes\n\n\t// Get timestamps\n\tbeforeEarliest := now.Add(-time.Minute)\n\tafterLatest := now.Add(time.Hour)\n\n\t// Default sort is by addition order/timestamp\n\tjsonStr := m.GetPostsInRange(\n\t\tbeforeEarliest.Unix(), // start at earliest post\n\t\tafterLatest.Unix(), // end at latest post\n\t\t1, // first page\n\t\t2, // all memes on the page\n\t\t\"UPVOTES\", // sort by upvote\n\t)\n\n\tuassert.NotEmpty(t, jsonStr, \"Expected non-empty JSON string, got empty string\")\n\n\t// Count the number of posts returned in the JSON string as a rudimentary check for correct pagination/filtering\n\tpostCount := strings.Count(jsonStr, `\"id\":\"`)\n\tuassert.Equal(t, uint64(m.MemeCounter), uint64(postCount))\n\n\t// Check if ordering is correct\n\tcheck := strings.Index(jsonStr, \"Meme #1\") \u003c= strings.Index(jsonStr, \"Meme #2\")\n\tuassert.True(t, check, ufmt.Sprintf(\"Expected %s to be before %s\", memeData1, memeData2))\n}\n\nfunc TestBadSortBy(t *testing.T) {\n\tm := NewMemeland()\n\tnow := time.Now()\n\n\tnumOfPosts := 5\n\tvar memeData []string\n\tfor i := 1; i \u003c= numOfPosts; i++ {\n\t\t// Prepare meme data\n\t\tnextTime := now.Add(time.Duration(i) * time.Minute)\n\t\tdata := ufmt.Sprintf(\"Meme #%d\", i)\n\t\tmemeData = append(memeData, data)\n\n\t\tm.PostMeme(data, nextTime.Unix())\n\t}\n\n\t// Get timestamps\n\tbeforeEarliest := now.Add(-1 * time.Minute)\n\tafterLatest := now.Add(time.Duration(numOfPosts)*time.Minute + time.Minute)\n\n\ttests := []struct {\n\t\tname string\n\t\tsortBy string\n\t\twantPanic string\n\t}{\n\t\t{\n\t\t\tname: \"Empty sortBy\",\n\t\t\tsortBy: \"\",\n\t\t\twantPanic: \"runtime error: index out of range\",\n\t\t},\n\t\t{\n\t\t\tname: \"Wrong sortBy\",\n\t\t\tsortBy: \"random string\",\n\t\t\twantPanic: \"\",\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tdefer func() {\n\t\t\t\tif r := recover(); r == nil {\n\t\t\t\t\tt.Errorf(\"code did not panic when it should have\")\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\t// Panics should be caught\n\t\t\t_ = m.GetPostsInRange(beforeEarliest.Unix(), afterLatest.Unix(), 1, 1, tc.sortBy)\n\t\t})\n\t}\n}\n\nfunc TestNoPosts(t *testing.T) {\n\tm := NewMemeland()\n\n\t// Add a post to Memeland\n\tnow := time.Now().Unix()\n\n\tjsonStr := m.GetPostsInRange(0, now, 1, 1, \"DATE_CREATED\")\n\n\tuassert.Equal(t, jsonStr, \"[]\")\n}\n\nfunc TestUpvote(t *testing.T) {\n\tm := NewMemeland()\n\n\t// Add a post to Memeland\n\tnow := time.Now().Unix()\n\tpostID := m.PostMeme(\"Test meme data\", now)\n\n\t// Initial upvote count should be 0\n\tpost := m.getPost(postID)\n\tuassert.Equal(t, 0, post.UpvoteTracker.Size())\n\n\t// Upvote the post\n\tupvoteResult := m.Upvote(postID)\n\tuassert.Equal(t, \"upvote successful\", upvoteResult)\n\n\t// Retrieve the post again and check the upvote count\n\tpost = m.getPost(postID)\n\tuassert.Equal(t, 1, post.UpvoteTracker.Size())\n}\n\nfunc TestDelete(t *testing.T) {\n\talice := testutils.TestAddress(\"alice\")\n\tstd.TestSetOrigCaller(alice)\n\n\t// Alice is admin\n\tm := NewMemeland()\n\n\t// Set caller to Bob\n\tbob := testutils.TestAddress(\"bob\")\n\tstd.TestSetOrigCaller(bob)\n\n\t// Bob adds post to Memeland\n\tnow := time.Now()\n\tpostID := m.PostMeme(\"Meme #1\", now.Unix())\n\n\t// Alice removes Bob's post\n\tstd.TestSetOrigCaller(alice)\n\n\tid := m.RemovePost(postID)\n\tuassert.Equal(t, postID, id, \"post IDs not matching\")\n\tuassert.Equal(t, 0, len(m.Posts), \"there should be 0 posts after removing\")\n}\n\nfunc TestDeleteByNonAdmin(t *testing.T) {\n\talice := testutils.TestAddress(\"alice\")\n\tstd.TestSetOrigCaller(alice)\n\n\tm := NewMemeland()\n\n\t// Add a post to Memeland\n\tnow := time.Now()\n\tpostID := m.PostMeme(\"Meme #1\", now.Unix())\n\n\t// Bob will try to delete meme posted by Alice, which should fail\n\tbob := testutils.TestAddress(\"bob\")\n\tstd.TestSetOrigCaller(bob)\n\n\tdefer func() {\n\t\tif r := recover(); r == nil {\n\t\t\tt.Errorf(\"code did not panic when it should have\")\n\t\t}\n\t}()\n\n\t// Should panic - caught by defer\n\tm.RemovePost(postID)\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":"merkle","path":"gno.land/p/demo/merkle","files":[{"name":"README.md","body":"# p/demo/merkle\n\nThis package implement a merkle tree that is complient with [merkletreejs](https://github.com/merkletreejs/merkletreejs)\n\n## [merkletreejs](https://github.com/merkletreejs/merkletreejs)\n\n```javascript\nconst { MerkleTree } = require(\"merkletreejs\");\nconst SHA256 = require(\"crypto-js/sha256\");\n\nlet leaves = [];\nfor (let i = 0; i \u003c 10; i++) {\n leaves.push(SHA256(`node_${i}`));\n}\n\nconst tree = new MerkleTree(leaves, SHA256);\nconst root = tree.getRoot().toString(\"hex\");\n\nconsole.log(root); // cd8a40502b0b92bf58e7432a5abb2d8b60121cf2b7966d6ebaf103f907a1bc21\n```\n"},{"name":"merkle.gno","body":"package merkle\n\nimport (\n\t\"bytes\"\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n\t\"errors\"\n)\n\ntype Hashable interface {\n\tBytes() []byte\n}\n\ntype nodes []Node\n\ntype Node struct {\n\thash []byte\n\n\tposition uint8\n}\n\nfunc NewNode(hash []byte, position uint8) Node {\n\treturn Node{\n\t\thash: hash,\n\t\tposition: position,\n\t}\n}\n\nfunc (n Node) Position() uint8 {\n\treturn n.position\n}\n\nfunc (n Node) Hash() string {\n\treturn hex.EncodeToString(n.hash[:])\n}\n\ntype Tree struct {\n\tlayers []nodes\n}\n\n// Root return the merkle root of the tree\nfunc (t *Tree) Root() string {\n\tfor _, l := range t.layers {\n\t\tif len(l) == 1 {\n\t\t\treturn l[0].Hash()\n\t\t}\n\t}\n\treturn \"\"\n}\n\n// NewTree create a new Merkle Tree\nfunc NewTree(data []Hashable) *Tree {\n\ttree := \u0026Tree{}\n\n\tleaves := make([]Node, len(data))\n\n\tfor i, d := range data {\n\t\thash := sha256.Sum256(d.Bytes())\n\t\tleaves[i] = Node{hash: hash[:]}\n\t}\n\n\ttree.layers = []nodes{nodes(leaves)}\n\n\tvar buff bytes.Buffer\n\tfor len(leaves) \u003e 1 {\n\t\tlevel := make([]Node, 0, len(leaves)/2+1)\n\t\tfor i := 0; i \u003c len(leaves); i += 2 {\n\t\t\tbuff.Reset()\n\n\t\t\tif i \u003c len(leaves)-1 {\n\t\t\t\tbuff.Write(leaves[i].hash)\n\t\t\t\tbuff.Write(leaves[i+1].hash)\n\t\t\t\thash := sha256.Sum256(buff.Bytes())\n\t\t\t\tlevel = append(level, Node{\n\t\t\t\t\thash: hash[:],\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tlevel = append(level, leaves[i])\n\t\t\t}\n\t\t}\n\t\tleaves = level\n\t\ttree.layers = append(tree.layers, level)\n\t}\n\treturn tree\n}\n\n// Proof return a MerkleProof\nfunc (t *Tree) Proof(data Hashable) ([]Node, error) {\n\ttargetHash := sha256.Sum256(data.Bytes())\n\ttargetIndex := -1\n\n\tfor i, layer := range t.layers[0] {\n\t\tif bytes.Equal(targetHash[:], layer.hash) {\n\t\t\ttargetIndex = i\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif targetIndex == -1 {\n\t\treturn nil, errors.New(\"target not found\")\n\t}\n\n\tproofs := make([]Node, 0, len(t.layers))\n\n\tfor _, layer := range t.layers {\n\t\tvar pairIndex int\n\n\t\tif targetIndex%2 == 0 {\n\t\t\tpairIndex = targetIndex + 1\n\t\t} else {\n\t\t\tpairIndex = targetIndex - 1\n\t\t}\n\t\tif pairIndex \u003c len(layer) {\n\t\t\tproofs = append(proofs, Node{\n\t\t\t\thash: layer[pairIndex].hash,\n\t\t\t\tposition: uint8(targetIndex) % 2,\n\t\t\t})\n\t\t}\n\t\ttargetIndex /= 2\n\t}\n\treturn proofs, nil\n}\n\n// Verify if a merkle proof is valid\nfunc (t *Tree) Verify(leaf Hashable, proofs []Node) bool {\n\treturn Verify(t.Root(), leaf, proofs)\n}\n\n// Verify if a merkle proof is valid\nfunc Verify(root string, leaf Hashable, proofs []Node) bool {\n\thash := sha256.Sum256(leaf.Bytes())\n\n\tfor i := 0; i \u003c len(proofs); i += 1 {\n\t\tvar h []byte\n\t\tif proofs[i].position == 0 {\n\t\t\th = append(hash[:], proofs[i].hash...)\n\t\t} else {\n\t\t\th = append(proofs[i].hash, hash[:]...)\n\t\t}\n\t\thash = sha256.Sum256(h)\n\t}\n\treturn hex.EncodeToString(hash[:]) == root\n}\n"},{"name":"merkle_test.gno","body":"package merkle\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n)\n\ntype testData struct {\n\tcontent string\n}\n\nfunc (d testData) Bytes() []byte {\n\treturn []byte(d.content)\n}\n\nfunc TestMerkleTree(t *testing.T) {\n\ttests := []struct {\n\t\tsize int\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tsize: 1,\n\t\t\texpected: \"cf9f824bce7f5bc63d557b23591f58577f53fe29f974a615bdddbd0140f912f4\",\n\t\t},\n\t\t{\n\t\t\tsize: 3,\n\t\t\texpected: \"1a4a5f0fa267244bf9f74a63fdf2a87eed5e97e4bd104a9e94728c8fb5442177\",\n\t\t},\n\t\t{\n\t\t\tsize: 10,\n\t\t\texpected: \"cd8a40502b0b92bf58e7432a5abb2d8b60121cf2b7966d6ebaf103f907a1bc21\",\n\t\t},\n\t\t{\n\t\t\tsize: 1000,\n\t\t\texpected: \"fa533d2efdf12be26bc410dfa42936ac63361324e35e9b1ff54d422a1dd2388b\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tvar leaves []Hashable\n\t\tfor i := 0; i \u003c test.size; i++ {\n\t\t\tleaves = append(leaves, testData{fmt.Sprintf(\"node_%d\", i)})\n\t\t}\n\n\t\ttree := NewTree(leaves)\n\n\t\tif tree == nil {\n\t\t\tt.Error(\"Merkle tree creation failed\")\n\t\t}\n\n\t\troot := tree.Root()\n\n\t\tif root != test.expected {\n\t\t\tt.Fatalf(\"merkle.Tree.Root(), expected: %s; got: %s\", test.expected, root)\n\t\t}\n\n\t\tfor _, leaf := range leaves {\n\t\t\tproofs, err := tree.Proof(leaf)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(\"failed to proof leaf: %v, on tree: %v\", leaf, test)\n\t\t\t}\n\n\t\t\tok := Verify(root, leaf, proofs)\n\t\t\tif !ok {\n\t\t\t\tt.Fatal(\"failed to verify leaf: %v, on tree: %v\", leaf, tree)\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":"microblog","path":"gno.land/p/demo/microblog","files":[{"name":"microblog.gno","body":"package microblog\n\nimport (\n\t\"errors\"\n\t\"sort\"\n\t\"std\"\n\t\"strings\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar (\n\tErrNotFound = errors.New(\"not found\")\n\tStatusNotFound = \"404\"\n)\n\ntype Microblog struct {\n\tTitle string\n\tPrefix string // i.e. r/gnoland/blog:\n\tPages avl.Tree // author (string) -\u003e Page\n}\n\nfunc NewMicroblog(title string, prefix string) (m *Microblog) {\n\treturn \u0026Microblog{\n\t\tTitle: title,\n\t\tPrefix: prefix,\n\t\tPages: avl.Tree{},\n\t}\n}\n\nfunc (m *Microblog) GetPages() []*Page {\n\tvar (\n\t\tpages = make([]*Page, m.Pages.Size())\n\t\tindex = 0\n\t)\n\n\tm.Pages.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tpages[index] = value.(*Page)\n\t\tindex++\n\t\treturn false\n\t})\n\n\tsort.Sort(byLastPosted(pages))\n\n\treturn pages\n}\n\nfunc (m *Microblog) NewPost(text string) error {\n\tauthor := std.GetOrigCaller()\n\t_, found := m.Pages.Get(author.String())\n\tif !found {\n\t\t// make a new page for the new author\n\t\tm.Pages.Set(author.String(), \u0026Page{\n\t\t\tAuthor: author,\n\t\t\tCreatedAt: time.Now(),\n\t\t})\n\t}\n\n\tpage, err := m.GetPage(author.String())\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn page.NewPost(text)\n}\n\nfunc (m *Microblog) GetPage(author string) (*Page, error) {\n\tsilo, found := m.Pages.Get(author)\n\tif !found {\n\t\treturn nil, ErrNotFound\n\t}\n\treturn silo.(*Page), nil\n}\n\ntype Page struct {\n\tID int\n\tAuthor std.Address\n\tCreatedAt time.Time\n\tLastPosted time.Time\n\tPosts avl.Tree // time -\u003e Post\n}\n\n// byLastPosted implements sort.Interface for []Page based on\n// the LastPosted field.\ntype byLastPosted []*Page\n\nfunc (a byLastPosted) Len() int { return len(a) }\nfunc (a byLastPosted) Swap(i, j int) { a[i], a[j] = a[j], a[i] }\nfunc (a byLastPosted) Less(i, j int) bool { return a[i].LastPosted.After(a[j].LastPosted) }\n\nfunc (p *Page) NewPost(text string) error {\n\tnow := time.Now()\n\tp.LastPosted = now\n\tp.Posts.Set(ufmt.Sprintf(\"%s%d\", now.Format(time.RFC3339), p.Posts.Size()), \u0026Post{\n\t\tID: p.Posts.Size(),\n\t\tText: text,\n\t\tCreatedAt: now,\n\t})\n\treturn nil\n}\n\nfunc (p *Page) GetPosts() []*Post {\n\tposts := make([]*Post, p.Posts.Size())\n\ti := 0\n\tp.Posts.ReverseIterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tpostParsed := value.(*Post)\n\t\tposts[i] = postParsed\n\t\ti++\n\t\treturn false\n\t})\n\treturn posts\n}\n\n// Post lists the specific update\ntype Post struct {\n\tID int\n\tCreatedAt time.Time\n\tText string\n}\n\nfunc (p *Post) String() string {\n\treturn \"\u003e \" + strings.ReplaceAll(p.Text, \"\\n\", \"\\n\u003e\\n\u003e\") + \"\\n\u003e\\n\u003e *\" + p.CreatedAt.Format(time.RFC1123) + \"*\"\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":"microblog","path":"gno.land/p/demo/microblog","files":[{"name":"microblog.gno","body":"package microblog\n\nimport (\n\t\"errors\"\n\t\"sort\"\n\t\"std\"\n\t\"strings\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar (\n\tErrNotFound = errors.New(\"not found\")\n\tStatusNotFound = \"404\"\n)\n\ntype Microblog struct {\n\tTitle string\n\tPrefix string // i.e. r/gnoland/blog:\n\tPages avl.Tree // author (string) -\u003e Page\n}\n\nfunc NewMicroblog(title string, prefix string) (m *Microblog) {\n\treturn \u0026Microblog{\n\t\tTitle: title,\n\t\tPrefix: prefix,\n\t\tPages: avl.Tree{},\n\t}\n}\n\nfunc (m *Microblog) GetPages() []*Page {\n\tvar (\n\t\tpages = make([]*Page, m.Pages.Size())\n\t\tindex = 0\n\t)\n\n\tm.Pages.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tpages[index] = value.(*Page)\n\t\tindex++\n\t\treturn false\n\t})\n\n\tsort.Sort(byLastPosted(pages))\n\n\treturn pages\n}\n\nfunc (m *Microblog) NewPost(text string) error {\n\tauthor := std.OrigCaller()\n\t_, found := m.Pages.Get(author.String())\n\tif !found {\n\t\t// make a new page for the new author\n\t\tm.Pages.Set(author.String(), \u0026Page{\n\t\t\tAuthor: author,\n\t\t\tCreatedAt: time.Now(),\n\t\t})\n\t}\n\n\tpage, err := m.GetPage(author.String())\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn page.NewPost(text)\n}\n\nfunc (m *Microblog) GetPage(author string) (*Page, error) {\n\tsilo, found := m.Pages.Get(author)\n\tif !found {\n\t\treturn nil, ErrNotFound\n\t}\n\treturn silo.(*Page), nil\n}\n\ntype Page struct {\n\tID int\n\tAuthor std.Address\n\tCreatedAt time.Time\n\tLastPosted time.Time\n\tPosts avl.Tree // time -\u003e Post\n}\n\n// byLastPosted implements sort.Interface for []Page based on\n// the LastPosted field.\ntype byLastPosted []*Page\n\nfunc (a byLastPosted) Len() int { return len(a) }\nfunc (a byLastPosted) Swap(i, j int) { a[i], a[j] = a[j], a[i] }\nfunc (a byLastPosted) Less(i, j int) bool { return a[i].LastPosted.After(a[j].LastPosted) }\n\nfunc (p *Page) NewPost(text string) error {\n\tnow := time.Now()\n\tp.LastPosted = now\n\tp.Posts.Set(ufmt.Sprintf(\"%s%d\", now.Format(time.RFC3339), p.Posts.Size()), \u0026Post{\n\t\tID: p.Posts.Size(),\n\t\tText: text,\n\t\tCreatedAt: now,\n\t})\n\treturn nil\n}\n\nfunc (p *Page) GetPosts() []*Post {\n\tposts := make([]*Post, p.Posts.Size())\n\ti := 0\n\tp.Posts.ReverseIterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tpostParsed := value.(*Post)\n\t\tposts[i] = postParsed\n\t\ti++\n\t\treturn false\n\t})\n\treturn posts\n}\n\n// Post lists the specific update\ntype Post struct {\n\tID int\n\tCreatedAt time.Time\n\tText string\n}\n\nfunc (p *Post) String() string {\n\treturn \"\u003e \" + strings.ReplaceAll(p.Text, \"\\n\", \"\\n\u003e\\n\u003e\") + \"\\n\u003e\\n\u003e *\" + p.CreatedAt.Format(time.RFC1123) + \"*\"\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":"nestedpkg","path":"gno.land/p/demo/nestedpkg","files":[{"name":"nestedpkg.gno","body":"// Package nestedpkg provides helpers for package-path based access control.\n// It is useful for upgrade patterns relying on namespaces.\npackage nestedpkg\n\n// To test this from a realm and have std.CurrentRealm/PrevRealm work correctly,\n// this file is tested from gno.land/r/demo/tests/nestedpkg_test.gno\n// XXX: move test to ths directory once we support testing a package and\n// specifying values for both PrevRealm and CurrentRealm.\n\nimport (\n\t\"std\"\n\t\"strings\"\n)\n\n// IsCallerSubPath checks if the caller realm is located in a subfolder of the current realm.\nfunc IsCallerSubPath() bool {\n\tvar (\n\t\tcur = std.CurrentRealm().PkgPath() + \"/\"\n\t\tprev = std.PrevRealm().PkgPath() + \"/\"\n\t)\n\treturn strings.HasPrefix(prev, cur)\n}\n\n// AssertCallerIsSubPath panics if IsCallerSubPath returns false.\nfunc AssertCallerIsSubPath() {\n\tvar (\n\t\tcur = std.CurrentRealm().PkgPath() + \"/\"\n\t\tprev = std.PrevRealm().PkgPath() + \"/\"\n\t)\n\tif !strings.HasPrefix(prev, cur) {\n\t\tpanic(\"call restricted to nested packages. current realm is \" + cur + \", previous realm is \" + prev)\n\t}\n}\n\n// IsCallerParentPath checks if the caller realm is located in a parent location of the current realm.\nfunc IsCallerParentPath() bool {\n\tvar (\n\t\tcur = std.CurrentRealm().PkgPath() + \"/\"\n\t\tprev = std.PrevRealm().PkgPath() + \"/\"\n\t)\n\treturn strings.HasPrefix(cur, prev)\n}\n\n// AssertCallerIsParentPath panics if IsCallerParentPath returns false.\nfunc AssertCallerIsParentPath() {\n\tvar (\n\t\tcur = std.CurrentRealm().PkgPath() + \"/\"\n\t\tprev = std.PrevRealm().PkgPath() + \"/\"\n\t)\n\tif !strings.HasPrefix(cur, prev) {\n\t\tpanic(\"call restricted to parent packages. current realm is \" + cur + \", previous realm is \" + prev)\n\t}\n}\n\n// IsSameNamespace checks if the caller realm and the current realm are in the same namespace.\nfunc IsSameNamespace() bool {\n\tvar (\n\t\tcur = nsFromPath(std.CurrentRealm().PkgPath()) + \"/\"\n\t\tprev = nsFromPath(std.PrevRealm().PkgPath()) + \"/\"\n\t)\n\treturn cur == prev\n}\n\n// AssertIsSameNamespace panics if IsSameNamespace returns false.\nfunc AssertIsSameNamespace() {\n\tvar (\n\t\tcur = nsFromPath(std.CurrentRealm().PkgPath()) + \"/\"\n\t\tprev = nsFromPath(std.PrevRealm().PkgPath()) + \"/\"\n\t)\n\tif cur != prev {\n\t\tpanic(\"call restricted to packages from the same namespace. current realm is \" + cur + \", previous realm is \" + prev)\n\t}\n}\n\n// nsFromPath extracts the namespace from a package path.\nfunc nsFromPath(pkgpath string) string {\n\tparts := strings.Split(pkgpath, \"/\")\n\n\t// Specifically for gno.land, potential paths are in the form of DOMAIN/r/NAMESPACE/...\n\t// XXX: Consider extra checks.\n\t// XXX: Support non gno.land domains, where p/ and r/ won't be enforced.\n\tif len(parts) \u003e= 3 {\n\t\treturn parts[2]\n\t}\n\treturn \"\"\n}\n\n// XXX: Consider adding IsCallerDirectlySubPath\n// XXX: Consider adding IsCallerDirectlyParentPath\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":"authorizable","path":"gno.land/p/demo/ownable/exts/authorizable","files":[{"name":"authorizable.gno","body":"// Package authorizable is an extension of p/demo/ownable;\n// It allows the user to instantiate an Authorizable struct, which extends\n// p/demo/ownable with a list of users that are authorized for something.\n// By using authorizable, you have a superuser (ownable), as well as another\n// authorization level, which can be used for adding moderators or similar to your realm.\npackage authorizable\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype Authorizable struct {\n\t*ownable.Ownable // owner in ownable is superuser\n\tauthorized *avl.Tree // std.Addr \u003e struct{}{}\n}\n\nfunc NewAuthorizable() *Authorizable {\n\ta := \u0026Authorizable{\n\t\townable.New(),\n\t\tavl.NewTree(),\n\t}\n\n\t// Add owner to auth list\n\ta.authorized.Set(a.Owner().String(), struct{}{})\n\treturn a\n}\n\nfunc NewAuthorizableWithAddress(addr std.Address) *Authorizable {\n\ta := \u0026Authorizable{\n\t\townable.NewWithAddress(addr),\n\t\tavl.NewTree(),\n\t}\n\n\t// Add owner to auth list\n\ta.authorized.Set(a.Owner().String(), struct{}{})\n\treturn a\n}\n\nfunc (a *Authorizable) AddToAuthList(addr std.Address) error {\n\tif err := a.CallerIsOwner(); err != nil {\n\t\treturn ErrNotSuperuser\n\t}\n\n\tif _, exists := a.authorized.Get(addr.String()); exists {\n\t\treturn ErrAlreadyInList\n\t}\n\n\ta.authorized.Set(addr.String(), struct{}{})\n\n\treturn nil\n}\n\nfunc (a *Authorizable) DeleteFromAuthList(addr std.Address) error {\n\tif err := a.CallerIsOwner(); err != nil {\n\t\treturn ErrNotSuperuser\n\t}\n\n\tif !a.authorized.Has(addr.String()) {\n\t\treturn ErrNotInAuthList\n\t}\n\n\tif _, removed := a.authorized.Remove(addr.String()); !removed {\n\t\tstr := ufmt.Sprintf(\"authorizable: could not remove %s from auth list\", addr.String())\n\t\tpanic(str)\n\t}\n\n\treturn nil\n}\n\nfunc (a Authorizable) CallerOnAuthList() error {\n\tcaller := std.PrevRealm().Addr()\n\n\tif !a.authorized.Has(caller.String()) {\n\t\treturn ErrNotInAuthList\n\t}\n\n\treturn nil\n}\n\nfunc (a Authorizable) AssertOnAuthList() {\n\tcaller := std.PrevRealm().Addr()\n\n\tif !a.authorized.Has(caller.String()) {\n\t\tpanic(ErrNotInAuthList)\n\t}\n}\n"},{"name":"authorizable_test.gno","body":"package authorizable\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\talice = testutils.TestAddress(\"alice\")\n\tbob = testutils.TestAddress(\"bob\")\n\tcharlie = testutils.TestAddress(\"charlie\")\n)\n\nfunc TestNewAuthorizable(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOrigCaller(alice) // TODO(bug, issue #2371): should not be needed\n\n\ta := NewAuthorizable()\n\tgot := a.Owner()\n\n\tif alice != got {\n\t\tt.Fatalf(\"Expected %s, got: %s\", alice, got)\n\t}\n}\n\nfunc TestNewAuthorizableWithAddress(t *testing.T) {\n\ta := NewAuthorizableWithAddress(alice)\n\n\tgot := a.Owner()\n\n\tif alice != got {\n\t\tt.Fatalf(\"Expected %s, got: %s\", alice, got)\n\t}\n}\n\nfunc TestCallerOnAuthList(t *testing.T) {\n\ta := NewAuthorizableWithAddress(alice)\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOrigCaller(alice)\n\n\tif err := a.CallerOnAuthList(); err == ErrNotInAuthList {\n\t\tt.Fatalf(\"expected alice to be on the list\")\n\t}\n}\n\nfunc TestNotCallerOnAuthList(t *testing.T) {\n\ta := NewAuthorizableWithAddress(alice)\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\tstd.TestSetOrigCaller(bob)\n\n\tif err := a.CallerOnAuthList(); err == nil {\n\t\tt.Fatalf(\"expected bob to not be on the list\")\n\t}\n}\n\nfunc TestAddToAuthList(t *testing.T) {\n\ta := NewAuthorizableWithAddress(alice)\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOrigCaller(alice)\n\n\tif err := a.AddToAuthList(bob); err != nil {\n\t\tt.Fatalf(\"Expected no error, got %v\", err)\n\t}\n\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\tstd.TestSetOrigCaller(bob)\n\n\tif err := a.AddToAuthList(bob); err == nil {\n\t\tt.Fatalf(\"Expected AddToAuth to error while bob called it, but it didn't\")\n\t}\n}\n\nfunc TestDeleteFromList(t *testing.T) {\n\ta := NewAuthorizableWithAddress(alice)\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOrigCaller(alice)\n\n\tif err := a.AddToAuthList(bob); err != nil {\n\t\tt.Fatalf(\"Expected no error, got %v\", err)\n\t}\n\n\tif err := a.AddToAuthList(charlie); err != nil {\n\t\tt.Fatalf(\"Expected no error, got %v\", err)\n\t}\n\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\tstd.TestSetOrigCaller(bob)\n\n\t// Try an unauthorized deletion\n\tif err := a.DeleteFromAuthList(alice); err == nil {\n\t\tt.Fatalf(\"Expected DelFromAuth to error with %v\", err)\n\t}\n\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOrigCaller(alice)\n\n\tif err := a.DeleteFromAuthList(charlie); err != nil {\n\t\tt.Fatalf(\"Expected no error, got %v\", err)\n\t}\n}\n\nfunc TestAssertOnList(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOrigCaller(alice)\n\ta := NewAuthorizableWithAddress(alice)\n\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\tstd.TestSetOrigCaller(bob)\n\n\tuassert.PanicsWithMessage(t, ErrNotInAuthList.Error(), func() {\n\t\ta.AssertOnAuthList()\n\t})\n}\n"},{"name":"errors.gno","body":"package authorizable\n\nimport \"errors\"\n\nvar (\n\tErrNotInAuthList = errors.New(\"authorizable: caller is not in authorized list\")\n\tErrNotSuperuser = errors.New(\"authorizable: caller is not superuser\")\n\tErrAlreadyInList = errors.New(\"authorizable: address is already in authorized list\")\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":"pausable","path":"gno.land/p/demo/pausable","files":[{"name":"pausable.gno","body":"package pausable\n\nimport \"gno.land/p/demo/ownable\"\n\ntype Pausable struct {\n\t*ownable.Ownable\n\tpaused bool\n}\n\n// New returns a new Pausable struct with non-paused state as default\nfunc New() *Pausable {\n\treturn \u0026Pausable{\n\t\tOwnable: ownable.New(),\n\t\tpaused: false,\n\t}\n}\n\n// NewFromOwnable is the same as New, but with a pre-existing top-level ownable\nfunc NewFromOwnable(ownable *ownable.Ownable) *Pausable {\n\treturn \u0026Pausable{\n\t\tOwnable: ownable,\n\t\tpaused: false,\n\t}\n}\n\n// IsPaused checks if Pausable is paused\nfunc (p Pausable) IsPaused() bool {\n\treturn p.paused\n}\n\n// Pause sets the state of Pausable to true, meaning all pausable functions are paused\nfunc (p *Pausable) Pause() error {\n\tif err := p.CallerIsOwner(); err != nil {\n\t\treturn err\n\t}\n\n\tp.paused = true\n\treturn nil\n}\n\n// Unpause sets the state of Pausable to false, meaning all pausable functions are resumed\nfunc (p *Pausable) Unpause() error {\n\tif err := p.CallerIsOwner(); err != nil {\n\t\treturn err\n\t}\n\n\tp.paused = false\n\treturn nil\n}\n"},{"name":"pausable_test.gno","body":"package pausable\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nvar (\n\tfirstCaller = std.Address(\"g1l9aypkr8xfvs82zeux486ddzec88ty69lue9de\")\n\tsecondCaller = std.Address(\"g127jydsh6cms3lrtdenydxsckh23a8d6emqcvfa\")\n)\n\nfunc TestNew(t *testing.T) {\n\tstd.TestSetOrigCaller(firstCaller)\n\n\tresult := New()\n\n\turequire.False(t, result.paused, \"Expected result to be unpaused\")\n\turequire.Equal(t, firstCaller.String(), result.Owner().String())\n}\n\nfunc TestNewFromOwnable(t *testing.T) {\n\tstd.TestSetOrigCaller(firstCaller)\n\to := ownable.New()\n\n\tstd.TestSetOrigCaller(secondCaller)\n\tresult := NewFromOwnable(o)\n\n\turequire.Equal(t, firstCaller.String(), result.Owner().String())\n}\n\nfunc TestSetUnpaused(t *testing.T) {\n\tstd.TestSetOrigCaller(firstCaller)\n\n\tresult := New()\n\tresult.Unpause()\n\n\turequire.False(t, result.IsPaused(), \"Expected result to be unpaused\")\n}\n\nfunc TestSetPaused(t *testing.T) {\n\tstd.TestSetOrigCaller(firstCaller)\n\n\tresult := New()\n\tresult.Pause()\n\n\turequire.True(t, result.IsPaused(), \"Expected result to be paused\")\n}\n\nfunc TestIsPaused(t *testing.T) {\n\tstd.TestSetOrigCaller(firstCaller)\n\n\tresult := New()\n\turequire.False(t, result.IsPaused(), \"Expected result to be unpaused\")\n\n\tresult.Pause()\n\turequire.True(t, result.IsPaused(), \"Expected result to be paused\")\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":"releases","path":"gno.land/p/demo/releases","files":[{"name":"changelog.gno","body":"package releases\n\ntype changelog struct {\n\tname string\n\treleases []release\n}\n\nfunc NewChangelog(name string) *changelog {\n\treturn \u0026changelog{\n\t\tname: name,\n\t\treleases: make([]release, 0),\n\t}\n}\n\nfunc (c *changelog) NewRelease(version, url, notes string) {\n\tif latest := c.Latest(); latest != nil {\n\t\tlatest.isLatest = false\n\t}\n\n\trelease := release{\n\t\t// manual\n\t\tversion: version,\n\t\turl: url,\n\t\tnotes: notes,\n\n\t\t// internal\n\t\tchangelog: c,\n\t\tisLatest: true,\n\t}\n\n\tc.releases = append(c.releases, release)\n}\n\nfunc (c *changelog) Render(path string) string {\n\tif path == \"\" {\n\t\toutput := \"# \" + c.name + \"\\n\\n\"\n\t\tmax := len(c.releases) - 1\n\t\tmin := 0\n\t\tif max-min \u003e 10 {\n\t\t\tmin = max - 10\n\t\t}\n\t\tfor i := max; i \u003e= min; i-- {\n\t\t\trelease := c.releases[i]\n\t\t\toutput += release.Render()\n\t\t}\n\t\treturn output\n\t}\n\n\trelease := c.ByVersion(path)\n\tif release != nil {\n\t\treturn release.Render()\n\t}\n\n\treturn \"no such release\"\n}\n\nfunc (c *changelog) Latest() *release {\n\tif len(c.releases) \u003e 0 {\n\t\tpos := len(c.releases) - 1\n\t\treturn \u0026c.releases[pos]\n\t}\n\treturn nil\n}\n\nfunc (c *changelog) ByVersion(version string) *release {\n\tfor _, release := range c.releases {\n\t\tif release.version == version {\n\t\t\treturn \u0026release\n\t\t}\n\t}\n\treturn nil\n}\n"},{"name":"release.gno","body":"package releases\n\ntype release struct {\n\t// manual\n\tversion string\n\turl string\n\tnotes string\n\n\t// internal\n\tisLatest bool\n\tchangelog *changelog\n}\n\nfunc (r *release) URL() string { return r.url }\nfunc (r *release) Version() string { return r.version }\nfunc (r *release) Notes() string { return r.notes }\nfunc (r *release) IsLatest() bool { return r.isLatest }\n\nfunc (r *release) Title() string {\n\toutput := r.changelog.name + \" \" + r.version\n\tif r.isLatest {\n\t\toutput += \" (latest)\"\n\t}\n\treturn output\n}\n\nfunc (r *release) Link() string {\n\treturn \"[\" + r.Title() + \"](\" + r.url + \")\"\n}\n\nfunc (r *release) Render() string {\n\toutput := \"\"\n\toutput += \"## \" + r.Link() + \"\\n\\n\"\n\tif r.notes != \"\" {\n\t\toutput += r.notes + \"\\n\\n\"\n\t}\n\treturn output\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":"simpledao","path":"gno.land/p/demo/simpledao","files":[{"name":"dao.gno","body":"package simpledao\n\nimport (\n\t\"errors\"\n\t\"std\"\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\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\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\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(\"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\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})\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\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\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, 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\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\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) 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\toutput := \"\"\n\toutput += ufmt.Sprintf(\"Author: %s\", p.Author().String())\n\toutput += \"\\n\\n\"\n\toutput += p.Description()\n\toutput += \"\\n\\n\"\n\toutput += ufmt.Sprintf(\"Status: %s\", p.Status().String())\n\toutput += \"\\n\\n\"\n\toutput += ufmt.Sprintf(\n\t\t\"Voting stats: YES %d (%d%%), NO %d (%d%%), ABSTAIN %d (%d%%), MISSING VOTE %d (%d%%)\",\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\toutput += \"\\n\\n\"\n\toutput += ufmt.Sprintf(\"Threshold met: %t\", stats.YayVotes \u003e (2*stats.TotalVotingPower)/3)\n\n\treturn output\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_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\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\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\tvar (\n\t\tcaller = getDAOCaller()\n\t\tsentCoins = std.OrigSend() // 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\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.OrigSend() // 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.OrigCaller()\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(\"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\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})\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\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\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, 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\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\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) 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\toutput := \"\"\n\toutput += ufmt.Sprintf(\"Author: %s\", p.Author().String())\n\toutput += \"\\n\\n\"\n\toutput += p.Description()\n\toutput += \"\\n\\n\"\n\toutput += ufmt.Sprintf(\"Status: %s\", p.Status().String())\n\toutput += \"\\n\\n\"\n\toutput += ufmt.Sprintf(\n\t\t\"Voting stats: YES %d (%d%%), NO %d (%d%%), ABSTAIN %d (%d%%), MISSING VOTE %d (%d%%)\",\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\toutput += \"\\n\\n\"\n\toutput += ufmt.Sprintf(\"Threshold met: %t\", stats.YayVotes \u003e (2*stats.TotalVotingPower)/3)\n\n\treturn output\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_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"stack","path":"gno.land/p/demo/stack","files":[{"name":"stack.gno","body":"package stack\n\ntype Stack struct {\n\ttop *node\n\tlength int\n}\n\ntype node struct {\n\tvalue interface{}\n\tprev *node\n}\n\nfunc New() *Stack {\n\treturn \u0026Stack{nil, 0}\n}\n\nfunc (s *Stack) Len() int {\n\treturn s.length\n}\n\nfunc (s *Stack) Top() interface{} {\n\tif s.length == 0 {\n\t\treturn nil\n\t}\n\treturn s.top.value\n}\n\nfunc (s *Stack) Pop() interface{} {\n\tif s.length == 0 {\n\t\treturn nil\n\t}\n\n\tnode := s.top\n\ts.top = node.prev\n\ts.length -= 1\n\treturn node.value\n}\n\nfunc (s *Stack) Push(value interface{}) {\n\tnode := \u0026node{value, s.top}\n\ts.top = node\n\ts.length += 1\n}\n"},{"name":"stack_test.gno","body":"package stack\n\nimport \"testing\"\n\nfunc TestStack(t *testing.T) {\n\ts := New() // Empty stack\n\n\tif s.Len() != 0 {\n\t\tt.Errorf(\"s.Len(): expected 0; got %d\", s.Len())\n\t}\n\n\ts.Push(1)\n\n\tif s.Len() != 1 {\n\t\tt.Errorf(\"s.Len(): expected 1; got %d\", s.Len())\n\t}\n\n\tif top := s.Top(); top.(int) != 1 {\n\t\tt.Errorf(\"s.Top(): expected 1; got %v\", top.(int))\n\t}\n\n\tif elem := s.Pop(); elem.(int) != 1 {\n\t\tt.Errorf(\"s.Pop(): expected 1; got %v\", elem.(int))\n\t}\n\tif s.Len() != 0 {\n\t\tt.Errorf(\"s.Len(): expected 0; got %d\", s.Len())\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":"subscription","path":"gno.land/p/demo/subscription","files":[{"name":"doc.gno","body":"// Package subscription provides a flexible system for managing both recurring and\n// lifetime subscriptions in Gno applications. It enables developers to handle\n// payment-based access control for services or products. The library supports\n// both subscriptions requiring periodic payments (recurring) and one-time payments\n// (lifetime). Subscriptions are tracked using an AVL tree for efficient management\n// of subscription statuses.\n//\n// Usage:\n//\n// Import the required sub-packages (`recurring` and/or `lifetime`) to manage specific\n// subscription types. The methods provided allow users to subscribe, check subscription\n// status, and manage payments.\n//\n// Recurring Subscription:\n//\n// Recurring subscriptions require periodic payments to maintain access.\n// Users pay to extend their access for a specific duration.\n//\n// Example:\n//\n//\t// Create a recurring subscription requiring 100 ugnot every 30 days\n//\trecSub := recurring.NewRecurringSubscription(time.Hour * 24 * 30, 100)\n//\n//\t// Process payment for the recurring subscription\n//\trecSub.Subscribe()\n//\n//\t// Gift a recurring subscription to another user\n//\trecSub.GiftSubscription(recipientAddress)\n//\n//\t// Check if a user has a valid subscription\n//\trecSub.HasValidSubscription(addr)\n//\n//\t// Get the expiration date of the subscription\n//\trecSub.GetExpiration(caller)\n//\n//\t// Update the subscription amount to 200 ugnot\n//\trecSub.UpdateAmount(200)\n//\n//\t// Get the current subscription amount\n//\trecSub.GetAmount()\n//\n// Lifetime Subscription:\n//\n// Lifetime subscriptions require a one-time payment for permanent access.\n// Once paid, users have indefinite access without further payments.\n//\n// Example:\n//\n//\t// Create a lifetime subscription costing 500 ugnot\n//\tlifeSub := lifetime.NewLifetimeSubscription(500)\n//\n//\t// Process payment for lifetime access\n//\tlifeSub.Subscribe()\n//\n//\t// Gift a lifetime subscription to another user\n//\tlifeSub.GiftSubscription(recipientAddress)\n//\n//\t// Check if a user has a valid subscription\n//\tlifeSub.HasValidSubscription(addr)\n//\n//\t// Update the lifetime subscription amount to 1000 ugnot\n//\tlifeSub.UpdateAmount(1000)\n//\n//\t// Get the current lifetime subscription amount\n//\tlifeSub.GetAmount()\npackage subscription\n"},{"name":"subscription.gno","body":"package subscription\n\nimport (\n\t\"std\"\n)\n\n// Subscription interface defines standard methods that all subscription types must implement.\ntype Subscription interface {\n\tHasValidSubscription(std.Address) error\n\tSubscribe() error\n\tUpdateAmount(newAmount int64) error\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":"lifetime","path":"gno.land/p/demo/subscription/lifetime","files":[{"name":"errors.gno","body":"package lifetime\n\nimport \"errors\"\n\nvar (\n\tErrNoSub = errors.New(\"lifetime subscription: no active subscription found\")\n\tErrAmt = errors.New(\"lifetime subscription: payment amount does not match the required subscription amount\")\n\tErrAlreadySub = errors.New(\"lifetime subscription: this address already has an active lifetime subscription\")\n\tErrNotAuthorized = errors.New(\"lifetime subscription: action not authorized\")\n)\n"},{"name":"lifetime.gno","body":"package lifetime\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n)\n\n// LifetimeSubscription represents a subscription that requires only a one-time payment.\n// It grants permanent access to a service or product.\ntype LifetimeSubscription struct {\n\townable.Ownable\n\tamount int64\n\tsubs *avl.Tree // std.Address -\u003e bool\n}\n\n// NewLifetimeSubscription creates and returns a new lifetime subscription.\nfunc NewLifetimeSubscription(amount int64) *LifetimeSubscription {\n\treturn \u0026LifetimeSubscription{\n\t\tOwnable: *ownable.New(),\n\t\tamount: amount,\n\t\tsubs: avl.NewTree(),\n\t}\n}\n\n// processSubscription handles the subscription process for a given receiver.\nfunc (ls *LifetimeSubscription) processSubscription(receiver std.Address) error {\n\tamount := std.GetOrigSend()\n\n\tif amount.AmountOf(\"ugnot\") != ls.amount {\n\t\treturn ErrAmt\n\t}\n\n\t_, exists := ls.subs.Get(receiver.String())\n\n\tif exists {\n\t\treturn ErrAlreadySub\n\t}\n\n\tls.subs.Set(receiver.String(), true)\n\n\treturn nil\n}\n\n// Subscribe processes the payment for a lifetime subscription.\nfunc (ls *LifetimeSubscription) Subscribe() error {\n\tcaller := std.PrevRealm().Addr()\n\treturn ls.processSubscription(caller)\n}\n\n// GiftSubscription allows the caller to pay for a lifetime subscription for another user.\nfunc (ls *LifetimeSubscription) GiftSubscription(receiver std.Address) error {\n\treturn ls.processSubscription(receiver)\n}\n\n// HasValidSubscription checks if the given address has an active lifetime subscription.\nfunc (ls *LifetimeSubscription) HasValidSubscription(addr std.Address) error {\n\t_, exists := ls.subs.Get(addr.String())\n\n\tif !exists {\n\t\treturn ErrNoSub\n\t}\n\n\treturn nil\n}\n\n// UpdateAmount allows the owner of the LifetimeSubscription contract to update the subscription price.\nfunc (ls *LifetimeSubscription) UpdateAmount(newAmount int64) error {\n\tif err := ls.CallerIsOwner(); err != nil {\n\t\treturn ErrNotAuthorized\n\t}\n\n\tls.amount = newAmount\n\treturn nil\n}\n\n// GetAmount returns the current subscription price.\nfunc (ls *LifetimeSubscription) GetAmount() int64 {\n\treturn ls.amount\n}\n"},{"name":"lifetime_test.gno","body":"package lifetime\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\talice = testutils.TestAddress(\"alice\")\n\tbob = testutils.TestAddress(\"bob\")\n\tcharlie = testutils.TestAddress(\"charlie\")\n)\n\nfunc TestLifetimeSubscription(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := ls.Subscribe()\n\tuassert.NoError(t, err, \"Expected ProcessPayment to succeed\")\n\n\terr = ls.HasValidSubscription(std.PrevRealm().Addr())\n\tuassert.NoError(t, err, \"Expected Alice to have access\")\n}\n\nfunc TestLifetimeSubscriptionGift(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := ls.GiftSubscription(bob)\n\tuassert.NoError(t, err, \"Expected ProcessPaymentGift to succeed for Bob\")\n\n\terr = ls.HasValidSubscription(bob)\n\tuassert.NoError(t, err, \"Expected Bob to have access\")\n\n\terr = ls.HasValidSubscription(charlie)\n\tuassert.Error(t, err, \"Expected Charlie to fail access check\")\n}\n\nfunc TestUpdateAmountAuthorization(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\terr := ls.UpdateAmount(2000)\n\tuassert.NoError(t, err, \"Expected Alice to succeed in updating amount\")\n\n\tstd.TestSetOrigCaller(bob)\n\n\terr = ls.UpdateAmount(3000)\n\tuassert.Error(t, err, \"Expected Bob to fail when updating amount\")\n}\n\nfunc TestIncorrectPaymentAmount(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 500}}, nil)\n\terr := ls.Subscribe()\n\tuassert.Error(t, err, \"Expected payment to fail with incorrect amount\")\n}\n\nfunc TestMultipleSubscriptionAttempts(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := ls.Subscribe()\n\tuassert.NoError(t, err, \"Expected first subscription to succeed\")\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr = ls.Subscribe()\n\tuassert.Error(t, err, \"Expected second subscription to fail as Alice is already subscribed\")\n}\n\nfunc TestGiftSubscriptionWithIncorrectAmount(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 500}}, nil)\n\terr := ls.GiftSubscription(bob)\n\tuassert.Error(t, err, \"Expected gift subscription to fail with incorrect amount\")\n\n\terr = ls.HasValidSubscription(bob)\n\tuassert.Error(t, err, \"Expected Bob to not have access after incorrect gift subscription\")\n}\n\nfunc TestUpdateAmountEffectiveness(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\terr := ls.UpdateAmount(2000)\n\tuassert.NoError(t, err, \"Expected Alice to succeed in updating amount\")\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr = ls.Subscribe()\n\tuassert.Error(t, err, \"Expected subscription to fail with old amount after update\")\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 2000}}, nil)\n\terr = ls.Subscribe()\n\tuassert.NoError(t, err, \"Expected subscription to succeed with new amount\")\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":"recurring","path":"gno.land/p/demo/subscription/recurring","files":[{"name":"errors.gno","body":"package recurring\n\nimport \"errors\"\n\nvar (\n\tErrNoSub = errors.New(\"recurring subscription: no active subscription found\")\n\tErrSubExpired = errors.New(\"recurring subscription: your subscription has expired\")\n\tErrAmt = errors.New(\"recurring subscription: payment amount does not match the required subscription amount\")\n\tErrAlreadySub = errors.New(\"recurring subscription: this address already has an active subscription\")\n\tErrNotAuthorized = errors.New(\"recurring subscription: action not authorized\")\n)\n"},{"name":"recurring.gno","body":"package recurring\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n)\n\n// RecurringSubscription represents a subscription that requires periodic payments.\n// It includes the duration of the subscription and the amount required per period.\ntype RecurringSubscription struct {\n\townable.Ownable\n\tduration time.Duration\n\tamount int64\n\tsubs *avl.Tree // std.Address -\u003e time.Time\n}\n\n// NewRecurringSubscription creates and returns a new recurring subscription.\nfunc NewRecurringSubscription(duration time.Duration, amount int64) *RecurringSubscription {\n\treturn \u0026RecurringSubscription{\n\t\tOwnable: *ownable.New(),\n\t\tduration: duration,\n\t\tamount: amount,\n\t\tsubs: avl.NewTree(),\n\t}\n}\n\n// HasValidSubscription verifies if the caller has an active recurring subscription.\nfunc (rs *RecurringSubscription) HasValidSubscription(addr std.Address) error {\n\texpTime, exists := rs.subs.Get(addr.String())\n\tif !exists {\n\t\treturn ErrNoSub\n\t}\n\n\tif time.Now().After(expTime.(time.Time)) {\n\t\treturn ErrSubExpired\n\t}\n\n\treturn nil\n}\n\n// processSubscription processes the payment for a given receiver and renews or adds their subscription.\nfunc (rs *RecurringSubscription) processSubscription(receiver std.Address) error {\n\tamount := std.GetOrigSend()\n\n\tif amount.AmountOf(\"ugnot\") != rs.amount {\n\t\treturn ErrAmt\n\t}\n\n\texpTime, exists := rs.subs.Get(receiver.String())\n\n\t// If the user is already a subscriber but his subscription has expired, authorize renewal\n\tif exists {\n\t\texpiration := expTime.(time.Time)\n\t\tif time.Now().Before(expiration) {\n\t\t\treturn ErrAlreadySub\n\t\t}\n\t}\n\n\t// Renew or add subscription\n\tnewExpiration := time.Now().Add(rs.duration)\n\trs.subs.Set(receiver.String(), newExpiration)\n\n\treturn nil\n}\n\n// Subscribe handles the payment for the caller's subscription.\nfunc (rs *RecurringSubscription) Subscribe() error {\n\tcaller := std.PrevRealm().Addr()\n\n\treturn rs.processSubscription(caller)\n}\n\n// GiftSubscription allows the user to pay for a subscription for another user (receiver).\nfunc (rs *RecurringSubscription) GiftSubscription(receiver std.Address) error {\n\treturn rs.processSubscription(receiver)\n}\n\n// GetExpiration returns the expiration date of the recurring subscription for a given caller.\nfunc (rs *RecurringSubscription) GetExpiration(addr std.Address) (time.Time, error) {\n\texpTime, exists := rs.subs.Get(addr.String())\n\tif !exists {\n\t\treturn time.Time{}, ErrNoSub\n\t}\n\n\treturn expTime.(time.Time), nil\n}\n\n// UpdateAmount allows the owner of the subscription contract to change the required subscription amount.\nfunc (rs *RecurringSubscription) UpdateAmount(newAmount int64) error {\n\tif err := rs.CallerIsOwner(); err != nil {\n\t\treturn ErrNotAuthorized\n\t}\n\n\trs.amount = newAmount\n\treturn nil\n}\n\n// GetAmount returns the current amount required for each subscription period.\nfunc (rs *RecurringSubscription) GetAmount() int64 {\n\treturn rs.amount\n}\n"},{"name":"recurring_test.gno","body":"package recurring\n\nimport (\n\t\"std\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\talice = testutils.TestAddress(\"alice\")\n\tbob = testutils.TestAddress(\"bob\")\n\tcharlie = testutils.TestAddress(\"charlie\")\n)\n\nfunc TestRecurringSubscription(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := rs.Subscribe()\n\tuassert.NoError(t, err, \"Expected ProcessPayment to succeed for Alice\")\n\n\terr = rs.HasValidSubscription(std.PrevRealm().Addr())\n\tuassert.NoError(t, err, \"Expected Alice to have access\")\n\n\texpiration, err := rs.GetExpiration(std.PrevRealm().Addr())\n\tuassert.NoError(t, err, \"Expected to get expiration for Alice\")\n}\n\nfunc TestRecurringSubscriptionGift(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := rs.GiftSubscription(bob)\n\tuassert.NoError(t, err, \"Expected ProcessPaymentGift to succeed for Bob\")\n\n\terr = rs.HasValidSubscription(bob)\n\tuassert.NoError(t, err, \"Expected Bob to have access\")\n\n\terr = rs.HasValidSubscription(charlie)\n\tuassert.Error(t, err, \"Expected Charlie to fail access check\")\n}\n\nfunc TestRecurringSubscriptionExpiration(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour, 1000)\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := rs.Subscribe()\n\tuassert.NoError(t, err, \"Expected ProcessPayment to succeed for Alice\")\n\n\terr = rs.HasValidSubscription(std.PrevRealm().Addr())\n\tuassert.NoError(t, err, \"Expected Alice to have access\")\n\n\texpiration := time.Now().Add(-time.Hour * 2)\n\trs.subs.Set(std.PrevRealm().Addr().String(), expiration)\n\n\terr = rs.HasValidSubscription(std.PrevRealm().Addr())\n\tuassert.Error(t, err, \"Expected Alice's subscription to be expired\")\n}\n\nfunc TestUpdateAmountAuthorization(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\terr := rs.UpdateAmount(2000)\n\tuassert.NoError(t, err, \"Expected Alice to succeed in updating amount\")\n\n\tstd.TestSetOrigCaller(bob)\n\terr = rs.UpdateAmount(3000)\n\tuassert.Error(t, err, \"Expected Bob to fail when updating amount\")\n}\n\nfunc TestGetAmount(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\tamount := rs.GetAmount()\n\tuassert.Equal(t, amount, int64(1000), \"Expected the initial amount to be 1000 ugnot\")\n\n\terr := rs.UpdateAmount(2000)\n\tuassert.NoError(t, err, \"Expected Alice to succeed in updating amount\")\n\n\tamount = rs.GetAmount()\n\tuassert.Equal(t, amount, int64(2000), \"Expected the updated amount to be 2000 ugnot\")\n}\n\nfunc TestIncorrectPaymentAmount(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 500}}, nil)\n\terr := rs.Subscribe()\n\tuassert.Error(t, err, \"Expected payment with incorrect amount to fail\")\n}\n\nfunc TestMultiplePaymentsForSameUser(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := rs.Subscribe()\n\tuassert.NoError(t, err, \"Expected first ProcessPayment to succeed for Alice\")\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr = rs.Subscribe()\n\tuassert.Error(t, err, \"Expected second ProcessPayment to fail for Alice due to existing subscription\")\n}\n\nfunc TestRecurringSubscriptionWithMultiplePayments(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour, 1000)\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := rs.Subscribe()\n\tuassert.NoError(t, err, \"Expected first ProcessPayment to succeed for Alice\")\n\n\terr = rs.HasValidSubscription(std.PrevRealm().Addr())\n\tuassert.NoError(t, err, \"Expected Alice to have access after first payment\")\n\n\texpiration := time.Now().Add(-time.Hour * 2)\n\trs.subs.Set(std.PrevRealm().Addr().String(), expiration)\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr = rs.Subscribe()\n\tuassert.NoError(t, err, \"Expected second ProcessPayment to succeed for Alice\")\n\n\terr = rs.HasValidSubscription(std.PrevRealm().Addr())\n\tuassert.NoError(t, err, \"Expected Alice to have access after second payment\")\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":"lifetime","path":"gno.land/p/demo/subscription/lifetime","files":[{"name":"errors.gno","body":"package lifetime\n\nimport \"errors\"\n\nvar (\n\tErrNoSub = errors.New(\"lifetime subscription: no active subscription found\")\n\tErrAmt = errors.New(\"lifetime subscription: payment amount does not match the required subscription amount\")\n\tErrAlreadySub = errors.New(\"lifetime subscription: this address already has an active lifetime subscription\")\n\tErrNotAuthorized = errors.New(\"lifetime subscription: action not authorized\")\n)\n"},{"name":"lifetime.gno","body":"package lifetime\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n)\n\n// LifetimeSubscription represents a subscription that requires only a one-time payment.\n// It grants permanent access to a service or product.\ntype LifetimeSubscription struct {\n\townable.Ownable\n\tamount int64\n\tsubs *avl.Tree // std.Address -\u003e bool\n}\n\n// NewLifetimeSubscription creates and returns a new lifetime subscription.\nfunc NewLifetimeSubscription(amount int64) *LifetimeSubscription {\n\treturn \u0026LifetimeSubscription{\n\t\tOwnable: *ownable.New(),\n\t\tamount: amount,\n\t\tsubs: avl.NewTree(),\n\t}\n}\n\n// processSubscription handles the subscription process for a given receiver.\nfunc (ls *LifetimeSubscription) processSubscription(receiver std.Address) error {\n\tamount := std.OrigSend()\n\n\tif amount.AmountOf(\"ugnot\") != ls.amount {\n\t\treturn ErrAmt\n\t}\n\n\t_, exists := ls.subs.Get(receiver.String())\n\n\tif exists {\n\t\treturn ErrAlreadySub\n\t}\n\n\tls.subs.Set(receiver.String(), true)\n\n\treturn nil\n}\n\n// Subscribe processes the payment for a lifetime subscription.\nfunc (ls *LifetimeSubscription) Subscribe() error {\n\tcaller := std.PrevRealm().Addr()\n\treturn ls.processSubscription(caller)\n}\n\n// GiftSubscription allows the caller to pay for a lifetime subscription for another user.\nfunc (ls *LifetimeSubscription) GiftSubscription(receiver std.Address) error {\n\treturn ls.processSubscription(receiver)\n}\n\n// HasValidSubscription checks if the given address has an active lifetime subscription.\nfunc (ls *LifetimeSubscription) HasValidSubscription(addr std.Address) error {\n\t_, exists := ls.subs.Get(addr.String())\n\n\tif !exists {\n\t\treturn ErrNoSub\n\t}\n\n\treturn nil\n}\n\n// UpdateAmount allows the owner of the LifetimeSubscription contract to update the subscription price.\nfunc (ls *LifetimeSubscription) UpdateAmount(newAmount int64) error {\n\tif err := ls.CallerIsOwner(); err != nil {\n\t\treturn ErrNotAuthorized\n\t}\n\n\tls.amount = newAmount\n\treturn nil\n}\n\n// GetAmount returns the current subscription price.\nfunc (ls *LifetimeSubscription) GetAmount() int64 {\n\treturn ls.amount\n}\n"},{"name":"lifetime_test.gno","body":"package lifetime\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\talice = testutils.TestAddress(\"alice\")\n\tbob = testutils.TestAddress(\"bob\")\n\tcharlie = testutils.TestAddress(\"charlie\")\n)\n\nfunc TestLifetimeSubscription(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := ls.Subscribe()\n\tuassert.NoError(t, err, \"Expected ProcessPayment to succeed\")\n\n\terr = ls.HasValidSubscription(std.PrevRealm().Addr())\n\tuassert.NoError(t, err, \"Expected Alice to have access\")\n}\n\nfunc TestLifetimeSubscriptionGift(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := ls.GiftSubscription(bob)\n\tuassert.NoError(t, err, \"Expected ProcessPaymentGift to succeed for Bob\")\n\n\terr = ls.HasValidSubscription(bob)\n\tuassert.NoError(t, err, \"Expected Bob to have access\")\n\n\terr = ls.HasValidSubscription(charlie)\n\tuassert.Error(t, err, \"Expected Charlie to fail access check\")\n}\n\nfunc TestUpdateAmountAuthorization(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\terr := ls.UpdateAmount(2000)\n\tuassert.NoError(t, err, \"Expected Alice to succeed in updating amount\")\n\n\tstd.TestSetOrigCaller(bob)\n\n\terr = ls.UpdateAmount(3000)\n\tuassert.Error(t, err, \"Expected Bob to fail when updating amount\")\n}\n\nfunc TestIncorrectPaymentAmount(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 500}}, nil)\n\terr := ls.Subscribe()\n\tuassert.Error(t, err, \"Expected payment to fail with incorrect amount\")\n}\n\nfunc TestMultipleSubscriptionAttempts(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := ls.Subscribe()\n\tuassert.NoError(t, err, \"Expected first subscription to succeed\")\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr = ls.Subscribe()\n\tuassert.Error(t, err, \"Expected second subscription to fail as Alice is already subscribed\")\n}\n\nfunc TestGiftSubscriptionWithIncorrectAmount(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 500}}, nil)\n\terr := ls.GiftSubscription(bob)\n\tuassert.Error(t, err, \"Expected gift subscription to fail with incorrect amount\")\n\n\terr = ls.HasValidSubscription(bob)\n\tuassert.Error(t, err, \"Expected Bob to not have access after incorrect gift subscription\")\n}\n\nfunc TestUpdateAmountEffectiveness(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\terr := ls.UpdateAmount(2000)\n\tuassert.NoError(t, err, \"Expected Alice to succeed in updating amount\")\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr = ls.Subscribe()\n\tuassert.Error(t, err, \"Expected subscription to fail with old amount after update\")\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 2000}}, nil)\n\terr = ls.Subscribe()\n\tuassert.NoError(t, err, \"Expected subscription to succeed with new amount\")\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":"recurring","path":"gno.land/p/demo/subscription/recurring","files":[{"name":"errors.gno","body":"package recurring\n\nimport \"errors\"\n\nvar (\n\tErrNoSub = errors.New(\"recurring subscription: no active subscription found\")\n\tErrSubExpired = errors.New(\"recurring subscription: your subscription has expired\")\n\tErrAmt = errors.New(\"recurring subscription: payment amount does not match the required subscription amount\")\n\tErrAlreadySub = errors.New(\"recurring subscription: this address already has an active subscription\")\n\tErrNotAuthorized = errors.New(\"recurring subscription: action not authorized\")\n)\n"},{"name":"recurring.gno","body":"package recurring\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n)\n\n// RecurringSubscription represents a subscription that requires periodic payments.\n// It includes the duration of the subscription and the amount required per period.\ntype RecurringSubscription struct {\n\townable.Ownable\n\tduration time.Duration\n\tamount int64\n\tsubs *avl.Tree // std.Address -\u003e time.Time\n}\n\n// NewRecurringSubscription creates and returns a new recurring subscription.\nfunc NewRecurringSubscription(duration time.Duration, amount int64) *RecurringSubscription {\n\treturn \u0026RecurringSubscription{\n\t\tOwnable: *ownable.New(),\n\t\tduration: duration,\n\t\tamount: amount,\n\t\tsubs: avl.NewTree(),\n\t}\n}\n\n// HasValidSubscription verifies if the caller has an active recurring subscription.\nfunc (rs *RecurringSubscription) HasValidSubscription(addr std.Address) error {\n\texpTime, exists := rs.subs.Get(addr.String())\n\tif !exists {\n\t\treturn ErrNoSub\n\t}\n\n\tif time.Now().After(expTime.(time.Time)) {\n\t\treturn ErrSubExpired\n\t}\n\n\treturn nil\n}\n\n// processSubscription processes the payment for a given receiver and renews or adds their subscription.\nfunc (rs *RecurringSubscription) processSubscription(receiver std.Address) error {\n\tamount := std.OrigSend()\n\n\tif amount.AmountOf(\"ugnot\") != rs.amount {\n\t\treturn ErrAmt\n\t}\n\n\texpTime, exists := rs.subs.Get(receiver.String())\n\n\t// If the user is already a subscriber but his subscription has expired, authorize renewal\n\tif exists {\n\t\texpiration := expTime.(time.Time)\n\t\tif time.Now().Before(expiration) {\n\t\t\treturn ErrAlreadySub\n\t\t}\n\t}\n\n\t// Renew or add subscription\n\tnewExpiration := time.Now().Add(rs.duration)\n\trs.subs.Set(receiver.String(), newExpiration)\n\n\treturn nil\n}\n\n// Subscribe handles the payment for the caller's subscription.\nfunc (rs *RecurringSubscription) Subscribe() error {\n\tcaller := std.PrevRealm().Addr()\n\n\treturn rs.processSubscription(caller)\n}\n\n// GiftSubscription allows the user to pay for a subscription for another user (receiver).\nfunc (rs *RecurringSubscription) GiftSubscription(receiver std.Address) error {\n\treturn rs.processSubscription(receiver)\n}\n\n// GetExpiration returns the expiration date of the recurring subscription for a given caller.\nfunc (rs *RecurringSubscription) GetExpiration(addr std.Address) (time.Time, error) {\n\texpTime, exists := rs.subs.Get(addr.String())\n\tif !exists {\n\t\treturn time.Time{}, ErrNoSub\n\t}\n\n\treturn expTime.(time.Time), nil\n}\n\n// UpdateAmount allows the owner of the subscription contract to change the required subscription amount.\nfunc (rs *RecurringSubscription) UpdateAmount(newAmount int64) error {\n\tif err := rs.CallerIsOwner(); err != nil {\n\t\treturn ErrNotAuthorized\n\t}\n\n\trs.amount = newAmount\n\treturn nil\n}\n\n// GetAmount returns the current amount required for each subscription period.\nfunc (rs *RecurringSubscription) GetAmount() int64 {\n\treturn rs.amount\n}\n"},{"name":"recurring_test.gno","body":"package recurring\n\nimport (\n\t\"std\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\talice = testutils.TestAddress(\"alice\")\n\tbob = testutils.TestAddress(\"bob\")\n\tcharlie = testutils.TestAddress(\"charlie\")\n)\n\nfunc TestRecurringSubscription(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := rs.Subscribe()\n\tuassert.NoError(t, err, \"Expected ProcessPayment to succeed for Alice\")\n\n\terr = rs.HasValidSubscription(std.PrevRealm().Addr())\n\tuassert.NoError(t, err, \"Expected Alice to have access\")\n\n\texpiration, err := rs.GetExpiration(std.PrevRealm().Addr())\n\tuassert.NoError(t, err, \"Expected to get expiration for Alice\")\n}\n\nfunc TestRecurringSubscriptionGift(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := rs.GiftSubscription(bob)\n\tuassert.NoError(t, err, \"Expected ProcessPaymentGift to succeed for Bob\")\n\n\terr = rs.HasValidSubscription(bob)\n\tuassert.NoError(t, err, \"Expected Bob to have access\")\n\n\terr = rs.HasValidSubscription(charlie)\n\tuassert.Error(t, err, \"Expected Charlie to fail access check\")\n}\n\nfunc TestRecurringSubscriptionExpiration(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour, 1000)\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := rs.Subscribe()\n\tuassert.NoError(t, err, \"Expected ProcessPayment to succeed for Alice\")\n\n\terr = rs.HasValidSubscription(std.PrevRealm().Addr())\n\tuassert.NoError(t, err, \"Expected Alice to have access\")\n\n\texpiration := time.Now().Add(-time.Hour * 2)\n\trs.subs.Set(std.PrevRealm().Addr().String(), expiration)\n\n\terr = rs.HasValidSubscription(std.PrevRealm().Addr())\n\tuassert.Error(t, err, \"Expected Alice's subscription to be expired\")\n}\n\nfunc TestUpdateAmountAuthorization(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\terr := rs.UpdateAmount(2000)\n\tuassert.NoError(t, err, \"Expected Alice to succeed in updating amount\")\n\n\tstd.TestSetOrigCaller(bob)\n\terr = rs.UpdateAmount(3000)\n\tuassert.Error(t, err, \"Expected Bob to fail when updating amount\")\n}\n\nfunc TestGetAmount(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\tamount := rs.GetAmount()\n\tuassert.Equal(t, amount, int64(1000), \"Expected the initial amount to be 1000 ugnot\")\n\n\terr := rs.UpdateAmount(2000)\n\tuassert.NoError(t, err, \"Expected Alice to succeed in updating amount\")\n\n\tamount = rs.GetAmount()\n\tuassert.Equal(t, amount, int64(2000), \"Expected the updated amount to be 2000 ugnot\")\n}\n\nfunc TestIncorrectPaymentAmount(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 500}}, nil)\n\terr := rs.Subscribe()\n\tuassert.Error(t, err, \"Expected payment with incorrect amount to fail\")\n}\n\nfunc TestMultiplePaymentsForSameUser(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := rs.Subscribe()\n\tuassert.NoError(t, err, \"Expected first ProcessPayment to succeed for Alice\")\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr = rs.Subscribe()\n\tuassert.Error(t, err, \"Expected second ProcessPayment to fail for Alice due to existing subscription\")\n}\n\nfunc TestRecurringSubscriptionWithMultiplePayments(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour, 1000)\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := rs.Subscribe()\n\tuassert.NoError(t, err, \"Expected first ProcessPayment to succeed for Alice\")\n\n\terr = rs.HasValidSubscription(std.PrevRealm().Addr())\n\tuassert.NoError(t, err, \"Expected Alice to have access after first payment\")\n\n\texpiration := time.Now().Add(-time.Hour * 2)\n\trs.subs.Set(std.PrevRealm().Addr().String(), expiration)\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr = rs.Subscribe()\n\tuassert.NoError(t, err, \"Expected second ProcessPayment to succeed for Alice\")\n\n\terr = rs.HasValidSubscription(std.PrevRealm().Addr())\n\tuassert.NoError(t, err, \"Expected Alice to have access after second payment\")\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":"svg","path":"gno.land/p/demo/svg","files":[{"name":"doc.gno","body":"/*\nPackage svg is a minimalist SVG generation library for Gno.\n\nThe svg package provides a simple and lightweight solution for programmatically generating SVG (Scalable Vector Graphics) markup in Gno. It allows you to create basic shapes like rectangles and circles, and output the generated SVG to a\n\nExample:\n\n\timport \"gno.land/p/demo/svg\"\"\n\n\tfunc Foo() string {\n\t canvas := svg.Canvas{Width: 200, Height: 200}\n\t canvas.DrawRectangle(50, 50, 100, 100, \"red\")\n\t canvas.DrawCircle(100, 100, 50, \"blue\")\n\t return canvas.String()\n\t}\n*/\npackage svg // import \"gno.land/p/demo/svg\"\n"},{"name":"svg.gno","body":"package svg\n\nimport \"gno.land/p/demo/ufmt\"\n\ntype Canvas struct {\n\tWidth int\n\tHeight int\n\tElems []Elem\n}\n\ntype Elem interface{ String() string }\n\nfunc (c Canvas) String() string {\n\toutput := \"\"\n\toutput += ufmt.Sprintf(`\u003csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"%d\" height=\"%d\"\u003e`, c.Width, c.Height)\n\tfor _, elem := range c.Elems {\n\t\toutput += elem.String()\n\t}\n\toutput += \"\u003c/svg\u003e\"\n\treturn output\n}\n\nfunc (c *Canvas) Append(elem Elem) {\n\tc.Elems = append(c.Elems, elem)\n}\n\ntype Circle struct {\n\tCX int // center X\n\tCY int // center Y\n\tR int // radius\n\tFill string\n}\n\nfunc (c Circle) String() string {\n\treturn ufmt.Sprintf(`\u003ccircle cx=\"%d\" cy=\"%d\" r=\"%d\" fill=\"%s\" /\u003e`, c.CX, c.CY, c.R, c.Fill)\n}\n\nfunc (c *Canvas) DrawCircle(cx, cy, r int, fill string) {\n\tc.Append(Circle{\n\t\tCX: cx,\n\t\tCY: cy,\n\t\tR: r,\n\t\tFill: fill,\n\t})\n}\n\ntype Rectangle struct {\n\tX, Y, Width, Height int\n\tFill string\n}\n\nfunc (c Rectangle) String() string {\n\treturn ufmt.Sprintf(`\u003crect x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\" fill=\"%s\" /\u003e`, c.X, c.Y, c.Width, c.Height, c.Fill)\n}\n\nfunc (c *Canvas) DrawRectangle(x, y, width, height int, fill string) {\n\tc.Append(Rectangle{\n\t\tX: x,\n\t\tY: y,\n\t\tWidth: width,\n\t\tHeight: height,\n\t\tFill: fill,\n\t})\n}\n\ntype Text struct {\n\tX, Y int\n\tText, Fill string\n}\n\nfunc (c Text) String() string {\n\treturn ufmt.Sprintf(`\u003ctext x=\"%d\" y=\"%d\" fill=\"%s\"\u003e%s\u003c/text\u003e`, c.X, c.Y, c.Fill, c.Text)\n}\n\nfunc (c *Canvas) DrawText(x, y int, text, fill string) {\n\tc.Append(Text{\n\t\tX: x,\n\t\tY: y,\n\t\tText: text,\n\t\tFill: fill,\n\t})\n}\n"},{"name":"z0_filetest.gno","body":"// PKGPATH: gno.land/p/demo/svg_test\npackage svg_test\n\nimport \"gno.land/p/demo/svg\"\n\nfunc main() {\n\tcanvas := svg.Canvas{Width: 500, Height: 500}\n\tcanvas.DrawRectangle(50, 50, 100, 100, \"red\")\n\tcanvas.DrawCircle(100, 100, 50, \"blue\")\n\tcanvas.DrawText(100, 100, \"hello world!\", \"magenta\")\n\tprintln(canvas)\n}\n\n// Output:\n// \u003csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"500\" height=\"500\"\u003e\u003crect x=\"50\" y=\"50\" width=\"100\" height=\"100\" fill=\"red\" /\u003e\u003ccircle cx=\"100\" cy=\"100\" r=\"50\" fill=\"blue\" /\u003e\u003ctext x=\"100\" y=\"100\" fill=\"magenta\"\u003ehello world!\u003c/text\u003e\u003c/svg\u003e\n"},{"name":"z1_filetest.gno","body":"// PKGPATH: gno.land/p/demo/svg_test\npackage svg_test\n\nimport \"gno.land/p/demo/svg\"\n\nfunc main() {\n\tcanvas := svg.Canvas{\n\t\tWidth: 500, Height: 500,\n\t\tElems: []svg.Elem{\n\t\t\tsvg.Rectangle{50, 50, 100, 100, \"red\"},\n\t\t\tsvg.Circle{50, 50, 100, \"red\"},\n\t\t\tsvg.Text{100, 100, \"hello world!\", \"magenta\"},\n\t\t},\n\t}\n\tprintln(canvas)\n}\n\n// Output:\n// \u003csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"500\" height=\"500\"\u003e\u003crect x=\"50\" y=\"50\" width=\"100\" height=\"100\" fill=\"red\" /\u003e\u003ccircle cx=\"50\" cy=\"50\" r=\"100\" fill=\"red\" /\u003e\u003ctext x=\"100\" y=\"100\" fill=\"magenta\"\u003ehello world!\u003c/text\u003e\u003c/svg\u003e\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":"tamagotchi","path":"gno.land/p/demo/tamagotchi","files":[{"name":"tamagotchi.gno","body":"package tamagotchi\n\nimport (\n\t\"time\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Tamagotchi structure\ntype Tamagotchi struct {\n\tname string\n\thunger int\n\thappiness int\n\thealth int\n\tage int\n\tmaxAge int\n\tsleepy int\n\tcreated time.Time\n\tlastUpdated time.Time\n}\n\nfunc New(name string) *Tamagotchi {\n\tnow := time.Now()\n\treturn \u0026Tamagotchi{\n\t\tname: name,\n\t\thunger: 50,\n\t\thappiness: 50,\n\t\thealth: 50,\n\t\tmaxAge: 100,\n\t\tlastUpdated: now,\n\t\tcreated: now,\n\t}\n}\n\nfunc (t *Tamagotchi) Name() string {\n\tt.update()\n\treturn t.name\n}\n\nfunc (t *Tamagotchi) Hunger() int {\n\tt.update()\n\treturn t.hunger\n}\n\nfunc (t *Tamagotchi) Happiness() int {\n\tt.update()\n\treturn t.happiness\n}\n\nfunc (t *Tamagotchi) Health() int {\n\tt.update()\n\treturn t.health\n}\n\nfunc (t *Tamagotchi) Age() int {\n\tt.update()\n\treturn t.age\n}\n\nfunc (t *Tamagotchi) Sleepy() int {\n\tt.update()\n\treturn t.sleepy\n}\n\n// Feed method for Tamagotchi\nfunc (t *Tamagotchi) Feed() {\n\tt.update()\n\tif t.dead() {\n\t\treturn\n\t}\n\tt.hunger = bound(t.hunger-10, 0, 100)\n}\n\n// Play method for Tamagotchi\nfunc (t *Tamagotchi) Play() {\n\tt.update()\n\tif t.dead() {\n\t\treturn\n\t}\n\tt.happiness = bound(t.happiness+10, 0, 100)\n}\n\n// Heal method for Tamagotchi\nfunc (t *Tamagotchi) Heal() {\n\tt.update()\n\n\tif t.dead() {\n\t\treturn\n\t}\n\tt.health = bound(t.health+10, 0, 100)\n}\n\nfunc (t Tamagotchi) dead() bool { return t.health == 0 }\n\n// Update applies changes based on the duration since the last update\nfunc (t *Tamagotchi) update() {\n\tif t.dead() {\n\t\treturn\n\t}\n\n\tnow := time.Now()\n\tif t.lastUpdated == now {\n\t\treturn\n\t}\n\n\tduration := now.Sub(t.lastUpdated)\n\telapsedMins := int(duration.Minutes())\n\n\tt.hunger = bound(t.hunger+elapsedMins, 0, 100)\n\tt.happiness = bound(t.happiness-elapsedMins, 0, 100)\n\tt.health = bound(t.health-elapsedMins, 0, 100)\n\tt.sleepy = bound(t.sleepy+elapsedMins, 0, 100)\n\n\t// age is hours since created\n\tt.age = int(now.Sub(t.created).Hours())\n\tif t.age \u003e t.maxAge {\n\t\tt.age = t.maxAge\n\t\tt.health = 0\n\t}\n\tif t.health == 0 {\n\t\tt.sleepy = 0\n\t\tt.happiness = 0\n\t\tt.hunger = 0\n\t}\n\n\tt.lastUpdated = now\n}\n\n// Face returns an ASCII art representation of the Tamagotchi's current state\nfunc (t *Tamagotchi) Face() string {\n\tt.update()\n\treturn t.face()\n}\n\nfunc (t *Tamagotchi) face() string {\n\tswitch {\n\tcase t.health == 0:\n\t\treturn \"😵\" // dead face\n\tcase t.health \u003c 30:\n\t\treturn \"😷\" // sick face\n\tcase t.happiness \u003c 30:\n\t\treturn \"😢\" // sad face\n\tcase t.hunger \u003e 70:\n\t\treturn \"😫\" // hungry face\n\tcase t.sleepy \u003e 70:\n\t\treturn \"😴\" // sleepy face\n\tdefault:\n\t\treturn \"😃\" // happy face\n\t}\n}\n\n// Markdown method for Tamagotchi\nfunc (t *Tamagotchi) Markdown() string {\n\tt.update()\n\treturn ufmt.Sprintf(`# %s %s\n\n* age: %d\n* hunger: %d\n* happiness: %d\n* health: %d\n* sleepy: %d`,\n\t\tt.name, t.Face(),\n\t\tt.age, t.hunger, t.happiness, t.health, t.sleepy,\n\t)\n}\n\nfunc bound(n, min, max int) int {\n\tif n \u003c min {\n\t\treturn min\n\t}\n\tif n \u003e max {\n\t\treturn max\n\t}\n\treturn n\n}\n"},{"name":"z0_filetest.gno","body":"package main\n\nimport (\n\t\"time\"\n\n\t\"internal/os_test\"\n\n\t\"gno.land/p/demo/tamagotchi\"\n)\n\nfunc main() {\n\tt := tamagotchi.New(\"Gnome\")\n\n\tprintln(\"\\n-- INITIAL\\n\")\n\tprintln(t.Markdown())\n\n\tprintln(\"\\n-- WAIT 20 minutes\\n\")\n\tos_test.Sleep(20 * time.Minute)\n\tprintln(t.Markdown())\n\n\tprintln(\"\\n-- FEEDx3, PLAYx2, HEALx4\\n\")\n\tt.Feed()\n\tt.Feed()\n\tt.Feed()\n\tt.Play()\n\tt.Play()\n\tt.Heal()\n\tt.Heal()\n\tt.Heal()\n\tt.Heal()\n\tprintln(t.Markdown())\n\n\tprintln(\"\\n-- WAIT 20 minutes\\n\")\n\tos_test.Sleep(20 * time.Minute)\n\tprintln(t.Markdown())\n\n\tprintln(\"\\n-- WAIT 20 hours\\n\")\n\tos_test.Sleep(20 * time.Hour)\n\tprintln(t.Markdown())\n\n\tprintln(\"\\n-- WAIT 20 hours\\n\")\n\tos_test.Sleep(20 * time.Hour)\n\tprintln(t.Markdown())\n}\n\n// Output:\n// -- INITIAL\n//\n// # Gnome 😃\n//\n// * age: 0\n// * hunger: 50\n// * happiness: 50\n// * health: 50\n// * sleepy: 0\n//\n// -- WAIT 20 minutes\n//\n// # Gnome 😃\n//\n// * age: 0\n// * hunger: 70\n// * happiness: 30\n// * health: 30\n// * sleepy: 20\n//\n// -- FEEDx3, PLAYx2, HEALx4\n//\n// # Gnome 😃\n//\n// * age: 0\n// * hunger: 40\n// * happiness: 50\n// * health: 70\n// * sleepy: 20\n//\n// -- WAIT 20 minutes\n//\n// # Gnome 😃\n//\n// * age: 0\n// * hunger: 60\n// * happiness: 30\n// * health: 50\n// * sleepy: 40\n//\n// -- WAIT 20 hours\n//\n// # Gnome 😵\n//\n// * age: 20\n// * hunger: 0\n// * happiness: 0\n// * health: 0\n// * sleepy: 0\n//\n// -- WAIT 20 hours\n//\n// # Gnome 😵\n//\n// * age: 20\n// * hunger: 0\n// * happiness: 0\n// * health: 0\n// * sleepy: 0\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":"subtests","path":"gno.land/p/demo/tests/subtests","files":[{"name":"subtests.gno","body":"package subtests\n\nimport (\n\t\"std\"\n)\n\nfunc GetCurrentRealm() std.Realm {\n\treturn std.CurrentRealm()\n}\n\nfunc GetPrevRealm() std.Realm {\n\treturn std.PrevRealm()\n}\n\nfunc Exec(fn func()) {\n\tfn()\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":"subtests","path":"gno.land/r/demo/tests/subtests","files":[{"name":"subtests.gno","body":"package subtests\n\nimport (\n\t\"std\"\n)\n\nfunc GetCurrentRealm() std.Realm {\n\treturn std.CurrentRealm()\n}\n\nfunc GetPrevRealm() std.Realm {\n\treturn std.PrevRealm()\n}\n\nfunc Exec(fn func()) {\n\tfn()\n}\n\nfunc CallAssertOriginCall() {\n\tstd.AssertOriginCall()\n}\n\nfunc CallIsOriginCall() bool {\n\treturn std.IsOriginCall()\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\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":"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.OrigCaller()\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\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":"tests","path":"gno.land/p/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":"tests.gno","body":"package tests\n\nimport (\n\t\"std\"\n\n\tpsubtests \"gno.land/p/demo/tests/subtests\"\n\t\"gno.land/r/demo/tests\"\n\trtests \"gno.land/r/demo/tests\"\n)\n\nconst World = \"world\"\n\n// IncCounter demonstrates that it's possible to call a realm function from\n// a package. So a package can potentially write into the store, by calling\n// an other realm.\nfunc IncCounter() {\n\ttests.IncCounter()\n}\n\nfunc CurrentRealmPath() string {\n\treturn std.CurrentRealm().PkgPath()\n}\n\n//----------------------------------------\n// cross realm test vars\n\ntype TestRealmObject2 struct {\n\tField string\n}\n\nfunc (o2 *TestRealmObject2) Modify() {\n\to2.Field = \"modified\"\n}\n\nvar (\n\tsomevalue1 TestRealmObject2\n\tSomeValue2 TestRealmObject2\n\tSomeValue3 *TestRealmObject2\n)\n\nfunc init() {\n\tsomevalue1 = TestRealmObject2{Field: \"init\"}\n\tSomeValue2 = TestRealmObject2{Field: \"init\"}\n\tSomeValue3 = \u0026TestRealmObject2{Field: \"init\"}\n}\n\nfunc ModifyTestRealmObject2a() {\n\tsomevalue1.Field = \"modified\"\n}\n\nfunc ModifyTestRealmObject2b() {\n\tSomeValue2.Field = \"modified\"\n}\n\nfunc ModifyTestRealmObject2c() {\n\tSomeValue3.Field = \"modified\"\n}\n\nfunc GetPrevRealm() std.Realm {\n\treturn std.PrevRealm()\n}\n\nfunc GetPSubtestsPrevRealm() std.Realm {\n\treturn psubtests.GetPrevRealm()\n}\n\nfunc GetRTestsGetPrevRealm() std.Realm {\n\treturn rtests.GetPrevRealm()\n}\n\n// Warning: unsafe pattern.\nfunc Exec(fn func()) {\n\tfn()\n}\n"},{"name":"tests_test.gno","body":"package tests_test\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/tests\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar World = \"WORLD\"\n\nfunc TestGetHelloWorld(t *testing.T) {\n\t// tests.World is 'world'\n\ts := \"hello \" + tests.World + World\n\tconst want = \"hello worldWORLD\"\n\n\tuassert.Equal(t, want, s)\n}\n"},{"name":"z0_filetest.gno","body":"package main\n\nimport (\n\tptests \"gno.land/p/demo/tests\"\n\trtests \"gno.land/r/demo/tests\"\n)\n\nfunc main() {\n\tprintln(rtests.Counter())\n\tptests.IncCounter()\n\tprintln(rtests.Counter())\n}\n\n// Output:\n// 0\n// 1\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":"p_crossrealm","path":"gno.land/p/demo/tests/p_crossrealm","files":[{"name":"p_crossrealm.gno","body":"package p_crossrealm\n\ntype Stringer interface {\n\tString() string\n}\n\ntype Container struct {\n\tA int\n\tB Stringer\n}\n\nfunc (c *Container) Touch() *Container {\n\tc.A += 1\n\treturn c\n}\n\nfunc (c *Container) Print() {\n\tprintln(\"A:\", c.A)\n\tif c.B == nil {\n\t\tprintln(\"B: undefined\")\n\t} else {\n\t\tprintln(\"B:\", c.B.String())\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":"todolist","path":"gno.land/p/demo/todolist","files":[{"name":"todolist.gno","body":"package todolist\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\ntype TodoList struct {\n\tTitle string\n\tTasks *avl.Tree\n\tOwner std.Address\n}\n\ntype Task struct {\n\tTitle string\n\tDone bool\n}\n\nfunc NewTodoList(title string) *TodoList {\n\treturn \u0026TodoList{\n\t\tTitle: title,\n\t\tTasks: avl.NewTree(),\n\t\tOwner: std.GetOrigCaller(),\n\t}\n}\n\nfunc NewTask(title string) *Task {\n\treturn \u0026Task{\n\t\tTitle: title,\n\t\tDone: false,\n\t}\n}\n\nfunc (tl *TodoList) AddTask(id int, task *Task) {\n\ttl.Tasks.Set(strconv.Itoa(id), task)\n}\n\nfunc ToggleTaskStatus(task *Task) {\n\ttask.Done = !task.Done\n}\n\nfunc (tl *TodoList) RemoveTask(taskId string) {\n\ttl.Tasks.Remove(taskId)\n}\n\nfunc (tl *TodoList) GetTasks() []*Task {\n\ttasks := make([]*Task, 0, tl.Tasks.Size())\n\ttl.Tasks.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\ttasks = append(tasks, value.(*Task))\n\t\treturn false\n\t})\n\treturn tasks\n}\n\nfunc (tl *TodoList) GetTodolistOwner() std.Address {\n\treturn tl.Owner\n}\n\nfunc (tl *TodoList) GetTodolistTitle() string {\n\treturn tl.Title\n}\n"},{"name":"todolist_test.gno","body":"package todolist\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestNewTodoList(t *testing.T) {\n\ttitle := \"My Todo List\"\n\ttodoList := NewTodoList(title)\n\n\tuassert.Equal(t, title, todoList.GetTodolistTitle())\n\tuassert.Equal(t, 0, len(todoList.GetTasks()))\n\tuassert.Equal(t, std.GetOrigCaller().String(), todoList.GetTodolistOwner().String())\n}\n\nfunc TestNewTask(t *testing.T) {\n\ttitle := \"My Task\"\n\ttask := NewTask(title)\n\n\tuassert.Equal(t, title, task.Title)\n\tuassert.False(t, task.Done, \"Expected task to be not done, but it is done\")\n}\n\nfunc TestAddTask(t *testing.T) {\n\ttodoList := NewTodoList(\"My Todo List\")\n\ttask := NewTask(\"My Task\")\n\n\ttodoList.AddTask(1, task)\n\n\ttasks := todoList.GetTasks()\n\n\tuassert.Equal(t, 1, len(tasks))\n\tuassert.True(t, tasks[0] == task, \"Task does not match\")\n}\n\nfunc TestToggleTaskStatus(t *testing.T) {\n\ttask := NewTask(\"My Task\")\n\n\tToggleTaskStatus(task)\n\tuassert.True(t, task.Done, \"Expected task to be done, but it is not done\")\n\n\tToggleTaskStatus(task)\n\tuassert.False(t, task.Done, \"Expected task to be done, but it is not done\")\n}\n\nfunc TestRemoveTask(t *testing.T) {\n\ttodoList := NewTodoList(\"My Todo List\")\n\ttask := NewTask(\"My Task\")\n\ttodoList.AddTask(1, task)\n\n\ttodoList.RemoveTask(\"1\")\n\n\ttasks := todoList.GetTasks()\n\tuassert.Equal(t, 0, len(tasks))\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":"todolist","path":"gno.land/p/demo/todolist","files":[{"name":"todolist.gno","body":"package todolist\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\ntype TodoList struct {\n\tTitle string\n\tTasks *avl.Tree\n\tOwner std.Address\n}\n\ntype Task struct {\n\tTitle string\n\tDone bool\n}\n\nfunc NewTodoList(title string) *TodoList {\n\treturn \u0026TodoList{\n\t\tTitle: title,\n\t\tTasks: avl.NewTree(),\n\t\tOwner: std.OrigCaller(),\n\t}\n}\n\nfunc NewTask(title string) *Task {\n\treturn \u0026Task{\n\t\tTitle: title,\n\t\tDone: false,\n\t}\n}\n\nfunc (tl *TodoList) AddTask(id int, task *Task) {\n\ttl.Tasks.Set(strconv.Itoa(id), task)\n}\n\nfunc ToggleTaskStatus(task *Task) {\n\ttask.Done = !task.Done\n}\n\nfunc (tl *TodoList) RemoveTask(taskId string) {\n\ttl.Tasks.Remove(taskId)\n}\n\nfunc (tl *TodoList) GetTasks() []*Task {\n\ttasks := make([]*Task, 0, tl.Tasks.Size())\n\ttl.Tasks.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\ttasks = append(tasks, value.(*Task))\n\t\treturn false\n\t})\n\treturn tasks\n}\n\nfunc (tl *TodoList) GetTodolistOwner() std.Address {\n\treturn tl.Owner\n}\n\nfunc (tl *TodoList) GetTodolistTitle() string {\n\treturn tl.Title\n}\n"},{"name":"todolist_test.gno","body":"package todolist\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestNewTodoList(t *testing.T) {\n\ttitle := \"My Todo List\"\n\ttodoList := NewTodoList(title)\n\n\tuassert.Equal(t, title, todoList.GetTodolistTitle())\n\tuassert.Equal(t, 0, len(todoList.GetTasks()))\n\tuassert.Equal(t, std.OrigCaller().String(), todoList.GetTodolistOwner().String())\n}\n\nfunc TestNewTask(t *testing.T) {\n\ttitle := \"My Task\"\n\ttask := NewTask(title)\n\n\tuassert.Equal(t, title, task.Title)\n\tuassert.False(t, task.Done, \"Expected task to be not done, but it is done\")\n}\n\nfunc TestAddTask(t *testing.T) {\n\ttodoList := NewTodoList(\"My Todo List\")\n\ttask := NewTask(\"My Task\")\n\n\ttodoList.AddTask(1, task)\n\n\ttasks := todoList.GetTasks()\n\n\tuassert.Equal(t, 1, len(tasks))\n\tuassert.True(t, tasks[0] == task, \"Task does not match\")\n}\n\nfunc TestToggleTaskStatus(t *testing.T) {\n\ttask := NewTask(\"My Task\")\n\n\tToggleTaskStatus(task)\n\tuassert.True(t, task.Done, \"Expected task to be done, but it is not done\")\n\n\tToggleTaskStatus(task)\n\tuassert.False(t, task.Done, \"Expected task to be done, but it is not done\")\n}\n\nfunc TestRemoveTask(t *testing.T) {\n\ttodoList := NewTodoList(\"My Todo List\")\n\ttask := NewTask(\"My Task\")\n\ttodoList.AddTask(1, task)\n\n\ttodoList.RemoveTask(\"1\")\n\n\ttasks := todoList.GetTasks()\n\tuassert.Equal(t, 0, len(tasks))\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":"ui","path":"gno.land/p/demo/ui","files":[{"name":"ui.gno","body":"package ui\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n)\n\ntype DOM struct {\n\t// metadata\n\tPrefix string\n\tTitle string\n\tWithComments bool\n\tClasses []string\n\n\t// elements\n\tHeader Element\n\tBody Element\n\tFooter Element\n}\n\nfunc (dom DOM) String() string {\n\tclasses := strings.Join(dom.Classes, \" \")\n\n\toutput := \"\"\n\n\tif classes != \"\" {\n\t\toutput += \"\u003cmain class='\" + classes + \"'\u003e\" + \"\\n\\n\"\n\t}\n\n\tif dom.Title != \"\" {\n\t\toutput += H1(dom.Title).String(dom) + \"\\n\"\n\t}\n\n\tif header := dom.Header.String(dom); header != \"\" {\n\t\tif dom.WithComments {\n\t\t\toutput += \"\u003c!-- header --\u003e\"\n\t\t}\n\t\toutput += header + \"\\n\"\n\t\tif dom.WithComments {\n\t\t\toutput += \"\u003c!-- /header --\u003e\"\n\t\t}\n\t}\n\n\tif body := dom.Body.String(dom); body != \"\" {\n\t\tif dom.WithComments {\n\t\t\toutput += \"\u003c!-- body --\u003e\"\n\t\t}\n\t\toutput += body + \"\\n\"\n\t\tif dom.WithComments {\n\t\t\toutput += \"\u003c!-- /body --\u003e\"\n\t\t}\n\t}\n\n\tif footer := dom.Footer.String(dom); footer != \"\" {\n\t\tif dom.WithComments {\n\t\t\toutput += \"\u003c!-- footer --\u003e\"\n\t\t}\n\t\toutput += footer + \"\\n\"\n\t\tif dom.WithComments {\n\t\t\toutput += \"\u003c!-- /footer --\u003e\"\n\t\t}\n\t}\n\n\tif classes != \"\" {\n\t\toutput += \"\u003c/main\u003e\"\n\t}\n\n\t// TODO: cleanup double new-lines.\n\n\treturn output\n}\n\ntype Jumbotron []DomStringer\n\nfunc (j Jumbotron) String(dom DOM) string {\n\toutput := `\u003cdiv class=\"jumbotron\"\u003e` + \"\\n\\n\"\n\tfor _, elem := range j {\n\t\toutput += elem.String(dom) + \"\\n\"\n\t}\n\toutput += `\u003c/div\u003e\u003c!-- /jumbotron --\u003e` + \"\\n\"\n\treturn output\n}\n\n// XXX: rename Element to Div?\ntype Element []DomStringer\n\nfunc (e *Element) Append(elems ...DomStringer) {\n\t*e = append(*e, elems...)\n}\n\nfunc (e *Element) String(dom DOM) string {\n\toutput := \"\"\n\tfor _, elem := range *e {\n\t\toutput += elem.String(dom) + \"\\n\"\n\t}\n\treturn output\n}\n\ntype Breadcrumb []DomStringer\n\nfunc (b *Breadcrumb) Append(elems ...DomStringer) {\n\t*b = append(*b, elems...)\n}\n\nfunc (b Breadcrumb) String(dom DOM) string {\n\toutput := \"\"\n\tfor idx, entry := range b {\n\t\tif idx \u003e 0 {\n\t\t\toutput += \" / \"\n\t\t}\n\t\toutput += entry.String(dom)\n\t}\n\treturn output\n}\n\ntype Columns struct {\n\tMaxWidth int\n\tColumns []Element\n}\n\nfunc (c *Columns) Append(elems ...Element) {\n\tc.Columns = append(c.Columns, elems...)\n}\n\nfunc (c Columns) String(dom DOM) string {\n\toutput := `\u003cdiv class=\"columns-` + strconv.Itoa(c.MaxWidth) + `\"\u003e` + \"\\n\"\n\tfor _, entry := range c.Columns {\n\t\toutput += `\u003cdiv class=\"column\"\u003e` + \"\\n\\n\"\n\t\toutput += entry.String(dom)\n\t\toutput += \"\u003c/div\u003e\u003c!-- /column--\u003e\\n\"\n\t}\n\toutput += \"\u003c/div\u003e\u003c!-- /columns-\" + strconv.Itoa(c.MaxWidth) + \" --\u003e\\n\"\n\treturn output\n}\n\ntype Link struct {\n\tText string\n\tPath string\n\tURL string\n}\n\n// TODO: image\n\n// TODO: pager\n\nfunc (l Link) String(dom DOM) string {\n\turl := \"\"\n\tswitch {\n\tcase l.Path != \"\" \u0026\u0026 l.URL != \"\":\n\t\tpanic(\"a link should have a path or a URL, not both.\")\n\tcase l.Path != \"\":\n\t\tif l.Text == \"\" {\n\t\t\tl.Text = l.Path\n\t\t}\n\t\turl = dom.Prefix + l.Path\n\tcase l.URL != \"\":\n\t\tif l.Text == \"\" {\n\t\t\tl.Text = l.URL\n\t\t}\n\t\turl = l.URL\n\t}\n\n\treturn \"[\" + l.Text + \"](\" + url + \")\"\n}\n\ntype BulletList []DomStringer\n\nfunc (bl BulletList) String(dom DOM) string {\n\toutput := \"\"\n\n\tfor _, entry := range bl {\n\t\toutput += \"- \" + entry.String(dom) + \"\\n\"\n\t}\n\n\treturn output\n}\n\nfunc Text(s string) DomStringer {\n\treturn Raw{Content: s}\n}\n\ntype DomStringer interface {\n\tString(dom DOM) string\n}\n\ntype Raw struct {\n\tContent string\n}\n\nfunc (r Raw) String(_ DOM) string {\n\treturn r.Content\n}\n\ntype (\n\tH1 string\n\tH2 string\n\tH3 string\n\tH4 string\n\tH5 string\n\tH6 string\n\tBold string\n\tItalic string\n\tCode string\n\tParagraph string\n\tQuote string\n\tHR struct{}\n)\n\nfunc (text H1) String(_ DOM) string { return \"# \" + string(text) + \"\\n\" }\nfunc (text H2) String(_ DOM) string { return \"## \" + string(text) + \"\\n\" }\nfunc (text H3) String(_ DOM) string { return \"### \" + string(text) + \"\\n\" }\nfunc (text H4) String(_ DOM) string { return \"#### \" + string(text) + \"\\n\" }\nfunc (text H5) String(_ DOM) string { return \"##### \" + string(text) + \"\\n\" }\nfunc (text H6) String(_ DOM) string { return \"###### \" + string(text) + \"\\n\" }\nfunc (text Quote) String(_ DOM) string { return \"\u003e \" + string(text) + \"\\n\" }\nfunc (text Bold) String(_ DOM) string { return \"**\" + string(text) + \"**\" }\nfunc (text Italic) String(_ DOM) string { return \"_\" + string(text) + \"_\" }\nfunc (text Paragraph) String(_ DOM) string { return \"\\n\" + string(text) + \"\\n\" }\nfunc (_ HR) String(_ DOM) string { return \"\\n---\\n\" }\n\nfunc (text Code) String(_ DOM) string {\n\t// multiline\n\tif strings.Contains(string(text), \"\\n\") {\n\t\treturn \"\\n```\\n\" + string(text) + \"\\n```\\n\"\n\t}\n\n\t// single line\n\treturn \"`\" + string(text) + \"`\"\n}\n"},{"name":"ui_test.gno","body":"package ui\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":"watchdog","path":"gno.land/p/demo/watchdog","files":[{"name":"watchdog.gno","body":"package watchdog\n\nimport \"time\"\n\ntype Watchdog struct {\n\tDuration time.Duration\n\tlastUpdate time.Time\n\tlastDown time.Time\n}\n\nfunc (w *Watchdog) Alive() {\n\tnow := time.Now()\n\tif !w.IsAlive() {\n\t\tw.lastDown = now\n\t}\n\tw.lastUpdate = now\n}\n\nfunc (w Watchdog) Status() string {\n\tif w.IsAlive() {\n\t\treturn \"OK\"\n\t}\n\treturn \"KO\"\n}\n\nfunc (w Watchdog) IsAlive() bool {\n\treturn time.Since(w.lastUpdate) \u003c w.Duration\n}\n\nfunc (w Watchdog) UpSince() time.Time {\n\treturn w.lastDown\n}\n\nfunc (w Watchdog) DownSince() time.Time {\n\tif !w.IsAlive() {\n\t\treturn w.lastUpdate\n\t}\n\treturn time.Time{}\n}\n"},{"name":"watchdog_test.gno","body":"package watchdog\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestPackage(t *testing.T) {\n\tw := Watchdog{Duration: 5 * time.Minute}\n\tuassert.False(t, w.IsAlive())\n\tw.Alive()\n\tuassert.True(t, w.IsAlive())\n\t// XXX: add more tests when we'll be able to \"skip time\".\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":"executor","path":"gno.land/p/gov/executor","files":[{"name":"callback.gno","body":"package executor\n\nimport (\n\t\"errors\"\n\t\"std\"\n)\n\nvar errInvalidCaller = errors.New(\"invalid executor caller\")\n\n// NewCallbackExecutor creates a new callback executor with the provided callback function\nfunc NewCallbackExecutor(callback func() error, path string) *CallbackExecutor {\n\treturn \u0026CallbackExecutor{\n\t\tcallback: callback,\n\t\tdaoPkgPath: path,\n\t}\n}\n\n// CallbackExecutor is an implementation of the dao.Executor interface,\n// based on a specific callback.\n// The given callback should verify the validity of the govdao call\ntype CallbackExecutor struct {\n\tcallback func() error // the callback to be executed\n\tdaoPkgPath string // the active pkg path of the govdao\n}\n\n// Execute runs the executor's callback function.\nfunc (exec *CallbackExecutor) Execute() error {\n\t// Verify the caller is an adequate Realm\n\tcaller := std.CurrentRealm().PkgPath()\n\tif caller != exec.daoPkgPath {\n\t\treturn errInvalidCaller\n\t}\n\n\tif exec.callback != nil {\n\t\treturn exec.callback()\n\t}\n\n\treturn nil\n}\n"},{"name":"context.gno","body":"package executor\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/context\"\n)\n\ntype propContextKey string\n\nfunc (k propContextKey) String() string { return string(k) }\n\nconst (\n\tstatusContextKey = propContextKey(\"govdao-prop-status\")\n\tapprovedStatus = \"approved\"\n)\n\nvar errNotApproved = errors.New(\"not approved by govdao\")\n\n// CtxExecutor is an implementation of the dao.Executor interface,\n// based on the given context.\n// It utilizes the given context to assert the validity of the govdao call\ntype CtxExecutor struct {\n\tcallbackCtx func(ctx context.Context) error // the callback ctx fn, if any\n\tdaoPkgPath string // the active pkg path of the govdao\n}\n\n// NewCtxExecutor creates a new executor with the provided callback function.\nfunc NewCtxExecutor(callback func(ctx context.Context) error, path string) *CtxExecutor {\n\treturn \u0026CtxExecutor{\n\t\tcallbackCtx: callback,\n\t\tdaoPkgPath: path,\n\t}\n}\n\n// Execute runs the executor's callback function\nfunc (exec *CtxExecutor) Execute() error {\n\t// Verify the caller is an adequate Realm\n\tcaller := std.CurrentRealm().PkgPath()\n\tif caller != exec.daoPkgPath {\n\t\treturn errInvalidCaller\n\t}\n\n\t// Create the context\n\tctx := context.WithValue(\n\t\tcontext.Empty(),\n\t\tstatusContextKey,\n\t\tapprovedStatus,\n\t)\n\n\treturn exec.callbackCtx(ctx)\n}\n\n// IsApprovedByGovdaoContext asserts that the govdao approved the context\nfunc IsApprovedByGovdaoContext(ctx context.Context) bool {\n\tv := ctx.Value(statusContextKey)\n\tif v == nil {\n\t\treturn false\n\t}\n\n\tvs, ok := v.(string)\n\n\treturn ok \u0026\u0026 vs == approvedStatus\n}\n\n// AssertContextApprovedByGovDAO asserts the given context\n// was approved by GOVDAO\nfunc AssertContextApprovedByGovDAO(ctx context.Context) {\n\tif IsApprovedByGovdaoContext(ctx) {\n\t\treturn\n\t}\n\n\tpanic(errNotApproved)\n}\n"},{"name":"proposal_test.gno","body":"package executor\n\nimport (\n\t\"errors\"\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/context\"\n\t\"gno.land/p/demo/uassert\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestExecutor_Callback(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"govdao not caller\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tcalled = false\n\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)\n\n\t\t// Create the executor\n\t\te := NewCallbackExecutor(cb, \"gno.land/r/gov/dao\")\n\n\t\t// Execute as not the /r/gov/dao caller\n\t\tuassert.ErrorIs(t, e.Execute(), errInvalidCaller)\n\t\tuassert.False(t, called, \"expected proposal to not execute\")\n\t})\n\n\tt.Run(\"execution successful\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tcalled = false\n\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)\n\n\t\t// Create the executor\n\t\tdaoPkgPath := \"gno.land/r/gov/dao\"\n\t\te := NewCallbackExecutor(cb, daoPkgPath)\n\n\t\t// Execute as the /r/gov/dao caller\n\t\tr := std.NewCodeRealm(daoPkgPath)\n\t\tstd.TestSetRealm(r)\n\n\t\tuassert.NoError(t, e.Execute())\n\t\tuassert.True(t, called, \"expected proposal to execute\")\n\t})\n\n\tt.Run(\"execution unsuccessful\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tcalled = false\n\t\t\texpectedErr = errors.New(\"unexpected\")\n\n\t\t\tcb = func() error {\n\t\t\t\tcalled = true\n\n\t\t\t\treturn expectedErr\n\t\t\t}\n\t\t)\n\n\t\t// Create the executor\n\t\tdaoPkgPath := \"gno.land/r/gov/dao\"\n\t\te := NewCallbackExecutor(cb, daoPkgPath)\n\n\t\t// Execute as the /r/gov/dao caller\n\t\tr := std.NewCodeRealm(daoPkgPath)\n\t\tstd.TestSetRealm(r)\n\n\t\tuassert.ErrorIs(t, e.Execute(), expectedErr)\n\t\tuassert.True(t, called, \"expected proposal to execute\")\n\t})\n}\n\nfunc TestExecutor_Context(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"govdao not caller\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tcalled = false\n\n\t\t\tcb = func(ctx context.Context) error {\n\t\t\t\tif !IsApprovedByGovdaoContext(ctx) {\n\t\t\t\t\tt.Fatal(\"not govdao caller\")\n\t\t\t\t}\n\n\t\t\t\tcalled = true\n\n\t\t\t\treturn nil\n\t\t\t}\n\t\t)\n\n\t\t// Create the executor\n\t\te := NewCtxExecutor(cb, \"gno.land/r/gov/dao\")\n\n\t\t// Execute as not the /r/gov/dao caller\n\t\tuassert.ErrorIs(t, e.Execute(), errInvalidCaller)\n\t\tuassert.False(t, called, \"expected proposal to not execute\")\n\t})\n\n\tt.Run(\"execution successful\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tcalled = false\n\n\t\t\tcb = func(ctx context.Context) error {\n\t\t\t\tif !IsApprovedByGovdaoContext(ctx) {\n\t\t\t\t\tt.Fatal(\"not govdao caller\")\n\t\t\t\t}\n\n\t\t\t\tcalled = true\n\n\t\t\t\treturn nil\n\t\t\t}\n\t\t)\n\n\t\t// Create the executor\n\t\tdaoPkgPath := \"gno.land/r/gov/dao\"\n\t\te := NewCtxExecutor(cb, daoPkgPath)\n\n\t\t// Execute as the /r/gov/dao caller\n\t\tr := std.NewCodeRealm(daoPkgPath)\n\t\tstd.TestSetRealm(r)\n\n\t\turequire.NoError(t, e.Execute())\n\t\tuassert.True(t, called, \"expected proposal to execute\")\n\t})\n\n\tt.Run(\"execution unsuccessful\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tcalled = false\n\t\t\texpectedErr = errors.New(\"unexpected\")\n\n\t\t\tcb = func(ctx context.Context) error {\n\t\t\t\tif !IsApprovedByGovdaoContext(ctx) {\n\t\t\t\t\tt.Fatal(\"not govdao caller\")\n\t\t\t\t}\n\n\t\t\t\tcalled = true\n\n\t\t\t\treturn expectedErr\n\t\t\t}\n\t\t)\n\n\t\t// Create the executor\n\t\tdaoPkgPath := \"gno.land/r/gov/dao\"\n\t\te := NewCtxExecutor(cb, daoPkgPath)\n\n\t\t// Execute as the /r/gov/dao caller\n\t\tr := std.NewCodeRealm(daoPkgPath)\n\t\tstd.TestSetRealm(r)\n\n\t\tuassert.NotPanics(t, func() {\n\t\t\terr := e.Execute()\n\n\t\t\tuassert.ErrorIs(t, err, expectedErr)\n\t\t})\n\n\t\tuassert.True(t, called, \"expected proposal to execute\")\n\t})\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}} @@ -76,7 +76,7 @@ {"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"poa","path":"gno.land/p/nt/poa","files":[{"name":"option.gno","body":"package poa\n\nimport \"gno.land/p/sys/validators\"\n\ntype Option func(*PoA)\n\n// WithInitialSet sets the initial PoA validator set\nfunc WithInitialSet(validators []validators.Validator) Option {\n\treturn func(p *PoA) {\n\t\tfor _, validator := range validators {\n\t\t\tp.validators.Set(validator.Address.String(), validator)\n\t\t}\n\t}\n}\n"},{"name":"poa.gno","body":"package poa\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/sys/validators\"\n)\n\nvar ErrInvalidVotingPower = errors.New(\"invalid voting power\")\n\n// PoA specifies the Proof of Authority validator set, with simple add / remove constraints.\n//\n// To add:\n// - proposed validator must not be part of the set already\n// - proposed validator voting power must be \u003e 0\n//\n// To remove:\n// - proposed validator must be part of the set already\ntype PoA struct {\n\tvalidators *avl.Tree // std.Address -\u003e validators.Validator\n}\n\n// NewPoA creates a new empty Proof of Authority validator set\nfunc NewPoA(opts ...Option) *PoA {\n\t// Create the empty set\n\tp := \u0026PoA{\n\t\tvalidators: avl.NewTree(),\n\t}\n\n\t// Apply the options\n\tfor _, opt := range opts {\n\t\topt(p)\n\t}\n\n\treturn p\n}\n\nfunc (p *PoA) AddValidator(address std.Address, pubKey string, power uint64) (validators.Validator, error) {\n\t// Validate that the operation is a valid call.\n\t// Check if the validator is already in the set\n\tif p.IsValidator(address) {\n\t\treturn validators.Validator{}, validators.ErrValidatorExists\n\t}\n\n\t// Make sure the voting power \u003e 0\n\tif power == 0 {\n\t\treturn validators.Validator{}, ErrInvalidVotingPower\n\t}\n\n\tv := validators.Validator{\n\t\tAddress: address,\n\t\tPubKey: pubKey, // TODO: in the future, verify the public key\n\t\tVotingPower: power,\n\t}\n\n\t// Add the validator to the set\n\tp.validators.Set(address.String(), v)\n\n\treturn v, nil\n}\n\nfunc (p *PoA) RemoveValidator(address std.Address) (validators.Validator, error) {\n\t// Validate that the operation is a valid call\n\t// Fetch the validator\n\tvalidator, err := p.GetValidator(address)\n\tif err != nil {\n\t\treturn validators.Validator{}, err\n\t}\n\n\t// Remove the validator from the set\n\tp.validators.Remove(address.String())\n\n\treturn validator, nil\n}\n\nfunc (p *PoA) IsValidator(address std.Address) bool {\n\t_, exists := p.validators.Get(address.String())\n\n\treturn exists\n}\n\nfunc (p *PoA) GetValidator(address std.Address) (validators.Validator, error) {\n\tvalidatorRaw, exists := p.validators.Get(address.String())\n\tif !exists {\n\t\treturn validators.Validator{}, validators.ErrValidatorMissing\n\t}\n\n\tvalidator := validatorRaw.(validators.Validator)\n\n\treturn validator, nil\n}\n\nfunc (p *PoA) GetValidators() []validators.Validator {\n\tvals := make([]validators.Validator, 0, p.validators.Size())\n\n\tp.validators.Iterate(\"\", \"\", func(_ string, value interface{}) bool {\n\t\tvalidator := value.(validators.Validator)\n\t\tvals = append(vals, validator)\n\n\t\treturn false\n\t})\n\n\treturn vals\n}\n"},{"name":"poa_test.gno","body":"package poa\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/urequire\"\n\t\"gno.land/p/sys/validators\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// generateTestValidators generates a dummy validator set\nfunc generateTestValidators(count int) []validators.Validator {\n\tvals := make([]validators.Validator, 0, count)\n\n\tfor i := 0; i \u003c count; i++ {\n\t\tval := validators.Validator{\n\t\t\tAddress: testutils.TestAddress(ufmt.Sprintf(\"%d\", i)),\n\t\t\tPubKey: \"public-key\",\n\t\t\tVotingPower: 1,\n\t\t}\n\n\t\tvals = append(vals, val)\n\t}\n\n\treturn vals\n}\n\nfunc TestPoA_AddValidator_Invalid(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"validator already in set\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tproposalAddress = testutils.TestAddress(\"caller\")\n\t\t\tproposalKey = \"public-key\"\n\n\t\t\tinitialSet = generateTestValidators(1)\n\t\t)\n\n\t\tinitialSet[0].Address = proposalAddress\n\t\tinitialSet[0].PubKey = proposalKey\n\n\t\t// Create the protocol with an initial set\n\t\tp := NewPoA(WithInitialSet(initialSet))\n\n\t\t// Attempt to add the validator\n\t\t_, err := p.AddValidator(proposalAddress, proposalKey, 1)\n\t\tuassert.ErrorIs(t, err, validators.ErrValidatorExists)\n\t})\n\n\tt.Run(\"invalid voting power\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tproposalAddress = testutils.TestAddress(\"caller\")\n\t\t\tproposalKey = \"public-key\"\n\t\t)\n\n\t\t// Create the protocol with no initial set\n\t\tp := NewPoA()\n\n\t\t// Attempt to add the validator\n\t\t_, err := p.AddValidator(proposalAddress, proposalKey, 0)\n\t\tuassert.ErrorIs(t, err, ErrInvalidVotingPower)\n\t})\n}\n\nfunc TestPoA_AddValidator(t *testing.T) {\n\tt.Parallel()\n\n\tvar (\n\t\tproposalAddress = testutils.TestAddress(\"caller\")\n\t\tproposalKey = \"public-key\"\n\t)\n\n\t// Create the protocol with no initial set\n\tp := NewPoA()\n\n\t// Attempt to add the validator\n\t_, err := p.AddValidator(proposalAddress, proposalKey, 1)\n\tuassert.NoError(t, err)\n\n\t// Make sure the validator is added\n\tif !p.IsValidator(proposalAddress) || p.validators.Size() != 1 {\n\t\tt.Fatal(\"address is not validator\")\n\t}\n}\n\nfunc TestPoA_RemoveValidator_Invalid(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"proposed removal not in set\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tproposalAddress = testutils.TestAddress(\"caller\")\n\t\t\tinitialSet = generateTestValidators(1)\n\t\t)\n\n\t\tinitialSet[0].Address = proposalAddress\n\n\t\t// Create the protocol with an initial set\n\t\tp := NewPoA(WithInitialSet(initialSet))\n\n\t\t// Attempt to remove the validator\n\t\t_, err := p.RemoveValidator(testutils.TestAddress(\"totally random\"))\n\t\tuassert.ErrorIs(t, err, validators.ErrValidatorMissing)\n\t})\n}\n\nfunc TestPoA_RemoveValidator(t *testing.T) {\n\tt.Parallel()\n\n\tvar (\n\t\tproposalAddress = testutils.TestAddress(\"caller\")\n\t\tinitialSet = generateTestValidators(1)\n\t)\n\n\tinitialSet[0].Address = proposalAddress\n\n\t// Create the protocol with an initial set\n\tp := NewPoA(WithInitialSet(initialSet))\n\n\t// Attempt to remove the validator\n\t_, err := p.RemoveValidator(proposalAddress)\n\turequire.NoError(t, err)\n\n\t// Make sure the validator is removed\n\tif p.IsValidator(proposalAddress) || p.validators.Size() != 0 {\n\t\tt.Fatal(\"address is validator\")\n\t}\n}\n\nfunc TestPoA_GetValidator(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"validator not in set\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Create the protocol with no initial set\n\t\tp := NewPoA()\n\n\t\t// Attempt to get the voting power\n\t\t_, err := p.GetValidator(testutils.TestAddress(\"caller\"))\n\t\tuassert.ErrorIs(t, err, validators.ErrValidatorMissing)\n\t})\n\n\tt.Run(\"validator fetched\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\taddress = testutils.TestAddress(\"caller\")\n\t\t\tpubKey = \"public-key\"\n\t\t\tvotingPower = uint64(10)\n\n\t\t\tinitialSet = generateTestValidators(1)\n\t\t)\n\n\t\tinitialSet[0].Address = address\n\t\tinitialSet[0].PubKey = pubKey\n\t\tinitialSet[0].VotingPower = votingPower\n\n\t\t// Create the protocol with an initial set\n\t\tp := NewPoA(WithInitialSet(initialSet))\n\n\t\t// Get the validator\n\t\tval, err := p.GetValidator(address)\n\t\turequire.NoError(t, err)\n\n\t\t// Validate the address\n\t\tif val.Address != address {\n\t\t\tt.Fatal(\"invalid address\")\n\t\t}\n\n\t\t// Validate the voting power\n\t\tif val.VotingPower != votingPower {\n\t\t\tt.Fatal(\"invalid voting power\")\n\t\t}\n\n\t\t// Validate the public key\n\t\tif val.PubKey != pubKey {\n\t\t\tt.Fatal(\"invalid public key\")\n\t\t}\n\t})\n}\n\nfunc TestPoA_GetValidators(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"empty set\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Create the protocol with no initial set\n\t\tp := NewPoA()\n\n\t\t// Attempt to get the voting power\n\t\tvals := p.GetValidators()\n\n\t\tif len(vals) != 0 {\n\t\t\tt.Fatal(\"validator set is not empty\")\n\t\t}\n\t})\n\n\tt.Run(\"validator set fetched\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tinitialSet := generateTestValidators(10)\n\n\t\t// Create the protocol with an initial set\n\t\tp := NewPoA(WithInitialSet(initialSet))\n\n\t\t// Get the validator set\n\t\tvals := p.GetValidators()\n\n\t\tif len(vals) != len(initialSet) {\n\t\t\tt.Fatal(\"returned validator set mismatch\")\n\t\t}\n\n\t\tfor _, val := range vals {\n\t\t\tfor _, initialVal := range initialSet {\n\t\t\t\tif val.Address != initialVal.Address {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\t// Validate the voting power\n\t\t\t\tuassert.Equal(t, val.VotingPower, initialVal.VotingPower)\n\n\t\t\t\t// Validate the public key\n\t\t\t\tuassert.Equal(t, val.PubKey, initialVal.PubKey)\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":"gnoface","path":"gno.land/r/demo/art/gnoface","files":[{"name":"gnoface.gno","body":"package gnoface\n\nimport (\n\t\"math/rand\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/entropy\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nfunc Render(path string) string {\n\tseed := uint64(entropy.New().Value())\n\n\tpath = strings.TrimSpace(path)\n\tif path != \"\" {\n\t\ts, err := strconv.Atoi(path)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tseed = uint64(s)\n\t}\n\n\toutput := ufmt.Sprintf(\"Gnoface #%d\\n\", seed)\n\toutput += \"```\\n\" + Draw(seed) + \"```\\n\"\n\treturn output\n}\n\nfunc Draw(seed uint64) string {\n\tvar (\n\t\thairs = []string{\n\t\t\t\" s\",\n\t\t\t\" .......\",\n\t\t\t\" s s s\",\n\t\t\t\" /\\\\ /\\\\\",\n\t\t\t\" |||||||\",\n\t\t}\n\t\theadtop = []string{\n\t\t\t\" /-------\\\\\",\n\t\t\t\" /~~~~~~~\\\\\",\n\t\t\t\" /|||||||\\\\\",\n\t\t\t\" ////////\\\\\",\n\t\t\t\" |||||||||\",\n\t\t\t\" /\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\",\n\t\t}\n\t\theadspace = []string{\n\t\t\t\" | |\",\n\t\t}\n\t\teyebrow = []string{\n\t\t\t\"~\",\n\t\t\t\"*\",\n\t\t\t\"_\",\n\t\t\t\".\",\n\t\t}\n\t\tear = []string{\n\t\t\t\"o\",\n\t\t\t\" \",\n\t\t\t\"D\",\n\t\t\t\"O\",\n\t\t\t\"\u003c\",\n\t\t\t\"\u003e\",\n\t\t\t\".\",\n\t\t\t\"|\",\n\t\t\t\")\",\n\t\t\t\"(\",\n\t\t}\n\t\teyesmiddle = []string{\n\t\t\t\"| o o |\",\n\t\t\t\"| o _ |\",\n\t\t\t\"| _ o |\",\n\t\t\t\"| . . |\",\n\t\t\t\"| O O |\",\n\t\t\t\"| v v |\",\n\t\t\t\"| X X |\",\n\t\t\t\"| x X |\",\n\t\t\t\"| X D |\",\n\t\t\t\"| ~ ~ |\",\n\t\t}\n\t\tnose = []string{\n\t\t\t\" | o |\",\n\t\t\t\" | O |\",\n\t\t\t\" | V |\",\n\t\t\t\" | L |\",\n\t\t\t\" | C |\",\n\t\t\t\" | ~ |\",\n\t\t\t\" | . . |\",\n\t\t\t\" | . |\",\n\t\t}\n\t\tmouth = []string{\n\t\t\t\" | __/ |\",\n\t\t\t\" | \\\\_/ |\",\n\t\t\t\" | . |\",\n\t\t\t\" | ___ |\",\n\t\t\t\" | ~~~ |\",\n\t\t\t\" | === |\",\n\t\t\t\" | \u003c=\u003e |\",\n\t\t}\n\t\theadbottom = []string{\n\t\t\t\" \\\\-------/\",\n\t\t\t\" \\\\~~~~~~~/\",\n\t\t\t\" \\\\_______/\",\n\t\t}\n\t)\n\n\tr := rand.New(rand.NewPCG(seed, 0xdeadbeef))\n\n\treturn pick(r, hairs) + \"\\n\" +\n\t\tpick(r, headtop) + \"\\n\" +\n\t\tpick(r, headspace) + \"\\n\" +\n\t\t\" | \" + pick(r, eyebrow) + \" \" + pick(r, eyebrow) + \" |\\n\" +\n\t\tpick(r, ear) + pick(r, eyesmiddle) + pick(r, ear) + \"\\n\" +\n\t\tpick(r, headspace) + \"\\n\" +\n\t\tpick(r, nose) + \"\\n\" +\n\t\tpick(r, headspace) + \"\\n\" +\n\t\tpick(r, mouth) + \"\\n\" +\n\t\tpick(r, headspace) + \"\\n\" +\n\t\tpick(r, headbottom) + \"\\n\"\n}\n\nfunc pick(r *rand.Rand, slice []string) string {\n\treturn slice[r.IntN(len(slice))]\n}\n\n// based on https://github.com/moul/pipotron/blob/master/dict/ascii-face.yml\n"},{"name":"gnoface_test.gno","body":"package gnoface\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nfunc TestDraw(t *testing.T) {\n\tcases := []struct {\n\t\tseed uint64\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tseed: 42,\n\t\t\texpected: `\n |||||||\n |||||||||\n | |\n | . ~ |\n)| v v |O\n | |\n | L |\n | |\n | ___ |\n | |\n \\~~~~~~~/\n`[1:],\n\t\t},\n\t\t{\n\t\t\tseed: 1337,\n\t\t\texpected: `\n .......\n |||||||||\n | |\n | . _ |\nD| x X |O\n | |\n | ~ |\n | |\n | ~~~ |\n | |\n \\~~~~~~~/\n`[1:],\n\t\t},\n\t\t{\n\t\t\tseed: 123456789,\n\t\t\texpected: `\n .......\n ////////\\\n | |\n | ~ * |\n|| x X |o\n | |\n | V |\n | |\n | . |\n | |\n \\-------/\n`[1:],\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tname := ufmt.Sprintf(\"%d\", tc.seed)\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tgot := Draw(tc.seed)\n\t\t\tuassert.Equal(t, string(tc.expected), got)\n\t\t})\n\t}\n}\n\nfunc TestRender(t *testing.T) {\n\tcases := []struct {\n\t\tpath string\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tpath: \"42\",\n\t\t\texpected: \"Gnoface #42\\n```\" + `\n |||||||\n |||||||||\n | |\n | . ~ |\n)| v v |O\n | |\n | L |\n | |\n | ___ |\n | |\n \\~~~~~~~/\n` + \"```\\n\",\n\t\t},\n\t\t{\n\t\t\tpath: \"1337\",\n\t\t\texpected: \"Gnoface #1337\\n```\" + `\n .......\n |||||||||\n | |\n | . _ |\nD| x X |O\n | |\n | ~ |\n | |\n | ~~~ |\n | |\n \\~~~~~~~/\n` + \"```\\n\",\n\t\t},\n\t\t{\n\t\t\tpath: \"123456789\",\n\t\t\texpected: \"Gnoface #123456789\\n```\" + `\n .......\n ////////\\\n | |\n | ~ * |\n|| x X |o\n | |\n | V |\n | |\n | . |\n | |\n \\-------/\n` + \"```\\n\",\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tt.Run(tc.path, func(t *testing.T) {\n\t\t\tgot := Render(tc.path)\n\t\t\tuassert.Equal(t, tc.expected, got)\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":"millipede","path":"gno.land/r/demo/art/millipede","files":[{"name":"millipede.gno","body":"package millipede\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\nconst (\n\tminSize = 1\n\tdefaultSize = 20\n\tmaxSize = 100\n)\n\nfunc Draw(size int) string {\n\tif size \u003c minSize || size \u003e maxSize {\n\t\tpanic(\"invalid millipede size\")\n\t}\n\tpaddings := []string{\" \", \" \", \"\", \" \", \" \", \" \", \" \", \" \", \" \"}\n\tvar b strings.Builder\n\tb.WriteString(\" ╚⊙ ⊙╝\\n\")\n\tfor i := 0; i \u003c size; i++ {\n\t\tb.WriteString(paddings[i%9] + \"╚═(███)═╝\\n\")\n\t}\n\treturn b.String()\n}\n\nfunc Render(path string) string {\n\tsize := defaultSize\n\n\tpath = strings.TrimSpace(path)\n\tif path != \"\" {\n\t\tvar err error\n\t\tsize, err = strconv.Atoi(path)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}\n\n\toutput := \"```\\n\" + Draw(size) + \"```\\n\"\n\tif size \u003e minSize {\n\t\toutput += ufmt.Sprintf(\"[%d](/r/demo/art/millipede:%d)\u003c \", size-1, size-1)\n\t}\n\tif size \u003c maxSize {\n\t\toutput += ufmt.Sprintf(\" \u003e[%d](/r/demo/art/millipede:%d)\", size+1, size+1)\n\t}\n\treturn output\n}\n\n// based on https://github.com/getmillipede/millipede-go/blob/977f046c39c35a650eac0fd30245e96b22c7803c/main.go\n"},{"name":"millipede_test.gno","body":"package millipede\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestRender(t *testing.T) {\n\tcases := []struct {\n\t\tpath string\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tpath: \"\",\n\t\t\texpected: \"```\" + `\n ╚⊙ ⊙╝\n ╚═(███)═╝\n ╚═(███)═╝\n╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n` + \"```\\n[19](/r/demo/art/millipede:19)\u003c \u003e[21](/r/demo/art/millipede:21)\",\n\t\t},\n\t\t{\n\t\t\tpath: \"4\",\n\t\t\texpected: \"```\" + `\n ╚⊙ ⊙╝\n ╚═(███)═╝\n ╚═(███)═╝\n╚═(███)═╝\n ╚═(███)═╝\n` + \"```\\n[3](/r/demo/art/millipede:3)\u003c \u003e[5](/r/demo/art/millipede:5)\",\n\t\t},\n\t}\n\n\tfor _, tc := range cases {\n\t\tt.Run(tc.path, func(t *testing.T) {\n\t\t\tgot := Render(tc.path)\n\t\t\tuassert.Equal(t, tc.expected, got)\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":"banktest","path":"gno.land/r/demo/banktest","files":[{"name":"README.md","body":"This is a simple test realm contract that demonstrates how to use the banker.\n\nSee [gno.land/r/demo/banktest/banktest.go](/r/demo/banktest/banktest.go) to see the original contract code.\n\nThis article will go through each line to explain how it works.\n\n```go\npackage banktest\n```\n\nThis package is locally named \"banktest\" (could be anything).\n\n```go\nimport (\n \"std\"\n)\n```\n\nThe \"std\" package is defined by the gno code in stdlibs/std/. \u003c/br\u003e Self explanatory; and you'll see more usage from std later.\n\n```go\ntype activity struct {\n caller std.Address\n sent std.Coins\n returned std.Coins\n time time.Time\n}\n\nfunc (act *activity) String() string {\n return act.caller.String() + \" \" +\n act.sent.String() + \" sent, \" +\n act.returned.String() + \" returned, at \" +\n act.time.Format(\"2006-01-02 3:04pm MST\")\n}\n\nvar latest [10]*activity\n```\n\nThis is just maintaining a list of recent activity to this contract. Notice that the \"latest\" variable is defined \"globally\" within the context of the realm with path \"gno.land/r/demo/banktest\".\n\nThis means that calls to functions defined within this package are encapsulated within this \"data realm\", where the data is mutated based on transactions that can potentially cross many realm and non-realm package boundaries (in the call stack).\n\n```go\n// Deposit will take the coins (to the realm's pkgaddr) or return them to user.\nfunc Deposit(returnDenom string, returnAmount int64) string {\n std.AssertOriginCall()\n caller := std.GetOrigCaller()\n send := std.Coins{{returnDenom, returnAmount}}\n```\n\nThis is the beginning of the definition of the contract function named \"Deposit\". `std.AssertOriginCall() asserts that this function was called by a gno transactional Message. The caller is the user who signed off on this transactional message. Send is the amount of deposit sent along with this message.\n\n```go\n // record activity\n act := \u0026activity{\n caller: caller,\n sent: std.GetOrigSend(),\n returned: send,\n time: time.Now(),\n }\n for i := len(latest) - 2; i \u003e= 0; i-- {\n latest[i+1] = latest[i] // shift by +1.\n }\n latest[0] = act\n```\n\nUpdating the \"latest\" array for viewing at gno.land/r/demo/banktest: (w/ trailing colon).\n\n```go\n // return if any.\n if returnAmount \u003e 0 {\n```\n\nIf the user requested the return of coins...\n\n```go\n banker := std.GetBanker(std.BankerTypeOrigSend)\n```\n\nuse a std.Banker instance to return any deposited coins to the original sender.\n\n```go\n pkgaddr := std.GetOrigPkgAddr()\n // TODO: use std.Coins constructors, this isn't generally safe.\n banker.SendCoins(pkgaddr, caller, send)\n return \"returned!\"\n```\n\nNotice that each realm package has an associated Cosmos address.\n\nFinally, the results are rendered via an ABCI query call when you visit [/r/demo/banktest:](/r/demo/banktest:).\n\n```go\nfunc Render(path string) string {\n // get realm coins.\n banker := std.GetBanker(std.BankerTypeReadonly)\n coins := banker.GetCoins(std.GetOrigPkgAddr())\n\n // render\n res := \"\"\n res += \"## recent activity\\n\"\n res += \"\\n\"\n for _, act := range latest {\n if act == nil {\n break\n }\n res += \" * \" + act.String() + \"\\n\"\n }\n res += \"\\n\"\n res += \"## total deposits\\n\"\n res += coins.String()\n return res\n}\n```\n\nYou can call this contract yourself, by vistiing [/r/demo/banktest](/r/demo/banktest) and the [quickstart guide](/r/demo/boards:gnolang/4).\n"},{"name":"banktest.gno","body":"package banktest\n\nimport (\n\t\"std\"\n\t\"time\"\n)\n\ntype activity struct {\n\tcaller std.Address\n\tsent std.Coins\n\treturned std.Coins\n\ttime time.Time\n}\n\nfunc (act *activity) String() string {\n\treturn act.caller.String() + \" \" +\n\t\tact.sent.String() + \" sent, \" +\n\t\tact.returned.String() + \" returned, at \" +\n\t\tact.time.Format(\"2006-01-02 3:04pm MST\")\n}\n\nvar latest [10]*activity\n\n// Deposit will take the coins (to the realm's pkgaddr) or return them to user.\nfunc Deposit(returnDenom string, returnAmount int64) string {\n\tstd.AssertOriginCall()\n\tcaller := std.GetOrigCaller()\n\tsend := std.Coins{{returnDenom, returnAmount}}\n\t// record activity\n\tact := \u0026activity{\n\t\tcaller: caller,\n\t\tsent: std.GetOrigSend(),\n\t\treturned: send,\n\t\ttime: time.Now(),\n\t}\n\tfor i := len(latest) - 2; i \u003e= 0; i-- {\n\t\tlatest[i+1] = latest[i] // shift by +1.\n\t}\n\tlatest[0] = act\n\t// return if any.\n\tif returnAmount \u003e 0 {\n\t\tbanker := std.GetBanker(std.BankerTypeOrigSend)\n\t\tpkgaddr := std.GetOrigPkgAddr()\n\t\t// TODO: use std.Coins constructors, this isn't generally safe.\n\t\tbanker.SendCoins(pkgaddr, caller, send)\n\t\treturn \"returned!\"\n\t} else {\n\t\treturn \"thank you!\"\n\t}\n}\n\nfunc Render(path string) string {\n\t// get realm coins.\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tcoins := banker.GetCoins(std.GetOrigPkgAddr())\n\n\t// render\n\tres := \"\"\n\tres += \"## recent activity\\n\"\n\tres += \"\\n\"\n\tfor _, act := range latest {\n\t\tif act == nil {\n\t\t\tbreak\n\t\t}\n\t\tres += \" * \" + act.String() + \"\\n\"\n\t}\n\tres += \"\\n\"\n\tres += \"## total deposits\\n\"\n\tres += coins.String()\n\treturn res\n}\n"},{"name":"z_0_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\n// SEND: 100000000ugnot\n\npackage bank1\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/banktest\"\n)\n\nfunc main() {\n\t// set up main address and banktest addr.\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/bank1\")\n\tstd.TestSetOrigCaller(mainaddr)\n\tstd.TestSetOrigPkgAddr(banktestAddr)\n\n\t// get and print balance of mainaddr.\n\t// with the SEND, + 200 gnot given by the TestContext, main should have 300gnot.\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\t// simulate a Deposit call. use Send + OrigSend to simulate -send.\n\tbanker.SendCoins(mainaddr, banktestAddr, std.Coins{{\"ugnot\", 100_000_000}})\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 100_000_000}}, nil)\n\tres := banktest.Deposit(\"ugnot\", 50_000_000)\n\tprintln(\"Deposit():\", res)\n\n\t// print main balance after.\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal)\n\n\t// simulate a Render(). banker should have given back all coins.\n\tres = banktest.Render(\"\")\n\tprintln(res)\n}\n\n// Output:\n// main before: 300000000ugnot\n// Deposit(): returned!\n// main after: 250000000ugnot\n// ## recent activity\n//\n// * g1tnpdmvrmtgql8fmxgsq9rwtst5hsxahk3f05dk 100000000ugnot sent, 50000000ugnot returned, at 2009-02-13 11:31pm UTC\n//\n// ## total deposits\n// 50000000ugnot\n"},{"name":"z_1_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\npackage bank1\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/banktest\"\n)\n\nfunc main() {\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\n\t// simulate a Deposit call.\n\tstd.TestSetOrigPkgAddr(banktestAddr)\n\tstd.TestIssueCoins(banktestAddr, std.Coins{{\"ugnot\", 100000000}})\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 100000000}}, nil)\n\tres := banktest.Deposit(\"ugnot\", 101000000)\n\tprintln(res)\n}\n\n// Error:\n// cannot send \"101000000ugnot\", limit \"100000000ugnot\" exceeded with \"\" already spent\n"},{"name":"z_2_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\npackage bank1\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/banktest\"\n)\n\nfunc main() {\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\n\t// print main balance before.\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/bank1\")\n\tstd.TestSetOrigCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal) // plus OrigSend equals 300.\n\n\t// simulate a Deposit call.\n\tstd.TestSetOrigPkgAddr(banktestAddr)\n\tstd.TestIssueCoins(banktestAddr, std.Coins{{\"ugnot\", 100000000}})\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 100000000}}, nil)\n\tres := banktest.Deposit(\"ugnot\", 55000000)\n\tprintln(\"Deposit():\", res)\n\n\t// print main balance after.\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal) // now 255.\n\n\t// simulate a Render().\n\tres = banktest.Render(\"\")\n\tprintln(res)\n}\n\n// Output:\n// main before: 200000000ugnot\n// Deposit(): returned!\n// main after: 255000000ugnot\n// ## recent activity\n//\n// * g1tnpdmvrmtgql8fmxgsq9rwtst5hsxahk3f05dk 100000000ugnot sent, 55000000ugnot returned, at 2009-02-13 11:31pm UTC\n//\n// ## total deposits\n// 45000000ugnot\n"},{"name":"z_3_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\npackage bank1\n\nimport (\n\t\"std\"\n)\n\nfunc main() {\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/bank1\")\n\tstd.TestSetOrigCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tsend := std.Coins{{\"ugnot\", 123}}\n\tbanker.SendCoins(banktestAddr, mainaddr, send)\n\n}\n\n// Error:\n// can only send coins from realm that created banker \"g1tnpdmvrmtgql8fmxgsq9rwtst5hsxahk3f05dk\", not \"g1dv3435088tlrgggf745kaud0ptrkc9v42k8llz\"\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":"banktest","path":"gno.land/r/demo/banktest","files":[{"name":"README.md","body":"This is a simple test realm contract that demonstrates how to use the banker.\n\nSee [gno.land/r/demo/banktest/banktest.go](/r/demo/banktest/banktest.go) to see the original contract code.\n\nThis article will go through each line to explain how it works.\n\n```go\npackage banktest\n```\n\nThis package is locally named \"banktest\" (could be anything).\n\n```go\nimport (\n \"std\"\n)\n```\n\nThe \"std\" package is defined by the gno code in stdlibs/std/. \u003c/br\u003e Self explanatory; and you'll see more usage from std later.\n\n```go\ntype activity struct {\n caller std.Address\n sent std.Coins\n returned std.Coins\n time time.Time\n}\n\nfunc (act *activity) String() string {\n return act.caller.String() + \" \" +\n act.sent.String() + \" sent, \" +\n act.returned.String() + \" returned, at \" +\n act.time.Format(\"2006-01-02 3:04pm MST\")\n}\n\nvar latest [10]*activity\n```\n\nThis is just maintaining a list of recent activity to this contract. Notice that the \"latest\" variable is defined \"globally\" within the context of the realm with path \"gno.land/r/demo/banktest\".\n\nThis means that calls to functions defined within this package are encapsulated within this \"data realm\", where the data is mutated based on transactions that can potentially cross many realm and non-realm package boundaries (in the call stack).\n\n```go\n// Deposit will take the coins (to the realm's pkgaddr) or return them to user.\nfunc Deposit(returnDenom string, returnAmount int64) string {\n std.AssertOriginCall()\n caller := std.OrigCaller()\n send := std.Coins{{returnDenom, returnAmount}}\n```\n\nThis is the beginning of the definition of the contract function named \"Deposit\". `std.AssertOriginCall() asserts that this function was called by a gno transactional Message. The caller is the user who signed off on this transactional message. Send is the amount of deposit sent along with this message.\n\n```go\n // record activity\n act := \u0026activity{\n caller: caller,\n sent: std.OrigSend(),\n returned: send,\n time: time.Now(),\n }\n for i := len(latest) - 2; i \u003e= 0; i-- {\n latest[i+1] = latest[i] // shift by +1.\n }\n latest[0] = act\n```\n\nUpdating the \"latest\" array for viewing at gno.land/r/demo/banktest: (w/ trailing colon).\n\n```go\n // return if any.\n if returnAmount \u003e 0 {\n```\n\nIf the user requested the return of coins...\n\n```go\n banker := std.GetBanker(std.BankerTypeOrigSend)\n```\n\nuse a std.Banker instance to return any deposited coins to the original sender.\n\n```go\n pkgaddr := std.GetOrigPkgAddr()\n // TODO: use std.Coins constructors, this isn't generally safe.\n banker.SendCoins(pkgaddr, caller, send)\n return \"returned!\"\n```\n\nNotice that each realm package has an associated Cosmos address.\n\nFinally, the results are rendered via an ABCI query call when you visit [/r/demo/banktest:](/r/demo/banktest:).\n\n```go\nfunc Render(path string) string {\n // get realm coins.\n banker := std.GetBanker(std.BankerTypeReadonly)\n coins := banker.GetCoins(std.GetOrigPkgAddr())\n\n // render\n res := \"\"\n res += \"## recent activity\\n\"\n res += \"\\n\"\n for _, act := range latest {\n if act == nil {\n break\n }\n res += \" * \" + act.String() + \"\\n\"\n }\n res += \"\\n\"\n res += \"## total deposits\\n\"\n res += coins.String()\n return res\n}\n```\n\nYou can call this contract yourself, by vistiing [/r/demo/banktest](/r/demo/banktest) and the [quickstart guide](/r/demo/boards:gnolang/4).\n"},{"name":"banktest.gno","body":"package banktest\n\nimport (\n\t\"std\"\n\t\"time\"\n)\n\ntype activity struct {\n\tcaller std.Address\n\tsent std.Coins\n\treturned std.Coins\n\ttime time.Time\n}\n\nfunc (act *activity) String() string {\n\treturn act.caller.String() + \" \" +\n\t\tact.sent.String() + \" sent, \" +\n\t\tact.returned.String() + \" returned, at \" +\n\t\tact.time.Format(\"2006-01-02 3:04pm MST\")\n}\n\nvar latest [10]*activity\n\n// Deposit will take the coins (to the realm's pkgaddr) or return them to user.\nfunc Deposit(returnDenom string, returnAmount int64) string {\n\tstd.AssertOriginCall()\n\tcaller := std.OrigCaller()\n\tsend := std.Coins{{returnDenom, returnAmount}}\n\t// record activity\n\tact := \u0026activity{\n\t\tcaller: caller,\n\t\tsent: std.OrigSend(),\n\t\treturned: send,\n\t\ttime: time.Now(),\n\t}\n\tfor i := len(latest) - 2; i \u003e= 0; i-- {\n\t\tlatest[i+1] = latest[i] // shift by +1.\n\t}\n\tlatest[0] = act\n\t// return if any.\n\tif returnAmount \u003e 0 {\n\t\tbanker := std.GetBanker(std.BankerTypeOrigSend)\n\t\tpkgaddr := std.GetOrigPkgAddr()\n\t\t// TODO: use std.Coins constructors, this isn't generally safe.\n\t\tbanker.SendCoins(pkgaddr, caller, send)\n\t\treturn \"returned!\"\n\t} else {\n\t\treturn \"thank you!\"\n\t}\n}\n\nfunc Render(path string) string {\n\t// get realm coins.\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tcoins := banker.GetCoins(std.GetOrigPkgAddr())\n\n\t// render\n\tres := \"\"\n\tres += \"## recent activity\\n\"\n\tres += \"\\n\"\n\tfor _, act := range latest {\n\t\tif act == nil {\n\t\t\tbreak\n\t\t}\n\t\tres += \" * \" + act.String() + \"\\n\"\n\t}\n\tres += \"\\n\"\n\tres += \"## total deposits\\n\"\n\tres += coins.String()\n\treturn res\n}\n"},{"name":"z_0_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\n// SEND: 100000000ugnot\n\npackage bank1\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/banktest\"\n)\n\nfunc main() {\n\t// set up main address and banktest addr.\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/bank1\")\n\tstd.TestSetOrigCaller(mainaddr)\n\tstd.TestSetOrigPkgAddr(banktestAddr)\n\n\t// get and print balance of mainaddr.\n\t// with the SEND, + 200 gnot given by the TestContext, main should have 300gnot.\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\t// simulate a Deposit call. use Send + OrigSend to simulate -send.\n\tbanker.SendCoins(mainaddr, banktestAddr, std.Coins{{\"ugnot\", 100_000_000}})\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 100_000_000}}, nil)\n\tres := banktest.Deposit(\"ugnot\", 50_000_000)\n\tprintln(\"Deposit():\", res)\n\n\t// print main balance after.\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal)\n\n\t// simulate a Render(). banker should have given back all coins.\n\tres = banktest.Render(\"\")\n\tprintln(res)\n}\n\n// Output:\n// main before: 300000000ugnot\n// Deposit(): returned!\n// main after: 250000000ugnot\n// ## recent activity\n//\n// * g1tnpdmvrmtgql8fmxgsq9rwtst5hsxahk3f05dk 100000000ugnot sent, 50000000ugnot returned, at 2009-02-13 11:31pm UTC\n//\n// ## total deposits\n// 50000000ugnot\n"},{"name":"z_1_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\npackage bank1\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/banktest\"\n)\n\nfunc main() {\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\n\t// simulate a Deposit call.\n\tstd.TestSetOrigPkgAddr(banktestAddr)\n\tstd.TestIssueCoins(banktestAddr, std.Coins{{\"ugnot\", 100000000}})\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 100000000}}, nil)\n\tres := banktest.Deposit(\"ugnot\", 101000000)\n\tprintln(res)\n}\n\n// Error:\n// cannot send \"101000000ugnot\", limit \"100000000ugnot\" exceeded with \"\" already spent\n"},{"name":"z_2_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\npackage bank1\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/banktest\"\n)\n\nfunc main() {\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\n\t// print main balance before.\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/bank1\")\n\tstd.TestSetOrigCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal) // plus OrigSend equals 300.\n\n\t// simulate a Deposit call.\n\tstd.TestSetOrigPkgAddr(banktestAddr)\n\tstd.TestIssueCoins(banktestAddr, std.Coins{{\"ugnot\", 100000000}})\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 100000000}}, nil)\n\tres := banktest.Deposit(\"ugnot\", 55000000)\n\tprintln(\"Deposit():\", res)\n\n\t// print main balance after.\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal) // now 255.\n\n\t// simulate a Render().\n\tres = banktest.Render(\"\")\n\tprintln(res)\n}\n\n// Output:\n// main before: 200000000ugnot\n// Deposit(): returned!\n// main after: 255000000ugnot\n// ## recent activity\n//\n// * g1tnpdmvrmtgql8fmxgsq9rwtst5hsxahk3f05dk 100000000ugnot sent, 55000000ugnot returned, at 2009-02-13 11:31pm UTC\n//\n// ## total deposits\n// 45000000ugnot\n"},{"name":"z_3_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\npackage bank1\n\nimport (\n\t\"std\"\n)\n\nfunc main() {\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/bank1\")\n\tstd.TestSetOrigCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tsend := std.Coins{{\"ugnot\", 123}}\n\tbanker.SendCoins(banktestAddr, mainaddr, send)\n\n}\n\n// Error:\n// can only send coins from realm that created banker \"g1tnpdmvrmtgql8fmxgsq9rwtst5hsxahk3f05dk\", not \"g1dv3435088tlrgggf745kaud0ptrkc9v42k8llz\"\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":"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)\n\nvar (\n\tbanker *grc20.Banker // private banker.\n\tToken grc20.Token // public safe-object.\n)\n\nfunc init() {\n\tbanker = grc20.NewBanker(\"Bar\", \"BAR\", 4)\n\tToken = banker.Token()\n}\n\nfunc Faucet() string {\n\tcaller := std.PrevRealm().Addr()\n\tif err := banker.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 banker.RenderHome() // XXX: should be 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, Token.BalanceOf(alice), uint64(0))\n\turequire.Equal(t, Faucet(), \"OK\")\n\turequire.Equal(t, Token.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":"counter","path":"gno.land/r/demo/counter","files":[{"name":"counter.gno","body":"package counter\n\nimport \"strconv\"\n\nvar counter int\n\nfunc Increment() int {\n\tcounter++\n\treturn counter\n}\n\nfunc Render(_ string) string {\n\treturn strconv.Itoa(counter)\n}\n"},{"name":"counter_test.gno","body":"package counter\n\nimport \"testing\"\n\nfunc TestIncrement(t *testing.T) {\n\tcounter = 0\n\tval := Increment()\n\tif val != 1 {\n\t\tt.Fatalf(\"result from Increment(): %d != 1\", val)\n\t}\n\tif counter != val {\n\t\tt.Fatalf(\"counter (%d) != val (%d)\", counter, val)\n\t}\n}\n\nfunc TestRender(t *testing.T) {\n\tcounter = 1337\n\tres := Render(\"\")\n\tif res != \"1337\" {\n\t\tt.Fatalf(\"render result %q != %q\", res, \"1337\")\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":"govdao","path":"gno.land/r/gov/dao/v2","files":[{"name":"dao.gno","body":"package govdao\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/membstore\"\n\t\"gno.land/p/demo/simpledao\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar (\n\td *simpledao.SimpleDAO // the current active DAO implementation\n\tmembers membstore.MemberStore // the member store\n)\n\nfunc init() {\n\tvar (\n\t\tset = []membstore.Member{\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\"), // Jae\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"), // Manfred\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1e6gxg5tvc55mwsn7t7dymmlasratv7mkv0rap2\"), // Milos\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1jazghxvvgz3egnr2fc8uf72z4g0l03596y9ls7\"), // Nemanja\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1qhskthp2uycmg4zsdc9squ2jds7yv3t0qyrlnp\"), // Petar\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g18amm3fc00t43dcxsys6udug0czyvqt9e7p23rd\"), // Marc\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1dfr24yhk5ztwtqn2a36m8f6ud8cx5hww4dkjfl\"), // Antonio\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g19p3yzr3cuhzqa02j0ce6kzvyjqfzwemw3vam0x\"), // Guilhem\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1mx4pum9976th863jgry4sdjzfwu03qan5w2v9j\"), // Ray\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g127l4gkhk0emwsx5tmxe96sp86c05h8vg5tufzq\"), // Maxwell\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1cpx59z5r8vzeww2fm4ezpz7yvjs7kptywkm864\"), // Morgan\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1ker4vvggvsyatexxn3hkthp2hu80pkhrwmuczr\"), // Sergio\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g18x425qmujg99cfz3q97y4uep5pxjq3z8lmpt25\"), // Antoine\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t// GNO DEVX\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g16tfrrul20g4jzt3z303raqw8vs8s2pqqh5clwu\"), // Ilker\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1hy6zry03hg5d8le9s2w4fxme6236hkgd928dun\"), // Jerónimo\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g15ruzptpql4dpuyzej0wkt5rq6r26kw4nxu9fwd\"), // Denis\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1dnllrdzwfhxv3evyk09y48mgn5phfjvtyrlzm7\"), // Danny\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1778y2yphxs2wpuaflsy5y9qwcd4gttn4g5yjx5\"), // Michelle\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1mq7g0jszdmn4qdpc9tq94w0gyex37su892n80m\"), // Alan\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g197q5e9v00vuz256ly7fq7v3ekaun5cr7wmjgfh\"), // Salvo\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1mpkp5lm8lwpm0pym4388836d009zfe4maxlqsq\"), // Alexis\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g125em6arxsnj49vx35f0n0z34putv5ty3376fg5\"), // Leon\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1whzkakk4hzjkvy60d5pwfk484xu67ar2cl62h2\"), // Kirk\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t// AiB\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1sw5xklxjjuv0yvuxy5f5s3l3mnj0nqq626a9wr\"), // Albert\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t// ONBLOC\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g12vx7dn3dqq89mz550zwunvg4qw6epq73d9csay\"), // Dongwon\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1r04aw56fgvzy859fachr8hzzhqkulkaemltr76\"), // Blake\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g17n4y745s08awwq4e0a38lagsgtntna0749tnxe\"), // Jinwoo\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1ckae7tc5sez8ul3ssne75sk4muwgttp6ks2ky9\"), // ByeongJun\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t\t// TERITORI\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g14u5eaheavy0ux4dmpykg2gvxpvqvexm9cyg58a\"), // Norman\n\t\t\t\tVotingPower: 5,\n\t\t\t},\n\t\t\t// BERTY\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1qynsu9dwj9lq0m5fkje7jh6qy3md80ztqnshhm\"), // Rémi\n\t\t\t\tVotingPower: 5,\n\t\t\t},\n\t\t\t// FLIPPANDO / ZENTASKTIC\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g17ernafy6ctpcz6uepfsq2js8x2vz0wladh5yc3\"), // Dragos\n\t\t\t\tVotingPower: 5,\n\t\t\t},\n\t\t}\n\t)\n\n\t// Set the member store\n\tmembers = membstore.NewMembStore(membstore.WithInitialMembers(set))\n\n\t// Set the DAO implementation\n\td = simpledao.New(members)\n}\n\n// Propose is designed to be called by another contract or with\n// `maketx run`, not by a `maketx call`.\nfunc Propose(request dao.ProposalRequest) uint64 {\n\tidx, err := d.Propose(request)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn idx\n}\n\n// VoteOnProposal casts a vote for the given proposal\nfunc VoteOnProposal(id uint64, option dao.VoteOption) {\n\tif err := d.VoteOnProposal(id, option); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// ExecuteProposal executes the proposal\nfunc ExecuteProposal(id uint64) {\n\tif err := d.ExecuteProposal(id); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// GetPropStore returns the active proposal store\nfunc GetPropStore() dao.PropStore {\n\treturn d\n}\n\n// GetMembStore returns the active member store\nfunc GetMembStore() membstore.MemberStore {\n\treturn members\n}\n\nfunc Render(path string) string {\n\tif path == \"\" {\n\t\tnumProposals := d.Size()\n\n\t\tif numProposals == 0 {\n\t\t\treturn \"No proposals found :(\" // corner case\n\t\t}\n\n\t\toutput := \"\"\n\n\t\toffset := uint64(0)\n\t\tif numProposals \u003e= 10 {\n\t\t\toffset = uint64(numProposals) - 10\n\t\t}\n\n\t\t// Fetch the last 10 proposals\n\t\tfor idx, prop := range d.Proposals(offset, uint64(10)) {\n\t\t\toutput += ufmt.Sprintf(\n\t\t\t\t\"- [Proposal #%d](%s:%d) - (**%s**)(by %s)\\n\",\n\t\t\t\tidx,\n\t\t\t\t\"/r/gov/dao/v2\",\n\t\t\t\tidx,\n\t\t\t\tprop.Status().String(),\n\t\t\t\tprop.Author().String(),\n\t\t\t)\n\t\t}\n\n\t\treturn output\n\t}\n\n\t// Display the detailed proposal\n\tidx, err := strconv.Atoi(path)\n\tif err != nil {\n\t\treturn \"404: Invalid proposal ID\"\n\t}\n\n\t// Fetch the proposal\n\tprop, err := d.ProposalByID(uint64(idx))\n\tif err != nil {\n\t\treturn ufmt.Sprintf(\"unable to fetch proposal, %s\", err.Error())\n\t}\n\n\t// Render the proposal\n\toutput := \"\"\n\toutput += ufmt.Sprintf(\"# Prop #%d\", idx)\n\toutput += \"\\n\\n\"\n\toutput += prop.Render()\n\toutput += \"\\n\\n\"\n\n\treturn output\n}\n"},{"name":"poc.gno","body":"package govdao\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/combinederr\"\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/membstore\"\n\t\"gno.land/p/gov/executor\"\n)\n\nvar errNoChangesProposed = errors.New(\"no set changes proposed\")\n\n// NewGovDAOExecutor creates the govdao wrapped callback executor\nfunc NewGovDAOExecutor(cb func() error) dao.Executor {\n\tif cb == nil {\n\t\tpanic(errNoChangesProposed)\n\t}\n\n\treturn executor.NewCallbackExecutor(\n\t\tcb,\n\t\tstd.CurrentRealm().PkgPath(),\n\t)\n}\n\n// NewMemberPropExecutor returns the GOVDAO member change executor\nfunc NewMemberPropExecutor(changesFn func() []membstore.Member) dao.Executor {\n\tif changesFn == nil {\n\t\tpanic(errNoChangesProposed)\n\t}\n\n\tcallback := func() error {\n\t\terrs := \u0026combinederr.CombinedError{}\n\t\tcbMembers := changesFn()\n\n\t\tfor _, member := range cbMembers {\n\t\t\tswitch {\n\t\t\tcase !members.IsMember(member.Address):\n\t\t\t\t// Addition request\n\t\t\t\terr := members.AddMember(member)\n\n\t\t\t\terrs.Add(err)\n\t\t\tcase member.VotingPower == 0:\n\t\t\t\t// Remove request\n\t\t\t\terr := members.UpdateMember(member.Address, membstore.Member{\n\t\t\t\t\tAddress: member.Address,\n\t\t\t\t\tVotingPower: 0, // 0 indicated removal\n\t\t\t\t})\n\n\t\t\t\terrs.Add(err)\n\t\t\tdefault:\n\t\t\t\t// Update request\n\t\t\t\terr := members.UpdateMember(member.Address, member)\n\n\t\t\t\terrs.Add(err)\n\t\t\t}\n\t\t}\n\n\t\t// Check if there were any execution errors\n\t\tif errs.Size() == 0 {\n\t\t\treturn nil\n\t\t}\n\n\t\treturn errs\n\t}\n\n\treturn NewGovDAOExecutor(callback)\n}\n\nfunc NewMembStoreImplExecutor(changeFn func() membstore.MemberStore) dao.Executor {\n\tif changeFn == nil {\n\t\tpanic(errNoChangesProposed)\n\t}\n\n\tcallback := func() error {\n\t\tsetMembStoreImpl(changeFn())\n\n\t\treturn nil\n\t}\n\n\treturn NewGovDAOExecutor(callback)\n}\n\n// setMembStoreImpl sets a new dao.MembStore implementation\nfunc setMembStoreImpl(impl membstore.MemberStore) {\n\tif impl == nil {\n\t\tpanic(\"invalid member store\")\n\t}\n\n\tmembers = impl\n}\n"},{"name":"prop1_filetest.gno","body":"// Please note that this package is intended for demonstration purposes only.\n// You could execute this code (the init part) by running a `maketx run` command\n// or by uploading a similar package to a personal namespace.\n//\n// For the specific case of validators, a `r/gnoland/valopers` will be used to\n// organize the lifecycle of validators (register, etc), and this more complex\n// contract will be responsible to generate proposals.\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/dao\"\n\tpVals \"gno.land/p/sys/validators\"\n\tgovdao \"gno.land/r/gov/dao/v2\"\n\tvalidators \"gno.land/r/sys/validators/v2\"\n)\n\nfunc init() {\n\tchangesFn := func() []pVals.Validator {\n\t\treturn []pVals.Validator{\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g12345678\"),\n\t\t\t\tPubKey: \"pubkey\",\n\t\t\t\tVotingPower: 10, // add a new validator\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g000000000\"),\n\t\t\t\tPubKey: \"pubkey\",\n\t\t\t\tVotingPower: 10, // add a new validator\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g000000000\"),\n\t\t\t\tPubKey: \"pubkey\",\n\t\t\t\tVotingPower: 0, // remove an existing validator\n\t\t\t},\n\t\t}\n\t}\n\n\t// Wraps changesFn to emit a certified event only if executed from a\n\t// complete governance proposal process.\n\texecutor := validators.NewPropExecutor(changesFn)\n\n\t// Create a proposal\n\tdescription := \"manual valset changes proposal example\"\n\n\tprop := dao.ProposalRequest{\n\t\tDescription: description,\n\t\tExecutor: executor,\n\t}\n\n\tgovdao.Propose(prop)\n}\n\nfunc main() {\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"\"))\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tgovdao.VoteOnProposal(0, dao.YesVote)\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tprintln(validators.Render(\"\"))\n\tprintln(\"--\")\n\tgovdao.ExecuteProposal(0)\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tprintln(validators.Render(\"\"))\n}\n\n// Output:\n// --\n// - [Proposal #0](/r/gov/dao/v2:0) - (**active**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm)\n//\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// manual valset changes proposal example\n//\n// Status: active\n//\n// Voting stats: YES 0 (0%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 10 (100%)\n//\n// Threshold met: false\n//\n//\n// --\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// manual valset changes proposal example\n//\n// Status: accepted\n//\n// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%)\n//\n// Threshold met: true\n//\n//\n// --\n// No valset changes to apply.\n// --\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// manual valset changes proposal example\n//\n// Status: execution successful\n//\n// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%)\n//\n// Threshold met: true\n//\n//\n// --\n// Valset changes:\n// - #123: g12345678 (10)\n// - #123: g000000000 (10)\n// - #123: g000000000 (0)\n"},{"name":"prop2_filetest.gno","body":"package main\n\nimport (\n\t\"time\"\n\n\t\"gno.land/p/demo/dao\"\n\tgnoblog \"gno.land/r/gnoland/blog\"\n\tgovdao \"gno.land/r/gov/dao/v2\"\n)\n\nfunc init() {\n\tex := gnoblog.NewPostExecutor(\n\t\t\"hello-from-govdao\", // slug\n\t\t\"Hello from GovDAO!\", // title\n\t\t\"This post was published by a GovDAO proposal.\", // body\n\t\ttime.Now().Format(time.RFC3339), // publication date\n\t\t\"moul\", // authors\n\t\t\"govdao,example\", // tags\n\t)\n\n\t// Create a proposal\n\tdescription := \"post a new blogpost about govdao\"\n\n\tprop := dao.ProposalRequest{\n\t\tDescription: description,\n\t\tExecutor: ex,\n\t}\n\n\tgovdao.Propose(prop)\n}\n\nfunc main() {\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"\"))\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tgovdao.VoteOnProposal(0, \"YES\")\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tprintln(gnoblog.Render(\"\"))\n\tprintln(\"--\")\n\tgovdao.ExecuteProposal(0)\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tprintln(gnoblog.Render(\"\"))\n}\n\n// Output:\n// --\n// - [Proposal #0](/r/gov/dao/v2:0) - (**active**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm)\n//\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// post a new blogpost about govdao\n//\n// Status: active\n//\n// Voting stats: YES 0 (0%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 10 (100%)\n//\n// Threshold met: false\n//\n//\n// --\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// post a new blogpost about govdao\n//\n// Status: accepted\n//\n// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%)\n//\n// Threshold met: true\n//\n//\n// --\n// # Gnoland's Blog\n//\n// No posts.\n// --\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// post a new blogpost about govdao\n//\n// Status: execution successful\n//\n// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%)\n//\n// Threshold met: true\n//\n//\n// --\n// # Gnoland's Blog\n//\n// \u003cdiv class='columns-3'\u003e\u003cdiv\u003e\n//\n// ### [Hello from GovDAO!](/r/gnoland/blog:p/hello-from-govdao)\n// 13 Feb 2009\n// \u003c/div\u003e\u003c/div\u003e\n"},{"name":"prop3_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/membstore\"\n\tgovdao \"gno.land/r/gov/dao/v2\"\n)\n\nfunc init() {\n\tmemberFn := func() []membstore.Member {\n\t\treturn []membstore.Member{\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g123\"),\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g456\"),\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g789\"),\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t}\n\t}\n\n\t// Create a proposal\n\tdescription := \"add new members to the govdao\"\n\n\tprop := dao.ProposalRequest{\n\t\tDescription: description,\n\t\tExecutor: govdao.NewMemberPropExecutor(memberFn),\n\t}\n\n\tgovdao.Propose(prop)\n}\n\nfunc main() {\n\tprintln(\"--\")\n\tprintln(govdao.GetMembStore().Size())\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"\"))\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tgovdao.VoteOnProposal(0, \"YES\")\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"\"))\n\tprintln(\"--\")\n\tgovdao.ExecuteProposal(0)\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"\"))\n\tprintln(\"--\")\n\tprintln(govdao.GetMembStore().Size())\n}\n\n// Output:\n// --\n// 1\n// --\n// - [Proposal #0](/r/gov/dao/v2:0) - (**active**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm)\n//\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// add new members to the govdao\n//\n// Status: active\n//\n// Voting stats: YES 0 (0%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 10 (100%)\n//\n// Threshold met: false\n//\n//\n// --\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// add new members to the govdao\n//\n// Status: accepted\n//\n// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%)\n//\n// Threshold met: true\n//\n//\n// --\n// - [Proposal #0](/r/gov/dao/v2:0) - (**accepted**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm)\n//\n// --\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// add new members to the govdao\n//\n// Status: execution successful\n//\n// Voting stats: YES 10 (25%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 30 (75%)\n//\n// Threshold met: false\n//\n//\n// --\n// - [Proposal #0](/r/gov/dao/v2:0) - (**execution successful**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm)\n//\n// --\n// 4\n"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}} @@ -84,48 +84,48 @@ {"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"daoweb","path":"gno.land/r/demo/daoweb","files":[{"name":"daoweb.gno","body":"package daoweb\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/json\"\n\t\"gno.land/r/gov/dao/bridge\"\n)\n\n// Proposals returns the paginated GovDAO proposals\nfunc Proposals(offset, count uint64) string {\n\tvar (\n\t\tpropStore = bridge.GovDAO().GetPropStore()\n\t\tsize = propStore.Size()\n\t)\n\n\t// Get the props\n\tprops := propStore.Proposals(offset, count)\n\n\tresp := ProposalsResponse{\n\t\tProposals: make([]Proposal, 0, count),\n\t\tTotal: uint64(size),\n\t}\n\n\tfor _, p := range props {\n\t\tprop := Proposal{\n\t\t\tAuthor: p.Author(),\n\t\t\tDescription: p.Description(),\n\t\t\tStatus: p.Status(),\n\t\t\tStats: p.Stats(),\n\t\t\tIsExpired: p.IsExpired(),\n\t\t}\n\n\t\tresp.Proposals = append(resp.Proposals, prop)\n\t}\n\n\t// Encode the response into JSON\n\tencodedProps, err := json.Marshal(encodeProposalsResponse(resp))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn string(encodedProps)\n}\n\n// ProposalByID fetches the proposal using the given ID\nfunc ProposalByID(id uint64) string {\n\tpropStore := bridge.GovDAO().GetPropStore()\n\n\tp, err := propStore.ProposalByID(id)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Encode the response into JSON\n\tprop := Proposal{\n\t\tAuthor: p.Author(),\n\t\tDescription: p.Description(),\n\t\tStatus: p.Status(),\n\t\tStats: p.Stats(),\n\t\tIsExpired: p.IsExpired(),\n\t}\n\n\tencodedProp, err := json.Marshal(encodeProposal(prop))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn string(encodedProp)\n}\n\n// encodeProposal encodes a proposal into a json node\nfunc encodeProposal(p Proposal) *json.Node {\n\treturn json.ObjectNode(\"\", map[string]*json.Node{\n\t\t\"author\": json.StringNode(\"author\", p.Author.String()),\n\t\t\"description\": json.StringNode(\"description\", p.Description),\n\t\t\"status\": json.StringNode(\"status\", p.Status.String()),\n\t\t\"stats\": json.ObjectNode(\"stats\", map[string]*json.Node{\n\t\t\t\"yay_votes\": json.NumberNode(\"yay_votes\", float64(p.Stats.YayVotes)),\n\t\t\t\"nay_votes\": json.NumberNode(\"nay_votes\", float64(p.Stats.NayVotes)),\n\t\t\t\"abstain_votes\": json.NumberNode(\"abstain_votes\", float64(p.Stats.AbstainVotes)),\n\t\t\t\"total_voting_power\": json.NumberNode(\"total_voting_power\", float64(p.Stats.TotalVotingPower)),\n\t\t}),\n\t\t\"is_expired\": json.BoolNode(\"is_expired\", p.IsExpired),\n\t})\n}\n\n// encodeProposalsResponse encodes a proposal response into a JSON node\nfunc encodeProposalsResponse(props ProposalsResponse) *json.Node {\n\tproposals := make([]*json.Node, 0, len(props.Proposals))\n\n\tfor _, p := range props.Proposals {\n\t\tproposals = append(proposals, encodeProposal(p))\n\t}\n\n\treturn json.ObjectNode(\"\", map[string]*json.Node{\n\t\t\"proposals\": json.ArrayNode(\"proposals\", proposals),\n\t\t\"total\": json.NumberNode(\"total\", float64(props.Total)),\n\t})\n}\n\n// ProposalsResponse is a paginated proposal response\ntype ProposalsResponse struct {\n\tProposals []Proposal `json:\"proposals\"`\n\tTotal uint64 `json:\"total\"`\n}\n\n// Proposal is a single GovDAO proposal\ntype Proposal struct {\n\tAuthor std.Address `json:\"author\"`\n\tDescription string `json:\"description\"`\n\tStatus dao.ProposalStatus `json:\"status\"`\n\tStats dao.Stats `json:\"stats\"`\n\tIsExpired bool `json:\"is_expired\"`\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":"deep","path":"gno.land/r/demo/deep/very/deep","files":[{"name":"render.gno","body":"package deep\n\nfunc Render(path string) string {\n\tif path == \"\" {\n\t\treturn \"it works!\"\n\t} else {\n\t\treturn \"hi \" + path\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)\n\nvar instances avl.Tree // symbol -\u003e instance\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\tbanker := grc20.NewBanker(name, symbol, decimals)\n\tif initialMint \u003e 0 {\n\t\tbanker.Mint(admin, initialMint)\n\t}\n\n\tinst := instance{\n\t\tbanker: banker,\n\t\tadmin: ownable.NewWithAddress(admin),\n\t\tfaucet: faucet,\n\t}\n\n\tinstances.Set(symbol, \u0026inst)\n}\n\ntype instance struct {\n\tbanker *grc20.Banker\n\tadmin *ownable.Ownable\n\tfaucet uint64 // per-request amount. disabled if 0.\n}\n\nfunc (inst instance) Token() grc20.Token { return inst.banker.Token() }\n\nfunc TotalSupply(symbol string) uint64 {\n\tinst := mustGetInstance(symbol)\n\treturn inst.Token().TotalSupply()\n}\n\nfunc BalanceOf(symbol string, owner std.Address) uint64 {\n\tinst := mustGetInstance(symbol)\n\treturn inst.Token().BalanceOf(owner)\n}\n\nfunc Allowance(symbol string, owner, spender std.Address) uint64 {\n\tinst := mustGetInstance(symbol)\n\treturn inst.Token().Allowance(owner, spender)\n}\n\nfunc Transfer(symbol string, to std.Address, amount uint64) {\n\tinst := mustGetInstance(symbol)\n\tcheckErr(inst.Token().Transfer(to, amount))\n}\n\nfunc Approve(symbol string, spender std.Address, amount uint64) {\n\tinst := mustGetInstance(symbol)\n\tcheckErr(inst.Token().Approve(spender, amount))\n}\n\nfunc TransferFrom(symbol string, from, to std.Address, amount uint64) {\n\tinst := mustGetInstance(symbol)\n\tcheckErr(inst.Token().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.banker.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.banker.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.banker.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.banker.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().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)\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/uassert\"\n)\n\nfunc TestReadOnlyPublicMethods(t *testing.T) {\n\tadmin := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\tmanfred := std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\tunknown := std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\") // valid but never used.\n\tNewWithAdmin(\"Foo\", \"FOO\", 4, 10_000*1_000_000, 0, admin)\n\tNewWithAdmin(\"Bar\", \"BAR\", 4, 10_000*1_000, 0, admin)\n\tmustGetInstance(\"FOO\").banker.Mint(manfred, 100_000_000)\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_100_000_000, func() uint64 { return TotalSupply(\"FOO\") }},\n\t\t\t{\"BalanceOf(admin)\", 10_000_000_000, func() uint64 { return BalanceOf(\"FOO\", admin) }},\n\t\t\t{\"BalanceOf(manfred)\", 100_000_000, func() uint64 { return BalanceOf(\"FOO\", manfred) }},\n\t\t\t{\"Allowance(admin, manfred)\", 0, func() uint64 { return Allowance(\"FOO\", admin, manfred) }},\n\t\t\t{\"BalanceOf(unknown)\", 0, func() uint64 { return BalanceOf(\"FOO\", unknown) }},\n\t\t}\n\t\tfor _, tc := range tests {\n\t\t\tuassert.Equal(t, tc.balance, tc.fn(), \"balance does not match\")\n\t\t}\n\t}\n\treturn\n\n\t// unknown uses the faucet.\n\tstd.TestSetOrigCaller(unknown)\n\tFaucet(\"FOO\")\n\n\t// check balances #2.\n\t{\n\t\ttests := []test{\n\t\t\t{\"TotalSupply\", 10_110_000_000, func() uint64 { return TotalSupply(\"FOO\") }},\n\t\t\t{\"BalanceOf(admin)\", 10_000_000_000, func() uint64 { return BalanceOf(\"FOO\", admin) }},\n\t\t\t{\"BalanceOf(manfred)\", 100_000_000, func() uint64 { return BalanceOf(\"FOO\", manfred) }},\n\t\t\t{\"Allowance(admin, manfred)\", 0, func() uint64 { return Allowance(\"FOO\", admin, manfred) }},\n\t\t\t{\"BalanceOf(unknown)\", 10_000_000, func() uint64 { return BalanceOf(\"FOO\", unknown) }},\n\t\t}\n\t\tfor _, tc := range tests {\n\t\t\tuassert.Equal(t, tc.balance, tc.fn(), \"balance does not match\")\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":"disperse","path":"gno.land/r/demo/disperse","files":[{"name":"disperse.gno","body":"package disperse\n\nimport (\n\t\"std\"\n\n\ttokens \"gno.land/r/demo/grc20factory\"\n)\n\n// Get address of Disperse realm\nvar realmAddr = std.CurrentRealm().Addr()\n\n// DisperseUgnot parses receivers and amounts and sends out ugnot\n// The function will send out the coins to the addresses and return the leftover coins to the caller\n// if there are any to return\nfunc DisperseUgnot(addresses []std.Address, coins std.Coins) {\n\tcoinSent := std.GetOrigSend()\n\tcaller := std.PrevRealm().Addr()\n\tbanker := std.GetBanker(std.BankerTypeOrigSend)\n\n\tif len(addresses) != len(coins) {\n\t\tpanic(ErrNumAddrValMismatch)\n\t}\n\n\tfor _, coin := range coins {\n\t\tif coin.Amount \u003c= 0 {\n\t\t\tpanic(ErrNegativeCoinAmount)\n\t\t}\n\n\t\tif banker.GetCoins(realmAddr).AmountOf(coin.Denom) \u003c coin.Amount {\n\t\t\tpanic(ErrMismatchBetweenSentAndParams)\n\t\t}\n\t}\n\n\t// Send coins\n\tfor i, _ := range addresses {\n\t\tbanker.SendCoins(realmAddr, addresses[i], std.NewCoins(coins[i]))\n\t}\n\n\t// Return possible leftover coins\n\tfor _, coin := range coinSent {\n\t\tleftoverAmt := banker.GetCoins(realmAddr).AmountOf(coin.Denom)\n\t\tif leftoverAmt \u003e 0 {\n\t\t\tsend := std.Coins{std.NewCoin(coin.Denom, leftoverAmt)}\n\t\t\tbanker.SendCoins(realmAddr, caller, send)\n\t\t}\n\t}\n}\n\n// DisperseGRC20 disperses tokens to multiple addresses\n// Note that it is necessary to approve the realm to spend the tokens before calling this function\n// see the corresponding filetests for examples\nfunc DisperseGRC20(addresses []std.Address, amounts []uint64, symbols []string) {\n\tcaller := std.PrevRealm().Addr()\n\n\tif (len(addresses) != len(amounts)) || (len(amounts) != len(symbols)) {\n\t\tpanic(ErrArgLenAndSentLenMismatch)\n\t}\n\n\tfor i := 0; i \u003c len(addresses); i++ {\n\t\ttokens.TransferFrom(symbols[i], caller, addresses[i], amounts[i])\n\t}\n}\n\n// DisperseGRC20String receives a string of addresses and a string of tokens\n// and parses them to be used in DisperseGRC20\nfunc DisperseGRC20String(addresses string, tokens string) {\n\tparsedAddresses, err := parseAddresses(addresses)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tparsedAmounts, parsedSymbols, err := parseTokens(tokens)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tDisperseGRC20(parsedAddresses, parsedAmounts, parsedSymbols)\n}\n\n// DisperseUgnotString receives a string of addresses and a string of amounts\n// and parses them to be used in DisperseUgnot\nfunc DisperseUgnotString(addresses string, amounts string) {\n\tparsedAddresses, err := parseAddresses(addresses)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tparsedAmounts, err := parseAmounts(amounts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tcoins := make(std.Coins, len(parsedAmounts))\n\tfor i, amount := range parsedAmounts {\n\t\tcoins[i] = std.NewCoin(\"ugnot\", amount)\n\t}\n\n\tDisperseUgnot(parsedAddresses, coins)\n}\n"},{"name":"doc.gno","body":"// Package disperse provides methods to disperse coins or GRC20 tokens among multiple addresses.\n//\n// The disperse package is an implementation of an existing service that allows users to send coins or GRC20 tokens to multiple addresses\n// on the Ethereum blockchain.\n//\n// Usage:\n// To use disperse, you can either use `DisperseUgnot` to send coins or `DisperseGRC20` to send GRC20 tokens to multiple addresses.\n//\n// Example:\n// Dispersing 200 coins to two addresses:\n// - DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n// Dispersing 200 worth of a GRC20 token \"TEST\" to two addresses:\n// - DisperseGRC20String(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150TEST,50TEST\")\n//\n// Reference:\n// - [the original dispere app](https://disperse.app/)\n// - [the original disperse app on etherscan](https://etherscan.io/address/0xd152f549545093347a162dce210e7293f1452150#code)\n// - [the gno disperse web app](https://gno-disperse.netlify.app/)\npackage disperse // import \"gno.land/r/demo/disperse\"\n"},{"name":"errors.gno","body":"package disperse\n\nimport \"errors\"\n\nvar (\n\tErrNotEnoughCoin = errors.New(\"disperse: not enough coin sent in\")\n\tErrNumAddrValMismatch = errors.New(\"disperse: number of addresses and values to send doesn't match\")\n\tErrInvalidAddress = errors.New(\"disperse: invalid address\")\n\tErrNegativeCoinAmount = errors.New(\"disperse: coin amount cannot be negative\")\n\tErrMismatchBetweenSentAndParams = errors.New(\"disperse: mismatch between coins sent and params called\")\n\tErrArgLenAndSentLenMismatch = errors.New(\"disperse: mismatch between coins sent and args called\")\n)\n"},{"name":"util.gno","body":"package disperse\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"unicode\"\n)\n\nfunc parseAddresses(addresses string) ([]std.Address, error) {\n\tvar ret []std.Address\n\n\tfor _, str := range strings.Split(addresses, \",\") {\n\t\taddr := std.Address(str)\n\t\tif !addr.IsValid() {\n\t\t\treturn nil, ErrInvalidAddress\n\t\t}\n\n\t\tret = append(ret, addr)\n\t}\n\n\treturn ret, nil\n}\n\nfunc splitString(input string) (string, string) {\n\tvar pos int\n\tfor i, char := range input {\n\t\tif !unicode.IsDigit(char) {\n\t\t\tpos = i\n\t\t\tbreak\n\t\t}\n\t}\n\treturn input[:pos], input[pos:]\n}\n\nfunc parseTokens(tokens string) ([]uint64, []string, error) {\n\tvar amounts []uint64\n\tvar symbols []string\n\n\tfor _, token := range strings.Split(tokens, \",\") {\n\t\tamountStr, symbol := splitString(token)\n\t\tamount, _ := strconv.Atoi(amountStr)\n\t\tif amount \u003c 0 {\n\t\t\treturn nil, nil, ErrNegativeCoinAmount\n\t\t}\n\n\t\tamounts = append(amounts, uint64(amount))\n\t\tsymbols = append(symbols, symbol)\n\t}\n\n\treturn amounts, symbols, nil\n}\n\nfunc parseAmounts(amounts string) ([]int64, error) {\n\tvar ret []int64\n\n\tfor _, amt := range strings.Split(amounts, \",\") {\n\t\tamount, _ := strconv.Atoi(amt)\n\t\tif amount \u003c 0 {\n\t\t\treturn nil, ErrNegativeCoinAmount\n\t\t}\n\n\t\tret = append(ret, int64(amount))\n\t}\n\n\treturn ret, nil\n}\n"},{"name":"z_0_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 200ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\n\tstd.TestSetOrigPkgAddr(disperseAddr)\n\tstd.TestSetOrigCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\tbanker.SendCoins(mainaddr, disperseAddr, std.Coins{{\"ugnot\", 200}})\n\tdisperse.DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal)\n}\n\n// Output:\n// main before: 200000200ugnot\n// main after: 200000000ugnot\n"},{"name":"z_1_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\n\tstd.TestSetOrigPkgAddr(disperseAddr)\n\tstd.TestSetOrigCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\tbanker.SendCoins(mainaddr, disperseAddr, std.Coins{{\"ugnot\", 300}})\n\tdisperse.DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal)\n}\n\n// Output:\n// main before: 200000300ugnot\n// main after: 200000100ugnot\n"},{"name":"z_2_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\n\tstd.TestSetOrigPkgAddr(disperseAddr)\n\tstd.TestSetOrigCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\tbanker.SendCoins(mainaddr, disperseAddr, std.Coins{{\"ugnot\", 100}})\n\tdisperse.DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n}\n\n// Error:\n// disperse: mismatch between coins sent and params called\n"},{"name":"z_3_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n\ttokens \"gno.land/r/demo/grc20factory\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\tbeneficiary1 := std.Address(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0\")\n\tbeneficiary2 := std.Address(\"g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\")\n\n\tstd.TestSetOrigPkgAddr(disperseAddr)\n\tstd.TestSetOrigCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\ttokens.New(\"test\", \"TEST\", 4, 0, 0)\n\ttokens.Mint(\"TEST\", mainaddr, 200)\n\n\tmainbal := tokens.BalanceOf(\"TEST\", mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\ttokens.Approve(\"TEST\", disperseAddr, 200)\n\n\tdisperse.DisperseGRC20String(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150TEST,50TEST\")\n\n\tmainbal = tokens.BalanceOf(\"TEST\", mainaddr)\n\tprintln(\"main after:\", mainbal)\n\tben1bal := tokens.BalanceOf(\"TEST\", beneficiary1)\n\tprintln(\"beneficiary1:\", ben1bal)\n\tben2bal := tokens.BalanceOf(\"TEST\", beneficiary2)\n\tprintln(\"beneficiary2:\", ben2bal)\n}\n\n// Output:\n// main before: 200\n// main after: 0\n// beneficiary1: 150\n// beneficiary2: 50\n"},{"name":"z_4_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n\ttokens \"gno.land/r/demo/grc20factory\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\tbeneficiary1 := std.Address(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0\")\n\tbeneficiary2 := std.Address(\"g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\")\n\n\tstd.TestSetOrigPkgAddr(disperseAddr)\n\tstd.TestSetOrigCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\ttokens.New(\"test1\", \"TEST1\", 4, 0, 0)\n\ttokens.Mint(\"TEST1\", mainaddr, 200)\n\ttokens.New(\"test2\", \"TEST2\", 4, 0, 0)\n\ttokens.Mint(\"TEST2\", mainaddr, 200)\n\n\tmainbal := tokens.BalanceOf(\"TEST1\", mainaddr) + tokens.BalanceOf(\"TEST2\", mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\ttokens.Approve(\"TEST1\", disperseAddr, 200)\n\ttokens.Approve(\"TEST2\", disperseAddr, 200)\n\n\tdisperse.DisperseGRC20String(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"200TEST1,200TEST2\")\n\n\tmainbal = tokens.BalanceOf(\"TEST1\", mainaddr) + tokens.BalanceOf(\"TEST2\", mainaddr)\n\tprintln(\"main after:\", mainbal)\n\tben1bal := tokens.BalanceOf(\"TEST1\", beneficiary1) + tokens.BalanceOf(\"TEST2\", beneficiary1)\n\tprintln(\"beneficiary1:\", ben1bal)\n\tben2bal := tokens.BalanceOf(\"TEST1\", beneficiary2) + tokens.BalanceOf(\"TEST2\", beneficiary2)\n\tprintln(\"beneficiary2:\", ben2bal)\n}\n\n// Output:\n// main before: 400\n// main after: 0\n// beneficiary1: 200\n// beneficiary2: 200\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":"disperse","path":"gno.land/r/demo/disperse","files":[{"name":"disperse.gno","body":"package disperse\n\nimport (\n\t\"std\"\n\n\ttokens \"gno.land/r/demo/grc20factory\"\n)\n\n// Get address of Disperse realm\nvar realmAddr = std.CurrentRealm().Addr()\n\n// DisperseUgnot parses receivers and amounts and sends out ugnot\n// The function will send out the coins to the addresses and return the leftover coins to the caller\n// if there are any to return\nfunc DisperseUgnot(addresses []std.Address, coins std.Coins) {\n\tcoinSent := std.OrigSend()\n\tcaller := std.PrevRealm().Addr()\n\tbanker := std.GetBanker(std.BankerTypeOrigSend)\n\n\tif len(addresses) != len(coins) {\n\t\tpanic(ErrNumAddrValMismatch)\n\t}\n\n\tfor _, coin := range coins {\n\t\tif coin.Amount \u003c= 0 {\n\t\t\tpanic(ErrNegativeCoinAmount)\n\t\t}\n\n\t\tif banker.GetCoins(realmAddr).AmountOf(coin.Denom) \u003c coin.Amount {\n\t\t\tpanic(ErrMismatchBetweenSentAndParams)\n\t\t}\n\t}\n\n\t// Send coins\n\tfor i, _ := range addresses {\n\t\tbanker.SendCoins(realmAddr, addresses[i], std.NewCoins(coins[i]))\n\t}\n\n\t// Return possible leftover coins\n\tfor _, coin := range coinSent {\n\t\tleftoverAmt := banker.GetCoins(realmAddr).AmountOf(coin.Denom)\n\t\tif leftoverAmt \u003e 0 {\n\t\t\tsend := std.Coins{std.NewCoin(coin.Denom, leftoverAmt)}\n\t\t\tbanker.SendCoins(realmAddr, caller, send)\n\t\t}\n\t}\n}\n\n// DisperseGRC20 disperses tokens to multiple addresses\n// Note that it is necessary to approve the realm to spend the tokens before calling this function\n// see the corresponding filetests for examples\nfunc DisperseGRC20(addresses []std.Address, amounts []uint64, symbols []string) {\n\tcaller := std.PrevRealm().Addr()\n\n\tif (len(addresses) != len(amounts)) || (len(amounts) != len(symbols)) {\n\t\tpanic(ErrArgLenAndSentLenMismatch)\n\t}\n\n\tfor i := 0; i \u003c len(addresses); i++ {\n\t\ttokens.TransferFrom(symbols[i], caller, addresses[i], amounts[i])\n\t}\n}\n\n// DisperseGRC20String receives a string of addresses and a string of tokens\n// and parses them to be used in DisperseGRC20\nfunc DisperseGRC20String(addresses string, tokens string) {\n\tparsedAddresses, err := parseAddresses(addresses)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tparsedAmounts, parsedSymbols, err := parseTokens(tokens)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tDisperseGRC20(parsedAddresses, parsedAmounts, parsedSymbols)\n}\n\n// DisperseUgnotString receives a string of addresses and a string of amounts\n// and parses them to be used in DisperseUgnot\nfunc DisperseUgnotString(addresses string, amounts string) {\n\tparsedAddresses, err := parseAddresses(addresses)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tparsedAmounts, err := parseAmounts(amounts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tcoins := make(std.Coins, len(parsedAmounts))\n\tfor i, amount := range parsedAmounts {\n\t\tcoins[i] = std.NewCoin(\"ugnot\", amount)\n\t}\n\n\tDisperseUgnot(parsedAddresses, coins)\n}\n"},{"name":"doc.gno","body":"// Package disperse provides methods to disperse coins or GRC20 tokens among multiple addresses.\n//\n// The disperse package is an implementation of an existing service that allows users to send coins or GRC20 tokens to multiple addresses\n// on the Ethereum blockchain.\n//\n// Usage:\n// To use disperse, you can either use `DisperseUgnot` to send coins or `DisperseGRC20` to send GRC20 tokens to multiple addresses.\n//\n// Example:\n// Dispersing 200 coins to two addresses:\n// - DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n// Dispersing 200 worth of a GRC20 token \"TEST\" to two addresses:\n// - DisperseGRC20String(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150TEST,50TEST\")\n//\n// Reference:\n// - [the original dispere app](https://disperse.app/)\n// - [the original disperse app on etherscan](https://etherscan.io/address/0xd152f549545093347a162dce210e7293f1452150#code)\n// - [the gno disperse web app](https://gno-disperse.netlify.app/)\npackage disperse // import \"gno.land/r/demo/disperse\"\n"},{"name":"errors.gno","body":"package disperse\n\nimport \"errors\"\n\nvar (\n\tErrNotEnoughCoin = errors.New(\"disperse: not enough coin sent in\")\n\tErrNumAddrValMismatch = errors.New(\"disperse: number of addresses and values to send doesn't match\")\n\tErrInvalidAddress = errors.New(\"disperse: invalid address\")\n\tErrNegativeCoinAmount = errors.New(\"disperse: coin amount cannot be negative\")\n\tErrMismatchBetweenSentAndParams = errors.New(\"disperse: mismatch between coins sent and params called\")\n\tErrArgLenAndSentLenMismatch = errors.New(\"disperse: mismatch between coins sent and args called\")\n)\n"},{"name":"util.gno","body":"package disperse\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"unicode\"\n)\n\nfunc parseAddresses(addresses string) ([]std.Address, error) {\n\tvar ret []std.Address\n\n\tfor _, str := range strings.Split(addresses, \",\") {\n\t\taddr := std.Address(str)\n\t\tif !addr.IsValid() {\n\t\t\treturn nil, ErrInvalidAddress\n\t\t}\n\n\t\tret = append(ret, addr)\n\t}\n\n\treturn ret, nil\n}\n\nfunc splitString(input string) (string, string) {\n\tvar pos int\n\tfor i, char := range input {\n\t\tif !unicode.IsDigit(char) {\n\t\t\tpos = i\n\t\t\tbreak\n\t\t}\n\t}\n\treturn input[:pos], input[pos:]\n}\n\nfunc parseTokens(tokens string) ([]uint64, []string, error) {\n\tvar amounts []uint64\n\tvar symbols []string\n\n\tfor _, token := range strings.Split(tokens, \",\") {\n\t\tamountStr, symbol := splitString(token)\n\t\tamount, _ := strconv.Atoi(amountStr)\n\t\tif amount \u003c 0 {\n\t\t\treturn nil, nil, ErrNegativeCoinAmount\n\t\t}\n\n\t\tamounts = append(amounts, uint64(amount))\n\t\tsymbols = append(symbols, symbol)\n\t}\n\n\treturn amounts, symbols, nil\n}\n\nfunc parseAmounts(amounts string) ([]int64, error) {\n\tvar ret []int64\n\n\tfor _, amt := range strings.Split(amounts, \",\") {\n\t\tamount, _ := strconv.Atoi(amt)\n\t\tif amount \u003c 0 {\n\t\t\treturn nil, ErrNegativeCoinAmount\n\t\t}\n\n\t\tret = append(ret, int64(amount))\n\t}\n\n\treturn ret, nil\n}\n"},{"name":"z_0_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 200ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\n\tstd.TestSetOrigPkgAddr(disperseAddr)\n\tstd.TestSetOrigCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\tbanker.SendCoins(mainaddr, disperseAddr, std.Coins{{\"ugnot\", 200}})\n\tdisperse.DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal)\n}\n\n// Output:\n// main before: 200000200ugnot\n// main after: 200000000ugnot\n"},{"name":"z_1_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\n\tstd.TestSetOrigPkgAddr(disperseAddr)\n\tstd.TestSetOrigCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\tbanker.SendCoins(mainaddr, disperseAddr, std.Coins{{\"ugnot\", 300}})\n\tdisperse.DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal)\n}\n\n// Output:\n// main before: 200000300ugnot\n// main after: 200000100ugnot\n"},{"name":"z_2_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\n\tstd.TestSetOrigPkgAddr(disperseAddr)\n\tstd.TestSetOrigCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\tbanker.SendCoins(mainaddr, disperseAddr, std.Coins{{\"ugnot\", 100}})\n\tdisperse.DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n}\n\n// Error:\n// disperse: mismatch between coins sent and params called\n"},{"name":"z_3_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n\ttokens \"gno.land/r/demo/grc20factory\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\tbeneficiary1 := std.Address(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0\")\n\tbeneficiary2 := std.Address(\"g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\")\n\n\tstd.TestSetOrigPkgAddr(disperseAddr)\n\tstd.TestSetOrigCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\ttokens.New(\"test\", \"TEST\", 4, 0, 0)\n\ttokens.Mint(\"TEST\", mainaddr, 200)\n\n\tmainbal := tokens.BalanceOf(\"TEST\", mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\ttokens.Approve(\"TEST\", disperseAddr, 200)\n\n\tdisperse.DisperseGRC20String(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150TEST,50TEST\")\n\n\tmainbal = tokens.BalanceOf(\"TEST\", mainaddr)\n\tprintln(\"main after:\", mainbal)\n\tben1bal := tokens.BalanceOf(\"TEST\", beneficiary1)\n\tprintln(\"beneficiary1:\", ben1bal)\n\tben2bal := tokens.BalanceOf(\"TEST\", beneficiary2)\n\tprintln(\"beneficiary2:\", ben2bal)\n}\n\n// Output:\n// main before: 200\n// main after: 0\n// beneficiary1: 150\n// beneficiary2: 50\n"},{"name":"z_4_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n\ttokens \"gno.land/r/demo/grc20factory\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\tbeneficiary1 := std.Address(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0\")\n\tbeneficiary2 := std.Address(\"g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\")\n\n\tstd.TestSetOrigPkgAddr(disperseAddr)\n\tstd.TestSetOrigCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\ttokens.New(\"test1\", \"TEST1\", 4, 0, 0)\n\ttokens.Mint(\"TEST1\", mainaddr, 200)\n\ttokens.New(\"test2\", \"TEST2\", 4, 0, 0)\n\ttokens.Mint(\"TEST2\", mainaddr, 200)\n\n\tmainbal := tokens.BalanceOf(\"TEST1\", mainaddr) + tokens.BalanceOf(\"TEST2\", mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\ttokens.Approve(\"TEST1\", disperseAddr, 200)\n\ttokens.Approve(\"TEST2\", disperseAddr, 200)\n\n\tdisperse.DisperseGRC20String(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"200TEST1,200TEST2\")\n\n\tmainbal = tokens.BalanceOf(\"TEST1\", mainaddr) + tokens.BalanceOf(\"TEST2\", mainaddr)\n\tprintln(\"main after:\", mainbal)\n\tben1bal := tokens.BalanceOf(\"TEST1\", beneficiary1) + tokens.BalanceOf(\"TEST2\", beneficiary1)\n\tprintln(\"beneficiary1:\", ben1bal)\n\tben2bal := tokens.BalanceOf(\"TEST1\", beneficiary2) + tokens.BalanceOf(\"TEST2\", beneficiary2)\n\tprintln(\"beneficiary2:\", ben2bal)\n}\n\n// Output:\n// main before: 400\n// main after: 0\n// beneficiary1: 200\n// beneficiary2: 200\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":"echo","path":"gno.land/r/demo/echo","files":[{"name":"echo.gno","body":"package echo\n\n/*\n * This realm echoes the `path` argument it received.\n * Can be used by developers as a simple endpoint to test\n * forbidden characters, for pentesting or simply to\n * test it works.\n *\n * See also r/demo/print (to print various thing like user address)\n */\nfunc Render(path string) string {\n\treturn path\n}\n"},{"name":"echo_test.gno","body":"package echo\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc Test(t *testing.T) {\n\turequire.Equal(t, \"aa\", Render(\"aa\"))\n\turequire.Equal(t, \"\", Render(\"\"))\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":"event","path":"gno.land/r/demo/event","files":[{"name":"event.gno","body":"package event\n\nimport (\n\t\"std\"\n)\n\nfunc Emit(value string) {\n\tstd.Emit(\"TAG\", \"key\", value)\n}\n"},{"name":"z1_filetest.gno","body":"package main\n\nimport \"gno.land/r/demo/event\"\n\nfunc main() {\n\tevent.Emit(\"foo\")\n\tevent.Emit(\"bar\")\n}\n\n// Events:\n// [\n// {\n// \"type\": \"TAG\",\n// \"attrs\": [\n// {\n// \"key\": \"key\",\n// \"value\": \"foo\"\n// }\n// ],\n// \"pkg_path\": \"gno.land/r/demo/event\",\n// \"func\": \"Emit\"\n// },\n// {\n// \"type\": \"TAG\",\n// \"attrs\": [\n// {\n// \"key\": \"key\",\n// \"value\": \"bar\"\n// }\n// ],\n// \"pkg_path\": \"gno.land/r/demo/event\",\n// \"func\": \"Emit\"\n// }\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":"foo1155","path":"gno.land/r/demo/foo1155","files":[{"name":"foo1155.gno","body":"package foo1155\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/grc/grc1155\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/r/demo/users\"\n\n\tpusers \"gno.land/p/demo/users\"\n)\n\nvar (\n\tdummyURI = \"ipfs://xyz\"\n\tadmin std.Address = \"g10x5phu0k6p64cwrhfpsc8tk43st9kug6wft530\"\n\tfoo = grc1155.NewBasicGRC1155Token(dummyURI)\n)\n\nfunc init() {\n\tmintGRC1155Token(admin) // @administrator (10)\n}\n\nfunc mintGRC1155Token(owner std.Address) {\n\tfor i := 1; i \u003c= 10; i++ {\n\t\ttid := grc1155.TokenID(ufmt.Sprintf(\"%d\", i))\n\t\tfoo.SafeMint(owner, tid, 100)\n\t}\n}\n\n// Getters\n\nfunc BalanceOf(user pusers.AddressOrName, tid grc1155.TokenID) uint64 {\n\tbalance, err := foo.BalanceOf(users.Resolve(user), tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn balance\n}\n\nfunc BalanceOfBatch(ul []pusers.AddressOrName, batch []grc1155.TokenID) []uint64 {\n\tvar usersResolved []std.Address\n\n\tfor i := 0; i \u003c len(ul); i++ {\n\t\tusersResolved[i] = users.Resolve(ul[i])\n\t}\n\tbalanceBatch, err := foo.BalanceOfBatch(usersResolved, batch)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn balanceBatch\n}\n\nfunc IsApprovedForAll(owner, user pusers.AddressOrName) bool {\n\treturn foo.IsApprovedForAll(users.Resolve(owner), users.Resolve(user))\n}\n\n// Setters\n\nfunc SetApprovalForAll(user pusers.AddressOrName, approved bool) {\n\terr := foo.SetApprovalForAll(users.Resolve(user), approved)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc TransferFrom(from, to pusers.AddressOrName, tid grc1155.TokenID, amount uint64) {\n\terr := foo.SafeTransferFrom(users.Resolve(from), users.Resolve(to), tid, amount)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc BatchTransferFrom(from, to pusers.AddressOrName, batch []grc1155.TokenID, amounts []uint64) {\n\terr := foo.SafeBatchTransferFrom(users.Resolve(from), users.Resolve(to), batch, amounts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// Admin\n\nfunc Mint(to pusers.AddressOrName, tid grc1155.TokenID, amount uint64) {\n\tcaller := std.GetOrigCaller()\n\tassertIsAdmin(caller)\n\terr := foo.SafeMint(users.Resolve(to), tid, amount)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc MintBatch(to pusers.AddressOrName, batch []grc1155.TokenID, amounts []uint64) {\n\tcaller := std.GetOrigCaller()\n\tassertIsAdmin(caller)\n\terr := foo.SafeBatchMint(users.Resolve(to), batch, amounts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc Burn(from pusers.AddressOrName, tid grc1155.TokenID, amount uint64) {\n\tcaller := std.GetOrigCaller()\n\tassertIsAdmin(caller)\n\terr := foo.Burn(users.Resolve(from), tid, amount)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc BurnBatch(from pusers.AddressOrName, batch []grc1155.TokenID, amounts []uint64) {\n\tcaller := std.GetOrigCaller()\n\tassertIsAdmin(caller)\n\terr := foo.BatchBurn(users.Resolve(from), batch, amounts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// Render\n\nfunc Render(path string) string {\n\tswitch {\n\tcase path == \"\":\n\t\treturn foo.RenderHome()\n\tdefault:\n\t\treturn \"404\\n\"\n\t}\n}\n\n// Util\n\nfunc assertIsAdmin(address std.Address) {\n\tif address != admin {\n\t\tpanic(\"restricted access\")\n\t}\n}\n"},{"name":"foo1155_test.gno","body":"package foo1155\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/grc/grc1155\"\n\t\"gno.land/p/demo/users\"\n)\n\nfunc TestFoo721(t *testing.T) {\n\tadmin := users.AddressOrName(\"g10x5phu0k6p64cwrhfpsc8tk43st9kug6wft530\")\n\tbob := users.AddressOrName(\"g1ze6et22ces5atv79y4xh38s4kuraey4y2fr6tw\")\n\ttid1 := grc1155.TokenID(\"1\")\n\ttid2 := grc1155.TokenID(\"2\")\n\n\tfor i, tc := range []struct {\n\t\tname string\n\t\texpected interface{}\n\t\tfn func() interface{}\n\t}{\n\t\t{\"BalanceOf(admin, tid1)\", uint64(100), func() interface{} { return BalanceOf(admin, tid1) }},\n\t\t{\"BalanceOf(bob, tid1)\", uint64(0), func() interface{} { return BalanceOf(bob, tid1) }},\n\t\t{\"IsApprovedForAll(admin, bob)\", false, func() interface{} { return IsApprovedForAll(admin, bob) }},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := tc.fn()\n\t\t\tif tc.expected != got {\n\t\t\t\tt.Errorf(\"expected: %v got: %v\", tc.expected, got)\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":"foo1155","path":"gno.land/r/demo/foo1155","files":[{"name":"foo1155.gno","body":"package foo1155\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/grc/grc1155\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/r/demo/users\"\n\n\tpusers \"gno.land/p/demo/users\"\n)\n\nvar (\n\tdummyURI = \"ipfs://xyz\"\n\tadmin std.Address = \"g10x5phu0k6p64cwrhfpsc8tk43st9kug6wft530\"\n\tfoo = grc1155.NewBasicGRC1155Token(dummyURI)\n)\n\nfunc init() {\n\tmintGRC1155Token(admin) // @administrator (10)\n}\n\nfunc mintGRC1155Token(owner std.Address) {\n\tfor i := 1; i \u003c= 10; i++ {\n\t\ttid := grc1155.TokenID(ufmt.Sprintf(\"%d\", i))\n\t\tfoo.SafeMint(owner, tid, 100)\n\t}\n}\n\n// Getters\n\nfunc BalanceOf(user pusers.AddressOrName, tid grc1155.TokenID) uint64 {\n\tbalance, err := foo.BalanceOf(users.Resolve(user), tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn balance\n}\n\nfunc BalanceOfBatch(ul []pusers.AddressOrName, batch []grc1155.TokenID) []uint64 {\n\tvar usersResolved []std.Address\n\n\tfor i := 0; i \u003c len(ul); i++ {\n\t\tusersResolved[i] = users.Resolve(ul[i])\n\t}\n\tbalanceBatch, err := foo.BalanceOfBatch(usersResolved, batch)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn balanceBatch\n}\n\nfunc IsApprovedForAll(owner, user pusers.AddressOrName) bool {\n\treturn foo.IsApprovedForAll(users.Resolve(owner), users.Resolve(user))\n}\n\n// Setters\n\nfunc SetApprovalForAll(user pusers.AddressOrName, approved bool) {\n\terr := foo.SetApprovalForAll(users.Resolve(user), approved)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc TransferFrom(from, to pusers.AddressOrName, tid grc1155.TokenID, amount uint64) {\n\terr := foo.SafeTransferFrom(users.Resolve(from), users.Resolve(to), tid, amount)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc BatchTransferFrom(from, to pusers.AddressOrName, batch []grc1155.TokenID, amounts []uint64) {\n\terr := foo.SafeBatchTransferFrom(users.Resolve(from), users.Resolve(to), batch, amounts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// Admin\n\nfunc Mint(to pusers.AddressOrName, tid grc1155.TokenID, amount uint64) {\n\tcaller := std.OrigCaller()\n\tassertIsAdmin(caller)\n\terr := foo.SafeMint(users.Resolve(to), tid, amount)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc MintBatch(to pusers.AddressOrName, batch []grc1155.TokenID, amounts []uint64) {\n\tcaller := std.OrigCaller()\n\tassertIsAdmin(caller)\n\terr := foo.SafeBatchMint(users.Resolve(to), batch, amounts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc Burn(from pusers.AddressOrName, tid grc1155.TokenID, amount uint64) {\n\tcaller := std.OrigCaller()\n\tassertIsAdmin(caller)\n\terr := foo.Burn(users.Resolve(from), tid, amount)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc BurnBatch(from pusers.AddressOrName, batch []grc1155.TokenID, amounts []uint64) {\n\tcaller := std.OrigCaller()\n\tassertIsAdmin(caller)\n\terr := foo.BatchBurn(users.Resolve(from), batch, amounts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// Render\n\nfunc Render(path string) string {\n\tswitch {\n\tcase path == \"\":\n\t\treturn foo.RenderHome()\n\tdefault:\n\t\treturn \"404\\n\"\n\t}\n}\n\n// Util\n\nfunc assertIsAdmin(address std.Address) {\n\tif address != admin {\n\t\tpanic(\"restricted access\")\n\t}\n}\n"},{"name":"foo1155_test.gno","body":"package foo1155\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/grc/grc1155\"\n\t\"gno.land/p/demo/users\"\n)\n\nfunc TestFoo721(t *testing.T) {\n\tadmin := users.AddressOrName(\"g10x5phu0k6p64cwrhfpsc8tk43st9kug6wft530\")\n\tbob := users.AddressOrName(\"g1ze6et22ces5atv79y4xh38s4kuraey4y2fr6tw\")\n\ttid1 := grc1155.TokenID(\"1\")\n\ttid2 := grc1155.TokenID(\"2\")\n\n\tfor i, tc := range []struct {\n\t\tname string\n\t\texpected interface{}\n\t\tfn func() interface{}\n\t}{\n\t\t{\"BalanceOf(admin, tid1)\", uint64(100), func() interface{} { return BalanceOf(admin, tid1) }},\n\t\t{\"BalanceOf(bob, tid1)\", uint64(0), func() interface{} { return BalanceOf(bob, tid1) }},\n\t\t{\"IsApprovedForAll(admin, bob)\", false, func() interface{} { return IsApprovedForAll(admin, bob) }},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := tc.fn()\n\t\t\tif tc.expected != got {\n\t\t\t\tt.Errorf(\"expected: %v got: %v\", tc.expected, got)\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/foo20","files":[{"name":"foo20.gno","body":"// foo20 is a GRC20 token contract where all the GRC20 methods are proxified\n// 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/users\"\n)\n\nvar (\n\tbanker *grc20.Banker\n\tadmin *ownable.Ownable\n\ttoken grc20.Token\n)\n\nfunc init() {\n\tadmin = ownable.NewWithAddress(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\") // @manfred\n\tbanker = grc20.NewBanker(\"Foo\", \"FOO\", 4)\n\tbanker.Mint(admin.Owner(), 1000000*10000) // @administrator (1M)\n\ttoken = banker.Token()\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\tcheckErr(token.Transfer(toAddr, amount))\n}\n\nfunc Approve(spender pusers.AddressOrName, amount uint64) {\n\tspenderAddr := users.Resolve(spender)\n\tcheckErr(token.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(token.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(banker.Mint(caller, amount))\n}\n\nfunc Mint(to pusers.AddressOrName, amount uint64) {\n\tadmin.AssertCallerIsOwner()\n\ttoAddr := users.Resolve(to)\n\tcheckErr(banker.Mint(toAddr, amount))\n}\n\nfunc Burn(from pusers.AddressOrName, amount uint64) {\n\tadmin.AssertCallerIsOwner()\n\tfromAddr := users.Resolve(from)\n\tcheckErr(banker.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 banker.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 := banker.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(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\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(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\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\tstd.TestSetOrigCaller(users.Resolve(admin))\n\t{\n\t\ttests := []test{\n\t\t\t{\"Transfer(admin, 1)\", \"cannot send transfer to self\", func() { Transfer(admin, 1) }},\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":"foo721","path":"gno.land/r/demo/foo721","files":[{"name":"foo721.gno","body":"package foo721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/grc/grc721\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/r/demo/users\"\n\n\tpusers \"gno.land/p/demo/users\"\n)\n\nvar (\n\tadmin std.Address = \"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\"\n\tfoo = grc721.NewBasicNFT(\"FooNFT\", \"FNFT\")\n)\n\nfunc init() {\n\tmintNNFT(admin, 10) // @administrator (10)\n\tmintNNFT(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\", 5) // @hariom (5)\n}\n\nfunc mintNNFT(owner std.Address, n uint64) {\n\tcount := foo.TokenCount()\n\tfor i := count; i \u003c count+n; i++ {\n\t\ttid := grc721.TokenID(ufmt.Sprintf(\"%d\", i))\n\t\tfoo.Mint(owner, tid)\n\t}\n}\n\n// Getters\n\nfunc BalanceOf(user pusers.AddressOrName) uint64 {\n\tbalance, err := foo.BalanceOf(users.Resolve(user))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn balance\n}\n\nfunc OwnerOf(tid grc721.TokenID) std.Address {\n\towner, err := foo.OwnerOf(tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn owner\n}\n\nfunc IsApprovedForAll(owner, user pusers.AddressOrName) bool {\n\treturn foo.IsApprovedForAll(users.Resolve(owner), users.Resolve(user))\n}\n\nfunc GetApproved(tid grc721.TokenID) std.Address {\n\taddr, err := foo.GetApproved(tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn addr\n}\n\n// Setters\n\nfunc Approve(user pusers.AddressOrName, tid grc721.TokenID) {\n\terr := foo.Approve(users.Resolve(user), tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc SetApprovalForAll(user pusers.AddressOrName, approved bool) {\n\terr := foo.SetApprovalForAll(users.Resolve(user), approved)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc TransferFrom(from, to pusers.AddressOrName, tid grc721.TokenID) {\n\terr := foo.TransferFrom(users.Resolve(from), users.Resolve(to), tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// Admin\n\nfunc Mint(to pusers.AddressOrName, tid grc721.TokenID) {\n\tcaller := std.PrevRealm().Addr()\n\tassertIsAdmin(caller)\n\terr := foo.Mint(users.Resolve(to), tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc Burn(tid grc721.TokenID) {\n\tcaller := std.PrevRealm().Addr()\n\tassertIsAdmin(caller)\n\terr := foo.Burn(tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// Render\n\nfunc Render(path string) string {\n\tswitch {\n\tcase path == \"\":\n\t\treturn foo.RenderHome()\n\tdefault:\n\t\treturn \"404\\n\"\n\t}\n}\n\n// Util\n\nfunc assertIsAdmin(address std.Address) {\n\tif address != admin {\n\t\tpanic(\"restricted access\")\n\t}\n}\n"},{"name":"foo721_test.gno","body":"package foo721\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/grc/grc721\"\n\t\"gno.land/r/demo/users\"\n\n\tpusers \"gno.land/p/demo/users\"\n)\n\nfunc TestFoo721(t *testing.T) {\n\tadmin := pusers.AddressOrName(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\thariom := pusers.AddressOrName(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tfor i, tc := range []struct {\n\t\tname string\n\t\texpected interface{}\n\t\tfn func() interface{}\n\t}{\n\t\t{\"BalanceOf(admin)\", uint64(10), func() interface{} { return BalanceOf(admin) }},\n\t\t{\"BalanceOf(hariom)\", uint64(5), func() interface{} { return BalanceOf(hariom) }},\n\t\t{\"OwnerOf(0)\", users.Resolve(admin), func() interface{} { return OwnerOf(grc721.TokenID(\"0\")) }},\n\t\t{\"IsApprovedForAll(admin, hariom)\", false, func() interface{} { return IsApprovedForAll(admin, hariom) }},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := tc.fn()\n\t\t\tif tc.expected != got {\n\t\t\t\tt.Errorf(\"expected: %v got: %v\", tc.expected, got)\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":"dice_roller","path":"gno.land/r/demo/games/dice_roller","files":[{"name":"dice_roller.gno","body":"package dice_roller\n\nimport (\n\t\"errors\"\n\t\"math/rand\"\n\t\"sort\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/entropy\"\n\t\"gno.land/p/demo/seqid\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/r/demo/users\"\n)\n\ntype (\n\t// game represents a Dice Roller game between two players\n\tgame struct {\n\t\tplayer1, player2 std.Address\n\t\troll1, roll2 int\n\t}\n\n\t// player holds the information about each player including their stats\n\tplayer struct {\n\t\taddr std.Address\n\t\twins, losses, draws, points int\n\t}\n\n\t// leaderBoard is a slice of players, used to sort players by rank\n\tleaderBoard []player\n)\n\nconst (\n\t// Constants to represent game result outcomes\n\tongoing = iota\n\twin\n\tdraw\n\tloss\n)\n\nvar (\n\tgames avl.Tree // AVL tree for storing game states\n\tgameId seqid.ID // Sequence ID for games\n\n\tplayers avl.Tree // AVL tree for storing player data\n\n\tseed = uint64(entropy.New().Seed())\n\tr = rand.New(rand.NewPCG(seed, 0xdeadbeef))\n)\n\n// rollDice generates a random dice roll between 1 and 6\nfunc rollDice() int {\n\treturn r.IntN(6) + 1\n}\n\n// NewGame initializes a new game with the provided opponent's address\nfunc NewGame(addr std.Address) int {\n\tif !addr.IsValid() {\n\t\tpanic(\"invalid opponent's address\")\n\t}\n\n\tgames.Set(gameId.Next().String(), \u0026game{\n\t\tplayer1: std.PrevRealm().Addr(),\n\t\tplayer2: addr,\n\t})\n\n\treturn int(gameId)\n}\n\n// Play allows a player to roll the dice and updates the game state accordingly\nfunc Play(idx int) int {\n\tg, err := getGame(idx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\troll := rollDice() // Random the player's dice roll\n\n\t// Play the game and update the player's roll\n\tif err := g.play(std.PrevRealm().Addr(), roll); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// If both players have rolled, update the results and leaderboard\n\tif g.isFinished() {\n\t\t// If the player is playing against themselves, no points are awarded\n\t\tif g.player1 == g.player2 {\n\t\t\treturn roll\n\t\t}\n\n\t\tplayer1 := getPlayer(g.player1)\n\t\tplayer2 := getPlayer(g.player2)\n\n\t\tif g.roll1 \u003e g.roll2 {\n\t\t\tplayer1.updateStats(win)\n\t\t\tplayer2.updateStats(loss)\n\t\t} else if g.roll2 \u003e g.roll1 {\n\t\t\tplayer2.updateStats(win)\n\t\t\tplayer1.updateStats(loss)\n\t\t} else {\n\t\t\tplayer1.updateStats(draw)\n\t\t\tplayer2.updateStats(draw)\n\t\t}\n\t}\n\n\treturn roll\n}\n\n// play processes a player's roll and updates their score\nfunc (g *game) play(player std.Address, roll int) error {\n\tif player != g.player1 \u0026\u0026 player != g.player2 {\n\t\treturn errors.New(\"invalid player\")\n\t}\n\n\tif g.isFinished() {\n\t\treturn errors.New(\"game over\")\n\t}\n\n\tif player == g.player1 \u0026\u0026 g.roll1 == 0 {\n\t\tg.roll1 = roll\n\t\treturn nil\n\t}\n\n\tif player == g.player2 \u0026\u0026 g.roll2 == 0 {\n\t\tg.roll2 = roll\n\t\treturn nil\n\t}\n\n\treturn errors.New(\"already played\")\n}\n\n// isFinished checks if the game has ended\nfunc (g *game) isFinished() bool {\n\treturn g.roll1 != 0 \u0026\u0026 g.roll2 != 0\n}\n\n// checkResult returns the game status as a formatted string\nfunc (g *game) status() string {\n\tif !g.isFinished() {\n\t\treturn resultIcon(ongoing) + \" Game still in progress\"\n\t}\n\n\tif g.roll1 \u003e g.roll2 {\n\t\treturn resultIcon(win) + \" Player1 Wins !\"\n\t} else if g.roll2 \u003e g.roll1 {\n\t\treturn resultIcon(win) + \" Player2 Wins !\"\n\t} else {\n\t\treturn resultIcon(draw) + \" It's a Draw !\"\n\t}\n}\n\n// Render provides a summary of the current state of games and leader board\nfunc Render(path string) string {\n\tvar sb strings.Builder\n\n\tsb.WriteString(`# 🎲 **Dice Roller Game**\n\nWelcome to Dice Roller! Challenge your friends to a simple yet exciting dice rolling game. Roll the dice and see who gets the highest score !\n\n---\n\n## **How to Play**:\n1. **Create a game**: Challenge an opponent using [NewGame](./dice_roller$help\u0026func=NewGame)\n2. **Roll the dice**: Play your turn by rolling a dice using [Play](./dice_roller$help\u0026func=Play)\n\n---\n\n## **Scoring Rules**:\n- **Win** 🏆: +3 points\n- **Draw** 🤝: +1 point each\n- **Lose** ❌: No points\n- **Playing against yourself**: No points or stats changes for you\n\n---\n\n## **Recent Games**:\nBelow are the results from the most recent games. Up to 10 recent games are displayed\n\n| Game | Player 1 | 🎲 Roll 1 | Player 2 | 🎲 Roll 2 | 🏆 Winner |\n|------|----------|-----------|----------|-----------|-----------|\n`)\n\n\tmaxGames := 10\n\tfor n := int(gameId); n \u003e 0 \u0026\u0026 int(gameId)-n \u003c maxGames; n-- {\n\t\tg, err := getGame(n)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tsb.WriteString(strconv.Itoa(n) + \" | \" +\n\t\t\t\"\u003cspan title=\\\"\" + string(g.player1) + \"\\\"\u003e\" + shortName(g.player1) + \"\u003c/span\u003e\" + \" | \" + diceIcon(g.roll1) + \" | \" +\n\t\t\t\"\u003cspan title=\\\"\" + string(g.player2) + \"\\\"\u003e\" + shortName(g.player2) + \"\u003c/span\u003e\" + \" | \" + diceIcon(g.roll2) + \" | \" +\n\t\t\tg.status() + \"\\n\")\n\t}\n\n\tsb.WriteString(`\n---\n\n## **Leaderboard**:\nThe top players are ranked by performance. Games played against oneself are not counted in the leaderboard\n\n| Rank | Player | Wins | Losses | Draws | Points |\n|------|-----------------------|------|--------|-------|--------|\n`)\n\n\tfor i, player := range getLeaderBoard() {\n\t\tsb.WriteString(ufmt.Sprintf(\"| %s | \u003cspan title=\\\"\"+string(player.addr)+\"\\\"\u003e**%s**\u003c/span\u003e | %d | %d | %d | %d |\\n\",\n\t\t\trankIcon(i+1),\n\t\t\tshortName(player.addr),\n\t\t\tplayer.wins,\n\t\t\tplayer.losses,\n\t\t\tplayer.draws,\n\t\t\tplayer.points,\n\t\t))\n\t}\n\n\tsb.WriteString(\"\\n---\\n**Good luck and have fun !** 🎉\")\n\treturn sb.String()\n}\n\n// shortName returns a shortened name for the given address\nfunc shortName(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user != nil {\n\t\treturn user.Name\n\t}\n\tif len(addr) \u003c 10 {\n\t\treturn string(addr)\n\t}\n\treturn string(addr)[:10] + \"...\"\n}\n\n// getGame retrieves the game state by its ID\nfunc getGame(idx int) (*game, error) {\n\tv, ok := games.Get(seqid.ID(idx).String())\n\tif !ok {\n\t\treturn nil, errors.New(\"game not found\")\n\t}\n\treturn v.(*game), nil\n}\n\n// updateResult updates the player's stats and points based on the game outcome\nfunc (p *player) updateStats(result int) {\n\tswitch result {\n\tcase win:\n\t\tp.wins++\n\t\tp.points += 3\n\tcase loss:\n\t\tp.losses++\n\tcase draw:\n\t\tp.draws++\n\t\tp.points++\n\t}\n}\n\n// getPlayer retrieves a player or initializes a new one if they don't exist\nfunc getPlayer(addr std.Address) *player {\n\tv, ok := players.Get(addr.String())\n\tif !ok {\n\t\tplayer := \u0026player{\n\t\t\taddr: addr,\n\t\t}\n\t\tplayers.Set(addr.String(), player)\n\t\treturn player\n\t}\n\n\treturn v.(*player)\n}\n\n// getLeaderBoard generates a leaderboard sorted by points\nfunc getLeaderBoard() leaderBoard {\n\tboard := leaderBoard{}\n\tplayers.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tplayer := value.(*player)\n\t\tboard = append(board, *player)\n\t\treturn false\n\t})\n\n\tsort.Sort(board)\n\n\treturn board\n}\n\n// Methods for sorting the leaderboard\nfunc (r leaderBoard) Len() int {\n\treturn len(r)\n}\n\nfunc (r leaderBoard) Less(i, j int) bool {\n\tif r[i].points != r[j].points {\n\t\treturn r[i].points \u003e r[j].points\n\t}\n\n\tif r[i].wins != r[j].wins {\n\t\treturn r[i].wins \u003e r[j].wins\n\t}\n\n\tif r[i].draws != r[j].draws {\n\t\treturn r[i].draws \u003e r[j].draws\n\t}\n\n\treturn false\n}\n\nfunc (r leaderBoard) Swap(i, j int) {\n\tr[i], r[j] = r[j], r[i]\n}\n"},{"name":"dice_roller_test.gno","body":"package dice_roller\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/seqid\"\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nvar (\n\tplayer1 = testutils.TestAddress(\"alice\")\n\tplayer2 = testutils.TestAddress(\"bob\")\n\tunknownPlayer = testutils.TestAddress(\"unknown\")\n)\n\n// resetGameState resets the game state for testing\nfunc resetGameState() {\n\tgames = avl.Tree{}\n\tgameId = seqid.ID(0)\n\tplayers = avl.Tree{}\n}\n\n// TestNewGame tests the initialization of a new game\nfunc TestNewGame(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOrigCaller(player1)\n\tgameID := NewGame(player2)\n\n\t// Verify that the game has been correctly initialized\n\tg, err := getGame(gameID)\n\turequire.NoError(t, err)\n\turequire.Equal(t, player1.String(), g.player1.String())\n\turequire.Equal(t, player2.String(), g.player2.String())\n\turequire.Equal(t, 0, g.roll1)\n\turequire.Equal(t, 0, g.roll2)\n}\n\n// TestPlay tests the dice rolling functionality for both players\nfunc TestPlay(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOrigCaller(player1)\n\tgameID := NewGame(player2)\n\n\tg, err := getGame(gameID)\n\turequire.NoError(t, err)\n\n\t// Simulate rolling dice for player 1\n\troll1 := Play(gameID)\n\n\t// Verify player 1's roll\n\turequire.NotEqual(t, 0, g.roll1)\n\turequire.Equal(t, g.roll1, roll1)\n\turequire.Equal(t, 0, g.roll2) // Player 2 hasn't rolled yet\n\n\t// Simulate rolling dice for player 2\n\tstd.TestSetOrigCaller(player2)\n\troll2 := Play(gameID)\n\n\t// Verify player 2's roll\n\turequire.NotEqual(t, 0, g.roll2)\n\turequire.Equal(t, g.roll1, roll1)\n\turequire.Equal(t, g.roll2, roll2)\n}\n\n// TestPlayAgainstSelf tests the scenario where a player plays against themselves\nfunc TestPlayAgainstSelf(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOrigCaller(player1)\n\tgameID := NewGame(player1)\n\n\t// Simulate rolling dice twice by the same player\n\troll1 := Play(gameID)\n\troll2 := Play(gameID)\n\n\tg, err := getGame(gameID)\n\turequire.NoError(t, err)\n\turequire.Equal(t, g.roll1, roll1)\n\turequire.Equal(t, g.roll2, roll2)\n}\n\n// TestPlayInvalidPlayer tests the scenario where an invalid player tries to play\nfunc TestPlayInvalidPlayer(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOrigCaller(player1)\n\tgameID := NewGame(player1)\n\n\t// Attempt to play as an invalid player\n\tstd.TestSetOrigCaller(unknownPlayer)\n\turequire.PanicsWithMessage(t, \"invalid player\", func() {\n\t\tPlay(gameID)\n\t})\n}\n\n// TestPlayAlreadyPlayed tests the scenario where a player tries to play again after already playing\nfunc TestPlayAlreadyPlayed(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOrigCaller(player1)\n\tgameID := NewGame(player2)\n\n\t// Player 1 rolls\n\tPlay(gameID)\n\n\t// Player 1 tries to roll again\n\turequire.PanicsWithMessage(t, \"already played\", func() {\n\t\tPlay(gameID)\n\t})\n}\n\n// TestPlayBeyondGameEnd tests that playing after both players have finished their rolls fails\nfunc TestPlayBeyondGameEnd(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOrigCaller(player1)\n\tgameID := NewGame(player2)\n\n\t// Play for both players\n\tstd.TestSetOrigCaller(player1)\n\tPlay(gameID)\n\tstd.TestSetOrigCaller(player2)\n\tPlay(gameID)\n\n\t// Check if the game is over\n\tg, err := getGame(gameID)\n\turequire.NoError(t, err)\n\n\t// Attempt to play more should fail\n\tstd.TestSetOrigCaller(player1)\n\turequire.PanicsWithMessage(t, \"game over\", func() {\n\t\tPlay(gameID)\n\t})\n}\n"},{"name":"icon.gno","body":"package dice_roller\n\nimport (\n\t\"strconv\"\n)\n\n// diceIcon returns an icon of the dice roll\nfunc diceIcon(roll int) string {\n\tswitch roll {\n\tcase 1:\n\t\treturn \"🎲1\"\n\tcase 2:\n\t\treturn \"🎲2\"\n\tcase 3:\n\t\treturn \"🎲3\"\n\tcase 4:\n\t\treturn \"🎲4\"\n\tcase 5:\n\t\treturn \"🎲5\"\n\tcase 6:\n\t\treturn \"🎲6\"\n\tdefault:\n\t\treturn \"❓\"\n\t}\n}\n\n// resultIcon returns the icon representing the result of a game\nfunc resultIcon(result int) string {\n\tswitch result {\n\tcase ongoing:\n\t\treturn \"🔄\"\n\tcase win:\n\t\treturn \"🏆\"\n\tcase loss:\n\t\treturn \"❌\"\n\tcase draw:\n\t\treturn \"🤝\"\n\tdefault:\n\t\treturn \"❓\"\n\t}\n}\n\n// rankIcon returns the icon for a player's rank\nfunc rankIcon(rank int) string {\n\tswitch rank {\n\tcase 1:\n\t\treturn \"🥇\"\n\tcase 2:\n\t\treturn \"🥈\"\n\tcase 3:\n\t\treturn \"🥉\"\n\tdefault:\n\t\treturn strconv.Itoa(rank)\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":"shifumi","path":"gno.land/r/demo/games/shifumi","files":[{"name":"shifumi.gno","body":"package shifumi\n\nimport (\n\t\"errors\"\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/seqid\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nconst (\n\tempty = iota\n\trock\n\tpaper\n\tscissors\n\tlast\n)\n\ntype game struct {\n\tplayer1, player2 std.Address // shifumi is a 2 players game\n\tmove1, move2 int // can be empty, rock, paper, or scissors\n}\n\nvar games avl.Tree\nvar id seqid.ID\n\nfunc (g *game) play(player std.Address, move int) error {\n\tif !(move \u003e empty \u0026\u0026 move \u003c last) {\n\t\treturn errors.New(\"invalid move\")\n\t}\n\tif player != g.player1 \u0026\u0026 player != g.player2 {\n\t\treturn errors.New(\"invalid player\")\n\t}\n\tif player == g.player1 \u0026\u0026 g.move1 == empty {\n\t\tg.move1 = move\n\t\treturn nil\n\t}\n\tif player == g.player2 \u0026\u0026 g.move2 == empty {\n\t\tg.move2 = move\n\t\treturn nil\n\t}\n\treturn errors.New(\"already played\")\n}\n\nfunc (g *game) winner() int {\n\tif g.move1 == empty || g.move2 == empty {\n\t\treturn -1\n\t}\n\tif g.move1 == g.move2 {\n\t\treturn 0\n\t}\n\tif g.move1 == rock \u0026\u0026 g.move2 == scissors ||\n\t\tg.move1 == paper \u0026\u0026 g.move2 == rock ||\n\t\tg.move1 == scissors \u0026\u0026 g.move2 == paper {\n\t\treturn 1\n\t}\n\treturn 2\n}\n\n// NewGame creates a new game where player1 is the caller and player2 the argument.\n// A new game index is returned.\nfunc NewGame(player std.Address) int {\n\tgames.Set(id.Next().String(), \u0026game{player1: std.PrevRealm().Addr(), player2: player})\n\treturn int(id)\n}\n\n// Play executes a move for the game at index idx, where move can be:\n// 1 (rock), 2 (paper), 3 (scissors).\nfunc Play(idx, move int) {\n\tv, ok := games.Get(seqid.ID(idx).String())\n\tif !ok {\n\t\tpanic(\"game not found\")\n\t}\n\tif err := v.(*game).play(std.PrevRealm().Addr(), move); err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc Render(path string) string {\n\tmov1 := []string{\"\", \" 🤜 \", \" 🫱 \", \" 👉 \"}\n\tmov2 := []string{\"\", \" 🤛 \", \" 🫲 \", \" 👈 \"}\n\twin := []string{\"pending\", \"draw\", \"player1\", \"player2\"}\n\n\toutput := `# 👊 ✋ ✌️ Shifumi\nActions:\n* [NewGame](shifumi$help\u0026func=NewGame) opponentAddress\n* [Play](shifumi$help\u0026func=Play) gameIndex move (1=rock, 2=paper, 3=scissors)\n\n game | player1 | | player2 | | win \n --- | --- | --- | --- | --- | ---\n`\n\t// Output the 100 most recent games.\n\tmaxGames := 100\n\tfor n := int(id); n \u003e 0 \u0026\u0026 int(id)-n \u003c maxGames; n-- {\n\t\tv, ok := games.Get(seqid.ID(n).String())\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tg := v.(*game)\n\t\toutput += strconv.Itoa(n) + \" | \" +\n\t\t\tshortName(g.player1) + \" | \" + mov1[g.move1] + \" | \" +\n\t\t\tshortName(g.player2) + \" | \" + mov2[g.move2] + \" | \" +\n\t\t\twin[g.winner()+1] + \"\\n\"\n\t}\n\treturn output\n}\n\nfunc shortName(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user != nil {\n\t\treturn user.Name\n\t}\n\tif len(addr) \u003c 10 {\n\t\treturn string(addr)\n\t}\n\treturn string(addr)[:10] + \"...\"\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":"groups","path":"gno.land/r/demo/groups","files":[{"name":"README.md","body":"### - test package\n\n ./build/gno test examples/gno.land/r/demo/groups/\n\n### - add pkg\n\n ./build/gnokey maketx addpkg -pkgdir \"examples/gno.land/r/demo/groups\" -deposit 100000000ugnot -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid dev -remote 0.0.0.0:26657 -pkgpath \"gno.land/r/demo/groups\" test1 \n\n### - create group\n\n ./build/gnokey maketx call -func \"CreateGroup\" -args \"dao_trinity_ngo\" -gas-fee \"1000000ugnot\" -gas-wanted 4000000 -broadcast -chainid dev -remote 0.0.0.0:26657 -pkgpath \"gno.land/r/demo/groups\" test1 \n\n### - add member\n\n ./build/gnokey maketx call -func \"AddMember\" -args \"1\" -args \"g1hd3gwzevxlqmd3jsf64mpfczag8a8e5j2wdn3c\" -args 12 -args \"i am new user\" -gas-fee \"1000000ugnot\" -gas-wanted \"4000000\" -broadcast -chainid dev -remote 0.0.0.0:26657 -pkgpath \"gno.land/r/demo/groups\" test1\n\n### - delete member\n\n ./build/gnokey maketx call -func \"DeleteMember\" -args \"1\" -args \"0\" -gas-fee \"1000000ugnot\" -gas-wanted \"4000000\" -broadcast -chainid dev -remote 0.0.0.0:26657 -pkgpath \"gno.land/r/demo/groups\" test1\n\n### - delete group\n\n ./build/gnokey maketx call -func \"DeleteGroup\" -args \"1\" -gas-fee \"1000000ugnot\" -gas-wanted \"4000000\" -broadcast -chainid dev -remote 0.0.0.0:26657 -pkgpath \"gno.land/r/demo/groups\" test1\n\n"},{"name":"group.gno","body":"package groups\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\ntype GroupID uint64\n\nfunc (gid GroupID) String() string {\n\treturn strconv.Itoa(int(gid))\n}\n\ntype Group struct {\n\tid GroupID\n\turl string\n\tname string\n\tlastMemberID MemberID\n\tmembers avl.Tree\n\tcreator std.Address\n\tcreatedAt time.Time\n}\n\nfunc newGroup(url string, name string, creator std.Address) *Group {\n\tif !reName.MatchString(name) {\n\t\tpanic(\"invalid name: \" + name)\n\t}\n\tif gGroupsByName.Has(name) {\n\t\tpanic(\"Group with such name already exists\")\n\t}\n\treturn \u0026Group{\n\t\tid: incGetGroupID(),\n\t\turl: url,\n\t\tname: name,\n\t\tcreator: creator,\n\t\tmembers: avl.Tree{},\n\t\tcreatedAt: time.Now(),\n\t}\n}\n\nfunc (group *Group) newMember(id MemberID, address std.Address, weight int, metadata string) *Member {\n\tif group.members.Has(address.String()) {\n\t\tpanic(\"this member for this group already exists\")\n\t}\n\treturn \u0026Member{\n\t\tid: id,\n\t\taddress: address,\n\t\tweight: weight,\n\t\tmetadata: metadata,\n\t\tcreatedAt: time.Now(),\n\t}\n}\n\nfunc (group *Group) HasPermission(addr std.Address, perm Permission) bool {\n\tif group.creator != addr {\n\t\treturn false\n\t}\n\treturn isValidPermission(perm)\n}\n\nfunc (group *Group) RenderGroup() string {\n\tstr := \"Group ID: \" + groupIDKey(group.id) + \"\\n\\n\" +\n\t\t\"Group Name: \" + group.name + \"\\n\\n\" +\n\t\t\"Group Creator: \" + usernameOf(group.creator) + \"\\n\\n\" +\n\t\t\"Group createdAt: \" + group.createdAt.String() + \"\\n\\n\" +\n\t\t\"Group Last MemberID: \" + memberIDKey(group.lastMemberID) + \"\\n\\n\"\n\n\tstr += \"Group Members: \\n\\n\"\n\tgroup.members.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tmember := value.(*Member)\n\t\tstr += member.getMemberStr()\n\t\treturn false\n\t})\n\treturn str\n}\n\nfunc (group *Group) deleteGroup() {\n\tgidkey := groupIDKey(group.id)\n\t_, gGroupsRemoved := gGroups.Remove(gidkey)\n\tif !gGroupsRemoved {\n\t\tpanic(\"group does not exist with id \" + group.id.String())\n\t}\n\tgGroupsByName.Remove(group.name)\n}\n\nfunc (group *Group) deleteMember(mid MemberID) {\n\tgidkey := groupIDKey(group.id)\n\tif !gGroups.Has(gidkey) {\n\t\tpanic(\"group does not exist with id \" + group.id.String())\n\t}\n\n\tg := getGroup(group.id)\n\tmidkey := memberIDKey(mid)\n\tg.members.Remove(midkey)\n}\n"},{"name":"groups.gno","body":"package groups\n\nimport (\n\t\"regexp\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n//----------------------------------------\n// Realm (package) state\n\nvar (\n\tgGroups avl.Tree // id -\u003e *Group\n\tgGroupsCtr int // increments Group.id\n\tgGroupsByName avl.Tree // name -\u003e *Group\n)\n\n//----------------------------------------\n// Constants\n\nvar reName = regexp.MustCompile(`^[a-z]+[_a-z0-9]{2,29}$`)\n"},{"name":"member.gno","body":"package groups\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n)\n\ntype MemberID uint64\n\ntype Member struct {\n\tid MemberID\n\taddress std.Address\n\tweight int\n\tmetadata string\n\tcreatedAt time.Time\n}\n\nfunc (mid MemberID) String() string {\n\treturn strconv.Itoa(int(mid))\n}\n\nfunc (member *Member) getMemberStr() string {\n\tmemberDataStr := \"\"\n\tmemberDataStr += \"\\t\\t\\t[\" + memberIDKey(member.id) + \", \" + member.address.String() + \", \" + strconv.Itoa(member.weight) + \", \" + member.metadata + \", \" + member.createdAt.String() + \"],\\n\\n\"\n\treturn memberDataStr\n}\n"},{"name":"misc.gno","body":"package groups\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/r/demo/users\"\n)\n\n//----------------------------------------\n// private utility methods\n// XXX ensure these cannot be called from public.\n\nfunc getGroup(gid GroupID) *Group {\n\tgidkey := groupIDKey(gid)\n\tgroup_, exists := gGroups.Get(gidkey)\n\tif !exists {\n\t\tpanic(\"group id (\" + gid.String() + \") does not exists\")\n\t}\n\tgroup := group_.(*Group)\n\treturn group\n}\n\nfunc incGetGroupID() GroupID {\n\tgGroupsCtr++\n\treturn GroupID(gGroupsCtr)\n}\n\nfunc padLeft(str string, length int) string {\n\tif len(str) \u003e= length {\n\t\treturn str\n\t}\n\treturn strings.Repeat(\" \", length-len(str)) + str\n}\n\nfunc padZero(u64 uint64, length int) string {\n\tstr := strconv.Itoa(int(u64))\n\tif len(str) \u003e= length {\n\t\treturn str\n\t}\n\treturn strings.Repeat(\"0\", length-len(str)) + str\n}\n\nfunc groupIDKey(gid GroupID) string {\n\treturn padZero(uint64(gid), 10)\n}\n\nfunc memberIDKey(mid MemberID) string {\n\treturn padZero(uint64(mid), 10)\n}\n\nfunc indentBody(indent string, body string) string {\n\tlines := strings.Split(body, \"\\n\")\n\tres := \"\"\n\tfor i, line := range lines {\n\t\tif i \u003e 0 {\n\t\t\tres += \"\\n\"\n\t\t}\n\t\tres += indent + line\n\t}\n\treturn res\n}\n\n// NOTE: length must be greater than 3.\nfunc summaryOf(str string, length int) string {\n\tlines := strings.SplitN(str, \"\\n\", 2)\n\tline := lines[0]\n\tif len(line) \u003e length {\n\t\tline = line[:(length-3)] + \"...\"\n\t} else if len(lines) \u003e 1 {\n\t\t// len(line) \u003c= 80\n\t\tline = line + \"...\"\n\t}\n\treturn line\n}\n\nfunc displayAddressMD(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user == nil {\n\t\treturn \"[\" + addr.String() + \"](/r/demo/users:\" + addr.String() + \")\"\n\t}\n\treturn \"[@\" + user.Name + \"](/r/demo/users:\" + user.Name + \")\"\n}\n\nfunc usernameOf(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user == nil {\n\t\tpanic(\"user not found\")\n\t}\n\treturn user.Name\n}\n\nfunc isValidPermission(perm Permission) bool {\n\treturn perm == EditPermission || perm == DeletePermission\n}\n"},{"name":"public.gno","body":"package groups\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\n//----------------------------------------\n// Public facing functions\n\nfunc GetGroupIDFromName(name string) (GroupID, bool) {\n\tgroupI, exists := gGroupsByName.Get(name)\n\tif !exists {\n\t\treturn 0, false\n\t}\n\treturn groupI.(*Group).id, true\n}\n\nfunc CreateGroup(name string) GroupID {\n\tstd.AssertOriginCall()\n\tcaller := std.GetOrigCaller()\n\tusernameOf(caller)\n\turl := \"/r/demo/groups:\" + name\n\tgroup := newGroup(url, name, caller)\n\tgidkey := groupIDKey(group.id)\n\tgGroups.Set(gidkey, group)\n\tgGroupsByName.Set(name, group)\n\treturn group.id\n}\n\nfunc AddMember(gid GroupID, address string, weight int, metadata string) MemberID {\n\tstd.AssertOriginCall()\n\tcaller := std.GetOrigCaller()\n\tusernameOf(caller)\n\tgroup := getGroup(gid)\n\tif !group.HasPermission(caller, EditPermission) {\n\t\tpanic(\"unauthorized to edit group\")\n\t}\n\tuser := users.GetUserByAddress(std.Address(address))\n\tif user == nil {\n\t\tpanic(\"unknown address \" + address)\n\t}\n\tmid := group.lastMemberID\n\tmember := group.newMember(mid, std.Address(address), weight, metadata)\n\tmidkey := memberIDKey(mid)\n\tgroup.members.Set(midkey, member)\n\tmid++\n\tgroup.lastMemberID = mid\n\treturn member.id\n}\n\nfunc DeleteGroup(gid GroupID) {\n\tstd.AssertOriginCall()\n\tcaller := std.GetOrigCaller()\n\tgroup := getGroup(gid)\n\tif !group.HasPermission(caller, DeletePermission) {\n\t\tpanic(\"unauthorized to delete group\")\n\t}\n\tgroup.deleteGroup()\n}\n\nfunc DeleteMember(gid GroupID, mid MemberID) {\n\tstd.AssertOriginCall()\n\tcaller := std.GetOrigCaller()\n\tgroup := getGroup(gid)\n\tif !group.HasPermission(caller, DeletePermission) {\n\t\tpanic(\"unauthorized to delete member\")\n\t}\n\tgroup.deleteMember(mid)\n}\n"},{"name":"render.gno","body":"package groups\n\nimport (\n\t\"strings\"\n)\n\n//----------------------------------------\n// Render functions\n\nfunc RenderGroup(gid GroupID) string {\n\tgroup := getGroup(gid)\n\tif group == nil {\n\t\treturn \"missing Group\"\n\t}\n\treturn group.RenderGroup()\n}\n\nfunc Render(path string) string {\n\tif path == \"\" {\n\t\tstr := \"List of all Groups:\\n\\n\"\n\t\tgGroups.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tgroup := value.(*Group)\n\t\t\tstr += \" * [\" + group.name + \"](\" + group.url + \")\\n\"\n\t\t\treturn false\n\t\t})\n\t\treturn str\n\t}\n\tparts := strings.Split(path, \"/\")\n\tif len(parts) == 1 {\n\t\t// /r/demo/groups:Group_NAME\n\t\tname := parts[0]\n\t\tgroupI, exists := gGroupsByName.Get(name)\n\t\tif !exists {\n\t\t\treturn \"Group does not exist: \" + name\n\t\t}\n\t\treturn groupI.(*Group).RenderGroup()\n\t} else {\n\t\treturn \"unrecognized path \" + path\n\t}\n}\n"},{"name":"role.gno","body":"package groups\n\ntype Permission string\n\nconst (\n\tDeletePermission Permission = \"role:delete\"\n\tEditPermission Permission = \"role:edit\"\n)\n"},{"name":"z_0_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\nimport (\n\t\"gno.land/r/demo/groups\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// user not found\n"},{"name":"z_0_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// payment must not be less than 20000000\n"},{"name":"z_0_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tprintln(groups.Render(\"\"))\n}\n\n// Output:\n// 1\n// List of all Groups:\n//\n// * [test_group](/r/demo/groups:test_group)\n"},{"name":"z_1_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.GetOrigCaller() // main\n\tusers.Register(\"\", \"gnouser0\", \"my profile 1\")\n\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"gnouser1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test1)\n\tusers.Register(caller, \"gnouser1\", \"my other profile 1\")\n\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest2 := testutils.TestAddress(\"gnouser2\")\n\tusers.Invite(test2.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test2)\n\tusers.Register(caller, \"gnouser2\", \"my other profile 2\")\n\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest3 := testutils.TestAddress(\"gnouser3\")\n\tusers.Invite(test3.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test3)\n\tusers.Register(caller, \"gnouser3\", \"my other profile 3\")\n\n\tstd.TestSetOrigCaller(caller)\n\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\n\tgroups.AddMember(gid, test3.String(), 32, \"i am from UAE\")\n\tprintln(groups.Render(\"test_group\"))\n}\n\n// Output:\n// 1\n// Group ID: 0000000001\n//\n// Group Name: test_group\n//\n// Group Creator: gnouser0\n//\n// Group createdAt: 2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001\n//\n// Group Last MemberID: 0000000001\n//\n// Group Members:\n//\n// \t\t\t[0000000000, g1vahx7atnv4erxh6lta047h6lta047h6ll85gpy, 32, i am from UAE, 2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001],\n"},{"name":"z_1_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tgroups.AddMember(2, \"g1vahx7atnv4erxh6lta047h6lta047h6ll85gpy\", 55, \"metadata3\")\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// group id (2) does not exists\n"},{"name":"z_1_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/groups\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\n\t// add member via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOrigCaller(test2)\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\n\tgroups.AddMember(gid, test2.String(), 42, \"metadata3\")\n}\n\n// Error:\n// user not found\n"},{"name":"z_2_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.GetOrigCaller() // main\n\tusers.Register(\"\", \"gnouser0\", \"my profile 1\")\n\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"gnouser1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test1)\n\tusers.Register(caller, \"gnouser1\", \"my other profile 1\")\n\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest2 := testutils.TestAddress(\"gnouser2\")\n\tusers.Invite(test2.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test2)\n\tusers.Register(caller, \"gnouser2\", \"my other profile 2\")\n\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest3 := testutils.TestAddress(\"gnouser3\")\n\tusers.Invite(test3.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test3)\n\tusers.Register(caller, \"gnouser3\", \"my other profile 3\")\n\n\tstd.TestSetOrigCaller(caller)\n\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\n\tgroups.AddMember(gid, test2.String(), 42, \"metadata3\")\n\n\tgroups.DeleteMember(gid, 0)\n\tprintln(groups.RenderGroup(gid))\n}\n\n// Output:\n// 1\n// Group ID: 0000000001\n//\n// Group Name: test_group\n//\n// Group Creator: gnouser0\n//\n// Group createdAt: 2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001\n//\n// Group Last MemberID: 0000000001\n//\n// Group Members:\n"},{"name":"z_2_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tgroups.DeleteMember(2, 0)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// group id (2) does not exists\n"},{"name":"z_2_d_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\n\t// delete member via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOrigCaller(test2)\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\n\tgroups.DeleteMember(gid, 0)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// unauthorized to delete member\n"},{"name":"z_2_e_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tgroups.DeleteGroup(gid)\n\tprintln(groups.Render(\"\"))\n}\n\n// Output:\n// 1\n// List of all Groups:\n"},{"name":"z_2_f_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tgroups.DeleteGroup(20)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// group id (20) does not exists\n"},{"name":"z_2_g_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\n\t// delete group via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOrigCaller(test2)\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\n\tgroups.DeleteGroup(gid)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// unauthorized to delete group\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":"keystore","path":"gno.land/r/demo/keystore","files":[{"name":"keystore.gno","body":"package keystore\n\nimport (\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar data avl.Tree\n\nconst (\n\tBaseURL = \"/r/demo/keystore\"\n\tStatusOK = \"ok\"\n\tStatusNoUser = \"user not found\"\n\tStatusNotFound = \"key not found\"\n\tStatusNoWriteAccess = \"no write access\"\n\tStatusCouldNotExecute = \"could not execute\"\n\tStatusNoDatabases = \"no databases\"\n)\n\nfunc init() {\n\tdata = avl.Tree{} // user -\u003e avl.Tree\n}\n\n// KeyStore stores the owner-specific avl.Tree\ntype KeyStore struct {\n\tOwner std.Address\n\tData avl.Tree\n}\n\n// Set will set a value to a key\n// requires write-access (original caller must be caller)\nfunc Set(k, v string) string {\n\torigOwner := std.GetOrigCaller()\n\treturn set(origOwner.String(), k, v)\n}\n\n// set (private) will set a key to value\n// requires write-access (original caller must be caller)\nfunc set(owner, k, v string) string {\n\torigOwner := std.GetOrigCaller()\n\tif origOwner.String() != owner {\n\t\treturn StatusNoWriteAccess\n\t}\n\tvar keystore *KeyStore\n\tkeystoreInterface, exists := data.Get(owner)\n\tif !exists {\n\t\tkeystore = \u0026KeyStore{\n\t\t\tOwner: origOwner,\n\t\t\tData: avl.Tree{},\n\t\t}\n\t\tdata.Set(owner, keystore)\n\t} else {\n\t\tkeystore = keystoreInterface.(*KeyStore)\n\t}\n\tkeystore.Data.Set(k, v)\n\treturn StatusOK\n}\n\n// Remove removes a key\n// requires write-access (original owner must be caller)\nfunc Remove(k string) string {\n\torigOwner := std.GetOrigCaller()\n\treturn remove(origOwner.String(), k)\n}\n\n// remove (private) removes a key\n// requires write-access (original owner must be caller)\nfunc remove(owner, k string) string {\n\torigOwner := std.GetOrigCaller()\n\tif origOwner.String() != owner {\n\t\treturn StatusNoWriteAccess\n\t}\n\tvar keystore *KeyStore\n\tkeystoreInterface, exists := data.Get(owner)\n\tif !exists {\n\t\tkeystore = \u0026KeyStore{\n\t\t\tOwner: origOwner,\n\t\t\tData: avl.Tree{},\n\t\t}\n\t\tdata.Set(owner, keystore)\n\t} else {\n\t\tkeystore = keystoreInterface.(*KeyStore)\n\t}\n\t_, removed := keystore.Data.Remove(k)\n\tif !removed {\n\t\treturn StatusCouldNotExecute\n\t}\n\treturn StatusOK\n}\n\n// Get returns a value for a key\n// read-only\nfunc Get(k string) string {\n\torigOwner := std.GetOrigCaller()\n\treturn remove(origOwner.String(), k)\n}\n\n// get (private) returns a value for a key\n// read-only\nfunc get(owner, k string) string {\n\tkeystoreInterface, exists := data.Get(owner)\n\tif !exists {\n\t\treturn StatusNoUser\n\t}\n\tkeystore := keystoreInterface.(*KeyStore)\n\tval, found := keystore.Data.Get(k)\n\tif !found {\n\t\treturn StatusNotFound\n\t}\n\treturn val.(string)\n}\n\n// Size returns size of database\n// read-only\nfunc Size() string {\n\torigOwner := std.GetOrigCaller()\n\treturn size(origOwner.String())\n}\n\nfunc size(owner string) string {\n\tkeystoreInterface, exists := data.Get(owner)\n\tif !exists {\n\t\treturn StatusNoUser\n\t}\n\tkeystore := keystoreInterface.(*KeyStore)\n\treturn ufmt.Sprintf(\"%d\", keystore.Data.Size())\n}\n\n// Render provides read-only url access to the functions of the keystore\n// \"\" -\u003e show all keystores listed by owner\n// \"owner\" -\u003e show all keys for that owner's keystore\n// \"owner:size\" -\u003e returns size of owner's keystore\n// \"owner:get:key\" -\u003e show value for that key in owner's keystore\nfunc Render(p string) string {\n\tvar response string\n\targs := strings.Split(p, \":\")\n\tnumArgs := len(args)\n\tif p == \"\" {\n\t\tnumArgs = 0\n\t}\n\tswitch numArgs {\n\tcase 0:\n\t\tif data.Size() == 0 {\n\t\t\treturn StatusNoDatabases\n\t\t}\n\t\tdata.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tks := value.(*KeyStore)\n\t\t\tresponse += ufmt.Sprintf(\"- [%s](%s:%s) (%d keys)\\n\", ks.Owner, BaseURL, ks.Owner, ks.Data.Size())\n\t\t\treturn false\n\t\t})\n\tcase 1:\n\t\towner := args[0]\n\t\tkeystoreInterface, exists := data.Get(owner)\n\t\tif !exists {\n\t\t\treturn StatusNoUser\n\t\t}\n\t\tks := keystoreInterface.(*KeyStore)\n\t\ti := 0\n\t\tresponse += ufmt.Sprintf(\"# %s database\\n\\n\", ks.Owner)\n\t\tks.Data.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tresponse += ufmt.Sprintf(\"- %d [%s](%s:%s:get:%s)\\n\", i, key, BaseURL, ks.Owner, key)\n\t\t\ti++\n\t\t\treturn false\n\t\t})\n\tcase 2:\n\t\towner := args[0]\n\t\tcmd := args[1]\n\t\tif cmd == \"size\" {\n\t\t\treturn size(owner)\n\t\t}\n\tcase 3:\n\t\towner := args[0]\n\t\tcmd := args[1]\n\t\tkey := args[2]\n\t\tif cmd == \"get\" {\n\t\t\treturn get(owner, key)\n\t\t}\n\t}\n\n\treturn response\n}\n"},{"name":"keystore_test.gno","body":"package keystore\n\nimport (\n\t\"std\"\n\t\"strings\"\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 TestRender(t *testing.T) {\n\tconst (\n\t\tauthor1 std.Address = testutils.TestAddress(\"author1\")\n\t\tauthor2 std.Address = testutils.TestAddress(\"author2\")\n\t)\n\n\ttt := []struct {\n\t\tcaller std.Address\n\t\towner std.Address\n\t\tps []string\n\t\texp string\n\t}{\n\t\t// can set database if the owner is the caller\n\t\t{author1, author1, []string{\"set\", \"hello\", \"gno\"}, StatusOK},\n\t\t{author1, author1, []string{\"size\"}, \"1\"},\n\t\t{author1, author1, []string{\"set\", \"hello\", \"world\"}, StatusOK},\n\t\t{author1, author1, []string{\"size\"}, \"1\"},\n\t\t{author1, author1, []string{\"set\", \"hi\", \"gno\"}, StatusOK},\n\t\t{author1, author1, []string{\"size\"}, \"2\"},\n\t\t// only owner can remove\n\t\t{author1, author1, []string{\"remove\", \"hi\"}, StatusOK},\n\t\t{author1, author1, []string{\"get\", \"hi\"}, StatusNotFound},\n\t\t{author1, author1, []string{\"size\"}, \"1\"},\n\t\t// add back\n\t\t{author1, author1, []string{\"set\", \"hi\", \"gno\"}, StatusOK},\n\t\t{author1, author1, []string{\"size\"}, \"2\"},\n\n\t\t// different owner has different database\n\t\t{author2, author2, []string{\"set\", \"hello\", \"universe\"}, StatusOK},\n\t\t// either author can get the other info\n\t\t{author1, author2, []string{\"get\", \"hello\"}, \"universe\"},\n\t\t// either author can get the other info\n\t\t{author2, author1, []string{\"get\", \"hello\"}, \"world\"},\n\t\t{author1, author2, []string{\"get\", \"hello\"}, \"universe\"},\n\t\t// anyone can view the databases\n\t\t{author1, author2, []string{}, `- [g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6](/r/demo/keystore:g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6) (2 keys)\n- [g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00](/r/demo/keystore:g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00) (1 keys)`},\n\t\t// anyone can view the keys in a database\n\t\t{author1, author2, []string{\"\"}, `# g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00 database\n\n- 0 [hello](/r/demo/keystore:g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00:get:hello)`},\n\t}\n\tfor _, tc := range tt {\n\t\tp := \"\"\n\t\tif len(tc.ps) \u003e 0 {\n\t\t\tp = tc.owner.String()\n\t\t\tfor i, psv := range tc.ps {\n\t\t\t\tp += \":\" + psv\n\t\t\t}\n\t\t}\n\t\tp = strings.TrimSuffix(p, \":\")\n\t\tt.Run(p, func(t *testing.T) {\n\t\t\tstd.TestSetOrigCaller(tc.caller)\n\t\t\tvar act string\n\t\t\tif len(tc.ps) \u003e 0 \u0026\u0026 tc.ps[0] == \"set\" {\n\t\t\t\tact = strings.TrimSpace(Set(tc.ps[1], tc.ps[2]))\n\t\t\t} else if len(tc.ps) \u003e 0 \u0026\u0026 tc.ps[0] == \"remove\" {\n\t\t\t\tact = strings.TrimSpace(Remove(tc.ps[1]))\n\t\t\t} else {\n\t\t\t\tact = strings.TrimSpace(Render(p))\n\t\t\t}\n\n\t\t\tuassert.Equal(t, tc.exp, act, ufmt.Sprintf(\"%v -\u003e '%s'\", tc.ps, p))\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":"groups","path":"gno.land/r/demo/groups","files":[{"name":"README.md","body":"### - test package\n\n ./build/gno test examples/gno.land/r/demo/groups/\n\n### - add pkg\n\n ./build/gnokey maketx addpkg -pkgdir \"examples/gno.land/r/demo/groups\" -deposit 100000000ugnot -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid dev -remote 0.0.0.0:26657 -pkgpath \"gno.land/r/demo/groups\" test1 \n\n### - create group\n\n ./build/gnokey maketx call -func \"CreateGroup\" -args \"dao_trinity_ngo\" -gas-fee \"1000000ugnot\" -gas-wanted 4000000 -broadcast -chainid dev -remote 0.0.0.0:26657 -pkgpath \"gno.land/r/demo/groups\" test1 \n\n### - add member\n\n ./build/gnokey maketx call -func \"AddMember\" -args \"1\" -args \"g1hd3gwzevxlqmd3jsf64mpfczag8a8e5j2wdn3c\" -args 12 -args \"i am new user\" -gas-fee \"1000000ugnot\" -gas-wanted \"4000000\" -broadcast -chainid dev -remote 0.0.0.0:26657 -pkgpath \"gno.land/r/demo/groups\" test1\n\n### - delete member\n\n ./build/gnokey maketx call -func \"DeleteMember\" -args \"1\" -args \"0\" -gas-fee \"1000000ugnot\" -gas-wanted \"4000000\" -broadcast -chainid dev -remote 0.0.0.0:26657 -pkgpath \"gno.land/r/demo/groups\" test1\n\n### - delete group\n\n ./build/gnokey maketx call -func \"DeleteGroup\" -args \"1\" -gas-fee \"1000000ugnot\" -gas-wanted \"4000000\" -broadcast -chainid dev -remote 0.0.0.0:26657 -pkgpath \"gno.land/r/demo/groups\" test1\n\n"},{"name":"group.gno","body":"package groups\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\ntype GroupID uint64\n\nfunc (gid GroupID) String() string {\n\treturn strconv.Itoa(int(gid))\n}\n\ntype Group struct {\n\tid GroupID\n\turl string\n\tname string\n\tlastMemberID MemberID\n\tmembers avl.Tree\n\tcreator std.Address\n\tcreatedAt time.Time\n}\n\nfunc newGroup(url string, name string, creator std.Address) *Group {\n\tif !reName.MatchString(name) {\n\t\tpanic(\"invalid name: \" + name)\n\t}\n\tif gGroupsByName.Has(name) {\n\t\tpanic(\"Group with such name already exists\")\n\t}\n\treturn \u0026Group{\n\t\tid: incGetGroupID(),\n\t\turl: url,\n\t\tname: name,\n\t\tcreator: creator,\n\t\tmembers: avl.Tree{},\n\t\tcreatedAt: time.Now(),\n\t}\n}\n\nfunc (group *Group) newMember(id MemberID, address std.Address, weight int, metadata string) *Member {\n\tif group.members.Has(address.String()) {\n\t\tpanic(\"this member for this group already exists\")\n\t}\n\treturn \u0026Member{\n\t\tid: id,\n\t\taddress: address,\n\t\tweight: weight,\n\t\tmetadata: metadata,\n\t\tcreatedAt: time.Now(),\n\t}\n}\n\nfunc (group *Group) HasPermission(addr std.Address, perm Permission) bool {\n\tif group.creator != addr {\n\t\treturn false\n\t}\n\treturn isValidPermission(perm)\n}\n\nfunc (group *Group) RenderGroup() string {\n\tstr := \"Group ID: \" + groupIDKey(group.id) + \"\\n\\n\" +\n\t\t\"Group Name: \" + group.name + \"\\n\\n\" +\n\t\t\"Group Creator: \" + usernameOf(group.creator) + \"\\n\\n\" +\n\t\t\"Group createdAt: \" + group.createdAt.String() + \"\\n\\n\" +\n\t\t\"Group Last MemberID: \" + memberIDKey(group.lastMemberID) + \"\\n\\n\"\n\n\tstr += \"Group Members: \\n\\n\"\n\tgroup.members.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tmember := value.(*Member)\n\t\tstr += member.getMemberStr()\n\t\treturn false\n\t})\n\treturn str\n}\n\nfunc (group *Group) deleteGroup() {\n\tgidkey := groupIDKey(group.id)\n\t_, gGroupsRemoved := gGroups.Remove(gidkey)\n\tif !gGroupsRemoved {\n\t\tpanic(\"group does not exist with id \" + group.id.String())\n\t}\n\tgGroupsByName.Remove(group.name)\n}\n\nfunc (group *Group) deleteMember(mid MemberID) {\n\tgidkey := groupIDKey(group.id)\n\tif !gGroups.Has(gidkey) {\n\t\tpanic(\"group does not exist with id \" + group.id.String())\n\t}\n\n\tg := getGroup(group.id)\n\tmidkey := memberIDKey(mid)\n\tg.members.Remove(midkey)\n}\n"},{"name":"groups.gno","body":"package groups\n\nimport (\n\t\"regexp\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n//----------------------------------------\n// Realm (package) state\n\nvar (\n\tgGroups avl.Tree // id -\u003e *Group\n\tgGroupsCtr int // increments Group.id\n\tgGroupsByName avl.Tree // name -\u003e *Group\n)\n\n//----------------------------------------\n// Constants\n\nvar reName = regexp.MustCompile(`^[a-z]+[_a-z0-9]{2,29}$`)\n"},{"name":"member.gno","body":"package groups\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n)\n\ntype MemberID uint64\n\ntype Member struct {\n\tid MemberID\n\taddress std.Address\n\tweight int\n\tmetadata string\n\tcreatedAt time.Time\n}\n\nfunc (mid MemberID) String() string {\n\treturn strconv.Itoa(int(mid))\n}\n\nfunc (member *Member) getMemberStr() string {\n\tmemberDataStr := \"\"\n\tmemberDataStr += \"\\t\\t\\t[\" + memberIDKey(member.id) + \", \" + member.address.String() + \", \" + strconv.Itoa(member.weight) + \", \" + member.metadata + \", \" + member.createdAt.String() + \"],\\n\\n\"\n\treturn memberDataStr\n}\n"},{"name":"misc.gno","body":"package groups\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/r/demo/users\"\n)\n\n//----------------------------------------\n// private utility methods\n// XXX ensure these cannot be called from public.\n\nfunc getGroup(gid GroupID) *Group {\n\tgidkey := groupIDKey(gid)\n\tgroup_, exists := gGroups.Get(gidkey)\n\tif !exists {\n\t\tpanic(\"group id (\" + gid.String() + \") does not exists\")\n\t}\n\tgroup := group_.(*Group)\n\treturn group\n}\n\nfunc incGetGroupID() GroupID {\n\tgGroupsCtr++\n\treturn GroupID(gGroupsCtr)\n}\n\nfunc padLeft(str string, length int) string {\n\tif len(str) \u003e= length {\n\t\treturn str\n\t}\n\treturn strings.Repeat(\" \", length-len(str)) + str\n}\n\nfunc padZero(u64 uint64, length int) string {\n\tstr := strconv.Itoa(int(u64))\n\tif len(str) \u003e= length {\n\t\treturn str\n\t}\n\treturn strings.Repeat(\"0\", length-len(str)) + str\n}\n\nfunc groupIDKey(gid GroupID) string {\n\treturn padZero(uint64(gid), 10)\n}\n\nfunc memberIDKey(mid MemberID) string {\n\treturn padZero(uint64(mid), 10)\n}\n\nfunc indentBody(indent string, body string) string {\n\tlines := strings.Split(body, \"\\n\")\n\tres := \"\"\n\tfor i, line := range lines {\n\t\tif i \u003e 0 {\n\t\t\tres += \"\\n\"\n\t\t}\n\t\tres += indent + line\n\t}\n\treturn res\n}\n\n// NOTE: length must be greater than 3.\nfunc summaryOf(str string, length int) string {\n\tlines := strings.SplitN(str, \"\\n\", 2)\n\tline := lines[0]\n\tif len(line) \u003e length {\n\t\tline = line[:(length-3)] + \"...\"\n\t} else if len(lines) \u003e 1 {\n\t\t// len(line) \u003c= 80\n\t\tline = line + \"...\"\n\t}\n\treturn line\n}\n\nfunc displayAddressMD(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user == nil {\n\t\treturn \"[\" + addr.String() + \"](/r/demo/users:\" + addr.String() + \")\"\n\t}\n\treturn \"[@\" + user.Name + \"](/r/demo/users:\" + user.Name + \")\"\n}\n\nfunc usernameOf(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user == nil {\n\t\tpanic(\"user not found\")\n\t}\n\treturn user.Name\n}\n\nfunc isValidPermission(perm Permission) bool {\n\treturn perm == EditPermission || perm == DeletePermission\n}\n"},{"name":"public.gno","body":"package groups\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\n//----------------------------------------\n// Public facing functions\n\nfunc GetGroupIDFromName(name string) (GroupID, bool) {\n\tgroupI, exists := gGroupsByName.Get(name)\n\tif !exists {\n\t\treturn 0, false\n\t}\n\treturn groupI.(*Group).id, true\n}\n\nfunc CreateGroup(name string) GroupID {\n\tstd.AssertOriginCall()\n\tcaller := std.OrigCaller()\n\tusernameOf(caller)\n\turl := \"/r/demo/groups:\" + name\n\tgroup := newGroup(url, name, caller)\n\tgidkey := groupIDKey(group.id)\n\tgGroups.Set(gidkey, group)\n\tgGroupsByName.Set(name, group)\n\treturn group.id\n}\n\nfunc AddMember(gid GroupID, address string, weight int, metadata string) MemberID {\n\tstd.AssertOriginCall()\n\tcaller := std.OrigCaller()\n\tusernameOf(caller)\n\tgroup := getGroup(gid)\n\tif !group.HasPermission(caller, EditPermission) {\n\t\tpanic(\"unauthorized to edit group\")\n\t}\n\tuser := users.GetUserByAddress(std.Address(address))\n\tif user == nil {\n\t\tpanic(\"unknown address \" + address)\n\t}\n\tmid := group.lastMemberID\n\tmember := group.newMember(mid, std.Address(address), weight, metadata)\n\tmidkey := memberIDKey(mid)\n\tgroup.members.Set(midkey, member)\n\tmid++\n\tgroup.lastMemberID = mid\n\treturn member.id\n}\n\nfunc DeleteGroup(gid GroupID) {\n\tstd.AssertOriginCall()\n\tcaller := std.OrigCaller()\n\tgroup := getGroup(gid)\n\tif !group.HasPermission(caller, DeletePermission) {\n\t\tpanic(\"unauthorized to delete group\")\n\t}\n\tgroup.deleteGroup()\n}\n\nfunc DeleteMember(gid GroupID, mid MemberID) {\n\tstd.AssertOriginCall()\n\tcaller := std.OrigCaller()\n\tgroup := getGroup(gid)\n\tif !group.HasPermission(caller, DeletePermission) {\n\t\tpanic(\"unauthorized to delete member\")\n\t}\n\tgroup.deleteMember(mid)\n}\n"},{"name":"render.gno","body":"package groups\n\nimport (\n\t\"strings\"\n)\n\n//----------------------------------------\n// Render functions\n\nfunc RenderGroup(gid GroupID) string {\n\tgroup := getGroup(gid)\n\tif group == nil {\n\t\treturn \"missing Group\"\n\t}\n\treturn group.RenderGroup()\n}\n\nfunc Render(path string) string {\n\tif path == \"\" {\n\t\tstr := \"List of all Groups:\\n\\n\"\n\t\tgGroups.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tgroup := value.(*Group)\n\t\t\tstr += \" * [\" + group.name + \"](\" + group.url + \")\\n\"\n\t\t\treturn false\n\t\t})\n\t\treturn str\n\t}\n\tparts := strings.Split(path, \"/\")\n\tif len(parts) == 1 {\n\t\t// /r/demo/groups:Group_NAME\n\t\tname := parts[0]\n\t\tgroupI, exists := gGroupsByName.Get(name)\n\t\tif !exists {\n\t\t\treturn \"Group does not exist: \" + name\n\t\t}\n\t\treturn groupI.(*Group).RenderGroup()\n\t} else {\n\t\treturn \"unrecognized path \" + path\n\t}\n}\n"},{"name":"role.gno","body":"package groups\n\ntype Permission string\n\nconst (\n\tDeletePermission Permission = \"role:delete\"\n\tEditPermission Permission = \"role:edit\"\n)\n"},{"name":"z_0_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\nimport (\n\t\"gno.land/r/demo/groups\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// user not found\n"},{"name":"z_0_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// payment must not be less than 20000000\n"},{"name":"z_0_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tprintln(groups.Render(\"\"))\n}\n\n// Output:\n// 1\n// List of all Groups:\n//\n// * [test_group](/r/demo/groups:test_group)\n"},{"name":"z_1_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OrigCaller() // main\n\tusers.Register(\"\", \"gnouser0\", \"my profile 1\")\n\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"gnouser1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test1)\n\tusers.Register(caller, \"gnouser1\", \"my other profile 1\")\n\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest2 := testutils.TestAddress(\"gnouser2\")\n\tusers.Invite(test2.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test2)\n\tusers.Register(caller, \"gnouser2\", \"my other profile 2\")\n\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest3 := testutils.TestAddress(\"gnouser3\")\n\tusers.Invite(test3.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test3)\n\tusers.Register(caller, \"gnouser3\", \"my other profile 3\")\n\n\tstd.TestSetOrigCaller(caller)\n\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\n\tgroups.AddMember(gid, test3.String(), 32, \"i am from UAE\")\n\tprintln(groups.Render(\"test_group\"))\n}\n\n// Output:\n// 1\n// Group ID: 0000000001\n//\n// Group Name: test_group\n//\n// Group Creator: gnouser0\n//\n// Group createdAt: 2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001\n//\n// Group Last MemberID: 0000000001\n//\n// Group Members:\n//\n// \t\t\t[0000000000, g1vahx7atnv4erxh6lta047h6lta047h6ll85gpy, 32, i am from UAE, 2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001],\n"},{"name":"z_1_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tgroups.AddMember(2, \"g1vahx7atnv4erxh6lta047h6lta047h6ll85gpy\", 55, \"metadata3\")\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// group id (2) does not exists\n"},{"name":"z_1_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/groups\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\n\t// add member via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOrigCaller(test2)\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\n\tgroups.AddMember(gid, test2.String(), 42, \"metadata3\")\n}\n\n// Error:\n// user not found\n"},{"name":"z_2_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OrigCaller() // main\n\tusers.Register(\"\", \"gnouser0\", \"my profile 1\")\n\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"gnouser1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test1)\n\tusers.Register(caller, \"gnouser1\", \"my other profile 1\")\n\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest2 := testutils.TestAddress(\"gnouser2\")\n\tusers.Invite(test2.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test2)\n\tusers.Register(caller, \"gnouser2\", \"my other profile 2\")\n\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest3 := testutils.TestAddress(\"gnouser3\")\n\tusers.Invite(test3.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test3)\n\tusers.Register(caller, \"gnouser3\", \"my other profile 3\")\n\n\tstd.TestSetOrigCaller(caller)\n\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\n\tgroups.AddMember(gid, test2.String(), 42, \"metadata3\")\n\n\tgroups.DeleteMember(gid, 0)\n\tprintln(groups.RenderGroup(gid))\n}\n\n// Output:\n// 1\n// Group ID: 0000000001\n//\n// Group Name: test_group\n//\n// Group Creator: gnouser0\n//\n// Group createdAt: 2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001\n//\n// Group Last MemberID: 0000000001\n//\n// Group Members:\n"},{"name":"z_2_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tgroups.DeleteMember(2, 0)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// group id (2) does not exists\n"},{"name":"z_2_d_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\n\t// delete member via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOrigCaller(test2)\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\n\tgroups.DeleteMember(gid, 0)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// unauthorized to delete member\n"},{"name":"z_2_e_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tgroups.DeleteGroup(gid)\n\tprintln(groups.Render(\"\"))\n}\n\n// Output:\n// 1\n// List of all Groups:\n"},{"name":"z_2_f_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tgroups.DeleteGroup(20)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// group id (20) does not exists\n"},{"name":"z_2_g_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\n\t// delete group via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOrigCaller(test2)\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\n\tgroups.DeleteGroup(gid)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// unauthorized to delete group\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":"keystore","path":"gno.land/r/demo/keystore","files":[{"name":"keystore.gno","body":"package keystore\n\nimport (\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar data avl.Tree\n\nconst (\n\tBaseURL = \"/r/demo/keystore\"\n\tStatusOK = \"ok\"\n\tStatusNoUser = \"user not found\"\n\tStatusNotFound = \"key not found\"\n\tStatusNoWriteAccess = \"no write access\"\n\tStatusCouldNotExecute = \"could not execute\"\n\tStatusNoDatabases = \"no databases\"\n)\n\nfunc init() {\n\tdata = avl.Tree{} // user -\u003e avl.Tree\n}\n\n// KeyStore stores the owner-specific avl.Tree\ntype KeyStore struct {\n\tOwner std.Address\n\tData avl.Tree\n}\n\n// Set will set a value to a key\n// requires write-access (original caller must be caller)\nfunc Set(k, v string) string {\n\torigOwner := std.OrigCaller()\n\treturn set(origOwner.String(), k, v)\n}\n\n// set (private) will set a key to value\n// requires write-access (original caller must be caller)\nfunc set(owner, k, v string) string {\n\torigOwner := std.OrigCaller()\n\tif origOwner.String() != owner {\n\t\treturn StatusNoWriteAccess\n\t}\n\tvar keystore *KeyStore\n\tkeystoreInterface, exists := data.Get(owner)\n\tif !exists {\n\t\tkeystore = \u0026KeyStore{\n\t\t\tOwner: origOwner,\n\t\t\tData: avl.Tree{},\n\t\t}\n\t\tdata.Set(owner, keystore)\n\t} else {\n\t\tkeystore = keystoreInterface.(*KeyStore)\n\t}\n\tkeystore.Data.Set(k, v)\n\treturn StatusOK\n}\n\n// Remove removes a key\n// requires write-access (original owner must be caller)\nfunc Remove(k string) string {\n\torigOwner := std.OrigCaller()\n\treturn remove(origOwner.String(), k)\n}\n\n// remove (private) removes a key\n// requires write-access (original owner must be caller)\nfunc remove(owner, k string) string {\n\torigOwner := std.OrigCaller()\n\tif origOwner.String() != owner {\n\t\treturn StatusNoWriteAccess\n\t}\n\tvar keystore *KeyStore\n\tkeystoreInterface, exists := data.Get(owner)\n\tif !exists {\n\t\tkeystore = \u0026KeyStore{\n\t\t\tOwner: origOwner,\n\t\t\tData: avl.Tree{},\n\t\t}\n\t\tdata.Set(owner, keystore)\n\t} else {\n\t\tkeystore = keystoreInterface.(*KeyStore)\n\t}\n\t_, removed := keystore.Data.Remove(k)\n\tif !removed {\n\t\treturn StatusCouldNotExecute\n\t}\n\treturn StatusOK\n}\n\n// Get returns a value for a key\n// read-only\nfunc Get(k string) string {\n\torigOwner := std.OrigCaller()\n\treturn remove(origOwner.String(), k)\n}\n\n// get (private) returns a value for a key\n// read-only\nfunc get(owner, k string) string {\n\tkeystoreInterface, exists := data.Get(owner)\n\tif !exists {\n\t\treturn StatusNoUser\n\t}\n\tkeystore := keystoreInterface.(*KeyStore)\n\tval, found := keystore.Data.Get(k)\n\tif !found {\n\t\treturn StatusNotFound\n\t}\n\treturn val.(string)\n}\n\n// Size returns size of database\n// read-only\nfunc Size() string {\n\torigOwner := std.OrigCaller()\n\treturn size(origOwner.String())\n}\n\nfunc size(owner string) string {\n\tkeystoreInterface, exists := data.Get(owner)\n\tif !exists {\n\t\treturn StatusNoUser\n\t}\n\tkeystore := keystoreInterface.(*KeyStore)\n\treturn ufmt.Sprintf(\"%d\", keystore.Data.Size())\n}\n\n// Render provides read-only url access to the functions of the keystore\n// \"\" -\u003e show all keystores listed by owner\n// \"owner\" -\u003e show all keys for that owner's keystore\n// \"owner:size\" -\u003e returns size of owner's keystore\n// \"owner:get:key\" -\u003e show value for that key in owner's keystore\nfunc Render(p string) string {\n\tvar response string\n\targs := strings.Split(p, \":\")\n\tnumArgs := len(args)\n\tif p == \"\" {\n\t\tnumArgs = 0\n\t}\n\tswitch numArgs {\n\tcase 0:\n\t\tif data.Size() == 0 {\n\t\t\treturn StatusNoDatabases\n\t\t}\n\t\tdata.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tks := value.(*KeyStore)\n\t\t\tresponse += ufmt.Sprintf(\"- [%s](%s:%s) (%d keys)\\n\", ks.Owner, BaseURL, ks.Owner, ks.Data.Size())\n\t\t\treturn false\n\t\t})\n\tcase 1:\n\t\towner := args[0]\n\t\tkeystoreInterface, exists := data.Get(owner)\n\t\tif !exists {\n\t\t\treturn StatusNoUser\n\t\t}\n\t\tks := keystoreInterface.(*KeyStore)\n\t\ti := 0\n\t\tresponse += ufmt.Sprintf(\"# %s database\\n\\n\", ks.Owner)\n\t\tks.Data.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tresponse += ufmt.Sprintf(\"- %d [%s](%s:%s:get:%s)\\n\", i, key, BaseURL, ks.Owner, key)\n\t\t\ti++\n\t\t\treturn false\n\t\t})\n\tcase 2:\n\t\towner := args[0]\n\t\tcmd := args[1]\n\t\tif cmd == \"size\" {\n\t\t\treturn size(owner)\n\t\t}\n\tcase 3:\n\t\towner := args[0]\n\t\tcmd := args[1]\n\t\tkey := args[2]\n\t\tif cmd == \"get\" {\n\t\t\treturn get(owner, key)\n\t\t}\n\t}\n\n\treturn response\n}\n"},{"name":"keystore_test.gno","body":"package keystore\n\nimport (\n\t\"std\"\n\t\"strings\"\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 TestRender(t *testing.T) {\n\tconst (\n\t\tauthor1 std.Address = testutils.TestAddress(\"author1\")\n\t\tauthor2 std.Address = testutils.TestAddress(\"author2\")\n\t)\n\n\ttt := []struct {\n\t\tcaller std.Address\n\t\towner std.Address\n\t\tps []string\n\t\texp string\n\t}{\n\t\t// can set database if the owner is the caller\n\t\t{author1, author1, []string{\"set\", \"hello\", \"gno\"}, StatusOK},\n\t\t{author1, author1, []string{\"size\"}, \"1\"},\n\t\t{author1, author1, []string{\"set\", \"hello\", \"world\"}, StatusOK},\n\t\t{author1, author1, []string{\"size\"}, \"1\"},\n\t\t{author1, author1, []string{\"set\", \"hi\", \"gno\"}, StatusOK},\n\t\t{author1, author1, []string{\"size\"}, \"2\"},\n\t\t// only owner can remove\n\t\t{author1, author1, []string{\"remove\", \"hi\"}, StatusOK},\n\t\t{author1, author1, []string{\"get\", \"hi\"}, StatusNotFound},\n\t\t{author1, author1, []string{\"size\"}, \"1\"},\n\t\t// add back\n\t\t{author1, author1, []string{\"set\", \"hi\", \"gno\"}, StatusOK},\n\t\t{author1, author1, []string{\"size\"}, \"2\"},\n\n\t\t// different owner has different database\n\t\t{author2, author2, []string{\"set\", \"hello\", \"universe\"}, StatusOK},\n\t\t// either author can get the other info\n\t\t{author1, author2, []string{\"get\", \"hello\"}, \"universe\"},\n\t\t// either author can get the other info\n\t\t{author2, author1, []string{\"get\", \"hello\"}, \"world\"},\n\t\t{author1, author2, []string{\"get\", \"hello\"}, \"universe\"},\n\t\t// anyone can view the databases\n\t\t{author1, author2, []string{}, `- [g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6](/r/demo/keystore:g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6) (2 keys)\n- [g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00](/r/demo/keystore:g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00) (1 keys)`},\n\t\t// anyone can view the keys in a database\n\t\t{author1, author2, []string{\"\"}, `# g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00 database\n\n- 0 [hello](/r/demo/keystore:g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00:get:hello)`},\n\t}\n\tfor _, tc := range tt {\n\t\tp := \"\"\n\t\tif len(tc.ps) \u003e 0 {\n\t\t\tp = tc.owner.String()\n\t\t\tfor i, psv := range tc.ps {\n\t\t\t\tp += \":\" + psv\n\t\t\t}\n\t\t}\n\t\tp = strings.TrimSuffix(p, \":\")\n\t\tt.Run(p, func(t *testing.T) {\n\t\t\tstd.TestSetOrigCaller(tc.caller)\n\t\t\tvar act string\n\t\t\tif len(tc.ps) \u003e 0 \u0026\u0026 tc.ps[0] == \"set\" {\n\t\t\t\tact = strings.TrimSpace(Set(tc.ps[1], tc.ps[2]))\n\t\t\t} else if len(tc.ps) \u003e 0 \u0026\u0026 tc.ps[0] == \"remove\" {\n\t\t\t\tact = strings.TrimSpace(Remove(tc.ps[1]))\n\t\t\t} else {\n\t\t\t\tact = strings.TrimSpace(Render(p))\n\t\t\t}\n\n\t\t\tuassert.Equal(t, tc.exp, act, ufmt.Sprintf(\"%v -\u003e '%s'\", tc.ps, p))\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":"markdown","path":"gno.land/r/demo/markdown_test","files":[{"name":"markdown.gno","body":"package markdown\n\n// this package can be used to test markdown rendering engines.\n\nfunc Render(path string) string {\n\toutput := `_imported from https://github.com/markedjs/marked/blob/master/docs/demo/quickref.md_\n\nMarkdown Quick Reference\n========================\n\nThis guide is a very brief overview, with examples, of the syntax that [Markdown] supports. It is itself written in Markdown and you can copy the samples over to the left-hand pane for experimentation. It's shown as *text* and not *rendered HTML*.\n\n[Markdown]: http://daringfireball.net/projects/markdown/\n\n\nSimple Text Formatting\n======================\n\nFirst thing is first. You can use *stars* or _underscores_ for italics. **Double stars** and __double underscores__ for bold. ***Three together*** for ___both___.\n\nParagraphs are pretty easy too. Just have a blank line between chunks of text.\n\n\u003e This chunk of text is in a block quote. Its multiple lines will all be\n\u003e indented a bit from the rest of the text.\n\u003e\n\u003e \u003e Multiple levels of block quotes also work.\n\nSometimes you want to include code, such as when you are explaining how ` + \"`\u003ch1\u003e`\" + ` HTML tags work, or maybe you are a programmer and you are discussing ` + \"`someMethod()`\" + `.\n\nIf you want to include code and have new\nlines preserved, indent the line with a tab\nor at least four spaces:\n\n Extra spaces work here too.\n This is also called preformatted text and it is useful for showing examples.\n The text will stay as text, so any *markdown* or \u003cu\u003eHTML\u003c/u\u003e you add will\n not show up formatted. This way you can show markdown examples in a\n markdown document.\n\n\u003e You can also use preformatted text with your blockquotes\n\u003e as long as you add at least five spaces.\n\n\nHeadings\n========\n\nThere are a couple of ways to make headings. Using three or more equals signs on a line under a heading makes it into an \"h1\" style. Three or more hyphens under a line makes it \"h2\" (slightly smaller). You can also use multiple pound symbols (` + \"`#`\" + `) before and after a heading. Pounds after the title are ignored. Here are some examples:\n\nThis is H1\n==========\n\nThis is H2\n----------\n\n# This is H1\n## This is H2\n### This is H3 with some extra pounds ###\n#### You get the idea ####\n##### I don't need extra pounds at the end\n###### H6 is the max\n\n\nLinks\n=====\n\nLet's link to a few sites. First, let's use the bare URL, like \u003chttps://www.github.com\u003e. Great for text, but ugly for HTML.\nNext is an inline link to [Google](https://www.google.com). A little nicer.\nThis is a reference-style link to [Wikipedia] [1].\nLastly, here's a pretty link to [Yahoo]. The reference-style and pretty links both automatically use the links defined below, but they could be defined *anywhere* in the markdown and are removed from the HTML. The names are also case insensitive, so you can use [YaHoO] and have it link properly.\n\n[1]: https://www.wikipedia.org\n[Yahoo]: https://www.yahoo.com\n\nTitle attributes may be added to links by adding text after a link.\nThis is the [inline link](https://www.bing.com \"Bing\") with a \"Bing\" title.\nYou can also go to [W3C] [2] and maybe visit a [friend].\n\n[2]: https://w3c.org (The W3C puts out specs for web-based things)\n[Friend]: https://facebook.com \"Facebook!\"\n\nEmail addresses in plain text are not linked: test@example.com.\nEmail addresses wrapped in angle brackets are linked: \u003ctest@example.com\u003e.\nThey are also obfuscated so that email harvesting spam robots hopefully won't get them.\n\n\nLists\n=====\n\n* This is a bulleted list\n* Great for shopping lists\n- You can also use hyphens\n+ Or plus symbols\n\nThe above is an \"unordered\" list. Now, on for a bit of order.\n\n1. Numbered lists are also easy\n2. Just start with a number\n3738762. However, the actual number doesn't matter when converted to HTML.\n1. This will still show up as 4.\n\nYou might want a few advanced lists:\n\n- This top-level list is wrapped in paragraph tags\n- This generates an extra space between each top-level item.\n\n- You do it by adding a blank line\n\n- This nested list also has blank lines between the list items.\n\n- How to create nested lists\n 1. Start your regular list\n 2. Indent nested lists with two spaces\n 3. Further nesting means you should indent with two more spaces\n * This line is indented with four spaces.\n\n- List items can be quite lengthy. You can keep typing and either continue\nthem on the next line with no indentation.\n\n- Alternately, if that looks ugly, you can also\n indent the next line a bit for a prettier look.\n\n- You can put large blocks of text in your list by just indenting with two spaces.\n\n This is formatted the same as code, but you can inspect the HTML\n and find that it's just wrapped in a ` + \"`\u003cp\u003e`\" + ` tag and *won't* be shown\n as preformatted text.\n\n You can keep adding more and more paragraphs to a single\n list item by adding the traditional blank line and then keep\n on indenting the paragraphs with two spaces.\n\n You really only need to indent the first line,\nbut that looks ugly.\n\n- Lists support blockquotes\n\n \u003e Just like this example here. By the way, you can\n \u003e nest lists inside blockquotes!\n \u003e - Fantastic!\n\n- Lists support preformatted text\n\n You just need to indent an additional four spaces.\n\n\nEven More\n=========\n\nHorizontal Rule\n---------------\n\nIf you need a horizontal rule you just need to put at least three hyphens, asterisks, or underscores on a line by themselves. You can also even put spaces between the characters.\n\n---\n****************************\n_ _ _ _ _ _ _\n\nThose three all produced horizontal lines. Keep in mind that three hyphens under any text turns that text into a heading, so add a blank like if you use hyphens.\n\nImages\n------\n\nImages work exactly like links, but they have exclamation points in front. They work with references and titles too.\n\n![Google Logo](https://www.google.com/images/errors/logo_sm.gif) and ![Happy].\n\n[Happy]: https://wpclipart.com/smiley/happy/simple_colors/smiley_face_simple_green_small.png (\"Smiley face\")\n\n\nInline HTML\n-----------\n\nIf markdown is too limiting, you can just insert your own \u003cstrike\u003ecrazy\u003c/strike\u003e HTML. Span-level HTML \u003cu\u003ecan *still* use markdown\u003c/u\u003e. Block level elements must be separated from text by a blank line and must not have any spaces before the opening and closing HTML.\n\n\u003cdiv style='font-family: \"Comic Sans MS\", \"Comic Sans\", cursive;'\u003e\nIt is a pity, but markdown does **not** work in here for most markdown parsers.\n[Marked] handles it pretty well.\n\u003c/div\u003e`\n\treturn output\n}\n"},{"name":"markdown_test.gno","body":"package markdown\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestRender(t *testing.T) {\n\toutput := Render(\"\")\n\tif !strings.Contains(output, \"\\nMarkdown Quick Reference\\n\") {\n\t\tt.Errorf(\"invalid output\")\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":"eval","path":"gno.land/r/demo/math_eval","files":[{"name":"math_eval.gno","body":"// eval realm is capable of evaluating 32-bit integer\n// expressions as they would appear in Go. For example:\n// /r/demo/math_eval:(4+12)/2-1+11*15\npackage eval\n\nimport (\n\tevalint32 \"gno.land/p/demo/math_eval/int32\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nfunc Render(p string) string {\n\tif len(p) == 0 {\n\t\treturn `\nevaluates 32-bit integer expressions. for example:\n\t\t\n[(4+12)/2-1+11*15](/r/demo/math_eval:(4+12)/2-1+11*15)\n\n`\n\t}\n\texpr, err := evalint32.Parse(p)\n\tif err != nil {\n\t\treturn err.Error()\n\t}\n\tres, err := evalint32.Eval(expr, nil)\n\tif err != nil {\n\t\treturn err.Error()\n\t}\n\n\treturn ufmt.Sprintf(\"%s = %d\", p, res)\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":"memeland","path":"gno.land/r/demo/memeland","files":[{"name":"memeland.gno","body":"package memeland\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/demo/memeland\"\n)\n\nvar m *memeland.Memeland\n\nfunc init() {\n\tm = memeland.NewMemeland()\n\tm.TransferOwnership(\"g125em6arxsnj49vx35f0n0z34putv5ty3376fg5\")\n}\n\nfunc PostMeme(data string, timestamp int64) string {\n\treturn m.PostMeme(data, timestamp)\n}\n\nfunc Upvote(id string) string {\n\treturn m.Upvote(id)\n}\n\nfunc GetPostsInRange(startTimestamp, endTimestamp int64, page, pageSize int, sortBy string) string {\n\treturn m.GetPostsInRange(startTimestamp, endTimestamp, page, pageSize, sortBy)\n}\n\nfunc RemovePost(id string) string {\n\treturn m.RemovePost(id)\n}\n\nfunc GetOwner() std.Address {\n\treturn m.Owner()\n}\n\nfunc TransferOwnership(newOwner std.Address) {\n\tif err := m.TransferOwnership(newOwner); err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc Render(path string) string {\n\tnumOfMemes := int(m.MemeCounter)\n\tif numOfMemes == 0 {\n\t\treturn \"No memes posted yet! :/\"\n\t}\n\n\t// Default render is get Posts since year 2000 to now\n\treturn m.GetPostsInRange(0, time.Now().Unix(), 1, 10, \"DATE_CREATED\")\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":"microblog","path":"gno.land/r/demo/microblog","files":[{"name":"README.md","body":"# microblog realm\n\n## Getting started:\n\n(One-time) Add the microblog package:\n\n```\ngnokey maketx addpkg --pkgpath \"gno.land/p/demo/microblog\" --pkgdir \"examples/gno.land/p/demo/microblog\" \\\n --deposit 100000000ugnot --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast --chainid dev --remote localhost:26657 \u003cYOURKEY\u003e\n```\n\n(One-time) Add the microblog realm:\n\n```\ngnokey maketx addpkg --pkgpath \"gno.land/r/demo/microblog\" --pkgdir \"examples/gno.land/r/demo/microblog\" \\\n --deposit 100000000ugnot --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast --chainid dev --remote localhost:26657 \u003cYOURKEY\u003e\n```\n\nAdd a microblog post:\n\n```\ngnokey maketx call --pkgpath \"gno.land/r/demo/microblog\" --func \"NewPost\" --args \"hello, world\" \\\n --gas-fee \"1000000ugnot\" --gas-wanted \"2000000\" --broadcast --chainid dev --remote localhost:26657 \u003cYOURKEY\u003e\n```"},{"name":"microblog.gno","body":"// Microblog is a website with shortform posts from users.\n// The API is simple - \"AddPost\" takes markdown and\n// adds it to the users site.\n// The microblog location is determined by the user address\n// /r/demo/microblog:\u003cYOUR-ADDRESS\u003e\npackage microblog\n\nimport (\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/microblog\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\ttitle = \"gno-based microblog\"\n\tprefix = \"/r/demo/microblog:\"\n\tm *microblog.Microblog\n)\n\nfunc init() {\n\tm = microblog.NewMicroblog(title, prefix)\n}\n\nfunc renderHome() string {\n\toutput := ufmt.Sprintf(\"# %s\\n\\n\", m.Title)\n\toutput += \"# pages\\n\\n\"\n\n\tfor _, page := range m.GetPages() {\n\t\tif u := users.GetUserByAddress(page.Author); u != nil {\n\t\t\toutput += ufmt.Sprintf(\"- [%s (%s)](%s%s)\\n\", u.Name, page.Author.String(), m.Prefix, page.Author.String())\n\t\t} else {\n\t\t\toutput += ufmt.Sprintf(\"- [%s](%s%s)\\n\", page.Author.String(), m.Prefix, page.Author.String())\n\t\t}\n\t}\n\n\treturn output\n}\n\nfunc renderUser(user string) string {\n\tsilo, found := m.Pages.Get(user)\n\tif !found {\n\t\treturn \"404\" // StatusNotFound\n\t}\n\n\treturn PageToString((silo.(*microblog.Page)))\n}\n\nfunc Render(path string) string {\n\tparts := strings.Split(path, \"/\")\n\n\tisHome := path == \"\"\n\tisUser := len(parts) == 1\n\n\tswitch {\n\tcase isHome:\n\t\treturn renderHome()\n\n\tcase isUser:\n\t\treturn renderUser(parts[0])\n\t}\n\n\treturn \"404\" // StatusNotFound\n}\n\nfunc PageToString(p *microblog.Page) string {\n\to := \"\"\n\tif u := users.GetUserByAddress(p.Author); u != nil {\n\t\to += ufmt.Sprintf(\"# [%s](/r/demo/users:%s)\\n\\n\", u, u)\n\t\to += ufmt.Sprintf(\"%s\\n\\n\", u.Profile)\n\t}\n\to += ufmt.Sprintf(\"## [%s](/r/demo/microblog:%s)\\n\\n\", p.Author, p.Author)\n\n\to += ufmt.Sprintf(\"joined %s, last updated %s\\n\\n\", p.CreatedAt.Format(\"2006-02-01\"), p.LastPosted.Format(\"2006-02-01\"))\n\to += \"## feed\\n\\n\"\n\tfor _, u := range p.GetPosts() {\n\t\to += u.String() + \"\\n\\n\"\n\t}\n\treturn o\n}\n\n// NewPost takes a single argument (post markdown) and\n// adds a post to the address of the caller.\nfunc NewPost(text string) string {\n\tif err := m.NewPost(text); err != nil {\n\t\treturn \"unable to add new post\"\n\t}\n\treturn \"added new post\"\n}\n\nfunc Register(name, profile string) string {\n\tcaller := std.GetOrigCaller() // main\n\tusers.Register(caller, name, profile)\n\treturn \"OK\"\n}\n"},{"name":"microblog_test.gno","body":"package microblog\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestMicroblog(t *testing.T) {\n\tconst (\n\t\tauthor1 std.Address = testutils.TestAddress(\"author1\")\n\t\tauthor2 std.Address = testutils.TestAddress(\"author2\")\n\t)\n\n\tstd.TestSetOrigCaller(author1)\n\n\turequire.Equal(t, \"404\", Render(\"/wrongpath\"), \"rendering not giving 404\")\n\turequire.NotEqual(t, \"404\", Render(\"\"), \"rendering / should not give 404\")\n\turequire.NoError(t, m.NewPost(\"goodbyte, web2\"), \"could not create post\")\n\n\t_, err := m.GetPage(author1.String())\n\turequire.NoError(t, err, \"silo should exist\")\n\n\t_, err = m.GetPage(\"no such author\")\n\turequire.Error(t, err, \"silo should not exist\")\n\n\tstd.TestSetOrigCaller(author2)\n\n\turequire.NoError(t, m.NewPost(\"hello, web3\"), \"could not create post\")\n\turequire.NoError(t, m.NewPost(\"hello again, web3\"), \"could not create post\")\n\turequire.NoError(t, m.NewPost(\"hi again,\\n web4?\"), \"could not create post\")\n\n\tprintln(\"--- MICROBLOG ---\\n\\n\")\n\n\texpected := `# gno-based microblog\n\n# pages\n\n- [g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6](/r/demo/microblog:g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6)\n- [g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00](/r/demo/microblog:g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00)\n`\n\turequire.Equal(t, expected, Render(\"\"), \"incorrect rendering\")\n\n\texpected = `## [g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6](/r/demo/microblog:g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6)\n\njoined 2009-13-02, last updated 2009-13-02\n\n## feed\n\n\u003e goodbyte, web2\n\u003e\n\u003e *Fri, 13 Feb 2009 23:31:30 UTC*`\n\n\turequire.Equal(t, expected, strings.TrimSpace(Render(author1.String())), \"incorrect rendering\")\n\n\texpected = `## [g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00](/r/demo/microblog:g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00)\n\njoined 2009-13-02, last updated 2009-13-02\n\n## feed\n\n\u003e hi again,\n\u003e\n\u003e web4?\n\u003e\n\u003e *Fri, 13 Feb 2009 23:31:30 UTC*\n\n\u003e hello again, web3\n\u003e\n\u003e *Fri, 13 Feb 2009 23:31:30 UTC*\n\n\u003e hello, web3\n\u003e\n\u003e *Fri, 13 Feb 2009 23:31:30 UTC*`\n\n\turequire.Equal(t, expected, strings.TrimSpace(Render(author2.String())), \"incorrect rendering\")\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":"microblog","path":"gno.land/r/demo/microblog","files":[{"name":"README.md","body":"# microblog realm\n\n## Getting started:\n\n(One-time) Add the microblog package:\n\n```\ngnokey maketx addpkg --pkgpath \"gno.land/p/demo/microblog\" --pkgdir \"examples/gno.land/p/demo/microblog\" \\\n --deposit 100000000ugnot --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast --chainid dev --remote localhost:26657 \u003cYOURKEY\u003e\n```\n\n(One-time) Add the microblog realm:\n\n```\ngnokey maketx addpkg --pkgpath \"gno.land/r/demo/microblog\" --pkgdir \"examples/gno.land/r/demo/microblog\" \\\n --deposit 100000000ugnot --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast --chainid dev --remote localhost:26657 \u003cYOURKEY\u003e\n```\n\nAdd a microblog post:\n\n```\ngnokey maketx call --pkgpath \"gno.land/r/demo/microblog\" --func \"NewPost\" --args \"hello, world\" \\\n --gas-fee \"1000000ugnot\" --gas-wanted \"2000000\" --broadcast --chainid dev --remote localhost:26657 \u003cYOURKEY\u003e\n```"},{"name":"microblog.gno","body":"// Microblog is a website with shortform posts from users.\n// The API is simple - \"AddPost\" takes markdown and\n// adds it to the users site.\n// The microblog location is determined by the user address\n// /r/demo/microblog:\u003cYOUR-ADDRESS\u003e\npackage microblog\n\nimport (\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/microblog\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\ttitle = \"gno-based microblog\"\n\tprefix = \"/r/demo/microblog:\"\n\tm *microblog.Microblog\n)\n\nfunc init() {\n\tm = microblog.NewMicroblog(title, prefix)\n}\n\nfunc renderHome() string {\n\toutput := ufmt.Sprintf(\"# %s\\n\\n\", m.Title)\n\toutput += \"# pages\\n\\n\"\n\n\tfor _, page := range m.GetPages() {\n\t\tif u := users.GetUserByAddress(page.Author); u != nil {\n\t\t\toutput += ufmt.Sprintf(\"- [%s (%s)](%s%s)\\n\", u.Name, page.Author.String(), m.Prefix, page.Author.String())\n\t\t} else {\n\t\t\toutput += ufmt.Sprintf(\"- [%s](%s%s)\\n\", page.Author.String(), m.Prefix, page.Author.String())\n\t\t}\n\t}\n\n\treturn output\n}\n\nfunc renderUser(user string) string {\n\tsilo, found := m.Pages.Get(user)\n\tif !found {\n\t\treturn \"404\" // StatusNotFound\n\t}\n\n\treturn PageToString((silo.(*microblog.Page)))\n}\n\nfunc Render(path string) string {\n\tparts := strings.Split(path, \"/\")\n\n\tisHome := path == \"\"\n\tisUser := len(parts) == 1\n\n\tswitch {\n\tcase isHome:\n\t\treturn renderHome()\n\n\tcase isUser:\n\t\treturn renderUser(parts[0])\n\t}\n\n\treturn \"404\" // StatusNotFound\n}\n\nfunc PageToString(p *microblog.Page) string {\n\to := \"\"\n\tif u := users.GetUserByAddress(p.Author); u != nil {\n\t\to += ufmt.Sprintf(\"# [%s](/r/demo/users:%s)\\n\\n\", u, u)\n\t\to += ufmt.Sprintf(\"%s\\n\\n\", u.Profile)\n\t}\n\to += ufmt.Sprintf(\"## [%s](/r/demo/microblog:%s)\\n\\n\", p.Author, p.Author)\n\n\to += ufmt.Sprintf(\"joined %s, last updated %s\\n\\n\", p.CreatedAt.Format(\"2006-02-01\"), p.LastPosted.Format(\"2006-02-01\"))\n\to += \"## feed\\n\\n\"\n\tfor _, u := range p.GetPosts() {\n\t\to += u.String() + \"\\n\\n\"\n\t}\n\treturn o\n}\n\n// NewPost takes a single argument (post markdown) and\n// adds a post to the address of the caller.\nfunc NewPost(text string) string {\n\tif err := m.NewPost(text); err != nil {\n\t\treturn \"unable to add new post\"\n\t}\n\treturn \"added new post\"\n}\n\nfunc Register(name, profile string) string {\n\tcaller := std.OrigCaller() // main\n\tusers.Register(caller, name, profile)\n\treturn \"OK\"\n}\n"},{"name":"microblog_test.gno","body":"package microblog\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestMicroblog(t *testing.T) {\n\tconst (\n\t\tauthor1 std.Address = testutils.TestAddress(\"author1\")\n\t\tauthor2 std.Address = testutils.TestAddress(\"author2\")\n\t)\n\n\tstd.TestSetOrigCaller(author1)\n\n\turequire.Equal(t, \"404\", Render(\"/wrongpath\"), \"rendering not giving 404\")\n\turequire.NotEqual(t, \"404\", Render(\"\"), \"rendering / should not give 404\")\n\turequire.NoError(t, m.NewPost(\"goodbyte, web2\"), \"could not create post\")\n\n\t_, err := m.GetPage(author1.String())\n\turequire.NoError(t, err, \"silo should exist\")\n\n\t_, err = m.GetPage(\"no such author\")\n\turequire.Error(t, err, \"silo should not exist\")\n\n\tstd.TestSetOrigCaller(author2)\n\n\turequire.NoError(t, m.NewPost(\"hello, web3\"), \"could not create post\")\n\turequire.NoError(t, m.NewPost(\"hello again, web3\"), \"could not create post\")\n\turequire.NoError(t, m.NewPost(\"hi again,\\n web4?\"), \"could not create post\")\n\n\tprintln(\"--- MICROBLOG ---\\n\\n\")\n\n\texpected := `# gno-based microblog\n\n# pages\n\n- [g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6](/r/demo/microblog:g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6)\n- [g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00](/r/demo/microblog:g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00)\n`\n\turequire.Equal(t, expected, Render(\"\"), \"incorrect rendering\")\n\n\texpected = `## [g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6](/r/demo/microblog:g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6)\n\njoined 2009-13-02, last updated 2009-13-02\n\n## feed\n\n\u003e goodbyte, web2\n\u003e\n\u003e *Fri, 13 Feb 2009 23:31:30 UTC*`\n\n\turequire.Equal(t, expected, strings.TrimSpace(Render(author1.String())), \"incorrect rendering\")\n\n\texpected = `## [g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00](/r/demo/microblog:g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00)\n\njoined 2009-13-02, last updated 2009-13-02\n\n## feed\n\n\u003e hi again,\n\u003e\n\u003e web4?\n\u003e\n\u003e *Fri, 13 Feb 2009 23:31:30 UTC*\n\n\u003e hello again, web3\n\u003e\n\u003e *Fri, 13 Feb 2009 23:31:30 UTC*\n\n\u003e hello, web3\n\u003e\n\u003e *Fri, 13 Feb 2009 23:31:30 UTC*`\n\n\turequire.Equal(t, expected, strings.TrimSpace(Render(author2.String())), \"incorrect rendering\")\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":"nft","path":"gno.land/r/demo/nft","files":[{"name":"README.md","body":"NFT's are all the rage these days, for various reasons.\n\nI read over EIP-721 which appears to be the de-facto NFT standard on Ethereum. Then, made a sample implementation of EIP-721 (let's here called GRC-721). The implementation isn't complete, but it demonstrates the main functionality.\n\n- [EIP-721](https://eips.ethereum.org/EIPS/eip-721)\n- [gno.land/r/demo/nft/nft.go](https://gno.land/r/demo/nft/nft.go)\n- [zrealm_nft3.go test](https://github.com/gnolang/gno/blob/master/tests/files2/zrealm_nft3.gno)\n\nIn short, this demonstrates how to implement Ethereum contract interfaces in gno.land; by using only standard Go language features.\n\nPlease leave a comment ([guide](https://gno.land/r/demo/boards:gnolang/1)).\n"},{"name":"nft.gno","body":"package nft\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/grc/grc721\"\n)\n\ntype token struct {\n\tgrc721.IGRC721 // implements the GRC721 interface\n\n\ttokenCounter int\n\ttokens avl.Tree // grc721.TokenID -\u003e *NFToken{}\n\toperators avl.Tree // owner std.Address -\u003e operator std.Address\n}\n\ntype NFToken struct {\n\towner std.Address\n\tapproved std.Address\n\ttokenID grc721.TokenID\n\tdata string\n}\n\nvar gToken = \u0026token{}\n\nfunc GetToken() *token { return gToken }\n\nfunc (grc *token) nextTokenID() grc721.TokenID {\n\tgrc.tokenCounter++\n\ts := strconv.Itoa(grc.tokenCounter)\n\treturn grc721.TokenID(s)\n}\n\nfunc (grc *token) getToken(tid grc721.TokenID) (*NFToken, bool) {\n\ttoken, ok := grc.tokens.Get(string(tid))\n\tif !ok {\n\t\treturn nil, false\n\t}\n\treturn token.(*NFToken), true\n}\n\nfunc (grc *token) Mint(to std.Address, data string) grc721.TokenID {\n\ttid := grc.nextTokenID()\n\tgrc.tokens.Set(string(tid), \u0026NFToken{\n\t\towner: to,\n\t\ttokenID: tid,\n\t\tdata: data,\n\t})\n\treturn tid\n}\n\nfunc (grc *token) BalanceOf(owner std.Address) (count int64) {\n\tpanic(\"not yet implemented\")\n}\n\nfunc (grc *token) OwnerOf(tid grc721.TokenID) std.Address {\n\ttoken, ok := grc.getToken(tid)\n\tif !ok {\n\t\tpanic(\"token does not exist\")\n\t}\n\treturn token.owner\n}\n\n// XXX not fully implemented yet.\nfunc (grc *token) SafeTransferFrom(from, to std.Address, tid grc721.TokenID) {\n\tgrc.TransferFrom(from, to, tid)\n\t// When transfer is complete, this function checks if `_to` is a smart\n\t// contract (code size \u003e 0). If so, it calls `onERC721Received` on\n\t// `_to` and throws if the return value is not\n\t// `bytes4(keccak256(\"onERC721Received(address,address,uint256,bytes)\"))`.\n\t// XXX ensure \"to\" is a realm with onERC721Received() signature.\n}\n\nfunc (grc *token) TransferFrom(from, to std.Address, tid grc721.TokenID) {\n\tcaller := std.GetCallerAt(2)\n\ttoken, ok := grc.getToken(tid)\n\t// Throws if `_tokenId` is not a valid NFT.\n\tif !ok {\n\t\tpanic(\"token does not exist\")\n\t}\n\t// Throws unless `msg.sender` is the current owner, an authorized\n\t// operator, or the approved address for this NFT.\n\tif caller != token.owner \u0026\u0026 caller != token.approved {\n\t\toperator, ok := grc.operators.Get(token.owner.String())\n\t\tif !ok || caller != operator.(std.Address) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t}\n\t// Throws if `_from` is not the current owner.\n\tif from != token.owner {\n\t\tpanic(\"from is not the current owner\")\n\t}\n\t// Throws if `_to` is the zero address.\n\tif to == \"\" {\n\t\tpanic(\"to cannot be empty\")\n\t}\n\t// Good.\n\ttoken.owner = to\n}\n\nfunc (grc *token) Approve(approved std.Address, tid grc721.TokenID) {\n\tcaller := std.GetCallerAt(2)\n\ttoken, ok := grc.getToken(tid)\n\t// Throws if `_tokenId` is not a valid NFT.\n\tif !ok {\n\t\tpanic(\"token does not exist\")\n\t}\n\t// Throws unless `msg.sender` is the current owner,\n\t// or an authorized operator.\n\tif caller != token.owner {\n\t\toperator, ok := grc.operators.Get(token.owner.String())\n\t\tif !ok || caller != operator.(std.Address) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t}\n\t// Good.\n\ttoken.approved = approved\n}\n\n// XXX make it work for set of operators.\nfunc (grc *token) SetApprovalForAll(operator std.Address, approved bool) {\n\tcaller := std.GetCallerAt(2)\n\tgrc.operators.Set(caller.String(), operator)\n}\n\nfunc (grc *token) GetApproved(tid grc721.TokenID) std.Address {\n\ttoken, ok := grc.getToken(tid)\n\t// Throws if `_tokenId` is not a valid NFT.\n\tif !ok {\n\t\tpanic(\"token does not exist\")\n\t}\n\treturn token.approved\n}\n\n// XXX make it work for set of operators\nfunc (grc *token) IsApprovedForAll(owner, operator std.Address) bool {\n\toperator2, ok := grc.operators.Get(owner.String())\n\tif !ok {\n\t\treturn false\n\t}\n\treturn operator == operator2.(std.Address)\n}\n"},{"name":"z_0_filetest.gno","body":"// PKGPATH: gno.land/r/demo/nft_test\npackage nft_test\n\nimport (\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/nft\"\n)\n\nfunc main() {\n\taddr1 := testutils.TestAddress(\"addr1\")\n\t// addr2 := testutils.TestAddress(\"addr2\")\n\tgrc721 := nft.GetToken()\n\ttid := grc721.Mint(addr1, \"NFT#1\")\n\tprintln(grc721.OwnerOf(tid))\n\tprintln(addr1)\n}\n\n// Output:\n// g1v9jxgu33ta047h6lta047h6lta047h6l43dqc5\n// g1v9jxgu33ta047h6lta047h6lta047h6l43dqc5\n\n// Realm:\n// switchrealm[\"gno.land/r/demo/nft\"]\n// switchrealm[\"gno.land/r/demo/nft\"]\n// c[67c479d3d51d4056b2f4111d5352912a00be311e:11]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"std.Address\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"g1v9jxgu33ta047h6lta047h6lta047h6l43dqc5\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"std.Address\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/grc/grc721.TokenID\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"1\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"NFT#1\"\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:11\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:10\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[67c479d3d51d4056b2f4111d5352912a00be311e:10]={\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:10\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:9\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/nft.NFToken\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"564a9e78be869bd258fc3c9ad56f5a75ed68818f\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:11\"\n// }\n// }\n// }\n// c[67c479d3d51d4056b2f4111d5352912a00be311e:9]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"1\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/nft.NFToken\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"b53ffc464e1b5655d19b9d5277f3491717c24aca\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:10\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:9\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:8\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[67c479d3d51d4056b2f4111d5352912a00be311e:8]={\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:8\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:5\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"b1d928b3716b147c92730e8d234162bec2f0f2fc\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:9\"\n// }\n// }\n// }\n// u[67c479d3d51d4056b2f4111d5352912a00be311e:5]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"b229b824842ec3e7f2341e33d0fa0ca77af2f480\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:8\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:5\",\n// \"ModTime\": \"7\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:4\",\n// \"RefCount\": \"1\"\n// }\n// }\n// u[67c479d3d51d4056b2f4111d5352912a00be311e:4]={\n// \"Fields\": [\n// {},\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"1e0b9dddb406b4f50500a022266a4cb8a4ea38c6\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:5\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"05ab6746ea84b55ca133806af215d99a1c4b045e\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:6\"\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:4\",\n// \"ModTime\": \"7\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:3\",\n// \"RefCount\": \"1\"\n// }\n// }\n// switchrealm[\"gno.land/r/demo/nft\"]\n// switchrealm[\"gno.land/r/demo/nft_test\"]\n"},{"name":"z_1_filetest.gno","body":"// PKGPATH: gno.land/r/demo/nft_test\npackage nft_test\n\nimport (\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/nft\"\n)\n\nfunc main() {\n\taddr1 := testutils.TestAddress(\"addr1\")\n\taddr2 := testutils.TestAddress(\"addr2\")\n\tgrc721 := nft.GetToken()\n\ttid := grc721.Mint(addr1, \"NFT#1\")\n\tprintln(grc721.OwnerOf(tid))\n\tprintln(addr1)\n\tgrc721.TransferFrom(addr1, addr2, tid)\n}\n\n// Error:\n// unauthorized\n"},{"name":"z_2_filetest.gno","body":"// PKGPATH: gno.land/r/demo/nft_test\npackage nft_test\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/nft\"\n)\n\nfunc main() {\n\tcaller := std.GetCallerAt(1)\n\taddr1 := testutils.TestAddress(\"addr1\")\n\t// addr2 := testutils.TestAddress(\"addr2\")\n\tgrc721 := nft.GetToken()\n\ttid := grc721.Mint(caller, \"NFT#1\")\n\tprintln(grc721.OwnerOf(tid))\n\tprintln(addr1)\n\tgrc721.TransferFrom(caller, addr1, tid)\n}\n\n// Output:\n// g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n// g1v9jxgu33ta047h6lta047h6lta047h6l43dqc5\n"},{"name":"z_3_filetest.gno","body":"// PKGPATH: gno.land/r/demo/nft_test\npackage nft_test\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/nft\"\n)\n\nfunc main() {\n\tcaller := std.GetCallerAt(1)\n\taddr1 := testutils.TestAddress(\"addr1\")\n\taddr2 := testutils.TestAddress(\"addr2\")\n\tgrc721 := nft.GetToken()\n\ttid := grc721.Mint(caller, \"NFT#1\")\n\tprintln(grc721.OwnerOf(tid))\n\tprintln(addr1)\n\tgrc721.Approve(caller, tid) // approve self.\n\tgrc721.TransferFrom(caller, addr1, tid)\n\tgrc721.TransferFrom(addr1, addr2, tid)\n}\n\n// Output:\n// g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n// g1v9jxgu33ta047h6lta047h6lta047h6l43dqc5\n"},{"name":"z_4_filetest.gno","body":"// PKGPATH: gno.land/r/demo/nft_test\npackage nft_test\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/nft\"\n)\n\nfunc main() {\n\tcaller := std.GetCallerAt(1)\n\taddr1 := testutils.TestAddress(\"addr1\")\n\taddr2 := testutils.TestAddress(\"addr2\")\n\tgrc721 := nft.GetToken()\n\ttid := grc721.Mint(caller, \"NFT#1\")\n\tprintln(grc721.OwnerOf(tid))\n\tprintln(addr1)\n\tgrc721.Approve(caller, tid) // approve self.\n\tgrc721.TransferFrom(caller, addr1, tid)\n\tgrc721.Approve(\"\", tid) // approve addr1.\n\tgrc721.TransferFrom(addr1, addr2, tid)\n}\n\n// Error:\n// unauthorized\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":"profile","path":"gno.land/r/demo/profile","files":[{"name":"profile.gno","body":"package profile\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/mux\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar (\n\tfields = avl.NewTree()\n\trouter = mux.NewRouter()\n)\n\n// Standard fields\nconst (\n\tDisplayName = \"DisplayName\"\n\tHomepage = \"Homepage\"\n\tBio = \"Bio\"\n\tAge = \"Age\"\n\tLocation = \"Location\"\n\tAvatar = \"Avatar\"\n\tGravatarEmail = \"GravatarEmail\"\n\tAvailableForHiring = \"AvailableForHiring\"\n\tInvalidField = \"InvalidField\"\n)\n\n// Events\nconst (\n\tProfileFieldCreated = \"ProfileFieldCreated\"\n\tProfileFieldUpdated = \"ProfileFieldUpdated\"\n)\n\n// Field types used when emitting event\nconst FieldType = \"FieldType\"\n\nconst (\n\tBoolField = \"BoolField\"\n\tStringField = \"StringField\"\n\tIntField = \"IntField\"\n)\n\nfunc init() {\n\trouter.HandleFunc(\"\", homeHandler)\n\trouter.HandleFunc(\"u/{addr}\", profileHandler)\n\trouter.HandleFunc(\"f/{addr}/{field}\", fieldHandler)\n}\n\n// List of supported string fields\nvar stringFields = map[string]bool{\n\tDisplayName: true,\n\tHomepage: true,\n\tBio: true,\n\tLocation: true,\n\tAvatar: true,\n\tGravatarEmail: true,\n}\n\n// List of support int fields\nvar intFields = map[string]bool{\n\tAge: true,\n}\n\n// List of support bool fields\nvar boolFields = map[string]bool{\n\tAvailableForHiring: true,\n}\n\n// Setters\n\nfunc SetStringField(field, value string) bool {\n\taddr := std.PrevRealm().Addr()\n\tkey := addr.String() + \":\" + field\n\tupdated := fields.Set(key, value)\n\n\tevent := ProfileFieldCreated\n\tif updated {\n\t\tevent = ProfileFieldUpdated\n\t}\n\n\tstd.Emit(event, FieldType, StringField, field, value)\n\n\treturn updated\n}\n\nfunc SetIntField(field string, value int) bool {\n\taddr := std.PrevRealm().Addr()\n\tkey := addr.String() + \":\" + field\n\tupdated := fields.Set(key, value)\n\n\tevent := ProfileFieldCreated\n\tif updated {\n\t\tevent = ProfileFieldUpdated\n\t}\n\n\tstd.Emit(event, FieldType, IntField, field, string(value))\n\n\treturn updated\n}\n\nfunc SetBoolField(field string, value bool) bool {\n\taddr := std.PrevRealm().Addr()\n\tkey := addr.String() + \":\" + field\n\tupdated := fields.Set(key, value)\n\n\tevent := ProfileFieldCreated\n\tif updated {\n\t\tevent = ProfileFieldUpdated\n\t}\n\n\tstd.Emit(event, FieldType, BoolField, field, ufmt.Sprintf(\"%t\", value))\n\n\treturn updated\n}\n\n// Getters\n\nfunc GetStringField(addr std.Address, field, def string) string {\n\tkey := addr.String() + \":\" + field\n\tif value, ok := fields.Get(key); ok {\n\t\treturn value.(string)\n\t}\n\n\treturn def\n}\n\nfunc GetBoolField(addr std.Address, field string, def bool) bool {\n\tkey := addr.String() + \":\" + field\n\tif value, ok := fields.Get(key); ok {\n\t\treturn value.(bool)\n\t}\n\n\treturn def\n}\n\nfunc GetIntField(addr std.Address, field string, def int) int {\n\tkey := addr.String() + \":\" + field\n\tif value, ok := fields.Get(key); ok {\n\t\treturn value.(int)\n\t}\n\n\treturn def\n}\n"},{"name":"profile_test.gno","body":"package profile\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\n// Global addresses for test users\nvar (\n\talice = testutils.TestAddress(\"alice\")\n\tbob = testutils.TestAddress(\"bob\")\n\tcharlie = testutils.TestAddress(\"charlie\")\n\tdave = testutils.TestAddress(\"dave\")\n\teve = testutils.TestAddress(\"eve\")\n\tfrank = testutils.TestAddress(\"frank\")\n\tuser1 = testutils.TestAddress(\"user1\")\n\tuser2 = testutils.TestAddress(\"user2\")\n)\n\nfunc TestStringFields(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\n\t// Get before setting\n\tname := GetStringField(alice, DisplayName, \"anon\")\n\tuassert.Equal(t, \"anon\", name)\n\n\t// Set new key\n\tupdated := SetStringField(DisplayName, \"Alice foo\")\n\tuassert.Equal(t, updated, false)\n\tupdated = SetStringField(Homepage, \"https://example.com\")\n\tuassert.Equal(t, updated, false)\n\n\t// Update the key\n\tupdated = SetStringField(DisplayName, \"Alice foo\")\n\tuassert.Equal(t, updated, true)\n\n\t// Get after setting\n\tname = GetStringField(alice, DisplayName, \"anon\")\n\thomepage := GetStringField(alice, Homepage, \"\")\n\tbio := GetStringField(alice, Bio, \"42\")\n\n\tuassert.Equal(t, \"Alice foo\", name)\n\tuassert.Equal(t, \"https://example.com\", homepage)\n\tuassert.Equal(t, \"42\", bio)\n}\n\nfunc TestIntFields(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\n\t// Get before setting\n\tage := GetIntField(bob, Age, 25)\n\tuassert.Equal(t, 25, age)\n\n\t// Set new key\n\tupdated := SetIntField(Age, 30)\n\tuassert.Equal(t, updated, false)\n\n\t// Update the key\n\tupdated = SetIntField(Age, 30)\n\tuassert.Equal(t, updated, true)\n\n\t// Get after setting\n\tage = GetIntField(bob, Age, 25)\n\tuassert.Equal(t, 30, age)\n}\n\nfunc TestBoolFields(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(charlie))\n\n\t// Get before setting\n\thiring := GetBoolField(charlie, AvailableForHiring, false)\n\tuassert.Equal(t, false, hiring)\n\n\t// Set\n\tupdated := SetBoolField(AvailableForHiring, true)\n\tuassert.Equal(t, updated, false)\n\n\t// Update the key\n\tupdated = SetBoolField(AvailableForHiring, true)\n\tuassert.Equal(t, updated, true)\n\n\t// Get after setting\n\thiring = GetBoolField(charlie, AvailableForHiring, false)\n\tuassert.Equal(t, true, hiring)\n}\n\nfunc TestMultipleProfiles(t *testing.T) {\n\t// Set profile for user1\n\tstd.TestSetRealm(std.NewUserRealm(user1))\n\tupdated := SetStringField(DisplayName, \"User One\")\n\tuassert.Equal(t, updated, false)\n\n\t// Set profile for user2\n\tstd.TestSetRealm(std.NewUserRealm(user2))\n\tupdated = SetStringField(DisplayName, \"User Two\")\n\tuassert.Equal(t, updated, false)\n\n\t// Get profiles\n\tstd.TestSetRealm(std.NewUserRealm(user1)) // Switch back to user1\n\tname1 := GetStringField(user1, DisplayName, \"anon\")\n\tstd.TestSetRealm(std.NewUserRealm(user2)) // Switch back to user2\n\tname2 := GetStringField(user2, DisplayName, \"anon\")\n\n\tuassert.Equal(t, \"User One\", name1)\n\tuassert.Equal(t, \"User Two\", name2)\n}\n\nfunc TestArbitraryStringField(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(user1))\n\n\t// Set arbitrary string field\n\tupdated := SetStringField(\"MyEmail\", \"my@email.com\")\n\tuassert.Equal(t, updated, false)\n\n\tval := GetStringField(user1, \"MyEmail\", \"\")\n\tuassert.Equal(t, val, \"my@email.com\")\n}\n\nfunc TestArbitraryIntField(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(user1))\n\n\t// Set arbitrary int field\n\tupdated := SetIntField(\"MyIncome\", 100_000)\n\tuassert.Equal(t, updated, false)\n\n\tval := GetIntField(user1, \"MyIncome\", 0)\n\tuassert.Equal(t, val, 100_000)\n}\n\nfunc TestArbitraryBoolField(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(user1))\n\n\t// Set arbitrary int field\n\tupdated := SetBoolField(\"IsWinner\", true)\n\tuassert.Equal(t, updated, false)\n\n\tval := GetBoolField(user1, \"IsWinner\", false)\n\tuassert.Equal(t, val, true)\n}\n"},{"name":"render.gno","body":"package profile\n\nimport (\n\t\"bytes\"\n\t\"net/url\"\n\t\"std\"\n\n\t\"gno.land/p/demo/mux\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nconst (\n\tBaseURL = \"/r/demo/profile\"\n\tSetStringFieldURL = BaseURL + \"$help\u0026func=SetStringField\u0026field=%s\"\n\tSetIntFieldURL = BaseURL + \"$help\u0026func=SetIntField\u0026field=%s\"\n\tSetBoolFieldURL = BaseURL + \"$help\u0026func=SetBoolField\u0026field=%s\"\n\tViewAllFieldsURL = BaseURL + \":u/%s\"\n\tViewFieldURL = BaseURL + \":f/%s/%s\"\n)\n\nfunc homeHandler(res *mux.ResponseWriter, req *mux.Request) {\n\tvar b bytes.Buffer\n\n\tb.WriteString(\"## Setters\\n\")\n\tfor field := range stringFields {\n\t\tlink := ufmt.Sprintf(SetStringFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- [Set %s](%s)\\n\", field, link))\n\t}\n\n\tfor field := range intFields {\n\t\tlink := ufmt.Sprintf(SetIntFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- [Set %s](%s)\\n\", field, link))\n\t}\n\n\tfor field := range boolFields {\n\t\tlink := ufmt.Sprintf(SetBoolFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- [Set %s Field](%s)\\n\", field, link))\n\t}\n\n\tb.WriteString(\"\\n---\\n\\n\")\n\n\tres.Write(b.String())\n}\n\nfunc profileHandler(res *mux.ResponseWriter, req *mux.Request) {\n\tvar b bytes.Buffer\n\taddr := req.GetVar(\"addr\")\n\n\tb.WriteString(ufmt.Sprintf(\"# Profile %s\\n\", addr))\n\n\taddress := std.Address(addr)\n\n\tfor field := range stringFields {\n\t\tvalue := GetStringField(address, field, \"n/a\")\n\t\tlink := ufmt.Sprintf(SetStringFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- %s: %s [Edit](%s)\\n\", field, value, link))\n\t}\n\n\tfor field := range intFields {\n\t\tvalue := GetIntField(address, field, 0)\n\t\tlink := ufmt.Sprintf(SetIntFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- %s: %d [Edit](%s)\\n\", field, value, link))\n\t}\n\n\tfor field := range boolFields {\n\t\tvalue := GetBoolField(address, field, false)\n\t\tlink := ufmt.Sprintf(SetBoolFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- %s: %t [Edit](%s)\\n\", field, value, link))\n\t}\n\n\tres.Write(b.String())\n}\n\nfunc fieldHandler(res *mux.ResponseWriter, req *mux.Request) {\n\tvar b bytes.Buffer\n\taddr := req.GetVar(\"addr\")\n\tfield := req.GetVar(\"field\")\n\n\tb.WriteString(ufmt.Sprintf(\"# Field %s for %s\\n\", field, addr))\n\n\taddress := std.Address(addr)\n\tvalue := \"n/a\"\n\tvar editLink string\n\n\tif _, ok := stringFields[field]; ok {\n\t\tvalue = ufmt.Sprintf(\"%s\", GetStringField(address, field, \"n/a\"))\n\t\teditLink = ufmt.Sprintf(SetStringFieldURL+\"\u0026addr=%s\u0026value=%s\", field, addr, url.QueryEscape(value))\n\t} else if _, ok := intFields[field]; ok {\n\t\tvalue = ufmt.Sprintf(\"%d\", GetIntField(address, field, 0))\n\t\teditLink = ufmt.Sprintf(SetIntFieldURL+\"\u0026addr=%s\u0026value=%s\", field, addr, value)\n\t} else if _, ok := boolFields[field]; ok {\n\t\tvalue = ufmt.Sprintf(\"%t\", GetBoolField(address, field, false))\n\t\teditLink = ufmt.Sprintf(SetBoolFieldURL+\"\u0026addr=%s\u0026value=%s\", field, addr, value)\n\t}\n\n\tb.WriteString(ufmt.Sprintf(\"- %s: %s [Edit](%s)\\n\", field, value, editLink))\n\n\tres.Write(b.String())\n}\n\nfunc Render(path string) string {\n\treturn router.Render(path)\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":"releases_example","path":"gno.land/r/demo/releases_example","files":[{"name":"dummy.gno","body":"package releases_example\n\nfunc init() {\n\t// dummy example data\n\tchangelog.NewRelease(\n\t\t\"v1\",\n\t\t\"r/demo/examples_example_v1\",\n\t\t\"initial release\",\n\t)\n\tchangelog.NewRelease(\n\t\t\"v2\",\n\t\t\"r/demo/examples_example_v2\",\n\t\t\"various improvements\",\n\t)\n}\n"},{"name":"example.gno","body":"// this package demonstrates a way to manage contract releases.\npackage releases_example\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/releases\"\n)\n\nvar (\n\tchangelog = releases.NewChangelog(\"example_app\")\n\tadmin = std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\") // @administrator\n)\n\nfunc init() {\n\t// FIXME: admin = std.GetCreator()\n}\n\nfunc NewRelease(name, url, notes string) {\n\tcaller := std.GetOrigCaller()\n\tif caller != admin {\n\t\tpanic(\"restricted area\")\n\t}\n\tchangelog.NewRelease(name, url, notes)\n}\n\nfunc UpdateAdmin(address std.Address) {\n\tcaller := std.GetOrigCaller()\n\tif caller != admin {\n\t\tpanic(\"restricted area\")\n\t}\n\tadmin = address\n}\n\nfunc Render(path string) string {\n\treturn changelog.Render(path)\n}\n"},{"name":"releases0_filetest.gno","body":"package main\n\nimport (\n\t\"gno.land/p/demo/releases\"\n)\n\nfunc main() {\n\tprintln(\"-----------\")\n\tchangelog := releases.NewChangelog(\"example\")\n\tprint(changelog.Render(\"\"))\n\n\tprintln(\"-----------\")\n\tchangelog.NewRelease(\"v1\", \"r/blahblah\", \"* initial version\")\n\tprint(changelog.Render(\"\"))\n\n\tprintln(\"-----------\")\n\tchangelog.NewRelease(\"v2\", \"r/blahblah2\", \"* various improvements\\n* new shiny logo\")\n\tprint(changelog.Render(\"\"))\n\n\tprintln(\"-----------\")\n\tprint(changelog.Latest().Render())\n}\n\n// Output:\n// -----------\n// # example\n//\n// -----------\n// # example\n//\n// ## [example v1 (latest)](r/blahblah)\n//\n// * initial version\n//\n// -----------\n// # example\n//\n// ## [example v2 (latest)](r/blahblah2)\n//\n// * various improvements\n// * new shiny logo\n//\n// ## [example v1](r/blahblah)\n//\n// * initial version\n//\n// -----------\n// ## [example v2 (latest)](r/blahblah2)\n//\n// * various improvements\n// * new shiny logo\n"},{"name":"releases1_filetest.gno","body":"package main\n\nimport (\n\t\"gno.land/r/demo/releases_example\"\n)\n\nfunc main() {\n\tprintln(\"-----------\")\n\tprint(releases_example.Render(\"\"))\n\n\tprintln(\"-----------\")\n\tprint(releases_example.Render(\"v1\"))\n\n\tprintln(\"-----------\")\n\tprint(releases_example.Render(\"v42\"))\n}\n\n// Output:\n// -----------\n// # example_app\n//\n// ## [example_app v2 (latest)](r/demo/examples_example_v2)\n//\n// various improvements\n//\n// ## [example_app v1](r/demo/examples_example_v1)\n//\n// initial release\n//\n// -----------\n// ## [example_app v1](r/demo/examples_example_v1)\n//\n// initial release\n//\n// -----------\n// no such release\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":"releases_example","path":"gno.land/r/demo/releases_example","files":[{"name":"dummy.gno","body":"package releases_example\n\nfunc init() {\n\t// dummy example data\n\tchangelog.NewRelease(\n\t\t\"v1\",\n\t\t\"r/demo/examples_example_v1\",\n\t\t\"initial release\",\n\t)\n\tchangelog.NewRelease(\n\t\t\"v2\",\n\t\t\"r/demo/examples_example_v2\",\n\t\t\"various improvements\",\n\t)\n}\n"},{"name":"example.gno","body":"// this package demonstrates a way to manage contract releases.\npackage releases_example\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/releases\"\n)\n\nvar (\n\tchangelog = releases.NewChangelog(\"example_app\")\n\tadmin = std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\") // @administrator\n)\n\nfunc init() {\n\t// FIXME: admin = std.GetCreator()\n}\n\nfunc NewRelease(name, url, notes string) {\n\tcaller := std.OrigCaller()\n\tif caller != admin {\n\t\tpanic(\"restricted area\")\n\t}\n\tchangelog.NewRelease(name, url, notes)\n}\n\nfunc UpdateAdmin(address std.Address) {\n\tcaller := std.OrigCaller()\n\tif caller != admin {\n\t\tpanic(\"restricted area\")\n\t}\n\tadmin = address\n}\n\nfunc Render(path string) string {\n\treturn changelog.Render(path)\n}\n"},{"name":"releases0_filetest.gno","body":"package main\n\nimport (\n\t\"gno.land/p/demo/releases\"\n)\n\nfunc main() {\n\tprintln(\"-----------\")\n\tchangelog := releases.NewChangelog(\"example\")\n\tprint(changelog.Render(\"\"))\n\n\tprintln(\"-----------\")\n\tchangelog.NewRelease(\"v1\", \"r/blahblah\", \"* initial version\")\n\tprint(changelog.Render(\"\"))\n\n\tprintln(\"-----------\")\n\tchangelog.NewRelease(\"v2\", \"r/blahblah2\", \"* various improvements\\n* new shiny logo\")\n\tprint(changelog.Render(\"\"))\n\n\tprintln(\"-----------\")\n\tprint(changelog.Latest().Render())\n}\n\n// Output:\n// -----------\n// # example\n//\n// -----------\n// # example\n//\n// ## [example v1 (latest)](r/blahblah)\n//\n// * initial version\n//\n// -----------\n// # example\n//\n// ## [example v2 (latest)](r/blahblah2)\n//\n// * various improvements\n// * new shiny logo\n//\n// ## [example v1](r/blahblah)\n//\n// * initial version\n//\n// -----------\n// ## [example v2 (latest)](r/blahblah2)\n//\n// * various improvements\n// * new shiny logo\n"},{"name":"releases1_filetest.gno","body":"package main\n\nimport (\n\t\"gno.land/r/demo/releases_example\"\n)\n\nfunc main() {\n\tprintln(\"-----------\")\n\tprint(releases_example.Render(\"\"))\n\n\tprintln(\"-----------\")\n\tprint(releases_example.Render(\"v1\"))\n\n\tprintln(\"-----------\")\n\tprint(releases_example.Render(\"v42\"))\n}\n\n// Output:\n// -----------\n// # example_app\n//\n// ## [example_app v2 (latest)](r/demo/examples_example_v2)\n//\n// various improvements\n//\n// ## [example_app v1](r/demo/examples_example_v1)\n//\n// initial release\n//\n// -----------\n// ## [example_app v1](r/demo/examples_example_v1)\n//\n// initial release\n//\n// -----------\n// no such release\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":"tamagotchi","path":"gno.land/r/demo/tamagotchi","files":[{"name":"realm.gno","body":"package tamagotchi\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/tamagotchi\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar t *tamagotchi.Tamagotchi\n\nfunc init() {\n\tReset(\"gnome#0\")\n}\n\nfunc Reset(optionalName string) string {\n\tname := optionalName\n\tif name == \"\" {\n\t\theight := std.GetHeight()\n\t\tname = ufmt.Sprintf(\"gnome#%d\", height)\n\t}\n\n\tt = tamagotchi.New(name)\n\n\treturn ufmt.Sprintf(\"A new tamagotchi is born. Their name is %s %s.\", t.Name(), t.Face())\n}\n\nfunc Feed() string {\n\tt.Feed()\n\treturn t.Markdown()\n}\n\nfunc Play() string {\n\tt.Play()\n\treturn t.Markdown()\n}\n\nfunc Heal() string {\n\tt.Heal()\n\treturn t.Markdown()\n}\n\nfunc Render(path string) string {\n\ttama := t.Markdown()\n\tlinks := `Actions:\n* [Feed](/r/demo/tamagotchi$help\u0026func=Feed)\n* [Play](/r/demo/tamagotchi$help\u0026func=Play)\n* [Heal](/r/demo/tamagotchi$help\u0026func=Heal)\n* [Reset](/r/demo/tamagotchi$help\u0026func=Reset)\n`\n\n\treturn tama + \"\\n\\n\" + links\n}\n"},{"name":"z0_filetest.gno","body":"package main\n\nimport (\n\t\"gno.land/r/demo/tamagotchi\"\n)\n\nfunc main() {\n\ttamagotchi.Reset(\"tamagnotchi\")\n\tprintln(tamagotchi.Render(\"\"))\n}\n\n// Output:\n// # tamagnotchi 😃\n//\n// * age: 0\n// * hunger: 50\n// * happiness: 50\n// * health: 50\n// * sleepy: 0\n//\n// Actions:\n// * [Feed](/r/demo/tamagotchi$help\u0026func=Feed)\n// * [Play](/r/demo/tamagotchi$help\u0026func=Play)\n// * [Heal](/r/demo/tamagotchi$help\u0026func=Heal)\n// * [Reset](/r/demo/tamagotchi$help\u0026func=Reset)\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"}]},"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_foo","path":"gno.land/r/demo/tests_foo","files":[{"name":"foo.gno","body":"package tests_foo\n\nimport (\n\t\"gno.land/r/demo/tests\"\n)\n\n// for testing gno.land/r/demo/tests/interfaces.go\n\ntype FooStringer struct {\n\tFieldA string\n}\n\nfunc (fs *FooStringer) String() string {\n\treturn \"\u0026FooStringer{\" + fs.FieldA + \"}\"\n}\n\nfunc AddFooStringer(fa string) {\n\ttests.AddStringer(\u0026FooStringer{fa})\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":"todolistrealm","path":"gno.land/r/demo/todolist","files":[{"name":"todolist.gno","body":"package todolistrealm\n\nimport (\n\t\"bytes\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/seqid\"\n\t\"gno.land/p/demo/todolist\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// State variables\nvar (\n\ttodolistTree *avl.Tree\n\ttlid seqid.ID\n)\n\n// Constructor\nfunc init() {\n\ttodolistTree = avl.NewTree()\n}\n\nfunc NewTodoList(title string) (int, string) {\n\t// Create new Todolist\n\ttl := todolist.NewTodoList(title)\n\t// Update AVL tree with new state\n\ttlid.Next()\n\ttodolistTree.Set(strconv.Itoa(int(tlid)), tl)\n\treturn int(tlid), \"created successfully\"\n}\n\nfunc AddTask(todolistID int, title string) string {\n\t// Get Todolist from AVL tree\n\ttl, ok := todolistTree.Get(strconv.Itoa(todolistID))\n\tif !ok {\n\t\tpanic(\"Todolist not found\")\n\t}\n\n\t// get the number of tasks in the todolist\n\tid := tl.(*todolist.TodoList).Tasks.Size()\n\n\t// create the task\n\ttask := todolist.NewTask(title)\n\n\t// Cast raw data from tree into Todolist struct\n\ttl.(*todolist.TodoList).AddTask(id, task)\n\n\treturn \"task added successfully\"\n}\n\nfunc ToggleTaskStatus(todolistID int, taskID int) string {\n\t// Get Todolist from AVL tree\n\ttl, ok := todolistTree.Get(strconv.Itoa(todolistID))\n\tif !ok {\n\t\tpanic(\"Todolist not found\")\n\t}\n\n\t// Get the task from the todolist\n\ttask, found := tl.(*todolist.TodoList).Tasks.Get(strconv.Itoa(taskID))\n\tif !found {\n\t\tpanic(\"Task not found\")\n\t}\n\n\t// Change the status of the task\n\ttodolist.ToggleTaskStatus(task.(*todolist.Task))\n\n\treturn \"task status changed successfully\"\n}\n\nfunc RemoveTask(todolistID int, taskID int) string {\n\t// Get Todolist from AVL tree\n\ttl, ok := todolistTree.Get(strconv.Itoa(todolistID))\n\tif !ok {\n\t\tpanic(\"Todolist not found\")\n\t}\n\n\t// Get the task from the todolist\n\t_, ok = tl.(*todolist.TodoList).Tasks.Get(strconv.Itoa(taskID))\n\tif !ok {\n\t\tpanic(\"Task not found\")\n\t}\n\n\t// Change the status of the task\n\ttl.(*todolist.TodoList).RemoveTask(strconv.Itoa(taskID))\n\n\treturn \"task status changed successfully\"\n}\n\nfunc RemoveTodoList(todolistID int) string {\n\t// Get Todolist from AVL tree\n\t_, ok := todolistTree.Get(strconv.Itoa(todolistID))\n\tif !ok {\n\t\tpanic(\"Todolist not found\")\n\t}\n\n\t// Remove the todolist\n\ttodolistTree.Remove(strconv.Itoa(todolistID))\n\n\treturn \"Todolist removed successfully\"\n}\n\nfunc Render(path string) string {\n\tif path == \"\" {\n\t\treturn renderHomepage()\n\t}\n\n\treturn \"unknown page\"\n}\n\nfunc renderHomepage() string {\n\t// Define empty buffer\n\tvar b bytes.Buffer\n\n\tb.WriteString(\"# Welcome to ToDolist\\n\\n\")\n\n\t// If no todolists have been created\n\tif todolistTree.Size() == 0 {\n\t\tb.WriteString(\"### No todolists available currently!\")\n\t\treturn b.String()\n\t}\n\n\t// Iterate through AVL tree\n\ttodolistTree.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t// cast raw data from tree into Todolist struct\n\t\ttl := value.(*todolist.TodoList)\n\n\t\t// Add Todolist name\n\t\tb.WriteString(\n\t\t\tufmt.Sprintf(\n\t\t\t\t\"## Todolist #%s: %s\\n\",\n\t\t\t\tkey, // Todolist ID\n\t\t\t\ttl.GetTodolistTitle(),\n\t\t\t),\n\t\t)\n\n\t\t// Add Todolist owner\n\t\tb.WriteString(\n\t\t\tufmt.Sprintf(\n\t\t\t\t\"#### Todolist owner : %s\\n\",\n\t\t\t\ttl.GetTodolistOwner(),\n\t\t\t),\n\t\t)\n\n\t\t// List all todos that are currently Todolisted\n\t\tif todos := tl.GetTasks(); len(todos) \u003e 0 {\n\t\t\tb.WriteString(\n\t\t\t\tufmt.Sprintf(\"Currently Todo tasks: %d\\n\\n\", len(todos)),\n\t\t\t)\n\n\t\t\tfor index, todo := range todos {\n\t\t\t\tb.WriteString(\n\t\t\t\t\tufmt.Sprintf(\"#%d - %s \", index, todo.Title),\n\t\t\t\t)\n\t\t\t\t// displays a checked box if task is marked as done, an empty box if not\n\t\t\t\tif todo.Done {\n\t\t\t\t\tb.WriteString(\n\t\t\t\t\t\t\"☑\\n\\n\",\n\t\t\t\t\t)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tb.WriteString(\n\t\t\t\t\t\"☐\\n\\n\",\n\t\t\t\t)\n\t\t\t}\n\t\t} else {\n\t\t\tb.WriteString(\"No tasks in this list currently\\n\")\n\t\t}\n\n\t\tb.WriteString(\"\\n\")\n\t\treturn false\n\t})\n\n\treturn b.String()\n}\n"},{"name":"todolist_test.gno","body":"package todolistrealm\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/todolist\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\tnode interface{}\n\ttdl *todolist.TodoList\n)\n\nfunc TestNewTodoList(t *testing.T) {\n\ttitle := \"My Todo List\"\n\ttlid, _ := NewTodoList(title)\n\tuassert.Equal(t, 1, tlid, \"tlid does not match\")\n\n\t// get the todolist node from the tree\n\tnode, _ = todolistTree.Get(strconv.Itoa(tlid))\n\t// convert the node to a TodoList struct\n\ttdl = node.(*todolist.TodoList)\n\n\tuassert.Equal(t, title, tdl.Title, \"title does not match\")\n\tuassert.Equal(t, 1, tlid, \"tlid does not match\")\n\tuassert.Equal(t, tdl.Owner.String(), std.GetOrigCaller().String(), \"owner does not match\")\n\tuassert.Equal(t, 0, len(tdl.GetTasks()), \"Expected no tasks in the todo list\")\n}\n\nfunc TestAddTask(t *testing.T) {\n\tAddTask(1, \"Task 1\")\n\n\ttasks := tdl.GetTasks()\n\tuassert.Equal(t, 1, len(tasks), \"total task does not match\")\n\tuassert.Equal(t, \"Task 1\", tasks[0].Title, \"task title does not match\")\n\tuassert.False(t, tasks[0].Done, \"Expected task to be not done\")\n}\n\nfunc TestToggleTaskStatus(t *testing.T) {\n\tToggleTaskStatus(1, 0)\n\ttask := tdl.GetTasks()[0]\n\tuassert.True(t, task.Done, \"Expected task to be done, but it is not marked as done\")\n\n\tToggleTaskStatus(1, 0)\n\tuassert.False(t, task.Done, \"Expected task to be not done, but it is marked as done\")\n}\n\nfunc TestRemoveTask(t *testing.T) {\n\tRemoveTask(1, 0)\n\ttasks := tdl.GetTasks()\n\tuassert.Equal(t, 0, len(tasks), \"Expected no tasks in the todo list\")\n}\n\nfunc TestRemoveTodoList(t *testing.T) {\n\tRemoveTodoList(1)\n\tuassert.Equal(t, 0, todolistTree.Size(), \"Expected no tasks in the todo list\")\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":"todolistrealm","path":"gno.land/r/demo/todolist","files":[{"name":"todolist.gno","body":"package todolistrealm\n\nimport (\n\t\"bytes\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/seqid\"\n\t\"gno.land/p/demo/todolist\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// State variables\nvar (\n\ttodolistTree *avl.Tree\n\ttlid seqid.ID\n)\n\n// Constructor\nfunc init() {\n\ttodolistTree = avl.NewTree()\n}\n\nfunc NewTodoList(title string) (int, string) {\n\t// Create new Todolist\n\ttl := todolist.NewTodoList(title)\n\t// Update AVL tree with new state\n\ttlid.Next()\n\ttodolistTree.Set(strconv.Itoa(int(tlid)), tl)\n\treturn int(tlid), \"created successfully\"\n}\n\nfunc AddTask(todolistID int, title string) string {\n\t// Get Todolist from AVL tree\n\ttl, ok := todolistTree.Get(strconv.Itoa(todolistID))\n\tif !ok {\n\t\tpanic(\"Todolist not found\")\n\t}\n\n\t// get the number of tasks in the todolist\n\tid := tl.(*todolist.TodoList).Tasks.Size()\n\n\t// create the task\n\ttask := todolist.NewTask(title)\n\n\t// Cast raw data from tree into Todolist struct\n\ttl.(*todolist.TodoList).AddTask(id, task)\n\n\treturn \"task added successfully\"\n}\n\nfunc ToggleTaskStatus(todolistID int, taskID int) string {\n\t// Get Todolist from AVL tree\n\ttl, ok := todolistTree.Get(strconv.Itoa(todolistID))\n\tif !ok {\n\t\tpanic(\"Todolist not found\")\n\t}\n\n\t// Get the task from the todolist\n\ttask, found := tl.(*todolist.TodoList).Tasks.Get(strconv.Itoa(taskID))\n\tif !found {\n\t\tpanic(\"Task not found\")\n\t}\n\n\t// Change the status of the task\n\ttodolist.ToggleTaskStatus(task.(*todolist.Task))\n\n\treturn \"task status changed successfully\"\n}\n\nfunc RemoveTask(todolistID int, taskID int) string {\n\t// Get Todolist from AVL tree\n\ttl, ok := todolistTree.Get(strconv.Itoa(todolistID))\n\tif !ok {\n\t\tpanic(\"Todolist not found\")\n\t}\n\n\t// Get the task from the todolist\n\t_, ok = tl.(*todolist.TodoList).Tasks.Get(strconv.Itoa(taskID))\n\tif !ok {\n\t\tpanic(\"Task not found\")\n\t}\n\n\t// Change the status of the task\n\ttl.(*todolist.TodoList).RemoveTask(strconv.Itoa(taskID))\n\n\treturn \"task status changed successfully\"\n}\n\nfunc RemoveTodoList(todolistID int) string {\n\t// Get Todolist from AVL tree\n\t_, ok := todolistTree.Get(strconv.Itoa(todolistID))\n\tif !ok {\n\t\tpanic(\"Todolist not found\")\n\t}\n\n\t// Remove the todolist\n\ttodolistTree.Remove(strconv.Itoa(todolistID))\n\n\treturn \"Todolist removed successfully\"\n}\n\nfunc Render(path string) string {\n\tif path == \"\" {\n\t\treturn renderHomepage()\n\t}\n\n\treturn \"unknown page\"\n}\n\nfunc renderHomepage() string {\n\t// Define empty buffer\n\tvar b bytes.Buffer\n\n\tb.WriteString(\"# Welcome to ToDolist\\n\\n\")\n\n\t// If no todolists have been created\n\tif todolistTree.Size() == 0 {\n\t\tb.WriteString(\"### No todolists available currently!\")\n\t\treturn b.String()\n\t}\n\n\t// Iterate through AVL tree\n\ttodolistTree.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t// cast raw data from tree into Todolist struct\n\t\ttl := value.(*todolist.TodoList)\n\n\t\t// Add Todolist name\n\t\tb.WriteString(\n\t\t\tufmt.Sprintf(\n\t\t\t\t\"## Todolist #%s: %s\\n\",\n\t\t\t\tkey, // Todolist ID\n\t\t\t\ttl.GetTodolistTitle(),\n\t\t\t),\n\t\t)\n\n\t\t// Add Todolist owner\n\t\tb.WriteString(\n\t\t\tufmt.Sprintf(\n\t\t\t\t\"#### Todolist owner : %s\\n\",\n\t\t\t\ttl.GetTodolistOwner(),\n\t\t\t),\n\t\t)\n\n\t\t// List all todos that are currently Todolisted\n\t\tif todos := tl.GetTasks(); len(todos) \u003e 0 {\n\t\t\tb.WriteString(\n\t\t\t\tufmt.Sprintf(\"Currently Todo tasks: %d\\n\\n\", len(todos)),\n\t\t\t)\n\n\t\t\tfor index, todo := range todos {\n\t\t\t\tb.WriteString(\n\t\t\t\t\tufmt.Sprintf(\"#%d - %s \", index, todo.Title),\n\t\t\t\t)\n\t\t\t\t// displays a checked box if task is marked as done, an empty box if not\n\t\t\t\tif todo.Done {\n\t\t\t\t\tb.WriteString(\n\t\t\t\t\t\t\"☑\\n\\n\",\n\t\t\t\t\t)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tb.WriteString(\n\t\t\t\t\t\"☐\\n\\n\",\n\t\t\t\t)\n\t\t\t}\n\t\t} else {\n\t\t\tb.WriteString(\"No tasks in this list currently\\n\")\n\t\t}\n\n\t\tb.WriteString(\"\\n\")\n\t\treturn false\n\t})\n\n\treturn b.String()\n}\n"},{"name":"todolist_test.gno","body":"package todolistrealm\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/todolist\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\tnode interface{}\n\ttdl *todolist.TodoList\n)\n\nfunc TestNewTodoList(t *testing.T) {\n\ttitle := \"My Todo List\"\n\ttlid, _ := NewTodoList(title)\n\tuassert.Equal(t, 1, tlid, \"tlid does not match\")\n\n\t// get the todolist node from the tree\n\tnode, _ = todolistTree.Get(strconv.Itoa(tlid))\n\t// convert the node to a TodoList struct\n\ttdl = node.(*todolist.TodoList)\n\n\tuassert.Equal(t, title, tdl.Title, \"title does not match\")\n\tuassert.Equal(t, 1, tlid, \"tlid does not match\")\n\tuassert.Equal(t, tdl.Owner.String(), std.OrigCaller().String(), \"owner does not match\")\n\tuassert.Equal(t, 0, len(tdl.GetTasks()), \"Expected no tasks in the todo list\")\n}\n\nfunc TestAddTask(t *testing.T) {\n\tAddTask(1, \"Task 1\")\n\n\ttasks := tdl.GetTasks()\n\tuassert.Equal(t, 1, len(tasks), \"total task does not match\")\n\tuassert.Equal(t, \"Task 1\", tasks[0].Title, \"task title does not match\")\n\tuassert.False(t, tasks[0].Done, \"Expected task to be not done\")\n}\n\nfunc TestToggleTaskStatus(t *testing.T) {\n\tToggleTaskStatus(1, 0)\n\ttask := tdl.GetTasks()[0]\n\tuassert.True(t, task.Done, \"Expected task to be done, but it is not marked as done\")\n\n\tToggleTaskStatus(1, 0)\n\tuassert.False(t, task.Done, \"Expected task to be not done, but it is marked as done\")\n}\n\nfunc TestRemoveTask(t *testing.T) {\n\tRemoveTask(1, 0)\n\ttasks := tdl.GetTasks()\n\tuassert.Equal(t, 0, len(tasks), \"Expected no tasks in the todo list\")\n}\n\nfunc TestRemoveTodoList(t *testing.T) {\n\tRemoveTodoList(1)\n\tuassert.Equal(t, 0, todolistTree.Size(), \"Expected no tasks in the todo list\")\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":"types","path":"gno.land/r/demo/types","files":[{"name":"types.gno","body":"// package to test types behavior in various conditions (TXs, imports).\npackage types\n\nimport (\n\t\"errors\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\nvar (\n\tgInt int = -42\n\tgUint uint = 42\n\tgString string = \"a string\"\n\tgStringSlice []string = []string{\"a\", \"string\", \"slice\"}\n\tgError error = errors.New(\"an error\")\n\tgIntSlice []int = []int{-42, 0, 42}\n\tgUintSlice []uint = []uint{0, 42, 84}\n\tgTree avl.Tree\n\t// gInterface = interface{}{-42, \"a string\", uint(42)}\n)\n\nfunc init() {\n\tgTree.Set(\"a\", \"content of A\")\n\tgTree.Set(\"b\", \"content of B\")\n}\n\nfunc Noop() {}\nfunc RetTimeNow() time.Time { return time.Now() }\nfunc RetString() string { return gString }\nfunc RetStringPointer() *string { return \u0026gString }\nfunc RetUint() uint { return gUint }\nfunc RetInt() int { return gInt }\nfunc RetUintPointer() *uint { return \u0026gUint }\nfunc RetIntPointer() *int { return \u0026gInt }\nfunc RetTree() avl.Tree { return gTree }\nfunc RetIntSlice() []int { return gIntSlice }\nfunc RetUintSlice() []uint { return gUintSlice }\nfunc RetStringSlice() []string { return gStringSlice }\nfunc RetError() error { return gError }\nfunc Panic() { panic(\"PANIC!\") }\n\n// TODO: floats\n// TODO: typed errors\n// TODO: ret interface\n// TODO: recover\n// TODO: take types as input\n\nfunc Render(path string) string {\n\treturn \"package to test data types.\"\n}\n"},{"name":"types_test.gno","body":"package types\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":"ui","path":"gno.land/r/demo/ui","files":[{"name":"ui.gno","body":"package ui\n\nimport \"gno.land/p/demo/ui\"\n\nfunc Render(path string) string {\n\t// TODO: build this realm as a demo one with one page per feature.\n\n\t// TODO: pagination\n\t// TODO: non-standard markdown\n\t// TODO: error, warn\n\t// TODO: header\n\t// TODO: HTML\n\t// TODO: toc\n\t// TODO: forms\n\t// TODO: comments\n\n\tdom := ui.DOM{\n\t\tPrefix: \"r/demo/ui:\",\n\t}\n\n\tdom.Title = \"UI Demo\"\n\n\tdom.Header.Append(ui.Breadcrumb{\n\t\tui.Link{Text: \"foo\", Path: \"foo\"},\n\t\tui.Link{Text: \"bar\", Path: \"foo/bar\"},\n\t})\n\n\tdom.Body.Append(\n\t\tui.Paragraph(\"Simple UI demonstration.\"),\n\t\tui.BulletList{\n\t\t\tui.Text(\"a text\"),\n\t\t\tui.Link{Text: \"a relative link\", Path: \"foobar\"},\n\t\t\tui.Text(\"another text\"),\n\t\t\t// ui.H1(\"a H1 text\"),\n\t\t\tui.Bold(\"a bold text\"),\n\t\t\tui.Italic(\"italic text\"),\n\t\t\tui.Text(\"raw markdown with **bold** text in the middle.\"),\n\t\t\tui.Code(\"some inline code\"),\n\t\t\tui.Link{Text: \"a remote link\", URL: \"https://gno.land\"},\n\t\t},\n\t)\n\n\tdom.Footer.Append(ui.Text(\"I'm the footer.\"))\n\tdom.Body.Append(ui.Text(\"another string.\"))\n\tdom.Body.Append(ui.Paragraph(\"a paragraph.\"), ui.HR{})\n\n\treturn dom.String()\n}\n"},{"name":"ui_test.gno","body":"package ui\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestRender(t *testing.T) {\n\tgot := Render(\"\")\n\texpected := \"# UI Demo\\n\\n[foo](r/demo/ui:foo) / [bar](r/demo/ui:foo/bar)\\n\\n\\nSimple UI demonstration.\\n\\n- a text\\n- [a relative link](r/demo/ui:foobar)\\n- another text\\n- **a bold text**\\n- _italic text_\\n- raw markdown with **bold** text in the middle.\\n- `some inline code`\\n- [a remote link](https://gno.land)\\n\\nanother string.\\n\\na paragraph.\\n\\n\\n---\\n\\n\\nI'm the footer.\\n\\n\"\n\tuassert.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":"userbook","path":"gno.land/r/demo/userbook","files":[{"name":"userbook.gno","body":"// This realm demonstrates a small userbook system working with gnoweb\npackage userbook\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/mux\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype Signup struct {\n\taccount string\n\theight int64\n}\n\n// signups - keep a slice of signed up addresses efficient pagination\nvar signups []Signup\n\n// tracker - keep track of who signed up\nvar (\n\ttracker *avl.Tree\n\trouter *mux.Router\n)\n\nconst (\n\tdefaultPageSize = 20\n\tpathArgument = \"number\"\n\tsubPath = \"page/{\" + pathArgument + \"}\"\n\tsignUpEvent = \"SignUp\"\n)\n\nfunc init() {\n\t// Set up tracker tree\n\ttracker = avl.NewTree()\n\n\t// Set up route handling\n\trouter = mux.NewRouter()\n\trouter.HandleFunc(\"\", renderHelper)\n\trouter.HandleFunc(subPath, renderHelper)\n\n\t// Sign up the deployer\n\tSignUp()\n}\n\nfunc SignUp() string {\n\t// Get transaction caller\n\tcaller := std.PrevRealm().Addr().String()\n\theight := std.GetHeight()\n\n\t// Check if the user is already signed up\n\tif _, exists := tracker.Get(caller); exists {\n\t\tpanic(caller + \" is already signed up!\")\n\t}\n\n\t// Sign up the user\n\ttracker.Set(caller, struct{}{})\n\tsignup := Signup{\n\t\tcaller,\n\t\theight,\n\t}\n\n\tsignups = append(signups, signup)\n\tstd.Emit(signUpEvent, \"SignedUpAccount\", signup.account)\n\n\treturn ufmt.Sprintf(\"%s added to userbook up at block #%d!\", signup.account, signup.height)\n}\n\nfunc GetSignupsInRange(page, pageSize int) ([]Signup, int) {\n\tif page \u003c 1 {\n\t\tpanic(\"page number cannot be less than 1\")\n\t}\n\n\tif pageSize \u003c 1 || pageSize \u003e 50 {\n\t\tpanic(\"page size must be from 1 to 50\")\n\t}\n\n\t// Pagination\n\t// Calculate indexes\n\tstartIndex := (page - 1) * pageSize\n\tendIndex := startIndex + pageSize\n\n\t// If page does not contain any users\n\tif startIndex \u003e= len(signups) {\n\t\treturn nil, -1\n\t}\n\n\t// If page contains fewer users than the page size\n\tif endIndex \u003e len(signups) {\n\t\tendIndex = len(signups)\n\t}\n\n\treturn signups[startIndex:endIndex], endIndex\n}\n\nfunc renderHelper(res *mux.ResponseWriter, req *mux.Request) {\n\ttotalSignups := len(signups)\n\tres.Write(\"# Welcome to UserBook!\\n\\n\")\n\n\t// Get URL parameter\n\tpage, err := strconv.Atoi(req.GetVar(\"number\"))\n\tif err != nil {\n\t\tpage = 1 // render first page on bad input\n\t}\n\n\t// Fetch paginated signups\n\tfetchedSignups, endIndex := GetSignupsInRange(page, defaultPageSize)\n\t// Handle empty page case\n\tif len(fetchedSignups) == 0 {\n\t\tres.Write(\"No users on this page!\\n\\n\")\n\t\tres.Write(\"---\\n\\n\")\n\t\tres.Write(\"[Back to Page #1](/r/demo/userbook:page/1)\\n\\n\")\n\t\treturn\n\t}\n\n\t// Write page title\n\tres.Write(ufmt.Sprintf(\"## UserBook - Page #%d:\\n\\n\", page))\n\n\t// Write signups\n\tpageStartIndex := defaultPageSize * (page - 1)\n\tfor i, signup := range fetchedSignups {\n\t\tout := ufmt.Sprintf(\"#### User #%d - %s - signed up at Block #%d\\n\", pageStartIndex+i, signup.account, signup.height)\n\t\tres.Write(out)\n\t}\n\n\tres.Write(\"---\\n\\n\")\n\n\t// Write UserBook info\n\tlatestSignupIndex := totalSignups - 1\n\tres.Write(ufmt.Sprintf(\"#### Total users: %d\\n\", totalSignups))\n\tres.Write(ufmt.Sprintf(\"#### Latest signup: User #%d at Block #%d\\n\", latestSignupIndex, signups[latestSignupIndex].height))\n\n\tres.Write(\"---\\n\\n\")\n\n\t// Write page number\n\tres.Write(ufmt.Sprintf(\"You're viewing page #%d\", page))\n\n\t// Write navigation buttons\n\tvar prevPage string\n\tvar nextPage string\n\t// If we are on any page that is not the first page\n\tif page \u003e 1 {\n\t\tprevPage = ufmt.Sprintf(\" - [Previous page](/r/demo/userbook:page/%d)\", page-1)\n\t}\n\n\t// If there are more pages after the current one\n\tif endIndex \u003c totalSignups {\n\t\tnextPage = ufmt.Sprintf(\" - [Next page](/r/demo/userbook:page/%d)\\n\\n\", page+1)\n\t}\n\n\tres.Write(prevPage)\n\tres.Write(nextPage)\n}\n\nfunc Render(path string) string {\n\treturn router.Render(path)\n}\n"},{"name":"userbook_test.gno","body":"package userbook\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nfunc TestRender(t *testing.T) {\n\t// Sign up 20 users + deployer\n\tfor i := 0; i \u003c 20; i++ {\n\t\taddrName := ufmt.Sprintf(\"test%d\", i)\n\t\tcaller := testutils.TestAddress(addrName)\n\t\tstd.TestSetOrigCaller(caller)\n\t\tSignUp()\n\t}\n\n\ttestCases := []struct {\n\t\tname string\n\t\tnextPage bool\n\t\tprevPage bool\n\t\tpath string\n\t\texpectedNumberOfUsers int\n\t}{\n\t\t{\n\t\t\tname: \"1st page render\",\n\t\t\tnextPage: true,\n\t\t\tprevPage: false,\n\t\t\tpath: \"page/1\",\n\t\t\texpectedNumberOfUsers: 20,\n\t\t},\n\t\t{\n\t\t\tname: \"2nd page render\",\n\t\t\tnextPage: false,\n\t\t\tprevPage: true,\n\t\t\tpath: \"page/2\",\n\t\t\texpectedNumberOfUsers: 1,\n\t\t},\n\t\t{\n\t\t\tname: \"Invalid path render\",\n\t\t\tnextPage: true,\n\t\t\tprevPage: false,\n\t\t\tpath: \"page/invalidtext\",\n\t\t\texpectedNumberOfUsers: 20,\n\t\t},\n\t\t{\n\t\t\tname: \"Empty Page\",\n\t\t\tnextPage: false,\n\t\t\tprevPage: false,\n\t\t\tpath: \"page/1000\",\n\t\t\texpectedNumberOfUsers: 0,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := Render(tc.path)\n\t\t\tnumUsers := countUsers(got)\n\n\t\t\tif tc.prevPage \u0026\u0026 !strings.Contains(got, \"Previous page\") {\n\t\t\t\tt.Fatalf(\"expected to find Previous page, didn't find it\")\n\t\t\t}\n\t\t\tif tc.nextPage \u0026\u0026 !strings.Contains(got, \"Next page\") {\n\t\t\t\tt.Fatalf(\"expected to find Next page, didn't find it\")\n\t\t\t}\n\n\t\t\tif tc.expectedNumberOfUsers != numUsers {\n\t\t\t\tt.Fatalf(\"expected %d, got %d users\", tc.expectedNumberOfUsers, numUsers)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc countUsers(input string) int {\n\treturn strings.Count(input, \"#### User #\")\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":"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/users\"\n)\n\nvar (\n\tbanker *grc20.Banker = grc20.NewBanker(\"wrapped GNOT\", \"wugnot\", 0)\n\tToken = banker.Token()\n)\n\nconst (\n\tugnotMinDeposit uint64 = 1000\n\twugnotMinDeposit uint64 = 1\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\tcheckErr(banker.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(banker.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 banker.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\tcheckErr(Token.Transfer(toAddr, amount))\n}\n\nfunc Approve(spender pusers.AddressOrName, amount uint64) {\n\tspenderAddr := users.Resolve(spender)\n\tcheckErr(Token.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(Token.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=200000000 |\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=200000000 |\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=200004242 |\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":""}} -{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"gnoblog","path":"gno.land/r/gnoland/blog","files":[{"name":"admin.gno","body":"package gnoblog\n\nimport (\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/r/gov/dao/bridge\"\n)\n\nvar (\n\tadminAddr std.Address\n\tmoderatorList avl.Tree\n\tcommenterList avl.Tree\n\tinPause bool\n)\n\nfunc init() {\n\t// adminAddr = std.GetOrigCaller() // FIXME: find a way to use this from the main's genesis.\n\tadminAddr = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"\n}\n\nfunc AdminSetAdminAddr(addr std.Address) {\n\tassertIsAdmin()\n\tadminAddr = addr\n}\n\nfunc AdminSetInPause(state bool) {\n\tassertIsAdmin()\n\tinPause = state\n}\n\nfunc AdminAddModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), true)\n}\n\nfunc AdminRemoveModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), false) // FIXME: delete instead?\n}\n\nfunc NewPostExecutor(slug, title, body, publicationDate, authors, tags string) dao.Executor {\n\tcallback := func() error {\n\t\taddPost(std.PrevRealm().Addr(), slug, title, body, publicationDate, authors, tags)\n\n\t\treturn nil\n\t}\n\n\treturn bridge.GovDAO().NewGovDAOExecutor(callback)\n}\n\nfunc ModAddPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\tcaller := std.GetOrigCaller()\n\taddPost(caller, slug, title, body, publicationDate, authors, tags)\n}\n\nfunc addPost(caller std.Address, slug, title, body, publicationDate, authors, tags string) {\n\tvar tagList []string\n\tif tags != \"\" {\n\t\ttagList = strings.Split(tags, \",\")\n\t}\n\tvar authorList []string\n\tif authors != \"\" {\n\t\tauthorList = strings.Split(authors, \",\")\n\t}\n\n\terr := b.NewPost(caller, slug, title, body, publicationDate, authorList, tagList)\n\n\tcheckErr(err)\n}\n\nfunc ModEditPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.GetPost(slug).Update(title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc ModRemovePost(slug string) {\n\tassertIsModerator()\n\n\tb.RemovePost(slug)\n}\n\nfunc ModAddCommenter(addr std.Address) {\n\tassertIsModerator()\n\tcommenterList.Set(addr.String(), true)\n}\n\nfunc ModDelCommenter(addr std.Address) {\n\tassertIsModerator()\n\tcommenterList.Set(addr.String(), false) // FIXME: delete instead?\n}\n\nfunc ModDelComment(slug string, index int) {\n\tassertIsModerator()\n\n\terr := b.GetPost(slug).DeleteComment(index)\n\tcheckErr(err)\n}\n\nfunc isAdmin(addr std.Address) bool {\n\treturn addr == adminAddr\n}\n\nfunc isModerator(addr std.Address) bool {\n\t_, found := moderatorList.Get(addr.String())\n\treturn found\n}\n\nfunc isCommenter(addr std.Address) bool {\n\t_, found := commenterList.Get(addr.String())\n\treturn found\n}\n\nfunc assertIsAdmin() {\n\tcaller := std.GetOrigCaller()\n\tif !isAdmin(caller) {\n\t\tpanic(\"access restricted.\")\n\t}\n}\n\nfunc assertIsModerator() {\n\tcaller := std.GetOrigCaller()\n\tif isAdmin(caller) || isModerator(caller) {\n\t\treturn\n\t}\n\tpanic(\"access restricted\")\n}\n\nfunc assertIsCommenter() {\n\tcaller := std.GetOrigCaller()\n\tif isAdmin(caller) || isModerator(caller) || isCommenter(caller) {\n\t\treturn\n\t}\n\tpanic(\"access restricted\")\n}\n\nfunc assertNotInPause() {\n\tif inPause {\n\t\tpanic(\"access restricted (pause)\")\n\t}\n}\n"},{"name":"gnoblog.gno","body":"package gnoblog\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/blog\"\n)\n\nvar b = \u0026blog.Blog{\n\tTitle: \"Gnoland's Blog\",\n\tPrefix: \"/r/gnoland/blog:\",\n}\n\nfunc AddComment(postSlug, comment string) {\n\tassertIsCommenter()\n\tassertNotInPause()\n\n\tcaller := std.GetOrigCaller()\n\terr := b.GetPost(postSlug).AddComment(caller, comment)\n\tcheckErr(err)\n}\n\nfunc Render(path string) string {\n\treturn b.Render(path)\n}\n\nfunc RenderLastPostsWidget(limit int) string {\n\treturn b.RenderLastPostsWidget(limit)\n}\n\nfunc PostExists(slug string) bool {\n\tif b.GetPost(slug) == nil {\n\t\treturn false\n\t}\n\treturn true\n}\n"},{"name":"gnoblog_test.gno","body":"package gnoblog\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestPackage(t *testing.T) {\n\tstd.TestSetOrigCaller(std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"))\n\n\tauthor := std.GetOrigCaller()\n\n\t// by default, no posts.\n\t{\n\t\tgot := Render(\"\")\n\t\texpected := `\n# Gnoland's Blog\n\nNo posts.\n`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// create two posts, list post.\n\t{\n\t\tModAddPost(\"slug1\", \"title1\", \"body1\", \"2022-05-20T13:17:22Z\", \"moul\", \"tag1,tag2\")\n\t\tModAddPost(\"slug2\", \"title2\", \"body2\", \"2022-05-20T13:17:23Z\", \"moul\", \"tag1,tag3\")\n\t\tgot := Render(\"\")\n\t\texpected := `\n\t# Gnoland's Blog\n\n\u003cdiv class='columns-3'\u003e\u003cdiv\u003e\n\n### [title2](/r/gnoland/blog:p/slug2)\n 20 May 2022\n\u003c/div\u003e\u003cdiv\u003e\n\n### [title1](/r/gnoland/blog:p/slug1)\n 20 May 2022\n\u003c/div\u003e\u003c/div\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// view post.\n\t{\n\t\tgot := Render(\"p/slug2\")\n\t\texpected := `\n\u003cmain class='gno-tmpl-page'\u003e\n\n# title2\n\nbody2\n\n---\n\nTags: [#tag1](/r/gnoland/blog:t/tag1) [#tag3](/r/gnoland/blog:t/tag3)\n\nWritten by moul on 20 May 2022\n\nPublished by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq to Gnoland's Blog\n\n---\n\u003cdetails\u003e\u003csummary\u003eComment section\u003c/summary\u003e\n\n\u003c/details\u003e\n\u003c/main\u003e\n\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// list by tags.\n\t{\n\t\tgot := Render(\"t/invalid\")\n\t\texpected := \"# [Gnoland's Blog](/r/gnoland/blog:) / t / invalid\\n\\nNo posts.\"\n\t\tassertMDEquals(t, got, expected)\n\n\t\tgot = Render(\"t/tag2\")\n\t\texpected = `\n# [Gnoland's Blog](/r/gnoland/blog:) / t / tag2\n\n\u003cdiv\u003e\n\n### [title1](/r/gnoland/blog:p/slug1)\n 20 May 2022\n\u003c/div\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// add comments.\n\t{\n\t\tAddComment(\"slug1\", \"comment1\")\n\t\tAddComment(\"slug2\", \"comment2\")\n\t\tAddComment(\"slug1\", \"comment3\")\n\t\tAddComment(\"slug2\", \"comment4\")\n\t\tAddComment(\"slug1\", \"comment5\")\n\t\tgot := Render(\"p/slug2\")\n\t\texpected := `\u003cmain class='gno-tmpl-page'\u003e\n\n# title2\n\nbody2\n\n---\n\nTags: [#tag1](/r/gnoland/blog:t/tag1) [#tag3](/r/gnoland/blog:t/tag3)\n\nWritten by moul on 20 May 2022\n\nPublished by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq to Gnoland's Blog\n\n---\n\u003cdetails\u003e\u003csummary\u003eComment section\u003c/summary\u003e\n\n\u003ch5\u003ecomment4\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003ch5\u003ecomment2\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003c/details\u003e\n\u003c/main\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// edit post.\n\t{\n\t\toldTitle := \"title2\"\n\t\toldDate := \"2022-05-20T13:17:23Z\"\n\n\t\tModEditPost(\"slug2\", oldTitle, \"body2++\", oldDate, \"manfred\", \"tag1,tag4\")\n\t\tgot := Render(\"p/slug2\")\n\t\texpected := `\u003cmain class='gno-tmpl-page'\u003e\n\n# title2\n\nbody2++\n\n---\n\nTags: [#tag1](/r/gnoland/blog:t/tag1) [#tag4](/r/gnoland/blog:t/tag4)\n\nWritten by manfred on 20 May 2022\n\nPublished by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq to Gnoland's Blog\n\n---\n\u003cdetails\u003e\u003csummary\u003eComment section\u003c/summary\u003e\n\n\u003ch5\u003ecomment4\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003ch5\u003ecomment2\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003c/details\u003e\n\u003c/main\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\n\t\thome := Render(\"\")\n\n\t\tif strings.Count(home, oldTitle) != 1 {\n\t\t\tt.Errorf(\"post not edited properly\")\n\t\t}\n\t\t// Edits work everything except title, slug, and publicationDate\n\t\t// Edits to the above will cause duplication on the blog home page\n\t}\n\n\t{ // Test remove functionality\n\t\ttitle := \"example title\"\n\t\tslug := \"testSlug1\"\n\t\tModAddPost(slug, title, \"body1\", \"2022-05-25T13:17:22Z\", \"moul\", \"tag1,tag2\")\n\n\t\tgot := Render(\"\")\n\n\t\tif !strings.Contains(got, title) {\n\t\t\tt.Errorf(\"post was not added properly\")\n\t\t}\n\n\t\tpostRender := Render(\"p/\" + slug)\n\n\t\tif !strings.Contains(postRender, title) {\n\t\t\tt.Errorf(\"post not rendered properly\")\n\t\t}\n\n\t\tModRemovePost(slug)\n\t\tgot = Render(\"\")\n\n\t\tif strings.Contains(got, title) {\n\t\t\tt.Errorf(\"post was not removed\")\n\t\t}\n\n\t\tpostRender = Render(\"p/\" + slug)\n\n\t\tassertMDEquals(t, postRender, \"404\")\n\t}\n\n\t// TODO: pagination.\n\t// TODO: ?format=...\n\n\t// all 404s\n\t{\n\t\tnotFoundPaths := []string{\n\t\t\t\"p/slug3\",\n\t\t\t\"p\",\n\t\t\t\"p/\",\n\t\t\t\"x/x\",\n\t\t\t\"t\",\n\t\t\t\"t/\",\n\t\t\t\"/\",\n\t\t\t\"p/slug1/\",\n\t\t}\n\t\tfor _, notFoundPath := range notFoundPaths {\n\t\t\tgot := Render(notFoundPath)\n\t\t\texpected := \"404\"\n\t\t\tif got != expected {\n\t\t\t\tt.Errorf(\"path %q: expected %q, got %q.\", notFoundPath, expected, got)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc assertMDEquals(t *testing.T, got, expected string) {\n\tt.Helper()\n\texpected = strings.TrimSpace(expected)\n\tgot = strings.TrimSpace(got)\n\tif expected != got {\n\t\tt.Errorf(\"invalid render output.\\nexpected %q.\\ngot %q.\", expected, got)\n\t}\n}\n"},{"name":"util.gno","body":"package gnoblog\n\nfunc checkErr(err error) {\n\tif err != nil {\n\t\tpanic(err)\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":"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/users\"\n)\n\nvar (\n\tbanker *grc20.Banker = grc20.NewBanker(\"wrapped GNOT\", \"wugnot\", 0)\n\tToken = banker.Token()\n)\n\nconst (\n\tugnotMinDeposit uint64 = 1000\n\twugnotMinDeposit uint64 = 1\n)\n\nfunc Deposit() {\n\tcaller := std.PrevRealm().Addr()\n\tsent := std.OrigSend()\n\tamount := sent.AmountOf(\"ugnot\")\n\n\trequire(uint64(amount) \u003e= ugnotMinDeposit, ufmt.Sprintf(\"Deposit below minimum: %d/%d ugnot.\", amount, ugnotMinDeposit))\n\tcheckErr(banker.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(banker.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 banker.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\tcheckErr(Token.Transfer(toAddr, amount))\n}\n\nfunc Approve(spender pusers.AddressOrName, amount uint64) {\n\tspenderAddr := users.Resolve(spender)\n\tcheckErr(Token.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(Token.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=200000000 |\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=200000000 |\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=200004242 |\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":""}} +{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"gnoblog","path":"gno.land/r/gnoland/blog","files":[{"name":"admin.gno","body":"package gnoblog\n\nimport (\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/r/gov/dao/bridge\"\n)\n\nvar (\n\tadminAddr std.Address\n\tmoderatorList avl.Tree\n\tcommenterList avl.Tree\n\tinPause bool\n)\n\nfunc init() {\n\t// adminAddr = std.OrigCaller() // FIXME: find a way to use this from the main's genesis.\n\tadminAddr = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"\n}\n\nfunc AdminSetAdminAddr(addr std.Address) {\n\tassertIsAdmin()\n\tadminAddr = addr\n}\n\nfunc AdminSetInPause(state bool) {\n\tassertIsAdmin()\n\tinPause = state\n}\n\nfunc AdminAddModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), true)\n}\n\nfunc AdminRemoveModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), false) // FIXME: delete instead?\n}\n\nfunc NewPostExecutor(slug, title, body, publicationDate, authors, tags string) dao.Executor {\n\tcallback := func() error {\n\t\taddPost(std.PrevRealm().Addr(), slug, title, body, publicationDate, authors, tags)\n\n\t\treturn nil\n\t}\n\n\treturn bridge.GovDAO().NewGovDAOExecutor(callback)\n}\n\nfunc ModAddPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\tcaller := std.OrigCaller()\n\taddPost(caller, slug, title, body, publicationDate, authors, tags)\n}\n\nfunc addPost(caller std.Address, slug, title, body, publicationDate, authors, tags string) {\n\tvar tagList []string\n\tif tags != \"\" {\n\t\ttagList = strings.Split(tags, \",\")\n\t}\n\tvar authorList []string\n\tif authors != \"\" {\n\t\tauthorList = strings.Split(authors, \",\")\n\t}\n\n\terr := b.NewPost(caller, slug, title, body, publicationDate, authorList, tagList)\n\n\tcheckErr(err)\n}\n\nfunc ModEditPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.GetPost(slug).Update(title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc ModRemovePost(slug string) {\n\tassertIsModerator()\n\n\tb.RemovePost(slug)\n}\n\nfunc ModAddCommenter(addr std.Address) {\n\tassertIsModerator()\n\tcommenterList.Set(addr.String(), true)\n}\n\nfunc ModDelCommenter(addr std.Address) {\n\tassertIsModerator()\n\tcommenterList.Set(addr.String(), false) // FIXME: delete instead?\n}\n\nfunc ModDelComment(slug string, index int) {\n\tassertIsModerator()\n\n\terr := b.GetPost(slug).DeleteComment(index)\n\tcheckErr(err)\n}\n\nfunc isAdmin(addr std.Address) bool {\n\treturn addr == adminAddr\n}\n\nfunc isModerator(addr std.Address) bool {\n\t_, found := moderatorList.Get(addr.String())\n\treturn found\n}\n\nfunc isCommenter(addr std.Address) bool {\n\t_, found := commenterList.Get(addr.String())\n\treturn found\n}\n\nfunc assertIsAdmin() {\n\tcaller := std.OrigCaller()\n\tif !isAdmin(caller) {\n\t\tpanic(\"access restricted.\")\n\t}\n}\n\nfunc assertIsModerator() {\n\tcaller := std.OrigCaller()\n\tif isAdmin(caller) || isModerator(caller) {\n\t\treturn\n\t}\n\tpanic(\"access restricted\")\n}\n\nfunc assertIsCommenter() {\n\tcaller := std.OrigCaller()\n\tif isAdmin(caller) || isModerator(caller) || isCommenter(caller) {\n\t\treturn\n\t}\n\tpanic(\"access restricted\")\n}\n\nfunc assertNotInPause() {\n\tif inPause {\n\t\tpanic(\"access restricted (pause)\")\n\t}\n}\n"},{"name":"gnoblog.gno","body":"package gnoblog\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/blog\"\n)\n\nvar b = \u0026blog.Blog{\n\tTitle: \"Gnoland's Blog\",\n\tPrefix: \"/r/gnoland/blog:\",\n}\n\nfunc AddComment(postSlug, comment string) {\n\tassertIsCommenter()\n\tassertNotInPause()\n\n\tcaller := std.OrigCaller()\n\terr := b.GetPost(postSlug).AddComment(caller, comment)\n\tcheckErr(err)\n}\n\nfunc Render(path string) string {\n\treturn b.Render(path)\n}\n\nfunc RenderLastPostsWidget(limit int) string {\n\treturn b.RenderLastPostsWidget(limit)\n}\n\nfunc PostExists(slug string) bool {\n\tif b.GetPost(slug) == nil {\n\t\treturn false\n\t}\n\treturn true\n}\n"},{"name":"gnoblog_test.gno","body":"package gnoblog\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestPackage(t *testing.T) {\n\tstd.TestSetOrigCaller(std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"))\n\n\tauthor := std.OrigCaller()\n\n\t// by default, no posts.\n\t{\n\t\tgot := Render(\"\")\n\t\texpected := `\n# Gnoland's Blog\n\nNo posts.\n`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// create two posts, list post.\n\t{\n\t\tModAddPost(\"slug1\", \"title1\", \"body1\", \"2022-05-20T13:17:22Z\", \"moul\", \"tag1,tag2\")\n\t\tModAddPost(\"slug2\", \"title2\", \"body2\", \"2022-05-20T13:17:23Z\", \"moul\", \"tag1,tag3\")\n\t\tgot := Render(\"\")\n\t\texpected := `\n\t# Gnoland's Blog\n\n\u003cdiv class='columns-3'\u003e\u003cdiv\u003e\n\n### [title2](/r/gnoland/blog:p/slug2)\n 20 May 2022\n\u003c/div\u003e\u003cdiv\u003e\n\n### [title1](/r/gnoland/blog:p/slug1)\n 20 May 2022\n\u003c/div\u003e\u003c/div\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// view post.\n\t{\n\t\tgot := Render(\"p/slug2\")\n\t\texpected := `\n\u003cmain class='gno-tmpl-page'\u003e\n\n# title2\n\nbody2\n\n---\n\nTags: [#tag1](/r/gnoland/blog:t/tag1) [#tag3](/r/gnoland/blog:t/tag3)\n\nWritten by moul on 20 May 2022\n\nPublished by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq to Gnoland's Blog\n\n---\n\u003cdetails\u003e\u003csummary\u003eComment section\u003c/summary\u003e\n\n\u003c/details\u003e\n\u003c/main\u003e\n\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// list by tags.\n\t{\n\t\tgot := Render(\"t/invalid\")\n\t\texpected := \"# [Gnoland's Blog](/r/gnoland/blog:) / t / invalid\\n\\nNo posts.\"\n\t\tassertMDEquals(t, got, expected)\n\n\t\tgot = Render(\"t/tag2\")\n\t\texpected = `\n# [Gnoland's Blog](/r/gnoland/blog:) / t / tag2\n\n\u003cdiv\u003e\n\n### [title1](/r/gnoland/blog:p/slug1)\n 20 May 2022\n\u003c/div\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// add comments.\n\t{\n\t\tAddComment(\"slug1\", \"comment1\")\n\t\tAddComment(\"slug2\", \"comment2\")\n\t\tAddComment(\"slug1\", \"comment3\")\n\t\tAddComment(\"slug2\", \"comment4\")\n\t\tAddComment(\"slug1\", \"comment5\")\n\t\tgot := Render(\"p/slug2\")\n\t\texpected := `\u003cmain class='gno-tmpl-page'\u003e\n\n# title2\n\nbody2\n\n---\n\nTags: [#tag1](/r/gnoland/blog:t/tag1) [#tag3](/r/gnoland/blog:t/tag3)\n\nWritten by moul on 20 May 2022\n\nPublished by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq to Gnoland's Blog\n\n---\n\u003cdetails\u003e\u003csummary\u003eComment section\u003c/summary\u003e\n\n\u003ch5\u003ecomment4\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003ch5\u003ecomment2\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003c/details\u003e\n\u003c/main\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// edit post.\n\t{\n\t\toldTitle := \"title2\"\n\t\toldDate := \"2022-05-20T13:17:23Z\"\n\n\t\tModEditPost(\"slug2\", oldTitle, \"body2++\", oldDate, \"manfred\", \"tag1,tag4\")\n\t\tgot := Render(\"p/slug2\")\n\t\texpected := `\u003cmain class='gno-tmpl-page'\u003e\n\n# title2\n\nbody2++\n\n---\n\nTags: [#tag1](/r/gnoland/blog:t/tag1) [#tag4](/r/gnoland/blog:t/tag4)\n\nWritten by manfred on 20 May 2022\n\nPublished by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq to Gnoland's Blog\n\n---\n\u003cdetails\u003e\u003csummary\u003eComment section\u003c/summary\u003e\n\n\u003ch5\u003ecomment4\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003ch5\u003ecomment2\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003c/details\u003e\n\u003c/main\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\n\t\thome := Render(\"\")\n\n\t\tif strings.Count(home, oldTitle) != 1 {\n\t\t\tt.Errorf(\"post not edited properly\")\n\t\t}\n\t\t// Edits work everything except title, slug, and publicationDate\n\t\t// Edits to the above will cause duplication on the blog home page\n\t}\n\n\t{ // Test remove functionality\n\t\ttitle := \"example title\"\n\t\tslug := \"testSlug1\"\n\t\tModAddPost(slug, title, \"body1\", \"2022-05-25T13:17:22Z\", \"moul\", \"tag1,tag2\")\n\n\t\tgot := Render(\"\")\n\n\t\tif !strings.Contains(got, title) {\n\t\t\tt.Errorf(\"post was not added properly\")\n\t\t}\n\n\t\tpostRender := Render(\"p/\" + slug)\n\n\t\tif !strings.Contains(postRender, title) {\n\t\t\tt.Errorf(\"post not rendered properly\")\n\t\t}\n\n\t\tModRemovePost(slug)\n\t\tgot = Render(\"\")\n\n\t\tif strings.Contains(got, title) {\n\t\t\tt.Errorf(\"post was not removed\")\n\t\t}\n\n\t\tpostRender = Render(\"p/\" + slug)\n\n\t\tassertMDEquals(t, postRender, \"404\")\n\t}\n\n\t// TODO: pagination.\n\t// TODO: ?format=...\n\n\t// all 404s\n\t{\n\t\tnotFoundPaths := []string{\n\t\t\t\"p/slug3\",\n\t\t\t\"p\",\n\t\t\t\"p/\",\n\t\t\t\"x/x\",\n\t\t\t\"t\",\n\t\t\t\"t/\",\n\t\t\t\"/\",\n\t\t\t\"p/slug1/\",\n\t\t}\n\t\tfor _, notFoundPath := range notFoundPaths {\n\t\t\tgot := Render(notFoundPath)\n\t\t\texpected := \"404\"\n\t\t\tif got != expected {\n\t\t\t\tt.Errorf(\"path %q: expected %q, got %q.\", notFoundPath, expected, got)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc assertMDEquals(t *testing.T, got, expected string) {\n\tt.Helper()\n\texpected = strings.TrimSpace(expected)\n\tgot = strings.TrimSpace(got)\n\tif expected != got {\n\t\tt.Errorf(\"invalid render output.\\nexpected %q.\\ngot %q.\", expected, got)\n\t}\n}\n"},{"name":"util.gno","body":"package gnoblog\n\nfunc checkErr(err error) {\n\tif err != nil {\n\t\tpanic(err)\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":"events","path":"gno.land/r/gnoland/events","files":[{"name":"administration.gno","body":"package events\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/ownable/exts/authorizable\"\n)\n\nvar (\n\tsu = std.Address(\"g125em6arxsnj49vx35f0n0z34putv5ty3376fg5\") // @leohhhn\n\tauth = authorizable.NewAuthorizableWithAddress(su)\n)\n\n// GetOwner gets the owner of the events realm\nfunc GetOwner() std.Address {\n\treturn auth.Owner()\n}\n\n// AddModerator adds a moderator to the events realm\nfunc AddModerator(mod std.Address) {\n\tauth.AssertCallerIsOwner()\n\n\tif err := auth.AddToAuthList(mod); err != nil {\n\t\tpanic(err)\n\t}\n}\n"},{"name":"errors.gno","body":"package events\n\nimport (\n\t\"errors\"\n\t\"strconv\"\n)\n\nvar (\n\tErrEmptyName = errors.New(\"event name cannot be empty\")\n\tErrNoSuchID = errors.New(\"event with specified ID does not exist\")\n\tErrMinWidgetSize = errors.New(\"you need to request at least 1 event to render\")\n\tErrMaxWidgetSize = errors.New(\"maximum number of events in widget is\" + strconv.Itoa(MaxWidgetSize))\n\tErrDescriptionTooLong = errors.New(\"event description is too long\")\n\tErrInvalidStartTime = errors.New(\"invalid start time format\")\n\tErrInvalidEndTime = errors.New(\"invalid end time format\")\n\tErrEndBeforeStart = errors.New(\"end time cannot be before start time\")\n\tErrStartEndTimezonemMismatch = errors.New(\"start and end timezones are not the same\")\n)\n"},{"name":"events.gno","body":"// Package events allows you to upload data about specific IRL/online events\n// It includes dynamic support for updating rendering events based on their\n// status, ie if they are upcoming, in progress, or in the past.\npackage events\n\nimport (\n\t\"sort\"\n\t\"std\"\n\t\"strings\"\n\t\"time\"\n\n\t\"gno.land/p/demo/seqid\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype (\n\tEvent struct {\n\t\tid string\n\t\tname string // name of event\n\t\tdescription string // short description of event\n\t\tlink string // link to auth corresponding web2 page, ie eventbrite/luma or conference page\n\t\tlocation string // location of the event\n\t\tstartTime time.Time // given in RFC3339\n\t\tendTime time.Time // end time of the event, given in RFC3339\n\t}\n\n\teventsSlice []*Event\n)\n\nvar (\n\tevents = make(eventsSlice, 0) // sorted\n\tidCounter seqid.ID\n)\n\nconst (\n\tmaxDescLength = 100\n\tEventAdded = \"EventAdded\"\n\tEventDeleted = \"EventDeleted\"\n\tEventEdited = \"EventEdited\"\n)\n\n// AddEvent adds auth new event\n// Start time \u0026 end time need to be specified in RFC3339, ie 2024-08-08T12:00:00+02:00\nfunc AddEvent(name, description, link, location, startTime, endTime string) (string, error) {\n\tauth.AssertOnAuthList()\n\n\tif strings.TrimSpace(name) == \"\" {\n\t\treturn \"\", ErrEmptyName\n\t}\n\n\tif len(description) \u003e maxDescLength {\n\t\treturn \"\", ufmt.Errorf(\"%s: provided length is %d, maximum is %d\", ErrDescriptionTooLong, len(description), maxDescLength)\n\t}\n\n\t// Parse times\n\tst, et, err := parseTimes(startTime, endTime)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tid := idCounter.Next().String()\n\te := \u0026Event{\n\t\tid: id,\n\t\tname: name,\n\t\tdescription: description,\n\t\tlink: link,\n\t\tlocation: location,\n\t\tstartTime: st,\n\t\tendTime: et,\n\t}\n\n\tevents = append(events, e)\n\tsort.Sort(events)\n\n\tstd.Emit(EventAdded,\n\t\t\"id\", e.id,\n\t)\n\n\treturn id, nil\n}\n\n// DeleteEvent deletes an event with auth given ID\nfunc DeleteEvent(id string) {\n\tauth.AssertOnAuthList()\n\n\te, idx, err := GetEventByID(id)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tevents = append(events[:idx], events[idx+1:]...)\n\n\tstd.Emit(EventDeleted,\n\t\t\"id\", e.id,\n\t)\n}\n\n// EditEvent edits an event with auth given ID\n// It only updates values corresponding to non-empty arguments sent with the call\n// Note: if you need to update the start time or end time, you need to provide both every time\nfunc EditEvent(id string, name, description, link, location, startTime, endTime string) {\n\tauth.AssertOnAuthList()\n\n\te, _, err := GetEventByID(id)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Set only valid values\n\tif strings.TrimSpace(name) != \"\" {\n\t\te.name = name\n\t}\n\n\tif strings.TrimSpace(description) != \"\" {\n\t\te.description = description\n\t}\n\n\tif strings.TrimSpace(link) != \"\" {\n\t\te.link = link\n\t}\n\n\tif strings.TrimSpace(location) != \"\" {\n\t\te.location = location\n\t}\n\n\tif strings.TrimSpace(startTime) != \"\" || strings.TrimSpace(endTime) != \"\" {\n\t\tst, et, err := parseTimes(startTime, endTime)\n\t\tif err != nil {\n\t\t\tpanic(err) // need to also revert other state changes\n\t\t}\n\n\t\toldStartTime := e.startTime\n\t\te.startTime = st\n\t\te.endTime = et\n\n\t\t// If sort order was disrupted, sort again\n\t\tif oldStartTime != e.startTime {\n\t\t\tsort.Sort(events)\n\t\t}\n\t}\n\n\tstd.Emit(EventEdited,\n\t\t\"id\", e.id,\n\t)\n}\n\nfunc GetEventByID(id string) (*Event, int, error) {\n\tfor i, event := range events {\n\t\tif event.id == id {\n\t\t\treturn event, i, nil\n\t\t}\n\t}\n\n\treturn nil, -1, ErrNoSuchID\n}\n\n// Len returns the length of the slice\nfunc (m eventsSlice) Len() int {\n\treturn len(m)\n}\n\n// Less compares the startTime fields of two elements\n// In this case, events will be sorted by largest startTime first (upcoming \u003e past)\nfunc (m eventsSlice) Less(i, j int) bool {\n\treturn m[i].startTime.After(m[j].startTime)\n}\n\n// Swap swaps two elements in the slice\nfunc (m eventsSlice) Swap(i, j int) {\n\tm[i], m[j] = m[j], m[i]\n}\n\n// parseTimes parses the start and end time for an event and checks for possible errors\nfunc parseTimes(startTime, endTime string) (time.Time, time.Time, error) {\n\tst, err := time.Parse(time.RFC3339, startTime)\n\tif err != nil {\n\t\treturn time.Time{}, time.Time{}, ufmt.Errorf(\"%s: %s\", ErrInvalidStartTime, err.Error())\n\t}\n\n\tet, err := time.Parse(time.RFC3339, endTime)\n\tif err != nil {\n\t\treturn time.Time{}, time.Time{}, ufmt.Errorf(\"%s: %s\", ErrInvalidEndTime, err.Error())\n\t}\n\n\tif et.Before(st) {\n\t\treturn time.Time{}, time.Time{}, ErrEndBeforeStart\n\t}\n\n\t_, stOffset := st.Zone()\n\t_, etOffset := et.Zone()\n\tif stOffset != etOffset {\n\t\treturn time.Time{}, time.Time{}, ErrStartEndTimezonemMismatch\n\t}\n\n\treturn st, et, nil\n}\n"},{"name":"events_test.gno","body":"package events\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gno.land/p/demo/uassert\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nvar (\n\tsuRealm = std.NewUserRealm(su)\n\n\tnow = \"2009-02-13T23:31:30Z\" // time.Now() is hardcoded to this value in the gno test machine currently\n\tparsedTimeNow, _ = time.Parse(time.RFC3339, now)\n)\n\nfunc TestAddEvent(t *testing.T) {\n\tstd.TestSetOrigCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\te1Start := parsedTimeNow.Add(time.Hour * 24 * 5)\n\te1End := e1Start.Add(time.Hour * 4)\n\n\tAddEvent(\"Event 1\", \"this event is upcoming\", \"gno.land\", \"gnome land\", e1Start.Format(time.RFC3339), e1End.Format(time.RFC3339))\n\n\tgot := renderHome(false)\n\n\tif !strings.Contains(got, \"Event 1\") {\n\t\tt.Fatalf(\"Expected to find Event 1 in render\")\n\t}\n\n\te2Start := parsedTimeNow.Add(-time.Hour * 24 * 5)\n\te2End := e2Start.Add(time.Hour * 4)\n\n\tAddEvent(\"Event 2\", \"this event is in the past\", \"gno.land\", \"gnome land\", e2Start.Format(time.RFC3339), e2End.Format(time.RFC3339))\n\n\tgot = renderHome(false)\n\n\tupcomingPos := strings.Index(got, \"## Upcoming events\")\n\tpastPos := strings.Index(got, \"## Past events\")\n\n\te1Pos := strings.Index(got, \"Event 1\")\n\te2Pos := strings.Index(got, \"Event 2\")\n\n\t// expected index ordering: upcoming \u003c e1 \u003c past \u003c e2\n\tif e1Pos \u003c upcomingPos || e1Pos \u003e pastPos {\n\t\tt.Fatalf(\"Expected to find Event 1 in Upcoming events\")\n\t}\n\n\tif e2Pos \u003c upcomingPos || e2Pos \u003c pastPos || e2Pos \u003c e1Pos {\n\t\tt.Fatalf(\"Expected to find Event 2 on auth different pos\")\n\t}\n\n\t// larger index =\u003e smaller startTime (future =\u003e past)\n\tif events[0].startTime.Unix() \u003c events[1].startTime.Unix() {\n\t\tt.Fatalf(\"expected ordering to be different\")\n\t}\n}\n\nfunc TestAddEventErrors(t *testing.T) {\n\tstd.TestSetOrigCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\t_, err := AddEvent(\"\", \"sample desc\", \"gno.land\", \"gnome land\", \"2009-02-13T23:31:31Z\", \"2009-02-13T23:33:31Z\")\n\tuassert.ErrorIs(t, err, ErrEmptyName)\n\n\t_, err = AddEvent(\"sample name\", \"sample desc\", \"gno.land\", \"gnome land\", \"\", \"2009-02-13T23:33:31Z\")\n\tuassert.ErrorContains(t, err, ErrInvalidStartTime.Error())\n\n\t_, err = AddEvent(\"sample name\", \"sample desc\", \"gno.land\", \"gnome land\", \"2009-02-13T23:31:31Z\", \"\")\n\tuassert.ErrorContains(t, err, ErrInvalidEndTime.Error())\n\n\t_, err = AddEvent(\"sample name\", \"sample desc\", \"gno.land\", \"gnome land\", \"2009-02-13T23:31:31Z\", \"2009-02-13T23:30:31Z\")\n\tuassert.ErrorIs(t, err, ErrEndBeforeStart)\n\n\t_, err = AddEvent(\"sample name\", \"sample desc\", \"gno.land\", \"gnome land\", \"2009-02-13T23:31:31+06:00\", \"2009-02-13T23:33:31+02:00\")\n\tuassert.ErrorIs(t, err, ErrStartEndTimezonemMismatch)\n\n\ttooLongDesc := `Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean ma`\n\t_, err = AddEvent(\"sample name\", tooLongDesc, \"gno.land\", \"gnome land\", \"2009-02-13T23:31:31Z\", \"2009-02-13T23:33:31Z\")\n\tuassert.ErrorContains(t, err, ErrDescriptionTooLong.Error())\n}\n\nfunc TestDeleteEvent(t *testing.T) {\n\tstd.TestSetOrigCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\te1Start := parsedTimeNow.Add(time.Hour * 24 * 5)\n\te1End := e1Start.Add(time.Hour * 4)\n\n\tid, _ := AddEvent(\"ToDelete\", \"description\", \"gno.land\", \"gnome land\", e1Start.Format(time.RFC3339), e1End.Format(time.RFC3339))\n\n\tgot := renderHome(false)\n\n\tif !strings.Contains(got, \"ToDelete\") {\n\t\tt.Fatalf(\"Expected to find ToDelete event in render\")\n\t}\n\n\tDeleteEvent(id)\n\tgot = renderHome(false)\n\n\tif strings.Contains(got, \"ToDelete\") {\n\t\tt.Fatalf(\"Did not expect to find ToDelete event in render\")\n\t}\n}\n\nfunc TestEditEvent(t *testing.T) {\n\tstd.TestSetOrigCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\te1Start := parsedTimeNow.Add(time.Hour * 24 * 5)\n\te1End := e1Start.Add(time.Hour * 4)\n\tloc := \"gnome land\"\n\n\tid, _ := AddEvent(\"ToDelete\", \"description\", \"gno.land\", loc, e1Start.Format(time.RFC3339), e1End.Format(time.RFC3339))\n\n\tnewName := \"New Name\"\n\tnewDesc := \"Normal description\"\n\tnewLink := \"new Link\"\n\tnewST := e1Start.Add(time.Hour)\n\tnewET := newST.Add(time.Hour)\n\n\tEditEvent(id, newName, newDesc, newLink, \"\", newST.Format(time.RFC3339), newET.Format(time.RFC3339))\n\tedited, _, _ := GetEventByID(id)\n\n\t// Check updated values\n\tuassert.Equal(t, edited.name, newName)\n\tuassert.Equal(t, edited.description, newDesc)\n\tuassert.Equal(t, edited.link, newLink)\n\tuassert.True(t, edited.startTime.Equal(newST))\n\tuassert.True(t, edited.endTime.Equal(newET))\n\n\t// Check if the old values are the same\n\tuassert.Equal(t, edited.location, loc)\n}\n\nfunc TestInvalidEdit(t *testing.T) {\n\tstd.TestSetOrigCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\tuassert.PanicsWithMessage(t, ErrNoSuchID.Error(), func() {\n\t\tEditEvent(\"123123\", \"\", \"\", \"\", \"\", \"\", \"\")\n\t})\n}\n\nfunc TestParseTimes(t *testing.T) {\n\t// times not provided\n\t// end time before start time\n\t// timezone Missmatch\n\n\t_, _, err := parseTimes(\"\", \"\")\n\tuassert.ErrorContains(t, err, ErrInvalidStartTime.Error())\n\n\t_, _, err = parseTimes(now, \"\")\n\tuassert.ErrorContains(t, err, ErrInvalidEndTime.Error())\n\n\t_, _, err = parseTimes(\"2009-02-13T23:30:30Z\", \"2009-02-13T21:30:30Z\")\n\tuassert.ErrorContains(t, err, ErrEndBeforeStart.Error())\n\n\t_, _, err = parseTimes(\"2009-02-10T23:30:30+02:00\", \"2009-02-13T21:30:33+05:00\")\n\tuassert.ErrorContains(t, err, ErrStartEndTimezonemMismatch.Error())\n}\n\nfunc TestRenderEventWidget(t *testing.T) {\n\tstd.TestSetOrigCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\t// No events yet\n\tevents = nil\n\tout, err := RenderEventWidget(1)\n\tuassert.NoError(t, err)\n\tuassert.Equal(t, out, \"No events.\")\n\n\t// Too many events\n\tout, err = RenderEventWidget(MaxWidgetSize + 1)\n\tuassert.ErrorIs(t, err, ErrMaxWidgetSize)\n\n\t// Too little events\n\tout, err = RenderEventWidget(0)\n\tuassert.ErrorIs(t, err, ErrMinWidgetSize)\n\n\t// Ordering \u0026 if requested amt is larger than the num of events that exist\n\te1Start := parsedTimeNow.Add(time.Hour * 24 * 5)\n\te1End := e1Start.Add(time.Hour * 4)\n\n\te2Start := parsedTimeNow.Add(time.Hour * 24 * 10) // event 2 is after event 1\n\te2End := e2Start.Add(time.Hour * 4)\n\n\t_, err = AddEvent(\"Event 1\", \"description\", \"gno.land\", \"loc\", e1Start.Format(time.RFC3339), e1End.Format(time.RFC3339))\n\turequire.NoError(t, err)\n\n\t_, err = AddEvent(\"Event 2\", \"description\", \"gno.land\", \"loc\", e2Start.Format(time.RFC3339), e2End.Format(time.RFC3339))\n\turequire.NoError(t, err)\n\n\tout, err = RenderEventWidget(MaxWidgetSize)\n\turequire.NoError(t, err)\n\n\tuniqueSequence := \"- [\" // sequence that is displayed once per each event as per the RenderEventWidget function\n\tuassert.Equal(t, 2, strings.Count(out, uniqueSequence))\n\n\tuassert.True(t, strings.Index(out, \"Event 1\") \u003e strings.Index(out, \"Event 2\"))\n}\n"},{"name":"rendering.gno","body":"package events\n\nimport (\n\t\"bytes\"\n\t\"time\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\nconst (\n\tMaxWidgetSize = 5\n)\n\n// RenderEventWidget shows up to eventsToRender of the latest events to a caller\nfunc RenderEventWidget(eventsToRender int) (string, error) {\n\tnumOfEvents := len(events)\n\tif numOfEvents == 0 {\n\t\treturn \"No events.\", nil\n\t}\n\n\tif eventsToRender \u003e MaxWidgetSize {\n\t\treturn \"\", ErrMaxWidgetSize\n\t}\n\n\tif eventsToRender \u003c 1 {\n\t\treturn \"\", ErrMinWidgetSize\n\t}\n\n\tif eventsToRender \u003e numOfEvents {\n\t\teventsToRender = numOfEvents\n\t}\n\n\toutput := \"\"\n\n\tfor _, event := range events[:eventsToRender] {\n\t\toutput += ufmt.Sprintf(\"- [%s](%s)\\n\", event.name, event.link)\n\t}\n\n\treturn output, nil\n}\n\n// renderHome renders the home page of the events realm\nfunc renderHome(admin bool) string {\n\toutput := \"# gno.land events\\n\\n\"\n\n\tif len(events) == 0 {\n\t\toutput += \"No upcoming or past events.\"\n\t\treturn output\n\t}\n\n\toutput += \"Below is a list of all gno.land events, including in progress, upcoming, and past ones.\\n\\n\"\n\toutput += \"---\\n\\n\"\n\n\tvar (\n\t\tinProgress = \"\"\n\t\tupcoming = \"\"\n\t\tpast = \"\"\n\t\tnow = time.Now()\n\t)\n\n\tfor _, e := range events {\n\t\tif now.Before(e.startTime) {\n\t\t\tupcoming += e.Render(admin)\n\t\t} else if now.After(e.endTime) {\n\t\t\tpast += e.Render(admin)\n\t\t} else {\n\t\t\tinProgress += e.Render(admin)\n\t\t}\n\t}\n\n\tif upcoming != \"\" {\n\t\t// Add upcoming events\n\t\toutput += \"## Upcoming events\\n\\n\"\n\t\toutput += \"\u003cdiv class='columns-3'\u003e\"\n\n\t\toutput += upcoming\n\n\t\toutput += \"\u003c/div\u003e\\n\\n\"\n\t\toutput += \"---\\n\\n\"\n\t}\n\n\tif inProgress != \"\" {\n\t\toutput += \"## Currently in progress\\n\\n\"\n\t\toutput += \"\u003cdiv class='columns-3'\u003e\"\n\n\t\toutput += inProgress\n\n\t\toutput += \"\u003c/div\u003e\\n\\n\"\n\t\toutput += \"---\\n\\n\"\n\t}\n\n\tif past != \"\" {\n\t\t// Add past events\n\t\toutput += \"## Past events\\n\\n\"\n\t\toutput += \"\u003cdiv class='columns-3'\u003e\"\n\n\t\toutput += past\n\n\t\toutput += \"\u003c/div\u003e\\n\\n\"\n\t}\n\n\treturn output\n}\n\n// Render returns the markdown representation of a single event instance\nfunc (e Event) Render(admin bool) string {\n\tvar buf bytes.Buffer\n\n\tbuf.WriteString(\"\u003cdiv\u003e\\n\\n\")\n\tbuf.WriteString(ufmt.Sprintf(\"### %s\\n\\n\", e.name))\n\tbuf.WriteString(ufmt.Sprintf(\"%s\\n\\n\", e.description))\n\tbuf.WriteString(ufmt.Sprintf(\"**Location:** %s\\n\\n\", e.location))\n\n\t_, offset := e.startTime.Zone() // offset is in seconds\n\thoursOffset := offset / (60 * 60)\n\tsign := \"\"\n\tif offset \u003e= 0 {\n\t\tsign = \"+\"\n\t}\n\n\tbuf.WriteString(ufmt.Sprintf(\"**Starts:** %s UTC%s%d\\n\\n\", e.startTime.Format(\"02 Jan 2006, 03:04 PM\"), sign, hoursOffset))\n\tbuf.WriteString(ufmt.Sprintf(\"**Ends:** %s UTC%s%d\\n\\n\", e.endTime.Format(\"02 Jan 2006, 03:04 PM\"), sign, hoursOffset))\n\n\tif admin {\n\t\tbuf.WriteString(ufmt.Sprintf(\"[EDIT](/r/gnoland/events$help\u0026func=EditEvent\u0026id=%s)\\n\\n\", e.id))\n\t\tbuf.WriteString(ufmt.Sprintf(\"[DELETE](/r/gnoland/events$help\u0026func=DeleteEvent\u0026id=%s)\\n\\n\", e.id))\n\t}\n\n\tif e.link != \"\" {\n\t\tbuf.WriteString(ufmt.Sprintf(\"[See more](%s)\\n\\n\", e.link))\n\t}\n\n\tbuf.WriteString(\"\u003c/div\u003e\")\n\n\treturn buf.String()\n}\n\n// Render is the main rendering entry point\nfunc Render(path string) string {\n\tif path == \"admin\" {\n\t\treturn renderHome(true)\n\t}\n\n\treturn renderHome(false)\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":"faucet","path":"gno.land/r/gnoland/faucet","files":[{"name":"admin.gno","body":"package faucet\n\nimport (\n\t\"errors\"\n\t\"std\"\n)\n\nfunc AdminSetInPause(inPause bool) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\tgInPause = inPause\n\treturn \"\"\n}\n\nfunc AdminSetMessage(message string) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\tgMessage = message\n\treturn \"\"\n}\n\nfunc AdminSetTransferLimit(amount int64) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\tgLimit = std.NewCoin(\"ugnot\", amount)\n\treturn \"\"\n}\n\nfunc AdminSetAdminAddr(addr std.Address) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\tgAdminAddr = addr\n\treturn \"\"\n}\n\nfunc AdminAddController(addr std.Address) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\n\tsize := gControllers.Size()\n\n\tif size \u003e= gControllersMaxSize {\n\t\treturn \"can not add more controllers than allowed\"\n\t}\n\n\tif gControllers.Has(addr.String()) {\n\t\treturn addr.String() + \" exists, no need to add.\"\n\t}\n\n\tgControllers.Set(addr.String(), addr)\n\n\treturn \"\"\n}\n\nfunc AdminRemoveController(addr std.Address) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\n\tif !gControllers.Has(addr.String()) {\n\t\treturn addr.String() + \" is not on the controller list\"\n\t}\n\n\t_, ok := gControllers.Remove(addr.String())\n\n\t// it not should happen.\n\t// we will check anyway to prevent issues in the underline implementation.\n\n\tif !ok {\n\t\treturn addr.String() + \" is not on the controller list\"\n\t}\n\n\treturn \"\"\n}\n\nfunc assertIsAdmin() error {\n\tcaller := std.GetOrigCaller()\n\tif caller != gAdminAddr {\n\t\treturn errors.New(\"restricted for admin\")\n\t}\n\treturn nil\n}\n"},{"name":"faucet.gno","body":"package faucet\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar (\n\t// configurable by admin.\n\tgAdminAddr std.Address = std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")\n\tgControllers = avl.NewTree()\n\tgControllersMaxSize = 10 // limit it to 10\n\tgInPause = false\n\tgMessage = \"# Community Faucet.\\n\\n\"\n\n\t// internal vars, for stats.\n\tgTotalTransferred std.Coins\n\tgTotalTransfers = uint(0)\n\n\t// per request limit, 350 gnot\n\tgLimit std.Coin = std.NewCoin(\"ugnot\", 350000000)\n)\n\nfunc Transfer(to std.Address, send int64) string {\n\tif err := assertIsController(); err != nil {\n\t\treturn err.Error()\n\t}\n\n\tif gInPause {\n\t\treturn errors.New(\"faucet in pause\").Error()\n\t}\n\n\t// limit the per request\n\tif send \u003e gLimit.Amount {\n\t\treturn errors.New(\"Per request limit \" + gLimit.String() + \" exceed\").Error()\n\t}\n\tsendCoins := std.Coins{std.NewCoin(\"ugnot\", send)}\n\n\tgTotalTransferred = gTotalTransferred.Add(sendCoins)\n\tgTotalTransfers++\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tpkgaddr := std.CurrentRealm().Addr()\n\tbanker.SendCoins(pkgaddr, to, sendCoins)\n\treturn \"\"\n}\n\nfunc GetPerTransferLimit() int64 {\n\treturn gLimit.Amount\n}\n\nfunc Render(_ string) string {\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tbalance := banker.GetCoins(std.CurrentRealm().Addr())\n\n\toutput := gMessage\n\tif gInPause {\n\t\toutput += \"Status: inactive.\\n\"\n\t} else {\n\t\toutput += \"Status: active.\\n\"\n\t}\n\toutput += ufmt.Sprintf(\"Balance: %s.\\n\", balance.String())\n\toutput += ufmt.Sprintf(\"Total transfers: %s (in %d times).\\n\\n\", gTotalTransferred.String(), gTotalTransfers)\n\n\toutput += \"Package address: \" + std.CurrentRealm().Addr().String() + \"\\n\\n\"\n\toutput += ufmt.Sprintf(\"Admin: %s\\n\\n \", gAdminAddr.String())\n\toutput += ufmt.Sprintf(\"Controllers:\\n\\n \")\n\n\tfor i := 0; i \u003c gControllers.Size(); i++ {\n\t\t_, v := gControllers.GetByIndex(i)\n\t\toutput += ufmt.Sprintf(\"%s \", v.(std.Address))\n\t}\n\n\toutput += \"\\n\\n\"\n\toutput += ufmt.Sprintf(\"Per request limit: %s\\n\\n\", gLimit.String())\n\n\treturn output\n}\n\nfunc assertIsController() error {\n\tcaller := std.GetOrigCaller()\n\n\tok := gControllers.Has(caller.String())\n\tif !ok {\n\t\treturn errors.New(caller.String() + \" is not on the controller list\")\n\t}\n\treturn nil\n}\n"},{"name":"faucet_test.gno","body":"package faucet\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/gnoland/faucet\"\n)\n\nfunc TestPackage(t *testing.T) {\n\tvar (\n\t\tadminaddr = std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")\n\t\tfaucetaddr = std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\t\tcontrolleraddr1 = testutils.TestAddress(\"controller1\")\n\t\tcontrolleraddr2 = testutils.TestAddress(\"controller2\")\n\t\tcontrolleraddr3 = testutils.TestAddress(\"controller3\")\n\t\tcontrolleraddr4 = testutils.TestAddress(\"controller4\")\n\t\tcontrolleraddr5 = testutils.TestAddress(\"controller5\")\n\t\tcontrolleraddr6 = testutils.TestAddress(\"controller6\")\n\t\tcontrolleraddr7 = testutils.TestAddress(\"controller7\")\n\t\tcontrolleraddr8 = testutils.TestAddress(\"controller8\")\n\t\tcontrolleraddr9 = testutils.TestAddress(\"controller9\")\n\t\tcontrolleraddr10 = testutils.TestAddress(\"controller10\")\n\t\tcontrolleraddr11 = testutils.TestAddress(\"controller11\")\n\n\t\ttest1addr = testutils.TestAddress(\"test1\")\n\t)\n\t// deposit 1000gnot to faucet contract\n\tstd.TestIssueCoins(faucetaddr, std.Coins{{\"ugnot\", 1000000000}})\n\tassertBalance(t, faucetaddr, 1200000000)\n\n\t// by default, balance is empty, and as a user I cannot call Transfer, or Admin commands.\n\n\tassertBalance(t, test1addr, 0)\n\tstd.TestSetOrigCaller(test1addr)\n\tassertErr(t, faucet.Transfer(test1addr, 1000000))\n\n\tassertErr(t, faucet.AdminAddController(controlleraddr1))\n\tstd.TestSetOrigCaller(controlleraddr1)\n\tassertErr(t, faucet.Transfer(test1addr, 1000000))\n\n\t// as an admin, add the controller to contract and deposit more 2000gnot to contract\n\tstd.TestSetOrigCaller(adminaddr)\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr1))\n\tassertBalance(t, faucetaddr, 1200000000)\n\n\t// now, send some tokens as controller.\n\tstd.TestSetOrigCaller(controlleraddr1)\n\tassertNoErr(t, faucet.Transfer(test1addr, 1000000))\n\tassertBalance(t, test1addr, 1000000)\n\tassertNoErr(t, faucet.Transfer(test1addr, 1000000))\n\tassertBalance(t, test1addr, 2000000)\n\tassertBalance(t, faucetaddr, 1198000000)\n\n\t// remove controller\n\t// as an admin, remove controller\n\tstd.TestSetOrigCaller(adminaddr)\n\tassertNoErr(t, faucet.AdminRemoveController(controlleraddr1))\n\tstd.TestSetOrigCaller(controlleraddr1)\n\tassertErr(t, faucet.Transfer(test1addr, 1000000))\n\n\t// duplicate controller\n\tstd.TestSetOrigCaller(adminaddr)\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr1))\n\tassertErr(t, faucet.AdminAddController(controlleraddr1))\n\t// add more than more than allowed controllers\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr2))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr3))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr4))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr5))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr6))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr7))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr8))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr9))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr10))\n\tassertErr(t, faucet.AdminAddController(controlleraddr11))\n\n\t// send more than per transfer limit\n\tstd.TestSetOrigCaller(adminaddr)\n\tfaucet.AdminSetTransferLimit(300000000)\n\tstd.TestSetOrigCaller(controlleraddr1)\n\tassertErr(t, faucet.Transfer(test1addr, 301000000))\n\n\t// block transefer from the address not on the controllers list.\n\tstd.TestSetOrigCaller(controlleraddr11)\n\tassertErr(t, faucet.Transfer(test1addr, 1000000))\n}\n\nfunc assertErr(t *testing.T, err string) {\n\tt.Helper()\n\n\tif err == \"\" {\n\t\tt.Logf(\"info: got err: %v\", err)\n\t\tt.Errorf(\"expected an error, got nil.\")\n\t}\n}\n\nfunc assertNoErr(t *testing.T, err string) {\n\tt.Helper()\n\tif err != \"\" {\n\t\tt.Errorf(\"got err: %v.\", err)\n\t}\n}\n\nfunc assertBalance(t *testing.T, addr std.Address, expectedBal int64) {\n\tt.Helper()\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tcoins := banker.GetCoins(addr)\n\tgot := coins.AmountOf(\"ugnot\")\n\n\tif expectedBal != got {\n\t\tt.Errorf(\"invalid balance: expected %d, got %d.\", expectedBal, got)\n\t}\n}\n"},{"name":"z0_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/gnoland/faucet\"\n)\n\n// mints ugnot to current realm\nfunc init() {\n\tfacuetaddr := std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\tstd.TestIssueCoins(facuetaddr, std.Coins{{\"ugnot\", 200000000}})\n}\n\n// assert render with empty path and no controllers\nfunc main() {\n\tprintln(faucet.Render(\"\"))\n}\n\n// Output:\n// # Community Faucet.\n//\n// Status: active.\n// Balance: 200000000ugnot.\n// Total transfers: (in 0 times).\n//\n// Package address: g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7\n//\n// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n//\n// Controllers:\n//\n//\n//\n// Per request limit: 350000000ugnot\n"},{"name":"z1_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/gnoland/faucet\"\n)\n\n// mints ugnot to current realm\nfunc init() {\n\tfacuetaddr := std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\tstd.TestIssueCoins(facuetaddr, std.Coins{{\"ugnot\", 200000000}})\n}\n\n// assert render with a path and no controllers\nfunc main() {\n\tprintln(faucet.Render(\"path\"))\n}\n\n// Output:\n// # Community Faucet.\n//\n// Status: active.\n// Balance: 200000000ugnot.\n// Total transfers: (in 0 times).\n//\n// Package address: g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7\n//\n// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n//\n// Controllers:\n//\n//\n//\n// Per request limit: 350000000ugnot\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/gnoland/faucet\"\n)\n\n// mints ugnot to current realm\nfunc init() {\n\tfacuetaddr := std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\tstd.TestIssueCoins(facuetaddr, std.Coins{{\"ugnot\", 200000000}})\n}\n\n// assert render with empty path and 2 controllers\nfunc main() {\n\tvar (\n\t\tadminaddr = std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")\n\t\tcontrolleraddr1 = testutils.TestAddress(\"controller1\")\n\t\tcontrolleraddr2 = testutils.TestAddress(\"controller2\")\n\t)\n\tstd.TestSetOrigCaller(adminaddr)\n\terr := faucet.AdminAddController(controlleraddr1)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\terr = faucet.AdminAddController(controlleraddr2)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\tprintln(faucet.Render(\"\"))\n}\n\n// Output:\n// # Community Faucet.\n//\n// Status: active.\n// Balance: 200000000ugnot.\n// Total transfers: (in 0 times).\n//\n// Package address: g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7\n//\n// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n//\n// Controllers:\n//\n// g1vdhkuarjdakxcetjx9047h6lta047h6lsdacav g1vdhkuarjdakxcetjxf047h6lta047h6lnrev3v\n//\n// Per request limit: 350000000ugnot\n"},{"name":"z3_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/gnoland/faucet\"\n)\n\n// mints coints to current realm\nfunc init() {\n\tfacuetaddr := std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\tstd.TestIssueCoins(facuetaddr, std.Coins{{\"ugnot\", 200000000}})\n}\n\n// assert render with 2 controllers and 2 transfers\nfunc main() {\n\tvar (\n\t\tadminaddr = std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")\n\t\tcontrolleraddr1 = testutils.TestAddress(\"controller1\")\n\t\tcontrolleraddr2 = testutils.TestAddress(\"controller2\")\n\t\ttestaddr1 = testutils.TestAddress(\"test1\")\n\t\ttestaddr2 = testutils.TestAddress(\"test2\")\n\t)\n\tstd.TestSetOrigCaller(adminaddr)\n\terr := faucet.AdminAddController(controlleraddr1)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\terr = faucet.AdminAddController(controlleraddr2)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\tstd.TestSetOrigCaller(controlleraddr1)\n\terr = faucet.Transfer(testaddr1, 1000000)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\tstd.TestSetOrigCaller(controlleraddr2)\n\terr = faucet.Transfer(testaddr1, 2000000)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\tprintln(faucet.Render(\"\"))\n}\n\n// Output:\n// # Community Faucet.\n//\n// Status: active.\n// Balance: 197000000ugnot.\n// Total transfers: 3000000ugnot (in 2 times).\n//\n// Package address: g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7\n//\n// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n//\n// Controllers:\n//\n// g1vdhkuarjdakxcetjx9047h6lta047h6lsdacav g1vdhkuarjdakxcetjxf047h6lta047h6lnrev3v\n//\n// Per request limit: 350000000ugnot\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":"ghverify","path":"gno.land/r/gnoland/ghverify","files":[{"name":"README.md","body":"# ghverify\n\nThis realm is intended to enable off chain gno address to github handle verification.\nThe steps are as follows:\n- A user calls `RequestVerification` and provides a github handle. This creates a new static oracle feed.\n- An off-chain agent controlled by the owner of this realm requests current feeds using the `GnorkleEntrypoint` function and provides a message of `\"request\"`\n- The agent receives the task information that includes the github handle and the gno address. It performs the verification step by checking whether this github user has the address in a github repository it controls.\n- The agent publishes the result of the verification by calling `GnorkleEntrypoint` with a message structured like: `\"ingest,\u003ctask id\u003e,\u003cverification status\u003e\"`. The verification status is `OK` if verification succeeded and any other value if it failed.\n- The oracle feed's ingester processes the verification and the handle to address mapping is written to the avl trees that exist as ghverify realm variables."},{"name":"contract.gno","body":"package ghverify\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/gnorkle/feeds/static\"\n\t\"gno.land/p/demo/gnorkle/gnorkle\"\n\t\"gno.land/p/demo/gnorkle/message\"\n)\n\nconst (\n\t// The agent should send this value if it has verified the github handle.\n\tverifiedResult = \"OK\"\n)\n\nvar (\n\townerAddress = std.GetOrigCaller()\n\toracle *gnorkle.Instance\n\tpostHandler postGnorkleMessageHandler\n\n\thandleToAddressMap = avl.NewTree()\n\taddressToHandleMap = avl.NewTree()\n)\n\nfunc init() {\n\toracle = gnorkle.NewInstance()\n\toracle.AddToWhitelist(\"\", []string{string(ownerAddress)})\n}\n\ntype postGnorkleMessageHandler struct{}\n\n// Handle does post processing after a message is ingested by the oracle feed. It extracts the value to realm\n// storage and removes the feed from the oracle.\nfunc (h postGnorkleMessageHandler) Handle(i *gnorkle.Instance, funcType message.FuncType, feed gnorkle.Feed) error {\n\tif funcType != message.FuncTypeIngest {\n\t\treturn nil\n\t}\n\n\tresult, _, consumable := feed.Value()\n\tif !consumable {\n\t\treturn nil\n\t}\n\n\t// The value is consumable, meaning the ingestion occurred, so we can remove the feed from the oracle\n\t// after saving it to realm storage.\n\tdefer oracle.RemoveFeed(feed.ID())\n\n\t// Couldn't verify; nothing to do.\n\tif result.String != verifiedResult {\n\t\treturn nil\n\t}\n\n\tfeedTasks := feed.Tasks()\n\tif len(feedTasks) != 1 {\n\t\treturn errors.New(\"expected feed to have exactly one task\")\n\t}\n\n\ttask, ok := feedTasks[0].(*verificationTask)\n\tif !ok {\n\t\treturn errors.New(\"expected ghverify task\")\n\t}\n\n\thandleToAddressMap.Set(task.githubHandle, task.gnoAddress)\n\taddressToHandleMap.Set(task.gnoAddress, task.githubHandle)\n\treturn nil\n}\n\n// RequestVerification creates a new static feed with a single task that will\n// instruct an agent to verify the github handle / gno address pair.\nfunc RequestVerification(githubHandle string) {\n\tgnoAddress := string(std.GetOrigCaller())\n\tif err := oracle.AddFeeds(\n\t\tstatic.NewSingleValueFeed(\n\t\t\tgnoAddress,\n\t\t\t\"string\",\n\t\t\t\u0026verificationTask{\n\t\t\t\tgnoAddress: gnoAddress,\n\t\t\t\tgithubHandle: githubHandle,\n\t\t\t},\n\t\t),\n\t); err != nil {\n\t\tpanic(err)\n\t}\n\tstd.Emit(\n\t\t\"verification_requested\",\n\t\t\"from\", gnoAddress,\n\t\t\"handle\", githubHandle,\n\t)\n}\n\n// GnorkleEntrypoint is the entrypoint to the gnorkle oracle handler.\nfunc GnorkleEntrypoint(message string) string {\n\tresult, err := oracle.HandleMessage(message, postHandler)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn result\n}\n\n// SetOwner transfers ownership of the contract to the given address.\nfunc SetOwner(owner std.Address) {\n\tif ownerAddress != std.GetOrigCaller() {\n\t\tpanic(\"only the owner can set a new owner\")\n\t}\n\n\townerAddress = owner\n\n\t// In the context of this contract, the owner is the only one that can\n\t// add new feeds to the oracle.\n\toracle.ClearWhitelist(\"\")\n\toracle.AddToWhitelist(\"\", []string{string(ownerAddress)})\n}\n\n// GetHandleByAddress returns the github handle associated with the given gno address.\nfunc GetHandleByAddress(address string) string {\n\tif value, ok := addressToHandleMap.Get(address); ok {\n\t\treturn value.(string)\n\t}\n\n\treturn \"\"\n}\n\n// GetAddressByHandle returns the gno address associated with the given github handle.\nfunc GetAddressByHandle(handle string) string {\n\tif value, ok := handleToAddressMap.Get(handle); ok {\n\t\treturn value.(string)\n\t}\n\n\treturn \"\"\n}\n\n// Render returns a json object string will all verified handle -\u003e address mappings.\nfunc Render(_ string) string {\n\tresult := \"{\"\n\tvar appendComma bool\n\thandleToAddressMap.Iterate(\"\", \"\", func(handle string, address interface{}) bool {\n\t\tif appendComma {\n\t\t\tresult += \",\"\n\t\t}\n\n\t\tresult += `\"` + handle + `\": \"` + address.(string) + `\"`\n\t\tappendComma = true\n\n\t\treturn false\n\t})\n\n\treturn result + \"}\"\n}\n"},{"name":"contract_test.gno","body":"package ghverify\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n)\n\nfunc TestVerificationLifecycle(t *testing.T) {\n\tdefaultAddress := std.GetOrigCaller()\n\tuser1Address := std.Address(testutils.TestAddress(\"user 1\"))\n\tuser2Address := std.Address(testutils.TestAddress(\"user 2\"))\n\n\t// Verify request returns no feeds.\n\tresult := GnorkleEntrypoint(\"request\")\n\tif result != \"[]\" {\n\t\tt.Fatalf(\"expected empty request result, got %s\", result)\n\t}\n\n\t// Make a verification request with the created user.\n\tstd.TestSetOrigCaller(user1Address)\n\tRequestVerification(\"deelawn\")\n\n\t// A subsequent request from the same address should panic because there is\n\t// already a feed with an ID of this user's address.\n\tvar errMsg string\n\tfunc() {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\terrMsg = r.(error).Error()\n\t\t\t}\n\t\t}()\n\t\tRequestVerification(\"deelawn\")\n\t}()\n\tif errMsg != \"feed already exists\" {\n\t\tt.Fatalf(\"expected feed already exists, got %s\", errMsg)\n\t}\n\n\t// Verify the request returns no feeds for this non-whitelisted user.\n\tresult = GnorkleEntrypoint(\"request\")\n\tif result != \"[]\" {\n\t\tt.Fatalf(\"expected empty request result, got %s\", result)\n\t}\n\n\t// Make a verification request with the created user.\n\tstd.TestSetOrigCaller(user2Address)\n\tRequestVerification(\"omarsy\")\n\n\t// Set the caller back to the whitelisted user and verify that the feed data\n\t// returned matches what should have been created by the `RequestVerification`\n\t// invocation.\n\tstd.TestSetOrigCaller(defaultAddress)\n\tresult = GnorkleEntrypoint(\"request\")\n\texpResult := `[{\"id\":\"` + string(user1Address) + `\",\"type\":\"0\",\"value_type\":\"string\",\"tasks\":[{\"gno_address\":\"` +\n\t\tstring(user1Address) + `\",\"github_handle\":\"deelawn\"}]},` +\n\t\t`{\"id\":\"` + string(user2Address) + `\",\"type\":\"0\",\"value_type\":\"string\",\"tasks\":[{\"gno_address\":\"` +\n\t\tstring(user2Address) + `\",\"github_handle\":\"omarsy\"}]}]`\n\tif result != expResult {\n\t\tt.Fatalf(\"expected request result %s, got %s\", expResult, result)\n\t}\n\n\t// Try to trigger feed ingestion from the non-authorized user.\n\tstd.TestSetOrigCaller(user1Address)\n\tfunc() {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\terrMsg = r.(error).Error()\n\t\t\t}\n\t\t}()\n\t\tGnorkleEntrypoint(\"ingest,\" + string(user1Address) + \",OK\")\n\t}()\n\tif errMsg != \"caller not whitelisted\" {\n\t\tt.Fatalf(\"expected caller not whitelisted, got %s\", errMsg)\n\t}\n\n\t// Set the caller back to the whitelisted user and transfer contract ownership.\n\tstd.TestSetOrigCaller(defaultAddress)\n\tSetOwner(defaultAddress)\n\n\t// Now trigger the feed ingestion from the user and new owner and only whitelisted address.\n\tGnorkleEntrypoint(\"ingest,\" + string(user1Address) + \",OK\")\n\tGnorkleEntrypoint(\"ingest,\" + string(user2Address) + \",OK\")\n\n\t// Verify the ingestion autocommitted the value and triggered the post handler.\n\tdata := Render(\"\")\n\texpResult = `{\"deelawn\": \"` + string(user1Address) + `\",\"omarsy\": \"` + string(user2Address) + `\"}`\n\tif data != expResult {\n\t\tt.Fatalf(\"expected render data %s, got %s\", expResult, data)\n\t}\n\n\t// Finally make sure the feed was cleaned up after the data was committed.\n\tresult = GnorkleEntrypoint(\"request\")\n\tif result != \"[]\" {\n\t\tt.Fatalf(\"expected empty request result, got %s\", result)\n\t}\n\n\t// Check that the accessor functions are working as expected.\n\tif handle := GetHandleByAddress(string(user1Address)); handle != \"deelawn\" {\n\t\tt.Fatalf(\"expected deelawn, got %s\", handle)\n\t}\n\tif address := GetAddressByHandle(\"deelawn\"); address != string(user1Address) {\n\t\tt.Fatalf(\"expected %s, got %s\", string(user1Address), address)\n\t}\n}\n"},{"name":"task.gno","body":"package ghverify\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n)\n\ntype verificationTask struct {\n\tgnoAddress string\n\tgithubHandle string\n}\n\n// MarshalJSON marshals the task contents to JSON.\nfunc (t *verificationTask) MarshalJSON() ([]byte, error) {\n\tbuf := new(bytes.Buffer)\n\tw := bufio.NewWriter(buf)\n\n\tw.Write(\n\t\t[]byte(`{\"gno_address\":\"` + t.gnoAddress + `\",\"github_handle\":\"` + t.githubHandle + `\"}`),\n\t)\n\n\tw.Flush()\n\treturn buf.Bytes(), nil\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":"faucet","path":"gno.land/r/gnoland/faucet","files":[{"name":"admin.gno","body":"package faucet\n\nimport (\n\t\"errors\"\n\t\"std\"\n)\n\nfunc AdminSetInPause(inPause bool) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\tgInPause = inPause\n\treturn \"\"\n}\n\nfunc AdminSetMessage(message string) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\tgMessage = message\n\treturn \"\"\n}\n\nfunc AdminSetTransferLimit(amount int64) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\tgLimit = std.NewCoin(\"ugnot\", amount)\n\treturn \"\"\n}\n\nfunc AdminSetAdminAddr(addr std.Address) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\tgAdminAddr = addr\n\treturn \"\"\n}\n\nfunc AdminAddController(addr std.Address) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\n\tsize := gControllers.Size()\n\n\tif size \u003e= gControllersMaxSize {\n\t\treturn \"can not add more controllers than allowed\"\n\t}\n\n\tif gControllers.Has(addr.String()) {\n\t\treturn addr.String() + \" exists, no need to add.\"\n\t}\n\n\tgControllers.Set(addr.String(), addr)\n\n\treturn \"\"\n}\n\nfunc AdminRemoveController(addr std.Address) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\n\tif !gControllers.Has(addr.String()) {\n\t\treturn addr.String() + \" is not on the controller list\"\n\t}\n\n\t_, ok := gControllers.Remove(addr.String())\n\n\t// it not should happen.\n\t// we will check anyway to prevent issues in the underline implementation.\n\n\tif !ok {\n\t\treturn addr.String() + \" is not on the controller list\"\n\t}\n\n\treturn \"\"\n}\n\nfunc assertIsAdmin() error {\n\tcaller := std.OrigCaller()\n\tif caller != gAdminAddr {\n\t\treturn errors.New(\"restricted for admin\")\n\t}\n\treturn nil\n}\n"},{"name":"faucet.gno","body":"package faucet\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar (\n\t// configurable by admin.\n\tgAdminAddr std.Address = std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")\n\tgControllers = avl.NewTree()\n\tgControllersMaxSize = 10 // limit it to 10\n\tgInPause = false\n\tgMessage = \"# Community Faucet.\\n\\n\"\n\n\t// internal vars, for stats.\n\tgTotalTransferred std.Coins\n\tgTotalTransfers = uint(0)\n\n\t// per request limit, 350 gnot\n\tgLimit std.Coin = std.NewCoin(\"ugnot\", 350000000)\n)\n\nfunc Transfer(to std.Address, send int64) string {\n\tif err := assertIsController(); err != nil {\n\t\treturn err.Error()\n\t}\n\n\tif gInPause {\n\t\treturn errors.New(\"faucet in pause\").Error()\n\t}\n\n\t// limit the per request\n\tif send \u003e gLimit.Amount {\n\t\treturn errors.New(\"Per request limit \" + gLimit.String() + \" exceed\").Error()\n\t}\n\tsendCoins := std.Coins{std.NewCoin(\"ugnot\", send)}\n\n\tgTotalTransferred = gTotalTransferred.Add(sendCoins)\n\tgTotalTransfers++\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tpkgaddr := std.CurrentRealm().Addr()\n\tbanker.SendCoins(pkgaddr, to, sendCoins)\n\treturn \"\"\n}\n\nfunc GetPerTransferLimit() int64 {\n\treturn gLimit.Amount\n}\n\nfunc Render(_ string) string {\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tbalance := banker.GetCoins(std.CurrentRealm().Addr())\n\n\toutput := gMessage\n\tif gInPause {\n\t\toutput += \"Status: inactive.\\n\"\n\t} else {\n\t\toutput += \"Status: active.\\n\"\n\t}\n\toutput += ufmt.Sprintf(\"Balance: %s.\\n\", balance.String())\n\toutput += ufmt.Sprintf(\"Total transfers: %s (in %d times).\\n\\n\", gTotalTransferred.String(), gTotalTransfers)\n\n\toutput += \"Package address: \" + std.CurrentRealm().Addr().String() + \"\\n\\n\"\n\toutput += ufmt.Sprintf(\"Admin: %s\\n\\n \", gAdminAddr.String())\n\toutput += ufmt.Sprintf(\"Controllers:\\n\\n \")\n\n\tfor i := 0; i \u003c gControllers.Size(); i++ {\n\t\t_, v := gControllers.GetByIndex(i)\n\t\toutput += ufmt.Sprintf(\"%s \", v.(std.Address))\n\t}\n\n\toutput += \"\\n\\n\"\n\toutput += ufmt.Sprintf(\"Per request limit: %s\\n\\n\", gLimit.String())\n\n\treturn output\n}\n\nfunc assertIsController() error {\n\tcaller := std.OrigCaller()\n\n\tok := gControllers.Has(caller.String())\n\tif !ok {\n\t\treturn errors.New(caller.String() + \" is not on the controller list\")\n\t}\n\treturn nil\n}\n"},{"name":"faucet_test.gno","body":"package faucet\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/gnoland/faucet\"\n)\n\nfunc TestPackage(t *testing.T) {\n\tvar (\n\t\tadminaddr = std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")\n\t\tfaucetaddr = std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\t\tcontrolleraddr1 = testutils.TestAddress(\"controller1\")\n\t\tcontrolleraddr2 = testutils.TestAddress(\"controller2\")\n\t\tcontrolleraddr3 = testutils.TestAddress(\"controller3\")\n\t\tcontrolleraddr4 = testutils.TestAddress(\"controller4\")\n\t\tcontrolleraddr5 = testutils.TestAddress(\"controller5\")\n\t\tcontrolleraddr6 = testutils.TestAddress(\"controller6\")\n\t\tcontrolleraddr7 = testutils.TestAddress(\"controller7\")\n\t\tcontrolleraddr8 = testutils.TestAddress(\"controller8\")\n\t\tcontrolleraddr9 = testutils.TestAddress(\"controller9\")\n\t\tcontrolleraddr10 = testutils.TestAddress(\"controller10\")\n\t\tcontrolleraddr11 = testutils.TestAddress(\"controller11\")\n\n\t\ttest1addr = testutils.TestAddress(\"test1\")\n\t)\n\t// deposit 1000gnot to faucet contract\n\tstd.TestIssueCoins(faucetaddr, std.Coins{{\"ugnot\", 1000000000}})\n\tassertBalance(t, faucetaddr, 1200000000)\n\n\t// by default, balance is empty, and as a user I cannot call Transfer, or Admin commands.\n\n\tassertBalance(t, test1addr, 0)\n\tstd.TestSetOrigCaller(test1addr)\n\tassertErr(t, faucet.Transfer(test1addr, 1000000))\n\n\tassertErr(t, faucet.AdminAddController(controlleraddr1))\n\tstd.TestSetOrigCaller(controlleraddr1)\n\tassertErr(t, faucet.Transfer(test1addr, 1000000))\n\n\t// as an admin, add the controller to contract and deposit more 2000gnot to contract\n\tstd.TestSetOrigCaller(adminaddr)\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr1))\n\tassertBalance(t, faucetaddr, 1200000000)\n\n\t// now, send some tokens as controller.\n\tstd.TestSetOrigCaller(controlleraddr1)\n\tassertNoErr(t, faucet.Transfer(test1addr, 1000000))\n\tassertBalance(t, test1addr, 1000000)\n\tassertNoErr(t, faucet.Transfer(test1addr, 1000000))\n\tassertBalance(t, test1addr, 2000000)\n\tassertBalance(t, faucetaddr, 1198000000)\n\n\t// remove controller\n\t// as an admin, remove controller\n\tstd.TestSetOrigCaller(adminaddr)\n\tassertNoErr(t, faucet.AdminRemoveController(controlleraddr1))\n\tstd.TestSetOrigCaller(controlleraddr1)\n\tassertErr(t, faucet.Transfer(test1addr, 1000000))\n\n\t// duplicate controller\n\tstd.TestSetOrigCaller(adminaddr)\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr1))\n\tassertErr(t, faucet.AdminAddController(controlleraddr1))\n\t// add more than more than allowed controllers\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr2))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr3))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr4))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr5))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr6))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr7))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr8))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr9))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr10))\n\tassertErr(t, faucet.AdminAddController(controlleraddr11))\n\n\t// send more than per transfer limit\n\tstd.TestSetOrigCaller(adminaddr)\n\tfaucet.AdminSetTransferLimit(300000000)\n\tstd.TestSetOrigCaller(controlleraddr1)\n\tassertErr(t, faucet.Transfer(test1addr, 301000000))\n\n\t// block transefer from the address not on the controllers list.\n\tstd.TestSetOrigCaller(controlleraddr11)\n\tassertErr(t, faucet.Transfer(test1addr, 1000000))\n}\n\nfunc assertErr(t *testing.T, err string) {\n\tt.Helper()\n\n\tif err == \"\" {\n\t\tt.Logf(\"info: got err: %v\", err)\n\t\tt.Errorf(\"expected an error, got nil.\")\n\t}\n}\n\nfunc assertNoErr(t *testing.T, err string) {\n\tt.Helper()\n\tif err != \"\" {\n\t\tt.Errorf(\"got err: %v.\", err)\n\t}\n}\n\nfunc assertBalance(t *testing.T, addr std.Address, expectedBal int64) {\n\tt.Helper()\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tcoins := banker.GetCoins(addr)\n\tgot := coins.AmountOf(\"ugnot\")\n\n\tif expectedBal != got {\n\t\tt.Errorf(\"invalid balance: expected %d, got %d.\", expectedBal, got)\n\t}\n}\n"},{"name":"z0_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/gnoland/faucet\"\n)\n\n// mints ugnot to current realm\nfunc init() {\n\tfacuetaddr := std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\tstd.TestIssueCoins(facuetaddr, std.Coins{{\"ugnot\", 200000000}})\n}\n\n// assert render with empty path and no controllers\nfunc main() {\n\tprintln(faucet.Render(\"\"))\n}\n\n// Output:\n// # Community Faucet.\n//\n// Status: active.\n// Balance: 200000000ugnot.\n// Total transfers: (in 0 times).\n//\n// Package address: g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7\n//\n// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n//\n// Controllers:\n//\n//\n//\n// Per request limit: 350000000ugnot\n"},{"name":"z1_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/gnoland/faucet\"\n)\n\n// mints ugnot to current realm\nfunc init() {\n\tfacuetaddr := std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\tstd.TestIssueCoins(facuetaddr, std.Coins{{\"ugnot\", 200000000}})\n}\n\n// assert render with a path and no controllers\nfunc main() {\n\tprintln(faucet.Render(\"path\"))\n}\n\n// Output:\n// # Community Faucet.\n//\n// Status: active.\n// Balance: 200000000ugnot.\n// Total transfers: (in 0 times).\n//\n// Package address: g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7\n//\n// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n//\n// Controllers:\n//\n//\n//\n// Per request limit: 350000000ugnot\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/gnoland/faucet\"\n)\n\n// mints ugnot to current realm\nfunc init() {\n\tfacuetaddr := std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\tstd.TestIssueCoins(facuetaddr, std.Coins{{\"ugnot\", 200000000}})\n}\n\n// assert render with empty path and 2 controllers\nfunc main() {\n\tvar (\n\t\tadminaddr = std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")\n\t\tcontrolleraddr1 = testutils.TestAddress(\"controller1\")\n\t\tcontrolleraddr2 = testutils.TestAddress(\"controller2\")\n\t)\n\tstd.TestSetOrigCaller(adminaddr)\n\terr := faucet.AdminAddController(controlleraddr1)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\terr = faucet.AdminAddController(controlleraddr2)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\tprintln(faucet.Render(\"\"))\n}\n\n// Output:\n// # Community Faucet.\n//\n// Status: active.\n// Balance: 200000000ugnot.\n// Total transfers: (in 0 times).\n//\n// Package address: g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7\n//\n// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n//\n// Controllers:\n//\n// g1vdhkuarjdakxcetjx9047h6lta047h6lsdacav g1vdhkuarjdakxcetjxf047h6lta047h6lnrev3v\n//\n// Per request limit: 350000000ugnot\n"},{"name":"z3_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/gnoland/faucet\"\n)\n\n// mints coints to current realm\nfunc init() {\n\tfacuetaddr := std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\tstd.TestIssueCoins(facuetaddr, std.Coins{{\"ugnot\", 200000000}})\n}\n\n// assert render with 2 controllers and 2 transfers\nfunc main() {\n\tvar (\n\t\tadminaddr = std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")\n\t\tcontrolleraddr1 = testutils.TestAddress(\"controller1\")\n\t\tcontrolleraddr2 = testutils.TestAddress(\"controller2\")\n\t\ttestaddr1 = testutils.TestAddress(\"test1\")\n\t\ttestaddr2 = testutils.TestAddress(\"test2\")\n\t)\n\tstd.TestSetOrigCaller(adminaddr)\n\terr := faucet.AdminAddController(controlleraddr1)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\terr = faucet.AdminAddController(controlleraddr2)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\tstd.TestSetOrigCaller(controlleraddr1)\n\terr = faucet.Transfer(testaddr1, 1000000)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\tstd.TestSetOrigCaller(controlleraddr2)\n\terr = faucet.Transfer(testaddr1, 2000000)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\tprintln(faucet.Render(\"\"))\n}\n\n// Output:\n// # Community Faucet.\n//\n// Status: active.\n// Balance: 197000000ugnot.\n// Total transfers: 3000000ugnot (in 2 times).\n//\n// Package address: g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7\n//\n// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n//\n// Controllers:\n//\n// g1vdhkuarjdakxcetjx9047h6lta047h6lsdacav g1vdhkuarjdakxcetjxf047h6lta047h6lnrev3v\n//\n// Per request limit: 350000000ugnot\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":"ghverify","path":"gno.land/r/gnoland/ghverify","files":[{"name":"README.md","body":"# ghverify\n\nThis realm is intended to enable off chain gno address to github handle verification.\nThe steps are as follows:\n- A user calls `RequestVerification` and provides a github handle. This creates a new static oracle feed.\n- An off-chain agent controlled by the owner of this realm requests current feeds using the `GnorkleEntrypoint` function and provides a message of `\"request\"`\n- The agent receives the task information that includes the github handle and the gno address. It performs the verification step by checking whether this github user has the address in a github repository it controls.\n- The agent publishes the result of the verification by calling `GnorkleEntrypoint` with a message structured like: `\"ingest,\u003ctask id\u003e,\u003cverification status\u003e\"`. The verification status is `OK` if verification succeeded and any other value if it failed.\n- The oracle feed's ingester processes the verification and the handle to address mapping is written to the avl trees that exist as ghverify realm variables."},{"name":"contract.gno","body":"package ghverify\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/gnorkle/feeds/static\"\n\t\"gno.land/p/demo/gnorkle/gnorkle\"\n\t\"gno.land/p/demo/gnorkle/message\"\n)\n\nconst (\n\t// The agent should send this value if it has verified the github handle.\n\tverifiedResult = \"OK\"\n)\n\nvar (\n\townerAddress = std.OrigCaller()\n\toracle *gnorkle.Instance\n\tpostHandler postGnorkleMessageHandler\n\n\thandleToAddressMap = avl.NewTree()\n\taddressToHandleMap = avl.NewTree()\n)\n\nfunc init() {\n\toracle = gnorkle.NewInstance()\n\toracle.AddToWhitelist(\"\", []string{string(ownerAddress)})\n}\n\ntype postGnorkleMessageHandler struct{}\n\n// Handle does post processing after a message is ingested by the oracle feed. It extracts the value to realm\n// storage and removes the feed from the oracle.\nfunc (h postGnorkleMessageHandler) Handle(i *gnorkle.Instance, funcType message.FuncType, feed gnorkle.Feed) error {\n\tif funcType != message.FuncTypeIngest {\n\t\treturn nil\n\t}\n\n\tresult, _, consumable := feed.Value()\n\tif !consumable {\n\t\treturn nil\n\t}\n\n\t// The value is consumable, meaning the ingestion occurred, so we can remove the feed from the oracle\n\t// after saving it to realm storage.\n\tdefer oracle.RemoveFeed(feed.ID())\n\n\t// Couldn't verify; nothing to do.\n\tif result.String != verifiedResult {\n\t\treturn nil\n\t}\n\n\tfeedTasks := feed.Tasks()\n\tif len(feedTasks) != 1 {\n\t\treturn errors.New(\"expected feed to have exactly one task\")\n\t}\n\n\ttask, ok := feedTasks[0].(*verificationTask)\n\tif !ok {\n\t\treturn errors.New(\"expected ghverify task\")\n\t}\n\n\thandleToAddressMap.Set(task.githubHandle, task.gnoAddress)\n\taddressToHandleMap.Set(task.gnoAddress, task.githubHandle)\n\treturn nil\n}\n\n// RequestVerification creates a new static feed with a single task that will\n// instruct an agent to verify the github handle / gno address pair.\nfunc RequestVerification(githubHandle string) {\n\tgnoAddress := string(std.OrigCaller())\n\tif err := oracle.AddFeeds(\n\t\tstatic.NewSingleValueFeed(\n\t\t\tgnoAddress,\n\t\t\t\"string\",\n\t\t\t\u0026verificationTask{\n\t\t\t\tgnoAddress: gnoAddress,\n\t\t\t\tgithubHandle: githubHandle,\n\t\t\t},\n\t\t),\n\t); err != nil {\n\t\tpanic(err)\n\t}\n\tstd.Emit(\n\t\t\"verification_requested\",\n\t\t\"from\", gnoAddress,\n\t\t\"handle\", githubHandle,\n\t)\n}\n\n// GnorkleEntrypoint is the entrypoint to the gnorkle oracle handler.\nfunc GnorkleEntrypoint(message string) string {\n\tresult, err := oracle.HandleMessage(message, postHandler)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn result\n}\n\n// SetOwner transfers ownership of the contract to the given address.\nfunc SetOwner(owner std.Address) {\n\tif ownerAddress != std.OrigCaller() {\n\t\tpanic(\"only the owner can set a new owner\")\n\t}\n\n\townerAddress = owner\n\n\t// In the context of this contract, the owner is the only one that can\n\t// add new feeds to the oracle.\n\toracle.ClearWhitelist(\"\")\n\toracle.AddToWhitelist(\"\", []string{string(ownerAddress)})\n}\n\n// GetHandleByAddress returns the github handle associated with the given gno address.\nfunc GetHandleByAddress(address string) string {\n\tif value, ok := addressToHandleMap.Get(address); ok {\n\t\treturn value.(string)\n\t}\n\n\treturn \"\"\n}\n\n// GetAddressByHandle returns the gno address associated with the given github handle.\nfunc GetAddressByHandle(handle string) string {\n\tif value, ok := handleToAddressMap.Get(handle); ok {\n\t\treturn value.(string)\n\t}\n\n\treturn \"\"\n}\n\n// Render returns a json object string will all verified handle -\u003e address mappings.\nfunc Render(_ string) string {\n\tresult := \"{\"\n\tvar appendComma bool\n\thandleToAddressMap.Iterate(\"\", \"\", func(handle string, address interface{}) bool {\n\t\tif appendComma {\n\t\t\tresult += \",\"\n\t\t}\n\n\t\tresult += `\"` + handle + `\": \"` + address.(string) + `\"`\n\t\tappendComma = true\n\n\t\treturn false\n\t})\n\n\treturn result + \"}\"\n}\n"},{"name":"contract_test.gno","body":"package ghverify\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n)\n\nfunc TestVerificationLifecycle(t *testing.T) {\n\tdefaultAddress := std.OrigCaller()\n\tuser1Address := std.Address(testutils.TestAddress(\"user 1\"))\n\tuser2Address := std.Address(testutils.TestAddress(\"user 2\"))\n\n\t// Verify request returns no feeds.\n\tresult := GnorkleEntrypoint(\"request\")\n\tif result != \"[]\" {\n\t\tt.Fatalf(\"expected empty request result, got %s\", result)\n\t}\n\n\t// Make a verification request with the created user.\n\tstd.TestSetOrigCaller(user1Address)\n\tRequestVerification(\"deelawn\")\n\n\t// A subsequent request from the same address should panic because there is\n\t// already a feed with an ID of this user's address.\n\tvar errMsg string\n\tfunc() {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\terrMsg = r.(error).Error()\n\t\t\t}\n\t\t}()\n\t\tRequestVerification(\"deelawn\")\n\t}()\n\tif errMsg != \"feed already exists\" {\n\t\tt.Fatalf(\"expected feed already exists, got %s\", errMsg)\n\t}\n\n\t// Verify the request returns no feeds for this non-whitelisted user.\n\tresult = GnorkleEntrypoint(\"request\")\n\tif result != \"[]\" {\n\t\tt.Fatalf(\"expected empty request result, got %s\", result)\n\t}\n\n\t// Make a verification request with the created user.\n\tstd.TestSetOrigCaller(user2Address)\n\tRequestVerification(\"omarsy\")\n\n\t// Set the caller back to the whitelisted user and verify that the feed data\n\t// returned matches what should have been created by the `RequestVerification`\n\t// invocation.\n\tstd.TestSetOrigCaller(defaultAddress)\n\tresult = GnorkleEntrypoint(\"request\")\n\texpResult := `[{\"id\":\"` + string(user1Address) + `\",\"type\":\"0\",\"value_type\":\"string\",\"tasks\":[{\"gno_address\":\"` +\n\t\tstring(user1Address) + `\",\"github_handle\":\"deelawn\"}]},` +\n\t\t`{\"id\":\"` + string(user2Address) + `\",\"type\":\"0\",\"value_type\":\"string\",\"tasks\":[{\"gno_address\":\"` +\n\t\tstring(user2Address) + `\",\"github_handle\":\"omarsy\"}]}]`\n\tif result != expResult {\n\t\tt.Fatalf(\"expected request result %s, got %s\", expResult, result)\n\t}\n\n\t// Try to trigger feed ingestion from the non-authorized user.\n\tstd.TestSetOrigCaller(user1Address)\n\tfunc() {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\terrMsg = r.(error).Error()\n\t\t\t}\n\t\t}()\n\t\tGnorkleEntrypoint(\"ingest,\" + string(user1Address) + \",OK\")\n\t}()\n\tif errMsg != \"caller not whitelisted\" {\n\t\tt.Fatalf(\"expected caller not whitelisted, got %s\", errMsg)\n\t}\n\n\t// Set the caller back to the whitelisted user and transfer contract ownership.\n\tstd.TestSetOrigCaller(defaultAddress)\n\tSetOwner(defaultAddress)\n\n\t// Now trigger the feed ingestion from the user and new owner and only whitelisted address.\n\tGnorkleEntrypoint(\"ingest,\" + string(user1Address) + \",OK\")\n\tGnorkleEntrypoint(\"ingest,\" + string(user2Address) + \",OK\")\n\n\t// Verify the ingestion autocommitted the value and triggered the post handler.\n\tdata := Render(\"\")\n\texpResult = `{\"deelawn\": \"` + string(user1Address) + `\",\"omarsy\": \"` + string(user2Address) + `\"}`\n\tif data != expResult {\n\t\tt.Fatalf(\"expected render data %s, got %s\", expResult, data)\n\t}\n\n\t// Finally make sure the feed was cleaned up after the data was committed.\n\tresult = GnorkleEntrypoint(\"request\")\n\tif result != \"[]\" {\n\t\tt.Fatalf(\"expected empty request result, got %s\", result)\n\t}\n\n\t// Check that the accessor functions are working as expected.\n\tif handle := GetHandleByAddress(string(user1Address)); handle != \"deelawn\" {\n\t\tt.Fatalf(\"expected deelawn, got %s\", handle)\n\t}\n\tif address := GetAddressByHandle(\"deelawn\"); address != string(user1Address) {\n\t\tt.Fatalf(\"expected %s, got %s\", string(user1Address), address)\n\t}\n}\n"},{"name":"task.gno","body":"package ghverify\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n)\n\ntype verificationTask struct {\n\tgnoAddress string\n\tgithubHandle string\n}\n\n// MarshalJSON marshals the task contents to JSON.\nfunc (t *verificationTask) MarshalJSON() ([]byte, error) {\n\tbuf := new(bytes.Buffer)\n\tw := bufio.NewWriter(buf)\n\n\tw.Write(\n\t\t[]byte(`{\"gno_address\":\"` + t.gnoAddress + `\",\"github_handle\":\"` + t.githubHandle + `\"}`),\n\t)\n\n\tw.Flush()\n\treturn buf.Bytes(), nil\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":"home","path":"gno.land/r/gnoland/home","files":[{"name":"home.gno","body":"package home\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/p/demo/ui\"\n\tblog \"gno.land/r/gnoland/blog\"\n\tevents \"gno.land/r/gnoland/events\"\n)\n\n// XXX: p/demo/ui API is crappy, we need to make it more idiomatic\n// XXX: use an updatable block system to update content from a DAO\n// XXX: var blocks avl.Tree\n\nvar (\n\toverride string\n\tadmin = ownable.NewWithAddress(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\") // @manfred by default\n)\n\nfunc Render(_ string) string {\n\tif override != \"\" {\n\t\treturn override\n\t}\n\n\tdom := ui.DOM{Prefix: \"r/gnoland/home:\"}\n\tdom.Title = \"Welcome to gno.land\"\n\tdom.Classes = []string{\"gno-tmpl-section\"}\n\n\t// body\n\tdom.Body.Append(introSection()...)\n\n\tdom.Body.Append(ui.Jumbotron(discoverLinks()))\n\n\tdom.Body.Append(\n\t\tui.Columns{3, []ui.Element{\n\t\t\tlastBlogposts(4),\n\t\t\tupcomingEvents(),\n\t\t\tlastContributions(4),\n\t\t}},\n\t)\n\n\tdom.Body.Append(ui.HR{})\n\tdom.Body.Append(playgroundSection()...)\n\tdom.Body.Append(ui.HR{})\n\tdom.Body.Append(packageStaffPicks()...)\n\tdom.Body.Append(ui.HR{})\n\tdom.Body.Append(worxDAO()...)\n\tdom.Body.Append(ui.HR{})\n\t// footer\n\tdom.Footer.Append(\n\t\tui.Columns{2, []ui.Element{\n\t\t\tsocialLinks(),\n\t\t\tquoteOfTheBlock(),\n\t\t}},\n\t)\n\n\t// Testnet disclaimer\n\tdom.Footer.Append(\n\t\tui.HR{},\n\t\tui.Bold(\"This is a testnet.\"),\n\t\tui.Text(\"Package names are not guaranteed to be available for production.\"),\n\t)\n\n\treturn dom.String()\n}\n\nfunc lastBlogposts(limit int) ui.Element {\n\tposts := blog.RenderLastPostsWidget(limit)\n\treturn ui.Element{\n\t\tui.H3(\"[Latest Blogposts](/r/gnoland/blog)\"),\n\t\tui.Text(posts),\n\t}\n}\n\nfunc lastContributions(limit int) ui.Element {\n\treturn ui.Element{\n\t\tui.H3(\"Latest Contributions\"),\n\t\t// TODO: import r/gh to\n\t\tui.Link{Text: \"View latest contributions\", URL: \"https://github.com/gnolang/gno/pulls\"},\n\t}\n}\n\nfunc upcomingEvents() ui.Element {\n\tout, _ := events.RenderEventWidget(events.MaxWidgetSize)\n\treturn ui.Element{\n\t\tui.H3(\"[Latest Events](/r/gnoland/events)\"),\n\t\tui.Text(out),\n\t}\n}\n\nfunc introSection() ui.Element {\n\treturn ui.Element{\n\t\tui.H3(\"We’re building gno.land, set to become the leading open-source smart contract platform, using Gno, an interpreted and fully deterministic variation of the Go programming language for succinct and composable smart contracts.\"),\n\t\tui.Paragraph(\"With transparent and timeless code, gno.land is the next generation of smart contract platforms, serving as the “GitHub” of the ecosystem, with realms built using fully transparent, auditable code that anyone can inspect and reuse.\"),\n\t\tui.Paragraph(\"Intuitive and easy to use, gno.land lowers the barrier to web3 and makes censorship-resistant platforms accessible to everyone. If you want to help lay the foundations of a fairer and freer world, join us today.\"),\n\t}\n}\n\nfunc worxDAO() ui.Element {\n\t// WorxDAO\n\t// XXX(manfred): please, let me finish a v0, then we can iterate\n\t// highest level == highest responsibility\n\t// teams are responsible for components they don't owne\n\t// flag : realm maintainers VS facilitators\n\t// teams\n\t// committee of trustees to create the directory\n\t// each directory is a name, has a parent and have groups\n\t// homepage team - blocks aggregating events\n\t// XXX: TODO\n\t/*`\n\t# Directory\n\n\t* gno.land (owned by group)\n\t *\n\t* gnovm\n\t * gnolang (language)\n\t * gnovm\n\t - current challenges / concerns / issues\n\t* tm2\n\t * amino\n\t *\n\n\t## Contributors\n\t``*/\n\treturn ui.Element{\n\t\tui.H3(\"Contributions (WorxDAO \u0026 GoR)\"),\n\t\t// TODO: GoR dashboard + WorxDAO topics\n\t\tui.Text(`coming soon`),\n\t}\n}\n\nfunc quoteOfTheBlock() ui.Element {\n\tquotes := []string{\n\t\t\"Gno is for Truth.\",\n\t\t\"Gno is for Social Coordination.\",\n\t\t\"Gno is _not only_ for DeFi.\",\n\t\t\"Now, you Gno.\",\n\t\t\"Come for the Go, Stay for the Gno.\",\n\t}\n\theight := std.GetHeight()\n\tidx := int(height) % len(quotes)\n\tqotb := quotes[idx]\n\n\treturn ui.Element{\n\t\tui.H3(ufmt.Sprintf(\"Quote of the ~Day~ Block#%d\", height)),\n\t\tui.Quote(qotb),\n\t}\n}\n\nfunc socialLinks() ui.Element {\n\treturn ui.Element{\n\t\tui.H3(\"Socials\"),\n\t\tui.BulletList{\n\t\t\t// XXX: improve UI to support a nice GO api for such links\n\t\t\tui.Text(\"Check out our [community projects](https://github.com/gnolang/awesome-gno)\"),\n\t\t\tui.Text(\"![Discord](static/img/ico-discord.svg) [Discord](https://discord.gg/S8nKUqwkPn)\"),\n\t\t\tui.Text(\"![Twitter](static/img/ico-twitter.svg) [Twitter](https://twitter.com/_gnoland)\"),\n\t\t\tui.Text(\"![Youtube](static/img/ico-youtube.svg) [Youtube](https://www.youtube.com/@_gnoland)\"),\n\t\t\tui.Text(\"![Telegram](static/img/ico-telegram.svg) [Telegram](https://t.me/gnoland)\"),\n\t\t},\n\t}\n}\n\nfunc playgroundSection() ui.Element {\n\treturn ui.Element{\n\t\tui.H3(\"[Gno Playground](https://play.gno.land)\"),\n\t\tui.Paragraph(`Gno Playground is a web application designed for building, running, testing, and interacting\nwith your Gno code, enhancing your understanding of the Gno language. With Gno Playground, you can share your code,\nexecute tests, deploy your realms and packages to gno.land, and explore a multitude of other features.`),\n\t\tui.Paragraph(\"Experience the convenience of code sharing and rapid experimentation with [Gno Playground](https://play.gno.land).\"),\n\t}\n}\n\nfunc packageStaffPicks() ui.Element {\n\t// XXX: make it modifiable from a DAO\n\treturn ui.Element{\n\t\tui.H3(\"Explore New Packages and Realms\"),\n\t\tui.Columns{\n\t\t\t3,\n\t\t\t[]ui.Element{\n\t\t\t\t{\n\t\t\t\t\tui.H4(\"[r/gnoland](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/gnoland)\"),\n\t\t\t\t\tui.BulletList{\n\t\t\t\t\t\tui.Link{URL: \"r/gnoland/blog\"},\n\t\t\t\t\t\tui.Link{URL: \"r/gnoland/dao\"},\n\t\t\t\t\t\tui.Link{URL: \"r/gnoland/faucet\"},\n\t\t\t\t\t\tui.Link{URL: \"r/gnoland/home\"},\n\t\t\t\t\t\tui.Link{URL: \"r/gnoland/pages\"},\n\t\t\t\t\t},\n\t\t\t\t\tui.H4(\"[r/sys](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/sys)\"),\n\t\t\t\t\tui.BulletList{\n\t\t\t\t\t\tui.Link{URL: \"r/sys/names\"},\n\t\t\t\t\t\tui.Link{URL: \"r/sys/rewards\"},\n\t\t\t\t\t\tui.Link{URL: \"/r/sys/validators/v2\"},\n\t\t\t\t\t},\n\t\t\t\t}, {\n\t\t\t\t\tui.H4(\"[r/demo](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo)\"),\n\t\t\t\t\tui.BulletList{\n\t\t\t\t\t\tui.Link{URL: \"r/demo/boards\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/users\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/banktest\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/foo20\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/foo721\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/microblog\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/nft\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/types\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/art/gnoface\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/art/millipede\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/groups\"},\n\t\t\t\t\t\tui.Text(\"...\"),\n\t\t\t\t\t},\n\t\t\t\t}, {\n\t\t\t\t\tui.H4(\"[p/demo](https://github.com/gnolang/gno/tree/master/examples/gno.land/p/demo)\"),\n\t\t\t\t\tui.BulletList{\n\t\t\t\t\t\tui.Link{URL: \"p/demo/avl\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/blog\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/ui\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/ufmt\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/merkle\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/bf\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/flow\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/gnode\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/grc/grc20\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/grc/grc721\"},\n\t\t\t\t\t\tui.Text(\"...\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc discoverLinks() ui.Element {\n\treturn ui.Element{\n\t\tui.Text(`\u003cdiv class=\"columns-3\"\u003e\n\u003cdiv class=\"column\"\u003e\n\n### Learn about gno.land\n\n- [About](/about)\n- [GitHub](https://github.com/gnolang)\n- [Blog](/blog)\n- [Events](/events)\n- Tokenomics (soon)\n- [Partners, Fund, Grants](/partners)\n- [Explore the Ecosystem](/ecosystem)\n- [Careers](https://jobs.ashbyhq.com/allinbits)\n\n\u003c/div\u003e\u003c!-- end column--\u003e\n\n\u003cdiv class=\"column\"\u003e\n\n### Build with Gno\n\n- [Write Gno in the browser](https://play.gno.land)\n- [Read about the Gno Language](/gnolang)\n- [Visit the official documentation](https://docs.gno.land)\n- [Gno by Example](https://gno-by-example.com/)\n- [Efficient local development for Gno](https://docs.gno.land/gno-tooling/cli/gno-tooling-gnodev)\n- [Get testnet GNOTs](https://faucet.gno.land)\n\n\u003c/div\u003e\u003c!-- end column--\u003e\n\u003cdiv class=\"column\"\u003e\n\n### Explore the universe\n\n- [Discover demo packages](https://github.com/gnolang/gno/tree/master/examples)\n- [Gnoscan](https://gnoscan.io)\n- [Portal Loop](https://docs.gno.land/concepts/portal-loop)\n- [Testnet 4](https://test4.gno.land/)\n- Testnet Faucet Hub (soon)\n\n\u003c/div\u003e\u003c!-- end column--\u003e\n\u003c/div\u003e\u003c!-- end columns-3--\u003e`),\n\t}\n}\n\nfunc AdminSetOverride(content string) {\n\tadmin.AssertCallerIsOwner()\n\toverride = content\n}\n\nfunc AdminTransferOwnership(newAdmin std.Address) {\n\tadmin.AssertCallerIsOwner()\n\tadmin.TransferOwnership(newAdmin)\n}\n"},{"name":"home_filetest.gno","body":"package main\n\nimport \"gno.land/r/gnoland/home\"\n\nfunc main() {\n\tprintln(home.Render(\"\"))\n}\n\n// Output:\n// \u003cmain class='gno-tmpl-section'\u003e\n//\n// # Welcome to gno.land\n//\n// ### We’re building gno.land, set to become the leading open-source smart contract platform, using Gno, an interpreted and fully deterministic variation of the Go programming language for succinct and composable smart contracts.\n//\n//\n// With transparent and timeless code, gno.land is the next generation of smart contract platforms, serving as the “GitHub” of the ecosystem, with realms built using fully transparent, auditable code that anyone can inspect and reuse.\n//\n//\n// Intuitive and easy to use, gno.land lowers the barrier to web3 and makes censorship-resistant platforms accessible to everyone. If you want to help lay the foundations of a fairer and freer world, join us today.\n//\n// \u003cdiv class=\"jumbotron\"\u003e\n//\n// \u003cdiv class=\"columns-3\"\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Learn about gno.land\n//\n// - [About](/about)\n// - [GitHub](https://github.com/gnolang)\n// - [Blog](/blog)\n// - [Events](/events)\n// - Tokenomics (soon)\n// - [Partners, Fund, Grants](/partners)\n// - [Explore the Ecosystem](/ecosystem)\n// - [Careers](https://jobs.ashbyhq.com/allinbits)\n//\n// \u003c/div\u003e\u003c!-- end column--\u003e\n//\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Build with Gno\n//\n// - [Write Gno in the browser](https://play.gno.land)\n// - [Read about the Gno Language](/gnolang)\n// - [Visit the official documentation](https://docs.gno.land)\n// - [Gno by Example](https://gno-by-example.com/)\n// - [Efficient local development for Gno](https://docs.gno.land/gno-tooling/cli/gno-tooling-gnodev)\n// - [Get testnet GNOTs](https://faucet.gno.land)\n//\n// \u003c/div\u003e\u003c!-- end column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Explore the universe\n//\n// - [Discover demo packages](https://github.com/gnolang/gno/tree/master/examples)\n// - [Gnoscan](https://gnoscan.io)\n// - [Portal Loop](https://docs.gno.land/concepts/portal-loop)\n// - [Testnet 4](https://test4.gno.land/)\n// - Testnet Faucet Hub (soon)\n//\n// \u003c/div\u003e\u003c!-- end column--\u003e\n// \u003c/div\u003e\u003c!-- end columns-3--\u003e\n// \u003c/div\u003e\u003c!-- /jumbotron --\u003e\n//\n// \u003cdiv class=\"columns-3\"\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### [Latest Blogposts](/r/gnoland/blog)\n//\n// No posts.\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### [Latest Events](/r/gnoland/events)\n//\n// No events.\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Latest Contributions\n//\n// [View latest contributions](https://github.com/gnolang/gno/pulls)\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003c/div\u003e\u003c!-- /columns-3 --\u003e\n//\n//\n// ---\n//\n// ### [Gno Playground](https://play.gno.land)\n//\n//\n// Gno Playground is a web application designed for building, running, testing, and interacting\n// with your Gno code, enhancing your understanding of the Gno language. With Gno Playground, you can share your code,\n// execute tests, deploy your realms and packages to gno.land, and explore a multitude of other features.\n//\n//\n// Experience the convenience of code sharing and rapid experimentation with [Gno Playground](https://play.gno.land).\n//\n//\n// ---\n//\n// ### Explore New Packages and Realms\n//\n// \u003cdiv class=\"columns-3\"\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// #### [r/gnoland](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/gnoland)\n//\n// - [r/gnoland/blog](r/gnoland/blog)\n// - [r/gnoland/dao](r/gnoland/dao)\n// - [r/gnoland/faucet](r/gnoland/faucet)\n// - [r/gnoland/home](r/gnoland/home)\n// - [r/gnoland/pages](r/gnoland/pages)\n//\n// #### [r/sys](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/sys)\n//\n// - [r/sys/names](r/sys/names)\n// - [r/sys/rewards](r/sys/rewards)\n// - [/r/sys/validators/v2](/r/sys/validators/v2)\n//\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// #### [r/demo](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo)\n//\n// - [r/demo/boards](r/demo/boards)\n// - [r/demo/users](r/demo/users)\n// - [r/demo/banktest](r/demo/banktest)\n// - [r/demo/foo20](r/demo/foo20)\n// - [r/demo/foo721](r/demo/foo721)\n// - [r/demo/microblog](r/demo/microblog)\n// - [r/demo/nft](r/demo/nft)\n// - [r/demo/types](r/demo/types)\n// - [r/demo/art/gnoface](r/demo/art/gnoface)\n// - [r/demo/art/millipede](r/demo/art/millipede)\n// - [r/demo/groups](r/demo/groups)\n// - ...\n//\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// #### [p/demo](https://github.com/gnolang/gno/tree/master/examples/gno.land/p/demo)\n//\n// - [p/demo/avl](p/demo/avl)\n// - [p/demo/blog](p/demo/blog)\n// - [p/demo/ui](p/demo/ui)\n// - [p/demo/ufmt](p/demo/ufmt)\n// - [p/demo/merkle](p/demo/merkle)\n// - [p/demo/bf](p/demo/bf)\n// - [p/demo/flow](p/demo/flow)\n// - [p/demo/gnode](p/demo/gnode)\n// - [p/demo/grc/grc20](p/demo/grc/grc20)\n// - [p/demo/grc/grc721](p/demo/grc/grc721)\n// - ...\n//\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003c/div\u003e\u003c!-- /columns-3 --\u003e\n//\n//\n// ---\n//\n// ### Contributions (WorxDAO \u0026 GoR)\n//\n// coming soon\n//\n// ---\n//\n//\n// \u003cdiv class=\"columns-2\"\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Socials\n//\n// - Check out our [community projects](https://github.com/gnolang/awesome-gno)\n// - ![Discord](static/img/ico-discord.svg) [Discord](https://discord.gg/S8nKUqwkPn)\n// - ![Twitter](static/img/ico-twitter.svg) [Twitter](https://twitter.com/_gnoland)\n// - ![Youtube](static/img/ico-youtube.svg) [Youtube](https://www.youtube.com/@_gnoland)\n// - ![Telegram](static/img/ico-telegram.svg) [Telegram](https://t.me/gnoland)\n//\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Quote of the ~Day~ Block#123\n//\n// \u003e Now, you Gno.\n//\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003c/div\u003e\u003c!-- /columns-2 --\u003e\n//\n//\n// ---\n//\n// **This is a testnet.**\n// Package names are not guaranteed to be available for production.\n//\n// \u003c/main\u003e\n"},{"name":"overide_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/gnoland/home\"\n)\n\nfunc main() {\n\tstd.TestSetOrigCaller(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\thome.AdminSetOverride(\"Hello World!\")\n\tprintln(home.Render(\"\"))\n\thome.AdminTransferOwnership(testutils.TestAddress(\"newAdmin\"))\n\tdefer func() {\n\t\tr := recover()\n\t\tprintln(\"r: \", r)\n\t}()\n\thome.AdminSetOverride(\"Not admin anymore\")\n}\n\n// Output:\n// Hello World!\n// r: ownable: caller is not owner\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":"monit","path":"gno.land/r/gnoland/monit","files":[{"name":"monit.gno","body":"// Package monit links a monitoring system with the chain in both directions.\n//\n// The agent will periodically call Incr() and verify that the value is always\n// higher than the previously known one. The contract will store the last update\n// time and use it to detect whether or not the monitoring agent is functioning\n// correctly.\npackage monit\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/p/demo/watchdog\"\n)\n\nvar (\n\tcounter int\n\tlastUpdate time.Time\n\tlastCaller std.Address\n\twd = watchdog.Watchdog{Duration: 5 * time.Minute}\n\towner = ownable.New() // TODO: replace with -\u003e ownable.NewWithAddress...\n\twatchdogDuration = 5 * time.Minute\n)\n\n// Incr increments the counter and informs the watchdog that we're alive.\n// This function can be called by anyone.\nfunc Incr() int {\n\tcounter++\n\tlastUpdate = time.Now()\n\tlastCaller = std.PrevRealm().Addr()\n\twd.Alive()\n\treturn counter\n}\n\n// Reset resets the realm state.\n// This function can only be called by the admin.\nfunc Reset() {\n\tif owner.CallerIsOwner() != nil { // TODO: replace with owner.AssertCallerIsOwner\n\t\tpanic(\"unauthorized\")\n\t}\n\tcounter = 0\n\tlastCaller = std.PrevRealm().Addr()\n\tlastUpdate = time.Now()\n\twd = watchdog.Watchdog{Duration: 5 * time.Minute}\n}\n\nfunc Render(_ string) string {\n\tstatus := wd.Status()\n\treturn ufmt.Sprintf(\n\t\t\"counter=%d\\nlast update=%s\\nlast caller=%s\\nstatus=%s\",\n\t\tcounter, lastUpdate, lastCaller, status,\n\t)\n}\n\n// TransferOwnership transfers ownership to a new owner. This is a proxy to\n// ownable.Ownable.TransferOwnership.\nfunc TransferOwnership(newOwner std.Address) { owner.TransferOwnership(newOwner) }\n"},{"name":"monit_test.gno","body":"package monit\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestPackage(t *testing.T) {\n\t// initial state, watchdog is KO.\n\t{\n\t\texpected := `counter=0\nlast update=0001-01-01 00:00:00 +0000 UTC\nlast caller=\nstatus=KO`\n\t\tgot := Render(\"\")\n\t\tuassert.Equal(t, expected, got)\n\t}\n\n\t// call Incr(), watchdog is OK.\n\tIncr()\n\tIncr()\n\tIncr()\n\t{\n\t\texpected := `counter=3\nlast update=2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001\nlast caller=g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\nstatus=OK`\n\t\tgot := Render(\"\")\n\t\tuassert.Equal(t, expected, got)\n\t}\n\n\t/* XXX: improve tests once we've the missing std.TestSkipTime feature\n\t\t// wait 1h, watchdog is KO.\n\t\tuse std.TestSkipTime(time.Hour)\n\t\t{\n\t\t\texpected := `counter=3\n\tlast update=2009-02-13 22:31:30 +0000 UTC m=+1234564290.000000001\n\tlast caller=g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n\tstatus=KO`\n\t\t\tgot := Render(\"\")\n\t\t\tuassert.Equal(t, expected, got)\n\t\t}\n\n\t\t// call Incr(), watchdog is OK.\n\t\tIncr()\n\t\t{\n\t\t\texpected := `counter=4\n\tlast update=2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001\n\tlast caller=g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n\tstatus=OK`\n\t\t\tgot := Render(\"\")\n\t\t\tuassert.Equal(t, expected, got)\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":"gnopages","path":"gno.land/r/gnoland/pages","files":[{"name":"admin.gno","body":"package gnopages\n\nimport (\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\nvar (\n\tadminAddr std.Address\n\tmoderatorList avl.Tree\n\tinPause bool\n)\n\nfunc init() {\n\t// adminAddr = std.GetOrigCaller() // FIXME: find a way to use this from the main's genesis.\n\tadminAddr = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"\n}\n\nfunc AdminSetAdminAddr(addr std.Address) {\n\tassertIsAdmin()\n\tadminAddr = addr\n}\n\nfunc AdminSetInPause(state bool) {\n\tassertIsAdmin()\n\tinPause = state\n}\n\nfunc AdminAddModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), true)\n}\n\nfunc AdminRemoveModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), false) // XXX: delete instead?\n}\n\nfunc ModAddPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\tcaller := std.GetOrigCaller()\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.NewPost(caller, slug, title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc ModEditPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.GetPost(slug).Update(title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc isAdmin(addr std.Address) bool {\n\treturn addr == adminAddr\n}\n\nfunc isModerator(addr std.Address) bool {\n\t_, found := moderatorList.Get(addr.String())\n\treturn found\n}\n\nfunc assertIsAdmin() {\n\tcaller := std.GetOrigCaller()\n\tif !isAdmin(caller) {\n\t\tpanic(\"access restricted.\")\n\t}\n}\n\nfunc assertIsModerator() {\n\tcaller := std.GetOrigCaller()\n\tif isAdmin(caller) || isModerator(caller) {\n\t\treturn\n\t}\n\tpanic(\"access restricted\")\n}\n\nfunc assertNotInPause() {\n\tif inPause {\n\t\tpanic(\"access restricted (pause)\")\n\t}\n}\n"},{"name":"page_about.gno","body":"package gnopages\n\nfunc init() {\n\tpath := \"about\"\n\ttitle := \"gno.land Is A Platform To Write Smart Contracts In Gno\"\n\t// XXX: description := \"On gno.land, developers write smart contracts and other blockchain apps using Gno without learning a language that’s exclusive to a single ecosystem.\"\n\tbody := `\ngno.land is a next-generation smart contract platform using Gno, an interpreted version of the general-purpose Go\nprogramming language. On gno.land, smart contracts can be uploaded on-chain only by publishing their full source code,\nmaking it trivial to verify the contract or fork it into an improved version. With a system to publish reusable code\nlibraries on-chain, gno.land serves as the “GitHub” of the ecosystem, with realms built using fully transparent,\nauditable code that anyone can inspect and reuse.\n\ngno.land addresses many pressing issues in the blockchain space, starting with the ease of use and intuitiveness of\nsmart contract platforms. Developers can write smart contracts without having to learn a new language that’s exclusive\nto a single ecosystem or limited by design. Go developers can easily port their existing web apps to gno.land or build\nnew ones from scratch, making web3 vastly more accessible.\n\nSecured by Proof of Contribution (PoC), a DAO-managed Proof-of-Authority consensus mechanism, gno.land prioritizes\nfairness and merit, rewarding the people most active on the platform. PoC restructures the financial incentives that\noften corrupt blockchain projects, opting instead to reward contributors for their work based on expertise, commitment, and\nalignment.\n\nOne of our inspirations for gno.land is the gospels, which built a system of moral code that lasted thousands of years.\nBy observing a minimal production implementation, gno.land’s design will endure over time and serve as a reference for\nfuture generations with censorship-resistant tools that improve their understanding of the world.\n`\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:22Z\", nil, nil)\n}\n"},{"name":"page_contribute.gno","body":"package gnopages\n\nfunc init() {\n\tpath := \"contribute\"\n\ttitle := \"Contributor Ecosystem: Call for Contributions\"\n\tbody := `\n\ngno.land puts at the center of its identity the contributors that help to create and shape the project into what it is; incentivizing those who contribute the most and help advance its vision. Eventually, contributions will be incentivized directly on-chain; in the meantime, this page serves to illustrate our current off-chain initiatives.\n\ngno.land is still in full-steam development. For now, we're looking for the earliest of adopters; curious to explore a new way to build smart contracts and eager to make an impact. Joining gno.land's development now means you can help to shape the base of its development ecosystem, which will pave the way for the next generation of blockchain programming.\n\nAs an open-source project, we welcome all contributions. On this page you can find some pointers on where to get started; as well as some incentives for the most valuable and important contributions.\n\n## Where to get started\n\nIf you are interested in contributing to gno.land, you can jump on in on our [GitHub monorepo](https://github.com/gnolang/gno/blob/master/CONTRIBUTING.md) - where most development happens.\n\nA good place where to start are the issues tagged [\"good first issue\"](https://github.com/gnolang/gno/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22). They should allow you to make some impact on the Gno repository while you're still exploring the details of how everything works.\n\n## Gno Bounties\n\nAdditionally, you can look out to help on specific issues labeled as bounties. All contributions will then concur to form your profile for Game of Realms.\n\nThe Gno bounty program is a good way to find interesting challenges in Gno, and get rewarded for helping us advance the project. We will maintain open and rewardable bounties in the gnolang/gno repository, and you can search all available bounties by using the [\"bounty\" label](https://github.com/gnolang/gno/labels/bounty).\n\nRecommendations on participating in the gno.land Bounty Program:\n\n- Identify the bounty you want to work on, and join in the discussion on the issue for anything that is unclear; or where you want to more clearly define the work to be done. At this stage, you can also start working on an initial implementation in your local enviornment.\n- Once you have spent time on the code related to the bounty, we recommend submitting a 'draft' PR as soon as possible.\n - The draft PR doesn't indicate that the bounty has been assigned to you, others are free to work on other draft PRs for the bounty.\n - Make sure to reference the bounty issue on the PR description you're writing.\n - After submitting the 'draft' PR, continue working until you are ready to mark the PR as \"ready for review\".\n - The core team will review the bounty PR submission after the work on the bounty has been completed, and determine if it qualifies for the bounty reward.\n- Ask for clarification early if an element on the requirements or implementation design is unclear.\n - Aside from publishing the PR early, keeping regular updates with the core team on the bounty issue is key to being on the right track.\n - As part of the requirements, you must adhere to the [contributing guidelines](https://github.com/gnolang/gno/blob/master/CONTRIBUTING.md); additionally, it is expected that any newly added code or functionality is properly documented, tested and covered, at least in 80% of added code.\n - You're welcome to propose additional features and work on an issue should you envision a plausible expansion or change in scope. The core team may assign a bounty to the additional work, or change the bounty with respect to the changed scope.\n\nYou may make your submission at any time; however we invite you to publish your draft PR very early in the development process. This will make your work public, so you can easily get help by the core team and other community members. Additionally, your work can be continued by other people should you get stuck or no longer be willing to work on the bounty. Likewise, you can continue the abandoned or stuck work that someone else worked on.\n\nDon't fear your work being \"stolen\": if a submission is the result of multiple people's efforts, we will look to split the bounty in a way that is fair and recognises each participant in creating the final outcome. Here are some examples of how that can happen:\n\n- If Alice does most of the work and abandons it; then Bob comes around and finishes the job, then Bob's PR will be merged. But the core team will propose a split like 70% for Alice and 30% for Bob (depending, of course, on the relative effort undertaken by both).\n- If Alice makes a PR that does only 50% of the work outlined in the requirements for the original issue, she will get 50%. Someone can still come up and finish the job; and claim the remaining part.\n\t- If you, for instance, cannot complete the entirety of the task or, as a non-developer, can only contribute a part of the specification/implementation, you may still be awarded a bounty for your input in the contribution.\n- If Alice makes a PR that aside from implementing what's required, also undertakes creating useful tools among the way, she may qualify for an \"outstanding contribution\"; and may be awarded up to 25% more of the original bounty's value. Or she may also ask if the team would be willing to offer a different bounty for the implementation of the tools.\n\nParticipants in the gno.land Bounty Program must meet the legal Terms and Conditions referenced [here](https://docs.google.com/document/d/e/2PACX-1vSUF-JwIXGscrNsc5QBD7Pa6i83mXUGogAEIf1wkeb_w42UgL3Lj6jFKMlNTdwEMUnhsLkjRlhe25K4/pub).\n\n### Bounty sizes\n\nEach bounty is associated with a size, to which corresponds the maximum compensation for the work involved on the bounty. A bounty size may under rare occasion be revisited to a bigger or smaller size; hence why it's important to talk about your proposed solution with the core team ahead of time.\n\nIn some cases, the work associated with a bounty may be outstanding. When that happens, the core team can decide to award up to 25% of the bounty's value to the recipient.\n\nThe value of the bounty, aside from the material completion of the task, considers the involved time in managing the created pull request and iterating on feedback.\n\n\nt-shirt size | expected compensation\n-------------|-----------------------\n[XS] | $ 500\n[S] | $ 1000\n[M] | $ 2000\n[L] | $ 4000\n[XL] | $ 8000\n_[XXL]_ \\* | $ 16000\n_[3XL]_ \\* | $ 32000\n\n[XS]: https://github.com/gnolang/gno/labels/bounty%2FXS\n[S]: https://github.com/gnolang/gno/labels/bounty%2FS\n[M]: https://github.com/gnolang/gno/labels/bounty%2FM\n[L]: https://github.com/gnolang/gno/labels/bounty%2FL\n[XL]: https://github.com/gnolang/gno/labels/bounty%2FXL\n[XXL]: https://github.com/gnolang/gno/labels/bounty%2FXXL\n[3XL]: https://github.com/gnolang/gno/labels/bounty%2F3XL\n\n\\*: XXL and 3XL bounties are exceptional. Almost no issues will have these sizes; most will be broken down into smaller bounties.\n\n## gno.land Grants\n\nThe gno.land grants program is to encourage and support the growth of the gno.land contributor community, and build out the usability of the platform and smart contract library. The program provides financial resources to contributors to explore the Gno tech stack, and build dApps, tooling, infrastructure, products, and smart contract libraries in gno.land.\n\nFor more details on gno.land grants, suggested topics, and how to apply, visit our grants [repository](https://github.com/gnolang/grants). \n\n## Join Game of Realms\n\nGame of Realms is the overarching contributor network of gnomes, currently running off-chain, and will eventually transition on-chain. At this stage, a Game of Realms contribution is comprised of high-impact contributions identified as ['notable contributions'](https://github.com/gnolang/game-of-realms/tree/main/contributors).\n\nThese contributions are not linked to immediate financial rewards, but are notable in nature, in the sense they are a challenge, make a significant addition to the project, and require persistence, with minimal feedback loops from the core team.\n\nThe selection of a notable contribution or the sum of contributions that equal 'notable' is based on the impact it has on the development of the project. For now, it is focused on code contributions, and will evolve over time. The Gno development teams will initially qualify and evaluate notable contributions, and vote off-chain on adding them to the 'notable contributions' folder on GitHub.\n\nYou can always contribute to the project, and all contributions will be noticed. Contributing now is a way to build your personal contributor profile in gno.land early on in the ecosystem, and signal your commitment to the project, the community, and its future.\n\nThere are a variety of ways to make your contributions count:\n\n- Core code contributions\n- Realm and pure package development\n- Validator tooling\n- Developer tooling\n- Tutorials and documentation\n\nTo start, we recommend you create a PR in the Game of Realms [repository](https://github.com/gnolang/game-of-realms) to create your profile page for all your contributions.`\n\n\t_ = b.NewPost(\"\", path, title, body, \"2024-09-05T00:00:00Z\", nil, nil)\n}\n"},{"name":"page_ecosystem.gno","body":"package gnopages\n\nfunc init() {\n\tvar (\n\t\tpath = \"ecosystem\"\n\t\ttitle = \"Discover gno.land Ecosystem Projects \u0026 Initiatives\"\n\t\t// XXX: description = \"Dive further into the gno.land ecosystem and discover the core infrastructure, projects, smart contracts, and tooling we’re building.\"\n\t\tbody = `\n### [Gno Playground](https://play.gno.land)\n\nGno Playground is a simple web interface that lets you write, test, and experiment with your Gno code to improve your\nunderstanding of the Gno language. You can share your code, run unit tests, deploy your realms and packages, and execute\nfunctions in your code using the repo.\n\nVisit the playground at [play.gno.land](https://play.gno.land)!\n\n### [Gno Studio Connect](https://gno.studio/connect)\n\nGno Studio Connect provides seamless access to realms, making it simple to explore, interact, and engage\nwith gno.land’s smart contracts through function calls. Connect focuses on function calls, enabling users to interact\nwith any realm’s exposed function(s) on gno.land.\n\nSee your realm interactions in [Gno Studio Connect](https://gno.studio/connect)\n\n### [Gnoscan](https://gnoscan.io)\n\nDeveloped by the Onbloc team, Gnoscan is gno.land’s blockchain explorer. Anyone can use Gnoscan to easily find\ninformation that resides on the gno.land blockchain, such as wallet addresses, TX hashes, blocks, and contracts.\nGnoscan makes our on-chain data easy to read and intuitive to discover.\n\nExplore the gno.land blockchain at [gnoscan.io](https://gnoscan.io)!\n\n### Adena\n\nAdena is a user-friendly non-custodial wallet for gno.land. Open-source and developed by Onbloc, Adena allows gnomes to\ninteract easily with the chain. With an emphasis on UX, Adena is built to handle millions of realms and tokens with a\nhigh-quality interface, support for NFTs and custom tokens, and seamless integration. Install Adena via the [official website](https://www.adena.app/)\n\n### Gnoswap\n\nGnoswap is currently under development and led by the Onbloc team. Gnoswap will be the first DEX on gno.land and is an\nautomated market maker (AMM) protocol written in Gno that allows for permissionless token exchanges on the platform.\n\n### Flippando\n\nFlippando is a simple on-chain memory game, ported from Solidity to Gno, which starts with an empty matrix to flip tiles\non to see what’s underneath. If the tiles match, they remain uncovered; if not, they are briefly shown, and the player\nmust memorize their colors until the entire matrix is uncovered. The end result can be minted as an NFT, which can later\nbe assembled into bigger, more complex NFTs, creating a digital “painting” with the uncovered tiles. Play the game at [Flippando](https://gno.flippando.xyz/flip)\n\n### Gno Native Kit\n\n[Gno Native Kit](https://github.com/gnolang/gnonative) is a framework that allows developers to build and port gno.land (d)apps written in the (d)app's native language.\n\n\n`\n\t)\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:23Z\", nil, nil)\n}\n"},{"name":"page_gnolang.gno","body":"package gnopages\n\nfunc init() {\n\tvar (\n\t\tpath = \"gnolang\"\n\t\ttitle = \"About the Gno, the Language for gno.land\"\n\t\t// TODO fix broken images\n\t\tbody = `\n\n[Gno](https://github.com/gnolang/gno) is an interpretation of the widely-used Go (Golang) programming language for blockchain created by Cosmos co-founder Jae Kwon in 2022 to mark a new era in smart contracting. Gno is ~99% identical to Go, so Go programmers can start coding in Gno right away, with a minimal learning curve. For example, Gno comes with blockchain-specific standard libraries, but any code that doesn’t use blockchain-specific logic can run in Go with minimal processing. Libraries that don’t make sense in the blockchain context, such as network or operating-system access, are not available in Gno. Otherwise, Gno loads and uses many standard libraries that power Go, so most of the parsing of the source code is the same.\n\nUnder the hood, the Gno code is parsed into an abstract syntax tree (AST) and the AST itself is used in the interpreter, rather than bytecode as in many virtual machines such as Java, Python, or Wasm. This makes even the GnoVM accessible to any Go programmer. The novel design of the intuitive GnoVM interpreter allows Gno to freeze and resume the program by persisting and loading the entire memory state. Gno is deterministic, auto-persisted, and auto-Merkle-ized, allowing (smart contract) programs to be succinct, as the programmer doesn’t have to serialize and deserialize objects to persist them into a database (unlike programming applications with the Cosmos SDK).\n\n## How Gno Differs from Go\n\n![Gno and Go differences](static/img/gno-language/go-and-gno.jpg)\n\nThe composable nature of Go/Gno allows for type-checked interactions between contracts, making gno.land safer and more powerful, as well as operationally cheaper and faster. Smart contracts on gno.land are light, simple, more focused, and easily interoperable—a network of interconnected contracts rather than siloed monoliths that limit interactions with other contracts.\n\n![Example of Gno code](static/img/gno-language/code-example.jpg)\n\n## Gno Inherits Go’s Built-in Security Features\n\nGo supports secure programming through exported/non-exported fields, enabling a “least-authority” design. It is easy to create objects and APIs that expose only what should be accessible to callers while hiding what should not be simply by the capitalization of letters, thus allowing a succinct representation of secure logic that can be called by multiple users.\n\nAnother major advantage of Go is that the language comes with an ecosystem of great tooling, like the compiler and third-party tools that statically analyze code. Gno inherits these advantages from Go directly to create a smart contract programming language that provides embedding, composability, type-check safety, and garbage collection, helping developers to write secure code relying on the compiler, parser, and interpreter to give warning alerts for common mistakes.\n\n## Gno vs Solidity\n\nThe most widely-adopted smart contract language today is Ethereum’s EVM-compatible Solidity. With bytecode built from the ground up and Turing complete, Solidity opened up a world of possibilities for decentralized applications (dApps) and there are currently more than 10 million contracts deployed on Ethereum. However, Solidity provides limited tooling and its EVM has a stack limit and computational inefficiencies.\n\nSolidity is designed for one purpose only (writing smart contracts) and is bound by the limitations of the EVM. In addition, developers have to learn several languages if they want to understand the whole stack or work across different ecosystems. Gno aspires to exceed Solidity on multiple fronts (and other smart contract languages like CosmWasm or Substrate) as every part of the stack is written in Gno. It’s easy for developers to understand the entire system just by studying a relatively small code base.\n\n## Gno Is Essential for the Wider Adoption of Web3\n\nGno makes imports as easy as they are in web2 with runtime-based imports for seamless dependency flow comprehension, and support for complex structs, beyond primitive types. Gno is ultimately cost-effective as dependencies are loaded once, enabling remote function calls as local, and providing automatic and independent per-realm state persistence.\n\nUsing Gno, developers can rapidly accelerate application development and adopt a modular structure by reusing and reassembling existing modules without building from scratch. They can embed one structure inside another in an intuitive way while preserving localism, and the language specification is simple, successfully balancing practicality and minimalism.\n\nThe Go language is so well designed that the Gno smart contract system will become the new gold standard for smart contract development and other blockchain applications. As a programming language that is universally adopted, secure, composable, and complete, Gno is essential for the broader adoption of web3 and its sustainable growth.`\n\t)\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:25Z\", nil, nil)\n}\n"},{"name":"page_license.gno","body":"package gnopages\n\nfunc init() {\n\tvar (\n\t\tpath = \"license\"\n\t\ttitle = \"Gno Network General Public License\"\n\t\tbody = `Copyright (C) 2024 NewTendermint, LLC\n\nThis program is free software: you can redistribute it and/or modify it under\nthe terms of the GNO Network General Public License as published by\nNewTendermint, LLC, either version 4 of the License, or (at your option) any\nlater version published by NewTendermint, LLC.\n\nThis program is distributed in the hope that it will be useful, but is provided\nas-is and WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNO Network\nGeneral Public License for more details.\n\nYou should have received a copy of the GNO Network General Public License along\nwith this program. If not, see \u003chttps://gno.land/license\u003e.\n\nAttached below are the terms of the GNO Network General Public License, Version\n4 (a fork of the GNU Affero General Public License 3).\n\n## Additional Terms\n\n### Strong Attribution\n\nIf any of your user interfaces, such as websites and mobile applications, serve\nas the primary point of entry to a platform or blockchain that 1) offers users\nthe ability to upload their own smart contracts to the platform or blockchain,\nand 2) leverages any Covered Work (including the GNO virtual machine) to run\nthose smart contracts on the platform or blockchain (\"Applicable Work\"), then\nthe Applicable Work must prominently link to (1) gno.land or (2) any other URL\ndesignated by NewTendermint, LLC that has not been rejected by the governance of\nthe first chain known as gno.land, provided that the identity of the first chain\nis not ambiguous. In the event the identity of the first chain is ambiguous,\nthen NewTendermint, LLC's designation shall control. Such link must appear\nconspicuously in the header or footer of the Applicable Work, such that all\nusers may learn of gno.land or the URL designated by NewTendermint, LLC.\n\nThis additional attribution requirement shall remain in effect for (1) 7\nyears from the date of publication of the Applicable Work, or (2) 7 years from\nthe date of publication of the Covered Work (including republication of new\nversions), whichever is later, but no later than 12 years after the application\nof this strong attribution requirement to the publication of the Applicable\nWork. For purposes of this Strong Attribution requirement, Covered Work shall\nmean any work that is licensed under the GNO Network General Public License,\nVersion 4 or later, by NewTendermint, LLC.\n\n\n# GNO NETWORK GENERAL PUBLIC LICENSE\n\nVersion 4, 7 May 2024\n\nModified from the GNU AFFERO GENERAL PUBLIC LICENSE.\nGNU is not affiliated with GNO or NewTendermint, LLC.\nCopyright (C) 2022 NewTendermint, LLC.\n\n## Preamble\n\nThe GNO Network General Public License is a free, copyleft license for\nsoftware and other kinds of works, specifically designed to ensure\ncooperation with the community in the case of network server software.\n\nThe licenses for most software and other practical works are designed\nto take away your freedom to share and change the works. By contrast,\nour General Public Licenses are intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.\n\nWhen we speak of free software, we are referring to freedom, not\nprice. Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\nDevelopers that use our General Public Licenses protect your rights\nwith two steps: (1) assert copyright on the software, and (2) offer\nyou this License which gives you legal permission to copy, distribute\nand/or modify the software.\n\nA secondary benefit of defending all users' freedom is that\nimprovements made in alternate versions of the program, if they\nreceive widespread use, become available for other developers to\nincorporate. Many developers of free software are heartened and\nencouraged by the resulting cooperation. However, in the case of\nsoftware used on network servers, this result may fail to come about.\nThe GNU General Public License permits making a modified version and\nletting the public access it on a server without ever releasing its\nsource code to the public.\n\nThe GNO Network General Public License is designed specifically to\nensure that, in such cases, the modified source code becomes available\nto the community. It requires the operator of a network server to\nprovide the source code of the modified version running there to the\nusers of that server. Therefore, public use of a modified version, on\na publicly accessible server, gives the public access to the source\ncode of the modified version.\n\nThe precise terms and conditions for copying, distribution and\nmodification follow.\n\n## TERMS AND CONDITIONS\n\n### 0. Definitions.\n\n\"This License\" refers to version 4 of the GNO Network General Public License.\n\n\"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n\"The Program\" refers to any copyrightable work licensed under this\nLicense. Each licensee is addressed as \"you\". \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\nTo \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy. The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\nA \"covered work\" means either the unmodified Program or a work based\non the Program.\n\nTo \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy. Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\nTo \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies. Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\nAn interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License. If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n### 1. Source Code.\n\nThe \"source code\" for a work means the preferred form of the work\nfor making modifications to it. \"Object code\" means any non-source\nform of a work.\n\nA \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\nThe \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form. A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\nThe \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities. However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work. For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\nThe Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\nThe Corresponding Source for a work in source code form is that\nsame work.\n\n### 2. Basic Permissions.\n\nAll rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met. This License explicitly affirms your unlimited\npermission to run the unmodified Program. The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work. This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\nYou may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force. You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright. Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\nConveying under any other circumstances is permitted solely under\nthe conditions stated below. Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n### 3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\nNo covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\nWhen you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n### 4. Conveying Verbatim Copies.\n\nYou may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\nYou may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n### 5. Conveying Modified Source Versions.\n\nYou may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n- a) The work must carry prominent notices stating that you modified\n it, and giving a relevant date.\n- b) The work must carry prominent notices stating that it is\n released under this License and any conditions added under section\n 7. This requirement modifies the requirement in section 4 to\n \"keep intact all notices\".\n- c) You must license the entire work, as a whole, under this\n License to anyone who comes into possession of a copy. This\n License will therefore apply, along with any applicable section 7\n additional terms, to the whole of the work, and all its parts,\n regardless of how they are packaged. This License gives no\n permission to license the work in any other way, but it does not\n invalidate such permission if you have separately received it.\n- d) If the work has interactive user interfaces, each must display\n Appropriate Legal Notices; however, if the Program has interactive\n interfaces that do not display Appropriate Legal Notices, your\n work need not make them do so.\n\nA compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit. Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n### 6. Conveying Non-Source Forms.\n\n You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n- a) Convey the object code in, or embodied in, a physical product\n (including a physical distribution medium), accompanied by the\n Corresponding Source fixed on a durable physical medium\n customarily used for software interchange.\n- b) Convey the object code in, or embodied in, a physical product\n (including a physical distribution medium), accompanied by a\n written offer, valid for at least three years and valid for as\n long as you offer spare parts or customer support for that product\n model, to give anyone who possesses the object code either (1) a\n copy of the Corresponding Source for all the software in the\n product that is covered by this License, on a durable physical\n medium customarily used for software interchange, for a price no\n more than your reasonable cost of physically performing this\n conveying of source, or (2) access to copy the\n Corresponding Source from a network server at no charge.\n- c) Convey individual copies of the object code with a copy of the\n written offer to provide the Corresponding Source. This\n alternative is allowed only occasionally and noncommercially, and\n only if you received the object code with such an offer, in accord\n with subsection 6b.\n- d) Convey the object code by offering access from a designated\n place (gratis or for a charge), and offer equivalent access to the\n Corresponding Source in the same way through the same place at no\n further charge. You need not require recipients to copy the\n Corresponding Source along with the object code. If the place to\n copy the object code is a network server, the Corresponding Source\n may be on a different server (operated by you or a third party)\n that supports equivalent copying facilities, provided you maintain\n clear directions next to the object code saying where to find the\n Corresponding Source. Regardless of what server hosts the\n Corresponding Source, you remain obligated to ensure that it is\n available for as long as needed to satisfy these requirements.\n- e) Convey the object code using peer-to-peer transmission, provided\n you inform other peers where the object code and Corresponding\n Source of the work are being offered to the general public at no\n charge under subsection 6d.\n\nA separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\nA \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling. In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage. For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product. A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n\"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source. The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\nIf you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information. But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\nThe requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed. Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\nCorresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n### 7. Additional Terms.\n\n\"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law. If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\nWhen you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit. (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.) You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\nNotwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n- a) Disclaiming warranty or limiting liability differently from the\n terms of sections 15 and 16 of this License; or\n- b) Requiring preservation of specified reasonable legal notices or\n author attributions in that material or in the Appropriate Legal\n Notices displayed by works containing it; or\n- c) Prohibiting misrepresentation of the origin of that material, or\n requiring that modified versions of such material be marked in\n reasonable ways as different from the original version; or\n- d) Limiting the use for publicity purposes of names of licensors or\n authors of the material; or\n- e) Declining to grant rights under trademark law for use of some\n trade names, trademarks, or service marks; or\n- f) Requiring indemnification of licensors and authors of that\n material by anyone who conveys the material (or modified versions of\n it) with contractual assumptions of liability to the recipient, for\n any liability that these contractual assumptions directly impose on\n those licensors and authors; or\n- g) Requiring strong attribution such as notices on any user interfaces\n that run or convey any covered work, such as a prominent link to a URL\n on the header of a website, such that all users of the covered work may\n become aware of the notice, for a period no longer than 20 years.\n\nAll other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10. If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term. If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\nIf you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\nAdditional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n### 8. Termination.\n\nYou may not propagate or modify a covered work except as expressly\nprovided under this License. Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\nHowever, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\nMoreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\nTermination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License. If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n### 9. Acceptance Not Required for Having Copies.\n\nYou are not required to accept this License in order to receive or\nrun a copy of the Program. Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance. However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work. These actions infringe copyright if you do\nnot accept this License. Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n### 10. Automatic Licensing of Downstream Recipients.\n\nEach time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License. You are not responsible\nfor enforcing compliance by third parties with this License.\n\nAn \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations. If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\nYou may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License. For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n### 11. Patents.\n\nA \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based. The\nwork thus licensed is called the contributor's \"contributor version\".\n\nA contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version. For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\nEach contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\nIn the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement). To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\nIf you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients. \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\nIf, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\nA patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License. You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\nNothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n### 12. No Surrender of Others' Freedom.\n\nIf conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License. If you cannot convey a\ncovered work so as to simultaneously satisfy your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all. For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n### 13. Remote Network Interaction; Use with the GNU General Public License.\n\nNotwithstanding any other provision of this License, if you modify the\nProgram, your modified version must prominently offer all users\ninteracting with it remotely through a computer network (if your version\nsupports such interaction) an opportunity to receive the Corresponding\nSource of your version by providing access to the Corresponding Source\nfrom a network server at no charge, through some standard or customary\nmeans of facilitating copying of software. This Corresponding Source\nshall include the Corresponding Source for any work covered by version 3\nof the GNU General Public License that is incorporated pursuant to the\nfollowing paragraph.\n\nNotwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU General Public License into a single\ncombined work, and to convey the resulting work. The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the work with which it is combined will remain governed by version\n3 of the GNU General Public License.\n\n### 14. Revised Versions of this License.\n\nNewTendermint LLC may publish revised and/or new versions of\nthe GNO Network General Public License from time to time. Such new versions\nwill be similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\nEach version is given a distinguishing version number. If the\nProgram specifies that a certain numbered version of the GNO Network General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Gno Software\nFoundation. If the Program does not specify a version number of the\nGNO Network General Public License, you may choose any version ever published\nby NewTendermint LLC.\n\nIf the Program specifies that a proxy can decide which future\nversions of the GNO Network General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\nLater license versions may give you additional or different\npermissions. However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n### 15. Disclaimer of Warranty.\n\nTHERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n### 16. Limitation of Liability.\n\nIN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n### 17. Interpretation of Sections 15 and 16.\n\nIf the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\nEND OF TERMS AND CONDITIONS\n\n## How to Apply These Terms to Your New Programs\n\nIf you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\nTo do so, attach the following notices to the program. It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n \u003cone line to give the program's name and a brief idea of what it does.\u003e\n Copyright (C) \u003cyear\u003e \u003cname of author\u003e\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNO Network General Public License as published by\n NewTendermint LLC, either version 4 of the License, or\n (at your option) any later version.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNO Network General Public License for more details.\n\n You should have received a copy of the GNO Network General Public License\n along with this program. If not, see \u003chttps://gno.land/license\u003e.\n\nAlso add information on how to contact you by electronic and paper mail.\n\nIf your software can interact with users remotely through a computer\nnetwork, you should also make sure that it provides a way for users to\nget its source. For example, if your program is a web application, its\ninterface could display a \"Source\" link that leads users to an archive\nof the code. There are many ways you could offer source, and different\nsolutions will be better for different programs; see section 13 for the\nspecific requirements.\n`\n\t)\n\t_ = b.NewPost(\"\", path, title, body, \"2024-04-22T00:00:00Z\", nil, nil)\n}\n"},{"name":"page_partners.gno","body":"package gnopages\n\nfunc init() {\n\tpath := \"partners\"\n\ttitle := \"Partnerships\"\n\tbody := `### Fund and Grants Program\n\nAre you a builder, tinkerer, or researcher? If you’re looking to create awesome dApps, tooling, infrastructure, \nor smart contract libraries on gno.land, you can apply for a grant. The gno.land Ecosystem Fund and Grants program \nprovides financial contributions for individuals and teams to innovate on the platform.\n\nRead more about our Funds and Grants program [here](https://github.com/gnolang/ecosystem-fund-grants).\n`\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:27Z\", nil, nil)\n}\n"},{"name":"page_start.gno","body":"package gnopages\n\nfunc init() {\n\tpath := \"start\"\n\ttitle := \"Getting Started with Gno\"\n\t// XXX: description := \"\"\n\n\t// TODO: codegen to use README files here\n\n\t/* TODO: port previous message: This is a demo of Gno smart contract programming. This document was\n\tconstructed by Gno onto a smart contract hosted on the data Realm\n\tname [\"gno.land/r/demo/boards\"](https://gno.land/r/demo/boards/)\n\t([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)).\n\t*/\n\tbody := `## Getting Started with Gno\n\n- [Install Gno Key](/r/demo/boards:testboard/5)\n- TODO: add more links\n`\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:28Z\", nil, nil)\n}\n"},{"name":"page_testnets.gno","body":"package gnopages\n\nfunc init() {\n\tpath := \"testnets\"\n\ttitle := \"gno.land Testnet List\"\n\tbody := `\n- [Portal Loop](https://docs.gno.land/concepts/portal-loop) - a rolling testnet\n- [staging.gno.land](https://staging.gno.land) - wiped every commit to monorepo master\n- _[test4.gno.land](https://test4.gno.land) (latest)_\n\nFor a list of RPC endpoints, see the [reference documentation](https://docs.gno.land/reference/rpc-endpoints).\n\n## Local development\n\nSee the \"Getting started\" section in the [official documentation](https://docs.gno.land/getting-started/local-setup).\n`\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:29Z\", nil, nil)\n}\n"},{"name":"page_tokenomics.gno","body":"package gnopages\n\nfunc init() {\n\tvar (\n\t\tpath = \"tokenomics\"\n\t\ttitle = \"gno.land Tokenomics\"\n\t\t// XXX: description = \"\"\"\n\t\tbody = `Lorem Ipsum`\n\t)\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:30Z\", nil, nil)\n}\n"},{"name":"pages.gno","body":"package gnopages\n\nimport (\n\t\"gno.land/p/demo/blog\"\n)\n\n// TODO: switch from p/blog to p/pages\n\nvar b = \u0026blog.Blog{\n\tTitle: \"Gnoland's Pages\",\n\tPrefix: \"/r/gnoland/pages:\",\n\tNoBreadcrumb: true,\n}\n\nfunc Render(path string) string {\n\treturn b.Render(path)\n}\n"},{"name":"pages_test.gno","body":"package gnopages\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestHome(t *testing.T) {\n\tprintedOnce := false\n\tgot := Render(\"\")\n\texpectedSubtrings := []string{\n\t\t\"/r/gnoland/pages:p/tokenomics\",\n\t\t\"/r/gnoland/pages:p/start\",\n\t\t\"/r/gnoland/pages:p/contribute\",\n\t\t\"/r/gnoland/pages:p/about\",\n\t\t\"/r/gnoland/pages:p/gnolang\",\n\t}\n\tfor _, substring := range expectedSubtrings {\n\t\tif !strings.Contains(got, substring) {\n\t\t\tif !printedOnce {\n\t\t\t\tprintln(got)\n\t\t\t\tprintedOnce = true\n\t\t\t}\n\t\t\tt.Errorf(\"expected %q, but not found.\", substring)\n\t\t}\n\t}\n}\n\nfunc TestAbout(t *testing.T) {\n\tprintedOnce := false\n\tgot := Render(\"p/about\")\n\texpectedSubtrings := []string{\n\t\t\"gno.land Is A Platform To Write Smart Contracts In Gno\",\n\t\t\"gno.land is a next-generation smart contract platform using Gno, an interpreted version of the general-purpose Go\\nprogramming language.\",\n\t}\n\tfor _, substring := range expectedSubtrings {\n\t\tif !strings.Contains(got, substring) {\n\t\t\tif !printedOnce {\n\t\t\t\tprintln(got)\n\t\t\t\tprintedOnce = true\n\t\t\t}\n\t\t\tt.Errorf(\"expected %q, but not found.\", substring)\n\t\t}\n\t}\n}\n"},{"name":"util.gno","body":"package gnopages\n\nfunc checkErr(err error) {\n\tif err != nil {\n\t\tpanic(err)\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":"gnopages","path":"gno.land/r/gnoland/pages","files":[{"name":"admin.gno","body":"package gnopages\n\nimport (\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\nvar (\n\tadminAddr std.Address\n\tmoderatorList avl.Tree\n\tinPause bool\n)\n\nfunc init() {\n\t// adminAddr = std.OrigCaller() // FIXME: find a way to use this from the main's genesis.\n\tadminAddr = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"\n}\n\nfunc AdminSetAdminAddr(addr std.Address) {\n\tassertIsAdmin()\n\tadminAddr = addr\n}\n\nfunc AdminSetInPause(state bool) {\n\tassertIsAdmin()\n\tinPause = state\n}\n\nfunc AdminAddModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), true)\n}\n\nfunc AdminRemoveModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), false) // XXX: delete instead?\n}\n\nfunc ModAddPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\tcaller := std.OrigCaller()\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.NewPost(caller, slug, title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc ModEditPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.GetPost(slug).Update(title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc isAdmin(addr std.Address) bool {\n\treturn addr == adminAddr\n}\n\nfunc isModerator(addr std.Address) bool {\n\t_, found := moderatorList.Get(addr.String())\n\treturn found\n}\n\nfunc assertIsAdmin() {\n\tcaller := std.OrigCaller()\n\tif !isAdmin(caller) {\n\t\tpanic(\"access restricted.\")\n\t}\n}\n\nfunc assertIsModerator() {\n\tcaller := std.OrigCaller()\n\tif isAdmin(caller) || isModerator(caller) {\n\t\treturn\n\t}\n\tpanic(\"access restricted\")\n}\n\nfunc assertNotInPause() {\n\tif inPause {\n\t\tpanic(\"access restricted (pause)\")\n\t}\n}\n"},{"name":"page_about.gno","body":"package gnopages\n\nfunc init() {\n\tpath := \"about\"\n\ttitle := \"gno.land Is A Platform To Write Smart Contracts In Gno\"\n\t// XXX: description := \"On gno.land, developers write smart contracts and other blockchain apps using Gno without learning a language that’s exclusive to a single ecosystem.\"\n\tbody := `\ngno.land is a next-generation smart contract platform using Gno, an interpreted version of the general-purpose Go\nprogramming language. On gno.land, smart contracts can be uploaded on-chain only by publishing their full source code,\nmaking it trivial to verify the contract or fork it into an improved version. With a system to publish reusable code\nlibraries on-chain, gno.land serves as the “GitHub” of the ecosystem, with realms built using fully transparent,\nauditable code that anyone can inspect and reuse.\n\ngno.land addresses many pressing issues in the blockchain space, starting with the ease of use and intuitiveness of\nsmart contract platforms. Developers can write smart contracts without having to learn a new language that’s exclusive\nto a single ecosystem or limited by design. Go developers can easily port their existing web apps to gno.land or build\nnew ones from scratch, making web3 vastly more accessible.\n\nSecured by Proof of Contribution (PoC), a DAO-managed Proof-of-Authority consensus mechanism, gno.land prioritizes\nfairness and merit, rewarding the people most active on the platform. PoC restructures the financial incentives that\noften corrupt blockchain projects, opting instead to reward contributors for their work based on expertise, commitment, and\nalignment.\n\nOne of our inspirations for gno.land is the gospels, which built a system of moral code that lasted thousands of years.\nBy observing a minimal production implementation, gno.land’s design will endure over time and serve as a reference for\nfuture generations with censorship-resistant tools that improve their understanding of the world.\n`\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:22Z\", nil, nil)\n}\n"},{"name":"page_contribute.gno","body":"package gnopages\n\nfunc init() {\n\tpath := \"contribute\"\n\ttitle := \"Contributor Ecosystem: Call for Contributions\"\n\tbody := `\n\ngno.land puts at the center of its identity the contributors that help to create and shape the project into what it is; incentivizing those who contribute the most and help advance its vision. Eventually, contributions will be incentivized directly on-chain; in the meantime, this page serves to illustrate our current off-chain initiatives.\n\ngno.land is still in full-steam development. For now, we're looking for the earliest of adopters; curious to explore a new way to build smart contracts and eager to make an impact. Joining gno.land's development now means you can help to shape the base of its development ecosystem, which will pave the way for the next generation of blockchain programming.\n\nAs an open-source project, we welcome all contributions. On this page you can find some pointers on where to get started; as well as some incentives for the most valuable and important contributions.\n\n## Where to get started\n\nIf you are interested in contributing to gno.land, you can jump on in on our [GitHub monorepo](https://github.com/gnolang/gno/blob/master/CONTRIBUTING.md) - where most development happens.\n\nA good place where to start are the issues tagged [\"good first issue\"](https://github.com/gnolang/gno/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22). They should allow you to make some impact on the Gno repository while you're still exploring the details of how everything works.\n\n## Gno Bounties\n\nAdditionally, you can look out to help on specific issues labeled as bounties. All contributions will then concur to form your profile for Game of Realms.\n\nThe Gno bounty program is a good way to find interesting challenges in Gno, and get rewarded for helping us advance the project. We will maintain open and rewardable bounties in the gnolang/gno repository, and you can search all available bounties by using the [\"bounty\" label](https://github.com/gnolang/gno/labels/bounty).\n\nRecommendations on participating in the gno.land Bounty Program:\n\n- Identify the bounty you want to work on, and join in the discussion on the issue for anything that is unclear; or where you want to more clearly define the work to be done. At this stage, you can also start working on an initial implementation in your local enviornment.\n- Once you have spent time on the code related to the bounty, we recommend submitting a 'draft' PR as soon as possible.\n - The draft PR doesn't indicate that the bounty has been assigned to you, others are free to work on other draft PRs for the bounty.\n - Make sure to reference the bounty issue on the PR description you're writing.\n - After submitting the 'draft' PR, continue working until you are ready to mark the PR as \"ready for review\".\n - The core team will review the bounty PR submission after the work on the bounty has been completed, and determine if it qualifies for the bounty reward.\n- Ask for clarification early if an element on the requirements or implementation design is unclear.\n - Aside from publishing the PR early, keeping regular updates with the core team on the bounty issue is key to being on the right track.\n - As part of the requirements, you must adhere to the [contributing guidelines](https://github.com/gnolang/gno/blob/master/CONTRIBUTING.md); additionally, it is expected that any newly added code or functionality is properly documented, tested and covered, at least in 80% of added code.\n - You're welcome to propose additional features and work on an issue should you envision a plausible expansion or change in scope. The core team may assign a bounty to the additional work, or change the bounty with respect to the changed scope.\n\nYou may make your submission at any time; however we invite you to publish your draft PR very early in the development process. This will make your work public, so you can easily get help by the core team and other community members. Additionally, your work can be continued by other people should you get stuck or no longer be willing to work on the bounty. Likewise, you can continue the abandoned or stuck work that someone else worked on.\n\nDon't fear your work being \"stolen\": if a submission is the result of multiple people's efforts, we will look to split the bounty in a way that is fair and recognises each participant in creating the final outcome. Here are some examples of how that can happen:\n\n- If Alice does most of the work and abandons it; then Bob comes around and finishes the job, then Bob's PR will be merged. But the core team will propose a split like 70% for Alice and 30% for Bob (depending, of course, on the relative effort undertaken by both).\n- If Alice makes a PR that does only 50% of the work outlined in the requirements for the original issue, she will get 50%. Someone can still come up and finish the job; and claim the remaining part.\n\t- If you, for instance, cannot complete the entirety of the task or, as a non-developer, can only contribute a part of the specification/implementation, you may still be awarded a bounty for your input in the contribution.\n- If Alice makes a PR that aside from implementing what's required, also undertakes creating useful tools among the way, she may qualify for an \"outstanding contribution\"; and may be awarded up to 25% more of the original bounty's value. Or she may also ask if the team would be willing to offer a different bounty for the implementation of the tools.\n\nParticipants in the gno.land Bounty Program must meet the legal Terms and Conditions referenced [here](https://docs.google.com/document/d/e/2PACX-1vSUF-JwIXGscrNsc5QBD7Pa6i83mXUGogAEIf1wkeb_w42UgL3Lj6jFKMlNTdwEMUnhsLkjRlhe25K4/pub).\n\n### Bounty sizes\n\nEach bounty is associated with a size, to which corresponds the maximum compensation for the work involved on the bounty. A bounty size may under rare occasion be revisited to a bigger or smaller size; hence why it's important to talk about your proposed solution with the core team ahead of time.\n\nIn some cases, the work associated with a bounty may be outstanding. When that happens, the core team can decide to award up to 25% of the bounty's value to the recipient.\n\nThe value of the bounty, aside from the material completion of the task, considers the involved time in managing the created pull request and iterating on feedback.\n\n\nt-shirt size | expected compensation\n-------------|-----------------------\n[XS] | $ 500\n[S] | $ 1000\n[M] | $ 2000\n[L] | $ 4000\n[XL] | $ 8000\n_[XXL]_ \\* | $ 16000\n_[3XL]_ \\* | $ 32000\n\n[XS]: https://github.com/gnolang/gno/labels/bounty%2FXS\n[S]: https://github.com/gnolang/gno/labels/bounty%2FS\n[M]: https://github.com/gnolang/gno/labels/bounty%2FM\n[L]: https://github.com/gnolang/gno/labels/bounty%2FL\n[XL]: https://github.com/gnolang/gno/labels/bounty%2FXL\n[XXL]: https://github.com/gnolang/gno/labels/bounty%2FXXL\n[3XL]: https://github.com/gnolang/gno/labels/bounty%2F3XL\n\n\\*: XXL and 3XL bounties are exceptional. Almost no issues will have these sizes; most will be broken down into smaller bounties.\n\n## gno.land Grants\n\nThe gno.land grants program is to encourage and support the growth of the gno.land contributor community, and build out the usability of the platform and smart contract library. The program provides financial resources to contributors to explore the Gno tech stack, and build dApps, tooling, infrastructure, products, and smart contract libraries in gno.land.\n\nFor more details on gno.land grants, suggested topics, and how to apply, visit our grants [repository](https://github.com/gnolang/grants). \n\n## Join Game of Realms\n\nGame of Realms is the overarching contributor network of gnomes, currently running off-chain, and will eventually transition on-chain. At this stage, a Game of Realms contribution is comprised of high-impact contributions identified as ['notable contributions'](https://github.com/gnolang/game-of-realms/tree/main/contributors).\n\nThese contributions are not linked to immediate financial rewards, but are notable in nature, in the sense they are a challenge, make a significant addition to the project, and require persistence, with minimal feedback loops from the core team.\n\nThe selection of a notable contribution or the sum of contributions that equal 'notable' is based on the impact it has on the development of the project. For now, it is focused on code contributions, and will evolve over time. The Gno development teams will initially qualify and evaluate notable contributions, and vote off-chain on adding them to the 'notable contributions' folder on GitHub.\n\nYou can always contribute to the project, and all contributions will be noticed. Contributing now is a way to build your personal contributor profile in gno.land early on in the ecosystem, and signal your commitment to the project, the community, and its future.\n\nThere are a variety of ways to make your contributions count:\n\n- Core code contributions\n- Realm and pure package development\n- Validator tooling\n- Developer tooling\n- Tutorials and documentation\n\nTo start, we recommend you create a PR in the Game of Realms [repository](https://github.com/gnolang/game-of-realms) to create your profile page for all your contributions.`\n\n\t_ = b.NewPost(\"\", path, title, body, \"2024-09-05T00:00:00Z\", nil, nil)\n}\n"},{"name":"page_ecosystem.gno","body":"package gnopages\n\nfunc init() {\n\tvar (\n\t\tpath = \"ecosystem\"\n\t\ttitle = \"Discover gno.land Ecosystem Projects \u0026 Initiatives\"\n\t\t// XXX: description = \"Dive further into the gno.land ecosystem and discover the core infrastructure, projects, smart contracts, and tooling we’re building.\"\n\t\tbody = `\n### [Gno Playground](https://play.gno.land)\n\nGno Playground is a simple web interface that lets you write, test, and experiment with your Gno code to improve your\nunderstanding of the Gno language. You can share your code, run unit tests, deploy your realms and packages, and execute\nfunctions in your code using the repo.\n\nVisit the playground at [play.gno.land](https://play.gno.land)!\n\n### [Gno Studio Connect](https://gno.studio/connect)\n\nGno Studio Connect provides seamless access to realms, making it simple to explore, interact, and engage\nwith gno.land’s smart contracts through function calls. Connect focuses on function calls, enabling users to interact\nwith any realm’s exposed function(s) on gno.land.\n\nSee your realm interactions in [Gno Studio Connect](https://gno.studio/connect)\n\n### [Gnoscan](https://gnoscan.io)\n\nDeveloped by the Onbloc team, Gnoscan is gno.land’s blockchain explorer. Anyone can use Gnoscan to easily find\ninformation that resides on the gno.land blockchain, such as wallet addresses, TX hashes, blocks, and contracts.\nGnoscan makes our on-chain data easy to read and intuitive to discover.\n\nExplore the gno.land blockchain at [gnoscan.io](https://gnoscan.io)!\n\n### Adena\n\nAdena is a user-friendly non-custodial wallet for gno.land. Open-source and developed by Onbloc, Adena allows gnomes to\ninteract easily with the chain. With an emphasis on UX, Adena is built to handle millions of realms and tokens with a\nhigh-quality interface, support for NFTs and custom tokens, and seamless integration. Install Adena via the [official website](https://www.adena.app/)\n\n### Gnoswap\n\nGnoswap is currently under development and led by the Onbloc team. Gnoswap will be the first DEX on gno.land and is an\nautomated market maker (AMM) protocol written in Gno that allows for permissionless token exchanges on the platform.\n\n### Flippando\n\nFlippando is a simple on-chain memory game, ported from Solidity to Gno, which starts with an empty matrix to flip tiles\non to see what’s underneath. If the tiles match, they remain uncovered; if not, they are briefly shown, and the player\nmust memorize their colors until the entire matrix is uncovered. The end result can be minted as an NFT, which can later\nbe assembled into bigger, more complex NFTs, creating a digital “painting” with the uncovered tiles. Play the game at [Flippando](https://gno.flippando.xyz/flip)\n\n### Gno Native Kit\n\n[Gno Native Kit](https://github.com/gnolang/gnonative) is a framework that allows developers to build and port gno.land (d)apps written in the (d)app's native language.\n\n\n`\n\t)\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:23Z\", nil, nil)\n}\n"},{"name":"page_gnolang.gno","body":"package gnopages\n\nfunc init() {\n\tvar (\n\t\tpath = \"gnolang\"\n\t\ttitle = \"About the Gno, the Language for gno.land\"\n\t\t// TODO fix broken images\n\t\tbody = `\n\n[Gno](https://github.com/gnolang/gno) is an interpretation of the widely-used Go (Golang) programming language for blockchain created by Cosmos co-founder Jae Kwon in 2022 to mark a new era in smart contracting. Gno is ~99% identical to Go, so Go programmers can start coding in Gno right away, with a minimal learning curve. For example, Gno comes with blockchain-specific standard libraries, but any code that doesn’t use blockchain-specific logic can run in Go with minimal processing. Libraries that don’t make sense in the blockchain context, such as network or operating-system access, are not available in Gno. Otherwise, Gno loads and uses many standard libraries that power Go, so most of the parsing of the source code is the same.\n\nUnder the hood, the Gno code is parsed into an abstract syntax tree (AST) and the AST itself is used in the interpreter, rather than bytecode as in many virtual machines such as Java, Python, or Wasm. This makes even the GnoVM accessible to any Go programmer. The novel design of the intuitive GnoVM interpreter allows Gno to freeze and resume the program by persisting and loading the entire memory state. Gno is deterministic, auto-persisted, and auto-Merkle-ized, allowing (smart contract) programs to be succinct, as the programmer doesn’t have to serialize and deserialize objects to persist them into a database (unlike programming applications with the Cosmos SDK).\n\n## How Gno Differs from Go\n\n![Gno and Go differences](static/img/gno-language/go-and-gno.jpg)\n\nThe composable nature of Go/Gno allows for type-checked interactions between contracts, making gno.land safer and more powerful, as well as operationally cheaper and faster. Smart contracts on gno.land are light, simple, more focused, and easily interoperable—a network of interconnected contracts rather than siloed monoliths that limit interactions with other contracts.\n\n![Example of Gno code](static/img/gno-language/code-example.jpg)\n\n## Gno Inherits Go’s Built-in Security Features\n\nGo supports secure programming through exported/non-exported fields, enabling a “least-authority” design. It is easy to create objects and APIs that expose only what should be accessible to callers while hiding what should not be simply by the capitalization of letters, thus allowing a succinct representation of secure logic that can be called by multiple users.\n\nAnother major advantage of Go is that the language comes with an ecosystem of great tooling, like the compiler and third-party tools that statically analyze code. Gno inherits these advantages from Go directly to create a smart contract programming language that provides embedding, composability, type-check safety, and garbage collection, helping developers to write secure code relying on the compiler, parser, and interpreter to give warning alerts for common mistakes.\n\n## Gno vs Solidity\n\nThe most widely-adopted smart contract language today is Ethereum’s EVM-compatible Solidity. With bytecode built from the ground up and Turing complete, Solidity opened up a world of possibilities for decentralized applications (dApps) and there are currently more than 10 million contracts deployed on Ethereum. However, Solidity provides limited tooling and its EVM has a stack limit and computational inefficiencies.\n\nSolidity is designed for one purpose only (writing smart contracts) and is bound by the limitations of the EVM. In addition, developers have to learn several languages if they want to understand the whole stack or work across different ecosystems. Gno aspires to exceed Solidity on multiple fronts (and other smart contract languages like CosmWasm or Substrate) as every part of the stack is written in Gno. It’s easy for developers to understand the entire system just by studying a relatively small code base.\n\n## Gno Is Essential for the Wider Adoption of Web3\n\nGno makes imports as easy as they are in web2 with runtime-based imports for seamless dependency flow comprehension, and support for complex structs, beyond primitive types. Gno is ultimately cost-effective as dependencies are loaded once, enabling remote function calls as local, and providing automatic and independent per-realm state persistence.\n\nUsing Gno, developers can rapidly accelerate application development and adopt a modular structure by reusing and reassembling existing modules without building from scratch. They can embed one structure inside another in an intuitive way while preserving localism, and the language specification is simple, successfully balancing practicality and minimalism.\n\nThe Go language is so well designed that the Gno smart contract system will become the new gold standard for smart contract development and other blockchain applications. As a programming language that is universally adopted, secure, composable, and complete, Gno is essential for the broader adoption of web3 and its sustainable growth.`\n\t)\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:25Z\", nil, nil)\n}\n"},{"name":"page_license.gno","body":"package gnopages\n\nfunc init() {\n\tvar (\n\t\tpath = \"license\"\n\t\ttitle = \"Gno Network General Public License\"\n\t\tbody = `Copyright (C) 2024 NewTendermint, LLC\n\nThis program is free software: you can redistribute it and/or modify it under\nthe terms of the GNO Network General Public License as published by\nNewTendermint, LLC, either version 4 of the License, or (at your option) any\nlater version published by NewTendermint, LLC.\n\nThis program is distributed in the hope that it will be useful, but is provided\nas-is and WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNO Network\nGeneral Public License for more details.\n\nYou should have received a copy of the GNO Network General Public License along\nwith this program. If not, see \u003chttps://gno.land/license\u003e.\n\nAttached below are the terms of the GNO Network General Public License, Version\n4 (a fork of the GNU Affero General Public License 3).\n\n## Additional Terms\n\n### Strong Attribution\n\nIf any of your user interfaces, such as websites and mobile applications, serve\nas the primary point of entry to a platform or blockchain that 1) offers users\nthe ability to upload their own smart contracts to the platform or blockchain,\nand 2) leverages any Covered Work (including the GNO virtual machine) to run\nthose smart contracts on the platform or blockchain (\"Applicable Work\"), then\nthe Applicable Work must prominently link to (1) gno.land or (2) any other URL\ndesignated by NewTendermint, LLC that has not been rejected by the governance of\nthe first chain known as gno.land, provided that the identity of the first chain\nis not ambiguous. In the event the identity of the first chain is ambiguous,\nthen NewTendermint, LLC's designation shall control. Such link must appear\nconspicuously in the header or footer of the Applicable Work, such that all\nusers may learn of gno.land or the URL designated by NewTendermint, LLC.\n\nThis additional attribution requirement shall remain in effect for (1) 7\nyears from the date of publication of the Applicable Work, or (2) 7 years from\nthe date of publication of the Covered Work (including republication of new\nversions), whichever is later, but no later than 12 years after the application\nof this strong attribution requirement to the publication of the Applicable\nWork. For purposes of this Strong Attribution requirement, Covered Work shall\nmean any work that is licensed under the GNO Network General Public License,\nVersion 4 or later, by NewTendermint, LLC.\n\n\n# GNO NETWORK GENERAL PUBLIC LICENSE\n\nVersion 4, 7 May 2024\n\nModified from the GNU AFFERO GENERAL PUBLIC LICENSE.\nGNU is not affiliated with GNO or NewTendermint, LLC.\nCopyright (C) 2022 NewTendermint, LLC.\n\n## Preamble\n\nThe GNO Network General Public License is a free, copyleft license for\nsoftware and other kinds of works, specifically designed to ensure\ncooperation with the community in the case of network server software.\n\nThe licenses for most software and other practical works are designed\nto take away your freedom to share and change the works. By contrast,\nour General Public Licenses are intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.\n\nWhen we speak of free software, we are referring to freedom, not\nprice. Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\nDevelopers that use our General Public Licenses protect your rights\nwith two steps: (1) assert copyright on the software, and (2) offer\nyou this License which gives you legal permission to copy, distribute\nand/or modify the software.\n\nA secondary benefit of defending all users' freedom is that\nimprovements made in alternate versions of the program, if they\nreceive widespread use, become available for other developers to\nincorporate. Many developers of free software are heartened and\nencouraged by the resulting cooperation. However, in the case of\nsoftware used on network servers, this result may fail to come about.\nThe GNU General Public License permits making a modified version and\nletting the public access it on a server without ever releasing its\nsource code to the public.\n\nThe GNO Network General Public License is designed specifically to\nensure that, in such cases, the modified source code becomes available\nto the community. It requires the operator of a network server to\nprovide the source code of the modified version running there to the\nusers of that server. Therefore, public use of a modified version, on\na publicly accessible server, gives the public access to the source\ncode of the modified version.\n\nThe precise terms and conditions for copying, distribution and\nmodification follow.\n\n## TERMS AND CONDITIONS\n\n### 0. Definitions.\n\n\"This License\" refers to version 4 of the GNO Network General Public License.\n\n\"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n\"The Program\" refers to any copyrightable work licensed under this\nLicense. Each licensee is addressed as \"you\". \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\nTo \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy. The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\nA \"covered work\" means either the unmodified Program or a work based\non the Program.\n\nTo \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy. Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\nTo \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies. Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\nAn interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License. If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n### 1. Source Code.\n\nThe \"source code\" for a work means the preferred form of the work\nfor making modifications to it. \"Object code\" means any non-source\nform of a work.\n\nA \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\nThe \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form. A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\nThe \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities. However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work. For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\nThe Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\nThe Corresponding Source for a work in source code form is that\nsame work.\n\n### 2. Basic Permissions.\n\nAll rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met. This License explicitly affirms your unlimited\npermission to run the unmodified Program. The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work. This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\nYou may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force. You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright. Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\nConveying under any other circumstances is permitted solely under\nthe conditions stated below. Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n### 3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\nNo covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\nWhen you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n### 4. Conveying Verbatim Copies.\n\nYou may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\nYou may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n### 5. Conveying Modified Source Versions.\n\nYou may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n- a) The work must carry prominent notices stating that you modified\n it, and giving a relevant date.\n- b) The work must carry prominent notices stating that it is\n released under this License and any conditions added under section\n 7. This requirement modifies the requirement in section 4 to\n \"keep intact all notices\".\n- c) You must license the entire work, as a whole, under this\n License to anyone who comes into possession of a copy. This\n License will therefore apply, along with any applicable section 7\n additional terms, to the whole of the work, and all its parts,\n regardless of how they are packaged. This License gives no\n permission to license the work in any other way, but it does not\n invalidate such permission if you have separately received it.\n- d) If the work has interactive user interfaces, each must display\n Appropriate Legal Notices; however, if the Program has interactive\n interfaces that do not display Appropriate Legal Notices, your\n work need not make them do so.\n\nA compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit. Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n### 6. Conveying Non-Source Forms.\n\n You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n- a) Convey the object code in, or embodied in, a physical product\n (including a physical distribution medium), accompanied by the\n Corresponding Source fixed on a durable physical medium\n customarily used for software interchange.\n- b) Convey the object code in, or embodied in, a physical product\n (including a physical distribution medium), accompanied by a\n written offer, valid for at least three years and valid for as\n long as you offer spare parts or customer support for that product\n model, to give anyone who possesses the object code either (1) a\n copy of the Corresponding Source for all the software in the\n product that is covered by this License, on a durable physical\n medium customarily used for software interchange, for a price no\n more than your reasonable cost of physically performing this\n conveying of source, or (2) access to copy the\n Corresponding Source from a network server at no charge.\n- c) Convey individual copies of the object code with a copy of the\n written offer to provide the Corresponding Source. This\n alternative is allowed only occasionally and noncommercially, and\n only if you received the object code with such an offer, in accord\n with subsection 6b.\n- d) Convey the object code by offering access from a designated\n place (gratis or for a charge), and offer equivalent access to the\n Corresponding Source in the same way through the same place at no\n further charge. You need not require recipients to copy the\n Corresponding Source along with the object code. If the place to\n copy the object code is a network server, the Corresponding Source\n may be on a different server (operated by you or a third party)\n that supports equivalent copying facilities, provided you maintain\n clear directions next to the object code saying where to find the\n Corresponding Source. Regardless of what server hosts the\n Corresponding Source, you remain obligated to ensure that it is\n available for as long as needed to satisfy these requirements.\n- e) Convey the object code using peer-to-peer transmission, provided\n you inform other peers where the object code and Corresponding\n Source of the work are being offered to the general public at no\n charge under subsection 6d.\n\nA separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\nA \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling. In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage. For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product. A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n\"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source. The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\nIf you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information. But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\nThe requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed. Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\nCorresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n### 7. Additional Terms.\n\n\"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law. If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\nWhen you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit. (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.) You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\nNotwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n- a) Disclaiming warranty or limiting liability differently from the\n terms of sections 15 and 16 of this License; or\n- b) Requiring preservation of specified reasonable legal notices or\n author attributions in that material or in the Appropriate Legal\n Notices displayed by works containing it; or\n- c) Prohibiting misrepresentation of the origin of that material, or\n requiring that modified versions of such material be marked in\n reasonable ways as different from the original version; or\n- d) Limiting the use for publicity purposes of names of licensors or\n authors of the material; or\n- e) Declining to grant rights under trademark law for use of some\n trade names, trademarks, or service marks; or\n- f) Requiring indemnification of licensors and authors of that\n material by anyone who conveys the material (or modified versions of\n it) with contractual assumptions of liability to the recipient, for\n any liability that these contractual assumptions directly impose on\n those licensors and authors; or\n- g) Requiring strong attribution such as notices on any user interfaces\n that run or convey any covered work, such as a prominent link to a URL\n on the header of a website, such that all users of the covered work may\n become aware of the notice, for a period no longer than 20 years.\n\nAll other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10. If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term. If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\nIf you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\nAdditional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n### 8. Termination.\n\nYou may not propagate or modify a covered work except as expressly\nprovided under this License. Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\nHowever, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\nMoreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\nTermination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License. If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n### 9. Acceptance Not Required for Having Copies.\n\nYou are not required to accept this License in order to receive or\nrun a copy of the Program. Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance. However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work. These actions infringe copyright if you do\nnot accept this License. Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n### 10. Automatic Licensing of Downstream Recipients.\n\nEach time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License. You are not responsible\nfor enforcing compliance by third parties with this License.\n\nAn \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations. If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\nYou may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License. For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n### 11. Patents.\n\nA \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based. The\nwork thus licensed is called the contributor's \"contributor version\".\n\nA contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version. For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\nEach contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\nIn the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement). To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\nIf you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients. \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\nIf, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\nA patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License. You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\nNothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n### 12. No Surrender of Others' Freedom.\n\nIf conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License. If you cannot convey a\ncovered work so as to simultaneously satisfy your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all. For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n### 13. Remote Network Interaction; Use with the GNU General Public License.\n\nNotwithstanding any other provision of this License, if you modify the\nProgram, your modified version must prominently offer all users\ninteracting with it remotely through a computer network (if your version\nsupports such interaction) an opportunity to receive the Corresponding\nSource of your version by providing access to the Corresponding Source\nfrom a network server at no charge, through some standard or customary\nmeans of facilitating copying of software. This Corresponding Source\nshall include the Corresponding Source for any work covered by version 3\nof the GNU General Public License that is incorporated pursuant to the\nfollowing paragraph.\n\nNotwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU General Public License into a single\ncombined work, and to convey the resulting work. The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the work with which it is combined will remain governed by version\n3 of the GNU General Public License.\n\n### 14. Revised Versions of this License.\n\nNewTendermint LLC may publish revised and/or new versions of\nthe GNO Network General Public License from time to time. Such new versions\nwill be similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\nEach version is given a distinguishing version number. If the\nProgram specifies that a certain numbered version of the GNO Network General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Gno Software\nFoundation. If the Program does not specify a version number of the\nGNO Network General Public License, you may choose any version ever published\nby NewTendermint LLC.\n\nIf the Program specifies that a proxy can decide which future\nversions of the GNO Network General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\nLater license versions may give you additional or different\npermissions. However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n### 15. Disclaimer of Warranty.\n\nTHERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n### 16. Limitation of Liability.\n\nIN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n### 17. Interpretation of Sections 15 and 16.\n\nIf the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\nEND OF TERMS AND CONDITIONS\n\n## How to Apply These Terms to Your New Programs\n\nIf you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\nTo do so, attach the following notices to the program. It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n \u003cone line to give the program's name and a brief idea of what it does.\u003e\n Copyright (C) \u003cyear\u003e \u003cname of author\u003e\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNO Network General Public License as published by\n NewTendermint LLC, either version 4 of the License, or\n (at your option) any later version.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNO Network General Public License for more details.\n\n You should have received a copy of the GNO Network General Public License\n along with this program. If not, see \u003chttps://gno.land/license\u003e.\n\nAlso add information on how to contact you by electronic and paper mail.\n\nIf your software can interact with users remotely through a computer\nnetwork, you should also make sure that it provides a way for users to\nget its source. For example, if your program is a web application, its\ninterface could display a \"Source\" link that leads users to an archive\nof the code. There are many ways you could offer source, and different\nsolutions will be better for different programs; see section 13 for the\nspecific requirements.\n`\n\t)\n\t_ = b.NewPost(\"\", path, title, body, \"2024-04-22T00:00:00Z\", nil, nil)\n}\n"},{"name":"page_partners.gno","body":"package gnopages\n\nfunc init() {\n\tpath := \"partners\"\n\ttitle := \"Partnerships\"\n\tbody := `### Fund and Grants Program\n\nAre you a builder, tinkerer, or researcher? If you’re looking to create awesome dApps, tooling, infrastructure, \nor smart contract libraries on gno.land, you can apply for a grant. The gno.land Ecosystem Fund and Grants program \nprovides financial contributions for individuals and teams to innovate on the platform.\n\nRead more about our Funds and Grants program [here](https://github.com/gnolang/ecosystem-fund-grants).\n`\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:27Z\", nil, nil)\n}\n"},{"name":"page_start.gno","body":"package gnopages\n\nfunc init() {\n\tpath := \"start\"\n\ttitle := \"Getting Started with Gno\"\n\t// XXX: description := \"\"\n\n\t// TODO: codegen to use README files here\n\n\t/* TODO: port previous message: This is a demo of Gno smart contract programming. This document was\n\tconstructed by Gno onto a smart contract hosted on the data Realm\n\tname [\"gno.land/r/demo/boards\"](https://gno.land/r/demo/boards/)\n\t([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)).\n\t*/\n\tbody := `## Getting Started with Gno\n\n- [Install Gno Key](/r/demo/boards:testboard/5)\n- TODO: add more links\n`\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:28Z\", nil, nil)\n}\n"},{"name":"page_testnets.gno","body":"package gnopages\n\nfunc init() {\n\tpath := \"testnets\"\n\ttitle := \"gno.land Testnet List\"\n\tbody := `\n- [Portal Loop](https://docs.gno.land/concepts/portal-loop) - a rolling testnet\n- [staging.gno.land](https://staging.gno.land) - wiped every commit to monorepo master\n- _[test4.gno.land](https://test4.gno.land) (latest)_\n\nFor a list of RPC endpoints, see the [reference documentation](https://docs.gno.land/reference/rpc-endpoints).\n\n## Local development\n\nSee the \"Getting started\" section in the [official documentation](https://docs.gno.land/getting-started/local-setup).\n`\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:29Z\", nil, nil)\n}\n"},{"name":"page_tokenomics.gno","body":"package gnopages\n\nfunc init() {\n\tvar (\n\t\tpath = \"tokenomics\"\n\t\ttitle = \"gno.land Tokenomics\"\n\t\t// XXX: description = \"\"\"\n\t\tbody = `Lorem Ipsum`\n\t)\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:30Z\", nil, nil)\n}\n"},{"name":"pages.gno","body":"package gnopages\n\nimport (\n\t\"gno.land/p/demo/blog\"\n)\n\n// TODO: switch from p/blog to p/pages\n\nvar b = \u0026blog.Blog{\n\tTitle: \"Gnoland's Pages\",\n\tPrefix: \"/r/gnoland/pages:\",\n\tNoBreadcrumb: true,\n}\n\nfunc Render(path string) string {\n\treturn b.Render(path)\n}\n"},{"name":"pages_test.gno","body":"package gnopages\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestHome(t *testing.T) {\n\tprintedOnce := false\n\tgot := Render(\"\")\n\texpectedSubtrings := []string{\n\t\t\"/r/gnoland/pages:p/tokenomics\",\n\t\t\"/r/gnoland/pages:p/start\",\n\t\t\"/r/gnoland/pages:p/contribute\",\n\t\t\"/r/gnoland/pages:p/about\",\n\t\t\"/r/gnoland/pages:p/gnolang\",\n\t}\n\tfor _, substring := range expectedSubtrings {\n\t\tif !strings.Contains(got, substring) {\n\t\t\tif !printedOnce {\n\t\t\t\tprintln(got)\n\t\t\t\tprintedOnce = true\n\t\t\t}\n\t\t\tt.Errorf(\"expected %q, but not found.\", substring)\n\t\t}\n\t}\n}\n\nfunc TestAbout(t *testing.T) {\n\tprintedOnce := false\n\tgot := Render(\"p/about\")\n\texpectedSubtrings := []string{\n\t\t\"gno.land Is A Platform To Write Smart Contracts In Gno\",\n\t\t\"gno.land is a next-generation smart contract platform using Gno, an interpreted version of the general-purpose Go\\nprogramming language.\",\n\t}\n\tfor _, substring := range expectedSubtrings {\n\t\tif !strings.Contains(got, substring) {\n\t\t\tif !printedOnce {\n\t\t\t\tprintln(got)\n\t\t\t\tprintedOnce = true\n\t\t\t}\n\t\t\tt.Errorf(\"expected %q, but not found.\", substring)\n\t\t}\n\t}\n}\n"},{"name":"util.gno","body":"package gnopages\n\nfunc checkErr(err error) {\n\tif err != nil {\n\t\tpanic(err)\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":"validators","path":"gno.land/r/sys/validators/v2","files":[{"name":"doc.gno","body":"// Package validators implements the on-chain validator set management through Proof of Contribution.\n// The Realm exposes only a public executor for govdao proposals, that can suggest validator set changes.\npackage validators\n"},{"name":"gnosdk.gno","body":"package validators\n\nimport (\n\t\"gno.land/p/sys/validators\"\n)\n\n// GetChanges returns the validator changes stored on the realm, since the given block number.\n// This function is intended to be called by gno.land through the GnoSDK\nfunc GetChanges(from int64) []validators.Validator {\n\tvalsetChanges := make([]validators.Validator, 0)\n\n\t// Gather the changes from the specified block\n\tchanges.Iterate(getBlockID(from), \"\", func(_ string, value interface{}) bool {\n\t\tchs := value.([]change)\n\n\t\tfor _, ch := range chs {\n\t\t\tvalsetChanges = append(valsetChanges, ch.validator)\n\t\t}\n\n\t\treturn false\n\t})\n\n\treturn valsetChanges\n}\n"},{"name":"init.gno","body":"package validators\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/nt/poa\"\n\t\"gno.land/p/sys/validators\"\n)\n\nfunc init() {\n\t// Prepare the initial validator set\n\tset := []validators.Validator{\n\t\t// core-val-1\n\t\t{\n\t\t\tAddress: std.Address(\"g1qn3jwvdpva622j3fyudqy65zstnqx2wnqhrs3s\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zpndqtjh5dcsnd0gcez3frs3w6rsttmlekj4cyywegyh0n8uprwvj5n8688\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// core-val-2\n\t\t{\n\t\t\tAddress: std.Address(\"g1gtu9czw9qavrtdnf936usvwjwyjz0x0jk243au\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zq4y0ppxhxazdwxhnsxxzdmh9rxht888n4fl0mskwcpq7y2404dm2h0lamk\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// core-val-3\n\t\t{\n\t\t\tAddress: std.Address(\"g19emxxnzzfa0pkffvthrss5drgccjnwj8mdme4f\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zq288fe7pd2yy3h2h8qjh0elu3pxuamf3wpa9qt9s6jja0r3k64ue4mh636\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// core-val-4\n\t\t{\n\t\t\tAddress: std.Address(\"g1hyxtsgjr5zt06jcx4z0xenn3u442ad2xgzu7lp\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zpy4mst534500z7k6xk5u7c9ex8zs44rjjhmxaxtw9zzjv82qkfhkhx2rfs\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// core-val-5\n\t\t{\n\t\t\tAddress: std.Address(\"g1l072ma0vfhx7s4vpevfvuxd6wzkv5ztt7gh99w\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zqtvz3g6nvu3d6wdz97w7jdw2sjc65us5u8gj8pm4mhasw7zxakjhjn9qkm\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// core-val-6\n\t\t{\n\t\t\tAddress: std.Address(\"g1uwqd3284kuzm56auwyc9d87jf3953tp9pnt506\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zp8xm09ura7mwyntee78cl64hgzq0x75f05tv7fkxpqvc797j37hsr3vgjg\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// berty-val-1\n\t\t{\n\t\t\tAddress: std.Address(\"g1ut590acnamvhkrh4qz6dz9zt9e3hyu499u0gvl\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zq2gncppkfzmx7s22mn60mf0uxzzpl23yx97hwmwm8yc6lupepqqnlexfch\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// onbloc-val-1\n\t\t{\n\t\t\tAddress: std.Address(\"g1arkzjfrte9l97v9q2qye07v0lw07039gaa3hfy\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zpkvdy7n9744qay76fzekpu9l6g3mp4hzhqjmp6k2as72ghlzc546ju3a09\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// onbloc-val-2\n\t\t{\n\t\t\tAddress: std.Address(\"g1x0m33nyne064xdx7tvlfcjwd4xkajjar6h523z\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zp6s70v4wurhg699w6f9emkwxdlm2eyf2uv64annj47npq85tjeucedmky9\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// devx-val-1\n\t\t{\n\t\t\tAddress: std.Address(\"g1mxguhd5zacar64txhfm0v7hhtph5wur5hx86vs\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zqz6fwulsygvu9xypka3zqxhkxllm467e3adphmj6y44vn3yy8qq34vxnse\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// devx-val-2\n\t\t{\n\t\t\tAddress: std.Address(\"g1t9ctfa468hn6czff8kazw08crazehcxaqa2uaa\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zpsq650w975vqsf6ajj5x4wdzfnrh64kmw7sljqz7wts6k0p6l36d0huls3\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// devx-val-3\n\t\t{\n\t\t\tAddress: std.Address(\"g1sll4rtvrepdyzcvg5ml0kjtl7fnwgcsxgg9s5q\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zplr4zg2smgha4n9huwcywm6pnkuny2x2j44kk4srxcf0rrmpql3035k8s2\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// devx-val-4\n\t\t{\n\t\t\tAddress: std.Address(\"g1aa5pp94eaextkump38766hpdra74xtfh805msv\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zqe85el3ardhel5vruywsdjw0vj2zjyhqhsyhcnuh0dy8xhuj8mxjg5h7uw\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// tori-val-1\n\t\t{\n\t\t\tAddress: std.Address(\"g1r2lwzu0y0na4686a0lz4f2zqxlffqkfm7lqqqp\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zq2quztlp2pffjsun3jeqyesru8rx9yc6tfj9na3hnw9qgn4zlrpul5mhd0\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// aib-val-1\n\t\t{\n\t\t\tAddress: std.Address(\"g1ecdu2gwz9d46srrhpu7k60pnrquvle5z2a5nn0\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zqnrer4hlsq7q22egx9ur357hg8ftsntyh4z2g7x69u2s4ay25vdw4tredd\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// aib-val-2\n\t\t{\n\t\t\tAddress: std.Address(\"g169wsuqlrscnvxtsu6wrc0zuwn39tmctw7q9f0q\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zpv6k4a2r6x6gt7eqp70l5vxluk9zkdmlqvkxztnc8zp2llq73e6ukxvsf6\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// aib-val-3\n\t\t{\n\t\t\tAddress: std.Address(\"g1hfwh3ufph3zczs5wu4qvpgtv79fzh30rgzdux8\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zqxx3qdzl9f6lee42vhtka5luujhxg22tesyww52af68f75zzp0snyhl8mw\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t}\n\n\t// The default valset protocol is PoA\n\tvp = poa.NewPoA(poa.WithInitialSet(set))\n\n\t// No changes to apply initially\n\tchanges = avl.NewTree()\n}\n"},{"name":"poc.gno","body":"package validators\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/sys/validators\"\n\t\"gno.land/r/gov/dao/bridge\"\n)\n\nconst errNoChangesProposed = \"no set changes proposed\"\n\n// NewPropExecutor creates a new executor that wraps a changes closure\n// proposal. This wrapper is required to ensure the GovDAO Realm actually\n// executed the callback.\n//\n// Concept adapted from:\n// https://github.com/gnolang/gno/pull/1945\nfunc NewPropExecutor(changesFn func() []validators.Validator) dao.Executor {\n\tif changesFn == nil {\n\t\tpanic(errNoChangesProposed)\n\t}\n\n\tcallback := func() error {\n\t\tfor _, change := range changesFn() {\n\t\t\tif change.VotingPower == 0 {\n\t\t\t\t// This change request is to remove the validator\n\t\t\t\tremoveValidator(change.Address)\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// This change request is to add the validator\n\t\t\taddValidator(change)\n\t\t}\n\n\t\treturn nil\n\t}\n\n\treturn bridge.GovDAO().NewGovDAOExecutor(callback)\n}\n\n// IsValidator returns a flag indicating if the given bech32 address\n// is part of the validator set\nfunc IsValidator(addr std.Address) bool {\n\treturn vp.IsValidator(addr)\n}\n\n// GetValidators returns the typed validator set\nfunc GetValidators() []validators.Validator {\n\treturn vp.GetValidators()\n}\n"},{"name":"validators.gno","body":"package validators\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/seqid\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/p/sys/validators\"\n)\n\nvar (\n\tvp validators.ValsetProtocol // p is the underlying validator set protocol\n\tchanges *avl.Tree // changes holds any valset changes; seqid(block number) -\u003e []change\n)\n\n// change represents a single valset change, tied to a specific block number\ntype change struct {\n\tblockNum int64 // the block number associated with the valset change\n\tvalidator validators.Validator // the validator update\n}\n\n// addValidator adds a new validator to the validator set.\n// If the validator is already present, the method errors out\nfunc addValidator(validator validators.Validator) {\n\tval, err := vp.AddValidator(validator.Address, validator.PubKey, validator.VotingPower)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Validator added, note the change\n\tch := change{\n\t\tblockNum: std.GetHeight(),\n\t\tvalidator: val,\n\t}\n\n\tsaveChange(ch)\n\n\t// Emit the validator set change\n\tstd.Emit(validators.ValidatorAddedEvent)\n}\n\n// removeValidator removes the given validator from the set.\n// If the validator is not present in the set, the method errors out\nfunc removeValidator(address std.Address) {\n\tval, err := vp.RemoveValidator(address)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Validator removed, note the change\n\tch := change{\n\t\tblockNum: std.GetHeight(),\n\t\tvalidator: validators.Validator{\n\t\t\tAddress: val.Address,\n\t\t\tPubKey: val.PubKey,\n\t\t\tVotingPower: 0, // nullified the voting power indicates removal\n\t\t},\n\t}\n\n\tsaveChange(ch)\n\n\t// Emit the validator set change\n\tstd.Emit(validators.ValidatorRemovedEvent)\n}\n\n// saveChange saves the valset change\nfunc saveChange(ch change) {\n\tid := getBlockID(ch.blockNum)\n\n\tsetRaw, exists := changes.Get(id)\n\tif !exists {\n\t\tchanges.Set(id, []change{ch})\n\n\t\treturn\n\t}\n\n\t// Save the change\n\tset := setRaw.([]change)\n\tset = append(set, ch)\n\n\tchanges.Set(id, set)\n}\n\n// getBlockID converts the block number to a sequential ID\nfunc getBlockID(blockNum int64) string {\n\treturn seqid.ID(uint64(blockNum)).String()\n}\n\nfunc Render(_ string) string {\n\tvar (\n\t\tsize = changes.Size()\n\t\tmaxDisplay = 10\n\t)\n\n\tif size == 0 {\n\t\treturn \"No valset changes to apply.\"\n\t}\n\n\toutput := \"Valset changes:\\n\"\n\tchanges.ReverseIterateByOffset(size-maxDisplay, maxDisplay, func(_ string, value interface{}) bool {\n\t\tchs := value.([]change)\n\n\t\tfor _, ch := range chs {\n\t\t\toutput += ufmt.Sprintf(\n\t\t\t\t\"- #%d: %s (%d)\\n\",\n\t\t\t\tch.blockNum,\n\t\t\t\tch.validator.Address.String(),\n\t\t\t\tch.validator.VotingPower,\n\t\t\t)\n\t\t}\n\n\t\treturn false\n\t})\n\n\treturn output\n}\n"},{"name":"validators_test.gno","body":"package validators\n\nimport (\n\t\"testing\"\n\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\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/sys/validators\"\n)\n\n// generateTestValidators generates a dummy validator set\nfunc generateTestValidators(count int) []validators.Validator {\n\tvals := make([]validators.Validator, 0, count)\n\n\tfor i := 0; i \u003c count; i++ {\n\t\tval := validators.Validator{\n\t\t\tAddress: testutils.TestAddress(ufmt.Sprintf(\"%d\", i)),\n\t\t\tPubKey: \"public-key\",\n\t\t\tVotingPower: 10,\n\t\t}\n\n\t\tvals = append(vals, val)\n\t}\n\n\treturn vals\n}\n\nfunc TestValidators_AddRemove(t *testing.T) {\n\t// Clear any changes\n\tchanges = avl.NewTree()\n\n\tvar (\n\t\tvals = generateTestValidators(100)\n\t\tinitialHeight = int64(123)\n\t)\n\n\t// Add in the validators\n\tfor _, val := range vals {\n\t\taddValidator(val)\n\n\t\t// Make sure the validator is added\n\t\tuassert.True(t, vp.IsValidator(val.Address))\n\n\t\tstd.TestSkipHeights(1)\n\t}\n\n\tfor i := initialHeight; i \u003c initialHeight+int64(len(vals)); i++ {\n\t\t// Make sure the changes are saved\n\t\tchs := GetChanges(i)\n\n\t\t// We use the funky index calculation to make sure\n\t\t// changes are properly handled for each block span\n\t\tuassert.Equal(t, initialHeight+int64(len(vals))-i, int64(len(chs)))\n\n\t\tfor index, val := range vals[i-initialHeight:] {\n\t\t\t// Make sure the changes are equal to the additions\n\t\t\tch := chs[index]\n\n\t\t\tuassert.Equal(t, val.Address, ch.Address)\n\t\t\tuassert.Equal(t, val.PubKey, ch.PubKey)\n\t\t\tuassert.Equal(t, val.VotingPower, ch.VotingPower)\n\t\t}\n\t}\n\n\t// Save the beginning height for the removal\n\tinitialRemoveHeight := std.GetHeight()\n\n\t// Clear any changes\n\tchanges = avl.NewTree()\n\n\t// Remove the validators\n\tfor _, val := range vals {\n\t\tremoveValidator(val.Address)\n\n\t\t// Make sure the validator is removed\n\t\tuassert.False(t, vp.IsValidator(val.Address))\n\n\t\tstd.TestSkipHeights(1)\n\t}\n\n\tfor i := initialRemoveHeight; i \u003c initialRemoveHeight+int64(len(vals)); i++ {\n\t\t// Make sure the changes are saved\n\t\tchs := GetChanges(i)\n\n\t\t// We use the funky index calculation to make sure\n\t\t// changes are properly handled for each block span\n\t\tuassert.Equal(t, initialRemoveHeight+int64(len(vals))-i, int64(len(chs)))\n\n\t\tfor index, val := range vals[i-initialRemoveHeight:] {\n\t\t\t// Make sure the changes are equal to the additions\n\t\t\tch := chs[index]\n\n\t\t\tuassert.Equal(t, val.Address, ch.Address)\n\t\t\tuassert.Equal(t, val.PubKey, ch.PubKey)\n\t\t\tuassert.Equal(t, uint64(0), ch.VotingPower)\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":"valopers","path":"gno.land/r/gnoland/valopers/v2","files":[{"name":"init.gno","body":"package valopers\n\nimport \"gno.land/p/demo/avl\"\n\nfunc init() {\n\tvalopers = avl.NewTree()\n}\n"},{"name":"valopers.gno","body":"// Package valopers is designed around the permissionless lifecycle of valoper profiles.\n// It also includes parts designed for govdao to propose valset changes based on registered valopers.\npackage valopers\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/ufmt\"\n\tpVals \"gno.land/p/sys/validators\"\n\t\"gno.land/r/gov/dao/bridge\"\n\tvalidators \"gno.land/r/sys/validators/v2\"\n)\n\nconst (\n\terrValoperExists = \"valoper already exists\"\n\terrValoperMissing = \"valoper does not exist\"\n\terrInvalidAddressUpdate = \"valoper updated address exists\"\n\terrValoperNotCaller = \"valoper is not the caller\"\n)\n\n// valopers keeps track of all the active validator operators\nvar valopers *avl.Tree // Address -\u003e Valoper\n\n// Valoper represents a validator operator profile\ntype Valoper struct {\n\tName string // the display name of the valoper\n\tMoniker string // the moniker of the valoper\n\tDescription string // the description of the valoper\n\n\tAddress std.Address // The bech32 gno address of the validator\n\tPubKey string // the bech32 public key of the validator\n\tP2PAddresses []string // the publicly reachable P2P addresses of the validator\n\tActive bool // flag indicating if the valoper is active\n}\n\n// Register registers a new valoper\nfunc Register(v Valoper) {\n\t// Check if the valoper is already registered\n\tif isValoper(v.Address) {\n\t\tpanic(errValoperExists)\n\t}\n\n\t// TODO add address derivation from public key\n\t// (when the laws of gno make it possible)\n\n\t// Save the valoper to the set\n\tvalopers.Set(v.Address.String(), v)\n}\n\n// Update updates an existing valoper\nfunc Update(address std.Address, v Valoper) {\n\t// Check if the valoper is present\n\tif !isValoper(address) {\n\t\tpanic(errValoperMissing)\n\t}\n\n\t// Check that the valoper wouldn't be\n\t// overwriting an existing one\n\tisAddressUpdate := address != v.Address\n\tif isAddressUpdate \u0026\u0026 isValoper(v.Address) {\n\t\tpanic(errInvalidAddressUpdate)\n\t}\n\n\t// Remove the old valoper info\n\t// in case the address changed\n\tif address != v.Address {\n\t\tvalopers.Remove(address.String())\n\t}\n\n\t// Save the new valoper info\n\tvalopers.Set(v.Address.String(), v)\n}\n\n// GetByAddr fetches the valoper using the address, if present\nfunc GetByAddr(address std.Address) Valoper {\n\tvaloperRaw, exists := valopers.Get(address.String())\n\tif !exists {\n\t\tpanic(errValoperMissing)\n\t}\n\n\treturn valoperRaw.(Valoper)\n}\n\n// Render renders the current valoper set\nfunc Render(_ string) string {\n\tif valopers.Size() == 0 {\n\t\treturn \"No valopers to display.\"\n\t}\n\n\toutput := \"Valset changes to apply:\\n\"\n\tvalopers.Iterate(\"\", \"\", func(_ string, value interface{}) bool {\n\t\tvaloper := value.(Valoper)\n\n\t\toutput += valoper.Render()\n\n\t\treturn false\n\t})\n\n\treturn output\n}\n\n// Render renders a single valoper with their information\nfunc (v Valoper) Render() string {\n\toutput := ufmt.Sprintf(\"## %s (%s)\\n\", v.Name, v.Moniker)\n\toutput += ufmt.Sprintf(\"%s\\n\\n\", v.Description)\n\toutput += ufmt.Sprintf(\"- Address: %s\\n\", v.Address.String())\n\toutput += ufmt.Sprintf(\"- PubKey: %s\\n\", v.PubKey)\n\toutput += \"- P2P Addresses: [\\n\"\n\n\tif len(v.P2PAddresses) == 0 {\n\t\toutput += \"]\\n\"\n\n\t\treturn output\n\t}\n\n\tfor index, addr := range v.P2PAddresses {\n\t\toutput += addr\n\n\t\tif index == len(v.P2PAddresses)-1 {\n\t\t\toutput += \"]\\n\"\n\n\t\t\tcontinue\n\t\t}\n\n\t\toutput += \",\\n\"\n\t}\n\n\treturn output\n}\n\n// isValoper checks if the valoper exists\nfunc isValoper(address std.Address) bool {\n\t_, exists := valopers.Get(address.String())\n\n\treturn exists\n}\n\n// GovDAOProposal creates a proposal to the GovDAO\n// for adding the given valoper to the validator set.\n// This function is meant to serve as a helper\n// for generating the govdao proposal\nfunc GovDAOProposal(address std.Address) {\n\tvar (\n\t\tvaloper = GetByAddr(address)\n\t\tvotingPower = uint64(1)\n\t)\n\n\t// Make sure the valoper is the caller\n\tif std.GetOrigCaller() != address {\n\t\tpanic(errValoperNotCaller)\n\t}\n\n\t// Determine the voting power\n\tif !valoper.Active {\n\t\tvotingPower = uint64(0)\n\t}\n\n\tchangesFn := func() []pVals.Validator {\n\t\treturn []pVals.Validator{\n\t\t\t{\n\t\t\t\tAddress: valoper.Address,\n\t\t\t\tPubKey: valoper.PubKey,\n\t\t\t\tVotingPower: votingPower,\n\t\t\t},\n\t\t}\n\t}\n\n\t// Create the executor\n\texecutor := validators.NewPropExecutor(changesFn)\n\n\t// Craft the proposal description\n\tdescription := ufmt.Sprintf(\n\t\t\"Add valoper %s (Address: %s; PubKey: %s) to the valset\",\n\t\tvaloper.Name,\n\t\tvaloper.Address.String(),\n\t\tvaloper.PubKey,\n\t)\n\n\tprop := dao.ProposalRequest{\n\t\tDescription: description,\n\t\tExecutor: executor,\n\t}\n\n\t// Create the govdao proposal\n\tbridge.GovDAO().Propose(prop)\n}\n"},{"name":"valopers_test.gno","body":"package valopers\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestValopers_Register(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"already a valoper\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Clear the set for the test\n\t\tvalopers = avl.NewTree()\n\n\t\tv := Valoper{\n\t\t\tAddress: testutils.TestAddress(\"valoper\"),\n\t\t}\n\n\t\t// Add the valoper\n\t\tvalopers.Set(v.Address.String(), v)\n\n\t\tuassert.PanicsWithMessage(t, errValoperExists, func() {\n\t\t\tRegister(v)\n\t\t})\n\t})\n\n\tt.Run(\"successful registration\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Clear the set for the test\n\t\tvalopers = avl.NewTree()\n\n\t\tv := Valoper{\n\t\t\tAddress: testutils.TestAddress(\"valoper\"),\n\t\t\tName: \"new valoper\",\n\t\t\tMoniker: \"val-1\",\n\t\t\tPubKey: \"pub key\",\n\t\t}\n\n\t\tuassert.NotPanics(t, func() {\n\t\t\tRegister(v)\n\t\t})\n\n\t\tuassert.NotPanics(t, func() {\n\t\t\tvaloper := GetByAddr(v.Address)\n\n\t\t\tuassert.Equal(t, v.Address, valoper.Address)\n\t\t\tuassert.Equal(t, v.Name, valoper.Name)\n\t\t\tuassert.Equal(t, v.Moniker, valoper.Moniker)\n\t\t\tuassert.Equal(t, v.PubKey, valoper.PubKey)\n\t\t})\n\t})\n}\n\nfunc TestValopers_Update(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"non-existing valoper\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Clear the set for the test\n\t\tvalopers = avl.NewTree()\n\n\t\tv := Valoper{}\n\n\t\t// Update the valoper\n\t\tuassert.PanicsWithMessage(t, errValoperMissing, func() {\n\t\t\tUpdate(v.Address, v)\n\t\t})\n\t})\n\n\tt.Run(\"overwrite valoper\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Clear the set for the test\n\t\tvalopers = avl.NewTree()\n\n\t\tone := Valoper{\n\t\t\tAddress: testutils.TestAddress(\"valoper 1\"),\n\t\t}\n\n\t\t// Add the valoper\n\t\tuassert.NotPanics(t, func() {\n\t\t\tRegister(one)\n\t\t})\n\n\t\tinitialAddress := testutils.TestAddress(\"valoper 2\")\n\t\ttwo := Valoper{\n\t\t\tAddress: initialAddress,\n\t\t}\n\n\t\t// Add the valoper\n\t\tuassert.NotPanics(t, func() {\n\t\t\tRegister(two)\n\t\t})\n\n\t\t// Update the valoper address\n\t\t// so it overlaps\n\t\ttwo = Valoper{\n\t\t\tAddress: one.Address,\n\t\t}\n\n\t\t// Update the valoper\n\t\tuassert.PanicsWithMessage(t, errInvalidAddressUpdate, func() {\n\t\t\tUpdate(initialAddress, two)\n\t\t})\n\t})\n\n\tt.Run(\"successful update\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Clear the set for the test\n\t\tvalopers = avl.NewTree()\n\n\t\tvar (\n\t\t\tname = \"new valoper\"\n\t\t\tv = Valoper{\n\t\t\t\tAddress: testutils.TestAddress(\"valoper\"),\n\t\t\t\tName: name,\n\t\t\t\tPubKey: \"pub key\",\n\t\t\t}\n\t\t)\n\n\t\t// Add the valoper\n\t\tuassert.NotPanics(t, func() {\n\t\t\tRegister(v)\n\t\t})\n\n\t\t// Update the valoper name\n\t\tv.Name = \"new name\"\n\t\tv.Active = false\n\n\t\t// Update the valoper\n\t\tuassert.NotPanics(t, func() {\n\t\t\tUpdate(v.Address, v)\n\t\t})\n\n\t\t// Make sure the valoper is updated\n\t\tuassert.NotPanics(t, func() {\n\t\t\tvaloper := GetByAddr(v.Address)\n\n\t\t\tuassert.Equal(t, v.Name, valoper.Name)\n\t\t\tuassert.Equal(t, v.Active, valoper.Active)\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":"valopers","path":"gno.land/r/gnoland/valopers/v2","files":[{"name":"init.gno","body":"package valopers\n\nimport \"gno.land/p/demo/avl\"\n\nfunc init() {\n\tvalopers = avl.NewTree()\n}\n"},{"name":"valopers.gno","body":"// Package valopers is designed around the permissionless lifecycle of valoper profiles.\n// It also includes parts designed for govdao to propose valset changes based on registered valopers.\npackage valopers\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/ufmt\"\n\tpVals \"gno.land/p/sys/validators\"\n\t\"gno.land/r/gov/dao/bridge\"\n\tvalidators \"gno.land/r/sys/validators/v2\"\n)\n\nconst (\n\terrValoperExists = \"valoper already exists\"\n\terrValoperMissing = \"valoper does not exist\"\n\terrInvalidAddressUpdate = \"valoper updated address exists\"\n\terrValoperNotCaller = \"valoper is not the caller\"\n)\n\n// valopers keeps track of all the active validator operators\nvar valopers *avl.Tree // Address -\u003e Valoper\n\n// Valoper represents a validator operator profile\ntype Valoper struct {\n\tName string // the display name of the valoper\n\tMoniker string // the moniker of the valoper\n\tDescription string // the description of the valoper\n\n\tAddress std.Address // The bech32 gno address of the validator\n\tPubKey string // the bech32 public key of the validator\n\tP2PAddresses []string // the publicly reachable P2P addresses of the validator\n\tActive bool // flag indicating if the valoper is active\n}\n\n// Register registers a new valoper\nfunc Register(v Valoper) {\n\t// Check if the valoper is already registered\n\tif isValoper(v.Address) {\n\t\tpanic(errValoperExists)\n\t}\n\n\t// TODO add address derivation from public key\n\t// (when the laws of gno make it possible)\n\n\t// Save the valoper to the set\n\tvalopers.Set(v.Address.String(), v)\n}\n\n// Update updates an existing valoper\nfunc Update(address std.Address, v Valoper) {\n\t// Check if the valoper is present\n\tif !isValoper(address) {\n\t\tpanic(errValoperMissing)\n\t}\n\n\t// Check that the valoper wouldn't be\n\t// overwriting an existing one\n\tisAddressUpdate := address != v.Address\n\tif isAddressUpdate \u0026\u0026 isValoper(v.Address) {\n\t\tpanic(errInvalidAddressUpdate)\n\t}\n\n\t// Remove the old valoper info\n\t// in case the address changed\n\tif address != v.Address {\n\t\tvalopers.Remove(address.String())\n\t}\n\n\t// Save the new valoper info\n\tvalopers.Set(v.Address.String(), v)\n}\n\n// GetByAddr fetches the valoper using the address, if present\nfunc GetByAddr(address std.Address) Valoper {\n\tvaloperRaw, exists := valopers.Get(address.String())\n\tif !exists {\n\t\tpanic(errValoperMissing)\n\t}\n\n\treturn valoperRaw.(Valoper)\n}\n\n// Render renders the current valoper set\nfunc Render(_ string) string {\n\tif valopers.Size() == 0 {\n\t\treturn \"No valopers to display.\"\n\t}\n\n\toutput := \"Valset changes to apply:\\n\"\n\tvalopers.Iterate(\"\", \"\", func(_ string, value interface{}) bool {\n\t\tvaloper := value.(Valoper)\n\n\t\toutput += valoper.Render()\n\n\t\treturn false\n\t})\n\n\treturn output\n}\n\n// Render renders a single valoper with their information\nfunc (v Valoper) Render() string {\n\toutput := ufmt.Sprintf(\"## %s (%s)\\n\", v.Name, v.Moniker)\n\toutput += ufmt.Sprintf(\"%s\\n\\n\", v.Description)\n\toutput += ufmt.Sprintf(\"- Address: %s\\n\", v.Address.String())\n\toutput += ufmt.Sprintf(\"- PubKey: %s\\n\", v.PubKey)\n\toutput += \"- P2P Addresses: [\\n\"\n\n\tif len(v.P2PAddresses) == 0 {\n\t\toutput += \"]\\n\"\n\n\t\treturn output\n\t}\n\n\tfor index, addr := range v.P2PAddresses {\n\t\toutput += addr\n\n\t\tif index == len(v.P2PAddresses)-1 {\n\t\t\toutput += \"]\\n\"\n\n\t\t\tcontinue\n\t\t}\n\n\t\toutput += \",\\n\"\n\t}\n\n\treturn output\n}\n\n// isValoper checks if the valoper exists\nfunc isValoper(address std.Address) bool {\n\t_, exists := valopers.Get(address.String())\n\n\treturn exists\n}\n\n// GovDAOProposal creates a proposal to the GovDAO\n// for adding the given valoper to the validator set.\n// This function is meant to serve as a helper\n// for generating the govdao proposal\nfunc GovDAOProposal(address std.Address) {\n\tvar (\n\t\tvaloper = GetByAddr(address)\n\t\tvotingPower = uint64(1)\n\t)\n\n\t// Make sure the valoper is the caller\n\tif std.OrigCaller() != address {\n\t\tpanic(errValoperNotCaller)\n\t}\n\n\t// Determine the voting power\n\tif !valoper.Active {\n\t\tvotingPower = uint64(0)\n\t}\n\n\tchangesFn := func() []pVals.Validator {\n\t\treturn []pVals.Validator{\n\t\t\t{\n\t\t\t\tAddress: valoper.Address,\n\t\t\t\tPubKey: valoper.PubKey,\n\t\t\t\tVotingPower: votingPower,\n\t\t\t},\n\t\t}\n\t}\n\n\t// Create the executor\n\texecutor := validators.NewPropExecutor(changesFn)\n\n\t// Craft the proposal description\n\tdescription := ufmt.Sprintf(\n\t\t\"Add valoper %s (Address: %s; PubKey: %s) to the valset\",\n\t\tvaloper.Name,\n\t\tvaloper.Address.String(),\n\t\tvaloper.PubKey,\n\t)\n\n\tprop := dao.ProposalRequest{\n\t\tDescription: description,\n\t\tExecutor: executor,\n\t}\n\n\t// Create the govdao proposal\n\tbridge.GovDAO().Propose(prop)\n}\n"},{"name":"valopers_test.gno","body":"package valopers\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestValopers_Register(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"already a valoper\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Clear the set for the test\n\t\tvalopers = avl.NewTree()\n\n\t\tv := Valoper{\n\t\t\tAddress: testutils.TestAddress(\"valoper\"),\n\t\t}\n\n\t\t// Add the valoper\n\t\tvalopers.Set(v.Address.String(), v)\n\n\t\tuassert.PanicsWithMessage(t, errValoperExists, func() {\n\t\t\tRegister(v)\n\t\t})\n\t})\n\n\tt.Run(\"successful registration\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Clear the set for the test\n\t\tvalopers = avl.NewTree()\n\n\t\tv := Valoper{\n\t\t\tAddress: testutils.TestAddress(\"valoper\"),\n\t\t\tName: \"new valoper\",\n\t\t\tMoniker: \"val-1\",\n\t\t\tPubKey: \"pub key\",\n\t\t}\n\n\t\tuassert.NotPanics(t, func() {\n\t\t\tRegister(v)\n\t\t})\n\n\t\tuassert.NotPanics(t, func() {\n\t\t\tvaloper := GetByAddr(v.Address)\n\n\t\t\tuassert.Equal(t, v.Address, valoper.Address)\n\t\t\tuassert.Equal(t, v.Name, valoper.Name)\n\t\t\tuassert.Equal(t, v.Moniker, valoper.Moniker)\n\t\t\tuassert.Equal(t, v.PubKey, valoper.PubKey)\n\t\t})\n\t})\n}\n\nfunc TestValopers_Update(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"non-existing valoper\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Clear the set for the test\n\t\tvalopers = avl.NewTree()\n\n\t\tv := Valoper{}\n\n\t\t// Update the valoper\n\t\tuassert.PanicsWithMessage(t, errValoperMissing, func() {\n\t\t\tUpdate(v.Address, v)\n\t\t})\n\t})\n\n\tt.Run(\"overwrite valoper\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Clear the set for the test\n\t\tvalopers = avl.NewTree()\n\n\t\tone := Valoper{\n\t\t\tAddress: testutils.TestAddress(\"valoper 1\"),\n\t\t}\n\n\t\t// Add the valoper\n\t\tuassert.NotPanics(t, func() {\n\t\t\tRegister(one)\n\t\t})\n\n\t\tinitialAddress := testutils.TestAddress(\"valoper 2\")\n\t\ttwo := Valoper{\n\t\t\tAddress: initialAddress,\n\t\t}\n\n\t\t// Add the valoper\n\t\tuassert.NotPanics(t, func() {\n\t\t\tRegister(two)\n\t\t})\n\n\t\t// Update the valoper address\n\t\t// so it overlaps\n\t\ttwo = Valoper{\n\t\t\tAddress: one.Address,\n\t\t}\n\n\t\t// Update the valoper\n\t\tuassert.PanicsWithMessage(t, errInvalidAddressUpdate, func() {\n\t\t\tUpdate(initialAddress, two)\n\t\t})\n\t})\n\n\tt.Run(\"successful update\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Clear the set for the test\n\t\tvalopers = avl.NewTree()\n\n\t\tvar (\n\t\t\tname = \"new valoper\"\n\t\t\tv = Valoper{\n\t\t\t\tAddress: testutils.TestAddress(\"valoper\"),\n\t\t\t\tName: name,\n\t\t\t\tPubKey: \"pub key\",\n\t\t\t}\n\t\t)\n\n\t\t// Add the valoper\n\t\tuassert.NotPanics(t, func() {\n\t\t\tRegister(v)\n\t\t})\n\n\t\t// Update the valoper name\n\t\tv.Name = \"new name\"\n\t\tv.Active = false\n\n\t\t// Update the valoper\n\t\tuassert.NotPanics(t, func() {\n\t\t\tUpdate(v.Address, v)\n\t\t})\n\n\t\t// Make sure the valoper is updated\n\t\tuassert.NotPanics(t, func() {\n\t\t\tvaloper := GetByAddr(v.Address)\n\n\t\t\tuassert.Equal(t, v.Name, valoper.Name)\n\t\t\tuassert.Equal(t, v.Active, valoper.Active)\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":"config","path":"gno.land/r/leon/config","files":[{"name":"config.gno","body":"package config\n\nimport (\n\t\"errors\"\n\t\"std\"\n)\n\nvar (\n\tmain std.Address // leon's main address\n\tbackup std.Address // backup address\n\n\tErrInvalidAddr = errors.New(\"leon's config: invalid address\")\n\tErrUnauthorized = errors.New(\"leon's config: unauthorized\")\n)\n\nfunc init() {\n\tmain = \"g125em6arxsnj49vx35f0n0z34putv5ty3376fg5\"\n}\n\nfunc Address() std.Address {\n\treturn main\n}\n\nfunc Backup() std.Address {\n\treturn backup\n}\n\nfunc SetAddress(a std.Address) error {\n\tif !a.IsValid() {\n\t\treturn ErrInvalidAddr\n\t}\n\n\tif err := checkAuthorized(); err != nil {\n\t\treturn err\n\t}\n\n\tmain = a\n\treturn nil\n}\n\nfunc SetBackup(a std.Address) error {\n\tif !a.IsValid() {\n\t\treturn ErrInvalidAddr\n\t}\n\n\tif err := checkAuthorized(); err != nil {\n\t\treturn err\n\t}\n\n\tbackup = a\n\treturn nil\n}\n\nfunc checkAuthorized() error {\n\tcaller := std.PrevRealm().Addr()\n\tisAuthorized := caller == main || caller == backup\n\n\tif !isAuthorized {\n\t\treturn ErrUnauthorized\n\t}\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_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"home","path":"gno.land/r/leon/home","files":[{"name":"home.gno","body":"package home\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/ufmt\"\n\n\t\"gno.land/r/demo/art/gnoface\"\n\t\"gno.land/r/demo/art/millipede\"\n\t\"gno.land/r/leon/config\"\n)\n\nvar (\n\tpfp string // link to profile picture\n\tpfpCaption string // profile picture caption\n\tabtMe [2]string\n)\n\nfunc init() {\n\tpfp = \"https://i.imgflip.com/91vskx.jpg\"\n\tpfpCaption = \"[My favourite painting \u0026 pfp](https://en.wikipedia.org/wiki/Wanderer_above_the_Sea_of_Fog)\"\n\tabtMe = [2]string{\n\t\t`### About me\nHi, I'm Leon, a DevRel Engineer at gno.land. I am a tech enthusiast, \nlife-long learner, and sharer of knowledge.`,\n\t\t`### Contributions\nMy contributions to gno.land can mainly be found \n[here](https://github.com/gnolang/gno/issues?q=sort:updated-desc+author:leohhhn).\n\nTODO import r/gh\n`,\n\t}\n}\n\nfunc UpdatePFP(url, caption string) {\n\tif !isAuthorized(std.PrevRealm().Addr()) {\n\t\tpanic(config.ErrUnauthorized)\n\t}\n\n\tpfp = url\n\tpfpCaption = caption\n}\n\nfunc UpdateAboutMe(col1, col2 string) {\n\tif !isAuthorized(std.PrevRealm().Addr()) {\n\t\tpanic(config.ErrUnauthorized)\n\t}\n\n\tabtMe[0] = col1\n\tabtMe[1] = col2\n}\n\nfunc Render(path string) string {\n\tout := \"# Leon's Homepage\\n\\n\"\n\n\tout += renderAboutMe()\n\tout += renderBlogPosts()\n\tout += \"\\n\\n\"\n\tout += renderArt()\n\n\treturn out\n}\n\nfunc renderBlogPosts() string {\n\tout := \"\"\n\t//out += \"## Leon's Blog Posts\"\n\n\t// todo fetch blog posts authored by @leohhhn\n\t// and render them\n\treturn out\n}\n\nfunc renderAboutMe() string {\n\tout := \"\u003cdiv class='columns-3'\u003e\"\n\n\tout += \"\u003cdiv\u003e\\n\\n\"\n\tout += ufmt.Sprintf(\"![my profile pic](%s)\\n\\n%s\\n\\n\", pfp, pfpCaption)\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\tout += \"\u003cdiv\u003e\\n\\n\"\n\tout += abtMe[0] + \"\\n\\n\"\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\tout += \"\u003cdiv\u003e\\n\\n\"\n\tout += abtMe[1] + \"\\n\\n\"\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\tout += \"\u003c/div\u003e\u003c!-- /columns-3 --\u003e\\n\\n\"\n\n\treturn out\n}\n\nfunc renderArt() string {\n\tout := `\u003cdiv class=\"jumbotron\"\u003e` + \"\\n\\n\"\n\tout += \"# Gno Art\\n\\n\"\n\n\tout += \"\u003cdiv class='columns-3'\u003e\"\n\n\tout += renderGnoFace()\n\tout += renderMillipede()\n\tout += \"Empty spot :/\"\n\n\tout += \"\u003c/div\u003e\u003c!-- /columns-3 --\u003e\\n\\n\"\n\n\tout += \"This art is dynamic; it will change with every new block.\\n\\n\"\n\tout += `\u003c/div\u003e\u003c!-- /jumbotron --\u003e` + \"\\n\"\n\n\treturn out\n}\n\nfunc renderGnoFace() string {\n\tout := \"\u003cdiv\u003e\\n\\n\"\n\tout += gnoface.Render(strconv.Itoa(int(std.GetHeight())))\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\treturn out\n}\n\nfunc renderMillipede() string {\n\tout := \"\u003cdiv\u003e\\n\\n\"\n\tout += \"Millipede\\n\\n\"\n\tout += \"```\\n\" + millipede.Draw(int(std.GetHeight())%10+1) + \"```\\n\"\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\treturn out\n}\n\nfunc isAuthorized(addr std.Address) bool {\n\treturn addr == config.Address() || addr == config.Backup()\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":"config","path":"gno.land/r/manfred/config","files":[{"name":"config.gno","body":"package config\n\nimport \"std\"\n\nvar addr = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc Addr() std.Address {\n\treturn addr\n}\n\nfunc UpdateAddr(newAddr std.Address) {\n\tAssertIsAdmin()\n\taddr = newAddr\n}\n\nfunc AssertIsAdmin() {\n\tif std.GetOrigCaller() != addr {\n\t\tpanic(\"restricted area\")\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":"config","path":"gno.land/r/manfred/config","files":[{"name":"config.gno","body":"package config\n\nimport \"std\"\n\nvar addr = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc Addr() std.Address {\n\treturn addr\n}\n\nfunc UpdateAddr(newAddr std.Address) {\n\tAssertIsAdmin()\n\taddr = newAddr\n}\n\nfunc AssertIsAdmin() {\n\tif std.OrigCaller() != addr {\n\t\tpanic(\"restricted area\")\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":"home","path":"gno.land/r/manfred/home","files":[{"name":"home.gno","body":"package home\n\nimport \"gno.land/r/manfred/config\"\n\nvar (\n\ttodos []string\n\tstatus string\n\tmemeImgURL string\n)\n\nfunc init() {\n\ttodos = append(todos, \"fill this todo list...\")\n\tstatus = \"Online\" // Initial status set to \"Online\"\n\tmemeImgURL = \"https://i.imgflip.com/7ze8dc.jpg\"\n}\n\nfunc Render(path string) string {\n\tcontent := \"# Manfred's (gn)home Dashboard\\n\\n\"\n\n\tcontent += \"## Meme\\n\"\n\tcontent += \"![](\" + memeImgURL + \")\\n\\n\"\n\n\tcontent += \"## Status\\n\"\n\tcontent += status + \"\\n\\n\"\n\n\tcontent += \"## Personal ToDo List\\n\"\n\tfor _, todo := range todos {\n\t\tcontent += \"- [ ] \" + todo + \"\\n\"\n\t}\n\tcontent += \"\\n\"\n\n\t// TODO: Implement a feature to list replies on r/boards on my posts\n\t// TODO: Maybe integrate a calendar feature for upcoming events?\n\n\treturn content\n}\n\nfunc AddNewTodo(todo string) {\n\tconfig.AssertIsAdmin()\n\ttodos = append(todos, todo)\n}\n\nfunc DeleteTodo(todoIndex int) {\n\tconfig.AssertIsAdmin()\n\tif todoIndex \u003e= 0 \u0026\u0026 todoIndex \u003c len(todos) {\n\t\t// Remove the todo from the list by merging slices from before and after the todo\n\t\ttodos = append(todos[:todoIndex], todos[todoIndex+1:]...)\n\t} else {\n\t\tpanic(\"Invalid todo index\")\n\t}\n}\n\nfunc UpdateStatus(newStatus string) {\n\tconfig.AssertIsAdmin()\n\tstatus = newStatus\n}\n"},{"name":"z1_filetest.gno","body":"package main\n\nimport \"gno.land/r/manfred/home\"\n\nfunc main() {\n\tprintln(home.Render(\"\"))\n}\n\n// Output:\n// # Manfred's (gn)home Dashboard\n//\n// ## Meme\n// ![](https://i.imgflip.com/7ze8dc.jpg)\n//\n// ## Status\n// Online\n//\n// ## Personal ToDo List\n// - [ ] fill this todo list...\n"},{"name":"z2_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/manfred/home\"\n)\n\nfunc main() {\n\tstd.TestSetOrigCaller(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\thome.AddNewTodo(\"aaa\")\n\thome.AddNewTodo(\"bbb\")\n\thome.AddNewTodo(\"ccc\")\n\thome.AddNewTodo(\"ddd\")\n\thome.AddNewTodo(\"eee\")\n\thome.UpdateStatus(\"Lorem Ipsum\")\n\thome.DeleteTodo(3)\n\tprintln(home.Render(\"\"))\n}\n\n// Output:\n// # Manfred's (gn)home Dashboard\n//\n// ## Meme\n// ![](https://i.imgflip.com/7ze8dc.jpg)\n//\n// ## Status\n// Lorem Ipsum\n//\n// ## Personal ToDo List\n// - [ ] fill this todo list...\n// - [ ] aaa\n// - [ ] bbb\n// - [ ] ddd\n// - [ ] eee\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":"present","path":"gno.land/r/manfred/present","files":[{"name":"admin.gno","body":"package present\n\nimport (\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\nvar (\n\tadminAddr std.Address\n\tmoderatorList avl.Tree\n\tinPause bool\n)\n\nfunc init() {\n\t// adminAddr = std.GetOrigCaller() // FIXME: find a way to use this from the main's genesis.\n\tadminAddr = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"\n}\n\nfunc AdminSetAdminAddr(addr std.Address) {\n\tassertIsAdmin()\n\tadminAddr = addr\n}\n\nfunc AdminSetInPause(state bool) {\n\tassertIsAdmin()\n\tinPause = state\n}\n\nfunc AdminAddModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), true)\n}\n\nfunc AdminRemoveModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), false) // XXX: delete instead?\n}\n\nfunc ModAddPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\tcaller := std.GetOrigCaller()\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.NewPost(caller, slug, title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc ModEditPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.GetPost(slug).Update(title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc isAdmin(addr std.Address) bool {\n\treturn addr == adminAddr\n}\n\nfunc isModerator(addr std.Address) bool {\n\t_, found := moderatorList.Get(addr.String())\n\treturn found\n}\n\nfunc assertIsAdmin() {\n\tcaller := std.GetOrigCaller()\n\tif !isAdmin(caller) {\n\t\tpanic(\"access restricted.\")\n\t}\n}\n\nfunc assertIsModerator() {\n\tcaller := std.GetOrigCaller()\n\tif isAdmin(caller) || isModerator(caller) {\n\t\treturn\n\t}\n\tpanic(\"access restricted\")\n}\n\nfunc assertNotInPause() {\n\tif inPause {\n\t\tpanic(\"access restricted (pause)\")\n\t}\n}\n\nfunc checkErr(err error) {\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n"},{"name":"present_miami23.gno","body":"package present\n\nfunc init() {\n\tpath := \"miami23\"\n\ttitle := \"Portal Loop Demo (Miami 2023)\"\n\tbody := `\nRendered by Gno.\n\n[Source (WIP)](https://github.com/gnolang/gno/pull/1176)\n\n## Portal Loop\n\n- DONE: Dynamic homepage, key pages, aliases, and redirects.\n- TODO: Deploy with history, complete worxdao v0.\n- Will replace the static gno.land site.\n- Enhances local development.\n\n[GitHub Issue](https://github.com/gnolang/gno/issues/1108)\n\n## Roadmap\n\n- Crafting the roadmap this week, open to collaboration.\n- Combining onchain (portal loop) and offchain (GitHub).\n- Next week: Unveiling the official v0 roadmap.\n\n## Teams, DAOs, Projects\n\n- Developing worxDAO contracts for directories of projects and teams.\n- GitHub teams and projects align with this structure.\n- CODEOWNER file updates coming.\n- Initial teams announced next week.\n\n## Tech Team Retreat Plan\n\n- Continue Portal Loop.\n- Consider dApp development.\n- Explore new topics [here](https://github.com/orgs/gnolang/projects/15/).\n- Engage in workshops.\n- Connect and have fun with colleagues.\n`\n\t_ = b.NewPost(adminAddr, path, title, body, \"2023-10-15T13:17:24Z\", []string{\"moul\"}, []string{\"demo\", \"portal-loop\", \"miami\"})\n}\n"},{"name":"present_miami23_filetest.gno","body":"package main\n\nimport (\n\t\"gno.land/r/manfred/present\"\n)\n\nfunc main() {\n\tprintln(present.Render(\"\"))\n\tprintln(\"------------------------------------\")\n\tprintln(present.Render(\"p/miami23\"))\n}\n"},{"name":"presentations.gno","body":"package present\n\nimport (\n\t\"gno.land/p/demo/blog\"\n)\n\n// TODO: switch from p/blog to p/present\n\nvar b = \u0026blog.Blog{\n\tTitle: \"Manfred's Presentations\",\n\tPrefix: \"/r/manfred/present:\",\n\tNoBreadcrumb: true,\n}\n\nfunc Render(path string) string {\n\treturn b.Render(path)\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":"present","path":"gno.land/r/manfred/present","files":[{"name":"admin.gno","body":"package present\n\nimport (\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\nvar (\n\tadminAddr std.Address\n\tmoderatorList avl.Tree\n\tinPause bool\n)\n\nfunc init() {\n\t// adminAddr = std.OrigCaller() // FIXME: find a way to use this from the main's genesis.\n\tadminAddr = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"\n}\n\nfunc AdminSetAdminAddr(addr std.Address) {\n\tassertIsAdmin()\n\tadminAddr = addr\n}\n\nfunc AdminSetInPause(state bool) {\n\tassertIsAdmin()\n\tinPause = state\n}\n\nfunc AdminAddModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), true)\n}\n\nfunc AdminRemoveModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), false) // XXX: delete instead?\n}\n\nfunc ModAddPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\tcaller := std.OrigCaller()\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.NewPost(caller, slug, title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc ModEditPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.GetPost(slug).Update(title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc isAdmin(addr std.Address) bool {\n\treturn addr == adminAddr\n}\n\nfunc isModerator(addr std.Address) bool {\n\t_, found := moderatorList.Get(addr.String())\n\treturn found\n}\n\nfunc assertIsAdmin() {\n\tcaller := std.OrigCaller()\n\tif !isAdmin(caller) {\n\t\tpanic(\"access restricted.\")\n\t}\n}\n\nfunc assertIsModerator() {\n\tcaller := std.OrigCaller()\n\tif isAdmin(caller) || isModerator(caller) {\n\t\treturn\n\t}\n\tpanic(\"access restricted\")\n}\n\nfunc assertNotInPause() {\n\tif inPause {\n\t\tpanic(\"access restricted (pause)\")\n\t}\n}\n\nfunc checkErr(err error) {\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n"},{"name":"present_miami23.gno","body":"package present\n\nfunc init() {\n\tpath := \"miami23\"\n\ttitle := \"Portal Loop Demo (Miami 2023)\"\n\tbody := `\nRendered by Gno.\n\n[Source (WIP)](https://github.com/gnolang/gno/pull/1176)\n\n## Portal Loop\n\n- DONE: Dynamic homepage, key pages, aliases, and redirects.\n- TODO: Deploy with history, complete worxdao v0.\n- Will replace the static gno.land site.\n- Enhances local development.\n\n[GitHub Issue](https://github.com/gnolang/gno/issues/1108)\n\n## Roadmap\n\n- Crafting the roadmap this week, open to collaboration.\n- Combining onchain (portal loop) and offchain (GitHub).\n- Next week: Unveiling the official v0 roadmap.\n\n## Teams, DAOs, Projects\n\n- Developing worxDAO contracts for directories of projects and teams.\n- GitHub teams and projects align with this structure.\n- CODEOWNER file updates coming.\n- Initial teams announced next week.\n\n## Tech Team Retreat Plan\n\n- Continue Portal Loop.\n- Consider dApp development.\n- Explore new topics [here](https://github.com/orgs/gnolang/projects/15/).\n- Engage in workshops.\n- Connect and have fun with colleagues.\n`\n\t_ = b.NewPost(adminAddr, path, title, body, \"2023-10-15T13:17:24Z\", []string{\"moul\"}, []string{\"demo\", \"portal-loop\", \"miami\"})\n}\n"},{"name":"present_miami23_filetest.gno","body":"package main\n\nimport (\n\t\"gno.land/r/manfred/present\"\n)\n\nfunc main() {\n\tprintln(present.Render(\"\"))\n\tprintln(\"------------------------------------\")\n\tprintln(present.Render(\"p/miami23\"))\n}\n"},{"name":"presentations.gno","body":"package present\n\nimport (\n\t\"gno.land/p/demo/blog\"\n)\n\n// TODO: switch from p/blog to p/present\n\nvar b = \u0026blog.Blog{\n\tTitle: \"Manfred's Presentations\",\n\tPrefix: \"/r/manfred/present:\",\n\tNoBreadcrumb: true,\n}\n\nfunc Render(path string) string {\n\treturn b.Render(path)\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":"guestbook","path":"gno.land/r/morgan/guestbook","files":[{"name":"admin.gno","body":"package guestbook\n\nimport (\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/seqid\"\n)\n\nvar owner = ownable.New()\n\n// AdminDelete removes the guestbook message with the given ID.\n// The user will still be marked as having submitted a message, so they\n// won't be able to re-submit a new message.\nfunc AdminDelete(signatureID string) {\n\towner.AssertCallerIsOwner()\n\n\tid, err := seqid.FromString(signatureID)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tidb := id.Binary()\n\tif !guestbook.Has(idb) {\n\t\tpanic(\"signature does not exist\")\n\t}\n\tguestbook.Remove(idb)\n}\n"},{"name":"guestbook.gno","body":"// Realm guestbook contains an implementation of a simple guestbook.\n// Come and sign yourself up!\npackage guestbook\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\t\"unicode\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/seqid\"\n)\n\n// Signature is a single entry in the guestbook.\ntype Signature struct {\n\tMessage string\n\tAuthor std.Address\n\tTime time.Time\n}\n\nconst (\n\tmaxMessageLength = 140\n\tmaxPerPage = 25\n)\n\nvar (\n\tsignatureID seqid.ID\n\tguestbook avl.Tree // id -\u003e Signature\n\thasSigned avl.Tree // address -\u003e struct{}\n)\n\nfunc init() {\n\tSign(\"You reached the end of the guestbook!\")\n}\n\nconst (\n\terrNotAUser = \"this guestbook can only be signed by users\"\n\terrAlreadySigned = \"you already signed the guestbook!\"\n\terrInvalidCharacterInMessage = \"invalid character in message\"\n)\n\n// Sign signs the guestbook, with the specified message.\nfunc Sign(message string) {\n\tprev := std.PrevRealm()\n\tswitch {\n\tcase !prev.IsUser():\n\t\tpanic(errNotAUser)\n\tcase hasSigned.Has(prev.Addr().String()):\n\t\tpanic(errAlreadySigned)\n\t}\n\tmessage = validateMessage(message)\n\n\tguestbook.Set(signatureID.Next().Binary(), Signature{\n\t\tMessage: message,\n\t\tAuthor: prev.Addr(),\n\t\t// NOTE: time.Now() will yield the \"block time\", which is deterministic.\n\t\tTime: time.Now(),\n\t})\n\thasSigned.Set(prev.Addr().String(), struct{}{})\n}\n\nfunc validateMessage(msg string) string {\n\tif len(msg) \u003e maxMessageLength {\n\t\tpanic(\"Keep it brief! (max \" + strconv.Itoa(maxMessageLength) + \" bytes!)\")\n\t}\n\tout := \"\"\n\tfor _, ch := range msg {\n\t\tswitch {\n\t\tcase unicode.IsLetter(ch),\n\t\t\tunicode.IsNumber(ch),\n\t\t\tunicode.IsSpace(ch),\n\t\t\tunicode.IsPunct(ch):\n\t\t\tout += string(ch)\n\t\tdefault:\n\t\t\tpanic(errInvalidCharacterInMessage)\n\t\t}\n\t}\n\treturn out\n}\n\nfunc Render(maxID string) string {\n\tvar bld strings.Builder\n\n\tbld.WriteString(\"# Guestbook 📝\\n\\n[Come sign the guestbook!](./guestbook$help\u0026func=Sign)\\n\\n---\\n\\n\")\n\n\tvar maxIDBinary string\n\tif maxID != \"\" {\n\t\tmid, err := seqid.FromString(maxID)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\t// AVL iteration is exclusive, so we need to decrease the ID value to get the \"true\" maximum.\n\t\tmid--\n\t\tmaxIDBinary = mid.Binary()\n\t}\n\n\tvar lastID seqid.ID\n\tvar printed int\n\tguestbook.ReverseIterate(\"\", maxIDBinary, func(key string, val interface{}) bool {\n\t\tsig := val.(Signature)\n\t\tmessage := strings.ReplaceAll(sig.Message, \"\\n\", \"\\n\u003e \")\n\t\tbld.WriteString(\"\u003e \" + message + \"\\n\u003e\\n\")\n\t\tidValue, ok := seqid.FromBinary(key)\n\t\tif !ok {\n\t\t\tpanic(\"invalid seqid id\")\n\t\t}\n\n\t\tbld.WriteString(\"\u003e _Written by \" + sig.Author.String() + \" at \" + sig.Time.Format(time.DateTime) + \"_ (#\" + idValue.String() + \")\\n\\n---\\n\\n\")\n\t\tlastID = idValue\n\n\t\tprinted++\n\t\t// stop after exceeding limit\n\t\treturn printed \u003e= maxPerPage\n\t})\n\n\tif printed == 0 {\n\t\tbld.WriteString(\"No messages!\")\n\t} else if printed \u003e= maxPerPage {\n\t\tbld.WriteString(\"\u003cp style='text-align:right'\u003e\u003ca href='./guestbook:\" + lastID.String() + \"'\u003eNext page\u003c/a\u003e\u003c/p\u003e\")\n\t}\n\n\treturn bld.String()\n}\n"},{"name":"guestbook_test.gno","body":"package guestbook\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n)\n\nfunc TestSign(t *testing.T) {\n\tguestbook = avl.Tree{}\n\thasSigned = avl.Tree{}\n\n\tstd.TestSetRealm(std.NewUserRealm(\"g1user\"))\n\tSign(\"Hello!\")\n\n\tstd.TestSetRealm(std.NewUserRealm(\"g1user2\"))\n\tSign(\"Hello2!\")\n\n\tres := Render(\"\")\n\tt.Log(res)\n\tif !strings.Contains(res, \"\u003e Hello!\\n\u003e\\n\u003e _Written by g1user \") {\n\t\tt.Error(\"does not contain first user's message\")\n\t}\n\tif !strings.Contains(res, \"\u003e Hello2!\\n\u003e\\n\u003e _Written by g1user2 \") {\n\t\tt.Error(\"does not contain second user's message\")\n\t}\n\tif guestbook.Size() != 2 {\n\t\tt.Error(\"invalid guestbook size\")\n\t}\n}\n\nfunc TestSign_FromRealm(t *testing.T) {\n\tstd.TestSetRealm(std.NewCodeRealm(\"gno.land/r/demo/users\"))\n\n\tdefer func() {\n\t\trec := recover()\n\t\tif rec == nil {\n\t\t\tt.Fatal(\"expected panic\")\n\t\t}\n\t\trecString, ok := rec.(string)\n\t\tif !ok {\n\t\t\tt.Fatal(\"not a string\", rec)\n\t\t} else if recString != errNotAUser {\n\t\t\tt.Fatal(\"invalid error\", recString)\n\t\t}\n\t}()\n\tSign(\"Hey!\")\n}\n\nfunc TestSign_Double(t *testing.T) {\n\t// Should not allow signing twice.\n\tguestbook = avl.Tree{}\n\thasSigned = avl.Tree{}\n\n\tstd.TestSetRealm(std.NewUserRealm(\"g1user\"))\n\tSign(\"Hello!\")\n\n\tdefer func() {\n\t\trec := recover()\n\t\tif rec == nil {\n\t\t\tt.Fatal(\"expected panic\")\n\t\t}\n\t\trecString, ok := rec.(string)\n\t\tif !ok {\n\t\t\tt.Error(\"type assertion failed\", rec)\n\t\t} else if recString != errAlreadySigned {\n\t\t\tt.Error(\"invalid error message\", recString)\n\t\t}\n\t}()\n\n\tSign(\"Hello again!\")\n}\n\nfunc TestSign_InvalidMessage(t *testing.T) {\n\t// Should not allow control characters in message.\n\tguestbook = avl.Tree{}\n\thasSigned = avl.Tree{}\n\n\tstd.TestSetRealm(std.NewUserRealm(\"g1user\"))\n\n\tdefer func() {\n\t\trec := recover()\n\t\tif rec == nil {\n\t\t\tt.Fatal(\"expected panic\")\n\t\t}\n\t\trecString, ok := rec.(string)\n\t\tif !ok {\n\t\t\tt.Error(\"type assertion failed\", rec)\n\t\t} else if recString != errInvalidCharacterInMessage {\n\t\t\tt.Error(\"invalid error message\", recString)\n\t\t}\n\t}()\n\tSign(\"\\x00Hello!\")\n}\n\nfunc TestAdminDelete(t *testing.T) {\n\tconst (\n\t\tuserAddr std.Address = \"g1user\"\n\t\tadminAddr std.Address = \"g1admin\"\n\t)\n\n\tguestbook = avl.Tree{}\n\thasSigned = avl.Tree{}\n\towner = ownable.NewWithAddress(adminAddr)\n\tsignatureID = 0\n\n\tstd.TestSetRealm(std.NewUserRealm(userAddr))\n\n\tconst bad = \"Very Bad Message! Nyeh heh heh!\"\n\tSign(bad)\n\n\tif rnd := Render(\"\"); !strings.Contains(rnd, bad) {\n\t\tt.Fatal(\"render does not contain bad message\", rnd)\n\t}\n\n\tstd.TestSetRealm(std.NewUserRealm(adminAddr))\n\tAdminDelete(signatureID.String())\n\n\tif rnd := Render(\"\"); strings.Contains(rnd, bad) {\n\t\tt.Error(\"render contains bad message\", rnd)\n\t}\n\tif guestbook.Size() != 0 {\n\t\tt.Error(\"invalid guestbook size\")\n\t}\n\tif hasSigned.Size() != 1 {\n\t\tt.Error(\"invalid hasSigned size\")\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":"home","path":"gno.land/r/morgan/home","files":[{"name":"home.gno","body":"package home\n\nconst staticHome = `# morgan's (gn)home\n\n- [📝 sign my guestbook](/r/morgan/guestbook)\n`\n\nfunc Render(path string) string {\n\treturn staticHome\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":"registry","path":"gno.land/r/stefann/registry","files":[{"name":"registry.gno","body":"package registry\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/ownable\"\n)\n\nvar (\n\tmainAddr std.Address\n\tbackupAddr std.Address\n\towner *ownable.Ownable\n)\n\nfunc init() {\n\tmainAddr = \"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\"\n\tbackupAddr = \"g13awn2575t8s2vf3svlprc4dg0e9z5wchejdxk8\"\n\n\towner = ownable.NewWithAddress(mainAddr)\n}\n\nfunc MainAddr() std.Address {\n\treturn mainAddr\n}\n\nfunc BackupAddr() std.Address {\n\treturn backupAddr\n}\n\nfunc SetMainAddr(addr std.Address) error {\n\tif !addr.IsValid() {\n\t\treturn errors.New(\"config: invalid address\")\n\t}\n\n\towner.AssertCallerIsOwner()\n\n\tmainAddr = addr\n\treturn nil\n}\n\nfunc SetBackupAddr(addr std.Address) error {\n\tif !addr.IsValid() {\n\t\treturn errors.New(\"config: invalid address\")\n\t}\n\n\towner.AssertCallerIsOwner()\n\n\tbackupAddr = addr\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_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"home","path":"gno.land/r/stefann/home","files":[{"name":"home.gno","body":"package home\n\nimport (\n\t\"sort\"\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/ufmt\"\n\n\t\"gno.land/r/stefann/registry\"\n)\n\ntype City struct {\n\tName string\n\tURL string\n}\n\ntype Sponsor struct {\n\tAddress std.Address\n\tAmount std.Coins\n}\n\ntype Profile struct {\n\tpfp string\n\taboutMe []string\n}\n\ntype Travel struct {\n\tcities []City\n\tcurrentCityIndex int\n\tjarLink string\n}\n\ntype Sponsorship struct {\n\tmaxSponsors int\n\tsponsors *avl.Tree\n\tDonationsCount int\n\tsponsorsCount int\n}\n\nvar (\n\tprofile Profile\n\ttravel Travel\n\tsponsorship Sponsorship\n\towner *ownable.Ownable\n)\n\nfunc init() {\n\towner = ownable.NewWithAddress(registry.MainAddr())\n\n\tprofile = Profile{\n\t\tpfp: \"https://i.ibb.co/Bc5YNCx/DSC-0095a.jpg\",\n\t\taboutMe: []string{\n\t\t\t`### About Me`,\n\t\t\t`Hey there! I’m Stefan, a student of Computer Science. I’m all about exploring and adventure — whether it’s diving into the latest tech or discovering a new city, I’m always up for the challenge!`,\n\n\t\t\t`### Contributions`,\n\t\t\t`I'm just getting started, but you can follow my journey through gno.land right [here](https://github.com/gnolang/hackerspace/issues/94) 🔗`,\n\t\t},\n\t}\n\n\ttravel = Travel{\n\t\tcities: []City{\n\t\t\t{Name: \"Venice\", URL: \"https://i.ibb.co/1mcZ7b1/venice.jpg\"},\n\t\t\t{Name: \"Tokyo\", URL: \"https://i.ibb.co/wNDJv3H/tokyo.jpg\"},\n\t\t\t{Name: \"São Paulo\", URL: \"https://i.ibb.co/yWMq2Sn/sao-paulo.jpg\"},\n\t\t\t{Name: \"Toronto\", URL: \"https://i.ibb.co/pb95HJB/toronto.jpg\"},\n\t\t\t{Name: \"Bangkok\", URL: \"https://i.ibb.co/pQy3w2g/bangkok.jpg\"},\n\t\t\t{Name: \"New York\", URL: \"https://i.ibb.co/6JWLm0h/new-york.jpg\"},\n\t\t\t{Name: \"Paris\", URL: \"https://i.ibb.co/q9vf6Hs/paris.jpg\"},\n\t\t\t{Name: \"Kandersteg\", URL: \"https://i.ibb.co/60DzywD/kandersteg.jpg\"},\n\t\t\t{Name: \"Rothenburg\", URL: \"https://i.ibb.co/cr8d2rQ/rothenburg.jpg\"},\n\t\t\t{Name: \"Capetown\", URL: \"https://i.ibb.co/bPGn0v3/capetown.jpg\"},\n\t\t\t{Name: \"Sydney\", URL: \"https://i.ibb.co/TBNzqfy/sydney.jpg\"},\n\t\t\t{Name: \"Oeschinen Lake\", URL: \"https://i.ibb.co/QJQwp2y/oeschinen-lake.jpg\"},\n\t\t\t{Name: \"Barra Grande\", URL: \"https://i.ibb.co/z4RXKc1/barra-grande.jpg\"},\n\t\t\t{Name: \"London\", URL: \"https://i.ibb.co/CPGtvgr/london.jpg\"},\n\t\t},\n\t\tcurrentCityIndex: 0,\n\t\tjarLink: \"https://TODO\", // This value should be injected through UpdateJarLink after deployment.\n\t}\n\n\tsponsorship = Sponsorship{\n\t\tmaxSponsors: 5,\n\t\tsponsors: avl.NewTree(),\n\t\tDonationsCount: 0,\n\t\tsponsorsCount: 0,\n\t}\n}\n\nfunc UpdateCities(newCities []City) {\n\towner.AssertCallerIsOwner()\n\ttravel.cities = newCities\n}\n\nfunc AddCities(newCities ...City) {\n\towner.AssertCallerIsOwner()\n\n\ttravel.cities = append(travel.cities, newCities...)\n}\n\nfunc UpdateJarLink(newLink string) {\n\towner.AssertCallerIsOwner()\n\ttravel.jarLink = newLink\n}\n\nfunc UpdatePFP(url string) {\n\towner.AssertCallerIsOwner()\n\tprofile.pfp = url\n}\n\nfunc UpdateAboutMe(aboutMeStr string) {\n\towner.AssertCallerIsOwner()\n\tprofile.aboutMe = strings.Split(aboutMeStr, \"|\")\n}\n\nfunc AddAboutMeRows(newRows ...string) {\n\towner.AssertCallerIsOwner()\n\n\tprofile.aboutMe = append(profile.aboutMe, newRows...)\n}\n\nfunc UpdateMaxSponsors(newMax int) {\n\towner.AssertCallerIsOwner()\n\tif newMax \u003c= 0 {\n\t\tpanic(\"maxSponsors must be greater than zero\")\n\t}\n\tsponsorship.maxSponsors = newMax\n}\n\nfunc Donate() {\n\taddress := std.GetOrigCaller()\n\tamount := std.GetOrigSend()\n\n\tif amount.AmountOf(\"ugnot\") == 0 {\n\t\tpanic(\"Donation must include GNOT\")\n\t}\n\n\texistingAmount, exists := sponsorship.sponsors.Get(address.String())\n\tif exists {\n\t\tupdatedAmount := existingAmount.(std.Coins).Add(amount)\n\t\tsponsorship.sponsors.Set(address.String(), updatedAmount)\n\t} else {\n\t\tsponsorship.sponsors.Set(address.String(), amount)\n\t\tsponsorship.sponsorsCount++\n\t}\n\n\ttravel.currentCityIndex++\n\tsponsorship.DonationsCount++\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\townerAddr := registry.MainAddr()\n\tbanker.SendCoins(std.CurrentRealm().Addr(), ownerAddr, banker.GetCoins(std.CurrentRealm().Addr()))\n}\n\ntype SponsorSlice []Sponsor\n\nfunc (s SponsorSlice) Len() int {\n\treturn len(s)\n}\n\nfunc (s SponsorSlice) Less(i, j int) bool {\n\treturn s[i].Amount.AmountOf(\"ugnot\") \u003e s[j].Amount.AmountOf(\"ugnot\")\n}\n\nfunc (s SponsorSlice) Swap(i, j int) {\n\ts[i], s[j] = s[j], s[i]\n}\n\nfunc GetTopSponsors() []Sponsor {\n\tvar sponsorSlice SponsorSlice\n\n\tsponsorship.sponsors.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\taddr := std.Address(key)\n\t\tamount := value.(std.Coins)\n\t\tsponsorSlice = append(sponsorSlice, Sponsor{Address: addr, Amount: amount})\n\t\treturn false\n\t})\n\n\tsort.Sort(sponsorSlice)\n\treturn sponsorSlice\n}\n\nfunc GetTotalDonations() int {\n\ttotal := 0\n\tsponsorship.sponsors.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\ttotal += int(value.(std.Coins).AmountOf(\"ugnot\"))\n\t\treturn false\n\t})\n\treturn total\n}\n\nfunc Render(path string) string {\n\tout := ufmt.Sprintf(\"# Exploring %s!\\n\\n\", travel.cities[travel.currentCityIndex].Name)\n\n\tout += renderAboutMe()\n\tout += \"\\n\\n\"\n\tout += renderTips()\n\n\treturn out\n}\n\nfunc renderAboutMe() string {\n\tout := \"\u003cdiv class='rows-3'\u003e\"\n\n\tout += \"\u003cdiv style='position: relative; text-align: center;'\u003e\\n\\n\"\n\n\tout += ufmt.Sprintf(\"\u003cdiv style='background-image: url(%s); background-size: cover; background-position: center; width: 100%%; height: 600px; position: relative; border-radius: 15px; overflow: hidden;'\u003e\\n\\n\", travel.cities[travel.currentCityIndex%len(travel.cities)].URL)\n\n\tout += ufmt.Sprintf(\"\u003cimg src='%s' alt='my profile pic' style='width: 250px; height: auto; aspect-ratio: 1 / 1; object-fit: cover; border-radius: 50%%; border: 3px solid #1e1e1e; position: absolute; top: 75%%; left: 50%%; transform: translate(-50%%, -50%%);'\u003e\\n\\n\", profile.pfp)\n\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\tfor _, rows := range profile.aboutMe {\n\t\tout += \"\u003cdiv\u003e\\n\\n\"\n\t\tout += rows + \"\\n\\n\"\n\t\tout += \"\u003c/div\u003e\\n\\n\"\n\t}\n\n\tout += \"\u003c/div\u003e\u003c!-- /rows-3 --\u003e\\n\\n\"\n\n\treturn out\n}\n\nfunc renderTips() string {\n\tout := `\u003cdiv class=\"jumbotron\" style=\"display: flex; flex-direction: column; justify-content: flex-start; align-items: center; padding-top: 40px; padding-bottom: 50px; text-align: center;\"\u003e` + \"\\n\\n\"\n\n\tout += `\u003cdiv class=\"rows-2\" style=\"max-width: 500px; width: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center;\"\u003e` + \"\\n\"\n\n\tout += `\u003ch1 style=\"margin-bottom: 50px;\"\u003eHelp Me Travel The World\u003c/h1\u003e` + \"\\n\\n\"\n\n\tout += renderTipsJar() + \"\\n\"\n\n\tout += ufmt.Sprintf(`\u003cstrong style=\"font-size: 1.2em;\"\u003eI am currently in %s, \u003cbr\u003e tip the jar to send me somewhere else!\u003c/strong\u003e`, travel.cities[travel.currentCityIndex].Name)\n\n\tout += `\u003cbr\u003e\u003cspan style=\"font-size: 1.2em; font-style: italic; margin-top: 10px; display: inline-block;\"\u003eClick the jar, tip in GNOT coins, and watch my background change as I head to a new adventure!\u003c/span\u003e\u003c/p\u003e` + \"\\n\\n\"\n\n\tout += renderSponsors()\n\n\tout += `\u003c/div\u003e\u003c!-- /rows-2 --\u003e` + \"\\n\\n\"\n\n\tout += `\u003c/div\u003e\u003c!-- /jumbotron --\u003e` + \"\\n\"\n\n\treturn out\n}\n\nfunc formatAddress(address string) string {\n\tif len(address) \u003c= 8 {\n\t\treturn address\n\t}\n\treturn address[:4] + \"...\" + address[len(address)-4:]\n}\n\nfunc renderSponsors() string {\n\tout := `\u003ch3 style=\"margin-top: 5px; margin-bottom: 20px\"\u003eSponsor Leaderboard\u003c/h3\u003e` + \"\\n\"\n\n\tif sponsorship.sponsorsCount == 0 {\n\t\treturn out + `\u003cp style=\"text-align: center;\"\u003eNo sponsors yet. Be the first to tip the jar!\u003c/p\u003e` + \"\\n\"\n\t}\n\n\ttopSponsors := GetTopSponsors()\n\tnumSponsors := len(topSponsors)\n\tif numSponsors \u003e sponsorship.maxSponsors {\n\t\tnumSponsors = sponsorship.maxSponsors\n\t}\n\n\tout += `\u003cul style=\"list-style-type: none; padding: 0; border: 1px solid #ddd; border-radius: 8px; width: 100%; max-width: 300px; margin: 0 auto;\"\u003e` + \"\\n\"\n\n\tfor i := 0; i \u003c numSponsors; i++ {\n\t\tsponsor := topSponsors[i]\n\t\tisLastItem := (i == numSponsors-1)\n\n\t\tpadding := \"10px 5px\"\n\t\tborder := \"border-bottom: 1px solid #ddd;\"\n\n\t\tif isLastItem {\n\t\t\tpadding = \"8px 5px\"\n\t\t\tborder = \"\"\n\t\t}\n\n\t\tout += ufmt.Sprintf(\n\t\t\t`\u003cli style=\"padding: %s; %s text-align: left;\"\u003e\n\t\t\t\t\u003cstrong style=\"padding-left: 5px;\"\u003e%d. %s\u003c/strong\u003e\n\t\t\t\t\u003cspan style=\"float: right; padding-right: 5px;\"\u003e%s\u003c/span\u003e\n\t\t\t\u003c/li\u003e`,\n\t\t\tpadding, border, i+1, formatAddress(sponsor.Address.String()), sponsor.Amount.String(),\n\t\t)\n\t}\n\n\treturn out\n}\n\nfunc renderTipsJar() string {\n\tout := ufmt.Sprintf(`\u003ca href=\"%s\" target=\"_blank\" style=\"display: block; text-decoration: none;\"\u003e`, travel.jarLink) + \"\\n\"\n\n\tout += `\u003cimg src=\"https://i.ibb.co/4TH9zbw/tips-jar.png\" alt=\"Tips Jar\" style=\"width: 300px; height: auto; display: block; margin: 0 auto;\"\u003e` + \"\\n\"\n\n\tout += `\u003c/a\u003e` + \"\\n\"\n\n\treturn out\n}\n"},{"name":"home_test.gno","body":"package home\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/testutils\"\n)\n\nfunc TestUpdatePFP(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOrigCaller(owner)\n\n\tprofile.pfp = \"\"\n\n\tUpdatePFP(\"https://example.com/pic.png\")\n\n\tif profile.pfp != \"https://example.com/pic.png\" {\n\t\tt.Fatalf(\"expected pfp to be https://example.com/pic.png, got %s\", profile.pfp)\n\t}\n}\n\nfunc TestUpdateAboutMe(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOrigCaller(owner)\n\n\tprofile.aboutMe = []string{}\n\n\tUpdateAboutMe(\"This is my new bio.|I love coding!\")\n\n\texpected := []string{\"This is my new bio.\", \"I love coding!\"}\n\n\tif len(profile.aboutMe) != len(expected) {\n\t\tt.Fatalf(\"expected aboutMe to have length %d, got %d\", len(expected), len(profile.aboutMe))\n\t}\n\n\tfor i := range profile.aboutMe {\n\t\tif profile.aboutMe[i] != expected[i] {\n\t\t\tt.Fatalf(\"expected aboutMe[%d] to be %s, got %s\", i, expected[i], profile.aboutMe[i])\n\t\t}\n\t}\n}\n\nfunc TestUpdateCities(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOrigCaller(owner)\n\n\ttravel.cities = []City{}\n\n\tnewCities := []City{\n\t\t{Name: \"Berlin\", URL: \"https://example.com/berlin.jpg\"},\n\t\t{Name: \"Vienna\", URL: \"https://example.com/vienna.jpg\"},\n\t}\n\n\tUpdateCities(newCities)\n\n\tif len(travel.cities) != 2 {\n\t\tt.Fatalf(\"expected 2 cities, got %d\", len(travel.cities))\n\t}\n\n\tif travel.cities[0].Name != \"Berlin\" || travel.cities[1].Name != \"Vienna\" {\n\t\tt.Fatalf(\"expected cities to be updated to Berlin and Vienna, got %+v\", travel.cities)\n\t}\n}\n\nfunc TestUpdateJarLink(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOrigCaller(owner)\n\n\ttravel.jarLink = \"\"\n\n\tUpdateJarLink(\"https://example.com/jar\")\n\n\tif travel.jarLink != \"https://example.com/jar\" {\n\t\tt.Fatalf(\"expected jarLink to be https://example.com/jar, got %s\", travel.jarLink)\n\t}\n}\n\nfunc TestUpdateMaxSponsors(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOrigCaller(owner)\n\n\tsponsorship.maxSponsors = 0\n\n\tUpdateMaxSponsors(10)\n\n\tif sponsorship.maxSponsors != 10 {\n\t\tt.Fatalf(\"expected maxSponsors to be 10, got %d\", sponsorship.maxSponsors)\n\t}\n\n\tdefer func() {\n\t\tif r := recover(); r == nil {\n\t\t\tt.Fatalf(\"expected panic for setting maxSponsors to 0\")\n\t\t}\n\t}()\n\tUpdateMaxSponsors(0)\n}\n\nfunc TestAddCities(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOrigCaller(owner)\n\n\ttravel.cities = []City{}\n\n\tAddCities(City{Name: \"Berlin\", URL: \"https://example.com/berlin.jpg\"})\n\n\tif len(travel.cities) != 1 {\n\t\tt.Fatalf(\"expected 1 city, got %d\", len(travel.cities))\n\t}\n\tif travel.cities[0].Name != \"Berlin\" || travel.cities[0].URL != \"https://example.com/berlin.jpg\" {\n\t\tt.Fatalf(\"expected city to be Berlin, got %+v\", travel.cities[0])\n\t}\n\n\tAddCities(\n\t\tCity{Name: \"Paris\", URL: \"https://example.com/paris.jpg\"},\n\t\tCity{Name: \"Tokyo\", URL: \"https://example.com/tokyo.jpg\"},\n\t)\n\n\tif len(travel.cities) != 3 {\n\t\tt.Fatalf(\"expected 3 cities, got %d\", len(travel.cities))\n\t}\n\tif travel.cities[1].Name != \"Paris\" || travel.cities[2].Name != \"Tokyo\" {\n\t\tt.Fatalf(\"expected cities to be Paris and Tokyo, got %+v\", travel.cities[1:])\n\t}\n}\n\nfunc TestAddAboutMeRows(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOrigCaller(owner)\n\n\tprofile.aboutMe = []string{}\n\n\tAddAboutMeRows(\"I love exploring new technologies!\")\n\n\tif len(profile.aboutMe) != 1 {\n\t\tt.Fatalf(\"expected 1 aboutMe row, got %d\", len(profile.aboutMe))\n\t}\n\tif profile.aboutMe[0] != \"I love exploring new technologies!\" {\n\t\tt.Fatalf(\"expected first aboutMe row to be 'I love exploring new technologies!', got %s\", profile.aboutMe[0])\n\t}\n\n\tAddAboutMeRows(\"Travel is my passion!\", \"Always learning.\")\n\n\tif len(profile.aboutMe) != 3 {\n\t\tt.Fatalf(\"expected 3 aboutMe rows, got %d\", len(profile.aboutMe))\n\t}\n\tif profile.aboutMe[1] != \"Travel is my passion!\" || profile.aboutMe[2] != \"Always learning.\" {\n\t\tt.Fatalf(\"expected aboutMe rows to be 'Travel is my passion!' and 'Always learning.', got %+v\", profile.aboutMe[1:])\n\t}\n}\n\nfunc TestDonate(t *testing.T) {\n\tvar user = testutils.TestAddress(\"user\")\n\tstd.TestSetOrigCaller(user)\n\n\tsponsorship.sponsors = avl.NewTree()\n\tsponsorship.DonationsCount = 0\n\tsponsorship.sponsorsCount = 0\n\ttravel.currentCityIndex = 0\n\n\tcoinsSent := std.NewCoins(std.NewCoin(\"ugnot\", 500))\n\tstd.TestSetOrigSend(coinsSent, std.NewCoins())\n\tDonate()\n\n\texistingAmount, exists := sponsorship.sponsors.Get(string(user))\n\tif !exists {\n\t\tt.Fatalf(\"expected sponsor to be added, but it was not found\")\n\t}\n\n\tif existingAmount.(std.Coins).AmountOf(\"ugnot\") != 500 {\n\t\tt.Fatalf(\"expected donation amount to be 500ugnot, got %d\", existingAmount.(std.Coins).AmountOf(\"ugnot\"))\n\t}\n\n\tif sponsorship.DonationsCount != 1 {\n\t\tt.Fatalf(\"expected DonationsCount to be 1, got %d\", sponsorship.DonationsCount)\n\t}\n\n\tif sponsorship.sponsorsCount != 1 {\n\t\tt.Fatalf(\"expected sponsorsCount to be 1, got %d\", sponsorship.sponsorsCount)\n\t}\n\n\tif travel.currentCityIndex != 1 {\n\t\tt.Fatalf(\"expected currentCityIndex to be 1, got %d\", travel.currentCityIndex)\n\t}\n\n\tcoinsSent = std.NewCoins(std.NewCoin(\"ugnot\", 300))\n\tstd.TestSetOrigSend(coinsSent, std.NewCoins())\n\tDonate()\n\n\texistingAmount, exists = sponsorship.sponsors.Get(string(user))\n\tif !exists {\n\t\tt.Fatalf(\"expected sponsor to exist after second donation, but it was not found\")\n\t}\n\n\tif existingAmount.(std.Coins).AmountOf(\"ugnot\") != 800 {\n\t\tt.Fatalf(\"expected total donation amount to be 800ugnot, got %d\", existingAmount.(std.Coins).AmountOf(\"ugnot\"))\n\t}\n\n\tif sponsorship.DonationsCount != 2 {\n\t\tt.Fatalf(\"expected DonationsCount to be 2 after second donation, got %d\", sponsorship.DonationsCount)\n\t}\n\n\tif travel.currentCityIndex != 2 {\n\t\tt.Fatalf(\"expected currentCityIndex to be 2 after second donation, got %d\", travel.currentCityIndex)\n\t}\n}\n\nfunc TestGetTopSponsors(t *testing.T) {\n\tvar user = testutils.TestAddress(\"user\")\n\tstd.TestSetOrigCaller(user)\n\n\tsponsorship.sponsors = avl.NewTree()\n\tsponsorship.sponsorsCount = 0\n\n\tsponsorship.sponsors.Set(\"g1address1\", std.NewCoins(std.NewCoin(\"ugnot\", 300)))\n\tsponsorship.sponsors.Set(\"g1address2\", std.NewCoins(std.NewCoin(\"ugnot\", 500)))\n\tsponsorship.sponsors.Set(\"g1address3\", std.NewCoins(std.NewCoin(\"ugnot\", 200)))\n\tsponsorship.sponsorsCount = 3\n\n\ttopSponsors := GetTopSponsors()\n\n\tif len(topSponsors) != 3 {\n\t\tt.Fatalf(\"expected 3 sponsors, got %d\", len(topSponsors))\n\t}\n\n\tif topSponsors[0].Address.String() != \"g1address2\" || topSponsors[0].Amount.AmountOf(\"ugnot\") != 500 {\n\t\tt.Fatalf(\"expected top sponsor to be g1address2 with 500ugnot, got %s with %dugnot\", topSponsors[0].Address.String(), topSponsors[0].Amount.AmountOf(\"ugnot\"))\n\t}\n\n\tif topSponsors[1].Address.String() != \"g1address1\" || topSponsors[1].Amount.AmountOf(\"ugnot\") != 300 {\n\t\tt.Fatalf(\"expected second sponsor to be g1address1 with 300ugnot, got %s with %dugnot\", topSponsors[1].Address.String(), topSponsors[1].Amount.AmountOf(\"ugnot\"))\n\t}\n\n\tif topSponsors[2].Address.String() != \"g1address3\" || topSponsors[2].Amount.AmountOf(\"ugnot\") != 200 {\n\t\tt.Fatalf(\"expected third sponsor to be g1address3 with 200ugnot, got %s with %dugnot\", topSponsors[2].Address.String(), topSponsors[2].Amount.AmountOf(\"ugnot\"))\n\t}\n}\n\nfunc TestGetTotalDonations(t *testing.T) {\n\tvar user = testutils.TestAddress(\"user\")\n\tstd.TestSetOrigCaller(user)\n\n\tsponsorship.sponsors = avl.NewTree()\n\tsponsorship.sponsorsCount = 0\n\n\tsponsorship.sponsors.Set(\"g1address1\", std.NewCoins(std.NewCoin(\"ugnot\", 300)))\n\tsponsorship.sponsors.Set(\"g1address2\", std.NewCoins(std.NewCoin(\"ugnot\", 500)))\n\tsponsorship.sponsors.Set(\"g1address3\", std.NewCoins(std.NewCoin(\"ugnot\", 200)))\n\tsponsorship.sponsorsCount = 3\n\n\ttotalDonations := GetTotalDonations()\n\n\tif totalDonations != 1000 {\n\t\tt.Fatalf(\"expected total donations to be 1000ugnot, got %dugnot\", totalDonations)\n\t}\n}\n\nfunc TestRender(t *testing.T) {\n\ttravel.currentCityIndex = 0\n\ttravel.cities = []City{\n\t\t{Name: \"Venice\", URL: \"https://example.com/venice.jpg\"},\n\t\t{Name: \"Paris\", URL: \"https://example.com/paris.jpg\"},\n\t}\n\n\toutput := Render(\"\")\n\n\texpectedCity := \"Venice\"\n\tif !strings.Contains(output, expectedCity) {\n\t\tt.Fatalf(\"expected output to contain city name '%s', got %s\", expectedCity, output)\n\t}\n\n\texpectedURL := \"https://example.com/venice.jpg\"\n\tif !strings.Contains(output, expectedURL) {\n\t\tt.Fatalf(\"expected output to contain city URL '%s', got %s\", expectedURL, output)\n\t}\n\n\ttravel.currentCityIndex = 1\n\toutput = Render(\"\")\n\n\texpectedCity = \"Paris\"\n\tif !strings.Contains(output, expectedCity) {\n\t\tt.Fatalf(\"expected output to contain city name '%s', got %s\", expectedCity, output)\n\t}\n\n\texpectedURL = \"https://example.com/paris.jpg\"\n\tif !strings.Contains(output, expectedURL) {\n\t\tt.Fatalf(\"expected output to contain city URL '%s', got %s\", expectedURL, output)\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":"home","path":"gno.land/r/stefann/home","files":[{"name":"home.gno","body":"package home\n\nimport (\n\t\"sort\"\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/ufmt\"\n\n\t\"gno.land/r/stefann/registry\"\n)\n\ntype City struct {\n\tName string\n\tURL string\n}\n\ntype Sponsor struct {\n\tAddress std.Address\n\tAmount std.Coins\n}\n\ntype Profile struct {\n\tpfp string\n\taboutMe []string\n}\n\ntype Travel struct {\n\tcities []City\n\tcurrentCityIndex int\n\tjarLink string\n}\n\ntype Sponsorship struct {\n\tmaxSponsors int\n\tsponsors *avl.Tree\n\tDonationsCount int\n\tsponsorsCount int\n}\n\nvar (\n\tprofile Profile\n\ttravel Travel\n\tsponsorship Sponsorship\n\towner *ownable.Ownable\n)\n\nfunc init() {\n\towner = ownable.NewWithAddress(registry.MainAddr())\n\n\tprofile = Profile{\n\t\tpfp: \"https://i.ibb.co/Bc5YNCx/DSC-0095a.jpg\",\n\t\taboutMe: []string{\n\t\t\t`### About Me`,\n\t\t\t`Hey there! I’m Stefan, a student of Computer Science. I’m all about exploring and adventure — whether it’s diving into the latest tech or discovering a new city, I’m always up for the challenge!`,\n\n\t\t\t`### Contributions`,\n\t\t\t`I'm just getting started, but you can follow my journey through gno.land right [here](https://github.com/gnolang/hackerspace/issues/94) 🔗`,\n\t\t},\n\t}\n\n\ttravel = Travel{\n\t\tcities: []City{\n\t\t\t{Name: \"Venice\", URL: \"https://i.ibb.co/1mcZ7b1/venice.jpg\"},\n\t\t\t{Name: \"Tokyo\", URL: \"https://i.ibb.co/wNDJv3H/tokyo.jpg\"},\n\t\t\t{Name: \"São Paulo\", URL: \"https://i.ibb.co/yWMq2Sn/sao-paulo.jpg\"},\n\t\t\t{Name: \"Toronto\", URL: \"https://i.ibb.co/pb95HJB/toronto.jpg\"},\n\t\t\t{Name: \"Bangkok\", URL: \"https://i.ibb.co/pQy3w2g/bangkok.jpg\"},\n\t\t\t{Name: \"New York\", URL: \"https://i.ibb.co/6JWLm0h/new-york.jpg\"},\n\t\t\t{Name: \"Paris\", URL: \"https://i.ibb.co/q9vf6Hs/paris.jpg\"},\n\t\t\t{Name: \"Kandersteg\", URL: \"https://i.ibb.co/60DzywD/kandersteg.jpg\"},\n\t\t\t{Name: \"Rothenburg\", URL: \"https://i.ibb.co/cr8d2rQ/rothenburg.jpg\"},\n\t\t\t{Name: \"Capetown\", URL: \"https://i.ibb.co/bPGn0v3/capetown.jpg\"},\n\t\t\t{Name: \"Sydney\", URL: \"https://i.ibb.co/TBNzqfy/sydney.jpg\"},\n\t\t\t{Name: \"Oeschinen Lake\", URL: \"https://i.ibb.co/QJQwp2y/oeschinen-lake.jpg\"},\n\t\t\t{Name: \"Barra Grande\", URL: \"https://i.ibb.co/z4RXKc1/barra-grande.jpg\"},\n\t\t\t{Name: \"London\", URL: \"https://i.ibb.co/CPGtvgr/london.jpg\"},\n\t\t},\n\t\tcurrentCityIndex: 0,\n\t\tjarLink: \"https://TODO\", // This value should be injected through UpdateJarLink after deployment.\n\t}\n\n\tsponsorship = Sponsorship{\n\t\tmaxSponsors: 5,\n\t\tsponsors: avl.NewTree(),\n\t\tDonationsCount: 0,\n\t\tsponsorsCount: 0,\n\t}\n}\n\nfunc UpdateCities(newCities []City) {\n\towner.AssertCallerIsOwner()\n\ttravel.cities = newCities\n}\n\nfunc AddCities(newCities ...City) {\n\towner.AssertCallerIsOwner()\n\n\ttravel.cities = append(travel.cities, newCities...)\n}\n\nfunc UpdateJarLink(newLink string) {\n\towner.AssertCallerIsOwner()\n\ttravel.jarLink = newLink\n}\n\nfunc UpdatePFP(url string) {\n\towner.AssertCallerIsOwner()\n\tprofile.pfp = url\n}\n\nfunc UpdateAboutMe(aboutMeStr string) {\n\towner.AssertCallerIsOwner()\n\tprofile.aboutMe = strings.Split(aboutMeStr, \"|\")\n}\n\nfunc AddAboutMeRows(newRows ...string) {\n\towner.AssertCallerIsOwner()\n\n\tprofile.aboutMe = append(profile.aboutMe, newRows...)\n}\n\nfunc UpdateMaxSponsors(newMax int) {\n\towner.AssertCallerIsOwner()\n\tif newMax \u003c= 0 {\n\t\tpanic(\"maxSponsors must be greater than zero\")\n\t}\n\tsponsorship.maxSponsors = newMax\n}\n\nfunc Donate() {\n\taddress := std.OrigCaller()\n\tamount := std.OrigSend()\n\n\tif amount.AmountOf(\"ugnot\") == 0 {\n\t\tpanic(\"Donation must include GNOT\")\n\t}\n\n\texistingAmount, exists := sponsorship.sponsors.Get(address.String())\n\tif exists {\n\t\tupdatedAmount := existingAmount.(std.Coins).Add(amount)\n\t\tsponsorship.sponsors.Set(address.String(), updatedAmount)\n\t} else {\n\t\tsponsorship.sponsors.Set(address.String(), amount)\n\t\tsponsorship.sponsorsCount++\n\t}\n\n\ttravel.currentCityIndex++\n\tsponsorship.DonationsCount++\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\townerAddr := registry.MainAddr()\n\tbanker.SendCoins(std.CurrentRealm().Addr(), ownerAddr, banker.GetCoins(std.CurrentRealm().Addr()))\n}\n\ntype SponsorSlice []Sponsor\n\nfunc (s SponsorSlice) Len() int {\n\treturn len(s)\n}\n\nfunc (s SponsorSlice) Less(i, j int) bool {\n\treturn s[i].Amount.AmountOf(\"ugnot\") \u003e s[j].Amount.AmountOf(\"ugnot\")\n}\n\nfunc (s SponsorSlice) Swap(i, j int) {\n\ts[i], s[j] = s[j], s[i]\n}\n\nfunc GetTopSponsors() []Sponsor {\n\tvar sponsorSlice SponsorSlice\n\n\tsponsorship.sponsors.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\taddr := std.Address(key)\n\t\tamount := value.(std.Coins)\n\t\tsponsorSlice = append(sponsorSlice, Sponsor{Address: addr, Amount: amount})\n\t\treturn false\n\t})\n\n\tsort.Sort(sponsorSlice)\n\treturn sponsorSlice\n}\n\nfunc GetTotalDonations() int {\n\ttotal := 0\n\tsponsorship.sponsors.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\ttotal += int(value.(std.Coins).AmountOf(\"ugnot\"))\n\t\treturn false\n\t})\n\treturn total\n}\n\nfunc Render(path string) string {\n\tout := ufmt.Sprintf(\"# Exploring %s!\\n\\n\", travel.cities[travel.currentCityIndex].Name)\n\n\tout += renderAboutMe()\n\tout += \"\\n\\n\"\n\tout += renderTips()\n\n\treturn out\n}\n\nfunc renderAboutMe() string {\n\tout := \"\u003cdiv class='rows-3'\u003e\"\n\n\tout += \"\u003cdiv style='position: relative; text-align: center;'\u003e\\n\\n\"\n\n\tout += ufmt.Sprintf(\"\u003cdiv style='background-image: url(%s); background-size: cover; background-position: center; width: 100%%; height: 600px; position: relative; border-radius: 15px; overflow: hidden;'\u003e\\n\\n\", travel.cities[travel.currentCityIndex%len(travel.cities)].URL)\n\n\tout += ufmt.Sprintf(\"\u003cimg src='%s' alt='my profile pic' style='width: 250px; height: auto; aspect-ratio: 1 / 1; object-fit: cover; border-radius: 50%%; border: 3px solid #1e1e1e; position: absolute; top: 75%%; left: 50%%; transform: translate(-50%%, -50%%);'\u003e\\n\\n\", profile.pfp)\n\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\tfor _, rows := range profile.aboutMe {\n\t\tout += \"\u003cdiv\u003e\\n\\n\"\n\t\tout += rows + \"\\n\\n\"\n\t\tout += \"\u003c/div\u003e\\n\\n\"\n\t}\n\n\tout += \"\u003c/div\u003e\u003c!-- /rows-3 --\u003e\\n\\n\"\n\n\treturn out\n}\n\nfunc renderTips() string {\n\tout := `\u003cdiv class=\"jumbotron\" style=\"display: flex; flex-direction: column; justify-content: flex-start; align-items: center; padding-top: 40px; padding-bottom: 50px; text-align: center;\"\u003e` + \"\\n\\n\"\n\n\tout += `\u003cdiv class=\"rows-2\" style=\"max-width: 500px; width: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center;\"\u003e` + \"\\n\"\n\n\tout += `\u003ch1 style=\"margin-bottom: 50px;\"\u003eHelp Me Travel The World\u003c/h1\u003e` + \"\\n\\n\"\n\n\tout += renderTipsJar() + \"\\n\"\n\n\tout += ufmt.Sprintf(`\u003cstrong style=\"font-size: 1.2em;\"\u003eI am currently in %s, \u003cbr\u003e tip the jar to send me somewhere else!\u003c/strong\u003e`, travel.cities[travel.currentCityIndex].Name)\n\n\tout += `\u003cbr\u003e\u003cspan style=\"font-size: 1.2em; font-style: italic; margin-top: 10px; display: inline-block;\"\u003eClick the jar, tip in GNOT coins, and watch my background change as I head to a new adventure!\u003c/span\u003e\u003c/p\u003e` + \"\\n\\n\"\n\n\tout += renderSponsors()\n\n\tout += `\u003c/div\u003e\u003c!-- /rows-2 --\u003e` + \"\\n\\n\"\n\n\tout += `\u003c/div\u003e\u003c!-- /jumbotron --\u003e` + \"\\n\"\n\n\treturn out\n}\n\nfunc formatAddress(address string) string {\n\tif len(address) \u003c= 8 {\n\t\treturn address\n\t}\n\treturn address[:4] + \"...\" + address[len(address)-4:]\n}\n\nfunc renderSponsors() string {\n\tout := `\u003ch3 style=\"margin-top: 5px; margin-bottom: 20px\"\u003eSponsor Leaderboard\u003c/h3\u003e` + \"\\n\"\n\n\tif sponsorship.sponsorsCount == 0 {\n\t\treturn out + `\u003cp style=\"text-align: center;\"\u003eNo sponsors yet. Be the first to tip the jar!\u003c/p\u003e` + \"\\n\"\n\t}\n\n\ttopSponsors := GetTopSponsors()\n\tnumSponsors := len(topSponsors)\n\tif numSponsors \u003e sponsorship.maxSponsors {\n\t\tnumSponsors = sponsorship.maxSponsors\n\t}\n\n\tout += `\u003cul style=\"list-style-type: none; padding: 0; border: 1px solid #ddd; border-radius: 8px; width: 100%; max-width: 300px; margin: 0 auto;\"\u003e` + \"\\n\"\n\n\tfor i := 0; i \u003c numSponsors; i++ {\n\t\tsponsor := topSponsors[i]\n\t\tisLastItem := (i == numSponsors-1)\n\n\t\tpadding := \"10px 5px\"\n\t\tborder := \"border-bottom: 1px solid #ddd;\"\n\n\t\tif isLastItem {\n\t\t\tpadding = \"8px 5px\"\n\t\t\tborder = \"\"\n\t\t}\n\n\t\tout += ufmt.Sprintf(\n\t\t\t`\u003cli style=\"padding: %s; %s text-align: left;\"\u003e\n\t\t\t\t\u003cstrong style=\"padding-left: 5px;\"\u003e%d. %s\u003c/strong\u003e\n\t\t\t\t\u003cspan style=\"float: right; padding-right: 5px;\"\u003e%s\u003c/span\u003e\n\t\t\t\u003c/li\u003e`,\n\t\t\tpadding, border, i+1, formatAddress(sponsor.Address.String()), sponsor.Amount.String(),\n\t\t)\n\t}\n\n\treturn out\n}\n\nfunc renderTipsJar() string {\n\tout := ufmt.Sprintf(`\u003ca href=\"%s\" target=\"_blank\" style=\"display: block; text-decoration: none;\"\u003e`, travel.jarLink) + \"\\n\"\n\n\tout += `\u003cimg src=\"https://i.ibb.co/4TH9zbw/tips-jar.png\" alt=\"Tips Jar\" style=\"width: 300px; height: auto; display: block; margin: 0 auto;\"\u003e` + \"\\n\"\n\n\tout += `\u003c/a\u003e` + \"\\n\"\n\n\treturn out\n}\n"},{"name":"home_test.gno","body":"package home\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/testutils\"\n)\n\nfunc TestUpdatePFP(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOrigCaller(owner)\n\n\tprofile.pfp = \"\"\n\n\tUpdatePFP(\"https://example.com/pic.png\")\n\n\tif profile.pfp != \"https://example.com/pic.png\" {\n\t\tt.Fatalf(\"expected pfp to be https://example.com/pic.png, got %s\", profile.pfp)\n\t}\n}\n\nfunc TestUpdateAboutMe(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOrigCaller(owner)\n\n\tprofile.aboutMe = []string{}\n\n\tUpdateAboutMe(\"This is my new bio.|I love coding!\")\n\n\texpected := []string{\"This is my new bio.\", \"I love coding!\"}\n\n\tif len(profile.aboutMe) != len(expected) {\n\t\tt.Fatalf(\"expected aboutMe to have length %d, got %d\", len(expected), len(profile.aboutMe))\n\t}\n\n\tfor i := range profile.aboutMe {\n\t\tif profile.aboutMe[i] != expected[i] {\n\t\t\tt.Fatalf(\"expected aboutMe[%d] to be %s, got %s\", i, expected[i], profile.aboutMe[i])\n\t\t}\n\t}\n}\n\nfunc TestUpdateCities(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOrigCaller(owner)\n\n\ttravel.cities = []City{}\n\n\tnewCities := []City{\n\t\t{Name: \"Berlin\", URL: \"https://example.com/berlin.jpg\"},\n\t\t{Name: \"Vienna\", URL: \"https://example.com/vienna.jpg\"},\n\t}\n\n\tUpdateCities(newCities)\n\n\tif len(travel.cities) != 2 {\n\t\tt.Fatalf(\"expected 2 cities, got %d\", len(travel.cities))\n\t}\n\n\tif travel.cities[0].Name != \"Berlin\" || travel.cities[1].Name != \"Vienna\" {\n\t\tt.Fatalf(\"expected cities to be updated to Berlin and Vienna, got %+v\", travel.cities)\n\t}\n}\n\nfunc TestUpdateJarLink(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOrigCaller(owner)\n\n\ttravel.jarLink = \"\"\n\n\tUpdateJarLink(\"https://example.com/jar\")\n\n\tif travel.jarLink != \"https://example.com/jar\" {\n\t\tt.Fatalf(\"expected jarLink to be https://example.com/jar, got %s\", travel.jarLink)\n\t}\n}\n\nfunc TestUpdateMaxSponsors(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOrigCaller(owner)\n\n\tsponsorship.maxSponsors = 0\n\n\tUpdateMaxSponsors(10)\n\n\tif sponsorship.maxSponsors != 10 {\n\t\tt.Fatalf(\"expected maxSponsors to be 10, got %d\", sponsorship.maxSponsors)\n\t}\n\n\tdefer func() {\n\t\tif r := recover(); r == nil {\n\t\t\tt.Fatalf(\"expected panic for setting maxSponsors to 0\")\n\t\t}\n\t}()\n\tUpdateMaxSponsors(0)\n}\n\nfunc TestAddCities(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOrigCaller(owner)\n\n\ttravel.cities = []City{}\n\n\tAddCities(City{Name: \"Berlin\", URL: \"https://example.com/berlin.jpg\"})\n\n\tif len(travel.cities) != 1 {\n\t\tt.Fatalf(\"expected 1 city, got %d\", len(travel.cities))\n\t}\n\tif travel.cities[0].Name != \"Berlin\" || travel.cities[0].URL != \"https://example.com/berlin.jpg\" {\n\t\tt.Fatalf(\"expected city to be Berlin, got %+v\", travel.cities[0])\n\t}\n\n\tAddCities(\n\t\tCity{Name: \"Paris\", URL: \"https://example.com/paris.jpg\"},\n\t\tCity{Name: \"Tokyo\", URL: \"https://example.com/tokyo.jpg\"},\n\t)\n\n\tif len(travel.cities) != 3 {\n\t\tt.Fatalf(\"expected 3 cities, got %d\", len(travel.cities))\n\t}\n\tif travel.cities[1].Name != \"Paris\" || travel.cities[2].Name != \"Tokyo\" {\n\t\tt.Fatalf(\"expected cities to be Paris and Tokyo, got %+v\", travel.cities[1:])\n\t}\n}\n\nfunc TestAddAboutMeRows(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOrigCaller(owner)\n\n\tprofile.aboutMe = []string{}\n\n\tAddAboutMeRows(\"I love exploring new technologies!\")\n\n\tif len(profile.aboutMe) != 1 {\n\t\tt.Fatalf(\"expected 1 aboutMe row, got %d\", len(profile.aboutMe))\n\t}\n\tif profile.aboutMe[0] != \"I love exploring new technologies!\" {\n\t\tt.Fatalf(\"expected first aboutMe row to be 'I love exploring new technologies!', got %s\", profile.aboutMe[0])\n\t}\n\n\tAddAboutMeRows(\"Travel is my passion!\", \"Always learning.\")\n\n\tif len(profile.aboutMe) != 3 {\n\t\tt.Fatalf(\"expected 3 aboutMe rows, got %d\", len(profile.aboutMe))\n\t}\n\tif profile.aboutMe[1] != \"Travel is my passion!\" || profile.aboutMe[2] != \"Always learning.\" {\n\t\tt.Fatalf(\"expected aboutMe rows to be 'Travel is my passion!' and 'Always learning.', got %+v\", profile.aboutMe[1:])\n\t}\n}\n\nfunc TestDonate(t *testing.T) {\n\tvar user = testutils.TestAddress(\"user\")\n\tstd.TestSetOrigCaller(user)\n\n\tsponsorship.sponsors = avl.NewTree()\n\tsponsorship.DonationsCount = 0\n\tsponsorship.sponsorsCount = 0\n\ttravel.currentCityIndex = 0\n\n\tcoinsSent := std.NewCoins(std.NewCoin(\"ugnot\", 500))\n\tstd.TestSetOrigSend(coinsSent, std.NewCoins())\n\tDonate()\n\n\texistingAmount, exists := sponsorship.sponsors.Get(string(user))\n\tif !exists {\n\t\tt.Fatalf(\"expected sponsor to be added, but it was not found\")\n\t}\n\n\tif existingAmount.(std.Coins).AmountOf(\"ugnot\") != 500 {\n\t\tt.Fatalf(\"expected donation amount to be 500ugnot, got %d\", existingAmount.(std.Coins).AmountOf(\"ugnot\"))\n\t}\n\n\tif sponsorship.DonationsCount != 1 {\n\t\tt.Fatalf(\"expected DonationsCount to be 1, got %d\", sponsorship.DonationsCount)\n\t}\n\n\tif sponsorship.sponsorsCount != 1 {\n\t\tt.Fatalf(\"expected sponsorsCount to be 1, got %d\", sponsorship.sponsorsCount)\n\t}\n\n\tif travel.currentCityIndex != 1 {\n\t\tt.Fatalf(\"expected currentCityIndex to be 1, got %d\", travel.currentCityIndex)\n\t}\n\n\tcoinsSent = std.NewCoins(std.NewCoin(\"ugnot\", 300))\n\tstd.TestSetOrigSend(coinsSent, std.NewCoins())\n\tDonate()\n\n\texistingAmount, exists = sponsorship.sponsors.Get(string(user))\n\tif !exists {\n\t\tt.Fatalf(\"expected sponsor to exist after second donation, but it was not found\")\n\t}\n\n\tif existingAmount.(std.Coins).AmountOf(\"ugnot\") != 800 {\n\t\tt.Fatalf(\"expected total donation amount to be 800ugnot, got %d\", existingAmount.(std.Coins).AmountOf(\"ugnot\"))\n\t}\n\n\tif sponsorship.DonationsCount != 2 {\n\t\tt.Fatalf(\"expected DonationsCount to be 2 after second donation, got %d\", sponsorship.DonationsCount)\n\t}\n\n\tif travel.currentCityIndex != 2 {\n\t\tt.Fatalf(\"expected currentCityIndex to be 2 after second donation, got %d\", travel.currentCityIndex)\n\t}\n}\n\nfunc TestGetTopSponsors(t *testing.T) {\n\tvar user = testutils.TestAddress(\"user\")\n\tstd.TestSetOrigCaller(user)\n\n\tsponsorship.sponsors = avl.NewTree()\n\tsponsorship.sponsorsCount = 0\n\n\tsponsorship.sponsors.Set(\"g1address1\", std.NewCoins(std.NewCoin(\"ugnot\", 300)))\n\tsponsorship.sponsors.Set(\"g1address2\", std.NewCoins(std.NewCoin(\"ugnot\", 500)))\n\tsponsorship.sponsors.Set(\"g1address3\", std.NewCoins(std.NewCoin(\"ugnot\", 200)))\n\tsponsorship.sponsorsCount = 3\n\n\ttopSponsors := GetTopSponsors()\n\n\tif len(topSponsors) != 3 {\n\t\tt.Fatalf(\"expected 3 sponsors, got %d\", len(topSponsors))\n\t}\n\n\tif topSponsors[0].Address.String() != \"g1address2\" || topSponsors[0].Amount.AmountOf(\"ugnot\") != 500 {\n\t\tt.Fatalf(\"expected top sponsor to be g1address2 with 500ugnot, got %s with %dugnot\", topSponsors[0].Address.String(), topSponsors[0].Amount.AmountOf(\"ugnot\"))\n\t}\n\n\tif topSponsors[1].Address.String() != \"g1address1\" || topSponsors[1].Amount.AmountOf(\"ugnot\") != 300 {\n\t\tt.Fatalf(\"expected second sponsor to be g1address1 with 300ugnot, got %s with %dugnot\", topSponsors[1].Address.String(), topSponsors[1].Amount.AmountOf(\"ugnot\"))\n\t}\n\n\tif topSponsors[2].Address.String() != \"g1address3\" || topSponsors[2].Amount.AmountOf(\"ugnot\") != 200 {\n\t\tt.Fatalf(\"expected third sponsor to be g1address3 with 200ugnot, got %s with %dugnot\", topSponsors[2].Address.String(), topSponsors[2].Amount.AmountOf(\"ugnot\"))\n\t}\n}\n\nfunc TestGetTotalDonations(t *testing.T) {\n\tvar user = testutils.TestAddress(\"user\")\n\tstd.TestSetOrigCaller(user)\n\n\tsponsorship.sponsors = avl.NewTree()\n\tsponsorship.sponsorsCount = 0\n\n\tsponsorship.sponsors.Set(\"g1address1\", std.NewCoins(std.NewCoin(\"ugnot\", 300)))\n\tsponsorship.sponsors.Set(\"g1address2\", std.NewCoins(std.NewCoin(\"ugnot\", 500)))\n\tsponsorship.sponsors.Set(\"g1address3\", std.NewCoins(std.NewCoin(\"ugnot\", 200)))\n\tsponsorship.sponsorsCount = 3\n\n\ttotalDonations := GetTotalDonations()\n\n\tif totalDonations != 1000 {\n\t\tt.Fatalf(\"expected total donations to be 1000ugnot, got %dugnot\", totalDonations)\n\t}\n}\n\nfunc TestRender(t *testing.T) {\n\ttravel.currentCityIndex = 0\n\ttravel.cities = []City{\n\t\t{Name: \"Venice\", URL: \"https://example.com/venice.jpg\"},\n\t\t{Name: \"Paris\", URL: \"https://example.com/paris.jpg\"},\n\t}\n\n\toutput := Render(\"\")\n\n\texpectedCity := \"Venice\"\n\tif !strings.Contains(output, expectedCity) {\n\t\tt.Fatalf(\"expected output to contain city name '%s', got %s\", expectedCity, output)\n\t}\n\n\texpectedURL := \"https://example.com/venice.jpg\"\n\tif !strings.Contains(output, expectedURL) {\n\t\tt.Fatalf(\"expected output to contain city URL '%s', got %s\", expectedURL, output)\n\t}\n\n\ttravel.currentCityIndex = 1\n\toutput = Render(\"\")\n\n\texpectedCity = \"Paris\"\n\tif !strings.Contains(output, expectedCity) {\n\t\tt.Fatalf(\"expected output to contain city name '%s', got %s\", expectedCity, output)\n\t}\n\n\texpectedURL = \"https://example.com/paris.jpg\"\n\tif !strings.Contains(output, expectedURL) {\n\t\tt.Fatalf(\"expected output to contain city URL '%s', got %s\", expectedURL, output)\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":"rewards","path":"gno.land/r/sys/rewards","files":[{"name":"rewards.gno","body":"// This package will be used to manage proof-of-contributions on the exposed smart-contract side.\npackage rewards\n\n// TODO: write specs.\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":"users","path":"gno.land/r/sys/users","files":[{"name":"verify.gno","body":"package users\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\" // @moul\n\ntype VerifyNameFunc func(enabled bool, address std.Address, name string) bool\n\nvar (\n\towner = ownable.NewWithAddress(admin) // Package owner\n\tcheckFunc = VerifyNameByUser // Checking namespace callback\n\tenabled = true // For now this package is disabled by default\n)\n\nfunc IsEnabled() bool { return enabled }\n\n// This method ensures that the given address has ownership of the given name.\nfunc IsAuthorizedAddressForName(address std.Address, name string) bool {\n\treturn checkFunc(enabled, address, name)\n}\n\n// VerifyNameByUser checks from the `users` package that the user has correctly\n// registered the given name.\n// This function considers as valid an `address` that matches the `name`.\nfunc VerifyNameByUser(enable bool, address std.Address, name string) bool {\n\tif !enable {\n\t\treturn true\n\t}\n\n\t// Allow user with their own address as name\n\tif address.String() == name {\n\t\treturn true\n\t}\n\n\tif user := users.GetUserByName(name); user != nil {\n\t\treturn user.Address == address\n\t}\n\n\treturn false\n}\n\n// Admin calls\n\n// Enable this package.\nfunc AdminEnable() {\n\tif err := owner.CallerIsOwner(); err != nil {\n\t\tpanic(err)\n\t}\n\n\tenabled = true\n}\n\n// Disable this package.\nfunc AdminDisable() {\n\tif err := owner.CallerIsOwner(); err != nil {\n\t\tpanic(err)\n\t}\n\n\tenabled = false\n}\n\n// AdminUpdateVerifyCall updates the method that verifies the namespace.\nfunc AdminUpdateVerifyCall(check VerifyNameFunc) {\n\tif err := owner.CallerIsOwner(); err != nil {\n\t\tpanic(err)\n\t}\n\n\tcheckFunc = check\n}\n\n// AdminTransferOwnership transfers the ownership to a new owner.\nfunc AdminTransferOwnership(newOwner std.Address) error {\n\tif err := owner.CallerIsOwner(); err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn owner.TransferOwnership(newOwner)\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}} From c58ae7216a8f3ccc678f35fbe49f1fe37427d66a Mon Sep 17 00:00:00 2001 From: hthieu1110 Date: Thu, 19 Dec 2024 21:39:10 +0700 Subject: [PATCH 02/14] chore: expanding abbreviations --- .../porting-solidity-to-gno/porting-13.gno | 20 ++-- .../porting-solidity-to-gno/porting-5.gno | 4 +- .../porting-solidity-to-gno/porting-6.gno | 24 ++--- .../porting-solidity-to-gno/porting-8.gno | 6 +- .../write-simple-dapp/poll-2.gno | 2 +- docs/concepts/effective-gno.md | 8 +- docs/concepts/stdlibs/banker.md | 2 +- docs/how-to-guides/porting-solidity-to-gno.md | 54 +++++------ docs/how-to-guides/write-simple-dapp.md | 2 +- docs/reference/stdlibs/std/banker.md | 4 +- docs/reference/stdlibs/std/chain.md | 12 +-- docs/reference/stdlibs/std/testing.md | 16 ++-- .../p/demo/gnorkle/gnorkle/instance.gno | 2 +- .../demo/grc/grc1155/basic_grc1155_token.gno | 20 ++-- .../grc/grc1155/basic_grc1155_token_test.gno | 6 +- .../p/demo/grc/grc20/tellers_test.gno | 4 +- .../p/demo/grc/grc721/basic_nft_test.gno | 6 +- .../demo/grc/grc721/grc721_metadata_test.gno | 6 +- .../p/demo/grc/grc721/grc721_royalty_test.gno | 6 +- .../p/demo/memeland/memeland_test.gno | 12 +-- .../gno.land/p/demo/microblog/microblog.gno | 2 +- .../exts/authorizable/authorizable_test.gno | 20 ++-- .../gno.land/p/demo/ownable/ownable_test.gno | 8 +- .../p/demo/pausable/pausable_test.gno | 12 +-- examples/gno.land/p/demo/simpledao/dao.gno | 6 +- .../gno.land/p/demo/simpledao/dao_test.gno | 42 ++++----- .../p/demo/subscription/lifetime/lifetime.gno | 2 +- .../subscription/lifetime/lifetime_test.gno | 18 ++-- .../demo/subscription/recurring/recurring.gno | 2 +- .../subscription/recurring/recurring_test.gno | 18 ++-- .../gno.land/p/demo/todolist/todolist.gno | 2 +- .../p/demo/todolist/todolist_test.gno | 2 +- examples/gno.land/p/n2p5/loci/loci_test.gno | 8 +- .../gno.land/p/n2p5/mgroup/mgroup_test.gno | 44 ++++----- examples/gno.land/r/demo/banktest/README.md | 6 +- .../gno.land/r/demo/banktest/banktest.gno | 6 +- .../gno.land/r/demo/banktest/z_0_filetest.gno | 6 +- .../gno.land/r/demo/banktest/z_1_filetest.gno | 2 +- .../gno.land/r/demo/banktest/z_2_filetest.gno | 6 +- .../gno.land/r/demo/banktest/z_3_filetest.gno | 2 +- examples/gno.land/r/demo/bar20/bar20_test.gno | 2 +- examples/gno.land/r/demo/boards/public.gno | 14 +-- .../r/demo/boards/z_12_a_filetest.gno | 4 +- .../gno.land/r/demo/boards/z_5_b_filetest.gno | 4 +- .../gno.land/r/demo/boards/z_5_c_filetest.gno | 4 +- .../gno.land/r/demo/boards/z_5_d_filetest.gno | 4 +- .../gno.land/r/demo/disperse/disperse.gno | 4 +- .../gno.land/r/demo/disperse/z_0_filetest.gno | 2 +- .../gno.land/r/demo/disperse/z_1_filetest.gno | 2 +- .../gno.land/r/demo/disperse/z_2_filetest.gno | 2 +- .../gno.land/r/demo/disperse/z_3_filetest.gno | 2 +- .../gno.land/r/demo/disperse/z_4_filetest.gno | 2 +- examples/gno.land/r/demo/foo1155/foo1155.gno | 8 +- examples/gno.land/r/demo/foo20/foo20_test.gno | 2 +- .../games/dice_roller/dice_roller_test.gno | 22 ++--- .../r/demo/grc20factory/grc20factory_test.gno | 10 +- examples/gno.land/r/demo/groups/public.gno | 8 +- .../gno.land/r/demo/groups/z_1_a_filetest.gno | 22 ++--- .../gno.land/r/demo/groups/z_1_c_filetest.gno | 4 +- .../gno.land/r/demo/groups/z_2_a_filetest.gno | 22 ++--- .../gno.land/r/demo/groups/z_2_d_filetest.gno | 4 +- .../gno.land/r/demo/groups/z_2_g_filetest.gno | 4 +- .../gno.land/r/demo/keystore/keystore.gno | 12 +-- .../r/demo/keystore/keystore_test.gno | 2 +- .../gno.land/r/demo/microblog/microblog.gno | 2 +- .../r/demo/microblog/microblog_test.gno | 4 +- .../r/demo/releases_example/example.gno | 4 +- examples/gno.land/r/demo/tests/tests.gno | 6 +- .../gno.land/r/demo/tests/z2_filetest.gno | 2 +- .../gno.land/r/demo/tests/z3_filetest.gno | 2 +- .../r/demo/todolist/todolist_test.gno | 2 +- examples/gno.land/r/demo/users/users.gno | 12 +-- .../gno.land/r/demo/users/z_0_filetest.gno | 2 +- .../gno.land/r/demo/users/z_10_filetest.gno | 8 +- .../gno.land/r/demo/users/z_11_filetest.gno | 6 +- .../gno.land/r/demo/users/z_11b_filetest.gno | 6 +- .../gno.land/r/demo/users/z_2_filetest.gno | 8 +- .../gno.land/r/demo/users/z_3_filetest.gno | 10 +- .../gno.land/r/demo/users/z_4_filetest.gno | 10 +- .../gno.land/r/demo/users/z_5_filetest.gno | 10 +- .../gno.land/r/demo/users/z_6_filetest.gno | 4 +- .../gno.land/r/demo/users/z_7_filetest.gno | 12 +-- .../gno.land/r/demo/users/z_7b_filetest.gno | 12 +-- .../gno.land/r/demo/users/z_8_filetest.gno | 12 +-- .../gno.land/r/demo/users/z_9_filetest.gno | 8 +- examples/gno.land/r/demo/wugnot/wugnot.gno | 2 +- .../gno.land/r/demo/wugnot/z0_filetest.gno | 6 +- examples/gno.land/r/gnoland/blog/admin.gno | 10 +- examples/gno.land/r/gnoland/blog/gnoblog.gno | 2 +- .../gno.land/r/gnoland/blog/gnoblog_test.gno | 4 +- .../gno.land/r/gnoland/events/events_test.gno | 12 +-- examples/gno.land/r/gnoland/faucet/admin.gno | 2 +- examples/gno.land/r/gnoland/faucet/faucet.gno | 2 +- .../gno.land/r/gnoland/faucet/faucet_test.gno | 20 ++-- .../gno.land/r/gnoland/faucet/z2_filetest.gno | 2 +- .../gno.land/r/gnoland/faucet/z3_filetest.gno | 6 +- .../gno.land/r/gnoland/ghverify/contract.gno | 6 +- .../r/gnoland/ghverify/contract_test.gno | 12 +-- .../r/gnoland/home/overide_filetest.gno | 2 +- examples/gno.land/r/gnoland/pages/admin.gno | 8 +- .../r/gnoland/valopers/v2/valopers.gno | 2 +- .../gno.land/r/gov/dao/bridge/bridge_test.gno | 2 +- examples/gno.land/r/leon/hof/hof_test.gno | 2 +- .../r/matijamarjanovic/home/config.gno | 4 +- .../gno.land/r/matijamarjanovic/home/home.gno | 6 +- .../r/matijamarjanovic/home/home_test.gno | 8 +- examples/gno.land/r/moul/config/config.gno | 2 +- examples/gno.land/r/moul/home/z2_filetest.gno | 2 +- examples/gno.land/r/moul/present/admin.gno | 8 +- .../r/n2p5/haystack/haystack_test.gno | 8 +- examples/gno.land/r/stefann/home/home.gno | 4 +- .../gno.land/r/stefann/home/home_test.gno | 24 ++--- .../upgrade_b/v1/v1.gno | 2 +- .../upgrade_b/v2/v2.gno | 2 +- .../nir1218_evaluation_proposal/committee.gno | 14 +-- .../committee_test.gno | 6 +- gno.land/cmd/gnoland/testdata/initctx.txtar | 2 +- .../cmd/gnoland/testdata/issue_1786.txtar | 8 +- .../cmd/gnoland/testdata/issue_2283.txtar | 4 +- .../testdata/issue_2283_cacheTypes.txtar | 4 +- gno.land/genesis/genesis_txs.jsonl | 2 +- gno.land/pkg/sdk/vm/handler_test.go | 2 +- gno.land/pkg/sdk/vm/keeper.go | 92 +++++++++---------- gno.land/pkg/sdk/vm/keeper_test.go | 42 ++++----- gnovm/pkg/gnolang/gonative.go | 2 +- gnovm/pkg/test/test.go | 22 ++--- gnovm/pkg/transpiler/transpiler_test.go | 6 +- gnovm/stdlibs/generated.go | 8 +- gnovm/stdlibs/std/banker.gno | 10 +- gnovm/stdlibs/std/banker.go | 12 +-- gnovm/stdlibs/std/context.go | 24 ++--- gnovm/stdlibs/std/native.gno | 12 +-- gnovm/stdlibs/std/native.go | 16 ++-- gnovm/stdlibs/std/native_test.go | 2 +- gnovm/tests/files/std2.gno | 2 +- gnovm/tests/files/std3.gno | 4 +- gnovm/tests/files/zrealm_initctx.gno | 4 +- gnovm/tests/files/zrealm_std0.gno | 2 +- gnovm/tests/files/zrealm_std1.gno | 4 +- gnovm/tests/files/zrealm_std2.gno | 4 +- gnovm/tests/files/zrealm_tests0.gno | 2 +- gnovm/tests/stdlibs/generated.go | 8 +- gnovm/tests/stdlibs/std/std.gno | 10 +- gnovm/tests/stdlibs/std/std.go | 18 ++-- .../test5.gno.land/genesis_txs.jsonl | 84 ++++++++--------- 145 files changed, 671 insertions(+), 671 deletions(-) diff --git a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-13.gno b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-13.gno index 0e5f2d57de9..fca8a9870d7 100644 --- a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-13.gno +++ b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-13.gno @@ -15,22 +15,22 @@ func TestFull(t *testing.T) { // Send two or more types of coins { - std.TestSetOrigCaller(bidder01) - std.TestSetOrigSend(std.Coins{{"ugnot", 0}, {"test", 1}}, nil) + std.TestSetOriginCaller(bidder01) + std.TestSetOriginSend(std.Coins{{"ugnot", 0}, {"test", 1}}, nil) shouldPanic(t, Bid) } // Send less than the highest bid { - std.TestSetOrigCaller(bidder01) - std.TestSetOrigSend(std.Coins{{"ugnot", 0}}, nil) + std.TestSetOriginCaller(bidder01) + std.TestSetOriginSend(std.Coins{{"ugnot", 0}}, nil) shouldPanic(t, Bid) } // Send more than the highest bid { - std.TestSetOrigCaller(bidder01) - std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil) + std.TestSetOriginCaller(bidder01) + std.TestSetOriginSend(std.Coins{{"ugnot", 1}}, nil) shouldNoPanic(t, Bid) shouldEqual(t, pendingReturns.Size(), 0) @@ -42,13 +42,13 @@ func TestFull(t *testing.T) { { // Send less amount than the current highest bid (current: 1) - std.TestSetOrigCaller(bidder02) - std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil) + std.TestSetOriginCaller(bidder02) + std.TestSetOriginSend(std.Coins{{"ugnot", 1}}, nil) shouldPanic(t, Bid) // Send more amount than the current highest bid (exceeded) - std.TestSetOrigCaller(bidder02) - std.TestSetOrigSend(std.Coins{{"ugnot", 2}}, nil) + std.TestSetOriginCaller(bidder02) + std.TestSetOriginSend(std.Coins{{"ugnot", 2}}, nil) shouldNoPanic(t, Bid) shouldEqual(t, highestBid, 2) diff --git a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-5.gno b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-5.gno index c1dfc2ae03e..873610d000b 100644 --- a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-5.gno +++ b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-5.gno @@ -3,7 +3,7 @@ func Bid() { panic("Exceeded auction end block") } - sentCoins := std.OrigSend() + sentCoins := std.OriginSend() if len(sentCoins) != 1 { panic("Send only one type of coin") } @@ -23,7 +23,7 @@ func Bid() { } // Update the top bidder address - highestBidder = std.OrigCaller() + highestBidder = std.OriginCaller() // Update the top bid amount highestBid = sentAmount } diff --git a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-6.gno b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-6.gno index b544d0017c4..b226d4a69a2 100644 --- a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-6.gno +++ b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-6.gno @@ -1,39 +1,39 @@ // Bid Function Test - Send Coin func TestBidCoins(t *testing.T) { // Sending two types of coins - std.TestSetOrigCaller(bidder01) - std.TestSetOrigSend(std.Coins{{"ugnot", 0}, {"test", 1}}, nil) + std.TestSetOriginCaller(bidder01) + std.TestSetOriginSend(std.Coins{{"ugnot", 0}, {"test", 1}}, nil) shouldPanic(t, Bid) // Sending lower amount than the current highest bid - std.TestSetOrigCaller(bidder01) - std.TestSetOrigSend(std.Coins{{"ugnot", 0}}, nil) + std.TestSetOriginCaller(bidder01) + std.TestSetOriginSend(std.Coins{{"ugnot", 0}}, nil) shouldPanic(t, Bid) // Sending more amount than the current highest bid (exceeded) - std.TestSetOrigCaller(bidder01) - std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil) + std.TestSetOriginCaller(bidder01) + std.TestSetOriginSend(std.Coins{{"ugnot", 1}}, nil) shouldNoPanic(t, Bid) } // Bid Function Test - Bid by two or more people func TestBidCoins(t *testing.T) { // bidder01 bidding with 1 coin - std.TestSetOrigCaller(bidder01) - std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil) + std.TestSetOriginCaller(bidder01) + std.TestSetOriginSend(std.Coins{{"ugnot", 1}}, nil) shouldNoPanic(t, Bid) shouldEqual(t, highestBid, 1) shouldEqual(t, highestBidder, bidder01) shouldEqual(t, pendingReturns.Size(), 0) // bidder02 bidding with 1 coin - std.TestSetOrigCaller(bidder02) - std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil) + std.TestSetOriginCaller(bidder02) + std.TestSetOriginSend(std.Coins{{"ugnot", 1}}, nil) shouldPanic(t, Bid) // bidder02 bidding with 2 coins - std.TestSetOrigCaller(bidder02) - std.TestSetOrigSend(std.Coins{{"ugnot", 2}}, nil) + std.TestSetOriginCaller(bidder02) + std.TestSetOriginSend(std.Coins{{"ugnot", 2}}, nil) shouldNoPanic(t, Bid) shouldEqual(t, highestBid, 2) shouldEqual(t, highestBidder, bidder02) diff --git a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-8.gno b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-8.gno index f1bda3ec56d..868fc0bf7fc 100644 --- a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-8.gno +++ b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-8.gno @@ -1,15 +1,15 @@ func Withdraw() { // Query the return amount to non-highest bidders - amount, _ := pendingReturns.Get(std.OrigCaller().String()) + amount, _ := pendingReturns.Get(std.OriginCaller().String()) if amount > 0 { // If there's an amount, reset the amount first, - pendingReturns.Set(std.OrigCaller().String(), 0) + pendingReturns.Set(std.OriginCaller().String(), 0) // Return the exceeded amount banker := std.GetBanker(std.BankerTypeRealmSend) pkgAddr := std.GetOrigPkgAddr() - banker.SendCoins(pkgAddr, std.OrigCaller(), std.Coins{{"ugnot", amount.(int64)}}) + banker.SendCoins(pkgAddr, std.OriginCaller(), std.Coins{{"ugnot", amount.(int64)}}) } } diff --git a/docs/assets/how-to-guides/write-simple-dapp/poll-2.gno b/docs/assets/how-to-guides/write-simple-dapp/poll-2.gno index 0521eb12cb2..22af32b34e0 100644 --- a/docs/assets/how-to-guides/write-simple-dapp/poll-2.gno +++ b/docs/assets/how-to-guides/write-simple-dapp/poll-2.gno @@ -40,7 +40,7 @@ func NewPoll(title, description string, deadline int64) string { // yes - true, no - false func Vote(id string, vote bool) string { // get txSender - txSender := std.OrigCaller() + txSender := std.OriginCaller() // get specific Poll from AVL tree pollRaw, exists := polls.Get(id) diff --git a/docs/concepts/effective-gno.md b/docs/concepts/effective-gno.md index 2b11ca693e6..f0e8863359e 100644 --- a/docs/concepts/effective-gno.md +++ b/docs/concepts/effective-gno.md @@ -169,10 +169,10 @@ var ( func init() { created = time.Now() - // std.OrigCaller in the context of realm initialisation is, + // std.OriginCaller in the context of realm initialisation is, // of course, the publisher of the realm :) // This can be better than hardcoding an admin address as a constant. - admin = std.OrigCaller() + admin = std.OriginCaller() // list is already initialized, so it will already contain "foo", "bar" and // the current time as existing items. list = append(list, admin.String()) @@ -498,10 +498,10 @@ One strategy is to look at the caller with `std.PrevRealm()`, which could be the EOA (Externally Owned Account), or the preceding realm in the call stack. Another approach is to look specifically at the EOA. For this, you should call -`std.OrigCaller()`, which returns the public address of the account that +`std.OriginCaller()`, which returns the public address of the account that signed the transaction. -TODO: explain when to use `std.OrigCaller`. +TODO: explain when to use `std.OriginCaller`. Internally, this call will look at the frame stack, which is basically the stack of callers including all the functions, anonymous functions, other realms, and diff --git a/docs/concepts/stdlibs/banker.md b/docs/concepts/stdlibs/banker.md index 873fac7c418..2ba4ca4402b 100644 --- a/docs/concepts/stdlibs/banker.md +++ b/docs/concepts/stdlibs/banker.md @@ -11,7 +11,7 @@ The Banker module can be cast into 4 subtypes of bankers that expose different f ### Banker Types 1. `BankerTypeReadonly` - read-only access to coin balances -2. `BankerTypeOrigSend` - full access to coins sent with the transaction that called the banker +2. `BankerTypeOriginSend` - full access to coins sent with the transaction that called the banker 3. `BankerTypeRealmSend` - full access to coins that the realm itself owns, including the ones sent with the transaction 4. `BankerTypeRealmIssue` - able to issue new coins diff --git a/docs/how-to-guides/porting-solidity-to-gno.md b/docs/how-to-guides/porting-solidity-to-gno.md index 7b57cf388c5..64acd30b426 100644 --- a/docs/how-to-guides/porting-solidity-to-gno.md +++ b/docs/how-to-guides/porting-solidity-to-gno.md @@ -353,7 +353,7 @@ func Bid() { panic("Exceeded auction end block") } - sentCoins := std.OrigSend() + sentCoins := std.OriginSend() if len(sentCoins) != 1 { panic("Send only one type of coin") } @@ -373,7 +373,7 @@ func Bid() { } // Update the top bidder address - highestBidder = std.OrigCaller() + highestBidder = std.OriginCaller() // Update the top bid amount highestBid = sentAmount } @@ -387,39 +387,39 @@ func Bid() { // Bid Function Test - Send Coin func TestBidCoins(t *testing.T) { // Sending two types of coins - std.TestSetOrigCaller(bidder01) - std.TestSetOrigSend(std.Coins{{"ugnot", 0}, {"test", 1}}, nil) + std.TestSetOriginCaller(bidder01) + std.TestSetOriginSend(std.Coins{{"ugnot", 0}, {"test", 1}}, nil) shouldPanic(t, Bid) // Sending lower amount than the current highest bid - std.TestSetOrigCaller(bidder01) - std.TestSetOrigSend(std.Coins{{"ugnot", 0}}, nil) + std.TestSetOriginCaller(bidder01) + std.TestSetOriginSend(std.Coins{{"ugnot", 0}}, nil) shouldPanic(t, Bid) // Sending more amount than the current highest bid (exceeded) - std.TestSetOrigCaller(bidder01) - std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil) + std.TestSetOriginCaller(bidder01) + std.TestSetOriginSend(std.Coins{{"ugnot", 1}}, nil) shouldNoPanic(t, Bid) } // Bid Function Test - Bid by two or more people func TestBidCoins(t *testing.T) { // bidder01 bidding with 1 coin - std.TestSetOrigCaller(bidder01) - std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil) + std.TestSetOriginCaller(bidder01) + std.TestSetOriginSend(std.Coins{{"ugnot", 1}}, nil) shouldNoPanic(t, Bid) shouldEqual(t, highestBid, 1) shouldEqual(t, highestBidder, bidder01) shouldEqual(t, pendingReturns.Size(), 0) // bidder02 bidding with 1 coin - std.TestSetOrigCaller(bidder02) - std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil) + std.TestSetOriginCaller(bidder02) + std.TestSetOriginSend(std.Coins{{"ugnot", 1}}, nil) shouldPanic(t, Bid) // bidder02 bidding with 2 coins - std.TestSetOrigCaller(bidder02) - std.TestSetOrigSend(std.Coins{{"ugnot", 2}}, nil) + std.TestSetOriginCaller(bidder02) + std.TestSetOriginSend(std.Coins{{"ugnot", 2}}, nil) shouldNoPanic(t, Bid) shouldEqual(t, highestBid, 2) shouldEqual(t, highestBidder, bidder02) @@ -466,17 +466,17 @@ function withdraw() external returns (bool) { ```go func Withdraw() { // Query the return amount to non-highest bidders - amount, _ := pendingReturns.Get(std.OrigCaller().String()) + amount, _ := pendingReturns.Get(std.OriginCaller().String()) if amount > 0 { // If there's an amount, reset the amount first, - pendingReturns.Set(std.OrigCaller().String(), 0) + pendingReturns.Set(std.OriginCaller().String(), 0) // Return the exceeded amount banker := std.GetBanker(std.BankerTypeRealmSend) pkgAddr := std.GetOrigPkgAddr() - banker.SendCoins(pkgAddr, std.OrigCaller(), std.Coins{{"ugnot", amount.(int64)}}) + banker.SendCoins(pkgAddr, std.OriginCaller(), std.Coins{{"ugnot", amount.(int64)}}) } } ``` @@ -620,22 +620,22 @@ func TestFull(t *testing.T) { // Send two or more types of coins { - std.TestSetOrigCaller(bidder01) - std.TestSetOrigSend(std.Coins{{"ugnot", 0}, {"test", 1}}, nil) + std.TestSetOriginCaller(bidder01) + std.TestSetOriginSend(std.Coins{{"ugnot", 0}, {"test", 1}}, nil) shouldPanic(t, Bid) } // Send less than the highest bid { - std.TestSetOrigCaller(bidder01) - std.TestSetOrigSend(std.Coins{{"ugnot", 0}}, nil) + std.TestSetOriginCaller(bidder01) + std.TestSetOriginSend(std.Coins{{"ugnot", 0}}, nil) shouldPanic(t, Bid) } // Send more than the highest bid { - std.TestSetOrigCaller(bidder01) - std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil) + std.TestSetOriginCaller(bidder01) + std.TestSetOriginSend(std.Coins{{"ugnot", 1}}, nil) shouldNoPanic(t, Bid) shouldEqual(t, pendingReturns.Size(), 0) @@ -647,13 +647,13 @@ func TestFull(t *testing.T) { { // Send less amount than the current highest bid (current: 1) - std.TestSetOrigCaller(bidder02) - std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil) + std.TestSetOriginCaller(bidder02) + std.TestSetOriginSend(std.Coins{{"ugnot", 1}}, nil) shouldPanic(t, Bid) // Send more amount than the current highest bid (exceeded) - std.TestSetOrigCaller(bidder02) - std.TestSetOrigSend(std.Coins{{"ugnot", 2}}, nil) + std.TestSetOriginCaller(bidder02) + std.TestSetOriginSend(std.Coins{{"ugnot", 2}}, nil) shouldNoPanic(t, Bid) shouldEqual(t, highestBid, 2) diff --git a/docs/how-to-guides/write-simple-dapp.md b/docs/how-to-guides/write-simple-dapp.md index 50c3ffe337a..e75ea5bc226 100644 --- a/docs/how-to-guides/write-simple-dapp.md +++ b/docs/how-to-guides/write-simple-dapp.md @@ -174,7 +174,7 @@ func NewPoll(title, description string, deadline int64) string { // yes - true, no - false func Vote(id string, vote bool) string { // get txSender - txSender := std.OrigCaller() + txSender := std.OriginCaller() // get specific Poll from AVL tree pollRaw, exists := polls.Get(id) diff --git a/docs/reference/stdlibs/std/banker.md b/docs/reference/stdlibs/std/banker.md index b60b55ee93b..ac1a95f9fd4 100644 --- a/docs/reference/stdlibs/std/banker.md +++ b/docs/reference/stdlibs/std/banker.md @@ -10,7 +10,7 @@ type BankerType uint8 const ( BankerTypeReadonly BankerType = iota - BankerTypeOrigSend + BankerTypeOriginSend BankerTypeRealmSend BankerTypeRealmIssue ) @@ -29,7 +29,7 @@ Returns `Banker` of the specified type. #### Parameters - `BankerType` - type of Banker to get: - `BankerTypeReadonly` - read-only access to coin balances - - `BankerTypeOrigSend` - full access to coins sent with the transaction that calls the banker + - `BankerTypeOriginSend` - full access to coins sent with the transaction that calls the banker - `BankerTypeRealmSend` - full access to coins that the realm itself owns, including the ones sent with the transaction - `BankerTypeRealmIssue` - able to issue new coins diff --git a/docs/reference/stdlibs/std/chain.md b/docs/reference/stdlibs/std/chain.md index 38abaac217a..08bca4bfedf 100644 --- a/docs/reference/stdlibs/std/chain.md +++ b/docs/reference/stdlibs/std/chain.md @@ -77,27 +77,27 @@ height := std.GetHeight() ``` --- -## OrigSend +## OriginSend ```go -func OrigSend() Coins +func OriginSend() Coins ``` Returns the `Coins` that were sent along with the calling transaction. #### Usage ```go -coinsSent := std.OrigSend() +coinsSent := std.OriginSend() ``` --- -## OrigCaller +## OriginCaller ```go -func OrigCaller() Address +func OriginCaller() Address ``` Returns the original signer of the transaction. #### Usage ```go -caller := std.OrigCaller() +caller := std.OriginCaller() ``` --- diff --git a/docs/reference/stdlibs/std/testing.md b/docs/reference/stdlibs/std/testing.md index 8a95ecf7827..62683322ccb 100644 --- a/docs/reference/stdlibs/std/testing.md +++ b/docs/reference/stdlibs/std/testing.md @@ -6,9 +6,9 @@ id: testing ```go func TestSkipHeights(count int64) -func TestSetOrigCaller(addr Address) +func TestSetOriginCaller(addr Address) func TestSetOrigPkgAddr(addr Address) -func TestSetOrigSend(sent, spent Coins) +func TestSetOriginSend(sent, spent Coins) func TestIssueCoins(addr Address, coins Coins) func TestSetRealm(realm Realm) func NewUserRealm(address Address) Realm @@ -32,16 +32,16 @@ std.TestSkipHeights(100) ``` --- -## TestSetOrigCaller +## TestSetOriginCaller ```go -func TestSetOrigCaller(addr Address) +func TestSetOriginCaller(addr Address) ``` Sets the current caller of the transaction to **addr**. #### Usage ```go -std.TestSetOrigCaller(std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")) +std.TestSetOriginCaller(std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")) ``` --- @@ -59,16 +59,16 @@ std.TestSetOrigPkgAddr(std.Address("g1ecely4gjy0yl6s9kt409ll330q9hk2lj9ls3ec")) --- -## TestSetOrigSend +## TestSetOriginSend ```go -func TestSetOrigSend(sent, spent Coins) +func TestSetOriginSend(sent, spent Coins) ``` Sets the sent & spent coins for the current context. #### Usage ```go -std.TestSetOrigSend(sent, spent Coins) +std.TestSetOriginSend(sent, spent Coins) ``` --- diff --git a/examples/gno.land/p/demo/gnorkle/gnorkle/instance.gno b/examples/gno.land/p/demo/gnorkle/gnorkle/instance.gno index 9d33f71ccd2..00fbff0cdbf 100644 --- a/examples/gno.land/p/demo/gnorkle/gnorkle/instance.gno +++ b/examples/gno.land/p/demo/gnorkle/gnorkle/instance.gno @@ -106,7 +106,7 @@ type PostMessageHandler interface { // TODO: Consider further message types that could allow administrative action such as modifying // a feed's whitelist without the owner of this oracle having to maintain a reference to it. func (i *Instance) HandleMessage(msg string, postHandler PostMessageHandler) (string, error) { - caller := string(std.OrigCaller()) + caller := string(std.OriginCaller()) funcType, msg := message.ParseFunc(msg) diff --git a/examples/gno.land/p/demo/grc/grc1155/basic_grc1155_token.gno b/examples/gno.land/p/demo/grc/grc1155/basic_grc1155_token.gno index 8b03a99a645..1ed5f93d5fd 100644 --- a/examples/gno.land/p/demo/grc/grc1155/basic_grc1155_token.gno +++ b/examples/gno.land/p/demo/grc/grc1155/basic_grc1155_token.gno @@ -62,7 +62,7 @@ func (s *basicGRC1155Token) SetApprovalForAll(operator std.Address, approved boo return ErrInvalidAddress } - caller := std.OrigCaller() + caller := std.OriginCaller() return s.setApprovalForAll(caller, operator, approved) } @@ -85,7 +85,7 @@ func (s *basicGRC1155Token) IsApprovedForAll(owner, operator std.Address) bool { // contract recipients are aware of the GRC1155 protocol to prevent // tokens from being forever locked. func (s *basicGRC1155Token) SafeTransferFrom(from, to std.Address, tid TokenID, amount uint64) error { - caller := std.OrigCaller() + caller := std.OriginCaller() if !s.IsApprovedForAll(caller, from) { return ErrCallerIsNotOwnerOrApproved } @@ -108,7 +108,7 @@ func (s *basicGRC1155Token) SafeTransferFrom(from, to std.Address, tid TokenID, // contract recipients are aware of the GRC1155 protocol to prevent // tokens from being forever locked. func (s *basicGRC1155Token) SafeBatchTransferFrom(from, to std.Address, batch []TokenID, amounts []uint64) error { - caller := std.OrigCaller() + caller := std.OriginCaller() if !s.IsApprovedForAll(caller, from) { return ErrCallerIsNotOwnerOrApproved } @@ -130,7 +130,7 @@ func (s *basicGRC1155Token) SafeBatchTransferFrom(from, to std.Address, batch [] // Creates `amount` tokens of token type `id`, and assigns them to `to`. Also checks that // contract recipients are using GRC1155 protocol. func (s *basicGRC1155Token) SafeMint(to std.Address, tid TokenID, amount uint64) error { - caller := std.OrigCaller() + caller := std.OriginCaller() err := s.mintBatch(to, []TokenID{tid}, []uint64{amount}) if err != nil { @@ -149,7 +149,7 @@ func (s *basicGRC1155Token) SafeMint(to std.Address, tid TokenID, amount uint64) // Batch version of `SafeMint()`. Also checks that // contract recipients are using GRC1155 protocol. func (s *basicGRC1155Token) SafeBatchMint(to std.Address, batch []TokenID, amounts []uint64) error { - caller := std.OrigCaller() + caller := std.OriginCaller() err := s.mintBatch(to, batch, amounts) if err != nil { @@ -167,7 +167,7 @@ func (s *basicGRC1155Token) SafeBatchMint(to std.Address, batch []TokenID, amoun // Destroys `amount` tokens of token type `id` from `from`. func (s *basicGRC1155Token) Burn(from std.Address, tid TokenID, amount uint64) error { - caller := std.OrigCaller() + caller := std.OriginCaller() err := s.burnBatch(from, []TokenID{tid}, []uint64{amount}) if err != nil { @@ -181,7 +181,7 @@ func (s *basicGRC1155Token) Burn(from std.Address, tid TokenID, amount uint64) e // Batch version of `Burn()` func (s *basicGRC1155Token) BatchBurn(from std.Address, batch []TokenID, amounts []uint64) error { - caller := std.OrigCaller() + caller := std.OriginCaller() err := s.burnBatch(from, batch, amounts) if err != nil { @@ -225,7 +225,7 @@ func (s *basicGRC1155Token) safeBatchTransferFrom(from, to std.Address, batch [] return ErrCannotTransferToSelf } - caller := std.OrigCaller() + caller := std.OriginCaller() s.beforeTokenTransfer(caller, from, to, batch, amounts) for i := 0; i < len(batch); i++ { @@ -265,7 +265,7 @@ func (s *basicGRC1155Token) mintBatch(to std.Address, batch []TokenID, amounts [ return ErrInvalidAddress } - caller := std.OrigCaller() + caller := std.OriginCaller() s.beforeTokenTransfer(caller, zeroAddress, to, batch, amounts) for i := 0; i < len(batch); i++ { @@ -294,7 +294,7 @@ func (s *basicGRC1155Token) burnBatch(from std.Address, batch []TokenID, amounts return ErrInvalidAddress } - caller := std.OrigCaller() + caller := std.OriginCaller() s.beforeTokenTransfer(caller, from, zeroAddress, batch, amounts) for i := 0; i < len(batch); i++ { diff --git a/examples/gno.land/p/demo/grc/grc1155/basic_grc1155_token_test.gno b/examples/gno.land/p/demo/grc/grc1155/basic_grc1155_token_test.gno index 4c1bf5d3828..c3b08f11c45 100644 --- a/examples/gno.land/p/demo/grc/grc1155/basic_grc1155_token_test.gno +++ b/examples/gno.land/p/demo/grc/grc1155/basic_grc1155_token_test.gno @@ -91,7 +91,7 @@ func TestSetApprovalForAll(t *testing.T) { dummy := NewBasicGRC1155Token(dummyURI) uassert.True(t, dummy != nil, "should not be nil") - caller := std.OrigCaller() + caller := std.OriginCaller() addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") isApprovedForAll := dummy.IsApprovedForAll(caller, addr) @@ -114,7 +114,7 @@ func TestSafeTransferFrom(t *testing.T) { dummy := NewBasicGRC1155Token(dummyURI) uassert.True(t, dummy != nil, "should not be nil") - caller := std.OrigCaller() + caller := std.OriginCaller() addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") tid := TokenID("1") @@ -145,7 +145,7 @@ func TestSafeBatchTransferFrom(t *testing.T) { dummy := NewBasicGRC1155Token(dummyURI) uassert.True(t, dummy != nil, "should not be nil") - caller := std.OrigCaller() + caller := std.OriginCaller() addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") tid1 := TokenID("1") diff --git a/examples/gno.land/p/demo/grc/grc20/tellers_test.gno b/examples/gno.land/p/demo/grc/grc20/tellers_test.gno index 2a724964edc..3175359df85 100644 --- a/examples/gno.land/p/demo/grc/grc20/tellers_test.gno +++ b/examples/gno.land/p/demo/grc/grc20/tellers_test.gno @@ -115,12 +115,12 @@ func TestCallerTeller(t *testing.T) { checkBalances(1000, 0, 0) checkAllowances(0, 0, 0, 0, 0, 0) - std.TestSetOrigCaller(alice) + std.TestSetOriginCaller(alice) urequire.NoError(t, teller.Approve(bob, 600)) checkBalances(1000, 0, 0) checkAllowances(600, 0, 0, 0, 0, 0) - std.TestSetOrigCaller(bob) + std.TestSetOriginCaller(bob) urequire.Error(t, teller.TransferFrom(alice, carl, 700)) checkBalances(1000, 0, 0) checkAllowances(600, 0, 0, 0, 0, 0) diff --git a/examples/gno.land/p/demo/grc/grc721/basic_nft_test.gno b/examples/gno.land/p/demo/grc/grc721/basic_nft_test.gno index 6375b0307a8..84f43f9457c 100644 --- a/examples/gno.land/p/demo/grc/grc721/basic_nft_test.gno +++ b/examples/gno.land/p/demo/grc/grc721/basic_nft_test.gno @@ -259,7 +259,7 @@ func TestSetTokenURI(t *testing.T) { addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") tokenURI := "http://example.com/token" - std.TestSetOrigCaller(std.Address(addr1)) // addr1 + std.TestSetOriginCaller(std.Address(addr1)) // addr1 dummy.mint(addr1, TokenID("1")) _, derr := dummy.SetTokenURI(TokenID("1"), TokenURI(tokenURI)) @@ -269,13 +269,13 @@ func TestSetTokenURI(t *testing.T) { _, err := dummy.SetTokenURI(TokenID("3"), TokenURI(tokenURI)) uassert.ErrorIs(t, err, ErrInvalidTokenId) - std.TestSetOrigCaller(std.Address(addr2)) // addr2 + std.TestSetOriginCaller(std.Address(addr2)) // addr2 _, cerr := dummy.SetTokenURI(TokenID("1"), TokenURI(tokenURI)) // addr2 trying to set URI for token 1 uassert.ErrorIs(t, cerr, ErrCallerIsNotOwner) // Test case: Retrieving TokenURI - std.TestSetOrigCaller(std.Address(addr1)) // addr1 + std.TestSetOriginCaller(std.Address(addr1)) // addr1 dummyTokenURI, err := dummy.TokenURI(TokenID("1")) uassert.NoError(t, err, "TokenURI error") diff --git a/examples/gno.land/p/demo/grc/grc721/grc721_metadata_test.gno b/examples/gno.land/p/demo/grc/grc721/grc721_metadata_test.gno index ad002a7c98e..b1de830fb36 100644 --- a/examples/gno.land/p/demo/grc/grc721/grc721_metadata_test.gno +++ b/examples/gno.land/p/demo/grc/grc721/grc721_metadata_test.gno @@ -31,7 +31,7 @@ func TestSetMetadata(t *testing.T) { youtubeURL := "test" // Set the original caller to addr1 - std.TestSetOrigCaller(addr1) // addr1 + std.TestSetOriginCaller(addr1) // addr1 // Mint a new token for addr1 dummy.mint(addr1, TokenID("1")) @@ -69,7 +69,7 @@ func TestSetMetadata(t *testing.T) { uassert.ErrorIs(t, err, ErrInvalidTokenId) // Set the original caller to addr2 - std.TestSetOrigCaller(addr2) // addr2 + std.TestSetOriginCaller(addr2) // addr2 // Try to set metadata for token 1 from addr2 (should fail) cerr := dummy.SetTokenMetadata(TokenID("1"), Metadata{ @@ -88,7 +88,7 @@ func TestSetMetadata(t *testing.T) { uassert.ErrorIs(t, cerr, ErrCallerIsNotOwner) // Set the original caller back to addr1 - std.TestSetOrigCaller(addr1) // addr1 + std.TestSetOriginCaller(addr1) // addr1 // Retrieve metadata for token 1 dummyMetadata, err := dummy.TokenMetadata(TokenID("1")) diff --git a/examples/gno.land/p/demo/grc/grc721/grc721_royalty_test.gno b/examples/gno.land/p/demo/grc/grc721/grc721_royalty_test.gno index 7893453a1c6..f7f61b273f4 100644 --- a/examples/gno.land/p/demo/grc/grc721/grc721_royalty_test.gno +++ b/examples/gno.land/p/demo/grc/grc721/grc721_royalty_test.gno @@ -21,7 +21,7 @@ func TestSetTokenRoyalty(t *testing.T) { salePrice := uint64(1000) expectRoyaltyAmount := uint64(100) - std.TestSetOrigCaller(addr1) // addr1 + std.TestSetOriginCaller(addr1) // addr1 dummy.mint(addr1, TokenID("1")) @@ -38,7 +38,7 @@ func TestSetTokenRoyalty(t *testing.T) { }) uassert.ErrorIs(t, derr, ErrInvalidTokenId) - std.TestSetOrigCaller(addr2) // addr2 + std.TestSetOriginCaller(addr2) // addr2 cerr := dummy.SetTokenRoyalty(TokenID("1"), RoyaltyInfo{ PaymentAddress: paymentAddress, @@ -61,7 +61,7 @@ func TestSetTokenRoyalty(t *testing.T) { uassert.ErrorIs(t, perr, ErrInvalidRoyaltyPercentage) // Test case: Retrieving Royalty Info - std.TestSetOrigCaller(addr1) // addr1 + std.TestSetOriginCaller(addr1) // addr1 dummyPaymentAddress, dummyRoyaltyAmount, rerr := dummy.RoyaltyInfo(TokenID("1"), salePrice) uassert.NoError(t, rerr, "RoyaltyInfo error") diff --git a/examples/gno.land/p/demo/memeland/memeland_test.gno b/examples/gno.land/p/demo/memeland/memeland_test.gno index 95065b8cd64..bf1084a6f55 100644 --- a/examples/gno.land/p/demo/memeland/memeland_test.gno +++ b/examples/gno.land/p/demo/memeland/memeland_test.gno @@ -122,7 +122,7 @@ func TestGetPostsInRangeByUpvote(t *testing.T) { m.Upvote(id2) // Change caller so avoid double upvote panic - std.TestSetOrigCaller(testutils.TestAddress("alice")) + std.TestSetOriginCaller(testutils.TestAddress("alice")) m.Upvote(id1) // Final upvote count: @@ -236,21 +236,21 @@ func TestUpvote(t *testing.T) { func TestDelete(t *testing.T) { alice := testutils.TestAddress("alice") - std.TestSetOrigCaller(alice) + std.TestSetOriginCaller(alice) // Alice is admin m := NewMemeland() // Set caller to Bob bob := testutils.TestAddress("bob") - std.TestSetOrigCaller(bob) + std.TestSetOriginCaller(bob) // Bob adds post to Memeland now := time.Now() postID := m.PostMeme("Meme #1", now.Unix()) // Alice removes Bob's post - std.TestSetOrigCaller(alice) + std.TestSetOriginCaller(alice) id := m.RemovePost(postID) uassert.Equal(t, postID, id, "post IDs not matching") @@ -259,7 +259,7 @@ func TestDelete(t *testing.T) { func TestDeleteByNonAdmin(t *testing.T) { alice := testutils.TestAddress("alice") - std.TestSetOrigCaller(alice) + std.TestSetOriginCaller(alice) m := NewMemeland() @@ -269,7 +269,7 @@ func TestDeleteByNonAdmin(t *testing.T) { // Bob will try to delete meme posted by Alice, which should fail bob := testutils.TestAddress("bob") - std.TestSetOrigCaller(bob) + std.TestSetOriginCaller(bob) defer func() { if r := recover(); r == nil { diff --git a/examples/gno.land/p/demo/microblog/microblog.gno b/examples/gno.land/p/demo/microblog/microblog.gno index fbc8510ea46..b94398f1cf0 100644 --- a/examples/gno.land/p/demo/microblog/microblog.gno +++ b/examples/gno.land/p/demo/microblog/microblog.gno @@ -48,7 +48,7 @@ func (m *Microblog) GetPages() []*Page { } func (m *Microblog) NewPost(text string) error { - author := std.OrigCaller() + author := std.OriginCaller() _, found := m.Pages.Get(author.String()) if !found { // make a new page for the new author diff --git a/examples/gno.land/p/demo/ownable/exts/authorizable/authorizable_test.gno b/examples/gno.land/p/demo/ownable/exts/authorizable/authorizable_test.gno index 10a5e411bdb..8fb1b9422f6 100644 --- a/examples/gno.land/p/demo/ownable/exts/authorizable/authorizable_test.gno +++ b/examples/gno.land/p/demo/ownable/exts/authorizable/authorizable_test.gno @@ -16,7 +16,7 @@ var ( func TestNewAuthorizable(t *testing.T) { std.TestSetRealm(std.NewUserRealm(alice)) - std.TestSetOrigCaller(alice) // TODO(bug, issue #2371): should not be needed + std.TestSetOriginCaller(alice) // TODO(bug, issue #2371): should not be needed a := NewAuthorizable() got := a.Owner() @@ -39,7 +39,7 @@ func TestNewAuthorizableWithAddress(t *testing.T) { func TestCallerOnAuthList(t *testing.T) { a := NewAuthorizableWithAddress(alice) std.TestSetRealm(std.NewUserRealm(alice)) - std.TestSetOrigCaller(alice) + std.TestSetOriginCaller(alice) if err := a.CallerOnAuthList(); err == ErrNotInAuthList { t.Fatalf("expected alice to be on the list") @@ -49,7 +49,7 @@ func TestCallerOnAuthList(t *testing.T) { func TestNotCallerOnAuthList(t *testing.T) { a := NewAuthorizableWithAddress(alice) std.TestSetRealm(std.NewUserRealm(bob)) - std.TestSetOrigCaller(bob) + std.TestSetOriginCaller(bob) if err := a.CallerOnAuthList(); err == nil { t.Fatalf("expected bob to not be on the list") @@ -59,14 +59,14 @@ func TestNotCallerOnAuthList(t *testing.T) { func TestAddToAuthList(t *testing.T) { a := NewAuthorizableWithAddress(alice) std.TestSetRealm(std.NewUserRealm(alice)) - std.TestSetOrigCaller(alice) + std.TestSetOriginCaller(alice) if err := a.AddToAuthList(bob); err != nil { t.Fatalf("Expected no error, got %v", err) } std.TestSetRealm(std.NewUserRealm(bob)) - std.TestSetOrigCaller(bob) + std.TestSetOriginCaller(bob) if err := a.AddToAuthList(bob); err == nil { t.Fatalf("Expected AddToAuth to error while bob called it, but it didn't") @@ -76,7 +76,7 @@ func TestAddToAuthList(t *testing.T) { func TestDeleteFromList(t *testing.T) { a := NewAuthorizableWithAddress(alice) std.TestSetRealm(std.NewUserRealm(alice)) - std.TestSetOrigCaller(alice) + std.TestSetOriginCaller(alice) if err := a.AddToAuthList(bob); err != nil { t.Fatalf("Expected no error, got %v", err) @@ -87,7 +87,7 @@ func TestDeleteFromList(t *testing.T) { } std.TestSetRealm(std.NewUserRealm(bob)) - std.TestSetOrigCaller(bob) + std.TestSetOriginCaller(bob) // Try an unauthorized deletion if err := a.DeleteFromAuthList(alice); err == nil { @@ -95,7 +95,7 @@ func TestDeleteFromList(t *testing.T) { } std.TestSetRealm(std.NewUserRealm(alice)) - std.TestSetOrigCaller(alice) + std.TestSetOriginCaller(alice) if err := a.DeleteFromAuthList(charlie); err != nil { t.Fatalf("Expected no error, got %v", err) @@ -104,11 +104,11 @@ func TestDeleteFromList(t *testing.T) { func TestAssertOnList(t *testing.T) { std.TestSetRealm(std.NewUserRealm(alice)) - std.TestSetOrigCaller(alice) + std.TestSetOriginCaller(alice) a := NewAuthorizableWithAddress(alice) std.TestSetRealm(std.NewUserRealm(bob)) - std.TestSetOrigCaller(bob) + std.TestSetOriginCaller(bob) uassert.PanicsWithMessage(t, ErrNotInAuthList.Error(), func() { a.AssertOnAuthList() diff --git a/examples/gno.land/p/demo/ownable/ownable_test.gno b/examples/gno.land/p/demo/ownable/ownable_test.gno index f58af9642c6..9407445848e 100644 --- a/examples/gno.land/p/demo/ownable/ownable_test.gno +++ b/examples/gno.land/p/demo/ownable/ownable_test.gno @@ -16,7 +16,7 @@ var ( func TestNew(t *testing.T) { std.TestSetRealm(std.NewUserRealm(alice)) - std.TestSetOrigCaller(alice) // TODO(bug): should not be needed + std.TestSetOriginCaller(alice) // TODO(bug): should not be needed o := New() got := o.Owner() @@ -50,7 +50,7 @@ func TestCallerIsOwner(t *testing.T) { unauthorizedCaller := bob std.TestSetRealm(std.NewUserRealm(unauthorizedCaller)) - std.TestSetOrigCaller(unauthorizedCaller) // TODO(bug): should not be needed + std.TestSetOriginCaller(unauthorizedCaller) // TODO(bug): should not be needed uassert.False(t, o.CallerIsOwner()) } @@ -71,12 +71,12 @@ func TestDropOwnership(t *testing.T) { func TestErrUnauthorized(t *testing.T) { std.TestSetRealm(std.NewUserRealm(alice)) - std.TestSetOrigCaller(alice) // TODO(bug): should not be needed + std.TestSetOriginCaller(alice) // TODO(bug): should not be needed o := New() std.TestSetRealm(std.NewUserRealm(bob)) - std.TestSetOrigCaller(bob) // TODO(bug): should not be needed + std.TestSetOriginCaller(bob) // TODO(bug): should not be needed uassert.ErrorContains(t, o.TransferOwnership(alice), ErrUnauthorized.Error()) uassert.ErrorContains(t, o.DropOwnership(), ErrUnauthorized.Error()) diff --git a/examples/gno.land/p/demo/pausable/pausable_test.gno b/examples/gno.land/p/demo/pausable/pausable_test.gno index c9557245bdf..5678c867dc7 100644 --- a/examples/gno.land/p/demo/pausable/pausable_test.gno +++ b/examples/gno.land/p/demo/pausable/pausable_test.gno @@ -14,7 +14,7 @@ var ( ) func TestNew(t *testing.T) { - std.TestSetOrigCaller(firstCaller) + std.TestSetOriginCaller(firstCaller) result := New() @@ -23,17 +23,17 @@ func TestNew(t *testing.T) { } func TestNewFromOwnable(t *testing.T) { - std.TestSetOrigCaller(firstCaller) + std.TestSetOriginCaller(firstCaller) o := ownable.New() - std.TestSetOrigCaller(secondCaller) + std.TestSetOriginCaller(secondCaller) result := NewFromOwnable(o) urequire.Equal(t, firstCaller.String(), result.Owner().String()) } func TestSetUnpaused(t *testing.T) { - std.TestSetOrigCaller(firstCaller) + std.TestSetOriginCaller(firstCaller) result := New() result.Unpause() @@ -42,7 +42,7 @@ func TestSetUnpaused(t *testing.T) { } func TestSetPaused(t *testing.T) { - std.TestSetOrigCaller(firstCaller) + std.TestSetOriginCaller(firstCaller) result := New() result.Pause() @@ -51,7 +51,7 @@ func TestSetPaused(t *testing.T) { } func TestIsPaused(t *testing.T) { - std.TestSetOrigCaller(firstCaller) + std.TestSetOriginCaller(firstCaller) result := New() urequire.False(t, result.IsPaused(), "Expected result to be unpaused") diff --git a/examples/gno.land/p/demo/simpledao/dao.gno b/examples/gno.land/p/demo/simpledao/dao.gno index 8728d825433..a8194eecdea 100644 --- a/examples/gno.land/p/demo/simpledao/dao.gno +++ b/examples/gno.land/p/demo/simpledao/dao.gno @@ -56,7 +56,7 @@ func (s *SimpleDAO) Propose(request dao.ProposalRequest) (uint64, error) { var ( caller = getDAOCaller() - sentCoins = std.OrigSend() // Get the sent coins, if any + sentCoins = std.OriginSend() // Get the sent coins, if any canCoverFee = sentCoins.AmountOf("ugnot") >= minProposalFee.Amount ) @@ -168,7 +168,7 @@ func (s *SimpleDAO) VoteOnProposal(id uint64, option dao.VoteOption) error { func (s *SimpleDAO) ExecuteProposal(id uint64) error { var ( caller = getDAOCaller() - sentCoins = std.OrigSend() // Get the sent coins, if any + sentCoins = std.OriginSend() // Get the sent coins, if any canCoverFee = sentCoins.AmountOf("ugnot") >= minExecuteFee.Amount ) @@ -219,5 +219,5 @@ func (s *SimpleDAO) ExecuteProposal(id uint64) error { // However, the current MsgRun context does not persist escaping the main() scope. // Until a better solution is developed, this enables proposals to be made through a package deployment + init() func getDAOCaller() std.Address { - return std.OrigCaller() + return std.OriginCaller() } diff --git a/examples/gno.land/p/demo/simpledao/dao_test.gno b/examples/gno.land/p/demo/simpledao/dao_test.gno index 46251e24dad..601dac8db77 100644 --- a/examples/gno.land/p/demo/simpledao/dao_test.gno +++ b/examples/gno.land/p/demo/simpledao/dao_test.gno @@ -74,7 +74,7 @@ func TestSimpleDAO_Propose(t *testing.T) { s = New(ms) ) - std.TestSetOrigSend(sentCoins, std.Coins{}) + std.TestSetOriginSend(sentCoins, std.Coins{}) _, err := s.Propose(dao.ProposalRequest{ Executor: ex, @@ -121,7 +121,7 @@ func TestSimpleDAO_Propose(t *testing.T) { // Set the sent coins to be lower // than the proposal fee - std.TestSetOrigSend(sentCoins, std.Coins{}) + std.TestSetOriginSend(sentCoins, std.Coins{}) _, err := s.Propose(dao.ProposalRequest{ Executor: ex, @@ -171,8 +171,8 @@ func TestSimpleDAO_Propose(t *testing.T) { // Set the sent coins to be enough // to cover the fee - std.TestSetOrigSend(sentCoins, std.Coins{}) - std.TestSetOrigCaller(proposer) + std.TestSetOriginSend(sentCoins, std.Coins{}) + std.TestSetOriginCaller(proposer) // Make sure the proposal was added id, err := s.Propose(dao.ProposalRequest{ @@ -221,7 +221,7 @@ func TestSimpleDAO_VoteOnProposal(t *testing.T) { s = New(ms) ) - std.TestSetOrigCaller(voter) + std.TestSetOriginCaller(voter) // Attempt to vote on the proposal uassert.ErrorContains( @@ -251,7 +251,7 @@ func TestSimpleDAO_VoteOnProposal(t *testing.T) { s = New(ms) ) - std.TestSetOrigCaller(voter) + std.TestSetOriginCaller(voter) // Attempt to vote on the proposal uassert.ErrorContains( @@ -285,7 +285,7 @@ func TestSimpleDAO_VoteOnProposal(t *testing.T) { } ) - std.TestSetOrigCaller(voter) + std.TestSetOriginCaller(voter) // Add an initial proposal id, err := s.addProposal(prop) @@ -327,7 +327,7 @@ func TestSimpleDAO_VoteOnProposal(t *testing.T) { } ) - std.TestSetOrigCaller(voter) + std.TestSetOriginCaller(voter) // Cast the initial vote urequire.NoError(t, prop.tally.castVote(member, dao.YesVote)) @@ -386,7 +386,7 @@ func TestSimpleDAO_VoteOnProposal(t *testing.T) { majorityIndex := (len(members)*2)/3 + 1 // 2/3+ for _, m := range members[:majorityIndex] { - std.TestSetOrigCaller(m.Address) + std.TestSetOriginCaller(m.Address) // Attempt to vote on the proposal urequire.NoError( @@ -441,7 +441,7 @@ func TestSimpleDAO_VoteOnProposal(t *testing.T) { majorityIndex := (len(members)*2)/3 + 1 // 2/3+ for _, m := range members[:majorityIndex] { - std.TestSetOrigCaller(m.Address) + std.TestSetOriginCaller(m.Address) // Attempt to vote on the proposal urequire.NoError( @@ -496,7 +496,7 @@ func TestSimpleDAO_VoteOnProposal(t *testing.T) { majorityIndex := (len(members)*2)/3 + 1 // 2/3+ for _, m := range members[:majorityIndex] { - std.TestSetOrigCaller(m.Address) + std.TestSetOriginCaller(m.Address) // Attempt to vote on the proposal urequire.NoError( @@ -551,7 +551,7 @@ func TestSimpleDAO_VoteOnProposal(t *testing.T) { // The first half votes yes for _, m := range members[:len(members)/2] { - std.TestSetOrigCaller(m.Address) + std.TestSetOriginCaller(m.Address) // Attempt to vote on the proposal urequire.NoError( @@ -562,7 +562,7 @@ func TestSimpleDAO_VoteOnProposal(t *testing.T) { // The other half votes no for _, m := range members[len(members)/2:] { - std.TestSetOrigCaller(m.Address) + std.TestSetOriginCaller(m.Address) // Attempt to vote on the proposal urequire.NoError( @@ -618,7 +618,7 @@ func TestSimpleDAO_VoteOnProposal(t *testing.T) { // The first quarter votes yes for _, m := range members[:len(members)/4] { - std.TestSetOrigCaller(m.Address) + std.TestSetOriginCaller(m.Address) // Attempt to vote on the proposal urequire.NoError( @@ -629,7 +629,7 @@ func TestSimpleDAO_VoteOnProposal(t *testing.T) { // The second quarter votes no for _, m := range members[len(members)/4 : len(members)/2] { - std.TestSetOrigCaller(m.Address) + std.TestSetOriginCaller(m.Address) // Attempt to vote on the proposal urequire.NoError( @@ -668,7 +668,7 @@ func TestSimpleDAO_ExecuteProposal(t *testing.T) { // Set the sent coins to be lower // than the execute fee - std.TestSetOrigSend(sentCoins, std.Coins{}) + std.TestSetOriginSend(sentCoins, std.Coins{}) uassert.ErrorIs( t, @@ -699,7 +699,7 @@ func TestSimpleDAO_ExecuteProposal(t *testing.T) { // Set the sent coins to be enough // so the execution can take place - std.TestSetOrigSend(sentCoins, std.Coins{}) + std.TestSetOriginSend(sentCoins, std.Coins{}) uassert.ErrorContains( t, @@ -726,7 +726,7 @@ func TestSimpleDAO_ExecuteProposal(t *testing.T) { } ) - std.TestSetOrigCaller(voter) + std.TestSetOriginCaller(voter) // Add an initial proposal id, err := s.addProposal(prop) @@ -776,7 +776,7 @@ func TestSimpleDAO_ExecuteProposal(t *testing.T) { } ) - std.TestSetOrigCaller(voter) + std.TestSetOriginCaller(voter) // Add an initial proposal id, err := s.addProposal(prop) @@ -820,7 +820,7 @@ func TestSimpleDAO_ExecuteProposal(t *testing.T) { } ) - std.TestSetOrigCaller(voter) + std.TestSetOriginCaller(voter) // Add an initial proposal id, err := s.addProposal(prop) @@ -864,7 +864,7 @@ func TestSimpleDAO_ExecuteProposal(t *testing.T) { } ) - std.TestSetOrigCaller(voter) + std.TestSetOriginCaller(voter) // Add an initial proposal id, err := s.addProposal(prop) diff --git a/examples/gno.land/p/demo/subscription/lifetime/lifetime.gno b/examples/gno.land/p/demo/subscription/lifetime/lifetime.gno index f89d2a8be7e..f16fc3f8798 100644 --- a/examples/gno.land/p/demo/subscription/lifetime/lifetime.gno +++ b/examples/gno.land/p/demo/subscription/lifetime/lifetime.gno @@ -26,7 +26,7 @@ func NewLifetimeSubscription(amount int64) *LifetimeSubscription { // processSubscription handles the subscription process for a given receiver. func (ls *LifetimeSubscription) processSubscription(receiver std.Address) error { - amount := std.OrigSend() + amount := std.OriginSend() if amount.AmountOf("ugnot") != ls.amount { return ErrAmt diff --git a/examples/gno.land/p/demo/subscription/lifetime/lifetime_test.gno b/examples/gno.land/p/demo/subscription/lifetime/lifetime_test.gno index efbae90c11c..2c8ea41b566 100644 --- a/examples/gno.land/p/demo/subscription/lifetime/lifetime_test.gno +++ b/examples/gno.land/p/demo/subscription/lifetime/lifetime_test.gno @@ -18,7 +18,7 @@ func TestLifetimeSubscription(t *testing.T) { std.TestSetRealm(std.NewUserRealm(alice)) ls := NewLifetimeSubscription(1000) - std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) + std.TestSetOriginSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) err := ls.Subscribe() uassert.NoError(t, err, "Expected ProcessPayment to succeed") @@ -30,7 +30,7 @@ func TestLifetimeSubscriptionGift(t *testing.T) { std.TestSetRealm(std.NewUserRealm(alice)) ls := NewLifetimeSubscription(1000) - std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) + std.TestSetOriginSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) err := ls.GiftSubscription(bob) uassert.NoError(t, err, "Expected ProcessPaymentGift to succeed for Bob") @@ -48,7 +48,7 @@ func TestUpdateAmountAuthorization(t *testing.T) { err := ls.UpdateAmount(2000) uassert.NoError(t, err, "Expected Alice to succeed in updating amount") - std.TestSetOrigCaller(bob) + std.TestSetOriginCaller(bob) err = ls.UpdateAmount(3000) uassert.Error(t, err, "Expected Bob to fail when updating amount") @@ -58,7 +58,7 @@ func TestIncorrectPaymentAmount(t *testing.T) { std.TestSetRealm(std.NewUserRealm(alice)) ls := NewLifetimeSubscription(1000) - std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 500}}, nil) + std.TestSetOriginSend([]std.Coin{{Denom: "ugnot", Amount: 500}}, nil) err := ls.Subscribe() uassert.Error(t, err, "Expected payment to fail with incorrect amount") } @@ -67,11 +67,11 @@ func TestMultipleSubscriptionAttempts(t *testing.T) { std.TestSetRealm(std.NewUserRealm(alice)) ls := NewLifetimeSubscription(1000) - std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) + std.TestSetOriginSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) err := ls.Subscribe() uassert.NoError(t, err, "Expected first subscription to succeed") - std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) + std.TestSetOriginSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) err = ls.Subscribe() uassert.Error(t, err, "Expected second subscription to fail as Alice is already subscribed") } @@ -80,7 +80,7 @@ func TestGiftSubscriptionWithIncorrectAmount(t *testing.T) { std.TestSetRealm(std.NewUserRealm(alice)) ls := NewLifetimeSubscription(1000) - std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 500}}, nil) + std.TestSetOriginSend([]std.Coin{{Denom: "ugnot", Amount: 500}}, nil) err := ls.GiftSubscription(bob) uassert.Error(t, err, "Expected gift subscription to fail with incorrect amount") @@ -95,11 +95,11 @@ func TestUpdateAmountEffectiveness(t *testing.T) { err := ls.UpdateAmount(2000) uassert.NoError(t, err, "Expected Alice to succeed in updating amount") - std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) + std.TestSetOriginSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) err = ls.Subscribe() uassert.Error(t, err, "Expected subscription to fail with old amount after update") - std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 2000}}, nil) + std.TestSetOriginSend([]std.Coin{{Denom: "ugnot", Amount: 2000}}, nil) err = ls.Subscribe() uassert.NoError(t, err, "Expected subscription to succeed with new amount") } diff --git a/examples/gno.land/p/demo/subscription/recurring/recurring.gno b/examples/gno.land/p/demo/subscription/recurring/recurring.gno index da2cdc66b9e..4e5b7e9729c 100644 --- a/examples/gno.land/p/demo/subscription/recurring/recurring.gno +++ b/examples/gno.land/p/demo/subscription/recurring/recurring.gno @@ -43,7 +43,7 @@ func (rs *RecurringSubscription) HasValidSubscription(addr std.Address) error { // processSubscription processes the payment for a given receiver and renews or adds their subscription. func (rs *RecurringSubscription) processSubscription(receiver std.Address) error { - amount := std.OrigSend() + amount := std.OriginSend() if amount.AmountOf("ugnot") != rs.amount { return ErrAmt diff --git a/examples/gno.land/p/demo/subscription/recurring/recurring_test.gno b/examples/gno.land/p/demo/subscription/recurring/recurring_test.gno index e8bca15c0bf..3a24818ac49 100644 --- a/examples/gno.land/p/demo/subscription/recurring/recurring_test.gno +++ b/examples/gno.land/p/demo/subscription/recurring/recurring_test.gno @@ -19,7 +19,7 @@ func TestRecurringSubscription(t *testing.T) { std.TestSetRealm(std.NewUserRealm(alice)) rs := NewRecurringSubscription(time.Hour*24, 1000) - std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) + std.TestSetOriginSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) err := rs.Subscribe() uassert.NoError(t, err, "Expected ProcessPayment to succeed for Alice") @@ -34,7 +34,7 @@ func TestRecurringSubscriptionGift(t *testing.T) { std.TestSetRealm(std.NewUserRealm(alice)) rs := NewRecurringSubscription(time.Hour*24, 1000) - std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) + std.TestSetOriginSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) err := rs.GiftSubscription(bob) uassert.NoError(t, err, "Expected ProcessPaymentGift to succeed for Bob") @@ -49,7 +49,7 @@ func TestRecurringSubscriptionExpiration(t *testing.T) { std.TestSetRealm(std.NewUserRealm(alice)) rs := NewRecurringSubscription(time.Hour, 1000) - std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) + std.TestSetOriginSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) err := rs.Subscribe() uassert.NoError(t, err, "Expected ProcessPayment to succeed for Alice") @@ -70,7 +70,7 @@ func TestUpdateAmountAuthorization(t *testing.T) { err := rs.UpdateAmount(2000) uassert.NoError(t, err, "Expected Alice to succeed in updating amount") - std.TestSetOrigCaller(bob) + std.TestSetOriginCaller(bob) err = rs.UpdateAmount(3000) uassert.Error(t, err, "Expected Bob to fail when updating amount") } @@ -93,7 +93,7 @@ func TestIncorrectPaymentAmount(t *testing.T) { std.TestSetRealm(std.NewUserRealm(alice)) rs := NewRecurringSubscription(time.Hour*24, 1000) - std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 500}}, nil) + std.TestSetOriginSend([]std.Coin{{Denom: "ugnot", Amount: 500}}, nil) err := rs.Subscribe() uassert.Error(t, err, "Expected payment with incorrect amount to fail") } @@ -102,11 +102,11 @@ func TestMultiplePaymentsForSameUser(t *testing.T) { std.TestSetRealm(std.NewUserRealm(alice)) rs := NewRecurringSubscription(time.Hour*24, 1000) - std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) + std.TestSetOriginSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) err := rs.Subscribe() uassert.NoError(t, err, "Expected first ProcessPayment to succeed for Alice") - std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) + std.TestSetOriginSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) err = rs.Subscribe() uassert.Error(t, err, "Expected second ProcessPayment to fail for Alice due to existing subscription") } @@ -115,7 +115,7 @@ func TestRecurringSubscriptionWithMultiplePayments(t *testing.T) { std.TestSetRealm(std.NewUserRealm(alice)) rs := NewRecurringSubscription(time.Hour, 1000) - std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) + std.TestSetOriginSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) err := rs.Subscribe() uassert.NoError(t, err, "Expected first ProcessPayment to succeed for Alice") @@ -125,7 +125,7 @@ func TestRecurringSubscriptionWithMultiplePayments(t *testing.T) { expiration := time.Now().Add(-time.Hour * 2) rs.subs.Set(std.PrevRealm().Addr().String(), expiration) - std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) + std.TestSetOriginSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) err = rs.Subscribe() uassert.NoError(t, err, "Expected second ProcessPayment to succeed for Alice") diff --git a/examples/gno.land/p/demo/todolist/todolist.gno b/examples/gno.land/p/demo/todolist/todolist.gno index aed3be308ba..531daf1dcb3 100644 --- a/examples/gno.land/p/demo/todolist/todolist.gno +++ b/examples/gno.land/p/demo/todolist/todolist.gno @@ -22,7 +22,7 @@ func NewTodoList(title string) *TodoList { return &TodoList{ Title: title, Tasks: avl.NewTree(), - Owner: std.OrigCaller(), + Owner: std.OriginCaller(), } } diff --git a/examples/gno.land/p/demo/todolist/todolist_test.gno b/examples/gno.land/p/demo/todolist/todolist_test.gno index 224f6afba47..26daf05a374 100644 --- a/examples/gno.land/p/demo/todolist/todolist_test.gno +++ b/examples/gno.land/p/demo/todolist/todolist_test.gno @@ -13,7 +13,7 @@ func TestNewTodoList(t *testing.T) { uassert.Equal(t, title, todoList.GetTodolistTitle()) uassert.Equal(t, 0, len(todoList.GetTasks())) - uassert.Equal(t, std.OrigCaller().String(), todoList.GetTodolistOwner().String()) + uassert.Equal(t, std.OriginCaller().String(), todoList.GetTodolistOwner().String()) } func TestNewTask(t *testing.T) { diff --git a/examples/gno.land/p/n2p5/loci/loci_test.gno b/examples/gno.land/p/n2p5/loci/loci_test.gno index bb216a8539e..74c0139faae 100644 --- a/examples/gno.land/p/n2p5/loci/loci_test.gno +++ b/examples/gno.land/p/n2p5/loci/loci_test.gno @@ -20,7 +20,7 @@ func TestLociStore(t *testing.T) { m1 := []byte("hello") m2 := []byte("world") - std.TestSetOrigCaller(u1) + std.TestSetOriginCaller(u1) // Ensure that the value is nil before setting it. r1 := store.Get(u1) @@ -52,11 +52,11 @@ func TestLociStore(t *testing.T) { m2 := []byte("world") m3 := []byte("goodbye") - std.TestSetOrigCaller(u1) + std.TestSetOriginCaller(u1) store.Set(m1) - std.TestSetOrigCaller(u2) + std.TestSetOriginCaller(u2) store.Set(m2) - std.TestSetOrigCaller(u3) + std.TestSetOriginCaller(u3) store.Set(m3) // Ensure that the value is correct after setting it. diff --git a/examples/gno.land/p/n2p5/mgroup/mgroup_test.gno b/examples/gno.land/p/n2p5/mgroup/mgroup_test.gno index 7ef0619188f..9b82042e4b7 100644 --- a/examples/gno.land/p/n2p5/mgroup/mgroup_test.gno +++ b/examples/gno.land/p/n2p5/mgroup/mgroup_test.gno @@ -21,7 +21,7 @@ func TestManagedGroup(t *testing.T) { g := New(u1) // happy path { - std.TestSetOrigCaller(u1) + std.TestSetOriginCaller(u1) err := g.AddBackupOwner(u2) if err != nil { t.Errorf("expected nil, got %v", err.Error()) @@ -29,7 +29,7 @@ func TestManagedGroup(t *testing.T) { } // ensure checking for authorized caller { - std.TestSetOrigCaller(u2) + std.TestSetOriginCaller(u2) err := g.AddBackupOwner(u3) if err != ownable.ErrUnauthorized { t.Errorf("expected %v, got %v", ErrNotBackupOwner.Error(), err.Error()) @@ -37,7 +37,7 @@ func TestManagedGroup(t *testing.T) { } // ensure invalid address is caught { - std.TestSetOrigCaller(u1) + std.TestSetOriginCaller(u1) var badAddr std.Address err := g.AddBackupOwner(badAddr) if err != ErrInvalidAddress { @@ -50,7 +50,7 @@ func TestManagedGroup(t *testing.T) { g := New(u1) // happy path { - std.TestSetOrigCaller(u1) + std.TestSetOriginCaller(u1) g.AddBackupOwner(u2) err := g.RemoveBackupOwner(u2) if err != nil { @@ -59,7 +59,7 @@ func TestManagedGroup(t *testing.T) { } // running this twice should not error. { - std.TestSetOrigCaller(u1) + std.TestSetOriginCaller(u1) err := g.RemoveBackupOwner(u2) if err != nil { t.Errorf("expected nil, got %v", err.Error()) @@ -67,14 +67,14 @@ func TestManagedGroup(t *testing.T) { } // ensure checking for authorized caller { - std.TestSetOrigCaller(u2) + std.TestSetOriginCaller(u2) err := g.RemoveBackupOwner(u3) if err != ownable.ErrUnauthorized { t.Errorf("expected %v, got %v", ErrNotBackupOwner.Error(), err.Error()) } } { - std.TestSetOrigCaller(u1) + std.TestSetOriginCaller(u1) var badAddr std.Address err := g.RemoveBackupOwner(badAddr) if err != ErrInvalidAddress { @@ -82,7 +82,7 @@ func TestManagedGroup(t *testing.T) { } } { - std.TestSetOrigCaller(u1) + std.TestSetOriginCaller(u1) err := g.RemoveBackupOwner(u1) if err != ErrCannotRemoveOwner { t.Errorf("expected %v, got %v", ErrCannotRemoveOwner.Error(), err.Error()) @@ -95,7 +95,7 @@ func TestManagedGroup(t *testing.T) { g.AddBackupOwner(u2) // happy path { - std.TestSetOrigCaller(u2) + std.TestSetOriginCaller(u2) err := g.ClaimOwnership() if err != nil { t.Errorf("expected nil, got %v", err.Error()) @@ -109,7 +109,7 @@ func TestManagedGroup(t *testing.T) { } // running this twice should not error. { - std.TestSetOrigCaller(u2) + std.TestSetOriginCaller(u2) err := g.ClaimOwnership() if err != nil { t.Errorf("expected nil, got %v", err.Error()) @@ -117,7 +117,7 @@ func TestManagedGroup(t *testing.T) { } // ensure checking for authorized caller { - std.TestSetOrigCaller(u3) + std.TestSetOriginCaller(u3) err := g.ClaimOwnership() if err != ErrNotMember { t.Errorf("expected %v, got %v", ErrNotMember.Error(), err.Error()) @@ -129,7 +129,7 @@ func TestManagedGroup(t *testing.T) { g := New(u1) // happy path { - std.TestSetOrigCaller(u1) + std.TestSetOriginCaller(u1) err := g.AddMember(u2) if err != nil { t.Errorf("expected nil, got %v", err.Error()) @@ -140,7 +140,7 @@ func TestManagedGroup(t *testing.T) { } // ensure checking for authorized caller { - std.TestSetOrigCaller(u2) + std.TestSetOriginCaller(u2) err := g.AddMember(u3) if err != ownable.ErrUnauthorized { t.Errorf("expected %v, got %v", ownable.ErrUnauthorized.Error(), err.Error()) @@ -148,7 +148,7 @@ func TestManagedGroup(t *testing.T) { } // ensure invalid address is caught { - std.TestSetOrigCaller(u1) + std.TestSetOriginCaller(u1) var badAddr std.Address err := g.AddMember(badAddr) if err != ErrInvalidAddress { @@ -161,7 +161,7 @@ func TestManagedGroup(t *testing.T) { g := New(u1) // happy path { - std.TestSetOrigCaller(u1) + std.TestSetOriginCaller(u1) g.AddMember(u2) err := g.RemoveMember(u2) if err != nil { @@ -173,7 +173,7 @@ func TestManagedGroup(t *testing.T) { } // running this twice should not error. { - std.TestSetOrigCaller(u1) + std.TestSetOriginCaller(u1) err := g.RemoveMember(u2) if err != nil { t.Errorf("expected nil, got %v", err.Error()) @@ -181,7 +181,7 @@ func TestManagedGroup(t *testing.T) { } // ensure checking for authorized caller { - std.TestSetOrigCaller(u2) + std.TestSetOriginCaller(u2) err := g.RemoveMember(u3) if err != ownable.ErrUnauthorized { t.Errorf("expected %v, got %v", ownable.ErrUnauthorized.Error(), err.Error()) @@ -189,7 +189,7 @@ func TestManagedGroup(t *testing.T) { } // ensure invalid address is caught { - std.TestSetOrigCaller(u1) + std.TestSetOriginCaller(u1) var badAddr std.Address err := g.RemoveMember(badAddr) if err != ErrInvalidAddress { @@ -198,7 +198,7 @@ func TestManagedGroup(t *testing.T) { } // ensure owner cannot be removed { - std.TestSetOrigCaller(u1) + std.TestSetOriginCaller(u1) err := g.RemoveMember(u1) if err != ErrCannotRemoveOwner { t.Errorf("expected %v, got %v", ErrCannotRemoveOwner.Error(), err.Error()) @@ -281,7 +281,7 @@ func TestManagedGroup(t *testing.T) { if g.Owner() != u1 { t.Errorf("expected %v, got %v", u1, g.Owner()) } - std.TestSetOrigCaller(u2) + std.TestSetOriginCaller(u2) g.ClaimOwnership() if g.Owner() != u2 { t.Errorf("expected %v, got %v", u2, g.Owner()) @@ -289,7 +289,7 @@ func TestManagedGroup(t *testing.T) { }) t.Run("BackupOwners", func(t *testing.T) { t.Parallel() - std.TestSetOrigCaller(u1) + std.TestSetOriginCaller(u1) g := New(u1) g.AddBackupOwner(u2) g.AddBackupOwner(u3) @@ -309,7 +309,7 @@ func TestManagedGroup(t *testing.T) { }) t.Run("Members", func(t *testing.T) { t.Parallel() - std.TestSetOrigCaller(u1) + std.TestSetOriginCaller(u1) g := New(u1) g.AddMember(u2) g.AddMember(u3) diff --git a/examples/gno.land/r/demo/banktest/README.md b/examples/gno.land/r/demo/banktest/README.md index 888a53b0742..43e52e3b394 100644 --- a/examples/gno.land/r/demo/banktest/README.md +++ b/examples/gno.land/r/demo/banktest/README.md @@ -44,7 +44,7 @@ This means that calls to functions defined within this package are encapsulated // Deposit will take the coins (to the realm's pkgaddr) or return them to user. func Deposit(returnDenom string, returnAmount int64) string { std.AssertOriginCall() - caller := std.OrigCaller() + caller := std.OriginCaller() send := std.Coins{{returnDenom, returnAmount}} ``` @@ -54,7 +54,7 @@ This is the beginning of the definition of the contract function named "Deposit" // record activity act := &activity{ caller: caller, - sent: std.OrigSend(), + sent: std.OriginSend(), returned: send, time: time.Now(), } @@ -74,7 +74,7 @@ Updating the "latest" array for viewing at gno.land/r/demo/banktest: (w/ trailin If the user requested the return of coins... ```go - banker := std.GetBanker(std.BankerTypeOrigSend) + banker := std.GetBanker(std.BankerTypeOriginSend) ``` use a std.Banker instance to return any deposited coins to the original sender. diff --git a/examples/gno.land/r/demo/banktest/banktest.gno b/examples/gno.land/r/demo/banktest/banktest.gno index b2d735431a9..566209b4af7 100644 --- a/examples/gno.land/r/demo/banktest/banktest.gno +++ b/examples/gno.land/r/demo/banktest/banktest.gno @@ -24,12 +24,12 @@ var latest [10]*activity // Deposit will take the coins (to the realm's pkgaddr) or return them to user. func Deposit(returnDenom string, returnAmount int64) string { std.AssertOriginCall() - caller := std.OrigCaller() + caller := std.OriginCaller() send := std.Coins{{returnDenom, returnAmount}} // record activity act := &activity{ caller: caller, - sent: std.OrigSend(), + sent: std.OriginSend(), returned: send, time: time.Now(), } @@ -39,7 +39,7 @@ func Deposit(returnDenom string, returnAmount int64) string { latest[0] = act // return if any. if returnAmount > 0 { - banker := std.GetBanker(std.BankerTypeOrigSend) + banker := std.GetBanker(std.BankerTypeOriginSend) pkgaddr := std.GetOrigPkgAddr() // TODO: use std.Coins constructors, this isn't generally safe. banker.SendCoins(pkgaddr, caller, send) diff --git a/examples/gno.land/r/demo/banktest/z_0_filetest.gno b/examples/gno.land/r/demo/banktest/z_0_filetest.gno index 5a8c8d70a48..e9a10e72d6d 100644 --- a/examples/gno.land/r/demo/banktest/z_0_filetest.gno +++ b/examples/gno.land/r/demo/banktest/z_0_filetest.gno @@ -17,7 +17,7 @@ func main() { // set up main address and banktest addr. banktestAddr := std.DerivePkgAddr("gno.land/r/demo/banktest") mainaddr := std.DerivePkgAddr("gno.land/r/demo/bank1") - std.TestSetOrigCaller(mainaddr) + std.TestSetOriginCaller(mainaddr) std.TestSetOrigPkgAddr(banktestAddr) // get and print balance of mainaddr. @@ -26,9 +26,9 @@ func main() { mainbal := banker.GetCoins(mainaddr) println("main before:", mainbal) - // simulate a Deposit call. use Send + OrigSend to simulate -send. + // simulate a Deposit call. use Send + OriginSend to simulate -send. banker.SendCoins(mainaddr, banktestAddr, std.Coins{{"ugnot", 100_000_000}}) - std.TestSetOrigSend(std.Coins{{"ugnot", 100_000_000}}, nil) + std.TestSetOriginSend(std.Coins{{"ugnot", 100_000_000}}, nil) res := banktest.Deposit("ugnot", 50_000_000) println("Deposit():", res) diff --git a/examples/gno.land/r/demo/banktest/z_1_filetest.gno b/examples/gno.land/r/demo/banktest/z_1_filetest.gno index 39682d26330..87be4dbdb3e 100644 --- a/examples/gno.land/r/demo/banktest/z_1_filetest.gno +++ b/examples/gno.land/r/demo/banktest/z_1_filetest.gno @@ -17,7 +17,7 @@ func main() { // simulate a Deposit call. std.TestSetOrigPkgAddr(banktestAddr) std.TestIssueCoins(banktestAddr, std.Coins{{"ugnot", 100000000}}) - std.TestSetOrigSend(std.Coins{{"ugnot", 100000000}}, nil) + std.TestSetOriginSend(std.Coins{{"ugnot", 100000000}}, nil) res := banktest.Deposit("ugnot", 101000000) println(res) } diff --git a/examples/gno.land/r/demo/banktest/z_2_filetest.gno b/examples/gno.land/r/demo/banktest/z_2_filetest.gno index e839f60354a..3dd8d6a4bd5 100644 --- a/examples/gno.land/r/demo/banktest/z_2_filetest.gno +++ b/examples/gno.land/r/demo/banktest/z_2_filetest.gno @@ -16,16 +16,16 @@ func main() { // print main balance before. mainaddr := std.DerivePkgAddr("gno.land/r/demo/bank1") - std.TestSetOrigCaller(mainaddr) + std.TestSetOriginCaller(mainaddr) banker := std.GetBanker(std.BankerTypeReadonly) mainbal := banker.GetCoins(mainaddr) - println("main before:", mainbal) // plus OrigSend equals 300. + println("main before:", mainbal) // plus OriginSend equals 300. // simulate a Deposit call. std.TestSetOrigPkgAddr(banktestAddr) std.TestIssueCoins(banktestAddr, std.Coins{{"ugnot", 100000000}}) - std.TestSetOrigSend(std.Coins{{"ugnot", 100000000}}, nil) + std.TestSetOriginSend(std.Coins{{"ugnot", 100000000}}, nil) res := banktest.Deposit("ugnot", 55000000) println("Deposit():", res) diff --git a/examples/gno.land/r/demo/banktest/z_3_filetest.gno b/examples/gno.land/r/demo/banktest/z_3_filetest.gno index 7b6758c3e4f..2270b6e0cce 100644 --- a/examples/gno.land/r/demo/banktest/z_3_filetest.gno +++ b/examples/gno.land/r/demo/banktest/z_3_filetest.gno @@ -13,7 +13,7 @@ func main() { banktestAddr := std.DerivePkgAddr("gno.land/r/demo/banktest") mainaddr := std.DerivePkgAddr("gno.land/r/demo/bank1") - std.TestSetOrigCaller(mainaddr) + std.TestSetOriginCaller(mainaddr) banker := std.GetBanker(std.BankerTypeRealmSend) send := std.Coins{{"ugnot", 123}} diff --git a/examples/gno.land/r/demo/bar20/bar20_test.gno b/examples/gno.land/r/demo/bar20/bar20_test.gno index 0561d13c865..81e986a7584 100644 --- a/examples/gno.land/r/demo/bar20/bar20_test.gno +++ b/examples/gno.land/r/demo/bar20/bar20_test.gno @@ -11,7 +11,7 @@ import ( func TestPackage(t *testing.T) { alice := testutils.TestAddress("alice") std.TestSetRealm(std.NewUserRealm(alice)) - std.TestSetOrigCaller(alice) // XXX: should not need this + std.TestSetOriginCaller(alice) // XXX: should not need this urequire.Equal(t, UserTeller.BalanceOf(alice), uint64(0)) urequire.Equal(t, Faucet(), "OK") diff --git a/examples/gno.land/r/demo/boards/public.gno b/examples/gno.land/r/demo/boards/public.gno index 4798a11d8f4..152a7d21c70 100644 --- a/examples/gno.land/r/demo/boards/public.gno +++ b/examples/gno.land/r/demo/boards/public.gno @@ -21,7 +21,7 @@ func CreateBoard(name string) BoardID { panic("invalid non-user call") } bid := incGetBoardID() - caller := std.OrigCaller() + caller := std.OriginCaller() if usernameOf(caller) == "" { panic("unauthorized") } @@ -34,7 +34,7 @@ func CreateBoard(name string) BoardID { } func checkAnonFee() bool { - sent := std.OrigSend() + sent := std.OriginSend() anonFeeCoin := std.NewCoin("ugnot", int64(gDefaultAnonFee)) if len(sent) == 1 && sent[0].IsGTE(anonFeeCoin) { return true @@ -46,7 +46,7 @@ func CreateThread(bid BoardID, title string, body string) PostID { if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { panic("invalid non-user call") } - caller := std.OrigCaller() + caller := std.OriginCaller() if usernameOf(caller) == "" { if !checkAnonFee() { panic("please register, otherwise minimum fee " + strconv.Itoa(gDefaultAnonFee) + " is required if anonymous") @@ -64,7 +64,7 @@ func CreateReply(bid BoardID, threadid, postid PostID, body string) PostID { if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { panic("invalid non-user call") } - caller := std.OrigCaller() + caller := std.OriginCaller() if usernameOf(caller) == "" { if !checkAnonFee() { panic("please register, otherwise minimum fee " + strconv.Itoa(gDefaultAnonFee) + " is required if anonymous") @@ -94,7 +94,7 @@ func CreateRepost(bid BoardID, postid PostID, title string, body string, dstBoar if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { panic("invalid non-user call") } - caller := std.OrigCaller() + caller := std.OriginCaller() if usernameOf(caller) == "" { // TODO: allow with gDefaultAnonFee payment. if !checkAnonFee() { @@ -124,7 +124,7 @@ func DeletePost(bid BoardID, threadid, postid PostID, reason string) { if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { panic("invalid non-user call") } - caller := std.OrigCaller() + caller := std.OriginCaller() board := getBoard(bid) if board == nil { panic("board not exist") @@ -156,7 +156,7 @@ func EditPost(bid BoardID, threadid, postid PostID, title, body string) { if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { panic("invalid non-user call") } - caller := std.OrigCaller() + caller := std.OriginCaller() board := getBoard(bid) if board == nil { panic("board not exist") diff --git a/examples/gno.land/r/demo/boards/z_12_a_filetest.gno b/examples/gno.land/r/demo/boards/z_12_a_filetest.gno index 909be880efa..baad057b361 100644 --- a/examples/gno.land/r/demo/boards/z_12_a_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_12_a_filetest.gno @@ -20,8 +20,8 @@ func main() { // create a repost via anon user test2 := testutils.TestAddress("test2") - std.TestSetOrigCaller(test2) - std.TestSetOrigSend(std.Coins{{"ugnot", 9000000}}, nil) + std.TestSetOriginCaller(test2) + std.TestSetOriginSend(std.Coins{{"ugnot", 9000000}}, nil) rid := boards.CreateRepost(bid1, pid, "", "Check this out", bid2) println(rid) diff --git a/examples/gno.land/r/demo/boards/z_5_b_filetest.gno b/examples/gno.land/r/demo/boards/z_5_b_filetest.gno index e79da5c3677..d9f3a29e7cb 100644 --- a/examples/gno.land/r/demo/boards/z_5_b_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_5_b_filetest.gno @@ -20,8 +20,8 @@ func main() { // create post via anon user test2 := testutils.TestAddress("test2") - std.TestSetOrigCaller(test2) - std.TestSetOrigSend(std.Coins{{"ugnot", 9000000}}, nil) + std.TestSetOriginCaller(test2) + std.TestSetOriginSend(std.Coins{{"ugnot", 9000000}}, nil) pid := boards.CreateThread(bid, "First Post (title)", "Body of the first post. (body)") println(boards.Render("test_board/" + strconv.Itoa(int(pid)))) diff --git a/examples/gno.land/r/demo/boards/z_5_c_filetest.gno b/examples/gno.land/r/demo/boards/z_5_c_filetest.gno index 723e6a10204..8afad8291ab 100644 --- a/examples/gno.land/r/demo/boards/z_5_c_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_5_c_filetest.gno @@ -20,8 +20,8 @@ func main() { // create post via anon user test2 := testutils.TestAddress("test2") - std.TestSetOrigCaller(test2) - std.TestSetOrigSend(std.Coins{{"ugnot", 101000000}}, nil) + std.TestSetOriginCaller(test2) + std.TestSetOriginSend(std.Coins{{"ugnot", 101000000}}, nil) pid := boards.CreateThread(bid, "First Post (title)", "Body of the first post. (body)") boards.CreateReply(bid, pid, pid, "Reply of the first post") diff --git a/examples/gno.land/r/demo/boards/z_5_d_filetest.gno b/examples/gno.land/r/demo/boards/z_5_d_filetest.gno index 54cfe49eec6..bb115656b41 100644 --- a/examples/gno.land/r/demo/boards/z_5_d_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_5_d_filetest.gno @@ -21,8 +21,8 @@ func main() { // create reply via anon user test2 := testutils.TestAddress("test2") - std.TestSetOrigCaller(test2) - std.TestSetOrigSend(std.Coins{{"ugnot", 9000000}}, nil) + std.TestSetOriginCaller(test2) + std.TestSetOriginSend(std.Coins{{"ugnot", 9000000}}, nil) boards.CreateReply(bid, pid, pid, "Reply of the first post") println(boards.Render("test_board/" + strconv.Itoa(int(pid)))) diff --git a/examples/gno.land/r/demo/disperse/disperse.gno b/examples/gno.land/r/demo/disperse/disperse.gno index 2ac73c6d566..acfb280f148 100644 --- a/examples/gno.land/r/demo/disperse/disperse.gno +++ b/examples/gno.land/r/demo/disperse/disperse.gno @@ -13,9 +13,9 @@ var realmAddr = std.CurrentRealm().Addr() // The function will send out the coins to the addresses and return the leftover coins to the caller // if there are any to return func DisperseUgnot(addresses []std.Address, coins std.Coins) { - coinSent := std.OrigSend() + coinSent := std.OriginSend() caller := std.PrevRealm().Addr() - banker := std.GetBanker(std.BankerTypeOrigSend) + banker := std.GetBanker(std.BankerTypeOriginSend) if len(addresses) != len(coins) { panic(ErrNumAddrValMismatch) diff --git a/examples/gno.land/r/demo/disperse/z_0_filetest.gno b/examples/gno.land/r/demo/disperse/z_0_filetest.gno index ca1e9ea0ce8..53169c075fe 100644 --- a/examples/gno.land/r/demo/disperse/z_0_filetest.gno +++ b/examples/gno.land/r/demo/disperse/z_0_filetest.gno @@ -15,7 +15,7 @@ func main() { mainaddr := std.DerivePkgAddr("gno.land/r/demo/main") std.TestSetOrigPkgAddr(disperseAddr) - std.TestSetOrigCaller(mainaddr) + std.TestSetOriginCaller(mainaddr) banker := std.GetBanker(std.BankerTypeRealmSend) diff --git a/examples/gno.land/r/demo/disperse/z_1_filetest.gno b/examples/gno.land/r/demo/disperse/z_1_filetest.gno index 4c27c50749f..36b0dab6f87 100644 --- a/examples/gno.land/r/demo/disperse/z_1_filetest.gno +++ b/examples/gno.land/r/demo/disperse/z_1_filetest.gno @@ -15,7 +15,7 @@ func main() { mainaddr := std.DerivePkgAddr("gno.land/r/demo/main") std.TestSetOrigPkgAddr(disperseAddr) - std.TestSetOrigCaller(mainaddr) + std.TestSetOriginCaller(mainaddr) banker := std.GetBanker(std.BankerTypeRealmSend) diff --git a/examples/gno.land/r/demo/disperse/z_2_filetest.gno b/examples/gno.land/r/demo/disperse/z_2_filetest.gno index 79e8d81e2b1..c50738e6402 100644 --- a/examples/gno.land/r/demo/disperse/z_2_filetest.gno +++ b/examples/gno.land/r/demo/disperse/z_2_filetest.gno @@ -15,7 +15,7 @@ func main() { mainaddr := std.DerivePkgAddr("gno.land/r/demo/main") std.TestSetOrigPkgAddr(disperseAddr) - std.TestSetOrigCaller(mainaddr) + std.TestSetOriginCaller(mainaddr) banker := std.GetBanker(std.BankerTypeRealmSend) diff --git a/examples/gno.land/r/demo/disperse/z_3_filetest.gno b/examples/gno.land/r/demo/disperse/z_3_filetest.gno index 7cb7ffbe71d..7c572cca7d6 100644 --- a/examples/gno.land/r/demo/disperse/z_3_filetest.gno +++ b/examples/gno.land/r/demo/disperse/z_3_filetest.gno @@ -18,7 +18,7 @@ func main() { beneficiary2 := std.Address("g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c") std.TestSetOrigPkgAddr(disperseAddr) - std.TestSetOrigCaller(mainaddr) + std.TestSetOriginCaller(mainaddr) banker := std.GetBanker(std.BankerTypeRealmSend) diff --git a/examples/gno.land/r/demo/disperse/z_4_filetest.gno b/examples/gno.land/r/demo/disperse/z_4_filetest.gno index 4dafb780e83..a03cfa08fa6 100644 --- a/examples/gno.land/r/demo/disperse/z_4_filetest.gno +++ b/examples/gno.land/r/demo/disperse/z_4_filetest.gno @@ -18,7 +18,7 @@ func main() { beneficiary2 := std.Address("g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c") std.TestSetOrigPkgAddr(disperseAddr) - std.TestSetOrigCaller(mainaddr) + std.TestSetOriginCaller(mainaddr) banker := std.GetBanker(std.BankerTypeRealmSend) diff --git a/examples/gno.land/r/demo/foo1155/foo1155.gno b/examples/gno.land/r/demo/foo1155/foo1155.gno index c2f86a76619..1a14571d236 100644 --- a/examples/gno.land/r/demo/foo1155/foo1155.gno +++ b/examples/gno.land/r/demo/foo1155/foo1155.gno @@ -82,7 +82,7 @@ func BatchTransferFrom(from, to pusers.AddressOrName, batch []grc1155.TokenID, a // Admin func Mint(to pusers.AddressOrName, tid grc1155.TokenID, amount uint64) { - caller := std.OrigCaller() + caller := std.OriginCaller() assertIsAdmin(caller) err := foo.SafeMint(users.Resolve(to), tid, amount) if err != nil { @@ -91,7 +91,7 @@ func Mint(to pusers.AddressOrName, tid grc1155.TokenID, amount uint64) { } func MintBatch(to pusers.AddressOrName, batch []grc1155.TokenID, amounts []uint64) { - caller := std.OrigCaller() + caller := std.OriginCaller() assertIsAdmin(caller) err := foo.SafeBatchMint(users.Resolve(to), batch, amounts) if err != nil { @@ -100,7 +100,7 @@ func MintBatch(to pusers.AddressOrName, batch []grc1155.TokenID, amounts []uint6 } func Burn(from pusers.AddressOrName, tid grc1155.TokenID, amount uint64) { - caller := std.OrigCaller() + caller := std.OriginCaller() assertIsAdmin(caller) err := foo.Burn(users.Resolve(from), tid, amount) if err != nil { @@ -109,7 +109,7 @@ func Burn(from pusers.AddressOrName, tid grc1155.TokenID, amount uint64) { } func BurnBatch(from pusers.AddressOrName, batch []grc1155.TokenID, amounts []uint64) { - caller := std.OrigCaller() + caller := std.OriginCaller() assertIsAdmin(caller) err := foo.BatchBurn(users.Resolve(from), batch, amounts) if err != nil { diff --git a/examples/gno.land/r/demo/foo20/foo20_test.gno b/examples/gno.land/r/demo/foo20/foo20_test.gno index b9e80fbb476..4d4111410f9 100644 --- a/examples/gno.land/r/demo/foo20/foo20_test.gno +++ b/examples/gno.land/r/demo/foo20/foo20_test.gno @@ -39,7 +39,7 @@ func TestReadOnlyPublicMethods(t *testing.T) { } // bob uses the faucet. - std.TestSetOrigCaller(users.Resolve(bob)) + std.TestSetOriginCaller(users.Resolve(bob)) Faucet() // check balances #2. diff --git a/examples/gno.land/r/demo/games/dice_roller/dice_roller_test.gno b/examples/gno.land/r/demo/games/dice_roller/dice_roller_test.gno index 2f6770a366f..3259e2e0ec2 100644 --- a/examples/gno.land/r/demo/games/dice_roller/dice_roller_test.gno +++ b/examples/gno.land/r/demo/games/dice_roller/dice_roller_test.gno @@ -27,7 +27,7 @@ func resetGameState() { func TestNewGame(t *testing.T) { resetGameState() - std.TestSetOrigCaller(player1) + std.TestSetOriginCaller(player1) gameID := NewGame(player2) // Verify that the game has been correctly initialized @@ -43,7 +43,7 @@ func TestNewGame(t *testing.T) { func TestPlay(t *testing.T) { resetGameState() - std.TestSetOrigCaller(player1) + std.TestSetOriginCaller(player1) gameID := NewGame(player2) g, err := getGame(gameID) @@ -58,7 +58,7 @@ func TestPlay(t *testing.T) { urequire.Equal(t, 0, g.roll2) // Player 2 hasn't rolled yet // Simulate rolling dice for player 2 - std.TestSetOrigCaller(player2) + std.TestSetOriginCaller(player2) roll2 := Play(gameID) // Verify player 2's roll @@ -71,7 +71,7 @@ func TestPlay(t *testing.T) { func TestPlayAgainstSelf(t *testing.T) { resetGameState() - std.TestSetOrigCaller(player1) + std.TestSetOriginCaller(player1) gameID := NewGame(player1) // Simulate rolling dice twice by the same player @@ -88,11 +88,11 @@ func TestPlayAgainstSelf(t *testing.T) { func TestPlayInvalidPlayer(t *testing.T) { resetGameState() - std.TestSetOrigCaller(player1) + std.TestSetOriginCaller(player1) gameID := NewGame(player1) // Attempt to play as an invalid player - std.TestSetOrigCaller(unknownPlayer) + std.TestSetOriginCaller(unknownPlayer) urequire.PanicsWithMessage(t, "invalid player", func() { Play(gameID) }) @@ -102,7 +102,7 @@ func TestPlayInvalidPlayer(t *testing.T) { func TestPlayAlreadyPlayed(t *testing.T) { resetGameState() - std.TestSetOrigCaller(player1) + std.TestSetOriginCaller(player1) gameID := NewGame(player2) // Player 1 rolls @@ -118,13 +118,13 @@ func TestPlayAlreadyPlayed(t *testing.T) { func TestPlayBeyondGameEnd(t *testing.T) { resetGameState() - std.TestSetOrigCaller(player1) + std.TestSetOriginCaller(player1) gameID := NewGame(player2) // Play for both players - std.TestSetOrigCaller(player1) + std.TestSetOriginCaller(player1) Play(gameID) - std.TestSetOrigCaller(player2) + std.TestSetOriginCaller(player2) Play(gameID) // Check if the game is over @@ -132,7 +132,7 @@ func TestPlayBeyondGameEnd(t *testing.T) { urequire.NoError(t, err) // Attempt to play more should fail - std.TestSetOrigCaller(player1) + std.TestSetOriginCaller(player1) urequire.PanicsWithMessage(t, "game over", func() { Play(gameID) }) diff --git a/examples/gno.land/r/demo/grc20factory/grc20factory_test.gno b/examples/gno.land/r/demo/grc20factory/grc20factory_test.gno index 46fc07fabf2..ec251f9c4a0 100644 --- a/examples/gno.land/r/demo/grc20factory/grc20factory_test.gno +++ b/examples/gno.land/r/demo/grc20factory/grc20factory_test.gno @@ -36,7 +36,7 @@ func TestReadOnlyPublicMethods(t *testing.T) { } // admin creates FOO and BAR. - std.TestSetOrigCaller(admin) + std.TestSetOriginCaller(admin) std.TestSetRealm(std.NewUserRealm(admin)) NewWithAdmin("Foo", "FOO", 3, 1_111_111_000, 5_555, admin) NewWithAdmin("Bar", "BAR", 3, 2_222_000, 6_666, admin) @@ -47,25 +47,25 @@ func TestReadOnlyPublicMethods(t *testing.T) { checkBalances("step2", 1_444_444_000, 1_111_111_000, 333_333_000, 0, 0) // carl uses the faucet. - std.TestSetOrigCaller(carl) + std.TestSetOriginCaller(carl) std.TestSetRealm(std.NewUserRealm(carl)) Faucet("FOO") checkBalances("step3", 1_444_449_555, 1_111_111_000, 333_333_000, 0, 5_555) // admin gives to bob some allowance. - std.TestSetOrigCaller(admin) + std.TestSetOriginCaller(admin) std.TestSetRealm(std.NewUserRealm(admin)) Approve("FOO", bob, 1_000_000) checkBalances("step4", 1_444_449_555, 1_111_111_000, 333_333_000, 1_000_000, 5_555) // bob uses a part of the allowance. - std.TestSetOrigCaller(bob) + std.TestSetOriginCaller(bob) std.TestSetRealm(std.NewUserRealm(bob)) TransferFrom("FOO", admin, carl, 400_000) checkBalances("step5", 1_444_449_555, 1_110_711_000, 333_333_000, 600_000, 405_555) // bob uses a part of the allowance. - std.TestSetOrigCaller(bob) + std.TestSetOriginCaller(bob) std.TestSetRealm(std.NewUserRealm(bob)) TransferFrom("FOO", admin, carl, 600_000) checkBalances("step6", 1_444_449_555, 1_110_111_000, 333_333_000, 0, 1_005_555) diff --git a/examples/gno.land/r/demo/groups/public.gno b/examples/gno.land/r/demo/groups/public.gno index 2d3d164eae7..d59b0c2042f 100644 --- a/examples/gno.land/r/demo/groups/public.gno +++ b/examples/gno.land/r/demo/groups/public.gno @@ -19,7 +19,7 @@ func GetGroupIDFromName(name string) (GroupID, bool) { func CreateGroup(name string) GroupID { std.AssertOriginCall() - caller := std.OrigCaller() + caller := std.OriginCaller() usernameOf(caller) url := "/r/demo/groups:" + name group := newGroup(url, name, caller) @@ -31,7 +31,7 @@ func CreateGroup(name string) GroupID { func AddMember(gid GroupID, address string, weight int, metadata string) MemberID { std.AssertOriginCall() - caller := std.OrigCaller() + caller := std.OriginCaller() usernameOf(caller) group := getGroup(gid) if !group.HasPermission(caller, EditPermission) { @@ -52,7 +52,7 @@ func AddMember(gid GroupID, address string, weight int, metadata string) MemberI func DeleteGroup(gid GroupID) { std.AssertOriginCall() - caller := std.OrigCaller() + caller := std.OriginCaller() group := getGroup(gid) if !group.HasPermission(caller, DeletePermission) { panic("unauthorized to delete group") @@ -62,7 +62,7 @@ func DeleteGroup(gid GroupID) { func DeleteMember(gid GroupID, mid MemberID) { std.AssertOriginCall() - caller := std.OrigCaller() + caller := std.OriginCaller() group := getGroup(gid) if !group.HasPermission(caller, DeletePermission) { panic("unauthorized to delete member") diff --git a/examples/gno.land/r/demo/groups/z_1_a_filetest.gno b/examples/gno.land/r/demo/groups/z_1_a_filetest.gno index 5949f97dfa6..1db5453ba34 100644 --- a/examples/gno.land/r/demo/groups/z_1_a_filetest.gno +++ b/examples/gno.land/r/demo/groups/z_1_a_filetest.gno @@ -16,43 +16,43 @@ var gid groups.GroupID const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") func main() { - caller := std.OrigCaller() // main + caller := std.OriginCaller() // main users.Register("", "gnouser0", "my profile 1") - std.TestSetOrigCaller(admin) + std.TestSetOriginCaller(admin) users.GrantInvites(caller.String() + ":1") // switch back to caller - std.TestSetOrigCaller(caller) + std.TestSetOriginCaller(caller) // invite another addr test1 := testutils.TestAddress("gnouser1") users.Invite(test1.String()) // switch to test1 - std.TestSetOrigCaller(test1) + std.TestSetOriginCaller(test1) users.Register(caller, "gnouser1", "my other profile 1") - std.TestSetOrigCaller(admin) + std.TestSetOriginCaller(admin) users.GrantInvites(caller.String() + ":1") // switch back to caller - std.TestSetOrigCaller(caller) + std.TestSetOriginCaller(caller) // invite another addr test2 := testutils.TestAddress("gnouser2") users.Invite(test2.String()) // switch to test1 - std.TestSetOrigCaller(test2) + std.TestSetOriginCaller(test2) users.Register(caller, "gnouser2", "my other profile 2") - std.TestSetOrigCaller(admin) + std.TestSetOriginCaller(admin) users.GrantInvites(caller.String() + ":1") // switch back to caller - std.TestSetOrigCaller(caller) + std.TestSetOriginCaller(caller) // invite another addr test3 := testutils.TestAddress("gnouser3") users.Invite(test3.String()) // switch to test1 - std.TestSetOrigCaller(test3) + std.TestSetOriginCaller(test3) users.Register(caller, "gnouser3", "my other profile 3") - std.TestSetOrigCaller(caller) + std.TestSetOriginCaller(caller) gid = groups.CreateGroup("test_group") println(gid) diff --git a/examples/gno.land/r/demo/groups/z_1_c_filetest.gno b/examples/gno.land/r/demo/groups/z_1_c_filetest.gno index 6eaabb07cdf..b13c88fe1c8 100644 --- a/examples/gno.land/r/demo/groups/z_1_c_filetest.gno +++ b/examples/gno.land/r/demo/groups/z_1_c_filetest.gno @@ -18,8 +18,8 @@ func main() { // add member via anon user test2 := testutils.TestAddress("test2") - std.TestSetOrigCaller(test2) - std.TestSetOrigSend(std.Coins{{"ugnot", 9000000}}, nil) + std.TestSetOriginCaller(test2) + std.TestSetOriginSend(std.Coins{{"ugnot", 9000000}}, nil) groups.AddMember(gid, test2.String(), 42, "metadata3") } diff --git a/examples/gno.land/r/demo/groups/z_2_a_filetest.gno b/examples/gno.land/r/demo/groups/z_2_a_filetest.gno index 9254d13ac55..a01b7571d26 100644 --- a/examples/gno.land/r/demo/groups/z_2_a_filetest.gno +++ b/examples/gno.land/r/demo/groups/z_2_a_filetest.gno @@ -16,43 +16,43 @@ var gid groups.GroupID const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") func main() { - caller := std.OrigCaller() // main + caller := std.OriginCaller() // main users.Register("", "gnouser0", "my profile 1") - std.TestSetOrigCaller(admin) + std.TestSetOriginCaller(admin) users.GrantInvites(caller.String() + ":1") // switch back to caller - std.TestSetOrigCaller(caller) + std.TestSetOriginCaller(caller) // invite another addr test1 := testutils.TestAddress("gnouser1") users.Invite(test1.String()) // switch to test1 - std.TestSetOrigCaller(test1) + std.TestSetOriginCaller(test1) users.Register(caller, "gnouser1", "my other profile 1") - std.TestSetOrigCaller(admin) + std.TestSetOriginCaller(admin) users.GrantInvites(caller.String() + ":1") // switch back to caller - std.TestSetOrigCaller(caller) + std.TestSetOriginCaller(caller) // invite another addr test2 := testutils.TestAddress("gnouser2") users.Invite(test2.String()) // switch to test1 - std.TestSetOrigCaller(test2) + std.TestSetOriginCaller(test2) users.Register(caller, "gnouser2", "my other profile 2") - std.TestSetOrigCaller(admin) + std.TestSetOriginCaller(admin) users.GrantInvites(caller.String() + ":1") // switch back to caller - std.TestSetOrigCaller(caller) + std.TestSetOriginCaller(caller) // invite another addr test3 := testutils.TestAddress("gnouser3") users.Invite(test3.String()) // switch to test1 - std.TestSetOrigCaller(test3) + std.TestSetOriginCaller(test3) users.Register(caller, "gnouser3", "my other profile 3") - std.TestSetOrigCaller(caller) + std.TestSetOriginCaller(caller) gid = groups.CreateGroup("test_group") println(gid) diff --git a/examples/gno.land/r/demo/groups/z_2_d_filetest.gno b/examples/gno.land/r/demo/groups/z_2_d_filetest.gno index 3caa726cbd3..ac91caf15b5 100644 --- a/examples/gno.land/r/demo/groups/z_2_d_filetest.gno +++ b/examples/gno.land/r/demo/groups/z_2_d_filetest.gno @@ -20,8 +20,8 @@ func main() { // delete member via anon user test2 := testutils.TestAddress("test2") - std.TestSetOrigCaller(test2) - std.TestSetOrigSend(std.Coins{{"ugnot", 9000000}}, nil) + std.TestSetOriginCaller(test2) + std.TestSetOriginSend(std.Coins{{"ugnot", 9000000}}, nil) groups.DeleteMember(gid, 0) println(groups.Render("")) diff --git a/examples/gno.land/r/demo/groups/z_2_g_filetest.gno b/examples/gno.land/r/demo/groups/z_2_g_filetest.gno index 6230b110c74..88805c2c94a 100644 --- a/examples/gno.land/r/demo/groups/z_2_g_filetest.gno +++ b/examples/gno.land/r/demo/groups/z_2_g_filetest.gno @@ -20,8 +20,8 @@ func main() { // delete group via anon user test2 := testutils.TestAddress("test2") - std.TestSetOrigCaller(test2) - std.TestSetOrigSend(std.Coins{{"ugnot", 9000000}}, nil) + std.TestSetOriginCaller(test2) + std.TestSetOriginSend(std.Coins{{"ugnot", 9000000}}, nil) groups.DeleteGroup(gid) println(groups.Render("")) diff --git a/examples/gno.land/r/demo/keystore/keystore.gno b/examples/gno.land/r/demo/keystore/keystore.gno index c76923089b1..5edff4ebf25 100644 --- a/examples/gno.land/r/demo/keystore/keystore.gno +++ b/examples/gno.land/r/demo/keystore/keystore.gno @@ -33,14 +33,14 @@ type KeyStore struct { // Set will set a value to a key // requires write-access (original caller must be caller) func Set(k, v string) string { - origOwner := std.OrigCaller() + origOwner := std.OriginCaller() return set(origOwner.String(), k, v) } // set (private) will set a key to value // requires write-access (original caller must be caller) func set(owner, k, v string) string { - origOwner := std.OrigCaller() + origOwner := std.OriginCaller() if origOwner.String() != owner { return StatusNoWriteAccess } @@ -62,14 +62,14 @@ func set(owner, k, v string) string { // Remove removes a key // requires write-access (original owner must be caller) func Remove(k string) string { - origOwner := std.OrigCaller() + origOwner := std.OriginCaller() return remove(origOwner.String(), k) } // remove (private) removes a key // requires write-access (original owner must be caller) func remove(owner, k string) string { - origOwner := std.OrigCaller() + origOwner := std.OriginCaller() if origOwner.String() != owner { return StatusNoWriteAccess } @@ -94,7 +94,7 @@ func remove(owner, k string) string { // Get returns a value for a key // read-only func Get(k string) string { - origOwner := std.OrigCaller() + origOwner := std.OriginCaller() return remove(origOwner.String(), k) } @@ -116,7 +116,7 @@ func get(owner, k string) string { // Size returns size of database // read-only func Size() string { - origOwner := std.OrigCaller() + origOwner := std.OriginCaller() return size(origOwner.String()) } diff --git a/examples/gno.land/r/demo/keystore/keystore_test.gno b/examples/gno.land/r/demo/keystore/keystore_test.gno index ffd8e60936f..57c6f850d51 100644 --- a/examples/gno.land/r/demo/keystore/keystore_test.gno +++ b/examples/gno.land/r/demo/keystore/keystore_test.gno @@ -62,7 +62,7 @@ func TestRender(t *testing.T) { } p = strings.TrimSuffix(p, ":") t.Run(p, func(t *testing.T) { - std.TestSetOrigCaller(tc.caller) + std.TestSetOriginCaller(tc.caller) var act string if len(tc.ps) > 0 && tc.ps[0] == "set" { act = strings.TrimSpace(Set(tc.ps[1], tc.ps[2])) diff --git a/examples/gno.land/r/demo/microblog/microblog.gno b/examples/gno.land/r/demo/microblog/microblog.gno index 725c0e2ad31..bfa8ad676d0 100644 --- a/examples/gno.land/r/demo/microblog/microblog.gno +++ b/examples/gno.land/r/demo/microblog/microblog.gno @@ -91,7 +91,7 @@ func NewPost(text string) string { } func Register(name, profile string) string { - caller := std.OrigCaller() // main + caller := std.OriginCaller() // main users.Register(caller, name, profile) return "OK" } diff --git a/examples/gno.land/r/demo/microblog/microblog_test.gno b/examples/gno.land/r/demo/microblog/microblog_test.gno index a3c8f04ee7f..463519481a6 100644 --- a/examples/gno.land/r/demo/microblog/microblog_test.gno +++ b/examples/gno.land/r/demo/microblog/microblog_test.gno @@ -15,7 +15,7 @@ func TestMicroblog(t *testing.T) { author2 std.Address = testutils.TestAddress("author2") ) - std.TestSetOrigCaller(author1) + std.TestSetOriginCaller(author1) urequire.Equal(t, "404", Render("/wrongpath"), "rendering not giving 404") urequire.NotEqual(t, "404", Render(""), "rendering / should not give 404") @@ -27,7 +27,7 @@ func TestMicroblog(t *testing.T) { _, err = m.GetPage("no such author") urequire.Error(t, err, "silo should not exist") - std.TestSetOrigCaller(author2) + std.TestSetOriginCaller(author2) urequire.NoError(t, m.NewPost("hello, web3"), "could not create post") urequire.NoError(t, m.NewPost("hello again, web3"), "could not create post") diff --git a/examples/gno.land/r/demo/releases_example/example.gno b/examples/gno.land/r/demo/releases_example/example.gno index 3f5c39d7236..74a306b4251 100644 --- a/examples/gno.land/r/demo/releases_example/example.gno +++ b/examples/gno.land/r/demo/releases_example/example.gno @@ -17,7 +17,7 @@ func init() { } func NewRelease(name, url, notes string) { - caller := std.OrigCaller() + caller := std.OriginCaller() if caller != admin { panic("restricted area") } @@ -25,7 +25,7 @@ func NewRelease(name, url, notes string) { } func UpdateAdmin(address std.Address) { - caller := std.OrigCaller() + caller := std.OriginCaller() if caller != admin { panic("restricted area") } diff --git a/examples/gno.land/r/demo/tests/tests.gno b/examples/gno.land/r/demo/tests/tests.gno index b0c4c3dbae6..79d2a5477fe 100644 --- a/examples/gno.land/r/demo/tests/tests.gno +++ b/examples/gno.land/r/demo/tests/tests.gno @@ -21,10 +21,10 @@ func CurrentRealmPath() string { return std.CurrentRealm().PkgPath() } -var initOrigCaller = std.OrigCaller() +var initOriginCaller = std.OriginCaller() -func InitOrigCaller() std.Address { - return initOrigCaller +func InitOriginCaller() std.Address { + return initOriginCaller } func CallAssertOriginCall() { diff --git a/examples/gno.land/r/demo/tests/z2_filetest.gno b/examples/gno.land/r/demo/tests/z2_filetest.gno index 147d2c12c6c..bd83026e568 100644 --- a/examples/gno.land/r/demo/tests/z2_filetest.gno +++ b/examples/gno.land/r/demo/tests/z2_filetest.gno @@ -14,7 +14,7 @@ func main() { eoa = testutils.TestAddress("someone") rTestsAddr = std.DerivePkgAddr("gno.land/r/demo/tests") ) - std.TestSetOrigCaller(eoa) + std.TestSetOriginCaller(eoa) println("tests.GetPrevRealm().Addr(): ", tests.GetPrevRealm().Addr()) println("tests.GetRSubtestsPrevRealm().Addr(): ", tests.GetRSubtestsPrevRealm().Addr()) } diff --git a/examples/gno.land/r/demo/tests/z3_filetest.gno b/examples/gno.land/r/demo/tests/z3_filetest.gno index 5430e7f7151..a7c3a2a0000 100644 --- a/examples/gno.land/r/demo/tests/z3_filetest.gno +++ b/examples/gno.land/r/demo/tests/z3_filetest.gno @@ -13,7 +13,7 @@ func main() { eoa = testutils.TestAddress("someone") rTestsAddr = std.DerivePkgAddr("gno.land/r/demo/tests") ) - std.TestSetOrigCaller(eoa) + std.TestSetOriginCaller(eoa) // Contrarily to z2_filetest.gno we EXPECT GetPrevRealms != eoa (#1704) if addr := tests.GetPrevRealm().Addr(); addr != eoa { println("want tests.GetPrevRealm().Addr ==", eoa, "got", addr) diff --git a/examples/gno.land/r/demo/todolist/todolist_test.gno b/examples/gno.land/r/demo/todolist/todolist_test.gno index 999ec0a8098..f6e510bcc03 100644 --- a/examples/gno.land/r/demo/todolist/todolist_test.gno +++ b/examples/gno.land/r/demo/todolist/todolist_test.gno @@ -26,7 +26,7 @@ func TestNewTodoList(t *testing.T) { uassert.Equal(t, title, tdl.Title, "title does not match") uassert.Equal(t, 1, tlid, "tlid does not match") - uassert.Equal(t, tdl.Owner.String(), std.OrigCaller().String(), "owner does not match") + uassert.Equal(t, tdl.Owner.String(), std.OriginCaller().String(), "owner does not match") uassert.Equal(t, 0, len(tdl.GetTasks()), "Expected no tasks in the todo list") } diff --git a/examples/gno.land/r/demo/users/users.gno b/examples/gno.land/r/demo/users/users.gno index 5205a6d3332..6eede5e120e 100644 --- a/examples/gno.land/r/demo/users/users.gno +++ b/examples/gno.land/r/demo/users/users.gno @@ -35,15 +35,15 @@ func Register(inviter std.Address, name string, profile string) { std.AssertOriginCall() // assert invited or paid. caller := std.GetCallerAt(2) - if caller != std.OrigCaller() { + if caller != std.OriginCaller() { panic("should not happen") // because std.AssertOrigCall(). } - sentCoins := std.OrigSend() + sentCoins := std.OriginSend() minCoin := std.NewCoin("ugnot", minFee) if inviter == "" { - // banker := std.GetBanker(std.BankerTypeOrigSend) + // banker := std.GetBanker(std.BankerTypeOriginSend) if len(sentCoins) == 1 && sentCoins[0].IsGTE(minCoin) { if sentCoins[0].Amount > minFee*maxFeeMult { panic("payment must not be greater than " + strconv.Itoa(int(minFee*maxFeeMult))) @@ -119,7 +119,7 @@ func Invite(invitee string) { std.AssertOriginCall() // get caller/inviter. caller := std.GetCallerAt(2) - if caller != std.OrigCaller() { + if caller != std.OriginCaller() { panic("should not happen") // because std.AssertOrigCall(). } lines := strings.Split(invitee, "\n") @@ -158,7 +158,7 @@ func GrantInvites(invites string) { std.AssertOriginCall() // assert admin. caller := std.GetCallerAt(2) - if caller != std.OrigCaller() { + if caller != std.OriginCaller() { panic("should not happen") // because std.AssertOrigCall(). } if caller != admin { @@ -278,7 +278,7 @@ func AdminAddRestrictedName(name string) { // assert CallTx call. std.AssertOriginCall() // get caller - caller := std.OrigCaller() + caller := std.OriginCaller() // assert admin if caller != admin { panic("unauthorized") diff --git a/examples/gno.land/r/demo/users/z_0_filetest.gno b/examples/gno.land/r/demo/users/z_0_filetest.gno index cbb2e9209f4..bc06449bf90 100644 --- a/examples/gno.land/r/demo/users/z_0_filetest.gno +++ b/examples/gno.land/r/demo/users/z_0_filetest.gno @@ -7,7 +7,7 @@ import ( ) func main() { - std.TestSetOrigSend(std.Coins{std.NewCoin("dontcare", 1)}, nil) + std.TestSetOriginSend(std.Coins{std.NewCoin("dontcare", 1)}, nil) users.Register("", "gnouser", "my profile") println("done") } diff --git a/examples/gno.land/r/demo/users/z_10_filetest.gno b/examples/gno.land/r/demo/users/z_10_filetest.gno index c2d7d9f67f1..e4a6f714d6b 100644 --- a/examples/gno.land/r/demo/users/z_10_filetest.gno +++ b/examples/gno.land/r/demo/users/z_10_filetest.gno @@ -11,20 +11,20 @@ import ( const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") func init() { - caller := std.OrigCaller() // main + caller := std.OriginCaller() // main test2 := testutils.TestAddress("test2") // as admin, invite gnouser and test2 - std.TestSetOrigCaller(admin) + std.TestSetOriginCaller(admin) users.Invite(caller.String() + "\n" + test2.String()) // register as caller - std.TestSetOrigCaller(caller) + std.TestSetOriginCaller(caller) users.Register(admin, "gnouser", "my profile") } func main() { // register as test2 test2 := testutils.TestAddress("test2") - std.TestSetOrigCaller(test2) + std.TestSetOriginCaller(test2) users.Register(admin, "test222", "my profile 2") println("done") } diff --git a/examples/gno.land/r/demo/users/z_11_filetest.gno b/examples/gno.land/r/demo/users/z_11_filetest.gno index b8da48e3887..378b58cbfba 100644 --- a/examples/gno.land/r/demo/users/z_11_filetest.gno +++ b/examples/gno.land/r/demo/users/z_11_filetest.gno @@ -11,12 +11,12 @@ import ( const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") func main() { - caller := std.OrigCaller() // main - std.TestSetOrigCaller(admin) + caller := std.OriginCaller() // main + std.TestSetOriginCaller(admin) users.AdminAddRestrictedName("superrestricted") // test restricted name - std.TestSetOrigCaller(caller) + std.TestSetOriginCaller(caller) users.Register("", "superrestricted", "my profile") println("done") } diff --git a/examples/gno.land/r/demo/users/z_11b_filetest.gno b/examples/gno.land/r/demo/users/z_11b_filetest.gno index 350952f3a8d..2ff0a66f87c 100644 --- a/examples/gno.land/r/demo/users/z_11b_filetest.gno +++ b/examples/gno.land/r/demo/users/z_11b_filetest.gno @@ -11,14 +11,14 @@ import ( const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") func main() { - caller := std.OrigCaller() // main - std.TestSetOrigCaller(admin) + caller := std.OriginCaller() // main + std.TestSetOriginCaller(admin) // add restricted name users.AdminAddRestrictedName("superrestricted") // grant invite to caller users.Invite(caller.String()) // set back caller - std.TestSetOrigCaller(caller) + std.TestSetOriginCaller(caller) // register restricted name with admin invite users.Register(admin, "superrestricted", "my profile") println("done") diff --git a/examples/gno.land/r/demo/users/z_2_filetest.gno b/examples/gno.land/r/demo/users/z_2_filetest.gno index 9d25bbd989b..1436c53ab79 100644 --- a/examples/gno.land/r/demo/users/z_2_filetest.gno +++ b/examples/gno.land/r/demo/users/z_2_filetest.gno @@ -12,18 +12,18 @@ import ( const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") func main() { - caller := std.OrigCaller() // main + caller := std.OriginCaller() // main users.Register("", "gnouser", "my profile") // as admin, grant invites to gnouser - std.TestSetOrigCaller(admin) + std.TestSetOriginCaller(admin) users.GrantInvites(caller.String() + ":1") // switch back to caller - std.TestSetOrigCaller(caller) + std.TestSetOriginCaller(caller) // invite another addr test1 := testutils.TestAddress("test1") users.Invite(test1.String()) // switch to test1 - std.TestSetOrigCaller(test1) + std.TestSetOriginCaller(test1) users.Register(caller, "satoshi", "my other profile") println("done") } diff --git a/examples/gno.land/r/demo/users/z_3_filetest.gno b/examples/gno.land/r/demo/users/z_3_filetest.gno index 15bbea39f35..29d082aad62 100644 --- a/examples/gno.land/r/demo/users/z_3_filetest.gno +++ b/examples/gno.land/r/demo/users/z_3_filetest.gno @@ -12,19 +12,19 @@ import ( const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") func main() { - caller := std.OrigCaller() // main + caller := std.OriginCaller() // main users.Register("", "gnouser", "my profile") // as admin, grant invites to gnouser - std.TestSetOrigCaller(admin) + std.TestSetOriginCaller(admin) users.GrantInvites(caller.String() + ":1") // switch back to caller - std.TestSetOrigCaller(caller) + std.TestSetOriginCaller(caller) // invite another addr test1 := testutils.TestAddress("test1") users.Invite(test1.String()) // switch to test1 - std.TestSetOrigCaller(test1) - std.TestSetOrigSend(std.Coins{{"dontcare", 1}}, nil) + std.TestSetOriginCaller(test1) + std.TestSetOriginSend(std.Coins{{"dontcare", 1}}, nil) users.Register(caller, "satoshi", "my other profile") println("done") } diff --git a/examples/gno.land/r/demo/users/z_4_filetest.gno b/examples/gno.land/r/demo/users/z_4_filetest.gno index 30d582a1fb6..53db4bb2f73 100644 --- a/examples/gno.land/r/demo/users/z_4_filetest.gno +++ b/examples/gno.land/r/demo/users/z_4_filetest.gno @@ -12,20 +12,20 @@ import ( const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") func main() { - caller := std.OrigCaller() // main + caller := std.OriginCaller() // main users.Register("", "gnouser", "my profile") // as admin, grant invites to gnouser - std.TestSetOrigCaller(admin) + std.TestSetOriginCaller(admin) users.GrantInvites(caller.String() + ":1") // switch back to caller - std.TestSetOrigCaller(caller) + std.TestSetOriginCaller(caller) // invite another addr test1 := testutils.TestAddress("test1") test2 := testutils.TestAddress("test2") users.Invite(test1.String()) // switch to test2 (not test1) - std.TestSetOrigCaller(test2) - std.TestSetOrigSend(std.Coins{{"dontcare", 1}}, nil) + std.TestSetOriginCaller(test2) + std.TestSetOriginSend(std.Coins{{"dontcare", 1}}, nil) users.Register(caller, "satoshi", "my other profile") println("done") } diff --git a/examples/gno.land/r/demo/users/z_5_filetest.gno b/examples/gno.land/r/demo/users/z_5_filetest.gno index 6c921f7eb8a..b02efa90212 100644 --- a/examples/gno.land/r/demo/users/z_5_filetest.gno +++ b/examples/gno.land/r/demo/users/z_5_filetest.gno @@ -12,19 +12,19 @@ import ( const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") func main() { - caller := std.OrigCaller() // main + caller := std.OriginCaller() // main users.Register("", "gnouser", "my profile") // as admin, grant invites to gnouser - std.TestSetOrigCaller(admin) + std.TestSetOriginCaller(admin) users.GrantInvites(caller.String() + ":1") // switch back to caller - std.TestSetOrigCaller(caller) + std.TestSetOriginCaller(caller) // invite another addr test1 := testutils.TestAddress("test1") users.Invite(test1.String()) // switch to test1 - std.TestSetOrigCaller(test1) - std.TestSetOrigSend(std.Coins{{"dontcare", 1}}, nil) + std.TestSetOriginCaller(test1) + std.TestSetOriginSend(std.Coins{{"dontcare", 1}}, nil) users.Register(caller, "satoshi", "my other profile") println(users.Render("")) println("========================================") diff --git a/examples/gno.land/r/demo/users/z_6_filetest.gno b/examples/gno.land/r/demo/users/z_6_filetest.gno index 329c52cdda4..737f09ee2cd 100644 --- a/examples/gno.land/r/demo/users/z_6_filetest.gno +++ b/examples/gno.land/r/demo/users/z_6_filetest.gno @@ -9,9 +9,9 @@ import ( const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") func main() { - caller := std.OrigCaller() + caller := std.OriginCaller() // as admin, grant invites to unregistered user. - std.TestSetOrigCaller(admin) + std.TestSetOriginCaller(admin) users.GrantInvites(caller.String() + ":1") println("done") } diff --git a/examples/gno.land/r/demo/users/z_7_filetest.gno b/examples/gno.land/r/demo/users/z_7_filetest.gno index a68ab255f24..e2c3e4e6612 100644 --- a/examples/gno.land/r/demo/users/z_7_filetest.gno +++ b/examples/gno.land/r/demo/users/z_7_filetest.gno @@ -12,22 +12,22 @@ import ( const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") func main() { - caller := std.OrigCaller() // main + caller := std.OriginCaller() // main users.Register("", "gnouser", "my profile") // as admin, grant invites to gnouser - std.TestSetOrigCaller(admin) + std.TestSetOriginCaller(admin) users.GrantInvites(caller.String() + ":1") // switch back to caller - std.TestSetOrigCaller(caller) + std.TestSetOriginCaller(caller) // invite another addr test1 := testutils.TestAddress("test1") users.Invite(test1.String()) // switch to test1 - std.TestSetOrigCaller(test1) - std.TestSetOrigSend(std.Coins{{"dontcare", 1}}, nil) + std.TestSetOriginCaller(test1) + std.TestSetOriginSend(std.Coins{{"dontcare", 1}}, nil) users.Register(caller, "satoshi", "my other profile") // as admin, grant invites to gnouser(again) and satoshi. - std.TestSetOrigCaller(admin) + std.TestSetOriginCaller(admin) users.GrantInvites(caller.String() + ":1\n" + test1.String() + ":1") println("done") } diff --git a/examples/gno.land/r/demo/users/z_7b_filetest.gno b/examples/gno.land/r/demo/users/z_7b_filetest.gno index 7fdf9fd1daf..c35b9756d3e 100644 --- a/examples/gno.land/r/demo/users/z_7b_filetest.gno +++ b/examples/gno.land/r/demo/users/z_7b_filetest.gno @@ -12,22 +12,22 @@ import ( const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") func main() { - caller := std.OrigCaller() // main + caller := std.OriginCaller() // main users.Register("", "gnouser", "my profile") // as admin, grant invites to gnouser - std.TestSetOrigCaller(admin) + std.TestSetOriginCaller(admin) users.GrantInvites(caller.String() + ":1\n") // switch back to caller - std.TestSetOrigCaller(caller) + std.TestSetOriginCaller(caller) // invite another addr test1 := testutils.TestAddress("test1") users.Invite(test1.String()) // switch to test1 - std.TestSetOrigCaller(test1) - std.TestSetOrigSend(std.Coins{{"dontcare", 1}}, nil) + std.TestSetOriginCaller(test1) + std.TestSetOriginSend(std.Coins{{"dontcare", 1}}, nil) users.Register(caller, "satoshi", "my other profile") // as admin, grant invites to gnouser(again) and satoshi. - std.TestSetOrigCaller(admin) + std.TestSetOriginCaller(admin) users.GrantInvites(caller.String() + ":1\n" + test1.String() + ":1") println("done") } diff --git a/examples/gno.land/r/demo/users/z_8_filetest.gno b/examples/gno.land/r/demo/users/z_8_filetest.gno index 01c45cb7ec6..56785ae85f1 100644 --- a/examples/gno.land/r/demo/users/z_8_filetest.gno +++ b/examples/gno.land/r/demo/users/z_8_filetest.gno @@ -12,22 +12,22 @@ import ( const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") func main() { - caller := std.OrigCaller() // main + caller := std.OriginCaller() // main users.Register("", "gnouser", "my profile") // as admin, grant invites to gnouser - std.TestSetOrigCaller(admin) + std.TestSetOriginCaller(admin) users.GrantInvites(caller.String() + ":1") // switch back to caller - std.TestSetOrigCaller(caller) + std.TestSetOriginCaller(caller) // invite another addr test1 := testutils.TestAddress("test1") users.Invite(test1.String()) // switch to test1 - std.TestSetOrigCaller(test1) - std.TestSetOrigSend(std.Coins{{"dontcare", 1}}, nil) + std.TestSetOriginCaller(test1) + std.TestSetOriginSend(std.Coins{{"dontcare", 1}}, nil) users.Register(caller, "satoshi", "my other profile") // as admin, grant invites to gnouser(again) and nonexistent user. - std.TestSetOrigCaller(admin) + std.TestSetOriginCaller(admin) test2 := testutils.TestAddress("test2") users.GrantInvites(caller.String() + ":1\n" + test2.String() + ":1") println("done") diff --git a/examples/gno.land/r/demo/users/z_9_filetest.gno b/examples/gno.land/r/demo/users/z_9_filetest.gno index 35a60722a26..d71e19d616d 100644 --- a/examples/gno.land/r/demo/users/z_9_filetest.gno +++ b/examples/gno.land/r/demo/users/z_9_filetest.gno @@ -10,16 +10,16 @@ import ( const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") func main() { - caller := std.OrigCaller() // main + caller := std.OriginCaller() // main test2 := testutils.TestAddress("test2") // as admin, invite gnouser and test2 - std.TestSetOrigCaller(admin) + std.TestSetOriginCaller(admin) users.Invite(caller.String() + "\n" + test2.String()) // register as caller - std.TestSetOrigCaller(caller) + std.TestSetOriginCaller(caller) users.Register(admin, "gnouser", "my profile") // register as test2 - std.TestSetOrigCaller(test2) + std.TestSetOriginCaller(test2) users.Register(admin, "test222", "my profile 2") println("done") } diff --git a/examples/gno.land/r/demo/wugnot/wugnot.gno b/examples/gno.land/r/demo/wugnot/wugnot.gno index a573d4696fe..91617dfff27 100644 --- a/examples/gno.land/r/demo/wugnot/wugnot.gno +++ b/examples/gno.land/r/demo/wugnot/wugnot.gno @@ -25,7 +25,7 @@ func init() { func Deposit() { caller := std.PrevRealm().Addr() - sent := std.OrigSend() + sent := std.OriginSend() amount := sent.AmountOf("ugnot") require(uint64(amount) >= ugnotMinDeposit, ufmt.Sprintf("Deposit below minimum: %d/%d ugnot.", amount, ugnotMinDeposit)) diff --git a/examples/gno.land/r/demo/wugnot/z0_filetest.gno b/examples/gno.land/r/demo/wugnot/z0_filetest.gno index 264bc8f19aa..72a9cd4e95b 100644 --- a/examples/gno.land/r/demo/wugnot/z0_filetest.gno +++ b/examples/gno.land/r/demo/wugnot/z0_filetest.gno @@ -29,8 +29,8 @@ func main() { // println(wugnot.Render("queues")) // println("A -", wugnot.Render("")) - std.TestSetOrigCaller(addr1) - std.TestSetOrigSend(std.Coins{{"ugnot", 123_400}}, nil) + std.TestSetOriginCaller(addr1) + std.TestSetOriginSend(std.Coins{{"ugnot", 123_400}}, nil) wugnot.Deposit() printBalances() wugnot.Withdraw(4242) @@ -40,7 +40,7 @@ func main() { func printBalances() { printSingleBalance := func(name string, addr std.Address) { wugnotBal := wugnot.BalanceOf(pusers.AddressOrName(addr)) - std.TestSetOrigCaller(addr) + std.TestSetOriginCaller(addr) robanker := std.GetBanker(std.BankerTypeReadonly) coins := robanker.GetCoins(addr).AmountOf("ugnot") fmt.Printf("| %-13s | addr=%s | wugnot=%-5d | ugnot=%-9d |\n", diff --git a/examples/gno.land/r/gnoland/blog/admin.gno b/examples/gno.land/r/gnoland/blog/admin.gno index e8bfaff9af4..046e957345f 100644 --- a/examples/gno.land/r/gnoland/blog/admin.gno +++ b/examples/gno.land/r/gnoland/blog/admin.gno @@ -17,7 +17,7 @@ var ( ) func init() { - // adminAddr = std.OrigCaller() // FIXME: find a way to use this from the main's genesis. + // adminAddr = std.OriginCaller() // FIXME: find a way to use this from the main's genesis. adminAddr = "g1manfred47kzduec920z88wfr64ylksmdcedlf5" // @moul } @@ -53,7 +53,7 @@ func NewPostExecutor(slug, title, body, publicationDate, authors, tags string) d func ModAddPost(slug, title, body, publicationDate, authors, tags string) { assertIsModerator() - caller := std.OrigCaller() + caller := std.OriginCaller() addPost(caller, slug, title, body, publicationDate, authors, tags) } @@ -120,14 +120,14 @@ func isCommenter(addr std.Address) bool { } func assertIsAdmin() { - caller := std.OrigCaller() + caller := std.OriginCaller() if !isAdmin(caller) { panic("access restricted.") } } func assertIsModerator() { - caller := std.OrigCaller() + caller := std.OriginCaller() if isAdmin(caller) || isModerator(caller) { return } @@ -135,7 +135,7 @@ func assertIsModerator() { } func assertIsCommenter() { - caller := std.OrigCaller() + caller := std.OriginCaller() if isAdmin(caller) || isModerator(caller) || isCommenter(caller) { return } diff --git a/examples/gno.land/r/gnoland/blog/gnoblog.gno b/examples/gno.land/r/gnoland/blog/gnoblog.gno index ab40da125e0..ee8fc01db69 100644 --- a/examples/gno.land/r/gnoland/blog/gnoblog.gno +++ b/examples/gno.land/r/gnoland/blog/gnoblog.gno @@ -15,7 +15,7 @@ func AddComment(postSlug, comment string) { assertIsCommenter() assertNotInPause() - caller := std.OrigCaller() + caller := std.OriginCaller() err := b.GetPost(postSlug).AddComment(caller, comment) checkErr(err) } diff --git a/examples/gno.land/r/gnoland/blog/gnoblog_test.gno b/examples/gno.land/r/gnoland/blog/gnoblog_test.gno index 9d1ecd798d7..e8a6813a4e3 100644 --- a/examples/gno.land/r/gnoland/blog/gnoblog_test.gno +++ b/examples/gno.land/r/gnoland/blog/gnoblog_test.gno @@ -7,9 +7,9 @@ import ( ) func TestPackage(t *testing.T) { - std.TestSetOrigCaller(std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5")) + std.TestSetOriginCaller(std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5")) - author := std.OrigCaller() + author := std.OriginCaller() // by default, no posts. { diff --git a/examples/gno.land/r/gnoland/events/events_test.gno b/examples/gno.land/r/gnoland/events/events_test.gno index 1d79b754ee4..47d327203d7 100644 --- a/examples/gno.land/r/gnoland/events/events_test.gno +++ b/examples/gno.land/r/gnoland/events/events_test.gno @@ -18,7 +18,7 @@ var ( ) func TestAddEvent(t *testing.T) { - std.TestSetOrigCaller(su) + std.TestSetOriginCaller(su) std.TestSetRealm(suRealm) e1Start := parsedTimeNow.Add(time.Hour * 24 * 5) @@ -61,7 +61,7 @@ func TestAddEvent(t *testing.T) { } func TestAddEventErrors(t *testing.T) { - std.TestSetOrigCaller(su) + std.TestSetOriginCaller(su) std.TestSetRealm(suRealm) _, err := AddEvent("", "sample desc", "gno.land", "gnome land", "2009-02-13T23:31:31Z", "2009-02-13T23:33:31Z") @@ -85,7 +85,7 @@ func TestAddEventErrors(t *testing.T) { } func TestDeleteEvent(t *testing.T) { - std.TestSetOrigCaller(su) + std.TestSetOriginCaller(su) std.TestSetRealm(suRealm) e1Start := parsedTimeNow.Add(time.Hour * 24 * 5) @@ -108,7 +108,7 @@ func TestDeleteEvent(t *testing.T) { } func TestEditEvent(t *testing.T) { - std.TestSetOrigCaller(su) + std.TestSetOriginCaller(su) std.TestSetRealm(suRealm) e1Start := parsedTimeNow.Add(time.Hour * 24 * 5) @@ -138,7 +138,7 @@ func TestEditEvent(t *testing.T) { } func TestInvalidEdit(t *testing.T) { - std.TestSetOrigCaller(su) + std.TestSetOriginCaller(su) std.TestSetRealm(suRealm) uassert.PanicsWithMessage(t, ErrNoSuchID.Error(), func() { @@ -165,7 +165,7 @@ func TestParseTimes(t *testing.T) { } func TestRenderEventWidget(t *testing.T) { - std.TestSetOrigCaller(su) + std.TestSetOriginCaller(su) std.TestSetRealm(suRealm) // No events yet diff --git a/examples/gno.land/r/gnoland/faucet/admin.gno b/examples/gno.land/r/gnoland/faucet/admin.gno index 2da0b6f42be..d991fcd9816 100644 --- a/examples/gno.land/r/gnoland/faucet/admin.gno +++ b/examples/gno.land/r/gnoland/faucet/admin.gno @@ -79,7 +79,7 @@ func AdminRemoveController(addr std.Address) string { } func assertIsAdmin() error { - caller := std.OrigCaller() + caller := std.OriginCaller() if caller != gAdminAddr { return errors.New("restricted for admin") } diff --git a/examples/gno.land/r/gnoland/faucet/faucet.gno b/examples/gno.land/r/gnoland/faucet/faucet.gno index c4f47fb9208..0297e25ca86 100644 --- a/examples/gno.land/r/gnoland/faucet/faucet.gno +++ b/examples/gno.land/r/gnoland/faucet/faucet.gno @@ -81,7 +81,7 @@ func Render(_ string) string { } func assertIsController() error { - caller := std.OrigCaller() + caller := std.OriginCaller() ok := gControllers.Has(caller.String()) if !ok { diff --git a/examples/gno.land/r/gnoland/faucet/faucet_test.gno b/examples/gno.land/r/gnoland/faucet/faucet_test.gno index cecbb2ebcd6..ae4e1a8e857 100644 --- a/examples/gno.land/r/gnoland/faucet/faucet_test.gno +++ b/examples/gno.land/r/gnoland/faucet/faucet_test.gno @@ -33,20 +33,20 @@ func TestPackage(t *testing.T) { // by default, balance is empty, and as a user I cannot call Transfer, or Admin commands. assertBalance(t, test1addr, 0) - std.TestSetOrigCaller(test1addr) + std.TestSetOriginCaller(test1addr) assertErr(t, faucet.Transfer(test1addr, 1000000)) assertErr(t, faucet.AdminAddController(controlleraddr1)) - std.TestSetOrigCaller(controlleraddr1) + std.TestSetOriginCaller(controlleraddr1) assertErr(t, faucet.Transfer(test1addr, 1000000)) // as an admin, add the controller to contract and deposit more 2000gnot to contract - std.TestSetOrigCaller(adminaddr) + std.TestSetOriginCaller(adminaddr) assertNoErr(t, faucet.AdminAddController(controlleraddr1)) assertBalance(t, faucetaddr, 1000000000) // now, send some tokens as controller. - std.TestSetOrigCaller(controlleraddr1) + std.TestSetOriginCaller(controlleraddr1) assertNoErr(t, faucet.Transfer(test1addr, 1000000)) assertBalance(t, test1addr, 1000000) assertNoErr(t, faucet.Transfer(test1addr, 1000000)) @@ -55,13 +55,13 @@ func TestPackage(t *testing.T) { // remove controller // as an admin, remove controller - std.TestSetOrigCaller(adminaddr) + std.TestSetOriginCaller(adminaddr) assertNoErr(t, faucet.AdminRemoveController(controlleraddr1)) - std.TestSetOrigCaller(controlleraddr1) + std.TestSetOriginCaller(controlleraddr1) assertErr(t, faucet.Transfer(test1addr, 1000000)) // duplicate controller - std.TestSetOrigCaller(adminaddr) + std.TestSetOriginCaller(adminaddr) assertNoErr(t, faucet.AdminAddController(controlleraddr1)) assertErr(t, faucet.AdminAddController(controlleraddr1)) // add more than more than allowed controllers @@ -77,13 +77,13 @@ func TestPackage(t *testing.T) { assertErr(t, faucet.AdminAddController(controlleraddr11)) // send more than per transfer limit - std.TestSetOrigCaller(adminaddr) + std.TestSetOriginCaller(adminaddr) faucet.AdminSetTransferLimit(300000000) - std.TestSetOrigCaller(controlleraddr1) + std.TestSetOriginCaller(controlleraddr1) assertErr(t, faucet.Transfer(test1addr, 301000000)) // block transefer from the address not on the controllers list. - std.TestSetOrigCaller(controlleraddr11) + std.TestSetOriginCaller(controlleraddr11) assertErr(t, faucet.Transfer(test1addr, 1000000)) } diff --git a/examples/gno.land/r/gnoland/faucet/z2_filetest.gno b/examples/gno.land/r/gnoland/faucet/z2_filetest.gno index d0616b3afcd..1490c46ffc9 100644 --- a/examples/gno.land/r/gnoland/faucet/z2_filetest.gno +++ b/examples/gno.land/r/gnoland/faucet/z2_filetest.gno @@ -20,7 +20,7 @@ func main() { controlleraddr1 = testutils.TestAddress("controller1") controlleraddr2 = testutils.TestAddress("controller2") ) - std.TestSetOrigCaller(adminaddr) + std.TestSetOriginCaller(adminaddr) err := faucet.AdminAddController(controlleraddr1) if err != "" { panic(err) diff --git a/examples/gno.land/r/gnoland/faucet/z3_filetest.gno b/examples/gno.land/r/gnoland/faucet/z3_filetest.gno index 0da06593710..90612aa3548 100644 --- a/examples/gno.land/r/gnoland/faucet/z3_filetest.gno +++ b/examples/gno.land/r/gnoland/faucet/z3_filetest.gno @@ -22,7 +22,7 @@ func main() { testaddr1 = testutils.TestAddress("test1") testaddr2 = testutils.TestAddress("test2") ) - std.TestSetOrigCaller(adminaddr) + std.TestSetOriginCaller(adminaddr) err := faucet.AdminAddController(controlleraddr1) if err != "" { panic(err) @@ -31,12 +31,12 @@ func main() { if err != "" { panic(err) } - std.TestSetOrigCaller(controlleraddr1) + std.TestSetOriginCaller(controlleraddr1) err = faucet.Transfer(testaddr1, 1000000) if err != "" { panic(err) } - std.TestSetOrigCaller(controlleraddr2) + std.TestSetOriginCaller(controlleraddr2) err = faucet.Transfer(testaddr1, 2000000) if err != "" { panic(err) diff --git a/examples/gno.land/r/gnoland/ghverify/contract.gno b/examples/gno.land/r/gnoland/ghverify/contract.gno index 38567620a6b..1540e548887 100644 --- a/examples/gno.land/r/gnoland/ghverify/contract.gno +++ b/examples/gno.land/r/gnoland/ghverify/contract.gno @@ -16,7 +16,7 @@ const ( ) var ( - ownerAddress = std.OrigCaller() + ownerAddress = std.OriginCaller() oracle *gnorkle.Instance postHandler postGnorkleMessageHandler @@ -70,7 +70,7 @@ func (h postGnorkleMessageHandler) Handle(i *gnorkle.Instance, funcType message. // RequestVerification creates a new static feed with a single task that will // instruct an agent to verify the github handle / gno address pair. func RequestVerification(githubHandle string) { - gnoAddress := string(std.OrigCaller()) + gnoAddress := string(std.OriginCaller()) if err := oracle.AddFeeds( static.NewSingleValueFeed( gnoAddress, @@ -102,7 +102,7 @@ func GnorkleEntrypoint(message string) string { // SetOwner transfers ownership of the contract to the given address. func SetOwner(owner std.Address) { - if ownerAddress != std.OrigCaller() { + if ownerAddress != std.OriginCaller() { panic("only the owner can set a new owner") } diff --git a/examples/gno.land/r/gnoland/ghverify/contract_test.gno b/examples/gno.land/r/gnoland/ghverify/contract_test.gno index a6a40c02e81..3bf6e306fed 100644 --- a/examples/gno.land/r/gnoland/ghverify/contract_test.gno +++ b/examples/gno.land/r/gnoland/ghverify/contract_test.gno @@ -8,7 +8,7 @@ import ( ) func TestVerificationLifecycle(t *testing.T) { - defaultAddress := std.OrigCaller() + defaultAddress := std.OriginCaller() user1Address := std.Address(testutils.TestAddress("user 1")) user2Address := std.Address(testutils.TestAddress("user 2")) @@ -19,7 +19,7 @@ func TestVerificationLifecycle(t *testing.T) { } // Make a verification request with the created user. - std.TestSetOrigCaller(user1Address) + std.TestSetOriginCaller(user1Address) RequestVerification("deelawn") // A subsequent request from the same address should panic because there is @@ -44,13 +44,13 @@ func TestVerificationLifecycle(t *testing.T) { } // Make a verification request with the created user. - std.TestSetOrigCaller(user2Address) + std.TestSetOriginCaller(user2Address) RequestVerification("omarsy") // Set the caller back to the whitelisted user and verify that the feed data // returned matches what should have been created by the `RequestVerification` // invocation. - std.TestSetOrigCaller(defaultAddress) + std.TestSetOriginCaller(defaultAddress) result = GnorkleEntrypoint("request") expResult := `[{"id":"` + string(user1Address) + `","type":"0","value_type":"string","tasks":[{"gno_address":"` + string(user1Address) + `","github_handle":"deelawn"}]},` + @@ -61,7 +61,7 @@ func TestVerificationLifecycle(t *testing.T) { } // Try to trigger feed ingestion from the non-authorized user. - std.TestSetOrigCaller(user1Address) + std.TestSetOriginCaller(user1Address) func() { defer func() { if r := recover(); r != nil { @@ -75,7 +75,7 @@ func TestVerificationLifecycle(t *testing.T) { } // Set the caller back to the whitelisted user and transfer contract ownership. - std.TestSetOrigCaller(defaultAddress) + std.TestSetOriginCaller(defaultAddress) SetOwner(defaultAddress) // Now trigger the feed ingestion from the user and new owner and only whitelisted address. diff --git a/examples/gno.land/r/gnoland/home/overide_filetest.gno b/examples/gno.land/r/gnoland/home/overide_filetest.gno index be7e33501d6..70c59cbaded 100644 --- a/examples/gno.land/r/gnoland/home/overide_filetest.gno +++ b/examples/gno.land/r/gnoland/home/overide_filetest.gno @@ -8,7 +8,7 @@ import ( ) func main() { - std.TestSetOrigCaller("g1manfred47kzduec920z88wfr64ylksmdcedlf5") + std.TestSetOriginCaller("g1manfred47kzduec920z88wfr64ylksmdcedlf5") home.AdminSetOverride("Hello World!") println(home.Render("")) home.AdminTransferOwnership(testutils.TestAddress("newAdmin")) diff --git a/examples/gno.land/r/gnoland/pages/admin.gno b/examples/gno.land/r/gnoland/pages/admin.gno index 894e76a41e7..963c9161df7 100644 --- a/examples/gno.land/r/gnoland/pages/admin.gno +++ b/examples/gno.land/r/gnoland/pages/admin.gno @@ -14,7 +14,7 @@ var ( ) func init() { - // adminAddr = std.OrigCaller() // FIXME: find a way to use this from the main's genesis. + // adminAddr = std.OriginCaller() // FIXME: find a way to use this from the main's genesis. adminAddr = "g1manfred47kzduec920z88wfr64ylksmdcedlf5" // @moul } @@ -41,7 +41,7 @@ func AdminRemoveModerator(addr std.Address) { func ModAddPost(slug, title, body, publicationDate, authors, tags string) { assertIsModerator() - caller := std.OrigCaller() + caller := std.OriginCaller() tagList := strings.Split(tags, ",") authorList := strings.Split(authors, ",") @@ -69,14 +69,14 @@ func isModerator(addr std.Address) bool { } func assertIsAdmin() { - caller := std.OrigCaller() + caller := std.OriginCaller() if !isAdmin(caller) { panic("access restricted.") } } func assertIsModerator() { - caller := std.OrigCaller() + caller := std.OriginCaller() if isAdmin(caller) || isModerator(caller) { return } diff --git a/examples/gno.land/r/gnoland/valopers/v2/valopers.gno b/examples/gno.land/r/gnoland/valopers/v2/valopers.gno index 93bc24c1aa0..cd2a99bb336 100644 --- a/examples/gno.land/r/gnoland/valopers/v2/valopers.gno +++ b/examples/gno.land/r/gnoland/valopers/v2/valopers.gno @@ -148,7 +148,7 @@ func GovDAOProposal(address std.Address) { ) // Make sure the valoper is the caller - if std.OrigCaller() != address { + if std.OriginCaller() != address { panic(errValoperNotCaller) } diff --git a/examples/gno.land/r/gov/dao/bridge/bridge_test.gno b/examples/gno.land/r/gov/dao/bridge/bridge_test.gno index 38b5d4be257..cff93fc497a 100644 --- a/examples/gno.land/r/gov/dao/bridge/bridge_test.gno +++ b/examples/gno.land/r/gov/dao/bridge/bridge_test.gno @@ -47,7 +47,7 @@ func TestBridge_SetDAO(t *testing.T) { } ) - std.TestSetOrigCaller(addr) + std.TestSetOriginCaller(addr) b.Ownable = ownable.NewWithAddress(addr) diff --git a/examples/gno.land/r/leon/hof/hof_test.gno b/examples/gno.land/r/leon/hof/hof_test.gno index 4d6f70eab88..4e133a6bbe8 100644 --- a/examples/gno.land/r/leon/hof/hof_test.gno +++ b/examples/gno.land/r/leon/hof/hof_test.gno @@ -103,7 +103,7 @@ func TestDownvote(t *testing.T) { func TestDelete(t *testing.T) { userRealm := std.NewUserRealm(admin) std.TestSetRealm(userRealm) - std.TestSetOrigCaller(admin) + std.TestSetOriginCaller(admin) uassert.PanicsWithMessage(t, ErrNoSuchItem.Error(), func() { Delete("nonexistentpkgpath") diff --git a/examples/gno.land/r/matijamarjanovic/home/config.gno b/examples/gno.land/r/matijamarjanovic/home/config.gno index 49d62077daf..8a5a4135025 100644 --- a/examples/gno.land/r/matijamarjanovic/home/config.gno +++ b/examples/gno.land/r/matijamarjanovic/home/config.gno @@ -48,7 +48,7 @@ func SetBackup(newAddress std.Address) error { } func checkAuthorized() error { - caller := std.OrigCaller() + caller := std.OriginCaller() if caller != mainAddr && caller != backupAddr { return errorUnauthorized } @@ -57,7 +57,7 @@ func checkAuthorized() error { } func AssertAuthorized() { - caller := std.OrigCaller() + caller := std.OriginCaller() if caller != mainAddr && caller != backupAddr { panic(errorUnauthorized) } diff --git a/examples/gno.land/r/matijamarjanovic/home/home.gno b/examples/gno.land/r/matijamarjanovic/home/home.gno index 78ac494da0e..bcb4f85ac44 100644 --- a/examples/gno.land/r/matijamarjanovic/home/home.gno +++ b/examples/gno.land/r/matijamarjanovic/home/home.gno @@ -71,21 +71,21 @@ func maxOfThree(a, b, c int64) int64 { } func VoteModern() { - ugnotAmount := std.OrigSend().AmountOf("ugnot") + ugnotAmount := std.OriginSend().AmountOf("ugnot") votes := ugnotAmount modernVotes += votes updateCurrentTheme() } func VoteClassic() { - ugnotAmount := std.OrigSend().AmountOf("ugnot") + ugnotAmount := std.OriginSend().AmountOf("ugnot") votes := ugnotAmount classicVotes += votes updateCurrentTheme() } func VoteMinimal() { - ugnotAmount := std.OrigSend().AmountOf("ugnot") + ugnotAmount := std.OriginSend().AmountOf("ugnot") votes := ugnotAmount minimalVotes += votes updateCurrentTheme() diff --git a/examples/gno.land/r/matijamarjanovic/home/home_test.gno b/examples/gno.land/r/matijamarjanovic/home/home_test.gno index 8cc6e6e5608..10e2e6db6fc 100644 --- a/examples/gno.land/r/matijamarjanovic/home/home_test.gno +++ b/examples/gno.land/r/matijamarjanovic/home/home_test.gno @@ -11,7 +11,7 @@ import ( // Helper function to set up test environment func setupTest() { - std.TestSetOrigCaller(std.Address("g1ej0qca5ptsw9kfr64ey8jvfy9eacga6mpj2z0y")) + std.TestSetOriginCaller(std.Address("g1ej0qca5ptsw9kfr64ey8jvfy9eacga6mpj2z0y")) } func TestUpdatePFP(t *testing.T) { @@ -41,7 +41,7 @@ func TestVoteModern(t *testing.T) { coinsSent := std.NewCoins(std.NewCoin("ugnot", 75000000)) coinsSpent := std.NewCoins(std.NewCoin("ugnot", 1)) - std.TestSetOrigSend(coinsSent, coinsSpent) + std.TestSetOriginSend(coinsSent, coinsSpent) VoteModern() uassert.Equal(t, int64(75000000), modernVotes, "Modern votes should be calculated correctly") @@ -55,7 +55,7 @@ func TestVoteClassic(t *testing.T) { coinsSent := std.NewCoins(std.NewCoin("ugnot", 75000000)) coinsSpent := std.NewCoins(std.NewCoin("ugnot", 1)) - std.TestSetOrigSend(coinsSent, coinsSpent) + std.TestSetOriginSend(coinsSent, coinsSpent) VoteClassic() uassert.Equal(t, int64(75000000), classicVotes, "Classic votes should be calculated correctly") @@ -69,7 +69,7 @@ func TestVoteMinimal(t *testing.T) { coinsSent := std.NewCoins(std.NewCoin("ugnot", 75000000)) coinsSpent := std.NewCoins(std.NewCoin("ugnot", 1)) - std.TestSetOrigSend(coinsSent, coinsSpent) + std.TestSetOriginSend(coinsSent, coinsSpent) VoteMinimal() uassert.Equal(t, int64(75000000), minimalVotes, "Minimal votes should be calculated correctly") diff --git a/examples/gno.land/r/moul/config/config.gno b/examples/gno.land/r/moul/config/config.gno index 203f7ce680b..0302163c3c8 100644 --- a/examples/gno.land/r/moul/config/config.gno +++ b/examples/gno.land/r/moul/config/config.gno @@ -14,7 +14,7 @@ func UpdateAddr(newAddr std.Address) { } func AssertIsAdmin() { - if std.OrigCaller() != addr { + if std.OriginCaller() != addr { panic("restricted area") } } diff --git a/examples/gno.land/r/moul/home/z2_filetest.gno b/examples/gno.land/r/moul/home/z2_filetest.gno index f471280d8ef..6055583c79d 100644 --- a/examples/gno.land/r/moul/home/z2_filetest.gno +++ b/examples/gno.land/r/moul/home/z2_filetest.gno @@ -7,7 +7,7 @@ import ( ) func main() { - std.TestSetOrigCaller("g1manfred47kzduec920z88wfr64ylksmdcedlf5") + std.TestSetOriginCaller("g1manfred47kzduec920z88wfr64ylksmdcedlf5") home.AddTodo("aaa") home.AddTodo("bbb") home.AddTodo("ccc") diff --git a/examples/gno.land/r/moul/present/admin.gno b/examples/gno.land/r/moul/present/admin.gno index 9e32cfa7624..2c0b93888ae 100644 --- a/examples/gno.land/r/moul/present/admin.gno +++ b/examples/gno.land/r/moul/present/admin.gno @@ -14,7 +14,7 @@ var ( ) func init() { - // adminAddr = std.OrigCaller() // FIXME: find a way to use this from the main's genesis. + // adminAddr = std.OriginCaller() // FIXME: find a way to use this from the main's genesis. adminAddr = "g1manfred47kzduec920z88wfr64ylksmdcedlf5" } @@ -41,7 +41,7 @@ func AdminRemoveModerator(addr std.Address) { func ModAddPost(slug, title, body, publicationDate, authors, tags string) { assertIsModerator() - caller := std.OrigCaller() + caller := std.OriginCaller() tagList := strings.Split(tags, ",") authorList := strings.Split(authors, ",") @@ -69,14 +69,14 @@ func isModerator(addr std.Address) bool { } func assertIsAdmin() { - caller := std.OrigCaller() + caller := std.OriginCaller() if !isAdmin(caller) { panic("access restricted.") } } func assertIsModerator() { - caller := std.OrigCaller() + caller := std.OriginCaller() if isAdmin(caller) || isModerator(caller) { return } diff --git a/examples/gno.land/r/n2p5/haystack/haystack_test.gno b/examples/gno.land/r/n2p5/haystack/haystack_test.gno index 52dadf8bf9e..2a25649ff5a 100644 --- a/examples/gno.land/r/n2p5/haystack/haystack_test.gno +++ b/examples/gno.land/r/n2p5/haystack/haystack_test.gno @@ -31,14 +31,14 @@ func TestHaystack(t *testing.T) { n2, _ := genNeedleHex(2) n3, _ := genNeedleHex(3) - std.TestSetOrigCaller(u1) + std.TestSetOriginCaller(u1) urequire.NotPanics(t, func() { Add(n1) }) urequire.PanicsWithMessage(t, haystack.ErrorDuplicateNeedle.Error(), func() { Add(n1) }) - std.TestSetOrigCaller(u2) + std.TestSetOriginCaller(u2) urequire.NotPanics(t, func() { Add(n2) }) urequire.NotPanics(t, func() { Add(n3) }) }) @@ -49,14 +49,14 @@ func TestHaystack(t *testing.T) { n1, h1 := genNeedleHex(4) _, h2 := genNeedleHex(5) - std.TestSetOrigCaller(u1) + std.TestSetOriginCaller(u1) urequire.NotPanics(t, func() { Add(n1) }) urequire.NotPanics(t, func() { result := Get(h1) urequire.Equal(t, n1, result) }) - std.TestSetOrigCaller(u2) + std.TestSetOriginCaller(u2) urequire.NotPanics(t, func() { result := Get(h1) urequire.Equal(t, n1, result) diff --git a/examples/gno.land/r/stefann/home/home.gno b/examples/gno.land/r/stefann/home/home.gno index 2d3a736793d..0aea795c4ca 100644 --- a/examples/gno.land/r/stefann/home/home.gno +++ b/examples/gno.land/r/stefann/home/home.gno @@ -131,8 +131,8 @@ func UpdateMaxSponsors(newMax int) { } func Donate() { - address := std.OrigCaller() - amount := std.OrigSend() + address := std.OriginCaller() + amount := std.OriginSend() if amount.AmountOf("ugnot") == 0 { panic("Donation must include GNOT") diff --git a/examples/gno.land/r/stefann/home/home_test.gno b/examples/gno.land/r/stefann/home/home_test.gno index ca146b9eb13..edc91301394 100644 --- a/examples/gno.land/r/stefann/home/home_test.gno +++ b/examples/gno.land/r/stefann/home/home_test.gno @@ -11,7 +11,7 @@ import ( func TestUpdatePFP(t *testing.T) { var owner = std.Address("g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8") - std.TestSetOrigCaller(owner) + std.TestSetOriginCaller(owner) profile.pfp = "" @@ -24,7 +24,7 @@ func TestUpdatePFP(t *testing.T) { func TestUpdateAboutMe(t *testing.T) { var owner = std.Address("g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8") - std.TestSetOrigCaller(owner) + std.TestSetOriginCaller(owner) profile.aboutMe = []string{} @@ -45,7 +45,7 @@ func TestUpdateAboutMe(t *testing.T) { func TestUpdateCities(t *testing.T) { var owner = std.Address("g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8") - std.TestSetOrigCaller(owner) + std.TestSetOriginCaller(owner) travel.cities = []City{} @@ -67,7 +67,7 @@ func TestUpdateCities(t *testing.T) { func TestUpdateJarLink(t *testing.T) { var owner = std.Address("g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8") - std.TestSetOrigCaller(owner) + std.TestSetOriginCaller(owner) travel.jarLink = "" @@ -80,7 +80,7 @@ func TestUpdateJarLink(t *testing.T) { func TestUpdateMaxSponsors(t *testing.T) { var owner = std.Address("g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8") - std.TestSetOrigCaller(owner) + std.TestSetOriginCaller(owner) sponsorship.maxSponsors = 0 @@ -100,7 +100,7 @@ func TestUpdateMaxSponsors(t *testing.T) { func TestAddCities(t *testing.T) { var owner = std.Address("g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8") - std.TestSetOrigCaller(owner) + std.TestSetOriginCaller(owner) travel.cities = []City{} @@ -128,7 +128,7 @@ func TestAddCities(t *testing.T) { func TestAddAboutMeRows(t *testing.T) { var owner = std.Address("g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8") - std.TestSetOrigCaller(owner) + std.TestSetOriginCaller(owner) profile.aboutMe = []string{} @@ -153,7 +153,7 @@ func TestAddAboutMeRows(t *testing.T) { func TestDonate(t *testing.T) { var user = testutils.TestAddress("user") - std.TestSetOrigCaller(user) + std.TestSetOriginCaller(user) sponsorship.sponsors = avl.NewTree() sponsorship.DonationsCount = 0 @@ -161,7 +161,7 @@ func TestDonate(t *testing.T) { travel.currentCityIndex = 0 coinsSent := std.NewCoins(std.NewCoin("ugnot", 500)) - std.TestSetOrigSend(coinsSent, std.NewCoins()) + std.TestSetOriginSend(coinsSent, std.NewCoins()) Donate() existingAmount, exists := sponsorship.sponsors.Get(string(user)) @@ -186,7 +186,7 @@ func TestDonate(t *testing.T) { } coinsSent = std.NewCoins(std.NewCoin("ugnot", 300)) - std.TestSetOrigSend(coinsSent, std.NewCoins()) + std.TestSetOriginSend(coinsSent, std.NewCoins()) Donate() existingAmount, exists = sponsorship.sponsors.Get(string(user)) @@ -209,7 +209,7 @@ func TestDonate(t *testing.T) { func TestGetTopSponsors(t *testing.T) { var user = testutils.TestAddress("user") - std.TestSetOrigCaller(user) + std.TestSetOriginCaller(user) sponsorship.sponsors = avl.NewTree() sponsorship.sponsorsCount = 0 @@ -240,7 +240,7 @@ func TestGetTopSponsors(t *testing.T) { func TestGetTotalDonations(t *testing.T) { var user = testutils.TestAddress("user") - std.TestSetOrigCaller(user) + std.TestSetOriginCaller(user) sponsorship.sponsors = avl.NewTree() sponsorship.sponsorsCount = 0 diff --git a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v1/v1.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v1/v1.gno index f7a1d828e38..100013ebd78 100644 --- a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v1/v1.gno +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v1/v1.gno @@ -25,7 +25,7 @@ func SetNextVersion(addr string) { std.AssertOriginCall() // assert admin. caller := std.GetCallerAt(2) - if caller != std.OrigCaller() { + if caller != std.OriginCaller() { panic("should not happen") // because std.AssertOrigCall(). } if caller != admin { diff --git a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v2/v2.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v2/v2.gno index 3b7b18ab1d1..32105b379dc 100644 --- a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v2/v2.gno +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v2/v2.gno @@ -29,7 +29,7 @@ func SetNextVersion(addr string) { std.AssertOriginCall() // assert admin. caller := std.GetCallerAt(2) - if caller != std.OrigCaller() { + if caller != std.OriginCaller() { panic("should not happen") // because std.AssertOrigCall(). } if caller != admin { diff --git a/examples/gno.land/r/x/nir1218_evaluation_proposal/committee.gno b/examples/gno.land/r/x/nir1218_evaluation_proposal/committee.gno index 316099db3dc..67311d401b3 100644 --- a/examples/gno.land/r/x/nir1218_evaluation_proposal/committee.gno +++ b/examples/gno.land/r/x/nir1218_evaluation_proposal/committee.gno @@ -36,7 +36,7 @@ func (c *Committee) DismissMembers(members []std.Address) []std.Address { func (c *Committee) AddCategory(name string, criteria []string) bool { // TODO error handling - if !c.isMember(std.OrigCaller()) { + if !c.isMember(std.OriginCaller()) { return false } category := NewCategory(name, criteria) @@ -45,7 +45,7 @@ func (c *Committee) AddCategory(name string, criteria []string) bool { } func (c *Committee) ApproveCategory(name string, option string) bool { - if !c.isMember(std.OrigCaller()) { + if !c.isMember(std.OriginCaller()) { return false } @@ -58,8 +58,8 @@ func (c *Committee) ApproveCategory(name string, option string) bool { return false } - vote := NewVote(std.OrigCaller(), option) - category.votes.Set(std.OrigCaller().String(), vote) + vote := NewVote(std.OriginCaller(), option) + category.votes.Set(std.OriginCaller().String(), vote) category.Tally() // TODO Add threshold factor for a category approval @@ -81,7 +81,7 @@ func (c *Committee) ApproveCategory(name string, option string) bool { // TODO error handling func (c *Committee) AddContribution(pr *PullRequest, contributor std.Address) (contributionId int, ok bool) { - if !c.isMember(std.OrigCaller()) { + if !c.isMember(std.OriginCaller()) { return -1, false } // Check the category of the PR matches a category this committee evaluates @@ -95,7 +95,7 @@ func (c *Committee) AddContribution(pr *PullRequest, contributor std.Address) (c // TODO error handling func (c *Committee) ApproveContribution(id int, option string) bool { - if !c.isMember(std.OrigCaller()) { + if !c.isMember(std.OriginCaller()) { return false } @@ -109,7 +109,7 @@ func (c *Committee) ApproveContribution(id int, option string) bool { return false } - vote := NewVote(std.OrigCaller(), option) + vote := NewVote(std.OriginCaller(), option) contribution.votes = append(contribution.votes, vote) contribution.Tally() diff --git a/examples/gno.land/r/x/nir1218_evaluation_proposal/committee_test.gno b/examples/gno.land/r/x/nir1218_evaluation_proposal/committee_test.gno index 8a3d16fd7f7..39e7fb6cabf 100644 --- a/examples/gno.land/r/x/nir1218_evaluation_proposal/committee_test.gno +++ b/examples/gno.land/r/x/nir1218_evaluation_proposal/committee_test.gno @@ -36,7 +36,7 @@ func TestCategoryEvaluationCriteria(t *testing.T) { c.DesignateMembers([]std.Address{member}) t.Run("Add First Committee Category and Evaluation Criteria", func(t *testing.T) { - std.TestSetOrigCaller(member) + std.TestSetOriginCaller(member) c.AddCategory(category, criteria) value, exists := c.categories.Get(category) if !exists { @@ -49,7 +49,7 @@ func TestCategoryEvaluationCriteria(t *testing.T) { }) t.Run("Add Second Committee Category and Evaluation Criteria", func(t *testing.T) { - std.TestSetOrigCaller(member) + std.TestSetOriginCaller(member) c.AddCategory(category2, criteria2) value2, exists2 := c.categories.Get(category2) if !exists2 { @@ -62,7 +62,7 @@ func TestCategoryEvaluationCriteria(t *testing.T) { }) t.Run("Approve First Committee Category", func(t *testing.T) { - std.TestSetOrigCaller(member) + std.TestSetOriginCaller(member) approved := c.ApproveCategory(category, VoteYes) if !approved { value, exists := c.categories.Get(category) diff --git a/gno.land/cmd/gnoland/testdata/initctx.txtar b/gno.land/cmd/gnoland/testdata/initctx.txtar index 64811459365..0dcbaf6d5b8 100644 --- a/gno.land/cmd/gnoland/testdata/initctx.txtar +++ b/gno.land/cmd/gnoland/testdata/initctx.txtar @@ -18,7 +18,7 @@ var orig = std.Address("orig") var prev = std.Address("prev") func init() { - orig = std.OrigCaller() + orig = std.OriginCaller() prev = std.PrevRealm().Addr() } diff --git a/gno.land/cmd/gnoland/testdata/issue_1786.txtar b/gno.land/cmd/gnoland/testdata/issue_1786.txtar index 1c60e84f8fa..9e789c723b3 100644 --- a/gno.land/cmd/gnoland/testdata/issue_1786.txtar +++ b/gno.land/cmd/gnoland/testdata/issue_1786.txtar @@ -50,7 +50,7 @@ import ( ) func ProxyWrap() { - sent := std.OrigSend() + sent := std.OriginSend() ugnotSent := uint64(sent.AmountOf("ugnot")) if ugnotSent == 0 { @@ -64,7 +64,7 @@ func ProxyWrap() { wugnot.Deposit() // `proxywugnot` has ugnot // SEND WUGNOT: PROXY_WUGNOT -> USER - wugnot.Transfer(pusers.AddressOrName(std.OrigCaller()), ugnotSent) + wugnot.Transfer(pusers.AddressOrName(std.OriginCaller()), ugnotSent) } func ProxyUnwrap(wugnotAmount uint64) { @@ -73,12 +73,12 @@ func ProxyUnwrap(wugnotAmount uint64) { } // SEND WUGNOT: USER -> PROXY_WUGNOT - wugnot.TransferFrom(pusers.AddressOrName(std.OrigCaller()), pusers.AddressOrName(std.CurrentRealm().Addr()), wugnotAmount) + wugnot.TransferFrom(pusers.AddressOrName(std.OriginCaller()), pusers.AddressOrName(std.CurrentRealm().Addr()), wugnotAmount) // UNWRAP IT wugnot.Withdraw(wugnotAmount) // SEND GNOT: PROXY_WUGNOT -> USER banker := std.GetBanker(std.BankerTypeRealmSend) - banker.SendCoins(std.CurrentRealm().Addr(), std.OrigCaller(), std.Coins{{"ugnot", int64(wugnotAmount)}}) + banker.SendCoins(std.CurrentRealm().Addr(), std.OriginCaller(), std.Coins{{"ugnot", int64(wugnotAmount)}}) } diff --git a/gno.land/cmd/gnoland/testdata/issue_2283.txtar b/gno.land/cmd/gnoland/testdata/issue_2283.txtar index 4f3e51c3e29..71398ef0d88 100644 --- a/gno.land/cmd/gnoland/testdata/issue_2283.txtar +++ b/gno.land/cmd/gnoland/testdata/issue_2283.txtar @@ -46,7 +46,7 @@ import ( }, { "Name": "feeds_test.gno", - "Body": "package social_feeds\n\nimport (\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t// Fake previous version for testing\n\tfeedsV7 \"gno.land/r/demo/teritori/social_feeds\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\trootPostID = PostID(0)\n\tpostID1 = PostID(1)\n\tfeedID1 = FeedID(1)\n\tcat1 = uint64(1)\n\tcat2 = uint64(2)\n\tuser = testutils.TestAddress(\"user\")\n\tfilter_all = []uint64{}\n)\n\nfunc getFeed1() *Feed {\n\treturn mustGetFeed(feedID1)\n}\n\nfunc getPost1() *Post {\n\tfeed1 := getFeed1()\n\tpost1 := feed1.MustGetPost(postID1)\n\treturn post1\n}\n\nfunc testCreateFeed(t *testing.T) {\n\tfeedID := CreateFeed(\"teritori1\")\n\tfeed := mustGetFeed(feedID)\n\n\tif feedID != 1 {\n\t\tt.Fatalf(\"expected feedID: 1, got %q.\", feedID)\n\t}\n\n\tif feed.name != \"teritori1\" {\n\t\tt.Fatalf(\"expected feedName: teritori1, got %q.\", feed.name)\n\t}\n}\n\nfunc testCreatePost(t *testing.T) {\n\tmetadata := `{\"gifs\": [], \"files\": [], \"title\": \"\", \"message\": \"testouille\", \"hashtags\": [], \"mentions\": [], \"createdAt\": \"2023-03-29T12:19:04.858Z\", \"updatedAt\": \"2023-03-29T12:19:04.858Z\"}`\n\tpostID := CreatePost(feedID1, rootPostID, cat1, metadata)\n\tfeed := mustGetFeed(feedID1)\n\tpost := feed.MustGetPost(postID)\n\n\tif postID != 1 {\n\t\tt.Fatalf(\"expected postID: 1, got %q.\", postID)\n\t}\n\n\tif post.category != cat1 {\n\t\tt.Fatalf(\"expected categoryID: %q, got %q.\", cat1, post.category)\n\t}\n}\n\nfunc toPostIDsStr(posts []*Post) string {\n\tvar postIDs []string\n\tfor _, post := range posts {\n\t\tpostIDs = append(postIDs, post.id.String())\n\t}\n\n\tpostIDsStr := strings.Join(postIDs, \",\")\n\treturn postIDsStr\n}\n\nfunc testGetPosts(t *testing.T) {\n\tuser := std.Address(\"user\")\n\tstd.TestSetOrigCaller(user)\n\n\tfeedID := CreateFeed(\"teritori10\")\n\tfeed := mustGetFeed(feedID)\n\n\tCreatePost(feedID, rootPostID, cat1, \"post1\")\n\tCreatePost(feedID, rootPostID, cat1, \"post2\")\n\tCreatePost(feedID, rootPostID, cat1, \"post3\")\n\tCreatePost(feedID, rootPostID, cat1, \"post4\")\n\tCreatePost(feedID, rootPostID, cat1, \"post5\")\n\tpostIDToFlagged := CreatePost(feedID, rootPostID, cat1, \"post6\")\n\tpostIDToHide := CreatePost(feedID, rootPostID, cat1, \"post7\")\n\tCreatePost(feedID, rootPostID, cat1, \"post8\")\n\n\tvar posts []*Post\n\tvar postIDsStr string\n\n\t// Query last 3 posts\n\tposts = getPosts(feed, 0, \"\", \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,7,6\" {\n\t\tt.Fatalf(\"expected posts order: 8,7,6. Got: %s\", postIDsStr)\n\t}\n\n\t// Query page 2\n\tposts = getPosts(feed, 0, \"\", \"\", []uint64{}, 3, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\tif postIDsStr != \"5,4,3\" {\n\t\tt.Fatalf(\"expected posts order: 5,4,3. Got: %s\", postIDsStr)\n\t}\n\n\t// Exclude hidden post\n\tHidePostForMe(feed.id, postIDToHide)\n\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,6,5\" {\n\t\tt.Fatalf(\"expected posts order: 8,6,5. Got: %s\", postIDsStr)\n\t}\n\n\t// Exclude flagged post\n\tFlagPost(feed.id, postIDToFlagged)\n\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,5,4\" {\n\t\tt.Fatalf(\"expected posts order: 8,5,4. Got: %s\", postIDsStr)\n\t}\n\n\t// Pagination with hidden/flagged posts\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 3, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"3,2,1\" {\n\t\tt.Fatalf(\"expected posts order: 3,2,1. Got: %s\", postIDsStr)\n\t}\n\n\t// Query out of range\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 6, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"\" {\n\t\tt.Fatalf(\"expected posts order: ''. Got: %s\", postIDsStr)\n\t}\n}\n\nfunc testReactPost(t *testing.T) {\n\tfeed := getFeed1()\n\tpost := getPost1()\n\n\ticon := \"🥰\"\n\tReactPost(feed.id, post.id, icon, true)\n\n\t// Set reaction\n\treactionCount_, ok := post.reactions.Get(\"🥰\")\n\tif !ok {\n\t\tt.Fatalf(\"expected 🥰 exists\")\n\t}\n\n\treactionCount := reactionCount_.(int)\n\tif reactionCount != 1 {\n\t\tt.Fatalf(\"expected reactionCount: 1, got %q.\", reactionCount)\n\t}\n\n\t// Unset reaction\n\tReactPost(feed.id, post.id, icon, false)\n\t_, exist := post.reactions.Get(\"🥰\")\n\tif exist {\n\t\tt.Fatalf(\"expected 🥰 not exist\")\n\t}\n}\n\nfunc testCreateAndDeleteComment(t *testing.T) {\n\tfeed1 := getFeed1()\n\tpost1 := getPost1()\n\n\tmetadata := `empty_meta_data`\n\n\tcommentID1 := CreatePost(feed1.id, post1.id, cat1, metadata)\n\tcommentID2 := CreatePost(feed1.id, post1.id, cat1, metadata)\n\tcomment2 := feed1.MustGetPost(commentID2)\n\n\tif comment2.id != 3 { // 1 post + 2 comments = 3\n\t\tt.Fatalf(\"expected comment postID: 3, got %q.\", comment2.id)\n\t}\n\n\tif comment2.parentID != post1.id {\n\t\tt.Fatalf(\"expected comment parentID: %q, got %q.\", post1.id, comment2.parentID)\n\t}\n\n\t// Check comment count on parent\n\tif post1.commentsCount != 2 {\n\t\tt.Fatalf(\"expected comments count: 2, got %d.\", post1.commentsCount)\n\t}\n\n\t// Get comments\n\tcomments := GetComments(feed1.id, post1.id, 0, 10)\n\tcommentsParsed := ujson.ParseSlice(comments)\n\n\tif len(commentsParsed) != 2 {\n\t\tt.Fatalf(\"expected encoded comments: 2, got %q.\", commentsParsed)\n\t}\n\n\t// Delete 1 comment\n\tDeletePost(feed1.id, comment2.id)\n\tcomments = GetComments(feed1.id, post1.id, 0, 10)\n\tcommentsParsed = ujson.ParseSlice(comments)\n\n\tif len(commentsParsed) != 1 {\n\t\tt.Fatalf(\"expected encoded comments: 1, got %q.\", commentsParsed)\n\t}\n\n\t// Check comment count on parent\n\tif post1.commentsCount != 1 {\n\t\tt.Fatalf(\"expected comments count: 1, got %d.\", post1.commentsCount)\n\t}\n}\n\nfunc countPosts(feedID FeedID, categories []uint64, limit uint8) int {\n\toffset := uint64(0)\n\n\tpostsStr := GetPosts(feedID, 0, \"\", categories, offset, limit)\n\tif postsStr == \"[]\" {\n\t\treturn 0\n\t}\n\n\tparsedPosts := ujson.ParseSlice(postsStr)\n\tpostsCount := len(parsedPosts)\n\treturn postsCount\n}\n\nfunc countPostsByUser(feedID FeedID, user string) int {\n\toffset := uint64(0)\n\tlimit := uint8(10)\n\n\tpostsStr := GetPosts(feedID, 0, user, []uint64{}, offset, limit)\n\tif postsStr == \"[]\" {\n\t\treturn 0\n\t}\n\n\tparsedPosts := ujson.ParseSlice(postsStr)\n\tpostsCount := len(parsedPosts)\n\treturn postsCount\n}\n\nfunc testFilterByCategories(t *testing.T) {\n\t// // Re-add reaction to test post list\n\t// ReactPost(1, postID, \"🥰\", true)\n\t// ReactPost(1, postID, \"😇\", true)\n\n\tfilter_cat1 := []uint64{1}\n\tfilter_cat1_2 := []uint64{1, 2}\n\tfilter_cat9 := []uint64{9}\n\tfilter_cat1_2_9 := []uint64{1, 2, 9}\n\n\tfeedID2 := CreateFeed(\"teritori2\")\n\tfeed2 := mustGetFeed(feedID2)\n\n\t// Create 2 posts on root with cat1\n\tpostID1 := CreatePost(feed2.id, rootPostID, cat1, \"metadata\")\n\tpostID2 := CreatePost(feed2.id, rootPostID, cat1, \"metadata\")\n\n\t// Create 1 posts on root with cat2\n\tpostID3 := CreatePost(feed2.id, rootPostID, cat2, \"metadata\")\n\n\t// Create comments on post 1\n\tcommentPostID1 := CreatePost(feed2.id, postID1, cat1, \"metadata\")\n\n\t// cat1: Should return max = limit\n\tif count := countPosts(feed2.id, filter_cat1, 1); count != 1 {\n\t\tt.Fatalf(\"expected posts count: 1, got %q.\", count)\n\t}\n\n\t// cat1: Should return max = total\n\tif count := countPosts(feed2.id, filter_cat1, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// cat 1 + 2: Should return max = limit\n\tif count := countPosts(feed2.id, filter_cat1_2, 2); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// cat 1 + 2: Should return max = total on both\n\tif count := countPosts(feed2.id, filter_cat1_2, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// cat 1, 2, 9: Should return total of 1, 2\n\tif count := countPosts(feed2.id, filter_cat1_2_9, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// cat 9: Should return 0\n\tif count := countPosts(feed2.id, filter_cat9, 10); count != 0 {\n\t\tt.Fatalf(\"expected posts count: 0, got %q.\", count)\n\t}\n\n\t// cat all: should return all\n\tif count := countPosts(feed2.id, filter_all, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// add comments should not impact the results\n\tCreatePost(feed2.id, postID1, cat1, \"metadata\")\n\tCreatePost(feed2.id, postID2, cat1, \"metadata\")\n\n\tif count := countPosts(feed2.id, filter_all, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// delete a post should affect the result\n\tDeletePost(feed2.id, postID1)\n\n\tif count := countPosts(feed2.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n}\n\nfunc testTipPost(t *testing.T) {\n\tcreator := testutils.TestAddress(\"creator\")\n\tstd.TestIssueCoins(creator, std.Coins{{\"ugnot\", 100_000_000}})\n\n\t// NOTE: Dont know why the address should be this to be able to call banker (= std.GetCallerAt(1))\n\ttipper := testutils.TestAddress(\"tipper\")\n\tstd.TestIssueCoins(tipper, std.Coins{{\"ugnot\", 50_000_000}})\n\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\n\t// Check Original coins of creator/tipper\n\tif coins := banker.GetCoins(creator); coins[0].Amount != 100_000_000 {\n\t\tt.Fatalf(\"expected creator coin count: 100_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\tif coins := banker.GetCoins(tipper); coins[0].Amount != 50_000_000 {\n\t\tt.Fatalf(\"expected tipper coin count: 50_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\t// Creator creates feed, post\n\tstd.TestSetOrigCaller(creator)\n\n\tfeedID3 := CreateFeed(\"teritori3\")\n\tfeed3 := mustGetFeed(feedID3)\n\n\tpostID1 := CreatePost(feed3.id, rootPostID, cat1, \"metadata\")\n\tpost1 := feed3.MustGetPost(postID1)\n\n\t// Tiper tips the ppst\n\tstd.TestSetOrigCaller(tipper)\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 1_000_000}}, nil)\n\tTipPost(feed3.id, post1.id)\n\n\t// Coin must be increased for creator\n\tif coins := banker.GetCoins(creator); coins[0].Amount != 101_000_000 {\n\t\tt.Fatalf(\"expected creator coin after beging tipped: 101_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\t// Total tip amount should increased\n\tif post1.tipAmount != 1_000_000 {\n\t\tt.Fatalf(\"expected total tipAmount: 1_000_000, got %d.\", post1.tipAmount)\n\t}\n\n\t// Add more tip should update this total\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 2_000_000}}, nil)\n\tTipPost(feed3.id, post1.id)\n\n\tif post1.tipAmount != 3_000_000 {\n\t\tt.Fatalf(\"expected total tipAmount: 3_000_000, got %d.\", post1.tipAmount)\n\t}\n}\n\nfunc testFlagPost(t *testing.T) {\n\tflagger := testutils.TestAddress(\"flagger\")\n\n\tfeedID9 := CreateFeed(\"teritori9\")\n\tfeed9 := mustGetFeed(feedID9)\n\n\tCreatePost(feed9.id, rootPostID, cat1, \"metadata1\")\n\tpid := CreatePost(feed9.id, rootPostID, cat1, \"metadata1\")\n\n\t// Flag post\n\tstd.TestSetOrigCaller(flagger)\n\tFlagPost(feed9.id, pid)\n\n\t// Another user flags\n\tanother := testutils.TestAddress(\"another\")\n\tstd.TestSetOrigCaller(another)\n\tFlagPost(feed9.id, pid)\n\n\tflaggedPostsStr := GetFlaggedPosts(feed9.id, 0, 10)\n\tparsed := ujson.ParseSlice(flaggedPostsStr)\n\tif flaggedPostsCount := len(parsed); flaggedPostsCount != 1 {\n\t\tt.Fatalf(\"expected flagged posts: 1, got %d.\", flaggedPostsCount)\n\t}\n}\n\nfunc testFilterUser(t *testing.T) {\n\tuser1 := testutils.TestAddress(\"user1\")\n\tuser2 := testutils.TestAddress(\"user2\")\n\n\t// User1 create 2 posts\n\tstd.TestSetOrigCaller(user1)\n\n\tfeedID4 := CreateFeed(\"teritori4\")\n\tfeed4 := mustGetFeed(feedID4)\n\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata2\": \"value\"}`)\n\n\t// User2 create 1 post\n\tstd.TestSetOrigCaller(user2)\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\n\tif count := countPostsByUser(feed4.id, user1.String()); count != 2 {\n\t\tt.Fatalf(\"expected total posts by user1: 2, got %d.\", count)\n\t}\n\n\tif count := countPostsByUser(feed4.id, user2.String()); count != 1 {\n\t\tt.Fatalf(\"expected total posts by user2: 1, got %d.\", count)\n\t}\n\n\tif count := countPostsByUser(feed4.id, \"\"); count != 3 {\n\t\tt.Fatalf(\"expected total posts: 3, got %d.\", count)\n\t}\n}\n\nfunc testHidePostForMe(t *testing.T) {\n\tuser := std.Address(\"user\")\n\tstd.TestSetOrigCaller(user)\n\n\tfeedID8 := CreateFeed(\"teritor8\")\n\tfeed8 := mustGetFeed(feedID8)\n\n\tpostIDToHide := CreatePost(feed8.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\tpostID := CreatePost(feed8.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// Hide a post for me\n\tHidePostForMe(feed8.id, postIDToHide)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 1 {\n\t\tt.Fatalf(\"expected posts count after hidding: 1, got %q.\", count)\n\t}\n\n\t// Query from another user should return full list\n\tanother := std.Address(\"another\")\n\tstd.TestSetOrigCaller(another)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count from another: 2, got %q.\", count)\n\t}\n\n\t// UnHide a post for me\n\tstd.TestSetOrigCaller(user)\n\tUnHidePostForMe(feed8.id, postIDToHide)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count after unhidding: 2, got %q.\", count)\n\t}\n}\n\nfunc testMigrateFeedData(t *testing.T) string {\n\tfeedID := feedsV7.CreateFeed(\"teritor11\")\n\n\t// Post to test\n\tpostID := feedsV7.CreatePost(feedID, feedsV7.PostID(0), 2, `{\"metadata\": \"value\"}`)\n\tfeedsV7.ReactPost(feedID, postID, \"🇬🇸\", true)\n\n\t// Add comment to post\n\tcommentID := feedsV7.CreatePost(feedID, postID, 2, `{\"comment1\": \"value\"}`)\n\tfeedsV7.ReactPost(feedID, commentID, \"🇬🇸\", true)\n\n\t// // Post with json metadata\n\tfeedsV7.CreatePost(feedID, feedsV7.PostID(0), 2, `{'a':1}`)\n\n\t// Expect: should convert feed data to JSON successfully without error\n\tdataJSON := feedsV7.ExportFeedData(feedID)\n\tif dataJSON == \"\" {\n\t\tt.Fatalf(\"expected feed data exported successfully\")\n\t}\n\n\t// Import data =====================================\n\tImportFeedData(FeedID(uint64(feedID)), dataJSON)\n\n\t// Test public func\n\t// MigrateFromPreviousFeed(feedID)\n}\n\nfunc Test(t *testing.T) {\n\ttestCreateFeed(t)\n\n\ttestCreatePost(t)\n\n\ttestGetPosts(t)\n\n\ttestReactPost(t)\n\n\ttestCreateAndDeleteComment(t)\n\n\ttestFilterByCategories(t)\n\n\ttestTipPost(t)\n\n\ttestFilterUser(t)\n\n\ttestFlagPost(t)\n\n\ttestHidePostForMe(t)\n\n\ttestMigrateFeedData(t)\n}\n" + "Body": "package social_feeds\n\nimport (\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t// Fake previous version for testing\n\tfeedsV7 \"gno.land/r/demo/teritori/social_feeds\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\trootPostID = PostID(0)\n\tpostID1 = PostID(1)\n\tfeedID1 = FeedID(1)\n\tcat1 = uint64(1)\n\tcat2 = uint64(2)\n\tuser = testutils.TestAddress(\"user\")\n\tfilter_all = []uint64{}\n)\n\nfunc getFeed1() *Feed {\n\treturn mustGetFeed(feedID1)\n}\n\nfunc getPost1() *Post {\n\tfeed1 := getFeed1()\n\tpost1 := feed1.MustGetPost(postID1)\n\treturn post1\n}\n\nfunc testCreateFeed(t *testing.T) {\n\tfeedID := CreateFeed(\"teritori1\")\n\tfeed := mustGetFeed(feedID)\n\n\tif feedID != 1 {\n\t\tt.Fatalf(\"expected feedID: 1, got %q.\", feedID)\n\t}\n\n\tif feed.name != \"teritori1\" {\n\t\tt.Fatalf(\"expected feedName: teritori1, got %q.\", feed.name)\n\t}\n}\n\nfunc testCreatePost(t *testing.T) {\n\tmetadata := `{\"gifs\": [], \"files\": [], \"title\": \"\", \"message\": \"testouille\", \"hashtags\": [], \"mentions\": [], \"createdAt\": \"2023-03-29T12:19:04.858Z\", \"updatedAt\": \"2023-03-29T12:19:04.858Z\"}`\n\tpostID := CreatePost(feedID1, rootPostID, cat1, metadata)\n\tfeed := mustGetFeed(feedID1)\n\tpost := feed.MustGetPost(postID)\n\n\tif postID != 1 {\n\t\tt.Fatalf(\"expected postID: 1, got %q.\", postID)\n\t}\n\n\tif post.category != cat1 {\n\t\tt.Fatalf(\"expected categoryID: %q, got %q.\", cat1, post.category)\n\t}\n}\n\nfunc toPostIDsStr(posts []*Post) string {\n\tvar postIDs []string\n\tfor _, post := range posts {\n\t\tpostIDs = append(postIDs, post.id.String())\n\t}\n\n\tpostIDsStr := strings.Join(postIDs, \",\")\n\treturn postIDsStr\n}\n\nfunc testGetPosts(t *testing.T) {\n\tuser := std.Address(\"user\")\n\tstd.TestSetOriginCaller(user)\n\n\tfeedID := CreateFeed(\"teritori10\")\n\tfeed := mustGetFeed(feedID)\n\n\tCreatePost(feedID, rootPostID, cat1, \"post1\")\n\tCreatePost(feedID, rootPostID, cat1, \"post2\")\n\tCreatePost(feedID, rootPostID, cat1, \"post3\")\n\tCreatePost(feedID, rootPostID, cat1, \"post4\")\n\tCreatePost(feedID, rootPostID, cat1, \"post5\")\n\tpostIDToFlagged := CreatePost(feedID, rootPostID, cat1, \"post6\")\n\tpostIDToHide := CreatePost(feedID, rootPostID, cat1, \"post7\")\n\tCreatePost(feedID, rootPostID, cat1, \"post8\")\n\n\tvar posts []*Post\n\tvar postIDsStr string\n\n\t// Query last 3 posts\n\tposts = getPosts(feed, 0, \"\", \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,7,6\" {\n\t\tt.Fatalf(\"expected posts order: 8,7,6. Got: %s\", postIDsStr)\n\t}\n\n\t// Query page 2\n\tposts = getPosts(feed, 0, \"\", \"\", []uint64{}, 3, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\tif postIDsStr != \"5,4,3\" {\n\t\tt.Fatalf(\"expected posts order: 5,4,3. Got: %s\", postIDsStr)\n\t}\n\n\t// Exclude hidden post\n\tHidePostForMe(feed.id, postIDToHide)\n\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,6,5\" {\n\t\tt.Fatalf(\"expected posts order: 8,6,5. Got: %s\", postIDsStr)\n\t}\n\n\t// Exclude flagged post\n\tFlagPost(feed.id, postIDToFlagged)\n\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,5,4\" {\n\t\tt.Fatalf(\"expected posts order: 8,5,4. Got: %s\", postIDsStr)\n\t}\n\n\t// Pagination with hidden/flagged posts\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 3, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"3,2,1\" {\n\t\tt.Fatalf(\"expected posts order: 3,2,1. Got: %s\", postIDsStr)\n\t}\n\n\t// Query out of range\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 6, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"\" {\n\t\tt.Fatalf(\"expected posts order: ''. Got: %s\", postIDsStr)\n\t}\n}\n\nfunc testReactPost(t *testing.T) {\n\tfeed := getFeed1()\n\tpost := getPost1()\n\n\ticon := \"🥰\"\n\tReactPost(feed.id, post.id, icon, true)\n\n\t// Set reaction\n\treactionCount_, ok := post.reactions.Get(\"🥰\")\n\tif !ok {\n\t\tt.Fatalf(\"expected 🥰 exists\")\n\t}\n\n\treactionCount := reactionCount_.(int)\n\tif reactionCount != 1 {\n\t\tt.Fatalf(\"expected reactionCount: 1, got %q.\", reactionCount)\n\t}\n\n\t// Unset reaction\n\tReactPost(feed.id, post.id, icon, false)\n\t_, exist := post.reactions.Get(\"🥰\")\n\tif exist {\n\t\tt.Fatalf(\"expected 🥰 not exist\")\n\t}\n}\n\nfunc testCreateAndDeleteComment(t *testing.T) {\n\tfeed1 := getFeed1()\n\tpost1 := getPost1()\n\n\tmetadata := `empty_meta_data`\n\n\tcommentID1 := CreatePost(feed1.id, post1.id, cat1, metadata)\n\tcommentID2 := CreatePost(feed1.id, post1.id, cat1, metadata)\n\tcomment2 := feed1.MustGetPost(commentID2)\n\n\tif comment2.id != 3 { // 1 post + 2 comments = 3\n\t\tt.Fatalf(\"expected comment postID: 3, got %q.\", comment2.id)\n\t}\n\n\tif comment2.parentID != post1.id {\n\t\tt.Fatalf(\"expected comment parentID: %q, got %q.\", post1.id, comment2.parentID)\n\t}\n\n\t// Check comment count on parent\n\tif post1.commentsCount != 2 {\n\t\tt.Fatalf(\"expected comments count: 2, got %d.\", post1.commentsCount)\n\t}\n\n\t// Get comments\n\tcomments := GetComments(feed1.id, post1.id, 0, 10)\n\tcommentsParsed := ujson.ParseSlice(comments)\n\n\tif len(commentsParsed) != 2 {\n\t\tt.Fatalf(\"expected encoded comments: 2, got %q.\", commentsParsed)\n\t}\n\n\t// Delete 1 comment\n\tDeletePost(feed1.id, comment2.id)\n\tcomments = GetComments(feed1.id, post1.id, 0, 10)\n\tcommentsParsed = ujson.ParseSlice(comments)\n\n\tif len(commentsParsed) != 1 {\n\t\tt.Fatalf(\"expected encoded comments: 1, got %q.\", commentsParsed)\n\t}\n\n\t// Check comment count on parent\n\tif post1.commentsCount != 1 {\n\t\tt.Fatalf(\"expected comments count: 1, got %d.\", post1.commentsCount)\n\t}\n}\n\nfunc countPosts(feedID FeedID, categories []uint64, limit uint8) int {\n\toffset := uint64(0)\n\n\tpostsStr := GetPosts(feedID, 0, \"\", categories, offset, limit)\n\tif postsStr == \"[]\" {\n\t\treturn 0\n\t}\n\n\tparsedPosts := ujson.ParseSlice(postsStr)\n\tpostsCount := len(parsedPosts)\n\treturn postsCount\n}\n\nfunc countPostsByUser(feedID FeedID, user string) int {\n\toffset := uint64(0)\n\tlimit := uint8(10)\n\n\tpostsStr := GetPosts(feedID, 0, user, []uint64{}, offset, limit)\n\tif postsStr == \"[]\" {\n\t\treturn 0\n\t}\n\n\tparsedPosts := ujson.ParseSlice(postsStr)\n\tpostsCount := len(parsedPosts)\n\treturn postsCount\n}\n\nfunc testFilterByCategories(t *testing.T) {\n\t// // Re-add reaction to test post list\n\t// ReactPost(1, postID, \"🥰\", true)\n\t// ReactPost(1, postID, \"😇\", true)\n\n\tfilter_cat1 := []uint64{1}\n\tfilter_cat1_2 := []uint64{1, 2}\n\tfilter_cat9 := []uint64{9}\n\tfilter_cat1_2_9 := []uint64{1, 2, 9}\n\n\tfeedID2 := CreateFeed(\"teritori2\")\n\tfeed2 := mustGetFeed(feedID2)\n\n\t// Create 2 posts on root with cat1\n\tpostID1 := CreatePost(feed2.id, rootPostID, cat1, \"metadata\")\n\tpostID2 := CreatePost(feed2.id, rootPostID, cat1, \"metadata\")\n\n\t// Create 1 posts on root with cat2\n\tpostID3 := CreatePost(feed2.id, rootPostID, cat2, \"metadata\")\n\n\t// Create comments on post 1\n\tcommentPostID1 := CreatePost(feed2.id, postID1, cat1, \"metadata\")\n\n\t// cat1: Should return max = limit\n\tif count := countPosts(feed2.id, filter_cat1, 1); count != 1 {\n\t\tt.Fatalf(\"expected posts count: 1, got %q.\", count)\n\t}\n\n\t// cat1: Should return max = total\n\tif count := countPosts(feed2.id, filter_cat1, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// cat 1 + 2: Should return max = limit\n\tif count := countPosts(feed2.id, filter_cat1_2, 2); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// cat 1 + 2: Should return max = total on both\n\tif count := countPosts(feed2.id, filter_cat1_2, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// cat 1, 2, 9: Should return total of 1, 2\n\tif count := countPosts(feed2.id, filter_cat1_2_9, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// cat 9: Should return 0\n\tif count := countPosts(feed2.id, filter_cat9, 10); count != 0 {\n\t\tt.Fatalf(\"expected posts count: 0, got %q.\", count)\n\t}\n\n\t// cat all: should return all\n\tif count := countPosts(feed2.id, filter_all, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// add comments should not impact the results\n\tCreatePost(feed2.id, postID1, cat1, \"metadata\")\n\tCreatePost(feed2.id, postID2, cat1, \"metadata\")\n\n\tif count := countPosts(feed2.id, filter_all, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// delete a post should affect the result\n\tDeletePost(feed2.id, postID1)\n\n\tif count := countPosts(feed2.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n}\n\nfunc testTipPost(t *testing.T) {\n\tcreator := testutils.TestAddress(\"creator\")\n\tstd.TestIssueCoins(creator, std.Coins{{\"ugnot\", 100_000_000}})\n\n\t// NOTE: Dont know why the address should be this to be able to call banker (= std.GetCallerAt(1))\n\ttipper := testutils.TestAddress(\"tipper\")\n\tstd.TestIssueCoins(tipper, std.Coins{{\"ugnot\", 50_000_000}})\n\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\n\t// Check Original coins of creator/tipper\n\tif coins := banker.GetCoins(creator); coins[0].Amount != 100_000_000 {\n\t\tt.Fatalf(\"expected creator coin count: 100_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\tif coins := banker.GetCoins(tipper); coins[0].Amount != 50_000_000 {\n\t\tt.Fatalf(\"expected tipper coin count: 50_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\t// Creator creates feed, post\n\tstd.TestSetOriginCaller(creator)\n\n\tfeedID3 := CreateFeed(\"teritori3\")\n\tfeed3 := mustGetFeed(feedID3)\n\n\tpostID1 := CreatePost(feed3.id, rootPostID, cat1, \"metadata\")\n\tpost1 := feed3.MustGetPost(postID1)\n\n\t// Tiper tips the ppst\n\tstd.TestSetOriginCaller(tipper)\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 1_000_000}}, nil)\n\tTipPost(feed3.id, post1.id)\n\n\t// Coin must be increased for creator\n\tif coins := banker.GetCoins(creator); coins[0].Amount != 101_000_000 {\n\t\tt.Fatalf(\"expected creator coin after beging tipped: 101_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\t// Total tip amount should increased\n\tif post1.tipAmount != 1_000_000 {\n\t\tt.Fatalf(\"expected total tipAmount: 1_000_000, got %d.\", post1.tipAmount)\n\t}\n\n\t// Add more tip should update this total\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 2_000_000}}, nil)\n\tTipPost(feed3.id, post1.id)\n\n\tif post1.tipAmount != 3_000_000 {\n\t\tt.Fatalf(\"expected total tipAmount: 3_000_000, got %d.\", post1.tipAmount)\n\t}\n}\n\nfunc testFlagPost(t *testing.T) {\n\tflagger := testutils.TestAddress(\"flagger\")\n\n\tfeedID9 := CreateFeed(\"teritori9\")\n\tfeed9 := mustGetFeed(feedID9)\n\n\tCreatePost(feed9.id, rootPostID, cat1, \"metadata1\")\n\tpid := CreatePost(feed9.id, rootPostID, cat1, \"metadata1\")\n\n\t// Flag post\n\tstd.TestSetOriginCaller(flagger)\n\tFlagPost(feed9.id, pid)\n\n\t// Another user flags\n\tanother := testutils.TestAddress(\"another\")\n\tstd.TestSetOriginCaller(another)\n\tFlagPost(feed9.id, pid)\n\n\tflaggedPostsStr := GetFlaggedPosts(feed9.id, 0, 10)\n\tparsed := ujson.ParseSlice(flaggedPostsStr)\n\tif flaggedPostsCount := len(parsed); flaggedPostsCount != 1 {\n\t\tt.Fatalf(\"expected flagged posts: 1, got %d.\", flaggedPostsCount)\n\t}\n}\n\nfunc testFilterUser(t *testing.T) {\n\tuser1 := testutils.TestAddress(\"user1\")\n\tuser2 := testutils.TestAddress(\"user2\")\n\n\t// User1 create 2 posts\n\tstd.TestSetOriginCaller(user1)\n\n\tfeedID4 := CreateFeed(\"teritori4\")\n\tfeed4 := mustGetFeed(feedID4)\n\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata2\": \"value\"}`)\n\n\t// User2 create 1 post\n\tstd.TestSetOriginCaller(user2)\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\n\tif count := countPostsByUser(feed4.id, user1.String()); count != 2 {\n\t\tt.Fatalf(\"expected total posts by user1: 2, got %d.\", count)\n\t}\n\n\tif count := countPostsByUser(feed4.id, user2.String()); count != 1 {\n\t\tt.Fatalf(\"expected total posts by user2: 1, got %d.\", count)\n\t}\n\n\tif count := countPostsByUser(feed4.id, \"\"); count != 3 {\n\t\tt.Fatalf(\"expected total posts: 3, got %d.\", count)\n\t}\n}\n\nfunc testHidePostForMe(t *testing.T) {\n\tuser := std.Address(\"user\")\n\tstd.TestSetOriginCaller(user)\n\n\tfeedID8 := CreateFeed(\"teritor8\")\n\tfeed8 := mustGetFeed(feedID8)\n\n\tpostIDToHide := CreatePost(feed8.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\tpostID := CreatePost(feed8.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// Hide a post for me\n\tHidePostForMe(feed8.id, postIDToHide)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 1 {\n\t\tt.Fatalf(\"expected posts count after hidding: 1, got %q.\", count)\n\t}\n\n\t// Query from another user should return full list\n\tanother := std.Address(\"another\")\n\tstd.TestSetOriginCaller(another)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count from another: 2, got %q.\", count)\n\t}\n\n\t// UnHide a post for me\n\tstd.TestSetOriginCaller(user)\n\tUnHidePostForMe(feed8.id, postIDToHide)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count after unhidding: 2, got %q.\", count)\n\t}\n}\n\nfunc testMigrateFeedData(t *testing.T) string {\n\tfeedID := feedsV7.CreateFeed(\"teritor11\")\n\n\t// Post to test\n\tpostID := feedsV7.CreatePost(feedID, feedsV7.PostID(0), 2, `{\"metadata\": \"value\"}`)\n\tfeedsV7.ReactPost(feedID, postID, \"🇬🇸\", true)\n\n\t// Add comment to post\n\tcommentID := feedsV7.CreatePost(feedID, postID, 2, `{\"comment1\": \"value\"}`)\n\tfeedsV7.ReactPost(feedID, commentID, \"🇬🇸\", true)\n\n\t// // Post with json metadata\n\tfeedsV7.CreatePost(feedID, feedsV7.PostID(0), 2, `{'a':1}`)\n\n\t// Expect: should convert feed data to JSON successfully without error\n\tdataJSON := feedsV7.ExportFeedData(feedID)\n\tif dataJSON == \"\" {\n\t\tt.Fatalf(\"expected feed data exported successfully\")\n\t}\n\n\t// Import data =====================================\n\tImportFeedData(FeedID(uint64(feedID)), dataJSON)\n\n\t// Test public func\n\t// MigrateFromPreviousFeed(feedID)\n}\n\nfunc Test(t *testing.T) {\n\ttestCreateFeed(t)\n\n\ttestCreatePost(t)\n\n\ttestGetPosts(t)\n\n\ttestReactPost(t)\n\n\ttestCreateAndDeleteComment(t)\n\n\ttestFilterByCategories(t)\n\n\ttestTipPost(t)\n\n\ttestFilterUser(t)\n\n\ttestFlagPost(t)\n\n\ttestHidePostForMe(t)\n\n\ttestMigrateFeedData(t)\n}\n" }, { "Name": "flags.gno", @@ -66,7 +66,7 @@ import ( }, { "Name": "post.gno", - "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n)\n\ntype PostID uint64\n\nfunc (pid PostID) String() string {\n\treturn strconv.Itoa(int(pid))\n}\n\nfunc (pid *PostID) FromJSON(ast *ujson.JSONASTNode) {\n\tval, err := strconv.Atoi(ast.Value)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t*pid = PostID(val)\n}\n\nfunc (pid PostID) ToJSON() string {\n\treturn strconv.Itoa(int(pid))\n}\n\ntype Reaction struct {\n\ticon string\n\tcount uint64\n}\n\nvar Categories []string = []string{\n\t\"Reaction\",\n\t\"Comment\",\n\t\"Normal\",\n\t\"Article\",\n\t\"Picture\",\n\t\"Audio\",\n\t\"Video\",\n}\n\ntype Post struct {\n\tid PostID\n\tparentID PostID\n\tfeedID FeedID\n\tcategory uint64\n\tmetadata string\n\treactions avl.Tree // icon -> count\n\tcomments avl.Tree // Post.id -> *Post\n\tcreator std.Address\n\ttipAmount uint64\n\tdeleted bool\n\tcommentsCount uint64\n\n\tcreatedAt int64\n\tupdatedAt int64\n\tdeletedAt int64\n}\n\nfunc newPost(feed *Feed, id PostID, creator std.Address, parentID PostID, category uint64, metadata string) *Post {\n\treturn &Post{\n\t\tid: id,\n\t\tparentID: parentID,\n\t\tfeedID: feed.id,\n\t\tcategory: category,\n\t\tmetadata: metadata,\n\t\treactions: avl.Tree{},\n\t\tcreator: creator,\n\t\tcreatedAt: time.Now().Unix(),\n\t}\n}\n\nfunc (post *Post) String() string {\n\treturn post.ToJSON()\n}\n\nfunc (post *Post) Update(category uint64, metadata string) {\n\tpost.category = category\n\tpost.metadata = metadata\n\tpost.updatedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Delete() {\n\tpost.deleted = true\n\tpost.deletedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Tip(from std.Address, to std.Address) {\n\treceivedCoins := std.OrigSend()\n\tamount := receivedCoins[0].Amount\n\n\tbanker := std.GetBanker(std.BankerTypeOrigSend)\n\t// banker := std.GetBanker(std.BankerTypeRealmSend)\n\tcoinsToSend := std.Coins{std.Coin{Denom: \"ugnot\", Amount: amount}}\n\tpkgaddr := std.GetOrigPkgAddr()\n\n\tbanker.SendCoins(pkgaddr, to, coinsToSend)\n\n\t// Update tip amount\n\tpost.tipAmount += uint64(amount)\n}\n\n// Always remove reaction if count = 0\nfunc (post *Post) React(icon string, up bool) {\n\tcount_, ok := post.reactions.Get(icon)\n\tcount := 0\n\n\tif ok {\n\t\tcount = count_.(int)\n\t}\n\n\tif up {\n\t\tcount++\n\t} else {\n\t\tcount--\n\t}\n\n\tif count <= 0 {\n\t\tpost.reactions.Remove(icon)\n\t} else {\n\t\tpost.reactions.Set(icon, count)\n\t}\n}\n\nfunc (post *Post) Render() string {\n\treturn post.metadata\n}\n\nfunc (post *Post) FromJSON(jsonData string) {\n\tast := ujson.TokenizeAndParse(jsonData)\n\tast.ParseObject([]*ujson.ParseKV{\n\t\t{Key: \"id\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.id = PostID(pid)\n\t\t}},\n\t\t{Key: \"parentID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.parentID = PostID(pid)\n\t\t}},\n\t\t{Key: \"feedID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tfid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.feedID = FeedID(fid)\n\t\t}},\n\t\t{Key: \"category\", Value: &post.category},\n\t\t{Key: \"metadata\", Value: &post.metadata},\n\t\t{Key: \"reactions\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\treactions := avl.NewTree()\n\t\t\tfor _, child := range node.ObjectChildren {\n\t\t\t\treactionCount := child.Value\n\t\t\t\treactions.Set(child.Key, reactionCount)\n\t\t\t}\n\t\t\tpost.reactions = *reactions\n\t\t}},\n\t\t{Key: \"commentsCount\", Value: &post.commentsCount},\n\t\t{Key: \"creator\", Value: &post.creator},\n\t\t{Key: \"tipAmount\", Value: &post.tipAmount},\n\t\t{Key: \"deleted\", Value: &post.deleted},\n\t\t{Key: \"createdAt\", Value: &post.createdAt},\n\t\t{Key: \"updatedAt\", Value: &post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: &post.deletedAt},\n\t})\n}\n\nfunc (post *Post) ToJSON() string {\n\treactionsKV := []ujson.FormatKV{}\n\tpost.reactions.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tcount := value.(int)\n\t\tdata := ujson.FormatKV{Key: key, Value: count}\n\t\treactionsKV = append(reactionsKV, data)\n\t\treturn false\n\t})\n\treactions := ujson.FormatObject(reactionsKV)\n\n\tpostJSON := ujson.FormatObject([]ujson.FormatKV{\n\t\t{Key: \"id\", Value: uint64(post.id)},\n\t\t{Key: \"parentID\", Value: uint64(post.parentID)},\n\t\t{Key: \"feedID\", Value: uint64(post.feedID)},\n\t\t{Key: \"category\", Value: post.category},\n\t\t{Key: \"metadata\", Value: post.metadata},\n\t\t{Key: \"reactions\", Value: reactions, Raw: true},\n\t\t{Key: \"creator\", Value: post.creator},\n\t\t{Key: \"tipAmount\", Value: post.tipAmount},\n\t\t{Key: \"deleted\", Value: post.deleted},\n\t\t{Key: \"commentsCount\", Value: post.commentsCount},\n\t\t{Key: \"createdAt\", Value: post.createdAt},\n\t\t{Key: \"updatedAt\", Value: post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: post.deletedAt},\n\t})\n\treturn postJSON\n}\n" + "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n)\n\ntype PostID uint64\n\nfunc (pid PostID) String() string {\n\treturn strconv.Itoa(int(pid))\n}\n\nfunc (pid *PostID) FromJSON(ast *ujson.JSONASTNode) {\n\tval, err := strconv.Atoi(ast.Value)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t*pid = PostID(val)\n}\n\nfunc (pid PostID) ToJSON() string {\n\treturn strconv.Itoa(int(pid))\n}\n\ntype Reaction struct {\n\ticon string\n\tcount uint64\n}\n\nvar Categories []string = []string{\n\t\"Reaction\",\n\t\"Comment\",\n\t\"Normal\",\n\t\"Article\",\n\t\"Picture\",\n\t\"Audio\",\n\t\"Video\",\n}\n\ntype Post struct {\n\tid PostID\n\tparentID PostID\n\tfeedID FeedID\n\tcategory uint64\n\tmetadata string\n\treactions avl.Tree // icon -> count\n\tcomments avl.Tree // Post.id -> *Post\n\tcreator std.Address\n\ttipAmount uint64\n\tdeleted bool\n\tcommentsCount uint64\n\n\tcreatedAt int64\n\tupdatedAt int64\n\tdeletedAt int64\n}\n\nfunc newPost(feed *Feed, id PostID, creator std.Address, parentID PostID, category uint64, metadata string) *Post {\n\treturn &Post{\n\t\tid: id,\n\t\tparentID: parentID,\n\t\tfeedID: feed.id,\n\t\tcategory: category,\n\t\tmetadata: metadata,\n\t\treactions: avl.Tree{},\n\t\tcreator: creator,\n\t\tcreatedAt: time.Now().Unix(),\n\t}\n}\n\nfunc (post *Post) String() string {\n\treturn post.ToJSON()\n}\n\nfunc (post *Post) Update(category uint64, metadata string) {\n\tpost.category = category\n\tpost.metadata = metadata\n\tpost.updatedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Delete() {\n\tpost.deleted = true\n\tpost.deletedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Tip(from std.Address, to std.Address) {\n\treceivedCoins := std.OriginSend()\n\tamount := receivedCoins[0].Amount\n\n\tbanker := std.GetBanker(std.BankerTypeOriginSend)\n\t// banker := std.GetBanker(std.BankerTypeRealmSend)\n\tcoinsToSend := std.Coins{std.Coin{Denom: \"ugnot\", Amount: amount}}\n\tpkgaddr := std.GetOrigPkgAddr()\n\n\tbanker.SendCoins(pkgaddr, to, coinsToSend)\n\n\t// Update tip amount\n\tpost.tipAmount += uint64(amount)\n}\n\n// Always remove reaction if count = 0\nfunc (post *Post) React(icon string, up bool) {\n\tcount_, ok := post.reactions.Get(icon)\n\tcount := 0\n\n\tif ok {\n\t\tcount = count_.(int)\n\t}\n\n\tif up {\n\t\tcount++\n\t} else {\n\t\tcount--\n\t}\n\n\tif count <= 0 {\n\t\tpost.reactions.Remove(icon)\n\t} else {\n\t\tpost.reactions.Set(icon, count)\n\t}\n}\n\nfunc (post *Post) Render() string {\n\treturn post.metadata\n}\n\nfunc (post *Post) FromJSON(jsonData string) {\n\tast := ujson.TokenizeAndParse(jsonData)\n\tast.ParseObject([]*ujson.ParseKV{\n\t\t{Key: \"id\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.id = PostID(pid)\n\t\t}},\n\t\t{Key: \"parentID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.parentID = PostID(pid)\n\t\t}},\n\t\t{Key: \"feedID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tfid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.feedID = FeedID(fid)\n\t\t}},\n\t\t{Key: \"category\", Value: &post.category},\n\t\t{Key: \"metadata\", Value: &post.metadata},\n\t\t{Key: \"reactions\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\treactions := avl.NewTree()\n\t\t\tfor _, child := range node.ObjectChildren {\n\t\t\t\treactionCount := child.Value\n\t\t\t\treactions.Set(child.Key, reactionCount)\n\t\t\t}\n\t\t\tpost.reactions = *reactions\n\t\t}},\n\t\t{Key: \"commentsCount\", Value: &post.commentsCount},\n\t\t{Key: \"creator\", Value: &post.creator},\n\t\t{Key: \"tipAmount\", Value: &post.tipAmount},\n\t\t{Key: \"deleted\", Value: &post.deleted},\n\t\t{Key: \"createdAt\", Value: &post.createdAt},\n\t\t{Key: \"updatedAt\", Value: &post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: &post.deletedAt},\n\t})\n}\n\nfunc (post *Post) ToJSON() string {\n\treactionsKV := []ujson.FormatKV{}\n\tpost.reactions.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tcount := value.(int)\n\t\tdata := ujson.FormatKV{Key: key, Value: count}\n\t\treactionsKV = append(reactionsKV, data)\n\t\treturn false\n\t})\n\treactions := ujson.FormatObject(reactionsKV)\n\n\tpostJSON := ujson.FormatObject([]ujson.FormatKV{\n\t\t{Key: \"id\", Value: uint64(post.id)},\n\t\t{Key: \"parentID\", Value: uint64(post.parentID)},\n\t\t{Key: \"feedID\", Value: uint64(post.feedID)},\n\t\t{Key: \"category\", Value: post.category},\n\t\t{Key: \"metadata\", Value: post.metadata},\n\t\t{Key: \"reactions\", Value: reactions, Raw: true},\n\t\t{Key: \"creator\", Value: post.creator},\n\t\t{Key: \"tipAmount\", Value: post.tipAmount},\n\t\t{Key: \"deleted\", Value: post.deleted},\n\t\t{Key: \"commentsCount\", Value: post.commentsCount},\n\t\t{Key: \"createdAt\", Value: post.createdAt},\n\t\t{Key: \"updatedAt\", Value: post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: post.deletedAt},\n\t})\n\treturn postJSON\n}\n" }, { "Name": "public.gno", diff --git a/gno.land/cmd/gnoland/testdata/issue_2283_cacheTypes.txtar b/gno.land/cmd/gnoland/testdata/issue_2283_cacheTypes.txtar index 412a1734f2b..cb940cc7af9 100644 --- a/gno.land/cmd/gnoland/testdata/issue_2283_cacheTypes.txtar +++ b/gno.land/cmd/gnoland/testdata/issue_2283_cacheTypes.txtar @@ -40,7 +40,7 @@ stdout OK! }, { "Name": "feeds_test.gno", - "Body": "package social_feeds\n\nimport (\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t// Fake previous version for testing\n\tfeedsV7 \"gno.land/r/demo/teritori/social_feeds\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\trootPostID = PostID(0)\n\tpostID1 = PostID(1)\n\tfeedID1 = FeedID(1)\n\tcat1 = uint64(1)\n\tcat2 = uint64(2)\n\tuser = testutils.TestAddress(\"user\")\n\tfilter_all = []uint64{}\n)\n\nfunc getFeed1() *Feed {\n\treturn mustGetFeed(feedID1)\n}\n\nfunc getPost1() *Post {\n\tfeed1 := getFeed1()\n\tpost1 := feed1.MustGetPost(postID1)\n\treturn post1\n}\n\nfunc testCreateFeed(t *testing.T) {\n\tfeedID := CreateFeed(\"teritori1\")\n\tfeed := mustGetFeed(feedID)\n\n\tif feedID != 1 {\n\t\tt.Fatalf(\"expected feedID: 1, got %q.\", feedID)\n\t}\n\n\tif feed.name != \"teritori1\" {\n\t\tt.Fatalf(\"expected feedName: teritori1, got %q.\", feed.name)\n\t}\n}\n\nfunc testCreatePost(t *testing.T) {\n\tmetadata := `{\"gifs\": [], \"files\": [], \"title\": \"\", \"message\": \"testouille\", \"hashtags\": [], \"mentions\": [], \"createdAt\": \"2023-03-29T12:19:04.858Z\", \"updatedAt\": \"2023-03-29T12:19:04.858Z\"}`\n\tpostID := CreatePost(feedID1, rootPostID, cat1, metadata)\n\tfeed := mustGetFeed(feedID1)\n\tpost := feed.MustGetPost(postID)\n\n\tif postID != 1 {\n\t\tt.Fatalf(\"expected postID: 1, got %q.\", postID)\n\t}\n\n\tif post.category != cat1 {\n\t\tt.Fatalf(\"expected categoryID: %q, got %q.\", cat1, post.category)\n\t}\n}\n\nfunc toPostIDsStr(posts []*Post) string {\n\tvar postIDs []string\n\tfor _, post := range posts {\n\t\tpostIDs = append(postIDs, post.id.String())\n\t}\n\n\tpostIDsStr := strings.Join(postIDs, \",\")\n\treturn postIDsStr\n}\n\nfunc testGetPosts(t *testing.T) {\n\tuser := std.Address(\"user\")\n\tstd.TestSetOrigCaller(user)\n\n\tfeedID := CreateFeed(\"teritori10\")\n\tfeed := mustGetFeed(feedID)\n\n\tCreatePost(feedID, rootPostID, cat1, \"post1\")\n\tCreatePost(feedID, rootPostID, cat1, \"post2\")\n\tCreatePost(feedID, rootPostID, cat1, \"post3\")\n\tCreatePost(feedID, rootPostID, cat1, \"post4\")\n\tCreatePost(feedID, rootPostID, cat1, \"post5\")\n\tpostIDToFlagged := CreatePost(feedID, rootPostID, cat1, \"post6\")\n\tpostIDToHide := CreatePost(feedID, rootPostID, cat1, \"post7\")\n\tCreatePost(feedID, rootPostID, cat1, \"post8\")\n\n\tvar posts []*Post\n\tvar postIDsStr string\n\n\t// Query last 3 posts\n\tposts = getPosts(feed, 0, \"\", \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,7,6\" {\n\t\tt.Fatalf(\"expected posts order: 8,7,6. Got: %s\", postIDsStr)\n\t}\n\n\t// Query page 2\n\tposts = getPosts(feed, 0, \"\", \"\", []uint64{}, 3, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\tif postIDsStr != \"5,4,3\" {\n\t\tt.Fatalf(\"expected posts order: 5,4,3. Got: %s\", postIDsStr)\n\t}\n\n\t// Exclude hidden post\n\tHidePostForMe(feed.id, postIDToHide)\n\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,6,5\" {\n\t\tt.Fatalf(\"expected posts order: 8,6,5. Got: %s\", postIDsStr)\n\t}\n\n\t// Exclude flagged post\n\tFlagPost(feed.id, postIDToFlagged)\n\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,5,4\" {\n\t\tt.Fatalf(\"expected posts order: 8,5,4. Got: %s\", postIDsStr)\n\t}\n\n\t// Pagination with hidden/flagged posts\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 3, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"3,2,1\" {\n\t\tt.Fatalf(\"expected posts order: 3,2,1. Got: %s\", postIDsStr)\n\t}\n\n\t// Query out of range\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 6, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"\" {\n\t\tt.Fatalf(\"expected posts order: ''. Got: %s\", postIDsStr)\n\t}\n}\n\nfunc testReactPost(t *testing.T) {\n\tfeed := getFeed1()\n\tpost := getPost1()\n\n\ticon := \"🥰\"\n\tReactPost(feed.id, post.id, icon, true)\n\n\t// Set reaction\n\treactionCount_, ok := post.reactions.Get(\"🥰\")\n\tif !ok {\n\t\tt.Fatalf(\"expected 🥰 exists\")\n\t}\n\n\treactionCount := reactionCount_.(int)\n\tif reactionCount != 1 {\n\t\tt.Fatalf(\"expected reactionCount: 1, got %q.\", reactionCount)\n\t}\n\n\t// Unset reaction\n\tReactPost(feed.id, post.id, icon, false)\n\t_, exist := post.reactions.Get(\"🥰\")\n\tif exist {\n\t\tt.Fatalf(\"expected 🥰 not exist\")\n\t}\n}\n\nfunc testCreateAndDeleteComment(t *testing.T) {\n\tfeed1 := getFeed1()\n\tpost1 := getPost1()\n\n\tmetadata := `empty_meta_data`\n\n\tcommentID1 := CreatePost(feed1.id, post1.id, cat1, metadata)\n\tcommentID2 := CreatePost(feed1.id, post1.id, cat1, metadata)\n\tcomment2 := feed1.MustGetPost(commentID2)\n\n\tif comment2.id != 3 { // 1 post + 2 comments = 3\n\t\tt.Fatalf(\"expected comment postID: 3, got %q.\", comment2.id)\n\t}\n\n\tif comment2.parentID != post1.id {\n\t\tt.Fatalf(\"expected comment parentID: %q, got %q.\", post1.id, comment2.parentID)\n\t}\n\n\t// Check comment count on parent\n\tif post1.commentsCount != 2 {\n\t\tt.Fatalf(\"expected comments count: 2, got %d.\", post1.commentsCount)\n\t}\n\n\t// Get comments\n\tcomments := GetComments(feed1.id, post1.id, 0, 10)\n\tcommentsParsed := ujson.ParseSlice(comments)\n\n\tif len(commentsParsed) != 2 {\n\t\tt.Fatalf(\"expected encoded comments: 2, got %q.\", commentsParsed)\n\t}\n\n\t// Delete 1 comment\n\tDeletePost(feed1.id, comment2.id)\n\tcomments = GetComments(feed1.id, post1.id, 0, 10)\n\tcommentsParsed = ujson.ParseSlice(comments)\n\n\tif len(commentsParsed) != 1 {\n\t\tt.Fatalf(\"expected encoded comments: 1, got %q.\", commentsParsed)\n\t}\n\n\t// Check comment count on parent\n\tif post1.commentsCount != 1 {\n\t\tt.Fatalf(\"expected comments count: 1, got %d.\", post1.commentsCount)\n\t}\n}\n\nfunc countPosts(feedID FeedID, categories []uint64, limit uint8) int {\n\toffset := uint64(0)\n\n\tpostsStr := GetPosts(feedID, 0, \"\", categories, offset, limit)\n\tif postsStr == \"[]\" {\n\t\treturn 0\n\t}\n\n\tparsedPosts := ujson.ParseSlice(postsStr)\n\tpostsCount := len(parsedPosts)\n\treturn postsCount\n}\n\nfunc countPostsByUser(feedID FeedID, user string) int {\n\toffset := uint64(0)\n\tlimit := uint8(10)\n\n\tpostsStr := GetPosts(feedID, 0, user, []uint64{}, offset, limit)\n\tif postsStr == \"[]\" {\n\t\treturn 0\n\t}\n\n\tparsedPosts := ujson.ParseSlice(postsStr)\n\tpostsCount := len(parsedPosts)\n\treturn postsCount\n}\n\nfunc testFilterByCategories(t *testing.T) {\n\t// // Re-add reaction to test post list\n\t// ReactPost(1, postID, \"🥰\", true)\n\t// ReactPost(1, postID, \"😇\", true)\n\n\tfilter_cat1 := []uint64{1}\n\tfilter_cat1_2 := []uint64{1, 2}\n\tfilter_cat9 := []uint64{9}\n\tfilter_cat1_2_9 := []uint64{1, 2, 9}\n\n\tfeedID2 := CreateFeed(\"teritori2\")\n\tfeed2 := mustGetFeed(feedID2)\n\n\t// Create 2 posts on root with cat1\n\tpostID1 := CreatePost(feed2.id, rootPostID, cat1, \"metadata\")\n\tpostID2 := CreatePost(feed2.id, rootPostID, cat1, \"metadata\")\n\n\t// Create 1 posts on root with cat2\n\tpostID3 := CreatePost(feed2.id, rootPostID, cat2, \"metadata\")\n\n\t// Create comments on post 1\n\tcommentPostID1 := CreatePost(feed2.id, postID1, cat1, \"metadata\")\n\n\t// cat1: Should return max = limit\n\tif count := countPosts(feed2.id, filter_cat1, 1); count != 1 {\n\t\tt.Fatalf(\"expected posts count: 1, got %q.\", count)\n\t}\n\n\t// cat1: Should return max = total\n\tif count := countPosts(feed2.id, filter_cat1, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// cat 1 + 2: Should return max = limit\n\tif count := countPosts(feed2.id, filter_cat1_2, 2); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// cat 1 + 2: Should return max = total on both\n\tif count := countPosts(feed2.id, filter_cat1_2, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// cat 1, 2, 9: Should return total of 1, 2\n\tif count := countPosts(feed2.id, filter_cat1_2_9, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// cat 9: Should return 0\n\tif count := countPosts(feed2.id, filter_cat9, 10); count != 0 {\n\t\tt.Fatalf(\"expected posts count: 0, got %q.\", count)\n\t}\n\n\t// cat all: should return all\n\tif count := countPosts(feed2.id, filter_all, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// add comments should not impact the results\n\tCreatePost(feed2.id, postID1, cat1, \"metadata\")\n\tCreatePost(feed2.id, postID2, cat1, \"metadata\")\n\n\tif count := countPosts(feed2.id, filter_all, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// delete a post should affect the result\n\tDeletePost(feed2.id, postID1)\n\n\tif count := countPosts(feed2.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n}\n\nfunc testTipPost(t *testing.T) {\n\tcreator := testutils.TestAddress(\"creator\")\n\tstd.TestIssueCoins(creator, std.Coins{{\"ugnot\", 100_000_000}})\n\n\t// NOTE: Dont know why the address should be this to be able to call banker (= std.GetCallerAt(1))\n\ttipper := testutils.TestAddress(\"tipper\")\n\tstd.TestIssueCoins(tipper, std.Coins{{\"ugnot\", 50_000_000}})\n\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\n\t// Check Original coins of creator/tipper\n\tif coins := banker.GetCoins(creator); coins[0].Amount != 100_000_000 {\n\t\tt.Fatalf(\"expected creator coin count: 100_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\tif coins := banker.GetCoins(tipper); coins[0].Amount != 50_000_000 {\n\t\tt.Fatalf(\"expected tipper coin count: 50_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\t// Creator creates feed, post\n\tstd.TestSetOrigCaller(creator)\n\n\tfeedID3 := CreateFeed(\"teritori3\")\n\tfeed3 := mustGetFeed(feedID3)\n\n\tpostID1 := CreatePost(feed3.id, rootPostID, cat1, \"metadata\")\n\tpost1 := feed3.MustGetPost(postID1)\n\n\t// Tiper tips the ppst\n\tstd.TestSetOrigCaller(tipper)\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 1_000_000}}, nil)\n\tTipPost(feed3.id, post1.id)\n\n\t// Coin must be increased for creator\n\tif coins := banker.GetCoins(creator); coins[0].Amount != 101_000_000 {\n\t\tt.Fatalf(\"expected creator coin after beging tipped: 101_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\t// Total tip amount should increased\n\tif post1.tipAmount != 1_000_000 {\n\t\tt.Fatalf(\"expected total tipAmount: 1_000_000, got %d.\", post1.tipAmount)\n\t}\n\n\t// Add more tip should update this total\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 2_000_000}}, nil)\n\tTipPost(feed3.id, post1.id)\n\n\tif post1.tipAmount != 3_000_000 {\n\t\tt.Fatalf(\"expected total tipAmount: 3_000_000, got %d.\", post1.tipAmount)\n\t}\n}\n\nfunc testFlagPost(t *testing.T) {\n\tflagger := testutils.TestAddress(\"flagger\")\n\n\tfeedID9 := CreateFeed(\"teritori9\")\n\tfeed9 := mustGetFeed(feedID9)\n\n\tCreatePost(feed9.id, rootPostID, cat1, \"metadata1\")\n\tpid := CreatePost(feed9.id, rootPostID, cat1, \"metadata1\")\n\n\t// Flag post\n\tstd.TestSetOrigCaller(flagger)\n\tFlagPost(feed9.id, pid)\n\n\t// Another user flags\n\tanother := testutils.TestAddress(\"another\")\n\tstd.TestSetOrigCaller(another)\n\tFlagPost(feed9.id, pid)\n\n\tflaggedPostsStr := GetFlaggedPosts(feed9.id, 0, 10)\n\tparsed := ujson.ParseSlice(flaggedPostsStr)\n\tif flaggedPostsCount := len(parsed); flaggedPostsCount != 1 {\n\t\tt.Fatalf(\"expected flagged posts: 1, got %d.\", flaggedPostsCount)\n\t}\n}\n\nfunc testFilterUser(t *testing.T) {\n\tuser1 := testutils.TestAddress(\"user1\")\n\tuser2 := testutils.TestAddress(\"user2\")\n\n\t// User1 create 2 posts\n\tstd.TestSetOrigCaller(user1)\n\n\tfeedID4 := CreateFeed(\"teritori4\")\n\tfeed4 := mustGetFeed(feedID4)\n\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata2\": \"value\"}`)\n\n\t// User2 create 1 post\n\tstd.TestSetOrigCaller(user2)\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\n\tif count := countPostsByUser(feed4.id, user1.String()); count != 2 {\n\t\tt.Fatalf(\"expected total posts by user1: 2, got %d.\", count)\n\t}\n\n\tif count := countPostsByUser(feed4.id, user2.String()); count != 1 {\n\t\tt.Fatalf(\"expected total posts by user2: 1, got %d.\", count)\n\t}\n\n\tif count := countPostsByUser(feed4.id, \"\"); count != 3 {\n\t\tt.Fatalf(\"expected total posts: 3, got %d.\", count)\n\t}\n}\n\nfunc testHidePostForMe(t *testing.T) {\n\tuser := std.Address(\"user\")\n\tstd.TestSetOrigCaller(user)\n\n\tfeedID8 := CreateFeed(\"teritor8\")\n\tfeed8 := mustGetFeed(feedID8)\n\n\tpostIDToHide := CreatePost(feed8.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\tpostID := CreatePost(feed8.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// Hide a post for me\n\tHidePostForMe(feed8.id, postIDToHide)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 1 {\n\t\tt.Fatalf(\"expected posts count after hidding: 1, got %q.\", count)\n\t}\n\n\t// Query from another user should return full list\n\tanother := std.Address(\"another\")\n\tstd.TestSetOrigCaller(another)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count from another: 2, got %q.\", count)\n\t}\n\n\t// UnHide a post for me\n\tstd.TestSetOrigCaller(user)\n\tUnHidePostForMe(feed8.id, postIDToHide)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count after unhidding: 2, got %q.\", count)\n\t}\n}\n\nfunc testMigrateFeedData(t *testing.T) string {\n\tfeedID := feedsV7.CreateFeed(\"teritor11\")\n\n\t// Post to test\n\tpostID := feedsV7.CreatePost(feedID, feedsV7.PostID(0), 2, `{\"metadata\": \"value\"}`)\n\tfeedsV7.ReactPost(feedID, postID, \"🇬🇸\", true)\n\n\t// Add comment to post\n\tcommentID := feedsV7.CreatePost(feedID, postID, 2, `{\"comment1\": \"value\"}`)\n\tfeedsV7.ReactPost(feedID, commentID, \"🇬🇸\", true)\n\n\t// // Post with json metadata\n\tfeedsV7.CreatePost(feedID, feedsV7.PostID(0), 2, `{'a':1}`)\n\n\t// Expect: should convert feed data to JSON successfully without error\n\tdataJSON := feedsV7.ExportFeedData(feedID)\n\tif dataJSON == \"\" {\n\t\tt.Fatalf(\"expected feed data exported successfully\")\n\t}\n\n\t// Import data =====================================\n\tImportFeedData(FeedID(uint64(feedID)), dataJSON)\n\n\t// Test public func\n\t// MigrateFromPreviousFeed(feedID)\n}\n\nfunc Test(t *testing.T) {\n\ttestCreateFeed(t)\n\n\ttestCreatePost(t)\n\n\ttestGetPosts(t)\n\n\ttestReactPost(t)\n\n\ttestCreateAndDeleteComment(t)\n\n\ttestFilterByCategories(t)\n\n\ttestTipPost(t)\n\n\ttestFilterUser(t)\n\n\ttestFlagPost(t)\n\n\ttestHidePostForMe(t)\n\n\ttestMigrateFeedData(t)\n}\n" + "Body": "package social_feeds\n\nimport (\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t// Fake previous version for testing\n\tfeedsV7 \"gno.land/r/demo/teritori/social_feeds\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\trootPostID = PostID(0)\n\tpostID1 = PostID(1)\n\tfeedID1 = FeedID(1)\n\tcat1 = uint64(1)\n\tcat2 = uint64(2)\n\tuser = testutils.TestAddress(\"user\")\n\tfilter_all = []uint64{}\n)\n\nfunc getFeed1() *Feed {\n\treturn mustGetFeed(feedID1)\n}\n\nfunc getPost1() *Post {\n\tfeed1 := getFeed1()\n\tpost1 := feed1.MustGetPost(postID1)\n\treturn post1\n}\n\nfunc testCreateFeed(t *testing.T) {\n\tfeedID := CreateFeed(\"teritori1\")\n\tfeed := mustGetFeed(feedID)\n\n\tif feedID != 1 {\n\t\tt.Fatalf(\"expected feedID: 1, got %q.\", feedID)\n\t}\n\n\tif feed.name != \"teritori1\" {\n\t\tt.Fatalf(\"expected feedName: teritori1, got %q.\", feed.name)\n\t}\n}\n\nfunc testCreatePost(t *testing.T) {\n\tmetadata := `{\"gifs\": [], \"files\": [], \"title\": \"\", \"message\": \"testouille\", \"hashtags\": [], \"mentions\": [], \"createdAt\": \"2023-03-29T12:19:04.858Z\", \"updatedAt\": \"2023-03-29T12:19:04.858Z\"}`\n\tpostID := CreatePost(feedID1, rootPostID, cat1, metadata)\n\tfeed := mustGetFeed(feedID1)\n\tpost := feed.MustGetPost(postID)\n\n\tif postID != 1 {\n\t\tt.Fatalf(\"expected postID: 1, got %q.\", postID)\n\t}\n\n\tif post.category != cat1 {\n\t\tt.Fatalf(\"expected categoryID: %q, got %q.\", cat1, post.category)\n\t}\n}\n\nfunc toPostIDsStr(posts []*Post) string {\n\tvar postIDs []string\n\tfor _, post := range posts {\n\t\tpostIDs = append(postIDs, post.id.String())\n\t}\n\n\tpostIDsStr := strings.Join(postIDs, \",\")\n\treturn postIDsStr\n}\n\nfunc testGetPosts(t *testing.T) {\n\tuser := std.Address(\"user\")\n\tstd.TestSetOriginCaller(user)\n\n\tfeedID := CreateFeed(\"teritori10\")\n\tfeed := mustGetFeed(feedID)\n\n\tCreatePost(feedID, rootPostID, cat1, \"post1\")\n\tCreatePost(feedID, rootPostID, cat1, \"post2\")\n\tCreatePost(feedID, rootPostID, cat1, \"post3\")\n\tCreatePost(feedID, rootPostID, cat1, \"post4\")\n\tCreatePost(feedID, rootPostID, cat1, \"post5\")\n\tpostIDToFlagged := CreatePost(feedID, rootPostID, cat1, \"post6\")\n\tpostIDToHide := CreatePost(feedID, rootPostID, cat1, \"post7\")\n\tCreatePost(feedID, rootPostID, cat1, \"post8\")\n\n\tvar posts []*Post\n\tvar postIDsStr string\n\n\t// Query last 3 posts\n\tposts = getPosts(feed, 0, \"\", \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,7,6\" {\n\t\tt.Fatalf(\"expected posts order: 8,7,6. Got: %s\", postIDsStr)\n\t}\n\n\t// Query page 2\n\tposts = getPosts(feed, 0, \"\", \"\", []uint64{}, 3, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\tif postIDsStr != \"5,4,3\" {\n\t\tt.Fatalf(\"expected posts order: 5,4,3. Got: %s\", postIDsStr)\n\t}\n\n\t// Exclude hidden post\n\tHidePostForMe(feed.id, postIDToHide)\n\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,6,5\" {\n\t\tt.Fatalf(\"expected posts order: 8,6,5. Got: %s\", postIDsStr)\n\t}\n\n\t// Exclude flagged post\n\tFlagPost(feed.id, postIDToFlagged)\n\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,5,4\" {\n\t\tt.Fatalf(\"expected posts order: 8,5,4. Got: %s\", postIDsStr)\n\t}\n\n\t// Pagination with hidden/flagged posts\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 3, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"3,2,1\" {\n\t\tt.Fatalf(\"expected posts order: 3,2,1. Got: %s\", postIDsStr)\n\t}\n\n\t// Query out of range\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 6, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"\" {\n\t\tt.Fatalf(\"expected posts order: ''. Got: %s\", postIDsStr)\n\t}\n}\n\nfunc testReactPost(t *testing.T) {\n\tfeed := getFeed1()\n\tpost := getPost1()\n\n\ticon := \"🥰\"\n\tReactPost(feed.id, post.id, icon, true)\n\n\t// Set reaction\n\treactionCount_, ok := post.reactions.Get(\"🥰\")\n\tif !ok {\n\t\tt.Fatalf(\"expected 🥰 exists\")\n\t}\n\n\treactionCount := reactionCount_.(int)\n\tif reactionCount != 1 {\n\t\tt.Fatalf(\"expected reactionCount: 1, got %q.\", reactionCount)\n\t}\n\n\t// Unset reaction\n\tReactPost(feed.id, post.id, icon, false)\n\t_, exist := post.reactions.Get(\"🥰\")\n\tif exist {\n\t\tt.Fatalf(\"expected 🥰 not exist\")\n\t}\n}\n\nfunc testCreateAndDeleteComment(t *testing.T) {\n\tfeed1 := getFeed1()\n\tpost1 := getPost1()\n\n\tmetadata := `empty_meta_data`\n\n\tcommentID1 := CreatePost(feed1.id, post1.id, cat1, metadata)\n\tcommentID2 := CreatePost(feed1.id, post1.id, cat1, metadata)\n\tcomment2 := feed1.MustGetPost(commentID2)\n\n\tif comment2.id != 3 { // 1 post + 2 comments = 3\n\t\tt.Fatalf(\"expected comment postID: 3, got %q.\", comment2.id)\n\t}\n\n\tif comment2.parentID != post1.id {\n\t\tt.Fatalf(\"expected comment parentID: %q, got %q.\", post1.id, comment2.parentID)\n\t}\n\n\t// Check comment count on parent\n\tif post1.commentsCount != 2 {\n\t\tt.Fatalf(\"expected comments count: 2, got %d.\", post1.commentsCount)\n\t}\n\n\t// Get comments\n\tcomments := GetComments(feed1.id, post1.id, 0, 10)\n\tcommentsParsed := ujson.ParseSlice(comments)\n\n\tif len(commentsParsed) != 2 {\n\t\tt.Fatalf(\"expected encoded comments: 2, got %q.\", commentsParsed)\n\t}\n\n\t// Delete 1 comment\n\tDeletePost(feed1.id, comment2.id)\n\tcomments = GetComments(feed1.id, post1.id, 0, 10)\n\tcommentsParsed = ujson.ParseSlice(comments)\n\n\tif len(commentsParsed) != 1 {\n\t\tt.Fatalf(\"expected encoded comments: 1, got %q.\", commentsParsed)\n\t}\n\n\t// Check comment count on parent\n\tif post1.commentsCount != 1 {\n\t\tt.Fatalf(\"expected comments count: 1, got %d.\", post1.commentsCount)\n\t}\n}\n\nfunc countPosts(feedID FeedID, categories []uint64, limit uint8) int {\n\toffset := uint64(0)\n\n\tpostsStr := GetPosts(feedID, 0, \"\", categories, offset, limit)\n\tif postsStr == \"[]\" {\n\t\treturn 0\n\t}\n\n\tparsedPosts := ujson.ParseSlice(postsStr)\n\tpostsCount := len(parsedPosts)\n\treturn postsCount\n}\n\nfunc countPostsByUser(feedID FeedID, user string) int {\n\toffset := uint64(0)\n\tlimit := uint8(10)\n\n\tpostsStr := GetPosts(feedID, 0, user, []uint64{}, offset, limit)\n\tif postsStr == \"[]\" {\n\t\treturn 0\n\t}\n\n\tparsedPosts := ujson.ParseSlice(postsStr)\n\tpostsCount := len(parsedPosts)\n\treturn postsCount\n}\n\nfunc testFilterByCategories(t *testing.T) {\n\t// // Re-add reaction to test post list\n\t// ReactPost(1, postID, \"🥰\", true)\n\t// ReactPost(1, postID, \"😇\", true)\n\n\tfilter_cat1 := []uint64{1}\n\tfilter_cat1_2 := []uint64{1, 2}\n\tfilter_cat9 := []uint64{9}\n\tfilter_cat1_2_9 := []uint64{1, 2, 9}\n\n\tfeedID2 := CreateFeed(\"teritori2\")\n\tfeed2 := mustGetFeed(feedID2)\n\n\t// Create 2 posts on root with cat1\n\tpostID1 := CreatePost(feed2.id, rootPostID, cat1, \"metadata\")\n\tpostID2 := CreatePost(feed2.id, rootPostID, cat1, \"metadata\")\n\n\t// Create 1 posts on root with cat2\n\tpostID3 := CreatePost(feed2.id, rootPostID, cat2, \"metadata\")\n\n\t// Create comments on post 1\n\tcommentPostID1 := CreatePost(feed2.id, postID1, cat1, \"metadata\")\n\n\t// cat1: Should return max = limit\n\tif count := countPosts(feed2.id, filter_cat1, 1); count != 1 {\n\t\tt.Fatalf(\"expected posts count: 1, got %q.\", count)\n\t}\n\n\t// cat1: Should return max = total\n\tif count := countPosts(feed2.id, filter_cat1, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// cat 1 + 2: Should return max = limit\n\tif count := countPosts(feed2.id, filter_cat1_2, 2); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// cat 1 + 2: Should return max = total on both\n\tif count := countPosts(feed2.id, filter_cat1_2, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// cat 1, 2, 9: Should return total of 1, 2\n\tif count := countPosts(feed2.id, filter_cat1_2_9, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// cat 9: Should return 0\n\tif count := countPosts(feed2.id, filter_cat9, 10); count != 0 {\n\t\tt.Fatalf(\"expected posts count: 0, got %q.\", count)\n\t}\n\n\t// cat all: should return all\n\tif count := countPosts(feed2.id, filter_all, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// add comments should not impact the results\n\tCreatePost(feed2.id, postID1, cat1, \"metadata\")\n\tCreatePost(feed2.id, postID2, cat1, \"metadata\")\n\n\tif count := countPosts(feed2.id, filter_all, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// delete a post should affect the result\n\tDeletePost(feed2.id, postID1)\n\n\tif count := countPosts(feed2.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n}\n\nfunc testTipPost(t *testing.T) {\n\tcreator := testutils.TestAddress(\"creator\")\n\tstd.TestIssueCoins(creator, std.Coins{{\"ugnot\", 100_000_000}})\n\n\t// NOTE: Dont know why the address should be this to be able to call banker (= std.GetCallerAt(1))\n\ttipper := testutils.TestAddress(\"tipper\")\n\tstd.TestIssueCoins(tipper, std.Coins{{\"ugnot\", 50_000_000}})\n\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\n\t// Check Original coins of creator/tipper\n\tif coins := banker.GetCoins(creator); coins[0].Amount != 100_000_000 {\n\t\tt.Fatalf(\"expected creator coin count: 100_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\tif coins := banker.GetCoins(tipper); coins[0].Amount != 50_000_000 {\n\t\tt.Fatalf(\"expected tipper coin count: 50_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\t// Creator creates feed, post\n\tstd.TestSetOriginCaller(creator)\n\n\tfeedID3 := CreateFeed(\"teritori3\")\n\tfeed3 := mustGetFeed(feedID3)\n\n\tpostID1 := CreatePost(feed3.id, rootPostID, cat1, \"metadata\")\n\tpost1 := feed3.MustGetPost(postID1)\n\n\t// Tiper tips the ppst\n\tstd.TestSetOriginCaller(tipper)\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 1_000_000}}, nil)\n\tTipPost(feed3.id, post1.id)\n\n\t// Coin must be increased for creator\n\tif coins := banker.GetCoins(creator); coins[0].Amount != 101_000_000 {\n\t\tt.Fatalf(\"expected creator coin after beging tipped: 101_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\t// Total tip amount should increased\n\tif post1.tipAmount != 1_000_000 {\n\t\tt.Fatalf(\"expected total tipAmount: 1_000_000, got %d.\", post1.tipAmount)\n\t}\n\n\t// Add more tip should update this total\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 2_000_000}}, nil)\n\tTipPost(feed3.id, post1.id)\n\n\tif post1.tipAmount != 3_000_000 {\n\t\tt.Fatalf(\"expected total tipAmount: 3_000_000, got %d.\", post1.tipAmount)\n\t}\n}\n\nfunc testFlagPost(t *testing.T) {\n\tflagger := testutils.TestAddress(\"flagger\")\n\n\tfeedID9 := CreateFeed(\"teritori9\")\n\tfeed9 := mustGetFeed(feedID9)\n\n\tCreatePost(feed9.id, rootPostID, cat1, \"metadata1\")\n\tpid := CreatePost(feed9.id, rootPostID, cat1, \"metadata1\")\n\n\t// Flag post\n\tstd.TestSetOriginCaller(flagger)\n\tFlagPost(feed9.id, pid)\n\n\t// Another user flags\n\tanother := testutils.TestAddress(\"another\")\n\tstd.TestSetOriginCaller(another)\n\tFlagPost(feed9.id, pid)\n\n\tflaggedPostsStr := GetFlaggedPosts(feed9.id, 0, 10)\n\tparsed := ujson.ParseSlice(flaggedPostsStr)\n\tif flaggedPostsCount := len(parsed); flaggedPostsCount != 1 {\n\t\tt.Fatalf(\"expected flagged posts: 1, got %d.\", flaggedPostsCount)\n\t}\n}\n\nfunc testFilterUser(t *testing.T) {\n\tuser1 := testutils.TestAddress(\"user1\")\n\tuser2 := testutils.TestAddress(\"user2\")\n\n\t// User1 create 2 posts\n\tstd.TestSetOriginCaller(user1)\n\n\tfeedID4 := CreateFeed(\"teritori4\")\n\tfeed4 := mustGetFeed(feedID4)\n\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata2\": \"value\"}`)\n\n\t// User2 create 1 post\n\tstd.TestSetOriginCaller(user2)\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\n\tif count := countPostsByUser(feed4.id, user1.String()); count != 2 {\n\t\tt.Fatalf(\"expected total posts by user1: 2, got %d.\", count)\n\t}\n\n\tif count := countPostsByUser(feed4.id, user2.String()); count != 1 {\n\t\tt.Fatalf(\"expected total posts by user2: 1, got %d.\", count)\n\t}\n\n\tif count := countPostsByUser(feed4.id, \"\"); count != 3 {\n\t\tt.Fatalf(\"expected total posts: 3, got %d.\", count)\n\t}\n}\n\nfunc testHidePostForMe(t *testing.T) {\n\tuser := std.Address(\"user\")\n\tstd.TestSetOriginCaller(user)\n\n\tfeedID8 := CreateFeed(\"teritor8\")\n\tfeed8 := mustGetFeed(feedID8)\n\n\tpostIDToHide := CreatePost(feed8.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\tpostID := CreatePost(feed8.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// Hide a post for me\n\tHidePostForMe(feed8.id, postIDToHide)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 1 {\n\t\tt.Fatalf(\"expected posts count after hidding: 1, got %q.\", count)\n\t}\n\n\t// Query from another user should return full list\n\tanother := std.Address(\"another\")\n\tstd.TestSetOriginCaller(another)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count from another: 2, got %q.\", count)\n\t}\n\n\t// UnHide a post for me\n\tstd.TestSetOriginCaller(user)\n\tUnHidePostForMe(feed8.id, postIDToHide)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count after unhidding: 2, got %q.\", count)\n\t}\n}\n\nfunc testMigrateFeedData(t *testing.T) string {\n\tfeedID := feedsV7.CreateFeed(\"teritor11\")\n\n\t// Post to test\n\tpostID := feedsV7.CreatePost(feedID, feedsV7.PostID(0), 2, `{\"metadata\": \"value\"}`)\n\tfeedsV7.ReactPost(feedID, postID, \"🇬🇸\", true)\n\n\t// Add comment to post\n\tcommentID := feedsV7.CreatePost(feedID, postID, 2, `{\"comment1\": \"value\"}`)\n\tfeedsV7.ReactPost(feedID, commentID, \"🇬🇸\", true)\n\n\t// // Post with json metadata\n\tfeedsV7.CreatePost(feedID, feedsV7.PostID(0), 2, `{'a':1}`)\n\n\t// Expect: should convert feed data to JSON successfully without error\n\tdataJSON := feedsV7.ExportFeedData(feedID)\n\tif dataJSON == \"\" {\n\t\tt.Fatalf(\"expected feed data exported successfully\")\n\t}\n\n\t// Import data =====================================\n\tImportFeedData(FeedID(uint64(feedID)), dataJSON)\n\n\t// Test public func\n\t// MigrateFromPreviousFeed(feedID)\n}\n\nfunc Test(t *testing.T) {\n\ttestCreateFeed(t)\n\n\ttestCreatePost(t)\n\n\ttestGetPosts(t)\n\n\ttestReactPost(t)\n\n\ttestCreateAndDeleteComment(t)\n\n\ttestFilterByCategories(t)\n\n\ttestTipPost(t)\n\n\ttestFilterUser(t)\n\n\ttestFlagPost(t)\n\n\ttestHidePostForMe(t)\n\n\ttestMigrateFeedData(t)\n}\n" }, { "Name": "flags.gno", @@ -60,7 +60,7 @@ stdout OK! }, { "Name": "post.gno", - "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n)\n\ntype PostID uint64\n\nfunc (pid PostID) String() string {\n\treturn strconv.Itoa(int(pid))\n}\n\nfunc (pid *PostID) FromJSON(ast *ujson.JSONASTNode) {\n\tval, err := strconv.Atoi(ast.Value)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t*pid = PostID(val)\n}\n\nfunc (pid PostID) ToJSON() string {\n\treturn strconv.Itoa(int(pid))\n}\n\ntype Reaction struct {\n\ticon string\n\tcount uint64\n}\n\nvar Categories []string = []string{\n\t\"Reaction\",\n\t\"Comment\",\n\t\"Normal\",\n\t\"Article\",\n\t\"Picture\",\n\t\"Audio\",\n\t\"Video\",\n}\n\ntype Post struct {\n\tid PostID\n\tparentID PostID\n\tfeedID FeedID\n\tcategory uint64\n\tmetadata string\n\treactions avl.Tree // icon -> count\n\tcomments avl.Tree // Post.id -> *Post\n\tcreator std.Address\n\ttipAmount uint64\n\tdeleted bool\n\tcommentsCount uint64\n\n\tcreatedAt int64\n\tupdatedAt int64\n\tdeletedAt int64\n}\n\nfunc newPost(feed *Feed, id PostID, creator std.Address, parentID PostID, category uint64, metadata string) *Post {\n\treturn &Post{\n\t\tid: id,\n\t\tparentID: parentID,\n\t\tfeedID: feed.id,\n\t\tcategory: category,\n\t\tmetadata: metadata,\n\t\treactions: avl.Tree{},\n\t\tcreator: creator,\n\t\tcreatedAt: time.Now().Unix(),\n\t}\n}\n\nfunc (post *Post) String() string {\n\treturn post.ToJSON()\n}\n\nfunc (post *Post) Update(category uint64, metadata string) {\n\tpost.category = category\n\tpost.metadata = metadata\n\tpost.updatedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Delete() {\n\tpost.deleted = true\n\tpost.deletedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Tip(from std.Address, to std.Address) {\n\treceivedCoins := std.OrigSend()\n\tamount := receivedCoins[0].Amount\n\n\tbanker := std.GetBanker(std.BankerTypeOrigSend)\n\t// banker := std.GetBanker(std.BankerTypeRealmSend)\n\tcoinsToSend := std.Coins{std.Coin{Denom: \"ugnot\", Amount: amount}}\n\tpkgaddr := std.GetOrigPkgAddr()\n\n\tbanker.SendCoins(pkgaddr, to, coinsToSend)\n\n\t// Update tip amount\n\tpost.tipAmount += uint64(amount)\n}\n\n// Always remove reaction if count = 0\nfunc (post *Post) React(icon string, up bool) {\n\tcount_, ok := post.reactions.Get(icon)\n\tcount := 0\n\n\tif ok {\n\t\tcount = count_.(int)\n\t}\n\n\tif up {\n\t\tcount++\n\t} else {\n\t\tcount--\n\t}\n\n\tif count <= 0 {\n\t\tpost.reactions.Remove(icon)\n\t} else {\n\t\tpost.reactions.Set(icon, count)\n\t}\n}\n\nfunc (post *Post) Render() string {\n\treturn post.metadata\n}\n\nfunc (post *Post) FromJSON(jsonData string) {\n\tast := ujson.TokenizeAndParse(jsonData)\n\tast.ParseObject([]*ujson.ParseKV{\n\t\t{Key: \"id\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.id = PostID(pid)\n\t\t}},\n\t\t{Key: \"parentID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.parentID = PostID(pid)\n\t\t}},\n\t\t{Key: \"feedID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tfid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.feedID = FeedID(fid)\n\t\t}},\n\t\t{Key: \"category\", Value: &post.category},\n\t\t{Key: \"metadata\", Value: &post.metadata},\n\t\t{Key: \"reactions\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\treactions := avl.NewTree()\n\t\t\tfor _, child := range node.ObjectChildren {\n\t\t\t\treactionCount := child.Value\n\t\t\t\treactions.Set(child.Key, reactionCount)\n\t\t\t}\n\t\t\tpost.reactions = *reactions\n\t\t}},\n\t\t{Key: \"commentsCount\", Value: &post.commentsCount},\n\t\t{Key: \"creator\", Value: &post.creator},\n\t\t{Key: \"tipAmount\", Value: &post.tipAmount},\n\t\t{Key: \"deleted\", Value: &post.deleted},\n\t\t{Key: \"createdAt\", Value: &post.createdAt},\n\t\t{Key: \"updatedAt\", Value: &post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: &post.deletedAt},\n\t})\n}\n\nfunc (post *Post) ToJSON() string {\n\treactionsKV := []ujson.FormatKV{}\n\tpost.reactions.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tcount := value.(int)\n\t\tdata := ujson.FormatKV{Key: key, Value: count}\n\t\treactionsKV = append(reactionsKV, data)\n\t\treturn false\n\t})\n\treactions := ujson.FormatObject(reactionsKV)\n\n\tpostJSON := ujson.FormatObject([]ujson.FormatKV{\n\t\t{Key: \"id\", Value: uint64(post.id)},\n\t\t{Key: \"parentID\", Value: uint64(post.parentID)},\n\t\t{Key: \"feedID\", Value: uint64(post.feedID)},\n\t\t{Key: \"category\", Value: post.category},\n\t\t{Key: \"metadata\", Value: post.metadata},\n\t\t{Key: \"reactions\", Value: reactions, Raw: true},\n\t\t{Key: \"creator\", Value: post.creator},\n\t\t{Key: \"tipAmount\", Value: post.tipAmount},\n\t\t{Key: \"deleted\", Value: post.deleted},\n\t\t{Key: \"commentsCount\", Value: post.commentsCount},\n\t\t{Key: \"createdAt\", Value: post.createdAt},\n\t\t{Key: \"updatedAt\", Value: post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: post.deletedAt},\n\t})\n\treturn postJSON\n}\n" + "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n)\n\ntype PostID uint64\n\nfunc (pid PostID) String() string {\n\treturn strconv.Itoa(int(pid))\n}\n\nfunc (pid *PostID) FromJSON(ast *ujson.JSONASTNode) {\n\tval, err := strconv.Atoi(ast.Value)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t*pid = PostID(val)\n}\n\nfunc (pid PostID) ToJSON() string {\n\treturn strconv.Itoa(int(pid))\n}\n\ntype Reaction struct {\n\ticon string\n\tcount uint64\n}\n\nvar Categories []string = []string{\n\t\"Reaction\",\n\t\"Comment\",\n\t\"Normal\",\n\t\"Article\",\n\t\"Picture\",\n\t\"Audio\",\n\t\"Video\",\n}\n\ntype Post struct {\n\tid PostID\n\tparentID PostID\n\tfeedID FeedID\n\tcategory uint64\n\tmetadata string\n\treactions avl.Tree // icon -> count\n\tcomments avl.Tree // Post.id -> *Post\n\tcreator std.Address\n\ttipAmount uint64\n\tdeleted bool\n\tcommentsCount uint64\n\n\tcreatedAt int64\n\tupdatedAt int64\n\tdeletedAt int64\n}\n\nfunc newPost(feed *Feed, id PostID, creator std.Address, parentID PostID, category uint64, metadata string) *Post {\n\treturn &Post{\n\t\tid: id,\n\t\tparentID: parentID,\n\t\tfeedID: feed.id,\n\t\tcategory: category,\n\t\tmetadata: metadata,\n\t\treactions: avl.Tree{},\n\t\tcreator: creator,\n\t\tcreatedAt: time.Now().Unix(),\n\t}\n}\n\nfunc (post *Post) String() string {\n\treturn post.ToJSON()\n}\n\nfunc (post *Post) Update(category uint64, metadata string) {\n\tpost.category = category\n\tpost.metadata = metadata\n\tpost.updatedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Delete() {\n\tpost.deleted = true\n\tpost.deletedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Tip(from std.Address, to std.Address) {\n\treceivedCoins := std.OriginSend()\n\tamount := receivedCoins[0].Amount\n\n\tbanker := std.GetBanker(std.BankerTypeOriginSend)\n\t// banker := std.GetBanker(std.BankerTypeRealmSend)\n\tcoinsToSend := std.Coins{std.Coin{Denom: \"ugnot\", Amount: amount}}\n\tpkgaddr := std.GetOrigPkgAddr()\n\n\tbanker.SendCoins(pkgaddr, to, coinsToSend)\n\n\t// Update tip amount\n\tpost.tipAmount += uint64(amount)\n}\n\n// Always remove reaction if count = 0\nfunc (post *Post) React(icon string, up bool) {\n\tcount_, ok := post.reactions.Get(icon)\n\tcount := 0\n\n\tif ok {\n\t\tcount = count_.(int)\n\t}\n\n\tif up {\n\t\tcount++\n\t} else {\n\t\tcount--\n\t}\n\n\tif count <= 0 {\n\t\tpost.reactions.Remove(icon)\n\t} else {\n\t\tpost.reactions.Set(icon, count)\n\t}\n}\n\nfunc (post *Post) Render() string {\n\treturn post.metadata\n}\n\nfunc (post *Post) FromJSON(jsonData string) {\n\tast := ujson.TokenizeAndParse(jsonData)\n\tast.ParseObject([]*ujson.ParseKV{\n\t\t{Key: \"id\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.id = PostID(pid)\n\t\t}},\n\t\t{Key: \"parentID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.parentID = PostID(pid)\n\t\t}},\n\t\t{Key: \"feedID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tfid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.feedID = FeedID(fid)\n\t\t}},\n\t\t{Key: \"category\", Value: &post.category},\n\t\t{Key: \"metadata\", Value: &post.metadata},\n\t\t{Key: \"reactions\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\treactions := avl.NewTree()\n\t\t\tfor _, child := range node.ObjectChildren {\n\t\t\t\treactionCount := child.Value\n\t\t\t\treactions.Set(child.Key, reactionCount)\n\t\t\t}\n\t\t\tpost.reactions = *reactions\n\t\t}},\n\t\t{Key: \"commentsCount\", Value: &post.commentsCount},\n\t\t{Key: \"creator\", Value: &post.creator},\n\t\t{Key: \"tipAmount\", Value: &post.tipAmount},\n\t\t{Key: \"deleted\", Value: &post.deleted},\n\t\t{Key: \"createdAt\", Value: &post.createdAt},\n\t\t{Key: \"updatedAt\", Value: &post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: &post.deletedAt},\n\t})\n}\n\nfunc (post *Post) ToJSON() string {\n\treactionsKV := []ujson.FormatKV{}\n\tpost.reactions.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tcount := value.(int)\n\t\tdata := ujson.FormatKV{Key: key, Value: count}\n\t\treactionsKV = append(reactionsKV, data)\n\t\treturn false\n\t})\n\treactions := ujson.FormatObject(reactionsKV)\n\n\tpostJSON := ujson.FormatObject([]ujson.FormatKV{\n\t\t{Key: \"id\", Value: uint64(post.id)},\n\t\t{Key: \"parentID\", Value: uint64(post.parentID)},\n\t\t{Key: \"feedID\", Value: uint64(post.feedID)},\n\t\t{Key: \"category\", Value: post.category},\n\t\t{Key: \"metadata\", Value: post.metadata},\n\t\t{Key: \"reactions\", Value: reactions, Raw: true},\n\t\t{Key: \"creator\", Value: post.creator},\n\t\t{Key: \"tipAmount\", Value: post.tipAmount},\n\t\t{Key: \"deleted\", Value: post.deleted},\n\t\t{Key: \"commentsCount\", Value: post.commentsCount},\n\t\t{Key: \"createdAt\", Value: post.createdAt},\n\t\t{Key: \"updatedAt\", Value: post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: post.deletedAt},\n\t})\n\treturn postJSON\n}\n" }, { "Name": "public.gno", diff --git a/gno.land/genesis/genesis_txs.jsonl b/gno.land/genesis/genesis_txs.jsonl index c8e5be67d8a..036d4686f72 100644 --- a/gno.land/genesis/genesis_txs.jsonl +++ b/gno.land/genesis/genesis_txs.jsonl @@ -10,7 +10,7 @@ {"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateBoard","args":["testboard"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"vzlSxEFh5jOkaSdv3rsV91v/OJKEF2qSuoCpri1u5tRWq62T7xr3KHRCF5qFnn4aQX/yE8g8f/Y//WPOCUGhJw=="}],"memo":""}} {"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","Hello World","This is a demo of Gno smart contract programming. This document was\nconstructed by Gno onto a smart contract hosted on the data Realm \nname [\"gno.land/r/demo/boards\"](https://gno.land/r/demo/boards/)\n([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)).\n\n## Starting the `gnoland` node node/validator.\n\nNOTE: Where you see `--remote %%REMOTE%%` here, that flag can be replaced\nwith `--remote localhost:26657` for local testnets.\n\n### build gnoland.\n\n```bash\ngit clone git@github.com:gnolang/gno.git\ncd ./gno\nmake \n```\n\n### add test account.\n\n```bash\n./build/gnokey add test1 --recover\n```\n\nUse this mnemonic:\n\u003e source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast\n\n### start gnoland validator node.\n\n```bash\n./build/gnoland\n```\n\n(This can be reset with `make reset`).\n\n### start gnoland web server (optional).\n\n```bash\ngo run ./gnoland/website\n```\n\n## Signing and broadcasting transactions.\n\n### publish the \"gno.land/p/demo/avl\" package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/p/demo/avl\" --pkgdir \"examples/gno.land/p/demo/avl\" --deposit 100ugnot --gas-fee 1ugnot --gas-wanted 2000000 \u003e addpkg.avl.unsigned.txt\n./build/gnokey query \"auth/accounts/g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\"\n./build/gnokey sign test1 --txpath addpkg.avl.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 0 \u003e addpkg.avl.signed.txt\n./build/gnokey broadcast addpkg.avl.signed.txt --remote %%REMOTE%%\n```\n\n### publish the \"gno.land/r/demo/boards\" realm package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/r/demo/boards\" --pkgdir \"examples/gno.land/r/demo/boards\" --deposit 100ugnot --gas-fee 1ugnot --gas-wanted 300000000 \u003e addpkg.boards.unsigned.txt\n./build/gnokey sign test1 --txpath addpkg.boards.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 1 \u003e addpkg.boards.signed.txt\n./build/gnokey broadcast addpkg.boards.signed.txt --remote %%REMOTE%%\n```\n\n### create a board with a smart contract call.\n\n```bash\n./build/gnokey maketx call test1 --pkgpath \"gno.land/r/demo/boards\" --func CreateBoard --args \"testboard\" --gas-fee 1ugnot --gas-wanted 2000000 \u003e createboard.unsigned.txt\n./build/gnokey sign test1 --txpath createboard.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 2 \u003e createboard.signed.txt\n./build/gnokey broadcast createboard.signed.txt --remote %%REMOTE%%\n```\nNext, query for the permanent board ID by querying (you need this to create a new post):\n\n```bash\n./build/gnokey query \"vm/qeval\" --data \"gno.land/r/demo/boards.GetBoardIDFromName(\\\"testboard\\\")\"\n```\n\n### create a post of a board with a smart contract call.\n\n```bash\n./build/gnokey maketx call test1 --pkgpath \"gno.land/r/demo/boards\" --func CreatePost --args 1 --args \"Hello World\" --args#file \"./examples/gno.land/r/demo/boards/README.md\" --gas-fee 1ugnot --gas-wanted 2000000 \u003e createpost.unsigned.txt\n./build/gnokey sign test1 --txpath createpost.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 3 \u003e createpost.signed.txt\n./build/gnokey broadcast createpost.signed.txt --remote %%REMOTE%%\n```\n\n### create a comment to a post.\n\n```bash\n./build/gnokey maketx call test1 --pkgpath \"gno.land/r/demo/boards\" --func CreateReply --args 1 --args 1 --args \"A comment\" --gas-fee 1ugnot --gas-wanted 2000000 \u003e createcomment.unsigned.txt\n./build/gnokey sign test1 --txpath createcomment.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 4 \u003e createcomment.signed.txt\n./build/gnokey broadcast createcomment.signed.txt --remote %%REMOTE%%\n```\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards:testboard/1\"\n```\n\n### render page with optional path expression.\n\nThe contents of `https://gno.land/r/demo/boards:` and `https://gno.land/r/demo/boards:testboard` are rendered by calling\nthe `Render(path string)` function like so:\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards:testboard\"\n```\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"V43B1waFxhzheW9TfmCpjLdrC4dC1yjUGES5y3J6QsNar6hRpNz4G1thzWmWK7xXhg8u1PCIpxLxGczKQYhuPw=="}],"memo":""}} {"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","NFT example","NFT's are all the rage these days, for various reasons.\n\nI read over EIP-721 which appears to be the de-facto NFT standard on Ethereum. Then, made a sample implementation of EIP-721 (let's here called GRC-721). The implementation isn't complete, but it demonstrates the main functionality.\n\n - [EIP-721](https://eips.ethereum.org/EIPS/eip-721)\n - [gno.land/r/demo/nft/nft.gno](https://gno.land/r/demo/nft/nft.gno)\n - [zrealm_nft3.gno test](https://github.com/gnolang/gno/blob/master/examples/gno.land/r/demo/nft/z_3_filetest.gno)\n\nIn short, this demonstrates how to implement Ethereum contract interfaces in gno.land; by using only standard Go language features.\n\nPlease leave a comment ([guide](https://gno.land/r/demo/boards:testboard/1)).\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"ZXfrTiHxPFQL8uSm+Tv7WXIHPMca9okhm94RAlC6YgNbB1VHQYYpoP4w+cnL3YskVzGrOZxensXa9CAZ+cNNeg=="}],"memo":""}} -{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","Simple echo example with coins","This is a simple test realm contract that demonstrates how to use the banker.\n\nSee [gno.land/r/demo/banktest/banktest.gno](/r/demo/banktest/banktest.gno) to see the original contract code.\n\nThis article will go through each line to explain how it works.\n\n```go\npackage banktest\n```\n\nThis package is locally named \"banktest\" (could be anything).\n\n```go\nimport (\n\t\"std\"\n)\n```\n\nThe \"std\" package is defined by the gno code in stdlibs/std/. \u003c/br\u003e\nSelf explanatory; and you'll see more usage from std later.\n\n```go\ntype activity struct {\n\tcaller std.Address\n\tsent std.Coins\n\treturned std.Coins\n\ttime std.Time\n}\n\nfunc (act *activity) String() string {\n\treturn act.caller.String() + \" \" +\n\t\tact.sent.String() + \" sent, \" +\n\t\tact.returned.String() + \" returned, at \" +\n\t\tstd.FormatTimestamp(act.time, \"2006-01-02 3:04pm MST\")\n}\n\nvar latest [10]*activity\n```\n\nThis is just maintaining a list of recent activity to this contract.\nNotice that the \"latest\" variable is defined \"globally\" within\nthe context of the realm with path \"gno.land/r/demo/banktest\".\n\nThis means that calls to functions defined within this package\nare encapsulated within this \"data realm\", where the data is \nmutated based on transactions that can potentially cross many\nrealm and non-realm packge boundaries (in the call stack).\n\n```go\n// Deposit will take the coins (to the realm's pkgaddr) or return them to user.\nfunc Deposit(returnDenom string, returnAmount int64) string {\n\tstd.AssertOriginCall()\n\tcaller := std.OrigCaller()\n\tsend := std.Coins{{returnDenom, returnAmount}}\n```\n\nThis is the beginning of the definition of the contract function named\n\"Deposit\". `std.AssertOriginCall() asserts that this function was called by a\ngno transactional Message. The caller is the user who signed off on this\ntransactional message. Send is the amount of deposit sent along with this\nmessage.\n\n```go\n\t// record activity\n\tact := \u0026activity{\n\t\tcaller: caller,\n\t\tsent: std.OrigSend(),\n\t\treturned: send,\n\t\ttime: std.GetTimestamp(),\n\t}\n\tfor i := len(latest) - 2; i \u003e= 0; i-- {\n\t\tlatest[i+1] = latest[i] // shift by +1.\n\t}\n\tlatest[0] = act\n```\n\nUpdating the \"latest\" array for viewing at gno.land/r/demo/banktest: (w/ trailing colon).\n\n```go\n\t// return if any.\n\tif returnAmount \u003e 0 {\n```\n\nIf the user requested the return of coins...\n\n```go\n\t\tbanker := std.GetBanker(std.BankerTypeOrigSend)\n```\n\nuse a std.Banker instance to return any deposited coins to the original sender.\n\n```go\n\t\tpkgaddr := std.GetOrigPkgAddr()\n\t\t// TODO: use std.Coins constructors, this isn't generally safe.\n\t\tbanker.SendCoins(pkgaddr, caller, send)\n\t\treturn \"returned!\"\n```\n\nNotice that each realm package has an associated Cosmos address.\n\n\nFinally, the results are rendered via an ABCI query call when you visit [/r/demo/banktest:](/r/demo/banktest:).\n\n```go\nfunc Render(path string) string {\n\t// get realm coins.\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tcoins := banker.GetCoins(std.GetOrigPkgAddr())\n\n\t// render\n\tres := \"\"\n\tres += \"## recent activity\\n\"\n\tres += \"\\n\"\n\tfor _, act := range latest {\n\t\tif act == nil {\n\t\t\tbreak\n\t\t}\n\t\tres += \" * \" + act.String() + \"\\n\"\n\t}\n\tres += \"\\n\"\n\tres += \"## total deposits\\n\"\n\tres += coins.String()\n\treturn res\n}\n```\n\nYou can call this contract yourself, by vistiing [/r/demo/banktest](/r/demo/banktest) and the [quickstart guide](/r/demo/boards:testboard/4).\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"iZX/llZlNTdZMLv1goCTgK2bWqzT8enlTq56wMTCpVxJGA0BTvuEM5Nnt9vrnlG6Taqj2GuTrmEnJBkDFTmt9g=="}],"memo":""}} +{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","Simple echo example with coins","This is a simple test realm contract that demonstrates how to use the banker.\n\nSee [gno.land/r/demo/banktest/banktest.gno](/r/demo/banktest/banktest.gno) to see the original contract code.\n\nThis article will go through each line to explain how it works.\n\n```go\npackage banktest\n```\n\nThis package is locally named \"banktest\" (could be anything).\n\n```go\nimport (\n\t\"std\"\n)\n```\n\nThe \"std\" package is defined by the gno code in stdlibs/std/. \u003c/br\u003e\nSelf explanatory; and you'll see more usage from std later.\n\n```go\ntype activity struct {\n\tcaller std.Address\n\tsent std.Coins\n\treturned std.Coins\n\ttime std.Time\n}\n\nfunc (act *activity) String() string {\n\treturn act.caller.String() + \" \" +\n\t\tact.sent.String() + \" sent, \" +\n\t\tact.returned.String() + \" returned, at \" +\n\t\tstd.FormatTimestamp(act.time, \"2006-01-02 3:04pm MST\")\n}\n\nvar latest [10]*activity\n```\n\nThis is just maintaining a list of recent activity to this contract.\nNotice that the \"latest\" variable is defined \"globally\" within\nthe context of the realm with path \"gno.land/r/demo/banktest\".\n\nThis means that calls to functions defined within this package\nare encapsulated within this \"data realm\", where the data is \nmutated based on transactions that can potentially cross many\nrealm and non-realm packge boundaries (in the call stack).\n\n```go\n// Deposit will take the coins (to the realm's pkgaddr) or return them to user.\nfunc Deposit(returnDenom string, returnAmount int64) string {\n\tstd.AssertOriginCall()\n\tcaller := std.OriginCaller()\n\tsend := std.Coins{{returnDenom, returnAmount}}\n```\n\nThis is the beginning of the definition of the contract function named\n\"Deposit\". `std.AssertOriginCall() asserts that this function was called by a\ngno transactional Message. The caller is the user who signed off on this\ntransactional message. Send is the amount of deposit sent along with this\nmessage.\n\n```go\n\t// record activity\n\tact := \u0026activity{\n\t\tcaller: caller,\n\t\tsent: std.OriginSend(),\n\t\treturned: send,\n\t\ttime: std.GetTimestamp(),\n\t}\n\tfor i := len(latest) - 2; i \u003e= 0; i-- {\n\t\tlatest[i+1] = latest[i] // shift by +1.\n\t}\n\tlatest[0] = act\n```\n\nUpdating the \"latest\" array for viewing at gno.land/r/demo/banktest: (w/ trailing colon).\n\n```go\n\t// return if any.\n\tif returnAmount \u003e 0 {\n```\n\nIf the user requested the return of coins...\n\n```go\n\t\tbanker := std.GetBanker(std.BankerTypeOriginSend)\n```\n\nuse a std.Banker instance to return any deposited coins to the original sender.\n\n```go\n\t\tpkgaddr := std.GetOrigPkgAddr()\n\t\t// TODO: use std.Coins constructors, this isn't generally safe.\n\t\tbanker.SendCoins(pkgaddr, caller, send)\n\t\treturn \"returned!\"\n```\n\nNotice that each realm package has an associated Cosmos address.\n\n\nFinally, the results are rendered via an ABCI query call when you visit [/r/demo/banktest:](/r/demo/banktest:).\n\n```go\nfunc Render(path string) string {\n\t// get realm coins.\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tcoins := banker.GetCoins(std.GetOrigPkgAddr())\n\n\t// render\n\tres := \"\"\n\tres += \"## recent activity\\n\"\n\tres += \"\\n\"\n\tfor _, act := range latest {\n\t\tif act == nil {\n\t\t\tbreak\n\t\t}\n\t\tres += \" * \" + act.String() + \"\\n\"\n\t}\n\tres += \"\\n\"\n\tres += \"## total deposits\\n\"\n\tres += coins.String()\n\treturn res\n}\n```\n\nYou can call this contract yourself, by vistiing [/r/demo/banktest](/r/demo/banktest) and the [quickstart guide](/r/demo/boards:testboard/4).\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"iZX/llZlNTdZMLv1goCTgK2bWqzT8enlTq56wMTCpVxJGA0BTvuEM5Nnt9vrnlG6Taqj2GuTrmEnJBkDFTmt9g=="}],"memo":""}} {"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","TASK: Describe in your words","Describe in an essay (250+ words), on your favorite medium, why you are interested in gno.land and gnolang.\n\nReply here with a URL link to your written piece as a comment, for rewards.\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AmG6kzznyo1uNqWPAYU6wDpsmzQKDaEOrVRaZ08vOyX0"},"signature":"4HBNtrta8HdeHj4JTN56PBTRK8GOe31NMRRXDiyYtjozuyRdWfOGEsGjGgHWcoBUJq6DepBgD4FetdqfhZ6TNQ=="}],"memo":""}} {"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1manfred47kzduec920z88wfr64ylksmdcedlf5","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","Getting Started","This is a demo of Gno smart contract programming. This document was\nconstructed by Gno onto a smart contract hosted on the data Realm\nname [\"gno.land/r/demo/boards\"](https://gno.land/r/demo/boards/)\n([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)).\n\n\n\n## Build `gnokey`, create your account, and interact with Gno.\n\nNOTE: Where you see `--remote %%REMOTE%%` here, that flag can be replaced\nwith `--remote localhost:26657` for local testnets.\n\n### Build `gnokey`.\n\n```bash\ngit clone git@github.com:gnolang/gno.git\ncd ./gno\nmake\n```\n\n### Generate a seed/mnemonic code.\n\n```bash\n./build/gnokey generate\n```\n\nNOTE: You can generate 24 words with any good bip39 generator.\n\n### Create a new account using your mnemonic.\n\n```bash\n./build/gnokey add KEYNAME --recover\n```\n\nNOTE: `KEYNAME` is your key identifier, and should be changed.\n\n### Verify that you can see your account locally.\n\n```bash\n./build/gnokey list\n```\n\n## Interact with the blockchain:\n\n### Get your current balance, account number, and sequence number.\n\n```bash\n./build/gnokey query auth/accounts/ACCOUNT_ADDR --remote %%REMOTE%%\n```\n\nNOTE: you can retrieve your `ACCOUNT_ADDR` with `./build/gnokey list`.\n\n### Acquire testnet tokens using the official faucet.\n\nGo to https://gno.land/faucet\n\n### Create a board with a smart contract call.\n\nNOTE: `BOARDNAME` will be the slug of the board, and should be changed.\n\n```bash\n./build/gnokey maketx call KEYNAME --pkgpath \"gno.land/r/demo/boards\" --func \"CreateBoard\" --args \"BOARDNAME\" --gas-fee \"1000000ugnot\" --gas-wanted \"2000000\" --broadcast=true --chainid %%CHAINID%% --remote %%REMOTE%%\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateBoard\n\nNext, query for the permanent board ID by querying (you need this to create a new post):\n\n```bash\n./build/gnokey query \"vm/qeval\" --data \"gno.land/r/demo/boards.GetBoardIDFromName(\\\"BOARDNAME\\\")\" --remote %%REMOTE%%\n```\n\n### Create a post of a board with a smart contract call.\n\nNOTE: If a board was created successfully, your SEQUENCE_NUMBER would have increased.\n\n```bash\n./build/gnokey maketx call KEYNAME --pkgpath \"gno.land/r/demo/boards\" --func \"CreateThread\" --args BOARD_ID --args \"Hello gno.land\" --args\\#file \"./examples/gno.land/r/demo/boards/example_post.md\" --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast=true --chainid %%CHAINID%% --remote %%REMOTE%%\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateThread\n\n### Create a comment to a post.\n\n```bash\n./build/gnokey maketx call KEYNAME --pkgpath \"gno.land/r/demo/boards\" --func \"CreateReply\" --args \"BOARD_ID\" --args \"1\" --args \"1\" --args \"Nice to meet you too.\" --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast=true --chainid %%CHAINID%% --remote %%REMOTE%%\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateReply\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards:BOARDNAME/1\" --remote %%REMOTE%%\n```\n\n### Render page with optional path expression.\n\nThe contents of `https://gno.land/r/demo/boards:` and `https://gno.land/r/demo/boards:gnolang` are rendered by calling\nthe `Render(path string)` function like so:\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards:gnolang\"\n```\n\n## Starting a local `gnoland` node:\n\n### Add test account.\n\n```bash\n./build/gnokey add test1 --recover\n```\n\nUse this mneonic:\n\u003e source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast\n\n### Start `gnoland` node.\n\n```bash\n./build/gnoland\n```\n\nNOTE: This can be reset with `make reset`\n\n### Publish the \"gno.land/p/demo/avl\" package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/p/demo/avl\" --pkgdir \"examples/gno.land/p/demo/avl\" --deposit 100000000ugnot --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast=true --chainid %%CHAINID%% --remote localhost:26657\n```\n\n### Publish the \"gno.land/r/demo/boards\" realm package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/r/demo/boards\" --pkgdir \"examples/gno.land/r/demo/boards\" --deposit 100000000ugnot --gas-fee 1000000ugnot --gas-wanted 300000000 --broadcast=true --chainid %%CHAINID%% --remote localhost:26657\n```\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AnK+a6mcFDjY6b/v6p7r8QFW1M1PgIoQxBgrwOoyY7v3"},"signature":"sHjOGXZEi9wt2FSXFHmkDDoVQyepvFHKRDDU0zgedHYnCYPx5/YndyihsDD5Y2Z7/RgNYBh4JlJwDMGFNStzBQ=="}],"memo":""}} {"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1manfred47kzduec920z88wfr64ylksmdcedlf5","send":"","pkg_path":"gno.land/r/gnoland/blog","func":"ModAddPost","args":["post1","First post","Lorem Ipsum","2022-05-20T13:17:22Z","","tag1,tag2"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AnK+a6mcFDjY6b/v6p7r8QFW1M1PgIoQxBgrwOoyY7v3"},"signature":"sHjOGXZEi9wt2FSXFHmkDDoVQyepvFHKRDDU0zgedHYnCYPx5/YndyihsDD5Y2Z7/RgNYBh4JlJwDMGFNStzBQ=="}],"memo":""}} diff --git a/gno.land/pkg/sdk/vm/handler_test.go b/gno.land/pkg/sdk/vm/handler_test.go index d159d992c50..b5b1040643c 100644 --- a/gno.land/pkg/sdk/vm/handler_test.go +++ b/gno.land/pkg/sdk/vm/handler_test.go @@ -120,7 +120,7 @@ import "std" import "time" var _ = time.RFC3339 -func caller() std.Address { return std.OrigCaller() } +func caller() std.Address { return std.OriginCaller() } var GetHeight = std.GetHeight var sl = []int{1,2,3,4,5} func fn() func(string) string { return Echo } diff --git a/gno.land/pkg/sdk/vm/keeper.go b/gno.land/pkg/sdk/vm/keeper.go index bf16cd44243..0da8987dae2 100644 --- a/gno.land/pkg/sdk/vm/keeper.go +++ b/gno.land/pkg/sdk/vm/keeper.go @@ -265,13 +265,13 @@ func (vm *VMKeeper) checkNamespacePermission(ctx sdk.Context, creator crypto.Add // Parse and run the files, construct *PV. pkgAddr := gno.DerivePkgAddr(pkgPath) msgCtx := stdlibs.ExecContext{ - ChainID: ctx.ChainID(), - ChainDomain: chainDomain, - Height: ctx.BlockHeight(), - Timestamp: ctx.BlockTime().Unix(), - OrigCaller: creator.Bech32(), - OrigSendSpent: new(std.Coins), - OrigPkgAddr: pkgAddr.Bech32(), + ChainID: ctx.ChainID(), + ChainDomain: chainDomain, + Height: ctx.BlockHeight(), + Timestamp: ctx.BlockTime().Unix(), + OriginCaller: creator.Bech32(), + OriginSendSpent: new(std.Coins), + OrigPkgAddr: pkgAddr.Bech32(), // XXX: should we remove the banker ? Banker: NewSDKBanker(vm, ctx), Params: NewSDKParams(vm, ctx), @@ -370,17 +370,17 @@ func (vm *VMKeeper) AddPackage(ctx sdk.Context, msg MsgAddPackage) (err error) { // Parse and run the files, construct *PV. msgCtx := stdlibs.ExecContext{ - ChainID: ctx.ChainID(), - ChainDomain: chainDomain, - Height: ctx.BlockHeight(), - Timestamp: ctx.BlockTime().Unix(), - OrigCaller: creator.Bech32(), - OrigSend: deposit, - OrigSendSpent: new(std.Coins), - OrigPkgAddr: pkgAddr.Bech32(), - Banker: NewSDKBanker(vm, ctx), - Params: NewSDKParams(vm, ctx), - EventLogger: ctx.EventLogger(), + ChainID: ctx.ChainID(), + ChainDomain: chainDomain, + Height: ctx.BlockHeight(), + Timestamp: ctx.BlockTime().Unix(), + OriginCaller: creator.Bech32(), + OriginSend: deposit, + OriginSendSpent: new(std.Coins), + OrigPkgAddr: pkgAddr.Bech32(), + Banker: NewSDKBanker(vm, ctx), + Params: NewSDKParams(vm, ctx), + EventLogger: ctx.EventLogger(), } // Parse and run the files, construct *PV. m2 := gno.NewMachineWithOptions( @@ -461,17 +461,17 @@ func (vm *VMKeeper) Call(ctx sdk.Context, msg MsgCall) (res string, err error) { // could it be safely partially memoized? chainDomain := vm.getChainDomainParam(ctx) msgCtx := stdlibs.ExecContext{ - ChainID: ctx.ChainID(), - ChainDomain: chainDomain, - Height: ctx.BlockHeight(), - Timestamp: ctx.BlockTime().Unix(), - OrigCaller: caller.Bech32(), - OrigSend: send, - OrigSendSpent: new(std.Coins), - OrigPkgAddr: pkgAddr.Bech32(), - Banker: NewSDKBanker(vm, ctx), - Params: NewSDKParams(vm, ctx), - EventLogger: ctx.EventLogger(), + ChainID: ctx.ChainID(), + ChainDomain: chainDomain, + Height: ctx.BlockHeight(), + Timestamp: ctx.BlockTime().Unix(), + OriginCaller: caller.Bech32(), + OriginSend: send, + OriginSendSpent: new(std.Coins), + OrigPkgAddr: pkgAddr.Bech32(), + Banker: NewSDKBanker(vm, ctx), + Params: NewSDKParams(vm, ctx), + EventLogger: ctx.EventLogger(), } // Construct machine and evaluate. m := gno.NewMachineWithOptions( @@ -576,17 +576,17 @@ func (vm *VMKeeper) Run(ctx sdk.Context, msg MsgRun) (res string, err error) { // Parse and run the files, construct *PV. msgCtx := stdlibs.ExecContext{ - ChainID: ctx.ChainID(), - ChainDomain: chainDomain, - Height: ctx.BlockHeight(), - Timestamp: ctx.BlockTime().Unix(), - OrigCaller: caller.Bech32(), - OrigSend: send, - OrigSendSpent: new(std.Coins), - OrigPkgAddr: pkgAddr.Bech32(), - Banker: NewSDKBanker(vm, ctx), - Params: NewSDKParams(vm, ctx), - EventLogger: ctx.EventLogger(), + ChainID: ctx.ChainID(), + ChainDomain: chainDomain, + Height: ctx.BlockHeight(), + Timestamp: ctx.BlockTime().Unix(), + OriginCaller: caller.Bech32(), + OriginSend: send, + OriginSendSpent: new(std.Coins), + OrigPkgAddr: pkgAddr.Bech32(), + Banker: NewSDKBanker(vm, ctx), + Params: NewSDKParams(vm, ctx), + EventLogger: ctx.EventLogger(), } buf := new(bytes.Buffer) @@ -733,9 +733,9 @@ func (vm *VMKeeper) QueryEval(ctx sdk.Context, pkgPath string, expr string) (res ChainDomain: chainDomain, Height: ctx.BlockHeight(), Timestamp: ctx.BlockTime().Unix(), - // OrigCaller: caller, - // OrigSend: send, - // OrigSendSpent: nil, + // OriginCaller: caller, + // OriginSend: send, + // OriginSendSpent: nil, OrigPkgAddr: pkgAddr.Bech32(), Banker: NewSDKBanker(vm, ctx), // safe as long as ctx is a fork to be discarded. Params: NewSDKParams(vm, ctx), @@ -790,9 +790,9 @@ func (vm *VMKeeper) QueryEvalString(ctx sdk.Context, pkgPath string, expr string ChainDomain: chainDomain, Height: ctx.BlockHeight(), Timestamp: ctx.BlockTime().Unix(), - // OrigCaller: caller, - // OrigSend: jsend, - // OrigSendSpent: nil, + // OriginCaller: caller, + // OriginSend: jsend, + // OriginSendSpent: nil, OrigPkgAddr: pkgAddr.Bech32(), Banker: NewSDKBanker(vm, ctx), // safe as long as ctx is a fork to be discarded. Params: NewSDKParams(vm, ctx), diff --git a/gno.land/pkg/sdk/vm/keeper_test.go b/gno.land/pkg/sdk/vm/keeper_test.go index 427f46afbdc..15514b69ab1 100644 --- a/gno.land/pkg/sdk/vm/keeper_test.go +++ b/gno.land/pkg/sdk/vm/keeper_test.go @@ -106,7 +106,7 @@ func Echo() string {return "hello world"}`, } // Sending total send amount succeeds. -func TestVMKeeperOrigSend1(t *testing.T) { +func TestVMKeeperOriginSend1(t *testing.T) { env := setupTestEnv() ctx := env.vmk.MakeGnoTransactionStore(env.ctx) @@ -128,10 +128,10 @@ func init() { } func Echo(msg string) string { - addr := std.OrigCaller() + addr := std.OriginCaller() pkgAddr := std.GetOrigPkgAddr() - send := std.OrigSend() - banker := std.GetBanker(std.BankerTypeOrigSend) + send := std.OriginSend() + banker := std.GetBanker(std.BankerTypeOriginSend) banker.SendCoins(pkgAddr, addr, send) // send back return "echo:"+msg }`}, @@ -151,7 +151,7 @@ func Echo(msg string) string { } // Sending too much fails -func TestVMKeeperOrigSend2(t *testing.T) { +func TestVMKeeperOriginSend2(t *testing.T) { env := setupTestEnv() ctx := env.vmk.MakeGnoTransactionStore(env.ctx) @@ -172,14 +172,14 @@ import "std" var admin std.Address func init() { - admin = std.OrigCaller() + admin = std.OriginCaller() } func Echo(msg string) string { - addr := std.OrigCaller() + addr := std.OriginCaller() pkgAddr := std.GetOrigPkgAddr() - send := std.OrigSend() - banker := std.GetBanker(std.BankerTypeOrigSend) + send := std.OriginSend() + banker := std.GetBanker(std.BankerTypeOriginSend) banker.SendCoins(pkgAddr, addr, send) // send back return "echo:"+msg } @@ -205,7 +205,7 @@ func GetAdmin() string { } // Sending more than tx send fails. -func TestVMKeeperOrigSend3(t *testing.T) { +func TestVMKeeperOriginSend3(t *testing.T) { env := setupTestEnv() ctx := env.vmk.MakeGnoTransactionStore(env.ctx) @@ -227,10 +227,10 @@ func init() { } func Echo(msg string) string { - addr := std.OrigCaller() + addr := std.OriginCaller() pkgAddr := std.GetOrigPkgAddr() send := std.Coins{{"ugnot", 10000000}} - banker := std.GetBanker(std.BankerTypeOrigSend) + banker := std.GetBanker(std.BankerTypeOriginSend) banker.SendCoins(pkgAddr, addr, send) // send back return "echo:"+msg }`}, @@ -271,7 +271,7 @@ func init() { } func Echo(msg string) string { - addr := std.OrigCaller() + addr := std.OriginCaller() pkgAddr := std.GetOrigPkgAddr() send := std.Coins{{"ugnot", 10000000}} banker := std.GetBanker(std.BankerTypeRealmSend) @@ -315,7 +315,7 @@ func init() { } func Echo(msg string) string { - addr := std.OrigCaller() + addr := std.OriginCaller() pkgAddr := std.GetOrigPkgAddr() send := std.Coins{{"ugnot", 10000000}} banker := std.GetBanker(std.BankerTypeRealmSend) @@ -390,8 +390,8 @@ func Do() string { assert.Equal(t, int64(1337), bar) } -// Assign admin as OrigCaller on deploying the package. -func TestVMKeeperOrigCallerInit(t *testing.T) { +// Assign admin as OriginCaller on deploying the package. +func TestVMKeeperOriginCallerInit(t *testing.T) { env := setupTestEnv() ctx := env.vmk.MakeGnoTransactionStore(env.ctx) @@ -412,14 +412,14 @@ import "std" var admin std.Address func init() { - admin = std.OrigCaller() + admin = std.OriginCaller() } func Echo(msg string) string { - addr := std.OrigCaller() + addr := std.OriginCaller() pkgAddr := std.GetOrigPkgAddr() - send := std.OrigSend() - banker := std.GetBanker(std.BankerTypeOrigSend) + send := std.OriginSend() + banker := std.GetBanker(std.BankerTypeOriginSend) banker.SendCoins(pkgAddr, addr, send) // send back return "echo:"+msg } @@ -500,7 +500,7 @@ package main import "std" func main() { - addr := std.OrigCaller() + addr := std.OriginCaller() println("hello world!", addr) } `}, diff --git a/gnovm/pkg/gnolang/gonative.go b/gnovm/pkg/gnolang/gonative.go index 5a39c76b5e1..276d2584b2d 100644 --- a/gnovm/pkg/gnolang/gonative.go +++ b/gnovm/pkg/gnolang/gonative.go @@ -272,7 +272,7 @@ func Go2GnoNativeValue(alloc *Allocator, rv reflect.Value) (tv TypedValue) { return go2GnoValue(alloc, rv) } -// NOTE: used by imports_test.go TestSetOrigCaller. +// NOTE: used by imports_test.go TestSetOriginCaller. func Gno2GoValue(tv *TypedValue, rv reflect.Value) (ret reflect.Value) { return gno2GoValue(tv, rv) } diff --git a/gnovm/pkg/test/test.go b/gnovm/pkg/test/test.go index 80f56e66d2e..be32a7379ab 100644 --- a/gnovm/pkg/test/test.go +++ b/gnovm/pkg/test/test.go @@ -51,17 +51,17 @@ func Context(pkgPath string, send std.Coins) *teststd.TestExecContext { }, } ctx := stdlibs.ExecContext{ - ChainID: "dev", - ChainDomain: "tests.gno.land", - Height: DefaultHeight, - Timestamp: DefaultTimestamp, - OrigCaller: DefaultCaller, - OrigPkgAddr: pkgAddr.Bech32(), - OrigSend: send, - OrigSendSpent: new(std.Coins), - Banker: banker, - Params: newTestParams(), - EventLogger: sdk.NewEventLogger(), + ChainID: "dev", + ChainDomain: "tests.gno.land", + Height: DefaultHeight, + Timestamp: DefaultTimestamp, + OriginCaller: DefaultCaller, + OrigPkgAddr: pkgAddr.Bech32(), + OriginSend: send, + OriginSendSpent: new(std.Coins), + Banker: banker, + Params: newTestParams(), + EventLogger: sdk.NewEventLogger(), } return &teststd.TestExecContext{ ExecContext: ctx, diff --git a/gnovm/pkg/transpiler/transpiler_test.go b/gnovm/pkg/transpiler/transpiler_test.go index 2a0707f7f79..38b241587b0 100644 --- a/gnovm/pkg/transpiler/transpiler_test.go +++ b/gnovm/pkg/transpiler/transpiler_test.go @@ -389,11 +389,11 @@ func otherFunc() { package std func AssertOriginCall() -func origCaller() string +func originCaller() string func testfunc() { AssertOriginCall() - println(origCaller()) + println(originCaller()) } `, expectedOutput: ` @@ -404,7 +404,7 @@ package std func testfunc() { AssertOriginCall(nil) - println(X_origCaller(nil)) + println(X_originCaller(nil)) } `, }, diff --git a/gnovm/stdlibs/generated.go b/gnovm/stdlibs/generated.go index 67b492a34b2..d89a9a5f87c 100644 --- a/gnovm/stdlibs/generated.go +++ b/gnovm/stdlibs/generated.go @@ -510,7 +510,7 @@ var nativeFuncs = [...]NativeFunc{ }, { "std", - "origSend", + "originSend", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("[]string")}, @@ -518,7 +518,7 @@ var nativeFuncs = [...]NativeFunc{ }, true, func(m *gno.Machine) { - r0, r1 := libs_std.X_origSend( + r0, r1 := libs_std.X_originSend( m, ) @@ -536,14 +536,14 @@ var nativeFuncs = [...]NativeFunc{ }, { "std", - "origCaller", + "originCaller", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, true, func(m *gno.Machine) { - r0 := libs_std.X_origCaller( + r0 := libs_std.X_originCaller( m, ) diff --git a/gnovm/stdlibs/std/banker.gno b/gnovm/stdlibs/std/banker.gno index 4c20e8d4b61..c48e65ead6e 100644 --- a/gnovm/stdlibs/std/banker.gno +++ b/gnovm/stdlibs/std/banker.gno @@ -36,7 +36,7 @@ const ( // Can only read state. BankerTypeReadonly BankerType = iota // Can only send from tx send. - BankerTypeOrigSend + BankerTypeOriginSend // Can send from all realm coins. BankerTypeRealmSend // Can issue and remove realm coins. @@ -49,8 +49,8 @@ func (b BankerType) String() string { switch b { case BankerTypeReadonly: return "BankerTypeReadonly" - case BankerTypeOrigSend: - return "BankerTypeOrigSend" + case BankerTypeOriginSend: + return "BankerTypeOriginSend" case BankerTypeRealmSend: return "BankerTypeRealmSend" case BankerTypeRealmIssue: @@ -72,10 +72,10 @@ func GetBanker(bt BankerType) Banker { } var pkgAddr Address - if bt == BankerTypeOrigSend { + if bt == BankerTypeOriginSend { pkgAddr = GetOrigPkgAddr() if pkgAddr != CurrentRealm().Addr() { - panic("banker with type BankerTypeOrigSend can only be instantiated by the origin package") + panic("banker with type BankerTypeOriginSend can only be instantiated by the origin package") } } else if bt == BankerTypeRealmSend || bt == BankerTypeRealmIssue { pkgAddr = CurrentRealm().Addr() diff --git a/gnovm/stdlibs/std/banker.go b/gnovm/stdlibs/std/banker.go index c57ba8529ed..ee02b0cfad0 100644 --- a/gnovm/stdlibs/std/banker.go +++ b/gnovm/stdlibs/std/banker.go @@ -25,7 +25,7 @@ const ( // Can only read state. btReadonly uint8 = iota //nolint // Can only send from tx send. - btOrigSend + btOriginSend // Can send from all realm coins. btRealmSend // Can issue and remove realm coins. @@ -45,19 +45,19 @@ func X_bankerSendCoins(m *gno.Machine, bt uint8, fromS, toS string, denoms []str from, to := crypto.Bech32Address(fromS), crypto.Bech32Address(toS) switch bt { - case btOrigSend: + case btOriginSend: // indirection allows us to "commit" in a second phase - spent := (*ctx.OrigSendSpent).Add(amt) - if !ctx.OrigSend.IsAllGTE(spent) { + spent := (*ctx.OriginSendSpent).Add(amt) + if !ctx.OriginSend.IsAllGTE(spent) { m.Panic(typedString( fmt.Sprintf( `cannot send "%v", limit "%v" exceeded with "%v" already spent`, - amt, ctx.OrigSend, *ctx.OrigSendSpent), + amt, ctx.OriginSend, *ctx.OriginSendSpent), )) return } ctx.Banker.SendCoins(from, to, amt) - *ctx.OrigSendSpent = spent + *ctx.OriginSendSpent = spent case btRealmSend, btRealmIssue: ctx.Banker.SendCoins(from, to, amt) default: diff --git a/gnovm/stdlibs/std/context.go b/gnovm/stdlibs/std/context.go index a8ef500c346..177fc9003a8 100644 --- a/gnovm/stdlibs/std/context.go +++ b/gnovm/stdlibs/std/context.go @@ -8,18 +8,18 @@ import ( ) type ExecContext struct { - ChainID string - ChainDomain string - Height int64 - Timestamp int64 // seconds - TimestampNano int64 // nanoseconds, only used for testing. - OrigCaller crypto.Bech32Address - OrigPkgAddr crypto.Bech32Address - OrigSend std.Coins - OrigSendSpent *std.Coins // mutable - Banker BankerInterface - Params ParamsInterface - EventLogger *sdk.EventLogger + ChainID string + ChainDomain string + Height int64 + Timestamp int64 // seconds + TimestampNano int64 // nanoseconds, only used for testing. + OriginCaller crypto.Bech32Address + OrigPkgAddr crypto.Bech32Address + OriginSend std.Coins + OriginSendSpent *std.Coins // mutable + Banker BankerInterface + Params ParamsInterface + EventLogger *sdk.EventLogger } // GetContext returns the execution context. diff --git a/gnovm/stdlibs/std/native.gno b/gnovm/stdlibs/std/native.gno index 98565237518..790de8aaaae 100644 --- a/gnovm/stdlibs/std/native.gno +++ b/gnovm/stdlibs/std/native.gno @@ -14,8 +14,8 @@ func GetChainID() string // injected func GetChainDomain() string // injected func GetHeight() int64 // injected -func OrigSend() Coins { - den, amt := origSend() +func OriginSend() Coins { + den, amt := originSend() coins := make(Coins, len(den)) for i := range coins { coins[i] = Coin{Denom: den[i], Amount: amt[i]} @@ -23,8 +23,8 @@ func OrigSend() Coins { return coins } -func OrigCaller() Address { - return Address(origCaller()) +func OriginCaller() Address { + return Address(originCaller()) } func CurrentRealm() Realm { @@ -58,8 +58,8 @@ func DecodeBech32(addr Address) (prefix string, bz [20]byte, ok bool) { } // Variations which don't use named types. -func origSend() (denoms []string, amounts []int64) -func origCaller() string +func originSend() (denoms []string, amounts []int64) +func originCaller() string func origPkgAddr() string func callerAt(n int) string func getRealm(height int) (address string, pkgPath string) diff --git a/gnovm/stdlibs/std/native.go b/gnovm/stdlibs/std/native.go index 4f65eaa92a5..7335ee321ad 100644 --- a/gnovm/stdlibs/std/native.go +++ b/gnovm/stdlibs/std/native.go @@ -67,13 +67,13 @@ func findPrevFuncName(m *gno.Machine, targetIndex int) string { panic("function name not found") } -func X_origSend(m *gno.Machine) (denoms []string, amounts []int64) { - os := GetContext(m).OrigSend +func X_originSend(m *gno.Machine) (denoms []string, amounts []int64) { + os := GetContext(m).OriginSend return ExpandCoins(os) } -func X_origCaller(m *gno.Machine) string { - return string(GetContext(m).OrigCaller) +func X_originCaller(m *gno.Machine) string { + return string(GetContext(m).OriginCaller) } func X_origPkgAddr(m *gno.Machine) string { @@ -95,9 +95,9 @@ func X_callerAt(m *gno.Machine, n int) string { return "" } if n == m.NumFrames() { - // This makes it consistent with OrigCaller. + // This makes it consistent with OriginCaller. ctx := GetContext(m) - return string(ctx.OrigCaller) + return string(ctx.OriginCaller) } return string(m.MustLastCallFrame(n).LastPackage.GetPkgAddr().Bech32()) } @@ -132,8 +132,8 @@ func X_getRealm(m *gno.Machine, height int) (address, pkgPath string) { } } - // Fallback case: return OrigCaller. - return string(ctx.OrigCaller), "" + // Fallback case: return OriginCaller. + return string(ctx.OriginCaller), "" } // currentRealm retrieves the current realm's address and pkgPath. diff --git a/gnovm/stdlibs/std/native_test.go b/gnovm/stdlibs/std/native_test.go index 851785575d7..ff86861647c 100644 --- a/gnovm/stdlibs/std/native_test.go +++ b/gnovm/stdlibs/std/native_test.go @@ -13,7 +13,7 @@ func TestPrevRealmIsOrigin(t *testing.T) { var ( user = gno.DerivePkgAddr("user1.gno").Bech32() ctx = ExecContext{ - OrigCaller: user, + OriginCaller: user, } msgCallFrame = &gno.Frame{LastPackage: &gno.PackageValue{PkgPath: "main"}} msgRunFrame = &gno.Frame{LastPackage: &gno.PackageValue{PkgPath: "gno.land/r/g1337/run"}} diff --git a/gnovm/tests/files/std2.gno b/gnovm/tests/files/std2.gno index 6698b9ae976..71865fdf97d 100644 --- a/gnovm/tests/files/std2.gno +++ b/gnovm/tests/files/std2.gno @@ -3,7 +3,7 @@ package main import "std" func main() { - caller := std.OrigCaller() + caller := std.OriginCaller() println(caller) } diff --git a/gnovm/tests/files/std3.gno b/gnovm/tests/files/std3.gno index d002a46b683..a95c7395664 100644 --- a/gnovm/tests/files/std3.gno +++ b/gnovm/tests/files/std3.gno @@ -6,8 +6,8 @@ import ( ) func main() { - caller := std.OrigCaller() - caller2 := std.OrigCaller() + caller := std.OriginCaller() + caller2 := std.OriginCaller() cmp := bytes.Compare([]byte(caller), []byte(caller2)) println(cmp) } diff --git a/gnovm/tests/files/zrealm_initctx.gno b/gnovm/tests/files/zrealm_initctx.gno index 2ff1034f9ae..4fc05a6e59c 100644 --- a/gnovm/tests/files/zrealm_initctx.gno +++ b/gnovm/tests/files/zrealm_initctx.gno @@ -10,8 +10,8 @@ var addr = std.Address("test") var addrInit = std.Address("addrInit") func init() { - addr = std.OrigCaller() - addrInit = tests.InitOrigCaller() + addr = std.OriginCaller() + addrInit = tests.InitOriginCaller() } func main() { diff --git a/gnovm/tests/files/zrealm_std0.gno b/gnovm/tests/files/zrealm_std0.gno index 02672de6937..fff6d6882b8 100644 --- a/gnovm/tests/files/zrealm_std0.gno +++ b/gnovm/tests/files/zrealm_std0.gno @@ -6,7 +6,7 @@ import ( ) func main() { - caller := std.OrigCaller() + caller := std.OriginCaller() println(caller) } diff --git a/gnovm/tests/files/zrealm_std1.gno b/gnovm/tests/files/zrealm_std1.gno index 01da962f949..8206b7b4097 100644 --- a/gnovm/tests/files/zrealm_std1.gno +++ b/gnovm/tests/files/zrealm_std1.gno @@ -8,14 +8,14 @@ import ( var aset *std.AddressList func init() { - caller := std.OrigCaller() + caller := std.OriginCaller() aset = std.NewAddressList() aset.AddAddress(caller) } func main() { println(*aset) - caller := std.OrigCaller() + caller := std.OriginCaller() err := aset.AddAddress(caller) println("error:", err) has := aset.HasAddress(caller) diff --git a/gnovm/tests/files/zrealm_std2.gno b/gnovm/tests/files/zrealm_std2.gno index 2d73e9c94c6..15d137bf556 100644 --- a/gnovm/tests/files/zrealm_std2.gno +++ b/gnovm/tests/files/zrealm_std2.gno @@ -9,14 +9,14 @@ import ( var aset std.AddressSet func init() { - caller := std.OrigCaller() + caller := std.OriginCaller() aset = std.NewAddressList() aset.AddAddress(caller) } func main() { println(*(aset.(*std.AddressList))) - caller := std.OrigCaller() + caller := std.OriginCaller() err := aset.AddAddress(caller) println("error:", err) has := aset.HasAddress(caller) diff --git a/gnovm/tests/files/zrealm_tests0.gno b/gnovm/tests/files/zrealm_tests0.gno index afb7e4a7c3b..a68139c3e21 100644 --- a/gnovm/tests/files/zrealm_tests0.gno +++ b/gnovm/tests/files/zrealm_tests0.gno @@ -880,7 +880,7 @@ func main() { // }, // "FileName": "tests.gno", // "IsMethod": false, -// "Name": "InitOrigCaller", +// "Name": "InitOriginCaller", // "NativeName": "", // "NativePkg": "", // "PkgPath": "gno.land/r/demo/tests", diff --git a/gnovm/tests/stdlibs/generated.go b/gnovm/tests/stdlibs/generated.go index db5ecdec05d..9bce3c5f08b 100644 --- a/gnovm/tests/stdlibs/generated.go +++ b/gnovm/tests/stdlibs/generated.go @@ -117,7 +117,7 @@ var nativeFuncs = [...]NativeFunc{ }, { "std", - "testSetOrigCaller", + "testSetOriginCaller", []gno.FieldTypeExpr{ {Name: gno.N("p0"), Type: gno.X("string")}, }, @@ -132,7 +132,7 @@ var nativeFuncs = [...]NativeFunc{ gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - testlibs_std.X_testSetOrigCaller( + testlibs_std.X_testSetOriginCaller( m, p0) }, @@ -187,7 +187,7 @@ var nativeFuncs = [...]NativeFunc{ }, { "std", - "testSetOrigSend", + "testSetOriginSend", []gno.FieldTypeExpr{ {Name: gno.N("p0"), Type: gno.X("[]string")}, {Name: gno.N("p1"), Type: gno.X("[]int64")}, @@ -214,7 +214,7 @@ var nativeFuncs = [...]NativeFunc{ gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 2, "")).TV, rp2) gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 3, "")).TV, rp3) - testlibs_std.X_testSetOrigSend( + testlibs_std.X_testSetOriginSend( m, p0, p1, p2, p3) }, diff --git a/gnovm/tests/stdlibs/std/std.gno b/gnovm/tests/stdlibs/std/std.gno index f2d67bef40d..b1d85822d3c 100644 --- a/gnovm/tests/stdlibs/std/std.gno +++ b/gnovm/tests/stdlibs/std/std.gno @@ -4,7 +4,7 @@ func AssertOriginCall() // injected func IsOriginCall() bool // injected func TestSkipHeights(count int64) // injected -func TestSetOrigCaller(addr Address) { testSetOrigCaller(string(addr)) } +func TestSetOriginCaller(addr Address) { testSetOriginCaller(string(addr)) } func TestSetOrigPkgAddr(addr Address) { testSetOrigPkgAddr(string(addr)) } // TestSetRealm sets the realm for the current frame. @@ -14,10 +14,10 @@ func TestSetRealm(rlm Realm) { testSetRealm(string(rlm.addr), rlm.pkgPath) } -func TestSetOrigSend(sent, spent Coins) { +func TestSetOriginSend(sent, spent Coins) { sentDenom, sentAmt := sent.expandNative() spentDenom, spentAmt := spent.expandNative() - testSetOrigSend(sentDenom, sentAmt, spentDenom, spentAmt) + testSetOriginSend(sentDenom, sentAmt, spentDenom, spentAmt) } func TestIssueCoins(addr Address, coins Coins) { @@ -29,10 +29,10 @@ func TestIssueCoins(addr Address, coins Coins) { func callerAt(n int) string // native bindings -func testSetOrigCaller(s string) +func testSetOriginCaller(s string) func testSetOrigPkgAddr(s string) func testSetRealm(addr, pkgPath string) -func testSetOrigSend( +func testSetOriginSend( sentDenom []string, sentAmt []int64, spentDenom []string, spentAmt []int64) func testIssueCoins(addr string, denom []string, amt []int64) diff --git a/gnovm/tests/stdlibs/std/std.go b/gnovm/tests/stdlibs/std/std.go index cd2ac4fae66..49daeb899de 100644 --- a/gnovm/tests/stdlibs/std/std.go +++ b/gnovm/tests/stdlibs/std/std.go @@ -84,16 +84,16 @@ func X_callerAt(m *gno.Machine, n int) string { return "" } if n == m.NumFrames()-1 { - // This makes it consistent with OrigCaller and TestSetOrigCaller. + // This makes it consistent with OriginCaller and TestSetOriginCaller. ctx := m.Context.(*TestExecContext) - return string(ctx.OrigCaller) + return string(ctx.OriginCaller) } return string(m.MustLastCallFrame(n).LastPackage.GetPkgAddr().Bech32()) } -func X_testSetOrigCaller(m *gno.Machine, addr string) { +func X_testSetOriginCaller(m *gno.Machine, addr string) { ctx := m.Context.(*TestExecContext) - ctx.OrigCaller = crypto.Bech32Address(addr) + ctx.OriginCaller = crypto.Bech32Address(addr) m.Context = ctx } @@ -160,22 +160,22 @@ func X_getRealm(m *gno.Machine, height int) (address string, pkgPath string) { } } - // Fallback case: return OrigCaller. - return string(ctx.OrigCaller), "" + // Fallback case: return OriginCaller. + return string(ctx.OriginCaller), "" } func X_isRealm(m *gno.Machine, pkgPath string) bool { return gno.IsRealmPath(pkgPath) } -func X_testSetOrigSend(m *gno.Machine, +func X_testSetOriginSend(m *gno.Machine, sentDenom []string, sentAmt []int64, spentDenom []string, spentAmt []int64, ) { ctx := m.Context.(*TestExecContext) - ctx.OrigSend = std.CompactCoins(sentDenom, sentAmt) + ctx.OriginSend = std.CompactCoins(sentDenom, sentAmt) spent := std.CompactCoins(spentDenom, spentAmt) - ctx.OrigSendSpent = &spent + ctx.OriginSendSpent = &spent m.Context = ctx } diff --git a/misc/deployments/test5.gno.land/genesis_txs.jsonl b/misc/deployments/test5.gno.land/genesis_txs.jsonl index 744c82d6abe..80488b98e90 100755 --- a/misc/deployments/test5.gno.land/genesis_txs.jsonl +++ b/misc/deployments/test5.gno.land/genesis_txs.jsonl @@ -24,49 +24,49 @@ {"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"feed","path":"gno.land/p/demo/gnorkle/feed","files":[{"name":"errors.gno","body":"package feed\n\nimport \"errors\"\n\nvar ErrUndefined = errors.New(\"undefined feed\")\n"},{"name":"task.gno","body":"package feed\n\n// Task is a unit of work that can be part of a `Feed` definition. Tasks\n// are executed by agents.\ntype Task interface {\n\tMarshalJSON() ([]byte, error)\n}\n"},{"name":"type.gno","body":"package feed\n\n// Type indicates the type of a feed.\ntype Type int\n\nconst (\n\t// TypeStatic indicates a feed cannot be changed once the first value is committed.\n\tTypeStatic Type = iota\n\t// TypeContinuous indicates a feed can continuously ingest values and will publish\n\t// a new value on request using the values it has ingested.\n\tTypeContinuous\n\t// TypePeriodic indicates a feed can accept one or more values within a certain period\n\t// and will proceed to commit these values at the end up each period to produce an\n\t// aggregate value before starting a new period.\n\tTypePeriodic\n)\n"},{"name":"value.gno","body":"package feed\n\nimport \"time\"\n\n// Value represents a value published by a feed. The `Time` is when the value was published.\ntype Value struct {\n\tString string\n\tTime time.Time\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":"ingester","path":"gno.land/p/demo/gnorkle/ingester","files":[{"name":"errors.gno","body":"package ingester\n\nimport \"errors\"\n\nvar ErrUndefined = errors.New(\"ingester undefined\")\n"},{"name":"type.gno","body":"package ingester\n\n// Type indicates an ingester type.\ntype Type int\n\nconst (\n\t// TypeSingle indicates an ingester that can only ingest a single within a given period or no period.\n\tTypeSingle Type = iota\n\t// TypeMulti indicates an ingester that can ingest multiple within a given period or no period\n\tTypeMulti\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":"message","path":"gno.land/p/demo/gnorkle/message","files":[{"name":"parse.gno","body":"package message\n\nimport \"strings\"\n\n// ParseFunc parses a raw message and returns the message function\n// type extracted from the remainder of the message.\nfunc ParseFunc(rawMsg string) (FuncType, string) {\n\tfuncType, remainder := parseFirstToken(rawMsg)\n\treturn FuncType(funcType), remainder\n}\n\n// ParseID parses a raw message and returns the ID extracted from\n// the remainder of the message.\nfunc ParseID(rawMsg string) (string, string) {\n\treturn parseFirstToken(rawMsg)\n}\n\nfunc parseFirstToken(rawMsg string) (string, string) {\n\tmsgParts := strings.SplitN(rawMsg, \",\", 2)\n\tif len(msgParts) \u003c 2 {\n\t\treturn msgParts[0], \"\"\n\t}\n\n\treturn msgParts[0], msgParts[1]\n}\n"},{"name":"parse_test.gno","body":"package message_test\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/gnorkle/message\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestParseFunc(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput string\n\t\texpFuncType message.FuncType\n\t\texpRemainder string\n\t}{\n\t\t{\n\t\t\tname: \"empty\",\n\t\t},\n\t\t{\n\t\t\tname: \"func only\",\n\t\t\tinput: \"ingest\",\n\t\t\texpFuncType: message.FuncTypeIngest,\n\t\t},\n\t\t{\n\t\t\tname: \"func with short remainder\",\n\t\t\tinput: \"commit,asdf\",\n\t\t\texpFuncType: message.FuncTypeCommit,\n\t\t\texpRemainder: \"asdf\",\n\t\t},\n\t\t{\n\t\t\tname: \"func with long remainder\",\n\t\t\tinput: \"request,hello,world,goodbye\",\n\t\t\texpFuncType: message.FuncTypeRequest,\n\t\t\texpRemainder: \"hello,world,goodbye\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tfuncType, remainder := message.ParseFunc(tt.input)\n\n\t\t\tuassert.Equal(t, string(tt.expFuncType), string(funcType))\n\t\t\tuassert.Equal(t, tt.expRemainder, remainder)\n\t\t})\n\t}\n}\n"},{"name":"type.gno","body":"package message\n\n// FuncType is the type of function that is being called by the agent.\ntype FuncType string\n\nconst (\n\t// FuncTypeIngest means the agent is sending data for ingestion.\n\tFuncTypeIngest FuncType = \"ingest\"\n\t// FuncTypeCommit means the agent is requesting a feed commit the transitive data\n\t// being held by its ingester.\n\tFuncTypeCommit FuncType = \"commit\"\n\t// FuncTypeRequest means the agent is requesting feed definitions for all those\n\t// that it is whitelisted to provide data for.\n\tFuncTypeRequest FuncType = \"request\"\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":"gnorkle","path":"gno.land/p/demo/gnorkle/gnorkle","files":[{"name":"feed.gno","body":"package gnorkle\n\nimport (\n\t\"gno.land/p/demo/gnorkle/feed\"\n\t\"gno.land/p/demo/gnorkle/message\"\n)\n\n// Feed is an abstraction used by a gnorkle `Instance` to ingest data from\n// agents and provide data feeds to consumers.\ntype Feed interface {\n\tID() string\n\tType() feed.Type\n\tValue() (value feed.Value, dataType string, consumable bool)\n\tIngest(funcType message.FuncType, rawMessage, providerAddress string) error\n\tMarshalJSON() ([]byte, error)\n\tTasks() []feed.Task\n\tIsActive() bool\n}\n\n// FeedWithWhitelist associates a `Whitelist` with a `Feed`.\ntype FeedWithWhitelist struct {\n\tFeed\n\tWhitelist\n}\n"},{"name":"ingester.gno","body":"package gnorkle\n\nimport \"gno.land/p/demo/gnorkle/ingester\"\n\n// Ingester is the abstraction that allows a `Feed` to ingest data from agents\n// and commit it to storage using zero or more intermediate aggregation steps.\ntype Ingester interface {\n\tType() ingester.Type\n\tIngest(value, providerAddress string) (canAutoCommit bool, err error)\n\tCommitValue(storage Storage, providerAddress string) error\n}\n"},{"name":"instance.gno","body":"package gnorkle\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/gnorkle/agent\"\n\t\"gno.land/p/demo/gnorkle/feed\"\n\t\"gno.land/p/demo/gnorkle/message\"\n)\n\n// Instance is a single instance of an oracle.\ntype Instance struct {\n\tfeeds *avl.Tree\n\twhitelist agent.Whitelist\n}\n\n// NewInstance creates a new instance of an oracle.\nfunc NewInstance() *Instance {\n\treturn \u0026Instance{\n\t\tfeeds: avl.NewTree(),\n\t}\n}\n\nfunc assertValidID(id string) error {\n\tif len(id) == 0 {\n\t\treturn errors.New(\"feed ids cannot be empty\")\n\t}\n\n\tif strings.Contains(id, \",\") {\n\t\treturn errors.New(\"feed ids cannot contain commas\")\n\t}\n\n\treturn nil\n}\n\nfunc (i *Instance) assertFeedDoesNotExist(id string) error {\n\tif i.feeds.Has(id) {\n\t\treturn errors.New(\"feed already exists\")\n\t}\n\n\treturn nil\n}\n\n// AddFeeds adds feeds to the instance with empty whitelists.\nfunc (i *Instance) AddFeeds(feeds ...Feed) error {\n\tfor _, feed := range feeds {\n\t\tif err := assertValidID(feed.ID()); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err := i.assertFeedDoesNotExist(feed.ID()); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\ti.feeds.Set(\n\t\t\tfeed.ID(),\n\t\t\tFeedWithWhitelist{\n\t\t\t\tWhitelist: new(agent.Whitelist),\n\t\t\t\tFeed: feed,\n\t\t\t},\n\t\t)\n\t}\n\n\treturn nil\n}\n\n// AddFeedsWithWhitelists adds feeds to the instance with the given whitelists.\nfunc (i *Instance) AddFeedsWithWhitelists(feeds ...FeedWithWhitelist) error {\n\tfor _, feed := range feeds {\n\t\tif err := i.assertFeedDoesNotExist(feed.ID()); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := assertValidID(feed.ID()); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\ti.feeds.Set(\n\t\t\tfeed.ID(),\n\t\t\tFeedWithWhitelist{\n\t\t\t\tWhitelist: feed.Whitelist,\n\t\t\t\tFeed: feed,\n\t\t\t},\n\t\t)\n\t}\n\n\treturn nil\n}\n\n// RemoveFeed removes a feed from the instance.\nfunc (i *Instance) RemoveFeed(id string) {\n\ti.feeds.Remove(id)\n}\n\n// PostMessageHandler is a type that allows for post-processing of feed state after a feed\n// ingests a message from an agent.\ntype PostMessageHandler interface {\n\tHandle(i *Instance, funcType message.FuncType, feed Feed) error\n}\n\n// HandleMessage handles a message from an agent and routes to either the logic that returns\n// feed definitions or the logic that allows a feed to ingest a message.\n//\n// TODO: Consider further message types that could allow administrative action such as modifying\n// a feed's whitelist without the owner of this oracle having to maintain a reference to it.\nfunc (i *Instance) HandleMessage(msg string, postHandler PostMessageHandler) (string, error) {\n\tcaller := string(std.OrigCaller())\n\n\tfuncType, msg := message.ParseFunc(msg)\n\n\tswitch funcType {\n\tcase message.FuncTypeRequest:\n\t\treturn i.GetFeedDefinitions(caller)\n\n\tdefault:\n\t\tid, msg := message.ParseID(msg)\n\t\tif err := assertValidID(id); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\tfeedWithWhitelist, err := i.getFeedWithWhitelist(id)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\tif !addressIsWhitelisted(\u0026i.whitelist, feedWithWhitelist, caller, nil) {\n\t\t\treturn \"\", errors.New(\"caller not whitelisted\")\n\t\t}\n\n\t\tif err := feedWithWhitelist.Ingest(funcType, msg, caller); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\tif postHandler != nil {\n\t\t\tpostHandler.Handle(i, funcType, feedWithWhitelist)\n\t\t}\n\t}\n\n\treturn \"\", nil\n}\n\nfunc (i *Instance) getFeed(id string) (Feed, error) {\n\tuntypedFeed, ok := i.feeds.Get(id)\n\tif !ok {\n\t\treturn nil, errors.New(\"invalid ingest id: \" + id)\n\t}\n\n\tfeed, ok := untypedFeed.(Feed)\n\tif !ok {\n\t\treturn nil, errors.New(\"invalid feed type\")\n\t}\n\n\treturn feed, nil\n}\n\nfunc (i *Instance) getFeedWithWhitelist(id string) (FeedWithWhitelist, error) {\n\tuntypedFeedWithWhitelist, ok := i.feeds.Get(id)\n\tif !ok {\n\t\treturn FeedWithWhitelist{}, errors.New(\"invalid ingest id: \" + id)\n\t}\n\n\tfeedWithWhitelist, ok := untypedFeedWithWhitelist.(FeedWithWhitelist)\n\tif !ok {\n\t\treturn FeedWithWhitelist{}, errors.New(\"invalid feed with whitelist type\")\n\t}\n\n\treturn feedWithWhitelist, nil\n}\n\n// GetFeedValue returns the most recently published value of a feed along with a string\n// representation of the value's type and boolean indicating whether the value is\n// okay for consumption.\nfunc (i *Instance) GetFeedValue(id string) (feed.Value, string, bool, error) {\n\tfoundFeed, err := i.getFeed(id)\n\tif err != nil {\n\t\treturn feed.Value{}, \"\", false, err\n\t}\n\n\tvalue, valueType, consumable := foundFeed.Value()\n\treturn value, valueType, consumable, nil\n}\n\n// GetFeedDefinitions returns a JSON string representing the feed definitions for which the given\n// agent address is whitelisted to provide values for ingestion.\nfunc (i *Instance) GetFeedDefinitions(forAddress string) (string, error) {\n\tinstanceHasAddressWhitelisted := !i.whitelist.HasDefinition() || i.whitelist.HasAddress(forAddress)\n\n\tbuf := new(strings.Builder)\n\tbuf.WriteString(\"[\")\n\tfirst := true\n\tvar err error\n\n\t// The boolean value returned by this callback function indicates whether to stop iterating.\n\ti.feeds.Iterate(\"\", \"\", func(_ string, value interface{}) bool {\n\t\tfeedWithWhitelist, ok := value.(FeedWithWhitelist)\n\t\tif !ok {\n\t\t\terr = errors.New(\"invalid feed type\")\n\t\t\treturn true\n\t\t}\n\n\t\t// Don't give agents the ability to try to publish to inactive feeds.\n\t\tif !feedWithWhitelist.IsActive() {\n\t\t\treturn false\n\t\t}\n\n\t\t// Skip feeds the address is not whitelisted for.\n\t\tif !addressIsWhitelisted(\u0026i.whitelist, feedWithWhitelist, forAddress, \u0026instanceHasAddressWhitelisted) {\n\t\t\treturn false\n\t\t}\n\n\t\tvar taskBytes []byte\n\t\tif taskBytes, err = feedWithWhitelist.Feed.MarshalJSON(); err != nil {\n\t\t\treturn true\n\t\t}\n\n\t\t// Guard against any tasks that shouldn't be returned; maybe they are not active because they have\n\t\t// already been completed.\n\t\tif len(taskBytes) == 0 {\n\t\t\treturn false\n\t\t}\n\n\t\tif !first {\n\t\t\tbuf.WriteString(\",\")\n\t\t}\n\n\t\tfirst = false\n\t\tbuf.Write(taskBytes)\n\t\treturn false\n\t})\n\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tbuf.WriteString(\"]\")\n\treturn buf.String(), nil\n}\n"},{"name":"storage.gno","body":"package gnorkle\n\nimport \"gno.land/p/demo/gnorkle/feed\"\n\n// Storage defines how published feed values should be read\n// and written.\ntype Storage interface {\n\tPut(value string) error\n\tGetLatest() feed.Value\n\tGetHistory() []feed.Value\n}\n"},{"name":"whitelist.gno","body":"package gnorkle\n\n// Whitelist is used to manage which agents are allowed to interact.\ntype Whitelist interface {\n\tClearAddresses()\n\tAddAddresses(addresses []string)\n\tRemoveAddress(address string)\n\tHasDefinition() bool\n\tHasAddress(address string) bool\n}\n\n// ClearWhitelist clears the whitelist of the instance or feed depending on the feed ID.\nfunc (i *Instance) ClearWhitelist(feedID string) error {\n\tif feedID == \"\" {\n\t\ti.whitelist.ClearAddresses()\n\t\treturn nil\n\t}\n\n\tfeedWithWhitelist, err := i.getFeedWithWhitelist(feedID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfeedWithWhitelist.ClearAddresses()\n\treturn nil\n}\n\n// AddToWhitelist adds the given addresses to the whitelist of the instance or feed depending on the feed ID.\nfunc (i *Instance) AddToWhitelist(feedID string, addresses []string) error {\n\tif feedID == \"\" {\n\t\ti.whitelist.AddAddresses(addresses)\n\t\treturn nil\n\t}\n\n\tfeedWithWhitelist, err := i.getFeedWithWhitelist(feedID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfeedWithWhitelist.AddAddresses(addresses)\n\treturn nil\n}\n\n// RemoveFromWhitelist removes the given address from the whitelist of the instance or feed depending on the feed ID.\nfunc (i *Instance) RemoveFromWhitelist(feedID string, address string) error {\n\tif feedID == \"\" {\n\t\ti.whitelist.RemoveAddress(address)\n\t\treturn nil\n\t}\n\n\tfeedWithWhitelist, err := i.getFeedWithWhitelist(feedID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfeedWithWhitelist.RemoveAddress(address)\n\treturn nil\n}\n\n// addressWhiteListed returns true if:\n// - the feed has a white list and the address is whitelisted, or\n// - the feed has no white list and the instance has a white list and the address is whitelisted, or\n// - the feed has no white list and the instance has no white list.\nfunc addressIsWhitelisted(instanceWhitelist, feedWhitelist Whitelist, address string, instanceWhitelistedOverride *bool) bool {\n\t// A feed whitelist takes priority, so it will return false if the feed has a whitelist and the caller is\n\t// not a part of it. An empty whitelist defers to the instance whitelist.\n\tif feedWhitelist != nil {\n\t\tif feedWhitelist.HasDefinition() \u0026\u0026 !feedWhitelist.HasAddress(address) {\n\t\t\treturn false\n\t\t}\n\n\t\t// Getting to this point means that one of the following is true:\n\t\t// - the feed has no defined whitelist (so it can't possibly have the address whitelisted)\n\t\t// - the feed has a defined whitelist and the caller is a part of it\n\t\t//\n\t\t// In this case, we can be sure that the boolean indicating whether the feed has this address whitelisted\n\t\t// is equivalent to the boolean indicating whether the feed has a defined whitelist.\n\t\tif feedWhitelist.HasDefinition() {\n\t\t\treturn true\n\t\t}\n\t}\n\n\tif instanceWhitelistedOverride != nil {\n\t\treturn *instanceWhitelistedOverride\n\t}\n\n\t// We were unable able to determine whether this address is allowed after looking at the feed whitelist,\n\t// so fall back to the instance whitelist. A complete absence of values in the instance whitelist means\n\t// that the instance has no whitelist so we can return true because everything is allowed by default.\n\tif instanceWhitelist == nil || !instanceWhitelist.HasDefinition() {\n\t\treturn true\n\t}\n\n\t// The instance whitelist is defined so if the address is present then it is allowed.\n\treturn instanceWhitelist.HasAddress(address)\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":"gnorkle","path":"gno.land/p/demo/gnorkle/gnorkle","files":[{"name":"feed.gno","body":"package gnorkle\n\nimport (\n\t\"gno.land/p/demo/gnorkle/feed\"\n\t\"gno.land/p/demo/gnorkle/message\"\n)\n\n// Feed is an abstraction used by a gnorkle `Instance` to ingest data from\n// agents and provide data feeds to consumers.\ntype Feed interface {\n\tID() string\n\tType() feed.Type\n\tValue() (value feed.Value, dataType string, consumable bool)\n\tIngest(funcType message.FuncType, rawMessage, providerAddress string) error\n\tMarshalJSON() ([]byte, error)\n\tTasks() []feed.Task\n\tIsActive() bool\n}\n\n// FeedWithWhitelist associates a `Whitelist` with a `Feed`.\ntype FeedWithWhitelist struct {\n\tFeed\n\tWhitelist\n}\n"},{"name":"ingester.gno","body":"package gnorkle\n\nimport \"gno.land/p/demo/gnorkle/ingester\"\n\n// Ingester is the abstraction that allows a `Feed` to ingest data from agents\n// and commit it to storage using zero or more intermediate aggregation steps.\ntype Ingester interface {\n\tType() ingester.Type\n\tIngest(value, providerAddress string) (canAutoCommit bool, err error)\n\tCommitValue(storage Storage, providerAddress string) error\n}\n"},{"name":"instance.gno","body":"package gnorkle\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/gnorkle/agent\"\n\t\"gno.land/p/demo/gnorkle/feed\"\n\t\"gno.land/p/demo/gnorkle/message\"\n)\n\n// Instance is a single instance of an oracle.\ntype Instance struct {\n\tfeeds *avl.Tree\n\twhitelist agent.Whitelist\n}\n\n// NewInstance creates a new instance of an oracle.\nfunc NewInstance() *Instance {\n\treturn \u0026Instance{\n\t\tfeeds: avl.NewTree(),\n\t}\n}\n\nfunc assertValidID(id string) error {\n\tif len(id) == 0 {\n\t\treturn errors.New(\"feed ids cannot be empty\")\n\t}\n\n\tif strings.Contains(id, \",\") {\n\t\treturn errors.New(\"feed ids cannot contain commas\")\n\t}\n\n\treturn nil\n}\n\nfunc (i *Instance) assertFeedDoesNotExist(id string) error {\n\tif i.feeds.Has(id) {\n\t\treturn errors.New(\"feed already exists\")\n\t}\n\n\treturn nil\n}\n\n// AddFeeds adds feeds to the instance with empty whitelists.\nfunc (i *Instance) AddFeeds(feeds ...Feed) error {\n\tfor _, feed := range feeds {\n\t\tif err := assertValidID(feed.ID()); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err := i.assertFeedDoesNotExist(feed.ID()); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\ti.feeds.Set(\n\t\t\tfeed.ID(),\n\t\t\tFeedWithWhitelist{\n\t\t\t\tWhitelist: new(agent.Whitelist),\n\t\t\t\tFeed: feed,\n\t\t\t},\n\t\t)\n\t}\n\n\treturn nil\n}\n\n// AddFeedsWithWhitelists adds feeds to the instance with the given whitelists.\nfunc (i *Instance) AddFeedsWithWhitelists(feeds ...FeedWithWhitelist) error {\n\tfor _, feed := range feeds {\n\t\tif err := i.assertFeedDoesNotExist(feed.ID()); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := assertValidID(feed.ID()); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\ti.feeds.Set(\n\t\t\tfeed.ID(),\n\t\t\tFeedWithWhitelist{\n\t\t\t\tWhitelist: feed.Whitelist,\n\t\t\t\tFeed: feed,\n\t\t\t},\n\t\t)\n\t}\n\n\treturn nil\n}\n\n// RemoveFeed removes a feed from the instance.\nfunc (i *Instance) RemoveFeed(id string) {\n\ti.feeds.Remove(id)\n}\n\n// PostMessageHandler is a type that allows for post-processing of feed state after a feed\n// ingests a message from an agent.\ntype PostMessageHandler interface {\n\tHandle(i *Instance, funcType message.FuncType, feed Feed) error\n}\n\n// HandleMessage handles a message from an agent and routes to either the logic that returns\n// feed definitions or the logic that allows a feed to ingest a message.\n//\n// TODO: Consider further message types that could allow administrative action such as modifying\n// a feed's whitelist without the owner of this oracle having to maintain a reference to it.\nfunc (i *Instance) HandleMessage(msg string, postHandler PostMessageHandler) (string, error) {\n\tcaller := string(std.OriginCaller())\n\n\tfuncType, msg := message.ParseFunc(msg)\n\n\tswitch funcType {\n\tcase message.FuncTypeRequest:\n\t\treturn i.GetFeedDefinitions(caller)\n\n\tdefault:\n\t\tid, msg := message.ParseID(msg)\n\t\tif err := assertValidID(id); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\tfeedWithWhitelist, err := i.getFeedWithWhitelist(id)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\tif !addressIsWhitelisted(\u0026i.whitelist, feedWithWhitelist, caller, nil) {\n\t\t\treturn \"\", errors.New(\"caller not whitelisted\")\n\t\t}\n\n\t\tif err := feedWithWhitelist.Ingest(funcType, msg, caller); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\tif postHandler != nil {\n\t\t\tpostHandler.Handle(i, funcType, feedWithWhitelist)\n\t\t}\n\t}\n\n\treturn \"\", nil\n}\n\nfunc (i *Instance) getFeed(id string) (Feed, error) {\n\tuntypedFeed, ok := i.feeds.Get(id)\n\tif !ok {\n\t\treturn nil, errors.New(\"invalid ingest id: \" + id)\n\t}\n\n\tfeed, ok := untypedFeed.(Feed)\n\tif !ok {\n\t\treturn nil, errors.New(\"invalid feed type\")\n\t}\n\n\treturn feed, nil\n}\n\nfunc (i *Instance) getFeedWithWhitelist(id string) (FeedWithWhitelist, error) {\n\tuntypedFeedWithWhitelist, ok := i.feeds.Get(id)\n\tif !ok {\n\t\treturn FeedWithWhitelist{}, errors.New(\"invalid ingest id: \" + id)\n\t}\n\n\tfeedWithWhitelist, ok := untypedFeedWithWhitelist.(FeedWithWhitelist)\n\tif !ok {\n\t\treturn FeedWithWhitelist{}, errors.New(\"invalid feed with whitelist type\")\n\t}\n\n\treturn feedWithWhitelist, nil\n}\n\n// GetFeedValue returns the most recently published value of a feed along with a string\n// representation of the value's type and boolean indicating whether the value is\n// okay for consumption.\nfunc (i *Instance) GetFeedValue(id string) (feed.Value, string, bool, error) {\n\tfoundFeed, err := i.getFeed(id)\n\tif err != nil {\n\t\treturn feed.Value{}, \"\", false, err\n\t}\n\n\tvalue, valueType, consumable := foundFeed.Value()\n\treturn value, valueType, consumable, nil\n}\n\n// GetFeedDefinitions returns a JSON string representing the feed definitions for which the given\n// agent address is whitelisted to provide values for ingestion.\nfunc (i *Instance) GetFeedDefinitions(forAddress string) (string, error) {\n\tinstanceHasAddressWhitelisted := !i.whitelist.HasDefinition() || i.whitelist.HasAddress(forAddress)\n\n\tbuf := new(strings.Builder)\n\tbuf.WriteString(\"[\")\n\tfirst := true\n\tvar err error\n\n\t// The boolean value returned by this callback function indicates whether to stop iterating.\n\ti.feeds.Iterate(\"\", \"\", func(_ string, value interface{}) bool {\n\t\tfeedWithWhitelist, ok := value.(FeedWithWhitelist)\n\t\tif !ok {\n\t\t\terr = errors.New(\"invalid feed type\")\n\t\t\treturn true\n\t\t}\n\n\t\t// Don't give agents the ability to try to publish to inactive feeds.\n\t\tif !feedWithWhitelist.IsActive() {\n\t\t\treturn false\n\t\t}\n\n\t\t// Skip feeds the address is not whitelisted for.\n\t\tif !addressIsWhitelisted(\u0026i.whitelist, feedWithWhitelist, forAddress, \u0026instanceHasAddressWhitelisted) {\n\t\t\treturn false\n\t\t}\n\n\t\tvar taskBytes []byte\n\t\tif taskBytes, err = feedWithWhitelist.Feed.MarshalJSON(); err != nil {\n\t\t\treturn true\n\t\t}\n\n\t\t// Guard against any tasks that shouldn't be returned; maybe they are not active because they have\n\t\t// already been completed.\n\t\tif len(taskBytes) == 0 {\n\t\t\treturn false\n\t\t}\n\n\t\tif !first {\n\t\t\tbuf.WriteString(\",\")\n\t\t}\n\n\t\tfirst = false\n\t\tbuf.Write(taskBytes)\n\t\treturn false\n\t})\n\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tbuf.WriteString(\"]\")\n\treturn buf.String(), nil\n}\n"},{"name":"storage.gno","body":"package gnorkle\n\nimport \"gno.land/p/demo/gnorkle/feed\"\n\n// Storage defines how published feed values should be read\n// and written.\ntype Storage interface {\n\tPut(value string) error\n\tGetLatest() feed.Value\n\tGetHistory() []feed.Value\n}\n"},{"name":"whitelist.gno","body":"package gnorkle\n\n// Whitelist is used to manage which agents are allowed to interact.\ntype Whitelist interface {\n\tClearAddresses()\n\tAddAddresses(addresses []string)\n\tRemoveAddress(address string)\n\tHasDefinition() bool\n\tHasAddress(address string) bool\n}\n\n// ClearWhitelist clears the whitelist of the instance or feed depending on the feed ID.\nfunc (i *Instance) ClearWhitelist(feedID string) error {\n\tif feedID == \"\" {\n\t\ti.whitelist.ClearAddresses()\n\t\treturn nil\n\t}\n\n\tfeedWithWhitelist, err := i.getFeedWithWhitelist(feedID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfeedWithWhitelist.ClearAddresses()\n\treturn nil\n}\n\n// AddToWhitelist adds the given addresses to the whitelist of the instance or feed depending on the feed ID.\nfunc (i *Instance) AddToWhitelist(feedID string, addresses []string) error {\n\tif feedID == \"\" {\n\t\ti.whitelist.AddAddresses(addresses)\n\t\treturn nil\n\t}\n\n\tfeedWithWhitelist, err := i.getFeedWithWhitelist(feedID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfeedWithWhitelist.AddAddresses(addresses)\n\treturn nil\n}\n\n// RemoveFromWhitelist removes the given address from the whitelist of the instance or feed depending on the feed ID.\nfunc (i *Instance) RemoveFromWhitelist(feedID string, address string) error {\n\tif feedID == \"\" {\n\t\ti.whitelist.RemoveAddress(address)\n\t\treturn nil\n\t}\n\n\tfeedWithWhitelist, err := i.getFeedWithWhitelist(feedID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfeedWithWhitelist.RemoveAddress(address)\n\treturn nil\n}\n\n// addressWhiteListed returns true if:\n// - the feed has a white list and the address is whitelisted, or\n// - the feed has no white list and the instance has a white list and the address is whitelisted, or\n// - the feed has no white list and the instance has no white list.\nfunc addressIsWhitelisted(instanceWhitelist, feedWhitelist Whitelist, address string, instanceWhitelistedOverride *bool) bool {\n\t// A feed whitelist takes priority, so it will return false if the feed has a whitelist and the caller is\n\t// not a part of it. An empty whitelist defers to the instance whitelist.\n\tif feedWhitelist != nil {\n\t\tif feedWhitelist.HasDefinition() \u0026\u0026 !feedWhitelist.HasAddress(address) {\n\t\t\treturn false\n\t\t}\n\n\t\t// Getting to this point means that one of the following is true:\n\t\t// - the feed has no defined whitelist (so it can't possibly have the address whitelisted)\n\t\t// - the feed has a defined whitelist and the caller is a part of it\n\t\t//\n\t\t// In this case, we can be sure that the boolean indicating whether the feed has this address whitelisted\n\t\t// is equivalent to the boolean indicating whether the feed has a defined whitelist.\n\t\tif feedWhitelist.HasDefinition() {\n\t\t\treturn true\n\t\t}\n\t}\n\n\tif instanceWhitelistedOverride != nil {\n\t\treturn *instanceWhitelistedOverride\n\t}\n\n\t// We were unable able to determine whether this address is allowed after looking at the feed whitelist,\n\t// so fall back to the instance whitelist. A complete absence of values in the instance whitelist means\n\t// that the instance has no whitelist so we can return true because everything is allowed by default.\n\tif instanceWhitelist == nil || !instanceWhitelist.HasDefinition() {\n\t\treturn true\n\t}\n\n\t// The instance whitelist is defined so if the address is present then it is allowed.\n\treturn instanceWhitelist.HasAddress(address)\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":"storage","path":"gno.land/p/demo/gnorkle/storage","files":[{"name":"errors.gno","body":"package storage\n\nimport \"errors\"\n\nvar ErrUndefined = errors.New(\"undefined storage\")\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":"simple","path":"gno.land/p/demo/gnorkle/storage/simple","files":[{"name":"storage.gno","body":"package simple\n\nimport (\n\t\"time\"\n\n\t\"gno.land/p/demo/gnorkle/feed\"\n\t\"gno.land/p/demo/gnorkle/storage\"\n)\n\n// Storage is simple, bounded storage for published feed values.\ntype Storage struct {\n\tvalues []feed.Value\n\tmaxValues uint\n}\n\n// NewStorage creates a new Storage with the given maximum number of values.\n// If maxValues is 0, the storage is bounded to a size of one. If this is not desirable,\n// then don't provide a value of 0.\nfunc NewStorage(maxValues uint) *Storage {\n\tif maxValues == 0 {\n\t\tmaxValues = 1\n\t}\n\n\treturn \u0026Storage{\n\t\tmaxValues: maxValues,\n\t}\n}\n\n// Put adds a new value to the storage. If the storage is full, the oldest value\n// is removed. If maxValues is 0, the storage is bounded to a size of one.\nfunc (s *Storage) Put(value string) error {\n\tif s == nil {\n\t\treturn storage.ErrUndefined\n\t}\n\n\ts.values = append(s.values, feed.Value{String: value, Time: time.Now()})\n\tif uint(len(s.values)) \u003e s.maxValues {\n\t\ts.values = s.values[1:]\n\t}\n\n\treturn nil\n}\n\n// GetLatest returns the most recently added value, or an empty value if none exist.\nfunc (s Storage) GetLatest() feed.Value {\n\tif len(s.values) == 0 {\n\t\treturn feed.Value{}\n\t}\n\n\treturn s.values[len(s.values)-1]\n}\n\n// GetHistory returns all values in the storage, from oldest to newest.\nfunc (s Storage) GetHistory() []feed.Value {\n\treturn s.values\n}\n"},{"name":"storage_test.gno","body":"package simple_test\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/gnorkle/storage\"\n\t\"gno.land/p/demo/gnorkle/storage/simple\"\n\t\"gno.land/p/demo/uassert\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestStorage(t *testing.T) {\n\tvar undefinedStorage *simple.Storage\n\terr := undefinedStorage.Put(\"\")\n\tuassert.ErrorIs(t, err, storage.ErrUndefined, \"expected storage.ErrUndefined on undefined storage\")\n\n\ttests := []struct {\n\t\tname string\n\t\tvaluesToPut []string\n\t\texpLatestValueString string\n\t\texpLatestValueTimeIsZero bool\n\t\texpHistoricalValueStrings []string\n\t}{\n\t\t{\n\t\t\tname: \"empty\",\n\t\t\texpLatestValueTimeIsZero: true,\n\t\t},\n\t\t{\n\t\t\tname: \"one value\",\n\t\t\tvaluesToPut: []string{\"one\"},\n\t\t\texpLatestValueString: \"one\",\n\t\t\texpHistoricalValueStrings: []string{\"one\"},\n\t\t},\n\t\t{\n\t\t\tname: \"two values\",\n\t\t\tvaluesToPut: []string{\"one\", \"two\"},\n\t\t\texpLatestValueString: \"two\",\n\t\t\texpHistoricalValueStrings: []string{\"one\", \"two\"},\n\t\t},\n\t\t{\n\t\t\tname: \"three values\",\n\t\t\tvaluesToPut: []string{\"one\", \"two\", \"three\"},\n\t\t\texpLatestValueString: \"three\",\n\t\t\texpHistoricalValueStrings: []string{\"two\", \"three\"},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tsimpleStorage := simple.NewStorage(2)\n\t\t\tfor _, value := range tt.valuesToPut {\n\t\t\t\terr := simpleStorage.Put(value)\n\t\t\t\turequire.NoError(t, err, \"unexpected error putting value in storage\")\n\t\t\t}\n\n\t\t\tlatestValue := simpleStorage.GetLatest()\n\t\t\tuassert.Equal(t, tt.expLatestValueString, latestValue.String)\n\t\t\tuassert.Equal(t, tt.expLatestValueTimeIsZero, latestValue.Time.IsZero())\n\n\t\t\thistoricalValues := simpleStorage.GetHistory()\n\t\t\turequire.Equal(t, len(tt.expHistoricalValueStrings), len(historicalValues), \"historical values length does not match\")\n\n\t\t\tfor i, expValue := range tt.expHistoricalValueStrings {\n\t\t\t\tuassert.Equal(t, historicalValues[i].String, expValue)\n\t\t\t\turequire.False(t, historicalValues[i].Time.IsZero(), ufmt.Sprintf(\"unexpeced zero time for historical value at index %d\", i))\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":"single","path":"gno.land/p/demo/gnorkle/ingesters/single","files":[{"name":"ingester.gno","body":"package single\n\nimport (\n\t\"gno.land/p/demo/gnorkle/gnorkle\"\n\t\"gno.land/p/demo/gnorkle/ingester\"\n)\n\n// ValueIngester is an ingester that ingests a single value.\ntype ValueIngester struct {\n\tvalue string\n}\n\n// Type returns the type of the ingester.\nfunc (i *ValueIngester) Type() ingester.Type {\n\treturn ingester.TypeSingle\n}\n\n// Ingest ingests a value provided by the given agent address.\nfunc (i *ValueIngester) Ingest(value, providerAddress string) (bool, error) {\n\tif i == nil {\n\t\treturn false, ingester.ErrUndefined\n\t}\n\n\ti.value = value\n\treturn true, nil\n}\n\n// CommitValue commits the ingested value to the given storage instance.\nfunc (i *ValueIngester) CommitValue(valueStorer gnorkle.Storage, providerAddress string) error {\n\tif i == nil {\n\t\treturn ingester.ErrUndefined\n\t}\n\n\treturn valueStorer.Put(i.value)\n}\n"},{"name":"ingester_test.gno","body":"package single_test\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/gnorkle/ingester\"\n\t\"gno.land/p/demo/gnorkle/ingesters/single\"\n\t\"gno.land/p/demo/gnorkle/storage/simple\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestValueIngester(t *testing.T) {\n\tstorage := simple.NewStorage(1)\n\n\tvar undefinedIngester *single.ValueIngester\n\t_, err := undefinedIngester.Ingest(\"asdf\", \"gno11111\")\n\tuassert.ErrorIs(t, err, ingester.ErrUndefined, \"undefined ingester call to Ingest should return ingester.ErrUndefined\")\n\n\terr = undefinedIngester.CommitValue(storage, \"gno11111\")\n\tuassert.ErrorIs(t, err, ingester.ErrUndefined, \"undefined ingester call to CommitValue should return ingester.ErrUndefined\")\n\n\tvar valueIngester single.ValueIngester\n\ttyp := valueIngester.Type()\n\tuassert.Equal(t, int(ingester.TypeSingle), int(typ), \"single value ingester should return type ingester.TypeSingle\")\n\n\tingestValue := \"value\"\n\tautocommit, err := valueIngester.Ingest(ingestValue, \"gno11111\")\n\tuassert.True(t, autocommit, \"single value ingester should return autocommit true\")\n\tuassert.NoError(t, err)\n\n\terr = valueIngester.CommitValue(storage, \"gno11111\")\n\tuassert.NoError(t, err)\n\n\tlatestValue := storage.GetLatest()\n\tuassert.Equal(t, ingestValue, latestValue.String)\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":"static","path":"gno.land/p/demo/gnorkle/feeds/static","files":[{"name":"feed.gno","body":"package static\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"errors\"\n\n\t\"gno.land/p/demo/gnorkle/feed\"\n\t\"gno.land/p/demo/gnorkle/gnorkle\"\n\t\"gno.land/p/demo/gnorkle/ingesters/single\"\n\t\"gno.land/p/demo/gnorkle/message\"\n\t\"gno.land/p/demo/gnorkle/storage/simple\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Feed is a static feed.\ntype Feed struct {\n\tid string\n\tisLocked bool\n\tvalueDataType string\n\tingester gnorkle.Ingester\n\tstorage gnorkle.Storage\n\ttasks []feed.Task\n}\n\n// NewFeed creates a new static feed.\nfunc NewFeed(\n\tid string,\n\tvalueDataType string,\n\tingester gnorkle.Ingester,\n\tstorage gnorkle.Storage,\n\ttasks ...feed.Task,\n) *Feed {\n\treturn \u0026Feed{\n\t\tid: id,\n\t\tvalueDataType: valueDataType,\n\t\tingester: ingester,\n\t\tstorage: storage,\n\t\ttasks: tasks,\n\t}\n}\n\n// NewSingleValueFeed is a convenience function for creating a static feed\n// that autocommits a value after a single ingestion.\nfunc NewSingleValueFeed(\n\tid string,\n\tvalueDataType string,\n\ttasks ...feed.Task,\n) *Feed {\n\treturn NewFeed(\n\t\tid,\n\t\tvalueDataType,\n\t\t\u0026single.ValueIngester{},\n\t\tsimple.NewStorage(1),\n\t\ttasks...,\n\t)\n}\n\n// ID returns the feed's ID.\nfunc (f Feed) ID() string {\n\treturn f.id\n}\n\n// Type returns the feed's type.\nfunc (f Feed) Type() feed.Type {\n\treturn feed.TypeStatic\n}\n\n// Ingest ingests a message into the feed. It either adds the value to the ingester's\n// pending values or commits the value to the storage.\nfunc (f *Feed) Ingest(funcType message.FuncType, msg, providerAddress string) error {\n\tif f == nil {\n\t\treturn feed.ErrUndefined\n\t}\n\n\tif f.isLocked {\n\t\treturn errors.New(\"feed locked\")\n\t}\n\n\tswitch funcType {\n\tcase message.FuncTypeIngest:\n\t\t// Autocommit the ingester's value if it's a single value ingester\n\t\t// because this is a static feed and this is the only value it will ever have.\n\t\tif canAutoCommit, err := f.ingester.Ingest(msg, providerAddress); canAutoCommit \u0026\u0026 err == nil {\n\t\t\tif err := f.ingester.CommitValue(f.storage, providerAddress); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tf.isLocked = true\n\t\t} else if err != nil {\n\t\t\treturn err\n\t\t}\n\n\tcase message.FuncTypeCommit:\n\t\tif err := f.ingester.CommitValue(f.storage, providerAddress); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tf.isLocked = true\n\n\tdefault:\n\t\treturn errors.New(\"invalid message function \" + string(funcType))\n\t}\n\n\treturn nil\n}\n\n// Value returns the feed's latest value, it's data type, and whether or not it can\n// be safely consumed. In this case it uses `f.isLocked` because, this being a static\n// feed, it will only ever have one value; once that value is committed the feed is locked\n// and there is a valid, non-empty value to consume.\nfunc (f Feed) Value() (feed.Value, string, bool) {\n\treturn f.storage.GetLatest(), f.valueDataType, f.isLocked\n}\n\n// MarshalJSON marshals the components of the feed that are needed for\n// an agent to execute tasks and send values for ingestion.\nfunc (f Feed) MarshalJSON() ([]byte, error) {\n\tbuf := new(bytes.Buffer)\n\tw := bufio.NewWriter(buf)\n\n\tw.Write([]byte(\n\t\t`{\"id\":\"` + f.id +\n\t\t\t`\",\"type\":\"` + ufmt.Sprintf(\"%d\", int(f.Type())) +\n\t\t\t`\",\"value_type\":\"` + f.valueDataType +\n\t\t\t`\",\"tasks\":[`),\n\t)\n\n\tfirst := true\n\tfor _, task := range f.tasks {\n\t\tif !first {\n\t\t\tw.WriteString(\",\")\n\t\t}\n\n\t\ttaskJSON, err := task.MarshalJSON()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tw.Write(taskJSON)\n\t\tfirst = false\n\t}\n\n\tw.Write([]byte(\"]}\"))\n\tw.Flush()\n\n\treturn buf.Bytes(), nil\n}\n\n// Tasks returns the feed's tasks. This allows task consumers to extract task\n// contents without having to marshal the entire feed.\nfunc (f Feed) Tasks() []feed.Task {\n\treturn f.tasks\n}\n\n// IsActive returns true if the feed is accepting ingestion requests from agents.\nfunc (f Feed) IsActive() bool {\n\treturn !f.isLocked\n}\n"},{"name":"feed_test.gno","body":"package static_test\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/gnorkle/feed\"\n\t\"gno.land/p/demo/gnorkle/feeds/static\"\n\t\"gno.land/p/demo/gnorkle/gnorkle\"\n\t\"gno.land/p/demo/gnorkle/ingester\"\n\t\"gno.land/p/demo/gnorkle/message\"\n\t\"gno.land/p/demo/gnorkle/storage/simple\"\n\t\"gno.land/p/demo/uassert\"\n\t\"gno.land/p/demo/urequire\"\n)\n\ntype mockIngester struct {\n\tcanAutoCommit bool\n\tingestErr error\n\tcommitErr error\n\tvalue string\n\tproviderAddress string\n}\n\nfunc (i mockIngester) Type() ingester.Type {\n\treturn ingester.Type(0)\n}\n\nfunc (i *mockIngester) Ingest(value, providerAddress string) (bool, error) {\n\tif i.ingestErr != nil {\n\t\treturn false, i.ingestErr\n\t}\n\n\ti.value = value\n\ti.providerAddress = providerAddress\n\treturn i.canAutoCommit, nil\n}\n\nfunc (i *mockIngester) CommitValue(storage gnorkle.Storage, providerAddress string) error {\n\tif i.commitErr != nil {\n\t\treturn i.commitErr\n\t}\n\n\treturn storage.Put(i.value)\n}\n\nfunc TestNewSingleValueFeed(t *testing.T) {\n\tstaticFeed := static.NewSingleValueFeed(\"1\", \"\")\n\n\tuassert.Equal(t, \"1\", staticFeed.ID())\n\tuassert.Equal(t, int(feed.TypeStatic), int(staticFeed.Type()))\n}\n\nfunc TestFeed_Ingest(t *testing.T) {\n\tvar undefinedFeed *static.Feed\n\terr := undefinedFeed.Ingest(\"\", \"\", \"\")\n\tuassert.ErrorIs(t, err, feed.ErrUndefined)\n\n\ttests := []struct {\n\t\tname string\n\t\tingester *mockIngester\n\t\tverifyIsLocked bool\n\t\tdoCommit bool\n\t\tfuncType message.FuncType\n\t\tmsg string\n\t\tproviderAddress string\n\t\texpFeedValueString string\n\t\texpErrText string\n\t\texpIsActive bool\n\t}{\n\t\t{\n\t\t\tname: \"func invalid error\",\n\t\t\tingester: \u0026mockIngester{},\n\t\t\tfuncType: message.FuncType(\"derp\"),\n\t\t\texpErrText: \"invalid message function derp\",\n\t\t\texpIsActive: true,\n\t\t},\n\t\t{\n\t\t\tname: \"func ingest ingest error\",\n\t\t\tingester: \u0026mockIngester{\n\t\t\t\tingestErr: errors.New(\"ingest error\"),\n\t\t\t},\n\t\t\tfuncType: message.FuncTypeIngest,\n\t\t\texpErrText: \"ingest error\",\n\t\t\texpIsActive: true,\n\t\t},\n\t\t{\n\t\t\tname: \"func ingest commit error\",\n\t\t\tingester: \u0026mockIngester{\n\t\t\t\tcommitErr: errors.New(\"commit error\"),\n\t\t\t\tcanAutoCommit: true,\n\t\t\t},\n\t\t\tfuncType: message.FuncTypeIngest,\n\t\t\texpErrText: \"commit error\",\n\t\t\texpIsActive: true,\n\t\t},\n\t\t{\n\t\t\tname: \"func commit commit error\",\n\t\t\tingester: \u0026mockIngester{\n\t\t\t\tcommitErr: errors.New(\"commit error\"),\n\t\t\t\tcanAutoCommit: true,\n\t\t\t},\n\t\t\tfuncType: message.FuncTypeCommit,\n\t\t\texpErrText: \"commit error\",\n\t\t\texpIsActive: true,\n\t\t},\n\t\t{\n\t\t\tname: \"only ingest\",\n\t\t\tingester: \u0026mockIngester{},\n\t\t\tfuncType: message.FuncTypeIngest,\n\t\t\tmsg: \"still active feed\",\n\t\t\tproviderAddress: \"gno1234\",\n\t\t\texpIsActive: true,\n\t\t},\n\t\t{\n\t\t\tname: \"ingest autocommit\",\n\t\t\tingester: \u0026mockIngester{canAutoCommit: true},\n\t\t\tfuncType: message.FuncTypeIngest,\n\t\t\tmsg: \"still active feed\",\n\t\t\tproviderAddress: \"gno1234\",\n\t\t\texpFeedValueString: \"still active feed\",\n\t\t\tverifyIsLocked: true,\n\t\t},\n\t\t{\n\t\t\tname: \"commit no value\",\n\t\t\tingester: \u0026mockIngester{},\n\t\t\tfuncType: message.FuncTypeCommit,\n\t\t\tmsg: \"shouldn't be stored\",\n\t\t\tverifyIsLocked: true,\n\t\t},\n\t\t{\n\t\t\tname: \"ingest then commmit\",\n\t\t\tingester: \u0026mockIngester{},\n\t\t\tfuncType: message.FuncTypeIngest,\n\t\t\tmsg: \"blahblah\",\n\t\t\tdoCommit: true,\n\t\t\texpFeedValueString: \"blahblah\",\n\t\t\tverifyIsLocked: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tstaticFeed := static.NewFeed(\n\t\t\t\t\"1\",\n\t\t\t\t\"string\",\n\t\t\t\ttt.ingester,\n\t\t\t\tsimple.NewStorage(1),\n\t\t\t\tnil,\n\t\t\t)\n\n\t\t\tvar errText string\n\t\t\tif err := staticFeed.Ingest(tt.funcType, tt.msg, tt.providerAddress); err != nil {\n\t\t\t\terrText = err.Error()\n\t\t\t}\n\n\t\t\turequire.Equal(t, tt.expErrText, errText)\n\n\t\t\tif tt.doCommit {\n\t\t\t\terr := staticFeed.Ingest(message.FuncTypeCommit, \"\", \"\")\n\t\t\t\turequire.NoError(t, err, \"follow up commit failed\")\n\t\t\t}\n\n\t\t\tif tt.verifyIsLocked {\n\t\t\t\terrText = \"\"\n\t\t\t\tif err := staticFeed.Ingest(tt.funcType, tt.msg, tt.providerAddress); err != nil {\n\t\t\t\t\terrText = err.Error()\n\t\t\t\t}\n\n\t\t\t\turequire.Equal(t, \"feed locked\", errText)\n\t\t\t}\n\n\t\t\tuassert.Equal(t, tt.providerAddress, tt.ingester.providerAddress)\n\n\t\t\tfeedValue, dataType, isLocked := staticFeed.Value()\n\t\t\tuassert.Equal(t, tt.expFeedValueString, feedValue.String)\n\t\t\tuassert.Equal(t, \"string\", dataType)\n\t\t\tuassert.Equal(t, tt.verifyIsLocked, isLocked)\n\t\t\tuassert.Equal(t, tt.expIsActive, staticFeed.IsActive())\n\t\t})\n\t}\n}\n\ntype mockTask struct {\n\terr error\n\tvalue string\n}\n\nfunc (t mockTask) MarshalJSON() ([]byte, error) {\n\tif t.err != nil {\n\t\treturn nil, t.err\n\t}\n\n\treturn []byte(`{\"value\":\"` + t.value + `\"}`), nil\n}\n\nfunc TestFeed_Tasks(t *testing.T) {\n\tid := \"99\"\n\tvalueDataType := \"int\"\n\n\ttests := []struct {\n\t\tname string\n\t\ttasks []feed.Task\n\t\texpErrText string\n\t\texpJSON string\n\t}{\n\t\t{\n\t\t\tname: \"no tasks\",\n\t\t\texpJSON: `{\"id\":\"99\",\"type\":\"0\",\"value_type\":\"int\",\"tasks\":[]}`,\n\t\t},\n\t\t{\n\t\t\tname: \"marshal error\",\n\t\t\ttasks: []feed.Task{\n\t\t\t\tmockTask{err: errors.New(\"marshal error\")},\n\t\t\t},\n\t\t\texpErrText: \"marshal error\",\n\t\t},\n\t\t{\n\t\t\tname: \"one task\",\n\t\t\ttasks: []feed.Task{\n\t\t\t\tmockTask{value: \"single\"},\n\t\t\t},\n\t\t\texpJSON: `{\"id\":\"99\",\"type\":\"0\",\"value_type\":\"int\",\"tasks\":[{\"value\":\"single\"}]}`,\n\t\t},\n\t\t{\n\t\t\tname: \"two tasks\",\n\t\t\ttasks: []feed.Task{\n\t\t\t\tmockTask{value: \"first\"},\n\t\t\t\tmockTask{value: \"second\"},\n\t\t\t},\n\t\t\texpJSON: `{\"id\":\"99\",\"type\":\"0\",\"value_type\":\"int\",\"tasks\":[{\"value\":\"first\"},{\"value\":\"second\"}]}`,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tstaticFeed := static.NewSingleValueFeed(\n\t\t\t\tid,\n\t\t\t\tvalueDataType,\n\t\t\t\ttt.tasks...,\n\t\t\t)\n\n\t\t\turequire.Equal(t, len(tt.tasks), len(staticFeed.Tasks()))\n\n\t\t\tvar errText string\n\t\t\tjson, err := staticFeed.MarshalJSON()\n\t\t\tif err != nil {\n\t\t\t\terrText = err.Error()\n\t\t\t}\n\n\t\t\turequire.Equal(t, tt.expErrText, errText)\n\t\t\turequire.Equal(t, tt.expJSON, string(json))\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":"exts","path":"gno.land/p/demo/grc/exts","files":[{"name":"token_metadata.gno","body":"package exts\n\ntype TokenMetadata interface {\n\t// Returns the name of the token.\n\tGetName() string\n\n\t// Returns the symbol of the token, usually a shorter version of the\n\t// name.\n\tGetSymbol() string\n\n\t// Returns the decimals places of the token.\n\tGetDecimals() uint\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":"grc1155","path":"gno.land/p/demo/grc/grc1155","files":[{"name":"README.md","body":"# GRC-1155 Spec: Multi Token Standard\n\nGRC1155 is a specification for managing multiple tokens based on Gnoland. The name and design is based on Ethereum's ERC1155 standard.\n\n## See also:\n\n[ERC-1155 Spec][erc-1155]\n\n[erc-1155]: https://eips.ethereum.org/EIPS/eip-1155"},{"name":"basic_grc1155_token.gno","body":"package grc1155\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype basicGRC1155Token struct {\n\turi string\n\tbalances avl.Tree // \"TokenId:Address\" -\u003e uint64\n\toperatorApprovals avl.Tree // \"OwnerAddress:OperatorAddress\" -\u003e bool\n}\n\nvar _ IGRC1155 = (*basicGRC1155Token)(nil)\n\n// Returns new basic GRC1155 token\nfunc NewBasicGRC1155Token(uri string) *basicGRC1155Token {\n\treturn \u0026basicGRC1155Token{\n\t\turi: uri,\n\t\tbalances: avl.Tree{},\n\t\toperatorApprovals: avl.Tree{},\n\t}\n}\n\nfunc (s *basicGRC1155Token) Uri() string { return s.uri }\n\n// BalanceOf returns the input address's balance of the token type requested\nfunc (s *basicGRC1155Token) BalanceOf(addr std.Address, tid TokenID) (uint64, error) {\n\tif !isValidAddress(addr) {\n\t\treturn 0, ErrInvalidAddress\n\t}\n\n\tkey := string(tid) + \":\" + addr.String()\n\tbalance, found := s.balances.Get(key)\n\tif !found {\n\t\treturn 0, nil\n\t}\n\n\treturn balance.(uint64), nil\n}\n\n// BalanceOfBatch returns the balance of multiple account/token pairs\nfunc (s *basicGRC1155Token) BalanceOfBatch(owners []std.Address, batch []TokenID) ([]uint64, error) {\n\tif len(owners) != len(batch) {\n\t\treturn nil, ErrMismatchLength\n\t}\n\n\tbalanceOfBatch := make([]uint64, len(owners))\n\n\tfor i := 0; i \u003c len(owners); i++ {\n\t\tbalanceOfBatch[i], _ = s.BalanceOf(owners[i], batch[i])\n\t}\n\n\treturn balanceOfBatch, nil\n}\n\n// SetApprovalForAll can approve the operator to operate on all tokens\nfunc (s *basicGRC1155Token) SetApprovalForAll(operator std.Address, approved bool) error {\n\tif !isValidAddress(operator) {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tcaller := std.OrigCaller()\n\treturn s.setApprovalForAll(caller, operator, approved)\n}\n\n// IsApprovedForAll returns true if operator is the owner or is approved for all by the owner.\n// Otherwise, returns false\nfunc (s *basicGRC1155Token) IsApprovedForAll(owner, operator std.Address) bool {\n\tif operator == owner {\n\t\treturn true\n\t}\n\tkey := owner.String() + \":\" + operator.String()\n\t_, found := s.operatorApprovals.Get(key)\n\tif !found {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// Safely transfers `tokenId` token from `from` to `to`, checking that\n// contract recipients are aware of the GRC1155 protocol to prevent\n// tokens from being forever locked.\nfunc (s *basicGRC1155Token) SafeTransferFrom(from, to std.Address, tid TokenID, amount uint64) error {\n\tcaller := std.OrigCaller()\n\tif !s.IsApprovedForAll(caller, from) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.safeBatchTransferFrom(from, to, []TokenID{tid}, []uint64{amount})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.doSafeTransferAcceptanceCheck(caller, from, to, tid, amount) {\n\t\treturn ErrTransferToRejectedOrNonGRC1155Receiver\n\t}\n\n\temit(\u0026TransferSingleEvent{caller, from, to, tid, amount})\n\n\treturn nil\n}\n\n// Safely transfers a `batch` of tokens from `from` to `to`, checking that\n// contract recipients are aware of the GRC1155 protocol to prevent\n// tokens from being forever locked.\nfunc (s *basicGRC1155Token) SafeBatchTransferFrom(from, to std.Address, batch []TokenID, amounts []uint64) error {\n\tcaller := std.OrigCaller()\n\tif !s.IsApprovedForAll(caller, from) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.safeBatchTransferFrom(from, to, batch, amounts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.doSafeBatchTransferAcceptanceCheck(caller, from, to, batch, amounts) {\n\t\treturn ErrTransferToRejectedOrNonGRC1155Receiver\n\t}\n\n\temit(\u0026TransferBatchEvent{caller, from, to, batch, amounts})\n\n\treturn nil\n}\n\n// Creates `amount` tokens of token type `id`, and assigns them to `to`. Also checks that\n// contract recipients are using GRC1155 protocol.\nfunc (s *basicGRC1155Token) SafeMint(to std.Address, tid TokenID, amount uint64) error {\n\tcaller := std.OrigCaller()\n\n\terr := s.mintBatch(to, []TokenID{tid}, []uint64{amount})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.doSafeTransferAcceptanceCheck(caller, zeroAddress, to, tid, amount) {\n\t\treturn ErrTransferToRejectedOrNonGRC1155Receiver\n\t}\n\n\temit(\u0026TransferSingleEvent{caller, zeroAddress, to, tid, amount})\n\n\treturn nil\n}\n\n// Batch version of `SafeMint()`. Also checks that\n// contract recipients are using GRC1155 protocol.\nfunc (s *basicGRC1155Token) SafeBatchMint(to std.Address, batch []TokenID, amounts []uint64) error {\n\tcaller := std.OrigCaller()\n\n\terr := s.mintBatch(to, batch, amounts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.doSafeBatchTransferAcceptanceCheck(caller, zeroAddress, to, batch, amounts) {\n\t\treturn ErrTransferToRejectedOrNonGRC1155Receiver\n\t}\n\n\temit(\u0026TransferBatchEvent{caller, zeroAddress, to, batch, amounts})\n\n\treturn nil\n}\n\n// Destroys `amount` tokens of token type `id` from `from`.\nfunc (s *basicGRC1155Token) Burn(from std.Address, tid TokenID, amount uint64) error {\n\tcaller := std.OrigCaller()\n\n\terr := s.burnBatch(from, []TokenID{tid}, []uint64{amount})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\temit(\u0026TransferSingleEvent{caller, from, zeroAddress, tid, amount})\n\n\treturn nil\n}\n\n// Batch version of `Burn()`\nfunc (s *basicGRC1155Token) BatchBurn(from std.Address, batch []TokenID, amounts []uint64) error {\n\tcaller := std.OrigCaller()\n\n\terr := s.burnBatch(from, batch, amounts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\temit(\u0026TransferBatchEvent{caller, from, zeroAddress, batch, amounts})\n\n\treturn nil\n}\n\n/* Helper methods */\n\n// Helper for SetApprovalForAll(): approve `operator` to operate on all of `owner` tokens\nfunc (s *basicGRC1155Token) setApprovalForAll(owner, operator std.Address, approved bool) error {\n\tif owner == operator {\n\t\treturn nil\n\t}\n\n\tkey := owner.String() + \":\" + operator.String()\n\tif approved {\n\t\ts.operatorApprovals.Set(key, approved)\n\t} else {\n\t\ts.operatorApprovals.Remove(key)\n\t}\n\n\temit(\u0026ApprovalForAllEvent{owner, operator, approved})\n\n\treturn nil\n}\n\n// Helper for SafeTransferFrom() and SafeBatchTransferFrom()\nfunc (s *basicGRC1155Token) safeBatchTransferFrom(from, to std.Address, batch []TokenID, amounts []uint64) error {\n\tif len(batch) != len(amounts) {\n\t\treturn ErrMismatchLength\n\t}\n\tif !isValidAddress(from) || !isValidAddress(to) {\n\t\treturn ErrInvalidAddress\n\t}\n\tif from == to {\n\t\treturn ErrCannotTransferToSelf\n\t}\n\n\tcaller := std.OrigCaller()\n\ts.beforeTokenTransfer(caller, from, to, batch, amounts)\n\n\tfor i := 0; i \u003c len(batch); i++ {\n\t\ttid := batch[i]\n\t\tamount := amounts[i]\n\t\tfromBalance, err := s.BalanceOf(from, tid)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif fromBalance \u003c amount {\n\t\t\treturn ErrInsufficientBalance\n\t\t}\n\t\ttoBalance, err := s.BalanceOf(to, tid)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfromBalance -= amount\n\t\ttoBalance += amount\n\t\tfromBalanceKey := string(tid) + \":\" + from.String()\n\t\ttoBalanceKey := string(tid) + \":\" + to.String()\n\t\ts.balances.Set(fromBalanceKey, fromBalance)\n\t\ts.balances.Set(toBalanceKey, toBalance)\n\t}\n\n\ts.afterTokenTransfer(caller, from, to, batch, amounts)\n\n\treturn nil\n}\n\n// Helper for SafeMint() and SafeBatchMint()\nfunc (s *basicGRC1155Token) mintBatch(to std.Address, batch []TokenID, amounts []uint64) error {\n\tif len(batch) != len(amounts) {\n\t\treturn ErrMismatchLength\n\t}\n\tif !isValidAddress(to) {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tcaller := std.OrigCaller()\n\ts.beforeTokenTransfer(caller, zeroAddress, to, batch, amounts)\n\n\tfor i := 0; i \u003c len(batch); i++ {\n\t\ttid := batch[i]\n\t\tamount := amounts[i]\n\t\ttoBalance, err := s.BalanceOf(to, tid)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ttoBalance += amount\n\t\ttoBalanceKey := string(tid) + \":\" + to.String()\n\t\ts.balances.Set(toBalanceKey, toBalance)\n\t}\n\n\ts.afterTokenTransfer(caller, zeroAddress, to, batch, amounts)\n\n\treturn nil\n}\n\n// Helper for Burn() and BurnBatch()\nfunc (s *basicGRC1155Token) burnBatch(from std.Address, batch []TokenID, amounts []uint64) error {\n\tif len(batch) != len(amounts) {\n\t\treturn ErrMismatchLength\n\t}\n\tif !isValidAddress(from) {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tcaller := std.OrigCaller()\n\ts.beforeTokenTransfer(caller, from, zeroAddress, batch, amounts)\n\n\tfor i := 0; i \u003c len(batch); i++ {\n\t\ttid := batch[i]\n\t\tamount := amounts[i]\n\t\tfromBalance, err := s.BalanceOf(from, tid)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif fromBalance \u003c amount {\n\t\t\treturn ErrBurnAmountExceedsBalance\n\t\t}\n\t\tfromBalance -= amount\n\t\tfromBalanceKey := string(tid) + \":\" + from.String()\n\t\ts.balances.Set(fromBalanceKey, fromBalance)\n\t}\n\n\ts.afterTokenTransfer(caller, from, zeroAddress, batch, amounts)\n\n\treturn nil\n}\n\nfunc (s *basicGRC1155Token) setUri(newUri string) {\n\ts.uri = newUri\n\temit(\u0026UpdateURIEvent{newUri})\n}\n\nfunc (s *basicGRC1155Token) beforeTokenTransfer(operator, from, to std.Address, batch []TokenID, amounts []uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicGRC1155Token) afterTokenTransfer(operator, from, to std.Address, batch []TokenID, amounts []uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicGRC1155Token) doSafeTransferAcceptanceCheck(operator, from, to std.Address, tid TokenID, amount uint64) bool {\n\t// TODO: Implementation\n\treturn true\n}\n\nfunc (s *basicGRC1155Token) doSafeBatchTransferAcceptanceCheck(operator, from, to std.Address, batch []TokenID, amounts []uint64) bool {\n\t// TODO: Implementation\n\treturn true\n}\n\nfunc (s *basicGRC1155Token) RenderHome() (str string) {\n\tstr += ufmt.Sprintf(\"# URI:%s\\n\", s.uri)\n\n\treturn\n}\n"},{"name":"basic_grc1155_token_test.gno","body":"package grc1155\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nconst dummyURI = \"ipfs://xyz\"\n\nfunc TestNewBasicGRC1155Token(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n}\n\nfunc TestUri(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\tuassert.Equal(t, dummyURI, dummy.Uri())\n}\n\nfunc TestBalanceOf(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\tbalanceZeroAddressOfToken1, err := dummy.BalanceOf(zeroAddress, tid1)\n\tuassert.Error(t, err, \"should result in error\")\n\n\tbalanceAddr1OfToken1, err := dummy.BalanceOf(addr1, tid1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(0), balanceAddr1OfToken1)\n\n\tdummy.mintBatch(addr1, []TokenID{tid1, tid2}, []uint64{10, 100})\n\tdummy.mintBatch(addr2, []TokenID{tid1}, []uint64{20})\n\n\tbalanceAddr1OfToken1, err = dummy.BalanceOf(addr1, tid1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tbalanceAddr1OfToken2, err := dummy.BalanceOf(addr1, tid2)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tbalanceAddr2OfToken1, err := dummy.BalanceOf(addr2, tid1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tuassert.Equal(t, uint64(10), balanceAddr1OfToken1)\n\tuassert.Equal(t, uint64(100), balanceAddr1OfToken2)\n\tuassert.Equal(t, uint64(20), balanceAddr2OfToken1)\n}\n\nfunc TestBalanceOfBatch(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{addr1, addr2}, []TokenID{tid1, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(0), balanceBatch[0])\n\tuassert.Equal(t, uint64(0), balanceBatch[1])\n\n\tdummy.mintBatch(addr1, []TokenID{tid1}, []uint64{10})\n\tdummy.mintBatch(addr2, []TokenID{tid2}, []uint64{20})\n\n\tbalanceBatch, err = dummy.BalanceOfBatch([]std.Address{addr1, addr2}, []TokenID{tid1, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(10), balanceBatch[0])\n\tuassert.Equal(t, uint64(20), balanceBatch[1])\n}\n\nfunc TestIsApprovedForAll(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(addr1, addr2)\n\tuassert.False(t, isApprovedForAll)\n}\n\nfunc TestSetApprovalForAll(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.OrigCaller()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(caller, addr)\n\tuassert.False(t, isApprovedForAll)\n\n\terr := dummy.SetApprovalForAll(addr, true)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tisApprovedForAll = dummy.IsApprovedForAll(caller, addr)\n\tuassert.True(t, isApprovedForAll)\n\n\terr = dummy.SetApprovalForAll(addr, false)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tisApprovedForAll = dummy.IsApprovedForAll(caller, addr)\n\tuassert.False(t, isApprovedForAll)\n}\n\nfunc TestSafeTransferFrom(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.OrigCaller()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\ttid := TokenID(\"1\")\n\n\tdummy.mintBatch(caller, []TokenID{tid}, []uint64{100})\n\n\terr := dummy.SafeTransferFrom(caller, zeroAddress, tid, 10)\n\tuassert.Error(t, err, \"should result in error\")\n\n\terr = dummy.SafeTransferFrom(caller, addr, tid, 160)\n\tuassert.Error(t, err, \"should result in error\")\n\n\terr = dummy.SafeTransferFrom(caller, addr, tid, 60)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check balance of caller after transfer\n\tbalanceOfCaller, err := dummy.BalanceOf(caller, tid)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(40), balanceOfCaller)\n\n\t// Check balance of addr after transfer\n\tbalanceOfAddr, err := dummy.BalanceOf(addr, tid)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(60), balanceOfAddr)\n}\n\nfunc TestSafeBatchTransferFrom(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.OrigCaller()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\tdummy.mintBatch(caller, []TokenID{tid1, tid2}, []uint64{10, 100})\n\n\terr := dummy.SafeBatchTransferFrom(caller, zeroAddress, []TokenID{tid1, tid2}, []uint64{4, 60})\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.SafeBatchTransferFrom(caller, addr, []TokenID{tid1, tid2}, []uint64{40, 60})\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.SafeBatchTransferFrom(caller, addr, []TokenID{tid1}, []uint64{40, 60})\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.SafeBatchTransferFrom(caller, addr, []TokenID{tid1, tid2}, []uint64{4, 60})\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{caller, addr, caller, addr}, []TokenID{tid1, tid1, tid2, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check token1's balance of caller after batch transfer\n\tuassert.Equal(t, uint64(6), balanceBatch[0])\n\n\t// Check token1's balance of addr after batch transfer\n\tuassert.Equal(t, uint64(4), balanceBatch[1])\n\n\t// Check token2's balance of caller after batch transfer\n\tuassert.Equal(t, uint64(40), balanceBatch[2])\n\n\t// Check token2's balance of addr after batch transfer\n\tuassert.Equal(t, uint64(60), balanceBatch[3])\n}\n\nfunc TestSafeMint(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\terr := dummy.SafeMint(zeroAddress, tid1, 100)\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.SafeMint(addr1, tid1, 100)\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.SafeMint(addr1, tid2, 200)\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.SafeMint(addr2, tid1, 50)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{addr1, addr2, addr1}, []TokenID{tid1, tid1, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\t// Check token1's balance of addr1 after mint\n\tuassert.Equal(t, uint64(100), balanceBatch[0])\n\t// Check token1's balance of addr2 after mint\n\tuassert.Equal(t, uint64(50), balanceBatch[1])\n\t// Check token2's balance of addr1 after mint\n\tuassert.Equal(t, uint64(200), balanceBatch[2])\n}\n\nfunc TestSafeBatchMint(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\terr := dummy.SafeBatchMint(zeroAddress, []TokenID{tid1, tid2}, []uint64{100, 200})\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.SafeBatchMint(addr1, []TokenID{tid1, tid2}, []uint64{100, 200})\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.SafeBatchMint(addr2, []TokenID{tid1, tid2}, []uint64{300, 400})\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{addr1, addr2, addr1, addr2}, []TokenID{tid1, tid1, tid2, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\t// Check token1's balance of addr1 after batch mint\n\tuassert.Equal(t, uint64(100), balanceBatch[0])\n\t// Check token1's balance of addr2 after batch mint\n\tuassert.Equal(t, uint64(300), balanceBatch[1])\n\t// Check token2's balance of addr1 after batch mint\n\tuassert.Equal(t, uint64(200), balanceBatch[2])\n\t// Check token2's balance of addr2 after batch mint\n\tuassert.Equal(t, uint64(400), balanceBatch[3])\n}\n\nfunc TestBurn(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\tdummy.mintBatch(addr, []TokenID{tid1, tid2}, []uint64{100, 200})\n\terr := dummy.Burn(zeroAddress, tid1, uint64(60))\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.Burn(addr, tid1, uint64(160))\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.Burn(addr, tid1, uint64(60))\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.Burn(addr, tid2, uint64(60))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{addr, addr}, []TokenID{tid1, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check token1's balance of addr after burn\n\tuassert.Equal(t, uint64(40), balanceBatch[0])\n\t// Check token2's balance of addr after burn\n\tuassert.Equal(t, uint64(140), balanceBatch[1])\n}\n\nfunc TestBatchBurn(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\tdummy.mintBatch(addr, []TokenID{tid1, tid2}, []uint64{100, 200})\n\terr := dummy.BatchBurn(zeroAddress, []TokenID{tid1, tid2}, []uint64{60, 60})\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.BatchBurn(addr, []TokenID{tid1, tid2}, []uint64{160, 60})\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.BatchBurn(addr, []TokenID{tid1, tid2}, []uint64{60, 60})\n\tuassert.NoError(t, err, \"should not result in error\")\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{addr, addr}, []TokenID{tid1, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check token1's balance of addr after batch burn\n\tuassert.Equal(t, uint64(40), balanceBatch[0])\n\t// Check token2's balance of addr after batch burn\n\tuassert.Equal(t, uint64(140), balanceBatch[1])\n}\n"},{"name":"errors.gno","body":"package grc1155\n\nimport \"errors\"\n\nvar (\n\tErrInvalidAddress = errors.New(\"invalid address\")\n\tErrMismatchLength = errors.New(\"accounts and ids length mismatch\")\n\tErrCannotTransferToSelf = errors.New(\"cannot send transfer to self\")\n\tErrTransferToRejectedOrNonGRC1155Receiver = errors.New(\"transfer to rejected or non GRC1155Receiver implementer\")\n\tErrCallerIsNotOwnerOrApproved = errors.New(\"caller is not token owner or approved\")\n\tErrInsufficientBalance = errors.New(\"insufficient balance for transfer\")\n\tErrBurnAmountExceedsBalance = errors.New(\"burn amount exceeds balance\")\n)\n"},{"name":"igrc1155.gno","body":"package grc1155\n\nimport \"std\"\n\ntype IGRC1155 interface {\n\tSafeTransferFrom(from, to std.Address, tid TokenID, amount uint64) error\n\tSafeBatchTransferFrom(from, to std.Address, batch []TokenID, amounts []uint64) error\n\tBalanceOf(owner std.Address, tid TokenID) (uint64, error)\n\tBalanceOfBatch(owners []std.Address, batch []TokenID) ([]uint64, error)\n\tSetApprovalForAll(operator std.Address, approved bool) error\n\tIsApprovedForAll(owner, operator std.Address) bool\n}\n\ntype TokenID string\n\ntype TransferSingleEvent struct {\n\tOperator std.Address\n\tFrom std.Address\n\tTo std.Address\n\tTokenID TokenID\n\tAmount uint64\n}\n\ntype TransferBatchEvent struct {\n\tOperator std.Address\n\tFrom std.Address\n\tTo std.Address\n\tBatch []TokenID\n\tAmounts []uint64\n}\n\ntype ApprovalForAllEvent struct {\n\tOwner std.Address\n\tOperator std.Address\n\tApproved bool\n}\n\ntype UpdateURIEvent struct {\n\tURI string\n}\n"},{"name":"util.gno","body":"package grc1155\n\nimport (\n\t\"std\"\n)\n\nconst zeroAddress std.Address = \"\"\n\nfunc isValidAddress(addr std.Address) bool {\n\tif !addr.IsValid() {\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc emit(event interface{}) {\n\t// TODO: setup a pubsub system here?\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":"banker.gno","body":"package grc20\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Banker implements a token banker with admin privileges.\n//\n// The Banker is intended to be used in two main ways:\n// 1. as a temporary object used to make the initial minting, then deleted.\n// 2. preserved in an unexported variable to support conditional administrative\n// tasks protected by the contract.\ntype Banker struct {\n\tname string\n\tsymbol string\n\tdecimals uint\n\ttotalSupply uint64\n\tbalances avl.Tree // std.Address(owner) -\u003e uint64\n\tallowances avl.Tree // string(owner+\":\"+spender) -\u003e uint64\n\ttoken *token // to share the same pointer\n}\n\nfunc NewBanker(name, symbol string, decimals uint) *Banker {\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\tb := Banker{\n\t\tname: name,\n\t\tsymbol: symbol,\n\t\tdecimals: decimals,\n\t}\n\tt := \u0026token{banker: \u0026b}\n\tb.token = t\n\treturn \u0026b\n}\n\nfunc (b Banker) Token() Token { return b.token } // Token returns a grc20 safe-object implementation.\nfunc (b Banker) GetName() string { return b.name }\nfunc (b Banker) GetSymbol() string { return b.symbol }\nfunc (b Banker) GetDecimals() uint { return b.decimals }\nfunc (b Banker) TotalSupply() uint64 { return b.totalSupply }\nfunc (b Banker) KnownAccounts() int { return b.balances.Size() }\n\nfunc (b *Banker) Mint(address std.Address, amount uint64) error {\n\tif !address.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\n\t// TODO: check for overflow\n\n\tb.totalSupply += amount\n\tcurrentBalance := b.BalanceOf(address)\n\tnewBalance := currentBalance + amount\n\n\tb.balances.Set(string(address), newBalance)\n\n\tstd.Emit(\n\t\tMintEvent,\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\nfunc (b *Banker) Burn(address std.Address, amount uint64) error {\n\tif !address.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\t// TODO: check for overflow\n\n\tcurrentBalance := b.BalanceOf(address)\n\tif currentBalance \u003c amount {\n\t\treturn ErrInsufficientBalance\n\t}\n\n\tb.totalSupply -= amount\n\tnewBalance := currentBalance - amount\n\n\tb.balances.Set(string(address), newBalance)\n\n\tstd.Emit(\n\t\tBurnEvent,\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\nfunc (b Banker) BalanceOf(address std.Address) uint64 {\n\tbalance, found := b.balances.Get(address.String())\n\tif !found {\n\t\treturn 0\n\t}\n\treturn balance.(uint64)\n}\n\nfunc (b *Banker) 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 := b.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\tb.allowances.Remove(key)\n\t} else {\n\t\tb.allowances.Set(key, newAllowance)\n\t}\n\n\treturn nil\n}\n\nfunc (b *Banker) 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\ttoBalance := b.BalanceOf(to)\n\tfromBalance := b.BalanceOf(from)\n\n\tif fromBalance \u003c amount {\n\t\treturn ErrInsufficientBalance\n\t}\n\n\tnewToBalance := toBalance + amount\n\tnewFromBalance := fromBalance - amount\n\n\tb.balances.Set(string(to), newToBalance)\n\tb.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\nfunc (b *Banker) TransferFrom(spender, from, to std.Address, amount uint64) error {\n\tif err := b.SpendAllowance(from, spender, amount); err != nil {\n\t\treturn err\n\t}\n\treturn b.Transfer(from, to, amount)\n}\n\nfunc (b *Banker) Allowance(owner, spender std.Address) uint64 {\n\tallowance, found := b.allowances.Get(allowanceKey(owner, spender))\n\tif !found {\n\t\treturn 0\n\t}\n\treturn allowance.(uint64)\n}\n\nfunc (b *Banker) Approve(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\tb.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\nfunc (b *Banker) RenderHome() string {\n\tstr := \"\"\n\tstr += ufmt.Sprintf(\"# %s ($%s)\\n\\n\", b.name, b.symbol)\n\tstr += ufmt.Sprintf(\"* **Decimals**: %d\\n\", b.decimals)\n\tstr += ufmt.Sprintf(\"* **Total supply**: %d\\n\", b.totalSupply)\n\tstr += ufmt.Sprintf(\"* **Known accounts**: %d\\n\", b.KnownAccounts())\n\treturn str\n}\n\nfunc allowanceKey(owner, spender std.Address) string {\n\treturn owner.String() + \":\" + spender.String()\n}\n"},{"name":"banker_test.gno","body":"package grc20\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestBankerImpl(t *testing.T) {\n\tdummy := NewBanker(\"Dummy\", \"DUMMY\", 4)\n\turequire.False(t, dummy == nil, \"dummy should not be nil\")\n}\n\nfunc TestAllowance(t *testing.T) {\n\tvar (\n\t\towner = testutils.TestAddress(\"owner\")\n\t\tspender = testutils.TestAddress(\"spender\")\n\t\tdest = testutils.TestAddress(\"dest\")\n\t)\n\n\tb := NewBanker(\"Dummy\", \"DUMMY\", 6)\n\turequire.NoError(t, b.Mint(owner, 100000000))\n\turequire.NoError(t, b.Approve(owner, spender, 5000000))\n\turequire.Error(t, b.TransferFrom(spender, owner, dest, 10000000), ErrInsufficientAllowance.Error(), \"should not be able to transfer more than approved\")\n\n\ttests := []struct {\n\t\tspend uint64\n\t\texp uint64\n\t}{\n\t\t{3, 4999997},\n\t\t{999997, 4000000},\n\t\t{4000000, 0},\n\t}\n\n\tfor _, tt := range tests {\n\t\tb0 := b.BalanceOf(dest)\n\t\turequire.NoError(t, b.TransferFrom(spender, owner, dest, tt.spend))\n\t\ta := b.Allowance(owner, spender)\n\t\turequire.Equal(t, a, tt.exp, ufmt.Sprintf(\"allowance exp: %d, got %d\", tt.exp, a))\n\t\tb := b.BalanceOf(dest)\n\t\texpB := b0 + tt.spend\n\t\turequire.Equal(t, b, expB, ufmt.Sprintf(\"balance exp: %d, got %d\", expB, b))\n\t}\n\n\turequire.Error(t, b.TransferFrom(spender, owner, dest, 1), \"no allowance\")\n\tkey := allowanceKey(owner, spender)\n\turequire.False(t, b.allowances.Has(key), \"allowance should be removed\")\n\turequire.Equal(t, b.Allowance(owner, spender), uint64(0), \"allowance should be 0\")\n}\n"},{"name":"token.gno","body":"package grc20\n\nimport (\n\t\"std\"\n)\n\n// token implements the Token interface.\n//\n// It is generated with Banker.Token().\n// It can safely be exposed publicly.\ntype token struct {\n\tbanker *Banker\n}\n\n// var _ Token = (*token)(nil)\nfunc (t *token) GetName() string { return t.banker.name }\nfunc (t *token) GetSymbol() string { return t.banker.symbol }\nfunc (t *token) GetDecimals() uint { return t.banker.decimals }\nfunc (t *token) TotalSupply() uint64 { return t.banker.totalSupply }\n\nfunc (t *token) BalanceOf(owner std.Address) uint64 {\n\treturn t.banker.BalanceOf(owner)\n}\n\nfunc (t *token) Transfer(to std.Address, amount uint64) error {\n\tcaller := std.PrevRealm().Addr()\n\treturn t.banker.Transfer(caller, to, amount)\n}\n\nfunc (t *token) Allowance(owner, spender std.Address) uint64 {\n\treturn t.banker.Allowance(owner, spender)\n}\n\nfunc (t *token) Approve(spender std.Address, amount uint64) error {\n\tcaller := std.PrevRealm().Addr()\n\treturn t.banker.Approve(caller, spender, amount)\n}\n\nfunc (t *token) TransferFrom(from, to std.Address, amount uint64) error {\n\tspender := std.PrevRealm().Addr()\n\tif err := t.banker.SpendAllowance(from, spender, amount); err != nil {\n\t\treturn err\n\t}\n\treturn t.banker.Transfer(from, to, amount)\n}\n"},{"name":"token_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/ufmt\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestUserTokenImpl(t *testing.T) {\n\tbank := NewBanker(\"Dummy\", \"DUMMY\", 4)\n\ttok := bank.Token()\n\t_ = tok\n}\n\nfunc TestUserApprove(t *testing.T) {\n\towner := testutils.TestAddress(\"owner\")\n\tspender := testutils.TestAddress(\"spender\")\n\tdest := testutils.TestAddress(\"dest\")\n\n\tbank := NewBanker(\"Dummy\", \"DUMMY\", 6)\n\ttok := bank.Token()\n\n\t// Set owner as the original caller\n\tstd.TestSetOrigCaller(owner)\n\t// Mint 100000000 tokens for owner\n\turequire.NoError(t, bank.Mint(owner, 100000000))\n\n\t// Approve spender to spend 5000000 tokens\n\turequire.NoError(t, tok.Approve(spender, 5000000))\n\n\t// Set spender as the original caller\n\tstd.TestSetOrigCaller(spender)\n\t// Try to transfer 10000000 tokens from owner to dest, should fail because it exceeds allowance\n\turequire.Error(t,\n\t\ttok.TransferFrom(owner, dest, 10000000),\n\t\tErrInsufficientAllowance.Error(),\n\t\t\"should not be able to transfer more than approved\",\n\t)\n\n\t// Define a set of test data with spend amount and expected remaining allowance\n\ttests := []struct {\n\t\tspend uint64 // Spend amount\n\t\texp uint64 // Remaining allowance\n\t}{\n\t\t{3, 4999997},\n\t\t{999997, 4000000},\n\t\t{4000000, 0},\n\t}\n\n\t// perform transfer operation,and check if allowance and balance are correct\n\tfor _, tt := range tests {\n\t\tb0 := tok.BalanceOf(dest)\n\t\t// Perform transfer from owner to dest\n\t\turequire.NoError(t, tok.TransferFrom(owner, dest, tt.spend))\n\t\ta := tok.Allowance(owner, spender)\n\t\t// Check if allowance equals expected value\n\t\turequire.True(t, a == tt.exp, ufmt.Sprintf(\"allowance exp: %d,got %d\", tt.exp, a))\n\n\t\t// Get dest current balance\n\t\tb := tok.BalanceOf(dest)\n\t\t// Calculate expected balance ,should be initial balance plus transfer amount\n\t\texpB := b0 + tt.spend\n\t\t// Check if balance equals expected value\n\t\turequire.True(t, b == expB, ufmt.Sprintf(\"balance exp: %d,got %d\", expB, b))\n\t}\n\n\t// Try to transfer one token from owner to dest ,should fail because no allowance left\n\turequire.Error(t, tok.TransferFrom(owner, dest, 1), ErrInsufficientAllowance.Error(), \"no allowance\")\n}\n"},{"name":"types.gno","body":"package grc20\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/grc/exts\"\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)\n\ntype Token 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 the risk\n\t// that someone may use both the old and the new allowance by unfortunate\n\t// transaction ordering. One possible solution to mitigate this race\n\t// condition is to first reduce the spender's allowance to 0 and set the\n\t// 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\nconst (\n\tMintEvent = \"Mint\"\n\tBurnEvent = \"Burn\"\n\tTransferEvent = \"Transfer\"\n\tApprovalEvent = \"Approval\"\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":"grc721","path":"gno.land/p/demo/grc/grc721","files":[{"name":"basic_nft.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype basicNFT struct {\n\tname string\n\tsymbol string\n\towners avl.Tree // tokenId -\u003e OwnerAddress\n\tbalances avl.Tree // OwnerAddress -\u003e TokenCount\n\ttokenApprovals avl.Tree // TokenId -\u003e ApprovedAddress\n\ttokenURIs avl.Tree // TokenId -\u003e URIs\n\toperatorApprovals avl.Tree // \"OwnerAddress:OperatorAddress\" -\u003e bool\n}\n\n// Returns new basic NFT\nfunc NewBasicNFT(name string, symbol string) *basicNFT {\n\treturn \u0026basicNFT{\n\t\tname: name,\n\t\tsymbol: symbol,\n\n\t\towners: avl.Tree{},\n\t\tbalances: avl.Tree{},\n\t\ttokenApprovals: avl.Tree{},\n\t\ttokenURIs: avl.Tree{},\n\t\toperatorApprovals: avl.Tree{},\n\t}\n}\n\nfunc (s *basicNFT) Name() string { return s.name }\nfunc (s *basicNFT) Symbol() string { return s.symbol }\nfunc (s *basicNFT) TokenCount() uint64 { return uint64(s.owners.Size()) }\n\n// BalanceOf returns balance of input address\nfunc (s *basicNFT) BalanceOf(addr std.Address) (uint64, error) {\n\tif err := isValidAddress(addr); err != nil {\n\t\treturn 0, err\n\t}\n\n\tbalance, found := s.balances.Get(addr.String())\n\tif !found {\n\t\treturn 0, nil\n\t}\n\n\treturn balance.(uint64), nil\n}\n\n// OwnerOf returns owner of input token id\nfunc (s *basicNFT) OwnerOf(tid TokenID) (std.Address, error) {\n\towner, found := s.owners.Get(string(tid))\n\tif !found {\n\t\treturn \"\", ErrInvalidTokenId\n\t}\n\n\treturn owner.(std.Address), nil\n}\n\n// TokenURI returns the URI of input token id\nfunc (s *basicNFT) TokenURI(tid TokenID) (string, error) {\n\turi, found := s.tokenURIs.Get(string(tid))\n\tif !found {\n\t\treturn \"\", ErrInvalidTokenId\n\t}\n\n\treturn uri.(string), nil\n}\n\nfunc (s *basicNFT) SetTokenURI(tid TokenID, tURI TokenURI) (bool, error) {\n\t// check for invalid TokenID\n\tif !s.exists(tid) {\n\t\treturn false, ErrInvalidTokenId\n\t}\n\n\t// check for the right owner\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn false, ErrCallerIsNotOwner\n\t}\n\ts.tokenURIs.Set(string(tid), string(tURI))\n\treturn true, nil\n}\n\n// IsApprovedForAll returns true if operator is approved for all by the owner.\n// Otherwise, returns false\nfunc (s *basicNFT) IsApprovedForAll(owner, operator std.Address) bool {\n\tkey := owner.String() + \":\" + operator.String()\n\t_, found := s.operatorApprovals.Get(key)\n\tif !found {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// Approve approves the input address for particular token\nfunc (s *basicNFT) Approve(to std.Address, tid TokenID) error {\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner == to {\n\t\treturn ErrApprovalToCurrentOwner\n\t}\n\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner \u0026\u0026 !s.IsApprovedForAll(owner, caller) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\ts.tokenApprovals.Set(string(tid), to.String())\n\tevent := ApprovalEvent{owner, to, tid}\n\temit(\u0026event)\n\n\treturn nil\n}\n\n// GetApproved return the approved address for token\nfunc (s *basicNFT) GetApproved(tid TokenID) (std.Address, error) {\n\taddr, found := s.tokenApprovals.Get(string(tid))\n\tif !found {\n\t\treturn zeroAddress, ErrTokenIdNotHasApproved\n\t}\n\n\treturn std.Address(addr.(string)), nil\n}\n\n// SetApprovalForAll can approve the operator to operate on all tokens\nfunc (s *basicNFT) SetApprovalForAll(operator std.Address, approved bool) error {\n\tif err := isValidAddress(operator); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tcaller := std.PrevRealm().Addr()\n\treturn s.setApprovalForAll(caller, operator, approved)\n}\n\n// Safely transfers `tokenId` token from `from` to `to`, checking that\n// contract recipients are aware of the GRC721 protocol to prevent\n// tokens from being forever locked.\nfunc (s *basicNFT) SafeTransferFrom(from, to std.Address, tid TokenID) error {\n\tcaller := std.PrevRealm().Addr()\n\tif !s.isApprovedOrOwner(caller, tid) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.transfer(from, to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.checkOnGRC721Received(from, to, tid) {\n\t\treturn ErrTransferToNonGRC721Receiver\n\t}\n\n\treturn nil\n}\n\n// Transfers `tokenId` token from `from` to `to`.\nfunc (s *basicNFT) TransferFrom(from, to std.Address, tid TokenID) error {\n\tcaller := std.PrevRealm().Addr()\n\tif !s.isApprovedOrOwner(caller, tid) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.transfer(from, to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// Mints `tokenId` and transfers it to `to`.\nfunc (s *basicNFT) Mint(to std.Address, tid TokenID) error {\n\treturn s.mint(to, tid)\n}\n\n// Mints `tokenId` and transfers it to `to`. Also checks that\n// contract recipients are using GRC721 protocol\nfunc (s *basicNFT) SafeMint(to std.Address, tid TokenID) error {\n\terr := s.mint(to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.checkOnGRC721Received(zeroAddress, to, tid) {\n\t\treturn ErrTransferToNonGRC721Receiver\n\t}\n\n\treturn nil\n}\n\nfunc (s *basicNFT) Burn(tid TokenID) error {\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ts.beforeTokenTransfer(owner, zeroAddress, tid, 1)\n\n\ts.tokenApprovals.Remove(string(tid))\n\tbalance, err := s.BalanceOf(owner)\n\tif err != nil {\n\t\treturn err\n\t}\n\tbalance -= 1\n\ts.balances.Set(owner.String(), balance)\n\ts.owners.Remove(string(tid))\n\n\tevent := TransferEvent{owner, zeroAddress, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(owner, zeroAddress, tid, 1)\n\n\treturn nil\n}\n\n/* Helper methods */\n\n// Helper for SetApprovalForAll()\nfunc (s *basicNFT) setApprovalForAll(owner, operator std.Address, approved bool) error {\n\tif owner == operator {\n\t\treturn ErrApprovalToCurrentOwner\n\t}\n\n\tkey := owner.String() + \":\" + operator.String()\n\ts.operatorApprovals.Set(key, approved)\n\n\tevent := ApprovalForAllEvent{owner, operator, approved}\n\temit(\u0026event)\n\n\treturn nil\n}\n\n// Helper for TransferFrom() and SafeTransferFrom()\nfunc (s *basicNFT) transfer(from, to std.Address, tid TokenID) error {\n\tif err := isValidAddress(from); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\tif err := isValidAddress(to); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tif from == to {\n\t\treturn ErrCannotTransferToSelf\n\t}\n\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner != from {\n\t\treturn ErrTransferFromIncorrectOwner\n\t}\n\n\ts.beforeTokenTransfer(from, to, tid, 1)\n\n\t// Check that tokenId was not transferred by `beforeTokenTransfer`\n\towner, err = s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner != from {\n\t\treturn ErrTransferFromIncorrectOwner\n\t}\n\n\ts.tokenApprovals.Remove(string(tid))\n\tfromBalance, err := s.BalanceOf(from)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance, err := s.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfromBalance -= 1\n\ttoBalance += 1\n\ts.balances.Set(from.String(), fromBalance)\n\ts.balances.Set(to.String(), toBalance)\n\ts.owners.Set(string(tid), to)\n\n\tevent := TransferEvent{from, to, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(from, to, tid, 1)\n\n\treturn nil\n}\n\n// Helper for Mint() and SafeMint()\nfunc (s *basicNFT) mint(to std.Address, tid TokenID) error {\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\tif s.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ts.beforeTokenTransfer(zeroAddress, to, tid, 1)\n\n\t// Check that tokenId was not minted by `beforeTokenTransfer`\n\tif s.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ttoBalance, err := s.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance += 1\n\ts.balances.Set(to.String(), toBalance)\n\ts.owners.Set(string(tid), to)\n\n\tevent := TransferEvent{zeroAddress, to, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(zeroAddress, to, tid, 1)\n\n\treturn nil\n}\n\nfunc (s *basicNFT) isApprovedOrOwner(addr std.Address, tid TokenID) bool {\n\towner, found := s.owners.Get(string(tid))\n\tif !found {\n\t\treturn false\n\t}\n\n\tif addr == owner.(std.Address) || s.IsApprovedForAll(owner.(std.Address), addr) {\n\t\treturn true\n\t}\n\n\t_, err := s.GetApproved(tid)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// Checks if token id already exists\nfunc (s *basicNFT) exists(tid TokenID) bool {\n\t_, found := s.owners.Get(string(tid))\n\treturn found\n}\n\nfunc (s *basicNFT) beforeTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicNFT) afterTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicNFT) checkOnGRC721Received(from, to std.Address, tid TokenID) bool {\n\t// TODO: Implementation\n\treturn true\n}\n\nfunc (s *basicNFT) RenderHome() (str string) {\n\tstr += ufmt.Sprintf(\"# %s ($%s)\\n\\n\", s.name, s.symbol)\n\tstr += ufmt.Sprintf(\"* **Total supply**: %d\\n\", s.TokenCount())\n\tstr += ufmt.Sprintf(\"* **Known accounts**: %d\\n\", s.balances.Size())\n\n\treturn\n}\n"},{"name":"basic_nft_test.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\tdummyNFTName = \"DummyNFT\"\n\tdummyNFTSymbol = \"DNFT\"\n)\n\nfunc TestNewBasicNFT(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n}\n\nfunc TestName(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tname := dummy.Name()\n\tuassert.Equal(t, dummyNFTName, name)\n}\n\nfunc TestSymbol(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tsymbol := dummy.Symbol()\n\tuassert.Equal(t, dummyNFTSymbol, symbol)\n}\n\nfunc TestTokenCount(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcount := dummy.TokenCount()\n\tuassert.Equal(t, uint64(0), count)\n\n\tdummy.mint(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\", TokenID(\"1\"))\n\tdummy.mint(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\", TokenID(\"2\"))\n\n\tcount = dummy.TokenCount()\n\tuassert.Equal(t, uint64(2), count)\n}\n\nfunc TestBalanceOf(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\tbalanceAddr1, err := dummy.BalanceOf(addr1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(0), balanceAddr1)\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\tdummy.mint(addr1, TokenID(\"2\"))\n\tdummy.mint(addr2, TokenID(\"3\"))\n\n\tbalanceAddr1, err = dummy.BalanceOf(addr1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tbalanceAddr2, err := dummy.BalanceOf(addr2)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tuassert.Equal(t, uint64(2), balanceAddr1)\n\tuassert.Equal(t, uint64(1), balanceAddr2)\n}\n\nfunc TestOwnerOf(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\towner, err := dummy.OwnerOf(TokenID(\"invalid\"))\n\tuassert.Error(t, err, \"should not result in error\")\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\tdummy.mint(addr2, TokenID(\"2\"))\n\n\t// Checking for token id \"1\"\n\towner, err = dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr1.String(), owner.String())\n\n\t// Checking for token id \"2\"\n\towner, err = dummy.OwnerOf(TokenID(\"2\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr2.String(), owner.String())\n}\n\nfunc TestIsApprovedForAll(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(addr1, addr2)\n\tuassert.False(t, isApprovedForAll)\n}\n\nfunc TestSetApprovalForAll(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(caller, addr)\n\tuassert.False(t, isApprovedForAll)\n\n\terr := dummy.SetApprovalForAll(addr, true)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tisApprovedForAll = dummy.IsApprovedForAll(caller, addr)\n\tuassert.True(t, isApprovedForAll)\n}\n\nfunc TestGetApproved(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tapprovedAddr, err := dummy.GetApproved(TokenID(\"invalid\"))\n\tuassert.Error(t, err, \"should result in error\")\n}\n\nfunc TestApprove(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\n\t_, err := dummy.GetApproved(TokenID(\"1\"))\n\tuassert.Error(t, err, \"should result in error\")\n\n\terr = dummy.Approve(addr, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tapprovedAddr, err := dummy.GetApproved(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should result in error\")\n\tuassert.Equal(t, addr.String(), approvedAddr.String())\n}\n\nfunc TestTransferFrom(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\tdummy.mint(caller, TokenID(\"2\"))\n\n\terr := dummy.TransferFrom(caller, addr, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should result in error\")\n\n\t// Check balance of caller after transfer\n\tbalanceOfCaller, err := dummy.BalanceOf(caller)\n\tuassert.NoError(t, err, \"should result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfCaller)\n\n\t// Check balance of addr after transfer\n\tbalanceOfAddr, err := dummy.BalanceOf(addr)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfAddr)\n\n\t// Check Owner of transferred Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should result in error\")\n\tuassert.Equal(t, addr.String(), owner.String())\n}\n\nfunc TestSafeTransferFrom(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\tdummy.mint(caller, TokenID(\"2\"))\n\n\terr := dummy.SafeTransferFrom(caller, addr, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check balance of caller after transfer\n\tbalanceOfCaller, err := dummy.BalanceOf(caller)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfCaller)\n\n\t// Check balance of addr after transfer\n\tbalanceOfAddr, err := dummy.BalanceOf(addr)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfAddr)\n\n\t// Check Owner of transferred Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr.String(), owner.String())\n}\n\nfunc TestMint(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\terr := dummy.Mint(addr1, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.Mint(addr1, TokenID(\"2\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.Mint(addr2, TokenID(\"3\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Try minting duplicate token id\n\terr = dummy.Mint(addr2, TokenID(\"1\"))\n\tuassert.Error(t, err, \"should not result in error\")\n\n\t// Check Owner of Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr1.String(), owner.String())\n}\n\nfunc TestBurn(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(addr, TokenID(\"1\"))\n\tdummy.mint(addr, TokenID(\"2\"))\n\n\terr := dummy.Burn(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check Owner of Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.Error(t, err, \"should result in error\")\n}\n\nfunc TestSetTokenURI(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\ttokenURI := \"http://example.com/token\"\n\n\tstd.TestSetOrigCaller(std.Address(addr1)) // addr1\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\t_, derr := dummy.SetTokenURI(TokenID(\"1\"), TokenURI(tokenURI))\n\tuassert.NoError(t, derr, \"should not result in error\")\n\n\t// Test case: Invalid token ID\n\t_, err := dummy.SetTokenURI(TokenID(\"3\"), TokenURI(tokenURI))\n\tuassert.ErrorIs(t, err, ErrInvalidTokenId)\n\n\tstd.TestSetOrigCaller(std.Address(addr2)) // addr2\n\n\t_, cerr := dummy.SetTokenURI(TokenID(\"1\"), TokenURI(tokenURI)) // addr2 trying to set URI for token 1\n\tuassert.ErrorIs(t, cerr, ErrCallerIsNotOwner)\n\n\t// Test case: Retrieving TokenURI\n\tstd.TestSetOrigCaller(std.Address(addr1)) // addr1\n\n\tdummyTokenURI, err := dummy.TokenURI(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"TokenURI error\")\n\tuassert.Equal(t, string(tokenURI), string(dummyTokenURI))\n}\n"},{"name":"errors.gno","body":"package grc721\n\nimport \"errors\"\n\nvar (\n\tErrInvalidTokenId = errors.New(\"invalid token id\")\n\tErrInvalidAddress = errors.New(\"invalid address\")\n\tErrTokenIdNotHasApproved = errors.New(\"token id not approved for anyone\")\n\tErrApprovalToCurrentOwner = errors.New(\"approval to current owner\")\n\tErrCallerIsNotOwner = errors.New(\"caller is not token owner\")\n\tErrCallerNotApprovedForAll = errors.New(\"caller is not approved for all\")\n\tErrCannotTransferToSelf = errors.New(\"cannot send transfer to self\")\n\tErrTransferFromIncorrectOwner = errors.New(\"transfer from incorrect owner\")\n\tErrTransferToNonGRC721Receiver = errors.New(\"transfer to non GRC721Receiver implementer\")\n\tErrCallerIsNotOwnerOrApproved = errors.New(\"caller is not token owner or approved\")\n\tErrTokenIdAlreadyExists = errors.New(\"token id already exists\")\n\n\t// ERC721Royalty\n\tErrInvalidRoyaltyPercentage = errors.New(\"invalid royalty percentage\")\n\tErrInvalidRoyaltyPaymentAddress = errors.New(\"invalid royalty paymentAddress\")\n\tErrCannotCalculateRoyaltyAmount = errors.New(\"cannot calculate royalty amount\")\n)\n"},{"name":"grc721_metadata.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n// metadataNFT represents an NFT with metadata extensions.\ntype metadataNFT struct {\n\t*basicNFT // Embedded basicNFT struct for basic NFT functionality\n\textensions *avl.Tree // AVL tree for storing metadata extensions\n}\n\n// Ensure that metadataNFT implements the IGRC721MetadataOnchain interface.\nvar _ IGRC721MetadataOnchain = (*metadataNFT)(nil)\n\n// NewNFTWithMetadata creates a new basic NFT with metadata extensions.\nfunc NewNFTWithMetadata(name string, symbol string) *metadataNFT {\n\t// Create a new basic NFT\n\tnft := NewBasicNFT(name, symbol)\n\n\t// Return a metadataNFT with basicNFT embedded and an empty AVL tree for extensions\n\treturn \u0026metadataNFT{\n\t\tbasicNFT: nft,\n\t\textensions: avl.NewTree(),\n\t}\n}\n\n// SetTokenMetadata sets metadata for a given token ID.\nfunc (s *metadataNFT) SetTokenMetadata(tid TokenID, metadata Metadata) error {\n\t// Check if the caller is the owner of the token\n\towner, err := s.basicNFT.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn ErrCallerIsNotOwner\n\t}\n\n\t// Set the metadata for the token ID in the extensions AVL tree\n\ts.extensions.Set(string(tid), metadata)\n\treturn nil\n}\n\n// TokenMetadata retrieves metadata for a given token ID.\nfunc (s *metadataNFT) TokenMetadata(tid TokenID) (Metadata, error) {\n\t// Retrieve metadata from the extensions AVL tree\n\tmetadata, found := s.extensions.Get(string(tid))\n\tif !found {\n\t\treturn Metadata{}, ErrInvalidTokenId\n\t}\n\n\treturn metadata.(Metadata), nil\n}\n\n// mint mints a new token and assigns it to the specified address.\nfunc (s *metadataNFT) mint(to std.Address, tid TokenID) error {\n\t// Check if the address is valid\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\t// Check if the token ID already exists\n\tif s.basicNFT.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ts.basicNFT.beforeTokenTransfer(zeroAddress, to, tid, 1)\n\n\t// Check if the token ID was minted by beforeTokenTransfer\n\tif s.basicNFT.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\t// Increment balance of the recipient address\n\ttoBalance, err := s.basicNFT.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance += 1\n\ts.basicNFT.balances.Set(to.String(), toBalance)\n\n\t// Set owner of the token ID to the recipient address\n\ts.basicNFT.owners.Set(string(tid), to)\n\n\t// Emit transfer event\n\tevent := TransferEvent{zeroAddress, to, tid}\n\temit(\u0026event)\n\n\ts.basicNFT.afterTokenTransfer(zeroAddress, to, tid, 1)\n\n\treturn nil\n}\n"},{"name":"grc721_metadata_test.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestSetMetadata(t *testing.T) {\n\t// Create a new dummy NFT with metadata\n\tdummy := NewNFTWithMetadata(dummyNFTName, dummyNFTSymbol)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n\n\t// Define addresses for testing purposes\n\taddr1 := testutils.TestAddress(\"alice\")\n\taddr2 := testutils.TestAddress(\"bob\")\n\n\t// Define metadata attributes\n\tname := \"test\"\n\tdescription := \"test\"\n\timage := \"test\"\n\timageData := \"test\"\n\texternalURL := \"test\"\n\tattributes := []Trait{}\n\tbackgroundColor := \"test\"\n\tanimationURL := \"test\"\n\tyoutubeURL := \"test\"\n\n\t// Set the original caller to addr1\n\tstd.TestSetOrigCaller(addr1) // addr1\n\n\t// Mint a new token for addr1\n\tdummy.mint(addr1, TokenID(\"1\"))\n\n\t// Set metadata for token 1\n\tderr := dummy.SetTokenMetadata(TokenID(\"1\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if there was an error setting metadata\n\tuassert.NoError(t, derr, \"Should not result in error\")\n\n\t// Test case: Invalid token ID\n\terr := dummy.SetTokenMetadata(TokenID(\"3\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if the error returned matches the expected error\n\tuassert.ErrorIs(t, err, ErrInvalidTokenId)\n\n\t// Set the original caller to addr2\n\tstd.TestSetOrigCaller(addr2) // addr2\n\n\t// Try to set metadata for token 1 from addr2 (should fail)\n\tcerr := dummy.SetTokenMetadata(TokenID(\"1\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if the error returned matches the expected error\n\tuassert.ErrorIs(t, cerr, ErrCallerIsNotOwner)\n\n\t// Set the original caller back to addr1\n\tstd.TestSetOrigCaller(addr1) // addr1\n\n\t// Retrieve metadata for token 1\n\tdummyMetadata, err := dummy.TokenMetadata(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"Metadata error\")\n\n\t// Check if metadata attributes match expected values\n\tuassert.Equal(t, image, dummyMetadata.Image)\n\tuassert.Equal(t, imageData, dummyMetadata.ImageData)\n\tuassert.Equal(t, externalURL, dummyMetadata.ExternalURL)\n\tuassert.Equal(t, description, dummyMetadata.Description)\n\tuassert.Equal(t, name, dummyMetadata.Name)\n\tuassert.Equal(t, len(attributes), len(dummyMetadata.Attributes))\n\tuassert.Equal(t, backgroundColor, dummyMetadata.BackgroundColor)\n\tuassert.Equal(t, animationURL, dummyMetadata.AnimationURL)\n\tuassert.Equal(t, youtubeURL, dummyMetadata.YoutubeURL)\n}\n"},{"name":"grc721_royalty.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n// royaltyNFT represents a non-fungible token (NFT) with royalty functionality.\ntype royaltyNFT struct {\n\t*metadataNFT // Embedding metadataNFT for NFT functionality\n\ttokenRoyaltyInfo *avl.Tree // AVL tree to store royalty information for each token\n\tmaxRoyaltyPercentage uint64 // maxRoyaltyPercentage represents the maximum royalty percentage that can be charged every sale\n}\n\n// Ensure that royaltyNFT implements the IGRC2981 interface.\nvar _ IGRC2981 = (*royaltyNFT)(nil)\n\n// NewNFTWithRoyalty creates a new royalty NFT with the specified name, symbol, and royalty calculator.\nfunc NewNFTWithRoyalty(name string, symbol string) *royaltyNFT {\n\t// Create a new NFT with metadata\n\tnft := NewNFTWithMetadata(name, symbol)\n\n\treturn \u0026royaltyNFT{\n\t\tmetadataNFT: nft,\n\t\ttokenRoyaltyInfo: avl.NewTree(),\n\t\tmaxRoyaltyPercentage: 100,\n\t}\n}\n\n// SetTokenRoyalty sets the royalty information for a specific token ID.\nfunc (r *royaltyNFT) SetTokenRoyalty(tid TokenID, royaltyInfo RoyaltyInfo) error {\n\t// Validate the payment address\n\tif err := isValidAddress(royaltyInfo.PaymentAddress); err != nil {\n\t\treturn ErrInvalidRoyaltyPaymentAddress\n\t}\n\n\t// Check if royalty percentage exceeds maxRoyaltyPercentage\n\tif royaltyInfo.Percentage \u003e r.maxRoyaltyPercentage {\n\t\treturn ErrInvalidRoyaltyPercentage\n\t}\n\n\t// Check if the caller is the owner of the token\n\towner, err := r.metadataNFT.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn ErrCallerIsNotOwner\n\t}\n\n\t// Set royalty information for the token\n\tr.tokenRoyaltyInfo.Set(string(tid), royaltyInfo)\n\n\treturn nil\n}\n\n// RoyaltyInfo returns the royalty information for the given token ID and sale price.\nfunc (r *royaltyNFT) RoyaltyInfo(tid TokenID, salePrice uint64) (std.Address, uint64, error) {\n\t// Retrieve royalty information for the token\n\tval, found := r.tokenRoyaltyInfo.Get(string(tid))\n\tif !found {\n\t\treturn \"\", 0, ErrInvalidTokenId\n\t}\n\n\troyaltyInfo := val.(RoyaltyInfo)\n\n\t// Calculate royalty amount\n\troyaltyAmount, _ := r.calculateRoyaltyAmount(salePrice, royaltyInfo.Percentage)\n\n\treturn royaltyInfo.PaymentAddress, royaltyAmount, nil\n}\n\nfunc (r *royaltyNFT) calculateRoyaltyAmount(salePrice, percentage uint64) (uint64, error) {\n\troyaltyAmount := (salePrice * percentage) / 100\n\treturn royaltyAmount, nil\n}\n"},{"name":"grc721_royalty_test.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestSetTokenRoyalty(t *testing.T) {\n\tdummy := NewNFTWithRoyalty(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := testutils.TestAddress(\"alice\")\n\taddr2 := testutils.TestAddress(\"bob\")\n\n\tpaymentAddress := testutils.TestAddress(\"john\")\n\tpercentage := uint64(10) // 10%\n\n\tsalePrice := uint64(1000)\n\texpectRoyaltyAmount := uint64(100)\n\n\tstd.TestSetOrigCaller(addr1) // addr1\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\n\tderr := dummy.SetTokenRoyalty(TokenID(\"1\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tuassert.NoError(t, derr, \"Should not result in error\")\n\n\t// Test case: Invalid token ID\n\terr := dummy.SetTokenRoyalty(TokenID(\"3\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tuassert.ErrorIs(t, derr, ErrInvalidTokenId)\n\n\tstd.TestSetOrigCaller(addr2) // addr2\n\n\tcerr := dummy.SetTokenRoyalty(TokenID(\"1\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tuassert.ErrorIs(t, cerr, ErrCallerIsNotOwner)\n\n\t// Test case: Invalid payment address\n\taerr := dummy.SetTokenRoyalty(TokenID(\"4\"), RoyaltyInfo{\n\t\tPaymentAddress: std.Address(\"###\"), // invalid address\n\t\tPercentage: percentage,\n\t})\n\tuassert.ErrorIs(t, aerr, ErrInvalidRoyaltyPaymentAddress)\n\n\t// Test case: Invalid percentage\n\tperr := dummy.SetTokenRoyalty(TokenID(\"5\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: uint64(200), // over maxRoyaltyPercentage\n\t})\n\tuassert.ErrorIs(t, perr, ErrInvalidRoyaltyPercentage)\n\n\t// Test case: Retrieving Royalty Info\n\tstd.TestSetOrigCaller(addr1) // addr1\n\n\tdummyPaymentAddress, dummyRoyaltyAmount, rerr := dummy.RoyaltyInfo(TokenID(\"1\"), salePrice)\n\tuassert.NoError(t, rerr, \"RoyaltyInfo error\")\n\tuassert.Equal(t, paymentAddress, dummyPaymentAddress)\n\tuassert.Equal(t, expectRoyaltyAmount, dummyRoyaltyAmount)\n}\n"},{"name":"igrc721.gno","body":"package grc721\n\nimport \"std\"\n\ntype IGRC721 interface {\n\tBalanceOf(owner std.Address) (uint64, error)\n\tOwnerOf(tid TokenID) (std.Address, error)\n\tSetTokenURI(tid TokenID, tURI TokenURI) (bool, error)\n\tSafeTransferFrom(from, to std.Address, tid TokenID) error\n\tTransferFrom(from, to std.Address, tid TokenID) error\n\tApprove(approved std.Address, tid TokenID) error\n\tSetApprovalForAll(operator std.Address, approved bool) error\n\tGetApproved(tid TokenID) (std.Address, error)\n\tIsApprovedForAll(owner, operator std.Address) bool\n}\n\ntype (\n\tTokenID string\n\tTokenURI string\n)\n\ntype TransferEvent struct {\n\tFrom std.Address\n\tTo std.Address\n\tTokenID TokenID\n}\n\ntype ApprovalEvent struct {\n\tOwner std.Address\n\tApproved std.Address\n\tTokenID TokenID\n}\n\ntype ApprovalForAllEvent struct {\n\tOwner std.Address\n\tOperator std.Address\n\tApproved bool\n}\n"},{"name":"igrc721_metadata.gno","body":"package grc721\n\n// IGRC721CollectionMetadata describes basic information about an NFT collection.\ntype IGRC721CollectionMetadata interface {\n\tName() string // Name returns the name of the collection.\n\tSymbol() string // Symbol returns the symbol of the collection.\n}\n\n// IGRC721Metadata follows the Ethereum standard\ntype IGRC721Metadata interface {\n\tIGRC721CollectionMetadata\n\tTokenURI(tid TokenID) (string, error) // TokenURI returns the URI of a specific token.\n}\n\n// IGRC721Metadata follows the OpenSea metadata standard\ntype IGRC721MetadataOnchain interface {\n\tIGRC721CollectionMetadata\n\tTokenMetadata(tid TokenID) (Metadata, error)\n}\n\ntype Trait struct {\n\tDisplayType string\n\tTraitType string\n\tValue string\n}\n\n// see: https://docs.opensea.io/docs/metadata-standards\ntype Metadata struct {\n\tImage string // URL to the image of the item. Can be any type of image (including SVGs, which will be cached into PNGs by OpenSea), IPFS or Arweave URLs or paths. We recommend using a minimum 3000 x 3000 image.\n\tImageData string // Raw SVG image data, if you want to generate images on the fly (not recommended). Only use this if you're not including the image parameter.\n\tExternalURL string // URL that will appear below the asset's image on OpenSea and will allow users to leave OpenSea and view the item on your site.\n\tDescription string // Human-readable description of the item. Markdown is supported.\n\tName string // Name of the item.\n\tAttributes []Trait // Attributes for the item, which will show up on the OpenSea page for the item.\n\tBackgroundColor string // Background color of the item on OpenSea. Must be a six-character hexadecimal without a pre-pended #\n\tAnimationURL string // URL to a multimedia attachment for the item. Supported file extensions: GLTF, GLB, WEBM, MP4, M4V, OGV, OGG, MP3, WAV, OGA, HTML (for rich experiences and interactive NFTs using JavaScript canvas, WebGL, etc.). Scripts and relative paths within the HTML page are now supported. Access to browser extensions is not supported.\n\tYoutubeURL string // URL to a YouTube video (only used if animation_url is not provided).\n}\n"},{"name":"igrc721_royalty.gno","body":"package grc721\n\nimport \"std\"\n\n// IGRC2981 follows the Ethereum standard\ntype IGRC2981 interface {\n\t// RoyaltyInfo retrieves royalty information for a tokenID and salePrice.\n\t// It returns the payment address, royalty amount, and an error if any.\n\tRoyaltyInfo(tokenID TokenID, salePrice uint64) (std.Address, uint64, error)\n}\n\n// RoyaltyInfo represents royalty information for a token.\ntype RoyaltyInfo struct {\n\tPaymentAddress std.Address // PaymentAddress is the address where royalty payment should be sent.\n\tPercentage uint64 // Percentage is the royalty percentage. It indicates the percentage of royalty to be paid for each sale. For example : Percentage = 10 =\u003e 10%\n}\n"},{"name":"util.gno","body":"package grc721\n\nimport (\n\t\"std\"\n)\n\nvar zeroAddress = std.Address(\"\")\n\nfunc isValidAddress(addr std.Address) error {\n\tif !addr.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\treturn nil\n}\n\nfunc emit(event interface{}) {\n\t// TODO: setup a pubsub system here?\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":"grc1155","path":"gno.land/p/demo/grc/grc1155","files":[{"name":"README.md","body":"# GRC-1155 Spec: Multi Token Standard\n\nGRC1155 is a specification for managing multiple tokens based on Gnoland. The name and design is based on Ethereum's ERC1155 standard.\n\n## See also:\n\n[ERC-1155 Spec][erc-1155]\n\n[erc-1155]: https://eips.ethereum.org/EIPS/eip-1155"},{"name":"basic_grc1155_token.gno","body":"package grc1155\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype basicGRC1155Token struct {\n\turi string\n\tbalances avl.Tree // \"TokenId:Address\" -\u003e uint64\n\toperatorApprovals avl.Tree // \"OwnerAddress:OperatorAddress\" -\u003e bool\n}\n\nvar _ IGRC1155 = (*basicGRC1155Token)(nil)\n\n// Returns new basic GRC1155 token\nfunc NewBasicGRC1155Token(uri string) *basicGRC1155Token {\n\treturn \u0026basicGRC1155Token{\n\t\turi: uri,\n\t\tbalances: avl.Tree{},\n\t\toperatorApprovals: avl.Tree{},\n\t}\n}\n\nfunc (s *basicGRC1155Token) Uri() string { return s.uri }\n\n// BalanceOf returns the input address's balance of the token type requested\nfunc (s *basicGRC1155Token) BalanceOf(addr std.Address, tid TokenID) (uint64, error) {\n\tif !isValidAddress(addr) {\n\t\treturn 0, ErrInvalidAddress\n\t}\n\n\tkey := string(tid) + \":\" + addr.String()\n\tbalance, found := s.balances.Get(key)\n\tif !found {\n\t\treturn 0, nil\n\t}\n\n\treturn balance.(uint64), nil\n}\n\n// BalanceOfBatch returns the balance of multiple account/token pairs\nfunc (s *basicGRC1155Token) BalanceOfBatch(owners []std.Address, batch []TokenID) ([]uint64, error) {\n\tif len(owners) != len(batch) {\n\t\treturn nil, ErrMismatchLength\n\t}\n\n\tbalanceOfBatch := make([]uint64, len(owners))\n\n\tfor i := 0; i \u003c len(owners); i++ {\n\t\tbalanceOfBatch[i], _ = s.BalanceOf(owners[i], batch[i])\n\t}\n\n\treturn balanceOfBatch, nil\n}\n\n// SetApprovalForAll can approve the operator to operate on all tokens\nfunc (s *basicGRC1155Token) SetApprovalForAll(operator std.Address, approved bool) error {\n\tif !isValidAddress(operator) {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tcaller := std.OriginCaller()\n\treturn s.setApprovalForAll(caller, operator, approved)\n}\n\n// IsApprovedForAll returns true if operator is the owner or is approved for all by the owner.\n// Otherwise, returns false\nfunc (s *basicGRC1155Token) IsApprovedForAll(owner, operator std.Address) bool {\n\tif operator == owner {\n\t\treturn true\n\t}\n\tkey := owner.String() + \":\" + operator.String()\n\t_, found := s.operatorApprovals.Get(key)\n\tif !found {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// Safely transfers `tokenId` token from `from` to `to`, checking that\n// contract recipients are aware of the GRC1155 protocol to prevent\n// tokens from being forever locked.\nfunc (s *basicGRC1155Token) SafeTransferFrom(from, to std.Address, tid TokenID, amount uint64) error {\n\tcaller := std.OriginCaller()\n\tif !s.IsApprovedForAll(caller, from) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.safeBatchTransferFrom(from, to, []TokenID{tid}, []uint64{amount})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.doSafeTransferAcceptanceCheck(caller, from, to, tid, amount) {\n\t\treturn ErrTransferToRejectedOrNonGRC1155Receiver\n\t}\n\n\temit(\u0026TransferSingleEvent{caller, from, to, tid, amount})\n\n\treturn nil\n}\n\n// Safely transfers a `batch` of tokens from `from` to `to`, checking that\n// contract recipients are aware of the GRC1155 protocol to prevent\n// tokens from being forever locked.\nfunc (s *basicGRC1155Token) SafeBatchTransferFrom(from, to std.Address, batch []TokenID, amounts []uint64) error {\n\tcaller := std.OriginCaller()\n\tif !s.IsApprovedForAll(caller, from) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.safeBatchTransferFrom(from, to, batch, amounts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.doSafeBatchTransferAcceptanceCheck(caller, from, to, batch, amounts) {\n\t\treturn ErrTransferToRejectedOrNonGRC1155Receiver\n\t}\n\n\temit(\u0026TransferBatchEvent{caller, from, to, batch, amounts})\n\n\treturn nil\n}\n\n// Creates `amount` tokens of token type `id`, and assigns them to `to`. Also checks that\n// contract recipients are using GRC1155 protocol.\nfunc (s *basicGRC1155Token) SafeMint(to std.Address, tid TokenID, amount uint64) error {\n\tcaller := std.OriginCaller()\n\n\terr := s.mintBatch(to, []TokenID{tid}, []uint64{amount})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.doSafeTransferAcceptanceCheck(caller, zeroAddress, to, tid, amount) {\n\t\treturn ErrTransferToRejectedOrNonGRC1155Receiver\n\t}\n\n\temit(\u0026TransferSingleEvent{caller, zeroAddress, to, tid, amount})\n\n\treturn nil\n}\n\n// Batch version of `SafeMint()`. Also checks that\n// contract recipients are using GRC1155 protocol.\nfunc (s *basicGRC1155Token) SafeBatchMint(to std.Address, batch []TokenID, amounts []uint64) error {\n\tcaller := std.OriginCaller()\n\n\terr := s.mintBatch(to, batch, amounts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.doSafeBatchTransferAcceptanceCheck(caller, zeroAddress, to, batch, amounts) {\n\t\treturn ErrTransferToRejectedOrNonGRC1155Receiver\n\t}\n\n\temit(\u0026TransferBatchEvent{caller, zeroAddress, to, batch, amounts})\n\n\treturn nil\n}\n\n// Destroys `amount` tokens of token type `id` from `from`.\nfunc (s *basicGRC1155Token) Burn(from std.Address, tid TokenID, amount uint64) error {\n\tcaller := std.OriginCaller()\n\n\terr := s.burnBatch(from, []TokenID{tid}, []uint64{amount})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\temit(\u0026TransferSingleEvent{caller, from, zeroAddress, tid, amount})\n\n\treturn nil\n}\n\n// Batch version of `Burn()`\nfunc (s *basicGRC1155Token) BatchBurn(from std.Address, batch []TokenID, amounts []uint64) error {\n\tcaller := std.OriginCaller()\n\n\terr := s.burnBatch(from, batch, amounts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\temit(\u0026TransferBatchEvent{caller, from, zeroAddress, batch, amounts})\n\n\treturn nil\n}\n\n/* Helper methods */\n\n// Helper for SetApprovalForAll(): approve `operator` to operate on all of `owner` tokens\nfunc (s *basicGRC1155Token) setApprovalForAll(owner, operator std.Address, approved bool) error {\n\tif owner == operator {\n\t\treturn nil\n\t}\n\n\tkey := owner.String() + \":\" + operator.String()\n\tif approved {\n\t\ts.operatorApprovals.Set(key, approved)\n\t} else {\n\t\ts.operatorApprovals.Remove(key)\n\t}\n\n\temit(\u0026ApprovalForAllEvent{owner, operator, approved})\n\n\treturn nil\n}\n\n// Helper for SafeTransferFrom() and SafeBatchTransferFrom()\nfunc (s *basicGRC1155Token) safeBatchTransferFrom(from, to std.Address, batch []TokenID, amounts []uint64) error {\n\tif len(batch) != len(amounts) {\n\t\treturn ErrMismatchLength\n\t}\n\tif !isValidAddress(from) || !isValidAddress(to) {\n\t\treturn ErrInvalidAddress\n\t}\n\tif from == to {\n\t\treturn ErrCannotTransferToSelf\n\t}\n\n\tcaller := std.OriginCaller()\n\ts.beforeTokenTransfer(caller, from, to, batch, amounts)\n\n\tfor i := 0; i \u003c len(batch); i++ {\n\t\ttid := batch[i]\n\t\tamount := amounts[i]\n\t\tfromBalance, err := s.BalanceOf(from, tid)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif fromBalance \u003c amount {\n\t\t\treturn ErrInsufficientBalance\n\t\t}\n\t\ttoBalance, err := s.BalanceOf(to, tid)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfromBalance -= amount\n\t\ttoBalance += amount\n\t\tfromBalanceKey := string(tid) + \":\" + from.String()\n\t\ttoBalanceKey := string(tid) + \":\" + to.String()\n\t\ts.balances.Set(fromBalanceKey, fromBalance)\n\t\ts.balances.Set(toBalanceKey, toBalance)\n\t}\n\n\ts.afterTokenTransfer(caller, from, to, batch, amounts)\n\n\treturn nil\n}\n\n// Helper for SafeMint() and SafeBatchMint()\nfunc (s *basicGRC1155Token) mintBatch(to std.Address, batch []TokenID, amounts []uint64) error {\n\tif len(batch) != len(amounts) {\n\t\treturn ErrMismatchLength\n\t}\n\tif !isValidAddress(to) {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tcaller := std.OriginCaller()\n\ts.beforeTokenTransfer(caller, zeroAddress, to, batch, amounts)\n\n\tfor i := 0; i \u003c len(batch); i++ {\n\t\ttid := batch[i]\n\t\tamount := amounts[i]\n\t\ttoBalance, err := s.BalanceOf(to, tid)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ttoBalance += amount\n\t\ttoBalanceKey := string(tid) + \":\" + to.String()\n\t\ts.balances.Set(toBalanceKey, toBalance)\n\t}\n\n\ts.afterTokenTransfer(caller, zeroAddress, to, batch, amounts)\n\n\treturn nil\n}\n\n// Helper for Burn() and BurnBatch()\nfunc (s *basicGRC1155Token) burnBatch(from std.Address, batch []TokenID, amounts []uint64) error {\n\tif len(batch) != len(amounts) {\n\t\treturn ErrMismatchLength\n\t}\n\tif !isValidAddress(from) {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tcaller := std.OriginCaller()\n\ts.beforeTokenTransfer(caller, from, zeroAddress, batch, amounts)\n\n\tfor i := 0; i \u003c len(batch); i++ {\n\t\ttid := batch[i]\n\t\tamount := amounts[i]\n\t\tfromBalance, err := s.BalanceOf(from, tid)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif fromBalance \u003c amount {\n\t\t\treturn ErrBurnAmountExceedsBalance\n\t\t}\n\t\tfromBalance -= amount\n\t\tfromBalanceKey := string(tid) + \":\" + from.String()\n\t\ts.balances.Set(fromBalanceKey, fromBalance)\n\t}\n\n\ts.afterTokenTransfer(caller, from, zeroAddress, batch, amounts)\n\n\treturn nil\n}\n\nfunc (s *basicGRC1155Token) setUri(newUri string) {\n\ts.uri = newUri\n\temit(\u0026UpdateURIEvent{newUri})\n}\n\nfunc (s *basicGRC1155Token) beforeTokenTransfer(operator, from, to std.Address, batch []TokenID, amounts []uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicGRC1155Token) afterTokenTransfer(operator, from, to std.Address, batch []TokenID, amounts []uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicGRC1155Token) doSafeTransferAcceptanceCheck(operator, from, to std.Address, tid TokenID, amount uint64) bool {\n\t// TODO: Implementation\n\treturn true\n}\n\nfunc (s *basicGRC1155Token) doSafeBatchTransferAcceptanceCheck(operator, from, to std.Address, batch []TokenID, amounts []uint64) bool {\n\t// TODO: Implementation\n\treturn true\n}\n\nfunc (s *basicGRC1155Token) RenderHome() (str string) {\n\tstr += ufmt.Sprintf(\"# URI:%s\\n\", s.uri)\n\n\treturn\n}\n"},{"name":"basic_grc1155_token_test.gno","body":"package grc1155\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nconst dummyURI = \"ipfs://xyz\"\n\nfunc TestNewBasicGRC1155Token(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n}\n\nfunc TestUri(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\tuassert.Equal(t, dummyURI, dummy.Uri())\n}\n\nfunc TestBalanceOf(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\tbalanceZeroAddressOfToken1, err := dummy.BalanceOf(zeroAddress, tid1)\n\tuassert.Error(t, err, \"should result in error\")\n\n\tbalanceAddr1OfToken1, err := dummy.BalanceOf(addr1, tid1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(0), balanceAddr1OfToken1)\n\n\tdummy.mintBatch(addr1, []TokenID{tid1, tid2}, []uint64{10, 100})\n\tdummy.mintBatch(addr2, []TokenID{tid1}, []uint64{20})\n\n\tbalanceAddr1OfToken1, err = dummy.BalanceOf(addr1, tid1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tbalanceAddr1OfToken2, err := dummy.BalanceOf(addr1, tid2)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tbalanceAddr2OfToken1, err := dummy.BalanceOf(addr2, tid1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tuassert.Equal(t, uint64(10), balanceAddr1OfToken1)\n\tuassert.Equal(t, uint64(100), balanceAddr1OfToken2)\n\tuassert.Equal(t, uint64(20), balanceAddr2OfToken1)\n}\n\nfunc TestBalanceOfBatch(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{addr1, addr2}, []TokenID{tid1, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(0), balanceBatch[0])\n\tuassert.Equal(t, uint64(0), balanceBatch[1])\n\n\tdummy.mintBatch(addr1, []TokenID{tid1}, []uint64{10})\n\tdummy.mintBatch(addr2, []TokenID{tid2}, []uint64{20})\n\n\tbalanceBatch, err = dummy.BalanceOfBatch([]std.Address{addr1, addr2}, []TokenID{tid1, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(10), balanceBatch[0])\n\tuassert.Equal(t, uint64(20), balanceBatch[1])\n}\n\nfunc TestIsApprovedForAll(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(addr1, addr2)\n\tuassert.False(t, isApprovedForAll)\n}\n\nfunc TestSetApprovalForAll(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.OriginCaller()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(caller, addr)\n\tuassert.False(t, isApprovedForAll)\n\n\terr := dummy.SetApprovalForAll(addr, true)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tisApprovedForAll = dummy.IsApprovedForAll(caller, addr)\n\tuassert.True(t, isApprovedForAll)\n\n\terr = dummy.SetApprovalForAll(addr, false)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tisApprovedForAll = dummy.IsApprovedForAll(caller, addr)\n\tuassert.False(t, isApprovedForAll)\n}\n\nfunc TestSafeTransferFrom(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.OriginCaller()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\ttid := TokenID(\"1\")\n\n\tdummy.mintBatch(caller, []TokenID{tid}, []uint64{100})\n\n\terr := dummy.SafeTransferFrom(caller, zeroAddress, tid, 10)\n\tuassert.Error(t, err, \"should result in error\")\n\n\terr = dummy.SafeTransferFrom(caller, addr, tid, 160)\n\tuassert.Error(t, err, \"should result in error\")\n\n\terr = dummy.SafeTransferFrom(caller, addr, tid, 60)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check balance of caller after transfer\n\tbalanceOfCaller, err := dummy.BalanceOf(caller, tid)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(40), balanceOfCaller)\n\n\t// Check balance of addr after transfer\n\tbalanceOfAddr, err := dummy.BalanceOf(addr, tid)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(60), balanceOfAddr)\n}\n\nfunc TestSafeBatchTransferFrom(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.OriginCaller()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\tdummy.mintBatch(caller, []TokenID{tid1, tid2}, []uint64{10, 100})\n\n\terr := dummy.SafeBatchTransferFrom(caller, zeroAddress, []TokenID{tid1, tid2}, []uint64{4, 60})\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.SafeBatchTransferFrom(caller, addr, []TokenID{tid1, tid2}, []uint64{40, 60})\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.SafeBatchTransferFrom(caller, addr, []TokenID{tid1}, []uint64{40, 60})\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.SafeBatchTransferFrom(caller, addr, []TokenID{tid1, tid2}, []uint64{4, 60})\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{caller, addr, caller, addr}, []TokenID{tid1, tid1, tid2, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check token1's balance of caller after batch transfer\n\tuassert.Equal(t, uint64(6), balanceBatch[0])\n\n\t// Check token1's balance of addr after batch transfer\n\tuassert.Equal(t, uint64(4), balanceBatch[1])\n\n\t// Check token2's balance of caller after batch transfer\n\tuassert.Equal(t, uint64(40), balanceBatch[2])\n\n\t// Check token2's balance of addr after batch transfer\n\tuassert.Equal(t, uint64(60), balanceBatch[3])\n}\n\nfunc TestSafeMint(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\terr := dummy.SafeMint(zeroAddress, tid1, 100)\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.SafeMint(addr1, tid1, 100)\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.SafeMint(addr1, tid2, 200)\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.SafeMint(addr2, tid1, 50)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{addr1, addr2, addr1}, []TokenID{tid1, tid1, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\t// Check token1's balance of addr1 after mint\n\tuassert.Equal(t, uint64(100), balanceBatch[0])\n\t// Check token1's balance of addr2 after mint\n\tuassert.Equal(t, uint64(50), balanceBatch[1])\n\t// Check token2's balance of addr1 after mint\n\tuassert.Equal(t, uint64(200), balanceBatch[2])\n}\n\nfunc TestSafeBatchMint(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\terr := dummy.SafeBatchMint(zeroAddress, []TokenID{tid1, tid2}, []uint64{100, 200})\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.SafeBatchMint(addr1, []TokenID{tid1, tid2}, []uint64{100, 200})\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.SafeBatchMint(addr2, []TokenID{tid1, tid2}, []uint64{300, 400})\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{addr1, addr2, addr1, addr2}, []TokenID{tid1, tid1, tid2, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\t// Check token1's balance of addr1 after batch mint\n\tuassert.Equal(t, uint64(100), balanceBatch[0])\n\t// Check token1's balance of addr2 after batch mint\n\tuassert.Equal(t, uint64(300), balanceBatch[1])\n\t// Check token2's balance of addr1 after batch mint\n\tuassert.Equal(t, uint64(200), balanceBatch[2])\n\t// Check token2's balance of addr2 after batch mint\n\tuassert.Equal(t, uint64(400), balanceBatch[3])\n}\n\nfunc TestBurn(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\tdummy.mintBatch(addr, []TokenID{tid1, tid2}, []uint64{100, 200})\n\terr := dummy.Burn(zeroAddress, tid1, uint64(60))\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.Burn(addr, tid1, uint64(160))\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.Burn(addr, tid1, uint64(60))\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.Burn(addr, tid2, uint64(60))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{addr, addr}, []TokenID{tid1, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check token1's balance of addr after burn\n\tuassert.Equal(t, uint64(40), balanceBatch[0])\n\t// Check token2's balance of addr after burn\n\tuassert.Equal(t, uint64(140), balanceBatch[1])\n}\n\nfunc TestBatchBurn(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\tdummy.mintBatch(addr, []TokenID{tid1, tid2}, []uint64{100, 200})\n\terr := dummy.BatchBurn(zeroAddress, []TokenID{tid1, tid2}, []uint64{60, 60})\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.BatchBurn(addr, []TokenID{tid1, tid2}, []uint64{160, 60})\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.BatchBurn(addr, []TokenID{tid1, tid2}, []uint64{60, 60})\n\tuassert.NoError(t, err, \"should not result in error\")\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{addr, addr}, []TokenID{tid1, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check token1's balance of addr after batch burn\n\tuassert.Equal(t, uint64(40), balanceBatch[0])\n\t// Check token2's balance of addr after batch burn\n\tuassert.Equal(t, uint64(140), balanceBatch[1])\n}\n"},{"name":"errors.gno","body":"package grc1155\n\nimport \"errors\"\n\nvar (\n\tErrInvalidAddress = errors.New(\"invalid address\")\n\tErrMismatchLength = errors.New(\"accounts and ids length mismatch\")\n\tErrCannotTransferToSelf = errors.New(\"cannot send transfer to self\")\n\tErrTransferToRejectedOrNonGRC1155Receiver = errors.New(\"transfer to rejected or non GRC1155Receiver implementer\")\n\tErrCallerIsNotOwnerOrApproved = errors.New(\"caller is not token owner or approved\")\n\tErrInsufficientBalance = errors.New(\"insufficient balance for transfer\")\n\tErrBurnAmountExceedsBalance = errors.New(\"burn amount exceeds balance\")\n)\n"},{"name":"igrc1155.gno","body":"package grc1155\n\nimport \"std\"\n\ntype IGRC1155 interface {\n\tSafeTransferFrom(from, to std.Address, tid TokenID, amount uint64) error\n\tSafeBatchTransferFrom(from, to std.Address, batch []TokenID, amounts []uint64) error\n\tBalanceOf(owner std.Address, tid TokenID) (uint64, error)\n\tBalanceOfBatch(owners []std.Address, batch []TokenID) ([]uint64, error)\n\tSetApprovalForAll(operator std.Address, approved bool) error\n\tIsApprovedForAll(owner, operator std.Address) bool\n}\n\ntype TokenID string\n\ntype TransferSingleEvent struct {\n\tOperator std.Address\n\tFrom std.Address\n\tTo std.Address\n\tTokenID TokenID\n\tAmount uint64\n}\n\ntype TransferBatchEvent struct {\n\tOperator std.Address\n\tFrom std.Address\n\tTo std.Address\n\tBatch []TokenID\n\tAmounts []uint64\n}\n\ntype ApprovalForAllEvent struct {\n\tOwner std.Address\n\tOperator std.Address\n\tApproved bool\n}\n\ntype UpdateURIEvent struct {\n\tURI string\n}\n"},{"name":"util.gno","body":"package grc1155\n\nimport (\n\t\"std\"\n)\n\nconst zeroAddress std.Address = \"\"\n\nfunc isValidAddress(addr std.Address) bool {\n\tif !addr.IsValid() {\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc emit(event interface{}) {\n\t// TODO: setup a pubsub system here?\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":"banker.gno","body":"package grc20\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Banker implements a token banker with admin privileges.\n//\n// The Banker is intended to be used in two main ways:\n// 1. as a temporary object used to make the initial minting, then deleted.\n// 2. preserved in an unexported variable to support conditional administrative\n// tasks protected by the contract.\ntype Banker struct {\n\tname string\n\tsymbol string\n\tdecimals uint\n\ttotalSupply uint64\n\tbalances avl.Tree // std.Address(owner) -\u003e uint64\n\tallowances avl.Tree // string(owner+\":\"+spender) -\u003e uint64\n\ttoken *token // to share the same pointer\n}\n\nfunc NewBanker(name, symbol string, decimals uint) *Banker {\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\tb := Banker{\n\t\tname: name,\n\t\tsymbol: symbol,\n\t\tdecimals: decimals,\n\t}\n\tt := \u0026token{banker: \u0026b}\n\tb.token = t\n\treturn \u0026b\n}\n\nfunc (b Banker) Token() Token { return b.token } // Token returns a grc20 safe-object implementation.\nfunc (b Banker) GetName() string { return b.name }\nfunc (b Banker) GetSymbol() string { return b.symbol }\nfunc (b Banker) GetDecimals() uint { return b.decimals }\nfunc (b Banker) TotalSupply() uint64 { return b.totalSupply }\nfunc (b Banker) KnownAccounts() int { return b.balances.Size() }\n\nfunc (b *Banker) Mint(address std.Address, amount uint64) error {\n\tif !address.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\n\t// TODO: check for overflow\n\n\tb.totalSupply += amount\n\tcurrentBalance := b.BalanceOf(address)\n\tnewBalance := currentBalance + amount\n\n\tb.balances.Set(string(address), newBalance)\n\n\tstd.Emit(\n\t\tMintEvent,\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\nfunc (b *Banker) Burn(address std.Address, amount uint64) error {\n\tif !address.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\t// TODO: check for overflow\n\n\tcurrentBalance := b.BalanceOf(address)\n\tif currentBalance \u003c amount {\n\t\treturn ErrInsufficientBalance\n\t}\n\n\tb.totalSupply -= amount\n\tnewBalance := currentBalance - amount\n\n\tb.balances.Set(string(address), newBalance)\n\n\tstd.Emit(\n\t\tBurnEvent,\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\nfunc (b Banker) BalanceOf(address std.Address) uint64 {\n\tbalance, found := b.balances.Get(address.String())\n\tif !found {\n\t\treturn 0\n\t}\n\treturn balance.(uint64)\n}\n\nfunc (b *Banker) 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 := b.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\tb.allowances.Remove(key)\n\t} else {\n\t\tb.allowances.Set(key, newAllowance)\n\t}\n\n\treturn nil\n}\n\nfunc (b *Banker) 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\ttoBalance := b.BalanceOf(to)\n\tfromBalance := b.BalanceOf(from)\n\n\tif fromBalance \u003c amount {\n\t\treturn ErrInsufficientBalance\n\t}\n\n\tnewToBalance := toBalance + amount\n\tnewFromBalance := fromBalance - amount\n\n\tb.balances.Set(string(to), newToBalance)\n\tb.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\nfunc (b *Banker) TransferFrom(spender, from, to std.Address, amount uint64) error {\n\tif err := b.SpendAllowance(from, spender, amount); err != nil {\n\t\treturn err\n\t}\n\treturn b.Transfer(from, to, amount)\n}\n\nfunc (b *Banker) Allowance(owner, spender std.Address) uint64 {\n\tallowance, found := b.allowances.Get(allowanceKey(owner, spender))\n\tif !found {\n\t\treturn 0\n\t}\n\treturn allowance.(uint64)\n}\n\nfunc (b *Banker) Approve(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\tb.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\nfunc (b *Banker) RenderHome() string {\n\tstr := \"\"\n\tstr += ufmt.Sprintf(\"# %s ($%s)\\n\\n\", b.name, b.symbol)\n\tstr += ufmt.Sprintf(\"* **Decimals**: %d\\n\", b.decimals)\n\tstr += ufmt.Sprintf(\"* **Total supply**: %d\\n\", b.totalSupply)\n\tstr += ufmt.Sprintf(\"* **Known accounts**: %d\\n\", b.KnownAccounts())\n\treturn str\n}\n\nfunc allowanceKey(owner, spender std.Address) string {\n\treturn owner.String() + \":\" + spender.String()\n}\n"},{"name":"banker_test.gno","body":"package grc20\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestBankerImpl(t *testing.T) {\n\tdummy := NewBanker(\"Dummy\", \"DUMMY\", 4)\n\turequire.False(t, dummy == nil, \"dummy should not be nil\")\n}\n\nfunc TestAllowance(t *testing.T) {\n\tvar (\n\t\towner = testutils.TestAddress(\"owner\")\n\t\tspender = testutils.TestAddress(\"spender\")\n\t\tdest = testutils.TestAddress(\"dest\")\n\t)\n\n\tb := NewBanker(\"Dummy\", \"DUMMY\", 6)\n\turequire.NoError(t, b.Mint(owner, 100000000))\n\turequire.NoError(t, b.Approve(owner, spender, 5000000))\n\turequire.Error(t, b.TransferFrom(spender, owner, dest, 10000000), ErrInsufficientAllowance.Error(), \"should not be able to transfer more than approved\")\n\n\ttests := []struct {\n\t\tspend uint64\n\t\texp uint64\n\t}{\n\t\t{3, 4999997},\n\t\t{999997, 4000000},\n\t\t{4000000, 0},\n\t}\n\n\tfor _, tt := range tests {\n\t\tb0 := b.BalanceOf(dest)\n\t\turequire.NoError(t, b.TransferFrom(spender, owner, dest, tt.spend))\n\t\ta := b.Allowance(owner, spender)\n\t\turequire.Equal(t, a, tt.exp, ufmt.Sprintf(\"allowance exp: %d, got %d\", tt.exp, a))\n\t\tb := b.BalanceOf(dest)\n\t\texpB := b0 + tt.spend\n\t\turequire.Equal(t, b, expB, ufmt.Sprintf(\"balance exp: %d, got %d\", expB, b))\n\t}\n\n\turequire.Error(t, b.TransferFrom(spender, owner, dest, 1), \"no allowance\")\n\tkey := allowanceKey(owner, spender)\n\turequire.False(t, b.allowances.Has(key), \"allowance should be removed\")\n\turequire.Equal(t, b.Allowance(owner, spender), uint64(0), \"allowance should be 0\")\n}\n"},{"name":"token.gno","body":"package grc20\n\nimport (\n\t\"std\"\n)\n\n// token implements the Token interface.\n//\n// It is generated with Banker.Token().\n// It can safely be exposed publicly.\ntype token struct {\n\tbanker *Banker\n}\n\n// var _ Token = (*token)(nil)\nfunc (t *token) GetName() string { return t.banker.name }\nfunc (t *token) GetSymbol() string { return t.banker.symbol }\nfunc (t *token) GetDecimals() uint { return t.banker.decimals }\nfunc (t *token) TotalSupply() uint64 { return t.banker.totalSupply }\n\nfunc (t *token) BalanceOf(owner std.Address) uint64 {\n\treturn t.banker.BalanceOf(owner)\n}\n\nfunc (t *token) Transfer(to std.Address, amount uint64) error {\n\tcaller := std.PrevRealm().Addr()\n\treturn t.banker.Transfer(caller, to, amount)\n}\n\nfunc (t *token) Allowance(owner, spender std.Address) uint64 {\n\treturn t.banker.Allowance(owner, spender)\n}\n\nfunc (t *token) Approve(spender std.Address, amount uint64) error {\n\tcaller := std.PrevRealm().Addr()\n\treturn t.banker.Approve(caller, spender, amount)\n}\n\nfunc (t *token) TransferFrom(from, to std.Address, amount uint64) error {\n\tspender := std.PrevRealm().Addr()\n\tif err := t.banker.SpendAllowance(from, spender, amount); err != nil {\n\t\treturn err\n\t}\n\treturn t.banker.Transfer(from, to, amount)\n}\n"},{"name":"token_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/ufmt\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestUserTokenImpl(t *testing.T) {\n\tbank := NewBanker(\"Dummy\", \"DUMMY\", 4)\n\ttok := bank.Token()\n\t_ = tok\n}\n\nfunc TestUserApprove(t *testing.T) {\n\towner := testutils.TestAddress(\"owner\")\n\tspender := testutils.TestAddress(\"spender\")\n\tdest := testutils.TestAddress(\"dest\")\n\n\tbank := NewBanker(\"Dummy\", \"DUMMY\", 6)\n\ttok := bank.Token()\n\n\t// Set owner as the original caller\n\tstd.TestSetOriginCaller(owner)\n\t// Mint 100000000 tokens for owner\n\turequire.NoError(t, bank.Mint(owner, 100000000))\n\n\t// Approve spender to spend 5000000 tokens\n\turequire.NoError(t, tok.Approve(spender, 5000000))\n\n\t// Set spender as the original caller\n\tstd.TestSetOriginCaller(spender)\n\t// Try to transfer 10000000 tokens from owner to dest, should fail because it exceeds allowance\n\turequire.Error(t,\n\t\ttok.TransferFrom(owner, dest, 10000000),\n\t\tErrInsufficientAllowance.Error(),\n\t\t\"should not be able to transfer more than approved\",\n\t)\n\n\t// Define a set of test data with spend amount and expected remaining allowance\n\ttests := []struct {\n\t\tspend uint64 // Spend amount\n\t\texp uint64 // Remaining allowance\n\t}{\n\t\t{3, 4999997},\n\t\t{999997, 4000000},\n\t\t{4000000, 0},\n\t}\n\n\t// perform transfer operation,and check if allowance and balance are correct\n\tfor _, tt := range tests {\n\t\tb0 := tok.BalanceOf(dest)\n\t\t// Perform transfer from owner to dest\n\t\turequire.NoError(t, tok.TransferFrom(owner, dest, tt.spend))\n\t\ta := tok.Allowance(owner, spender)\n\t\t// Check if allowance equals expected value\n\t\turequire.True(t, a == tt.exp, ufmt.Sprintf(\"allowance exp: %d,got %d\", tt.exp, a))\n\n\t\t// Get dest current balance\n\t\tb := tok.BalanceOf(dest)\n\t\t// Calculate expected balance ,should be initial balance plus transfer amount\n\t\texpB := b0 + tt.spend\n\t\t// Check if balance equals expected value\n\t\turequire.True(t, b == expB, ufmt.Sprintf(\"balance exp: %d,got %d\", expB, b))\n\t}\n\n\t// Try to transfer one token from owner to dest ,should fail because no allowance left\n\turequire.Error(t, tok.TransferFrom(owner, dest, 1), ErrInsufficientAllowance.Error(), \"no allowance\")\n}\n"},{"name":"types.gno","body":"package grc20\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/grc/exts\"\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)\n\ntype Token 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 the risk\n\t// that someone may use both the old and the new allowance by unfortunate\n\t// transaction ordering. One possible solution to mitigate this race\n\t// condition is to first reduce the spender's allowance to 0 and set the\n\t// 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\nconst (\n\tMintEvent = \"Mint\"\n\tBurnEvent = \"Burn\"\n\tTransferEvent = \"Transfer\"\n\tApprovalEvent = \"Approval\"\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":"grc721","path":"gno.land/p/demo/grc/grc721","files":[{"name":"basic_nft.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype basicNFT struct {\n\tname string\n\tsymbol string\n\towners avl.Tree // tokenId -\u003e OwnerAddress\n\tbalances avl.Tree // OwnerAddress -\u003e TokenCount\n\ttokenApprovals avl.Tree // TokenId -\u003e ApprovedAddress\n\ttokenURIs avl.Tree // TokenId -\u003e URIs\n\toperatorApprovals avl.Tree // \"OwnerAddress:OperatorAddress\" -\u003e bool\n}\n\n// Returns new basic NFT\nfunc NewBasicNFT(name string, symbol string) *basicNFT {\n\treturn \u0026basicNFT{\n\t\tname: name,\n\t\tsymbol: symbol,\n\n\t\towners: avl.Tree{},\n\t\tbalances: avl.Tree{},\n\t\ttokenApprovals: avl.Tree{},\n\t\ttokenURIs: avl.Tree{},\n\t\toperatorApprovals: avl.Tree{},\n\t}\n}\n\nfunc (s *basicNFT) Name() string { return s.name }\nfunc (s *basicNFT) Symbol() string { return s.symbol }\nfunc (s *basicNFT) TokenCount() uint64 { return uint64(s.owners.Size()) }\n\n// BalanceOf returns balance of input address\nfunc (s *basicNFT) BalanceOf(addr std.Address) (uint64, error) {\n\tif err := isValidAddress(addr); err != nil {\n\t\treturn 0, err\n\t}\n\n\tbalance, found := s.balances.Get(addr.String())\n\tif !found {\n\t\treturn 0, nil\n\t}\n\n\treturn balance.(uint64), nil\n}\n\n// OwnerOf returns owner of input token id\nfunc (s *basicNFT) OwnerOf(tid TokenID) (std.Address, error) {\n\towner, found := s.owners.Get(string(tid))\n\tif !found {\n\t\treturn \"\", ErrInvalidTokenId\n\t}\n\n\treturn owner.(std.Address), nil\n}\n\n// TokenURI returns the URI of input token id\nfunc (s *basicNFT) TokenURI(tid TokenID) (string, error) {\n\turi, found := s.tokenURIs.Get(string(tid))\n\tif !found {\n\t\treturn \"\", ErrInvalidTokenId\n\t}\n\n\treturn uri.(string), nil\n}\n\nfunc (s *basicNFT) SetTokenURI(tid TokenID, tURI TokenURI) (bool, error) {\n\t// check for invalid TokenID\n\tif !s.exists(tid) {\n\t\treturn false, ErrInvalidTokenId\n\t}\n\n\t// check for the right owner\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn false, ErrCallerIsNotOwner\n\t}\n\ts.tokenURIs.Set(string(tid), string(tURI))\n\treturn true, nil\n}\n\n// IsApprovedForAll returns true if operator is approved for all by the owner.\n// Otherwise, returns false\nfunc (s *basicNFT) IsApprovedForAll(owner, operator std.Address) bool {\n\tkey := owner.String() + \":\" + operator.String()\n\t_, found := s.operatorApprovals.Get(key)\n\tif !found {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// Approve approves the input address for particular token\nfunc (s *basicNFT) Approve(to std.Address, tid TokenID) error {\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner == to {\n\t\treturn ErrApprovalToCurrentOwner\n\t}\n\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner \u0026\u0026 !s.IsApprovedForAll(owner, caller) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\ts.tokenApprovals.Set(string(tid), to.String())\n\tevent := ApprovalEvent{owner, to, tid}\n\temit(\u0026event)\n\n\treturn nil\n}\n\n// GetApproved return the approved address for token\nfunc (s *basicNFT) GetApproved(tid TokenID) (std.Address, error) {\n\taddr, found := s.tokenApprovals.Get(string(tid))\n\tif !found {\n\t\treturn zeroAddress, ErrTokenIdNotHasApproved\n\t}\n\n\treturn std.Address(addr.(string)), nil\n}\n\n// SetApprovalForAll can approve the operator to operate on all tokens\nfunc (s *basicNFT) SetApprovalForAll(operator std.Address, approved bool) error {\n\tif err := isValidAddress(operator); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tcaller := std.PrevRealm().Addr()\n\treturn s.setApprovalForAll(caller, operator, approved)\n}\n\n// Safely transfers `tokenId` token from `from` to `to`, checking that\n// contract recipients are aware of the GRC721 protocol to prevent\n// tokens from being forever locked.\nfunc (s *basicNFT) SafeTransferFrom(from, to std.Address, tid TokenID) error {\n\tcaller := std.PrevRealm().Addr()\n\tif !s.isApprovedOrOwner(caller, tid) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.transfer(from, to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.checkOnGRC721Received(from, to, tid) {\n\t\treturn ErrTransferToNonGRC721Receiver\n\t}\n\n\treturn nil\n}\n\n// Transfers `tokenId` token from `from` to `to`.\nfunc (s *basicNFT) TransferFrom(from, to std.Address, tid TokenID) error {\n\tcaller := std.PrevRealm().Addr()\n\tif !s.isApprovedOrOwner(caller, tid) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.transfer(from, to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// Mints `tokenId` and transfers it to `to`.\nfunc (s *basicNFT) Mint(to std.Address, tid TokenID) error {\n\treturn s.mint(to, tid)\n}\n\n// Mints `tokenId` and transfers it to `to`. Also checks that\n// contract recipients are using GRC721 protocol\nfunc (s *basicNFT) SafeMint(to std.Address, tid TokenID) error {\n\terr := s.mint(to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.checkOnGRC721Received(zeroAddress, to, tid) {\n\t\treturn ErrTransferToNonGRC721Receiver\n\t}\n\n\treturn nil\n}\n\nfunc (s *basicNFT) Burn(tid TokenID) error {\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ts.beforeTokenTransfer(owner, zeroAddress, tid, 1)\n\n\ts.tokenApprovals.Remove(string(tid))\n\tbalance, err := s.BalanceOf(owner)\n\tif err != nil {\n\t\treturn err\n\t}\n\tbalance -= 1\n\ts.balances.Set(owner.String(), balance)\n\ts.owners.Remove(string(tid))\n\n\tevent := TransferEvent{owner, zeroAddress, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(owner, zeroAddress, tid, 1)\n\n\treturn nil\n}\n\n/* Helper methods */\n\n// Helper for SetApprovalForAll()\nfunc (s *basicNFT) setApprovalForAll(owner, operator std.Address, approved bool) error {\n\tif owner == operator {\n\t\treturn ErrApprovalToCurrentOwner\n\t}\n\n\tkey := owner.String() + \":\" + operator.String()\n\ts.operatorApprovals.Set(key, approved)\n\n\tevent := ApprovalForAllEvent{owner, operator, approved}\n\temit(\u0026event)\n\n\treturn nil\n}\n\n// Helper for TransferFrom() and SafeTransferFrom()\nfunc (s *basicNFT) transfer(from, to std.Address, tid TokenID) error {\n\tif err := isValidAddress(from); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\tif err := isValidAddress(to); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tif from == to {\n\t\treturn ErrCannotTransferToSelf\n\t}\n\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner != from {\n\t\treturn ErrTransferFromIncorrectOwner\n\t}\n\n\ts.beforeTokenTransfer(from, to, tid, 1)\n\n\t// Check that tokenId was not transferred by `beforeTokenTransfer`\n\towner, err = s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner != from {\n\t\treturn ErrTransferFromIncorrectOwner\n\t}\n\n\ts.tokenApprovals.Remove(string(tid))\n\tfromBalance, err := s.BalanceOf(from)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance, err := s.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfromBalance -= 1\n\ttoBalance += 1\n\ts.balances.Set(from.String(), fromBalance)\n\ts.balances.Set(to.String(), toBalance)\n\ts.owners.Set(string(tid), to)\n\n\tevent := TransferEvent{from, to, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(from, to, tid, 1)\n\n\treturn nil\n}\n\n// Helper for Mint() and SafeMint()\nfunc (s *basicNFT) mint(to std.Address, tid TokenID) error {\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\tif s.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ts.beforeTokenTransfer(zeroAddress, to, tid, 1)\n\n\t// Check that tokenId was not minted by `beforeTokenTransfer`\n\tif s.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ttoBalance, err := s.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance += 1\n\ts.balances.Set(to.String(), toBalance)\n\ts.owners.Set(string(tid), to)\n\n\tevent := TransferEvent{zeroAddress, to, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(zeroAddress, to, tid, 1)\n\n\treturn nil\n}\n\nfunc (s *basicNFT) isApprovedOrOwner(addr std.Address, tid TokenID) bool {\n\towner, found := s.owners.Get(string(tid))\n\tif !found {\n\t\treturn false\n\t}\n\n\tif addr == owner.(std.Address) || s.IsApprovedForAll(owner.(std.Address), addr) {\n\t\treturn true\n\t}\n\n\t_, err := s.GetApproved(tid)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// Checks if token id already exists\nfunc (s *basicNFT) exists(tid TokenID) bool {\n\t_, found := s.owners.Get(string(tid))\n\treturn found\n}\n\nfunc (s *basicNFT) beforeTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicNFT) afterTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicNFT) checkOnGRC721Received(from, to std.Address, tid TokenID) bool {\n\t// TODO: Implementation\n\treturn true\n}\n\nfunc (s *basicNFT) RenderHome() (str string) {\n\tstr += ufmt.Sprintf(\"# %s ($%s)\\n\\n\", s.name, s.symbol)\n\tstr += ufmt.Sprintf(\"* **Total supply**: %d\\n\", s.TokenCount())\n\tstr += ufmt.Sprintf(\"* **Known accounts**: %d\\n\", s.balances.Size())\n\n\treturn\n}\n"},{"name":"basic_nft_test.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\tdummyNFTName = \"DummyNFT\"\n\tdummyNFTSymbol = \"DNFT\"\n)\n\nfunc TestNewBasicNFT(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n}\n\nfunc TestName(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tname := dummy.Name()\n\tuassert.Equal(t, dummyNFTName, name)\n}\n\nfunc TestSymbol(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tsymbol := dummy.Symbol()\n\tuassert.Equal(t, dummyNFTSymbol, symbol)\n}\n\nfunc TestTokenCount(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcount := dummy.TokenCount()\n\tuassert.Equal(t, uint64(0), count)\n\n\tdummy.mint(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\", TokenID(\"1\"))\n\tdummy.mint(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\", TokenID(\"2\"))\n\n\tcount = dummy.TokenCount()\n\tuassert.Equal(t, uint64(2), count)\n}\n\nfunc TestBalanceOf(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\tbalanceAddr1, err := dummy.BalanceOf(addr1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(0), balanceAddr1)\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\tdummy.mint(addr1, TokenID(\"2\"))\n\tdummy.mint(addr2, TokenID(\"3\"))\n\n\tbalanceAddr1, err = dummy.BalanceOf(addr1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tbalanceAddr2, err := dummy.BalanceOf(addr2)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tuassert.Equal(t, uint64(2), balanceAddr1)\n\tuassert.Equal(t, uint64(1), balanceAddr2)\n}\n\nfunc TestOwnerOf(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\towner, err := dummy.OwnerOf(TokenID(\"invalid\"))\n\tuassert.Error(t, err, \"should not result in error\")\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\tdummy.mint(addr2, TokenID(\"2\"))\n\n\t// Checking for token id \"1\"\n\towner, err = dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr1.String(), owner.String())\n\n\t// Checking for token id \"2\"\n\towner, err = dummy.OwnerOf(TokenID(\"2\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr2.String(), owner.String())\n}\n\nfunc TestIsApprovedForAll(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(addr1, addr2)\n\tuassert.False(t, isApprovedForAll)\n}\n\nfunc TestSetApprovalForAll(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(caller, addr)\n\tuassert.False(t, isApprovedForAll)\n\n\terr := dummy.SetApprovalForAll(addr, true)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tisApprovedForAll = dummy.IsApprovedForAll(caller, addr)\n\tuassert.True(t, isApprovedForAll)\n}\n\nfunc TestGetApproved(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tapprovedAddr, err := dummy.GetApproved(TokenID(\"invalid\"))\n\tuassert.Error(t, err, \"should result in error\")\n}\n\nfunc TestApprove(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\n\t_, err := dummy.GetApproved(TokenID(\"1\"))\n\tuassert.Error(t, err, \"should result in error\")\n\n\terr = dummy.Approve(addr, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tapprovedAddr, err := dummy.GetApproved(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should result in error\")\n\tuassert.Equal(t, addr.String(), approvedAddr.String())\n}\n\nfunc TestTransferFrom(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\tdummy.mint(caller, TokenID(\"2\"))\n\n\terr := dummy.TransferFrom(caller, addr, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should result in error\")\n\n\t// Check balance of caller after transfer\n\tbalanceOfCaller, err := dummy.BalanceOf(caller)\n\tuassert.NoError(t, err, \"should result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfCaller)\n\n\t// Check balance of addr after transfer\n\tbalanceOfAddr, err := dummy.BalanceOf(addr)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfAddr)\n\n\t// Check Owner of transferred Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should result in error\")\n\tuassert.Equal(t, addr.String(), owner.String())\n}\n\nfunc TestSafeTransferFrom(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\tdummy.mint(caller, TokenID(\"2\"))\n\n\terr := dummy.SafeTransferFrom(caller, addr, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check balance of caller after transfer\n\tbalanceOfCaller, err := dummy.BalanceOf(caller)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfCaller)\n\n\t// Check balance of addr after transfer\n\tbalanceOfAddr, err := dummy.BalanceOf(addr)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfAddr)\n\n\t// Check Owner of transferred Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr.String(), owner.String())\n}\n\nfunc TestMint(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\terr := dummy.Mint(addr1, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.Mint(addr1, TokenID(\"2\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.Mint(addr2, TokenID(\"3\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Try minting duplicate token id\n\terr = dummy.Mint(addr2, TokenID(\"1\"))\n\tuassert.Error(t, err, \"should not result in error\")\n\n\t// Check Owner of Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr1.String(), owner.String())\n}\n\nfunc TestBurn(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(addr, TokenID(\"1\"))\n\tdummy.mint(addr, TokenID(\"2\"))\n\n\terr := dummy.Burn(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check Owner of Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.Error(t, err, \"should result in error\")\n}\n\nfunc TestSetTokenURI(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\ttokenURI := \"http://example.com/token\"\n\n\tstd.TestSetOriginCaller(std.Address(addr1)) // addr1\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\t_, derr := dummy.SetTokenURI(TokenID(\"1\"), TokenURI(tokenURI))\n\tuassert.NoError(t, derr, \"should not result in error\")\n\n\t// Test case: Invalid token ID\n\t_, err := dummy.SetTokenURI(TokenID(\"3\"), TokenURI(tokenURI))\n\tuassert.ErrorIs(t, err, ErrInvalidTokenId)\n\n\tstd.TestSetOriginCaller(std.Address(addr2)) // addr2\n\n\t_, cerr := dummy.SetTokenURI(TokenID(\"1\"), TokenURI(tokenURI)) // addr2 trying to set URI for token 1\n\tuassert.ErrorIs(t, cerr, ErrCallerIsNotOwner)\n\n\t// Test case: Retrieving TokenURI\n\tstd.TestSetOriginCaller(std.Address(addr1)) // addr1\n\n\tdummyTokenURI, err := dummy.TokenURI(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"TokenURI error\")\n\tuassert.Equal(t, string(tokenURI), string(dummyTokenURI))\n}\n"},{"name":"errors.gno","body":"package grc721\n\nimport \"errors\"\n\nvar (\n\tErrInvalidTokenId = errors.New(\"invalid token id\")\n\tErrInvalidAddress = errors.New(\"invalid address\")\n\tErrTokenIdNotHasApproved = errors.New(\"token id not approved for anyone\")\n\tErrApprovalToCurrentOwner = errors.New(\"approval to current owner\")\n\tErrCallerIsNotOwner = errors.New(\"caller is not token owner\")\n\tErrCallerNotApprovedForAll = errors.New(\"caller is not approved for all\")\n\tErrCannotTransferToSelf = errors.New(\"cannot send transfer to self\")\n\tErrTransferFromIncorrectOwner = errors.New(\"transfer from incorrect owner\")\n\tErrTransferToNonGRC721Receiver = errors.New(\"transfer to non GRC721Receiver implementer\")\n\tErrCallerIsNotOwnerOrApproved = errors.New(\"caller is not token owner or approved\")\n\tErrTokenIdAlreadyExists = errors.New(\"token id already exists\")\n\n\t// ERC721Royalty\n\tErrInvalidRoyaltyPercentage = errors.New(\"invalid royalty percentage\")\n\tErrInvalidRoyaltyPaymentAddress = errors.New(\"invalid royalty paymentAddress\")\n\tErrCannotCalculateRoyaltyAmount = errors.New(\"cannot calculate royalty amount\")\n)\n"},{"name":"grc721_metadata.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n// metadataNFT represents an NFT with metadata extensions.\ntype metadataNFT struct {\n\t*basicNFT // Embedded basicNFT struct for basic NFT functionality\n\textensions *avl.Tree // AVL tree for storing metadata extensions\n}\n\n// Ensure that metadataNFT implements the IGRC721MetadataOnchain interface.\nvar _ IGRC721MetadataOnchain = (*metadataNFT)(nil)\n\n// NewNFTWithMetadata creates a new basic NFT with metadata extensions.\nfunc NewNFTWithMetadata(name string, symbol string) *metadataNFT {\n\t// Create a new basic NFT\n\tnft := NewBasicNFT(name, symbol)\n\n\t// Return a metadataNFT with basicNFT embedded and an empty AVL tree for extensions\n\treturn \u0026metadataNFT{\n\t\tbasicNFT: nft,\n\t\textensions: avl.NewTree(),\n\t}\n}\n\n// SetTokenMetadata sets metadata for a given token ID.\nfunc (s *metadataNFT) SetTokenMetadata(tid TokenID, metadata Metadata) error {\n\t// Check if the caller is the owner of the token\n\towner, err := s.basicNFT.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn ErrCallerIsNotOwner\n\t}\n\n\t// Set the metadata for the token ID in the extensions AVL tree\n\ts.extensions.Set(string(tid), metadata)\n\treturn nil\n}\n\n// TokenMetadata retrieves metadata for a given token ID.\nfunc (s *metadataNFT) TokenMetadata(tid TokenID) (Metadata, error) {\n\t// Retrieve metadata from the extensions AVL tree\n\tmetadata, found := s.extensions.Get(string(tid))\n\tif !found {\n\t\treturn Metadata{}, ErrInvalidTokenId\n\t}\n\n\treturn metadata.(Metadata), nil\n}\n\n// mint mints a new token and assigns it to the specified address.\nfunc (s *metadataNFT) mint(to std.Address, tid TokenID) error {\n\t// Check if the address is valid\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\t// Check if the token ID already exists\n\tif s.basicNFT.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ts.basicNFT.beforeTokenTransfer(zeroAddress, to, tid, 1)\n\n\t// Check if the token ID was minted by beforeTokenTransfer\n\tif s.basicNFT.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\t// Increment balance of the recipient address\n\ttoBalance, err := s.basicNFT.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance += 1\n\ts.basicNFT.balances.Set(to.String(), toBalance)\n\n\t// Set owner of the token ID to the recipient address\n\ts.basicNFT.owners.Set(string(tid), to)\n\n\t// Emit transfer event\n\tevent := TransferEvent{zeroAddress, to, tid}\n\temit(\u0026event)\n\n\ts.basicNFT.afterTokenTransfer(zeroAddress, to, tid, 1)\n\n\treturn nil\n}\n"},{"name":"grc721_metadata_test.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestSetMetadata(t *testing.T) {\n\t// Create a new dummy NFT with metadata\n\tdummy := NewNFTWithMetadata(dummyNFTName, dummyNFTSymbol)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n\n\t// Define addresses for testing purposes\n\taddr1 := testutils.TestAddress(\"alice\")\n\taddr2 := testutils.TestAddress(\"bob\")\n\n\t// Define metadata attributes\n\tname := \"test\"\n\tdescription := \"test\"\n\timage := \"test\"\n\timageData := \"test\"\n\texternalURL := \"test\"\n\tattributes := []Trait{}\n\tbackgroundColor := \"test\"\n\tanimationURL := \"test\"\n\tyoutubeURL := \"test\"\n\n\t// Set the original caller to addr1\n\tstd.TestSetOriginCaller(addr1) // addr1\n\n\t// Mint a new token for addr1\n\tdummy.mint(addr1, TokenID(\"1\"))\n\n\t// Set metadata for token 1\n\tderr := dummy.SetTokenMetadata(TokenID(\"1\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if there was an error setting metadata\n\tuassert.NoError(t, derr, \"Should not result in error\")\n\n\t// Test case: Invalid token ID\n\terr := dummy.SetTokenMetadata(TokenID(\"3\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if the error returned matches the expected error\n\tuassert.ErrorIs(t, err, ErrInvalidTokenId)\n\n\t// Set the original caller to addr2\n\tstd.TestSetOriginCaller(addr2) // addr2\n\n\t// Try to set metadata for token 1 from addr2 (should fail)\n\tcerr := dummy.SetTokenMetadata(TokenID(\"1\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if the error returned matches the expected error\n\tuassert.ErrorIs(t, cerr, ErrCallerIsNotOwner)\n\n\t// Set the original caller back to addr1\n\tstd.TestSetOriginCaller(addr1) // addr1\n\n\t// Retrieve metadata for token 1\n\tdummyMetadata, err := dummy.TokenMetadata(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"Metadata error\")\n\n\t// Check if metadata attributes match expected values\n\tuassert.Equal(t, image, dummyMetadata.Image)\n\tuassert.Equal(t, imageData, dummyMetadata.ImageData)\n\tuassert.Equal(t, externalURL, dummyMetadata.ExternalURL)\n\tuassert.Equal(t, description, dummyMetadata.Description)\n\tuassert.Equal(t, name, dummyMetadata.Name)\n\tuassert.Equal(t, len(attributes), len(dummyMetadata.Attributes))\n\tuassert.Equal(t, backgroundColor, dummyMetadata.BackgroundColor)\n\tuassert.Equal(t, animationURL, dummyMetadata.AnimationURL)\n\tuassert.Equal(t, youtubeURL, dummyMetadata.YoutubeURL)\n}\n"},{"name":"grc721_royalty.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n// royaltyNFT represents a non-fungible token (NFT) with royalty functionality.\ntype royaltyNFT struct {\n\t*metadataNFT // Embedding metadataNFT for NFT functionality\n\ttokenRoyaltyInfo *avl.Tree // AVL tree to store royalty information for each token\n\tmaxRoyaltyPercentage uint64 // maxRoyaltyPercentage represents the maximum royalty percentage that can be charged every sale\n}\n\n// Ensure that royaltyNFT implements the IGRC2981 interface.\nvar _ IGRC2981 = (*royaltyNFT)(nil)\n\n// NewNFTWithRoyalty creates a new royalty NFT with the specified name, symbol, and royalty calculator.\nfunc NewNFTWithRoyalty(name string, symbol string) *royaltyNFT {\n\t// Create a new NFT with metadata\n\tnft := NewNFTWithMetadata(name, symbol)\n\n\treturn \u0026royaltyNFT{\n\t\tmetadataNFT: nft,\n\t\ttokenRoyaltyInfo: avl.NewTree(),\n\t\tmaxRoyaltyPercentage: 100,\n\t}\n}\n\n// SetTokenRoyalty sets the royalty information for a specific token ID.\nfunc (r *royaltyNFT) SetTokenRoyalty(tid TokenID, royaltyInfo RoyaltyInfo) error {\n\t// Validate the payment address\n\tif err := isValidAddress(royaltyInfo.PaymentAddress); err != nil {\n\t\treturn ErrInvalidRoyaltyPaymentAddress\n\t}\n\n\t// Check if royalty percentage exceeds maxRoyaltyPercentage\n\tif royaltyInfo.Percentage \u003e r.maxRoyaltyPercentage {\n\t\treturn ErrInvalidRoyaltyPercentage\n\t}\n\n\t// Check if the caller is the owner of the token\n\towner, err := r.metadataNFT.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn ErrCallerIsNotOwner\n\t}\n\n\t// Set royalty information for the token\n\tr.tokenRoyaltyInfo.Set(string(tid), royaltyInfo)\n\n\treturn nil\n}\n\n// RoyaltyInfo returns the royalty information for the given token ID and sale price.\nfunc (r *royaltyNFT) RoyaltyInfo(tid TokenID, salePrice uint64) (std.Address, uint64, error) {\n\t// Retrieve royalty information for the token\n\tval, found := r.tokenRoyaltyInfo.Get(string(tid))\n\tif !found {\n\t\treturn \"\", 0, ErrInvalidTokenId\n\t}\n\n\troyaltyInfo := val.(RoyaltyInfo)\n\n\t// Calculate royalty amount\n\troyaltyAmount, _ := r.calculateRoyaltyAmount(salePrice, royaltyInfo.Percentage)\n\n\treturn royaltyInfo.PaymentAddress, royaltyAmount, nil\n}\n\nfunc (r *royaltyNFT) calculateRoyaltyAmount(salePrice, percentage uint64) (uint64, error) {\n\troyaltyAmount := (salePrice * percentage) / 100\n\treturn royaltyAmount, nil\n}\n"},{"name":"grc721_royalty_test.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestSetTokenRoyalty(t *testing.T) {\n\tdummy := NewNFTWithRoyalty(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := testutils.TestAddress(\"alice\")\n\taddr2 := testutils.TestAddress(\"bob\")\n\n\tpaymentAddress := testutils.TestAddress(\"john\")\n\tpercentage := uint64(10) // 10%\n\n\tsalePrice := uint64(1000)\n\texpectRoyaltyAmount := uint64(100)\n\n\tstd.TestSetOriginCaller(addr1) // addr1\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\n\tderr := dummy.SetTokenRoyalty(TokenID(\"1\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tuassert.NoError(t, derr, \"Should not result in error\")\n\n\t// Test case: Invalid token ID\n\terr := dummy.SetTokenRoyalty(TokenID(\"3\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tuassert.ErrorIs(t, derr, ErrInvalidTokenId)\n\n\tstd.TestSetOriginCaller(addr2) // addr2\n\n\tcerr := dummy.SetTokenRoyalty(TokenID(\"1\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tuassert.ErrorIs(t, cerr, ErrCallerIsNotOwner)\n\n\t// Test case: Invalid payment address\n\taerr := dummy.SetTokenRoyalty(TokenID(\"4\"), RoyaltyInfo{\n\t\tPaymentAddress: std.Address(\"###\"), // invalid address\n\t\tPercentage: percentage,\n\t})\n\tuassert.ErrorIs(t, aerr, ErrInvalidRoyaltyPaymentAddress)\n\n\t// Test case: Invalid percentage\n\tperr := dummy.SetTokenRoyalty(TokenID(\"5\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: uint64(200), // over maxRoyaltyPercentage\n\t})\n\tuassert.ErrorIs(t, perr, ErrInvalidRoyaltyPercentage)\n\n\t// Test case: Retrieving Royalty Info\n\tstd.TestSetOriginCaller(addr1) // addr1\n\n\tdummyPaymentAddress, dummyRoyaltyAmount, rerr := dummy.RoyaltyInfo(TokenID(\"1\"), salePrice)\n\tuassert.NoError(t, rerr, \"RoyaltyInfo error\")\n\tuassert.Equal(t, paymentAddress, dummyPaymentAddress)\n\tuassert.Equal(t, expectRoyaltyAmount, dummyRoyaltyAmount)\n}\n"},{"name":"igrc721.gno","body":"package grc721\n\nimport \"std\"\n\ntype IGRC721 interface {\n\tBalanceOf(owner std.Address) (uint64, error)\n\tOwnerOf(tid TokenID) (std.Address, error)\n\tSetTokenURI(tid TokenID, tURI TokenURI) (bool, error)\n\tSafeTransferFrom(from, to std.Address, tid TokenID) error\n\tTransferFrom(from, to std.Address, tid TokenID) error\n\tApprove(approved std.Address, tid TokenID) error\n\tSetApprovalForAll(operator std.Address, approved bool) error\n\tGetApproved(tid TokenID) (std.Address, error)\n\tIsApprovedForAll(owner, operator std.Address) bool\n}\n\ntype (\n\tTokenID string\n\tTokenURI string\n)\n\ntype TransferEvent struct {\n\tFrom std.Address\n\tTo std.Address\n\tTokenID TokenID\n}\n\ntype ApprovalEvent struct {\n\tOwner std.Address\n\tApproved std.Address\n\tTokenID TokenID\n}\n\ntype ApprovalForAllEvent struct {\n\tOwner std.Address\n\tOperator std.Address\n\tApproved bool\n}\n"},{"name":"igrc721_metadata.gno","body":"package grc721\n\n// IGRC721CollectionMetadata describes basic information about an NFT collection.\ntype IGRC721CollectionMetadata interface {\n\tName() string // Name returns the name of the collection.\n\tSymbol() string // Symbol returns the symbol of the collection.\n}\n\n// IGRC721Metadata follows the Ethereum standard\ntype IGRC721Metadata interface {\n\tIGRC721CollectionMetadata\n\tTokenURI(tid TokenID) (string, error) // TokenURI returns the URI of a specific token.\n}\n\n// IGRC721Metadata follows the OpenSea metadata standard\ntype IGRC721MetadataOnchain interface {\n\tIGRC721CollectionMetadata\n\tTokenMetadata(tid TokenID) (Metadata, error)\n}\n\ntype Trait struct {\n\tDisplayType string\n\tTraitType string\n\tValue string\n}\n\n// see: https://docs.opensea.io/docs/metadata-standards\ntype Metadata struct {\n\tImage string // URL to the image of the item. Can be any type of image (including SVGs, which will be cached into PNGs by OpenSea), IPFS or Arweave URLs or paths. We recommend using a minimum 3000 x 3000 image.\n\tImageData string // Raw SVG image data, if you want to generate images on the fly (not recommended). Only use this if you're not including the image parameter.\n\tExternalURL string // URL that will appear below the asset's image on OpenSea and will allow users to leave OpenSea and view the item on your site.\n\tDescription string // Human-readable description of the item. Markdown is supported.\n\tName string // Name of the item.\n\tAttributes []Trait // Attributes for the item, which will show up on the OpenSea page for the item.\n\tBackgroundColor string // Background color of the item on OpenSea. Must be a six-character hexadecimal without a pre-pended #\n\tAnimationURL string // URL to a multimedia attachment for the item. Supported file extensions: GLTF, GLB, WEBM, MP4, M4V, OGV, OGG, MP3, WAV, OGA, HTML (for rich experiences and interactive NFTs using JavaScript canvas, WebGL, etc.). Scripts and relative paths within the HTML page are now supported. Access to browser extensions is not supported.\n\tYoutubeURL string // URL to a YouTube video (only used if animation_url is not provided).\n}\n"},{"name":"igrc721_royalty.gno","body":"package grc721\n\nimport \"std\"\n\n// IGRC2981 follows the Ethereum standard\ntype IGRC2981 interface {\n\t// RoyaltyInfo retrieves royalty information for a tokenID and salePrice.\n\t// It returns the payment address, royalty amount, and an error if any.\n\tRoyaltyInfo(tokenID TokenID, salePrice uint64) (std.Address, uint64, error)\n}\n\n// RoyaltyInfo represents royalty information for a token.\ntype RoyaltyInfo struct {\n\tPaymentAddress std.Address // PaymentAddress is the address where royalty payment should be sent.\n\tPercentage uint64 // Percentage is the royalty percentage. It indicates the percentage of royalty to be paid for each sale. For example : Percentage = 10 =\u003e 10%\n}\n"},{"name":"util.gno","body":"package grc721\n\nimport (\n\t\"std\"\n)\n\nvar zeroAddress = std.Address(\"\")\n\nfunc isValidAddress(addr std.Address) error {\n\tif !addr.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\treturn nil\n}\n\nfunc emit(event interface{}) {\n\t// TODO: setup a pubsub system here?\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":"grc777","path":"gno.land/p/demo/grc/grc777","files":[{"name":"dummy_test.gno","body":"package grc777\n\nimport (\n\t\"std\"\n\t\"testing\"\n)\n\ntype dummyImpl struct{}\n\n// FIXME: this should fail.\nvar _ IGRC777 = (*dummyImpl)(nil)\n\nfunc TestInterface(t *testing.T) {\n\tvar dummy IGRC777 = \u0026dummyImpl{}\n}\n\nfunc (impl *dummyImpl) GetName() string { panic(\"not implemented\") }\nfunc (impl *dummyImpl) GetSymbol() string { panic(\"not implemented\") }\nfunc (impl *dummyImpl) GetDecimals() uint { panic(\"not implemented\") }\nfunc (impl *dummyImpl) Granularity() (granularity uint64) { panic(\"not implemented\") }\nfunc (impl *dummyImpl) TotalSupply() (supply uint64) { panic(\"not implemented\") }\nfunc (impl *dummyImpl) BalanceOf(address std.Address) uint64 { panic(\"not implemented\") }\nfunc (impl *dummyImpl) Burn(amount uint64, data []byte) { panic(\"not implemented\") }\nfunc (impl *dummyImpl) AuthorizeOperator(operator std.Address) { panic(\"not implemented\") }\nfunc (impl *dummyImpl) RevokeOperator(operators std.Address) { panic(\"not implemented\") }\nfunc (impl *dummyImpl) DefaultOperators() []std.Address { panic(\"not implemented\") }\nfunc (impl *dummyImpl) Send(recipient std.Address, amount uint64, data []byte) {\n\tpanic(\"not implemented\")\n}\n\nfunc (impl *dummyImpl) IsOperatorFor(operator, tokenHolder std.Address) bool {\n\tpanic(\"not implemented\")\n}\n\nfunc (impl *dummyImpl) OperatorSend(sender, recipient std.Address, amount uint64, data, operatorData []byte) {\n\tpanic(\"not implemented\")\n}\n\nfunc (impl *dummyImpl) OperatorBurn(account std.Address, amount uint64, data, operatorData []byte) {\n\tpanic(\"not implemented\")\n}\n"},{"name":"igrc777.gno","body":"package grc777\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/grc/exts\"\n)\n\n// TODO: use big.Int or a custom uint64 instead of uint64\n\ntype IGRC777 interface {\n\texts.TokenMetadata\n\n\t// Returns the smallest part of the token that is not divisible. This\n\t// means all token operations (creation, movement and destruction) must\n\t// have amounts that are a multiple of this number.\n\t//\n\t// For most token contracts, this value will equal 1.\n\tGranularity() (granularity uint64)\n\n\t// Returns the amount of tokens in existence.\n\tTotalSupply() (supply uint64)\n\n\t// Returns the amount of tokens owned by an account (`owner`).\n\tBalanceOf(address std.Address) uint64\n\n\t// Moves `amount` tokens from the caller's account to `recipient`.\n\t//\n\t// If send or receive hooks are registered for the caller and `recipient`,\n\t// the corresponding functions will be called with `data` and empty\n\t// `operatorData`. See {IERC777Sender} and {IERC777Recipient}.\n\t//\n\t// Emits a {Sent} event.\n\t//\n\t// Requirements\n\t//\n\t// - the caller must have at least `amount` tokens.\n\t// - `recipient` cannot be the zero address.\n\t// - if `recipient` is a contract, it must implement the {IERC777Recipient}\n\t// interface.\n\tSend(recipient std.Address, amount uint64, data []byte)\n\n\t// Destroys `amount` tokens from the caller's account, reducing the\n\t// total supply.\n\t//\n\t// If a send hook is registered for the caller, the corresponding function\n\t// will be called with `data` and empty `operatorData`. See {IERC777Sender}.\n\t//\n\t// Emits a {Burned} event.\n\t//\n\t// Requirements\n\t//\n\t// - the caller must have at least `amount` tokens.\n\tBurn(amount uint64, data []byte)\n\n\t// Returns true if an account is an operator of `tokenHolder`.\n\t// Operators can send and burn tokens on behalf of their owners. All\n\t// accounts are their own operator.\n\t//\n\t// See {operatorSend} and {operatorBurn}.\n\tIsOperatorFor(operator, tokenHolder std.Address) bool\n\n\t// Make an account an operator of the caller.\n\t//\n\t// See {isOperatorFor}.\n\t//\n\t// Emits an {AuthorizedOperator} event.\n\t//\n\t// Requirements\n\t//\n\t// - `operator` cannot be calling address.\n\tAuthorizeOperator(operator std.Address)\n\n\t// Revoke an account's operator status for the caller.\n\t//\n\t// See {isOperatorFor} and {defaultOperators}.\n\t//\n\t// Emits a {RevokedOperator} event.\n\t//\n\t// Requirements\n\t//\n\t// - `operator` cannot be calling address.\n\tRevokeOperator(operators std.Address)\n\n\t// Returns the list of default operators. These accounts are operators\n\t// for all token holders, even if {authorizeOperator} was never called on\n\t// them.\n\t//\n\t// This list is immutable, but individual holders may revoke these via\n\t// {revokeOperator}, in which case {isOperatorFor} will return false.\n\tDefaultOperators() []std.Address\n\n\t// Moves `amount` tokens from `sender` to `recipient`. The caller must\n\t// be an operator of `sender`.\n\t//\n\t// If send or receive hooks are registered for `sender` and `recipient`,\n\t// the corresponding functions will be called with `data` and\n\t// `operatorData`. See {IERC777Sender} and {IERC777Recipient}.\n\t//\n\t// Emits a {Sent} event.\n\t//\n\t// Requirements\n\t//\n\t// - `sender` cannot be the zero address.\n\t// - `sender` must have at least `amount` tokens.\n\t// - the caller must be an operator for `sender`.\n\t// - `recipient` cannot be the zero address.\n\t// - if `recipient` is a contract, it must implement the {IERC777Recipient}\n\t// interface.\n\tOperatorSend(sender, recipient std.Address, amount uint64, data, operatorData []byte)\n\n\t// Destroys `amount` tokens from `account`, reducing the total supply.\n\t// The caller must be an operator of `account`.\n\t//\n\t// If a send hook is registered for `account`, the corresponding function\n\t// will be called with `data` and `operatorData`. See {IERC777Sender}.\n\t//\n\t// Emits a {Burned} event.\n\t//\n\t// Requirements\n\t//\n\t// - `account` cannot be the zero address.\n\t// - `account` must have at least `amount` tokens.\n\t// - the caller must be an operator for `account`.\n\tOperatorBurn(account std.Address, amount uint64, data, operatorData []byte)\n}\n\n// Emitted when `amount` tokens are created by `operator` and assigned to `to`.\n//\n// Note that some additional user `data` and `operatorData` can be logged in the event.\ntype MintedEvent struct {\n\tOperator std.Address\n\tTo std.Address\n\tAmount uint64\n\tData []byte\n\tOperatorData []byte\n}\n\n// Emitted when `operator` destroys `amount` tokens from `account`.\n//\n// Note that some additional user `data` and `operatorData` can be logged in the event.\ntype BurnedEvent struct {\n\tOperator std.Address\n\tFrom std.Address\n\tAmount uint64\n\tData []byte\n\tOperatorData []byte\n}\n\n// Emitted when `operator` is made operator for `tokenHolder`\ntype AuthorizedOperatorEvent struct {\n\tOperator std.Address\n\tTokenHolder std.Address\n}\n\n// Emitted when `operator` is revoked its operator status for `tokenHolder`.\ntype RevokedOperatorEvent struct {\n\tOperator std.Address\n\tTokenHolder std.Address\n}\n\ntype SentEvent struct {\n\tOperator std.Address\n\tFrom std.Address\n\tTo std.Address\n\tAmount uint64\n\tData []byte\n\tOperatorData []byte\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":"rat","path":"gno.land/p/demo/rat","files":[{"name":"maths.gno","body":"package rat\n\nconst (\n\tintSize = 32 \u003c\u003c (^uint(0) \u003e\u003e 63) // 32 or 64\n\n\tMaxInt = 1\u003c\u003c(intSize-1) - 1\n\tMinInt = -1 \u003c\u003c (intSize - 1)\n\tMaxInt8 = 1\u003c\u003c7 - 1\n\tMinInt8 = -1 \u003c\u003c 7\n\tMaxInt16 = 1\u003c\u003c15 - 1\n\tMinInt16 = -1 \u003c\u003c 15\n\tMaxInt32 = 1\u003c\u003c31 - 1\n\tMinInt32 = -1 \u003c\u003c 31\n\tMaxInt64 = 1\u003c\u003c63 - 1\n\tMinInt64 = -1 \u003c\u003c 63\n\tMaxUint = 1\u003c\u003cintSize - 1\n\tMaxUint8 = 1\u003c\u003c8 - 1\n\tMaxUint16 = 1\u003c\u003c16 - 1\n\tMaxUint32 = 1\u003c\u003c32 - 1\n\tMaxUint64 = 1\u003c\u003c64 - 1\n)\n"},{"name":"rat.gno","body":"package rat\n\n//----------------------------------------\n// Rat fractions\n\n// represents a fraction.\ntype Rat struct {\n\tX int32\n\tY int32 // must be positive\n}\n\nfunc NewRat(x, y int32) Rat {\n\tif y \u003c= 0 {\n\t\tpanic(\"invalid std.Rat denominator\")\n\t}\n\treturn Rat{X: x, Y: y}\n}\n\nfunc (r1 Rat) IsValid() bool {\n\tif r1.Y \u003c= 0 {\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc (r1 Rat) Cmp(r2 Rat) int {\n\tif !r1.IsValid() {\n\t\tpanic(\"invalid std.Rat left operand\")\n\t}\n\tif !r2.IsValid() {\n\t\tpanic(\"invalid std.Rat right operand\")\n\t}\n\tvar p1, p2 int64\n\tp1 = int64(r1.X) * int64(r2.Y)\n\tp2 = int64(r1.Y) * int64(r2.X)\n\tif p1 \u003c p2 {\n\t\treturn -1\n\t} else if p1 == p2 {\n\t\treturn 0\n\t} else {\n\t\treturn 1\n\t}\n}\n\n//func (r1 Rat) Plus(r2 Rat) Rat {\n// XXX\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":"txlink","path":"gno.land/p/moul/txlink","files":[{"name":"txlink.gno","body":"// Package txlink provides utilities for creating transaction-related links\n// compatible with Gnoweb, Gnobro, and other clients within the Gno ecosystem.\n//\n// This package is optimized for generating lightweight transaction links with\n// flexible arguments, allowing users to build dynamic links that integrate\n// seamlessly with various Gno clients.\n//\n// The primary function, URL, is designed to produce markdown links for\n// transaction functions in the current \"relative realm\". By specifying a custom\n// Realm, you can generate links that either use the current realm path or a\n// fully qualified path for another realm.\n//\n// This package is a streamlined alternative to helplink, providing similar\n// functionality for transaction links without the full feature set of helplink.\npackage txlink\n\nimport (\n\t\"std\"\n\t\"strings\"\n)\n\nconst chainDomain = \"gno.land\" // XXX: std.ChainDomain (#2911)\n\n// URL returns a URL for the specified function with optional key-value\n// arguments, for the current realm.\nfunc URL(fn string, args ...string) string {\n\treturn Realm(\"\").URL(fn, args...)\n}\n\n// Realm represents a specific realm for generating tx links.\ntype Realm string\n\n// prefix returns the URL prefix for the realm.\nfunc (r Realm) prefix() string {\n\t// relative\n\tif r == \"\" {\n\t\tcurPath := std.CurrentRealm().PkgPath()\n\t\treturn strings.TrimPrefix(curPath, chainDomain)\n\t}\n\n\t// local realm -\u003e /realm\n\trealm := string(r)\n\tif strings.Contains(realm, chainDomain) {\n\t\treturn strings.TrimPrefix(realm, chainDomain)\n\t}\n\n\t// remote realm -\u003e https://remote.land/realm\n\treturn \"https://\" + string(r)\n}\n\n// URL returns a URL for the specified function with optional key-value\n// arguments.\nfunc (r Realm) URL(fn string, args ...string) string {\n\t// Start with the base query\n\turl := r.prefix() + \"$help\u0026func=\" + fn\n\n\t// Check if args length is even\n\tif len(args)%2 != 0 {\n\t\t// If not even, we can choose to handle the error here.\n\t\t// For example, we can just return the URL without appending\n\t\t// more args.\n\t\treturn url\n\t}\n\n\t// Append key-value pairs to the URL\n\tfor i := 0; i \u003c len(args); i += 2 {\n\t\tkey := args[i]\n\t\tvalue := args[i+1]\n\t\t// XXX: escape keys and args\n\t\turl += \"\u0026\" + key + \"=\" + value\n\t}\n\n\treturn url\n}\n"},{"name":"txlink_test.gno","body":"package txlink\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestURL(t *testing.T) {\n\ttests := []struct {\n\t\tfn string\n\t\targs []string\n\t\twant string\n\t\trealm Realm\n\t}{\n\t\t{\"foo\", []string{\"bar\", \"1\", \"baz\", \"2\"}, \"$help\u0026func=foo\u0026bar=1\u0026baz=2\", \"\"},\n\t\t{\"testFunc\", []string{\"key\", \"value\"}, \"$help\u0026func=testFunc\u0026key=value\", \"\"},\n\t\t{\"noArgsFunc\", []string{}, \"$help\u0026func=noArgsFunc\", \"\"},\n\t\t{\"oddArgsFunc\", []string{\"key\"}, \"$help\u0026func=oddArgsFunc\", \"\"},\n\t\t{\"foo\", []string{\"bar\", \"1\", \"baz\", \"2\"}, \"/r/lorem/ipsum$help\u0026func=foo\u0026bar=1\u0026baz=2\", \"gno.land/r/lorem/ipsum\"},\n\t\t{\"testFunc\", []string{\"key\", \"value\"}, \"/r/lorem/ipsum$help\u0026func=testFunc\u0026key=value\", \"gno.land/r/lorem/ipsum\"},\n\t\t{\"noArgsFunc\", []string{}, \"/r/lorem/ipsum$help\u0026func=noArgsFunc\", \"gno.land/r/lorem/ipsum\"},\n\t\t{\"oddArgsFunc\", []string{\"key\"}, \"/r/lorem/ipsum$help\u0026func=oddArgsFunc\", \"gno.land/r/lorem/ipsum\"},\n\t\t{\"foo\", []string{\"bar\", \"1\", \"baz\", \"2\"}, \"https://gno.world/r/lorem/ipsum$help\u0026func=foo\u0026bar=1\u0026baz=2\", \"gno.world/r/lorem/ipsum\"},\n\t\t{\"testFunc\", []string{\"key\", \"value\"}, \"https://gno.world/r/lorem/ipsum$help\u0026func=testFunc\u0026key=value\", \"gno.world/r/lorem/ipsum\"},\n\t\t{\"noArgsFunc\", []string{}, \"https://gno.world/r/lorem/ipsum$help\u0026func=noArgsFunc\", \"gno.world/r/lorem/ipsum\"},\n\t\t{\"oddArgsFunc\", []string{\"key\"}, \"https://gno.world/r/lorem/ipsum$help\u0026func=oddArgsFunc\", \"gno.world/r/lorem/ipsum\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\ttitle := tt.fn\n\t\tt.Run(title, func(t *testing.T) {\n\t\t\tgot := tt.realm.URL(tt.fn, tt.args...)\n\t\t\turequire.Equal(t, tt.want, got)\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":"users","path":"gno.land/p/demo/users","files":[{"name":"types.gno","body":"package users\n\ntype AddressOrName string\n\nfunc (aon AddressOrName) IsName() bool {\n\treturn aon != \"\" \u0026\u0026 aon[0] == '@'\n}\n\nfunc (aon AddressOrName) GetName() (string, bool) {\n\tif len(aon) \u003e= 2 \u0026\u0026 aon[0] == '@' {\n\t\treturn string(aon[1:]), true\n\t}\n\treturn \"\", false\n}\n"},{"name":"users.gno","body":"package users\n\nimport (\n\t\"std\"\n\t\"strconv\"\n)\n\n//----------------------------------------\n// Types\n\ntype User struct {\n\tAddress std.Address\n\tName string\n\tProfile string\n\tNumber int\n\tInvites int\n\tInviter std.Address\n}\n\nfunc (u *User) Render() string {\n\tstr := \"## user \" + u.Name + \"\\n\" +\n\t\t\"\\n\" +\n\t\t\" * address = \" + string(u.Address) + \"\\n\" +\n\t\t\" * \" + strconv.Itoa(u.Invites) + \" invites\\n\"\n\tif u.Inviter != \"\" {\n\t\tstr = str + \" * invited by \" + string(u.Inviter) + \"\\n\"\n\t}\n\tstr = str + \"\\n\" +\n\t\tu.Profile + \"\\n\"\n\treturn str\n}\n"},{"name":"users_test.gno","body":"package users\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":"users","path":"gno.land/r/demo/users","files":[{"name":"preregister.gno","body":"package users\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/users\"\n)\n\n// pre-restricted names\nvar preRestrictedNames = []string{\n\t\"bitcoin\", \"cosmos\", \"newtendermint\", \"ethereum\",\n}\n\n// pre-registered users\nvar preRegisteredUsers = []struct {\n\tName string\n\tAddress std.Address\n}{\n\t// system name\n\t{\"archives\", \"g1xlnyjrnf03ju82v0f98ruhpgnquk28knmjfe5k\"}, // -\u003e @r_archives\n\t{\"demo\", \"g13ek2zz9qurzynzvssyc4sthwppnruhnp0gdz8n\"}, // -\u003e @r_demo\n\t{\"gno\", \"g19602kd9tfxrfd60sgreadt9zvdyyuudcyxsz8a\"}, // -\u003e @r_gno\n\t{\"gnoland\", \"g1g3lsfxhvaqgdv4ccemwpnms4fv6t3aq3p5z6u7\"}, // -\u003e @r_gnoland\n\t{\"gnolang\", \"g1yjlnm3z2630gg5mryjd79907e0zx658wxs9hnd\"}, // -\u003e @r_gnolang\n\t{\"gov\", \"g1g73v2anukg4ej7axwqpthsatzrxjsh0wk797da\"}, // -\u003e @r_gov\n\t{\"nt\", \"g15ge0ae9077eh40erwrn2eq0xw6wupwqthpv34l\"}, // -\u003e @r_nt\n\t{\"sys\", \"g1r929wt2qplfawe4lvqv9zuwfdcz4vxdun7qh8l\"}, // -\u003e @r_sys\n\t{\"x\", \"g164sdpew3c2t3rvxj3kmfv7c7ujlvcw2punzzuz\"}, // -\u003e @r_x\n\n\t// test1 user\n\t{\"test1\", \"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\"}, // -\u003e @test1\n\n\t// Onbloc\n\t{\"gnoswap\", \"g1lmvrrrr4er2us84h2732sru76c9zl2nvknha8c\"}, // -\u003e @r_gnoswap\n\t{\"onbloc\", \"g12vx7dn3dqq89mz550zwunvg4qw6epq73d9csay\"}, // -\u003e @r_onbloc\n\n\t// Dragos\n\t{\"flippando\", \"g1z82x8mxa0pz5s9u7csy6zya4x0ut9uw6p7d8dk\"}, // -\u003e @r_flippando\n\t{\"zentasktic\", \"g1paxgmwy2wzhx0l6qvav2p8thvphc5c030xz35c\"}, // -\u003e @r_zentasktic\n}\n\nfunc init() {\n\t// add pre-registered users\n\tfor _, res := range preRegisteredUsers {\n\t\t// assert not already registered.\n\t\t_, ok := name2User.Get(res.Name)\n\t\tif ok {\n\t\t\tpanic(\"name already registered\")\n\t\t}\n\n\t\t_, ok = addr2User.Get(res.Address.String())\n\t\tif ok {\n\t\t\tpanic(\"address already registered\")\n\t\t}\n\n\t\tcounter++\n\t\tuser := \u0026users.User{\n\t\t\tAddress: res.Address,\n\t\t\tName: res.Name,\n\t\t\tProfile: \"\",\n\t\t\tNumber: counter,\n\t\t\tInvites: int(0),\n\t\t\tInviter: admin,\n\t\t}\n\t\tname2User.Set(res.Name, user)\n\t\taddr2User.Set(res.Address.String(), user)\n\t}\n\n\t// add pre-restricted names\n\tfor _, name := range preRestrictedNames {\n\t\tif _, ok := name2User.Get(name); ok {\n\t\t\tpanic(\"name already registered\")\n\t\t}\n\n\t\trestricted.Set(name, true)\n\t}\n}\n"},{"name":"users.gno","body":"package users\n\nimport (\n\t\"regexp\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/avl/pager\"\n\t\"gno.land/p/demo/avlhelpers\"\n\t\"gno.land/p/demo/users\"\n)\n\n//----------------------------------------\n// State\n\nvar (\n\tadmin std.Address = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\" // @moul\n\n\trestricted avl.Tree // Name -\u003e true - restricted name\n\tname2User avl.Tree // Name -\u003e *users.User\n\taddr2User avl.Tree // std.Address -\u003e *users.User\n\tinvites avl.Tree // string(inviter+\":\"+invited) -\u003e true\n\tcounter int // user id counter\n\tminFee int64 = 20 * 1_000_000 // minimum gnot must be paid to register.\n\tmaxFeeMult int64 = 10 // maximum multiples of minFee accepted.\n)\n\n//----------------------------------------\n// Top-level functions\n\nfunc Register(inviter std.Address, name string, profile string) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// assert invited or paid.\n\tcaller := std.GetCallerAt(2)\n\tif caller != std.OrigCaller() {\n\t\tpanic(\"should not happen\") // because std.AssertOrigCall().\n\t}\n\n\tsentCoins := std.OrigSend()\n\tminCoin := std.NewCoin(\"ugnot\", minFee)\n\n\tif inviter == \"\" {\n\t\t// banker := std.GetBanker(std.BankerTypeOrigSend)\n\t\tif len(sentCoins) == 1 \u0026\u0026 sentCoins[0].IsGTE(minCoin) {\n\t\t\tif sentCoins[0].Amount \u003e minFee*maxFeeMult {\n\t\t\t\tpanic(\"payment must not be greater than \" + strconv.Itoa(int(minFee*maxFeeMult)))\n\t\t\t} else {\n\t\t\t\t// ok\n\t\t\t}\n\t\t} else {\n\t\t\tpanic(\"payment must not be less than \" + strconv.Itoa(int(minFee)))\n\t\t}\n\t} else {\n\t\tinvitekey := inviter.String() + \":\" + caller.String()\n\t\t_, ok := invites.Get(invitekey)\n\t\tif !ok {\n\t\t\tpanic(\"invalid invitation\")\n\t\t}\n\t\tinvites.Remove(invitekey)\n\t}\n\n\t// assert not already registered.\n\t_, ok := name2User.Get(name)\n\tif ok {\n\t\tpanic(\"name already registered: \" + name)\n\t}\n\t_, ok = addr2User.Get(caller.String())\n\tif ok {\n\t\tpanic(\"address already registered: \" + caller.String())\n\t}\n\n\tisInviterAdmin := inviter == admin\n\n\t// check for restricted name\n\tif _, isRestricted := restricted.Get(name); isRestricted {\n\t\t// only address invite by the admin can register restricted name\n\t\tif !isInviterAdmin {\n\t\t\tpanic(\"restricted name: \" + name)\n\t\t}\n\n\t\trestricted.Remove(name)\n\t}\n\n\t// assert name is valid.\n\t// admin inviter can bypass name restriction\n\tif !isInviterAdmin \u0026\u0026 !reName.MatchString(name) {\n\t\tpanic(\"invalid name: \" + name + \" (must be at least 6 characters, lowercase alphanumeric with underscore)\")\n\t}\n\n\t// remainder of fees go toward invites.\n\tinvites := int(0)\n\tif len(sentCoins) == 1 {\n\t\tif sentCoins[0].Denom == \"ugnot\" \u0026\u0026 sentCoins[0].Amount \u003e= minFee {\n\t\t\tinvites = int(sentCoins[0].Amount / minFee)\n\t\t\tif inviter == \"\" \u0026\u0026 invites \u003e 0 {\n\t\t\t\tinvites -= 1\n\t\t\t}\n\t\t}\n\t}\n\t// register.\n\tcounter++\n\tuser := \u0026users.User{\n\t\tAddress: caller,\n\t\tName: name,\n\t\tProfile: profile,\n\t\tNumber: counter,\n\t\tInvites: invites,\n\t\tInviter: inviter,\n\t}\n\tname2User.Set(name, user)\n\taddr2User.Set(caller.String(), user)\n}\n\nfunc Invite(invitee string) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// get caller/inviter.\n\tcaller := std.GetCallerAt(2)\n\tif caller != std.OrigCaller() {\n\t\tpanic(\"should not happen\") // because std.AssertOrigCall().\n\t}\n\tlines := strings.Split(invitee, \"\\n\")\n\tif caller == admin {\n\t\t// nothing to do, all good\n\t} else {\n\t\t// ensure has invites.\n\t\tuserI, ok := addr2User.Get(caller.String())\n\t\tif !ok {\n\t\t\tpanic(\"user unknown\")\n\t\t}\n\t\tuser := userI.(*users.User)\n\t\tif user.Invites \u003c= 0 {\n\t\t\tpanic(\"user has no invite tokens\")\n\t\t}\n\t\tuser.Invites -= len(lines)\n\t\tif user.Invites \u003c 0 {\n\t\t\tpanic(\"user has insufficient invite tokens\")\n\t\t}\n\t}\n\t// for each line...\n\tfor _, line := range lines {\n\t\tif line == \"\" {\n\t\t\tcontinue // file bodies have a trailing newline.\n\t\t} else if strings.HasPrefix(line, `//`) {\n\t\t\tcontinue // comment\n\t\t}\n\t\t// record invite.\n\t\tinvitekey := string(caller) + \":\" + string(line)\n\t\tinvites.Set(invitekey, true)\n\t}\n}\n\nfunc GrantInvites(invites string) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// assert admin.\n\tcaller := std.GetCallerAt(2)\n\tif caller != std.OrigCaller() {\n\t\tpanic(\"should not happen\") // because std.AssertOrigCall().\n\t}\n\tif caller != admin {\n\t\tpanic(\"unauthorized\")\n\t}\n\t// for each line...\n\tlines := strings.Split(invites, \"\\n\")\n\tfor _, line := range lines {\n\t\tif line == \"\" {\n\t\t\tcontinue // file bodies have a trailing newline.\n\t\t} else if strings.HasPrefix(line, `//`) {\n\t\t\tcontinue // comment\n\t\t}\n\t\t// parse name and invites.\n\t\tvar name string\n\t\tvar invites int\n\t\tparts := strings.Split(line, \":\")\n\t\tif len(parts) == 1 { // short for :1.\n\t\t\tname = parts[0]\n\t\t\tinvites = 1\n\t\t} else if len(parts) == 2 {\n\t\t\tname = parts[0]\n\t\t\tinvites_, err := strconv.Atoi(parts[1])\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t\tinvites = int(invites_)\n\t\t} else {\n\t\t\tpanic(\"should not happen\")\n\t\t}\n\t\t// give invites.\n\t\tuserI, ok := name2User.Get(name)\n\t\tif !ok {\n\t\t\t// maybe address.\n\t\t\tuserI, ok = addr2User.Get(name)\n\t\t\tif !ok {\n\t\t\t\tpanic(\"invalid user \" + name)\n\t\t\t}\n\t\t}\n\t\tuser := userI.(*users.User)\n\t\tuser.Invites += invites\n\t}\n}\n\n// Any leftover fees go toward invitations.\nfunc SetMinFee(newMinFee int64) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// assert admin caller.\n\tcaller := std.GetCallerAt(2)\n\tif caller != admin {\n\t\tpanic(\"unauthorized\")\n\t}\n\t// update global variables.\n\tminFee = newMinFee\n}\n\n// This helps prevent fat finger accidents.\nfunc SetMaxFeeMultiple(newMaxFeeMult int64) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// assert admin caller.\n\tcaller := std.GetCallerAt(2)\n\tif caller != admin {\n\t\tpanic(\"unauthorized\")\n\t}\n\t// update global variables.\n\tmaxFeeMult = newMaxFeeMult\n}\n\n//----------------------------------------\n// Exposed public functions\n\nfunc GetUserByName(name string) *users.User {\n\tuserI, ok := name2User.Get(name)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn userI.(*users.User)\n}\n\nfunc GetUserByAddress(addr std.Address) *users.User {\n\tuserI, ok := addr2User.Get(addr.String())\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn userI.(*users.User)\n}\n\n// unlike GetUserByName, input must be \"@\" prefixed for names.\nfunc GetUserByAddressOrName(input users.AddressOrName) *users.User {\n\tname, isName := input.GetName()\n\tif isName {\n\t\treturn GetUserByName(name)\n\t}\n\treturn GetUserByAddress(std.Address(input))\n}\n\n// Get a list of user names starting from the given prefix. Limit the\n// number of results to maxResults. (This can be used for a name search tool.)\nfunc ListUsersByPrefix(prefix string, maxResults int) []string {\n\treturn avlhelpers.ListByteStringKeysByPrefix(name2User, prefix, maxResults)\n}\n\nfunc Resolve(input users.AddressOrName) std.Address {\n\tname, isName := input.GetName()\n\tif !isName {\n\t\treturn std.Address(input) // TODO check validity\n\t}\n\n\tuser := GetUserByName(name)\n\treturn user.Address\n}\n\n// Add restricted name to the list\nfunc AdminAddRestrictedName(name string) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// get caller\n\tcaller := std.OrigCaller()\n\t// assert admin\n\tif caller != admin {\n\t\tpanic(\"unauthorized\")\n\t}\n\n\tif user := GetUserByName(name); user != nil {\n\t\tpanic(\"already registered name\")\n\t}\n\n\t// register restricted name\n\n\trestricted.Set(name, true)\n}\n\n//----------------------------------------\n// Constants\n\n// NOTE: name length must be clearly distinguishable from a bech32 address.\nvar reName = regexp.MustCompile(`^[a-z]+[_a-z0-9]{5,16}$`)\n\n//----------------------------------------\n// Render main page\n\nfunc Render(fullPath string) string {\n\tpath, _ := splitPathAndQuery(fullPath)\n\tif path == \"\" {\n\t\treturn renderHome(fullPath)\n\t} else if len(path) \u003e= 38 { // 39? 40?\n\t\tif path[:2] != \"g1\" {\n\t\t\treturn \"invalid address \" + path\n\t\t}\n\t\tuser := GetUserByAddress(std.Address(path))\n\t\tif user == nil {\n\t\t\t// TODO: display basic information about account.\n\t\t\treturn \"unknown address \" + path\n\t\t}\n\t\treturn user.Render()\n\t} else {\n\t\tuser := GetUserByName(path)\n\t\tif user == nil {\n\t\t\treturn \"unknown username \" + path\n\t\t}\n\t\treturn user.Render()\n\t}\n}\n\nfunc renderHome(path string) string {\n\tdoc := \"\"\n\n\tpage := pager.NewPager(\u0026name2User, 50).MustGetPageByPath(path)\n\n\tfor _, item := range page.Items {\n\t\tuser := item.Value.(*users.User)\n\t\tdoc += \" * [\" + user.Name + \"](/r/demo/users:\" + user.Name + \")\\n\"\n\t}\n\tdoc += \"\\n\"\n\tdoc += page.Selector()\n\treturn doc\n}\n\nfunc splitPathAndQuery(fullPath string) (string, string) {\n\tparts := strings.SplitN(fullPath, \"?\", 2)\n\tpath := parts[0]\n\tqueryString := \"\"\n\tif len(parts) \u003e 1 {\n\t\tqueryString = \"?\" + parts[1]\n\t}\n\treturn path, queryString\n}\n"},{"name":"users_test.gno","body":"package users\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestPreRegisteredTest1(t *testing.T) {\n\tnames := ListUsersByPrefix(\"test1\", 1)\n\tuassert.Equal(t, len(names), 1)\n\tuassert.Equal(t, names[0], \"test1\")\n}\n"},{"name":"z_0_b_filetest.gno","body":"package main\n\n// SEND: 19900000ugnot\n\nimport (\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Error:\n// payment must not be less than 20000000\n"},{"name":"z_0_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tstd.TestSetOrigSend(std.Coins{std.NewCoin(\"dontcare\", 1)}, nil)\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Error:\n// incompatible coin denominations: dontcare, ugnot\n"},{"name":"z_10_filetest.gno","body":"// PKGPATH: gno.land/r/demo/users_test\npackage users_test\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc init() {\n\tcaller := std.OrigCaller() // main\n\ttest2 := testutils.TestAddress(\"test2\")\n\t// as admin, invite gnouser and test2\n\tstd.TestSetOrigCaller(admin)\n\tusers.Invite(caller.String() + \"\\n\" + test2.String())\n\t// register as caller\n\tstd.TestSetOrigCaller(caller)\n\tusers.Register(admin, \"gnouser\", \"my profile\")\n}\n\nfunc main() {\n\t// register as test2\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOrigCaller(test2)\n\tusers.Register(admin, \"test222\", \"my profile 2\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_11_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OrigCaller() // main\n\tstd.TestSetOrigCaller(admin)\n\tusers.AdminAddRestrictedName(\"superrestricted\")\n\n\t// test restricted name\n\tstd.TestSetOrigCaller(caller)\n\tusers.Register(\"\", \"superrestricted\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Error:\n// restricted name: superrestricted\n"},{"name":"z_11b_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OrigCaller() // main\n\tstd.TestSetOrigCaller(admin)\n\t// add restricted name\n\tusers.AdminAddRestrictedName(\"superrestricted\")\n\t// grant invite to caller\n\tusers.Invite(caller.String())\n\t// set back caller\n\tstd.TestSetOrigCaller(caller)\n\t// register restricted name with admin invite\n\tusers.Register(admin, \"superrestricted\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_12_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"alicia\", \"my profile\")\n\n\t{\n\t\t// Normal usage\n\t\tnames := users.ListUsersByPrefix(\"a\", 1)\n\t\tprintln(\"# names: \" + strconv.Itoa(len(names)))\n\t\tprintln(\"name: \" + names[0])\n\t}\n\n\t{\n\t\t// Empty prefix: match all\n\t\tnames := users.ListUsersByPrefix(\"\", 1)\n\t\tprintln(\"# names: \" + strconv.Itoa(len(names)))\n\t\tprintln(\"name: \" + names[0])\n\t}\n\n\t{\n\t\t// The prefix is before \"alicia\"\n\t\tnames := users.ListUsersByPrefix(\"alich\", 1)\n\t\tprintln(\"# names: \" + strconv.Itoa(len(names)))\n\t}\n\n\t{\n\t\t// The prefix is after the last name\n\t\tnames := users.ListUsersByPrefix(\"y\", 10)\n\t\tprintln(\"# names: \" + strconv.Itoa(len(names)))\n\t}\n\n\t// More tests are in p/demo/avlhelpers\n}\n\n// Output:\n// # names: 1\n// name: alicia\n// # names: 1\n// name: alicia\n// # names: 0\n// # names: 0\n"},{"name":"z_1_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_2_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OrigCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test1)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_3_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OrigCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test1)\n\tstd.TestSetOrigSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_4_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OrigCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\ttest2 := testutils.TestAddress(\"test2\")\n\tusers.Invite(test1.String())\n\t// switch to test2 (not test1)\n\tstd.TestSetOrigCaller(test2)\n\tstd.TestSetOrigSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\tprintln(\"done\")\n}\n\n// Error:\n// invalid invitation\n"},{"name":"z_5_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OrigCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test1)\n\tstd.TestSetOrigSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\tprintln(users.Render(\"\"))\n\tprintln(\"========================================\")\n\tprintln(users.Render(\"?page=2\"))\n\tprintln(\"========================================\")\n\tprintln(users.Render(\"gnouser\"))\n\tprintln(\"========================================\")\n\tprintln(users.Render(\"satoshi\"))\n\tprintln(\"========================================\")\n\tprintln(users.Render(\"badname\"))\n}\n\n// Output:\n// * [archives](/r/demo/users:archives)\n// * [demo](/r/demo/users:demo)\n// * [gno](/r/demo/users:gno)\n// * [gnoland](/r/demo/users:gnoland)\n// * [gnolang](/r/demo/users:gnolang)\n// * [gnouser](/r/demo/users:gnouser)\n// * [gov](/r/demo/users:gov)\n// * [nt](/r/demo/users:nt)\n// * [satoshi](/r/demo/users:satoshi)\n// * [sys](/r/demo/users:sys)\n// * [test1](/r/demo/users:test1)\n// * [x](/r/demo/users:x)\n//\n//\n// ========================================\n//\n//\n// ========================================\n// ## user gnouser\n//\n// * address = g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n// * 9 invites\n//\n// my profile\n//\n// ========================================\n// ## user satoshi\n//\n// * address = g1w3jhxap3ta047h6lta047h6lta047h6l4mfnm7\n// * 0 invites\n// * invited by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// my other profile\n//\n// ========================================\n// unknown username badname\n"},{"name":"z_6_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OrigCaller()\n\t// as admin, grant invites to unregistered user.\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\tprintln(\"done\")\n}\n\n// Error:\n// invalid user g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n"},{"name":"z_7_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OrigCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test1)\n\tstd.TestSetOrigSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\t// as admin, grant invites to gnouser(again) and satoshi.\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\\n\" + test1.String() + \":1\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_7b_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OrigCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\\n\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test1)\n\tstd.TestSetOrigSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\t// as admin, grant invites to gnouser(again) and satoshi.\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\\n\" + test1.String() + \":1\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_8_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OrigCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test1)\n\tstd.TestSetOrigSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\t// as admin, grant invites to gnouser(again) and nonexistent user.\n\tstd.TestSetOrigCaller(admin)\n\ttest2 := testutils.TestAddress(\"test2\")\n\tusers.GrantInvites(caller.String() + \":1\\n\" + test2.String() + \":1\")\n\tprintln(\"done\")\n}\n\n// Error:\n// invalid user g1w3jhxapjta047h6lta047h6lta047h6laqcyu4\n"},{"name":"z_9_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OrigCaller() // main\n\ttest2 := testutils.TestAddress(\"test2\")\n\t// as admin, invite gnouser and test2\n\tstd.TestSetOrigCaller(admin)\n\tusers.Invite(caller.String() + \"\\n\" + test2.String())\n\t// register as caller\n\tstd.TestSetOrigCaller(caller)\n\tusers.Register(admin, \"gnouser\", \"my profile\")\n\t// register as test2\n\tstd.TestSetOrigCaller(test2)\n\tusers.Register(admin, \"test222\", \"my profile 2\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\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":"boards","path":"gno.land/r/demo/boards","files":[{"name":"README.md","body":"This is a demo of Gno smart contract programming. This document was\nconstructed by Gno onto a smart contract hosted on the data Realm\nname [\"gno.land/r/demo/boards\"](https://gno.land/r/demo/boards/)\n([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)).\n\n\n\n## Build `gnokey`, create your account, and interact with Gno.\n\nNOTE: Where you see `-remote localhost:26657` here, that flag can be replaced\nwith `-remote gno.land:26657` if you have $GNOT on the testnet.\n(To use the testnet, also replace `-chainid dev` with `-chainid portal-loop` .)\n\n### Build `gnokey` (and other tools).\n\n```bash\ngit clone git@github.com:gnolang/gno.git\ncd gno/gno.land\nmake build\n```\n\n### Generate a seed/mnemonic code.\n\n```bash\n./build/gnokey generate\n```\n\nNOTE: You can generate 24 words with any good bip39 generator.\n\n### Create a new account using your mnemonic.\n\n```bash\n./build/gnokey add -recover KEYNAME\n```\n\nNOTE: `KEYNAME` is your key identifier, and should be changed.\n\n### Verify that you can see your account locally.\n\n```bash\n./build/gnokey list\n```\n\nTake note of your `addr` which looks something like `g17sphqax3kasjptdkmuqvn740u8dhtx4kxl6ljf` .\nYou will use this as your `ACCOUNT_ADDR`.\n\n## Interact with the blockchain.\n\n### Add $GNOT for your account.\n\nBefore starting the `gnoland` node for the first time, your new account can be given $GNOT in the node genesis.\nEdit the file `gno.land/genesis/genesis_balances.txt` and add the following line (simlar to the others), using\nyour `ACCOUNT_ADDR` and `KEYNAME`\n\n`ACCOUNT_ADDR=10000000000ugnot # @KEYNAME`\n\n### Alternative: Run a faucet to add $GNOT.\n\nInstead of editing `gno.land/genesis/genesis_balances.txt`, a more general solution (with more steps)\nis to run a local \"faucet\" and use the web browser to add $GNOT. (This can be done at any time.)\nSee this page: https://github.com/gnolang/gno/blob/master/contribs/gnofaucet/README.md\n\n\n### Start the `gnoland` node.\n\n```bash\n./build/gnoland start\n```\n\nNOTE: The node already has the \"boards\" realm.\n\nLeave this running in the terminal. In a new terminal, cd to the same folder `gno/gno.land` .\n\n### Get your current balance, account number, and sequence number.\n\n```bash\n./build/gnokey query auth/accounts/ACCOUNT_ADDR -remote localhost:26657\n```\n\n### Register a board username with a smart contract call.\n\nThe `USERNAME` for posting can different than your `KEYNAME`. It is internally linked to your `ACCOUNT_ADDR`. It must be at least 6 characters, lowercase alphanumeric with underscore.\n\n```bash\n./build/gnokey maketx call -pkgpath \"gno.land/r/demo/users\" -func \"Register\" -args \"\" -args \"USERNAME\" -args \"Profile description\" -gas-fee \"10000000ugnot\" -gas-wanted \"2000000\" -send \"200000000ugnot\" -broadcast -chainid dev -remote 127.0.0.1:26657 KEYNAME\n```\n\nInteractive documentation: https://gno.land/r/demo/users$help\u0026func=Register\n\n### Create a board with a smart contract call.\n\n```bash\n./build/gnokey maketx call -pkgpath \"gno.land/r/demo/boards\" -func \"CreateBoard\" -args \"BOARDNAME\" -gas-fee \"1000000ugnot\" -gas-wanted \"10000000\" -broadcast -chainid dev -remote localhost:26657 KEYNAME\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateBoard\n\nNext, query for the permanent board ID by querying (you need this to create a new post):\n\n```bash\n./build/gnokey query \"vm/qeval\" -data 'gno.land/r/demo/boards.GetBoardIDFromName(\"BOARDNAME\")' -remote localhost:26657\n```\n\n### Create a post of a board with a smart contract call.\n\nNOTE: If a board was created successfully, your SEQUENCE_NUMBER would have increased.\n\n```bash\n./build/gnokey maketx call -pkgpath \"gno.land/r/demo/boards\" -func \"CreateThread\" -args BOARD_ID -args \"Hello gno.land\" -args \"Text of the post\" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid dev -remote localhost:26657 KEYNAME\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateThread\n\n### Create a comment to a post.\n\n```bash\n./build/gnokey maketx call -pkgpath \"gno.land/r/demo/boards\" -func \"CreateReply\" -args BOARD_ID -args \"1\" -args \"1\" -args \"Nice to meet you too.\" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid dev -remote localhost:26657 KEYNAME\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateReply\n\n```bash\n./build/gnokey query \"vm/qrender\" -data \"gno.land/r/demo/boards:BOARDNAME/1\" -remote localhost:26657\n```\n\n### Render page with optional path expression.\n\nThe contents of `https://gno.land/r/demo/boards:` and `https://gno.land/r/demo/boards:gnolang` are rendered by calling\nthe `Render(path string)` function like so:\n\n```bash\n./build/gnokey query \"vm/qrender\" -data \"gno.land/r/demo/boards:gnolang\"\n```\n## View the board in the browser.\n\n### Start the web server.\n\n```bash\n./build/gnoweb\n```\n\nThis should print something like `Running on http://127.0.0.1:8888` . Leave this running in the terminal.\n\n### View in the browser\n\nIn your browser, navigate to the printed address http://127.0.0.1:8888 .\nTo see you post, click on the package `/r/demo/boards` .\n"},{"name":"board.gno","body":"package boards\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/moul/txlink\"\n)\n\n//----------------------------------------\n// Board\n\ntype BoardID uint64\n\nfunc (bid BoardID) String() string {\n\treturn strconv.Itoa(int(bid))\n}\n\ntype Board struct {\n\tid BoardID // only set for public boards.\n\turl string\n\tname string\n\tcreator std.Address\n\tthreads avl.Tree // Post.id -\u003e *Post\n\tpostsCtr uint64 // increments Post.id\n\tcreatedAt time.Time\n\tdeleted avl.Tree // TODO reserved for fast-delete.\n}\n\nfunc newBoard(id BoardID, url string, name string, creator std.Address) *Board {\n\tif !reName.MatchString(name) {\n\t\tpanic(\"invalid name: \" + name)\n\t}\n\texists := gBoardsByName.Has(name)\n\tif exists {\n\t\tpanic(\"board already exists\")\n\t}\n\treturn \u0026Board{\n\t\tid: id,\n\t\turl: url,\n\t\tname: name,\n\t\tcreator: creator,\n\t\tthreads: avl.Tree{},\n\t\tcreatedAt: time.Now(),\n\t\tdeleted: avl.Tree{},\n\t}\n}\n\n/* TODO support this once we figure out how to ensure URL correctness.\n// A private board is not tracked by gBoards*,\n// but must be persisted by the caller's realm.\n// Private boards have 0 id and does not ping\n// back the remote board on reposts.\nfunc NewPrivateBoard(url string, name string, creator std.Address) *Board {\n\treturn newBoard(0, url, name, creator)\n}\n*/\n\nfunc (board *Board) IsPrivate() bool {\n\treturn board.id == 0\n}\n\nfunc (board *Board) GetThread(pid PostID) *Post {\n\tpidkey := postIDKey(pid)\n\tpostI, exists := board.threads.Get(pidkey)\n\tif !exists {\n\t\treturn nil\n\t}\n\treturn postI.(*Post)\n}\n\nfunc (board *Board) AddThread(creator std.Address, title string, body string) *Post {\n\tpid := board.incGetPostID()\n\tpidkey := postIDKey(pid)\n\tthread := newPost(board, pid, creator, title, body, pid, 0, 0)\n\tboard.threads.Set(pidkey, thread)\n\treturn thread\n}\n\n// NOTE: this can be potentially very expensive for threads with many replies.\n// TODO: implement optional fast-delete where thread is simply moved.\nfunc (board *Board) DeleteThread(pid PostID) {\n\tpidkey := postIDKey(pid)\n\t_, removed := board.threads.Remove(pidkey)\n\tif !removed {\n\t\tpanic(\"thread does not exist with id \" + pid.String())\n\t}\n}\n\nfunc (board *Board) HasPermission(addr std.Address, perm Permission) bool {\n\tif board.creator == addr {\n\t\tswitch perm {\n\t\tcase EditPermission:\n\t\t\treturn true\n\t\tcase DeletePermission:\n\t\t\treturn true\n\t\tdefault:\n\t\t\treturn false\n\t\t}\n\t}\n\treturn false\n}\n\n// Renders the board for display suitable as plaintext in\n// console. This is suitable for demonstration or tests,\n// but not for prod.\nfunc (board *Board) RenderBoard() string {\n\tstr := \"\"\n\tstr += \"\\\\[[post](\" + board.GetPostFormURL() + \")]\\n\\n\"\n\tif board.threads.Size() \u003e 0 {\n\t\tboard.threads.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tif str != \"\" {\n\t\t\t\tstr += \"----------------------------------------\\n\"\n\t\t\t}\n\t\t\tstr += value.(*Post).RenderSummary() + \"\\n\"\n\t\t\treturn false\n\t\t})\n\t}\n\treturn str\n}\n\nfunc (board *Board) incGetPostID() PostID {\n\tboard.postsCtr++\n\treturn PostID(board.postsCtr)\n}\n\nfunc (board *Board) GetURLFromThreadAndReplyID(threadID, replyID PostID) string {\n\tif replyID == 0 {\n\t\treturn board.url + \"/\" + threadID.String()\n\t} else {\n\t\treturn board.url + \"/\" + threadID.String() + \"/\" + replyID.String()\n\t}\n}\n\nfunc (board *Board) GetPostFormURL() string {\n\treturn txlink.URL(\"CreateThread\", \"bid\", board.id.String())\n}\n"},{"name":"boards.gno","body":"package boards\n\nimport (\n\t\"regexp\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n//----------------------------------------\n// Realm (package) state\n\nvar (\n\tgBoards avl.Tree // id -\u003e *Board\n\tgBoardsCtr int // increments Board.id\n\tgBoardsByName avl.Tree // name -\u003e *Board\n\tgDefaultAnonFee = 100000000 // minimum fee required if anonymous\n)\n\n//----------------------------------------\n// Constants\n\nvar reName = regexp.MustCompile(`^[a-z]+[_a-z0-9]{2,29}$`)\n"},{"name":"misc.gno","body":"package boards\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/r/demo/users\"\n)\n\n//----------------------------------------\n// private utility methods\n// XXX ensure these cannot be called from public.\n\nfunc getBoard(bid BoardID) *Board {\n\tbidkey := boardIDKey(bid)\n\tboard_, exists := gBoards.Get(bidkey)\n\tif !exists {\n\t\treturn nil\n\t}\n\tboard := board_.(*Board)\n\treturn board\n}\n\nfunc incGetBoardID() BoardID {\n\tgBoardsCtr++\n\treturn BoardID(gBoardsCtr)\n}\n\nfunc padLeft(str string, length int) string {\n\tif len(str) \u003e= length {\n\t\treturn str\n\t} else {\n\t\treturn strings.Repeat(\" \", length-len(str)) + str\n\t}\n}\n\nfunc padZero(u64 uint64, length int) string {\n\tstr := strconv.Itoa(int(u64))\n\tif len(str) \u003e= length {\n\t\treturn str\n\t} else {\n\t\treturn strings.Repeat(\"0\", length-len(str)) + str\n\t}\n}\n\nfunc boardIDKey(bid BoardID) string {\n\treturn padZero(uint64(bid), 10)\n}\n\nfunc postIDKey(pid PostID) string {\n\treturn padZero(uint64(pid), 10)\n}\n\nfunc indentBody(indent string, body string) string {\n\tlines := strings.Split(body, \"\\n\")\n\tres := \"\"\n\tfor i, line := range lines {\n\t\tif i \u003e 0 {\n\t\t\tres += \"\\n\"\n\t\t}\n\t\tres += indent + line\n\t}\n\treturn res\n}\n\n// NOTE: length must be greater than 3.\nfunc summaryOf(str string, length int) string {\n\tlines := strings.SplitN(str, \"\\n\", 2)\n\tline := lines[0]\n\tif len(line) \u003e length {\n\t\tline = line[:(length-3)] + \"...\"\n\t} else if len(lines) \u003e 1 {\n\t\t// len(line) \u003c= 80\n\t\tline = line + \"...\"\n\t}\n\treturn line\n}\n\nfunc displayAddressMD(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user == nil {\n\t\treturn \"[\" + addr.String() + \"](/r/demo/users:\" + addr.String() + \")\"\n\t} else {\n\t\treturn \"[@\" + user.Name + \"](/r/demo/users:\" + user.Name + \")\"\n\t}\n}\n\nfunc usernameOf(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user == nil {\n\t\treturn \"\"\n\t}\n\treturn user.Name\n}\n"},{"name":"post.gno","body":"package boards\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/moul/txlink\"\n)\n\n//----------------------------------------\n// Post\n\n// NOTE: a PostID is relative to the board.\ntype PostID uint64\n\nfunc (pid PostID) String() string {\n\treturn strconv.Itoa(int(pid))\n}\n\n// A Post is a \"thread\" or a \"reply\" depending on context.\n// A thread is a Post of a Board that holds other replies.\ntype Post struct {\n\tboard *Board\n\tid PostID\n\tcreator std.Address\n\ttitle string // optional\n\tbody string\n\treplies avl.Tree // Post.id -\u003e *Post\n\trepliesAll avl.Tree // Post.id -\u003e *Post (all replies, for top-level posts)\n\treposts avl.Tree // Board.id -\u003e Post.id\n\tthreadID PostID // original Post.id\n\tparentID PostID // parent Post.id (if reply or repost)\n\trepostBoard BoardID // original Board.id (if repost)\n\tcreatedAt time.Time\n\tupdatedAt time.Time\n}\n\nfunc newPost(board *Board, id PostID, creator std.Address, title, body string, threadID, parentID PostID, repostBoard BoardID) *Post {\n\treturn \u0026Post{\n\t\tboard: board,\n\t\tid: id,\n\t\tcreator: creator,\n\t\ttitle: title,\n\t\tbody: body,\n\t\treplies: avl.Tree{},\n\t\trepliesAll: avl.Tree{},\n\t\treposts: avl.Tree{},\n\t\tthreadID: threadID,\n\t\tparentID: parentID,\n\t\trepostBoard: repostBoard,\n\t\tcreatedAt: time.Now(),\n\t}\n}\n\nfunc (post *Post) IsThread() bool {\n\treturn post.parentID == 0\n}\n\nfunc (post *Post) GetPostID() PostID {\n\treturn post.id\n}\n\nfunc (post *Post) AddReply(creator std.Address, body string) *Post {\n\tboard := post.board\n\tpid := board.incGetPostID()\n\tpidkey := postIDKey(pid)\n\treply := newPost(board, pid, creator, \"\", body, post.threadID, post.id, 0)\n\tpost.replies.Set(pidkey, reply)\n\tif post.threadID == post.id {\n\t\tpost.repliesAll.Set(pidkey, reply)\n\t} else {\n\t\tthread := board.GetThread(post.threadID)\n\t\tthread.repliesAll.Set(pidkey, reply)\n\t}\n\treturn reply\n}\n\nfunc (post *Post) Update(title string, body string) {\n\tpost.title = title\n\tpost.body = body\n\tpost.updatedAt = time.Now()\n}\n\nfunc (thread *Post) GetReply(pid PostID) *Post {\n\tpidkey := postIDKey(pid)\n\treplyI, ok := thread.repliesAll.Get(pidkey)\n\tif !ok {\n\t\treturn nil\n\t} else {\n\t\treturn replyI.(*Post)\n\t}\n}\n\nfunc (post *Post) AddRepostTo(creator std.Address, title, body string, dst *Board) *Post {\n\tif !post.IsThread() {\n\t\tpanic(\"cannot repost non-thread post\")\n\t}\n\tpid := dst.incGetPostID()\n\tpidkey := postIDKey(pid)\n\trepost := newPost(dst, pid, creator, title, body, pid, post.id, post.board.id)\n\tdst.threads.Set(pidkey, repost)\n\tif !dst.IsPrivate() {\n\t\tbidkey := boardIDKey(dst.id)\n\t\tpost.reposts.Set(bidkey, pid)\n\t}\n\treturn repost\n}\n\nfunc (thread *Post) DeletePost(pid PostID) {\n\tif thread.id == pid {\n\t\tpanic(\"should not happen\")\n\t}\n\tpidkey := postIDKey(pid)\n\tpostI, removed := thread.repliesAll.Remove(pidkey)\n\tif !removed {\n\t\tpanic(\"post not found in thread\")\n\t}\n\tpost := postI.(*Post)\n\tif post.parentID != thread.id {\n\t\tparent := thread.GetReply(post.parentID)\n\t\tparent.replies.Remove(pidkey)\n\t} else {\n\t\tthread.replies.Remove(pidkey)\n\t}\n}\n\nfunc (post *Post) HasPermission(addr std.Address, perm Permission) bool {\n\tif post.creator == addr {\n\t\tswitch perm {\n\t\tcase EditPermission:\n\t\t\treturn true\n\t\tcase DeletePermission:\n\t\t\treturn true\n\t\tdefault:\n\t\t\treturn false\n\t\t}\n\t}\n\t// post notes inherit permissions of the board.\n\treturn post.board.HasPermission(addr, perm)\n}\n\nfunc (post *Post) GetSummary() string {\n\treturn summaryOf(post.body, 80)\n}\n\nfunc (post *Post) GetURL() string {\n\tif post.IsThread() {\n\t\treturn post.board.GetURLFromThreadAndReplyID(\n\t\t\tpost.id, 0)\n\t} else {\n\t\treturn post.board.GetURLFromThreadAndReplyID(\n\t\t\tpost.threadID, post.id)\n\t}\n}\n\nfunc (post *Post) GetReplyFormURL() string {\n\treturn txlink.URL(\"CreateReply\",\n\t\t\"bid\", post.board.id.String(),\n\t\t\"threadid\", post.threadID.String(),\n\t\t\"postid\", post.id.String(),\n\t)\n}\n\nfunc (post *Post) GetRepostFormURL() string {\n\treturn txlink.URL(\"CreateRepost\",\n\t\t\"bid\", post.board.id.String(),\n\t\t\"postid\", post.id.String(),\n\t)\n}\n\nfunc (post *Post) GetDeleteFormURL() string {\n\treturn txlink.URL(\"DeletePost\",\n\t\t\"bid\", post.board.id.String(),\n\t\t\"threadid\", post.threadID.String(),\n\t\t\"postid\", post.id.String(),\n\t)\n}\n\nfunc (post *Post) RenderSummary() string {\n\tif post.repostBoard != 0 {\n\t\tdstBoard := getBoard(post.repostBoard)\n\t\tif dstBoard == nil {\n\t\t\tpanic(\"repostBoard does not exist\")\n\t\t}\n\t\tthread := dstBoard.GetThread(PostID(post.parentID))\n\t\tif thread == nil {\n\t\t\treturn \"reposted post does not exist\"\n\t\t}\n\t\treturn \"Repost: \" + post.GetSummary() + \"\\n\" + thread.RenderSummary()\n\t}\n\tstr := \"\"\n\tif post.title != \"\" {\n\t\tstr += \"## [\" + summaryOf(post.title, 80) + \"](\" + post.GetURL() + \")\\n\"\n\t\tstr += \"\\n\"\n\t}\n\tstr += post.GetSummary() + \"\\n\"\n\tstr += \"\\\\- \" + displayAddressMD(post.creator) + \",\"\n\tstr += \" [\" + post.createdAt.Format(\"2006-01-02 3:04pm MST\") + \"](\" + post.GetURL() + \")\"\n\tstr += \" \\\\[[x](\" + post.GetDeleteFormURL() + \")]\"\n\tstr += \" (\" + strconv.Itoa(post.replies.Size()) + \" replies)\"\n\tstr += \" (\" + strconv.Itoa(post.reposts.Size()) + \" reposts)\" + \"\\n\"\n\treturn str\n}\n\nfunc (post *Post) RenderPost(indent string, levels int) string {\n\tif post == nil {\n\t\treturn \"nil post\"\n\t}\n\tstr := \"\"\n\tif post.title != \"\" {\n\t\tstr += indent + \"# \" + post.title + \"\\n\"\n\t\tstr += indent + \"\\n\"\n\t}\n\tstr += indentBody(indent, post.body) + \"\\n\" // TODO: indent body lines.\n\tstr += indent + \"\\\\- \" + displayAddressMD(post.creator) + \", \"\n\tstr += \"[\" + post.createdAt.Format(\"2006-01-02 3:04pm (MST)\") + \"](\" + post.GetURL() + \")\"\n\tstr += \" \\\\[[reply](\" + post.GetReplyFormURL() + \")]\"\n\tif post.IsThread() {\n\t\tstr += \" \\\\[[repost](\" + post.GetRepostFormURL() + \")]\"\n\t}\n\tstr += \" \\\\[[x](\" + post.GetDeleteFormURL() + \")]\\n\"\n\tif levels \u003e 0 {\n\t\tif post.replies.Size() \u003e 0 {\n\t\t\tpost.replies.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\t\tstr += indent + \"\\n\"\n\t\t\t\tstr += value.(*Post).RenderPost(indent+\"\u003e \", levels-1)\n\t\t\t\treturn false\n\t\t\t})\n\t\t}\n\t} else {\n\t\tif post.replies.Size() \u003e 0 {\n\t\t\tstr += indent + \"\\n\"\n\t\t\tstr += indent + \"_[see all \" + strconv.Itoa(post.replies.Size()) + \" replies](\" + post.GetURL() + \")_\\n\"\n\t\t}\n\t}\n\treturn str\n}\n\n// render reply and link to context thread\nfunc (post *Post) RenderInner() string {\n\tif post.IsThread() {\n\t\tpanic(\"unexpected thread\")\n\t}\n\tthreadID := post.threadID\n\t// replyID := post.id\n\tparentID := post.parentID\n\tstr := \"\"\n\tstr += \"_[see thread](\" + post.board.GetURLFromThreadAndReplyID(\n\t\tthreadID, 0) + \")_\\n\\n\"\n\tthread := post.board.GetThread(post.threadID)\n\tvar parent *Post\n\tif thread.id == parentID {\n\t\tparent = thread\n\t} else {\n\t\tparent = thread.GetReply(parentID)\n\t}\n\tstr += parent.RenderPost(\"\", 0)\n\tstr += \"\\n\"\n\tstr += post.RenderPost(\"\u003e \", 5)\n\treturn str\n}\n"},{"name":"public.gno","body":"package boards\n\nimport (\n\t\"std\"\n\t\"strconv\"\n)\n\n//----------------------------------------\n// Public facing functions\n\nfunc GetBoardIDFromName(name string) (BoardID, bool) {\n\tboardI, exists := gBoardsByName.Get(name)\n\tif !exists {\n\t\treturn 0, false\n\t}\n\treturn boardI.(*Board).id, true\n}\n\nfunc CreateBoard(name string) BoardID {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tbid := incGetBoardID()\n\tcaller := std.OrigCaller()\n\tif usernameOf(caller) == \"\" {\n\t\tpanic(\"unauthorized\")\n\t}\n\turl := \"/r/demo/boards:\" + name\n\tboard := newBoard(bid, url, name, caller)\n\tbidkey := boardIDKey(bid)\n\tgBoards.Set(bidkey, board)\n\tgBoardsByName.Set(name, board)\n\treturn board.id\n}\n\nfunc checkAnonFee() bool {\n\tsent := std.OrigSend()\n\tanonFeeCoin := std.NewCoin(\"ugnot\", int64(gDefaultAnonFee))\n\tif len(sent) == 1 \u0026\u0026 sent[0].IsGTE(anonFeeCoin) {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc CreateThread(bid BoardID, title string, body string) PostID {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.OrigCaller()\n\tif usernameOf(caller) == \"\" {\n\t\tif !checkAnonFee() {\n\t\t\tpanic(\"please register, otherwise minimum fee \" + strconv.Itoa(gDefaultAnonFee) + \" is required if anonymous\")\n\t\t}\n\t}\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"board not exist\")\n\t}\n\tthread := board.AddThread(caller, title, body)\n\treturn thread.id\n}\n\nfunc CreateReply(bid BoardID, threadid, postid PostID, body string) PostID {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.OrigCaller()\n\tif usernameOf(caller) == \"\" {\n\t\tif !checkAnonFee() {\n\t\t\tpanic(\"please register, otherwise minimum fee \" + strconv.Itoa(gDefaultAnonFee) + \" is required if anonymous\")\n\t\t}\n\t}\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"board not exist\")\n\t}\n\tthread := board.GetThread(threadid)\n\tif thread == nil {\n\t\tpanic(\"thread not exist\")\n\t}\n\tif postid == threadid {\n\t\treply := thread.AddReply(caller, body)\n\t\treturn reply.id\n\t} else {\n\t\tpost := thread.GetReply(postid)\n\t\treply := post.AddReply(caller, body)\n\t\treturn reply.id\n\t}\n}\n\n// If dstBoard is private, does not ping back.\n// If board specified by bid is private, panics.\nfunc CreateRepost(bid BoardID, postid PostID, title string, body string, dstBoardID BoardID) PostID {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.OrigCaller()\n\tif usernameOf(caller) == \"\" {\n\t\t// TODO: allow with gDefaultAnonFee payment.\n\t\tif !checkAnonFee() {\n\t\t\tpanic(\"please register, otherwise minimum fee \" + strconv.Itoa(gDefaultAnonFee) + \" is required if anonymous\")\n\t\t}\n\t}\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"src board not exist\")\n\t}\n\tif board.IsPrivate() {\n\t\tpanic(\"cannot repost from a private board\")\n\t}\n\tdst := getBoard(dstBoardID)\n\tif dst == nil {\n\t\tpanic(\"dst board not exist\")\n\t}\n\tthread := board.GetThread(postid)\n\tif thread == nil {\n\t\tpanic(\"thread not exist\")\n\t}\n\trepost := thread.AddRepostTo(caller, title, body, dst)\n\treturn repost.id\n}\n\nfunc DeletePost(bid BoardID, threadid, postid PostID, reason string) {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.OrigCaller()\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"board not exist\")\n\t}\n\tthread := board.GetThread(threadid)\n\tif thread == nil {\n\t\tpanic(\"thread not exist\")\n\t}\n\tif postid == threadid {\n\t\t// delete thread\n\t\tif !thread.HasPermission(caller, DeletePermission) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t\tboard.DeleteThread(threadid)\n\t} else {\n\t\t// delete thread's post\n\t\tpost := thread.GetReply(postid)\n\t\tif post == nil {\n\t\t\tpanic(\"post not exist\")\n\t\t}\n\t\tif !post.HasPermission(caller, DeletePermission) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t\tthread.DeletePost(postid)\n\t}\n}\n\nfunc EditPost(bid BoardID, threadid, postid PostID, title, body string) {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.OrigCaller()\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"board not exist\")\n\t}\n\tthread := board.GetThread(threadid)\n\tif thread == nil {\n\t\tpanic(\"thread not exist\")\n\t}\n\tif postid == threadid {\n\t\t// edit thread\n\t\tif !thread.HasPermission(caller, EditPermission) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t\tthread.Update(title, body)\n\t} else {\n\t\t// edit thread's post\n\t\tpost := thread.GetReply(postid)\n\t\tif post == nil {\n\t\t\tpanic(\"post not exist\")\n\t\t}\n\t\tif !post.HasPermission(caller, EditPermission) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t\tpost.Update(title, body)\n\t}\n}\n"},{"name":"render.gno","body":"package boards\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n)\n\n//----------------------------------------\n// Render functions\n\nfunc RenderBoard(bid BoardID) string {\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\treturn \"missing board\"\n\t}\n\treturn board.RenderBoard()\n}\n\nfunc Render(path string) string {\n\tif path == \"\" {\n\t\tstr := \"These are all the boards of this realm:\\n\\n\"\n\t\tgBoards.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tboard := value.(*Board)\n\t\t\tstr += \" * [\" + board.url + \"](\" + board.url + \")\\n\"\n\t\t\treturn false\n\t\t})\n\t\treturn str\n\t}\n\tparts := strings.Split(path, \"/\")\n\tif len(parts) == 1 {\n\t\t// /r/demo/boards:BOARD_NAME\n\t\tname := parts[0]\n\t\tboardI, exists := gBoardsByName.Get(name)\n\t\tif !exists {\n\t\t\treturn \"board does not exist: \" + name\n\t\t}\n\t\treturn boardI.(*Board).RenderBoard()\n\t} else if len(parts) == 2 {\n\t\t// /r/demo/boards:BOARD_NAME/THREAD_ID\n\t\tname := parts[0]\n\t\tboardI, exists := gBoardsByName.Get(name)\n\t\tif !exists {\n\t\t\treturn \"board does not exist: \" + name\n\t\t}\n\t\tpid, err := strconv.Atoi(parts[1])\n\t\tif err != nil {\n\t\t\treturn \"invalid thread id: \" + parts[1]\n\t\t}\n\t\tboard := boardI.(*Board)\n\t\tthread := board.GetThread(PostID(pid))\n\t\tif thread == nil {\n\t\t\treturn \"thread does not exist with id: \" + parts[1]\n\t\t}\n\t\treturn thread.RenderPost(\"\", 5)\n\t} else if len(parts) == 3 {\n\t\t// /r/demo/boards:BOARD_NAME/THREAD_ID/REPLY_ID\n\t\tname := parts[0]\n\t\tboardI, exists := gBoardsByName.Get(name)\n\t\tif !exists {\n\t\t\treturn \"board does not exist: \" + name\n\t\t}\n\t\tpid, err := strconv.Atoi(parts[1])\n\t\tif err != nil {\n\t\t\treturn \"invalid thread id: \" + parts[1]\n\t\t}\n\t\tboard := boardI.(*Board)\n\t\tthread := board.GetThread(PostID(pid))\n\t\tif thread == nil {\n\t\t\treturn \"thread does not exist with id: \" + parts[1]\n\t\t}\n\t\trid, err := strconv.Atoi(parts[2])\n\t\tif err != nil {\n\t\t\treturn \"invalid reply id: \" + parts[2]\n\t\t}\n\t\treply := thread.GetReply(PostID(rid))\n\t\tif reply == nil {\n\t\t\treturn \"reply does not exist with id: \" + parts[2]\n\t\t}\n\t\treturn reply.RenderInner()\n\t} else {\n\t\treturn \"unrecognized path \" + path\n\t}\n}\n"},{"name":"role.gno","body":"package boards\n\ntype Permission string\n\nconst (\n\tDeletePermission Permission = \"role:delete\"\n\tEditPermission Permission = \"role:edit\"\n)\n"},{"name":"z_0_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\nimport (\n\t\"gno.land/r/demo/boards\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid := boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// unauthorized\n"},{"name":"z_0_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 19900000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid = boards.CreateBoard(\"test_board\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// payment must not be less than 20000000\n"},{"name":"z_0_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tboards.CreateThread(1, \"First Post (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// board not exist\n"},{"name":"z_0_d_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateReply(bid, 0, 0, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// thread not exist\n"},{"name":"z_0_e_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tboards.CreateReply(bid, 0, 0, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// board not exist\n"},{"name":"z_0_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 20000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid := boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Output:\n// \\[[post](/r/demo/boards$help\u0026func=CreateThread\u0026bid=1)]\n//\n// ----------------------------------------\n// ## [First Post (title)](/r/demo/boards:test_board/1)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/1) \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)] (0 replies) (0 reposts)\n//\n// ----------------------------------------\n// ## [Second Post (title)](/r/demo/boards:test_board/2)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/2) \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)] (1 replies) (0 reposts)\n"},{"name":"z_10_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// boardId 2 not exist\n\tboards.DeletePost(2, pid, pid, \"\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// board not exist\n"},{"name":"z_10_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// pid of 2 not exist\n\tboards.DeletePost(bid, 2, 2, \"\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// thread not exist\n"},{"name":"z_10_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"First reply of the First post\\n\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.DeletePost(bid, pid, rid, \"\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e First reply of the First post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n//\n// ----------------------------------------------------\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n"},{"name":"z_10_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.DeletePost(bid, pid, pid, \"\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// ----------------------------------------------------\n// thread does not exist with id: 1\n"},{"name":"z_11_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// board 2 not exist\n\tboards.EditPost(2, pid, pid, \"Edited: First Post in (title)\", \"Edited: Body of the first post. (body)\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// board not exist\n"},{"name":"z_11_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// thread 2 not exist\n\tboards.EditPost(bid, 2, pid, \"Edited: First Post in (title)\", \"Edited: Body of the first post. (body)\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// thread not exist\n"},{"name":"z_11_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// post 2 not exist\n\tboards.EditPost(bid, pid, 2, \"Edited: First Post in (title)\", \"Edited: Body of the first post. (body)\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// post not exist\n"},{"name":"z_11_d_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"First reply of the First post\\n\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.EditPost(bid, pid, rid, \"\", \"Edited: First reply of the First post\\n\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e First reply of the First post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n//\n// ----------------------------------------------------\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e Edited: First reply of the First post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n"},{"name":"z_11_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.EditPost(bid, pid, pid, \"Edited: First Post in (title)\", \"Edited: Body of the first post. (body)\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// ----------------------------------------------------\n// # Edited: First Post in (title)\n//\n// Edited: Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n"},{"name":"z_12_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// create a post via registered user\n\tbid1 := boards.CreateBoard(\"test_board1\")\n\tpid := boards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tbid2 := boards.CreateBoard(\"test_board2\")\n\n\t// create a repost via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOrigCaller(test2)\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\n\trid := boards.CreateRepost(bid1, pid, \"\", \"Check this out\", bid2)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board1\"))\n}\n\n// Error:\n// please register, otherwise minimum fee 100000000 is required if anonymous\n"},{"name":"z_12_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid1 := boards.CreateBoard(\"test_board1\")\n\tpid := boards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tbid2 := boards.CreateBoard(\"test_board2\")\n\n\t// create a repost to a non-existing board\n\trid := boards.CreateRepost(5, pid, \"\", \"Check this out\", bid2)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board1\"))\n}\n\n// Error:\n// src board not exist\n"},{"name":"z_12_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid1 := boards.CreateBoard(\"test_board1\")\n\tboards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tbid2 := boards.CreateBoard(\"test_board2\")\n\n\t// create a repost to a non-existing thread\n\trid := boards.CreateRepost(bid1, 5, \"\", \"Check this out\", bid2)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board1\"))\n}\n\n// Error:\n// thread not exist\n"},{"name":"z_12_d_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid1 := boards.CreateBoard(\"test_board1\")\n\tpid := boards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tboards.CreateBoard(\"test_board2\")\n\n\t// create a repost to a non-existing destination board\n\trid := boards.CreateRepost(bid1, pid, \"\", \"Check this out\", 5)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board1\"))\n}\n\n// Error:\n// dst board not exist\n"},{"name":"z_12_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid1 boards.BoardID\n\tbid2 boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid1 = boards.CreateBoard(\"test_board1\")\n\tpid = boards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tbid2 = boards.CreateBoard(\"test_board2\")\n}\n\nfunc main() {\n\trid := boards.CreateRepost(bid1, pid, \"\", \"Check this out\", bid2)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board2\"))\n}\n\n// Output:\n// 1\n// \\[[post](/r/demo/boards$help\u0026func=CreateThread\u0026bid=2)]\n//\n// ----------------------------------------\n// Repost: Check this out\n// ## [First Post (title)](/r/demo/boards:test_board1/1)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board1/1) \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)] (0 replies) (1 reposts)\n"},{"name":"z_1_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar board *boards.Board\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\t_ = boards.CreateBoard(\"test_board_1\")\n\t_ = boards.CreateBoard(\"test_board_2\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"\"))\n}\n\n// Output:\n// These are all the boards of this realm:\n//\n// * [/r/demo/boards:test_board_1](/r/demo/boards:test_board_1)\n// * [/r/demo/boards:test_board_2](/r/demo/boards:test_board_2)\n"},{"name":"z_2_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n"},{"name":"z_3_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n}\n\nfunc main() {\n\trid := boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// 3\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n"},{"name":"z_4_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid := boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n\tprintln(rid)\n}\n\nfunc main() {\n\trid2 := boards.CreateReply(bid, pid, pid, \"Second reply of the second post\")\n\tprintln(rid2)\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// 3\n// 4\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n//\n// \u003e Second reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=4)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=4)]\n\n// Realm:\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/boards\"]\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111\",\n// \"ModTime\": \"123\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"68663c8895d37d479e417c11e21badfe21345c61\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:112\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Post\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"3f34ac77289aa1d5f9a2f8b6d083138325816fb0\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {},\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"94a6665a44bac6ede7f3e3b87173e537b12f9532\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"bc8e5b4e782a0bbc4ac9689681f119beb7b34d59\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"9957eadbc91dd32f33b0d815e041a32dbdea0671\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131]={\n// \"Fields\": [\n// {\n// \"N\": \"AAAAgJSeXbo=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"65536\"\n// }\n// },\n// {\n// \"N\": \"AbSNdvQQIhE=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"1024\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Location\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"336074805fc853987abe6f7fe3ad97a6a6f3077a:2\"\n// },\n// \"Index\": \"182\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"65536\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"1024\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Location\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Board\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:84\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"N\": \"BAAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.PostID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"std.Address\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"Second reply of the second post\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"f91e355bd19240f0f3350a7fa0e6a82b72225916\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"9ee9c4117be283fc51ffcc5ecd65b75ecef5a9dd\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"eb768b0140a5fe95f9c58747f0960d647dacfd42\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.PostID\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.PostID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.BoardID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Time\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"0fd3352422af0a56a77ef2c9e88f479054e3d51f\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Time\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"bed4afa8ffdbbf775451c947fc68b27a345ce32a\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132\"\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\",\n// \"IsEscaped\": true,\n// \"ModTime\": \"0\",\n// \"RefCount\": \"2\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Post\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"c45bbd47a46681a63af973db0ec2180922e4a8ae\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\"\n// }\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120\",\n// \"ModTime\": \"134\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"dc1f011553dc53e7a846049e08cc77fa35ea6a51\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:121\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Post\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"96b86b4585c7f1075d7794180a5581f72733a7ab\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {},\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"32274e1f28fb2b97d67a1262afd362d370de7faa\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"c2cfd6aec36a462f35bf02e5bf4a127aa1bb7ac2\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"5cb875179e86d32c517322af7a323b2a5f3e6cc5\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\"\n// }\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:85]={\n// \"Fields\": [\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.BoardID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"/r/demo/boards:test_board\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"test_board\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"std.Address\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"a416a751c3a45a1e5cba11e737c51340b081e372\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:86\"\n// }\n// },\n// {\n// \"N\": \"BAAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"65536\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Time\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"36299fccbc13f2a84c4629fad4cb940f0bd4b1c6\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:87\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"af6ed0268f99b7f369329094eb6dfaea7812708b\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:88\"\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:85\",\n// \"ModTime\": \"121\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:84\",\n// \"RefCount\": \"1\"\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"9809329dc1ddc5d3556f7a8fa3c2cebcbf65560b\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106\",\n// \"ModTime\": \"121\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:105\",\n// \"RefCount\": \"1\"\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"ceae9a1c4ed28bb51062e6ccdccfad0caafd1c4f\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107\",\n// \"ModTime\": \"121\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:105\",\n// \"RefCount\": \"1\"\n// }\n// }\n// switchrealm[\"gno.land/r/demo/boards\"]\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/boards\"]\n// switchrealm[\"gno.land/r/demo/boards_test\"]\n"},{"name":"z_5_b_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// create board via registered user\n\tbid := boards.CreateBoard(\"test_board\")\n\n\t// create post via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOrigCaller(test2)\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\n\tpid := boards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// please register, otherwise minimum fee 100000000 is required if anonymous\n"},{"name":"z_5_c_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// create board via registered user\n\tbid := boards.CreateBoard(\"test_board\")\n\n\t// create post via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOrigCaller(test2)\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 101000000}}, nil)\n\n\tpid := boards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the first post\")\n\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post (title)\n//\n// Body of the first post. (body)\n// \\- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/r/demo/users:g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e Reply of the first post\n// \u003e \\- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/r/demo/users:g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n"},{"name":"z_5_d_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// create board via registered user\n\tbid := boards.CreateBoard(\"test_board\")\n\tpid := boards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\n\t// create reply via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOrigCaller(test2)\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\tboards.CreateReply(bid, pid, pid, \"Reply of the first post\")\n\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// please register, otherwise minimum fee 100000000 is required if anonymous\n"},{"name":"z_5_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid := boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\trid2 := boards.CreateReply(bid, pid, pid, \"Second reply of the second post\\n\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n//\n// \u003e Second reply of the second post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=4)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=4)]\n"},{"name":"z_6_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tboards.CreateReply(bid, pid, pid, \"Second reply of the second post\\n\")\n\tboards.CreateReply(bid, pid, rid, \"First reply of the first reply\\n\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n// \u003e\n// \u003e \u003e First reply of the first reply\n// \u003e \u003e\n// \u003e \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/5) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=5)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=5)]\n//\n// \u003e Second reply of the second post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=4)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=4)]\n"},{"name":"z_7_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc init() {\n\t// register\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\t// create board and post\n\tbid := boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Output:\n// \\[[post](/r/demo/boards$help\u0026func=CreateThread\u0026bid=1)]\n//\n// ----------------------------------------\n// ## [First Post (title)](/r/demo/boards:test_board/1)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/1) \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)] (0 replies) (0 reposts)\n"},{"name":"z_8_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tboards.CreateReply(bid, pid, pid, \"Second reply of the second post\\n\")\n\trid2 := boards.CreateReply(bid, pid, rid, \"First reply of the first reply\\n\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid)) + \"/\" + strconv.Itoa(int(rid2))))\n}\n\n// Output:\n// _[see thread](/r/demo/boards:test_board/2)_\n//\n// Reply of the second post\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n//\n// _[see all 1 replies](/r/demo/boards:test_board/2/3)_\n//\n// \u003e First reply of the first reply\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/5) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=5)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=5)]\n"},{"name":"z_9_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar dstBoard boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tdstBoard = boards.CreateBoard(\"dst_board\")\n\n\tboards.CreateRepost(0, 0, \"First Post in (title)\", \"Body of the first post. (body)\", dstBoard)\n}\n\nfunc main() {\n}\n\n// Error:\n// src board not exist\n"},{"name":"z_9_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tsrcBoard boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tsrcBoard = boards.CreateBoard(\"first_board\")\n\tpid = boards.CreateThread(srcBoard, \"First Post in (title)\", \"Body of the first post. (body)\")\n\n\tboards.CreateRepost(srcBoard, pid, \"First Post in (title)\", \"Body of the first post. (body)\", 0)\n}\n\nfunc main() {\n}\n\n// Error:\n// dst board not exist\n"},{"name":"z_9_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tfirstBoard boards.BoardID\n\tsecondBoard boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tfirstBoard = boards.CreateBoard(\"first_board\")\n\tsecondBoard = boards.CreateBoard(\"second_board\")\n\tpid = boards.CreateThread(firstBoard, \"First Post in (title)\", \"Body of the first post. (body)\")\n\n\tboards.CreateRepost(firstBoard, pid, \"First Post in (title)\", \"Body of the first post. (body)\", secondBoard)\n}\n\nfunc main() {\n\tprintln(boards.Render(\"second_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:second_board/1/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=2\u0026threadid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=2\u0026threadid=1\u0026postid=1)]\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":"users","path":"gno.land/r/demo/users","files":[{"name":"preregister.gno","body":"package users\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/users\"\n)\n\n// pre-restricted names\nvar preRestrictedNames = []string{\n\t\"bitcoin\", \"cosmos\", \"newtendermint\", \"ethereum\",\n}\n\n// pre-registered users\nvar preRegisteredUsers = []struct {\n\tName string\n\tAddress std.Address\n}{\n\t// system name\n\t{\"archives\", \"g1xlnyjrnf03ju82v0f98ruhpgnquk28knmjfe5k\"}, // -\u003e @r_archives\n\t{\"demo\", \"g13ek2zz9qurzynzvssyc4sthwppnruhnp0gdz8n\"}, // -\u003e @r_demo\n\t{\"gno\", \"g19602kd9tfxrfd60sgreadt9zvdyyuudcyxsz8a\"}, // -\u003e @r_gno\n\t{\"gnoland\", \"g1g3lsfxhvaqgdv4ccemwpnms4fv6t3aq3p5z6u7\"}, // -\u003e @r_gnoland\n\t{\"gnolang\", \"g1yjlnm3z2630gg5mryjd79907e0zx658wxs9hnd\"}, // -\u003e @r_gnolang\n\t{\"gov\", \"g1g73v2anukg4ej7axwqpthsatzrxjsh0wk797da\"}, // -\u003e @r_gov\n\t{\"nt\", \"g15ge0ae9077eh40erwrn2eq0xw6wupwqthpv34l\"}, // -\u003e @r_nt\n\t{\"sys\", \"g1r929wt2qplfawe4lvqv9zuwfdcz4vxdun7qh8l\"}, // -\u003e @r_sys\n\t{\"x\", \"g164sdpew3c2t3rvxj3kmfv7c7ujlvcw2punzzuz\"}, // -\u003e @r_x\n\n\t// test1 user\n\t{\"test1\", \"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\"}, // -\u003e @test1\n\n\t// Onbloc\n\t{\"gnoswap\", \"g1lmvrrrr4er2us84h2732sru76c9zl2nvknha8c\"}, // -\u003e @r_gnoswap\n\t{\"onbloc\", \"g12vx7dn3dqq89mz550zwunvg4qw6epq73d9csay\"}, // -\u003e @r_onbloc\n\n\t// Dragos\n\t{\"flippando\", \"g1z82x8mxa0pz5s9u7csy6zya4x0ut9uw6p7d8dk\"}, // -\u003e @r_flippando\n\t{\"zentasktic\", \"g1paxgmwy2wzhx0l6qvav2p8thvphc5c030xz35c\"}, // -\u003e @r_zentasktic\n}\n\nfunc init() {\n\t// add pre-registered users\n\tfor _, res := range preRegisteredUsers {\n\t\t// assert not already registered.\n\t\t_, ok := name2User.Get(res.Name)\n\t\tif ok {\n\t\t\tpanic(\"name already registered\")\n\t\t}\n\n\t\t_, ok = addr2User.Get(res.Address.String())\n\t\tif ok {\n\t\t\tpanic(\"address already registered\")\n\t\t}\n\n\t\tcounter++\n\t\tuser := \u0026users.User{\n\t\t\tAddress: res.Address,\n\t\t\tName: res.Name,\n\t\t\tProfile: \"\",\n\t\t\tNumber: counter,\n\t\t\tInvites: int(0),\n\t\t\tInviter: admin,\n\t\t}\n\t\tname2User.Set(res.Name, user)\n\t\taddr2User.Set(res.Address.String(), user)\n\t}\n\n\t// add pre-restricted names\n\tfor _, name := range preRestrictedNames {\n\t\tif _, ok := name2User.Get(name); ok {\n\t\t\tpanic(\"name already registered\")\n\t\t}\n\n\t\trestricted.Set(name, true)\n\t}\n}\n"},{"name":"users.gno","body":"package users\n\nimport (\n\t\"regexp\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/avl/pager\"\n\t\"gno.land/p/demo/avlhelpers\"\n\t\"gno.land/p/demo/users\"\n)\n\n//----------------------------------------\n// State\n\nvar (\n\tadmin std.Address = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\" // @moul\n\n\trestricted avl.Tree // Name -\u003e true - restricted name\n\tname2User avl.Tree // Name -\u003e *users.User\n\taddr2User avl.Tree // std.Address -\u003e *users.User\n\tinvites avl.Tree // string(inviter+\":\"+invited) -\u003e true\n\tcounter int // user id counter\n\tminFee int64 = 20 * 1_000_000 // minimum gnot must be paid to register.\n\tmaxFeeMult int64 = 10 // maximum multiples of minFee accepted.\n)\n\n//----------------------------------------\n// Top-level functions\n\nfunc Register(inviter std.Address, name string, profile string) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// assert invited or paid.\n\tcaller := std.GetCallerAt(2)\n\tif caller != std.OriginCaller() {\n\t\tpanic(\"should not happen\") // because std.AssertOrigCall().\n\t}\n\n\tsentCoins := std.OriginSend()\n\tminCoin := std.NewCoin(\"ugnot\", minFee)\n\n\tif inviter == \"\" {\n\t\t// banker := std.GetBanker(std.BankerTypeOriginSend)\n\t\tif len(sentCoins) == 1 \u0026\u0026 sentCoins[0].IsGTE(minCoin) {\n\t\t\tif sentCoins[0].Amount \u003e minFee*maxFeeMult {\n\t\t\t\tpanic(\"payment must not be greater than \" + strconv.Itoa(int(minFee*maxFeeMult)))\n\t\t\t} else {\n\t\t\t\t// ok\n\t\t\t}\n\t\t} else {\n\t\t\tpanic(\"payment must not be less than \" + strconv.Itoa(int(minFee)))\n\t\t}\n\t} else {\n\t\tinvitekey := inviter.String() + \":\" + caller.String()\n\t\t_, ok := invites.Get(invitekey)\n\t\tif !ok {\n\t\t\tpanic(\"invalid invitation\")\n\t\t}\n\t\tinvites.Remove(invitekey)\n\t}\n\n\t// assert not already registered.\n\t_, ok := name2User.Get(name)\n\tif ok {\n\t\tpanic(\"name already registered: \" + name)\n\t}\n\t_, ok = addr2User.Get(caller.String())\n\tif ok {\n\t\tpanic(\"address already registered: \" + caller.String())\n\t}\n\n\tisInviterAdmin := inviter == admin\n\n\t// check for restricted name\n\tif _, isRestricted := restricted.Get(name); isRestricted {\n\t\t// only address invite by the admin can register restricted name\n\t\tif !isInviterAdmin {\n\t\t\tpanic(\"restricted name: \" + name)\n\t\t}\n\n\t\trestricted.Remove(name)\n\t}\n\n\t// assert name is valid.\n\t// admin inviter can bypass name restriction\n\tif !isInviterAdmin \u0026\u0026 !reName.MatchString(name) {\n\t\tpanic(\"invalid name: \" + name + \" (must be at least 6 characters, lowercase alphanumeric with underscore)\")\n\t}\n\n\t// remainder of fees go toward invites.\n\tinvites := int(0)\n\tif len(sentCoins) == 1 {\n\t\tif sentCoins[0].Denom == \"ugnot\" \u0026\u0026 sentCoins[0].Amount \u003e= minFee {\n\t\t\tinvites = int(sentCoins[0].Amount / minFee)\n\t\t\tif inviter == \"\" \u0026\u0026 invites \u003e 0 {\n\t\t\t\tinvites -= 1\n\t\t\t}\n\t\t}\n\t}\n\t// register.\n\tcounter++\n\tuser := \u0026users.User{\n\t\tAddress: caller,\n\t\tName: name,\n\t\tProfile: profile,\n\t\tNumber: counter,\n\t\tInvites: invites,\n\t\tInviter: inviter,\n\t}\n\tname2User.Set(name, user)\n\taddr2User.Set(caller.String(), user)\n}\n\nfunc Invite(invitee string) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// get caller/inviter.\n\tcaller := std.GetCallerAt(2)\n\tif caller != std.OriginCaller() {\n\t\tpanic(\"should not happen\") // because std.AssertOrigCall().\n\t}\n\tlines := strings.Split(invitee, \"\\n\")\n\tif caller == admin {\n\t\t// nothing to do, all good\n\t} else {\n\t\t// ensure has invites.\n\t\tuserI, ok := addr2User.Get(caller.String())\n\t\tif !ok {\n\t\t\tpanic(\"user unknown\")\n\t\t}\n\t\tuser := userI.(*users.User)\n\t\tif user.Invites \u003c= 0 {\n\t\t\tpanic(\"user has no invite tokens\")\n\t\t}\n\t\tuser.Invites -= len(lines)\n\t\tif user.Invites \u003c 0 {\n\t\t\tpanic(\"user has insufficient invite tokens\")\n\t\t}\n\t}\n\t// for each line...\n\tfor _, line := range lines {\n\t\tif line == \"\" {\n\t\t\tcontinue // file bodies have a trailing newline.\n\t\t} else if strings.HasPrefix(line, `//`) {\n\t\t\tcontinue // comment\n\t\t}\n\t\t// record invite.\n\t\tinvitekey := string(caller) + \":\" + string(line)\n\t\tinvites.Set(invitekey, true)\n\t}\n}\n\nfunc GrantInvites(invites string) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// assert admin.\n\tcaller := std.GetCallerAt(2)\n\tif caller != std.OriginCaller() {\n\t\tpanic(\"should not happen\") // because std.AssertOrigCall().\n\t}\n\tif caller != admin {\n\t\tpanic(\"unauthorized\")\n\t}\n\t// for each line...\n\tlines := strings.Split(invites, \"\\n\")\n\tfor _, line := range lines {\n\t\tif line == \"\" {\n\t\t\tcontinue // file bodies have a trailing newline.\n\t\t} else if strings.HasPrefix(line, `//`) {\n\t\t\tcontinue // comment\n\t\t}\n\t\t// parse name and invites.\n\t\tvar name string\n\t\tvar invites int\n\t\tparts := strings.Split(line, \":\")\n\t\tif len(parts) == 1 { // short for :1.\n\t\t\tname = parts[0]\n\t\t\tinvites = 1\n\t\t} else if len(parts) == 2 {\n\t\t\tname = parts[0]\n\t\t\tinvites_, err := strconv.Atoi(parts[1])\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t\tinvites = int(invites_)\n\t\t} else {\n\t\t\tpanic(\"should not happen\")\n\t\t}\n\t\t// give invites.\n\t\tuserI, ok := name2User.Get(name)\n\t\tif !ok {\n\t\t\t// maybe address.\n\t\t\tuserI, ok = addr2User.Get(name)\n\t\t\tif !ok {\n\t\t\t\tpanic(\"invalid user \" + name)\n\t\t\t}\n\t\t}\n\t\tuser := userI.(*users.User)\n\t\tuser.Invites += invites\n\t}\n}\n\n// Any leftover fees go toward invitations.\nfunc SetMinFee(newMinFee int64) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// assert admin caller.\n\tcaller := std.GetCallerAt(2)\n\tif caller != admin {\n\t\tpanic(\"unauthorized\")\n\t}\n\t// update global variables.\n\tminFee = newMinFee\n}\n\n// This helps prevent fat finger accidents.\nfunc SetMaxFeeMultiple(newMaxFeeMult int64) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// assert admin caller.\n\tcaller := std.GetCallerAt(2)\n\tif caller != admin {\n\t\tpanic(\"unauthorized\")\n\t}\n\t// update global variables.\n\tmaxFeeMult = newMaxFeeMult\n}\n\n//----------------------------------------\n// Exposed public functions\n\nfunc GetUserByName(name string) *users.User {\n\tuserI, ok := name2User.Get(name)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn userI.(*users.User)\n}\n\nfunc GetUserByAddress(addr std.Address) *users.User {\n\tuserI, ok := addr2User.Get(addr.String())\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn userI.(*users.User)\n}\n\n// unlike GetUserByName, input must be \"@\" prefixed for names.\nfunc GetUserByAddressOrName(input users.AddressOrName) *users.User {\n\tname, isName := input.GetName()\n\tif isName {\n\t\treturn GetUserByName(name)\n\t}\n\treturn GetUserByAddress(std.Address(input))\n}\n\n// Get a list of user names starting from the given prefix. Limit the\n// number of results to maxResults. (This can be used for a name search tool.)\nfunc ListUsersByPrefix(prefix string, maxResults int) []string {\n\treturn avlhelpers.ListByteStringKeysByPrefix(name2User, prefix, maxResults)\n}\n\nfunc Resolve(input users.AddressOrName) std.Address {\n\tname, isName := input.GetName()\n\tif !isName {\n\t\treturn std.Address(input) // TODO check validity\n\t}\n\n\tuser := GetUserByName(name)\n\treturn user.Address\n}\n\n// Add restricted name to the list\nfunc AdminAddRestrictedName(name string) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// get caller\n\tcaller := std.OriginCaller()\n\t// assert admin\n\tif caller != admin {\n\t\tpanic(\"unauthorized\")\n\t}\n\n\tif user := GetUserByName(name); user != nil {\n\t\tpanic(\"already registered name\")\n\t}\n\n\t// register restricted name\n\n\trestricted.Set(name, true)\n}\n\n//----------------------------------------\n// Constants\n\n// NOTE: name length must be clearly distinguishable from a bech32 address.\nvar reName = regexp.MustCompile(`^[a-z]+[_a-z0-9]{5,16}$`)\n\n//----------------------------------------\n// Render main page\n\nfunc Render(fullPath string) string {\n\tpath, _ := splitPathAndQuery(fullPath)\n\tif path == \"\" {\n\t\treturn renderHome(fullPath)\n\t} else if len(path) \u003e= 38 { // 39? 40?\n\t\tif path[:2] != \"g1\" {\n\t\t\treturn \"invalid address \" + path\n\t\t}\n\t\tuser := GetUserByAddress(std.Address(path))\n\t\tif user == nil {\n\t\t\t// TODO: display basic information about account.\n\t\t\treturn \"unknown address \" + path\n\t\t}\n\t\treturn user.Render()\n\t} else {\n\t\tuser := GetUserByName(path)\n\t\tif user == nil {\n\t\t\treturn \"unknown username \" + path\n\t\t}\n\t\treturn user.Render()\n\t}\n}\n\nfunc renderHome(path string) string {\n\tdoc := \"\"\n\n\tpage := pager.NewPager(\u0026name2User, 50).MustGetPageByPath(path)\n\n\tfor _, item := range page.Items {\n\t\tuser := item.Value.(*users.User)\n\t\tdoc += \" * [\" + user.Name + \"](/r/demo/users:\" + user.Name + \")\\n\"\n\t}\n\tdoc += \"\\n\"\n\tdoc += page.Selector()\n\treturn doc\n}\n\nfunc splitPathAndQuery(fullPath string) (string, string) {\n\tparts := strings.SplitN(fullPath, \"?\", 2)\n\tpath := parts[0]\n\tqueryString := \"\"\n\tif len(parts) \u003e 1 {\n\t\tqueryString = \"?\" + parts[1]\n\t}\n\treturn path, queryString\n}\n"},{"name":"users_test.gno","body":"package users\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestPreRegisteredTest1(t *testing.T) {\n\tnames := ListUsersByPrefix(\"test1\", 1)\n\tuassert.Equal(t, len(names), 1)\n\tuassert.Equal(t, names[0], \"test1\")\n}\n"},{"name":"z_0_b_filetest.gno","body":"package main\n\n// SEND: 19900000ugnot\n\nimport (\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Error:\n// payment must not be less than 20000000\n"},{"name":"z_0_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tstd.TestSetOriginSend(std.Coins{std.NewCoin(\"dontcare\", 1)}, nil)\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Error:\n// incompatible coin denominations: dontcare, ugnot\n"},{"name":"z_10_filetest.gno","body":"// PKGPATH: gno.land/r/demo/users_test\npackage users_test\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc init() {\n\tcaller := std.OriginCaller() // main\n\ttest2 := testutils.TestAddress(\"test2\")\n\t// as admin, invite gnouser and test2\n\tstd.TestSetOriginCaller(admin)\n\tusers.Invite(caller.String() + \"\\n\" + test2.String())\n\t// register as caller\n\tstd.TestSetOriginCaller(caller)\n\tusers.Register(admin, \"gnouser\", \"my profile\")\n}\n\nfunc main() {\n\t// register as test2\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOriginCaller(test2)\n\tusers.Register(admin, \"test222\", \"my profile 2\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_11_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tstd.TestSetOriginCaller(admin)\n\tusers.AdminAddRestrictedName(\"superrestricted\")\n\n\t// test restricted name\n\tstd.TestSetOriginCaller(caller)\n\tusers.Register(\"\", \"superrestricted\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Error:\n// restricted name: superrestricted\n"},{"name":"z_11b_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tstd.TestSetOriginCaller(admin)\n\t// add restricted name\n\tusers.AdminAddRestrictedName(\"superrestricted\")\n\t// grant invite to caller\n\tusers.Invite(caller.String())\n\t// set back caller\n\tstd.TestSetOriginCaller(caller)\n\t// register restricted name with admin invite\n\tusers.Register(admin, \"superrestricted\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_12_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"alicia\", \"my profile\")\n\n\t{\n\t\t// Normal usage\n\t\tnames := users.ListUsersByPrefix(\"a\", 1)\n\t\tprintln(\"# names: \" + strconv.Itoa(len(names)))\n\t\tprintln(\"name: \" + names[0])\n\t}\n\n\t{\n\t\t// Empty prefix: match all\n\t\tnames := users.ListUsersByPrefix(\"\", 1)\n\t\tprintln(\"# names: \" + strconv.Itoa(len(names)))\n\t\tprintln(\"name: \" + names[0])\n\t}\n\n\t{\n\t\t// The prefix is before \"alicia\"\n\t\tnames := users.ListUsersByPrefix(\"alich\", 1)\n\t\tprintln(\"# names: \" + strconv.Itoa(len(names)))\n\t}\n\n\t{\n\t\t// The prefix is after the last name\n\t\tnames := users.ListUsersByPrefix(\"y\", 10)\n\t\tprintln(\"# names: \" + strconv.Itoa(len(names)))\n\t}\n\n\t// More tests are in p/demo/avlhelpers\n}\n\n// Output:\n// # names: 1\n// name: alicia\n// # names: 1\n// name: alicia\n// # names: 0\n// # names: 0\n"},{"name":"z_1_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_2_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_3_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tstd.TestSetOriginSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_4_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\ttest2 := testutils.TestAddress(\"test2\")\n\tusers.Invite(test1.String())\n\t// switch to test2 (not test1)\n\tstd.TestSetOriginCaller(test2)\n\tstd.TestSetOriginSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\tprintln(\"done\")\n}\n\n// Error:\n// invalid invitation\n"},{"name":"z_5_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tstd.TestSetOriginSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\tprintln(users.Render(\"\"))\n\tprintln(\"========================================\")\n\tprintln(users.Render(\"?page=2\"))\n\tprintln(\"========================================\")\n\tprintln(users.Render(\"gnouser\"))\n\tprintln(\"========================================\")\n\tprintln(users.Render(\"satoshi\"))\n\tprintln(\"========================================\")\n\tprintln(users.Render(\"badname\"))\n}\n\n// Output:\n// * [archives](/r/demo/users:archives)\n// * [demo](/r/demo/users:demo)\n// * [gno](/r/demo/users:gno)\n// * [gnoland](/r/demo/users:gnoland)\n// * [gnolang](/r/demo/users:gnolang)\n// * [gnouser](/r/demo/users:gnouser)\n// * [gov](/r/demo/users:gov)\n// * [nt](/r/demo/users:nt)\n// * [satoshi](/r/demo/users:satoshi)\n// * [sys](/r/demo/users:sys)\n// * [test1](/r/demo/users:test1)\n// * [x](/r/demo/users:x)\n//\n//\n// ========================================\n//\n//\n// ========================================\n// ## user gnouser\n//\n// * address = g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n// * 9 invites\n//\n// my profile\n//\n// ========================================\n// ## user satoshi\n//\n// * address = g1w3jhxap3ta047h6lta047h6lta047h6l4mfnm7\n// * 0 invites\n// * invited by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// my other profile\n//\n// ========================================\n// unknown username badname\n"},{"name":"z_6_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller()\n\t// as admin, grant invites to unregistered user.\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\tprintln(\"done\")\n}\n\n// Error:\n// invalid user g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n"},{"name":"z_7_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tstd.TestSetOriginSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\t// as admin, grant invites to gnouser(again) and satoshi.\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\\n\" + test1.String() + \":1\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_7b_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\\n\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tstd.TestSetOriginSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\t// as admin, grant invites to gnouser(again) and satoshi.\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\\n\" + test1.String() + \":1\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_8_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tstd.TestSetOriginSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\t// as admin, grant invites to gnouser(again) and nonexistent user.\n\tstd.TestSetOriginCaller(admin)\n\ttest2 := testutils.TestAddress(\"test2\")\n\tusers.GrantInvites(caller.String() + \":1\\n\" + test2.String() + \":1\")\n\tprintln(\"done\")\n}\n\n// Error:\n// invalid user g1w3jhxapjta047h6lta047h6lta047h6laqcyu4\n"},{"name":"z_9_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\ttest2 := testutils.TestAddress(\"test2\")\n\t// as admin, invite gnouser and test2\n\tstd.TestSetOriginCaller(admin)\n\tusers.Invite(caller.String() + \"\\n\" + test2.String())\n\t// register as caller\n\tstd.TestSetOriginCaller(caller)\n\tusers.Register(admin, \"gnouser\", \"my profile\")\n\t// register as test2\n\tstd.TestSetOriginCaller(test2)\n\tusers.Register(admin, \"test222\", \"my profile 2\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\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":"boards","path":"gno.land/r/demo/boards","files":[{"name":"README.md","body":"This is a demo of Gno smart contract programming. This document was\nconstructed by Gno onto a smart contract hosted on the data Realm\nname [\"gno.land/r/demo/boards\"](https://gno.land/r/demo/boards/)\n([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)).\n\n\n\n## Build `gnokey`, create your account, and interact with Gno.\n\nNOTE: Where you see `-remote localhost:26657` here, that flag can be replaced\nwith `-remote gno.land:26657` if you have $GNOT on the testnet.\n(To use the testnet, also replace `-chainid dev` with `-chainid portal-loop` .)\n\n### Build `gnokey` (and other tools).\n\n```bash\ngit clone git@github.com:gnolang/gno.git\ncd gno/gno.land\nmake build\n```\n\n### Generate a seed/mnemonic code.\n\n```bash\n./build/gnokey generate\n```\n\nNOTE: You can generate 24 words with any good bip39 generator.\n\n### Create a new account using your mnemonic.\n\n```bash\n./build/gnokey add -recover KEYNAME\n```\n\nNOTE: `KEYNAME` is your key identifier, and should be changed.\n\n### Verify that you can see your account locally.\n\n```bash\n./build/gnokey list\n```\n\nTake note of your `addr` which looks something like `g17sphqax3kasjptdkmuqvn740u8dhtx4kxl6ljf` .\nYou will use this as your `ACCOUNT_ADDR`.\n\n## Interact with the blockchain.\n\n### Add $GNOT for your account.\n\nBefore starting the `gnoland` node for the first time, your new account can be given $GNOT in the node genesis.\nEdit the file `gno.land/genesis/genesis_balances.txt` and add the following line (simlar to the others), using\nyour `ACCOUNT_ADDR` and `KEYNAME`\n\n`ACCOUNT_ADDR=10000000000ugnot # @KEYNAME`\n\n### Alternative: Run a faucet to add $GNOT.\n\nInstead of editing `gno.land/genesis/genesis_balances.txt`, a more general solution (with more steps)\nis to run a local \"faucet\" and use the web browser to add $GNOT. (This can be done at any time.)\nSee this page: https://github.com/gnolang/gno/blob/master/contribs/gnofaucet/README.md\n\n\n### Start the `gnoland` node.\n\n```bash\n./build/gnoland start\n```\n\nNOTE: The node already has the \"boards\" realm.\n\nLeave this running in the terminal. In a new terminal, cd to the same folder `gno/gno.land` .\n\n### Get your current balance, account number, and sequence number.\n\n```bash\n./build/gnokey query auth/accounts/ACCOUNT_ADDR -remote localhost:26657\n```\n\n### Register a board username with a smart contract call.\n\nThe `USERNAME` for posting can different than your `KEYNAME`. It is internally linked to your `ACCOUNT_ADDR`. It must be at least 6 characters, lowercase alphanumeric with underscore.\n\n```bash\n./build/gnokey maketx call -pkgpath \"gno.land/r/demo/users\" -func \"Register\" -args \"\" -args \"USERNAME\" -args \"Profile description\" -gas-fee \"10000000ugnot\" -gas-wanted \"2000000\" -send \"200000000ugnot\" -broadcast -chainid dev -remote 127.0.0.1:26657 KEYNAME\n```\n\nInteractive documentation: https://gno.land/r/demo/users$help\u0026func=Register\n\n### Create a board with a smart contract call.\n\n```bash\n./build/gnokey maketx call -pkgpath \"gno.land/r/demo/boards\" -func \"CreateBoard\" -args \"BOARDNAME\" -gas-fee \"1000000ugnot\" -gas-wanted \"10000000\" -broadcast -chainid dev -remote localhost:26657 KEYNAME\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateBoard\n\nNext, query for the permanent board ID by querying (you need this to create a new post):\n\n```bash\n./build/gnokey query \"vm/qeval\" -data 'gno.land/r/demo/boards.GetBoardIDFromName(\"BOARDNAME\")' -remote localhost:26657\n```\n\n### Create a post of a board with a smart contract call.\n\nNOTE: If a board was created successfully, your SEQUENCE_NUMBER would have increased.\n\n```bash\n./build/gnokey maketx call -pkgpath \"gno.land/r/demo/boards\" -func \"CreateThread\" -args BOARD_ID -args \"Hello gno.land\" -args \"Text of the post\" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid dev -remote localhost:26657 KEYNAME\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateThread\n\n### Create a comment to a post.\n\n```bash\n./build/gnokey maketx call -pkgpath \"gno.land/r/demo/boards\" -func \"CreateReply\" -args BOARD_ID -args \"1\" -args \"1\" -args \"Nice to meet you too.\" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid dev -remote localhost:26657 KEYNAME\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateReply\n\n```bash\n./build/gnokey query \"vm/qrender\" -data \"gno.land/r/demo/boards:BOARDNAME/1\" -remote localhost:26657\n```\n\n### Render page with optional path expression.\n\nThe contents of `https://gno.land/r/demo/boards:` and `https://gno.land/r/demo/boards:gnolang` are rendered by calling\nthe `Render(path string)` function like so:\n\n```bash\n./build/gnokey query \"vm/qrender\" -data \"gno.land/r/demo/boards:gnolang\"\n```\n## View the board in the browser.\n\n### Start the web server.\n\n```bash\n./build/gnoweb\n```\n\nThis should print something like `Running on http://127.0.0.1:8888` . Leave this running in the terminal.\n\n### View in the browser\n\nIn your browser, navigate to the printed address http://127.0.0.1:8888 .\nTo see you post, click on the package `/r/demo/boards` .\n"},{"name":"board.gno","body":"package boards\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/moul/txlink\"\n)\n\n//----------------------------------------\n// Board\n\ntype BoardID uint64\n\nfunc (bid BoardID) String() string {\n\treturn strconv.Itoa(int(bid))\n}\n\ntype Board struct {\n\tid BoardID // only set for public boards.\n\turl string\n\tname string\n\tcreator std.Address\n\tthreads avl.Tree // Post.id -\u003e *Post\n\tpostsCtr uint64 // increments Post.id\n\tcreatedAt time.Time\n\tdeleted avl.Tree // TODO reserved for fast-delete.\n}\n\nfunc newBoard(id BoardID, url string, name string, creator std.Address) *Board {\n\tif !reName.MatchString(name) {\n\t\tpanic(\"invalid name: \" + name)\n\t}\n\texists := gBoardsByName.Has(name)\n\tif exists {\n\t\tpanic(\"board already exists\")\n\t}\n\treturn \u0026Board{\n\t\tid: id,\n\t\turl: url,\n\t\tname: name,\n\t\tcreator: creator,\n\t\tthreads: avl.Tree{},\n\t\tcreatedAt: time.Now(),\n\t\tdeleted: avl.Tree{},\n\t}\n}\n\n/* TODO support this once we figure out how to ensure URL correctness.\n// A private board is not tracked by gBoards*,\n// but must be persisted by the caller's realm.\n// Private boards have 0 id and does not ping\n// back the remote board on reposts.\nfunc NewPrivateBoard(url string, name string, creator std.Address) *Board {\n\treturn newBoard(0, url, name, creator)\n}\n*/\n\nfunc (board *Board) IsPrivate() bool {\n\treturn board.id == 0\n}\n\nfunc (board *Board) GetThread(pid PostID) *Post {\n\tpidkey := postIDKey(pid)\n\tpostI, exists := board.threads.Get(pidkey)\n\tif !exists {\n\t\treturn nil\n\t}\n\treturn postI.(*Post)\n}\n\nfunc (board *Board) AddThread(creator std.Address, title string, body string) *Post {\n\tpid := board.incGetPostID()\n\tpidkey := postIDKey(pid)\n\tthread := newPost(board, pid, creator, title, body, pid, 0, 0)\n\tboard.threads.Set(pidkey, thread)\n\treturn thread\n}\n\n// NOTE: this can be potentially very expensive for threads with many replies.\n// TODO: implement optional fast-delete where thread is simply moved.\nfunc (board *Board) DeleteThread(pid PostID) {\n\tpidkey := postIDKey(pid)\n\t_, removed := board.threads.Remove(pidkey)\n\tif !removed {\n\t\tpanic(\"thread does not exist with id \" + pid.String())\n\t}\n}\n\nfunc (board *Board) HasPermission(addr std.Address, perm Permission) bool {\n\tif board.creator == addr {\n\t\tswitch perm {\n\t\tcase EditPermission:\n\t\t\treturn true\n\t\tcase DeletePermission:\n\t\t\treturn true\n\t\tdefault:\n\t\t\treturn false\n\t\t}\n\t}\n\treturn false\n}\n\n// Renders the board for display suitable as plaintext in\n// console. This is suitable for demonstration or tests,\n// but not for prod.\nfunc (board *Board) RenderBoard() string {\n\tstr := \"\"\n\tstr += \"\\\\[[post](\" + board.GetPostFormURL() + \")]\\n\\n\"\n\tif board.threads.Size() \u003e 0 {\n\t\tboard.threads.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tif str != \"\" {\n\t\t\t\tstr += \"----------------------------------------\\n\"\n\t\t\t}\n\t\t\tstr += value.(*Post).RenderSummary() + \"\\n\"\n\t\t\treturn false\n\t\t})\n\t}\n\treturn str\n}\n\nfunc (board *Board) incGetPostID() PostID {\n\tboard.postsCtr++\n\treturn PostID(board.postsCtr)\n}\n\nfunc (board *Board) GetURLFromThreadAndReplyID(threadID, replyID PostID) string {\n\tif replyID == 0 {\n\t\treturn board.url + \"/\" + threadID.String()\n\t} else {\n\t\treturn board.url + \"/\" + threadID.String() + \"/\" + replyID.String()\n\t}\n}\n\nfunc (board *Board) GetPostFormURL() string {\n\treturn txlink.URL(\"CreateThread\", \"bid\", board.id.String())\n}\n"},{"name":"boards.gno","body":"package boards\n\nimport (\n\t\"regexp\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n//----------------------------------------\n// Realm (package) state\n\nvar (\n\tgBoards avl.Tree // id -\u003e *Board\n\tgBoardsCtr int // increments Board.id\n\tgBoardsByName avl.Tree // name -\u003e *Board\n\tgDefaultAnonFee = 100000000 // minimum fee required if anonymous\n)\n\n//----------------------------------------\n// Constants\n\nvar reName = regexp.MustCompile(`^[a-z]+[_a-z0-9]{2,29}$`)\n"},{"name":"misc.gno","body":"package boards\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/r/demo/users\"\n)\n\n//----------------------------------------\n// private utility methods\n// XXX ensure these cannot be called from public.\n\nfunc getBoard(bid BoardID) *Board {\n\tbidkey := boardIDKey(bid)\n\tboard_, exists := gBoards.Get(bidkey)\n\tif !exists {\n\t\treturn nil\n\t}\n\tboard := board_.(*Board)\n\treturn board\n}\n\nfunc incGetBoardID() BoardID {\n\tgBoardsCtr++\n\treturn BoardID(gBoardsCtr)\n}\n\nfunc padLeft(str string, length int) string {\n\tif len(str) \u003e= length {\n\t\treturn str\n\t} else {\n\t\treturn strings.Repeat(\" \", length-len(str)) + str\n\t}\n}\n\nfunc padZero(u64 uint64, length int) string {\n\tstr := strconv.Itoa(int(u64))\n\tif len(str) \u003e= length {\n\t\treturn str\n\t} else {\n\t\treturn strings.Repeat(\"0\", length-len(str)) + str\n\t}\n}\n\nfunc boardIDKey(bid BoardID) string {\n\treturn padZero(uint64(bid), 10)\n}\n\nfunc postIDKey(pid PostID) string {\n\treturn padZero(uint64(pid), 10)\n}\n\nfunc indentBody(indent string, body string) string {\n\tlines := strings.Split(body, \"\\n\")\n\tres := \"\"\n\tfor i, line := range lines {\n\t\tif i \u003e 0 {\n\t\t\tres += \"\\n\"\n\t\t}\n\t\tres += indent + line\n\t}\n\treturn res\n}\n\n// NOTE: length must be greater than 3.\nfunc summaryOf(str string, length int) string {\n\tlines := strings.SplitN(str, \"\\n\", 2)\n\tline := lines[0]\n\tif len(line) \u003e length {\n\t\tline = line[:(length-3)] + \"...\"\n\t} else if len(lines) \u003e 1 {\n\t\t// len(line) \u003c= 80\n\t\tline = line + \"...\"\n\t}\n\treturn line\n}\n\nfunc displayAddressMD(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user == nil {\n\t\treturn \"[\" + addr.String() + \"](/r/demo/users:\" + addr.String() + \")\"\n\t} else {\n\t\treturn \"[@\" + user.Name + \"](/r/demo/users:\" + user.Name + \")\"\n\t}\n}\n\nfunc usernameOf(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user == nil {\n\t\treturn \"\"\n\t}\n\treturn user.Name\n}\n"},{"name":"post.gno","body":"package boards\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/moul/txlink\"\n)\n\n//----------------------------------------\n// Post\n\n// NOTE: a PostID is relative to the board.\ntype PostID uint64\n\nfunc (pid PostID) String() string {\n\treturn strconv.Itoa(int(pid))\n}\n\n// A Post is a \"thread\" or a \"reply\" depending on context.\n// A thread is a Post of a Board that holds other replies.\ntype Post struct {\n\tboard *Board\n\tid PostID\n\tcreator std.Address\n\ttitle string // optional\n\tbody string\n\treplies avl.Tree // Post.id -\u003e *Post\n\trepliesAll avl.Tree // Post.id -\u003e *Post (all replies, for top-level posts)\n\treposts avl.Tree // Board.id -\u003e Post.id\n\tthreadID PostID // original Post.id\n\tparentID PostID // parent Post.id (if reply or repost)\n\trepostBoard BoardID // original Board.id (if repost)\n\tcreatedAt time.Time\n\tupdatedAt time.Time\n}\n\nfunc newPost(board *Board, id PostID, creator std.Address, title, body string, threadID, parentID PostID, repostBoard BoardID) *Post {\n\treturn \u0026Post{\n\t\tboard: board,\n\t\tid: id,\n\t\tcreator: creator,\n\t\ttitle: title,\n\t\tbody: body,\n\t\treplies: avl.Tree{},\n\t\trepliesAll: avl.Tree{},\n\t\treposts: avl.Tree{},\n\t\tthreadID: threadID,\n\t\tparentID: parentID,\n\t\trepostBoard: repostBoard,\n\t\tcreatedAt: time.Now(),\n\t}\n}\n\nfunc (post *Post) IsThread() bool {\n\treturn post.parentID == 0\n}\n\nfunc (post *Post) GetPostID() PostID {\n\treturn post.id\n}\n\nfunc (post *Post) AddReply(creator std.Address, body string) *Post {\n\tboard := post.board\n\tpid := board.incGetPostID()\n\tpidkey := postIDKey(pid)\n\treply := newPost(board, pid, creator, \"\", body, post.threadID, post.id, 0)\n\tpost.replies.Set(pidkey, reply)\n\tif post.threadID == post.id {\n\t\tpost.repliesAll.Set(pidkey, reply)\n\t} else {\n\t\tthread := board.GetThread(post.threadID)\n\t\tthread.repliesAll.Set(pidkey, reply)\n\t}\n\treturn reply\n}\n\nfunc (post *Post) Update(title string, body string) {\n\tpost.title = title\n\tpost.body = body\n\tpost.updatedAt = time.Now()\n}\n\nfunc (thread *Post) GetReply(pid PostID) *Post {\n\tpidkey := postIDKey(pid)\n\treplyI, ok := thread.repliesAll.Get(pidkey)\n\tif !ok {\n\t\treturn nil\n\t} else {\n\t\treturn replyI.(*Post)\n\t}\n}\n\nfunc (post *Post) AddRepostTo(creator std.Address, title, body string, dst *Board) *Post {\n\tif !post.IsThread() {\n\t\tpanic(\"cannot repost non-thread post\")\n\t}\n\tpid := dst.incGetPostID()\n\tpidkey := postIDKey(pid)\n\trepost := newPost(dst, pid, creator, title, body, pid, post.id, post.board.id)\n\tdst.threads.Set(pidkey, repost)\n\tif !dst.IsPrivate() {\n\t\tbidkey := boardIDKey(dst.id)\n\t\tpost.reposts.Set(bidkey, pid)\n\t}\n\treturn repost\n}\n\nfunc (thread *Post) DeletePost(pid PostID) {\n\tif thread.id == pid {\n\t\tpanic(\"should not happen\")\n\t}\n\tpidkey := postIDKey(pid)\n\tpostI, removed := thread.repliesAll.Remove(pidkey)\n\tif !removed {\n\t\tpanic(\"post not found in thread\")\n\t}\n\tpost := postI.(*Post)\n\tif post.parentID != thread.id {\n\t\tparent := thread.GetReply(post.parentID)\n\t\tparent.replies.Remove(pidkey)\n\t} else {\n\t\tthread.replies.Remove(pidkey)\n\t}\n}\n\nfunc (post *Post) HasPermission(addr std.Address, perm Permission) bool {\n\tif post.creator == addr {\n\t\tswitch perm {\n\t\tcase EditPermission:\n\t\t\treturn true\n\t\tcase DeletePermission:\n\t\t\treturn true\n\t\tdefault:\n\t\t\treturn false\n\t\t}\n\t}\n\t// post notes inherit permissions of the board.\n\treturn post.board.HasPermission(addr, perm)\n}\n\nfunc (post *Post) GetSummary() string {\n\treturn summaryOf(post.body, 80)\n}\n\nfunc (post *Post) GetURL() string {\n\tif post.IsThread() {\n\t\treturn post.board.GetURLFromThreadAndReplyID(\n\t\t\tpost.id, 0)\n\t} else {\n\t\treturn post.board.GetURLFromThreadAndReplyID(\n\t\t\tpost.threadID, post.id)\n\t}\n}\n\nfunc (post *Post) GetReplyFormURL() string {\n\treturn txlink.URL(\"CreateReply\",\n\t\t\"bid\", post.board.id.String(),\n\t\t\"threadid\", post.threadID.String(),\n\t\t\"postid\", post.id.String(),\n\t)\n}\n\nfunc (post *Post) GetRepostFormURL() string {\n\treturn txlink.URL(\"CreateRepost\",\n\t\t\"bid\", post.board.id.String(),\n\t\t\"postid\", post.id.String(),\n\t)\n}\n\nfunc (post *Post) GetDeleteFormURL() string {\n\treturn txlink.URL(\"DeletePost\",\n\t\t\"bid\", post.board.id.String(),\n\t\t\"threadid\", post.threadID.String(),\n\t\t\"postid\", post.id.String(),\n\t)\n}\n\nfunc (post *Post) RenderSummary() string {\n\tif post.repostBoard != 0 {\n\t\tdstBoard := getBoard(post.repostBoard)\n\t\tif dstBoard == nil {\n\t\t\tpanic(\"repostBoard does not exist\")\n\t\t}\n\t\tthread := dstBoard.GetThread(PostID(post.parentID))\n\t\tif thread == nil {\n\t\t\treturn \"reposted post does not exist\"\n\t\t}\n\t\treturn \"Repost: \" + post.GetSummary() + \"\\n\" + thread.RenderSummary()\n\t}\n\tstr := \"\"\n\tif post.title != \"\" {\n\t\tstr += \"## [\" + summaryOf(post.title, 80) + \"](\" + post.GetURL() + \")\\n\"\n\t\tstr += \"\\n\"\n\t}\n\tstr += post.GetSummary() + \"\\n\"\n\tstr += \"\\\\- \" + displayAddressMD(post.creator) + \",\"\n\tstr += \" [\" + post.createdAt.Format(\"2006-01-02 3:04pm MST\") + \"](\" + post.GetURL() + \")\"\n\tstr += \" \\\\[[x](\" + post.GetDeleteFormURL() + \")]\"\n\tstr += \" (\" + strconv.Itoa(post.replies.Size()) + \" replies)\"\n\tstr += \" (\" + strconv.Itoa(post.reposts.Size()) + \" reposts)\" + \"\\n\"\n\treturn str\n}\n\nfunc (post *Post) RenderPost(indent string, levels int) string {\n\tif post == nil {\n\t\treturn \"nil post\"\n\t}\n\tstr := \"\"\n\tif post.title != \"\" {\n\t\tstr += indent + \"# \" + post.title + \"\\n\"\n\t\tstr += indent + \"\\n\"\n\t}\n\tstr += indentBody(indent, post.body) + \"\\n\" // TODO: indent body lines.\n\tstr += indent + \"\\\\- \" + displayAddressMD(post.creator) + \", \"\n\tstr += \"[\" + post.createdAt.Format(\"2006-01-02 3:04pm (MST)\") + \"](\" + post.GetURL() + \")\"\n\tstr += \" \\\\[[reply](\" + post.GetReplyFormURL() + \")]\"\n\tif post.IsThread() {\n\t\tstr += \" \\\\[[repost](\" + post.GetRepostFormURL() + \")]\"\n\t}\n\tstr += \" \\\\[[x](\" + post.GetDeleteFormURL() + \")]\\n\"\n\tif levels \u003e 0 {\n\t\tif post.replies.Size() \u003e 0 {\n\t\t\tpost.replies.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\t\tstr += indent + \"\\n\"\n\t\t\t\tstr += value.(*Post).RenderPost(indent+\"\u003e \", levels-1)\n\t\t\t\treturn false\n\t\t\t})\n\t\t}\n\t} else {\n\t\tif post.replies.Size() \u003e 0 {\n\t\t\tstr += indent + \"\\n\"\n\t\t\tstr += indent + \"_[see all \" + strconv.Itoa(post.replies.Size()) + \" replies](\" + post.GetURL() + \")_\\n\"\n\t\t}\n\t}\n\treturn str\n}\n\n// render reply and link to context thread\nfunc (post *Post) RenderInner() string {\n\tif post.IsThread() {\n\t\tpanic(\"unexpected thread\")\n\t}\n\tthreadID := post.threadID\n\t// replyID := post.id\n\tparentID := post.parentID\n\tstr := \"\"\n\tstr += \"_[see thread](\" + post.board.GetURLFromThreadAndReplyID(\n\t\tthreadID, 0) + \")_\\n\\n\"\n\tthread := post.board.GetThread(post.threadID)\n\tvar parent *Post\n\tif thread.id == parentID {\n\t\tparent = thread\n\t} else {\n\t\tparent = thread.GetReply(parentID)\n\t}\n\tstr += parent.RenderPost(\"\", 0)\n\tstr += \"\\n\"\n\tstr += post.RenderPost(\"\u003e \", 5)\n\treturn str\n}\n"},{"name":"public.gno","body":"package boards\n\nimport (\n\t\"std\"\n\t\"strconv\"\n)\n\n//----------------------------------------\n// Public facing functions\n\nfunc GetBoardIDFromName(name string) (BoardID, bool) {\n\tboardI, exists := gBoardsByName.Get(name)\n\tif !exists {\n\t\treturn 0, false\n\t}\n\treturn boardI.(*Board).id, true\n}\n\nfunc CreateBoard(name string) BoardID {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tbid := incGetBoardID()\n\tcaller := std.OriginCaller()\n\tif usernameOf(caller) == \"\" {\n\t\tpanic(\"unauthorized\")\n\t}\n\turl := \"/r/demo/boards:\" + name\n\tboard := newBoard(bid, url, name, caller)\n\tbidkey := boardIDKey(bid)\n\tgBoards.Set(bidkey, board)\n\tgBoardsByName.Set(name, board)\n\treturn board.id\n}\n\nfunc checkAnonFee() bool {\n\tsent := std.OriginSend()\n\tanonFeeCoin := std.NewCoin(\"ugnot\", int64(gDefaultAnonFee))\n\tif len(sent) == 1 \u0026\u0026 sent[0].IsGTE(anonFeeCoin) {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc CreateThread(bid BoardID, title string, body string) PostID {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.OriginCaller()\n\tif usernameOf(caller) == \"\" {\n\t\tif !checkAnonFee() {\n\t\t\tpanic(\"please register, otherwise minimum fee \" + strconv.Itoa(gDefaultAnonFee) + \" is required if anonymous\")\n\t\t}\n\t}\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"board not exist\")\n\t}\n\tthread := board.AddThread(caller, title, body)\n\treturn thread.id\n}\n\nfunc CreateReply(bid BoardID, threadid, postid PostID, body string) PostID {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.OriginCaller()\n\tif usernameOf(caller) == \"\" {\n\t\tif !checkAnonFee() {\n\t\t\tpanic(\"please register, otherwise minimum fee \" + strconv.Itoa(gDefaultAnonFee) + \" is required if anonymous\")\n\t\t}\n\t}\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"board not exist\")\n\t}\n\tthread := board.GetThread(threadid)\n\tif thread == nil {\n\t\tpanic(\"thread not exist\")\n\t}\n\tif postid == threadid {\n\t\treply := thread.AddReply(caller, body)\n\t\treturn reply.id\n\t} else {\n\t\tpost := thread.GetReply(postid)\n\t\treply := post.AddReply(caller, body)\n\t\treturn reply.id\n\t}\n}\n\n// If dstBoard is private, does not ping back.\n// If board specified by bid is private, panics.\nfunc CreateRepost(bid BoardID, postid PostID, title string, body string, dstBoardID BoardID) PostID {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.OriginCaller()\n\tif usernameOf(caller) == \"\" {\n\t\t// TODO: allow with gDefaultAnonFee payment.\n\t\tif !checkAnonFee() {\n\t\t\tpanic(\"please register, otherwise minimum fee \" + strconv.Itoa(gDefaultAnonFee) + \" is required if anonymous\")\n\t\t}\n\t}\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"src board not exist\")\n\t}\n\tif board.IsPrivate() {\n\t\tpanic(\"cannot repost from a private board\")\n\t}\n\tdst := getBoard(dstBoardID)\n\tif dst == nil {\n\t\tpanic(\"dst board not exist\")\n\t}\n\tthread := board.GetThread(postid)\n\tif thread == nil {\n\t\tpanic(\"thread not exist\")\n\t}\n\trepost := thread.AddRepostTo(caller, title, body, dst)\n\treturn repost.id\n}\n\nfunc DeletePost(bid BoardID, threadid, postid PostID, reason string) {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.OriginCaller()\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"board not exist\")\n\t}\n\tthread := board.GetThread(threadid)\n\tif thread == nil {\n\t\tpanic(\"thread not exist\")\n\t}\n\tif postid == threadid {\n\t\t// delete thread\n\t\tif !thread.HasPermission(caller, DeletePermission) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t\tboard.DeleteThread(threadid)\n\t} else {\n\t\t// delete thread's post\n\t\tpost := thread.GetReply(postid)\n\t\tif post == nil {\n\t\t\tpanic(\"post not exist\")\n\t\t}\n\t\tif !post.HasPermission(caller, DeletePermission) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t\tthread.DeletePost(postid)\n\t}\n}\n\nfunc EditPost(bid BoardID, threadid, postid PostID, title, body string) {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.OriginCaller()\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"board not exist\")\n\t}\n\tthread := board.GetThread(threadid)\n\tif thread == nil {\n\t\tpanic(\"thread not exist\")\n\t}\n\tif postid == threadid {\n\t\t// edit thread\n\t\tif !thread.HasPermission(caller, EditPermission) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t\tthread.Update(title, body)\n\t} else {\n\t\t// edit thread's post\n\t\tpost := thread.GetReply(postid)\n\t\tif post == nil {\n\t\t\tpanic(\"post not exist\")\n\t\t}\n\t\tif !post.HasPermission(caller, EditPermission) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t\tpost.Update(title, body)\n\t}\n}\n"},{"name":"render.gno","body":"package boards\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n)\n\n//----------------------------------------\n// Render functions\n\nfunc RenderBoard(bid BoardID) string {\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\treturn \"missing board\"\n\t}\n\treturn board.RenderBoard()\n}\n\nfunc Render(path string) string {\n\tif path == \"\" {\n\t\tstr := \"These are all the boards of this realm:\\n\\n\"\n\t\tgBoards.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tboard := value.(*Board)\n\t\t\tstr += \" * [\" + board.url + \"](\" + board.url + \")\\n\"\n\t\t\treturn false\n\t\t})\n\t\treturn str\n\t}\n\tparts := strings.Split(path, \"/\")\n\tif len(parts) == 1 {\n\t\t// /r/demo/boards:BOARD_NAME\n\t\tname := parts[0]\n\t\tboardI, exists := gBoardsByName.Get(name)\n\t\tif !exists {\n\t\t\treturn \"board does not exist: \" + name\n\t\t}\n\t\treturn boardI.(*Board).RenderBoard()\n\t} else if len(parts) == 2 {\n\t\t// /r/demo/boards:BOARD_NAME/THREAD_ID\n\t\tname := parts[0]\n\t\tboardI, exists := gBoardsByName.Get(name)\n\t\tif !exists {\n\t\t\treturn \"board does not exist: \" + name\n\t\t}\n\t\tpid, err := strconv.Atoi(parts[1])\n\t\tif err != nil {\n\t\t\treturn \"invalid thread id: \" + parts[1]\n\t\t}\n\t\tboard := boardI.(*Board)\n\t\tthread := board.GetThread(PostID(pid))\n\t\tif thread == nil {\n\t\t\treturn \"thread does not exist with id: \" + parts[1]\n\t\t}\n\t\treturn thread.RenderPost(\"\", 5)\n\t} else if len(parts) == 3 {\n\t\t// /r/demo/boards:BOARD_NAME/THREAD_ID/REPLY_ID\n\t\tname := parts[0]\n\t\tboardI, exists := gBoardsByName.Get(name)\n\t\tif !exists {\n\t\t\treturn \"board does not exist: \" + name\n\t\t}\n\t\tpid, err := strconv.Atoi(parts[1])\n\t\tif err != nil {\n\t\t\treturn \"invalid thread id: \" + parts[1]\n\t\t}\n\t\tboard := boardI.(*Board)\n\t\tthread := board.GetThread(PostID(pid))\n\t\tif thread == nil {\n\t\t\treturn \"thread does not exist with id: \" + parts[1]\n\t\t}\n\t\trid, err := strconv.Atoi(parts[2])\n\t\tif err != nil {\n\t\t\treturn \"invalid reply id: \" + parts[2]\n\t\t}\n\t\treply := thread.GetReply(PostID(rid))\n\t\tif reply == nil {\n\t\t\treturn \"reply does not exist with id: \" + parts[2]\n\t\t}\n\t\treturn reply.RenderInner()\n\t} else {\n\t\treturn \"unrecognized path \" + path\n\t}\n}\n"},{"name":"role.gno","body":"package boards\n\ntype Permission string\n\nconst (\n\tDeletePermission Permission = \"role:delete\"\n\tEditPermission Permission = \"role:edit\"\n)\n"},{"name":"z_0_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\nimport (\n\t\"gno.land/r/demo/boards\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid := boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// unauthorized\n"},{"name":"z_0_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 19900000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid = boards.CreateBoard(\"test_board\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// payment must not be less than 20000000\n"},{"name":"z_0_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tboards.CreateThread(1, \"First Post (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// board not exist\n"},{"name":"z_0_d_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateReply(bid, 0, 0, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// thread not exist\n"},{"name":"z_0_e_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tboards.CreateReply(bid, 0, 0, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// board not exist\n"},{"name":"z_0_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 20000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid := boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Output:\n// \\[[post](/r/demo/boards$help\u0026func=CreateThread\u0026bid=1)]\n//\n// ----------------------------------------\n// ## [First Post (title)](/r/demo/boards:test_board/1)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/1) \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)] (0 replies) (0 reposts)\n//\n// ----------------------------------------\n// ## [Second Post (title)](/r/demo/boards:test_board/2)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/2) \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)] (1 replies) (0 reposts)\n"},{"name":"z_10_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// boardId 2 not exist\n\tboards.DeletePost(2, pid, pid, \"\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// board not exist\n"},{"name":"z_10_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// pid of 2 not exist\n\tboards.DeletePost(bid, 2, 2, \"\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// thread not exist\n"},{"name":"z_10_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"First reply of the First post\\n\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.DeletePost(bid, pid, rid, \"\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e First reply of the First post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n//\n// ----------------------------------------------------\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n"},{"name":"z_10_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.DeletePost(bid, pid, pid, \"\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// ----------------------------------------------------\n// thread does not exist with id: 1\n"},{"name":"z_11_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// board 2 not exist\n\tboards.EditPost(2, pid, pid, \"Edited: First Post in (title)\", \"Edited: Body of the first post. (body)\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// board not exist\n"},{"name":"z_11_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// thread 2 not exist\n\tboards.EditPost(bid, 2, pid, \"Edited: First Post in (title)\", \"Edited: Body of the first post. (body)\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// thread not exist\n"},{"name":"z_11_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// post 2 not exist\n\tboards.EditPost(bid, pid, 2, \"Edited: First Post in (title)\", \"Edited: Body of the first post. (body)\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// post not exist\n"},{"name":"z_11_d_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"First reply of the First post\\n\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.EditPost(bid, pid, rid, \"\", \"Edited: First reply of the First post\\n\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e First reply of the First post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n//\n// ----------------------------------------------------\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e Edited: First reply of the First post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n"},{"name":"z_11_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.EditPost(bid, pid, pid, \"Edited: First Post in (title)\", \"Edited: Body of the first post. (body)\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// ----------------------------------------------------\n// # Edited: First Post in (title)\n//\n// Edited: Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n"},{"name":"z_12_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// create a post via registered user\n\tbid1 := boards.CreateBoard(\"test_board1\")\n\tpid := boards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tbid2 := boards.CreateBoard(\"test_board2\")\n\n\t// create a repost via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOriginCaller(test2)\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\n\trid := boards.CreateRepost(bid1, pid, \"\", \"Check this out\", bid2)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board1\"))\n}\n\n// Error:\n// please register, otherwise minimum fee 100000000 is required if anonymous\n"},{"name":"z_12_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid1 := boards.CreateBoard(\"test_board1\")\n\tpid := boards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tbid2 := boards.CreateBoard(\"test_board2\")\n\n\t// create a repost to a non-existing board\n\trid := boards.CreateRepost(5, pid, \"\", \"Check this out\", bid2)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board1\"))\n}\n\n// Error:\n// src board not exist\n"},{"name":"z_12_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid1 := boards.CreateBoard(\"test_board1\")\n\tboards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tbid2 := boards.CreateBoard(\"test_board2\")\n\n\t// create a repost to a non-existing thread\n\trid := boards.CreateRepost(bid1, 5, \"\", \"Check this out\", bid2)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board1\"))\n}\n\n// Error:\n// thread not exist\n"},{"name":"z_12_d_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid1 := boards.CreateBoard(\"test_board1\")\n\tpid := boards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tboards.CreateBoard(\"test_board2\")\n\n\t// create a repost to a non-existing destination board\n\trid := boards.CreateRepost(bid1, pid, \"\", \"Check this out\", 5)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board1\"))\n}\n\n// Error:\n// dst board not exist\n"},{"name":"z_12_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid1 boards.BoardID\n\tbid2 boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid1 = boards.CreateBoard(\"test_board1\")\n\tpid = boards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tbid2 = boards.CreateBoard(\"test_board2\")\n}\n\nfunc main() {\n\trid := boards.CreateRepost(bid1, pid, \"\", \"Check this out\", bid2)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board2\"))\n}\n\n// Output:\n// 1\n// \\[[post](/r/demo/boards$help\u0026func=CreateThread\u0026bid=2)]\n//\n// ----------------------------------------\n// Repost: Check this out\n// ## [First Post (title)](/r/demo/boards:test_board1/1)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board1/1) \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)] (0 replies) (1 reposts)\n"},{"name":"z_1_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar board *boards.Board\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\t_ = boards.CreateBoard(\"test_board_1\")\n\t_ = boards.CreateBoard(\"test_board_2\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"\"))\n}\n\n// Output:\n// These are all the boards of this realm:\n//\n// * [/r/demo/boards:test_board_1](/r/demo/boards:test_board_1)\n// * [/r/demo/boards:test_board_2](/r/demo/boards:test_board_2)\n"},{"name":"z_2_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n"},{"name":"z_3_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n}\n\nfunc main() {\n\trid := boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// 3\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n"},{"name":"z_4_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid := boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n\tprintln(rid)\n}\n\nfunc main() {\n\trid2 := boards.CreateReply(bid, pid, pid, \"Second reply of the second post\")\n\tprintln(rid2)\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// 3\n// 4\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n//\n// \u003e Second reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=4)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=4)]\n\n// Realm:\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/boards\"]\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111\",\n// \"ModTime\": \"123\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"68663c8895d37d479e417c11e21badfe21345c61\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:112\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Post\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"3f34ac77289aa1d5f9a2f8b6d083138325816fb0\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {},\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"94a6665a44bac6ede7f3e3b87173e537b12f9532\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"bc8e5b4e782a0bbc4ac9689681f119beb7b34d59\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"9957eadbc91dd32f33b0d815e041a32dbdea0671\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131]={\n// \"Fields\": [\n// {\n// \"N\": \"AAAAgJSeXbo=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"65536\"\n// }\n// },\n// {\n// \"N\": \"AbSNdvQQIhE=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"1024\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Location\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"336074805fc853987abe6f7fe3ad97a6a6f3077a:2\"\n// },\n// \"Index\": \"182\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"65536\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"1024\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Location\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Board\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:84\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"N\": \"BAAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.PostID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"std.Address\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"Second reply of the second post\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"f91e355bd19240f0f3350a7fa0e6a82b72225916\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"9ee9c4117be283fc51ffcc5ecd65b75ecef5a9dd\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"eb768b0140a5fe95f9c58747f0960d647dacfd42\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.PostID\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.PostID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.BoardID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Time\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"0fd3352422af0a56a77ef2c9e88f479054e3d51f\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Time\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"bed4afa8ffdbbf775451c947fc68b27a345ce32a\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132\"\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\",\n// \"IsEscaped\": true,\n// \"ModTime\": \"0\",\n// \"RefCount\": \"2\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Post\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"c45bbd47a46681a63af973db0ec2180922e4a8ae\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\"\n// }\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120\",\n// \"ModTime\": \"134\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"dc1f011553dc53e7a846049e08cc77fa35ea6a51\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:121\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Post\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"96b86b4585c7f1075d7794180a5581f72733a7ab\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {},\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"32274e1f28fb2b97d67a1262afd362d370de7faa\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"c2cfd6aec36a462f35bf02e5bf4a127aa1bb7ac2\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"5cb875179e86d32c517322af7a323b2a5f3e6cc5\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\"\n// }\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:85]={\n// \"Fields\": [\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.BoardID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"/r/demo/boards:test_board\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"test_board\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"std.Address\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"a416a751c3a45a1e5cba11e737c51340b081e372\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:86\"\n// }\n// },\n// {\n// \"N\": \"BAAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"65536\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Time\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"36299fccbc13f2a84c4629fad4cb940f0bd4b1c6\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:87\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"af6ed0268f99b7f369329094eb6dfaea7812708b\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:88\"\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:85\",\n// \"ModTime\": \"121\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:84\",\n// \"RefCount\": \"1\"\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"9809329dc1ddc5d3556f7a8fa3c2cebcbf65560b\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106\",\n// \"ModTime\": \"121\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:105\",\n// \"RefCount\": \"1\"\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"ceae9a1c4ed28bb51062e6ccdccfad0caafd1c4f\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107\",\n// \"ModTime\": \"121\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:105\",\n// \"RefCount\": \"1\"\n// }\n// }\n// switchrealm[\"gno.land/r/demo/boards\"]\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/boards\"]\n// switchrealm[\"gno.land/r/demo/boards_test\"]\n"},{"name":"z_5_b_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// create board via registered user\n\tbid := boards.CreateBoard(\"test_board\")\n\n\t// create post via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOriginCaller(test2)\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\n\tpid := boards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// please register, otherwise minimum fee 100000000 is required if anonymous\n"},{"name":"z_5_c_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// create board via registered user\n\tbid := boards.CreateBoard(\"test_board\")\n\n\t// create post via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOriginCaller(test2)\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 101000000}}, nil)\n\n\tpid := boards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the first post\")\n\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post (title)\n//\n// Body of the first post. (body)\n// \\- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/r/demo/users:g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e Reply of the first post\n// \u003e \\- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/r/demo/users:g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n"},{"name":"z_5_d_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// create board via registered user\n\tbid := boards.CreateBoard(\"test_board\")\n\tpid := boards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\n\t// create reply via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOriginCaller(test2)\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\tboards.CreateReply(bid, pid, pid, \"Reply of the first post\")\n\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// please register, otherwise minimum fee 100000000 is required if anonymous\n"},{"name":"z_5_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid := boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\trid2 := boards.CreateReply(bid, pid, pid, \"Second reply of the second post\\n\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n//\n// \u003e Second reply of the second post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=4)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=4)]\n"},{"name":"z_6_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tboards.CreateReply(bid, pid, pid, \"Second reply of the second post\\n\")\n\tboards.CreateReply(bid, pid, rid, \"First reply of the first reply\\n\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n// \u003e\n// \u003e \u003e First reply of the first reply\n// \u003e \u003e\n// \u003e \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/5) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=5)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=5)]\n//\n// \u003e Second reply of the second post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=4)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=4)]\n"},{"name":"z_7_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc init() {\n\t// register\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\t// create board and post\n\tbid := boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Output:\n// \\[[post](/r/demo/boards$help\u0026func=CreateThread\u0026bid=1)]\n//\n// ----------------------------------------\n// ## [First Post (title)](/r/demo/boards:test_board/1)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/1) \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)] (0 replies) (0 reposts)\n"},{"name":"z_8_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tboards.CreateReply(bid, pid, pid, \"Second reply of the second post\\n\")\n\trid2 := boards.CreateReply(bid, pid, rid, \"First reply of the first reply\\n\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid)) + \"/\" + strconv.Itoa(int(rid2))))\n}\n\n// Output:\n// _[see thread](/r/demo/boards:test_board/2)_\n//\n// Reply of the second post\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n//\n// _[see all 1 replies](/r/demo/boards:test_board/2/3)_\n//\n// \u003e First reply of the first reply\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/5) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=5)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=5)]\n"},{"name":"z_9_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar dstBoard boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tdstBoard = boards.CreateBoard(\"dst_board\")\n\n\tboards.CreateRepost(0, 0, \"First Post in (title)\", \"Body of the first post. (body)\", dstBoard)\n}\n\nfunc main() {\n}\n\n// Error:\n// src board not exist\n"},{"name":"z_9_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tsrcBoard boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tsrcBoard = boards.CreateBoard(\"first_board\")\n\tpid = boards.CreateThread(srcBoard, \"First Post in (title)\", \"Body of the first post. (body)\")\n\n\tboards.CreateRepost(srcBoard, pid, \"First Post in (title)\", \"Body of the first post. (body)\", 0)\n}\n\nfunc main() {\n}\n\n// Error:\n// dst board not exist\n"},{"name":"z_9_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tfirstBoard boards.BoardID\n\tsecondBoard boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tfirstBoard = boards.CreateBoard(\"first_board\")\n\tsecondBoard = boards.CreateBoard(\"second_board\")\n\tpid = boards.CreateThread(firstBoard, \"First Post in (title)\", \"Body of the first post. (body)\")\n\n\tboards.CreateRepost(firstBoard, pid, \"First Post in (title)\", \"Body of the first post. (body)\", secondBoard)\n}\n\nfunc main() {\n\tprintln(boards.Render(\"second_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:second_board/1/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=2\u0026threadid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=2\u0026threadid=1\u0026postid=1)]\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":"groups","path":"gno.land/p/demo/groups","files":[{"name":"groups.gno","body":"package groups\n\nimport \"gno.land/r/demo/boards\"\n\n// TODO implement something and test.\ntype Group struct {\n\tBoard *boards.Board\n}\n"},{"name":"vote_set.gno","body":"package groups\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/demo/rat\"\n)\n\n//----------------------------------------\n// VoteSet\n\ntype VoteSet interface {\n\t// number of present votes in set.\n\tSize() int\n\t// add or update vote for voter.\n\tSetVote(voter std.Address, value string) error\n\t// count the number of votes for value.\n\tCountVotes(value string) int\n}\n\n//----------------------------------------\n// VoteList\n\ntype Vote struct {\n\tVoter std.Address\n\tValue string\n}\n\ntype VoteList []Vote\n\nfunc NewVoteList() *VoteList {\n\treturn \u0026VoteList{}\n}\n\nfunc (vlist *VoteList) Size() int {\n\treturn len(*vlist)\n}\n\nfunc (vlist *VoteList) SetVote(voter std.Address, value string) error {\n\t// TODO optimize with binary algorithm\n\tfor i, vote := range *vlist {\n\t\tif vote.Voter == voter {\n\t\t\t// update vote\n\t\t\t(*vlist)[i] = Vote{\n\t\t\t\tVoter: voter,\n\t\t\t\tValue: value,\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t}\n\t*vlist = append(*vlist, Vote{\n\t\tVoter: voter,\n\t\tValue: value,\n\t})\n\treturn nil\n}\n\nfunc (vlist *VoteList) CountVotes(target string) int {\n\t// TODO optimize with binary algorithm\n\tvar count int\n\tfor _, vote := range *vlist {\n\t\tif vote.Value == target {\n\t\t\tcount++\n\t\t}\n\t}\n\treturn count\n}\n\n//----------------------------------------\n// Committee\n\ntype Committee struct {\n\tQuorum rat.Rat\n\tThreshold rat.Rat\n\tAddresses std.AddressSet\n}\n\n//----------------------------------------\n// VoteSession\n// NOTE: this seems a bit too formal and\n// complicated vs what might be possible;\n// something simpler, more informal.\n\ntype SessionStatus int\n\nconst (\n\tSessionNew SessionStatus = iota\n\tSessionStarted\n\tSessionCompleted\n\tSessionCanceled\n)\n\ntype VoteSession struct {\n\tName string\n\tCreator std.Address\n\tBody string\n\tStart time.Time\n\tDeadline time.Time\n\tStatus SessionStatus\n\tCommittee *Committee\n\tVotes VoteSet\n\tChoices []string\n\tResult string\n}\n"},{"name":"z_1_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\nimport (\n\t\"gno.land/p/demo/groups\"\n\t\"gno.land/p/demo/testutils\"\n)\n\nvar vset groups.VoteSet\n\nfunc init() {\n\taddr1 := testutils.TestAddress(\"test1\")\n\taddr2 := testutils.TestAddress(\"test2\")\n\tvset = groups.NewVoteList()\n\tvset.SetVote(addr1, \"yes\")\n\tvset.SetVote(addr2, \"yes\")\n}\n\nfunc main() {\n\tprintln(vset.Size())\n\tprintln(\"yes:\", vset.CountVotes(\"yes\"))\n\tprintln(\"no:\", vset.CountVotes(\"no\"))\n}\n\n// Output:\n// 2\n// yes: 2\n// no: 0\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":"uint256","path":"gno.land/p/demo/uint256","files":[{"name":"LICENSE","body":"BSD 3-Clause License\n\nCopyright 2020 uint256 Authors\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this\n list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright notice,\n this list of conditions and the following disclaimer in the documentation\n and/or other materials provided with the distribution.\n\n3. Neither the name of the copyright holder nor the names of its\n contributors may be used to endorse or promote products derived from\n this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"},{"name":"README.md","body":"# Fixed size 256-bit math library\n\nThis is a library specialized at replacing the `big.Int` library for math based on 256-bit types.\n\noriginal repository: [uint256](\u003chttps://github.com/holiman/uint256/tree/master\u003e)\n"},{"name":"arithmetic.gno","body":"// arithmetic provides arithmetic operations for Uint objects.\n// This includes basic binary operations such as addition, subtraction, multiplication, division, and modulo operations\n// as well as overflow checks, and negation. These functions are essential for numeric\n// calculations using 256-bit unsigned integers.\npackage uint256\n\nimport (\n\t\"math/bits\"\n)\n\n// Add sets z to the sum x+y\nfunc (z *Uint) Add(x, y *Uint) *Uint {\n\tvar carry uint64\n\tz.arr[0], carry = bits.Add64(x.arr[0], y.arr[0], 0)\n\tz.arr[1], carry = bits.Add64(x.arr[1], y.arr[1], carry)\n\tz.arr[2], carry = bits.Add64(x.arr[2], y.arr[2], carry)\n\tz.arr[3], _ = bits.Add64(x.arr[3], y.arr[3], carry)\n\treturn z\n}\n\n// AddOverflow sets z to the sum x+y, and returns z and whether overflow occurred\nfunc (z *Uint) AddOverflow(x, y *Uint) (*Uint, bool) {\n\tvar carry uint64\n\tz.arr[0], carry = bits.Add64(x.arr[0], y.arr[0], 0)\n\tz.arr[1], carry = bits.Add64(x.arr[1], y.arr[1], carry)\n\tz.arr[2], carry = bits.Add64(x.arr[2], y.arr[2], carry)\n\tz.arr[3], carry = bits.Add64(x.arr[3], y.arr[3], carry)\n\treturn z, carry != 0\n}\n\n// Sub sets z to the difference x-y\nfunc (z *Uint) Sub(x, y *Uint) *Uint {\n\tvar carry uint64\n\tz.arr[0], carry = bits.Sub64(x.arr[0], y.arr[0], 0)\n\tz.arr[1], carry = bits.Sub64(x.arr[1], y.arr[1], carry)\n\tz.arr[2], carry = bits.Sub64(x.arr[2], y.arr[2], carry)\n\tz.arr[3], _ = bits.Sub64(x.arr[3], y.arr[3], carry)\n\treturn z\n}\n\n// SubOverflow sets z to the difference x-y and returns z and true if the operation underflowed\nfunc (z *Uint) SubOverflow(x, y *Uint) (*Uint, bool) {\n\tvar carry uint64\n\tz.arr[0], carry = bits.Sub64(x.arr[0], y.arr[0], 0)\n\tz.arr[1], carry = bits.Sub64(x.arr[1], y.arr[1], carry)\n\tz.arr[2], carry = bits.Sub64(x.arr[2], y.arr[2], carry)\n\tz.arr[3], carry = bits.Sub64(x.arr[3], y.arr[3], carry)\n\treturn z, carry != 0\n}\n\n// Neg returns -x mod 2^256.\nfunc (z *Uint) Neg(x *Uint) *Uint {\n\treturn z.Sub(new(Uint), x)\n}\n\n// commented out for possible overflow\n// Mul sets z to the product x*y\nfunc (z *Uint) Mul(x, y *Uint) *Uint {\n\tvar (\n\t\tres Uint\n\t\tcarry uint64\n\t\tres1, res2, res3 uint64\n\t)\n\n\tcarry, res.arr[0] = bits.Mul64(x.arr[0], y.arr[0])\n\tcarry, res1 = umulHop(carry, x.arr[1], y.arr[0])\n\tcarry, res2 = umulHop(carry, x.arr[2], y.arr[0])\n\tres3 = x.arr[3]*y.arr[0] + carry\n\n\tcarry, res.arr[1] = umulHop(res1, x.arr[0], y.arr[1])\n\tcarry, res2 = umulStep(res2, x.arr[1], y.arr[1], carry)\n\tres3 = res3 + x.arr[2]*y.arr[1] + carry\n\n\tcarry, res.arr[2] = umulHop(res2, x.arr[0], y.arr[2])\n\tres3 = res3 + x.arr[1]*y.arr[2] + carry\n\n\tres.arr[3] = res3 + x.arr[0]*y.arr[3]\n\n\treturn z.Set(\u0026res)\n}\n\n// MulOverflow sets z to the product x*y, and returns z and whether overflow occurred\nfunc (z *Uint) MulOverflow(x, y *Uint) (*Uint, bool) {\n\tp := umul(x, y)\n\tcopy(z.arr[:], p[:4])\n\treturn z, (p[4] | p[5] | p[6] | p[7]) != 0\n}\n\n// commented out for possible overflow\n// Div sets z to the quotient x/y for returns z.\n// If y == 0, z is set to 0\nfunc (z *Uint) Div(x, y *Uint) *Uint {\n\tif y.IsZero() || y.Gt(x) {\n\t\treturn z.Clear()\n\t}\n\tif x.Eq(y) {\n\t\treturn z.SetOne()\n\t}\n\t// Shortcut some cases\n\tif x.IsUint64() {\n\t\treturn z.SetUint64(x.Uint64() / y.Uint64())\n\t}\n\n\t// At this point, we know\n\t// x/y ; x \u003e y \u003e 0\n\n\tvar quot Uint\n\tudivrem(quot.arr[:], x.arr[:], y)\n\treturn z.Set(\u0026quot)\n}\n\n// MulMod calculates the modulo-m multiplication of x and y and\n// returns z.\n// If m == 0, z is set to 0 (OBS: differs from the big.Int)\nfunc (z *Uint) MulMod(x, y, m *Uint) *Uint {\n\tif x.IsZero() || y.IsZero() || m.IsZero() {\n\t\treturn z.Clear()\n\t}\n\tp := umul(x, y)\n\n\tif m.arr[3] != 0 {\n\t\tmu := Reciprocal(m)\n\t\tr := reduce4(p, m, mu)\n\t\treturn z.Set(\u0026r)\n\t}\n\n\tvar (\n\t\tpl Uint\n\t\tph Uint\n\t)\n\n\tpl = Uint{arr: [4]uint64{p[0], p[1], p[2], p[3]}}\n\tph = Uint{arr: [4]uint64{p[4], p[5], p[6], p[7]}}\n\n\t// If the multiplication is within 256 bits use Mod().\n\tif ph.IsZero() {\n\t\treturn z.Mod(\u0026pl, m)\n\t}\n\n\tvar quot [8]uint64\n\trem := udivrem(quot[:], p[:], m)\n\treturn z.Set(\u0026rem)\n}\n\n// Mod sets z to the modulus x%y for y != 0 and returns z.\n// If y == 0, z is set to 0 (OBS: differs from the big.Uint)\nfunc (z *Uint) Mod(x, y *Uint) *Uint {\n\tif x.IsZero() || y.IsZero() {\n\t\treturn z.Clear()\n\t}\n\tswitch x.Cmp(y) {\n\tcase -1:\n\t\t// x \u003c y\n\t\tcopy(z.arr[:], x.arr[:])\n\t\treturn z\n\tcase 0:\n\t\t// x == y\n\t\treturn z.Clear() // They are equal\n\t}\n\n\t// At this point:\n\t// x != 0\n\t// y != 0\n\t// x \u003e y\n\n\t// Shortcut trivial case\n\tif x.IsUint64() {\n\t\treturn z.SetUint64(x.Uint64() % y.Uint64())\n\t}\n\n\tvar quot Uint\n\t*z = udivrem(quot.arr[:], x.arr[:], y)\n\treturn z\n}\n\n// DivMod sets z to the quotient x div y and m to the modulus x mod y and returns the pair (z, m) for y != 0.\n// If y == 0, both z and m are set to 0 (OBS: differs from the big.Int)\nfunc (z *Uint) DivMod(x, y, m *Uint) (*Uint, *Uint) {\n\tif y.IsZero() {\n\t\treturn z.Clear(), m.Clear()\n\t}\n\tvar quot Uint\n\t*m = udivrem(quot.arr[:], x.arr[:], y)\n\t*z = quot\n\treturn z, m\n}\n\n// Exp sets z = base**exponent mod 2**256, and returns z.\nfunc (z *Uint) Exp(base, exponent *Uint) *Uint {\n\tres := Uint{arr: [4]uint64{1, 0, 0, 0}}\n\tmultiplier := *base\n\texpBitLen := exponent.BitLen()\n\n\tcurBit := 0\n\tword := exponent.arr[0]\n\tfor ; curBit \u003c expBitLen \u0026\u0026 curBit \u003c 64; curBit++ {\n\t\tif word\u00261 == 1 {\n\t\t\tres.Mul(\u0026res, \u0026multiplier)\n\t\t}\n\t\tmultiplier.squared()\n\t\tword \u003e\u003e= 1\n\t}\n\n\tword = exponent.arr[1]\n\tfor ; curBit \u003c expBitLen \u0026\u0026 curBit \u003c 128; curBit++ {\n\t\tif word\u00261 == 1 {\n\t\t\tres.Mul(\u0026res, \u0026multiplier)\n\t\t}\n\t\tmultiplier.squared()\n\t\tword \u003e\u003e= 1\n\t}\n\n\tword = exponent.arr[2]\n\tfor ; curBit \u003c expBitLen \u0026\u0026 curBit \u003c 192; curBit++ {\n\t\tif word\u00261 == 1 {\n\t\t\tres.Mul(\u0026res, \u0026multiplier)\n\t\t}\n\t\tmultiplier.squared()\n\t\tword \u003e\u003e= 1\n\t}\n\n\tword = exponent.arr[3]\n\tfor ; curBit \u003c expBitLen \u0026\u0026 curBit \u003c 256; curBit++ {\n\t\tif word\u00261 == 1 {\n\t\t\tres.Mul(\u0026res, \u0026multiplier)\n\t\t}\n\t\tmultiplier.squared()\n\t\tword \u003e\u003e= 1\n\t}\n\treturn z.Set(\u0026res)\n}\n\nfunc (z *Uint) squared() {\n\tvar (\n\t\tres Uint\n\t\tcarry0, carry1, carry2 uint64\n\t\tres1, res2 uint64\n\t)\n\n\tcarry0, res.arr[0] = bits.Mul64(z.arr[0], z.arr[0])\n\tcarry0, res1 = umulHop(carry0, z.arr[0], z.arr[1])\n\tcarry0, res2 = umulHop(carry0, z.arr[0], z.arr[2])\n\n\tcarry1, res.arr[1] = umulHop(res1, z.arr[0], z.arr[1])\n\tcarry1, res2 = umulStep(res2, z.arr[1], z.arr[1], carry1)\n\n\tcarry2, res.arr[2] = umulHop(res2, z.arr[0], z.arr[2])\n\n\tres.arr[3] = 2*(z.arr[0]*z.arr[3]+z.arr[1]*z.arr[2]) + carry0 + carry1 + carry2\n\n\tz.Set(\u0026res)\n}\n\n// udivrem divides u by d and produces both quotient and remainder.\n// The quotient is stored in provided quot - len(u)-len(d)+1 words.\n// It loosely follows the Knuth's division algorithm (sometimes referenced as \"schoolbook\" division) using 64-bit words.\n// See Knuth, Volume 2, section 4.3.1, Algorithm D.\nfunc udivrem(quot, u []uint64, d *Uint) (rem Uint) {\n\tvar dLen int\n\tfor i := len(d.arr) - 1; i \u003e= 0; i-- {\n\t\tif d.arr[i] != 0 {\n\t\t\tdLen = i + 1\n\t\t\tbreak\n\t\t}\n\t}\n\n\tshift := uint(bits.LeadingZeros64(d.arr[dLen-1]))\n\n\tvar dnStorage Uint\n\tdn := dnStorage.arr[:dLen]\n\tfor i := dLen - 1; i \u003e 0; i-- {\n\t\tdn[i] = (d.arr[i] \u003c\u003c shift) | (d.arr[i-1] \u003e\u003e (64 - shift))\n\t}\n\tdn[0] = d.arr[0] \u003c\u003c shift\n\n\tvar uLen int\n\tfor i := len(u) - 1; i \u003e= 0; i-- {\n\t\tif u[i] != 0 {\n\t\t\tuLen = i + 1\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif uLen \u003c dLen {\n\t\tcopy(rem.arr[:], u)\n\t\treturn rem\n\t}\n\n\tvar unStorage [9]uint64\n\tun := unStorage[:uLen+1]\n\tun[uLen] = u[uLen-1] \u003e\u003e (64 - shift)\n\tfor i := uLen - 1; i \u003e 0; i-- {\n\t\tun[i] = (u[i] \u003c\u003c shift) | (u[i-1] \u003e\u003e (64 - shift))\n\t}\n\tun[0] = u[0] \u003c\u003c shift\n\n\t// TODO: Skip the highest word of numerator if not significant.\n\n\tif dLen == 1 {\n\t\tr := udivremBy1(quot, un, dn[0])\n\t\trem.SetUint64(r \u003e\u003e shift)\n\t\treturn rem\n\t}\n\n\tudivremKnuth(quot, un, dn)\n\n\tfor i := 0; i \u003c dLen-1; i++ {\n\t\trem.arr[i] = (un[i] \u003e\u003e shift) | (un[i+1] \u003c\u003c (64 - shift))\n\t}\n\trem.arr[dLen-1] = un[dLen-1] \u003e\u003e shift\n\n\treturn rem\n}\n\n// umul computes full 256 x 256 -\u003e 512 multiplication.\nfunc umul(x, y *Uint) [8]uint64 {\n\tvar (\n\t\tres [8]uint64\n\t\tcarry, carry4, carry5, carry6 uint64\n\t\tres1, res2, res3, res4, res5 uint64\n\t)\n\n\tcarry, res[0] = bits.Mul64(x.arr[0], y.arr[0])\n\tcarry, res1 = umulHop(carry, x.arr[1], y.arr[0])\n\tcarry, res2 = umulHop(carry, x.arr[2], y.arr[0])\n\tcarry4, res3 = umulHop(carry, x.arr[3], y.arr[0])\n\n\tcarry, res[1] = umulHop(res1, x.arr[0], y.arr[1])\n\tcarry, res2 = umulStep(res2, x.arr[1], y.arr[1], carry)\n\tcarry, res3 = umulStep(res3, x.arr[2], y.arr[1], carry)\n\tcarry5, res4 = umulStep(carry4, x.arr[3], y.arr[1], carry)\n\n\tcarry, res[2] = umulHop(res2, x.arr[0], y.arr[2])\n\tcarry, res3 = umulStep(res3, x.arr[1], y.arr[2], carry)\n\tcarry, res4 = umulStep(res4, x.arr[2], y.arr[2], carry)\n\tcarry6, res5 = umulStep(carry5, x.arr[3], y.arr[2], carry)\n\n\tcarry, res[3] = umulHop(res3, x.arr[0], y.arr[3])\n\tcarry, res[4] = umulStep(res4, x.arr[1], y.arr[3], carry)\n\tcarry, res[5] = umulStep(res5, x.arr[2], y.arr[3], carry)\n\tres[7], res[6] = umulStep(carry6, x.arr[3], y.arr[3], carry)\n\n\treturn res\n}\n\n// umulStep computes (hi * 2^64 + lo) = z + (x * y) + carry.\nfunc umulStep(z, x, y, carry uint64) (hi, lo uint64) {\n\thi, lo = bits.Mul64(x, y)\n\tlo, carry = bits.Add64(lo, carry, 0)\n\thi, _ = bits.Add64(hi, 0, carry)\n\tlo, carry = bits.Add64(lo, z, 0)\n\thi, _ = bits.Add64(hi, 0, carry)\n\treturn hi, lo\n}\n\n// umulHop computes (hi * 2^64 + lo) = z + (x * y)\nfunc umulHop(z, x, y uint64) (hi, lo uint64) {\n\thi, lo = bits.Mul64(x, y)\n\tlo, carry := bits.Add64(lo, z, 0)\n\thi, _ = bits.Add64(hi, 0, carry)\n\treturn hi, lo\n}\n\n// udivremBy1 divides u by single normalized word d and produces both quotient and remainder.\n// The quotient is stored in provided quot.\nfunc udivremBy1(quot, u []uint64, d uint64) (rem uint64) {\n\treciprocal := reciprocal2by1(d)\n\trem = u[len(u)-1] // Set the top word as remainder.\n\tfor j := len(u) - 2; j \u003e= 0; j-- {\n\t\tquot[j], rem = udivrem2by1(rem, u[j], d, reciprocal)\n\t}\n\treturn rem\n}\n\n// udivremKnuth implements the division of u by normalized multiple word d from the Knuth's division algorithm.\n// The quotient is stored in provided quot - len(u)-len(d) words.\n// Updates u to contain the remainder - len(d) words.\nfunc udivremKnuth(quot, u, d []uint64) {\n\tdh := d[len(d)-1]\n\tdl := d[len(d)-2]\n\treciprocal := reciprocal2by1(dh)\n\n\tfor j := len(u) - len(d) - 1; j \u003e= 0; j-- {\n\t\tu2 := u[j+len(d)]\n\t\tu1 := u[j+len(d)-1]\n\t\tu0 := u[j+len(d)-2]\n\n\t\tvar qhat, rhat uint64\n\t\tif u2 \u003e= dh { // Division overflows.\n\t\t\tqhat = ^uint64(0)\n\t\t\t// TODO: Add \"qhat one to big\" adjustment (not needed for correctness, but helps avoiding \"add back\" case).\n\t\t} else {\n\t\t\tqhat, rhat = udivrem2by1(u2, u1, dh, reciprocal)\n\t\t\tph, pl := bits.Mul64(qhat, dl)\n\t\t\tif ph \u003e rhat || (ph == rhat \u0026\u0026 pl \u003e u0) {\n\t\t\t\tqhat--\n\t\t\t\t// TODO: Add \"qhat one to big\" adjustment (not needed for correctness, but helps avoiding \"add back\" case).\n\t\t\t}\n\t\t}\n\n\t\t// Multiply and subtract.\n\t\tborrow := subMulTo(u[j:], d, qhat)\n\t\tu[j+len(d)] = u2 - borrow\n\t\tif u2 \u003c borrow { // Too much subtracted, add back.\n\t\t\tqhat--\n\t\t\tu[j+len(d)] += addTo(u[j:], d)\n\t\t}\n\n\t\tquot[j] = qhat // Store quotient digit.\n\t}\n}\n\n// isBitSet returns true if bit n-th is set, where n = 0 is LSB.\n// The n must be \u003c= 255.\nfunc (z *Uint) isBitSet(n uint) bool {\n\treturn (z.arr[n/64] \u0026 (1 \u003c\u003c (n % 64))) != 0\n}\n\n// addTo computes x += y.\n// Requires len(x) \u003e= len(y).\nfunc addTo(x, y []uint64) uint64 {\n\tvar carry uint64\n\tfor i := 0; i \u003c len(y); i++ {\n\t\tx[i], carry = bits.Add64(x[i], y[i], carry)\n\t}\n\treturn carry\n}\n\n// subMulTo computes x -= y * multiplier.\n// Requires len(x) \u003e= len(y).\nfunc subMulTo(x, y []uint64, multiplier uint64) uint64 {\n\tvar borrow uint64\n\tfor i := 0; i \u003c len(y); i++ {\n\t\ts, carry1 := bits.Sub64(x[i], borrow, 0)\n\t\tph, pl := bits.Mul64(y[i], multiplier)\n\t\tt, carry2 := bits.Sub64(s, pl, 0)\n\t\tx[i] = t\n\t\tborrow = ph + carry1 + carry2\n\t}\n\treturn borrow\n}\n\n// reciprocal2by1 computes \u003c^d, ^0\u003e / d.\nfunc reciprocal2by1(d uint64) uint64 {\n\treciprocal, _ := bits.Div64(^d, ^uint64(0), d)\n\treturn reciprocal\n}\n\n// udivrem2by1 divides \u003cuh, ul\u003e / d and produces both quotient and remainder.\n// It uses the provided d's reciprocal.\n// Implementation ported from https://github.com/chfast/intx and is based on\n// \"Improved division by invariant integers\", Algorithm 4.\nfunc udivrem2by1(uh, ul, d, reciprocal uint64) (quot, rem uint64) {\n\tqh, ql := bits.Mul64(reciprocal, uh)\n\tql, carry := bits.Add64(ql, ul, 0)\n\tqh, _ = bits.Add64(qh, uh, carry)\n\tqh++\n\n\tr := ul - qh*d\n\n\tif r \u003e ql {\n\t\tqh--\n\t\tr += d\n\t}\n\n\tif r \u003e= d {\n\t\tqh++\n\t\tr -= d\n\t}\n\n\treturn qh, r\n}\n"},{"name":"arithmetic_test.gno","body":"package uint256\n\nimport (\n\t\"testing\"\n)\n\ntype binOp2Test struct {\n\tx, y, want string\n}\n\nfunc TestAdd(t *testing.T) {\n\ttests := []binOp2Test{\n\t\t{\"0\", \"1\", \"1\"},\n\t\t{\"1\", \"0\", \"1\"},\n\t\t{\"1\", \"1\", \"2\"},\n\t\t{\"1\", \"3\", \"4\"},\n\t\t{\"10\", \"10\", \"20\"},\n\t\t{\"18446744073709551615\", \"18446744073709551615\", \"36893488147419103230\"}, // uint64 overflow\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\ty := MustFromDecimal(tt.y)\n\n\t\twant := MustFromDecimal(tt.want)\n\t\tgot := new(Uint).Add(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Add(%s, %s) = %v, want %v\", tt.x, tt.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestAddOverflow(t *testing.T) {\n\ttests := []struct {\n\t\tx, y string\n\t\twant string\n\t\toverflow bool\n\t}{\n\t\t{\"0\", \"1\", \"1\", false},\n\t\t{\"1\", \"0\", \"1\", false},\n\t\t{\"1\", \"1\", \"2\", false},\n\t\t{\"10\", \"10\", \"20\", false},\n\t\t{\"18446744073709551615\", \"18446744073709551615\", \"36893488147419103230\", false}, // uint64 overflow, but not Uint256 overflow\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"1\", \"0\", true}, // 2^256 - 1 + 1, should overflow\n\t\t{\"57896044618658097711785492504343953926634992332820282019728792003956564819967\", \"57896044618658097711785492504343953926634992332820282019728792003956564819968\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\", false}, // (2^255 - 1) + 2^255, no overflow\n\t\t{\"57896044618658097711785492504343953926634992332820282019728792003956564819967\", \"57896044618658097711785492504343953926634992332820282019728792003956564819969\", \"0\", true}, // (2^255 - 1) + (2^255 + 1), should overflow\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\ty := MustFromDecimal(tt.y)\n\t\twant, _ := FromDecimal(tt.want)\n\n\t\tgot, overflow := new(Uint).AddOverflow(x, y)\n\n\t\tif got.Cmp(want) != 0 || overflow != tt.overflow {\n\t\t\tt.Errorf(\"AddOverflow(%s, %s) = (%s, %v), want (%s, %v)\",\n\t\t\t\ttt.x, tt.y, got.ToString(), overflow, tt.want, tt.overflow)\n\t\t}\n\t}\n}\n\nfunc TestSub(t *testing.T) {\n\ttests := []binOp2Test{\n\t\t{\"1\", \"0\", \"1\"},\n\t\t{\"1\", \"1\", \"0\"},\n\t\t{\"10\", \"10\", \"0\"},\n\t\t{\"31337\", \"1337\", \"30000\"},\n\t\t{\"2\", \"3\", twoPow256Sub1}, // underflow\n\t}\n\n\tfor _, tc := range tests {\n\t\tx := MustFromDecimal(tc.x)\n\t\ty := MustFromDecimal(tc.y)\n\n\t\twant := MustFromDecimal(tc.want)\n\n\t\tgot := new(Uint).Sub(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\n\t\t\t\t\"Sub(%s, %s) = %v, want %v\",\n\t\t\t\ttc.x, tc.y, got.ToString(), want.ToString(),\n\t\t\t)\n\t\t}\n\t}\n}\n\nfunc TestSubOverflow(t *testing.T) {\n\ttests := []struct {\n\t\tx, y string\n\t\twant string\n\t\toverflow bool\n\t}{\n\t\t{\"1\", \"0\", \"1\", false},\n\t\t{\"1\", \"1\", \"0\", false},\n\t\t{\"10\", \"10\", \"0\", false},\n\t\t{\"31337\", \"1337\", \"30000\", false},\n\t\t{\"0\", \"1\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\", true}, // 0 - 1, should underflow\n\t\t{\"57896044618658097711785492504343953926634992332820282019728792003956564819968\", \"1\", \"57896044618658097711785492504343953926634992332820282019728792003956564819967\", false}, // 2^255 - 1, no underflow\n\t\t{\"57896044618658097711785492504343953926634992332820282019728792003956564819968\", \"57896044618658097711785492504343953926634992332820282019728792003956564819969\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\", true}, // 2^255 - (2^255 + 1), should underflow\n\t}\n\n\tfor _, tc := range tests {\n\t\tx := MustFromDecimal(tc.x)\n\t\ty := MustFromDecimal(tc.y)\n\t\twant := MustFromDecimal(tc.want)\n\n\t\tgot, overflow := new(Uint).SubOverflow(x, y)\n\n\t\tif got.Cmp(want) != 0 || overflow != tc.overflow {\n\t\t\tt.Errorf(\n\t\t\t\t\"SubOverflow(%s, %s) = (%s, %v), want (%s, %v)\",\n\t\t\t\ttc.x, tc.y, got.ToString(), overflow, tc.want, tc.overflow,\n\t\t\t)\n\t\t}\n\t}\n}\n\nfunc TestMul(t *testing.T) {\n\ttests := []binOp2Test{\n\t\t{\"1\", \"0\", \"0\"},\n\t\t{\"1\", \"1\", \"1\"},\n\t\t{\"10\", \"10\", \"100\"},\n\t\t{\"18446744073709551615\", \"2\", \"36893488147419103230\"}, // uint64 overflow\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\ty := MustFromDecimal(tt.y)\n\t\twant := MustFromDecimal(tt.want)\n\t\tgot := new(Uint).Mul(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Mul(%s, %s) = %v, want %v\", tt.x, tt.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestMulOverflow(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty string\n\t\twantZ string\n\t\twantOver bool\n\t}{\n\t\t{\"0x1\", \"0x1\", \"0x1\", false},\n\t\t{\"0x0\", \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0x0\", false},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0x2\", \"0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe\", true},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0x1\", true},\n\t\t{\"0x8000000000000000000000000000000000000000000000000000000000000000\", \"0x2\", \"0x0\", true},\n\t\t{\"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0x2\", \"0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe\", false},\n\t\t{\"0x100000000000000000\", \"0x100000000000000000\", \"0x10000000000000000000000000000000000\", false},\n\t\t{\"0x10000000000000000000000000000000\", \"0x10000000000000000000000000000000\", \"0x100000000000000000000000000000000000000000000000000000000000000\", false},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromHex(tt.x)\n\t\ty := MustFromHex(tt.y)\n\t\twantZ := MustFromHex(tt.wantZ)\n\n\t\tgotZ, gotOver := new(Uint).MulOverflow(x, y)\n\n\t\tif gotZ.Neq(wantZ) {\n\t\t\tt.Errorf(\n\t\t\t\t\"MulOverflow(%s, %s) = %s, want %s\",\n\t\t\t\ttt.x, tt.y, gotZ.ToString(), wantZ.ToString(),\n\t\t\t)\n\t\t}\n\t\tif gotOver != tt.wantOver {\n\t\t\tt.Errorf(\"MulOverflow(%s, %s) = %v, want %v\", tt.x, tt.y, gotOver, tt.wantOver)\n\t\t}\n\t}\n}\n\nfunc TestDiv(t *testing.T) {\n\ttests := []binOp2Test{\n\t\t{\"31337\", \"3\", \"10445\"},\n\t\t{\"31337\", \"0\", \"0\"},\n\t\t{\"0\", \"31337\", \"0\"},\n\t\t{\"1\", \"1\", \"1\"},\n\t\t{\"1000000000000000000\", \"3\", \"333333333333333333\"},\n\t\t{twoPow256Sub1, \"2\", \"57896044618658097711785492504343953926634992332820282019728792003956564819967\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\ty := MustFromDecimal(tt.y)\n\t\twant := MustFromDecimal(tt.want)\n\n\t\tgot := new(Uint).Div(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Div(%s, %s) = %v, want %v\", tt.x, tt.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestMod(t *testing.T) {\n\ttests := []binOp2Test{\n\t\t{\"31337\", \"3\", \"2\"},\n\t\t{\"31337\", \"0\", \"0\"},\n\t\t{\"0\", \"31337\", \"0\"},\n\t\t{\"2\", \"31337\", \"2\"},\n\t\t{\"1\", \"1\", \"0\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"2\", \"1\"}, // 2^256 - 1 mod 2\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"3\", \"0\"}, // 2^256 - 1 mod 3\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"57896044618658097711785492504343953926634992332820282019728792003956564819968\", \"57896044618658097711785492504343953926634992332820282019728792003956564819967\"}, // 2^256 - 1 mod 2^255\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\ty := MustFromDecimal(tt.y)\n\t\twant := MustFromDecimal(tt.want)\n\n\t\tgot := new(Uint).Mod(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Mod(%s, %s) = %v, want %v\", tt.x, tt.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestMulMod(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty string\n\t\tm string\n\t\twant string\n\t}{\n\t\t{\"0x1\", \"0x1\", \"0x2\", \"0x1\"},\n\t\t{\"0x10\", \"0x10\", \"0x7\", \"0x4\"},\n\t\t{\"0x100\", \"0x100\", \"0x17\", \"0x9\"},\n\t\t{\"0x31337\", \"0x31337\", \"0x31338\", \"0x1\"},\n\t\t{\"0x0\", \"0x31337\", \"0x31338\", \"0x0\"},\n\t\t{\"0x31337\", \"0x0\", \"0x31338\", \"0x0\"},\n\t\t{\"0x2\", \"0x3\", \"0x5\", \"0x1\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0x0\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe\", \"0x1\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0xffffffffffffffffffffffffffffffff\", \"0x0\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromHex(tt.x)\n\t\ty := MustFromHex(tt.y)\n\t\tm := MustFromHex(tt.m)\n\t\twant := MustFromHex(tt.want)\n\n\t\tgot := new(Uint).MulMod(x, y, m)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\n\t\t\t\t\"MulMod(%s, %s, %s) = %s, want %s\",\n\t\t\t\ttt.x, tt.y, tt.m, got.ToString(), want.ToString(),\n\t\t\t)\n\t\t}\n\t}\n}\n\nfunc TestDivMod(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty string\n\t\twantDiv string\n\t\twantMod string\n\t}{\n\t\t{\"1\", \"1\", \"1\", \"0\"},\n\t\t{\"10\", \"10\", \"1\", \"0\"},\n\t\t{\"100\", \"10\", \"10\", \"0\"},\n\t\t{\"31337\", \"3\", \"10445\", \"2\"},\n\t\t{\"31337\", \"0\", \"0\", \"0\"},\n\t\t{\"0\", \"31337\", \"0\", \"0\"},\n\t\t{\"2\", \"31337\", \"0\", \"2\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\ty := MustFromDecimal(tt.y)\n\t\twantDiv := MustFromDecimal(tt.wantDiv)\n\t\twantMod := MustFromDecimal(tt.wantMod)\n\n\t\tgotDiv := new(Uint)\n\t\tgotMod := new(Uint)\n\t\tgotDiv.DivMod(x, y, gotMod)\n\n\t\tfor i := range gotDiv.arr {\n\t\t\tif gotDiv.arr[i] != wantDiv.arr[i] {\n\t\t\t\tt.Errorf(\"DivMod(%s, %s) got Div %v, want Div %v\", tt.x, tt.y, gotDiv, wantDiv)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfor i := range gotMod.arr {\n\t\t\tif gotMod.arr[i] != wantMod.arr[i] {\n\t\t\t\tt.Errorf(\"DivMod(%s, %s) got Mod %v, want Mod %v\", tt.x, tt.y, gotMod, wantMod)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestNeg(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant string\n\t}{\n\t\t{\"31337\", \"115792089237316195423570985008687907853269984665640564039457584007913129608599\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129608599\", \"31337\"},\n\t\t{\"0\", \"0\"},\n\t\t{\"2\", \"115792089237316195423570985008687907853269984665640564039457584007913129639934\"},\n\t\t{\"1\", twoPow256Sub1},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\twant := MustFromDecimal(tt.want)\n\n\t\tgot := new(Uint).Neg(x)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Neg(%s) = %v, want %v\", tt.x, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestExp(t *testing.T) {\n\ttests := []binOp2Test{\n\t\t{\"31337\", \"3\", \"30773171189753\"},\n\t\t{\"31337\", \"0\", \"1\"},\n\t\t{\"0\", \"31337\", \"0\"},\n\t\t{\"1\", \"1\", \"1\"},\n\t\t{\"2\", \"3\", \"8\"},\n\t\t{\"2\", \"64\", \"18446744073709551616\"},\n\t\t{\"2\", \"128\", \"340282366920938463463374607431768211456\"},\n\t\t{\"2\", \"255\", \"57896044618658097711785492504343953926634992332820282019728792003956564819968\"},\n\t\t{\"2\", \"256\", \"0\"}, // overflow\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\ty := MustFromDecimal(tt.y)\n\t\twant := MustFromDecimal(tt.want)\n\n\t\tgot := new(Uint).Exp(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\n\t\t\t\t\"Exp(%s, %s) = %v, want %v\",\n\t\t\t\ttt.x, tt.y, got.ToString(), want.ToString(),\n\t\t\t)\n\t\t}\n\t}\n}\n\nfunc TestExp_LargeExponent(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tbase string\n\t\texponent string\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname: \"2^129\",\n\t\t\tbase: \"2\",\n\t\t\texponent: \"680564733841876926926749214863536422912\",\n\t\t\texpected: \"0\",\n\t\t},\n\t\t{\n\t\t\tname: \"2^193\",\n\t\t\tbase: \"2\",\n\t\t\texponent: \"12379400392853802746563808384000000000000000000\",\n\t\t\texpected: \"0\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tbase := MustFromDecimal(tt.base)\n\t\t\texponent := MustFromDecimal(tt.exponent)\n\t\t\texpected := MustFromDecimal(tt.expected)\n\n\t\t\tresult := new(Uint).Exp(base, exponent)\n\n\t\t\tif result.Neq(expected) {\n\t\t\t\tt.Errorf(\n\t\t\t\t\t\"Test %s failed. Expected %s, got %s\",\n\t\t\t\t\ttt.name, expected.ToString(), result.ToString(),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n}\n"},{"name":"bits_table.gno","body":"// Copyright 2017 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n// Code generated by go run make_tables.go. DO NOT EDIT.\n\npackage uint256\n\nconst ntz8tab = \"\" +\n\t\"\\x08\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x05\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x06\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x05\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x07\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x05\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x06\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x05\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\"\n\nconst pop8tab = \"\" +\n\t\"\\x00\\x01\\x01\\x02\\x01\\x02\\x02\\x03\\x01\\x02\\x02\\x03\\x02\\x03\\x03\\x04\" +\n\t\"\\x01\\x02\\x02\\x03\\x02\\x03\\x03\\x04\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\" +\n\t\"\\x01\\x02\\x02\\x03\\x02\\x03\\x03\\x04\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\" +\n\t\"\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\" +\n\t\"\\x01\\x02\\x02\\x03\\x02\\x03\\x03\\x04\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\" +\n\t\"\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\" +\n\t\"\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\" +\n\t\"\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\\x04\\x05\\x05\\x06\\x05\\x06\\x06\\x07\" +\n\t\"\\x01\\x02\\x02\\x03\\x02\\x03\\x03\\x04\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\" +\n\t\"\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\" +\n\t\"\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\" +\n\t\"\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\\x04\\x05\\x05\\x06\\x05\\x06\\x06\\x07\" +\n\t\"\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\" +\n\t\"\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\\x04\\x05\\x05\\x06\\x05\\x06\\x06\\x07\" +\n\t\"\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\\x04\\x05\\x05\\x06\\x05\\x06\\x06\\x07\" +\n\t\"\\x04\\x05\\x05\\x06\\x05\\x06\\x06\\x07\\x05\\x06\\x06\\x07\\x06\\x07\\x07\\x08\"\n\nconst rev8tab = \"\" +\n\t\"\\x00\\x80\\x40\\xc0\\x20\\xa0\\x60\\xe0\\x10\\x90\\x50\\xd0\\x30\\xb0\\x70\\xf0\" +\n\t\"\\x08\\x88\\x48\\xc8\\x28\\xa8\\x68\\xe8\\x18\\x98\\x58\\xd8\\x38\\xb8\\x78\\xf8\" +\n\t\"\\x04\\x84\\x44\\xc4\\x24\\xa4\\x64\\xe4\\x14\\x94\\x54\\xd4\\x34\\xb4\\x74\\xf4\" +\n\t\"\\x0c\\x8c\\x4c\\xcc\\x2c\\xac\\x6c\\xec\\x1c\\x9c\\x5c\\xdc\\x3c\\xbc\\x7c\\xfc\" +\n\t\"\\x02\\x82\\x42\\xc2\\x22\\xa2\\x62\\xe2\\x12\\x92\\x52\\xd2\\x32\\xb2\\x72\\xf2\" +\n\t\"\\x0a\\x8a\\x4a\\xca\\x2a\\xaa\\x6a\\xea\\x1a\\x9a\\x5a\\xda\\x3a\\xba\\x7a\\xfa\" +\n\t\"\\x06\\x86\\x46\\xc6\\x26\\xa6\\x66\\xe6\\x16\\x96\\x56\\xd6\\x36\\xb6\\x76\\xf6\" +\n\t\"\\x0e\\x8e\\x4e\\xce\\x2e\\xae\\x6e\\xee\\x1e\\x9e\\x5e\\xde\\x3e\\xbe\\x7e\\xfe\" +\n\t\"\\x01\\x81\\x41\\xc1\\x21\\xa1\\x61\\xe1\\x11\\x91\\x51\\xd1\\x31\\xb1\\x71\\xf1\" +\n\t\"\\x09\\x89\\x49\\xc9\\x29\\xa9\\x69\\xe9\\x19\\x99\\x59\\xd9\\x39\\xb9\\x79\\xf9\" +\n\t\"\\x05\\x85\\x45\\xc5\\x25\\xa5\\x65\\xe5\\x15\\x95\\x55\\xd5\\x35\\xb5\\x75\\xf5\" +\n\t\"\\x0d\\x8d\\x4d\\xcd\\x2d\\xad\\x6d\\xed\\x1d\\x9d\\x5d\\xdd\\x3d\\xbd\\x7d\\xfd\" +\n\t\"\\x03\\x83\\x43\\xc3\\x23\\xa3\\x63\\xe3\\x13\\x93\\x53\\xd3\\x33\\xb3\\x73\\xf3\" +\n\t\"\\x0b\\x8b\\x4b\\xcb\\x2b\\xab\\x6b\\xeb\\x1b\\x9b\\x5b\\xdb\\x3b\\xbb\\x7b\\xfb\" +\n\t\"\\x07\\x87\\x47\\xc7\\x27\\xa7\\x67\\xe7\\x17\\x97\\x57\\xd7\\x37\\xb7\\x77\\xf7\" +\n\t\"\\x0f\\x8f\\x4f\\xcf\\x2f\\xaf\\x6f\\xef\\x1f\\x9f\\x5f\\xdf\\x3f\\xbf\\x7f\\xff\"\n\nconst len8tab = \"\" +\n\t\"\\x00\\x01\\x02\\x02\\x03\\x03\\x03\\x03\\x04\\x04\\x04\\x04\\x04\\x04\\x04\\x04\" +\n\t\"\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\" +\n\t\"\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\" +\n\t\"\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\" +\n\t\"\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\" +\n\t\"\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\" +\n\t\"\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\" +\n\t\"\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\"\n"},{"name":"bitwise.gno","body":"// bitwise contains bitwise operations for Uint instances.\n// This file includes functions to perform bitwise AND, OR, XOR, and NOT operations, as well as bit shifting.\n// These operations are crucial for manipulating individual bits within a 256-bit unsigned integer.\npackage uint256\n\n// Or sets z = x | y and returns z.\nfunc (z *Uint) Or(x, y *Uint) *Uint {\n\tz.arr[0] = x.arr[0] | y.arr[0]\n\tz.arr[1] = x.arr[1] | y.arr[1]\n\tz.arr[2] = x.arr[2] | y.arr[2]\n\tz.arr[3] = x.arr[3] | y.arr[3]\n\treturn z\n}\n\n// And sets z = x \u0026 y and returns z.\nfunc (z *Uint) And(x, y *Uint) *Uint {\n\tz.arr[0] = x.arr[0] \u0026 y.arr[0]\n\tz.arr[1] = x.arr[1] \u0026 y.arr[1]\n\tz.arr[2] = x.arr[2] \u0026 y.arr[2]\n\tz.arr[3] = x.arr[3] \u0026 y.arr[3]\n\treturn z\n}\n\n// Not sets z = ^x and returns z.\nfunc (z *Uint) Not(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = ^x.arr[3], ^x.arr[2], ^x.arr[1], ^x.arr[0]\n\treturn z\n}\n\n// AndNot sets z = x \u0026^ y and returns z.\nfunc (z *Uint) AndNot(x, y *Uint) *Uint {\n\tz.arr[0] = x.arr[0] \u0026^ y.arr[0]\n\tz.arr[1] = x.arr[1] \u0026^ y.arr[1]\n\tz.arr[2] = x.arr[2] \u0026^ y.arr[2]\n\tz.arr[3] = x.arr[3] \u0026^ y.arr[3]\n\treturn z\n}\n\n// Xor sets z = x ^ y and returns z.\nfunc (z *Uint) Xor(x, y *Uint) *Uint {\n\tz.arr[0] = x.arr[0] ^ y.arr[0]\n\tz.arr[1] = x.arr[1] ^ y.arr[1]\n\tz.arr[2] = x.arr[2] ^ y.arr[2]\n\tz.arr[3] = x.arr[3] ^ y.arr[3]\n\treturn z\n}\n\n// Lsh sets z = x \u003c\u003c n and returns z.\nfunc (z *Uint) Lsh(x *Uint, n uint) *Uint {\n\t// n % 64 == 0\n\tif n\u00260x3f == 0 {\n\t\tswitch n {\n\t\tcase 0:\n\t\t\treturn z.Set(x)\n\t\tcase 64:\n\t\t\treturn z.lsh64(x)\n\t\tcase 128:\n\t\t\treturn z.lsh128(x)\n\t\tcase 192:\n\t\t\treturn z.lsh192(x)\n\t\tdefault:\n\t\t\treturn z.Clear()\n\t\t}\n\t}\n\tvar a, b uint64\n\t// Big swaps first\n\tswitch {\n\tcase n \u003e 192:\n\t\tif n \u003e 256 {\n\t\t\treturn z.Clear()\n\t\t}\n\t\tz.lsh192(x)\n\t\tn -= 192\n\t\tgoto sh192\n\tcase n \u003e 128:\n\t\tz.lsh128(x)\n\t\tn -= 128\n\t\tgoto sh128\n\tcase n \u003e 64:\n\t\tz.lsh64(x)\n\t\tn -= 64\n\t\tgoto sh64\n\tdefault:\n\t\tz.Set(x)\n\t}\n\n\t// remaining shifts\n\ta = z.arr[0] \u003e\u003e (64 - n)\n\tz.arr[0] = z.arr[0] \u003c\u003c n\n\nsh64:\n\tb = z.arr[1] \u003e\u003e (64 - n)\n\tz.arr[1] = (z.arr[1] \u003c\u003c n) | a\n\nsh128:\n\ta = z.arr[2] \u003e\u003e (64 - n)\n\tz.arr[2] = (z.arr[2] \u003c\u003c n) | b\n\nsh192:\n\tz.arr[3] = (z.arr[3] \u003c\u003c n) | a\n\n\treturn z\n}\n\n// Rsh sets z = x \u003e\u003e n and returns z.\nfunc (z *Uint) Rsh(x *Uint, n uint) *Uint {\n\t// n % 64 == 0\n\tif n\u00260x3f == 0 {\n\t\tswitch n {\n\t\tcase 0:\n\t\t\treturn z.Set(x)\n\t\tcase 64:\n\t\t\treturn z.rsh64(x)\n\t\tcase 128:\n\t\t\treturn z.rsh128(x)\n\t\tcase 192:\n\t\t\treturn z.rsh192(x)\n\t\tdefault:\n\t\t\treturn z.Clear()\n\t\t}\n\t}\n\tvar a, b uint64\n\t// Big swaps first\n\tswitch {\n\tcase n \u003e 192:\n\t\tif n \u003e 256 {\n\t\t\treturn z.Clear()\n\t\t}\n\t\tz.rsh192(x)\n\t\tn -= 192\n\t\tgoto sh192\n\tcase n \u003e 128:\n\t\tz.rsh128(x)\n\t\tn -= 128\n\t\tgoto sh128\n\tcase n \u003e 64:\n\t\tz.rsh64(x)\n\t\tn -= 64\n\t\tgoto sh64\n\tdefault:\n\t\tz.Set(x)\n\t}\n\n\t// remaining shifts\n\ta = z.arr[3] \u003c\u003c (64 - n)\n\tz.arr[3] = z.arr[3] \u003e\u003e n\n\nsh64:\n\tb = z.arr[2] \u003c\u003c (64 - n)\n\tz.arr[2] = (z.arr[2] \u003e\u003e n) | a\n\nsh128:\n\ta = z.arr[1] \u003c\u003c (64 - n)\n\tz.arr[1] = (z.arr[1] \u003e\u003e n) | b\n\nsh192:\n\tz.arr[0] = (z.arr[0] \u003e\u003e n) | a\n\n\treturn z\n}\n\n// SRsh (Signed/Arithmetic right shift)\n// considers z to be a signed integer, during right-shift\n// and sets z = x \u003e\u003e n and returns z.\nfunc (z *Uint) SRsh(x *Uint, n uint) *Uint {\n\t// If the MSB is 0, SRsh is same as Rsh.\n\tif !x.isBitSet(255) {\n\t\treturn z.Rsh(x, n)\n\t}\n\tif n%64 == 0 {\n\t\tswitch n {\n\t\tcase 0:\n\t\t\treturn z.Set(x)\n\t\tcase 64:\n\t\t\treturn z.srsh64(x)\n\t\tcase 128:\n\t\t\treturn z.srsh128(x)\n\t\tcase 192:\n\t\t\treturn z.srsh192(x)\n\t\tdefault:\n\t\t\treturn z.SetAllOne()\n\t\t}\n\t}\n\tvar a uint64 = MaxUint64 \u003c\u003c (64 - n%64)\n\t// Big swaps first\n\tswitch {\n\tcase n \u003e 192:\n\t\tif n \u003e 256 {\n\t\t\treturn z.SetAllOne()\n\t\t}\n\t\tz.srsh192(x)\n\t\tn -= 192\n\t\tgoto sh192\n\tcase n \u003e 128:\n\t\tz.srsh128(x)\n\t\tn -= 128\n\t\tgoto sh128\n\tcase n \u003e 64:\n\t\tz.srsh64(x)\n\t\tn -= 64\n\t\tgoto sh64\n\tdefault:\n\t\tz.Set(x)\n\t}\n\n\t// remaining shifts\n\tz.arr[3], a = (z.arr[3]\u003e\u003en)|a, z.arr[3]\u003c\u003c(64-n)\n\nsh64:\n\tz.arr[2], a = (z.arr[2]\u003e\u003en)|a, z.arr[2]\u003c\u003c(64-n)\n\nsh128:\n\tz.arr[1], a = (z.arr[1]\u003e\u003en)|a, z.arr[1]\u003c\u003c(64-n)\n\nsh192:\n\tz.arr[0] = (z.arr[0] \u003e\u003e n) | a\n\n\treturn z\n}\n\nfunc (z *Uint) lsh64(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = x.arr[2], x.arr[1], x.arr[0], 0\n\treturn z\n}\n\nfunc (z *Uint) lsh128(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = x.arr[1], x.arr[0], 0, 0\n\treturn z\n}\n\nfunc (z *Uint) lsh192(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = x.arr[0], 0, 0, 0\n\treturn z\n}\n\nfunc (z *Uint) rsh64(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, x.arr[3], x.arr[2], x.arr[1]\n\treturn z\n}\n\nfunc (z *Uint) rsh128(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, 0, x.arr[3], x.arr[2]\n\treturn z\n}\n\nfunc (z *Uint) rsh192(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, 0, 0, x.arr[3]\n\treturn z\n}\n\nfunc (z *Uint) srsh64(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = MaxUint64, x.arr[3], x.arr[2], x.arr[1]\n\treturn z\n}\n\nfunc (z *Uint) srsh128(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = MaxUint64, MaxUint64, x.arr[3], x.arr[2]\n\treturn z\n}\n\nfunc (z *Uint) srsh192(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = MaxUint64, MaxUint64, MaxUint64, x.arr[3]\n\treturn z\n}\n"},{"name":"bitwise_test.gno","body":"package uint256\n\nimport \"testing\"\n\ntype logicOpTest struct {\n\tname string\n\tx Uint\n\ty Uint\n\twant Uint\n}\n\nfunc TestOr(t *testing.T) {\n\ttests := []logicOpTest{\n\t\t{\n\t\t\tname: \"all zeros\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tres := new(Uint).Or(\u0026tt.x, \u0026tt.y)\n\t\t\tif *res != tt.want {\n\t\t\t\tt.Errorf(\n\t\t\t\t\t\"Or(%s, %s) = %s, want %s\",\n\t\t\t\t\ttt.x.ToString(), tt.y.ToString(), res.ToString(), (tt.want).ToString(),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestAnd(t *testing.T) {\n\ttests := []logicOpTest{\n\t\t{\n\t\t\tname: \"all zeros\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 2\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 3\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand zero\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}},\n\t\t\twant: Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tres := new(Uint).And(\u0026tt.x, \u0026tt.y)\n\t\t\tif *res != tt.want {\n\t\t\t\tt.Errorf(\n\t\t\t\t\t\"And(%s, %s) = %s, want %s\",\n\t\t\t\t\ttt.x.ToString(), tt.y.ToString(), res.ToString(), (tt.want).ToString(),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNot(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tx Uint\n\t\twant Uint\n\t}{\n\t\t{\n\t\t\tname: \"all zeros\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tres := new(Uint).Not(\u0026tt.x)\n\t\t\tif *res != tt.want {\n\t\t\t\tt.Errorf(\n\t\t\t\t\t\"Not(%s) = %s, want %s\",\n\t\t\t\t\ttt.x.ToString(), res.ToString(), (tt.want).ToString(),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestAndNot(t *testing.T) {\n\ttests := []logicOpTest{\n\t\t{\n\t\t\tname: \"all zeros\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 2\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 3\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand zero\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}},\n\t\t\twant: Uint{arr: [4]uint64{0xAAAAAAAAAAAAAAAA, 0x5555555555555555, 0x0000000000000000, ^uint64(0)}},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tres := new(Uint).AndNot(\u0026tt.x, \u0026tt.y)\n\t\t\tif *res != tt.want {\n\t\t\t\tt.Errorf(\n\t\t\t\t\t\"AndNot(%s, %s) = %s, want %s\",\n\t\t\t\t\ttt.x.ToString(), tt.y.ToString(), res.ToString(), (tt.want).ToString(),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestXor(t *testing.T) {\n\ttests := []logicOpTest{\n\t\t{\n\t\t\tname: \"all zeros\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 2\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 3\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand zero\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF}},\n\t\t\twant: Uint{arr: [4]uint64{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}},\n\t\t\twant: Uint{arr: [4]uint64{0xAAAAAAAAAAAAAAAA, 0x5555555555555555, 0x0000000000000000, ^uint64(0)}},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tres := new(Uint).Xor(\u0026tt.x, \u0026tt.y)\n\t\t\tif *res != tt.want {\n\t\t\t\tt.Errorf(\n\t\t\t\t\t\"Xor(%s, %s) = %s, want %s\",\n\t\t\t\t\ttt.x.ToString(), tt.y.ToString(), res.ToString(), (tt.want).ToString(),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestLsh(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty uint\n\t\twant string\n\t}{\n\t\t{\"0\", 0, \"0\"},\n\t\t{\"0\", 1, \"0\"},\n\t\t{\"0\", 64, \"0\"},\n\t\t{\"1\", 0, \"1\"},\n\t\t{\"1\", 1, \"2\"},\n\t\t{\"1\", 64, \"18446744073709551616\"},\n\t\t{\"1\", 128, \"340282366920938463463374607431768211456\"},\n\t\t{\"1\", 192, \"6277101735386680763835789423207666416102355444464034512896\"},\n\t\t{\"1\", 255, \"57896044618658097711785492504343953926634992332820282019728792003956564819968\"},\n\t\t{\"1\", 256, \"0\"},\n\t\t{\"31337\", 0, \"31337\"},\n\t\t{\"31337\", 1, \"62674\"},\n\t\t{\"31337\", 64, \"578065619037836218990592\"},\n\t\t{\"31337\", 128, \"10663428532201448629551770073089320442396672\"},\n\t\t{\"31337\", 192, \"196705537081812415096322133155058642481399512563169449530621952\"},\n\t\t{\"31337\", 193, \"393411074163624830192644266310117284962799025126338899061243904\"},\n\t\t{\"31337\", 255, \"57896044618658097711785492504343953926634992332820282019728792003956564819968\"},\n\t\t{\"31337\", 256, \"0\"},\n\t\t// 64 \u003c n \u003c 128\n\t\t{\"1\", 65, \"36893488147419103232\"},\n\t\t{\"31337\", 100, \"39724366859352024754702188346867712\"},\n\n\t\t// 128 \u003c n \u003c 192\n\t\t{\"1\", 129, \"680564733841876926926749214863536422912\"},\n\t\t{\"31337\", 150, \"44725660946326664792723507424638829088826130956288\"},\n\n\t\t// 192 \u003c n \u003c 256\n\t\t{\"1\", 193, \"12554203470773361527671578846415332832204710888928069025792\"},\n\t\t{\"31337\", 200, \"50356617492943978264658466087695012475238275216171379079839219712\"},\n\n\t\t// n \u003e 256\n\t\t{\"1\", 257, \"0\"},\n\t\t{\"31337\", 300, \"0\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\twant := MustFromDecimal(tt.want)\n\n\t\tgot := new(Uint).Lsh(x, tt.y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Lsh(%s, %d) = %s, want %s\", tt.x, tt.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestRsh(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty uint\n\t\twant string\n\t}{\n\t\t{\"0\", 0, \"0\"},\n\t\t{\"0\", 1, \"0\"},\n\t\t{\"0\", 64, \"0\"},\n\t\t{\"1\", 0, \"1\"},\n\t\t{\"1\", 1, \"0\"},\n\t\t{\"1\", 64, \"0\"},\n\t\t{\"1\", 128, \"0\"},\n\t\t{\"1\", 192, \"0\"},\n\t\t{\"1\", 255, \"0\"},\n\t\t{\"57896044618658097711785492504343953926634992332820282019728792003956564819968\", 255, \"1\"},\n\t\t{\"6277101735386680763835789423207666416102355444464034512896\", 192, \"1\"},\n\t\t{\"340282366920938463463374607431768211456\", 128, \"1\"},\n\t\t{\"18446744073709551616\", 64, \"1\"},\n\t\t{\"393411074163624830192644266310117284962799025126338899061243904\", 193, \"31337\"},\n\t\t{\"196705537081812415096322133155058642481399512563169449530621952\", 192, \"31337\"},\n\t\t{\"10663428532201448629551770073089320442396672\", 128, \"31337\"},\n\t\t{\"578065619037836218990592\", 64, \"31337\"},\n\t\t{twoPow256Sub1, 256, \"0\"},\n\t\t// outliers\n\t\t{\"340282366920938463463374607431768211455\", 129, \"0\"},\n\t\t{\"18446744073709551615\", 65, \"0\"},\n\t\t{twoPow256Sub1, 1, \"57896044618658097711785492504343953926634992332820282019728792003956564819967\"},\n\n\t\t// n \u003e 256\n\t\t{\"1\", 257, \"0\"},\n\t\t{\"31337\", 300, \"0\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\n\t\twant := MustFromDecimal(tt.want)\n\t\tgot := new(Uint).Rsh(x, tt.y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Rsh(%s, %d) = %s, want %s\", tt.x, tt.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestSRsh(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty uint\n\t\twant string\n\t}{\n\t\t// Positive numbers (behaves like Rsh)\n\t\t{\"0x0\", 0, \"0x0\"},\n\t\t{\"0x0\", 1, \"0x0\"},\n\t\t{\"0x1\", 0, \"0x1\"},\n\t\t{\"0x1\", 1, \"0x0\"},\n\t\t{\"0x31337\", 0, \"0x31337\"},\n\t\t{\"0x31337\", 4, \"0x3133\"},\n\t\t{\"0x31337\", 8, \"0x313\"},\n\t\t{\"0x31337\", 16, \"0x3\"},\n\t\t{\"0x10000000000000000\", 64, \"0x1\"}, // 2^64 \u003e\u003e 64\n\n\t\t// // Numbers with MSB set (negative numbers in two's complement)\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 0, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 1, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 4, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 64, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 128, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 192, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 255, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\n\t\t// Large positive number close to max value\n\t\t{\"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 1, \"0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 2, \"0x1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 64, \"0x7fffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 128, \"0x7fffffffffffffffffffffffffffffff\"},\n\t\t{\"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 192, \"0x7fffffffffffffff\"},\n\t\t{\"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 255, \"0x0\"},\n\n\t\t// Specific cases\n\t\t{\"0x8000000000000000000000000000000000000000000000000000000000000000\", 1, \"0xc000000000000000000000000000000000000000000000000000000000000000\"},\n\t\t{\"0x8000000000000000000000000000000000000000000000000000000000000001\", 1, \"0xc000000000000000000000000000000000000000000000000000000000000000\"},\n\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 65, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 127, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 129, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 193, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\n\t\t// n \u003e 256\n\t\t{\"0x1\", 257, \"0x0\"},\n\t\t{\"0x31337\", 300, \"0x0\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromHex(tt.x)\n\t\twant := MustFromHex(tt.want)\n\n\t\tgot := new(Uint).SRsh(x, tt.y)\n\n\t\tif !got.Eq(want) {\n\t\t\tt.Errorf(\"SRsh(%s, %d) = %s, want %s\", tt.x, tt.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n"},{"name":"cmp.gno","body":"// cmp (or, comparisons) includes methods for comparing Uint instances.\n// These comparison functions cover a range of operations including equality checks, less than/greater than\n// evaluations, and specialized comparisons such as signed greater than. These are fundamental for logical\n// decision making based on Uint values.\npackage uint256\n\nimport (\n\t\"math/bits\"\n)\n\n// Cmp compares z and x and returns:\n//\n//\t-1 if z \u003c x\n//\t 0 if z == x\n//\t+1 if z \u003e x\nfunc (z *Uint) Cmp(x *Uint) (r int) {\n\t// z \u003c x \u003c=\u003e z - x \u003c 0 i.e. when subtraction overflows.\n\td0, carry := bits.Sub64(z.arr[0], x.arr[0], 0)\n\td1, carry := bits.Sub64(z.arr[1], x.arr[1], carry)\n\td2, carry := bits.Sub64(z.arr[2], x.arr[2], carry)\n\td3, carry := bits.Sub64(z.arr[3], x.arr[3], carry)\n\tif carry == 1 {\n\t\treturn -1\n\t}\n\tif d0|d1|d2|d3 == 0 {\n\t\treturn 0\n\t}\n\treturn 1\n}\n\n// IsZero returns true if z == 0\nfunc (z *Uint) IsZero() bool {\n\treturn (z.arr[0] | z.arr[1] | z.arr[2] | z.arr[3]) == 0\n}\n\n// Sign returns:\n//\n//\t-1 if z \u003c 0\n//\t 0 if z == 0\n//\t+1 if z \u003e 0\n//\n// Where z is interpreted as a two's complement signed number\nfunc (z *Uint) Sign() int {\n\tif z.IsZero() {\n\t\treturn 0\n\t}\n\tif z.arr[3] \u003c 0x8000000000000000 {\n\t\treturn 1\n\t}\n\treturn -1\n}\n\n// LtUint64 returns true if z is smaller than n\nfunc (z *Uint) LtUint64(n uint64) bool {\n\treturn z.arr[0] \u003c n \u0026\u0026 (z.arr[1]|z.arr[2]|z.arr[3]) == 0\n}\n\n// GtUint64 returns true if z is larger than n\nfunc (z *Uint) GtUint64(n uint64) bool {\n\treturn z.arr[0] \u003e n || (z.arr[1]|z.arr[2]|z.arr[3]) != 0\n}\n\n// Lt returns true if z \u003c x\nfunc (z *Uint) Lt(x *Uint) bool {\n\t// z \u003c x \u003c=\u003e z - x \u003c 0 i.e. when subtraction overflows.\n\t_, carry := bits.Sub64(z.arr[0], x.arr[0], 0)\n\t_, carry = bits.Sub64(z.arr[1], x.arr[1], carry)\n\t_, carry = bits.Sub64(z.arr[2], x.arr[2], carry)\n\t_, carry = bits.Sub64(z.arr[3], x.arr[3], carry)\n\n\treturn carry != 0\n}\n\n// Gt returns true if z \u003e x\nfunc (z *Uint) Gt(x *Uint) bool {\n\treturn x.Lt(z)\n}\n\n// Lte returns true if z \u003c= x\nfunc (z *Uint) Lte(x *Uint) bool {\n\tcond1 := z.Lt(x)\n\tcond2 := z.Eq(x)\n\n\tif cond1 || cond2 {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// Gte returns true if z \u003e= x\nfunc (z *Uint) Gte(x *Uint) bool {\n\tcond1 := z.Gt(x)\n\tcond2 := z.Eq(x)\n\n\tif cond1 || cond2 {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// Eq returns true if z == x\nfunc (z *Uint) Eq(x *Uint) bool {\n\treturn (z.arr[0] == x.arr[0]) \u0026\u0026 (z.arr[1] == x.arr[1]) \u0026\u0026 (z.arr[2] == x.arr[2]) \u0026\u0026 (z.arr[3] == x.arr[3])\n}\n\n// Neq returns true if z != x\nfunc (z *Uint) Neq(x *Uint) bool {\n\treturn !z.Eq(x)\n}\n\n// Sgt interprets z and x as signed integers, and returns\n// true if z \u003e x\nfunc (z *Uint) Sgt(x *Uint) bool {\n\tzSign := z.Sign()\n\txSign := x.Sign()\n\n\tswitch {\n\tcase zSign \u003e= 0 \u0026\u0026 xSign \u003c 0:\n\t\treturn true\n\tcase zSign \u003c 0 \u0026\u0026 xSign \u003e= 0:\n\t\treturn false\n\tdefault:\n\t\treturn z.Gt(x)\n\t}\n}\n"},{"name":"cmp_test.gno","body":"package uint256\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestSign(t *testing.T) {\n\ttests := []struct {\n\t\tinput *Uint\n\t\texpected int\n\t}{\n\t\t{\n\t\t\tinput: NewUint(0),\n\t\t\texpected: 0,\n\t\t},\n\t\t{\n\t\t\tinput: NewUint(1),\n\t\t\texpected: 1,\n\t\t},\n\t\t{\n\t\t\tinput: NewUint(0x7fffffffffffffff),\n\t\t\texpected: 1,\n\t\t},\n\t\t{\n\t\t\tinput: NewUint(0x8000000000000000),\n\t\t\texpected: 1,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.input.ToString(), func(t *testing.T) {\n\t\t\tresult := tt.input.Sign()\n\t\t\tif result != tt.expected {\n\t\t\t\tt.Errorf(\"Sign() = %d; want %d\", result, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCmp(t *testing.T) {\n\ttests := []struct {\n\t\tx, y string\n\t\twant int\n\t}{\n\t\t{\"0\", \"0\", 0},\n\t\t{\"0\", \"1\", -1},\n\t\t{\"1\", \"0\", 1},\n\t\t{\"1\", \"1\", 0},\n\t\t{\"10\", \"10\", 0},\n\t\t{\"10\", \"11\", -1},\n\t\t{\"11\", \"10\", 1},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx := MustFromDecimal(tc.x)\n\t\ty := MustFromDecimal(tc.y)\n\n\t\tgot := x.Cmp(y)\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Cmp(%s, %s) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestIsZero(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant bool\n\t}{\n\t\t{\"0\", true},\n\t\t{\"1\", false},\n\t\t{\"10\", false},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\n\t\tgot := x.IsZero()\n\t\tif got != tt.want {\n\t\t\tt.Errorf(\"IsZero(%s) = %v, want %v\", tt.x, got, tt.want)\n\t\t}\n\t}\n}\n\nfunc TestLtUint64(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty uint64\n\t\twant bool\n\t}{\n\t\t{\"0\", 1, true},\n\t\t{\"1\", 0, false},\n\t\t{\"10\", 10, false},\n\t\t{\"0xffffffffffffffff\", 0, false},\n\t\t{\"0x10000000000000000\", 10000000000000000, false},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx := parseTestString(t, tc.x)\n\n\t\tgot := x.LtUint64(tc.y)\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"LtUint64(%s, %d) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestUint_GtUint64(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tz string\n\t\tn uint64\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"z \u003e n\",\n\t\t\tz: \"1\",\n\t\t\tn: 0,\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"z \u003c n\",\n\t\t\tz: \"18446744073709551615\",\n\t\t\tn: 0xFFFFFFFFFFFFFFFF,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"z == n\",\n\t\t\tz: \"18446744073709551615\",\n\t\t\tn: 0xFFFFFFFFFFFFFFFF,\n\t\t\twant: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tz := MustFromDecimal(tt.z)\n\n\t\t\tif got := z.GtUint64(tt.n); got != tt.want {\n\t\t\t\tt.Errorf(\"Uint.GtUint64() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSGT(t *testing.T) {\n\tx := MustFromHex(\"0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe\")\n\ty := MustFromHex(\"0x0\")\n\tactual := x.Sgt(y)\n\tif actual {\n\t\tt.Fatalf(\"Expected %v false\", actual)\n\t}\n\n\tx = MustFromHex(\"0x0\")\n\ty = MustFromHex(\"0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe\")\n\tactual = x.Sgt(y)\n\tif !actual {\n\t\tt.Fatalf(\"Expected %v true\", actual)\n\t}\n}\n\nfunc TestEq(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty string\n\t\twant bool\n\t}{\n\t\t{\"0xffffffffffffffff\", \"18446744073709551615\", true},\n\t\t{\"0x10000000000000000\", \"18446744073709551616\", true},\n\t\t{\"0\", \"0\", true},\n\t\t{twoPow256Sub1, twoPow256Sub1, true},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := parseTestString(t, tt.x)\n\n\t\ty, err := FromDecimal(tt.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.Eq(y)\n\n\t\tif got != tt.want {\n\t\t\tt.Errorf(\"Eq(%s, %s) = %v, want %v\", tt.x, tt.y, got, tt.want)\n\t\t}\n\t}\n}\n\nfunc TestUint_Lte(t *testing.T) {\n\ttests := []struct {\n\t\tz, x string\n\t\twant bool\n\t}{\n\t\t{\"10\", \"20\", true},\n\t\t{\"20\", \"10\", false},\n\t\t{\"10\", \"10\", true},\n\t\t{\"0\", \"0\", true},\n\t}\n\n\tfor _, tt := range tests {\n\t\tz, err := FromDecimal(tt.z)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\t\tx, err := FromDecimal(tt.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\t\tif got := z.Lte(x); got != tt.want {\n\t\t\tt.Errorf(\"Uint.Lte(%v, %v) = %v, want %v\", tt.z, tt.x, got, tt.want)\n\t\t}\n\t}\n}\n\nfunc TestUint_Gte(t *testing.T) {\n\ttests := []struct {\n\t\tz, x string\n\t\twant bool\n\t}{\n\t\t{\"20\", \"10\", true},\n\t\t{\"10\", \"20\", false},\n\t\t{\"10\", \"10\", true},\n\t\t{\"0\", \"0\", true},\n\t}\n\n\tfor _, tt := range tests {\n\t\tz := parseTestString(t, tt.z)\n\t\tx := parseTestString(t, tt.x)\n\n\t\tif got := z.Gte(x); got != tt.want {\n\t\t\tt.Errorf(\"Uint.Gte(%v, %v) = %v, want %v\", tt.z, tt.x, got, tt.want)\n\t\t}\n\t}\n}\n\nfunc parseTestString(_ *testing.T, s string) *Uint {\n\tvar x *Uint\n\n\tif strings.HasPrefix(s, \"0x\") {\n\t\tx = MustFromHex(s)\n\t} else {\n\t\tx = MustFromDecimal(s)\n\t}\n\n\treturn x\n}\n"},{"name":"conversion.gno","body":"// conversions contains methods for converting Uint instances to other types and vice versa.\n// This includes conversions to and from basic types such as uint64 and int32, as well as string representations\n// and byte slices. Additionally, it covers marshaling and unmarshaling for JSON and other text formats.\npackage uint256\n\nimport (\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// Uint64 returns the lower 64-bits of z\nfunc (z *Uint) Uint64() uint64 {\n\treturn z.arr[0]\n}\n\n// Uint64WithOverflow returns the lower 64-bits of z and bool whether overflow occurred\nfunc (z *Uint) Uint64WithOverflow() (uint64, bool) {\n\treturn z.arr[0], (z.arr[1] | z.arr[2] | z.arr[3]) != 0\n}\n\n// SetUint64 sets z to the value x\nfunc (z *Uint) SetUint64(x uint64) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, 0, 0, x\n\treturn z\n}\n\n// IsUint64 reports whether z can be represented as a uint64.\nfunc (z *Uint) IsUint64() bool {\n\treturn (z.arr[1] | z.arr[2] | z.arr[3]) == 0\n}\n\n// Dec returns the decimal representation of z.\nfunc (z *Uint) Dec() string {\n\tif z.IsZero() {\n\t\treturn \"0\"\n\t}\n\tif z.IsUint64() {\n\t\treturn strconv.FormatUint(z.Uint64(), 10)\n\t}\n\n\t// The max uint64 value being 18446744073709551615, the largest\n\t// power-of-ten below that is 10000000000000000000.\n\t// When we do a DivMod using that number, the remainder that we\n\t// get back is the lower part of the output.\n\t//\n\t// The ascii-output of remainder will never exceed 19 bytes (since it will be\n\t// below 10000000000000000000).\n\t//\n\t// Algorithm example using 100 as divisor\n\t//\n\t// 12345 % 100 = 45 (rem)\n\t// 12345 / 100 = 123 (quo)\n\t// -\u003e output '45', continue iterate on 123\n\tvar (\n\t\t// out is 98 bytes long: 78 (max size of a string without leading zeroes,\n\t\t// plus slack so we can copy 19 bytes every iteration).\n\t\t// We init it with zeroes, because when strconv appends the ascii representations,\n\t\t// it will omit leading zeroes.\n\t\tout = []byte(\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\")\n\t\tdivisor = NewUint(10000000000000000000) // 20 digits\n\t\ty = new(Uint).Set(z) // copy to avoid modifying z\n\t\tpos = len(out) // position to write to\n\t\tbuf = make([]byte, 0, 19) // buffer to write uint64:s to\n\t)\n\tfor {\n\t\t// Obtain Q and R for divisor\n\t\tvar quot Uint\n\t\trem := udivrem(quot.arr[:], y.arr[:], divisor)\n\t\ty.Set(\u0026quot) // Set Q for next loop\n\t\t// Convert the R to ascii representation\n\t\tbuf = strconv.AppendUint(buf[:0], rem.Uint64(), 10)\n\t\t// Copy in the ascii digits\n\t\tcopy(out[pos-len(buf):], buf)\n\t\tif y.IsZero() {\n\t\t\tbreak\n\t\t}\n\t\t// Move 19 digits left\n\t\tpos -= 19\n\t}\n\t// skip leading zeroes by only using the 'used size' of buf\n\treturn string(out[pos-len(buf):])\n}\n\nfunc (z *Uint) Scan(src interface{}) error {\n\tif src == nil {\n\t\tz.Clear()\n\t\treturn nil\n\t}\n\n\tswitch src := src.(type) {\n\tcase string:\n\t\treturn z.scanScientificFromString(src)\n\tcase []byte:\n\t\treturn z.scanScientificFromString(string(src))\n\t}\n\treturn errors.New(\"default // unsupported type: can't convert to uint256.Uint\")\n}\n\nfunc (z *Uint) scanScientificFromString(src string) error {\n\tif len(src) == 0 {\n\t\tz.Clear()\n\t\treturn nil\n\t}\n\n\tidx := strings.IndexByte(src, 'e')\n\tif idx == -1 {\n\t\treturn z.SetFromDecimal(src)\n\t}\n\tif err := z.SetFromDecimal(src[:idx]); err != nil {\n\t\treturn err\n\t}\n\tif src[(idx+1):] == \"0\" {\n\t\treturn nil\n\t}\n\texp := new(Uint)\n\tif err := exp.SetFromDecimal(src[(idx + 1):]); err != nil {\n\t\treturn err\n\t}\n\tif exp.GtUint64(77) { // 10**78 is larger than 2**256\n\t\treturn ErrBig256Range\n\t}\n\texp.Exp(NewUint(10), exp)\n\tif _, overflow := z.MulOverflow(z, exp); overflow {\n\t\treturn ErrBig256Range\n\t}\n\treturn nil\n}\n\n// ToString returns the decimal string representation of z. It returns an empty string if z is nil.\n// OBS: doesn't exist from holiman's uint256\nfunc (z *Uint) ToString() string {\n\tif z == nil {\n\t\treturn \"\"\n\t}\n\n\treturn z.Dec()\n}\n\n// MarshalJSON implements json.Marshaler.\n// MarshalJSON marshals using the 'decimal string' representation. This is _not_ compatible\n// with big.Uint: big.Uint marshals into JSON 'native' numeric format.\n//\n// The JSON native format is, on some platforms, (e.g. javascript), limited to 53-bit large\n// integer space. Thus, U256 uses string-format, which is not compatible with\n// big.int (big.Uint refuses to unmarshal a string representation).\nfunc (z *Uint) MarshalJSON() ([]byte, error) {\n\treturn []byte(`\"` + z.Dec() + `\"`), nil\n}\n\n// UnmarshalJSON implements json.Unmarshaler. UnmarshalJSON accepts either\n// - Quoted string: either hexadecimal OR decimal\n// - Not quoted string: only decimal\nfunc (z *Uint) UnmarshalJSON(input []byte) error {\n\tif len(input) \u003c 2 || input[0] != '\"' || input[len(input)-1] != '\"' {\n\t\t// if not quoted, it must be decimal\n\t\treturn z.fromDecimal(string(input))\n\t}\n\treturn z.UnmarshalText(input[1 : len(input)-1])\n}\n\n// MarshalText implements encoding.TextMarshaler\n// MarshalText marshals using the decimal representation (compatible with big.Uint)\nfunc (z *Uint) MarshalText() ([]byte, error) {\n\treturn []byte(z.Dec()), nil\n}\n\n// UnmarshalText implements encoding.TextUnmarshaler. This method\n// can unmarshal either hexadecimal or decimal.\n// - For hexadecimal, the input _must_ be prefixed with 0x or 0X\nfunc (z *Uint) UnmarshalText(input []byte) error {\n\tif len(input) \u003e= 2 \u0026\u0026 input[0] == '0' \u0026\u0026 (input[1] == 'x' || input[1] == 'X') {\n\t\treturn z.fromHex(string(input))\n\t}\n\treturn z.fromDecimal(string(input))\n}\n\n// SetBytes interprets buf as the bytes of a big-endian unsigned\n// integer, sets z to that value, and returns z.\n// If buf is larger than 32 bytes, the last 32 bytes is used.\nfunc (z *Uint) SetBytes(buf []byte) *Uint {\n\tswitch l := len(buf); l {\n\tcase 0:\n\t\tz.Clear()\n\tcase 1:\n\t\tz.SetBytes1(buf)\n\tcase 2:\n\t\tz.SetBytes2(buf)\n\tcase 3:\n\t\tz.SetBytes3(buf)\n\tcase 4:\n\t\tz.SetBytes4(buf)\n\tcase 5:\n\t\tz.SetBytes5(buf)\n\tcase 6:\n\t\tz.SetBytes6(buf)\n\tcase 7:\n\t\tz.SetBytes7(buf)\n\tcase 8:\n\t\tz.SetBytes8(buf)\n\tcase 9:\n\t\tz.SetBytes9(buf)\n\tcase 10:\n\t\tz.SetBytes10(buf)\n\tcase 11:\n\t\tz.SetBytes11(buf)\n\tcase 12:\n\t\tz.SetBytes12(buf)\n\tcase 13:\n\t\tz.SetBytes13(buf)\n\tcase 14:\n\t\tz.SetBytes14(buf)\n\tcase 15:\n\t\tz.SetBytes15(buf)\n\tcase 16:\n\t\tz.SetBytes16(buf)\n\tcase 17:\n\t\tz.SetBytes17(buf)\n\tcase 18:\n\t\tz.SetBytes18(buf)\n\tcase 19:\n\t\tz.SetBytes19(buf)\n\tcase 20:\n\t\tz.SetBytes20(buf)\n\tcase 21:\n\t\tz.SetBytes21(buf)\n\tcase 22:\n\t\tz.SetBytes22(buf)\n\tcase 23:\n\t\tz.SetBytes23(buf)\n\tcase 24:\n\t\tz.SetBytes24(buf)\n\tcase 25:\n\t\tz.SetBytes25(buf)\n\tcase 26:\n\t\tz.SetBytes26(buf)\n\tcase 27:\n\t\tz.SetBytes27(buf)\n\tcase 28:\n\t\tz.SetBytes28(buf)\n\tcase 29:\n\t\tz.SetBytes29(buf)\n\tcase 30:\n\t\tz.SetBytes30(buf)\n\tcase 31:\n\t\tz.SetBytes31(buf)\n\tdefault:\n\t\tz.SetBytes32(buf[l-32:])\n\t}\n\treturn z\n}\n\n// SetBytes1 is identical to SetBytes(in[:1]), but panics is input is too short\nfunc (z *Uint) SetBytes1(in []byte) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = uint64(in[0])\n\treturn z\n}\n\n// SetBytes2 is identical to SetBytes(in[:2]), but panics is input is too short\nfunc (z *Uint) SetBytes2(in []byte) *Uint {\n\t_ = in[1] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = uint64(binary.BigEndian.Uint16(in[0:2]))\n\treturn z\n}\n\n// SetBytes3 is identical to SetBytes(in[:3]), but panics is input is too short\nfunc (z *Uint) SetBytes3(in []byte) *Uint {\n\t_ = in[2] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = uint64(binary.BigEndian.Uint16(in[1:3])) | uint64(in[0])\u003c\u003c16\n\treturn z\n}\n\n// SetBytes4 is identical to SetBytes(in[:4]), but panics is input is too short\nfunc (z *Uint) SetBytes4(in []byte) *Uint {\n\t_ = in[3] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = uint64(binary.BigEndian.Uint32(in[0:4]))\n\treturn z\n}\n\n// SetBytes5 is identical to SetBytes(in[:5]), but panics is input is too short\nfunc (z *Uint) SetBytes5(in []byte) *Uint {\n\t_ = in[4] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = bigEndianUint40(in[0:5])\n\treturn z\n}\n\n// SetBytes6 is identical to SetBytes(in[:6]), but panics is input is too short\nfunc (z *Uint) SetBytes6(in []byte) *Uint {\n\t_ = in[5] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = bigEndianUint48(in[0:6])\n\treturn z\n}\n\n// SetBytes7 is identical to SetBytes(in[:7]), but panics is input is too short\nfunc (z *Uint) SetBytes7(in []byte) *Uint {\n\t_ = in[6] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = bigEndianUint56(in[0:7])\n\treturn z\n}\n\n// SetBytes8 is identical to SetBytes(in[:8]), but panics is input is too short\nfunc (z *Uint) SetBytes8(in []byte) *Uint {\n\t_ = in[7] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = binary.BigEndian.Uint64(in[0:8])\n\treturn z\n}\n\n// SetBytes9 is identical to SetBytes(in[:9]), but panics is input is too short\nfunc (z *Uint) SetBytes9(in []byte) *Uint {\n\t_ = in[8] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = uint64(in[0])\n\tz.arr[0] = binary.BigEndian.Uint64(in[1:9])\n\treturn z\n}\n\n// SetBytes10 is identical to SetBytes(in[:10]), but panics is input is too short\nfunc (z *Uint) SetBytes10(in []byte) *Uint {\n\t_ = in[9] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = uint64(binary.BigEndian.Uint16(in[0:2]))\n\tz.arr[0] = binary.BigEndian.Uint64(in[2:10])\n\treturn z\n}\n\n// SetBytes11 is identical to SetBytes(in[:11]), but panics is input is too short\nfunc (z *Uint) SetBytes11(in []byte) *Uint {\n\t_ = in[10] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = uint64(binary.BigEndian.Uint16(in[1:3])) | uint64(in[0])\u003c\u003c16\n\tz.arr[0] = binary.BigEndian.Uint64(in[3:11])\n\treturn z\n}\n\n// SetBytes12 is identical to SetBytes(in[:12]), but panics is input is too short\nfunc (z *Uint) SetBytes12(in []byte) *Uint {\n\t_ = in[11] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = uint64(binary.BigEndian.Uint32(in[0:4]))\n\tz.arr[0] = binary.BigEndian.Uint64(in[4:12])\n\treturn z\n}\n\n// SetBytes13 is identical to SetBytes(in[:13]), but panics is input is too short\nfunc (z *Uint) SetBytes13(in []byte) *Uint {\n\t_ = in[12] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = bigEndianUint40(in[0:5])\n\tz.arr[0] = binary.BigEndian.Uint64(in[5:13])\n\treturn z\n}\n\n// SetBytes14 is identical to SetBytes(in[:14]), but panics is input is too short\nfunc (z *Uint) SetBytes14(in []byte) *Uint {\n\t_ = in[13] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = bigEndianUint48(in[0:6])\n\tz.arr[0] = binary.BigEndian.Uint64(in[6:14])\n\treturn z\n}\n\n// SetBytes15 is identical to SetBytes(in[:15]), but panics is input is too short\nfunc (z *Uint) SetBytes15(in []byte) *Uint {\n\t_ = in[14] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = bigEndianUint56(in[0:7])\n\tz.arr[0] = binary.BigEndian.Uint64(in[7:15])\n\treturn z\n}\n\n// SetBytes16 is identical to SetBytes(in[:16]), but panics is input is too short\nfunc (z *Uint) SetBytes16(in []byte) *Uint {\n\t_ = in[15] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = binary.BigEndian.Uint64(in[0:8])\n\tz.arr[0] = binary.BigEndian.Uint64(in[8:16])\n\treturn z\n}\n\n// SetBytes17 is identical to SetBytes(in[:17]), but panics is input is too short\nfunc (z *Uint) SetBytes17(in []byte) *Uint {\n\t_ = in[16] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = uint64(in[0])\n\tz.arr[1] = binary.BigEndian.Uint64(in[1:9])\n\tz.arr[0] = binary.BigEndian.Uint64(in[9:17])\n\treturn z\n}\n\n// SetBytes18 is identical to SetBytes(in[:18]), but panics is input is too short\nfunc (z *Uint) SetBytes18(in []byte) *Uint {\n\t_ = in[17] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = uint64(binary.BigEndian.Uint16(in[0:2]))\n\tz.arr[1] = binary.BigEndian.Uint64(in[2:10])\n\tz.arr[0] = binary.BigEndian.Uint64(in[10:18])\n\treturn z\n}\n\n// SetBytes19 is identical to SetBytes(in[:19]), but panics is input is too short\nfunc (z *Uint) SetBytes19(in []byte) *Uint {\n\t_ = in[18] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = uint64(binary.BigEndian.Uint16(in[1:3])) | uint64(in[0])\u003c\u003c16\n\tz.arr[1] = binary.BigEndian.Uint64(in[3:11])\n\tz.arr[0] = binary.BigEndian.Uint64(in[11:19])\n\treturn z\n}\n\n// SetBytes20 is identical to SetBytes(in[:20]), but panics is input is too short\nfunc (z *Uint) SetBytes20(in []byte) *Uint {\n\t_ = in[19] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = uint64(binary.BigEndian.Uint32(in[0:4]))\n\tz.arr[1] = binary.BigEndian.Uint64(in[4:12])\n\tz.arr[0] = binary.BigEndian.Uint64(in[12:20])\n\treturn z\n}\n\n// SetBytes21 is identical to SetBytes(in[:21]), but panics is input is too short\nfunc (z *Uint) SetBytes21(in []byte) *Uint {\n\t_ = in[20] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = bigEndianUint40(in[0:5])\n\tz.arr[1] = binary.BigEndian.Uint64(in[5:13])\n\tz.arr[0] = binary.BigEndian.Uint64(in[13:21])\n\treturn z\n}\n\n// SetBytes22 is identical to SetBytes(in[:22]), but panics is input is too short\nfunc (z *Uint) SetBytes22(in []byte) *Uint {\n\t_ = in[21] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = bigEndianUint48(in[0:6])\n\tz.arr[1] = binary.BigEndian.Uint64(in[6:14])\n\tz.arr[0] = binary.BigEndian.Uint64(in[14:22])\n\treturn z\n}\n\n// SetBytes23 is identical to SetBytes(in[:23]), but panics is input is too short\nfunc (z *Uint) SetBytes23(in []byte) *Uint {\n\t_ = in[22] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = bigEndianUint56(in[0:7])\n\tz.arr[1] = binary.BigEndian.Uint64(in[7:15])\n\tz.arr[0] = binary.BigEndian.Uint64(in[15:23])\n\treturn z\n}\n\n// SetBytes24 is identical to SetBytes(in[:24]), but panics is input is too short\nfunc (z *Uint) SetBytes24(in []byte) *Uint {\n\t_ = in[23] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = binary.BigEndian.Uint64(in[0:8])\n\tz.arr[1] = binary.BigEndian.Uint64(in[8:16])\n\tz.arr[0] = binary.BigEndian.Uint64(in[16:24])\n\treturn z\n}\n\n// SetBytes25 is identical to SetBytes(in[:25]), but panics is input is too short\nfunc (z *Uint) SetBytes25(in []byte) *Uint {\n\t_ = in[24] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = uint64(in[0])\n\tz.arr[2] = binary.BigEndian.Uint64(in[1:9])\n\tz.arr[1] = binary.BigEndian.Uint64(in[9:17])\n\tz.arr[0] = binary.BigEndian.Uint64(in[17:25])\n\treturn z\n}\n\n// SetBytes26 is identical to SetBytes(in[:26]), but panics is input is too short\nfunc (z *Uint) SetBytes26(in []byte) *Uint {\n\t_ = in[25] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = uint64(binary.BigEndian.Uint16(in[0:2]))\n\tz.arr[2] = binary.BigEndian.Uint64(in[2:10])\n\tz.arr[1] = binary.BigEndian.Uint64(in[10:18])\n\tz.arr[0] = binary.BigEndian.Uint64(in[18:26])\n\treturn z\n}\n\n// SetBytes27 is identical to SetBytes(in[:27]), but panics is input is too short\nfunc (z *Uint) SetBytes27(in []byte) *Uint {\n\t_ = in[26] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = uint64(binary.BigEndian.Uint16(in[1:3])) | uint64(in[0])\u003c\u003c16\n\tz.arr[2] = binary.BigEndian.Uint64(in[3:11])\n\tz.arr[1] = binary.BigEndian.Uint64(in[11:19])\n\tz.arr[0] = binary.BigEndian.Uint64(in[19:27])\n\treturn z\n}\n\n// SetBytes28 is identical to SetBytes(in[:28]), but panics is input is too short\nfunc (z *Uint) SetBytes28(in []byte) *Uint {\n\t_ = in[27] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = uint64(binary.BigEndian.Uint32(in[0:4]))\n\tz.arr[2] = binary.BigEndian.Uint64(in[4:12])\n\tz.arr[1] = binary.BigEndian.Uint64(in[12:20])\n\tz.arr[0] = binary.BigEndian.Uint64(in[20:28])\n\treturn z\n}\n\n// SetBytes29 is identical to SetBytes(in[:29]), but panics is input is too short\nfunc (z *Uint) SetBytes29(in []byte) *Uint {\n\t_ = in[23] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = bigEndianUint40(in[0:5])\n\tz.arr[2] = binary.BigEndian.Uint64(in[5:13])\n\tz.arr[1] = binary.BigEndian.Uint64(in[13:21])\n\tz.arr[0] = binary.BigEndian.Uint64(in[21:29])\n\treturn z\n}\n\n// SetBytes30 is identical to SetBytes(in[:30]), but panics is input is too short\nfunc (z *Uint) SetBytes30(in []byte) *Uint {\n\t_ = in[29] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = bigEndianUint48(in[0:6])\n\tz.arr[2] = binary.BigEndian.Uint64(in[6:14])\n\tz.arr[1] = binary.BigEndian.Uint64(in[14:22])\n\tz.arr[0] = binary.BigEndian.Uint64(in[22:30])\n\treturn z\n}\n\n// SetBytes31 is identical to SetBytes(in[:31]), but panics is input is too short\nfunc (z *Uint) SetBytes31(in []byte) *Uint {\n\t_ = in[30] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = bigEndianUint56(in[0:7])\n\tz.arr[2] = binary.BigEndian.Uint64(in[7:15])\n\tz.arr[1] = binary.BigEndian.Uint64(in[15:23])\n\tz.arr[0] = binary.BigEndian.Uint64(in[23:31])\n\treturn z\n}\n\n// SetBytes32 sets z to the value of the big-endian 256-bit unsigned integer in.\nfunc (z *Uint) SetBytes32(in []byte) *Uint {\n\t_ = in[31] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = binary.BigEndian.Uint64(in[0:8])\n\tz.arr[2] = binary.BigEndian.Uint64(in[8:16])\n\tz.arr[1] = binary.BigEndian.Uint64(in[16:24])\n\tz.arr[0] = binary.BigEndian.Uint64(in[24:32])\n\treturn z\n}\n\n// Utility methods that are \"missing\" among the bigEndian.UintXX methods.\n\n// bigEndianUint40 returns the uint64 value represented by the 5 bytes in big-endian order.\nfunc bigEndianUint40(b []byte) uint64 {\n\t_ = b[4] // bounds check hint to compiler; see golang.org/issue/14808\n\treturn uint64(b[4]) | uint64(b[3])\u003c\u003c8 | uint64(b[2])\u003c\u003c16 | uint64(b[1])\u003c\u003c24 |\n\t\tuint64(b[0])\u003c\u003c32\n}\n\n// bigEndianUint56 returns the uint64 value represented by the 7 bytes in big-endian order.\nfunc bigEndianUint56(b []byte) uint64 {\n\t_ = b[6] // bounds check hint to compiler; see golang.org/issue/14808\n\treturn uint64(b[6]) | uint64(b[5])\u003c\u003c8 | uint64(b[4])\u003c\u003c16 | uint64(b[3])\u003c\u003c24 |\n\t\tuint64(b[2])\u003c\u003c32 | uint64(b[1])\u003c\u003c40 | uint64(b[0])\u003c\u003c48\n}\n\n// bigEndianUint48 returns the uint64 value represented by the 6 bytes in big-endian order.\nfunc bigEndianUint48(b []byte) uint64 {\n\t_ = b[5] // bounds check hint to compiler; see golang.org/issue/14808\n\treturn uint64(b[5]) | uint64(b[4])\u003c\u003c8 | uint64(b[3])\u003c\u003c16 | uint64(b[2])\u003c\u003c24 |\n\t\tuint64(b[1])\u003c\u003c32 | uint64(b[0])\u003c\u003c40\n}\n"},{"name":"conversion_test.gno","body":"package uint256\n\nimport \"testing\"\n\nfunc TestIsUint64(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant bool\n\t}{\n\t\t{\"0x0\", true},\n\t\t{\"0x1\", true},\n\t\t{\"0x10\", true},\n\t\t{\"0xffffffffffffffff\", true},\n\t\t{\"0x10000000000000000\", false},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromHex(tt.x)\n\t\tgot := x.IsUint64()\n\n\t\tif got != tt.want {\n\t\t\tt.Errorf(\"IsUint64(%s) = %v, want %v\", tt.x, got, tt.want)\n\t\t}\n\t}\n}\n\nfunc TestDec(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tz Uint\n\t\twant string\n\t}{\n\t\t{\n\t\t\tname: \"zero\",\n\t\t\tz: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: \"0\",\n\t\t},\n\t\t{\n\t\t\tname: \"less than 20 digits\",\n\t\t\tz: Uint{arr: [4]uint64{1234567890, 0, 0, 0}},\n\t\t\twant: \"1234567890\",\n\t\t},\n\t\t{\n\t\t\tname: \"max possible value\",\n\t\t\tz: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: twoPow256Sub1,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresult := tt.z.Dec()\n\t\t\tif result != tt.want {\n\t\t\t\tt.Errorf(\"Dec(%v) = %s, want %s\", tt.z, result, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUint_Scan(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput interface{}\n\t\twant *Uint\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"nil\",\n\t\t\tinput: nil,\n\t\t\twant: NewUint(0),\n\t\t},\n\t\t{\n\t\t\tname: \"valid scientific notation\",\n\t\t\tinput: \"1e4\",\n\t\t\twant: NewUint(10000),\n\t\t},\n\t\t{\n\t\t\tname: \"valid decimal string\",\n\t\t\tinput: \"12345\",\n\t\t\twant: NewUint(12345),\n\t\t},\n\t\t{\n\t\t\tname: \"valid byte slice\",\n\t\t\tinput: []byte(\"12345\"),\n\t\t\twant: NewUint(12345),\n\t\t},\n\t\t{\n\t\t\tname: \"invalid string\",\n\t\t\tinput: \"invalid\",\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"out of range\",\n\t\t\tinput: \"115792089237316195423570985008687907853269984665640564039457584007913129639936\", // 2^256\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"unsupported type\",\n\t\t\tinput: 123,\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tz := new(Uint)\n\t\t\terr := z.Scan(tt.input)\n\n\t\t\tif tt.wantErr {\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Errorf(\"Scan() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"Scan() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\t}\n\t\t\t\tif !z.Eq(tt.want) {\n\t\t\t\t\tt.Errorf(\"Scan() = %v, want %v\", z, tt.want)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSetBytes(t *testing.T) {\n\ttests := []struct {\n\t\tinput []byte\n\t\texpected string\n\t}{\n\t\t{[]byte{}, \"0\"},\n\t\t{[]byte{0x01}, \"1\"},\n\t\t{[]byte{0x12, 0x34}, \"4660\"},\n\t\t{[]byte{0x12, 0x34, 0x56}, \"1193046\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78}, \"305419896\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a}, \"78187493530\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc}, \"20015998343868\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde}, \"5124095576030430\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}, \"1311768467463790320\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12}, \"335812727670730321938\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34}, \"85968058283706962416180\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56}, \"22007822920628982378542166\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78}, \"5634002667681019488906794616\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a}, \"1442304682926340989160139421850\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc}, \"369229998829143293224995691993788\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde}, \"94522879700260683065598897150409950\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}, \"24197857203266734864793317670504947440\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12}, \"6194651444036284125387089323649266544658\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34}, \"1585830769673288736099094866854212235432500\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56}, \"405972677036361916441368285914678332270720086\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78}, \"103929005321308650608990281194157653061304342136\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a}, \"26605825362255014555901511985704359183693911586970\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc}, \"6811091292737283726310787068340315951025641366264508\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde}, \"1743639370940744633935561489495120883462564189763714270\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}, \"446371678960830626287503741310750946166416432579510853360\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12}, \"114271149813972640329600957775552242218602606740354778460178\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34}, \"29253414352376995924377845190541374007962267325530823285805620\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56}, \"7488874074208510956640728368778591746038340435335890761166238806\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78}, \"1917151762997378804900026462407319486985815151445988034858557134456\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a}, \"490790851327328974054406774376273788668368678770172936923790626420890\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc}, \"125642457939796217357928134240326089899102381765164271852490400363748028\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde}, \"32164469232587831643629602365523479014170209731882053594237542493119495390\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}, \"8234104123542484900769178205574010627627573691361805720124810878238590820080\"},\n\t\t// over 32 bytes (last 32 bytes are used)\n\t\t{append([]byte{0xff}, []byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}...), \"8234104123542484900769178205574010627627573691361805720124810878238590820080\"},\n\t}\n\n\tfor _, test := range tests {\n\t\tz := new(Uint)\n\t\tz.SetBytes(test.input)\n\t\texpected := MustFromDecimal(test.expected)\n\t\tif z.Cmp(expected) != 0 {\n\t\t\tt.Errorf(\"SetBytes(%x) = %s, expected %s\", test.input, z.ToString(), test.expected)\n\t\t}\n\t}\n}\n"},{"name":"error.gno","body":"package uint256\n\nimport (\n\t\"errors\"\n)\n\nvar (\n\tErrEmptyString = errors.New(\"empty hex string\")\n\tErrSyntax = errors.New(\"invalid hex string\")\n\tErrRange = errors.New(\"number out of range\")\n\tErrMissingPrefix = errors.New(\"hex string without 0x prefix\")\n\tErrEmptyNumber = errors.New(\"hex string \\\"0x\\\"\")\n\tErrLeadingZero = errors.New(\"hex number with leading zero digits\")\n\tErrBig256Range = errors.New(\"hex number \u003e 256 bits\")\n\tErrBadBufferLength = errors.New(\"bad ssz buffer length\")\n\tErrBadEncodedLength = errors.New(\"bad ssz encoded length\")\n\tErrInvalidBase = errors.New(\"invalid base\")\n\tErrInvalidBitSize = errors.New(\"invalid bit size\")\n)\n\ntype u256Error struct {\n\tfn string // function name\n\tinput string\n\terr error\n}\n\nfunc (e *u256Error) Error() string {\n\treturn e.fn + \": \" + e.input + \": \" + e.err.Error()\n}\n\nfunc (e *u256Error) Unwrap() error {\n\treturn e.err\n}\n\nfunc errEmptyString(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrEmptyString}\n}\n\nfunc errSyntax(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrSyntax}\n}\n\nfunc errMissingPrefix(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrMissingPrefix}\n}\n\nfunc errEmptyNumber(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrEmptyNumber}\n}\n\nfunc errLeadingZero(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrLeadingZero}\n}\n\nfunc errRange(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrRange}\n}\n\nfunc errBig256Range(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrBig256Range}\n}\n\nfunc errBadBufferLength(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrBadBufferLength}\n}\n\nfunc errInvalidBase(fn string, base int) error {\n\treturn \u0026u256Error{fn: fn, input: string(base), err: ErrInvalidBase}\n}\n\nfunc errInvalidBitSize(fn string, bitSize int) error {\n\treturn \u0026u256Error{fn: fn, input: string(bitSize), err: ErrInvalidBitSize}\n}\n"},{"name":"mod.gno","body":"package uint256\n\nimport (\n\t\"math/bits\"\n)\n\n// Some utility functions\n\n// Reciprocal computes a 320-bit value representing 1/m\n//\n// Notes:\n// - specialized for m.arr[3] != 0, hence limited to 2^192 \u003c= m \u003c 2^256\n// - returns zero if m.arr[3] == 0\n// - starts with a 32-bit division, refines with newton-raphson iterations\nfunc Reciprocal(m *Uint) (mu [5]uint64) {\n\tif m.arr[3] == 0 {\n\t\treturn mu\n\t}\n\n\ts := bits.LeadingZeros64(m.arr[3]) // Replace with leadingZeros(m) for general case\n\tp := 255 - s // floor(log_2(m)), m\u003e0\n\n\t// 0 or a power of 2?\n\n\t// Check if at least one bit is set in m.arr[2], m.arr[1] or m.arr[0],\n\t// or at least two bits in m.arr[3]\n\n\tif m.arr[0]|m.arr[1]|m.arr[2]|(m.arr[3]\u0026(m.arr[3]-1)) == 0 {\n\n\t\tmu[4] = ^uint64(0) \u003e\u003e uint(p\u002663)\n\t\tmu[3] = ^uint64(0)\n\t\tmu[2] = ^uint64(0)\n\t\tmu[1] = ^uint64(0)\n\t\tmu[0] = ^uint64(0)\n\n\t\treturn mu\n\t}\n\n\t// Maximise division precision by left-aligning divisor\n\n\tvar (\n\t\ty Uint // left-aligned copy of m\n\t\tr0 uint32 // estimate of 2^31/y\n\t)\n\n\ty.Lsh(m, uint(s)) // 1/2 \u003c y \u003c 1\n\n\t// Extract most significant 32 bits\n\n\tyh := uint32(y.arr[3] \u003e\u003e 32)\n\n\tif yh == 0x80000000 { // Avoid overflow in division\n\t\tr0 = 0xffffffff\n\t} else {\n\t\tr0, _ = bits.Div32(0x80000000, 0, yh)\n\t}\n\n\t// First iteration: 32 -\u003e 64\n\n\tt1 := uint64(r0) // 2^31/y\n\tt1 *= t1 // 2^62/y^2\n\tt1, _ = bits.Mul64(t1, y.arr[3]) // 2^62/y^2 * 2^64/y / 2^64 = 2^62/y\n\n\tr1 := uint64(r0) \u003c\u003c 32 // 2^63/y\n\tr1 -= t1 // 2^63/y - 2^62/y = 2^62/y\n\tr1 *= 2 // 2^63/y\n\n\tif (r1 | (y.arr[3] \u003c\u003c 1)) == 0 {\n\t\tr1 = ^uint64(0)\n\t}\n\n\t// Second iteration: 64 -\u003e 128\n\n\t// square: 2^126/y^2\n\ta2h, a2l := bits.Mul64(r1, r1)\n\n\t// multiply by y: e2h:e2l:b2h = 2^126/y^2 * 2^128/y / 2^128 = 2^126/y\n\tb2h, _ := bits.Mul64(a2l, y.arr[2])\n\tc2h, c2l := bits.Mul64(a2l, y.arr[3])\n\td2h, d2l := bits.Mul64(a2h, y.arr[2])\n\te2h, e2l := bits.Mul64(a2h, y.arr[3])\n\n\tb2h, c := bits.Add64(b2h, c2l, 0)\n\te2l, c = bits.Add64(e2l, c2h, c)\n\te2h, _ = bits.Add64(e2h, 0, c)\n\n\t_, c = bits.Add64(b2h, d2l, 0)\n\te2l, c = bits.Add64(e2l, d2h, c)\n\te2h, _ = bits.Add64(e2h, 0, c)\n\n\t// subtract: t2h:t2l = 2^127/y - 2^126/y = 2^126/y\n\tt2l, b := bits.Sub64(0, e2l, 0)\n\tt2h, _ := bits.Sub64(r1, e2h, b)\n\n\t// double: r2h:r2l = 2^127/y\n\tr2l, c := bits.Add64(t2l, t2l, 0)\n\tr2h, _ := bits.Add64(t2h, t2h, c)\n\n\tif (r2h | r2l | (y.arr[3] \u003c\u003c 1)) == 0 {\n\t\tr2h = ^uint64(0)\n\t\tr2l = ^uint64(0)\n\t}\n\n\t// Third iteration: 128 -\u003e 192\n\n\t// square r2 (keep 256 bits): 2^190/y^2\n\ta3h, a3l := bits.Mul64(r2l, r2l)\n\tb3h, b3l := bits.Mul64(r2l, r2h)\n\tc3h, c3l := bits.Mul64(r2h, r2h)\n\n\ta3h, c = bits.Add64(a3h, b3l, 0)\n\tc3l, c = bits.Add64(c3l, b3h, c)\n\tc3h, _ = bits.Add64(c3h, 0, c)\n\n\ta3h, c = bits.Add64(a3h, b3l, 0)\n\tc3l, c = bits.Add64(c3l, b3h, c)\n\tc3h, _ = bits.Add64(c3h, 0, c)\n\n\t// multiply by y: q = 2^190/y^2 * 2^192/y / 2^192 = 2^190/y\n\n\tx0 := a3l\n\tx1 := a3h\n\tx2 := c3l\n\tx3 := c3h\n\n\tvar q0, q1, q2, q3, q4, t0 uint64\n\n\tq0, _ = bits.Mul64(x2, y.arr[0])\n\tq1, t0 = bits.Mul64(x3, y.arr[0])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, _ = bits.Add64(q1, 0, c)\n\n\tt1, _ = bits.Mul64(x1, y.arr[1])\n\tq0, c = bits.Add64(q0, t1, 0)\n\tq2, t0 = bits.Mul64(x3, y.arr[1])\n\tq1, c = bits.Add64(q1, t0, c)\n\tq2, _ = bits.Add64(q2, 0, c)\n\n\tt1, t0 = bits.Mul64(x2, y.arr[1])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tq2, _ = bits.Add64(q2, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, y.arr[2])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tq3, t0 = bits.Mul64(x3, y.arr[2])\n\tq2, c = bits.Add64(q2, t0, c)\n\tq3, _ = bits.Add64(q3, 0, c)\n\n\tt1, _ = bits.Mul64(x0, y.arr[2])\n\tq0, c = bits.Add64(q0, t1, 0)\n\tt1, t0 = bits.Mul64(x2, y.arr[2])\n\tq1, c = bits.Add64(q1, t0, c)\n\tq2, c = bits.Add64(q2, t1, c)\n\tq3, _ = bits.Add64(q3, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, y.arr[3])\n\tq1, c = bits.Add64(q1, t0, 0)\n\tq2, c = bits.Add64(q2, t1, c)\n\tq4, t0 = bits.Mul64(x3, y.arr[3])\n\tq3, c = bits.Add64(q3, t0, c)\n\tq4, _ = bits.Add64(q4, 0, c)\n\n\tt1, t0 = bits.Mul64(x0, y.arr[3])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tt1, t0 = bits.Mul64(x2, y.arr[3])\n\tq2, c = bits.Add64(q2, t0, c)\n\tq3, c = bits.Add64(q3, t1, c)\n\tq4, _ = bits.Add64(q4, 0, c)\n\n\t// subtract: t3 = 2^191/y - 2^190/y = 2^190/y\n\t_, b = bits.Sub64(0, q0, 0)\n\t_, b = bits.Sub64(0, q1, b)\n\tt3l, b := bits.Sub64(0, q2, b)\n\tt3m, b := bits.Sub64(r2l, q3, b)\n\tt3h, _ := bits.Sub64(r2h, q4, b)\n\n\t// double: r3 = 2^191/y\n\tr3l, c := bits.Add64(t3l, t3l, 0)\n\tr3m, c := bits.Add64(t3m, t3m, c)\n\tr3h, _ := bits.Add64(t3h, t3h, c)\n\n\t// Fourth iteration: 192 -\u003e 320\n\n\t// square r3\n\n\ta4h, a4l := bits.Mul64(r3l, r3l)\n\tb4h, b4l := bits.Mul64(r3l, r3m)\n\tc4h, c4l := bits.Mul64(r3l, r3h)\n\td4h, d4l := bits.Mul64(r3m, r3m)\n\te4h, e4l := bits.Mul64(r3m, r3h)\n\tf4h, f4l := bits.Mul64(r3h, r3h)\n\n\tb4h, c = bits.Add64(b4h, c4l, 0)\n\te4l, c = bits.Add64(e4l, c4h, c)\n\te4h, _ = bits.Add64(e4h, 0, c)\n\n\ta4h, c = bits.Add64(a4h, b4l, 0)\n\td4l, c = bits.Add64(d4l, b4h, c)\n\td4h, c = bits.Add64(d4h, e4l, c)\n\tf4l, c = bits.Add64(f4l, e4h, c)\n\tf4h, _ = bits.Add64(f4h, 0, c)\n\n\ta4h, c = bits.Add64(a4h, b4l, 0)\n\td4l, c = bits.Add64(d4l, b4h, c)\n\td4h, c = bits.Add64(d4h, e4l, c)\n\tf4l, c = bits.Add64(f4l, e4h, c)\n\tf4h, _ = bits.Add64(f4h, 0, c)\n\n\t// multiply by y\n\n\tx1, x0 = bits.Mul64(d4h, y.arr[0])\n\tx3, x2 = bits.Mul64(f4h, y.arr[0])\n\tt1, t0 = bits.Mul64(f4l, y.arr[0])\n\tx1, c = bits.Add64(x1, t0, 0)\n\tx2, c = bits.Add64(x2, t1, c)\n\tx3, _ = bits.Add64(x3, 0, c)\n\n\tt1, t0 = bits.Mul64(d4h, y.arr[1])\n\tx1, c = bits.Add64(x1, t0, 0)\n\tx2, c = bits.Add64(x2, t1, c)\n\tx4, t0 := bits.Mul64(f4h, y.arr[1])\n\tx3, c = bits.Add64(x3, t0, c)\n\tx4, _ = bits.Add64(x4, 0, c)\n\tt1, t0 = bits.Mul64(d4l, y.arr[1])\n\tx0, c = bits.Add64(x0, t0, 0)\n\tx1, c = bits.Add64(x1, t1, c)\n\tt1, t0 = bits.Mul64(f4l, y.arr[1])\n\tx2, c = bits.Add64(x2, t0, c)\n\tx3, c = bits.Add64(x3, t1, c)\n\tx4, _ = bits.Add64(x4, 0, c)\n\n\tt1, t0 = bits.Mul64(a4h, y.arr[2])\n\tx0, c = bits.Add64(x0, t0, 0)\n\tx1, c = bits.Add64(x1, t1, c)\n\tt1, t0 = bits.Mul64(d4h, y.arr[2])\n\tx2, c = bits.Add64(x2, t0, c)\n\tx3, c = bits.Add64(x3, t1, c)\n\tx5, t0 := bits.Mul64(f4h, y.arr[2])\n\tx4, c = bits.Add64(x4, t0, c)\n\tx5, _ = bits.Add64(x5, 0, c)\n\tt1, t0 = bits.Mul64(d4l, y.arr[2])\n\tx1, c = bits.Add64(x1, t0, 0)\n\tx2, c = bits.Add64(x2, t1, c)\n\tt1, t0 = bits.Mul64(f4l, y.arr[2])\n\tx3, c = bits.Add64(x3, t0, c)\n\tx4, c = bits.Add64(x4, t1, c)\n\tx5, _ = bits.Add64(x5, 0, c)\n\n\tt1, t0 = bits.Mul64(a4h, y.arr[3])\n\tx1, c = bits.Add64(x1, t0, 0)\n\tx2, c = bits.Add64(x2, t1, c)\n\tt1, t0 = bits.Mul64(d4h, y.arr[3])\n\tx3, c = bits.Add64(x3, t0, c)\n\tx4, c = bits.Add64(x4, t1, c)\n\tx6, t0 := bits.Mul64(f4h, y.arr[3])\n\tx5, c = bits.Add64(x5, t0, c)\n\tx6, _ = bits.Add64(x6, 0, c)\n\tt1, t0 = bits.Mul64(a4l, y.arr[3])\n\tx0, c = bits.Add64(x0, t0, 0)\n\tx1, c = bits.Add64(x1, t1, c)\n\tt1, t0 = bits.Mul64(d4l, y.arr[3])\n\tx2, c = bits.Add64(x2, t0, c)\n\tx3, c = bits.Add64(x3, t1, c)\n\tt1, t0 = bits.Mul64(f4l, y.arr[3])\n\tx4, c = bits.Add64(x4, t0, c)\n\tx5, c = bits.Add64(x5, t1, c)\n\tx6, _ = bits.Add64(x6, 0, c)\n\n\t// subtract\n\t_, b = bits.Sub64(0, x0, 0)\n\t_, b = bits.Sub64(0, x1, b)\n\tr4l, b := bits.Sub64(0, x2, b)\n\tr4k, b := bits.Sub64(0, x3, b)\n\tr4j, b := bits.Sub64(r3l, x4, b)\n\tr4i, b := bits.Sub64(r3m, x5, b)\n\tr4h, _ := bits.Sub64(r3h, x6, b)\n\n\t// Multiply candidate for 1/4y by y, with full precision\n\n\tx0 = r4l\n\tx1 = r4k\n\tx2 = r4j\n\tx3 = r4i\n\tx4 = r4h\n\n\tq1, q0 = bits.Mul64(x0, y.arr[0])\n\tq3, q2 = bits.Mul64(x2, y.arr[0])\n\tq5, q4 := bits.Mul64(x4, y.arr[0])\n\n\tt1, t0 = bits.Mul64(x1, y.arr[0])\n\tq1, c = bits.Add64(q1, t0, 0)\n\tq2, c = bits.Add64(q2, t1, c)\n\tt1, t0 = bits.Mul64(x3, y.arr[0])\n\tq3, c = bits.Add64(q3, t0, c)\n\tq4, c = bits.Add64(q4, t1, c)\n\tq5, _ = bits.Add64(q5, 0, c)\n\n\tt1, t0 = bits.Mul64(x0, y.arr[1])\n\tq1, c = bits.Add64(q1, t0, 0)\n\tq2, c = bits.Add64(q2, t1, c)\n\tt1, t0 = bits.Mul64(x2, y.arr[1])\n\tq3, c = bits.Add64(q3, t0, c)\n\tq4, c = bits.Add64(q4, t1, c)\n\tq6, t0 := bits.Mul64(x4, y.arr[1])\n\tq5, c = bits.Add64(q5, t0, c)\n\tq6, _ = bits.Add64(q6, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, y.arr[1])\n\tq2, c = bits.Add64(q2, t0, 0)\n\tq3, c = bits.Add64(q3, t1, c)\n\tt1, t0 = bits.Mul64(x3, y.arr[1])\n\tq4, c = bits.Add64(q4, t0, c)\n\tq5, c = bits.Add64(q5, t1, c)\n\tq6, _ = bits.Add64(q6, 0, c)\n\n\tt1, t0 = bits.Mul64(x0, y.arr[2])\n\tq2, c = bits.Add64(q2, t0, 0)\n\tq3, c = bits.Add64(q3, t1, c)\n\tt1, t0 = bits.Mul64(x2, y.arr[2])\n\tq4, c = bits.Add64(q4, t0, c)\n\tq5, c = bits.Add64(q5, t1, c)\n\tq7, t0 := bits.Mul64(x4, y.arr[2])\n\tq6, c = bits.Add64(q6, t0, c)\n\tq7, _ = bits.Add64(q7, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, y.arr[2])\n\tq3, c = bits.Add64(q3, t0, 0)\n\tq4, c = bits.Add64(q4, t1, c)\n\tt1, t0 = bits.Mul64(x3, y.arr[2])\n\tq5, c = bits.Add64(q5, t0, c)\n\tq6, c = bits.Add64(q6, t1, c)\n\tq7, _ = bits.Add64(q7, 0, c)\n\n\tt1, t0 = bits.Mul64(x0, y.arr[3])\n\tq3, c = bits.Add64(q3, t0, 0)\n\tq4, c = bits.Add64(q4, t1, c)\n\tt1, t0 = bits.Mul64(x2, y.arr[3])\n\tq5, c = bits.Add64(q5, t0, c)\n\tq6, c = bits.Add64(q6, t1, c)\n\tq8, t0 := bits.Mul64(x4, y.arr[3])\n\tq7, c = bits.Add64(q7, t0, c)\n\tq8, _ = bits.Add64(q8, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, y.arr[3])\n\tq4, c = bits.Add64(q4, t0, 0)\n\tq5, c = bits.Add64(q5, t1, c)\n\tt1, t0 = bits.Mul64(x3, y.arr[3])\n\tq6, c = bits.Add64(q6, t0, c)\n\tq7, c = bits.Add64(q7, t1, c)\n\tq8, _ = bits.Add64(q8, 0, c)\n\n\t// Final adjustment\n\n\t// subtract q from 1/4\n\t_, b = bits.Sub64(0, q0, 0)\n\t_, b = bits.Sub64(0, q1, b)\n\t_, b = bits.Sub64(0, q2, b)\n\t_, b = bits.Sub64(0, q3, b)\n\t_, b = bits.Sub64(0, q4, b)\n\t_, b = bits.Sub64(0, q5, b)\n\t_, b = bits.Sub64(0, q6, b)\n\t_, b = bits.Sub64(0, q7, b)\n\t_, b = bits.Sub64(uint64(1)\u003c\u003c62, q8, b)\n\n\t// decrement the result\n\tx0, t := bits.Sub64(r4l, 1, 0)\n\tx1, t = bits.Sub64(r4k, 0, t)\n\tx2, t = bits.Sub64(r4j, 0, t)\n\tx3, t = bits.Sub64(r4i, 0, t)\n\tx4, _ = bits.Sub64(r4h, 0, t)\n\n\t// commit the decrement if the subtraction underflowed (reciprocal was too large)\n\tif b != 0 {\n\t\tr4h, r4i, r4j, r4k, r4l = x4, x3, x2, x1, x0\n\t}\n\n\t// Shift to correct bit alignment, truncating excess bits\n\n\tp = (p \u0026 63) - 1\n\n\tx0, c = bits.Add64(r4l, r4l, 0)\n\tx1, c = bits.Add64(r4k, r4k, c)\n\tx2, c = bits.Add64(r4j, r4j, c)\n\tx3, c = bits.Add64(r4i, r4i, c)\n\tx4, _ = bits.Add64(r4h, r4h, c)\n\n\tif p \u003c 0 {\n\t\tr4h, r4i, r4j, r4k, r4l = x4, x3, x2, x1, x0\n\t\tp = 0 // avoid negative shift below\n\t}\n\n\t{\n\t\tr := uint(p) // right shift\n\t\tl := uint(64 - r) // left shift\n\n\t\tx0 = (r4l \u003e\u003e r) | (r4k \u003c\u003c l)\n\t\tx1 = (r4k \u003e\u003e r) | (r4j \u003c\u003c l)\n\t\tx2 = (r4j \u003e\u003e r) | (r4i \u003c\u003c l)\n\t\tx3 = (r4i \u003e\u003e r) | (r4h \u003c\u003c l)\n\t\tx4 = (r4h \u003e\u003e r)\n\t}\n\n\tif p \u003e 0 {\n\t\tr4h, r4i, r4j, r4k, r4l = x4, x3, x2, x1, x0\n\t}\n\n\tmu[0] = r4l\n\tmu[1] = r4k\n\tmu[2] = r4j\n\tmu[3] = r4i\n\tmu[4] = r4h\n\n\treturn mu\n}\n\n// reduce4 computes the least non-negative residue of x modulo m\n//\n// requires a four-word modulus (m.arr[3] \u003e 1) and its inverse (mu)\nfunc reduce4(x [8]uint64, m *Uint, mu [5]uint64) (z Uint) {\n\t// NB: Most variable names in the comments match the pseudocode for\n\t// \tBarrett reduction in the Handbook of Applied Cryptography.\n\n\t// q1 = x/2^192\n\n\tx0 := x[3]\n\tx1 := x[4]\n\tx2 := x[5]\n\tx3 := x[6]\n\tx4 := x[7]\n\n\t// q2 = q1 * mu; q3 = q2 / 2^320\n\n\tvar q0, q1, q2, q3, q4, q5, t0, t1, c uint64\n\n\tq0, _ = bits.Mul64(x3, mu[0])\n\tq1, t0 = bits.Mul64(x4, mu[0])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, _ = bits.Add64(q1, 0, c)\n\n\tt1, _ = bits.Mul64(x2, mu[1])\n\tq0, c = bits.Add64(q0, t1, 0)\n\tq2, t0 = bits.Mul64(x4, mu[1])\n\tq1, c = bits.Add64(q1, t0, c)\n\tq2, _ = bits.Add64(q2, 0, c)\n\n\tt1, t0 = bits.Mul64(x3, mu[1])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tq2, _ = bits.Add64(q2, 0, c)\n\n\tt1, t0 = bits.Mul64(x2, mu[2])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tq3, t0 = bits.Mul64(x4, mu[2])\n\tq2, c = bits.Add64(q2, t0, c)\n\tq3, _ = bits.Add64(q3, 0, c)\n\n\tt1, _ = bits.Mul64(x1, mu[2])\n\tq0, c = bits.Add64(q0, t1, 0)\n\tt1, t0 = bits.Mul64(x3, mu[2])\n\tq1, c = bits.Add64(q1, t0, c)\n\tq2, c = bits.Add64(q2, t1, c)\n\tq3, _ = bits.Add64(q3, 0, c)\n\n\tt1, _ = bits.Mul64(x0, mu[3])\n\tq0, c = bits.Add64(q0, t1, 0)\n\tt1, t0 = bits.Mul64(x2, mu[3])\n\tq1, c = bits.Add64(q1, t0, c)\n\tq2, c = bits.Add64(q2, t1, c)\n\tq4, t0 = bits.Mul64(x4, mu[3])\n\tq3, c = bits.Add64(q3, t0, c)\n\tq4, _ = bits.Add64(q4, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, mu[3])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tt1, t0 = bits.Mul64(x3, mu[3])\n\tq2, c = bits.Add64(q2, t0, c)\n\tq3, c = bits.Add64(q3, t1, c)\n\tq4, _ = bits.Add64(q4, 0, c)\n\n\tt1, t0 = bits.Mul64(x0, mu[4])\n\t_, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tt1, t0 = bits.Mul64(x2, mu[4])\n\tq2, c = bits.Add64(q2, t0, c)\n\tq3, c = bits.Add64(q3, t1, c)\n\tq5, t0 = bits.Mul64(x4, mu[4])\n\tq4, c = bits.Add64(q4, t0, c)\n\tq5, _ = bits.Add64(q5, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, mu[4])\n\tq1, c = bits.Add64(q1, t0, 0)\n\tq2, c = bits.Add64(q2, t1, c)\n\tt1, t0 = bits.Mul64(x3, mu[4])\n\tq3, c = bits.Add64(q3, t0, c)\n\tq4, c = bits.Add64(q4, t1, c)\n\tq5, _ = bits.Add64(q5, 0, c)\n\n\t// Drop the fractional part of q3\n\n\tq0 = q1\n\tq1 = q2\n\tq2 = q3\n\tq3 = q4\n\tq4 = q5\n\n\t// r1 = x mod 2^320\n\n\tx0 = x[0]\n\tx1 = x[1]\n\tx2 = x[2]\n\tx3 = x[3]\n\tx4 = x[4]\n\n\t// r2 = q3 * m mod 2^320\n\n\tvar r0, r1, r2, r3, r4 uint64\n\n\tr4, r3 = bits.Mul64(q0, m.arr[3])\n\t_, t0 = bits.Mul64(q1, m.arr[3])\n\tr4, _ = bits.Add64(r4, t0, 0)\n\n\tt1, r2 = bits.Mul64(q0, m.arr[2])\n\tr3, c = bits.Add64(r3, t1, 0)\n\t_, t0 = bits.Mul64(q2, m.arr[2])\n\tr4, _ = bits.Add64(r4, t0, c)\n\n\tt1, t0 = bits.Mul64(q1, m.arr[2])\n\tr3, c = bits.Add64(r3, t0, 0)\n\tr4, _ = bits.Add64(r4, t1, c)\n\n\tt1, r1 = bits.Mul64(q0, m.arr[1])\n\tr2, c = bits.Add64(r2, t1, 0)\n\tt1, t0 = bits.Mul64(q2, m.arr[1])\n\tr3, c = bits.Add64(r3, t0, c)\n\tr4, _ = bits.Add64(r4, t1, c)\n\n\tt1, t0 = bits.Mul64(q1, m.arr[1])\n\tr2, c = bits.Add64(r2, t0, 0)\n\tr3, c = bits.Add64(r3, t1, c)\n\t_, t0 = bits.Mul64(q3, m.arr[1])\n\tr4, _ = bits.Add64(r4, t0, c)\n\n\tt1, r0 = bits.Mul64(q0, m.arr[0])\n\tr1, c = bits.Add64(r1, t1, 0)\n\tt1, t0 = bits.Mul64(q2, m.arr[0])\n\tr2, c = bits.Add64(r2, t0, c)\n\tr3, c = bits.Add64(r3, t1, c)\n\t_, t0 = bits.Mul64(q4, m.arr[0])\n\tr4, _ = bits.Add64(r4, t0, c)\n\n\tt1, t0 = bits.Mul64(q1, m.arr[0])\n\tr1, c = bits.Add64(r1, t0, 0)\n\tr2, c = bits.Add64(r2, t1, c)\n\tt1, t0 = bits.Mul64(q3, m.arr[0])\n\tr3, c = bits.Add64(r3, t0, c)\n\tr4, _ = bits.Add64(r4, t1, c)\n\n\t// r = r1 - r2\n\n\tvar b uint64\n\n\tr0, b = bits.Sub64(x0, r0, 0)\n\tr1, b = bits.Sub64(x1, r1, b)\n\tr2, b = bits.Sub64(x2, r2, b)\n\tr3, b = bits.Sub64(x3, r3, b)\n\tr4, b = bits.Sub64(x4, r4, b)\n\n\t// if r\u003c0 then r+=m\n\n\tif b != 0 {\n\t\tr0, c = bits.Add64(r0, m.arr[0], 0)\n\t\tr1, c = bits.Add64(r1, m.arr[1], c)\n\t\tr2, c = bits.Add64(r2, m.arr[2], c)\n\t\tr3, c = bits.Add64(r3, m.arr[3], c)\n\t\tr4, _ = bits.Add64(r4, 0, c)\n\t}\n\n\t// while (r\u003e=m) r-=m\n\n\tfor {\n\t\t// q = r - m\n\t\tq0, b = bits.Sub64(r0, m.arr[0], 0)\n\t\tq1, b = bits.Sub64(r1, m.arr[1], b)\n\t\tq2, b = bits.Sub64(r2, m.arr[2], b)\n\t\tq3, b = bits.Sub64(r3, m.arr[3], b)\n\t\tq4, b = bits.Sub64(r4, 0, b)\n\n\t\t// if borrow break\n\t\tif b != 0 {\n\t\t\tbreak\n\t\t}\n\n\t\t// r = q\n\t\tr4, r3, r2, r1, r0 = q4, q3, q2, q1, q0\n\t}\n\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = r3, r2, r1, r0\n\n\treturn z\n}\n"},{"name":"uint256.gno","body":"// Ported from https://github.com/holiman/uint256\n// This package provides a 256-bit unsigned integer type, Uint256, and associated functions.\npackage uint256\n\nimport (\n\t\"errors\"\n\t\"math/bits\"\n\t\"strconv\"\n)\n\nconst (\n\tMaxUint64 = 1\u003c\u003c64 - 1\n\tuintSize = 32 \u003c\u003c (^uint(0) \u003e\u003e 63)\n)\n\n// Uint is represented as an array of 4 uint64, in little-endian order,\n// so that Uint[3] is the most significant, and Uint[0] is the least significant\ntype Uint struct {\n\tarr [4]uint64\n}\n\n// NewUint returns a new initialized Uint.\nfunc NewUint(val uint64) *Uint {\n\tz := \u0026Uint{arr: [4]uint64{val, 0, 0, 0}}\n\treturn z\n}\n\n// Zero returns a new Uint initialized to zero.\nfunc Zero() *Uint {\n\treturn NewUint(0)\n}\n\n// One returns a new Uint initialized to one.\nfunc One() *Uint {\n\treturn NewUint(1)\n}\n\n// SetAllOne sets all the bits of z to 1\nfunc (z *Uint) SetAllOne() *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = MaxUint64, MaxUint64, MaxUint64, MaxUint64\n\treturn z\n}\n\n// Set sets z to x and returns z.\nfunc (z *Uint) Set(x *Uint) *Uint {\n\t*z = *x\n\n\treturn z\n}\n\n// SetOne sets z to 1\nfunc (z *Uint) SetOne() *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, 0, 0, 1\n\treturn z\n}\n\nconst twoPow256Sub1 = \"115792089237316195423570985008687907853269984665640564039457584007913129639935\"\n\n// SetFromDecimal sets z from the given string, interpreted as a decimal number.\n// OBS! This method is _not_ strictly identical to the (*big.Uint).SetString(..., 10) method.\n// Notable differences:\n// - This method does not accept underscore input, e.g. \"100_000\",\n// - This method does not accept negative zero as valid, e.g \"-0\",\n// - (this method does not accept any negative input as valid))\nfunc (z *Uint) SetFromDecimal(s string) (err error) {\n\t// Remove max one leading +\n\tif len(s) \u003e 0 \u0026\u0026 s[0] == '+' {\n\t\ts = s[1:]\n\t}\n\t// Remove any number of leading zeroes\n\tif len(s) \u003e 0 \u0026\u0026 s[0] == '0' {\n\t\tvar i int\n\t\tvar c rune\n\t\tfor i, c = range s {\n\t\t\tif c != '0' {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\ts = s[i:]\n\t}\n\tif len(s) \u003c len(twoPow256Sub1) {\n\t\treturn z.fromDecimal(s)\n\t}\n\tif len(s) == len(twoPow256Sub1) {\n\t\tif s \u003e twoPow256Sub1 {\n\t\t\treturn ErrBig256Range\n\t\t}\n\t\treturn z.fromDecimal(s)\n\t}\n\treturn ErrBig256Range\n}\n\n// FromDecimal is a convenience-constructor to create an Uint from a\n// decimal (base 10) string. Numbers larger than 256 bits are not accepted.\nfunc FromDecimal(decimal string) (*Uint, error) {\n\tvar z Uint\n\tif err := z.SetFromDecimal(decimal); err != nil {\n\t\treturn nil, err\n\t}\n\treturn \u0026z, nil\n}\n\n// MustFromDecimal is a convenience-constructor to create an Uint from a\n// decimal (base 10) string.\n// Returns a new Uint and panics if any error occurred.\nfunc MustFromDecimal(decimal string) *Uint {\n\tvar z Uint\n\tif err := z.SetFromDecimal(decimal); err != nil {\n\t\tpanic(err)\n\t}\n\treturn \u0026z\n}\n\n// multipliers holds the values that are needed for fromDecimal\nvar multipliers = [5]*Uint{\n\tnil, // represents first round, no multiplication needed\n\t{[4]uint64{10000000000000000000, 0, 0, 0}}, // 10 ^ 19\n\t{[4]uint64{687399551400673280, 5421010862427522170, 0, 0}}, // 10 ^ 38\n\t{[4]uint64{5332261958806667264, 17004971331911604867, 2938735877055718769, 0}}, // 10 ^ 57\n\t{[4]uint64{0, 8607968719199866880, 532749306367912313, 1593091911132452277}}, // 10 ^ 76\n}\n\n// fromDecimal is a helper function to only ever be called via SetFromDecimal\n// this function takes a string and chunks it up, calling ParseUint on it up to 5 times\n// these chunks are then multiplied by the proper power of 10, then added together.\nfunc (z *Uint) fromDecimal(bs string) error {\n\t// first clear the input\n\tz.Clear()\n\t// the maximum value of uint64 is 18446744073709551615, which is 20 characters\n\t// one less means that a string of 19 9's is always within the uint64 limit\n\tvar (\n\t\tnum uint64\n\t\terr error\n\t\tremaining = len(bs)\n\t)\n\tif remaining == 0 {\n\t\treturn errors.New(\"EOF\")\n\t}\n\t// We proceed in steps of 19 characters (nibbles), from least significant to most significant.\n\t// This means that the first (up to) 19 characters do not need to be multiplied.\n\t// In the second iteration, our slice of 19 characters needs to be multipleied\n\t// by a factor of 10^19. Et cetera.\n\tfor i, mult := range multipliers {\n\t\tif remaining \u003c= 0 {\n\t\t\treturn nil // Done\n\t\t} else if remaining \u003e 19 {\n\t\t\tnum, err = strconv.ParseUint(bs[remaining-19:remaining], 10, 64)\n\t\t} else {\n\t\t\t// Final round\n\t\t\tnum, err = strconv.ParseUint(bs, 10, 64)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t// add that number to our running total\n\t\tif i == 0 {\n\t\t\tz.SetUint64(num)\n\t\t} else {\n\t\t\tbase := NewUint(num)\n\t\t\tz.Add(z, base.Mul(base, mult))\n\t\t}\n\t\t// Chop off another 19 characters\n\t\tif remaining \u003e 19 {\n\t\t\tbs = bs[0 : remaining-19]\n\t\t}\n\t\tremaining -= 19\n\t}\n\treturn nil\n}\n\n// Byte sets z to the value of the byte at position n,\n// with 'z' considered as a big-endian 32-byte integer\n// if 'n' \u003e 32, f is set to 0\n// Example: f = '5', n=31 =\u003e 5\nfunc (z *Uint) Byte(n *Uint) *Uint {\n\t// in z, z.arr[0] is the least significant\n\tif number, overflow := n.Uint64WithOverflow(); !overflow {\n\t\tif number \u003c 32 {\n\t\t\tnumber := z.arr[4-1-number/8]\n\t\t\toffset := (n.arr[0] \u0026 0x7) \u003c\u003c 3 // 8*(n.d % 8)\n\t\t\tz.arr[0] = (number \u0026 (0xff00000000000000 \u003e\u003e offset)) \u003e\u003e (56 - offset)\n\t\t\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\t\t\treturn z\n\t\t}\n\t}\n\n\treturn z.Clear()\n}\n\n// BitLen returns the number of bits required to represent z\nfunc (z *Uint) BitLen() int {\n\tswitch {\n\tcase z.arr[3] != 0:\n\t\treturn 192 + bits.Len64(z.arr[3])\n\tcase z.arr[2] != 0:\n\t\treturn 128 + bits.Len64(z.arr[2])\n\tcase z.arr[1] != 0:\n\t\treturn 64 + bits.Len64(z.arr[1])\n\tdefault:\n\t\treturn bits.Len64(z.arr[0])\n\t}\n}\n\n// ByteLen returns the number of bytes required to represent z\nfunc (z *Uint) ByteLen() int {\n\treturn (z.BitLen() + 7) / 8\n}\n\n// Clear sets z to 0\nfunc (z *Uint) Clear() *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, 0, 0, 0\n\treturn z\n}\n\nconst (\n\t// hextable = \"0123456789abcdef\"\n\tbintable = \"\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\a\\b\\t\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\n\\v\\f\\r\\x0e\\x0f\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\n\\v\\f\\r\\x0e\\x0f\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\"\n\tbadNibble = 0xff\n)\n\n// SetFromHex sets z from the given string, interpreted as a hexadecimal number.\n// OBS! This method is _not_ strictly identical to the (*big.Int).SetString(..., 16) method.\n// Notable differences:\n// - This method _require_ \"0x\" or \"0X\" prefix.\n// - This method does not accept zero-prefixed hex, e.g. \"0x0001\"\n// - This method does not accept underscore input, e.g. \"100_000\",\n// - This method does not accept negative zero as valid, e.g \"-0x0\",\n// - (this method does not accept any negative input as valid)\nfunc (z *Uint) SetFromHex(hex string) error {\n\treturn z.fromHex(hex)\n}\n\n// fromHex is the internal implementation of parsing a hex-string.\nfunc (z *Uint) fromHex(hex string) error {\n\tif err := checkNumberS(hex); err != nil {\n\t\treturn err\n\t}\n\tif len(hex) \u003e 66 {\n\t\treturn ErrBig256Range\n\t}\n\tz.Clear()\n\tend := len(hex)\n\tfor i := 0; i \u003c 4; i++ {\n\t\tstart := end - 16\n\t\tif start \u003c 2 {\n\t\t\tstart = 2\n\t\t}\n\t\tfor ri := start; ri \u003c end; ri++ {\n\t\t\tnib := bintable[hex[ri]]\n\t\t\tif nib == badNibble {\n\t\t\t\treturn ErrSyntax\n\t\t\t}\n\t\t\tz.arr[i] = z.arr[i] \u003c\u003c 4\n\t\t\tz.arr[i] += uint64(nib)\n\t\t}\n\t\tend = start\n\t}\n\treturn nil\n}\n\n// FromHex is a convenience-constructor to create an Uint from\n// a hexadecimal string. The string is required to be '0x'-prefixed\n// Numbers larger than 256 bits are not accepted.\nfunc FromHex(hex string) (*Uint, error) {\n\tvar z Uint\n\tif err := z.fromHex(hex); err != nil {\n\t\treturn nil, err\n\t}\n\treturn \u0026z, nil\n}\n\n// MustFromHex is a convenience-constructor to create an Uint from\n// a hexadecimal string.\n// Returns a new Uint and panics if any error occurred.\nfunc MustFromHex(hex string) *Uint {\n\tvar z Uint\n\tif err := z.fromHex(hex); err != nil {\n\t\tpanic(err)\n\t}\n\treturn \u0026z\n}\n\n// Clone creates a new Uint identical to z\nfunc (z *Uint) Clone() *Uint {\n\tvar x Uint\n\tx.arr[0] = z.arr[0]\n\tx.arr[1] = z.arr[1]\n\tx.arr[2] = z.arr[2]\n\tx.arr[3] = z.arr[3]\n\n\treturn \u0026x\n}\n"},{"name":"uint256_test.gno","body":"package uint256\n\nimport (\n\t\"testing\"\n)\n\nfunc TestSetAllOne(t *testing.T) {\n\tz := Zero()\n\tz.SetAllOne()\n\tif z.ToString() != twoPow256Sub1 {\n\t\tt.Errorf(\"Expected all ones, got %s\", z.ToString())\n\t}\n}\n\nfunc TestByte(t *testing.T) {\n\ttests := []struct {\n\t\tinput string\n\t\tposition uint64\n\t\texpected byte\n\t}{\n\t\t{\"0x1000000000000000000000000000000000000000000000000000000000000000\", 0, 16},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 0, 255},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 31, 255},\n\t}\n\n\tfor i, tt := range tests {\n\t\tz, _ := FromHex(tt.input)\n\t\tn := NewUint(tt.position)\n\t\tresult := z.Byte(n)\n\n\t\tif result.arr[0] != uint64(tt.expected) {\n\t\t\tt.Errorf(\"Test case %d failed. Input: %s, Position: %d, Expected: %d, Got: %d\",\n\t\t\t\ti, tt.input, tt.position, tt.expected, result.arr[0])\n\t\t}\n\n\t\t// check other array elements are 0\n\t\tif result.arr[1] != 0 || result.arr[2] != 0 || result.arr[3] != 0 {\n\t\t\tt.Errorf(\"Test case %d failed. Non-zero values in upper bytes\", i)\n\t\t}\n\t}\n\n\t// overflow\n\tz, _ := FromHex(\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\")\n\tn := NewUint(32)\n\tresult := z.Byte(n)\n\n\tif !result.IsZero() {\n\t\tt.Errorf(\"Expected zero for position \u003e= 32, got %v\", result)\n\t}\n}\n\nfunc TestBitLen(t *testing.T) {\n\ttests := []struct {\n\t\tinput string\n\t\texpected int\n\t}{\n\t\t{\"0x0\", 0},\n\t\t{\"0x1\", 1},\n\t\t{\"0xff\", 8},\n\t\t{\"0x100\", 9},\n\t\t{\"0xffff\", 16},\n\t\t{\"0x10000\", 17},\n\t\t{\"0xffffffffffffffff\", 64},\n\t\t{\"0x10000000000000000\", 65},\n\t\t{\"0xffffffffffffffffffffffffffffffff\", 128},\n\t\t{\"0x100000000000000000000000000000000\", 129},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 256},\n\t}\n\n\tfor i, tt := range tests {\n\t\tz, _ := FromHex(tt.input)\n\t\tresult := z.BitLen()\n\n\t\tif result != tt.expected {\n\t\t\tt.Errorf(\"Test case %d failed. Input: %s, Expected: %d, Got: %d\",\n\t\t\t\ti, tt.input, tt.expected, result)\n\t\t}\n\t}\n}\n\nfunc TestByteLen(t *testing.T) {\n\ttests := []struct {\n\t\tinput string\n\t\texpected int\n\t}{\n\t\t{\"0x0\", 0},\n\t\t{\"0x1\", 1},\n\t\t{\"0xff\", 1},\n\t\t{\"0x100\", 2},\n\t\t{\"0xffff\", 2},\n\t\t{\"0x10000\", 3},\n\t\t{\"0xffffffffffffffff\", 8},\n\t\t{\"0x10000000000000000\", 9},\n\t\t{\"0xffffffffffffffffffffffffffffffff\", 16},\n\t\t{\"0x100000000000000000000000000000000\", 17},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 32},\n\t}\n\n\tfor i, tt := range tests {\n\t\tz, _ := FromHex(tt.input)\n\t\tresult := z.ByteLen()\n\n\t\tif result != tt.expected {\n\t\t\tt.Errorf(\"Test case %d failed. Input: %s, Expected: %d, Got: %d\",\n\t\t\t\ti, tt.input, tt.expected, result)\n\t\t}\n\t}\n}\n\nfunc TestClone(t *testing.T) {\n\ttests := []struct {\n\t\tinput string\n\t\texpected string\n\t}{\n\t\t{\"0x1\", \"1\"},\n\t\t{\"0x100\", \"256\"},\n\t\t{\"0x10000000000000000\", \"18446744073709551616\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tz, _ := FromHex(tt.input)\n\t\tresult := z.Clone()\n\t\tif result.ToString() != tt.expected {\n\t\t\tt.Errorf(\"Test %s failed. Expected %s, got %s\", tt.input, tt.expected, result.ToString())\n\t\t}\n\t}\n}\n"},{"name":"utils.gno","body":"package uint256\n\nfunc checkNumberS(input string) error {\n\tconst fn = \"UnmarshalText\"\n\tl := len(input)\n\tif l == 0 {\n\t\treturn errEmptyString(fn, input)\n\t}\n\tif l \u003c 2 || input[0] != '0' ||\n\t\t(input[1] != 'x' \u0026\u0026 input[1] != 'X') {\n\t\treturn errMissingPrefix(fn, input)\n\t}\n\tif l == 2 {\n\t\treturn errEmptyNumber(fn, input)\n\t}\n\tif len(input) \u003e 3 \u0026\u0026 input[2] == '0' {\n\t\treturn errLeadingZero(fn, input)\n\t}\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_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"int256","path":"gno.land/p/demo/int256","files":[{"name":"LICENSE","body":"MIT License\n\nCopyright (c) 2023 Trịnh Đức Bảo Linh(Kevin)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."},{"name":"README.md","body":"# Fixed size signed 256-bit math library\n\n1. This is a library specialized at replacing the big.Int library for math based on signed 256-bit types.\n2. It uses [uint256](https://github.com/gnolang/gno/tree/master/examples/gno.land/p/demo/uint256) as the underlying type.\n\nported from [mempooler/int256](https://github.com/mempooler/int256)\n"},{"name":"absolute.gno","body":"package int256\n\nimport \"gno.land/p/demo/uint256\"\n\n// Abs returns |z|\nfunc (z *Int) Abs() *uint256.Uint {\n\treturn z.abs.Clone()\n}\n\n// AbsGt returns true if |z| \u003e x, where x is a uint256\nfunc (z *Int) AbsGt(x *uint256.Uint) bool {\n\treturn z.abs.Gt(x)\n}\n\n// AbsLt returns true if |z| \u003c x, where x is a uint256\nfunc (z *Int) AbsLt(x *uint256.Uint) bool {\n\treturn z.abs.Lt(x)\n}\n"},{"name":"absolute_test.gno","body":"package int256\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uint256\"\n)\n\nfunc TestAbs(t *testing.T) {\n\ttests := []struct {\n\t\tx, want string\n\t}{\n\t\t{\"0\", \"0\"},\n\t\t{\"1\", \"1\"},\n\t\t{\"-1\", \"1\"},\n\t\t{\"-2\", \"2\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.Abs()\n\n\t\tif got.ToString() != tc.want {\n\t\t\tt.Errorf(\"Abs(%s) = %v, want %v\", tc.x, got.ToString(), tc.want)\n\t\t}\n\t}\n}\n\nfunc TestAbsGt(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"0\", \"false\"},\n\t\t{\"1\", \"0\", \"true\"},\n\t\t{\"-1\", \"0\", \"true\"},\n\t\t{\"-1\", \"1\", \"false\"},\n\t\t{\"-2\", \"1\", \"true\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"0\", \"true\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"1\", \"true\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"false\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := uint256.FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.AbsGt(y)\n\n\t\tif got != (tc.want == \"true\") {\n\t\t\tt.Errorf(\"AbsGt(%s, %s) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestAbsLt(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"0\", \"false\"},\n\t\t{\"1\", \"0\", \"false\"},\n\t\t{\"-1\", \"0\", \"false\"},\n\t\t{\"-1\", \"1\", \"false\"},\n\t\t{\"-2\", \"1\", \"false\"},\n\t\t{\"-5\", \"10\", \"true\"},\n\t\t{\"31330\", \"31337\", \"true\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"0\", \"false\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"1\", \"false\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"false\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := uint256.FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.AbsLt(y)\n\n\t\tif got != (tc.want == \"true\") {\n\t\t\tt.Errorf(\"AbsLt(%s, %s) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n"},{"name":"arithmetic.gno","body":"package int256\n\nimport \"gno.land/p/demo/uint256\"\n\nfunc (z *Int) Add(x, y *Int) *Int {\n\tz.initiateAbs()\n\n\tif x.neg == y.neg {\n\t\t// If both numbers have the same sign, add their absolute values\n\t\tz.abs.Add(x.abs, y.abs)\n\t\tz.neg = x.neg\n\t} else {\n\t\tswitch x.abs.Cmp(y.abs) {\n\t\tcase 1: // x \u003e y\n\t\t\tz.abs.Sub(x.abs, y.abs)\n\t\t\tz.neg = x.neg\n\t\tcase -1: // x \u003c y\n\t\t\tz.abs.Sub(y.abs, x.abs)\n\t\t\tz.neg = y.neg\n\t\tcase 0: // x == y\n\t\t\tz.abs = uint256.NewUint(0)\n\t\t}\n\t}\n\n\treturn z\n}\n\n// AddUint256 set z to the sum x + y, where y is a uint256, and returns z\nfunc (z *Int) AddUint256(x *Int, y *uint256.Uint) *Int {\n\tif x.neg {\n\t\tif x.abs.Gt(y) {\n\t\t\tz.abs.Sub(x.abs, y)\n\t\t\tz.neg = true\n\t\t} else {\n\t\t\tz.abs.Sub(y, x.abs)\n\t\t\tz.neg = false\n\t\t}\n\t} else {\n\t\tz.abs.Add(x.abs, y)\n\t\tz.neg = false\n\t}\n\treturn z\n}\n\n// Sets z to the sum x + y, where z and x are uint256s and y is an int256.\nfunc AddDelta(z, x *uint256.Uint, y *Int) {\n\tif y.neg {\n\t\tz.Sub(x, y.abs)\n\t} else {\n\t\tz.Add(x, y.abs)\n\t}\n}\n\n// Sets z to the sum x + y, where z and x are uint256s and y is an int256.\nfunc AddDeltaOverflow(z, x *uint256.Uint, y *Int) bool {\n\tvar overflow bool\n\tif y.neg {\n\t\t_, overflow = z.SubOverflow(x, y.abs)\n\t} else {\n\t\t_, overflow = z.AddOverflow(x, y.abs)\n\t}\n\treturn overflow\n}\n\n// Sub sets z to the difference x-y and returns z.\nfunc (z *Int) Sub(x, y *Int) *Int {\n\tz.initiateAbs()\n\n\tif x.neg != y.neg {\n\t\t// If sign are different, add the absolute values\n\t\tz.abs.Add(x.abs, y.abs)\n\t\tz.neg = x.neg\n\t} else {\n\t\tswitch x.abs.Cmp(y.abs) {\n\t\tcase 1: // x \u003e y\n\t\t\tz.abs.Sub(x.abs, y.abs)\n\t\t\tz.neg = x.neg\n\t\tcase -1: // x \u003c y\n\t\t\tz.abs.Sub(y.abs, x.abs)\n\t\t\tz.neg = !x.neg\n\t\tcase 0: // x == y\n\t\t\tz.abs = uint256.NewUint(0)\n\t\t}\n\t}\n\n\t// Ensure zero is always positive\n\tif z.abs.IsZero() {\n\t\tz.neg = false\n\t}\n\treturn z\n}\n\n// SubUint256 set z to the difference x - y, where y is a uint256, and returns z\nfunc (z *Int) SubUint256(x *Int, y *uint256.Uint) *Int {\n\tif x.neg {\n\t\tz.abs.Add(x.abs, y)\n\t\tz.neg = true\n\t} else {\n\t\tif x.abs.Lt(y) {\n\t\t\tz.abs.Sub(y, x.abs)\n\t\t\tz.neg = true\n\t\t} else {\n\t\t\tz.abs.Sub(x.abs, y)\n\t\t\tz.neg = false\n\t\t}\n\t}\n\treturn z\n}\n\n// Mul sets z to the product x*y and returns z.\nfunc (z *Int) Mul(x, y *Int) *Int {\n\tz.initiateAbs()\n\n\tz.abs = z.abs.Mul(x.abs, y.abs)\n\tz.neg = x.neg != y.neg \u0026\u0026 !z.abs.IsZero() // 0 has no sign\n\treturn z\n}\n\n// MulUint256 sets z to the product x*y, where y is a uint256, and returns z\nfunc (z *Int) MulUint256(x *Int, y *uint256.Uint) *Int {\n\tz.abs.Mul(x.abs, y)\n\tif z.abs.IsZero() {\n\t\tz.neg = false\n\t} else {\n\t\tz.neg = x.neg\n\t}\n\treturn z\n}\n\n// Div sets z to the quotient x/y for y != 0 and returns z.\nfunc (z *Int) Div(x, y *Int) *Int {\n\tz.initiateAbs()\n\n\tif y.abs.IsZero() {\n\t\tpanic(\"division by zero\")\n\t}\n\n\tz.abs.Div(x.abs, y.abs)\n\tz.neg = (x.neg != y.neg) \u0026\u0026 !z.abs.IsZero() // 0 has no sign\n\n\treturn z\n}\n\n// DivUint256 sets z to the quotient x/y, where y is a uint256, and returns z\n// If y == 0, z is set to 0\nfunc (z *Int) DivUint256(x *Int, y *uint256.Uint) *Int {\n\tz.abs.Div(x.abs, y)\n\tif z.abs.IsZero() {\n\t\tz.neg = false\n\t} else {\n\t\tz.neg = x.neg\n\t}\n\treturn z\n}\n\n// Quo sets z to the quotient x/y for y != 0 and returns z.\n// If y == 0, a division-by-zero run-time panic occurs.\n// OBS: differs from mempooler int256, we need to panic manually if y == 0\n// Quo implements truncated division (like Go); see QuoRem for more details.\nfunc (z *Int) Quo(x, y *Int) *Int {\n\tif y.IsZero() {\n\t\tpanic(\"division by zero\")\n\t}\n\n\tz.initiateAbs()\n\n\tz.abs = z.abs.Div(x.abs, y.abs)\n\tz.neg = !(z.abs.IsZero()) \u0026\u0026 x.neg != y.neg // 0 has no sign\n\treturn z\n}\n\n// Rem sets z to the remainder x%y for y != 0 and returns z.\n// If y == 0, a division-by-zero run-time panic occurs.\n// OBS: differs from mempooler int256, we need to panic manually if y == 0\n// Rem implements truncated modulus (like Go); see QuoRem for more details.\nfunc (z *Int) Rem(x, y *Int) *Int {\n\tif y.IsZero() {\n\t\tpanic(\"division by zero\")\n\t}\n\n\tz.initiateAbs()\n\n\tz.abs.Mod(x.abs, y.abs)\n\tz.neg = z.abs.Sign() \u003e 0 \u0026\u0026 x.neg // 0 has no sign\n\treturn z\n}\n\n// Mod sets z to the modulus x%y for y != 0 and returns z.\n// If y == 0, z is set to 0 (OBS: differs from the big.Int)\nfunc (z *Int) Mod(x, y *Int) *Int {\n\tif x.neg {\n\t\tz.abs.Div(x.abs, y.abs)\n\t\tz.abs.Add(z.abs, one)\n\t\tz.abs.Mul(z.abs, y.abs)\n\t\tz.abs.Sub(z.abs, x.abs)\n\t\tz.abs.Mod(z.abs, y.abs)\n\t} else {\n\t\tz.abs.Mod(x.abs, y.abs)\n\t}\n\tz.neg = false\n\treturn z\n}\n"},{"name":"arithmetic_test.gno","body":"package int256\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uint256\"\n)\n\nfunc TestAdd(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"1\", \"1\"},\n\t\t{\"1\", \"0\", \"1\"},\n\t\t{\"1\", \"1\", \"2\"},\n\t\t{\"1\", \"2\", \"3\"},\n\t\t// NEGATIVE\n\t\t{\"-1\", \"1\", \"0\"},\n\t\t{\"1\", \"-1\", \"0\"},\n\t\t{\"3\", \"-3\", \"0\"},\n\t\t{\"-1\", \"-1\", \"-2\"},\n\t\t{\"-1\", \"-2\", \"-3\"},\n\t\t{\"-1\", \"3\", \"2\"},\n\t\t{\"3\", \"-1\", \"2\"},\n\t\t// OVERFLOW\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"1\", \"0\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.Add(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Add(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestAddUint256(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"1\", \"1\"},\n\t\t{\"1\", \"0\", \"1\"},\n\t\t{\"1\", \"1\", \"2\"},\n\t\t{\"1\", \"2\", \"3\"},\n\t\t{\"-1\", \"1\", \"0\"},\n\t\t{\"-1\", \"3\", \"2\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639934\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"1\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"115792089237316195423570985008687907853269984665640564039457584007913129639934\", \"-1\"},\n\t\t// OVERFLOW\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"0\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := uint256.FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.AddUint256(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"AddUint256(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestAddDelta(t *testing.T) {\n\ttests := []struct {\n\t\tz, x, y, want string\n\t}{\n\t\t{\"0\", \"0\", \"0\", \"0\"},\n\t\t{\"0\", \"0\", \"1\", \"1\"},\n\t\t{\"0\", \"1\", \"0\", \"1\"},\n\t\t{\"0\", \"1\", \"1\", \"2\"},\n\t\t{\"1\", \"2\", \"3\", \"5\"},\n\t\t{\"5\", \"10\", \"-3\", \"7\"},\n\t\t// underflow\n\t\t{\"1\", \"2\", \"-3\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tz, err := uint256.FromDecimal(tc.z)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tx, err := uint256.FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := uint256.FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tAddDelta(z, x, y)\n\n\t\tif z.Neq(want) {\n\t\t\tt.Errorf(\"AddDelta(%s, %s, %s) = %v, want %v\", tc.z, tc.x, tc.y, z.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestAddDeltaOverflow(t *testing.T) {\n\ttests := []struct {\n\t\tz, x, y string\n\t\twant bool\n\t}{\n\t\t{\"0\", \"0\", \"0\", false},\n\t\t// underflow\n\t\t{\"1\", \"2\", \"-3\", true},\n\t}\n\n\tfor _, tc := range tests {\n\t\tz, err := uint256.FromDecimal(tc.z)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tx, err := uint256.FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tresult := AddDeltaOverflow(z, x, y)\n\t\tif result != tc.want {\n\t\t\tt.Errorf(\"AddDeltaOverflow(%s, %s, %s) = %v, want %v\", tc.z, tc.x, tc.y, result, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestSub(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"1\", \"0\", \"1\"},\n\t\t{\"1\", \"1\", \"0\"},\n\t\t{\"-1\", \"1\", \"-2\"},\n\t\t{\"1\", \"-1\", \"2\"},\n\t\t{\"-1\", \"-1\", \"0\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"0\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"0\", \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\"},\n\t\t{x: \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", y: \"1\", want: \"0\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.Sub(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Sub(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestSubUint256(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"1\", \"-1\"},\n\t\t{\"1\", \"0\", \"1\"},\n\t\t{\"1\", \"1\", \"0\"},\n\t\t{\"1\", \"2\", \"-1\"},\n\t\t{\"-1\", \"1\", \"-2\"},\n\t\t{\"-1\", \"3\", \"-4\"},\n\t\t// underflow\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"1\", \"-0\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"2\", \"-1\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"3\", \"-2\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := uint256.FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.SubUint256(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"SubUint256(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestMul(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"5\", \"3\", \"15\"},\n\t\t{\"-5\", \"3\", \"-15\"},\n\t\t{\"5\", \"-3\", \"-15\"},\n\t\t{\"0\", \"3\", \"0\"},\n\t\t{\"3\", \"0\", \"0\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.Mul(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Mul(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestMulUint256(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"1\", \"0\"},\n\t\t{\"1\", \"0\", \"0\"},\n\t\t{\"1\", \"1\", \"1\"},\n\t\t{\"1\", \"2\", \"2\"},\n\t\t{\"-1\", \"1\", \"-1\"},\n\t\t{\"-1\", \"3\", \"-3\"},\n\t\t{\"3\", \"4\", \"12\"},\n\t\t{\"-3\", \"4\", \"-12\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639934\", \"2\", \"-115792089237316195423570985008687907853269984665640564039457584007913129639932\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639934\", \"2\", \"115792089237316195423570985008687907853269984665640564039457584007913129639932\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := uint256.FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.MulUint256(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"MulUint256(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestDiv(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, expected string\n\t}{\n\t\t{\"1\", \"1\", \"1\"},\n\t\t{\"0\", \"1\", \"0\"},\n\t\t{\"-1\", \"1\", \"-1\"},\n\t\t{\"1\", \"-1\", \"-1\"},\n\t\t{\"-1\", \"-1\", \"1\"},\n\t\t{\"-6\", \"3\", \"-2\"},\n\t\t{\"10\", \"-2\", \"-5\"},\n\t\t{\"-10\", \"3\", \"-3\"},\n\t\t{\"7\", \"3\", \"2\"},\n\t\t{\"-7\", \"3\", \"-2\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"2\", \"57896044618658097711785492504343953926634992332820282019728792003956564819967\"}, // Max uint256 / 2\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.x+\"/\"+tt.y, func(t *testing.T) {\n\t\t\tx := MustFromDecimal(tt.x)\n\t\t\ty := MustFromDecimal(tt.y)\n\t\t\tresult := Zero().Div(x, y)\n\t\t\tif result.ToString() != tt.expected {\n\t\t\t\tt.Errorf(\"Div(%s, %s) = %s, want %s\", tt.x, tt.y, result.ToString(), tt.expected)\n\t\t\t}\n\t\t\tif result.abs.IsZero() \u0026\u0026 result.neg {\n\t\t\t\tt.Errorf(\"Div(%s, %s) resulted in negative zero\", tt.x, tt.y)\n\t\t\t}\n\t\t})\n\t}\n\n\tt.Run(\"Division by zero\", func(t *testing.T) {\n\t\tdefer func() {\n\t\t\tif r := recover(); r == nil {\n\t\t\t\tt.Errorf(\"Div(1, 0) did not panic\")\n\t\t\t}\n\t\t}()\n\t\tx := MustFromDecimal(\"1\")\n\t\ty := MustFromDecimal(\"0\")\n\t\tZero().Div(x, y)\n\t})\n}\n\nfunc TestDivUint256(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"1\", \"0\"},\n\t\t{\"1\", \"0\", \"0\"},\n\t\t{\"1\", \"1\", \"1\"},\n\t\t{\"1\", \"2\", \"0\"},\n\t\t{\"-1\", \"1\", \"-1\"},\n\t\t{\"-1\", \"3\", \"0\"},\n\t\t{\"4\", \"3\", \"1\"},\n\t\t{\"25\", \"5\", \"5\"},\n\t\t{\"25\", \"4\", \"6\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639934\", \"2\", \"-57896044618658097711785492504343953926634992332820282019728792003956564819967\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639934\", \"2\", \"57896044618658097711785492504343953926634992332820282019728792003956564819967\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := uint256.FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.DivUint256(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"DivUint256(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestQuo(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"1\", \"0\"},\n\t\t{\"0\", \"-1\", \"0\"},\n\t\t{\"10\", \"1\", \"10\"},\n\t\t{\"10\", \"-1\", \"-10\"},\n\t\t{\"-10\", \"1\", \"-10\"},\n\t\t{\"-10\", \"-1\", \"10\"},\n\t\t{\"10\", \"-3\", \"-3\"},\n\t\t{\"10\", \"3\", \"3\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.Quo(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Quo(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestRem(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"1\", \"0\"},\n\t\t{\"0\", \"-1\", \"0\"},\n\t\t{\"10\", \"1\", \"0\"},\n\t\t{\"10\", \"-1\", \"0\"},\n\t\t{\"-10\", \"1\", \"0\"},\n\t\t{\"-10\", \"-1\", \"0\"},\n\t\t{\"10\", \"3\", \"1\"},\n\t\t{\"10\", \"-3\", \"1\"},\n\t\t{\"-10\", \"3\", \"-1\"},\n\t\t{\"-10\", \"-3\", \"-1\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.Rem(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Rem(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestMod(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"1\", \"0\"},\n\t\t{\"0\", \"-1\", \"0\"},\n\t\t{\"10\", \"0\", \"0\"},\n\t\t{\"10\", \"1\", \"0\"},\n\t\t{\"10\", \"-1\", \"0\"},\n\t\t{\"-10\", \"0\", \"0\"},\n\t\t{\"-10\", \"1\", \"0\"},\n\t\t{\"-10\", \"-1\", \"0\"},\n\t\t{\"10\", \"3\", \"1\"},\n\t\t{\"10\", \"-3\", \"1\"},\n\t\t{\"-10\", \"3\", \"2\"},\n\t\t{\"-10\", \"-3\", \"2\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.Mod(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Mod(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n"},{"name":"bitwise.gno","body":"package int256\n\nimport (\n\t\"gno.land/p/demo/uint256\"\n)\n\n// Or sets z = x | y and returns z.\nfunc (z *Int) Or(x, y *Int) *Int {\n\tif x.neg == y.neg {\n\t\tif x.neg {\n\t\t\t// (-x) | (-y) == ^(x-1) | ^(y-1) == ^((x-1) \u0026 (y-1)) == -(((x-1) \u0026 (y-1)) + 1)\n\t\t\tx1 := new(uint256.Uint).Sub(x.abs, one)\n\t\t\ty1 := new(uint256.Uint).Sub(y.abs, one)\n\t\t\tz.abs = z.abs.Add(z.abs.And(x1, y1), one)\n\t\t\tz.neg = true // z cannot be zero if x and y are negative\n\t\t\treturn z\n\t\t}\n\n\t\t// x | y == x | y\n\t\tz.abs = z.abs.Or(x.abs, y.abs)\n\t\tz.neg = false\n\t\treturn z\n\t}\n\n\t// x.neg != y.neg\n\tif x.neg {\n\t\tx, y = y, x // | is symmetric\n\t}\n\n\t// x | (-y) == x | ^(y-1) == ^((y-1) \u0026^ x) == -(^((y-1) \u0026^ x) + 1)\n\ty1 := new(uint256.Uint).Sub(y.abs, one)\n\tz.abs = z.abs.Add(z.abs.AndNot(y1, x.abs), one)\n\tz.neg = true // z cannot be zero if one of x or y is negative\n\n\treturn z\n}\n\n// And sets z = x \u0026 y and returns z.\nfunc (z *Int) And(x, y *Int) *Int {\n\tif x.neg == y.neg {\n\t\tif x.neg {\n\t\t\t// (-x) \u0026 (-y) == ^(x-1) \u0026 ^(y-1) == ^((x-1) | (y-1)) == -(((x-1) | (y-1)) + 1)\n\t\t\tx1 := new(uint256.Uint).Sub(x.abs, one)\n\t\t\ty1 := new(uint256.Uint).Sub(y.abs, one)\n\t\t\tz.abs = z.abs.Add(z.abs.Or(x1, y1), one)\n\t\t\tz.neg = true // z cannot be zero if x and y are negative\n\t\t\treturn z\n\t\t}\n\n\t\t// x \u0026 y == x \u0026 y\n\t\tz.abs = z.abs.And(x.abs, y.abs)\n\t\tz.neg = false\n\t\treturn z\n\t}\n\n\t// x.neg != y.neg\n\t// REF: https://cs.opensource.google/go/go/+/refs/tags/go1.22.1:src/math/big/int.go;l=1192-1202;drc=d57303e65f00b84b528ee682747dbe1fd3316d30\n\tif x.neg {\n\t\tx, y = y, x // \u0026 is symmetric\n\t}\n\n\t// x \u0026 (-y) == x \u0026 ^(y-1) == x \u0026^ (y-1)\n\ty1 := new(uint256.Uint).Sub(y.abs, uint256.One())\n\tz.abs = z.abs.AndNot(x.abs, y1)\n\tz.neg = false\n\treturn z\n}\n\n// Rsh sets z = x \u003e\u003e n and returns z.\n// OBS: Different from original implementation it was using math.Big\nfunc (z *Int) Rsh(x *Int, n uint) *Int {\n\tif !x.neg {\n\t\tz.abs.Rsh(x.abs, n)\n\t\tz.neg = x.neg\n\t\treturn z\n\t}\n\n\t// REF: https://cs.opensource.google/go/go/+/refs/tags/go1.22.1:src/math/big/int.go;l=1118-1126;drc=d57303e65f00b84b528ee682747dbe1fd3316d30\n\tt := NewInt(0).Sub(FromUint256(x.abs), NewInt(1))\n\tt = t.Rsh(t, n)\n\n\t_tmp := t.Add(t, NewInt(1))\n\tz.abs = _tmp.Abs()\n\tz.neg = true\n\n\treturn z\n}\n\n// Lsh sets z = x \u003c\u003c n and returns z.\nfunc (z *Int) Lsh(x *Int, n uint) *Int {\n\tz.abs.Lsh(x.abs, n)\n\tz.neg = x.neg\n\treturn z\n}\n"},{"name":"bitwise_test.gno","body":"package int256\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uint256\"\n)\n\nfunc TestOr(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tx, y, want Int\n\t}{\n\t\t{\n\t\t\tname: \"all zeroes\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t},\n\t\t{\n\t\t\tname: \"all ones\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand all ones\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := New()\n\t\t\tgot.Or(\u0026tc.x, \u0026tc.y)\n\n\t\t\tif got.Neq(\u0026tc.want) {\n\t\t\t\tt.Errorf(\"Or(%v, %v) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestAnd(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tx, y, want Int\n\t}{\n\t\t{\n\t\t\tname: \"all zeroes\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t},\n\t\t{\n\t\t\tname: \"all ones\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 2\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 3\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand zero\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand all ones\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}}, neg: false},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := New()\n\t\t\tgot.And(\u0026tc.x, \u0026tc.y)\n\n\t\t\tif got.Neq(\u0026tc.want) {\n\t\t\t\tt.Errorf(\"And(%v, %v) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestRsh(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\tn uint\n\t\twant string\n\t}{\n\t\t{\"1024\", 0, \"1024\"},\n\t\t{\"1024\", 1, \"512\"},\n\t\t{\"1024\", 2, \"256\"},\n\t\t{\"1024\", 10, \"1\"},\n\t\t{\"1024\", 11, \"0\"},\n\t\t{\"18446744073709551615\", 0, \"18446744073709551615\"},\n\t\t{\"18446744073709551615\", 1, \"9223372036854775807\"},\n\t\t{\"18446744073709551615\", 62, \"3\"},\n\t\t{\"18446744073709551615\", 63, \"1\"},\n\t\t{\"18446744073709551615\", 64, \"0\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", 0, \"115792089237316195423570985008687907853269984665640564039457584007913129639935\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", 1, \"57896044618658097711785492504343953926634992332820282019728792003956564819967\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", 128, \"340282366920938463463374607431768211455\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", 255, \"1\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", 256, \"0\"},\n\t\t{\"-1024\", 0, \"-1024\"},\n\t\t{\"-1024\", 1, \"-512\"},\n\t\t{\"-1024\", 2, \"-256\"},\n\t\t{\"-1024\", 10, \"-1\"},\n\t\t{\"-1024\", 10, \"-1\"},\n\t\t{\"-9223372036854775808\", 0, \"-9223372036854775808\"},\n\t\t{\"-9223372036854775808\", 1, \"-4611686018427387904\"},\n\t\t{\"-9223372036854775808\", 62, \"-2\"},\n\t\t{\"-9223372036854775808\", 63, \"-1\"},\n\t\t{\"-9223372036854775808\", 64, \"-1\"},\n\t\t{\"-57896044618658097711785492504343953926634992332820282019728792003956564819968\", 0, \"-57896044618658097711785492504343953926634992332820282019728792003956564819968\"},\n\t\t{\"-57896044618658097711785492504343953926634992332820282019728792003956564819968\", 1, \"-28948022309329048855892746252171976963317496166410141009864396001978282409984\"},\n\t\t{\"-57896044618658097711785492504343953926634992332820282019728792003956564819968\", 253, \"-4\"},\n\t\t{\"-57896044618658097711785492504343953926634992332820282019728792003956564819968\", 254, \"-2\"},\n\t\t{\"-57896044618658097711785492504343953926634992332820282019728792003956564819968\", 255, \"-1\"},\n\t\t{\"-57896044618658097711785492504343953926634992332820282019728792003956564819968\", 256, \"-1\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.Rsh(x, tc.n)\n\n\t\tif got.ToString() != tc.want {\n\t\t\tt.Errorf(\"Rsh(%s, %d) = %v, want %v\", tc.x, tc.n, got.ToString(), tc.want)\n\t\t}\n\t}\n}\n\nfunc TestLsh(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\tn uint\n\t\twant string\n\t}{\n\t\t{\"1\", 0, \"1\"},\n\t\t{\"1\", 1, \"2\"},\n\t\t{\"1\", 2, \"4\"},\n\t\t{\"2\", 0, \"2\"},\n\t\t{\"2\", 1, \"4\"},\n\t\t{\"2\", 2, \"8\"},\n\t\t{\"-2\", 0, \"-2\"},\n\t\t{\"-4\", 0, \"-4\"},\n\t\t{\"-8\", 0, \"-8\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.Lsh(x, tc.n)\n\n\t\tif got.ToString() != tc.want {\n\t\t\tt.Errorf(\"Lsh(%s, %d) = %v, want %v\", tc.x, tc.n, got.ToString(), tc.want)\n\t\t}\n\t}\n}\n"},{"name":"cmp.gno","body":"package int256\n\n// Eq returns true if z == x\nfunc (z *Int) Eq(x *Int) bool {\n\treturn (z.neg == x.neg) \u0026\u0026 z.abs.Eq(x.abs)\n}\n\n// Neq returns true if z != x\nfunc (z *Int) Neq(x *Int) bool {\n\treturn !z.Eq(x)\n}\n\n// Cmp compares x and y and returns:\n//\n//\t-1 if x \u003c y\n//\t 0 if x == y\n//\t+1 if x \u003e y\nfunc (z *Int) Cmp(x *Int) (r int) {\n\t// x cmp y == x cmp y\n\t// x cmp (-y) == x\n\t// (-x) cmp y == y\n\t// (-x) cmp (-y) == -(x cmp y)\n\tswitch {\n\tcase z == x:\n\t\t// nothing to do\n\tcase z.neg == x.neg:\n\t\tr = z.abs.Cmp(x.abs)\n\t\tif z.neg {\n\t\t\tr = -r\n\t\t}\n\tcase z.neg:\n\t\tr = -1\n\tdefault:\n\t\tr = 1\n\t}\n\treturn\n}\n\n// IsZero returns true if z == 0\nfunc (z *Int) IsZero() bool {\n\treturn z.abs.IsZero()\n}\n\n// IsNeg returns true if z \u003c 0\nfunc (z *Int) IsNeg() bool {\n\treturn z.neg\n}\n\n// Lt returns true if z \u003c x\nfunc (z *Int) Lt(x *Int) bool {\n\tif z.neg {\n\t\tif x.neg {\n\t\t\treturn z.abs.Gt(x.abs)\n\t\t} else {\n\t\t\treturn true\n\t\t}\n\t} else {\n\t\tif x.neg {\n\t\t\treturn false\n\t\t} else {\n\t\t\treturn z.abs.Lt(x.abs)\n\t\t}\n\t}\n}\n\n// Gt returns true if z \u003e x\nfunc (z *Int) Gt(x *Int) bool {\n\tif z.neg {\n\t\tif x.neg {\n\t\t\treturn z.abs.Lt(x.abs)\n\t\t} else {\n\t\t\treturn false\n\t\t}\n\t} else {\n\t\tif x.neg {\n\t\t\treturn true\n\t\t} else {\n\t\t\treturn z.abs.Gt(x.abs)\n\t\t}\n\t}\n}\n\n// Clone creates a new Int identical to z\nfunc (z *Int) Clone() *Int {\n\treturn \u0026Int{z.abs.Clone(), z.neg}\n}\n"},{"name":"cmp_test.gno","body":"package int256\n\nimport \"testing\"\n\nfunc TestEq(t *testing.T) {\n\ttests := []struct {\n\t\tx, y string\n\t\twant bool\n\t}{\n\t\t{\"0\", \"0\", true},\n\t\t{\"0\", \"1\", false},\n\t\t{\"1\", \"0\", false},\n\t\t{\"-1\", \"0\", false},\n\t\t{\"0\", \"-1\", false},\n\t\t{\"1\", \"1\", true},\n\t\t{\"-1\", \"-1\", true},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", false},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", true},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.Eq(y)\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Eq(%s, %s) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestNeq(t *testing.T) {\n\ttests := []struct {\n\t\tx, y string\n\t\twant bool\n\t}{\n\t\t{\"0\", \"0\", false},\n\t\t{\"0\", \"1\", true},\n\t\t{\"1\", \"0\", true},\n\t\t{\"-1\", \"0\", true},\n\t\t{\"0\", \"-1\", true},\n\t\t{\"1\", \"1\", false},\n\t\t{\"-1\", \"-1\", false},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", true},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", false},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.Neq(y)\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Neq(%s, %s) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestCmp(t *testing.T) {\n\ttests := []struct {\n\t\tx, y string\n\t\twant int\n\t}{\n\t\t{\"0\", \"0\", 0},\n\t\t{\"0\", \"1\", -1},\n\t\t{\"1\", \"0\", 1},\n\t\t{\"-1\", \"0\", -1},\n\t\t{\"0\", \"-1\", 1},\n\t\t{\"1\", \"1\", 0},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", 1},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.Cmp(y)\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Cmp(%s, %s) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestIsZero(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant bool\n\t}{\n\t\t{\"0\", true},\n\t\t{\"-0\", true},\n\t\t{\"1\", false},\n\t\t{\"-1\", false},\n\t\t{\"10\", false},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.IsZero()\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"IsZero(%s) = %v, want %v\", tc.x, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestIsNeg(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant bool\n\t}{\n\t\t{\"0\", false},\n\t\t{\"-0\", true}, // TODO: should this be false?\n\t\t{\"1\", false},\n\t\t{\"-1\", true},\n\t\t{\"10\", false},\n\t\t{\"-10\", true},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.IsNeg()\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"IsNeg(%s) = %v, want %v\", tc.x, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestLt(t *testing.T) {\n\ttests := []struct {\n\t\tx, y string\n\t\twant bool\n\t}{\n\t\t{\"0\", \"0\", false},\n\t\t{\"0\", \"1\", true},\n\t\t{\"1\", \"0\", false},\n\t\t{\"-1\", \"0\", true},\n\t\t{\"0\", \"-1\", false},\n\t\t{\"1\", \"1\", false},\n\t\t{\"-1\", \"-1\", false},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", false},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.Lt(y)\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Lt(%s, %s) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestGt(t *testing.T) {\n\ttests := []struct {\n\t\tx, y string\n\t\twant bool\n\t}{\n\t\t{\"0\", \"0\", false},\n\t\t{\"0\", \"1\", false},\n\t\t{\"1\", \"0\", true},\n\t\t{\"-1\", \"0\", false},\n\t\t{\"0\", \"-1\", true},\n\t\t{\"1\", \"1\", false},\n\t\t{\"-1\", \"-1\", false},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", true},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.Gt(y)\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Gt(%s, %s) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestClone(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t}{\n\t\t{\"0\"},\n\t\t{\"-0\"},\n\t\t{\"1\"},\n\t\t{\"-1\"},\n\t\t{\"10\"},\n\t\t{\"-10\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty := x.Clone()\n\n\t\tif x.Cmp(y) != 0 {\n\t\t\tt.Errorf(\"Clone(%s) = %v, want %v\", tc.x, y, x)\n\t\t}\n\t}\n}\n"},{"name":"conversion.gno","body":"package int256\n\nimport \"gno.land/p/demo/uint256\"\n\n// SetInt64 sets z to x and returns z.\nfunc (z *Int) SetInt64(x int64) *Int {\n\tz.initiateAbs()\n\n\tneg := false\n\tif x \u003c 0 {\n\t\tneg = true\n\t\tx = -x\n\t}\n\tif z.abs == nil {\n\t\tpanic(\"abs is nil\")\n\t}\n\tz.abs = z.abs.SetUint64(uint64(x))\n\tz.neg = neg\n\treturn z\n}\n\n// SetUint64 sets z to x and returns z.\nfunc (z *Int) SetUint64(x uint64) *Int {\n\tz.initiateAbs()\n\n\tif z.abs == nil {\n\t\tpanic(\"abs is nil\")\n\t}\n\tz.abs = z.abs.SetUint64(x)\n\tz.neg = false\n\treturn z\n}\n\n// Uint64 returns the lower 64-bits of z\nfunc (z *Int) Uint64() uint64 {\n\treturn z.abs.Uint64()\n}\n\n// Int64 returns the lower 64-bits of z\nfunc (z *Int) Int64() int64 {\n\t_abs := z.abs.Clone()\n\n\tif z.neg {\n\t\treturn -int64(_abs.Uint64())\n\t}\n\treturn int64(_abs.Uint64())\n}\n\n// Neg sets z to -x and returns z.)\nfunc (z *Int) Neg(x *Int) *Int {\n\tz.abs.Set(x.abs)\n\tif z.abs.IsZero() {\n\t\tz.neg = false\n\t} else {\n\t\tz.neg = !x.neg\n\t}\n\treturn z\n}\n\n// Set sets z to x and returns z.\nfunc (z *Int) Set(x *Int) *Int {\n\tz.abs.Set(x.abs)\n\tz.neg = x.neg\n\treturn z\n}\n\n// SetFromUint256 converts a uint256.Uint to Int and sets the value to z.\nfunc (z *Int) SetUint256(x *uint256.Uint) *Int {\n\tz.abs.Set(x)\n\tz.neg = false\n\treturn z\n}\n\n// OBS, differs from original mempooler int256\n// ToString returns the decimal representation of z.\nfunc (z *Int) ToString() string {\n\tif z == nil {\n\t\tpanic(\"int256: nil pointer to ToString()\")\n\t}\n\n\tt := z.abs.Dec()\n\tif z.neg {\n\t\treturn \"-\" + t\n\t}\n\n\treturn t\n}\n"},{"name":"conversion_test.gno","body":"package int256\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uint256\"\n)\n\nfunc TestSetInt64(t *testing.T) {\n\ttests := []struct {\n\t\tx int64\n\t\twant string\n\t}{\n\t\t{0, \"0\"},\n\t\t{1, \"1\"},\n\t\t{-1, \"-1\"},\n\t\t{9223372036854775807, \"9223372036854775807\"},\n\t\t{-9223372036854775808, \"-9223372036854775808\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tvar z Int\n\t\tz.SetInt64(tc.x)\n\n\t\tgot := z.ToString()\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"SetInt64(%d) = %s, want %s\", tc.x, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestSetUint64(t *testing.T) {\n\ttests := []struct {\n\t\tx uint64\n\t\twant string\n\t}{\n\t\t{0, \"0\"},\n\t\t{1, \"1\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tvar z Int\n\t\tz.SetUint64(tc.x)\n\n\t\tgot := z.ToString()\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"SetUint64(%d) = %s, want %s\", tc.x, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestUint64(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant uint64\n\t}{\n\t\t{\"0\", 0},\n\t\t{\"1\", 1},\n\t\t{\"9223372036854775807\", 9223372036854775807},\n\t\t{\"9223372036854775808\", 9223372036854775808},\n\t\t{\"18446744073709551615\", 18446744073709551615},\n\t\t{\"18446744073709551616\", 0},\n\t\t{\"18446744073709551617\", 1},\n\t\t{\"-1\", 1},\n\t\t{\"-18446744073709551615\", 18446744073709551615},\n\t\t{\"-18446744073709551616\", 0},\n\t\t{\"-18446744073709551617\", 1},\n\t}\n\n\tfor _, tc := range tests {\n\t\tz := MustFromDecimal(tc.x)\n\n\t\tgot := z.Uint64()\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Uint64(%s) = %d, want %d\", tc.x, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestInt64(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant int64\n\t}{\n\t\t{\"0\", 0},\n\t\t{\"1\", 1},\n\t\t{\"9223372036854775807\", 9223372036854775807},\n\t\t{\"18446744073709551616\", 0},\n\t\t{\"18446744073709551617\", 1},\n\t\t{\"-1\", -1},\n\t\t{\"-9223372036854775808\", -9223372036854775808},\n\t}\n\n\tfor _, tc := range tests {\n\t\tz := MustFromDecimal(tc.x)\n\n\t\tgot := z.Int64()\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Uint64(%s) = %d, want %d\", tc.x, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestNeg(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant string\n\t}{\n\t\t{\"0\", \"0\"},\n\t\t{\"1\", \"-1\"},\n\t\t{\"-1\", \"1\"},\n\t\t{\"9223372036854775807\", \"-9223372036854775807\"},\n\t\t{\"-18446744073709551615\", \"18446744073709551615\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tz := MustFromDecimal(tc.x)\n\t\tz.Neg(z)\n\n\t\tgot := z.ToString()\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Neg(%s) = %s, want %s\", tc.x, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestSet(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant string\n\t}{\n\t\t{\"0\", \"0\"},\n\t\t{\"1\", \"1\"},\n\t\t{\"-1\", \"-1\"},\n\t\t{\"9223372036854775807\", \"9223372036854775807\"},\n\t\t{\"-18446744073709551615\", \"-18446744073709551615\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tz := MustFromDecimal(tc.x)\n\t\tz.Set(z)\n\n\t\tgot := z.ToString()\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Set(%s) = %s, want %s\", tc.x, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestSetUint256(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant string\n\t}{\n\t\t{\"0\", \"0\"},\n\t\t{\"1\", \"1\"},\n\t\t{\"9223372036854775807\", \"9223372036854775807\"},\n\t\t{\"18446744073709551615\", \"18446744073709551615\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tgot := New()\n\n\t\tz := uint256.MustFromDecimal(tc.x)\n\t\tgot.SetUint256(z)\n\n\t\tif got.ToString() != tc.want {\n\t\t\tt.Errorf(\"SetUint256(%s) = %s, want %s\", tc.x, got.ToString(), tc.want)\n\t\t}\n\t}\n}\n\nfunc TestToString(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tsetup func() *Int\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname: \"Zero from subtraction\",\n\t\t\tsetup: func() *Int {\n\t\t\t\tminusThree := MustFromDecimal(\"-3\")\n\t\t\t\tthree := MustFromDecimal(\"3\")\n\t\t\t\treturn Zero().Add(minusThree, three)\n\t\t\t},\n\t\t\texpected: \"0\",\n\t\t},\n\t\t{\n\t\t\tname: \"Zero from right shift\",\n\t\t\tsetup: func() *Int {\n\t\t\t\treturn Zero().Rsh(One(), 1234)\n\t\t\t},\n\t\t\texpected: \"0\",\n\t\t},\n\t\t{\n\t\t\tname: \"Positive number\",\n\t\t\tsetup: func() *Int {\n\t\t\t\treturn MustFromDecimal(\"42\")\n\t\t\t},\n\t\t\texpected: \"42\",\n\t\t},\n\t\t{\n\t\t\tname: \"Negative number\",\n\t\t\tsetup: func() *Int {\n\t\t\t\treturn MustFromDecimal(\"-42\")\n\t\t\t},\n\t\t\texpected: \"-42\",\n\t\t},\n\t\t{\n\t\t\tname: \"Large positive number\",\n\t\t\tsetup: func() *Int {\n\t\t\t\treturn MustFromDecimal(\"115792089237316195423570985008687907853269984665640564039457584007913129639935\")\n\t\t\t},\n\t\t\texpected: \"115792089237316195423570985008687907853269984665640564039457584007913129639935\",\n\t\t},\n\t\t{\n\t\t\tname: \"Large negative number\",\n\t\t\tsetup: func() *Int {\n\t\t\t\treturn MustFromDecimal(\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\")\n\t\t\t},\n\t\t\texpected: \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tz := tt.setup()\n\t\t\tresult := z.ToString()\n\t\t\tif result != tt.expected {\n\t\t\t\tt.Errorf(\"ToString() = %s, want %s\", result, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n"},{"name":"int256.gno","body":"// This package provides a 256-bit signed integer type, Int, and associated functions.\npackage int256\n\nimport (\n\t\"gno.land/p/demo/uint256\"\n)\n\nvar one = uint256.NewUint(1)\n\ntype Int struct {\n\tabs *uint256.Uint\n\tneg bool\n}\n\n// Zero returns a new Int set to 0.\nfunc Zero() *Int {\n\treturn NewInt(0)\n}\n\n// One returns a new Int set to 1.\nfunc One() *Int {\n\treturn NewInt(1)\n}\n\n// Sign returns:\n//\n//\t-1 if x \u003c 0\n//\t 0 if x == 0\n//\t+1 if x \u003e 0\nfunc (z *Int) Sign() int {\n\tz.initiateAbs()\n\n\tif z.abs.IsZero() {\n\t\treturn 0\n\t}\n\tif z.neg {\n\t\treturn -1\n\t}\n\treturn 1\n}\n\n// New returns a new Int set to 0.\nfunc New() *Int {\n\treturn \u0026Int{\n\t\tabs: new(uint256.Uint),\n\t}\n}\n\n// NewInt allocates and returns a new Int set to x.\nfunc NewInt(x int64) *Int {\n\treturn New().SetInt64(x)\n}\n\n// FromDecimal returns a new Int from a decimal string.\n// Returns a new Int and an error if the string is not a valid decimal.\nfunc FromDecimal(s string) (*Int, error) {\n\treturn new(Int).SetString(s)\n}\n\n// MustFromDecimal returns a new Int from a decimal string.\n// Panics if the string is not a valid decimal.\nfunc MustFromDecimal(s string) *Int {\n\tz, err := FromDecimal(s)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn z\n}\n\n// SetString sets s to the value of z and returns z and a boolean indicating success.\nfunc (z *Int) SetString(s string) (*Int, error) {\n\tneg := false\n\t// Remove max one leading +\n\tif len(s) \u003e 0 \u0026\u0026 s[0] == '+' {\n\t\tneg = false\n\t\ts = s[1:]\n\t}\n\n\tif len(s) \u003e 0 \u0026\u0026 s[0] == '-' {\n\t\tneg = true\n\t\ts = s[1:]\n\t}\n\tvar (\n\t\tabs *uint256.Uint\n\t\terr error\n\t)\n\tabs, err = uint256.FromDecimal(s)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn \u0026Int{\n\t\tabs,\n\t\tneg,\n\t}, nil\n}\n\n// FromUint256 is a convenience-constructor from uint256.Uint.\n// Returns a new Int and whether overflow occurred.\n// OBS: If u is `nil`, this method returns `nil, false`\nfunc FromUint256(x *uint256.Uint) *Int {\n\tif x == nil {\n\t\treturn nil\n\t}\n\tz := Zero()\n\n\tz.SetUint256(x)\n\treturn z\n}\n\n// OBS, differs from original mempooler int256\n// NilToZero sets z to 0 and return it if it's nil, otherwise it returns z\nfunc (z *Int) NilToZero() *Int {\n\tif z == nil {\n\t\treturn NewInt(0)\n\t}\n\treturn z\n}\n\n// initiateAbs sets default value for `z` or `z.abs` value if is nil\n// OBS: differs from mempooler int256. It checks not only `z.abs` but also `z`\nfunc (z *Int) initiateAbs() {\n\tif z == nil || z.abs == nil {\n\t\tz.abs = new(uint256.Uint)\n\t}\n}\n"},{"name":"int256_test.gno","body":"// ported from github.com/mempooler/int256\npackage int256\n\nimport \"testing\"\n\nfunc TestSign(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant int\n\t}{\n\t\t{\"0\", 0},\n\t\t{\"1\", 1},\n\t\t{\"-1\", -1},\n\t}\n\n\tfor _, tc := range tests {\n\t\tz := MustFromDecimal(tc.x)\n\t\tgot := z.Sign()\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Sign(%s) = %d, want %d\", tc.x, got, tc.want)\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":"json","path":"gno.land/p/demo/json","files":[{"name":"LICENSE","body":"# MIT License\n\nCopyright (c) 2019 Pyzhov Stepan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"},{"name":"README.md","body":"# JSON Parser\n\nThe JSON parser is a package that provides functionality for parsing and processing JSON strings. This package accepts JSON strings as byte slices.\n\nCurrently, gno does not [support the `reflect` package](https://docs.gno.land/concepts/effective-gno#reflection-is-never-clear), so it cannot retrieve type information at runtime. Therefore, it is designed to infer and handle type information when parsing JSON strings using a state machine approach.\n\nAfter passing through the state machine, JSON strings are represented as the `Node` type. The `Node` type represents nodes for JSON data, including various types such as `ObjectNode`, `ArrayNode`, `StringNode`, `NumberNode`, `BoolNode`, and `NullNode`.\n\nThis package provides methods for manipulating, searching, and extracting the Node type.\n\n## State Machine\n\nTo parse JSON strings, a [finite state machine](https://en.wikipedia.org/wiki/Finite-state_machine) approach is used. The state machine transitions to the next state based on the current state and the input character while parsing the JSON string. Through this method, type information can be inferred and processed without reflect, and the amount of parser code can be significantly reduced.\n\nThe image below shows the state transitions of the state machine according to the states and input characters.\n\n```mermaid\nstateDiagram-v2\n [*] --\u003e __: Start\n __ --\u003e ST: String\n __ --\u003e MI: Number\n __ --\u003e ZE: Zero\n __ --\u003e IN: Integer\n __ --\u003e T1: Boolean (true)\n __ --\u003e F1: Boolean (false)\n __ --\u003e N1: Null\n __ --\u003e ec: Empty Object End\n __ --\u003e cc: Object End\n __ --\u003e bc: Array End\n __ --\u003e co: Object Begin\n __ --\u003e bo: Array Begin\n __ --\u003e cm: Comma\n __ --\u003e cl: Colon\n __ --\u003e OK: Success/End\n ST --\u003e OK: String Complete\n MI --\u003e OK: Number Complete\n ZE --\u003e OK: Zero Complete\n IN --\u003e OK: Integer Complete\n T1 --\u003e OK: True Complete\n F1 --\u003e OK: False Complete\n N1 --\u003e OK: Null Complete\n ec --\u003e OK: Empty Object Complete\n cc --\u003e OK: Object Complete\n bc --\u003e OK: Array Complete\n co --\u003e OB: Inside Object\n bo --\u003e AR: Inside Array\n cm --\u003e KE: Expecting New Key\n cm --\u003e VA: Expecting New Value\n cl --\u003e VA: Expecting Value\n OB --\u003e ST: String in Object (Key)\n OB --\u003e ec: Empty Object\n OB --\u003e cc: End Object\n AR --\u003e ST: String in Array\n AR --\u003e bc: End Array\n KE --\u003e ST: String as Key\n VA --\u003e ST: String as Value\n VA --\u003e MI: Number as Value\n VA --\u003e T1: True as Value\n VA --\u003e F1: False as Value\n VA --\u003e N1: Null as Value\n OK --\u003e [*]: End\n```\n\n## Examples\n\nThis package provides parsing functionality along with encoding and decoding functionality. The following examples demonstrate how to use this package.\n\n### Decoding\n\nDecoding (or Unmarshaling) is the functionality that converts an input byte slice JSON string into a `Node` type.\n\nThe converted `Node` type allows you to modify the JSON data or search and extract data that meets specific conditions.\n\n```go\npackage main\n\nimport (\n \"gno.land/p/demo/json\"\n \"gno.land/p/demo/ufmt\"\n)\n\nfunc main() {\n node, err := json.Unmarshal([]byte(`{\"foo\": \"var\"}`))\n if err != nil {\n ufmt.Errorf(\"error: %v\", err)\n }\n\n ufmt.Sprintf(\"node: %v\", node)\n}\n```\n\n### Encoding\n\nEncoding (or Marshaling) is the functionality that converts JSON data represented as a Node type into a byte slice JSON string.\n\n\u003e ⚠️ Caution: Converting a large `Node` type into a JSON string may _impact performance_. or might be cause _unexpected behavior_.\n\n```go\npackage main\n\nimport (\n \"gno.land/p/demo/json\"\n \"gno.land/p/demo/ufmt\"\n)\n\nfunc main() {\n node := ObjectNode(\"\", map[string]*Node{\n \"foo\": StringNode(\"foo\", \"bar\"),\n \"baz\": NumberNode(\"baz\", 100500),\n \"qux\": NullNode(\"qux\"),\n })\n\n b, err := json.Marshal(node)\n if err != nil {\n ufmt.Errorf(\"error: %v\", err)\n }\n\n ufmt.Sprintf(\"json: %s\", string(b))\n}\n```\n\n### Searching\n\nOnce the JSON data converted into a `Node` type, you can **search** and **extract** data that satisfy specific conditions. For example, you can find data with a specific type or data with a specific key.\n\nTo use this functionality, you can use methods in the `GetXXX` prefixed methods. The `MustXXX` methods also provide the same functionality as the former methods, but they will **panic** if data doesn't satisfies the condition.\n\nHere is an example of finding data with a specific key. For more examples, please refer to the [node.gno](node.gno) file.\n\n```go\npackage main\n\nimport (\n \"gno.land/p/demo/json\"\n \"gno.land/p/demo/ufmt\"\n)\n\nfunc main() {\n root, err := Unmarshal([]byte(`{\"foo\": true, \"bar\": null}`))\n if err != nil {\n ufmt.Errorf(\"error: %v\", err)\n }\n\n value, err := root.GetKey(\"foo\")\n if err != nil {\n ufmt.Errorf(\"error occurred while getting key, %s\", err)\n }\n\n if value.MustBool() != true {\n ufmt.Errorf(\"value is not true\")\n }\n\n value, err = root.GetKey(\"bar\")\n if err != nil {\n t.Errorf(\"error occurred while getting key, %s\", err)\n }\n\n _, err = root.GetKey(\"baz\")\n if err == nil {\n t.Errorf(\"key baz is not exist. must be failed\")\n }\n}\n```\n\n## Contributing\n\nPlease submit any issues or pull requests for this package through the GitHub repository at [gnolang/gno](\u003chttps://github.com/gnolang/gno\u003e).\n"},{"name":"buffer.gno","body":"package json\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype buffer struct {\n\tdata []byte\n\tlength int\n\tindex int\n\n\tlast States\n\tstate States\n\tclass Classes\n}\n\n// newBuffer creates a new buffer with the given data\nfunc newBuffer(data []byte) *buffer {\n\treturn \u0026buffer{\n\t\tdata: data,\n\t\tlength: len(data),\n\t\tlast: GO,\n\t\tstate: GO,\n\t}\n}\n\n// first retrieves the first non-whitespace (or other escaped) character in the buffer.\nfunc (b *buffer) first() (byte, error) {\n\tfor ; b.index \u003c b.length; b.index++ {\n\t\tc := b.data[b.index]\n\n\t\tif !(c == whiteSpace || c == carriageReturn || c == newLine || c == tab) {\n\t\t\treturn c, nil\n\t\t}\n\t}\n\n\treturn 0, io.EOF\n}\n\n// current returns the byte of the current index.\nfunc (b *buffer) current() (byte, error) {\n\tif b.index \u003e= b.length {\n\t\treturn 0, io.EOF\n\t}\n\n\treturn b.data[b.index], nil\n}\n\n// next moves to the next byte and returns it.\nfunc (b *buffer) next() (byte, error) {\n\tb.index++\n\treturn b.current()\n}\n\n// step just moves to the next position.\nfunc (b *buffer) step() error {\n\t_, err := b.next()\n\treturn err\n}\n\n// move moves the index by the given position.\nfunc (b *buffer) move(pos int) error {\n\tnewIndex := b.index + pos\n\n\tif newIndex \u003e b.length {\n\t\treturn io.EOF\n\t}\n\n\tb.index = newIndex\n\n\treturn nil\n}\n\n// slice returns the slice from the current index to the given position.\nfunc (b *buffer) slice(pos int) ([]byte, error) {\n\tend := b.index + pos\n\n\tif end \u003e b.length {\n\t\treturn nil, io.EOF\n\t}\n\n\treturn b.data[b.index:end], nil\n}\n\n// sliceFromIndices returns a slice of the buffer's data starting from 'start' up to (but not including) 'stop'.\nfunc (b *buffer) sliceFromIndices(start, stop int) []byte {\n\tif start \u003e b.length {\n\t\tstart = b.length\n\t}\n\n\tif stop \u003e b.length {\n\t\tstop = b.length\n\t}\n\n\treturn b.data[start:stop]\n}\n\n// skip moves the index to skip the given byte.\nfunc (b *buffer) skip(bs byte) error {\n\tfor b.index \u003c b.length {\n\t\tif b.data[b.index] == bs \u0026\u0026 !b.backslash() {\n\t\t\treturn nil\n\t\t}\n\n\t\tb.index++\n\t}\n\n\treturn io.EOF\n}\n\n// skipAndReturnIndex moves the buffer index forward by one and returns the new index.\nfunc (b *buffer) skipAndReturnIndex() (int, error) {\n\terr := b.step()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn b.index, nil\n}\n\n// skipUntil moves the buffer index forward until it encounters a byte contained in the endTokens set.\nfunc (b *buffer) skipUntil(endTokens map[byte]bool) (int, error) {\n\tfor b.index \u003c b.length {\n\t\tcurrentByte, err := b.current()\n\t\tif err != nil {\n\t\t\treturn b.index, err\n\t\t}\n\n\t\t// Check if the current byte is in the set of end tokens.\n\t\tif _, exists := endTokens[currentByte]; exists {\n\t\t\treturn b.index, nil\n\t\t}\n\n\t\tb.index++\n\t}\n\n\treturn b.index, io.EOF\n}\n\n// significantTokens is a map where the keys are the significant characters in a JSON path.\n// The values in the map are all true, which allows us to use the map as a set for quick lookups.\nvar significantTokens = [256]bool{\n\tdot: true, // access properties of an object\n\tdollarSign: true, // root object\n\tatSign: true, // current object\n\tbracketOpen: true, // start of an array index or filter expression\n\tbracketClose: true, // end of an array index or filter expression\n}\n\n// filterTokens stores the filter expression tokens.\nvar filterTokens = [256]bool{\n\taesterisk: true, // wildcard\n\tandSign: true,\n\torSign: true,\n}\n\n// skipToNextSignificantToken advances the buffer index to the next significant character.\n// Significant characters are defined based on the JSON path syntax.\nfunc (b *buffer) skipToNextSignificantToken() {\n\tfor b.index \u003c b.length {\n\t\tcurrent := b.data[b.index]\n\n\t\tif significantTokens[current] {\n\t\t\tbreak\n\t\t}\n\n\t\tb.index++\n\t}\n}\n\n// backslash checks to see if the number of backslashes before the current index is odd.\n//\n// This is used to check if the current character is escaped. However, unlike the \"unescape\" function,\n// \"backslash\" only serves to check the number of backslashes.\nfunc (b *buffer) backslash() bool {\n\tif b.index == 0 {\n\t\treturn false\n\t}\n\n\tcount := 0\n\tfor i := b.index - 1; ; i-- {\n\t\tif b.data[i] != backSlash {\n\t\t\tbreak\n\t\t}\n\n\t\tcount++\n\n\t\tif i == 0 {\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn count%2 != 0\n}\n\n// numIndex holds a map of valid numeric characters\nvar numIndex = [256]bool{\n\t'0': true,\n\t'1': true,\n\t'2': true,\n\t'3': true,\n\t'4': true,\n\t'5': true,\n\t'6': true,\n\t'7': true,\n\t'8': true,\n\t'9': true,\n\t'.': true,\n\t'e': true,\n\t'E': true,\n}\n\n// pathToken checks if the current token is a valid JSON path token.\nfunc (b *buffer) pathToken() error {\n\tvar stack []byte\n\n\tinToken := false\n\tinNumber := false\n\tfirst := b.index\n\n\tfor b.index \u003c b.length {\n\t\tc := b.data[b.index]\n\n\t\tswitch {\n\t\tcase c == doubleQuote || c == singleQuote:\n\t\t\tinToken = true\n\t\t\tif err := b.step(); err != nil {\n\t\t\t\treturn errors.New(\"error stepping through buffer\")\n\t\t\t}\n\n\t\t\tif err := b.skip(c); err != nil {\n\t\t\t\treturn errUnmatchedQuotePath\n\t\t\t}\n\n\t\t\tif b.index \u003e= b.length {\n\t\t\t\treturn errUnmatchedQuotePath\n\t\t\t}\n\n\t\tcase c == bracketOpen || c == parenOpen:\n\t\t\tinToken = true\n\t\t\tstack = append(stack, c)\n\n\t\tcase c == bracketClose || c == parenClose:\n\t\t\tinToken = true\n\t\t\tif len(stack) == 0 || (c == bracketClose \u0026\u0026 stack[len(stack)-1] != bracketOpen) || (c == parenClose \u0026\u0026 stack[len(stack)-1] != parenOpen) {\n\t\t\t\treturn errUnmatchedParenthesis\n\t\t\t}\n\n\t\t\tstack = stack[:len(stack)-1]\n\n\t\tcase pathStateContainsValidPathToken(c):\n\t\t\tinToken = true\n\n\t\tcase c == plus || c == minus:\n\t\t\tif inNumber || (b.index \u003e 0 \u0026\u0026 numIndex[b.data[b.index-1]]) {\n\t\t\t\tinToken = true\n\t\t\t} else if !inToken \u0026\u0026 (b.index+1 \u003c b.length \u0026\u0026 numIndex[b.data[b.index+1]]) {\n\t\t\t\tinToken = true\n\t\t\t\tinNumber = true\n\t\t\t} else if !inToken {\n\t\t\t\treturn errInvalidToken\n\t\t\t}\n\n\t\tdefault:\n\t\t\tif len(stack) != 0 || inToken {\n\t\t\t\tinToken = true\n\t\t\t} else {\n\t\t\t\tgoto end\n\t\t\t}\n\t\t}\n\n\t\tb.index++\n\t}\n\nend:\n\tif len(stack) != 0 {\n\t\treturn errUnmatchedParenthesis\n\t}\n\n\tif first == b.index {\n\t\treturn errors.New(\"no token found\")\n\t}\n\n\tif inNumber \u0026\u0026 !numIndex[b.data[b.index-1]] {\n\t\tinNumber = false\n\t}\n\n\treturn nil\n}\n\nfunc pathStateContainsValidPathToken(c byte) bool {\n\tif significantTokens[c] {\n\t\treturn true\n\t}\n\n\tif filterTokens[c] {\n\t\treturn true\n\t}\n\n\tif numIndex[c] {\n\t\treturn true\n\t}\n\n\tif 'A' \u003c= c \u0026\u0026 c \u003c= 'Z' || 'a' \u003c= c \u0026\u0026 c \u003c= 'z' {\n\t\treturn true\n\t}\n\n\treturn false\n}\n\nfunc (b *buffer) numeric(token bool) error {\n\tif token {\n\t\tb.last = GO\n\t}\n\n\tfor ; b.index \u003c b.length; b.index++ {\n\t\tb.class = b.getClasses(doubleQuote)\n\t\tif b.class == __ {\n\t\t\treturn errInvalidToken\n\t\t}\n\n\t\tb.state = StateTransitionTable[b.last][b.class]\n\t\tif b.state == __ {\n\t\t\tif token {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\treturn errInvalidToken\n\t\t}\n\n\t\tif b.state \u003c __ {\n\t\t\treturn nil\n\t\t}\n\n\t\tif b.state \u003c MI || b.state \u003e E3 {\n\t\t\treturn nil\n\t\t}\n\n\t\tb.last = b.state\n\t}\n\n\tif b.last != ZE \u0026\u0026 b.last != IN \u0026\u0026 b.last != FR \u0026\u0026 b.last != E3 {\n\t\treturn errInvalidToken\n\t}\n\n\treturn nil\n}\n\nfunc (b *buffer) getClasses(c byte) Classes {\n\tif b.data[b.index] \u003e= 128 {\n\t\treturn C_ETC\n\t}\n\n\tif c == singleQuote {\n\t\treturn QuoteAsciiClasses[b.data[b.index]]\n\t}\n\n\treturn AsciiClasses[b.data[b.index]]\n}\n\nfunc (b *buffer) getState() States {\n\tb.last = b.state\n\n\tb.class = b.getClasses(doubleQuote)\n\tif b.class == __ {\n\t\treturn __\n\t}\n\n\tb.state = StateTransitionTable[b.last][b.class]\n\n\treturn b.state\n}\n\n// string parses a string token from the buffer.\nfunc (b *buffer) string(search byte, token bool) error {\n\tif token {\n\t\tb.last = GO\n\t}\n\n\tfor ; b.index \u003c b.length; b.index++ {\n\t\tb.class = b.getClasses(search)\n\n\t\tif b.class == __ {\n\t\t\treturn errInvalidToken\n\t\t}\n\n\t\tb.state = StateTransitionTable[b.last][b.class]\n\t\tif b.state == __ {\n\t\t\treturn errInvalidToken\n\t\t}\n\n\t\tif b.state \u003c __ {\n\t\t\tbreak\n\t\t}\n\n\t\tb.last = b.state\n\t}\n\n\treturn nil\n}\n\nfunc (b *buffer) word(bs []byte) error {\n\tvar c byte\n\n\tmax := len(bs)\n\tindex := 0\n\n\tfor ; b.index \u003c b.length \u0026\u0026 index \u003c max; b.index++ {\n\t\tc = b.data[b.index]\n\n\t\tif c != bs[index] {\n\t\t\treturn errInvalidToken\n\t\t}\n\n\t\tindex++\n\t\tif index \u003e= max {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif index != max {\n\t\treturn errInvalidToken\n\t}\n\n\treturn nil\n}\n\nfunc numberKind2f64(value interface{}) (result float64, err error) {\n\tswitch typed := value.(type) {\n\tcase float64:\n\t\tresult = typed\n\tcase float32:\n\t\tresult = float64(typed)\n\tcase int:\n\t\tresult = float64(typed)\n\tcase int8:\n\t\tresult = float64(typed)\n\tcase int16:\n\t\tresult = float64(typed)\n\tcase int32:\n\t\tresult = float64(typed)\n\tcase int64:\n\t\tresult = float64(typed)\n\tcase uint:\n\t\tresult = float64(typed)\n\tcase uint8:\n\t\tresult = float64(typed)\n\tcase uint16:\n\t\tresult = float64(typed)\n\tcase uint32:\n\t\tresult = float64(typed)\n\tcase uint64:\n\t\tresult = float64(typed)\n\tdefault:\n\t\terr = ufmt.Errorf(\"invalid number type: %T\", value)\n\t}\n\n\treturn\n}\n"},{"name":"buffer_test.gno","body":"package json\n\nimport (\n\t\"testing\"\n)\n\nfunc TestBufferCurrent(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tbuffer *buffer\n\t\texpected byte\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"Valid current byte\",\n\t\t\tbuffer: \u0026buffer{\n\t\t\t\tdata: []byte(\"test\"),\n\t\t\t\tlength: 4,\n\t\t\t\tindex: 1,\n\t\t\t},\n\t\t\texpected: 'e',\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"EOF\",\n\t\t\tbuffer: \u0026buffer{\n\t\t\t\tdata: []byte(\"test\"),\n\t\t\t\tlength: 4,\n\t\t\t\tindex: 4,\n\t\t\t},\n\t\t\texpected: 0,\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.buffer.current()\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"buffer.current() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif got != tt.expected {\n\t\t\t\tt.Errorf(\"buffer.current() = %v, want %v\", got, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBufferStep(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tbuffer *buffer\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"Valid step\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 0},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"EOF error\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 3},\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\terr := tt.buffer.step()\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"buffer.step() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBufferNext(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tbuffer *buffer\n\t\twant byte\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"Valid next byte\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 0},\n\t\t\twant: 'e',\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"EOF error\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 3},\n\t\t\twant: 0,\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.buffer.next()\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"buffer.next() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif got != tt.want {\n\t\t\t\tt.Errorf(\"buffer.next() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBufferSlice(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tbuffer *buffer\n\t\tpos int\n\t\twant []byte\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"Valid slice -- 0 characters\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 0},\n\t\t\tpos: 0,\n\t\t\twant: nil,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Valid slice -- 1 character\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 0},\n\t\t\tpos: 1,\n\t\t\twant: []byte(\"t\"),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Valid slice -- 2 characters\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 1},\n\t\t\tpos: 2,\n\t\t\twant: []byte(\"es\"),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Valid slice -- 3 characters\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 0},\n\t\t\tpos: 3,\n\t\t\twant: []byte(\"tes\"),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Valid slice -- 4 characters\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 0},\n\t\t\tpos: 4,\n\t\t\twant: []byte(\"test\"),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"EOF error\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 3},\n\t\t\tpos: 2,\n\t\t\twant: nil,\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.buffer.slice(tt.pos)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"buffer.slice() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif string(got) != string(tt.want) {\n\t\t\t\tt.Errorf(\"buffer.slice() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBufferMove(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tbuffer *buffer\n\t\tpos int\n\t\twantErr bool\n\t\twantIdx int\n\t}{\n\t\t{\n\t\t\tname: \"Valid move\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 1},\n\t\t\tpos: 2,\n\t\t\twantErr: false,\n\t\t\twantIdx: 3,\n\t\t},\n\t\t{\n\t\t\tname: \"Move beyond length\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 1},\n\t\t\tpos: 4,\n\t\t\twantErr: true,\n\t\t\twantIdx: 1,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\terr := tt.buffer.move(tt.pos)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"buffer.move() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t\tif tt.buffer.index != tt.wantIdx {\n\t\t\t\tt.Errorf(\"buffer.move() index = %v, want %v\", tt.buffer.index, tt.wantIdx)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBufferSkip(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tbuffer *buffer\n\t\tb byte\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"Skip byte\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 0},\n\t\t\tb: 'e',\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Skip to EOF\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 0},\n\t\t\tb: 'x',\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\terr := tt.buffer.skip(tt.b)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"buffer.skip() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSkipToNextSignificantToken(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput []byte\n\t\texpected int\n\t}{\n\t\t{\"No significant chars\", []byte(\"abc\"), 3},\n\t\t{\"One significant char at start\", []byte(\".abc\"), 0},\n\t\t{\"Significant char in middle\", []byte(\"ab.c\"), 2},\n\t\t{\"Multiple significant chars\", []byte(\"a$.c\"), 1},\n\t\t{\"Significant char at end\", []byte(\"abc$\"), 3},\n\t\t{\"Only significant chars\", []byte(\"$.\"), 0},\n\t\t{\"Empty string\", []byte(\"\"), 0},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tb := newBuffer(tt.input)\n\t\t\tb.skipToNextSignificantToken()\n\t\t\tif b.index != tt.expected {\n\t\t\t\tt.Errorf(\"after skipToNextSignificantToken(), got index = %v, want %v\", b.index, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc mockBuffer(s string) *buffer {\n\treturn newBuffer([]byte(s))\n}\n\nfunc TestSkipAndReturnIndex(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput string\n\t\texpected int\n\t}{\n\t\t{\"StartOfString\", \"\", 0},\n\t\t{\"MiddleOfString\", \"abcdef\", 1},\n\t\t{\"EndOfString\", \"abc\", 1},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tbuf := mockBuffer(tt.input)\n\t\t\tgot, err := buf.skipAndReturnIndex()\n\t\t\tif err != nil \u0026\u0026 tt.input != \"\" { // Expect no error unless input is empty\n\t\t\t\tt.Errorf(\"skipAndReturnIndex() error = %v\", err)\n\t\t\t}\n\t\t\tif got != tt.expected {\n\t\t\t\tt.Errorf(\"skipAndReturnIndex() = %v, want %v\", got, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSkipUntil(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput string\n\t\ttokens map[byte]bool\n\t\texpected int\n\t}{\n\t\t{\"SkipToToken\", \"abcdefg\", map[byte]bool{'c': true}, 2},\n\t\t{\"SkipToEnd\", \"abcdefg\", map[byte]bool{'h': true}, 7},\n\t\t{\"SkipNone\", \"abcdefg\", map[byte]bool{'a': true}, 0},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tbuf := mockBuffer(tt.input)\n\t\t\tgot, err := buf.skipUntil(tt.tokens)\n\t\t\tif err != nil \u0026\u0026 got != len(tt.input) { // Expect error only if reached end without finding token\n\t\t\t\tt.Errorf(\"skipUntil() error = %v\", err)\n\t\t\t}\n\t\t\tif got != tt.expected {\n\t\t\t\tt.Errorf(\"skipUntil() = %v, want %v\", got, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSliceFromIndices(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput string\n\t\tstart int\n\t\tend int\n\t\texpected string\n\t}{\n\t\t{\"FullString\", \"abcdefg\", 0, 7, \"abcdefg\"},\n\t\t{\"Substring\", \"abcdefg\", 2, 5, \"cde\"},\n\t\t{\"OutOfBounds\", \"abcdefg\", 5, 10, \"fg\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tbuf := mockBuffer(tt.input)\n\t\t\tgot := buf.sliceFromIndices(tt.start, tt.end)\n\t\t\tif string(got) != tt.expected {\n\t\t\t\tt.Errorf(\"sliceFromIndices() = %v, want %v\", string(got), tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBufferToken(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tpath string\n\t\tindex int\n\t\tisErr bool\n\t}{\n\t\t{\n\t\t\tname: \"Simple valid path\",\n\t\t\tpath: \"@.length\",\n\t\t\tindex: 8,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Path with array expr\",\n\t\t\tpath: \"@['foo'].0.bar\",\n\t\t\tindex: 14,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Path with array expr and simple fomula\",\n\t\t\tpath: \"@['foo'].[(@.length - 1)].*\",\n\t\t\tindex: 27,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Path with filter expr\",\n\t\t\tpath: \"@['foo'].[?(@.bar == 1 \u0026 @.baz \u003c @.length)].*\",\n\t\t\tindex: 45,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"addition of foo and bar\",\n\t\t\tpath: \"@.foo+@.bar\",\n\t\t\tindex: 11,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"logical AND of foo and bar\",\n\t\t\tpath: \"@.foo \u0026\u0026 @.bar\",\n\t\t\tindex: 14,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"logical OR of foo and bar\",\n\t\t\tpath: \"@.foo || @.bar\",\n\t\t\tindex: 14,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"accessing third element of foo\",\n\t\t\tpath: \"@.foo,3\",\n\t\t\tindex: 7,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"accessing last element of array\",\n\t\t\tpath: \"@.length-1\",\n\t\t\tindex: 10,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"number 1\",\n\t\t\tpath: \"1\",\n\t\t\tindex: 1,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"float\",\n\t\t\tpath: \"3.1e4\",\n\t\t\tindex: 5,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"float with minus\",\n\t\t\tpath: \"3.1e-4\",\n\t\t\tindex: 6,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"float with plus\",\n\t\t\tpath: \"3.1e+4\",\n\t\t\tindex: 6,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"negative number\",\n\t\t\tpath: \"-12345\",\n\t\t\tindex: 6,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"negative float\",\n\t\t\tpath: \"-3.1e4\",\n\t\t\tindex: 6,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"negative float with minus\",\n\t\t\tpath: \"-3.1e-4\",\n\t\t\tindex: 7,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"negative float with plus\",\n\t\t\tpath: \"-3.1e+4\",\n\t\t\tindex: 7,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"string number\",\n\t\t\tpath: \"'12345'\",\n\t\t\tindex: 7,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"string with backslash\",\n\t\t\tpath: \"'foo \\\\'bar '\",\n\t\t\tindex: 12,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"string with inner double quotes\",\n\t\t\tpath: \"'foo \\\"bar \\\"'\",\n\t\t\tindex: 12,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"parenthesis 1\",\n\t\t\tpath: \"(@abc)\",\n\t\t\tindex: 6,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"parenthesis 2\",\n\t\t\tpath: \"[()]\",\n\t\t\tindex: 4,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"parenthesis mismatch\",\n\t\t\tpath: \"[(])\",\n\t\t\tindex: 2,\n\t\t\tisErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"parenthesis mismatch 2\",\n\t\t\tpath: \"(\",\n\t\t\tindex: 1,\n\t\t\tisErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"parenthesis mismatch 3\",\n\t\t\tpath: \"())]\",\n\t\t\tindex: 2,\n\t\t\tisErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"bracket mismatch\",\n\t\t\tpath: \"[()\",\n\t\t\tindex: 3,\n\t\t\tisErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"bracket mismatch 2\",\n\t\t\tpath: \"()]\",\n\t\t\tindex: 2,\n\t\t\tisErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"path does not close bracket\",\n\t\t\tpath: \"@.foo[)\",\n\t\t\tindex: 6,\n\t\t\tisErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tbuf := newBuffer([]byte(tt.path))\n\n\t\t\terr := buf.pathToken()\n\t\t\tif tt.isErr {\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Errorf(\"Expected an error for path `%s`, but got none\", tt.path)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif err == nil \u0026\u0026 tt.isErr {\n\t\t\t\tt.Errorf(\"Expected an error for path `%s`, but got none\", tt.path)\n\t\t\t}\n\n\t\t\tif buf.index != tt.index {\n\t\t\t\tt.Errorf(\"Expected final index %d, got %d (token: `%s`) for path `%s`\", tt.index, buf.index, string(buf.data[buf.index]), tt.path)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBufferFirst(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tdata []byte\n\t\texpected byte\n\t}{\n\t\t{\n\t\t\tname: \"Valid first byte\",\n\t\t\tdata: []byte(\"test\"),\n\t\t\texpected: 't',\n\t\t},\n\t\t{\n\t\t\tname: \"Empty buffer\",\n\t\t\tdata: []byte(\"\"),\n\t\t\texpected: 0,\n\t\t},\n\t\t{\n\t\t\tname: \"Whitespace buffer\",\n\t\t\tdata: []byte(\" \"),\n\t\t\texpected: 0,\n\t\t},\n\t\t{\n\t\t\tname: \"whitespace in middle\",\n\t\t\tdata: []byte(\"hello world\"),\n\t\t\texpected: 'h',\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tb := newBuffer(tt.data)\n\n\t\t\tgot, err := b.first()\n\t\t\tif err != nil \u0026\u0026 tt.expected != 0 {\n\t\t\t\tt.Errorf(\"Unexpected error: %v\", err)\n\t\t\t}\n\n\t\t\tif got != tt.expected {\n\t\t\t\tt.Errorf(\"Expected first byte to be %q, got %q\", tt.expected, got)\n\t\t\t}\n\t\t})\n\t}\n}\n"},{"name":"builder.gno","body":"package json\n\ntype NodeBuilder struct {\n\tnode *Node\n}\n\nfunc Builder() *NodeBuilder {\n\treturn \u0026NodeBuilder{node: ObjectNode(\"\", nil)}\n}\n\nfunc (b *NodeBuilder) WriteString(key, value string) *NodeBuilder {\n\tb.node.AppendObject(key, StringNode(\"\", value))\n\treturn b\n}\n\nfunc (b *NodeBuilder) WriteNumber(key string, value float64) *NodeBuilder {\n\tb.node.AppendObject(key, NumberNode(\"\", value))\n\treturn b\n}\n\nfunc (b *NodeBuilder) WriteBool(key string, value bool) *NodeBuilder {\n\tb.node.AppendObject(key, BoolNode(\"\", value))\n\treturn b\n}\n\nfunc (b *NodeBuilder) WriteNull(key string) *NodeBuilder {\n\tb.node.AppendObject(key, NullNode(\"\"))\n\treturn b\n}\n\nfunc (b *NodeBuilder) WriteObject(key string, fn func(*NodeBuilder)) *NodeBuilder {\n\tnestedBuilder := \u0026NodeBuilder{node: ObjectNode(\"\", nil)}\n\tfn(nestedBuilder)\n\tb.node.AppendObject(key, nestedBuilder.node)\n\treturn b\n}\n\nfunc (b *NodeBuilder) WriteArray(key string, fn func(*ArrayBuilder)) *NodeBuilder {\n\tarrayBuilder := \u0026ArrayBuilder{nodes: []*Node{}}\n\tfn(arrayBuilder)\n\tb.node.AppendObject(key, ArrayNode(\"\", arrayBuilder.nodes))\n\treturn b\n}\n\nfunc (b *NodeBuilder) Node() *Node {\n\treturn b.node\n}\n\ntype ArrayBuilder struct {\n\tnodes []*Node\n}\n\nfunc (ab *ArrayBuilder) WriteString(value string) *ArrayBuilder {\n\tab.nodes = append(ab.nodes, StringNode(\"\", value))\n\treturn ab\n}\n\nfunc (ab *ArrayBuilder) WriteNumber(value float64) *ArrayBuilder {\n\tab.nodes = append(ab.nodes, NumberNode(\"\", value))\n\treturn ab\n}\n\nfunc (ab *ArrayBuilder) WriteInt(value int) *ArrayBuilder {\n\treturn ab.WriteNumber(float64(value))\n}\n\nfunc (ab *ArrayBuilder) WriteBool(value bool) *ArrayBuilder {\n\tab.nodes = append(ab.nodes, BoolNode(\"\", value))\n\treturn ab\n}\n\nfunc (ab *ArrayBuilder) WriteNull() *ArrayBuilder {\n\tab.nodes = append(ab.nodes, NullNode(\"\"))\n\treturn ab\n}\n\nfunc (ab *ArrayBuilder) WriteObject(fn func(*NodeBuilder)) *ArrayBuilder {\n\tnestedBuilder := \u0026NodeBuilder{node: ObjectNode(\"\", nil)}\n\tfn(nestedBuilder)\n\tab.nodes = append(ab.nodes, nestedBuilder.node)\n\treturn ab\n}\n\nfunc (ab *ArrayBuilder) WriteArray(fn func(*ArrayBuilder)) *ArrayBuilder {\n\tnestedArrayBuilder := \u0026ArrayBuilder{nodes: []*Node{}}\n\tfn(nestedArrayBuilder)\n\tab.nodes = append(ab.nodes, ArrayNode(\"\", nestedArrayBuilder.nodes))\n\treturn ab\n}\n"},{"name":"builder_test.gno","body":"package json\n\nimport (\n\t\"testing\"\n)\n\nfunc TestNodeBuilder(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tbuild func() *Node\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname: \"plain object\",\n\t\t\tbuild: func() *Node {\n\t\t\t\treturn Builder().\n\t\t\t\t\tWriteString(\"name\", \"Alice\").\n\t\t\t\t\tWriteNumber(\"age\", 30).\n\t\t\t\t\tWriteBool(\"is_student\", false).\n\t\t\t\t\tNode()\n\t\t\t},\n\t\t\texpected: `{\"name\":\"Alice\",\"age\":30,\"is_student\":false}`,\n\t\t},\n\t\t{\n\t\t\tname: \"nested object\",\n\t\t\tbuild: func() *Node {\n\t\t\t\treturn Builder().\n\t\t\t\t\tWriteString(\"name\", \"Alice\").\n\t\t\t\t\tWriteObject(\"address\", func(b *NodeBuilder) {\n\t\t\t\t\t\tb.WriteString(\"city\", \"New York\").\n\t\t\t\t\t\t\tWriteNumber(\"zipcode\", 10001)\n\t\t\t\t\t}).\n\t\t\t\t\tNode()\n\t\t\t},\n\t\t\texpected: `{\"name\":\"Alice\",\"address\":{\"city\":\"New York\",\"zipcode\":10001}}`,\n\t\t},\n\t\t{\n\t\t\tname: \"null node\",\n\t\t\tbuild: func() *Node {\n\t\t\t\treturn Builder().WriteNull(\"foo\").Node()\n\t\t\t},\n\t\t\texpected: `{\"foo\":null}`,\n\t\t},\n\t\t{\n\t\t\tname: \"array node\",\n\t\t\tbuild: func() *Node {\n\t\t\t\treturn Builder().\n\t\t\t\t\tWriteArray(\"items\", func(ab *ArrayBuilder) {\n\t\t\t\t\t\tab.WriteString(\"item1\").\n\t\t\t\t\t\t\tWriteString(\"item2\").\n\t\t\t\t\t\t\tWriteString(\"item3\")\n\t\t\t\t\t}).\n\t\t\t\t\tNode()\n\t\t\t},\n\t\t\texpected: `{\"items\":[\"item1\",\"item2\",\"item3\"]}`,\n\t\t},\n\t\t{\n\t\t\tname: \"array with objects\",\n\t\t\tbuild: func() *Node {\n\t\t\t\treturn Builder().\n\t\t\t\t\tWriteArray(\"users\", func(ab *ArrayBuilder) {\n\t\t\t\t\t\tab.WriteObject(func(b *NodeBuilder) {\n\t\t\t\t\t\t\tb.WriteString(\"name\", \"Bob\").\n\t\t\t\t\t\t\t\tWriteNumber(\"age\", 25)\n\t\t\t\t\t\t}).\n\t\t\t\t\t\t\tWriteObject(func(b *NodeBuilder) {\n\t\t\t\t\t\t\t\tb.WriteString(\"name\", \"Carol\").\n\t\t\t\t\t\t\t\t\tWriteNumber(\"age\", 27)\n\t\t\t\t\t\t\t})\n\t\t\t\t\t}).\n\t\t\t\t\tNode()\n\t\t\t},\n\t\t\texpected: `{\"users\":[{\"name\":\"Bob\",\"age\":25},{\"name\":\"Carol\",\"age\":27}]}`,\n\t\t},\n\t\t{\n\t\t\tname: \"array with various types\",\n\t\t\tbuild: func() *Node {\n\t\t\t\treturn Builder().\n\t\t\t\t\tWriteArray(\"values\", func(ab *ArrayBuilder) {\n\t\t\t\t\t\tab.WriteString(\"item1\").\n\t\t\t\t\t\t\tWriteNumber(123).\n\t\t\t\t\t\t\tWriteBool(true).\n\t\t\t\t\t\t\tWriteNull()\n\t\t\t\t\t}).\n\t\t\t\t\tNode()\n\t\t\t},\n\t\t\texpected: `{\"values\":[\"item1\",123,true,null]}`,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tnode := tt.build()\n\t\t\tvalue, err := Marshal(node)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t\tif string(value) != tt.expected {\n\t\t\t\tt.Errorf(\"expected %s, got %s\", tt.expected, string(value))\n\t\t\t}\n\t\t})\n\t}\n}\n"},{"name":"decode.gno","body":"// ref: https://github.com/spyzhov/ajson/blob/master/decode.go\n\npackage json\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// This limits the max nesting depth to prevent stack overflow.\n// This is permitted by https://tools.ietf.org/html/rfc7159#section-9\nconst maxNestingDepth = 10000\n\n// Unmarshal parses the JSON-encoded data and returns a Node.\n// The data must be a valid JSON-encoded value.\n//\n// Usage:\n//\n//\tnode, err := json.Unmarshal([]byte(`{\"key\": \"value\"}`))\n//\tif err != nil {\n//\t\tufmt.Println(err)\n//\t}\n//\tprintln(node) // {\"key\": \"value\"}\nfunc Unmarshal(data []byte) (*Node, error) {\n\tbuf := newBuffer(data)\n\n\tvar (\n\t\tstate States\n\t\tkey *string\n\t\tcurrent *Node\n\t\tnesting int\n\t\tuseKey = func() **string {\n\t\t\ttmp := cptrs(key)\n\t\t\tkey = nil\n\t\t\treturn \u0026tmp\n\t\t}\n\t\terr error\n\t)\n\n\tif _, err = buf.first(); err != nil {\n\t\treturn nil, io.EOF\n\t}\n\n\tfor {\n\t\tstate = buf.getState()\n\t\tif state == __ {\n\t\t\treturn nil, unexpectedTokenError(buf.data, buf.index)\n\t\t}\n\n\t\t// region state machine\n\t\tif state \u003e= GO {\n\t\t\tswitch buf.state {\n\t\t\tcase ST: // string\n\t\t\t\tif current != nil \u0026\u0026 current.IsObject() \u0026\u0026 key == nil {\n\t\t\t\t\t// key detected\n\t\t\t\t\tif key, err = getString(buf); err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\n\t\t\t\t\tbuf.state = CO\n\t\t\t\t} else {\n\t\t\t\t\tcurrent, nesting, err = createNestedNode(current, buf, String, nesting, useKey())\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\n\t\t\t\t\terr = buf.string(doubleQuote, false)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\n\t\t\t\t\tcurrent, nesting = updateNode(current, buf, nesting, true)\n\t\t\t\t\tbuf.state = OK\n\t\t\t\t}\n\n\t\t\tcase MI, ZE, IN: // number\n\t\t\t\tcurrent, err = processNumericNode(current, buf, useKey())\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\tcase T1, F1: // boolean\n\t\t\t\tliteral := falseLiteral\n\t\t\t\tif buf.state == T1 {\n\t\t\t\t\tliteral = trueLiteral\n\t\t\t\t}\n\n\t\t\t\tcurrent, nesting, err = processLiteralNode(current, buf, Boolean, literal, useKey(), nesting)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\tcase N1: // null\n\t\t\t\tcurrent, nesting, err = processLiteralNode(current, buf, Null, nullLiteral, useKey(), nesting)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t// region action\n\t\t\tswitch state {\n\t\t\tcase ec, cc: // \u003cempty\u003e }\n\t\t\t\tif key != nil {\n\t\t\t\t\treturn nil, unexpectedTokenError(buf.data, buf.index)\n\t\t\t\t}\n\n\t\t\t\tcurrent, nesting, err = updateNodeAndSetBufferState(current, buf, nesting, Object)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\tcase bc: // ]\n\t\t\t\tcurrent, nesting, err = updateNodeAndSetBufferState(current, buf, nesting, Array)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\tcase co, bo: // { [\n\t\t\t\tvalTyp, bState := Object, OB\n\t\t\t\tif state == bo {\n\t\t\t\t\tvalTyp, bState = Array, AR\n\t\t\t\t}\n\n\t\t\t\tcurrent, nesting, err = createNestedNode(current, buf, valTyp, nesting, useKey())\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\t\tbuf.state = bState\n\n\t\t\tcase cm: // ,\n\t\t\t\tif current == nil {\n\t\t\t\t\treturn nil, unexpectedTokenError(buf.data, buf.index)\n\t\t\t\t}\n\n\t\t\t\tif !current.isContainer() {\n\t\t\t\t\treturn nil, unexpectedTokenError(buf.data, buf.index)\n\t\t\t\t}\n\n\t\t\t\tif current.IsObject() {\n\t\t\t\t\tbuf.state = KE // key expected\n\t\t\t\t} else {\n\t\t\t\t\tbuf.state = VA // value expected\n\t\t\t\t}\n\n\t\t\tcase cl: // :\n\t\t\t\tif current == nil || !current.IsObject() || key == nil {\n\t\t\t\t\treturn nil, unexpectedTokenError(buf.data, buf.index)\n\t\t\t\t}\n\n\t\t\t\tbuf.state = VA\n\n\t\t\tdefault:\n\t\t\t\treturn nil, unexpectedTokenError(buf.data, buf.index)\n\t\t\t}\n\t\t}\n\n\t\tif buf.step() != nil {\n\t\t\tbreak\n\t\t}\n\n\t\tif _, err = buf.first(); err != nil {\n\t\t\terr = nil\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif current == nil || buf.state != OK {\n\t\treturn nil, io.EOF\n\t}\n\n\troot := current.root()\n\tif !root.ready() {\n\t\treturn nil, io.EOF\n\t}\n\n\treturn root, err\n}\n\n// UnmarshalSafe parses the JSON-encoded data and returns a Node.\nfunc UnmarshalSafe(data []byte) (*Node, error) {\n\tvar safe []byte\n\tsafe = append(safe, data...)\n\treturn Unmarshal(safe)\n}\n\n// processNumericNode creates a new node, processes a numeric value,\n// sets the node's borders, and moves to the previous node.\nfunc processNumericNode(current *Node, buf *buffer, key **string) (*Node, error) {\n\tvar err error\n\tcurrent, err = createNode(current, buf, Number, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err = buf.numeric(false); err != nil {\n\t\treturn nil, err\n\t}\n\n\tcurrent.borders[1] = buf.index\n\tif current.prev != nil {\n\t\tcurrent = current.prev\n\t}\n\n\tbuf.index -= 1\n\tbuf.state = OK\n\n\treturn current, nil\n}\n\n// processLiteralNode creates a new node, processes a literal value,\n// sets the node's borders, and moves to the previous node.\nfunc processLiteralNode(\n\tcurrent *Node,\n\tbuf *buffer,\n\tliteralType ValueType,\n\tliteralValue []byte,\n\tuseKey **string,\n\tnesting int,\n) (*Node, int, error) {\n\tvar err error\n\tcurrent, nesting, err = createLiteralNode(current, buf, literalType, literalValue, useKey, nesting)\n\tif err != nil {\n\t\treturn nil, nesting, err\n\t}\n\treturn current, nesting, nil\n}\n\n// isValidContainerType checks if the current node is a valid container (object or array).\n// The container must satisfy the following conditions:\n// 1. The current node must not be nil.\n// 2. The current node must be an object or array.\n// 3. The current node must not be ready.\nfunc isValidContainerType(current *Node, nodeType ValueType) bool {\n\tswitch nodeType {\n\tcase Object:\n\t\treturn current != nil \u0026\u0026 current.IsObject() \u0026\u0026 !current.ready()\n\tcase Array:\n\t\treturn current != nil \u0026\u0026 current.IsArray() \u0026\u0026 !current.ready()\n\tdefault:\n\t\treturn false\n\t}\n}\n\n// getString extracts a string from the buffer and advances the buffer index past the string.\nfunc getString(b *buffer) (*string, error) {\n\tstart := b.index\n\tif err := b.string(doubleQuote, false); err != nil {\n\t\treturn nil, err\n\t}\n\n\tvalue, ok := Unquote(b.data[start:b.index+1], doubleQuote)\n\tif !ok {\n\t\treturn nil, unexpectedTokenError(b.data, start)\n\t}\n\n\treturn \u0026value, nil\n}\n\n// createNode creates a new node and sets the key if it is not nil.\nfunc createNode(\n\tcurrent *Node,\n\tbuf *buffer,\n\tnodeType ValueType,\n\tkey **string,\n) (*Node, error) {\n\tvar err error\n\tcurrent, err = NewNode(current, buf, nodeType, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn current, nil\n}\n\n// createNestedNode creates a new nested node (array or object) and sets the key if it is not nil.\nfunc createNestedNode(\n\tcurrent *Node,\n\tbuf *buffer,\n\tnodeType ValueType,\n\tnesting int,\n\tkey **string,\n) (*Node, int, error) {\n\tvar err error\n\tif nesting, err = checkNestingDepth(nesting); err != nil {\n\t\treturn nil, nesting, err\n\t}\n\n\tif current, err = createNode(current, buf, nodeType, key); err != nil {\n\t\treturn nil, nesting, err\n\t}\n\n\treturn current, nesting, nil\n}\n\n// createLiteralNode creates a new literal node and sets the key if it is not nil.\n// The literal is a byte slice that represents a boolean or null value.\nfunc createLiteralNode(\n\tcurrent *Node,\n\tbuf *buffer,\n\tliteralType ValueType,\n\tliteral []byte,\n\tuseKey **string,\n\tnesting int,\n) (*Node, int, error) {\n\tvar err error\n\tif current, err = createNode(current, buf, literalType, useKey); err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\tif err = buf.word(literal); err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\tcurrent, nesting = updateNode(current, buf, nesting, false)\n\tbuf.state = OK\n\n\treturn current, nesting, nil\n}\n\n// updateNode updates the current node and returns the previous node.\nfunc updateNode(\n\tcurrent *Node, buf *buffer, nesting int, decreaseLevel bool,\n) (*Node, int) {\n\tcurrent.borders[1] = buf.index + 1\n\n\tprev := current.prev\n\tif prev == nil {\n\t\treturn current, nesting\n\t}\n\n\tcurrent = prev\n\tif decreaseLevel {\n\t\tnesting--\n\t}\n\n\treturn current, nesting\n}\n\n// updateNodeAndSetBufferState updates the current node and sets the buffer state to OK.\nfunc updateNodeAndSetBufferState(\n\tcurrent *Node,\n\tbuf *buffer,\n\tnesting int,\n\ttyp ValueType,\n) (*Node, int, error) {\n\tif !isValidContainerType(current, typ) {\n\t\treturn nil, nesting, unexpectedTokenError(buf.data, buf.index)\n\t}\n\n\tcurrent, nesting = updateNode(current, buf, nesting, true)\n\tbuf.state = OK\n\n\treturn current, nesting, nil\n}\n\n// checkNestingDepth checks if the nesting depth is within the maximum allowed depth.\nfunc checkNestingDepth(nesting int) (int, error) {\n\tif nesting \u003e= maxNestingDepth {\n\t\treturn nesting, errors.New(\"maximum nesting depth exceeded\")\n\t}\n\n\treturn nesting + 1, nil\n}\n\nfunc unexpectedTokenError(data []byte, index int) error {\n\treturn ufmt.Errorf(\"unexpected token at index %d. data %b\", index, data)\n}\n"},{"name":"decode_test.gno","body":"package json\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n)\n\ntype testNode struct {\n\tname string\n\tinput []byte\n\tvalue []byte\n\t_type ValueType\n}\n\nfunc simpleValid(test *testNode, t *testing.T) {\n\troot, err := Unmarshal(test.input)\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(%s): %s\", test.input, err.Error())\n\t} else if root == nil {\n\t\tt.Errorf(\"Error on Unmarshal(%s): root is nil\", test.name)\n\t} else if root.nodeType != test._type {\n\t\tt.Errorf(\"Error on Unmarshal(%s): wrong type\", test.name)\n\t} else if !bytes.Equal(root.source(), test.value) {\n\t\tt.Errorf(\"Error on Unmarshal(%s): %s != %s\", test.name, root.source(), test.value)\n\t}\n}\n\nfunc simpleInvalid(test *testNode, t *testing.T) {\n\troot, err := Unmarshal(test.input)\n\tif err == nil {\n\t\tt.Errorf(\"Error on Unmarshal(%s): error expected, got '%s'\", test.name, root.source())\n\t} else if root != nil {\n\t\tt.Errorf(\"Error on Unmarshal(%s): root is not nil\", test.name)\n\t}\n}\n\nfunc simpleCorrupted(name string) *testNode {\n\treturn \u0026testNode{name: name, input: []byte(name)}\n}\n\nfunc TestUnmarshal_StringSimpleSuccess(t *testing.T) {\n\ttests := []*testNode{\n\t\t{name: \"blank\", input: []byte(\"\\\"\\\"\"), _type: String, value: []byte(\"\\\"\\\"\")},\n\t\t{name: \"char\", input: []byte(\"\\\"c\\\"\"), _type: String, value: []byte(\"\\\"c\\\"\")},\n\t\t{name: \"word\", input: []byte(\"\\\"cat\\\"\"), _type: String, value: []byte(\"\\\"cat\\\"\")},\n\t\t{name: \"spaces\", input: []byte(\" \\\"good cat or dog\\\"\\r\\n \"), _type: String, value: []byte(\"\\\"good cat or dog\\\"\")},\n\t\t{name: \"backslash\", input: []byte(\"\\\"good \\\\\\\"cat\\\\\\\"\\\"\"), _type: String, value: []byte(\"\\\"good \\\\\\\"cat\\\\\\\"\\\"\")},\n\t\t{name: \"backslash 2\", input: []byte(\"\\\"good \\\\\\\\\\\\\\\"cat\\\\\\\"\\\"\"), _type: String, value: []byte(\"\\\"good \\\\\\\\\\\\\\\"cat\\\\\\\"\\\"\")},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tsimpleValid(test, t)\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal_NumericSimpleSuccess(t *testing.T) {\n\ttests := []*testNode{\n\t\t{name: \"1\", input: []byte(\"1\"), _type: Number, value: []byte(\"1\")},\n\t\t{name: \"-1\", input: []byte(\"-1\"), _type: Number, value: []byte(\"-1\")},\n\n\t\t{name: \"1234567890\", input: []byte(\"1234567890\"), _type: Number, value: []byte(\"1234567890\")},\n\t\t{name: \"-123\", input: []byte(\"-123\"), _type: Number, value: []byte(\"-123\")},\n\n\t\t{name: \"123.456\", input: []byte(\"123.456\"), _type: Number, value: []byte(\"123.456\")},\n\t\t{name: \"-123.456\", input: []byte(\"-123.456\"), _type: Number, value: []byte(\"-123.456\")},\n\n\t\t{name: \"1e3\", input: []byte(\"1e3\"), _type: Number, value: []byte(\"1e3\")},\n\t\t{name: \"1e+3\", input: []byte(\"1e+3\"), _type: Number, value: []byte(\"1e+3\")},\n\t\t{name: \"1e-3\", input: []byte(\"1e-3\"), _type: Number, value: []byte(\"1e-3\")},\n\t\t{name: \"-1e3\", input: []byte(\"-1e3\"), _type: Number, value: []byte(\"-1e3\")},\n\t\t{name: \"-1e-3\", input: []byte(\"-1e-3\"), _type: Number, value: []byte(\"-1e-3\")},\n\n\t\t{name: \"1.123e3456\", input: []byte(\"1.123e3456\"), _type: Number, value: []byte(\"1.123e3456\")},\n\t\t{name: \"1.123e-3456\", input: []byte(\"1.123e-3456\"), _type: Number, value: []byte(\"1.123e-3456\")},\n\t\t{name: \"-1.123e3456\", input: []byte(\"-1.123e3456\"), _type: Number, value: []byte(\"-1.123e3456\")},\n\t\t{name: \"-1.123e-3456\", input: []byte(\"-1.123e-3456\"), _type: Number, value: []byte(\"-1.123e-3456\")},\n\n\t\t{name: \"1E3\", input: []byte(\"1E3\"), _type: Number, value: []byte(\"1E3\")},\n\t\t{name: \"1E-3\", input: []byte(\"1E-3\"), _type: Number, value: []byte(\"1E-3\")},\n\t\t{name: \"-1E3\", input: []byte(\"-1E3\"), _type: Number, value: []byte(\"-1E3\")},\n\t\t{name: \"-1E-3\", input: []byte(\"-1E-3\"), _type: Number, value: []byte(\"-1E-3\")},\n\n\t\t{name: \"1.123E3456\", input: []byte(\"1.123E3456\"), _type: Number, value: []byte(\"1.123E3456\")},\n\t\t{name: \"1.123E-3456\", input: []byte(\"1.123E-3456\"), _type: Number, value: []byte(\"1.123E-3456\")},\n\t\t{name: \"-1.123E3456\", input: []byte(\"-1.123E3456\"), _type: Number, value: []byte(\"-1.123E3456\")},\n\t\t{name: \"-1.123E-3456\", input: []byte(\"-1.123E-3456\"), _type: Number, value: []byte(\"-1.123E-3456\")},\n\n\t\t{name: \"-1.123E-3456 with spaces\", input: []byte(\" \\r -1.123E-3456 \\t\\n\"), _type: Number, value: []byte(\"-1.123E-3456\")},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\troot, err := Unmarshal(test.input)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Error on Unmarshal(%s): %s\", test.name, err.Error())\n\t\t\t} else if root == nil {\n\t\t\t\tt.Errorf(\"Error on Unmarshal(%s): root is nil\", test.name)\n\t\t\t} else if root.nodeType != test._type {\n\t\t\t\tt.Errorf(\"Error on Unmarshal(%s): wrong type\", test.name)\n\t\t\t} else if !bytes.Equal(root.source(), test.value) {\n\t\t\t\tt.Errorf(\"Error on Unmarshal(%s): %s != %s\", test.name, root.source(), test.value)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal_StringSimpleCorrupted(t *testing.T) {\n\ttests := []*testNode{\n\t\t{name: \"white NL\", input: []byte(\"\\\"foo\\nbar\\\"\")},\n\t\t{name: \"white R\", input: []byte(\"\\\"foo\\rbar\\\"\")},\n\t\t{name: \"white Tab\", input: []byte(\"\\\"foo\\tbar\\\"\")},\n\t\t{name: \"wrong quotes\", input: []byte(\"'cat'\")},\n\t\t{name: \"double string\", input: []byte(\"\\\"Hello\\\" \\\"World\\\"\")},\n\t\t{name: \"quotes in quotes\", input: []byte(\"\\\"good \\\"cat\\\"\\\"\")},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tsimpleInvalid(test, t)\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal_ObjectSimpleSuccess(t *testing.T) {\n\ttests := []*testNode{\n\t\t{name: \"{}\", input: []byte(\"{}\"), _type: Object, value: []byte(\"{}\")},\n\t\t{name: `{ \\r\\n }`, input: []byte(\"{ \\r\\n }\"), _type: Object, value: []byte(\"{ \\r\\n }\")},\n\t\t{name: `{\"key\":1}`, input: []byte(`{\"key\":1}`), _type: Object, value: []byte(`{\"key\":1}`)},\n\t\t{name: `{\"key\":true}`, input: []byte(`{\"key\":true}`), _type: Object, value: []byte(`{\"key\":true}`)},\n\t\t{name: `{\"key\":\"value\"}`, input: []byte(`{\"key\":\"value\"}`), _type: Object, value: []byte(`{\"key\":\"value\"}`)},\n\t\t{name: `{\"foo\":\"bar\",\"baz\":\"foo\"}`, input: []byte(`{\"foo\":\"bar\", \"baz\":\"foo\"}`), _type: Object, value: []byte(`{\"foo\":\"bar\", \"baz\":\"foo\"}`)},\n\t\t{name: \"spaces\", input: []byte(` { \"foo\" : \"bar\" , \"baz\" : \"foo\" } `), _type: Object, value: []byte(`{ \"foo\" : \"bar\" , \"baz\" : \"foo\" }`)},\n\t\t{name: \"nested\", input: []byte(`{\"foo\":{\"bar\":{\"baz\":{}}}}`), _type: Object, value: []byte(`{\"foo\":{\"bar\":{\"baz\":{}}}}`)},\n\t\t{name: \"array\", input: []byte(`{\"array\":[{},{},{\"foo\":[{\"bar\":[\"baz\"]}]}]}`), _type: Object, value: []byte(`{\"array\":[{},{},{\"foo\":[{\"bar\":[\"baz\"]}]}]}`)},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tsimpleValid(test, t)\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal_ObjectSimpleCorrupted(t *testing.T) {\n\ttests := []*testNode{\n\t\tsimpleCorrupted(\"{{{\\\"key\\\": \\\"foo\\\"{{{{\"),\n\t\tsimpleCorrupted(\"}\"),\n\t\tsimpleCorrupted(\"{ }}}}}}}\"),\n\t\tsimpleCorrupted(\" }\"),\n\t\tsimpleCorrupted(\"{,}\"),\n\t\tsimpleCorrupted(\"{:}\"),\n\t\tsimpleCorrupted(\"{100000}\"),\n\t\tsimpleCorrupted(\"{1:1}\"),\n\t\tsimpleCorrupted(\"{'1:2,3:4'}\"),\n\t\tsimpleCorrupted(`{\"d\"}`),\n\t\tsimpleCorrupted(`{\"foo\"}`),\n\t\tsimpleCorrupted(`{\"foo\":}`),\n\t\tsimpleCorrupted(`{:\"foo\"}`),\n\t\tsimpleCorrupted(`{\"foo\":bar}`),\n\t\tsimpleCorrupted(`{\"foo\":\"bar\",}`),\n\t\tsimpleCorrupted(`{}{}`),\n\t\tsimpleCorrupted(`{},{}`),\n\t\tsimpleCorrupted(`{[},{]}`),\n\t\tsimpleCorrupted(`{[,]}`),\n\t\tsimpleCorrupted(`{[]}`),\n\t\tsimpleCorrupted(`{}1`),\n\t\tsimpleCorrupted(`1{}`),\n\t\tsimpleCorrupted(`{\"x\"::1}`),\n\t\tsimpleCorrupted(`{null:null}`),\n\t\tsimpleCorrupted(`{\"foo:\"bar\"}`),\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tsimpleInvalid(test, t)\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal_NullSimpleCorrupted(t *testing.T) {\n\ttests := []*testNode{\n\t\t{name: \"nul\", input: []byte(\"nul\")},\n\t\t{name: \"nil\", input: []byte(\"nil\")},\n\t\t{name: \"nill\", input: []byte(\"nill\")},\n\t\t{name: \"NILL\", input: []byte(\"NILL\")},\n\t\t{name: \"Null\", input: []byte(\"Null\")},\n\t\t{name: \"NULL\", input: []byte(\"NULL\")},\n\t\t{name: \"spaces\", input: []byte(\"Nu ll\")},\n\t\t{name: \"null1\", input: []byte(\"null1\")},\n\t\t{name: \"double\", input: []byte(\"null null\")},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tsimpleInvalid(test, t)\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal_BoolSimpleSuccess(t *testing.T) {\n\ttests := []*testNode{\n\t\t{name: \"lower true\", input: []byte(\"true\"), _type: Boolean, value: []byte(\"true\")},\n\t\t{name: \"lower false\", input: []byte(\"false\"), _type: Boolean, value: []byte(\"false\")},\n\t\t{name: \"spaces true\", input: []byte(\" true\\r\\n \"), _type: Boolean, value: []byte(\"true\")},\n\t\t{name: \"spaces false\", input: []byte(\" false\\r\\n \"), _type: Boolean, value: []byte(\"false\")},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tsimpleValid(test, t)\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal_BoolSimpleCorrupted(t *testing.T) {\n\ttests := []*testNode{\n\t\tsimpleCorrupted(\"tru\"),\n\t\tsimpleCorrupted(\"fals\"),\n\t\tsimpleCorrupted(\"tre\"),\n\t\tsimpleCorrupted(\"fal se\"),\n\t\tsimpleCorrupted(\"true false\"),\n\t\tsimpleCorrupted(\"True\"),\n\t\tsimpleCorrupted(\"TRUE\"),\n\t\tsimpleCorrupted(\"False\"),\n\t\tsimpleCorrupted(\"FALSE\"),\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tsimpleInvalid(test, t)\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal_ArraySimpleSuccess(t *testing.T) {\n\ttests := []*testNode{\n\t\t{name: \"[]\", input: []byte(\"[]\"), _type: Array, value: []byte(\"[]\")},\n\t\t{name: \"[1]\", input: []byte(\"[1]\"), _type: Array, value: []byte(\"[1]\")},\n\t\t{name: \"[1,2,3]\", input: []byte(\"[1,2,3]\"), _type: Array, value: []byte(\"[1,2,3]\")},\n\t\t{name: \"[1, 2, 3]\", input: []byte(\"[1, 2, 3]\"), _type: Array, value: []byte(\"[1, 2, 3]\")},\n\t\t{name: \"[1,[2],3]\", input: []byte(\"[1,[2],3]\"), _type: Array, value: []byte(\"[1,[2],3]\")},\n\t\t{name: \"[[],[],[]]\", input: []byte(\"[[],[],[]]\"), _type: Array, value: []byte(\"[[],[],[]]\")},\n\t\t{name: \"[[[[[]]]]]\", input: []byte(\"[[[[[]]]]]\"), _type: Array, value: []byte(\"[[[[[]]]]]\")},\n\t\t{name: \"[true,null,1,\\\"foo\\\",[]]\", input: []byte(\"[true,null,1,\\\"foo\\\",[]]\"), _type: Array, value: []byte(\"[true,null,1,\\\"foo\\\",[]]\")},\n\t\t{name: \"spaces\", input: []byte(\"\\n\\r [\\n1\\n ]\\r\\n\"), _type: Array, value: []byte(\"[\\n1\\n ]\")},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tsimpleValid(test, t)\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal_ArraySimpleCorrupted(t *testing.T) {\n\ttests := []*testNode{\n\t\tsimpleCorrupted(\"[,]\"),\n\t\tsimpleCorrupted(\"[]\\\\\"),\n\t\tsimpleCorrupted(\"[1,]\"),\n\t\tsimpleCorrupted(\"[[]\"),\n\t\tsimpleCorrupted(\"[]]\"),\n\t\tsimpleCorrupted(\"1[]\"),\n\t\tsimpleCorrupted(\"[]1\"),\n\t\tsimpleCorrupted(\"[[]1]\"),\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tsimpleInvalid(test, t)\n\t\t})\n\t}\n}\n\n// Examples from https://json.org/example.html\nfunc TestUnmarshal(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tvalue string\n\t}{\n\t\t{\n\t\t\tname: \"glossary\",\n\t\t\tvalue: `{\n\t\t\t\t\"glossary\": {\n\t\t\t\t\t\"title\": \"example glossary\",\n\t\t\t\t\t\"GlossDiv\": {\n\t\t\t\t\t\t\"title\": \"S\",\n\t\t\t\t\t\t\"GlossList\": {\n\t\t\t\t\t\t\t\"GlossEntry\": {\n\t\t\t\t\t\t\t\t\"ID\": \"SGML\",\n\t\t\t\t\t\t\t\t\"SortAs\": \"SGML\",\n\t\t\t\t\t\t\t\t\"GlossTerm\": \"Standard Generalized Markup Language\",\n\t\t\t\t\t\t\t\t\"Acronym\": \"SGML\",\n\t\t\t\t\t\t\t\t\"Abbrev\": \"ISO 8879:1986\",\n\t\t\t\t\t\t\t\t\"GlossDef\": {\n\t\t\t\t\t\t\t\t\t\"para\": \"A meta-markup language, used to create markup languages such as DocBook.\",\n\t\t\t\t\t\t\t\t\t\"GlossSeeAlso\": [\"GML\", \"XML\"]\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"GlossSee\": \"markup\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}`,\n\t\t},\n\t\t{\n\t\t\tname: \"menu\",\n\t\t\tvalue: `{\"menu\": {\n\t\t\t\t\"id\": \"file\",\n\t\t\t\t\"value\": \"File\",\n\t\t\t\t\"popup\": {\n\t\t\t\t \"menuitem\": [\n\t\t\t\t\t{\"value\": \"New\", \"onclick\": \"CreateNewDoc()\"},\n\t\t\t\t\t{\"value\": \"Open\", \"onclick\": \"OpenDoc()\"},\n\t\t\t\t\t{\"value\": \"Close\", \"onclick\": \"CloseDoc()\"}\n\t\t\t\t ]\n\t\t\t\t}\n\t\t\t}}`,\n\t\t},\n\t\t{\n\t\t\tname: \"widget\",\n\t\t\tvalue: `{\"widget\": {\n\t\t\t\t\"debug\": \"on\",\n\t\t\t\t\"window\": {\n\t\t\t\t\t\"title\": \"Sample Konfabulator Widget\",\n\t\t\t\t\t\"name\": \"main_window\",\n\t\t\t\t\t\"width\": 500,\n\t\t\t\t\t\"height\": 500\n\t\t\t\t},\n\t\t\t\t\"image\": { \n\t\t\t\t\t\"src\": \"Images/Sun.png\",\n\t\t\t\t\t\"name\": \"sun1\",\n\t\t\t\t\t\"hOffset\": 250,\n\t\t\t\t\t\"vOffset\": 250,\n\t\t\t\t\t\"alignment\": \"center\"\n\t\t\t\t},\n\t\t\t\t\"text\": {\n\t\t\t\t\t\"data\": \"Click Here\",\n\t\t\t\t\t\"size\": 36,\n\t\t\t\t\t\"style\": \"bold\",\n\t\t\t\t\t\"name\": \"text1\",\n\t\t\t\t\t\"hOffset\": 250,\n\t\t\t\t\t\"vOffset\": 100,\n\t\t\t\t\t\"alignment\": \"center\",\n\t\t\t\t\t\"onMouseUp\": \"sun1.opacity = (sun1.opacity / 100) * 90;\"\n\t\t\t\t}\n\t\t\t}} `,\n\t\t},\n\t\t{\n\t\t\tname: \"web-app\",\n\t\t\tvalue: `{\"web-app\": {\n\t\t\t\t\"servlet\": [ \n\t\t\t\t {\n\t\t\t\t\t\"servlet-name\": \"cofaxCDS\",\n\t\t\t\t\t\"servlet-class\": \"org.cofax.cds.CDSServlet\",\n\t\t\t\t\t\"init-param\": {\n\t\t\t\t\t \"configGlossary:installationAt\": \"Philadelphia, PA\",\n\t\t\t\t\t \"configGlossary:adminEmail\": \"ksm@pobox.com\",\n\t\t\t\t\t \"configGlossary:poweredBy\": \"Cofax\",\n\t\t\t\t\t \"configGlossary:poweredByIcon\": \"/images/cofax.gif\",\n\t\t\t\t\t \"configGlossary:staticPath\": \"/content/static\",\n\t\t\t\t\t \"templateProcessorClass\": \"org.cofax.WysiwygTemplate\",\n\t\t\t\t\t \"templateLoaderClass\": \"org.cofax.FilesTemplateLoader\",\n\t\t\t\t\t \"templatePath\": \"templates\",\n\t\t\t\t\t \"templateOverridePath\": \"\",\n\t\t\t\t\t \"defaultListTemplate\": \"listTemplate.htm\",\n\t\t\t\t\t \"defaultFileTemplate\": \"articleTemplate.htm\",\n\t\t\t\t\t \"useJSP\": false,\n\t\t\t\t\t \"jspListTemplate\": \"listTemplate.jsp\",\n\t\t\t\t\t \"jspFileTemplate\": \"articleTemplate.jsp\",\n\t\t\t\t\t \"cachePackageTagsTrack\": 200,\n\t\t\t\t\t \"cachePackageTagsStore\": 200,\n\t\t\t\t\t \"cachePackageTagsRefresh\": 60,\n\t\t\t\t\t \"cacheTemplatesTrack\": 100,\n\t\t\t\t\t \"cacheTemplatesStore\": 50,\n\t\t\t\t\t \"cacheTemplatesRefresh\": 15,\n\t\t\t\t\t \"cachePagesTrack\": 200,\n\t\t\t\t\t \"cachePagesStore\": 100,\n\t\t\t\t\t \"cachePagesRefresh\": 10,\n\t\t\t\t\t \"cachePagesDirtyRead\": 10,\n\t\t\t\t\t \"searchEngineListTemplate\": \"forSearchEnginesList.htm\",\n\t\t\t\t\t \"searchEngineFileTemplate\": \"forSearchEngines.htm\",\n\t\t\t\t\t \"searchEngineRobotsDb\": \"WEB-INF/robots.db\",\n\t\t\t\t\t \"useDataStore\": true,\n\t\t\t\t\t \"dataStoreClass\": \"org.cofax.SqlDataStore\",\n\t\t\t\t\t \"redirectionClass\": \"org.cofax.SqlRedirection\",\n\t\t\t\t\t \"dataStoreName\": \"cofax\",\n\t\t\t\t\t \"dataStoreDriver\": \"com.microsoft.jdbc.sqlserver.SQLServerDriver\",\n\t\t\t\t\t \"dataStoreUrl\": \"jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon\",\n\t\t\t\t\t \"dataStoreUser\": \"sa\",\n\t\t\t\t\t \"dataStorePassword\": \"dataStoreTestQuery\",\n\t\t\t\t\t \"dataStoreTestQuery\": \"SET NOCOUNT ON;select test='test';\",\n\t\t\t\t\t \"dataStoreLogFile\": \"/usr/local/tomcat/logs/datastore.log\",\n\t\t\t\t\t \"dataStoreInitConns\": 10,\n\t\t\t\t\t \"dataStoreMaxConns\": 100,\n\t\t\t\t\t \"dataStoreConnUsageLimit\": 100,\n\t\t\t\t\t \"dataStoreLogLevel\": \"debug\",\n\t\t\t\t\t \"maxUrlLength\": 500}},\n\t\t\t\t {\n\t\t\t\t\t\"servlet-name\": \"cofaxEmail\",\n\t\t\t\t\t\"servlet-class\": \"org.cofax.cds.EmailServlet\",\n\t\t\t\t\t\"init-param\": {\n\t\t\t\t\t\"mailHost\": \"mail1\",\n\t\t\t\t\t\"mailHostOverride\": \"mail2\"}},\n\t\t\t\t {\n\t\t\t\t\t\"servlet-name\": \"cofaxAdmin\",\n\t\t\t\t\t\"servlet-class\": \"org.cofax.cds.AdminServlet\"},\n\t\t\t \n\t\t\t\t {\n\t\t\t\t\t\"servlet-name\": \"fileServlet\",\n\t\t\t\t\t\"servlet-class\": \"org.cofax.cds.FileServlet\"},\n\t\t\t\t {\n\t\t\t\t\t\"servlet-name\": \"cofaxTools\",\n\t\t\t\t\t\"servlet-class\": \"org.cofax.cms.CofaxToolsServlet\",\n\t\t\t\t\t\"init-param\": {\n\t\t\t\t\t \"templatePath\": \"toolstemplates/\",\n\t\t\t\t\t \"log\": 1,\n\t\t\t\t\t \"logLocation\": \"/usr/local/tomcat/logs/CofaxTools.log\",\n\t\t\t\t\t \"logMaxSize\": \"\",\n\t\t\t\t\t \"dataLog\": 1,\n\t\t\t\t\t \"dataLogLocation\": \"/usr/local/tomcat/logs/dataLog.log\",\n\t\t\t\t\t \"dataLogMaxSize\": \"\",\n\t\t\t\t\t \"removePageCache\": \"/content/admin/remove?cache=pages\u0026id=\",\n\t\t\t\t\t \"removeTemplateCache\": \"/content/admin/remove?cache=templates\u0026id=\",\n\t\t\t\t\t \"fileTransferFolder\": \"/usr/local/tomcat/webapps/content/fileTransferFolder\",\n\t\t\t\t\t \"lookInContext\": 1,\n\t\t\t\t\t \"adminGroupID\": 4,\n\t\t\t\t\t \"betaServer\": true}}],\n\t\t\t\t\"servlet-mapping\": {\n\t\t\t\t \"cofaxCDS\": \"/\",\n\t\t\t\t \"cofaxEmail\": \"/cofaxutil/aemail/*\",\n\t\t\t\t \"cofaxAdmin\": \"/admin/*\",\n\t\t\t\t \"fileServlet\": \"/static/*\",\n\t\t\t\t \"cofaxTools\": \"/tools/*\"},\n\t\t\t \n\t\t\t\t\"taglib\": {\n\t\t\t\t \"taglib-uri\": \"cofax.tld\",\n\t\t\t\t \"taglib-location\": \"/WEB-INF/tlds/cofax.tld\"}}}`,\n\t\t},\n\t\t{\n\t\t\tname: \"SVG Viewer\",\n\t\t\tvalue: `{\"menu\": {\n\t\t\t\t\"header\": \"SVG Viewer\",\n\t\t\t\t\"items\": [\n\t\t\t\t\t{\"id\": \"Open\"},\n\t\t\t\t\t{\"id\": \"OpenNew\", \"label\": \"Open New\"},\n\t\t\t\t\tnull,\n\t\t\t\t\t{\"id\": \"ZoomIn\", \"label\": \"Zoom In\"},\n\t\t\t\t\t{\"id\": \"ZoomOut\", \"label\": \"Zoom Out\"},\n\t\t\t\t\t{\"id\": \"OriginalView\", \"label\": \"Original View\"},\n\t\t\t\t\tnull,\n\t\t\t\t\t{\"id\": \"Quality\"},\n\t\t\t\t\t{\"id\": \"Pause\"},\n\t\t\t\t\t{\"id\": \"Mute\"},\n\t\t\t\t\tnull,\n\t\t\t\t\t{\"id\": \"Find\", \"label\": \"Find...\"},\n\t\t\t\t\t{\"id\": \"FindAgain\", \"label\": \"Find Again\"},\n\t\t\t\t\t{\"id\": \"Copy\"},\n\t\t\t\t\t{\"id\": \"CopyAgain\", \"label\": \"Copy Again\"},\n\t\t\t\t\t{\"id\": \"CopySVG\", \"label\": \"Copy SVG\"},\n\t\t\t\t\t{\"id\": \"ViewSVG\", \"label\": \"View SVG\"},\n\t\t\t\t\t{\"id\": \"ViewSource\", \"label\": \"View Source\"},\n\t\t\t\t\t{\"id\": \"SaveAs\", \"label\": \"Save As\"},\n\t\t\t\t\tnull,\n\t\t\t\t\t{\"id\": \"Help\"},\n\t\t\t\t\t{\"id\": \"About\", \"label\": \"About Adobe CVG Viewer...\"}\n\t\t\t\t]\n\t\t\t}}`,\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\t_, err := Unmarshal([]byte(test.value))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Error on Unmarshal: %s\", err.Error())\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUnmarshalSafe(t *testing.T) {\n\tjson := []byte(`{ \"store\": {\n\t\t\"book\": [ \n\t\t { \"category\": \"reference\",\n\t\t\t\"author\": \"Nigel Rees\",\n\t\t\t\"title\": \"Sayings of the Century\",\n\t\t\t\"price\": 8.95\n\t\t },\n\t\t { \"category\": \"fiction\",\n\t\t\t\"author\": \"Evelyn Waugh\",\n\t\t\t\"title\": \"Sword of Honour\",\n\t\t\t\"price\": 12.99\n\t\t },\n\t\t { \"category\": \"fiction\",\n\t\t\t\"author\": \"Herman Melville\",\n\t\t\t\"title\": \"Moby Dick\",\n\t\t\t\"isbn\": \"0-553-21311-3\",\n\t\t\t\"price\": 8.99\n\t\t },\n\t\t { \"category\": \"fiction\",\n\t\t\t\"author\": \"J. R. R. Tolkien\",\n\t\t\t\"title\": \"The Lord of the Rings\",\n\t\t\t\"isbn\": \"0-395-19395-8\",\n\t\t\t\"price\": 22.99\n\t\t }\n\t\t],\n\t\t\"bicycle\": {\n\t\t \"color\": \"red\",\n\t\t \"price\": 19.95\n\t\t}\n\t }\n\t}`)\n\tsafe, err := UnmarshalSafe(json)\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal: %s\", err.Error())\n\t} else if safe == nil {\n\t\tt.Errorf(\"Error on Unmarshal: safe is nil\")\n\t} else {\n\t\troot, err := Unmarshal(json)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"Error on Unmarshal: %s\", err.Error())\n\t\t} else if root == nil {\n\t\t\tt.Errorf(\"Error on Unmarshal: root is nil\")\n\t\t} else if !bytes.Equal(root.source(), safe.source()) {\n\t\t\tt.Errorf(\"Error on UnmarshalSafe: values not same\")\n\t\t}\n\t}\n}\n\n// BenchmarkGoStdUnmarshal-8 \t 61698\t 19350 ns/op\t 288 B/op\t 6 allocs/op\n// BenchmarkUnmarshal-8 \t 45620\t 26165 ns/op\t 21889 B/op\t 367 allocs/op\n//\n// type bench struct {\n// \tName string `json:\"name\"`\n// \tValue int `json:\"value\"`\n// }\n\n// func BenchmarkGoStdUnmarshal(b *testing.B) {\n// \tdata := []byte(webApp)\n// \tfor i := 0; i \u003c b.N; i++ {\n// \t\terr := json.Unmarshal(data, \u0026bench{})\n// \t\tif err != nil {\n// \t\t\tb.Fatal(err)\n// \t\t}\n// \t}\n// }\n\n// func BenchmarkUnmarshal(b *testing.B) {\n// \tdata := []byte(webApp)\n// \tfor i := 0; i \u003c b.N; i++ {\n// \t\t_, err := Unmarshal(data)\n// \t\tif err != nil {\n// \t\t\tb.Fatal(err)\n// \t\t}\n// \t}\n// }\n"},{"name":"encode.gno","body":"package json\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Marshal returns the JSON encoding of a Node.\nfunc Marshal(node *Node) ([]byte, error) {\n\tvar (\n\t\tbuf bytes.Buffer\n\t\tsVal string\n\t\tbVal bool\n\t\tnVal float64\n\t\toVal []byte\n\t\terr error\n\t)\n\n\tif node == nil {\n\t\treturn nil, errors.New(\"node is nil\")\n\t}\n\n\tif !node.modified \u0026\u0026 !node.ready() {\n\t\treturn nil, errors.New(\"node is not ready\")\n\t}\n\n\tif !node.modified \u0026\u0026 node.ready() {\n\t\tbuf.Write(node.source())\n\t}\n\n\tif node.modified {\n\t\tswitch node.nodeType {\n\t\tcase Null:\n\t\t\tbuf.Write(nullLiteral)\n\n\t\tcase Number:\n\t\t\tnVal, err = node.GetNumeric()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tnum := strconv.FormatFloat(nVal, 'f', -1, 64)\n\t\t\tbuf.WriteString(num)\n\n\t\tcase String:\n\t\t\tsVal, err = node.GetString()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tquoted := ufmt.Sprintf(\"%s\", strconv.Quote(sVal))\n\t\t\tbuf.WriteString(quoted)\n\n\t\tcase Boolean:\n\t\t\tbVal, err = node.GetBool()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tbStr := ufmt.Sprintf(\"%t\", bVal)\n\t\t\tbuf.WriteString(bStr)\n\n\t\tcase Array:\n\t\t\tbuf.WriteByte(bracketOpen)\n\n\t\t\tfor i := 0; i \u003c len(node.next); i++ {\n\t\t\t\tif i != 0 {\n\t\t\t\t\tbuf.WriteByte(comma)\n\t\t\t\t}\n\n\t\t\t\telem, ok := node.next[strconv.Itoa(i)]\n\t\t\t\tif !ok {\n\t\t\t\t\treturn nil, ufmt.Errorf(\"array element %d is not found\", i)\n\t\t\t\t}\n\n\t\t\t\toVal, err = Marshal(elem)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\t\tbuf.Write(oVal)\n\t\t\t}\n\n\t\t\tbuf.WriteByte(bracketClose)\n\n\t\tcase Object:\n\t\t\tbuf.WriteByte(curlyOpen)\n\n\t\t\tbVal = false\n\t\t\tfor k, v := range node.next {\n\t\t\t\tif bVal {\n\t\t\t\t\tbuf.WriteByte(comma)\n\t\t\t\t} else {\n\t\t\t\t\tbVal = true\n\t\t\t\t}\n\n\t\t\t\tkey := ufmt.Sprintf(\"%s\", strconv.Quote(k))\n\t\t\t\tbuf.WriteString(key)\n\t\t\t\tbuf.WriteByte(colon)\n\n\t\t\t\toVal, err = Marshal(v)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\t\tbuf.Write(oVal)\n\t\t\t}\n\n\t\t\tbuf.WriteByte(curlyClose)\n\t\t}\n\t}\n\n\treturn buf.Bytes(), nil\n}\n"},{"name":"encode_test.gno","body":"package json\n\nimport \"testing\"\n\nfunc TestMarshal_Primitive(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tnode *Node\n\t}{\n\t\t{\n\t\t\tname: \"null\",\n\t\t\tnode: NullNode(\"\"),\n\t\t},\n\t\t{\n\t\t\tname: \"true\",\n\t\t\tnode: BoolNode(\"\", true),\n\t\t},\n\t\t{\n\t\t\tname: \"false\",\n\t\t\tnode: BoolNode(\"\", false),\n\t\t},\n\t\t{\n\t\t\tname: `\"string\"`,\n\t\t\tnode: StringNode(\"\", \"string\"),\n\t\t},\n\t\t{\n\t\t\tname: `\"one \\\"encoded\\\" string\"`,\n\t\t\tnode: StringNode(\"\", `one \"encoded\" string`),\n\t\t},\n\t\t{\n\t\t\tname: `{\"foo\":\"bar\"}`,\n\t\t\tnode: ObjectNode(\"\", map[string]*Node{\n\t\t\t\t\"foo\": StringNode(\"foo\", \"bar\"),\n\t\t\t}),\n\t\t},\n\t\t{\n\t\t\tname: \"42\",\n\t\t\tnode: NumberNode(\"\", 42),\n\t\t},\n\t\t{\n\t\t\tname: \"3.14\",\n\t\t\tnode: NumberNode(\"\", 3.14),\n\t\t},\n\t\t{\n\t\t\tname: `[1,2,3]`,\n\t\t\tnode: ArrayNode(\"\", []*Node{\n\t\t\t\tNumberNode(\"0\", 1),\n\t\t\t\tNumberNode(\"2\", 2),\n\t\t\t\tNumberNode(\"3\", 3),\n\t\t\t}),\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tvalue, err := Marshal(test.node)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t} else if string(value) != test.name {\n\t\t\t\tt.Errorf(\"wrong result: '%s', expected '%s'\", value, test.name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestMarshal_Object(t *testing.T) {\n\tnode := ObjectNode(\"\", map[string]*Node{\n\t\t\"foo\": StringNode(\"foo\", \"bar\"),\n\t\t\"baz\": NumberNode(\"baz\", 100500),\n\t\t\"qux\": NullNode(\"qux\"),\n\t})\n\n\tmustKey := []string{\"foo\", \"baz\", \"qux\"}\n\n\tvalue, err := Marshal(node)\n\tif err != nil {\n\t\tt.Errorf(\"unexpected error: %s\", err)\n\t}\n\n\t// the order of keys in the map is not guaranteed\n\t// so we need to unmarshal the result and check the keys\n\tdecoded, err := Unmarshal(value)\n\tif err != nil {\n\t\tt.Errorf(\"unexpected error: %s\", err)\n\t}\n\n\tfor _, key := range mustKey {\n\t\tif node, err := decoded.GetKey(key); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t} else {\n\t\t\tif node == nil {\n\t\t\t\tt.Errorf(\"node is nil\")\n\t\t\t} else if node.key == nil {\n\t\t\t\tt.Errorf(\"key is nil\")\n\t\t\t} else if *node.key != key {\n\t\t\t\tt.Errorf(\"wrong key: '%s', expected '%s'\", *node.key, key)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc valueNode(prev *Node, key string, typ ValueType, val interface{}) *Node {\n\tcurr := \u0026Node{\n\t\tprev: prev,\n\t\tdata: nil,\n\t\tkey: \u0026key,\n\t\tborders: [2]int{0, 0},\n\t\tvalue: val,\n\t\tmodified: true,\n\t}\n\n\tif val != nil {\n\t\tcurr.nodeType = typ\n\t}\n\n\treturn curr\n}\n\nfunc TestMarshal_Errors(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tnode func() (node *Node)\n\t}{\n\t\t{\n\t\t\tname: \"nil\",\n\t\t\tnode: func() (node *Node) {\n\t\t\t\treturn\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"broken\",\n\t\t\tnode: func() (node *Node) {\n\t\t\t\tnode = Must(Unmarshal([]byte(`{}`)))\n\t\t\t\tnode.borders[1] = 0\n\t\t\t\treturn\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Numeric\",\n\t\t\tnode: func() (node *Node) {\n\t\t\t\treturn valueNode(nil, \"\", Number, false)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"String\",\n\t\t\tnode: func() (node *Node) {\n\t\t\t\treturn valueNode(nil, \"\", String, false)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Bool\",\n\t\t\tnode: func() (node *Node) {\n\t\t\t\treturn valueNode(nil, \"\", Boolean, 1)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Array_1\",\n\t\t\tnode: func() (node *Node) {\n\t\t\t\tnode = ArrayNode(\"\", nil)\n\t\t\t\tnode.next[\"1\"] = NullNode(\"1\")\n\t\t\t\treturn\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Array_2\",\n\t\t\tnode: func() (node *Node) {\n\t\t\t\treturn ArrayNode(\"\", []*Node{valueNode(nil, \"\", Boolean, 1)})\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Object\",\n\t\t\tnode: func() (node *Node) {\n\t\t\t\treturn ObjectNode(\"\", map[string]*Node{\"key\": valueNode(nil, \"key\", Boolean, 1)})\n\t\t\t},\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tvalue, err := Marshal(test.node())\n\t\t\tif err == nil {\n\t\t\t\tt.Errorf(\"expected error\")\n\t\t\t} else if len(value) != 0 {\n\t\t\t\tt.Errorf(\"wrong result\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestMarshal_Nil(t *testing.T) {\n\t_, err := Marshal(nil)\n\tif err == nil {\n\t\tt.Error(\"Expected error for nil node, but got nil\")\n\t}\n}\n\nfunc TestMarshal_NotModified(t *testing.T) {\n\tnode := \u0026Node{}\n\t_, err := Marshal(node)\n\tif err == nil {\n\t\tt.Error(\"Expected error for not modified node, but got nil\")\n\t}\n}\n\nfunc TestMarshalCycleReference(t *testing.T) {\n\tnode1 := \u0026Node{\n\t\tkey: stringPtr(\"node1\"),\n\t\tnodeType: String,\n\t\tnext: map[string]*Node{\n\t\t\t\"next\": nil,\n\t\t},\n\t}\n\n\tnode2 := \u0026Node{\n\t\tkey: stringPtr(\"node2\"),\n\t\tnodeType: String,\n\t\tprev: node1,\n\t}\n\n\tnode1.next[\"next\"] = node2\n\n\t_, err := Marshal(node1)\n\tif err == nil {\n\t\tt.Error(\"Expected error for cycle reference, but got nil\")\n\t}\n}\n\nfunc TestMarshalNoCycleReference(t *testing.T) {\n\tnode1 := \u0026Node{\n\t\tkey: stringPtr(\"node1\"),\n\t\tnodeType: String,\n\t\tvalue: \"value1\",\n\t\tmodified: true,\n\t}\n\n\tnode2 := \u0026Node{\n\t\tkey: stringPtr(\"node2\"),\n\t\tnodeType: String,\n\t\tvalue: \"value2\",\n\t\tmodified: true,\n\t}\n\n\t_, err := Marshal(node1)\n\tif err != nil {\n\t\tt.Errorf(\"Unexpected error: %v\", err)\n\t}\n\n\t_, err = Marshal(node2)\n\tif err != nil {\n\t\tt.Errorf(\"Unexpected error: %v\", err)\n\t}\n}\n\nfunc stringPtr(s string) *string {\n\treturn \u0026s\n}\n"},{"name":"errors.gno","body":"package json\n\nimport \"errors\"\n\nvar (\n\terrNilNode = errors.New(\"node is nil\")\n\terrNotArrayNode = errors.New(\"node is not array\")\n\terrNotBoolNode = errors.New(\"node is not boolean\")\n\terrNotNullNode = errors.New(\"node is not null\")\n\terrNotNumberNode = errors.New(\"node is not number\")\n\terrNotObjectNode = errors.New(\"node is not object\")\n\terrNotStringNode = errors.New(\"node is not string\")\n\terrInvalidToken = errors.New(\"invalid token\")\n\terrIndexNotFound = errors.New(\"index not found\")\n\terrInvalidAppend = errors.New(\"can't append value to non-appendable node\")\n\terrInvalidAppendCycle = errors.New(\"appending value to itself or its children or parents will cause a cycle\")\n\terrInvalidEscapeSequence = errors.New(\"invalid escape sequence\")\n\terrInvalidStringValue = errors.New(\"invalid string value\")\n\terrEmptyBooleanNode = errors.New(\"boolean node is empty\")\n\terrEmptyStringNode = errors.New(\"string node is empty\")\n\terrKeyRequired = errors.New(\"key is required for object\")\n\terrUnmatchedParenthesis = errors.New(\"mismatched bracket or parenthesis\")\n\terrUnmatchedQuotePath = errors.New(\"unmatched quote in path\")\n)\n\nvar (\n\terrInvalidStringInput = errors.New(\"invalid string input\")\n\terrMalformedBooleanValue = errors.New(\"malformed boolean value\")\n\terrEmptyByteSlice = errors.New(\"empty byte slice\")\n\terrInvalidExponentValue = errors.New(\"invalid exponent value\")\n\terrNonDigitCharacters = errors.New(\"non-digit characters found\")\n\terrNumericRangeExceeded = errors.New(\"numeric value exceeds the range limit\")\n\terrMultipleDecimalPoints = errors.New(\"multiple decimal points found\")\n)\n"},{"name":"escape.gno","body":"package json\n\nimport (\n\t\"unicode/utf8\"\n)\n\nconst (\n\tsupplementalPlanesOffset = 0x10000\n\thighSurrogateOffset = 0xD800\n\tlowSurrogateOffset = 0xDC00\n\tsurrogateEnd = 0xDFFF\n\tbasicMultilingualPlaneOffset = 0xFFFF\n\tbadHex = -1\n\n\tsingleUnicodeEscapeLen = 6\n\tsurrogatePairLen = 12\n)\n\nvar hexLookupTable = [256]int{\n\t'0': 0x0, '1': 0x1, '2': 0x2, '3': 0x3, '4': 0x4,\n\t'5': 0x5, '6': 0x6, '7': 0x7, '8': 0x8, '9': 0x9,\n\t'A': 0xA, 'B': 0xB, 'C': 0xC, 'D': 0xD, 'E': 0xE, 'F': 0xF,\n\t'a': 0xA, 'b': 0xB, 'c': 0xC, 'd': 0xD, 'e': 0xE, 'f': 0xF,\n\t// Fill unspecified index-value pairs with key and value of -1\n\t'G': -1, 'H': -1, 'I': -1, 'J': -1,\n\t'K': -1, 'L': -1, 'M': -1, 'N': -1,\n\t'O': -1, 'P': -1, 'Q': -1, 'R': -1,\n\t'S': -1, 'T': -1, 'U': -1, 'V': -1,\n\t'W': -1, 'X': -1, 'Y': -1, 'Z': -1,\n\t'g': -1, 'h': -1, 'i': -1, 'j': -1,\n\t'k': -1, 'l': -1, 'm': -1, 'n': -1,\n\t'o': -1, 'p': -1, 'q': -1, 'r': -1,\n\t's': -1, 't': -1, 'u': -1, 'v': -1,\n\t'w': -1, 'x': -1, 'y': -1, 'z': -1,\n}\n\nfunc h2i(c byte) int {\n\treturn hexLookupTable[c]\n}\n\n// Unescape takes an input byte slice, processes it to Unescape certain characters,\n// and writes the result into an output byte slice.\n//\n// it returns the processed slice and any error encountered during the Unescape operation.\nfunc Unescape(input, output []byte) ([]byte, error) {\n\t// ensure the output slice has enough capacity to hold the input slice.\n\tinputLen := len(input)\n\tif cap(output) \u003c inputLen {\n\t\toutput = make([]byte, inputLen)\n\t}\n\n\tinPos, outPos := 0, 0\n\n\tfor inPos \u003c len(input) {\n\t\tc := input[inPos]\n\t\tif c != backSlash {\n\t\t\toutput[outPos] = c\n\t\t\tinPos++\n\t\t\toutPos++\n\t\t} else {\n\t\t\t// process escape sequence\n\t\t\tinLen, outLen, err := processEscapedUTF8(input[inPos:], output[outPos:])\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tinPos += inLen\n\t\t\toutPos += outLen\n\t\t}\n\t}\n\n\treturn output[:outPos], nil\n}\n\n// isSurrogatePair returns true if the rune is a surrogate pair.\n//\n// A surrogate pairs are used in UTF-16 encoding to encode characters\n// outside the Basic Multilingual Plane (BMP).\nfunc isSurrogatePair(r rune) bool {\n\treturn highSurrogateOffset \u003c= r \u0026\u0026 r \u003c= surrogateEnd\n}\n\n// isHighSurrogate checks if the rune is a high surrogate (U+D800 to U+DBFF).\nfunc isHighSurrogate(r rune) bool {\n\treturn r \u003e= highSurrogateOffset \u0026\u0026 r \u003c= 0xDBFF\n}\n\n// isLowSurrogate checks if the rune is a low surrogate (U+DC00 to U+DFFF).\nfunc isLowSurrogate(r rune) bool {\n\treturn r \u003e= lowSurrogateOffset \u0026\u0026 r \u003c= surrogateEnd\n}\n\n// combineSurrogates reconstruct the original unicode code points in the\n// supplemental plane by combinin the high and low surrogate.\n//\n// The hight surrogate in the range from U+D800 to U+DBFF,\n// and the low surrogate in the range from U+DC00 to U+DFFF.\n//\n// The formula to combine the surrogates is:\n// (high - 0xD800) * 0x400 + (low - 0xDC00) + 0x10000\nfunc combineSurrogates(high, low rune) rune {\n\treturn ((high - highSurrogateOffset) \u003c\u003c 10) + (low - lowSurrogateOffset) + supplementalPlanesOffset\n}\n\n// deocdeSingleUnicodeEscape decodes a unicode escape sequence (e.g., \\uXXXX) into a rune.\nfunc decodeSingleUnicodeEscape(b []byte) (rune, bool) {\n\tif len(b) \u003c 6 {\n\t\treturn utf8.RuneError, false\n\t}\n\n\t// convert hex to decimal\n\th1, h2, h3, h4 := h2i(b[2]), h2i(b[3]), h2i(b[4]), h2i(b[5])\n\tif h1 == badHex || h2 == badHex || h3 == badHex || h4 == badHex {\n\t\treturn utf8.RuneError, false\n\t}\n\n\treturn rune(h1\u003c\u003c12 + h2\u003c\u003c8 + h3\u003c\u003c4 + h4), true\n}\n\n// decodeUnicodeEscape decodes a Unicode escape sequence from a byte slice.\n// It handles both single Unicode escape sequences and surrogate pairs.\nfunc decodeUnicodeEscape(b []byte) (rune, int) {\n\t// decode the first Unicode escape sequence.\n\tr, ok := decodeSingleUnicodeEscape(b)\n\tif !ok {\n\t\treturn utf8.RuneError, -1\n\t}\n\n\t// if the rune is within the BMP and not a surrogate, return it\n\tif r \u003c= basicMultilingualPlaneOffset \u0026\u0026 !isSurrogatePair(r) {\n\t\treturn r, 6\n\t}\n\n\tif !isHighSurrogate(r) {\n\t\t// invalid surrogate pair.\n\t\treturn utf8.RuneError, -1\n\t}\n\n\t// if the rune is a high surrogate, need to decode the next escape sequence.\n\n\t// ensure there are enough bytes for the next escape sequence.\n\tif len(b) \u003c surrogatePairLen {\n\t\treturn utf8.RuneError, -1\n\t}\n\t// decode the second Unicode escape sequence.\n\tr2, ok := decodeSingleUnicodeEscape(b[singleUnicodeEscapeLen:])\n\tif !ok {\n\t\treturn utf8.RuneError, -1\n\t}\n\t// check if the second rune is a low surrogate.\n\tif isLowSurrogate(r2) {\n\t\tcombined := combineSurrogates(r, r2)\n\t\treturn combined, surrogatePairLen\n\t}\n\treturn utf8.RuneError, -1\n}\n\nvar escapeByteSet = [256]byte{\n\t'\"': doubleQuote,\n\t'\\\\': backSlash,\n\t'/': slash,\n\t'b': backSpace,\n\t'f': formFeed,\n\t'n': newLine,\n\t'r': carriageReturn,\n\t't': tab,\n}\n\n// Unquote takes a byte slice and unquotes it by removing\n// the surrounding quotes and unescaping the contents.\nfunc Unquote(s []byte, border byte) (string, bool) {\n\ts, ok := unquoteBytes(s, border)\n\treturn string(s), ok\n}\n\n// unquoteBytes takes a byte slice and unquotes it by removing\nfunc unquoteBytes(s []byte, border byte) ([]byte, bool) {\n\tif len(s) \u003c 2 || s[0] != border || s[len(s)-1] != border {\n\t\treturn nil, false\n\t}\n\n\ts = s[1 : len(s)-1]\n\n\tr := 0\n\tfor r \u003c len(s) {\n\t\tc := s[r]\n\n\t\tif c == backSlash || c == border || c \u003c 0x20 {\n\t\t\tbreak\n\t\t}\n\n\t\tif c \u003c utf8.RuneSelf {\n\t\t\tr++\n\t\t\tcontinue\n\t\t}\n\n\t\trr, size := utf8.DecodeRune(s[r:])\n\t\tif rr == utf8.RuneError \u0026\u0026 size == 1 {\n\t\t\tbreak\n\t\t}\n\n\t\tr += size\n\t}\n\n\tif r == len(s) {\n\t\treturn s, true\n\t}\n\n\tutfDoubleMax := utf8.UTFMax * 2\n\tb := make([]byte, len(s)+utfDoubleMax)\n\tw := copy(b, s[0:r])\n\n\tfor r \u003c len(s) {\n\t\tif w \u003e= len(b)-utf8.UTFMax {\n\t\t\tnb := make([]byte, utfDoubleMax+(2*len(b)))\n\t\t\tcopy(nb, b)\n\t\t\tb = nb\n\t\t}\n\n\t\tc := s[r]\n\t\tif c == backSlash {\n\t\t\tr++\n\t\t\tif r \u003e= len(s) {\n\t\t\t\treturn nil, false\n\t\t\t}\n\n\t\t\tif s[r] == 'u' {\n\t\t\t\trr, res := decodeUnicodeEscape(s[r-1:])\n\t\t\t\tif res \u003c 0 {\n\t\t\t\t\treturn nil, false\n\t\t\t\t}\n\n\t\t\t\tw += utf8.EncodeRune(b[w:], rr)\n\t\t\t\tr += 5\n\t\t\t} else {\n\t\t\t\tdecode := escapeByteSet[s[r]]\n\t\t\t\tif decode == 0 {\n\t\t\t\t\treturn nil, false\n\t\t\t\t}\n\n\t\t\t\tif decode == doubleQuote || decode == backSlash || decode == slash {\n\t\t\t\t\tdecode = s[r]\n\t\t\t\t}\n\n\t\t\t\tb[w] = decode\n\t\t\t\tr++\n\t\t\t\tw++\n\t\t\t}\n\t\t} else if c == border || c \u003c 0x20 {\n\t\t\treturn nil, false\n\t\t} else if c \u003c utf8.RuneSelf {\n\t\t\tb[w] = c\n\t\t\tr++\n\t\t\tw++\n\t\t} else {\n\t\t\trr, size := utf8.DecodeRune(s[r:])\n\n\t\t\tif rr == utf8.RuneError \u0026\u0026 size == 1 {\n\t\t\t\treturn nil, false\n\t\t\t}\n\n\t\t\tr += size\n\t\t\tw += utf8.EncodeRune(b[w:], rr)\n\t\t}\n\t}\n\n\treturn b[:w], true\n}\n\n// processEscapedUTF8 converts escape sequences to UTF-8 characters.\n// It decodes Unicode escape sequences (\\uXXXX) to UTF-8 and\n// converts standard escape sequences (e.g., \\n) to their corresponding special characters.\nfunc processEscapedUTF8(in, out []byte) (int, int, error) {\n\tif len(in) \u003c 2 || in[0] != backSlash {\n\t\treturn -1, -1, errInvalidEscapeSequence\n\t}\n\n\tescapeSeqLen := 2\n\tescapeChar := in[1]\n\n\tif escapeChar != 'u' {\n\t\tval := escapeByteSet[escapeChar]\n\t\tif val == 0 {\n\t\t\treturn -1, -1, errInvalidEscapeSequence\n\t\t}\n\n\t\tout[0] = val\n\t\treturn escapeSeqLen, 1, nil\n\t}\n\n\tr, size := decodeUnicodeEscape(in)\n\tif size == -1 {\n\t\treturn -1, -1, errInvalidEscapeSequence\n\t}\n\n\toutLen := utf8.EncodeRune(out, r)\n\n\treturn size, outLen, nil\n}\n"},{"name":"escape_test.gno","body":"package json\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\t\"unicode/utf8\"\n)\n\nfunc TestHexToInt(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tc byte\n\t\twant int\n\t}{\n\t\t{\"Digit 0\", '0', 0},\n\t\t{\"Digit 9\", '9', 9},\n\t\t{\"Uppercase A\", 'A', 10},\n\t\t{\"Uppercase F\", 'F', 15},\n\t\t{\"Lowercase a\", 'a', 10},\n\t\t{\"Lowercase f\", 'f', 15},\n\t\t{\"Invalid character1\", 'g', badHex},\n\t\t{\"Invalid character2\", 'G', badHex},\n\t\t{\"Invalid character3\", 'z', badHex},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := h2i(tt.c); got != tt.want {\n\t\t\t\tt.Errorf(\"h2i() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestIsSurrogatePair(t *testing.T) {\n\ttestCases := []struct {\n\t\tname string\n\t\tr rune\n\t\texpected bool\n\t}{\n\t\t{\"high surrogate start\", 0xD800, true},\n\t\t{\"high surrogate end\", 0xDBFF, true},\n\t\t{\"low surrogate start\", 0xDC00, true},\n\t\t{\"low surrogate end\", 0xDFFF, true},\n\t\t{\"Non-surrogate\", 0x0000, false},\n\t\t{\"Non-surrogate 2\", 0xE000, false},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif got := isSurrogatePair(tc.r); got != tc.expected {\n\t\t\t\tt.Errorf(\"isSurrogate() = %v, want %v\", got, tc.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCombineSurrogates(t *testing.T) {\n\ttestCases := []struct {\n\t\thigh, low rune\n\t\texpected rune\n\t}{\n\t\t{0xD83D, 0xDC36, 0x1F436}, // 🐶 U+1F436 DOG FACE\n\t\t{0xD83D, 0xDE00, 0x1F600}, // 😀 U+1F600 GRINNING FACE\n\t\t{0xD83C, 0xDF03, 0x1F303}, // 🌃 U+1F303 NIGHT WITH STARS\n\t}\n\n\tfor _, tc := range testCases {\n\t\tresult := combineSurrogates(tc.high, tc.low)\n\t\tif result != tc.expected {\n\t\t\tt.Errorf(\"combineSurrogates(%U, %U) = %U; want %U\", tc.high, tc.low, result, tc.expected)\n\t\t}\n\t}\n}\n\nfunc TestDecodeSingleUnicodeEscape(t *testing.T) {\n\ttestCases := []struct {\n\t\tinput []byte\n\t\texpected rune\n\t\tisValid bool\n\t}{\n\t\t// valid unicode escape sequences\n\t\t{[]byte(`\\u0041`), 'A', true},\n\t\t{[]byte(`\\u03B1`), 'α', true},\n\t\t{[]byte(`\\u00E9`), 'é', true}, // valid non-English character\n\t\t{[]byte(`\\u0021`), '!', true}, // valid special character\n\t\t{[]byte(`\\uFF11`), '1', true},\n\t\t{[]byte(`\\uD83D`), 0xD83D, true},\n\t\t{[]byte(`\\uDE03`), 0xDE03, true},\n\n\t\t// invalid unicode escape sequences\n\t\t{[]byte(`\\u004`), utf8.RuneError, false}, // too short\n\t\t{[]byte(`\\uXYZW`), utf8.RuneError, false}, // invalid hex\n\t\t{[]byte(`\\u00G1`), utf8.RuneError, false}, // non-hex character\n\t}\n\n\tfor _, tc := range testCases {\n\t\tresult, isValid := decodeSingleUnicodeEscape(tc.input)\n\t\tif result != tc.expected || isValid != tc.isValid {\n\t\t\tt.Errorf(\"decodeSingleUnicodeEscape(%s) = (%U, %v); want (%U, %v)\", tc.input, result, isValid, tc.expected, tc.isValid)\n\t\t}\n\t}\n}\n\nfunc TestDecodeUnicodeEscape(t *testing.T) {\n\ttests := []struct {\n\t\tinput []byte\n\t\texpected rune\n\t\tsize int\n\t}{\n\t\t{[]byte(`\\u0041`), 'A', 6},\n\t\t{[]byte(`\\uD83D\\uDE00`), 0x1F600, 12}, // 😀\n\t\t{[]byte(`\\uD834\\uDD1E`), 0x1D11E, 12}, // 𝄞\n\t\t{[]byte(`\\uFFFF`), '\\uFFFF', 6},\n\t\t{[]byte(`\\uXYZW`), utf8.RuneError, -1},\n\t\t{[]byte(`\\uD800`), utf8.RuneError, -1}, // single high surrogate\n\t\t{[]byte(`\\uDC00`), utf8.RuneError, -1}, // single low surrogate\n\t\t{[]byte(`\\uD800\\uDC00`), 0x10000, 12}, // First code point above U+FFFF\n\t\t{[]byte(`\\uDBFF\\uDFFF`), 0x10FFFF, 12}, // Maximum code point\n\t\t{[]byte(`\\uD83D\\u0041`), utf8.RuneError, -1}, // invalid surrogate pair\n\t}\n\n\tfor _, tc := range tests {\n\t\tr, size := decodeUnicodeEscape(tc.input)\n\t\tif r != tc.expected || size != tc.size {\n\t\t\tt.Errorf(\"decodeUnicodeEscape(%q) = (%U, %d); want (%U, %d)\", tc.input, r, size, tc.expected, tc.size)\n\t\t}\n\t}\n}\n\nfunc TestUnescapeToUTF8(t *testing.T) {\n\ttests := []struct {\n\t\tinput []byte\n\t\texpectedIn int\n\t\texpectedOut int\n\t\tisError bool\n\t}{\n\t\t// valid escape sequences\n\t\t{[]byte(`\\n`), 2, 1, false},\n\t\t{[]byte(`\\t`), 2, 1, false},\n\t\t{[]byte(`\\u0041`), 6, 1, false},\n\t\t{[]byte(`\\u03B1`), 6, 2, false},\n\t\t{[]byte(`\\uD830\\uDE03`), 12, 4, false},\n\n\t\t// invalid escape sequences\n\t\t{[]byte(`\\`), -1, -1, true}, // incomplete escape sequence\n\t\t{[]byte(`\\x`), -1, -1, true}, // invalid escape character\n\t\t{[]byte(`\\u`), -1, -1, true}, // incomplete unicode escape sequence\n\t\t{[]byte(`\\u004`), -1, -1, true}, // invalid unicode escape sequence\n\t\t{[]byte(`\\uXYZW`), -1, -1, true}, // invalid unicode escape sequence\n\t\t{[]byte(`\\uD83D\\u0041`), -1, -1, true}, // invalid unicode escape sequence\n\t}\n\n\tfor _, tc := range tests {\n\t\tinput := make([]byte, len(tc.input))\n\t\tcopy(input, tc.input)\n\t\toutput := make([]byte, utf8.UTFMax)\n\t\tinLen, outLen, err := processEscapedUTF8(input, output)\n\t\tif (err != nil) != tc.isError {\n\t\t\tt.Errorf(\"processEscapedUTF8(%q) = %v; want %v\", tc.input, err, tc.isError)\n\t\t}\n\n\t\tif inLen != tc.expectedIn || outLen != tc.expectedOut {\n\t\t\tt.Errorf(\"processEscapedUTF8(%q) = (%d, %d); want (%d, %d)\", tc.input, inLen, outLen, tc.expectedIn, tc.expectedOut)\n\t\t}\n\t}\n}\n\nfunc TestUnescape(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput []byte\n\t\texpected []byte\n\t\tisError bool\n\t}{\n\t\t{\"NoEscape\", []byte(\"hello world\"), []byte(\"hello world\"), false},\n\t\t{\"SingleEscape\", []byte(\"hello\\\\nworld\"), []byte(\"hello\\nworld\"), false},\n\t\t{\"MultipleEscapes\", []byte(\"line1\\\\nline2\\\\r\\\\nline3\"), []byte(\"line1\\nline2\\r\\nline3\"), false},\n\t\t{\"UnicodeEscape\", []byte(\"snowman:\\\\u2603\"), []byte(\"snowman:\\u2603\"), false},\n\t\t{\"SurrogatePair\", []byte(\"emoji:\\\\uD83D\\\\uDE00\"), []byte(\"emoji:😀\"), false},\n\t\t{\"InvalidEscape\", []byte(\"hello\\\\xworld\"), nil, true},\n\t\t{\"IncompleteUnicode\", []byte(\"incomplete:\\\\u123\"), nil, true},\n\t\t{\"InvalidSurrogatePair\", []byte(\"invalid:\\\\uD83D\\\\u0041\"), nil, true},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\toutput := make([]byte, len(tc.input)*2) // Allocate extra space for possible expansion\n\t\t\tresult, err := Unescape(tc.input, output)\n\t\t\tif (err != nil) != tc.isError {\n\t\t\t\tt.Errorf(\"Unescape(%q) error = %v; want error = %v\", tc.input, err, tc.isError)\n\t\t\t}\n\n\t\t\tif !tc.isError \u0026\u0026 !bytes.Equal(result, tc.expected) {\n\t\t\t\tt.Errorf(\"Unescape(%q) = %q; want %q\", tc.input, result, tc.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUnquoteBytes(t *testing.T) {\n\ttests := []struct {\n\t\tinput []byte\n\t\tborder byte\n\t\texpected []byte\n\t\tok bool\n\t}{\n\t\t{[]byte(\"\\\"hello\\\"\"), '\"', []byte(\"hello\"), true},\n\t\t{[]byte(\"'hello'\"), '\\'', []byte(\"hello\"), true},\n\t\t{[]byte(\"\\\"hello\"), '\"', nil, false},\n\t\t{[]byte(\"hello\\\"\"), '\"', nil, false},\n\t\t{[]byte(\"\\\"he\\\\\\\"llo\\\"\"), '\"', []byte(\"he\\\"llo\"), true},\n\t\t{[]byte(\"\\\"he\\\\nllo\\\"\"), '\"', []byte(\"he\\nllo\"), true},\n\t\t{[]byte(\"\\\"\\\"\"), '\"', []byte(\"\"), true},\n\t\t{[]byte(\"''\"), '\\'', []byte(\"\"), true},\n\t\t{[]byte(\"\\\"\\\\u0041\\\"\"), '\"', []byte(\"A\"), true},\n\t\t{[]byte(`\"Hello, 世界\"`), '\"', []byte(\"Hello, 世界\"), true},\n\t\t{[]byte(`\"Hello, \\x80\"`), '\"', nil, false},\n\t\t{[]byte(`\"invalid surrogate: \\uD83D\\u0041\"`), '\"', nil, false},\n\t}\n\n\tfor _, tc := range tests {\n\t\tresult, pass := unquoteBytes(tc.input, tc.border)\n\n\t\tif pass != tc.ok {\n\t\t\tt.Errorf(\"unquoteBytes(%q) = %v; want %v\", tc.input, pass, tc.ok)\n\t\t}\n\n\t\tif !bytes.Equal(result, tc.expected) {\n\t\t\tt.Errorf(\"unquoteBytes(%q) = %q; want %q\", tc.input, result, tc.expected)\n\t\t}\n\t}\n}\n"},{"name":"indent.gno","body":"package json\n\nimport (\n\t\"bytes\"\n\t\"strings\"\n)\n\n// indentGrowthFactor specifies the growth factor of indenting JSON input.\n// A factor no higher than 2 ensures that wasted space never exceeds 50%.\nconst indentGrowthFactor = 2\n\n// IndentJSON formats the JSON data with the specified indentation.\nfunc Indent(data []byte, indent string) ([]byte, error) {\n\tvar (\n\t\tout bytes.Buffer\n\t\tlevel int\n\t\tinArray bool\n\t\tarrayDepth int\n\t)\n\n\tfor i := 0; i \u003c len(data); i++ {\n\t\tc := data[i] // current character\n\n\t\tswitch c {\n\t\tcase bracketOpen:\n\t\t\tarrayDepth++\n\t\t\tif arrayDepth \u003e 1 {\n\t\t\t\tlevel++ // increase the level if it's nested array\n\t\t\t\tinArray = true\n\n\t\t\t\tif err := out.WriteByte(c); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\t\tif err := writeNewlineAndIndent(\u0026out, level, indent); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// case of the top-level array\n\t\t\t\tinArray = true\n\t\t\t\tif err := out.WriteByte(c); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\n\t\tcase bracketClose:\n\t\t\tif inArray \u0026\u0026 arrayDepth \u003e 1 { // nested array\n\t\t\t\tlevel--\n\t\t\t\tif err := writeNewlineAndIndent(\u0026out, level, indent); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tarrayDepth--\n\t\t\tif arrayDepth == 0 {\n\t\t\t\tinArray = false\n\t\t\t}\n\n\t\t\tif err := out.WriteByte(c); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\tcase curlyOpen:\n\t\t\t// check if the empty object or array\n\t\t\t// we don't need to apply the indent when it's empty containers.\n\t\t\tif i+1 \u003c len(data) \u0026\u0026 data[i+1] == curlyClose {\n\t\t\t\tif err := out.WriteByte(c); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\t\ti++ // skip next character\n\t\t\t\tif err := out.WriteByte(data[i]); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif err := out.WriteByte(c); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\t\tlevel++\n\t\t\t\tif err := writeNewlineAndIndent(\u0026out, level, indent); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\n\t\tcase curlyClose:\n\t\t\tlevel--\n\t\t\tif err := writeNewlineAndIndent(\u0026out, level, indent); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif err := out.WriteByte(c); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\tcase comma, colon:\n\t\t\tif err := out.WriteByte(c); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif inArray \u0026\u0026 arrayDepth \u003e 1 { // nested array\n\t\t\t\tif err := writeNewlineAndIndent(\u0026out, level, indent); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t} else if c == colon {\n\t\t\t\tif err := out.WriteByte(' '); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\n\t\tdefault:\n\t\t\tif err := out.WriteByte(c); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn out.Bytes(), nil\n}\n\nfunc writeNewlineAndIndent(out *bytes.Buffer, level int, indent string) error {\n\tif err := out.WriteByte('\\n'); err != nil {\n\t\treturn err\n\t}\n\n\tidt := strings.Repeat(indent, level*indentGrowthFactor)\n\tif _, err := out.WriteString(idt); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"},{"name":"indent_test.gno","body":"package json\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n)\n\nfunc TestIndentJSON(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput []byte\n\t\tindent string\n\t\texpected []byte\n\t}{\n\t\t{\n\t\t\tname: \"empty object\",\n\t\t\tinput: []byte(`{}`),\n\t\t\tindent: \" \",\n\t\t\texpected: []byte(`{}`),\n\t\t},\n\t\t{\n\t\t\tname: \"empty array\",\n\t\t\tinput: []byte(`[]`),\n\t\t\tindent: \" \",\n\t\t\texpected: []byte(`[]`),\n\t\t},\n\t\t{\n\t\t\tname: \"nested object\",\n\t\t\tinput: []byte(`{{}}`),\n\t\t\tindent: \"\\t\",\n\t\t\texpected: []byte(\"{\\n\\t\\t{}\\n}\"),\n\t\t},\n\t\t{\n\t\t\tname: \"nested array\",\n\t\t\tinput: []byte(`[[[]]]`),\n\t\t\tindent: \"\\t\",\n\t\t\texpected: []byte(\"[[\\n\\t\\t[\\n\\t\\t\\t\\t\\n\\t\\t]\\n]]\"),\n\t\t},\n\t\t{\n\t\t\tname: \"top-level array\",\n\t\t\tinput: []byte(`[\"apple\",\"banana\",\"cherry\"]`),\n\t\t\tindent: \"\\t\",\n\t\t\texpected: []byte(`[\"apple\",\"banana\",\"cherry\"]`),\n\t\t},\n\t\t{\n\t\t\tname: \"array of arrays\",\n\t\t\tinput: []byte(`[\"apple\",[\"banana\",\"cherry\"],\"date\"]`),\n\t\t\tindent: \" \",\n\t\t\texpected: []byte(\"[\\\"apple\\\",[\\n \\\"banana\\\",\\n \\\"cherry\\\"\\n],\\\"date\\\"]\"),\n\t\t},\n\n\t\t{\n\t\t\tname: \"nested array in object\",\n\t\t\tinput: []byte(`{\"fruits\":[\"apple\",[\"banana\",\"cherry\"],\"date\"]}`),\n\t\t\tindent: \" \",\n\t\t\texpected: []byte(\"{\\n \\\"fruits\\\": [\\\"apple\\\",[\\n \\\"banana\\\",\\n \\\"cherry\\\"\\n ],\\\"date\\\"]\\n}\"),\n\t\t},\n\t\t{\n\t\t\tname: \"complex nested structure\",\n\t\t\tinput: []byte(`{\"data\":{\"array\":[1,2,3],\"bool\":true,\"nestedArray\":[[\"a\",\"b\"],\"c\"]}}`),\n\t\t\tindent: \" \",\n\t\t\texpected: []byte(\"{\\n \\\"data\\\": {\\n \\\"array\\\": [1,2,3],\\\"bool\\\": true,\\\"nestedArray\\\": [[\\n \\\"a\\\",\\n \\\"b\\\"\\n ],\\\"c\\\"]\\n }\\n}\"),\n\t\t},\n\t\t{\n\t\t\tname: \"custom ident character\",\n\t\t\tinput: []byte(`{\"fruits\":[\"apple\",[\"banana\",\"cherry\"],\"date\"]}`),\n\t\t\tindent: \"*\",\n\t\t\texpected: []byte(\"{\\n**\\\"fruits\\\": [\\\"apple\\\",[\\n****\\\"banana\\\",\\n****\\\"cherry\\\"\\n**],\\\"date\\\"]\\n}\"),\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tactual, err := Indent(tt.input, tt.indent)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"IndentJSON() error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !bytes.Equal(actual, tt.expected) {\n\t\t\t\tt.Errorf(\"IndentJSON() = %q, want %q\", actual, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n"},{"name":"internal.gno","body":"package json\n\n// Reference: https://github.com/freddierice/php_source/blob/467ed5d6edff72219afd3e644516f131118ef48e/ext/json/JSON_parser.c\n// Copyright (c) 2005 JSON.org\n\n// Go implementation is taken from: https://github.com/spyzhov/ajson/blob/master/internal/state.go\n\ntype (\n\tStates int8 // possible states of the parser\n\tClasses int8 // JSON string character types\n)\n\nconst __ = -1\n\n// enum classes\nconst (\n\tC_SPACE Classes = iota /* space */\n\tC_WHITE /* other whitespace */\n\tC_LCURB /* { */\n\tC_RCURB /* } */\n\tC_LSQRB /* [ */\n\tC_RSQRB /* ] */\n\tC_COLON /* : */\n\tC_COMMA /* , */\n\tC_QUOTE /* \" */\n\tC_BACKS /* \\ */\n\tC_SLASH /* / */\n\tC_PLUS /* + */\n\tC_MINUS /* - */\n\tC_POINT /* . */\n\tC_ZERO /* 0 */\n\tC_DIGIT /* 123456789 */\n\tC_LOW_A /* a */\n\tC_LOW_B /* b */\n\tC_LOW_C /* c */\n\tC_LOW_D /* d */\n\tC_LOW_E /* e */\n\tC_LOW_F /* f */\n\tC_LOW_L /* l */\n\tC_LOW_N /* n */\n\tC_LOW_R /* r */\n\tC_LOW_S /* s */\n\tC_LOW_T /* t */\n\tC_LOW_U /* u */\n\tC_ABCDF /* ABCDF */\n\tC_E /* E */\n\tC_ETC /* everything else */\n)\n\n// AsciiClasses array maps the 128 ASCII characters into character classes.\nvar AsciiClasses = [128]Classes{\n\t/*\n\t This array maps the 128 ASCII characters into character classes.\n\t The remaining Unicode characters should be mapped to C_ETC.\n\t Non-whitespace control characters are errors.\n\t*/\n\t__, __, __, __, __, __, __, __,\n\t__, C_WHITE, C_WHITE, __, __, C_WHITE, __, __,\n\t__, __, __, __, __, __, __, __,\n\t__, __, __, __, __, __, __, __,\n\n\tC_SPACE, C_ETC, C_QUOTE, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_PLUS, C_COMMA, C_MINUS, C_POINT, C_SLASH,\n\tC_ZERO, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT,\n\tC_DIGIT, C_DIGIT, C_COLON, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC,\n\n\tC_ETC, C_ABCDF, C_ABCDF, C_ABCDF, C_ABCDF, C_E, C_ABCDF, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_LSQRB, C_BACKS, C_RSQRB, C_ETC, C_ETC,\n\n\tC_ETC, C_LOW_A, C_LOW_B, C_LOW_C, C_LOW_D, C_LOW_E, C_LOW_F, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_ETC, C_LOW_L, C_ETC, C_LOW_N, C_ETC,\n\tC_ETC, C_ETC, C_LOW_R, C_LOW_S, C_LOW_T, C_LOW_U, C_ETC, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_LCURB, C_ETC, C_RCURB, C_ETC, C_ETC,\n}\n\n// QuoteAsciiClasses is a HACK for single quote from AsciiClasses\nvar QuoteAsciiClasses = [128]Classes{\n\t/*\n\t This array maps the 128 ASCII characters into character classes.\n\t The remaining Unicode characters should be mapped to C_ETC.\n\t Non-whitespace control characters are errors.\n\t*/\n\t__, __, __, __, __, __, __, __,\n\t__, C_WHITE, C_WHITE, __, __, C_WHITE, __, __,\n\t__, __, __, __, __, __, __, __,\n\t__, __, __, __, __, __, __, __,\n\n\tC_SPACE, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_QUOTE,\n\tC_ETC, C_ETC, C_ETC, C_PLUS, C_COMMA, C_MINUS, C_POINT, C_SLASH,\n\tC_ZERO, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT,\n\tC_DIGIT, C_DIGIT, C_COLON, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC,\n\n\tC_ETC, C_ABCDF, C_ABCDF, C_ABCDF, C_ABCDF, C_E, C_ABCDF, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_LSQRB, C_BACKS, C_RSQRB, C_ETC, C_ETC,\n\n\tC_ETC, C_LOW_A, C_LOW_B, C_LOW_C, C_LOW_D, C_LOW_E, C_LOW_F, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_ETC, C_LOW_L, C_ETC, C_LOW_N, C_ETC,\n\tC_ETC, C_ETC, C_LOW_R, C_LOW_S, C_LOW_T, C_LOW_U, C_ETC, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_LCURB, C_ETC, C_RCURB, C_ETC, C_ETC,\n}\n\n/*\nThe state codes.\n*/\nconst (\n\tGO States = iota /* start */\n\tOK /* ok */\n\tOB /* object */\n\tKE /* key */\n\tCO /* colon */\n\tVA /* value */\n\tAR /* array */\n\tST /* string */\n\tES /* escape */\n\tU1 /* u1 */\n\tU2 /* u2 */\n\tU3 /* u3 */\n\tU4 /* u4 */\n\tMI /* minus */\n\tZE /* zero */\n\tIN /* integer */\n\tDT /* dot */\n\tFR /* fraction */\n\tE1 /* e */\n\tE2 /* ex */\n\tE3 /* exp */\n\tT1 /* tr */\n\tT2 /* tru */\n\tT3 /* true */\n\tF1 /* fa */\n\tF2 /* fal */\n\tF3 /* fals */\n\tF4 /* false */\n\tN1 /* nu */\n\tN2 /* nul */\n\tN3 /* null */\n)\n\n// List of action codes.\n// these constants are defining an action that should be performed under certain conditions.\nconst (\n\tcl States = -2 /* colon */\n\tcm States = -3 /* comma */\n\tqt States = -4 /* quote */\n\tbo States = -5 /* bracket open */\n\tco States = -6 /* curly bracket open */\n\tbc States = -7 /* bracket close */\n\tcc States = -8 /* curly bracket close */\n\tec States = -9 /* curly bracket empty */\n)\n\n// StateTransitionTable is the state transition table takes the current state and the current symbol, and returns either\n// a new state or an action. An action is represented as a negative number. A JSON text is accepted if at the end of the\n// text the state is OK and if the mode is DONE.\nvar StateTransitionTable = [31][31]States{\n\t/*\n\t The state transition table takes the current state and the current symbol,\n\t and returns either a new state or an action. An action is represented as a\n\t negative number. A JSON text is accepted if at the end of the text the\n\t state is OK and if the mode is DONE.\n\t white 1-9 ABCDF etc\n\t space | { } [ ] : , \" \\ / + - . 0 | a b c d e f l n r s t u | E |*/\n\t/*start GO*/ {GO, GO, co, __, bo, __, __, __, ST, __, __, __, MI, __, ZE, IN, __, __, __, __, __, F1, __, N1, __, __, T1, __, __, __, __},\n\t/*ok OK*/ {OK, OK, __, cc, __, bc, __, cm, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},\n\t/*object OB*/ {OB, OB, __, ec, __, __, __, __, ST, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},\n\t/*key KE*/ {KE, KE, __, __, __, __, __, __, ST, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},\n\t/*colon CO*/ {CO, CO, __, __, __, __, cl, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},\n\t/*value VA*/ {VA, VA, co, __, bo, __, __, __, ST, __, __, __, MI, __, ZE, IN, __, __, __, __, __, F1, __, N1, __, __, T1, __, __, __, __},\n\t/*array AR*/ {AR, AR, co, __, bo, bc, __, __, ST, __, __, __, MI, __, ZE, IN, __, __, __, __, __, F1, __, N1, __, __, T1, __, __, __, __},\n\t/*string ST*/ {ST, __, ST, ST, ST, ST, ST, ST, qt, ES, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST},\n\t/*escape ES*/ {__, __, __, __, __, __, __, __, ST, ST, ST, __, __, __, __, __, __, ST, __, __, __, ST, __, ST, ST, __, ST, U1, __, __, __},\n\t/*u1 U1*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, U2, U2, U2, U2, U2, U2, U2, U2, __, __, __, __, __, __, U2, U2, __},\n\t/*u2 U2*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, U3, U3, U3, U3, U3, U3, U3, U3, __, __, __, __, __, __, U3, U3, __},\n\t/*u3 U3*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, U4, U4, U4, U4, U4, U4, U4, U4, __, __, __, __, __, __, U4, U4, __},\n\t/*u4 U4*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, ST, ST, ST, ST, ST, ST, ST, ST, __, __, __, __, __, __, ST, ST, __},\n\t/*minus MI*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, ZE, IN, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},\n\t/*zero ZE*/ {OK, OK, __, cc, __, bc, __, cm, __, __, __, __, __, DT, __, __, __, __, __, __, E1, __, __, __, __, __, __, __, __, E1, __},\n\t/*int IN*/ {OK, OK, __, cc, __, bc, __, cm, __, __, __, __, __, DT, IN, IN, __, __, __, __, E1, __, __, __, __, __, __, __, __, E1, __},\n\t/*dot DT*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, FR, FR, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},\n\t/*frac FR*/ {OK, OK, __, cc, __, bc, __, cm, __, __, __, __, __, __, FR, FR, __, __, __, __, E1, __, __, __, __, __, __, __, __, E1, __},\n\t/*e E1*/ {__, __, __, __, __, __, __, __, __, __, __, E2, E2, __, E3, E3, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},\n\t/*ex E2*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, E3, E3, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},\n\t/*exp E3*/ {OK, OK, __, cc, __, bc, __, cm, __, __, __, __, __, __, E3, E3, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},\n\t/*tr T1*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, T2, __, __, __, __, __, __},\n\t/*tru T2*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, T3, __, __, __},\n\t/*true T3*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, OK, __, __, __, __, __, __, __, __, __, __},\n\t/*fa F1*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, F2, __, __, __, __, __, __, __, __, __, __, __, __, __, __},\n\t/*fal F2*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, F3, __, __, __, __, __, __, __, __},\n\t/*fals F3*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, F4, __, __, __, __, __},\n\t/*false F4*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, OK, __, __, __, __, __, __, __, __, __, __},\n\t/*nu N1*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, N2, __, __, __},\n\t/*nul N2*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, N3, __, __, __, __, __, __, __, __},\n\t/*null N3*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, OK, __, __, __, __, __, __, __, __},\n}\n"},{"name":"node.gno","body":"package json\n\nimport (\n\t\"errors\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Node represents a JSON node.\ntype Node struct {\n\tprev *Node // prev is the parent node of the current node.\n\tnext map[string]*Node // next is the child nodes of the current node.\n\tkey *string // key holds the key of the current node in the parent node.\n\tdata []byte // byte slice of JSON data\n\tvalue interface{} // value holds the value of the current node.\n\tnodeType ValueType // NodeType holds the type of the current node. (Object, Array, String, Number, Boolean, Null)\n\tindex *int // index holds the index of the current node in the parent array node.\n\tborders [2]int // borders stores the start and end index of the current node in the data.\n\tmodified bool // modified indicates the current node is changed or not.\n}\n\n// NewNode creates a new node instance with the given parent node, buffer, type, and key.\nfunc NewNode(prev *Node, b *buffer, typ ValueType, key **string) (*Node, error) {\n\tcurr := \u0026Node{\n\t\tprev: prev,\n\t\tdata: b.data,\n\t\tborders: [2]int{b.index, 0},\n\t\tkey: *key,\n\t\tnodeType: typ,\n\t\tmodified: false,\n\t}\n\n\tif typ == Object || typ == Array {\n\t\tcurr.next = make(map[string]*Node)\n\t}\n\n\tif prev != nil {\n\t\tif prev.IsArray() {\n\t\t\tsize := len(prev.next)\n\t\t\tcurr.index = \u0026size\n\n\t\t\tprev.next[strconv.Itoa(size)] = curr\n\t\t} else if prev.IsObject() {\n\t\t\tif key == nil {\n\t\t\t\treturn nil, errKeyRequired\n\t\t\t}\n\n\t\t\tprev.next[**key] = curr\n\t\t} else {\n\t\t\treturn nil, errors.New(\"invalid parent type\")\n\t\t}\n\t}\n\n\treturn curr, nil\n}\n\n// load retrieves the value of the current node.\nfunc (n *Node) load() interface{} {\n\treturn n.value\n}\n\n// Changed checks the current node is changed or not.\nfunc (n *Node) Changed() bool {\n\treturn n.modified\n}\n\n// Key returns the key of the current node.\nfunc (n *Node) Key() string {\n\tif n == nil || n.key == nil {\n\t\treturn \"\"\n\t}\n\n\treturn *n.key\n}\n\n// HasKey checks the current node has the given key or not.\nfunc (n *Node) HasKey(key string) bool {\n\tif n == nil {\n\t\treturn false\n\t}\n\n\t_, ok := n.next[key]\n\treturn ok\n}\n\n// GetKey returns the value of the given key from the current object node.\nfunc (n *Node) GetKey(key string) (*Node, error) {\n\tif n == nil {\n\t\treturn nil, errNilNode\n\t}\n\n\tif n.Type() != Object {\n\t\treturn nil, ufmt.Errorf(\"target node is not object type. got: %s\", n.Type().String())\n\t}\n\n\tvalue, ok := n.next[key]\n\tif !ok {\n\t\treturn nil, ufmt.Errorf(\"key not found: %s\", key)\n\t}\n\n\treturn value, nil\n}\n\n// MustKey returns the value of the given key from the current object node.\nfunc (n *Node) MustKey(key string) *Node {\n\tval, err := n.GetKey(key)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn val\n}\n\n// UniqueKeyLists traverses the current JSON nodes and collects all the unique keys.\nfunc (n *Node) UniqueKeyLists() []string {\n\tvar collectKeys func(*Node) []string\n\tcollectKeys = func(node *Node) []string {\n\t\tif node == nil || !node.IsObject() {\n\t\t\treturn nil\n\t\t}\n\n\t\tresult := make(map[string]bool)\n\t\tfor key, childNode := range node.next {\n\t\t\tresult[key] = true\n\t\t\tchildKeys := collectKeys(childNode)\n\t\t\tfor _, childKey := range childKeys {\n\t\t\t\tresult[childKey] = true\n\t\t\t}\n\t\t}\n\n\t\tkeys := make([]string, 0, len(result))\n\t\tfor key := range result {\n\t\t\tkeys = append(keys, key)\n\t\t}\n\t\treturn keys\n\t}\n\n\treturn collectKeys(n)\n}\n\n// Empty returns true if the current node is empty.\nfunc (n *Node) Empty() bool {\n\tif n == nil {\n\t\treturn false\n\t}\n\n\treturn len(n.next) == 0\n}\n\n// Type returns the type (ValueType) of the current node.\nfunc (n *Node) Type() ValueType {\n\treturn n.nodeType\n}\n\n// Value returns the value of the current node.\n//\n// Usage:\n//\n//\troot := Unmarshal([]byte(`{\"key\": \"value\"}`))\n//\tval, err := root.MustKey(\"key\").Value()\n//\tif err != nil {\n//\t\tt.Errorf(\"Value returns error: %v\", err)\n//\t}\n//\n//\tresult: \"value\"\nfunc (n *Node) Value() (value interface{}, err error) {\n\tvalue = n.load()\n\n\tif value == nil {\n\t\tswitch n.nodeType {\n\t\tcase Null:\n\t\t\treturn nil, nil\n\n\t\tcase Number:\n\t\t\tvalue, err = strconv.ParseFloat(string(n.source()), 64)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tn.value = value\n\n\t\tcase String:\n\t\t\tvar ok bool\n\t\t\tvalue, ok = Unquote(n.source(), doubleQuote)\n\t\t\tif !ok {\n\t\t\t\treturn \"\", errInvalidStringValue\n\t\t\t}\n\n\t\t\tn.value = value\n\n\t\tcase Boolean:\n\t\t\tif len(n.source()) == 0 {\n\t\t\t\treturn nil, errEmptyBooleanNode\n\t\t\t}\n\n\t\t\tb := n.source()[0]\n\t\t\tvalue = b == 't' || b == 'T'\n\t\t\tn.value = value\n\n\t\tcase Array:\n\t\t\telems := make([]*Node, len(n.next))\n\n\t\t\tfor _, e := range n.next {\n\t\t\t\telems[*e.index] = e\n\t\t\t}\n\n\t\t\tvalue = elems\n\t\t\tn.value = value\n\n\t\tcase Object:\n\t\t\tobj := make(map[string]*Node, len(n.next))\n\n\t\t\tfor k, v := range n.next {\n\t\t\t\tobj[k] = v\n\t\t\t}\n\n\t\t\tvalue = obj\n\t\t\tn.value = value\n\t\t}\n\t}\n\n\treturn value, nil\n}\n\n// Delete removes the current node from the parent node.\n//\n// Usage:\n//\n//\troot := Unmarshal([]byte(`{\"key\": \"value\"}`))\n//\tif err := root.MustKey(\"key\").Delete(); err != nil {\n//\t\tt.Errorf(\"Delete returns error: %v\", err)\n//\t}\n//\n//\tresult: {} (empty object)\nfunc (n *Node) Delete() error {\n\tif n == nil {\n\t\treturn errors.New(\"can't delete nil node\")\n\t}\n\n\tif n.prev == nil {\n\t\treturn nil\n\t}\n\n\treturn n.prev.remove(n)\n}\n\n// Size returns the size (length) of the current array node.\n//\n// Usage:\n//\n//\troot := ArrayNode(\"\", []*Node{StringNode(\"\", \"foo\"), NumberNode(\"\", 1)})\n//\tif root == nil {\n//\t\tt.Errorf(\"ArrayNode returns nil\")\n//\t}\n//\n//\tif root.Size() != 2 {\n//\t\tt.Errorf(\"ArrayNode returns wrong size: %d\", root.Size())\n//\t}\nfunc (n *Node) Size() int {\n\tif n == nil {\n\t\treturn 0\n\t}\n\n\treturn len(n.next)\n}\n\n// Index returns the index of the current node in the parent array node.\n//\n// Usage:\n//\n//\troot := ArrayNode(\"\", []*Node{StringNode(\"\", \"foo\"), NumberNode(\"\", 1)})\n//\tif root == nil {\n//\t\tt.Errorf(\"ArrayNode returns nil\")\n//\t}\n//\n//\tif root.MustIndex(1).Index() != 1 {\n//\t\tt.Errorf(\"Index returns wrong index: %d\", root.MustIndex(1).Index())\n//\t}\n//\n// We can also use the index to the byte slice of the JSON data directly.\n//\n// Example:\n//\n//\troot := Unmarshal([]byte(`[\"foo\", 1]`))\n//\tif root == nil {\n//\t\tt.Errorf(\"Unmarshal returns nil\")\n//\t}\n//\n//\tif string(root.MustIndex(1).source()) != \"1\" {\n//\t\tt.Errorf(\"source returns wrong result: %s\", root.MustIndex(1).source())\n//\t}\nfunc (n *Node) Index() int {\n\tif n == nil || n.index == nil {\n\t\treturn -1\n\t}\n\n\treturn *n.index\n}\n\n// MustIndex returns the array element at the given index.\n//\n// If the index is negative, it returns the index is from the end of the array.\n// Also, it panics if the index is not found.\n//\n// check the Index method for detailed usage.\nfunc (n *Node) MustIndex(expectIdx int) *Node {\n\tval, err := n.GetIndex(expectIdx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn val\n}\n\n// GetIndex returns the array element at the given index.\n//\n// if the index is negative, it returns the index is from the end of the array.\nfunc (n *Node) GetIndex(idx int) (*Node, error) {\n\tif n == nil {\n\t\treturn nil, errNilNode\n\t}\n\n\tif !n.IsArray() {\n\t\treturn nil, errNotArrayNode\n\t}\n\n\tif idx \u003e n.Size() {\n\t\treturn nil, errors.New(\"input index exceeds the array size\")\n\t}\n\n\tif idx \u003c 0 {\n\t\tidx += len(n.next)\n\t}\n\n\tchild, ok := n.next[strconv.Itoa(idx)]\n\tif !ok {\n\t\treturn nil, errIndexNotFound\n\t}\n\n\treturn child, nil\n}\n\n// DeleteIndex removes the array element at the given index.\nfunc (n *Node) DeleteIndex(idx int) error {\n\tnode, err := n.GetIndex(idx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn n.remove(node)\n}\n\n// NullNode creates a new null type node.\n//\n// Usage:\n//\n//\t_ := NullNode(\"\")\nfunc NullNode(key string) *Node {\n\treturn \u0026Node{\n\t\tkey: \u0026key,\n\t\tvalue: nil,\n\t\tnodeType: Null,\n\t\tmodified: true,\n\t}\n}\n\n// NumberNode creates a new number type node.\n//\n// Usage:\n//\n//\troot := NumberNode(\"\", 1)\n//\tif root == nil {\n//\t\tt.Errorf(\"NumberNode returns nil\")\n//\t}\nfunc NumberNode(key string, value float64) *Node {\n\treturn \u0026Node{\n\t\tkey: \u0026key,\n\t\tvalue: value,\n\t\tnodeType: Number,\n\t\tmodified: true,\n\t}\n}\n\n// StringNode creates a new string type node.\n//\n// Usage:\n//\n//\troot := StringNode(\"\", \"foo\")\n//\tif root == nil {\n//\t\tt.Errorf(\"StringNode returns nil\")\n//\t}\nfunc StringNode(key string, value string) *Node {\n\treturn \u0026Node{\n\t\tkey: \u0026key,\n\t\tvalue: value,\n\t\tnodeType: String,\n\t\tmodified: true,\n\t}\n}\n\n// BoolNode creates a new given boolean value node.\n//\n// Usage:\n//\n//\troot := BoolNode(\"\", true)\n//\tif root == nil {\n//\t\tt.Errorf(\"BoolNode returns nil\")\n//\t}\nfunc BoolNode(key string, value bool) *Node {\n\treturn \u0026Node{\n\t\tkey: \u0026key,\n\t\tvalue: value,\n\t\tnodeType: Boolean,\n\t\tmodified: true,\n\t}\n}\n\n// ArrayNode creates a new array type node.\n//\n// If the given value is nil, it creates an empty array node.\n//\n// Usage:\n//\n//\troot := ArrayNode(\"\", []*Node{StringNode(\"\", \"foo\"), NumberNode(\"\", 1)})\n//\tif root == nil {\n//\t\tt.Errorf(\"ArrayNode returns nil\")\n//\t}\nfunc ArrayNode(key string, value []*Node) *Node {\n\tcurr := \u0026Node{\n\t\tkey: \u0026key,\n\t\tnodeType: Array,\n\t\tmodified: true,\n\t}\n\n\tcurr.next = make(map[string]*Node, len(value))\n\tif value != nil {\n\t\tcurr.value = value\n\n\t\tfor i, v := range value {\n\t\t\tidx := i\n\t\t\tcurr.next[strconv.Itoa(i)] = v\n\n\t\t\tv.prev = curr\n\t\t\tv.index = \u0026idx\n\t\t}\n\t}\n\n\treturn curr\n}\n\n// ObjectNode creates a new object type node.\n//\n// If the given value is nil, it creates an empty object node.\n//\n// next is a map of key and value pairs of the object.\nfunc ObjectNode(key string, value map[string]*Node) *Node {\n\tcurr := \u0026Node{\n\t\tnodeType: Object,\n\t\tkey: \u0026key,\n\t\tnext: value,\n\t\tmodified: true,\n\t}\n\n\tif value != nil {\n\t\tcurr.value = value\n\n\t\tfor key, val := range value {\n\t\t\tvkey := key\n\t\t\tval.prev = curr\n\t\t\tval.key = \u0026vkey\n\t\t}\n\t} else {\n\t\tcurr.next = make(map[string]*Node)\n\t}\n\n\treturn curr\n}\n\n// IsArray returns true if the current node is array type.\nfunc (n *Node) IsArray() bool {\n\treturn n.nodeType == Array\n}\n\n// IsObject returns true if the current node is object type.\nfunc (n *Node) IsObject() bool {\n\treturn n.nodeType == Object\n}\n\n// IsNull returns true if the current node is null type.\nfunc (n *Node) IsNull() bool {\n\treturn n.nodeType == Null\n}\n\n// IsBool returns true if the current node is boolean type.\nfunc (n *Node) IsBool() bool {\n\treturn n.nodeType == Boolean\n}\n\n// IsString returns true if the current node is string type.\nfunc (n *Node) IsString() bool {\n\treturn n.nodeType == String\n}\n\n// IsNumber returns true if the current node is number type.\nfunc (n *Node) IsNumber() bool {\n\treturn n.nodeType == Number\n}\n\n// ready checks the current node is ready or not.\n//\n// the meaning of ready is the current node is parsed and has a valid value.\nfunc (n *Node) ready() bool {\n\treturn n.borders[1] != 0\n}\n\n// source returns the source of the current node.\nfunc (n *Node) source() []byte {\n\tif n == nil {\n\t\treturn nil\n\t}\n\n\tif n.ready() \u0026\u0026 !n.modified \u0026\u0026 n.data != nil {\n\t\treturn (n.data)[n.borders[0]:n.borders[1]]\n\t}\n\n\treturn nil\n}\n\n// root returns the root node of the current node.\nfunc (n *Node) root() *Node {\n\tif n == nil {\n\t\treturn nil\n\t}\n\n\tcurr := n\n\tfor curr.prev != nil {\n\t\tcurr = curr.prev\n\t}\n\n\treturn curr\n}\n\n// GetNull returns the null value if current node is null type.\n//\n// Usage:\n//\n//\troot := Unmarshal([]byte(\"null\"))\n//\tval, err := root.GetNull()\n//\tif err != nil {\n//\t\tt.Errorf(\"GetNull returns error: %v\", err)\n//\t}\n//\tif val != nil {\n//\t\tt.Errorf(\"GetNull returns wrong result: %v\", val)\n//\t}\nfunc (n *Node) GetNull() (interface{}, error) {\n\tif n == nil {\n\t\treturn nil, errNilNode\n\t}\n\n\tif !n.IsNull() {\n\t\treturn nil, errNotNullNode\n\t}\n\n\treturn nil, nil\n}\n\n// MustNull returns the null value if current node is null type.\n//\n// It panics if the current node is not null type.\nfunc (n *Node) MustNull() interface{} {\n\tv, err := n.GetNull()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn v\n}\n\n// GetNumeric returns the numeric (int/float) value if current node is number type.\n//\n// Usage:\n//\n//\troot := Unmarshal([]byte(\"10.5\"))\n//\tval, err := root.GetNumeric()\n//\tif err != nil {\n//\t\tt.Errorf(\"GetNumeric returns error: %v\", err)\n//\t}\n//\tprintln(val) // 10.5\nfunc (n *Node) GetNumeric() (float64, error) {\n\tif n == nil {\n\t\treturn 0, errNilNode\n\t}\n\n\tif n.nodeType != Number {\n\t\treturn 0, errNotNumberNode\n\t}\n\n\tval, err := n.Value()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tv, ok := val.(float64)\n\tif !ok {\n\t\treturn 0, errNotNumberNode\n\t}\n\n\treturn v, nil\n}\n\n// MustNumeric returns the numeric (int/float) value if current node is number type.\n//\n// It panics if the current node is not number type.\nfunc (n *Node) MustNumeric() float64 {\n\tv, err := n.GetNumeric()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn v\n}\n\n// GetString returns the string value if current node is string type.\n//\n// Usage:\n//\n//\troot, err := Unmarshal([]byte(\"foo\"))\n//\tif err != nil {\n//\t\tt.Errorf(\"Error on Unmarshal(): %s\", err)\n//\t}\n//\n//\tstr, err := root.GetString()\n//\tif err != nil {\n//\t\tt.Errorf(\"should retrieve string value: %s\", err)\n//\t}\n//\n//\tprintln(str) // \"foo\"\nfunc (n *Node) GetString() (string, error) {\n\tif n == nil {\n\t\treturn \"\", errEmptyStringNode\n\t}\n\n\tif !n.IsString() {\n\t\treturn \"\", errNotStringNode\n\t}\n\n\tval, err := n.Value()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tv, ok := val.(string)\n\tif !ok {\n\t\treturn \"\", errNotStringNode\n\t}\n\n\treturn v, nil\n}\n\n// MustString returns the string value if current node is string type.\n//\n// It panics if the current node is not string type.\nfunc (n *Node) MustString() string {\n\tv, err := n.GetString()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn v\n}\n\n// GetBool returns the boolean value if current node is boolean type.\n//\n// Usage:\n//\n//\troot := Unmarshal([]byte(\"true\"))\n//\tval, err := root.GetBool()\n//\tif err != nil {\n//\t\tt.Errorf(\"GetBool returns error: %v\", err)\n//\t}\n//\tprintln(val) // true\nfunc (n *Node) GetBool() (bool, error) {\n\tif n == nil {\n\t\treturn false, errNilNode\n\t}\n\n\tif n.nodeType != Boolean {\n\t\treturn false, errNotBoolNode\n\t}\n\n\tval, err := n.Value()\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tv, ok := val.(bool)\n\tif !ok {\n\t\treturn false, errNotBoolNode\n\t}\n\n\treturn v, nil\n}\n\n// MustBool returns the boolean value if current node is boolean type.\n//\n// It panics if the current node is not boolean type.\nfunc (n *Node) MustBool() bool {\n\tv, err := n.GetBool()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn v\n}\n\n// GetArray returns the array value if current node is array type.\n//\n// Usage:\n//\n//\t\troot := Must(Unmarshal([]byte(`[\"foo\", 1]`)))\n//\t\tarr, err := root.GetArray()\n//\t\tif err != nil {\n//\t\t\tt.Errorf(\"GetArray returns error: %v\", err)\n//\t\t}\n//\n//\t\tfor _, val := range arr {\n//\t\t\tprintln(val)\n//\t\t}\n//\n//\t result: \"foo\", 1\nfunc (n *Node) GetArray() ([]*Node, error) {\n\tif n == nil {\n\t\treturn nil, errNilNode\n\t}\n\n\tif n.nodeType != Array {\n\t\treturn nil, errNotArrayNode\n\t}\n\n\tval, err := n.Value()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tv, ok := val.([]*Node)\n\tif !ok {\n\t\treturn nil, errNotArrayNode\n\t}\n\n\treturn v, nil\n}\n\n// MustArray returns the array value if current node is array type.\n//\n// It panics if the current node is not array type.\nfunc (n *Node) MustArray() []*Node {\n\tv, err := n.GetArray()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn v\n}\n\n// AppendArray appends the given values to the current array node.\n//\n// If the current node is not array type, it returns an error.\n//\n// Example 1:\n//\n//\troot := Must(Unmarshal([]byte(`[{\"foo\":\"bar\"}]`)))\n//\tif err := root.AppendArray(NullNode(\"\")); err != nil {\n//\t\tt.Errorf(\"should not return error: %s\", err)\n//\t}\n//\n//\tresult: [{\"foo\":\"bar\"}, null]\n//\n// Example 2:\n//\n//\troot := Must(Unmarshal([]byte(`[\"bar\", \"baz\"]`)))\n//\terr := root.AppendArray(NumberNode(\"\", 1), StringNode(\"\", \"foo\"))\n//\tif err != nil {\n//\t\tt.Errorf(\"AppendArray returns error: %v\", err)\n//\t }\n//\n//\tresult: [\"bar\", \"baz\", 1, \"foo\"]\nfunc (n *Node) AppendArray(value ...*Node) error {\n\tif !n.IsArray() {\n\t\treturn errInvalidAppend\n\t}\n\n\tfor _, val := range value {\n\t\tif err := n.append(nil, val); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tn.mark()\n\treturn nil\n}\n\n// ArrayEach executes the callback for each element in the JSON array.\n//\n// Usage:\n//\n//\tjsonArrayNode.ArrayEach(func(i int, valueNode *Node) {\n//\t ufmt.Println(i, valueNode)\n//\t})\nfunc (n *Node) ArrayEach(callback func(i int, target *Node)) {\n\tif n == nil || !n.IsArray() {\n\t\treturn\n\t}\n\n\tfor idx := 0; idx \u003c len(n.next); idx++ {\n\t\telement, err := n.GetIndex(idx)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tcallback(idx, element)\n\t}\n}\n\n// GetObject returns the object value if current node is object type.\n//\n// Usage:\n//\n//\troot := Must(Unmarshal([]byte(`{\"key\": \"value\"}`)))\n//\tobj, err := root.GetObject()\n//\tif err != nil {\n//\t\tt.Errorf(\"GetObject returns error: %v\", err)\n//\t}\n//\n//\tresult: map[string]*Node{\"key\": StringNode(\"key\", \"value\")}\nfunc (n *Node) GetObject() (map[string]*Node, error) {\n\tif n == nil {\n\t\treturn nil, errNilNode\n\t}\n\n\tif !n.IsObject() {\n\t\treturn nil, errNotObjectNode\n\t}\n\n\tval, err := n.Value()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tv, ok := val.(map[string]*Node)\n\tif !ok {\n\t\treturn nil, errNotObjectNode\n\t}\n\n\treturn v, nil\n}\n\n// MustObject returns the object value if current node is object type.\n//\n// It panics if the current node is not object type.\nfunc (n *Node) MustObject() map[string]*Node {\n\tv, err := n.GetObject()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn v\n}\n\n// AppendObject appends the given key and value to the current object node.\n//\n// If the current node is not object type, it returns an error.\nfunc (n *Node) AppendObject(key string, value *Node) error {\n\tif !n.IsObject() {\n\t\treturn errInvalidAppend\n\t}\n\n\tif err := n.append(\u0026key, value); err != nil {\n\t\treturn err\n\t}\n\n\tn.mark()\n\treturn nil\n}\n\n// ObjectEach executes the callback for each key-value pair in the JSON object.\n//\n// Usage:\n//\n//\tjsonObjectNode.ObjectEach(func(key string, valueNode *Node) {\n//\t ufmt.Println(key, valueNode)\n//\t})\nfunc (n *Node) ObjectEach(callback func(key string, value *Node)) {\n\tif n == nil || !n.IsObject() {\n\t\treturn\n\t}\n\n\tfor key, child := range n.next {\n\t\tcallback(key, child)\n\t}\n}\n\n// String converts the node to a string representation.\nfunc (n *Node) String() string {\n\tif n == nil {\n\t\treturn \"\"\n\t}\n\n\tif n.ready() \u0026\u0026 !n.modified {\n\t\treturn string(n.source())\n\t}\n\n\tval, err := Marshal(n)\n\tif err != nil {\n\t\treturn \"error: \" + err.Error()\n\t}\n\n\treturn string(val)\n}\n\n// Path builds the path of the current node.\n//\n// For example:\n//\n//\t{ \"key\": { \"sub\": [ \"val1\", \"val2\" ] }}\n//\n// The path of \"val2\" is: $.key.sub[1]\nfunc (n *Node) Path() string {\n\tif n == nil {\n\t\treturn \"\"\n\t}\n\n\tvar sb strings.Builder\n\n\tif n.prev == nil {\n\t\tsb.WriteString(\"$\")\n\t} else {\n\t\tsb.WriteString(n.prev.Path())\n\n\t\tif n.key != nil {\n\t\t\tsb.WriteString(\"['\" + n.Key() + \"']\")\n\t\t} else {\n\t\t\tsb.WriteString(\"[\" + strconv.Itoa(n.Index()) + \"]\")\n\t\t}\n\t}\n\n\treturn sb.String()\n}\n\n// mark marks the current node as modified.\nfunc (n *Node) mark() {\n\tnode := n\n\tfor node != nil \u0026\u0026 !node.modified {\n\t\tnode.modified = true\n\t\tnode = node.prev\n\t}\n}\n\n// isContainer checks the current node type is array or object.\nfunc (n *Node) isContainer() bool {\n\treturn n.IsArray() || n.IsObject()\n}\n\n// remove removes the value from the current container type node.\nfunc (n *Node) remove(v *Node) error {\n\tif !n.isContainer() {\n\t\treturn ufmt.Errorf(\n\t\t\t\"can't remove value from non-array or non-object node. got=%s\",\n\t\t\tn.Type().String(),\n\t\t)\n\t}\n\n\tif v.prev != n {\n\t\treturn errors.New(\"invalid parent node\")\n\t}\n\n\tn.mark()\n\tif n.IsArray() {\n\t\tdelete(n.next, strconv.Itoa(*v.index))\n\t\tn.dropIndex(*v.index)\n\t} else {\n\t\tdelete(n.next, *v.key)\n\t}\n\n\tv.prev = nil\n\treturn nil\n}\n\n// dropIndex rebase the index of current array node values.\nfunc (n *Node) dropIndex(idx int) {\n\tfor i := idx + 1; i \u003c= len(n.next); i++ {\n\t\tprv := i - 1\n\t\tif curr, ok := n.next[strconv.Itoa(i)]; ok {\n\t\t\tcurr.index = \u0026prv\n\t\t\tn.next[strconv.Itoa(prv)] = curr\n\t\t}\n\n\t\tdelete(n.next, strconv.Itoa(i))\n\t}\n}\n\n// append is a helper function to append the given value to the current container type node.\nfunc (n *Node) append(key *string, val *Node) error {\n\tif n.isSameOrParentNode(val) {\n\t\treturn errInvalidAppendCycle\n\t}\n\n\tif val.prev != nil {\n\t\tif err := val.prev.remove(val); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tval.prev = n\n\tval.key = key\n\n\tif key == nil {\n\t\tsize := len(n.next)\n\t\tval.index = \u0026size\n\t\tn.next[strconv.Itoa(size)] = val\n\t} else {\n\t\tif old, ok := n.next[*key]; ok {\n\t\t\tif err := n.remove(old); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tn.next[*key] = val\n\t}\n\n\treturn nil\n}\n\nfunc (n *Node) isSameOrParentNode(nd *Node) bool {\n\treturn n == nd || n.isParentNode(nd)\n}\n\nfunc (n *Node) isParentNode(nd *Node) bool {\n\tif n == nil {\n\t\treturn false\n\t}\n\n\tfor curr := nd.prev; curr != nil; curr = curr.prev {\n\t\tif curr == n {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n// cptrs returns the pointer of the given string value.\nfunc cptrs(cpy *string) *string {\n\tif cpy == nil {\n\t\treturn nil\n\t}\n\n\tval := *cpy\n\n\treturn \u0026val\n}\n\n// cptri returns the pointer of the given integer value.\nfunc cptri(i *int) *int {\n\tif i == nil {\n\t\treturn nil\n\t}\n\n\tval := *i\n\treturn \u0026val\n}\n\n// Must panics if the given node is not fulfilled the expectation.\n// Usage:\n//\n//\tnode := Must(Unmarshal([]byte(`{\"key\": \"value\"}`))\nfunc Must(root *Node, expect error) *Node {\n\tif expect != nil {\n\t\tpanic(expect)\n\t}\n\n\treturn root\n}\n"},{"name":"node_test.gno","body":"package json\n\nimport (\n\t\"bytes\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar (\n\tnilKey *string\n\tdummyKey = \"key\"\n)\n\ntype _args struct {\n\tprev *Node\n\tbuf *buffer\n\ttyp ValueType\n\tkey **string\n}\n\ntype simpleNode struct {\n\tname string\n\tnode *Node\n}\n\nfunc TestNode_CreateNewNode(t *testing.T) {\n\trel := \u0026dummyKey\n\n\ttests := []struct {\n\t\tname string\n\t\targs _args\n\t\texpectCurr *Node\n\t\texpectErr bool\n\t\texpectPanic bool\n\t}{\n\t\t{\n\t\t\tname: \"child for non container type\",\n\t\t\targs: _args{\n\t\t\t\tprev: BoolNode(\"\", true),\n\t\t\t\tbuf: newBuffer(make([]byte, 10)),\n\t\t\t\ttyp: Boolean,\n\t\t\t\tkey: \u0026rel,\n\t\t\t},\n\t\t\texpectCurr: nil,\n\t\t\texpectErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tdefer func() {\n\t\t\t\tif r := recover(); r != nil {\n\t\t\t\t\tif tt.expectPanic {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tt.Errorf(\"%s panic occurred when not expected: %v\", tt.name, r)\n\t\t\t\t} else if tt.expectPanic {\n\t\t\t\t\tt.Errorf(\"%s expected panic but didn't occur\", tt.name)\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\tgot, err := NewNode(tt.args.prev, tt.args.buf, tt.args.typ, tt.args.key)\n\t\t\tif (err != nil) != tt.expectErr {\n\t\t\t\tt.Errorf(\"%s error = %v, expect error %v\", tt.name, err, tt.expectErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif tt.expectErr {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif !compareNodes(got, tt.expectCurr) {\n\t\t\t\tt.Errorf(\"%s got = %v, want %v\", tt.name, got, tt.expectCurr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_Value(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tdata []byte\n\t\t_type ValueType\n\t\texpected interface{}\n\t\terrExpected bool\n\t}{\n\t\t{name: \"null\", data: []byte(\"null\"), _type: Null, expected: nil},\n\t\t{name: \"1\", data: []byte(\"1\"), _type: Number, expected: float64(1)},\n\t\t{name: \".1\", data: []byte(\".1\"), _type: Number, expected: float64(.1)},\n\t\t{name: \"-.1e1\", data: []byte(\"-.1e1\"), _type: Number, expected: float64(-1)},\n\t\t{name: \"string\", data: []byte(\"\\\"foo\\\"\"), _type: String, expected: \"foo\"},\n\t\t{name: \"space\", data: []byte(\"\\\"foo bar\\\"\"), _type: String, expected: \"foo bar\"},\n\t\t{name: \"true\", data: []byte(\"true\"), _type: Boolean, expected: true},\n\t\t{name: \"invalid true\", data: []byte(\"tru\"), _type: Unknown, errExpected: true},\n\t\t{name: \"invalid false\", data: []byte(\"fals\"), _type: Unknown, errExpected: true},\n\t\t{name: \"false\", data: []byte(\"false\"), _type: Boolean, expected: false},\n\t\t{name: \"e1\", data: []byte(\"e1\"), _type: Unknown, errExpected: true},\n\t\t{name: \"1a\", data: []byte(\"1a\"), _type: Unknown, errExpected: true},\n\t\t{name: \"string error\", data: []byte(\"\\\"foo\\nbar\\\"\"), _type: String, errExpected: true},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tcurr := \u0026Node{\n\t\t\t\tdata: tt.data,\n\t\t\t\tnodeType: tt._type,\n\t\t\t\tborders: [2]int{0, len(tt.data)},\n\t\t\t}\n\n\t\t\tgot, err := curr.Value()\n\t\t\tif err != nil {\n\t\t\t\tif !tt.errExpected {\n\t\t\t\t\tt.Errorf(\"%s error = %v, expect error %v\", tt.name, err, tt.errExpected)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif got != tt.expected {\n\t\t\t\tt.Errorf(\"%s got = %v, want %v\", tt.name, got, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_Delete(t *testing.T) {\n\troot := Must(Unmarshal([]byte(`{\"foo\":\"bar\"}`)))\n\tif err := root.Delete(); err != nil {\n\t\tt.Errorf(\"Delete returns error: %v\", err)\n\t}\n\n\tif value, err := Marshal(root); err != nil {\n\t\tt.Errorf(\"Marshal returns error: %v\", err)\n\t} else if string(value) != `{\"foo\":\"bar\"}` {\n\t\tt.Errorf(\"Marshal returns wrong value: %s\", string(value))\n\t}\n\n\tfoo := root.MustKey(\"foo\")\n\tif err := foo.Delete(); err != nil {\n\t\tt.Errorf(\"Delete returns error while handling foo: %v\", err)\n\t}\n\n\tif value, err := Marshal(root); err != nil {\n\t\tt.Errorf(\"Marshal returns error: %v\", err)\n\t} else if string(value) != `{}` {\n\t\tt.Errorf(\"Marshal returns wrong value: %s\", string(value))\n\t}\n\n\tif value, err := Marshal(foo); err != nil {\n\t\tt.Errorf(\"Marshal returns error: %v\", err)\n\t} else if string(value) != `\"bar\"` {\n\t\tt.Errorf(\"Marshal returns wrong value: %s\", string(value))\n\t}\n\n\tif foo.prev != nil {\n\t\tt.Errorf(\"foo.prev should be nil\")\n\t}\n}\n\nfunc TestNode_ObjectNode(t *testing.T) {\n\tobjs := map[string]*Node{\n\t\t\"key1\": NullNode(\"null\"),\n\t\t\"key2\": NumberNode(\"answer\", 42),\n\t\t\"key3\": StringNode(\"string\", \"foobar\"),\n\t\t\"key4\": BoolNode(\"bool\", true),\n\t}\n\n\tnode := ObjectNode(\"test\", objs)\n\n\tif len(node.next) != len(objs) {\n\t\tt.Errorf(\"ObjectNode: want %v got %v\", len(objs), len(node.next))\n\t}\n\n\tfor k, v := range objs {\n\t\tif node.next[k] == nil {\n\t\t\tt.Errorf(\"ObjectNode: want %v got %v\", v, node.next[k])\n\t\t}\n\t}\n}\n\nfunc TestNode_AppendObject(t *testing.T) {\n\tif err := Must(Unmarshal([]byte(`{\"foo\":\"bar\",\"baz\":null}`))).AppendObject(\"biz\", NullNode(\"\")); err != nil {\n\t\tt.Errorf(\"AppendArray should return error\")\n\t}\n\n\troot := Must(Unmarshal([]byte(`{\"foo\":\"bar\"}`)))\n\tif err := root.AppendObject(\"baz\", NullNode(\"\")); err != nil {\n\t\tt.Errorf(\"AppendObject should not return error: %s\", err)\n\t}\n\n\tif value, err := Marshal(root); err != nil {\n\t\tt.Errorf(\"Marshal returns error: %v\", err)\n\t} else if isSameObject(string(value), `\"{\"foo\":\"bar\",\"baz\":null}\"`) {\n\t\tt.Errorf(\"Marshal returns wrong value: %s\", string(value))\n\t}\n\n\t// FIXME: this may fail if execute test in more than 3 times in a row.\n\tif err := root.AppendObject(\"biz\", NumberNode(\"\", 42)); err != nil {\n\t\tt.Errorf(\"AppendObject returns error: %v\", err)\n\t}\n\n\tval, err := Marshal(root)\n\tif err != nil {\n\t\tt.Errorf(\"Marshal returns error: %v\", err)\n\t}\n\n\t// FIXME: this may fail if execute test in more than 3 times in a row.\n\tif isSameObject(string(val), `\"{\"foo\":\"bar\",\"baz\":null,\"biz\":42}\"`) {\n\t\tt.Errorf(\"Marshal returns wrong value: %s\", string(val))\n\t}\n}\n\nfunc TestNode_ArrayNode(t *testing.T) {\n\tarr := []*Node{\n\t\tNullNode(\"nil\"),\n\t\tNumberNode(\"num\", 42),\n\t\tStringNode(\"str\", \"foobar\"),\n\t\tBoolNode(\"bool\", true),\n\t}\n\n\tnode := ArrayNode(\"test\", arr)\n\n\tif len(node.next) != len(arr) {\n\t\tt.Errorf(\"ArrayNode: want %v got %v\", len(arr), len(node.next))\n\t}\n\n\tfor i, v := range arr {\n\t\tif node.next[strconv.Itoa(i)] == nil {\n\t\t\tt.Errorf(\"ArrayNode: want %v got %v\", v, node.next[strconv.Itoa(i)])\n\t\t}\n\t}\n}\n\nfunc TestNode_AppendArray(t *testing.T) {\n\tif err := Must(Unmarshal([]byte(`[{\"foo\":\"bar\"}]`))).AppendArray(NullNode(\"\")); err != nil {\n\t\tt.Errorf(\"should return error\")\n\t}\n\n\troot := Must(Unmarshal([]byte(`[{\"foo\":\"bar\"}]`)))\n\tif err := root.AppendArray(NullNode(\"\")); err != nil {\n\t\tt.Errorf(\"should not return error: %s\", err)\n\t}\n\n\tif value, err := Marshal(root); err != nil {\n\t\tt.Errorf(\"Marshal returns error: %v\", err)\n\t} else if string(value) != `[{\"foo\":\"bar\"},null]` {\n\t\tt.Errorf(\"Marshal returns wrong value: %s\", string(value))\n\t}\n\n\tif err := root.AppendArray(\n\t\tNumberNode(\"\", 1),\n\t\tStringNode(\"\", \"foo\"),\n\t\tMust(Unmarshal([]byte(`[0,1,null,true,\"example\"]`))),\n\t\tMust(Unmarshal([]byte(`{\"foo\": true, \"bar\": null, \"baz\": 123}`))),\n\t); err != nil {\n\t\tt.Errorf(\"AppendArray returns error: %v\", err)\n\t}\n\n\tif value, err := Marshal(root); err != nil {\n\t\tt.Errorf(\"Marshal returns error: %v\", err)\n\t} else if string(value) != `[{\"foo\":\"bar\"},null,1,\"foo\",[0,1,null,true,\"example\"],{\"foo\": true, \"bar\": null, \"baz\": 123}]` {\n\t\tt.Errorf(\"Marshal returns wrong value: %s\", string(value))\n\t}\n}\n\n/******** value getter ********/\n\nfunc TestNode_GetBool(t *testing.T) {\n\troot, err := Unmarshal([]byte(`true`))\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err.Error())\n\t\treturn\n\t}\n\n\tvalue, err := root.GetBool()\n\tif err != nil {\n\t\tt.Errorf(\"Error on root.GetBool(): %s\", err.Error())\n\t}\n\n\tif !value {\n\t\tt.Errorf(\"root.GetBool() is corrupted\")\n\t}\n}\n\nfunc TestNode_GetBool_Fail(t *testing.T) {\n\ttests := []simpleNode{\n\t\t{\"nil node\", (*Node)(nil)},\n\t\t{\"literally null node\", NullNode(\"\")},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif _, err := tt.node.GetBool(); err == nil {\n\t\t\t\tt.Errorf(\"%s should be an error\", tt.name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_IsBool(t *testing.T) {\n\ttests := []simpleNode{\n\t\t{\"true\", BoolNode(\"\", true)},\n\t\t{\"false\", BoolNode(\"\", false)},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif !tt.node.IsBool() {\n\t\t\t\tt.Errorf(\"%s should be a bool\", tt.name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_IsBool_With_Unmarshal(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tjson []byte\n\t\twant bool\n\t}{\n\t\t{\"true\", []byte(\"true\"), true},\n\t\t{\"false\", []byte(\"false\"), true},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\troot, err := Unmarshal(tt.json)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Error on Unmarshal(): %s\", err.Error())\n\t\t\t}\n\n\t\t\tif root.IsBool() != tt.want {\n\t\t\t\tt.Errorf(\"%s should be a bool\", tt.name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nvar nullJson = []byte(`null`)\n\nfunc TestNode_GetNull(t *testing.T) {\n\troot, err := Unmarshal(nullJson)\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err.Error())\n\t}\n\n\tvalue, err := root.GetNull()\n\tif err != nil {\n\t\tt.Errorf(\"error occurred while getting null, %s\", err)\n\t}\n\n\tif value != nil {\n\t\tt.Errorf(\"value is not matched. expected: nil, got: %v\", value)\n\t}\n}\n\nfunc TestNode_GetNull_Fail(t *testing.T) {\n\ttests := []simpleNode{\n\t\t{\"nil node\", (*Node)(nil)},\n\t\t{\"number node is null\", NumberNode(\"\", 42)},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif _, err := tt.node.GetNull(); err == nil {\n\t\t\t\tt.Errorf(\"%s should be an error\", tt.name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_MustNull(t *testing.T) {\n\troot, err := Unmarshal(nullJson)\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err.Error())\n\t}\n\n\tvalue := root.MustNull()\n\tif value != nil {\n\t\tt.Errorf(\"value is not matched. expected: nil, got: %v\", value)\n\t}\n}\n\nfunc TestNode_GetNumeric_Float(t *testing.T) {\n\troot, err := Unmarshal([]byte(`123.456`))\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err)\n\t\treturn\n\t}\n\n\tvalue, err := root.GetNumeric()\n\tif err != nil {\n\t\tt.Errorf(\"Error on root.GetNumeric(): %s\", err)\n\t}\n\n\tif value != float64(123.456) {\n\t\tt.Errorf(ufmt.Sprintf(\"value is not matched. expected: 123.456, got: %v\", value))\n\t}\n}\n\nfunc TestNode_GetNumeric_Scientific_Notation(t *testing.T) {\n\troot, err := Unmarshal([]byte(`1e3`))\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err)\n\t\treturn\n\t}\n\n\tvalue, err := root.GetNumeric()\n\tif err != nil {\n\t\tt.Errorf(\"Error on root.GetNumeric(): %s\", err)\n\t}\n\n\tif value != float64(1000) {\n\t\tt.Errorf(ufmt.Sprintf(\"value is not matched. expected: 1000, got: %v\", value))\n\t}\n}\n\nfunc TestNode_GetNumeric_With_Unmarshal(t *testing.T) {\n\troot, err := Unmarshal([]byte(`123`))\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err)\n\t\treturn\n\t}\n\n\tvalue, err := root.GetNumeric()\n\tif err != nil {\n\t\tt.Errorf(\"Error on root.GetNumeric(): %s\", err)\n\t}\n\n\tif value != float64(123) {\n\t\tt.Errorf(ufmt.Sprintf(\"value is not matched. expected: 123, got: %v\", value))\n\t}\n}\n\nfunc TestNode_GetNumeric_Fail(t *testing.T) {\n\ttests := []simpleNode{\n\t\t{\"nil node\", (*Node)(nil)},\n\t\t{\"null node\", NullNode(\"\")},\n\t\t{\"string node\", StringNode(\"\", \"123\")},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif _, err := tt.node.GetNumeric(); err == nil {\n\t\t\t\tt.Errorf(\"%s should be an error\", tt.name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_GetString(t *testing.T) {\n\troot, err := Unmarshal([]byte(`\"123foobar 3456\"`))\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err)\n\t}\n\n\tvalue, err := root.GetString()\n\tif err != nil {\n\t\tt.Errorf(\"Error on root.GetString(): %s\", err)\n\t}\n\n\tif value != \"123foobar 3456\" {\n\t\tt.Errorf(ufmt.Sprintf(\"value is not matched. expected: 123, got: %s\", value))\n\t}\n}\n\nfunc TestNode_GetString_Fail(t *testing.T) {\n\ttests := []simpleNode{\n\t\t{\"nil node\", (*Node)(nil)},\n\t\t{\"null node\", NullNode(\"\")},\n\t\t{\"number node\", NumberNode(\"\", 123)},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif _, err := tt.node.GetString(); err == nil {\n\t\t\t\tt.Errorf(\"%s should be an error\", tt.name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_MustString(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tdata []byte\n\t}{\n\t\t{\"foo\", []byte(`\"foo\"`)},\n\t\t{\"foo bar\", []byte(`\"foo bar\"`)},\n\t\t{\"\", []byte(`\"\"`)},\n\t\t{\"안녕하세요\", []byte(`\"안녕하세요\"`)},\n\t\t{\"こんにちは\", []byte(`\"こんにちは\"`)},\n\t\t{\"你好\", []byte(`\"你好\"`)},\n\t\t{\"one \\\"encoded\\\" string\", []byte(`\"one \\\"encoded\\\" string\"`)},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\troot, err := Unmarshal(tt.data)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Error on Unmarshal(): %s\", err)\n\t\t\t}\n\n\t\t\tvalue := root.MustString()\n\t\t\tif value != tt.name {\n\t\t\t\tt.Errorf(\"value is not matched. expected: %s, got: %s\", tt.name, value)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal_Array(t *testing.T) {\n\troot, err := Unmarshal([]byte(\" [1,[\\\"1\\\",[1,[1,2,3]]]]\\r\\n\"))\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal: %s\", err.Error())\n\t}\n\n\tif root == nil {\n\t\tt.Errorf(\"Error on Unmarshal: root is nil\")\n\t}\n\n\tif root.Type() != Array {\n\t\tt.Errorf(\"Error on Unmarshal: wrong type\")\n\t}\n\n\tarray, err := root.GetArray()\n\tif err != nil {\n\t\tt.Errorf(\"error occurred while getting array, %s\", err)\n\t} else if len(array) != 2 {\n\t\tt.Errorf(\"expected 2 elements, got %d\", len(array))\n\t} else if val, err := array[0].GetNumeric(); err != nil {\n\t\tt.Errorf(\"value of array[0] is not numeric. got: %v\", array[0].value)\n\t} else if val != 1 {\n\t\tt.Errorf(\"Error on array[0].GetNumeric(): expected to be '1', got: %v\", val)\n\t} else if val, err := array[1].GetArray(); err != nil {\n\t\tt.Errorf(\"error occurred while getting array, %s\", err.Error())\n\t} else if len(val) != 2 {\n\t\tt.Errorf(\"Error on array[1].GetArray(): expected 2 elements, got %d\", len(val))\n\t} else if el, err := val[0].GetString(); err != nil {\n\t\tt.Errorf(\"error occurred while getting string, %s\", err.Error())\n\t} else if el != \"1\" {\n\t\tt.Errorf(\"Error on val[0].GetString(): expected to be '1', got: %s\", el)\n\t}\n}\n\nvar sampleArr = []byte(`[-1, 2, 3, 4, 5, 6]`)\n\nfunc TestNode_GetArray(t *testing.T) {\n\troot, err := Unmarshal(sampleArr)\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err)\n\t\treturn\n\t}\n\n\tarray, err := root.GetArray()\n\tif err != nil {\n\t\tt.Errorf(\"Error on root.GetArray(): %s\", err)\n\t}\n\n\tif len(array) != 6 {\n\t\tt.Errorf(ufmt.Sprintf(\"length is not matched. expected: 3, got: %d\", len(array)))\n\t}\n\n\tfor i, node := range array {\n\t\tfor j, val := range []int{-1, 2, 3, 4, 5, 6} {\n\t\t\tif i == j {\n\t\t\t\tif v, err := node.GetNumeric(); err != nil {\n\t\t\t\t\tt.Errorf(ufmt.Sprintf(\"Error on node.GetNumeric(): %s\", err))\n\t\t\t\t} else if v != float64(val) {\n\t\t\t\t\tt.Errorf(ufmt.Sprintf(\"value is not matched. expected: %d, got: %v\", val, v))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestNode_GetArray_Fail(t *testing.T) {\n\ttests := []simpleNode{\n\t\t{\"nil node\", (*Node)(nil)},\n\t\t{\"null node\", NullNode(\"\")},\n\t\t{\"number node\", NumberNode(\"\", 123)},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif _, err := tt.node.GetArray(); err == nil {\n\t\t\t\tt.Errorf(\"%s should be an error\", tt.name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_IsArray(t *testing.T) {\n\troot, err := Unmarshal(sampleArr)\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err)\n\t\treturn\n\t}\n\n\tif root.Type() != Array {\n\t\tt.Errorf(ufmt.Sprintf(\"Must be an array. got: %s\", root.Type().String()))\n\t}\n}\n\nfunc TestNode_ArrayEach(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tjson string\n\t\texpected []int\n\t}{\n\t\t{\n\t\t\tname: \"empty array\",\n\t\t\tjson: `[]`,\n\t\t\texpected: []int{},\n\t\t},\n\t\t{\n\t\t\tname: \"single element\",\n\t\t\tjson: `[42]`,\n\t\t\texpected: []int{42},\n\t\t},\n\t\t{\n\t\t\tname: \"multiple elements\",\n\t\t\tjson: `[1, 2, 3, 4, 5]`,\n\t\t\texpected: []int{1, 2, 3, 4, 5},\n\t\t},\n\t\t{\n\t\t\tname: \"multiple elements but all values are same\",\n\t\t\tjson: `[1, 1, 1, 1, 1]`,\n\t\t\texpected: []int{1, 1, 1, 1, 1},\n\t\t},\n\t\t{\n\t\t\tname: \"multiple elements with non-numeric values\",\n\t\t\tjson: `[\"a\", \"b\", \"c\", \"d\", \"e\"]`,\n\t\t\texpected: []int{},\n\t\t},\n\t\t{\n\t\t\tname: \"non-array node\",\n\t\t\tjson: `{\"not\": \"an array\"}`,\n\t\t\texpected: []int{},\n\t\t},\n\t\t{\n\t\t\tname: \"array containing numeric and non-numeric elements\",\n\t\t\tjson: `[\"1\", 2, 3, \"4\", 5, \"6\"]`,\n\t\t\texpected: []int{2, 3, 5},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\troot, err := Unmarshal([]byte(tc.json))\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Unmarshal failed: %v\", err)\n\t\t\t}\n\n\t\t\tvar result []int // callback result\n\t\t\troot.ArrayEach(func(index int, element *Node) {\n\t\t\t\tif val, err := strconv.Atoi(element.String()); err == nil {\n\t\t\t\t\tresult = append(result, val)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tif len(result) != len(tc.expected) {\n\t\t\t\tt.Errorf(\"%s: expected %d elements, got %d\", tc.name, len(tc.expected), len(result))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfor i, val := range result {\n\t\t\t\tif val != tc.expected[i] {\n\t\t\t\t\tt.Errorf(\"%s: expected value at index %d to be %d, got %d\", tc.name, i, tc.expected[i], val)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_Key(t *testing.T) {\n\troot, err := Unmarshal([]byte(`{\"foo\": true, \"bar\": null, \"baz\": 123, \"biz\": [1,2,3]}`))\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err.Error())\n\t}\n\n\tobj := root.MustObject()\n\tfor key, node := range obj {\n\t\tif key != node.Key() {\n\t\t\tt.Errorf(\"Key() = %v, want %v\", node.Key(), key)\n\t\t}\n\t}\n\n\tkeys := []string{\"foo\", \"bar\", \"baz\", \"biz\"}\n\tfor _, key := range keys {\n\t\tif obj[key].Key() != key {\n\t\t\tt.Errorf(\"Key() = %v, want %v\", obj[key].Key(), key)\n\t\t}\n\t}\n\n\t// TODO: resolve stack overflow\n\t// if root.MustKey(\"foo\").Clone().Key() != \"\" {\n\t// \tt.Errorf(\"wrong key found for cloned key\")\n\t// }\n\n\tif (*Node)(nil).Key() != \"\" {\n\t\tt.Errorf(\"wrong key found for nil node\")\n\t}\n}\n\nfunc TestNode_Size(t *testing.T) {\n\troot, err := Unmarshal(sampleArr)\n\tif err != nil {\n\t\tt.Errorf(\"error occurred while unmarshal\")\n\t}\n\n\tsize := root.Size()\n\tif size != 6 {\n\t\tt.Errorf(ufmt.Sprintf(\"Size() must be 6. got: %v\", size))\n\t}\n\n\tif (*Node)(nil).Size() != 0 {\n\t\tt.Errorf(ufmt.Sprintf(\"Size() must be 0. got: %v\", (*Node)(nil).Size()))\n\t}\n}\n\nfunc TestNode_Index(t *testing.T) {\n\troot, err := Unmarshal([]byte(`[1, 2, 3, 4, 5, 6]`))\n\tif err != nil {\n\t\tt.Error(\"error occurred while unmarshal\")\n\t}\n\n\tarr := root.MustArray()\n\tfor i, node := range arr {\n\t\tif i != node.Index() {\n\t\t\tt.Errorf(ufmt.Sprintf(\"Index() must be nil. got: %v\", i))\n\t\t}\n\t}\n}\n\nfunc TestNode_Index_Fail(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tnode *Node\n\t\twant int\n\t}{\n\t\t{\"nil node\", (*Node)(nil), -1},\n\t\t{\"null node\", NullNode(\"\"), -1},\n\t\t{\"object node\", ObjectNode(\"\", nil), -1},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := tt.node.Index(); got != tt.want {\n\t\t\t\tt.Errorf(\"Index() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_GetIndex(t *testing.T) {\n\troot := Must(Unmarshal([]byte(`[1, 2, 3, 4, 5, 6]`)))\n\texpected := []int{1, 2, 3, 4, 5, 6}\n\n\tif len(expected) != root.Size() {\n\t\tt.Errorf(\"length is not matched. expected: %d, got: %d\", len(expected), root.Size())\n\t}\n\n\t// TODO: if length exceeds, stack overflow occurs. need to fix\n\tfor i, v := range expected {\n\t\tval, err := root.GetIndex(i)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"error occurred while getting index %d, %s\", i, err)\n\t\t}\n\n\t\tif val.MustNumeric() != float64(v) {\n\t\t\tt.Errorf(\"value is not matched. expected: %d, got: %v\", v, val.MustNumeric())\n\t\t}\n\t}\n}\n\nfunc TestNode_GetIndex_InputIndex_Exceed_Original_Node_Index(t *testing.T) {\n\troot, err := Unmarshal([]byte(`[1, 2, 3, 4, 5, 6]`))\n\tif err != nil {\n\t\tt.Errorf(\"error occurred while unmarshal\")\n\t}\n\n\t_, err = root.GetIndex(10)\n\tif err == nil {\n\t\tt.Errorf(\"GetIndex should return error\")\n\t}\n}\n\nfunc TestNode_DeleteIndex(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\texpected string\n\t\tindex int\n\t\tok bool\n\t}{\n\t\t{`null`, ``, 0, false},\n\t\t{`1`, ``, 0, false},\n\t\t{`{}`, ``, 0, false},\n\t\t{`{\"foo\":\"bar\"}`, ``, 0, false},\n\t\t{`true`, ``, 0, false},\n\t\t{`[]`, ``, 0, false},\n\t\t{`[]`, ``, -1, false},\n\t\t{`[1]`, `[]`, 0, true},\n\t\t{`[{}]`, `[]`, 0, true},\n\t\t{`[{}, [], 42]`, `[{}, []]`, -1, true},\n\t\t{`[{}, [], 42]`, `[[], 42]`, 0, true},\n\t\t{`[{}, [], 42]`, `[{}, 42]`, 1, true},\n\t\t{`[{}, [], 42]`, `[{}, []]`, 2, true},\n\t\t{`[{}, [], 42]`, ``, 10, false},\n\t\t{`[{}, [], 42]`, ``, -10, false},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\troot := Must(Unmarshal([]byte(tt.name)))\n\t\t\terr := root.DeleteIndex(tt.index)\n\t\t\tif err != nil \u0026\u0026 tt.ok {\n\t\t\t\tt.Errorf(\"DeleteIndex returns error: %v\", err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_GetKey(t *testing.T) {\n\troot, err := Unmarshal([]byte(`{\"foo\": true, \"bar\": null}`))\n\tif err != nil {\n\t\tt.Error(\"error occurred while unmarshal\")\n\t}\n\n\tvalue, err := root.GetKey(\"foo\")\n\tif err != nil {\n\t\tt.Errorf(\"error occurred while getting key, %s\", err)\n\t}\n\n\tif value.MustBool() != true {\n\t\tt.Errorf(\"value is not matched. expected: true, got: %v\", value.MustBool())\n\t}\n\n\tvalue, err = root.GetKey(\"bar\")\n\tif err != nil {\n\t\tt.Errorf(\"error occurred while getting key, %s\", err)\n\t}\n\n\t_, err = root.GetKey(\"baz\")\n\tif err == nil {\n\t\tt.Errorf(\"key baz is not exist. must be failed\")\n\t}\n\n\tif value.MustNull() != nil {\n\t\tt.Errorf(\"value is not matched. expected: nil, got: %v\", value.MustNull())\n\t}\n}\n\nfunc TestNode_GetKey_Fail(t *testing.T) {\n\ttests := []simpleNode{\n\t\t{\"nil node\", (*Node)(nil)},\n\t\t{\"null node\", NullNode(\"\")},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif _, err := tt.node.GetKey(\"\"); err == nil {\n\t\t\t\tt.Errorf(\"%s should be an error\", tt.name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_GetUniqueKeyList(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tjson string\n\t\texpected []string\n\t}{\n\t\t{\n\t\t\tname: \"simple foo/bar\",\n\t\t\tjson: `{\"foo\": true, \"bar\": null}`,\n\t\t\texpected: []string{\"foo\", \"bar\"},\n\t\t},\n\t\t{\n\t\t\tname: \"empty object\",\n\t\t\tjson: `{}`,\n\t\t\texpected: []string{},\n\t\t},\n\t\t{\n\t\t\tname: \"nested object\",\n\t\t\tjson: `{\n\t\t\t\t\"outer\": {\n\t\t\t\t\t\"inner\": {\n\t\t\t\t\t\t\"key\": \"value\"\n\t\t\t\t\t},\n\t\t\t\t\t\"array\": [1, 2, 3]\n\t\t\t\t},\n\t\t\t\t\"another\": \"item\"\n\t\t\t}`,\n\t\t\texpected: []string{\"outer\", \"inner\", \"key\", \"array\", \"another\"},\n\t\t},\n\t\t{\n\t\t\tname: \"complex object\",\n\t\t\tjson: `{\n\t\t\t\t\"Image\": {\n\t\t\t\t\t\"Width\": 800,\n\t\t\t\t\t\"Height\": 600,\n\t\t\t\t\t\"Title\": \"View from 15th Floor\",\n\t\t\t\t\t\"Thumbnail\": {\n\t\t\t\t\t\t\"Url\": \"http://www.example.com/image/481989943\",\n\t\t\t\t\t\t\"Height\": 125,\n\t\t\t\t\t\t\"Width\": 100\n\t\t\t\t\t},\n\t\t\t\t\t\"Animated\": false,\n\t\t\t\t\t\"IDs\": [116, 943, 234, 38793]\n\t\t\t\t}\n\t\t\t}`,\n\t\t\texpected: []string{\"Image\", \"Width\", \"Height\", \"Title\", \"Thumbnail\", \"Url\", \"Animated\", \"IDs\"},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\troot, err := Unmarshal([]byte(tt.json))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error occurred while unmarshal\")\n\t\t\t}\n\n\t\t\tvalue := root.UniqueKeyLists()\n\t\t\tif len(value) != len(tt.expected) {\n\t\t\t\tt.Errorf(\"%s length must be %v. got: %v. retrieved keys: %s\", tt.name, len(tt.expected), len(value), value)\n\t\t\t}\n\n\t\t\tfor _, key := range value {\n\t\t\t\tif !contains(tt.expected, key) {\n\t\t\t\t\tt.Errorf(\"EachKey() must be in %v. got: %v\", tt.expected, key)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TODO: resolve stack overflow\nfunc TestNode_IsEmpty(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tnode *Node\n\t\texpected bool\n\t}{\n\t\t{\"nil node\", (*Node)(nil), false}, // nil node is not empty.\n\t\t// {\"null node\", NullNode(\"\"), true},\n\t\t{\"empty object\", ObjectNode(\"\", nil), true},\n\t\t{\"empty array\", ArrayNode(\"\", nil), true},\n\t\t{\"non-empty object\", ObjectNode(\"\", map[string]*Node{\"foo\": BoolNode(\"foo\", true)}), false},\n\t\t{\"non-empty array\", ArrayNode(\"\", []*Node{BoolNode(\"0\", true)}), false},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := tt.node.Empty(); got != tt.expected {\n\t\t\t\tt.Errorf(\"%s = %v, want %v\", tt.name, got, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_Index_EmptyList(t *testing.T) {\n\troot, err := Unmarshal([]byte(`[]`))\n\tif err != nil {\n\t\tt.Errorf(\"error occurred while unmarshal\")\n\t}\n\n\tarray := root.MustArray()\n\tfor i, node := range array {\n\t\tif i != node.Index() {\n\t\t\tt.Errorf(ufmt.Sprintf(\"Index() must be nil. got: %v\", i))\n\t\t}\n\t}\n}\n\nfunc TestNode_GetObject(t *testing.T) {\n\troot, err := Unmarshal([]byte(`{\"foo\": true,\"bar\": null}`))\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err.Error())\n\t\treturn\n\t}\n\n\tvalue, err := root.GetObject()\n\tif err != nil {\n\t\tt.Errorf(\"Error on root.GetObject(): %s\", err.Error())\n\t}\n\n\tif _, ok := value[\"foo\"]; !ok {\n\t\tt.Errorf(\"root.GetObject() is corrupted: foo\")\n\t}\n\n\tif _, ok := value[\"bar\"]; !ok {\n\t\tt.Errorf(\"root.GetObject() is corrupted: bar\")\n\t}\n}\n\nfunc TestNode_GetObject_Fail(t *testing.T) {\n\ttests := []simpleNode{\n\t\t{\"nil node\", (*Node)(nil)},\n\t\t{\"get object from null node\", NullNode(\"\")},\n\t\t{\"not object node\", NumberNode(\"\", 123)},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif _, err := tt.node.GetObject(); err == nil {\n\t\t\t\tt.Errorf(\"%s should be an error\", tt.name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_ObjectEach(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tjson string\n\t\texpected map[string]int\n\t}{\n\t\t{\n\t\t\tname: \"empty object\",\n\t\t\tjson: `{}`,\n\t\t\texpected: make(map[string]int),\n\t\t},\n\t\t{\n\t\t\tname: \"single key-value pair\",\n\t\t\tjson: `{\"key\": 42}`,\n\t\t\texpected: map[string]int{\"key\": 42},\n\t\t},\n\t\t{\n\t\t\tname: \"multiple key-value pairs\",\n\t\t\tjson: `{\"one\": 1, \"two\": 2, \"three\": 3}`,\n\t\t\texpected: map[string]int{\"one\": 1, \"two\": 2, \"three\": 3},\n\t\t},\n\t\t{\n\t\t\tname: \"multiple key-value pairs with some non-numeric values\",\n\t\t\tjson: `{\"one\": 1, \"two\": \"2\", \"three\": 3, \"four\": \"4\"}`,\n\t\t\texpected: map[string]int{\"one\": 1, \"three\": 3},\n\t\t},\n\t\t{\n\t\t\tname: \"non-object node\",\n\t\t\tjson: `[\"not\", \"an\", \"object\"]`,\n\t\t\texpected: make(map[string]int),\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\troot, err := Unmarshal([]byte(tc.json))\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Unmarshal failed: %v\", err)\n\t\t\t}\n\n\t\t\tresult := make(map[string]int)\n\t\t\troot.ObjectEach(func(key string, value *Node) {\n\t\t\t\t// extract integer values from the object\n\t\t\t\tif val, err := strconv.Atoi(value.String()); err == nil {\n\t\t\t\t\tresult[key] = val\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tif len(result) != len(tc.expected) {\n\t\t\t\tt.Errorf(\"%s: expected %d key-value pairs, got %d\", tc.name, len(tc.expected), len(result))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfor key, val := range tc.expected {\n\t\t\t\tif result[key] != val {\n\t\t\t\t\tt.Errorf(\"%s: expected value for key %s to be %d, got %d\", tc.name, key, val, result[key])\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_ExampleMust(t *testing.T) {\n\tdata := []byte(`{\n \"Image\": {\n \"Width\": 800,\n \"Height\": 600,\n \"Title\": \"View from 15th Floor\",\n \"Thumbnail\": {\n \"Url\": \"http://www.example.com/image/481989943\",\n \"Height\": 125,\n \"Width\": 100\n },\n \"Animated\" : false,\n \"IDs\": [116, 943, 234, 38793]\n }\n }`)\n\n\troot := Must(Unmarshal(data))\n\tif root.Size() != 1 {\n\t\tt.Errorf(\"root.Size() must be 1. got: %v\", root.Size())\n\t}\n\n\tufmt.Sprintf(\"Object has %d inheritors inside\", root.Size())\n\t// Output:\n\t// Object has 1 inheritors inside\n}\n\n// Calculate AVG price from different types of objects, JSON from: https://goessner.net/articles/JsonPath/index.html#e3\nfunc TestExampleUnmarshal(t *testing.T) {\n\tdata := []byte(`{ \"store\": {\n \"book\": [ \n { \"category\": \"reference\",\n \"author\": \"Nigel Rees\",\n \"title\": \"Sayings of the Century\",\n \"price\": 8.95\n },\n { \"category\": \"fiction\",\n \"author\": \"Evelyn Waugh\",\n \"title\": \"Sword of Honour\",\n \"price\": 12.99\n },\n { \"category\": \"fiction\",\n \"author\": \"Herman Melville\",\n \"title\": \"Moby Dick\",\n \"isbn\": \"0-553-21311-3\",\n \"price\": 8.99\n },\n { \"category\": \"fiction\",\n \"author\": \"J. R. R. Tolkien\",\n \"title\": \"The Lord of the Rings\",\n \"isbn\": \"0-395-19395-8\",\n \"price\": 22.99\n }\n ],\n \"bicycle\": { \"color\": \"red\",\n \"price\": 19.95\n },\n \"tools\": null\n }\n}`)\n\n\troot, err := Unmarshal(data)\n\tif err != nil {\n\t\tt.Errorf(\"error occurred when unmarshal\")\n\t}\n\n\tstore := root.MustKey(\"store\").MustObject()\n\n\tvar prices float64\n\tsize := 0\n\tfor _, objects := range store {\n\t\tif objects.IsArray() \u0026\u0026 objects.Size() \u003e 0 {\n\t\t\tsize += objects.Size()\n\t\t\tfor _, object := range objects.MustArray() {\n\t\t\t\tprices += object.MustKey(\"price\").MustNumeric()\n\t\t\t}\n\t\t} else if objects.IsObject() \u0026\u0026 objects.HasKey(\"price\") {\n\t\t\tsize++\n\t\t\tprices += objects.MustKey(\"price\").MustNumeric()\n\t\t}\n\t}\n\n\tresult := int(prices / float64(size))\n\tufmt.Sprintf(\"AVG price: %d\", result)\n}\n\nfunc TestNode_ExampleMust_panic(t *testing.T) {\n\tdefer func() {\n\t\tif r := recover(); r == nil {\n\t\t\tt.Errorf(\"The code did not panic\")\n\t\t}\n\t}()\n\tdata := []byte(`{]`)\n\troot := Must(Unmarshal(data))\n\tufmt.Sprintf(\"Object has %d inheritors inside\", root.Size())\n}\n\nfunc TestNode_Path(t *testing.T) {\n\tdata := []byte(`{\n \"Image\": {\n \"Width\": 800,\n \"Height\": 600,\n \"Title\": \"View from 15th Floor\",\n \"Thumbnail\": {\n \"Url\": \"http://www.example.com/image/481989943\",\n \"Height\": 125,\n \"Width\": 100\n },\n \"Animated\" : false,\n \"IDs\": [116, 943, 234, 38793]\n }\n }`)\n\n\troot, err := Unmarshal(data)\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err.Error())\n\t\treturn\n\t}\n\n\tif root.Path() != \"$\" {\n\t\tt.Errorf(\"Wrong root.Path()\")\n\t}\n\n\telement := root.MustKey(\"Image\").MustKey(\"Thumbnail\").MustKey(\"Url\")\n\tif element.Path() != \"$['Image']['Thumbnail']['Url']\" {\n\t\tt.Errorf(\"Wrong path found: %s\", element.Path())\n\t}\n\n\tif (*Node)(nil).Path() != \"\" {\n\t\tt.Errorf(\"Wrong (nil).Path()\")\n\t}\n}\n\nfunc TestNode_Path2(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tnode *Node\n\t\twant string\n\t}{\n\t\t{\n\t\t\tname: \"Node with key\",\n\t\t\tnode: \u0026Node{\n\t\t\t\tprev: \u0026Node{},\n\t\t\t\tkey: func() *string { s := \"key\"; return \u0026s }(),\n\t\t\t},\n\t\t\twant: \"$['key']\",\n\t\t},\n\t\t{\n\t\t\tname: \"Node with index\",\n\t\t\tnode: \u0026Node{\n\t\t\t\tprev: \u0026Node{},\n\t\t\t\tindex: func() *int { i := 1; return \u0026i }(),\n\t\t\t},\n\t\t\twant: \"$[1]\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := tt.node.Path(); got != tt.want {\n\t\t\t\tt.Errorf(\"Path() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_Root(t *testing.T) {\n\troot := \u0026Node{}\n\tchild := \u0026Node{prev: root}\n\tgrandChild := \u0026Node{prev: child}\n\n\ttests := []struct {\n\t\tname string\n\t\tnode *Node\n\t\twant *Node\n\t}{\n\t\t{\n\t\t\tname: \"Root node\",\n\t\t\tnode: root,\n\t\t\twant: root,\n\t\t},\n\t\t{\n\t\t\tname: \"Child node\",\n\t\t\tnode: child,\n\t\t\twant: root,\n\t\t},\n\t\t{\n\t\t\tname: \"Grandchild node\",\n\t\t\tnode: grandChild,\n\t\t\twant: root,\n\t\t},\n\t\t{\n\t\t\tname: \"Node is nil\",\n\t\t\tnode: nil,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := tt.node.root(); got != tt.want {\n\t\t\t\tt.Errorf(\"root() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc contains(slice []string, item string) bool {\n\tfor _, a := range slice {\n\t\tif a == item {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n// ignore the sequence of keys by ordering them.\n// need to avoid import encoding/json and reflect package.\n// because gno does not support them for now.\n// TODO: use encoding/json to compare the result after if possible in gno.\nfunc isSameObject(a, b string) bool {\n\taPairs := strings.Split(strings.Trim(a, \"{}\"), \",\")\n\tbPairs := strings.Split(strings.Trim(b, \"{}\"), \",\")\n\n\taMap := make(map[string]string)\n\tbMap := make(map[string]string)\n\tfor _, pair := range aPairs {\n\t\tkv := strings.Split(pair, \":\")\n\t\tkey := strings.Trim(kv[0], `\"`)\n\t\tvalue := strings.Trim(kv[1], `\"`)\n\t\taMap[key] = value\n\t}\n\tfor _, pair := range bPairs {\n\t\tkv := strings.Split(pair, \":\")\n\t\tkey := strings.Trim(kv[0], `\"`)\n\t\tvalue := strings.Trim(kv[1], `\"`)\n\t\tbMap[key] = value\n\t}\n\n\taKeys := make([]string, 0, len(aMap))\n\tbKeys := make([]string, 0, len(bMap))\n\tfor k := range aMap {\n\t\taKeys = append(aKeys, k)\n\t}\n\n\tfor k := range bMap {\n\t\tbKeys = append(bKeys, k)\n\t}\n\n\tsort.Strings(aKeys)\n\tsort.Strings(bKeys)\n\n\tif len(aKeys) != len(bKeys) {\n\t\treturn false\n\t}\n\n\tfor i := range aKeys {\n\t\tif aKeys[i] != bKeys[i] {\n\t\t\treturn false\n\t\t}\n\n\t\tif aMap[aKeys[i]] != bMap[bKeys[i]] {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc compareNodes(n1, n2 *Node) bool {\n\tif n1 == nil || n2 == nil {\n\t\treturn n1 == n2\n\t}\n\n\tif n1.key != n2.key {\n\t\treturn false\n\t}\n\n\tif !bytes.Equal(n1.data, n2.data) {\n\t\treturn false\n\t}\n\n\tif n1.index != n2.index {\n\t\treturn false\n\t}\n\n\tif n1.borders != n2.borders {\n\t\treturn false\n\t}\n\n\tif n1.modified != n2.modified {\n\t\treturn false\n\t}\n\n\tif n1.nodeType != n2.nodeType {\n\t\treturn false\n\t}\n\n\tif !compareNodes(n1.prev, n2.prev) {\n\t\treturn false\n\t}\n\n\tif len(n1.next) != len(n2.next) {\n\t\treturn false\n\t}\n\n\tfor k, v := range n1.next {\n\t\tif !compareNodes(v, n2.next[k]) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n"},{"name":"parser.gno","body":"package json\n\nimport (\n\t\"bytes\"\n)\n\nconst (\n\tunescapeStackBufSize = 64\n\tabsMinInt64 = 1 \u003c\u003c 63\n\tmaxInt64 = absMinInt64 - 1\n\tmaxUint64 = 1\u003c\u003c64 - 1\n)\n\n// PaseStringLiteral parses a string from the given byte slice.\nfunc ParseStringLiteral(data []byte) (string, error) {\n\tvar buf [unescapeStackBufSize]byte\n\n\tbf, err := Unescape(data, buf[:])\n\tif err != nil {\n\t\treturn \"\", errInvalidStringInput\n\t}\n\n\treturn string(bf), nil\n}\n\n// ParseBoolLiteral parses a boolean value from the given byte slice.\nfunc ParseBoolLiteral(data []byte) (bool, error) {\n\tswitch {\n\tcase bytes.Equal(data, trueLiteral):\n\t\treturn true, nil\n\tcase bytes.Equal(data, falseLiteral):\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, errMalformedBooleanValue\n\t}\n}\n"},{"name":"parser_test.gno","body":"package json\n\nimport \"testing\"\n\nfunc TestParseStringLiteral(t *testing.T) {\n\ttests := []struct {\n\t\tinput string\n\t\texpected string\n\t\tisError bool\n\t}{\n\t\t{`\"Hello, World!\"`, \"\\\"Hello, World!\\\"\", false},\n\t\t{`\\uFF11`, \"\\uFF11\", false},\n\t\t{`\\uFFFF`, \"\\uFFFF\", false},\n\t\t{`true`, \"true\", false},\n\t\t{`false`, \"false\", false},\n\t\t{`\\uDF00`, \"\", true},\n\t}\n\n\tfor i, tt := range tests {\n\t\ts, err := ParseStringLiteral([]byte(tt.input))\n\n\t\tif !tt.isError \u0026\u0026 err != nil {\n\t\t\tt.Errorf(\"%d. unexpected error: %s\", i, err)\n\t\t}\n\n\t\tif tt.isError \u0026\u0026 err == nil {\n\t\t\tt.Errorf(\"%d. expected error, but not error\", i)\n\t\t}\n\n\t\tif s != tt.expected {\n\t\t\tt.Errorf(\"%d. expected=%s, but actual=%s\", i, tt.expected, s)\n\t\t}\n\t}\n}\n\nfunc TestParseBoolLiteral(t *testing.T) {\n\ttests := []struct {\n\t\tinput string\n\t\texpected bool\n\t\tisError bool\n\t}{\n\t\t{`true`, true, false},\n\t\t{`false`, false, false},\n\t\t{`TRUE`, false, true},\n\t\t{`FALSE`, false, true},\n\t\t{`foo`, false, true},\n\t\t{`\"true\"`, false, true},\n\t\t{`\"false\"`, false, true},\n\t}\n\n\tfor i, tt := range tests {\n\t\tb, err := ParseBoolLiteral([]byte(tt.input))\n\n\t\tif !tt.isError \u0026\u0026 err != nil {\n\t\t\tt.Errorf(\"%d. unexpected error: %s\", i, err)\n\t\t}\n\n\t\tif tt.isError \u0026\u0026 err == nil {\n\t\t\tt.Errorf(\"%d. expected error, but not error\", i)\n\t\t}\n\n\t\tif b != tt.expected {\n\t\t\tt.Errorf(\"%d. expected=%t, but actual=%t\", i, tt.expected, b)\n\t\t}\n\t}\n}\n"},{"name":"path.gno","body":"package json\n\nimport (\n\t\"errors\"\n)\n\n// ParsePath takes a JSONPath string and returns a slice of strings representing the path segments.\nfunc ParsePath(path string) ([]string, error) {\n\tbuf := newBuffer([]byte(path))\n\tresult := make([]string, 0)\n\n\tfor {\n\t\tb, err := buf.current()\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\n\t\tswitch {\n\t\tcase b == dollarSign || b == atSign:\n\t\t\tresult = append(result, string(b))\n\t\t\tbuf.step()\n\n\t\tcase b == dot:\n\t\t\tbuf.step()\n\n\t\t\tif next, _ := buf.current(); next == dot {\n\t\t\t\tbuf.step()\n\t\t\t\tresult = append(result, \"..\")\n\n\t\t\t\textractNextSegment(buf, \u0026result)\n\t\t\t} else {\n\t\t\t\textractNextSegment(buf, \u0026result)\n\t\t\t}\n\n\t\tcase b == bracketOpen:\n\t\t\tstart := buf.index\n\t\t\tbuf.step()\n\n\t\t\tfor {\n\t\t\t\tif buf.index \u003e= buf.length || buf.data[buf.index] == bracketClose {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tbuf.step()\n\t\t\t}\n\n\t\t\tif buf.index \u003e= buf.length {\n\t\t\t\treturn nil, errors.New(\"unexpected end of path\")\n\t\t\t}\n\n\t\t\tsegment := string(buf.sliceFromIndices(start+1, buf.index))\n\t\t\tresult = append(result, segment)\n\n\t\t\tbuf.step()\n\n\t\tdefault:\n\t\t\tbuf.step()\n\t\t}\n\t}\n\n\treturn result, nil\n}\n\n// extractNextSegment extracts the segment from the current index\n// to the next significant character and adds it to the resulting slice.\nfunc extractNextSegment(buf *buffer, result *[]string) {\n\tstart := buf.index\n\tbuf.skipToNextSignificantToken()\n\n\tif buf.index \u003c= start {\n\t\treturn\n\t}\n\n\tsegment := string(buf.sliceFromIndices(start, buf.index))\n\tif segment != \"\" {\n\t\t*result = append(*result, segment)\n\t}\n}\n"},{"name":"path_test.gno","body":"package json\n\nimport \"testing\"\n\nfunc TestParseJSONPath(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tpath string\n\t\texpected []string\n\t}{\n\t\t{name: \"Empty string path\", path: \"\", expected: []string{}},\n\t\t{name: \"Root only path\", path: \"$\", expected: []string{\"$\"}},\n\t\t{name: \"Root with dot path\", path: \"$.\", expected: []string{\"$\"}},\n\t\t{name: \"All objects in path\", path: \"$..\", expected: []string{\"$\", \"..\"}},\n\t\t{name: \"Only children in path\", path: \"$.*\", expected: []string{\"$\", \"*\"}},\n\t\t{name: \"All objects' children in path\", path: \"$..*\", expected: []string{\"$\", \"..\", \"*\"}},\n\t\t{name: \"Simple dot notation path\", path: \"$.root.element\", expected: []string{\"$\", \"root\", \"element\"}},\n\t\t{name: \"Complex dot notation path with wildcard\", path: \"$.root.*.element\", expected: []string{\"$\", \"root\", \"*\", \"element\"}},\n\t\t{name: \"Path with array wildcard\", path: \"$.phoneNumbers[*].type\", expected: []string{\"$\", \"phoneNumbers\", \"*\", \"type\"}},\n\t\t{name: \"Path with filter expression\", path: \"$.store.book[?(@.price \u003c 10)].title\", expected: []string{\"$\", \"store\", \"book\", \"?(@.price \u003c 10)\", \"title\"}},\n\t\t{name: \"Path with formula\", path: \"$..phoneNumbers..('ty' + 'pe')\", expected: []string{\"$\", \"..\", \"phoneNumbers\", \"..\", \"('ty' + 'pe')\"}},\n\t\t{name: \"Simple bracket notation path\", path: \"$['root']['element']\", expected: []string{\"$\", \"'root'\", \"'element'\"}},\n\t\t{name: \"Complex bracket notation path with wildcard\", path: \"$['root'][*]['element']\", expected: []string{\"$\", \"'root'\", \"*\", \"'element'\"}},\n\t\t{name: \"Bracket notation path with integer index\", path: \"$['store']['book'][0]['title']\", expected: []string{\"$\", \"'store'\", \"'book'\", \"0\", \"'title'\"}},\n\t\t{name: \"Complex path with wildcard in bracket notation\", path: \"$['root'].*['element']\", expected: []string{\"$\", \"'root'\", \"*\", \"'element'\"}},\n\t\t{name: \"Mixed notation path with dot after bracket\", path: \"$.['root'].*.['element']\", expected: []string{\"$\", \"'root'\", \"*\", \"'element'\"}},\n\t\t{name: \"Mixed notation path with dot before bracket\", path: \"$['root'].*.['element']\", expected: []string{\"$\", \"'root'\", \"*\", \"'element'\"}},\n\t\t{name: \"Single character path with root\", path: \"$.a\", expected: []string{\"$\", \"a\"}},\n\t\t{name: \"Multiple characters path with root\", path: \"$.abc\", expected: []string{\"$\", \"abc\"}},\n\t\t{name: \"Multiple segments path with root\", path: \"$.a.b.c\", expected: []string{\"$\", \"a\", \"b\", \"c\"}},\n\t\t{name: \"Multiple segments path with wildcard and root\", path: \"$.a.*.c\", expected: []string{\"$\", \"a\", \"*\", \"c\"}},\n\t\t{name: \"Multiple segments path with filter and root\", path: \"$.a[?(@.b == 'c')].d\", expected: []string{\"$\", \"a\", \"?(@.b == 'c')\", \"d\"}},\n\t\t{name: \"Complex path with multiple filters\", path: \"$.a[?(@.b == 'c')].d[?(@.e == 'f')].g\", expected: []string{\"$\", \"a\", \"?(@.b == 'c')\", \"d\", \"?(@.e == 'f')\", \"g\"}},\n\t\t{name: \"Complex path with multiple filters and wildcards\", path: \"$.a[?(@.b == 'c')].*.d[?(@.e == 'f')].g\", expected: []string{\"$\", \"a\", \"?(@.b == 'c')\", \"*\", \"d\", \"?(@.e == 'f')\", \"g\"}},\n\t\t{name: \"Path with array index and root\", path: \"$.a[0].b\", expected: []string{\"$\", \"a\", \"0\", \"b\"}},\n\t\t{name: \"Path with multiple array indices and root\", path: \"$.a[0].b[1].c\", expected: []string{\"$\", \"a\", \"0\", \"b\", \"1\", \"c\"}},\n\t\t{name: \"Path with array index, wildcard and root\", path: \"$.a[0].*.c\", expected: []string{\"$\", \"a\", \"0\", \"*\", \"c\"}},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\treult, _ := ParsePath(tt.path)\n\t\t\tif !isEqualSlice(reult, tt.expected) {\n\t\t\t\tt.Errorf(\"ParsePath(%s) expected: %v, got: %v\", tt.path, tt.expected, reult)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc isEqualSlice(a, b []string) bool {\n\tif len(a) != len(b) {\n\t\treturn false\n\t}\n\n\tfor i, v := range a {\n\t\tif v != b[i] {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n"},{"name":"token.gno","body":"package json\n\nconst (\n\tbracketOpen = '['\n\tbracketClose = ']'\n\tparenOpen = '('\n\tparenClose = ')'\n\tcurlyOpen = '{'\n\tcurlyClose = '}'\n\tcomma = ','\n\tdot = '.'\n\tcolon = ':'\n\tbackTick = '`'\n\tsingleQuote = '\\''\n\tdoubleQuote = '\"'\n\temptyString = \"\"\n\twhiteSpace = ' '\n\tplus = '+'\n\tminus = '-'\n\taesterisk = '*'\n\tbang = '!'\n\tquestion = '?'\n\tnewLine = '\\n'\n\ttab = '\\t'\n\tcarriageReturn = '\\r'\n\tformFeed = '\\f'\n\tbackSpace = '\\b'\n\tslash = '/'\n\tbackSlash = '\\\\'\n\tunderScore = '_'\n\tdollarSign = '$'\n\tatSign = '@'\n\tandSign = '\u0026'\n\torSign = '|'\n)\n\nvar (\n\ttrueLiteral = []byte(\"true\")\n\tfalseLiteral = []byte(\"false\")\n\tnullLiteral = []byte(\"null\")\n)\n\ntype ValueType int\n\nconst (\n\tNotExist ValueType = iota\n\tString\n\tNumber\n\tFloat\n\tObject\n\tArray\n\tBoolean\n\tNull\n\tUnknown\n)\n\nfunc (v ValueType) String() string {\n\tswitch v {\n\tcase NotExist:\n\t\treturn \"not-exist\"\n\tcase String:\n\t\treturn \"string\"\n\tcase Number:\n\t\treturn \"number\"\n\tcase Object:\n\t\treturn \"object\"\n\tcase Array:\n\t\treturn \"array\"\n\tcase Boolean:\n\t\treturn \"boolean\"\n\tcase Null:\n\t\treturn \"null\"\n\tdefault:\n\t\treturn \"unknown\"\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":"int32","path":"gno.land/p/demo/math_eval/int32","files":[{"name":"int32.gno","body":"// eval/int32 is a evaluator for int32 expressions.\n// This code is heavily forked from https://github.com/dengsgo/math-engine\n// which is licensed under Apache 2.0:\n// https://raw.githubusercontent.com/dengsgo/math-engine/298e2b57b7e7350d0f67bd036916efd5709abe25/LICENSE\npackage int32\n\nimport (\n\t\"errors\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\nconst (\n\tIdentifier = iota\n\tNumber // numbers\n\tOperator // +, -, *, /, etc.\n\tVariable // x, y, z, etc. (one-letter only)\n)\n\ntype expression interface {\n\tString() string\n}\n\ntype expressionRaw struct {\n\texpression string\n\tType int\n\tFlag int\n\tOffset int\n}\n\ntype parser struct {\n\tInput string\n\tch byte\n\toffset int\n\terr error\n}\n\ntype expressionNumber struct {\n\tVal int\n\tStr string\n}\n\ntype expressionVariable struct {\n\tVal int\n\tStr string\n}\n\ntype expressionOperation struct {\n\tOp string\n\tLhs,\n\tRhs expression\n}\n\ntype ast struct {\n\trawexpressions []*expressionRaw\n\tsource string\n\tcurrentexpression *expressionRaw\n\tcurrentIndex int\n\tdepth int\n\terr error\n}\n\n// Parse takes an expression string, e.g. \"1+2\" and returns\n// a parsed expression. If there is an error it will return.\nfunc Parse(s string) (ar expression, err error) {\n\ttoks, err := lexer(s)\n\tif err != nil {\n\t\treturn\n\t}\n\tast, err := newAST(toks, s)\n\tif err != nil {\n\t\treturn\n\t}\n\tar, err = ast.parseExpression()\n\treturn\n}\n\n// Eval takes a parsed expression and a map of variables (or nil). The parsed\n// expression is evaluated using any variables and returns the\n// resulting int and/or error.\nfunc Eval(expr expression, variables map[string]int) (res int, err error) {\n\tif err != nil {\n\t\treturn\n\t}\n\tvar l, r int\n\tswitch expr.(type) {\n\tcase expressionVariable:\n\t\tast := expr.(expressionVariable)\n\t\tok := false\n\t\tif variables != nil {\n\t\t\tres, ok = variables[ast.Str]\n\t\t}\n\t\tif !ok {\n\t\t\terr = ufmt.Errorf(\"variable '%s' not found\", ast.Str)\n\t\t}\n\t\treturn\n\tcase expressionOperation:\n\t\tast := expr.(expressionOperation)\n\t\tl, err = Eval(ast.Lhs, variables)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tr, err = Eval(ast.Rhs, variables)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tswitch ast.Op {\n\t\tcase \"+\":\n\t\t\tres = l + r\n\t\tcase \"-\":\n\t\t\tres = l - r\n\t\tcase \"*\":\n\t\t\tres = l * r\n\t\tcase \"/\":\n\t\t\tif r == 0 {\n\t\t\t\terr = ufmt.Errorf(\"violation of arithmetic specification: a division by zero in Eval: [%d/%d]\", l, r)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tres = l / r\n\t\tcase \"%\":\n\t\t\tif r == 0 {\n\t\t\t\tres = 0\n\t\t\t} else {\n\t\t\t\tres = l % r\n\t\t\t}\n\t\tcase \"^\":\n\t\t\tres = l ^ r\n\t\tcase \"\u003e\u003e\":\n\t\t\tres = l \u003e\u003e r\n\t\tcase \"\u003c\u003c\":\n\t\t\tres = l \u003c\u003c r\n\t\tcase \"\u003e\":\n\t\t\tif l \u003e r {\n\t\t\t\tres = 1\n\t\t\t} else {\n\t\t\t\tres = 0\n\t\t\t}\n\t\tcase \"\u003c\":\n\t\t\tif l \u003c r {\n\t\t\t\tres = 1\n\t\t\t} else {\n\t\t\t\tres = 0\n\t\t\t}\n\t\tcase \"\u0026\":\n\t\t\tres = l \u0026 r\n\t\tcase \"|\":\n\t\t\tres = l | r\n\t\tdefault:\n\n\t\t}\n\tcase expressionNumber:\n\t\tres = expr.(expressionNumber).Val\n\t}\n\n\treturn\n}\n\nfunc expressionError(s string, pos int) string {\n\tr := strings.Repeat(\"-\", len(s)) + \"\\n\"\n\ts += \"\\n\"\n\tfor i := 0; i \u003c pos; i++ {\n\t\ts += \" \"\n\t}\n\ts += \"^\\n\"\n\treturn r + s + r\n}\n\nfunc (n expressionVariable) String() string {\n\treturn ufmt.Sprintf(\n\t\t\"expressionVariable: %s\",\n\t\tn.Str,\n\t)\n}\n\nfunc (n expressionNumber) String() string {\n\treturn ufmt.Sprintf(\n\t\t\"expressionNumber: %s\",\n\t\tn.Str,\n\t)\n}\n\nfunc (b expressionOperation) String() string {\n\treturn ufmt.Sprintf(\n\t\t\"expressionOperation: (%s %s %s)\",\n\t\tb.Op,\n\t\tb.Lhs.String(),\n\t\tb.Rhs.String(),\n\t)\n}\n\nfunc newAST(toks []*expressionRaw, s string) (*ast, error) {\n\ta := \u0026ast{\n\t\trawexpressions: toks,\n\t\tsource: s,\n\t}\n\tif a.rawexpressions == nil || len(a.rawexpressions) == 0 {\n\t\treturn a, errors.New(\"empty token\")\n\t} else {\n\t\ta.currentIndex = 0\n\t\ta.currentexpression = a.rawexpressions[0]\n\t}\n\treturn a, nil\n}\n\nfunc (a *ast) parseExpression() (expression, error) {\n\ta.depth++ // called depth\n\tlhs := a.parsePrimary()\n\tr := a.parseBinOpRHS(0, lhs)\n\ta.depth--\n\tif a.depth == 0 \u0026\u0026 a.currentIndex != len(a.rawexpressions) \u0026\u0026 a.err == nil {\n\t\treturn r, ufmt.Errorf(\"bad expression, reaching the end or missing the operator\\n%s\",\n\t\t\texpressionError(a.source, a.currentexpression.Offset))\n\t}\n\treturn r, nil\n}\n\nfunc (a *ast) getNextexpressionRaw() *expressionRaw {\n\ta.currentIndex++\n\tif a.currentIndex \u003c len(a.rawexpressions) {\n\t\ta.currentexpression = a.rawexpressions[a.currentIndex]\n\t\treturn a.currentexpression\n\t}\n\treturn nil\n}\n\nfunc (a *ast) getTokPrecedence() int {\n\tswitch a.currentexpression.expression {\n\tcase \"/\", \"%\", \"*\":\n\t\treturn 100\n\tcase \"\u003c\u003c\", \"\u003e\u003e\":\n\t\treturn 80\n\tcase \"+\", \"-\":\n\t\treturn 75\n\tcase \"\u003c\", \"\u003e\":\n\t\treturn 70\n\tcase \"\u0026\":\n\t\treturn 60\n\tcase \"^\":\n\t\treturn 50\n\tcase \"|\":\n\t\treturn 40\n\t}\n\treturn -1\n}\n\nfunc (a *ast) parseNumber() expressionNumber {\n\tf64, err := strconv.Atoi(a.currentexpression.expression)\n\tif err != nil {\n\t\ta.err = ufmt.Errorf(\"%v\\nwant '(' or '0-9' but get '%s'\\n%s\",\n\t\t\terr.Error(),\n\t\t\ta.currentexpression.expression,\n\t\t\texpressionError(a.source, a.currentexpression.Offset))\n\t\treturn expressionNumber{}\n\t}\n\tn := expressionNumber{\n\t\tVal: f64,\n\t\tStr: a.currentexpression.expression,\n\t}\n\ta.getNextexpressionRaw()\n\treturn n\n}\n\nfunc (a *ast) parseVariable() expressionVariable {\n\tn := expressionVariable{\n\t\tVal: 0,\n\t\tStr: a.currentexpression.expression,\n\t}\n\ta.getNextexpressionRaw()\n\treturn n\n}\n\nfunc (a *ast) parsePrimary() expression {\n\tswitch a.currentexpression.Type {\n\tcase Variable:\n\t\treturn a.parseVariable()\n\tcase Number:\n\t\treturn a.parseNumber()\n\tcase Operator:\n\t\tif a.currentexpression.expression == \"(\" {\n\t\t\tt := a.getNextexpressionRaw()\n\t\t\tif t == nil {\n\t\t\t\ta.err = ufmt.Errorf(\"want '(' or '0-9' but get EOF\\n%s\",\n\t\t\t\t\texpressionError(a.source, a.currentexpression.Offset))\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\te, _ := a.parseExpression()\n\t\t\tif e == nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tif a.currentexpression.expression != \")\" {\n\t\t\t\ta.err = ufmt.Errorf(\"want ')' but get %s\\n%s\",\n\t\t\t\t\ta.currentexpression.expression,\n\t\t\t\t\texpressionError(a.source, a.currentexpression.Offset))\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\ta.getNextexpressionRaw()\n\t\t\treturn e\n\t\t} else if a.currentexpression.expression == \"-\" {\n\t\t\tif a.getNextexpressionRaw() == nil {\n\t\t\t\ta.err = ufmt.Errorf(\"want '0-9' but get '-'\\n%s\",\n\t\t\t\t\texpressionError(a.source, a.currentexpression.Offset))\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tbin := expressionOperation{\n\t\t\t\tOp: \"-\",\n\t\t\t\tLhs: expressionNumber{},\n\t\t\t\tRhs: a.parsePrimary(),\n\t\t\t}\n\t\t\treturn bin\n\t\t} else {\n\t\t\treturn a.parseNumber()\n\t\t}\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc (a *ast) parseBinOpRHS(execPrec int, lhs expression) expression {\n\tfor {\n\t\ttokPrec := a.getTokPrecedence()\n\t\tif tokPrec \u003c execPrec {\n\t\t\treturn lhs\n\t\t}\n\t\tbinOp := a.currentexpression.expression\n\t\tif a.getNextexpressionRaw() == nil {\n\t\t\ta.err = ufmt.Errorf(\"want '(' or '0-9' but get EOF\\n%s\",\n\t\t\t\texpressionError(a.source, a.currentexpression.Offset))\n\t\t\treturn nil\n\t\t}\n\t\trhs := a.parsePrimary()\n\t\tif rhs == nil {\n\t\t\treturn nil\n\t\t}\n\t\tnextPrec := a.getTokPrecedence()\n\t\tif tokPrec \u003c nextPrec {\n\t\t\trhs = a.parseBinOpRHS(tokPrec+1, rhs)\n\t\t\tif rhs == nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tlhs = expressionOperation{\n\t\t\tOp: binOp,\n\t\t\tLhs: lhs,\n\t\t\tRhs: rhs,\n\t\t}\n\t}\n}\n\nfunc lexer(s string) ([]*expressionRaw, error) {\n\tp := \u0026parser{\n\t\tInput: s,\n\t\terr: nil,\n\t\tch: s[0],\n\t}\n\ttoks := p.parse()\n\tif p.err != nil {\n\t\treturn nil, p.err\n\t}\n\treturn toks, nil\n}\n\nfunc (p *parser) parse() []*expressionRaw {\n\ttoks := make([]*expressionRaw, 0)\n\tfor {\n\t\ttok := p.nextTok()\n\t\tif tok == nil {\n\t\t\tbreak\n\t\t}\n\t\ttoks = append(toks, tok)\n\t}\n\treturn toks\n}\n\nfunc (p *parser) nextTok() *expressionRaw {\n\tif p.offset \u003e= len(p.Input) || p.err != nil {\n\t\treturn nil\n\t}\n\tvar err error\n\tfor p.isWhitespace(p.ch) \u0026\u0026 err == nil {\n\t\terr = p.nextCh()\n\t}\n\tstart := p.offset\n\tvar tok *expressionRaw\n\tswitch p.ch {\n\tcase\n\t\t'(',\n\t\t')',\n\t\t'+',\n\t\t'-',\n\t\t'*',\n\t\t'/',\n\t\t'^',\n\t\t'\u0026',\n\t\t'|',\n\t\t'%':\n\t\ttok = \u0026expressionRaw{\n\t\t\texpression: string(p.ch),\n\t\t\tType: Operator,\n\t\t}\n\t\ttok.Offset = start\n\t\terr = p.nextCh()\n\tcase '\u003e', '\u003c':\n\t\ttokS := string(p.ch)\n\t\tbb, be := p.nextChPeek()\n\t\tif be == nil \u0026\u0026 string(bb) == tokS {\n\t\t\ttokS += string(p.ch)\n\t\t}\n\t\ttok = \u0026expressionRaw{\n\t\t\texpression: tokS,\n\t\t\tType: Operator,\n\t\t}\n\t\ttok.Offset = start\n\t\tif len(tokS) \u003e 1 {\n\t\t\tp.nextCh()\n\t\t}\n\t\terr = p.nextCh()\n\tcase\n\t\t'0',\n\t\t'1',\n\t\t'2',\n\t\t'3',\n\t\t'4',\n\t\t'5',\n\t\t'6',\n\t\t'7',\n\t\t'8',\n\t\t'9':\n\t\tfor p.isDigitNum(p.ch) \u0026\u0026 p.nextCh() == nil {\n\t\t\tif (p.ch == '-' || p.ch == '+') \u0026\u0026 p.Input[p.offset-1] != 'e' {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\ttok = \u0026expressionRaw{\n\t\t\texpression: strings.ReplaceAll(p.Input[start:p.offset], \"_\", \"\"),\n\t\t\tType: Number,\n\t\t}\n\t\ttok.Offset = start\n\tdefault:\n\t\tif p.isChar(p.ch) {\n\t\t\ttok = \u0026expressionRaw{\n\t\t\t\texpression: string(p.ch),\n\t\t\t\tType: Variable,\n\t\t\t}\n\t\t\ttok.Offset = start\n\t\t\terr = p.nextCh()\n\t\t} else if p.ch != ' ' {\n\t\t\tp.err = ufmt.Errorf(\"symbol error: unknown '%v', pos [%v:]\\n%s\",\n\t\t\t\tstring(p.ch),\n\t\t\t\tstart,\n\t\t\t\texpressionError(p.Input, start))\n\t\t}\n\t}\n\treturn tok\n}\n\nfunc (p *parser) nextChPeek() (byte, error) {\n\toffset := p.offset + 1\n\tif offset \u003c len(p.Input) {\n\t\treturn p.Input[offset], nil\n\t}\n\treturn byte(0), errors.New(\"no byte\")\n}\n\nfunc (p *parser) nextCh() error {\n\tp.offset++\n\tif p.offset \u003c len(p.Input) {\n\t\tp.ch = p.Input[p.offset]\n\t\treturn nil\n\t}\n\treturn errors.New(\"EOF\")\n}\n\nfunc (p *parser) isWhitespace(c byte) bool {\n\treturn c == ' ' ||\n\t\tc == '\\t' ||\n\t\tc == '\\n' ||\n\t\tc == '\\v' ||\n\t\tc == '\\f' ||\n\t\tc == '\\r'\n}\n\nfunc (p *parser) isDigitNum(c byte) bool {\n\treturn '0' \u003c= c \u0026\u0026 c \u003c= '9' || c == '.' || c == '_' || c == 'e' || c == '-' || c == '+'\n}\n\nfunc (p *parser) isChar(c byte) bool {\n\treturn 'a' \u003c= c \u0026\u0026 c \u003c= 'z' || 'A' \u003c= c \u0026\u0026 c \u003c= 'Z'\n}\n\nfunc (p *parser) isWordChar(c byte) bool {\n\treturn p.isChar(c) || '0' \u003c= c \u0026\u0026 c \u003c= '9'\n}\n"},{"name":"int32_test.gno","body":"package int32\n\nimport \"testing\"\n\nfunc TestOne(t *testing.T) {\n\tttt := []struct {\n\t\texp string\n\t\tres int\n\t}{\n\t\t{\"1\", 1},\n\t\t{\"--1\", 1},\n\t\t{\"1+2\", 3},\n\t\t{\"-1+2\", 1},\n\t\t{\"-(1+2)\", -3},\n\t\t{\"-(1+2)*5\", -15},\n\t\t{\"-(1+2)*5/3\", -5},\n\t\t{\"1+(-(1+2)*5/3)\", -4},\n\t\t{\"3^4\", 3 ^ 4},\n\t\t{\"8%2\", 8 % 2},\n\t\t{\"8%3\", 8 % 3},\n\t\t{\"8|3\", 8 | 3},\n\t\t{\"10%2\", 0},\n\t\t{\"(4 + 3)/2-1+11*15\", (4+3)/2 - 1 + 11*15},\n\t\t{\n\t\t\t\"(30099\u003e\u003e10^30099\u003e\u003e11)%5*((30099\u003e\u003e14\u00263^30099\u003e\u003e15\u00261)+1)*30099%99 + ((3 + (30099 \u003e\u003e 14 \u0026 3) - (30099 \u003e\u003e 16 \u0026 1)) / 3 * 30099 % 99 \u0026 64)\",\n\t\t\t(30099\u003e\u003e10^30099\u003e\u003e11)%5*((30099\u003e\u003e14\u00263^30099\u003e\u003e15\u00261)+1)*30099%99 + ((3 + (30099 \u003e\u003e 14 \u0026 3) - (30099 \u003e\u003e 16 \u0026 1)) / 3 * 30099 % 99 \u0026 64),\n\t\t},\n\t\t{\n\t\t\t\"(1023850\u003e\u003e10^1023850\u003e\u003e11)%5*((1023850\u003e\u003e14\u00263^1023850\u003e\u003e15\u00261)+1)*1023850%99 + ((3 + (1023850 \u003e\u003e 14 \u0026 3) - (1023850 \u003e\u003e 16 \u0026 1)) / 3 * 1023850 % 99 \u0026 64)\",\n\t\t\t(1023850\u003e\u003e10^1023850\u003e\u003e11)%5*((1023850\u003e\u003e14\u00263^1023850\u003e\u003e15\u00261)+1)*1023850%99 + ((3 + (1023850 \u003e\u003e 14 \u0026 3) - (1023850 \u003e\u003e 16 \u0026 1)) / 3 * 1023850 % 99 \u0026 64),\n\t\t},\n\t\t{\"((0000+1)*0000)\", 0},\n\t}\n\tfor _, tc := range ttt {\n\t\tt.Run(tc.exp, func(t *testing.T) {\n\t\t\texp, err := Parse(tc.exp)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"%s:\\n%s\", tc.exp, err.Error())\n\t\t\t} else {\n\t\t\t\tres, errEval := Eval(exp, nil)\n\t\t\t\tif errEval != nil {\n\t\t\t\t\tt.Errorf(\"eval error: %s\", errEval.Error())\n\t\t\t\t} else if res != tc.res {\n\t\t\t\t\tt.Errorf(\"%s:\\nexpected %d, got %d\", tc.exp, tc.res, res)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestVariables(t *testing.T) {\n\tfn := func(x, y int) int {\n\t\treturn 1 + ((x*3+1)*(x*2))\u003e\u003ey + 1\n\t}\n\texpr := \"1 + ((x*3+1)*(x*2))\u003e\u003ey + 1\"\n\texp, err := Parse(expr)\n\tif err != nil {\n\t\tt.Errorf(\"could not parse: %s\", err.Error())\n\t}\n\tvariables := make(map[string]int)\n\tfor i := 0; i \u003c 10; i++ {\n\t\tvariables[\"x\"] = i\n\t\tvariables[\"y\"] = 2\n\t\tres, errEval := Eval(exp, variables)\n\t\tif errEval != nil {\n\t\t\tt.Errorf(\"could not evaluate: %s\", err.Error())\n\t\t}\n\t\texpected := fn(variables[\"x\"], variables[\"y\"])\n\t\tif res != expected {\n\t\t\tt.Errorf(\"expected: %d, actual: %d\", expected, res)\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":"membstore","path":"gno.land/p/demo/membstore","files":[{"name":"members.gno","body":"package membstore\n\nimport (\n\t\"std\"\n)\n\n// MemberStore defines the member storage abstraction\ntype MemberStore interface {\n\t// Members returns all members in the store\n\tMembers(offset, count uint64) []Member\n\n\t// Size returns the current size of the store\n\tSize() int\n\n\t// IsMember returns a flag indicating if the given address\n\t// belongs to a member\n\tIsMember(address std.Address) bool\n\n\t// TotalPower returns the total voting power of the member store\n\tTotalPower() uint64\n\n\t// Member returns the requested member\n\tMember(address std.Address) (Member, error)\n\n\t// AddMember adds a member to the store\n\tAddMember(member Member) error\n\n\t// UpdateMember updates the member in the store.\n\t// If updating a member's voting power to 0,\n\t// the member will be removed\n\tUpdateMember(address std.Address, member Member) error\n}\n\n// Member holds the relevant member information\ntype Member struct {\n\tAddress std.Address // bech32 gno address of the member (unique)\n\tVotingPower uint64 // the voting power of the member\n}\n"},{"name":"membstore.gno","body":"package membstore\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar (\n\tErrAlreadyMember = errors.New(\"address is already a member\")\n\tErrMissingMember = errors.New(\"address is not a member\")\n\tErrInvalidAddressUpdate = errors.New(\"invalid address update\")\n\tErrNotGovDAO = errors.New(\"caller not correct govdao instance\")\n)\n\n// maxRequestMembers is the maximum number of\n// paginated members that can be requested\nconst maxRequestMembers = 50\n\ntype Option func(*MembStore)\n\n// WithInitialMembers initializes the member store\n// with an initial member list\nfunc WithInitialMembers(members []Member) Option {\n\treturn func(store *MembStore) {\n\t\tfor _, m := range members {\n\t\t\tmemberAddr := m.Address.String()\n\n\t\t\t// Check if the member already exists\n\t\t\tif store.members.Has(memberAddr) {\n\t\t\t\tpanic(ufmt.Errorf(\"%s, %s\", memberAddr, ErrAlreadyMember))\n\t\t\t}\n\n\t\t\tstore.members.Set(memberAddr, m)\n\t\t\tstore.totalVotingPower += m.VotingPower\n\t\t}\n\t}\n}\n\n// WithDAOPkgPath initializes the member store\n// with a dao package path guard\nfunc WithDAOPkgPath(daoPkgPath string) Option {\n\treturn func(store *MembStore) {\n\t\tstore.daoPkgPath = daoPkgPath\n\t}\n}\n\n// MembStore implements the dao.MembStore abstraction\ntype MembStore struct {\n\tdaoPkgPath string // active dao pkg path, if any\n\tmembers *avl.Tree // std.Address -\u003e Member\n\ttotalVotingPower uint64 // cached value for quick lookups\n}\n\n// NewMembStore creates a new member store\nfunc NewMembStore(opts ...Option) *MembStore {\n\tm := \u0026MembStore{\n\t\tmembers: avl.NewTree(), // empty set\n\t\tdaoPkgPath: \"\", // no dao guard\n\t\ttotalVotingPower: 0,\n\t}\n\n\t// Apply the options\n\tfor _, opt := range opts {\n\t\topt(m)\n\t}\n\n\treturn m\n}\n\n// AddMember adds member to the member store `m`.\n// It fails if the caller is not GovDAO or\n// if the member is already present\nfunc (m *MembStore) AddMember(member Member) error {\n\tif !m.isCallerDAORealm() {\n\t\treturn ErrNotGovDAO\n\t}\n\n\t// Check if the member exists\n\tif m.IsMember(member.Address) {\n\t\treturn ErrAlreadyMember\n\t}\n\n\t// Add the member\n\tm.members.Set(member.Address.String(), member)\n\n\t// Update the total voting power\n\tm.totalVotingPower += member.VotingPower\n\n\treturn nil\n}\n\n// UpdateMember updates the member with the given address.\n// Updating fails if the caller is not GovDAO.\nfunc (m *MembStore) UpdateMember(address std.Address, member Member) error {\n\tif !m.isCallerDAORealm() {\n\t\treturn ErrNotGovDAO\n\t}\n\n\t// Get the member\n\toldMember, err := m.Member(address)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Check if this is a removal request\n\tif member.VotingPower == 0 {\n\t\tm.members.Remove(address.String())\n\n\t\t// Update the total voting power\n\t\tm.totalVotingPower -= oldMember.VotingPower\n\n\t\treturn nil\n\t}\n\n\t// Check that the member wouldn't be\n\t// overwriting an existing one\n\tisAddressUpdate := address != member.Address\n\tif isAddressUpdate \u0026\u0026 m.IsMember(member.Address) {\n\t\treturn ErrInvalidAddressUpdate\n\t}\n\n\t// Remove the old member info\n\t// in case the address changed\n\tif address != member.Address {\n\t\tm.members.Remove(address.String())\n\t}\n\n\t// Save the new member info\n\tm.members.Set(member.Address.String(), member)\n\n\t// Update the total voting power\n\tdifference := member.VotingPower - oldMember.VotingPower\n\tm.totalVotingPower += difference\n\n\treturn nil\n}\n\n// IsMember returns a flag indicating if the given\n// address belongs to a member of the member store\nfunc (m *MembStore) IsMember(address std.Address) bool {\n\t_, exists := m.members.Get(address.String())\n\n\treturn exists\n}\n\n// Member returns the member associated with the given address\nfunc (m *MembStore) Member(address std.Address) (Member, error) {\n\tmember, exists := m.members.Get(address.String())\n\tif !exists {\n\t\treturn Member{}, ErrMissingMember\n\t}\n\n\treturn member.(Member), nil\n}\n\n// Members returns a paginated list of members from\n// the member store. If the store is empty, an empty slice\n// is returned instead\nfunc (m *MembStore) Members(offset, count uint64) []Member {\n\t// Calculate the left and right bounds\n\tif count \u003c 1 || offset \u003e= uint64(m.members.Size()) {\n\t\treturn []Member{}\n\t}\n\n\t// Limit the maximum number of returned members\n\tif count \u003e maxRequestMembers {\n\t\tcount = maxRequestMembers\n\t}\n\n\t// Gather the members\n\tmembers := make([]Member, 0)\n\tm.members.IterateByOffset(\n\t\tint(offset),\n\t\tint(count),\n\t\tfunc(_ string, val interface{}) bool {\n\t\t\tmember := val.(Member)\n\n\t\t\t// Save the member\n\t\t\tmembers = append(members, member)\n\n\t\t\treturn false\n\t\t})\n\n\treturn members\n}\n\n// Size returns the number of active members in the member store\nfunc (m *MembStore) Size() int {\n\treturn m.members.Size()\n}\n\n// TotalPower returns the total voting power\n// of the member store\nfunc (m *MembStore) TotalPower() uint64 {\n\treturn m.totalVotingPower\n}\n\n// isCallerDAORealm returns a flag indicating if the\n// current caller context is the active DAO Realm.\n// We need to include a dao guard, even if the\n// executor guarantees it, because\n// the API of the member store is public and callable\n// by anyone who has a reference to the member store instance.\nfunc (m *MembStore) isCallerDAORealm() bool {\n\treturn m.daoPkgPath == \"\" || std.CurrentRealm().PkgPath() == m.daoPkgPath\n}\n"},{"name":"membstore_test.gno","body":"package membstore\n\nimport (\n\t\"testing\"\n\n\t\"std\"\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\n// generateMembers generates dummy govdao members\nfunc generateMembers(t *testing.T, count int) []Member {\n\tt.Helper()\n\n\tmembers := make([]Member, 0, count)\n\n\tfor i := 0; i \u003c count; i++ {\n\t\tmembers = append(members, 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 TestMembStore_GetMember(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"member not found\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Create an empty store\n\t\tm := NewMembStore()\n\n\t\t_, err := m.Member(testutils.TestAddress(\"random\"))\n\t\tuassert.ErrorIs(t, err, ErrMissingMember)\n\t})\n\n\tt.Run(\"valid member fetched\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Create a non-empty store\n\t\tmembers := generateMembers(t, 1)\n\n\t\tm := NewMembStore(WithInitialMembers(members))\n\n\t\t_, err := m.Member(members[0].Address)\n\t\tuassert.NoError(t, err)\n\t})\n}\n\nfunc TestMembStore_GetMembers(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"no members\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Create an empty store\n\t\tm := NewMembStore()\n\n\t\tmembers := m.Members(0, 10)\n\t\tuassert.Equal(t, 0, len(members))\n\t})\n\n\tt.Run(\"proper pagination\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tnumMembers = maxRequestMembers * 2\n\t\t\thalfRange = numMembers / 2\n\n\t\t\tmembers = generateMembers(t, numMembers)\n\t\t\tm = NewMembStore(WithInitialMembers(members))\n\n\t\t\tverifyMembersPresent = func(members, fetchedMembers []Member) {\n\t\t\t\tfor _, fetchedMember := range fetchedMembers {\n\t\t\t\t\tfor _, member := range members {\n\t\t\t\t\t\tif member.Address != fetchedMember.Address {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tuassert.Equal(t, member.VotingPower, fetchedMember.VotingPower)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t)\n\n\t\turequire.Equal(t, numMembers, m.Size())\n\n\t\tfetchedMembers := m.Members(0, uint64(halfRange))\n\t\turequire.Equal(t, halfRange, len(fetchedMembers))\n\n\t\t// Verify the members\n\t\tverifyMembersPresent(members, fetchedMembers)\n\n\t\t// Fetch the other half\n\t\tfetchedMembers = m.Members(uint64(halfRange), uint64(halfRange))\n\t\turequire.Equal(t, halfRange, len(fetchedMembers))\n\n\t\t// Verify the members\n\t\tverifyMembersPresent(members, fetchedMembers)\n\t})\n}\n\nfunc TestMembStore_IsMember(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"non-existing member\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Create an empty store\n\t\tm := NewMembStore()\n\n\t\tuassert.False(t, m.IsMember(testutils.TestAddress(\"random\")))\n\t})\n\n\tt.Run(\"existing member\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Create a non-empty store\n\t\tmembers := generateMembers(t, 50)\n\n\t\tm := NewMembStore(WithInitialMembers(members))\n\n\t\tfor _, member := range members {\n\t\t\tuassert.True(t, m.IsMember(member.Address))\n\t\t}\n\t})\n}\n\nfunc TestMembStore_AddMember(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"caller not govdao\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Create an empty store\n\t\tm := NewMembStore(WithDAOPkgPath(\"gno.land/r/gov/dao\"))\n\n\t\t// Attempt to add a member\n\t\tmember := generateMembers(t, 1)[0]\n\t\tuassert.ErrorIs(t, m.AddMember(member), ErrNotGovDAO)\n\t})\n\n\tt.Run(\"member already exists\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\t// Execute as the /r/gov/dao caller\n\t\t\tdaoPkgPath = \"gno.land/r/gov/dao\"\n\t\t\tr = std.NewCodeRealm(daoPkgPath)\n\t\t)\n\n\t\tstd.TestSetRealm(r)\n\n\t\t// Create a non-empty store\n\t\tmembers := generateMembers(t, 1)\n\t\tm := NewMembStore(WithDAOPkgPath(daoPkgPath), WithInitialMembers(members))\n\n\t\t// Attempt to add a member\n\t\tuassert.ErrorIs(t, m.AddMember(members[0]), ErrAlreadyMember)\n\t})\n\n\tt.Run(\"new member added\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\t// Execute as the /r/gov/dao caller\n\t\t\tdaoPkgPath = \"gno.land/r/gov/dao\"\n\t\t\tr = std.NewCodeRealm(daoPkgPath)\n\t\t)\n\n\t\tstd.TestSetRealm(r)\n\n\t\t// Create an empty store\n\t\tmembers := generateMembers(t, 1)\n\t\tm := NewMembStore(WithDAOPkgPath(daoPkgPath))\n\n\t\t// Attempt to add a member\n\t\turequire.NoError(t, m.AddMember(members[0]))\n\n\t\t// Make sure the member is added\n\t\tuassert.True(t, m.IsMember(members[0].Address))\n\t})\n}\n\nfunc TestMembStore_Size(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"empty govdao\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Create an empty store\n\t\tm := NewMembStore()\n\n\t\tuassert.Equal(t, 0, m.Size())\n\t})\n\n\tt.Run(\"non-empty govdao\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Create a non-empty store\n\t\tmembers := generateMembers(t, 50)\n\t\tm := NewMembStore(WithInitialMembers(members))\n\n\t\tuassert.Equal(t, len(members), m.Size())\n\t})\n}\n\nfunc TestMembStore_UpdateMember(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"caller not govdao\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Create an empty store\n\t\tm := NewMembStore(WithDAOPkgPath(\"gno.land/r/gov/dao\"))\n\n\t\t// Attempt to update a member\n\t\tmember := generateMembers(t, 1)[0]\n\t\tuassert.ErrorIs(t, m.UpdateMember(member.Address, member), ErrNotGovDAO)\n\t})\n\n\tt.Run(\"non-existing member\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\t// Execute as the /r/gov/dao caller\n\t\t\tdaoPkgPath = \"gno.land/r/gov/dao\"\n\t\t\tr = std.NewCodeRealm(daoPkgPath)\n\t\t)\n\n\t\tstd.TestSetRealm(r)\n\n\t\t// Create an empty store\n\t\tmembers := generateMembers(t, 1)\n\t\tm := NewMembStore(WithDAOPkgPath(daoPkgPath))\n\n\t\t// Attempt to update a member\n\t\tuassert.ErrorIs(t, m.UpdateMember(members[0].Address, members[0]), ErrMissingMember)\n\t})\n\n\tt.Run(\"overwrite member attempt\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\t// Execute as the /r/gov/dao caller\n\t\t\tdaoPkgPath = \"gno.land/r/gov/dao\"\n\t\t\tr = std.NewCodeRealm(daoPkgPath)\n\t\t)\n\n\t\tstd.TestSetRealm(r)\n\n\t\t// Create a non-empty store\n\t\tmembers := generateMembers(t, 2)\n\t\tm := NewMembStore(WithDAOPkgPath(daoPkgPath), WithInitialMembers(members))\n\n\t\t// Attempt to update a member\n\t\tuassert.ErrorIs(t, m.UpdateMember(members[0].Address, members[1]), ErrInvalidAddressUpdate)\n\t})\n\n\tt.Run(\"successful update\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\t// Execute as the /r/gov/dao caller\n\t\t\tdaoPkgPath = \"gno.land/r/gov/dao\"\n\t\t\tr = std.NewCodeRealm(daoPkgPath)\n\t\t)\n\n\t\tstd.TestSetRealm(r)\n\n\t\t// Create a non-empty store\n\t\tmembers := generateMembers(t, 1)\n\t\tm := NewMembStore(WithDAOPkgPath(daoPkgPath), WithInitialMembers(members))\n\n\t\toldVotingPower := m.totalVotingPower\n\t\turequire.Equal(t, members[0].VotingPower, oldVotingPower)\n\n\t\tvotingPower := uint64(300)\n\t\tmembers[0].VotingPower = votingPower\n\n\t\t// Attempt to update a member\n\t\tuassert.NoError(t, m.UpdateMember(members[0].Address, members[0]))\n\t\tuassert.Equal(t, votingPower, m.Members(0, 10)[0].VotingPower)\n\t\turequire.Equal(t, votingPower, m.totalVotingPower)\n\t})\n\n\tt.Run(\"member removed\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\t// Execute as the /r/gov/dao caller\n\t\t\tdaoPkgPath = \"gno.land/r/gov/dao\"\n\t\t\tr = std.NewCodeRealm(daoPkgPath)\n\t\t)\n\n\t\tstd.TestSetRealm(r)\n\n\t\t// Create a non-empty store\n\t\tmembers := generateMembers(t, 1)\n\t\tm := NewMembStore(WithDAOPkgPath(daoPkgPath), WithInitialMembers(members))\n\n\t\tvotingPower := uint64(0)\n\t\tmembers[0].VotingPower = votingPower\n\n\t\t// Attempt to update a member\n\t\tuassert.NoError(t, m.UpdateMember(members[0].Address, members[0]))\n\n\t\t// Make sure the member was removed\n\t\tuassert.False(t, m.IsMember(members[0].Address))\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":"ownable","path":"gno.land/p/demo/ownable","files":[{"name":"errors.gno","body":"package ownable\n\nimport \"errors\"\n\nvar (\n\tErrUnauthorized = errors.New(\"ownable: caller is not owner\")\n\tErrInvalidAddress = errors.New(\"ownable: new owner address is invalid\")\n)\n"},{"name":"ownable.gno","body":"package ownable\n\nimport \"std\"\n\nconst OwnershipTransferEvent = \"OwnershipTransfer\"\n\n// Ownable is meant to be used as a top-level object to make your contract ownable OR\n// being embedded in a Gno object to manage per-object ownership.\ntype Ownable struct {\n\towner std.Address\n}\n\nfunc New() *Ownable {\n\treturn \u0026Ownable{\n\t\towner: std.PrevRealm().Addr(),\n\t}\n}\n\nfunc NewWithAddress(addr std.Address) *Ownable {\n\treturn \u0026Ownable{\n\t\towner: addr,\n\t}\n}\n\n// TransferOwnership transfers ownership of the Ownable struct to a new address\nfunc (o *Ownable) TransferOwnership(newOwner std.Address) error {\n\terr := o.CallerIsOwner()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !newOwner.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tprevOwner := o.owner\n\to.owner = newOwner\n\tstd.Emit(\n\t\tOwnershipTransferEvent,\n\t\t\"from\", string(prevOwner),\n\t\t\"to\", string(newOwner),\n\t)\n\n\treturn nil\n}\n\n// DropOwnership removes the owner, effectively disabling any owner-related actions\n// Top-level usage: disables all only-owner actions/functions,\n// Embedded usage: behaves like a burn functionality, removing the owner from the struct\nfunc (o *Ownable) DropOwnership() error {\n\terr := o.CallerIsOwner()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tprevOwner := o.owner\n\to.owner = \"\"\n\n\tstd.Emit(\n\t\tOwnershipTransferEvent,\n\t\t\"from\", string(prevOwner),\n\t\t\"to\", \"\",\n\t)\n\n\treturn nil\n}\n\n// Owner returns the owner address from Ownable\nfunc (o Ownable) Owner() std.Address {\n\treturn o.owner\n}\n\n// CallerIsOwner checks if the caller of the function is the Realm's owner\nfunc (o Ownable) CallerIsOwner() error {\n\tif std.PrevRealm().Addr() == o.owner {\n\t\treturn nil\n\t}\n\n\treturn ErrUnauthorized\n}\n\n// AssertCallerIsOwner panics if the caller is not the owner\nfunc (o Ownable) AssertCallerIsOwner() {\n\tif std.PrevRealm().Addr() != o.owner {\n\t\tpanic(ErrUnauthorized)\n\t}\n}\n"},{"name":"ownable_test.gno","body":"package ownable\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\talice = testutils.TestAddress(\"alice\")\n\tbob = testutils.TestAddress(\"bob\")\n)\n\nfunc TestNew(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOrigCaller(alice) // TODO(bug): should not be needed\n\n\to := New()\n\tgot := o.Owner()\n\tif alice != got {\n\t\tt.Fatalf(\"Expected %s, got: %s\", alice, got)\n\t}\n}\n\nfunc TestNewWithAddress(t *testing.T) {\n\to := NewWithAddress(alice)\n\n\tgot := o.Owner()\n\tif alice != got {\n\t\tt.Fatalf(\"Expected %s, got: %s\", alice, got)\n\t}\n}\n\nfunc TestTransferOwnership(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\n\to := New()\n\n\terr := o.TransferOwnership(bob)\n\tif err != nil {\n\t\tt.Fatalf(\"TransferOwnership failed, %v\", err)\n\t}\n\n\tgot := o.Owner()\n\tif bob != got {\n\t\tt.Fatalf(\"Expected: %s, got: %s\", bob, got)\n\t}\n}\n\nfunc TestCallerIsOwner(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\n\to := New()\n\tunauthorizedCaller := bob\n\n\tstd.TestSetRealm(std.NewUserRealm(unauthorizedCaller))\n\tstd.TestSetOrigCaller(unauthorizedCaller) // TODO(bug): should not be needed\n\n\terr := o.CallerIsOwner()\n\tuassert.Error(t, err) // XXX: IsError(..., unauthorizedCaller)\n}\n\nfunc TestDropOwnership(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\n\to := New()\n\n\terr := o.DropOwnership()\n\tuassert.NoError(t, err, \"DropOwnership failed\")\n\n\towner := o.Owner()\n\tuassert.Empty(t, owner, \"owner should be empty\")\n}\n\n// Errors\n\nfunc TestErrUnauthorized(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOrigCaller(alice) // TODO(bug): should not be needed\n\n\to := New()\n\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\tstd.TestSetOrigCaller(bob) // TODO(bug): should not be needed\n\n\terr := o.TransferOwnership(alice)\n\tif err != ErrUnauthorized {\n\t\tt.Fatalf(\"Should've been ErrUnauthorized, was %v\", err)\n\t}\n\n\terr = o.DropOwnership()\n\tuassert.ErrorContains(t, err, ErrUnauthorized.Error())\n}\n\nfunc TestErrInvalidAddress(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\n\to := New()\n\n\terr := o.TransferOwnership(\"\")\n\tuassert.ErrorContains(t, err, ErrInvalidAddress.Error())\n\n\terr = o.TransferOwnership(\"10000000001000000000100000000010000000001000000000\")\n\tuassert.ErrorContains(t, err, ErrInvalidAddress.Error())\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":"ownable","path":"gno.land/p/demo/ownable","files":[{"name":"errors.gno","body":"package ownable\n\nimport \"errors\"\n\nvar (\n\tErrUnauthorized = errors.New(\"ownable: caller is not owner\")\n\tErrInvalidAddress = errors.New(\"ownable: new owner address is invalid\")\n)\n"},{"name":"ownable.gno","body":"package ownable\n\nimport \"std\"\n\nconst OwnershipTransferEvent = \"OwnershipTransfer\"\n\n// Ownable is meant to be used as a top-level object to make your contract ownable OR\n// being embedded in a Gno object to manage per-object ownership.\ntype Ownable struct {\n\towner std.Address\n}\n\nfunc New() *Ownable {\n\treturn \u0026Ownable{\n\t\towner: std.PrevRealm().Addr(),\n\t}\n}\n\nfunc NewWithAddress(addr std.Address) *Ownable {\n\treturn \u0026Ownable{\n\t\towner: addr,\n\t}\n}\n\n// TransferOwnership transfers ownership of the Ownable struct to a new address\nfunc (o *Ownable) TransferOwnership(newOwner std.Address) error {\n\terr := o.CallerIsOwner()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !newOwner.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tprevOwner := o.owner\n\to.owner = newOwner\n\tstd.Emit(\n\t\tOwnershipTransferEvent,\n\t\t\"from\", string(prevOwner),\n\t\t\"to\", string(newOwner),\n\t)\n\n\treturn nil\n}\n\n// DropOwnership removes the owner, effectively disabling any owner-related actions\n// Top-level usage: disables all only-owner actions/functions,\n// Embedded usage: behaves like a burn functionality, removing the owner from the struct\nfunc (o *Ownable) DropOwnership() error {\n\terr := o.CallerIsOwner()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tprevOwner := o.owner\n\to.owner = \"\"\n\n\tstd.Emit(\n\t\tOwnershipTransferEvent,\n\t\t\"from\", string(prevOwner),\n\t\t\"to\", \"\",\n\t)\n\n\treturn nil\n}\n\n// Owner returns the owner address from Ownable\nfunc (o Ownable) Owner() std.Address {\n\treturn o.owner\n}\n\n// CallerIsOwner checks if the caller of the function is the Realm's owner\nfunc (o Ownable) CallerIsOwner() error {\n\tif std.PrevRealm().Addr() == o.owner {\n\t\treturn nil\n\t}\n\n\treturn ErrUnauthorized\n}\n\n// AssertCallerIsOwner panics if the caller is not the owner\nfunc (o Ownable) AssertCallerIsOwner() {\n\tif std.PrevRealm().Addr() != o.owner {\n\t\tpanic(ErrUnauthorized)\n\t}\n}\n"},{"name":"ownable_test.gno","body":"package ownable\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\talice = testutils.TestAddress(\"alice\")\n\tbob = testutils.TestAddress(\"bob\")\n)\n\nfunc TestNew(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOriginCaller(alice) // TODO(bug): should not be needed\n\n\to := New()\n\tgot := o.Owner()\n\tif alice != got {\n\t\tt.Fatalf(\"Expected %s, got: %s\", alice, got)\n\t}\n}\n\nfunc TestNewWithAddress(t *testing.T) {\n\to := NewWithAddress(alice)\n\n\tgot := o.Owner()\n\tif alice != got {\n\t\tt.Fatalf(\"Expected %s, got: %s\", alice, got)\n\t}\n}\n\nfunc TestTransferOwnership(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\n\to := New()\n\n\terr := o.TransferOwnership(bob)\n\tif err != nil {\n\t\tt.Fatalf(\"TransferOwnership failed, %v\", err)\n\t}\n\n\tgot := o.Owner()\n\tif bob != got {\n\t\tt.Fatalf(\"Expected: %s, got: %s\", bob, got)\n\t}\n}\n\nfunc TestCallerIsOwner(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\n\to := New()\n\tunauthorizedCaller := bob\n\n\tstd.TestSetRealm(std.NewUserRealm(unauthorizedCaller))\n\tstd.TestSetOriginCaller(unauthorizedCaller) // TODO(bug): should not be needed\n\n\terr := o.CallerIsOwner()\n\tuassert.Error(t, err) // XXX: IsError(..., unauthorizedCaller)\n}\n\nfunc TestDropOwnership(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\n\to := New()\n\n\terr := o.DropOwnership()\n\tuassert.NoError(t, err, \"DropOwnership failed\")\n\n\towner := o.Owner()\n\tuassert.Empty(t, owner, \"owner should be empty\")\n}\n\n// Errors\n\nfunc TestErrUnauthorized(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOriginCaller(alice) // TODO(bug): should not be needed\n\n\to := New()\n\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\tstd.TestSetOriginCaller(bob) // TODO(bug): should not be needed\n\n\terr := o.TransferOwnership(alice)\n\tif err != ErrUnauthorized {\n\t\tt.Fatalf(\"Should've been ErrUnauthorized, was %v\", err)\n\t}\n\n\terr = o.DropOwnership()\n\tuassert.ErrorContains(t, err, ErrUnauthorized.Error())\n}\n\nfunc TestErrInvalidAddress(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\n\to := New()\n\n\terr := o.TransferOwnership(\"\")\n\tuassert.ErrorContains(t, err, ErrInvalidAddress.Error())\n\n\terr = o.TransferOwnership(\"10000000001000000000100000000010000000001000000000\")\n\tuassert.ErrorContains(t, err, ErrInvalidAddress.Error())\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":"seqid","path":"gno.land/p/demo/seqid","files":[{"name":"README.md","body":"# seqid\n\n```\npackage seqid // import \"gno.land/p/demo/seqid\"\n\nPackage seqid provides a simple way to have sequential IDs which will be ordered\ncorrectly when inserted in an AVL tree.\n\nSample usage:\n\n var id seqid.ID\n var users avl.Tree\n\n func NewUser() {\n \tusers.Set(id.Next().Binary(), \u0026User{ ... })\n }\n\nTYPES\n\ntype ID uint64\n An ID is a simple sequential ID generator.\n\nfunc FromBinary(b string) (ID, bool)\n FromBinary creates a new ID from the given string.\n\nfunc (i ID) Binary() string\n Binary returns a big-endian binary representation of the ID, suitable to be\n used as an AVL key.\n\nfunc (i *ID) Next() ID\n Next advances the ID i. It will panic if increasing ID would overflow.\n\nfunc (i *ID) TryNext() (ID, bool)\n TryNext increases i by 1 and returns its value. It returns true if\n successful, or false if the increment would result in an overflow.\n```\n"},{"name":"seqid.gno","body":"// Package seqid provides a simple way to have sequential IDs which will be\n// ordered correctly when inserted in an AVL tree.\n//\n// Sample usage:\n//\n//\tvar id seqid.ID\n//\tvar users avl.Tree\n//\n//\tfunc NewUser() {\n//\t\tusers.Set(id.Next().String(), \u0026User{ ... })\n//\t}\npackage seqid\n\nimport (\n\t\"encoding/binary\"\n\n\t\"gno.land/p/demo/cford32\"\n)\n\n// An ID is a simple sequential ID generator.\ntype ID uint64\n\n// Next advances the ID i.\n// It will panic if increasing ID would overflow.\nfunc (i *ID) Next() ID {\n\tnext, ok := i.TryNext()\n\tif !ok {\n\t\tpanic(\"seqid: next ID overflows uint64\")\n\t}\n\treturn next\n}\n\nconst maxID ID = 1\u003c\u003c64 - 1\n\n// TryNext increases i by 1 and returns its value.\n// It returns true if successful, or false if the increment would result in\n// an overflow.\nfunc (i *ID) TryNext() (ID, bool) {\n\tif *i == maxID {\n\t\t// Addition will overflow.\n\t\treturn 0, false\n\t}\n\t*i++\n\treturn *i, true\n}\n\n// Binary returns a big-endian binary representation of the ID,\n// suitable to be used as an AVL key.\nfunc (i ID) Binary() string {\n\tbuf := make([]byte, 8)\n\tbinary.BigEndian.PutUint64(buf, uint64(i))\n\treturn string(buf)\n}\n\n// String encodes i using cford32's compact encoding. For more information,\n// see the documentation for package [gno.land/p/demo/cford32].\n//\n// The result of String will be a 7-byte string for IDs [0,2^34), and a\n// 13-byte string for all values following that. All generated string IDs\n// follow the same lexicographic order as their number values; that is, for any\n// two IDs (x, y) such that x \u003c y, x.String() \u003c y.String().\n// As such, this string representation is suitable to be used as an AVL key.\nfunc (i ID) String() string {\n\treturn string(cford32.PutCompact(uint64(i)))\n}\n\n// FromBinary creates a new ID from the given string, expected to be a binary\n// big-endian encoding of an ID (such as that of [ID.Binary]).\n// The second return value is true if the conversion was successful.\nfunc FromBinary(b string) (ID, bool) {\n\tif len(b) != 8 {\n\t\treturn 0, false\n\t}\n\treturn ID(binary.BigEndian.Uint64([]byte(b))), true\n}\n\n// FromString creates a new ID from the given string, expected to be a string\n// representation using cford32, such as that returned by [ID.String].\n//\n// The encoding scheme used by cford32 allows the same ID to have many\n// different representations (though the one returned by [ID.String] is only\n// one, deterministic and safe to be used in AVL). The encoding scheme is\n// \"human-centric\" and is thus case insensitive, and maps some ambiguous\n// characters to be the same, ie. L = I = 1, O = 0. For this reason, when\n// parsing user input to retrieve a key (encoded as a string), always sanitize\n// it first using FromString, then run String(), instead of using the user's\n// input directly.\nfunc FromString(b string) (ID, error) {\n\tn, err := cford32.Uint64([]byte(b))\n\treturn ID(n), err\n}\n"},{"name":"seqid_test.gno","body":"package seqid\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestID(t *testing.T) {\n\tvar i ID\n\n\tfor j := 0; j \u003c 100; j++ {\n\t\ti.Next()\n\t}\n\tif i != 100 {\n\t\tt.Fatalf(\"invalid: wanted %d got %d\", 100, i)\n\t}\n}\n\nfunc TestID_Overflow(t *testing.T) {\n\ti := ID(maxID)\n\n\tdefer func() {\n\t\terr := recover()\n\t\tif !strings.Contains(fmt.Sprint(err), \"next ID overflows\") {\n\t\t\tt.Errorf(\"did not overflow\")\n\t\t}\n\t}()\n\n\ti.Next()\n}\n\nfunc TestID_Binary(t *testing.T) {\n\tvar i ID\n\tprev := i.Binary()\n\n\tfor j := 0; j \u003c 1000; j++ {\n\t\tcur := i.Next().Binary()\n\t\tif cur \u003c= prev {\n\t\t\tt.Fatalf(\"cur %x \u003e prev %x\", cur, prev)\n\t\t}\n\t\tprev = cur\n\t}\n}\n\nfunc TestID_String(t *testing.T) {\n\tvar i ID\n\tprev := i.String()\n\n\tfor j := 0; j \u003c 1000; j++ {\n\t\tcur := i.Next().String()\n\t\tif cur \u003c= prev {\n\t\t\tt.Fatalf(\"cur %s \u003e prev %s\", cur, prev)\n\t\t}\n\t\tprev = cur\n\t}\n\n\t// Test for when cford32 switches over to the long encoding.\n\ti = 1\u003c\u003c34 - 512\n\tfor j := 0; j \u003c 1024; j++ {\n\t\tcur := i.Next().String()\n\t\t// println(cur)\n\t\tif cur \u003c= prev {\n\t\t\tt.Fatalf(\"cur %s \u003e prev %s\", cur, prev)\n\t\t}\n\t\tprev = cur\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":"memeland","path":"gno.land/p/demo/memeland","files":[{"name":"memeland.gno","body":"package memeland\n\nimport (\n\t\"sort\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/seqid\"\n)\n\nconst (\n\tDATE_CREATED = \"DATE_CREATED\"\n\tUPVOTES = \"UPVOTES\"\n)\n\ntype Post struct {\n\tID string\n\tData string\n\tAuthor std.Address\n\tTimestamp time.Time\n\tUpvoteTracker *avl.Tree // address \u003e struct{}{}\n}\n\ntype Memeland struct {\n\t*ownable.Ownable\n\tPosts []*Post\n\tMemeCounter seqid.ID\n}\n\nfunc NewMemeland() *Memeland {\n\treturn \u0026Memeland{\n\t\tOwnable: ownable.New(),\n\t\tPosts: make([]*Post, 0),\n\t}\n}\n\n// PostMeme - Adds a new post\nfunc (m *Memeland) PostMeme(data string, timestamp int64) string {\n\tif data == \"\" || timestamp \u003c= 0 {\n\t\tpanic(\"timestamp or data cannot be empty\")\n\t}\n\n\t// Generate ID\n\tid := m.MemeCounter.Next().String()\n\n\tnewPost := \u0026Post{\n\t\tID: id,\n\t\tData: data,\n\t\tAuthor: std.PrevRealm().Addr(),\n\t\tTimestamp: time.Unix(timestamp, 0),\n\t\tUpvoteTracker: avl.NewTree(),\n\t}\n\n\tm.Posts = append(m.Posts, newPost)\n\treturn id\n}\n\nfunc (m *Memeland) Upvote(id string) string {\n\tpost := m.getPost(id)\n\tif post == nil {\n\t\tpanic(\"post with specified ID does not exist\")\n\t}\n\n\tcaller := std.PrevRealm().Addr().String()\n\n\tif _, exists := post.UpvoteTracker.Get(caller); exists {\n\t\tpanic(\"user has already upvoted this post\")\n\t}\n\n\tpost.UpvoteTracker.Set(caller, struct{}{})\n\n\treturn \"upvote successful\"\n}\n\n// GetPostsInRange returns a JSON string of posts within the given timestamp range, supporting pagination\nfunc (m *Memeland) GetPostsInRange(startTimestamp, endTimestamp int64, page, pageSize int, sortBy string) string {\n\tif len(m.Posts) == 0 {\n\t\treturn \"[]\"\n\t}\n\n\tif page \u003c 1 {\n\t\tpanic(\"page number cannot be less than 1\")\n\t}\n\n\t// No empty pages\n\tif pageSize \u003c 1 {\n\t\tpanic(\"page size cannot be less than 1\")\n\t}\n\n\t// No pages larger than 10\n\tif pageSize \u003e 10 {\n\t\tpanic(\"page size cannot be larger than 10\")\n\t}\n\n\t// Need to pass in a sort parameter\n\tif sortBy == \"\" {\n\t\tpanic(\"sort order cannot be empty\")\n\t}\n\n\tvar filteredPosts []*Post\n\n\tstart := time.Unix(startTimestamp, 0)\n\tend := time.Unix(endTimestamp, 0)\n\n\t// Filtering posts\n\tfor _, p := range m.Posts {\n\t\tif !p.Timestamp.Before(start) \u0026\u0026 !p.Timestamp.After(end) {\n\t\t\tfilteredPosts = append(filteredPosts, p)\n\t\t}\n\t}\n\n\tswitch sortBy {\n\t// Sort by upvote descending\n\tcase UPVOTES:\n\t\tdateSorter := PostSorter{\n\t\t\tPosts: filteredPosts,\n\t\t\tLessF: func(i, j int) bool {\n\t\t\t\treturn filteredPosts[i].UpvoteTracker.Size() \u003e filteredPosts[j].UpvoteTracker.Size()\n\t\t\t},\n\t\t}\n\t\tsort.Sort(dateSorter)\n\tcase DATE_CREATED:\n\t\t// Sort by timestamp, beginning with newest\n\t\tdateSorter := PostSorter{\n\t\t\tPosts: filteredPosts,\n\t\t\tLessF: func(i, j int) bool {\n\t\t\t\treturn filteredPosts[i].Timestamp.After(filteredPosts[j].Timestamp)\n\t\t\t},\n\t\t}\n\t\tsort.Sort(dateSorter)\n\tdefault:\n\t\tpanic(\"sort order can only be \\\"UPVOTES\\\" or \\\"DATE_CREATED\\\"\")\n\t}\n\n\t// Pagination\n\tstartIndex := (page - 1) * pageSize\n\tendIndex := startIndex + pageSize\n\n\t// If page does not contain any posts\n\tif startIndex \u003e= len(filteredPosts) {\n\t\treturn \"[]\"\n\t}\n\n\t// If page contains fewer posts than the page size\n\tif endIndex \u003e len(filteredPosts) {\n\t\tendIndex = len(filteredPosts)\n\t}\n\n\t// Return JSON representation of paginated and sorted posts\n\treturn PostsToJSONString(filteredPosts[startIndex:endIndex])\n}\n\n// RemovePost allows the owner to remove a post with a specific ID\nfunc (m *Memeland) RemovePost(id string) string {\n\tif id == \"\" {\n\t\tpanic(\"id cannot be empty\")\n\t}\n\n\tif err := m.CallerIsOwner(); err != nil {\n\t\tpanic(err)\n\t}\n\n\tfor i, post := range m.Posts {\n\t\tif post.ID == id {\n\t\t\tm.Posts = append(m.Posts[:i], m.Posts[i+1:]...)\n\t\t\treturn id\n\t\t}\n\t}\n\n\tpanic(\"post with specified id does not exist\")\n}\n\n// PostsToJSONString converts a slice of Post structs into a JSON string\nfunc PostsToJSONString(posts []*Post) string {\n\tvar sb strings.Builder\n\tsb.WriteString(\"[\")\n\n\tfor i, post := range posts {\n\t\tif i \u003e 0 {\n\t\t\tsb.WriteString(\",\")\n\t\t}\n\n\t\tsb.WriteString(PostToJSONString(post))\n\t}\n\tsb.WriteString(\"]\")\n\n\treturn sb.String()\n}\n\n// PostToJSONString returns a Post formatted as a JSON string\nfunc PostToJSONString(post *Post) string {\n\tvar sb strings.Builder\n\n\tsb.WriteString(\"{\")\n\tsb.WriteString(`\"id\":\"` + post.ID + `\",`)\n\tsb.WriteString(`\"data\":\"` + escapeString(post.Data) + `\",`)\n\tsb.WriteString(`\"author\":\"` + escapeString(post.Author.String()) + `\",`)\n\tsb.WriteString(`\"timestamp\":\"` + strconv.Itoa(int(post.Timestamp.Unix())) + `\",`)\n\tsb.WriteString(`\"upvotes\":` + strconv.Itoa(post.UpvoteTracker.Size()))\n\tsb.WriteString(\"}\")\n\n\treturn sb.String()\n}\n\n// escapeString escapes quotes in a string for JSON compatibility.\nfunc escapeString(s string) string {\n\treturn strings.ReplaceAll(s, `\"`, `\\\"`)\n}\n\nfunc (m *Memeland) getPost(id string) *Post {\n\tfor _, p := range m.Posts {\n\t\tif p.ID == id {\n\t\t\treturn p\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// PostSorter is a flexible sorter for the *Post slice\ntype PostSorter struct {\n\tPosts []*Post\n\tLessF func(i, j int) bool\n}\n\nfunc (p PostSorter) Len() int {\n\treturn len(p.Posts)\n}\n\nfunc (p PostSorter) Swap(i, j int) {\n\tp.Posts[i], p.Posts[j] = p.Posts[j], p.Posts[i]\n}\n\nfunc (p PostSorter) Less(i, j int) bool {\n\treturn p.LessF(i, j)\n}\n"},{"name":"memeland_test.gno","body":"package memeland\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\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 TestPostMeme(t *testing.T) {\n\tm := NewMemeland()\n\tid := m.PostMeme(\"Test meme data\", time.Now().Unix())\n\tuassert.NotEqual(t, \"\", string(id), \"Expected valid ID, got empty string\")\n}\n\nfunc TestGetPostsInRangePagination(t *testing.T) {\n\tm := NewMemeland()\n\tnow := time.Now()\n\n\tnumOfPosts := 5\n\tvar memeData []string\n\tfor i := 1; i \u003c= numOfPosts; i++ {\n\t\t// Prepare meme data\n\t\tnextTime := now.Add(time.Duration(i) * time.Minute)\n\t\tdata := ufmt.Sprintf(\"Meme #%d\", i)\n\t\tmemeData = append(memeData, data)\n\n\t\tm.PostMeme(data, nextTime.Unix())\n\t}\n\n\t// Get timestamps\n\tbeforeEarliest := now.Add(-1 * time.Minute)\n\tafterLatest := now.Add(time.Duration(numOfPosts)*time.Minute + time.Minute)\n\n\ttestCases := []struct {\n\t\tpage int\n\t\tpageSize int\n\t\texpectedNumOfPosts int\n\t}{\n\t\t{page: 1, pageSize: 1, expectedNumOfPosts: 1}, // one per page\n\t\t{page: 2, pageSize: 1, expectedNumOfPosts: 1}, // one on second page\n\t\t{page: 1, pageSize: numOfPosts, expectedNumOfPosts: numOfPosts}, // all posts on single page\n\t\t{page: 12, pageSize: 1, expectedNumOfPosts: 0}, // empty page\n\t\t{page: 1, pageSize: numOfPosts + 1, expectedNumOfPosts: numOfPosts}, // page with fewer posts than its size\n\t\t{page: 5, pageSize: numOfPosts / 5, expectedNumOfPosts: 1}, // evenly distribute posts per page\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(ufmt.Sprintf(\"Page%d_Size%d\", tc.page, tc.pageSize), func(t *testing.T) {\n\t\t\tresult := m.GetPostsInRange(beforeEarliest.Unix(), afterLatest.Unix(), tc.page, tc.pageSize, \"DATE_CREATED\")\n\n\t\t\t// Count posts by how many times id: shows up in JSON string\n\t\t\tpostCount := strings.Count(result, `\"id\":\"`)\n\t\t\tuassert.Equal(t, tc.expectedNumOfPosts, postCount)\n\t\t})\n\t}\n}\n\nfunc TestGetPostsInRangeByTimestamp(t *testing.T) {\n\tm := NewMemeland()\n\tnow := time.Now()\n\n\tnumOfPosts := 5\n\tvar memeData []string\n\tfor i := 1; i \u003c= numOfPosts; i++ {\n\t\t// Prepare meme data\n\t\tnextTime := now.Add(time.Duration(i) * time.Minute)\n\t\tdata := ufmt.Sprintf(\"Meme #%d\", i)\n\t\tmemeData = append(memeData, data)\n\n\t\tm.PostMeme(data, nextTime.Unix())\n\t}\n\n\t// Get timestamps\n\tbeforeEarliest := now.Add(-1 * time.Minute)\n\tafterLatest := now.Add(time.Duration(numOfPosts)*time.Minute + time.Minute)\n\n\t// Default sort is by addition order/timestamp\n\tjsonStr := m.GetPostsInRange(\n\t\tbeforeEarliest.Unix(), // start at earliest post\n\t\tafterLatest.Unix(), // end at latest post\n\t\t1, // first page\n\t\tnumOfPosts, // all memes on the page\n\t\t\"DATE_CREATED\", // sort by newest first\n\t)\n\n\tuassert.NotEmpty(t, jsonStr, \"Expected non-empty JSON string, got empty string\")\n\n\t// Count the number of posts returned in the JSON string as a rudimentary check for correct pagination/filtering\n\tpostCount := strings.Count(jsonStr, `\"id\":\"`)\n\tuassert.Equal(t, uint64(m.MemeCounter), uint64(postCount))\n\n\t// Check if data is there\n\tfor _, expData := range memeData {\n\t\tcheck := strings.Contains(jsonStr, expData)\n\t\tuassert.True(t, check, ufmt.Sprintf(\"Expected %s in the JSON string, but counld't find it\", expData))\n\t}\n\n\t// Check if ordering is correct, sort by created date\n\tfor i := 0; i \u003c len(memeData)-2; i++ {\n\t\tcheck := strings.Index(jsonStr, memeData[i]) \u003e= strings.Index(jsonStr, memeData[i+1])\n\t\tuassert.True(t, check, ufmt.Sprintf(\"Expected %s to be before %s, but was at %d, and %d\", memeData[i], memeData[i+1], i, i+1))\n\t}\n}\n\nfunc TestGetPostsInRangeByUpvote(t *testing.T) {\n\tm := NewMemeland()\n\tnow := time.Now()\n\n\tmemeData1 := \"Meme #1\"\n\tmemeData2 := \"Meme #2\"\n\n\t// Create posts at specific times for testing\n\tid1 := m.PostMeme(memeData1, now.Unix())\n\tid2 := m.PostMeme(memeData2, now.Add(time.Minute).Unix())\n\n\tm.Upvote(id1)\n\tm.Upvote(id2)\n\n\t// Change caller so avoid double upvote panic\n\tstd.TestSetOrigCaller(testutils.TestAddress(\"alice\"))\n\tm.Upvote(id1)\n\n\t// Final upvote count:\n\t// Meme #1 - 2 upvote\n\t// Meme #2 - 1 upvotes\n\n\t// Get timestamps\n\tbeforeEarliest := now.Add(-time.Minute)\n\tafterLatest := now.Add(time.Hour)\n\n\t// Default sort is by addition order/timestamp\n\tjsonStr := m.GetPostsInRange(\n\t\tbeforeEarliest.Unix(), // start at earliest post\n\t\tafterLatest.Unix(), // end at latest post\n\t\t1, // first page\n\t\t2, // all memes on the page\n\t\t\"UPVOTES\", // sort by upvote\n\t)\n\n\tuassert.NotEmpty(t, jsonStr, \"Expected non-empty JSON string, got empty string\")\n\n\t// Count the number of posts returned in the JSON string as a rudimentary check for correct pagination/filtering\n\tpostCount := strings.Count(jsonStr, `\"id\":\"`)\n\tuassert.Equal(t, uint64(m.MemeCounter), uint64(postCount))\n\n\t// Check if ordering is correct\n\tcheck := strings.Index(jsonStr, \"Meme #1\") \u003c= strings.Index(jsonStr, \"Meme #2\")\n\tuassert.True(t, check, ufmt.Sprintf(\"Expected %s to be before %s\", memeData1, memeData2))\n}\n\nfunc TestBadSortBy(t *testing.T) {\n\tm := NewMemeland()\n\tnow := time.Now()\n\n\tnumOfPosts := 5\n\tvar memeData []string\n\tfor i := 1; i \u003c= numOfPosts; i++ {\n\t\t// Prepare meme data\n\t\tnextTime := now.Add(time.Duration(i) * time.Minute)\n\t\tdata := ufmt.Sprintf(\"Meme #%d\", i)\n\t\tmemeData = append(memeData, data)\n\n\t\tm.PostMeme(data, nextTime.Unix())\n\t}\n\n\t// Get timestamps\n\tbeforeEarliest := now.Add(-1 * time.Minute)\n\tafterLatest := now.Add(time.Duration(numOfPosts)*time.Minute + time.Minute)\n\n\ttests := []struct {\n\t\tname string\n\t\tsortBy string\n\t\twantPanic string\n\t}{\n\t\t{\n\t\t\tname: \"Empty sortBy\",\n\t\t\tsortBy: \"\",\n\t\t\twantPanic: \"runtime error: index out of range\",\n\t\t},\n\t\t{\n\t\t\tname: \"Wrong sortBy\",\n\t\t\tsortBy: \"random string\",\n\t\t\twantPanic: \"\",\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tdefer func() {\n\t\t\t\tif r := recover(); r == nil {\n\t\t\t\t\tt.Errorf(\"code did not panic when it should have\")\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\t// Panics should be caught\n\t\t\t_ = m.GetPostsInRange(beforeEarliest.Unix(), afterLatest.Unix(), 1, 1, tc.sortBy)\n\t\t})\n\t}\n}\n\nfunc TestNoPosts(t *testing.T) {\n\tm := NewMemeland()\n\n\t// Add a post to Memeland\n\tnow := time.Now().Unix()\n\n\tjsonStr := m.GetPostsInRange(0, now, 1, 1, \"DATE_CREATED\")\n\n\tuassert.Equal(t, jsonStr, \"[]\")\n}\n\nfunc TestUpvote(t *testing.T) {\n\tm := NewMemeland()\n\n\t// Add a post to Memeland\n\tnow := time.Now().Unix()\n\tpostID := m.PostMeme(\"Test meme data\", now)\n\n\t// Initial upvote count should be 0\n\tpost := m.getPost(postID)\n\tuassert.Equal(t, 0, post.UpvoteTracker.Size())\n\n\t// Upvote the post\n\tupvoteResult := m.Upvote(postID)\n\tuassert.Equal(t, \"upvote successful\", upvoteResult)\n\n\t// Retrieve the post again and check the upvote count\n\tpost = m.getPost(postID)\n\tuassert.Equal(t, 1, post.UpvoteTracker.Size())\n}\n\nfunc TestDelete(t *testing.T) {\n\talice := testutils.TestAddress(\"alice\")\n\tstd.TestSetOrigCaller(alice)\n\n\t// Alice is admin\n\tm := NewMemeland()\n\n\t// Set caller to Bob\n\tbob := testutils.TestAddress(\"bob\")\n\tstd.TestSetOrigCaller(bob)\n\n\t// Bob adds post to Memeland\n\tnow := time.Now()\n\tpostID := m.PostMeme(\"Meme #1\", now.Unix())\n\n\t// Alice removes Bob's post\n\tstd.TestSetOrigCaller(alice)\n\n\tid := m.RemovePost(postID)\n\tuassert.Equal(t, postID, id, \"post IDs not matching\")\n\tuassert.Equal(t, 0, len(m.Posts), \"there should be 0 posts after removing\")\n}\n\nfunc TestDeleteByNonAdmin(t *testing.T) {\n\talice := testutils.TestAddress(\"alice\")\n\tstd.TestSetOrigCaller(alice)\n\n\tm := NewMemeland()\n\n\t// Add a post to Memeland\n\tnow := time.Now()\n\tpostID := m.PostMeme(\"Meme #1\", now.Unix())\n\n\t// Bob will try to delete meme posted by Alice, which should fail\n\tbob := testutils.TestAddress(\"bob\")\n\tstd.TestSetOrigCaller(bob)\n\n\tdefer func() {\n\t\tif r := recover(); r == nil {\n\t\t\tt.Errorf(\"code did not panic when it should have\")\n\t\t}\n\t}()\n\n\t// Should panic - caught by defer\n\tm.RemovePost(postID)\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":"memeland","path":"gno.land/p/demo/memeland","files":[{"name":"memeland.gno","body":"package memeland\n\nimport (\n\t\"sort\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/seqid\"\n)\n\nconst (\n\tDATE_CREATED = \"DATE_CREATED\"\n\tUPVOTES = \"UPVOTES\"\n)\n\ntype Post struct {\n\tID string\n\tData string\n\tAuthor std.Address\n\tTimestamp time.Time\n\tUpvoteTracker *avl.Tree // address \u003e struct{}{}\n}\n\ntype Memeland struct {\n\t*ownable.Ownable\n\tPosts []*Post\n\tMemeCounter seqid.ID\n}\n\nfunc NewMemeland() *Memeland {\n\treturn \u0026Memeland{\n\t\tOwnable: ownable.New(),\n\t\tPosts: make([]*Post, 0),\n\t}\n}\n\n// PostMeme - Adds a new post\nfunc (m *Memeland) PostMeme(data string, timestamp int64) string {\n\tif data == \"\" || timestamp \u003c= 0 {\n\t\tpanic(\"timestamp or data cannot be empty\")\n\t}\n\n\t// Generate ID\n\tid := m.MemeCounter.Next().String()\n\n\tnewPost := \u0026Post{\n\t\tID: id,\n\t\tData: data,\n\t\tAuthor: std.PrevRealm().Addr(),\n\t\tTimestamp: time.Unix(timestamp, 0),\n\t\tUpvoteTracker: avl.NewTree(),\n\t}\n\n\tm.Posts = append(m.Posts, newPost)\n\treturn id\n}\n\nfunc (m *Memeland) Upvote(id string) string {\n\tpost := m.getPost(id)\n\tif post == nil {\n\t\tpanic(\"post with specified ID does not exist\")\n\t}\n\n\tcaller := std.PrevRealm().Addr().String()\n\n\tif _, exists := post.UpvoteTracker.Get(caller); exists {\n\t\tpanic(\"user has already upvoted this post\")\n\t}\n\n\tpost.UpvoteTracker.Set(caller, struct{}{})\n\n\treturn \"upvote successful\"\n}\n\n// GetPostsInRange returns a JSON string of posts within the given timestamp range, supporting pagination\nfunc (m *Memeland) GetPostsInRange(startTimestamp, endTimestamp int64, page, pageSize int, sortBy string) string {\n\tif len(m.Posts) == 0 {\n\t\treturn \"[]\"\n\t}\n\n\tif page \u003c 1 {\n\t\tpanic(\"page number cannot be less than 1\")\n\t}\n\n\t// No empty pages\n\tif pageSize \u003c 1 {\n\t\tpanic(\"page size cannot be less than 1\")\n\t}\n\n\t// No pages larger than 10\n\tif pageSize \u003e 10 {\n\t\tpanic(\"page size cannot be larger than 10\")\n\t}\n\n\t// Need to pass in a sort parameter\n\tif sortBy == \"\" {\n\t\tpanic(\"sort order cannot be empty\")\n\t}\n\n\tvar filteredPosts []*Post\n\n\tstart := time.Unix(startTimestamp, 0)\n\tend := time.Unix(endTimestamp, 0)\n\n\t// Filtering posts\n\tfor _, p := range m.Posts {\n\t\tif !p.Timestamp.Before(start) \u0026\u0026 !p.Timestamp.After(end) {\n\t\t\tfilteredPosts = append(filteredPosts, p)\n\t\t}\n\t}\n\n\tswitch sortBy {\n\t// Sort by upvote descending\n\tcase UPVOTES:\n\t\tdateSorter := PostSorter{\n\t\t\tPosts: filteredPosts,\n\t\t\tLessF: func(i, j int) bool {\n\t\t\t\treturn filteredPosts[i].UpvoteTracker.Size() \u003e filteredPosts[j].UpvoteTracker.Size()\n\t\t\t},\n\t\t}\n\t\tsort.Sort(dateSorter)\n\tcase DATE_CREATED:\n\t\t// Sort by timestamp, beginning with newest\n\t\tdateSorter := PostSorter{\n\t\t\tPosts: filteredPosts,\n\t\t\tLessF: func(i, j int) bool {\n\t\t\t\treturn filteredPosts[i].Timestamp.After(filteredPosts[j].Timestamp)\n\t\t\t},\n\t\t}\n\t\tsort.Sort(dateSorter)\n\tdefault:\n\t\tpanic(\"sort order can only be \\\"UPVOTES\\\" or \\\"DATE_CREATED\\\"\")\n\t}\n\n\t// Pagination\n\tstartIndex := (page - 1) * pageSize\n\tendIndex := startIndex + pageSize\n\n\t// If page does not contain any posts\n\tif startIndex \u003e= len(filteredPosts) {\n\t\treturn \"[]\"\n\t}\n\n\t// If page contains fewer posts than the page size\n\tif endIndex \u003e len(filteredPosts) {\n\t\tendIndex = len(filteredPosts)\n\t}\n\n\t// Return JSON representation of paginated and sorted posts\n\treturn PostsToJSONString(filteredPosts[startIndex:endIndex])\n}\n\n// RemovePost allows the owner to remove a post with a specific ID\nfunc (m *Memeland) RemovePost(id string) string {\n\tif id == \"\" {\n\t\tpanic(\"id cannot be empty\")\n\t}\n\n\tif err := m.CallerIsOwner(); err != nil {\n\t\tpanic(err)\n\t}\n\n\tfor i, post := range m.Posts {\n\t\tif post.ID == id {\n\t\t\tm.Posts = append(m.Posts[:i], m.Posts[i+1:]...)\n\t\t\treturn id\n\t\t}\n\t}\n\n\tpanic(\"post with specified id does not exist\")\n}\n\n// PostsToJSONString converts a slice of Post structs into a JSON string\nfunc PostsToJSONString(posts []*Post) string {\n\tvar sb strings.Builder\n\tsb.WriteString(\"[\")\n\n\tfor i, post := range posts {\n\t\tif i \u003e 0 {\n\t\t\tsb.WriteString(\",\")\n\t\t}\n\n\t\tsb.WriteString(PostToJSONString(post))\n\t}\n\tsb.WriteString(\"]\")\n\n\treturn sb.String()\n}\n\n// PostToJSONString returns a Post formatted as a JSON string\nfunc PostToJSONString(post *Post) string {\n\tvar sb strings.Builder\n\n\tsb.WriteString(\"{\")\n\tsb.WriteString(`\"id\":\"` + post.ID + `\",`)\n\tsb.WriteString(`\"data\":\"` + escapeString(post.Data) + `\",`)\n\tsb.WriteString(`\"author\":\"` + escapeString(post.Author.String()) + `\",`)\n\tsb.WriteString(`\"timestamp\":\"` + strconv.Itoa(int(post.Timestamp.Unix())) + `\",`)\n\tsb.WriteString(`\"upvotes\":` + strconv.Itoa(post.UpvoteTracker.Size()))\n\tsb.WriteString(\"}\")\n\n\treturn sb.String()\n}\n\n// escapeString escapes quotes in a string for JSON compatibility.\nfunc escapeString(s string) string {\n\treturn strings.ReplaceAll(s, `\"`, `\\\"`)\n}\n\nfunc (m *Memeland) getPost(id string) *Post {\n\tfor _, p := range m.Posts {\n\t\tif p.ID == id {\n\t\t\treturn p\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// PostSorter is a flexible sorter for the *Post slice\ntype PostSorter struct {\n\tPosts []*Post\n\tLessF func(i, j int) bool\n}\n\nfunc (p PostSorter) Len() int {\n\treturn len(p.Posts)\n}\n\nfunc (p PostSorter) Swap(i, j int) {\n\tp.Posts[i], p.Posts[j] = p.Posts[j], p.Posts[i]\n}\n\nfunc (p PostSorter) Less(i, j int) bool {\n\treturn p.LessF(i, j)\n}\n"},{"name":"memeland_test.gno","body":"package memeland\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\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 TestPostMeme(t *testing.T) {\n\tm := NewMemeland()\n\tid := m.PostMeme(\"Test meme data\", time.Now().Unix())\n\tuassert.NotEqual(t, \"\", string(id), \"Expected valid ID, got empty string\")\n}\n\nfunc TestGetPostsInRangePagination(t *testing.T) {\n\tm := NewMemeland()\n\tnow := time.Now()\n\n\tnumOfPosts := 5\n\tvar memeData []string\n\tfor i := 1; i \u003c= numOfPosts; i++ {\n\t\t// Prepare meme data\n\t\tnextTime := now.Add(time.Duration(i) * time.Minute)\n\t\tdata := ufmt.Sprintf(\"Meme #%d\", i)\n\t\tmemeData = append(memeData, data)\n\n\t\tm.PostMeme(data, nextTime.Unix())\n\t}\n\n\t// Get timestamps\n\tbeforeEarliest := now.Add(-1 * time.Minute)\n\tafterLatest := now.Add(time.Duration(numOfPosts)*time.Minute + time.Minute)\n\n\ttestCases := []struct {\n\t\tpage int\n\t\tpageSize int\n\t\texpectedNumOfPosts int\n\t}{\n\t\t{page: 1, pageSize: 1, expectedNumOfPosts: 1}, // one per page\n\t\t{page: 2, pageSize: 1, expectedNumOfPosts: 1}, // one on second page\n\t\t{page: 1, pageSize: numOfPosts, expectedNumOfPosts: numOfPosts}, // all posts on single page\n\t\t{page: 12, pageSize: 1, expectedNumOfPosts: 0}, // empty page\n\t\t{page: 1, pageSize: numOfPosts + 1, expectedNumOfPosts: numOfPosts}, // page with fewer posts than its size\n\t\t{page: 5, pageSize: numOfPosts / 5, expectedNumOfPosts: 1}, // evenly distribute posts per page\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(ufmt.Sprintf(\"Page%d_Size%d\", tc.page, tc.pageSize), func(t *testing.T) {\n\t\t\tresult := m.GetPostsInRange(beforeEarliest.Unix(), afterLatest.Unix(), tc.page, tc.pageSize, \"DATE_CREATED\")\n\n\t\t\t// Count posts by how many times id: shows up in JSON string\n\t\t\tpostCount := strings.Count(result, `\"id\":\"`)\n\t\t\tuassert.Equal(t, tc.expectedNumOfPosts, postCount)\n\t\t})\n\t}\n}\n\nfunc TestGetPostsInRangeByTimestamp(t *testing.T) {\n\tm := NewMemeland()\n\tnow := time.Now()\n\n\tnumOfPosts := 5\n\tvar memeData []string\n\tfor i := 1; i \u003c= numOfPosts; i++ {\n\t\t// Prepare meme data\n\t\tnextTime := now.Add(time.Duration(i) * time.Minute)\n\t\tdata := ufmt.Sprintf(\"Meme #%d\", i)\n\t\tmemeData = append(memeData, data)\n\n\t\tm.PostMeme(data, nextTime.Unix())\n\t}\n\n\t// Get timestamps\n\tbeforeEarliest := now.Add(-1 * time.Minute)\n\tafterLatest := now.Add(time.Duration(numOfPosts)*time.Minute + time.Minute)\n\n\t// Default sort is by addition order/timestamp\n\tjsonStr := m.GetPostsInRange(\n\t\tbeforeEarliest.Unix(), // start at earliest post\n\t\tafterLatest.Unix(), // end at latest post\n\t\t1, // first page\n\t\tnumOfPosts, // all memes on the page\n\t\t\"DATE_CREATED\", // sort by newest first\n\t)\n\n\tuassert.NotEmpty(t, jsonStr, \"Expected non-empty JSON string, got empty string\")\n\n\t// Count the number of posts returned in the JSON string as a rudimentary check for correct pagination/filtering\n\tpostCount := strings.Count(jsonStr, `\"id\":\"`)\n\tuassert.Equal(t, uint64(m.MemeCounter), uint64(postCount))\n\n\t// Check if data is there\n\tfor _, expData := range memeData {\n\t\tcheck := strings.Contains(jsonStr, expData)\n\t\tuassert.True(t, check, ufmt.Sprintf(\"Expected %s in the JSON string, but counld't find it\", expData))\n\t}\n\n\t// Check if ordering is correct, sort by created date\n\tfor i := 0; i \u003c len(memeData)-2; i++ {\n\t\tcheck := strings.Index(jsonStr, memeData[i]) \u003e= strings.Index(jsonStr, memeData[i+1])\n\t\tuassert.True(t, check, ufmt.Sprintf(\"Expected %s to be before %s, but was at %d, and %d\", memeData[i], memeData[i+1], i, i+1))\n\t}\n}\n\nfunc TestGetPostsInRangeByUpvote(t *testing.T) {\n\tm := NewMemeland()\n\tnow := time.Now()\n\n\tmemeData1 := \"Meme #1\"\n\tmemeData2 := \"Meme #2\"\n\n\t// Create posts at specific times for testing\n\tid1 := m.PostMeme(memeData1, now.Unix())\n\tid2 := m.PostMeme(memeData2, now.Add(time.Minute).Unix())\n\n\tm.Upvote(id1)\n\tm.Upvote(id2)\n\n\t// Change caller so avoid double upvote panic\n\tstd.TestSetOriginCaller(testutils.TestAddress(\"alice\"))\n\tm.Upvote(id1)\n\n\t// Final upvote count:\n\t// Meme #1 - 2 upvote\n\t// Meme #2 - 1 upvotes\n\n\t// Get timestamps\n\tbeforeEarliest := now.Add(-time.Minute)\n\tafterLatest := now.Add(time.Hour)\n\n\t// Default sort is by addition order/timestamp\n\tjsonStr := m.GetPostsInRange(\n\t\tbeforeEarliest.Unix(), // start at earliest post\n\t\tafterLatest.Unix(), // end at latest post\n\t\t1, // first page\n\t\t2, // all memes on the page\n\t\t\"UPVOTES\", // sort by upvote\n\t)\n\n\tuassert.NotEmpty(t, jsonStr, \"Expected non-empty JSON string, got empty string\")\n\n\t// Count the number of posts returned in the JSON string as a rudimentary check for correct pagination/filtering\n\tpostCount := strings.Count(jsonStr, `\"id\":\"`)\n\tuassert.Equal(t, uint64(m.MemeCounter), uint64(postCount))\n\n\t// Check if ordering is correct\n\tcheck := strings.Index(jsonStr, \"Meme #1\") \u003c= strings.Index(jsonStr, \"Meme #2\")\n\tuassert.True(t, check, ufmt.Sprintf(\"Expected %s to be before %s\", memeData1, memeData2))\n}\n\nfunc TestBadSortBy(t *testing.T) {\n\tm := NewMemeland()\n\tnow := time.Now()\n\n\tnumOfPosts := 5\n\tvar memeData []string\n\tfor i := 1; i \u003c= numOfPosts; i++ {\n\t\t// Prepare meme data\n\t\tnextTime := now.Add(time.Duration(i) * time.Minute)\n\t\tdata := ufmt.Sprintf(\"Meme #%d\", i)\n\t\tmemeData = append(memeData, data)\n\n\t\tm.PostMeme(data, nextTime.Unix())\n\t}\n\n\t// Get timestamps\n\tbeforeEarliest := now.Add(-1 * time.Minute)\n\tafterLatest := now.Add(time.Duration(numOfPosts)*time.Minute + time.Minute)\n\n\ttests := []struct {\n\t\tname string\n\t\tsortBy string\n\t\twantPanic string\n\t}{\n\t\t{\n\t\t\tname: \"Empty sortBy\",\n\t\t\tsortBy: \"\",\n\t\t\twantPanic: \"runtime error: index out of range\",\n\t\t},\n\t\t{\n\t\t\tname: \"Wrong sortBy\",\n\t\t\tsortBy: \"random string\",\n\t\t\twantPanic: \"\",\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tdefer func() {\n\t\t\t\tif r := recover(); r == nil {\n\t\t\t\t\tt.Errorf(\"code did not panic when it should have\")\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\t// Panics should be caught\n\t\t\t_ = m.GetPostsInRange(beforeEarliest.Unix(), afterLatest.Unix(), 1, 1, tc.sortBy)\n\t\t})\n\t}\n}\n\nfunc TestNoPosts(t *testing.T) {\n\tm := NewMemeland()\n\n\t// Add a post to Memeland\n\tnow := time.Now().Unix()\n\n\tjsonStr := m.GetPostsInRange(0, now, 1, 1, \"DATE_CREATED\")\n\n\tuassert.Equal(t, jsonStr, \"[]\")\n}\n\nfunc TestUpvote(t *testing.T) {\n\tm := NewMemeland()\n\n\t// Add a post to Memeland\n\tnow := time.Now().Unix()\n\tpostID := m.PostMeme(\"Test meme data\", now)\n\n\t// Initial upvote count should be 0\n\tpost := m.getPost(postID)\n\tuassert.Equal(t, 0, post.UpvoteTracker.Size())\n\n\t// Upvote the post\n\tupvoteResult := m.Upvote(postID)\n\tuassert.Equal(t, \"upvote successful\", upvoteResult)\n\n\t// Retrieve the post again and check the upvote count\n\tpost = m.getPost(postID)\n\tuassert.Equal(t, 1, post.UpvoteTracker.Size())\n}\n\nfunc TestDelete(t *testing.T) {\n\talice := testutils.TestAddress(\"alice\")\n\tstd.TestSetOriginCaller(alice)\n\n\t// Alice is admin\n\tm := NewMemeland()\n\n\t// Set caller to Bob\n\tbob := testutils.TestAddress(\"bob\")\n\tstd.TestSetOriginCaller(bob)\n\n\t// Bob adds post to Memeland\n\tnow := time.Now()\n\tpostID := m.PostMeme(\"Meme #1\", now.Unix())\n\n\t// Alice removes Bob's post\n\tstd.TestSetOriginCaller(alice)\n\n\tid := m.RemovePost(postID)\n\tuassert.Equal(t, postID, id, \"post IDs not matching\")\n\tuassert.Equal(t, 0, len(m.Posts), \"there should be 0 posts after removing\")\n}\n\nfunc TestDeleteByNonAdmin(t *testing.T) {\n\talice := testutils.TestAddress(\"alice\")\n\tstd.TestSetOriginCaller(alice)\n\n\tm := NewMemeland()\n\n\t// Add a post to Memeland\n\tnow := time.Now()\n\tpostID := m.PostMeme(\"Meme #1\", now.Unix())\n\n\t// Bob will try to delete meme posted by Alice, which should fail\n\tbob := testutils.TestAddress(\"bob\")\n\tstd.TestSetOriginCaller(bob)\n\n\tdefer func() {\n\t\tif r := recover(); r == nil {\n\t\t\tt.Errorf(\"code did not panic when it should have\")\n\t\t}\n\t}()\n\n\t// Should panic - caught by defer\n\tm.RemovePost(postID)\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":"merkle","path":"gno.land/p/demo/merkle","files":[{"name":"README.md","body":"# p/demo/merkle\n\nThis package implement a merkle tree that is complient with [merkletreejs](https://github.com/merkletreejs/merkletreejs)\n\n## [merkletreejs](https://github.com/merkletreejs/merkletreejs)\n\n```javascript\nconst { MerkleTree } = require(\"merkletreejs\");\nconst SHA256 = require(\"crypto-js/sha256\");\n\nlet leaves = [];\nfor (let i = 0; i \u003c 10; i++) {\n leaves.push(SHA256(`node_${i}`));\n}\n\nconst tree = new MerkleTree(leaves, SHA256);\nconst root = tree.getRoot().toString(\"hex\");\n\nconsole.log(root); // cd8a40502b0b92bf58e7432a5abb2d8b60121cf2b7966d6ebaf103f907a1bc21\n```\n"},{"name":"merkle.gno","body":"package merkle\n\nimport (\n\t\"bytes\"\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n\t\"errors\"\n)\n\ntype Hashable interface {\n\tBytes() []byte\n}\n\ntype nodes []Node\n\ntype Node struct {\n\thash []byte\n\n\tposition uint8\n}\n\nfunc NewNode(hash []byte, position uint8) Node {\n\treturn Node{\n\t\thash: hash,\n\t\tposition: position,\n\t}\n}\n\nfunc (n Node) Position() uint8 {\n\treturn n.position\n}\n\nfunc (n Node) Hash() string {\n\treturn hex.EncodeToString(n.hash[:])\n}\n\ntype Tree struct {\n\tlayers []nodes\n}\n\n// Root return the merkle root of the tree\nfunc (t *Tree) Root() string {\n\tfor _, l := range t.layers {\n\t\tif len(l) == 1 {\n\t\t\treturn l[0].Hash()\n\t\t}\n\t}\n\treturn \"\"\n}\n\n// NewTree create a new Merkle Tree\nfunc NewTree(data []Hashable) *Tree {\n\ttree := \u0026Tree{}\n\n\tleaves := make([]Node, len(data))\n\n\tfor i, d := range data {\n\t\thash := sha256.Sum256(d.Bytes())\n\t\tleaves[i] = Node{hash: hash[:]}\n\t}\n\n\ttree.layers = []nodes{nodes(leaves)}\n\n\tvar buff bytes.Buffer\n\tfor len(leaves) \u003e 1 {\n\t\tlevel := make([]Node, 0, len(leaves)/2+1)\n\t\tfor i := 0; i \u003c len(leaves); i += 2 {\n\t\t\tbuff.Reset()\n\n\t\t\tif i \u003c len(leaves)-1 {\n\t\t\t\tbuff.Write(leaves[i].hash)\n\t\t\t\tbuff.Write(leaves[i+1].hash)\n\t\t\t\thash := sha256.Sum256(buff.Bytes())\n\t\t\t\tlevel = append(level, Node{\n\t\t\t\t\thash: hash[:],\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tlevel = append(level, leaves[i])\n\t\t\t}\n\t\t}\n\t\tleaves = level\n\t\ttree.layers = append(tree.layers, level)\n\t}\n\treturn tree\n}\n\n// Proof return a MerkleProof\nfunc (t *Tree) Proof(data Hashable) ([]Node, error) {\n\ttargetHash := sha256.Sum256(data.Bytes())\n\ttargetIndex := -1\n\n\tfor i, layer := range t.layers[0] {\n\t\tif bytes.Equal(targetHash[:], layer.hash) {\n\t\t\ttargetIndex = i\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif targetIndex == -1 {\n\t\treturn nil, errors.New(\"target not found\")\n\t}\n\n\tproofs := make([]Node, 0, len(t.layers))\n\n\tfor _, layer := range t.layers {\n\t\tvar pairIndex int\n\n\t\tif targetIndex%2 == 0 {\n\t\t\tpairIndex = targetIndex + 1\n\t\t} else {\n\t\t\tpairIndex = targetIndex - 1\n\t\t}\n\t\tif pairIndex \u003c len(layer) {\n\t\t\tproofs = append(proofs, Node{\n\t\t\t\thash: layer[pairIndex].hash,\n\t\t\t\tposition: uint8(targetIndex) % 2,\n\t\t\t})\n\t\t}\n\t\ttargetIndex /= 2\n\t}\n\treturn proofs, nil\n}\n\n// Verify if a merkle proof is valid\nfunc (t *Tree) Verify(leaf Hashable, proofs []Node) bool {\n\treturn Verify(t.Root(), leaf, proofs)\n}\n\n// Verify if a merkle proof is valid\nfunc Verify(root string, leaf Hashable, proofs []Node) bool {\n\thash := sha256.Sum256(leaf.Bytes())\n\n\tfor i := 0; i \u003c len(proofs); i += 1 {\n\t\tvar h []byte\n\t\tif proofs[i].position == 0 {\n\t\t\th = append(hash[:], proofs[i].hash...)\n\t\t} else {\n\t\t\th = append(proofs[i].hash, hash[:]...)\n\t\t}\n\t\thash = sha256.Sum256(h)\n\t}\n\treturn hex.EncodeToString(hash[:]) == root\n}\n"},{"name":"merkle_test.gno","body":"package merkle\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n)\n\ntype testData struct {\n\tcontent string\n}\n\nfunc (d testData) Bytes() []byte {\n\treturn []byte(d.content)\n}\n\nfunc TestMerkleTree(t *testing.T) {\n\ttests := []struct {\n\t\tsize int\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tsize: 1,\n\t\t\texpected: \"cf9f824bce7f5bc63d557b23591f58577f53fe29f974a615bdddbd0140f912f4\",\n\t\t},\n\t\t{\n\t\t\tsize: 3,\n\t\t\texpected: \"1a4a5f0fa267244bf9f74a63fdf2a87eed5e97e4bd104a9e94728c8fb5442177\",\n\t\t},\n\t\t{\n\t\t\tsize: 10,\n\t\t\texpected: \"cd8a40502b0b92bf58e7432a5abb2d8b60121cf2b7966d6ebaf103f907a1bc21\",\n\t\t},\n\t\t{\n\t\t\tsize: 1000,\n\t\t\texpected: \"fa533d2efdf12be26bc410dfa42936ac63361324e35e9b1ff54d422a1dd2388b\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tvar leaves []Hashable\n\t\tfor i := 0; i \u003c test.size; i++ {\n\t\t\tleaves = append(leaves, testData{fmt.Sprintf(\"node_%d\", i)})\n\t\t}\n\n\t\ttree := NewTree(leaves)\n\n\t\tif tree == nil {\n\t\t\tt.Error(\"Merkle tree creation failed\")\n\t\t}\n\n\t\troot := tree.Root()\n\n\t\tif root != test.expected {\n\t\t\tt.Fatalf(\"merkle.Tree.Root(), expected: %s; got: %s\", test.expected, root)\n\t\t}\n\n\t\tfor _, leaf := range leaves {\n\t\t\tproofs, err := tree.Proof(leaf)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(\"failed to proof leaf: %v, on tree: %v\", leaf, test)\n\t\t\t}\n\n\t\t\tok := Verify(root, leaf, proofs)\n\t\t\tif !ok {\n\t\t\t\tt.Fatal(\"failed to verify leaf: %v, on tree: %v\", leaf, tree)\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":"microblog","path":"gno.land/p/demo/microblog","files":[{"name":"microblog.gno","body":"package microblog\n\nimport (\n\t\"errors\"\n\t\"sort\"\n\t\"std\"\n\t\"strings\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar (\n\tErrNotFound = errors.New(\"not found\")\n\tStatusNotFound = \"404\"\n)\n\ntype Microblog struct {\n\tTitle string\n\tPrefix string // i.e. r/gnoland/blog:\n\tPages avl.Tree // author (string) -\u003e Page\n}\n\nfunc NewMicroblog(title string, prefix string) (m *Microblog) {\n\treturn \u0026Microblog{\n\t\tTitle: title,\n\t\tPrefix: prefix,\n\t\tPages: avl.Tree{},\n\t}\n}\n\nfunc (m *Microblog) GetPages() []*Page {\n\tvar (\n\t\tpages = make([]*Page, m.Pages.Size())\n\t\tindex = 0\n\t)\n\n\tm.Pages.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tpages[index] = value.(*Page)\n\t\tindex++\n\t\treturn false\n\t})\n\n\tsort.Sort(byLastPosted(pages))\n\n\treturn pages\n}\n\nfunc (m *Microblog) NewPost(text string) error {\n\tauthor := std.OrigCaller()\n\t_, found := m.Pages.Get(author.String())\n\tif !found {\n\t\t// make a new page for the new author\n\t\tm.Pages.Set(author.String(), \u0026Page{\n\t\t\tAuthor: author,\n\t\t\tCreatedAt: time.Now(),\n\t\t})\n\t}\n\n\tpage, err := m.GetPage(author.String())\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn page.NewPost(text)\n}\n\nfunc (m *Microblog) GetPage(author string) (*Page, error) {\n\tsilo, found := m.Pages.Get(author)\n\tif !found {\n\t\treturn nil, ErrNotFound\n\t}\n\treturn silo.(*Page), nil\n}\n\ntype Page struct {\n\tID int\n\tAuthor std.Address\n\tCreatedAt time.Time\n\tLastPosted time.Time\n\tPosts avl.Tree // time -\u003e Post\n}\n\n// byLastPosted implements sort.Interface for []Page based on\n// the LastPosted field.\ntype byLastPosted []*Page\n\nfunc (a byLastPosted) Len() int { return len(a) }\nfunc (a byLastPosted) Swap(i, j int) { a[i], a[j] = a[j], a[i] }\nfunc (a byLastPosted) Less(i, j int) bool { return a[i].LastPosted.After(a[j].LastPosted) }\n\nfunc (p *Page) NewPost(text string) error {\n\tnow := time.Now()\n\tp.LastPosted = now\n\tp.Posts.Set(ufmt.Sprintf(\"%s%d\", now.Format(time.RFC3339), p.Posts.Size()), \u0026Post{\n\t\tID: p.Posts.Size(),\n\t\tText: text,\n\t\tCreatedAt: now,\n\t})\n\treturn nil\n}\n\nfunc (p *Page) GetPosts() []*Post {\n\tposts := make([]*Post, p.Posts.Size())\n\ti := 0\n\tp.Posts.ReverseIterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tpostParsed := value.(*Post)\n\t\tposts[i] = postParsed\n\t\ti++\n\t\treturn false\n\t})\n\treturn posts\n}\n\n// Post lists the specific update\ntype Post struct {\n\tID int\n\tCreatedAt time.Time\n\tText string\n}\n\nfunc (p *Post) String() string {\n\treturn \"\u003e \" + strings.ReplaceAll(p.Text, \"\\n\", \"\\n\u003e\\n\u003e\") + \"\\n\u003e\\n\u003e *\" + p.CreatedAt.Format(time.RFC1123) + \"*\"\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":"microblog","path":"gno.land/p/demo/microblog","files":[{"name":"microblog.gno","body":"package microblog\n\nimport (\n\t\"errors\"\n\t\"sort\"\n\t\"std\"\n\t\"strings\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar (\n\tErrNotFound = errors.New(\"not found\")\n\tStatusNotFound = \"404\"\n)\n\ntype Microblog struct {\n\tTitle string\n\tPrefix string // i.e. r/gnoland/blog:\n\tPages avl.Tree // author (string) -\u003e Page\n}\n\nfunc NewMicroblog(title string, prefix string) (m *Microblog) {\n\treturn \u0026Microblog{\n\t\tTitle: title,\n\t\tPrefix: prefix,\n\t\tPages: avl.Tree{},\n\t}\n}\n\nfunc (m *Microblog) GetPages() []*Page {\n\tvar (\n\t\tpages = make([]*Page, m.Pages.Size())\n\t\tindex = 0\n\t)\n\n\tm.Pages.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tpages[index] = value.(*Page)\n\t\tindex++\n\t\treturn false\n\t})\n\n\tsort.Sort(byLastPosted(pages))\n\n\treturn pages\n}\n\nfunc (m *Microblog) NewPost(text string) error {\n\tauthor := std.OriginCaller()\n\t_, found := m.Pages.Get(author.String())\n\tif !found {\n\t\t// make a new page for the new author\n\t\tm.Pages.Set(author.String(), \u0026Page{\n\t\t\tAuthor: author,\n\t\t\tCreatedAt: time.Now(),\n\t\t})\n\t}\n\n\tpage, err := m.GetPage(author.String())\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn page.NewPost(text)\n}\n\nfunc (m *Microblog) GetPage(author string) (*Page, error) {\n\tsilo, found := m.Pages.Get(author)\n\tif !found {\n\t\treturn nil, ErrNotFound\n\t}\n\treturn silo.(*Page), nil\n}\n\ntype Page struct {\n\tID int\n\tAuthor std.Address\n\tCreatedAt time.Time\n\tLastPosted time.Time\n\tPosts avl.Tree // time -\u003e Post\n}\n\n// byLastPosted implements sort.Interface for []Page based on\n// the LastPosted field.\ntype byLastPosted []*Page\n\nfunc (a byLastPosted) Len() int { return len(a) }\nfunc (a byLastPosted) Swap(i, j int) { a[i], a[j] = a[j], a[i] }\nfunc (a byLastPosted) Less(i, j int) bool { return a[i].LastPosted.After(a[j].LastPosted) }\n\nfunc (p *Page) NewPost(text string) error {\n\tnow := time.Now()\n\tp.LastPosted = now\n\tp.Posts.Set(ufmt.Sprintf(\"%s%d\", now.Format(time.RFC3339), p.Posts.Size()), \u0026Post{\n\t\tID: p.Posts.Size(),\n\t\tText: text,\n\t\tCreatedAt: now,\n\t})\n\treturn nil\n}\n\nfunc (p *Page) GetPosts() []*Post {\n\tposts := make([]*Post, p.Posts.Size())\n\ti := 0\n\tp.Posts.ReverseIterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tpostParsed := value.(*Post)\n\t\tposts[i] = postParsed\n\t\ti++\n\t\treturn false\n\t})\n\treturn posts\n}\n\n// Post lists the specific update\ntype Post struct {\n\tID int\n\tCreatedAt time.Time\n\tText string\n}\n\nfunc (p *Post) String() string {\n\treturn \"\u003e \" + strings.ReplaceAll(p.Text, \"\\n\", \"\\n\u003e\\n\u003e\") + \"\\n\u003e\\n\u003e *\" + p.CreatedAt.Format(time.RFC1123) + \"*\"\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":"nestedpkg","path":"gno.land/p/demo/nestedpkg","files":[{"name":"nestedpkg.gno","body":"// Package nestedpkg provides helpers for package-path based access control.\n// It is useful for upgrade patterns relying on namespaces.\npackage nestedpkg\n\n// To test this from a realm and have std.CurrentRealm/PrevRealm work correctly,\n// this file is tested from gno.land/r/demo/tests/nestedpkg_test.gno\n// XXX: move test to ths directory once we support testing a package and\n// specifying values for both PrevRealm and CurrentRealm.\n\nimport (\n\t\"std\"\n\t\"strings\"\n)\n\n// IsCallerSubPath checks if the caller realm is located in a subfolder of the current realm.\nfunc IsCallerSubPath() bool {\n\tvar (\n\t\tcur = std.CurrentRealm().PkgPath() + \"/\"\n\t\tprev = std.PrevRealm().PkgPath() + \"/\"\n\t)\n\treturn strings.HasPrefix(prev, cur)\n}\n\n// AssertCallerIsSubPath panics if IsCallerSubPath returns false.\nfunc AssertCallerIsSubPath() {\n\tvar (\n\t\tcur = std.CurrentRealm().PkgPath() + \"/\"\n\t\tprev = std.PrevRealm().PkgPath() + \"/\"\n\t)\n\tif !strings.HasPrefix(prev, cur) {\n\t\tpanic(\"call restricted to nested packages. current realm is \" + cur + \", previous realm is \" + prev)\n\t}\n}\n\n// IsCallerParentPath checks if the caller realm is located in a parent location of the current realm.\nfunc IsCallerParentPath() bool {\n\tvar (\n\t\tcur = std.CurrentRealm().PkgPath() + \"/\"\n\t\tprev = std.PrevRealm().PkgPath() + \"/\"\n\t)\n\treturn strings.HasPrefix(cur, prev)\n}\n\n// AssertCallerIsParentPath panics if IsCallerParentPath returns false.\nfunc AssertCallerIsParentPath() {\n\tvar (\n\t\tcur = std.CurrentRealm().PkgPath() + \"/\"\n\t\tprev = std.PrevRealm().PkgPath() + \"/\"\n\t)\n\tif !strings.HasPrefix(cur, prev) {\n\t\tpanic(\"call restricted to parent packages. current realm is \" + cur + \", previous realm is \" + prev)\n\t}\n}\n\n// IsSameNamespace checks if the caller realm and the current realm are in the same namespace.\nfunc IsSameNamespace() bool {\n\tvar (\n\t\tcur = nsFromPath(std.CurrentRealm().PkgPath()) + \"/\"\n\t\tprev = nsFromPath(std.PrevRealm().PkgPath()) + \"/\"\n\t)\n\treturn cur == prev\n}\n\n// AssertIsSameNamespace panics if IsSameNamespace returns false.\nfunc AssertIsSameNamespace() {\n\tvar (\n\t\tcur = nsFromPath(std.CurrentRealm().PkgPath()) + \"/\"\n\t\tprev = nsFromPath(std.PrevRealm().PkgPath()) + \"/\"\n\t)\n\tif cur != prev {\n\t\tpanic(\"call restricted to packages from the same namespace. current realm is \" + cur + \", previous realm is \" + prev)\n\t}\n}\n\n// nsFromPath extracts the namespace from a package path.\nfunc nsFromPath(pkgpath string) string {\n\tparts := strings.Split(pkgpath, \"/\")\n\n\t// Specifically for gno.land, potential paths are in the form of DOMAIN/r/NAMESPACE/...\n\t// XXX: Consider extra checks.\n\t// XXX: Support non gno.land domains, where p/ and r/ won't be enforced.\n\tif len(parts) \u003e= 3 {\n\t\treturn parts[2]\n\t}\n\treturn \"\"\n}\n\n// XXX: Consider adding IsCallerDirectlySubPath\n// XXX: Consider adding IsCallerDirectlyParentPath\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":"authorizable","path":"gno.land/p/demo/ownable/exts/authorizable","files":[{"name":"authorizable.gno","body":"// Package authorizable is an extension of p/demo/ownable;\n// It allows the user to instantiate an Authorizable struct, which extends\n// p/demo/ownable with a list of users that are authorized for something.\n// By using authorizable, you have a superuser (ownable), as well as another\n// authorization level, which can be used for adding moderators or similar to your realm.\npackage authorizable\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype Authorizable struct {\n\t*ownable.Ownable // owner in ownable is superuser\n\tauthorized *avl.Tree // std.Addr \u003e struct{}{}\n}\n\nfunc NewAuthorizable() *Authorizable {\n\ta := \u0026Authorizable{\n\t\townable.New(),\n\t\tavl.NewTree(),\n\t}\n\n\t// Add owner to auth list\n\ta.authorized.Set(a.Owner().String(), struct{}{})\n\treturn a\n}\n\nfunc NewAuthorizableWithAddress(addr std.Address) *Authorizable {\n\ta := \u0026Authorizable{\n\t\townable.NewWithAddress(addr),\n\t\tavl.NewTree(),\n\t}\n\n\t// Add owner to auth list\n\ta.authorized.Set(a.Owner().String(), struct{}{})\n\treturn a\n}\n\nfunc (a *Authorizable) AddToAuthList(addr std.Address) error {\n\tif err := a.CallerIsOwner(); err != nil {\n\t\treturn ErrNotSuperuser\n\t}\n\n\tif _, exists := a.authorized.Get(addr.String()); exists {\n\t\treturn ErrAlreadyInList\n\t}\n\n\ta.authorized.Set(addr.String(), struct{}{})\n\n\treturn nil\n}\n\nfunc (a *Authorizable) DeleteFromAuthList(addr std.Address) error {\n\tif err := a.CallerIsOwner(); err != nil {\n\t\treturn ErrNotSuperuser\n\t}\n\n\tif !a.authorized.Has(addr.String()) {\n\t\treturn ErrNotInAuthList\n\t}\n\n\tif _, removed := a.authorized.Remove(addr.String()); !removed {\n\t\tstr := ufmt.Sprintf(\"authorizable: could not remove %s from auth list\", addr.String())\n\t\tpanic(str)\n\t}\n\n\treturn nil\n}\n\nfunc (a Authorizable) CallerOnAuthList() error {\n\tcaller := std.PrevRealm().Addr()\n\n\tif !a.authorized.Has(caller.String()) {\n\t\treturn ErrNotInAuthList\n\t}\n\n\treturn nil\n}\n\nfunc (a Authorizable) AssertOnAuthList() {\n\tcaller := std.PrevRealm().Addr()\n\n\tif !a.authorized.Has(caller.String()) {\n\t\tpanic(ErrNotInAuthList)\n\t}\n}\n"},{"name":"authorizable_test.gno","body":"package authorizable\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\talice = testutils.TestAddress(\"alice\")\n\tbob = testutils.TestAddress(\"bob\")\n\tcharlie = testutils.TestAddress(\"charlie\")\n)\n\nfunc TestNewAuthorizable(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOrigCaller(alice) // TODO(bug, issue #2371): should not be needed\n\n\ta := NewAuthorizable()\n\tgot := a.Owner()\n\n\tif alice != got {\n\t\tt.Fatalf(\"Expected %s, got: %s\", alice, got)\n\t}\n}\n\nfunc TestNewAuthorizableWithAddress(t *testing.T) {\n\ta := NewAuthorizableWithAddress(alice)\n\n\tgot := a.Owner()\n\n\tif alice != got {\n\t\tt.Fatalf(\"Expected %s, got: %s\", alice, got)\n\t}\n}\n\nfunc TestCallerOnAuthList(t *testing.T) {\n\ta := NewAuthorizableWithAddress(alice)\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOrigCaller(alice)\n\n\tif err := a.CallerOnAuthList(); err == ErrNotInAuthList {\n\t\tt.Fatalf(\"expected alice to be on the list\")\n\t}\n}\n\nfunc TestNotCallerOnAuthList(t *testing.T) {\n\ta := NewAuthorizableWithAddress(alice)\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\tstd.TestSetOrigCaller(bob)\n\n\tif err := a.CallerOnAuthList(); err == nil {\n\t\tt.Fatalf(\"expected bob to not be on the list\")\n\t}\n}\n\nfunc TestAddToAuthList(t *testing.T) {\n\ta := NewAuthorizableWithAddress(alice)\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOrigCaller(alice)\n\n\tif err := a.AddToAuthList(bob); err != nil {\n\t\tt.Fatalf(\"Expected no error, got %v\", err)\n\t}\n\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\tstd.TestSetOrigCaller(bob)\n\n\tif err := a.AddToAuthList(bob); err == nil {\n\t\tt.Fatalf(\"Expected AddToAuth to error while bob called it, but it didn't\")\n\t}\n}\n\nfunc TestDeleteFromList(t *testing.T) {\n\ta := NewAuthorizableWithAddress(alice)\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOrigCaller(alice)\n\n\tif err := a.AddToAuthList(bob); err != nil {\n\t\tt.Fatalf(\"Expected no error, got %v\", err)\n\t}\n\n\tif err := a.AddToAuthList(charlie); err != nil {\n\t\tt.Fatalf(\"Expected no error, got %v\", err)\n\t}\n\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\tstd.TestSetOrigCaller(bob)\n\n\t// Try an unauthorized deletion\n\tif err := a.DeleteFromAuthList(alice); err == nil {\n\t\tt.Fatalf(\"Expected DelFromAuth to error with %v\", err)\n\t}\n\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOrigCaller(alice)\n\n\tif err := a.DeleteFromAuthList(charlie); err != nil {\n\t\tt.Fatalf(\"Expected no error, got %v\", err)\n\t}\n}\n\nfunc TestAssertOnList(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOrigCaller(alice)\n\ta := NewAuthorizableWithAddress(alice)\n\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\tstd.TestSetOrigCaller(bob)\n\n\tuassert.PanicsWithMessage(t, ErrNotInAuthList.Error(), func() {\n\t\ta.AssertOnAuthList()\n\t})\n}\n"},{"name":"errors.gno","body":"package authorizable\n\nimport \"errors\"\n\nvar (\n\tErrNotInAuthList = errors.New(\"authorizable: caller is not in authorized list\")\n\tErrNotSuperuser = errors.New(\"authorizable: caller is not superuser\")\n\tErrAlreadyInList = errors.New(\"authorizable: address is already in authorized list\")\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":"pausable","path":"gno.land/p/demo/pausable","files":[{"name":"pausable.gno","body":"package pausable\n\nimport \"gno.land/p/demo/ownable\"\n\ntype Pausable struct {\n\t*ownable.Ownable\n\tpaused bool\n}\n\n// New returns a new Pausable struct with non-paused state as default\nfunc New() *Pausable {\n\treturn \u0026Pausable{\n\t\tOwnable: ownable.New(),\n\t\tpaused: false,\n\t}\n}\n\n// NewFromOwnable is the same as New, but with a pre-existing top-level ownable\nfunc NewFromOwnable(ownable *ownable.Ownable) *Pausable {\n\treturn \u0026Pausable{\n\t\tOwnable: ownable,\n\t\tpaused: false,\n\t}\n}\n\n// IsPaused checks if Pausable is paused\nfunc (p Pausable) IsPaused() bool {\n\treturn p.paused\n}\n\n// Pause sets the state of Pausable to true, meaning all pausable functions are paused\nfunc (p *Pausable) Pause() error {\n\tif err := p.CallerIsOwner(); err != nil {\n\t\treturn err\n\t}\n\n\tp.paused = true\n\treturn nil\n}\n\n// Unpause sets the state of Pausable to false, meaning all pausable functions are resumed\nfunc (p *Pausable) Unpause() error {\n\tif err := p.CallerIsOwner(); err != nil {\n\t\treturn err\n\t}\n\n\tp.paused = false\n\treturn nil\n}\n"},{"name":"pausable_test.gno","body":"package pausable\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nvar (\n\tfirstCaller = std.Address(\"g1l9aypkr8xfvs82zeux486ddzec88ty69lue9de\")\n\tsecondCaller = std.Address(\"g127jydsh6cms3lrtdenydxsckh23a8d6emqcvfa\")\n)\n\nfunc TestNew(t *testing.T) {\n\tstd.TestSetOrigCaller(firstCaller)\n\n\tresult := New()\n\n\turequire.False(t, result.paused, \"Expected result to be unpaused\")\n\turequire.Equal(t, firstCaller.String(), result.Owner().String())\n}\n\nfunc TestNewFromOwnable(t *testing.T) {\n\tstd.TestSetOrigCaller(firstCaller)\n\to := ownable.New()\n\n\tstd.TestSetOrigCaller(secondCaller)\n\tresult := NewFromOwnable(o)\n\n\turequire.Equal(t, firstCaller.String(), result.Owner().String())\n}\n\nfunc TestSetUnpaused(t *testing.T) {\n\tstd.TestSetOrigCaller(firstCaller)\n\n\tresult := New()\n\tresult.Unpause()\n\n\turequire.False(t, result.IsPaused(), \"Expected result to be unpaused\")\n}\n\nfunc TestSetPaused(t *testing.T) {\n\tstd.TestSetOrigCaller(firstCaller)\n\n\tresult := New()\n\tresult.Pause()\n\n\turequire.True(t, result.IsPaused(), \"Expected result to be paused\")\n}\n\nfunc TestIsPaused(t *testing.T) {\n\tstd.TestSetOrigCaller(firstCaller)\n\n\tresult := New()\n\turequire.False(t, result.IsPaused(), \"Expected result to be unpaused\")\n\n\tresult.Pause()\n\turequire.True(t, result.IsPaused(), \"Expected result to be paused\")\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":"authorizable","path":"gno.land/p/demo/ownable/exts/authorizable","files":[{"name":"authorizable.gno","body":"// Package authorizable is an extension of p/demo/ownable;\n// It allows the user to instantiate an Authorizable struct, which extends\n// p/demo/ownable with a list of users that are authorized for something.\n// By using authorizable, you have a superuser (ownable), as well as another\n// authorization level, which can be used for adding moderators or similar to your realm.\npackage authorizable\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype Authorizable struct {\n\t*ownable.Ownable // owner in ownable is superuser\n\tauthorized *avl.Tree // std.Addr \u003e struct{}{}\n}\n\nfunc NewAuthorizable() *Authorizable {\n\ta := \u0026Authorizable{\n\t\townable.New(),\n\t\tavl.NewTree(),\n\t}\n\n\t// Add owner to auth list\n\ta.authorized.Set(a.Owner().String(), struct{}{})\n\treturn a\n}\n\nfunc NewAuthorizableWithAddress(addr std.Address) *Authorizable {\n\ta := \u0026Authorizable{\n\t\townable.NewWithAddress(addr),\n\t\tavl.NewTree(),\n\t}\n\n\t// Add owner to auth list\n\ta.authorized.Set(a.Owner().String(), struct{}{})\n\treturn a\n}\n\nfunc (a *Authorizable) AddToAuthList(addr std.Address) error {\n\tif err := a.CallerIsOwner(); err != nil {\n\t\treturn ErrNotSuperuser\n\t}\n\n\tif _, exists := a.authorized.Get(addr.String()); exists {\n\t\treturn ErrAlreadyInList\n\t}\n\n\ta.authorized.Set(addr.String(), struct{}{})\n\n\treturn nil\n}\n\nfunc (a *Authorizable) DeleteFromAuthList(addr std.Address) error {\n\tif err := a.CallerIsOwner(); err != nil {\n\t\treturn ErrNotSuperuser\n\t}\n\n\tif !a.authorized.Has(addr.String()) {\n\t\treturn ErrNotInAuthList\n\t}\n\n\tif _, removed := a.authorized.Remove(addr.String()); !removed {\n\t\tstr := ufmt.Sprintf(\"authorizable: could not remove %s from auth list\", addr.String())\n\t\tpanic(str)\n\t}\n\n\treturn nil\n}\n\nfunc (a Authorizable) CallerOnAuthList() error {\n\tcaller := std.PrevRealm().Addr()\n\n\tif !a.authorized.Has(caller.String()) {\n\t\treturn ErrNotInAuthList\n\t}\n\n\treturn nil\n}\n\nfunc (a Authorizable) AssertOnAuthList() {\n\tcaller := std.PrevRealm().Addr()\n\n\tif !a.authorized.Has(caller.String()) {\n\t\tpanic(ErrNotInAuthList)\n\t}\n}\n"},{"name":"authorizable_test.gno","body":"package authorizable\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\talice = testutils.TestAddress(\"alice\")\n\tbob = testutils.TestAddress(\"bob\")\n\tcharlie = testutils.TestAddress(\"charlie\")\n)\n\nfunc TestNewAuthorizable(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOriginCaller(alice) // TODO(bug, issue #2371): should not be needed\n\n\ta := NewAuthorizable()\n\tgot := a.Owner()\n\n\tif alice != got {\n\t\tt.Fatalf(\"Expected %s, got: %s\", alice, got)\n\t}\n}\n\nfunc TestNewAuthorizableWithAddress(t *testing.T) {\n\ta := NewAuthorizableWithAddress(alice)\n\n\tgot := a.Owner()\n\n\tif alice != got {\n\t\tt.Fatalf(\"Expected %s, got: %s\", alice, got)\n\t}\n}\n\nfunc TestCallerOnAuthList(t *testing.T) {\n\ta := NewAuthorizableWithAddress(alice)\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOriginCaller(alice)\n\n\tif err := a.CallerOnAuthList(); err == ErrNotInAuthList {\n\t\tt.Fatalf(\"expected alice to be on the list\")\n\t}\n}\n\nfunc TestNotCallerOnAuthList(t *testing.T) {\n\ta := NewAuthorizableWithAddress(alice)\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\tstd.TestSetOriginCaller(bob)\n\n\tif err := a.CallerOnAuthList(); err == nil {\n\t\tt.Fatalf(\"expected bob to not be on the list\")\n\t}\n}\n\nfunc TestAddToAuthList(t *testing.T) {\n\ta := NewAuthorizableWithAddress(alice)\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOriginCaller(alice)\n\n\tif err := a.AddToAuthList(bob); err != nil {\n\t\tt.Fatalf(\"Expected no error, got %v\", err)\n\t}\n\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\tstd.TestSetOriginCaller(bob)\n\n\tif err := a.AddToAuthList(bob); err == nil {\n\t\tt.Fatalf(\"Expected AddToAuth to error while bob called it, but it didn't\")\n\t}\n}\n\nfunc TestDeleteFromList(t *testing.T) {\n\ta := NewAuthorizableWithAddress(alice)\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOriginCaller(alice)\n\n\tif err := a.AddToAuthList(bob); err != nil {\n\t\tt.Fatalf(\"Expected no error, got %v\", err)\n\t}\n\n\tif err := a.AddToAuthList(charlie); err != nil {\n\t\tt.Fatalf(\"Expected no error, got %v\", err)\n\t}\n\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\tstd.TestSetOriginCaller(bob)\n\n\t// Try an unauthorized deletion\n\tif err := a.DeleteFromAuthList(alice); err == nil {\n\t\tt.Fatalf(\"Expected DelFromAuth to error with %v\", err)\n\t}\n\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOriginCaller(alice)\n\n\tif err := a.DeleteFromAuthList(charlie); err != nil {\n\t\tt.Fatalf(\"Expected no error, got %v\", err)\n\t}\n}\n\nfunc TestAssertOnList(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOriginCaller(alice)\n\ta := NewAuthorizableWithAddress(alice)\n\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\tstd.TestSetOriginCaller(bob)\n\n\tuassert.PanicsWithMessage(t, ErrNotInAuthList.Error(), func() {\n\t\ta.AssertOnAuthList()\n\t})\n}\n"},{"name":"errors.gno","body":"package authorizable\n\nimport \"errors\"\n\nvar (\n\tErrNotInAuthList = errors.New(\"authorizable: caller is not in authorized list\")\n\tErrNotSuperuser = errors.New(\"authorizable: caller is not superuser\")\n\tErrAlreadyInList = errors.New(\"authorizable: address is already in authorized list\")\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":"pausable","path":"gno.land/p/demo/pausable","files":[{"name":"pausable.gno","body":"package pausable\n\nimport \"gno.land/p/demo/ownable\"\n\ntype Pausable struct {\n\t*ownable.Ownable\n\tpaused bool\n}\n\n// New returns a new Pausable struct with non-paused state as default\nfunc New() *Pausable {\n\treturn \u0026Pausable{\n\t\tOwnable: ownable.New(),\n\t\tpaused: false,\n\t}\n}\n\n// NewFromOwnable is the same as New, but with a pre-existing top-level ownable\nfunc NewFromOwnable(ownable *ownable.Ownable) *Pausable {\n\treturn \u0026Pausable{\n\t\tOwnable: ownable,\n\t\tpaused: false,\n\t}\n}\n\n// IsPaused checks if Pausable is paused\nfunc (p Pausable) IsPaused() bool {\n\treturn p.paused\n}\n\n// Pause sets the state of Pausable to true, meaning all pausable functions are paused\nfunc (p *Pausable) Pause() error {\n\tif err := p.CallerIsOwner(); err != nil {\n\t\treturn err\n\t}\n\n\tp.paused = true\n\treturn nil\n}\n\n// Unpause sets the state of Pausable to false, meaning all pausable functions are resumed\nfunc (p *Pausable) Unpause() error {\n\tif err := p.CallerIsOwner(); err != nil {\n\t\treturn err\n\t}\n\n\tp.paused = false\n\treturn nil\n}\n"},{"name":"pausable_test.gno","body":"package pausable\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nvar (\n\tfirstCaller = std.Address(\"g1l9aypkr8xfvs82zeux486ddzec88ty69lue9de\")\n\tsecondCaller = std.Address(\"g127jydsh6cms3lrtdenydxsckh23a8d6emqcvfa\")\n)\n\nfunc TestNew(t *testing.T) {\n\tstd.TestSetOriginCaller(firstCaller)\n\n\tresult := New()\n\n\turequire.False(t, result.paused, \"Expected result to be unpaused\")\n\turequire.Equal(t, firstCaller.String(), result.Owner().String())\n}\n\nfunc TestNewFromOwnable(t *testing.T) {\n\tstd.TestSetOriginCaller(firstCaller)\n\to := ownable.New()\n\n\tstd.TestSetOriginCaller(secondCaller)\n\tresult := NewFromOwnable(o)\n\n\turequire.Equal(t, firstCaller.String(), result.Owner().String())\n}\n\nfunc TestSetUnpaused(t *testing.T) {\n\tstd.TestSetOriginCaller(firstCaller)\n\n\tresult := New()\n\tresult.Unpause()\n\n\turequire.False(t, result.IsPaused(), \"Expected result to be unpaused\")\n}\n\nfunc TestSetPaused(t *testing.T) {\n\tstd.TestSetOriginCaller(firstCaller)\n\n\tresult := New()\n\tresult.Pause()\n\n\turequire.True(t, result.IsPaused(), \"Expected result to be paused\")\n}\n\nfunc TestIsPaused(t *testing.T) {\n\tstd.TestSetOriginCaller(firstCaller)\n\n\tresult := New()\n\turequire.False(t, result.IsPaused(), \"Expected result to be unpaused\")\n\n\tresult.Pause()\n\turequire.True(t, result.IsPaused(), \"Expected result to be paused\")\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":"releases","path":"gno.land/p/demo/releases","files":[{"name":"changelog.gno","body":"package releases\n\ntype changelog struct {\n\tname string\n\treleases []release\n}\n\nfunc NewChangelog(name string) *changelog {\n\treturn \u0026changelog{\n\t\tname: name,\n\t\treleases: make([]release, 0),\n\t}\n}\n\nfunc (c *changelog) NewRelease(version, url, notes string) {\n\tif latest := c.Latest(); latest != nil {\n\t\tlatest.isLatest = false\n\t}\n\n\trelease := release{\n\t\t// manual\n\t\tversion: version,\n\t\turl: url,\n\t\tnotes: notes,\n\n\t\t// internal\n\t\tchangelog: c,\n\t\tisLatest: true,\n\t}\n\n\tc.releases = append(c.releases, release)\n}\n\nfunc (c *changelog) Render(path string) string {\n\tif path == \"\" {\n\t\toutput := \"# \" + c.name + \"\\n\\n\"\n\t\tmax := len(c.releases) - 1\n\t\tmin := 0\n\t\tif max-min \u003e 10 {\n\t\t\tmin = max - 10\n\t\t}\n\t\tfor i := max; i \u003e= min; i-- {\n\t\t\trelease := c.releases[i]\n\t\t\toutput += release.Render()\n\t\t}\n\t\treturn output\n\t}\n\n\trelease := c.ByVersion(path)\n\tif release != nil {\n\t\treturn release.Render()\n\t}\n\n\treturn \"no such release\"\n}\n\nfunc (c *changelog) Latest() *release {\n\tif len(c.releases) \u003e 0 {\n\t\tpos := len(c.releases) - 1\n\t\treturn \u0026c.releases[pos]\n\t}\n\treturn nil\n}\n\nfunc (c *changelog) ByVersion(version string) *release {\n\tfor _, release := range c.releases {\n\t\tif release.version == version {\n\t\t\treturn \u0026release\n\t\t}\n\t}\n\treturn nil\n}\n"},{"name":"release.gno","body":"package releases\n\ntype release struct {\n\t// manual\n\tversion string\n\turl string\n\tnotes string\n\n\t// internal\n\tisLatest bool\n\tchangelog *changelog\n}\n\nfunc (r *release) URL() string { return r.url }\nfunc (r *release) Version() string { return r.version }\nfunc (r *release) Notes() string { return r.notes }\nfunc (r *release) IsLatest() bool { return r.isLatest }\n\nfunc (r *release) Title() string {\n\toutput := r.changelog.name + \" \" + r.version\n\tif r.isLatest {\n\t\toutput += \" (latest)\"\n\t}\n\treturn output\n}\n\nfunc (r *release) Link() string {\n\treturn \"[\" + r.Title() + \"](\" + r.url + \")\"\n}\n\nfunc (r *release) Render() string {\n\toutput := \"\"\n\toutput += \"## \" + r.Link() + \"\\n\\n\"\n\tif r.notes != \"\" {\n\t\toutput += r.notes + \"\\n\\n\"\n\t}\n\treturn output\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":"simpledao","path":"gno.land/p/demo/simpledao","files":[{"name":"dao.gno","body":"package simpledao\n\nimport (\n\t\"errors\"\n\t\"std\"\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\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\tvar (\n\t\tcaller = getDAOCaller()\n\t\tsentCoins = std.OrigSend() // 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\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.OrigSend() // 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.OrigCaller()\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(\"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\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})\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\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\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, 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\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\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) 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\toutput := \"\"\n\toutput += ufmt.Sprintf(\"Author: %s\", p.Author().String())\n\toutput += \"\\n\\n\"\n\toutput += p.Description()\n\toutput += \"\\n\\n\"\n\toutput += ufmt.Sprintf(\"Status: %s\", p.Status().String())\n\toutput += \"\\n\\n\"\n\toutput += ufmt.Sprintf(\n\t\t\"Voting stats: YES %d (%d%%), NO %d (%d%%), ABSTAIN %d (%d%%), MISSING VOTE %d (%d%%)\",\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\toutput += \"\\n\\n\"\n\toutput += ufmt.Sprintf(\"Threshold met: %t\", stats.YayVotes \u003e (2*stats.TotalVotingPower)/3)\n\n\treturn output\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_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\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\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\tvar (\n\t\tcaller = getDAOCaller()\n\t\tsentCoins = std.OriginSend() // 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\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.OriginSend() // 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.OriginCaller()\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(\"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\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.TestSetOriginSend(sentCoins, std.Coins{})\n\n\t\t_, err := s.Propose(dao.ProposalRequest{\n\t\t\tExecutor: ex,\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\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.TestSetOriginSend(sentCoins, std.Coins{})\n\t\tstd.TestSetOriginCaller(proposer)\n\n\t\t// Make sure the proposal was added\n\t\tid, err := s.Propose(dao.ProposalRequest{\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, 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.TestSetOriginCaller(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.TestSetOriginCaller(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.TestSetOriginCaller(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.TestSetOriginCaller(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.TestSetOriginCaller(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.TestSetOriginCaller(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.TestSetOriginCaller(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.TestSetOriginCaller(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.TestSetOriginCaller(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.TestSetOriginCaller(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.TestSetOriginCaller(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.TestSetOriginSend(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.TestSetOriginSend(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.TestSetOriginCaller(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.TestSetOriginCaller(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.TestSetOriginCaller(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.TestSetOriginCaller(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\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\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) 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\toutput := \"\"\n\toutput += ufmt.Sprintf(\"Author: %s\", p.Author().String())\n\toutput += \"\\n\\n\"\n\toutput += p.Description()\n\toutput += \"\\n\\n\"\n\toutput += ufmt.Sprintf(\"Status: %s\", p.Status().String())\n\toutput += \"\\n\\n\"\n\toutput += ufmt.Sprintf(\n\t\t\"Voting stats: YES %d (%d%%), NO %d (%d%%), ABSTAIN %d (%d%%), MISSING VOTE %d (%d%%)\",\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\toutput += \"\\n\\n\"\n\toutput += ufmt.Sprintf(\"Threshold met: %t\", stats.YayVotes \u003e (2*stats.TotalVotingPower)/3)\n\n\treturn output\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_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"stack","path":"gno.land/p/demo/stack","files":[{"name":"stack.gno","body":"package stack\n\ntype Stack struct {\n\ttop *node\n\tlength int\n}\n\ntype node struct {\n\tvalue interface{}\n\tprev *node\n}\n\nfunc New() *Stack {\n\treturn \u0026Stack{nil, 0}\n}\n\nfunc (s *Stack) Len() int {\n\treturn s.length\n}\n\nfunc (s *Stack) Top() interface{} {\n\tif s.length == 0 {\n\t\treturn nil\n\t}\n\treturn s.top.value\n}\n\nfunc (s *Stack) Pop() interface{} {\n\tif s.length == 0 {\n\t\treturn nil\n\t}\n\n\tnode := s.top\n\ts.top = node.prev\n\ts.length -= 1\n\treturn node.value\n}\n\nfunc (s *Stack) Push(value interface{}) {\n\tnode := \u0026node{value, s.top}\n\ts.top = node\n\ts.length += 1\n}\n"},{"name":"stack_test.gno","body":"package stack\n\nimport \"testing\"\n\nfunc TestStack(t *testing.T) {\n\ts := New() // Empty stack\n\n\tif s.Len() != 0 {\n\t\tt.Errorf(\"s.Len(): expected 0; got %d\", s.Len())\n\t}\n\n\ts.Push(1)\n\n\tif s.Len() != 1 {\n\t\tt.Errorf(\"s.Len(): expected 1; got %d\", s.Len())\n\t}\n\n\tif top := s.Top(); top.(int) != 1 {\n\t\tt.Errorf(\"s.Top(): expected 1; got %v\", top.(int))\n\t}\n\n\tif elem := s.Pop(); elem.(int) != 1 {\n\t\tt.Errorf(\"s.Pop(): expected 1; got %v\", elem.(int))\n\t}\n\tif s.Len() != 0 {\n\t\tt.Errorf(\"s.Len(): expected 0; got %d\", s.Len())\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":"subscription","path":"gno.land/p/demo/subscription","files":[{"name":"doc.gno","body":"// Package subscription provides a flexible system for managing both recurring and\n// lifetime subscriptions in Gno applications. It enables developers to handle\n// payment-based access control for services or products. The library supports\n// both subscriptions requiring periodic payments (recurring) and one-time payments\n// (lifetime). Subscriptions are tracked using an AVL tree for efficient management\n// of subscription statuses.\n//\n// Usage:\n//\n// Import the required sub-packages (`recurring` and/or `lifetime`) to manage specific\n// subscription types. The methods provided allow users to subscribe, check subscription\n// status, and manage payments.\n//\n// Recurring Subscription:\n//\n// Recurring subscriptions require periodic payments to maintain access.\n// Users pay to extend their access for a specific duration.\n//\n// Example:\n//\n//\t// Create a recurring subscription requiring 100 ugnot every 30 days\n//\trecSub := recurring.NewRecurringSubscription(time.Hour * 24 * 30, 100)\n//\n//\t// Process payment for the recurring subscription\n//\trecSub.Subscribe()\n//\n//\t// Gift a recurring subscription to another user\n//\trecSub.GiftSubscription(recipientAddress)\n//\n//\t// Check if a user has a valid subscription\n//\trecSub.HasValidSubscription(addr)\n//\n//\t// Get the expiration date of the subscription\n//\trecSub.GetExpiration(caller)\n//\n//\t// Update the subscription amount to 200 ugnot\n//\trecSub.UpdateAmount(200)\n//\n//\t// Get the current subscription amount\n//\trecSub.GetAmount()\n//\n// Lifetime Subscription:\n//\n// Lifetime subscriptions require a one-time payment for permanent access.\n// Once paid, users have indefinite access without further payments.\n//\n// Example:\n//\n//\t// Create a lifetime subscription costing 500 ugnot\n//\tlifeSub := lifetime.NewLifetimeSubscription(500)\n//\n//\t// Process payment for lifetime access\n//\tlifeSub.Subscribe()\n//\n//\t// Gift a lifetime subscription to another user\n//\tlifeSub.GiftSubscription(recipientAddress)\n//\n//\t// Check if a user has a valid subscription\n//\tlifeSub.HasValidSubscription(addr)\n//\n//\t// Update the lifetime subscription amount to 1000 ugnot\n//\tlifeSub.UpdateAmount(1000)\n//\n//\t// Get the current lifetime subscription amount\n//\tlifeSub.GetAmount()\npackage subscription\n"},{"name":"subscription.gno","body":"package subscription\n\nimport (\n\t\"std\"\n)\n\n// Subscription interface defines standard methods that all subscription types must implement.\ntype Subscription interface {\n\tHasValidSubscription(std.Address) error\n\tSubscribe() error\n\tUpdateAmount(newAmount int64) error\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":"lifetime","path":"gno.land/p/demo/subscription/lifetime","files":[{"name":"errors.gno","body":"package lifetime\n\nimport \"errors\"\n\nvar (\n\tErrNoSub = errors.New(\"lifetime subscription: no active subscription found\")\n\tErrAmt = errors.New(\"lifetime subscription: payment amount does not match the required subscription amount\")\n\tErrAlreadySub = errors.New(\"lifetime subscription: this address already has an active lifetime subscription\")\n\tErrNotAuthorized = errors.New(\"lifetime subscription: action not authorized\")\n)\n"},{"name":"lifetime.gno","body":"package lifetime\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n)\n\n// LifetimeSubscription represents a subscription that requires only a one-time payment.\n// It grants permanent access to a service or product.\ntype LifetimeSubscription struct {\n\townable.Ownable\n\tamount int64\n\tsubs *avl.Tree // std.Address -\u003e bool\n}\n\n// NewLifetimeSubscription creates and returns a new lifetime subscription.\nfunc NewLifetimeSubscription(amount int64) *LifetimeSubscription {\n\treturn \u0026LifetimeSubscription{\n\t\tOwnable: *ownable.New(),\n\t\tamount: amount,\n\t\tsubs: avl.NewTree(),\n\t}\n}\n\n// processSubscription handles the subscription process for a given receiver.\nfunc (ls *LifetimeSubscription) processSubscription(receiver std.Address) error {\n\tamount := std.OrigSend()\n\n\tif amount.AmountOf(\"ugnot\") != ls.amount {\n\t\treturn ErrAmt\n\t}\n\n\t_, exists := ls.subs.Get(receiver.String())\n\n\tif exists {\n\t\treturn ErrAlreadySub\n\t}\n\n\tls.subs.Set(receiver.String(), true)\n\n\treturn nil\n}\n\n// Subscribe processes the payment for a lifetime subscription.\nfunc (ls *LifetimeSubscription) Subscribe() error {\n\tcaller := std.PrevRealm().Addr()\n\treturn ls.processSubscription(caller)\n}\n\n// GiftSubscription allows the caller to pay for a lifetime subscription for another user.\nfunc (ls *LifetimeSubscription) GiftSubscription(receiver std.Address) error {\n\treturn ls.processSubscription(receiver)\n}\n\n// HasValidSubscription checks if the given address has an active lifetime subscription.\nfunc (ls *LifetimeSubscription) HasValidSubscription(addr std.Address) error {\n\t_, exists := ls.subs.Get(addr.String())\n\n\tif !exists {\n\t\treturn ErrNoSub\n\t}\n\n\treturn nil\n}\n\n// UpdateAmount allows the owner of the LifetimeSubscription contract to update the subscription price.\nfunc (ls *LifetimeSubscription) UpdateAmount(newAmount int64) error {\n\tif err := ls.CallerIsOwner(); err != nil {\n\t\treturn ErrNotAuthorized\n\t}\n\n\tls.amount = newAmount\n\treturn nil\n}\n\n// GetAmount returns the current subscription price.\nfunc (ls *LifetimeSubscription) GetAmount() int64 {\n\treturn ls.amount\n}\n"},{"name":"lifetime_test.gno","body":"package lifetime\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\talice = testutils.TestAddress(\"alice\")\n\tbob = testutils.TestAddress(\"bob\")\n\tcharlie = testutils.TestAddress(\"charlie\")\n)\n\nfunc TestLifetimeSubscription(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := ls.Subscribe()\n\tuassert.NoError(t, err, \"Expected ProcessPayment to succeed\")\n\n\terr = ls.HasValidSubscription(std.PrevRealm().Addr())\n\tuassert.NoError(t, err, \"Expected Alice to have access\")\n}\n\nfunc TestLifetimeSubscriptionGift(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := ls.GiftSubscription(bob)\n\tuassert.NoError(t, err, \"Expected ProcessPaymentGift to succeed for Bob\")\n\n\terr = ls.HasValidSubscription(bob)\n\tuassert.NoError(t, err, \"Expected Bob to have access\")\n\n\terr = ls.HasValidSubscription(charlie)\n\tuassert.Error(t, err, \"Expected Charlie to fail access check\")\n}\n\nfunc TestUpdateAmountAuthorization(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\terr := ls.UpdateAmount(2000)\n\tuassert.NoError(t, err, \"Expected Alice to succeed in updating amount\")\n\n\tstd.TestSetOrigCaller(bob)\n\n\terr = ls.UpdateAmount(3000)\n\tuassert.Error(t, err, \"Expected Bob to fail when updating amount\")\n}\n\nfunc TestIncorrectPaymentAmount(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 500}}, nil)\n\terr := ls.Subscribe()\n\tuassert.Error(t, err, \"Expected payment to fail with incorrect amount\")\n}\n\nfunc TestMultipleSubscriptionAttempts(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := ls.Subscribe()\n\tuassert.NoError(t, err, \"Expected first subscription to succeed\")\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr = ls.Subscribe()\n\tuassert.Error(t, err, \"Expected second subscription to fail as Alice is already subscribed\")\n}\n\nfunc TestGiftSubscriptionWithIncorrectAmount(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 500}}, nil)\n\terr := ls.GiftSubscription(bob)\n\tuassert.Error(t, err, \"Expected gift subscription to fail with incorrect amount\")\n\n\terr = ls.HasValidSubscription(bob)\n\tuassert.Error(t, err, \"Expected Bob to not have access after incorrect gift subscription\")\n}\n\nfunc TestUpdateAmountEffectiveness(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\terr := ls.UpdateAmount(2000)\n\tuassert.NoError(t, err, \"Expected Alice to succeed in updating amount\")\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr = ls.Subscribe()\n\tuassert.Error(t, err, \"Expected subscription to fail with old amount after update\")\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 2000}}, nil)\n\terr = ls.Subscribe()\n\tuassert.NoError(t, err, \"Expected subscription to succeed with new amount\")\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":"recurring","path":"gno.land/p/demo/subscription/recurring","files":[{"name":"errors.gno","body":"package recurring\n\nimport \"errors\"\n\nvar (\n\tErrNoSub = errors.New(\"recurring subscription: no active subscription found\")\n\tErrSubExpired = errors.New(\"recurring subscription: your subscription has expired\")\n\tErrAmt = errors.New(\"recurring subscription: payment amount does not match the required subscription amount\")\n\tErrAlreadySub = errors.New(\"recurring subscription: this address already has an active subscription\")\n\tErrNotAuthorized = errors.New(\"recurring subscription: action not authorized\")\n)\n"},{"name":"recurring.gno","body":"package recurring\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n)\n\n// RecurringSubscription represents a subscription that requires periodic payments.\n// It includes the duration of the subscription and the amount required per period.\ntype RecurringSubscription struct {\n\townable.Ownable\n\tduration time.Duration\n\tamount int64\n\tsubs *avl.Tree // std.Address -\u003e time.Time\n}\n\n// NewRecurringSubscription creates and returns a new recurring subscription.\nfunc NewRecurringSubscription(duration time.Duration, amount int64) *RecurringSubscription {\n\treturn \u0026RecurringSubscription{\n\t\tOwnable: *ownable.New(),\n\t\tduration: duration,\n\t\tamount: amount,\n\t\tsubs: avl.NewTree(),\n\t}\n}\n\n// HasValidSubscription verifies if the caller has an active recurring subscription.\nfunc (rs *RecurringSubscription) HasValidSubscription(addr std.Address) error {\n\texpTime, exists := rs.subs.Get(addr.String())\n\tif !exists {\n\t\treturn ErrNoSub\n\t}\n\n\tif time.Now().After(expTime.(time.Time)) {\n\t\treturn ErrSubExpired\n\t}\n\n\treturn nil\n}\n\n// processSubscription processes the payment for a given receiver and renews or adds their subscription.\nfunc (rs *RecurringSubscription) processSubscription(receiver std.Address) error {\n\tamount := std.OrigSend()\n\n\tif amount.AmountOf(\"ugnot\") != rs.amount {\n\t\treturn ErrAmt\n\t}\n\n\texpTime, exists := rs.subs.Get(receiver.String())\n\n\t// If the user is already a subscriber but his subscription has expired, authorize renewal\n\tif exists {\n\t\texpiration := expTime.(time.Time)\n\t\tif time.Now().Before(expiration) {\n\t\t\treturn ErrAlreadySub\n\t\t}\n\t}\n\n\t// Renew or add subscription\n\tnewExpiration := time.Now().Add(rs.duration)\n\trs.subs.Set(receiver.String(), newExpiration)\n\n\treturn nil\n}\n\n// Subscribe handles the payment for the caller's subscription.\nfunc (rs *RecurringSubscription) Subscribe() error {\n\tcaller := std.PrevRealm().Addr()\n\n\treturn rs.processSubscription(caller)\n}\n\n// GiftSubscription allows the user to pay for a subscription for another user (receiver).\nfunc (rs *RecurringSubscription) GiftSubscription(receiver std.Address) error {\n\treturn rs.processSubscription(receiver)\n}\n\n// GetExpiration returns the expiration date of the recurring subscription for a given caller.\nfunc (rs *RecurringSubscription) GetExpiration(addr std.Address) (time.Time, error) {\n\texpTime, exists := rs.subs.Get(addr.String())\n\tif !exists {\n\t\treturn time.Time{}, ErrNoSub\n\t}\n\n\treturn expTime.(time.Time), nil\n}\n\n// UpdateAmount allows the owner of the subscription contract to change the required subscription amount.\nfunc (rs *RecurringSubscription) UpdateAmount(newAmount int64) error {\n\tif err := rs.CallerIsOwner(); err != nil {\n\t\treturn ErrNotAuthorized\n\t}\n\n\trs.amount = newAmount\n\treturn nil\n}\n\n// GetAmount returns the current amount required for each subscription period.\nfunc (rs *RecurringSubscription) GetAmount() int64 {\n\treturn rs.amount\n}\n"},{"name":"recurring_test.gno","body":"package recurring\n\nimport (\n\t\"std\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\talice = testutils.TestAddress(\"alice\")\n\tbob = testutils.TestAddress(\"bob\")\n\tcharlie = testutils.TestAddress(\"charlie\")\n)\n\nfunc TestRecurringSubscription(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := rs.Subscribe()\n\tuassert.NoError(t, err, \"Expected ProcessPayment to succeed for Alice\")\n\n\terr = rs.HasValidSubscription(std.PrevRealm().Addr())\n\tuassert.NoError(t, err, \"Expected Alice to have access\")\n\n\texpiration, err := rs.GetExpiration(std.PrevRealm().Addr())\n\tuassert.NoError(t, err, \"Expected to get expiration for Alice\")\n}\n\nfunc TestRecurringSubscriptionGift(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := rs.GiftSubscription(bob)\n\tuassert.NoError(t, err, \"Expected ProcessPaymentGift to succeed for Bob\")\n\n\terr = rs.HasValidSubscription(bob)\n\tuassert.NoError(t, err, \"Expected Bob to have access\")\n\n\terr = rs.HasValidSubscription(charlie)\n\tuassert.Error(t, err, \"Expected Charlie to fail access check\")\n}\n\nfunc TestRecurringSubscriptionExpiration(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour, 1000)\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := rs.Subscribe()\n\tuassert.NoError(t, err, \"Expected ProcessPayment to succeed for Alice\")\n\n\terr = rs.HasValidSubscription(std.PrevRealm().Addr())\n\tuassert.NoError(t, err, \"Expected Alice to have access\")\n\n\texpiration := time.Now().Add(-time.Hour * 2)\n\trs.subs.Set(std.PrevRealm().Addr().String(), expiration)\n\n\terr = rs.HasValidSubscription(std.PrevRealm().Addr())\n\tuassert.Error(t, err, \"Expected Alice's subscription to be expired\")\n}\n\nfunc TestUpdateAmountAuthorization(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\terr := rs.UpdateAmount(2000)\n\tuassert.NoError(t, err, \"Expected Alice to succeed in updating amount\")\n\n\tstd.TestSetOrigCaller(bob)\n\terr = rs.UpdateAmount(3000)\n\tuassert.Error(t, err, \"Expected Bob to fail when updating amount\")\n}\n\nfunc TestGetAmount(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\tamount := rs.GetAmount()\n\tuassert.Equal(t, amount, int64(1000), \"Expected the initial amount to be 1000 ugnot\")\n\n\terr := rs.UpdateAmount(2000)\n\tuassert.NoError(t, err, \"Expected Alice to succeed in updating amount\")\n\n\tamount = rs.GetAmount()\n\tuassert.Equal(t, amount, int64(2000), \"Expected the updated amount to be 2000 ugnot\")\n}\n\nfunc TestIncorrectPaymentAmount(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 500}}, nil)\n\terr := rs.Subscribe()\n\tuassert.Error(t, err, \"Expected payment with incorrect amount to fail\")\n}\n\nfunc TestMultiplePaymentsForSameUser(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := rs.Subscribe()\n\tuassert.NoError(t, err, \"Expected first ProcessPayment to succeed for Alice\")\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr = rs.Subscribe()\n\tuassert.Error(t, err, \"Expected second ProcessPayment to fail for Alice due to existing subscription\")\n}\n\nfunc TestRecurringSubscriptionWithMultiplePayments(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour, 1000)\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := rs.Subscribe()\n\tuassert.NoError(t, err, \"Expected first ProcessPayment to succeed for Alice\")\n\n\terr = rs.HasValidSubscription(std.PrevRealm().Addr())\n\tuassert.NoError(t, err, \"Expected Alice to have access after first payment\")\n\n\texpiration := time.Now().Add(-time.Hour * 2)\n\trs.subs.Set(std.PrevRealm().Addr().String(), expiration)\n\n\tstd.TestSetOrigSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr = rs.Subscribe()\n\tuassert.NoError(t, err, \"Expected second ProcessPayment to succeed for Alice\")\n\n\terr = rs.HasValidSubscription(std.PrevRealm().Addr())\n\tuassert.NoError(t, err, \"Expected Alice to have access after second payment\")\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":"lifetime","path":"gno.land/p/demo/subscription/lifetime","files":[{"name":"errors.gno","body":"package lifetime\n\nimport \"errors\"\n\nvar (\n\tErrNoSub = errors.New(\"lifetime subscription: no active subscription found\")\n\tErrAmt = errors.New(\"lifetime subscription: payment amount does not match the required subscription amount\")\n\tErrAlreadySub = errors.New(\"lifetime subscription: this address already has an active lifetime subscription\")\n\tErrNotAuthorized = errors.New(\"lifetime subscription: action not authorized\")\n)\n"},{"name":"lifetime.gno","body":"package lifetime\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n)\n\n// LifetimeSubscription represents a subscription that requires only a one-time payment.\n// It grants permanent access to a service or product.\ntype LifetimeSubscription struct {\n\townable.Ownable\n\tamount int64\n\tsubs *avl.Tree // std.Address -\u003e bool\n}\n\n// NewLifetimeSubscription creates and returns a new lifetime subscription.\nfunc NewLifetimeSubscription(amount int64) *LifetimeSubscription {\n\treturn \u0026LifetimeSubscription{\n\t\tOwnable: *ownable.New(),\n\t\tamount: amount,\n\t\tsubs: avl.NewTree(),\n\t}\n}\n\n// processSubscription handles the subscription process for a given receiver.\nfunc (ls *LifetimeSubscription) processSubscription(receiver std.Address) error {\n\tamount := std.OriginSend()\n\n\tif amount.AmountOf(\"ugnot\") != ls.amount {\n\t\treturn ErrAmt\n\t}\n\n\t_, exists := ls.subs.Get(receiver.String())\n\n\tif exists {\n\t\treturn ErrAlreadySub\n\t}\n\n\tls.subs.Set(receiver.String(), true)\n\n\treturn nil\n}\n\n// Subscribe processes the payment for a lifetime subscription.\nfunc (ls *LifetimeSubscription) Subscribe() error {\n\tcaller := std.PrevRealm().Addr()\n\treturn ls.processSubscription(caller)\n}\n\n// GiftSubscription allows the caller to pay for a lifetime subscription for another user.\nfunc (ls *LifetimeSubscription) GiftSubscription(receiver std.Address) error {\n\treturn ls.processSubscription(receiver)\n}\n\n// HasValidSubscription checks if the given address has an active lifetime subscription.\nfunc (ls *LifetimeSubscription) HasValidSubscription(addr std.Address) error {\n\t_, exists := ls.subs.Get(addr.String())\n\n\tif !exists {\n\t\treturn ErrNoSub\n\t}\n\n\treturn nil\n}\n\n// UpdateAmount allows the owner of the LifetimeSubscription contract to update the subscription price.\nfunc (ls *LifetimeSubscription) UpdateAmount(newAmount int64) error {\n\tif err := ls.CallerIsOwner(); err != nil {\n\t\treturn ErrNotAuthorized\n\t}\n\n\tls.amount = newAmount\n\treturn nil\n}\n\n// GetAmount returns the current subscription price.\nfunc (ls *LifetimeSubscription) GetAmount() int64 {\n\treturn ls.amount\n}\n"},{"name":"lifetime_test.gno","body":"package lifetime\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\talice = testutils.TestAddress(\"alice\")\n\tbob = testutils.TestAddress(\"bob\")\n\tcharlie = testutils.TestAddress(\"charlie\")\n)\n\nfunc TestLifetimeSubscription(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := ls.Subscribe()\n\tuassert.NoError(t, err, \"Expected ProcessPayment to succeed\")\n\n\terr = ls.HasValidSubscription(std.PrevRealm().Addr())\n\tuassert.NoError(t, err, \"Expected Alice to have access\")\n}\n\nfunc TestLifetimeSubscriptionGift(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := ls.GiftSubscription(bob)\n\tuassert.NoError(t, err, \"Expected ProcessPaymentGift to succeed for Bob\")\n\n\terr = ls.HasValidSubscription(bob)\n\tuassert.NoError(t, err, \"Expected Bob to have access\")\n\n\terr = ls.HasValidSubscription(charlie)\n\tuassert.Error(t, err, \"Expected Charlie to fail access check\")\n}\n\nfunc TestUpdateAmountAuthorization(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\terr := ls.UpdateAmount(2000)\n\tuassert.NoError(t, err, \"Expected Alice to succeed in updating amount\")\n\n\tstd.TestSetOriginCaller(bob)\n\n\terr = ls.UpdateAmount(3000)\n\tuassert.Error(t, err, \"Expected Bob to fail when updating amount\")\n}\n\nfunc TestIncorrectPaymentAmount(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 500}}, nil)\n\terr := ls.Subscribe()\n\tuassert.Error(t, err, \"Expected payment to fail with incorrect amount\")\n}\n\nfunc TestMultipleSubscriptionAttempts(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := ls.Subscribe()\n\tuassert.NoError(t, err, \"Expected first subscription to succeed\")\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr = ls.Subscribe()\n\tuassert.Error(t, err, \"Expected second subscription to fail as Alice is already subscribed\")\n}\n\nfunc TestGiftSubscriptionWithIncorrectAmount(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 500}}, nil)\n\terr := ls.GiftSubscription(bob)\n\tuassert.Error(t, err, \"Expected gift subscription to fail with incorrect amount\")\n\n\terr = ls.HasValidSubscription(bob)\n\tuassert.Error(t, err, \"Expected Bob to not have access after incorrect gift subscription\")\n}\n\nfunc TestUpdateAmountEffectiveness(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\terr := ls.UpdateAmount(2000)\n\tuassert.NoError(t, err, \"Expected Alice to succeed in updating amount\")\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr = ls.Subscribe()\n\tuassert.Error(t, err, \"Expected subscription to fail with old amount after update\")\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 2000}}, nil)\n\terr = ls.Subscribe()\n\tuassert.NoError(t, err, \"Expected subscription to succeed with new amount\")\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":"recurring","path":"gno.land/p/demo/subscription/recurring","files":[{"name":"errors.gno","body":"package recurring\n\nimport \"errors\"\n\nvar (\n\tErrNoSub = errors.New(\"recurring subscription: no active subscription found\")\n\tErrSubExpired = errors.New(\"recurring subscription: your subscription has expired\")\n\tErrAmt = errors.New(\"recurring subscription: payment amount does not match the required subscription amount\")\n\tErrAlreadySub = errors.New(\"recurring subscription: this address already has an active subscription\")\n\tErrNotAuthorized = errors.New(\"recurring subscription: action not authorized\")\n)\n"},{"name":"recurring.gno","body":"package recurring\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n)\n\n// RecurringSubscription represents a subscription that requires periodic payments.\n// It includes the duration of the subscription and the amount required per period.\ntype RecurringSubscription struct {\n\townable.Ownable\n\tduration time.Duration\n\tamount int64\n\tsubs *avl.Tree // std.Address -\u003e time.Time\n}\n\n// NewRecurringSubscription creates and returns a new recurring subscription.\nfunc NewRecurringSubscription(duration time.Duration, amount int64) *RecurringSubscription {\n\treturn \u0026RecurringSubscription{\n\t\tOwnable: *ownable.New(),\n\t\tduration: duration,\n\t\tamount: amount,\n\t\tsubs: avl.NewTree(),\n\t}\n}\n\n// HasValidSubscription verifies if the caller has an active recurring subscription.\nfunc (rs *RecurringSubscription) HasValidSubscription(addr std.Address) error {\n\texpTime, exists := rs.subs.Get(addr.String())\n\tif !exists {\n\t\treturn ErrNoSub\n\t}\n\n\tif time.Now().After(expTime.(time.Time)) {\n\t\treturn ErrSubExpired\n\t}\n\n\treturn nil\n}\n\n// processSubscription processes the payment for a given receiver and renews or adds their subscription.\nfunc (rs *RecurringSubscription) processSubscription(receiver std.Address) error {\n\tamount := std.OriginSend()\n\n\tif amount.AmountOf(\"ugnot\") != rs.amount {\n\t\treturn ErrAmt\n\t}\n\n\texpTime, exists := rs.subs.Get(receiver.String())\n\n\t// If the user is already a subscriber but his subscription has expired, authorize renewal\n\tif exists {\n\t\texpiration := expTime.(time.Time)\n\t\tif time.Now().Before(expiration) {\n\t\t\treturn ErrAlreadySub\n\t\t}\n\t}\n\n\t// Renew or add subscription\n\tnewExpiration := time.Now().Add(rs.duration)\n\trs.subs.Set(receiver.String(), newExpiration)\n\n\treturn nil\n}\n\n// Subscribe handles the payment for the caller's subscription.\nfunc (rs *RecurringSubscription) Subscribe() error {\n\tcaller := std.PrevRealm().Addr()\n\n\treturn rs.processSubscription(caller)\n}\n\n// GiftSubscription allows the user to pay for a subscription for another user (receiver).\nfunc (rs *RecurringSubscription) GiftSubscription(receiver std.Address) error {\n\treturn rs.processSubscription(receiver)\n}\n\n// GetExpiration returns the expiration date of the recurring subscription for a given caller.\nfunc (rs *RecurringSubscription) GetExpiration(addr std.Address) (time.Time, error) {\n\texpTime, exists := rs.subs.Get(addr.String())\n\tif !exists {\n\t\treturn time.Time{}, ErrNoSub\n\t}\n\n\treturn expTime.(time.Time), nil\n}\n\n// UpdateAmount allows the owner of the subscription contract to change the required subscription amount.\nfunc (rs *RecurringSubscription) UpdateAmount(newAmount int64) error {\n\tif err := rs.CallerIsOwner(); err != nil {\n\t\treturn ErrNotAuthorized\n\t}\n\n\trs.amount = newAmount\n\treturn nil\n}\n\n// GetAmount returns the current amount required for each subscription period.\nfunc (rs *RecurringSubscription) GetAmount() int64 {\n\treturn rs.amount\n}\n"},{"name":"recurring_test.gno","body":"package recurring\n\nimport (\n\t\"std\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\talice = testutils.TestAddress(\"alice\")\n\tbob = testutils.TestAddress(\"bob\")\n\tcharlie = testutils.TestAddress(\"charlie\")\n)\n\nfunc TestRecurringSubscription(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := rs.Subscribe()\n\tuassert.NoError(t, err, \"Expected ProcessPayment to succeed for Alice\")\n\n\terr = rs.HasValidSubscription(std.PrevRealm().Addr())\n\tuassert.NoError(t, err, \"Expected Alice to have access\")\n\n\texpiration, err := rs.GetExpiration(std.PrevRealm().Addr())\n\tuassert.NoError(t, err, \"Expected to get expiration for Alice\")\n}\n\nfunc TestRecurringSubscriptionGift(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := rs.GiftSubscription(bob)\n\tuassert.NoError(t, err, \"Expected ProcessPaymentGift to succeed for Bob\")\n\n\terr = rs.HasValidSubscription(bob)\n\tuassert.NoError(t, err, \"Expected Bob to have access\")\n\n\terr = rs.HasValidSubscription(charlie)\n\tuassert.Error(t, err, \"Expected Charlie to fail access check\")\n}\n\nfunc TestRecurringSubscriptionExpiration(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour, 1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := rs.Subscribe()\n\tuassert.NoError(t, err, \"Expected ProcessPayment to succeed for Alice\")\n\n\terr = rs.HasValidSubscription(std.PrevRealm().Addr())\n\tuassert.NoError(t, err, \"Expected Alice to have access\")\n\n\texpiration := time.Now().Add(-time.Hour * 2)\n\trs.subs.Set(std.PrevRealm().Addr().String(), expiration)\n\n\terr = rs.HasValidSubscription(std.PrevRealm().Addr())\n\tuassert.Error(t, err, \"Expected Alice's subscription to be expired\")\n}\n\nfunc TestUpdateAmountAuthorization(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\terr := rs.UpdateAmount(2000)\n\tuassert.NoError(t, err, \"Expected Alice to succeed in updating amount\")\n\n\tstd.TestSetOriginCaller(bob)\n\terr = rs.UpdateAmount(3000)\n\tuassert.Error(t, err, \"Expected Bob to fail when updating amount\")\n}\n\nfunc TestGetAmount(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\tamount := rs.GetAmount()\n\tuassert.Equal(t, amount, int64(1000), \"Expected the initial amount to be 1000 ugnot\")\n\n\terr := rs.UpdateAmount(2000)\n\tuassert.NoError(t, err, \"Expected Alice to succeed in updating amount\")\n\n\tamount = rs.GetAmount()\n\tuassert.Equal(t, amount, int64(2000), \"Expected the updated amount to be 2000 ugnot\")\n}\n\nfunc TestIncorrectPaymentAmount(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 500}}, nil)\n\terr := rs.Subscribe()\n\tuassert.Error(t, err, \"Expected payment with incorrect amount to fail\")\n}\n\nfunc TestMultiplePaymentsForSameUser(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := rs.Subscribe()\n\tuassert.NoError(t, err, \"Expected first ProcessPayment to succeed for Alice\")\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr = rs.Subscribe()\n\tuassert.Error(t, err, \"Expected second ProcessPayment to fail for Alice due to existing subscription\")\n}\n\nfunc TestRecurringSubscriptionWithMultiplePayments(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour, 1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := rs.Subscribe()\n\tuassert.NoError(t, err, \"Expected first ProcessPayment to succeed for Alice\")\n\n\terr = rs.HasValidSubscription(std.PrevRealm().Addr())\n\tuassert.NoError(t, err, \"Expected Alice to have access after first payment\")\n\n\texpiration := time.Now().Add(-time.Hour * 2)\n\trs.subs.Set(std.PrevRealm().Addr().String(), expiration)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr = rs.Subscribe()\n\tuassert.NoError(t, err, \"Expected second ProcessPayment to succeed for Alice\")\n\n\terr = rs.HasValidSubscription(std.PrevRealm().Addr())\n\tuassert.NoError(t, err, \"Expected Alice to have access after second payment\")\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":"svg","path":"gno.land/p/demo/svg","files":[{"name":"doc.gno","body":"/*\nPackage svg is a minimalist SVG generation library for Gno.\n\nThe svg package provides a simple and lightweight solution for programmatically generating SVG (Scalable Vector Graphics) markup in Gno. It allows you to create basic shapes like rectangles and circles, and output the generated SVG to a\n\nExample:\n\n\timport \"gno.land/p/demo/svg\"\"\n\n\tfunc Foo() string {\n\t canvas := svg.Canvas{Width: 200, Height: 200}\n\t canvas.DrawRectangle(50, 50, 100, 100, \"red\")\n\t canvas.DrawCircle(100, 100, 50, \"blue\")\n\t return canvas.String()\n\t}\n*/\npackage svg // import \"gno.land/p/demo/svg\"\n"},{"name":"svg.gno","body":"package svg\n\nimport \"gno.land/p/demo/ufmt\"\n\ntype Canvas struct {\n\tWidth int\n\tHeight int\n\tElems []Elem\n}\n\ntype Elem interface{ String() string }\n\nfunc (c Canvas) String() string {\n\toutput := \"\"\n\toutput += ufmt.Sprintf(`\u003csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"%d\" height=\"%d\"\u003e`, c.Width, c.Height)\n\tfor _, elem := range c.Elems {\n\t\toutput += elem.String()\n\t}\n\toutput += \"\u003c/svg\u003e\"\n\treturn output\n}\n\nfunc (c *Canvas) Append(elem Elem) {\n\tc.Elems = append(c.Elems, elem)\n}\n\ntype Circle struct {\n\tCX int // center X\n\tCY int // center Y\n\tR int // radius\n\tFill string\n}\n\nfunc (c Circle) String() string {\n\treturn ufmt.Sprintf(`\u003ccircle cx=\"%d\" cy=\"%d\" r=\"%d\" fill=\"%s\" /\u003e`, c.CX, c.CY, c.R, c.Fill)\n}\n\nfunc (c *Canvas) DrawCircle(cx, cy, r int, fill string) {\n\tc.Append(Circle{\n\t\tCX: cx,\n\t\tCY: cy,\n\t\tR: r,\n\t\tFill: fill,\n\t})\n}\n\ntype Rectangle struct {\n\tX, Y, Width, Height int\n\tFill string\n}\n\nfunc (c Rectangle) String() string {\n\treturn ufmt.Sprintf(`\u003crect x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\" fill=\"%s\" /\u003e`, c.X, c.Y, c.Width, c.Height, c.Fill)\n}\n\nfunc (c *Canvas) DrawRectangle(x, y, width, height int, fill string) {\n\tc.Append(Rectangle{\n\t\tX: x,\n\t\tY: y,\n\t\tWidth: width,\n\t\tHeight: height,\n\t\tFill: fill,\n\t})\n}\n\ntype Text struct {\n\tX, Y int\n\tText, Fill string\n}\n\nfunc (c Text) String() string {\n\treturn ufmt.Sprintf(`\u003ctext x=\"%d\" y=\"%d\" fill=\"%s\"\u003e%s\u003c/text\u003e`, c.X, c.Y, c.Fill, c.Text)\n}\n\nfunc (c *Canvas) DrawText(x, y int, text, fill string) {\n\tc.Append(Text{\n\t\tX: x,\n\t\tY: y,\n\t\tText: text,\n\t\tFill: fill,\n\t})\n}\n"},{"name":"z0_filetest.gno","body":"// PKGPATH: gno.land/p/demo/svg_test\npackage svg_test\n\nimport \"gno.land/p/demo/svg\"\n\nfunc main() {\n\tcanvas := svg.Canvas{Width: 500, Height: 500}\n\tcanvas.DrawRectangle(50, 50, 100, 100, \"red\")\n\tcanvas.DrawCircle(100, 100, 50, \"blue\")\n\tcanvas.DrawText(100, 100, \"hello world!\", \"magenta\")\n\tprintln(canvas)\n}\n\n// Output:\n// \u003csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"500\" height=\"500\"\u003e\u003crect x=\"50\" y=\"50\" width=\"100\" height=\"100\" fill=\"red\" /\u003e\u003ccircle cx=\"100\" cy=\"100\" r=\"50\" fill=\"blue\" /\u003e\u003ctext x=\"100\" y=\"100\" fill=\"magenta\"\u003ehello world!\u003c/text\u003e\u003c/svg\u003e\n"},{"name":"z1_filetest.gno","body":"// PKGPATH: gno.land/p/demo/svg_test\npackage svg_test\n\nimport \"gno.land/p/demo/svg\"\n\nfunc main() {\n\tcanvas := svg.Canvas{\n\t\tWidth: 500, Height: 500,\n\t\tElems: []svg.Elem{\n\t\t\tsvg.Rectangle{50, 50, 100, 100, \"red\"},\n\t\t\tsvg.Circle{50, 50, 100, \"red\"},\n\t\t\tsvg.Text{100, 100, \"hello world!\", \"magenta\"},\n\t\t},\n\t}\n\tprintln(canvas)\n}\n\n// Output:\n// \u003csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"500\" height=\"500\"\u003e\u003crect x=\"50\" y=\"50\" width=\"100\" height=\"100\" fill=\"red\" /\u003e\u003ccircle cx=\"50\" cy=\"50\" r=\"100\" fill=\"red\" /\u003e\u003ctext x=\"100\" y=\"100\" fill=\"magenta\"\u003ehello world!\u003c/text\u003e\u003c/svg\u003e\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":"tamagotchi","path":"gno.land/p/demo/tamagotchi","files":[{"name":"tamagotchi.gno","body":"package tamagotchi\n\nimport (\n\t\"time\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Tamagotchi structure\ntype Tamagotchi struct {\n\tname string\n\thunger int\n\thappiness int\n\thealth int\n\tage int\n\tmaxAge int\n\tsleepy int\n\tcreated time.Time\n\tlastUpdated time.Time\n}\n\nfunc New(name string) *Tamagotchi {\n\tnow := time.Now()\n\treturn \u0026Tamagotchi{\n\t\tname: name,\n\t\thunger: 50,\n\t\thappiness: 50,\n\t\thealth: 50,\n\t\tmaxAge: 100,\n\t\tlastUpdated: now,\n\t\tcreated: now,\n\t}\n}\n\nfunc (t *Tamagotchi) Name() string {\n\tt.update()\n\treturn t.name\n}\n\nfunc (t *Tamagotchi) Hunger() int {\n\tt.update()\n\treturn t.hunger\n}\n\nfunc (t *Tamagotchi) Happiness() int {\n\tt.update()\n\treturn t.happiness\n}\n\nfunc (t *Tamagotchi) Health() int {\n\tt.update()\n\treturn t.health\n}\n\nfunc (t *Tamagotchi) Age() int {\n\tt.update()\n\treturn t.age\n}\n\nfunc (t *Tamagotchi) Sleepy() int {\n\tt.update()\n\treturn t.sleepy\n}\n\n// Feed method for Tamagotchi\nfunc (t *Tamagotchi) Feed() {\n\tt.update()\n\tif t.dead() {\n\t\treturn\n\t}\n\tt.hunger = bound(t.hunger-10, 0, 100)\n}\n\n// Play method for Tamagotchi\nfunc (t *Tamagotchi) Play() {\n\tt.update()\n\tif t.dead() {\n\t\treturn\n\t}\n\tt.happiness = bound(t.happiness+10, 0, 100)\n}\n\n// Heal method for Tamagotchi\nfunc (t *Tamagotchi) Heal() {\n\tt.update()\n\n\tif t.dead() {\n\t\treturn\n\t}\n\tt.health = bound(t.health+10, 0, 100)\n}\n\nfunc (t Tamagotchi) dead() bool { return t.health == 0 }\n\n// Update applies changes based on the duration since the last update\nfunc (t *Tamagotchi) update() {\n\tif t.dead() {\n\t\treturn\n\t}\n\n\tnow := time.Now()\n\tif t.lastUpdated == now {\n\t\treturn\n\t}\n\n\tduration := now.Sub(t.lastUpdated)\n\telapsedMins := int(duration.Minutes())\n\n\tt.hunger = bound(t.hunger+elapsedMins, 0, 100)\n\tt.happiness = bound(t.happiness-elapsedMins, 0, 100)\n\tt.health = bound(t.health-elapsedMins, 0, 100)\n\tt.sleepy = bound(t.sleepy+elapsedMins, 0, 100)\n\n\t// age is hours since created\n\tt.age = int(now.Sub(t.created).Hours())\n\tif t.age \u003e t.maxAge {\n\t\tt.age = t.maxAge\n\t\tt.health = 0\n\t}\n\tif t.health == 0 {\n\t\tt.sleepy = 0\n\t\tt.happiness = 0\n\t\tt.hunger = 0\n\t}\n\n\tt.lastUpdated = now\n}\n\n// Face returns an ASCII art representation of the Tamagotchi's current state\nfunc (t *Tamagotchi) Face() string {\n\tt.update()\n\treturn t.face()\n}\n\nfunc (t *Tamagotchi) face() string {\n\tswitch {\n\tcase t.health == 0:\n\t\treturn \"😵\" // dead face\n\tcase t.health \u003c 30:\n\t\treturn \"😷\" // sick face\n\tcase t.happiness \u003c 30:\n\t\treturn \"😢\" // sad face\n\tcase t.hunger \u003e 70:\n\t\treturn \"😫\" // hungry face\n\tcase t.sleepy \u003e 70:\n\t\treturn \"😴\" // sleepy face\n\tdefault:\n\t\treturn \"😃\" // happy face\n\t}\n}\n\n// Markdown method for Tamagotchi\nfunc (t *Tamagotchi) Markdown() string {\n\tt.update()\n\treturn ufmt.Sprintf(`# %s %s\n\n* age: %d\n* hunger: %d\n* happiness: %d\n* health: %d\n* sleepy: %d`,\n\t\tt.name, t.Face(),\n\t\tt.age, t.hunger, t.happiness, t.health, t.sleepy,\n\t)\n}\n\nfunc bound(n, min, max int) int {\n\tif n \u003c min {\n\t\treturn min\n\t}\n\tif n \u003e max {\n\t\treturn max\n\t}\n\treturn n\n}\n"},{"name":"z0_filetest.gno","body":"package main\n\nimport (\n\t\"time\"\n\n\t\"internal/os_test\"\n\n\t\"gno.land/p/demo/tamagotchi\"\n)\n\nfunc main() {\n\tt := tamagotchi.New(\"Gnome\")\n\n\tprintln(\"\\n-- INITIAL\\n\")\n\tprintln(t.Markdown())\n\n\tprintln(\"\\n-- WAIT 20 minutes\\n\")\n\tos_test.Sleep(20 * time.Minute)\n\tprintln(t.Markdown())\n\n\tprintln(\"\\n-- FEEDx3, PLAYx2, HEALx4\\n\")\n\tt.Feed()\n\tt.Feed()\n\tt.Feed()\n\tt.Play()\n\tt.Play()\n\tt.Heal()\n\tt.Heal()\n\tt.Heal()\n\tt.Heal()\n\tprintln(t.Markdown())\n\n\tprintln(\"\\n-- WAIT 20 minutes\\n\")\n\tos_test.Sleep(20 * time.Minute)\n\tprintln(t.Markdown())\n\n\tprintln(\"\\n-- WAIT 20 hours\\n\")\n\tos_test.Sleep(20 * time.Hour)\n\tprintln(t.Markdown())\n\n\tprintln(\"\\n-- WAIT 20 hours\\n\")\n\tos_test.Sleep(20 * time.Hour)\n\tprintln(t.Markdown())\n}\n\n// Output:\n// -- INITIAL\n//\n// # Gnome 😃\n//\n// * age: 0\n// * hunger: 50\n// * happiness: 50\n// * health: 50\n// * sleepy: 0\n//\n// -- WAIT 20 minutes\n//\n// # Gnome 😃\n//\n// * age: 0\n// * hunger: 70\n// * happiness: 30\n// * health: 30\n// * sleepy: 20\n//\n// -- FEEDx3, PLAYx2, HEALx4\n//\n// # Gnome 😃\n//\n// * age: 0\n// * hunger: 40\n// * happiness: 50\n// * health: 70\n// * sleepy: 20\n//\n// -- WAIT 20 minutes\n//\n// # Gnome 😃\n//\n// * age: 0\n// * hunger: 60\n// * happiness: 30\n// * health: 50\n// * sleepy: 40\n//\n// -- WAIT 20 hours\n//\n// # Gnome 😵\n//\n// * age: 20\n// * hunger: 0\n// * happiness: 0\n// * health: 0\n// * sleepy: 0\n//\n// -- WAIT 20 hours\n//\n// # Gnome 😵\n//\n// * age: 20\n// * hunger: 0\n// * happiness: 0\n// * health: 0\n// * sleepy: 0\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":"subtests","path":"gno.land/p/demo/tests/subtests","files":[{"name":"subtests.gno","body":"package subtests\n\nimport (\n\t\"std\"\n)\n\nfunc GetCurrentRealm() std.Realm {\n\treturn std.CurrentRealm()\n}\n\nfunc GetPrevRealm() std.Realm {\n\treturn std.PrevRealm()\n}\n\nfunc Exec(fn func()) {\n\tfn()\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":"subtests","path":"gno.land/r/demo/tests/subtests","files":[{"name":"subtests.gno","body":"package subtests\n\nimport (\n\t\"std\"\n)\n\nfunc GetCurrentRealm() std.Realm {\n\treturn std.CurrentRealm()\n}\n\nfunc GetPrevRealm() std.Realm {\n\treturn std.PrevRealm()\n}\n\nfunc Exec(fn func()) {\n\tfn()\n}\n\nfunc CallAssertOriginCall() {\n\tstd.AssertOriginCall()\n}\n\nfunc CallIsOriginCall() bool {\n\treturn std.IsOriginCall()\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.OrigCaller()\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\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":"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 initOriginCaller = std.OriginCaller()\n\nfunc InitOriginCaller() std.Address {\n\treturn initOriginCaller\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\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.TestSetOriginCaller(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.TestSetOriginCaller(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":"tests","path":"gno.land/p/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":"tests.gno","body":"package tests\n\nimport (\n\t\"std\"\n\n\tpsubtests \"gno.land/p/demo/tests/subtests\"\n\t\"gno.land/r/demo/tests\"\n\trtests \"gno.land/r/demo/tests\"\n)\n\nconst World = \"world\"\n\n// IncCounter demonstrates that it's possible to call a realm function from\n// a package. So a package can potentially write into the store, by calling\n// an other realm.\nfunc IncCounter() {\n\ttests.IncCounter()\n}\n\nfunc CurrentRealmPath() string {\n\treturn std.CurrentRealm().PkgPath()\n}\n\n//----------------------------------------\n// cross realm test vars\n\ntype TestRealmObject2 struct {\n\tField string\n}\n\nfunc (o2 *TestRealmObject2) Modify() {\n\to2.Field = \"modified\"\n}\n\nvar (\n\tsomevalue1 TestRealmObject2\n\tSomeValue2 TestRealmObject2\n\tSomeValue3 *TestRealmObject2\n)\n\nfunc init() {\n\tsomevalue1 = TestRealmObject2{Field: \"init\"}\n\tSomeValue2 = TestRealmObject2{Field: \"init\"}\n\tSomeValue3 = \u0026TestRealmObject2{Field: \"init\"}\n}\n\nfunc ModifyTestRealmObject2a() {\n\tsomevalue1.Field = \"modified\"\n}\n\nfunc ModifyTestRealmObject2b() {\n\tSomeValue2.Field = \"modified\"\n}\n\nfunc ModifyTestRealmObject2c() {\n\tSomeValue3.Field = \"modified\"\n}\n\nfunc GetPrevRealm() std.Realm {\n\treturn std.PrevRealm()\n}\n\nfunc GetPSubtestsPrevRealm() std.Realm {\n\treturn psubtests.GetPrevRealm()\n}\n\nfunc GetRTestsGetPrevRealm() std.Realm {\n\treturn rtests.GetPrevRealm()\n}\n\n// Warning: unsafe pattern.\nfunc Exec(fn func()) {\n\tfn()\n}\n"},{"name":"tests_test.gno","body":"package tests_test\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/tests\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar World = \"WORLD\"\n\nfunc TestGetHelloWorld(t *testing.T) {\n\t// tests.World is 'world'\n\ts := \"hello \" + tests.World + World\n\tconst want = \"hello worldWORLD\"\n\n\tuassert.Equal(t, want, s)\n}\n"},{"name":"z0_filetest.gno","body":"package main\n\nimport (\n\tptests \"gno.land/p/demo/tests\"\n\trtests \"gno.land/r/demo/tests\"\n)\n\nfunc main() {\n\tprintln(rtests.Counter())\n\tptests.IncCounter()\n\tprintln(rtests.Counter())\n}\n\n// Output:\n// 0\n// 1\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":"p_crossrealm","path":"gno.land/p/demo/tests/p_crossrealm","files":[{"name":"p_crossrealm.gno","body":"package p_crossrealm\n\ntype Stringer interface {\n\tString() string\n}\n\ntype Container struct {\n\tA int\n\tB Stringer\n}\n\nfunc (c *Container) Touch() *Container {\n\tc.A += 1\n\treturn c\n}\n\nfunc (c *Container) Print() {\n\tprintln(\"A:\", c.A)\n\tif c.B == nil {\n\t\tprintln(\"B: undefined\")\n\t} else {\n\t\tprintln(\"B:\", c.B.String())\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":"todolist","path":"gno.land/p/demo/todolist","files":[{"name":"todolist.gno","body":"package todolist\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\ntype TodoList struct {\n\tTitle string\n\tTasks *avl.Tree\n\tOwner std.Address\n}\n\ntype Task struct {\n\tTitle string\n\tDone bool\n}\n\nfunc NewTodoList(title string) *TodoList {\n\treturn \u0026TodoList{\n\t\tTitle: title,\n\t\tTasks: avl.NewTree(),\n\t\tOwner: std.OrigCaller(),\n\t}\n}\n\nfunc NewTask(title string) *Task {\n\treturn \u0026Task{\n\t\tTitle: title,\n\t\tDone: false,\n\t}\n}\n\nfunc (tl *TodoList) AddTask(id int, task *Task) {\n\ttl.Tasks.Set(strconv.Itoa(id), task)\n}\n\nfunc ToggleTaskStatus(task *Task) {\n\ttask.Done = !task.Done\n}\n\nfunc (tl *TodoList) RemoveTask(taskId string) {\n\ttl.Tasks.Remove(taskId)\n}\n\nfunc (tl *TodoList) GetTasks() []*Task {\n\ttasks := make([]*Task, 0, tl.Tasks.Size())\n\ttl.Tasks.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\ttasks = append(tasks, value.(*Task))\n\t\treturn false\n\t})\n\treturn tasks\n}\n\nfunc (tl *TodoList) GetTodolistOwner() std.Address {\n\treturn tl.Owner\n}\n\nfunc (tl *TodoList) GetTodolistTitle() string {\n\treturn tl.Title\n}\n"},{"name":"todolist_test.gno","body":"package todolist\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestNewTodoList(t *testing.T) {\n\ttitle := \"My Todo List\"\n\ttodoList := NewTodoList(title)\n\n\tuassert.Equal(t, title, todoList.GetTodolistTitle())\n\tuassert.Equal(t, 0, len(todoList.GetTasks()))\n\tuassert.Equal(t, std.OrigCaller().String(), todoList.GetTodolistOwner().String())\n}\n\nfunc TestNewTask(t *testing.T) {\n\ttitle := \"My Task\"\n\ttask := NewTask(title)\n\n\tuassert.Equal(t, title, task.Title)\n\tuassert.False(t, task.Done, \"Expected task to be not done, but it is done\")\n}\n\nfunc TestAddTask(t *testing.T) {\n\ttodoList := NewTodoList(\"My Todo List\")\n\ttask := NewTask(\"My Task\")\n\n\ttodoList.AddTask(1, task)\n\n\ttasks := todoList.GetTasks()\n\n\tuassert.Equal(t, 1, len(tasks))\n\tuassert.True(t, tasks[0] == task, \"Task does not match\")\n}\n\nfunc TestToggleTaskStatus(t *testing.T) {\n\ttask := NewTask(\"My Task\")\n\n\tToggleTaskStatus(task)\n\tuassert.True(t, task.Done, \"Expected task to be done, but it is not done\")\n\n\tToggleTaskStatus(task)\n\tuassert.False(t, task.Done, \"Expected task to be done, but it is not done\")\n}\n\nfunc TestRemoveTask(t *testing.T) {\n\ttodoList := NewTodoList(\"My Todo List\")\n\ttask := NewTask(\"My Task\")\n\ttodoList.AddTask(1, task)\n\n\ttodoList.RemoveTask(\"1\")\n\n\ttasks := todoList.GetTasks()\n\tuassert.Equal(t, 0, len(tasks))\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":"todolist","path":"gno.land/p/demo/todolist","files":[{"name":"todolist.gno","body":"package todolist\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\ntype TodoList struct {\n\tTitle string\n\tTasks *avl.Tree\n\tOwner std.Address\n}\n\ntype Task struct {\n\tTitle string\n\tDone bool\n}\n\nfunc NewTodoList(title string) *TodoList {\n\treturn \u0026TodoList{\n\t\tTitle: title,\n\t\tTasks: avl.NewTree(),\n\t\tOwner: std.OriginCaller(),\n\t}\n}\n\nfunc NewTask(title string) *Task {\n\treturn \u0026Task{\n\t\tTitle: title,\n\t\tDone: false,\n\t}\n}\n\nfunc (tl *TodoList) AddTask(id int, task *Task) {\n\ttl.Tasks.Set(strconv.Itoa(id), task)\n}\n\nfunc ToggleTaskStatus(task *Task) {\n\ttask.Done = !task.Done\n}\n\nfunc (tl *TodoList) RemoveTask(taskId string) {\n\ttl.Tasks.Remove(taskId)\n}\n\nfunc (tl *TodoList) GetTasks() []*Task {\n\ttasks := make([]*Task, 0, tl.Tasks.Size())\n\ttl.Tasks.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\ttasks = append(tasks, value.(*Task))\n\t\treturn false\n\t})\n\treturn tasks\n}\n\nfunc (tl *TodoList) GetTodolistOwner() std.Address {\n\treturn tl.Owner\n}\n\nfunc (tl *TodoList) GetTodolistTitle() string {\n\treturn tl.Title\n}\n"},{"name":"todolist_test.gno","body":"package todolist\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestNewTodoList(t *testing.T) {\n\ttitle := \"My Todo List\"\n\ttodoList := NewTodoList(title)\n\n\tuassert.Equal(t, title, todoList.GetTodolistTitle())\n\tuassert.Equal(t, 0, len(todoList.GetTasks()))\n\tuassert.Equal(t, std.OriginCaller().String(), todoList.GetTodolistOwner().String())\n}\n\nfunc TestNewTask(t *testing.T) {\n\ttitle := \"My Task\"\n\ttask := NewTask(title)\n\n\tuassert.Equal(t, title, task.Title)\n\tuassert.False(t, task.Done, \"Expected task to be not done, but it is done\")\n}\n\nfunc TestAddTask(t *testing.T) {\n\ttodoList := NewTodoList(\"My Todo List\")\n\ttask := NewTask(\"My Task\")\n\n\ttodoList.AddTask(1, task)\n\n\ttasks := todoList.GetTasks()\n\n\tuassert.Equal(t, 1, len(tasks))\n\tuassert.True(t, tasks[0] == task, \"Task does not match\")\n}\n\nfunc TestToggleTaskStatus(t *testing.T) {\n\ttask := NewTask(\"My Task\")\n\n\tToggleTaskStatus(task)\n\tuassert.True(t, task.Done, \"Expected task to be done, but it is not done\")\n\n\tToggleTaskStatus(task)\n\tuassert.False(t, task.Done, \"Expected task to be done, but it is not done\")\n}\n\nfunc TestRemoveTask(t *testing.T) {\n\ttodoList := NewTodoList(\"My Todo List\")\n\ttask := NewTask(\"My Task\")\n\ttodoList.AddTask(1, task)\n\n\ttodoList.RemoveTask(\"1\")\n\n\ttasks := todoList.GetTasks()\n\tuassert.Equal(t, 0, len(tasks))\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":"ui","path":"gno.land/p/demo/ui","files":[{"name":"ui.gno","body":"package ui\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n)\n\ntype DOM struct {\n\t// metadata\n\tPrefix string\n\tTitle string\n\tWithComments bool\n\tClasses []string\n\n\t// elements\n\tHeader Element\n\tBody Element\n\tFooter Element\n}\n\nfunc (dom DOM) String() string {\n\tclasses := strings.Join(dom.Classes, \" \")\n\n\toutput := \"\"\n\n\tif classes != \"\" {\n\t\toutput += \"\u003cmain class='\" + classes + \"'\u003e\" + \"\\n\\n\"\n\t}\n\n\tif dom.Title != \"\" {\n\t\toutput += H1(dom.Title).String(dom) + \"\\n\"\n\t}\n\n\tif header := dom.Header.String(dom); header != \"\" {\n\t\tif dom.WithComments {\n\t\t\toutput += \"\u003c!-- header --\u003e\"\n\t\t}\n\t\toutput += header + \"\\n\"\n\t\tif dom.WithComments {\n\t\t\toutput += \"\u003c!-- /header --\u003e\"\n\t\t}\n\t}\n\n\tif body := dom.Body.String(dom); body != \"\" {\n\t\tif dom.WithComments {\n\t\t\toutput += \"\u003c!-- body --\u003e\"\n\t\t}\n\t\toutput += body + \"\\n\"\n\t\tif dom.WithComments {\n\t\t\toutput += \"\u003c!-- /body --\u003e\"\n\t\t}\n\t}\n\n\tif footer := dom.Footer.String(dom); footer != \"\" {\n\t\tif dom.WithComments {\n\t\t\toutput += \"\u003c!-- footer --\u003e\"\n\t\t}\n\t\toutput += footer + \"\\n\"\n\t\tif dom.WithComments {\n\t\t\toutput += \"\u003c!-- /footer --\u003e\"\n\t\t}\n\t}\n\n\tif classes != \"\" {\n\t\toutput += \"\u003c/main\u003e\"\n\t}\n\n\t// TODO: cleanup double new-lines.\n\n\treturn output\n}\n\ntype Jumbotron []DomStringer\n\nfunc (j Jumbotron) String(dom DOM) string {\n\toutput := `\u003cdiv class=\"jumbotron\"\u003e` + \"\\n\\n\"\n\tfor _, elem := range j {\n\t\toutput += elem.String(dom) + \"\\n\"\n\t}\n\toutput += `\u003c/div\u003e\u003c!-- /jumbotron --\u003e` + \"\\n\"\n\treturn output\n}\n\n// XXX: rename Element to Div?\ntype Element []DomStringer\n\nfunc (e *Element) Append(elems ...DomStringer) {\n\t*e = append(*e, elems...)\n}\n\nfunc (e *Element) String(dom DOM) string {\n\toutput := \"\"\n\tfor _, elem := range *e {\n\t\toutput += elem.String(dom) + \"\\n\"\n\t}\n\treturn output\n}\n\ntype Breadcrumb []DomStringer\n\nfunc (b *Breadcrumb) Append(elems ...DomStringer) {\n\t*b = append(*b, elems...)\n}\n\nfunc (b Breadcrumb) String(dom DOM) string {\n\toutput := \"\"\n\tfor idx, entry := range b {\n\t\tif idx \u003e 0 {\n\t\t\toutput += \" / \"\n\t\t}\n\t\toutput += entry.String(dom)\n\t}\n\treturn output\n}\n\ntype Columns struct {\n\tMaxWidth int\n\tColumns []Element\n}\n\nfunc (c *Columns) Append(elems ...Element) {\n\tc.Columns = append(c.Columns, elems...)\n}\n\nfunc (c Columns) String(dom DOM) string {\n\toutput := `\u003cdiv class=\"columns-` + strconv.Itoa(c.MaxWidth) + `\"\u003e` + \"\\n\"\n\tfor _, entry := range c.Columns {\n\t\toutput += `\u003cdiv class=\"column\"\u003e` + \"\\n\\n\"\n\t\toutput += entry.String(dom)\n\t\toutput += \"\u003c/div\u003e\u003c!-- /column--\u003e\\n\"\n\t}\n\toutput += \"\u003c/div\u003e\u003c!-- /columns-\" + strconv.Itoa(c.MaxWidth) + \" --\u003e\\n\"\n\treturn output\n}\n\ntype Link struct {\n\tText string\n\tPath string\n\tURL string\n}\n\n// TODO: image\n\n// TODO: pager\n\nfunc (l Link) String(dom DOM) string {\n\turl := \"\"\n\tswitch {\n\tcase l.Path != \"\" \u0026\u0026 l.URL != \"\":\n\t\tpanic(\"a link should have a path or a URL, not both.\")\n\tcase l.Path != \"\":\n\t\tif l.Text == \"\" {\n\t\t\tl.Text = l.Path\n\t\t}\n\t\turl = dom.Prefix + l.Path\n\tcase l.URL != \"\":\n\t\tif l.Text == \"\" {\n\t\t\tl.Text = l.URL\n\t\t}\n\t\turl = l.URL\n\t}\n\n\treturn \"[\" + l.Text + \"](\" + url + \")\"\n}\n\ntype BulletList []DomStringer\n\nfunc (bl BulletList) String(dom DOM) string {\n\toutput := \"\"\n\n\tfor _, entry := range bl {\n\t\toutput += \"- \" + entry.String(dom) + \"\\n\"\n\t}\n\n\treturn output\n}\n\nfunc Text(s string) DomStringer {\n\treturn Raw{Content: s}\n}\n\ntype DomStringer interface {\n\tString(dom DOM) string\n}\n\ntype Raw struct {\n\tContent string\n}\n\nfunc (r Raw) String(_ DOM) string {\n\treturn r.Content\n}\n\ntype (\n\tH1 string\n\tH2 string\n\tH3 string\n\tH4 string\n\tH5 string\n\tH6 string\n\tBold string\n\tItalic string\n\tCode string\n\tParagraph string\n\tQuote string\n\tHR struct{}\n)\n\nfunc (text H1) String(_ DOM) string { return \"# \" + string(text) + \"\\n\" }\nfunc (text H2) String(_ DOM) string { return \"## \" + string(text) + \"\\n\" }\nfunc (text H3) String(_ DOM) string { return \"### \" + string(text) + \"\\n\" }\nfunc (text H4) String(_ DOM) string { return \"#### \" + string(text) + \"\\n\" }\nfunc (text H5) String(_ DOM) string { return \"##### \" + string(text) + \"\\n\" }\nfunc (text H6) String(_ DOM) string { return \"###### \" + string(text) + \"\\n\" }\nfunc (text Quote) String(_ DOM) string { return \"\u003e \" + string(text) + \"\\n\" }\nfunc (text Bold) String(_ DOM) string { return \"**\" + string(text) + \"**\" }\nfunc (text Italic) String(_ DOM) string { return \"_\" + string(text) + \"_\" }\nfunc (text Paragraph) String(_ DOM) string { return \"\\n\" + string(text) + \"\\n\" }\nfunc (_ HR) String(_ DOM) string { return \"\\n---\\n\" }\n\nfunc (text Code) String(_ DOM) string {\n\t// multiline\n\tif strings.Contains(string(text), \"\\n\") {\n\t\treturn \"\\n```\\n\" + string(text) + \"\\n```\\n\"\n\t}\n\n\t// single line\n\treturn \"`\" + string(text) + \"`\"\n}\n"},{"name":"ui_test.gno","body":"package ui\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":"watchdog","path":"gno.land/p/demo/watchdog","files":[{"name":"watchdog.gno","body":"package watchdog\n\nimport \"time\"\n\ntype Watchdog struct {\n\tDuration time.Duration\n\tlastUpdate time.Time\n\tlastDown time.Time\n}\n\nfunc (w *Watchdog) Alive() {\n\tnow := time.Now()\n\tif !w.IsAlive() {\n\t\tw.lastDown = now\n\t}\n\tw.lastUpdate = now\n}\n\nfunc (w Watchdog) Status() string {\n\tif w.IsAlive() {\n\t\treturn \"OK\"\n\t}\n\treturn \"KO\"\n}\n\nfunc (w Watchdog) IsAlive() bool {\n\treturn time.Since(w.lastUpdate) \u003c w.Duration\n}\n\nfunc (w Watchdog) UpSince() time.Time {\n\treturn w.lastDown\n}\n\nfunc (w Watchdog) DownSince() time.Time {\n\tif !w.IsAlive() {\n\t\treturn w.lastUpdate\n\t}\n\treturn time.Time{}\n}\n"},{"name":"watchdog_test.gno","body":"package watchdog\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestPackage(t *testing.T) {\n\tw := Watchdog{Duration: 5 * time.Minute}\n\tuassert.False(t, w.IsAlive())\n\tw.Alive()\n\tuassert.True(t, w.IsAlive())\n\t// XXX: add more tests when we'll be able to \"skip time\".\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":"executor","path":"gno.land/p/gov/executor","files":[{"name":"callback.gno","body":"package executor\n\nimport (\n\t\"errors\"\n\t\"std\"\n)\n\nvar errInvalidCaller = errors.New(\"invalid executor caller\")\n\n// NewCallbackExecutor creates a new callback executor with the provided callback function\nfunc NewCallbackExecutor(callback func() error, path string) *CallbackExecutor {\n\treturn \u0026CallbackExecutor{\n\t\tcallback: callback,\n\t\tdaoPkgPath: path,\n\t}\n}\n\n// CallbackExecutor is an implementation of the dao.Executor interface,\n// based on a specific callback.\n// The given callback should verify the validity of the govdao call\ntype CallbackExecutor struct {\n\tcallback func() error // the callback to be executed\n\tdaoPkgPath string // the active pkg path of the govdao\n}\n\n// Execute runs the executor's callback function.\nfunc (exec *CallbackExecutor) Execute() error {\n\t// Verify the caller is an adequate Realm\n\tcaller := std.CurrentRealm().PkgPath()\n\tif caller != exec.daoPkgPath {\n\t\treturn errInvalidCaller\n\t}\n\n\tif exec.callback != nil {\n\t\treturn exec.callback()\n\t}\n\n\treturn nil\n}\n"},{"name":"context.gno","body":"package executor\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/context\"\n)\n\ntype propContextKey string\n\nfunc (k propContextKey) String() string { return string(k) }\n\nconst (\n\tstatusContextKey = propContextKey(\"govdao-prop-status\")\n\tapprovedStatus = \"approved\"\n)\n\nvar errNotApproved = errors.New(\"not approved by govdao\")\n\n// CtxExecutor is an implementation of the dao.Executor interface,\n// based on the given context.\n// It utilizes the given context to assert the validity of the govdao call\ntype CtxExecutor struct {\n\tcallbackCtx func(ctx context.Context) error // the callback ctx fn, if any\n\tdaoPkgPath string // the active pkg path of the govdao\n}\n\n// NewCtxExecutor creates a new executor with the provided callback function.\nfunc NewCtxExecutor(callback func(ctx context.Context) error, path string) *CtxExecutor {\n\treturn \u0026CtxExecutor{\n\t\tcallbackCtx: callback,\n\t\tdaoPkgPath: path,\n\t}\n}\n\n// Execute runs the executor's callback function\nfunc (exec *CtxExecutor) Execute() error {\n\t// Verify the caller is an adequate Realm\n\tcaller := std.CurrentRealm().PkgPath()\n\tif caller != exec.daoPkgPath {\n\t\treturn errInvalidCaller\n\t}\n\n\t// Create the context\n\tctx := context.WithValue(\n\t\tcontext.Empty(),\n\t\tstatusContextKey,\n\t\tapprovedStatus,\n\t)\n\n\treturn exec.callbackCtx(ctx)\n}\n\n// IsApprovedByGovdaoContext asserts that the govdao approved the context\nfunc IsApprovedByGovdaoContext(ctx context.Context) bool {\n\tv := ctx.Value(statusContextKey)\n\tif v == nil {\n\t\treturn false\n\t}\n\n\tvs, ok := v.(string)\n\n\treturn ok \u0026\u0026 vs == approvedStatus\n}\n\n// AssertContextApprovedByGovDAO asserts the given context\n// was approved by GOVDAO\nfunc AssertContextApprovedByGovDAO(ctx context.Context) {\n\tif IsApprovedByGovdaoContext(ctx) {\n\t\treturn\n\t}\n\n\tpanic(errNotApproved)\n}\n"},{"name":"proposal_test.gno","body":"package executor\n\nimport (\n\t\"errors\"\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/context\"\n\t\"gno.land/p/demo/uassert\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestExecutor_Callback(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"govdao not caller\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tcalled = false\n\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)\n\n\t\t// Create the executor\n\t\te := NewCallbackExecutor(cb, \"gno.land/r/gov/dao\")\n\n\t\t// Execute as not the /r/gov/dao caller\n\t\tuassert.ErrorIs(t, e.Execute(), errInvalidCaller)\n\t\tuassert.False(t, called, \"expected proposal to not execute\")\n\t})\n\n\tt.Run(\"execution successful\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tcalled = false\n\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)\n\n\t\t// Create the executor\n\t\tdaoPkgPath := \"gno.land/r/gov/dao\"\n\t\te := NewCallbackExecutor(cb, daoPkgPath)\n\n\t\t// Execute as the /r/gov/dao caller\n\t\tr := std.NewCodeRealm(daoPkgPath)\n\t\tstd.TestSetRealm(r)\n\n\t\tuassert.NoError(t, e.Execute())\n\t\tuassert.True(t, called, \"expected proposal to execute\")\n\t})\n\n\tt.Run(\"execution unsuccessful\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tcalled = false\n\t\t\texpectedErr = errors.New(\"unexpected\")\n\n\t\t\tcb = func() error {\n\t\t\t\tcalled = true\n\n\t\t\t\treturn expectedErr\n\t\t\t}\n\t\t)\n\n\t\t// Create the executor\n\t\tdaoPkgPath := \"gno.land/r/gov/dao\"\n\t\te := NewCallbackExecutor(cb, daoPkgPath)\n\n\t\t// Execute as the /r/gov/dao caller\n\t\tr := std.NewCodeRealm(daoPkgPath)\n\t\tstd.TestSetRealm(r)\n\n\t\tuassert.ErrorIs(t, e.Execute(), expectedErr)\n\t\tuassert.True(t, called, \"expected proposal to execute\")\n\t})\n}\n\nfunc TestExecutor_Context(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"govdao not caller\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tcalled = false\n\n\t\t\tcb = func(ctx context.Context) error {\n\t\t\t\tif !IsApprovedByGovdaoContext(ctx) {\n\t\t\t\t\tt.Fatal(\"not govdao caller\")\n\t\t\t\t}\n\n\t\t\t\tcalled = true\n\n\t\t\t\treturn nil\n\t\t\t}\n\t\t)\n\n\t\t// Create the executor\n\t\te := NewCtxExecutor(cb, \"gno.land/r/gov/dao\")\n\n\t\t// Execute as not the /r/gov/dao caller\n\t\tuassert.ErrorIs(t, e.Execute(), errInvalidCaller)\n\t\tuassert.False(t, called, \"expected proposal to not execute\")\n\t})\n\n\tt.Run(\"execution successful\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tcalled = false\n\n\t\t\tcb = func(ctx context.Context) error {\n\t\t\t\tif !IsApprovedByGovdaoContext(ctx) {\n\t\t\t\t\tt.Fatal(\"not govdao caller\")\n\t\t\t\t}\n\n\t\t\t\tcalled = true\n\n\t\t\t\treturn nil\n\t\t\t}\n\t\t)\n\n\t\t// Create the executor\n\t\tdaoPkgPath := \"gno.land/r/gov/dao\"\n\t\te := NewCtxExecutor(cb, daoPkgPath)\n\n\t\t// Execute as the /r/gov/dao caller\n\t\tr := std.NewCodeRealm(daoPkgPath)\n\t\tstd.TestSetRealm(r)\n\n\t\turequire.NoError(t, e.Execute())\n\t\tuassert.True(t, called, \"expected proposal to execute\")\n\t})\n\n\tt.Run(\"execution unsuccessful\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tcalled = false\n\t\t\texpectedErr = errors.New(\"unexpected\")\n\n\t\t\tcb = func(ctx context.Context) error {\n\t\t\t\tif !IsApprovedByGovdaoContext(ctx) {\n\t\t\t\t\tt.Fatal(\"not govdao caller\")\n\t\t\t\t}\n\n\t\t\t\tcalled = true\n\n\t\t\t\treturn expectedErr\n\t\t\t}\n\t\t)\n\n\t\t// Create the executor\n\t\tdaoPkgPath := \"gno.land/r/gov/dao\"\n\t\te := NewCtxExecutor(cb, daoPkgPath)\n\n\t\t// Execute as the /r/gov/dao caller\n\t\tr := std.NewCodeRealm(daoPkgPath)\n\t\tstd.TestSetRealm(r)\n\n\t\tuassert.NotPanics(t, func() {\n\t\t\terr := e.Execute()\n\n\t\t\tuassert.ErrorIs(t, err, expectedErr)\n\t\t})\n\n\t\tuassert.True(t, called, \"expected proposal to execute\")\n\t})\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}} @@ -76,56 +76,56 @@ {"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"poa","path":"gno.land/p/nt/poa","files":[{"name":"option.gno","body":"package poa\n\nimport \"gno.land/p/sys/validators\"\n\ntype Option func(*PoA)\n\n// WithInitialSet sets the initial PoA validator set\nfunc WithInitialSet(validators []validators.Validator) Option {\n\treturn func(p *PoA) {\n\t\tfor _, validator := range validators {\n\t\t\tp.validators.Set(validator.Address.String(), validator)\n\t\t}\n\t}\n}\n"},{"name":"poa.gno","body":"package poa\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/sys/validators\"\n)\n\nvar ErrInvalidVotingPower = errors.New(\"invalid voting power\")\n\n// PoA specifies the Proof of Authority validator set, with simple add / remove constraints.\n//\n// To add:\n// - proposed validator must not be part of the set already\n// - proposed validator voting power must be \u003e 0\n//\n// To remove:\n// - proposed validator must be part of the set already\ntype PoA struct {\n\tvalidators *avl.Tree // std.Address -\u003e validators.Validator\n}\n\n// NewPoA creates a new empty Proof of Authority validator set\nfunc NewPoA(opts ...Option) *PoA {\n\t// Create the empty set\n\tp := \u0026PoA{\n\t\tvalidators: avl.NewTree(),\n\t}\n\n\t// Apply the options\n\tfor _, opt := range opts {\n\t\topt(p)\n\t}\n\n\treturn p\n}\n\nfunc (p *PoA) AddValidator(address std.Address, pubKey string, power uint64) (validators.Validator, error) {\n\t// Validate that the operation is a valid call.\n\t// Check if the validator is already in the set\n\tif p.IsValidator(address) {\n\t\treturn validators.Validator{}, validators.ErrValidatorExists\n\t}\n\n\t// Make sure the voting power \u003e 0\n\tif power == 0 {\n\t\treturn validators.Validator{}, ErrInvalidVotingPower\n\t}\n\n\tv := validators.Validator{\n\t\tAddress: address,\n\t\tPubKey: pubKey, // TODO: in the future, verify the public key\n\t\tVotingPower: power,\n\t}\n\n\t// Add the validator to the set\n\tp.validators.Set(address.String(), v)\n\n\treturn v, nil\n}\n\nfunc (p *PoA) RemoveValidator(address std.Address) (validators.Validator, error) {\n\t// Validate that the operation is a valid call\n\t// Fetch the validator\n\tvalidator, err := p.GetValidator(address)\n\tif err != nil {\n\t\treturn validators.Validator{}, err\n\t}\n\n\t// Remove the validator from the set\n\tp.validators.Remove(address.String())\n\n\treturn validator, nil\n}\n\nfunc (p *PoA) IsValidator(address std.Address) bool {\n\t_, exists := p.validators.Get(address.String())\n\n\treturn exists\n}\n\nfunc (p *PoA) GetValidator(address std.Address) (validators.Validator, error) {\n\tvalidatorRaw, exists := p.validators.Get(address.String())\n\tif !exists {\n\t\treturn validators.Validator{}, validators.ErrValidatorMissing\n\t}\n\n\tvalidator := validatorRaw.(validators.Validator)\n\n\treturn validator, nil\n}\n\nfunc (p *PoA) GetValidators() []validators.Validator {\n\tvals := make([]validators.Validator, 0, p.validators.Size())\n\n\tp.validators.Iterate(\"\", \"\", func(_ string, value interface{}) bool {\n\t\tvalidator := value.(validators.Validator)\n\t\tvals = append(vals, validator)\n\n\t\treturn false\n\t})\n\n\treturn vals\n}\n"},{"name":"poa_test.gno","body":"package poa\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/urequire\"\n\t\"gno.land/p/sys/validators\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// generateTestValidators generates a dummy validator set\nfunc generateTestValidators(count int) []validators.Validator {\n\tvals := make([]validators.Validator, 0, count)\n\n\tfor i := 0; i \u003c count; i++ {\n\t\tval := validators.Validator{\n\t\t\tAddress: testutils.TestAddress(ufmt.Sprintf(\"%d\", i)),\n\t\t\tPubKey: \"public-key\",\n\t\t\tVotingPower: 1,\n\t\t}\n\n\t\tvals = append(vals, val)\n\t}\n\n\treturn vals\n}\n\nfunc TestPoA_AddValidator_Invalid(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"validator already in set\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tproposalAddress = testutils.TestAddress(\"caller\")\n\t\t\tproposalKey = \"public-key\"\n\n\t\t\tinitialSet = generateTestValidators(1)\n\t\t)\n\n\t\tinitialSet[0].Address = proposalAddress\n\t\tinitialSet[0].PubKey = proposalKey\n\n\t\t// Create the protocol with an initial set\n\t\tp := NewPoA(WithInitialSet(initialSet))\n\n\t\t// Attempt to add the validator\n\t\t_, err := p.AddValidator(proposalAddress, proposalKey, 1)\n\t\tuassert.ErrorIs(t, err, validators.ErrValidatorExists)\n\t})\n\n\tt.Run(\"invalid voting power\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tproposalAddress = testutils.TestAddress(\"caller\")\n\t\t\tproposalKey = \"public-key\"\n\t\t)\n\n\t\t// Create the protocol with no initial set\n\t\tp := NewPoA()\n\n\t\t// Attempt to add the validator\n\t\t_, err := p.AddValidator(proposalAddress, proposalKey, 0)\n\t\tuassert.ErrorIs(t, err, ErrInvalidVotingPower)\n\t})\n}\n\nfunc TestPoA_AddValidator(t *testing.T) {\n\tt.Parallel()\n\n\tvar (\n\t\tproposalAddress = testutils.TestAddress(\"caller\")\n\t\tproposalKey = \"public-key\"\n\t)\n\n\t// Create the protocol with no initial set\n\tp := NewPoA()\n\n\t// Attempt to add the validator\n\t_, err := p.AddValidator(proposalAddress, proposalKey, 1)\n\tuassert.NoError(t, err)\n\n\t// Make sure the validator is added\n\tif !p.IsValidator(proposalAddress) || p.validators.Size() != 1 {\n\t\tt.Fatal(\"address is not validator\")\n\t}\n}\n\nfunc TestPoA_RemoveValidator_Invalid(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"proposed removal not in set\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tproposalAddress = testutils.TestAddress(\"caller\")\n\t\t\tinitialSet = generateTestValidators(1)\n\t\t)\n\n\t\tinitialSet[0].Address = proposalAddress\n\n\t\t// Create the protocol with an initial set\n\t\tp := NewPoA(WithInitialSet(initialSet))\n\n\t\t// Attempt to remove the validator\n\t\t_, err := p.RemoveValidator(testutils.TestAddress(\"totally random\"))\n\t\tuassert.ErrorIs(t, err, validators.ErrValidatorMissing)\n\t})\n}\n\nfunc TestPoA_RemoveValidator(t *testing.T) {\n\tt.Parallel()\n\n\tvar (\n\t\tproposalAddress = testutils.TestAddress(\"caller\")\n\t\tinitialSet = generateTestValidators(1)\n\t)\n\n\tinitialSet[0].Address = proposalAddress\n\n\t// Create the protocol with an initial set\n\tp := NewPoA(WithInitialSet(initialSet))\n\n\t// Attempt to remove the validator\n\t_, err := p.RemoveValidator(proposalAddress)\n\turequire.NoError(t, err)\n\n\t// Make sure the validator is removed\n\tif p.IsValidator(proposalAddress) || p.validators.Size() != 0 {\n\t\tt.Fatal(\"address is validator\")\n\t}\n}\n\nfunc TestPoA_GetValidator(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"validator not in set\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Create the protocol with no initial set\n\t\tp := NewPoA()\n\n\t\t// Attempt to get the voting power\n\t\t_, err := p.GetValidator(testutils.TestAddress(\"caller\"))\n\t\tuassert.ErrorIs(t, err, validators.ErrValidatorMissing)\n\t})\n\n\tt.Run(\"validator fetched\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\taddress = testutils.TestAddress(\"caller\")\n\t\t\tpubKey = \"public-key\"\n\t\t\tvotingPower = uint64(10)\n\n\t\t\tinitialSet = generateTestValidators(1)\n\t\t)\n\n\t\tinitialSet[0].Address = address\n\t\tinitialSet[0].PubKey = pubKey\n\t\tinitialSet[0].VotingPower = votingPower\n\n\t\t// Create the protocol with an initial set\n\t\tp := NewPoA(WithInitialSet(initialSet))\n\n\t\t// Get the validator\n\t\tval, err := p.GetValidator(address)\n\t\turequire.NoError(t, err)\n\n\t\t// Validate the address\n\t\tif val.Address != address {\n\t\t\tt.Fatal(\"invalid address\")\n\t\t}\n\n\t\t// Validate the voting power\n\t\tif val.VotingPower != votingPower {\n\t\t\tt.Fatal(\"invalid voting power\")\n\t\t}\n\n\t\t// Validate the public key\n\t\tif val.PubKey != pubKey {\n\t\t\tt.Fatal(\"invalid public key\")\n\t\t}\n\t})\n}\n\nfunc TestPoA_GetValidators(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"empty set\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Create the protocol with no initial set\n\t\tp := NewPoA()\n\n\t\t// Attempt to get the voting power\n\t\tvals := p.GetValidators()\n\n\t\tif len(vals) != 0 {\n\t\t\tt.Fatal(\"validator set is not empty\")\n\t\t}\n\t})\n\n\tt.Run(\"validator set fetched\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tinitialSet := generateTestValidators(10)\n\n\t\t// Create the protocol with an initial set\n\t\tp := NewPoA(WithInitialSet(initialSet))\n\n\t\t// Get the validator set\n\t\tvals := p.GetValidators()\n\n\t\tif len(vals) != len(initialSet) {\n\t\t\tt.Fatal(\"returned validator set mismatch\")\n\t\t}\n\n\t\tfor _, val := range vals {\n\t\t\tfor _, initialVal := range initialSet {\n\t\t\t\tif val.Address != initialVal.Address {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\t// Validate the voting power\n\t\t\t\tuassert.Equal(t, val.VotingPower, initialVal.VotingPower)\n\n\t\t\t\t// Validate the public key\n\t\t\t\tuassert.Equal(t, val.PubKey, initialVal.PubKey)\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":"gnoface","path":"gno.land/r/demo/art/gnoface","files":[{"name":"gnoface.gno","body":"package gnoface\n\nimport (\n\t\"math/rand\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/entropy\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nfunc Render(path string) string {\n\tseed := uint64(entropy.New().Value())\n\n\tpath = strings.TrimSpace(path)\n\tif path != \"\" {\n\t\ts, err := strconv.Atoi(path)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tseed = uint64(s)\n\t}\n\n\toutput := ufmt.Sprintf(\"Gnoface #%d\\n\", seed)\n\toutput += \"```\\n\" + Draw(seed) + \"```\\n\"\n\treturn output\n}\n\nfunc Draw(seed uint64) string {\n\tvar (\n\t\thairs = []string{\n\t\t\t\" s\",\n\t\t\t\" .......\",\n\t\t\t\" s s s\",\n\t\t\t\" /\\\\ /\\\\\",\n\t\t\t\" |||||||\",\n\t\t}\n\t\theadtop = []string{\n\t\t\t\" /-------\\\\\",\n\t\t\t\" /~~~~~~~\\\\\",\n\t\t\t\" /|||||||\\\\\",\n\t\t\t\" ////////\\\\\",\n\t\t\t\" |||||||||\",\n\t\t\t\" /\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\",\n\t\t}\n\t\theadspace = []string{\n\t\t\t\" | |\",\n\t\t}\n\t\teyebrow = []string{\n\t\t\t\"~\",\n\t\t\t\"*\",\n\t\t\t\"_\",\n\t\t\t\".\",\n\t\t}\n\t\tear = []string{\n\t\t\t\"o\",\n\t\t\t\" \",\n\t\t\t\"D\",\n\t\t\t\"O\",\n\t\t\t\"\u003c\",\n\t\t\t\"\u003e\",\n\t\t\t\".\",\n\t\t\t\"|\",\n\t\t\t\")\",\n\t\t\t\"(\",\n\t\t}\n\t\teyesmiddle = []string{\n\t\t\t\"| o o |\",\n\t\t\t\"| o _ |\",\n\t\t\t\"| _ o |\",\n\t\t\t\"| . . |\",\n\t\t\t\"| O O |\",\n\t\t\t\"| v v |\",\n\t\t\t\"| X X |\",\n\t\t\t\"| x X |\",\n\t\t\t\"| X D |\",\n\t\t\t\"| ~ ~ |\",\n\t\t}\n\t\tnose = []string{\n\t\t\t\" | o |\",\n\t\t\t\" | O |\",\n\t\t\t\" | V |\",\n\t\t\t\" | L |\",\n\t\t\t\" | C |\",\n\t\t\t\" | ~ |\",\n\t\t\t\" | . . |\",\n\t\t\t\" | . |\",\n\t\t}\n\t\tmouth = []string{\n\t\t\t\" | __/ |\",\n\t\t\t\" | \\\\_/ |\",\n\t\t\t\" | . |\",\n\t\t\t\" | ___ |\",\n\t\t\t\" | ~~~ |\",\n\t\t\t\" | === |\",\n\t\t\t\" | \u003c=\u003e |\",\n\t\t}\n\t\theadbottom = []string{\n\t\t\t\" \\\\-------/\",\n\t\t\t\" \\\\~~~~~~~/\",\n\t\t\t\" \\\\_______/\",\n\t\t}\n\t)\n\n\tr := rand.New(rand.NewPCG(seed, 0xdeadbeef))\n\n\treturn pick(r, hairs) + \"\\n\" +\n\t\tpick(r, headtop) + \"\\n\" +\n\t\tpick(r, headspace) + \"\\n\" +\n\t\t\" | \" + pick(r, eyebrow) + \" \" + pick(r, eyebrow) + \" |\\n\" +\n\t\tpick(r, ear) + pick(r, eyesmiddle) + pick(r, ear) + \"\\n\" +\n\t\tpick(r, headspace) + \"\\n\" +\n\t\tpick(r, nose) + \"\\n\" +\n\t\tpick(r, headspace) + \"\\n\" +\n\t\tpick(r, mouth) + \"\\n\" +\n\t\tpick(r, headspace) + \"\\n\" +\n\t\tpick(r, headbottom) + \"\\n\"\n}\n\nfunc pick(r *rand.Rand, slice []string) string {\n\treturn slice[r.IntN(len(slice))]\n}\n\n// based on https://github.com/moul/pipotron/blob/master/dict/ascii-face.yml\n"},{"name":"gnoface_test.gno","body":"package gnoface\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nfunc TestDraw(t *testing.T) {\n\tcases := []struct {\n\t\tseed uint64\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tseed: 42,\n\t\t\texpected: `\n |||||||\n |||||||||\n | |\n | . ~ |\n)| v v |O\n | |\n | L |\n | |\n | ___ |\n | |\n \\~~~~~~~/\n`[1:],\n\t\t},\n\t\t{\n\t\t\tseed: 1337,\n\t\t\texpected: `\n .......\n |||||||||\n | |\n | . _ |\nD| x X |O\n | |\n | ~ |\n | |\n | ~~~ |\n | |\n \\~~~~~~~/\n`[1:],\n\t\t},\n\t\t{\n\t\t\tseed: 123456789,\n\t\t\texpected: `\n .......\n ////////\\\n | |\n | ~ * |\n|| x X |o\n | |\n | V |\n | |\n | . |\n | |\n \\-------/\n`[1:],\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tname := ufmt.Sprintf(\"%d\", tc.seed)\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tgot := Draw(tc.seed)\n\t\t\tuassert.Equal(t, string(tc.expected), got)\n\t\t})\n\t}\n}\n\nfunc TestRender(t *testing.T) {\n\tcases := []struct {\n\t\tpath string\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tpath: \"42\",\n\t\t\texpected: \"Gnoface #42\\n```\" + `\n |||||||\n |||||||||\n | |\n | . ~ |\n)| v v |O\n | |\n | L |\n | |\n | ___ |\n | |\n \\~~~~~~~/\n` + \"```\\n\",\n\t\t},\n\t\t{\n\t\t\tpath: \"1337\",\n\t\t\texpected: \"Gnoface #1337\\n```\" + `\n .......\n |||||||||\n | |\n | . _ |\nD| x X |O\n | |\n | ~ |\n | |\n | ~~~ |\n | |\n \\~~~~~~~/\n` + \"```\\n\",\n\t\t},\n\t\t{\n\t\t\tpath: \"123456789\",\n\t\t\texpected: \"Gnoface #123456789\\n```\" + `\n .......\n ////////\\\n | |\n | ~ * |\n|| x X |o\n | |\n | V |\n | |\n | . |\n | |\n \\-------/\n` + \"```\\n\",\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tt.Run(tc.path, func(t *testing.T) {\n\t\t\tgot := Render(tc.path)\n\t\t\tuassert.Equal(t, tc.expected, got)\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":"millipede","path":"gno.land/r/demo/art/millipede","files":[{"name":"millipede.gno","body":"package millipede\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\nconst (\n\tminSize = 1\n\tdefaultSize = 20\n\tmaxSize = 100\n)\n\nfunc Draw(size int) string {\n\tif size \u003c minSize || size \u003e maxSize {\n\t\tpanic(\"invalid millipede size\")\n\t}\n\tpaddings := []string{\" \", \" \", \"\", \" \", \" \", \" \", \" \", \" \", \" \"}\n\tvar b strings.Builder\n\tb.WriteString(\" ╚⊙ ⊙╝\\n\")\n\tfor i := 0; i \u003c size; i++ {\n\t\tb.WriteString(paddings[i%9] + \"╚═(███)═╝\\n\")\n\t}\n\treturn b.String()\n}\n\nfunc Render(path string) string {\n\tsize := defaultSize\n\n\tpath = strings.TrimSpace(path)\n\tif path != \"\" {\n\t\tvar err error\n\t\tsize, err = strconv.Atoi(path)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}\n\n\toutput := \"```\\n\" + Draw(size) + \"```\\n\"\n\tif size \u003e minSize {\n\t\toutput += ufmt.Sprintf(\"[%d](/r/demo/art/millipede:%d)\u003c \", size-1, size-1)\n\t}\n\tif size \u003c maxSize {\n\t\toutput += ufmt.Sprintf(\" \u003e[%d](/r/demo/art/millipede:%d)\", size+1, size+1)\n\t}\n\treturn output\n}\n\n// based on https://github.com/getmillipede/millipede-go/blob/977f046c39c35a650eac0fd30245e96b22c7803c/main.go\n"},{"name":"millipede_test.gno","body":"package millipede\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestRender(t *testing.T) {\n\tcases := []struct {\n\t\tpath string\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tpath: \"\",\n\t\t\texpected: \"```\" + `\n ╚⊙ ⊙╝\n ╚═(███)═╝\n ╚═(███)═╝\n╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n` + \"```\\n[19](/r/demo/art/millipede:19)\u003c \u003e[21](/r/demo/art/millipede:21)\",\n\t\t},\n\t\t{\n\t\t\tpath: \"4\",\n\t\t\texpected: \"```\" + `\n ╚⊙ ⊙╝\n ╚═(███)═╝\n ╚═(███)═╝\n╚═(███)═╝\n ╚═(███)═╝\n` + \"```\\n[3](/r/demo/art/millipede:3)\u003c \u003e[5](/r/demo/art/millipede:5)\",\n\t\t},\n\t}\n\n\tfor _, tc := range cases {\n\t\tt.Run(tc.path, func(t *testing.T) {\n\t\t\tgot := Render(tc.path)\n\t\t\tuassert.Equal(t, tc.expected, got)\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":"banktest","path":"gno.land/r/demo/banktest","files":[{"name":"README.md","body":"This is a simple test realm contract that demonstrates how to use the banker.\n\nSee [gno.land/r/demo/banktest/banktest.go](/r/demo/banktest/banktest.go) to see the original contract code.\n\nThis article will go through each line to explain how it works.\n\n```go\npackage banktest\n```\n\nThis package is locally named \"banktest\" (could be anything).\n\n```go\nimport (\n \"std\"\n)\n```\n\nThe \"std\" package is defined by the gno code in stdlibs/std/. \u003c/br\u003e Self explanatory; and you'll see more usage from std later.\n\n```go\ntype activity struct {\n caller std.Address\n sent std.Coins\n returned std.Coins\n time time.Time\n}\n\nfunc (act *activity) String() string {\n return act.caller.String() + \" \" +\n act.sent.String() + \" sent, \" +\n act.returned.String() + \" returned, at \" +\n act.time.Format(\"2006-01-02 3:04pm MST\")\n}\n\nvar latest [10]*activity\n```\n\nThis is just maintaining a list of recent activity to this contract. Notice that the \"latest\" variable is defined \"globally\" within the context of the realm with path \"gno.land/r/demo/banktest\".\n\nThis means that calls to functions defined within this package are encapsulated within this \"data realm\", where the data is mutated based on transactions that can potentially cross many realm and non-realm package boundaries (in the call stack).\n\n```go\n// Deposit will take the coins (to the realm's pkgaddr) or return them to user.\nfunc Deposit(returnDenom string, returnAmount int64) string {\n std.AssertOriginCall()\n caller := std.OrigCaller()\n send := std.Coins{{returnDenom, returnAmount}}\n```\n\nThis is the beginning of the definition of the contract function named \"Deposit\". `std.AssertOriginCall() asserts that this function was called by a gno transactional Message. The caller is the user who signed off on this transactional message. Send is the amount of deposit sent along with this message.\n\n```go\n // record activity\n act := \u0026activity{\n caller: caller,\n sent: std.OrigSend(),\n returned: send,\n time: time.Now(),\n }\n for i := len(latest) - 2; i \u003e= 0; i-- {\n latest[i+1] = latest[i] // shift by +1.\n }\n latest[0] = act\n```\n\nUpdating the \"latest\" array for viewing at gno.land/r/demo/banktest: (w/ trailing colon).\n\n```go\n // return if any.\n if returnAmount \u003e 0 {\n```\n\nIf the user requested the return of coins...\n\n```go\n banker := std.GetBanker(std.BankerTypeOrigSend)\n```\n\nuse a std.Banker instance to return any deposited coins to the original sender.\n\n```go\n pkgaddr := std.GetOrigPkgAddr()\n // TODO: use std.Coins constructors, this isn't generally safe.\n banker.SendCoins(pkgaddr, caller, send)\n return \"returned!\"\n```\n\nNotice that each realm package has an associated Cosmos address.\n\nFinally, the results are rendered via an ABCI query call when you visit [/r/demo/banktest:](/r/demo/banktest:).\n\n```go\nfunc Render(path string) string {\n // get realm coins.\n banker := std.GetBanker(std.BankerTypeReadonly)\n coins := banker.GetCoins(std.GetOrigPkgAddr())\n\n // render\n res := \"\"\n res += \"## recent activity\\n\"\n res += \"\\n\"\n for _, act := range latest {\n if act == nil {\n break\n }\n res += \" * \" + act.String() + \"\\n\"\n }\n res += \"\\n\"\n res += \"## total deposits\\n\"\n res += coins.String()\n return res\n}\n```\n\nYou can call this contract yourself, by vistiing [/r/demo/banktest](/r/demo/banktest) and the [quickstart guide](/r/demo/boards:gnolang/4).\n"},{"name":"banktest.gno","body":"package banktest\n\nimport (\n\t\"std\"\n\t\"time\"\n)\n\ntype activity struct {\n\tcaller std.Address\n\tsent std.Coins\n\treturned std.Coins\n\ttime time.Time\n}\n\nfunc (act *activity) String() string {\n\treturn act.caller.String() + \" \" +\n\t\tact.sent.String() + \" sent, \" +\n\t\tact.returned.String() + \" returned, at \" +\n\t\tact.time.Format(\"2006-01-02 3:04pm MST\")\n}\n\nvar latest [10]*activity\n\n// Deposit will take the coins (to the realm's pkgaddr) or return them to user.\nfunc Deposit(returnDenom string, returnAmount int64) string {\n\tstd.AssertOriginCall()\n\tcaller := std.OrigCaller()\n\tsend := std.Coins{{returnDenom, returnAmount}}\n\t// record activity\n\tact := \u0026activity{\n\t\tcaller: caller,\n\t\tsent: std.OrigSend(),\n\t\treturned: send,\n\t\ttime: time.Now(),\n\t}\n\tfor i := len(latest) - 2; i \u003e= 0; i-- {\n\t\tlatest[i+1] = latest[i] // shift by +1.\n\t}\n\tlatest[0] = act\n\t// return if any.\n\tif returnAmount \u003e 0 {\n\t\tbanker := std.GetBanker(std.BankerTypeOrigSend)\n\t\tpkgaddr := std.GetOrigPkgAddr()\n\t\t// TODO: use std.Coins constructors, this isn't generally safe.\n\t\tbanker.SendCoins(pkgaddr, caller, send)\n\t\treturn \"returned!\"\n\t} else {\n\t\treturn \"thank you!\"\n\t}\n}\n\nfunc Render(path string) string {\n\t// get realm coins.\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tcoins := banker.GetCoins(std.GetOrigPkgAddr())\n\n\t// render\n\tres := \"\"\n\tres += \"## recent activity\\n\"\n\tres += \"\\n\"\n\tfor _, act := range latest {\n\t\tif act == nil {\n\t\t\tbreak\n\t\t}\n\t\tres += \" * \" + act.String() + \"\\n\"\n\t}\n\tres += \"\\n\"\n\tres += \"## total deposits\\n\"\n\tres += coins.String()\n\treturn res\n}\n"},{"name":"z_0_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\n// SEND: 100000000ugnot\n\npackage bank1\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/banktest\"\n)\n\nfunc main() {\n\t// set up main address and banktest addr.\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/bank1\")\n\tstd.TestSetOrigCaller(mainaddr)\n\tstd.TestSetOrigPkgAddr(banktestAddr)\n\n\t// get and print balance of mainaddr.\n\t// with the SEND, + 200 gnot given by the TestContext, main should have 300gnot.\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\t// simulate a Deposit call. use Send + OrigSend to simulate -send.\n\tbanker.SendCoins(mainaddr, banktestAddr, std.Coins{{\"ugnot\", 100_000_000}})\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 100_000_000}}, nil)\n\tres := banktest.Deposit(\"ugnot\", 50_000_000)\n\tprintln(\"Deposit():\", res)\n\n\t// print main balance after.\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal)\n\n\t// simulate a Render(). banker should have given back all coins.\n\tres = banktest.Render(\"\")\n\tprintln(res)\n}\n\n// Output:\n// main before: 300000000ugnot\n// Deposit(): returned!\n// main after: 250000000ugnot\n// ## recent activity\n//\n// * g1tnpdmvrmtgql8fmxgsq9rwtst5hsxahk3f05dk 100000000ugnot sent, 50000000ugnot returned, at 2009-02-13 11:31pm UTC\n//\n// ## total deposits\n// 50000000ugnot\n"},{"name":"z_1_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\npackage bank1\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/banktest\"\n)\n\nfunc main() {\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\n\t// simulate a Deposit call.\n\tstd.TestSetOrigPkgAddr(banktestAddr)\n\tstd.TestIssueCoins(banktestAddr, std.Coins{{\"ugnot\", 100000000}})\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 100000000}}, nil)\n\tres := banktest.Deposit(\"ugnot\", 101000000)\n\tprintln(res)\n}\n\n// Error:\n// cannot send \"101000000ugnot\", limit \"100000000ugnot\" exceeded with \"\" already spent\n"},{"name":"z_2_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\npackage bank1\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/banktest\"\n)\n\nfunc main() {\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\n\t// print main balance before.\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/bank1\")\n\tstd.TestSetOrigCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal) // plus OrigSend equals 300.\n\n\t// simulate a Deposit call.\n\tstd.TestSetOrigPkgAddr(banktestAddr)\n\tstd.TestIssueCoins(banktestAddr, std.Coins{{\"ugnot\", 100000000}})\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 100000000}}, nil)\n\tres := banktest.Deposit(\"ugnot\", 55000000)\n\tprintln(\"Deposit():\", res)\n\n\t// print main balance after.\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal) // now 255.\n\n\t// simulate a Render().\n\tres = banktest.Render(\"\")\n\tprintln(res)\n}\n\n// Output:\n// main before: 200000000ugnot\n// Deposit(): returned!\n// main after: 255000000ugnot\n// ## recent activity\n//\n// * g1tnpdmvrmtgql8fmxgsq9rwtst5hsxahk3f05dk 100000000ugnot sent, 55000000ugnot returned, at 2009-02-13 11:31pm UTC\n//\n// ## total deposits\n// 45000000ugnot\n"},{"name":"z_3_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\npackage bank1\n\nimport (\n\t\"std\"\n)\n\nfunc main() {\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/bank1\")\n\tstd.TestSetOrigCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tsend := std.Coins{{\"ugnot\", 123}}\n\tbanker.SendCoins(banktestAddr, mainaddr, send)\n\n}\n\n// Error:\n// can only send coins from realm that created banker \"g1tnpdmvrmtgql8fmxgsq9rwtst5hsxahk3f05dk\", not \"g1dv3435088tlrgggf745kaud0ptrkc9v42k8llz\"\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":"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)\n\nvar (\n\tbanker *grc20.Banker // private banker.\n\tToken grc20.Token // public safe-object.\n)\n\nfunc init() {\n\tbanker = grc20.NewBanker(\"Bar\", \"BAR\", 4)\n\tToken = banker.Token()\n}\n\nfunc Faucet() string {\n\tcaller := std.PrevRealm().Addr()\n\tif err := banker.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 banker.RenderHome() // XXX: should be 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, Token.BalanceOf(alice), uint64(0))\n\turequire.Equal(t, Faucet(), \"OK\")\n\turequire.Equal(t, Token.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":"banktest","path":"gno.land/r/demo/banktest","files":[{"name":"README.md","body":"This is a simple test realm contract that demonstrates how to use the banker.\n\nSee [gno.land/r/demo/banktest/banktest.go](/r/demo/banktest/banktest.go) to see the original contract code.\n\nThis article will go through each line to explain how it works.\n\n```go\npackage banktest\n```\n\nThis package is locally named \"banktest\" (could be anything).\n\n```go\nimport (\n \"std\"\n)\n```\n\nThe \"std\" package is defined by the gno code in stdlibs/std/. \u003c/br\u003e Self explanatory; and you'll see more usage from std later.\n\n```go\ntype activity struct {\n caller std.Address\n sent std.Coins\n returned std.Coins\n time time.Time\n}\n\nfunc (act *activity) String() string {\n return act.caller.String() + \" \" +\n act.sent.String() + \" sent, \" +\n act.returned.String() + \" returned, at \" +\n act.time.Format(\"2006-01-02 3:04pm MST\")\n}\n\nvar latest [10]*activity\n```\n\nThis is just maintaining a list of recent activity to this contract. Notice that the \"latest\" variable is defined \"globally\" within the context of the realm with path \"gno.land/r/demo/banktest\".\n\nThis means that calls to functions defined within this package are encapsulated within this \"data realm\", where the data is mutated based on transactions that can potentially cross many realm and non-realm package boundaries (in the call stack).\n\n```go\n// Deposit will take the coins (to the realm's pkgaddr) or return them to user.\nfunc Deposit(returnDenom string, returnAmount int64) string {\n std.AssertOriginCall()\n caller := std.OriginCaller()\n send := std.Coins{{returnDenom, returnAmount}}\n```\n\nThis is the beginning of the definition of the contract function named \"Deposit\". `std.AssertOriginCall() asserts that this function was called by a gno transactional Message. The caller is the user who signed off on this transactional message. Send is the amount of deposit sent along with this message.\n\n```go\n // record activity\n act := \u0026activity{\n caller: caller,\n sent: std.OriginSend(),\n returned: send,\n time: time.Now(),\n }\n for i := len(latest) - 2; i \u003e= 0; i-- {\n latest[i+1] = latest[i] // shift by +1.\n }\n latest[0] = act\n```\n\nUpdating the \"latest\" array for viewing at gno.land/r/demo/banktest: (w/ trailing colon).\n\n```go\n // return if any.\n if returnAmount \u003e 0 {\n```\n\nIf the user requested the return of coins...\n\n```go\n banker := std.GetBanker(std.BankerTypeOriginSend)\n```\n\nuse a std.Banker instance to return any deposited coins to the original sender.\n\n```go\n pkgaddr := std.GetOrigPkgAddr()\n // TODO: use std.Coins constructors, this isn't generally safe.\n banker.SendCoins(pkgaddr, caller, send)\n return \"returned!\"\n```\n\nNotice that each realm package has an associated Cosmos address.\n\nFinally, the results are rendered via an ABCI query call when you visit [/r/demo/banktest:](/r/demo/banktest:).\n\n```go\nfunc Render(path string) string {\n // get realm coins.\n banker := std.GetBanker(std.BankerTypeReadonly)\n coins := banker.GetCoins(std.GetOrigPkgAddr())\n\n // render\n res := \"\"\n res += \"## recent activity\\n\"\n res += \"\\n\"\n for _, act := range latest {\n if act == nil {\n break\n }\n res += \" * \" + act.String() + \"\\n\"\n }\n res += \"\\n\"\n res += \"## total deposits\\n\"\n res += coins.String()\n return res\n}\n```\n\nYou can call this contract yourself, by vistiing [/r/demo/banktest](/r/demo/banktest) and the [quickstart guide](/r/demo/boards:gnolang/4).\n"},{"name":"banktest.gno","body":"package banktest\n\nimport (\n\t\"std\"\n\t\"time\"\n)\n\ntype activity struct {\n\tcaller std.Address\n\tsent std.Coins\n\treturned std.Coins\n\ttime time.Time\n}\n\nfunc (act *activity) String() string {\n\treturn act.caller.String() + \" \" +\n\t\tact.sent.String() + \" sent, \" +\n\t\tact.returned.String() + \" returned, at \" +\n\t\tact.time.Format(\"2006-01-02 3:04pm MST\")\n}\n\nvar latest [10]*activity\n\n// Deposit will take the coins (to the realm's pkgaddr) or return them to user.\nfunc Deposit(returnDenom string, returnAmount int64) string {\n\tstd.AssertOriginCall()\n\tcaller := std.OriginCaller()\n\tsend := std.Coins{{returnDenom, returnAmount}}\n\t// record activity\n\tact := \u0026activity{\n\t\tcaller: caller,\n\t\tsent: std.OriginSend(),\n\t\treturned: send,\n\t\ttime: time.Now(),\n\t}\n\tfor i := len(latest) - 2; i \u003e= 0; i-- {\n\t\tlatest[i+1] = latest[i] // shift by +1.\n\t}\n\tlatest[0] = act\n\t// return if any.\n\tif returnAmount \u003e 0 {\n\t\tbanker := std.GetBanker(std.BankerTypeOriginSend)\n\t\tpkgaddr := std.GetOrigPkgAddr()\n\t\t// TODO: use std.Coins constructors, this isn't generally safe.\n\t\tbanker.SendCoins(pkgaddr, caller, send)\n\t\treturn \"returned!\"\n\t} else {\n\t\treturn \"thank you!\"\n\t}\n}\n\nfunc Render(path string) string {\n\t// get realm coins.\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tcoins := banker.GetCoins(std.GetOrigPkgAddr())\n\n\t// render\n\tres := \"\"\n\tres += \"## recent activity\\n\"\n\tres += \"\\n\"\n\tfor _, act := range latest {\n\t\tif act == nil {\n\t\t\tbreak\n\t\t}\n\t\tres += \" * \" + act.String() + \"\\n\"\n\t}\n\tres += \"\\n\"\n\tres += \"## total deposits\\n\"\n\tres += coins.String()\n\treturn res\n}\n"},{"name":"z_0_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\n// SEND: 100000000ugnot\n\npackage bank1\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/banktest\"\n)\n\nfunc main() {\n\t// set up main address and banktest addr.\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/bank1\")\n\tstd.TestSetOriginCaller(mainaddr)\n\tstd.TestSetOrigPkgAddr(banktestAddr)\n\n\t// get and print balance of mainaddr.\n\t// with the SEND, + 200 gnot given by the TestContext, main should have 300gnot.\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\t// simulate a Deposit call. use Send + OriginSend to simulate -send.\n\tbanker.SendCoins(mainaddr, banktestAddr, std.Coins{{\"ugnot\", 100_000_000}})\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 100_000_000}}, nil)\n\tres := banktest.Deposit(\"ugnot\", 50_000_000)\n\tprintln(\"Deposit():\", res)\n\n\t// print main balance after.\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal)\n\n\t// simulate a Render(). banker should have given back all coins.\n\tres = banktest.Render(\"\")\n\tprintln(res)\n}\n\n// Output:\n// main before: 300000000ugnot\n// Deposit(): returned!\n// main after: 250000000ugnot\n// ## recent activity\n//\n// * g1tnpdmvrmtgql8fmxgsq9rwtst5hsxahk3f05dk 100000000ugnot sent, 50000000ugnot returned, at 2009-02-13 11:31pm UTC\n//\n// ## total deposits\n// 50000000ugnot\n"},{"name":"z_1_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\npackage bank1\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/banktest\"\n)\n\nfunc main() {\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\n\t// simulate a Deposit call.\n\tstd.TestSetOrigPkgAddr(banktestAddr)\n\tstd.TestIssueCoins(banktestAddr, std.Coins{{\"ugnot\", 100000000}})\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 100000000}}, nil)\n\tres := banktest.Deposit(\"ugnot\", 101000000)\n\tprintln(res)\n}\n\n// Error:\n// cannot send \"101000000ugnot\", limit \"100000000ugnot\" exceeded with \"\" already spent\n"},{"name":"z_2_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\npackage bank1\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/banktest\"\n)\n\nfunc main() {\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\n\t// print main balance before.\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/bank1\")\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal) // plus OriginSend equals 300.\n\n\t// simulate a Deposit call.\n\tstd.TestSetOrigPkgAddr(banktestAddr)\n\tstd.TestIssueCoins(banktestAddr, std.Coins{{\"ugnot\", 100000000}})\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 100000000}}, nil)\n\tres := banktest.Deposit(\"ugnot\", 55000000)\n\tprintln(\"Deposit():\", res)\n\n\t// print main balance after.\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal) // now 255.\n\n\t// simulate a Render().\n\tres = banktest.Render(\"\")\n\tprintln(res)\n}\n\n// Output:\n// main before: 200000000ugnot\n// Deposit(): returned!\n// main after: 255000000ugnot\n// ## recent activity\n//\n// * g1tnpdmvrmtgql8fmxgsq9rwtst5hsxahk3f05dk 100000000ugnot sent, 55000000ugnot returned, at 2009-02-13 11:31pm UTC\n//\n// ## total deposits\n// 45000000ugnot\n"},{"name":"z_3_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\npackage bank1\n\nimport (\n\t\"std\"\n)\n\nfunc main() {\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/bank1\")\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tsend := std.Coins{{\"ugnot\", 123}}\n\tbanker.SendCoins(banktestAddr, mainaddr, send)\n\n}\n\n// Error:\n// can only send coins from realm that created banker \"g1tnpdmvrmtgql8fmxgsq9rwtst5hsxahk3f05dk\", not \"g1dv3435088tlrgggf745kaud0ptrkc9v42k8llz\"\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":"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)\n\nvar (\n\tbanker *grc20.Banker // private banker.\n\tToken grc20.Token // public safe-object.\n)\n\nfunc init() {\n\tbanker = grc20.NewBanker(\"Bar\", \"BAR\", 4)\n\tToken = banker.Token()\n}\n\nfunc Faucet() string {\n\tcaller := std.PrevRealm().Addr()\n\tif err := banker.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 banker.RenderHome() // XXX: should be 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.TestSetOriginCaller(alice) // XXX: should not need this\n\n\turequire.Equal(t, Token.BalanceOf(alice), uint64(0))\n\turequire.Equal(t, Faucet(), \"OK\")\n\turequire.Equal(t, Token.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":"counter","path":"gno.land/r/demo/counter","files":[{"name":"counter.gno","body":"package counter\n\nimport \"strconv\"\n\nvar counter int\n\nfunc Increment() int {\n\tcounter++\n\treturn counter\n}\n\nfunc Render(_ string) string {\n\treturn strconv.Itoa(counter)\n}\n"},{"name":"counter_test.gno","body":"package counter\n\nimport \"testing\"\n\nfunc TestIncrement(t *testing.T) {\n\tcounter = 0\n\tval := Increment()\n\tif val != 1 {\n\t\tt.Fatalf(\"result from Increment(): %d != 1\", val)\n\t}\n\tif counter != val {\n\t\tt.Fatalf(\"counter (%d) != val (%d)\", counter, val)\n\t}\n}\n\nfunc TestRender(t *testing.T) {\n\tcounter = 1337\n\tres := Render(\"\")\n\tif res != \"1337\" {\n\t\tt.Fatalf(\"render result %q != %q\", res, \"1337\")\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":"govdao","path":"gno.land/r/gov/dao/v2","files":[{"name":"dao.gno","body":"package govdao\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/membstore\"\n\t\"gno.land/p/demo/simpledao\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar (\n\td *simpledao.SimpleDAO // the current active DAO implementation\n\tmembers membstore.MemberStore // the member store\n)\n\nfunc init() {\n\tvar (\n\t\tset = []membstore.Member{\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\"), // Jae\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"), // Manfred\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1e6gxg5tvc55mwsn7t7dymmlasratv7mkv0rap2\"), // Milos\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1jazghxvvgz3egnr2fc8uf72z4g0l03596y9ls7\"), // Nemanja\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1qhskthp2uycmg4zsdc9squ2jds7yv3t0qyrlnp\"), // Petar\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g18amm3fc00t43dcxsys6udug0czyvqt9e7p23rd\"), // Marc\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1dfr24yhk5ztwtqn2a36m8f6ud8cx5hww4dkjfl\"), // Antonio\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g19p3yzr3cuhzqa02j0ce6kzvyjqfzwemw3vam0x\"), // Guilhem\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1mx4pum9976th863jgry4sdjzfwu03qan5w2v9j\"), // Ray\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g127l4gkhk0emwsx5tmxe96sp86c05h8vg5tufzq\"), // Maxwell\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1cpx59z5r8vzeww2fm4ezpz7yvjs7kptywkm864\"), // Morgan\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1ker4vvggvsyatexxn3hkthp2hu80pkhrwmuczr\"), // Sergio\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g18x425qmujg99cfz3q97y4uep5pxjq3z8lmpt25\"), // Antoine\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t// GNO DEVX\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g16tfrrul20g4jzt3z303raqw8vs8s2pqqh5clwu\"), // Ilker\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1hy6zry03hg5d8le9s2w4fxme6236hkgd928dun\"), // Jerónimo\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g15ruzptpql4dpuyzej0wkt5rq6r26kw4nxu9fwd\"), // Denis\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1dnllrdzwfhxv3evyk09y48mgn5phfjvtyrlzm7\"), // Danny\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1778y2yphxs2wpuaflsy5y9qwcd4gttn4g5yjx5\"), // Michelle\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1mq7g0jszdmn4qdpc9tq94w0gyex37su892n80m\"), // Alan\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g197q5e9v00vuz256ly7fq7v3ekaun5cr7wmjgfh\"), // Salvo\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1mpkp5lm8lwpm0pym4388836d009zfe4maxlqsq\"), // Alexis\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g125em6arxsnj49vx35f0n0z34putv5ty3376fg5\"), // Leon\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1whzkakk4hzjkvy60d5pwfk484xu67ar2cl62h2\"), // Kirk\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t// AiB\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1sw5xklxjjuv0yvuxy5f5s3l3mnj0nqq626a9wr\"), // Albert\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t// ONBLOC\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g12vx7dn3dqq89mz550zwunvg4qw6epq73d9csay\"), // Dongwon\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1r04aw56fgvzy859fachr8hzzhqkulkaemltr76\"), // Blake\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g17n4y745s08awwq4e0a38lagsgtntna0749tnxe\"), // Jinwoo\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1ckae7tc5sez8ul3ssne75sk4muwgttp6ks2ky9\"), // ByeongJun\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t\t// TERITORI\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g14u5eaheavy0ux4dmpykg2gvxpvqvexm9cyg58a\"), // Norman\n\t\t\t\tVotingPower: 5,\n\t\t\t},\n\t\t\t// BERTY\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1qynsu9dwj9lq0m5fkje7jh6qy3md80ztqnshhm\"), // Rémi\n\t\t\t\tVotingPower: 5,\n\t\t\t},\n\t\t\t// FLIPPANDO / ZENTASKTIC\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g17ernafy6ctpcz6uepfsq2js8x2vz0wladh5yc3\"), // Dragos\n\t\t\t\tVotingPower: 5,\n\t\t\t},\n\t\t}\n\t)\n\n\t// Set the member store\n\tmembers = membstore.NewMembStore(membstore.WithInitialMembers(set))\n\n\t// Set the DAO implementation\n\td = simpledao.New(members)\n}\n\n// Propose is designed to be called by another contract or with\n// `maketx run`, not by a `maketx call`.\nfunc Propose(request dao.ProposalRequest) uint64 {\n\tidx, err := d.Propose(request)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn idx\n}\n\n// VoteOnProposal casts a vote for the given proposal\nfunc VoteOnProposal(id uint64, option dao.VoteOption) {\n\tif err := d.VoteOnProposal(id, option); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// ExecuteProposal executes the proposal\nfunc ExecuteProposal(id uint64) {\n\tif err := d.ExecuteProposal(id); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// GetPropStore returns the active proposal store\nfunc GetPropStore() dao.PropStore {\n\treturn d\n}\n\n// GetMembStore returns the active member store\nfunc GetMembStore() membstore.MemberStore {\n\treturn members\n}\n\nfunc Render(path string) string {\n\tif path == \"\" {\n\t\tnumProposals := d.Size()\n\n\t\tif numProposals == 0 {\n\t\t\treturn \"No proposals found :(\" // corner case\n\t\t}\n\n\t\toutput := \"\"\n\n\t\toffset := uint64(0)\n\t\tif numProposals \u003e= 10 {\n\t\t\toffset = uint64(numProposals) - 10\n\t\t}\n\n\t\t// Fetch the last 10 proposals\n\t\tfor idx, prop := range d.Proposals(offset, uint64(10)) {\n\t\t\toutput += ufmt.Sprintf(\n\t\t\t\t\"- [Proposal #%d](%s:%d) - (**%s**)(by %s)\\n\",\n\t\t\t\tidx,\n\t\t\t\t\"/r/gov/dao/v2\",\n\t\t\t\tidx,\n\t\t\t\tprop.Status().String(),\n\t\t\t\tprop.Author().String(),\n\t\t\t)\n\t\t}\n\n\t\treturn output\n\t}\n\n\t// Display the detailed proposal\n\tidx, err := strconv.Atoi(path)\n\tif err != nil {\n\t\treturn \"404: Invalid proposal ID\"\n\t}\n\n\t// Fetch the proposal\n\tprop, err := d.ProposalByID(uint64(idx))\n\tif err != nil {\n\t\treturn ufmt.Sprintf(\"unable to fetch proposal, %s\", err.Error())\n\t}\n\n\t// Render the proposal\n\toutput := \"\"\n\toutput += ufmt.Sprintf(\"# Prop #%d\", idx)\n\toutput += \"\\n\\n\"\n\toutput += prop.Render()\n\toutput += \"\\n\\n\"\n\n\treturn output\n}\n"},{"name":"poc.gno","body":"package govdao\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/combinederr\"\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/membstore\"\n\t\"gno.land/p/gov/executor\"\n)\n\nvar errNoChangesProposed = errors.New(\"no set changes proposed\")\n\n// NewGovDAOExecutor creates the govdao wrapped callback executor\nfunc NewGovDAOExecutor(cb func() error) dao.Executor {\n\tif cb == nil {\n\t\tpanic(errNoChangesProposed)\n\t}\n\n\treturn executor.NewCallbackExecutor(\n\t\tcb,\n\t\tstd.CurrentRealm().PkgPath(),\n\t)\n}\n\n// NewMemberPropExecutor returns the GOVDAO member change executor\nfunc NewMemberPropExecutor(changesFn func() []membstore.Member) dao.Executor {\n\tif changesFn == nil {\n\t\tpanic(errNoChangesProposed)\n\t}\n\n\tcallback := func() error {\n\t\terrs := \u0026combinederr.CombinedError{}\n\t\tcbMembers := changesFn()\n\n\t\tfor _, member := range cbMembers {\n\t\t\tswitch {\n\t\t\tcase !members.IsMember(member.Address):\n\t\t\t\t// Addition request\n\t\t\t\terr := members.AddMember(member)\n\n\t\t\t\terrs.Add(err)\n\t\t\tcase member.VotingPower == 0:\n\t\t\t\t// Remove request\n\t\t\t\terr := members.UpdateMember(member.Address, membstore.Member{\n\t\t\t\t\tAddress: member.Address,\n\t\t\t\t\tVotingPower: 0, // 0 indicated removal\n\t\t\t\t})\n\n\t\t\t\terrs.Add(err)\n\t\t\tdefault:\n\t\t\t\t// Update request\n\t\t\t\terr := members.UpdateMember(member.Address, member)\n\n\t\t\t\terrs.Add(err)\n\t\t\t}\n\t\t}\n\n\t\t// Check if there were any execution errors\n\t\tif errs.Size() == 0 {\n\t\t\treturn nil\n\t\t}\n\n\t\treturn errs\n\t}\n\n\treturn NewGovDAOExecutor(callback)\n}\n\nfunc NewMembStoreImplExecutor(changeFn func() membstore.MemberStore) dao.Executor {\n\tif changeFn == nil {\n\t\tpanic(errNoChangesProposed)\n\t}\n\n\tcallback := func() error {\n\t\tsetMembStoreImpl(changeFn())\n\n\t\treturn nil\n\t}\n\n\treturn NewGovDAOExecutor(callback)\n}\n\n// setMembStoreImpl sets a new dao.MembStore implementation\nfunc setMembStoreImpl(impl membstore.MemberStore) {\n\tif impl == nil {\n\t\tpanic(\"invalid member store\")\n\t}\n\n\tmembers = impl\n}\n"},{"name":"prop1_filetest.gno","body":"// Please note that this package is intended for demonstration purposes only.\n// You could execute this code (the init part) by running a `maketx run` command\n// or by uploading a similar package to a personal namespace.\n//\n// For the specific case of validators, a `r/gnoland/valopers` will be used to\n// organize the lifecycle of validators (register, etc), and this more complex\n// contract will be responsible to generate proposals.\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/dao\"\n\tpVals \"gno.land/p/sys/validators\"\n\tgovdao \"gno.land/r/gov/dao/v2\"\n\tvalidators \"gno.land/r/sys/validators/v2\"\n)\n\nfunc init() {\n\tchangesFn := func() []pVals.Validator {\n\t\treturn []pVals.Validator{\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g12345678\"),\n\t\t\t\tPubKey: \"pubkey\",\n\t\t\t\tVotingPower: 10, // add a new validator\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g000000000\"),\n\t\t\t\tPubKey: \"pubkey\",\n\t\t\t\tVotingPower: 10, // add a new validator\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g000000000\"),\n\t\t\t\tPubKey: \"pubkey\",\n\t\t\t\tVotingPower: 0, // remove an existing validator\n\t\t\t},\n\t\t}\n\t}\n\n\t// Wraps changesFn to emit a certified event only if executed from a\n\t// complete governance proposal process.\n\texecutor := validators.NewPropExecutor(changesFn)\n\n\t// Create a proposal\n\tdescription := \"manual valset changes proposal example\"\n\n\tprop := dao.ProposalRequest{\n\t\tDescription: description,\n\t\tExecutor: executor,\n\t}\n\n\tgovdao.Propose(prop)\n}\n\nfunc main() {\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"\"))\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tgovdao.VoteOnProposal(0, dao.YesVote)\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tprintln(validators.Render(\"\"))\n\tprintln(\"--\")\n\tgovdao.ExecuteProposal(0)\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tprintln(validators.Render(\"\"))\n}\n\n// Output:\n// --\n// - [Proposal #0](/r/gov/dao/v2:0) - (**active**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm)\n//\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// manual valset changes proposal example\n//\n// Status: active\n//\n// Voting stats: YES 0 (0%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 10 (100%)\n//\n// Threshold met: false\n//\n//\n// --\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// manual valset changes proposal example\n//\n// Status: accepted\n//\n// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%)\n//\n// Threshold met: true\n//\n//\n// --\n// No valset changes to apply.\n// --\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// manual valset changes proposal example\n//\n// Status: execution successful\n//\n// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%)\n//\n// Threshold met: true\n//\n//\n// --\n// Valset changes:\n// - #123: g12345678 (10)\n// - #123: g000000000 (10)\n// - #123: g000000000 (0)\n"},{"name":"prop2_filetest.gno","body":"package main\n\nimport (\n\t\"time\"\n\n\t\"gno.land/p/demo/dao\"\n\tgnoblog \"gno.land/r/gnoland/blog\"\n\tgovdao \"gno.land/r/gov/dao/v2\"\n)\n\nfunc init() {\n\tex := gnoblog.NewPostExecutor(\n\t\t\"hello-from-govdao\", // slug\n\t\t\"Hello from GovDAO!\", // title\n\t\t\"This post was published by a GovDAO proposal.\", // body\n\t\ttime.Now().Format(time.RFC3339), // publication date\n\t\t\"moul\", // authors\n\t\t\"govdao,example\", // tags\n\t)\n\n\t// Create a proposal\n\tdescription := \"post a new blogpost about govdao\"\n\n\tprop := dao.ProposalRequest{\n\t\tDescription: description,\n\t\tExecutor: ex,\n\t}\n\n\tgovdao.Propose(prop)\n}\n\nfunc main() {\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"\"))\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tgovdao.VoteOnProposal(0, \"YES\")\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tprintln(gnoblog.Render(\"\"))\n\tprintln(\"--\")\n\tgovdao.ExecuteProposal(0)\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tprintln(gnoblog.Render(\"\"))\n}\n\n// Output:\n// --\n// - [Proposal #0](/r/gov/dao/v2:0) - (**active**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm)\n//\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// post a new blogpost about govdao\n//\n// Status: active\n//\n// Voting stats: YES 0 (0%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 10 (100%)\n//\n// Threshold met: false\n//\n//\n// --\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// post a new blogpost about govdao\n//\n// Status: accepted\n//\n// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%)\n//\n// Threshold met: true\n//\n//\n// --\n// # Gnoland's Blog\n//\n// No posts.\n// --\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// post a new blogpost about govdao\n//\n// Status: execution successful\n//\n// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%)\n//\n// Threshold met: true\n//\n//\n// --\n// # Gnoland's Blog\n//\n// \u003cdiv class='columns-3'\u003e\u003cdiv\u003e\n//\n// ### [Hello from GovDAO!](/r/gnoland/blog:p/hello-from-govdao)\n// 13 Feb 2009\n// \u003c/div\u003e\u003c/div\u003e\n"},{"name":"prop3_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/membstore\"\n\tgovdao \"gno.land/r/gov/dao/v2\"\n)\n\nfunc init() {\n\tmemberFn := func() []membstore.Member {\n\t\treturn []membstore.Member{\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g123\"),\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g456\"),\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g789\"),\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t}\n\t}\n\n\t// Create a proposal\n\tdescription := \"add new members to the govdao\"\n\n\tprop := dao.ProposalRequest{\n\t\tDescription: description,\n\t\tExecutor: govdao.NewMemberPropExecutor(memberFn),\n\t}\n\n\tgovdao.Propose(prop)\n}\n\nfunc main() {\n\tprintln(\"--\")\n\tprintln(govdao.GetMembStore().Size())\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"\"))\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tgovdao.VoteOnProposal(0, \"YES\")\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"\"))\n\tprintln(\"--\")\n\tgovdao.ExecuteProposal(0)\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"\"))\n\tprintln(\"--\")\n\tprintln(govdao.GetMembStore().Size())\n}\n\n// Output:\n// --\n// 1\n// --\n// - [Proposal #0](/r/gov/dao/v2:0) - (**active**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm)\n//\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// add new members to the govdao\n//\n// Status: active\n//\n// Voting stats: YES 0 (0%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 10 (100%)\n//\n// Threshold met: false\n//\n//\n// --\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// add new members to the govdao\n//\n// Status: accepted\n//\n// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%)\n//\n// Threshold met: true\n//\n//\n// --\n// - [Proposal #0](/r/gov/dao/v2:0) - (**accepted**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm)\n//\n// --\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// add new members to the govdao\n//\n// Status: execution successful\n//\n// Voting stats: YES 10 (25%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 30 (75%)\n//\n// Threshold met: false\n//\n//\n// --\n// - [Proposal #0](/r/gov/dao/v2:0) - (**execution successful**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm)\n//\n// --\n// 4\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":"bridge","path":"gno.land/r/gov/dao/bridge","files":[{"name":"bridge.gno","body":"package bridge\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/ownable\"\n)\n\nconst initialOwner = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\") // @moul\n\nvar b *Bridge\n\n// Bridge is the active GovDAO\n// implementation bridge\ntype Bridge struct {\n\t*ownable.Ownable\n\n\tdao DAO\n}\n\n// init constructs the initial GovDAO implementation\nfunc init() {\n\tb = \u0026Bridge{\n\t\tOwnable: ownable.NewWithAddress(initialOwner),\n\t\tdao: \u0026govdaoV2{},\n\t}\n}\n\n// SetDAO sets the currently active GovDAO implementation\nfunc SetDAO(dao DAO) {\n\tb.AssertCallerIsOwner()\n\n\tb.dao = dao\n}\n\n// GovDAO returns the current GovDAO implementation\nfunc GovDAO() DAO {\n\treturn b.dao\n}\n"},{"name":"bridge_test.gno","body":"package bridge\n\nimport (\n\t\"testing\"\n\n\t\"std\"\n\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestBridge_DAO(t *testing.T) {\n\tvar (\n\t\tproposalID = uint64(10)\n\t\tmockDAO = \u0026mockDAO{\n\t\t\tproposeFn: func(_ dao.ProposalRequest) uint64 {\n\t\t\t\treturn proposalID\n\t\t\t},\n\t\t}\n\t)\n\n\tb.dao = mockDAO\n\n\tuassert.Equal(t, proposalID, GovDAO().Propose(dao.ProposalRequest{}))\n}\n\nfunc TestBridge_SetDAO(t *testing.T) {\n\tt.Run(\"invalid owner\", func(t *testing.T) {\n\t\t// Attempt to set a new DAO implementation\n\t\tuassert.PanicsWithMessage(t, ownable.ErrUnauthorized.Error(), func() {\n\t\t\tSetDAO(\u0026mockDAO{})\n\t\t})\n\t})\n\n\tt.Run(\"valid owner\", func(t *testing.T) {\n\t\tvar (\n\t\t\taddr = testutils.TestAddress(\"owner\")\n\n\t\t\tproposalID = uint64(10)\n\t\t\tmockDAO = \u0026mockDAO{\n\t\t\t\tproposeFn: func(_ dao.ProposalRequest) uint64 {\n\t\t\t\t\treturn proposalID\n\t\t\t\t},\n\t\t\t}\n\t\t)\n\n\t\tstd.TestSetOrigCaller(addr)\n\n\t\tb.Ownable = ownable.NewWithAddress(addr)\n\n\t\turequire.NotPanics(t, func() {\n\t\t\tSetDAO(mockDAO)\n\t\t})\n\n\t\tuassert.Equal(\n\t\t\tt,\n\t\t\tmockDAO.Propose(dao.ProposalRequest{}),\n\t\t\tGovDAO().Propose(dao.ProposalRequest{}),\n\t\t)\n\t})\n}\n"},{"name":"doc.gno","body":"// Package bridge represents a GovDAO implementation wrapper, used by other Realms and Packages to\n// always fetch the most active GovDAO implementation, instead of directly referencing it, and having to\n// update it each time the GovDAO implementation changes\npackage bridge\n"},{"name":"mock_test.gno","body":"package bridge\n\nimport (\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/membstore\"\n)\n\ntype (\n\tproposeDelegate func(dao.ProposalRequest) uint64\n\tvoteOnProposalDelegate func(uint64, dao.VoteOption)\n\texecuteProposalDelegate func(uint64)\n\tgetPropStoreDelegate func() dao.PropStore\n\tgetMembStoreDelegate func() membstore.MemberStore\n\tnewGovDAOExecutorDelegate func(func() error) dao.Executor\n)\n\ntype mockDAO struct {\n\tproposeFn proposeDelegate\n\tvoteOnProposalFn voteOnProposalDelegate\n\texecuteProposalFn executeProposalDelegate\n\tgetPropStoreFn getPropStoreDelegate\n\tgetMembStoreFn getMembStoreDelegate\n\tnewGovDAOExecutorFn newGovDAOExecutorDelegate\n}\n\nfunc (m *mockDAO) Propose(request dao.ProposalRequest) uint64 {\n\tif m.proposeFn != nil {\n\t\treturn m.proposeFn(request)\n\t}\n\n\treturn 0\n}\n\nfunc (m *mockDAO) VoteOnProposal(id uint64, option dao.VoteOption) {\n\tif m.voteOnProposalFn != nil {\n\t\tm.voteOnProposalFn(id, option)\n\t}\n}\n\nfunc (m *mockDAO) ExecuteProposal(id uint64) {\n\tif m.executeProposalFn != nil {\n\t\tm.executeProposalFn(id)\n\t}\n}\n\nfunc (m *mockDAO) GetPropStore() dao.PropStore {\n\tif m.getPropStoreFn != nil {\n\t\treturn m.getPropStoreFn()\n\t}\n\n\treturn nil\n}\n\nfunc (m *mockDAO) GetMembStore() membstore.MemberStore {\n\tif m.getMembStoreFn != nil {\n\t\treturn m.getMembStoreFn()\n\t}\n\n\treturn nil\n}\n\nfunc (m *mockDAO) NewGovDAOExecutor(cb func() error) dao.Executor {\n\tif m.newGovDAOExecutorFn != nil {\n\t\treturn m.newGovDAOExecutorFn(cb)\n\t}\n\n\treturn nil\n}\n"},{"name":"types.gno","body":"package bridge\n\nimport (\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/membstore\"\n)\n\n// DAO abstracts the commonly used DAO interface\ntype DAO interface {\n\tPropose(dao.ProposalRequest) uint64\n\tVoteOnProposal(uint64, dao.VoteOption)\n\tExecuteProposal(uint64)\n\tGetPropStore() dao.PropStore\n\tGetMembStore() membstore.MemberStore\n\n\tNewGovDAOExecutor(func() error) dao.Executor\n}\n"},{"name":"v2.gno","body":"package bridge\n\nimport (\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/membstore\"\n\tgovdao \"gno.land/r/gov/dao/v2\"\n)\n\n// govdaoV2 is a wrapper for interacting with the /r/gov/dao/v2 Realm\ntype govdaoV2 struct{}\n\nfunc (g *govdaoV2) Propose(request dao.ProposalRequest) uint64 {\n\treturn govdao.Propose(request)\n}\n\nfunc (g *govdaoV2) VoteOnProposal(id uint64, option dao.VoteOption) {\n\tgovdao.VoteOnProposal(id, option)\n}\n\nfunc (g *govdaoV2) ExecuteProposal(id uint64) {\n\tgovdao.ExecuteProposal(id)\n}\n\nfunc (g *govdaoV2) GetPropStore() dao.PropStore {\n\treturn govdao.GetPropStore()\n}\n\nfunc (g *govdaoV2) GetMembStore() membstore.MemberStore {\n\treturn govdao.GetMembStore()\n}\n\nfunc (g *govdaoV2) NewGovDAOExecutor(cb func() error) dao.Executor {\n\treturn govdao.NewGovDAOExecutor(cb)\n}\n\nfunc (g *govdaoV2) NewMemberPropExecutor(cb func() []membstore.Member) dao.Executor {\n\treturn govdao.NewMemberPropExecutor(cb)\n}\n\nfunc (g *govdaoV2) NewMembStoreImplExecutor(cb func() membstore.MemberStore) dao.Executor {\n\treturn govdao.NewMembStoreImplExecutor(cb)\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":"bridge","path":"gno.land/r/gov/dao/bridge","files":[{"name":"bridge.gno","body":"package bridge\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/ownable\"\n)\n\nconst initialOwner = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\") // @moul\n\nvar b *Bridge\n\n// Bridge is the active GovDAO\n// implementation bridge\ntype Bridge struct {\n\t*ownable.Ownable\n\n\tdao DAO\n}\n\n// init constructs the initial GovDAO implementation\nfunc init() {\n\tb = \u0026Bridge{\n\t\tOwnable: ownable.NewWithAddress(initialOwner),\n\t\tdao: \u0026govdaoV2{},\n\t}\n}\n\n// SetDAO sets the currently active GovDAO implementation\nfunc SetDAO(dao DAO) {\n\tb.AssertCallerIsOwner()\n\n\tb.dao = dao\n}\n\n// GovDAO returns the current GovDAO implementation\nfunc GovDAO() DAO {\n\treturn b.dao\n}\n"},{"name":"bridge_test.gno","body":"package bridge\n\nimport (\n\t\"testing\"\n\n\t\"std\"\n\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestBridge_DAO(t *testing.T) {\n\tvar (\n\t\tproposalID = uint64(10)\n\t\tmockDAO = \u0026mockDAO{\n\t\t\tproposeFn: func(_ dao.ProposalRequest) uint64 {\n\t\t\t\treturn proposalID\n\t\t\t},\n\t\t}\n\t)\n\n\tb.dao = mockDAO\n\n\tuassert.Equal(t, proposalID, GovDAO().Propose(dao.ProposalRequest{}))\n}\n\nfunc TestBridge_SetDAO(t *testing.T) {\n\tt.Run(\"invalid owner\", func(t *testing.T) {\n\t\t// Attempt to set a new DAO implementation\n\t\tuassert.PanicsWithMessage(t, ownable.ErrUnauthorized.Error(), func() {\n\t\t\tSetDAO(\u0026mockDAO{})\n\t\t})\n\t})\n\n\tt.Run(\"valid owner\", func(t *testing.T) {\n\t\tvar (\n\t\t\taddr = testutils.TestAddress(\"owner\")\n\n\t\t\tproposalID = uint64(10)\n\t\t\tmockDAO = \u0026mockDAO{\n\t\t\t\tproposeFn: func(_ dao.ProposalRequest) uint64 {\n\t\t\t\t\treturn proposalID\n\t\t\t\t},\n\t\t\t}\n\t\t)\n\n\t\tstd.TestSetOriginCaller(addr)\n\n\t\tb.Ownable = ownable.NewWithAddress(addr)\n\n\t\turequire.NotPanics(t, func() {\n\t\t\tSetDAO(mockDAO)\n\t\t})\n\n\t\tuassert.Equal(\n\t\t\tt,\n\t\t\tmockDAO.Propose(dao.ProposalRequest{}),\n\t\t\tGovDAO().Propose(dao.ProposalRequest{}),\n\t\t)\n\t})\n}\n"},{"name":"doc.gno","body":"// Package bridge represents a GovDAO implementation wrapper, used by other Realms and Packages to\n// always fetch the most active GovDAO implementation, instead of directly referencing it, and having to\n// update it each time the GovDAO implementation changes\npackage bridge\n"},{"name":"mock_test.gno","body":"package bridge\n\nimport (\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/membstore\"\n)\n\ntype (\n\tproposeDelegate func(dao.ProposalRequest) uint64\n\tvoteOnProposalDelegate func(uint64, dao.VoteOption)\n\texecuteProposalDelegate func(uint64)\n\tgetPropStoreDelegate func() dao.PropStore\n\tgetMembStoreDelegate func() membstore.MemberStore\n\tnewGovDAOExecutorDelegate func(func() error) dao.Executor\n)\n\ntype mockDAO struct {\n\tproposeFn proposeDelegate\n\tvoteOnProposalFn voteOnProposalDelegate\n\texecuteProposalFn executeProposalDelegate\n\tgetPropStoreFn getPropStoreDelegate\n\tgetMembStoreFn getMembStoreDelegate\n\tnewGovDAOExecutorFn newGovDAOExecutorDelegate\n}\n\nfunc (m *mockDAO) Propose(request dao.ProposalRequest) uint64 {\n\tif m.proposeFn != nil {\n\t\treturn m.proposeFn(request)\n\t}\n\n\treturn 0\n}\n\nfunc (m *mockDAO) VoteOnProposal(id uint64, option dao.VoteOption) {\n\tif m.voteOnProposalFn != nil {\n\t\tm.voteOnProposalFn(id, option)\n\t}\n}\n\nfunc (m *mockDAO) ExecuteProposal(id uint64) {\n\tif m.executeProposalFn != nil {\n\t\tm.executeProposalFn(id)\n\t}\n}\n\nfunc (m *mockDAO) GetPropStore() dao.PropStore {\n\tif m.getPropStoreFn != nil {\n\t\treturn m.getPropStoreFn()\n\t}\n\n\treturn nil\n}\n\nfunc (m *mockDAO) GetMembStore() membstore.MemberStore {\n\tif m.getMembStoreFn != nil {\n\t\treturn m.getMembStoreFn()\n\t}\n\n\treturn nil\n}\n\nfunc (m *mockDAO) NewGovDAOExecutor(cb func() error) dao.Executor {\n\tif m.newGovDAOExecutorFn != nil {\n\t\treturn m.newGovDAOExecutorFn(cb)\n\t}\n\n\treturn nil\n}\n"},{"name":"types.gno","body":"package bridge\n\nimport (\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/membstore\"\n)\n\n// DAO abstracts the commonly used DAO interface\ntype DAO interface {\n\tPropose(dao.ProposalRequest) uint64\n\tVoteOnProposal(uint64, dao.VoteOption)\n\tExecuteProposal(uint64)\n\tGetPropStore() dao.PropStore\n\tGetMembStore() membstore.MemberStore\n\n\tNewGovDAOExecutor(func() error) dao.Executor\n}\n"},{"name":"v2.gno","body":"package bridge\n\nimport (\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/membstore\"\n\tgovdao \"gno.land/r/gov/dao/v2\"\n)\n\n// govdaoV2 is a wrapper for interacting with the /r/gov/dao/v2 Realm\ntype govdaoV2 struct{}\n\nfunc (g *govdaoV2) Propose(request dao.ProposalRequest) uint64 {\n\treturn govdao.Propose(request)\n}\n\nfunc (g *govdaoV2) VoteOnProposal(id uint64, option dao.VoteOption) {\n\tgovdao.VoteOnProposal(id, option)\n}\n\nfunc (g *govdaoV2) ExecuteProposal(id uint64) {\n\tgovdao.ExecuteProposal(id)\n}\n\nfunc (g *govdaoV2) GetPropStore() dao.PropStore {\n\treturn govdao.GetPropStore()\n}\n\nfunc (g *govdaoV2) GetMembStore() membstore.MemberStore {\n\treturn govdao.GetMembStore()\n}\n\nfunc (g *govdaoV2) NewGovDAOExecutor(cb func() error) dao.Executor {\n\treturn govdao.NewGovDAOExecutor(cb)\n}\n\nfunc (g *govdaoV2) NewMemberPropExecutor(cb func() []membstore.Member) dao.Executor {\n\treturn govdao.NewMemberPropExecutor(cb)\n}\n\nfunc (g *govdaoV2) NewMembStoreImplExecutor(cb func() membstore.MemberStore) dao.Executor {\n\treturn govdao.NewMembStoreImplExecutor(cb)\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":"daoweb","path":"gno.land/r/demo/daoweb","files":[{"name":"daoweb.gno","body":"package daoweb\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/json\"\n\t\"gno.land/r/gov/dao/bridge\"\n)\n\n// Proposals returns the paginated GovDAO proposals\nfunc Proposals(offset, count uint64) string {\n\tvar (\n\t\tpropStore = bridge.GovDAO().GetPropStore()\n\t\tsize = propStore.Size()\n\t)\n\n\t// Get the props\n\tprops := propStore.Proposals(offset, count)\n\n\tresp := ProposalsResponse{\n\t\tProposals: make([]Proposal, 0, count),\n\t\tTotal: uint64(size),\n\t}\n\n\tfor _, p := range props {\n\t\tprop := Proposal{\n\t\t\tAuthor: p.Author(),\n\t\t\tDescription: p.Description(),\n\t\t\tStatus: p.Status(),\n\t\t\tStats: p.Stats(),\n\t\t\tIsExpired: p.IsExpired(),\n\t\t}\n\n\t\tresp.Proposals = append(resp.Proposals, prop)\n\t}\n\n\t// Encode the response into JSON\n\tencodedProps, err := json.Marshal(encodeProposalsResponse(resp))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn string(encodedProps)\n}\n\n// ProposalByID fetches the proposal using the given ID\nfunc ProposalByID(id uint64) string {\n\tpropStore := bridge.GovDAO().GetPropStore()\n\n\tp, err := propStore.ProposalByID(id)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Encode the response into JSON\n\tprop := Proposal{\n\t\tAuthor: p.Author(),\n\t\tDescription: p.Description(),\n\t\tStatus: p.Status(),\n\t\tStats: p.Stats(),\n\t\tIsExpired: p.IsExpired(),\n\t}\n\n\tencodedProp, err := json.Marshal(encodeProposal(prop))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn string(encodedProp)\n}\n\n// encodeProposal encodes a proposal into a json node\nfunc encodeProposal(p Proposal) *json.Node {\n\treturn json.ObjectNode(\"\", map[string]*json.Node{\n\t\t\"author\": json.StringNode(\"author\", p.Author.String()),\n\t\t\"description\": json.StringNode(\"description\", p.Description),\n\t\t\"status\": json.StringNode(\"status\", p.Status.String()),\n\t\t\"stats\": json.ObjectNode(\"stats\", map[string]*json.Node{\n\t\t\t\"yay_votes\": json.NumberNode(\"yay_votes\", float64(p.Stats.YayVotes)),\n\t\t\t\"nay_votes\": json.NumberNode(\"nay_votes\", float64(p.Stats.NayVotes)),\n\t\t\t\"abstain_votes\": json.NumberNode(\"abstain_votes\", float64(p.Stats.AbstainVotes)),\n\t\t\t\"total_voting_power\": json.NumberNode(\"total_voting_power\", float64(p.Stats.TotalVotingPower)),\n\t\t}),\n\t\t\"is_expired\": json.BoolNode(\"is_expired\", p.IsExpired),\n\t})\n}\n\n// encodeProposalsResponse encodes a proposal response into a JSON node\nfunc encodeProposalsResponse(props ProposalsResponse) *json.Node {\n\tproposals := make([]*json.Node, 0, len(props.Proposals))\n\n\tfor _, p := range props.Proposals {\n\t\tproposals = append(proposals, encodeProposal(p))\n\t}\n\n\treturn json.ObjectNode(\"\", map[string]*json.Node{\n\t\t\"proposals\": json.ArrayNode(\"proposals\", proposals),\n\t\t\"total\": json.NumberNode(\"total\", float64(props.Total)),\n\t})\n}\n\n// ProposalsResponse is a paginated proposal response\ntype ProposalsResponse struct {\n\tProposals []Proposal `json:\"proposals\"`\n\tTotal uint64 `json:\"total\"`\n}\n\n// Proposal is a single GovDAO proposal\ntype Proposal struct {\n\tAuthor std.Address `json:\"author\"`\n\tDescription string `json:\"description\"`\n\tStatus dao.ProposalStatus `json:\"status\"`\n\tStats dao.Stats `json:\"stats\"`\n\tIsExpired bool `json:\"is_expired\"`\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":"deep","path":"gno.land/r/demo/deep/very/deep","files":[{"name":"render.gno","body":"package deep\n\nfunc Render(path string) string {\n\tif path == \"\" {\n\t\treturn \"it works!\"\n\t} else {\n\t\treturn \"hi \" + path\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)\n\nvar instances avl.Tree // symbol -\u003e instance\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\tbanker := grc20.NewBanker(name, symbol, decimals)\n\tif initialMint \u003e 0 {\n\t\tbanker.Mint(admin, initialMint)\n\t}\n\n\tinst := instance{\n\t\tbanker: banker,\n\t\tadmin: ownable.NewWithAddress(admin),\n\t\tfaucet: faucet,\n\t}\n\n\tinstances.Set(symbol, \u0026inst)\n}\n\ntype instance struct {\n\tbanker *grc20.Banker\n\tadmin *ownable.Ownable\n\tfaucet uint64 // per-request amount. disabled if 0.\n}\n\nfunc (inst instance) Token() grc20.Token { return inst.banker.Token() }\n\nfunc TotalSupply(symbol string) uint64 {\n\tinst := mustGetInstance(symbol)\n\treturn inst.Token().TotalSupply()\n}\n\nfunc BalanceOf(symbol string, owner std.Address) uint64 {\n\tinst := mustGetInstance(symbol)\n\treturn inst.Token().BalanceOf(owner)\n}\n\nfunc Allowance(symbol string, owner, spender std.Address) uint64 {\n\tinst := mustGetInstance(symbol)\n\treturn inst.Token().Allowance(owner, spender)\n}\n\nfunc Transfer(symbol string, to std.Address, amount uint64) {\n\tinst := mustGetInstance(symbol)\n\tcheckErr(inst.Token().Transfer(to, amount))\n}\n\nfunc Approve(symbol string, spender std.Address, amount uint64) {\n\tinst := mustGetInstance(symbol)\n\tcheckErr(inst.Token().Approve(spender, amount))\n}\n\nfunc TransferFrom(symbol string, from, to std.Address, amount uint64) {\n\tinst := mustGetInstance(symbol)\n\tcheckErr(inst.Token().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.banker.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.banker.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.banker.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.banker.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().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)\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/uassert\"\n)\n\nfunc TestReadOnlyPublicMethods(t *testing.T) {\n\tadmin := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\tmanfred := std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\tunknown := std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\") // valid but never used.\n\tNewWithAdmin(\"Foo\", \"FOO\", 4, 10_000*1_000_000, 0, admin)\n\tNewWithAdmin(\"Bar\", \"BAR\", 4, 10_000*1_000, 0, admin)\n\tmustGetInstance(\"FOO\").banker.Mint(manfred, 100_000_000)\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_100_000_000, func() uint64 { return TotalSupply(\"FOO\") }},\n\t\t\t{\"BalanceOf(admin)\", 10_000_000_000, func() uint64 { return BalanceOf(\"FOO\", admin) }},\n\t\t\t{\"BalanceOf(manfred)\", 100_000_000, func() uint64 { return BalanceOf(\"FOO\", manfred) }},\n\t\t\t{\"Allowance(admin, manfred)\", 0, func() uint64 { return Allowance(\"FOO\", admin, manfred) }},\n\t\t\t{\"BalanceOf(unknown)\", 0, func() uint64 { return BalanceOf(\"FOO\", unknown) }},\n\t\t}\n\t\tfor _, tc := range tests {\n\t\t\tuassert.Equal(t, tc.balance, tc.fn(), \"balance does not match\")\n\t\t}\n\t}\n\treturn\n\n\t// unknown uses the faucet.\n\tstd.TestSetOrigCaller(unknown)\n\tFaucet(\"FOO\")\n\n\t// check balances #2.\n\t{\n\t\ttests := []test{\n\t\t\t{\"TotalSupply\", 10_110_000_000, func() uint64 { return TotalSupply(\"FOO\") }},\n\t\t\t{\"BalanceOf(admin)\", 10_000_000_000, func() uint64 { return BalanceOf(\"FOO\", admin) }},\n\t\t\t{\"BalanceOf(manfred)\", 100_000_000, func() uint64 { return BalanceOf(\"FOO\", manfred) }},\n\t\t\t{\"Allowance(admin, manfred)\", 0, func() uint64 { return Allowance(\"FOO\", admin, manfred) }},\n\t\t\t{\"BalanceOf(unknown)\", 10_000_000, func() uint64 { return BalanceOf(\"FOO\", unknown) }},\n\t\t}\n\t\tfor _, tc := range tests {\n\t\t\tuassert.Equal(t, tc.balance, tc.fn(), \"balance does not match\")\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":"disperse","path":"gno.land/r/demo/disperse","files":[{"name":"disperse.gno","body":"package disperse\n\nimport (\n\t\"std\"\n\n\ttokens \"gno.land/r/demo/grc20factory\"\n)\n\n// Get address of Disperse realm\nvar realmAddr = std.CurrentRealm().Addr()\n\n// DisperseUgnot parses receivers and amounts and sends out ugnot\n// The function will send out the coins to the addresses and return the leftover coins to the caller\n// if there are any to return\nfunc DisperseUgnot(addresses []std.Address, coins std.Coins) {\n\tcoinSent := std.OrigSend()\n\tcaller := std.PrevRealm().Addr()\n\tbanker := std.GetBanker(std.BankerTypeOrigSend)\n\n\tif len(addresses) != len(coins) {\n\t\tpanic(ErrNumAddrValMismatch)\n\t}\n\n\tfor _, coin := range coins {\n\t\tif coin.Amount \u003c= 0 {\n\t\t\tpanic(ErrNegativeCoinAmount)\n\t\t}\n\n\t\tif banker.GetCoins(realmAddr).AmountOf(coin.Denom) \u003c coin.Amount {\n\t\t\tpanic(ErrMismatchBetweenSentAndParams)\n\t\t}\n\t}\n\n\t// Send coins\n\tfor i, _ := range addresses {\n\t\tbanker.SendCoins(realmAddr, addresses[i], std.NewCoins(coins[i]))\n\t}\n\n\t// Return possible leftover coins\n\tfor _, coin := range coinSent {\n\t\tleftoverAmt := banker.GetCoins(realmAddr).AmountOf(coin.Denom)\n\t\tif leftoverAmt \u003e 0 {\n\t\t\tsend := std.Coins{std.NewCoin(coin.Denom, leftoverAmt)}\n\t\t\tbanker.SendCoins(realmAddr, caller, send)\n\t\t}\n\t}\n}\n\n// DisperseGRC20 disperses tokens to multiple addresses\n// Note that it is necessary to approve the realm to spend the tokens before calling this function\n// see the corresponding filetests for examples\nfunc DisperseGRC20(addresses []std.Address, amounts []uint64, symbols []string) {\n\tcaller := std.PrevRealm().Addr()\n\n\tif (len(addresses) != len(amounts)) || (len(amounts) != len(symbols)) {\n\t\tpanic(ErrArgLenAndSentLenMismatch)\n\t}\n\n\tfor i := 0; i \u003c len(addresses); i++ {\n\t\ttokens.TransferFrom(symbols[i], caller, addresses[i], amounts[i])\n\t}\n}\n\n// DisperseGRC20String receives a string of addresses and a string of tokens\n// and parses them to be used in DisperseGRC20\nfunc DisperseGRC20String(addresses string, tokens string) {\n\tparsedAddresses, err := parseAddresses(addresses)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tparsedAmounts, parsedSymbols, err := parseTokens(tokens)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tDisperseGRC20(parsedAddresses, parsedAmounts, parsedSymbols)\n}\n\n// DisperseUgnotString receives a string of addresses and a string of amounts\n// and parses them to be used in DisperseUgnot\nfunc DisperseUgnotString(addresses string, amounts string) {\n\tparsedAddresses, err := parseAddresses(addresses)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tparsedAmounts, err := parseAmounts(amounts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tcoins := make(std.Coins, len(parsedAmounts))\n\tfor i, amount := range parsedAmounts {\n\t\tcoins[i] = std.NewCoin(\"ugnot\", amount)\n\t}\n\n\tDisperseUgnot(parsedAddresses, coins)\n}\n"},{"name":"doc.gno","body":"// Package disperse provides methods to disperse coins or GRC20 tokens among multiple addresses.\n//\n// The disperse package is an implementation of an existing service that allows users to send coins or GRC20 tokens to multiple addresses\n// on the Ethereum blockchain.\n//\n// Usage:\n// To use disperse, you can either use `DisperseUgnot` to send coins or `DisperseGRC20` to send GRC20 tokens to multiple addresses.\n//\n// Example:\n// Dispersing 200 coins to two addresses:\n// - DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n// Dispersing 200 worth of a GRC20 token \"TEST\" to two addresses:\n// - DisperseGRC20String(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150TEST,50TEST\")\n//\n// Reference:\n// - [the original dispere app](https://disperse.app/)\n// - [the original disperse app on etherscan](https://etherscan.io/address/0xd152f549545093347a162dce210e7293f1452150#code)\n// - [the gno disperse web app](https://gno-disperse.netlify.app/)\npackage disperse // import \"gno.land/r/demo/disperse\"\n"},{"name":"errors.gno","body":"package disperse\n\nimport \"errors\"\n\nvar (\n\tErrNotEnoughCoin = errors.New(\"disperse: not enough coin sent in\")\n\tErrNumAddrValMismatch = errors.New(\"disperse: number of addresses and values to send doesn't match\")\n\tErrInvalidAddress = errors.New(\"disperse: invalid address\")\n\tErrNegativeCoinAmount = errors.New(\"disperse: coin amount cannot be negative\")\n\tErrMismatchBetweenSentAndParams = errors.New(\"disperse: mismatch between coins sent and params called\")\n\tErrArgLenAndSentLenMismatch = errors.New(\"disperse: mismatch between coins sent and args called\")\n)\n"},{"name":"util.gno","body":"package disperse\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"unicode\"\n)\n\nfunc parseAddresses(addresses string) ([]std.Address, error) {\n\tvar ret []std.Address\n\n\tfor _, str := range strings.Split(addresses, \",\") {\n\t\taddr := std.Address(str)\n\t\tif !addr.IsValid() {\n\t\t\treturn nil, ErrInvalidAddress\n\t\t}\n\n\t\tret = append(ret, addr)\n\t}\n\n\treturn ret, nil\n}\n\nfunc splitString(input string) (string, string) {\n\tvar pos int\n\tfor i, char := range input {\n\t\tif !unicode.IsDigit(char) {\n\t\t\tpos = i\n\t\t\tbreak\n\t\t}\n\t}\n\treturn input[:pos], input[pos:]\n}\n\nfunc parseTokens(tokens string) ([]uint64, []string, error) {\n\tvar amounts []uint64\n\tvar symbols []string\n\n\tfor _, token := range strings.Split(tokens, \",\") {\n\t\tamountStr, symbol := splitString(token)\n\t\tamount, _ := strconv.Atoi(amountStr)\n\t\tif amount \u003c 0 {\n\t\t\treturn nil, nil, ErrNegativeCoinAmount\n\t\t}\n\n\t\tamounts = append(amounts, uint64(amount))\n\t\tsymbols = append(symbols, symbol)\n\t}\n\n\treturn amounts, symbols, nil\n}\n\nfunc parseAmounts(amounts string) ([]int64, error) {\n\tvar ret []int64\n\n\tfor _, amt := range strings.Split(amounts, \",\") {\n\t\tamount, _ := strconv.Atoi(amt)\n\t\tif amount \u003c 0 {\n\t\t\treturn nil, ErrNegativeCoinAmount\n\t\t}\n\n\t\tret = append(ret, int64(amount))\n\t}\n\n\treturn ret, nil\n}\n"},{"name":"z_0_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 200ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\n\tstd.TestSetOrigPkgAddr(disperseAddr)\n\tstd.TestSetOrigCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\tbanker.SendCoins(mainaddr, disperseAddr, std.Coins{{\"ugnot\", 200}})\n\tdisperse.DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal)\n}\n\n// Output:\n// main before: 200000200ugnot\n// main after: 200000000ugnot\n"},{"name":"z_1_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\n\tstd.TestSetOrigPkgAddr(disperseAddr)\n\tstd.TestSetOrigCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\tbanker.SendCoins(mainaddr, disperseAddr, std.Coins{{\"ugnot\", 300}})\n\tdisperse.DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal)\n}\n\n// Output:\n// main before: 200000300ugnot\n// main after: 200000100ugnot\n"},{"name":"z_2_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\n\tstd.TestSetOrigPkgAddr(disperseAddr)\n\tstd.TestSetOrigCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\tbanker.SendCoins(mainaddr, disperseAddr, std.Coins{{\"ugnot\", 100}})\n\tdisperse.DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n}\n\n// Error:\n// disperse: mismatch between coins sent and params called\n"},{"name":"z_3_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n\ttokens \"gno.land/r/demo/grc20factory\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\tbeneficiary1 := std.Address(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0\")\n\tbeneficiary2 := std.Address(\"g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\")\n\n\tstd.TestSetOrigPkgAddr(disperseAddr)\n\tstd.TestSetOrigCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\ttokens.New(\"test\", \"TEST\", 4, 0, 0)\n\ttokens.Mint(\"TEST\", mainaddr, 200)\n\n\tmainbal := tokens.BalanceOf(\"TEST\", mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\ttokens.Approve(\"TEST\", disperseAddr, 200)\n\n\tdisperse.DisperseGRC20String(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150TEST,50TEST\")\n\n\tmainbal = tokens.BalanceOf(\"TEST\", mainaddr)\n\tprintln(\"main after:\", mainbal)\n\tben1bal := tokens.BalanceOf(\"TEST\", beneficiary1)\n\tprintln(\"beneficiary1:\", ben1bal)\n\tben2bal := tokens.BalanceOf(\"TEST\", beneficiary2)\n\tprintln(\"beneficiary2:\", ben2bal)\n}\n\n// Output:\n// main before: 200\n// main after: 0\n// beneficiary1: 150\n// beneficiary2: 50\n"},{"name":"z_4_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n\ttokens \"gno.land/r/demo/grc20factory\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\tbeneficiary1 := std.Address(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0\")\n\tbeneficiary2 := std.Address(\"g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\")\n\n\tstd.TestSetOrigPkgAddr(disperseAddr)\n\tstd.TestSetOrigCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\ttokens.New(\"test1\", \"TEST1\", 4, 0, 0)\n\ttokens.Mint(\"TEST1\", mainaddr, 200)\n\ttokens.New(\"test2\", \"TEST2\", 4, 0, 0)\n\ttokens.Mint(\"TEST2\", mainaddr, 200)\n\n\tmainbal := tokens.BalanceOf(\"TEST1\", mainaddr) + tokens.BalanceOf(\"TEST2\", mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\ttokens.Approve(\"TEST1\", disperseAddr, 200)\n\ttokens.Approve(\"TEST2\", disperseAddr, 200)\n\n\tdisperse.DisperseGRC20String(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"200TEST1,200TEST2\")\n\n\tmainbal = tokens.BalanceOf(\"TEST1\", mainaddr) + tokens.BalanceOf(\"TEST2\", mainaddr)\n\tprintln(\"main after:\", mainbal)\n\tben1bal := tokens.BalanceOf(\"TEST1\", beneficiary1) + tokens.BalanceOf(\"TEST2\", beneficiary1)\n\tprintln(\"beneficiary1:\", ben1bal)\n\tben2bal := tokens.BalanceOf(\"TEST1\", beneficiary2) + tokens.BalanceOf(\"TEST2\", beneficiary2)\n\tprintln(\"beneficiary2:\", ben2bal)\n}\n\n// Output:\n// main before: 400\n// main after: 0\n// beneficiary1: 200\n// beneficiary2: 200\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)\n\nvar instances avl.Tree // symbol -\u003e instance\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\tbanker := grc20.NewBanker(name, symbol, decimals)\n\tif initialMint \u003e 0 {\n\t\tbanker.Mint(admin, initialMint)\n\t}\n\n\tinst := instance{\n\t\tbanker: banker,\n\t\tadmin: ownable.NewWithAddress(admin),\n\t\tfaucet: faucet,\n\t}\n\n\tinstances.Set(symbol, \u0026inst)\n}\n\ntype instance struct {\n\tbanker *grc20.Banker\n\tadmin *ownable.Ownable\n\tfaucet uint64 // per-request amount. disabled if 0.\n}\n\nfunc (inst instance) Token() grc20.Token { return inst.banker.Token() }\n\nfunc TotalSupply(symbol string) uint64 {\n\tinst := mustGetInstance(symbol)\n\treturn inst.Token().TotalSupply()\n}\n\nfunc BalanceOf(symbol string, owner std.Address) uint64 {\n\tinst := mustGetInstance(symbol)\n\treturn inst.Token().BalanceOf(owner)\n}\n\nfunc Allowance(symbol string, owner, spender std.Address) uint64 {\n\tinst := mustGetInstance(symbol)\n\treturn inst.Token().Allowance(owner, spender)\n}\n\nfunc Transfer(symbol string, to std.Address, amount uint64) {\n\tinst := mustGetInstance(symbol)\n\tcheckErr(inst.Token().Transfer(to, amount))\n}\n\nfunc Approve(symbol string, spender std.Address, amount uint64) {\n\tinst := mustGetInstance(symbol)\n\tcheckErr(inst.Token().Approve(spender, amount))\n}\n\nfunc TransferFrom(symbol string, from, to std.Address, amount uint64) {\n\tinst := mustGetInstance(symbol)\n\tcheckErr(inst.Token().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.banker.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.banker.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.banker.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.banker.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().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)\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/uassert\"\n)\n\nfunc TestReadOnlyPublicMethods(t *testing.T) {\n\tadmin := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\tmanfred := std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\tunknown := std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\") // valid but never used.\n\tNewWithAdmin(\"Foo\", \"FOO\", 4, 10_000*1_000_000, 0, admin)\n\tNewWithAdmin(\"Bar\", \"BAR\", 4, 10_000*1_000, 0, admin)\n\tmustGetInstance(\"FOO\").banker.Mint(manfred, 100_000_000)\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_100_000_000, func() uint64 { return TotalSupply(\"FOO\") }},\n\t\t\t{\"BalanceOf(admin)\", 10_000_000_000, func() uint64 { return BalanceOf(\"FOO\", admin) }},\n\t\t\t{\"BalanceOf(manfred)\", 100_000_000, func() uint64 { return BalanceOf(\"FOO\", manfred) }},\n\t\t\t{\"Allowance(admin, manfred)\", 0, func() uint64 { return Allowance(\"FOO\", admin, manfred) }},\n\t\t\t{\"BalanceOf(unknown)\", 0, func() uint64 { return BalanceOf(\"FOO\", unknown) }},\n\t\t}\n\t\tfor _, tc := range tests {\n\t\t\tuassert.Equal(t, tc.balance, tc.fn(), \"balance does not match\")\n\t\t}\n\t}\n\treturn\n\n\t// unknown uses the faucet.\n\tstd.TestSetOriginCaller(unknown)\n\tFaucet(\"FOO\")\n\n\t// check balances #2.\n\t{\n\t\ttests := []test{\n\t\t\t{\"TotalSupply\", 10_110_000_000, func() uint64 { return TotalSupply(\"FOO\") }},\n\t\t\t{\"BalanceOf(admin)\", 10_000_000_000, func() uint64 { return BalanceOf(\"FOO\", admin) }},\n\t\t\t{\"BalanceOf(manfred)\", 100_000_000, func() uint64 { return BalanceOf(\"FOO\", manfred) }},\n\t\t\t{\"Allowance(admin, manfred)\", 0, func() uint64 { return Allowance(\"FOO\", admin, manfred) }},\n\t\t\t{\"BalanceOf(unknown)\", 10_000_000, func() uint64 { return BalanceOf(\"FOO\", unknown) }},\n\t\t}\n\t\tfor _, tc := range tests {\n\t\t\tuassert.Equal(t, tc.balance, tc.fn(), \"balance does not match\")\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":"disperse","path":"gno.land/r/demo/disperse","files":[{"name":"disperse.gno","body":"package disperse\n\nimport (\n\t\"std\"\n\n\ttokens \"gno.land/r/demo/grc20factory\"\n)\n\n// Get address of Disperse realm\nvar realmAddr = std.CurrentRealm().Addr()\n\n// DisperseUgnot parses receivers and amounts and sends out ugnot\n// The function will send out the coins to the addresses and return the leftover coins to the caller\n// if there are any to return\nfunc DisperseUgnot(addresses []std.Address, coins std.Coins) {\n\tcoinSent := std.OriginSend()\n\tcaller := std.PrevRealm().Addr()\n\tbanker := std.GetBanker(std.BankerTypeOriginSend)\n\n\tif len(addresses) != len(coins) {\n\t\tpanic(ErrNumAddrValMismatch)\n\t}\n\n\tfor _, coin := range coins {\n\t\tif coin.Amount \u003c= 0 {\n\t\t\tpanic(ErrNegativeCoinAmount)\n\t\t}\n\n\t\tif banker.GetCoins(realmAddr).AmountOf(coin.Denom) \u003c coin.Amount {\n\t\t\tpanic(ErrMismatchBetweenSentAndParams)\n\t\t}\n\t}\n\n\t// Send coins\n\tfor i, _ := range addresses {\n\t\tbanker.SendCoins(realmAddr, addresses[i], std.NewCoins(coins[i]))\n\t}\n\n\t// Return possible leftover coins\n\tfor _, coin := range coinSent {\n\t\tleftoverAmt := banker.GetCoins(realmAddr).AmountOf(coin.Denom)\n\t\tif leftoverAmt \u003e 0 {\n\t\t\tsend := std.Coins{std.NewCoin(coin.Denom, leftoverAmt)}\n\t\t\tbanker.SendCoins(realmAddr, caller, send)\n\t\t}\n\t}\n}\n\n// DisperseGRC20 disperses tokens to multiple addresses\n// Note that it is necessary to approve the realm to spend the tokens before calling this function\n// see the corresponding filetests for examples\nfunc DisperseGRC20(addresses []std.Address, amounts []uint64, symbols []string) {\n\tcaller := std.PrevRealm().Addr()\n\n\tif (len(addresses) != len(amounts)) || (len(amounts) != len(symbols)) {\n\t\tpanic(ErrArgLenAndSentLenMismatch)\n\t}\n\n\tfor i := 0; i \u003c len(addresses); i++ {\n\t\ttokens.TransferFrom(symbols[i], caller, addresses[i], amounts[i])\n\t}\n}\n\n// DisperseGRC20String receives a string of addresses and a string of tokens\n// and parses them to be used in DisperseGRC20\nfunc DisperseGRC20String(addresses string, tokens string) {\n\tparsedAddresses, err := parseAddresses(addresses)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tparsedAmounts, parsedSymbols, err := parseTokens(tokens)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tDisperseGRC20(parsedAddresses, parsedAmounts, parsedSymbols)\n}\n\n// DisperseUgnotString receives a string of addresses and a string of amounts\n// and parses them to be used in DisperseUgnot\nfunc DisperseUgnotString(addresses string, amounts string) {\n\tparsedAddresses, err := parseAddresses(addresses)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tparsedAmounts, err := parseAmounts(amounts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tcoins := make(std.Coins, len(parsedAmounts))\n\tfor i, amount := range parsedAmounts {\n\t\tcoins[i] = std.NewCoin(\"ugnot\", amount)\n\t}\n\n\tDisperseUgnot(parsedAddresses, coins)\n}\n"},{"name":"doc.gno","body":"// Package disperse provides methods to disperse coins or GRC20 tokens among multiple addresses.\n//\n// The disperse package is an implementation of an existing service that allows users to send coins or GRC20 tokens to multiple addresses\n// on the Ethereum blockchain.\n//\n// Usage:\n// To use disperse, you can either use `DisperseUgnot` to send coins or `DisperseGRC20` to send GRC20 tokens to multiple addresses.\n//\n// Example:\n// Dispersing 200 coins to two addresses:\n// - DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n// Dispersing 200 worth of a GRC20 token \"TEST\" to two addresses:\n// - DisperseGRC20String(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150TEST,50TEST\")\n//\n// Reference:\n// - [the original dispere app](https://disperse.app/)\n// - [the original disperse app on etherscan](https://etherscan.io/address/0xd152f549545093347a162dce210e7293f1452150#code)\n// - [the gno disperse web app](https://gno-disperse.netlify.app/)\npackage disperse // import \"gno.land/r/demo/disperse\"\n"},{"name":"errors.gno","body":"package disperse\n\nimport \"errors\"\n\nvar (\n\tErrNotEnoughCoin = errors.New(\"disperse: not enough coin sent in\")\n\tErrNumAddrValMismatch = errors.New(\"disperse: number of addresses and values to send doesn't match\")\n\tErrInvalidAddress = errors.New(\"disperse: invalid address\")\n\tErrNegativeCoinAmount = errors.New(\"disperse: coin amount cannot be negative\")\n\tErrMismatchBetweenSentAndParams = errors.New(\"disperse: mismatch between coins sent and params called\")\n\tErrArgLenAndSentLenMismatch = errors.New(\"disperse: mismatch between coins sent and args called\")\n)\n"},{"name":"util.gno","body":"package disperse\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"unicode\"\n)\n\nfunc parseAddresses(addresses string) ([]std.Address, error) {\n\tvar ret []std.Address\n\n\tfor _, str := range strings.Split(addresses, \",\") {\n\t\taddr := std.Address(str)\n\t\tif !addr.IsValid() {\n\t\t\treturn nil, ErrInvalidAddress\n\t\t}\n\n\t\tret = append(ret, addr)\n\t}\n\n\treturn ret, nil\n}\n\nfunc splitString(input string) (string, string) {\n\tvar pos int\n\tfor i, char := range input {\n\t\tif !unicode.IsDigit(char) {\n\t\t\tpos = i\n\t\t\tbreak\n\t\t}\n\t}\n\treturn input[:pos], input[pos:]\n}\n\nfunc parseTokens(tokens string) ([]uint64, []string, error) {\n\tvar amounts []uint64\n\tvar symbols []string\n\n\tfor _, token := range strings.Split(tokens, \",\") {\n\t\tamountStr, symbol := splitString(token)\n\t\tamount, _ := strconv.Atoi(amountStr)\n\t\tif amount \u003c 0 {\n\t\t\treturn nil, nil, ErrNegativeCoinAmount\n\t\t}\n\n\t\tamounts = append(amounts, uint64(amount))\n\t\tsymbols = append(symbols, symbol)\n\t}\n\n\treturn amounts, symbols, nil\n}\n\nfunc parseAmounts(amounts string) ([]int64, error) {\n\tvar ret []int64\n\n\tfor _, amt := range strings.Split(amounts, \",\") {\n\t\tamount, _ := strconv.Atoi(amt)\n\t\tif amount \u003c 0 {\n\t\t\treturn nil, ErrNegativeCoinAmount\n\t\t}\n\n\t\tret = append(ret, int64(amount))\n\t}\n\n\treturn ret, nil\n}\n"},{"name":"z_0_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 200ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\n\tstd.TestSetOrigPkgAddr(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\tbanker.SendCoins(mainaddr, disperseAddr, std.Coins{{\"ugnot\", 200}})\n\tdisperse.DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal)\n}\n\n// Output:\n// main before: 200000200ugnot\n// main after: 200000000ugnot\n"},{"name":"z_1_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\n\tstd.TestSetOrigPkgAddr(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\tbanker.SendCoins(mainaddr, disperseAddr, std.Coins{{\"ugnot\", 300}})\n\tdisperse.DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal)\n}\n\n// Output:\n// main before: 200000300ugnot\n// main after: 200000100ugnot\n"},{"name":"z_2_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\n\tstd.TestSetOrigPkgAddr(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\tbanker.SendCoins(mainaddr, disperseAddr, std.Coins{{\"ugnot\", 100}})\n\tdisperse.DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n}\n\n// Error:\n// disperse: mismatch between coins sent and params called\n"},{"name":"z_3_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n\ttokens \"gno.land/r/demo/grc20factory\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\tbeneficiary1 := std.Address(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0\")\n\tbeneficiary2 := std.Address(\"g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\")\n\n\tstd.TestSetOrigPkgAddr(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\ttokens.New(\"test\", \"TEST\", 4, 0, 0)\n\ttokens.Mint(\"TEST\", mainaddr, 200)\n\n\tmainbal := tokens.BalanceOf(\"TEST\", mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\ttokens.Approve(\"TEST\", disperseAddr, 200)\n\n\tdisperse.DisperseGRC20String(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150TEST,50TEST\")\n\n\tmainbal = tokens.BalanceOf(\"TEST\", mainaddr)\n\tprintln(\"main after:\", mainbal)\n\tben1bal := tokens.BalanceOf(\"TEST\", beneficiary1)\n\tprintln(\"beneficiary1:\", ben1bal)\n\tben2bal := tokens.BalanceOf(\"TEST\", beneficiary2)\n\tprintln(\"beneficiary2:\", ben2bal)\n}\n\n// Output:\n// main before: 200\n// main after: 0\n// beneficiary1: 150\n// beneficiary2: 50\n"},{"name":"z_4_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n\ttokens \"gno.land/r/demo/grc20factory\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\tbeneficiary1 := std.Address(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0\")\n\tbeneficiary2 := std.Address(\"g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\")\n\n\tstd.TestSetOrigPkgAddr(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\ttokens.New(\"test1\", \"TEST1\", 4, 0, 0)\n\ttokens.Mint(\"TEST1\", mainaddr, 200)\n\ttokens.New(\"test2\", \"TEST2\", 4, 0, 0)\n\ttokens.Mint(\"TEST2\", mainaddr, 200)\n\n\tmainbal := tokens.BalanceOf(\"TEST1\", mainaddr) + tokens.BalanceOf(\"TEST2\", mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\ttokens.Approve(\"TEST1\", disperseAddr, 200)\n\ttokens.Approve(\"TEST2\", disperseAddr, 200)\n\n\tdisperse.DisperseGRC20String(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"200TEST1,200TEST2\")\n\n\tmainbal = tokens.BalanceOf(\"TEST1\", mainaddr) + tokens.BalanceOf(\"TEST2\", mainaddr)\n\tprintln(\"main after:\", mainbal)\n\tben1bal := tokens.BalanceOf(\"TEST1\", beneficiary1) + tokens.BalanceOf(\"TEST2\", beneficiary1)\n\tprintln(\"beneficiary1:\", ben1bal)\n\tben2bal := tokens.BalanceOf(\"TEST1\", beneficiary2) + tokens.BalanceOf(\"TEST2\", beneficiary2)\n\tprintln(\"beneficiary2:\", ben2bal)\n}\n\n// Output:\n// main before: 400\n// main after: 0\n// beneficiary1: 200\n// beneficiary2: 200\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":"echo","path":"gno.land/r/demo/echo","files":[{"name":"echo.gno","body":"package echo\n\n/*\n * This realm echoes the `path` argument it received.\n * Can be used by developers as a simple endpoint to test\n * forbidden characters, for pentesting or simply to\n * test it works.\n *\n * See also r/demo/print (to print various thing like user address)\n */\nfunc Render(path string) string {\n\treturn path\n}\n"},{"name":"echo_test.gno","body":"package echo\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc Test(t *testing.T) {\n\turequire.Equal(t, \"aa\", Render(\"aa\"))\n\turequire.Equal(t, \"\", Render(\"\"))\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":"event","path":"gno.land/r/demo/event","files":[{"name":"event.gno","body":"package event\n\nimport (\n\t\"std\"\n)\n\nfunc Emit(value string) {\n\tstd.Emit(\"TAG\", \"key\", value)\n}\n"},{"name":"z1_filetest.gno","body":"package main\n\nimport \"gno.land/r/demo/event\"\n\nfunc main() {\n\tevent.Emit(\"foo\")\n\tevent.Emit(\"bar\")\n}\n\n// Events:\n// [\n// {\n// \"type\": \"TAG\",\n// \"attrs\": [\n// {\n// \"key\": \"key\",\n// \"value\": \"foo\"\n// }\n// ],\n// \"pkg_path\": \"gno.land/r/demo/event\",\n// \"func\": \"Emit\"\n// },\n// {\n// \"type\": \"TAG\",\n// \"attrs\": [\n// {\n// \"key\": \"key\",\n// \"value\": \"bar\"\n// }\n// ],\n// \"pkg_path\": \"gno.land/r/demo/event\",\n// \"func\": \"Emit\"\n// }\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":"foo1155","path":"gno.land/r/demo/foo1155","files":[{"name":"foo1155.gno","body":"package foo1155\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/grc/grc1155\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/r/demo/users\"\n\n\tpusers \"gno.land/p/demo/users\"\n)\n\nvar (\n\tdummyURI = \"ipfs://xyz\"\n\tadmin std.Address = \"g10x5phu0k6p64cwrhfpsc8tk43st9kug6wft530\"\n\tfoo = grc1155.NewBasicGRC1155Token(dummyURI)\n)\n\nfunc init() {\n\tmintGRC1155Token(admin) // @administrator (10)\n}\n\nfunc mintGRC1155Token(owner std.Address) {\n\tfor i := 1; i \u003c= 10; i++ {\n\t\ttid := grc1155.TokenID(ufmt.Sprintf(\"%d\", i))\n\t\tfoo.SafeMint(owner, tid, 100)\n\t}\n}\n\n// Getters\n\nfunc BalanceOf(user pusers.AddressOrName, tid grc1155.TokenID) uint64 {\n\tbalance, err := foo.BalanceOf(users.Resolve(user), tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn balance\n}\n\nfunc BalanceOfBatch(ul []pusers.AddressOrName, batch []grc1155.TokenID) []uint64 {\n\tvar usersResolved []std.Address\n\n\tfor i := 0; i \u003c len(ul); i++ {\n\t\tusersResolved[i] = users.Resolve(ul[i])\n\t}\n\tbalanceBatch, err := foo.BalanceOfBatch(usersResolved, batch)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn balanceBatch\n}\n\nfunc IsApprovedForAll(owner, user pusers.AddressOrName) bool {\n\treturn foo.IsApprovedForAll(users.Resolve(owner), users.Resolve(user))\n}\n\n// Setters\n\nfunc SetApprovalForAll(user pusers.AddressOrName, approved bool) {\n\terr := foo.SetApprovalForAll(users.Resolve(user), approved)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc TransferFrom(from, to pusers.AddressOrName, tid grc1155.TokenID, amount uint64) {\n\terr := foo.SafeTransferFrom(users.Resolve(from), users.Resolve(to), tid, amount)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc BatchTransferFrom(from, to pusers.AddressOrName, batch []grc1155.TokenID, amounts []uint64) {\n\terr := foo.SafeBatchTransferFrom(users.Resolve(from), users.Resolve(to), batch, amounts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// Admin\n\nfunc Mint(to pusers.AddressOrName, tid grc1155.TokenID, amount uint64) {\n\tcaller := std.OrigCaller()\n\tassertIsAdmin(caller)\n\terr := foo.SafeMint(users.Resolve(to), tid, amount)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc MintBatch(to pusers.AddressOrName, batch []grc1155.TokenID, amounts []uint64) {\n\tcaller := std.OrigCaller()\n\tassertIsAdmin(caller)\n\terr := foo.SafeBatchMint(users.Resolve(to), batch, amounts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc Burn(from pusers.AddressOrName, tid grc1155.TokenID, amount uint64) {\n\tcaller := std.OrigCaller()\n\tassertIsAdmin(caller)\n\terr := foo.Burn(users.Resolve(from), tid, amount)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc BurnBatch(from pusers.AddressOrName, batch []grc1155.TokenID, amounts []uint64) {\n\tcaller := std.OrigCaller()\n\tassertIsAdmin(caller)\n\terr := foo.BatchBurn(users.Resolve(from), batch, amounts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// Render\n\nfunc Render(path string) string {\n\tswitch {\n\tcase path == \"\":\n\t\treturn foo.RenderHome()\n\tdefault:\n\t\treturn \"404\\n\"\n\t}\n}\n\n// Util\n\nfunc assertIsAdmin(address std.Address) {\n\tif address != admin {\n\t\tpanic(\"restricted access\")\n\t}\n}\n"},{"name":"foo1155_test.gno","body":"package foo1155\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/grc/grc1155\"\n\t\"gno.land/p/demo/users\"\n)\n\nfunc TestFoo721(t *testing.T) {\n\tadmin := users.AddressOrName(\"g10x5phu0k6p64cwrhfpsc8tk43st9kug6wft530\")\n\tbob := users.AddressOrName(\"g1ze6et22ces5atv79y4xh38s4kuraey4y2fr6tw\")\n\ttid1 := grc1155.TokenID(\"1\")\n\ttid2 := grc1155.TokenID(\"2\")\n\n\tfor i, tc := range []struct {\n\t\tname string\n\t\texpected interface{}\n\t\tfn func() interface{}\n\t}{\n\t\t{\"BalanceOf(admin, tid1)\", uint64(100), func() interface{} { return BalanceOf(admin, tid1) }},\n\t\t{\"BalanceOf(bob, tid1)\", uint64(0), func() interface{} { return BalanceOf(bob, tid1) }},\n\t\t{\"IsApprovedForAll(admin, bob)\", false, func() interface{} { return IsApprovedForAll(admin, bob) }},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := tc.fn()\n\t\t\tif tc.expected != got {\n\t\t\t\tt.Errorf(\"expected: %v got: %v\", tc.expected, got)\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/foo20","files":[{"name":"foo20.gno","body":"// foo20 is a GRC20 token contract where all the GRC20 methods are proxified\n// 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/users\"\n)\n\nvar (\n\tbanker *grc20.Banker\n\tadmin *ownable.Ownable\n\ttoken grc20.Token\n)\n\nfunc init() {\n\tadmin = ownable.NewWithAddress(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\") // @manfred\n\tbanker = grc20.NewBanker(\"Foo\", \"FOO\", 4)\n\tbanker.Mint(admin.Owner(), 1000000*10000) // @administrator (1M)\n\ttoken = banker.Token()\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\tcheckErr(token.Transfer(toAddr, amount))\n}\n\nfunc Approve(spender pusers.AddressOrName, amount uint64) {\n\tspenderAddr := users.Resolve(spender)\n\tcheckErr(token.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(token.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(banker.Mint(caller, amount))\n}\n\nfunc Mint(to pusers.AddressOrName, amount uint64) {\n\tadmin.AssertCallerIsOwner()\n\ttoAddr := users.Resolve(to)\n\tcheckErr(banker.Mint(toAddr, amount))\n}\n\nfunc Burn(from pusers.AddressOrName, amount uint64) {\n\tadmin.AssertCallerIsOwner()\n\tfromAddr := users.Resolve(from)\n\tcheckErr(banker.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 banker.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 := banker.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(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\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(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\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\tstd.TestSetOrigCaller(users.Resolve(admin))\n\t{\n\t\ttests := []test{\n\t\t\t{\"Transfer(admin, 1)\", \"cannot send transfer to self\", func() { Transfer(admin, 1) }},\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":"foo1155","path":"gno.land/r/demo/foo1155","files":[{"name":"foo1155.gno","body":"package foo1155\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/grc/grc1155\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/r/demo/users\"\n\n\tpusers \"gno.land/p/demo/users\"\n)\n\nvar (\n\tdummyURI = \"ipfs://xyz\"\n\tadmin std.Address = \"g10x5phu0k6p64cwrhfpsc8tk43st9kug6wft530\"\n\tfoo = grc1155.NewBasicGRC1155Token(dummyURI)\n)\n\nfunc init() {\n\tmintGRC1155Token(admin) // @administrator (10)\n}\n\nfunc mintGRC1155Token(owner std.Address) {\n\tfor i := 1; i \u003c= 10; i++ {\n\t\ttid := grc1155.TokenID(ufmt.Sprintf(\"%d\", i))\n\t\tfoo.SafeMint(owner, tid, 100)\n\t}\n}\n\n// Getters\n\nfunc BalanceOf(user pusers.AddressOrName, tid grc1155.TokenID) uint64 {\n\tbalance, err := foo.BalanceOf(users.Resolve(user), tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn balance\n}\n\nfunc BalanceOfBatch(ul []pusers.AddressOrName, batch []grc1155.TokenID) []uint64 {\n\tvar usersResolved []std.Address\n\n\tfor i := 0; i \u003c len(ul); i++ {\n\t\tusersResolved[i] = users.Resolve(ul[i])\n\t}\n\tbalanceBatch, err := foo.BalanceOfBatch(usersResolved, batch)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn balanceBatch\n}\n\nfunc IsApprovedForAll(owner, user pusers.AddressOrName) bool {\n\treturn foo.IsApprovedForAll(users.Resolve(owner), users.Resolve(user))\n}\n\n// Setters\n\nfunc SetApprovalForAll(user pusers.AddressOrName, approved bool) {\n\terr := foo.SetApprovalForAll(users.Resolve(user), approved)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc TransferFrom(from, to pusers.AddressOrName, tid grc1155.TokenID, amount uint64) {\n\terr := foo.SafeTransferFrom(users.Resolve(from), users.Resolve(to), tid, amount)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc BatchTransferFrom(from, to pusers.AddressOrName, batch []grc1155.TokenID, amounts []uint64) {\n\terr := foo.SafeBatchTransferFrom(users.Resolve(from), users.Resolve(to), batch, amounts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// Admin\n\nfunc Mint(to pusers.AddressOrName, tid grc1155.TokenID, amount uint64) {\n\tcaller := std.OriginCaller()\n\tassertIsAdmin(caller)\n\terr := foo.SafeMint(users.Resolve(to), tid, amount)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc MintBatch(to pusers.AddressOrName, batch []grc1155.TokenID, amounts []uint64) {\n\tcaller := std.OriginCaller()\n\tassertIsAdmin(caller)\n\terr := foo.SafeBatchMint(users.Resolve(to), batch, amounts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc Burn(from pusers.AddressOrName, tid grc1155.TokenID, amount uint64) {\n\tcaller := std.OriginCaller()\n\tassertIsAdmin(caller)\n\terr := foo.Burn(users.Resolve(from), tid, amount)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc BurnBatch(from pusers.AddressOrName, batch []grc1155.TokenID, amounts []uint64) {\n\tcaller := std.OriginCaller()\n\tassertIsAdmin(caller)\n\terr := foo.BatchBurn(users.Resolve(from), batch, amounts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// Render\n\nfunc Render(path string) string {\n\tswitch {\n\tcase path == \"\":\n\t\treturn foo.RenderHome()\n\tdefault:\n\t\treturn \"404\\n\"\n\t}\n}\n\n// Util\n\nfunc assertIsAdmin(address std.Address) {\n\tif address != admin {\n\t\tpanic(\"restricted access\")\n\t}\n}\n"},{"name":"foo1155_test.gno","body":"package foo1155\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/grc/grc1155\"\n\t\"gno.land/p/demo/users\"\n)\n\nfunc TestFoo721(t *testing.T) {\n\tadmin := users.AddressOrName(\"g10x5phu0k6p64cwrhfpsc8tk43st9kug6wft530\")\n\tbob := users.AddressOrName(\"g1ze6et22ces5atv79y4xh38s4kuraey4y2fr6tw\")\n\ttid1 := grc1155.TokenID(\"1\")\n\ttid2 := grc1155.TokenID(\"2\")\n\n\tfor i, tc := range []struct {\n\t\tname string\n\t\texpected interface{}\n\t\tfn func() interface{}\n\t}{\n\t\t{\"BalanceOf(admin, tid1)\", uint64(100), func() interface{} { return BalanceOf(admin, tid1) }},\n\t\t{\"BalanceOf(bob, tid1)\", uint64(0), func() interface{} { return BalanceOf(bob, tid1) }},\n\t\t{\"IsApprovedForAll(admin, bob)\", false, func() interface{} { return IsApprovedForAll(admin, bob) }},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := tc.fn()\n\t\t\tif tc.expected != got {\n\t\t\t\tt.Errorf(\"expected: %v got: %v\", tc.expected, got)\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/foo20","files":[{"name":"foo20.gno","body":"// foo20 is a GRC20 token contract where all the GRC20 methods are proxified\n// 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/users\"\n)\n\nvar (\n\tbanker *grc20.Banker\n\tadmin *ownable.Ownable\n\ttoken grc20.Token\n)\n\nfunc init() {\n\tadmin = ownable.NewWithAddress(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\") // @manfred\n\tbanker = grc20.NewBanker(\"Foo\", \"FOO\", 4)\n\tbanker.Mint(admin.Owner(), 1000000*10000) // @administrator (1M)\n\ttoken = banker.Token()\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\tcheckErr(token.Transfer(toAddr, amount))\n}\n\nfunc Approve(spender pusers.AddressOrName, amount uint64) {\n\tspenderAddr := users.Resolve(spender)\n\tcheckErr(token.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(token.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(banker.Mint(caller, amount))\n}\n\nfunc Mint(to pusers.AddressOrName, amount uint64) {\n\tadmin.AssertCallerIsOwner()\n\ttoAddr := users.Resolve(to)\n\tcheckErr(banker.Mint(toAddr, amount))\n}\n\nfunc Burn(from pusers.AddressOrName, amount uint64) {\n\tadmin.AssertCallerIsOwner()\n\tfromAddr := users.Resolve(from)\n\tcheckErr(banker.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 banker.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 := banker.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(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\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.TestSetOriginCaller(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(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\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\tstd.TestSetOriginCaller(users.Resolve(admin))\n\t{\n\t\ttests := []test{\n\t\t\t{\"Transfer(admin, 1)\", \"cannot send transfer to self\", func() { Transfer(admin, 1) }},\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":"foo721","path":"gno.land/r/demo/foo721","files":[{"name":"foo721.gno","body":"package foo721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/grc/grc721\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/r/demo/users\"\n\n\tpusers \"gno.land/p/demo/users\"\n)\n\nvar (\n\tadmin std.Address = \"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\"\n\tfoo = grc721.NewBasicNFT(\"FooNFT\", \"FNFT\")\n)\n\nfunc init() {\n\tmintNNFT(admin, 10) // @administrator (10)\n\tmintNNFT(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\", 5) // @hariom (5)\n}\n\nfunc mintNNFT(owner std.Address, n uint64) {\n\tcount := foo.TokenCount()\n\tfor i := count; i \u003c count+n; i++ {\n\t\ttid := grc721.TokenID(ufmt.Sprintf(\"%d\", i))\n\t\tfoo.Mint(owner, tid)\n\t}\n}\n\n// Getters\n\nfunc BalanceOf(user pusers.AddressOrName) uint64 {\n\tbalance, err := foo.BalanceOf(users.Resolve(user))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn balance\n}\n\nfunc OwnerOf(tid grc721.TokenID) std.Address {\n\towner, err := foo.OwnerOf(tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn owner\n}\n\nfunc IsApprovedForAll(owner, user pusers.AddressOrName) bool {\n\treturn foo.IsApprovedForAll(users.Resolve(owner), users.Resolve(user))\n}\n\nfunc GetApproved(tid grc721.TokenID) std.Address {\n\taddr, err := foo.GetApproved(tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn addr\n}\n\n// Setters\n\nfunc Approve(user pusers.AddressOrName, tid grc721.TokenID) {\n\terr := foo.Approve(users.Resolve(user), tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc SetApprovalForAll(user pusers.AddressOrName, approved bool) {\n\terr := foo.SetApprovalForAll(users.Resolve(user), approved)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc TransferFrom(from, to pusers.AddressOrName, tid grc721.TokenID) {\n\terr := foo.TransferFrom(users.Resolve(from), users.Resolve(to), tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// Admin\n\nfunc Mint(to pusers.AddressOrName, tid grc721.TokenID) {\n\tcaller := std.PrevRealm().Addr()\n\tassertIsAdmin(caller)\n\terr := foo.Mint(users.Resolve(to), tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc Burn(tid grc721.TokenID) {\n\tcaller := std.PrevRealm().Addr()\n\tassertIsAdmin(caller)\n\terr := foo.Burn(tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// Render\n\nfunc Render(path string) string {\n\tswitch {\n\tcase path == \"\":\n\t\treturn foo.RenderHome()\n\tdefault:\n\t\treturn \"404\\n\"\n\t}\n}\n\n// Util\n\nfunc assertIsAdmin(address std.Address) {\n\tif address != admin {\n\t\tpanic(\"restricted access\")\n\t}\n}\n"},{"name":"foo721_test.gno","body":"package foo721\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/grc/grc721\"\n\t\"gno.land/r/demo/users\"\n\n\tpusers \"gno.land/p/demo/users\"\n)\n\nfunc TestFoo721(t *testing.T) {\n\tadmin := pusers.AddressOrName(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\thariom := pusers.AddressOrName(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tfor i, tc := range []struct {\n\t\tname string\n\t\texpected interface{}\n\t\tfn func() interface{}\n\t}{\n\t\t{\"BalanceOf(admin)\", uint64(10), func() interface{} { return BalanceOf(admin) }},\n\t\t{\"BalanceOf(hariom)\", uint64(5), func() interface{} { return BalanceOf(hariom) }},\n\t\t{\"OwnerOf(0)\", users.Resolve(admin), func() interface{} { return OwnerOf(grc721.TokenID(\"0\")) }},\n\t\t{\"IsApprovedForAll(admin, hariom)\", false, func() interface{} { return IsApprovedForAll(admin, hariom) }},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := tc.fn()\n\t\t\tif tc.expected != got {\n\t\t\t\tt.Errorf(\"expected: %v got: %v\", tc.expected, got)\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":"dice_roller","path":"gno.land/r/demo/games/dice_roller","files":[{"name":"dice_roller.gno","body":"package dice_roller\n\nimport (\n\t\"errors\"\n\t\"math/rand\"\n\t\"sort\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/entropy\"\n\t\"gno.land/p/demo/seqid\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/r/demo/users\"\n)\n\ntype (\n\t// game represents a Dice Roller game between two players\n\tgame struct {\n\t\tplayer1, player2 std.Address\n\t\troll1, roll2 int\n\t}\n\n\t// player holds the information about each player including their stats\n\tplayer struct {\n\t\taddr std.Address\n\t\twins, losses, draws, points int\n\t}\n\n\t// leaderBoard is a slice of players, used to sort players by rank\n\tleaderBoard []player\n)\n\nconst (\n\t// Constants to represent game result outcomes\n\tongoing = iota\n\twin\n\tdraw\n\tloss\n)\n\nvar (\n\tgames avl.Tree // AVL tree for storing game states\n\tgameId seqid.ID // Sequence ID for games\n\n\tplayers avl.Tree // AVL tree for storing player data\n\n\tseed = uint64(entropy.New().Seed())\n\tr = rand.New(rand.NewPCG(seed, 0xdeadbeef))\n)\n\n// rollDice generates a random dice roll between 1 and 6\nfunc rollDice() int {\n\treturn r.IntN(6) + 1\n}\n\n// NewGame initializes a new game with the provided opponent's address\nfunc NewGame(addr std.Address) int {\n\tif !addr.IsValid() {\n\t\tpanic(\"invalid opponent's address\")\n\t}\n\n\tgames.Set(gameId.Next().String(), \u0026game{\n\t\tplayer1: std.PrevRealm().Addr(),\n\t\tplayer2: addr,\n\t})\n\n\treturn int(gameId)\n}\n\n// Play allows a player to roll the dice and updates the game state accordingly\nfunc Play(idx int) int {\n\tg, err := getGame(idx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\troll := rollDice() // Random the player's dice roll\n\n\t// Play the game and update the player's roll\n\tif err := g.play(std.PrevRealm().Addr(), roll); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// If both players have rolled, update the results and leaderboard\n\tif g.isFinished() {\n\t\t// If the player is playing against themselves, no points are awarded\n\t\tif g.player1 == g.player2 {\n\t\t\treturn roll\n\t\t}\n\n\t\tplayer1 := getPlayer(g.player1)\n\t\tplayer2 := getPlayer(g.player2)\n\n\t\tif g.roll1 \u003e g.roll2 {\n\t\t\tplayer1.updateStats(win)\n\t\t\tplayer2.updateStats(loss)\n\t\t} else if g.roll2 \u003e g.roll1 {\n\t\t\tplayer2.updateStats(win)\n\t\t\tplayer1.updateStats(loss)\n\t\t} else {\n\t\t\tplayer1.updateStats(draw)\n\t\t\tplayer2.updateStats(draw)\n\t\t}\n\t}\n\n\treturn roll\n}\n\n// play processes a player's roll and updates their score\nfunc (g *game) play(player std.Address, roll int) error {\n\tif player != g.player1 \u0026\u0026 player != g.player2 {\n\t\treturn errors.New(\"invalid player\")\n\t}\n\n\tif g.isFinished() {\n\t\treturn errors.New(\"game over\")\n\t}\n\n\tif player == g.player1 \u0026\u0026 g.roll1 == 0 {\n\t\tg.roll1 = roll\n\t\treturn nil\n\t}\n\n\tif player == g.player2 \u0026\u0026 g.roll2 == 0 {\n\t\tg.roll2 = roll\n\t\treturn nil\n\t}\n\n\treturn errors.New(\"already played\")\n}\n\n// isFinished checks if the game has ended\nfunc (g *game) isFinished() bool {\n\treturn g.roll1 != 0 \u0026\u0026 g.roll2 != 0\n}\n\n// checkResult returns the game status as a formatted string\nfunc (g *game) status() string {\n\tif !g.isFinished() {\n\t\treturn resultIcon(ongoing) + \" Game still in progress\"\n\t}\n\n\tif g.roll1 \u003e g.roll2 {\n\t\treturn resultIcon(win) + \" Player1 Wins !\"\n\t} else if g.roll2 \u003e g.roll1 {\n\t\treturn resultIcon(win) + \" Player2 Wins !\"\n\t} else {\n\t\treturn resultIcon(draw) + \" It's a Draw !\"\n\t}\n}\n\n// Render provides a summary of the current state of games and leader board\nfunc Render(path string) string {\n\tvar sb strings.Builder\n\n\tsb.WriteString(`# 🎲 **Dice Roller Game**\n\nWelcome to Dice Roller! Challenge your friends to a simple yet exciting dice rolling game. Roll the dice and see who gets the highest score !\n\n---\n\n## **How to Play**:\n1. **Create a game**: Challenge an opponent using [NewGame](./dice_roller$help\u0026func=NewGame)\n2. **Roll the dice**: Play your turn by rolling a dice using [Play](./dice_roller$help\u0026func=Play)\n\n---\n\n## **Scoring Rules**:\n- **Win** 🏆: +3 points\n- **Draw** 🤝: +1 point each\n- **Lose** ❌: No points\n- **Playing against yourself**: No points or stats changes for you\n\n---\n\n## **Recent Games**:\nBelow are the results from the most recent games. Up to 10 recent games are displayed\n\n| Game | Player 1 | 🎲 Roll 1 | Player 2 | 🎲 Roll 2 | 🏆 Winner |\n|------|----------|-----------|----------|-----------|-----------|\n`)\n\n\tmaxGames := 10\n\tfor n := int(gameId); n \u003e 0 \u0026\u0026 int(gameId)-n \u003c maxGames; n-- {\n\t\tg, err := getGame(n)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tsb.WriteString(strconv.Itoa(n) + \" | \" +\n\t\t\t\"\u003cspan title=\\\"\" + string(g.player1) + \"\\\"\u003e\" + shortName(g.player1) + \"\u003c/span\u003e\" + \" | \" + diceIcon(g.roll1) + \" | \" +\n\t\t\t\"\u003cspan title=\\\"\" + string(g.player2) + \"\\\"\u003e\" + shortName(g.player2) + \"\u003c/span\u003e\" + \" | \" + diceIcon(g.roll2) + \" | \" +\n\t\t\tg.status() + \"\\n\")\n\t}\n\n\tsb.WriteString(`\n---\n\n## **Leaderboard**:\nThe top players are ranked by performance. Games played against oneself are not counted in the leaderboard\n\n| Rank | Player | Wins | Losses | Draws | Points |\n|------|-----------------------|------|--------|-------|--------|\n`)\n\n\tfor i, player := range getLeaderBoard() {\n\t\tsb.WriteString(ufmt.Sprintf(\"| %s | \u003cspan title=\\\"\"+string(player.addr)+\"\\\"\u003e**%s**\u003c/span\u003e | %d | %d | %d | %d |\\n\",\n\t\t\trankIcon(i+1),\n\t\t\tshortName(player.addr),\n\t\t\tplayer.wins,\n\t\t\tplayer.losses,\n\t\t\tplayer.draws,\n\t\t\tplayer.points,\n\t\t))\n\t}\n\n\tsb.WriteString(\"\\n---\\n**Good luck and have fun !** 🎉\")\n\treturn sb.String()\n}\n\n// shortName returns a shortened name for the given address\nfunc shortName(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user != nil {\n\t\treturn user.Name\n\t}\n\tif len(addr) \u003c 10 {\n\t\treturn string(addr)\n\t}\n\treturn string(addr)[:10] + \"...\"\n}\n\n// getGame retrieves the game state by its ID\nfunc getGame(idx int) (*game, error) {\n\tv, ok := games.Get(seqid.ID(idx).String())\n\tif !ok {\n\t\treturn nil, errors.New(\"game not found\")\n\t}\n\treturn v.(*game), nil\n}\n\n// updateResult updates the player's stats and points based on the game outcome\nfunc (p *player) updateStats(result int) {\n\tswitch result {\n\tcase win:\n\t\tp.wins++\n\t\tp.points += 3\n\tcase loss:\n\t\tp.losses++\n\tcase draw:\n\t\tp.draws++\n\t\tp.points++\n\t}\n}\n\n// getPlayer retrieves a player or initializes a new one if they don't exist\nfunc getPlayer(addr std.Address) *player {\n\tv, ok := players.Get(addr.String())\n\tif !ok {\n\t\tplayer := \u0026player{\n\t\t\taddr: addr,\n\t\t}\n\t\tplayers.Set(addr.String(), player)\n\t\treturn player\n\t}\n\n\treturn v.(*player)\n}\n\n// getLeaderBoard generates a leaderboard sorted by points\nfunc getLeaderBoard() leaderBoard {\n\tboard := leaderBoard{}\n\tplayers.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tplayer := value.(*player)\n\t\tboard = append(board, *player)\n\t\treturn false\n\t})\n\n\tsort.Sort(board)\n\n\treturn board\n}\n\n// Methods for sorting the leaderboard\nfunc (r leaderBoard) Len() int {\n\treturn len(r)\n}\n\nfunc (r leaderBoard) Less(i, j int) bool {\n\tif r[i].points != r[j].points {\n\t\treturn r[i].points \u003e r[j].points\n\t}\n\n\tif r[i].wins != r[j].wins {\n\t\treturn r[i].wins \u003e r[j].wins\n\t}\n\n\tif r[i].draws != r[j].draws {\n\t\treturn r[i].draws \u003e r[j].draws\n\t}\n\n\treturn false\n}\n\nfunc (r leaderBoard) Swap(i, j int) {\n\tr[i], r[j] = r[j], r[i]\n}\n"},{"name":"dice_roller_test.gno","body":"package dice_roller\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/seqid\"\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nvar (\n\tplayer1 = testutils.TestAddress(\"alice\")\n\tplayer2 = testutils.TestAddress(\"bob\")\n\tunknownPlayer = testutils.TestAddress(\"unknown\")\n)\n\n// resetGameState resets the game state for testing\nfunc resetGameState() {\n\tgames = avl.Tree{}\n\tgameId = seqid.ID(0)\n\tplayers = avl.Tree{}\n}\n\n// TestNewGame tests the initialization of a new game\nfunc TestNewGame(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOrigCaller(player1)\n\tgameID := NewGame(player2)\n\n\t// Verify that the game has been correctly initialized\n\tg, err := getGame(gameID)\n\turequire.NoError(t, err)\n\turequire.Equal(t, player1.String(), g.player1.String())\n\turequire.Equal(t, player2.String(), g.player2.String())\n\turequire.Equal(t, 0, g.roll1)\n\turequire.Equal(t, 0, g.roll2)\n}\n\n// TestPlay tests the dice rolling functionality for both players\nfunc TestPlay(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOrigCaller(player1)\n\tgameID := NewGame(player2)\n\n\tg, err := getGame(gameID)\n\turequire.NoError(t, err)\n\n\t// Simulate rolling dice for player 1\n\troll1 := Play(gameID)\n\n\t// Verify player 1's roll\n\turequire.NotEqual(t, 0, g.roll1)\n\turequire.Equal(t, g.roll1, roll1)\n\turequire.Equal(t, 0, g.roll2) // Player 2 hasn't rolled yet\n\n\t// Simulate rolling dice for player 2\n\tstd.TestSetOrigCaller(player2)\n\troll2 := Play(gameID)\n\n\t// Verify player 2's roll\n\turequire.NotEqual(t, 0, g.roll2)\n\turequire.Equal(t, g.roll1, roll1)\n\turequire.Equal(t, g.roll2, roll2)\n}\n\n// TestPlayAgainstSelf tests the scenario where a player plays against themselves\nfunc TestPlayAgainstSelf(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOrigCaller(player1)\n\tgameID := NewGame(player1)\n\n\t// Simulate rolling dice twice by the same player\n\troll1 := Play(gameID)\n\troll2 := Play(gameID)\n\n\tg, err := getGame(gameID)\n\turequire.NoError(t, err)\n\turequire.Equal(t, g.roll1, roll1)\n\turequire.Equal(t, g.roll2, roll2)\n}\n\n// TestPlayInvalidPlayer tests the scenario where an invalid player tries to play\nfunc TestPlayInvalidPlayer(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOrigCaller(player1)\n\tgameID := NewGame(player1)\n\n\t// Attempt to play as an invalid player\n\tstd.TestSetOrigCaller(unknownPlayer)\n\turequire.PanicsWithMessage(t, \"invalid player\", func() {\n\t\tPlay(gameID)\n\t})\n}\n\n// TestPlayAlreadyPlayed tests the scenario where a player tries to play again after already playing\nfunc TestPlayAlreadyPlayed(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOrigCaller(player1)\n\tgameID := NewGame(player2)\n\n\t// Player 1 rolls\n\tPlay(gameID)\n\n\t// Player 1 tries to roll again\n\turequire.PanicsWithMessage(t, \"already played\", func() {\n\t\tPlay(gameID)\n\t})\n}\n\n// TestPlayBeyondGameEnd tests that playing after both players have finished their rolls fails\nfunc TestPlayBeyondGameEnd(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOrigCaller(player1)\n\tgameID := NewGame(player2)\n\n\t// Play for both players\n\tstd.TestSetOrigCaller(player1)\n\tPlay(gameID)\n\tstd.TestSetOrigCaller(player2)\n\tPlay(gameID)\n\n\t// Check if the game is over\n\tg, err := getGame(gameID)\n\turequire.NoError(t, err)\n\n\t// Attempt to play more should fail\n\tstd.TestSetOrigCaller(player1)\n\turequire.PanicsWithMessage(t, \"game over\", func() {\n\t\tPlay(gameID)\n\t})\n}\n"},{"name":"icon.gno","body":"package dice_roller\n\nimport (\n\t\"strconv\"\n)\n\n// diceIcon returns an icon of the dice roll\nfunc diceIcon(roll int) string {\n\tswitch roll {\n\tcase 1:\n\t\treturn \"🎲1\"\n\tcase 2:\n\t\treturn \"🎲2\"\n\tcase 3:\n\t\treturn \"🎲3\"\n\tcase 4:\n\t\treturn \"🎲4\"\n\tcase 5:\n\t\treturn \"🎲5\"\n\tcase 6:\n\t\treturn \"🎲6\"\n\tdefault:\n\t\treturn \"❓\"\n\t}\n}\n\n// resultIcon returns the icon representing the result of a game\nfunc resultIcon(result int) string {\n\tswitch result {\n\tcase ongoing:\n\t\treturn \"🔄\"\n\tcase win:\n\t\treturn \"🏆\"\n\tcase loss:\n\t\treturn \"❌\"\n\tcase draw:\n\t\treturn \"🤝\"\n\tdefault:\n\t\treturn \"❓\"\n\t}\n}\n\n// rankIcon returns the icon for a player's rank\nfunc rankIcon(rank int) string {\n\tswitch rank {\n\tcase 1:\n\t\treturn \"🥇\"\n\tcase 2:\n\t\treturn \"🥈\"\n\tcase 3:\n\t\treturn \"🥉\"\n\tdefault:\n\t\treturn strconv.Itoa(rank)\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":"dice_roller","path":"gno.land/r/demo/games/dice_roller","files":[{"name":"dice_roller.gno","body":"package dice_roller\n\nimport (\n\t\"errors\"\n\t\"math/rand\"\n\t\"sort\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/entropy\"\n\t\"gno.land/p/demo/seqid\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/r/demo/users\"\n)\n\ntype (\n\t// game represents a Dice Roller game between two players\n\tgame struct {\n\t\tplayer1, player2 std.Address\n\t\troll1, roll2 int\n\t}\n\n\t// player holds the information about each player including their stats\n\tplayer struct {\n\t\taddr std.Address\n\t\twins, losses, draws, points int\n\t}\n\n\t// leaderBoard is a slice of players, used to sort players by rank\n\tleaderBoard []player\n)\n\nconst (\n\t// Constants to represent game result outcomes\n\tongoing = iota\n\twin\n\tdraw\n\tloss\n)\n\nvar (\n\tgames avl.Tree // AVL tree for storing game states\n\tgameId seqid.ID // Sequence ID for games\n\n\tplayers avl.Tree // AVL tree for storing player data\n\n\tseed = uint64(entropy.New().Seed())\n\tr = rand.New(rand.NewPCG(seed, 0xdeadbeef))\n)\n\n// rollDice generates a random dice roll between 1 and 6\nfunc rollDice() int {\n\treturn r.IntN(6) + 1\n}\n\n// NewGame initializes a new game with the provided opponent's address\nfunc NewGame(addr std.Address) int {\n\tif !addr.IsValid() {\n\t\tpanic(\"invalid opponent's address\")\n\t}\n\n\tgames.Set(gameId.Next().String(), \u0026game{\n\t\tplayer1: std.PrevRealm().Addr(),\n\t\tplayer2: addr,\n\t})\n\n\treturn int(gameId)\n}\n\n// Play allows a player to roll the dice and updates the game state accordingly\nfunc Play(idx int) int {\n\tg, err := getGame(idx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\troll := rollDice() // Random the player's dice roll\n\n\t// Play the game and update the player's roll\n\tif err := g.play(std.PrevRealm().Addr(), roll); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// If both players have rolled, update the results and leaderboard\n\tif g.isFinished() {\n\t\t// If the player is playing against themselves, no points are awarded\n\t\tif g.player1 == g.player2 {\n\t\t\treturn roll\n\t\t}\n\n\t\tplayer1 := getPlayer(g.player1)\n\t\tplayer2 := getPlayer(g.player2)\n\n\t\tif g.roll1 \u003e g.roll2 {\n\t\t\tplayer1.updateStats(win)\n\t\t\tplayer2.updateStats(loss)\n\t\t} else if g.roll2 \u003e g.roll1 {\n\t\t\tplayer2.updateStats(win)\n\t\t\tplayer1.updateStats(loss)\n\t\t} else {\n\t\t\tplayer1.updateStats(draw)\n\t\t\tplayer2.updateStats(draw)\n\t\t}\n\t}\n\n\treturn roll\n}\n\n// play processes a player's roll and updates their score\nfunc (g *game) play(player std.Address, roll int) error {\n\tif player != g.player1 \u0026\u0026 player != g.player2 {\n\t\treturn errors.New(\"invalid player\")\n\t}\n\n\tif g.isFinished() {\n\t\treturn errors.New(\"game over\")\n\t}\n\n\tif player == g.player1 \u0026\u0026 g.roll1 == 0 {\n\t\tg.roll1 = roll\n\t\treturn nil\n\t}\n\n\tif player == g.player2 \u0026\u0026 g.roll2 == 0 {\n\t\tg.roll2 = roll\n\t\treturn nil\n\t}\n\n\treturn errors.New(\"already played\")\n}\n\n// isFinished checks if the game has ended\nfunc (g *game) isFinished() bool {\n\treturn g.roll1 != 0 \u0026\u0026 g.roll2 != 0\n}\n\n// checkResult returns the game status as a formatted string\nfunc (g *game) status() string {\n\tif !g.isFinished() {\n\t\treturn resultIcon(ongoing) + \" Game still in progress\"\n\t}\n\n\tif g.roll1 \u003e g.roll2 {\n\t\treturn resultIcon(win) + \" Player1 Wins !\"\n\t} else if g.roll2 \u003e g.roll1 {\n\t\treturn resultIcon(win) + \" Player2 Wins !\"\n\t} else {\n\t\treturn resultIcon(draw) + \" It's a Draw !\"\n\t}\n}\n\n// Render provides a summary of the current state of games and leader board\nfunc Render(path string) string {\n\tvar sb strings.Builder\n\n\tsb.WriteString(`# 🎲 **Dice Roller Game**\n\nWelcome to Dice Roller! Challenge your friends to a simple yet exciting dice rolling game. Roll the dice and see who gets the highest score !\n\n---\n\n## **How to Play**:\n1. **Create a game**: Challenge an opponent using [NewGame](./dice_roller$help\u0026func=NewGame)\n2. **Roll the dice**: Play your turn by rolling a dice using [Play](./dice_roller$help\u0026func=Play)\n\n---\n\n## **Scoring Rules**:\n- **Win** 🏆: +3 points\n- **Draw** 🤝: +1 point each\n- **Lose** ❌: No points\n- **Playing against yourself**: No points or stats changes for you\n\n---\n\n## **Recent Games**:\nBelow are the results from the most recent games. Up to 10 recent games are displayed\n\n| Game | Player 1 | 🎲 Roll 1 | Player 2 | 🎲 Roll 2 | 🏆 Winner |\n|------|----------|-----------|----------|-----------|-----------|\n`)\n\n\tmaxGames := 10\n\tfor n := int(gameId); n \u003e 0 \u0026\u0026 int(gameId)-n \u003c maxGames; n-- {\n\t\tg, err := getGame(n)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tsb.WriteString(strconv.Itoa(n) + \" | \" +\n\t\t\t\"\u003cspan title=\\\"\" + string(g.player1) + \"\\\"\u003e\" + shortName(g.player1) + \"\u003c/span\u003e\" + \" | \" + diceIcon(g.roll1) + \" | \" +\n\t\t\t\"\u003cspan title=\\\"\" + string(g.player2) + \"\\\"\u003e\" + shortName(g.player2) + \"\u003c/span\u003e\" + \" | \" + diceIcon(g.roll2) + \" | \" +\n\t\t\tg.status() + \"\\n\")\n\t}\n\n\tsb.WriteString(`\n---\n\n## **Leaderboard**:\nThe top players are ranked by performance. Games played against oneself are not counted in the leaderboard\n\n| Rank | Player | Wins | Losses | Draws | Points |\n|------|-----------------------|------|--------|-------|--------|\n`)\n\n\tfor i, player := range getLeaderBoard() {\n\t\tsb.WriteString(ufmt.Sprintf(\"| %s | \u003cspan title=\\\"\"+string(player.addr)+\"\\\"\u003e**%s**\u003c/span\u003e | %d | %d | %d | %d |\\n\",\n\t\t\trankIcon(i+1),\n\t\t\tshortName(player.addr),\n\t\t\tplayer.wins,\n\t\t\tplayer.losses,\n\t\t\tplayer.draws,\n\t\t\tplayer.points,\n\t\t))\n\t}\n\n\tsb.WriteString(\"\\n---\\n**Good luck and have fun !** 🎉\")\n\treturn sb.String()\n}\n\n// shortName returns a shortened name for the given address\nfunc shortName(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user != nil {\n\t\treturn user.Name\n\t}\n\tif len(addr) \u003c 10 {\n\t\treturn string(addr)\n\t}\n\treturn string(addr)[:10] + \"...\"\n}\n\n// getGame retrieves the game state by its ID\nfunc getGame(idx int) (*game, error) {\n\tv, ok := games.Get(seqid.ID(idx).String())\n\tif !ok {\n\t\treturn nil, errors.New(\"game not found\")\n\t}\n\treturn v.(*game), nil\n}\n\n// updateResult updates the player's stats and points based on the game outcome\nfunc (p *player) updateStats(result int) {\n\tswitch result {\n\tcase win:\n\t\tp.wins++\n\t\tp.points += 3\n\tcase loss:\n\t\tp.losses++\n\tcase draw:\n\t\tp.draws++\n\t\tp.points++\n\t}\n}\n\n// getPlayer retrieves a player or initializes a new one if they don't exist\nfunc getPlayer(addr std.Address) *player {\n\tv, ok := players.Get(addr.String())\n\tif !ok {\n\t\tplayer := \u0026player{\n\t\t\taddr: addr,\n\t\t}\n\t\tplayers.Set(addr.String(), player)\n\t\treturn player\n\t}\n\n\treturn v.(*player)\n}\n\n// getLeaderBoard generates a leaderboard sorted by points\nfunc getLeaderBoard() leaderBoard {\n\tboard := leaderBoard{}\n\tplayers.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tplayer := value.(*player)\n\t\tboard = append(board, *player)\n\t\treturn false\n\t})\n\n\tsort.Sort(board)\n\n\treturn board\n}\n\n// Methods for sorting the leaderboard\nfunc (r leaderBoard) Len() int {\n\treturn len(r)\n}\n\nfunc (r leaderBoard) Less(i, j int) bool {\n\tif r[i].points != r[j].points {\n\t\treturn r[i].points \u003e r[j].points\n\t}\n\n\tif r[i].wins != r[j].wins {\n\t\treturn r[i].wins \u003e r[j].wins\n\t}\n\n\tif r[i].draws != r[j].draws {\n\t\treturn r[i].draws \u003e r[j].draws\n\t}\n\n\treturn false\n}\n\nfunc (r leaderBoard) Swap(i, j int) {\n\tr[i], r[j] = r[j], r[i]\n}\n"},{"name":"dice_roller_test.gno","body":"package dice_roller\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/seqid\"\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nvar (\n\tplayer1 = testutils.TestAddress(\"alice\")\n\tplayer2 = testutils.TestAddress(\"bob\")\n\tunknownPlayer = testutils.TestAddress(\"unknown\")\n)\n\n// resetGameState resets the game state for testing\nfunc resetGameState() {\n\tgames = avl.Tree{}\n\tgameId = seqid.ID(0)\n\tplayers = avl.Tree{}\n}\n\n// TestNewGame tests the initialization of a new game\nfunc TestNewGame(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOriginCaller(player1)\n\tgameID := NewGame(player2)\n\n\t// Verify that the game has been correctly initialized\n\tg, err := getGame(gameID)\n\turequire.NoError(t, err)\n\turequire.Equal(t, player1.String(), g.player1.String())\n\turequire.Equal(t, player2.String(), g.player2.String())\n\turequire.Equal(t, 0, g.roll1)\n\turequire.Equal(t, 0, g.roll2)\n}\n\n// TestPlay tests the dice rolling functionality for both players\nfunc TestPlay(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOriginCaller(player1)\n\tgameID := NewGame(player2)\n\n\tg, err := getGame(gameID)\n\turequire.NoError(t, err)\n\n\t// Simulate rolling dice for player 1\n\troll1 := Play(gameID)\n\n\t// Verify player 1's roll\n\turequire.NotEqual(t, 0, g.roll1)\n\turequire.Equal(t, g.roll1, roll1)\n\turequire.Equal(t, 0, g.roll2) // Player 2 hasn't rolled yet\n\n\t// Simulate rolling dice for player 2\n\tstd.TestSetOriginCaller(player2)\n\troll2 := Play(gameID)\n\n\t// Verify player 2's roll\n\turequire.NotEqual(t, 0, g.roll2)\n\turequire.Equal(t, g.roll1, roll1)\n\turequire.Equal(t, g.roll2, roll2)\n}\n\n// TestPlayAgainstSelf tests the scenario where a player plays against themselves\nfunc TestPlayAgainstSelf(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOriginCaller(player1)\n\tgameID := NewGame(player1)\n\n\t// Simulate rolling dice twice by the same player\n\troll1 := Play(gameID)\n\troll2 := Play(gameID)\n\n\tg, err := getGame(gameID)\n\turequire.NoError(t, err)\n\turequire.Equal(t, g.roll1, roll1)\n\turequire.Equal(t, g.roll2, roll2)\n}\n\n// TestPlayInvalidPlayer tests the scenario where an invalid player tries to play\nfunc TestPlayInvalidPlayer(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOriginCaller(player1)\n\tgameID := NewGame(player1)\n\n\t// Attempt to play as an invalid player\n\tstd.TestSetOriginCaller(unknownPlayer)\n\turequire.PanicsWithMessage(t, \"invalid player\", func() {\n\t\tPlay(gameID)\n\t})\n}\n\n// TestPlayAlreadyPlayed tests the scenario where a player tries to play again after already playing\nfunc TestPlayAlreadyPlayed(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOriginCaller(player1)\n\tgameID := NewGame(player2)\n\n\t// Player 1 rolls\n\tPlay(gameID)\n\n\t// Player 1 tries to roll again\n\turequire.PanicsWithMessage(t, \"already played\", func() {\n\t\tPlay(gameID)\n\t})\n}\n\n// TestPlayBeyondGameEnd tests that playing after both players have finished their rolls fails\nfunc TestPlayBeyondGameEnd(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOriginCaller(player1)\n\tgameID := NewGame(player2)\n\n\t// Play for both players\n\tstd.TestSetOriginCaller(player1)\n\tPlay(gameID)\n\tstd.TestSetOriginCaller(player2)\n\tPlay(gameID)\n\n\t// Check if the game is over\n\tg, err := getGame(gameID)\n\turequire.NoError(t, err)\n\n\t// Attempt to play more should fail\n\tstd.TestSetOriginCaller(player1)\n\turequire.PanicsWithMessage(t, \"game over\", func() {\n\t\tPlay(gameID)\n\t})\n}\n"},{"name":"icon.gno","body":"package dice_roller\n\nimport (\n\t\"strconv\"\n)\n\n// diceIcon returns an icon of the dice roll\nfunc diceIcon(roll int) string {\n\tswitch roll {\n\tcase 1:\n\t\treturn \"🎲1\"\n\tcase 2:\n\t\treturn \"🎲2\"\n\tcase 3:\n\t\treturn \"🎲3\"\n\tcase 4:\n\t\treturn \"🎲4\"\n\tcase 5:\n\t\treturn \"🎲5\"\n\tcase 6:\n\t\treturn \"🎲6\"\n\tdefault:\n\t\treturn \"❓\"\n\t}\n}\n\n// resultIcon returns the icon representing the result of a game\nfunc resultIcon(result int) string {\n\tswitch result {\n\tcase ongoing:\n\t\treturn \"🔄\"\n\tcase win:\n\t\treturn \"🏆\"\n\tcase loss:\n\t\treturn \"❌\"\n\tcase draw:\n\t\treturn \"🤝\"\n\tdefault:\n\t\treturn \"❓\"\n\t}\n}\n\n// rankIcon returns the icon for a player's rank\nfunc rankIcon(rank int) string {\n\tswitch rank {\n\tcase 1:\n\t\treturn \"🥇\"\n\tcase 2:\n\t\treturn \"🥈\"\n\tcase 3:\n\t\treturn \"🥉\"\n\tdefault:\n\t\treturn strconv.Itoa(rank)\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":"shifumi","path":"gno.land/r/demo/games/shifumi","files":[{"name":"shifumi.gno","body":"package shifumi\n\nimport (\n\t\"errors\"\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/seqid\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nconst (\n\tempty = iota\n\trock\n\tpaper\n\tscissors\n\tlast\n)\n\ntype game struct {\n\tplayer1, player2 std.Address // shifumi is a 2 players game\n\tmove1, move2 int // can be empty, rock, paper, or scissors\n}\n\nvar games avl.Tree\nvar id seqid.ID\n\nfunc (g *game) play(player std.Address, move int) error {\n\tif !(move \u003e empty \u0026\u0026 move \u003c last) {\n\t\treturn errors.New(\"invalid move\")\n\t}\n\tif player != g.player1 \u0026\u0026 player != g.player2 {\n\t\treturn errors.New(\"invalid player\")\n\t}\n\tif player == g.player1 \u0026\u0026 g.move1 == empty {\n\t\tg.move1 = move\n\t\treturn nil\n\t}\n\tif player == g.player2 \u0026\u0026 g.move2 == empty {\n\t\tg.move2 = move\n\t\treturn nil\n\t}\n\treturn errors.New(\"already played\")\n}\n\nfunc (g *game) winner() int {\n\tif g.move1 == empty || g.move2 == empty {\n\t\treturn -1\n\t}\n\tif g.move1 == g.move2 {\n\t\treturn 0\n\t}\n\tif g.move1 == rock \u0026\u0026 g.move2 == scissors ||\n\t\tg.move1 == paper \u0026\u0026 g.move2 == rock ||\n\t\tg.move1 == scissors \u0026\u0026 g.move2 == paper {\n\t\treturn 1\n\t}\n\treturn 2\n}\n\n// NewGame creates a new game where player1 is the caller and player2 the argument.\n// A new game index is returned.\nfunc NewGame(player std.Address) int {\n\tgames.Set(id.Next().String(), \u0026game{player1: std.PrevRealm().Addr(), player2: player})\n\treturn int(id)\n}\n\n// Play executes a move for the game at index idx, where move can be:\n// 1 (rock), 2 (paper), 3 (scissors).\nfunc Play(idx, move int) {\n\tv, ok := games.Get(seqid.ID(idx).String())\n\tif !ok {\n\t\tpanic(\"game not found\")\n\t}\n\tif err := v.(*game).play(std.PrevRealm().Addr(), move); err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc Render(path string) string {\n\tmov1 := []string{\"\", \" 🤜 \", \" 🫱 \", \" 👉 \"}\n\tmov2 := []string{\"\", \" 🤛 \", \" 🫲 \", \" 👈 \"}\n\twin := []string{\"pending\", \"draw\", \"player1\", \"player2\"}\n\n\toutput := `# 👊 ✋ ✌️ Shifumi\nActions:\n* [NewGame](shifumi$help\u0026func=NewGame) opponentAddress\n* [Play](shifumi$help\u0026func=Play) gameIndex move (1=rock, 2=paper, 3=scissors)\n\n game | player1 | | player2 | | win \n --- | --- | --- | --- | --- | ---\n`\n\t// Output the 100 most recent games.\n\tmaxGames := 100\n\tfor n := int(id); n \u003e 0 \u0026\u0026 int(id)-n \u003c maxGames; n-- {\n\t\tv, ok := games.Get(seqid.ID(n).String())\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tg := v.(*game)\n\t\toutput += strconv.Itoa(n) + \" | \" +\n\t\t\tshortName(g.player1) + \" | \" + mov1[g.move1] + \" | \" +\n\t\t\tshortName(g.player2) + \" | \" + mov2[g.move2] + \" | \" +\n\t\t\twin[g.winner()+1] + \"\\n\"\n\t}\n\treturn output\n}\n\nfunc shortName(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user != nil {\n\t\treturn user.Name\n\t}\n\tif len(addr) \u003c 10 {\n\t\treturn string(addr)\n\t}\n\treturn string(addr)[:10] + \"...\"\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":"groups","path":"gno.land/r/demo/groups","files":[{"name":"README.md","body":"### - test package\n\n ./build/gno test examples/gno.land/r/demo/groups/\n\n### - add pkg\n\n ./build/gnokey maketx addpkg -pkgdir \"examples/gno.land/r/demo/groups\" -deposit 100000000ugnot -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid dev -remote 0.0.0.0:26657 -pkgpath \"gno.land/r/demo/groups\" test1 \n\n### - create group\n\n ./build/gnokey maketx call -func \"CreateGroup\" -args \"dao_trinity_ngo\" -gas-fee \"1000000ugnot\" -gas-wanted 4000000 -broadcast -chainid dev -remote 0.0.0.0:26657 -pkgpath \"gno.land/r/demo/groups\" test1 \n\n### - add member\n\n ./build/gnokey maketx call -func \"AddMember\" -args \"1\" -args \"g1hd3gwzevxlqmd3jsf64mpfczag8a8e5j2wdn3c\" -args 12 -args \"i am new user\" -gas-fee \"1000000ugnot\" -gas-wanted \"4000000\" -broadcast -chainid dev -remote 0.0.0.0:26657 -pkgpath \"gno.land/r/demo/groups\" test1\n\n### - delete member\n\n ./build/gnokey maketx call -func \"DeleteMember\" -args \"1\" -args \"0\" -gas-fee \"1000000ugnot\" -gas-wanted \"4000000\" -broadcast -chainid dev -remote 0.0.0.0:26657 -pkgpath \"gno.land/r/demo/groups\" test1\n\n### - delete group\n\n ./build/gnokey maketx call -func \"DeleteGroup\" -args \"1\" -gas-fee \"1000000ugnot\" -gas-wanted \"4000000\" -broadcast -chainid dev -remote 0.0.0.0:26657 -pkgpath \"gno.land/r/demo/groups\" test1\n\n"},{"name":"group.gno","body":"package groups\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\ntype GroupID uint64\n\nfunc (gid GroupID) String() string {\n\treturn strconv.Itoa(int(gid))\n}\n\ntype Group struct {\n\tid GroupID\n\turl string\n\tname string\n\tlastMemberID MemberID\n\tmembers avl.Tree\n\tcreator std.Address\n\tcreatedAt time.Time\n}\n\nfunc newGroup(url string, name string, creator std.Address) *Group {\n\tif !reName.MatchString(name) {\n\t\tpanic(\"invalid name: \" + name)\n\t}\n\tif gGroupsByName.Has(name) {\n\t\tpanic(\"Group with such name already exists\")\n\t}\n\treturn \u0026Group{\n\t\tid: incGetGroupID(),\n\t\turl: url,\n\t\tname: name,\n\t\tcreator: creator,\n\t\tmembers: avl.Tree{},\n\t\tcreatedAt: time.Now(),\n\t}\n}\n\nfunc (group *Group) newMember(id MemberID, address std.Address, weight int, metadata string) *Member {\n\tif group.members.Has(address.String()) {\n\t\tpanic(\"this member for this group already exists\")\n\t}\n\treturn \u0026Member{\n\t\tid: id,\n\t\taddress: address,\n\t\tweight: weight,\n\t\tmetadata: metadata,\n\t\tcreatedAt: time.Now(),\n\t}\n}\n\nfunc (group *Group) HasPermission(addr std.Address, perm Permission) bool {\n\tif group.creator != addr {\n\t\treturn false\n\t}\n\treturn isValidPermission(perm)\n}\n\nfunc (group *Group) RenderGroup() string {\n\tstr := \"Group ID: \" + groupIDKey(group.id) + \"\\n\\n\" +\n\t\t\"Group Name: \" + group.name + \"\\n\\n\" +\n\t\t\"Group Creator: \" + usernameOf(group.creator) + \"\\n\\n\" +\n\t\t\"Group createdAt: \" + group.createdAt.String() + \"\\n\\n\" +\n\t\t\"Group Last MemberID: \" + memberIDKey(group.lastMemberID) + \"\\n\\n\"\n\n\tstr += \"Group Members: \\n\\n\"\n\tgroup.members.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tmember := value.(*Member)\n\t\tstr += member.getMemberStr()\n\t\treturn false\n\t})\n\treturn str\n}\n\nfunc (group *Group) deleteGroup() {\n\tgidkey := groupIDKey(group.id)\n\t_, gGroupsRemoved := gGroups.Remove(gidkey)\n\tif !gGroupsRemoved {\n\t\tpanic(\"group does not exist with id \" + group.id.String())\n\t}\n\tgGroupsByName.Remove(group.name)\n}\n\nfunc (group *Group) deleteMember(mid MemberID) {\n\tgidkey := groupIDKey(group.id)\n\tif !gGroups.Has(gidkey) {\n\t\tpanic(\"group does not exist with id \" + group.id.String())\n\t}\n\n\tg := getGroup(group.id)\n\tmidkey := memberIDKey(mid)\n\tg.members.Remove(midkey)\n}\n"},{"name":"groups.gno","body":"package groups\n\nimport (\n\t\"regexp\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n//----------------------------------------\n// Realm (package) state\n\nvar (\n\tgGroups avl.Tree // id -\u003e *Group\n\tgGroupsCtr int // increments Group.id\n\tgGroupsByName avl.Tree // name -\u003e *Group\n)\n\n//----------------------------------------\n// Constants\n\nvar reName = regexp.MustCompile(`^[a-z]+[_a-z0-9]{2,29}$`)\n"},{"name":"member.gno","body":"package groups\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n)\n\ntype MemberID uint64\n\ntype Member struct {\n\tid MemberID\n\taddress std.Address\n\tweight int\n\tmetadata string\n\tcreatedAt time.Time\n}\n\nfunc (mid MemberID) String() string {\n\treturn strconv.Itoa(int(mid))\n}\n\nfunc (member *Member) getMemberStr() string {\n\tmemberDataStr := \"\"\n\tmemberDataStr += \"\\t\\t\\t[\" + memberIDKey(member.id) + \", \" + member.address.String() + \", \" + strconv.Itoa(member.weight) + \", \" + member.metadata + \", \" + member.createdAt.String() + \"],\\n\\n\"\n\treturn memberDataStr\n}\n"},{"name":"misc.gno","body":"package groups\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/r/demo/users\"\n)\n\n//----------------------------------------\n// private utility methods\n// XXX ensure these cannot be called from public.\n\nfunc getGroup(gid GroupID) *Group {\n\tgidkey := groupIDKey(gid)\n\tgroup_, exists := gGroups.Get(gidkey)\n\tif !exists {\n\t\tpanic(\"group id (\" + gid.String() + \") does not exists\")\n\t}\n\tgroup := group_.(*Group)\n\treturn group\n}\n\nfunc incGetGroupID() GroupID {\n\tgGroupsCtr++\n\treturn GroupID(gGroupsCtr)\n}\n\nfunc padLeft(str string, length int) string {\n\tif len(str) \u003e= length {\n\t\treturn str\n\t}\n\treturn strings.Repeat(\" \", length-len(str)) + str\n}\n\nfunc padZero(u64 uint64, length int) string {\n\tstr := strconv.Itoa(int(u64))\n\tif len(str) \u003e= length {\n\t\treturn str\n\t}\n\treturn strings.Repeat(\"0\", length-len(str)) + str\n}\n\nfunc groupIDKey(gid GroupID) string {\n\treturn padZero(uint64(gid), 10)\n}\n\nfunc memberIDKey(mid MemberID) string {\n\treturn padZero(uint64(mid), 10)\n}\n\nfunc indentBody(indent string, body string) string {\n\tlines := strings.Split(body, \"\\n\")\n\tres := \"\"\n\tfor i, line := range lines {\n\t\tif i \u003e 0 {\n\t\t\tres += \"\\n\"\n\t\t}\n\t\tres += indent + line\n\t}\n\treturn res\n}\n\n// NOTE: length must be greater than 3.\nfunc summaryOf(str string, length int) string {\n\tlines := strings.SplitN(str, \"\\n\", 2)\n\tline := lines[0]\n\tif len(line) \u003e length {\n\t\tline = line[:(length-3)] + \"...\"\n\t} else if len(lines) \u003e 1 {\n\t\t// len(line) \u003c= 80\n\t\tline = line + \"...\"\n\t}\n\treturn line\n}\n\nfunc displayAddressMD(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user == nil {\n\t\treturn \"[\" + addr.String() + \"](/r/demo/users:\" + addr.String() + \")\"\n\t}\n\treturn \"[@\" + user.Name + \"](/r/demo/users:\" + user.Name + \")\"\n}\n\nfunc usernameOf(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user == nil {\n\t\tpanic(\"user not found\")\n\t}\n\treturn user.Name\n}\n\nfunc isValidPermission(perm Permission) bool {\n\treturn perm == EditPermission || perm == DeletePermission\n}\n"},{"name":"public.gno","body":"package groups\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\n//----------------------------------------\n// Public facing functions\n\nfunc GetGroupIDFromName(name string) (GroupID, bool) {\n\tgroupI, exists := gGroupsByName.Get(name)\n\tif !exists {\n\t\treturn 0, false\n\t}\n\treturn groupI.(*Group).id, true\n}\n\nfunc CreateGroup(name string) GroupID {\n\tstd.AssertOriginCall()\n\tcaller := std.OrigCaller()\n\tusernameOf(caller)\n\turl := \"/r/demo/groups:\" + name\n\tgroup := newGroup(url, name, caller)\n\tgidkey := groupIDKey(group.id)\n\tgGroups.Set(gidkey, group)\n\tgGroupsByName.Set(name, group)\n\treturn group.id\n}\n\nfunc AddMember(gid GroupID, address string, weight int, metadata string) MemberID {\n\tstd.AssertOriginCall()\n\tcaller := std.OrigCaller()\n\tusernameOf(caller)\n\tgroup := getGroup(gid)\n\tif !group.HasPermission(caller, EditPermission) {\n\t\tpanic(\"unauthorized to edit group\")\n\t}\n\tuser := users.GetUserByAddress(std.Address(address))\n\tif user == nil {\n\t\tpanic(\"unknown address \" + address)\n\t}\n\tmid := group.lastMemberID\n\tmember := group.newMember(mid, std.Address(address), weight, metadata)\n\tmidkey := memberIDKey(mid)\n\tgroup.members.Set(midkey, member)\n\tmid++\n\tgroup.lastMemberID = mid\n\treturn member.id\n}\n\nfunc DeleteGroup(gid GroupID) {\n\tstd.AssertOriginCall()\n\tcaller := std.OrigCaller()\n\tgroup := getGroup(gid)\n\tif !group.HasPermission(caller, DeletePermission) {\n\t\tpanic(\"unauthorized to delete group\")\n\t}\n\tgroup.deleteGroup()\n}\n\nfunc DeleteMember(gid GroupID, mid MemberID) {\n\tstd.AssertOriginCall()\n\tcaller := std.OrigCaller()\n\tgroup := getGroup(gid)\n\tif !group.HasPermission(caller, DeletePermission) {\n\t\tpanic(\"unauthorized to delete member\")\n\t}\n\tgroup.deleteMember(mid)\n}\n"},{"name":"render.gno","body":"package groups\n\nimport (\n\t\"strings\"\n)\n\n//----------------------------------------\n// Render functions\n\nfunc RenderGroup(gid GroupID) string {\n\tgroup := getGroup(gid)\n\tif group == nil {\n\t\treturn \"missing Group\"\n\t}\n\treturn group.RenderGroup()\n}\n\nfunc Render(path string) string {\n\tif path == \"\" {\n\t\tstr := \"List of all Groups:\\n\\n\"\n\t\tgGroups.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tgroup := value.(*Group)\n\t\t\tstr += \" * [\" + group.name + \"](\" + group.url + \")\\n\"\n\t\t\treturn false\n\t\t})\n\t\treturn str\n\t}\n\tparts := strings.Split(path, \"/\")\n\tif len(parts) == 1 {\n\t\t// /r/demo/groups:Group_NAME\n\t\tname := parts[0]\n\t\tgroupI, exists := gGroupsByName.Get(name)\n\t\tif !exists {\n\t\t\treturn \"Group does not exist: \" + name\n\t\t}\n\t\treturn groupI.(*Group).RenderGroup()\n\t} else {\n\t\treturn \"unrecognized path \" + path\n\t}\n}\n"},{"name":"role.gno","body":"package groups\n\ntype Permission string\n\nconst (\n\tDeletePermission Permission = \"role:delete\"\n\tEditPermission Permission = \"role:edit\"\n)\n"},{"name":"z_0_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\nimport (\n\t\"gno.land/r/demo/groups\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// user not found\n"},{"name":"z_0_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// payment must not be less than 20000000\n"},{"name":"z_0_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tprintln(groups.Render(\"\"))\n}\n\n// Output:\n// 1\n// List of all Groups:\n//\n// * [test_group](/r/demo/groups:test_group)\n"},{"name":"z_1_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OrigCaller() // main\n\tusers.Register(\"\", \"gnouser0\", \"my profile 1\")\n\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"gnouser1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test1)\n\tusers.Register(caller, \"gnouser1\", \"my other profile 1\")\n\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest2 := testutils.TestAddress(\"gnouser2\")\n\tusers.Invite(test2.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test2)\n\tusers.Register(caller, \"gnouser2\", \"my other profile 2\")\n\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest3 := testutils.TestAddress(\"gnouser3\")\n\tusers.Invite(test3.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test3)\n\tusers.Register(caller, \"gnouser3\", \"my other profile 3\")\n\n\tstd.TestSetOrigCaller(caller)\n\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\n\tgroups.AddMember(gid, test3.String(), 32, \"i am from UAE\")\n\tprintln(groups.Render(\"test_group\"))\n}\n\n// Output:\n// 1\n// Group ID: 0000000001\n//\n// Group Name: test_group\n//\n// Group Creator: gnouser0\n//\n// Group createdAt: 2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001\n//\n// Group Last MemberID: 0000000001\n//\n// Group Members:\n//\n// \t\t\t[0000000000, g1vahx7atnv4erxh6lta047h6lta047h6ll85gpy, 32, i am from UAE, 2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001],\n"},{"name":"z_1_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tgroups.AddMember(2, \"g1vahx7atnv4erxh6lta047h6lta047h6ll85gpy\", 55, \"metadata3\")\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// group id (2) does not exists\n"},{"name":"z_1_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/groups\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\n\t// add member via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOrigCaller(test2)\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\n\tgroups.AddMember(gid, test2.String(), 42, \"metadata3\")\n}\n\n// Error:\n// user not found\n"},{"name":"z_2_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OrigCaller() // main\n\tusers.Register(\"\", \"gnouser0\", \"my profile 1\")\n\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"gnouser1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test1)\n\tusers.Register(caller, \"gnouser1\", \"my other profile 1\")\n\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest2 := testutils.TestAddress(\"gnouser2\")\n\tusers.Invite(test2.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test2)\n\tusers.Register(caller, \"gnouser2\", \"my other profile 2\")\n\n\tstd.TestSetOrigCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOrigCaller(caller)\n\t// invite another addr\n\ttest3 := testutils.TestAddress(\"gnouser3\")\n\tusers.Invite(test3.String())\n\t// switch to test1\n\tstd.TestSetOrigCaller(test3)\n\tusers.Register(caller, \"gnouser3\", \"my other profile 3\")\n\n\tstd.TestSetOrigCaller(caller)\n\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\n\tgroups.AddMember(gid, test2.String(), 42, \"metadata3\")\n\n\tgroups.DeleteMember(gid, 0)\n\tprintln(groups.RenderGroup(gid))\n}\n\n// Output:\n// 1\n// Group ID: 0000000001\n//\n// Group Name: test_group\n//\n// Group Creator: gnouser0\n//\n// Group createdAt: 2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001\n//\n// Group Last MemberID: 0000000001\n//\n// Group Members:\n"},{"name":"z_2_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tgroups.DeleteMember(2, 0)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// group id (2) does not exists\n"},{"name":"z_2_d_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\n\t// delete member via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOrigCaller(test2)\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\n\tgroups.DeleteMember(gid, 0)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// unauthorized to delete member\n"},{"name":"z_2_e_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tgroups.DeleteGroup(gid)\n\tprintln(groups.Render(\"\"))\n}\n\n// Output:\n// 1\n// List of all Groups:\n"},{"name":"z_2_f_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tgroups.DeleteGroup(20)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// group id (20) does not exists\n"},{"name":"z_2_g_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\n\t// delete group via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOrigCaller(test2)\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\n\tgroups.DeleteGroup(gid)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// unauthorized to delete group\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":"keystore","path":"gno.land/r/demo/keystore","files":[{"name":"keystore.gno","body":"package keystore\n\nimport (\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar data avl.Tree\n\nconst (\n\tBaseURL = \"/r/demo/keystore\"\n\tStatusOK = \"ok\"\n\tStatusNoUser = \"user not found\"\n\tStatusNotFound = \"key not found\"\n\tStatusNoWriteAccess = \"no write access\"\n\tStatusCouldNotExecute = \"could not execute\"\n\tStatusNoDatabases = \"no databases\"\n)\n\nfunc init() {\n\tdata = avl.Tree{} // user -\u003e avl.Tree\n}\n\n// KeyStore stores the owner-specific avl.Tree\ntype KeyStore struct {\n\tOwner std.Address\n\tData avl.Tree\n}\n\n// Set will set a value to a key\n// requires write-access (original caller must be caller)\nfunc Set(k, v string) string {\n\torigOwner := std.OrigCaller()\n\treturn set(origOwner.String(), k, v)\n}\n\n// set (private) will set a key to value\n// requires write-access (original caller must be caller)\nfunc set(owner, k, v string) string {\n\torigOwner := std.OrigCaller()\n\tif origOwner.String() != owner {\n\t\treturn StatusNoWriteAccess\n\t}\n\tvar keystore *KeyStore\n\tkeystoreInterface, exists := data.Get(owner)\n\tif !exists {\n\t\tkeystore = \u0026KeyStore{\n\t\t\tOwner: origOwner,\n\t\t\tData: avl.Tree{},\n\t\t}\n\t\tdata.Set(owner, keystore)\n\t} else {\n\t\tkeystore = keystoreInterface.(*KeyStore)\n\t}\n\tkeystore.Data.Set(k, v)\n\treturn StatusOK\n}\n\n// Remove removes a key\n// requires write-access (original owner must be caller)\nfunc Remove(k string) string {\n\torigOwner := std.OrigCaller()\n\treturn remove(origOwner.String(), k)\n}\n\n// remove (private) removes a key\n// requires write-access (original owner must be caller)\nfunc remove(owner, k string) string {\n\torigOwner := std.OrigCaller()\n\tif origOwner.String() != owner {\n\t\treturn StatusNoWriteAccess\n\t}\n\tvar keystore *KeyStore\n\tkeystoreInterface, exists := data.Get(owner)\n\tif !exists {\n\t\tkeystore = \u0026KeyStore{\n\t\t\tOwner: origOwner,\n\t\t\tData: avl.Tree{},\n\t\t}\n\t\tdata.Set(owner, keystore)\n\t} else {\n\t\tkeystore = keystoreInterface.(*KeyStore)\n\t}\n\t_, removed := keystore.Data.Remove(k)\n\tif !removed {\n\t\treturn StatusCouldNotExecute\n\t}\n\treturn StatusOK\n}\n\n// Get returns a value for a key\n// read-only\nfunc Get(k string) string {\n\torigOwner := std.OrigCaller()\n\treturn remove(origOwner.String(), k)\n}\n\n// get (private) returns a value for a key\n// read-only\nfunc get(owner, k string) string {\n\tkeystoreInterface, exists := data.Get(owner)\n\tif !exists {\n\t\treturn StatusNoUser\n\t}\n\tkeystore := keystoreInterface.(*KeyStore)\n\tval, found := keystore.Data.Get(k)\n\tif !found {\n\t\treturn StatusNotFound\n\t}\n\treturn val.(string)\n}\n\n// Size returns size of database\n// read-only\nfunc Size() string {\n\torigOwner := std.OrigCaller()\n\treturn size(origOwner.String())\n}\n\nfunc size(owner string) string {\n\tkeystoreInterface, exists := data.Get(owner)\n\tif !exists {\n\t\treturn StatusNoUser\n\t}\n\tkeystore := keystoreInterface.(*KeyStore)\n\treturn ufmt.Sprintf(\"%d\", keystore.Data.Size())\n}\n\n// Render provides read-only url access to the functions of the keystore\n// \"\" -\u003e show all keystores listed by owner\n// \"owner\" -\u003e show all keys for that owner's keystore\n// \"owner:size\" -\u003e returns size of owner's keystore\n// \"owner:get:key\" -\u003e show value for that key in owner's keystore\nfunc Render(p string) string {\n\tvar response string\n\targs := strings.Split(p, \":\")\n\tnumArgs := len(args)\n\tif p == \"\" {\n\t\tnumArgs = 0\n\t}\n\tswitch numArgs {\n\tcase 0:\n\t\tif data.Size() == 0 {\n\t\t\treturn StatusNoDatabases\n\t\t}\n\t\tdata.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tks := value.(*KeyStore)\n\t\t\tresponse += ufmt.Sprintf(\"- [%s](%s:%s) (%d keys)\\n\", ks.Owner, BaseURL, ks.Owner, ks.Data.Size())\n\t\t\treturn false\n\t\t})\n\tcase 1:\n\t\towner := args[0]\n\t\tkeystoreInterface, exists := data.Get(owner)\n\t\tif !exists {\n\t\t\treturn StatusNoUser\n\t\t}\n\t\tks := keystoreInterface.(*KeyStore)\n\t\ti := 0\n\t\tresponse += ufmt.Sprintf(\"# %s database\\n\\n\", ks.Owner)\n\t\tks.Data.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tresponse += ufmt.Sprintf(\"- %d [%s](%s:%s:get:%s)\\n\", i, key, BaseURL, ks.Owner, key)\n\t\t\ti++\n\t\t\treturn false\n\t\t})\n\tcase 2:\n\t\towner := args[0]\n\t\tcmd := args[1]\n\t\tif cmd == \"size\" {\n\t\t\treturn size(owner)\n\t\t}\n\tcase 3:\n\t\towner := args[0]\n\t\tcmd := args[1]\n\t\tkey := args[2]\n\t\tif cmd == \"get\" {\n\t\t\treturn get(owner, key)\n\t\t}\n\t}\n\n\treturn response\n}\n"},{"name":"keystore_test.gno","body":"package keystore\n\nimport (\n\t\"std\"\n\t\"strings\"\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 TestRender(t *testing.T) {\n\tconst (\n\t\tauthor1 std.Address = testutils.TestAddress(\"author1\")\n\t\tauthor2 std.Address = testutils.TestAddress(\"author2\")\n\t)\n\n\ttt := []struct {\n\t\tcaller std.Address\n\t\towner std.Address\n\t\tps []string\n\t\texp string\n\t}{\n\t\t// can set database if the owner is the caller\n\t\t{author1, author1, []string{\"set\", \"hello\", \"gno\"}, StatusOK},\n\t\t{author1, author1, []string{\"size\"}, \"1\"},\n\t\t{author1, author1, []string{\"set\", \"hello\", \"world\"}, StatusOK},\n\t\t{author1, author1, []string{\"size\"}, \"1\"},\n\t\t{author1, author1, []string{\"set\", \"hi\", \"gno\"}, StatusOK},\n\t\t{author1, author1, []string{\"size\"}, \"2\"},\n\t\t// only owner can remove\n\t\t{author1, author1, []string{\"remove\", \"hi\"}, StatusOK},\n\t\t{author1, author1, []string{\"get\", \"hi\"}, StatusNotFound},\n\t\t{author1, author1, []string{\"size\"}, \"1\"},\n\t\t// add back\n\t\t{author1, author1, []string{\"set\", \"hi\", \"gno\"}, StatusOK},\n\t\t{author1, author1, []string{\"size\"}, \"2\"},\n\n\t\t// different owner has different database\n\t\t{author2, author2, []string{\"set\", \"hello\", \"universe\"}, StatusOK},\n\t\t// either author can get the other info\n\t\t{author1, author2, []string{\"get\", \"hello\"}, \"universe\"},\n\t\t// either author can get the other info\n\t\t{author2, author1, []string{\"get\", \"hello\"}, \"world\"},\n\t\t{author1, author2, []string{\"get\", \"hello\"}, \"universe\"},\n\t\t// anyone can view the databases\n\t\t{author1, author2, []string{}, `- [g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6](/r/demo/keystore:g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6) (2 keys)\n- [g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00](/r/demo/keystore:g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00) (1 keys)`},\n\t\t// anyone can view the keys in a database\n\t\t{author1, author2, []string{\"\"}, `# g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00 database\n\n- 0 [hello](/r/demo/keystore:g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00:get:hello)`},\n\t}\n\tfor _, tc := range tt {\n\t\tp := \"\"\n\t\tif len(tc.ps) \u003e 0 {\n\t\t\tp = tc.owner.String()\n\t\t\tfor i, psv := range tc.ps {\n\t\t\t\tp += \":\" + psv\n\t\t\t}\n\t\t}\n\t\tp = strings.TrimSuffix(p, \":\")\n\t\tt.Run(p, func(t *testing.T) {\n\t\t\tstd.TestSetOrigCaller(tc.caller)\n\t\t\tvar act string\n\t\t\tif len(tc.ps) \u003e 0 \u0026\u0026 tc.ps[0] == \"set\" {\n\t\t\t\tact = strings.TrimSpace(Set(tc.ps[1], tc.ps[2]))\n\t\t\t} else if len(tc.ps) \u003e 0 \u0026\u0026 tc.ps[0] == \"remove\" {\n\t\t\t\tact = strings.TrimSpace(Remove(tc.ps[1]))\n\t\t\t} else {\n\t\t\t\tact = strings.TrimSpace(Render(p))\n\t\t\t}\n\n\t\t\tuassert.Equal(t, tc.exp, act, ufmt.Sprintf(\"%v -\u003e '%s'\", tc.ps, p))\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":"groups","path":"gno.land/r/demo/groups","files":[{"name":"README.md","body":"### - test package\n\n ./build/gno test examples/gno.land/r/demo/groups/\n\n### - add pkg\n\n ./build/gnokey maketx addpkg -pkgdir \"examples/gno.land/r/demo/groups\" -deposit 100000000ugnot -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid dev -remote 0.0.0.0:26657 -pkgpath \"gno.land/r/demo/groups\" test1 \n\n### - create group\n\n ./build/gnokey maketx call -func \"CreateGroup\" -args \"dao_trinity_ngo\" -gas-fee \"1000000ugnot\" -gas-wanted 4000000 -broadcast -chainid dev -remote 0.0.0.0:26657 -pkgpath \"gno.land/r/demo/groups\" test1 \n\n### - add member\n\n ./build/gnokey maketx call -func \"AddMember\" -args \"1\" -args \"g1hd3gwzevxlqmd3jsf64mpfczag8a8e5j2wdn3c\" -args 12 -args \"i am new user\" -gas-fee \"1000000ugnot\" -gas-wanted \"4000000\" -broadcast -chainid dev -remote 0.0.0.0:26657 -pkgpath \"gno.land/r/demo/groups\" test1\n\n### - delete member\n\n ./build/gnokey maketx call -func \"DeleteMember\" -args \"1\" -args \"0\" -gas-fee \"1000000ugnot\" -gas-wanted \"4000000\" -broadcast -chainid dev -remote 0.0.0.0:26657 -pkgpath \"gno.land/r/demo/groups\" test1\n\n### - delete group\n\n ./build/gnokey maketx call -func \"DeleteGroup\" -args \"1\" -gas-fee \"1000000ugnot\" -gas-wanted \"4000000\" -broadcast -chainid dev -remote 0.0.0.0:26657 -pkgpath \"gno.land/r/demo/groups\" test1\n\n"},{"name":"group.gno","body":"package groups\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\ntype GroupID uint64\n\nfunc (gid GroupID) String() string {\n\treturn strconv.Itoa(int(gid))\n}\n\ntype Group struct {\n\tid GroupID\n\turl string\n\tname string\n\tlastMemberID MemberID\n\tmembers avl.Tree\n\tcreator std.Address\n\tcreatedAt time.Time\n}\n\nfunc newGroup(url string, name string, creator std.Address) *Group {\n\tif !reName.MatchString(name) {\n\t\tpanic(\"invalid name: \" + name)\n\t}\n\tif gGroupsByName.Has(name) {\n\t\tpanic(\"Group with such name already exists\")\n\t}\n\treturn \u0026Group{\n\t\tid: incGetGroupID(),\n\t\turl: url,\n\t\tname: name,\n\t\tcreator: creator,\n\t\tmembers: avl.Tree{},\n\t\tcreatedAt: time.Now(),\n\t}\n}\n\nfunc (group *Group) newMember(id MemberID, address std.Address, weight int, metadata string) *Member {\n\tif group.members.Has(address.String()) {\n\t\tpanic(\"this member for this group already exists\")\n\t}\n\treturn \u0026Member{\n\t\tid: id,\n\t\taddress: address,\n\t\tweight: weight,\n\t\tmetadata: metadata,\n\t\tcreatedAt: time.Now(),\n\t}\n}\n\nfunc (group *Group) HasPermission(addr std.Address, perm Permission) bool {\n\tif group.creator != addr {\n\t\treturn false\n\t}\n\treturn isValidPermission(perm)\n}\n\nfunc (group *Group) RenderGroup() string {\n\tstr := \"Group ID: \" + groupIDKey(group.id) + \"\\n\\n\" +\n\t\t\"Group Name: \" + group.name + \"\\n\\n\" +\n\t\t\"Group Creator: \" + usernameOf(group.creator) + \"\\n\\n\" +\n\t\t\"Group createdAt: \" + group.createdAt.String() + \"\\n\\n\" +\n\t\t\"Group Last MemberID: \" + memberIDKey(group.lastMemberID) + \"\\n\\n\"\n\n\tstr += \"Group Members: \\n\\n\"\n\tgroup.members.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tmember := value.(*Member)\n\t\tstr += member.getMemberStr()\n\t\treturn false\n\t})\n\treturn str\n}\n\nfunc (group *Group) deleteGroup() {\n\tgidkey := groupIDKey(group.id)\n\t_, gGroupsRemoved := gGroups.Remove(gidkey)\n\tif !gGroupsRemoved {\n\t\tpanic(\"group does not exist with id \" + group.id.String())\n\t}\n\tgGroupsByName.Remove(group.name)\n}\n\nfunc (group *Group) deleteMember(mid MemberID) {\n\tgidkey := groupIDKey(group.id)\n\tif !gGroups.Has(gidkey) {\n\t\tpanic(\"group does not exist with id \" + group.id.String())\n\t}\n\n\tg := getGroup(group.id)\n\tmidkey := memberIDKey(mid)\n\tg.members.Remove(midkey)\n}\n"},{"name":"groups.gno","body":"package groups\n\nimport (\n\t\"regexp\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n//----------------------------------------\n// Realm (package) state\n\nvar (\n\tgGroups avl.Tree // id -\u003e *Group\n\tgGroupsCtr int // increments Group.id\n\tgGroupsByName avl.Tree // name -\u003e *Group\n)\n\n//----------------------------------------\n// Constants\n\nvar reName = regexp.MustCompile(`^[a-z]+[_a-z0-9]{2,29}$`)\n"},{"name":"member.gno","body":"package groups\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n)\n\ntype MemberID uint64\n\ntype Member struct {\n\tid MemberID\n\taddress std.Address\n\tweight int\n\tmetadata string\n\tcreatedAt time.Time\n}\n\nfunc (mid MemberID) String() string {\n\treturn strconv.Itoa(int(mid))\n}\n\nfunc (member *Member) getMemberStr() string {\n\tmemberDataStr := \"\"\n\tmemberDataStr += \"\\t\\t\\t[\" + memberIDKey(member.id) + \", \" + member.address.String() + \", \" + strconv.Itoa(member.weight) + \", \" + member.metadata + \", \" + member.createdAt.String() + \"],\\n\\n\"\n\treturn memberDataStr\n}\n"},{"name":"misc.gno","body":"package groups\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/r/demo/users\"\n)\n\n//----------------------------------------\n// private utility methods\n// XXX ensure these cannot be called from public.\n\nfunc getGroup(gid GroupID) *Group {\n\tgidkey := groupIDKey(gid)\n\tgroup_, exists := gGroups.Get(gidkey)\n\tif !exists {\n\t\tpanic(\"group id (\" + gid.String() + \") does not exists\")\n\t}\n\tgroup := group_.(*Group)\n\treturn group\n}\n\nfunc incGetGroupID() GroupID {\n\tgGroupsCtr++\n\treturn GroupID(gGroupsCtr)\n}\n\nfunc padLeft(str string, length int) string {\n\tif len(str) \u003e= length {\n\t\treturn str\n\t}\n\treturn strings.Repeat(\" \", length-len(str)) + str\n}\n\nfunc padZero(u64 uint64, length int) string {\n\tstr := strconv.Itoa(int(u64))\n\tif len(str) \u003e= length {\n\t\treturn str\n\t}\n\treturn strings.Repeat(\"0\", length-len(str)) + str\n}\n\nfunc groupIDKey(gid GroupID) string {\n\treturn padZero(uint64(gid), 10)\n}\n\nfunc memberIDKey(mid MemberID) string {\n\treturn padZero(uint64(mid), 10)\n}\n\nfunc indentBody(indent string, body string) string {\n\tlines := strings.Split(body, \"\\n\")\n\tres := \"\"\n\tfor i, line := range lines {\n\t\tif i \u003e 0 {\n\t\t\tres += \"\\n\"\n\t\t}\n\t\tres += indent + line\n\t}\n\treturn res\n}\n\n// NOTE: length must be greater than 3.\nfunc summaryOf(str string, length int) string {\n\tlines := strings.SplitN(str, \"\\n\", 2)\n\tline := lines[0]\n\tif len(line) \u003e length {\n\t\tline = line[:(length-3)] + \"...\"\n\t} else if len(lines) \u003e 1 {\n\t\t// len(line) \u003c= 80\n\t\tline = line + \"...\"\n\t}\n\treturn line\n}\n\nfunc displayAddressMD(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user == nil {\n\t\treturn \"[\" + addr.String() + \"](/r/demo/users:\" + addr.String() + \")\"\n\t}\n\treturn \"[@\" + user.Name + \"](/r/demo/users:\" + user.Name + \")\"\n}\n\nfunc usernameOf(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user == nil {\n\t\tpanic(\"user not found\")\n\t}\n\treturn user.Name\n}\n\nfunc isValidPermission(perm Permission) bool {\n\treturn perm == EditPermission || perm == DeletePermission\n}\n"},{"name":"public.gno","body":"package groups\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\n//----------------------------------------\n// Public facing functions\n\nfunc GetGroupIDFromName(name string) (GroupID, bool) {\n\tgroupI, exists := gGroupsByName.Get(name)\n\tif !exists {\n\t\treturn 0, false\n\t}\n\treturn groupI.(*Group).id, true\n}\n\nfunc CreateGroup(name string) GroupID {\n\tstd.AssertOriginCall()\n\tcaller := std.OriginCaller()\n\tusernameOf(caller)\n\turl := \"/r/demo/groups:\" + name\n\tgroup := newGroup(url, name, caller)\n\tgidkey := groupIDKey(group.id)\n\tgGroups.Set(gidkey, group)\n\tgGroupsByName.Set(name, group)\n\treturn group.id\n}\n\nfunc AddMember(gid GroupID, address string, weight int, metadata string) MemberID {\n\tstd.AssertOriginCall()\n\tcaller := std.OriginCaller()\n\tusernameOf(caller)\n\tgroup := getGroup(gid)\n\tif !group.HasPermission(caller, EditPermission) {\n\t\tpanic(\"unauthorized to edit group\")\n\t}\n\tuser := users.GetUserByAddress(std.Address(address))\n\tif user == nil {\n\t\tpanic(\"unknown address \" + address)\n\t}\n\tmid := group.lastMemberID\n\tmember := group.newMember(mid, std.Address(address), weight, metadata)\n\tmidkey := memberIDKey(mid)\n\tgroup.members.Set(midkey, member)\n\tmid++\n\tgroup.lastMemberID = mid\n\treturn member.id\n}\n\nfunc DeleteGroup(gid GroupID) {\n\tstd.AssertOriginCall()\n\tcaller := std.OriginCaller()\n\tgroup := getGroup(gid)\n\tif !group.HasPermission(caller, DeletePermission) {\n\t\tpanic(\"unauthorized to delete group\")\n\t}\n\tgroup.deleteGroup()\n}\n\nfunc DeleteMember(gid GroupID, mid MemberID) {\n\tstd.AssertOriginCall()\n\tcaller := std.OriginCaller()\n\tgroup := getGroup(gid)\n\tif !group.HasPermission(caller, DeletePermission) {\n\t\tpanic(\"unauthorized to delete member\")\n\t}\n\tgroup.deleteMember(mid)\n}\n"},{"name":"render.gno","body":"package groups\n\nimport (\n\t\"strings\"\n)\n\n//----------------------------------------\n// Render functions\n\nfunc RenderGroup(gid GroupID) string {\n\tgroup := getGroup(gid)\n\tif group == nil {\n\t\treturn \"missing Group\"\n\t}\n\treturn group.RenderGroup()\n}\n\nfunc Render(path string) string {\n\tif path == \"\" {\n\t\tstr := \"List of all Groups:\\n\\n\"\n\t\tgGroups.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tgroup := value.(*Group)\n\t\t\tstr += \" * [\" + group.name + \"](\" + group.url + \")\\n\"\n\t\t\treturn false\n\t\t})\n\t\treturn str\n\t}\n\tparts := strings.Split(path, \"/\")\n\tif len(parts) == 1 {\n\t\t// /r/demo/groups:Group_NAME\n\t\tname := parts[0]\n\t\tgroupI, exists := gGroupsByName.Get(name)\n\t\tif !exists {\n\t\t\treturn \"Group does not exist: \" + name\n\t\t}\n\t\treturn groupI.(*Group).RenderGroup()\n\t} else {\n\t\treturn \"unrecognized path \" + path\n\t}\n}\n"},{"name":"role.gno","body":"package groups\n\ntype Permission string\n\nconst (\n\tDeletePermission Permission = \"role:delete\"\n\tEditPermission Permission = \"role:edit\"\n)\n"},{"name":"z_0_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\nimport (\n\t\"gno.land/r/demo/groups\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// user not found\n"},{"name":"z_0_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// payment must not be less than 20000000\n"},{"name":"z_0_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tprintln(groups.Render(\"\"))\n}\n\n// Output:\n// 1\n// List of all Groups:\n//\n// * [test_group](/r/demo/groups:test_group)\n"},{"name":"z_1_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser0\", \"my profile 1\")\n\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"gnouser1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tusers.Register(caller, \"gnouser1\", \"my other profile 1\")\n\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest2 := testutils.TestAddress(\"gnouser2\")\n\tusers.Invite(test2.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test2)\n\tusers.Register(caller, \"gnouser2\", \"my other profile 2\")\n\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest3 := testutils.TestAddress(\"gnouser3\")\n\tusers.Invite(test3.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test3)\n\tusers.Register(caller, \"gnouser3\", \"my other profile 3\")\n\n\tstd.TestSetOriginCaller(caller)\n\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\n\tgroups.AddMember(gid, test3.String(), 32, \"i am from UAE\")\n\tprintln(groups.Render(\"test_group\"))\n}\n\n// Output:\n// 1\n// Group ID: 0000000001\n//\n// Group Name: test_group\n//\n// Group Creator: gnouser0\n//\n// Group createdAt: 2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001\n//\n// Group Last MemberID: 0000000001\n//\n// Group Members:\n//\n// \t\t\t[0000000000, g1vahx7atnv4erxh6lta047h6lta047h6ll85gpy, 32, i am from UAE, 2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001],\n"},{"name":"z_1_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tgroups.AddMember(2, \"g1vahx7atnv4erxh6lta047h6lta047h6ll85gpy\", 55, \"metadata3\")\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// group id (2) does not exists\n"},{"name":"z_1_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/groups\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\n\t// add member via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOriginCaller(test2)\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\n\tgroups.AddMember(gid, test2.String(), 42, \"metadata3\")\n}\n\n// Error:\n// user not found\n"},{"name":"z_2_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser0\", \"my profile 1\")\n\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"gnouser1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tusers.Register(caller, \"gnouser1\", \"my other profile 1\")\n\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest2 := testutils.TestAddress(\"gnouser2\")\n\tusers.Invite(test2.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test2)\n\tusers.Register(caller, \"gnouser2\", \"my other profile 2\")\n\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest3 := testutils.TestAddress(\"gnouser3\")\n\tusers.Invite(test3.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test3)\n\tusers.Register(caller, \"gnouser3\", \"my other profile 3\")\n\n\tstd.TestSetOriginCaller(caller)\n\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\n\tgroups.AddMember(gid, test2.String(), 42, \"metadata3\")\n\n\tgroups.DeleteMember(gid, 0)\n\tprintln(groups.RenderGroup(gid))\n}\n\n// Output:\n// 1\n// Group ID: 0000000001\n//\n// Group Name: test_group\n//\n// Group Creator: gnouser0\n//\n// Group createdAt: 2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001\n//\n// Group Last MemberID: 0000000001\n//\n// Group Members:\n"},{"name":"z_2_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tgroups.DeleteMember(2, 0)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// group id (2) does not exists\n"},{"name":"z_2_d_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\n\t// delete member via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOriginCaller(test2)\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\n\tgroups.DeleteMember(gid, 0)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// unauthorized to delete member\n"},{"name":"z_2_e_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tgroups.DeleteGroup(gid)\n\tprintln(groups.Render(\"\"))\n}\n\n// Output:\n// 1\n// List of all Groups:\n"},{"name":"z_2_f_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tgroups.DeleteGroup(20)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// group id (20) does not exists\n"},{"name":"z_2_g_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\n\t// delete group via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOriginCaller(test2)\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\n\tgroups.DeleteGroup(gid)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// unauthorized to delete group\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":"keystore","path":"gno.land/r/demo/keystore","files":[{"name":"keystore.gno","body":"package keystore\n\nimport (\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar data avl.Tree\n\nconst (\n\tBaseURL = \"/r/demo/keystore\"\n\tStatusOK = \"ok\"\n\tStatusNoUser = \"user not found\"\n\tStatusNotFound = \"key not found\"\n\tStatusNoWriteAccess = \"no write access\"\n\tStatusCouldNotExecute = \"could not execute\"\n\tStatusNoDatabases = \"no databases\"\n)\n\nfunc init() {\n\tdata = avl.Tree{} // user -\u003e avl.Tree\n}\n\n// KeyStore stores the owner-specific avl.Tree\ntype KeyStore struct {\n\tOwner std.Address\n\tData avl.Tree\n}\n\n// Set will set a value to a key\n// requires write-access (original caller must be caller)\nfunc Set(k, v string) string {\n\torigOwner := std.OriginCaller()\n\treturn set(origOwner.String(), k, v)\n}\n\n// set (private) will set a key to value\n// requires write-access (original caller must be caller)\nfunc set(owner, k, v string) string {\n\torigOwner := std.OriginCaller()\n\tif origOwner.String() != owner {\n\t\treturn StatusNoWriteAccess\n\t}\n\tvar keystore *KeyStore\n\tkeystoreInterface, exists := data.Get(owner)\n\tif !exists {\n\t\tkeystore = \u0026KeyStore{\n\t\t\tOwner: origOwner,\n\t\t\tData: avl.Tree{},\n\t\t}\n\t\tdata.Set(owner, keystore)\n\t} else {\n\t\tkeystore = keystoreInterface.(*KeyStore)\n\t}\n\tkeystore.Data.Set(k, v)\n\treturn StatusOK\n}\n\n// Remove removes a key\n// requires write-access (original owner must be caller)\nfunc Remove(k string) string {\n\torigOwner := std.OriginCaller()\n\treturn remove(origOwner.String(), k)\n}\n\n// remove (private) removes a key\n// requires write-access (original owner must be caller)\nfunc remove(owner, k string) string {\n\torigOwner := std.OriginCaller()\n\tif origOwner.String() != owner {\n\t\treturn StatusNoWriteAccess\n\t}\n\tvar keystore *KeyStore\n\tkeystoreInterface, exists := data.Get(owner)\n\tif !exists {\n\t\tkeystore = \u0026KeyStore{\n\t\t\tOwner: origOwner,\n\t\t\tData: avl.Tree{},\n\t\t}\n\t\tdata.Set(owner, keystore)\n\t} else {\n\t\tkeystore = keystoreInterface.(*KeyStore)\n\t}\n\t_, removed := keystore.Data.Remove(k)\n\tif !removed {\n\t\treturn StatusCouldNotExecute\n\t}\n\treturn StatusOK\n}\n\n// Get returns a value for a key\n// read-only\nfunc Get(k string) string {\n\torigOwner := std.OriginCaller()\n\treturn remove(origOwner.String(), k)\n}\n\n// get (private) returns a value for a key\n// read-only\nfunc get(owner, k string) string {\n\tkeystoreInterface, exists := data.Get(owner)\n\tif !exists {\n\t\treturn StatusNoUser\n\t}\n\tkeystore := keystoreInterface.(*KeyStore)\n\tval, found := keystore.Data.Get(k)\n\tif !found {\n\t\treturn StatusNotFound\n\t}\n\treturn val.(string)\n}\n\n// Size returns size of database\n// read-only\nfunc Size() string {\n\torigOwner := std.OriginCaller()\n\treturn size(origOwner.String())\n}\n\nfunc size(owner string) string {\n\tkeystoreInterface, exists := data.Get(owner)\n\tif !exists {\n\t\treturn StatusNoUser\n\t}\n\tkeystore := keystoreInterface.(*KeyStore)\n\treturn ufmt.Sprintf(\"%d\", keystore.Data.Size())\n}\n\n// Render provides read-only url access to the functions of the keystore\n// \"\" -\u003e show all keystores listed by owner\n// \"owner\" -\u003e show all keys for that owner's keystore\n// \"owner:size\" -\u003e returns size of owner's keystore\n// \"owner:get:key\" -\u003e show value for that key in owner's keystore\nfunc Render(p string) string {\n\tvar response string\n\targs := strings.Split(p, \":\")\n\tnumArgs := len(args)\n\tif p == \"\" {\n\t\tnumArgs = 0\n\t}\n\tswitch numArgs {\n\tcase 0:\n\t\tif data.Size() == 0 {\n\t\t\treturn StatusNoDatabases\n\t\t}\n\t\tdata.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tks := value.(*KeyStore)\n\t\t\tresponse += ufmt.Sprintf(\"- [%s](%s:%s) (%d keys)\\n\", ks.Owner, BaseURL, ks.Owner, ks.Data.Size())\n\t\t\treturn false\n\t\t})\n\tcase 1:\n\t\towner := args[0]\n\t\tkeystoreInterface, exists := data.Get(owner)\n\t\tif !exists {\n\t\t\treturn StatusNoUser\n\t\t}\n\t\tks := keystoreInterface.(*KeyStore)\n\t\ti := 0\n\t\tresponse += ufmt.Sprintf(\"# %s database\\n\\n\", ks.Owner)\n\t\tks.Data.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tresponse += ufmt.Sprintf(\"- %d [%s](%s:%s:get:%s)\\n\", i, key, BaseURL, ks.Owner, key)\n\t\t\ti++\n\t\t\treturn false\n\t\t})\n\tcase 2:\n\t\towner := args[0]\n\t\tcmd := args[1]\n\t\tif cmd == \"size\" {\n\t\t\treturn size(owner)\n\t\t}\n\tcase 3:\n\t\towner := args[0]\n\t\tcmd := args[1]\n\t\tkey := args[2]\n\t\tif cmd == \"get\" {\n\t\t\treturn get(owner, key)\n\t\t}\n\t}\n\n\treturn response\n}\n"},{"name":"keystore_test.gno","body":"package keystore\n\nimport (\n\t\"std\"\n\t\"strings\"\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 TestRender(t *testing.T) {\n\tconst (\n\t\tauthor1 std.Address = testutils.TestAddress(\"author1\")\n\t\tauthor2 std.Address = testutils.TestAddress(\"author2\")\n\t)\n\n\ttt := []struct {\n\t\tcaller std.Address\n\t\towner std.Address\n\t\tps []string\n\t\texp string\n\t}{\n\t\t// can set database if the owner is the caller\n\t\t{author1, author1, []string{\"set\", \"hello\", \"gno\"}, StatusOK},\n\t\t{author1, author1, []string{\"size\"}, \"1\"},\n\t\t{author1, author1, []string{\"set\", \"hello\", \"world\"}, StatusOK},\n\t\t{author1, author1, []string{\"size\"}, \"1\"},\n\t\t{author1, author1, []string{\"set\", \"hi\", \"gno\"}, StatusOK},\n\t\t{author1, author1, []string{\"size\"}, \"2\"},\n\t\t// only owner can remove\n\t\t{author1, author1, []string{\"remove\", \"hi\"}, StatusOK},\n\t\t{author1, author1, []string{\"get\", \"hi\"}, StatusNotFound},\n\t\t{author1, author1, []string{\"size\"}, \"1\"},\n\t\t// add back\n\t\t{author1, author1, []string{\"set\", \"hi\", \"gno\"}, StatusOK},\n\t\t{author1, author1, []string{\"size\"}, \"2\"},\n\n\t\t// different owner has different database\n\t\t{author2, author2, []string{\"set\", \"hello\", \"universe\"}, StatusOK},\n\t\t// either author can get the other info\n\t\t{author1, author2, []string{\"get\", \"hello\"}, \"universe\"},\n\t\t// either author can get the other info\n\t\t{author2, author1, []string{\"get\", \"hello\"}, \"world\"},\n\t\t{author1, author2, []string{\"get\", \"hello\"}, \"universe\"},\n\t\t// anyone can view the databases\n\t\t{author1, author2, []string{}, `- [g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6](/r/demo/keystore:g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6) (2 keys)\n- [g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00](/r/demo/keystore:g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00) (1 keys)`},\n\t\t// anyone can view the keys in a database\n\t\t{author1, author2, []string{\"\"}, `# g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00 database\n\n- 0 [hello](/r/demo/keystore:g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00:get:hello)`},\n\t}\n\tfor _, tc := range tt {\n\t\tp := \"\"\n\t\tif len(tc.ps) \u003e 0 {\n\t\t\tp = tc.owner.String()\n\t\t\tfor i, psv := range tc.ps {\n\t\t\t\tp += \":\" + psv\n\t\t\t}\n\t\t}\n\t\tp = strings.TrimSuffix(p, \":\")\n\t\tt.Run(p, func(t *testing.T) {\n\t\t\tstd.TestSetOriginCaller(tc.caller)\n\t\t\tvar act string\n\t\t\tif len(tc.ps) \u003e 0 \u0026\u0026 tc.ps[0] == \"set\" {\n\t\t\t\tact = strings.TrimSpace(Set(tc.ps[1], tc.ps[2]))\n\t\t\t} else if len(tc.ps) \u003e 0 \u0026\u0026 tc.ps[0] == \"remove\" {\n\t\t\t\tact = strings.TrimSpace(Remove(tc.ps[1]))\n\t\t\t} else {\n\t\t\t\tact = strings.TrimSpace(Render(p))\n\t\t\t}\n\n\t\t\tuassert.Equal(t, tc.exp, act, ufmt.Sprintf(\"%v -\u003e '%s'\", tc.ps, p))\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":"markdown","path":"gno.land/r/demo/markdown_test","files":[{"name":"markdown.gno","body":"package markdown\n\n// this package can be used to test markdown rendering engines.\n\nfunc Render(path string) string {\n\toutput := `_imported from https://github.com/markedjs/marked/blob/master/docs/demo/quickref.md_\n\nMarkdown Quick Reference\n========================\n\nThis guide is a very brief overview, with examples, of the syntax that [Markdown] supports. It is itself written in Markdown and you can copy the samples over to the left-hand pane for experimentation. It's shown as *text* and not *rendered HTML*.\n\n[Markdown]: http://daringfireball.net/projects/markdown/\n\n\nSimple Text Formatting\n======================\n\nFirst thing is first. You can use *stars* or _underscores_ for italics. **Double stars** and __double underscores__ for bold. ***Three together*** for ___both___.\n\nParagraphs are pretty easy too. Just have a blank line between chunks of text.\n\n\u003e This chunk of text is in a block quote. Its multiple lines will all be\n\u003e indented a bit from the rest of the text.\n\u003e\n\u003e \u003e Multiple levels of block quotes also work.\n\nSometimes you want to include code, such as when you are explaining how ` + \"`\u003ch1\u003e`\" + ` HTML tags work, or maybe you are a programmer and you are discussing ` + \"`someMethod()`\" + `.\n\nIf you want to include code and have new\nlines preserved, indent the line with a tab\nor at least four spaces:\n\n Extra spaces work here too.\n This is also called preformatted text and it is useful for showing examples.\n The text will stay as text, so any *markdown* or \u003cu\u003eHTML\u003c/u\u003e you add will\n not show up formatted. This way you can show markdown examples in a\n markdown document.\n\n\u003e You can also use preformatted text with your blockquotes\n\u003e as long as you add at least five spaces.\n\n\nHeadings\n========\n\nThere are a couple of ways to make headings. Using three or more equals signs on a line under a heading makes it into an \"h1\" style. Three or more hyphens under a line makes it \"h2\" (slightly smaller). You can also use multiple pound symbols (` + \"`#`\" + `) before and after a heading. Pounds after the title are ignored. Here are some examples:\n\nThis is H1\n==========\n\nThis is H2\n----------\n\n# This is H1\n## This is H2\n### This is H3 with some extra pounds ###\n#### You get the idea ####\n##### I don't need extra pounds at the end\n###### H6 is the max\n\n\nLinks\n=====\n\nLet's link to a few sites. First, let's use the bare URL, like \u003chttps://www.github.com\u003e. Great for text, but ugly for HTML.\nNext is an inline link to [Google](https://www.google.com). A little nicer.\nThis is a reference-style link to [Wikipedia] [1].\nLastly, here's a pretty link to [Yahoo]. The reference-style and pretty links both automatically use the links defined below, but they could be defined *anywhere* in the markdown and are removed from the HTML. The names are also case insensitive, so you can use [YaHoO] and have it link properly.\n\n[1]: https://www.wikipedia.org\n[Yahoo]: https://www.yahoo.com\n\nTitle attributes may be added to links by adding text after a link.\nThis is the [inline link](https://www.bing.com \"Bing\") with a \"Bing\" title.\nYou can also go to [W3C] [2] and maybe visit a [friend].\n\n[2]: https://w3c.org (The W3C puts out specs for web-based things)\n[Friend]: https://facebook.com \"Facebook!\"\n\nEmail addresses in plain text are not linked: test@example.com.\nEmail addresses wrapped in angle brackets are linked: \u003ctest@example.com\u003e.\nThey are also obfuscated so that email harvesting spam robots hopefully won't get them.\n\n\nLists\n=====\n\n* This is a bulleted list\n* Great for shopping lists\n- You can also use hyphens\n+ Or plus symbols\n\nThe above is an \"unordered\" list. Now, on for a bit of order.\n\n1. Numbered lists are also easy\n2. Just start with a number\n3738762. However, the actual number doesn't matter when converted to HTML.\n1. This will still show up as 4.\n\nYou might want a few advanced lists:\n\n- This top-level list is wrapped in paragraph tags\n- This generates an extra space between each top-level item.\n\n- You do it by adding a blank line\n\n- This nested list also has blank lines between the list items.\n\n- How to create nested lists\n 1. Start your regular list\n 2. Indent nested lists with two spaces\n 3. Further nesting means you should indent with two more spaces\n * This line is indented with four spaces.\n\n- List items can be quite lengthy. You can keep typing and either continue\nthem on the next line with no indentation.\n\n- Alternately, if that looks ugly, you can also\n indent the next line a bit for a prettier look.\n\n- You can put large blocks of text in your list by just indenting with two spaces.\n\n This is formatted the same as code, but you can inspect the HTML\n and find that it's just wrapped in a ` + \"`\u003cp\u003e`\" + ` tag and *won't* be shown\n as preformatted text.\n\n You can keep adding more and more paragraphs to a single\n list item by adding the traditional blank line and then keep\n on indenting the paragraphs with two spaces.\n\n You really only need to indent the first line,\nbut that looks ugly.\n\n- Lists support blockquotes\n\n \u003e Just like this example here. By the way, you can\n \u003e nest lists inside blockquotes!\n \u003e - Fantastic!\n\n- Lists support preformatted text\n\n You just need to indent an additional four spaces.\n\n\nEven More\n=========\n\nHorizontal Rule\n---------------\n\nIf you need a horizontal rule you just need to put at least three hyphens, asterisks, or underscores on a line by themselves. You can also even put spaces between the characters.\n\n---\n****************************\n_ _ _ _ _ _ _\n\nThose three all produced horizontal lines. Keep in mind that three hyphens under any text turns that text into a heading, so add a blank like if you use hyphens.\n\nImages\n------\n\nImages work exactly like links, but they have exclamation points in front. They work with references and titles too.\n\n![Google Logo](https://www.google.com/images/errors/logo_sm.gif) and ![Happy].\n\n[Happy]: https://wpclipart.com/smiley/happy/simple_colors/smiley_face_simple_green_small.png (\"Smiley face\")\n\n\nInline HTML\n-----------\n\nIf markdown is too limiting, you can just insert your own \u003cstrike\u003ecrazy\u003c/strike\u003e HTML. Span-level HTML \u003cu\u003ecan *still* use markdown\u003c/u\u003e. Block level elements must be separated from text by a blank line and must not have any spaces before the opening and closing HTML.\n\n\u003cdiv style='font-family: \"Comic Sans MS\", \"Comic Sans\", cursive;'\u003e\nIt is a pity, but markdown does **not** work in here for most markdown parsers.\n[Marked] handles it pretty well.\n\u003c/div\u003e`\n\treturn output\n}\n"},{"name":"markdown_test.gno","body":"package markdown\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestRender(t *testing.T) {\n\toutput := Render(\"\")\n\tif !strings.Contains(output, \"\\nMarkdown Quick Reference\\n\") {\n\t\tt.Errorf(\"invalid output\")\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":"eval","path":"gno.land/r/demo/math_eval","files":[{"name":"math_eval.gno","body":"// eval realm is capable of evaluating 32-bit integer\n// expressions as they would appear in Go. For example:\n// /r/demo/math_eval:(4+12)/2-1+11*15\npackage eval\n\nimport (\n\tevalint32 \"gno.land/p/demo/math_eval/int32\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nfunc Render(p string) string {\n\tif len(p) == 0 {\n\t\treturn `\nevaluates 32-bit integer expressions. for example:\n\t\t\n[(4+12)/2-1+11*15](/r/demo/math_eval:(4+12)/2-1+11*15)\n\n`\n\t}\n\texpr, err := evalint32.Parse(p)\n\tif err != nil {\n\t\treturn err.Error()\n\t}\n\tres, err := evalint32.Eval(expr, nil)\n\tif err != nil {\n\t\treturn err.Error()\n\t}\n\n\treturn ufmt.Sprintf(\"%s = %d\", p, res)\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":"memeland","path":"gno.land/r/demo/memeland","files":[{"name":"memeland.gno","body":"package memeland\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/demo/memeland\"\n)\n\nvar m *memeland.Memeland\n\nfunc init() {\n\tm = memeland.NewMemeland()\n\tm.TransferOwnership(\"g125em6arxsnj49vx35f0n0z34putv5ty3376fg5\")\n}\n\nfunc PostMeme(data string, timestamp int64) string {\n\treturn m.PostMeme(data, timestamp)\n}\n\nfunc Upvote(id string) string {\n\treturn m.Upvote(id)\n}\n\nfunc GetPostsInRange(startTimestamp, endTimestamp int64, page, pageSize int, sortBy string) string {\n\treturn m.GetPostsInRange(startTimestamp, endTimestamp, page, pageSize, sortBy)\n}\n\nfunc RemovePost(id string) string {\n\treturn m.RemovePost(id)\n}\n\nfunc GetOwner() std.Address {\n\treturn m.Owner()\n}\n\nfunc TransferOwnership(newOwner std.Address) {\n\tif err := m.TransferOwnership(newOwner); err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc Render(path string) string {\n\tnumOfMemes := int(m.MemeCounter)\n\tif numOfMemes == 0 {\n\t\treturn \"No memes posted yet! :/\"\n\t}\n\n\t// Default render is get Posts since year 2000 to now\n\treturn m.GetPostsInRange(0, time.Now().Unix(), 1, 10, \"DATE_CREATED\")\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":"microblog","path":"gno.land/r/demo/microblog","files":[{"name":"README.md","body":"# microblog realm\n\n## Getting started:\n\n(One-time) Add the microblog package:\n\n```\ngnokey maketx addpkg --pkgpath \"gno.land/p/demo/microblog\" --pkgdir \"examples/gno.land/p/demo/microblog\" \\\n --deposit 100000000ugnot --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast --chainid dev --remote localhost:26657 \u003cYOURKEY\u003e\n```\n\n(One-time) Add the microblog realm:\n\n```\ngnokey maketx addpkg --pkgpath \"gno.land/r/demo/microblog\" --pkgdir \"examples/gno.land/r/demo/microblog\" \\\n --deposit 100000000ugnot --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast --chainid dev --remote localhost:26657 \u003cYOURKEY\u003e\n```\n\nAdd a microblog post:\n\n```\ngnokey maketx call --pkgpath \"gno.land/r/demo/microblog\" --func \"NewPost\" --args \"hello, world\" \\\n --gas-fee \"1000000ugnot\" --gas-wanted \"2000000\" --broadcast --chainid dev --remote localhost:26657 \u003cYOURKEY\u003e\n```"},{"name":"microblog.gno","body":"// Microblog is a website with shortform posts from users.\n// The API is simple - \"AddPost\" takes markdown and\n// adds it to the users site.\n// The microblog location is determined by the user address\n// /r/demo/microblog:\u003cYOUR-ADDRESS\u003e\npackage microblog\n\nimport (\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/microblog\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\ttitle = \"gno-based microblog\"\n\tprefix = \"/r/demo/microblog:\"\n\tm *microblog.Microblog\n)\n\nfunc init() {\n\tm = microblog.NewMicroblog(title, prefix)\n}\n\nfunc renderHome() string {\n\toutput := ufmt.Sprintf(\"# %s\\n\\n\", m.Title)\n\toutput += \"# pages\\n\\n\"\n\n\tfor _, page := range m.GetPages() {\n\t\tif u := users.GetUserByAddress(page.Author); u != nil {\n\t\t\toutput += ufmt.Sprintf(\"- [%s (%s)](%s%s)\\n\", u.Name, page.Author.String(), m.Prefix, page.Author.String())\n\t\t} else {\n\t\t\toutput += ufmt.Sprintf(\"- [%s](%s%s)\\n\", page.Author.String(), m.Prefix, page.Author.String())\n\t\t}\n\t}\n\n\treturn output\n}\n\nfunc renderUser(user string) string {\n\tsilo, found := m.Pages.Get(user)\n\tif !found {\n\t\treturn \"404\" // StatusNotFound\n\t}\n\n\treturn PageToString((silo.(*microblog.Page)))\n}\n\nfunc Render(path string) string {\n\tparts := strings.Split(path, \"/\")\n\n\tisHome := path == \"\"\n\tisUser := len(parts) == 1\n\n\tswitch {\n\tcase isHome:\n\t\treturn renderHome()\n\n\tcase isUser:\n\t\treturn renderUser(parts[0])\n\t}\n\n\treturn \"404\" // StatusNotFound\n}\n\nfunc PageToString(p *microblog.Page) string {\n\to := \"\"\n\tif u := users.GetUserByAddress(p.Author); u != nil {\n\t\to += ufmt.Sprintf(\"# [%s](/r/demo/users:%s)\\n\\n\", u, u)\n\t\to += ufmt.Sprintf(\"%s\\n\\n\", u.Profile)\n\t}\n\to += ufmt.Sprintf(\"## [%s](/r/demo/microblog:%s)\\n\\n\", p.Author, p.Author)\n\n\to += ufmt.Sprintf(\"joined %s, last updated %s\\n\\n\", p.CreatedAt.Format(\"2006-02-01\"), p.LastPosted.Format(\"2006-02-01\"))\n\to += \"## feed\\n\\n\"\n\tfor _, u := range p.GetPosts() {\n\t\to += u.String() + \"\\n\\n\"\n\t}\n\treturn o\n}\n\n// NewPost takes a single argument (post markdown) and\n// adds a post to the address of the caller.\nfunc NewPost(text string) string {\n\tif err := m.NewPost(text); err != nil {\n\t\treturn \"unable to add new post\"\n\t}\n\treturn \"added new post\"\n}\n\nfunc Register(name, profile string) string {\n\tcaller := std.OrigCaller() // main\n\tusers.Register(caller, name, profile)\n\treturn \"OK\"\n}\n"},{"name":"microblog_test.gno","body":"package microblog\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestMicroblog(t *testing.T) {\n\tconst (\n\t\tauthor1 std.Address = testutils.TestAddress(\"author1\")\n\t\tauthor2 std.Address = testutils.TestAddress(\"author2\")\n\t)\n\n\tstd.TestSetOrigCaller(author1)\n\n\turequire.Equal(t, \"404\", Render(\"/wrongpath\"), \"rendering not giving 404\")\n\turequire.NotEqual(t, \"404\", Render(\"\"), \"rendering / should not give 404\")\n\turequire.NoError(t, m.NewPost(\"goodbyte, web2\"), \"could not create post\")\n\n\t_, err := m.GetPage(author1.String())\n\turequire.NoError(t, err, \"silo should exist\")\n\n\t_, err = m.GetPage(\"no such author\")\n\turequire.Error(t, err, \"silo should not exist\")\n\n\tstd.TestSetOrigCaller(author2)\n\n\turequire.NoError(t, m.NewPost(\"hello, web3\"), \"could not create post\")\n\turequire.NoError(t, m.NewPost(\"hello again, web3\"), \"could not create post\")\n\turequire.NoError(t, m.NewPost(\"hi again,\\n web4?\"), \"could not create post\")\n\n\tprintln(\"--- MICROBLOG ---\\n\\n\")\n\n\texpected := `# gno-based microblog\n\n# pages\n\n- [g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6](/r/demo/microblog:g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6)\n- [g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00](/r/demo/microblog:g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00)\n`\n\turequire.Equal(t, expected, Render(\"\"), \"incorrect rendering\")\n\n\texpected = `## [g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6](/r/demo/microblog:g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6)\n\njoined 2009-13-02, last updated 2009-13-02\n\n## feed\n\n\u003e goodbyte, web2\n\u003e\n\u003e *Fri, 13 Feb 2009 23:31:30 UTC*`\n\n\turequire.Equal(t, expected, strings.TrimSpace(Render(author1.String())), \"incorrect rendering\")\n\n\texpected = `## [g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00](/r/demo/microblog:g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00)\n\njoined 2009-13-02, last updated 2009-13-02\n\n## feed\n\n\u003e hi again,\n\u003e\n\u003e web4?\n\u003e\n\u003e *Fri, 13 Feb 2009 23:31:30 UTC*\n\n\u003e hello again, web3\n\u003e\n\u003e *Fri, 13 Feb 2009 23:31:30 UTC*\n\n\u003e hello, web3\n\u003e\n\u003e *Fri, 13 Feb 2009 23:31:30 UTC*`\n\n\turequire.Equal(t, expected, strings.TrimSpace(Render(author2.String())), \"incorrect rendering\")\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":"microblog","path":"gno.land/r/demo/microblog","files":[{"name":"README.md","body":"# microblog realm\n\n## Getting started:\n\n(One-time) Add the microblog package:\n\n```\ngnokey maketx addpkg --pkgpath \"gno.land/p/demo/microblog\" --pkgdir \"examples/gno.land/p/demo/microblog\" \\\n --deposit 100000000ugnot --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast --chainid dev --remote localhost:26657 \u003cYOURKEY\u003e\n```\n\n(One-time) Add the microblog realm:\n\n```\ngnokey maketx addpkg --pkgpath \"gno.land/r/demo/microblog\" --pkgdir \"examples/gno.land/r/demo/microblog\" \\\n --deposit 100000000ugnot --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast --chainid dev --remote localhost:26657 \u003cYOURKEY\u003e\n```\n\nAdd a microblog post:\n\n```\ngnokey maketx call --pkgpath \"gno.land/r/demo/microblog\" --func \"NewPost\" --args \"hello, world\" \\\n --gas-fee \"1000000ugnot\" --gas-wanted \"2000000\" --broadcast --chainid dev --remote localhost:26657 \u003cYOURKEY\u003e\n```"},{"name":"microblog.gno","body":"// Microblog is a website with shortform posts from users.\n// The API is simple - \"AddPost\" takes markdown and\n// adds it to the users site.\n// The microblog location is determined by the user address\n// /r/demo/microblog:\u003cYOUR-ADDRESS\u003e\npackage microblog\n\nimport (\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/microblog\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\ttitle = \"gno-based microblog\"\n\tprefix = \"/r/demo/microblog:\"\n\tm *microblog.Microblog\n)\n\nfunc init() {\n\tm = microblog.NewMicroblog(title, prefix)\n}\n\nfunc renderHome() string {\n\toutput := ufmt.Sprintf(\"# %s\\n\\n\", m.Title)\n\toutput += \"# pages\\n\\n\"\n\n\tfor _, page := range m.GetPages() {\n\t\tif u := users.GetUserByAddress(page.Author); u != nil {\n\t\t\toutput += ufmt.Sprintf(\"- [%s (%s)](%s%s)\\n\", u.Name, page.Author.String(), m.Prefix, page.Author.String())\n\t\t} else {\n\t\t\toutput += ufmt.Sprintf(\"- [%s](%s%s)\\n\", page.Author.String(), m.Prefix, page.Author.String())\n\t\t}\n\t}\n\n\treturn output\n}\n\nfunc renderUser(user string) string {\n\tsilo, found := m.Pages.Get(user)\n\tif !found {\n\t\treturn \"404\" // StatusNotFound\n\t}\n\n\treturn PageToString((silo.(*microblog.Page)))\n}\n\nfunc Render(path string) string {\n\tparts := strings.Split(path, \"/\")\n\n\tisHome := path == \"\"\n\tisUser := len(parts) == 1\n\n\tswitch {\n\tcase isHome:\n\t\treturn renderHome()\n\n\tcase isUser:\n\t\treturn renderUser(parts[0])\n\t}\n\n\treturn \"404\" // StatusNotFound\n}\n\nfunc PageToString(p *microblog.Page) string {\n\to := \"\"\n\tif u := users.GetUserByAddress(p.Author); u != nil {\n\t\to += ufmt.Sprintf(\"# [%s](/r/demo/users:%s)\\n\\n\", u, u)\n\t\to += ufmt.Sprintf(\"%s\\n\\n\", u.Profile)\n\t}\n\to += ufmt.Sprintf(\"## [%s](/r/demo/microblog:%s)\\n\\n\", p.Author, p.Author)\n\n\to += ufmt.Sprintf(\"joined %s, last updated %s\\n\\n\", p.CreatedAt.Format(\"2006-02-01\"), p.LastPosted.Format(\"2006-02-01\"))\n\to += \"## feed\\n\\n\"\n\tfor _, u := range p.GetPosts() {\n\t\to += u.String() + \"\\n\\n\"\n\t}\n\treturn o\n}\n\n// NewPost takes a single argument (post markdown) and\n// adds a post to the address of the caller.\nfunc NewPost(text string) string {\n\tif err := m.NewPost(text); err != nil {\n\t\treturn \"unable to add new post\"\n\t}\n\treturn \"added new post\"\n}\n\nfunc Register(name, profile string) string {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(caller, name, profile)\n\treturn \"OK\"\n}\n"},{"name":"microblog_test.gno","body":"package microblog\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestMicroblog(t *testing.T) {\n\tconst (\n\t\tauthor1 std.Address = testutils.TestAddress(\"author1\")\n\t\tauthor2 std.Address = testutils.TestAddress(\"author2\")\n\t)\n\n\tstd.TestSetOriginCaller(author1)\n\n\turequire.Equal(t, \"404\", Render(\"/wrongpath\"), \"rendering not giving 404\")\n\turequire.NotEqual(t, \"404\", Render(\"\"), \"rendering / should not give 404\")\n\turequire.NoError(t, m.NewPost(\"goodbyte, web2\"), \"could not create post\")\n\n\t_, err := m.GetPage(author1.String())\n\turequire.NoError(t, err, \"silo should exist\")\n\n\t_, err = m.GetPage(\"no such author\")\n\turequire.Error(t, err, \"silo should not exist\")\n\n\tstd.TestSetOriginCaller(author2)\n\n\turequire.NoError(t, m.NewPost(\"hello, web3\"), \"could not create post\")\n\turequire.NoError(t, m.NewPost(\"hello again, web3\"), \"could not create post\")\n\turequire.NoError(t, m.NewPost(\"hi again,\\n web4?\"), \"could not create post\")\n\n\tprintln(\"--- MICROBLOG ---\\n\\n\")\n\n\texpected := `# gno-based microblog\n\n# pages\n\n- [g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6](/r/demo/microblog:g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6)\n- [g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00](/r/demo/microblog:g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00)\n`\n\turequire.Equal(t, expected, Render(\"\"), \"incorrect rendering\")\n\n\texpected = `## [g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6](/r/demo/microblog:g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6)\n\njoined 2009-13-02, last updated 2009-13-02\n\n## feed\n\n\u003e goodbyte, web2\n\u003e\n\u003e *Fri, 13 Feb 2009 23:31:30 UTC*`\n\n\turequire.Equal(t, expected, strings.TrimSpace(Render(author1.String())), \"incorrect rendering\")\n\n\texpected = `## [g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00](/r/demo/microblog:g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00)\n\njoined 2009-13-02, last updated 2009-13-02\n\n## feed\n\n\u003e hi again,\n\u003e\n\u003e web4?\n\u003e\n\u003e *Fri, 13 Feb 2009 23:31:30 UTC*\n\n\u003e hello again, web3\n\u003e\n\u003e *Fri, 13 Feb 2009 23:31:30 UTC*\n\n\u003e hello, web3\n\u003e\n\u003e *Fri, 13 Feb 2009 23:31:30 UTC*`\n\n\turequire.Equal(t, expected, strings.TrimSpace(Render(author2.String())), \"incorrect rendering\")\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":"nft","path":"gno.land/r/demo/nft","files":[{"name":"README.md","body":"NFT's are all the rage these days, for various reasons.\n\nI read over EIP-721 which appears to be the de-facto NFT standard on Ethereum. Then, made a sample implementation of EIP-721 (let's here called GRC-721). The implementation isn't complete, but it demonstrates the main functionality.\n\n- [EIP-721](https://eips.ethereum.org/EIPS/eip-721)\n- [gno.land/r/demo/nft/nft.go](https://gno.land/r/demo/nft/nft.go)\n- [zrealm_nft3.go test](https://github.com/gnolang/gno/blob/master/tests/files2/zrealm_nft3.gno)\n\nIn short, this demonstrates how to implement Ethereum contract interfaces in gno.land; by using only standard Go language features.\n\nPlease leave a comment ([guide](https://gno.land/r/demo/boards:gnolang/1)).\n"},{"name":"nft.gno","body":"package nft\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/grc/grc721\"\n)\n\ntype token struct {\n\tgrc721.IGRC721 // implements the GRC721 interface\n\n\ttokenCounter int\n\ttokens avl.Tree // grc721.TokenID -\u003e *NFToken{}\n\toperators avl.Tree // owner std.Address -\u003e operator std.Address\n}\n\ntype NFToken struct {\n\towner std.Address\n\tapproved std.Address\n\ttokenID grc721.TokenID\n\tdata string\n}\n\nvar gToken = \u0026token{}\n\nfunc GetToken() *token { return gToken }\n\nfunc (grc *token) nextTokenID() grc721.TokenID {\n\tgrc.tokenCounter++\n\ts := strconv.Itoa(grc.tokenCounter)\n\treturn grc721.TokenID(s)\n}\n\nfunc (grc *token) getToken(tid grc721.TokenID) (*NFToken, bool) {\n\ttoken, ok := grc.tokens.Get(string(tid))\n\tif !ok {\n\t\treturn nil, false\n\t}\n\treturn token.(*NFToken), true\n}\n\nfunc (grc *token) Mint(to std.Address, data string) grc721.TokenID {\n\ttid := grc.nextTokenID()\n\tgrc.tokens.Set(string(tid), \u0026NFToken{\n\t\towner: to,\n\t\ttokenID: tid,\n\t\tdata: data,\n\t})\n\treturn tid\n}\n\nfunc (grc *token) BalanceOf(owner std.Address) (count int64) {\n\tpanic(\"not yet implemented\")\n}\n\nfunc (grc *token) OwnerOf(tid grc721.TokenID) std.Address {\n\ttoken, ok := grc.getToken(tid)\n\tif !ok {\n\t\tpanic(\"token does not exist\")\n\t}\n\treturn token.owner\n}\n\n// XXX not fully implemented yet.\nfunc (grc *token) SafeTransferFrom(from, to std.Address, tid grc721.TokenID) {\n\tgrc.TransferFrom(from, to, tid)\n\t// When transfer is complete, this function checks if `_to` is a smart\n\t// contract (code size \u003e 0). If so, it calls `onERC721Received` on\n\t// `_to` and throws if the return value is not\n\t// `bytes4(keccak256(\"onERC721Received(address,address,uint256,bytes)\"))`.\n\t// XXX ensure \"to\" is a realm with onERC721Received() signature.\n}\n\nfunc (grc *token) TransferFrom(from, to std.Address, tid grc721.TokenID) {\n\tcaller := std.GetCallerAt(2)\n\ttoken, ok := grc.getToken(tid)\n\t// Throws if `_tokenId` is not a valid NFT.\n\tif !ok {\n\t\tpanic(\"token does not exist\")\n\t}\n\t// Throws unless `msg.sender` is the current owner, an authorized\n\t// operator, or the approved address for this NFT.\n\tif caller != token.owner \u0026\u0026 caller != token.approved {\n\t\toperator, ok := grc.operators.Get(token.owner.String())\n\t\tif !ok || caller != operator.(std.Address) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t}\n\t// Throws if `_from` is not the current owner.\n\tif from != token.owner {\n\t\tpanic(\"from is not the current owner\")\n\t}\n\t// Throws if `_to` is the zero address.\n\tif to == \"\" {\n\t\tpanic(\"to cannot be empty\")\n\t}\n\t// Good.\n\ttoken.owner = to\n}\n\nfunc (grc *token) Approve(approved std.Address, tid grc721.TokenID) {\n\tcaller := std.GetCallerAt(2)\n\ttoken, ok := grc.getToken(tid)\n\t// Throws if `_tokenId` is not a valid NFT.\n\tif !ok {\n\t\tpanic(\"token does not exist\")\n\t}\n\t// Throws unless `msg.sender` is the current owner,\n\t// or an authorized operator.\n\tif caller != token.owner {\n\t\toperator, ok := grc.operators.Get(token.owner.String())\n\t\tif !ok || caller != operator.(std.Address) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t}\n\t// Good.\n\ttoken.approved = approved\n}\n\n// XXX make it work for set of operators.\nfunc (grc *token) SetApprovalForAll(operator std.Address, approved bool) {\n\tcaller := std.GetCallerAt(2)\n\tgrc.operators.Set(caller.String(), operator)\n}\n\nfunc (grc *token) GetApproved(tid grc721.TokenID) std.Address {\n\ttoken, ok := grc.getToken(tid)\n\t// Throws if `_tokenId` is not a valid NFT.\n\tif !ok {\n\t\tpanic(\"token does not exist\")\n\t}\n\treturn token.approved\n}\n\n// XXX make it work for set of operators\nfunc (grc *token) IsApprovedForAll(owner, operator std.Address) bool {\n\toperator2, ok := grc.operators.Get(owner.String())\n\tif !ok {\n\t\treturn false\n\t}\n\treturn operator == operator2.(std.Address)\n}\n"},{"name":"z_0_filetest.gno","body":"// PKGPATH: gno.land/r/demo/nft_test\npackage nft_test\n\nimport (\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/nft\"\n)\n\nfunc main() {\n\taddr1 := testutils.TestAddress(\"addr1\")\n\t// addr2 := testutils.TestAddress(\"addr2\")\n\tgrc721 := nft.GetToken()\n\ttid := grc721.Mint(addr1, \"NFT#1\")\n\tprintln(grc721.OwnerOf(tid))\n\tprintln(addr1)\n}\n\n// Output:\n// g1v9jxgu33ta047h6lta047h6lta047h6l43dqc5\n// g1v9jxgu33ta047h6lta047h6lta047h6l43dqc5\n\n// Realm:\n// switchrealm[\"gno.land/r/demo/nft\"]\n// switchrealm[\"gno.land/r/demo/nft\"]\n// c[67c479d3d51d4056b2f4111d5352912a00be311e:11]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"std.Address\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"g1v9jxgu33ta047h6lta047h6lta047h6l43dqc5\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"std.Address\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/grc/grc721.TokenID\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"1\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"NFT#1\"\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:11\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:10\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[67c479d3d51d4056b2f4111d5352912a00be311e:10]={\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:10\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:9\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/nft.NFToken\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"564a9e78be869bd258fc3c9ad56f5a75ed68818f\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:11\"\n// }\n// }\n// }\n// c[67c479d3d51d4056b2f4111d5352912a00be311e:9]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"1\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/nft.NFToken\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"b53ffc464e1b5655d19b9d5277f3491717c24aca\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:10\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:9\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:8\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[67c479d3d51d4056b2f4111d5352912a00be311e:8]={\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:8\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:5\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"b1d928b3716b147c92730e8d234162bec2f0f2fc\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:9\"\n// }\n// }\n// }\n// u[67c479d3d51d4056b2f4111d5352912a00be311e:5]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"b229b824842ec3e7f2341e33d0fa0ca77af2f480\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:8\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:5\",\n// \"ModTime\": \"7\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:4\",\n// \"RefCount\": \"1\"\n// }\n// }\n// u[67c479d3d51d4056b2f4111d5352912a00be311e:4]={\n// \"Fields\": [\n// {},\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"1e0b9dddb406b4f50500a022266a4cb8a4ea38c6\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:5\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"05ab6746ea84b55ca133806af215d99a1c4b045e\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:6\"\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:4\",\n// \"ModTime\": \"7\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:3\",\n// \"RefCount\": \"1\"\n// }\n// }\n// switchrealm[\"gno.land/r/demo/nft\"]\n// switchrealm[\"gno.land/r/demo/nft_test\"]\n"},{"name":"z_1_filetest.gno","body":"// PKGPATH: gno.land/r/demo/nft_test\npackage nft_test\n\nimport (\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/nft\"\n)\n\nfunc main() {\n\taddr1 := testutils.TestAddress(\"addr1\")\n\taddr2 := testutils.TestAddress(\"addr2\")\n\tgrc721 := nft.GetToken()\n\ttid := grc721.Mint(addr1, \"NFT#1\")\n\tprintln(grc721.OwnerOf(tid))\n\tprintln(addr1)\n\tgrc721.TransferFrom(addr1, addr2, tid)\n}\n\n// Error:\n// unauthorized\n"},{"name":"z_2_filetest.gno","body":"// PKGPATH: gno.land/r/demo/nft_test\npackage nft_test\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/nft\"\n)\n\nfunc main() {\n\tcaller := std.GetCallerAt(1)\n\taddr1 := testutils.TestAddress(\"addr1\")\n\t// addr2 := testutils.TestAddress(\"addr2\")\n\tgrc721 := nft.GetToken()\n\ttid := grc721.Mint(caller, \"NFT#1\")\n\tprintln(grc721.OwnerOf(tid))\n\tprintln(addr1)\n\tgrc721.TransferFrom(caller, addr1, tid)\n}\n\n// Output:\n// g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n// g1v9jxgu33ta047h6lta047h6lta047h6l43dqc5\n"},{"name":"z_3_filetest.gno","body":"// PKGPATH: gno.land/r/demo/nft_test\npackage nft_test\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/nft\"\n)\n\nfunc main() {\n\tcaller := std.GetCallerAt(1)\n\taddr1 := testutils.TestAddress(\"addr1\")\n\taddr2 := testutils.TestAddress(\"addr2\")\n\tgrc721 := nft.GetToken()\n\ttid := grc721.Mint(caller, \"NFT#1\")\n\tprintln(grc721.OwnerOf(tid))\n\tprintln(addr1)\n\tgrc721.Approve(caller, tid) // approve self.\n\tgrc721.TransferFrom(caller, addr1, tid)\n\tgrc721.TransferFrom(addr1, addr2, tid)\n}\n\n// Output:\n// g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n// g1v9jxgu33ta047h6lta047h6lta047h6l43dqc5\n"},{"name":"z_4_filetest.gno","body":"// PKGPATH: gno.land/r/demo/nft_test\npackage nft_test\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/nft\"\n)\n\nfunc main() {\n\tcaller := std.GetCallerAt(1)\n\taddr1 := testutils.TestAddress(\"addr1\")\n\taddr2 := testutils.TestAddress(\"addr2\")\n\tgrc721 := nft.GetToken()\n\ttid := grc721.Mint(caller, \"NFT#1\")\n\tprintln(grc721.OwnerOf(tid))\n\tprintln(addr1)\n\tgrc721.Approve(caller, tid) // approve self.\n\tgrc721.TransferFrom(caller, addr1, tid)\n\tgrc721.Approve(\"\", tid) // approve addr1.\n\tgrc721.TransferFrom(addr1, addr2, tid)\n}\n\n// Error:\n// unauthorized\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":"profile","path":"gno.land/r/demo/profile","files":[{"name":"profile.gno","body":"package profile\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/mux\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar (\n\tfields = avl.NewTree()\n\trouter = mux.NewRouter()\n)\n\n// Standard fields\nconst (\n\tDisplayName = \"DisplayName\"\n\tHomepage = \"Homepage\"\n\tBio = \"Bio\"\n\tAge = \"Age\"\n\tLocation = \"Location\"\n\tAvatar = \"Avatar\"\n\tGravatarEmail = \"GravatarEmail\"\n\tAvailableForHiring = \"AvailableForHiring\"\n\tInvalidField = \"InvalidField\"\n)\n\n// Events\nconst (\n\tProfileFieldCreated = \"ProfileFieldCreated\"\n\tProfileFieldUpdated = \"ProfileFieldUpdated\"\n)\n\n// Field types used when emitting event\nconst FieldType = \"FieldType\"\n\nconst (\n\tBoolField = \"BoolField\"\n\tStringField = \"StringField\"\n\tIntField = \"IntField\"\n)\n\nfunc init() {\n\trouter.HandleFunc(\"\", homeHandler)\n\trouter.HandleFunc(\"u/{addr}\", profileHandler)\n\trouter.HandleFunc(\"f/{addr}/{field}\", fieldHandler)\n}\n\n// List of supported string fields\nvar stringFields = map[string]bool{\n\tDisplayName: true,\n\tHomepage: true,\n\tBio: true,\n\tLocation: true,\n\tAvatar: true,\n\tGravatarEmail: true,\n}\n\n// List of support int fields\nvar intFields = map[string]bool{\n\tAge: true,\n}\n\n// List of support bool fields\nvar boolFields = map[string]bool{\n\tAvailableForHiring: true,\n}\n\n// Setters\n\nfunc SetStringField(field, value string) bool {\n\taddr := std.PrevRealm().Addr()\n\tkey := addr.String() + \":\" + field\n\tupdated := fields.Set(key, value)\n\n\tevent := ProfileFieldCreated\n\tif updated {\n\t\tevent = ProfileFieldUpdated\n\t}\n\n\tstd.Emit(event, FieldType, StringField, field, value)\n\n\treturn updated\n}\n\nfunc SetIntField(field string, value int) bool {\n\taddr := std.PrevRealm().Addr()\n\tkey := addr.String() + \":\" + field\n\tupdated := fields.Set(key, value)\n\n\tevent := ProfileFieldCreated\n\tif updated {\n\t\tevent = ProfileFieldUpdated\n\t}\n\n\tstd.Emit(event, FieldType, IntField, field, string(value))\n\n\treturn updated\n}\n\nfunc SetBoolField(field string, value bool) bool {\n\taddr := std.PrevRealm().Addr()\n\tkey := addr.String() + \":\" + field\n\tupdated := fields.Set(key, value)\n\n\tevent := ProfileFieldCreated\n\tif updated {\n\t\tevent = ProfileFieldUpdated\n\t}\n\n\tstd.Emit(event, FieldType, BoolField, field, ufmt.Sprintf(\"%t\", value))\n\n\treturn updated\n}\n\n// Getters\n\nfunc GetStringField(addr std.Address, field, def string) string {\n\tkey := addr.String() + \":\" + field\n\tif value, ok := fields.Get(key); ok {\n\t\treturn value.(string)\n\t}\n\n\treturn def\n}\n\nfunc GetBoolField(addr std.Address, field string, def bool) bool {\n\tkey := addr.String() + \":\" + field\n\tif value, ok := fields.Get(key); ok {\n\t\treturn value.(bool)\n\t}\n\n\treturn def\n}\n\nfunc GetIntField(addr std.Address, field string, def int) int {\n\tkey := addr.String() + \":\" + field\n\tif value, ok := fields.Get(key); ok {\n\t\treturn value.(int)\n\t}\n\n\treturn def\n}\n"},{"name":"profile_test.gno","body":"package profile\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\n// Global addresses for test users\nvar (\n\talice = testutils.TestAddress(\"alice\")\n\tbob = testutils.TestAddress(\"bob\")\n\tcharlie = testutils.TestAddress(\"charlie\")\n\tdave = testutils.TestAddress(\"dave\")\n\teve = testutils.TestAddress(\"eve\")\n\tfrank = testutils.TestAddress(\"frank\")\n\tuser1 = testutils.TestAddress(\"user1\")\n\tuser2 = testutils.TestAddress(\"user2\")\n)\n\nfunc TestStringFields(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\n\t// Get before setting\n\tname := GetStringField(alice, DisplayName, \"anon\")\n\tuassert.Equal(t, \"anon\", name)\n\n\t// Set new key\n\tupdated := SetStringField(DisplayName, \"Alice foo\")\n\tuassert.Equal(t, updated, false)\n\tupdated = SetStringField(Homepage, \"https://example.com\")\n\tuassert.Equal(t, updated, false)\n\n\t// Update the key\n\tupdated = SetStringField(DisplayName, \"Alice foo\")\n\tuassert.Equal(t, updated, true)\n\n\t// Get after setting\n\tname = GetStringField(alice, DisplayName, \"anon\")\n\thomepage := GetStringField(alice, Homepage, \"\")\n\tbio := GetStringField(alice, Bio, \"42\")\n\n\tuassert.Equal(t, \"Alice foo\", name)\n\tuassert.Equal(t, \"https://example.com\", homepage)\n\tuassert.Equal(t, \"42\", bio)\n}\n\nfunc TestIntFields(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\n\t// Get before setting\n\tage := GetIntField(bob, Age, 25)\n\tuassert.Equal(t, 25, age)\n\n\t// Set new key\n\tupdated := SetIntField(Age, 30)\n\tuassert.Equal(t, updated, false)\n\n\t// Update the key\n\tupdated = SetIntField(Age, 30)\n\tuassert.Equal(t, updated, true)\n\n\t// Get after setting\n\tage = GetIntField(bob, Age, 25)\n\tuassert.Equal(t, 30, age)\n}\n\nfunc TestBoolFields(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(charlie))\n\n\t// Get before setting\n\thiring := GetBoolField(charlie, AvailableForHiring, false)\n\tuassert.Equal(t, false, hiring)\n\n\t// Set\n\tupdated := SetBoolField(AvailableForHiring, true)\n\tuassert.Equal(t, updated, false)\n\n\t// Update the key\n\tupdated = SetBoolField(AvailableForHiring, true)\n\tuassert.Equal(t, updated, true)\n\n\t// Get after setting\n\thiring = GetBoolField(charlie, AvailableForHiring, false)\n\tuassert.Equal(t, true, hiring)\n}\n\nfunc TestMultipleProfiles(t *testing.T) {\n\t// Set profile for user1\n\tstd.TestSetRealm(std.NewUserRealm(user1))\n\tupdated := SetStringField(DisplayName, \"User One\")\n\tuassert.Equal(t, updated, false)\n\n\t// Set profile for user2\n\tstd.TestSetRealm(std.NewUserRealm(user2))\n\tupdated = SetStringField(DisplayName, \"User Two\")\n\tuassert.Equal(t, updated, false)\n\n\t// Get profiles\n\tstd.TestSetRealm(std.NewUserRealm(user1)) // Switch back to user1\n\tname1 := GetStringField(user1, DisplayName, \"anon\")\n\tstd.TestSetRealm(std.NewUserRealm(user2)) // Switch back to user2\n\tname2 := GetStringField(user2, DisplayName, \"anon\")\n\n\tuassert.Equal(t, \"User One\", name1)\n\tuassert.Equal(t, \"User Two\", name2)\n}\n\nfunc TestArbitraryStringField(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(user1))\n\n\t// Set arbitrary string field\n\tupdated := SetStringField(\"MyEmail\", \"my@email.com\")\n\tuassert.Equal(t, updated, false)\n\n\tval := GetStringField(user1, \"MyEmail\", \"\")\n\tuassert.Equal(t, val, \"my@email.com\")\n}\n\nfunc TestArbitraryIntField(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(user1))\n\n\t// Set arbitrary int field\n\tupdated := SetIntField(\"MyIncome\", 100_000)\n\tuassert.Equal(t, updated, false)\n\n\tval := GetIntField(user1, \"MyIncome\", 0)\n\tuassert.Equal(t, val, 100_000)\n}\n\nfunc TestArbitraryBoolField(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(user1))\n\n\t// Set arbitrary int field\n\tupdated := SetBoolField(\"IsWinner\", true)\n\tuassert.Equal(t, updated, false)\n\n\tval := GetBoolField(user1, \"IsWinner\", false)\n\tuassert.Equal(t, val, true)\n}\n"},{"name":"render.gno","body":"package profile\n\nimport (\n\t\"bytes\"\n\t\"net/url\"\n\t\"std\"\n\n\t\"gno.land/p/demo/mux\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nconst (\n\tBaseURL = \"/r/demo/profile\"\n\tSetStringFieldURL = BaseURL + \"$help\u0026func=SetStringField\u0026field=%s\"\n\tSetIntFieldURL = BaseURL + \"$help\u0026func=SetIntField\u0026field=%s\"\n\tSetBoolFieldURL = BaseURL + \"$help\u0026func=SetBoolField\u0026field=%s\"\n\tViewAllFieldsURL = BaseURL + \":u/%s\"\n\tViewFieldURL = BaseURL + \":f/%s/%s\"\n)\n\nfunc homeHandler(res *mux.ResponseWriter, req *mux.Request) {\n\tvar b bytes.Buffer\n\n\tb.WriteString(\"## Setters\\n\")\n\tfor field := range stringFields {\n\t\tlink := ufmt.Sprintf(SetStringFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- [Set %s](%s)\\n\", field, link))\n\t}\n\n\tfor field := range intFields {\n\t\tlink := ufmt.Sprintf(SetIntFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- [Set %s](%s)\\n\", field, link))\n\t}\n\n\tfor field := range boolFields {\n\t\tlink := ufmt.Sprintf(SetBoolFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- [Set %s Field](%s)\\n\", field, link))\n\t}\n\n\tb.WriteString(\"\\n---\\n\\n\")\n\n\tres.Write(b.String())\n}\n\nfunc profileHandler(res *mux.ResponseWriter, req *mux.Request) {\n\tvar b bytes.Buffer\n\taddr := req.GetVar(\"addr\")\n\n\tb.WriteString(ufmt.Sprintf(\"# Profile %s\\n\", addr))\n\n\taddress := std.Address(addr)\n\n\tfor field := range stringFields {\n\t\tvalue := GetStringField(address, field, \"n/a\")\n\t\tlink := ufmt.Sprintf(SetStringFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- %s: %s [Edit](%s)\\n\", field, value, link))\n\t}\n\n\tfor field := range intFields {\n\t\tvalue := GetIntField(address, field, 0)\n\t\tlink := ufmt.Sprintf(SetIntFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- %s: %d [Edit](%s)\\n\", field, value, link))\n\t}\n\n\tfor field := range boolFields {\n\t\tvalue := GetBoolField(address, field, false)\n\t\tlink := ufmt.Sprintf(SetBoolFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- %s: %t [Edit](%s)\\n\", field, value, link))\n\t}\n\n\tres.Write(b.String())\n}\n\nfunc fieldHandler(res *mux.ResponseWriter, req *mux.Request) {\n\tvar b bytes.Buffer\n\taddr := req.GetVar(\"addr\")\n\tfield := req.GetVar(\"field\")\n\n\tb.WriteString(ufmt.Sprintf(\"# Field %s for %s\\n\", field, addr))\n\n\taddress := std.Address(addr)\n\tvalue := \"n/a\"\n\tvar editLink string\n\n\tif _, ok := stringFields[field]; ok {\n\t\tvalue = ufmt.Sprintf(\"%s\", GetStringField(address, field, \"n/a\"))\n\t\teditLink = ufmt.Sprintf(SetStringFieldURL+\"\u0026addr=%s\u0026value=%s\", field, addr, url.QueryEscape(value))\n\t} else if _, ok := intFields[field]; ok {\n\t\tvalue = ufmt.Sprintf(\"%d\", GetIntField(address, field, 0))\n\t\teditLink = ufmt.Sprintf(SetIntFieldURL+\"\u0026addr=%s\u0026value=%s\", field, addr, value)\n\t} else if _, ok := boolFields[field]; ok {\n\t\tvalue = ufmt.Sprintf(\"%t\", GetBoolField(address, field, false))\n\t\teditLink = ufmt.Sprintf(SetBoolFieldURL+\"\u0026addr=%s\u0026value=%s\", field, addr, value)\n\t}\n\n\tb.WriteString(ufmt.Sprintf(\"- %s: %s [Edit](%s)\\n\", field, value, editLink))\n\n\tres.Write(b.String())\n}\n\nfunc Render(path string) string {\n\treturn router.Render(path)\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":"releases_example","path":"gno.land/r/demo/releases_example","files":[{"name":"dummy.gno","body":"package releases_example\n\nfunc init() {\n\t// dummy example data\n\tchangelog.NewRelease(\n\t\t\"v1\",\n\t\t\"r/demo/examples_example_v1\",\n\t\t\"initial release\",\n\t)\n\tchangelog.NewRelease(\n\t\t\"v2\",\n\t\t\"r/demo/examples_example_v2\",\n\t\t\"various improvements\",\n\t)\n}\n"},{"name":"example.gno","body":"// this package demonstrates a way to manage contract releases.\npackage releases_example\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/releases\"\n)\n\nvar (\n\tchangelog = releases.NewChangelog(\"example_app\")\n\tadmin = std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\") // @administrator\n)\n\nfunc init() {\n\t// FIXME: admin = std.GetCreator()\n}\n\nfunc NewRelease(name, url, notes string) {\n\tcaller := std.OrigCaller()\n\tif caller != admin {\n\t\tpanic(\"restricted area\")\n\t}\n\tchangelog.NewRelease(name, url, notes)\n}\n\nfunc UpdateAdmin(address std.Address) {\n\tcaller := std.OrigCaller()\n\tif caller != admin {\n\t\tpanic(\"restricted area\")\n\t}\n\tadmin = address\n}\n\nfunc Render(path string) string {\n\treturn changelog.Render(path)\n}\n"},{"name":"releases0_filetest.gno","body":"package main\n\nimport (\n\t\"gno.land/p/demo/releases\"\n)\n\nfunc main() {\n\tprintln(\"-----------\")\n\tchangelog := releases.NewChangelog(\"example\")\n\tprint(changelog.Render(\"\"))\n\n\tprintln(\"-----------\")\n\tchangelog.NewRelease(\"v1\", \"r/blahblah\", \"* initial version\")\n\tprint(changelog.Render(\"\"))\n\n\tprintln(\"-----------\")\n\tchangelog.NewRelease(\"v2\", \"r/blahblah2\", \"* various improvements\\n* new shiny logo\")\n\tprint(changelog.Render(\"\"))\n\n\tprintln(\"-----------\")\n\tprint(changelog.Latest().Render())\n}\n\n// Output:\n// -----------\n// # example\n//\n// -----------\n// # example\n//\n// ## [example v1 (latest)](r/blahblah)\n//\n// * initial version\n//\n// -----------\n// # example\n//\n// ## [example v2 (latest)](r/blahblah2)\n//\n// * various improvements\n// * new shiny logo\n//\n// ## [example v1](r/blahblah)\n//\n// * initial version\n//\n// -----------\n// ## [example v2 (latest)](r/blahblah2)\n//\n// * various improvements\n// * new shiny logo\n"},{"name":"releases1_filetest.gno","body":"package main\n\nimport (\n\t\"gno.land/r/demo/releases_example\"\n)\n\nfunc main() {\n\tprintln(\"-----------\")\n\tprint(releases_example.Render(\"\"))\n\n\tprintln(\"-----------\")\n\tprint(releases_example.Render(\"v1\"))\n\n\tprintln(\"-----------\")\n\tprint(releases_example.Render(\"v42\"))\n}\n\n// Output:\n// -----------\n// # example_app\n//\n// ## [example_app v2 (latest)](r/demo/examples_example_v2)\n//\n// various improvements\n//\n// ## [example_app v1](r/demo/examples_example_v1)\n//\n// initial release\n//\n// -----------\n// ## [example_app v1](r/demo/examples_example_v1)\n//\n// initial release\n//\n// -----------\n// no such release\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":"releases_example","path":"gno.land/r/demo/releases_example","files":[{"name":"dummy.gno","body":"package releases_example\n\nfunc init() {\n\t// dummy example data\n\tchangelog.NewRelease(\n\t\t\"v1\",\n\t\t\"r/demo/examples_example_v1\",\n\t\t\"initial release\",\n\t)\n\tchangelog.NewRelease(\n\t\t\"v2\",\n\t\t\"r/demo/examples_example_v2\",\n\t\t\"various improvements\",\n\t)\n}\n"},{"name":"example.gno","body":"// this package demonstrates a way to manage contract releases.\npackage releases_example\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/releases\"\n)\n\nvar (\n\tchangelog = releases.NewChangelog(\"example_app\")\n\tadmin = std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\") // @administrator\n)\n\nfunc init() {\n\t// FIXME: admin = std.GetCreator()\n}\n\nfunc NewRelease(name, url, notes string) {\n\tcaller := std.OriginCaller()\n\tif caller != admin {\n\t\tpanic(\"restricted area\")\n\t}\n\tchangelog.NewRelease(name, url, notes)\n}\n\nfunc UpdateAdmin(address std.Address) {\n\tcaller := std.OriginCaller()\n\tif caller != admin {\n\t\tpanic(\"restricted area\")\n\t}\n\tadmin = address\n}\n\nfunc Render(path string) string {\n\treturn changelog.Render(path)\n}\n"},{"name":"releases0_filetest.gno","body":"package main\n\nimport (\n\t\"gno.land/p/demo/releases\"\n)\n\nfunc main() {\n\tprintln(\"-----------\")\n\tchangelog := releases.NewChangelog(\"example\")\n\tprint(changelog.Render(\"\"))\n\n\tprintln(\"-----------\")\n\tchangelog.NewRelease(\"v1\", \"r/blahblah\", \"* initial version\")\n\tprint(changelog.Render(\"\"))\n\n\tprintln(\"-----------\")\n\tchangelog.NewRelease(\"v2\", \"r/blahblah2\", \"* various improvements\\n* new shiny logo\")\n\tprint(changelog.Render(\"\"))\n\n\tprintln(\"-----------\")\n\tprint(changelog.Latest().Render())\n}\n\n// Output:\n// -----------\n// # example\n//\n// -----------\n// # example\n//\n// ## [example v1 (latest)](r/blahblah)\n//\n// * initial version\n//\n// -----------\n// # example\n//\n// ## [example v2 (latest)](r/blahblah2)\n//\n// * various improvements\n// * new shiny logo\n//\n// ## [example v1](r/blahblah)\n//\n// * initial version\n//\n// -----------\n// ## [example v2 (latest)](r/blahblah2)\n//\n// * various improvements\n// * new shiny logo\n"},{"name":"releases1_filetest.gno","body":"package main\n\nimport (\n\t\"gno.land/r/demo/releases_example\"\n)\n\nfunc main() {\n\tprintln(\"-----------\")\n\tprint(releases_example.Render(\"\"))\n\n\tprintln(\"-----------\")\n\tprint(releases_example.Render(\"v1\"))\n\n\tprintln(\"-----------\")\n\tprint(releases_example.Render(\"v42\"))\n}\n\n// Output:\n// -----------\n// # example_app\n//\n// ## [example_app v2 (latest)](r/demo/examples_example_v2)\n//\n// various improvements\n//\n// ## [example_app v1](r/demo/examples_example_v1)\n//\n// initial release\n//\n// -----------\n// ## [example_app v1](r/demo/examples_example_v1)\n//\n// initial release\n//\n// -----------\n// no such release\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":"tamagotchi","path":"gno.land/r/demo/tamagotchi","files":[{"name":"realm.gno","body":"package tamagotchi\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/tamagotchi\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar t *tamagotchi.Tamagotchi\n\nfunc init() {\n\tReset(\"gnome#0\")\n}\n\nfunc Reset(optionalName string) string {\n\tname := optionalName\n\tif name == \"\" {\n\t\theight := std.GetHeight()\n\t\tname = ufmt.Sprintf(\"gnome#%d\", height)\n\t}\n\n\tt = tamagotchi.New(name)\n\n\treturn ufmt.Sprintf(\"A new tamagotchi is born. Their name is %s %s.\", t.Name(), t.Face())\n}\n\nfunc Feed() string {\n\tt.Feed()\n\treturn t.Markdown()\n}\n\nfunc Play() string {\n\tt.Play()\n\treturn t.Markdown()\n}\n\nfunc Heal() string {\n\tt.Heal()\n\treturn t.Markdown()\n}\n\nfunc Render(path string) string {\n\ttama := t.Markdown()\n\tlinks := `Actions:\n* [Feed](/r/demo/tamagotchi$help\u0026func=Feed)\n* [Play](/r/demo/tamagotchi$help\u0026func=Play)\n* [Heal](/r/demo/tamagotchi$help\u0026func=Heal)\n* [Reset](/r/demo/tamagotchi$help\u0026func=Reset)\n`\n\n\treturn tama + \"\\n\\n\" + links\n}\n"},{"name":"z0_filetest.gno","body":"package main\n\nimport (\n\t\"gno.land/r/demo/tamagotchi\"\n)\n\nfunc main() {\n\ttamagotchi.Reset(\"tamagnotchi\")\n\tprintln(tamagotchi.Render(\"\"))\n}\n\n// Output:\n// # tamagnotchi 😃\n//\n// * age: 0\n// * hunger: 50\n// * happiness: 50\n// * health: 50\n// * sleepy: 0\n//\n// Actions:\n// * [Feed](/r/demo/tamagotchi$help\u0026func=Feed)\n// * [Play](/r/demo/tamagotchi$help\u0026func=Play)\n// * [Heal](/r/demo/tamagotchi$help\u0026func=Heal)\n// * [Reset](/r/demo/tamagotchi$help\u0026func=Reset)\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"}]},"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_foo","path":"gno.land/r/demo/tests_foo","files":[{"name":"foo.gno","body":"package tests_foo\n\nimport (\n\t\"gno.land/r/demo/tests\"\n)\n\n// for testing gno.land/r/demo/tests/interfaces.go\n\ntype FooStringer struct {\n\tFieldA string\n}\n\nfunc (fs *FooStringer) String() string {\n\treturn \"\u0026FooStringer{\" + fs.FieldA + \"}\"\n}\n\nfunc AddFooStringer(fa string) {\n\ttests.AddStringer(\u0026FooStringer{fa})\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":"todolistrealm","path":"gno.land/r/demo/todolist","files":[{"name":"todolist.gno","body":"package todolistrealm\n\nimport (\n\t\"bytes\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/seqid\"\n\t\"gno.land/p/demo/todolist\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// State variables\nvar (\n\ttodolistTree *avl.Tree\n\ttlid seqid.ID\n)\n\n// Constructor\nfunc init() {\n\ttodolistTree = avl.NewTree()\n}\n\nfunc NewTodoList(title string) (int, string) {\n\t// Create new Todolist\n\ttl := todolist.NewTodoList(title)\n\t// Update AVL tree with new state\n\ttlid.Next()\n\ttodolistTree.Set(strconv.Itoa(int(tlid)), tl)\n\treturn int(tlid), \"created successfully\"\n}\n\nfunc AddTask(todolistID int, title string) string {\n\t// Get Todolist from AVL tree\n\ttl, ok := todolistTree.Get(strconv.Itoa(todolistID))\n\tif !ok {\n\t\tpanic(\"Todolist not found\")\n\t}\n\n\t// get the number of tasks in the todolist\n\tid := tl.(*todolist.TodoList).Tasks.Size()\n\n\t// create the task\n\ttask := todolist.NewTask(title)\n\n\t// Cast raw data from tree into Todolist struct\n\ttl.(*todolist.TodoList).AddTask(id, task)\n\n\treturn \"task added successfully\"\n}\n\nfunc ToggleTaskStatus(todolistID int, taskID int) string {\n\t// Get Todolist from AVL tree\n\ttl, ok := todolistTree.Get(strconv.Itoa(todolistID))\n\tif !ok {\n\t\tpanic(\"Todolist not found\")\n\t}\n\n\t// Get the task from the todolist\n\ttask, found := tl.(*todolist.TodoList).Tasks.Get(strconv.Itoa(taskID))\n\tif !found {\n\t\tpanic(\"Task not found\")\n\t}\n\n\t// Change the status of the task\n\ttodolist.ToggleTaskStatus(task.(*todolist.Task))\n\n\treturn \"task status changed successfully\"\n}\n\nfunc RemoveTask(todolistID int, taskID int) string {\n\t// Get Todolist from AVL tree\n\ttl, ok := todolistTree.Get(strconv.Itoa(todolistID))\n\tif !ok {\n\t\tpanic(\"Todolist not found\")\n\t}\n\n\t// Get the task from the todolist\n\t_, ok = tl.(*todolist.TodoList).Tasks.Get(strconv.Itoa(taskID))\n\tif !ok {\n\t\tpanic(\"Task not found\")\n\t}\n\n\t// Change the status of the task\n\ttl.(*todolist.TodoList).RemoveTask(strconv.Itoa(taskID))\n\n\treturn \"task status changed successfully\"\n}\n\nfunc RemoveTodoList(todolistID int) string {\n\t// Get Todolist from AVL tree\n\t_, ok := todolistTree.Get(strconv.Itoa(todolistID))\n\tif !ok {\n\t\tpanic(\"Todolist not found\")\n\t}\n\n\t// Remove the todolist\n\ttodolistTree.Remove(strconv.Itoa(todolistID))\n\n\treturn \"Todolist removed successfully\"\n}\n\nfunc Render(path string) string {\n\tif path == \"\" {\n\t\treturn renderHomepage()\n\t}\n\n\treturn \"unknown page\"\n}\n\nfunc renderHomepage() string {\n\t// Define empty buffer\n\tvar b bytes.Buffer\n\n\tb.WriteString(\"# Welcome to ToDolist\\n\\n\")\n\n\t// If no todolists have been created\n\tif todolistTree.Size() == 0 {\n\t\tb.WriteString(\"### No todolists available currently!\")\n\t\treturn b.String()\n\t}\n\n\t// Iterate through AVL tree\n\ttodolistTree.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t// cast raw data from tree into Todolist struct\n\t\ttl := value.(*todolist.TodoList)\n\n\t\t// Add Todolist name\n\t\tb.WriteString(\n\t\t\tufmt.Sprintf(\n\t\t\t\t\"## Todolist #%s: %s\\n\",\n\t\t\t\tkey, // Todolist ID\n\t\t\t\ttl.GetTodolistTitle(),\n\t\t\t),\n\t\t)\n\n\t\t// Add Todolist owner\n\t\tb.WriteString(\n\t\t\tufmt.Sprintf(\n\t\t\t\t\"#### Todolist owner : %s\\n\",\n\t\t\t\ttl.GetTodolistOwner(),\n\t\t\t),\n\t\t)\n\n\t\t// List all todos that are currently Todolisted\n\t\tif todos := tl.GetTasks(); len(todos) \u003e 0 {\n\t\t\tb.WriteString(\n\t\t\t\tufmt.Sprintf(\"Currently Todo tasks: %d\\n\\n\", len(todos)),\n\t\t\t)\n\n\t\t\tfor index, todo := range todos {\n\t\t\t\tb.WriteString(\n\t\t\t\t\tufmt.Sprintf(\"#%d - %s \", index, todo.Title),\n\t\t\t\t)\n\t\t\t\t// displays a checked box if task is marked as done, an empty box if not\n\t\t\t\tif todo.Done {\n\t\t\t\t\tb.WriteString(\n\t\t\t\t\t\t\"☑\\n\\n\",\n\t\t\t\t\t)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tb.WriteString(\n\t\t\t\t\t\"☐\\n\\n\",\n\t\t\t\t)\n\t\t\t}\n\t\t} else {\n\t\t\tb.WriteString(\"No tasks in this list currently\\n\")\n\t\t}\n\n\t\tb.WriteString(\"\\n\")\n\t\treturn false\n\t})\n\n\treturn b.String()\n}\n"},{"name":"todolist_test.gno","body":"package todolistrealm\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/todolist\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\tnode interface{}\n\ttdl *todolist.TodoList\n)\n\nfunc TestNewTodoList(t *testing.T) {\n\ttitle := \"My Todo List\"\n\ttlid, _ := NewTodoList(title)\n\tuassert.Equal(t, 1, tlid, \"tlid does not match\")\n\n\t// get the todolist node from the tree\n\tnode, _ = todolistTree.Get(strconv.Itoa(tlid))\n\t// convert the node to a TodoList struct\n\ttdl = node.(*todolist.TodoList)\n\n\tuassert.Equal(t, title, tdl.Title, \"title does not match\")\n\tuassert.Equal(t, 1, tlid, \"tlid does not match\")\n\tuassert.Equal(t, tdl.Owner.String(), std.OrigCaller().String(), \"owner does not match\")\n\tuassert.Equal(t, 0, len(tdl.GetTasks()), \"Expected no tasks in the todo list\")\n}\n\nfunc TestAddTask(t *testing.T) {\n\tAddTask(1, \"Task 1\")\n\n\ttasks := tdl.GetTasks()\n\tuassert.Equal(t, 1, len(tasks), \"total task does not match\")\n\tuassert.Equal(t, \"Task 1\", tasks[0].Title, \"task title does not match\")\n\tuassert.False(t, tasks[0].Done, \"Expected task to be not done\")\n}\n\nfunc TestToggleTaskStatus(t *testing.T) {\n\tToggleTaskStatus(1, 0)\n\ttask := tdl.GetTasks()[0]\n\tuassert.True(t, task.Done, \"Expected task to be done, but it is not marked as done\")\n\n\tToggleTaskStatus(1, 0)\n\tuassert.False(t, task.Done, \"Expected task to be not done, but it is marked as done\")\n}\n\nfunc TestRemoveTask(t *testing.T) {\n\tRemoveTask(1, 0)\n\ttasks := tdl.GetTasks()\n\tuassert.Equal(t, 0, len(tasks), \"Expected no tasks in the todo list\")\n}\n\nfunc TestRemoveTodoList(t *testing.T) {\n\tRemoveTodoList(1)\n\tuassert.Equal(t, 0, todolistTree.Size(), \"Expected no tasks in the todo list\")\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":"todolistrealm","path":"gno.land/r/demo/todolist","files":[{"name":"todolist.gno","body":"package todolistrealm\n\nimport (\n\t\"bytes\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/seqid\"\n\t\"gno.land/p/demo/todolist\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// State variables\nvar (\n\ttodolistTree *avl.Tree\n\ttlid seqid.ID\n)\n\n// Constructor\nfunc init() {\n\ttodolistTree = avl.NewTree()\n}\n\nfunc NewTodoList(title string) (int, string) {\n\t// Create new Todolist\n\ttl := todolist.NewTodoList(title)\n\t// Update AVL tree with new state\n\ttlid.Next()\n\ttodolistTree.Set(strconv.Itoa(int(tlid)), tl)\n\treturn int(tlid), \"created successfully\"\n}\n\nfunc AddTask(todolistID int, title string) string {\n\t// Get Todolist from AVL tree\n\ttl, ok := todolistTree.Get(strconv.Itoa(todolistID))\n\tif !ok {\n\t\tpanic(\"Todolist not found\")\n\t}\n\n\t// get the number of tasks in the todolist\n\tid := tl.(*todolist.TodoList).Tasks.Size()\n\n\t// create the task\n\ttask := todolist.NewTask(title)\n\n\t// Cast raw data from tree into Todolist struct\n\ttl.(*todolist.TodoList).AddTask(id, task)\n\n\treturn \"task added successfully\"\n}\n\nfunc ToggleTaskStatus(todolistID int, taskID int) string {\n\t// Get Todolist from AVL tree\n\ttl, ok := todolistTree.Get(strconv.Itoa(todolistID))\n\tif !ok {\n\t\tpanic(\"Todolist not found\")\n\t}\n\n\t// Get the task from the todolist\n\ttask, found := tl.(*todolist.TodoList).Tasks.Get(strconv.Itoa(taskID))\n\tif !found {\n\t\tpanic(\"Task not found\")\n\t}\n\n\t// Change the status of the task\n\ttodolist.ToggleTaskStatus(task.(*todolist.Task))\n\n\treturn \"task status changed successfully\"\n}\n\nfunc RemoveTask(todolistID int, taskID int) string {\n\t// Get Todolist from AVL tree\n\ttl, ok := todolistTree.Get(strconv.Itoa(todolistID))\n\tif !ok {\n\t\tpanic(\"Todolist not found\")\n\t}\n\n\t// Get the task from the todolist\n\t_, ok = tl.(*todolist.TodoList).Tasks.Get(strconv.Itoa(taskID))\n\tif !ok {\n\t\tpanic(\"Task not found\")\n\t}\n\n\t// Change the status of the task\n\ttl.(*todolist.TodoList).RemoveTask(strconv.Itoa(taskID))\n\n\treturn \"task status changed successfully\"\n}\n\nfunc RemoveTodoList(todolistID int) string {\n\t// Get Todolist from AVL tree\n\t_, ok := todolistTree.Get(strconv.Itoa(todolistID))\n\tif !ok {\n\t\tpanic(\"Todolist not found\")\n\t}\n\n\t// Remove the todolist\n\ttodolistTree.Remove(strconv.Itoa(todolistID))\n\n\treturn \"Todolist removed successfully\"\n}\n\nfunc Render(path string) string {\n\tif path == \"\" {\n\t\treturn renderHomepage()\n\t}\n\n\treturn \"unknown page\"\n}\n\nfunc renderHomepage() string {\n\t// Define empty buffer\n\tvar b bytes.Buffer\n\n\tb.WriteString(\"# Welcome to ToDolist\\n\\n\")\n\n\t// If no todolists have been created\n\tif todolistTree.Size() == 0 {\n\t\tb.WriteString(\"### No todolists available currently!\")\n\t\treturn b.String()\n\t}\n\n\t// Iterate through AVL tree\n\ttodolistTree.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t// cast raw data from tree into Todolist struct\n\t\ttl := value.(*todolist.TodoList)\n\n\t\t// Add Todolist name\n\t\tb.WriteString(\n\t\t\tufmt.Sprintf(\n\t\t\t\t\"## Todolist #%s: %s\\n\",\n\t\t\t\tkey, // Todolist ID\n\t\t\t\ttl.GetTodolistTitle(),\n\t\t\t),\n\t\t)\n\n\t\t// Add Todolist owner\n\t\tb.WriteString(\n\t\t\tufmt.Sprintf(\n\t\t\t\t\"#### Todolist owner : %s\\n\",\n\t\t\t\ttl.GetTodolistOwner(),\n\t\t\t),\n\t\t)\n\n\t\t// List all todos that are currently Todolisted\n\t\tif todos := tl.GetTasks(); len(todos) \u003e 0 {\n\t\t\tb.WriteString(\n\t\t\t\tufmt.Sprintf(\"Currently Todo tasks: %d\\n\\n\", len(todos)),\n\t\t\t)\n\n\t\t\tfor index, todo := range todos {\n\t\t\t\tb.WriteString(\n\t\t\t\t\tufmt.Sprintf(\"#%d - %s \", index, todo.Title),\n\t\t\t\t)\n\t\t\t\t// displays a checked box if task is marked as done, an empty box if not\n\t\t\t\tif todo.Done {\n\t\t\t\t\tb.WriteString(\n\t\t\t\t\t\t\"☑\\n\\n\",\n\t\t\t\t\t)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tb.WriteString(\n\t\t\t\t\t\"☐\\n\\n\",\n\t\t\t\t)\n\t\t\t}\n\t\t} else {\n\t\t\tb.WriteString(\"No tasks in this list currently\\n\")\n\t\t}\n\n\t\tb.WriteString(\"\\n\")\n\t\treturn false\n\t})\n\n\treturn b.String()\n}\n"},{"name":"todolist_test.gno","body":"package todolistrealm\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/todolist\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\tnode interface{}\n\ttdl *todolist.TodoList\n)\n\nfunc TestNewTodoList(t *testing.T) {\n\ttitle := \"My Todo List\"\n\ttlid, _ := NewTodoList(title)\n\tuassert.Equal(t, 1, tlid, \"tlid does not match\")\n\n\t// get the todolist node from the tree\n\tnode, _ = todolistTree.Get(strconv.Itoa(tlid))\n\t// convert the node to a TodoList struct\n\ttdl = node.(*todolist.TodoList)\n\n\tuassert.Equal(t, title, tdl.Title, \"title does not match\")\n\tuassert.Equal(t, 1, tlid, \"tlid does not match\")\n\tuassert.Equal(t, tdl.Owner.String(), std.OriginCaller().String(), \"owner does not match\")\n\tuassert.Equal(t, 0, len(tdl.GetTasks()), \"Expected no tasks in the todo list\")\n}\n\nfunc TestAddTask(t *testing.T) {\n\tAddTask(1, \"Task 1\")\n\n\ttasks := tdl.GetTasks()\n\tuassert.Equal(t, 1, len(tasks), \"total task does not match\")\n\tuassert.Equal(t, \"Task 1\", tasks[0].Title, \"task title does not match\")\n\tuassert.False(t, tasks[0].Done, \"Expected task to be not done\")\n}\n\nfunc TestToggleTaskStatus(t *testing.T) {\n\tToggleTaskStatus(1, 0)\n\ttask := tdl.GetTasks()[0]\n\tuassert.True(t, task.Done, \"Expected task to be done, but it is not marked as done\")\n\n\tToggleTaskStatus(1, 0)\n\tuassert.False(t, task.Done, \"Expected task to be not done, but it is marked as done\")\n}\n\nfunc TestRemoveTask(t *testing.T) {\n\tRemoveTask(1, 0)\n\ttasks := tdl.GetTasks()\n\tuassert.Equal(t, 0, len(tasks), \"Expected no tasks in the todo list\")\n}\n\nfunc TestRemoveTodoList(t *testing.T) {\n\tRemoveTodoList(1)\n\tuassert.Equal(t, 0, todolistTree.Size(), \"Expected no tasks in the todo list\")\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":"types","path":"gno.land/r/demo/types","files":[{"name":"types.gno","body":"// package to test types behavior in various conditions (TXs, imports).\npackage types\n\nimport (\n\t\"errors\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\nvar (\n\tgInt int = -42\n\tgUint uint = 42\n\tgString string = \"a string\"\n\tgStringSlice []string = []string{\"a\", \"string\", \"slice\"}\n\tgError error = errors.New(\"an error\")\n\tgIntSlice []int = []int{-42, 0, 42}\n\tgUintSlice []uint = []uint{0, 42, 84}\n\tgTree avl.Tree\n\t// gInterface = interface{}{-42, \"a string\", uint(42)}\n)\n\nfunc init() {\n\tgTree.Set(\"a\", \"content of A\")\n\tgTree.Set(\"b\", \"content of B\")\n}\n\nfunc Noop() {}\nfunc RetTimeNow() time.Time { return time.Now() }\nfunc RetString() string { return gString }\nfunc RetStringPointer() *string { return \u0026gString }\nfunc RetUint() uint { return gUint }\nfunc RetInt() int { return gInt }\nfunc RetUintPointer() *uint { return \u0026gUint }\nfunc RetIntPointer() *int { return \u0026gInt }\nfunc RetTree() avl.Tree { return gTree }\nfunc RetIntSlice() []int { return gIntSlice }\nfunc RetUintSlice() []uint { return gUintSlice }\nfunc RetStringSlice() []string { return gStringSlice }\nfunc RetError() error { return gError }\nfunc Panic() { panic(\"PANIC!\") }\n\n// TODO: floats\n// TODO: typed errors\n// TODO: ret interface\n// TODO: recover\n// TODO: take types as input\n\nfunc Render(path string) string {\n\treturn \"package to test data types.\"\n}\n"},{"name":"types_test.gno","body":"package types\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":"ui","path":"gno.land/r/demo/ui","files":[{"name":"ui.gno","body":"package ui\n\nimport \"gno.land/p/demo/ui\"\n\nfunc Render(path string) string {\n\t// TODO: build this realm as a demo one with one page per feature.\n\n\t// TODO: pagination\n\t// TODO: non-standard markdown\n\t// TODO: error, warn\n\t// TODO: header\n\t// TODO: HTML\n\t// TODO: toc\n\t// TODO: forms\n\t// TODO: comments\n\n\tdom := ui.DOM{\n\t\tPrefix: \"r/demo/ui:\",\n\t}\n\n\tdom.Title = \"UI Demo\"\n\n\tdom.Header.Append(ui.Breadcrumb{\n\t\tui.Link{Text: \"foo\", Path: \"foo\"},\n\t\tui.Link{Text: \"bar\", Path: \"foo/bar\"},\n\t})\n\n\tdom.Body.Append(\n\t\tui.Paragraph(\"Simple UI demonstration.\"),\n\t\tui.BulletList{\n\t\t\tui.Text(\"a text\"),\n\t\t\tui.Link{Text: \"a relative link\", Path: \"foobar\"},\n\t\t\tui.Text(\"another text\"),\n\t\t\t// ui.H1(\"a H1 text\"),\n\t\t\tui.Bold(\"a bold text\"),\n\t\t\tui.Italic(\"italic text\"),\n\t\t\tui.Text(\"raw markdown with **bold** text in the middle.\"),\n\t\t\tui.Code(\"some inline code\"),\n\t\t\tui.Link{Text: \"a remote link\", URL: \"https://gno.land\"},\n\t\t},\n\t)\n\n\tdom.Footer.Append(ui.Text(\"I'm the footer.\"))\n\tdom.Body.Append(ui.Text(\"another string.\"))\n\tdom.Body.Append(ui.Paragraph(\"a paragraph.\"), ui.HR{})\n\n\treturn dom.String()\n}\n"},{"name":"ui_test.gno","body":"package ui\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestRender(t *testing.T) {\n\tgot := Render(\"\")\n\texpected := \"# UI Demo\\n\\n[foo](r/demo/ui:foo) / [bar](r/demo/ui:foo/bar)\\n\\n\\nSimple UI demonstration.\\n\\n- a text\\n- [a relative link](r/demo/ui:foobar)\\n- another text\\n- **a bold text**\\n- _italic text_\\n- raw markdown with **bold** text in the middle.\\n- `some inline code`\\n- [a remote link](https://gno.land)\\n\\nanother string.\\n\\na paragraph.\\n\\n\\n---\\n\\n\\nI'm the footer.\\n\\n\"\n\tuassert.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":"userbook","path":"gno.land/r/demo/userbook","files":[{"name":"userbook.gno","body":"// This realm demonstrates a small userbook system working with gnoweb\npackage userbook\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/mux\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype Signup struct {\n\taccount string\n\theight int64\n}\n\n// signups - keep a slice of signed up addresses efficient pagination\nvar signups []Signup\n\n// tracker - keep track of who signed up\nvar (\n\ttracker *avl.Tree\n\trouter *mux.Router\n)\n\nconst (\n\tdefaultPageSize = 20\n\tpathArgument = \"number\"\n\tsubPath = \"page/{\" + pathArgument + \"}\"\n\tsignUpEvent = \"SignUp\"\n)\n\nfunc init() {\n\t// Set up tracker tree\n\ttracker = avl.NewTree()\n\n\t// Set up route handling\n\trouter = mux.NewRouter()\n\trouter.HandleFunc(\"\", renderHelper)\n\trouter.HandleFunc(subPath, renderHelper)\n\n\t// Sign up the deployer\n\tSignUp()\n}\n\nfunc SignUp() string {\n\t// Get transaction caller\n\tcaller := std.PrevRealm().Addr().String()\n\theight := std.GetHeight()\n\n\t// Check if the user is already signed up\n\tif _, exists := tracker.Get(caller); exists {\n\t\tpanic(caller + \" is already signed up!\")\n\t}\n\n\t// Sign up the user\n\ttracker.Set(caller, struct{}{})\n\tsignup := Signup{\n\t\tcaller,\n\t\theight,\n\t}\n\n\tsignups = append(signups, signup)\n\tstd.Emit(signUpEvent, \"SignedUpAccount\", signup.account)\n\n\treturn ufmt.Sprintf(\"%s added to userbook up at block #%d!\", signup.account, signup.height)\n}\n\nfunc GetSignupsInRange(page, pageSize int) ([]Signup, int) {\n\tif page \u003c 1 {\n\t\tpanic(\"page number cannot be less than 1\")\n\t}\n\n\tif pageSize \u003c 1 || pageSize \u003e 50 {\n\t\tpanic(\"page size must be from 1 to 50\")\n\t}\n\n\t// Pagination\n\t// Calculate indexes\n\tstartIndex := (page - 1) * pageSize\n\tendIndex := startIndex + pageSize\n\n\t// If page does not contain any users\n\tif startIndex \u003e= len(signups) {\n\t\treturn nil, -1\n\t}\n\n\t// If page contains fewer users than the page size\n\tif endIndex \u003e len(signups) {\n\t\tendIndex = len(signups)\n\t}\n\n\treturn signups[startIndex:endIndex], endIndex\n}\n\nfunc renderHelper(res *mux.ResponseWriter, req *mux.Request) {\n\ttotalSignups := len(signups)\n\tres.Write(\"# Welcome to UserBook!\\n\\n\")\n\n\t// Get URL parameter\n\tpage, err := strconv.Atoi(req.GetVar(\"number\"))\n\tif err != nil {\n\t\tpage = 1 // render first page on bad input\n\t}\n\n\t// Fetch paginated signups\n\tfetchedSignups, endIndex := GetSignupsInRange(page, defaultPageSize)\n\t// Handle empty page case\n\tif len(fetchedSignups) == 0 {\n\t\tres.Write(\"No users on this page!\\n\\n\")\n\t\tres.Write(\"---\\n\\n\")\n\t\tres.Write(\"[Back to Page #1](/r/demo/userbook:page/1)\\n\\n\")\n\t\treturn\n\t}\n\n\t// Write page title\n\tres.Write(ufmt.Sprintf(\"## UserBook - Page #%d:\\n\\n\", page))\n\n\t// Write signups\n\tpageStartIndex := defaultPageSize * (page - 1)\n\tfor i, signup := range fetchedSignups {\n\t\tout := ufmt.Sprintf(\"#### User #%d - %s - signed up at Block #%d\\n\", pageStartIndex+i, signup.account, signup.height)\n\t\tres.Write(out)\n\t}\n\n\tres.Write(\"---\\n\\n\")\n\n\t// Write UserBook info\n\tlatestSignupIndex := totalSignups - 1\n\tres.Write(ufmt.Sprintf(\"#### Total users: %d\\n\", totalSignups))\n\tres.Write(ufmt.Sprintf(\"#### Latest signup: User #%d at Block #%d\\n\", latestSignupIndex, signups[latestSignupIndex].height))\n\n\tres.Write(\"---\\n\\n\")\n\n\t// Write page number\n\tres.Write(ufmt.Sprintf(\"You're viewing page #%d\", page))\n\n\t// Write navigation buttons\n\tvar prevPage string\n\tvar nextPage string\n\t// If we are on any page that is not the first page\n\tif page \u003e 1 {\n\t\tprevPage = ufmt.Sprintf(\" - [Previous page](/r/demo/userbook:page/%d)\", page-1)\n\t}\n\n\t// If there are more pages after the current one\n\tif endIndex \u003c totalSignups {\n\t\tnextPage = ufmt.Sprintf(\" - [Next page](/r/demo/userbook:page/%d)\\n\\n\", page+1)\n\t}\n\n\tres.Write(prevPage)\n\tres.Write(nextPage)\n}\n\nfunc Render(path string) string {\n\treturn router.Render(path)\n}\n"},{"name":"userbook_test.gno","body":"package userbook\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nfunc TestRender(t *testing.T) {\n\t// Sign up 20 users + deployer\n\tfor i := 0; i \u003c 20; i++ {\n\t\taddrName := ufmt.Sprintf(\"test%d\", i)\n\t\tcaller := testutils.TestAddress(addrName)\n\t\tstd.TestSetOrigCaller(caller)\n\t\tSignUp()\n\t}\n\n\ttestCases := []struct {\n\t\tname string\n\t\tnextPage bool\n\t\tprevPage bool\n\t\tpath string\n\t\texpectedNumberOfUsers int\n\t}{\n\t\t{\n\t\t\tname: \"1st page render\",\n\t\t\tnextPage: true,\n\t\t\tprevPage: false,\n\t\t\tpath: \"page/1\",\n\t\t\texpectedNumberOfUsers: 20,\n\t\t},\n\t\t{\n\t\t\tname: \"2nd page render\",\n\t\t\tnextPage: false,\n\t\t\tprevPage: true,\n\t\t\tpath: \"page/2\",\n\t\t\texpectedNumberOfUsers: 1,\n\t\t},\n\t\t{\n\t\t\tname: \"Invalid path render\",\n\t\t\tnextPage: true,\n\t\t\tprevPage: false,\n\t\t\tpath: \"page/invalidtext\",\n\t\t\texpectedNumberOfUsers: 20,\n\t\t},\n\t\t{\n\t\t\tname: \"Empty Page\",\n\t\t\tnextPage: false,\n\t\t\tprevPage: false,\n\t\t\tpath: \"page/1000\",\n\t\t\texpectedNumberOfUsers: 0,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := Render(tc.path)\n\t\t\tnumUsers := countUsers(got)\n\n\t\t\tif tc.prevPage \u0026\u0026 !strings.Contains(got, \"Previous page\") {\n\t\t\t\tt.Fatalf(\"expected to find Previous page, didn't find it\")\n\t\t\t}\n\t\t\tif tc.nextPage \u0026\u0026 !strings.Contains(got, \"Next page\") {\n\t\t\t\tt.Fatalf(\"expected to find Next page, didn't find it\")\n\t\t\t}\n\n\t\t\tif tc.expectedNumberOfUsers != numUsers {\n\t\t\t\tt.Fatalf(\"expected %d, got %d users\", tc.expectedNumberOfUsers, numUsers)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc countUsers(input string) int {\n\treturn strings.Count(input, \"#### User #\")\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":"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/users\"\n)\n\nvar (\n\tbanker *grc20.Banker = grc20.NewBanker(\"wrapped GNOT\", \"wugnot\", 0)\n\tToken = banker.Token()\n)\n\nconst (\n\tugnotMinDeposit uint64 = 1000\n\twugnotMinDeposit uint64 = 1\n)\n\nfunc Deposit() {\n\tcaller := std.PrevRealm().Addr()\n\tsent := std.OrigSend()\n\tamount := sent.AmountOf(\"ugnot\")\n\n\trequire(uint64(amount) \u003e= ugnotMinDeposit, ufmt.Sprintf(\"Deposit below minimum: %d/%d ugnot.\", amount, ugnotMinDeposit))\n\tcheckErr(banker.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(banker.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 banker.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\tcheckErr(Token.Transfer(toAddr, amount))\n}\n\nfunc Approve(spender pusers.AddressOrName, amount uint64) {\n\tspenderAddr := users.Resolve(spender)\n\tcheckErr(Token.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(Token.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=200000000 |\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=200000000 |\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=200004242 |\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":""}} -{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"gnoblog","path":"gno.land/r/gnoland/blog","files":[{"name":"admin.gno","body":"package gnoblog\n\nimport (\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/r/gov/dao/bridge\"\n)\n\nvar (\n\tadminAddr std.Address\n\tmoderatorList avl.Tree\n\tcommenterList avl.Tree\n\tinPause bool\n)\n\nfunc init() {\n\t// adminAddr = std.OrigCaller() // FIXME: find a way to use this from the main's genesis.\n\tadminAddr = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"\n}\n\nfunc AdminSetAdminAddr(addr std.Address) {\n\tassertIsAdmin()\n\tadminAddr = addr\n}\n\nfunc AdminSetInPause(state bool) {\n\tassertIsAdmin()\n\tinPause = state\n}\n\nfunc AdminAddModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), true)\n}\n\nfunc AdminRemoveModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), false) // FIXME: delete instead?\n}\n\nfunc NewPostExecutor(slug, title, body, publicationDate, authors, tags string) dao.Executor {\n\tcallback := func() error {\n\t\taddPost(std.PrevRealm().Addr(), slug, title, body, publicationDate, authors, tags)\n\n\t\treturn nil\n\t}\n\n\treturn bridge.GovDAO().NewGovDAOExecutor(callback)\n}\n\nfunc ModAddPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\tcaller := std.OrigCaller()\n\taddPost(caller, slug, title, body, publicationDate, authors, tags)\n}\n\nfunc addPost(caller std.Address, slug, title, body, publicationDate, authors, tags string) {\n\tvar tagList []string\n\tif tags != \"\" {\n\t\ttagList = strings.Split(tags, \",\")\n\t}\n\tvar authorList []string\n\tif authors != \"\" {\n\t\tauthorList = strings.Split(authors, \",\")\n\t}\n\n\terr := b.NewPost(caller, slug, title, body, publicationDate, authorList, tagList)\n\n\tcheckErr(err)\n}\n\nfunc ModEditPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.GetPost(slug).Update(title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc ModRemovePost(slug string) {\n\tassertIsModerator()\n\n\tb.RemovePost(slug)\n}\n\nfunc ModAddCommenter(addr std.Address) {\n\tassertIsModerator()\n\tcommenterList.Set(addr.String(), true)\n}\n\nfunc ModDelCommenter(addr std.Address) {\n\tassertIsModerator()\n\tcommenterList.Set(addr.String(), false) // FIXME: delete instead?\n}\n\nfunc ModDelComment(slug string, index int) {\n\tassertIsModerator()\n\n\terr := b.GetPost(slug).DeleteComment(index)\n\tcheckErr(err)\n}\n\nfunc isAdmin(addr std.Address) bool {\n\treturn addr == adminAddr\n}\n\nfunc isModerator(addr std.Address) bool {\n\t_, found := moderatorList.Get(addr.String())\n\treturn found\n}\n\nfunc isCommenter(addr std.Address) bool {\n\t_, found := commenterList.Get(addr.String())\n\treturn found\n}\n\nfunc assertIsAdmin() {\n\tcaller := std.OrigCaller()\n\tif !isAdmin(caller) {\n\t\tpanic(\"access restricted.\")\n\t}\n}\n\nfunc assertIsModerator() {\n\tcaller := std.OrigCaller()\n\tif isAdmin(caller) || isModerator(caller) {\n\t\treturn\n\t}\n\tpanic(\"access restricted\")\n}\n\nfunc assertIsCommenter() {\n\tcaller := std.OrigCaller()\n\tif isAdmin(caller) || isModerator(caller) || isCommenter(caller) {\n\t\treturn\n\t}\n\tpanic(\"access restricted\")\n}\n\nfunc assertNotInPause() {\n\tif inPause {\n\t\tpanic(\"access restricted (pause)\")\n\t}\n}\n"},{"name":"gnoblog.gno","body":"package gnoblog\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/blog\"\n)\n\nvar b = \u0026blog.Blog{\n\tTitle: \"Gnoland's Blog\",\n\tPrefix: \"/r/gnoland/blog:\",\n}\n\nfunc AddComment(postSlug, comment string) {\n\tassertIsCommenter()\n\tassertNotInPause()\n\n\tcaller := std.OrigCaller()\n\terr := b.GetPost(postSlug).AddComment(caller, comment)\n\tcheckErr(err)\n}\n\nfunc Render(path string) string {\n\treturn b.Render(path)\n}\n\nfunc RenderLastPostsWidget(limit int) string {\n\treturn b.RenderLastPostsWidget(limit)\n}\n\nfunc PostExists(slug string) bool {\n\tif b.GetPost(slug) == nil {\n\t\treturn false\n\t}\n\treturn true\n}\n"},{"name":"gnoblog_test.gno","body":"package gnoblog\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestPackage(t *testing.T) {\n\tstd.TestSetOrigCaller(std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"))\n\n\tauthor := std.OrigCaller()\n\n\t// by default, no posts.\n\t{\n\t\tgot := Render(\"\")\n\t\texpected := `\n# Gnoland's Blog\n\nNo posts.\n`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// create two posts, list post.\n\t{\n\t\tModAddPost(\"slug1\", \"title1\", \"body1\", \"2022-05-20T13:17:22Z\", \"moul\", \"tag1,tag2\")\n\t\tModAddPost(\"slug2\", \"title2\", \"body2\", \"2022-05-20T13:17:23Z\", \"moul\", \"tag1,tag3\")\n\t\tgot := Render(\"\")\n\t\texpected := `\n\t# Gnoland's Blog\n\n\u003cdiv class='columns-3'\u003e\u003cdiv\u003e\n\n### [title2](/r/gnoland/blog:p/slug2)\n 20 May 2022\n\u003c/div\u003e\u003cdiv\u003e\n\n### [title1](/r/gnoland/blog:p/slug1)\n 20 May 2022\n\u003c/div\u003e\u003c/div\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// view post.\n\t{\n\t\tgot := Render(\"p/slug2\")\n\t\texpected := `\n\u003cmain class='gno-tmpl-page'\u003e\n\n# title2\n\nbody2\n\n---\n\nTags: [#tag1](/r/gnoland/blog:t/tag1) [#tag3](/r/gnoland/blog:t/tag3)\n\nWritten by moul on 20 May 2022\n\nPublished by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq to Gnoland's Blog\n\n---\n\u003cdetails\u003e\u003csummary\u003eComment section\u003c/summary\u003e\n\n\u003c/details\u003e\n\u003c/main\u003e\n\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// list by tags.\n\t{\n\t\tgot := Render(\"t/invalid\")\n\t\texpected := \"# [Gnoland's Blog](/r/gnoland/blog:) / t / invalid\\n\\nNo posts.\"\n\t\tassertMDEquals(t, got, expected)\n\n\t\tgot = Render(\"t/tag2\")\n\t\texpected = `\n# [Gnoland's Blog](/r/gnoland/blog:) / t / tag2\n\n\u003cdiv\u003e\n\n### [title1](/r/gnoland/blog:p/slug1)\n 20 May 2022\n\u003c/div\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// add comments.\n\t{\n\t\tAddComment(\"slug1\", \"comment1\")\n\t\tAddComment(\"slug2\", \"comment2\")\n\t\tAddComment(\"slug1\", \"comment3\")\n\t\tAddComment(\"slug2\", \"comment4\")\n\t\tAddComment(\"slug1\", \"comment5\")\n\t\tgot := Render(\"p/slug2\")\n\t\texpected := `\u003cmain class='gno-tmpl-page'\u003e\n\n# title2\n\nbody2\n\n---\n\nTags: [#tag1](/r/gnoland/blog:t/tag1) [#tag3](/r/gnoland/blog:t/tag3)\n\nWritten by moul on 20 May 2022\n\nPublished by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq to Gnoland's Blog\n\n---\n\u003cdetails\u003e\u003csummary\u003eComment section\u003c/summary\u003e\n\n\u003ch5\u003ecomment4\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003ch5\u003ecomment2\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003c/details\u003e\n\u003c/main\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// edit post.\n\t{\n\t\toldTitle := \"title2\"\n\t\toldDate := \"2022-05-20T13:17:23Z\"\n\n\t\tModEditPost(\"slug2\", oldTitle, \"body2++\", oldDate, \"manfred\", \"tag1,tag4\")\n\t\tgot := Render(\"p/slug2\")\n\t\texpected := `\u003cmain class='gno-tmpl-page'\u003e\n\n# title2\n\nbody2++\n\n---\n\nTags: [#tag1](/r/gnoland/blog:t/tag1) [#tag4](/r/gnoland/blog:t/tag4)\n\nWritten by manfred on 20 May 2022\n\nPublished by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq to Gnoland's Blog\n\n---\n\u003cdetails\u003e\u003csummary\u003eComment section\u003c/summary\u003e\n\n\u003ch5\u003ecomment4\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003ch5\u003ecomment2\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003c/details\u003e\n\u003c/main\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\n\t\thome := Render(\"\")\n\n\t\tif strings.Count(home, oldTitle) != 1 {\n\t\t\tt.Errorf(\"post not edited properly\")\n\t\t}\n\t\t// Edits work everything except title, slug, and publicationDate\n\t\t// Edits to the above will cause duplication on the blog home page\n\t}\n\n\t{ // Test remove functionality\n\t\ttitle := \"example title\"\n\t\tslug := \"testSlug1\"\n\t\tModAddPost(slug, title, \"body1\", \"2022-05-25T13:17:22Z\", \"moul\", \"tag1,tag2\")\n\n\t\tgot := Render(\"\")\n\n\t\tif !strings.Contains(got, title) {\n\t\t\tt.Errorf(\"post was not added properly\")\n\t\t}\n\n\t\tpostRender := Render(\"p/\" + slug)\n\n\t\tif !strings.Contains(postRender, title) {\n\t\t\tt.Errorf(\"post not rendered properly\")\n\t\t}\n\n\t\tModRemovePost(slug)\n\t\tgot = Render(\"\")\n\n\t\tif strings.Contains(got, title) {\n\t\t\tt.Errorf(\"post was not removed\")\n\t\t}\n\n\t\tpostRender = Render(\"p/\" + slug)\n\n\t\tassertMDEquals(t, postRender, \"404\")\n\t}\n\n\t// TODO: pagination.\n\t// TODO: ?format=...\n\n\t// all 404s\n\t{\n\t\tnotFoundPaths := []string{\n\t\t\t\"p/slug3\",\n\t\t\t\"p\",\n\t\t\t\"p/\",\n\t\t\t\"x/x\",\n\t\t\t\"t\",\n\t\t\t\"t/\",\n\t\t\t\"/\",\n\t\t\t\"p/slug1/\",\n\t\t}\n\t\tfor _, notFoundPath := range notFoundPaths {\n\t\t\tgot := Render(notFoundPath)\n\t\t\texpected := \"404\"\n\t\t\tif got != expected {\n\t\t\t\tt.Errorf(\"path %q: expected %q, got %q.\", notFoundPath, expected, got)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc assertMDEquals(t *testing.T, got, expected string) {\n\tt.Helper()\n\texpected = strings.TrimSpace(expected)\n\tgot = strings.TrimSpace(got)\n\tif expected != got {\n\t\tt.Errorf(\"invalid render output.\\nexpected %q.\\ngot %q.\", expected, got)\n\t}\n}\n"},{"name":"util.gno","body":"package gnoblog\n\nfunc checkErr(err error) {\n\tif err != nil {\n\t\tpanic(err)\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":"events","path":"gno.land/r/gnoland/events","files":[{"name":"administration.gno","body":"package events\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/ownable/exts/authorizable\"\n)\n\nvar (\n\tsu = std.Address(\"g125em6arxsnj49vx35f0n0z34putv5ty3376fg5\") // @leohhhn\n\tauth = authorizable.NewAuthorizableWithAddress(su)\n)\n\n// GetOwner gets the owner of the events realm\nfunc GetOwner() std.Address {\n\treturn auth.Owner()\n}\n\n// AddModerator adds a moderator to the events realm\nfunc AddModerator(mod std.Address) {\n\tauth.AssertCallerIsOwner()\n\n\tif err := auth.AddToAuthList(mod); err != nil {\n\t\tpanic(err)\n\t}\n}\n"},{"name":"errors.gno","body":"package events\n\nimport (\n\t\"errors\"\n\t\"strconv\"\n)\n\nvar (\n\tErrEmptyName = errors.New(\"event name cannot be empty\")\n\tErrNoSuchID = errors.New(\"event with specified ID does not exist\")\n\tErrMinWidgetSize = errors.New(\"you need to request at least 1 event to render\")\n\tErrMaxWidgetSize = errors.New(\"maximum number of events in widget is\" + strconv.Itoa(MaxWidgetSize))\n\tErrDescriptionTooLong = errors.New(\"event description is too long\")\n\tErrInvalidStartTime = errors.New(\"invalid start time format\")\n\tErrInvalidEndTime = errors.New(\"invalid end time format\")\n\tErrEndBeforeStart = errors.New(\"end time cannot be before start time\")\n\tErrStartEndTimezonemMismatch = errors.New(\"start and end timezones are not the same\")\n)\n"},{"name":"events.gno","body":"// Package events allows you to upload data about specific IRL/online events\n// It includes dynamic support for updating rendering events based on their\n// status, ie if they are upcoming, in progress, or in the past.\npackage events\n\nimport (\n\t\"sort\"\n\t\"std\"\n\t\"strings\"\n\t\"time\"\n\n\t\"gno.land/p/demo/seqid\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype (\n\tEvent struct {\n\t\tid string\n\t\tname string // name of event\n\t\tdescription string // short description of event\n\t\tlink string // link to auth corresponding web2 page, ie eventbrite/luma or conference page\n\t\tlocation string // location of the event\n\t\tstartTime time.Time // given in RFC3339\n\t\tendTime time.Time // end time of the event, given in RFC3339\n\t}\n\n\teventsSlice []*Event\n)\n\nvar (\n\tevents = make(eventsSlice, 0) // sorted\n\tidCounter seqid.ID\n)\n\nconst (\n\tmaxDescLength = 100\n\tEventAdded = \"EventAdded\"\n\tEventDeleted = \"EventDeleted\"\n\tEventEdited = \"EventEdited\"\n)\n\n// AddEvent adds auth new event\n// Start time \u0026 end time need to be specified in RFC3339, ie 2024-08-08T12:00:00+02:00\nfunc AddEvent(name, description, link, location, startTime, endTime string) (string, error) {\n\tauth.AssertOnAuthList()\n\n\tif strings.TrimSpace(name) == \"\" {\n\t\treturn \"\", ErrEmptyName\n\t}\n\n\tif len(description) \u003e maxDescLength {\n\t\treturn \"\", ufmt.Errorf(\"%s: provided length is %d, maximum is %d\", ErrDescriptionTooLong, len(description), maxDescLength)\n\t}\n\n\t// Parse times\n\tst, et, err := parseTimes(startTime, endTime)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tid := idCounter.Next().String()\n\te := \u0026Event{\n\t\tid: id,\n\t\tname: name,\n\t\tdescription: description,\n\t\tlink: link,\n\t\tlocation: location,\n\t\tstartTime: st,\n\t\tendTime: et,\n\t}\n\n\tevents = append(events, e)\n\tsort.Sort(events)\n\n\tstd.Emit(EventAdded,\n\t\t\"id\", e.id,\n\t)\n\n\treturn id, nil\n}\n\n// DeleteEvent deletes an event with auth given ID\nfunc DeleteEvent(id string) {\n\tauth.AssertOnAuthList()\n\n\te, idx, err := GetEventByID(id)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tevents = append(events[:idx], events[idx+1:]...)\n\n\tstd.Emit(EventDeleted,\n\t\t\"id\", e.id,\n\t)\n}\n\n// EditEvent edits an event with auth given ID\n// It only updates values corresponding to non-empty arguments sent with the call\n// Note: if you need to update the start time or end time, you need to provide both every time\nfunc EditEvent(id string, name, description, link, location, startTime, endTime string) {\n\tauth.AssertOnAuthList()\n\n\te, _, err := GetEventByID(id)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Set only valid values\n\tif strings.TrimSpace(name) != \"\" {\n\t\te.name = name\n\t}\n\n\tif strings.TrimSpace(description) != \"\" {\n\t\te.description = description\n\t}\n\n\tif strings.TrimSpace(link) != \"\" {\n\t\te.link = link\n\t}\n\n\tif strings.TrimSpace(location) != \"\" {\n\t\te.location = location\n\t}\n\n\tif strings.TrimSpace(startTime) != \"\" || strings.TrimSpace(endTime) != \"\" {\n\t\tst, et, err := parseTimes(startTime, endTime)\n\t\tif err != nil {\n\t\t\tpanic(err) // need to also revert other state changes\n\t\t}\n\n\t\toldStartTime := e.startTime\n\t\te.startTime = st\n\t\te.endTime = et\n\n\t\t// If sort order was disrupted, sort again\n\t\tif oldStartTime != e.startTime {\n\t\t\tsort.Sort(events)\n\t\t}\n\t}\n\n\tstd.Emit(EventEdited,\n\t\t\"id\", e.id,\n\t)\n}\n\nfunc GetEventByID(id string) (*Event, int, error) {\n\tfor i, event := range events {\n\t\tif event.id == id {\n\t\t\treturn event, i, nil\n\t\t}\n\t}\n\n\treturn nil, -1, ErrNoSuchID\n}\n\n// Len returns the length of the slice\nfunc (m eventsSlice) Len() int {\n\treturn len(m)\n}\n\n// Less compares the startTime fields of two elements\n// In this case, events will be sorted by largest startTime first (upcoming \u003e past)\nfunc (m eventsSlice) Less(i, j int) bool {\n\treturn m[i].startTime.After(m[j].startTime)\n}\n\n// Swap swaps two elements in the slice\nfunc (m eventsSlice) Swap(i, j int) {\n\tm[i], m[j] = m[j], m[i]\n}\n\n// parseTimes parses the start and end time for an event and checks for possible errors\nfunc parseTimes(startTime, endTime string) (time.Time, time.Time, error) {\n\tst, err := time.Parse(time.RFC3339, startTime)\n\tif err != nil {\n\t\treturn time.Time{}, time.Time{}, ufmt.Errorf(\"%s: %s\", ErrInvalidStartTime, err.Error())\n\t}\n\n\tet, err := time.Parse(time.RFC3339, endTime)\n\tif err != nil {\n\t\treturn time.Time{}, time.Time{}, ufmt.Errorf(\"%s: %s\", ErrInvalidEndTime, err.Error())\n\t}\n\n\tif et.Before(st) {\n\t\treturn time.Time{}, time.Time{}, ErrEndBeforeStart\n\t}\n\n\t_, stOffset := st.Zone()\n\t_, etOffset := et.Zone()\n\tif stOffset != etOffset {\n\t\treturn time.Time{}, time.Time{}, ErrStartEndTimezonemMismatch\n\t}\n\n\treturn st, et, nil\n}\n"},{"name":"events_test.gno","body":"package events\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gno.land/p/demo/uassert\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nvar (\n\tsuRealm = std.NewUserRealm(su)\n\n\tnow = \"2009-02-13T23:31:30Z\" // time.Now() is hardcoded to this value in the gno test machine currently\n\tparsedTimeNow, _ = time.Parse(time.RFC3339, now)\n)\n\nfunc TestAddEvent(t *testing.T) {\n\tstd.TestSetOrigCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\te1Start := parsedTimeNow.Add(time.Hour * 24 * 5)\n\te1End := e1Start.Add(time.Hour * 4)\n\n\tAddEvent(\"Event 1\", \"this event is upcoming\", \"gno.land\", \"gnome land\", e1Start.Format(time.RFC3339), e1End.Format(time.RFC3339))\n\n\tgot := renderHome(false)\n\n\tif !strings.Contains(got, \"Event 1\") {\n\t\tt.Fatalf(\"Expected to find Event 1 in render\")\n\t}\n\n\te2Start := parsedTimeNow.Add(-time.Hour * 24 * 5)\n\te2End := e2Start.Add(time.Hour * 4)\n\n\tAddEvent(\"Event 2\", \"this event is in the past\", \"gno.land\", \"gnome land\", e2Start.Format(time.RFC3339), e2End.Format(time.RFC3339))\n\n\tgot = renderHome(false)\n\n\tupcomingPos := strings.Index(got, \"## Upcoming events\")\n\tpastPos := strings.Index(got, \"## Past events\")\n\n\te1Pos := strings.Index(got, \"Event 1\")\n\te2Pos := strings.Index(got, \"Event 2\")\n\n\t// expected index ordering: upcoming \u003c e1 \u003c past \u003c e2\n\tif e1Pos \u003c upcomingPos || e1Pos \u003e pastPos {\n\t\tt.Fatalf(\"Expected to find Event 1 in Upcoming events\")\n\t}\n\n\tif e2Pos \u003c upcomingPos || e2Pos \u003c pastPos || e2Pos \u003c e1Pos {\n\t\tt.Fatalf(\"Expected to find Event 2 on auth different pos\")\n\t}\n\n\t// larger index =\u003e smaller startTime (future =\u003e past)\n\tif events[0].startTime.Unix() \u003c events[1].startTime.Unix() {\n\t\tt.Fatalf(\"expected ordering to be different\")\n\t}\n}\n\nfunc TestAddEventErrors(t *testing.T) {\n\tstd.TestSetOrigCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\t_, err := AddEvent(\"\", \"sample desc\", \"gno.land\", \"gnome land\", \"2009-02-13T23:31:31Z\", \"2009-02-13T23:33:31Z\")\n\tuassert.ErrorIs(t, err, ErrEmptyName)\n\n\t_, err = AddEvent(\"sample name\", \"sample desc\", \"gno.land\", \"gnome land\", \"\", \"2009-02-13T23:33:31Z\")\n\tuassert.ErrorContains(t, err, ErrInvalidStartTime.Error())\n\n\t_, err = AddEvent(\"sample name\", \"sample desc\", \"gno.land\", \"gnome land\", \"2009-02-13T23:31:31Z\", \"\")\n\tuassert.ErrorContains(t, err, ErrInvalidEndTime.Error())\n\n\t_, err = AddEvent(\"sample name\", \"sample desc\", \"gno.land\", \"gnome land\", \"2009-02-13T23:31:31Z\", \"2009-02-13T23:30:31Z\")\n\tuassert.ErrorIs(t, err, ErrEndBeforeStart)\n\n\t_, err = AddEvent(\"sample name\", \"sample desc\", \"gno.land\", \"gnome land\", \"2009-02-13T23:31:31+06:00\", \"2009-02-13T23:33:31+02:00\")\n\tuassert.ErrorIs(t, err, ErrStartEndTimezonemMismatch)\n\n\ttooLongDesc := `Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean ma`\n\t_, err = AddEvent(\"sample name\", tooLongDesc, \"gno.land\", \"gnome land\", \"2009-02-13T23:31:31Z\", \"2009-02-13T23:33:31Z\")\n\tuassert.ErrorContains(t, err, ErrDescriptionTooLong.Error())\n}\n\nfunc TestDeleteEvent(t *testing.T) {\n\tstd.TestSetOrigCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\te1Start := parsedTimeNow.Add(time.Hour * 24 * 5)\n\te1End := e1Start.Add(time.Hour * 4)\n\n\tid, _ := AddEvent(\"ToDelete\", \"description\", \"gno.land\", \"gnome land\", e1Start.Format(time.RFC3339), e1End.Format(time.RFC3339))\n\n\tgot := renderHome(false)\n\n\tif !strings.Contains(got, \"ToDelete\") {\n\t\tt.Fatalf(\"Expected to find ToDelete event in render\")\n\t}\n\n\tDeleteEvent(id)\n\tgot = renderHome(false)\n\n\tif strings.Contains(got, \"ToDelete\") {\n\t\tt.Fatalf(\"Did not expect to find ToDelete event in render\")\n\t}\n}\n\nfunc TestEditEvent(t *testing.T) {\n\tstd.TestSetOrigCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\te1Start := parsedTimeNow.Add(time.Hour * 24 * 5)\n\te1End := e1Start.Add(time.Hour * 4)\n\tloc := \"gnome land\"\n\n\tid, _ := AddEvent(\"ToDelete\", \"description\", \"gno.land\", loc, e1Start.Format(time.RFC3339), e1End.Format(time.RFC3339))\n\n\tnewName := \"New Name\"\n\tnewDesc := \"Normal description\"\n\tnewLink := \"new Link\"\n\tnewST := e1Start.Add(time.Hour)\n\tnewET := newST.Add(time.Hour)\n\n\tEditEvent(id, newName, newDesc, newLink, \"\", newST.Format(time.RFC3339), newET.Format(time.RFC3339))\n\tedited, _, _ := GetEventByID(id)\n\n\t// Check updated values\n\tuassert.Equal(t, edited.name, newName)\n\tuassert.Equal(t, edited.description, newDesc)\n\tuassert.Equal(t, edited.link, newLink)\n\tuassert.True(t, edited.startTime.Equal(newST))\n\tuassert.True(t, edited.endTime.Equal(newET))\n\n\t// Check if the old values are the same\n\tuassert.Equal(t, edited.location, loc)\n}\n\nfunc TestInvalidEdit(t *testing.T) {\n\tstd.TestSetOrigCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\tuassert.PanicsWithMessage(t, ErrNoSuchID.Error(), func() {\n\t\tEditEvent(\"123123\", \"\", \"\", \"\", \"\", \"\", \"\")\n\t})\n}\n\nfunc TestParseTimes(t *testing.T) {\n\t// times not provided\n\t// end time before start time\n\t// timezone Missmatch\n\n\t_, _, err := parseTimes(\"\", \"\")\n\tuassert.ErrorContains(t, err, ErrInvalidStartTime.Error())\n\n\t_, _, err = parseTimes(now, \"\")\n\tuassert.ErrorContains(t, err, ErrInvalidEndTime.Error())\n\n\t_, _, err = parseTimes(\"2009-02-13T23:30:30Z\", \"2009-02-13T21:30:30Z\")\n\tuassert.ErrorContains(t, err, ErrEndBeforeStart.Error())\n\n\t_, _, err = parseTimes(\"2009-02-10T23:30:30+02:00\", \"2009-02-13T21:30:33+05:00\")\n\tuassert.ErrorContains(t, err, ErrStartEndTimezonemMismatch.Error())\n}\n\nfunc TestRenderEventWidget(t *testing.T) {\n\tstd.TestSetOrigCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\t// No events yet\n\tevents = nil\n\tout, err := RenderEventWidget(1)\n\tuassert.NoError(t, err)\n\tuassert.Equal(t, out, \"No events.\")\n\n\t// Too many events\n\tout, err = RenderEventWidget(MaxWidgetSize + 1)\n\tuassert.ErrorIs(t, err, ErrMaxWidgetSize)\n\n\t// Too little events\n\tout, err = RenderEventWidget(0)\n\tuassert.ErrorIs(t, err, ErrMinWidgetSize)\n\n\t// Ordering \u0026 if requested amt is larger than the num of events that exist\n\te1Start := parsedTimeNow.Add(time.Hour * 24 * 5)\n\te1End := e1Start.Add(time.Hour * 4)\n\n\te2Start := parsedTimeNow.Add(time.Hour * 24 * 10) // event 2 is after event 1\n\te2End := e2Start.Add(time.Hour * 4)\n\n\t_, err = AddEvent(\"Event 1\", \"description\", \"gno.land\", \"loc\", e1Start.Format(time.RFC3339), e1End.Format(time.RFC3339))\n\turequire.NoError(t, err)\n\n\t_, err = AddEvent(\"Event 2\", \"description\", \"gno.land\", \"loc\", e2Start.Format(time.RFC3339), e2End.Format(time.RFC3339))\n\turequire.NoError(t, err)\n\n\tout, err = RenderEventWidget(MaxWidgetSize)\n\turequire.NoError(t, err)\n\n\tuniqueSequence := \"- [\" // sequence that is displayed once per each event as per the RenderEventWidget function\n\tuassert.Equal(t, 2, strings.Count(out, uniqueSequence))\n\n\tuassert.True(t, strings.Index(out, \"Event 1\") \u003e strings.Index(out, \"Event 2\"))\n}\n"},{"name":"rendering.gno","body":"package events\n\nimport (\n\t\"bytes\"\n\t\"time\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\nconst (\n\tMaxWidgetSize = 5\n)\n\n// RenderEventWidget shows up to eventsToRender of the latest events to a caller\nfunc RenderEventWidget(eventsToRender int) (string, error) {\n\tnumOfEvents := len(events)\n\tif numOfEvents == 0 {\n\t\treturn \"No events.\", nil\n\t}\n\n\tif eventsToRender \u003e MaxWidgetSize {\n\t\treturn \"\", ErrMaxWidgetSize\n\t}\n\n\tif eventsToRender \u003c 1 {\n\t\treturn \"\", ErrMinWidgetSize\n\t}\n\n\tif eventsToRender \u003e numOfEvents {\n\t\teventsToRender = numOfEvents\n\t}\n\n\toutput := \"\"\n\n\tfor _, event := range events[:eventsToRender] {\n\t\toutput += ufmt.Sprintf(\"- [%s](%s)\\n\", event.name, event.link)\n\t}\n\n\treturn output, nil\n}\n\n// renderHome renders the home page of the events realm\nfunc renderHome(admin bool) string {\n\toutput := \"# gno.land events\\n\\n\"\n\n\tif len(events) == 0 {\n\t\toutput += \"No upcoming or past events.\"\n\t\treturn output\n\t}\n\n\toutput += \"Below is a list of all gno.land events, including in progress, upcoming, and past ones.\\n\\n\"\n\toutput += \"---\\n\\n\"\n\n\tvar (\n\t\tinProgress = \"\"\n\t\tupcoming = \"\"\n\t\tpast = \"\"\n\t\tnow = time.Now()\n\t)\n\n\tfor _, e := range events {\n\t\tif now.Before(e.startTime) {\n\t\t\tupcoming += e.Render(admin)\n\t\t} else if now.After(e.endTime) {\n\t\t\tpast += e.Render(admin)\n\t\t} else {\n\t\t\tinProgress += e.Render(admin)\n\t\t}\n\t}\n\n\tif upcoming != \"\" {\n\t\t// Add upcoming events\n\t\toutput += \"## Upcoming events\\n\\n\"\n\t\toutput += \"\u003cdiv class='columns-3'\u003e\"\n\n\t\toutput += upcoming\n\n\t\toutput += \"\u003c/div\u003e\\n\\n\"\n\t\toutput += \"---\\n\\n\"\n\t}\n\n\tif inProgress != \"\" {\n\t\toutput += \"## Currently in progress\\n\\n\"\n\t\toutput += \"\u003cdiv class='columns-3'\u003e\"\n\n\t\toutput += inProgress\n\n\t\toutput += \"\u003c/div\u003e\\n\\n\"\n\t\toutput += \"---\\n\\n\"\n\t}\n\n\tif past != \"\" {\n\t\t// Add past events\n\t\toutput += \"## Past events\\n\\n\"\n\t\toutput += \"\u003cdiv class='columns-3'\u003e\"\n\n\t\toutput += past\n\n\t\toutput += \"\u003c/div\u003e\\n\\n\"\n\t}\n\n\treturn output\n}\n\n// Render returns the markdown representation of a single event instance\nfunc (e Event) Render(admin bool) string {\n\tvar buf bytes.Buffer\n\n\tbuf.WriteString(\"\u003cdiv\u003e\\n\\n\")\n\tbuf.WriteString(ufmt.Sprintf(\"### %s\\n\\n\", e.name))\n\tbuf.WriteString(ufmt.Sprintf(\"%s\\n\\n\", e.description))\n\tbuf.WriteString(ufmt.Sprintf(\"**Location:** %s\\n\\n\", e.location))\n\n\t_, offset := e.startTime.Zone() // offset is in seconds\n\thoursOffset := offset / (60 * 60)\n\tsign := \"\"\n\tif offset \u003e= 0 {\n\t\tsign = \"+\"\n\t}\n\n\tbuf.WriteString(ufmt.Sprintf(\"**Starts:** %s UTC%s%d\\n\\n\", e.startTime.Format(\"02 Jan 2006, 03:04 PM\"), sign, hoursOffset))\n\tbuf.WriteString(ufmt.Sprintf(\"**Ends:** %s UTC%s%d\\n\\n\", e.endTime.Format(\"02 Jan 2006, 03:04 PM\"), sign, hoursOffset))\n\n\tif admin {\n\t\tbuf.WriteString(ufmt.Sprintf(\"[EDIT](/r/gnoland/events$help\u0026func=EditEvent\u0026id=%s)\\n\\n\", e.id))\n\t\tbuf.WriteString(ufmt.Sprintf(\"[DELETE](/r/gnoland/events$help\u0026func=DeleteEvent\u0026id=%s)\\n\\n\", e.id))\n\t}\n\n\tif e.link != \"\" {\n\t\tbuf.WriteString(ufmt.Sprintf(\"[See more](%s)\\n\\n\", e.link))\n\t}\n\n\tbuf.WriteString(\"\u003c/div\u003e\")\n\n\treturn buf.String()\n}\n\n// Render is the main rendering entry point\nfunc Render(path string) string {\n\tif path == \"admin\" {\n\t\treturn renderHome(true)\n\t}\n\n\treturn renderHome(false)\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":"faucet","path":"gno.land/r/gnoland/faucet","files":[{"name":"admin.gno","body":"package faucet\n\nimport (\n\t\"errors\"\n\t\"std\"\n)\n\nfunc AdminSetInPause(inPause bool) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\tgInPause = inPause\n\treturn \"\"\n}\n\nfunc AdminSetMessage(message string) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\tgMessage = message\n\treturn \"\"\n}\n\nfunc AdminSetTransferLimit(amount int64) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\tgLimit = std.NewCoin(\"ugnot\", amount)\n\treturn \"\"\n}\n\nfunc AdminSetAdminAddr(addr std.Address) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\tgAdminAddr = addr\n\treturn \"\"\n}\n\nfunc AdminAddController(addr std.Address) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\n\tsize := gControllers.Size()\n\n\tif size \u003e= gControllersMaxSize {\n\t\treturn \"can not add more controllers than allowed\"\n\t}\n\n\tif gControllers.Has(addr.String()) {\n\t\treturn addr.String() + \" exists, no need to add.\"\n\t}\n\n\tgControllers.Set(addr.String(), addr)\n\n\treturn \"\"\n}\n\nfunc AdminRemoveController(addr std.Address) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\n\tif !gControllers.Has(addr.String()) {\n\t\treturn addr.String() + \" is not on the controller list\"\n\t}\n\n\t_, ok := gControllers.Remove(addr.String())\n\n\t// it not should happen.\n\t// we will check anyway to prevent issues in the underline implementation.\n\n\tif !ok {\n\t\treturn addr.String() + \" is not on the controller list\"\n\t}\n\n\treturn \"\"\n}\n\nfunc assertIsAdmin() error {\n\tcaller := std.OrigCaller()\n\tif caller != gAdminAddr {\n\t\treturn errors.New(\"restricted for admin\")\n\t}\n\treturn nil\n}\n"},{"name":"faucet.gno","body":"package faucet\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar (\n\t// configurable by admin.\n\tgAdminAddr std.Address = std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")\n\tgControllers = avl.NewTree()\n\tgControllersMaxSize = 10 // limit it to 10\n\tgInPause = false\n\tgMessage = \"# Community Faucet.\\n\\n\"\n\n\t// internal vars, for stats.\n\tgTotalTransferred std.Coins\n\tgTotalTransfers = uint(0)\n\n\t// per request limit, 350 gnot\n\tgLimit std.Coin = std.NewCoin(\"ugnot\", 350000000)\n)\n\nfunc Transfer(to std.Address, send int64) string {\n\tif err := assertIsController(); err != nil {\n\t\treturn err.Error()\n\t}\n\n\tif gInPause {\n\t\treturn errors.New(\"faucet in pause\").Error()\n\t}\n\n\t// limit the per request\n\tif send \u003e gLimit.Amount {\n\t\treturn errors.New(\"Per request limit \" + gLimit.String() + \" exceed\").Error()\n\t}\n\tsendCoins := std.Coins{std.NewCoin(\"ugnot\", send)}\n\n\tgTotalTransferred = gTotalTransferred.Add(sendCoins)\n\tgTotalTransfers++\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tpkgaddr := std.CurrentRealm().Addr()\n\tbanker.SendCoins(pkgaddr, to, sendCoins)\n\treturn \"\"\n}\n\nfunc GetPerTransferLimit() int64 {\n\treturn gLimit.Amount\n}\n\nfunc Render(_ string) string {\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tbalance := banker.GetCoins(std.CurrentRealm().Addr())\n\n\toutput := gMessage\n\tif gInPause {\n\t\toutput += \"Status: inactive.\\n\"\n\t} else {\n\t\toutput += \"Status: active.\\n\"\n\t}\n\toutput += ufmt.Sprintf(\"Balance: %s.\\n\", balance.String())\n\toutput += ufmt.Sprintf(\"Total transfers: %s (in %d times).\\n\\n\", gTotalTransferred.String(), gTotalTransfers)\n\n\toutput += \"Package address: \" + std.CurrentRealm().Addr().String() + \"\\n\\n\"\n\toutput += ufmt.Sprintf(\"Admin: %s\\n\\n \", gAdminAddr.String())\n\toutput += ufmt.Sprintf(\"Controllers:\\n\\n \")\n\n\tfor i := 0; i \u003c gControllers.Size(); i++ {\n\t\t_, v := gControllers.GetByIndex(i)\n\t\toutput += ufmt.Sprintf(\"%s \", v.(std.Address))\n\t}\n\n\toutput += \"\\n\\n\"\n\toutput += ufmt.Sprintf(\"Per request limit: %s\\n\\n\", gLimit.String())\n\n\treturn output\n}\n\nfunc assertIsController() error {\n\tcaller := std.OrigCaller()\n\n\tok := gControllers.Has(caller.String())\n\tif !ok {\n\t\treturn errors.New(caller.String() + \" is not on the controller list\")\n\t}\n\treturn nil\n}\n"},{"name":"faucet_test.gno","body":"package faucet\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/gnoland/faucet\"\n)\n\nfunc TestPackage(t *testing.T) {\n\tvar (\n\t\tadminaddr = std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")\n\t\tfaucetaddr = std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\t\tcontrolleraddr1 = testutils.TestAddress(\"controller1\")\n\t\tcontrolleraddr2 = testutils.TestAddress(\"controller2\")\n\t\tcontrolleraddr3 = testutils.TestAddress(\"controller3\")\n\t\tcontrolleraddr4 = testutils.TestAddress(\"controller4\")\n\t\tcontrolleraddr5 = testutils.TestAddress(\"controller5\")\n\t\tcontrolleraddr6 = testutils.TestAddress(\"controller6\")\n\t\tcontrolleraddr7 = testutils.TestAddress(\"controller7\")\n\t\tcontrolleraddr8 = testutils.TestAddress(\"controller8\")\n\t\tcontrolleraddr9 = testutils.TestAddress(\"controller9\")\n\t\tcontrolleraddr10 = testutils.TestAddress(\"controller10\")\n\t\tcontrolleraddr11 = testutils.TestAddress(\"controller11\")\n\n\t\ttest1addr = testutils.TestAddress(\"test1\")\n\t)\n\t// deposit 1000gnot to faucet contract\n\tstd.TestIssueCoins(faucetaddr, std.Coins{{\"ugnot\", 1000000000}})\n\tassertBalance(t, faucetaddr, 1200000000)\n\n\t// by default, balance is empty, and as a user I cannot call Transfer, or Admin commands.\n\n\tassertBalance(t, test1addr, 0)\n\tstd.TestSetOrigCaller(test1addr)\n\tassertErr(t, faucet.Transfer(test1addr, 1000000))\n\n\tassertErr(t, faucet.AdminAddController(controlleraddr1))\n\tstd.TestSetOrigCaller(controlleraddr1)\n\tassertErr(t, faucet.Transfer(test1addr, 1000000))\n\n\t// as an admin, add the controller to contract and deposit more 2000gnot to contract\n\tstd.TestSetOrigCaller(adminaddr)\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr1))\n\tassertBalance(t, faucetaddr, 1200000000)\n\n\t// now, send some tokens as controller.\n\tstd.TestSetOrigCaller(controlleraddr1)\n\tassertNoErr(t, faucet.Transfer(test1addr, 1000000))\n\tassertBalance(t, test1addr, 1000000)\n\tassertNoErr(t, faucet.Transfer(test1addr, 1000000))\n\tassertBalance(t, test1addr, 2000000)\n\tassertBalance(t, faucetaddr, 1198000000)\n\n\t// remove controller\n\t// as an admin, remove controller\n\tstd.TestSetOrigCaller(adminaddr)\n\tassertNoErr(t, faucet.AdminRemoveController(controlleraddr1))\n\tstd.TestSetOrigCaller(controlleraddr1)\n\tassertErr(t, faucet.Transfer(test1addr, 1000000))\n\n\t// duplicate controller\n\tstd.TestSetOrigCaller(adminaddr)\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr1))\n\tassertErr(t, faucet.AdminAddController(controlleraddr1))\n\t// add more than more than allowed controllers\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr2))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr3))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr4))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr5))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr6))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr7))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr8))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr9))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr10))\n\tassertErr(t, faucet.AdminAddController(controlleraddr11))\n\n\t// send more than per transfer limit\n\tstd.TestSetOrigCaller(adminaddr)\n\tfaucet.AdminSetTransferLimit(300000000)\n\tstd.TestSetOrigCaller(controlleraddr1)\n\tassertErr(t, faucet.Transfer(test1addr, 301000000))\n\n\t// block transefer from the address not on the controllers list.\n\tstd.TestSetOrigCaller(controlleraddr11)\n\tassertErr(t, faucet.Transfer(test1addr, 1000000))\n}\n\nfunc assertErr(t *testing.T, err string) {\n\tt.Helper()\n\n\tif err == \"\" {\n\t\tt.Logf(\"info: got err: %v\", err)\n\t\tt.Errorf(\"expected an error, got nil.\")\n\t}\n}\n\nfunc assertNoErr(t *testing.T, err string) {\n\tt.Helper()\n\tif err != \"\" {\n\t\tt.Errorf(\"got err: %v.\", err)\n\t}\n}\n\nfunc assertBalance(t *testing.T, addr std.Address, expectedBal int64) {\n\tt.Helper()\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tcoins := banker.GetCoins(addr)\n\tgot := coins.AmountOf(\"ugnot\")\n\n\tif expectedBal != got {\n\t\tt.Errorf(\"invalid balance: expected %d, got %d.\", expectedBal, got)\n\t}\n}\n"},{"name":"z0_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/gnoland/faucet\"\n)\n\n// mints ugnot to current realm\nfunc init() {\n\tfacuetaddr := std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\tstd.TestIssueCoins(facuetaddr, std.Coins{{\"ugnot\", 200000000}})\n}\n\n// assert render with empty path and no controllers\nfunc main() {\n\tprintln(faucet.Render(\"\"))\n}\n\n// Output:\n// # Community Faucet.\n//\n// Status: active.\n// Balance: 200000000ugnot.\n// Total transfers: (in 0 times).\n//\n// Package address: g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7\n//\n// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n//\n// Controllers:\n//\n//\n//\n// Per request limit: 350000000ugnot\n"},{"name":"z1_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/gnoland/faucet\"\n)\n\n// mints ugnot to current realm\nfunc init() {\n\tfacuetaddr := std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\tstd.TestIssueCoins(facuetaddr, std.Coins{{\"ugnot\", 200000000}})\n}\n\n// assert render with a path and no controllers\nfunc main() {\n\tprintln(faucet.Render(\"path\"))\n}\n\n// Output:\n// # Community Faucet.\n//\n// Status: active.\n// Balance: 200000000ugnot.\n// Total transfers: (in 0 times).\n//\n// Package address: g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7\n//\n// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n//\n// Controllers:\n//\n//\n//\n// Per request limit: 350000000ugnot\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/gnoland/faucet\"\n)\n\n// mints ugnot to current realm\nfunc init() {\n\tfacuetaddr := std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\tstd.TestIssueCoins(facuetaddr, std.Coins{{\"ugnot\", 200000000}})\n}\n\n// assert render with empty path and 2 controllers\nfunc main() {\n\tvar (\n\t\tadminaddr = std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")\n\t\tcontrolleraddr1 = testutils.TestAddress(\"controller1\")\n\t\tcontrolleraddr2 = testutils.TestAddress(\"controller2\")\n\t)\n\tstd.TestSetOrigCaller(adminaddr)\n\terr := faucet.AdminAddController(controlleraddr1)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\terr = faucet.AdminAddController(controlleraddr2)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\tprintln(faucet.Render(\"\"))\n}\n\n// Output:\n// # Community Faucet.\n//\n// Status: active.\n// Balance: 200000000ugnot.\n// Total transfers: (in 0 times).\n//\n// Package address: g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7\n//\n// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n//\n// Controllers:\n//\n// g1vdhkuarjdakxcetjx9047h6lta047h6lsdacav g1vdhkuarjdakxcetjxf047h6lta047h6lnrev3v\n//\n// Per request limit: 350000000ugnot\n"},{"name":"z3_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/gnoland/faucet\"\n)\n\n// mints coints to current realm\nfunc init() {\n\tfacuetaddr := std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\tstd.TestIssueCoins(facuetaddr, std.Coins{{\"ugnot\", 200000000}})\n}\n\n// assert render with 2 controllers and 2 transfers\nfunc main() {\n\tvar (\n\t\tadminaddr = std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")\n\t\tcontrolleraddr1 = testutils.TestAddress(\"controller1\")\n\t\tcontrolleraddr2 = testutils.TestAddress(\"controller2\")\n\t\ttestaddr1 = testutils.TestAddress(\"test1\")\n\t\ttestaddr2 = testutils.TestAddress(\"test2\")\n\t)\n\tstd.TestSetOrigCaller(adminaddr)\n\terr := faucet.AdminAddController(controlleraddr1)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\terr = faucet.AdminAddController(controlleraddr2)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\tstd.TestSetOrigCaller(controlleraddr1)\n\terr = faucet.Transfer(testaddr1, 1000000)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\tstd.TestSetOrigCaller(controlleraddr2)\n\terr = faucet.Transfer(testaddr1, 2000000)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\tprintln(faucet.Render(\"\"))\n}\n\n// Output:\n// # Community Faucet.\n//\n// Status: active.\n// Balance: 197000000ugnot.\n// Total transfers: 3000000ugnot (in 2 times).\n//\n// Package address: g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7\n//\n// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n//\n// Controllers:\n//\n// g1vdhkuarjdakxcetjx9047h6lta047h6lsdacav g1vdhkuarjdakxcetjxf047h6lta047h6lnrev3v\n//\n// Per request limit: 350000000ugnot\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":"ghverify","path":"gno.land/r/gnoland/ghverify","files":[{"name":"README.md","body":"# ghverify\n\nThis realm is intended to enable off chain gno address to github handle verification.\nThe steps are as follows:\n- A user calls `RequestVerification` and provides a github handle. This creates a new static oracle feed.\n- An off-chain agent controlled by the owner of this realm requests current feeds using the `GnorkleEntrypoint` function and provides a message of `\"request\"`\n- The agent receives the task information that includes the github handle and the gno address. It performs the verification step by checking whether this github user has the address in a github repository it controls.\n- The agent publishes the result of the verification by calling `GnorkleEntrypoint` with a message structured like: `\"ingest,\u003ctask id\u003e,\u003cverification status\u003e\"`. The verification status is `OK` if verification succeeded and any other value if it failed.\n- The oracle feed's ingester processes the verification and the handle to address mapping is written to the avl trees that exist as ghverify realm variables."},{"name":"contract.gno","body":"package ghverify\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/gnorkle/feeds/static\"\n\t\"gno.land/p/demo/gnorkle/gnorkle\"\n\t\"gno.land/p/demo/gnorkle/message\"\n)\n\nconst (\n\t// The agent should send this value if it has verified the github handle.\n\tverifiedResult = \"OK\"\n)\n\nvar (\n\townerAddress = std.OrigCaller()\n\toracle *gnorkle.Instance\n\tpostHandler postGnorkleMessageHandler\n\n\thandleToAddressMap = avl.NewTree()\n\taddressToHandleMap = avl.NewTree()\n)\n\nfunc init() {\n\toracle = gnorkle.NewInstance()\n\toracle.AddToWhitelist(\"\", []string{string(ownerAddress)})\n}\n\ntype postGnorkleMessageHandler struct{}\n\n// Handle does post processing after a message is ingested by the oracle feed. It extracts the value to realm\n// storage and removes the feed from the oracle.\nfunc (h postGnorkleMessageHandler) Handle(i *gnorkle.Instance, funcType message.FuncType, feed gnorkle.Feed) error {\n\tif funcType != message.FuncTypeIngest {\n\t\treturn nil\n\t}\n\n\tresult, _, consumable := feed.Value()\n\tif !consumable {\n\t\treturn nil\n\t}\n\n\t// The value is consumable, meaning the ingestion occurred, so we can remove the feed from the oracle\n\t// after saving it to realm storage.\n\tdefer oracle.RemoveFeed(feed.ID())\n\n\t// Couldn't verify; nothing to do.\n\tif result.String != verifiedResult {\n\t\treturn nil\n\t}\n\n\tfeedTasks := feed.Tasks()\n\tif len(feedTasks) != 1 {\n\t\treturn errors.New(\"expected feed to have exactly one task\")\n\t}\n\n\ttask, ok := feedTasks[0].(*verificationTask)\n\tif !ok {\n\t\treturn errors.New(\"expected ghverify task\")\n\t}\n\n\thandleToAddressMap.Set(task.githubHandle, task.gnoAddress)\n\taddressToHandleMap.Set(task.gnoAddress, task.githubHandle)\n\treturn nil\n}\n\n// RequestVerification creates a new static feed with a single task that will\n// instruct an agent to verify the github handle / gno address pair.\nfunc RequestVerification(githubHandle string) {\n\tgnoAddress := string(std.OrigCaller())\n\tif err := oracle.AddFeeds(\n\t\tstatic.NewSingleValueFeed(\n\t\t\tgnoAddress,\n\t\t\t\"string\",\n\t\t\t\u0026verificationTask{\n\t\t\t\tgnoAddress: gnoAddress,\n\t\t\t\tgithubHandle: githubHandle,\n\t\t\t},\n\t\t),\n\t); err != nil {\n\t\tpanic(err)\n\t}\n\tstd.Emit(\n\t\t\"verification_requested\",\n\t\t\"from\", gnoAddress,\n\t\t\"handle\", githubHandle,\n\t)\n}\n\n// GnorkleEntrypoint is the entrypoint to the gnorkle oracle handler.\nfunc GnorkleEntrypoint(message string) string {\n\tresult, err := oracle.HandleMessage(message, postHandler)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn result\n}\n\n// SetOwner transfers ownership of the contract to the given address.\nfunc SetOwner(owner std.Address) {\n\tif ownerAddress != std.OrigCaller() {\n\t\tpanic(\"only the owner can set a new owner\")\n\t}\n\n\townerAddress = owner\n\n\t// In the context of this contract, the owner is the only one that can\n\t// add new feeds to the oracle.\n\toracle.ClearWhitelist(\"\")\n\toracle.AddToWhitelist(\"\", []string{string(ownerAddress)})\n}\n\n// GetHandleByAddress returns the github handle associated with the given gno address.\nfunc GetHandleByAddress(address string) string {\n\tif value, ok := addressToHandleMap.Get(address); ok {\n\t\treturn value.(string)\n\t}\n\n\treturn \"\"\n}\n\n// GetAddressByHandle returns the gno address associated with the given github handle.\nfunc GetAddressByHandle(handle string) string {\n\tif value, ok := handleToAddressMap.Get(handle); ok {\n\t\treturn value.(string)\n\t}\n\n\treturn \"\"\n}\n\n// Render returns a json object string will all verified handle -\u003e address mappings.\nfunc Render(_ string) string {\n\tresult := \"{\"\n\tvar appendComma bool\n\thandleToAddressMap.Iterate(\"\", \"\", func(handle string, address interface{}) bool {\n\t\tif appendComma {\n\t\t\tresult += \",\"\n\t\t}\n\n\t\tresult += `\"` + handle + `\": \"` + address.(string) + `\"`\n\t\tappendComma = true\n\n\t\treturn false\n\t})\n\n\treturn result + \"}\"\n}\n"},{"name":"contract_test.gno","body":"package ghverify\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n)\n\nfunc TestVerificationLifecycle(t *testing.T) {\n\tdefaultAddress := std.OrigCaller()\n\tuser1Address := std.Address(testutils.TestAddress(\"user 1\"))\n\tuser2Address := std.Address(testutils.TestAddress(\"user 2\"))\n\n\t// Verify request returns no feeds.\n\tresult := GnorkleEntrypoint(\"request\")\n\tif result != \"[]\" {\n\t\tt.Fatalf(\"expected empty request result, got %s\", result)\n\t}\n\n\t// Make a verification request with the created user.\n\tstd.TestSetOrigCaller(user1Address)\n\tRequestVerification(\"deelawn\")\n\n\t// A subsequent request from the same address should panic because there is\n\t// already a feed with an ID of this user's address.\n\tvar errMsg string\n\tfunc() {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\terrMsg = r.(error).Error()\n\t\t\t}\n\t\t}()\n\t\tRequestVerification(\"deelawn\")\n\t}()\n\tif errMsg != \"feed already exists\" {\n\t\tt.Fatalf(\"expected feed already exists, got %s\", errMsg)\n\t}\n\n\t// Verify the request returns no feeds for this non-whitelisted user.\n\tresult = GnorkleEntrypoint(\"request\")\n\tif result != \"[]\" {\n\t\tt.Fatalf(\"expected empty request result, got %s\", result)\n\t}\n\n\t// Make a verification request with the created user.\n\tstd.TestSetOrigCaller(user2Address)\n\tRequestVerification(\"omarsy\")\n\n\t// Set the caller back to the whitelisted user and verify that the feed data\n\t// returned matches what should have been created by the `RequestVerification`\n\t// invocation.\n\tstd.TestSetOrigCaller(defaultAddress)\n\tresult = GnorkleEntrypoint(\"request\")\n\texpResult := `[{\"id\":\"` + string(user1Address) + `\",\"type\":\"0\",\"value_type\":\"string\",\"tasks\":[{\"gno_address\":\"` +\n\t\tstring(user1Address) + `\",\"github_handle\":\"deelawn\"}]},` +\n\t\t`{\"id\":\"` + string(user2Address) + `\",\"type\":\"0\",\"value_type\":\"string\",\"tasks\":[{\"gno_address\":\"` +\n\t\tstring(user2Address) + `\",\"github_handle\":\"omarsy\"}]}]`\n\tif result != expResult {\n\t\tt.Fatalf(\"expected request result %s, got %s\", expResult, result)\n\t}\n\n\t// Try to trigger feed ingestion from the non-authorized user.\n\tstd.TestSetOrigCaller(user1Address)\n\tfunc() {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\terrMsg = r.(error).Error()\n\t\t\t}\n\t\t}()\n\t\tGnorkleEntrypoint(\"ingest,\" + string(user1Address) + \",OK\")\n\t}()\n\tif errMsg != \"caller not whitelisted\" {\n\t\tt.Fatalf(\"expected caller not whitelisted, got %s\", errMsg)\n\t}\n\n\t// Set the caller back to the whitelisted user and transfer contract ownership.\n\tstd.TestSetOrigCaller(defaultAddress)\n\tSetOwner(defaultAddress)\n\n\t// Now trigger the feed ingestion from the user and new owner and only whitelisted address.\n\tGnorkleEntrypoint(\"ingest,\" + string(user1Address) + \",OK\")\n\tGnorkleEntrypoint(\"ingest,\" + string(user2Address) + \",OK\")\n\n\t// Verify the ingestion autocommitted the value and triggered the post handler.\n\tdata := Render(\"\")\n\texpResult = `{\"deelawn\": \"` + string(user1Address) + `\",\"omarsy\": \"` + string(user2Address) + `\"}`\n\tif data != expResult {\n\t\tt.Fatalf(\"expected render data %s, got %s\", expResult, data)\n\t}\n\n\t// Finally make sure the feed was cleaned up after the data was committed.\n\tresult = GnorkleEntrypoint(\"request\")\n\tif result != \"[]\" {\n\t\tt.Fatalf(\"expected empty request result, got %s\", result)\n\t}\n\n\t// Check that the accessor functions are working as expected.\n\tif handle := GetHandleByAddress(string(user1Address)); handle != \"deelawn\" {\n\t\tt.Fatalf(\"expected deelawn, got %s\", handle)\n\t}\n\tif address := GetAddressByHandle(\"deelawn\"); address != string(user1Address) {\n\t\tt.Fatalf(\"expected %s, got %s\", string(user1Address), address)\n\t}\n}\n"},{"name":"task.gno","body":"package ghverify\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n)\n\ntype verificationTask struct {\n\tgnoAddress string\n\tgithubHandle string\n}\n\n// MarshalJSON marshals the task contents to JSON.\nfunc (t *verificationTask) MarshalJSON() ([]byte, error) {\n\tbuf := new(bytes.Buffer)\n\tw := bufio.NewWriter(buf)\n\n\tw.Write(\n\t\t[]byte(`{\"gno_address\":\"` + t.gnoAddress + `\",\"github_handle\":\"` + t.githubHandle + `\"}`),\n\t)\n\n\tw.Flush()\n\treturn buf.Bytes(), nil\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":"home","path":"gno.land/r/gnoland/home","files":[{"name":"home.gno","body":"package home\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/p/demo/ui\"\n\tblog \"gno.land/r/gnoland/blog\"\n\tevents \"gno.land/r/gnoland/events\"\n)\n\n// XXX: p/demo/ui API is crappy, we need to make it more idiomatic\n// XXX: use an updatable block system to update content from a DAO\n// XXX: var blocks avl.Tree\n\nvar (\n\toverride string\n\tadmin = ownable.NewWithAddress(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\") // @manfred by default\n)\n\nfunc Render(_ string) string {\n\tif override != \"\" {\n\t\treturn override\n\t}\n\n\tdom := ui.DOM{Prefix: \"r/gnoland/home:\"}\n\tdom.Title = \"Welcome to gno.land\"\n\tdom.Classes = []string{\"gno-tmpl-section\"}\n\n\t// body\n\tdom.Body.Append(introSection()...)\n\n\tdom.Body.Append(ui.Jumbotron(discoverLinks()))\n\n\tdom.Body.Append(\n\t\tui.Columns{3, []ui.Element{\n\t\t\tlastBlogposts(4),\n\t\t\tupcomingEvents(),\n\t\t\tlastContributions(4),\n\t\t}},\n\t)\n\n\tdom.Body.Append(ui.HR{})\n\tdom.Body.Append(playgroundSection()...)\n\tdom.Body.Append(ui.HR{})\n\tdom.Body.Append(packageStaffPicks()...)\n\tdom.Body.Append(ui.HR{})\n\tdom.Body.Append(worxDAO()...)\n\tdom.Body.Append(ui.HR{})\n\t// footer\n\tdom.Footer.Append(\n\t\tui.Columns{2, []ui.Element{\n\t\t\tsocialLinks(),\n\t\t\tquoteOfTheBlock(),\n\t\t}},\n\t)\n\n\t// Testnet disclaimer\n\tdom.Footer.Append(\n\t\tui.HR{},\n\t\tui.Bold(\"This is a testnet.\"),\n\t\tui.Text(\"Package names are not guaranteed to be available for production.\"),\n\t)\n\n\treturn dom.String()\n}\n\nfunc lastBlogposts(limit int) ui.Element {\n\tposts := blog.RenderLastPostsWidget(limit)\n\treturn ui.Element{\n\t\tui.H3(\"[Latest Blogposts](/r/gnoland/blog)\"),\n\t\tui.Text(posts),\n\t}\n}\n\nfunc lastContributions(limit int) ui.Element {\n\treturn ui.Element{\n\t\tui.H3(\"Latest Contributions\"),\n\t\t// TODO: import r/gh to\n\t\tui.Link{Text: \"View latest contributions\", URL: \"https://github.com/gnolang/gno/pulls\"},\n\t}\n}\n\nfunc upcomingEvents() ui.Element {\n\tout, _ := events.RenderEventWidget(events.MaxWidgetSize)\n\treturn ui.Element{\n\t\tui.H3(\"[Latest Events](/r/gnoland/events)\"),\n\t\tui.Text(out),\n\t}\n}\n\nfunc introSection() ui.Element {\n\treturn ui.Element{\n\t\tui.H3(\"We’re building gno.land, set to become the leading open-source smart contract platform, using Gno, an interpreted and fully deterministic variation of the Go programming language for succinct and composable smart contracts.\"),\n\t\tui.Paragraph(\"With transparent and timeless code, gno.land is the next generation of smart contract platforms, serving as the “GitHub” of the ecosystem, with realms built using fully transparent, auditable code that anyone can inspect and reuse.\"),\n\t\tui.Paragraph(\"Intuitive and easy to use, gno.land lowers the barrier to web3 and makes censorship-resistant platforms accessible to everyone. If you want to help lay the foundations of a fairer and freer world, join us today.\"),\n\t}\n}\n\nfunc worxDAO() ui.Element {\n\t// WorxDAO\n\t// XXX(manfred): please, let me finish a v0, then we can iterate\n\t// highest level == highest responsibility\n\t// teams are responsible for components they don't owne\n\t// flag : realm maintainers VS facilitators\n\t// teams\n\t// committee of trustees to create the directory\n\t// each directory is a name, has a parent and have groups\n\t// homepage team - blocks aggregating events\n\t// XXX: TODO\n\t/*`\n\t# Directory\n\n\t* gno.land (owned by group)\n\t *\n\t* gnovm\n\t * gnolang (language)\n\t * gnovm\n\t - current challenges / concerns / issues\n\t* tm2\n\t * amino\n\t *\n\n\t## Contributors\n\t``*/\n\treturn ui.Element{\n\t\tui.H3(\"Contributions (WorxDAO \u0026 GoR)\"),\n\t\t// TODO: GoR dashboard + WorxDAO topics\n\t\tui.Text(`coming soon`),\n\t}\n}\n\nfunc quoteOfTheBlock() ui.Element {\n\tquotes := []string{\n\t\t\"Gno is for Truth.\",\n\t\t\"Gno is for Social Coordination.\",\n\t\t\"Gno is _not only_ for DeFi.\",\n\t\t\"Now, you Gno.\",\n\t\t\"Come for the Go, Stay for the Gno.\",\n\t}\n\theight := std.GetHeight()\n\tidx := int(height) % len(quotes)\n\tqotb := quotes[idx]\n\n\treturn ui.Element{\n\t\tui.H3(ufmt.Sprintf(\"Quote of the ~Day~ Block#%d\", height)),\n\t\tui.Quote(qotb),\n\t}\n}\n\nfunc socialLinks() ui.Element {\n\treturn ui.Element{\n\t\tui.H3(\"Socials\"),\n\t\tui.BulletList{\n\t\t\t// XXX: improve UI to support a nice GO api for such links\n\t\t\tui.Text(\"Check out our [community projects](https://github.com/gnolang/awesome-gno)\"),\n\t\t\tui.Text(\"![Discord](static/img/ico-discord.svg) [Discord](https://discord.gg/S8nKUqwkPn)\"),\n\t\t\tui.Text(\"![Twitter](static/img/ico-twitter.svg) [Twitter](https://twitter.com/_gnoland)\"),\n\t\t\tui.Text(\"![Youtube](static/img/ico-youtube.svg) [Youtube](https://www.youtube.com/@_gnoland)\"),\n\t\t\tui.Text(\"![Telegram](static/img/ico-telegram.svg) [Telegram](https://t.me/gnoland)\"),\n\t\t},\n\t}\n}\n\nfunc playgroundSection() ui.Element {\n\treturn ui.Element{\n\t\tui.H3(\"[Gno Playground](https://play.gno.land)\"),\n\t\tui.Paragraph(`Gno Playground is a web application designed for building, running, testing, and interacting\nwith your Gno code, enhancing your understanding of the Gno language. With Gno Playground, you can share your code,\nexecute tests, deploy your realms and packages to gno.land, and explore a multitude of other features.`),\n\t\tui.Paragraph(\"Experience the convenience of code sharing and rapid experimentation with [Gno Playground](https://play.gno.land).\"),\n\t}\n}\n\nfunc packageStaffPicks() ui.Element {\n\t// XXX: make it modifiable from a DAO\n\treturn ui.Element{\n\t\tui.H3(\"Explore New Packages and Realms\"),\n\t\tui.Columns{\n\t\t\t3,\n\t\t\t[]ui.Element{\n\t\t\t\t{\n\t\t\t\t\tui.H4(\"[r/gnoland](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/gnoland)\"),\n\t\t\t\t\tui.BulletList{\n\t\t\t\t\t\tui.Link{URL: \"r/gnoland/blog\"},\n\t\t\t\t\t\tui.Link{URL: \"r/gnoland/dao\"},\n\t\t\t\t\t\tui.Link{URL: \"r/gnoland/faucet\"},\n\t\t\t\t\t\tui.Link{URL: \"r/gnoland/home\"},\n\t\t\t\t\t\tui.Link{URL: \"r/gnoland/pages\"},\n\t\t\t\t\t},\n\t\t\t\t\tui.H4(\"[r/sys](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/sys)\"),\n\t\t\t\t\tui.BulletList{\n\t\t\t\t\t\tui.Link{URL: \"r/sys/names\"},\n\t\t\t\t\t\tui.Link{URL: \"r/sys/rewards\"},\n\t\t\t\t\t\tui.Link{URL: \"/r/sys/validators/v2\"},\n\t\t\t\t\t},\n\t\t\t\t}, {\n\t\t\t\t\tui.H4(\"[r/demo](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo)\"),\n\t\t\t\t\tui.BulletList{\n\t\t\t\t\t\tui.Link{URL: \"r/demo/boards\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/users\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/banktest\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/foo20\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/foo721\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/microblog\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/nft\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/types\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/art/gnoface\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/art/millipede\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/groups\"},\n\t\t\t\t\t\tui.Text(\"...\"),\n\t\t\t\t\t},\n\t\t\t\t}, {\n\t\t\t\t\tui.H4(\"[p/demo](https://github.com/gnolang/gno/tree/master/examples/gno.land/p/demo)\"),\n\t\t\t\t\tui.BulletList{\n\t\t\t\t\t\tui.Link{URL: \"p/demo/avl\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/blog\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/ui\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/ufmt\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/merkle\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/bf\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/flow\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/gnode\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/grc/grc20\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/grc/grc721\"},\n\t\t\t\t\t\tui.Text(\"...\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc discoverLinks() ui.Element {\n\treturn ui.Element{\n\t\tui.Text(`\u003cdiv class=\"columns-3\"\u003e\n\u003cdiv class=\"column\"\u003e\n\n### Learn about gno.land\n\n- [About](/about)\n- [GitHub](https://github.com/gnolang)\n- [Blog](/blog)\n- [Events](/events)\n- Tokenomics (soon)\n- [Partners, Fund, Grants](/partners)\n- [Explore the Ecosystem](/ecosystem)\n- [Careers](https://jobs.ashbyhq.com/allinbits)\n\n\u003c/div\u003e\u003c!-- end column--\u003e\n\n\u003cdiv class=\"column\"\u003e\n\n### Build with Gno\n\n- [Write Gno in the browser](https://play.gno.land)\n- [Read about the Gno Language](/gnolang)\n- [Visit the official documentation](https://docs.gno.land)\n- [Gno by Example](https://gno-by-example.com/)\n- [Efficient local development for Gno](https://docs.gno.land/gno-tooling/cli/gno-tooling-gnodev)\n- [Get testnet GNOTs](https://faucet.gno.land)\n\n\u003c/div\u003e\u003c!-- end column--\u003e\n\u003cdiv class=\"column\"\u003e\n\n### Explore the universe\n\n- [Discover demo packages](https://github.com/gnolang/gno/tree/master/examples)\n- [Gnoscan](https://gnoscan.io)\n- [Portal Loop](https://docs.gno.land/concepts/portal-loop)\n- [Testnet 4](https://test4.gno.land/)\n- Testnet Faucet Hub (soon)\n\n\u003c/div\u003e\u003c!-- end column--\u003e\n\u003c/div\u003e\u003c!-- end columns-3--\u003e`),\n\t}\n}\n\nfunc AdminSetOverride(content string) {\n\tadmin.AssertCallerIsOwner()\n\toverride = content\n}\n\nfunc AdminTransferOwnership(newAdmin std.Address) {\n\tadmin.AssertCallerIsOwner()\n\tadmin.TransferOwnership(newAdmin)\n}\n"},{"name":"home_filetest.gno","body":"package main\n\nimport \"gno.land/r/gnoland/home\"\n\nfunc main() {\n\tprintln(home.Render(\"\"))\n}\n\n// Output:\n// \u003cmain class='gno-tmpl-section'\u003e\n//\n// # Welcome to gno.land\n//\n// ### We’re building gno.land, set to become the leading open-source smart contract platform, using Gno, an interpreted and fully deterministic variation of the Go programming language for succinct and composable smart contracts.\n//\n//\n// With transparent and timeless code, gno.land is the next generation of smart contract platforms, serving as the “GitHub” of the ecosystem, with realms built using fully transparent, auditable code that anyone can inspect and reuse.\n//\n//\n// Intuitive and easy to use, gno.land lowers the barrier to web3 and makes censorship-resistant platforms accessible to everyone. If you want to help lay the foundations of a fairer and freer world, join us today.\n//\n// \u003cdiv class=\"jumbotron\"\u003e\n//\n// \u003cdiv class=\"columns-3\"\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Learn about gno.land\n//\n// - [About](/about)\n// - [GitHub](https://github.com/gnolang)\n// - [Blog](/blog)\n// - [Events](/events)\n// - Tokenomics (soon)\n// - [Partners, Fund, Grants](/partners)\n// - [Explore the Ecosystem](/ecosystem)\n// - [Careers](https://jobs.ashbyhq.com/allinbits)\n//\n// \u003c/div\u003e\u003c!-- end column--\u003e\n//\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Build with Gno\n//\n// - [Write Gno in the browser](https://play.gno.land)\n// - [Read about the Gno Language](/gnolang)\n// - [Visit the official documentation](https://docs.gno.land)\n// - [Gno by Example](https://gno-by-example.com/)\n// - [Efficient local development for Gno](https://docs.gno.land/gno-tooling/cli/gno-tooling-gnodev)\n// - [Get testnet GNOTs](https://faucet.gno.land)\n//\n// \u003c/div\u003e\u003c!-- end column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Explore the universe\n//\n// - [Discover demo packages](https://github.com/gnolang/gno/tree/master/examples)\n// - [Gnoscan](https://gnoscan.io)\n// - [Portal Loop](https://docs.gno.land/concepts/portal-loop)\n// - [Testnet 4](https://test4.gno.land/)\n// - Testnet Faucet Hub (soon)\n//\n// \u003c/div\u003e\u003c!-- end column--\u003e\n// \u003c/div\u003e\u003c!-- end columns-3--\u003e\n// \u003c/div\u003e\u003c!-- /jumbotron --\u003e\n//\n// \u003cdiv class=\"columns-3\"\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### [Latest Blogposts](/r/gnoland/blog)\n//\n// No posts.\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### [Latest Events](/r/gnoland/events)\n//\n// No events.\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Latest Contributions\n//\n// [View latest contributions](https://github.com/gnolang/gno/pulls)\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003c/div\u003e\u003c!-- /columns-3 --\u003e\n//\n//\n// ---\n//\n// ### [Gno Playground](https://play.gno.land)\n//\n//\n// Gno Playground is a web application designed for building, running, testing, and interacting\n// with your Gno code, enhancing your understanding of the Gno language. With Gno Playground, you can share your code,\n// execute tests, deploy your realms and packages to gno.land, and explore a multitude of other features.\n//\n//\n// Experience the convenience of code sharing and rapid experimentation with [Gno Playground](https://play.gno.land).\n//\n//\n// ---\n//\n// ### Explore New Packages and Realms\n//\n// \u003cdiv class=\"columns-3\"\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// #### [r/gnoland](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/gnoland)\n//\n// - [r/gnoland/blog](r/gnoland/blog)\n// - [r/gnoland/dao](r/gnoland/dao)\n// - [r/gnoland/faucet](r/gnoland/faucet)\n// - [r/gnoland/home](r/gnoland/home)\n// - [r/gnoland/pages](r/gnoland/pages)\n//\n// #### [r/sys](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/sys)\n//\n// - [r/sys/names](r/sys/names)\n// - [r/sys/rewards](r/sys/rewards)\n// - [/r/sys/validators/v2](/r/sys/validators/v2)\n//\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// #### [r/demo](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo)\n//\n// - [r/demo/boards](r/demo/boards)\n// - [r/demo/users](r/demo/users)\n// - [r/demo/banktest](r/demo/banktest)\n// - [r/demo/foo20](r/demo/foo20)\n// - [r/demo/foo721](r/demo/foo721)\n// - [r/demo/microblog](r/demo/microblog)\n// - [r/demo/nft](r/demo/nft)\n// - [r/demo/types](r/demo/types)\n// - [r/demo/art/gnoface](r/demo/art/gnoface)\n// - [r/demo/art/millipede](r/demo/art/millipede)\n// - [r/demo/groups](r/demo/groups)\n// - ...\n//\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// #### [p/demo](https://github.com/gnolang/gno/tree/master/examples/gno.land/p/demo)\n//\n// - [p/demo/avl](p/demo/avl)\n// - [p/demo/blog](p/demo/blog)\n// - [p/demo/ui](p/demo/ui)\n// - [p/demo/ufmt](p/demo/ufmt)\n// - [p/demo/merkle](p/demo/merkle)\n// - [p/demo/bf](p/demo/bf)\n// - [p/demo/flow](p/demo/flow)\n// - [p/demo/gnode](p/demo/gnode)\n// - [p/demo/grc/grc20](p/demo/grc/grc20)\n// - [p/demo/grc/grc721](p/demo/grc/grc721)\n// - ...\n//\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003c/div\u003e\u003c!-- /columns-3 --\u003e\n//\n//\n// ---\n//\n// ### Contributions (WorxDAO \u0026 GoR)\n//\n// coming soon\n//\n// ---\n//\n//\n// \u003cdiv class=\"columns-2\"\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Socials\n//\n// - Check out our [community projects](https://github.com/gnolang/awesome-gno)\n// - ![Discord](static/img/ico-discord.svg) [Discord](https://discord.gg/S8nKUqwkPn)\n// - ![Twitter](static/img/ico-twitter.svg) [Twitter](https://twitter.com/_gnoland)\n// - ![Youtube](static/img/ico-youtube.svg) [Youtube](https://www.youtube.com/@_gnoland)\n// - ![Telegram](static/img/ico-telegram.svg) [Telegram](https://t.me/gnoland)\n//\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Quote of the ~Day~ Block#123\n//\n// \u003e Now, you Gno.\n//\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003c/div\u003e\u003c!-- /columns-2 --\u003e\n//\n//\n// ---\n//\n// **This is a testnet.**\n// Package names are not guaranteed to be available for production.\n//\n// \u003c/main\u003e\n"},{"name":"overide_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/gnoland/home\"\n)\n\nfunc main() {\n\tstd.TestSetOrigCaller(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\thome.AdminSetOverride(\"Hello World!\")\n\tprintln(home.Render(\"\"))\n\thome.AdminTransferOwnership(testutils.TestAddress(\"newAdmin\"))\n\tdefer func() {\n\t\tr := recover()\n\t\tprintln(\"r: \", r)\n\t}()\n\thome.AdminSetOverride(\"Not admin anymore\")\n}\n\n// Output:\n// Hello World!\n// r: ownable: caller is not owner\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":"userbook","path":"gno.land/r/demo/userbook","files":[{"name":"userbook.gno","body":"// This realm demonstrates a small userbook system working with gnoweb\npackage userbook\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/mux\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype Signup struct {\n\taccount string\n\theight int64\n}\n\n// signups - keep a slice of signed up addresses efficient pagination\nvar signups []Signup\n\n// tracker - keep track of who signed up\nvar (\n\ttracker *avl.Tree\n\trouter *mux.Router\n)\n\nconst (\n\tdefaultPageSize = 20\n\tpathArgument = \"number\"\n\tsubPath = \"page/{\" + pathArgument + \"}\"\n\tsignUpEvent = \"SignUp\"\n)\n\nfunc init() {\n\t// Set up tracker tree\n\ttracker = avl.NewTree()\n\n\t// Set up route handling\n\trouter = mux.NewRouter()\n\trouter.HandleFunc(\"\", renderHelper)\n\trouter.HandleFunc(subPath, renderHelper)\n\n\t// Sign up the deployer\n\tSignUp()\n}\n\nfunc SignUp() string {\n\t// Get transaction caller\n\tcaller := std.PrevRealm().Addr().String()\n\theight := std.GetHeight()\n\n\t// Check if the user is already signed up\n\tif _, exists := tracker.Get(caller); exists {\n\t\tpanic(caller + \" is already signed up!\")\n\t}\n\n\t// Sign up the user\n\ttracker.Set(caller, struct{}{})\n\tsignup := Signup{\n\t\tcaller,\n\t\theight,\n\t}\n\n\tsignups = append(signups, signup)\n\tstd.Emit(signUpEvent, \"SignedUpAccount\", signup.account)\n\n\treturn ufmt.Sprintf(\"%s added to userbook up at block #%d!\", signup.account, signup.height)\n}\n\nfunc GetSignupsInRange(page, pageSize int) ([]Signup, int) {\n\tif page \u003c 1 {\n\t\tpanic(\"page number cannot be less than 1\")\n\t}\n\n\tif pageSize \u003c 1 || pageSize \u003e 50 {\n\t\tpanic(\"page size must be from 1 to 50\")\n\t}\n\n\t// Pagination\n\t// Calculate indexes\n\tstartIndex := (page - 1) * pageSize\n\tendIndex := startIndex + pageSize\n\n\t// If page does not contain any users\n\tif startIndex \u003e= len(signups) {\n\t\treturn nil, -1\n\t}\n\n\t// If page contains fewer users than the page size\n\tif endIndex \u003e len(signups) {\n\t\tendIndex = len(signups)\n\t}\n\n\treturn signups[startIndex:endIndex], endIndex\n}\n\nfunc renderHelper(res *mux.ResponseWriter, req *mux.Request) {\n\ttotalSignups := len(signups)\n\tres.Write(\"# Welcome to UserBook!\\n\\n\")\n\n\t// Get URL parameter\n\tpage, err := strconv.Atoi(req.GetVar(\"number\"))\n\tif err != nil {\n\t\tpage = 1 // render first page on bad input\n\t}\n\n\t// Fetch paginated signups\n\tfetchedSignups, endIndex := GetSignupsInRange(page, defaultPageSize)\n\t// Handle empty page case\n\tif len(fetchedSignups) == 0 {\n\t\tres.Write(\"No users on this page!\\n\\n\")\n\t\tres.Write(\"---\\n\\n\")\n\t\tres.Write(\"[Back to Page #1](/r/demo/userbook:page/1)\\n\\n\")\n\t\treturn\n\t}\n\n\t// Write page title\n\tres.Write(ufmt.Sprintf(\"## UserBook - Page #%d:\\n\\n\", page))\n\n\t// Write signups\n\tpageStartIndex := defaultPageSize * (page - 1)\n\tfor i, signup := range fetchedSignups {\n\t\tout := ufmt.Sprintf(\"#### User #%d - %s - signed up at Block #%d\\n\", pageStartIndex+i, signup.account, signup.height)\n\t\tres.Write(out)\n\t}\n\n\tres.Write(\"---\\n\\n\")\n\n\t// Write UserBook info\n\tlatestSignupIndex := totalSignups - 1\n\tres.Write(ufmt.Sprintf(\"#### Total users: %d\\n\", totalSignups))\n\tres.Write(ufmt.Sprintf(\"#### Latest signup: User #%d at Block #%d\\n\", latestSignupIndex, signups[latestSignupIndex].height))\n\n\tres.Write(\"---\\n\\n\")\n\n\t// Write page number\n\tres.Write(ufmt.Sprintf(\"You're viewing page #%d\", page))\n\n\t// Write navigation buttons\n\tvar prevPage string\n\tvar nextPage string\n\t// If we are on any page that is not the first page\n\tif page \u003e 1 {\n\t\tprevPage = ufmt.Sprintf(\" - [Previous page](/r/demo/userbook:page/%d)\", page-1)\n\t}\n\n\t// If there are more pages after the current one\n\tif endIndex \u003c totalSignups {\n\t\tnextPage = ufmt.Sprintf(\" - [Next page](/r/demo/userbook:page/%d)\\n\\n\", page+1)\n\t}\n\n\tres.Write(prevPage)\n\tres.Write(nextPage)\n}\n\nfunc Render(path string) string {\n\treturn router.Render(path)\n}\n"},{"name":"userbook_test.gno","body":"package userbook\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nfunc TestRender(t *testing.T) {\n\t// Sign up 20 users + deployer\n\tfor i := 0; i \u003c 20; i++ {\n\t\taddrName := ufmt.Sprintf(\"test%d\", i)\n\t\tcaller := testutils.TestAddress(addrName)\n\t\tstd.TestSetOriginCaller(caller)\n\t\tSignUp()\n\t}\n\n\ttestCases := []struct {\n\t\tname string\n\t\tnextPage bool\n\t\tprevPage bool\n\t\tpath string\n\t\texpectedNumberOfUsers int\n\t}{\n\t\t{\n\t\t\tname: \"1st page render\",\n\t\t\tnextPage: true,\n\t\t\tprevPage: false,\n\t\t\tpath: \"page/1\",\n\t\t\texpectedNumberOfUsers: 20,\n\t\t},\n\t\t{\n\t\t\tname: \"2nd page render\",\n\t\t\tnextPage: false,\n\t\t\tprevPage: true,\n\t\t\tpath: \"page/2\",\n\t\t\texpectedNumberOfUsers: 1,\n\t\t},\n\t\t{\n\t\t\tname: \"Invalid path render\",\n\t\t\tnextPage: true,\n\t\t\tprevPage: false,\n\t\t\tpath: \"page/invalidtext\",\n\t\t\texpectedNumberOfUsers: 20,\n\t\t},\n\t\t{\n\t\t\tname: \"Empty Page\",\n\t\t\tnextPage: false,\n\t\t\tprevPage: false,\n\t\t\tpath: \"page/1000\",\n\t\t\texpectedNumberOfUsers: 0,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := Render(tc.path)\n\t\t\tnumUsers := countUsers(got)\n\n\t\t\tif tc.prevPage \u0026\u0026 !strings.Contains(got, \"Previous page\") {\n\t\t\t\tt.Fatalf(\"expected to find Previous page, didn't find it\")\n\t\t\t}\n\t\t\tif tc.nextPage \u0026\u0026 !strings.Contains(got, \"Next page\") {\n\t\t\t\tt.Fatalf(\"expected to find Next page, didn't find it\")\n\t\t\t}\n\n\t\t\tif tc.expectedNumberOfUsers != numUsers {\n\t\t\t\tt.Fatalf(\"expected %d, got %d users\", tc.expectedNumberOfUsers, numUsers)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc countUsers(input string) int {\n\treturn strings.Count(input, \"#### User #\")\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":"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/users\"\n)\n\nvar (\n\tbanker *grc20.Banker = grc20.NewBanker(\"wrapped GNOT\", \"wugnot\", 0)\n\tToken = banker.Token()\n)\n\nconst (\n\tugnotMinDeposit uint64 = 1000\n\twugnotMinDeposit uint64 = 1\n)\n\nfunc Deposit() {\n\tcaller := std.PrevRealm().Addr()\n\tsent := std.OriginSend()\n\tamount := sent.AmountOf(\"ugnot\")\n\n\trequire(uint64(amount) \u003e= ugnotMinDeposit, ufmt.Sprintf(\"Deposit below minimum: %d/%d ugnot.\", amount, ugnotMinDeposit))\n\tcheckErr(banker.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(banker.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 banker.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\tcheckErr(Token.Transfer(toAddr, amount))\n}\n\nfunc Approve(spender pusers.AddressOrName, amount uint64) {\n\tspenderAddr := users.Resolve(spender)\n\tcheckErr(Token.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(Token.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.TestSetOriginCaller(addr1)\n\tstd.TestSetOriginSend(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.TestSetOriginCaller(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=200000000 |\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=200000000 |\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=200004242 |\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":""}} +{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"gnoblog","path":"gno.land/r/gnoland/blog","files":[{"name":"admin.gno","body":"package gnoblog\n\nimport (\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/r/gov/dao/bridge\"\n)\n\nvar (\n\tadminAddr std.Address\n\tmoderatorList avl.Tree\n\tcommenterList avl.Tree\n\tinPause bool\n)\n\nfunc init() {\n\t// adminAddr = std.OriginCaller() // FIXME: find a way to use this from the main's genesis.\n\tadminAddr = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"\n}\n\nfunc AdminSetAdminAddr(addr std.Address) {\n\tassertIsAdmin()\n\tadminAddr = addr\n}\n\nfunc AdminSetInPause(state bool) {\n\tassertIsAdmin()\n\tinPause = state\n}\n\nfunc AdminAddModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), true)\n}\n\nfunc AdminRemoveModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), false) // FIXME: delete instead?\n}\n\nfunc NewPostExecutor(slug, title, body, publicationDate, authors, tags string) dao.Executor {\n\tcallback := func() error {\n\t\taddPost(std.PrevRealm().Addr(), slug, title, body, publicationDate, authors, tags)\n\n\t\treturn nil\n\t}\n\n\treturn bridge.GovDAO().NewGovDAOExecutor(callback)\n}\n\nfunc ModAddPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\tcaller := std.OriginCaller()\n\taddPost(caller, slug, title, body, publicationDate, authors, tags)\n}\n\nfunc addPost(caller std.Address, slug, title, body, publicationDate, authors, tags string) {\n\tvar tagList []string\n\tif tags != \"\" {\n\t\ttagList = strings.Split(tags, \",\")\n\t}\n\tvar authorList []string\n\tif authors != \"\" {\n\t\tauthorList = strings.Split(authors, \",\")\n\t}\n\n\terr := b.NewPost(caller, slug, title, body, publicationDate, authorList, tagList)\n\n\tcheckErr(err)\n}\n\nfunc ModEditPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.GetPost(slug).Update(title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc ModRemovePost(slug string) {\n\tassertIsModerator()\n\n\tb.RemovePost(slug)\n}\n\nfunc ModAddCommenter(addr std.Address) {\n\tassertIsModerator()\n\tcommenterList.Set(addr.String(), true)\n}\n\nfunc ModDelCommenter(addr std.Address) {\n\tassertIsModerator()\n\tcommenterList.Set(addr.String(), false) // FIXME: delete instead?\n}\n\nfunc ModDelComment(slug string, index int) {\n\tassertIsModerator()\n\n\terr := b.GetPost(slug).DeleteComment(index)\n\tcheckErr(err)\n}\n\nfunc isAdmin(addr std.Address) bool {\n\treturn addr == adminAddr\n}\n\nfunc isModerator(addr std.Address) bool {\n\t_, found := moderatorList.Get(addr.String())\n\treturn found\n}\n\nfunc isCommenter(addr std.Address) bool {\n\t_, found := commenterList.Get(addr.String())\n\treturn found\n}\n\nfunc assertIsAdmin() {\n\tcaller := std.OriginCaller()\n\tif !isAdmin(caller) {\n\t\tpanic(\"access restricted.\")\n\t}\n}\n\nfunc assertIsModerator() {\n\tcaller := std.OriginCaller()\n\tif isAdmin(caller) || isModerator(caller) {\n\t\treturn\n\t}\n\tpanic(\"access restricted\")\n}\n\nfunc assertIsCommenter() {\n\tcaller := std.OriginCaller()\n\tif isAdmin(caller) || isModerator(caller) || isCommenter(caller) {\n\t\treturn\n\t}\n\tpanic(\"access restricted\")\n}\n\nfunc assertNotInPause() {\n\tif inPause {\n\t\tpanic(\"access restricted (pause)\")\n\t}\n}\n"},{"name":"gnoblog.gno","body":"package gnoblog\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/blog\"\n)\n\nvar b = \u0026blog.Blog{\n\tTitle: \"Gnoland's Blog\",\n\tPrefix: \"/r/gnoland/blog:\",\n}\n\nfunc AddComment(postSlug, comment string) {\n\tassertIsCommenter()\n\tassertNotInPause()\n\n\tcaller := std.OriginCaller()\n\terr := b.GetPost(postSlug).AddComment(caller, comment)\n\tcheckErr(err)\n}\n\nfunc Render(path string) string {\n\treturn b.Render(path)\n}\n\nfunc RenderLastPostsWidget(limit int) string {\n\treturn b.RenderLastPostsWidget(limit)\n}\n\nfunc PostExists(slug string) bool {\n\tif b.GetPost(slug) == nil {\n\t\treturn false\n\t}\n\treturn true\n}\n"},{"name":"gnoblog_test.gno","body":"package gnoblog\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestPackage(t *testing.T) {\n\tstd.TestSetOriginCaller(std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"))\n\n\tauthor := std.OriginCaller()\n\n\t// by default, no posts.\n\t{\n\t\tgot := Render(\"\")\n\t\texpected := `\n# Gnoland's Blog\n\nNo posts.\n`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// create two posts, list post.\n\t{\n\t\tModAddPost(\"slug1\", \"title1\", \"body1\", \"2022-05-20T13:17:22Z\", \"moul\", \"tag1,tag2\")\n\t\tModAddPost(\"slug2\", \"title2\", \"body2\", \"2022-05-20T13:17:23Z\", \"moul\", \"tag1,tag3\")\n\t\tgot := Render(\"\")\n\t\texpected := `\n\t# Gnoland's Blog\n\n\u003cdiv class='columns-3'\u003e\u003cdiv\u003e\n\n### [title2](/r/gnoland/blog:p/slug2)\n 20 May 2022\n\u003c/div\u003e\u003cdiv\u003e\n\n### [title1](/r/gnoland/blog:p/slug1)\n 20 May 2022\n\u003c/div\u003e\u003c/div\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// view post.\n\t{\n\t\tgot := Render(\"p/slug2\")\n\t\texpected := `\n\u003cmain class='gno-tmpl-page'\u003e\n\n# title2\n\nbody2\n\n---\n\nTags: [#tag1](/r/gnoland/blog:t/tag1) [#tag3](/r/gnoland/blog:t/tag3)\n\nWritten by moul on 20 May 2022\n\nPublished by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq to Gnoland's Blog\n\n---\n\u003cdetails\u003e\u003csummary\u003eComment section\u003c/summary\u003e\n\n\u003c/details\u003e\n\u003c/main\u003e\n\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// list by tags.\n\t{\n\t\tgot := Render(\"t/invalid\")\n\t\texpected := \"# [Gnoland's Blog](/r/gnoland/blog:) / t / invalid\\n\\nNo posts.\"\n\t\tassertMDEquals(t, got, expected)\n\n\t\tgot = Render(\"t/tag2\")\n\t\texpected = `\n# [Gnoland's Blog](/r/gnoland/blog:) / t / tag2\n\n\u003cdiv\u003e\n\n### [title1](/r/gnoland/blog:p/slug1)\n 20 May 2022\n\u003c/div\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// add comments.\n\t{\n\t\tAddComment(\"slug1\", \"comment1\")\n\t\tAddComment(\"slug2\", \"comment2\")\n\t\tAddComment(\"slug1\", \"comment3\")\n\t\tAddComment(\"slug2\", \"comment4\")\n\t\tAddComment(\"slug1\", \"comment5\")\n\t\tgot := Render(\"p/slug2\")\n\t\texpected := `\u003cmain class='gno-tmpl-page'\u003e\n\n# title2\n\nbody2\n\n---\n\nTags: [#tag1](/r/gnoland/blog:t/tag1) [#tag3](/r/gnoland/blog:t/tag3)\n\nWritten by moul on 20 May 2022\n\nPublished by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq to Gnoland's Blog\n\n---\n\u003cdetails\u003e\u003csummary\u003eComment section\u003c/summary\u003e\n\n\u003ch5\u003ecomment4\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003ch5\u003ecomment2\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003c/details\u003e\n\u003c/main\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// edit post.\n\t{\n\t\toldTitle := \"title2\"\n\t\toldDate := \"2022-05-20T13:17:23Z\"\n\n\t\tModEditPost(\"slug2\", oldTitle, \"body2++\", oldDate, \"manfred\", \"tag1,tag4\")\n\t\tgot := Render(\"p/slug2\")\n\t\texpected := `\u003cmain class='gno-tmpl-page'\u003e\n\n# title2\n\nbody2++\n\n---\n\nTags: [#tag1](/r/gnoland/blog:t/tag1) [#tag4](/r/gnoland/blog:t/tag4)\n\nWritten by manfred on 20 May 2022\n\nPublished by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq to Gnoland's Blog\n\n---\n\u003cdetails\u003e\u003csummary\u003eComment section\u003c/summary\u003e\n\n\u003ch5\u003ecomment4\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003ch5\u003ecomment2\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003c/details\u003e\n\u003c/main\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\n\t\thome := Render(\"\")\n\n\t\tif strings.Count(home, oldTitle) != 1 {\n\t\t\tt.Errorf(\"post not edited properly\")\n\t\t}\n\t\t// Edits work everything except title, slug, and publicationDate\n\t\t// Edits to the above will cause duplication on the blog home page\n\t}\n\n\t{ // Test remove functionality\n\t\ttitle := \"example title\"\n\t\tslug := \"testSlug1\"\n\t\tModAddPost(slug, title, \"body1\", \"2022-05-25T13:17:22Z\", \"moul\", \"tag1,tag2\")\n\n\t\tgot := Render(\"\")\n\n\t\tif !strings.Contains(got, title) {\n\t\t\tt.Errorf(\"post was not added properly\")\n\t\t}\n\n\t\tpostRender := Render(\"p/\" + slug)\n\n\t\tif !strings.Contains(postRender, title) {\n\t\t\tt.Errorf(\"post not rendered properly\")\n\t\t}\n\n\t\tModRemovePost(slug)\n\t\tgot = Render(\"\")\n\n\t\tif strings.Contains(got, title) {\n\t\t\tt.Errorf(\"post was not removed\")\n\t\t}\n\n\t\tpostRender = Render(\"p/\" + slug)\n\n\t\tassertMDEquals(t, postRender, \"404\")\n\t}\n\n\t// TODO: pagination.\n\t// TODO: ?format=...\n\n\t// all 404s\n\t{\n\t\tnotFoundPaths := []string{\n\t\t\t\"p/slug3\",\n\t\t\t\"p\",\n\t\t\t\"p/\",\n\t\t\t\"x/x\",\n\t\t\t\"t\",\n\t\t\t\"t/\",\n\t\t\t\"/\",\n\t\t\t\"p/slug1/\",\n\t\t}\n\t\tfor _, notFoundPath := range notFoundPaths {\n\t\t\tgot := Render(notFoundPath)\n\t\t\texpected := \"404\"\n\t\t\tif got != expected {\n\t\t\t\tt.Errorf(\"path %q: expected %q, got %q.\", notFoundPath, expected, got)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc assertMDEquals(t *testing.T, got, expected string) {\n\tt.Helper()\n\texpected = strings.TrimSpace(expected)\n\tgot = strings.TrimSpace(got)\n\tif expected != got {\n\t\tt.Errorf(\"invalid render output.\\nexpected %q.\\ngot %q.\", expected, got)\n\t}\n}\n"},{"name":"util.gno","body":"package gnoblog\n\nfunc checkErr(err error) {\n\tif err != nil {\n\t\tpanic(err)\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":"events","path":"gno.land/r/gnoland/events","files":[{"name":"administration.gno","body":"package events\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/ownable/exts/authorizable\"\n)\n\nvar (\n\tsu = std.Address(\"g125em6arxsnj49vx35f0n0z34putv5ty3376fg5\") // @leohhhn\n\tauth = authorizable.NewAuthorizableWithAddress(su)\n)\n\n// GetOwner gets the owner of the events realm\nfunc GetOwner() std.Address {\n\treturn auth.Owner()\n}\n\n// AddModerator adds a moderator to the events realm\nfunc AddModerator(mod std.Address) {\n\tauth.AssertCallerIsOwner()\n\n\tif err := auth.AddToAuthList(mod); err != nil {\n\t\tpanic(err)\n\t}\n}\n"},{"name":"errors.gno","body":"package events\n\nimport (\n\t\"errors\"\n\t\"strconv\"\n)\n\nvar (\n\tErrEmptyName = errors.New(\"event name cannot be empty\")\n\tErrNoSuchID = errors.New(\"event with specified ID does not exist\")\n\tErrMinWidgetSize = errors.New(\"you need to request at least 1 event to render\")\n\tErrMaxWidgetSize = errors.New(\"maximum number of events in widget is\" + strconv.Itoa(MaxWidgetSize))\n\tErrDescriptionTooLong = errors.New(\"event description is too long\")\n\tErrInvalidStartTime = errors.New(\"invalid start time format\")\n\tErrInvalidEndTime = errors.New(\"invalid end time format\")\n\tErrEndBeforeStart = errors.New(\"end time cannot be before start time\")\n\tErrStartEndTimezonemMismatch = errors.New(\"start and end timezones are not the same\")\n)\n"},{"name":"events.gno","body":"// Package events allows you to upload data about specific IRL/online events\n// It includes dynamic support for updating rendering events based on their\n// status, ie if they are upcoming, in progress, or in the past.\npackage events\n\nimport (\n\t\"sort\"\n\t\"std\"\n\t\"strings\"\n\t\"time\"\n\n\t\"gno.land/p/demo/seqid\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype (\n\tEvent struct {\n\t\tid string\n\t\tname string // name of event\n\t\tdescription string // short description of event\n\t\tlink string // link to auth corresponding web2 page, ie eventbrite/luma or conference page\n\t\tlocation string // location of the event\n\t\tstartTime time.Time // given in RFC3339\n\t\tendTime time.Time // end time of the event, given in RFC3339\n\t}\n\n\teventsSlice []*Event\n)\n\nvar (\n\tevents = make(eventsSlice, 0) // sorted\n\tidCounter seqid.ID\n)\n\nconst (\n\tmaxDescLength = 100\n\tEventAdded = \"EventAdded\"\n\tEventDeleted = \"EventDeleted\"\n\tEventEdited = \"EventEdited\"\n)\n\n// AddEvent adds auth new event\n// Start time \u0026 end time need to be specified in RFC3339, ie 2024-08-08T12:00:00+02:00\nfunc AddEvent(name, description, link, location, startTime, endTime string) (string, error) {\n\tauth.AssertOnAuthList()\n\n\tif strings.TrimSpace(name) == \"\" {\n\t\treturn \"\", ErrEmptyName\n\t}\n\n\tif len(description) \u003e maxDescLength {\n\t\treturn \"\", ufmt.Errorf(\"%s: provided length is %d, maximum is %d\", ErrDescriptionTooLong, len(description), maxDescLength)\n\t}\n\n\t// Parse times\n\tst, et, err := parseTimes(startTime, endTime)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tid := idCounter.Next().String()\n\te := \u0026Event{\n\t\tid: id,\n\t\tname: name,\n\t\tdescription: description,\n\t\tlink: link,\n\t\tlocation: location,\n\t\tstartTime: st,\n\t\tendTime: et,\n\t}\n\n\tevents = append(events, e)\n\tsort.Sort(events)\n\n\tstd.Emit(EventAdded,\n\t\t\"id\", e.id,\n\t)\n\n\treturn id, nil\n}\n\n// DeleteEvent deletes an event with auth given ID\nfunc DeleteEvent(id string) {\n\tauth.AssertOnAuthList()\n\n\te, idx, err := GetEventByID(id)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tevents = append(events[:idx], events[idx+1:]...)\n\n\tstd.Emit(EventDeleted,\n\t\t\"id\", e.id,\n\t)\n}\n\n// EditEvent edits an event with auth given ID\n// It only updates values corresponding to non-empty arguments sent with the call\n// Note: if you need to update the start time or end time, you need to provide both every time\nfunc EditEvent(id string, name, description, link, location, startTime, endTime string) {\n\tauth.AssertOnAuthList()\n\n\te, _, err := GetEventByID(id)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Set only valid values\n\tif strings.TrimSpace(name) != \"\" {\n\t\te.name = name\n\t}\n\n\tif strings.TrimSpace(description) != \"\" {\n\t\te.description = description\n\t}\n\n\tif strings.TrimSpace(link) != \"\" {\n\t\te.link = link\n\t}\n\n\tif strings.TrimSpace(location) != \"\" {\n\t\te.location = location\n\t}\n\n\tif strings.TrimSpace(startTime) != \"\" || strings.TrimSpace(endTime) != \"\" {\n\t\tst, et, err := parseTimes(startTime, endTime)\n\t\tif err != nil {\n\t\t\tpanic(err) // need to also revert other state changes\n\t\t}\n\n\t\toldStartTime := e.startTime\n\t\te.startTime = st\n\t\te.endTime = et\n\n\t\t// If sort order was disrupted, sort again\n\t\tif oldStartTime != e.startTime {\n\t\t\tsort.Sort(events)\n\t\t}\n\t}\n\n\tstd.Emit(EventEdited,\n\t\t\"id\", e.id,\n\t)\n}\n\nfunc GetEventByID(id string) (*Event, int, error) {\n\tfor i, event := range events {\n\t\tif event.id == id {\n\t\t\treturn event, i, nil\n\t\t}\n\t}\n\n\treturn nil, -1, ErrNoSuchID\n}\n\n// Len returns the length of the slice\nfunc (m eventsSlice) Len() int {\n\treturn len(m)\n}\n\n// Less compares the startTime fields of two elements\n// In this case, events will be sorted by largest startTime first (upcoming \u003e past)\nfunc (m eventsSlice) Less(i, j int) bool {\n\treturn m[i].startTime.After(m[j].startTime)\n}\n\n// Swap swaps two elements in the slice\nfunc (m eventsSlice) Swap(i, j int) {\n\tm[i], m[j] = m[j], m[i]\n}\n\n// parseTimes parses the start and end time for an event and checks for possible errors\nfunc parseTimes(startTime, endTime string) (time.Time, time.Time, error) {\n\tst, err := time.Parse(time.RFC3339, startTime)\n\tif err != nil {\n\t\treturn time.Time{}, time.Time{}, ufmt.Errorf(\"%s: %s\", ErrInvalidStartTime, err.Error())\n\t}\n\n\tet, err := time.Parse(time.RFC3339, endTime)\n\tif err != nil {\n\t\treturn time.Time{}, time.Time{}, ufmt.Errorf(\"%s: %s\", ErrInvalidEndTime, err.Error())\n\t}\n\n\tif et.Before(st) {\n\t\treturn time.Time{}, time.Time{}, ErrEndBeforeStart\n\t}\n\n\t_, stOffset := st.Zone()\n\t_, etOffset := et.Zone()\n\tif stOffset != etOffset {\n\t\treturn time.Time{}, time.Time{}, ErrStartEndTimezonemMismatch\n\t}\n\n\treturn st, et, nil\n}\n"},{"name":"events_test.gno","body":"package events\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gno.land/p/demo/uassert\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nvar (\n\tsuRealm = std.NewUserRealm(su)\n\n\tnow = \"2009-02-13T23:31:30Z\" // time.Now() is hardcoded to this value in the gno test machine currently\n\tparsedTimeNow, _ = time.Parse(time.RFC3339, now)\n)\n\nfunc TestAddEvent(t *testing.T) {\n\tstd.TestSetOriginCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\te1Start := parsedTimeNow.Add(time.Hour * 24 * 5)\n\te1End := e1Start.Add(time.Hour * 4)\n\n\tAddEvent(\"Event 1\", \"this event is upcoming\", \"gno.land\", \"gnome land\", e1Start.Format(time.RFC3339), e1End.Format(time.RFC3339))\n\n\tgot := renderHome(false)\n\n\tif !strings.Contains(got, \"Event 1\") {\n\t\tt.Fatalf(\"Expected to find Event 1 in render\")\n\t}\n\n\te2Start := parsedTimeNow.Add(-time.Hour * 24 * 5)\n\te2End := e2Start.Add(time.Hour * 4)\n\n\tAddEvent(\"Event 2\", \"this event is in the past\", \"gno.land\", \"gnome land\", e2Start.Format(time.RFC3339), e2End.Format(time.RFC3339))\n\n\tgot = renderHome(false)\n\n\tupcomingPos := strings.Index(got, \"## Upcoming events\")\n\tpastPos := strings.Index(got, \"## Past events\")\n\n\te1Pos := strings.Index(got, \"Event 1\")\n\te2Pos := strings.Index(got, \"Event 2\")\n\n\t// expected index ordering: upcoming \u003c e1 \u003c past \u003c e2\n\tif e1Pos \u003c upcomingPos || e1Pos \u003e pastPos {\n\t\tt.Fatalf(\"Expected to find Event 1 in Upcoming events\")\n\t}\n\n\tif e2Pos \u003c upcomingPos || e2Pos \u003c pastPos || e2Pos \u003c e1Pos {\n\t\tt.Fatalf(\"Expected to find Event 2 on auth different pos\")\n\t}\n\n\t// larger index =\u003e smaller startTime (future =\u003e past)\n\tif events[0].startTime.Unix() \u003c events[1].startTime.Unix() {\n\t\tt.Fatalf(\"expected ordering to be different\")\n\t}\n}\n\nfunc TestAddEventErrors(t *testing.T) {\n\tstd.TestSetOriginCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\t_, err := AddEvent(\"\", \"sample desc\", \"gno.land\", \"gnome land\", \"2009-02-13T23:31:31Z\", \"2009-02-13T23:33:31Z\")\n\tuassert.ErrorIs(t, err, ErrEmptyName)\n\n\t_, err = AddEvent(\"sample name\", \"sample desc\", \"gno.land\", \"gnome land\", \"\", \"2009-02-13T23:33:31Z\")\n\tuassert.ErrorContains(t, err, ErrInvalidStartTime.Error())\n\n\t_, err = AddEvent(\"sample name\", \"sample desc\", \"gno.land\", \"gnome land\", \"2009-02-13T23:31:31Z\", \"\")\n\tuassert.ErrorContains(t, err, ErrInvalidEndTime.Error())\n\n\t_, err = AddEvent(\"sample name\", \"sample desc\", \"gno.land\", \"gnome land\", \"2009-02-13T23:31:31Z\", \"2009-02-13T23:30:31Z\")\n\tuassert.ErrorIs(t, err, ErrEndBeforeStart)\n\n\t_, err = AddEvent(\"sample name\", \"sample desc\", \"gno.land\", \"gnome land\", \"2009-02-13T23:31:31+06:00\", \"2009-02-13T23:33:31+02:00\")\n\tuassert.ErrorIs(t, err, ErrStartEndTimezonemMismatch)\n\n\ttooLongDesc := `Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean ma`\n\t_, err = AddEvent(\"sample name\", tooLongDesc, \"gno.land\", \"gnome land\", \"2009-02-13T23:31:31Z\", \"2009-02-13T23:33:31Z\")\n\tuassert.ErrorContains(t, err, ErrDescriptionTooLong.Error())\n}\n\nfunc TestDeleteEvent(t *testing.T) {\n\tstd.TestSetOriginCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\te1Start := parsedTimeNow.Add(time.Hour * 24 * 5)\n\te1End := e1Start.Add(time.Hour * 4)\n\n\tid, _ := AddEvent(\"ToDelete\", \"description\", \"gno.land\", \"gnome land\", e1Start.Format(time.RFC3339), e1End.Format(time.RFC3339))\n\n\tgot := renderHome(false)\n\n\tif !strings.Contains(got, \"ToDelete\") {\n\t\tt.Fatalf(\"Expected to find ToDelete event in render\")\n\t}\n\n\tDeleteEvent(id)\n\tgot = renderHome(false)\n\n\tif strings.Contains(got, \"ToDelete\") {\n\t\tt.Fatalf(\"Did not expect to find ToDelete event in render\")\n\t}\n}\n\nfunc TestEditEvent(t *testing.T) {\n\tstd.TestSetOriginCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\te1Start := parsedTimeNow.Add(time.Hour * 24 * 5)\n\te1End := e1Start.Add(time.Hour * 4)\n\tloc := \"gnome land\"\n\n\tid, _ := AddEvent(\"ToDelete\", \"description\", \"gno.land\", loc, e1Start.Format(time.RFC3339), e1End.Format(time.RFC3339))\n\n\tnewName := \"New Name\"\n\tnewDesc := \"Normal description\"\n\tnewLink := \"new Link\"\n\tnewST := e1Start.Add(time.Hour)\n\tnewET := newST.Add(time.Hour)\n\n\tEditEvent(id, newName, newDesc, newLink, \"\", newST.Format(time.RFC3339), newET.Format(time.RFC3339))\n\tedited, _, _ := GetEventByID(id)\n\n\t// Check updated values\n\tuassert.Equal(t, edited.name, newName)\n\tuassert.Equal(t, edited.description, newDesc)\n\tuassert.Equal(t, edited.link, newLink)\n\tuassert.True(t, edited.startTime.Equal(newST))\n\tuassert.True(t, edited.endTime.Equal(newET))\n\n\t// Check if the old values are the same\n\tuassert.Equal(t, edited.location, loc)\n}\n\nfunc TestInvalidEdit(t *testing.T) {\n\tstd.TestSetOriginCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\tuassert.PanicsWithMessage(t, ErrNoSuchID.Error(), func() {\n\t\tEditEvent(\"123123\", \"\", \"\", \"\", \"\", \"\", \"\")\n\t})\n}\n\nfunc TestParseTimes(t *testing.T) {\n\t// times not provided\n\t// end time before start time\n\t// timezone Missmatch\n\n\t_, _, err := parseTimes(\"\", \"\")\n\tuassert.ErrorContains(t, err, ErrInvalidStartTime.Error())\n\n\t_, _, err = parseTimes(now, \"\")\n\tuassert.ErrorContains(t, err, ErrInvalidEndTime.Error())\n\n\t_, _, err = parseTimes(\"2009-02-13T23:30:30Z\", \"2009-02-13T21:30:30Z\")\n\tuassert.ErrorContains(t, err, ErrEndBeforeStart.Error())\n\n\t_, _, err = parseTimes(\"2009-02-10T23:30:30+02:00\", \"2009-02-13T21:30:33+05:00\")\n\tuassert.ErrorContains(t, err, ErrStartEndTimezonemMismatch.Error())\n}\n\nfunc TestRenderEventWidget(t *testing.T) {\n\tstd.TestSetOriginCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\t// No events yet\n\tevents = nil\n\tout, err := RenderEventWidget(1)\n\tuassert.NoError(t, err)\n\tuassert.Equal(t, out, \"No events.\")\n\n\t// Too many events\n\tout, err = RenderEventWidget(MaxWidgetSize + 1)\n\tuassert.ErrorIs(t, err, ErrMaxWidgetSize)\n\n\t// Too little events\n\tout, err = RenderEventWidget(0)\n\tuassert.ErrorIs(t, err, ErrMinWidgetSize)\n\n\t// Ordering \u0026 if requested amt is larger than the num of events that exist\n\te1Start := parsedTimeNow.Add(time.Hour * 24 * 5)\n\te1End := e1Start.Add(time.Hour * 4)\n\n\te2Start := parsedTimeNow.Add(time.Hour * 24 * 10) // event 2 is after event 1\n\te2End := e2Start.Add(time.Hour * 4)\n\n\t_, err = AddEvent(\"Event 1\", \"description\", \"gno.land\", \"loc\", e1Start.Format(time.RFC3339), e1End.Format(time.RFC3339))\n\turequire.NoError(t, err)\n\n\t_, err = AddEvent(\"Event 2\", \"description\", \"gno.land\", \"loc\", e2Start.Format(time.RFC3339), e2End.Format(time.RFC3339))\n\turequire.NoError(t, err)\n\n\tout, err = RenderEventWidget(MaxWidgetSize)\n\turequire.NoError(t, err)\n\n\tuniqueSequence := \"- [\" // sequence that is displayed once per each event as per the RenderEventWidget function\n\tuassert.Equal(t, 2, strings.Count(out, uniqueSequence))\n\n\tuassert.True(t, strings.Index(out, \"Event 1\") \u003e strings.Index(out, \"Event 2\"))\n}\n"},{"name":"rendering.gno","body":"package events\n\nimport (\n\t\"bytes\"\n\t\"time\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\nconst (\n\tMaxWidgetSize = 5\n)\n\n// RenderEventWidget shows up to eventsToRender of the latest events to a caller\nfunc RenderEventWidget(eventsToRender int) (string, error) {\n\tnumOfEvents := len(events)\n\tif numOfEvents == 0 {\n\t\treturn \"No events.\", nil\n\t}\n\n\tif eventsToRender \u003e MaxWidgetSize {\n\t\treturn \"\", ErrMaxWidgetSize\n\t}\n\n\tif eventsToRender \u003c 1 {\n\t\treturn \"\", ErrMinWidgetSize\n\t}\n\n\tif eventsToRender \u003e numOfEvents {\n\t\teventsToRender = numOfEvents\n\t}\n\n\toutput := \"\"\n\n\tfor _, event := range events[:eventsToRender] {\n\t\toutput += ufmt.Sprintf(\"- [%s](%s)\\n\", event.name, event.link)\n\t}\n\n\treturn output, nil\n}\n\n// renderHome renders the home page of the events realm\nfunc renderHome(admin bool) string {\n\toutput := \"# gno.land events\\n\\n\"\n\n\tif len(events) == 0 {\n\t\toutput += \"No upcoming or past events.\"\n\t\treturn output\n\t}\n\n\toutput += \"Below is a list of all gno.land events, including in progress, upcoming, and past ones.\\n\\n\"\n\toutput += \"---\\n\\n\"\n\n\tvar (\n\t\tinProgress = \"\"\n\t\tupcoming = \"\"\n\t\tpast = \"\"\n\t\tnow = time.Now()\n\t)\n\n\tfor _, e := range events {\n\t\tif now.Before(e.startTime) {\n\t\t\tupcoming += e.Render(admin)\n\t\t} else if now.After(e.endTime) {\n\t\t\tpast += e.Render(admin)\n\t\t} else {\n\t\t\tinProgress += e.Render(admin)\n\t\t}\n\t}\n\n\tif upcoming != \"\" {\n\t\t// Add upcoming events\n\t\toutput += \"## Upcoming events\\n\\n\"\n\t\toutput += \"\u003cdiv class='columns-3'\u003e\"\n\n\t\toutput += upcoming\n\n\t\toutput += \"\u003c/div\u003e\\n\\n\"\n\t\toutput += \"---\\n\\n\"\n\t}\n\n\tif inProgress != \"\" {\n\t\toutput += \"## Currently in progress\\n\\n\"\n\t\toutput += \"\u003cdiv class='columns-3'\u003e\"\n\n\t\toutput += inProgress\n\n\t\toutput += \"\u003c/div\u003e\\n\\n\"\n\t\toutput += \"---\\n\\n\"\n\t}\n\n\tif past != \"\" {\n\t\t// Add past events\n\t\toutput += \"## Past events\\n\\n\"\n\t\toutput += \"\u003cdiv class='columns-3'\u003e\"\n\n\t\toutput += past\n\n\t\toutput += \"\u003c/div\u003e\\n\\n\"\n\t}\n\n\treturn output\n}\n\n// Render returns the markdown representation of a single event instance\nfunc (e Event) Render(admin bool) string {\n\tvar buf bytes.Buffer\n\n\tbuf.WriteString(\"\u003cdiv\u003e\\n\\n\")\n\tbuf.WriteString(ufmt.Sprintf(\"### %s\\n\\n\", e.name))\n\tbuf.WriteString(ufmt.Sprintf(\"%s\\n\\n\", e.description))\n\tbuf.WriteString(ufmt.Sprintf(\"**Location:** %s\\n\\n\", e.location))\n\n\t_, offset := e.startTime.Zone() // offset is in seconds\n\thoursOffset := offset / (60 * 60)\n\tsign := \"\"\n\tif offset \u003e= 0 {\n\t\tsign = \"+\"\n\t}\n\n\tbuf.WriteString(ufmt.Sprintf(\"**Starts:** %s UTC%s%d\\n\\n\", e.startTime.Format(\"02 Jan 2006, 03:04 PM\"), sign, hoursOffset))\n\tbuf.WriteString(ufmt.Sprintf(\"**Ends:** %s UTC%s%d\\n\\n\", e.endTime.Format(\"02 Jan 2006, 03:04 PM\"), sign, hoursOffset))\n\n\tif admin {\n\t\tbuf.WriteString(ufmt.Sprintf(\"[EDIT](/r/gnoland/events$help\u0026func=EditEvent\u0026id=%s)\\n\\n\", e.id))\n\t\tbuf.WriteString(ufmt.Sprintf(\"[DELETE](/r/gnoland/events$help\u0026func=DeleteEvent\u0026id=%s)\\n\\n\", e.id))\n\t}\n\n\tif e.link != \"\" {\n\t\tbuf.WriteString(ufmt.Sprintf(\"[See more](%s)\\n\\n\", e.link))\n\t}\n\n\tbuf.WriteString(\"\u003c/div\u003e\")\n\n\treturn buf.String()\n}\n\n// Render is the main rendering entry point\nfunc Render(path string) string {\n\tif path == \"admin\" {\n\t\treturn renderHome(true)\n\t}\n\n\treturn renderHome(false)\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":"faucet","path":"gno.land/r/gnoland/faucet","files":[{"name":"admin.gno","body":"package faucet\n\nimport (\n\t\"errors\"\n\t\"std\"\n)\n\nfunc AdminSetInPause(inPause bool) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\tgInPause = inPause\n\treturn \"\"\n}\n\nfunc AdminSetMessage(message string) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\tgMessage = message\n\treturn \"\"\n}\n\nfunc AdminSetTransferLimit(amount int64) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\tgLimit = std.NewCoin(\"ugnot\", amount)\n\treturn \"\"\n}\n\nfunc AdminSetAdminAddr(addr std.Address) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\tgAdminAddr = addr\n\treturn \"\"\n}\n\nfunc AdminAddController(addr std.Address) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\n\tsize := gControllers.Size()\n\n\tif size \u003e= gControllersMaxSize {\n\t\treturn \"can not add more controllers than allowed\"\n\t}\n\n\tif gControllers.Has(addr.String()) {\n\t\treturn addr.String() + \" exists, no need to add.\"\n\t}\n\n\tgControllers.Set(addr.String(), addr)\n\n\treturn \"\"\n}\n\nfunc AdminRemoveController(addr std.Address) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\n\tif !gControllers.Has(addr.String()) {\n\t\treturn addr.String() + \" is not on the controller list\"\n\t}\n\n\t_, ok := gControllers.Remove(addr.String())\n\n\t// it not should happen.\n\t// we will check anyway to prevent issues in the underline implementation.\n\n\tif !ok {\n\t\treturn addr.String() + \" is not on the controller list\"\n\t}\n\n\treturn \"\"\n}\n\nfunc assertIsAdmin() error {\n\tcaller := std.OriginCaller()\n\tif caller != gAdminAddr {\n\t\treturn errors.New(\"restricted for admin\")\n\t}\n\treturn nil\n}\n"},{"name":"faucet.gno","body":"package faucet\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar (\n\t// configurable by admin.\n\tgAdminAddr std.Address = std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")\n\tgControllers = avl.NewTree()\n\tgControllersMaxSize = 10 // limit it to 10\n\tgInPause = false\n\tgMessage = \"# Community Faucet.\\n\\n\"\n\n\t// internal vars, for stats.\n\tgTotalTransferred std.Coins\n\tgTotalTransfers = uint(0)\n\n\t// per request limit, 350 gnot\n\tgLimit std.Coin = std.NewCoin(\"ugnot\", 350000000)\n)\n\nfunc Transfer(to std.Address, send int64) string {\n\tif err := assertIsController(); err != nil {\n\t\treturn err.Error()\n\t}\n\n\tif gInPause {\n\t\treturn errors.New(\"faucet in pause\").Error()\n\t}\n\n\t// limit the per request\n\tif send \u003e gLimit.Amount {\n\t\treturn errors.New(\"Per request limit \" + gLimit.String() + \" exceed\").Error()\n\t}\n\tsendCoins := std.Coins{std.NewCoin(\"ugnot\", send)}\n\n\tgTotalTransferred = gTotalTransferred.Add(sendCoins)\n\tgTotalTransfers++\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tpkgaddr := std.CurrentRealm().Addr()\n\tbanker.SendCoins(pkgaddr, to, sendCoins)\n\treturn \"\"\n}\n\nfunc GetPerTransferLimit() int64 {\n\treturn gLimit.Amount\n}\n\nfunc Render(_ string) string {\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tbalance := banker.GetCoins(std.CurrentRealm().Addr())\n\n\toutput := gMessage\n\tif gInPause {\n\t\toutput += \"Status: inactive.\\n\"\n\t} else {\n\t\toutput += \"Status: active.\\n\"\n\t}\n\toutput += ufmt.Sprintf(\"Balance: %s.\\n\", balance.String())\n\toutput += ufmt.Sprintf(\"Total transfers: %s (in %d times).\\n\\n\", gTotalTransferred.String(), gTotalTransfers)\n\n\toutput += \"Package address: \" + std.CurrentRealm().Addr().String() + \"\\n\\n\"\n\toutput += ufmt.Sprintf(\"Admin: %s\\n\\n \", gAdminAddr.String())\n\toutput += ufmt.Sprintf(\"Controllers:\\n\\n \")\n\n\tfor i := 0; i \u003c gControllers.Size(); i++ {\n\t\t_, v := gControllers.GetByIndex(i)\n\t\toutput += ufmt.Sprintf(\"%s \", v.(std.Address))\n\t}\n\n\toutput += \"\\n\\n\"\n\toutput += ufmt.Sprintf(\"Per request limit: %s\\n\\n\", gLimit.String())\n\n\treturn output\n}\n\nfunc assertIsController() error {\n\tcaller := std.OriginCaller()\n\n\tok := gControllers.Has(caller.String())\n\tif !ok {\n\t\treturn errors.New(caller.String() + \" is not on the controller list\")\n\t}\n\treturn nil\n}\n"},{"name":"faucet_test.gno","body":"package faucet\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/gnoland/faucet\"\n)\n\nfunc TestPackage(t *testing.T) {\n\tvar (\n\t\tadminaddr = std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")\n\t\tfaucetaddr = std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\t\tcontrolleraddr1 = testutils.TestAddress(\"controller1\")\n\t\tcontrolleraddr2 = testutils.TestAddress(\"controller2\")\n\t\tcontrolleraddr3 = testutils.TestAddress(\"controller3\")\n\t\tcontrolleraddr4 = testutils.TestAddress(\"controller4\")\n\t\tcontrolleraddr5 = testutils.TestAddress(\"controller5\")\n\t\tcontrolleraddr6 = testutils.TestAddress(\"controller6\")\n\t\tcontrolleraddr7 = testutils.TestAddress(\"controller7\")\n\t\tcontrolleraddr8 = testutils.TestAddress(\"controller8\")\n\t\tcontrolleraddr9 = testutils.TestAddress(\"controller9\")\n\t\tcontrolleraddr10 = testutils.TestAddress(\"controller10\")\n\t\tcontrolleraddr11 = testutils.TestAddress(\"controller11\")\n\n\t\ttest1addr = testutils.TestAddress(\"test1\")\n\t)\n\t// deposit 1000gnot to faucet contract\n\tstd.TestIssueCoins(faucetaddr, std.Coins{{\"ugnot\", 1000000000}})\n\tassertBalance(t, faucetaddr, 1200000000)\n\n\t// by default, balance is empty, and as a user I cannot call Transfer, or Admin commands.\n\n\tassertBalance(t, test1addr, 0)\n\tstd.TestSetOriginCaller(test1addr)\n\tassertErr(t, faucet.Transfer(test1addr, 1000000))\n\n\tassertErr(t, faucet.AdminAddController(controlleraddr1))\n\tstd.TestSetOriginCaller(controlleraddr1)\n\tassertErr(t, faucet.Transfer(test1addr, 1000000))\n\n\t// as an admin, add the controller to contract and deposit more 2000gnot to contract\n\tstd.TestSetOriginCaller(adminaddr)\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr1))\n\tassertBalance(t, faucetaddr, 1200000000)\n\n\t// now, send some tokens as controller.\n\tstd.TestSetOriginCaller(controlleraddr1)\n\tassertNoErr(t, faucet.Transfer(test1addr, 1000000))\n\tassertBalance(t, test1addr, 1000000)\n\tassertNoErr(t, faucet.Transfer(test1addr, 1000000))\n\tassertBalance(t, test1addr, 2000000)\n\tassertBalance(t, faucetaddr, 1198000000)\n\n\t// remove controller\n\t// as an admin, remove controller\n\tstd.TestSetOriginCaller(adminaddr)\n\tassertNoErr(t, faucet.AdminRemoveController(controlleraddr1))\n\tstd.TestSetOriginCaller(controlleraddr1)\n\tassertErr(t, faucet.Transfer(test1addr, 1000000))\n\n\t// duplicate controller\n\tstd.TestSetOriginCaller(adminaddr)\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr1))\n\tassertErr(t, faucet.AdminAddController(controlleraddr1))\n\t// add more than more than allowed controllers\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr2))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr3))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr4))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr5))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr6))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr7))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr8))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr9))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr10))\n\tassertErr(t, faucet.AdminAddController(controlleraddr11))\n\n\t// send more than per transfer limit\n\tstd.TestSetOriginCaller(adminaddr)\n\tfaucet.AdminSetTransferLimit(300000000)\n\tstd.TestSetOriginCaller(controlleraddr1)\n\tassertErr(t, faucet.Transfer(test1addr, 301000000))\n\n\t// block transefer from the address not on the controllers list.\n\tstd.TestSetOriginCaller(controlleraddr11)\n\tassertErr(t, faucet.Transfer(test1addr, 1000000))\n}\n\nfunc assertErr(t *testing.T, err string) {\n\tt.Helper()\n\n\tif err == \"\" {\n\t\tt.Logf(\"info: got err: %v\", err)\n\t\tt.Errorf(\"expected an error, got nil.\")\n\t}\n}\n\nfunc assertNoErr(t *testing.T, err string) {\n\tt.Helper()\n\tif err != \"\" {\n\t\tt.Errorf(\"got err: %v.\", err)\n\t}\n}\n\nfunc assertBalance(t *testing.T, addr std.Address, expectedBal int64) {\n\tt.Helper()\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tcoins := banker.GetCoins(addr)\n\tgot := coins.AmountOf(\"ugnot\")\n\n\tif expectedBal != got {\n\t\tt.Errorf(\"invalid balance: expected %d, got %d.\", expectedBal, got)\n\t}\n}\n"},{"name":"z0_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/gnoland/faucet\"\n)\n\n// mints ugnot to current realm\nfunc init() {\n\tfacuetaddr := std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\tstd.TestIssueCoins(facuetaddr, std.Coins{{\"ugnot\", 200000000}})\n}\n\n// assert render with empty path and no controllers\nfunc main() {\n\tprintln(faucet.Render(\"\"))\n}\n\n// Output:\n// # Community Faucet.\n//\n// Status: active.\n// Balance: 200000000ugnot.\n// Total transfers: (in 0 times).\n//\n// Package address: g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7\n//\n// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n//\n// Controllers:\n//\n//\n//\n// Per request limit: 350000000ugnot\n"},{"name":"z1_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/gnoland/faucet\"\n)\n\n// mints ugnot to current realm\nfunc init() {\n\tfacuetaddr := std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\tstd.TestIssueCoins(facuetaddr, std.Coins{{\"ugnot\", 200000000}})\n}\n\n// assert render with a path and no controllers\nfunc main() {\n\tprintln(faucet.Render(\"path\"))\n}\n\n// Output:\n// # Community Faucet.\n//\n// Status: active.\n// Balance: 200000000ugnot.\n// Total transfers: (in 0 times).\n//\n// Package address: g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7\n//\n// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n//\n// Controllers:\n//\n//\n//\n// Per request limit: 350000000ugnot\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/gnoland/faucet\"\n)\n\n// mints ugnot to current realm\nfunc init() {\n\tfacuetaddr := std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\tstd.TestIssueCoins(facuetaddr, std.Coins{{\"ugnot\", 200000000}})\n}\n\n// assert render with empty path and 2 controllers\nfunc main() {\n\tvar (\n\t\tadminaddr = std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")\n\t\tcontrolleraddr1 = testutils.TestAddress(\"controller1\")\n\t\tcontrolleraddr2 = testutils.TestAddress(\"controller2\")\n\t)\n\tstd.TestSetOriginCaller(adminaddr)\n\terr := faucet.AdminAddController(controlleraddr1)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\terr = faucet.AdminAddController(controlleraddr2)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\tprintln(faucet.Render(\"\"))\n}\n\n// Output:\n// # Community Faucet.\n//\n// Status: active.\n// Balance: 200000000ugnot.\n// Total transfers: (in 0 times).\n//\n// Package address: g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7\n//\n// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n//\n// Controllers:\n//\n// g1vdhkuarjdakxcetjx9047h6lta047h6lsdacav g1vdhkuarjdakxcetjxf047h6lta047h6lnrev3v\n//\n// Per request limit: 350000000ugnot\n"},{"name":"z3_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/gnoland/faucet\"\n)\n\n// mints coints to current realm\nfunc init() {\n\tfacuetaddr := std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\tstd.TestIssueCoins(facuetaddr, std.Coins{{\"ugnot\", 200000000}})\n}\n\n// assert render with 2 controllers and 2 transfers\nfunc main() {\n\tvar (\n\t\tadminaddr = std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")\n\t\tcontrolleraddr1 = testutils.TestAddress(\"controller1\")\n\t\tcontrolleraddr2 = testutils.TestAddress(\"controller2\")\n\t\ttestaddr1 = testutils.TestAddress(\"test1\")\n\t\ttestaddr2 = testutils.TestAddress(\"test2\")\n\t)\n\tstd.TestSetOriginCaller(adminaddr)\n\terr := faucet.AdminAddController(controlleraddr1)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\terr = faucet.AdminAddController(controlleraddr2)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\tstd.TestSetOriginCaller(controlleraddr1)\n\terr = faucet.Transfer(testaddr1, 1000000)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\tstd.TestSetOriginCaller(controlleraddr2)\n\terr = faucet.Transfer(testaddr1, 2000000)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\tprintln(faucet.Render(\"\"))\n}\n\n// Output:\n// # Community Faucet.\n//\n// Status: active.\n// Balance: 197000000ugnot.\n// Total transfers: 3000000ugnot (in 2 times).\n//\n// Package address: g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7\n//\n// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n//\n// Controllers:\n//\n// g1vdhkuarjdakxcetjx9047h6lta047h6lsdacav g1vdhkuarjdakxcetjxf047h6lta047h6lnrev3v\n//\n// Per request limit: 350000000ugnot\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":"ghverify","path":"gno.land/r/gnoland/ghverify","files":[{"name":"README.md","body":"# ghverify\n\nThis realm is intended to enable off chain gno address to github handle verification.\nThe steps are as follows:\n- A user calls `RequestVerification` and provides a github handle. This creates a new static oracle feed.\n- An off-chain agent controlled by the owner of this realm requests current feeds using the `GnorkleEntrypoint` function and provides a message of `\"request\"`\n- The agent receives the task information that includes the github handle and the gno address. It performs the verification step by checking whether this github user has the address in a github repository it controls.\n- The agent publishes the result of the verification by calling `GnorkleEntrypoint` with a message structured like: `\"ingest,\u003ctask id\u003e,\u003cverification status\u003e\"`. The verification status is `OK` if verification succeeded and any other value if it failed.\n- The oracle feed's ingester processes the verification and the handle to address mapping is written to the avl trees that exist as ghverify realm variables."},{"name":"contract.gno","body":"package ghverify\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/gnorkle/feeds/static\"\n\t\"gno.land/p/demo/gnorkle/gnorkle\"\n\t\"gno.land/p/demo/gnorkle/message\"\n)\n\nconst (\n\t// The agent should send this value if it has verified the github handle.\n\tverifiedResult = \"OK\"\n)\n\nvar (\n\townerAddress = std.OriginCaller()\n\toracle *gnorkle.Instance\n\tpostHandler postGnorkleMessageHandler\n\n\thandleToAddressMap = avl.NewTree()\n\taddressToHandleMap = avl.NewTree()\n)\n\nfunc init() {\n\toracle = gnorkle.NewInstance()\n\toracle.AddToWhitelist(\"\", []string{string(ownerAddress)})\n}\n\ntype postGnorkleMessageHandler struct{}\n\n// Handle does post processing after a message is ingested by the oracle feed. It extracts the value to realm\n// storage and removes the feed from the oracle.\nfunc (h postGnorkleMessageHandler) Handle(i *gnorkle.Instance, funcType message.FuncType, feed gnorkle.Feed) error {\n\tif funcType != message.FuncTypeIngest {\n\t\treturn nil\n\t}\n\n\tresult, _, consumable := feed.Value()\n\tif !consumable {\n\t\treturn nil\n\t}\n\n\t// The value is consumable, meaning the ingestion occurred, so we can remove the feed from the oracle\n\t// after saving it to realm storage.\n\tdefer oracle.RemoveFeed(feed.ID())\n\n\t// Couldn't verify; nothing to do.\n\tif result.String != verifiedResult {\n\t\treturn nil\n\t}\n\n\tfeedTasks := feed.Tasks()\n\tif len(feedTasks) != 1 {\n\t\treturn errors.New(\"expected feed to have exactly one task\")\n\t}\n\n\ttask, ok := feedTasks[0].(*verificationTask)\n\tif !ok {\n\t\treturn errors.New(\"expected ghverify task\")\n\t}\n\n\thandleToAddressMap.Set(task.githubHandle, task.gnoAddress)\n\taddressToHandleMap.Set(task.gnoAddress, task.githubHandle)\n\treturn nil\n}\n\n// RequestVerification creates a new static feed with a single task that will\n// instruct an agent to verify the github handle / gno address pair.\nfunc RequestVerification(githubHandle string) {\n\tgnoAddress := string(std.OriginCaller())\n\tif err := oracle.AddFeeds(\n\t\tstatic.NewSingleValueFeed(\n\t\t\tgnoAddress,\n\t\t\t\"string\",\n\t\t\t\u0026verificationTask{\n\t\t\t\tgnoAddress: gnoAddress,\n\t\t\t\tgithubHandle: githubHandle,\n\t\t\t},\n\t\t),\n\t); err != nil {\n\t\tpanic(err)\n\t}\n\tstd.Emit(\n\t\t\"verification_requested\",\n\t\t\"from\", gnoAddress,\n\t\t\"handle\", githubHandle,\n\t)\n}\n\n// GnorkleEntrypoint is the entrypoint to the gnorkle oracle handler.\nfunc GnorkleEntrypoint(message string) string {\n\tresult, err := oracle.HandleMessage(message, postHandler)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn result\n}\n\n// SetOwner transfers ownership of the contract to the given address.\nfunc SetOwner(owner std.Address) {\n\tif ownerAddress != std.OriginCaller() {\n\t\tpanic(\"only the owner can set a new owner\")\n\t}\n\n\townerAddress = owner\n\n\t// In the context of this contract, the owner is the only one that can\n\t// add new feeds to the oracle.\n\toracle.ClearWhitelist(\"\")\n\toracle.AddToWhitelist(\"\", []string{string(ownerAddress)})\n}\n\n// GetHandleByAddress returns the github handle associated with the given gno address.\nfunc GetHandleByAddress(address string) string {\n\tif value, ok := addressToHandleMap.Get(address); ok {\n\t\treturn value.(string)\n\t}\n\n\treturn \"\"\n}\n\n// GetAddressByHandle returns the gno address associated with the given github handle.\nfunc GetAddressByHandle(handle string) string {\n\tif value, ok := handleToAddressMap.Get(handle); ok {\n\t\treturn value.(string)\n\t}\n\n\treturn \"\"\n}\n\n// Render returns a json object string will all verified handle -\u003e address mappings.\nfunc Render(_ string) string {\n\tresult := \"{\"\n\tvar appendComma bool\n\thandleToAddressMap.Iterate(\"\", \"\", func(handle string, address interface{}) bool {\n\t\tif appendComma {\n\t\t\tresult += \",\"\n\t\t}\n\n\t\tresult += `\"` + handle + `\": \"` + address.(string) + `\"`\n\t\tappendComma = true\n\n\t\treturn false\n\t})\n\n\treturn result + \"}\"\n}\n"},{"name":"contract_test.gno","body":"package ghverify\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n)\n\nfunc TestVerificationLifecycle(t *testing.T) {\n\tdefaultAddress := std.OriginCaller()\n\tuser1Address := std.Address(testutils.TestAddress(\"user 1\"))\n\tuser2Address := std.Address(testutils.TestAddress(\"user 2\"))\n\n\t// Verify request returns no feeds.\n\tresult := GnorkleEntrypoint(\"request\")\n\tif result != \"[]\" {\n\t\tt.Fatalf(\"expected empty request result, got %s\", result)\n\t}\n\n\t// Make a verification request with the created user.\n\tstd.TestSetOriginCaller(user1Address)\n\tRequestVerification(\"deelawn\")\n\n\t// A subsequent request from the same address should panic because there is\n\t// already a feed with an ID of this user's address.\n\tvar errMsg string\n\tfunc() {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\terrMsg = r.(error).Error()\n\t\t\t}\n\t\t}()\n\t\tRequestVerification(\"deelawn\")\n\t}()\n\tif errMsg != \"feed already exists\" {\n\t\tt.Fatalf(\"expected feed already exists, got %s\", errMsg)\n\t}\n\n\t// Verify the request returns no feeds for this non-whitelisted user.\n\tresult = GnorkleEntrypoint(\"request\")\n\tif result != \"[]\" {\n\t\tt.Fatalf(\"expected empty request result, got %s\", result)\n\t}\n\n\t// Make a verification request with the created user.\n\tstd.TestSetOriginCaller(user2Address)\n\tRequestVerification(\"omarsy\")\n\n\t// Set the caller back to the whitelisted user and verify that the feed data\n\t// returned matches what should have been created by the `RequestVerification`\n\t// invocation.\n\tstd.TestSetOriginCaller(defaultAddress)\n\tresult = GnorkleEntrypoint(\"request\")\n\texpResult := `[{\"id\":\"` + string(user1Address) + `\",\"type\":\"0\",\"value_type\":\"string\",\"tasks\":[{\"gno_address\":\"` +\n\t\tstring(user1Address) + `\",\"github_handle\":\"deelawn\"}]},` +\n\t\t`{\"id\":\"` + string(user2Address) + `\",\"type\":\"0\",\"value_type\":\"string\",\"tasks\":[{\"gno_address\":\"` +\n\t\tstring(user2Address) + `\",\"github_handle\":\"omarsy\"}]}]`\n\tif result != expResult {\n\t\tt.Fatalf(\"expected request result %s, got %s\", expResult, result)\n\t}\n\n\t// Try to trigger feed ingestion from the non-authorized user.\n\tstd.TestSetOriginCaller(user1Address)\n\tfunc() {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\terrMsg = r.(error).Error()\n\t\t\t}\n\t\t}()\n\t\tGnorkleEntrypoint(\"ingest,\" + string(user1Address) + \",OK\")\n\t}()\n\tif errMsg != \"caller not whitelisted\" {\n\t\tt.Fatalf(\"expected caller not whitelisted, got %s\", errMsg)\n\t}\n\n\t// Set the caller back to the whitelisted user and transfer contract ownership.\n\tstd.TestSetOriginCaller(defaultAddress)\n\tSetOwner(defaultAddress)\n\n\t// Now trigger the feed ingestion from the user and new owner and only whitelisted address.\n\tGnorkleEntrypoint(\"ingest,\" + string(user1Address) + \",OK\")\n\tGnorkleEntrypoint(\"ingest,\" + string(user2Address) + \",OK\")\n\n\t// Verify the ingestion autocommitted the value and triggered the post handler.\n\tdata := Render(\"\")\n\texpResult = `{\"deelawn\": \"` + string(user1Address) + `\",\"omarsy\": \"` + string(user2Address) + `\"}`\n\tif data != expResult {\n\t\tt.Fatalf(\"expected render data %s, got %s\", expResult, data)\n\t}\n\n\t// Finally make sure the feed was cleaned up after the data was committed.\n\tresult = GnorkleEntrypoint(\"request\")\n\tif result != \"[]\" {\n\t\tt.Fatalf(\"expected empty request result, got %s\", result)\n\t}\n\n\t// Check that the accessor functions are working as expected.\n\tif handle := GetHandleByAddress(string(user1Address)); handle != \"deelawn\" {\n\t\tt.Fatalf(\"expected deelawn, got %s\", handle)\n\t}\n\tif address := GetAddressByHandle(\"deelawn\"); address != string(user1Address) {\n\t\tt.Fatalf(\"expected %s, got %s\", string(user1Address), address)\n\t}\n}\n"},{"name":"task.gno","body":"package ghverify\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n)\n\ntype verificationTask struct {\n\tgnoAddress string\n\tgithubHandle string\n}\n\n// MarshalJSON marshals the task contents to JSON.\nfunc (t *verificationTask) MarshalJSON() ([]byte, error) {\n\tbuf := new(bytes.Buffer)\n\tw := bufio.NewWriter(buf)\n\n\tw.Write(\n\t\t[]byte(`{\"gno_address\":\"` + t.gnoAddress + `\",\"github_handle\":\"` + t.githubHandle + `\"}`),\n\t)\n\n\tw.Flush()\n\treturn buf.Bytes(), nil\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":"home","path":"gno.land/r/gnoland/home","files":[{"name":"home.gno","body":"package home\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/p/demo/ui\"\n\tblog \"gno.land/r/gnoland/blog\"\n\tevents \"gno.land/r/gnoland/events\"\n)\n\n// XXX: p/demo/ui API is crappy, we need to make it more idiomatic\n// XXX: use an updatable block system to update content from a DAO\n// XXX: var blocks avl.Tree\n\nvar (\n\toverride string\n\tadmin = ownable.NewWithAddress(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\") // @manfred by default\n)\n\nfunc Render(_ string) string {\n\tif override != \"\" {\n\t\treturn override\n\t}\n\n\tdom := ui.DOM{Prefix: \"r/gnoland/home:\"}\n\tdom.Title = \"Welcome to gno.land\"\n\tdom.Classes = []string{\"gno-tmpl-section\"}\n\n\t// body\n\tdom.Body.Append(introSection()...)\n\n\tdom.Body.Append(ui.Jumbotron(discoverLinks()))\n\n\tdom.Body.Append(\n\t\tui.Columns{3, []ui.Element{\n\t\t\tlastBlogposts(4),\n\t\t\tupcomingEvents(),\n\t\t\tlastContributions(4),\n\t\t}},\n\t)\n\n\tdom.Body.Append(ui.HR{})\n\tdom.Body.Append(playgroundSection()...)\n\tdom.Body.Append(ui.HR{})\n\tdom.Body.Append(packageStaffPicks()...)\n\tdom.Body.Append(ui.HR{})\n\tdom.Body.Append(worxDAO()...)\n\tdom.Body.Append(ui.HR{})\n\t// footer\n\tdom.Footer.Append(\n\t\tui.Columns{2, []ui.Element{\n\t\t\tsocialLinks(),\n\t\t\tquoteOfTheBlock(),\n\t\t}},\n\t)\n\n\t// Testnet disclaimer\n\tdom.Footer.Append(\n\t\tui.HR{},\n\t\tui.Bold(\"This is a testnet.\"),\n\t\tui.Text(\"Package names are not guaranteed to be available for production.\"),\n\t)\n\n\treturn dom.String()\n}\n\nfunc lastBlogposts(limit int) ui.Element {\n\tposts := blog.RenderLastPostsWidget(limit)\n\treturn ui.Element{\n\t\tui.H3(\"[Latest Blogposts](/r/gnoland/blog)\"),\n\t\tui.Text(posts),\n\t}\n}\n\nfunc lastContributions(limit int) ui.Element {\n\treturn ui.Element{\n\t\tui.H3(\"Latest Contributions\"),\n\t\t// TODO: import r/gh to\n\t\tui.Link{Text: \"View latest contributions\", URL: \"https://github.com/gnolang/gno/pulls\"},\n\t}\n}\n\nfunc upcomingEvents() ui.Element {\n\tout, _ := events.RenderEventWidget(events.MaxWidgetSize)\n\treturn ui.Element{\n\t\tui.H3(\"[Latest Events](/r/gnoland/events)\"),\n\t\tui.Text(out),\n\t}\n}\n\nfunc introSection() ui.Element {\n\treturn ui.Element{\n\t\tui.H3(\"We’re building gno.land, set to become the leading open-source smart contract platform, using Gno, an interpreted and fully deterministic variation of the Go programming language for succinct and composable smart contracts.\"),\n\t\tui.Paragraph(\"With transparent and timeless code, gno.land is the next generation of smart contract platforms, serving as the “GitHub” of the ecosystem, with realms built using fully transparent, auditable code that anyone can inspect and reuse.\"),\n\t\tui.Paragraph(\"Intuitive and easy to use, gno.land lowers the barrier to web3 and makes censorship-resistant platforms accessible to everyone. If you want to help lay the foundations of a fairer and freer world, join us today.\"),\n\t}\n}\n\nfunc worxDAO() ui.Element {\n\t// WorxDAO\n\t// XXX(manfred): please, let me finish a v0, then we can iterate\n\t// highest level == highest responsibility\n\t// teams are responsible for components they don't owne\n\t// flag : realm maintainers VS facilitators\n\t// teams\n\t// committee of trustees to create the directory\n\t// each directory is a name, has a parent and have groups\n\t// homepage team - blocks aggregating events\n\t// XXX: TODO\n\t/*`\n\t# Directory\n\n\t* gno.land (owned by group)\n\t *\n\t* gnovm\n\t * gnolang (language)\n\t * gnovm\n\t - current challenges / concerns / issues\n\t* tm2\n\t * amino\n\t *\n\n\t## Contributors\n\t``*/\n\treturn ui.Element{\n\t\tui.H3(\"Contributions (WorxDAO \u0026 GoR)\"),\n\t\t// TODO: GoR dashboard + WorxDAO topics\n\t\tui.Text(`coming soon`),\n\t}\n}\n\nfunc quoteOfTheBlock() ui.Element {\n\tquotes := []string{\n\t\t\"Gno is for Truth.\",\n\t\t\"Gno is for Social Coordination.\",\n\t\t\"Gno is _not only_ for DeFi.\",\n\t\t\"Now, you Gno.\",\n\t\t\"Come for the Go, Stay for the Gno.\",\n\t}\n\theight := std.GetHeight()\n\tidx := int(height) % len(quotes)\n\tqotb := quotes[idx]\n\n\treturn ui.Element{\n\t\tui.H3(ufmt.Sprintf(\"Quote of the ~Day~ Block#%d\", height)),\n\t\tui.Quote(qotb),\n\t}\n}\n\nfunc socialLinks() ui.Element {\n\treturn ui.Element{\n\t\tui.H3(\"Socials\"),\n\t\tui.BulletList{\n\t\t\t// XXX: improve UI to support a nice GO api for such links\n\t\t\tui.Text(\"Check out our [community projects](https://github.com/gnolang/awesome-gno)\"),\n\t\t\tui.Text(\"![Discord](static/img/ico-discord.svg) [Discord](https://discord.gg/S8nKUqwkPn)\"),\n\t\t\tui.Text(\"![Twitter](static/img/ico-twitter.svg) [Twitter](https://twitter.com/_gnoland)\"),\n\t\t\tui.Text(\"![Youtube](static/img/ico-youtube.svg) [Youtube](https://www.youtube.com/@_gnoland)\"),\n\t\t\tui.Text(\"![Telegram](static/img/ico-telegram.svg) [Telegram](https://t.me/gnoland)\"),\n\t\t},\n\t}\n}\n\nfunc playgroundSection() ui.Element {\n\treturn ui.Element{\n\t\tui.H3(\"[Gno Playground](https://play.gno.land)\"),\n\t\tui.Paragraph(`Gno Playground is a web application designed for building, running, testing, and interacting\nwith your Gno code, enhancing your understanding of the Gno language. With Gno Playground, you can share your code,\nexecute tests, deploy your realms and packages to gno.land, and explore a multitude of other features.`),\n\t\tui.Paragraph(\"Experience the convenience of code sharing and rapid experimentation with [Gno Playground](https://play.gno.land).\"),\n\t}\n}\n\nfunc packageStaffPicks() ui.Element {\n\t// XXX: make it modifiable from a DAO\n\treturn ui.Element{\n\t\tui.H3(\"Explore New Packages and Realms\"),\n\t\tui.Columns{\n\t\t\t3,\n\t\t\t[]ui.Element{\n\t\t\t\t{\n\t\t\t\t\tui.H4(\"[r/gnoland](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/gnoland)\"),\n\t\t\t\t\tui.BulletList{\n\t\t\t\t\t\tui.Link{URL: \"r/gnoland/blog\"},\n\t\t\t\t\t\tui.Link{URL: \"r/gnoland/dao\"},\n\t\t\t\t\t\tui.Link{URL: \"r/gnoland/faucet\"},\n\t\t\t\t\t\tui.Link{URL: \"r/gnoland/home\"},\n\t\t\t\t\t\tui.Link{URL: \"r/gnoland/pages\"},\n\t\t\t\t\t},\n\t\t\t\t\tui.H4(\"[r/sys](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/sys)\"),\n\t\t\t\t\tui.BulletList{\n\t\t\t\t\t\tui.Link{URL: \"r/sys/names\"},\n\t\t\t\t\t\tui.Link{URL: \"r/sys/rewards\"},\n\t\t\t\t\t\tui.Link{URL: \"/r/sys/validators/v2\"},\n\t\t\t\t\t},\n\t\t\t\t}, {\n\t\t\t\t\tui.H4(\"[r/demo](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo)\"),\n\t\t\t\t\tui.BulletList{\n\t\t\t\t\t\tui.Link{URL: \"r/demo/boards\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/users\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/banktest\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/foo20\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/foo721\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/microblog\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/nft\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/types\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/art/gnoface\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/art/millipede\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/groups\"},\n\t\t\t\t\t\tui.Text(\"...\"),\n\t\t\t\t\t},\n\t\t\t\t}, {\n\t\t\t\t\tui.H4(\"[p/demo](https://github.com/gnolang/gno/tree/master/examples/gno.land/p/demo)\"),\n\t\t\t\t\tui.BulletList{\n\t\t\t\t\t\tui.Link{URL: \"p/demo/avl\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/blog\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/ui\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/ufmt\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/merkle\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/bf\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/flow\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/gnode\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/grc/grc20\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/grc/grc721\"},\n\t\t\t\t\t\tui.Text(\"...\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc discoverLinks() ui.Element {\n\treturn ui.Element{\n\t\tui.Text(`\u003cdiv class=\"columns-3\"\u003e\n\u003cdiv class=\"column\"\u003e\n\n### Learn about gno.land\n\n- [About](/about)\n- [GitHub](https://github.com/gnolang)\n- [Blog](/blog)\n- [Events](/events)\n- Tokenomics (soon)\n- [Partners, Fund, Grants](/partners)\n- [Explore the Ecosystem](/ecosystem)\n- [Careers](https://jobs.ashbyhq.com/allinbits)\n\n\u003c/div\u003e\u003c!-- end column--\u003e\n\n\u003cdiv class=\"column\"\u003e\n\n### Build with Gno\n\n- [Write Gno in the browser](https://play.gno.land)\n- [Read about the Gno Language](/gnolang)\n- [Visit the official documentation](https://docs.gno.land)\n- [Gno by Example](https://gno-by-example.com/)\n- [Efficient local development for Gno](https://docs.gno.land/gno-tooling/cli/gno-tooling-gnodev)\n- [Get testnet GNOTs](https://faucet.gno.land)\n\n\u003c/div\u003e\u003c!-- end column--\u003e\n\u003cdiv class=\"column\"\u003e\n\n### Explore the universe\n\n- [Discover demo packages](https://github.com/gnolang/gno/tree/master/examples)\n- [Gnoscan](https://gnoscan.io)\n- [Portal Loop](https://docs.gno.land/concepts/portal-loop)\n- [Testnet 4](https://test4.gno.land/)\n- Testnet Faucet Hub (soon)\n\n\u003c/div\u003e\u003c!-- end column--\u003e\n\u003c/div\u003e\u003c!-- end columns-3--\u003e`),\n\t}\n}\n\nfunc AdminSetOverride(content string) {\n\tadmin.AssertCallerIsOwner()\n\toverride = content\n}\n\nfunc AdminTransferOwnership(newAdmin std.Address) {\n\tadmin.AssertCallerIsOwner()\n\tadmin.TransferOwnership(newAdmin)\n}\n"},{"name":"home_filetest.gno","body":"package main\n\nimport \"gno.land/r/gnoland/home\"\n\nfunc main() {\n\tprintln(home.Render(\"\"))\n}\n\n// Output:\n// \u003cmain class='gno-tmpl-section'\u003e\n//\n// # Welcome to gno.land\n//\n// ### We’re building gno.land, set to become the leading open-source smart contract platform, using Gno, an interpreted and fully deterministic variation of the Go programming language for succinct and composable smart contracts.\n//\n//\n// With transparent and timeless code, gno.land is the next generation of smart contract platforms, serving as the “GitHub” of the ecosystem, with realms built using fully transparent, auditable code that anyone can inspect and reuse.\n//\n//\n// Intuitive and easy to use, gno.land lowers the barrier to web3 and makes censorship-resistant platforms accessible to everyone. If you want to help lay the foundations of a fairer and freer world, join us today.\n//\n// \u003cdiv class=\"jumbotron\"\u003e\n//\n// \u003cdiv class=\"columns-3\"\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Learn about gno.land\n//\n// - [About](/about)\n// - [GitHub](https://github.com/gnolang)\n// - [Blog](/blog)\n// - [Events](/events)\n// - Tokenomics (soon)\n// - [Partners, Fund, Grants](/partners)\n// - [Explore the Ecosystem](/ecosystem)\n// - [Careers](https://jobs.ashbyhq.com/allinbits)\n//\n// \u003c/div\u003e\u003c!-- end column--\u003e\n//\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Build with Gno\n//\n// - [Write Gno in the browser](https://play.gno.land)\n// - [Read about the Gno Language](/gnolang)\n// - [Visit the official documentation](https://docs.gno.land)\n// - [Gno by Example](https://gno-by-example.com/)\n// - [Efficient local development for Gno](https://docs.gno.land/gno-tooling/cli/gno-tooling-gnodev)\n// - [Get testnet GNOTs](https://faucet.gno.land)\n//\n// \u003c/div\u003e\u003c!-- end column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Explore the universe\n//\n// - [Discover demo packages](https://github.com/gnolang/gno/tree/master/examples)\n// - [Gnoscan](https://gnoscan.io)\n// - [Portal Loop](https://docs.gno.land/concepts/portal-loop)\n// - [Testnet 4](https://test4.gno.land/)\n// - Testnet Faucet Hub (soon)\n//\n// \u003c/div\u003e\u003c!-- end column--\u003e\n// \u003c/div\u003e\u003c!-- end columns-3--\u003e\n// \u003c/div\u003e\u003c!-- /jumbotron --\u003e\n//\n// \u003cdiv class=\"columns-3\"\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### [Latest Blogposts](/r/gnoland/blog)\n//\n// No posts.\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### [Latest Events](/r/gnoland/events)\n//\n// No events.\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Latest Contributions\n//\n// [View latest contributions](https://github.com/gnolang/gno/pulls)\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003c/div\u003e\u003c!-- /columns-3 --\u003e\n//\n//\n// ---\n//\n// ### [Gno Playground](https://play.gno.land)\n//\n//\n// Gno Playground is a web application designed for building, running, testing, and interacting\n// with your Gno code, enhancing your understanding of the Gno language. With Gno Playground, you can share your code,\n// execute tests, deploy your realms and packages to gno.land, and explore a multitude of other features.\n//\n//\n// Experience the convenience of code sharing and rapid experimentation with [Gno Playground](https://play.gno.land).\n//\n//\n// ---\n//\n// ### Explore New Packages and Realms\n//\n// \u003cdiv class=\"columns-3\"\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// #### [r/gnoland](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/gnoland)\n//\n// - [r/gnoland/blog](r/gnoland/blog)\n// - [r/gnoland/dao](r/gnoland/dao)\n// - [r/gnoland/faucet](r/gnoland/faucet)\n// - [r/gnoland/home](r/gnoland/home)\n// - [r/gnoland/pages](r/gnoland/pages)\n//\n// #### [r/sys](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/sys)\n//\n// - [r/sys/names](r/sys/names)\n// - [r/sys/rewards](r/sys/rewards)\n// - [/r/sys/validators/v2](/r/sys/validators/v2)\n//\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// #### [r/demo](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo)\n//\n// - [r/demo/boards](r/demo/boards)\n// - [r/demo/users](r/demo/users)\n// - [r/demo/banktest](r/demo/banktest)\n// - [r/demo/foo20](r/demo/foo20)\n// - [r/demo/foo721](r/demo/foo721)\n// - [r/demo/microblog](r/demo/microblog)\n// - [r/demo/nft](r/demo/nft)\n// - [r/demo/types](r/demo/types)\n// - [r/demo/art/gnoface](r/demo/art/gnoface)\n// - [r/demo/art/millipede](r/demo/art/millipede)\n// - [r/demo/groups](r/demo/groups)\n// - ...\n//\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// #### [p/demo](https://github.com/gnolang/gno/tree/master/examples/gno.land/p/demo)\n//\n// - [p/demo/avl](p/demo/avl)\n// - [p/demo/blog](p/demo/blog)\n// - [p/demo/ui](p/demo/ui)\n// - [p/demo/ufmt](p/demo/ufmt)\n// - [p/demo/merkle](p/demo/merkle)\n// - [p/demo/bf](p/demo/bf)\n// - [p/demo/flow](p/demo/flow)\n// - [p/demo/gnode](p/demo/gnode)\n// - [p/demo/grc/grc20](p/demo/grc/grc20)\n// - [p/demo/grc/grc721](p/demo/grc/grc721)\n// - ...\n//\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003c/div\u003e\u003c!-- /columns-3 --\u003e\n//\n//\n// ---\n//\n// ### Contributions (WorxDAO \u0026 GoR)\n//\n// coming soon\n//\n// ---\n//\n//\n// \u003cdiv class=\"columns-2\"\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Socials\n//\n// - Check out our [community projects](https://github.com/gnolang/awesome-gno)\n// - ![Discord](static/img/ico-discord.svg) [Discord](https://discord.gg/S8nKUqwkPn)\n// - ![Twitter](static/img/ico-twitter.svg) [Twitter](https://twitter.com/_gnoland)\n// - ![Youtube](static/img/ico-youtube.svg) [Youtube](https://www.youtube.com/@_gnoland)\n// - ![Telegram](static/img/ico-telegram.svg) [Telegram](https://t.me/gnoland)\n//\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Quote of the ~Day~ Block#123\n//\n// \u003e Now, you Gno.\n//\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003c/div\u003e\u003c!-- /columns-2 --\u003e\n//\n//\n// ---\n//\n// **This is a testnet.**\n// Package names are not guaranteed to be available for production.\n//\n// \u003c/main\u003e\n"},{"name":"overide_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/gnoland/home\"\n)\n\nfunc main() {\n\tstd.TestSetOriginCaller(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\thome.AdminSetOverride(\"Hello World!\")\n\tprintln(home.Render(\"\"))\n\thome.AdminTransferOwnership(testutils.TestAddress(\"newAdmin\"))\n\tdefer func() {\n\t\tr := recover()\n\t\tprintln(\"r: \", r)\n\t}()\n\thome.AdminSetOverride(\"Not admin anymore\")\n}\n\n// Output:\n// Hello World!\n// r: ownable: caller is not owner\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":"monit","path":"gno.land/r/gnoland/monit","files":[{"name":"monit.gno","body":"// Package monit links a monitoring system with the chain in both directions.\n//\n// The agent will periodically call Incr() and verify that the value is always\n// higher than the previously known one. The contract will store the last update\n// time and use it to detect whether or not the monitoring agent is functioning\n// correctly.\npackage monit\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/p/demo/watchdog\"\n)\n\nvar (\n\tcounter int\n\tlastUpdate time.Time\n\tlastCaller std.Address\n\twd = watchdog.Watchdog{Duration: 5 * time.Minute}\n\towner = ownable.New() // TODO: replace with -\u003e ownable.NewWithAddress...\n\twatchdogDuration = 5 * time.Minute\n)\n\n// Incr increments the counter and informs the watchdog that we're alive.\n// This function can be called by anyone.\nfunc Incr() int {\n\tcounter++\n\tlastUpdate = time.Now()\n\tlastCaller = std.PrevRealm().Addr()\n\twd.Alive()\n\treturn counter\n}\n\n// Reset resets the realm state.\n// This function can only be called by the admin.\nfunc Reset() {\n\tif owner.CallerIsOwner() != nil { // TODO: replace with owner.AssertCallerIsOwner\n\t\tpanic(\"unauthorized\")\n\t}\n\tcounter = 0\n\tlastCaller = std.PrevRealm().Addr()\n\tlastUpdate = time.Now()\n\twd = watchdog.Watchdog{Duration: 5 * time.Minute}\n}\n\nfunc Render(_ string) string {\n\tstatus := wd.Status()\n\treturn ufmt.Sprintf(\n\t\t\"counter=%d\\nlast update=%s\\nlast caller=%s\\nstatus=%s\",\n\t\tcounter, lastUpdate, lastCaller, status,\n\t)\n}\n\n// TransferOwnership transfers ownership to a new owner. This is a proxy to\n// ownable.Ownable.TransferOwnership.\nfunc TransferOwnership(newOwner std.Address) { owner.TransferOwnership(newOwner) }\n"},{"name":"monit_test.gno","body":"package monit\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestPackage(t *testing.T) {\n\t// initial state, watchdog is KO.\n\t{\n\t\texpected := `counter=0\nlast update=0001-01-01 00:00:00 +0000 UTC\nlast caller=\nstatus=KO`\n\t\tgot := Render(\"\")\n\t\tuassert.Equal(t, expected, got)\n\t}\n\n\t// call Incr(), watchdog is OK.\n\tIncr()\n\tIncr()\n\tIncr()\n\t{\n\t\texpected := `counter=3\nlast update=2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001\nlast caller=g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\nstatus=OK`\n\t\tgot := Render(\"\")\n\t\tuassert.Equal(t, expected, got)\n\t}\n\n\t/* XXX: improve tests once we've the missing std.TestSkipTime feature\n\t\t// wait 1h, watchdog is KO.\n\t\tuse std.TestSkipTime(time.Hour)\n\t\t{\n\t\t\texpected := `counter=3\n\tlast update=2009-02-13 22:31:30 +0000 UTC m=+1234564290.000000001\n\tlast caller=g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n\tstatus=KO`\n\t\t\tgot := Render(\"\")\n\t\t\tuassert.Equal(t, expected, got)\n\t\t}\n\n\t\t// call Incr(), watchdog is OK.\n\t\tIncr()\n\t\t{\n\t\t\texpected := `counter=4\n\tlast update=2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001\n\tlast caller=g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n\tstatus=OK`\n\t\t\tgot := Render(\"\")\n\t\t\tuassert.Equal(t, expected, got)\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":"gnopages","path":"gno.land/r/gnoland/pages","files":[{"name":"admin.gno","body":"package gnopages\n\nimport (\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\nvar (\n\tadminAddr std.Address\n\tmoderatorList avl.Tree\n\tinPause bool\n)\n\nfunc init() {\n\t// adminAddr = std.OrigCaller() // FIXME: find a way to use this from the main's genesis.\n\tadminAddr = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"\n}\n\nfunc AdminSetAdminAddr(addr std.Address) {\n\tassertIsAdmin()\n\tadminAddr = addr\n}\n\nfunc AdminSetInPause(state bool) {\n\tassertIsAdmin()\n\tinPause = state\n}\n\nfunc AdminAddModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), true)\n}\n\nfunc AdminRemoveModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), false) // XXX: delete instead?\n}\n\nfunc ModAddPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\tcaller := std.OrigCaller()\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.NewPost(caller, slug, title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc ModEditPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.GetPost(slug).Update(title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc isAdmin(addr std.Address) bool {\n\treturn addr == adminAddr\n}\n\nfunc isModerator(addr std.Address) bool {\n\t_, found := moderatorList.Get(addr.String())\n\treturn found\n}\n\nfunc assertIsAdmin() {\n\tcaller := std.OrigCaller()\n\tif !isAdmin(caller) {\n\t\tpanic(\"access restricted.\")\n\t}\n}\n\nfunc assertIsModerator() {\n\tcaller := std.OrigCaller()\n\tif isAdmin(caller) || isModerator(caller) {\n\t\treturn\n\t}\n\tpanic(\"access restricted\")\n}\n\nfunc assertNotInPause() {\n\tif inPause {\n\t\tpanic(\"access restricted (pause)\")\n\t}\n}\n"},{"name":"page_about.gno","body":"package gnopages\n\nfunc init() {\n\tpath := \"about\"\n\ttitle := \"gno.land Is A Platform To Write Smart Contracts In Gno\"\n\t// XXX: description := \"On gno.land, developers write smart contracts and other blockchain apps using Gno without learning a language that’s exclusive to a single ecosystem.\"\n\tbody := `\ngno.land is a next-generation smart contract platform using Gno, an interpreted version of the general-purpose Go\nprogramming language. On gno.land, smart contracts can be uploaded on-chain only by publishing their full source code,\nmaking it trivial to verify the contract or fork it into an improved version. With a system to publish reusable code\nlibraries on-chain, gno.land serves as the “GitHub” of the ecosystem, with realms built using fully transparent,\nauditable code that anyone can inspect and reuse.\n\ngno.land addresses many pressing issues in the blockchain space, starting with the ease of use and intuitiveness of\nsmart contract platforms. Developers can write smart contracts without having to learn a new language that’s exclusive\nto a single ecosystem or limited by design. Go developers can easily port their existing web apps to gno.land or build\nnew ones from scratch, making web3 vastly more accessible.\n\nSecured by Proof of Contribution (PoC), a DAO-managed Proof-of-Authority consensus mechanism, gno.land prioritizes\nfairness and merit, rewarding the people most active on the platform. PoC restructures the financial incentives that\noften corrupt blockchain projects, opting instead to reward contributors for their work based on expertise, commitment, and\nalignment.\n\nOne of our inspirations for gno.land is the gospels, which built a system of moral code that lasted thousands of years.\nBy observing a minimal production implementation, gno.land’s design will endure over time and serve as a reference for\nfuture generations with censorship-resistant tools that improve their understanding of the world.\n`\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:22Z\", nil, nil)\n}\n"},{"name":"page_contribute.gno","body":"package gnopages\n\nfunc init() {\n\tpath := \"contribute\"\n\ttitle := \"Contributor Ecosystem: Call for Contributions\"\n\tbody := `\n\ngno.land puts at the center of its identity the contributors that help to create and shape the project into what it is; incentivizing those who contribute the most and help advance its vision. Eventually, contributions will be incentivized directly on-chain; in the meantime, this page serves to illustrate our current off-chain initiatives.\n\ngno.land is still in full-steam development. For now, we're looking for the earliest of adopters; curious to explore a new way to build smart contracts and eager to make an impact. Joining gno.land's development now means you can help to shape the base of its development ecosystem, which will pave the way for the next generation of blockchain programming.\n\nAs an open-source project, we welcome all contributions. On this page you can find some pointers on where to get started; as well as some incentives for the most valuable and important contributions.\n\n## Where to get started\n\nIf you are interested in contributing to gno.land, you can jump on in on our [GitHub monorepo](https://github.com/gnolang/gno/blob/master/CONTRIBUTING.md) - where most development happens.\n\nA good place where to start are the issues tagged [\"good first issue\"](https://github.com/gnolang/gno/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22). They should allow you to make some impact on the Gno repository while you're still exploring the details of how everything works.\n\n## Gno Bounties\n\nAdditionally, you can look out to help on specific issues labeled as bounties. All contributions will then concur to form your profile for Game of Realms.\n\nThe Gno bounty program is a good way to find interesting challenges in Gno, and get rewarded for helping us advance the project. We will maintain open and rewardable bounties in the gnolang/gno repository, and you can search all available bounties by using the [\"bounty\" label](https://github.com/gnolang/gno/labels/bounty).\n\nRecommendations on participating in the gno.land Bounty Program:\n\n- Identify the bounty you want to work on, and join in the discussion on the issue for anything that is unclear; or where you want to more clearly define the work to be done. At this stage, you can also start working on an initial implementation in your local enviornment.\n- Once you have spent time on the code related to the bounty, we recommend submitting a 'draft' PR as soon as possible.\n - The draft PR doesn't indicate that the bounty has been assigned to you, others are free to work on other draft PRs for the bounty.\n - Make sure to reference the bounty issue on the PR description you're writing.\n - After submitting the 'draft' PR, continue working until you are ready to mark the PR as \"ready for review\".\n - The core team will review the bounty PR submission after the work on the bounty has been completed, and determine if it qualifies for the bounty reward.\n- Ask for clarification early if an element on the requirements or implementation design is unclear.\n - Aside from publishing the PR early, keeping regular updates with the core team on the bounty issue is key to being on the right track.\n - As part of the requirements, you must adhere to the [contributing guidelines](https://github.com/gnolang/gno/blob/master/CONTRIBUTING.md); additionally, it is expected that any newly added code or functionality is properly documented, tested and covered, at least in 80% of added code.\n - You're welcome to propose additional features and work on an issue should you envision a plausible expansion or change in scope. The core team may assign a bounty to the additional work, or change the bounty with respect to the changed scope.\n\nYou may make your submission at any time; however we invite you to publish your draft PR very early in the development process. This will make your work public, so you can easily get help by the core team and other community members. Additionally, your work can be continued by other people should you get stuck or no longer be willing to work on the bounty. Likewise, you can continue the abandoned or stuck work that someone else worked on.\n\nDon't fear your work being \"stolen\": if a submission is the result of multiple people's efforts, we will look to split the bounty in a way that is fair and recognises each participant in creating the final outcome. Here are some examples of how that can happen:\n\n- If Alice does most of the work and abandons it; then Bob comes around and finishes the job, then Bob's PR will be merged. But the core team will propose a split like 70% for Alice and 30% for Bob (depending, of course, on the relative effort undertaken by both).\n- If Alice makes a PR that does only 50% of the work outlined in the requirements for the original issue, she will get 50%. Someone can still come up and finish the job; and claim the remaining part.\n\t- If you, for instance, cannot complete the entirety of the task or, as a non-developer, can only contribute a part of the specification/implementation, you may still be awarded a bounty for your input in the contribution.\n- If Alice makes a PR that aside from implementing what's required, also undertakes creating useful tools among the way, she may qualify for an \"outstanding contribution\"; and may be awarded up to 25% more of the original bounty's value. Or she may also ask if the team would be willing to offer a different bounty for the implementation of the tools.\n\nParticipants in the gno.land Bounty Program must meet the legal Terms and Conditions referenced [here](https://docs.google.com/document/d/e/2PACX-1vSUF-JwIXGscrNsc5QBD7Pa6i83mXUGogAEIf1wkeb_w42UgL3Lj6jFKMlNTdwEMUnhsLkjRlhe25K4/pub).\n\n### Bounty sizes\n\nEach bounty is associated with a size, to which corresponds the maximum compensation for the work involved on the bounty. A bounty size may under rare occasion be revisited to a bigger or smaller size; hence why it's important to talk about your proposed solution with the core team ahead of time.\n\nIn some cases, the work associated with a bounty may be outstanding. When that happens, the core team can decide to award up to 25% of the bounty's value to the recipient.\n\nThe value of the bounty, aside from the material completion of the task, considers the involved time in managing the created pull request and iterating on feedback.\n\n\nt-shirt size | expected compensation\n-------------|-----------------------\n[XS] | $ 500\n[S] | $ 1000\n[M] | $ 2000\n[L] | $ 4000\n[XL] | $ 8000\n_[XXL]_ \\* | $ 16000\n_[3XL]_ \\* | $ 32000\n\n[XS]: https://github.com/gnolang/gno/labels/bounty%2FXS\n[S]: https://github.com/gnolang/gno/labels/bounty%2FS\n[M]: https://github.com/gnolang/gno/labels/bounty%2FM\n[L]: https://github.com/gnolang/gno/labels/bounty%2FL\n[XL]: https://github.com/gnolang/gno/labels/bounty%2FXL\n[XXL]: https://github.com/gnolang/gno/labels/bounty%2FXXL\n[3XL]: https://github.com/gnolang/gno/labels/bounty%2F3XL\n\n\\*: XXL and 3XL bounties are exceptional. Almost no issues will have these sizes; most will be broken down into smaller bounties.\n\n## gno.land Grants\n\nThe gno.land grants program is to encourage and support the growth of the gno.land contributor community, and build out the usability of the platform and smart contract library. The program provides financial resources to contributors to explore the Gno tech stack, and build dApps, tooling, infrastructure, products, and smart contract libraries in gno.land.\n\nFor more details on gno.land grants, suggested topics, and how to apply, visit our grants [repository](https://github.com/gnolang/grants). \n\n## Join Game of Realms\n\nGame of Realms is the overarching contributor network of gnomes, currently running off-chain, and will eventually transition on-chain. At this stage, a Game of Realms contribution is comprised of high-impact contributions identified as ['notable contributions'](https://github.com/gnolang/game-of-realms/tree/main/contributors).\n\nThese contributions are not linked to immediate financial rewards, but are notable in nature, in the sense they are a challenge, make a significant addition to the project, and require persistence, with minimal feedback loops from the core team.\n\nThe selection of a notable contribution or the sum of contributions that equal 'notable' is based on the impact it has on the development of the project. For now, it is focused on code contributions, and will evolve over time. The Gno development teams will initially qualify and evaluate notable contributions, and vote off-chain on adding them to the 'notable contributions' folder on GitHub.\n\nYou can always contribute to the project, and all contributions will be noticed. Contributing now is a way to build your personal contributor profile in gno.land early on in the ecosystem, and signal your commitment to the project, the community, and its future.\n\nThere are a variety of ways to make your contributions count:\n\n- Core code contributions\n- Realm and pure package development\n- Validator tooling\n- Developer tooling\n- Tutorials and documentation\n\nTo start, we recommend you create a PR in the Game of Realms [repository](https://github.com/gnolang/game-of-realms) to create your profile page for all your contributions.`\n\n\t_ = b.NewPost(\"\", path, title, body, \"2024-09-05T00:00:00Z\", nil, nil)\n}\n"},{"name":"page_ecosystem.gno","body":"package gnopages\n\nfunc init() {\n\tvar (\n\t\tpath = \"ecosystem\"\n\t\ttitle = \"Discover gno.land Ecosystem Projects \u0026 Initiatives\"\n\t\t// XXX: description = \"Dive further into the gno.land ecosystem and discover the core infrastructure, projects, smart contracts, and tooling we’re building.\"\n\t\tbody = `\n### [Gno Playground](https://play.gno.land)\n\nGno Playground is a simple web interface that lets you write, test, and experiment with your Gno code to improve your\nunderstanding of the Gno language. You can share your code, run unit tests, deploy your realms and packages, and execute\nfunctions in your code using the repo.\n\nVisit the playground at [play.gno.land](https://play.gno.land)!\n\n### [Gno Studio Connect](https://gno.studio/connect)\n\nGno Studio Connect provides seamless access to realms, making it simple to explore, interact, and engage\nwith gno.land’s smart contracts through function calls. Connect focuses on function calls, enabling users to interact\nwith any realm’s exposed function(s) on gno.land.\n\nSee your realm interactions in [Gno Studio Connect](https://gno.studio/connect)\n\n### [Gnoscan](https://gnoscan.io)\n\nDeveloped by the Onbloc team, Gnoscan is gno.land’s blockchain explorer. Anyone can use Gnoscan to easily find\ninformation that resides on the gno.land blockchain, such as wallet addresses, TX hashes, blocks, and contracts.\nGnoscan makes our on-chain data easy to read and intuitive to discover.\n\nExplore the gno.land blockchain at [gnoscan.io](https://gnoscan.io)!\n\n### Adena\n\nAdena is a user-friendly non-custodial wallet for gno.land. Open-source and developed by Onbloc, Adena allows gnomes to\ninteract easily with the chain. With an emphasis on UX, Adena is built to handle millions of realms and tokens with a\nhigh-quality interface, support for NFTs and custom tokens, and seamless integration. Install Adena via the [official website](https://www.adena.app/)\n\n### Gnoswap\n\nGnoswap is currently under development and led by the Onbloc team. Gnoswap will be the first DEX on gno.land and is an\nautomated market maker (AMM) protocol written in Gno that allows for permissionless token exchanges on the platform.\n\n### Flippando\n\nFlippando is a simple on-chain memory game, ported from Solidity to Gno, which starts with an empty matrix to flip tiles\non to see what’s underneath. If the tiles match, they remain uncovered; if not, they are briefly shown, and the player\nmust memorize their colors until the entire matrix is uncovered. The end result can be minted as an NFT, which can later\nbe assembled into bigger, more complex NFTs, creating a digital “painting” with the uncovered tiles. Play the game at [Flippando](https://gno.flippando.xyz/flip)\n\n### Gno Native Kit\n\n[Gno Native Kit](https://github.com/gnolang/gnonative) is a framework that allows developers to build and port gno.land (d)apps written in the (d)app's native language.\n\n\n`\n\t)\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:23Z\", nil, nil)\n}\n"},{"name":"page_gnolang.gno","body":"package gnopages\n\nfunc init() {\n\tvar (\n\t\tpath = \"gnolang\"\n\t\ttitle = \"About the Gno, the Language for gno.land\"\n\t\t// TODO fix broken images\n\t\tbody = `\n\n[Gno](https://github.com/gnolang/gno) is an interpretation of the widely-used Go (Golang) programming language for blockchain created by Cosmos co-founder Jae Kwon in 2022 to mark a new era in smart contracting. Gno is ~99% identical to Go, so Go programmers can start coding in Gno right away, with a minimal learning curve. For example, Gno comes with blockchain-specific standard libraries, but any code that doesn’t use blockchain-specific logic can run in Go with minimal processing. Libraries that don’t make sense in the blockchain context, such as network or operating-system access, are not available in Gno. Otherwise, Gno loads and uses many standard libraries that power Go, so most of the parsing of the source code is the same.\n\nUnder the hood, the Gno code is parsed into an abstract syntax tree (AST) and the AST itself is used in the interpreter, rather than bytecode as in many virtual machines such as Java, Python, or Wasm. This makes even the GnoVM accessible to any Go programmer. The novel design of the intuitive GnoVM interpreter allows Gno to freeze and resume the program by persisting and loading the entire memory state. Gno is deterministic, auto-persisted, and auto-Merkle-ized, allowing (smart contract) programs to be succinct, as the programmer doesn’t have to serialize and deserialize objects to persist them into a database (unlike programming applications with the Cosmos SDK).\n\n## How Gno Differs from Go\n\n![Gno and Go differences](static/img/gno-language/go-and-gno.jpg)\n\nThe composable nature of Go/Gno allows for type-checked interactions between contracts, making gno.land safer and more powerful, as well as operationally cheaper and faster. Smart contracts on gno.land are light, simple, more focused, and easily interoperable—a network of interconnected contracts rather than siloed monoliths that limit interactions with other contracts.\n\n![Example of Gno code](static/img/gno-language/code-example.jpg)\n\n## Gno Inherits Go’s Built-in Security Features\n\nGo supports secure programming through exported/non-exported fields, enabling a “least-authority” design. It is easy to create objects and APIs that expose only what should be accessible to callers while hiding what should not be simply by the capitalization of letters, thus allowing a succinct representation of secure logic that can be called by multiple users.\n\nAnother major advantage of Go is that the language comes with an ecosystem of great tooling, like the compiler and third-party tools that statically analyze code. Gno inherits these advantages from Go directly to create a smart contract programming language that provides embedding, composability, type-check safety, and garbage collection, helping developers to write secure code relying on the compiler, parser, and interpreter to give warning alerts for common mistakes.\n\n## Gno vs Solidity\n\nThe most widely-adopted smart contract language today is Ethereum’s EVM-compatible Solidity. With bytecode built from the ground up and Turing complete, Solidity opened up a world of possibilities for decentralized applications (dApps) and there are currently more than 10 million contracts deployed on Ethereum. However, Solidity provides limited tooling and its EVM has a stack limit and computational inefficiencies.\n\nSolidity is designed for one purpose only (writing smart contracts) and is bound by the limitations of the EVM. In addition, developers have to learn several languages if they want to understand the whole stack or work across different ecosystems. Gno aspires to exceed Solidity on multiple fronts (and other smart contract languages like CosmWasm or Substrate) as every part of the stack is written in Gno. It’s easy for developers to understand the entire system just by studying a relatively small code base.\n\n## Gno Is Essential for the Wider Adoption of Web3\n\nGno makes imports as easy as they are in web2 with runtime-based imports for seamless dependency flow comprehension, and support for complex structs, beyond primitive types. Gno is ultimately cost-effective as dependencies are loaded once, enabling remote function calls as local, and providing automatic and independent per-realm state persistence.\n\nUsing Gno, developers can rapidly accelerate application development and adopt a modular structure by reusing and reassembling existing modules without building from scratch. They can embed one structure inside another in an intuitive way while preserving localism, and the language specification is simple, successfully balancing practicality and minimalism.\n\nThe Go language is so well designed that the Gno smart contract system will become the new gold standard for smart contract development and other blockchain applications. As a programming language that is universally adopted, secure, composable, and complete, Gno is essential for the broader adoption of web3 and its sustainable growth.`\n\t)\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:25Z\", nil, nil)\n}\n"},{"name":"page_license.gno","body":"package gnopages\n\nfunc init() {\n\tvar (\n\t\tpath = \"license\"\n\t\ttitle = \"Gno Network General Public License\"\n\t\tbody = `Copyright (C) 2024 NewTendermint, LLC\n\nThis program is free software: you can redistribute it and/or modify it under\nthe terms of the GNO Network General Public License as published by\nNewTendermint, LLC, either version 4 of the License, or (at your option) any\nlater version published by NewTendermint, LLC.\n\nThis program is distributed in the hope that it will be useful, but is provided\nas-is and WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNO Network\nGeneral Public License for more details.\n\nYou should have received a copy of the GNO Network General Public License along\nwith this program. If not, see \u003chttps://gno.land/license\u003e.\n\nAttached below are the terms of the GNO Network General Public License, Version\n4 (a fork of the GNU Affero General Public License 3).\n\n## Additional Terms\n\n### Strong Attribution\n\nIf any of your user interfaces, such as websites and mobile applications, serve\nas the primary point of entry to a platform or blockchain that 1) offers users\nthe ability to upload their own smart contracts to the platform or blockchain,\nand 2) leverages any Covered Work (including the GNO virtual machine) to run\nthose smart contracts on the platform or blockchain (\"Applicable Work\"), then\nthe Applicable Work must prominently link to (1) gno.land or (2) any other URL\ndesignated by NewTendermint, LLC that has not been rejected by the governance of\nthe first chain known as gno.land, provided that the identity of the first chain\nis not ambiguous. In the event the identity of the first chain is ambiguous,\nthen NewTendermint, LLC's designation shall control. Such link must appear\nconspicuously in the header or footer of the Applicable Work, such that all\nusers may learn of gno.land or the URL designated by NewTendermint, LLC.\n\nThis additional attribution requirement shall remain in effect for (1) 7\nyears from the date of publication of the Applicable Work, or (2) 7 years from\nthe date of publication of the Covered Work (including republication of new\nversions), whichever is later, but no later than 12 years after the application\nof this strong attribution requirement to the publication of the Applicable\nWork. For purposes of this Strong Attribution requirement, Covered Work shall\nmean any work that is licensed under the GNO Network General Public License,\nVersion 4 or later, by NewTendermint, LLC.\n\n\n# GNO NETWORK GENERAL PUBLIC LICENSE\n\nVersion 4, 7 May 2024\n\nModified from the GNU AFFERO GENERAL PUBLIC LICENSE.\nGNU is not affiliated with GNO or NewTendermint, LLC.\nCopyright (C) 2022 NewTendermint, LLC.\n\n## Preamble\n\nThe GNO Network General Public License is a free, copyleft license for\nsoftware and other kinds of works, specifically designed to ensure\ncooperation with the community in the case of network server software.\n\nThe licenses for most software and other practical works are designed\nto take away your freedom to share and change the works. By contrast,\nour General Public Licenses are intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.\n\nWhen we speak of free software, we are referring to freedom, not\nprice. Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\nDevelopers that use our General Public Licenses protect your rights\nwith two steps: (1) assert copyright on the software, and (2) offer\nyou this License which gives you legal permission to copy, distribute\nand/or modify the software.\n\nA secondary benefit of defending all users' freedom is that\nimprovements made in alternate versions of the program, if they\nreceive widespread use, become available for other developers to\nincorporate. Many developers of free software are heartened and\nencouraged by the resulting cooperation. However, in the case of\nsoftware used on network servers, this result may fail to come about.\nThe GNU General Public License permits making a modified version and\nletting the public access it on a server without ever releasing its\nsource code to the public.\n\nThe GNO Network General Public License is designed specifically to\nensure that, in such cases, the modified source code becomes available\nto the community. It requires the operator of a network server to\nprovide the source code of the modified version running there to the\nusers of that server. Therefore, public use of a modified version, on\na publicly accessible server, gives the public access to the source\ncode of the modified version.\n\nThe precise terms and conditions for copying, distribution and\nmodification follow.\n\n## TERMS AND CONDITIONS\n\n### 0. Definitions.\n\n\"This License\" refers to version 4 of the GNO Network General Public License.\n\n\"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n\"The Program\" refers to any copyrightable work licensed under this\nLicense. Each licensee is addressed as \"you\". \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\nTo \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy. The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\nA \"covered work\" means either the unmodified Program or a work based\non the Program.\n\nTo \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy. Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\nTo \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies. Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\nAn interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License. If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n### 1. Source Code.\n\nThe \"source code\" for a work means the preferred form of the work\nfor making modifications to it. \"Object code\" means any non-source\nform of a work.\n\nA \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\nThe \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form. A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\nThe \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities. However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work. For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\nThe Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\nThe Corresponding Source for a work in source code form is that\nsame work.\n\n### 2. Basic Permissions.\n\nAll rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met. This License explicitly affirms your unlimited\npermission to run the unmodified Program. The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work. This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\nYou may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force. You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright. Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\nConveying under any other circumstances is permitted solely under\nthe conditions stated below. Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n### 3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\nNo covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\nWhen you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n### 4. Conveying Verbatim Copies.\n\nYou may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\nYou may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n### 5. Conveying Modified Source Versions.\n\nYou may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n- a) The work must carry prominent notices stating that you modified\n it, and giving a relevant date.\n- b) The work must carry prominent notices stating that it is\n released under this License and any conditions added under section\n 7. This requirement modifies the requirement in section 4 to\n \"keep intact all notices\".\n- c) You must license the entire work, as a whole, under this\n License to anyone who comes into possession of a copy. This\n License will therefore apply, along with any applicable section 7\n additional terms, to the whole of the work, and all its parts,\n regardless of how they are packaged. This License gives no\n permission to license the work in any other way, but it does not\n invalidate such permission if you have separately received it.\n- d) If the work has interactive user interfaces, each must display\n Appropriate Legal Notices; however, if the Program has interactive\n interfaces that do not display Appropriate Legal Notices, your\n work need not make them do so.\n\nA compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit. Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n### 6. Conveying Non-Source Forms.\n\n You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n- a) Convey the object code in, or embodied in, a physical product\n (including a physical distribution medium), accompanied by the\n Corresponding Source fixed on a durable physical medium\n customarily used for software interchange.\n- b) Convey the object code in, or embodied in, a physical product\n (including a physical distribution medium), accompanied by a\n written offer, valid for at least three years and valid for as\n long as you offer spare parts or customer support for that product\n model, to give anyone who possesses the object code either (1) a\n copy of the Corresponding Source for all the software in the\n product that is covered by this License, on a durable physical\n medium customarily used for software interchange, for a price no\n more than your reasonable cost of physically performing this\n conveying of source, or (2) access to copy the\n Corresponding Source from a network server at no charge.\n- c) Convey individual copies of the object code with a copy of the\n written offer to provide the Corresponding Source. This\n alternative is allowed only occasionally and noncommercially, and\n only if you received the object code with such an offer, in accord\n with subsection 6b.\n- d) Convey the object code by offering access from a designated\n place (gratis or for a charge), and offer equivalent access to the\n Corresponding Source in the same way through the same place at no\n further charge. You need not require recipients to copy the\n Corresponding Source along with the object code. If the place to\n copy the object code is a network server, the Corresponding Source\n may be on a different server (operated by you or a third party)\n that supports equivalent copying facilities, provided you maintain\n clear directions next to the object code saying where to find the\n Corresponding Source. Regardless of what server hosts the\n Corresponding Source, you remain obligated to ensure that it is\n available for as long as needed to satisfy these requirements.\n- e) Convey the object code using peer-to-peer transmission, provided\n you inform other peers where the object code and Corresponding\n Source of the work are being offered to the general public at no\n charge under subsection 6d.\n\nA separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\nA \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling. In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage. For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product. A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n\"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source. The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\nIf you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information. But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\nThe requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed. Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\nCorresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n### 7. Additional Terms.\n\n\"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law. If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\nWhen you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit. (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.) You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\nNotwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n- a) Disclaiming warranty or limiting liability differently from the\n terms of sections 15 and 16 of this License; or\n- b) Requiring preservation of specified reasonable legal notices or\n author attributions in that material or in the Appropriate Legal\n Notices displayed by works containing it; or\n- c) Prohibiting misrepresentation of the origin of that material, or\n requiring that modified versions of such material be marked in\n reasonable ways as different from the original version; or\n- d) Limiting the use for publicity purposes of names of licensors or\n authors of the material; or\n- e) Declining to grant rights under trademark law for use of some\n trade names, trademarks, or service marks; or\n- f) Requiring indemnification of licensors and authors of that\n material by anyone who conveys the material (or modified versions of\n it) with contractual assumptions of liability to the recipient, for\n any liability that these contractual assumptions directly impose on\n those licensors and authors; or\n- g) Requiring strong attribution such as notices on any user interfaces\n that run or convey any covered work, such as a prominent link to a URL\n on the header of a website, such that all users of the covered work may\n become aware of the notice, for a period no longer than 20 years.\n\nAll other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10. If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term. If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\nIf you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\nAdditional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n### 8. Termination.\n\nYou may not propagate or modify a covered work except as expressly\nprovided under this License. Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\nHowever, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\nMoreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\nTermination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License. If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n### 9. Acceptance Not Required for Having Copies.\n\nYou are not required to accept this License in order to receive or\nrun a copy of the Program. Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance. However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work. These actions infringe copyright if you do\nnot accept this License. Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n### 10. Automatic Licensing of Downstream Recipients.\n\nEach time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License. You are not responsible\nfor enforcing compliance by third parties with this License.\n\nAn \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations. If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\nYou may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License. For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n### 11. Patents.\n\nA \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based. The\nwork thus licensed is called the contributor's \"contributor version\".\n\nA contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version. For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\nEach contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\nIn the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement). To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\nIf you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients. \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\nIf, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\nA patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License. You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\nNothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n### 12. No Surrender of Others' Freedom.\n\nIf conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License. If you cannot convey a\ncovered work so as to simultaneously satisfy your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all. For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n### 13. Remote Network Interaction; Use with the GNU General Public License.\n\nNotwithstanding any other provision of this License, if you modify the\nProgram, your modified version must prominently offer all users\ninteracting with it remotely through a computer network (if your version\nsupports such interaction) an opportunity to receive the Corresponding\nSource of your version by providing access to the Corresponding Source\nfrom a network server at no charge, through some standard or customary\nmeans of facilitating copying of software. This Corresponding Source\nshall include the Corresponding Source for any work covered by version 3\nof the GNU General Public License that is incorporated pursuant to the\nfollowing paragraph.\n\nNotwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU General Public License into a single\ncombined work, and to convey the resulting work. The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the work with which it is combined will remain governed by version\n3 of the GNU General Public License.\n\n### 14. Revised Versions of this License.\n\nNewTendermint LLC may publish revised and/or new versions of\nthe GNO Network General Public License from time to time. Such new versions\nwill be similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\nEach version is given a distinguishing version number. If the\nProgram specifies that a certain numbered version of the GNO Network General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Gno Software\nFoundation. If the Program does not specify a version number of the\nGNO Network General Public License, you may choose any version ever published\nby NewTendermint LLC.\n\nIf the Program specifies that a proxy can decide which future\nversions of the GNO Network General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\nLater license versions may give you additional or different\npermissions. However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n### 15. Disclaimer of Warranty.\n\nTHERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n### 16. Limitation of Liability.\n\nIN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n### 17. Interpretation of Sections 15 and 16.\n\nIf the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\nEND OF TERMS AND CONDITIONS\n\n## How to Apply These Terms to Your New Programs\n\nIf you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\nTo do so, attach the following notices to the program. It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n \u003cone line to give the program's name and a brief idea of what it does.\u003e\n Copyright (C) \u003cyear\u003e \u003cname of author\u003e\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNO Network General Public License as published by\n NewTendermint LLC, either version 4 of the License, or\n (at your option) any later version.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNO Network General Public License for more details.\n\n You should have received a copy of the GNO Network General Public License\n along with this program. If not, see \u003chttps://gno.land/license\u003e.\n\nAlso add information on how to contact you by electronic and paper mail.\n\nIf your software can interact with users remotely through a computer\nnetwork, you should also make sure that it provides a way for users to\nget its source. For example, if your program is a web application, its\ninterface could display a \"Source\" link that leads users to an archive\nof the code. There are many ways you could offer source, and different\nsolutions will be better for different programs; see section 13 for the\nspecific requirements.\n`\n\t)\n\t_ = b.NewPost(\"\", path, title, body, \"2024-04-22T00:00:00Z\", nil, nil)\n}\n"},{"name":"page_partners.gno","body":"package gnopages\n\nfunc init() {\n\tpath := \"partners\"\n\ttitle := \"Partnerships\"\n\tbody := `### Fund and Grants Program\n\nAre you a builder, tinkerer, or researcher? If you’re looking to create awesome dApps, tooling, infrastructure, \nor smart contract libraries on gno.land, you can apply for a grant. The gno.land Ecosystem Fund and Grants program \nprovides financial contributions for individuals and teams to innovate on the platform.\n\nRead more about our Funds and Grants program [here](https://github.com/gnolang/ecosystem-fund-grants).\n`\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:27Z\", nil, nil)\n}\n"},{"name":"page_start.gno","body":"package gnopages\n\nfunc init() {\n\tpath := \"start\"\n\ttitle := \"Getting Started with Gno\"\n\t// XXX: description := \"\"\n\n\t// TODO: codegen to use README files here\n\n\t/* TODO: port previous message: This is a demo of Gno smart contract programming. This document was\n\tconstructed by Gno onto a smart contract hosted on the data Realm\n\tname [\"gno.land/r/demo/boards\"](https://gno.land/r/demo/boards/)\n\t([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)).\n\t*/\n\tbody := `## Getting Started with Gno\n\n- [Install Gno Key](/r/demo/boards:testboard/5)\n- TODO: add more links\n`\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:28Z\", nil, nil)\n}\n"},{"name":"page_testnets.gno","body":"package gnopages\n\nfunc init() {\n\tpath := \"testnets\"\n\ttitle := \"gno.land Testnet List\"\n\tbody := `\n- [Portal Loop](https://docs.gno.land/concepts/portal-loop) - a rolling testnet\n- [staging.gno.land](https://staging.gno.land) - wiped every commit to monorepo master\n- _[test4.gno.land](https://test4.gno.land) (latest)_\n\nFor a list of RPC endpoints, see the [reference documentation](https://docs.gno.land/reference/rpc-endpoints).\n\n## Local development\n\nSee the \"Getting started\" section in the [official documentation](https://docs.gno.land/getting-started/local-setup).\n`\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:29Z\", nil, nil)\n}\n"},{"name":"page_tokenomics.gno","body":"package gnopages\n\nfunc init() {\n\tvar (\n\t\tpath = \"tokenomics\"\n\t\ttitle = \"gno.land Tokenomics\"\n\t\t// XXX: description = \"\"\"\n\t\tbody = `Lorem Ipsum`\n\t)\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:30Z\", nil, nil)\n}\n"},{"name":"pages.gno","body":"package gnopages\n\nimport (\n\t\"gno.land/p/demo/blog\"\n)\n\n// TODO: switch from p/blog to p/pages\n\nvar b = \u0026blog.Blog{\n\tTitle: \"Gnoland's Pages\",\n\tPrefix: \"/r/gnoland/pages:\",\n\tNoBreadcrumb: true,\n}\n\nfunc Render(path string) string {\n\treturn b.Render(path)\n}\n"},{"name":"pages_test.gno","body":"package gnopages\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestHome(t *testing.T) {\n\tprintedOnce := false\n\tgot := Render(\"\")\n\texpectedSubtrings := []string{\n\t\t\"/r/gnoland/pages:p/tokenomics\",\n\t\t\"/r/gnoland/pages:p/start\",\n\t\t\"/r/gnoland/pages:p/contribute\",\n\t\t\"/r/gnoland/pages:p/about\",\n\t\t\"/r/gnoland/pages:p/gnolang\",\n\t}\n\tfor _, substring := range expectedSubtrings {\n\t\tif !strings.Contains(got, substring) {\n\t\t\tif !printedOnce {\n\t\t\t\tprintln(got)\n\t\t\t\tprintedOnce = true\n\t\t\t}\n\t\t\tt.Errorf(\"expected %q, but not found.\", substring)\n\t\t}\n\t}\n}\n\nfunc TestAbout(t *testing.T) {\n\tprintedOnce := false\n\tgot := Render(\"p/about\")\n\texpectedSubtrings := []string{\n\t\t\"gno.land Is A Platform To Write Smart Contracts In Gno\",\n\t\t\"gno.land is a next-generation smart contract platform using Gno, an interpreted version of the general-purpose Go\\nprogramming language.\",\n\t}\n\tfor _, substring := range expectedSubtrings {\n\t\tif !strings.Contains(got, substring) {\n\t\t\tif !printedOnce {\n\t\t\t\tprintln(got)\n\t\t\t\tprintedOnce = true\n\t\t\t}\n\t\t\tt.Errorf(\"expected %q, but not found.\", substring)\n\t\t}\n\t}\n}\n"},{"name":"util.gno","body":"package gnopages\n\nfunc checkErr(err error) {\n\tif err != nil {\n\t\tpanic(err)\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":"gnopages","path":"gno.land/r/gnoland/pages","files":[{"name":"admin.gno","body":"package gnopages\n\nimport (\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\nvar (\n\tadminAddr std.Address\n\tmoderatorList avl.Tree\n\tinPause bool\n)\n\nfunc init() {\n\t// adminAddr = std.OriginCaller() // FIXME: find a way to use this from the main's genesis.\n\tadminAddr = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"\n}\n\nfunc AdminSetAdminAddr(addr std.Address) {\n\tassertIsAdmin()\n\tadminAddr = addr\n}\n\nfunc AdminSetInPause(state bool) {\n\tassertIsAdmin()\n\tinPause = state\n}\n\nfunc AdminAddModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), true)\n}\n\nfunc AdminRemoveModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), false) // XXX: delete instead?\n}\n\nfunc ModAddPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\tcaller := std.OriginCaller()\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.NewPost(caller, slug, title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc ModEditPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.GetPost(slug).Update(title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc isAdmin(addr std.Address) bool {\n\treturn addr == adminAddr\n}\n\nfunc isModerator(addr std.Address) bool {\n\t_, found := moderatorList.Get(addr.String())\n\treturn found\n}\n\nfunc assertIsAdmin() {\n\tcaller := std.OriginCaller()\n\tif !isAdmin(caller) {\n\t\tpanic(\"access restricted.\")\n\t}\n}\n\nfunc assertIsModerator() {\n\tcaller := std.OriginCaller()\n\tif isAdmin(caller) || isModerator(caller) {\n\t\treturn\n\t}\n\tpanic(\"access restricted\")\n}\n\nfunc assertNotInPause() {\n\tif inPause {\n\t\tpanic(\"access restricted (pause)\")\n\t}\n}\n"},{"name":"page_about.gno","body":"package gnopages\n\nfunc init() {\n\tpath := \"about\"\n\ttitle := \"gno.land Is A Platform To Write Smart Contracts In Gno\"\n\t// XXX: description := \"On gno.land, developers write smart contracts and other blockchain apps using Gno without learning a language that’s exclusive to a single ecosystem.\"\n\tbody := `\ngno.land is a next-generation smart contract platform using Gno, an interpreted version of the general-purpose Go\nprogramming language. On gno.land, smart contracts can be uploaded on-chain only by publishing their full source code,\nmaking it trivial to verify the contract or fork it into an improved version. With a system to publish reusable code\nlibraries on-chain, gno.land serves as the “GitHub” of the ecosystem, with realms built using fully transparent,\nauditable code that anyone can inspect and reuse.\n\ngno.land addresses many pressing issues in the blockchain space, starting with the ease of use and intuitiveness of\nsmart contract platforms. Developers can write smart contracts without having to learn a new language that’s exclusive\nto a single ecosystem or limited by design. Go developers can easily port their existing web apps to gno.land or build\nnew ones from scratch, making web3 vastly more accessible.\n\nSecured by Proof of Contribution (PoC), a DAO-managed Proof-of-Authority consensus mechanism, gno.land prioritizes\nfairness and merit, rewarding the people most active on the platform. PoC restructures the financial incentives that\noften corrupt blockchain projects, opting instead to reward contributors for their work based on expertise, commitment, and\nalignment.\n\nOne of our inspirations for gno.land is the gospels, which built a system of moral code that lasted thousands of years.\nBy observing a minimal production implementation, gno.land’s design will endure over time and serve as a reference for\nfuture generations with censorship-resistant tools that improve their understanding of the world.\n`\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:22Z\", nil, nil)\n}\n"},{"name":"page_contribute.gno","body":"package gnopages\n\nfunc init() {\n\tpath := \"contribute\"\n\ttitle := \"Contributor Ecosystem: Call for Contributions\"\n\tbody := `\n\ngno.land puts at the center of its identity the contributors that help to create and shape the project into what it is; incentivizing those who contribute the most and help advance its vision. Eventually, contributions will be incentivized directly on-chain; in the meantime, this page serves to illustrate our current off-chain initiatives.\n\ngno.land is still in full-steam development. For now, we're looking for the earliest of adopters; curious to explore a new way to build smart contracts and eager to make an impact. Joining gno.land's development now means you can help to shape the base of its development ecosystem, which will pave the way for the next generation of blockchain programming.\n\nAs an open-source project, we welcome all contributions. On this page you can find some pointers on where to get started; as well as some incentives for the most valuable and important contributions.\n\n## Where to get started\n\nIf you are interested in contributing to gno.land, you can jump on in on our [GitHub monorepo](https://github.com/gnolang/gno/blob/master/CONTRIBUTING.md) - where most development happens.\n\nA good place where to start are the issues tagged [\"good first issue\"](https://github.com/gnolang/gno/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22). They should allow you to make some impact on the Gno repository while you're still exploring the details of how everything works.\n\n## Gno Bounties\n\nAdditionally, you can look out to help on specific issues labeled as bounties. All contributions will then concur to form your profile for Game of Realms.\n\nThe Gno bounty program is a good way to find interesting challenges in Gno, and get rewarded for helping us advance the project. We will maintain open and rewardable bounties in the gnolang/gno repository, and you can search all available bounties by using the [\"bounty\" label](https://github.com/gnolang/gno/labels/bounty).\n\nRecommendations on participating in the gno.land Bounty Program:\n\n- Identify the bounty you want to work on, and join in the discussion on the issue for anything that is unclear; or where you want to more clearly define the work to be done. At this stage, you can also start working on an initial implementation in your local enviornment.\n- Once you have spent time on the code related to the bounty, we recommend submitting a 'draft' PR as soon as possible.\n - The draft PR doesn't indicate that the bounty has been assigned to you, others are free to work on other draft PRs for the bounty.\n - Make sure to reference the bounty issue on the PR description you're writing.\n - After submitting the 'draft' PR, continue working until you are ready to mark the PR as \"ready for review\".\n - The core team will review the bounty PR submission after the work on the bounty has been completed, and determine if it qualifies for the bounty reward.\n- Ask for clarification early if an element on the requirements or implementation design is unclear.\n - Aside from publishing the PR early, keeping regular updates with the core team on the bounty issue is key to being on the right track.\n - As part of the requirements, you must adhere to the [contributing guidelines](https://github.com/gnolang/gno/blob/master/CONTRIBUTING.md); additionally, it is expected that any newly added code or functionality is properly documented, tested and covered, at least in 80% of added code.\n - You're welcome to propose additional features and work on an issue should you envision a plausible expansion or change in scope. The core team may assign a bounty to the additional work, or change the bounty with respect to the changed scope.\n\nYou may make your submission at any time; however we invite you to publish your draft PR very early in the development process. This will make your work public, so you can easily get help by the core team and other community members. Additionally, your work can be continued by other people should you get stuck or no longer be willing to work on the bounty. Likewise, you can continue the abandoned or stuck work that someone else worked on.\n\nDon't fear your work being \"stolen\": if a submission is the result of multiple people's efforts, we will look to split the bounty in a way that is fair and recognises each participant in creating the final outcome. Here are some examples of how that can happen:\n\n- If Alice does most of the work and abandons it; then Bob comes around and finishes the job, then Bob's PR will be merged. But the core team will propose a split like 70% for Alice and 30% for Bob (depending, of course, on the relative effort undertaken by both).\n- If Alice makes a PR that does only 50% of the work outlined in the requirements for the original issue, she will get 50%. Someone can still come up and finish the job; and claim the remaining part.\n\t- If you, for instance, cannot complete the entirety of the task or, as a non-developer, can only contribute a part of the specification/implementation, you may still be awarded a bounty for your input in the contribution.\n- If Alice makes a PR that aside from implementing what's required, also undertakes creating useful tools among the way, she may qualify for an \"outstanding contribution\"; and may be awarded up to 25% more of the original bounty's value. Or she may also ask if the team would be willing to offer a different bounty for the implementation of the tools.\n\nParticipants in the gno.land Bounty Program must meet the legal Terms and Conditions referenced [here](https://docs.google.com/document/d/e/2PACX-1vSUF-JwIXGscrNsc5QBD7Pa6i83mXUGogAEIf1wkeb_w42UgL3Lj6jFKMlNTdwEMUnhsLkjRlhe25K4/pub).\n\n### Bounty sizes\n\nEach bounty is associated with a size, to which corresponds the maximum compensation for the work involved on the bounty. A bounty size may under rare occasion be revisited to a bigger or smaller size; hence why it's important to talk about your proposed solution with the core team ahead of time.\n\nIn some cases, the work associated with a bounty may be outstanding. When that happens, the core team can decide to award up to 25% of the bounty's value to the recipient.\n\nThe value of the bounty, aside from the material completion of the task, considers the involved time in managing the created pull request and iterating on feedback.\n\n\nt-shirt size | expected compensation\n-------------|-----------------------\n[XS] | $ 500\n[S] | $ 1000\n[M] | $ 2000\n[L] | $ 4000\n[XL] | $ 8000\n_[XXL]_ \\* | $ 16000\n_[3XL]_ \\* | $ 32000\n\n[XS]: https://github.com/gnolang/gno/labels/bounty%2FXS\n[S]: https://github.com/gnolang/gno/labels/bounty%2FS\n[M]: https://github.com/gnolang/gno/labels/bounty%2FM\n[L]: https://github.com/gnolang/gno/labels/bounty%2FL\n[XL]: https://github.com/gnolang/gno/labels/bounty%2FXL\n[XXL]: https://github.com/gnolang/gno/labels/bounty%2FXXL\n[3XL]: https://github.com/gnolang/gno/labels/bounty%2F3XL\n\n\\*: XXL and 3XL bounties are exceptional. Almost no issues will have these sizes; most will be broken down into smaller bounties.\n\n## gno.land Grants\n\nThe gno.land grants program is to encourage and support the growth of the gno.land contributor community, and build out the usability of the platform and smart contract library. The program provides financial resources to contributors to explore the Gno tech stack, and build dApps, tooling, infrastructure, products, and smart contract libraries in gno.land.\n\nFor more details on gno.land grants, suggested topics, and how to apply, visit our grants [repository](https://github.com/gnolang/grants). \n\n## Join Game of Realms\n\nGame of Realms is the overarching contributor network of gnomes, currently running off-chain, and will eventually transition on-chain. At this stage, a Game of Realms contribution is comprised of high-impact contributions identified as ['notable contributions'](https://github.com/gnolang/game-of-realms/tree/main/contributors).\n\nThese contributions are not linked to immediate financial rewards, but are notable in nature, in the sense they are a challenge, make a significant addition to the project, and require persistence, with minimal feedback loops from the core team.\n\nThe selection of a notable contribution or the sum of contributions that equal 'notable' is based on the impact it has on the development of the project. For now, it is focused on code contributions, and will evolve over time. The Gno development teams will initially qualify and evaluate notable contributions, and vote off-chain on adding them to the 'notable contributions' folder on GitHub.\n\nYou can always contribute to the project, and all contributions will be noticed. Contributing now is a way to build your personal contributor profile in gno.land early on in the ecosystem, and signal your commitment to the project, the community, and its future.\n\nThere are a variety of ways to make your contributions count:\n\n- Core code contributions\n- Realm and pure package development\n- Validator tooling\n- Developer tooling\n- Tutorials and documentation\n\nTo start, we recommend you create a PR in the Game of Realms [repository](https://github.com/gnolang/game-of-realms) to create your profile page for all your contributions.`\n\n\t_ = b.NewPost(\"\", path, title, body, \"2024-09-05T00:00:00Z\", nil, nil)\n}\n"},{"name":"page_ecosystem.gno","body":"package gnopages\n\nfunc init() {\n\tvar (\n\t\tpath = \"ecosystem\"\n\t\ttitle = \"Discover gno.land Ecosystem Projects \u0026 Initiatives\"\n\t\t// XXX: description = \"Dive further into the gno.land ecosystem and discover the core infrastructure, projects, smart contracts, and tooling we’re building.\"\n\t\tbody = `\n### [Gno Playground](https://play.gno.land)\n\nGno Playground is a simple web interface that lets you write, test, and experiment with your Gno code to improve your\nunderstanding of the Gno language. You can share your code, run unit tests, deploy your realms and packages, and execute\nfunctions in your code using the repo.\n\nVisit the playground at [play.gno.land](https://play.gno.land)!\n\n### [Gno Studio Connect](https://gno.studio/connect)\n\nGno Studio Connect provides seamless access to realms, making it simple to explore, interact, and engage\nwith gno.land’s smart contracts through function calls. Connect focuses on function calls, enabling users to interact\nwith any realm’s exposed function(s) on gno.land.\n\nSee your realm interactions in [Gno Studio Connect](https://gno.studio/connect)\n\n### [Gnoscan](https://gnoscan.io)\n\nDeveloped by the Onbloc team, Gnoscan is gno.land’s blockchain explorer. Anyone can use Gnoscan to easily find\ninformation that resides on the gno.land blockchain, such as wallet addresses, TX hashes, blocks, and contracts.\nGnoscan makes our on-chain data easy to read and intuitive to discover.\n\nExplore the gno.land blockchain at [gnoscan.io](https://gnoscan.io)!\n\n### Adena\n\nAdena is a user-friendly non-custodial wallet for gno.land. Open-source and developed by Onbloc, Adena allows gnomes to\ninteract easily with the chain. With an emphasis on UX, Adena is built to handle millions of realms and tokens with a\nhigh-quality interface, support for NFTs and custom tokens, and seamless integration. Install Adena via the [official website](https://www.adena.app/)\n\n### Gnoswap\n\nGnoswap is currently under development and led by the Onbloc team. Gnoswap will be the first DEX on gno.land and is an\nautomated market maker (AMM) protocol written in Gno that allows for permissionless token exchanges on the platform.\n\n### Flippando\n\nFlippando is a simple on-chain memory game, ported from Solidity to Gno, which starts with an empty matrix to flip tiles\non to see what’s underneath. If the tiles match, they remain uncovered; if not, they are briefly shown, and the player\nmust memorize their colors until the entire matrix is uncovered. The end result can be minted as an NFT, which can later\nbe assembled into bigger, more complex NFTs, creating a digital “painting” with the uncovered tiles. Play the game at [Flippando](https://gno.flippando.xyz/flip)\n\n### Gno Native Kit\n\n[Gno Native Kit](https://github.com/gnolang/gnonative) is a framework that allows developers to build and port gno.land (d)apps written in the (d)app's native language.\n\n\n`\n\t)\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:23Z\", nil, nil)\n}\n"},{"name":"page_gnolang.gno","body":"package gnopages\n\nfunc init() {\n\tvar (\n\t\tpath = \"gnolang\"\n\t\ttitle = \"About the Gno, the Language for gno.land\"\n\t\t// TODO fix broken images\n\t\tbody = `\n\n[Gno](https://github.com/gnolang/gno) is an interpretation of the widely-used Go (Golang) programming language for blockchain created by Cosmos co-founder Jae Kwon in 2022 to mark a new era in smart contracting. Gno is ~99% identical to Go, so Go programmers can start coding in Gno right away, with a minimal learning curve. For example, Gno comes with blockchain-specific standard libraries, but any code that doesn’t use blockchain-specific logic can run in Go with minimal processing. Libraries that don’t make sense in the blockchain context, such as network or operating-system access, are not available in Gno. Otherwise, Gno loads and uses many standard libraries that power Go, so most of the parsing of the source code is the same.\n\nUnder the hood, the Gno code is parsed into an abstract syntax tree (AST) and the AST itself is used in the interpreter, rather than bytecode as in many virtual machines such as Java, Python, or Wasm. This makes even the GnoVM accessible to any Go programmer. The novel design of the intuitive GnoVM interpreter allows Gno to freeze and resume the program by persisting and loading the entire memory state. Gno is deterministic, auto-persisted, and auto-Merkle-ized, allowing (smart contract) programs to be succinct, as the programmer doesn’t have to serialize and deserialize objects to persist them into a database (unlike programming applications with the Cosmos SDK).\n\n## How Gno Differs from Go\n\n![Gno and Go differences](static/img/gno-language/go-and-gno.jpg)\n\nThe composable nature of Go/Gno allows for type-checked interactions between contracts, making gno.land safer and more powerful, as well as operationally cheaper and faster. Smart contracts on gno.land are light, simple, more focused, and easily interoperable—a network of interconnected contracts rather than siloed monoliths that limit interactions with other contracts.\n\n![Example of Gno code](static/img/gno-language/code-example.jpg)\n\n## Gno Inherits Go’s Built-in Security Features\n\nGo supports secure programming through exported/non-exported fields, enabling a “least-authority” design. It is easy to create objects and APIs that expose only what should be accessible to callers while hiding what should not be simply by the capitalization of letters, thus allowing a succinct representation of secure logic that can be called by multiple users.\n\nAnother major advantage of Go is that the language comes with an ecosystem of great tooling, like the compiler and third-party tools that statically analyze code. Gno inherits these advantages from Go directly to create a smart contract programming language that provides embedding, composability, type-check safety, and garbage collection, helping developers to write secure code relying on the compiler, parser, and interpreter to give warning alerts for common mistakes.\n\n## Gno vs Solidity\n\nThe most widely-adopted smart contract language today is Ethereum’s EVM-compatible Solidity. With bytecode built from the ground up and Turing complete, Solidity opened up a world of possibilities for decentralized applications (dApps) and there are currently more than 10 million contracts deployed on Ethereum. However, Solidity provides limited tooling and its EVM has a stack limit and computational inefficiencies.\n\nSolidity is designed for one purpose only (writing smart contracts) and is bound by the limitations of the EVM. In addition, developers have to learn several languages if they want to understand the whole stack or work across different ecosystems. Gno aspires to exceed Solidity on multiple fronts (and other smart contract languages like CosmWasm or Substrate) as every part of the stack is written in Gno. It’s easy for developers to understand the entire system just by studying a relatively small code base.\n\n## Gno Is Essential for the Wider Adoption of Web3\n\nGno makes imports as easy as they are in web2 with runtime-based imports for seamless dependency flow comprehension, and support for complex structs, beyond primitive types. Gno is ultimately cost-effective as dependencies are loaded once, enabling remote function calls as local, and providing automatic and independent per-realm state persistence.\n\nUsing Gno, developers can rapidly accelerate application development and adopt a modular structure by reusing and reassembling existing modules without building from scratch. They can embed one structure inside another in an intuitive way while preserving localism, and the language specification is simple, successfully balancing practicality and minimalism.\n\nThe Go language is so well designed that the Gno smart contract system will become the new gold standard for smart contract development and other blockchain applications. As a programming language that is universally adopted, secure, composable, and complete, Gno is essential for the broader adoption of web3 and its sustainable growth.`\n\t)\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:25Z\", nil, nil)\n}\n"},{"name":"page_license.gno","body":"package gnopages\n\nfunc init() {\n\tvar (\n\t\tpath = \"license\"\n\t\ttitle = \"Gno Network General Public License\"\n\t\tbody = `Copyright (C) 2024 NewTendermint, LLC\n\nThis program is free software: you can redistribute it and/or modify it under\nthe terms of the GNO Network General Public License as published by\nNewTendermint, LLC, either version 4 of the License, or (at your option) any\nlater version published by NewTendermint, LLC.\n\nThis program is distributed in the hope that it will be useful, but is provided\nas-is and WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNO Network\nGeneral Public License for more details.\n\nYou should have received a copy of the GNO Network General Public License along\nwith this program. If not, see \u003chttps://gno.land/license\u003e.\n\nAttached below are the terms of the GNO Network General Public License, Version\n4 (a fork of the GNU Affero General Public License 3).\n\n## Additional Terms\n\n### Strong Attribution\n\nIf any of your user interfaces, such as websites and mobile applications, serve\nas the primary point of entry to a platform or blockchain that 1) offers users\nthe ability to upload their own smart contracts to the platform or blockchain,\nand 2) leverages any Covered Work (including the GNO virtual machine) to run\nthose smart contracts on the platform or blockchain (\"Applicable Work\"), then\nthe Applicable Work must prominently link to (1) gno.land or (2) any other URL\ndesignated by NewTendermint, LLC that has not been rejected by the governance of\nthe first chain known as gno.land, provided that the identity of the first chain\nis not ambiguous. In the event the identity of the first chain is ambiguous,\nthen NewTendermint, LLC's designation shall control. Such link must appear\nconspicuously in the header or footer of the Applicable Work, such that all\nusers may learn of gno.land or the URL designated by NewTendermint, LLC.\n\nThis additional attribution requirement shall remain in effect for (1) 7\nyears from the date of publication of the Applicable Work, or (2) 7 years from\nthe date of publication of the Covered Work (including republication of new\nversions), whichever is later, but no later than 12 years after the application\nof this strong attribution requirement to the publication of the Applicable\nWork. For purposes of this Strong Attribution requirement, Covered Work shall\nmean any work that is licensed under the GNO Network General Public License,\nVersion 4 or later, by NewTendermint, LLC.\n\n\n# GNO NETWORK GENERAL PUBLIC LICENSE\n\nVersion 4, 7 May 2024\n\nModified from the GNU AFFERO GENERAL PUBLIC LICENSE.\nGNU is not affiliated with GNO or NewTendermint, LLC.\nCopyright (C) 2022 NewTendermint, LLC.\n\n## Preamble\n\nThe GNO Network General Public License is a free, copyleft license for\nsoftware and other kinds of works, specifically designed to ensure\ncooperation with the community in the case of network server software.\n\nThe licenses for most software and other practical works are designed\nto take away your freedom to share and change the works. By contrast,\nour General Public Licenses are intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.\n\nWhen we speak of free software, we are referring to freedom, not\nprice. Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\nDevelopers that use our General Public Licenses protect your rights\nwith two steps: (1) assert copyright on the software, and (2) offer\nyou this License which gives you legal permission to copy, distribute\nand/or modify the software.\n\nA secondary benefit of defending all users' freedom is that\nimprovements made in alternate versions of the program, if they\nreceive widespread use, become available for other developers to\nincorporate. Many developers of free software are heartened and\nencouraged by the resulting cooperation. However, in the case of\nsoftware used on network servers, this result may fail to come about.\nThe GNU General Public License permits making a modified version and\nletting the public access it on a server without ever releasing its\nsource code to the public.\n\nThe GNO Network General Public License is designed specifically to\nensure that, in such cases, the modified source code becomes available\nto the community. It requires the operator of a network server to\nprovide the source code of the modified version running there to the\nusers of that server. Therefore, public use of a modified version, on\na publicly accessible server, gives the public access to the source\ncode of the modified version.\n\nThe precise terms and conditions for copying, distribution and\nmodification follow.\n\n## TERMS AND CONDITIONS\n\n### 0. Definitions.\n\n\"This License\" refers to version 4 of the GNO Network General Public License.\n\n\"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n\"The Program\" refers to any copyrightable work licensed under this\nLicense. Each licensee is addressed as \"you\". \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\nTo \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy. The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\nA \"covered work\" means either the unmodified Program or a work based\non the Program.\n\nTo \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy. Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\nTo \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies. Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\nAn interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License. If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n### 1. Source Code.\n\nThe \"source code\" for a work means the preferred form of the work\nfor making modifications to it. \"Object code\" means any non-source\nform of a work.\n\nA \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\nThe \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form. A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\nThe \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities. However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work. For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\nThe Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\nThe Corresponding Source for a work in source code form is that\nsame work.\n\n### 2. Basic Permissions.\n\nAll rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met. This License explicitly affirms your unlimited\npermission to run the unmodified Program. The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work. This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\nYou may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force. You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright. Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\nConveying under any other circumstances is permitted solely under\nthe conditions stated below. Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n### 3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\nNo covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\nWhen you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n### 4. Conveying Verbatim Copies.\n\nYou may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\nYou may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n### 5. Conveying Modified Source Versions.\n\nYou may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n- a) The work must carry prominent notices stating that you modified\n it, and giving a relevant date.\n- b) The work must carry prominent notices stating that it is\n released under this License and any conditions added under section\n 7. This requirement modifies the requirement in section 4 to\n \"keep intact all notices\".\n- c) You must license the entire work, as a whole, under this\n License to anyone who comes into possession of a copy. This\n License will therefore apply, along with any applicable section 7\n additional terms, to the whole of the work, and all its parts,\n regardless of how they are packaged. This License gives no\n permission to license the work in any other way, but it does not\n invalidate such permission if you have separately received it.\n- d) If the work has interactive user interfaces, each must display\n Appropriate Legal Notices; however, if the Program has interactive\n interfaces that do not display Appropriate Legal Notices, your\n work need not make them do so.\n\nA compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit. Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n### 6. Conveying Non-Source Forms.\n\n You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n- a) Convey the object code in, or embodied in, a physical product\n (including a physical distribution medium), accompanied by the\n Corresponding Source fixed on a durable physical medium\n customarily used for software interchange.\n- b) Convey the object code in, or embodied in, a physical product\n (including a physical distribution medium), accompanied by a\n written offer, valid for at least three years and valid for as\n long as you offer spare parts or customer support for that product\n model, to give anyone who possesses the object code either (1) a\n copy of the Corresponding Source for all the software in the\n product that is covered by this License, on a durable physical\n medium customarily used for software interchange, for a price no\n more than your reasonable cost of physically performing this\n conveying of source, or (2) access to copy the\n Corresponding Source from a network server at no charge.\n- c) Convey individual copies of the object code with a copy of the\n written offer to provide the Corresponding Source. This\n alternative is allowed only occasionally and noncommercially, and\n only if you received the object code with such an offer, in accord\n with subsection 6b.\n- d) Convey the object code by offering access from a designated\n place (gratis or for a charge), and offer equivalent access to the\n Corresponding Source in the same way through the same place at no\n further charge. You need not require recipients to copy the\n Corresponding Source along with the object code. If the place to\n copy the object code is a network server, the Corresponding Source\n may be on a different server (operated by you or a third party)\n that supports equivalent copying facilities, provided you maintain\n clear directions next to the object code saying where to find the\n Corresponding Source. Regardless of what server hosts the\n Corresponding Source, you remain obligated to ensure that it is\n available for as long as needed to satisfy these requirements.\n- e) Convey the object code using peer-to-peer transmission, provided\n you inform other peers where the object code and Corresponding\n Source of the work are being offered to the general public at no\n charge under subsection 6d.\n\nA separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\nA \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling. In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage. For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product. A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n\"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source. The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\nIf you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information. But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\nThe requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed. Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\nCorresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n### 7. Additional Terms.\n\n\"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law. If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\nWhen you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit. (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.) You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\nNotwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n- a) Disclaiming warranty or limiting liability differently from the\n terms of sections 15 and 16 of this License; or\n- b) Requiring preservation of specified reasonable legal notices or\n author attributions in that material or in the Appropriate Legal\n Notices displayed by works containing it; or\n- c) Prohibiting misrepresentation of the origin of that material, or\n requiring that modified versions of such material be marked in\n reasonable ways as different from the original version; or\n- d) Limiting the use for publicity purposes of names of licensors or\n authors of the material; or\n- e) Declining to grant rights under trademark law for use of some\n trade names, trademarks, or service marks; or\n- f) Requiring indemnification of licensors and authors of that\n material by anyone who conveys the material (or modified versions of\n it) with contractual assumptions of liability to the recipient, for\n any liability that these contractual assumptions directly impose on\n those licensors and authors; or\n- g) Requiring strong attribution such as notices on any user interfaces\n that run or convey any covered work, such as a prominent link to a URL\n on the header of a website, such that all users of the covered work may\n become aware of the notice, for a period no longer than 20 years.\n\nAll other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10. If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term. If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\nIf you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\nAdditional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n### 8. Termination.\n\nYou may not propagate or modify a covered work except as expressly\nprovided under this License. Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\nHowever, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\nMoreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\nTermination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License. If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n### 9. Acceptance Not Required for Having Copies.\n\nYou are not required to accept this License in order to receive or\nrun a copy of the Program. Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance. However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work. These actions infringe copyright if you do\nnot accept this License. Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n### 10. Automatic Licensing of Downstream Recipients.\n\nEach time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License. You are not responsible\nfor enforcing compliance by third parties with this License.\n\nAn \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations. If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\nYou may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License. For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n### 11. Patents.\n\nA \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based. The\nwork thus licensed is called the contributor's \"contributor version\".\n\nA contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version. For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\nEach contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\nIn the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement). To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\nIf you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients. \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\nIf, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\nA patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License. You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\nNothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n### 12. No Surrender of Others' Freedom.\n\nIf conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License. If you cannot convey a\ncovered work so as to simultaneously satisfy your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all. For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n### 13. Remote Network Interaction; Use with the GNU General Public License.\n\nNotwithstanding any other provision of this License, if you modify the\nProgram, your modified version must prominently offer all users\ninteracting with it remotely through a computer network (if your version\nsupports such interaction) an opportunity to receive the Corresponding\nSource of your version by providing access to the Corresponding Source\nfrom a network server at no charge, through some standard or customary\nmeans of facilitating copying of software. This Corresponding Source\nshall include the Corresponding Source for any work covered by version 3\nof the GNU General Public License that is incorporated pursuant to the\nfollowing paragraph.\n\nNotwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU General Public License into a single\ncombined work, and to convey the resulting work. The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the work with which it is combined will remain governed by version\n3 of the GNU General Public License.\n\n### 14. Revised Versions of this License.\n\nNewTendermint LLC may publish revised and/or new versions of\nthe GNO Network General Public License from time to time. Such new versions\nwill be similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\nEach version is given a distinguishing version number. If the\nProgram specifies that a certain numbered version of the GNO Network General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Gno Software\nFoundation. If the Program does not specify a version number of the\nGNO Network General Public License, you may choose any version ever published\nby NewTendermint LLC.\n\nIf the Program specifies that a proxy can decide which future\nversions of the GNO Network General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\nLater license versions may give you additional or different\npermissions. However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n### 15. Disclaimer of Warranty.\n\nTHERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n### 16. Limitation of Liability.\n\nIN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n### 17. Interpretation of Sections 15 and 16.\n\nIf the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\nEND OF TERMS AND CONDITIONS\n\n## How to Apply These Terms to Your New Programs\n\nIf you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\nTo do so, attach the following notices to the program. It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n \u003cone line to give the program's name and a brief idea of what it does.\u003e\n Copyright (C) \u003cyear\u003e \u003cname of author\u003e\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNO Network General Public License as published by\n NewTendermint LLC, either version 4 of the License, or\n (at your option) any later version.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNO Network General Public License for more details.\n\n You should have received a copy of the GNO Network General Public License\n along with this program. If not, see \u003chttps://gno.land/license\u003e.\n\nAlso add information on how to contact you by electronic and paper mail.\n\nIf your software can interact with users remotely through a computer\nnetwork, you should also make sure that it provides a way for users to\nget its source. For example, if your program is a web application, its\ninterface could display a \"Source\" link that leads users to an archive\nof the code. There are many ways you could offer source, and different\nsolutions will be better for different programs; see section 13 for the\nspecific requirements.\n`\n\t)\n\t_ = b.NewPost(\"\", path, title, body, \"2024-04-22T00:00:00Z\", nil, nil)\n}\n"},{"name":"page_partners.gno","body":"package gnopages\n\nfunc init() {\n\tpath := \"partners\"\n\ttitle := \"Partnerships\"\n\tbody := `### Fund and Grants Program\n\nAre you a builder, tinkerer, or researcher? If you’re looking to create awesome dApps, tooling, infrastructure, \nor smart contract libraries on gno.land, you can apply for a grant. The gno.land Ecosystem Fund and Grants program \nprovides financial contributions for individuals and teams to innovate on the platform.\n\nRead more about our Funds and Grants program [here](https://github.com/gnolang/ecosystem-fund-grants).\n`\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:27Z\", nil, nil)\n}\n"},{"name":"page_start.gno","body":"package gnopages\n\nfunc init() {\n\tpath := \"start\"\n\ttitle := \"Getting Started with Gno\"\n\t// XXX: description := \"\"\n\n\t// TODO: codegen to use README files here\n\n\t/* TODO: port previous message: This is a demo of Gno smart contract programming. This document was\n\tconstructed by Gno onto a smart contract hosted on the data Realm\n\tname [\"gno.land/r/demo/boards\"](https://gno.land/r/demo/boards/)\n\t([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)).\n\t*/\n\tbody := `## Getting Started with Gno\n\n- [Install Gno Key](/r/demo/boards:testboard/5)\n- TODO: add more links\n`\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:28Z\", nil, nil)\n}\n"},{"name":"page_testnets.gno","body":"package gnopages\n\nfunc init() {\n\tpath := \"testnets\"\n\ttitle := \"gno.land Testnet List\"\n\tbody := `\n- [Portal Loop](https://docs.gno.land/concepts/portal-loop) - a rolling testnet\n- [staging.gno.land](https://staging.gno.land) - wiped every commit to monorepo master\n- _[test4.gno.land](https://test4.gno.land) (latest)_\n\nFor a list of RPC endpoints, see the [reference documentation](https://docs.gno.land/reference/rpc-endpoints).\n\n## Local development\n\nSee the \"Getting started\" section in the [official documentation](https://docs.gno.land/getting-started/local-setup).\n`\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:29Z\", nil, nil)\n}\n"},{"name":"page_tokenomics.gno","body":"package gnopages\n\nfunc init() {\n\tvar (\n\t\tpath = \"tokenomics\"\n\t\ttitle = \"gno.land Tokenomics\"\n\t\t// XXX: description = \"\"\"\n\t\tbody = `Lorem Ipsum`\n\t)\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:30Z\", nil, nil)\n}\n"},{"name":"pages.gno","body":"package gnopages\n\nimport (\n\t\"gno.land/p/demo/blog\"\n)\n\n// TODO: switch from p/blog to p/pages\n\nvar b = \u0026blog.Blog{\n\tTitle: \"Gnoland's Pages\",\n\tPrefix: \"/r/gnoland/pages:\",\n\tNoBreadcrumb: true,\n}\n\nfunc Render(path string) string {\n\treturn b.Render(path)\n}\n"},{"name":"pages_test.gno","body":"package gnopages\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestHome(t *testing.T) {\n\tprintedOnce := false\n\tgot := Render(\"\")\n\texpectedSubtrings := []string{\n\t\t\"/r/gnoland/pages:p/tokenomics\",\n\t\t\"/r/gnoland/pages:p/start\",\n\t\t\"/r/gnoland/pages:p/contribute\",\n\t\t\"/r/gnoland/pages:p/about\",\n\t\t\"/r/gnoland/pages:p/gnolang\",\n\t}\n\tfor _, substring := range expectedSubtrings {\n\t\tif !strings.Contains(got, substring) {\n\t\t\tif !printedOnce {\n\t\t\t\tprintln(got)\n\t\t\t\tprintedOnce = true\n\t\t\t}\n\t\t\tt.Errorf(\"expected %q, but not found.\", substring)\n\t\t}\n\t}\n}\n\nfunc TestAbout(t *testing.T) {\n\tprintedOnce := false\n\tgot := Render(\"p/about\")\n\texpectedSubtrings := []string{\n\t\t\"gno.land Is A Platform To Write Smart Contracts In Gno\",\n\t\t\"gno.land is a next-generation smart contract platform using Gno, an interpreted version of the general-purpose Go\\nprogramming language.\",\n\t}\n\tfor _, substring := range expectedSubtrings {\n\t\tif !strings.Contains(got, substring) {\n\t\t\tif !printedOnce {\n\t\t\t\tprintln(got)\n\t\t\t\tprintedOnce = true\n\t\t\t}\n\t\t\tt.Errorf(\"expected %q, but not found.\", substring)\n\t\t}\n\t}\n}\n"},{"name":"util.gno","body":"package gnopages\n\nfunc checkErr(err error) {\n\tif err != nil {\n\t\tpanic(err)\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":"validators","path":"gno.land/r/sys/validators/v2","files":[{"name":"doc.gno","body":"// Package validators implements the on-chain validator set management through Proof of Contribution.\n// The Realm exposes only a public executor for govdao proposals, that can suggest validator set changes.\npackage validators\n"},{"name":"gnosdk.gno","body":"package validators\n\nimport (\n\t\"gno.land/p/sys/validators\"\n)\n\n// GetChanges returns the validator changes stored on the realm, since the given block number.\n// This function is intended to be called by gno.land through the GnoSDK\nfunc GetChanges(from int64) []validators.Validator {\n\tvalsetChanges := make([]validators.Validator, 0)\n\n\t// Gather the changes from the specified block\n\tchanges.Iterate(getBlockID(from), \"\", func(_ string, value interface{}) bool {\n\t\tchs := value.([]change)\n\n\t\tfor _, ch := range chs {\n\t\t\tvalsetChanges = append(valsetChanges, ch.validator)\n\t\t}\n\n\t\treturn false\n\t})\n\n\treturn valsetChanges\n}\n"},{"name":"init.gno","body":"package validators\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/nt/poa\"\n\t\"gno.land/p/sys/validators\"\n)\n\nfunc init() {\n\t// Prepare the initial validator set\n\tset := []validators.Validator{\n\t\t// core-val-1\n\t\t{\n\t\t\tAddress: std.Address(\"g1qn3jwvdpva622j3fyudqy65zstnqx2wnqhrs3s\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zpndqtjh5dcsnd0gcez3frs3w6rsttmlekj4cyywegyh0n8uprwvj5n8688\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// core-val-2\n\t\t{\n\t\t\tAddress: std.Address(\"g1gtu9czw9qavrtdnf936usvwjwyjz0x0jk243au\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zq4y0ppxhxazdwxhnsxxzdmh9rxht888n4fl0mskwcpq7y2404dm2h0lamk\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// core-val-3\n\t\t{\n\t\t\tAddress: std.Address(\"g19emxxnzzfa0pkffvthrss5drgccjnwj8mdme4f\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zq288fe7pd2yy3h2h8qjh0elu3pxuamf3wpa9qt9s6jja0r3k64ue4mh636\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// core-val-4\n\t\t{\n\t\t\tAddress: std.Address(\"g1hyxtsgjr5zt06jcx4z0xenn3u442ad2xgzu7lp\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zpy4mst534500z7k6xk5u7c9ex8zs44rjjhmxaxtw9zzjv82qkfhkhx2rfs\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// core-val-5\n\t\t{\n\t\t\tAddress: std.Address(\"g1l072ma0vfhx7s4vpevfvuxd6wzkv5ztt7gh99w\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zqtvz3g6nvu3d6wdz97w7jdw2sjc65us5u8gj8pm4mhasw7zxakjhjn9qkm\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// core-val-6\n\t\t{\n\t\t\tAddress: std.Address(\"g1uwqd3284kuzm56auwyc9d87jf3953tp9pnt506\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zp8xm09ura7mwyntee78cl64hgzq0x75f05tv7fkxpqvc797j37hsr3vgjg\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// berty-val-1\n\t\t{\n\t\t\tAddress: std.Address(\"g1ut590acnamvhkrh4qz6dz9zt9e3hyu499u0gvl\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zq2gncppkfzmx7s22mn60mf0uxzzpl23yx97hwmwm8yc6lupepqqnlexfch\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// onbloc-val-1\n\t\t{\n\t\t\tAddress: std.Address(\"g1arkzjfrte9l97v9q2qye07v0lw07039gaa3hfy\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zpkvdy7n9744qay76fzekpu9l6g3mp4hzhqjmp6k2as72ghlzc546ju3a09\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// onbloc-val-2\n\t\t{\n\t\t\tAddress: std.Address(\"g1x0m33nyne064xdx7tvlfcjwd4xkajjar6h523z\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zp6s70v4wurhg699w6f9emkwxdlm2eyf2uv64annj47npq85tjeucedmky9\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// devx-val-1\n\t\t{\n\t\t\tAddress: std.Address(\"g1mxguhd5zacar64txhfm0v7hhtph5wur5hx86vs\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zqz6fwulsygvu9xypka3zqxhkxllm467e3adphmj6y44vn3yy8qq34vxnse\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// devx-val-2\n\t\t{\n\t\t\tAddress: std.Address(\"g1t9ctfa468hn6czff8kazw08crazehcxaqa2uaa\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zpsq650w975vqsf6ajj5x4wdzfnrh64kmw7sljqz7wts6k0p6l36d0huls3\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// devx-val-3\n\t\t{\n\t\t\tAddress: std.Address(\"g1sll4rtvrepdyzcvg5ml0kjtl7fnwgcsxgg9s5q\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zplr4zg2smgha4n9huwcywm6pnkuny2x2j44kk4srxcf0rrmpql3035k8s2\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// devx-val-4\n\t\t{\n\t\t\tAddress: std.Address(\"g1aa5pp94eaextkump38766hpdra74xtfh805msv\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zqe85el3ardhel5vruywsdjw0vj2zjyhqhsyhcnuh0dy8xhuj8mxjg5h7uw\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// tori-val-1\n\t\t{\n\t\t\tAddress: std.Address(\"g1r2lwzu0y0na4686a0lz4f2zqxlffqkfm7lqqqp\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zq2quztlp2pffjsun3jeqyesru8rx9yc6tfj9na3hnw9qgn4zlrpul5mhd0\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// aib-val-1\n\t\t{\n\t\t\tAddress: std.Address(\"g1ecdu2gwz9d46srrhpu7k60pnrquvle5z2a5nn0\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zqnrer4hlsq7q22egx9ur357hg8ftsntyh4z2g7x69u2s4ay25vdw4tredd\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// aib-val-2\n\t\t{\n\t\t\tAddress: std.Address(\"g169wsuqlrscnvxtsu6wrc0zuwn39tmctw7q9f0q\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zpv6k4a2r6x6gt7eqp70l5vxluk9zkdmlqvkxztnc8zp2llq73e6ukxvsf6\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// aib-val-3\n\t\t{\n\t\t\tAddress: std.Address(\"g1hfwh3ufph3zczs5wu4qvpgtv79fzh30rgzdux8\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zqxx3qdzl9f6lee42vhtka5luujhxg22tesyww52af68f75zzp0snyhl8mw\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t}\n\n\t// The default valset protocol is PoA\n\tvp = poa.NewPoA(poa.WithInitialSet(set))\n\n\t// No changes to apply initially\n\tchanges = avl.NewTree()\n}\n"},{"name":"poc.gno","body":"package validators\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/sys/validators\"\n\t\"gno.land/r/gov/dao/bridge\"\n)\n\nconst errNoChangesProposed = \"no set changes proposed\"\n\n// NewPropExecutor creates a new executor that wraps a changes closure\n// proposal. This wrapper is required to ensure the GovDAO Realm actually\n// executed the callback.\n//\n// Concept adapted from:\n// https://github.com/gnolang/gno/pull/1945\nfunc NewPropExecutor(changesFn func() []validators.Validator) dao.Executor {\n\tif changesFn == nil {\n\t\tpanic(errNoChangesProposed)\n\t}\n\n\tcallback := func() error {\n\t\tfor _, change := range changesFn() {\n\t\t\tif change.VotingPower == 0 {\n\t\t\t\t// This change request is to remove the validator\n\t\t\t\tremoveValidator(change.Address)\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// This change request is to add the validator\n\t\t\taddValidator(change)\n\t\t}\n\n\t\treturn nil\n\t}\n\n\treturn bridge.GovDAO().NewGovDAOExecutor(callback)\n}\n\n// IsValidator returns a flag indicating if the given bech32 address\n// is part of the validator set\nfunc IsValidator(addr std.Address) bool {\n\treturn vp.IsValidator(addr)\n}\n\n// GetValidators returns the typed validator set\nfunc GetValidators() []validators.Validator {\n\treturn vp.GetValidators()\n}\n"},{"name":"validators.gno","body":"package validators\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/seqid\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/p/sys/validators\"\n)\n\nvar (\n\tvp validators.ValsetProtocol // p is the underlying validator set protocol\n\tchanges *avl.Tree // changes holds any valset changes; seqid(block number) -\u003e []change\n)\n\n// change represents a single valset change, tied to a specific block number\ntype change struct {\n\tblockNum int64 // the block number associated with the valset change\n\tvalidator validators.Validator // the validator update\n}\n\n// addValidator adds a new validator to the validator set.\n// If the validator is already present, the method errors out\nfunc addValidator(validator validators.Validator) {\n\tval, err := vp.AddValidator(validator.Address, validator.PubKey, validator.VotingPower)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Validator added, note the change\n\tch := change{\n\t\tblockNum: std.GetHeight(),\n\t\tvalidator: val,\n\t}\n\n\tsaveChange(ch)\n\n\t// Emit the validator set change\n\tstd.Emit(validators.ValidatorAddedEvent)\n}\n\n// removeValidator removes the given validator from the set.\n// If the validator is not present in the set, the method errors out\nfunc removeValidator(address std.Address) {\n\tval, err := vp.RemoveValidator(address)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Validator removed, note the change\n\tch := change{\n\t\tblockNum: std.GetHeight(),\n\t\tvalidator: validators.Validator{\n\t\t\tAddress: val.Address,\n\t\t\tPubKey: val.PubKey,\n\t\t\tVotingPower: 0, // nullified the voting power indicates removal\n\t\t},\n\t}\n\n\tsaveChange(ch)\n\n\t// Emit the validator set change\n\tstd.Emit(validators.ValidatorRemovedEvent)\n}\n\n// saveChange saves the valset change\nfunc saveChange(ch change) {\n\tid := getBlockID(ch.blockNum)\n\n\tsetRaw, exists := changes.Get(id)\n\tif !exists {\n\t\tchanges.Set(id, []change{ch})\n\n\t\treturn\n\t}\n\n\t// Save the change\n\tset := setRaw.([]change)\n\tset = append(set, ch)\n\n\tchanges.Set(id, set)\n}\n\n// getBlockID converts the block number to a sequential ID\nfunc getBlockID(blockNum int64) string {\n\treturn seqid.ID(uint64(blockNum)).String()\n}\n\nfunc Render(_ string) string {\n\tvar (\n\t\tsize = changes.Size()\n\t\tmaxDisplay = 10\n\t)\n\n\tif size == 0 {\n\t\treturn \"No valset changes to apply.\"\n\t}\n\n\toutput := \"Valset changes:\\n\"\n\tchanges.ReverseIterateByOffset(size-maxDisplay, maxDisplay, func(_ string, value interface{}) bool {\n\t\tchs := value.([]change)\n\n\t\tfor _, ch := range chs {\n\t\t\toutput += ufmt.Sprintf(\n\t\t\t\t\"- #%d: %s (%d)\\n\",\n\t\t\t\tch.blockNum,\n\t\t\t\tch.validator.Address.String(),\n\t\t\t\tch.validator.VotingPower,\n\t\t\t)\n\t\t}\n\n\t\treturn false\n\t})\n\n\treturn output\n}\n"},{"name":"validators_test.gno","body":"package validators\n\nimport (\n\t\"testing\"\n\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\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/sys/validators\"\n)\n\n// generateTestValidators generates a dummy validator set\nfunc generateTestValidators(count int) []validators.Validator {\n\tvals := make([]validators.Validator, 0, count)\n\n\tfor i := 0; i \u003c count; i++ {\n\t\tval := validators.Validator{\n\t\t\tAddress: testutils.TestAddress(ufmt.Sprintf(\"%d\", i)),\n\t\t\tPubKey: \"public-key\",\n\t\t\tVotingPower: 10,\n\t\t}\n\n\t\tvals = append(vals, val)\n\t}\n\n\treturn vals\n}\n\nfunc TestValidators_AddRemove(t *testing.T) {\n\t// Clear any changes\n\tchanges = avl.NewTree()\n\n\tvar (\n\t\tvals = generateTestValidators(100)\n\t\tinitialHeight = int64(123)\n\t)\n\n\t// Add in the validators\n\tfor _, val := range vals {\n\t\taddValidator(val)\n\n\t\t// Make sure the validator is added\n\t\tuassert.True(t, vp.IsValidator(val.Address))\n\n\t\tstd.TestSkipHeights(1)\n\t}\n\n\tfor i := initialHeight; i \u003c initialHeight+int64(len(vals)); i++ {\n\t\t// Make sure the changes are saved\n\t\tchs := GetChanges(i)\n\n\t\t// We use the funky index calculation to make sure\n\t\t// changes are properly handled for each block span\n\t\tuassert.Equal(t, initialHeight+int64(len(vals))-i, int64(len(chs)))\n\n\t\tfor index, val := range vals[i-initialHeight:] {\n\t\t\t// Make sure the changes are equal to the additions\n\t\t\tch := chs[index]\n\n\t\t\tuassert.Equal(t, val.Address, ch.Address)\n\t\t\tuassert.Equal(t, val.PubKey, ch.PubKey)\n\t\t\tuassert.Equal(t, val.VotingPower, ch.VotingPower)\n\t\t}\n\t}\n\n\t// Save the beginning height for the removal\n\tinitialRemoveHeight := std.GetHeight()\n\n\t// Clear any changes\n\tchanges = avl.NewTree()\n\n\t// Remove the validators\n\tfor _, val := range vals {\n\t\tremoveValidator(val.Address)\n\n\t\t// Make sure the validator is removed\n\t\tuassert.False(t, vp.IsValidator(val.Address))\n\n\t\tstd.TestSkipHeights(1)\n\t}\n\n\tfor i := initialRemoveHeight; i \u003c initialRemoveHeight+int64(len(vals)); i++ {\n\t\t// Make sure the changes are saved\n\t\tchs := GetChanges(i)\n\n\t\t// We use the funky index calculation to make sure\n\t\t// changes are properly handled for each block span\n\t\tuassert.Equal(t, initialRemoveHeight+int64(len(vals))-i, int64(len(chs)))\n\n\t\tfor index, val := range vals[i-initialRemoveHeight:] {\n\t\t\t// Make sure the changes are equal to the additions\n\t\t\tch := chs[index]\n\n\t\t\tuassert.Equal(t, val.Address, ch.Address)\n\t\t\tuassert.Equal(t, val.PubKey, ch.PubKey)\n\t\t\tuassert.Equal(t, uint64(0), ch.VotingPower)\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":"valopers","path":"gno.land/r/gnoland/valopers/v2","files":[{"name":"init.gno","body":"package valopers\n\nimport \"gno.land/p/demo/avl\"\n\nfunc init() {\n\tvalopers = avl.NewTree()\n}\n"},{"name":"valopers.gno","body":"// Package valopers is designed around the permissionless lifecycle of valoper profiles.\n// It also includes parts designed for govdao to propose valset changes based on registered valopers.\npackage valopers\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/ufmt\"\n\tpVals \"gno.land/p/sys/validators\"\n\t\"gno.land/r/gov/dao/bridge\"\n\tvalidators \"gno.land/r/sys/validators/v2\"\n)\n\nconst (\n\terrValoperExists = \"valoper already exists\"\n\terrValoperMissing = \"valoper does not exist\"\n\terrInvalidAddressUpdate = \"valoper updated address exists\"\n\terrValoperNotCaller = \"valoper is not the caller\"\n)\n\n// valopers keeps track of all the active validator operators\nvar valopers *avl.Tree // Address -\u003e Valoper\n\n// Valoper represents a validator operator profile\ntype Valoper struct {\n\tName string // the display name of the valoper\n\tMoniker string // the moniker of the valoper\n\tDescription string // the description of the valoper\n\n\tAddress std.Address // The bech32 gno address of the validator\n\tPubKey string // the bech32 public key of the validator\n\tP2PAddresses []string // the publicly reachable P2P addresses of the validator\n\tActive bool // flag indicating if the valoper is active\n}\n\n// Register registers a new valoper\nfunc Register(v Valoper) {\n\t// Check if the valoper is already registered\n\tif isValoper(v.Address) {\n\t\tpanic(errValoperExists)\n\t}\n\n\t// TODO add address derivation from public key\n\t// (when the laws of gno make it possible)\n\n\t// Save the valoper to the set\n\tvalopers.Set(v.Address.String(), v)\n}\n\n// Update updates an existing valoper\nfunc Update(address std.Address, v Valoper) {\n\t// Check if the valoper is present\n\tif !isValoper(address) {\n\t\tpanic(errValoperMissing)\n\t}\n\n\t// Check that the valoper wouldn't be\n\t// overwriting an existing one\n\tisAddressUpdate := address != v.Address\n\tif isAddressUpdate \u0026\u0026 isValoper(v.Address) {\n\t\tpanic(errInvalidAddressUpdate)\n\t}\n\n\t// Remove the old valoper info\n\t// in case the address changed\n\tif address != v.Address {\n\t\tvalopers.Remove(address.String())\n\t}\n\n\t// Save the new valoper info\n\tvalopers.Set(v.Address.String(), v)\n}\n\n// GetByAddr fetches the valoper using the address, if present\nfunc GetByAddr(address std.Address) Valoper {\n\tvaloperRaw, exists := valopers.Get(address.String())\n\tif !exists {\n\t\tpanic(errValoperMissing)\n\t}\n\n\treturn valoperRaw.(Valoper)\n}\n\n// Render renders the current valoper set\nfunc Render(_ string) string {\n\tif valopers.Size() == 0 {\n\t\treturn \"No valopers to display.\"\n\t}\n\n\toutput := \"Valset changes to apply:\\n\"\n\tvalopers.Iterate(\"\", \"\", func(_ string, value interface{}) bool {\n\t\tvaloper := value.(Valoper)\n\n\t\toutput += valoper.Render()\n\n\t\treturn false\n\t})\n\n\treturn output\n}\n\n// Render renders a single valoper with their information\nfunc (v Valoper) Render() string {\n\toutput := ufmt.Sprintf(\"## %s (%s)\\n\", v.Name, v.Moniker)\n\toutput += ufmt.Sprintf(\"%s\\n\\n\", v.Description)\n\toutput += ufmt.Sprintf(\"- Address: %s\\n\", v.Address.String())\n\toutput += ufmt.Sprintf(\"- PubKey: %s\\n\", v.PubKey)\n\toutput += \"- P2P Addresses: [\\n\"\n\n\tif len(v.P2PAddresses) == 0 {\n\t\toutput += \"]\\n\"\n\n\t\treturn output\n\t}\n\n\tfor index, addr := range v.P2PAddresses {\n\t\toutput += addr\n\n\t\tif index == len(v.P2PAddresses)-1 {\n\t\t\toutput += \"]\\n\"\n\n\t\t\tcontinue\n\t\t}\n\n\t\toutput += \",\\n\"\n\t}\n\n\treturn output\n}\n\n// isValoper checks if the valoper exists\nfunc isValoper(address std.Address) bool {\n\t_, exists := valopers.Get(address.String())\n\n\treturn exists\n}\n\n// GovDAOProposal creates a proposal to the GovDAO\n// for adding the given valoper to the validator set.\n// This function is meant to serve as a helper\n// for generating the govdao proposal\nfunc GovDAOProposal(address std.Address) {\n\tvar (\n\t\tvaloper = GetByAddr(address)\n\t\tvotingPower = uint64(1)\n\t)\n\n\t// Make sure the valoper is the caller\n\tif std.OrigCaller() != address {\n\t\tpanic(errValoperNotCaller)\n\t}\n\n\t// Determine the voting power\n\tif !valoper.Active {\n\t\tvotingPower = uint64(0)\n\t}\n\n\tchangesFn := func() []pVals.Validator {\n\t\treturn []pVals.Validator{\n\t\t\t{\n\t\t\t\tAddress: valoper.Address,\n\t\t\t\tPubKey: valoper.PubKey,\n\t\t\t\tVotingPower: votingPower,\n\t\t\t},\n\t\t}\n\t}\n\n\t// Create the executor\n\texecutor := validators.NewPropExecutor(changesFn)\n\n\t// Craft the proposal description\n\tdescription := ufmt.Sprintf(\n\t\t\"Add valoper %s (Address: %s; PubKey: %s) to the valset\",\n\t\tvaloper.Name,\n\t\tvaloper.Address.String(),\n\t\tvaloper.PubKey,\n\t)\n\n\tprop := dao.ProposalRequest{\n\t\tDescription: description,\n\t\tExecutor: executor,\n\t}\n\n\t// Create the govdao proposal\n\tbridge.GovDAO().Propose(prop)\n}\n"},{"name":"valopers_test.gno","body":"package valopers\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestValopers_Register(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"already a valoper\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Clear the set for the test\n\t\tvalopers = avl.NewTree()\n\n\t\tv := Valoper{\n\t\t\tAddress: testutils.TestAddress(\"valoper\"),\n\t\t}\n\n\t\t// Add the valoper\n\t\tvalopers.Set(v.Address.String(), v)\n\n\t\tuassert.PanicsWithMessage(t, errValoperExists, func() {\n\t\t\tRegister(v)\n\t\t})\n\t})\n\n\tt.Run(\"successful registration\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Clear the set for the test\n\t\tvalopers = avl.NewTree()\n\n\t\tv := Valoper{\n\t\t\tAddress: testutils.TestAddress(\"valoper\"),\n\t\t\tName: \"new valoper\",\n\t\t\tMoniker: \"val-1\",\n\t\t\tPubKey: \"pub key\",\n\t\t}\n\n\t\tuassert.NotPanics(t, func() {\n\t\t\tRegister(v)\n\t\t})\n\n\t\tuassert.NotPanics(t, func() {\n\t\t\tvaloper := GetByAddr(v.Address)\n\n\t\t\tuassert.Equal(t, v.Address, valoper.Address)\n\t\t\tuassert.Equal(t, v.Name, valoper.Name)\n\t\t\tuassert.Equal(t, v.Moniker, valoper.Moniker)\n\t\t\tuassert.Equal(t, v.PubKey, valoper.PubKey)\n\t\t})\n\t})\n}\n\nfunc TestValopers_Update(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"non-existing valoper\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Clear the set for the test\n\t\tvalopers = avl.NewTree()\n\n\t\tv := Valoper{}\n\n\t\t// Update the valoper\n\t\tuassert.PanicsWithMessage(t, errValoperMissing, func() {\n\t\t\tUpdate(v.Address, v)\n\t\t})\n\t})\n\n\tt.Run(\"overwrite valoper\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Clear the set for the test\n\t\tvalopers = avl.NewTree()\n\n\t\tone := Valoper{\n\t\t\tAddress: testutils.TestAddress(\"valoper 1\"),\n\t\t}\n\n\t\t// Add the valoper\n\t\tuassert.NotPanics(t, func() {\n\t\t\tRegister(one)\n\t\t})\n\n\t\tinitialAddress := testutils.TestAddress(\"valoper 2\")\n\t\ttwo := Valoper{\n\t\t\tAddress: initialAddress,\n\t\t}\n\n\t\t// Add the valoper\n\t\tuassert.NotPanics(t, func() {\n\t\t\tRegister(two)\n\t\t})\n\n\t\t// Update the valoper address\n\t\t// so it overlaps\n\t\ttwo = Valoper{\n\t\t\tAddress: one.Address,\n\t\t}\n\n\t\t// Update the valoper\n\t\tuassert.PanicsWithMessage(t, errInvalidAddressUpdate, func() {\n\t\t\tUpdate(initialAddress, two)\n\t\t})\n\t})\n\n\tt.Run(\"successful update\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Clear the set for the test\n\t\tvalopers = avl.NewTree()\n\n\t\tvar (\n\t\t\tname = \"new valoper\"\n\t\t\tv = Valoper{\n\t\t\t\tAddress: testutils.TestAddress(\"valoper\"),\n\t\t\t\tName: name,\n\t\t\t\tPubKey: \"pub key\",\n\t\t\t}\n\t\t)\n\n\t\t// Add the valoper\n\t\tuassert.NotPanics(t, func() {\n\t\t\tRegister(v)\n\t\t})\n\n\t\t// Update the valoper name\n\t\tv.Name = \"new name\"\n\t\tv.Active = false\n\n\t\t// Update the valoper\n\t\tuassert.NotPanics(t, func() {\n\t\t\tUpdate(v.Address, v)\n\t\t})\n\n\t\t// Make sure the valoper is updated\n\t\tuassert.NotPanics(t, func() {\n\t\t\tvaloper := GetByAddr(v.Address)\n\n\t\t\tuassert.Equal(t, v.Name, valoper.Name)\n\t\t\tuassert.Equal(t, v.Active, valoper.Active)\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":"valopers","path":"gno.land/r/gnoland/valopers/v2","files":[{"name":"init.gno","body":"package valopers\n\nimport \"gno.land/p/demo/avl\"\n\nfunc init() {\n\tvalopers = avl.NewTree()\n}\n"},{"name":"valopers.gno","body":"// Package valopers is designed around the permissionless lifecycle of valoper profiles.\n// It also includes parts designed for govdao to propose valset changes based on registered valopers.\npackage valopers\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/ufmt\"\n\tpVals \"gno.land/p/sys/validators\"\n\t\"gno.land/r/gov/dao/bridge\"\n\tvalidators \"gno.land/r/sys/validators/v2\"\n)\n\nconst (\n\terrValoperExists = \"valoper already exists\"\n\terrValoperMissing = \"valoper does not exist\"\n\terrInvalidAddressUpdate = \"valoper updated address exists\"\n\terrValoperNotCaller = \"valoper is not the caller\"\n)\n\n// valopers keeps track of all the active validator operators\nvar valopers *avl.Tree // Address -\u003e Valoper\n\n// Valoper represents a validator operator profile\ntype Valoper struct {\n\tName string // the display name of the valoper\n\tMoniker string // the moniker of the valoper\n\tDescription string // the description of the valoper\n\n\tAddress std.Address // The bech32 gno address of the validator\n\tPubKey string // the bech32 public key of the validator\n\tP2PAddresses []string // the publicly reachable P2P addresses of the validator\n\tActive bool // flag indicating if the valoper is active\n}\n\n// Register registers a new valoper\nfunc Register(v Valoper) {\n\t// Check if the valoper is already registered\n\tif isValoper(v.Address) {\n\t\tpanic(errValoperExists)\n\t}\n\n\t// TODO add address derivation from public key\n\t// (when the laws of gno make it possible)\n\n\t// Save the valoper to the set\n\tvalopers.Set(v.Address.String(), v)\n}\n\n// Update updates an existing valoper\nfunc Update(address std.Address, v Valoper) {\n\t// Check if the valoper is present\n\tif !isValoper(address) {\n\t\tpanic(errValoperMissing)\n\t}\n\n\t// Check that the valoper wouldn't be\n\t// overwriting an existing one\n\tisAddressUpdate := address != v.Address\n\tif isAddressUpdate \u0026\u0026 isValoper(v.Address) {\n\t\tpanic(errInvalidAddressUpdate)\n\t}\n\n\t// Remove the old valoper info\n\t// in case the address changed\n\tif address != v.Address {\n\t\tvalopers.Remove(address.String())\n\t}\n\n\t// Save the new valoper info\n\tvalopers.Set(v.Address.String(), v)\n}\n\n// GetByAddr fetches the valoper using the address, if present\nfunc GetByAddr(address std.Address) Valoper {\n\tvaloperRaw, exists := valopers.Get(address.String())\n\tif !exists {\n\t\tpanic(errValoperMissing)\n\t}\n\n\treturn valoperRaw.(Valoper)\n}\n\n// Render renders the current valoper set\nfunc Render(_ string) string {\n\tif valopers.Size() == 0 {\n\t\treturn \"No valopers to display.\"\n\t}\n\n\toutput := \"Valset changes to apply:\\n\"\n\tvalopers.Iterate(\"\", \"\", func(_ string, value interface{}) bool {\n\t\tvaloper := value.(Valoper)\n\n\t\toutput += valoper.Render()\n\n\t\treturn false\n\t})\n\n\treturn output\n}\n\n// Render renders a single valoper with their information\nfunc (v Valoper) Render() string {\n\toutput := ufmt.Sprintf(\"## %s (%s)\\n\", v.Name, v.Moniker)\n\toutput += ufmt.Sprintf(\"%s\\n\\n\", v.Description)\n\toutput += ufmt.Sprintf(\"- Address: %s\\n\", v.Address.String())\n\toutput += ufmt.Sprintf(\"- PubKey: %s\\n\", v.PubKey)\n\toutput += \"- P2P Addresses: [\\n\"\n\n\tif len(v.P2PAddresses) == 0 {\n\t\toutput += \"]\\n\"\n\n\t\treturn output\n\t}\n\n\tfor index, addr := range v.P2PAddresses {\n\t\toutput += addr\n\n\t\tif index == len(v.P2PAddresses)-1 {\n\t\t\toutput += \"]\\n\"\n\n\t\t\tcontinue\n\t\t}\n\n\t\toutput += \",\\n\"\n\t}\n\n\treturn output\n}\n\n// isValoper checks if the valoper exists\nfunc isValoper(address std.Address) bool {\n\t_, exists := valopers.Get(address.String())\n\n\treturn exists\n}\n\n// GovDAOProposal creates a proposal to the GovDAO\n// for adding the given valoper to the validator set.\n// This function is meant to serve as a helper\n// for generating the govdao proposal\nfunc GovDAOProposal(address std.Address) {\n\tvar (\n\t\tvaloper = GetByAddr(address)\n\t\tvotingPower = uint64(1)\n\t)\n\n\t// Make sure the valoper is the caller\n\tif std.OriginCaller() != address {\n\t\tpanic(errValoperNotCaller)\n\t}\n\n\t// Determine the voting power\n\tif !valoper.Active {\n\t\tvotingPower = uint64(0)\n\t}\n\n\tchangesFn := func() []pVals.Validator {\n\t\treturn []pVals.Validator{\n\t\t\t{\n\t\t\t\tAddress: valoper.Address,\n\t\t\t\tPubKey: valoper.PubKey,\n\t\t\t\tVotingPower: votingPower,\n\t\t\t},\n\t\t}\n\t}\n\n\t// Create the executor\n\texecutor := validators.NewPropExecutor(changesFn)\n\n\t// Craft the proposal description\n\tdescription := ufmt.Sprintf(\n\t\t\"Add valoper %s (Address: %s; PubKey: %s) to the valset\",\n\t\tvaloper.Name,\n\t\tvaloper.Address.String(),\n\t\tvaloper.PubKey,\n\t)\n\n\tprop := dao.ProposalRequest{\n\t\tDescription: description,\n\t\tExecutor: executor,\n\t}\n\n\t// Create the govdao proposal\n\tbridge.GovDAO().Propose(prop)\n}\n"},{"name":"valopers_test.gno","body":"package valopers\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestValopers_Register(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"already a valoper\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Clear the set for the test\n\t\tvalopers = avl.NewTree()\n\n\t\tv := Valoper{\n\t\t\tAddress: testutils.TestAddress(\"valoper\"),\n\t\t}\n\n\t\t// Add the valoper\n\t\tvalopers.Set(v.Address.String(), v)\n\n\t\tuassert.PanicsWithMessage(t, errValoperExists, func() {\n\t\t\tRegister(v)\n\t\t})\n\t})\n\n\tt.Run(\"successful registration\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Clear the set for the test\n\t\tvalopers = avl.NewTree()\n\n\t\tv := Valoper{\n\t\t\tAddress: testutils.TestAddress(\"valoper\"),\n\t\t\tName: \"new valoper\",\n\t\t\tMoniker: \"val-1\",\n\t\t\tPubKey: \"pub key\",\n\t\t}\n\n\t\tuassert.NotPanics(t, func() {\n\t\t\tRegister(v)\n\t\t})\n\n\t\tuassert.NotPanics(t, func() {\n\t\t\tvaloper := GetByAddr(v.Address)\n\n\t\t\tuassert.Equal(t, v.Address, valoper.Address)\n\t\t\tuassert.Equal(t, v.Name, valoper.Name)\n\t\t\tuassert.Equal(t, v.Moniker, valoper.Moniker)\n\t\t\tuassert.Equal(t, v.PubKey, valoper.PubKey)\n\t\t})\n\t})\n}\n\nfunc TestValopers_Update(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"non-existing valoper\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Clear the set for the test\n\t\tvalopers = avl.NewTree()\n\n\t\tv := Valoper{}\n\n\t\t// Update the valoper\n\t\tuassert.PanicsWithMessage(t, errValoperMissing, func() {\n\t\t\tUpdate(v.Address, v)\n\t\t})\n\t})\n\n\tt.Run(\"overwrite valoper\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Clear the set for the test\n\t\tvalopers = avl.NewTree()\n\n\t\tone := Valoper{\n\t\t\tAddress: testutils.TestAddress(\"valoper 1\"),\n\t\t}\n\n\t\t// Add the valoper\n\t\tuassert.NotPanics(t, func() {\n\t\t\tRegister(one)\n\t\t})\n\n\t\tinitialAddress := testutils.TestAddress(\"valoper 2\")\n\t\ttwo := Valoper{\n\t\t\tAddress: initialAddress,\n\t\t}\n\n\t\t// Add the valoper\n\t\tuassert.NotPanics(t, func() {\n\t\t\tRegister(two)\n\t\t})\n\n\t\t// Update the valoper address\n\t\t// so it overlaps\n\t\ttwo = Valoper{\n\t\t\tAddress: one.Address,\n\t\t}\n\n\t\t// Update the valoper\n\t\tuassert.PanicsWithMessage(t, errInvalidAddressUpdate, func() {\n\t\t\tUpdate(initialAddress, two)\n\t\t})\n\t})\n\n\tt.Run(\"successful update\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Clear the set for the test\n\t\tvalopers = avl.NewTree()\n\n\t\tvar (\n\t\t\tname = \"new valoper\"\n\t\t\tv = Valoper{\n\t\t\t\tAddress: testutils.TestAddress(\"valoper\"),\n\t\t\t\tName: name,\n\t\t\t\tPubKey: \"pub key\",\n\t\t\t}\n\t\t)\n\n\t\t// Add the valoper\n\t\tuassert.NotPanics(t, func() {\n\t\t\tRegister(v)\n\t\t})\n\n\t\t// Update the valoper name\n\t\tv.Name = \"new name\"\n\t\tv.Active = false\n\n\t\t// Update the valoper\n\t\tuassert.NotPanics(t, func() {\n\t\t\tUpdate(v.Address, v)\n\t\t})\n\n\t\t// Make sure the valoper is updated\n\t\tuassert.NotPanics(t, func() {\n\t\t\tvaloper := GetByAddr(v.Address)\n\n\t\t\tuassert.Equal(t, v.Name, valoper.Name)\n\t\t\tuassert.Equal(t, v.Active, valoper.Active)\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":"config","path":"gno.land/r/leon/config","files":[{"name":"config.gno","body":"package config\n\nimport (\n\t\"errors\"\n\t\"std\"\n)\n\nvar (\n\tmain std.Address // leon's main address\n\tbackup std.Address // backup address\n\n\tErrInvalidAddr = errors.New(\"leon's config: invalid address\")\n\tErrUnauthorized = errors.New(\"leon's config: unauthorized\")\n)\n\nfunc init() {\n\tmain = \"g125em6arxsnj49vx35f0n0z34putv5ty3376fg5\"\n}\n\nfunc Address() std.Address {\n\treturn main\n}\n\nfunc Backup() std.Address {\n\treturn backup\n}\n\nfunc SetAddress(a std.Address) error {\n\tif !a.IsValid() {\n\t\treturn ErrInvalidAddr\n\t}\n\n\tif err := checkAuthorized(); err != nil {\n\t\treturn err\n\t}\n\n\tmain = a\n\treturn nil\n}\n\nfunc SetBackup(a std.Address) error {\n\tif !a.IsValid() {\n\t\treturn ErrInvalidAddr\n\t}\n\n\tif err := checkAuthorized(); err != nil {\n\t\treturn err\n\t}\n\n\tbackup = a\n\treturn nil\n}\n\nfunc checkAuthorized() error {\n\tcaller := std.PrevRealm().Addr()\n\tisAuthorized := caller == main || caller == backup\n\n\tif !isAuthorized {\n\t\treturn ErrUnauthorized\n\t}\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_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"home","path":"gno.land/r/leon/home","files":[{"name":"home.gno","body":"package home\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/ufmt\"\n\n\t\"gno.land/r/demo/art/gnoface\"\n\t\"gno.land/r/demo/art/millipede\"\n\t\"gno.land/r/leon/config\"\n)\n\nvar (\n\tpfp string // link to profile picture\n\tpfpCaption string // profile picture caption\n\tabtMe [2]string\n)\n\nfunc init() {\n\tpfp = \"https://i.imgflip.com/91vskx.jpg\"\n\tpfpCaption = \"[My favourite painting \u0026 pfp](https://en.wikipedia.org/wiki/Wanderer_above_the_Sea_of_Fog)\"\n\tabtMe = [2]string{\n\t\t`### About me\nHi, I'm Leon, a DevRel Engineer at gno.land. I am a tech enthusiast, \nlife-long learner, and sharer of knowledge.`,\n\t\t`### Contributions\nMy contributions to gno.land can mainly be found \n[here](https://github.com/gnolang/gno/issues?q=sort:updated-desc+author:leohhhn).\n\nTODO import r/gh\n`,\n\t}\n}\n\nfunc UpdatePFP(url, caption string) {\n\tif !isAuthorized(std.PrevRealm().Addr()) {\n\t\tpanic(config.ErrUnauthorized)\n\t}\n\n\tpfp = url\n\tpfpCaption = caption\n}\n\nfunc UpdateAboutMe(col1, col2 string) {\n\tif !isAuthorized(std.PrevRealm().Addr()) {\n\t\tpanic(config.ErrUnauthorized)\n\t}\n\n\tabtMe[0] = col1\n\tabtMe[1] = col2\n}\n\nfunc Render(path string) string {\n\tout := \"# Leon's Homepage\\n\\n\"\n\n\tout += renderAboutMe()\n\tout += renderBlogPosts()\n\tout += \"\\n\\n\"\n\tout += renderArt()\n\n\treturn out\n}\n\nfunc renderBlogPosts() string {\n\tout := \"\"\n\t//out += \"## Leon's Blog Posts\"\n\n\t// todo fetch blog posts authored by @leohhhn\n\t// and render them\n\treturn out\n}\n\nfunc renderAboutMe() string {\n\tout := \"\u003cdiv class='columns-3'\u003e\"\n\n\tout += \"\u003cdiv\u003e\\n\\n\"\n\tout += ufmt.Sprintf(\"![my profile pic](%s)\\n\\n%s\\n\\n\", pfp, pfpCaption)\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\tout += \"\u003cdiv\u003e\\n\\n\"\n\tout += abtMe[0] + \"\\n\\n\"\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\tout += \"\u003cdiv\u003e\\n\\n\"\n\tout += abtMe[1] + \"\\n\\n\"\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\tout += \"\u003c/div\u003e\u003c!-- /columns-3 --\u003e\\n\\n\"\n\n\treturn out\n}\n\nfunc renderArt() string {\n\tout := `\u003cdiv class=\"jumbotron\"\u003e` + \"\\n\\n\"\n\tout += \"# Gno Art\\n\\n\"\n\n\tout += \"\u003cdiv class='columns-3'\u003e\"\n\n\tout += renderGnoFace()\n\tout += renderMillipede()\n\tout += \"Empty spot :/\"\n\n\tout += \"\u003c/div\u003e\u003c!-- /columns-3 --\u003e\\n\\n\"\n\n\tout += \"This art is dynamic; it will change with every new block.\\n\\n\"\n\tout += `\u003c/div\u003e\u003c!-- /jumbotron --\u003e` + \"\\n\"\n\n\treturn out\n}\n\nfunc renderGnoFace() string {\n\tout := \"\u003cdiv\u003e\\n\\n\"\n\tout += gnoface.Render(strconv.Itoa(int(std.GetHeight())))\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\treturn out\n}\n\nfunc renderMillipede() string {\n\tout := \"\u003cdiv\u003e\\n\\n\"\n\tout += \"Millipede\\n\\n\"\n\tout += \"```\\n\" + millipede.Draw(int(std.GetHeight())%10+1) + \"```\\n\"\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\treturn out\n}\n\nfunc isAuthorized(addr std.Address) bool {\n\treturn addr == config.Address() || addr == config.Backup()\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":"config","path":"gno.land/r/manfred/config","files":[{"name":"config.gno","body":"package config\n\nimport \"std\"\n\nvar addr = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc Addr() std.Address {\n\treturn addr\n}\n\nfunc UpdateAddr(newAddr std.Address) {\n\tAssertIsAdmin()\n\taddr = newAddr\n}\n\nfunc AssertIsAdmin() {\n\tif std.OrigCaller() != addr {\n\t\tpanic(\"restricted area\")\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":"home","path":"gno.land/r/manfred/home","files":[{"name":"home.gno","body":"package home\n\nimport \"gno.land/r/manfred/config\"\n\nvar (\n\ttodos []string\n\tstatus string\n\tmemeImgURL string\n)\n\nfunc init() {\n\ttodos = append(todos, \"fill this todo list...\")\n\tstatus = \"Online\" // Initial status set to \"Online\"\n\tmemeImgURL = \"https://i.imgflip.com/7ze8dc.jpg\"\n}\n\nfunc Render(path string) string {\n\tcontent := \"# Manfred's (gn)home Dashboard\\n\\n\"\n\n\tcontent += \"## Meme\\n\"\n\tcontent += \"![](\" + memeImgURL + \")\\n\\n\"\n\n\tcontent += \"## Status\\n\"\n\tcontent += status + \"\\n\\n\"\n\n\tcontent += \"## Personal ToDo List\\n\"\n\tfor _, todo := range todos {\n\t\tcontent += \"- [ ] \" + todo + \"\\n\"\n\t}\n\tcontent += \"\\n\"\n\n\t// TODO: Implement a feature to list replies on r/boards on my posts\n\t// TODO: Maybe integrate a calendar feature for upcoming events?\n\n\treturn content\n}\n\nfunc AddNewTodo(todo string) {\n\tconfig.AssertIsAdmin()\n\ttodos = append(todos, todo)\n}\n\nfunc DeleteTodo(todoIndex int) {\n\tconfig.AssertIsAdmin()\n\tif todoIndex \u003e= 0 \u0026\u0026 todoIndex \u003c len(todos) {\n\t\t// Remove the todo from the list by merging slices from before and after the todo\n\t\ttodos = append(todos[:todoIndex], todos[todoIndex+1:]...)\n\t} else {\n\t\tpanic(\"Invalid todo index\")\n\t}\n}\n\nfunc UpdateStatus(newStatus string) {\n\tconfig.AssertIsAdmin()\n\tstatus = newStatus\n}\n"},{"name":"z1_filetest.gno","body":"package main\n\nimport \"gno.land/r/manfred/home\"\n\nfunc main() {\n\tprintln(home.Render(\"\"))\n}\n\n// Output:\n// # Manfred's (gn)home Dashboard\n//\n// ## Meme\n// ![](https://i.imgflip.com/7ze8dc.jpg)\n//\n// ## Status\n// Online\n//\n// ## Personal ToDo List\n// - [ ] fill this todo list...\n"},{"name":"z2_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/manfred/home\"\n)\n\nfunc main() {\n\tstd.TestSetOrigCaller(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\thome.AddNewTodo(\"aaa\")\n\thome.AddNewTodo(\"bbb\")\n\thome.AddNewTodo(\"ccc\")\n\thome.AddNewTodo(\"ddd\")\n\thome.AddNewTodo(\"eee\")\n\thome.UpdateStatus(\"Lorem Ipsum\")\n\thome.DeleteTodo(3)\n\tprintln(home.Render(\"\"))\n}\n\n// Output:\n// # Manfred's (gn)home Dashboard\n//\n// ## Meme\n// ![](https://i.imgflip.com/7ze8dc.jpg)\n//\n// ## Status\n// Lorem Ipsum\n//\n// ## Personal ToDo List\n// - [ ] fill this todo list...\n// - [ ] aaa\n// - [ ] bbb\n// - [ ] ddd\n// - [ ] eee\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":"present","path":"gno.land/r/manfred/present","files":[{"name":"admin.gno","body":"package present\n\nimport (\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\nvar (\n\tadminAddr std.Address\n\tmoderatorList avl.Tree\n\tinPause bool\n)\n\nfunc init() {\n\t// adminAddr = std.OrigCaller() // FIXME: find a way to use this from the main's genesis.\n\tadminAddr = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"\n}\n\nfunc AdminSetAdminAddr(addr std.Address) {\n\tassertIsAdmin()\n\tadminAddr = addr\n}\n\nfunc AdminSetInPause(state bool) {\n\tassertIsAdmin()\n\tinPause = state\n}\n\nfunc AdminAddModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), true)\n}\n\nfunc AdminRemoveModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), false) // XXX: delete instead?\n}\n\nfunc ModAddPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\tcaller := std.OrigCaller()\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.NewPost(caller, slug, title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc ModEditPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.GetPost(slug).Update(title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc isAdmin(addr std.Address) bool {\n\treturn addr == adminAddr\n}\n\nfunc isModerator(addr std.Address) bool {\n\t_, found := moderatorList.Get(addr.String())\n\treturn found\n}\n\nfunc assertIsAdmin() {\n\tcaller := std.OrigCaller()\n\tif !isAdmin(caller) {\n\t\tpanic(\"access restricted.\")\n\t}\n}\n\nfunc assertIsModerator() {\n\tcaller := std.OrigCaller()\n\tif isAdmin(caller) || isModerator(caller) {\n\t\treturn\n\t}\n\tpanic(\"access restricted\")\n}\n\nfunc assertNotInPause() {\n\tif inPause {\n\t\tpanic(\"access restricted (pause)\")\n\t}\n}\n\nfunc checkErr(err error) {\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n"},{"name":"present_miami23.gno","body":"package present\n\nfunc init() {\n\tpath := \"miami23\"\n\ttitle := \"Portal Loop Demo (Miami 2023)\"\n\tbody := `\nRendered by Gno.\n\n[Source (WIP)](https://github.com/gnolang/gno/pull/1176)\n\n## Portal Loop\n\n- DONE: Dynamic homepage, key pages, aliases, and redirects.\n- TODO: Deploy with history, complete worxdao v0.\n- Will replace the static gno.land site.\n- Enhances local development.\n\n[GitHub Issue](https://github.com/gnolang/gno/issues/1108)\n\n## Roadmap\n\n- Crafting the roadmap this week, open to collaboration.\n- Combining onchain (portal loop) and offchain (GitHub).\n- Next week: Unveiling the official v0 roadmap.\n\n## Teams, DAOs, Projects\n\n- Developing worxDAO contracts for directories of projects and teams.\n- GitHub teams and projects align with this structure.\n- CODEOWNER file updates coming.\n- Initial teams announced next week.\n\n## Tech Team Retreat Plan\n\n- Continue Portal Loop.\n- Consider dApp development.\n- Explore new topics [here](https://github.com/orgs/gnolang/projects/15/).\n- Engage in workshops.\n- Connect and have fun with colleagues.\n`\n\t_ = b.NewPost(adminAddr, path, title, body, \"2023-10-15T13:17:24Z\", []string{\"moul\"}, []string{\"demo\", \"portal-loop\", \"miami\"})\n}\n"},{"name":"present_miami23_filetest.gno","body":"package main\n\nimport (\n\t\"gno.land/r/manfred/present\"\n)\n\nfunc main() {\n\tprintln(present.Render(\"\"))\n\tprintln(\"------------------------------------\")\n\tprintln(present.Render(\"p/miami23\"))\n}\n"},{"name":"presentations.gno","body":"package present\n\nimport (\n\t\"gno.land/p/demo/blog\"\n)\n\n// TODO: switch from p/blog to p/present\n\nvar b = \u0026blog.Blog{\n\tTitle: \"Manfred's Presentations\",\n\tPrefix: \"/r/manfred/present:\",\n\tNoBreadcrumb: true,\n}\n\nfunc Render(path string) string {\n\treturn b.Render(path)\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":"config","path":"gno.land/r/manfred/config","files":[{"name":"config.gno","body":"package config\n\nimport \"std\"\n\nvar addr = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc Addr() std.Address {\n\treturn addr\n}\n\nfunc UpdateAddr(newAddr std.Address) {\n\tAssertIsAdmin()\n\taddr = newAddr\n}\n\nfunc AssertIsAdmin() {\n\tif std.OriginCaller() != addr {\n\t\tpanic(\"restricted area\")\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":"home","path":"gno.land/r/manfred/home","files":[{"name":"home.gno","body":"package home\n\nimport \"gno.land/r/manfred/config\"\n\nvar (\n\ttodos []string\n\tstatus string\n\tmemeImgURL string\n)\n\nfunc init() {\n\ttodos = append(todos, \"fill this todo list...\")\n\tstatus = \"Online\" // Initial status set to \"Online\"\n\tmemeImgURL = \"https://i.imgflip.com/7ze8dc.jpg\"\n}\n\nfunc Render(path string) string {\n\tcontent := \"# Manfred's (gn)home Dashboard\\n\\n\"\n\n\tcontent += \"## Meme\\n\"\n\tcontent += \"![](\" + memeImgURL + \")\\n\\n\"\n\n\tcontent += \"## Status\\n\"\n\tcontent += status + \"\\n\\n\"\n\n\tcontent += \"## Personal ToDo List\\n\"\n\tfor _, todo := range todos {\n\t\tcontent += \"- [ ] \" + todo + \"\\n\"\n\t}\n\tcontent += \"\\n\"\n\n\t// TODO: Implement a feature to list replies on r/boards on my posts\n\t// TODO: Maybe integrate a calendar feature for upcoming events?\n\n\treturn content\n}\n\nfunc AddNewTodo(todo string) {\n\tconfig.AssertIsAdmin()\n\ttodos = append(todos, todo)\n}\n\nfunc DeleteTodo(todoIndex int) {\n\tconfig.AssertIsAdmin()\n\tif todoIndex \u003e= 0 \u0026\u0026 todoIndex \u003c len(todos) {\n\t\t// Remove the todo from the list by merging slices from before and after the todo\n\t\ttodos = append(todos[:todoIndex], todos[todoIndex+1:]...)\n\t} else {\n\t\tpanic(\"Invalid todo index\")\n\t}\n}\n\nfunc UpdateStatus(newStatus string) {\n\tconfig.AssertIsAdmin()\n\tstatus = newStatus\n}\n"},{"name":"z1_filetest.gno","body":"package main\n\nimport \"gno.land/r/manfred/home\"\n\nfunc main() {\n\tprintln(home.Render(\"\"))\n}\n\n// Output:\n// # Manfred's (gn)home Dashboard\n//\n// ## Meme\n// ![](https://i.imgflip.com/7ze8dc.jpg)\n//\n// ## Status\n// Online\n//\n// ## Personal ToDo List\n// - [ ] fill this todo list...\n"},{"name":"z2_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/manfred/home\"\n)\n\nfunc main() {\n\tstd.TestSetOriginCaller(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\thome.AddNewTodo(\"aaa\")\n\thome.AddNewTodo(\"bbb\")\n\thome.AddNewTodo(\"ccc\")\n\thome.AddNewTodo(\"ddd\")\n\thome.AddNewTodo(\"eee\")\n\thome.UpdateStatus(\"Lorem Ipsum\")\n\thome.DeleteTodo(3)\n\tprintln(home.Render(\"\"))\n}\n\n// Output:\n// # Manfred's (gn)home Dashboard\n//\n// ## Meme\n// ![](https://i.imgflip.com/7ze8dc.jpg)\n//\n// ## Status\n// Lorem Ipsum\n//\n// ## Personal ToDo List\n// - [ ] fill this todo list...\n// - [ ] aaa\n// - [ ] bbb\n// - [ ] ddd\n// - [ ] eee\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":"present","path":"gno.land/r/manfred/present","files":[{"name":"admin.gno","body":"package present\n\nimport (\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\nvar (\n\tadminAddr std.Address\n\tmoderatorList avl.Tree\n\tinPause bool\n)\n\nfunc init() {\n\t// adminAddr = std.OriginCaller() // FIXME: find a way to use this from the main's genesis.\n\tadminAddr = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"\n}\n\nfunc AdminSetAdminAddr(addr std.Address) {\n\tassertIsAdmin()\n\tadminAddr = addr\n}\n\nfunc AdminSetInPause(state bool) {\n\tassertIsAdmin()\n\tinPause = state\n}\n\nfunc AdminAddModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), true)\n}\n\nfunc AdminRemoveModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), false) // XXX: delete instead?\n}\n\nfunc ModAddPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\tcaller := std.OriginCaller()\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.NewPost(caller, slug, title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc ModEditPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.GetPost(slug).Update(title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc isAdmin(addr std.Address) bool {\n\treturn addr == adminAddr\n}\n\nfunc isModerator(addr std.Address) bool {\n\t_, found := moderatorList.Get(addr.String())\n\treturn found\n}\n\nfunc assertIsAdmin() {\n\tcaller := std.OriginCaller()\n\tif !isAdmin(caller) {\n\t\tpanic(\"access restricted.\")\n\t}\n}\n\nfunc assertIsModerator() {\n\tcaller := std.OriginCaller()\n\tif isAdmin(caller) || isModerator(caller) {\n\t\treturn\n\t}\n\tpanic(\"access restricted\")\n}\n\nfunc assertNotInPause() {\n\tif inPause {\n\t\tpanic(\"access restricted (pause)\")\n\t}\n}\n\nfunc checkErr(err error) {\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n"},{"name":"present_miami23.gno","body":"package present\n\nfunc init() {\n\tpath := \"miami23\"\n\ttitle := \"Portal Loop Demo (Miami 2023)\"\n\tbody := `\nRendered by Gno.\n\n[Source (WIP)](https://github.com/gnolang/gno/pull/1176)\n\n## Portal Loop\n\n- DONE: Dynamic homepage, key pages, aliases, and redirects.\n- TODO: Deploy with history, complete worxdao v0.\n- Will replace the static gno.land site.\n- Enhances local development.\n\n[GitHub Issue](https://github.com/gnolang/gno/issues/1108)\n\n## Roadmap\n\n- Crafting the roadmap this week, open to collaboration.\n- Combining onchain (portal loop) and offchain (GitHub).\n- Next week: Unveiling the official v0 roadmap.\n\n## Teams, DAOs, Projects\n\n- Developing worxDAO contracts for directories of projects and teams.\n- GitHub teams and projects align with this structure.\n- CODEOWNER file updates coming.\n- Initial teams announced next week.\n\n## Tech Team Retreat Plan\n\n- Continue Portal Loop.\n- Consider dApp development.\n- Explore new topics [here](https://github.com/orgs/gnolang/projects/15/).\n- Engage in workshops.\n- Connect and have fun with colleagues.\n`\n\t_ = b.NewPost(adminAddr, path, title, body, \"2023-10-15T13:17:24Z\", []string{\"moul\"}, []string{\"demo\", \"portal-loop\", \"miami\"})\n}\n"},{"name":"present_miami23_filetest.gno","body":"package main\n\nimport (\n\t\"gno.land/r/manfred/present\"\n)\n\nfunc main() {\n\tprintln(present.Render(\"\"))\n\tprintln(\"------------------------------------\")\n\tprintln(present.Render(\"p/miami23\"))\n}\n"},{"name":"presentations.gno","body":"package present\n\nimport (\n\t\"gno.land/p/demo/blog\"\n)\n\n// TODO: switch from p/blog to p/present\n\nvar b = \u0026blog.Blog{\n\tTitle: \"Manfred's Presentations\",\n\tPrefix: \"/r/manfred/present:\",\n\tNoBreadcrumb: true,\n}\n\nfunc Render(path string) string {\n\treturn b.Render(path)\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":"guestbook","path":"gno.land/r/morgan/guestbook","files":[{"name":"admin.gno","body":"package guestbook\n\nimport (\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/seqid\"\n)\n\nvar owner = ownable.New()\n\n// AdminDelete removes the guestbook message with the given ID.\n// The user will still be marked as having submitted a message, so they\n// won't be able to re-submit a new message.\nfunc AdminDelete(signatureID string) {\n\towner.AssertCallerIsOwner()\n\n\tid, err := seqid.FromString(signatureID)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tidb := id.Binary()\n\tif !guestbook.Has(idb) {\n\t\tpanic(\"signature does not exist\")\n\t}\n\tguestbook.Remove(idb)\n}\n"},{"name":"guestbook.gno","body":"// Realm guestbook contains an implementation of a simple guestbook.\n// Come and sign yourself up!\npackage guestbook\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\t\"unicode\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/seqid\"\n)\n\n// Signature is a single entry in the guestbook.\ntype Signature struct {\n\tMessage string\n\tAuthor std.Address\n\tTime time.Time\n}\n\nconst (\n\tmaxMessageLength = 140\n\tmaxPerPage = 25\n)\n\nvar (\n\tsignatureID seqid.ID\n\tguestbook avl.Tree // id -\u003e Signature\n\thasSigned avl.Tree // address -\u003e struct{}\n)\n\nfunc init() {\n\tSign(\"You reached the end of the guestbook!\")\n}\n\nconst (\n\terrNotAUser = \"this guestbook can only be signed by users\"\n\terrAlreadySigned = \"you already signed the guestbook!\"\n\terrInvalidCharacterInMessage = \"invalid character in message\"\n)\n\n// Sign signs the guestbook, with the specified message.\nfunc Sign(message string) {\n\tprev := std.PrevRealm()\n\tswitch {\n\tcase !prev.IsUser():\n\t\tpanic(errNotAUser)\n\tcase hasSigned.Has(prev.Addr().String()):\n\t\tpanic(errAlreadySigned)\n\t}\n\tmessage = validateMessage(message)\n\n\tguestbook.Set(signatureID.Next().Binary(), Signature{\n\t\tMessage: message,\n\t\tAuthor: prev.Addr(),\n\t\t// NOTE: time.Now() will yield the \"block time\", which is deterministic.\n\t\tTime: time.Now(),\n\t})\n\thasSigned.Set(prev.Addr().String(), struct{}{})\n}\n\nfunc validateMessage(msg string) string {\n\tif len(msg) \u003e maxMessageLength {\n\t\tpanic(\"Keep it brief! (max \" + strconv.Itoa(maxMessageLength) + \" bytes!)\")\n\t}\n\tout := \"\"\n\tfor _, ch := range msg {\n\t\tswitch {\n\t\tcase unicode.IsLetter(ch),\n\t\t\tunicode.IsNumber(ch),\n\t\t\tunicode.IsSpace(ch),\n\t\t\tunicode.IsPunct(ch):\n\t\t\tout += string(ch)\n\t\tdefault:\n\t\t\tpanic(errInvalidCharacterInMessage)\n\t\t}\n\t}\n\treturn out\n}\n\nfunc Render(maxID string) string {\n\tvar bld strings.Builder\n\n\tbld.WriteString(\"# Guestbook 📝\\n\\n[Come sign the guestbook!](./guestbook$help\u0026func=Sign)\\n\\n---\\n\\n\")\n\n\tvar maxIDBinary string\n\tif maxID != \"\" {\n\t\tmid, err := seqid.FromString(maxID)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\t// AVL iteration is exclusive, so we need to decrease the ID value to get the \"true\" maximum.\n\t\tmid--\n\t\tmaxIDBinary = mid.Binary()\n\t}\n\n\tvar lastID seqid.ID\n\tvar printed int\n\tguestbook.ReverseIterate(\"\", maxIDBinary, func(key string, val interface{}) bool {\n\t\tsig := val.(Signature)\n\t\tmessage := strings.ReplaceAll(sig.Message, \"\\n\", \"\\n\u003e \")\n\t\tbld.WriteString(\"\u003e \" + message + \"\\n\u003e\\n\")\n\t\tidValue, ok := seqid.FromBinary(key)\n\t\tif !ok {\n\t\t\tpanic(\"invalid seqid id\")\n\t\t}\n\n\t\tbld.WriteString(\"\u003e _Written by \" + sig.Author.String() + \" at \" + sig.Time.Format(time.DateTime) + \"_ (#\" + idValue.String() + \")\\n\\n---\\n\\n\")\n\t\tlastID = idValue\n\n\t\tprinted++\n\t\t// stop after exceeding limit\n\t\treturn printed \u003e= maxPerPage\n\t})\n\n\tif printed == 0 {\n\t\tbld.WriteString(\"No messages!\")\n\t} else if printed \u003e= maxPerPage {\n\t\tbld.WriteString(\"\u003cp style='text-align:right'\u003e\u003ca href='./guestbook:\" + lastID.String() + \"'\u003eNext page\u003c/a\u003e\u003c/p\u003e\")\n\t}\n\n\treturn bld.String()\n}\n"},{"name":"guestbook_test.gno","body":"package guestbook\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n)\n\nfunc TestSign(t *testing.T) {\n\tguestbook = avl.Tree{}\n\thasSigned = avl.Tree{}\n\n\tstd.TestSetRealm(std.NewUserRealm(\"g1user\"))\n\tSign(\"Hello!\")\n\n\tstd.TestSetRealm(std.NewUserRealm(\"g1user2\"))\n\tSign(\"Hello2!\")\n\n\tres := Render(\"\")\n\tt.Log(res)\n\tif !strings.Contains(res, \"\u003e Hello!\\n\u003e\\n\u003e _Written by g1user \") {\n\t\tt.Error(\"does not contain first user's message\")\n\t}\n\tif !strings.Contains(res, \"\u003e Hello2!\\n\u003e\\n\u003e _Written by g1user2 \") {\n\t\tt.Error(\"does not contain second user's message\")\n\t}\n\tif guestbook.Size() != 2 {\n\t\tt.Error(\"invalid guestbook size\")\n\t}\n}\n\nfunc TestSign_FromRealm(t *testing.T) {\n\tstd.TestSetRealm(std.NewCodeRealm(\"gno.land/r/demo/users\"))\n\n\tdefer func() {\n\t\trec := recover()\n\t\tif rec == nil {\n\t\t\tt.Fatal(\"expected panic\")\n\t\t}\n\t\trecString, ok := rec.(string)\n\t\tif !ok {\n\t\t\tt.Fatal(\"not a string\", rec)\n\t\t} else if recString != errNotAUser {\n\t\t\tt.Fatal(\"invalid error\", recString)\n\t\t}\n\t}()\n\tSign(\"Hey!\")\n}\n\nfunc TestSign_Double(t *testing.T) {\n\t// Should not allow signing twice.\n\tguestbook = avl.Tree{}\n\thasSigned = avl.Tree{}\n\n\tstd.TestSetRealm(std.NewUserRealm(\"g1user\"))\n\tSign(\"Hello!\")\n\n\tdefer func() {\n\t\trec := recover()\n\t\tif rec == nil {\n\t\t\tt.Fatal(\"expected panic\")\n\t\t}\n\t\trecString, ok := rec.(string)\n\t\tif !ok {\n\t\t\tt.Error(\"type assertion failed\", rec)\n\t\t} else if recString != errAlreadySigned {\n\t\t\tt.Error(\"invalid error message\", recString)\n\t\t}\n\t}()\n\n\tSign(\"Hello again!\")\n}\n\nfunc TestSign_InvalidMessage(t *testing.T) {\n\t// Should not allow control characters in message.\n\tguestbook = avl.Tree{}\n\thasSigned = avl.Tree{}\n\n\tstd.TestSetRealm(std.NewUserRealm(\"g1user\"))\n\n\tdefer func() {\n\t\trec := recover()\n\t\tif rec == nil {\n\t\t\tt.Fatal(\"expected panic\")\n\t\t}\n\t\trecString, ok := rec.(string)\n\t\tif !ok {\n\t\t\tt.Error(\"type assertion failed\", rec)\n\t\t} else if recString != errInvalidCharacterInMessage {\n\t\t\tt.Error(\"invalid error message\", recString)\n\t\t}\n\t}()\n\tSign(\"\\x00Hello!\")\n}\n\nfunc TestAdminDelete(t *testing.T) {\n\tconst (\n\t\tuserAddr std.Address = \"g1user\"\n\t\tadminAddr std.Address = \"g1admin\"\n\t)\n\n\tguestbook = avl.Tree{}\n\thasSigned = avl.Tree{}\n\towner = ownable.NewWithAddress(adminAddr)\n\tsignatureID = 0\n\n\tstd.TestSetRealm(std.NewUserRealm(userAddr))\n\n\tconst bad = \"Very Bad Message! Nyeh heh heh!\"\n\tSign(bad)\n\n\tif rnd := Render(\"\"); !strings.Contains(rnd, bad) {\n\t\tt.Fatal(\"render does not contain bad message\", rnd)\n\t}\n\n\tstd.TestSetRealm(std.NewUserRealm(adminAddr))\n\tAdminDelete(signatureID.String())\n\n\tif rnd := Render(\"\"); strings.Contains(rnd, bad) {\n\t\tt.Error(\"render contains bad message\", rnd)\n\t}\n\tif guestbook.Size() != 0 {\n\t\tt.Error(\"invalid guestbook size\")\n\t}\n\tif hasSigned.Size() != 1 {\n\t\tt.Error(\"invalid hasSigned size\")\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":"home","path":"gno.land/r/morgan/home","files":[{"name":"home.gno","body":"package home\n\nconst staticHome = `# morgan's (gn)home\n\n- [📝 sign my guestbook](/r/morgan/guestbook)\n`\n\nfunc Render(path string) string {\n\treturn staticHome\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":"registry","path":"gno.land/r/stefann/registry","files":[{"name":"registry.gno","body":"package registry\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/ownable\"\n)\n\nvar (\n\tmainAddr std.Address\n\tbackupAddr std.Address\n\towner *ownable.Ownable\n)\n\nfunc init() {\n\tmainAddr = \"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\"\n\tbackupAddr = \"g13awn2575t8s2vf3svlprc4dg0e9z5wchejdxk8\"\n\n\towner = ownable.NewWithAddress(mainAddr)\n}\n\nfunc MainAddr() std.Address {\n\treturn mainAddr\n}\n\nfunc BackupAddr() std.Address {\n\treturn backupAddr\n}\n\nfunc SetMainAddr(addr std.Address) error {\n\tif !addr.IsValid() {\n\t\treturn errors.New(\"config: invalid address\")\n\t}\n\n\towner.AssertCallerIsOwner()\n\n\tmainAddr = addr\n\treturn nil\n}\n\nfunc SetBackupAddr(addr std.Address) error {\n\tif !addr.IsValid() {\n\t\treturn errors.New(\"config: invalid address\")\n\t}\n\n\towner.AssertCallerIsOwner()\n\n\tbackupAddr = addr\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_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"home","path":"gno.land/r/stefann/home","files":[{"name":"home.gno","body":"package home\n\nimport (\n\t\"sort\"\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/ufmt\"\n\n\t\"gno.land/r/stefann/registry\"\n)\n\ntype City struct {\n\tName string\n\tURL string\n}\n\ntype Sponsor struct {\n\tAddress std.Address\n\tAmount std.Coins\n}\n\ntype Profile struct {\n\tpfp string\n\taboutMe []string\n}\n\ntype Travel struct {\n\tcities []City\n\tcurrentCityIndex int\n\tjarLink string\n}\n\ntype Sponsorship struct {\n\tmaxSponsors int\n\tsponsors *avl.Tree\n\tDonationsCount int\n\tsponsorsCount int\n}\n\nvar (\n\tprofile Profile\n\ttravel Travel\n\tsponsorship Sponsorship\n\towner *ownable.Ownable\n)\n\nfunc init() {\n\towner = ownable.NewWithAddress(registry.MainAddr())\n\n\tprofile = Profile{\n\t\tpfp: \"https://i.ibb.co/Bc5YNCx/DSC-0095a.jpg\",\n\t\taboutMe: []string{\n\t\t\t`### About Me`,\n\t\t\t`Hey there! I’m Stefan, a student of Computer Science. I’m all about exploring and adventure — whether it’s diving into the latest tech or discovering a new city, I’m always up for the challenge!`,\n\n\t\t\t`### Contributions`,\n\t\t\t`I'm just getting started, but you can follow my journey through gno.land right [here](https://github.com/gnolang/hackerspace/issues/94) 🔗`,\n\t\t},\n\t}\n\n\ttravel = Travel{\n\t\tcities: []City{\n\t\t\t{Name: \"Venice\", URL: \"https://i.ibb.co/1mcZ7b1/venice.jpg\"},\n\t\t\t{Name: \"Tokyo\", URL: \"https://i.ibb.co/wNDJv3H/tokyo.jpg\"},\n\t\t\t{Name: \"São Paulo\", URL: \"https://i.ibb.co/yWMq2Sn/sao-paulo.jpg\"},\n\t\t\t{Name: \"Toronto\", URL: \"https://i.ibb.co/pb95HJB/toronto.jpg\"},\n\t\t\t{Name: \"Bangkok\", URL: \"https://i.ibb.co/pQy3w2g/bangkok.jpg\"},\n\t\t\t{Name: \"New York\", URL: \"https://i.ibb.co/6JWLm0h/new-york.jpg\"},\n\t\t\t{Name: \"Paris\", URL: \"https://i.ibb.co/q9vf6Hs/paris.jpg\"},\n\t\t\t{Name: \"Kandersteg\", URL: \"https://i.ibb.co/60DzywD/kandersteg.jpg\"},\n\t\t\t{Name: \"Rothenburg\", URL: \"https://i.ibb.co/cr8d2rQ/rothenburg.jpg\"},\n\t\t\t{Name: \"Capetown\", URL: \"https://i.ibb.co/bPGn0v3/capetown.jpg\"},\n\t\t\t{Name: \"Sydney\", URL: \"https://i.ibb.co/TBNzqfy/sydney.jpg\"},\n\t\t\t{Name: \"Oeschinen Lake\", URL: \"https://i.ibb.co/QJQwp2y/oeschinen-lake.jpg\"},\n\t\t\t{Name: \"Barra Grande\", URL: \"https://i.ibb.co/z4RXKc1/barra-grande.jpg\"},\n\t\t\t{Name: \"London\", URL: \"https://i.ibb.co/CPGtvgr/london.jpg\"},\n\t\t},\n\t\tcurrentCityIndex: 0,\n\t\tjarLink: \"https://TODO\", // This value should be injected through UpdateJarLink after deployment.\n\t}\n\n\tsponsorship = Sponsorship{\n\t\tmaxSponsors: 5,\n\t\tsponsors: avl.NewTree(),\n\t\tDonationsCount: 0,\n\t\tsponsorsCount: 0,\n\t}\n}\n\nfunc UpdateCities(newCities []City) {\n\towner.AssertCallerIsOwner()\n\ttravel.cities = newCities\n}\n\nfunc AddCities(newCities ...City) {\n\towner.AssertCallerIsOwner()\n\n\ttravel.cities = append(travel.cities, newCities...)\n}\n\nfunc UpdateJarLink(newLink string) {\n\towner.AssertCallerIsOwner()\n\ttravel.jarLink = newLink\n}\n\nfunc UpdatePFP(url string) {\n\towner.AssertCallerIsOwner()\n\tprofile.pfp = url\n}\n\nfunc UpdateAboutMe(aboutMeStr string) {\n\towner.AssertCallerIsOwner()\n\tprofile.aboutMe = strings.Split(aboutMeStr, \"|\")\n}\n\nfunc AddAboutMeRows(newRows ...string) {\n\towner.AssertCallerIsOwner()\n\n\tprofile.aboutMe = append(profile.aboutMe, newRows...)\n}\n\nfunc UpdateMaxSponsors(newMax int) {\n\towner.AssertCallerIsOwner()\n\tif newMax \u003c= 0 {\n\t\tpanic(\"maxSponsors must be greater than zero\")\n\t}\n\tsponsorship.maxSponsors = newMax\n}\n\nfunc Donate() {\n\taddress := std.OrigCaller()\n\tamount := std.OrigSend()\n\n\tif amount.AmountOf(\"ugnot\") == 0 {\n\t\tpanic(\"Donation must include GNOT\")\n\t}\n\n\texistingAmount, exists := sponsorship.sponsors.Get(address.String())\n\tif exists {\n\t\tupdatedAmount := existingAmount.(std.Coins).Add(amount)\n\t\tsponsorship.sponsors.Set(address.String(), updatedAmount)\n\t} else {\n\t\tsponsorship.sponsors.Set(address.String(), amount)\n\t\tsponsorship.sponsorsCount++\n\t}\n\n\ttravel.currentCityIndex++\n\tsponsorship.DonationsCount++\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\townerAddr := registry.MainAddr()\n\tbanker.SendCoins(std.CurrentRealm().Addr(), ownerAddr, banker.GetCoins(std.CurrentRealm().Addr()))\n}\n\ntype SponsorSlice []Sponsor\n\nfunc (s SponsorSlice) Len() int {\n\treturn len(s)\n}\n\nfunc (s SponsorSlice) Less(i, j int) bool {\n\treturn s[i].Amount.AmountOf(\"ugnot\") \u003e s[j].Amount.AmountOf(\"ugnot\")\n}\n\nfunc (s SponsorSlice) Swap(i, j int) {\n\ts[i], s[j] = s[j], s[i]\n}\n\nfunc GetTopSponsors() []Sponsor {\n\tvar sponsorSlice SponsorSlice\n\n\tsponsorship.sponsors.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\taddr := std.Address(key)\n\t\tamount := value.(std.Coins)\n\t\tsponsorSlice = append(sponsorSlice, Sponsor{Address: addr, Amount: amount})\n\t\treturn false\n\t})\n\n\tsort.Sort(sponsorSlice)\n\treturn sponsorSlice\n}\n\nfunc GetTotalDonations() int {\n\ttotal := 0\n\tsponsorship.sponsors.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\ttotal += int(value.(std.Coins).AmountOf(\"ugnot\"))\n\t\treturn false\n\t})\n\treturn total\n}\n\nfunc Render(path string) string {\n\tout := ufmt.Sprintf(\"# Exploring %s!\\n\\n\", travel.cities[travel.currentCityIndex].Name)\n\n\tout += renderAboutMe()\n\tout += \"\\n\\n\"\n\tout += renderTips()\n\n\treturn out\n}\n\nfunc renderAboutMe() string {\n\tout := \"\u003cdiv class='rows-3'\u003e\"\n\n\tout += \"\u003cdiv style='position: relative; text-align: center;'\u003e\\n\\n\"\n\n\tout += ufmt.Sprintf(\"\u003cdiv style='background-image: url(%s); background-size: cover; background-position: center; width: 100%%; height: 600px; position: relative; border-radius: 15px; overflow: hidden;'\u003e\\n\\n\", travel.cities[travel.currentCityIndex%len(travel.cities)].URL)\n\n\tout += ufmt.Sprintf(\"\u003cimg src='%s' alt='my profile pic' style='width: 250px; height: auto; aspect-ratio: 1 / 1; object-fit: cover; border-radius: 50%%; border: 3px solid #1e1e1e; position: absolute; top: 75%%; left: 50%%; transform: translate(-50%%, -50%%);'\u003e\\n\\n\", profile.pfp)\n\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\tfor _, rows := range profile.aboutMe {\n\t\tout += \"\u003cdiv\u003e\\n\\n\"\n\t\tout += rows + \"\\n\\n\"\n\t\tout += \"\u003c/div\u003e\\n\\n\"\n\t}\n\n\tout += \"\u003c/div\u003e\u003c!-- /rows-3 --\u003e\\n\\n\"\n\n\treturn out\n}\n\nfunc renderTips() string {\n\tout := `\u003cdiv class=\"jumbotron\" style=\"display: flex; flex-direction: column; justify-content: flex-start; align-items: center; padding-top: 40px; padding-bottom: 50px; text-align: center;\"\u003e` + \"\\n\\n\"\n\n\tout += `\u003cdiv class=\"rows-2\" style=\"max-width: 500px; width: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center;\"\u003e` + \"\\n\"\n\n\tout += `\u003ch1 style=\"margin-bottom: 50px;\"\u003eHelp Me Travel The World\u003c/h1\u003e` + \"\\n\\n\"\n\n\tout += renderTipsJar() + \"\\n\"\n\n\tout += ufmt.Sprintf(`\u003cstrong style=\"font-size: 1.2em;\"\u003eI am currently in %s, \u003cbr\u003e tip the jar to send me somewhere else!\u003c/strong\u003e`, travel.cities[travel.currentCityIndex].Name)\n\n\tout += `\u003cbr\u003e\u003cspan style=\"font-size: 1.2em; font-style: italic; margin-top: 10px; display: inline-block;\"\u003eClick the jar, tip in GNOT coins, and watch my background change as I head to a new adventure!\u003c/span\u003e\u003c/p\u003e` + \"\\n\\n\"\n\n\tout += renderSponsors()\n\n\tout += `\u003c/div\u003e\u003c!-- /rows-2 --\u003e` + \"\\n\\n\"\n\n\tout += `\u003c/div\u003e\u003c!-- /jumbotron --\u003e` + \"\\n\"\n\n\treturn out\n}\n\nfunc formatAddress(address string) string {\n\tif len(address) \u003c= 8 {\n\t\treturn address\n\t}\n\treturn address[:4] + \"...\" + address[len(address)-4:]\n}\n\nfunc renderSponsors() string {\n\tout := `\u003ch3 style=\"margin-top: 5px; margin-bottom: 20px\"\u003eSponsor Leaderboard\u003c/h3\u003e` + \"\\n\"\n\n\tif sponsorship.sponsorsCount == 0 {\n\t\treturn out + `\u003cp style=\"text-align: center;\"\u003eNo sponsors yet. Be the first to tip the jar!\u003c/p\u003e` + \"\\n\"\n\t}\n\n\ttopSponsors := GetTopSponsors()\n\tnumSponsors := len(topSponsors)\n\tif numSponsors \u003e sponsorship.maxSponsors {\n\t\tnumSponsors = sponsorship.maxSponsors\n\t}\n\n\tout += `\u003cul style=\"list-style-type: none; padding: 0; border: 1px solid #ddd; border-radius: 8px; width: 100%; max-width: 300px; margin: 0 auto;\"\u003e` + \"\\n\"\n\n\tfor i := 0; i \u003c numSponsors; i++ {\n\t\tsponsor := topSponsors[i]\n\t\tisLastItem := (i == numSponsors-1)\n\n\t\tpadding := \"10px 5px\"\n\t\tborder := \"border-bottom: 1px solid #ddd;\"\n\n\t\tif isLastItem {\n\t\t\tpadding = \"8px 5px\"\n\t\t\tborder = \"\"\n\t\t}\n\n\t\tout += ufmt.Sprintf(\n\t\t\t`\u003cli style=\"padding: %s; %s text-align: left;\"\u003e\n\t\t\t\t\u003cstrong style=\"padding-left: 5px;\"\u003e%d. %s\u003c/strong\u003e\n\t\t\t\t\u003cspan style=\"float: right; padding-right: 5px;\"\u003e%s\u003c/span\u003e\n\t\t\t\u003c/li\u003e`,\n\t\t\tpadding, border, i+1, formatAddress(sponsor.Address.String()), sponsor.Amount.String(),\n\t\t)\n\t}\n\n\treturn out\n}\n\nfunc renderTipsJar() string {\n\tout := ufmt.Sprintf(`\u003ca href=\"%s\" target=\"_blank\" style=\"display: block; text-decoration: none;\"\u003e`, travel.jarLink) + \"\\n\"\n\n\tout += `\u003cimg src=\"https://i.ibb.co/4TH9zbw/tips-jar.png\" alt=\"Tips Jar\" style=\"width: 300px; height: auto; display: block; margin: 0 auto;\"\u003e` + \"\\n\"\n\n\tout += `\u003c/a\u003e` + \"\\n\"\n\n\treturn out\n}\n"},{"name":"home_test.gno","body":"package home\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/testutils\"\n)\n\nfunc TestUpdatePFP(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOrigCaller(owner)\n\n\tprofile.pfp = \"\"\n\n\tUpdatePFP(\"https://example.com/pic.png\")\n\n\tif profile.pfp != \"https://example.com/pic.png\" {\n\t\tt.Fatalf(\"expected pfp to be https://example.com/pic.png, got %s\", profile.pfp)\n\t}\n}\n\nfunc TestUpdateAboutMe(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOrigCaller(owner)\n\n\tprofile.aboutMe = []string{}\n\n\tUpdateAboutMe(\"This is my new bio.|I love coding!\")\n\n\texpected := []string{\"This is my new bio.\", \"I love coding!\"}\n\n\tif len(profile.aboutMe) != len(expected) {\n\t\tt.Fatalf(\"expected aboutMe to have length %d, got %d\", len(expected), len(profile.aboutMe))\n\t}\n\n\tfor i := range profile.aboutMe {\n\t\tif profile.aboutMe[i] != expected[i] {\n\t\t\tt.Fatalf(\"expected aboutMe[%d] to be %s, got %s\", i, expected[i], profile.aboutMe[i])\n\t\t}\n\t}\n}\n\nfunc TestUpdateCities(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOrigCaller(owner)\n\n\ttravel.cities = []City{}\n\n\tnewCities := []City{\n\t\t{Name: \"Berlin\", URL: \"https://example.com/berlin.jpg\"},\n\t\t{Name: \"Vienna\", URL: \"https://example.com/vienna.jpg\"},\n\t}\n\n\tUpdateCities(newCities)\n\n\tif len(travel.cities) != 2 {\n\t\tt.Fatalf(\"expected 2 cities, got %d\", len(travel.cities))\n\t}\n\n\tif travel.cities[0].Name != \"Berlin\" || travel.cities[1].Name != \"Vienna\" {\n\t\tt.Fatalf(\"expected cities to be updated to Berlin and Vienna, got %+v\", travel.cities)\n\t}\n}\n\nfunc TestUpdateJarLink(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOrigCaller(owner)\n\n\ttravel.jarLink = \"\"\n\n\tUpdateJarLink(\"https://example.com/jar\")\n\n\tif travel.jarLink != \"https://example.com/jar\" {\n\t\tt.Fatalf(\"expected jarLink to be https://example.com/jar, got %s\", travel.jarLink)\n\t}\n}\n\nfunc TestUpdateMaxSponsors(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOrigCaller(owner)\n\n\tsponsorship.maxSponsors = 0\n\n\tUpdateMaxSponsors(10)\n\n\tif sponsorship.maxSponsors != 10 {\n\t\tt.Fatalf(\"expected maxSponsors to be 10, got %d\", sponsorship.maxSponsors)\n\t}\n\n\tdefer func() {\n\t\tif r := recover(); r == nil {\n\t\t\tt.Fatalf(\"expected panic for setting maxSponsors to 0\")\n\t\t}\n\t}()\n\tUpdateMaxSponsors(0)\n}\n\nfunc TestAddCities(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOrigCaller(owner)\n\n\ttravel.cities = []City{}\n\n\tAddCities(City{Name: \"Berlin\", URL: \"https://example.com/berlin.jpg\"})\n\n\tif len(travel.cities) != 1 {\n\t\tt.Fatalf(\"expected 1 city, got %d\", len(travel.cities))\n\t}\n\tif travel.cities[0].Name != \"Berlin\" || travel.cities[0].URL != \"https://example.com/berlin.jpg\" {\n\t\tt.Fatalf(\"expected city to be Berlin, got %+v\", travel.cities[0])\n\t}\n\n\tAddCities(\n\t\tCity{Name: \"Paris\", URL: \"https://example.com/paris.jpg\"},\n\t\tCity{Name: \"Tokyo\", URL: \"https://example.com/tokyo.jpg\"},\n\t)\n\n\tif len(travel.cities) != 3 {\n\t\tt.Fatalf(\"expected 3 cities, got %d\", len(travel.cities))\n\t}\n\tif travel.cities[1].Name != \"Paris\" || travel.cities[2].Name != \"Tokyo\" {\n\t\tt.Fatalf(\"expected cities to be Paris and Tokyo, got %+v\", travel.cities[1:])\n\t}\n}\n\nfunc TestAddAboutMeRows(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOrigCaller(owner)\n\n\tprofile.aboutMe = []string{}\n\n\tAddAboutMeRows(\"I love exploring new technologies!\")\n\n\tif len(profile.aboutMe) != 1 {\n\t\tt.Fatalf(\"expected 1 aboutMe row, got %d\", len(profile.aboutMe))\n\t}\n\tif profile.aboutMe[0] != \"I love exploring new technologies!\" {\n\t\tt.Fatalf(\"expected first aboutMe row to be 'I love exploring new technologies!', got %s\", profile.aboutMe[0])\n\t}\n\n\tAddAboutMeRows(\"Travel is my passion!\", \"Always learning.\")\n\n\tif len(profile.aboutMe) != 3 {\n\t\tt.Fatalf(\"expected 3 aboutMe rows, got %d\", len(profile.aboutMe))\n\t}\n\tif profile.aboutMe[1] != \"Travel is my passion!\" || profile.aboutMe[2] != \"Always learning.\" {\n\t\tt.Fatalf(\"expected aboutMe rows to be 'Travel is my passion!' and 'Always learning.', got %+v\", profile.aboutMe[1:])\n\t}\n}\n\nfunc TestDonate(t *testing.T) {\n\tvar user = testutils.TestAddress(\"user\")\n\tstd.TestSetOrigCaller(user)\n\n\tsponsorship.sponsors = avl.NewTree()\n\tsponsorship.DonationsCount = 0\n\tsponsorship.sponsorsCount = 0\n\ttravel.currentCityIndex = 0\n\n\tcoinsSent := std.NewCoins(std.NewCoin(\"ugnot\", 500))\n\tstd.TestSetOrigSend(coinsSent, std.NewCoins())\n\tDonate()\n\n\texistingAmount, exists := sponsorship.sponsors.Get(string(user))\n\tif !exists {\n\t\tt.Fatalf(\"expected sponsor to be added, but it was not found\")\n\t}\n\n\tif existingAmount.(std.Coins).AmountOf(\"ugnot\") != 500 {\n\t\tt.Fatalf(\"expected donation amount to be 500ugnot, got %d\", existingAmount.(std.Coins).AmountOf(\"ugnot\"))\n\t}\n\n\tif sponsorship.DonationsCount != 1 {\n\t\tt.Fatalf(\"expected DonationsCount to be 1, got %d\", sponsorship.DonationsCount)\n\t}\n\n\tif sponsorship.sponsorsCount != 1 {\n\t\tt.Fatalf(\"expected sponsorsCount to be 1, got %d\", sponsorship.sponsorsCount)\n\t}\n\n\tif travel.currentCityIndex != 1 {\n\t\tt.Fatalf(\"expected currentCityIndex to be 1, got %d\", travel.currentCityIndex)\n\t}\n\n\tcoinsSent = std.NewCoins(std.NewCoin(\"ugnot\", 300))\n\tstd.TestSetOrigSend(coinsSent, std.NewCoins())\n\tDonate()\n\n\texistingAmount, exists = sponsorship.sponsors.Get(string(user))\n\tif !exists {\n\t\tt.Fatalf(\"expected sponsor to exist after second donation, but it was not found\")\n\t}\n\n\tif existingAmount.(std.Coins).AmountOf(\"ugnot\") != 800 {\n\t\tt.Fatalf(\"expected total donation amount to be 800ugnot, got %d\", existingAmount.(std.Coins).AmountOf(\"ugnot\"))\n\t}\n\n\tif sponsorship.DonationsCount != 2 {\n\t\tt.Fatalf(\"expected DonationsCount to be 2 after second donation, got %d\", sponsorship.DonationsCount)\n\t}\n\n\tif travel.currentCityIndex != 2 {\n\t\tt.Fatalf(\"expected currentCityIndex to be 2 after second donation, got %d\", travel.currentCityIndex)\n\t}\n}\n\nfunc TestGetTopSponsors(t *testing.T) {\n\tvar user = testutils.TestAddress(\"user\")\n\tstd.TestSetOrigCaller(user)\n\n\tsponsorship.sponsors = avl.NewTree()\n\tsponsorship.sponsorsCount = 0\n\n\tsponsorship.sponsors.Set(\"g1address1\", std.NewCoins(std.NewCoin(\"ugnot\", 300)))\n\tsponsorship.sponsors.Set(\"g1address2\", std.NewCoins(std.NewCoin(\"ugnot\", 500)))\n\tsponsorship.sponsors.Set(\"g1address3\", std.NewCoins(std.NewCoin(\"ugnot\", 200)))\n\tsponsorship.sponsorsCount = 3\n\n\ttopSponsors := GetTopSponsors()\n\n\tif len(topSponsors) != 3 {\n\t\tt.Fatalf(\"expected 3 sponsors, got %d\", len(topSponsors))\n\t}\n\n\tif topSponsors[0].Address.String() != \"g1address2\" || topSponsors[0].Amount.AmountOf(\"ugnot\") != 500 {\n\t\tt.Fatalf(\"expected top sponsor to be g1address2 with 500ugnot, got %s with %dugnot\", topSponsors[0].Address.String(), topSponsors[0].Amount.AmountOf(\"ugnot\"))\n\t}\n\n\tif topSponsors[1].Address.String() != \"g1address1\" || topSponsors[1].Amount.AmountOf(\"ugnot\") != 300 {\n\t\tt.Fatalf(\"expected second sponsor to be g1address1 with 300ugnot, got %s with %dugnot\", topSponsors[1].Address.String(), topSponsors[1].Amount.AmountOf(\"ugnot\"))\n\t}\n\n\tif topSponsors[2].Address.String() != \"g1address3\" || topSponsors[2].Amount.AmountOf(\"ugnot\") != 200 {\n\t\tt.Fatalf(\"expected third sponsor to be g1address3 with 200ugnot, got %s with %dugnot\", topSponsors[2].Address.String(), topSponsors[2].Amount.AmountOf(\"ugnot\"))\n\t}\n}\n\nfunc TestGetTotalDonations(t *testing.T) {\n\tvar user = testutils.TestAddress(\"user\")\n\tstd.TestSetOrigCaller(user)\n\n\tsponsorship.sponsors = avl.NewTree()\n\tsponsorship.sponsorsCount = 0\n\n\tsponsorship.sponsors.Set(\"g1address1\", std.NewCoins(std.NewCoin(\"ugnot\", 300)))\n\tsponsorship.sponsors.Set(\"g1address2\", std.NewCoins(std.NewCoin(\"ugnot\", 500)))\n\tsponsorship.sponsors.Set(\"g1address3\", std.NewCoins(std.NewCoin(\"ugnot\", 200)))\n\tsponsorship.sponsorsCount = 3\n\n\ttotalDonations := GetTotalDonations()\n\n\tif totalDonations != 1000 {\n\t\tt.Fatalf(\"expected total donations to be 1000ugnot, got %dugnot\", totalDonations)\n\t}\n}\n\nfunc TestRender(t *testing.T) {\n\ttravel.currentCityIndex = 0\n\ttravel.cities = []City{\n\t\t{Name: \"Venice\", URL: \"https://example.com/venice.jpg\"},\n\t\t{Name: \"Paris\", URL: \"https://example.com/paris.jpg\"},\n\t}\n\n\toutput := Render(\"\")\n\n\texpectedCity := \"Venice\"\n\tif !strings.Contains(output, expectedCity) {\n\t\tt.Fatalf(\"expected output to contain city name '%s', got %s\", expectedCity, output)\n\t}\n\n\texpectedURL := \"https://example.com/venice.jpg\"\n\tif !strings.Contains(output, expectedURL) {\n\t\tt.Fatalf(\"expected output to contain city URL '%s', got %s\", expectedURL, output)\n\t}\n\n\ttravel.currentCityIndex = 1\n\toutput = Render(\"\")\n\n\texpectedCity = \"Paris\"\n\tif !strings.Contains(output, expectedCity) {\n\t\tt.Fatalf(\"expected output to contain city name '%s', got %s\", expectedCity, output)\n\t}\n\n\texpectedURL = \"https://example.com/paris.jpg\"\n\tif !strings.Contains(output, expectedURL) {\n\t\tt.Fatalf(\"expected output to contain city URL '%s', got %s\", expectedURL, output)\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":"home","path":"gno.land/r/stefann/home","files":[{"name":"home.gno","body":"package home\n\nimport (\n\t\"sort\"\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/ufmt\"\n\n\t\"gno.land/r/stefann/registry\"\n)\n\ntype City struct {\n\tName string\n\tURL string\n}\n\ntype Sponsor struct {\n\tAddress std.Address\n\tAmount std.Coins\n}\n\ntype Profile struct {\n\tpfp string\n\taboutMe []string\n}\n\ntype Travel struct {\n\tcities []City\n\tcurrentCityIndex int\n\tjarLink string\n}\n\ntype Sponsorship struct {\n\tmaxSponsors int\n\tsponsors *avl.Tree\n\tDonationsCount int\n\tsponsorsCount int\n}\n\nvar (\n\tprofile Profile\n\ttravel Travel\n\tsponsorship Sponsorship\n\towner *ownable.Ownable\n)\n\nfunc init() {\n\towner = ownable.NewWithAddress(registry.MainAddr())\n\n\tprofile = Profile{\n\t\tpfp: \"https://i.ibb.co/Bc5YNCx/DSC-0095a.jpg\",\n\t\taboutMe: []string{\n\t\t\t`### About Me`,\n\t\t\t`Hey there! I’m Stefan, a student of Computer Science. I’m all about exploring and adventure — whether it’s diving into the latest tech or discovering a new city, I’m always up for the challenge!`,\n\n\t\t\t`### Contributions`,\n\t\t\t`I'm just getting started, but you can follow my journey through gno.land right [here](https://github.com/gnolang/hackerspace/issues/94) 🔗`,\n\t\t},\n\t}\n\n\ttravel = Travel{\n\t\tcities: []City{\n\t\t\t{Name: \"Venice\", URL: \"https://i.ibb.co/1mcZ7b1/venice.jpg\"},\n\t\t\t{Name: \"Tokyo\", URL: \"https://i.ibb.co/wNDJv3H/tokyo.jpg\"},\n\t\t\t{Name: \"São Paulo\", URL: \"https://i.ibb.co/yWMq2Sn/sao-paulo.jpg\"},\n\t\t\t{Name: \"Toronto\", URL: \"https://i.ibb.co/pb95HJB/toronto.jpg\"},\n\t\t\t{Name: \"Bangkok\", URL: \"https://i.ibb.co/pQy3w2g/bangkok.jpg\"},\n\t\t\t{Name: \"New York\", URL: \"https://i.ibb.co/6JWLm0h/new-york.jpg\"},\n\t\t\t{Name: \"Paris\", URL: \"https://i.ibb.co/q9vf6Hs/paris.jpg\"},\n\t\t\t{Name: \"Kandersteg\", URL: \"https://i.ibb.co/60DzywD/kandersteg.jpg\"},\n\t\t\t{Name: \"Rothenburg\", URL: \"https://i.ibb.co/cr8d2rQ/rothenburg.jpg\"},\n\t\t\t{Name: \"Capetown\", URL: \"https://i.ibb.co/bPGn0v3/capetown.jpg\"},\n\t\t\t{Name: \"Sydney\", URL: \"https://i.ibb.co/TBNzqfy/sydney.jpg\"},\n\t\t\t{Name: \"Oeschinen Lake\", URL: \"https://i.ibb.co/QJQwp2y/oeschinen-lake.jpg\"},\n\t\t\t{Name: \"Barra Grande\", URL: \"https://i.ibb.co/z4RXKc1/barra-grande.jpg\"},\n\t\t\t{Name: \"London\", URL: \"https://i.ibb.co/CPGtvgr/london.jpg\"},\n\t\t},\n\t\tcurrentCityIndex: 0,\n\t\tjarLink: \"https://TODO\", // This value should be injected through UpdateJarLink after deployment.\n\t}\n\n\tsponsorship = Sponsorship{\n\t\tmaxSponsors: 5,\n\t\tsponsors: avl.NewTree(),\n\t\tDonationsCount: 0,\n\t\tsponsorsCount: 0,\n\t}\n}\n\nfunc UpdateCities(newCities []City) {\n\towner.AssertCallerIsOwner()\n\ttravel.cities = newCities\n}\n\nfunc AddCities(newCities ...City) {\n\towner.AssertCallerIsOwner()\n\n\ttravel.cities = append(travel.cities, newCities...)\n}\n\nfunc UpdateJarLink(newLink string) {\n\towner.AssertCallerIsOwner()\n\ttravel.jarLink = newLink\n}\n\nfunc UpdatePFP(url string) {\n\towner.AssertCallerIsOwner()\n\tprofile.pfp = url\n}\n\nfunc UpdateAboutMe(aboutMeStr string) {\n\towner.AssertCallerIsOwner()\n\tprofile.aboutMe = strings.Split(aboutMeStr, \"|\")\n}\n\nfunc AddAboutMeRows(newRows ...string) {\n\towner.AssertCallerIsOwner()\n\n\tprofile.aboutMe = append(profile.aboutMe, newRows...)\n}\n\nfunc UpdateMaxSponsors(newMax int) {\n\towner.AssertCallerIsOwner()\n\tif newMax \u003c= 0 {\n\t\tpanic(\"maxSponsors must be greater than zero\")\n\t}\n\tsponsorship.maxSponsors = newMax\n}\n\nfunc Donate() {\n\taddress := std.OriginCaller()\n\tamount := std.OriginSend()\n\n\tif amount.AmountOf(\"ugnot\") == 0 {\n\t\tpanic(\"Donation must include GNOT\")\n\t}\n\n\texistingAmount, exists := sponsorship.sponsors.Get(address.String())\n\tif exists {\n\t\tupdatedAmount := existingAmount.(std.Coins).Add(amount)\n\t\tsponsorship.sponsors.Set(address.String(), updatedAmount)\n\t} else {\n\t\tsponsorship.sponsors.Set(address.String(), amount)\n\t\tsponsorship.sponsorsCount++\n\t}\n\n\ttravel.currentCityIndex++\n\tsponsorship.DonationsCount++\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\townerAddr := registry.MainAddr()\n\tbanker.SendCoins(std.CurrentRealm().Addr(), ownerAddr, banker.GetCoins(std.CurrentRealm().Addr()))\n}\n\ntype SponsorSlice []Sponsor\n\nfunc (s SponsorSlice) Len() int {\n\treturn len(s)\n}\n\nfunc (s SponsorSlice) Less(i, j int) bool {\n\treturn s[i].Amount.AmountOf(\"ugnot\") \u003e s[j].Amount.AmountOf(\"ugnot\")\n}\n\nfunc (s SponsorSlice) Swap(i, j int) {\n\ts[i], s[j] = s[j], s[i]\n}\n\nfunc GetTopSponsors() []Sponsor {\n\tvar sponsorSlice SponsorSlice\n\n\tsponsorship.sponsors.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\taddr := std.Address(key)\n\t\tamount := value.(std.Coins)\n\t\tsponsorSlice = append(sponsorSlice, Sponsor{Address: addr, Amount: amount})\n\t\treturn false\n\t})\n\n\tsort.Sort(sponsorSlice)\n\treturn sponsorSlice\n}\n\nfunc GetTotalDonations() int {\n\ttotal := 0\n\tsponsorship.sponsors.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\ttotal += int(value.(std.Coins).AmountOf(\"ugnot\"))\n\t\treturn false\n\t})\n\treturn total\n}\n\nfunc Render(path string) string {\n\tout := ufmt.Sprintf(\"# Exploring %s!\\n\\n\", travel.cities[travel.currentCityIndex].Name)\n\n\tout += renderAboutMe()\n\tout += \"\\n\\n\"\n\tout += renderTips()\n\n\treturn out\n}\n\nfunc renderAboutMe() string {\n\tout := \"\u003cdiv class='rows-3'\u003e\"\n\n\tout += \"\u003cdiv style='position: relative; text-align: center;'\u003e\\n\\n\"\n\n\tout += ufmt.Sprintf(\"\u003cdiv style='background-image: url(%s); background-size: cover; background-position: center; width: 100%%; height: 600px; position: relative; border-radius: 15px; overflow: hidden;'\u003e\\n\\n\", travel.cities[travel.currentCityIndex%len(travel.cities)].URL)\n\n\tout += ufmt.Sprintf(\"\u003cimg src='%s' alt='my profile pic' style='width: 250px; height: auto; aspect-ratio: 1 / 1; object-fit: cover; border-radius: 50%%; border: 3px solid #1e1e1e; position: absolute; top: 75%%; left: 50%%; transform: translate(-50%%, -50%%);'\u003e\\n\\n\", profile.pfp)\n\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\tfor _, rows := range profile.aboutMe {\n\t\tout += \"\u003cdiv\u003e\\n\\n\"\n\t\tout += rows + \"\\n\\n\"\n\t\tout += \"\u003c/div\u003e\\n\\n\"\n\t}\n\n\tout += \"\u003c/div\u003e\u003c!-- /rows-3 --\u003e\\n\\n\"\n\n\treturn out\n}\n\nfunc renderTips() string {\n\tout := `\u003cdiv class=\"jumbotron\" style=\"display: flex; flex-direction: column; justify-content: flex-start; align-items: center; padding-top: 40px; padding-bottom: 50px; text-align: center;\"\u003e` + \"\\n\\n\"\n\n\tout += `\u003cdiv class=\"rows-2\" style=\"max-width: 500px; width: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center;\"\u003e` + \"\\n\"\n\n\tout += `\u003ch1 style=\"margin-bottom: 50px;\"\u003eHelp Me Travel The World\u003c/h1\u003e` + \"\\n\\n\"\n\n\tout += renderTipsJar() + \"\\n\"\n\n\tout += ufmt.Sprintf(`\u003cstrong style=\"font-size: 1.2em;\"\u003eI am currently in %s, \u003cbr\u003e tip the jar to send me somewhere else!\u003c/strong\u003e`, travel.cities[travel.currentCityIndex].Name)\n\n\tout += `\u003cbr\u003e\u003cspan style=\"font-size: 1.2em; font-style: italic; margin-top: 10px; display: inline-block;\"\u003eClick the jar, tip in GNOT coins, and watch my background change as I head to a new adventure!\u003c/span\u003e\u003c/p\u003e` + \"\\n\\n\"\n\n\tout += renderSponsors()\n\n\tout += `\u003c/div\u003e\u003c!-- /rows-2 --\u003e` + \"\\n\\n\"\n\n\tout += `\u003c/div\u003e\u003c!-- /jumbotron --\u003e` + \"\\n\"\n\n\treturn out\n}\n\nfunc formatAddress(address string) string {\n\tif len(address) \u003c= 8 {\n\t\treturn address\n\t}\n\treturn address[:4] + \"...\" + address[len(address)-4:]\n}\n\nfunc renderSponsors() string {\n\tout := `\u003ch3 style=\"margin-top: 5px; margin-bottom: 20px\"\u003eSponsor Leaderboard\u003c/h3\u003e` + \"\\n\"\n\n\tif sponsorship.sponsorsCount == 0 {\n\t\treturn out + `\u003cp style=\"text-align: center;\"\u003eNo sponsors yet. Be the first to tip the jar!\u003c/p\u003e` + \"\\n\"\n\t}\n\n\ttopSponsors := GetTopSponsors()\n\tnumSponsors := len(topSponsors)\n\tif numSponsors \u003e sponsorship.maxSponsors {\n\t\tnumSponsors = sponsorship.maxSponsors\n\t}\n\n\tout += `\u003cul style=\"list-style-type: none; padding: 0; border: 1px solid #ddd; border-radius: 8px; width: 100%; max-width: 300px; margin: 0 auto;\"\u003e` + \"\\n\"\n\n\tfor i := 0; i \u003c numSponsors; i++ {\n\t\tsponsor := topSponsors[i]\n\t\tisLastItem := (i == numSponsors-1)\n\n\t\tpadding := \"10px 5px\"\n\t\tborder := \"border-bottom: 1px solid #ddd;\"\n\n\t\tif isLastItem {\n\t\t\tpadding = \"8px 5px\"\n\t\t\tborder = \"\"\n\t\t}\n\n\t\tout += ufmt.Sprintf(\n\t\t\t`\u003cli style=\"padding: %s; %s text-align: left;\"\u003e\n\t\t\t\t\u003cstrong style=\"padding-left: 5px;\"\u003e%d. %s\u003c/strong\u003e\n\t\t\t\t\u003cspan style=\"float: right; padding-right: 5px;\"\u003e%s\u003c/span\u003e\n\t\t\t\u003c/li\u003e`,\n\t\t\tpadding, border, i+1, formatAddress(sponsor.Address.String()), sponsor.Amount.String(),\n\t\t)\n\t}\n\n\treturn out\n}\n\nfunc renderTipsJar() string {\n\tout := ufmt.Sprintf(`\u003ca href=\"%s\" target=\"_blank\" style=\"display: block; text-decoration: none;\"\u003e`, travel.jarLink) + \"\\n\"\n\n\tout += `\u003cimg src=\"https://i.ibb.co/4TH9zbw/tips-jar.png\" alt=\"Tips Jar\" style=\"width: 300px; height: auto; display: block; margin: 0 auto;\"\u003e` + \"\\n\"\n\n\tout += `\u003c/a\u003e` + \"\\n\"\n\n\treturn out\n}\n"},{"name":"home_test.gno","body":"package home\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/testutils\"\n)\n\nfunc TestUpdatePFP(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOriginCaller(owner)\n\n\tprofile.pfp = \"\"\n\n\tUpdatePFP(\"https://example.com/pic.png\")\n\n\tif profile.pfp != \"https://example.com/pic.png\" {\n\t\tt.Fatalf(\"expected pfp to be https://example.com/pic.png, got %s\", profile.pfp)\n\t}\n}\n\nfunc TestUpdateAboutMe(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOriginCaller(owner)\n\n\tprofile.aboutMe = []string{}\n\n\tUpdateAboutMe(\"This is my new bio.|I love coding!\")\n\n\texpected := []string{\"This is my new bio.\", \"I love coding!\"}\n\n\tif len(profile.aboutMe) != len(expected) {\n\t\tt.Fatalf(\"expected aboutMe to have length %d, got %d\", len(expected), len(profile.aboutMe))\n\t}\n\n\tfor i := range profile.aboutMe {\n\t\tif profile.aboutMe[i] != expected[i] {\n\t\t\tt.Fatalf(\"expected aboutMe[%d] to be %s, got %s\", i, expected[i], profile.aboutMe[i])\n\t\t}\n\t}\n}\n\nfunc TestUpdateCities(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOriginCaller(owner)\n\n\ttravel.cities = []City{}\n\n\tnewCities := []City{\n\t\t{Name: \"Berlin\", URL: \"https://example.com/berlin.jpg\"},\n\t\t{Name: \"Vienna\", URL: \"https://example.com/vienna.jpg\"},\n\t}\n\n\tUpdateCities(newCities)\n\n\tif len(travel.cities) != 2 {\n\t\tt.Fatalf(\"expected 2 cities, got %d\", len(travel.cities))\n\t}\n\n\tif travel.cities[0].Name != \"Berlin\" || travel.cities[1].Name != \"Vienna\" {\n\t\tt.Fatalf(\"expected cities to be updated to Berlin and Vienna, got %+v\", travel.cities)\n\t}\n}\n\nfunc TestUpdateJarLink(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOriginCaller(owner)\n\n\ttravel.jarLink = \"\"\n\n\tUpdateJarLink(\"https://example.com/jar\")\n\n\tif travel.jarLink != \"https://example.com/jar\" {\n\t\tt.Fatalf(\"expected jarLink to be https://example.com/jar, got %s\", travel.jarLink)\n\t}\n}\n\nfunc TestUpdateMaxSponsors(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOriginCaller(owner)\n\n\tsponsorship.maxSponsors = 0\n\n\tUpdateMaxSponsors(10)\n\n\tif sponsorship.maxSponsors != 10 {\n\t\tt.Fatalf(\"expected maxSponsors to be 10, got %d\", sponsorship.maxSponsors)\n\t}\n\n\tdefer func() {\n\t\tif r := recover(); r == nil {\n\t\t\tt.Fatalf(\"expected panic for setting maxSponsors to 0\")\n\t\t}\n\t}()\n\tUpdateMaxSponsors(0)\n}\n\nfunc TestAddCities(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOriginCaller(owner)\n\n\ttravel.cities = []City{}\n\n\tAddCities(City{Name: \"Berlin\", URL: \"https://example.com/berlin.jpg\"})\n\n\tif len(travel.cities) != 1 {\n\t\tt.Fatalf(\"expected 1 city, got %d\", len(travel.cities))\n\t}\n\tif travel.cities[0].Name != \"Berlin\" || travel.cities[0].URL != \"https://example.com/berlin.jpg\" {\n\t\tt.Fatalf(\"expected city to be Berlin, got %+v\", travel.cities[0])\n\t}\n\n\tAddCities(\n\t\tCity{Name: \"Paris\", URL: \"https://example.com/paris.jpg\"},\n\t\tCity{Name: \"Tokyo\", URL: \"https://example.com/tokyo.jpg\"},\n\t)\n\n\tif len(travel.cities) != 3 {\n\t\tt.Fatalf(\"expected 3 cities, got %d\", len(travel.cities))\n\t}\n\tif travel.cities[1].Name != \"Paris\" || travel.cities[2].Name != \"Tokyo\" {\n\t\tt.Fatalf(\"expected cities to be Paris and Tokyo, got %+v\", travel.cities[1:])\n\t}\n}\n\nfunc TestAddAboutMeRows(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOriginCaller(owner)\n\n\tprofile.aboutMe = []string{}\n\n\tAddAboutMeRows(\"I love exploring new technologies!\")\n\n\tif len(profile.aboutMe) != 1 {\n\t\tt.Fatalf(\"expected 1 aboutMe row, got %d\", len(profile.aboutMe))\n\t}\n\tif profile.aboutMe[0] != \"I love exploring new technologies!\" {\n\t\tt.Fatalf(\"expected first aboutMe row to be 'I love exploring new technologies!', got %s\", profile.aboutMe[0])\n\t}\n\n\tAddAboutMeRows(\"Travel is my passion!\", \"Always learning.\")\n\n\tif len(profile.aboutMe) != 3 {\n\t\tt.Fatalf(\"expected 3 aboutMe rows, got %d\", len(profile.aboutMe))\n\t}\n\tif profile.aboutMe[1] != \"Travel is my passion!\" || profile.aboutMe[2] != \"Always learning.\" {\n\t\tt.Fatalf(\"expected aboutMe rows to be 'Travel is my passion!' and 'Always learning.', got %+v\", profile.aboutMe[1:])\n\t}\n}\n\nfunc TestDonate(t *testing.T) {\n\tvar user = testutils.TestAddress(\"user\")\n\tstd.TestSetOriginCaller(user)\n\n\tsponsorship.sponsors = avl.NewTree()\n\tsponsorship.DonationsCount = 0\n\tsponsorship.sponsorsCount = 0\n\ttravel.currentCityIndex = 0\n\n\tcoinsSent := std.NewCoins(std.NewCoin(\"ugnot\", 500))\n\tstd.TestSetOriginSend(coinsSent, std.NewCoins())\n\tDonate()\n\n\texistingAmount, exists := sponsorship.sponsors.Get(string(user))\n\tif !exists {\n\t\tt.Fatalf(\"expected sponsor to be added, but it was not found\")\n\t}\n\n\tif existingAmount.(std.Coins).AmountOf(\"ugnot\") != 500 {\n\t\tt.Fatalf(\"expected donation amount to be 500ugnot, got %d\", existingAmount.(std.Coins).AmountOf(\"ugnot\"))\n\t}\n\n\tif sponsorship.DonationsCount != 1 {\n\t\tt.Fatalf(\"expected DonationsCount to be 1, got %d\", sponsorship.DonationsCount)\n\t}\n\n\tif sponsorship.sponsorsCount != 1 {\n\t\tt.Fatalf(\"expected sponsorsCount to be 1, got %d\", sponsorship.sponsorsCount)\n\t}\n\n\tif travel.currentCityIndex != 1 {\n\t\tt.Fatalf(\"expected currentCityIndex to be 1, got %d\", travel.currentCityIndex)\n\t}\n\n\tcoinsSent = std.NewCoins(std.NewCoin(\"ugnot\", 300))\n\tstd.TestSetOriginSend(coinsSent, std.NewCoins())\n\tDonate()\n\n\texistingAmount, exists = sponsorship.sponsors.Get(string(user))\n\tif !exists {\n\t\tt.Fatalf(\"expected sponsor to exist after second donation, but it was not found\")\n\t}\n\n\tif existingAmount.(std.Coins).AmountOf(\"ugnot\") != 800 {\n\t\tt.Fatalf(\"expected total donation amount to be 800ugnot, got %d\", existingAmount.(std.Coins).AmountOf(\"ugnot\"))\n\t}\n\n\tif sponsorship.DonationsCount != 2 {\n\t\tt.Fatalf(\"expected DonationsCount to be 2 after second donation, got %d\", sponsorship.DonationsCount)\n\t}\n\n\tif travel.currentCityIndex != 2 {\n\t\tt.Fatalf(\"expected currentCityIndex to be 2 after second donation, got %d\", travel.currentCityIndex)\n\t}\n}\n\nfunc TestGetTopSponsors(t *testing.T) {\n\tvar user = testutils.TestAddress(\"user\")\n\tstd.TestSetOriginCaller(user)\n\n\tsponsorship.sponsors = avl.NewTree()\n\tsponsorship.sponsorsCount = 0\n\n\tsponsorship.sponsors.Set(\"g1address1\", std.NewCoins(std.NewCoin(\"ugnot\", 300)))\n\tsponsorship.sponsors.Set(\"g1address2\", std.NewCoins(std.NewCoin(\"ugnot\", 500)))\n\tsponsorship.sponsors.Set(\"g1address3\", std.NewCoins(std.NewCoin(\"ugnot\", 200)))\n\tsponsorship.sponsorsCount = 3\n\n\ttopSponsors := GetTopSponsors()\n\n\tif len(topSponsors) != 3 {\n\t\tt.Fatalf(\"expected 3 sponsors, got %d\", len(topSponsors))\n\t}\n\n\tif topSponsors[0].Address.String() != \"g1address2\" || topSponsors[0].Amount.AmountOf(\"ugnot\") != 500 {\n\t\tt.Fatalf(\"expected top sponsor to be g1address2 with 500ugnot, got %s with %dugnot\", topSponsors[0].Address.String(), topSponsors[0].Amount.AmountOf(\"ugnot\"))\n\t}\n\n\tif topSponsors[1].Address.String() != \"g1address1\" || topSponsors[1].Amount.AmountOf(\"ugnot\") != 300 {\n\t\tt.Fatalf(\"expected second sponsor to be g1address1 with 300ugnot, got %s with %dugnot\", topSponsors[1].Address.String(), topSponsors[1].Amount.AmountOf(\"ugnot\"))\n\t}\n\n\tif topSponsors[2].Address.String() != \"g1address3\" || topSponsors[2].Amount.AmountOf(\"ugnot\") != 200 {\n\t\tt.Fatalf(\"expected third sponsor to be g1address3 with 200ugnot, got %s with %dugnot\", topSponsors[2].Address.String(), topSponsors[2].Amount.AmountOf(\"ugnot\"))\n\t}\n}\n\nfunc TestGetTotalDonations(t *testing.T) {\n\tvar user = testutils.TestAddress(\"user\")\n\tstd.TestSetOriginCaller(user)\n\n\tsponsorship.sponsors = avl.NewTree()\n\tsponsorship.sponsorsCount = 0\n\n\tsponsorship.sponsors.Set(\"g1address1\", std.NewCoins(std.NewCoin(\"ugnot\", 300)))\n\tsponsorship.sponsors.Set(\"g1address2\", std.NewCoins(std.NewCoin(\"ugnot\", 500)))\n\tsponsorship.sponsors.Set(\"g1address3\", std.NewCoins(std.NewCoin(\"ugnot\", 200)))\n\tsponsorship.sponsorsCount = 3\n\n\ttotalDonations := GetTotalDonations()\n\n\tif totalDonations != 1000 {\n\t\tt.Fatalf(\"expected total donations to be 1000ugnot, got %dugnot\", totalDonations)\n\t}\n}\n\nfunc TestRender(t *testing.T) {\n\ttravel.currentCityIndex = 0\n\ttravel.cities = []City{\n\t\t{Name: \"Venice\", URL: \"https://example.com/venice.jpg\"},\n\t\t{Name: \"Paris\", URL: \"https://example.com/paris.jpg\"},\n\t}\n\n\toutput := Render(\"\")\n\n\texpectedCity := \"Venice\"\n\tif !strings.Contains(output, expectedCity) {\n\t\tt.Fatalf(\"expected output to contain city name '%s', got %s\", expectedCity, output)\n\t}\n\n\texpectedURL := \"https://example.com/venice.jpg\"\n\tif !strings.Contains(output, expectedURL) {\n\t\tt.Fatalf(\"expected output to contain city URL '%s', got %s\", expectedURL, output)\n\t}\n\n\ttravel.currentCityIndex = 1\n\toutput = Render(\"\")\n\n\texpectedCity = \"Paris\"\n\tif !strings.Contains(output, expectedCity) {\n\t\tt.Fatalf(\"expected output to contain city name '%s', got %s\", expectedCity, output)\n\t}\n\n\texpectedURL = \"https://example.com/paris.jpg\"\n\tif !strings.Contains(output, expectedURL) {\n\t\tt.Fatalf(\"expected output to contain city URL '%s', got %s\", expectedURL, output)\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":"rewards","path":"gno.land/r/sys/rewards","files":[{"name":"rewards.gno","body":"// This package will be used to manage proof-of-contributions on the exposed smart-contract side.\npackage rewards\n\n// TODO: write specs.\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":"users","path":"gno.land/r/sys/users","files":[{"name":"verify.gno","body":"package users\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\" // @moul\n\ntype VerifyNameFunc func(enabled bool, address std.Address, name string) bool\n\nvar (\n\towner = ownable.NewWithAddress(admin) // Package owner\n\tcheckFunc = VerifyNameByUser // Checking namespace callback\n\tenabled = true // For now this package is disabled by default\n)\n\nfunc IsEnabled() bool { return enabled }\n\n// This method ensures that the given address has ownership of the given name.\nfunc IsAuthorizedAddressForName(address std.Address, name string) bool {\n\treturn checkFunc(enabled, address, name)\n}\n\n// VerifyNameByUser checks from the `users` package that the user has correctly\n// registered the given name.\n// This function considers as valid an `address` that matches the `name`.\nfunc VerifyNameByUser(enable bool, address std.Address, name string) bool {\n\tif !enable {\n\t\treturn true\n\t}\n\n\t// Allow user with their own address as name\n\tif address.String() == name {\n\t\treturn true\n\t}\n\n\tif user := users.GetUserByName(name); user != nil {\n\t\treturn user.Address == address\n\t}\n\n\treturn false\n}\n\n// Admin calls\n\n// Enable this package.\nfunc AdminEnable() {\n\tif err := owner.CallerIsOwner(); err != nil {\n\t\tpanic(err)\n\t}\n\n\tenabled = true\n}\n\n// Disable this package.\nfunc AdminDisable() {\n\tif err := owner.CallerIsOwner(); err != nil {\n\t\tpanic(err)\n\t}\n\n\tenabled = false\n}\n\n// AdminUpdateVerifyCall updates the method that verifies the namespace.\nfunc AdminUpdateVerifyCall(check VerifyNameFunc) {\n\tif err := owner.CallerIsOwner(); err != nil {\n\t\tpanic(err)\n\t}\n\n\tcheckFunc = check\n}\n\n// AdminTransferOwnership transfers the ownership to a new owner.\nfunc AdminTransferOwnership(newOwner std.Address) error {\n\tif err := owner.CallerIsOwner(); err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn owner.TransferOwnership(newOwner)\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}} From 6879236f4245399e2f47fe64563ff17dcc239979 Mon Sep 17 00:00:00 2001 From: hthieu1110 Date: Thu, 19 Dec 2024 21:46:59 +0700 Subject: [PATCH 03/14] chore: remove prefix GetCallerAt => CallerAt --- docs/reference/stdlibs/std/chain.md | 10 +++++----- examples/gno.land/p/demo/entropy/entropy.gno | 4 ++-- examples/gno.land/p/demo/testutils/misc.gno | 2 +- examples/gno.land/r/demo/nft/nft.gno | 6 +++--- examples/gno.land/r/demo/nft/z_2_filetest.gno | 2 +- examples/gno.land/r/demo/nft/z_3_filetest.gno | 2 +- examples/gno.land/r/demo/nft/z_4_filetest.gno | 2 +- examples/gno.land/r/demo/users/users.gno | 10 +++++----- .../r/x/manfred_upgrade_patterns/upgrade_b/v1/v1.gno | 2 +- .../r/x/manfred_upgrade_patterns/upgrade_b/v2/v2.gno | 2 +- gno.land/cmd/gnoland/testdata/issue_2283.txtar | 2 +- .../cmd/gnoland/testdata/issue_2283_cacheTypes.txtar | 2 +- misc/deployments/test5.gno.land/genesis_txs.jsonl | 8 ++++---- 13 files changed, 27 insertions(+), 27 deletions(-) diff --git a/docs/reference/stdlibs/std/chain.md b/docs/reference/stdlibs/std/chain.md index 08bca4bfedf..ef7627a0b55 100644 --- a/docs/reference/stdlibs/std/chain.md +++ b/docs/reference/stdlibs/std/chain.md @@ -138,17 +138,17 @@ prevRealm := std.PrevRealm() ``` --- -## GetCallerAt +## CallerAt ```go -func GetCallerAt(n int) Address +func CallerAt(n int) Address ``` Returns the n-th caller of the function, going back in the call trace. #### Usage ```go -currentRealm := std.GetCallerAt(1) // returns address of current realm -previousRealm := std.GetCallerAt(2) // returns address of previous realm/caller -std.GetCallerAt(0) // error, n must be > 0 +currentRealm := std.CallerAt(1) // returns address of current realm +previousRealm := std.CallerAt(2) // returns address of previous realm/caller +std.CallerAt(0) // error, n must be > 0 ``` --- diff --git a/examples/gno.land/p/demo/entropy/entropy.gno b/examples/gno.land/p/demo/entropy/entropy.gno index 9e8f656c21b..ede61b74dee 100644 --- a/examples/gno.land/p/demo/entropy/entropy.gno +++ b/examples/gno.land/p/demo/entropy/entropy.gno @@ -57,9 +57,9 @@ func (i *Instance) addEntropy() { // handle callers { - caller1 := std.GetCallerAt(1).String() + caller1 := std.CallerAt(1).String() i.djb2String(caller1) - caller2 := std.GetCallerAt(2).String() + caller2 := std.CallerAt(2).String() i.djb2String(caller2) } diff --git a/examples/gno.land/p/demo/testutils/misc.gno b/examples/gno.land/p/demo/testutils/misc.gno index d48304ad441..60dd611ef49 100644 --- a/examples/gno.land/p/demo/testutils/misc.gno +++ b/examples/gno.land/p/demo/testutils/misc.gno @@ -1,6 +1,6 @@ package testutils -// For testing std.GetCallerAt(). +// For testing std.CallerAt(). func WrapCall(fn func()) { fn() } diff --git a/examples/gno.land/r/demo/nft/nft.gno b/examples/gno.land/r/demo/nft/nft.gno index f4340d6d2a9..fe328a5be17 100644 --- a/examples/gno.land/r/demo/nft/nft.gno +++ b/examples/gno.land/r/demo/nft/nft.gno @@ -74,7 +74,7 @@ func (grc *token) SafeTransferFrom(from, to std.Address, tid grc721.TokenID) { } func (grc *token) TransferFrom(from, to std.Address, tid grc721.TokenID) { - caller := std.GetCallerAt(2) + caller := std.CallerAt(2) token, ok := grc.getToken(tid) // Throws if `_tokenId` is not a valid NFT. if !ok { @@ -101,7 +101,7 @@ func (grc *token) TransferFrom(from, to std.Address, tid grc721.TokenID) { } func (grc *token) Approve(approved std.Address, tid grc721.TokenID) { - caller := std.GetCallerAt(2) + caller := std.CallerAt(2) token, ok := grc.getToken(tid) // Throws if `_tokenId` is not a valid NFT. if !ok { @@ -121,7 +121,7 @@ func (grc *token) Approve(approved std.Address, tid grc721.TokenID) { // XXX make it work for set of operators. func (grc *token) SetApprovalForAll(operator std.Address, approved bool) { - caller := std.GetCallerAt(2) + caller := std.CallerAt(2) grc.operators.Set(caller.String(), operator) } diff --git a/examples/gno.land/r/demo/nft/z_2_filetest.gno b/examples/gno.land/r/demo/nft/z_2_filetest.gno index 91c48bd5957..1848cd8222e 100644 --- a/examples/gno.land/r/demo/nft/z_2_filetest.gno +++ b/examples/gno.land/r/demo/nft/z_2_filetest.gno @@ -9,7 +9,7 @@ import ( ) func main() { - caller := std.GetCallerAt(1) + caller := std.CallerAt(1) addr1 := testutils.TestAddress("addr1") // addr2 := testutils.TestAddress("addr2") grc721 := nft.GetToken() diff --git a/examples/gno.land/r/demo/nft/z_3_filetest.gno b/examples/gno.land/r/demo/nft/z_3_filetest.gno index d0210c8ba4d..09402b38246 100644 --- a/examples/gno.land/r/demo/nft/z_3_filetest.gno +++ b/examples/gno.land/r/demo/nft/z_3_filetest.gno @@ -9,7 +9,7 @@ import ( ) func main() { - caller := std.GetCallerAt(1) + caller := std.CallerAt(1) addr1 := testutils.TestAddress("addr1") addr2 := testutils.TestAddress("addr2") grc721 := nft.GetToken() diff --git a/examples/gno.land/r/demo/nft/z_4_filetest.gno b/examples/gno.land/r/demo/nft/z_4_filetest.gno index b38ce8ea190..3a167f3c87b 100644 --- a/examples/gno.land/r/demo/nft/z_4_filetest.gno +++ b/examples/gno.land/r/demo/nft/z_4_filetest.gno @@ -9,7 +9,7 @@ import ( ) func main() { - caller := std.GetCallerAt(1) + caller := std.CallerAt(1) addr1 := testutils.TestAddress("addr1") addr2 := testutils.TestAddress("addr2") grc721 := nft.GetToken() diff --git a/examples/gno.land/r/demo/users/users.gno b/examples/gno.land/r/demo/users/users.gno index 6eede5e120e..3f3edd82064 100644 --- a/examples/gno.land/r/demo/users/users.gno +++ b/examples/gno.land/r/demo/users/users.gno @@ -34,7 +34,7 @@ func Register(inviter std.Address, name string, profile string) { // assert CallTx call. std.AssertOriginCall() // assert invited or paid. - caller := std.GetCallerAt(2) + caller := std.CallerAt(2) if caller != std.OriginCaller() { panic("should not happen") // because std.AssertOrigCall(). } @@ -118,7 +118,7 @@ func Invite(invitee string) { // assert CallTx call. std.AssertOriginCall() // get caller/inviter. - caller := std.GetCallerAt(2) + caller := std.CallerAt(2) if caller != std.OriginCaller() { panic("should not happen") // because std.AssertOrigCall(). } @@ -157,7 +157,7 @@ func GrantInvites(invites string) { // assert CallTx call. std.AssertOriginCall() // assert admin. - caller := std.GetCallerAt(2) + caller := std.CallerAt(2) if caller != std.OriginCaller() { panic("should not happen") // because std.AssertOrigCall(). } @@ -208,7 +208,7 @@ func SetMinFee(newMinFee int64) { // assert CallTx call. std.AssertOriginCall() // assert admin caller. - caller := std.GetCallerAt(2) + caller := std.CallerAt(2) if caller != admin { panic("unauthorized") } @@ -221,7 +221,7 @@ func SetMaxFeeMultiple(newMaxFeeMult int64) { // assert CallTx call. std.AssertOriginCall() // assert admin caller. - caller := std.GetCallerAt(2) + caller := std.CallerAt(2) if caller != admin { panic("unauthorized") } diff --git a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v1/v1.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v1/v1.gno index 100013ebd78..7317fee9478 100644 --- a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v1/v1.gno +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v1/v1.gno @@ -24,7 +24,7 @@ func SetNextVersion(addr string) { // assert CallTx call. std.AssertOriginCall() // assert admin. - caller := std.GetCallerAt(2) + caller := std.CallerAt(2) if caller != std.OriginCaller() { panic("should not happen") // because std.AssertOrigCall(). } diff --git a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v2/v2.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v2/v2.gno index 32105b379dc..3bb5606ec84 100644 --- a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v2/v2.gno +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v2/v2.gno @@ -28,7 +28,7 @@ func SetNextVersion(addr string) { // assert CallTx call. std.AssertOriginCall() // assert admin. - caller := std.GetCallerAt(2) + caller := std.CallerAt(2) if caller != std.OriginCaller() { panic("should not happen") // because std.AssertOrigCall(). } diff --git a/gno.land/cmd/gnoland/testdata/issue_2283.txtar b/gno.land/cmd/gnoland/testdata/issue_2283.txtar index 71398ef0d88..92061d13b33 100644 --- a/gno.land/cmd/gnoland/testdata/issue_2283.txtar +++ b/gno.land/cmd/gnoland/testdata/issue_2283.txtar @@ -46,7 +46,7 @@ import ( }, { "Name": "feeds_test.gno", - "Body": "package social_feeds\n\nimport (\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t// Fake previous version for testing\n\tfeedsV7 \"gno.land/r/demo/teritori/social_feeds\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\trootPostID = PostID(0)\n\tpostID1 = PostID(1)\n\tfeedID1 = FeedID(1)\n\tcat1 = uint64(1)\n\tcat2 = uint64(2)\n\tuser = testutils.TestAddress(\"user\")\n\tfilter_all = []uint64{}\n)\n\nfunc getFeed1() *Feed {\n\treturn mustGetFeed(feedID1)\n}\n\nfunc getPost1() *Post {\n\tfeed1 := getFeed1()\n\tpost1 := feed1.MustGetPost(postID1)\n\treturn post1\n}\n\nfunc testCreateFeed(t *testing.T) {\n\tfeedID := CreateFeed(\"teritori1\")\n\tfeed := mustGetFeed(feedID)\n\n\tif feedID != 1 {\n\t\tt.Fatalf(\"expected feedID: 1, got %q.\", feedID)\n\t}\n\n\tif feed.name != \"teritori1\" {\n\t\tt.Fatalf(\"expected feedName: teritori1, got %q.\", feed.name)\n\t}\n}\n\nfunc testCreatePost(t *testing.T) {\n\tmetadata := `{\"gifs\": [], \"files\": [], \"title\": \"\", \"message\": \"testouille\", \"hashtags\": [], \"mentions\": [], \"createdAt\": \"2023-03-29T12:19:04.858Z\", \"updatedAt\": \"2023-03-29T12:19:04.858Z\"}`\n\tpostID := CreatePost(feedID1, rootPostID, cat1, metadata)\n\tfeed := mustGetFeed(feedID1)\n\tpost := feed.MustGetPost(postID)\n\n\tif postID != 1 {\n\t\tt.Fatalf(\"expected postID: 1, got %q.\", postID)\n\t}\n\n\tif post.category != cat1 {\n\t\tt.Fatalf(\"expected categoryID: %q, got %q.\", cat1, post.category)\n\t}\n}\n\nfunc toPostIDsStr(posts []*Post) string {\n\tvar postIDs []string\n\tfor _, post := range posts {\n\t\tpostIDs = append(postIDs, post.id.String())\n\t}\n\n\tpostIDsStr := strings.Join(postIDs, \",\")\n\treturn postIDsStr\n}\n\nfunc testGetPosts(t *testing.T) {\n\tuser := std.Address(\"user\")\n\tstd.TestSetOriginCaller(user)\n\n\tfeedID := CreateFeed(\"teritori10\")\n\tfeed := mustGetFeed(feedID)\n\n\tCreatePost(feedID, rootPostID, cat1, \"post1\")\n\tCreatePost(feedID, rootPostID, cat1, \"post2\")\n\tCreatePost(feedID, rootPostID, cat1, \"post3\")\n\tCreatePost(feedID, rootPostID, cat1, \"post4\")\n\tCreatePost(feedID, rootPostID, cat1, \"post5\")\n\tpostIDToFlagged := CreatePost(feedID, rootPostID, cat1, \"post6\")\n\tpostIDToHide := CreatePost(feedID, rootPostID, cat1, \"post7\")\n\tCreatePost(feedID, rootPostID, cat1, \"post8\")\n\n\tvar posts []*Post\n\tvar postIDsStr string\n\n\t// Query last 3 posts\n\tposts = getPosts(feed, 0, \"\", \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,7,6\" {\n\t\tt.Fatalf(\"expected posts order: 8,7,6. Got: %s\", postIDsStr)\n\t}\n\n\t// Query page 2\n\tposts = getPosts(feed, 0, \"\", \"\", []uint64{}, 3, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\tif postIDsStr != \"5,4,3\" {\n\t\tt.Fatalf(\"expected posts order: 5,4,3. Got: %s\", postIDsStr)\n\t}\n\n\t// Exclude hidden post\n\tHidePostForMe(feed.id, postIDToHide)\n\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,6,5\" {\n\t\tt.Fatalf(\"expected posts order: 8,6,5. Got: %s\", postIDsStr)\n\t}\n\n\t// Exclude flagged post\n\tFlagPost(feed.id, postIDToFlagged)\n\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,5,4\" {\n\t\tt.Fatalf(\"expected posts order: 8,5,4. Got: %s\", postIDsStr)\n\t}\n\n\t// Pagination with hidden/flagged posts\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 3, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"3,2,1\" {\n\t\tt.Fatalf(\"expected posts order: 3,2,1. Got: %s\", postIDsStr)\n\t}\n\n\t// Query out of range\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 6, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"\" {\n\t\tt.Fatalf(\"expected posts order: ''. Got: %s\", postIDsStr)\n\t}\n}\n\nfunc testReactPost(t *testing.T) {\n\tfeed := getFeed1()\n\tpost := getPost1()\n\n\ticon := \"🥰\"\n\tReactPost(feed.id, post.id, icon, true)\n\n\t// Set reaction\n\treactionCount_, ok := post.reactions.Get(\"🥰\")\n\tif !ok {\n\t\tt.Fatalf(\"expected 🥰 exists\")\n\t}\n\n\treactionCount := reactionCount_.(int)\n\tif reactionCount != 1 {\n\t\tt.Fatalf(\"expected reactionCount: 1, got %q.\", reactionCount)\n\t}\n\n\t// Unset reaction\n\tReactPost(feed.id, post.id, icon, false)\n\t_, exist := post.reactions.Get(\"🥰\")\n\tif exist {\n\t\tt.Fatalf(\"expected 🥰 not exist\")\n\t}\n}\n\nfunc testCreateAndDeleteComment(t *testing.T) {\n\tfeed1 := getFeed1()\n\tpost1 := getPost1()\n\n\tmetadata := `empty_meta_data`\n\n\tcommentID1 := CreatePost(feed1.id, post1.id, cat1, metadata)\n\tcommentID2 := CreatePost(feed1.id, post1.id, cat1, metadata)\n\tcomment2 := feed1.MustGetPost(commentID2)\n\n\tif comment2.id != 3 { // 1 post + 2 comments = 3\n\t\tt.Fatalf(\"expected comment postID: 3, got %q.\", comment2.id)\n\t}\n\n\tif comment2.parentID != post1.id {\n\t\tt.Fatalf(\"expected comment parentID: %q, got %q.\", post1.id, comment2.parentID)\n\t}\n\n\t// Check comment count on parent\n\tif post1.commentsCount != 2 {\n\t\tt.Fatalf(\"expected comments count: 2, got %d.\", post1.commentsCount)\n\t}\n\n\t// Get comments\n\tcomments := GetComments(feed1.id, post1.id, 0, 10)\n\tcommentsParsed := ujson.ParseSlice(comments)\n\n\tif len(commentsParsed) != 2 {\n\t\tt.Fatalf(\"expected encoded comments: 2, got %q.\", commentsParsed)\n\t}\n\n\t// Delete 1 comment\n\tDeletePost(feed1.id, comment2.id)\n\tcomments = GetComments(feed1.id, post1.id, 0, 10)\n\tcommentsParsed = ujson.ParseSlice(comments)\n\n\tif len(commentsParsed) != 1 {\n\t\tt.Fatalf(\"expected encoded comments: 1, got %q.\", commentsParsed)\n\t}\n\n\t// Check comment count on parent\n\tif post1.commentsCount != 1 {\n\t\tt.Fatalf(\"expected comments count: 1, got %d.\", post1.commentsCount)\n\t}\n}\n\nfunc countPosts(feedID FeedID, categories []uint64, limit uint8) int {\n\toffset := uint64(0)\n\n\tpostsStr := GetPosts(feedID, 0, \"\", categories, offset, limit)\n\tif postsStr == \"[]\" {\n\t\treturn 0\n\t}\n\n\tparsedPosts := ujson.ParseSlice(postsStr)\n\tpostsCount := len(parsedPosts)\n\treturn postsCount\n}\n\nfunc countPostsByUser(feedID FeedID, user string) int {\n\toffset := uint64(0)\n\tlimit := uint8(10)\n\n\tpostsStr := GetPosts(feedID, 0, user, []uint64{}, offset, limit)\n\tif postsStr == \"[]\" {\n\t\treturn 0\n\t}\n\n\tparsedPosts := ujson.ParseSlice(postsStr)\n\tpostsCount := len(parsedPosts)\n\treturn postsCount\n}\n\nfunc testFilterByCategories(t *testing.T) {\n\t// // Re-add reaction to test post list\n\t// ReactPost(1, postID, \"🥰\", true)\n\t// ReactPost(1, postID, \"😇\", true)\n\n\tfilter_cat1 := []uint64{1}\n\tfilter_cat1_2 := []uint64{1, 2}\n\tfilter_cat9 := []uint64{9}\n\tfilter_cat1_2_9 := []uint64{1, 2, 9}\n\n\tfeedID2 := CreateFeed(\"teritori2\")\n\tfeed2 := mustGetFeed(feedID2)\n\n\t// Create 2 posts on root with cat1\n\tpostID1 := CreatePost(feed2.id, rootPostID, cat1, \"metadata\")\n\tpostID2 := CreatePost(feed2.id, rootPostID, cat1, \"metadata\")\n\n\t// Create 1 posts on root with cat2\n\tpostID3 := CreatePost(feed2.id, rootPostID, cat2, \"metadata\")\n\n\t// Create comments on post 1\n\tcommentPostID1 := CreatePost(feed2.id, postID1, cat1, \"metadata\")\n\n\t// cat1: Should return max = limit\n\tif count := countPosts(feed2.id, filter_cat1, 1); count != 1 {\n\t\tt.Fatalf(\"expected posts count: 1, got %q.\", count)\n\t}\n\n\t// cat1: Should return max = total\n\tif count := countPosts(feed2.id, filter_cat1, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// cat 1 + 2: Should return max = limit\n\tif count := countPosts(feed2.id, filter_cat1_2, 2); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// cat 1 + 2: Should return max = total on both\n\tif count := countPosts(feed2.id, filter_cat1_2, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// cat 1, 2, 9: Should return total of 1, 2\n\tif count := countPosts(feed2.id, filter_cat1_2_9, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// cat 9: Should return 0\n\tif count := countPosts(feed2.id, filter_cat9, 10); count != 0 {\n\t\tt.Fatalf(\"expected posts count: 0, got %q.\", count)\n\t}\n\n\t// cat all: should return all\n\tif count := countPosts(feed2.id, filter_all, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// add comments should not impact the results\n\tCreatePost(feed2.id, postID1, cat1, \"metadata\")\n\tCreatePost(feed2.id, postID2, cat1, \"metadata\")\n\n\tif count := countPosts(feed2.id, filter_all, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// delete a post should affect the result\n\tDeletePost(feed2.id, postID1)\n\n\tif count := countPosts(feed2.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n}\n\nfunc testTipPost(t *testing.T) {\n\tcreator := testutils.TestAddress(\"creator\")\n\tstd.TestIssueCoins(creator, std.Coins{{\"ugnot\", 100_000_000}})\n\n\t// NOTE: Dont know why the address should be this to be able to call banker (= std.GetCallerAt(1))\n\ttipper := testutils.TestAddress(\"tipper\")\n\tstd.TestIssueCoins(tipper, std.Coins{{\"ugnot\", 50_000_000}})\n\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\n\t// Check Original coins of creator/tipper\n\tif coins := banker.GetCoins(creator); coins[0].Amount != 100_000_000 {\n\t\tt.Fatalf(\"expected creator coin count: 100_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\tif coins := banker.GetCoins(tipper); coins[0].Amount != 50_000_000 {\n\t\tt.Fatalf(\"expected tipper coin count: 50_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\t// Creator creates feed, post\n\tstd.TestSetOriginCaller(creator)\n\n\tfeedID3 := CreateFeed(\"teritori3\")\n\tfeed3 := mustGetFeed(feedID3)\n\n\tpostID1 := CreatePost(feed3.id, rootPostID, cat1, \"metadata\")\n\tpost1 := feed3.MustGetPost(postID1)\n\n\t// Tiper tips the ppst\n\tstd.TestSetOriginCaller(tipper)\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 1_000_000}}, nil)\n\tTipPost(feed3.id, post1.id)\n\n\t// Coin must be increased for creator\n\tif coins := banker.GetCoins(creator); coins[0].Amount != 101_000_000 {\n\t\tt.Fatalf(\"expected creator coin after beging tipped: 101_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\t// Total tip amount should increased\n\tif post1.tipAmount != 1_000_000 {\n\t\tt.Fatalf(\"expected total tipAmount: 1_000_000, got %d.\", post1.tipAmount)\n\t}\n\n\t// Add more tip should update this total\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 2_000_000}}, nil)\n\tTipPost(feed3.id, post1.id)\n\n\tif post1.tipAmount != 3_000_000 {\n\t\tt.Fatalf(\"expected total tipAmount: 3_000_000, got %d.\", post1.tipAmount)\n\t}\n}\n\nfunc testFlagPost(t *testing.T) {\n\tflagger := testutils.TestAddress(\"flagger\")\n\n\tfeedID9 := CreateFeed(\"teritori9\")\n\tfeed9 := mustGetFeed(feedID9)\n\n\tCreatePost(feed9.id, rootPostID, cat1, \"metadata1\")\n\tpid := CreatePost(feed9.id, rootPostID, cat1, \"metadata1\")\n\n\t// Flag post\n\tstd.TestSetOriginCaller(flagger)\n\tFlagPost(feed9.id, pid)\n\n\t// Another user flags\n\tanother := testutils.TestAddress(\"another\")\n\tstd.TestSetOriginCaller(another)\n\tFlagPost(feed9.id, pid)\n\n\tflaggedPostsStr := GetFlaggedPosts(feed9.id, 0, 10)\n\tparsed := ujson.ParseSlice(flaggedPostsStr)\n\tif flaggedPostsCount := len(parsed); flaggedPostsCount != 1 {\n\t\tt.Fatalf(\"expected flagged posts: 1, got %d.\", flaggedPostsCount)\n\t}\n}\n\nfunc testFilterUser(t *testing.T) {\n\tuser1 := testutils.TestAddress(\"user1\")\n\tuser2 := testutils.TestAddress(\"user2\")\n\n\t// User1 create 2 posts\n\tstd.TestSetOriginCaller(user1)\n\n\tfeedID4 := CreateFeed(\"teritori4\")\n\tfeed4 := mustGetFeed(feedID4)\n\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata2\": \"value\"}`)\n\n\t// User2 create 1 post\n\tstd.TestSetOriginCaller(user2)\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\n\tif count := countPostsByUser(feed4.id, user1.String()); count != 2 {\n\t\tt.Fatalf(\"expected total posts by user1: 2, got %d.\", count)\n\t}\n\n\tif count := countPostsByUser(feed4.id, user2.String()); count != 1 {\n\t\tt.Fatalf(\"expected total posts by user2: 1, got %d.\", count)\n\t}\n\n\tif count := countPostsByUser(feed4.id, \"\"); count != 3 {\n\t\tt.Fatalf(\"expected total posts: 3, got %d.\", count)\n\t}\n}\n\nfunc testHidePostForMe(t *testing.T) {\n\tuser := std.Address(\"user\")\n\tstd.TestSetOriginCaller(user)\n\n\tfeedID8 := CreateFeed(\"teritor8\")\n\tfeed8 := mustGetFeed(feedID8)\n\n\tpostIDToHide := CreatePost(feed8.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\tpostID := CreatePost(feed8.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// Hide a post for me\n\tHidePostForMe(feed8.id, postIDToHide)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 1 {\n\t\tt.Fatalf(\"expected posts count after hidding: 1, got %q.\", count)\n\t}\n\n\t// Query from another user should return full list\n\tanother := std.Address(\"another\")\n\tstd.TestSetOriginCaller(another)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count from another: 2, got %q.\", count)\n\t}\n\n\t// UnHide a post for me\n\tstd.TestSetOriginCaller(user)\n\tUnHidePostForMe(feed8.id, postIDToHide)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count after unhidding: 2, got %q.\", count)\n\t}\n}\n\nfunc testMigrateFeedData(t *testing.T) string {\n\tfeedID := feedsV7.CreateFeed(\"teritor11\")\n\n\t// Post to test\n\tpostID := feedsV7.CreatePost(feedID, feedsV7.PostID(0), 2, `{\"metadata\": \"value\"}`)\n\tfeedsV7.ReactPost(feedID, postID, \"🇬🇸\", true)\n\n\t// Add comment to post\n\tcommentID := feedsV7.CreatePost(feedID, postID, 2, `{\"comment1\": \"value\"}`)\n\tfeedsV7.ReactPost(feedID, commentID, \"🇬🇸\", true)\n\n\t// // Post with json metadata\n\tfeedsV7.CreatePost(feedID, feedsV7.PostID(0), 2, `{'a':1}`)\n\n\t// Expect: should convert feed data to JSON successfully without error\n\tdataJSON := feedsV7.ExportFeedData(feedID)\n\tif dataJSON == \"\" {\n\t\tt.Fatalf(\"expected feed data exported successfully\")\n\t}\n\n\t// Import data =====================================\n\tImportFeedData(FeedID(uint64(feedID)), dataJSON)\n\n\t// Test public func\n\t// MigrateFromPreviousFeed(feedID)\n}\n\nfunc Test(t *testing.T) {\n\ttestCreateFeed(t)\n\n\ttestCreatePost(t)\n\n\ttestGetPosts(t)\n\n\ttestReactPost(t)\n\n\ttestCreateAndDeleteComment(t)\n\n\ttestFilterByCategories(t)\n\n\ttestTipPost(t)\n\n\ttestFilterUser(t)\n\n\ttestFlagPost(t)\n\n\ttestHidePostForMe(t)\n\n\ttestMigrateFeedData(t)\n}\n" + "Body": "package social_feeds\n\nimport (\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t// Fake previous version for testing\n\tfeedsV7 \"gno.land/r/demo/teritori/social_feeds\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\trootPostID = PostID(0)\n\tpostID1 = PostID(1)\n\tfeedID1 = FeedID(1)\n\tcat1 = uint64(1)\n\tcat2 = uint64(2)\n\tuser = testutils.TestAddress(\"user\")\n\tfilter_all = []uint64{}\n)\n\nfunc getFeed1() *Feed {\n\treturn mustGetFeed(feedID1)\n}\n\nfunc getPost1() *Post {\n\tfeed1 := getFeed1()\n\tpost1 := feed1.MustGetPost(postID1)\n\treturn post1\n}\n\nfunc testCreateFeed(t *testing.T) {\n\tfeedID := CreateFeed(\"teritori1\")\n\tfeed := mustGetFeed(feedID)\n\n\tif feedID != 1 {\n\t\tt.Fatalf(\"expected feedID: 1, got %q.\", feedID)\n\t}\n\n\tif feed.name != \"teritori1\" {\n\t\tt.Fatalf(\"expected feedName: teritori1, got %q.\", feed.name)\n\t}\n}\n\nfunc testCreatePost(t *testing.T) {\n\tmetadata := `{\"gifs\": [], \"files\": [], \"title\": \"\", \"message\": \"testouille\", \"hashtags\": [], \"mentions\": [], \"createdAt\": \"2023-03-29T12:19:04.858Z\", \"updatedAt\": \"2023-03-29T12:19:04.858Z\"}`\n\tpostID := CreatePost(feedID1, rootPostID, cat1, metadata)\n\tfeed := mustGetFeed(feedID1)\n\tpost := feed.MustGetPost(postID)\n\n\tif postID != 1 {\n\t\tt.Fatalf(\"expected postID: 1, got %q.\", postID)\n\t}\n\n\tif post.category != cat1 {\n\t\tt.Fatalf(\"expected categoryID: %q, got %q.\", cat1, post.category)\n\t}\n}\n\nfunc toPostIDsStr(posts []*Post) string {\n\tvar postIDs []string\n\tfor _, post := range posts {\n\t\tpostIDs = append(postIDs, post.id.String())\n\t}\n\n\tpostIDsStr := strings.Join(postIDs, \",\")\n\treturn postIDsStr\n}\n\nfunc testGetPosts(t *testing.T) {\n\tuser := std.Address(\"user\")\n\tstd.TestSetOriginCaller(user)\n\n\tfeedID := CreateFeed(\"teritori10\")\n\tfeed := mustGetFeed(feedID)\n\n\tCreatePost(feedID, rootPostID, cat1, \"post1\")\n\tCreatePost(feedID, rootPostID, cat1, \"post2\")\n\tCreatePost(feedID, rootPostID, cat1, \"post3\")\n\tCreatePost(feedID, rootPostID, cat1, \"post4\")\n\tCreatePost(feedID, rootPostID, cat1, \"post5\")\n\tpostIDToFlagged := CreatePost(feedID, rootPostID, cat1, \"post6\")\n\tpostIDToHide := CreatePost(feedID, rootPostID, cat1, \"post7\")\n\tCreatePost(feedID, rootPostID, cat1, \"post8\")\n\n\tvar posts []*Post\n\tvar postIDsStr string\n\n\t// Query last 3 posts\n\tposts = getPosts(feed, 0, \"\", \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,7,6\" {\n\t\tt.Fatalf(\"expected posts order: 8,7,6. Got: %s\", postIDsStr)\n\t}\n\n\t// Query page 2\n\tposts = getPosts(feed, 0, \"\", \"\", []uint64{}, 3, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\tif postIDsStr != \"5,4,3\" {\n\t\tt.Fatalf(\"expected posts order: 5,4,3. Got: %s\", postIDsStr)\n\t}\n\n\t// Exclude hidden post\n\tHidePostForMe(feed.id, postIDToHide)\n\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,6,5\" {\n\t\tt.Fatalf(\"expected posts order: 8,6,5. Got: %s\", postIDsStr)\n\t}\n\n\t// Exclude flagged post\n\tFlagPost(feed.id, postIDToFlagged)\n\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,5,4\" {\n\t\tt.Fatalf(\"expected posts order: 8,5,4. Got: %s\", postIDsStr)\n\t}\n\n\t// Pagination with hidden/flagged posts\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 3, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"3,2,1\" {\n\t\tt.Fatalf(\"expected posts order: 3,2,1. Got: %s\", postIDsStr)\n\t}\n\n\t// Query out of range\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 6, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"\" {\n\t\tt.Fatalf(\"expected posts order: ''. Got: %s\", postIDsStr)\n\t}\n}\n\nfunc testReactPost(t *testing.T) {\n\tfeed := getFeed1()\n\tpost := getPost1()\n\n\ticon := \"🥰\"\n\tReactPost(feed.id, post.id, icon, true)\n\n\t// Set reaction\n\treactionCount_, ok := post.reactions.Get(\"🥰\")\n\tif !ok {\n\t\tt.Fatalf(\"expected 🥰 exists\")\n\t}\n\n\treactionCount := reactionCount_.(int)\n\tif reactionCount != 1 {\n\t\tt.Fatalf(\"expected reactionCount: 1, got %q.\", reactionCount)\n\t}\n\n\t// Unset reaction\n\tReactPost(feed.id, post.id, icon, false)\n\t_, exist := post.reactions.Get(\"🥰\")\n\tif exist {\n\t\tt.Fatalf(\"expected 🥰 not exist\")\n\t}\n}\n\nfunc testCreateAndDeleteComment(t *testing.T) {\n\tfeed1 := getFeed1()\n\tpost1 := getPost1()\n\n\tmetadata := `empty_meta_data`\n\n\tcommentID1 := CreatePost(feed1.id, post1.id, cat1, metadata)\n\tcommentID2 := CreatePost(feed1.id, post1.id, cat1, metadata)\n\tcomment2 := feed1.MustGetPost(commentID2)\n\n\tif comment2.id != 3 { // 1 post + 2 comments = 3\n\t\tt.Fatalf(\"expected comment postID: 3, got %q.\", comment2.id)\n\t}\n\n\tif comment2.parentID != post1.id {\n\t\tt.Fatalf(\"expected comment parentID: %q, got %q.\", post1.id, comment2.parentID)\n\t}\n\n\t// Check comment count on parent\n\tif post1.commentsCount != 2 {\n\t\tt.Fatalf(\"expected comments count: 2, got %d.\", post1.commentsCount)\n\t}\n\n\t// Get comments\n\tcomments := GetComments(feed1.id, post1.id, 0, 10)\n\tcommentsParsed := ujson.ParseSlice(comments)\n\n\tif len(commentsParsed) != 2 {\n\t\tt.Fatalf(\"expected encoded comments: 2, got %q.\", commentsParsed)\n\t}\n\n\t// Delete 1 comment\n\tDeletePost(feed1.id, comment2.id)\n\tcomments = GetComments(feed1.id, post1.id, 0, 10)\n\tcommentsParsed = ujson.ParseSlice(comments)\n\n\tif len(commentsParsed) != 1 {\n\t\tt.Fatalf(\"expected encoded comments: 1, got %q.\", commentsParsed)\n\t}\n\n\t// Check comment count on parent\n\tif post1.commentsCount != 1 {\n\t\tt.Fatalf(\"expected comments count: 1, got %d.\", post1.commentsCount)\n\t}\n}\n\nfunc countPosts(feedID FeedID, categories []uint64, limit uint8) int {\n\toffset := uint64(0)\n\n\tpostsStr := GetPosts(feedID, 0, \"\", categories, offset, limit)\n\tif postsStr == \"[]\" {\n\t\treturn 0\n\t}\n\n\tparsedPosts := ujson.ParseSlice(postsStr)\n\tpostsCount := len(parsedPosts)\n\treturn postsCount\n}\n\nfunc countPostsByUser(feedID FeedID, user string) int {\n\toffset := uint64(0)\n\tlimit := uint8(10)\n\n\tpostsStr := GetPosts(feedID, 0, user, []uint64{}, offset, limit)\n\tif postsStr == \"[]\" {\n\t\treturn 0\n\t}\n\n\tparsedPosts := ujson.ParseSlice(postsStr)\n\tpostsCount := len(parsedPosts)\n\treturn postsCount\n}\n\nfunc testFilterByCategories(t *testing.T) {\n\t// // Re-add reaction to test post list\n\t// ReactPost(1, postID, \"🥰\", true)\n\t// ReactPost(1, postID, \"😇\", true)\n\n\tfilter_cat1 := []uint64{1}\n\tfilter_cat1_2 := []uint64{1, 2}\n\tfilter_cat9 := []uint64{9}\n\tfilter_cat1_2_9 := []uint64{1, 2, 9}\n\n\tfeedID2 := CreateFeed(\"teritori2\")\n\tfeed2 := mustGetFeed(feedID2)\n\n\t// Create 2 posts on root with cat1\n\tpostID1 := CreatePost(feed2.id, rootPostID, cat1, \"metadata\")\n\tpostID2 := CreatePost(feed2.id, rootPostID, cat1, \"metadata\")\n\n\t// Create 1 posts on root with cat2\n\tpostID3 := CreatePost(feed2.id, rootPostID, cat2, \"metadata\")\n\n\t// Create comments on post 1\n\tcommentPostID1 := CreatePost(feed2.id, postID1, cat1, \"metadata\")\n\n\t// cat1: Should return max = limit\n\tif count := countPosts(feed2.id, filter_cat1, 1); count != 1 {\n\t\tt.Fatalf(\"expected posts count: 1, got %q.\", count)\n\t}\n\n\t// cat1: Should return max = total\n\tif count := countPosts(feed2.id, filter_cat1, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// cat 1 + 2: Should return max = limit\n\tif count := countPosts(feed2.id, filter_cat1_2, 2); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// cat 1 + 2: Should return max = total on both\n\tif count := countPosts(feed2.id, filter_cat1_2, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// cat 1, 2, 9: Should return total of 1, 2\n\tif count := countPosts(feed2.id, filter_cat1_2_9, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// cat 9: Should return 0\n\tif count := countPosts(feed2.id, filter_cat9, 10); count != 0 {\n\t\tt.Fatalf(\"expected posts count: 0, got %q.\", count)\n\t}\n\n\t// cat all: should return all\n\tif count := countPosts(feed2.id, filter_all, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// add comments should not impact the results\n\tCreatePost(feed2.id, postID1, cat1, \"metadata\")\n\tCreatePost(feed2.id, postID2, cat1, \"metadata\")\n\n\tif count := countPosts(feed2.id, filter_all, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// delete a post should affect the result\n\tDeletePost(feed2.id, postID1)\n\n\tif count := countPosts(feed2.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n}\n\nfunc testTipPost(t *testing.T) {\n\tcreator := testutils.TestAddress(\"creator\")\n\tstd.TestIssueCoins(creator, std.Coins{{\"ugnot\", 100_000_000}})\n\n\t// NOTE: Dont know why the address should be this to be able to call banker (= std.CallerAt(1))\n\ttipper := testutils.TestAddress(\"tipper\")\n\tstd.TestIssueCoins(tipper, std.Coins{{\"ugnot\", 50_000_000}})\n\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\n\t// Check Original coins of creator/tipper\n\tif coins := banker.GetCoins(creator); coins[0].Amount != 100_000_000 {\n\t\tt.Fatalf(\"expected creator coin count: 100_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\tif coins := banker.GetCoins(tipper); coins[0].Amount != 50_000_000 {\n\t\tt.Fatalf(\"expected tipper coin count: 50_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\t// Creator creates feed, post\n\tstd.TestSetOriginCaller(creator)\n\n\tfeedID3 := CreateFeed(\"teritori3\")\n\tfeed3 := mustGetFeed(feedID3)\n\n\tpostID1 := CreatePost(feed3.id, rootPostID, cat1, \"metadata\")\n\tpost1 := feed3.MustGetPost(postID1)\n\n\t// Tiper tips the ppst\n\tstd.TestSetOriginCaller(tipper)\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 1_000_000}}, nil)\n\tTipPost(feed3.id, post1.id)\n\n\t// Coin must be increased for creator\n\tif coins := banker.GetCoins(creator); coins[0].Amount != 101_000_000 {\n\t\tt.Fatalf(\"expected creator coin after beging tipped: 101_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\t// Total tip amount should increased\n\tif post1.tipAmount != 1_000_000 {\n\t\tt.Fatalf(\"expected total tipAmount: 1_000_000, got %d.\", post1.tipAmount)\n\t}\n\n\t// Add more tip should update this total\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 2_000_000}}, nil)\n\tTipPost(feed3.id, post1.id)\n\n\tif post1.tipAmount != 3_000_000 {\n\t\tt.Fatalf(\"expected total tipAmount: 3_000_000, got %d.\", post1.tipAmount)\n\t}\n}\n\nfunc testFlagPost(t *testing.T) {\n\tflagger := testutils.TestAddress(\"flagger\")\n\n\tfeedID9 := CreateFeed(\"teritori9\")\n\tfeed9 := mustGetFeed(feedID9)\n\n\tCreatePost(feed9.id, rootPostID, cat1, \"metadata1\")\n\tpid := CreatePost(feed9.id, rootPostID, cat1, \"metadata1\")\n\n\t// Flag post\n\tstd.TestSetOriginCaller(flagger)\n\tFlagPost(feed9.id, pid)\n\n\t// Another user flags\n\tanother := testutils.TestAddress(\"another\")\n\tstd.TestSetOriginCaller(another)\n\tFlagPost(feed9.id, pid)\n\n\tflaggedPostsStr := GetFlaggedPosts(feed9.id, 0, 10)\n\tparsed := ujson.ParseSlice(flaggedPostsStr)\n\tif flaggedPostsCount := len(parsed); flaggedPostsCount != 1 {\n\t\tt.Fatalf(\"expected flagged posts: 1, got %d.\", flaggedPostsCount)\n\t}\n}\n\nfunc testFilterUser(t *testing.T) {\n\tuser1 := testutils.TestAddress(\"user1\")\n\tuser2 := testutils.TestAddress(\"user2\")\n\n\t// User1 create 2 posts\n\tstd.TestSetOriginCaller(user1)\n\n\tfeedID4 := CreateFeed(\"teritori4\")\n\tfeed4 := mustGetFeed(feedID4)\n\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata2\": \"value\"}`)\n\n\t// User2 create 1 post\n\tstd.TestSetOriginCaller(user2)\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\n\tif count := countPostsByUser(feed4.id, user1.String()); count != 2 {\n\t\tt.Fatalf(\"expected total posts by user1: 2, got %d.\", count)\n\t}\n\n\tif count := countPostsByUser(feed4.id, user2.String()); count != 1 {\n\t\tt.Fatalf(\"expected total posts by user2: 1, got %d.\", count)\n\t}\n\n\tif count := countPostsByUser(feed4.id, \"\"); count != 3 {\n\t\tt.Fatalf(\"expected total posts: 3, got %d.\", count)\n\t}\n}\n\nfunc testHidePostForMe(t *testing.T) {\n\tuser := std.Address(\"user\")\n\tstd.TestSetOriginCaller(user)\n\n\tfeedID8 := CreateFeed(\"teritor8\")\n\tfeed8 := mustGetFeed(feedID8)\n\n\tpostIDToHide := CreatePost(feed8.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\tpostID := CreatePost(feed8.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// Hide a post for me\n\tHidePostForMe(feed8.id, postIDToHide)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 1 {\n\t\tt.Fatalf(\"expected posts count after hidding: 1, got %q.\", count)\n\t}\n\n\t// Query from another user should return full list\n\tanother := std.Address(\"another\")\n\tstd.TestSetOriginCaller(another)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count from another: 2, got %q.\", count)\n\t}\n\n\t// UnHide a post for me\n\tstd.TestSetOriginCaller(user)\n\tUnHidePostForMe(feed8.id, postIDToHide)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count after unhidding: 2, got %q.\", count)\n\t}\n}\n\nfunc testMigrateFeedData(t *testing.T) string {\n\tfeedID := feedsV7.CreateFeed(\"teritor11\")\n\n\t// Post to test\n\tpostID := feedsV7.CreatePost(feedID, feedsV7.PostID(0), 2, `{\"metadata\": \"value\"}`)\n\tfeedsV7.ReactPost(feedID, postID, \"🇬🇸\", true)\n\n\t// Add comment to post\n\tcommentID := feedsV7.CreatePost(feedID, postID, 2, `{\"comment1\": \"value\"}`)\n\tfeedsV7.ReactPost(feedID, commentID, \"🇬🇸\", true)\n\n\t// // Post with json metadata\n\tfeedsV7.CreatePost(feedID, feedsV7.PostID(0), 2, `{'a':1}`)\n\n\t// Expect: should convert feed data to JSON successfully without error\n\tdataJSON := feedsV7.ExportFeedData(feedID)\n\tif dataJSON == \"\" {\n\t\tt.Fatalf(\"expected feed data exported successfully\")\n\t}\n\n\t// Import data =====================================\n\tImportFeedData(FeedID(uint64(feedID)), dataJSON)\n\n\t// Test public func\n\t// MigrateFromPreviousFeed(feedID)\n}\n\nfunc Test(t *testing.T) {\n\ttestCreateFeed(t)\n\n\ttestCreatePost(t)\n\n\ttestGetPosts(t)\n\n\ttestReactPost(t)\n\n\ttestCreateAndDeleteComment(t)\n\n\ttestFilterByCategories(t)\n\n\ttestTipPost(t)\n\n\ttestFilterUser(t)\n\n\ttestFlagPost(t)\n\n\ttestHidePostForMe(t)\n\n\ttestMigrateFeedData(t)\n}\n" }, { "Name": "flags.gno", diff --git a/gno.land/cmd/gnoland/testdata/issue_2283_cacheTypes.txtar b/gno.land/cmd/gnoland/testdata/issue_2283_cacheTypes.txtar index cb940cc7af9..da7e105e1d5 100644 --- a/gno.land/cmd/gnoland/testdata/issue_2283_cacheTypes.txtar +++ b/gno.land/cmd/gnoland/testdata/issue_2283_cacheTypes.txtar @@ -40,7 +40,7 @@ stdout OK! }, { "Name": "feeds_test.gno", - "Body": "package social_feeds\n\nimport (\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t// Fake previous version for testing\n\tfeedsV7 \"gno.land/r/demo/teritori/social_feeds\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\trootPostID = PostID(0)\n\tpostID1 = PostID(1)\n\tfeedID1 = FeedID(1)\n\tcat1 = uint64(1)\n\tcat2 = uint64(2)\n\tuser = testutils.TestAddress(\"user\")\n\tfilter_all = []uint64{}\n)\n\nfunc getFeed1() *Feed {\n\treturn mustGetFeed(feedID1)\n}\n\nfunc getPost1() *Post {\n\tfeed1 := getFeed1()\n\tpost1 := feed1.MustGetPost(postID1)\n\treturn post1\n}\n\nfunc testCreateFeed(t *testing.T) {\n\tfeedID := CreateFeed(\"teritori1\")\n\tfeed := mustGetFeed(feedID)\n\n\tif feedID != 1 {\n\t\tt.Fatalf(\"expected feedID: 1, got %q.\", feedID)\n\t}\n\n\tif feed.name != \"teritori1\" {\n\t\tt.Fatalf(\"expected feedName: teritori1, got %q.\", feed.name)\n\t}\n}\n\nfunc testCreatePost(t *testing.T) {\n\tmetadata := `{\"gifs\": [], \"files\": [], \"title\": \"\", \"message\": \"testouille\", \"hashtags\": [], \"mentions\": [], \"createdAt\": \"2023-03-29T12:19:04.858Z\", \"updatedAt\": \"2023-03-29T12:19:04.858Z\"}`\n\tpostID := CreatePost(feedID1, rootPostID, cat1, metadata)\n\tfeed := mustGetFeed(feedID1)\n\tpost := feed.MustGetPost(postID)\n\n\tif postID != 1 {\n\t\tt.Fatalf(\"expected postID: 1, got %q.\", postID)\n\t}\n\n\tif post.category != cat1 {\n\t\tt.Fatalf(\"expected categoryID: %q, got %q.\", cat1, post.category)\n\t}\n}\n\nfunc toPostIDsStr(posts []*Post) string {\n\tvar postIDs []string\n\tfor _, post := range posts {\n\t\tpostIDs = append(postIDs, post.id.String())\n\t}\n\n\tpostIDsStr := strings.Join(postIDs, \",\")\n\treturn postIDsStr\n}\n\nfunc testGetPosts(t *testing.T) {\n\tuser := std.Address(\"user\")\n\tstd.TestSetOriginCaller(user)\n\n\tfeedID := CreateFeed(\"teritori10\")\n\tfeed := mustGetFeed(feedID)\n\n\tCreatePost(feedID, rootPostID, cat1, \"post1\")\n\tCreatePost(feedID, rootPostID, cat1, \"post2\")\n\tCreatePost(feedID, rootPostID, cat1, \"post3\")\n\tCreatePost(feedID, rootPostID, cat1, \"post4\")\n\tCreatePost(feedID, rootPostID, cat1, \"post5\")\n\tpostIDToFlagged := CreatePost(feedID, rootPostID, cat1, \"post6\")\n\tpostIDToHide := CreatePost(feedID, rootPostID, cat1, \"post7\")\n\tCreatePost(feedID, rootPostID, cat1, \"post8\")\n\n\tvar posts []*Post\n\tvar postIDsStr string\n\n\t// Query last 3 posts\n\tposts = getPosts(feed, 0, \"\", \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,7,6\" {\n\t\tt.Fatalf(\"expected posts order: 8,7,6. Got: %s\", postIDsStr)\n\t}\n\n\t// Query page 2\n\tposts = getPosts(feed, 0, \"\", \"\", []uint64{}, 3, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\tif postIDsStr != \"5,4,3\" {\n\t\tt.Fatalf(\"expected posts order: 5,4,3. Got: %s\", postIDsStr)\n\t}\n\n\t// Exclude hidden post\n\tHidePostForMe(feed.id, postIDToHide)\n\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,6,5\" {\n\t\tt.Fatalf(\"expected posts order: 8,6,5. Got: %s\", postIDsStr)\n\t}\n\n\t// Exclude flagged post\n\tFlagPost(feed.id, postIDToFlagged)\n\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,5,4\" {\n\t\tt.Fatalf(\"expected posts order: 8,5,4. Got: %s\", postIDsStr)\n\t}\n\n\t// Pagination with hidden/flagged posts\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 3, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"3,2,1\" {\n\t\tt.Fatalf(\"expected posts order: 3,2,1. Got: %s\", postIDsStr)\n\t}\n\n\t// Query out of range\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 6, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"\" {\n\t\tt.Fatalf(\"expected posts order: ''. Got: %s\", postIDsStr)\n\t}\n}\n\nfunc testReactPost(t *testing.T) {\n\tfeed := getFeed1()\n\tpost := getPost1()\n\n\ticon := \"🥰\"\n\tReactPost(feed.id, post.id, icon, true)\n\n\t// Set reaction\n\treactionCount_, ok := post.reactions.Get(\"🥰\")\n\tif !ok {\n\t\tt.Fatalf(\"expected 🥰 exists\")\n\t}\n\n\treactionCount := reactionCount_.(int)\n\tif reactionCount != 1 {\n\t\tt.Fatalf(\"expected reactionCount: 1, got %q.\", reactionCount)\n\t}\n\n\t// Unset reaction\n\tReactPost(feed.id, post.id, icon, false)\n\t_, exist := post.reactions.Get(\"🥰\")\n\tif exist {\n\t\tt.Fatalf(\"expected 🥰 not exist\")\n\t}\n}\n\nfunc testCreateAndDeleteComment(t *testing.T) {\n\tfeed1 := getFeed1()\n\tpost1 := getPost1()\n\n\tmetadata := `empty_meta_data`\n\n\tcommentID1 := CreatePost(feed1.id, post1.id, cat1, metadata)\n\tcommentID2 := CreatePost(feed1.id, post1.id, cat1, metadata)\n\tcomment2 := feed1.MustGetPost(commentID2)\n\n\tif comment2.id != 3 { // 1 post + 2 comments = 3\n\t\tt.Fatalf(\"expected comment postID: 3, got %q.\", comment2.id)\n\t}\n\n\tif comment2.parentID != post1.id {\n\t\tt.Fatalf(\"expected comment parentID: %q, got %q.\", post1.id, comment2.parentID)\n\t}\n\n\t// Check comment count on parent\n\tif post1.commentsCount != 2 {\n\t\tt.Fatalf(\"expected comments count: 2, got %d.\", post1.commentsCount)\n\t}\n\n\t// Get comments\n\tcomments := GetComments(feed1.id, post1.id, 0, 10)\n\tcommentsParsed := ujson.ParseSlice(comments)\n\n\tif len(commentsParsed) != 2 {\n\t\tt.Fatalf(\"expected encoded comments: 2, got %q.\", commentsParsed)\n\t}\n\n\t// Delete 1 comment\n\tDeletePost(feed1.id, comment2.id)\n\tcomments = GetComments(feed1.id, post1.id, 0, 10)\n\tcommentsParsed = ujson.ParseSlice(comments)\n\n\tif len(commentsParsed) != 1 {\n\t\tt.Fatalf(\"expected encoded comments: 1, got %q.\", commentsParsed)\n\t}\n\n\t// Check comment count on parent\n\tif post1.commentsCount != 1 {\n\t\tt.Fatalf(\"expected comments count: 1, got %d.\", post1.commentsCount)\n\t}\n}\n\nfunc countPosts(feedID FeedID, categories []uint64, limit uint8) int {\n\toffset := uint64(0)\n\n\tpostsStr := GetPosts(feedID, 0, \"\", categories, offset, limit)\n\tif postsStr == \"[]\" {\n\t\treturn 0\n\t}\n\n\tparsedPosts := ujson.ParseSlice(postsStr)\n\tpostsCount := len(parsedPosts)\n\treturn postsCount\n}\n\nfunc countPostsByUser(feedID FeedID, user string) int {\n\toffset := uint64(0)\n\tlimit := uint8(10)\n\n\tpostsStr := GetPosts(feedID, 0, user, []uint64{}, offset, limit)\n\tif postsStr == \"[]\" {\n\t\treturn 0\n\t}\n\n\tparsedPosts := ujson.ParseSlice(postsStr)\n\tpostsCount := len(parsedPosts)\n\treturn postsCount\n}\n\nfunc testFilterByCategories(t *testing.T) {\n\t// // Re-add reaction to test post list\n\t// ReactPost(1, postID, \"🥰\", true)\n\t// ReactPost(1, postID, \"😇\", true)\n\n\tfilter_cat1 := []uint64{1}\n\tfilter_cat1_2 := []uint64{1, 2}\n\tfilter_cat9 := []uint64{9}\n\tfilter_cat1_2_9 := []uint64{1, 2, 9}\n\n\tfeedID2 := CreateFeed(\"teritori2\")\n\tfeed2 := mustGetFeed(feedID2)\n\n\t// Create 2 posts on root with cat1\n\tpostID1 := CreatePost(feed2.id, rootPostID, cat1, \"metadata\")\n\tpostID2 := CreatePost(feed2.id, rootPostID, cat1, \"metadata\")\n\n\t// Create 1 posts on root with cat2\n\tpostID3 := CreatePost(feed2.id, rootPostID, cat2, \"metadata\")\n\n\t// Create comments on post 1\n\tcommentPostID1 := CreatePost(feed2.id, postID1, cat1, \"metadata\")\n\n\t// cat1: Should return max = limit\n\tif count := countPosts(feed2.id, filter_cat1, 1); count != 1 {\n\t\tt.Fatalf(\"expected posts count: 1, got %q.\", count)\n\t}\n\n\t// cat1: Should return max = total\n\tif count := countPosts(feed2.id, filter_cat1, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// cat 1 + 2: Should return max = limit\n\tif count := countPosts(feed2.id, filter_cat1_2, 2); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// cat 1 + 2: Should return max = total on both\n\tif count := countPosts(feed2.id, filter_cat1_2, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// cat 1, 2, 9: Should return total of 1, 2\n\tif count := countPosts(feed2.id, filter_cat1_2_9, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// cat 9: Should return 0\n\tif count := countPosts(feed2.id, filter_cat9, 10); count != 0 {\n\t\tt.Fatalf(\"expected posts count: 0, got %q.\", count)\n\t}\n\n\t// cat all: should return all\n\tif count := countPosts(feed2.id, filter_all, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// add comments should not impact the results\n\tCreatePost(feed2.id, postID1, cat1, \"metadata\")\n\tCreatePost(feed2.id, postID2, cat1, \"metadata\")\n\n\tif count := countPosts(feed2.id, filter_all, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// delete a post should affect the result\n\tDeletePost(feed2.id, postID1)\n\n\tif count := countPosts(feed2.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n}\n\nfunc testTipPost(t *testing.T) {\n\tcreator := testutils.TestAddress(\"creator\")\n\tstd.TestIssueCoins(creator, std.Coins{{\"ugnot\", 100_000_000}})\n\n\t// NOTE: Dont know why the address should be this to be able to call banker (= std.GetCallerAt(1))\n\ttipper := testutils.TestAddress(\"tipper\")\n\tstd.TestIssueCoins(tipper, std.Coins{{\"ugnot\", 50_000_000}})\n\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\n\t// Check Original coins of creator/tipper\n\tif coins := banker.GetCoins(creator); coins[0].Amount != 100_000_000 {\n\t\tt.Fatalf(\"expected creator coin count: 100_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\tif coins := banker.GetCoins(tipper); coins[0].Amount != 50_000_000 {\n\t\tt.Fatalf(\"expected tipper coin count: 50_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\t// Creator creates feed, post\n\tstd.TestSetOriginCaller(creator)\n\n\tfeedID3 := CreateFeed(\"teritori3\")\n\tfeed3 := mustGetFeed(feedID3)\n\n\tpostID1 := CreatePost(feed3.id, rootPostID, cat1, \"metadata\")\n\tpost1 := feed3.MustGetPost(postID1)\n\n\t// Tiper tips the ppst\n\tstd.TestSetOriginCaller(tipper)\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 1_000_000}}, nil)\n\tTipPost(feed3.id, post1.id)\n\n\t// Coin must be increased for creator\n\tif coins := banker.GetCoins(creator); coins[0].Amount != 101_000_000 {\n\t\tt.Fatalf(\"expected creator coin after beging tipped: 101_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\t// Total tip amount should increased\n\tif post1.tipAmount != 1_000_000 {\n\t\tt.Fatalf(\"expected total tipAmount: 1_000_000, got %d.\", post1.tipAmount)\n\t}\n\n\t// Add more tip should update this total\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 2_000_000}}, nil)\n\tTipPost(feed3.id, post1.id)\n\n\tif post1.tipAmount != 3_000_000 {\n\t\tt.Fatalf(\"expected total tipAmount: 3_000_000, got %d.\", post1.tipAmount)\n\t}\n}\n\nfunc testFlagPost(t *testing.T) {\n\tflagger := testutils.TestAddress(\"flagger\")\n\n\tfeedID9 := CreateFeed(\"teritori9\")\n\tfeed9 := mustGetFeed(feedID9)\n\n\tCreatePost(feed9.id, rootPostID, cat1, \"metadata1\")\n\tpid := CreatePost(feed9.id, rootPostID, cat1, \"metadata1\")\n\n\t// Flag post\n\tstd.TestSetOriginCaller(flagger)\n\tFlagPost(feed9.id, pid)\n\n\t// Another user flags\n\tanother := testutils.TestAddress(\"another\")\n\tstd.TestSetOriginCaller(another)\n\tFlagPost(feed9.id, pid)\n\n\tflaggedPostsStr := GetFlaggedPosts(feed9.id, 0, 10)\n\tparsed := ujson.ParseSlice(flaggedPostsStr)\n\tif flaggedPostsCount := len(parsed); flaggedPostsCount != 1 {\n\t\tt.Fatalf(\"expected flagged posts: 1, got %d.\", flaggedPostsCount)\n\t}\n}\n\nfunc testFilterUser(t *testing.T) {\n\tuser1 := testutils.TestAddress(\"user1\")\n\tuser2 := testutils.TestAddress(\"user2\")\n\n\t// User1 create 2 posts\n\tstd.TestSetOriginCaller(user1)\n\n\tfeedID4 := CreateFeed(\"teritori4\")\n\tfeed4 := mustGetFeed(feedID4)\n\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata2\": \"value\"}`)\n\n\t// User2 create 1 post\n\tstd.TestSetOriginCaller(user2)\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\n\tif count := countPostsByUser(feed4.id, user1.String()); count != 2 {\n\t\tt.Fatalf(\"expected total posts by user1: 2, got %d.\", count)\n\t}\n\n\tif count := countPostsByUser(feed4.id, user2.String()); count != 1 {\n\t\tt.Fatalf(\"expected total posts by user2: 1, got %d.\", count)\n\t}\n\n\tif count := countPostsByUser(feed4.id, \"\"); count != 3 {\n\t\tt.Fatalf(\"expected total posts: 3, got %d.\", count)\n\t}\n}\n\nfunc testHidePostForMe(t *testing.T) {\n\tuser := std.Address(\"user\")\n\tstd.TestSetOriginCaller(user)\n\n\tfeedID8 := CreateFeed(\"teritor8\")\n\tfeed8 := mustGetFeed(feedID8)\n\n\tpostIDToHide := CreatePost(feed8.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\tpostID := CreatePost(feed8.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// Hide a post for me\n\tHidePostForMe(feed8.id, postIDToHide)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 1 {\n\t\tt.Fatalf(\"expected posts count after hidding: 1, got %q.\", count)\n\t}\n\n\t// Query from another user should return full list\n\tanother := std.Address(\"another\")\n\tstd.TestSetOriginCaller(another)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count from another: 2, got %q.\", count)\n\t}\n\n\t// UnHide a post for me\n\tstd.TestSetOriginCaller(user)\n\tUnHidePostForMe(feed8.id, postIDToHide)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count after unhidding: 2, got %q.\", count)\n\t}\n}\n\nfunc testMigrateFeedData(t *testing.T) string {\n\tfeedID := feedsV7.CreateFeed(\"teritor11\")\n\n\t// Post to test\n\tpostID := feedsV7.CreatePost(feedID, feedsV7.PostID(0), 2, `{\"metadata\": \"value\"}`)\n\tfeedsV7.ReactPost(feedID, postID, \"🇬🇸\", true)\n\n\t// Add comment to post\n\tcommentID := feedsV7.CreatePost(feedID, postID, 2, `{\"comment1\": \"value\"}`)\n\tfeedsV7.ReactPost(feedID, commentID, \"🇬🇸\", true)\n\n\t// // Post with json metadata\n\tfeedsV7.CreatePost(feedID, feedsV7.PostID(0), 2, `{'a':1}`)\n\n\t// Expect: should convert feed data to JSON successfully without error\n\tdataJSON := feedsV7.ExportFeedData(feedID)\n\tif dataJSON == \"\" {\n\t\tt.Fatalf(\"expected feed data exported successfully\")\n\t}\n\n\t// Import data =====================================\n\tImportFeedData(FeedID(uint64(feedID)), dataJSON)\n\n\t// Test public func\n\t// MigrateFromPreviousFeed(feedID)\n}\n\nfunc Test(t *testing.T) {\n\ttestCreateFeed(t)\n\n\ttestCreatePost(t)\n\n\ttestGetPosts(t)\n\n\ttestReactPost(t)\n\n\ttestCreateAndDeleteComment(t)\n\n\ttestFilterByCategories(t)\n\n\ttestTipPost(t)\n\n\ttestFilterUser(t)\n\n\ttestFlagPost(t)\n\n\ttestHidePostForMe(t)\n\n\ttestMigrateFeedData(t)\n}\n" + "Body": "package social_feeds\n\nimport (\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t// Fake previous version for testing\n\tfeedsV7 \"gno.land/r/demo/teritori/social_feeds\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\trootPostID = PostID(0)\n\tpostID1 = PostID(1)\n\tfeedID1 = FeedID(1)\n\tcat1 = uint64(1)\n\tcat2 = uint64(2)\n\tuser = testutils.TestAddress(\"user\")\n\tfilter_all = []uint64{}\n)\n\nfunc getFeed1() *Feed {\n\treturn mustGetFeed(feedID1)\n}\n\nfunc getPost1() *Post {\n\tfeed1 := getFeed1()\n\tpost1 := feed1.MustGetPost(postID1)\n\treturn post1\n}\n\nfunc testCreateFeed(t *testing.T) {\n\tfeedID := CreateFeed(\"teritori1\")\n\tfeed := mustGetFeed(feedID)\n\n\tif feedID != 1 {\n\t\tt.Fatalf(\"expected feedID: 1, got %q.\", feedID)\n\t}\n\n\tif feed.name != \"teritori1\" {\n\t\tt.Fatalf(\"expected feedName: teritori1, got %q.\", feed.name)\n\t}\n}\n\nfunc testCreatePost(t *testing.T) {\n\tmetadata := `{\"gifs\": [], \"files\": [], \"title\": \"\", \"message\": \"testouille\", \"hashtags\": [], \"mentions\": [], \"createdAt\": \"2023-03-29T12:19:04.858Z\", \"updatedAt\": \"2023-03-29T12:19:04.858Z\"}`\n\tpostID := CreatePost(feedID1, rootPostID, cat1, metadata)\n\tfeed := mustGetFeed(feedID1)\n\tpost := feed.MustGetPost(postID)\n\n\tif postID != 1 {\n\t\tt.Fatalf(\"expected postID: 1, got %q.\", postID)\n\t}\n\n\tif post.category != cat1 {\n\t\tt.Fatalf(\"expected categoryID: %q, got %q.\", cat1, post.category)\n\t}\n}\n\nfunc toPostIDsStr(posts []*Post) string {\n\tvar postIDs []string\n\tfor _, post := range posts {\n\t\tpostIDs = append(postIDs, post.id.String())\n\t}\n\n\tpostIDsStr := strings.Join(postIDs, \",\")\n\treturn postIDsStr\n}\n\nfunc testGetPosts(t *testing.T) {\n\tuser := std.Address(\"user\")\n\tstd.TestSetOriginCaller(user)\n\n\tfeedID := CreateFeed(\"teritori10\")\n\tfeed := mustGetFeed(feedID)\n\n\tCreatePost(feedID, rootPostID, cat1, \"post1\")\n\tCreatePost(feedID, rootPostID, cat1, \"post2\")\n\tCreatePost(feedID, rootPostID, cat1, \"post3\")\n\tCreatePost(feedID, rootPostID, cat1, \"post4\")\n\tCreatePost(feedID, rootPostID, cat1, \"post5\")\n\tpostIDToFlagged := CreatePost(feedID, rootPostID, cat1, \"post6\")\n\tpostIDToHide := CreatePost(feedID, rootPostID, cat1, \"post7\")\n\tCreatePost(feedID, rootPostID, cat1, \"post8\")\n\n\tvar posts []*Post\n\tvar postIDsStr string\n\n\t// Query last 3 posts\n\tposts = getPosts(feed, 0, \"\", \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,7,6\" {\n\t\tt.Fatalf(\"expected posts order: 8,7,6. Got: %s\", postIDsStr)\n\t}\n\n\t// Query page 2\n\tposts = getPosts(feed, 0, \"\", \"\", []uint64{}, 3, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\tif postIDsStr != \"5,4,3\" {\n\t\tt.Fatalf(\"expected posts order: 5,4,3. Got: %s\", postIDsStr)\n\t}\n\n\t// Exclude hidden post\n\tHidePostForMe(feed.id, postIDToHide)\n\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,6,5\" {\n\t\tt.Fatalf(\"expected posts order: 8,6,5. Got: %s\", postIDsStr)\n\t}\n\n\t// Exclude flagged post\n\tFlagPost(feed.id, postIDToFlagged)\n\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,5,4\" {\n\t\tt.Fatalf(\"expected posts order: 8,5,4. Got: %s\", postIDsStr)\n\t}\n\n\t// Pagination with hidden/flagged posts\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 3, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"3,2,1\" {\n\t\tt.Fatalf(\"expected posts order: 3,2,1. Got: %s\", postIDsStr)\n\t}\n\n\t// Query out of range\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 6, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"\" {\n\t\tt.Fatalf(\"expected posts order: ''. Got: %s\", postIDsStr)\n\t}\n}\n\nfunc testReactPost(t *testing.T) {\n\tfeed := getFeed1()\n\tpost := getPost1()\n\n\ticon := \"🥰\"\n\tReactPost(feed.id, post.id, icon, true)\n\n\t// Set reaction\n\treactionCount_, ok := post.reactions.Get(\"🥰\")\n\tif !ok {\n\t\tt.Fatalf(\"expected 🥰 exists\")\n\t}\n\n\treactionCount := reactionCount_.(int)\n\tif reactionCount != 1 {\n\t\tt.Fatalf(\"expected reactionCount: 1, got %q.\", reactionCount)\n\t}\n\n\t// Unset reaction\n\tReactPost(feed.id, post.id, icon, false)\n\t_, exist := post.reactions.Get(\"🥰\")\n\tif exist {\n\t\tt.Fatalf(\"expected 🥰 not exist\")\n\t}\n}\n\nfunc testCreateAndDeleteComment(t *testing.T) {\n\tfeed1 := getFeed1()\n\tpost1 := getPost1()\n\n\tmetadata := `empty_meta_data`\n\n\tcommentID1 := CreatePost(feed1.id, post1.id, cat1, metadata)\n\tcommentID2 := CreatePost(feed1.id, post1.id, cat1, metadata)\n\tcomment2 := feed1.MustGetPost(commentID2)\n\n\tif comment2.id != 3 { // 1 post + 2 comments = 3\n\t\tt.Fatalf(\"expected comment postID: 3, got %q.\", comment2.id)\n\t}\n\n\tif comment2.parentID != post1.id {\n\t\tt.Fatalf(\"expected comment parentID: %q, got %q.\", post1.id, comment2.parentID)\n\t}\n\n\t// Check comment count on parent\n\tif post1.commentsCount != 2 {\n\t\tt.Fatalf(\"expected comments count: 2, got %d.\", post1.commentsCount)\n\t}\n\n\t// Get comments\n\tcomments := GetComments(feed1.id, post1.id, 0, 10)\n\tcommentsParsed := ujson.ParseSlice(comments)\n\n\tif len(commentsParsed) != 2 {\n\t\tt.Fatalf(\"expected encoded comments: 2, got %q.\", commentsParsed)\n\t}\n\n\t// Delete 1 comment\n\tDeletePost(feed1.id, comment2.id)\n\tcomments = GetComments(feed1.id, post1.id, 0, 10)\n\tcommentsParsed = ujson.ParseSlice(comments)\n\n\tif len(commentsParsed) != 1 {\n\t\tt.Fatalf(\"expected encoded comments: 1, got %q.\", commentsParsed)\n\t}\n\n\t// Check comment count on parent\n\tif post1.commentsCount != 1 {\n\t\tt.Fatalf(\"expected comments count: 1, got %d.\", post1.commentsCount)\n\t}\n}\n\nfunc countPosts(feedID FeedID, categories []uint64, limit uint8) int {\n\toffset := uint64(0)\n\n\tpostsStr := GetPosts(feedID, 0, \"\", categories, offset, limit)\n\tif postsStr == \"[]\" {\n\t\treturn 0\n\t}\n\n\tparsedPosts := ujson.ParseSlice(postsStr)\n\tpostsCount := len(parsedPosts)\n\treturn postsCount\n}\n\nfunc countPostsByUser(feedID FeedID, user string) int {\n\toffset := uint64(0)\n\tlimit := uint8(10)\n\n\tpostsStr := GetPosts(feedID, 0, user, []uint64{}, offset, limit)\n\tif postsStr == \"[]\" {\n\t\treturn 0\n\t}\n\n\tparsedPosts := ujson.ParseSlice(postsStr)\n\tpostsCount := len(parsedPosts)\n\treturn postsCount\n}\n\nfunc testFilterByCategories(t *testing.T) {\n\t// // Re-add reaction to test post list\n\t// ReactPost(1, postID, \"🥰\", true)\n\t// ReactPost(1, postID, \"😇\", true)\n\n\tfilter_cat1 := []uint64{1}\n\tfilter_cat1_2 := []uint64{1, 2}\n\tfilter_cat9 := []uint64{9}\n\tfilter_cat1_2_9 := []uint64{1, 2, 9}\n\n\tfeedID2 := CreateFeed(\"teritori2\")\n\tfeed2 := mustGetFeed(feedID2)\n\n\t// Create 2 posts on root with cat1\n\tpostID1 := CreatePost(feed2.id, rootPostID, cat1, \"metadata\")\n\tpostID2 := CreatePost(feed2.id, rootPostID, cat1, \"metadata\")\n\n\t// Create 1 posts on root with cat2\n\tpostID3 := CreatePost(feed2.id, rootPostID, cat2, \"metadata\")\n\n\t// Create comments on post 1\n\tcommentPostID1 := CreatePost(feed2.id, postID1, cat1, \"metadata\")\n\n\t// cat1: Should return max = limit\n\tif count := countPosts(feed2.id, filter_cat1, 1); count != 1 {\n\t\tt.Fatalf(\"expected posts count: 1, got %q.\", count)\n\t}\n\n\t// cat1: Should return max = total\n\tif count := countPosts(feed2.id, filter_cat1, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// cat 1 + 2: Should return max = limit\n\tif count := countPosts(feed2.id, filter_cat1_2, 2); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// cat 1 + 2: Should return max = total on both\n\tif count := countPosts(feed2.id, filter_cat1_2, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// cat 1, 2, 9: Should return total of 1, 2\n\tif count := countPosts(feed2.id, filter_cat1_2_9, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// cat 9: Should return 0\n\tif count := countPosts(feed2.id, filter_cat9, 10); count != 0 {\n\t\tt.Fatalf(\"expected posts count: 0, got %q.\", count)\n\t}\n\n\t// cat all: should return all\n\tif count := countPosts(feed2.id, filter_all, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// add comments should not impact the results\n\tCreatePost(feed2.id, postID1, cat1, \"metadata\")\n\tCreatePost(feed2.id, postID2, cat1, \"metadata\")\n\n\tif count := countPosts(feed2.id, filter_all, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// delete a post should affect the result\n\tDeletePost(feed2.id, postID1)\n\n\tif count := countPosts(feed2.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n}\n\nfunc testTipPost(t *testing.T) {\n\tcreator := testutils.TestAddress(\"creator\")\n\tstd.TestIssueCoins(creator, std.Coins{{\"ugnot\", 100_000_000}})\n\n\t// NOTE: Dont know why the address should be this to be able to call banker (= std.CallerAt(1))\n\ttipper := testutils.TestAddress(\"tipper\")\n\tstd.TestIssueCoins(tipper, std.Coins{{\"ugnot\", 50_000_000}})\n\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\n\t// Check Original coins of creator/tipper\n\tif coins := banker.GetCoins(creator); coins[0].Amount != 100_000_000 {\n\t\tt.Fatalf(\"expected creator coin count: 100_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\tif coins := banker.GetCoins(tipper); coins[0].Amount != 50_000_000 {\n\t\tt.Fatalf(\"expected tipper coin count: 50_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\t// Creator creates feed, post\n\tstd.TestSetOriginCaller(creator)\n\n\tfeedID3 := CreateFeed(\"teritori3\")\n\tfeed3 := mustGetFeed(feedID3)\n\n\tpostID1 := CreatePost(feed3.id, rootPostID, cat1, \"metadata\")\n\tpost1 := feed3.MustGetPost(postID1)\n\n\t// Tiper tips the ppst\n\tstd.TestSetOriginCaller(tipper)\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 1_000_000}}, nil)\n\tTipPost(feed3.id, post1.id)\n\n\t// Coin must be increased for creator\n\tif coins := banker.GetCoins(creator); coins[0].Amount != 101_000_000 {\n\t\tt.Fatalf(\"expected creator coin after beging tipped: 101_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\t// Total tip amount should increased\n\tif post1.tipAmount != 1_000_000 {\n\t\tt.Fatalf(\"expected total tipAmount: 1_000_000, got %d.\", post1.tipAmount)\n\t}\n\n\t// Add more tip should update this total\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 2_000_000}}, nil)\n\tTipPost(feed3.id, post1.id)\n\n\tif post1.tipAmount != 3_000_000 {\n\t\tt.Fatalf(\"expected total tipAmount: 3_000_000, got %d.\", post1.tipAmount)\n\t}\n}\n\nfunc testFlagPost(t *testing.T) {\n\tflagger := testutils.TestAddress(\"flagger\")\n\n\tfeedID9 := CreateFeed(\"teritori9\")\n\tfeed9 := mustGetFeed(feedID9)\n\n\tCreatePost(feed9.id, rootPostID, cat1, \"metadata1\")\n\tpid := CreatePost(feed9.id, rootPostID, cat1, \"metadata1\")\n\n\t// Flag post\n\tstd.TestSetOriginCaller(flagger)\n\tFlagPost(feed9.id, pid)\n\n\t// Another user flags\n\tanother := testutils.TestAddress(\"another\")\n\tstd.TestSetOriginCaller(another)\n\tFlagPost(feed9.id, pid)\n\n\tflaggedPostsStr := GetFlaggedPosts(feed9.id, 0, 10)\n\tparsed := ujson.ParseSlice(flaggedPostsStr)\n\tif flaggedPostsCount := len(parsed); flaggedPostsCount != 1 {\n\t\tt.Fatalf(\"expected flagged posts: 1, got %d.\", flaggedPostsCount)\n\t}\n}\n\nfunc testFilterUser(t *testing.T) {\n\tuser1 := testutils.TestAddress(\"user1\")\n\tuser2 := testutils.TestAddress(\"user2\")\n\n\t// User1 create 2 posts\n\tstd.TestSetOriginCaller(user1)\n\n\tfeedID4 := CreateFeed(\"teritori4\")\n\tfeed4 := mustGetFeed(feedID4)\n\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata2\": \"value\"}`)\n\n\t// User2 create 1 post\n\tstd.TestSetOriginCaller(user2)\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\n\tif count := countPostsByUser(feed4.id, user1.String()); count != 2 {\n\t\tt.Fatalf(\"expected total posts by user1: 2, got %d.\", count)\n\t}\n\n\tif count := countPostsByUser(feed4.id, user2.String()); count != 1 {\n\t\tt.Fatalf(\"expected total posts by user2: 1, got %d.\", count)\n\t}\n\n\tif count := countPostsByUser(feed4.id, \"\"); count != 3 {\n\t\tt.Fatalf(\"expected total posts: 3, got %d.\", count)\n\t}\n}\n\nfunc testHidePostForMe(t *testing.T) {\n\tuser := std.Address(\"user\")\n\tstd.TestSetOriginCaller(user)\n\n\tfeedID8 := CreateFeed(\"teritor8\")\n\tfeed8 := mustGetFeed(feedID8)\n\n\tpostIDToHide := CreatePost(feed8.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\tpostID := CreatePost(feed8.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// Hide a post for me\n\tHidePostForMe(feed8.id, postIDToHide)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 1 {\n\t\tt.Fatalf(\"expected posts count after hidding: 1, got %q.\", count)\n\t}\n\n\t// Query from another user should return full list\n\tanother := std.Address(\"another\")\n\tstd.TestSetOriginCaller(another)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count from another: 2, got %q.\", count)\n\t}\n\n\t// UnHide a post for me\n\tstd.TestSetOriginCaller(user)\n\tUnHidePostForMe(feed8.id, postIDToHide)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count after unhidding: 2, got %q.\", count)\n\t}\n}\n\nfunc testMigrateFeedData(t *testing.T) string {\n\tfeedID := feedsV7.CreateFeed(\"teritor11\")\n\n\t// Post to test\n\tpostID := feedsV7.CreatePost(feedID, feedsV7.PostID(0), 2, `{\"metadata\": \"value\"}`)\n\tfeedsV7.ReactPost(feedID, postID, \"🇬🇸\", true)\n\n\t// Add comment to post\n\tcommentID := feedsV7.CreatePost(feedID, postID, 2, `{\"comment1\": \"value\"}`)\n\tfeedsV7.ReactPost(feedID, commentID, \"🇬🇸\", true)\n\n\t// // Post with json metadata\n\tfeedsV7.CreatePost(feedID, feedsV7.PostID(0), 2, `{'a':1}`)\n\n\t// Expect: should convert feed data to JSON successfully without error\n\tdataJSON := feedsV7.ExportFeedData(feedID)\n\tif dataJSON == \"\" {\n\t\tt.Fatalf(\"expected feed data exported successfully\")\n\t}\n\n\t// Import data =====================================\n\tImportFeedData(FeedID(uint64(feedID)), dataJSON)\n\n\t// Test public func\n\t// MigrateFromPreviousFeed(feedID)\n}\n\nfunc Test(t *testing.T) {\n\ttestCreateFeed(t)\n\n\ttestCreatePost(t)\n\n\ttestGetPosts(t)\n\n\ttestReactPost(t)\n\n\ttestCreateAndDeleteComment(t)\n\n\ttestFilterByCategories(t)\n\n\ttestTipPost(t)\n\n\ttestFilterUser(t)\n\n\ttestFlagPost(t)\n\n\ttestHidePostForMe(t)\n\n\ttestMigrateFeedData(t)\n}\n" }, { "Name": "flags.gno", diff --git a/misc/deployments/test5.gno.land/genesis_txs.jsonl b/misc/deployments/test5.gno.land/genesis_txs.jsonl index 80488b98e90..2a3c299c9e7 100755 --- a/misc/deployments/test5.gno.land/genesis_txs.jsonl +++ b/misc/deployments/test5.gno.land/genesis_txs.jsonl @@ -1,6 +1,6 @@ {"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"bank","path":"gno.land/p/demo/bank","files":[{"name":"types.gno","body":"// TODO: this is an example, and needs to be fixed up and tested.\n\npackage bank\n\n// NOTE: unexposed struct for security.\ntype order struct {\n\tfrom Address\n\tto Address\n\tamount Coins\n\tprocessed bool\n}\n\n// NOTE: unexposed methods for security.\nfunc (ch *order) string() string {\n\treturn \"TODO\"\n}\n\n// Wraps the internal *order for external use.\ntype Order struct {\n\t*order\n}\n\n// XXX only exposed for demonstration. TODO unexpose, make full demo.\nfunc NewOrder(from Address, to Address, amount Coins) Order {\n\treturn Order{\n\t\torder: \u0026order{\n\t\t\tfrom: from,\n\t\t\tto: to,\n\t\t\tamount: amount,\n\t\t},\n\t}\n}\n\n// Panics if error, or already processed.\nfunc (o Order) Execute() {\n\tif o.order.processed {\n\t\tpanic(\"order already processed\")\n\t}\n\to.order.processed = true\n\t// TODO implemement.\n}\n\nfunc (o Order) IsZero() bool {\n\treturn o.order == nil\n}\n\nfunc (o Order) From() Address {\n\treturn o.order.from\n}\n\nfunc (o Order) To() Address {\n\treturn o.order.to\n}\n\nfunc (o Order) Amount() Coins {\n\treturn o.order.amount\n}\n\nfunc (o Order) Processed() bool {\n\treturn o.order.processed\n}\n\n//----------------------------------------\n// Escrow\n\ntype EscrowTerms struct {\n\tPartyA Address\n\tPartyB Address\n\tAmountA Coins\n\tAmountB Coins\n}\n\ntype EscrowContract struct {\n\tEscrowTerms\n\tOrderA Order\n\tOrderB Order\n}\n\nfunc CreateEscrow(terms EscrowTerms) *EscrowContract {\n\treturn \u0026EscrowContract{\n\t\tEscrowTerms: terms,\n\t}\n}\n\nfunc (esc *EscrowContract) SetOrderA(order Order) {\n\tif !esc.OrderA.IsZero() {\n\t\tpanic(\"order-a already set\")\n\t}\n\tif esc.EscrowTerms.PartyA != order.From() {\n\t\tpanic(\"invalid order-a:from mismatch\")\n\t}\n\tif esc.EscrowTerms.PartyB != order.To() {\n\t\tpanic(\"invalid order-a:to mismatch\")\n\t}\n\tif !esc.EscrowTerms.AmountA.Equal(order.Amount()) {\n\t\tpanic(\"invalid order-a amount\")\n\t}\n\tesc.OrderA = order\n}\n\nfunc (esc *EscrowContract) SetOrderB(order Order) {\n\tif !esc.OrderB.IsZero() {\n\t\tpanic(\"order-b already set\")\n\t}\n\tif esc.EscrowTerms.PartyB != order.From() {\n\t\tpanic(\"invalid order-b:from mismatch\")\n\t}\n\tif esc.EscrowTerms.PartyA != order.To() {\n\t\tpanic(\"invalid order-b:to mismatch\")\n\t}\n\tif !esc.EscrowTerms.AmountB.Equal(order.Amount()) {\n\t\tpanic(\"invalid order-b amount\")\n\t}\n\tesc.OrderA = order\n}\n\nfunc (esc *EscrowContract) Execute() {\n\tif esc.OrderA.IsZero() {\n\t\tpanic(\"order-a not yet set\")\n\t}\n\tif esc.OrderB.IsZero() {\n\t\tpanic(\"order-b not yet set\")\n\t}\n\t// NOTE: succeeds atomically.\n\tesc.OrderA.Execute()\n\tesc.OrderB.Execute()\n}\n\n//----------------------------------------\n// TODO: actually implement these in std package.\n\ntype (\n\tAddress string\n\tCoins []Coin\n\tCoin struct {\n\t\tDenom bool\n\t\tAmount int64\n\t}\n)\n\nfunc (a Coins) Equal(b Coins) bool {\n\tif len(a) != len(b) {\n\t\treturn false\n\t}\n\tfor i, v := range a {\n\t\tif v != b[i] {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\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":"avl","path":"gno.land/p/demo/avl","files":[{"name":"node.gno","body":"package avl\n\n//----------------------------------------\n// Node\n\n// Node represents a node in an AVL tree.\ntype Node struct {\n\tkey string // key is the unique identifier for the node.\n\tvalue interface{} // value is the data stored in the node.\n\theight int8 // height is the height of the node in the tree.\n\tsize int // size is the number of nodes in the subtree rooted at this node.\n\tleftNode *Node // leftNode is the left child of the node.\n\trightNode *Node // rightNode is the right child of the node.\n}\n\n// NewNode creates a new node with the given key and value.\nfunc NewNode(key string, value interface{}) *Node {\n\treturn \u0026Node{\n\t\tkey: key,\n\t\tvalue: value,\n\t\theight: 0,\n\t\tsize: 1,\n\t}\n}\n\n// Size returns the size of the subtree rooted at the node.\nfunc (node *Node) Size() int {\n\tif node == nil {\n\t\treturn 0\n\t}\n\treturn node.size\n}\n\n// IsLeaf checks if the node is a leaf node (has no children).\nfunc (node *Node) IsLeaf() bool {\n\treturn node.height == 0\n}\n\n// Key returns the key of the node.\nfunc (node *Node) Key() string {\n\treturn node.key\n}\n\n// Value returns the value of the node.\nfunc (node *Node) Value() interface{} {\n\treturn node.value\n}\n\n// _copy creates a copy of the node (excluding value).\nfunc (node *Node) _copy() *Node {\n\tif node.height == 0 {\n\t\tpanic(\"Why are you copying a value node?\")\n\t}\n\treturn \u0026Node{\n\t\tkey: node.key,\n\t\theight: node.height,\n\t\tsize: node.size,\n\t\tleftNode: node.leftNode,\n\t\trightNode: node.rightNode,\n\t}\n}\n\n// Has checks if a node with the given key exists in the subtree rooted at the node.\nfunc (node *Node) Has(key string) (has bool) {\n\tif node == nil {\n\t\treturn false\n\t}\n\tif node.key == key {\n\t\treturn true\n\t}\n\tif node.height == 0 {\n\t\treturn false\n\t}\n\tif key \u003c node.key {\n\t\treturn node.getLeftNode().Has(key)\n\t}\n\treturn node.getRightNode().Has(key)\n}\n\n// Get searches for a node with the given key in the subtree rooted at the node\n// and returns its index, value, and whether it exists.\nfunc (node *Node) Get(key string) (index int, value interface{}, exists bool) {\n\tif node == nil {\n\t\treturn 0, nil, false\n\t}\n\n\tif node.height == 0 {\n\t\tif node.key == key {\n\t\t\treturn 0, node.value, true\n\t\t}\n\t\tif node.key \u003c key {\n\t\t\treturn 1, nil, false\n\t\t}\n\t\treturn 0, nil, false\n\t}\n\n\tif key \u003c node.key {\n\t\treturn node.getLeftNode().Get(key)\n\t}\n\n\trightNode := node.getRightNode()\n\tindex, value, exists = rightNode.Get(key)\n\tindex += node.size - rightNode.size\n\treturn index, value, exists\n}\n\n// GetByIndex retrieves the key-value pair of the node at the given index\n// in the subtree rooted at the node.\nfunc (node *Node) GetByIndex(index int) (key string, value interface{}) {\n\tif node.height == 0 {\n\t\tif index == 0 {\n\t\t\treturn node.key, node.value\n\t\t}\n\t\tpanic(\"GetByIndex asked for invalid index\")\n\t}\n\t// TODO: could improve this by storing the sizes\n\tleftNode := node.getLeftNode()\n\tif index \u003c leftNode.size {\n\t\treturn leftNode.GetByIndex(index)\n\t}\n\treturn node.getRightNode().GetByIndex(index - leftNode.size)\n}\n\n// Set inserts a new node with the given key-value pair into the subtree rooted at the node,\n// and returns the new root of the subtree and whether an existing node was updated.\n//\n// XXX consider a better way to do this... perhaps split Node from Node.\nfunc (node *Node) Set(key string, value interface{}) (newSelf *Node, updated bool) {\n\tif node == nil {\n\t\treturn NewNode(key, value), false\n\t}\n\n\tif node.height == 0 {\n\t\treturn node.setLeaf(key, value)\n\t}\n\n\tnode = node._copy()\n\tif key \u003c node.key {\n\t\tnode.leftNode, updated = node.getLeftNode().Set(key, value)\n\t} else {\n\t\tnode.rightNode, updated = node.getRightNode().Set(key, value)\n\t}\n\n\tif updated {\n\t\treturn node, updated\n\t}\n\n\tnode.calcHeightAndSize()\n\treturn node.balance(), updated\n}\n\n// setLeaf inserts a new leaf node with the given key-value pair into the subtree rooted at the node,\n// and returns the new root of the subtree and whether an existing node was updated.\nfunc (node *Node) setLeaf(key string, value interface{}) (newSelf *Node, updated bool) {\n\tif key == node.key {\n\t\treturn NewNode(key, value), true\n\t}\n\n\tif key \u003c node.key {\n\t\treturn \u0026Node{\n\t\t\tkey: node.key,\n\t\t\theight: 1,\n\t\t\tsize: 2,\n\t\t\tleftNode: NewNode(key, value),\n\t\t\trightNode: node,\n\t\t}, false\n\t}\n\n\treturn \u0026Node{\n\t\tkey: key,\n\t\theight: 1,\n\t\tsize: 2,\n\t\tleftNode: node,\n\t\trightNode: NewNode(key, value),\n\t}, false\n}\n\n// Remove deletes the node with the given key from the subtree rooted at the node.\n// returns the new root of the subtree, the new leftmost leaf key (if changed),\n// the removed value and the removal was successful.\nfunc (node *Node) Remove(key string) (\n\tnewNode *Node, newKey string, value interface{}, removed bool,\n) {\n\tif node == nil {\n\t\treturn nil, \"\", nil, false\n\t}\n\tif node.height == 0 {\n\t\tif key == node.key {\n\t\t\treturn nil, \"\", node.value, true\n\t\t}\n\t\treturn node, \"\", nil, false\n\t}\n\tif key \u003c node.key {\n\t\tvar newLeftNode *Node\n\t\tnewLeftNode, newKey, value, removed = node.getLeftNode().Remove(key)\n\t\tif !removed {\n\t\t\treturn node, \"\", value, false\n\t\t}\n\t\tif newLeftNode == nil { // left node held value, was removed\n\t\t\treturn node.rightNode, node.key, value, true\n\t\t}\n\t\tnode = node._copy()\n\t\tnode.leftNode = newLeftNode\n\t\tnode.calcHeightAndSize()\n\t\tnode = node.balance()\n\t\treturn node, newKey, value, true\n\t}\n\n\tvar newRightNode *Node\n\tnewRightNode, newKey, value, removed = node.getRightNode().Remove(key)\n\tif !removed {\n\t\treturn node, \"\", value, false\n\t}\n\tif newRightNode == nil { // right node held value, was removed\n\t\treturn node.leftNode, \"\", value, true\n\t}\n\tnode = node._copy()\n\tnode.rightNode = newRightNode\n\tif newKey != \"\" {\n\t\tnode.key = newKey\n\t}\n\tnode.calcHeightAndSize()\n\tnode = node.balance()\n\treturn node, \"\", value, true\n}\n\n// getLeftNode returns the left child of the node.\nfunc (node *Node) getLeftNode() *Node {\n\treturn node.leftNode\n}\n\n// getRightNode returns the right child of the node.\nfunc (node *Node) getRightNode() *Node {\n\treturn node.rightNode\n}\n\n// rotateRight performs a right rotation on the node and returns the new root.\n// NOTE: overwrites node\n// TODO: optimize balance \u0026 rotate\nfunc (node *Node) rotateRight() *Node {\n\tnode = node._copy()\n\tl := node.getLeftNode()\n\t_l := l._copy()\n\n\t_lrCached := _l.rightNode\n\t_l.rightNode = node\n\tnode.leftNode = _lrCached\n\n\tnode.calcHeightAndSize()\n\t_l.calcHeightAndSize()\n\n\treturn _l\n}\n\n// rotateLeft performs a left rotation on the node and returns the new root.\n// NOTE: overwrites node\n// TODO: optimize balance \u0026 rotate\nfunc (node *Node) rotateLeft() *Node {\n\tnode = node._copy()\n\tr := node.getRightNode()\n\t_r := r._copy()\n\n\t_rlCached := _r.leftNode\n\t_r.leftNode = node\n\tnode.rightNode = _rlCached\n\n\tnode.calcHeightAndSize()\n\t_r.calcHeightAndSize()\n\n\treturn _r\n}\n\n// calcHeightAndSize updates the height and size of the node based on its children.\n// NOTE: mutates height and size\nfunc (node *Node) calcHeightAndSize() {\n\tnode.height = maxInt8(node.getLeftNode().height, node.getRightNode().height) + 1\n\tnode.size = node.getLeftNode().size + node.getRightNode().size\n}\n\n// calcBalance calculates the balance factor of the node.\nfunc (node *Node) calcBalance() int {\n\treturn int(node.getLeftNode().height) - int(node.getRightNode().height)\n}\n\n// balance balances the subtree rooted at the node and returns the new root.\n// NOTE: assumes that node can be modified\n// TODO: optimize balance \u0026 rotate\nfunc (node *Node) balance() (newSelf *Node) {\n\tbalance := node.calcBalance()\n\tif balance \u003e= -1 {\n\t\treturn node\n\t}\n\tif balance \u003e 1 {\n\t\tif node.getLeftNode().calcBalance() \u003e= 0 {\n\t\t\t// Left Left Case\n\t\t\treturn node.rotateRight()\n\t\t}\n\t\t// Left Right Case\n\t\tleft := node.getLeftNode()\n\t\tnode.leftNode = left.rotateLeft()\n\t\treturn node.rotateRight()\n\t}\n\n\tif node.getRightNode().calcBalance() \u003c= 0 {\n\t\t// Right Right Case\n\t\treturn node.rotateLeft()\n\t}\n\n\t// Right Left Case\n\tright := node.getRightNode()\n\tnode.rightNode = right.rotateRight()\n\treturn node.rotateLeft()\n}\n\n// Shortcut for TraverseInRange.\nfunc (node *Node) Iterate(start, end string, cb func(*Node) bool) bool {\n\treturn node.TraverseInRange(start, end, true, true, cb)\n}\n\n// Shortcut for TraverseInRange.\nfunc (node *Node) ReverseIterate(start, end string, cb func(*Node) bool) bool {\n\treturn node.TraverseInRange(start, end, false, true, cb)\n}\n\n// TraverseInRange traverses all nodes, including inner nodes.\n// Start is inclusive and end is exclusive when ascending,\n// Start and end are inclusive when descending.\n// Empty start and empty end denote no start and no end.\n// If leavesOnly is true, only visit leaf nodes.\n// NOTE: To simulate an exclusive reverse traversal,\n// just append 0x00 to start.\nfunc (node *Node) TraverseInRange(start, end string, ascending bool, leavesOnly bool, cb func(*Node) bool) bool {\n\tif node == nil {\n\t\treturn false\n\t}\n\tafterStart := (start == \"\" || start \u003c node.key)\n\tstartOrAfter := (start == \"\" || start \u003c= node.key)\n\tbeforeEnd := false\n\tif ascending {\n\t\tbeforeEnd = (end == \"\" || node.key \u003c end)\n\t} else {\n\t\tbeforeEnd = (end == \"\" || node.key \u003c= end)\n\t}\n\n\t// Run callback per inner/leaf node.\n\tstop := false\n\tif (!node.IsLeaf() \u0026\u0026 !leavesOnly) ||\n\t\t(node.IsLeaf() \u0026\u0026 startOrAfter \u0026\u0026 beforeEnd) {\n\t\tstop = cb(node)\n\t\tif stop {\n\t\t\treturn stop\n\t\t}\n\t}\n\tif node.IsLeaf() {\n\t\treturn stop\n\t}\n\n\tif ascending {\n\t\t// check lower nodes, then higher\n\t\tif afterStart {\n\t\t\tstop = node.getLeftNode().TraverseInRange(start, end, ascending, leavesOnly, cb)\n\t\t}\n\t\tif stop {\n\t\t\treturn stop\n\t\t}\n\t\tif beforeEnd {\n\t\t\tstop = node.getRightNode().TraverseInRange(start, end, ascending, leavesOnly, cb)\n\t\t}\n\t} else {\n\t\t// check the higher nodes first\n\t\tif beforeEnd {\n\t\t\tstop = node.getRightNode().TraverseInRange(start, end, ascending, leavesOnly, cb)\n\t\t}\n\t\tif stop {\n\t\t\treturn stop\n\t\t}\n\t\tif afterStart {\n\t\t\tstop = node.getLeftNode().TraverseInRange(start, end, ascending, leavesOnly, cb)\n\t\t}\n\t}\n\n\treturn stop\n}\n\n// TraverseByOffset traverses all nodes, including inner nodes.\n// A limit of math.MaxInt means no limit.\nfunc (node *Node) TraverseByOffset(offset, limit int, descending bool, leavesOnly bool, cb func(*Node) bool) bool {\n\tif node == nil {\n\t\treturn false\n\t}\n\n\t// fast paths. these happen only if TraverseByOffset is called directly on a leaf.\n\tif limit \u003c= 0 || offset \u003e= node.size {\n\t\treturn false\n\t}\n\tif node.IsLeaf() {\n\t\tif offset \u003e 0 {\n\t\t\treturn false\n\t\t}\n\t\treturn cb(node)\n\t}\n\n\t// go to the actual recursive function.\n\treturn node.traverseByOffset(offset, limit, descending, leavesOnly, cb)\n}\n\n// TraverseByOffset traverses the subtree rooted at the node by offset and limit,\n// in either ascending or descending order, and applies the callback function to each traversed node.\n// If leavesOnly is true, only leaf nodes are visited.\nfunc (node *Node) traverseByOffset(offset, limit int, descending bool, leavesOnly bool, cb func(*Node) bool) bool {\n\t// caller guarantees: offset \u003c node.size; limit \u003e 0.\n\tif !leavesOnly {\n\t\tif cb(node) {\n\t\t\treturn true\n\t\t}\n\t}\n\tfirst, second := node.getLeftNode(), node.getRightNode()\n\tif descending {\n\t\tfirst, second = second, first\n\t}\n\tif first.IsLeaf() {\n\t\t// either run or skip, based on offset\n\t\tif offset \u003e 0 {\n\t\t\toffset--\n\t\t} else {\n\t\t\tcb(first)\n\t\t\tlimit--\n\t\t\tif limit \u003c= 0 {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// possible cases:\n\t\t// 1 the offset given skips the first node entirely\n\t\t// 2 the offset skips none or part of the first node, but the limit requires some of the second node.\n\t\t// 3 the offset skips none or part of the first node, and the limit stops our search on the first node.\n\t\tif offset \u003e= first.size {\n\t\t\toffset -= first.size // 1\n\t\t} else {\n\t\t\tif first.traverseByOffset(offset, limit, descending, leavesOnly, cb) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\t// number of leaves which could actually be called from inside\n\t\t\tdelta := first.size - offset\n\t\t\toffset = 0\n\t\t\tif delta \u003e= limit {\n\t\t\t\treturn true // 3\n\t\t\t}\n\t\t\tlimit -= delta // 2\n\t\t}\n\t}\n\n\t// because of the caller guarantees and the way we handle the first node,\n\t// at this point we know that limit \u003e 0 and there must be some values in\n\t// this second node that we include.\n\n\t// =\u003e if the second node is a leaf, it has to be included.\n\tif second.IsLeaf() {\n\t\treturn cb(second)\n\t}\n\t// =\u003e if it is not a leaf, it will still be enough to recursively call this\n\t// function with the updated offset and limit\n\treturn second.traverseByOffset(offset, limit, descending, leavesOnly, cb)\n}\n\n// Only used in testing...\nfunc (node *Node) lmd() *Node {\n\tif node.height == 0 {\n\t\treturn node\n\t}\n\treturn node.getLeftNode().lmd()\n}\n\n// Only used in testing...\nfunc (node *Node) rmd() *Node {\n\tif node.height == 0 {\n\t\treturn node\n\t}\n\treturn node.getRightNode().rmd()\n}\n\nfunc maxInt8(a, b int8) int8 {\n\tif a \u003e b {\n\t\treturn a\n\t}\n\treturn b\n}\n"},{"name":"node_test.gno","body":"package avl\n\nimport (\n\t\"sort\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestTraverseByOffset(t *testing.T) {\n\tconst testStrings = `Alfa\nAlfred\nAlpha\nAlphabet\nBeta\nBeth\nBook\nBrowser`\n\ttt := []struct {\n\t\tname string\n\t\tdesc bool\n\t}{\n\t\t{\"ascending\", false},\n\t\t{\"descending\", true},\n\t}\n\n\tfor _, tt := range tt {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tsl := strings.Split(testStrings, \"\\n\")\n\n\t\t\t// sort a first time in the order opposite to how we'll be traversing\n\t\t\t// the tree, to ensure that we are not just iterating through with\n\t\t\t// insertion order.\n\t\t\tsort.Strings(sl)\n\t\t\tif !tt.desc {\n\t\t\t\treverseSlice(sl)\n\t\t\t}\n\n\t\t\tr := NewNode(sl[0], nil)\n\t\t\tfor _, v := range sl[1:] {\n\t\t\t\tr, _ = r.Set(v, nil)\n\t\t\t}\n\n\t\t\t// then sort sl in the order we'll be traversing it, so that we can\n\t\t\t// compare the result with sl.\n\t\t\treverseSlice(sl)\n\n\t\t\tvar result []string\n\t\t\tfor i := 0; i \u003c len(sl); i++ {\n\t\t\t\tr.TraverseByOffset(i, 1, tt.desc, true, func(n *Node) bool {\n\t\t\t\t\tresult = append(result, n.Key())\n\t\t\t\t\treturn false\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tif !slicesEqual(sl, result) {\n\t\t\t\tt.Errorf(\"want %v got %v\", sl, result)\n\t\t\t}\n\n\t\t\tfor l := 2; l \u003c= len(sl); l++ {\n\t\t\t\t// \"slices\"\n\t\t\t\tfor i := 0; i \u003c= len(sl); i++ {\n\t\t\t\t\tmax := i + l\n\t\t\t\t\tif max \u003e len(sl) {\n\t\t\t\t\t\tmax = len(sl)\n\t\t\t\t\t}\n\t\t\t\t\texp := sl[i:max]\n\t\t\t\t\tactual := []string{}\n\n\t\t\t\t\tr.TraverseByOffset(i, l, tt.desc, true, func(tr *Node) bool {\n\t\t\t\t\t\tactual = append(actual, tr.Key())\n\t\t\t\t\t\treturn false\n\t\t\t\t\t})\n\t\t\t\t\tif !slicesEqual(exp, actual) {\n\t\t\t\t\t\tt.Errorf(\"want %v got %v\", exp, actual)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestHas(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput []string\n\t\thasKey string\n\t\texpected bool\n\t}{\n\t\t{\n\t\t\t\"has key in non-empty tree\",\n\t\t\t[]string{\"C\", \"A\", \"B\", \"E\", \"D\"},\n\t\t\t\"B\",\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"does not have key in non-empty tree\",\n\t\t\t[]string{\"C\", \"A\", \"B\", \"E\", \"D\"},\n\t\t\t\"F\",\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"has key in single-node tree\",\n\t\t\t[]string{\"A\"},\n\t\t\t\"A\",\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"does not have key in single-node tree\",\n\t\t\t[]string{\"A\"},\n\t\t\t\"B\",\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"does not have key in empty tree\",\n\t\t\t[]string{},\n\t\t\t\"A\",\n\t\t\tfalse,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar tree *Node\n\t\t\tfor _, key := range tt.input {\n\t\t\t\ttree, _ = tree.Set(key, nil)\n\t\t\t}\n\n\t\t\tresult := tree.Has(tt.hasKey)\n\n\t\t\tif result != tt.expected {\n\t\t\t\tt.Errorf(\"Expected %v, got %v\", tt.expected, result)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGet(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput []string\n\t\tgetKey string\n\t\texpectIdx int\n\t\texpectVal interface{}\n\t\texpectExists bool\n\t}{\n\t\t{\n\t\t\t\"get existing key\",\n\t\t\t[]string{\"C\", \"A\", \"B\", \"E\", \"D\"},\n\t\t\t\"B\",\n\t\t\t1,\n\t\t\tnil,\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"get non-existent key (smaller)\",\n\t\t\t[]string{\"C\", \"A\", \"B\", \"E\", \"D\"},\n\t\t\t\"@\",\n\t\t\t0,\n\t\t\tnil,\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"get non-existent key (larger)\",\n\t\t\t[]string{\"C\", \"A\", \"B\", \"E\", \"D\"},\n\t\t\t\"F\",\n\t\t\t5,\n\t\t\tnil,\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"get from empty tree\",\n\t\t\t[]string{},\n\t\t\t\"A\",\n\t\t\t0,\n\t\t\tnil,\n\t\t\tfalse,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar tree *Node\n\t\t\tfor _, key := range tt.input {\n\t\t\t\ttree, _ = tree.Set(key, nil)\n\t\t\t}\n\n\t\t\tidx, val, exists := tree.Get(tt.getKey)\n\n\t\t\tif idx != tt.expectIdx {\n\t\t\t\tt.Errorf(\"Expected index %d, got %d\", tt.expectIdx, idx)\n\t\t\t}\n\n\t\t\tif val != tt.expectVal {\n\t\t\t\tt.Errorf(\"Expected value %v, got %v\", tt.expectVal, val)\n\t\t\t}\n\n\t\t\tif exists != tt.expectExists {\n\t\t\t\tt.Errorf(\"Expected exists %t, got %t\", tt.expectExists, exists)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGetByIndex(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput []string\n\t\tidx int\n\t\texpectKey string\n\t\texpectVal interface{}\n\t\texpectPanic bool\n\t}{\n\t\t{\n\t\t\t\"get by valid index\",\n\t\t\t[]string{\"C\", \"A\", \"B\", \"E\", \"D\"},\n\t\t\t2,\n\t\t\t\"C\",\n\t\t\tnil,\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"get by valid index (smallest)\",\n\t\t\t[]string{\"C\", \"A\", \"B\", \"E\", \"D\"},\n\t\t\t0,\n\t\t\t\"A\",\n\t\t\tnil,\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"get by valid index (largest)\",\n\t\t\t[]string{\"C\", \"A\", \"B\", \"E\", \"D\"},\n\t\t\t4,\n\t\t\t\"E\",\n\t\t\tnil,\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"get by invalid index (negative)\",\n\t\t\t[]string{\"C\", \"A\", \"B\", \"E\", \"D\"},\n\t\t\t-1,\n\t\t\t\"\",\n\t\t\tnil,\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"get by invalid index (out of range)\",\n\t\t\t[]string{\"C\", \"A\", \"B\", \"E\", \"D\"},\n\t\t\t5,\n\t\t\t\"\",\n\t\t\tnil,\n\t\t\ttrue,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar tree *Node\n\t\t\tfor _, key := range tt.input {\n\t\t\t\ttree, _ = tree.Set(key, nil)\n\t\t\t}\n\n\t\t\tif tt.expectPanic {\n\t\t\t\tdefer func() {\n\t\t\t\t\tif r := recover(); r == nil {\n\t\t\t\t\t\tt.Errorf(\"Expected a panic but didn't get one\")\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t}\n\n\t\t\tkey, val := tree.GetByIndex(tt.idx)\n\n\t\t\tif !tt.expectPanic {\n\t\t\t\tif key != tt.expectKey {\n\t\t\t\t\tt.Errorf(\"Expected key %s, got %s\", tt.expectKey, key)\n\t\t\t\t}\n\n\t\t\t\tif val != tt.expectVal {\n\t\t\t\t\tt.Errorf(\"Expected value %v, got %v\", tt.expectVal, val)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestRemove(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput []string\n\t\tremoveKey string\n\t\texpected []string\n\t}{\n\t\t{\n\t\t\t\"remove leaf node\",\n\t\t\t[]string{\"C\", \"A\", \"B\", \"D\"},\n\t\t\t\"B\",\n\t\t\t[]string{\"A\", \"C\", \"D\"},\n\t\t},\n\t\t{\n\t\t\t\"remove node with one child\",\n\t\t\t[]string{\"C\", \"A\", \"B\", \"D\"},\n\t\t\t\"A\",\n\t\t\t[]string{\"B\", \"C\", \"D\"},\n\t\t},\n\t\t{\n\t\t\t\"remove node with two children\",\n\t\t\t[]string{\"C\", \"A\", \"B\", \"E\", \"D\"},\n\t\t\t\"C\",\n\t\t\t[]string{\"A\", \"B\", \"D\", \"E\"},\n\t\t},\n\t\t{\n\t\t\t\"remove root node\",\n\t\t\t[]string{\"C\", \"A\", \"B\", \"E\", \"D\"},\n\t\t\t\"C\",\n\t\t\t[]string{\"A\", \"B\", \"D\", \"E\"},\n\t\t},\n\t\t{\n\t\t\t\"remove non-existent key\",\n\t\t\t[]string{\"C\", \"A\", \"B\", \"E\", \"D\"},\n\t\t\t\"F\",\n\t\t\t[]string{\"A\", \"B\", \"C\", \"D\", \"E\"},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar tree *Node\n\t\t\tfor _, key := range tt.input {\n\t\t\t\ttree, _ = tree.Set(key, nil)\n\t\t\t}\n\n\t\t\ttree, _, _, _ = tree.Remove(tt.removeKey)\n\n\t\t\tresult := make([]string, 0)\n\t\t\ttree.Iterate(\"\", \"\", func(n *Node) bool {\n\t\t\t\tresult = append(result, n.Key())\n\t\t\t\treturn false\n\t\t\t})\n\n\t\t\tif !slicesEqual(tt.expected, result) {\n\t\t\t\tt.Errorf(\"want %v got %v\", tt.expected, result)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTraverse(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput []string\n\t\texpected []string\n\t}{\n\t\t{\n\t\t\t\"empty tree\",\n\t\t\t[]string{},\n\t\t\t[]string{},\n\t\t},\n\t\t{\n\t\t\t\"single node tree\",\n\t\t\t[]string{\"A\"},\n\t\t\t[]string{\"A\"},\n\t\t},\n\t\t{\n\t\t\t\"small tree\",\n\t\t\t[]string{\"C\", \"A\", \"B\", \"E\", \"D\"},\n\t\t\t[]string{\"A\", \"B\", \"C\", \"D\", \"E\"},\n\t\t},\n\t\t{\n\t\t\t\"large tree\",\n\t\t\t[]string{\"H\", \"D\", \"L\", \"B\", \"F\", \"J\", \"N\", \"A\", \"C\", \"E\", \"G\", \"I\", \"K\", \"M\", \"O\"},\n\t\t\t[]string{\"A\", \"B\", \"C\", \"D\", \"E\", \"F\", \"G\", \"H\", \"I\", \"J\", \"K\", \"L\", \"M\", \"N\", \"O\"},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar tree *Node\n\t\t\tfor _, key := range tt.input {\n\t\t\t\ttree, _ = tree.Set(key, nil)\n\t\t\t}\n\n\t\t\tt.Run(\"iterate\", func(t *testing.T) {\n\t\t\t\tvar result []string\n\t\t\t\ttree.Iterate(\"\", \"\", func(n *Node) bool {\n\t\t\t\t\tresult = append(result, n.Key())\n\t\t\t\t\treturn false\n\t\t\t\t})\n\t\t\t\tif !slicesEqual(tt.expected, result) {\n\t\t\t\t\tt.Errorf(\"want %v got %v\", tt.expected, result)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tt.Run(\"ReverseIterate\", func(t *testing.T) {\n\t\t\t\tvar result []string\n\t\t\t\ttree.ReverseIterate(\"\", \"\", func(n *Node) bool {\n\t\t\t\t\tresult = append(result, n.Key())\n\t\t\t\t\treturn false\n\t\t\t\t})\n\t\t\t\texpected := make([]string, len(tt.expected))\n\t\t\t\tcopy(expected, tt.expected)\n\t\t\t\tfor i, j := 0, len(expected)-1; i \u003c j; i, j = i+1, j-1 {\n\t\t\t\t\texpected[i], expected[j] = expected[j], expected[i]\n\t\t\t\t}\n\t\t\t\tif !slicesEqual(expected, result) {\n\t\t\t\t\tt.Errorf(\"want %v got %v\", expected, result)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tt.Run(\"TraverseInRange\", func(t *testing.T) {\n\t\t\t\tvar result []string\n\t\t\t\tstart, end := \"C\", \"M\"\n\t\t\t\ttree.TraverseInRange(start, end, true, true, func(n *Node) bool {\n\t\t\t\t\tresult = append(result, n.Key())\n\t\t\t\t\treturn false\n\t\t\t\t})\n\t\t\t\texpected := make([]string, 0)\n\t\t\t\tfor _, key := range tt.expected {\n\t\t\t\t\tif key \u003e= start \u0026\u0026 key \u003c end {\n\t\t\t\t\t\texpected = append(expected, key)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif !slicesEqual(expected, result) {\n\t\t\t\t\tt.Errorf(\"want %v got %v\", expected, result)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc TestRotateWhenHeightDiffers(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput []string\n\t\texpected []string\n\t}{\n\t\t{\n\t\t\t\"right rotation when left subtree is higher\",\n\t\t\t[]string{\"E\", \"C\", \"A\", \"B\", \"D\"},\n\t\t\t[]string{\"A\", \"B\", \"C\", \"E\", \"D\"},\n\t\t},\n\t\t{\n\t\t\t\"left rotation when right subtree is higher\",\n\t\t\t[]string{\"A\", \"C\", \"E\", \"D\", \"F\"},\n\t\t\t[]string{\"A\", \"C\", \"D\", \"E\", \"F\"},\n\t\t},\n\t\t{\n\t\t\t\"left-right rotation\",\n\t\t\t[]string{\"E\", \"A\", \"C\", \"B\", \"D\"},\n\t\t\t[]string{\"A\", \"B\", \"C\", \"E\", \"D\"},\n\t\t},\n\t\t{\n\t\t\t\"right-left rotation\",\n\t\t\t[]string{\"A\", \"E\", \"C\", \"B\", \"D\"},\n\t\t\t[]string{\"A\", \"B\", \"C\", \"E\", \"D\"},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar tree *Node\n\t\t\tfor _, key := range tt.input {\n\t\t\t\ttree, _ = tree.Set(key, nil)\n\t\t\t}\n\n\t\t\t// perform rotation or balance\n\t\t\ttree = tree.balance()\n\n\t\t\t// check tree structure\n\t\t\tvar result []string\n\t\t\ttree.Iterate(\"\", \"\", func(n *Node) bool {\n\t\t\t\tresult = append(result, n.Key())\n\t\t\t\treturn false\n\t\t\t})\n\n\t\t\tif !slicesEqual(tt.expected, result) {\n\t\t\t\tt.Errorf(\"want %v got %v\", tt.expected, result)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestRotateAndBalance(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput []string\n\t\texpected []string\n\t}{\n\t\t{\n\t\t\t\"right rotation\",\n\t\t\t[]string{\"A\", \"B\", \"C\", \"D\", \"E\"},\n\t\t\t[]string{\"A\", \"B\", \"C\", \"D\", \"E\"},\n\t\t},\n\t\t{\n\t\t\t\"left rotation\",\n\t\t\t[]string{\"E\", \"D\", \"C\", \"B\", \"A\"},\n\t\t\t[]string{\"A\", \"B\", \"C\", \"D\", \"E\"},\n\t\t},\n\t\t{\n\t\t\t\"left-right rotation\",\n\t\t\t[]string{\"C\", \"A\", \"E\", \"B\", \"D\"},\n\t\t\t[]string{\"A\", \"B\", \"C\", \"D\", \"E\"},\n\t\t},\n\t\t{\n\t\t\t\"right-left rotation\",\n\t\t\t[]string{\"C\", \"E\", \"A\", \"D\", \"B\"},\n\t\t\t[]string{\"A\", \"B\", \"C\", \"D\", \"E\"},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar tree *Node\n\t\t\tfor _, key := range tt.input {\n\t\t\t\ttree, _ = tree.Set(key, nil)\n\t\t\t}\n\n\t\t\ttree = tree.balance()\n\n\t\t\tvar result []string\n\t\t\ttree.Iterate(\"\", \"\", func(n *Node) bool {\n\t\t\t\tresult = append(result, n.Key())\n\t\t\t\treturn false\n\t\t\t})\n\n\t\t\tif !slicesEqual(tt.expected, result) {\n\t\t\t\tt.Errorf(\"want %v got %v\", tt.expected, result)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc slicesEqual(w1, w2 []string) bool {\n\tif len(w1) != len(w2) {\n\t\treturn false\n\t}\n\tfor i := 0; i \u003c len(w1); i++ {\n\t\tif w1[0] != w2[0] {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc maxint8(a, b int8) int8 {\n\tif a \u003e b {\n\t\treturn a\n\t}\n\treturn b\n}\n\nfunc reverseSlice(ss []string) {\n\tfor i := 0; i \u003c len(ss)/2; i++ {\n\t\tj := len(ss) - 1 - i\n\t\tss[i], ss[j] = ss[j], ss[i]\n\t}\n}\n"},{"name":"tree.gno","body":"package avl\n\ntype IterCbFn func(key string, value interface{}) bool\n\n//----------------------------------------\n// Tree\n\n// The zero struct can be used as an empty tree.\ntype Tree struct {\n\tnode *Node\n}\n\n// NewTree creates a new empty AVL tree.\nfunc NewTree() *Tree {\n\treturn \u0026Tree{\n\t\tnode: nil,\n\t}\n}\n\n// Size returns the number of key-value pair in the tree.\nfunc (tree *Tree) Size() int {\n\treturn tree.node.Size()\n}\n\n// Has checks whether a key exists in the tree.\n// It returns true if the key exists, otherwise false.\nfunc (tree *Tree) Has(key string) (has bool) {\n\treturn tree.node.Has(key)\n}\n\n// Get retrieves the value associated with the given key.\n// It returns the value and a boolean indicating whether the key exists.\nfunc (tree *Tree) Get(key string) (value interface{}, exists bool) {\n\t_, value, exists = tree.node.Get(key)\n\treturn\n}\n\n// GetByIndex retrieves the key-value pair at the specified index in the tree.\n// It returns the key and value at the given index.\nfunc (tree *Tree) GetByIndex(index int) (key string, value interface{}) {\n\treturn tree.node.GetByIndex(index)\n}\n\n// Set inserts a key-value pair into the tree.\n// If the key already exists, the value will be updated.\n// It returns a boolean indicating whether the key was newly inserted or updated.\nfunc (tree *Tree) Set(key string, value interface{}) (updated bool) {\n\tnewnode, updated := tree.node.Set(key, value)\n\ttree.node = newnode\n\treturn updated\n}\n\n// Remove removes a key-value pair from the tree.\n// It returns the removed value and a boolean indicating whether the key was found and removed.\nfunc (tree *Tree) Remove(key string) (value interface{}, removed bool) {\n\tnewnode, _, value, removed := tree.node.Remove(key)\n\ttree.node = newnode\n\treturn value, removed\n}\n\n// Iterate performs an in-order traversal of the tree within the specified key range.\n// It calls the provided callback function for each key-value pair encountered.\n// If the callback returns true, the iteration is stopped.\nfunc (tree *Tree) Iterate(start, end string, cb IterCbFn) bool {\n\treturn tree.node.TraverseInRange(start, end, true, true,\n\t\tfunc(node *Node) bool {\n\t\t\treturn cb(node.Key(), node.Value())\n\t\t},\n\t)\n}\n\n// ReverseIterate performs a reverse in-order traversal of the tree within the specified key range.\n// It calls the provided callback function for each key-value pair encountered.\n// If the callback returns true, the iteration is stopped.\nfunc (tree *Tree) ReverseIterate(start, end string, cb IterCbFn) bool {\n\treturn tree.node.TraverseInRange(start, end, false, true,\n\t\tfunc(node *Node) bool {\n\t\t\treturn cb(node.Key(), node.Value())\n\t\t},\n\t)\n}\n\n// IterateByOffset performs an in-order traversal of the tree starting from the specified offset.\n// It calls the provided callback function for each key-value pair encountered, up to the specified count.\n// If the callback returns true, the iteration is stopped.\nfunc (tree *Tree) IterateByOffset(offset int, count int, cb IterCbFn) bool {\n\treturn tree.node.TraverseByOffset(offset, count, true, true,\n\t\tfunc(node *Node) bool {\n\t\t\treturn cb(node.Key(), node.Value())\n\t\t},\n\t)\n}\n\n// ReverseIterateByOffset performs a reverse in-order traversal of the tree starting from the specified offset.\n// It calls the provided callback function for each key-value pair encountered, up to the specified count.\n// If the callback returns true, the iteration is stopped.\nfunc (tree *Tree) ReverseIterateByOffset(offset int, count int, cb IterCbFn) bool {\n\treturn tree.node.TraverseByOffset(offset, count, false, true,\n\t\tfunc(node *Node) bool {\n\t\t\treturn cb(node.Key(), node.Value())\n\t\t},\n\t)\n}\n"},{"name":"tree_test.gno","body":"package avl\n\nimport \"testing\"\n\nfunc TestNewTree(t *testing.T) {\n\ttree := NewTree()\n\tif tree.node != nil {\n\t\tt.Error(\"Expected tree.node to be nil\")\n\t}\n}\n\nfunc TestTreeSize(t *testing.T) {\n\ttree := NewTree()\n\tif tree.Size() != 0 {\n\t\tt.Error(\"Expected empty tree size to be 0\")\n\t}\n\n\ttree.Set(\"key1\", \"value1\")\n\ttree.Set(\"key2\", \"value2\")\n\tif tree.Size() != 2 {\n\t\tt.Error(\"Expected tree size to be 2\")\n\t}\n}\n\nfunc TestTreeHas(t *testing.T) {\n\ttree := NewTree()\n\ttree.Set(\"key1\", \"value1\")\n\n\tif !tree.Has(\"key1\") {\n\t\tt.Error(\"Expected tree to have key1\")\n\t}\n\n\tif tree.Has(\"key2\") {\n\t\tt.Error(\"Expected tree to not have key2\")\n\t}\n}\n\nfunc TestTreeGet(t *testing.T) {\n\ttree := NewTree()\n\ttree.Set(\"key1\", \"value1\")\n\n\tvalue, exists := tree.Get(\"key1\")\n\tif !exists || value != \"value1\" {\n\t\tt.Error(\"Expected Get to return value1 and true\")\n\t}\n\n\t_, exists = tree.Get(\"key2\")\n\tif exists {\n\t\tt.Error(\"Expected Get to return false for non-existent key\")\n\t}\n}\n\nfunc TestTreeGetByIndex(t *testing.T) {\n\ttree := NewTree()\n\ttree.Set(\"key1\", \"value1\")\n\ttree.Set(\"key2\", \"value2\")\n\n\tkey, value := tree.GetByIndex(0)\n\tif key != \"key1\" || value != \"value1\" {\n\t\tt.Error(\"Expected GetByIndex(0) to return key1 and value1\")\n\t}\n\n\tkey, value = tree.GetByIndex(1)\n\tif key != \"key2\" || value != \"value2\" {\n\t\tt.Error(\"Expected GetByIndex(1) to return key2 and value2\")\n\t}\n\n\tdefer func() {\n\t\tif r := recover(); r == nil {\n\t\t\tt.Error(\"Expected GetByIndex to panic for out-of-range index\")\n\t\t}\n\t}()\n\ttree.GetByIndex(2)\n}\n\nfunc TestTreeRemove(t *testing.T) {\n\ttree := NewTree()\n\ttree.Set(\"key1\", \"value1\")\n\n\tvalue, removed := tree.Remove(\"key1\")\n\tif !removed || value != \"value1\" || tree.Size() != 0 {\n\t\tt.Error(\"Expected Remove to remove key-value pair\")\n\t}\n\n\t_, removed = tree.Remove(\"key2\")\n\tif removed {\n\t\tt.Error(\"Expected Remove to return false for non-existent key\")\n\t}\n}\n\nfunc TestTreeIterate(t *testing.T) {\n\ttree := NewTree()\n\ttree.Set(\"key1\", \"value1\")\n\ttree.Set(\"key2\", \"value2\")\n\ttree.Set(\"key3\", \"value3\")\n\n\tvar keys []string\n\ttree.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tkeys = append(keys, key)\n\t\treturn false\n\t})\n\n\texpectedKeys := []string{\"key1\", \"key2\", \"key3\"}\n\tif !slicesEqual(keys, expectedKeys) {\n\t\tt.Errorf(\"Expected keys %v, got %v\", expectedKeys, keys)\n\t}\n}\n\nfunc TestTreeReverseIterate(t *testing.T) {\n\ttree := NewTree()\n\ttree.Set(\"key1\", \"value1\")\n\ttree.Set(\"key2\", \"value2\")\n\ttree.Set(\"key3\", \"value3\")\n\n\tvar keys []string\n\ttree.ReverseIterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tkeys = append(keys, key)\n\t\treturn false\n\t})\n\n\texpectedKeys := []string{\"key3\", \"key2\", \"key1\"}\n\tif !slicesEqual(keys, expectedKeys) {\n\t\tt.Errorf(\"Expected keys %v, got %v\", expectedKeys, keys)\n\t}\n}\n\nfunc TestTreeIterateByOffset(t *testing.T) {\n\ttree := NewTree()\n\ttree.Set(\"key1\", \"value1\")\n\ttree.Set(\"key2\", \"value2\")\n\ttree.Set(\"key3\", \"value3\")\n\n\tvar keys []string\n\ttree.IterateByOffset(1, 2, func(key string, value interface{}) bool {\n\t\tkeys = append(keys, key)\n\t\treturn false\n\t})\n\n\texpectedKeys := []string{\"key2\", \"key3\"}\n\tif !slicesEqual(keys, expectedKeys) {\n\t\tt.Errorf(\"Expected keys %v, got %v\", expectedKeys, keys)\n\t}\n}\n\nfunc TestTreeReverseIterateByOffset(t *testing.T) {\n\ttree := NewTree()\n\ttree.Set(\"key1\", \"value1\")\n\ttree.Set(\"key2\", \"value2\")\n\ttree.Set(\"key3\", \"value3\")\n\n\tvar keys []string\n\ttree.ReverseIterateByOffset(1, 2, func(key string, value interface{}) bool {\n\t\tkeys = append(keys, key)\n\t\treturn false\n\t})\n\n\texpectedKeys := []string{\"key2\", \"key1\"}\n\tif !slicesEqual(keys, expectedKeys) {\n\t\tt.Errorf(\"Expected keys %v, got %v\", expectedKeys, keys)\n\t}\n}\n"},{"name":"z_0_filetest.gno","body":"// PKGPATH: gno.land/r/test\npackage test\n\nimport (\n\t\"gno.land/p/demo/avl\"\n)\n\nvar node *avl.Node\n\nfunc init() {\n\tnode = avl.NewNode(\"key0\", \"value0\")\n\t// node, _ = node.Set(\"key0\", \"value0\")\n}\n\nfunc main() {\n\tvar updated bool\n\tnode, updated = node.Set(\"key1\", \"value1\")\n\t// println(node, updated)\n\tprintln(updated, node.Size())\n}\n\n// Output:\n// false 2\n\n// Realm:\n// switchrealm[\"gno.land/r/test\"]\n// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={\n// \"ObjectInfo\": {\n// \"ID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:4\",\n// \"ModTime\": \"7\",\n// \"OwnerID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:7\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"627e8e517e7ae5db0f3b753e2a32b607989198b6\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:5\"\n// }\n// }\n// }\n// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:9]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"key1\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"value1\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:9\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:8\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:8]={\n// \"ObjectInfo\": {\n// \"ID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:8\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:7\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"b28057ab7be6383785c0a5503e8a531bdbc21851\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:9\"\n// }\n// }\n// }\n// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:7]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"key1\"\n// }\n// },\n// {},\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"6da365f0d6cacbcdf53cd5a4b125803cddce08c2\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:4\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"f216afe7b5a17f4ebdbb98dceccedbc22e237596\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:8\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:7\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:6\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:6]={\n// \"ObjectInfo\": {\n// \"ID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:6\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:2\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"ff1a50d8489090af37a2c7766d659f0d717939b5\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:7\"\n// }\n// }\n// }\n// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={\n// \"Blank\": {},\n// \"ObjectInfo\": {\n// \"ID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:2\",\n// \"IsEscaped\": true,\n// \"ModTime\": \"5\",\n// \"RefCount\": \"2\"\n// },\n// \"Parent\": null,\n// \"Source\": {\n// \"@type\": \"/gno.RefNode\",\n// \"BlockNode\": null,\n// \"Location\": {\n// \"Column\": \"0\",\n// \"File\": \"\",\n// \"Line\": \"0\",\n// \"PkgPath\": \"gno.land/r/test\"\n// }\n// },\n// \"Values\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"ae86874f9b47fa5e64c30b3e92e9d07f2ec967a4\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:6\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.FuncType\",\n// \"Params\": [],\n// \"Results\": []\n// },\n// \"V\": {\n// \"@type\": \"/gno.FuncValue\",\n// \"Closure\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:3\"\n// },\n// \"FileName\": \"main.gno\",\n// \"IsMethod\": false,\n// \"Name\": \"init.1\",\n// \"NativeName\": \"\",\n// \"NativePkg\": \"\",\n// \"PkgPath\": \"gno.land/r/test\",\n// \"Source\": {\n// \"@type\": \"/gno.RefNode\",\n// \"BlockNode\": null,\n// \"Location\": {\n// \"Column\": \"1\",\n// \"File\": \"main.gno\",\n// \"Line\": \"10\",\n// \"PkgPath\": \"gno.land/r/test\"\n// }\n// },\n// \"Type\": {\n// \"@type\": \"/gno.FuncType\",\n// \"Params\": [],\n// \"Results\": []\n// }\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.FuncType\",\n// \"Params\": [],\n// \"Results\": []\n// },\n// \"V\": {\n// \"@type\": \"/gno.FuncValue\",\n// \"Closure\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:3\"\n// },\n// \"FileName\": \"main.gno\",\n// \"IsMethod\": false,\n// \"Name\": \"main\",\n// \"NativeName\": \"\",\n// \"NativePkg\": \"\",\n// \"PkgPath\": \"gno.land/r/test\",\n// \"Source\": {\n// \"@type\": \"/gno.RefNode\",\n// \"BlockNode\": null,\n// \"Location\": {\n// \"Column\": \"1\",\n// \"File\": \"main.gno\",\n// \"Line\": \"15\",\n// \"PkgPath\": \"gno.land/r/test\"\n// }\n// },\n// \"Type\": {\n// \"@type\": \"/gno.FuncType\",\n// \"Params\": [],\n// \"Results\": []\n// }\n// }\n// }\n// ]\n// }\n"},{"name":"z_1_filetest.gno","body":"// PKGPATH: gno.land/r/test\npackage test\n\nimport (\n\t\"gno.land/p/demo/avl\"\n)\n\nvar node *avl.Node\n\nfunc init() {\n\tnode = avl.NewNode(\"key0\", \"value0\")\n\tnode, _ = node.Set(\"key1\", \"value1\")\n}\n\nfunc main() {\n\tvar updated bool\n\tnode, updated = node.Set(\"key2\", \"value2\")\n\t// println(node, updated)\n\tprintln(updated, node.Size())\n}\n\n// Output:\n// false 3\n\n// Realm:\n// switchrealm[\"gno.land/r/test\"]\n// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:15]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"key2\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"value2\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:15\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:14\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:14]={\n// \"ObjectInfo\": {\n// \"ID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:14\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:13\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"143aebc820da33550f7338723fb1e2eec575b196\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:15\"\n// }\n// }\n// }\n// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:13]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"key2\"\n// }\n// },\n// {},\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"2f3adc5d0f2a3fe0331cfa93572a7abdde14c9aa\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:8\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"2e733a8e9e74fe14f0a5d10fb0f6728fa53d052d\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:14\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:13\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:12\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:12]={\n// \"ObjectInfo\": {\n// \"ID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:12\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:11\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"fe20a19f956511f274dc77854e9e5468387260f4\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:13\"\n// }\n// }\n// }\n// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:11]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"key1\"\n// }\n// },\n// {},\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AwAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"c89a71bdf045e8bde2059dc9d33839f916e02e5d\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:6\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"90fa67f8c47db4b9b2a60425dff08d5a3385100f\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:12\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:11\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:10\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:10]={\n// \"ObjectInfo\": {\n// \"ID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:10\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:2\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"83e42caaf53070dd95b5f859053eb51ed900bbda\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:11\"\n// }\n// }\n// }\n// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={\n// \"Blank\": {},\n// \"ObjectInfo\": {\n// \"ID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:2\",\n// \"IsEscaped\": true,\n// \"ModTime\": \"9\",\n// \"RefCount\": \"2\"\n// },\n// \"Parent\": null,\n// \"Source\": {\n// \"@type\": \"/gno.RefNode\",\n// \"BlockNode\": null,\n// \"Location\": {\n// \"Column\": \"0\",\n// \"File\": \"\",\n// \"Line\": \"0\",\n// \"PkgPath\": \"gno.land/r/test\"\n// }\n// },\n// \"Values\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"1faa9fa4ba1935121a6d3f0a623772e9d4499b0a\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:10\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.FuncType\",\n// \"Params\": [],\n// \"Results\": []\n// },\n// \"V\": {\n// \"@type\": \"/gno.FuncValue\",\n// \"Closure\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:3\"\n// },\n// \"FileName\": \"main.gno\",\n// \"IsMethod\": false,\n// \"Name\": \"init.1\",\n// \"NativeName\": \"\",\n// \"NativePkg\": \"\",\n// \"PkgPath\": \"gno.land/r/test\",\n// \"Source\": {\n// \"@type\": \"/gno.RefNode\",\n// \"BlockNode\": null,\n// \"Location\": {\n// \"Column\": \"1\",\n// \"File\": \"main.gno\",\n// \"Line\": \"10\",\n// \"PkgPath\": \"gno.land/r/test\"\n// }\n// },\n// \"Type\": {\n// \"@type\": \"/gno.FuncType\",\n// \"Params\": [],\n// \"Results\": []\n// }\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.FuncType\",\n// \"Params\": [],\n// \"Results\": []\n// },\n// \"V\": {\n// \"@type\": \"/gno.FuncValue\",\n// \"Closure\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:3\"\n// },\n// \"FileName\": \"main.gno\",\n// \"IsMethod\": false,\n// \"Name\": \"main\",\n// \"NativeName\": \"\",\n// \"NativePkg\": \"\",\n// \"PkgPath\": \"gno.land/r/test\",\n// \"Source\": {\n// \"@type\": \"/gno.RefNode\",\n// \"BlockNode\": null,\n// \"Location\": {\n// \"Column\": \"1\",\n// \"File\": \"main.gno\",\n// \"Line\": \"15\",\n// \"PkgPath\": \"gno.land/r/test\"\n// }\n// },\n// \"Type\": {\n// \"@type\": \"/gno.FuncType\",\n// \"Params\": [],\n// \"Results\": []\n// }\n// }\n// }\n// ]\n// }\n// d[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]\n// d[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]\n"},{"name":"z_2_filetest.gno","body":"// PKGPATH: gno.land/r/test\npackage test\n\nimport (\n\t\"gno.land/p/demo/avl\"\n)\n\nvar tree avl.Tree\n\nfunc init() {\n\ttree.Set(\"key0\", \"value0\")\n\ttree.Set(\"key1\", \"value1\")\n}\n\nfunc main() {\n\tvar updated bool\n\tupdated = tree.Set(\"key2\", \"value2\")\n\tprintln(updated, tree.Size())\n}\n\n// Output:\n// false 3\n\n// Realm:\n// switchrealm[\"gno.land/r/test\"]\n// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:16]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"key2\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"value2\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:16\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:15\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:15]={\n// \"ObjectInfo\": {\n// \"ID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:15\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:14\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"db333c89cd6773709e031f1f4e4ed4d3fed66c11\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:16\"\n// }\n// }\n// }\n// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:14]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"key2\"\n// }\n// },\n// {},\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"849a50d6c78d65742752e3c89ad8dd556e2e63cb\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:9\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"b4fc2fdd2d0fe936c87ed2ace97136cffeed207f\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:15\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:14\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:13\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:13]={\n// \"ObjectInfo\": {\n// \"ID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:13\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:12\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"a1160b0060ad752dbfe5fe436f7734bb19136150\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:14\"\n// }\n// }\n// }\n// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:12]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"key1\"\n// }\n// },\n// {},\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AwAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"fd95e08763159ac529e26986d652e752e78b6325\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:7\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"3ecdcf148fe2f9e97b72a3bedf303b2ba56d4f4b\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:13\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:12\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:11\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:11]={\n// \"ObjectInfo\": {\n// \"ID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:11\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:3\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"63126557dba88f8556f7a0ccbbfc1d218ae7a302\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:12\"\n// }\n// }\n// }\n// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:3]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"d31c7e797793e03ffe0bbcb72f963264f8300d22\",\n// \"ObjectID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:11\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:3\",\n// \"ModTime\": \"10\",\n// \"OwnerID\": \"a8ada09dee16d791fd406d629fe29bb0ed084a30:2\",\n// \"RefCount\": \"1\"\n// }\n// }\n// d[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]\n// d[a8ada09dee16d791fd406d629fe29bb0ed084a30:6]\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":"testutils","path":"gno.land/p/demo/testutils","files":[{"name":"access.gno","body":"package testutils\n\n// for testing access. see tests/files/access*.go\n\n// NOTE: non-package variables cannot be overridden, except during init().\nvar (\n\tTestVar1 int\n\ttestVar2 int\n)\n\nfunc init() {\n\tTestVar1 = 123\n\ttestVar2 = 456\n}\n\ntype TestAccessStruct struct {\n\tPublicField string\n\tprivateField string\n}\n\nfunc (tas TestAccessStruct) PublicMethod() string {\n\treturn tas.PublicField + \"/\" + tas.privateField\n}\n\nfunc (tas TestAccessStruct) privateMethod() string {\n\treturn tas.PublicField + \"/\" + tas.privateField\n}\n\nfunc NewTestAccessStruct(pub, priv string) TestAccessStruct {\n\treturn TestAccessStruct{\n\t\tPublicField: pub,\n\t\tprivateField: priv,\n\t}\n}\n\n// see access6.g0 etc.\ntype PrivateInterface interface {\n\tprivateMethod() string\n}\n\nfunc PrintPrivateInterface(pi PrivateInterface) {\n\tprintln(\"testutils.PrintPrivateInterface\", pi.privateMethod())\n}\n"},{"name":"crypto.gno","body":"package testutils\n\nimport \"std\"\n\nfunc TestAddress(name string) std.Address {\n\tif len(name) \u003e std.RawAddressSize {\n\t\tpanic(\"address name cannot be greater than std.AddressSize bytes\")\n\t}\n\taddr := std.RawAddress{}\n\t// TODO: use strings.RepeatString or similar.\n\t// NOTE: I miss python's \"\".Join().\n\tblanks := \"____________________\"\n\tcopy(addr[:], []byte(blanks))\n\tcopy(addr[:], []byte(name))\n\treturn std.Address(std.EncodeBech32(\"g\", addr))\n}\n"},{"name":"misc.gno","body":"package testutils\n\n// For testing std.GetCallerAt().\nfunc WrapCall(fn func()) {\n\tfn()\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":"testutils","path":"gno.land/p/demo/testutils","files":[{"name":"access.gno","body":"package testutils\n\n// for testing access. see tests/files/access*.go\n\n// NOTE: non-package variables cannot be overridden, except during init().\nvar (\n\tTestVar1 int\n\ttestVar2 int\n)\n\nfunc init() {\n\tTestVar1 = 123\n\ttestVar2 = 456\n}\n\ntype TestAccessStruct struct {\n\tPublicField string\n\tprivateField string\n}\n\nfunc (tas TestAccessStruct) PublicMethod() string {\n\treturn tas.PublicField + \"/\" + tas.privateField\n}\n\nfunc (tas TestAccessStruct) privateMethod() string {\n\treturn tas.PublicField + \"/\" + tas.privateField\n}\n\nfunc NewTestAccessStruct(pub, priv string) TestAccessStruct {\n\treturn TestAccessStruct{\n\t\tPublicField: pub,\n\t\tprivateField: priv,\n\t}\n}\n\n// see access6.g0 etc.\ntype PrivateInterface interface {\n\tprivateMethod() string\n}\n\nfunc PrintPrivateInterface(pi PrivateInterface) {\n\tprintln(\"testutils.PrintPrivateInterface\", pi.privateMethod())\n}\n"},{"name":"crypto.gno","body":"package testutils\n\nimport \"std\"\n\nfunc TestAddress(name string) std.Address {\n\tif len(name) \u003e std.RawAddressSize {\n\t\tpanic(\"address name cannot be greater than std.AddressSize bytes\")\n\t}\n\taddr := std.RawAddress{}\n\t// TODO: use strings.RepeatString or similar.\n\t// NOTE: I miss python's \"\".Join().\n\tblanks := \"____________________\"\n\tcopy(addr[:], []byte(blanks))\n\tcopy(addr[:], []byte(name))\n\treturn std.Address(std.EncodeBech32(\"g\", addr))\n}\n"},{"name":"misc.gno","body":"package testutils\n\n// For testing std.CallerAt().\nfunc WrapCall(fn func()) {\n\tfn()\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":"diff","path":"gno.land/p/demo/diff","files":[{"name":"diff.gno","body":"// The diff package implements the Myers diff algorithm to compute the edit distance\n// and generate a minimal edit script between two strings.\n//\n// Edit distance, also known as Levenshtein distance, is a measure of the similarity\n// between two strings. It is defined as the minimum number of single-character edits (insertions,\n// deletions, or substitutions) required to change one string into the other.\npackage diff\n\nimport (\n\t\"strings\"\n)\n\n// EditType represents the type of edit operation in a diff.\ntype EditType uint8\n\nconst (\n\t// EditKeep indicates that a character is unchanged in both strings.\n\tEditKeep EditType = iota\n\n\t// EditInsert indicates that a character was inserted in the new string.\n\tEditInsert\n\n\t// EditDelete indicates that a character was deleted from the old string.\n\tEditDelete\n)\n\n// Edit represent a single edit operation in a diff.\ntype Edit struct {\n\t// Type is the kind of edit operation.\n\tType EditType\n\n\t// Char is the character involved in the edit operation.\n\tChar rune\n}\n\n// MyersDiff computes the difference between two strings using Myers' diff algorithm.\n// It returns a slice of Edit operations that transform the old string into the new string.\n// This implementation finds the shortest edit script (SES) that represents the minimal\n// set of operations to transform one string into the other.\n//\n// The function handles both ASCII and non-ASCII characters correctly.\n//\n// Time complexity: O((N+M)D), where N and M are the lengths of the input strings,\n// and D is the size of the minimum edit script.\n//\n// Space complexity: O((N+M)D)\n//\n// In the worst case, where the strings are completely different, D can be as large as N+M,\n// leading to a time and space complexity of O((N+M)^2). However, for strings with many\n// common substrings, the performance is much better, often closer to O(N+M).\n//\n// Parameters:\n// - old: the original string.\n// - new: the modified string.\n//\n// Returns:\n// - A slice of Edit operations representing the minimum difference between the two strings.\nfunc MyersDiff(old, new string) []Edit {\n\toldRunes, newRunes := []rune(old), []rune(new)\n\tn, m := len(oldRunes), len(newRunes)\n\n\tif n == 0 \u0026\u0026 m == 0 {\n\t\treturn []Edit{}\n\t}\n\n\t// old is empty\n\tif n == 0 {\n\t\tedits := make([]Edit, m)\n\t\tfor i, r := range newRunes {\n\t\t\tedits[i] = Edit{Type: EditInsert, Char: r}\n\t\t}\n\t\treturn edits\n\t}\n\n\tif m == 0 {\n\t\tedits := make([]Edit, n)\n\t\tfor i, r := range oldRunes {\n\t\t\tedits[i] = Edit{Type: EditDelete, Char: r}\n\t\t}\n\t\treturn edits\n\t}\n\n\tmax := n + m\n\tv := make([]int, 2*max+1)\n\tvar trace [][]int\nsearch:\n\tfor d := 0; d \u003c= max; d++ {\n\t\t// iterate through diagonals\n\t\tfor k := -d; k \u003c= d; k += 2 {\n\t\t\tvar x int\n\t\t\tif k == -d || (k != d \u0026\u0026 v[max+k-1] \u003c v[max+k+1]) {\n\t\t\t\tx = v[max+k+1] // move down\n\t\t\t} else {\n\t\t\t\tx = v[max+k-1] + 1 // move right\n\t\t\t}\n\t\t\ty := x - k\n\n\t\t\t// extend the path as far as possible with matching characters\n\t\t\tfor x \u003c n \u0026\u0026 y \u003c m \u0026\u0026 oldRunes[x] == newRunes[y] {\n\t\t\t\tx++\n\t\t\t\ty++\n\t\t\t}\n\n\t\t\tv[max+k] = x\n\n\t\t\t// check if we've reached the end of both strings\n\t\t\tif x == n \u0026\u0026 y == m {\n\t\t\t\ttrace = append(trace, append([]int(nil), v...))\n\t\t\t\tbreak search\n\t\t\t}\n\t\t}\n\t\ttrace = append(trace, append([]int(nil), v...))\n\t}\n\n\t// backtrack to construct the edit script\n\tedits := make([]Edit, 0, n+m)\n\tx, y := n, m\n\tfor d := len(trace) - 1; d \u003e= 0; d-- {\n\t\tvPrev := trace[d]\n\t\tk := x - y\n\t\tvar prevK int\n\t\tif k == -d || (k != d \u0026\u0026 vPrev[max+k-1] \u003c vPrev[max+k+1]) {\n\t\t\tprevK = k + 1\n\t\t} else {\n\t\t\tprevK = k - 1\n\t\t}\n\t\tprevX := vPrev[max+prevK]\n\t\tprevY := prevX - prevK\n\n\t\t// add keep edits for matching characters\n\t\tfor x \u003e prevX \u0026\u0026 y \u003e prevY {\n\t\t\tif x \u003e 0 \u0026\u0026 y \u003e 0 {\n\t\t\t\tedits = append([]Edit{{Type: EditKeep, Char: oldRunes[x-1]}}, edits...)\n\t\t\t}\n\t\t\tx--\n\t\t\ty--\n\t\t}\n\t\tif y \u003e prevY {\n\t\t\tif y \u003e 0 {\n\t\t\t\tedits = append([]Edit{{Type: EditInsert, Char: newRunes[y-1]}}, edits...)\n\t\t\t}\n\t\t\ty--\n\t\t} else if x \u003e prevX {\n\t\t\tif x \u003e 0 {\n\t\t\t\tedits = append([]Edit{{Type: EditDelete, Char: oldRunes[x-1]}}, edits...)\n\t\t\t}\n\t\t\tx--\n\t\t}\n\t}\n\n\treturn edits\n}\n\n// Format converts a slice of Edit operations into a human-readable string representation.\n// It groups consecutive edits of the same type and formats them as follows:\n// - Unchanged characters are left as-is\n// - Inserted characters are wrapped in [+...]\n// - Deleted characters are wrapped in [-...]\n//\n// This function is useful for visualizing the differences between two strings\n// in a compact and intuitive format.\n//\n// Parameters:\n// - edits: A slice of Edit operations, typically produced by MyersDiff\n//\n// Returns:\n// - A formatted string representing the diff\n//\n// Example output:\n//\n//\tFor the diff between \"abcd\" and \"acbd\", the output might be:\n//\t\"a[-b]c[+b]d\"\n//\n// Note:\n//\n//\tThe function assumes that the input slice of edits is in the correct order.\n//\tAn empty input slice will result in an empty string.\nfunc Format(edits []Edit) string {\n\tif len(edits) == 0 {\n\t\treturn \"\"\n\t}\n\n\tvar (\n\t\tresult strings.Builder\n\t\tcurrentType EditType\n\t\tcurrentChars strings.Builder\n\t)\n\n\tflushCurrent := func() {\n\t\tif currentChars.Len() \u003e 0 {\n\t\t\tswitch currentType {\n\t\t\tcase EditKeep:\n\t\t\t\tresult.WriteString(currentChars.String())\n\t\t\tcase EditInsert:\n\t\t\t\tresult.WriteString(\"[+\")\n\t\t\t\tresult.WriteString(currentChars.String())\n\t\t\t\tresult.WriteByte(']')\n\t\t\tcase EditDelete:\n\t\t\t\tresult.WriteString(\"[-\")\n\t\t\t\tresult.WriteString(currentChars.String())\n\t\t\t\tresult.WriteByte(']')\n\t\t\t}\n\t\t\tcurrentChars.Reset()\n\t\t}\n\t}\n\n\tfor _, edit := range edits {\n\t\tif edit.Type != currentType {\n\t\t\tflushCurrent()\n\t\t\tcurrentType = edit.Type\n\t\t}\n\t\tcurrentChars.WriteRune(edit.Char)\n\t}\n\tflushCurrent()\n\n\treturn result.String()\n}\n"},{"name":"diff_test.gno","body":"package diff\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestMyersDiff(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\told string\n\t\tnew string\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname: \"No difference\",\n\t\t\told: \"abc\",\n\t\t\tnew: \"abc\",\n\t\t\texpected: \"abc\",\n\t\t},\n\t\t{\n\t\t\tname: \"Simple insertion\",\n\t\t\told: \"ac\",\n\t\t\tnew: \"abc\",\n\t\t\texpected: \"a[+b]c\",\n\t\t},\n\t\t{\n\t\t\tname: \"Simple deletion\",\n\t\t\told: \"abc\",\n\t\t\tnew: \"ac\",\n\t\t\texpected: \"a[-b]c\",\n\t\t},\n\t\t{\n\t\t\tname: \"Simple substitution\",\n\t\t\told: \"abc\",\n\t\t\tnew: \"abd\",\n\t\t\texpected: \"ab[-c][+d]\",\n\t\t},\n\t\t{\n\t\t\tname: \"Multiple changes\",\n\t\t\told: \"The quick brown fox jumps over the lazy dog\",\n\t\t\tnew: \"The quick brown cat jumps over the lazy dog\",\n\t\t\texpected: \"The quick brown [-fox][+cat] jumps over the lazy dog\",\n\t\t},\n\t\t{\n\t\t\tname: \"Prefix and suffix\",\n\t\t\told: \"Hello, world!\",\n\t\t\tnew: \"Hello, beautiful world!\",\n\t\t\texpected: \"Hello, [+beautiful ]world!\",\n\t\t},\n\t\t{\n\t\t\tname: \"Complete change\",\n\t\t\told: \"abcdef\",\n\t\t\tnew: \"ghijkl\",\n\t\t\texpected: \"[-abcdef][+ghijkl]\",\n\t\t},\n\t\t{\n\t\t\tname: \"Empty strings\",\n\t\t\told: \"\",\n\t\t\tnew: \"\",\n\t\t\texpected: \"\",\n\t\t},\n\t\t{\n\t\t\tname: \"Old empty\",\n\t\t\told: \"\",\n\t\t\tnew: \"abc\",\n\t\t\texpected: \"[+abc]\",\n\t\t},\n\t\t{\n\t\t\tname: \"New empty\",\n\t\t\told: \"abc\",\n\t\t\tnew: \"\",\n\t\t\texpected: \"[-abc]\",\n\t\t},\n\t\t{\n\t\t\tname: \"non-ascii (Korean characters)\",\n\t\t\told: \"ASCII 문자가 아닌 것도 되나?\",\n\t\t\tnew: \"ASCII 문자가 아닌 것도 됨.\",\n\t\t\texpected: \"ASCII 문자가 아닌 것도 [-되나?][+됨.]\",\n\t\t},\n\t\t{\n\t\t\tname: \"Emoji diff\",\n\t\t\told: \"Hello 👋 World 🌍\",\n\t\t\tnew: \"Hello 👋 Beautiful 🌸 World 🌍\",\n\t\t\texpected: \"Hello 👋 [+Beautiful 🌸 ]World 🌍\",\n\t\t},\n\t\t{\n\t\t\tname: \"Mixed multibyte and ASCII\",\n\t\t\told: \"こんにちは World\",\n\t\t\tnew: \"こんばんは World\",\n\t\t\texpected: \"こん[-にち][+ばん]は World\",\n\t\t},\n\t\t{\n\t\t\tname: \"Chinese characters\",\n\t\t\told: \"我喜欢编程\",\n\t\t\tnew: \"我喜欢看书和编程\",\n\t\t\texpected: \"我喜欢[+看书和]编程\",\n\t\t},\n\t\t{\n\t\t\tname: \"Combining characters\",\n\t\t\told: \"e\\u0301\", // é (e + ´)\n\t\t\tnew: \"e\\u0300\", // è (e + `)\n\t\t\texpected: \"e[-\\u0301][+\\u0300]\",\n\t\t},\n\t\t{\n\t\t\tname: \"Right-to-Left languages\",\n\t\t\told: \"שלום\",\n\t\t\tnew: \"שלום עולם\",\n\t\t\texpected: \"שלום[+ עולם]\",\n\t\t},\n\t\t{\n\t\t\tname: \"Normalization NFC and NFD\",\n\t\t\told: \"e\\u0301\", // NFD (decomposed)\n\t\t\tnew: \"\\u00e9\", // NFC (precomposed)\n\t\t\texpected: \"[-e\\u0301][+\\u00e9]\",\n\t\t},\n\t\t{\n\t\t\tname: \"Case sensitivity\",\n\t\t\told: \"abc\",\n\t\t\tnew: \"Abc\",\n\t\t\texpected: \"[-a][+A]bc\",\n\t\t},\n\t\t{\n\t\t\tname: \"Surrogate pairs\",\n\t\t\told: \"Hello 🌍\",\n\t\t\tnew: \"Hello 🌎\",\n\t\t\texpected: \"Hello [-🌍][+🌎]\",\n\t\t},\n\t\t{\n\t\t\tname: \"Control characters\",\n\t\t\told: \"Line1\\nLine2\",\n\t\t\tnew: \"Line1\\r\\nLine2\",\n\t\t\texpected: \"Line1[+\\r]\\nLine2\",\n\t\t},\n\t\t{\n\t\t\tname: \"Mixed scripts\",\n\t\t\told: \"Hello नमस्ते こんにちは\",\n\t\t\tnew: \"Hello สวัสดี こんにちは\",\n\t\t\texpected: \"Hello [-नमस्ते][+สวัสดี] こんにちは\",\n\t\t},\n\t\t{\n\t\t\tname: \"Unicode normalization\",\n\t\t\told: \"é\", // U+00E9 (precomposed)\n\t\t\tnew: \"e\\u0301\", // U+0065 U+0301 (decomposed)\n\t\t\texpected: \"[-é][+e\\u0301]\",\n\t\t},\n\t\t{\n\t\t\tname: \"Directional marks\",\n\t\t\told: \"Hello\\u200Eworld\", // LTR mark\n\t\t\tnew: \"Hello\\u200Fworld\", // RTL mark\n\t\t\texpected: \"Hello[-\\u200E][+\\u200F]world\",\n\t\t},\n\t\t{\n\t\t\tname: \"Zero-width characters\",\n\t\t\told: \"ab\\u200Bc\", // Zero-width space\n\t\t\tnew: \"abc\",\n\t\t\texpected: \"ab[-\\u200B]c\",\n\t\t},\n\t\t{\n\t\t\tname: \"Worst-case scenario (completely different strings)\",\n\t\t\told: strings.Repeat(\"a\", 1000),\n\t\t\tnew: strings.Repeat(\"b\", 1000),\n\t\t\texpected: \"[-\" + strings.Repeat(\"a\", 1000) + \"][+\" + strings.Repeat(\"b\", 1000) + \"]\",\n\t\t},\n\t\t{\n\t\t\tname: \"Very long strings\",\n\t\t\told: strings.Repeat(\"a\", 10000) + \"b\" + strings.Repeat(\"a\", 10000),\n\t\t\tnew: strings.Repeat(\"a\", 10000) + \"c\" + strings.Repeat(\"a\", 10000),\n\t\t\texpected: strings.Repeat(\"a\", 10000) + \"[-b][+c]\" + strings.Repeat(\"a\", 10000),\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tdiff := MyersDiff(tc.old, tc.new)\n\t\t\tresult := Format(diff)\n\t\t\tif result != tc.expected {\n\t\t\t\tt.Errorf(\"Expected: %s, got: %s\", tc.expected, result)\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":"uassert","path":"gno.land/p/demo/uassert","files":[{"name":"doc.gno","body":"package uassert // import \"gno.land/p/demo/uassert\"\n"},{"name":"helpers.gno","body":"package uassert\n\nimport \"strings\"\n\nfunc fail(t TestingT, customMsgs []string, failureMessage string, args ...interface{}) bool {\n\tcustomMsg := \"\"\n\tif len(customMsgs) \u003e 0 {\n\t\tcustomMsg = strings.Join(customMsgs, \" \")\n\t}\n\tif customMsg != \"\" {\n\t\tfailureMessage += \" - \" + customMsg\n\t}\n\tt.Errorf(failureMessage, args...)\n\treturn false\n}\n\nfunc autofail(t TestingT, success bool, customMsgs []string, failureMessage string, args ...interface{}) bool {\n\tif success {\n\t\treturn true\n\t}\n\treturn fail(t, customMsgs, failureMessage, args...)\n}\n\nfunc checkDidPanic(f func()) (didPanic bool, message string) {\n\tdidPanic = true\n\tdefer func() {\n\t\tr := recover()\n\n\t\tif r == nil {\n\t\t\tmessage = \"nil\"\n\t\t\treturn\n\t\t}\n\n\t\terr, ok := r.(error)\n\t\tif ok {\n\t\t\tmessage = err.Error()\n\t\t\treturn\n\t\t}\n\n\t\terrStr, ok := r.(string)\n\t\tif ok {\n\t\t\tmessage = errStr\n\t\t\treturn\n\t\t}\n\n\t\tmessage = \"recover: unsupported type\"\n\t}()\n\tf()\n\tdidPanic = false\n\treturn\n}\n"},{"name":"mock_test.gno","body":"package uassert\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n)\n\ntype mockTestingT struct {\n\tfmt string\n\targs []interface{}\n}\n\n// --- interface mock\n\nvar _ TestingT = (*mockTestingT)(nil)\n\nfunc (mockT *mockTestingT) Helper() { /* noop */ }\nfunc (mockT *mockTestingT) Skip(args ...interface{}) { /* not implmented */ }\nfunc (mockT *mockTestingT) Fail() { /* not implmented */ }\nfunc (mockT *mockTestingT) FailNow() { /* not implmented */ }\nfunc (mockT *mockTestingT) Logf(fmt string, args ...interface{}) { /* noop */ }\n\nfunc (mockT *mockTestingT) Fatalf(fmt string, args ...interface{}) {\n\tmockT.fmt = \"fatal: \" + fmt\n\tmockT.args = args\n}\n\nfunc (mockT *mockTestingT) Errorf(fmt string, args ...interface{}) {\n\tmockT.fmt = \"error: \" + fmt\n\tmockT.args = args\n}\n\n// --- helpers\n\nfunc (mockT *mockTestingT) actualString() string {\n\tres := fmt.Sprintf(mockT.fmt, mockT.args...)\n\tmockT.reset()\n\treturn res\n}\n\nfunc (mockT *mockTestingT) reset() {\n\tmockT.fmt = \"\"\n\tmockT.args = nil\n}\n\nfunc (mockT *mockTestingT) equals(t *testing.T, expected string) {\n\tactual := mockT.actualString()\n\n\tif expected != actual {\n\t\tt.Errorf(\"mockT differs:\\n- expected: %s\\n- actual: %s\\n\", expected, actual)\n\t}\n}\n\nfunc (mockT *mockTestingT) empty(t *testing.T) {\n\tif mockT.fmt != \"\" || mockT.args != nil {\n\t\tactual := mockT.actualString()\n\t\tt.Errorf(\"mockT should be empty, got %s\", actual)\n\t}\n}\n"},{"name":"types.gno","body":"package uassert\n\ntype TestingT interface {\n\tHelper()\n\tSkip(args ...interface{})\n\tFatalf(fmt string, args ...interface{})\n\tErrorf(fmt string, args ...interface{})\n\tLogf(fmt string, args ...interface{})\n\tFail()\n\tFailNow()\n}\n"},{"name":"uassert.gno","body":"// uassert is an adapted lighter version of https://github.com/stretchr/testify/assert.\npackage uassert\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/diff\"\n)\n\n// NoError asserts that a function returned no error (i.e. `nil`).\nfunc NoError(t TestingT, err error, msgs ...string) bool {\n\tt.Helper()\n\tif err != nil {\n\t\treturn fail(t, msgs, \"unexpected error: %s\", err.Error())\n\t}\n\treturn true\n}\n\n// Error asserts that a function returned an error (i.e. not `nil`).\nfunc Error(t TestingT, err error, msgs ...string) bool {\n\tt.Helper()\n\tif err == nil {\n\t\treturn fail(t, msgs, \"an error is expected but got nil\")\n\t}\n\treturn true\n}\n\n// ErrorContains asserts that a function returned an error (i.e. not `nil`)\n// and that the error contains the specified substring.\nfunc ErrorContains(t TestingT, err error, contains string, msgs ...string) bool {\n\tt.Helper()\n\n\tif !Error(t, err, msgs...) {\n\t\treturn false\n\t}\n\n\tactual := err.Error()\n\tif !strings.Contains(actual, contains) {\n\t\treturn fail(t, msgs, \"error %q does not contain %q\", actual, contains)\n\t}\n\n\treturn true\n}\n\n// True asserts that the specified value is true.\nfunc True(t TestingT, value bool, msgs ...string) bool {\n\tt.Helper()\n\tif !value {\n\t\treturn fail(t, msgs, \"should be true\")\n\t}\n\treturn true\n}\n\n// False asserts that the specified value is false.\nfunc False(t TestingT, value bool, msgs ...string) bool {\n\tt.Helper()\n\tif value {\n\t\treturn fail(t, msgs, \"should be false\")\n\t}\n\treturn true\n}\n\n// ErrorIs asserts the given error matches the target error\nfunc ErrorIs(t TestingT, err, target error, msgs ...string) bool {\n\tt.Helper()\n\n\tif err == nil || target == nil {\n\t\treturn err == target\n\t}\n\n\t// XXX: if errors.Is(err, target) return true\n\n\tif err.Error() != target.Error() {\n\t\treturn fail(t, msgs, \"error mismatch, expected %s, got %s\", target.Error(), err.Error())\n\t}\n\n\treturn true\n}\n\n// PanicsWithMessage asserts that the code inside the specified func panics,\n// and that the recovered panic value satisfies the given message\nfunc PanicsWithMessage(t TestingT, msg string, f func(), msgs ...string) bool {\n\tt.Helper()\n\n\tdidPanic, panicValue := checkDidPanic(f)\n\tif !didPanic {\n\t\treturn fail(t, msgs, \"func should panic\\n\\tPanic value:\\t%v\", panicValue)\n\t}\n\n\tif panicValue != msg {\n\t\treturn fail(t, msgs, \"func should panic with message:\\t%s\\n\\tPanic value:\\t%s\", msg, panicValue)\n\t}\n\treturn true\n}\n\n// NotPanics asserts that the code inside the specified func does NOT panic.\nfunc NotPanics(t TestingT, f func(), msgs ...string) bool {\n\tt.Helper()\n\n\tdidPanic, panicValue := checkDidPanic(f)\n\n\tif didPanic {\n\t\treturn fail(t, msgs, \"func should not panic\\n\\tPanic value:\\t%s\", panicValue)\n\t}\n\treturn true\n}\n\n// Equal asserts that two objects are equal.\nfunc Equal(t TestingT, expected, actual interface{}, msgs ...string) bool {\n\tt.Helper()\n\n\tif expected == nil || actual == nil {\n\t\treturn expected == actual\n\t}\n\n\t// XXX: errors\n\t// XXX: slices\n\t// XXX: pointers\n\n\tequal := false\n\tok_ := false\n\tes, as := \"unsupported type\", \"unsupported type\"\n\n\tswitch ev := expected.(type) {\n\tcase string:\n\t\tif av, ok := actual.(string); ok {\n\t\t\tequal = ev == av\n\t\t\tok_ = true\n\t\t\tes, as = ev, av\n\t\t\tif !equal {\n\t\t\t\tdif := diff.MyersDiff(ev, av)\n\t\t\t\treturn fail(t, msgs, \"uassert.Equal: strings are different\\n\\tDiff: %s\", diff.Format(dif))\n\t\t\t}\n\t\t}\n\tcase std.Address:\n\t\tif av, ok := actual.(std.Address); ok {\n\t\t\tequal = ev == av\n\t\t\tok_ = true\n\t\t\tes, as = string(ev), string(av)\n\t\t}\n\tcase int:\n\t\tif av, ok := actual.(int); ok {\n\t\t\tequal = ev == av\n\t\t\tok_ = true\n\t\t\tes, as = strconv.Itoa(ev), strconv.Itoa(av)\n\t\t}\n\tcase int8:\n\t\tif av, ok := actual.(int8); ok {\n\t\t\tequal = ev == av\n\t\t\tok_ = true\n\t\t\tes, as = strconv.Itoa(int(ev)), strconv.Itoa(int(av))\n\t\t}\n\tcase int16:\n\t\tif av, ok := actual.(int16); ok {\n\t\t\tequal = ev == av\n\t\t\tok_ = true\n\t\t\tes, as = strconv.Itoa(int(ev)), strconv.Itoa(int(av))\n\t\t}\n\tcase int32:\n\t\tif av, ok := actual.(int32); ok {\n\t\t\tequal = ev == av\n\t\t\tok_ = true\n\t\t\tes, as = strconv.Itoa(int(ev)), strconv.Itoa(int(av))\n\t\t}\n\tcase int64:\n\t\tif av, ok := actual.(int64); ok {\n\t\t\tequal = ev == av\n\t\t\tok_ = true\n\t\t\tes, as = strconv.Itoa(int(ev)), strconv.Itoa(int(av))\n\t\t}\n\tcase uint:\n\t\tif av, ok := actual.(uint); ok {\n\t\t\tequal = ev == av\n\t\t\tok_ = true\n\t\t\tes, as = strconv.FormatUint(uint64(ev), 10), strconv.FormatUint(uint64(av), 10)\n\t\t}\n\tcase uint8:\n\t\tif av, ok := actual.(uint8); ok {\n\t\t\tequal = ev == av\n\t\t\tok_ = true\n\t\t\tes, as = strconv.FormatUint(uint64(ev), 10), strconv.FormatUint(uint64(av), 10)\n\t\t}\n\tcase uint16:\n\t\tif av, ok := actual.(uint16); ok {\n\t\t\tequal = ev == av\n\t\t\tok_ = true\n\t\t\tes, as = strconv.FormatUint(uint64(ev), 10), strconv.FormatUint(uint64(av), 10)\n\t\t}\n\tcase uint32:\n\t\tif av, ok := actual.(uint32); ok {\n\t\t\tequal = ev == av\n\t\t\tok_ = true\n\t\t\tes, as = strconv.FormatUint(uint64(ev), 10), strconv.FormatUint(uint64(av), 10)\n\t\t}\n\tcase uint64:\n\t\tif av, ok := actual.(uint64); ok {\n\t\t\tequal = ev == av\n\t\t\tok_ = true\n\t\t\tes, as = strconv.FormatUint(ev, 10), strconv.FormatUint(av, 10)\n\t\t}\n\tcase bool:\n\t\tif av, ok := actual.(bool); ok {\n\t\t\tequal = ev == av\n\t\t\tok_ = true\n\t\t\tif ev {\n\t\t\t\tes, as = \"true\", \"false\"\n\t\t\t} else {\n\t\t\t\tes, as = \"false\", \"true\"\n\t\t\t}\n\t\t}\n\tcase float32:\n\t\tif av, ok := actual.(float32); ok {\n\t\t\tequal = ev == av\n\t\t\tok_ = true\n\t\t}\n\tcase float64:\n\t\tif av, ok := actual.(float64); ok {\n\t\t\tequal = ev == av\n\t\t\tok_ = true\n\t\t}\n\tdefault:\n\t\treturn fail(t, msgs, \"uassert.Equal: unsupported type\")\n\t}\n\n\t/*\n\t\t// XXX: implement stringer and other well known similar interfaces\n\t\ttype stringer interface{ String() string }\n\t\tif ev, ok := expected.(stringer); ok {\n\t\t\tif av, ok := actual.(stringer); ok {\n\t\t\t\tequal = ev.String() == av.String()\n\t\t\t\tok_ = true\n\t\t\t}\n\t\t}\n\t*/\n\n\tif !ok_ {\n\t\treturn fail(t, msgs, \"uassert.Equal: different types\") // XXX: display the types\n\t}\n\tif !equal {\n\t\treturn fail(t, msgs, \"uassert.Equal: same type but different value\\n\\texpected: %s\\n\\tactual: %s\", es, as)\n\t}\n\n\treturn true\n}\n\n// NotEqual asserts that two objects are not equal.\nfunc NotEqual(t TestingT, expected, actual interface{}, msgs ...string) bool {\n\tt.Helper()\n\n\tif expected == nil || actual == nil {\n\t\treturn expected != actual\n\t}\n\n\t// XXX: errors\n\t// XXX: slices\n\t// XXX: pointers\n\n\tnotEqual := false\n\tok_ := false\n\tes, as := \"unsupported type\", \"unsupported type\"\n\n\tswitch ev := expected.(type) {\n\tcase string:\n\t\tif av, ok := actual.(string); ok {\n\t\t\tnotEqual = ev != av\n\t\t\tok_ = true\n\t\t\tes, as = ev, av\n\t\t}\n\tcase std.Address:\n\t\tif av, ok := actual.(std.Address); ok {\n\t\t\tnotEqual = ev != av\n\t\t\tok_ = true\n\t\t\tes, as = string(ev), string(av)\n\t\t}\n\tcase int:\n\t\tif av, ok := actual.(int); ok {\n\t\t\tnotEqual = ev != av\n\t\t\tok_ = true\n\t\t\tes, as = strconv.Itoa(ev), strconv.Itoa(av)\n\t\t}\n\tcase int8:\n\t\tif av, ok := actual.(int8); ok {\n\t\t\tnotEqual = ev != av\n\t\t\tok_ = true\n\t\t\tes, as = strconv.Itoa(int(ev)), strconv.Itoa(int(av))\n\t\t}\n\tcase int16:\n\t\tif av, ok := actual.(int16); ok {\n\t\t\tnotEqual = ev != av\n\t\t\tok_ = true\n\t\t\tes, as = strconv.Itoa(int(ev)), strconv.Itoa(int(av))\n\t\t}\n\tcase int32:\n\t\tif av, ok := actual.(int32); ok {\n\t\t\tnotEqual = ev != av\n\t\t\tok_ = true\n\t\t\tes, as = strconv.Itoa(int(ev)), strconv.Itoa(int(av))\n\t\t}\n\tcase int64:\n\t\tif av, ok := actual.(int64); ok {\n\t\t\tnotEqual = ev != av\n\t\t\tok_ = true\n\t\t\tes, as = strconv.Itoa(int(ev)), strconv.Itoa(int(av))\n\t\t}\n\tcase uint:\n\t\tif av, ok := actual.(uint); ok {\n\t\t\tnotEqual = ev != av\n\t\t\tok_ = true\n\t\t\tes, as = strconv.FormatUint(uint64(ev), 10), strconv.FormatUint(uint64(av), 10)\n\t\t}\n\tcase uint8:\n\t\tif av, ok := actual.(uint8); ok {\n\t\t\tnotEqual = ev != av\n\t\t\tok_ = true\n\t\t\tes, as = strconv.FormatUint(uint64(ev), 10), strconv.FormatUint(uint64(av), 10)\n\t\t}\n\tcase uint16:\n\t\tif av, ok := actual.(uint16); ok {\n\t\t\tnotEqual = ev != av\n\t\t\tok_ = true\n\t\t\tes, as = strconv.FormatUint(uint64(ev), 10), strconv.FormatUint(uint64(av), 10)\n\t\t}\n\tcase uint32:\n\t\tif av, ok := actual.(uint32); ok {\n\t\t\tnotEqual = ev != av\n\t\t\tok_ = true\n\t\t\tes, as = strconv.FormatUint(uint64(ev), 10), strconv.FormatUint(uint64(av), 10)\n\t\t}\n\tcase uint64:\n\t\tif av, ok := actual.(uint64); ok {\n\t\t\tnotEqual = ev != av\n\t\t\tok_ = true\n\t\t\tes, as = strconv.FormatUint(ev, 10), strconv.FormatUint(av, 10)\n\t\t}\n\tcase bool:\n\t\tif av, ok := actual.(bool); ok {\n\t\t\tnotEqual = ev != av\n\t\t\tok_ = true\n\t\t\tif ev {\n\t\t\t\tes, as = \"true\", \"false\"\n\t\t\t} else {\n\t\t\t\tes, as = \"false\", \"true\"\n\t\t\t}\n\t\t}\n\tcase float32:\n\t\tif av, ok := actual.(float32); ok {\n\t\t\tnotEqual = ev != av\n\t\t\tok_ = true\n\t\t}\n\tcase float64:\n\t\tif av, ok := actual.(float64); ok {\n\t\t\tnotEqual = ev != av\n\t\t\tok_ = true\n\t\t}\n\tdefault:\n\t\treturn fail(t, msgs, \"uassert.NotEqual: unsupported type\")\n\t}\n\n\t/*\n\t\t// XXX: implement stringer and other well known similar interfaces\n\t\ttype stringer interface{ String() string }\n\t\tif ev, ok := expected.(stringer); ok {\n\t\t\tif av, ok := actual.(stringer); ok {\n\t\t\t\tnotEqual = ev.String() != av.String()\n\t\t\t\tok_ = true\n\t\t\t}\n\t\t}\n\t*/\n\n\tif !ok_ {\n\t\treturn fail(t, msgs, \"uassert.NotEqual: different types\") // XXX: display the types\n\t}\n\tif !notEqual {\n\t\treturn fail(t, msgs, \"uassert.NotEqual: same type and same value\\n\\texpected: %s\\n\\tactual: %s\", es, as)\n\t}\n\n\treturn true\n}\n\nfunc isNumberEmpty(n interface{}) (isNumber, isEmpty bool) {\n\tswitch n := n.(type) {\n\t// NOTE: the cases are split individually, so that n becomes of the\n\t// asserted type; the type of '0' was correctly inferred and converted\n\t// to the corresponding type, int, int8, etc.\n\tcase int:\n\t\treturn true, n == 0\n\tcase int8:\n\t\treturn true, n == 0\n\tcase int16:\n\t\treturn true, n == 0\n\tcase int32:\n\t\treturn true, n == 0\n\tcase int64:\n\t\treturn true, n == 0\n\tcase uint:\n\t\treturn true, n == 0\n\tcase uint8:\n\t\treturn true, n == 0\n\tcase uint16:\n\t\treturn true, n == 0\n\tcase uint32:\n\t\treturn true, n == 0\n\tcase uint64:\n\t\treturn true, n == 0\n\tcase float32:\n\t\treturn true, n == 0\n\tcase float64:\n\t\treturn true, n == 0\n\t}\n\treturn false, false\n}\nfunc Empty(t TestingT, obj interface{}, msgs ...string) bool {\n\tt.Helper()\n\n\tisNumber, isEmpty := isNumberEmpty(obj)\n\tif isNumber {\n\t\tif !isEmpty {\n\t\t\treturn fail(t, msgs, \"uassert.Empty: not empty number: %d\", obj)\n\t\t}\n\t} else {\n\t\tswitch val := obj.(type) {\n\t\tcase string:\n\t\t\tif val != \"\" {\n\t\t\t\treturn fail(t, msgs, \"uassert.Empty: not empty string: %s\", val)\n\t\t\t}\n\t\tcase std.Address:\n\t\t\tvar zeroAddr std.Address\n\t\t\tif val != zeroAddr {\n\t\t\t\treturn fail(t, msgs, \"uassert.Empty: not empty std.Address: %s\", string(val))\n\t\t\t}\n\t\tdefault:\n\t\t\treturn fail(t, msgs, \"uassert.Empty: unsupported type\")\n\t\t}\n\t}\n\treturn true\n}\n\nfunc NotEmpty(t TestingT, obj interface{}, msgs ...string) bool {\n\tt.Helper()\n\tisNumber, isEmpty := isNumberEmpty(obj)\n\tif isNumber {\n\t\tif isEmpty {\n\t\t\treturn fail(t, msgs, \"uassert.NotEmpty: empty number: %d\", obj)\n\t\t}\n\t} else {\n\t\tswitch val := obj.(type) {\n\t\tcase string:\n\t\t\tif val == \"\" {\n\t\t\t\treturn fail(t, msgs, \"uassert.NotEmpty: empty string: %s\", val)\n\t\t\t}\n\t\tcase std.Address:\n\t\t\tvar zeroAddr std.Address\n\t\t\tif val == zeroAddr {\n\t\t\t\treturn fail(t, msgs, \"uassert.NotEmpty: empty std.Address: %s\", string(val))\n\t\t\t}\n\t\tdefault:\n\t\t\treturn fail(t, msgs, \"uassert.NotEmpty: unsupported type\")\n\t\t}\n\t}\n\treturn true\n}\n"},{"name":"uassert_test.gno","body":"package uassert\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"std\"\n\t\"testing\"\n)\n\nvar _ TestingT = (*testing.T)(nil)\n\nfunc TestMock(t *testing.T) {\n\tmockT := new(mockTestingT)\n\tmockT.empty(t)\n\tNoError(mockT, errors.New(\"foo\"))\n\tmockT.equals(t, \"error: unexpected error: foo\")\n\tNoError(mockT, errors.New(\"foo\"), \"custom message\")\n\tmockT.equals(t, \"error: unexpected error: foo - custom message\")\n\tNoError(mockT, errors.New(\"foo\"), \"custom\", \"message\")\n\tmockT.equals(t, \"error: unexpected error: foo - custom message\")\n}\n\nfunc TestNoError(t *testing.T) {\n\tmockT := new(mockTestingT)\n\tTrue(t, NoError(mockT, nil))\n\tmockT.empty(t)\n\tFalse(t, NoError(mockT, errors.New(\"foo bar\")))\n\tmockT.equals(t, \"error: unexpected error: foo bar\")\n}\n\nfunc TestError(t *testing.T) {\n\tmockT := new(mockTestingT)\n\tTrue(t, Error(mockT, errors.New(\"foo bar\")))\n\tmockT.empty(t)\n\tFalse(t, Error(mockT, nil))\n\tmockT.equals(t, \"error: an error is expected but got nil\")\n}\n\nfunc TestErrorContains(t *testing.T) {\n\tmockT := new(mockTestingT)\n\n\t// nil error\n\tvar err error\n\tFalse(t, ErrorContains(mockT, err, \"\"), \"ErrorContains should return false for nil arg\")\n}\n\nfunc TestTrue(t *testing.T) {\n\tmockT := new(mockTestingT)\n\tif !True(mockT, true) {\n\t\tt.Error(\"True should return true\")\n\t}\n\tmockT.empty(t)\n\tif True(mockT, false) {\n\t\tt.Error(\"True should return false\")\n\t}\n\tmockT.equals(t, \"error: should be true\")\n}\n\nfunc TestFalse(t *testing.T) {\n\tmockT := new(mockTestingT)\n\tif !False(mockT, false) {\n\t\tt.Error(\"False should return true\")\n\t}\n\tmockT.empty(t)\n\tif False(mockT, true) {\n\t\tt.Error(\"False should return false\")\n\t}\n\tmockT.equals(t, \"error: should be false\")\n}\n\nfunc TestPanicsWithMessage(t *testing.T) {\n\tmockT := new(mockTestingT)\n\tif !PanicsWithMessage(mockT, \"panic\", func() {\n\t\tpanic(errors.New(\"panic\"))\n\t}) {\n\t\tt.Error(\"PanicsWithMessage should return true\")\n\t}\n\tmockT.empty(t)\n\n\tif PanicsWithMessage(mockT, \"Panic!\", func() {\n\t\t// noop\n\t}) {\n\t\tt.Error(\"PanicsWithMessage should return false\")\n\t}\n\tmockT.equals(t, \"error: func should panic\\n\\tPanic value:\\tnil\")\n\n\tif PanicsWithMessage(mockT, \"at the disco\", func() {\n\t\tpanic(errors.New(\"panic\"))\n\t}) {\n\t\tt.Error(\"PanicsWithMessage should return false\")\n\t}\n\tmockT.equals(t, \"error: func should panic with message:\\tat the disco\\n\\tPanic value:\\tpanic\")\n\n\tif PanicsWithMessage(mockT, \"Panic!\", func() {\n\t\tpanic(\"panic\")\n\t}) {\n\t\tt.Error(\"PanicsWithMessage should return false\")\n\t}\n\tmockT.equals(t, \"error: func should panic with message:\\tPanic!\\n\\tPanic value:\\tpanic\")\n}\n\nfunc TestNotPanics(t *testing.T) {\n\tmockT := new(mockTestingT)\n\n\tif !NotPanics(mockT, func() {\n\t\t// noop\n\t}) {\n\t\tt.Error(\"NotPanics should return true\")\n\t}\n\tmockT.empty(t)\n\n\tif NotPanics(mockT, func() {\n\t\tpanic(\"Panic!\")\n\t}) {\n\t\tt.Error(\"NotPanics should return false\")\n\t}\n}\n\nfunc TestEqual(t *testing.T) {\n\tmockT := new(mockTestingT)\n\n\tcases := []struct {\n\t\texpected interface{}\n\t\tactual interface{}\n\t\tresult bool\n\t\tremark string\n\t}{\n\t\t// expected to be equal\n\t\t{\"Hello World\", \"Hello World\", true, \"\"},\n\t\t{123, 123, true, \"\"},\n\t\t{123.5, 123.5, true, \"\"},\n\t\t{nil, nil, true, \"\"},\n\t\t{int32(123), int32(123), true, \"\"},\n\t\t{uint64(123), uint64(123), true, \"\"},\n\t\t{std.Address(\"g12345\"), std.Address(\"g12345\"), true, \"\"},\n\t\t// XXX: continue\n\n\t\t// not expected to be equal\n\t\t{\"Hello World\", 42, false, \"\"},\n\t\t{41, 42, false, \"\"},\n\t\t{10, uint(10), false, \"\"},\n\t\t// XXX: continue\n\n\t\t// expected to raise errors\n\t\t// XXX: todo\n\t}\n\n\tfor _, c := range cases {\n\t\tname := fmt.Sprintf(\"Equal(%v, %v)\", c.expected, c.actual)\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tres := Equal(mockT, c.expected, c.actual)\n\n\t\t\tif res != c.result {\n\t\t\t\tt.Errorf(\"%s should return %v: %s - %s\", name, c.result, c.remark, mockT.actualString())\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNotEqual(t *testing.T) {\n\tmockT := new(mockTestingT)\n\n\tcases := []struct {\n\t\texpected interface{}\n\t\tactual interface{}\n\t\tresult bool\n\t\tremark string\n\t}{\n\t\t// expected to be not equal\n\t\t{\"Hello World\", \"Hello\", true, \"\"},\n\t\t{123, 124, true, \"\"},\n\t\t{123.5, 123.6, true, \"\"},\n\t\t{nil, 123, true, \"\"},\n\t\t{int32(123), int32(124), true, \"\"},\n\t\t{uint64(123), uint64(124), true, \"\"},\n\t\t{std.Address(\"g12345\"), std.Address(\"g67890\"), true, \"\"},\n\t\t// XXX: continue\n\n\t\t// not expected to be not equal\n\t\t{\"Hello World\", \"Hello World\", false, \"\"},\n\t\t{123, 123, false, \"\"},\n\t\t{123.5, 123.5, false, \"\"},\n\t\t{nil, nil, false, \"\"},\n\t\t{int32(123), int32(123), false, \"\"},\n\t\t{uint64(123), uint64(123), false, \"\"},\n\t\t{std.Address(\"g12345\"), std.Address(\"g12345\"), false, \"\"},\n\t\t// XXX: continue\n\n\t\t// expected to raise errors\n\t\t// XXX: todo\n\t}\n\n\tfor _, c := range cases {\n\t\tname := fmt.Sprintf(\"NotEqual(%v, %v)\", c.expected, c.actual)\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tres := NotEqual(mockT, c.expected, c.actual)\n\n\t\t\tif res != c.result {\n\t\t\t\tt.Errorf(\"%s should return %v: %s - %s\", name, c.result, c.remark, mockT.actualString())\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype myStruct struct {\n\tS string\n\tI int\n}\n\nfunc TestEmpty(t *testing.T) {\n\tmockT := new(mockTestingT)\n\n\tcases := []struct {\n\t\tobj interface{}\n\t\texpectedEmpty bool\n\t}{\n\t\t// expected to be empty\n\t\t{\"\", true},\n\t\t{0, true},\n\t\t{int(0), true},\n\t\t{int32(0), true},\n\t\t{int64(0), true},\n\t\t{uint(0), true},\n\t\t// XXX: continue\n\n\t\t// not expected to be empty\n\t\t{\"Hello World\", false},\n\t\t{1, false},\n\t\t{int32(1), false},\n\t\t{uint64(1), false},\n\t\t{std.Address(\"g12345\"), false},\n\n\t\t// unsupported\n\t\t{nil, false},\n\t\t{myStruct{}, false},\n\t\t{\u0026myStruct{}, false},\n\t}\n\n\tfor _, c := range cases {\n\t\tname := fmt.Sprintf(\"Empty(%v)\", c.obj)\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tres := Empty(mockT, c.obj)\n\n\t\t\tif res != c.expectedEmpty {\n\t\t\t\tt.Errorf(\"%s should return %v: %s\", name, c.expectedEmpty, mockT.actualString())\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestEqualWithStringDiff(t *testing.T) {\n\tcases := []struct {\n\t\tname string\n\t\texpected string\n\t\tactual string\n\t\tshouldPass bool\n\t\texpectedMsg string\n\t}{\n\t\t{\n\t\t\tname: \"Identical strings\",\n\t\t\texpected: \"Hello, world!\",\n\t\t\tactual: \"Hello, world!\",\n\t\t\tshouldPass: true,\n\t\t\texpectedMsg: \"\",\n\t\t},\n\t\t{\n\t\t\tname: \"Different strings - simple\",\n\t\t\texpected: \"Hello, world!\",\n\t\t\tactual: \"Hello, World!\",\n\t\t\tshouldPass: false,\n\t\t\texpectedMsg: \"error: uassert.Equal: strings are different\\n\\tDiff: Hello, [-w][+W]orld!\",\n\t\t},\n\t\t{\n\t\t\tname: \"Different strings - complex\",\n\t\t\texpected: \"The quick brown fox jumps over the lazy dog\",\n\t\t\tactual: \"The quick brown cat jumps over the lazy dog\",\n\t\t\tshouldPass: false,\n\t\t\texpectedMsg: \"error: uassert.Equal: strings are different\\n\\tDiff: The quick brown [-fox][+cat] jumps over the lazy dog\",\n\t\t},\n\t\t{\n\t\t\tname: \"Different strings - prefix\",\n\t\t\texpected: \"prefix_string\",\n\t\t\tactual: \"string\",\n\t\t\tshouldPass: false,\n\t\t\texpectedMsg: \"error: uassert.Equal: strings are different\\n\\tDiff: [-prefix_]string\",\n\t\t},\n\t\t{\n\t\t\tname: \"Different strings - suffix\",\n\t\t\texpected: \"string\",\n\t\t\tactual: \"string_suffix\",\n\t\t\tshouldPass: false,\n\t\t\texpectedMsg: \"error: uassert.Equal: strings are different\\n\\tDiff: string[+_suffix]\",\n\t\t},\n\t\t{\n\t\t\tname: \"Empty string vs non-empty string\",\n\t\t\texpected: \"\",\n\t\t\tactual: \"non-empty\",\n\t\t\tshouldPass: false,\n\t\t\texpectedMsg: \"error: uassert.Equal: strings are different\\n\\tDiff: [+non-empty]\",\n\t\t},\n\t\t{\n\t\t\tname: \"Non-empty string vs empty string\",\n\t\t\texpected: \"non-empty\",\n\t\t\tactual: \"\",\n\t\t\tshouldPass: false,\n\t\t\texpectedMsg: \"error: uassert.Equal: strings are different\\n\\tDiff: [-non-empty]\",\n\t\t},\n\t}\n\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tmockT := \u0026mockTestingT{}\n\t\t\tresult := Equal(mockT, tc.expected, tc.actual)\n\n\t\t\tif result != tc.shouldPass {\n\t\t\t\tt.Errorf(\"Expected Equal to return %v, but got %v\", tc.shouldPass, result)\n\t\t\t}\n\n\t\t\tif tc.shouldPass {\n\t\t\t\tmockT.empty(t)\n\t\t\t} else {\n\t\t\t\tmockT.equals(t, tc.expectedMsg)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNotEmpty(t *testing.T) {\n\tmockT := new(mockTestingT)\n\n\tcases := []struct {\n\t\tobj interface{}\n\t\texpectedNotEmpty bool\n\t}{\n\t\t// expected to be empty\n\t\t{\"\", false},\n\t\t{0, false},\n\t\t{int(0), false},\n\t\t{int32(0), false},\n\t\t{int64(0), false},\n\t\t{uint(0), false},\n\t\t{std.Address(\"\"), false},\n\n\t\t// not expected to be empty\n\t\t{\"Hello World\", true},\n\t\t{1, true},\n\t\t{int32(1), true},\n\t\t{uint64(1), true},\n\t\t{std.Address(\"g12345\"), true},\n\n\t\t// unsupported\n\t\t{nil, false},\n\t\t{myStruct{}, false},\n\t\t{\u0026myStruct{}, false},\n\t}\n\n\tfor _, c := range cases {\n\t\tname := fmt.Sprintf(\"NotEmpty(%v)\", c.obj)\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tres := NotEmpty(mockT, c.obj)\n\n\t\t\tif res != c.expectedNotEmpty {\n\t\t\t\tt.Errorf(\"%s should return %v: %s\", name, c.expectedNotEmpty, mockT.actualString())\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":"ufmt","path":"gno.land/p/demo/ufmt","files":[{"name":"ufmt.gno","body":"// Package ufmt provides utility functions for formatting strings, similarly\n// to the Go package \"fmt\", of which only a subset is currently supported\n// (hence the name µfmt - micro fmt).\npackage ufmt\n\nimport (\n\t\"errors\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// Println formats using the default formats for its operands and writes to standard output.\n// Println writes the given arguments to standard output with spaces between arguments\n// and a newline at the end.\nfunc Println(args ...interface{}) {\n\tvar strs []string\n\tfor _, arg := range args {\n\t\tswitch v := arg.(type) {\n\t\tcase string:\n\t\t\tstrs = append(strs, v)\n\t\tcase (interface{ String() string }):\n\t\t\tstrs = append(strs, v.String())\n\t\tcase error:\n\t\t\tstrs = append(strs, v.Error())\n\t\tcase int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:\n\t\t\tstrs = append(strs, Sprintf(\"%d\", v))\n\t\tcase bool:\n\t\t\tif v {\n\t\t\t\tstrs = append(strs, \"true\")\n\t\t\t} else {\n\t\t\t\tstrs = append(strs, \"false\")\n\t\t\t}\n\t\tcase nil:\n\t\t\tstrs = append(strs, \"\u003cnil\u003e\")\n\t\tdefault:\n\t\t\tstrs = append(strs, \"(unhandled)\")\n\t\t}\n\t}\n\n\t// TODO: remove println after gno supports os.Stdout\n\tprintln(strings.Join(strs, \" \"))\n}\n\n// Sprintf offers similar functionality to Go's fmt.Sprintf, or the sprintf\n// equivalent available in many languages, including C/C++.\n// The number of args passed must exactly match the arguments consumed by the format.\n// A limited number of formatting verbs and features are currently supported,\n// hence the name ufmt (µfmt, micro-fmt).\n//\n// The currently formatted verbs are the following:\n//\n//\t%s: places a string value directly.\n//\t If the value implements the interface interface{ String() string },\n//\t the String() method is called to retrieve the value. Same about Error()\n//\t string.\n//\t%c: formats the character represented by Unicode code point\n//\t%d: formats an integer value using package \"strconv\".\n//\t Currently supports only uint, uint64, int, int64.\n//\t%t: formats a boolean value to \"true\" or \"false\".\n//\t%x: formats an integer value as a hexadecimal string.\n//\t Currently supports only uint8, []uint8, [32]uint8.\n//\t%c: formats a rune value as a string.\n//\t Currently supports only rune, int.\n//\t%q: formats a string value as a quoted string.\n//\t%T: formats the type of the value.\n//\t%%: outputs a literal %. Does not consume an argument.\nfunc Sprintf(format string, args ...interface{}) string {\n\t// we use runes to handle multi-byte characters\n\tsTor := []rune(format)\n\tend := len(sTor)\n\targNum := 0\n\targLen := len(args)\n\tbuf := \"\"\n\n\tfor i := 0; i \u003c end; {\n\t\tisLast := i == end-1\n\t\tc := string(sTor[i])\n\n\t\tif isLast || c != \"%\" {\n\t\t\t// we don't check for invalid format like a one ending with \"%\"\n\t\t\tbuf += string(c)\n\t\t\ti++\n\t\t\tcontinue\n\t\t}\n\n\t\tverb := string(sTor[i+1])\n\t\tif verb == \"%\" {\n\t\t\tbuf += \"%\"\n\t\t\ti += 2\n\t\t\tcontinue\n\t\t}\n\n\t\tif argNum \u003e argLen {\n\t\t\tpanic(\"invalid number of arguments to ufmt.Sprintf\")\n\t\t}\n\t\targ := args[argNum]\n\t\targNum++\n\n\t\tswitch verb {\n\t\tcase \"s\":\n\t\t\tswitch v := arg.(type) {\n\t\t\tcase interface{ String() string }:\n\t\t\t\tbuf += v.String()\n\t\t\tcase error:\n\t\t\t\tbuf += v.Error()\n\t\t\tcase string:\n\t\t\t\tbuf += v\n\t\t\tdefault:\n\t\t\t\tbuf += fallback(verb, v)\n\t\t\t}\n\t\tcase \"c\":\n\t\t\tswitch v := arg.(type) {\n\t\t\t// rune is int32. Exclude overflowing numeric types and dups (byte, int32):\n\t\t\tcase rune:\n\t\t\t\tbuf += string(v)\n\t\t\tcase int:\n\t\t\t\tbuf += string(v)\n\t\t\tcase int8:\n\t\t\t\tbuf += string(v)\n\t\t\tcase int16:\n\t\t\t\tbuf += string(v)\n\t\t\tcase uint:\n\t\t\t\tbuf += string(v)\n\t\t\tcase uint8:\n\t\t\t\tbuf += string(v)\n\t\t\tcase uint16:\n\t\t\t\tbuf += string(v)\n\t\t\tdefault:\n\t\t\t\tbuf += fallback(verb, v)\n\t\t\t}\n\t\tcase \"d\":\n\t\t\tswitch v := arg.(type) {\n\t\t\tcase int:\n\t\t\t\tbuf += strconv.Itoa(v)\n\t\t\tcase int8:\n\t\t\t\tbuf += strconv.Itoa(int(v))\n\t\t\tcase int16:\n\t\t\t\tbuf += strconv.Itoa(int(v))\n\t\t\tcase int32:\n\t\t\t\tbuf += strconv.Itoa(int(v))\n\t\t\tcase int64:\n\t\t\t\tbuf += strconv.Itoa(int(v))\n\t\t\tcase uint:\n\t\t\t\tbuf += strconv.FormatUint(uint64(v), 10)\n\t\t\tcase uint8:\n\t\t\t\tbuf += strconv.FormatUint(uint64(v), 10)\n\t\t\tcase uint16:\n\t\t\t\tbuf += strconv.FormatUint(uint64(v), 10)\n\t\t\tcase uint32:\n\t\t\t\tbuf += strconv.FormatUint(uint64(v), 10)\n\t\t\tcase uint64:\n\t\t\t\tbuf += strconv.FormatUint(v, 10)\n\t\t\tdefault:\n\t\t\t\tbuf += fallback(verb, v)\n\t\t\t}\n\t\tcase \"t\":\n\t\t\tswitch v := arg.(type) {\n\t\t\tcase bool:\n\t\t\t\tif v {\n\t\t\t\t\tbuf += \"true\"\n\t\t\t\t} else {\n\t\t\t\t\tbuf += \"false\"\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\tbuf += fallback(verb, v)\n\t\t\t}\n\t\tcase \"x\":\n\t\t\tswitch v := arg.(type) {\n\t\t\tcase uint8:\n\t\t\t\tbuf += strconv.FormatUint(uint64(v), 16)\n\t\t\tdefault:\n\t\t\t\tbuf += \"(unhandled)\"\n\t\t\t}\n\t\tcase \"q\":\n\t\t\tswitch v := arg.(type) {\n\t\t\tcase string:\n\t\t\t\tbuf += strconv.Quote(v)\n\t\t\tdefault:\n\t\t\t\tbuf += \"(unhandled)\"\n\t\t\t}\n\t\tcase \"T\":\n\t\t\tswitch arg.(type) {\n\t\t\tcase bool:\n\t\t\t\tbuf += \"bool\"\n\t\t\tcase int:\n\t\t\t\tbuf += \"int\"\n\t\t\tcase int8:\n\t\t\t\tbuf += \"int8\"\n\t\t\tcase int16:\n\t\t\t\tbuf += \"int16\"\n\t\t\tcase int32:\n\t\t\t\tbuf += \"int32\"\n\t\t\tcase int64:\n\t\t\t\tbuf += \"int64\"\n\t\t\tcase uint:\n\t\t\t\tbuf += \"uint\"\n\t\t\tcase uint8:\n\t\t\t\tbuf += \"uint8\"\n\t\t\tcase uint16:\n\t\t\t\tbuf += \"uint16\"\n\t\t\tcase uint32:\n\t\t\t\tbuf += \"uint32\"\n\t\t\tcase uint64:\n\t\t\t\tbuf += \"uint64\"\n\t\t\tcase string:\n\t\t\t\tbuf += \"string\"\n\t\t\tcase []byte:\n\t\t\t\tbuf += \"[]byte\"\n\t\t\tcase []rune:\n\t\t\t\tbuf += \"[]rune\"\n\t\t\tdefault:\n\t\t\t\tbuf += \"unknown\"\n\t\t\t}\n\t\t// % handled before, as it does not consume an argument\n\t\tdefault:\n\t\t\tbuf += \"(unhandled verb: %\" + verb + \")\"\n\t\t}\n\n\t\ti += 2\n\t}\n\tif argNum \u003c argLen {\n\t\tpanic(\"too many arguments to ufmt.Sprintf\")\n\t}\n\treturn buf\n}\n\n// This function is used to mimic Go's fmt.Sprintf\n// specific behaviour of showing verb/type mismatches,\n// where for example:\n//\n//\tfmt.Sprintf(\"%d\", \"foo\") gives \"%!d(string=foo)\"\n//\n// Here:\n//\n//\tfallback(\"s\", 8) -\u003e \"%!s(int=8)\"\n//\tfallback(\"d\", nil) -\u003e \"%!d(\u003cnil\u003e)\", and so on.\nfunc fallback(verb string, arg interface{}) string {\n\tvar s string\n\tswitch v := arg.(type) {\n\tcase string:\n\t\ts = \"string=\" + v\n\tcase (interface{ String() string }):\n\t\ts = \"string=\" + v.String()\n\tcase error:\n\t\t// note: also \"string=\" in Go fmt\n\t\ts = \"string=\" + v.Error()\n\tcase int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:\n\t\t// note: rune, byte would be dups, being aliases\n\t\tif typename, e := typeToString(v); e != nil {\n\t\t\tpanic(\"should not happen\")\n\t\t} else {\n\t\t\ts = typename + \"=\" + Sprintf(\"%d\", v)\n\t\t}\n\tcase bool:\n\t\tif v {\n\t\t\ts = \"bool=true\"\n\t\t} else {\n\t\t\ts = \"bool=false\"\n\t\t}\n\tcase nil:\n\t\ts = \"\u003cnil\u003e\"\n\tdefault:\n\t\ts = \"(unhandled)\"\n\t}\n\treturn \"%!\" + verb + \"(\" + s + \")\"\n}\n\n// Get the name of the type of `v` as a string.\n// The recognized type of v is currently limited to native non-composite types.\n// An error is returned otherwise.\nfunc typeToString(v interface{}) (string, error) {\n\tswitch v.(type) {\n\tcase string:\n\t\treturn \"string\", nil\n\tcase int:\n\t\treturn \"int\", nil\n\tcase int8:\n\t\treturn \"int8\", nil\n\tcase int16:\n\t\treturn \"int16\", nil\n\tcase int32:\n\t\treturn \"int32\", nil\n\tcase int64:\n\t\treturn \"int64\", nil\n\tcase uint:\n\t\treturn \"uint\", nil\n\tcase uint8:\n\t\treturn \"uint8\", nil\n\tcase uint16:\n\t\treturn \"uint16\", nil\n\tcase uint32:\n\t\treturn \"uint32\", nil\n\tcase uint64:\n\t\treturn \"uint64\", nil\n\tcase float32:\n\t\treturn \"float32\", nil\n\tcase float64:\n\t\treturn \"float64\", nil\n\tcase bool:\n\t\treturn \"bool\", nil\n\tdefault:\n\t\treturn \"\", errors.New(\"(unsupported type)\")\n\t}\n}\n\n// errMsg implements the error interface.\ntype errMsg struct {\n\tmsg string\n}\n\n// Error defines the requirements of the error interface.\n// It functions similarly to Go's errors.New()\nfunc (e *errMsg) Error() string {\n\treturn e.msg\n}\n\n// Errorf is a function that mirrors the functionality of fmt.Errorf.\n//\n// It takes a format string and arguments to create a formatted string,\n// then sets this string as the 'msg' field of an errMsg struct and returns a pointer to this struct.\n//\n// This function operates in a similar manner to Go's fmt.Errorf,\n// providing a way to create formatted error messages.\n//\n// The currently formatted verbs are the following:\n//\n//\t%s: places a string value directly.\n//\t If the value implements the interface interface{ String() string },\n//\t the String() method is called to retrieve the value. Same for error.\n//\t%c: formats the character represented by Unicode code point\n//\t%d: formats an integer value using package \"strconv\".\n//\t Currently supports only uint, uint64, int, int64.\n//\t%t: formats a boolean value to \"true\" or \"false\".\n//\t%%: outputs a literal %. Does not consume an argument.\nfunc Errorf(format string, args ...interface{}) error {\n\treturn \u0026errMsg{Sprintf(format, args...)}\n}\n"},{"name":"ufmt_test.gno","body":"package ufmt\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"testing\"\n)\n\ntype stringer struct{}\n\nfunc (stringer) String() string {\n\treturn \"I'm a stringer\"\n}\n\nfunc TestSprintf(t *testing.T) {\n\ttru := true\n\tcases := []struct {\n\t\tformat string\n\t\tvalues []interface{}\n\t\texpectedOutput string\n\t}{\n\t\t{\"hello %s!\", []interface{}{\"planet\"}, \"hello planet!\"},\n\t\t{\"hi %%%s!\", []interface{}{\"worl%d\"}, \"hi %worl%d!\"},\n\t\t{\"%s %c %d %t\", []interface{}{\"foo\", 'α', 421, true}, \"foo α 421 true\"},\n\t\t{\"string [%s]\", []interface{}{\"foo\"}, \"string [foo]\"},\n\t\t{\"int [%d]\", []interface{}{int(42)}, \"int [42]\"},\n\t\t{\"int8 [%d]\", []interface{}{int8(8)}, \"int8 [8]\"},\n\t\t{\"int16 [%d]\", []interface{}{int16(16)}, \"int16 [16]\"},\n\t\t{\"int32 [%d]\", []interface{}{int32(32)}, \"int32 [32]\"},\n\t\t{\"int64 [%d]\", []interface{}{int64(64)}, \"int64 [64]\"},\n\t\t{\"uint [%d]\", []interface{}{uint(42)}, \"uint [42]\"},\n\t\t{\"uint8 [%d]\", []interface{}{uint8(8)}, \"uint8 [8]\"},\n\t\t{\"uint16 [%d]\", []interface{}{uint16(16)}, \"uint16 [16]\"},\n\t\t{\"uint32 [%d]\", []interface{}{uint32(32)}, \"uint32 [32]\"},\n\t\t{\"uint64 [%d]\", []interface{}{uint64(64)}, \"uint64 [64]\"},\n\t\t{\"bool [%t]\", []interface{}{true}, \"bool [true]\"},\n\t\t{\"bool [%t]\", []interface{}{false}, \"bool [false]\"},\n\t\t{\"no args\", nil, \"no args\"},\n\t\t{\"finish with %\", nil, \"finish with %\"},\n\t\t{\"stringer [%s]\", []interface{}{stringer{}}, \"stringer [I'm a stringer]\"},\n\t\t{\"â\", nil, \"â\"},\n\t\t{\"Hello, World! 😊\", nil, \"Hello, World! 😊\"},\n\t\t{\"unicode formatting: %s\", []interface{}{\"😊\"}, \"unicode formatting: 😊\"},\n\t\t{\"invalid hex [%x]\", []interface{}{\"invalid\"}, \"invalid hex [(unhandled)]\"},\n\t\t{\"rune as character [%c]\", []interface{}{rune('A')}, \"rune as character [A]\"},\n\t\t{\"int as character [%c]\", []interface{}{int('B')}, \"int as character [B]\"},\n\t\t{\"quoted string [%q]\", []interface{}{\"hello\"}, \"quoted string [\\\"hello\\\"]\"},\n\t\t{\"quoted string with escape [%q]\", []interface{}{\"\\thello\\nworld\\\\\"}, \"quoted string with escape [\\\"\\\\thello\\\\nworld\\\\\\\\\\\"]\"},\n\t\t{\"invalid quoted string [%q]\", []interface{}{123}, \"invalid quoted string [(unhandled)]\"},\n\t\t{\"type of bool [%T]\", []interface{}{true}, \"type of bool [bool]\"},\n\t\t{\"type of int [%T]\", []interface{}{123}, \"type of int [int]\"},\n\t\t{\"type of string [%T]\", []interface{}{\"hello\"}, \"type of string [string]\"},\n\t\t{\"type of []byte [%T]\", []interface{}{[]byte{1, 2, 3}}, \"type of []byte [[]byte]\"},\n\t\t{\"type of []rune [%T]\", []interface{}{[]rune{'a', 'b', 'c'}}, \"type of []rune [[]rune]\"},\n\t\t{\"type of unknown [%T]\", []interface{}{struct{}{}}, \"type of unknown [unknown]\"},\n\t\t// mismatch printing\n\t\t{\"%s\", []interface{}{nil}, \"%!s(\u003cnil\u003e)\"},\n\t\t{\"%s\", []interface{}{421}, \"%!s(int=421)\"},\n\t\t{\"%s\", []interface{}{\"z\"}, \"z\"},\n\t\t{\"%s\", []interface{}{tru}, \"%!s(bool=true)\"},\n\t\t{\"%s\", []interface{}{'z'}, \"%!s(int32=122)\"},\n\n\t\t{\"%c\", []interface{}{nil}, \"%!c(\u003cnil\u003e)\"},\n\t\t{\"%c\", []interface{}{421}, \"ƥ\"},\n\t\t{\"%c\", []interface{}{\"z\"}, \"%!c(string=z)\"},\n\t\t{\"%c\", []interface{}{tru}, \"%!c(bool=true)\"},\n\t\t{\"%c\", []interface{}{'z'}, \"z\"},\n\n\t\t{\"%d\", []interface{}{nil}, \"%!d(\u003cnil\u003e)\"},\n\t\t{\"%d\", []interface{}{421}, \"421\"},\n\t\t{\"%d\", []interface{}{\"z\"}, \"%!d(string=z)\"},\n\t\t{\"%d\", []interface{}{tru}, \"%!d(bool=true)\"},\n\t\t{\"%d\", []interface{}{'z'}, \"122\"},\n\n\t\t{\"%t\", []interface{}{nil}, \"%!t(\u003cnil\u003e)\"},\n\t\t{\"%t\", []interface{}{421}, \"%!t(int=421)\"},\n\t\t{\"%t\", []interface{}{\"z\"}, \"%!t(string=z)\"},\n\t\t{\"%t\", []interface{}{tru}, \"true\"},\n\t\t{\"%t\", []interface{}{'z'}, \"%!t(int32=122)\"},\n\t}\n\n\tfor _, tc := range cases {\n\t\tname := fmt.Sprintf(tc.format, tc.values...)\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tgot := Sprintf(tc.format, tc.values...)\n\t\t\tif got != tc.expectedOutput {\n\t\t\t\tt.Errorf(\"got %q, want %q.\", got, tc.expectedOutput)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestErrorf(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tformat string\n\t\targs []interface{}\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname: \"simple string\",\n\t\t\tformat: \"error: %s\",\n\t\t\targs: []interface{}{\"something went wrong\"},\n\t\t\texpected: \"error: something went wrong\",\n\t\t},\n\t\t{\n\t\t\tname: \"integer value\",\n\t\t\tformat: \"value: %d\",\n\t\t\targs: []interface{}{42},\n\t\t\texpected: \"value: 42\",\n\t\t},\n\t\t{\n\t\t\tname: \"boolean value\",\n\t\t\tformat: \"success: %t\",\n\t\t\targs: []interface{}{true},\n\t\t\texpected: \"success: true\",\n\t\t},\n\t\t{\n\t\t\tname: \"multiple values\",\n\t\t\tformat: \"error %d: %s (success=%t)\",\n\t\t\targs: []interface{}{123, \"failure occurred\", false},\n\t\t\texpected: \"error 123: failure occurred (success=false)\",\n\t\t},\n\t\t{\n\t\t\tname: \"literal percent\",\n\t\t\tformat: \"literal %%\",\n\t\t\targs: []interface{}{},\n\t\t\texpected: \"literal %\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\terr := Errorf(tt.format, tt.args...)\n\t\t\tif err.Error() != tt.expected {\n\t\t\t\tt.Errorf(\"Errorf(%q, %v) = %q, expected %q\", tt.format, tt.args, err.Error(), tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestPrintErrors(t *testing.T) {\n\tgot := Sprintf(\"error: %s\", errors.New(\"can I be printed?\"))\n\texpectedOutput := \"error: can I be printed?\"\n\tif got != expectedOutput {\n\t\tt.Errorf(\"got %q, want %q.\", got, expectedOutput)\n\t}\n}\n\n// NOTE: Currently, there is no way to get the output of Println without using os.Stdout,\n// so we can only test that it doesn't panic and print arguments well.\nfunc TestPrintln(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\targs []interface{}\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname: \"Empty args\",\n\t\t\targs: []interface{}{},\n\t\t\texpected: \"\",\n\t\t},\n\t\t{\n\t\t\tname: \"String args\",\n\t\t\targs: []interface{}{\"Hello\", \"World\"},\n\t\t\texpected: \"Hello World\",\n\t\t},\n\t\t{\n\t\t\tname: \"Integer args\",\n\t\t\targs: []interface{}{1, 2, 3},\n\t\t\texpected: \"1 2 3\",\n\t\t},\n\t\t{\n\t\t\tname: \"Mixed args\",\n\t\t\targs: []interface{}{\"Hello\", 42, true, false, \"World\"},\n\t\t\texpected: \"Hello 42 true false World\",\n\t\t},\n\t\t{\n\t\t\tname: \"Unhandled type\",\n\t\t\targs: []interface{}{\"Hello\", 3.14, []int{1, 2, 3}},\n\t\t\texpected: \"Hello (unhandled) (unhandled)\",\n\t\t},\n\t}\n\n\t// TODO: replace os.Stdout with a buffer to capture the output and test it.\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tPrintln(tt.args...)\n\t\t})\n\t}\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}} @@ -16,7 +16,7 @@ {"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"context","path":"gno.land/p/demo/context","files":[{"name":"context.gno","body":"// Package context provides a minimal implementation of Go context with support\n// for Value and WithValue.\n//\n// Adapted from https://github.com/golang/go/tree/master/src/context/.\n// Copyright 2016 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\npackage context\n\ntype Context interface {\n\t// Value returns the value associated with this context for key, or nil\n\t// if no value is associated with key.\n\tValue(key interface{}) interface{}\n}\n\n// Empty returns a non-nil, empty context, similar with context.Background and\n// context.TODO in Go.\nfunc Empty() Context {\n\treturn \u0026emptyCtx{}\n}\n\ntype emptyCtx struct{}\n\nfunc (ctx emptyCtx) Value(key interface{}) interface{} {\n\treturn nil\n}\n\nfunc (ctx emptyCtx) String() string {\n\treturn \"context.Empty\"\n}\n\ntype valueCtx struct {\n\tparent Context\n\tkey, val interface{}\n}\n\nfunc (ctx *valueCtx) Value(key interface{}) interface{} {\n\tif ctx.key == key {\n\t\treturn ctx.val\n\t}\n\treturn ctx.parent.Value(key)\n}\n\nfunc stringify(v interface{}) string {\n\tswitch s := v.(type) {\n\tcase stringer:\n\t\treturn s.String()\n\tcase string:\n\t\treturn s\n\t}\n\treturn \"non-stringer\"\n}\n\ntype stringer interface {\n\tString() string\n}\n\nfunc (c *valueCtx) String() string {\n\treturn stringify(c.parent) + \".WithValue(\" +\n\t\tstringify(c.key) + \", \" +\n\t\tstringify(c.val) + \")\"\n}\n\n// WithValue returns a copy of parent in which the value associated with key is\n// val.\nfunc WithValue(parent Context, key, val interface{}) Context {\n\tif key == nil {\n\t\tpanic(\"nil key\")\n\t}\n\t// XXX: if !reflect.TypeOf(key).Comparable() { panic(\"key is not comparable\") }\n\treturn \u0026valueCtx{parent, key, val}\n}\n"},{"name":"context_test.gno","body":"package context\n\nimport \"testing\"\n\nfunc TestContextExample(t *testing.T) {\n\ttype favContextKey string\n\n\tk := favContextKey(\"language\")\n\tctx := WithValue(Empty(), k, \"Gno\")\n\n\tif v := ctx.Value(k); v != nil {\n\t\tif string(v) != \"Gno\" {\n\t\t\tt.Errorf(\"language value should be Gno, but is %s\", v)\n\t\t}\n\t} else {\n\t\tt.Errorf(\"language key value was not found\")\n\t}\n\n\tif v := ctx.Value(favContextKey(\"color\")); v != nil {\n\t\tt.Errorf(\"color key was found\")\n\t}\n}\n\n// otherContext is a Context that's not one of the types defined in context.go.\n// This lets us test code paths that differ based on the underlying type of the\n// Context.\ntype otherContext struct {\n\tContext\n}\n\ntype (\n\tkey1 int\n\tkey2 int\n)\n\n// func (k key2) String() string { return fmt.Sprintf(\"%[1]T(%[1]d)\", k) }\n\nvar (\n\tk1 = key1(1)\n\tk2 = key2(1) // same int as k1, different type\n\tk3 = key2(3) // same type as k2, different int\n)\n\nfunc TestValues(t *testing.T) {\n\tcheck := func(c Context, nm, v1, v2, v3 string) {\n\t\tif v, ok := c.Value(k1).(string); ok == (len(v1) == 0) || v != v1 {\n\t\t\tt.Errorf(`%s.Value(k1).(string) = %q, %t want %q, %t`, nm, v, ok, v1, len(v1) != 0)\n\t\t}\n\t\tif v, ok := c.Value(k2).(string); ok == (len(v2) == 0) || v != v2 {\n\t\t\tt.Errorf(`%s.Value(k2).(string) = %q, %t want %q, %t`, nm, v, ok, v2, len(v2) != 0)\n\t\t}\n\t\tif v, ok := c.Value(k3).(string); ok == (len(v3) == 0) || v != v3 {\n\t\t\tt.Errorf(`%s.Value(k3).(string) = %q, %t want %q, %t`, nm, v, ok, v3, len(v3) != 0)\n\t\t}\n\t}\n\n\tc0 := Empty()\n\tcheck(c0, \"c0\", \"\", \"\", \"\")\n\n\tt.Skip() // XXX: depends on https://github.com/gnolang/gno/issues/2386\n\n\tc1 := WithValue(Empty(), k1, \"c1k1\")\n\tcheck(c1, \"c1\", \"c1k1\", \"\", \"\")\n\n\t/*if got, want := c1.String(), `context.Empty.WithValue(context_test.key1, c1k1)`; got != want {\n\t\tt.Errorf(\"c.String() = %q want %q\", got, want)\n\t}*/\n\n\tc2 := WithValue(c1, k2, \"c2k2\")\n\tcheck(c2, \"c2\", \"c1k1\", \"c2k2\", \"\")\n\n\t/*if got, want := fmt.Sprint(c2), `context.Empty.WithValue(context_test.key1, c1k1).WithValue(context_test.key2(1), c2k2)`; got != want {\n\t\tt.Errorf(\"c.String() = %q want %q\", got, want)\n\t}*/\n\n\tc3 := WithValue(c2, k3, \"c3k3\")\n\tcheck(c3, \"c2\", \"c1k1\", \"c2k2\", \"c3k3\")\n\n\tc4 := WithValue(c3, k1, nil)\n\tcheck(c4, \"c4\", \"\", \"c2k2\", \"c3k3\")\n\n\to0 := otherContext{Empty()}\n\tcheck(o0, \"o0\", \"\", \"\", \"\")\n\n\to1 := otherContext{WithValue(Empty(), k1, \"c1k1\")}\n\tcheck(o1, \"o1\", \"c1k1\", \"\", \"\")\n\n\to2 := WithValue(o1, k2, \"o2k2\")\n\tcheck(o2, \"o2\", \"c1k1\", \"o2k2\", \"\")\n\n\to3 := otherContext{c4}\n\tcheck(o3, \"o3\", \"\", \"c2k2\", \"c3k3\")\n\n\to4 := WithValue(o3, k3, nil)\n\tcheck(o4, \"o4\", \"\", \"c2k2\", \"\")\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":"dao","path":"gno.land/p/demo/dao","files":[{"name":"dao.gno","body":"package dao\n\nconst (\n\tProposalAddedEvent = \"ProposalAdded\" // emitted when a new proposal has been added\n\tProposalAcceptedEvent = \"ProposalAccepted\" // emitted when a proposal has been accepted\n\tProposalNotAcceptedEvent = \"ProposalNotAccepted\" // emitted when a proposal has not been accepted\n\tProposalExecutedEvent = \"ProposalExecuted\" // emitted when a proposal has been executed\n\n\tProposalEventIDKey = \"proposal-id\"\n\tProposalEventAuthorKey = \"proposal-author\"\n\tProposalEventExecutionKey = \"exec-status\"\n)\n\n// ProposalRequest is a single govdao proposal request\n// that contains the necessary information to\n// log and generate a valid proposal\ntype ProposalRequest struct {\n\tDescription string // the description associated with the proposal\n\tExecutor Executor // the proposal executor\n}\n\n// DAO defines the DAO abstraction\ntype DAO interface {\n\t// PropStore is the DAO proposal storage\n\tPropStore\n\n\t// Propose adds a new proposal to the executor-based GOVDAO.\n\t// Returns the generated proposal ID\n\tPropose(request ProposalRequest) (uint64, error)\n\n\t// ExecuteProposal executes the proposal with the given ID\n\tExecuteProposal(id uint64) error\n}\n"},{"name":"doc.gno","body":"// Package dao houses common DAO building blocks (framework), which can be used or adopted by any\n// specific DAO implementation. By design, the DAO should house the proposals it receives, but not the actual\n// DAO members or proposal votes. These abstractions should be implemented by a separate entity, to keep the DAO\n// agnostic of implementation details such as these (member / vote management).\npackage dao\n"},{"name":"events.gno","body":"package dao\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// EmitProposalAdded emits an event signaling that\n// a given proposal was added\nfunc EmitProposalAdded(id uint64, proposer std.Address) {\n\tstd.Emit(\n\t\tProposalAddedEvent,\n\t\tProposalEventIDKey, ufmt.Sprintf(\"%d\", id),\n\t\tProposalEventAuthorKey, proposer.String(),\n\t)\n}\n\n// EmitProposalAccepted emits an event signaling that\n// a given proposal was accepted\nfunc EmitProposalAccepted(id uint64) {\n\tstd.Emit(\n\t\tProposalAcceptedEvent,\n\t\tProposalEventIDKey, ufmt.Sprintf(\"%d\", id),\n\t)\n}\n\n// EmitProposalNotAccepted emits an event signaling that\n// a given proposal was not accepted\nfunc EmitProposalNotAccepted(id uint64) {\n\tstd.Emit(\n\t\tProposalNotAcceptedEvent,\n\t\tProposalEventIDKey, ufmt.Sprintf(\"%d\", id),\n\t)\n}\n\n// EmitProposalExecuted emits an event signaling that\n// a given proposal was executed, with the given status\nfunc EmitProposalExecuted(id uint64, status ProposalStatus) {\n\tstd.Emit(\n\t\tProposalExecutedEvent,\n\t\tProposalEventIDKey, ufmt.Sprintf(\"%d\", id),\n\t\tProposalEventExecutionKey, status.String(),\n\t)\n}\n\n// EmitVoteAdded emits an event signaling that\n// a vote was cast for a given proposal\nfunc EmitVoteAdded(id uint64, voter std.Address, option VoteOption) {\n\tstd.Emit(\n\t\tVoteAddedEvent,\n\t\tVoteAddedIDKey, ufmt.Sprintf(\"%d\", id),\n\t\tVoteAddedAuthorKey, voter.String(),\n\t\tVoteAddedOptionKey, option.String(),\n\t)\n}\n"},{"name":"executor.gno","body":"package dao\n\n// Executor represents a minimal closure-oriented proposal design.\n// It is intended to be used by a govdao governance proposal (v1, v2, etc)\ntype Executor interface {\n\t// Execute executes the given proposal, and returns any error encountered\n\t// during the execution\n\tExecute() error\n}\n"},{"name":"proposals.gno","body":"package dao\n\nimport \"std\"\n\n// ProposalStatus is the currently active proposal status,\n// changed based on DAO functionality.\n// Status transitions:\n//\n// ACTIVE -\u003e ACCEPTED -\u003e EXECUTION(SUCCEEDED/FAILED)\n//\n// ACTIVE -\u003e NOT ACCEPTED\ntype ProposalStatus string\n\nvar (\n\tActive ProposalStatus = \"active\" // proposal is still active\n\tAccepted ProposalStatus = \"accepted\" // proposal gathered quorum\n\tNotAccepted ProposalStatus = \"not accepted\" // proposal failed to gather quorum\n\tExecutionSuccessful ProposalStatus = \"execution successful\" // proposal is executed successfully\n\tExecutionFailed ProposalStatus = \"execution failed\" // proposal is failed during execution\n)\n\nfunc (s ProposalStatus) String() string {\n\treturn string(s)\n}\n\n// PropStore defines the proposal storage abstraction\ntype PropStore interface {\n\t// Proposals returns the given paginated proposals\n\tProposals(offset, count uint64) []Proposal\n\n\t// ProposalByID returns the proposal associated with\n\t// the given ID, if any\n\tProposalByID(id uint64) (Proposal, error)\n\n\t// Size returns the number of proposals in\n\t// the proposal store\n\tSize() int\n}\n\n// Proposal is the single proposal abstraction\ntype Proposal interface {\n\t// Author returns the author of the proposal\n\tAuthor() std.Address\n\n\t// Description returns the description of the proposal\n\tDescription() string\n\n\t// Status returns the status of the proposal\n\tStatus() ProposalStatus\n\n\t// Executor returns the proposal executor\n\tExecutor() Executor\n\n\t// Stats returns the voting stats of the proposal\n\tStats() Stats\n\n\t// IsExpired returns a flag indicating if the proposal expired\n\tIsExpired() bool\n\n\t// Render renders the proposal in a readable format\n\tRender() string\n}\n"},{"name":"vote.gno","body":"package dao\n\n// NOTE:\n// This voting pods will be removed in a future version of the\n// p/demo/dao package. A DAO shouldn't have to comply with or define how the voting mechanism works internally;\n// it should be viewed as an entity that makes decisions\n//\n// The extent of \"votes being enforced\" in this implementation is just in the context\n// of types a DAO can use (import), and in the context of \"Stats\", where\n// there is a notion of \"Yay\", \"Nay\" and \"Abstain\" votes.\nconst (\n\tVoteAddedEvent = \"VoteAdded\" // emitted when a vote was cast for a proposal\n\n\tVoteAddedIDKey = \"proposal-id\"\n\tVoteAddedAuthorKey = \"author\"\n\tVoteAddedOptionKey = \"option\"\n)\n\n// VoteOption is the limited voting option for a DAO proposal\ntype VoteOption string\n\nconst (\n\tYesVote VoteOption = \"YES\" // Proposal should be accepted\n\tNoVote VoteOption = \"NO\" // Proposal should be rejected\n\tAbstainVote VoteOption = \"ABSTAIN\" // Side is not chosen\n)\n\nfunc (v VoteOption) String() string {\n\treturn string(v)\n}\n\n// Stats encompasses the proposal voting stats\ntype Stats struct {\n\tYayVotes uint64\n\tNayVotes uint64\n\tAbstainVotes uint64\n\n\tTotalVotingPower uint64\n}\n\n// YayPercent returns the percentage (0-100) of the yay votes\n// in relation to the total voting power\nfunc (v Stats) YayPercent() uint64 {\n\treturn v.YayVotes * 100 / v.TotalVotingPower\n}\n\n// NayPercent returns the percentage (0-100) of the nay votes\n// in relation to the total voting power\nfunc (v Stats) NayPercent() uint64 {\n\treturn v.NayVotes * 100 / v.TotalVotingPower\n}\n\n// AbstainPercent returns the percentage (0-100) of the abstain votes\n// in relation to the total voting power\nfunc (v Stats) AbstainPercent() uint64 {\n\treturn v.AbstainVotes * 100 / v.TotalVotingPower\n}\n\n// MissingVotes returns the summed voting power that has not\n// participated in proposal voting yet\nfunc (v Stats) MissingVotes() uint64 {\n\treturn v.TotalVotingPower - (v.YayVotes + v.NayVotes + v.AbstainVotes)\n}\n\n// MissingVotesPercent returns the percentage (0-100) of the missing votes\n// in relation to the total voting power\nfunc (v Stats) MissingVotesPercent() uint64 {\n\treturn v.MissingVotes() * 100 / v.TotalVotingPower\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":"dom","path":"gno.land/p/demo/dom","files":[{"name":"dom.gno","body":"// XXX This is only used for testing in ./tests.\n// Otherwise this package is deprecated.\n// TODO: replace with a package that is supported, and delete this.\n\npackage dom\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\ntype Plot struct {\n\tName string\n\tPosts avl.Tree // postsCtr -\u003e *Post\n\tPostsCtr int\n}\n\nfunc (plot *Plot) AddPost(title string, body string) {\n\tctr := plot.PostsCtr\n\tplot.PostsCtr++\n\tkey := strconv.Itoa(ctr)\n\tpost := \u0026Post{\n\t\tTitle: title,\n\t\tBody: body,\n\t}\n\tplot.Posts.Set(key, post)\n}\n\nfunc (plot *Plot) String() string {\n\tstr := \"# [plot] \" + plot.Name + \"\\n\"\n\tif plot.Posts.Size() \u003e 0 {\n\t\tplot.Posts.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tstr += \"\\n\"\n\t\t\tstr += value.(*Post).String()\n\t\t\treturn false\n\t\t})\n\t}\n\treturn str\n}\n\ntype Post struct {\n\tTitle string\n\tBody string\n\tComments avl.Tree\n}\n\nfunc (post *Post) String() string {\n\tstr := \"## \" + post.Title + \"\\n\"\n\tstr += \"\"\n\tstr += post.Body\n\tif post.Comments.Size() \u003e 0 {\n\t\tpost.Comments.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tstr += \"\\n\"\n\t\t\tstr += value.(*Comment).String()\n\t\t\treturn false\n\t\t})\n\t}\n\treturn str\n}\n\ntype Comment struct {\n\tCreator string\n\tBody string\n}\n\nfunc (cmm Comment) String() string {\n\treturn cmm.Body + \" - @\" + cmm.Creator + \"\\n\"\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":"entropy","path":"gno.land/p/demo/entropy","files":[{"name":"entropy.gno","body":"// Entropy generates fully deterministic, cost-effective, and hard to guess\n// numbers.\n//\n// It is designed both for single-usage, like seeding math/rand or for being\n// reused which increases the entropy and its cost effectiveness.\n//\n// Disclaimer: this package is unsafe and won't prevent others to guess values\n// in advance.\n//\n// It uses the Bernstein's hash djb2 to be CPU-cycle efficient.\npackage entropy\n\nimport (\n\t\"math\"\n\t\"std\"\n\t\"time\"\n)\n\ntype Instance struct {\n\tvalue uint32\n}\n\nfunc New() *Instance {\n\tr := Instance{value: 5381}\n\tr.addEntropy()\n\treturn \u0026r\n}\n\nfunc FromSeed(seed uint32) *Instance {\n\tr := Instance{value: seed}\n\tr.addEntropy()\n\treturn \u0026r\n}\n\nfunc (i *Instance) Seed() uint32 {\n\treturn i.value\n}\n\nfunc (i *Instance) djb2String(input string) {\n\tfor _, c := range input {\n\t\ti.djb2Uint32(uint32(c))\n\t}\n}\n\n// super fast random algorithm.\n// http://www.cse.yorku.ca/~oz/hash.html\nfunc (i *Instance) djb2Uint32(input uint32) {\n\ti.value = (i.value \u003c\u003c 5) + i.value + input\n}\n\n// AddEntropy uses various runtime variables to add entropy to the existing seed.\nfunc (i *Instance) addEntropy() {\n\t// FIXME: reapply the 5381 initial value?\n\n\t// inherit previous entropy\n\t// nothing to do\n\n\t// handle callers\n\t{\n\t\tcaller1 := std.GetCallerAt(1).String()\n\t\ti.djb2String(caller1)\n\t\tcaller2 := std.GetCallerAt(2).String()\n\t\ti.djb2String(caller2)\n\t}\n\n\t// height\n\t{\n\t\theight := std.GetHeight()\n\t\tif height \u003e= math.MaxUint32 {\n\t\t\theight -= math.MaxUint32\n\t\t}\n\t\ti.djb2Uint32(uint32(height))\n\t}\n\n\t// time\n\t{\n\t\tsecs := time.Now().Second()\n\t\ti.djb2Uint32(uint32(secs))\n\t\tnsecs := time.Now().Nanosecond()\n\t\ti.djb2Uint32(uint32(nsecs))\n\t}\n\n\t// FIXME: compute other hard-to-guess but deterministic variables, like real gas?\n}\n\nfunc (i *Instance) Value() uint32 {\n\ti.addEntropy()\n\treturn i.value\n}\n"},{"name":"entropy_test.gno","body":"package entropy\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"testing\"\n)\n\nfunc TestInstance(t *testing.T) {\n\tinstance := New()\n\tif instance == nil {\n\t\tt.Errorf(\"instance should not be nil\")\n\t}\n}\n\nfunc TestInstanceValue(t *testing.T) {\n\tbaseEntropy := New()\n\tbaseResult := computeValue(t, baseEntropy)\n\n\tsameHeightEntropy := New()\n\tsameHeightResult := computeValue(t, sameHeightEntropy)\n\n\tif baseResult != sameHeightResult {\n\t\tt.Errorf(\"should have the same result: new=%s, base=%s\", sameHeightResult, baseResult)\n\t}\n\n\tstd.TestSkipHeights(1)\n\tdifferentHeightEntropy := New()\n\tdifferentHeightResult := computeValue(t, differentHeightEntropy)\n\n\tif baseResult == differentHeightResult {\n\t\tt.Errorf(\"should have different result: new=%s, base=%s\", differentHeightResult, baseResult)\n\t}\n}\n\nfunc computeValue(t *testing.T, r *Instance) string {\n\tt.Helper()\n\n\tout := \"\"\n\tfor i := 0; i \u003c 10; i++ {\n\t\tval := int(r.Value())\n\t\tout += strconv.Itoa(val) + \" \"\n\t}\n\n\treturn out\n}\n"},{"name":"z_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/entropy\"\n)\n\nfunc main() {\n\t// initial\n\tprintln(\"---\")\n\tr := entropy.New()\n\tprintln(r.Value())\n\tprintln(r.Value())\n\tprintln(r.Value())\n\tprintln(r.Value())\n\tprintln(r.Value())\n\n\t// should be the same\n\tprintln(\"---\")\n\tr = entropy.New()\n\tprintln(r.Value())\n\tprintln(r.Value())\n\tprintln(r.Value())\n\tprintln(r.Value())\n\tprintln(r.Value())\n\n\tstd.TestSkipHeights(1)\n\tprintln(\"---\")\n\tr = entropy.New()\n\tprintln(r.Value())\n\tprintln(r.Value())\n\tprintln(r.Value())\n\tprintln(r.Value())\n\tprintln(r.Value())\n}\n\n// Output:\n// ---\n// 4129293727\n// 2141104956\n// 1950222777\n// 3348280598\n// 438354259\n// ---\n// 4129293727\n// 2141104956\n// 1950222777\n// 3348280598\n// 438354259\n// ---\n// 49506731\n// 1539580078\n// 2695928529\n// 1895482388\n// 3462727799\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":"entropy","path":"gno.land/p/demo/entropy","files":[{"name":"entropy.gno","body":"// Entropy generates fully deterministic, cost-effective, and hard to guess\n// numbers.\n//\n// It is designed both for single-usage, like seeding math/rand or for being\n// reused which increases the entropy and its cost effectiveness.\n//\n// Disclaimer: this package is unsafe and won't prevent others to guess values\n// in advance.\n//\n// It uses the Bernstein's hash djb2 to be CPU-cycle efficient.\npackage entropy\n\nimport (\n\t\"math\"\n\t\"std\"\n\t\"time\"\n)\n\ntype Instance struct {\n\tvalue uint32\n}\n\nfunc New() *Instance {\n\tr := Instance{value: 5381}\n\tr.addEntropy()\n\treturn \u0026r\n}\n\nfunc FromSeed(seed uint32) *Instance {\n\tr := Instance{value: seed}\n\tr.addEntropy()\n\treturn \u0026r\n}\n\nfunc (i *Instance) Seed() uint32 {\n\treturn i.value\n}\n\nfunc (i *Instance) djb2String(input string) {\n\tfor _, c := range input {\n\t\ti.djb2Uint32(uint32(c))\n\t}\n}\n\n// super fast random algorithm.\n// http://www.cse.yorku.ca/~oz/hash.html\nfunc (i *Instance) djb2Uint32(input uint32) {\n\ti.value = (i.value \u003c\u003c 5) + i.value + input\n}\n\n// AddEntropy uses various runtime variables to add entropy to the existing seed.\nfunc (i *Instance) addEntropy() {\n\t// FIXME: reapply the 5381 initial value?\n\n\t// inherit previous entropy\n\t// nothing to do\n\n\t// handle callers\n\t{\n\t\tcaller1 := std.CallerAt(1).String()\n\t\ti.djb2String(caller1)\n\t\tcaller2 := std.CallerAt(2).String()\n\t\ti.djb2String(caller2)\n\t}\n\n\t// height\n\t{\n\t\theight := std.GetHeight()\n\t\tif height \u003e= math.MaxUint32 {\n\t\t\theight -= math.MaxUint32\n\t\t}\n\t\ti.djb2Uint32(uint32(height))\n\t}\n\n\t// time\n\t{\n\t\tsecs := time.Now().Second()\n\t\ti.djb2Uint32(uint32(secs))\n\t\tnsecs := time.Now().Nanosecond()\n\t\ti.djb2Uint32(uint32(nsecs))\n\t}\n\n\t// FIXME: compute other hard-to-guess but deterministic variables, like real gas?\n}\n\nfunc (i *Instance) Value() uint32 {\n\ti.addEntropy()\n\treturn i.value\n}\n"},{"name":"entropy_test.gno","body":"package entropy\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"testing\"\n)\n\nfunc TestInstance(t *testing.T) {\n\tinstance := New()\n\tif instance == nil {\n\t\tt.Errorf(\"instance should not be nil\")\n\t}\n}\n\nfunc TestInstanceValue(t *testing.T) {\n\tbaseEntropy := New()\n\tbaseResult := computeValue(t, baseEntropy)\n\n\tsameHeightEntropy := New()\n\tsameHeightResult := computeValue(t, sameHeightEntropy)\n\n\tif baseResult != sameHeightResult {\n\t\tt.Errorf(\"should have the same result: new=%s, base=%s\", sameHeightResult, baseResult)\n\t}\n\n\tstd.TestSkipHeights(1)\n\tdifferentHeightEntropy := New()\n\tdifferentHeightResult := computeValue(t, differentHeightEntropy)\n\n\tif baseResult == differentHeightResult {\n\t\tt.Errorf(\"should have different result: new=%s, base=%s\", differentHeightResult, baseResult)\n\t}\n}\n\nfunc computeValue(t *testing.T, r *Instance) string {\n\tt.Helper()\n\n\tout := \"\"\n\tfor i := 0; i \u003c 10; i++ {\n\t\tval := int(r.Value())\n\t\tout += strconv.Itoa(val) + \" \"\n\t}\n\n\treturn out\n}\n"},{"name":"z_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/entropy\"\n)\n\nfunc main() {\n\t// initial\n\tprintln(\"---\")\n\tr := entropy.New()\n\tprintln(r.Value())\n\tprintln(r.Value())\n\tprintln(r.Value())\n\tprintln(r.Value())\n\tprintln(r.Value())\n\n\t// should be the same\n\tprintln(\"---\")\n\tr = entropy.New()\n\tprintln(r.Value())\n\tprintln(r.Value())\n\tprintln(r.Value())\n\tprintln(r.Value())\n\tprintln(r.Value())\n\n\tstd.TestSkipHeights(1)\n\tprintln(\"---\")\n\tr = entropy.New()\n\tprintln(r.Value())\n\tprintln(r.Value())\n\tprintln(r.Value())\n\tprintln(r.Value())\n\tprintln(r.Value())\n}\n\n// Output:\n// ---\n// 4129293727\n// 2141104956\n// 1950222777\n// 3348280598\n// 438354259\n// ---\n// 4129293727\n// 2141104956\n// 1950222777\n// 3348280598\n// 438354259\n// ---\n// 49506731\n// 1539580078\n// 2695928529\n// 1895482388\n// 3462727799\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":"flow","path":"gno.land/p/demo/flow","files":[{"name":"LICENSE","body":"https://github.com/mxk/go-flowrate/blob/master/LICENSE\nBSD 3-Clause \"New\" or \"Revised\" License\n\nCopyright (c) 2014 The Go-FlowRate Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\n notice, this list of conditions and the following disclaimer.\n\n * Redistributions in binary form must reproduce the above copyright\n notice, this list of conditions and the following disclaimer in the\n documentation and/or other materials provided with the\n distribution.\n\n * Neither the name of the go-flowrate project nor the names of its\n contributors may be used to endorse or promote products derived\n from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"},{"name":"README.md","body":"Data Flow Rate Control\n======================\n\nTo download and install this package run:\n\ngo get github.com/mxk/go-flowrate/flowrate\n\nThe documentation is available at:\n\nhttp://godoc.org/github.com/mxk/go-flowrate/flowrate\n"},{"name":"flow.gno","body":"//\n// Written by Maxim Khitrov (November 2012)\n//\n// XXX modified to disable blocking, time.Sleep().\n\n// Package flow provides the tools for monitoring and limiting the flow rate\n// of an arbitrary data stream.\npackage flow\n\nimport (\n\t\"math\"\n\t// \"sync\"\n\t\"time\"\n)\n\n// Monitor monitors and limits the transfer rate of a data stream.\ntype Monitor struct {\n\t// mu sync.Mutex // Mutex guarding access to all internal fields\n\tactive bool // Flag indicating an active transfer\n\tstart time.Duration // Transfer start time (clock() value)\n\tbytes int64 // Total number of bytes transferred\n\tsamples int64 // Total number of samples taken\n\n\trSample float64 // Most recent transfer rate sample (bytes per second)\n\trEMA float64 // Exponential moving average of rSample\n\trPeak float64 // Peak transfer rate (max of all rSamples)\n\trWindow float64 // rEMA window (seconds)\n\n\tsBytes int64 // Number of bytes transferred since sLast\n\tsLast time.Duration // Most recent sample time (stop time when inactive)\n\tsRate time.Duration // Sampling rate\n\n\ttBytes int64 // Number of bytes expected in the current transfer\n\ttLast time.Duration // Time of the most recent transfer of at least 1 byte\n}\n\n// New creates a new flow control monitor. Instantaneous transfer rate is\n// measured and updated for each sampleRate interval. windowSize determines the\n// weight of each sample in the exponential moving average (EMA) calculation.\n// The exact formulas are:\n//\n//\tsampleTime = currentTime - prevSampleTime\n//\tsampleRate = byteCount / sampleTime\n//\tweight = 1 - exp(-sampleTime/windowSize)\n//\tnewRate = weight*sampleRate + (1-weight)*oldRate\n//\n// The default values for sampleRate and windowSize (if \u003c= 0) are 100ms and 1s,\n// respectively.\nfunc New(sampleRate, windowSize time.Duration) *Monitor {\n\tif sampleRate = clockRound(sampleRate); sampleRate \u003c= 0 {\n\t\tsampleRate = 5 * clockRate\n\t}\n\tif windowSize \u003c= 0 {\n\t\twindowSize = 1 * time.Second\n\t}\n\tnow := clock()\n\treturn \u0026Monitor{\n\t\tactive: true,\n\t\tstart: now,\n\t\trWindow: windowSize.Seconds(),\n\t\tsLast: now,\n\t\tsRate: sampleRate,\n\t\ttLast: now,\n\t}\n}\n\n// Update records the transfer of n bytes and returns n. It should be called\n// after each Read/Write operation, even if n is 0.\nfunc (m *Monitor) Update(n int) int {\n\t// m.mu.Lock()\n\tm.update(n)\n\t// m.mu.Unlock()\n\treturn n\n}\n\n// Hack to set the current rEMA.\nfunc (m *Monitor) SetREMA(rEMA float64) {\n\t// m.mu.Lock()\n\tm.rEMA = rEMA\n\tm.samples++\n\t// m.mu.Unlock()\n}\n\n// IO is a convenience method intended to wrap io.Reader and io.Writer method\n// execution. It calls m.Update(n) and then returns (n, err) unmodified.\nfunc (m *Monitor) IO(n int, err error) (int, error) {\n\treturn m.Update(n), err\n}\n\n// Done marks the transfer as finished and prevents any further updates or\n// limiting. Instantaneous and current transfer rates drop to 0. Update, IO, and\n// Limit methods become NOOPs. It returns the total number of bytes transferred.\nfunc (m *Monitor) Done() int64 {\n\t// m.mu.Lock()\n\tif now := m.update(0); m.sBytes \u003e 0 {\n\t\tm.reset(now)\n\t}\n\tm.active = false\n\tm.tLast = 0\n\tn := m.bytes\n\t// m.mu.Unlock()\n\treturn n\n}\n\n// timeRemLimit is the maximum Status.TimeRem value.\nconst timeRemLimit = 999*time.Hour + 59*time.Minute + 59*time.Second\n\n// Status represents the current Monitor status. All transfer rates are in bytes\n// per second rounded to the nearest byte.\ntype Status struct {\n\tActive bool // Flag indicating an active transfer\n\tStart time.Time // Transfer start time\n\tDuration time.Duration // Time period covered by the statistics\n\tIdle time.Duration // Time since the last transfer of at least 1 byte\n\tBytes int64 // Total number of bytes transferred\n\tSamples int64 // Total number of samples taken\n\tInstRate int64 // Instantaneous transfer rate\n\tCurRate int64 // Current transfer rate (EMA of InstRate)\n\tAvgRate int64 // Average transfer rate (Bytes / Duration)\n\tPeakRate int64 // Maximum instantaneous transfer rate\n\tBytesRem int64 // Number of bytes remaining in the transfer\n\tTimeRem time.Duration // Estimated time to completion\n\tProgress Percent // Overall transfer progress\n}\n\nfunc (s Status) String() string {\n\treturn \"STATUS{}\"\n}\n\n// Status returns current transfer status information. The returned value\n// becomes static after a call to Done.\nfunc (m *Monitor) Status() Status {\n\t// m.mu.Lock()\n\tnow := m.update(0)\n\ts := Status{\n\t\tActive: m.active,\n\t\tStart: clockToTime(m.start),\n\t\tDuration: m.sLast - m.start,\n\t\tIdle: now - m.tLast,\n\t\tBytes: m.bytes,\n\t\tSamples: m.samples,\n\t\tPeakRate: round(m.rPeak),\n\t\tBytesRem: m.tBytes - m.bytes,\n\t\tProgress: percentOf(float64(m.bytes), float64(m.tBytes)),\n\t}\n\tif s.BytesRem \u003c 0 {\n\t\ts.BytesRem = 0\n\t}\n\tif s.Duration \u003e 0 {\n\t\trAvg := float64(s.Bytes) / s.Duration.Seconds()\n\t\ts.AvgRate = round(rAvg)\n\t\tif s.Active {\n\t\t\ts.InstRate = round(m.rSample)\n\t\t\ts.CurRate = round(m.rEMA)\n\t\t\tif s.BytesRem \u003e 0 {\n\t\t\t\tif tRate := 0.8*m.rEMA + 0.2*rAvg; tRate \u003e 0 {\n\t\t\t\t\tns := float64(s.BytesRem) / tRate * 1e9\n\t\t\t\t\tif ns \u003e float64(timeRemLimit) {\n\t\t\t\t\t\tns = float64(timeRemLimit)\n\t\t\t\t\t}\n\t\t\t\t\ts.TimeRem = clockRound(time.Duration(ns))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// m.mu.Unlock()\n\treturn s\n}\n\n// Limit restricts the instantaneous (per-sample) data flow to rate bytes per\n// second. It returns the maximum number of bytes (0 \u003c= n \u003c= want) that may be\n// transferred immediately without exceeding the limit. If block == true, the\n// call blocks until n \u003e 0. want is returned unmodified if want \u003c 1, rate \u003c 1,\n// or the transfer is inactive (after a call to Done).\n//\n// At least one byte is always allowed to be transferred in any given sampling\n// period. Thus, if the sampling rate is 100ms, the lowest achievable flow rate\n// is 10 bytes per second.\n//\n// For usage examples, see the implementation of Reader and Writer in io.go.\nfunc (m *Monitor) Limit(want int, rate int64, block bool) (n int) {\n\tif block {\n\t\tpanic(\"blocking not yet supported\")\n\t}\n\tif want \u003c 1 || rate \u003c 1 {\n\t\treturn want\n\t}\n\t// m.mu.Lock()\n\n\t// Determine the maximum number of bytes that can be sent in one sample\n\tlimit := round(float64(rate) * m.sRate.Seconds())\n\tif limit \u003c= 0 {\n\t\tlimit = 1\n\t}\n\n\t_ = m.update(0)\n\t/* XXX\n\t// If block == true, wait until m.sBytes \u003c limit\n\tif now := m.update(0); block {\n\t\tfor m.sBytes \u003e= limit \u0026\u0026 m.active {\n\t\t\tnow = m.waitNextSample(now)\n\t\t}\n\t}\n\t*/\n\n\t// Make limit \u003c= want (unlimited if the transfer is no longer active)\n\tif limit -= m.sBytes; limit \u003e int64(want) || !m.active {\n\t\tlimit = int64(want)\n\t}\n\t// m.mu.Unlock()\n\n\tif limit \u003c 0 {\n\t\tlimit = 0\n\t}\n\treturn int(limit)\n}\n\n// SetTransferSize specifies the total size of the data transfer, which allows\n// the Monitor to calculate the overall progress and time to completion.\nfunc (m *Monitor) SetTransferSize(bytes int64) {\n\tif bytes \u003c 0 {\n\t\tbytes = 0\n\t}\n\t// m.mu.Lock()\n\tm.tBytes = bytes\n\t// m.mu.Unlock()\n}\n\n// update accumulates the transferred byte count for the current sample until\n// clock() - m.sLast \u003e= m.sRate. The monitor status is updated once the current\n// sample is done.\nfunc (m *Monitor) update(n int) (now time.Duration) {\n\tif !m.active {\n\t\treturn\n\t}\n\tif now = clock(); n \u003e 0 {\n\t\tm.tLast = now\n\t}\n\tm.sBytes += int64(n)\n\tif sTime := now - m.sLast; sTime \u003e= m.sRate {\n\t\tt := sTime.Seconds()\n\t\tif m.rSample = float64(m.sBytes) / t; m.rSample \u003e m.rPeak {\n\t\t\tm.rPeak = m.rSample\n\t\t}\n\n\t\t// Exponential moving average using a method similar to *nix load\n\t\t// average calculation. Longer sampling periods carry greater weight.\n\t\tif m.samples \u003e 0 {\n\t\t\tw := math.Exp(-t / m.rWindow)\n\t\t\tm.rEMA = m.rSample + w*(m.rEMA-m.rSample)\n\t\t} else {\n\t\t\tm.rEMA = m.rSample\n\t\t}\n\t\tm.reset(now)\n\t}\n\treturn\n}\n\n// reset clears the current sample state in preparation for the next sample.\nfunc (m *Monitor) reset(sampleTime time.Duration) {\n\tm.bytes += m.sBytes\n\tm.samples++\n\tm.sBytes = 0\n\tm.sLast = sampleTime\n}\n\n/*\n// waitNextSample sleeps for the remainder of the current sample. The lock is\n// released and reacquired during the actual sleep period, so it's possible for\n// the transfer to be inactive when this method returns.\nfunc (m *Monitor) waitNextSample(now time.Duration) time.Duration {\n\tconst minWait = 5 * time.Millisecond\n\tcurrent := m.sLast\n\n\t// sleep until the last sample time changes (ideally, just one iteration)\n\tfor m.sLast == current \u0026\u0026 m.active {\n\t\td := current + m.sRate - now\n\t\t// m.mu.Unlock()\n\t\tif d \u003c minWait {\n\t\t\td = minWait\n\t\t}\n\t\ttime.Sleep(d)\n\t\t// m.mu.Lock()\n\t\tnow = m.update(0)\n\t}\n\treturn now\n}\n*/\n"},{"name":"io.gno","body":"//\n// Written by Maxim Khitrov (November 2012)\n//\n\npackage flow\n\nimport (\n\t\"errors\"\n\t\"io\"\n)\n\n// ErrLimit is returned by the Writer when a non-blocking write is short due to\n// the transfer rate limit.\nvar ErrLimit = errors.New(\"flowrate: flow rate limit exceeded\")\n\n// Limiter is implemented by the Reader and Writer to provide a consistent\n// interface for monitoring and controlling data transfer.\ntype Limiter interface {\n\tDone() int64\n\tStatus() Status\n\tSetTransferSize(bytes int64)\n\tSetLimit(new int64) (old int64)\n\tSetBlocking(new bool) (old bool)\n}\n\n// Reader implements io.ReadCloser with a restriction on the rate of data\n// transfer.\ntype Reader struct {\n\tio.Reader // Data source\n\t*Monitor // Flow control monitor\n\n\tlimit int64 // Rate limit in bytes per second (unlimited when \u003c= 0)\n\tblock bool // What to do when no new bytes can be read due to the limit\n}\n\n// NewReader restricts all Read operations on r to limit bytes per second.\nfunc NewReader(r io.Reader, limit int64) *Reader {\n\treturn \u0026Reader{r, New(0, 0), limit, false} // XXX default false\n}\n\n// Read reads up to len(p) bytes into p without exceeding the current transfer\n// rate limit. It returns (0, nil) immediately if r is non-blocking and no new\n// bytes can be read at this time.\nfunc (r *Reader) Read(p []byte) (n int, err error) {\n\tp = p[:r.Limit(len(p), r.limit, r.block)]\n\tif len(p) \u003e 0 {\n\t\tn, err = r.IO(r.Reader.Read(p))\n\t}\n\treturn\n}\n\n// SetLimit changes the transfer rate limit to new bytes per second and returns\n// the previous setting.\nfunc (r *Reader) SetLimit(new int64) (old int64) {\n\told, r.limit = r.limit, new\n\treturn\n}\n\n// SetBlocking changes the blocking behavior and returns the previous setting. A\n// Read call on a non-blocking reader returns immediately if no additional bytes\n// may be read at this time due to the rate limit.\nfunc (r *Reader) SetBlocking(new bool) (old bool) {\n\tif new == true {\n\t\tpanic(\"blocking not yet supported\")\n\t}\n\told, r.block = r.block, new\n\treturn\n}\n\n// Close closes the underlying reader if it implements the io.Closer interface.\nfunc (r *Reader) Close() error {\n\tdefer r.Done()\n\tif c, ok := r.Reader.(io.Closer); ok {\n\t\treturn c.Close()\n\t}\n\treturn nil\n}\n\n// Writer implements io.WriteCloser with a restriction on the rate of data\n// transfer.\ntype Writer struct {\n\tio.Writer // Data destination\n\t*Monitor // Flow control monitor\n\n\tlimit int64 // Rate limit in bytes per second (unlimited when \u003c= 0)\n\tblock bool // What to do when no new bytes can be written due to the limit\n}\n\n// NewWriter restricts all Write operations on w to limit bytes per second. The\n// transfer rate and the default blocking behavior (true) can be changed\n// directly on the returned *Writer.\nfunc NewWriter(w io.Writer, limit int64) *Writer {\n\treturn \u0026Writer{w, New(0, 0), limit, false} // XXX default false\n}\n\n// Write writes len(p) bytes from p to the underlying data stream without\n// exceeding the current transfer rate limit. It returns (n, ErrLimit) if w is\n// non-blocking and no additional bytes can be written at this time.\nfunc (w *Writer) Write(p []byte) (n int, err error) {\n\tvar c int\n\tfor len(p) \u003e 0 \u0026\u0026 err == nil {\n\t\ts := p[:w.Limit(len(p), w.limit, w.block)]\n\t\tif len(s) \u003e 0 {\n\t\t\tc, err = w.IO(w.Writer.Write(s))\n\t\t} else {\n\t\t\treturn n, ErrLimit\n\t\t}\n\t\tp = p[c:]\n\t\tn += c\n\t}\n\treturn\n}\n\n// SetLimit changes the transfer rate limit to new bytes per second and returns\n// the previous setting.\nfunc (w *Writer) SetLimit(new int64) (old int64) {\n\told, w.limit = w.limit, new\n\treturn\n}\n\n// SetBlocking changes the blocking behavior and returns the previous setting. A\n// Write call on a non-blocking writer returns as soon as no additional bytes\n// may be written at this time due to the rate limit.\nfunc (w *Writer) SetBlocking(new bool) (old bool) {\n\told, w.block = w.block, new\n\treturn\n}\n\n// Close closes the underlying writer if it implements the io.Closer interface.\nfunc (w *Writer) Close() error {\n\tdefer w.Done()\n\tif c, ok := w.Writer.(io.Closer); ok {\n\t\treturn c.Close()\n\t}\n\treturn nil\n}\n"},{"name":"io_test.gno","body":"//\n// Written by Maxim Khitrov (November 2012)\n//\n\npackage flow\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\t\"time\"\n\n\tios_test \"internal/os_test\"\n)\n\n// XXX ugh, I can't even sleep milliseconds.\n// XXX\n\nconst (\n\t_50ms = 50 * time.Millisecond\n\t_100ms = 100 * time.Millisecond\n\t_200ms = 200 * time.Millisecond\n\t_300ms = 300 * time.Millisecond\n\t_400ms = 400 * time.Millisecond\n\t_500ms = 500 * time.Millisecond\n)\n\nfunc nextStatus(m *Monitor) Status {\n\tsamples := m.samples\n\tfor i := 0; i \u003c 30; i++ {\n\t\tif s := m.Status(); s.Samples != samples {\n\t\t\treturn s\n\t\t}\n\t\tios_test.Sleep(5 * time.Millisecond)\n\t}\n\treturn m.Status()\n}\n\nfunc TestReader(t *testing.T) {\n\tin := make([]byte, 100)\n\tfor i := range in {\n\t\tin[i] = byte(i)\n\t}\n\tb := make([]byte, 100)\n\tr := NewReader(bytes.NewReader(in), 100)\n\tstart := time.Now()\n\n\t// Make sure r implements Limiter\n\t_ = Limiter(r)\n\n\t// 1st read of 10 bytes is performed immediately\n\tif n, err := r.Read(b); n != 10 {\n\t\tt.Fatalf(\"r.Read(b) expected 10 (\u003cnil\u003e); got %v\", n)\n\t} else if err != nil {\n\t\tt.Fatalf(\"r.Read(b) expected 10 (\u003cnil\u003e); got %v (%v)\", n, err.Error())\n\t} else if rt := time.Since(start); rt \u003e _50ms {\n\t\tt.Fatalf(\"r.Read(b) took too long (%v)\", rt.String())\n\t}\n\n\t// No new Reads allowed in the current sample\n\tr.SetBlocking(false)\n\tif n, err := r.Read(b); n != 0 {\n\t\tt.Fatalf(\"r.Read(b) expected 0 (\u003cnil\u003e); got %v\", n)\n\t} else if err != nil {\n\t\tt.Fatalf(\"r.Read(b) expected 0 (\u003cnil\u003e); got %v (%v)\", n, err.Error())\n\t} else if rt := time.Since(start); rt \u003e _50ms {\n\t\tt.Fatalf(\"r.Read(b) took too long (%v)\", rt.String())\n\t}\n\n\tstatus := [6]Status{0: r.Status()} // No samples in the first status\n\n\t// 2nd read of 10 bytes blocks until the next sample\n\t// r.SetBlocking(true)\n\tios_test.Sleep(100 * time.Millisecond)\n\tif n, err := r.Read(b[10:]); n != 10 {\n\t\tt.Fatalf(\"r.Read(b[10:]) expected 10 (\u003cnil\u003e); got %v\", n)\n\t} else if err != nil {\n\t\tt.Fatalf(\"r.Read(b[10:]) expected 10 (\u003cnil\u003e); got %v (%v)\", n, err.Error())\n\t} else if rt := time.Since(start); rt \u003c _100ms {\n\t\tt.Fatalf(\"r.Read(b[10:]) returned ahead of time (%v)\", rt.String())\n\t}\n\n\tstatus[1] = r.Status() // 1st sample\n\tstatus[2] = nextStatus(r.Monitor) // 2nd sample\n\tstatus[3] = nextStatus(r.Monitor) // No activity for the 3rd sample\n\n\tif n := r.Done(); n != 20 {\n\t\tt.Fatalf(\"r.Done() expected 20; got %v\", n)\n\t}\n\n\tstatus[4] = r.Status()\n\tstatus[5] = nextStatus(r.Monitor) // Timeout\n\tstart = status[0].Start\n\n\t// Active, Start, Duration, Idle, Bytes, Samples, InstRate, CurRate, AvgRate, PeakRate, BytesRem, TimeRem, Progress\n\twant := []Status{\n\t\t{true, start, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},\n\t\t{true, start, _100ms, 0, 10, 1, 100, 100, 100, 100, 0, 0, 0},\n\t\t{true, start, _200ms, _100ms, 20, 2, 100, 100, 100, 100, 0, 0, 0},\n\t\t{true, start, _300ms, _200ms, 20, 3, 0, 90, 67, 100, 0, 0, 0},\n\t\t{false, start, _300ms, 0, 20, 3, 0, 0, 67, 100, 0, 0, 0},\n\t\t{false, start, _300ms, 0, 20, 3, 0, 0, 67, 100, 0, 0, 0},\n\t}\n\tfor i, s := range status {\n\t\t// XXX s := s\n\t\tif !statusesAreEqual(\u0026s, \u0026want[i]) {\n\t\t\tt.Errorf(\"r.Status(%v)\\nexpected: %v\\ngot : %v\", i, want[i].String(), s.String())\n\t\t}\n\t}\n\tif !bytes.Equal(b[:20], in[:20]) {\n\t\tt.Errorf(\"r.Read() input doesn't match output\")\n\t}\n}\n\n// XXX blocking writer test doesn't work.\nfunc _TestWriter(t *testing.T) {\n\tb := make([]byte, 100)\n\tfor i := range b {\n\t\tb[i] = byte(i)\n\t}\n\tw := NewWriter(\u0026bytes.Buffer{}, 200)\n\tstart := time.Now()\n\n\t// Make sure w implements Limiter\n\t_ = Limiter(w)\n\n\t// Non-blocking 20-byte write for the first sample returns ErrLimit\n\tw.SetBlocking(false)\n\tif n, err := w.Write(b); n != 20 || err != ErrLimit {\n\t\tt.Fatalf(\"w.Write(b) expected 20 (ErrLimit); got %v (%v)\", n, err.Error())\n\t} else if rt := time.Since(start); rt \u003e _50ms {\n\t\tt.Fatalf(\"w.Write(b) took too long (%v)\", rt)\n\t}\n\n\t// Blocking 80-byte write\n\t// w.SetBlocking(true)\n\t// XXX This test doesn't work, because w.Write calls w.Limit(block=false),\n\t// XXX and it returns ErrLimit after 20. What we want is to keep waiting until 80 is returned,\n\t// XXX but blocking isn't supported. Sleeping 800 shouldn't be sufficient either (its a burst).\n\t// XXX This limits the usage of Limiter and m.Limit().\n\tios_test.Sleep(800 * time.Millisecond)\n\tif n, err := w.Write(b[20:]); n \u003c 80 {\n\t} else if n != 80 || err != nil {\n\t\tt.Fatalf(\"w.Write(b[20:]) expected 80 (\u003cnil\u003e); got %v (%v)\", n, err.Error())\n\t} else if rt := time.Since(start); rt \u003c _300ms {\n\t\t// Explanation for `rt \u003c _300ms` (as opposed to `\u003c _400ms`)\n\t\t//\n\t\t// |\u003c-- start | |\n\t\t// epochs: -----0ms|---100ms|---200ms|---300ms|---400ms\n\t\t// sends: 20|20 |20 |20 |20#\n\t\t//\n\t\t// NOTE: The '#' symbol can thus happen before 400ms is up.\n\t\t// Thus, we can only panic if rt \u003c _300ms.\n\t\tt.Fatalf(\"w.Write(b[20:]) returned ahead of time (%v)\", rt.String())\n\t}\n\n\tw.SetTransferSize(100)\n\tstatus := []Status{w.Status(), nextStatus(w.Monitor)}\n\tstart = status[0].Start\n\n\t// Active, Start, Duration, Idle, Bytes, Samples, InstRate, CurRate, AvgRate, PeakRate, BytesRem, TimeRem, Progress\n\twant := []Status{\n\t\t{true, start, _400ms, 0, 80, 4, 200, 200, 200, 200, 20, _100ms, 80000},\n\t\t{true, start, _500ms, _100ms, 100, 5, 200, 200, 200, 200, 0, 0, 100000},\n\t}\n\tfor i, s := range status {\n\t\t// XXX s := s\n\t\tif !statusesAreEqual(\u0026s, \u0026want[i]) {\n\t\t\tt.Errorf(\"w.Status(%v)\\nexpected: %v\\ngot : %v\\n\", i, want[i].String(), s.String())\n\t\t}\n\t}\n\tif !bytes.Equal(b, w.Writer.(*bytes.Buffer).Bytes()) {\n\t\tt.Errorf(\"w.Write() input doesn't match output\")\n\t}\n}\n\nconst (\n\tmaxDeviationForDuration = 50 * time.Millisecond\n\tmaxDeviationForRate int64 = 50\n)\n\n// statusesAreEqual returns true if s1 is equal to s2. Equality here means\n// general equality of fields except for the duration and rates, which can\n// drift due to unpredictable delays (e.g. thread wakes up 25ms after\n// `time.Sleep` has ended).\nfunc statusesAreEqual(s1 *Status, s2 *Status) bool {\n\tif s1.Active == s2.Active \u0026\u0026\n\t\ts1.Start == s2.Start \u0026\u0026\n\t\tdurationsAreEqual(s1.Duration, s2.Duration, maxDeviationForDuration) \u0026\u0026\n\t\ts1.Idle == s2.Idle \u0026\u0026\n\t\ts1.Bytes == s2.Bytes \u0026\u0026\n\t\ts1.Samples == s2.Samples \u0026\u0026\n\t\tratesAreEqual(s1.InstRate, s2.InstRate, maxDeviationForRate) \u0026\u0026\n\t\tratesAreEqual(s1.CurRate, s2.CurRate, maxDeviationForRate) \u0026\u0026\n\t\tratesAreEqual(s1.AvgRate, s2.AvgRate, maxDeviationForRate) \u0026\u0026\n\t\tratesAreEqual(s1.PeakRate, s2.PeakRate, maxDeviationForRate) \u0026\u0026\n\t\ts1.BytesRem == s2.BytesRem \u0026\u0026\n\t\tdurationsAreEqual(s1.TimeRem, s2.TimeRem, maxDeviationForDuration) \u0026\u0026\n\t\ts1.Progress == s2.Progress {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc durationsAreEqual(d1 time.Duration, d2 time.Duration, maxDeviation time.Duration) bool {\n\treturn d2-d1 \u003c= maxDeviation\n}\n\nfunc ratesAreEqual(r1 int64, r2 int64, maxDeviation int64) bool {\n\tsub := r1 - r2\n\tif sub \u003c 0 {\n\t\tsub = -sub\n\t}\n\tif sub \u003c= maxDeviation {\n\t\treturn true\n\t}\n\treturn false\n}\n"},{"name":"util.gno","body":"//\n// Written by Maxim Khitrov (November 2012)\n//\n\npackage flow\n\nimport (\n\t\"math\"\n\t\"strconv\"\n\t\"time\"\n)\n\n// clockRate is the resolution and precision of clock().\nconst clockRate = 20 * time.Millisecond\n\n// czero is the process start time rounded down to the nearest clockRate\n// increment.\nvar czero = time.Now().Round(clockRate)\n\n// clock returns a low resolution timestamp relative to the process start time.\nfunc clock() time.Duration {\n\treturn time.Now().Round(clockRate).Sub(czero)\n}\n\n// clockToTime converts a clock() timestamp to an absolute time.Time value.\nfunc clockToTime(c time.Duration) time.Time {\n\treturn czero.Add(c)\n}\n\n// clockRound returns d rounded to the nearest clockRate increment.\nfunc clockRound(d time.Duration) time.Duration {\n\treturn (d + clockRate\u003e\u003e1) / clockRate * clockRate\n}\n\n// round returns x rounded to the nearest int64 (non-negative values only).\nfunc round(x float64) int64 {\n\tif _, frac := math.Modf(x); frac \u003e= 0.5 {\n\t\treturn int64(math.Ceil(x))\n\t}\n\treturn int64(math.Floor(x))\n}\n\n// Percent represents a percentage in increments of 1/1000th of a percent.\ntype Percent uint32\n\n// percentOf calculates what percent of the total is x.\nfunc percentOf(x, total float64) Percent {\n\tif x \u003c 0 || total \u003c= 0 {\n\t\treturn 0\n\t} else if p := round(x / total * 1e5); p \u003c= math.MaxUint32 {\n\t\treturn Percent(p)\n\t}\n\treturn Percent(math.MaxUint32)\n}\n\nfunc (p Percent) Float() float64 {\n\treturn float64(p) * 1e-3\n}\n\nfunc (p Percent) String() string {\n\tvar buf [12]byte\n\tb := strconv.AppendUint(buf[:0], uint64(p)/1000, 10)\n\tn := len(b)\n\tb = strconv.AppendUint(b, 1000+uint64(p)%1000, 10)\n\tb[n] = '.'\n\treturn string(append(b, '%'))\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":"fqname","path":"gno.land/p/demo/fqname","files":[{"name":"fqname.gno","body":"// Package fqname provides utilities for handling fully qualified identifiers in\n// Gno. A fully qualified identifier typically includes a package path followed\n// by a dot (.) and then the name of a variable, function, type, or other\n// package-level declaration.\npackage fqname\n\nimport \"strings\"\n\n// Parse splits a fully qualified identifier into its package path and name\n// components. It handles cases with and without slashes in the package path.\n//\n//\tpkgpath, name := fqname.Parse(\"gno.land/p/demo/avl.Tree\")\n//\tufmt.Sprintf(\"Package: %s, Name: %s\\n\", id.Package, id.Name)\n//\t// Output: Package: gno.land/p/demo/avl, Name: Tree\nfunc Parse(fqname string) (pkgpath, name string) {\n\t// Find the index of the last slash.\n\tlastSlashIndex := strings.LastIndex(fqname, \"/\")\n\tif lastSlashIndex == -1 {\n\t\t// No slash found, handle it as a simple package name with dot notation.\n\t\tdotIndex := strings.LastIndex(fqname, \".\")\n\t\tif dotIndex == -1 {\n\t\t\treturn fqname, \"\"\n\t\t}\n\t\treturn fqname[:dotIndex], fqname[dotIndex+1:]\n\t}\n\n\t// Get the part after the last slash.\n\tafterSlash := fqname[lastSlashIndex+1:]\n\n\t// Check for a dot in the substring after the last slash.\n\tdotIndex := strings.Index(afterSlash, \".\")\n\tif dotIndex == -1 {\n\t\t// No dot found after the last slash\n\t\treturn fqname, \"\"\n\t}\n\n\t// Split at the dot to separate the base and the suffix.\n\tbase := fqname[:lastSlashIndex+1+dotIndex]\n\tsuffix := afterSlash[dotIndex+1:]\n\n\treturn base, suffix\n}\n\n// Construct a qualified identifier.\n//\n//\tfqName := fqname.Construct(\"gno.land/r/demo/foo20\", \"GRC20\")\n//\tfmt.Println(\"Fully Qualified Name:\", fqName)\n//\t// Output: gno.land/r/demo/foo20.GRC20\nfunc Construct(pkgpath, name string) string {\n\t// TODO: ensure pkgpath is valid - and as such last part does not contain a dot.\n\tif name == \"\" {\n\t\treturn pkgpath\n\t}\n\treturn pkgpath + \".\" + name\n}\n\n// RenderLink creates a formatted link for a fully qualified identifier.\n// If the package path starts with \"gno.land\", it converts it to a markdown link.\n// If the domain is different or missing, it returns the input as is.\nfunc RenderLink(pkgPath, slug string) string {\n\tif strings.HasPrefix(pkgPath, \"gno.land\") {\n\t\tpkgLink := strings.TrimPrefix(pkgPath, \"gno.land\")\n\t\tif slug != \"\" {\n\t\t\treturn \"[\" + pkgPath + \"](\" + pkgLink + \").\" + slug\n\t\t}\n\t\treturn \"[\" + pkgPath + \"](\" + pkgLink + \")\"\n\t}\n\tif slug != \"\" {\n\t\treturn pkgPath + \".\" + slug\n\t}\n\treturn pkgPath\n}\n"},{"name":"fqname_test.gno","body":"package fqname\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestParse(t *testing.T) {\n\ttests := []struct {\n\t\tinput string\n\t\texpectedPkgPath string\n\t\texpectedName string\n\t}{\n\t\t{\"gno.land/p/demo/avl.Tree\", \"gno.land/p/demo/avl\", \"Tree\"},\n\t\t{\"gno.land/p/demo/avl\", \"gno.land/p/demo/avl\", \"\"},\n\t\t{\"gno.land/p/demo/avl.Tree.Node\", \"gno.land/p/demo/avl\", \"Tree.Node\"},\n\t\t{\"gno.land/p/demo/avl/nested.Package.Func\", \"gno.land/p/demo/avl/nested\", \"Package.Func\"},\n\t\t{\"path/filepath.Split\", \"path/filepath\", \"Split\"},\n\t\t{\"path.Split\", \"path\", \"Split\"},\n\t\t{\"path/filepath\", \"path/filepath\", \"\"},\n\t\t{\"path\", \"path\", \"\"},\n\t\t{\"\", \"\", \"\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tpkgpath, name := Parse(tt.input)\n\t\tuassert.Equal(t, tt.expectedPkgPath, pkgpath, \"Package path did not match\")\n\t\tuassert.Equal(t, tt.expectedName, name, \"Name did not match\")\n\t}\n}\n\nfunc TestConstruct(t *testing.T) {\n\ttests := []struct {\n\t\tpkgpath string\n\t\tname string\n\t\texpected string\n\t}{\n\t\t{\"gno.land/r/demo/foo20\", \"GRC20\", \"gno.land/r/demo/foo20.GRC20\"},\n\t\t{\"gno.land/r/demo/foo20\", \"\", \"gno.land/r/demo/foo20\"},\n\t\t{\"path\", \"\", \"path\"},\n\t\t{\"path\", \"Split\", \"path.Split\"},\n\t\t{\"path/filepath\", \"\", \"path/filepath\"},\n\t\t{\"path/filepath\", \"Split\", \"path/filepath.Split\"},\n\t\t{\"\", \"JustName\", \".JustName\"},\n\t\t{\"\", \"\", \"\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tresult := Construct(tt.pkgpath, tt.name)\n\t\tuassert.Equal(t, tt.expected, result, \"Constructed FQName did not match expected\")\n\t}\n}\n\nfunc TestRenderLink(t *testing.T) {\n\ttests := []struct {\n\t\tpkgPath string\n\t\tslug string\n\t\texpected string\n\t}{\n\t\t{\"gno.land/p/demo/avl\", \"Tree\", \"[gno.land/p/demo/avl](/p/demo/avl).Tree\"},\n\t\t{\"gno.land/p/demo/avl\", \"\", \"[gno.land/p/demo/avl](/p/demo/avl)\"},\n\t\t{\"github.com/a/b\", \"C\", \"github.com/a/b.C\"},\n\t\t{\"example.com/pkg\", \"Func\", \"example.com/pkg.Func\"},\n\t\t{\"gno.land/r/demo/foo20\", \"GRC20\", \"[gno.land/r/demo/foo20](/r/demo/foo20).GRC20\"},\n\t\t{\"gno.land/r/demo/foo20\", \"\", \"[gno.land/r/demo/foo20](/r/demo/foo20)\"},\n\t\t{\"\", \"\", \"\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tresult := RenderLink(tt.pkgPath, tt.slug)\n\t\tuassert.Equal(t, tt.expected, result, \"Rendered link did not match expected\")\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":"gnode","path":"gno.land/p/demo/gnode","files":[{"name":"gnode.gno","body":"package gnode\n\n// XXX what about Gnodes signing on behalf of others?\n// XXX like a multi-sig of Gnodes?\n\ntype Name string\n\ntype Gnode interface {\n\t//----------------------------------------\n\t// Basic properties\n\tGetName() Name\n\n\t//----------------------------------------\n\t// Affiliate Gnodes\n\tNumAffiliates() int\n\tGetAffiliates(Name) Affiliate\n\tAddAffiliate(Affiliate) error // must be affiliated\n\tRemAffiliate(Name) error // must have become unaffiliated\n\n\t//----------------------------------------\n\t// Signing\n\tNumSignedDocuments() int\n\tGetSignedDocument(idx int) Document\n\tSignDocument(doc Document) (int, error) // index relative to signer\n\n\t//----------------------------------------\n\t// Rendering\n\tRenderLines() []string\n}\n\ntype Affiliate struct {\n\tType string\n\tGnode Gnode\n\tTags []string\n}\n\ntype MyGnode struct {\n\tName\n\t// Owners // voting set, something that gives authority of action.\n\t// Treasury //\n\t// Affiliates //\n\t// Board // discussions\n\t// Data // XXX ?\n}\n\ntype Affiliates []*Affiliate\n\n// Documents are equal if they compare equal.\n// NOTE: requires all fields to be comparable.\ntype Document struct {\n\tAuthors string\n\t// Timestamp\n\t// Body\n\t// Attachments\n}\n\n// ACTIONS\n\n// * Lend tokens\n// * Pay tokens\n// * Administrate transferrable and non-transferrable tokens\n// * Sum tokens\n// * Passthrough dependencies\n// * Code\n// * ...\n"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}} @@ -37,7 +37,7 @@ {"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"rat","path":"gno.land/p/demo/rat","files":[{"name":"maths.gno","body":"package rat\n\nconst (\n\tintSize = 32 \u003c\u003c (^uint(0) \u003e\u003e 63) // 32 or 64\n\n\tMaxInt = 1\u003c\u003c(intSize-1) - 1\n\tMinInt = -1 \u003c\u003c (intSize - 1)\n\tMaxInt8 = 1\u003c\u003c7 - 1\n\tMinInt8 = -1 \u003c\u003c 7\n\tMaxInt16 = 1\u003c\u003c15 - 1\n\tMinInt16 = -1 \u003c\u003c 15\n\tMaxInt32 = 1\u003c\u003c31 - 1\n\tMinInt32 = -1 \u003c\u003c 31\n\tMaxInt64 = 1\u003c\u003c63 - 1\n\tMinInt64 = -1 \u003c\u003c 63\n\tMaxUint = 1\u003c\u003cintSize - 1\n\tMaxUint8 = 1\u003c\u003c8 - 1\n\tMaxUint16 = 1\u003c\u003c16 - 1\n\tMaxUint32 = 1\u003c\u003c32 - 1\n\tMaxUint64 = 1\u003c\u003c64 - 1\n)\n"},{"name":"rat.gno","body":"package rat\n\n//----------------------------------------\n// Rat fractions\n\n// represents a fraction.\ntype Rat struct {\n\tX int32\n\tY int32 // must be positive\n}\n\nfunc NewRat(x, y int32) Rat {\n\tif y \u003c= 0 {\n\t\tpanic(\"invalid std.Rat denominator\")\n\t}\n\treturn Rat{X: x, Y: y}\n}\n\nfunc (r1 Rat) IsValid() bool {\n\tif r1.Y \u003c= 0 {\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc (r1 Rat) Cmp(r2 Rat) int {\n\tif !r1.IsValid() {\n\t\tpanic(\"invalid std.Rat left operand\")\n\t}\n\tif !r2.IsValid() {\n\t\tpanic(\"invalid std.Rat right operand\")\n\t}\n\tvar p1, p2 int64\n\tp1 = int64(r1.X) * int64(r2.Y)\n\tp2 = int64(r1.Y) * int64(r2.X)\n\tif p1 \u003c p2 {\n\t\treturn -1\n\t} else if p1 == p2 {\n\t\treturn 0\n\t} else {\n\t\treturn 1\n\t}\n}\n\n//func (r1 Rat) Plus(r2 Rat) Rat {\n// XXX\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":"txlink","path":"gno.land/p/moul/txlink","files":[{"name":"txlink.gno","body":"// Package txlink provides utilities for creating transaction-related links\n// compatible with Gnoweb, Gnobro, and other clients within the Gno ecosystem.\n//\n// This package is optimized for generating lightweight transaction links with\n// flexible arguments, allowing users to build dynamic links that integrate\n// seamlessly with various Gno clients.\n//\n// The primary function, URL, is designed to produce markdown links for\n// transaction functions in the current \"relative realm\". By specifying a custom\n// Realm, you can generate links that either use the current realm path or a\n// fully qualified path for another realm.\n//\n// This package is a streamlined alternative to helplink, providing similar\n// functionality for transaction links without the full feature set of helplink.\npackage txlink\n\nimport (\n\t\"std\"\n\t\"strings\"\n)\n\nconst chainDomain = \"gno.land\" // XXX: std.ChainDomain (#2911)\n\n// URL returns a URL for the specified function with optional key-value\n// arguments, for the current realm.\nfunc URL(fn string, args ...string) string {\n\treturn Realm(\"\").URL(fn, args...)\n}\n\n// Realm represents a specific realm for generating tx links.\ntype Realm string\n\n// prefix returns the URL prefix for the realm.\nfunc (r Realm) prefix() string {\n\t// relative\n\tif r == \"\" {\n\t\tcurPath := std.CurrentRealm().PkgPath()\n\t\treturn strings.TrimPrefix(curPath, chainDomain)\n\t}\n\n\t// local realm -\u003e /realm\n\trealm := string(r)\n\tif strings.Contains(realm, chainDomain) {\n\t\treturn strings.TrimPrefix(realm, chainDomain)\n\t}\n\n\t// remote realm -\u003e https://remote.land/realm\n\treturn \"https://\" + string(r)\n}\n\n// URL returns a URL for the specified function with optional key-value\n// arguments.\nfunc (r Realm) URL(fn string, args ...string) string {\n\t// Start with the base query\n\turl := r.prefix() + \"$help\u0026func=\" + fn\n\n\t// Check if args length is even\n\tif len(args)%2 != 0 {\n\t\t// If not even, we can choose to handle the error here.\n\t\t// For example, we can just return the URL without appending\n\t\t// more args.\n\t\treturn url\n\t}\n\n\t// Append key-value pairs to the URL\n\tfor i := 0; i \u003c len(args); i += 2 {\n\t\tkey := args[i]\n\t\tvalue := args[i+1]\n\t\t// XXX: escape keys and args\n\t\turl += \"\u0026\" + key + \"=\" + value\n\t}\n\n\treturn url\n}\n"},{"name":"txlink_test.gno","body":"package txlink\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestURL(t *testing.T) {\n\ttests := []struct {\n\t\tfn string\n\t\targs []string\n\t\twant string\n\t\trealm Realm\n\t}{\n\t\t{\"foo\", []string{\"bar\", \"1\", \"baz\", \"2\"}, \"$help\u0026func=foo\u0026bar=1\u0026baz=2\", \"\"},\n\t\t{\"testFunc\", []string{\"key\", \"value\"}, \"$help\u0026func=testFunc\u0026key=value\", \"\"},\n\t\t{\"noArgsFunc\", []string{}, \"$help\u0026func=noArgsFunc\", \"\"},\n\t\t{\"oddArgsFunc\", []string{\"key\"}, \"$help\u0026func=oddArgsFunc\", \"\"},\n\t\t{\"foo\", []string{\"bar\", \"1\", \"baz\", \"2\"}, \"/r/lorem/ipsum$help\u0026func=foo\u0026bar=1\u0026baz=2\", \"gno.land/r/lorem/ipsum\"},\n\t\t{\"testFunc\", []string{\"key\", \"value\"}, \"/r/lorem/ipsum$help\u0026func=testFunc\u0026key=value\", \"gno.land/r/lorem/ipsum\"},\n\t\t{\"noArgsFunc\", []string{}, \"/r/lorem/ipsum$help\u0026func=noArgsFunc\", \"gno.land/r/lorem/ipsum\"},\n\t\t{\"oddArgsFunc\", []string{\"key\"}, \"/r/lorem/ipsum$help\u0026func=oddArgsFunc\", \"gno.land/r/lorem/ipsum\"},\n\t\t{\"foo\", []string{\"bar\", \"1\", \"baz\", \"2\"}, \"https://gno.world/r/lorem/ipsum$help\u0026func=foo\u0026bar=1\u0026baz=2\", \"gno.world/r/lorem/ipsum\"},\n\t\t{\"testFunc\", []string{\"key\", \"value\"}, \"https://gno.world/r/lorem/ipsum$help\u0026func=testFunc\u0026key=value\", \"gno.world/r/lorem/ipsum\"},\n\t\t{\"noArgsFunc\", []string{}, \"https://gno.world/r/lorem/ipsum$help\u0026func=noArgsFunc\", \"gno.world/r/lorem/ipsum\"},\n\t\t{\"oddArgsFunc\", []string{\"key\"}, \"https://gno.world/r/lorem/ipsum$help\u0026func=oddArgsFunc\", \"gno.world/r/lorem/ipsum\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\ttitle := tt.fn\n\t\tt.Run(title, func(t *testing.T) {\n\t\t\tgot := tt.realm.URL(tt.fn, tt.args...)\n\t\t\turequire.Equal(t, tt.want, got)\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":"users","path":"gno.land/p/demo/users","files":[{"name":"types.gno","body":"package users\n\ntype AddressOrName string\n\nfunc (aon AddressOrName) IsName() bool {\n\treturn aon != \"\" \u0026\u0026 aon[0] == '@'\n}\n\nfunc (aon AddressOrName) GetName() (string, bool) {\n\tif len(aon) \u003e= 2 \u0026\u0026 aon[0] == '@' {\n\t\treturn string(aon[1:]), true\n\t}\n\treturn \"\", false\n}\n"},{"name":"users.gno","body":"package users\n\nimport (\n\t\"std\"\n\t\"strconv\"\n)\n\n//----------------------------------------\n// Types\n\ntype User struct {\n\tAddress std.Address\n\tName string\n\tProfile string\n\tNumber int\n\tInvites int\n\tInviter std.Address\n}\n\nfunc (u *User) Render() string {\n\tstr := \"## user \" + u.Name + \"\\n\" +\n\t\t\"\\n\" +\n\t\t\" * address = \" + string(u.Address) + \"\\n\" +\n\t\t\" * \" + strconv.Itoa(u.Invites) + \" invites\\n\"\n\tif u.Inviter != \"\" {\n\t\tstr = str + \" * invited by \" + string(u.Inviter) + \"\\n\"\n\t}\n\tstr = str + \"\\n\" +\n\t\tu.Profile + \"\\n\"\n\treturn str\n}\n"},{"name":"users_test.gno","body":"package users\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":"users","path":"gno.land/r/demo/users","files":[{"name":"preregister.gno","body":"package users\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/users\"\n)\n\n// pre-restricted names\nvar preRestrictedNames = []string{\n\t\"bitcoin\", \"cosmos\", \"newtendermint\", \"ethereum\",\n}\n\n// pre-registered users\nvar preRegisteredUsers = []struct {\n\tName string\n\tAddress std.Address\n}{\n\t// system name\n\t{\"archives\", \"g1xlnyjrnf03ju82v0f98ruhpgnquk28knmjfe5k\"}, // -\u003e @r_archives\n\t{\"demo\", \"g13ek2zz9qurzynzvssyc4sthwppnruhnp0gdz8n\"}, // -\u003e @r_demo\n\t{\"gno\", \"g19602kd9tfxrfd60sgreadt9zvdyyuudcyxsz8a\"}, // -\u003e @r_gno\n\t{\"gnoland\", \"g1g3lsfxhvaqgdv4ccemwpnms4fv6t3aq3p5z6u7\"}, // -\u003e @r_gnoland\n\t{\"gnolang\", \"g1yjlnm3z2630gg5mryjd79907e0zx658wxs9hnd\"}, // -\u003e @r_gnolang\n\t{\"gov\", \"g1g73v2anukg4ej7axwqpthsatzrxjsh0wk797da\"}, // -\u003e @r_gov\n\t{\"nt\", \"g15ge0ae9077eh40erwrn2eq0xw6wupwqthpv34l\"}, // -\u003e @r_nt\n\t{\"sys\", \"g1r929wt2qplfawe4lvqv9zuwfdcz4vxdun7qh8l\"}, // -\u003e @r_sys\n\t{\"x\", \"g164sdpew3c2t3rvxj3kmfv7c7ujlvcw2punzzuz\"}, // -\u003e @r_x\n\n\t// test1 user\n\t{\"test1\", \"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\"}, // -\u003e @test1\n\n\t// Onbloc\n\t{\"gnoswap\", \"g1lmvrrrr4er2us84h2732sru76c9zl2nvknha8c\"}, // -\u003e @r_gnoswap\n\t{\"onbloc\", \"g12vx7dn3dqq89mz550zwunvg4qw6epq73d9csay\"}, // -\u003e @r_onbloc\n\n\t// Dragos\n\t{\"flippando\", \"g1z82x8mxa0pz5s9u7csy6zya4x0ut9uw6p7d8dk\"}, // -\u003e @r_flippando\n\t{\"zentasktic\", \"g1paxgmwy2wzhx0l6qvav2p8thvphc5c030xz35c\"}, // -\u003e @r_zentasktic\n}\n\nfunc init() {\n\t// add pre-registered users\n\tfor _, res := range preRegisteredUsers {\n\t\t// assert not already registered.\n\t\t_, ok := name2User.Get(res.Name)\n\t\tif ok {\n\t\t\tpanic(\"name already registered\")\n\t\t}\n\n\t\t_, ok = addr2User.Get(res.Address.String())\n\t\tif ok {\n\t\t\tpanic(\"address already registered\")\n\t\t}\n\n\t\tcounter++\n\t\tuser := \u0026users.User{\n\t\t\tAddress: res.Address,\n\t\t\tName: res.Name,\n\t\t\tProfile: \"\",\n\t\t\tNumber: counter,\n\t\t\tInvites: int(0),\n\t\t\tInviter: admin,\n\t\t}\n\t\tname2User.Set(res.Name, user)\n\t\taddr2User.Set(res.Address.String(), user)\n\t}\n\n\t// add pre-restricted names\n\tfor _, name := range preRestrictedNames {\n\t\tif _, ok := name2User.Get(name); ok {\n\t\t\tpanic(\"name already registered\")\n\t\t}\n\n\t\trestricted.Set(name, true)\n\t}\n}\n"},{"name":"users.gno","body":"package users\n\nimport (\n\t\"regexp\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/avl/pager\"\n\t\"gno.land/p/demo/avlhelpers\"\n\t\"gno.land/p/demo/users\"\n)\n\n//----------------------------------------\n// State\n\nvar (\n\tadmin std.Address = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\" // @moul\n\n\trestricted avl.Tree // Name -\u003e true - restricted name\n\tname2User avl.Tree // Name -\u003e *users.User\n\taddr2User avl.Tree // std.Address -\u003e *users.User\n\tinvites avl.Tree // string(inviter+\":\"+invited) -\u003e true\n\tcounter int // user id counter\n\tminFee int64 = 20 * 1_000_000 // minimum gnot must be paid to register.\n\tmaxFeeMult int64 = 10 // maximum multiples of minFee accepted.\n)\n\n//----------------------------------------\n// Top-level functions\n\nfunc Register(inviter std.Address, name string, profile string) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// assert invited or paid.\n\tcaller := std.GetCallerAt(2)\n\tif caller != std.OriginCaller() {\n\t\tpanic(\"should not happen\") // because std.AssertOrigCall().\n\t}\n\n\tsentCoins := std.OriginSend()\n\tminCoin := std.NewCoin(\"ugnot\", minFee)\n\n\tif inviter == \"\" {\n\t\t// banker := std.GetBanker(std.BankerTypeOriginSend)\n\t\tif len(sentCoins) == 1 \u0026\u0026 sentCoins[0].IsGTE(minCoin) {\n\t\t\tif sentCoins[0].Amount \u003e minFee*maxFeeMult {\n\t\t\t\tpanic(\"payment must not be greater than \" + strconv.Itoa(int(minFee*maxFeeMult)))\n\t\t\t} else {\n\t\t\t\t// ok\n\t\t\t}\n\t\t} else {\n\t\t\tpanic(\"payment must not be less than \" + strconv.Itoa(int(minFee)))\n\t\t}\n\t} else {\n\t\tinvitekey := inviter.String() + \":\" + caller.String()\n\t\t_, ok := invites.Get(invitekey)\n\t\tif !ok {\n\t\t\tpanic(\"invalid invitation\")\n\t\t}\n\t\tinvites.Remove(invitekey)\n\t}\n\n\t// assert not already registered.\n\t_, ok := name2User.Get(name)\n\tif ok {\n\t\tpanic(\"name already registered: \" + name)\n\t}\n\t_, ok = addr2User.Get(caller.String())\n\tif ok {\n\t\tpanic(\"address already registered: \" + caller.String())\n\t}\n\n\tisInviterAdmin := inviter == admin\n\n\t// check for restricted name\n\tif _, isRestricted := restricted.Get(name); isRestricted {\n\t\t// only address invite by the admin can register restricted name\n\t\tif !isInviterAdmin {\n\t\t\tpanic(\"restricted name: \" + name)\n\t\t}\n\n\t\trestricted.Remove(name)\n\t}\n\n\t// assert name is valid.\n\t// admin inviter can bypass name restriction\n\tif !isInviterAdmin \u0026\u0026 !reName.MatchString(name) {\n\t\tpanic(\"invalid name: \" + name + \" (must be at least 6 characters, lowercase alphanumeric with underscore)\")\n\t}\n\n\t// remainder of fees go toward invites.\n\tinvites := int(0)\n\tif len(sentCoins) == 1 {\n\t\tif sentCoins[0].Denom == \"ugnot\" \u0026\u0026 sentCoins[0].Amount \u003e= minFee {\n\t\t\tinvites = int(sentCoins[0].Amount / minFee)\n\t\t\tif inviter == \"\" \u0026\u0026 invites \u003e 0 {\n\t\t\t\tinvites -= 1\n\t\t\t}\n\t\t}\n\t}\n\t// register.\n\tcounter++\n\tuser := \u0026users.User{\n\t\tAddress: caller,\n\t\tName: name,\n\t\tProfile: profile,\n\t\tNumber: counter,\n\t\tInvites: invites,\n\t\tInviter: inviter,\n\t}\n\tname2User.Set(name, user)\n\taddr2User.Set(caller.String(), user)\n}\n\nfunc Invite(invitee string) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// get caller/inviter.\n\tcaller := std.GetCallerAt(2)\n\tif caller != std.OriginCaller() {\n\t\tpanic(\"should not happen\") // because std.AssertOrigCall().\n\t}\n\tlines := strings.Split(invitee, \"\\n\")\n\tif caller == admin {\n\t\t// nothing to do, all good\n\t} else {\n\t\t// ensure has invites.\n\t\tuserI, ok := addr2User.Get(caller.String())\n\t\tif !ok {\n\t\t\tpanic(\"user unknown\")\n\t\t}\n\t\tuser := userI.(*users.User)\n\t\tif user.Invites \u003c= 0 {\n\t\t\tpanic(\"user has no invite tokens\")\n\t\t}\n\t\tuser.Invites -= len(lines)\n\t\tif user.Invites \u003c 0 {\n\t\t\tpanic(\"user has insufficient invite tokens\")\n\t\t}\n\t}\n\t// for each line...\n\tfor _, line := range lines {\n\t\tif line == \"\" {\n\t\t\tcontinue // file bodies have a trailing newline.\n\t\t} else if strings.HasPrefix(line, `//`) {\n\t\t\tcontinue // comment\n\t\t}\n\t\t// record invite.\n\t\tinvitekey := string(caller) + \":\" + string(line)\n\t\tinvites.Set(invitekey, true)\n\t}\n}\n\nfunc GrantInvites(invites string) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// assert admin.\n\tcaller := std.GetCallerAt(2)\n\tif caller != std.OriginCaller() {\n\t\tpanic(\"should not happen\") // because std.AssertOrigCall().\n\t}\n\tif caller != admin {\n\t\tpanic(\"unauthorized\")\n\t}\n\t// for each line...\n\tlines := strings.Split(invites, \"\\n\")\n\tfor _, line := range lines {\n\t\tif line == \"\" {\n\t\t\tcontinue // file bodies have a trailing newline.\n\t\t} else if strings.HasPrefix(line, `//`) {\n\t\t\tcontinue // comment\n\t\t}\n\t\t// parse name and invites.\n\t\tvar name string\n\t\tvar invites int\n\t\tparts := strings.Split(line, \":\")\n\t\tif len(parts) == 1 { // short for :1.\n\t\t\tname = parts[0]\n\t\t\tinvites = 1\n\t\t} else if len(parts) == 2 {\n\t\t\tname = parts[0]\n\t\t\tinvites_, err := strconv.Atoi(parts[1])\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t\tinvites = int(invites_)\n\t\t} else {\n\t\t\tpanic(\"should not happen\")\n\t\t}\n\t\t// give invites.\n\t\tuserI, ok := name2User.Get(name)\n\t\tif !ok {\n\t\t\t// maybe address.\n\t\t\tuserI, ok = addr2User.Get(name)\n\t\t\tif !ok {\n\t\t\t\tpanic(\"invalid user \" + name)\n\t\t\t}\n\t\t}\n\t\tuser := userI.(*users.User)\n\t\tuser.Invites += invites\n\t}\n}\n\n// Any leftover fees go toward invitations.\nfunc SetMinFee(newMinFee int64) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// assert admin caller.\n\tcaller := std.GetCallerAt(2)\n\tif caller != admin {\n\t\tpanic(\"unauthorized\")\n\t}\n\t// update global variables.\n\tminFee = newMinFee\n}\n\n// This helps prevent fat finger accidents.\nfunc SetMaxFeeMultiple(newMaxFeeMult int64) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// assert admin caller.\n\tcaller := std.GetCallerAt(2)\n\tif caller != admin {\n\t\tpanic(\"unauthorized\")\n\t}\n\t// update global variables.\n\tmaxFeeMult = newMaxFeeMult\n}\n\n//----------------------------------------\n// Exposed public functions\n\nfunc GetUserByName(name string) *users.User {\n\tuserI, ok := name2User.Get(name)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn userI.(*users.User)\n}\n\nfunc GetUserByAddress(addr std.Address) *users.User {\n\tuserI, ok := addr2User.Get(addr.String())\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn userI.(*users.User)\n}\n\n// unlike GetUserByName, input must be \"@\" prefixed for names.\nfunc GetUserByAddressOrName(input users.AddressOrName) *users.User {\n\tname, isName := input.GetName()\n\tif isName {\n\t\treturn GetUserByName(name)\n\t}\n\treturn GetUserByAddress(std.Address(input))\n}\n\n// Get a list of user names starting from the given prefix. Limit the\n// number of results to maxResults. (This can be used for a name search tool.)\nfunc ListUsersByPrefix(prefix string, maxResults int) []string {\n\treturn avlhelpers.ListByteStringKeysByPrefix(name2User, prefix, maxResults)\n}\n\nfunc Resolve(input users.AddressOrName) std.Address {\n\tname, isName := input.GetName()\n\tif !isName {\n\t\treturn std.Address(input) // TODO check validity\n\t}\n\n\tuser := GetUserByName(name)\n\treturn user.Address\n}\n\n// Add restricted name to the list\nfunc AdminAddRestrictedName(name string) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// get caller\n\tcaller := std.OriginCaller()\n\t// assert admin\n\tif caller != admin {\n\t\tpanic(\"unauthorized\")\n\t}\n\n\tif user := GetUserByName(name); user != nil {\n\t\tpanic(\"already registered name\")\n\t}\n\n\t// register restricted name\n\n\trestricted.Set(name, true)\n}\n\n//----------------------------------------\n// Constants\n\n// NOTE: name length must be clearly distinguishable from a bech32 address.\nvar reName = regexp.MustCompile(`^[a-z]+[_a-z0-9]{5,16}$`)\n\n//----------------------------------------\n// Render main page\n\nfunc Render(fullPath string) string {\n\tpath, _ := splitPathAndQuery(fullPath)\n\tif path == \"\" {\n\t\treturn renderHome(fullPath)\n\t} else if len(path) \u003e= 38 { // 39? 40?\n\t\tif path[:2] != \"g1\" {\n\t\t\treturn \"invalid address \" + path\n\t\t}\n\t\tuser := GetUserByAddress(std.Address(path))\n\t\tif user == nil {\n\t\t\t// TODO: display basic information about account.\n\t\t\treturn \"unknown address \" + path\n\t\t}\n\t\treturn user.Render()\n\t} else {\n\t\tuser := GetUserByName(path)\n\t\tif user == nil {\n\t\t\treturn \"unknown username \" + path\n\t\t}\n\t\treturn user.Render()\n\t}\n}\n\nfunc renderHome(path string) string {\n\tdoc := \"\"\n\n\tpage := pager.NewPager(\u0026name2User, 50).MustGetPageByPath(path)\n\n\tfor _, item := range page.Items {\n\t\tuser := item.Value.(*users.User)\n\t\tdoc += \" * [\" + user.Name + \"](/r/demo/users:\" + user.Name + \")\\n\"\n\t}\n\tdoc += \"\\n\"\n\tdoc += page.Selector()\n\treturn doc\n}\n\nfunc splitPathAndQuery(fullPath string) (string, string) {\n\tparts := strings.SplitN(fullPath, \"?\", 2)\n\tpath := parts[0]\n\tqueryString := \"\"\n\tif len(parts) \u003e 1 {\n\t\tqueryString = \"?\" + parts[1]\n\t}\n\treturn path, queryString\n}\n"},{"name":"users_test.gno","body":"package users\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestPreRegisteredTest1(t *testing.T) {\n\tnames := ListUsersByPrefix(\"test1\", 1)\n\tuassert.Equal(t, len(names), 1)\n\tuassert.Equal(t, names[0], \"test1\")\n}\n"},{"name":"z_0_b_filetest.gno","body":"package main\n\n// SEND: 19900000ugnot\n\nimport (\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Error:\n// payment must not be less than 20000000\n"},{"name":"z_0_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tstd.TestSetOriginSend(std.Coins{std.NewCoin(\"dontcare\", 1)}, nil)\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Error:\n// incompatible coin denominations: dontcare, ugnot\n"},{"name":"z_10_filetest.gno","body":"// PKGPATH: gno.land/r/demo/users_test\npackage users_test\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc init() {\n\tcaller := std.OriginCaller() // main\n\ttest2 := testutils.TestAddress(\"test2\")\n\t// as admin, invite gnouser and test2\n\tstd.TestSetOriginCaller(admin)\n\tusers.Invite(caller.String() + \"\\n\" + test2.String())\n\t// register as caller\n\tstd.TestSetOriginCaller(caller)\n\tusers.Register(admin, \"gnouser\", \"my profile\")\n}\n\nfunc main() {\n\t// register as test2\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOriginCaller(test2)\n\tusers.Register(admin, \"test222\", \"my profile 2\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_11_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tstd.TestSetOriginCaller(admin)\n\tusers.AdminAddRestrictedName(\"superrestricted\")\n\n\t// test restricted name\n\tstd.TestSetOriginCaller(caller)\n\tusers.Register(\"\", \"superrestricted\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Error:\n// restricted name: superrestricted\n"},{"name":"z_11b_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tstd.TestSetOriginCaller(admin)\n\t// add restricted name\n\tusers.AdminAddRestrictedName(\"superrestricted\")\n\t// grant invite to caller\n\tusers.Invite(caller.String())\n\t// set back caller\n\tstd.TestSetOriginCaller(caller)\n\t// register restricted name with admin invite\n\tusers.Register(admin, \"superrestricted\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_12_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"alicia\", \"my profile\")\n\n\t{\n\t\t// Normal usage\n\t\tnames := users.ListUsersByPrefix(\"a\", 1)\n\t\tprintln(\"# names: \" + strconv.Itoa(len(names)))\n\t\tprintln(\"name: \" + names[0])\n\t}\n\n\t{\n\t\t// Empty prefix: match all\n\t\tnames := users.ListUsersByPrefix(\"\", 1)\n\t\tprintln(\"# names: \" + strconv.Itoa(len(names)))\n\t\tprintln(\"name: \" + names[0])\n\t}\n\n\t{\n\t\t// The prefix is before \"alicia\"\n\t\tnames := users.ListUsersByPrefix(\"alich\", 1)\n\t\tprintln(\"# names: \" + strconv.Itoa(len(names)))\n\t}\n\n\t{\n\t\t// The prefix is after the last name\n\t\tnames := users.ListUsersByPrefix(\"y\", 10)\n\t\tprintln(\"# names: \" + strconv.Itoa(len(names)))\n\t}\n\n\t// More tests are in p/demo/avlhelpers\n}\n\n// Output:\n// # names: 1\n// name: alicia\n// # names: 1\n// name: alicia\n// # names: 0\n// # names: 0\n"},{"name":"z_1_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_2_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_3_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tstd.TestSetOriginSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_4_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\ttest2 := testutils.TestAddress(\"test2\")\n\tusers.Invite(test1.String())\n\t// switch to test2 (not test1)\n\tstd.TestSetOriginCaller(test2)\n\tstd.TestSetOriginSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\tprintln(\"done\")\n}\n\n// Error:\n// invalid invitation\n"},{"name":"z_5_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tstd.TestSetOriginSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\tprintln(users.Render(\"\"))\n\tprintln(\"========================================\")\n\tprintln(users.Render(\"?page=2\"))\n\tprintln(\"========================================\")\n\tprintln(users.Render(\"gnouser\"))\n\tprintln(\"========================================\")\n\tprintln(users.Render(\"satoshi\"))\n\tprintln(\"========================================\")\n\tprintln(users.Render(\"badname\"))\n}\n\n// Output:\n// * [archives](/r/demo/users:archives)\n// * [demo](/r/demo/users:demo)\n// * [gno](/r/demo/users:gno)\n// * [gnoland](/r/demo/users:gnoland)\n// * [gnolang](/r/demo/users:gnolang)\n// * [gnouser](/r/demo/users:gnouser)\n// * [gov](/r/demo/users:gov)\n// * [nt](/r/demo/users:nt)\n// * [satoshi](/r/demo/users:satoshi)\n// * [sys](/r/demo/users:sys)\n// * [test1](/r/demo/users:test1)\n// * [x](/r/demo/users:x)\n//\n//\n// ========================================\n//\n//\n// ========================================\n// ## user gnouser\n//\n// * address = g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n// * 9 invites\n//\n// my profile\n//\n// ========================================\n// ## user satoshi\n//\n// * address = g1w3jhxap3ta047h6lta047h6lta047h6l4mfnm7\n// * 0 invites\n// * invited by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// my other profile\n//\n// ========================================\n// unknown username badname\n"},{"name":"z_6_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller()\n\t// as admin, grant invites to unregistered user.\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\tprintln(\"done\")\n}\n\n// Error:\n// invalid user g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n"},{"name":"z_7_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tstd.TestSetOriginSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\t// as admin, grant invites to gnouser(again) and satoshi.\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\\n\" + test1.String() + \":1\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_7b_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\\n\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tstd.TestSetOriginSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\t// as admin, grant invites to gnouser(again) and satoshi.\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\\n\" + test1.String() + \":1\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_8_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tstd.TestSetOriginSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\t// as admin, grant invites to gnouser(again) and nonexistent user.\n\tstd.TestSetOriginCaller(admin)\n\ttest2 := testutils.TestAddress(\"test2\")\n\tusers.GrantInvites(caller.String() + \":1\\n\" + test2.String() + \":1\")\n\tprintln(\"done\")\n}\n\n// Error:\n// invalid user g1w3jhxapjta047h6lta047h6lta047h6laqcyu4\n"},{"name":"z_9_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\ttest2 := testutils.TestAddress(\"test2\")\n\t// as admin, invite gnouser and test2\n\tstd.TestSetOriginCaller(admin)\n\tusers.Invite(caller.String() + \"\\n\" + test2.String())\n\t// register as caller\n\tstd.TestSetOriginCaller(caller)\n\tusers.Register(admin, \"gnouser\", \"my profile\")\n\t// register as test2\n\tstd.TestSetOriginCaller(test2)\n\tusers.Register(admin, \"test222\", \"my profile 2\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\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":"users","path":"gno.land/r/demo/users","files":[{"name":"preregister.gno","body":"package users\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/users\"\n)\n\n// pre-restricted names\nvar preRestrictedNames = []string{\n\t\"bitcoin\", \"cosmos\", \"newtendermint\", \"ethereum\",\n}\n\n// pre-registered users\nvar preRegisteredUsers = []struct {\n\tName string\n\tAddress std.Address\n}{\n\t// system name\n\t{\"archives\", \"g1xlnyjrnf03ju82v0f98ruhpgnquk28knmjfe5k\"}, // -\u003e @r_archives\n\t{\"demo\", \"g13ek2zz9qurzynzvssyc4sthwppnruhnp0gdz8n\"}, // -\u003e @r_demo\n\t{\"gno\", \"g19602kd9tfxrfd60sgreadt9zvdyyuudcyxsz8a\"}, // -\u003e @r_gno\n\t{\"gnoland\", \"g1g3lsfxhvaqgdv4ccemwpnms4fv6t3aq3p5z6u7\"}, // -\u003e @r_gnoland\n\t{\"gnolang\", \"g1yjlnm3z2630gg5mryjd79907e0zx658wxs9hnd\"}, // -\u003e @r_gnolang\n\t{\"gov\", \"g1g73v2anukg4ej7axwqpthsatzrxjsh0wk797da\"}, // -\u003e @r_gov\n\t{\"nt\", \"g15ge0ae9077eh40erwrn2eq0xw6wupwqthpv34l\"}, // -\u003e @r_nt\n\t{\"sys\", \"g1r929wt2qplfawe4lvqv9zuwfdcz4vxdun7qh8l\"}, // -\u003e @r_sys\n\t{\"x\", \"g164sdpew3c2t3rvxj3kmfv7c7ujlvcw2punzzuz\"}, // -\u003e @r_x\n\n\t// test1 user\n\t{\"test1\", \"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\"}, // -\u003e @test1\n\n\t// Onbloc\n\t{\"gnoswap\", \"g1lmvrrrr4er2us84h2732sru76c9zl2nvknha8c\"}, // -\u003e @r_gnoswap\n\t{\"onbloc\", \"g12vx7dn3dqq89mz550zwunvg4qw6epq73d9csay\"}, // -\u003e @r_onbloc\n\n\t// Dragos\n\t{\"flippando\", \"g1z82x8mxa0pz5s9u7csy6zya4x0ut9uw6p7d8dk\"}, // -\u003e @r_flippando\n\t{\"zentasktic\", \"g1paxgmwy2wzhx0l6qvav2p8thvphc5c030xz35c\"}, // -\u003e @r_zentasktic\n}\n\nfunc init() {\n\t// add pre-registered users\n\tfor _, res := range preRegisteredUsers {\n\t\t// assert not already registered.\n\t\t_, ok := name2User.Get(res.Name)\n\t\tif ok {\n\t\t\tpanic(\"name already registered\")\n\t\t}\n\n\t\t_, ok = addr2User.Get(res.Address.String())\n\t\tif ok {\n\t\t\tpanic(\"address already registered\")\n\t\t}\n\n\t\tcounter++\n\t\tuser := \u0026users.User{\n\t\t\tAddress: res.Address,\n\t\t\tName: res.Name,\n\t\t\tProfile: \"\",\n\t\t\tNumber: counter,\n\t\t\tInvites: int(0),\n\t\t\tInviter: admin,\n\t\t}\n\t\tname2User.Set(res.Name, user)\n\t\taddr2User.Set(res.Address.String(), user)\n\t}\n\n\t// add pre-restricted names\n\tfor _, name := range preRestrictedNames {\n\t\tif _, ok := name2User.Get(name); ok {\n\t\t\tpanic(\"name already registered\")\n\t\t}\n\n\t\trestricted.Set(name, true)\n\t}\n}\n"},{"name":"users.gno","body":"package users\n\nimport (\n\t\"regexp\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/avl/pager\"\n\t\"gno.land/p/demo/avlhelpers\"\n\t\"gno.land/p/demo/users\"\n)\n\n//----------------------------------------\n// State\n\nvar (\n\tadmin std.Address = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\" // @moul\n\n\trestricted avl.Tree // Name -\u003e true - restricted name\n\tname2User avl.Tree // Name -\u003e *users.User\n\taddr2User avl.Tree // std.Address -\u003e *users.User\n\tinvites avl.Tree // string(inviter+\":\"+invited) -\u003e true\n\tcounter int // user id counter\n\tminFee int64 = 20 * 1_000_000 // minimum gnot must be paid to register.\n\tmaxFeeMult int64 = 10 // maximum multiples of minFee accepted.\n)\n\n//----------------------------------------\n// Top-level functions\n\nfunc Register(inviter std.Address, name string, profile string) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// assert invited or paid.\n\tcaller := std.CallerAt(2)\n\tif caller != std.OriginCaller() {\n\t\tpanic(\"should not happen\") // because std.AssertOrigCall().\n\t}\n\n\tsentCoins := std.OriginSend()\n\tminCoin := std.NewCoin(\"ugnot\", minFee)\n\n\tif inviter == \"\" {\n\t\t// banker := std.GetBanker(std.BankerTypeOriginSend)\n\t\tif len(sentCoins) == 1 \u0026\u0026 sentCoins[0].IsGTE(minCoin) {\n\t\t\tif sentCoins[0].Amount \u003e minFee*maxFeeMult {\n\t\t\t\tpanic(\"payment must not be greater than \" + strconv.Itoa(int(minFee*maxFeeMult)))\n\t\t\t} else {\n\t\t\t\t// ok\n\t\t\t}\n\t\t} else {\n\t\t\tpanic(\"payment must not be less than \" + strconv.Itoa(int(minFee)))\n\t\t}\n\t} else {\n\t\tinvitekey := inviter.String() + \":\" + caller.String()\n\t\t_, ok := invites.Get(invitekey)\n\t\tif !ok {\n\t\t\tpanic(\"invalid invitation\")\n\t\t}\n\t\tinvites.Remove(invitekey)\n\t}\n\n\t// assert not already registered.\n\t_, ok := name2User.Get(name)\n\tif ok {\n\t\tpanic(\"name already registered: \" + name)\n\t}\n\t_, ok = addr2User.Get(caller.String())\n\tif ok {\n\t\tpanic(\"address already registered: \" + caller.String())\n\t}\n\n\tisInviterAdmin := inviter == admin\n\n\t// check for restricted name\n\tif _, isRestricted := restricted.Get(name); isRestricted {\n\t\t// only address invite by the admin can register restricted name\n\t\tif !isInviterAdmin {\n\t\t\tpanic(\"restricted name: \" + name)\n\t\t}\n\n\t\trestricted.Remove(name)\n\t}\n\n\t// assert name is valid.\n\t// admin inviter can bypass name restriction\n\tif !isInviterAdmin \u0026\u0026 !reName.MatchString(name) {\n\t\tpanic(\"invalid name: \" + name + \" (must be at least 6 characters, lowercase alphanumeric with underscore)\")\n\t}\n\n\t// remainder of fees go toward invites.\n\tinvites := int(0)\n\tif len(sentCoins) == 1 {\n\t\tif sentCoins[0].Denom == \"ugnot\" \u0026\u0026 sentCoins[0].Amount \u003e= minFee {\n\t\t\tinvites = int(sentCoins[0].Amount / minFee)\n\t\t\tif inviter == \"\" \u0026\u0026 invites \u003e 0 {\n\t\t\t\tinvites -= 1\n\t\t\t}\n\t\t}\n\t}\n\t// register.\n\tcounter++\n\tuser := \u0026users.User{\n\t\tAddress: caller,\n\t\tName: name,\n\t\tProfile: profile,\n\t\tNumber: counter,\n\t\tInvites: invites,\n\t\tInviter: inviter,\n\t}\n\tname2User.Set(name, user)\n\taddr2User.Set(caller.String(), user)\n}\n\nfunc Invite(invitee string) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// get caller/inviter.\n\tcaller := std.CallerAt(2)\n\tif caller != std.OriginCaller() {\n\t\tpanic(\"should not happen\") // because std.AssertOrigCall().\n\t}\n\tlines := strings.Split(invitee, \"\\n\")\n\tif caller == admin {\n\t\t// nothing to do, all good\n\t} else {\n\t\t// ensure has invites.\n\t\tuserI, ok := addr2User.Get(caller.String())\n\t\tif !ok {\n\t\t\tpanic(\"user unknown\")\n\t\t}\n\t\tuser := userI.(*users.User)\n\t\tif user.Invites \u003c= 0 {\n\t\t\tpanic(\"user has no invite tokens\")\n\t\t}\n\t\tuser.Invites -= len(lines)\n\t\tif user.Invites \u003c 0 {\n\t\t\tpanic(\"user has insufficient invite tokens\")\n\t\t}\n\t}\n\t// for each line...\n\tfor _, line := range lines {\n\t\tif line == \"\" {\n\t\t\tcontinue // file bodies have a trailing newline.\n\t\t} else if strings.HasPrefix(line, `//`) {\n\t\t\tcontinue // comment\n\t\t}\n\t\t// record invite.\n\t\tinvitekey := string(caller) + \":\" + string(line)\n\t\tinvites.Set(invitekey, true)\n\t}\n}\n\nfunc GrantInvites(invites string) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// assert admin.\n\tcaller := std.CallerAt(2)\n\tif caller != std.OriginCaller() {\n\t\tpanic(\"should not happen\") // because std.AssertOrigCall().\n\t}\n\tif caller != admin {\n\t\tpanic(\"unauthorized\")\n\t}\n\t// for each line...\n\tlines := strings.Split(invites, \"\\n\")\n\tfor _, line := range lines {\n\t\tif line == \"\" {\n\t\t\tcontinue // file bodies have a trailing newline.\n\t\t} else if strings.HasPrefix(line, `//`) {\n\t\t\tcontinue // comment\n\t\t}\n\t\t// parse name and invites.\n\t\tvar name string\n\t\tvar invites int\n\t\tparts := strings.Split(line, \":\")\n\t\tif len(parts) == 1 { // short for :1.\n\t\t\tname = parts[0]\n\t\t\tinvites = 1\n\t\t} else if len(parts) == 2 {\n\t\t\tname = parts[0]\n\t\t\tinvites_, err := strconv.Atoi(parts[1])\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t\tinvites = int(invites_)\n\t\t} else {\n\t\t\tpanic(\"should not happen\")\n\t\t}\n\t\t// give invites.\n\t\tuserI, ok := name2User.Get(name)\n\t\tif !ok {\n\t\t\t// maybe address.\n\t\t\tuserI, ok = addr2User.Get(name)\n\t\t\tif !ok {\n\t\t\t\tpanic(\"invalid user \" + name)\n\t\t\t}\n\t\t}\n\t\tuser := userI.(*users.User)\n\t\tuser.Invites += invites\n\t}\n}\n\n// Any leftover fees go toward invitations.\nfunc SetMinFee(newMinFee int64) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// assert admin caller.\n\tcaller := std.CallerAt(2)\n\tif caller != admin {\n\t\tpanic(\"unauthorized\")\n\t}\n\t// update global variables.\n\tminFee = newMinFee\n}\n\n// This helps prevent fat finger accidents.\nfunc SetMaxFeeMultiple(newMaxFeeMult int64) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// assert admin caller.\n\tcaller := std.CallerAt(2)\n\tif caller != admin {\n\t\tpanic(\"unauthorized\")\n\t}\n\t// update global variables.\n\tmaxFeeMult = newMaxFeeMult\n}\n\n//----------------------------------------\n// Exposed public functions\n\nfunc GetUserByName(name string) *users.User {\n\tuserI, ok := name2User.Get(name)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn userI.(*users.User)\n}\n\nfunc GetUserByAddress(addr std.Address) *users.User {\n\tuserI, ok := addr2User.Get(addr.String())\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn userI.(*users.User)\n}\n\n// unlike GetUserByName, input must be \"@\" prefixed for names.\nfunc GetUserByAddressOrName(input users.AddressOrName) *users.User {\n\tname, isName := input.GetName()\n\tif isName {\n\t\treturn GetUserByName(name)\n\t}\n\treturn GetUserByAddress(std.Address(input))\n}\n\n// Get a list of user names starting from the given prefix. Limit the\n// number of results to maxResults. (This can be used for a name search tool.)\nfunc ListUsersByPrefix(prefix string, maxResults int) []string {\n\treturn avlhelpers.ListByteStringKeysByPrefix(name2User, prefix, maxResults)\n}\n\nfunc Resolve(input users.AddressOrName) std.Address {\n\tname, isName := input.GetName()\n\tif !isName {\n\t\treturn std.Address(input) // TODO check validity\n\t}\n\n\tuser := GetUserByName(name)\n\treturn user.Address\n}\n\n// Add restricted name to the list\nfunc AdminAddRestrictedName(name string) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// get caller\n\tcaller := std.OriginCaller()\n\t// assert admin\n\tif caller != admin {\n\t\tpanic(\"unauthorized\")\n\t}\n\n\tif user := GetUserByName(name); user != nil {\n\t\tpanic(\"already registered name\")\n\t}\n\n\t// register restricted name\n\n\trestricted.Set(name, true)\n}\n\n//----------------------------------------\n// Constants\n\n// NOTE: name length must be clearly distinguishable from a bech32 address.\nvar reName = regexp.MustCompile(`^[a-z]+[_a-z0-9]{5,16}$`)\n\n//----------------------------------------\n// Render main page\n\nfunc Render(fullPath string) string {\n\tpath, _ := splitPathAndQuery(fullPath)\n\tif path == \"\" {\n\t\treturn renderHome(fullPath)\n\t} else if len(path) \u003e= 38 { // 39? 40?\n\t\tif path[:2] != \"g1\" {\n\t\t\treturn \"invalid address \" + path\n\t\t}\n\t\tuser := GetUserByAddress(std.Address(path))\n\t\tif user == nil {\n\t\t\t// TODO: display basic information about account.\n\t\t\treturn \"unknown address \" + path\n\t\t}\n\t\treturn user.Render()\n\t} else {\n\t\tuser := GetUserByName(path)\n\t\tif user == nil {\n\t\t\treturn \"unknown username \" + path\n\t\t}\n\t\treturn user.Render()\n\t}\n}\n\nfunc renderHome(path string) string {\n\tdoc := \"\"\n\n\tpage := pager.NewPager(\u0026name2User, 50).MustGetPageByPath(path)\n\n\tfor _, item := range page.Items {\n\t\tuser := item.Value.(*users.User)\n\t\tdoc += \" * [\" + user.Name + \"](/r/demo/users:\" + user.Name + \")\\n\"\n\t}\n\tdoc += \"\\n\"\n\tdoc += page.Selector()\n\treturn doc\n}\n\nfunc splitPathAndQuery(fullPath string) (string, string) {\n\tparts := strings.SplitN(fullPath, \"?\", 2)\n\tpath := parts[0]\n\tqueryString := \"\"\n\tif len(parts) \u003e 1 {\n\t\tqueryString = \"?\" + parts[1]\n\t}\n\treturn path, queryString\n}\n"},{"name":"users_test.gno","body":"package users\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestPreRegisteredTest1(t *testing.T) {\n\tnames := ListUsersByPrefix(\"test1\", 1)\n\tuassert.Equal(t, len(names), 1)\n\tuassert.Equal(t, names[0], \"test1\")\n}\n"},{"name":"z_0_b_filetest.gno","body":"package main\n\n// SEND: 19900000ugnot\n\nimport (\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Error:\n// payment must not be less than 20000000\n"},{"name":"z_0_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tstd.TestSetOriginSend(std.Coins{std.NewCoin(\"dontcare\", 1)}, nil)\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Error:\n// incompatible coin denominations: dontcare, ugnot\n"},{"name":"z_10_filetest.gno","body":"// PKGPATH: gno.land/r/demo/users_test\npackage users_test\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc init() {\n\tcaller := std.OriginCaller() // main\n\ttest2 := testutils.TestAddress(\"test2\")\n\t// as admin, invite gnouser and test2\n\tstd.TestSetOriginCaller(admin)\n\tusers.Invite(caller.String() + \"\\n\" + test2.String())\n\t// register as caller\n\tstd.TestSetOriginCaller(caller)\n\tusers.Register(admin, \"gnouser\", \"my profile\")\n}\n\nfunc main() {\n\t// register as test2\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOriginCaller(test2)\n\tusers.Register(admin, \"test222\", \"my profile 2\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_11_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tstd.TestSetOriginCaller(admin)\n\tusers.AdminAddRestrictedName(\"superrestricted\")\n\n\t// test restricted name\n\tstd.TestSetOriginCaller(caller)\n\tusers.Register(\"\", \"superrestricted\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Error:\n// restricted name: superrestricted\n"},{"name":"z_11b_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tstd.TestSetOriginCaller(admin)\n\t// add restricted name\n\tusers.AdminAddRestrictedName(\"superrestricted\")\n\t// grant invite to caller\n\tusers.Invite(caller.String())\n\t// set back caller\n\tstd.TestSetOriginCaller(caller)\n\t// register restricted name with admin invite\n\tusers.Register(admin, \"superrestricted\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_12_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"alicia\", \"my profile\")\n\n\t{\n\t\t// Normal usage\n\t\tnames := users.ListUsersByPrefix(\"a\", 1)\n\t\tprintln(\"# names: \" + strconv.Itoa(len(names)))\n\t\tprintln(\"name: \" + names[0])\n\t}\n\n\t{\n\t\t// Empty prefix: match all\n\t\tnames := users.ListUsersByPrefix(\"\", 1)\n\t\tprintln(\"# names: \" + strconv.Itoa(len(names)))\n\t\tprintln(\"name: \" + names[0])\n\t}\n\n\t{\n\t\t// The prefix is before \"alicia\"\n\t\tnames := users.ListUsersByPrefix(\"alich\", 1)\n\t\tprintln(\"# names: \" + strconv.Itoa(len(names)))\n\t}\n\n\t{\n\t\t// The prefix is after the last name\n\t\tnames := users.ListUsersByPrefix(\"y\", 10)\n\t\tprintln(\"# names: \" + strconv.Itoa(len(names)))\n\t}\n\n\t// More tests are in p/demo/avlhelpers\n}\n\n// Output:\n// # names: 1\n// name: alicia\n// # names: 1\n// name: alicia\n// # names: 0\n// # names: 0\n"},{"name":"z_1_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_2_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_3_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tstd.TestSetOriginSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_4_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\ttest2 := testutils.TestAddress(\"test2\")\n\tusers.Invite(test1.String())\n\t// switch to test2 (not test1)\n\tstd.TestSetOriginCaller(test2)\n\tstd.TestSetOriginSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\tprintln(\"done\")\n}\n\n// Error:\n// invalid invitation\n"},{"name":"z_5_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tstd.TestSetOriginSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\tprintln(users.Render(\"\"))\n\tprintln(\"========================================\")\n\tprintln(users.Render(\"?page=2\"))\n\tprintln(\"========================================\")\n\tprintln(users.Render(\"gnouser\"))\n\tprintln(\"========================================\")\n\tprintln(users.Render(\"satoshi\"))\n\tprintln(\"========================================\")\n\tprintln(users.Render(\"badname\"))\n}\n\n// Output:\n// * [archives](/r/demo/users:archives)\n// * [demo](/r/demo/users:demo)\n// * [gno](/r/demo/users:gno)\n// * [gnoland](/r/demo/users:gnoland)\n// * [gnolang](/r/demo/users:gnolang)\n// * [gnouser](/r/demo/users:gnouser)\n// * [gov](/r/demo/users:gov)\n// * [nt](/r/demo/users:nt)\n// * [satoshi](/r/demo/users:satoshi)\n// * [sys](/r/demo/users:sys)\n// * [test1](/r/demo/users:test1)\n// * [x](/r/demo/users:x)\n//\n//\n// ========================================\n//\n//\n// ========================================\n// ## user gnouser\n//\n// * address = g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n// * 9 invites\n//\n// my profile\n//\n// ========================================\n// ## user satoshi\n//\n// * address = g1w3jhxap3ta047h6lta047h6lta047h6l4mfnm7\n// * 0 invites\n// * invited by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// my other profile\n//\n// ========================================\n// unknown username badname\n"},{"name":"z_6_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller()\n\t// as admin, grant invites to unregistered user.\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\tprintln(\"done\")\n}\n\n// Error:\n// invalid user g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n"},{"name":"z_7_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tstd.TestSetOriginSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\t// as admin, grant invites to gnouser(again) and satoshi.\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\\n\" + test1.String() + \":1\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_7b_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\\n\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tstd.TestSetOriginSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\t// as admin, grant invites to gnouser(again) and satoshi.\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\\n\" + test1.String() + \":1\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_8_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tstd.TestSetOriginSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\t// as admin, grant invites to gnouser(again) and nonexistent user.\n\tstd.TestSetOriginCaller(admin)\n\ttest2 := testutils.TestAddress(\"test2\")\n\tusers.GrantInvites(caller.String() + \":1\\n\" + test2.String() + \":1\")\n\tprintln(\"done\")\n}\n\n// Error:\n// invalid user g1w3jhxapjta047h6lta047h6lta047h6laqcyu4\n"},{"name":"z_9_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\ttest2 := testutils.TestAddress(\"test2\")\n\t// as admin, invite gnouser and test2\n\tstd.TestSetOriginCaller(admin)\n\tusers.Invite(caller.String() + \"\\n\" + test2.String())\n\t// register as caller\n\tstd.TestSetOriginCaller(caller)\n\tusers.Register(admin, \"gnouser\", \"my profile\")\n\t// register as test2\n\tstd.TestSetOriginCaller(test2)\n\tusers.Register(admin, \"test222\", \"my profile 2\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\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":"boards","path":"gno.land/r/demo/boards","files":[{"name":"README.md","body":"This is a demo of Gno smart contract programming. This document was\nconstructed by Gno onto a smart contract hosted on the data Realm\nname [\"gno.land/r/demo/boards\"](https://gno.land/r/demo/boards/)\n([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)).\n\n\n\n## Build `gnokey`, create your account, and interact with Gno.\n\nNOTE: Where you see `-remote localhost:26657` here, that flag can be replaced\nwith `-remote gno.land:26657` if you have $GNOT on the testnet.\n(To use the testnet, also replace `-chainid dev` with `-chainid portal-loop` .)\n\n### Build `gnokey` (and other tools).\n\n```bash\ngit clone git@github.com:gnolang/gno.git\ncd gno/gno.land\nmake build\n```\n\n### Generate a seed/mnemonic code.\n\n```bash\n./build/gnokey generate\n```\n\nNOTE: You can generate 24 words with any good bip39 generator.\n\n### Create a new account using your mnemonic.\n\n```bash\n./build/gnokey add -recover KEYNAME\n```\n\nNOTE: `KEYNAME` is your key identifier, and should be changed.\n\n### Verify that you can see your account locally.\n\n```bash\n./build/gnokey list\n```\n\nTake note of your `addr` which looks something like `g17sphqax3kasjptdkmuqvn740u8dhtx4kxl6ljf` .\nYou will use this as your `ACCOUNT_ADDR`.\n\n## Interact with the blockchain.\n\n### Add $GNOT for your account.\n\nBefore starting the `gnoland` node for the first time, your new account can be given $GNOT in the node genesis.\nEdit the file `gno.land/genesis/genesis_balances.txt` and add the following line (simlar to the others), using\nyour `ACCOUNT_ADDR` and `KEYNAME`\n\n`ACCOUNT_ADDR=10000000000ugnot # @KEYNAME`\n\n### Alternative: Run a faucet to add $GNOT.\n\nInstead of editing `gno.land/genesis/genesis_balances.txt`, a more general solution (with more steps)\nis to run a local \"faucet\" and use the web browser to add $GNOT. (This can be done at any time.)\nSee this page: https://github.com/gnolang/gno/blob/master/contribs/gnofaucet/README.md\n\n\n### Start the `gnoland` node.\n\n```bash\n./build/gnoland start\n```\n\nNOTE: The node already has the \"boards\" realm.\n\nLeave this running in the terminal. In a new terminal, cd to the same folder `gno/gno.land` .\n\n### Get your current balance, account number, and sequence number.\n\n```bash\n./build/gnokey query auth/accounts/ACCOUNT_ADDR -remote localhost:26657\n```\n\n### Register a board username with a smart contract call.\n\nThe `USERNAME` for posting can different than your `KEYNAME`. It is internally linked to your `ACCOUNT_ADDR`. It must be at least 6 characters, lowercase alphanumeric with underscore.\n\n```bash\n./build/gnokey maketx call -pkgpath \"gno.land/r/demo/users\" -func \"Register\" -args \"\" -args \"USERNAME\" -args \"Profile description\" -gas-fee \"10000000ugnot\" -gas-wanted \"2000000\" -send \"200000000ugnot\" -broadcast -chainid dev -remote 127.0.0.1:26657 KEYNAME\n```\n\nInteractive documentation: https://gno.land/r/demo/users$help\u0026func=Register\n\n### Create a board with a smart contract call.\n\n```bash\n./build/gnokey maketx call -pkgpath \"gno.land/r/demo/boards\" -func \"CreateBoard\" -args \"BOARDNAME\" -gas-fee \"1000000ugnot\" -gas-wanted \"10000000\" -broadcast -chainid dev -remote localhost:26657 KEYNAME\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateBoard\n\nNext, query for the permanent board ID by querying (you need this to create a new post):\n\n```bash\n./build/gnokey query \"vm/qeval\" -data 'gno.land/r/demo/boards.GetBoardIDFromName(\"BOARDNAME\")' -remote localhost:26657\n```\n\n### Create a post of a board with a smart contract call.\n\nNOTE: If a board was created successfully, your SEQUENCE_NUMBER would have increased.\n\n```bash\n./build/gnokey maketx call -pkgpath \"gno.land/r/demo/boards\" -func \"CreateThread\" -args BOARD_ID -args \"Hello gno.land\" -args \"Text of the post\" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid dev -remote localhost:26657 KEYNAME\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateThread\n\n### Create a comment to a post.\n\n```bash\n./build/gnokey maketx call -pkgpath \"gno.land/r/demo/boards\" -func \"CreateReply\" -args BOARD_ID -args \"1\" -args \"1\" -args \"Nice to meet you too.\" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid dev -remote localhost:26657 KEYNAME\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateReply\n\n```bash\n./build/gnokey query \"vm/qrender\" -data \"gno.land/r/demo/boards:BOARDNAME/1\" -remote localhost:26657\n```\n\n### Render page with optional path expression.\n\nThe contents of `https://gno.land/r/demo/boards:` and `https://gno.land/r/demo/boards:gnolang` are rendered by calling\nthe `Render(path string)` function like so:\n\n```bash\n./build/gnokey query \"vm/qrender\" -data \"gno.land/r/demo/boards:gnolang\"\n```\n## View the board in the browser.\n\n### Start the web server.\n\n```bash\n./build/gnoweb\n```\n\nThis should print something like `Running on http://127.0.0.1:8888` . Leave this running in the terminal.\n\n### View in the browser\n\nIn your browser, navigate to the printed address http://127.0.0.1:8888 .\nTo see you post, click on the package `/r/demo/boards` .\n"},{"name":"board.gno","body":"package boards\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/moul/txlink\"\n)\n\n//----------------------------------------\n// Board\n\ntype BoardID uint64\n\nfunc (bid BoardID) String() string {\n\treturn strconv.Itoa(int(bid))\n}\n\ntype Board struct {\n\tid BoardID // only set for public boards.\n\turl string\n\tname string\n\tcreator std.Address\n\tthreads avl.Tree // Post.id -\u003e *Post\n\tpostsCtr uint64 // increments Post.id\n\tcreatedAt time.Time\n\tdeleted avl.Tree // TODO reserved for fast-delete.\n}\n\nfunc newBoard(id BoardID, url string, name string, creator std.Address) *Board {\n\tif !reName.MatchString(name) {\n\t\tpanic(\"invalid name: \" + name)\n\t}\n\texists := gBoardsByName.Has(name)\n\tif exists {\n\t\tpanic(\"board already exists\")\n\t}\n\treturn \u0026Board{\n\t\tid: id,\n\t\turl: url,\n\t\tname: name,\n\t\tcreator: creator,\n\t\tthreads: avl.Tree{},\n\t\tcreatedAt: time.Now(),\n\t\tdeleted: avl.Tree{},\n\t}\n}\n\n/* TODO support this once we figure out how to ensure URL correctness.\n// A private board is not tracked by gBoards*,\n// but must be persisted by the caller's realm.\n// Private boards have 0 id and does not ping\n// back the remote board on reposts.\nfunc NewPrivateBoard(url string, name string, creator std.Address) *Board {\n\treturn newBoard(0, url, name, creator)\n}\n*/\n\nfunc (board *Board) IsPrivate() bool {\n\treturn board.id == 0\n}\n\nfunc (board *Board) GetThread(pid PostID) *Post {\n\tpidkey := postIDKey(pid)\n\tpostI, exists := board.threads.Get(pidkey)\n\tif !exists {\n\t\treturn nil\n\t}\n\treturn postI.(*Post)\n}\n\nfunc (board *Board) AddThread(creator std.Address, title string, body string) *Post {\n\tpid := board.incGetPostID()\n\tpidkey := postIDKey(pid)\n\tthread := newPost(board, pid, creator, title, body, pid, 0, 0)\n\tboard.threads.Set(pidkey, thread)\n\treturn thread\n}\n\n// NOTE: this can be potentially very expensive for threads with many replies.\n// TODO: implement optional fast-delete where thread is simply moved.\nfunc (board *Board) DeleteThread(pid PostID) {\n\tpidkey := postIDKey(pid)\n\t_, removed := board.threads.Remove(pidkey)\n\tif !removed {\n\t\tpanic(\"thread does not exist with id \" + pid.String())\n\t}\n}\n\nfunc (board *Board) HasPermission(addr std.Address, perm Permission) bool {\n\tif board.creator == addr {\n\t\tswitch perm {\n\t\tcase EditPermission:\n\t\t\treturn true\n\t\tcase DeletePermission:\n\t\t\treturn true\n\t\tdefault:\n\t\t\treturn false\n\t\t}\n\t}\n\treturn false\n}\n\n// Renders the board for display suitable as plaintext in\n// console. This is suitable for demonstration or tests,\n// but not for prod.\nfunc (board *Board) RenderBoard() string {\n\tstr := \"\"\n\tstr += \"\\\\[[post](\" + board.GetPostFormURL() + \")]\\n\\n\"\n\tif board.threads.Size() \u003e 0 {\n\t\tboard.threads.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tif str != \"\" {\n\t\t\t\tstr += \"----------------------------------------\\n\"\n\t\t\t}\n\t\t\tstr += value.(*Post).RenderSummary() + \"\\n\"\n\t\t\treturn false\n\t\t})\n\t}\n\treturn str\n}\n\nfunc (board *Board) incGetPostID() PostID {\n\tboard.postsCtr++\n\treturn PostID(board.postsCtr)\n}\n\nfunc (board *Board) GetURLFromThreadAndReplyID(threadID, replyID PostID) string {\n\tif replyID == 0 {\n\t\treturn board.url + \"/\" + threadID.String()\n\t} else {\n\t\treturn board.url + \"/\" + threadID.String() + \"/\" + replyID.String()\n\t}\n}\n\nfunc (board *Board) GetPostFormURL() string {\n\treturn txlink.URL(\"CreateThread\", \"bid\", board.id.String())\n}\n"},{"name":"boards.gno","body":"package boards\n\nimport (\n\t\"regexp\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n//----------------------------------------\n// Realm (package) state\n\nvar (\n\tgBoards avl.Tree // id -\u003e *Board\n\tgBoardsCtr int // increments Board.id\n\tgBoardsByName avl.Tree // name -\u003e *Board\n\tgDefaultAnonFee = 100000000 // minimum fee required if anonymous\n)\n\n//----------------------------------------\n// Constants\n\nvar reName = regexp.MustCompile(`^[a-z]+[_a-z0-9]{2,29}$`)\n"},{"name":"misc.gno","body":"package boards\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/r/demo/users\"\n)\n\n//----------------------------------------\n// private utility methods\n// XXX ensure these cannot be called from public.\n\nfunc getBoard(bid BoardID) *Board {\n\tbidkey := boardIDKey(bid)\n\tboard_, exists := gBoards.Get(bidkey)\n\tif !exists {\n\t\treturn nil\n\t}\n\tboard := board_.(*Board)\n\treturn board\n}\n\nfunc incGetBoardID() BoardID {\n\tgBoardsCtr++\n\treturn BoardID(gBoardsCtr)\n}\n\nfunc padLeft(str string, length int) string {\n\tif len(str) \u003e= length {\n\t\treturn str\n\t} else {\n\t\treturn strings.Repeat(\" \", length-len(str)) + str\n\t}\n}\n\nfunc padZero(u64 uint64, length int) string {\n\tstr := strconv.Itoa(int(u64))\n\tif len(str) \u003e= length {\n\t\treturn str\n\t} else {\n\t\treturn strings.Repeat(\"0\", length-len(str)) + str\n\t}\n}\n\nfunc boardIDKey(bid BoardID) string {\n\treturn padZero(uint64(bid), 10)\n}\n\nfunc postIDKey(pid PostID) string {\n\treturn padZero(uint64(pid), 10)\n}\n\nfunc indentBody(indent string, body string) string {\n\tlines := strings.Split(body, \"\\n\")\n\tres := \"\"\n\tfor i, line := range lines {\n\t\tif i \u003e 0 {\n\t\t\tres += \"\\n\"\n\t\t}\n\t\tres += indent + line\n\t}\n\treturn res\n}\n\n// NOTE: length must be greater than 3.\nfunc summaryOf(str string, length int) string {\n\tlines := strings.SplitN(str, \"\\n\", 2)\n\tline := lines[0]\n\tif len(line) \u003e length {\n\t\tline = line[:(length-3)] + \"...\"\n\t} else if len(lines) \u003e 1 {\n\t\t// len(line) \u003c= 80\n\t\tline = line + \"...\"\n\t}\n\treturn line\n}\n\nfunc displayAddressMD(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user == nil {\n\t\treturn \"[\" + addr.String() + \"](/r/demo/users:\" + addr.String() + \")\"\n\t} else {\n\t\treturn \"[@\" + user.Name + \"](/r/demo/users:\" + user.Name + \")\"\n\t}\n}\n\nfunc usernameOf(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user == nil {\n\t\treturn \"\"\n\t}\n\treturn user.Name\n}\n"},{"name":"post.gno","body":"package boards\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/moul/txlink\"\n)\n\n//----------------------------------------\n// Post\n\n// NOTE: a PostID is relative to the board.\ntype PostID uint64\n\nfunc (pid PostID) String() string {\n\treturn strconv.Itoa(int(pid))\n}\n\n// A Post is a \"thread\" or a \"reply\" depending on context.\n// A thread is a Post of a Board that holds other replies.\ntype Post struct {\n\tboard *Board\n\tid PostID\n\tcreator std.Address\n\ttitle string // optional\n\tbody string\n\treplies avl.Tree // Post.id -\u003e *Post\n\trepliesAll avl.Tree // Post.id -\u003e *Post (all replies, for top-level posts)\n\treposts avl.Tree // Board.id -\u003e Post.id\n\tthreadID PostID // original Post.id\n\tparentID PostID // parent Post.id (if reply or repost)\n\trepostBoard BoardID // original Board.id (if repost)\n\tcreatedAt time.Time\n\tupdatedAt time.Time\n}\n\nfunc newPost(board *Board, id PostID, creator std.Address, title, body string, threadID, parentID PostID, repostBoard BoardID) *Post {\n\treturn \u0026Post{\n\t\tboard: board,\n\t\tid: id,\n\t\tcreator: creator,\n\t\ttitle: title,\n\t\tbody: body,\n\t\treplies: avl.Tree{},\n\t\trepliesAll: avl.Tree{},\n\t\treposts: avl.Tree{},\n\t\tthreadID: threadID,\n\t\tparentID: parentID,\n\t\trepostBoard: repostBoard,\n\t\tcreatedAt: time.Now(),\n\t}\n}\n\nfunc (post *Post) IsThread() bool {\n\treturn post.parentID == 0\n}\n\nfunc (post *Post) GetPostID() PostID {\n\treturn post.id\n}\n\nfunc (post *Post) AddReply(creator std.Address, body string) *Post {\n\tboard := post.board\n\tpid := board.incGetPostID()\n\tpidkey := postIDKey(pid)\n\treply := newPost(board, pid, creator, \"\", body, post.threadID, post.id, 0)\n\tpost.replies.Set(pidkey, reply)\n\tif post.threadID == post.id {\n\t\tpost.repliesAll.Set(pidkey, reply)\n\t} else {\n\t\tthread := board.GetThread(post.threadID)\n\t\tthread.repliesAll.Set(pidkey, reply)\n\t}\n\treturn reply\n}\n\nfunc (post *Post) Update(title string, body string) {\n\tpost.title = title\n\tpost.body = body\n\tpost.updatedAt = time.Now()\n}\n\nfunc (thread *Post) GetReply(pid PostID) *Post {\n\tpidkey := postIDKey(pid)\n\treplyI, ok := thread.repliesAll.Get(pidkey)\n\tif !ok {\n\t\treturn nil\n\t} else {\n\t\treturn replyI.(*Post)\n\t}\n}\n\nfunc (post *Post) AddRepostTo(creator std.Address, title, body string, dst *Board) *Post {\n\tif !post.IsThread() {\n\t\tpanic(\"cannot repost non-thread post\")\n\t}\n\tpid := dst.incGetPostID()\n\tpidkey := postIDKey(pid)\n\trepost := newPost(dst, pid, creator, title, body, pid, post.id, post.board.id)\n\tdst.threads.Set(pidkey, repost)\n\tif !dst.IsPrivate() {\n\t\tbidkey := boardIDKey(dst.id)\n\t\tpost.reposts.Set(bidkey, pid)\n\t}\n\treturn repost\n}\n\nfunc (thread *Post) DeletePost(pid PostID) {\n\tif thread.id == pid {\n\t\tpanic(\"should not happen\")\n\t}\n\tpidkey := postIDKey(pid)\n\tpostI, removed := thread.repliesAll.Remove(pidkey)\n\tif !removed {\n\t\tpanic(\"post not found in thread\")\n\t}\n\tpost := postI.(*Post)\n\tif post.parentID != thread.id {\n\t\tparent := thread.GetReply(post.parentID)\n\t\tparent.replies.Remove(pidkey)\n\t} else {\n\t\tthread.replies.Remove(pidkey)\n\t}\n}\n\nfunc (post *Post) HasPermission(addr std.Address, perm Permission) bool {\n\tif post.creator == addr {\n\t\tswitch perm {\n\t\tcase EditPermission:\n\t\t\treturn true\n\t\tcase DeletePermission:\n\t\t\treturn true\n\t\tdefault:\n\t\t\treturn false\n\t\t}\n\t}\n\t// post notes inherit permissions of the board.\n\treturn post.board.HasPermission(addr, perm)\n}\n\nfunc (post *Post) GetSummary() string {\n\treturn summaryOf(post.body, 80)\n}\n\nfunc (post *Post) GetURL() string {\n\tif post.IsThread() {\n\t\treturn post.board.GetURLFromThreadAndReplyID(\n\t\t\tpost.id, 0)\n\t} else {\n\t\treturn post.board.GetURLFromThreadAndReplyID(\n\t\t\tpost.threadID, post.id)\n\t}\n}\n\nfunc (post *Post) GetReplyFormURL() string {\n\treturn txlink.URL(\"CreateReply\",\n\t\t\"bid\", post.board.id.String(),\n\t\t\"threadid\", post.threadID.String(),\n\t\t\"postid\", post.id.String(),\n\t)\n}\n\nfunc (post *Post) GetRepostFormURL() string {\n\treturn txlink.URL(\"CreateRepost\",\n\t\t\"bid\", post.board.id.String(),\n\t\t\"postid\", post.id.String(),\n\t)\n}\n\nfunc (post *Post) GetDeleteFormURL() string {\n\treturn txlink.URL(\"DeletePost\",\n\t\t\"bid\", post.board.id.String(),\n\t\t\"threadid\", post.threadID.String(),\n\t\t\"postid\", post.id.String(),\n\t)\n}\n\nfunc (post *Post) RenderSummary() string {\n\tif post.repostBoard != 0 {\n\t\tdstBoard := getBoard(post.repostBoard)\n\t\tif dstBoard == nil {\n\t\t\tpanic(\"repostBoard does not exist\")\n\t\t}\n\t\tthread := dstBoard.GetThread(PostID(post.parentID))\n\t\tif thread == nil {\n\t\t\treturn \"reposted post does not exist\"\n\t\t}\n\t\treturn \"Repost: \" + post.GetSummary() + \"\\n\" + thread.RenderSummary()\n\t}\n\tstr := \"\"\n\tif post.title != \"\" {\n\t\tstr += \"## [\" + summaryOf(post.title, 80) + \"](\" + post.GetURL() + \")\\n\"\n\t\tstr += \"\\n\"\n\t}\n\tstr += post.GetSummary() + \"\\n\"\n\tstr += \"\\\\- \" + displayAddressMD(post.creator) + \",\"\n\tstr += \" [\" + post.createdAt.Format(\"2006-01-02 3:04pm MST\") + \"](\" + post.GetURL() + \")\"\n\tstr += \" \\\\[[x](\" + post.GetDeleteFormURL() + \")]\"\n\tstr += \" (\" + strconv.Itoa(post.replies.Size()) + \" replies)\"\n\tstr += \" (\" + strconv.Itoa(post.reposts.Size()) + \" reposts)\" + \"\\n\"\n\treturn str\n}\n\nfunc (post *Post) RenderPost(indent string, levels int) string {\n\tif post == nil {\n\t\treturn \"nil post\"\n\t}\n\tstr := \"\"\n\tif post.title != \"\" {\n\t\tstr += indent + \"# \" + post.title + \"\\n\"\n\t\tstr += indent + \"\\n\"\n\t}\n\tstr += indentBody(indent, post.body) + \"\\n\" // TODO: indent body lines.\n\tstr += indent + \"\\\\- \" + displayAddressMD(post.creator) + \", \"\n\tstr += \"[\" + post.createdAt.Format(\"2006-01-02 3:04pm (MST)\") + \"](\" + post.GetURL() + \")\"\n\tstr += \" \\\\[[reply](\" + post.GetReplyFormURL() + \")]\"\n\tif post.IsThread() {\n\t\tstr += \" \\\\[[repost](\" + post.GetRepostFormURL() + \")]\"\n\t}\n\tstr += \" \\\\[[x](\" + post.GetDeleteFormURL() + \")]\\n\"\n\tif levels \u003e 0 {\n\t\tif post.replies.Size() \u003e 0 {\n\t\t\tpost.replies.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\t\tstr += indent + \"\\n\"\n\t\t\t\tstr += value.(*Post).RenderPost(indent+\"\u003e \", levels-1)\n\t\t\t\treturn false\n\t\t\t})\n\t\t}\n\t} else {\n\t\tif post.replies.Size() \u003e 0 {\n\t\t\tstr += indent + \"\\n\"\n\t\t\tstr += indent + \"_[see all \" + strconv.Itoa(post.replies.Size()) + \" replies](\" + post.GetURL() + \")_\\n\"\n\t\t}\n\t}\n\treturn str\n}\n\n// render reply and link to context thread\nfunc (post *Post) RenderInner() string {\n\tif post.IsThread() {\n\t\tpanic(\"unexpected thread\")\n\t}\n\tthreadID := post.threadID\n\t// replyID := post.id\n\tparentID := post.parentID\n\tstr := \"\"\n\tstr += \"_[see thread](\" + post.board.GetURLFromThreadAndReplyID(\n\t\tthreadID, 0) + \")_\\n\\n\"\n\tthread := post.board.GetThread(post.threadID)\n\tvar parent *Post\n\tif thread.id == parentID {\n\t\tparent = thread\n\t} else {\n\t\tparent = thread.GetReply(parentID)\n\t}\n\tstr += parent.RenderPost(\"\", 0)\n\tstr += \"\\n\"\n\tstr += post.RenderPost(\"\u003e \", 5)\n\treturn str\n}\n"},{"name":"public.gno","body":"package boards\n\nimport (\n\t\"std\"\n\t\"strconv\"\n)\n\n//----------------------------------------\n// Public facing functions\n\nfunc GetBoardIDFromName(name string) (BoardID, bool) {\n\tboardI, exists := gBoardsByName.Get(name)\n\tif !exists {\n\t\treturn 0, false\n\t}\n\treturn boardI.(*Board).id, true\n}\n\nfunc CreateBoard(name string) BoardID {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tbid := incGetBoardID()\n\tcaller := std.OriginCaller()\n\tif usernameOf(caller) == \"\" {\n\t\tpanic(\"unauthorized\")\n\t}\n\turl := \"/r/demo/boards:\" + name\n\tboard := newBoard(bid, url, name, caller)\n\tbidkey := boardIDKey(bid)\n\tgBoards.Set(bidkey, board)\n\tgBoardsByName.Set(name, board)\n\treturn board.id\n}\n\nfunc checkAnonFee() bool {\n\tsent := std.OriginSend()\n\tanonFeeCoin := std.NewCoin(\"ugnot\", int64(gDefaultAnonFee))\n\tif len(sent) == 1 \u0026\u0026 sent[0].IsGTE(anonFeeCoin) {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc CreateThread(bid BoardID, title string, body string) PostID {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.OriginCaller()\n\tif usernameOf(caller) == \"\" {\n\t\tif !checkAnonFee() {\n\t\t\tpanic(\"please register, otherwise minimum fee \" + strconv.Itoa(gDefaultAnonFee) + \" is required if anonymous\")\n\t\t}\n\t}\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"board not exist\")\n\t}\n\tthread := board.AddThread(caller, title, body)\n\treturn thread.id\n}\n\nfunc CreateReply(bid BoardID, threadid, postid PostID, body string) PostID {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.OriginCaller()\n\tif usernameOf(caller) == \"\" {\n\t\tif !checkAnonFee() {\n\t\t\tpanic(\"please register, otherwise minimum fee \" + strconv.Itoa(gDefaultAnonFee) + \" is required if anonymous\")\n\t\t}\n\t}\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"board not exist\")\n\t}\n\tthread := board.GetThread(threadid)\n\tif thread == nil {\n\t\tpanic(\"thread not exist\")\n\t}\n\tif postid == threadid {\n\t\treply := thread.AddReply(caller, body)\n\t\treturn reply.id\n\t} else {\n\t\tpost := thread.GetReply(postid)\n\t\treply := post.AddReply(caller, body)\n\t\treturn reply.id\n\t}\n}\n\n// If dstBoard is private, does not ping back.\n// If board specified by bid is private, panics.\nfunc CreateRepost(bid BoardID, postid PostID, title string, body string, dstBoardID BoardID) PostID {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.OriginCaller()\n\tif usernameOf(caller) == \"\" {\n\t\t// TODO: allow with gDefaultAnonFee payment.\n\t\tif !checkAnonFee() {\n\t\t\tpanic(\"please register, otherwise minimum fee \" + strconv.Itoa(gDefaultAnonFee) + \" is required if anonymous\")\n\t\t}\n\t}\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"src board not exist\")\n\t}\n\tif board.IsPrivate() {\n\t\tpanic(\"cannot repost from a private board\")\n\t}\n\tdst := getBoard(dstBoardID)\n\tif dst == nil {\n\t\tpanic(\"dst board not exist\")\n\t}\n\tthread := board.GetThread(postid)\n\tif thread == nil {\n\t\tpanic(\"thread not exist\")\n\t}\n\trepost := thread.AddRepostTo(caller, title, body, dst)\n\treturn repost.id\n}\n\nfunc DeletePost(bid BoardID, threadid, postid PostID, reason string) {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.OriginCaller()\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"board not exist\")\n\t}\n\tthread := board.GetThread(threadid)\n\tif thread == nil {\n\t\tpanic(\"thread not exist\")\n\t}\n\tif postid == threadid {\n\t\t// delete thread\n\t\tif !thread.HasPermission(caller, DeletePermission) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t\tboard.DeleteThread(threadid)\n\t} else {\n\t\t// delete thread's post\n\t\tpost := thread.GetReply(postid)\n\t\tif post == nil {\n\t\t\tpanic(\"post not exist\")\n\t\t}\n\t\tif !post.HasPermission(caller, DeletePermission) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t\tthread.DeletePost(postid)\n\t}\n}\n\nfunc EditPost(bid BoardID, threadid, postid PostID, title, body string) {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.OriginCaller()\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"board not exist\")\n\t}\n\tthread := board.GetThread(threadid)\n\tif thread == nil {\n\t\tpanic(\"thread not exist\")\n\t}\n\tif postid == threadid {\n\t\t// edit thread\n\t\tif !thread.HasPermission(caller, EditPermission) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t\tthread.Update(title, body)\n\t} else {\n\t\t// edit thread's post\n\t\tpost := thread.GetReply(postid)\n\t\tif post == nil {\n\t\t\tpanic(\"post not exist\")\n\t\t}\n\t\tif !post.HasPermission(caller, EditPermission) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t\tpost.Update(title, body)\n\t}\n}\n"},{"name":"render.gno","body":"package boards\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n)\n\n//----------------------------------------\n// Render functions\n\nfunc RenderBoard(bid BoardID) string {\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\treturn \"missing board\"\n\t}\n\treturn board.RenderBoard()\n}\n\nfunc Render(path string) string {\n\tif path == \"\" {\n\t\tstr := \"These are all the boards of this realm:\\n\\n\"\n\t\tgBoards.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tboard := value.(*Board)\n\t\t\tstr += \" * [\" + board.url + \"](\" + board.url + \")\\n\"\n\t\t\treturn false\n\t\t})\n\t\treturn str\n\t}\n\tparts := strings.Split(path, \"/\")\n\tif len(parts) == 1 {\n\t\t// /r/demo/boards:BOARD_NAME\n\t\tname := parts[0]\n\t\tboardI, exists := gBoardsByName.Get(name)\n\t\tif !exists {\n\t\t\treturn \"board does not exist: \" + name\n\t\t}\n\t\treturn boardI.(*Board).RenderBoard()\n\t} else if len(parts) == 2 {\n\t\t// /r/demo/boards:BOARD_NAME/THREAD_ID\n\t\tname := parts[0]\n\t\tboardI, exists := gBoardsByName.Get(name)\n\t\tif !exists {\n\t\t\treturn \"board does not exist: \" + name\n\t\t}\n\t\tpid, err := strconv.Atoi(parts[1])\n\t\tif err != nil {\n\t\t\treturn \"invalid thread id: \" + parts[1]\n\t\t}\n\t\tboard := boardI.(*Board)\n\t\tthread := board.GetThread(PostID(pid))\n\t\tif thread == nil {\n\t\t\treturn \"thread does not exist with id: \" + parts[1]\n\t\t}\n\t\treturn thread.RenderPost(\"\", 5)\n\t} else if len(parts) == 3 {\n\t\t// /r/demo/boards:BOARD_NAME/THREAD_ID/REPLY_ID\n\t\tname := parts[0]\n\t\tboardI, exists := gBoardsByName.Get(name)\n\t\tif !exists {\n\t\t\treturn \"board does not exist: \" + name\n\t\t}\n\t\tpid, err := strconv.Atoi(parts[1])\n\t\tif err != nil {\n\t\t\treturn \"invalid thread id: \" + parts[1]\n\t\t}\n\t\tboard := boardI.(*Board)\n\t\tthread := board.GetThread(PostID(pid))\n\t\tif thread == nil {\n\t\t\treturn \"thread does not exist with id: \" + parts[1]\n\t\t}\n\t\trid, err := strconv.Atoi(parts[2])\n\t\tif err != nil {\n\t\t\treturn \"invalid reply id: \" + parts[2]\n\t\t}\n\t\treply := thread.GetReply(PostID(rid))\n\t\tif reply == nil {\n\t\t\treturn \"reply does not exist with id: \" + parts[2]\n\t\t}\n\t\treturn reply.RenderInner()\n\t} else {\n\t\treturn \"unrecognized path \" + path\n\t}\n}\n"},{"name":"role.gno","body":"package boards\n\ntype Permission string\n\nconst (\n\tDeletePermission Permission = \"role:delete\"\n\tEditPermission Permission = \"role:edit\"\n)\n"},{"name":"z_0_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\nimport (\n\t\"gno.land/r/demo/boards\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid := boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// unauthorized\n"},{"name":"z_0_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 19900000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid = boards.CreateBoard(\"test_board\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// payment must not be less than 20000000\n"},{"name":"z_0_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tboards.CreateThread(1, \"First Post (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// board not exist\n"},{"name":"z_0_d_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateReply(bid, 0, 0, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// thread not exist\n"},{"name":"z_0_e_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tboards.CreateReply(bid, 0, 0, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// board not exist\n"},{"name":"z_0_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 20000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid := boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Output:\n// \\[[post](/r/demo/boards$help\u0026func=CreateThread\u0026bid=1)]\n//\n// ----------------------------------------\n// ## [First Post (title)](/r/demo/boards:test_board/1)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/1) \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)] (0 replies) (0 reposts)\n//\n// ----------------------------------------\n// ## [Second Post (title)](/r/demo/boards:test_board/2)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/2) \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)] (1 replies) (0 reposts)\n"},{"name":"z_10_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// boardId 2 not exist\n\tboards.DeletePost(2, pid, pid, \"\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// board not exist\n"},{"name":"z_10_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// pid of 2 not exist\n\tboards.DeletePost(bid, 2, 2, \"\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// thread not exist\n"},{"name":"z_10_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"First reply of the First post\\n\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.DeletePost(bid, pid, rid, \"\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e First reply of the First post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n//\n// ----------------------------------------------------\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n"},{"name":"z_10_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.DeletePost(bid, pid, pid, \"\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// ----------------------------------------------------\n// thread does not exist with id: 1\n"},{"name":"z_11_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// board 2 not exist\n\tboards.EditPost(2, pid, pid, \"Edited: First Post in (title)\", \"Edited: Body of the first post. (body)\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// board not exist\n"},{"name":"z_11_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// thread 2 not exist\n\tboards.EditPost(bid, 2, pid, \"Edited: First Post in (title)\", \"Edited: Body of the first post. (body)\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// thread not exist\n"},{"name":"z_11_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// post 2 not exist\n\tboards.EditPost(bid, pid, 2, \"Edited: First Post in (title)\", \"Edited: Body of the first post. (body)\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// post not exist\n"},{"name":"z_11_d_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"First reply of the First post\\n\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.EditPost(bid, pid, rid, \"\", \"Edited: First reply of the First post\\n\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e First reply of the First post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n//\n// ----------------------------------------------------\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e Edited: First reply of the First post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n"},{"name":"z_11_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.EditPost(bid, pid, pid, \"Edited: First Post in (title)\", \"Edited: Body of the first post. (body)\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// ----------------------------------------------------\n// # Edited: First Post in (title)\n//\n// Edited: Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n"},{"name":"z_12_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// create a post via registered user\n\tbid1 := boards.CreateBoard(\"test_board1\")\n\tpid := boards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tbid2 := boards.CreateBoard(\"test_board2\")\n\n\t// create a repost via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOriginCaller(test2)\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\n\trid := boards.CreateRepost(bid1, pid, \"\", \"Check this out\", bid2)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board1\"))\n}\n\n// Error:\n// please register, otherwise minimum fee 100000000 is required if anonymous\n"},{"name":"z_12_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid1 := boards.CreateBoard(\"test_board1\")\n\tpid := boards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tbid2 := boards.CreateBoard(\"test_board2\")\n\n\t// create a repost to a non-existing board\n\trid := boards.CreateRepost(5, pid, \"\", \"Check this out\", bid2)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board1\"))\n}\n\n// Error:\n// src board not exist\n"},{"name":"z_12_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid1 := boards.CreateBoard(\"test_board1\")\n\tboards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tbid2 := boards.CreateBoard(\"test_board2\")\n\n\t// create a repost to a non-existing thread\n\trid := boards.CreateRepost(bid1, 5, \"\", \"Check this out\", bid2)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board1\"))\n}\n\n// Error:\n// thread not exist\n"},{"name":"z_12_d_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid1 := boards.CreateBoard(\"test_board1\")\n\tpid := boards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tboards.CreateBoard(\"test_board2\")\n\n\t// create a repost to a non-existing destination board\n\trid := boards.CreateRepost(bid1, pid, \"\", \"Check this out\", 5)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board1\"))\n}\n\n// Error:\n// dst board not exist\n"},{"name":"z_12_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid1 boards.BoardID\n\tbid2 boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid1 = boards.CreateBoard(\"test_board1\")\n\tpid = boards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tbid2 = boards.CreateBoard(\"test_board2\")\n}\n\nfunc main() {\n\trid := boards.CreateRepost(bid1, pid, \"\", \"Check this out\", bid2)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board2\"))\n}\n\n// Output:\n// 1\n// \\[[post](/r/demo/boards$help\u0026func=CreateThread\u0026bid=2)]\n//\n// ----------------------------------------\n// Repost: Check this out\n// ## [First Post (title)](/r/demo/boards:test_board1/1)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board1/1) \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)] (0 replies) (1 reposts)\n"},{"name":"z_1_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar board *boards.Board\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\t_ = boards.CreateBoard(\"test_board_1\")\n\t_ = boards.CreateBoard(\"test_board_2\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"\"))\n}\n\n// Output:\n// These are all the boards of this realm:\n//\n// * [/r/demo/boards:test_board_1](/r/demo/boards:test_board_1)\n// * [/r/demo/boards:test_board_2](/r/demo/boards:test_board_2)\n"},{"name":"z_2_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n"},{"name":"z_3_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n}\n\nfunc main() {\n\trid := boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// 3\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n"},{"name":"z_4_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid := boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n\tprintln(rid)\n}\n\nfunc main() {\n\trid2 := boards.CreateReply(bid, pid, pid, \"Second reply of the second post\")\n\tprintln(rid2)\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// 3\n// 4\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n//\n// \u003e Second reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=4)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=4)]\n\n// Realm:\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/boards\"]\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111\",\n// \"ModTime\": \"123\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"68663c8895d37d479e417c11e21badfe21345c61\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:112\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Post\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"3f34ac77289aa1d5f9a2f8b6d083138325816fb0\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {},\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"94a6665a44bac6ede7f3e3b87173e537b12f9532\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"bc8e5b4e782a0bbc4ac9689681f119beb7b34d59\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"9957eadbc91dd32f33b0d815e041a32dbdea0671\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131]={\n// \"Fields\": [\n// {\n// \"N\": \"AAAAgJSeXbo=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"65536\"\n// }\n// },\n// {\n// \"N\": \"AbSNdvQQIhE=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"1024\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Location\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"336074805fc853987abe6f7fe3ad97a6a6f3077a:2\"\n// },\n// \"Index\": \"182\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"65536\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"1024\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Location\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Board\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:84\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"N\": \"BAAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.PostID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"std.Address\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"Second reply of the second post\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"f91e355bd19240f0f3350a7fa0e6a82b72225916\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"9ee9c4117be283fc51ffcc5ecd65b75ecef5a9dd\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"eb768b0140a5fe95f9c58747f0960d647dacfd42\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.PostID\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.PostID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.BoardID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Time\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"0fd3352422af0a56a77ef2c9e88f479054e3d51f\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Time\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"bed4afa8ffdbbf775451c947fc68b27a345ce32a\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132\"\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\",\n// \"IsEscaped\": true,\n// \"ModTime\": \"0\",\n// \"RefCount\": \"2\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Post\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"c45bbd47a46681a63af973db0ec2180922e4a8ae\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\"\n// }\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120\",\n// \"ModTime\": \"134\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"dc1f011553dc53e7a846049e08cc77fa35ea6a51\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:121\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Post\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"96b86b4585c7f1075d7794180a5581f72733a7ab\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {},\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"32274e1f28fb2b97d67a1262afd362d370de7faa\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"c2cfd6aec36a462f35bf02e5bf4a127aa1bb7ac2\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"5cb875179e86d32c517322af7a323b2a5f3e6cc5\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\"\n// }\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:85]={\n// \"Fields\": [\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.BoardID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"/r/demo/boards:test_board\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"test_board\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"std.Address\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"a416a751c3a45a1e5cba11e737c51340b081e372\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:86\"\n// }\n// },\n// {\n// \"N\": \"BAAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"65536\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Time\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"36299fccbc13f2a84c4629fad4cb940f0bd4b1c6\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:87\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"af6ed0268f99b7f369329094eb6dfaea7812708b\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:88\"\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:85\",\n// \"ModTime\": \"121\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:84\",\n// \"RefCount\": \"1\"\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"9809329dc1ddc5d3556f7a8fa3c2cebcbf65560b\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106\",\n// \"ModTime\": \"121\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:105\",\n// \"RefCount\": \"1\"\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"ceae9a1c4ed28bb51062e6ccdccfad0caafd1c4f\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107\",\n// \"ModTime\": \"121\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:105\",\n// \"RefCount\": \"1\"\n// }\n// }\n// switchrealm[\"gno.land/r/demo/boards\"]\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/boards\"]\n// switchrealm[\"gno.land/r/demo/boards_test\"]\n"},{"name":"z_5_b_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// create board via registered user\n\tbid := boards.CreateBoard(\"test_board\")\n\n\t// create post via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOriginCaller(test2)\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\n\tpid := boards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// please register, otherwise minimum fee 100000000 is required if anonymous\n"},{"name":"z_5_c_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// create board via registered user\n\tbid := boards.CreateBoard(\"test_board\")\n\n\t// create post via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOriginCaller(test2)\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 101000000}}, nil)\n\n\tpid := boards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the first post\")\n\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post (title)\n//\n// Body of the first post. (body)\n// \\- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/r/demo/users:g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e Reply of the first post\n// \u003e \\- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/r/demo/users:g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n"},{"name":"z_5_d_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// create board via registered user\n\tbid := boards.CreateBoard(\"test_board\")\n\tpid := boards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\n\t// create reply via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOriginCaller(test2)\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\tboards.CreateReply(bid, pid, pid, \"Reply of the first post\")\n\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// please register, otherwise minimum fee 100000000 is required if anonymous\n"},{"name":"z_5_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid := boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\trid2 := boards.CreateReply(bid, pid, pid, \"Second reply of the second post\\n\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n//\n// \u003e Second reply of the second post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=4)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=4)]\n"},{"name":"z_6_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tboards.CreateReply(bid, pid, pid, \"Second reply of the second post\\n\")\n\tboards.CreateReply(bid, pid, rid, \"First reply of the first reply\\n\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n// \u003e\n// \u003e \u003e First reply of the first reply\n// \u003e \u003e\n// \u003e \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/5) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=5)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=5)]\n//\n// \u003e Second reply of the second post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=4)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=4)]\n"},{"name":"z_7_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc init() {\n\t// register\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\t// create board and post\n\tbid := boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Output:\n// \\[[post](/r/demo/boards$help\u0026func=CreateThread\u0026bid=1)]\n//\n// ----------------------------------------\n// ## [First Post (title)](/r/demo/boards:test_board/1)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/1) \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)] (0 replies) (0 reposts)\n"},{"name":"z_8_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tboards.CreateReply(bid, pid, pid, \"Second reply of the second post\\n\")\n\trid2 := boards.CreateReply(bid, pid, rid, \"First reply of the first reply\\n\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid)) + \"/\" + strconv.Itoa(int(rid2))))\n}\n\n// Output:\n// _[see thread](/r/demo/boards:test_board/2)_\n//\n// Reply of the second post\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n//\n// _[see all 1 replies](/r/demo/boards:test_board/2/3)_\n//\n// \u003e First reply of the first reply\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/5) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=5)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=5)]\n"},{"name":"z_9_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar dstBoard boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tdstBoard = boards.CreateBoard(\"dst_board\")\n\n\tboards.CreateRepost(0, 0, \"First Post in (title)\", \"Body of the first post. (body)\", dstBoard)\n}\n\nfunc main() {\n}\n\n// Error:\n// src board not exist\n"},{"name":"z_9_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tsrcBoard boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tsrcBoard = boards.CreateBoard(\"first_board\")\n\tpid = boards.CreateThread(srcBoard, \"First Post in (title)\", \"Body of the first post. (body)\")\n\n\tboards.CreateRepost(srcBoard, pid, \"First Post in (title)\", \"Body of the first post. (body)\", 0)\n}\n\nfunc main() {\n}\n\n// Error:\n// dst board not exist\n"},{"name":"z_9_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tfirstBoard boards.BoardID\n\tsecondBoard boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tfirstBoard = boards.CreateBoard(\"first_board\")\n\tsecondBoard = boards.CreateBoard(\"second_board\")\n\tpid = boards.CreateThread(firstBoard, \"First Post in (title)\", \"Body of the first post. (body)\")\n\n\tboards.CreateRepost(firstBoard, pid, \"First Post in (title)\", \"Body of the first post. (body)\", secondBoard)\n}\n\nfunc main() {\n\tprintln(boards.Render(\"second_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:second_board/1/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=2\u0026threadid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=2\u0026threadid=1\u0026postid=1)]\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":"groups","path":"gno.land/p/demo/groups","files":[{"name":"groups.gno","body":"package groups\n\nimport \"gno.land/r/demo/boards\"\n\n// TODO implement something and test.\ntype Group struct {\n\tBoard *boards.Board\n}\n"},{"name":"vote_set.gno","body":"package groups\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/demo/rat\"\n)\n\n//----------------------------------------\n// VoteSet\n\ntype VoteSet interface {\n\t// number of present votes in set.\n\tSize() int\n\t// add or update vote for voter.\n\tSetVote(voter std.Address, value string) error\n\t// count the number of votes for value.\n\tCountVotes(value string) int\n}\n\n//----------------------------------------\n// VoteList\n\ntype Vote struct {\n\tVoter std.Address\n\tValue string\n}\n\ntype VoteList []Vote\n\nfunc NewVoteList() *VoteList {\n\treturn \u0026VoteList{}\n}\n\nfunc (vlist *VoteList) Size() int {\n\treturn len(*vlist)\n}\n\nfunc (vlist *VoteList) SetVote(voter std.Address, value string) error {\n\t// TODO optimize with binary algorithm\n\tfor i, vote := range *vlist {\n\t\tif vote.Voter == voter {\n\t\t\t// update vote\n\t\t\t(*vlist)[i] = Vote{\n\t\t\t\tVoter: voter,\n\t\t\t\tValue: value,\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t}\n\t*vlist = append(*vlist, Vote{\n\t\tVoter: voter,\n\t\tValue: value,\n\t})\n\treturn nil\n}\n\nfunc (vlist *VoteList) CountVotes(target string) int {\n\t// TODO optimize with binary algorithm\n\tvar count int\n\tfor _, vote := range *vlist {\n\t\tif vote.Value == target {\n\t\t\tcount++\n\t\t}\n\t}\n\treturn count\n}\n\n//----------------------------------------\n// Committee\n\ntype Committee struct {\n\tQuorum rat.Rat\n\tThreshold rat.Rat\n\tAddresses std.AddressSet\n}\n\n//----------------------------------------\n// VoteSession\n// NOTE: this seems a bit too formal and\n// complicated vs what might be possible;\n// something simpler, more informal.\n\ntype SessionStatus int\n\nconst (\n\tSessionNew SessionStatus = iota\n\tSessionStarted\n\tSessionCompleted\n\tSessionCanceled\n)\n\ntype VoteSession struct {\n\tName string\n\tCreator std.Address\n\tBody string\n\tStart time.Time\n\tDeadline time.Time\n\tStatus SessionStatus\n\tCommittee *Committee\n\tVotes VoteSet\n\tChoices []string\n\tResult string\n}\n"},{"name":"z_1_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\nimport (\n\t\"gno.land/p/demo/groups\"\n\t\"gno.land/p/demo/testutils\"\n)\n\nvar vset groups.VoteSet\n\nfunc init() {\n\taddr1 := testutils.TestAddress(\"test1\")\n\taddr2 := testutils.TestAddress(\"test2\")\n\tvset = groups.NewVoteList()\n\tvset.SetVote(addr1, \"yes\")\n\tvset.SetVote(addr2, \"yes\")\n}\n\nfunc main() {\n\tprintln(vset.Size())\n\tprintln(\"yes:\", vset.CountVotes(\"yes\"))\n\tprintln(\"no:\", vset.CountVotes(\"no\"))\n}\n\n// Output:\n// 2\n// yes: 2\n// no: 0\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":"uint256","path":"gno.land/p/demo/uint256","files":[{"name":"LICENSE","body":"BSD 3-Clause License\n\nCopyright 2020 uint256 Authors\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this\n list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright notice,\n this list of conditions and the following disclaimer in the documentation\n and/or other materials provided with the distribution.\n\n3. Neither the name of the copyright holder nor the names of its\n contributors may be used to endorse or promote products derived from\n this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"},{"name":"README.md","body":"# Fixed size 256-bit math library\n\nThis is a library specialized at replacing the `big.Int` library for math based on 256-bit types.\n\noriginal repository: [uint256](\u003chttps://github.com/holiman/uint256/tree/master\u003e)\n"},{"name":"arithmetic.gno","body":"// arithmetic provides arithmetic operations for Uint objects.\n// This includes basic binary operations such as addition, subtraction, multiplication, division, and modulo operations\n// as well as overflow checks, and negation. These functions are essential for numeric\n// calculations using 256-bit unsigned integers.\npackage uint256\n\nimport (\n\t\"math/bits\"\n)\n\n// Add sets z to the sum x+y\nfunc (z *Uint) Add(x, y *Uint) *Uint {\n\tvar carry uint64\n\tz.arr[0], carry = bits.Add64(x.arr[0], y.arr[0], 0)\n\tz.arr[1], carry = bits.Add64(x.arr[1], y.arr[1], carry)\n\tz.arr[2], carry = bits.Add64(x.arr[2], y.arr[2], carry)\n\tz.arr[3], _ = bits.Add64(x.arr[3], y.arr[3], carry)\n\treturn z\n}\n\n// AddOverflow sets z to the sum x+y, and returns z and whether overflow occurred\nfunc (z *Uint) AddOverflow(x, y *Uint) (*Uint, bool) {\n\tvar carry uint64\n\tz.arr[0], carry = bits.Add64(x.arr[0], y.arr[0], 0)\n\tz.arr[1], carry = bits.Add64(x.arr[1], y.arr[1], carry)\n\tz.arr[2], carry = bits.Add64(x.arr[2], y.arr[2], carry)\n\tz.arr[3], carry = bits.Add64(x.arr[3], y.arr[3], carry)\n\treturn z, carry != 0\n}\n\n// Sub sets z to the difference x-y\nfunc (z *Uint) Sub(x, y *Uint) *Uint {\n\tvar carry uint64\n\tz.arr[0], carry = bits.Sub64(x.arr[0], y.arr[0], 0)\n\tz.arr[1], carry = bits.Sub64(x.arr[1], y.arr[1], carry)\n\tz.arr[2], carry = bits.Sub64(x.arr[2], y.arr[2], carry)\n\tz.arr[3], _ = bits.Sub64(x.arr[3], y.arr[3], carry)\n\treturn z\n}\n\n// SubOverflow sets z to the difference x-y and returns z and true if the operation underflowed\nfunc (z *Uint) SubOverflow(x, y *Uint) (*Uint, bool) {\n\tvar carry uint64\n\tz.arr[0], carry = bits.Sub64(x.arr[0], y.arr[0], 0)\n\tz.arr[1], carry = bits.Sub64(x.arr[1], y.arr[1], carry)\n\tz.arr[2], carry = bits.Sub64(x.arr[2], y.arr[2], carry)\n\tz.arr[3], carry = bits.Sub64(x.arr[3], y.arr[3], carry)\n\treturn z, carry != 0\n}\n\n// Neg returns -x mod 2^256.\nfunc (z *Uint) Neg(x *Uint) *Uint {\n\treturn z.Sub(new(Uint), x)\n}\n\n// commented out for possible overflow\n// Mul sets z to the product x*y\nfunc (z *Uint) Mul(x, y *Uint) *Uint {\n\tvar (\n\t\tres Uint\n\t\tcarry uint64\n\t\tres1, res2, res3 uint64\n\t)\n\n\tcarry, res.arr[0] = bits.Mul64(x.arr[0], y.arr[0])\n\tcarry, res1 = umulHop(carry, x.arr[1], y.arr[0])\n\tcarry, res2 = umulHop(carry, x.arr[2], y.arr[0])\n\tres3 = x.arr[3]*y.arr[0] + carry\n\n\tcarry, res.arr[1] = umulHop(res1, x.arr[0], y.arr[1])\n\tcarry, res2 = umulStep(res2, x.arr[1], y.arr[1], carry)\n\tres3 = res3 + x.arr[2]*y.arr[1] + carry\n\n\tcarry, res.arr[2] = umulHop(res2, x.arr[0], y.arr[2])\n\tres3 = res3 + x.arr[1]*y.arr[2] + carry\n\n\tres.arr[3] = res3 + x.arr[0]*y.arr[3]\n\n\treturn z.Set(\u0026res)\n}\n\n// MulOverflow sets z to the product x*y, and returns z and whether overflow occurred\nfunc (z *Uint) MulOverflow(x, y *Uint) (*Uint, bool) {\n\tp := umul(x, y)\n\tcopy(z.arr[:], p[:4])\n\treturn z, (p[4] | p[5] | p[6] | p[7]) != 0\n}\n\n// commented out for possible overflow\n// Div sets z to the quotient x/y for returns z.\n// If y == 0, z is set to 0\nfunc (z *Uint) Div(x, y *Uint) *Uint {\n\tif y.IsZero() || y.Gt(x) {\n\t\treturn z.Clear()\n\t}\n\tif x.Eq(y) {\n\t\treturn z.SetOne()\n\t}\n\t// Shortcut some cases\n\tif x.IsUint64() {\n\t\treturn z.SetUint64(x.Uint64() / y.Uint64())\n\t}\n\n\t// At this point, we know\n\t// x/y ; x \u003e y \u003e 0\n\n\tvar quot Uint\n\tudivrem(quot.arr[:], x.arr[:], y)\n\treturn z.Set(\u0026quot)\n}\n\n// MulMod calculates the modulo-m multiplication of x and y and\n// returns z.\n// If m == 0, z is set to 0 (OBS: differs from the big.Int)\nfunc (z *Uint) MulMod(x, y, m *Uint) *Uint {\n\tif x.IsZero() || y.IsZero() || m.IsZero() {\n\t\treturn z.Clear()\n\t}\n\tp := umul(x, y)\n\n\tif m.arr[3] != 0 {\n\t\tmu := Reciprocal(m)\n\t\tr := reduce4(p, m, mu)\n\t\treturn z.Set(\u0026r)\n\t}\n\n\tvar (\n\t\tpl Uint\n\t\tph Uint\n\t)\n\n\tpl = Uint{arr: [4]uint64{p[0], p[1], p[2], p[3]}}\n\tph = Uint{arr: [4]uint64{p[4], p[5], p[6], p[7]}}\n\n\t// If the multiplication is within 256 bits use Mod().\n\tif ph.IsZero() {\n\t\treturn z.Mod(\u0026pl, m)\n\t}\n\n\tvar quot [8]uint64\n\trem := udivrem(quot[:], p[:], m)\n\treturn z.Set(\u0026rem)\n}\n\n// Mod sets z to the modulus x%y for y != 0 and returns z.\n// If y == 0, z is set to 0 (OBS: differs from the big.Uint)\nfunc (z *Uint) Mod(x, y *Uint) *Uint {\n\tif x.IsZero() || y.IsZero() {\n\t\treturn z.Clear()\n\t}\n\tswitch x.Cmp(y) {\n\tcase -1:\n\t\t// x \u003c y\n\t\tcopy(z.arr[:], x.arr[:])\n\t\treturn z\n\tcase 0:\n\t\t// x == y\n\t\treturn z.Clear() // They are equal\n\t}\n\n\t// At this point:\n\t// x != 0\n\t// y != 0\n\t// x \u003e y\n\n\t// Shortcut trivial case\n\tif x.IsUint64() {\n\t\treturn z.SetUint64(x.Uint64() % y.Uint64())\n\t}\n\n\tvar quot Uint\n\t*z = udivrem(quot.arr[:], x.arr[:], y)\n\treturn z\n}\n\n// DivMod sets z to the quotient x div y and m to the modulus x mod y and returns the pair (z, m) for y != 0.\n// If y == 0, both z and m are set to 0 (OBS: differs from the big.Int)\nfunc (z *Uint) DivMod(x, y, m *Uint) (*Uint, *Uint) {\n\tif y.IsZero() {\n\t\treturn z.Clear(), m.Clear()\n\t}\n\tvar quot Uint\n\t*m = udivrem(quot.arr[:], x.arr[:], y)\n\t*z = quot\n\treturn z, m\n}\n\n// Exp sets z = base**exponent mod 2**256, and returns z.\nfunc (z *Uint) Exp(base, exponent *Uint) *Uint {\n\tres := Uint{arr: [4]uint64{1, 0, 0, 0}}\n\tmultiplier := *base\n\texpBitLen := exponent.BitLen()\n\n\tcurBit := 0\n\tword := exponent.arr[0]\n\tfor ; curBit \u003c expBitLen \u0026\u0026 curBit \u003c 64; curBit++ {\n\t\tif word\u00261 == 1 {\n\t\t\tres.Mul(\u0026res, \u0026multiplier)\n\t\t}\n\t\tmultiplier.squared()\n\t\tword \u003e\u003e= 1\n\t}\n\n\tword = exponent.arr[1]\n\tfor ; curBit \u003c expBitLen \u0026\u0026 curBit \u003c 128; curBit++ {\n\t\tif word\u00261 == 1 {\n\t\t\tres.Mul(\u0026res, \u0026multiplier)\n\t\t}\n\t\tmultiplier.squared()\n\t\tword \u003e\u003e= 1\n\t}\n\n\tword = exponent.arr[2]\n\tfor ; curBit \u003c expBitLen \u0026\u0026 curBit \u003c 192; curBit++ {\n\t\tif word\u00261 == 1 {\n\t\t\tres.Mul(\u0026res, \u0026multiplier)\n\t\t}\n\t\tmultiplier.squared()\n\t\tword \u003e\u003e= 1\n\t}\n\n\tword = exponent.arr[3]\n\tfor ; curBit \u003c expBitLen \u0026\u0026 curBit \u003c 256; curBit++ {\n\t\tif word\u00261 == 1 {\n\t\t\tres.Mul(\u0026res, \u0026multiplier)\n\t\t}\n\t\tmultiplier.squared()\n\t\tword \u003e\u003e= 1\n\t}\n\treturn z.Set(\u0026res)\n}\n\nfunc (z *Uint) squared() {\n\tvar (\n\t\tres Uint\n\t\tcarry0, carry1, carry2 uint64\n\t\tres1, res2 uint64\n\t)\n\n\tcarry0, res.arr[0] = bits.Mul64(z.arr[0], z.arr[0])\n\tcarry0, res1 = umulHop(carry0, z.arr[0], z.arr[1])\n\tcarry0, res2 = umulHop(carry0, z.arr[0], z.arr[2])\n\n\tcarry1, res.arr[1] = umulHop(res1, z.arr[0], z.arr[1])\n\tcarry1, res2 = umulStep(res2, z.arr[1], z.arr[1], carry1)\n\n\tcarry2, res.arr[2] = umulHop(res2, z.arr[0], z.arr[2])\n\n\tres.arr[3] = 2*(z.arr[0]*z.arr[3]+z.arr[1]*z.arr[2]) + carry0 + carry1 + carry2\n\n\tz.Set(\u0026res)\n}\n\n// udivrem divides u by d and produces both quotient and remainder.\n// The quotient is stored in provided quot - len(u)-len(d)+1 words.\n// It loosely follows the Knuth's division algorithm (sometimes referenced as \"schoolbook\" division) using 64-bit words.\n// See Knuth, Volume 2, section 4.3.1, Algorithm D.\nfunc udivrem(quot, u []uint64, d *Uint) (rem Uint) {\n\tvar dLen int\n\tfor i := len(d.arr) - 1; i \u003e= 0; i-- {\n\t\tif d.arr[i] != 0 {\n\t\t\tdLen = i + 1\n\t\t\tbreak\n\t\t}\n\t}\n\n\tshift := uint(bits.LeadingZeros64(d.arr[dLen-1]))\n\n\tvar dnStorage Uint\n\tdn := dnStorage.arr[:dLen]\n\tfor i := dLen - 1; i \u003e 0; i-- {\n\t\tdn[i] = (d.arr[i] \u003c\u003c shift) | (d.arr[i-1] \u003e\u003e (64 - shift))\n\t}\n\tdn[0] = d.arr[0] \u003c\u003c shift\n\n\tvar uLen int\n\tfor i := len(u) - 1; i \u003e= 0; i-- {\n\t\tif u[i] != 0 {\n\t\t\tuLen = i + 1\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif uLen \u003c dLen {\n\t\tcopy(rem.arr[:], u)\n\t\treturn rem\n\t}\n\n\tvar unStorage [9]uint64\n\tun := unStorage[:uLen+1]\n\tun[uLen] = u[uLen-1] \u003e\u003e (64 - shift)\n\tfor i := uLen - 1; i \u003e 0; i-- {\n\t\tun[i] = (u[i] \u003c\u003c shift) | (u[i-1] \u003e\u003e (64 - shift))\n\t}\n\tun[0] = u[0] \u003c\u003c shift\n\n\t// TODO: Skip the highest word of numerator if not significant.\n\n\tif dLen == 1 {\n\t\tr := udivremBy1(quot, un, dn[0])\n\t\trem.SetUint64(r \u003e\u003e shift)\n\t\treturn rem\n\t}\n\n\tudivremKnuth(quot, un, dn)\n\n\tfor i := 0; i \u003c dLen-1; i++ {\n\t\trem.arr[i] = (un[i] \u003e\u003e shift) | (un[i+1] \u003c\u003c (64 - shift))\n\t}\n\trem.arr[dLen-1] = un[dLen-1] \u003e\u003e shift\n\n\treturn rem\n}\n\n// umul computes full 256 x 256 -\u003e 512 multiplication.\nfunc umul(x, y *Uint) [8]uint64 {\n\tvar (\n\t\tres [8]uint64\n\t\tcarry, carry4, carry5, carry6 uint64\n\t\tres1, res2, res3, res4, res5 uint64\n\t)\n\n\tcarry, res[0] = bits.Mul64(x.arr[0], y.arr[0])\n\tcarry, res1 = umulHop(carry, x.arr[1], y.arr[0])\n\tcarry, res2 = umulHop(carry, x.arr[2], y.arr[0])\n\tcarry4, res3 = umulHop(carry, x.arr[3], y.arr[0])\n\n\tcarry, res[1] = umulHop(res1, x.arr[0], y.arr[1])\n\tcarry, res2 = umulStep(res2, x.arr[1], y.arr[1], carry)\n\tcarry, res3 = umulStep(res3, x.arr[2], y.arr[1], carry)\n\tcarry5, res4 = umulStep(carry4, x.arr[3], y.arr[1], carry)\n\n\tcarry, res[2] = umulHop(res2, x.arr[0], y.arr[2])\n\tcarry, res3 = umulStep(res3, x.arr[1], y.arr[2], carry)\n\tcarry, res4 = umulStep(res4, x.arr[2], y.arr[2], carry)\n\tcarry6, res5 = umulStep(carry5, x.arr[3], y.arr[2], carry)\n\n\tcarry, res[3] = umulHop(res3, x.arr[0], y.arr[3])\n\tcarry, res[4] = umulStep(res4, x.arr[1], y.arr[3], carry)\n\tcarry, res[5] = umulStep(res5, x.arr[2], y.arr[3], carry)\n\tres[7], res[6] = umulStep(carry6, x.arr[3], y.arr[3], carry)\n\n\treturn res\n}\n\n// umulStep computes (hi * 2^64 + lo) = z + (x * y) + carry.\nfunc umulStep(z, x, y, carry uint64) (hi, lo uint64) {\n\thi, lo = bits.Mul64(x, y)\n\tlo, carry = bits.Add64(lo, carry, 0)\n\thi, _ = bits.Add64(hi, 0, carry)\n\tlo, carry = bits.Add64(lo, z, 0)\n\thi, _ = bits.Add64(hi, 0, carry)\n\treturn hi, lo\n}\n\n// umulHop computes (hi * 2^64 + lo) = z + (x * y)\nfunc umulHop(z, x, y uint64) (hi, lo uint64) {\n\thi, lo = bits.Mul64(x, y)\n\tlo, carry := bits.Add64(lo, z, 0)\n\thi, _ = bits.Add64(hi, 0, carry)\n\treturn hi, lo\n}\n\n// udivremBy1 divides u by single normalized word d and produces both quotient and remainder.\n// The quotient is stored in provided quot.\nfunc udivremBy1(quot, u []uint64, d uint64) (rem uint64) {\n\treciprocal := reciprocal2by1(d)\n\trem = u[len(u)-1] // Set the top word as remainder.\n\tfor j := len(u) - 2; j \u003e= 0; j-- {\n\t\tquot[j], rem = udivrem2by1(rem, u[j], d, reciprocal)\n\t}\n\treturn rem\n}\n\n// udivremKnuth implements the division of u by normalized multiple word d from the Knuth's division algorithm.\n// The quotient is stored in provided quot - len(u)-len(d) words.\n// Updates u to contain the remainder - len(d) words.\nfunc udivremKnuth(quot, u, d []uint64) {\n\tdh := d[len(d)-1]\n\tdl := d[len(d)-2]\n\treciprocal := reciprocal2by1(dh)\n\n\tfor j := len(u) - len(d) - 1; j \u003e= 0; j-- {\n\t\tu2 := u[j+len(d)]\n\t\tu1 := u[j+len(d)-1]\n\t\tu0 := u[j+len(d)-2]\n\n\t\tvar qhat, rhat uint64\n\t\tif u2 \u003e= dh { // Division overflows.\n\t\t\tqhat = ^uint64(0)\n\t\t\t// TODO: Add \"qhat one to big\" adjustment (not needed for correctness, but helps avoiding \"add back\" case).\n\t\t} else {\n\t\t\tqhat, rhat = udivrem2by1(u2, u1, dh, reciprocal)\n\t\t\tph, pl := bits.Mul64(qhat, dl)\n\t\t\tif ph \u003e rhat || (ph == rhat \u0026\u0026 pl \u003e u0) {\n\t\t\t\tqhat--\n\t\t\t\t// TODO: Add \"qhat one to big\" adjustment (not needed for correctness, but helps avoiding \"add back\" case).\n\t\t\t}\n\t\t}\n\n\t\t// Multiply and subtract.\n\t\tborrow := subMulTo(u[j:], d, qhat)\n\t\tu[j+len(d)] = u2 - borrow\n\t\tif u2 \u003c borrow { // Too much subtracted, add back.\n\t\t\tqhat--\n\t\t\tu[j+len(d)] += addTo(u[j:], d)\n\t\t}\n\n\t\tquot[j] = qhat // Store quotient digit.\n\t}\n}\n\n// isBitSet returns true if bit n-th is set, where n = 0 is LSB.\n// The n must be \u003c= 255.\nfunc (z *Uint) isBitSet(n uint) bool {\n\treturn (z.arr[n/64] \u0026 (1 \u003c\u003c (n % 64))) != 0\n}\n\n// addTo computes x += y.\n// Requires len(x) \u003e= len(y).\nfunc addTo(x, y []uint64) uint64 {\n\tvar carry uint64\n\tfor i := 0; i \u003c len(y); i++ {\n\t\tx[i], carry = bits.Add64(x[i], y[i], carry)\n\t}\n\treturn carry\n}\n\n// subMulTo computes x -= y * multiplier.\n// Requires len(x) \u003e= len(y).\nfunc subMulTo(x, y []uint64, multiplier uint64) uint64 {\n\tvar borrow uint64\n\tfor i := 0; i \u003c len(y); i++ {\n\t\ts, carry1 := bits.Sub64(x[i], borrow, 0)\n\t\tph, pl := bits.Mul64(y[i], multiplier)\n\t\tt, carry2 := bits.Sub64(s, pl, 0)\n\t\tx[i] = t\n\t\tborrow = ph + carry1 + carry2\n\t}\n\treturn borrow\n}\n\n// reciprocal2by1 computes \u003c^d, ^0\u003e / d.\nfunc reciprocal2by1(d uint64) uint64 {\n\treciprocal, _ := bits.Div64(^d, ^uint64(0), d)\n\treturn reciprocal\n}\n\n// udivrem2by1 divides \u003cuh, ul\u003e / d and produces both quotient and remainder.\n// It uses the provided d's reciprocal.\n// Implementation ported from https://github.com/chfast/intx and is based on\n// \"Improved division by invariant integers\", Algorithm 4.\nfunc udivrem2by1(uh, ul, d, reciprocal uint64) (quot, rem uint64) {\n\tqh, ql := bits.Mul64(reciprocal, uh)\n\tql, carry := bits.Add64(ql, ul, 0)\n\tqh, _ = bits.Add64(qh, uh, carry)\n\tqh++\n\n\tr := ul - qh*d\n\n\tif r \u003e ql {\n\t\tqh--\n\t\tr += d\n\t}\n\n\tif r \u003e= d {\n\t\tqh++\n\t\tr -= d\n\t}\n\n\treturn qh, r\n}\n"},{"name":"arithmetic_test.gno","body":"package uint256\n\nimport (\n\t\"testing\"\n)\n\ntype binOp2Test struct {\n\tx, y, want string\n}\n\nfunc TestAdd(t *testing.T) {\n\ttests := []binOp2Test{\n\t\t{\"0\", \"1\", \"1\"},\n\t\t{\"1\", \"0\", \"1\"},\n\t\t{\"1\", \"1\", \"2\"},\n\t\t{\"1\", \"3\", \"4\"},\n\t\t{\"10\", \"10\", \"20\"},\n\t\t{\"18446744073709551615\", \"18446744073709551615\", \"36893488147419103230\"}, // uint64 overflow\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\ty := MustFromDecimal(tt.y)\n\n\t\twant := MustFromDecimal(tt.want)\n\t\tgot := new(Uint).Add(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Add(%s, %s) = %v, want %v\", tt.x, tt.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestAddOverflow(t *testing.T) {\n\ttests := []struct {\n\t\tx, y string\n\t\twant string\n\t\toverflow bool\n\t}{\n\t\t{\"0\", \"1\", \"1\", false},\n\t\t{\"1\", \"0\", \"1\", false},\n\t\t{\"1\", \"1\", \"2\", false},\n\t\t{\"10\", \"10\", \"20\", false},\n\t\t{\"18446744073709551615\", \"18446744073709551615\", \"36893488147419103230\", false}, // uint64 overflow, but not Uint256 overflow\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"1\", \"0\", true}, // 2^256 - 1 + 1, should overflow\n\t\t{\"57896044618658097711785492504343953926634992332820282019728792003956564819967\", \"57896044618658097711785492504343953926634992332820282019728792003956564819968\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\", false}, // (2^255 - 1) + 2^255, no overflow\n\t\t{\"57896044618658097711785492504343953926634992332820282019728792003956564819967\", \"57896044618658097711785492504343953926634992332820282019728792003956564819969\", \"0\", true}, // (2^255 - 1) + (2^255 + 1), should overflow\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\ty := MustFromDecimal(tt.y)\n\t\twant, _ := FromDecimal(tt.want)\n\n\t\tgot, overflow := new(Uint).AddOverflow(x, y)\n\n\t\tif got.Cmp(want) != 0 || overflow != tt.overflow {\n\t\t\tt.Errorf(\"AddOverflow(%s, %s) = (%s, %v), want (%s, %v)\",\n\t\t\t\ttt.x, tt.y, got.ToString(), overflow, tt.want, tt.overflow)\n\t\t}\n\t}\n}\n\nfunc TestSub(t *testing.T) {\n\ttests := []binOp2Test{\n\t\t{\"1\", \"0\", \"1\"},\n\t\t{\"1\", \"1\", \"0\"},\n\t\t{\"10\", \"10\", \"0\"},\n\t\t{\"31337\", \"1337\", \"30000\"},\n\t\t{\"2\", \"3\", twoPow256Sub1}, // underflow\n\t}\n\n\tfor _, tc := range tests {\n\t\tx := MustFromDecimal(tc.x)\n\t\ty := MustFromDecimal(tc.y)\n\n\t\twant := MustFromDecimal(tc.want)\n\n\t\tgot := new(Uint).Sub(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\n\t\t\t\t\"Sub(%s, %s) = %v, want %v\",\n\t\t\t\ttc.x, tc.y, got.ToString(), want.ToString(),\n\t\t\t)\n\t\t}\n\t}\n}\n\nfunc TestSubOverflow(t *testing.T) {\n\ttests := []struct {\n\t\tx, y string\n\t\twant string\n\t\toverflow bool\n\t}{\n\t\t{\"1\", \"0\", \"1\", false},\n\t\t{\"1\", \"1\", \"0\", false},\n\t\t{\"10\", \"10\", \"0\", false},\n\t\t{\"31337\", \"1337\", \"30000\", false},\n\t\t{\"0\", \"1\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\", true}, // 0 - 1, should underflow\n\t\t{\"57896044618658097711785492504343953926634992332820282019728792003956564819968\", \"1\", \"57896044618658097711785492504343953926634992332820282019728792003956564819967\", false}, // 2^255 - 1, no underflow\n\t\t{\"57896044618658097711785492504343953926634992332820282019728792003956564819968\", \"57896044618658097711785492504343953926634992332820282019728792003956564819969\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\", true}, // 2^255 - (2^255 + 1), should underflow\n\t}\n\n\tfor _, tc := range tests {\n\t\tx := MustFromDecimal(tc.x)\n\t\ty := MustFromDecimal(tc.y)\n\t\twant := MustFromDecimal(tc.want)\n\n\t\tgot, overflow := new(Uint).SubOverflow(x, y)\n\n\t\tif got.Cmp(want) != 0 || overflow != tc.overflow {\n\t\t\tt.Errorf(\n\t\t\t\t\"SubOverflow(%s, %s) = (%s, %v), want (%s, %v)\",\n\t\t\t\ttc.x, tc.y, got.ToString(), overflow, tc.want, tc.overflow,\n\t\t\t)\n\t\t}\n\t}\n}\n\nfunc TestMul(t *testing.T) {\n\ttests := []binOp2Test{\n\t\t{\"1\", \"0\", \"0\"},\n\t\t{\"1\", \"1\", \"1\"},\n\t\t{\"10\", \"10\", \"100\"},\n\t\t{\"18446744073709551615\", \"2\", \"36893488147419103230\"}, // uint64 overflow\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\ty := MustFromDecimal(tt.y)\n\t\twant := MustFromDecimal(tt.want)\n\t\tgot := new(Uint).Mul(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Mul(%s, %s) = %v, want %v\", tt.x, tt.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestMulOverflow(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty string\n\t\twantZ string\n\t\twantOver bool\n\t}{\n\t\t{\"0x1\", \"0x1\", \"0x1\", false},\n\t\t{\"0x0\", \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0x0\", false},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0x2\", \"0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe\", true},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0x1\", true},\n\t\t{\"0x8000000000000000000000000000000000000000000000000000000000000000\", \"0x2\", \"0x0\", true},\n\t\t{\"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0x2\", \"0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe\", false},\n\t\t{\"0x100000000000000000\", \"0x100000000000000000\", \"0x10000000000000000000000000000000000\", false},\n\t\t{\"0x10000000000000000000000000000000\", \"0x10000000000000000000000000000000\", \"0x100000000000000000000000000000000000000000000000000000000000000\", false},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromHex(tt.x)\n\t\ty := MustFromHex(tt.y)\n\t\twantZ := MustFromHex(tt.wantZ)\n\n\t\tgotZ, gotOver := new(Uint).MulOverflow(x, y)\n\n\t\tif gotZ.Neq(wantZ) {\n\t\t\tt.Errorf(\n\t\t\t\t\"MulOverflow(%s, %s) = %s, want %s\",\n\t\t\t\ttt.x, tt.y, gotZ.ToString(), wantZ.ToString(),\n\t\t\t)\n\t\t}\n\t\tif gotOver != tt.wantOver {\n\t\t\tt.Errorf(\"MulOverflow(%s, %s) = %v, want %v\", tt.x, tt.y, gotOver, tt.wantOver)\n\t\t}\n\t}\n}\n\nfunc TestDiv(t *testing.T) {\n\ttests := []binOp2Test{\n\t\t{\"31337\", \"3\", \"10445\"},\n\t\t{\"31337\", \"0\", \"0\"},\n\t\t{\"0\", \"31337\", \"0\"},\n\t\t{\"1\", \"1\", \"1\"},\n\t\t{\"1000000000000000000\", \"3\", \"333333333333333333\"},\n\t\t{twoPow256Sub1, \"2\", \"57896044618658097711785492504343953926634992332820282019728792003956564819967\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\ty := MustFromDecimal(tt.y)\n\t\twant := MustFromDecimal(tt.want)\n\n\t\tgot := new(Uint).Div(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Div(%s, %s) = %v, want %v\", tt.x, tt.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestMod(t *testing.T) {\n\ttests := []binOp2Test{\n\t\t{\"31337\", \"3\", \"2\"},\n\t\t{\"31337\", \"0\", \"0\"},\n\t\t{\"0\", \"31337\", \"0\"},\n\t\t{\"2\", \"31337\", \"2\"},\n\t\t{\"1\", \"1\", \"0\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"2\", \"1\"}, // 2^256 - 1 mod 2\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"3\", \"0\"}, // 2^256 - 1 mod 3\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"57896044618658097711785492504343953926634992332820282019728792003956564819968\", \"57896044618658097711785492504343953926634992332820282019728792003956564819967\"}, // 2^256 - 1 mod 2^255\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\ty := MustFromDecimal(tt.y)\n\t\twant := MustFromDecimal(tt.want)\n\n\t\tgot := new(Uint).Mod(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Mod(%s, %s) = %v, want %v\", tt.x, tt.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestMulMod(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty string\n\t\tm string\n\t\twant string\n\t}{\n\t\t{\"0x1\", \"0x1\", \"0x2\", \"0x1\"},\n\t\t{\"0x10\", \"0x10\", \"0x7\", \"0x4\"},\n\t\t{\"0x100\", \"0x100\", \"0x17\", \"0x9\"},\n\t\t{\"0x31337\", \"0x31337\", \"0x31338\", \"0x1\"},\n\t\t{\"0x0\", \"0x31337\", \"0x31338\", \"0x0\"},\n\t\t{\"0x31337\", \"0x0\", \"0x31338\", \"0x0\"},\n\t\t{\"0x2\", \"0x3\", \"0x5\", \"0x1\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0x0\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe\", \"0x1\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0xffffffffffffffffffffffffffffffff\", \"0x0\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromHex(tt.x)\n\t\ty := MustFromHex(tt.y)\n\t\tm := MustFromHex(tt.m)\n\t\twant := MustFromHex(tt.want)\n\n\t\tgot := new(Uint).MulMod(x, y, m)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\n\t\t\t\t\"MulMod(%s, %s, %s) = %s, want %s\",\n\t\t\t\ttt.x, tt.y, tt.m, got.ToString(), want.ToString(),\n\t\t\t)\n\t\t}\n\t}\n}\n\nfunc TestDivMod(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty string\n\t\twantDiv string\n\t\twantMod string\n\t}{\n\t\t{\"1\", \"1\", \"1\", \"0\"},\n\t\t{\"10\", \"10\", \"1\", \"0\"},\n\t\t{\"100\", \"10\", \"10\", \"0\"},\n\t\t{\"31337\", \"3\", \"10445\", \"2\"},\n\t\t{\"31337\", \"0\", \"0\", \"0\"},\n\t\t{\"0\", \"31337\", \"0\", \"0\"},\n\t\t{\"2\", \"31337\", \"0\", \"2\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\ty := MustFromDecimal(tt.y)\n\t\twantDiv := MustFromDecimal(tt.wantDiv)\n\t\twantMod := MustFromDecimal(tt.wantMod)\n\n\t\tgotDiv := new(Uint)\n\t\tgotMod := new(Uint)\n\t\tgotDiv.DivMod(x, y, gotMod)\n\n\t\tfor i := range gotDiv.arr {\n\t\t\tif gotDiv.arr[i] != wantDiv.arr[i] {\n\t\t\t\tt.Errorf(\"DivMod(%s, %s) got Div %v, want Div %v\", tt.x, tt.y, gotDiv, wantDiv)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfor i := range gotMod.arr {\n\t\t\tif gotMod.arr[i] != wantMod.arr[i] {\n\t\t\t\tt.Errorf(\"DivMod(%s, %s) got Mod %v, want Mod %v\", tt.x, tt.y, gotMod, wantMod)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestNeg(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant string\n\t}{\n\t\t{\"31337\", \"115792089237316195423570985008687907853269984665640564039457584007913129608599\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129608599\", \"31337\"},\n\t\t{\"0\", \"0\"},\n\t\t{\"2\", \"115792089237316195423570985008687907853269984665640564039457584007913129639934\"},\n\t\t{\"1\", twoPow256Sub1},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\twant := MustFromDecimal(tt.want)\n\n\t\tgot := new(Uint).Neg(x)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Neg(%s) = %v, want %v\", tt.x, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestExp(t *testing.T) {\n\ttests := []binOp2Test{\n\t\t{\"31337\", \"3\", \"30773171189753\"},\n\t\t{\"31337\", \"0\", \"1\"},\n\t\t{\"0\", \"31337\", \"0\"},\n\t\t{\"1\", \"1\", \"1\"},\n\t\t{\"2\", \"3\", \"8\"},\n\t\t{\"2\", \"64\", \"18446744073709551616\"},\n\t\t{\"2\", \"128\", \"340282366920938463463374607431768211456\"},\n\t\t{\"2\", \"255\", \"57896044618658097711785492504343953926634992332820282019728792003956564819968\"},\n\t\t{\"2\", \"256\", \"0\"}, // overflow\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\ty := MustFromDecimal(tt.y)\n\t\twant := MustFromDecimal(tt.want)\n\n\t\tgot := new(Uint).Exp(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\n\t\t\t\t\"Exp(%s, %s) = %v, want %v\",\n\t\t\t\ttt.x, tt.y, got.ToString(), want.ToString(),\n\t\t\t)\n\t\t}\n\t}\n}\n\nfunc TestExp_LargeExponent(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tbase string\n\t\texponent string\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname: \"2^129\",\n\t\t\tbase: \"2\",\n\t\t\texponent: \"680564733841876926926749214863536422912\",\n\t\t\texpected: \"0\",\n\t\t},\n\t\t{\n\t\t\tname: \"2^193\",\n\t\t\tbase: \"2\",\n\t\t\texponent: \"12379400392853802746563808384000000000000000000\",\n\t\t\texpected: \"0\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tbase := MustFromDecimal(tt.base)\n\t\t\texponent := MustFromDecimal(tt.exponent)\n\t\t\texpected := MustFromDecimal(tt.expected)\n\n\t\t\tresult := new(Uint).Exp(base, exponent)\n\n\t\t\tif result.Neq(expected) {\n\t\t\t\tt.Errorf(\n\t\t\t\t\t\"Test %s failed. Expected %s, got %s\",\n\t\t\t\t\ttt.name, expected.ToString(), result.ToString(),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n}\n"},{"name":"bits_table.gno","body":"// Copyright 2017 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n// Code generated by go run make_tables.go. DO NOT EDIT.\n\npackage uint256\n\nconst ntz8tab = \"\" +\n\t\"\\x08\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x05\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x06\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x05\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x07\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x05\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x06\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x05\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\"\n\nconst pop8tab = \"\" +\n\t\"\\x00\\x01\\x01\\x02\\x01\\x02\\x02\\x03\\x01\\x02\\x02\\x03\\x02\\x03\\x03\\x04\" +\n\t\"\\x01\\x02\\x02\\x03\\x02\\x03\\x03\\x04\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\" +\n\t\"\\x01\\x02\\x02\\x03\\x02\\x03\\x03\\x04\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\" +\n\t\"\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\" +\n\t\"\\x01\\x02\\x02\\x03\\x02\\x03\\x03\\x04\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\" +\n\t\"\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\" +\n\t\"\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\" +\n\t\"\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\\x04\\x05\\x05\\x06\\x05\\x06\\x06\\x07\" +\n\t\"\\x01\\x02\\x02\\x03\\x02\\x03\\x03\\x04\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\" +\n\t\"\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\" +\n\t\"\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\" +\n\t\"\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\\x04\\x05\\x05\\x06\\x05\\x06\\x06\\x07\" +\n\t\"\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\" +\n\t\"\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\\x04\\x05\\x05\\x06\\x05\\x06\\x06\\x07\" +\n\t\"\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\\x04\\x05\\x05\\x06\\x05\\x06\\x06\\x07\" +\n\t\"\\x04\\x05\\x05\\x06\\x05\\x06\\x06\\x07\\x05\\x06\\x06\\x07\\x06\\x07\\x07\\x08\"\n\nconst rev8tab = \"\" +\n\t\"\\x00\\x80\\x40\\xc0\\x20\\xa0\\x60\\xe0\\x10\\x90\\x50\\xd0\\x30\\xb0\\x70\\xf0\" +\n\t\"\\x08\\x88\\x48\\xc8\\x28\\xa8\\x68\\xe8\\x18\\x98\\x58\\xd8\\x38\\xb8\\x78\\xf8\" +\n\t\"\\x04\\x84\\x44\\xc4\\x24\\xa4\\x64\\xe4\\x14\\x94\\x54\\xd4\\x34\\xb4\\x74\\xf4\" +\n\t\"\\x0c\\x8c\\x4c\\xcc\\x2c\\xac\\x6c\\xec\\x1c\\x9c\\x5c\\xdc\\x3c\\xbc\\x7c\\xfc\" +\n\t\"\\x02\\x82\\x42\\xc2\\x22\\xa2\\x62\\xe2\\x12\\x92\\x52\\xd2\\x32\\xb2\\x72\\xf2\" +\n\t\"\\x0a\\x8a\\x4a\\xca\\x2a\\xaa\\x6a\\xea\\x1a\\x9a\\x5a\\xda\\x3a\\xba\\x7a\\xfa\" +\n\t\"\\x06\\x86\\x46\\xc6\\x26\\xa6\\x66\\xe6\\x16\\x96\\x56\\xd6\\x36\\xb6\\x76\\xf6\" +\n\t\"\\x0e\\x8e\\x4e\\xce\\x2e\\xae\\x6e\\xee\\x1e\\x9e\\x5e\\xde\\x3e\\xbe\\x7e\\xfe\" +\n\t\"\\x01\\x81\\x41\\xc1\\x21\\xa1\\x61\\xe1\\x11\\x91\\x51\\xd1\\x31\\xb1\\x71\\xf1\" +\n\t\"\\x09\\x89\\x49\\xc9\\x29\\xa9\\x69\\xe9\\x19\\x99\\x59\\xd9\\x39\\xb9\\x79\\xf9\" +\n\t\"\\x05\\x85\\x45\\xc5\\x25\\xa5\\x65\\xe5\\x15\\x95\\x55\\xd5\\x35\\xb5\\x75\\xf5\" +\n\t\"\\x0d\\x8d\\x4d\\xcd\\x2d\\xad\\x6d\\xed\\x1d\\x9d\\x5d\\xdd\\x3d\\xbd\\x7d\\xfd\" +\n\t\"\\x03\\x83\\x43\\xc3\\x23\\xa3\\x63\\xe3\\x13\\x93\\x53\\xd3\\x33\\xb3\\x73\\xf3\" +\n\t\"\\x0b\\x8b\\x4b\\xcb\\x2b\\xab\\x6b\\xeb\\x1b\\x9b\\x5b\\xdb\\x3b\\xbb\\x7b\\xfb\" +\n\t\"\\x07\\x87\\x47\\xc7\\x27\\xa7\\x67\\xe7\\x17\\x97\\x57\\xd7\\x37\\xb7\\x77\\xf7\" +\n\t\"\\x0f\\x8f\\x4f\\xcf\\x2f\\xaf\\x6f\\xef\\x1f\\x9f\\x5f\\xdf\\x3f\\xbf\\x7f\\xff\"\n\nconst len8tab = \"\" +\n\t\"\\x00\\x01\\x02\\x02\\x03\\x03\\x03\\x03\\x04\\x04\\x04\\x04\\x04\\x04\\x04\\x04\" +\n\t\"\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\" +\n\t\"\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\" +\n\t\"\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\" +\n\t\"\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\" +\n\t\"\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\" +\n\t\"\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\" +\n\t\"\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\"\n"},{"name":"bitwise.gno","body":"// bitwise contains bitwise operations for Uint instances.\n// This file includes functions to perform bitwise AND, OR, XOR, and NOT operations, as well as bit shifting.\n// These operations are crucial for manipulating individual bits within a 256-bit unsigned integer.\npackage uint256\n\n// Or sets z = x | y and returns z.\nfunc (z *Uint) Or(x, y *Uint) *Uint {\n\tz.arr[0] = x.arr[0] | y.arr[0]\n\tz.arr[1] = x.arr[1] | y.arr[1]\n\tz.arr[2] = x.arr[2] | y.arr[2]\n\tz.arr[3] = x.arr[3] | y.arr[3]\n\treturn z\n}\n\n// And sets z = x \u0026 y and returns z.\nfunc (z *Uint) And(x, y *Uint) *Uint {\n\tz.arr[0] = x.arr[0] \u0026 y.arr[0]\n\tz.arr[1] = x.arr[1] \u0026 y.arr[1]\n\tz.arr[2] = x.arr[2] \u0026 y.arr[2]\n\tz.arr[3] = x.arr[3] \u0026 y.arr[3]\n\treturn z\n}\n\n// Not sets z = ^x and returns z.\nfunc (z *Uint) Not(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = ^x.arr[3], ^x.arr[2], ^x.arr[1], ^x.arr[0]\n\treturn z\n}\n\n// AndNot sets z = x \u0026^ y and returns z.\nfunc (z *Uint) AndNot(x, y *Uint) *Uint {\n\tz.arr[0] = x.arr[0] \u0026^ y.arr[0]\n\tz.arr[1] = x.arr[1] \u0026^ y.arr[1]\n\tz.arr[2] = x.arr[2] \u0026^ y.arr[2]\n\tz.arr[3] = x.arr[3] \u0026^ y.arr[3]\n\treturn z\n}\n\n// Xor sets z = x ^ y and returns z.\nfunc (z *Uint) Xor(x, y *Uint) *Uint {\n\tz.arr[0] = x.arr[0] ^ y.arr[0]\n\tz.arr[1] = x.arr[1] ^ y.arr[1]\n\tz.arr[2] = x.arr[2] ^ y.arr[2]\n\tz.arr[3] = x.arr[3] ^ y.arr[3]\n\treturn z\n}\n\n// Lsh sets z = x \u003c\u003c n and returns z.\nfunc (z *Uint) Lsh(x *Uint, n uint) *Uint {\n\t// n % 64 == 0\n\tif n\u00260x3f == 0 {\n\t\tswitch n {\n\t\tcase 0:\n\t\t\treturn z.Set(x)\n\t\tcase 64:\n\t\t\treturn z.lsh64(x)\n\t\tcase 128:\n\t\t\treturn z.lsh128(x)\n\t\tcase 192:\n\t\t\treturn z.lsh192(x)\n\t\tdefault:\n\t\t\treturn z.Clear()\n\t\t}\n\t}\n\tvar a, b uint64\n\t// Big swaps first\n\tswitch {\n\tcase n \u003e 192:\n\t\tif n \u003e 256 {\n\t\t\treturn z.Clear()\n\t\t}\n\t\tz.lsh192(x)\n\t\tn -= 192\n\t\tgoto sh192\n\tcase n \u003e 128:\n\t\tz.lsh128(x)\n\t\tn -= 128\n\t\tgoto sh128\n\tcase n \u003e 64:\n\t\tz.lsh64(x)\n\t\tn -= 64\n\t\tgoto sh64\n\tdefault:\n\t\tz.Set(x)\n\t}\n\n\t// remaining shifts\n\ta = z.arr[0] \u003e\u003e (64 - n)\n\tz.arr[0] = z.arr[0] \u003c\u003c n\n\nsh64:\n\tb = z.arr[1] \u003e\u003e (64 - n)\n\tz.arr[1] = (z.arr[1] \u003c\u003c n) | a\n\nsh128:\n\ta = z.arr[2] \u003e\u003e (64 - n)\n\tz.arr[2] = (z.arr[2] \u003c\u003c n) | b\n\nsh192:\n\tz.arr[3] = (z.arr[3] \u003c\u003c n) | a\n\n\treturn z\n}\n\n// Rsh sets z = x \u003e\u003e n and returns z.\nfunc (z *Uint) Rsh(x *Uint, n uint) *Uint {\n\t// n % 64 == 0\n\tif n\u00260x3f == 0 {\n\t\tswitch n {\n\t\tcase 0:\n\t\t\treturn z.Set(x)\n\t\tcase 64:\n\t\t\treturn z.rsh64(x)\n\t\tcase 128:\n\t\t\treturn z.rsh128(x)\n\t\tcase 192:\n\t\t\treturn z.rsh192(x)\n\t\tdefault:\n\t\t\treturn z.Clear()\n\t\t}\n\t}\n\tvar a, b uint64\n\t// Big swaps first\n\tswitch {\n\tcase n \u003e 192:\n\t\tif n \u003e 256 {\n\t\t\treturn z.Clear()\n\t\t}\n\t\tz.rsh192(x)\n\t\tn -= 192\n\t\tgoto sh192\n\tcase n \u003e 128:\n\t\tz.rsh128(x)\n\t\tn -= 128\n\t\tgoto sh128\n\tcase n \u003e 64:\n\t\tz.rsh64(x)\n\t\tn -= 64\n\t\tgoto sh64\n\tdefault:\n\t\tz.Set(x)\n\t}\n\n\t// remaining shifts\n\ta = z.arr[3] \u003c\u003c (64 - n)\n\tz.arr[3] = z.arr[3] \u003e\u003e n\n\nsh64:\n\tb = z.arr[2] \u003c\u003c (64 - n)\n\tz.arr[2] = (z.arr[2] \u003e\u003e n) | a\n\nsh128:\n\ta = z.arr[1] \u003c\u003c (64 - n)\n\tz.arr[1] = (z.arr[1] \u003e\u003e n) | b\n\nsh192:\n\tz.arr[0] = (z.arr[0] \u003e\u003e n) | a\n\n\treturn z\n}\n\n// SRsh (Signed/Arithmetic right shift)\n// considers z to be a signed integer, during right-shift\n// and sets z = x \u003e\u003e n and returns z.\nfunc (z *Uint) SRsh(x *Uint, n uint) *Uint {\n\t// If the MSB is 0, SRsh is same as Rsh.\n\tif !x.isBitSet(255) {\n\t\treturn z.Rsh(x, n)\n\t}\n\tif n%64 == 0 {\n\t\tswitch n {\n\t\tcase 0:\n\t\t\treturn z.Set(x)\n\t\tcase 64:\n\t\t\treturn z.srsh64(x)\n\t\tcase 128:\n\t\t\treturn z.srsh128(x)\n\t\tcase 192:\n\t\t\treturn z.srsh192(x)\n\t\tdefault:\n\t\t\treturn z.SetAllOne()\n\t\t}\n\t}\n\tvar a uint64 = MaxUint64 \u003c\u003c (64 - n%64)\n\t// Big swaps first\n\tswitch {\n\tcase n \u003e 192:\n\t\tif n \u003e 256 {\n\t\t\treturn z.SetAllOne()\n\t\t}\n\t\tz.srsh192(x)\n\t\tn -= 192\n\t\tgoto sh192\n\tcase n \u003e 128:\n\t\tz.srsh128(x)\n\t\tn -= 128\n\t\tgoto sh128\n\tcase n \u003e 64:\n\t\tz.srsh64(x)\n\t\tn -= 64\n\t\tgoto sh64\n\tdefault:\n\t\tz.Set(x)\n\t}\n\n\t// remaining shifts\n\tz.arr[3], a = (z.arr[3]\u003e\u003en)|a, z.arr[3]\u003c\u003c(64-n)\n\nsh64:\n\tz.arr[2], a = (z.arr[2]\u003e\u003en)|a, z.arr[2]\u003c\u003c(64-n)\n\nsh128:\n\tz.arr[1], a = (z.arr[1]\u003e\u003en)|a, z.arr[1]\u003c\u003c(64-n)\n\nsh192:\n\tz.arr[0] = (z.arr[0] \u003e\u003e n) | a\n\n\treturn z\n}\n\nfunc (z *Uint) lsh64(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = x.arr[2], x.arr[1], x.arr[0], 0\n\treturn z\n}\n\nfunc (z *Uint) lsh128(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = x.arr[1], x.arr[0], 0, 0\n\treturn z\n}\n\nfunc (z *Uint) lsh192(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = x.arr[0], 0, 0, 0\n\treturn z\n}\n\nfunc (z *Uint) rsh64(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, x.arr[3], x.arr[2], x.arr[1]\n\treturn z\n}\n\nfunc (z *Uint) rsh128(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, 0, x.arr[3], x.arr[2]\n\treturn z\n}\n\nfunc (z *Uint) rsh192(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, 0, 0, x.arr[3]\n\treturn z\n}\n\nfunc (z *Uint) srsh64(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = MaxUint64, x.arr[3], x.arr[2], x.arr[1]\n\treturn z\n}\n\nfunc (z *Uint) srsh128(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = MaxUint64, MaxUint64, x.arr[3], x.arr[2]\n\treturn z\n}\n\nfunc (z *Uint) srsh192(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = MaxUint64, MaxUint64, MaxUint64, x.arr[3]\n\treturn z\n}\n"},{"name":"bitwise_test.gno","body":"package uint256\n\nimport \"testing\"\n\ntype logicOpTest struct {\n\tname string\n\tx Uint\n\ty Uint\n\twant Uint\n}\n\nfunc TestOr(t *testing.T) {\n\ttests := []logicOpTest{\n\t\t{\n\t\t\tname: \"all zeros\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tres := new(Uint).Or(\u0026tt.x, \u0026tt.y)\n\t\t\tif *res != tt.want {\n\t\t\t\tt.Errorf(\n\t\t\t\t\t\"Or(%s, %s) = %s, want %s\",\n\t\t\t\t\ttt.x.ToString(), tt.y.ToString(), res.ToString(), (tt.want).ToString(),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestAnd(t *testing.T) {\n\ttests := []logicOpTest{\n\t\t{\n\t\t\tname: \"all zeros\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 2\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 3\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand zero\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}},\n\t\t\twant: Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tres := new(Uint).And(\u0026tt.x, \u0026tt.y)\n\t\t\tif *res != tt.want {\n\t\t\t\tt.Errorf(\n\t\t\t\t\t\"And(%s, %s) = %s, want %s\",\n\t\t\t\t\ttt.x.ToString(), tt.y.ToString(), res.ToString(), (tt.want).ToString(),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNot(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tx Uint\n\t\twant Uint\n\t}{\n\t\t{\n\t\t\tname: \"all zeros\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tres := new(Uint).Not(\u0026tt.x)\n\t\t\tif *res != tt.want {\n\t\t\t\tt.Errorf(\n\t\t\t\t\t\"Not(%s) = %s, want %s\",\n\t\t\t\t\ttt.x.ToString(), res.ToString(), (tt.want).ToString(),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestAndNot(t *testing.T) {\n\ttests := []logicOpTest{\n\t\t{\n\t\t\tname: \"all zeros\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 2\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 3\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand zero\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}},\n\t\t\twant: Uint{arr: [4]uint64{0xAAAAAAAAAAAAAAAA, 0x5555555555555555, 0x0000000000000000, ^uint64(0)}},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tres := new(Uint).AndNot(\u0026tt.x, \u0026tt.y)\n\t\t\tif *res != tt.want {\n\t\t\t\tt.Errorf(\n\t\t\t\t\t\"AndNot(%s, %s) = %s, want %s\",\n\t\t\t\t\ttt.x.ToString(), tt.y.ToString(), res.ToString(), (tt.want).ToString(),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestXor(t *testing.T) {\n\ttests := []logicOpTest{\n\t\t{\n\t\t\tname: \"all zeros\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 2\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 3\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand zero\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF}},\n\t\t\twant: Uint{arr: [4]uint64{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}},\n\t\t\twant: Uint{arr: [4]uint64{0xAAAAAAAAAAAAAAAA, 0x5555555555555555, 0x0000000000000000, ^uint64(0)}},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tres := new(Uint).Xor(\u0026tt.x, \u0026tt.y)\n\t\t\tif *res != tt.want {\n\t\t\t\tt.Errorf(\n\t\t\t\t\t\"Xor(%s, %s) = %s, want %s\",\n\t\t\t\t\ttt.x.ToString(), tt.y.ToString(), res.ToString(), (tt.want).ToString(),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestLsh(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty uint\n\t\twant string\n\t}{\n\t\t{\"0\", 0, \"0\"},\n\t\t{\"0\", 1, \"0\"},\n\t\t{\"0\", 64, \"0\"},\n\t\t{\"1\", 0, \"1\"},\n\t\t{\"1\", 1, \"2\"},\n\t\t{\"1\", 64, \"18446744073709551616\"},\n\t\t{\"1\", 128, \"340282366920938463463374607431768211456\"},\n\t\t{\"1\", 192, \"6277101735386680763835789423207666416102355444464034512896\"},\n\t\t{\"1\", 255, \"57896044618658097711785492504343953926634992332820282019728792003956564819968\"},\n\t\t{\"1\", 256, \"0\"},\n\t\t{\"31337\", 0, \"31337\"},\n\t\t{\"31337\", 1, \"62674\"},\n\t\t{\"31337\", 64, \"578065619037836218990592\"},\n\t\t{\"31337\", 128, \"10663428532201448629551770073089320442396672\"},\n\t\t{\"31337\", 192, \"196705537081812415096322133155058642481399512563169449530621952\"},\n\t\t{\"31337\", 193, \"393411074163624830192644266310117284962799025126338899061243904\"},\n\t\t{\"31337\", 255, \"57896044618658097711785492504343953926634992332820282019728792003956564819968\"},\n\t\t{\"31337\", 256, \"0\"},\n\t\t// 64 \u003c n \u003c 128\n\t\t{\"1\", 65, \"36893488147419103232\"},\n\t\t{\"31337\", 100, \"39724366859352024754702188346867712\"},\n\n\t\t// 128 \u003c n \u003c 192\n\t\t{\"1\", 129, \"680564733841876926926749214863536422912\"},\n\t\t{\"31337\", 150, \"44725660946326664792723507424638829088826130956288\"},\n\n\t\t// 192 \u003c n \u003c 256\n\t\t{\"1\", 193, \"12554203470773361527671578846415332832204710888928069025792\"},\n\t\t{\"31337\", 200, \"50356617492943978264658466087695012475238275216171379079839219712\"},\n\n\t\t// n \u003e 256\n\t\t{\"1\", 257, \"0\"},\n\t\t{\"31337\", 300, \"0\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\twant := MustFromDecimal(tt.want)\n\n\t\tgot := new(Uint).Lsh(x, tt.y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Lsh(%s, %d) = %s, want %s\", tt.x, tt.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestRsh(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty uint\n\t\twant string\n\t}{\n\t\t{\"0\", 0, \"0\"},\n\t\t{\"0\", 1, \"0\"},\n\t\t{\"0\", 64, \"0\"},\n\t\t{\"1\", 0, \"1\"},\n\t\t{\"1\", 1, \"0\"},\n\t\t{\"1\", 64, \"0\"},\n\t\t{\"1\", 128, \"0\"},\n\t\t{\"1\", 192, \"0\"},\n\t\t{\"1\", 255, \"0\"},\n\t\t{\"57896044618658097711785492504343953926634992332820282019728792003956564819968\", 255, \"1\"},\n\t\t{\"6277101735386680763835789423207666416102355444464034512896\", 192, \"1\"},\n\t\t{\"340282366920938463463374607431768211456\", 128, \"1\"},\n\t\t{\"18446744073709551616\", 64, \"1\"},\n\t\t{\"393411074163624830192644266310117284962799025126338899061243904\", 193, \"31337\"},\n\t\t{\"196705537081812415096322133155058642481399512563169449530621952\", 192, \"31337\"},\n\t\t{\"10663428532201448629551770073089320442396672\", 128, \"31337\"},\n\t\t{\"578065619037836218990592\", 64, \"31337\"},\n\t\t{twoPow256Sub1, 256, \"0\"},\n\t\t// outliers\n\t\t{\"340282366920938463463374607431768211455\", 129, \"0\"},\n\t\t{\"18446744073709551615\", 65, \"0\"},\n\t\t{twoPow256Sub1, 1, \"57896044618658097711785492504343953926634992332820282019728792003956564819967\"},\n\n\t\t// n \u003e 256\n\t\t{\"1\", 257, \"0\"},\n\t\t{\"31337\", 300, \"0\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\n\t\twant := MustFromDecimal(tt.want)\n\t\tgot := new(Uint).Rsh(x, tt.y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Rsh(%s, %d) = %s, want %s\", tt.x, tt.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestSRsh(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty uint\n\t\twant string\n\t}{\n\t\t// Positive numbers (behaves like Rsh)\n\t\t{\"0x0\", 0, \"0x0\"},\n\t\t{\"0x0\", 1, \"0x0\"},\n\t\t{\"0x1\", 0, \"0x1\"},\n\t\t{\"0x1\", 1, \"0x0\"},\n\t\t{\"0x31337\", 0, \"0x31337\"},\n\t\t{\"0x31337\", 4, \"0x3133\"},\n\t\t{\"0x31337\", 8, \"0x313\"},\n\t\t{\"0x31337\", 16, \"0x3\"},\n\t\t{\"0x10000000000000000\", 64, \"0x1\"}, // 2^64 \u003e\u003e 64\n\n\t\t// // Numbers with MSB set (negative numbers in two's complement)\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 0, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 1, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 4, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 64, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 128, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 192, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 255, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\n\t\t// Large positive number close to max value\n\t\t{\"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 1, \"0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 2, \"0x1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 64, \"0x7fffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 128, \"0x7fffffffffffffffffffffffffffffff\"},\n\t\t{\"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 192, \"0x7fffffffffffffff\"},\n\t\t{\"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 255, \"0x0\"},\n\n\t\t// Specific cases\n\t\t{\"0x8000000000000000000000000000000000000000000000000000000000000000\", 1, \"0xc000000000000000000000000000000000000000000000000000000000000000\"},\n\t\t{\"0x8000000000000000000000000000000000000000000000000000000000000001\", 1, \"0xc000000000000000000000000000000000000000000000000000000000000000\"},\n\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 65, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 127, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 129, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 193, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\n\t\t// n \u003e 256\n\t\t{\"0x1\", 257, \"0x0\"},\n\t\t{\"0x31337\", 300, \"0x0\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromHex(tt.x)\n\t\twant := MustFromHex(tt.want)\n\n\t\tgot := new(Uint).SRsh(x, tt.y)\n\n\t\tif !got.Eq(want) {\n\t\t\tt.Errorf(\"SRsh(%s, %d) = %s, want %s\", tt.x, tt.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n"},{"name":"cmp.gno","body":"// cmp (or, comparisons) includes methods for comparing Uint instances.\n// These comparison functions cover a range of operations including equality checks, less than/greater than\n// evaluations, and specialized comparisons such as signed greater than. These are fundamental for logical\n// decision making based on Uint values.\npackage uint256\n\nimport (\n\t\"math/bits\"\n)\n\n// Cmp compares z and x and returns:\n//\n//\t-1 if z \u003c x\n//\t 0 if z == x\n//\t+1 if z \u003e x\nfunc (z *Uint) Cmp(x *Uint) (r int) {\n\t// z \u003c x \u003c=\u003e z - x \u003c 0 i.e. when subtraction overflows.\n\td0, carry := bits.Sub64(z.arr[0], x.arr[0], 0)\n\td1, carry := bits.Sub64(z.arr[1], x.arr[1], carry)\n\td2, carry := bits.Sub64(z.arr[2], x.arr[2], carry)\n\td3, carry := bits.Sub64(z.arr[3], x.arr[3], carry)\n\tif carry == 1 {\n\t\treturn -1\n\t}\n\tif d0|d1|d2|d3 == 0 {\n\t\treturn 0\n\t}\n\treturn 1\n}\n\n// IsZero returns true if z == 0\nfunc (z *Uint) IsZero() bool {\n\treturn (z.arr[0] | z.arr[1] | z.arr[2] | z.arr[3]) == 0\n}\n\n// Sign returns:\n//\n//\t-1 if z \u003c 0\n//\t 0 if z == 0\n//\t+1 if z \u003e 0\n//\n// Where z is interpreted as a two's complement signed number\nfunc (z *Uint) Sign() int {\n\tif z.IsZero() {\n\t\treturn 0\n\t}\n\tif z.arr[3] \u003c 0x8000000000000000 {\n\t\treturn 1\n\t}\n\treturn -1\n}\n\n// LtUint64 returns true if z is smaller than n\nfunc (z *Uint) LtUint64(n uint64) bool {\n\treturn z.arr[0] \u003c n \u0026\u0026 (z.arr[1]|z.arr[2]|z.arr[3]) == 0\n}\n\n// GtUint64 returns true if z is larger than n\nfunc (z *Uint) GtUint64(n uint64) bool {\n\treturn z.arr[0] \u003e n || (z.arr[1]|z.arr[2]|z.arr[3]) != 0\n}\n\n// Lt returns true if z \u003c x\nfunc (z *Uint) Lt(x *Uint) bool {\n\t// z \u003c x \u003c=\u003e z - x \u003c 0 i.e. when subtraction overflows.\n\t_, carry := bits.Sub64(z.arr[0], x.arr[0], 0)\n\t_, carry = bits.Sub64(z.arr[1], x.arr[1], carry)\n\t_, carry = bits.Sub64(z.arr[2], x.arr[2], carry)\n\t_, carry = bits.Sub64(z.arr[3], x.arr[3], carry)\n\n\treturn carry != 0\n}\n\n// Gt returns true if z \u003e x\nfunc (z *Uint) Gt(x *Uint) bool {\n\treturn x.Lt(z)\n}\n\n// Lte returns true if z \u003c= x\nfunc (z *Uint) Lte(x *Uint) bool {\n\tcond1 := z.Lt(x)\n\tcond2 := z.Eq(x)\n\n\tif cond1 || cond2 {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// Gte returns true if z \u003e= x\nfunc (z *Uint) Gte(x *Uint) bool {\n\tcond1 := z.Gt(x)\n\tcond2 := z.Eq(x)\n\n\tif cond1 || cond2 {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// Eq returns true if z == x\nfunc (z *Uint) Eq(x *Uint) bool {\n\treturn (z.arr[0] == x.arr[0]) \u0026\u0026 (z.arr[1] == x.arr[1]) \u0026\u0026 (z.arr[2] == x.arr[2]) \u0026\u0026 (z.arr[3] == x.arr[3])\n}\n\n// Neq returns true if z != x\nfunc (z *Uint) Neq(x *Uint) bool {\n\treturn !z.Eq(x)\n}\n\n// Sgt interprets z and x as signed integers, and returns\n// true if z \u003e x\nfunc (z *Uint) Sgt(x *Uint) bool {\n\tzSign := z.Sign()\n\txSign := x.Sign()\n\n\tswitch {\n\tcase zSign \u003e= 0 \u0026\u0026 xSign \u003c 0:\n\t\treturn true\n\tcase zSign \u003c 0 \u0026\u0026 xSign \u003e= 0:\n\t\treturn false\n\tdefault:\n\t\treturn z.Gt(x)\n\t}\n}\n"},{"name":"cmp_test.gno","body":"package uint256\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestSign(t *testing.T) {\n\ttests := []struct {\n\t\tinput *Uint\n\t\texpected int\n\t}{\n\t\t{\n\t\t\tinput: NewUint(0),\n\t\t\texpected: 0,\n\t\t},\n\t\t{\n\t\t\tinput: NewUint(1),\n\t\t\texpected: 1,\n\t\t},\n\t\t{\n\t\t\tinput: NewUint(0x7fffffffffffffff),\n\t\t\texpected: 1,\n\t\t},\n\t\t{\n\t\t\tinput: NewUint(0x8000000000000000),\n\t\t\texpected: 1,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.input.ToString(), func(t *testing.T) {\n\t\t\tresult := tt.input.Sign()\n\t\t\tif result != tt.expected {\n\t\t\t\tt.Errorf(\"Sign() = %d; want %d\", result, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCmp(t *testing.T) {\n\ttests := []struct {\n\t\tx, y string\n\t\twant int\n\t}{\n\t\t{\"0\", \"0\", 0},\n\t\t{\"0\", \"1\", -1},\n\t\t{\"1\", \"0\", 1},\n\t\t{\"1\", \"1\", 0},\n\t\t{\"10\", \"10\", 0},\n\t\t{\"10\", \"11\", -1},\n\t\t{\"11\", \"10\", 1},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx := MustFromDecimal(tc.x)\n\t\ty := MustFromDecimal(tc.y)\n\n\t\tgot := x.Cmp(y)\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Cmp(%s, %s) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestIsZero(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant bool\n\t}{\n\t\t{\"0\", true},\n\t\t{\"1\", false},\n\t\t{\"10\", false},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\n\t\tgot := x.IsZero()\n\t\tif got != tt.want {\n\t\t\tt.Errorf(\"IsZero(%s) = %v, want %v\", tt.x, got, tt.want)\n\t\t}\n\t}\n}\n\nfunc TestLtUint64(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty uint64\n\t\twant bool\n\t}{\n\t\t{\"0\", 1, true},\n\t\t{\"1\", 0, false},\n\t\t{\"10\", 10, false},\n\t\t{\"0xffffffffffffffff\", 0, false},\n\t\t{\"0x10000000000000000\", 10000000000000000, false},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx := parseTestString(t, tc.x)\n\n\t\tgot := x.LtUint64(tc.y)\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"LtUint64(%s, %d) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestUint_GtUint64(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tz string\n\t\tn uint64\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"z \u003e n\",\n\t\t\tz: \"1\",\n\t\t\tn: 0,\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"z \u003c n\",\n\t\t\tz: \"18446744073709551615\",\n\t\t\tn: 0xFFFFFFFFFFFFFFFF,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"z == n\",\n\t\t\tz: \"18446744073709551615\",\n\t\t\tn: 0xFFFFFFFFFFFFFFFF,\n\t\t\twant: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tz := MustFromDecimal(tt.z)\n\n\t\t\tif got := z.GtUint64(tt.n); got != tt.want {\n\t\t\t\tt.Errorf(\"Uint.GtUint64() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSGT(t *testing.T) {\n\tx := MustFromHex(\"0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe\")\n\ty := MustFromHex(\"0x0\")\n\tactual := x.Sgt(y)\n\tif actual {\n\t\tt.Fatalf(\"Expected %v false\", actual)\n\t}\n\n\tx = MustFromHex(\"0x0\")\n\ty = MustFromHex(\"0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe\")\n\tactual = x.Sgt(y)\n\tif !actual {\n\t\tt.Fatalf(\"Expected %v true\", actual)\n\t}\n}\n\nfunc TestEq(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty string\n\t\twant bool\n\t}{\n\t\t{\"0xffffffffffffffff\", \"18446744073709551615\", true},\n\t\t{\"0x10000000000000000\", \"18446744073709551616\", true},\n\t\t{\"0\", \"0\", true},\n\t\t{twoPow256Sub1, twoPow256Sub1, true},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := parseTestString(t, tt.x)\n\n\t\ty, err := FromDecimal(tt.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.Eq(y)\n\n\t\tif got != tt.want {\n\t\t\tt.Errorf(\"Eq(%s, %s) = %v, want %v\", tt.x, tt.y, got, tt.want)\n\t\t}\n\t}\n}\n\nfunc TestUint_Lte(t *testing.T) {\n\ttests := []struct {\n\t\tz, x string\n\t\twant bool\n\t}{\n\t\t{\"10\", \"20\", true},\n\t\t{\"20\", \"10\", false},\n\t\t{\"10\", \"10\", true},\n\t\t{\"0\", \"0\", true},\n\t}\n\n\tfor _, tt := range tests {\n\t\tz, err := FromDecimal(tt.z)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\t\tx, err := FromDecimal(tt.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\t\tif got := z.Lte(x); got != tt.want {\n\t\t\tt.Errorf(\"Uint.Lte(%v, %v) = %v, want %v\", tt.z, tt.x, got, tt.want)\n\t\t}\n\t}\n}\n\nfunc TestUint_Gte(t *testing.T) {\n\ttests := []struct {\n\t\tz, x string\n\t\twant bool\n\t}{\n\t\t{\"20\", \"10\", true},\n\t\t{\"10\", \"20\", false},\n\t\t{\"10\", \"10\", true},\n\t\t{\"0\", \"0\", true},\n\t}\n\n\tfor _, tt := range tests {\n\t\tz := parseTestString(t, tt.z)\n\t\tx := parseTestString(t, tt.x)\n\n\t\tif got := z.Gte(x); got != tt.want {\n\t\t\tt.Errorf(\"Uint.Gte(%v, %v) = %v, want %v\", tt.z, tt.x, got, tt.want)\n\t\t}\n\t}\n}\n\nfunc parseTestString(_ *testing.T, s string) *Uint {\n\tvar x *Uint\n\n\tif strings.HasPrefix(s, \"0x\") {\n\t\tx = MustFromHex(s)\n\t} else {\n\t\tx = MustFromDecimal(s)\n\t}\n\n\treturn x\n}\n"},{"name":"conversion.gno","body":"// conversions contains methods for converting Uint instances to other types and vice versa.\n// This includes conversions to and from basic types such as uint64 and int32, as well as string representations\n// and byte slices. Additionally, it covers marshaling and unmarshaling for JSON and other text formats.\npackage uint256\n\nimport (\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// Uint64 returns the lower 64-bits of z\nfunc (z *Uint) Uint64() uint64 {\n\treturn z.arr[0]\n}\n\n// Uint64WithOverflow returns the lower 64-bits of z and bool whether overflow occurred\nfunc (z *Uint) Uint64WithOverflow() (uint64, bool) {\n\treturn z.arr[0], (z.arr[1] | z.arr[2] | z.arr[3]) != 0\n}\n\n// SetUint64 sets z to the value x\nfunc (z *Uint) SetUint64(x uint64) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, 0, 0, x\n\treturn z\n}\n\n// IsUint64 reports whether z can be represented as a uint64.\nfunc (z *Uint) IsUint64() bool {\n\treturn (z.arr[1] | z.arr[2] | z.arr[3]) == 0\n}\n\n// Dec returns the decimal representation of z.\nfunc (z *Uint) Dec() string {\n\tif z.IsZero() {\n\t\treturn \"0\"\n\t}\n\tif z.IsUint64() {\n\t\treturn strconv.FormatUint(z.Uint64(), 10)\n\t}\n\n\t// The max uint64 value being 18446744073709551615, the largest\n\t// power-of-ten below that is 10000000000000000000.\n\t// When we do a DivMod using that number, the remainder that we\n\t// get back is the lower part of the output.\n\t//\n\t// The ascii-output of remainder will never exceed 19 bytes (since it will be\n\t// below 10000000000000000000).\n\t//\n\t// Algorithm example using 100 as divisor\n\t//\n\t// 12345 % 100 = 45 (rem)\n\t// 12345 / 100 = 123 (quo)\n\t// -\u003e output '45', continue iterate on 123\n\tvar (\n\t\t// out is 98 bytes long: 78 (max size of a string without leading zeroes,\n\t\t// plus slack so we can copy 19 bytes every iteration).\n\t\t// We init it with zeroes, because when strconv appends the ascii representations,\n\t\t// it will omit leading zeroes.\n\t\tout = []byte(\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\")\n\t\tdivisor = NewUint(10000000000000000000) // 20 digits\n\t\ty = new(Uint).Set(z) // copy to avoid modifying z\n\t\tpos = len(out) // position to write to\n\t\tbuf = make([]byte, 0, 19) // buffer to write uint64:s to\n\t)\n\tfor {\n\t\t// Obtain Q and R for divisor\n\t\tvar quot Uint\n\t\trem := udivrem(quot.arr[:], y.arr[:], divisor)\n\t\ty.Set(\u0026quot) // Set Q for next loop\n\t\t// Convert the R to ascii representation\n\t\tbuf = strconv.AppendUint(buf[:0], rem.Uint64(), 10)\n\t\t// Copy in the ascii digits\n\t\tcopy(out[pos-len(buf):], buf)\n\t\tif y.IsZero() {\n\t\t\tbreak\n\t\t}\n\t\t// Move 19 digits left\n\t\tpos -= 19\n\t}\n\t// skip leading zeroes by only using the 'used size' of buf\n\treturn string(out[pos-len(buf):])\n}\n\nfunc (z *Uint) Scan(src interface{}) error {\n\tif src == nil {\n\t\tz.Clear()\n\t\treturn nil\n\t}\n\n\tswitch src := src.(type) {\n\tcase string:\n\t\treturn z.scanScientificFromString(src)\n\tcase []byte:\n\t\treturn z.scanScientificFromString(string(src))\n\t}\n\treturn errors.New(\"default // unsupported type: can't convert to uint256.Uint\")\n}\n\nfunc (z *Uint) scanScientificFromString(src string) error {\n\tif len(src) == 0 {\n\t\tz.Clear()\n\t\treturn nil\n\t}\n\n\tidx := strings.IndexByte(src, 'e')\n\tif idx == -1 {\n\t\treturn z.SetFromDecimal(src)\n\t}\n\tif err := z.SetFromDecimal(src[:idx]); err != nil {\n\t\treturn err\n\t}\n\tif src[(idx+1):] == \"0\" {\n\t\treturn nil\n\t}\n\texp := new(Uint)\n\tif err := exp.SetFromDecimal(src[(idx + 1):]); err != nil {\n\t\treturn err\n\t}\n\tif exp.GtUint64(77) { // 10**78 is larger than 2**256\n\t\treturn ErrBig256Range\n\t}\n\texp.Exp(NewUint(10), exp)\n\tif _, overflow := z.MulOverflow(z, exp); overflow {\n\t\treturn ErrBig256Range\n\t}\n\treturn nil\n}\n\n// ToString returns the decimal string representation of z. It returns an empty string if z is nil.\n// OBS: doesn't exist from holiman's uint256\nfunc (z *Uint) ToString() string {\n\tif z == nil {\n\t\treturn \"\"\n\t}\n\n\treturn z.Dec()\n}\n\n// MarshalJSON implements json.Marshaler.\n// MarshalJSON marshals using the 'decimal string' representation. This is _not_ compatible\n// with big.Uint: big.Uint marshals into JSON 'native' numeric format.\n//\n// The JSON native format is, on some platforms, (e.g. javascript), limited to 53-bit large\n// integer space. Thus, U256 uses string-format, which is not compatible with\n// big.int (big.Uint refuses to unmarshal a string representation).\nfunc (z *Uint) MarshalJSON() ([]byte, error) {\n\treturn []byte(`\"` + z.Dec() + `\"`), nil\n}\n\n// UnmarshalJSON implements json.Unmarshaler. UnmarshalJSON accepts either\n// - Quoted string: either hexadecimal OR decimal\n// - Not quoted string: only decimal\nfunc (z *Uint) UnmarshalJSON(input []byte) error {\n\tif len(input) \u003c 2 || input[0] != '\"' || input[len(input)-1] != '\"' {\n\t\t// if not quoted, it must be decimal\n\t\treturn z.fromDecimal(string(input))\n\t}\n\treturn z.UnmarshalText(input[1 : len(input)-1])\n}\n\n// MarshalText implements encoding.TextMarshaler\n// MarshalText marshals using the decimal representation (compatible with big.Uint)\nfunc (z *Uint) MarshalText() ([]byte, error) {\n\treturn []byte(z.Dec()), nil\n}\n\n// UnmarshalText implements encoding.TextUnmarshaler. This method\n// can unmarshal either hexadecimal or decimal.\n// - For hexadecimal, the input _must_ be prefixed with 0x or 0X\nfunc (z *Uint) UnmarshalText(input []byte) error {\n\tif len(input) \u003e= 2 \u0026\u0026 input[0] == '0' \u0026\u0026 (input[1] == 'x' || input[1] == 'X') {\n\t\treturn z.fromHex(string(input))\n\t}\n\treturn z.fromDecimal(string(input))\n}\n\n// SetBytes interprets buf as the bytes of a big-endian unsigned\n// integer, sets z to that value, and returns z.\n// If buf is larger than 32 bytes, the last 32 bytes is used.\nfunc (z *Uint) SetBytes(buf []byte) *Uint {\n\tswitch l := len(buf); l {\n\tcase 0:\n\t\tz.Clear()\n\tcase 1:\n\t\tz.SetBytes1(buf)\n\tcase 2:\n\t\tz.SetBytes2(buf)\n\tcase 3:\n\t\tz.SetBytes3(buf)\n\tcase 4:\n\t\tz.SetBytes4(buf)\n\tcase 5:\n\t\tz.SetBytes5(buf)\n\tcase 6:\n\t\tz.SetBytes6(buf)\n\tcase 7:\n\t\tz.SetBytes7(buf)\n\tcase 8:\n\t\tz.SetBytes8(buf)\n\tcase 9:\n\t\tz.SetBytes9(buf)\n\tcase 10:\n\t\tz.SetBytes10(buf)\n\tcase 11:\n\t\tz.SetBytes11(buf)\n\tcase 12:\n\t\tz.SetBytes12(buf)\n\tcase 13:\n\t\tz.SetBytes13(buf)\n\tcase 14:\n\t\tz.SetBytes14(buf)\n\tcase 15:\n\t\tz.SetBytes15(buf)\n\tcase 16:\n\t\tz.SetBytes16(buf)\n\tcase 17:\n\t\tz.SetBytes17(buf)\n\tcase 18:\n\t\tz.SetBytes18(buf)\n\tcase 19:\n\t\tz.SetBytes19(buf)\n\tcase 20:\n\t\tz.SetBytes20(buf)\n\tcase 21:\n\t\tz.SetBytes21(buf)\n\tcase 22:\n\t\tz.SetBytes22(buf)\n\tcase 23:\n\t\tz.SetBytes23(buf)\n\tcase 24:\n\t\tz.SetBytes24(buf)\n\tcase 25:\n\t\tz.SetBytes25(buf)\n\tcase 26:\n\t\tz.SetBytes26(buf)\n\tcase 27:\n\t\tz.SetBytes27(buf)\n\tcase 28:\n\t\tz.SetBytes28(buf)\n\tcase 29:\n\t\tz.SetBytes29(buf)\n\tcase 30:\n\t\tz.SetBytes30(buf)\n\tcase 31:\n\t\tz.SetBytes31(buf)\n\tdefault:\n\t\tz.SetBytes32(buf[l-32:])\n\t}\n\treturn z\n}\n\n// SetBytes1 is identical to SetBytes(in[:1]), but panics is input is too short\nfunc (z *Uint) SetBytes1(in []byte) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = uint64(in[0])\n\treturn z\n}\n\n// SetBytes2 is identical to SetBytes(in[:2]), but panics is input is too short\nfunc (z *Uint) SetBytes2(in []byte) *Uint {\n\t_ = in[1] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = uint64(binary.BigEndian.Uint16(in[0:2]))\n\treturn z\n}\n\n// SetBytes3 is identical to SetBytes(in[:3]), but panics is input is too short\nfunc (z *Uint) SetBytes3(in []byte) *Uint {\n\t_ = in[2] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = uint64(binary.BigEndian.Uint16(in[1:3])) | uint64(in[0])\u003c\u003c16\n\treturn z\n}\n\n// SetBytes4 is identical to SetBytes(in[:4]), but panics is input is too short\nfunc (z *Uint) SetBytes4(in []byte) *Uint {\n\t_ = in[3] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = uint64(binary.BigEndian.Uint32(in[0:4]))\n\treturn z\n}\n\n// SetBytes5 is identical to SetBytes(in[:5]), but panics is input is too short\nfunc (z *Uint) SetBytes5(in []byte) *Uint {\n\t_ = in[4] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = bigEndianUint40(in[0:5])\n\treturn z\n}\n\n// SetBytes6 is identical to SetBytes(in[:6]), but panics is input is too short\nfunc (z *Uint) SetBytes6(in []byte) *Uint {\n\t_ = in[5] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = bigEndianUint48(in[0:6])\n\treturn z\n}\n\n// SetBytes7 is identical to SetBytes(in[:7]), but panics is input is too short\nfunc (z *Uint) SetBytes7(in []byte) *Uint {\n\t_ = in[6] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = bigEndianUint56(in[0:7])\n\treturn z\n}\n\n// SetBytes8 is identical to SetBytes(in[:8]), but panics is input is too short\nfunc (z *Uint) SetBytes8(in []byte) *Uint {\n\t_ = in[7] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = binary.BigEndian.Uint64(in[0:8])\n\treturn z\n}\n\n// SetBytes9 is identical to SetBytes(in[:9]), but panics is input is too short\nfunc (z *Uint) SetBytes9(in []byte) *Uint {\n\t_ = in[8] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = uint64(in[0])\n\tz.arr[0] = binary.BigEndian.Uint64(in[1:9])\n\treturn z\n}\n\n// SetBytes10 is identical to SetBytes(in[:10]), but panics is input is too short\nfunc (z *Uint) SetBytes10(in []byte) *Uint {\n\t_ = in[9] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = uint64(binary.BigEndian.Uint16(in[0:2]))\n\tz.arr[0] = binary.BigEndian.Uint64(in[2:10])\n\treturn z\n}\n\n// SetBytes11 is identical to SetBytes(in[:11]), but panics is input is too short\nfunc (z *Uint) SetBytes11(in []byte) *Uint {\n\t_ = in[10] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = uint64(binary.BigEndian.Uint16(in[1:3])) | uint64(in[0])\u003c\u003c16\n\tz.arr[0] = binary.BigEndian.Uint64(in[3:11])\n\treturn z\n}\n\n// SetBytes12 is identical to SetBytes(in[:12]), but panics is input is too short\nfunc (z *Uint) SetBytes12(in []byte) *Uint {\n\t_ = in[11] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = uint64(binary.BigEndian.Uint32(in[0:4]))\n\tz.arr[0] = binary.BigEndian.Uint64(in[4:12])\n\treturn z\n}\n\n// SetBytes13 is identical to SetBytes(in[:13]), but panics is input is too short\nfunc (z *Uint) SetBytes13(in []byte) *Uint {\n\t_ = in[12] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = bigEndianUint40(in[0:5])\n\tz.arr[0] = binary.BigEndian.Uint64(in[5:13])\n\treturn z\n}\n\n// SetBytes14 is identical to SetBytes(in[:14]), but panics is input is too short\nfunc (z *Uint) SetBytes14(in []byte) *Uint {\n\t_ = in[13] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = bigEndianUint48(in[0:6])\n\tz.arr[0] = binary.BigEndian.Uint64(in[6:14])\n\treturn z\n}\n\n// SetBytes15 is identical to SetBytes(in[:15]), but panics is input is too short\nfunc (z *Uint) SetBytes15(in []byte) *Uint {\n\t_ = in[14] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = bigEndianUint56(in[0:7])\n\tz.arr[0] = binary.BigEndian.Uint64(in[7:15])\n\treturn z\n}\n\n// SetBytes16 is identical to SetBytes(in[:16]), but panics is input is too short\nfunc (z *Uint) SetBytes16(in []byte) *Uint {\n\t_ = in[15] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = binary.BigEndian.Uint64(in[0:8])\n\tz.arr[0] = binary.BigEndian.Uint64(in[8:16])\n\treturn z\n}\n\n// SetBytes17 is identical to SetBytes(in[:17]), but panics is input is too short\nfunc (z *Uint) SetBytes17(in []byte) *Uint {\n\t_ = in[16] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = uint64(in[0])\n\tz.arr[1] = binary.BigEndian.Uint64(in[1:9])\n\tz.arr[0] = binary.BigEndian.Uint64(in[9:17])\n\treturn z\n}\n\n// SetBytes18 is identical to SetBytes(in[:18]), but panics is input is too short\nfunc (z *Uint) SetBytes18(in []byte) *Uint {\n\t_ = in[17] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = uint64(binary.BigEndian.Uint16(in[0:2]))\n\tz.arr[1] = binary.BigEndian.Uint64(in[2:10])\n\tz.arr[0] = binary.BigEndian.Uint64(in[10:18])\n\treturn z\n}\n\n// SetBytes19 is identical to SetBytes(in[:19]), but panics is input is too short\nfunc (z *Uint) SetBytes19(in []byte) *Uint {\n\t_ = in[18] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = uint64(binary.BigEndian.Uint16(in[1:3])) | uint64(in[0])\u003c\u003c16\n\tz.arr[1] = binary.BigEndian.Uint64(in[3:11])\n\tz.arr[0] = binary.BigEndian.Uint64(in[11:19])\n\treturn z\n}\n\n// SetBytes20 is identical to SetBytes(in[:20]), but panics is input is too short\nfunc (z *Uint) SetBytes20(in []byte) *Uint {\n\t_ = in[19] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = uint64(binary.BigEndian.Uint32(in[0:4]))\n\tz.arr[1] = binary.BigEndian.Uint64(in[4:12])\n\tz.arr[0] = binary.BigEndian.Uint64(in[12:20])\n\treturn z\n}\n\n// SetBytes21 is identical to SetBytes(in[:21]), but panics is input is too short\nfunc (z *Uint) SetBytes21(in []byte) *Uint {\n\t_ = in[20] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = bigEndianUint40(in[0:5])\n\tz.arr[1] = binary.BigEndian.Uint64(in[5:13])\n\tz.arr[0] = binary.BigEndian.Uint64(in[13:21])\n\treturn z\n}\n\n// SetBytes22 is identical to SetBytes(in[:22]), but panics is input is too short\nfunc (z *Uint) SetBytes22(in []byte) *Uint {\n\t_ = in[21] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = bigEndianUint48(in[0:6])\n\tz.arr[1] = binary.BigEndian.Uint64(in[6:14])\n\tz.arr[0] = binary.BigEndian.Uint64(in[14:22])\n\treturn z\n}\n\n// SetBytes23 is identical to SetBytes(in[:23]), but panics is input is too short\nfunc (z *Uint) SetBytes23(in []byte) *Uint {\n\t_ = in[22] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = bigEndianUint56(in[0:7])\n\tz.arr[1] = binary.BigEndian.Uint64(in[7:15])\n\tz.arr[0] = binary.BigEndian.Uint64(in[15:23])\n\treturn z\n}\n\n// SetBytes24 is identical to SetBytes(in[:24]), but panics is input is too short\nfunc (z *Uint) SetBytes24(in []byte) *Uint {\n\t_ = in[23] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = binary.BigEndian.Uint64(in[0:8])\n\tz.arr[1] = binary.BigEndian.Uint64(in[8:16])\n\tz.arr[0] = binary.BigEndian.Uint64(in[16:24])\n\treturn z\n}\n\n// SetBytes25 is identical to SetBytes(in[:25]), but panics is input is too short\nfunc (z *Uint) SetBytes25(in []byte) *Uint {\n\t_ = in[24] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = uint64(in[0])\n\tz.arr[2] = binary.BigEndian.Uint64(in[1:9])\n\tz.arr[1] = binary.BigEndian.Uint64(in[9:17])\n\tz.arr[0] = binary.BigEndian.Uint64(in[17:25])\n\treturn z\n}\n\n// SetBytes26 is identical to SetBytes(in[:26]), but panics is input is too short\nfunc (z *Uint) SetBytes26(in []byte) *Uint {\n\t_ = in[25] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = uint64(binary.BigEndian.Uint16(in[0:2]))\n\tz.arr[2] = binary.BigEndian.Uint64(in[2:10])\n\tz.arr[1] = binary.BigEndian.Uint64(in[10:18])\n\tz.arr[0] = binary.BigEndian.Uint64(in[18:26])\n\treturn z\n}\n\n// SetBytes27 is identical to SetBytes(in[:27]), but panics is input is too short\nfunc (z *Uint) SetBytes27(in []byte) *Uint {\n\t_ = in[26] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = uint64(binary.BigEndian.Uint16(in[1:3])) | uint64(in[0])\u003c\u003c16\n\tz.arr[2] = binary.BigEndian.Uint64(in[3:11])\n\tz.arr[1] = binary.BigEndian.Uint64(in[11:19])\n\tz.arr[0] = binary.BigEndian.Uint64(in[19:27])\n\treturn z\n}\n\n// SetBytes28 is identical to SetBytes(in[:28]), but panics is input is too short\nfunc (z *Uint) SetBytes28(in []byte) *Uint {\n\t_ = in[27] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = uint64(binary.BigEndian.Uint32(in[0:4]))\n\tz.arr[2] = binary.BigEndian.Uint64(in[4:12])\n\tz.arr[1] = binary.BigEndian.Uint64(in[12:20])\n\tz.arr[0] = binary.BigEndian.Uint64(in[20:28])\n\treturn z\n}\n\n// SetBytes29 is identical to SetBytes(in[:29]), but panics is input is too short\nfunc (z *Uint) SetBytes29(in []byte) *Uint {\n\t_ = in[23] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = bigEndianUint40(in[0:5])\n\tz.arr[2] = binary.BigEndian.Uint64(in[5:13])\n\tz.arr[1] = binary.BigEndian.Uint64(in[13:21])\n\tz.arr[0] = binary.BigEndian.Uint64(in[21:29])\n\treturn z\n}\n\n// SetBytes30 is identical to SetBytes(in[:30]), but panics is input is too short\nfunc (z *Uint) SetBytes30(in []byte) *Uint {\n\t_ = in[29] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = bigEndianUint48(in[0:6])\n\tz.arr[2] = binary.BigEndian.Uint64(in[6:14])\n\tz.arr[1] = binary.BigEndian.Uint64(in[14:22])\n\tz.arr[0] = binary.BigEndian.Uint64(in[22:30])\n\treturn z\n}\n\n// SetBytes31 is identical to SetBytes(in[:31]), but panics is input is too short\nfunc (z *Uint) SetBytes31(in []byte) *Uint {\n\t_ = in[30] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = bigEndianUint56(in[0:7])\n\tz.arr[2] = binary.BigEndian.Uint64(in[7:15])\n\tz.arr[1] = binary.BigEndian.Uint64(in[15:23])\n\tz.arr[0] = binary.BigEndian.Uint64(in[23:31])\n\treturn z\n}\n\n// SetBytes32 sets z to the value of the big-endian 256-bit unsigned integer in.\nfunc (z *Uint) SetBytes32(in []byte) *Uint {\n\t_ = in[31] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = binary.BigEndian.Uint64(in[0:8])\n\tz.arr[2] = binary.BigEndian.Uint64(in[8:16])\n\tz.arr[1] = binary.BigEndian.Uint64(in[16:24])\n\tz.arr[0] = binary.BigEndian.Uint64(in[24:32])\n\treturn z\n}\n\n// Utility methods that are \"missing\" among the bigEndian.UintXX methods.\n\n// bigEndianUint40 returns the uint64 value represented by the 5 bytes in big-endian order.\nfunc bigEndianUint40(b []byte) uint64 {\n\t_ = b[4] // bounds check hint to compiler; see golang.org/issue/14808\n\treturn uint64(b[4]) | uint64(b[3])\u003c\u003c8 | uint64(b[2])\u003c\u003c16 | uint64(b[1])\u003c\u003c24 |\n\t\tuint64(b[0])\u003c\u003c32\n}\n\n// bigEndianUint56 returns the uint64 value represented by the 7 bytes in big-endian order.\nfunc bigEndianUint56(b []byte) uint64 {\n\t_ = b[6] // bounds check hint to compiler; see golang.org/issue/14808\n\treturn uint64(b[6]) | uint64(b[5])\u003c\u003c8 | uint64(b[4])\u003c\u003c16 | uint64(b[3])\u003c\u003c24 |\n\t\tuint64(b[2])\u003c\u003c32 | uint64(b[1])\u003c\u003c40 | uint64(b[0])\u003c\u003c48\n}\n\n// bigEndianUint48 returns the uint64 value represented by the 6 bytes in big-endian order.\nfunc bigEndianUint48(b []byte) uint64 {\n\t_ = b[5] // bounds check hint to compiler; see golang.org/issue/14808\n\treturn uint64(b[5]) | uint64(b[4])\u003c\u003c8 | uint64(b[3])\u003c\u003c16 | uint64(b[2])\u003c\u003c24 |\n\t\tuint64(b[1])\u003c\u003c32 | uint64(b[0])\u003c\u003c40\n}\n"},{"name":"conversion_test.gno","body":"package uint256\n\nimport \"testing\"\n\nfunc TestIsUint64(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant bool\n\t}{\n\t\t{\"0x0\", true},\n\t\t{\"0x1\", true},\n\t\t{\"0x10\", true},\n\t\t{\"0xffffffffffffffff\", true},\n\t\t{\"0x10000000000000000\", false},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromHex(tt.x)\n\t\tgot := x.IsUint64()\n\n\t\tif got != tt.want {\n\t\t\tt.Errorf(\"IsUint64(%s) = %v, want %v\", tt.x, got, tt.want)\n\t\t}\n\t}\n}\n\nfunc TestDec(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tz Uint\n\t\twant string\n\t}{\n\t\t{\n\t\t\tname: \"zero\",\n\t\t\tz: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: \"0\",\n\t\t},\n\t\t{\n\t\t\tname: \"less than 20 digits\",\n\t\t\tz: Uint{arr: [4]uint64{1234567890, 0, 0, 0}},\n\t\t\twant: \"1234567890\",\n\t\t},\n\t\t{\n\t\t\tname: \"max possible value\",\n\t\t\tz: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: twoPow256Sub1,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresult := tt.z.Dec()\n\t\t\tif result != tt.want {\n\t\t\t\tt.Errorf(\"Dec(%v) = %s, want %s\", tt.z, result, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUint_Scan(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput interface{}\n\t\twant *Uint\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"nil\",\n\t\t\tinput: nil,\n\t\t\twant: NewUint(0),\n\t\t},\n\t\t{\n\t\t\tname: \"valid scientific notation\",\n\t\t\tinput: \"1e4\",\n\t\t\twant: NewUint(10000),\n\t\t},\n\t\t{\n\t\t\tname: \"valid decimal string\",\n\t\t\tinput: \"12345\",\n\t\t\twant: NewUint(12345),\n\t\t},\n\t\t{\n\t\t\tname: \"valid byte slice\",\n\t\t\tinput: []byte(\"12345\"),\n\t\t\twant: NewUint(12345),\n\t\t},\n\t\t{\n\t\t\tname: \"invalid string\",\n\t\t\tinput: \"invalid\",\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"out of range\",\n\t\t\tinput: \"115792089237316195423570985008687907853269984665640564039457584007913129639936\", // 2^256\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"unsupported type\",\n\t\t\tinput: 123,\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tz := new(Uint)\n\t\t\terr := z.Scan(tt.input)\n\n\t\t\tif tt.wantErr {\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Errorf(\"Scan() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"Scan() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\t}\n\t\t\t\tif !z.Eq(tt.want) {\n\t\t\t\t\tt.Errorf(\"Scan() = %v, want %v\", z, tt.want)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSetBytes(t *testing.T) {\n\ttests := []struct {\n\t\tinput []byte\n\t\texpected string\n\t}{\n\t\t{[]byte{}, \"0\"},\n\t\t{[]byte{0x01}, \"1\"},\n\t\t{[]byte{0x12, 0x34}, \"4660\"},\n\t\t{[]byte{0x12, 0x34, 0x56}, \"1193046\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78}, \"305419896\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a}, \"78187493530\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc}, \"20015998343868\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde}, \"5124095576030430\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}, \"1311768467463790320\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12}, \"335812727670730321938\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34}, \"85968058283706962416180\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56}, \"22007822920628982378542166\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78}, \"5634002667681019488906794616\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a}, \"1442304682926340989160139421850\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc}, \"369229998829143293224995691993788\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde}, \"94522879700260683065598897150409950\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}, \"24197857203266734864793317670504947440\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12}, \"6194651444036284125387089323649266544658\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34}, \"1585830769673288736099094866854212235432500\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56}, \"405972677036361916441368285914678332270720086\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78}, \"103929005321308650608990281194157653061304342136\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a}, \"26605825362255014555901511985704359183693911586970\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc}, \"6811091292737283726310787068340315951025641366264508\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde}, \"1743639370940744633935561489495120883462564189763714270\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}, \"446371678960830626287503741310750946166416432579510853360\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12}, \"114271149813972640329600957775552242218602606740354778460178\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34}, \"29253414352376995924377845190541374007962267325530823285805620\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56}, \"7488874074208510956640728368778591746038340435335890761166238806\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78}, \"1917151762997378804900026462407319486985815151445988034858557134456\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a}, \"490790851327328974054406774376273788668368678770172936923790626420890\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc}, \"125642457939796217357928134240326089899102381765164271852490400363748028\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde}, \"32164469232587831643629602365523479014170209731882053594237542493119495390\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}, \"8234104123542484900769178205574010627627573691361805720124810878238590820080\"},\n\t\t// over 32 bytes (last 32 bytes are used)\n\t\t{append([]byte{0xff}, []byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}...), \"8234104123542484900769178205574010627627573691361805720124810878238590820080\"},\n\t}\n\n\tfor _, test := range tests {\n\t\tz := new(Uint)\n\t\tz.SetBytes(test.input)\n\t\texpected := MustFromDecimal(test.expected)\n\t\tif z.Cmp(expected) != 0 {\n\t\t\tt.Errorf(\"SetBytes(%x) = %s, expected %s\", test.input, z.ToString(), test.expected)\n\t\t}\n\t}\n}\n"},{"name":"error.gno","body":"package uint256\n\nimport (\n\t\"errors\"\n)\n\nvar (\n\tErrEmptyString = errors.New(\"empty hex string\")\n\tErrSyntax = errors.New(\"invalid hex string\")\n\tErrRange = errors.New(\"number out of range\")\n\tErrMissingPrefix = errors.New(\"hex string without 0x prefix\")\n\tErrEmptyNumber = errors.New(\"hex string \\\"0x\\\"\")\n\tErrLeadingZero = errors.New(\"hex number with leading zero digits\")\n\tErrBig256Range = errors.New(\"hex number \u003e 256 bits\")\n\tErrBadBufferLength = errors.New(\"bad ssz buffer length\")\n\tErrBadEncodedLength = errors.New(\"bad ssz encoded length\")\n\tErrInvalidBase = errors.New(\"invalid base\")\n\tErrInvalidBitSize = errors.New(\"invalid bit size\")\n)\n\ntype u256Error struct {\n\tfn string // function name\n\tinput string\n\terr error\n}\n\nfunc (e *u256Error) Error() string {\n\treturn e.fn + \": \" + e.input + \": \" + e.err.Error()\n}\n\nfunc (e *u256Error) Unwrap() error {\n\treturn e.err\n}\n\nfunc errEmptyString(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrEmptyString}\n}\n\nfunc errSyntax(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrSyntax}\n}\n\nfunc errMissingPrefix(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrMissingPrefix}\n}\n\nfunc errEmptyNumber(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrEmptyNumber}\n}\n\nfunc errLeadingZero(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrLeadingZero}\n}\n\nfunc errRange(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrRange}\n}\n\nfunc errBig256Range(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrBig256Range}\n}\n\nfunc errBadBufferLength(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrBadBufferLength}\n}\n\nfunc errInvalidBase(fn string, base int) error {\n\treturn \u0026u256Error{fn: fn, input: string(base), err: ErrInvalidBase}\n}\n\nfunc errInvalidBitSize(fn string, bitSize int) error {\n\treturn \u0026u256Error{fn: fn, input: string(bitSize), err: ErrInvalidBitSize}\n}\n"},{"name":"mod.gno","body":"package uint256\n\nimport (\n\t\"math/bits\"\n)\n\n// Some utility functions\n\n// Reciprocal computes a 320-bit value representing 1/m\n//\n// Notes:\n// - specialized for m.arr[3] != 0, hence limited to 2^192 \u003c= m \u003c 2^256\n// - returns zero if m.arr[3] == 0\n// - starts with a 32-bit division, refines with newton-raphson iterations\nfunc Reciprocal(m *Uint) (mu [5]uint64) {\n\tif m.arr[3] == 0 {\n\t\treturn mu\n\t}\n\n\ts := bits.LeadingZeros64(m.arr[3]) // Replace with leadingZeros(m) for general case\n\tp := 255 - s // floor(log_2(m)), m\u003e0\n\n\t// 0 or a power of 2?\n\n\t// Check if at least one bit is set in m.arr[2], m.arr[1] or m.arr[0],\n\t// or at least two bits in m.arr[3]\n\n\tif m.arr[0]|m.arr[1]|m.arr[2]|(m.arr[3]\u0026(m.arr[3]-1)) == 0 {\n\n\t\tmu[4] = ^uint64(0) \u003e\u003e uint(p\u002663)\n\t\tmu[3] = ^uint64(0)\n\t\tmu[2] = ^uint64(0)\n\t\tmu[1] = ^uint64(0)\n\t\tmu[0] = ^uint64(0)\n\n\t\treturn mu\n\t}\n\n\t// Maximise division precision by left-aligning divisor\n\n\tvar (\n\t\ty Uint // left-aligned copy of m\n\t\tr0 uint32 // estimate of 2^31/y\n\t)\n\n\ty.Lsh(m, uint(s)) // 1/2 \u003c y \u003c 1\n\n\t// Extract most significant 32 bits\n\n\tyh := uint32(y.arr[3] \u003e\u003e 32)\n\n\tif yh == 0x80000000 { // Avoid overflow in division\n\t\tr0 = 0xffffffff\n\t} else {\n\t\tr0, _ = bits.Div32(0x80000000, 0, yh)\n\t}\n\n\t// First iteration: 32 -\u003e 64\n\n\tt1 := uint64(r0) // 2^31/y\n\tt1 *= t1 // 2^62/y^2\n\tt1, _ = bits.Mul64(t1, y.arr[3]) // 2^62/y^2 * 2^64/y / 2^64 = 2^62/y\n\n\tr1 := uint64(r0) \u003c\u003c 32 // 2^63/y\n\tr1 -= t1 // 2^63/y - 2^62/y = 2^62/y\n\tr1 *= 2 // 2^63/y\n\n\tif (r1 | (y.arr[3] \u003c\u003c 1)) == 0 {\n\t\tr1 = ^uint64(0)\n\t}\n\n\t// Second iteration: 64 -\u003e 128\n\n\t// square: 2^126/y^2\n\ta2h, a2l := bits.Mul64(r1, r1)\n\n\t// multiply by y: e2h:e2l:b2h = 2^126/y^2 * 2^128/y / 2^128 = 2^126/y\n\tb2h, _ := bits.Mul64(a2l, y.arr[2])\n\tc2h, c2l := bits.Mul64(a2l, y.arr[3])\n\td2h, d2l := bits.Mul64(a2h, y.arr[2])\n\te2h, e2l := bits.Mul64(a2h, y.arr[3])\n\n\tb2h, c := bits.Add64(b2h, c2l, 0)\n\te2l, c = bits.Add64(e2l, c2h, c)\n\te2h, _ = bits.Add64(e2h, 0, c)\n\n\t_, c = bits.Add64(b2h, d2l, 0)\n\te2l, c = bits.Add64(e2l, d2h, c)\n\te2h, _ = bits.Add64(e2h, 0, c)\n\n\t// subtract: t2h:t2l = 2^127/y - 2^126/y = 2^126/y\n\tt2l, b := bits.Sub64(0, e2l, 0)\n\tt2h, _ := bits.Sub64(r1, e2h, b)\n\n\t// double: r2h:r2l = 2^127/y\n\tr2l, c := bits.Add64(t2l, t2l, 0)\n\tr2h, _ := bits.Add64(t2h, t2h, c)\n\n\tif (r2h | r2l | (y.arr[3] \u003c\u003c 1)) == 0 {\n\t\tr2h = ^uint64(0)\n\t\tr2l = ^uint64(0)\n\t}\n\n\t// Third iteration: 128 -\u003e 192\n\n\t// square r2 (keep 256 bits): 2^190/y^2\n\ta3h, a3l := bits.Mul64(r2l, r2l)\n\tb3h, b3l := bits.Mul64(r2l, r2h)\n\tc3h, c3l := bits.Mul64(r2h, r2h)\n\n\ta3h, c = bits.Add64(a3h, b3l, 0)\n\tc3l, c = bits.Add64(c3l, b3h, c)\n\tc3h, _ = bits.Add64(c3h, 0, c)\n\n\ta3h, c = bits.Add64(a3h, b3l, 0)\n\tc3l, c = bits.Add64(c3l, b3h, c)\n\tc3h, _ = bits.Add64(c3h, 0, c)\n\n\t// multiply by y: q = 2^190/y^2 * 2^192/y / 2^192 = 2^190/y\n\n\tx0 := a3l\n\tx1 := a3h\n\tx2 := c3l\n\tx3 := c3h\n\n\tvar q0, q1, q2, q3, q4, t0 uint64\n\n\tq0, _ = bits.Mul64(x2, y.arr[0])\n\tq1, t0 = bits.Mul64(x3, y.arr[0])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, _ = bits.Add64(q1, 0, c)\n\n\tt1, _ = bits.Mul64(x1, y.arr[1])\n\tq0, c = bits.Add64(q0, t1, 0)\n\tq2, t0 = bits.Mul64(x3, y.arr[1])\n\tq1, c = bits.Add64(q1, t0, c)\n\tq2, _ = bits.Add64(q2, 0, c)\n\n\tt1, t0 = bits.Mul64(x2, y.arr[1])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tq2, _ = bits.Add64(q2, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, y.arr[2])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tq3, t0 = bits.Mul64(x3, y.arr[2])\n\tq2, c = bits.Add64(q2, t0, c)\n\tq3, _ = bits.Add64(q3, 0, c)\n\n\tt1, _ = bits.Mul64(x0, y.arr[2])\n\tq0, c = bits.Add64(q0, t1, 0)\n\tt1, t0 = bits.Mul64(x2, y.arr[2])\n\tq1, c = bits.Add64(q1, t0, c)\n\tq2, c = bits.Add64(q2, t1, c)\n\tq3, _ = bits.Add64(q3, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, y.arr[3])\n\tq1, c = bits.Add64(q1, t0, 0)\n\tq2, c = bits.Add64(q2, t1, c)\n\tq4, t0 = bits.Mul64(x3, y.arr[3])\n\tq3, c = bits.Add64(q3, t0, c)\n\tq4, _ = bits.Add64(q4, 0, c)\n\n\tt1, t0 = bits.Mul64(x0, y.arr[3])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tt1, t0 = bits.Mul64(x2, y.arr[3])\n\tq2, c = bits.Add64(q2, t0, c)\n\tq3, c = bits.Add64(q3, t1, c)\n\tq4, _ = bits.Add64(q4, 0, c)\n\n\t// subtract: t3 = 2^191/y - 2^190/y = 2^190/y\n\t_, b = bits.Sub64(0, q0, 0)\n\t_, b = bits.Sub64(0, q1, b)\n\tt3l, b := bits.Sub64(0, q2, b)\n\tt3m, b := bits.Sub64(r2l, q3, b)\n\tt3h, _ := bits.Sub64(r2h, q4, b)\n\n\t// double: r3 = 2^191/y\n\tr3l, c := bits.Add64(t3l, t3l, 0)\n\tr3m, c := bits.Add64(t3m, t3m, c)\n\tr3h, _ := bits.Add64(t3h, t3h, c)\n\n\t// Fourth iteration: 192 -\u003e 320\n\n\t// square r3\n\n\ta4h, a4l := bits.Mul64(r3l, r3l)\n\tb4h, b4l := bits.Mul64(r3l, r3m)\n\tc4h, c4l := bits.Mul64(r3l, r3h)\n\td4h, d4l := bits.Mul64(r3m, r3m)\n\te4h, e4l := bits.Mul64(r3m, r3h)\n\tf4h, f4l := bits.Mul64(r3h, r3h)\n\n\tb4h, c = bits.Add64(b4h, c4l, 0)\n\te4l, c = bits.Add64(e4l, c4h, c)\n\te4h, _ = bits.Add64(e4h, 0, c)\n\n\ta4h, c = bits.Add64(a4h, b4l, 0)\n\td4l, c = bits.Add64(d4l, b4h, c)\n\td4h, c = bits.Add64(d4h, e4l, c)\n\tf4l, c = bits.Add64(f4l, e4h, c)\n\tf4h, _ = bits.Add64(f4h, 0, c)\n\n\ta4h, c = bits.Add64(a4h, b4l, 0)\n\td4l, c = bits.Add64(d4l, b4h, c)\n\td4h, c = bits.Add64(d4h, e4l, c)\n\tf4l, c = bits.Add64(f4l, e4h, c)\n\tf4h, _ = bits.Add64(f4h, 0, c)\n\n\t// multiply by y\n\n\tx1, x0 = bits.Mul64(d4h, y.arr[0])\n\tx3, x2 = bits.Mul64(f4h, y.arr[0])\n\tt1, t0 = bits.Mul64(f4l, y.arr[0])\n\tx1, c = bits.Add64(x1, t0, 0)\n\tx2, c = bits.Add64(x2, t1, c)\n\tx3, _ = bits.Add64(x3, 0, c)\n\n\tt1, t0 = bits.Mul64(d4h, y.arr[1])\n\tx1, c = bits.Add64(x1, t0, 0)\n\tx2, c = bits.Add64(x2, t1, c)\n\tx4, t0 := bits.Mul64(f4h, y.arr[1])\n\tx3, c = bits.Add64(x3, t0, c)\n\tx4, _ = bits.Add64(x4, 0, c)\n\tt1, t0 = bits.Mul64(d4l, y.arr[1])\n\tx0, c = bits.Add64(x0, t0, 0)\n\tx1, c = bits.Add64(x1, t1, c)\n\tt1, t0 = bits.Mul64(f4l, y.arr[1])\n\tx2, c = bits.Add64(x2, t0, c)\n\tx3, c = bits.Add64(x3, t1, c)\n\tx4, _ = bits.Add64(x4, 0, c)\n\n\tt1, t0 = bits.Mul64(a4h, y.arr[2])\n\tx0, c = bits.Add64(x0, t0, 0)\n\tx1, c = bits.Add64(x1, t1, c)\n\tt1, t0 = bits.Mul64(d4h, y.arr[2])\n\tx2, c = bits.Add64(x2, t0, c)\n\tx3, c = bits.Add64(x3, t1, c)\n\tx5, t0 := bits.Mul64(f4h, y.arr[2])\n\tx4, c = bits.Add64(x4, t0, c)\n\tx5, _ = bits.Add64(x5, 0, c)\n\tt1, t0 = bits.Mul64(d4l, y.arr[2])\n\tx1, c = bits.Add64(x1, t0, 0)\n\tx2, c = bits.Add64(x2, t1, c)\n\tt1, t0 = bits.Mul64(f4l, y.arr[2])\n\tx3, c = bits.Add64(x3, t0, c)\n\tx4, c = bits.Add64(x4, t1, c)\n\tx5, _ = bits.Add64(x5, 0, c)\n\n\tt1, t0 = bits.Mul64(a4h, y.arr[3])\n\tx1, c = bits.Add64(x1, t0, 0)\n\tx2, c = bits.Add64(x2, t1, c)\n\tt1, t0 = bits.Mul64(d4h, y.arr[3])\n\tx3, c = bits.Add64(x3, t0, c)\n\tx4, c = bits.Add64(x4, t1, c)\n\tx6, t0 := bits.Mul64(f4h, y.arr[3])\n\tx5, c = bits.Add64(x5, t0, c)\n\tx6, _ = bits.Add64(x6, 0, c)\n\tt1, t0 = bits.Mul64(a4l, y.arr[3])\n\tx0, c = bits.Add64(x0, t0, 0)\n\tx1, c = bits.Add64(x1, t1, c)\n\tt1, t0 = bits.Mul64(d4l, y.arr[3])\n\tx2, c = bits.Add64(x2, t0, c)\n\tx3, c = bits.Add64(x3, t1, c)\n\tt1, t0 = bits.Mul64(f4l, y.arr[3])\n\tx4, c = bits.Add64(x4, t0, c)\n\tx5, c = bits.Add64(x5, t1, c)\n\tx6, _ = bits.Add64(x6, 0, c)\n\n\t// subtract\n\t_, b = bits.Sub64(0, x0, 0)\n\t_, b = bits.Sub64(0, x1, b)\n\tr4l, b := bits.Sub64(0, x2, b)\n\tr4k, b := bits.Sub64(0, x3, b)\n\tr4j, b := bits.Sub64(r3l, x4, b)\n\tr4i, b := bits.Sub64(r3m, x5, b)\n\tr4h, _ := bits.Sub64(r3h, x6, b)\n\n\t// Multiply candidate for 1/4y by y, with full precision\n\n\tx0 = r4l\n\tx1 = r4k\n\tx2 = r4j\n\tx3 = r4i\n\tx4 = r4h\n\n\tq1, q0 = bits.Mul64(x0, y.arr[0])\n\tq3, q2 = bits.Mul64(x2, y.arr[0])\n\tq5, q4 := bits.Mul64(x4, y.arr[0])\n\n\tt1, t0 = bits.Mul64(x1, y.arr[0])\n\tq1, c = bits.Add64(q1, t0, 0)\n\tq2, c = bits.Add64(q2, t1, c)\n\tt1, t0 = bits.Mul64(x3, y.arr[0])\n\tq3, c = bits.Add64(q3, t0, c)\n\tq4, c = bits.Add64(q4, t1, c)\n\tq5, _ = bits.Add64(q5, 0, c)\n\n\tt1, t0 = bits.Mul64(x0, y.arr[1])\n\tq1, c = bits.Add64(q1, t0, 0)\n\tq2, c = bits.Add64(q2, t1, c)\n\tt1, t0 = bits.Mul64(x2, y.arr[1])\n\tq3, c = bits.Add64(q3, t0, c)\n\tq4, c = bits.Add64(q4, t1, c)\n\tq6, t0 := bits.Mul64(x4, y.arr[1])\n\tq5, c = bits.Add64(q5, t0, c)\n\tq6, _ = bits.Add64(q6, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, y.arr[1])\n\tq2, c = bits.Add64(q2, t0, 0)\n\tq3, c = bits.Add64(q3, t1, c)\n\tt1, t0 = bits.Mul64(x3, y.arr[1])\n\tq4, c = bits.Add64(q4, t0, c)\n\tq5, c = bits.Add64(q5, t1, c)\n\tq6, _ = bits.Add64(q6, 0, c)\n\n\tt1, t0 = bits.Mul64(x0, y.arr[2])\n\tq2, c = bits.Add64(q2, t0, 0)\n\tq3, c = bits.Add64(q3, t1, c)\n\tt1, t0 = bits.Mul64(x2, y.arr[2])\n\tq4, c = bits.Add64(q4, t0, c)\n\tq5, c = bits.Add64(q5, t1, c)\n\tq7, t0 := bits.Mul64(x4, y.arr[2])\n\tq6, c = bits.Add64(q6, t0, c)\n\tq7, _ = bits.Add64(q7, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, y.arr[2])\n\tq3, c = bits.Add64(q3, t0, 0)\n\tq4, c = bits.Add64(q4, t1, c)\n\tt1, t0 = bits.Mul64(x3, y.arr[2])\n\tq5, c = bits.Add64(q5, t0, c)\n\tq6, c = bits.Add64(q6, t1, c)\n\tq7, _ = bits.Add64(q7, 0, c)\n\n\tt1, t0 = bits.Mul64(x0, y.arr[3])\n\tq3, c = bits.Add64(q3, t0, 0)\n\tq4, c = bits.Add64(q4, t1, c)\n\tt1, t0 = bits.Mul64(x2, y.arr[3])\n\tq5, c = bits.Add64(q5, t0, c)\n\tq6, c = bits.Add64(q6, t1, c)\n\tq8, t0 := bits.Mul64(x4, y.arr[3])\n\tq7, c = bits.Add64(q7, t0, c)\n\tq8, _ = bits.Add64(q8, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, y.arr[3])\n\tq4, c = bits.Add64(q4, t0, 0)\n\tq5, c = bits.Add64(q5, t1, c)\n\tt1, t0 = bits.Mul64(x3, y.arr[3])\n\tq6, c = bits.Add64(q6, t0, c)\n\tq7, c = bits.Add64(q7, t1, c)\n\tq8, _ = bits.Add64(q8, 0, c)\n\n\t// Final adjustment\n\n\t// subtract q from 1/4\n\t_, b = bits.Sub64(0, q0, 0)\n\t_, b = bits.Sub64(0, q1, b)\n\t_, b = bits.Sub64(0, q2, b)\n\t_, b = bits.Sub64(0, q3, b)\n\t_, b = bits.Sub64(0, q4, b)\n\t_, b = bits.Sub64(0, q5, b)\n\t_, b = bits.Sub64(0, q6, b)\n\t_, b = bits.Sub64(0, q7, b)\n\t_, b = bits.Sub64(uint64(1)\u003c\u003c62, q8, b)\n\n\t// decrement the result\n\tx0, t := bits.Sub64(r4l, 1, 0)\n\tx1, t = bits.Sub64(r4k, 0, t)\n\tx2, t = bits.Sub64(r4j, 0, t)\n\tx3, t = bits.Sub64(r4i, 0, t)\n\tx4, _ = bits.Sub64(r4h, 0, t)\n\n\t// commit the decrement if the subtraction underflowed (reciprocal was too large)\n\tif b != 0 {\n\t\tr4h, r4i, r4j, r4k, r4l = x4, x3, x2, x1, x0\n\t}\n\n\t// Shift to correct bit alignment, truncating excess bits\n\n\tp = (p \u0026 63) - 1\n\n\tx0, c = bits.Add64(r4l, r4l, 0)\n\tx1, c = bits.Add64(r4k, r4k, c)\n\tx2, c = bits.Add64(r4j, r4j, c)\n\tx3, c = bits.Add64(r4i, r4i, c)\n\tx4, _ = bits.Add64(r4h, r4h, c)\n\n\tif p \u003c 0 {\n\t\tr4h, r4i, r4j, r4k, r4l = x4, x3, x2, x1, x0\n\t\tp = 0 // avoid negative shift below\n\t}\n\n\t{\n\t\tr := uint(p) // right shift\n\t\tl := uint(64 - r) // left shift\n\n\t\tx0 = (r4l \u003e\u003e r) | (r4k \u003c\u003c l)\n\t\tx1 = (r4k \u003e\u003e r) | (r4j \u003c\u003c l)\n\t\tx2 = (r4j \u003e\u003e r) | (r4i \u003c\u003c l)\n\t\tx3 = (r4i \u003e\u003e r) | (r4h \u003c\u003c l)\n\t\tx4 = (r4h \u003e\u003e r)\n\t}\n\n\tif p \u003e 0 {\n\t\tr4h, r4i, r4j, r4k, r4l = x4, x3, x2, x1, x0\n\t}\n\n\tmu[0] = r4l\n\tmu[1] = r4k\n\tmu[2] = r4j\n\tmu[3] = r4i\n\tmu[4] = r4h\n\n\treturn mu\n}\n\n// reduce4 computes the least non-negative residue of x modulo m\n//\n// requires a four-word modulus (m.arr[3] \u003e 1) and its inverse (mu)\nfunc reduce4(x [8]uint64, m *Uint, mu [5]uint64) (z Uint) {\n\t// NB: Most variable names in the comments match the pseudocode for\n\t// \tBarrett reduction in the Handbook of Applied Cryptography.\n\n\t// q1 = x/2^192\n\n\tx0 := x[3]\n\tx1 := x[4]\n\tx2 := x[5]\n\tx3 := x[6]\n\tx4 := x[7]\n\n\t// q2 = q1 * mu; q3 = q2 / 2^320\n\n\tvar q0, q1, q2, q3, q4, q5, t0, t1, c uint64\n\n\tq0, _ = bits.Mul64(x3, mu[0])\n\tq1, t0 = bits.Mul64(x4, mu[0])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, _ = bits.Add64(q1, 0, c)\n\n\tt1, _ = bits.Mul64(x2, mu[1])\n\tq0, c = bits.Add64(q0, t1, 0)\n\tq2, t0 = bits.Mul64(x4, mu[1])\n\tq1, c = bits.Add64(q1, t0, c)\n\tq2, _ = bits.Add64(q2, 0, c)\n\n\tt1, t0 = bits.Mul64(x3, mu[1])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tq2, _ = bits.Add64(q2, 0, c)\n\n\tt1, t0 = bits.Mul64(x2, mu[2])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tq3, t0 = bits.Mul64(x4, mu[2])\n\tq2, c = bits.Add64(q2, t0, c)\n\tq3, _ = bits.Add64(q3, 0, c)\n\n\tt1, _ = bits.Mul64(x1, mu[2])\n\tq0, c = bits.Add64(q0, t1, 0)\n\tt1, t0 = bits.Mul64(x3, mu[2])\n\tq1, c = bits.Add64(q1, t0, c)\n\tq2, c = bits.Add64(q2, t1, c)\n\tq3, _ = bits.Add64(q3, 0, c)\n\n\tt1, _ = bits.Mul64(x0, mu[3])\n\tq0, c = bits.Add64(q0, t1, 0)\n\tt1, t0 = bits.Mul64(x2, mu[3])\n\tq1, c = bits.Add64(q1, t0, c)\n\tq2, c = bits.Add64(q2, t1, c)\n\tq4, t0 = bits.Mul64(x4, mu[3])\n\tq3, c = bits.Add64(q3, t0, c)\n\tq4, _ = bits.Add64(q4, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, mu[3])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tt1, t0 = bits.Mul64(x3, mu[3])\n\tq2, c = bits.Add64(q2, t0, c)\n\tq3, c = bits.Add64(q3, t1, c)\n\tq4, _ = bits.Add64(q4, 0, c)\n\n\tt1, t0 = bits.Mul64(x0, mu[4])\n\t_, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tt1, t0 = bits.Mul64(x2, mu[4])\n\tq2, c = bits.Add64(q2, t0, c)\n\tq3, c = bits.Add64(q3, t1, c)\n\tq5, t0 = bits.Mul64(x4, mu[4])\n\tq4, c = bits.Add64(q4, t0, c)\n\tq5, _ = bits.Add64(q5, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, mu[4])\n\tq1, c = bits.Add64(q1, t0, 0)\n\tq2, c = bits.Add64(q2, t1, c)\n\tt1, t0 = bits.Mul64(x3, mu[4])\n\tq3, c = bits.Add64(q3, t0, c)\n\tq4, c = bits.Add64(q4, t1, c)\n\tq5, _ = bits.Add64(q5, 0, c)\n\n\t// Drop the fractional part of q3\n\n\tq0 = q1\n\tq1 = q2\n\tq2 = q3\n\tq3 = q4\n\tq4 = q5\n\n\t// r1 = x mod 2^320\n\n\tx0 = x[0]\n\tx1 = x[1]\n\tx2 = x[2]\n\tx3 = x[3]\n\tx4 = x[4]\n\n\t// r2 = q3 * m mod 2^320\n\n\tvar r0, r1, r2, r3, r4 uint64\n\n\tr4, r3 = bits.Mul64(q0, m.arr[3])\n\t_, t0 = bits.Mul64(q1, m.arr[3])\n\tr4, _ = bits.Add64(r4, t0, 0)\n\n\tt1, r2 = bits.Mul64(q0, m.arr[2])\n\tr3, c = bits.Add64(r3, t1, 0)\n\t_, t0 = bits.Mul64(q2, m.arr[2])\n\tr4, _ = bits.Add64(r4, t0, c)\n\n\tt1, t0 = bits.Mul64(q1, m.arr[2])\n\tr3, c = bits.Add64(r3, t0, 0)\n\tr4, _ = bits.Add64(r4, t1, c)\n\n\tt1, r1 = bits.Mul64(q0, m.arr[1])\n\tr2, c = bits.Add64(r2, t1, 0)\n\tt1, t0 = bits.Mul64(q2, m.arr[1])\n\tr3, c = bits.Add64(r3, t0, c)\n\tr4, _ = bits.Add64(r4, t1, c)\n\n\tt1, t0 = bits.Mul64(q1, m.arr[1])\n\tr2, c = bits.Add64(r2, t0, 0)\n\tr3, c = bits.Add64(r3, t1, c)\n\t_, t0 = bits.Mul64(q3, m.arr[1])\n\tr4, _ = bits.Add64(r4, t0, c)\n\n\tt1, r0 = bits.Mul64(q0, m.arr[0])\n\tr1, c = bits.Add64(r1, t1, 0)\n\tt1, t0 = bits.Mul64(q2, m.arr[0])\n\tr2, c = bits.Add64(r2, t0, c)\n\tr3, c = bits.Add64(r3, t1, c)\n\t_, t0 = bits.Mul64(q4, m.arr[0])\n\tr4, _ = bits.Add64(r4, t0, c)\n\n\tt1, t0 = bits.Mul64(q1, m.arr[0])\n\tr1, c = bits.Add64(r1, t0, 0)\n\tr2, c = bits.Add64(r2, t1, c)\n\tt1, t0 = bits.Mul64(q3, m.arr[0])\n\tr3, c = bits.Add64(r3, t0, c)\n\tr4, _ = bits.Add64(r4, t1, c)\n\n\t// r = r1 - r2\n\n\tvar b uint64\n\n\tr0, b = bits.Sub64(x0, r0, 0)\n\tr1, b = bits.Sub64(x1, r1, b)\n\tr2, b = bits.Sub64(x2, r2, b)\n\tr3, b = bits.Sub64(x3, r3, b)\n\tr4, b = bits.Sub64(x4, r4, b)\n\n\t// if r\u003c0 then r+=m\n\n\tif b != 0 {\n\t\tr0, c = bits.Add64(r0, m.arr[0], 0)\n\t\tr1, c = bits.Add64(r1, m.arr[1], c)\n\t\tr2, c = bits.Add64(r2, m.arr[2], c)\n\t\tr3, c = bits.Add64(r3, m.arr[3], c)\n\t\tr4, _ = bits.Add64(r4, 0, c)\n\t}\n\n\t// while (r\u003e=m) r-=m\n\n\tfor {\n\t\t// q = r - m\n\t\tq0, b = bits.Sub64(r0, m.arr[0], 0)\n\t\tq1, b = bits.Sub64(r1, m.arr[1], b)\n\t\tq2, b = bits.Sub64(r2, m.arr[2], b)\n\t\tq3, b = bits.Sub64(r3, m.arr[3], b)\n\t\tq4, b = bits.Sub64(r4, 0, b)\n\n\t\t// if borrow break\n\t\tif b != 0 {\n\t\t\tbreak\n\t\t}\n\n\t\t// r = q\n\t\tr4, r3, r2, r1, r0 = q4, q3, q2, q1, q0\n\t}\n\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = r3, r2, r1, r0\n\n\treturn z\n}\n"},{"name":"uint256.gno","body":"// Ported from https://github.com/holiman/uint256\n// This package provides a 256-bit unsigned integer type, Uint256, and associated functions.\npackage uint256\n\nimport (\n\t\"errors\"\n\t\"math/bits\"\n\t\"strconv\"\n)\n\nconst (\n\tMaxUint64 = 1\u003c\u003c64 - 1\n\tuintSize = 32 \u003c\u003c (^uint(0) \u003e\u003e 63)\n)\n\n// Uint is represented as an array of 4 uint64, in little-endian order,\n// so that Uint[3] is the most significant, and Uint[0] is the least significant\ntype Uint struct {\n\tarr [4]uint64\n}\n\n// NewUint returns a new initialized Uint.\nfunc NewUint(val uint64) *Uint {\n\tz := \u0026Uint{arr: [4]uint64{val, 0, 0, 0}}\n\treturn z\n}\n\n// Zero returns a new Uint initialized to zero.\nfunc Zero() *Uint {\n\treturn NewUint(0)\n}\n\n// One returns a new Uint initialized to one.\nfunc One() *Uint {\n\treturn NewUint(1)\n}\n\n// SetAllOne sets all the bits of z to 1\nfunc (z *Uint) SetAllOne() *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = MaxUint64, MaxUint64, MaxUint64, MaxUint64\n\treturn z\n}\n\n// Set sets z to x and returns z.\nfunc (z *Uint) Set(x *Uint) *Uint {\n\t*z = *x\n\n\treturn z\n}\n\n// SetOne sets z to 1\nfunc (z *Uint) SetOne() *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, 0, 0, 1\n\treturn z\n}\n\nconst twoPow256Sub1 = \"115792089237316195423570985008687907853269984665640564039457584007913129639935\"\n\n// SetFromDecimal sets z from the given string, interpreted as a decimal number.\n// OBS! This method is _not_ strictly identical to the (*big.Uint).SetString(..., 10) method.\n// Notable differences:\n// - This method does not accept underscore input, e.g. \"100_000\",\n// - This method does not accept negative zero as valid, e.g \"-0\",\n// - (this method does not accept any negative input as valid))\nfunc (z *Uint) SetFromDecimal(s string) (err error) {\n\t// Remove max one leading +\n\tif len(s) \u003e 0 \u0026\u0026 s[0] == '+' {\n\t\ts = s[1:]\n\t}\n\t// Remove any number of leading zeroes\n\tif len(s) \u003e 0 \u0026\u0026 s[0] == '0' {\n\t\tvar i int\n\t\tvar c rune\n\t\tfor i, c = range s {\n\t\t\tif c != '0' {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\ts = s[i:]\n\t}\n\tif len(s) \u003c len(twoPow256Sub1) {\n\t\treturn z.fromDecimal(s)\n\t}\n\tif len(s) == len(twoPow256Sub1) {\n\t\tif s \u003e twoPow256Sub1 {\n\t\t\treturn ErrBig256Range\n\t\t}\n\t\treturn z.fromDecimal(s)\n\t}\n\treturn ErrBig256Range\n}\n\n// FromDecimal is a convenience-constructor to create an Uint from a\n// decimal (base 10) string. Numbers larger than 256 bits are not accepted.\nfunc FromDecimal(decimal string) (*Uint, error) {\n\tvar z Uint\n\tif err := z.SetFromDecimal(decimal); err != nil {\n\t\treturn nil, err\n\t}\n\treturn \u0026z, nil\n}\n\n// MustFromDecimal is a convenience-constructor to create an Uint from a\n// decimal (base 10) string.\n// Returns a new Uint and panics if any error occurred.\nfunc MustFromDecimal(decimal string) *Uint {\n\tvar z Uint\n\tif err := z.SetFromDecimal(decimal); err != nil {\n\t\tpanic(err)\n\t}\n\treturn \u0026z\n}\n\n// multipliers holds the values that are needed for fromDecimal\nvar multipliers = [5]*Uint{\n\tnil, // represents first round, no multiplication needed\n\t{[4]uint64{10000000000000000000, 0, 0, 0}}, // 10 ^ 19\n\t{[4]uint64{687399551400673280, 5421010862427522170, 0, 0}}, // 10 ^ 38\n\t{[4]uint64{5332261958806667264, 17004971331911604867, 2938735877055718769, 0}}, // 10 ^ 57\n\t{[4]uint64{0, 8607968719199866880, 532749306367912313, 1593091911132452277}}, // 10 ^ 76\n}\n\n// fromDecimal is a helper function to only ever be called via SetFromDecimal\n// this function takes a string and chunks it up, calling ParseUint on it up to 5 times\n// these chunks are then multiplied by the proper power of 10, then added together.\nfunc (z *Uint) fromDecimal(bs string) error {\n\t// first clear the input\n\tz.Clear()\n\t// the maximum value of uint64 is 18446744073709551615, which is 20 characters\n\t// one less means that a string of 19 9's is always within the uint64 limit\n\tvar (\n\t\tnum uint64\n\t\terr error\n\t\tremaining = len(bs)\n\t)\n\tif remaining == 0 {\n\t\treturn errors.New(\"EOF\")\n\t}\n\t// We proceed in steps of 19 characters (nibbles), from least significant to most significant.\n\t// This means that the first (up to) 19 characters do not need to be multiplied.\n\t// In the second iteration, our slice of 19 characters needs to be multipleied\n\t// by a factor of 10^19. Et cetera.\n\tfor i, mult := range multipliers {\n\t\tif remaining \u003c= 0 {\n\t\t\treturn nil // Done\n\t\t} else if remaining \u003e 19 {\n\t\t\tnum, err = strconv.ParseUint(bs[remaining-19:remaining], 10, 64)\n\t\t} else {\n\t\t\t// Final round\n\t\t\tnum, err = strconv.ParseUint(bs, 10, 64)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t// add that number to our running total\n\t\tif i == 0 {\n\t\t\tz.SetUint64(num)\n\t\t} else {\n\t\t\tbase := NewUint(num)\n\t\t\tz.Add(z, base.Mul(base, mult))\n\t\t}\n\t\t// Chop off another 19 characters\n\t\tif remaining \u003e 19 {\n\t\t\tbs = bs[0 : remaining-19]\n\t\t}\n\t\tremaining -= 19\n\t}\n\treturn nil\n}\n\n// Byte sets z to the value of the byte at position n,\n// with 'z' considered as a big-endian 32-byte integer\n// if 'n' \u003e 32, f is set to 0\n// Example: f = '5', n=31 =\u003e 5\nfunc (z *Uint) Byte(n *Uint) *Uint {\n\t// in z, z.arr[0] is the least significant\n\tif number, overflow := n.Uint64WithOverflow(); !overflow {\n\t\tif number \u003c 32 {\n\t\t\tnumber := z.arr[4-1-number/8]\n\t\t\toffset := (n.arr[0] \u0026 0x7) \u003c\u003c 3 // 8*(n.d % 8)\n\t\t\tz.arr[0] = (number \u0026 (0xff00000000000000 \u003e\u003e offset)) \u003e\u003e (56 - offset)\n\t\t\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\t\t\treturn z\n\t\t}\n\t}\n\n\treturn z.Clear()\n}\n\n// BitLen returns the number of bits required to represent z\nfunc (z *Uint) BitLen() int {\n\tswitch {\n\tcase z.arr[3] != 0:\n\t\treturn 192 + bits.Len64(z.arr[3])\n\tcase z.arr[2] != 0:\n\t\treturn 128 + bits.Len64(z.arr[2])\n\tcase z.arr[1] != 0:\n\t\treturn 64 + bits.Len64(z.arr[1])\n\tdefault:\n\t\treturn bits.Len64(z.arr[0])\n\t}\n}\n\n// ByteLen returns the number of bytes required to represent z\nfunc (z *Uint) ByteLen() int {\n\treturn (z.BitLen() + 7) / 8\n}\n\n// Clear sets z to 0\nfunc (z *Uint) Clear() *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, 0, 0, 0\n\treturn z\n}\n\nconst (\n\t// hextable = \"0123456789abcdef\"\n\tbintable = \"\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\a\\b\\t\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\n\\v\\f\\r\\x0e\\x0f\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\n\\v\\f\\r\\x0e\\x0f\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\"\n\tbadNibble = 0xff\n)\n\n// SetFromHex sets z from the given string, interpreted as a hexadecimal number.\n// OBS! This method is _not_ strictly identical to the (*big.Int).SetString(..., 16) method.\n// Notable differences:\n// - This method _require_ \"0x\" or \"0X\" prefix.\n// - This method does not accept zero-prefixed hex, e.g. \"0x0001\"\n// - This method does not accept underscore input, e.g. \"100_000\",\n// - This method does not accept negative zero as valid, e.g \"-0x0\",\n// - (this method does not accept any negative input as valid)\nfunc (z *Uint) SetFromHex(hex string) error {\n\treturn z.fromHex(hex)\n}\n\n// fromHex is the internal implementation of parsing a hex-string.\nfunc (z *Uint) fromHex(hex string) error {\n\tif err := checkNumberS(hex); err != nil {\n\t\treturn err\n\t}\n\tif len(hex) \u003e 66 {\n\t\treturn ErrBig256Range\n\t}\n\tz.Clear()\n\tend := len(hex)\n\tfor i := 0; i \u003c 4; i++ {\n\t\tstart := end - 16\n\t\tif start \u003c 2 {\n\t\t\tstart = 2\n\t\t}\n\t\tfor ri := start; ri \u003c end; ri++ {\n\t\t\tnib := bintable[hex[ri]]\n\t\t\tif nib == badNibble {\n\t\t\t\treturn ErrSyntax\n\t\t\t}\n\t\t\tz.arr[i] = z.arr[i] \u003c\u003c 4\n\t\t\tz.arr[i] += uint64(nib)\n\t\t}\n\t\tend = start\n\t}\n\treturn nil\n}\n\n// FromHex is a convenience-constructor to create an Uint from\n// a hexadecimal string. The string is required to be '0x'-prefixed\n// Numbers larger than 256 bits are not accepted.\nfunc FromHex(hex string) (*Uint, error) {\n\tvar z Uint\n\tif err := z.fromHex(hex); err != nil {\n\t\treturn nil, err\n\t}\n\treturn \u0026z, nil\n}\n\n// MustFromHex is a convenience-constructor to create an Uint from\n// a hexadecimal string.\n// Returns a new Uint and panics if any error occurred.\nfunc MustFromHex(hex string) *Uint {\n\tvar z Uint\n\tif err := z.fromHex(hex); err != nil {\n\t\tpanic(err)\n\t}\n\treturn \u0026z\n}\n\n// Clone creates a new Uint identical to z\nfunc (z *Uint) Clone() *Uint {\n\tvar x Uint\n\tx.arr[0] = z.arr[0]\n\tx.arr[1] = z.arr[1]\n\tx.arr[2] = z.arr[2]\n\tx.arr[3] = z.arr[3]\n\n\treturn \u0026x\n}\n"},{"name":"uint256_test.gno","body":"package uint256\n\nimport (\n\t\"testing\"\n)\n\nfunc TestSetAllOne(t *testing.T) {\n\tz := Zero()\n\tz.SetAllOne()\n\tif z.ToString() != twoPow256Sub1 {\n\t\tt.Errorf(\"Expected all ones, got %s\", z.ToString())\n\t}\n}\n\nfunc TestByte(t *testing.T) {\n\ttests := []struct {\n\t\tinput string\n\t\tposition uint64\n\t\texpected byte\n\t}{\n\t\t{\"0x1000000000000000000000000000000000000000000000000000000000000000\", 0, 16},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 0, 255},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 31, 255},\n\t}\n\n\tfor i, tt := range tests {\n\t\tz, _ := FromHex(tt.input)\n\t\tn := NewUint(tt.position)\n\t\tresult := z.Byte(n)\n\n\t\tif result.arr[0] != uint64(tt.expected) {\n\t\t\tt.Errorf(\"Test case %d failed. Input: %s, Position: %d, Expected: %d, Got: %d\",\n\t\t\t\ti, tt.input, tt.position, tt.expected, result.arr[0])\n\t\t}\n\n\t\t// check other array elements are 0\n\t\tif result.arr[1] != 0 || result.arr[2] != 0 || result.arr[3] != 0 {\n\t\t\tt.Errorf(\"Test case %d failed. Non-zero values in upper bytes\", i)\n\t\t}\n\t}\n\n\t// overflow\n\tz, _ := FromHex(\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\")\n\tn := NewUint(32)\n\tresult := z.Byte(n)\n\n\tif !result.IsZero() {\n\t\tt.Errorf(\"Expected zero for position \u003e= 32, got %v\", result)\n\t}\n}\n\nfunc TestBitLen(t *testing.T) {\n\ttests := []struct {\n\t\tinput string\n\t\texpected int\n\t}{\n\t\t{\"0x0\", 0},\n\t\t{\"0x1\", 1},\n\t\t{\"0xff\", 8},\n\t\t{\"0x100\", 9},\n\t\t{\"0xffff\", 16},\n\t\t{\"0x10000\", 17},\n\t\t{\"0xffffffffffffffff\", 64},\n\t\t{\"0x10000000000000000\", 65},\n\t\t{\"0xffffffffffffffffffffffffffffffff\", 128},\n\t\t{\"0x100000000000000000000000000000000\", 129},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 256},\n\t}\n\n\tfor i, tt := range tests {\n\t\tz, _ := FromHex(tt.input)\n\t\tresult := z.BitLen()\n\n\t\tif result != tt.expected {\n\t\t\tt.Errorf(\"Test case %d failed. Input: %s, Expected: %d, Got: %d\",\n\t\t\t\ti, tt.input, tt.expected, result)\n\t\t}\n\t}\n}\n\nfunc TestByteLen(t *testing.T) {\n\ttests := []struct {\n\t\tinput string\n\t\texpected int\n\t}{\n\t\t{\"0x0\", 0},\n\t\t{\"0x1\", 1},\n\t\t{\"0xff\", 1},\n\t\t{\"0x100\", 2},\n\t\t{\"0xffff\", 2},\n\t\t{\"0x10000\", 3},\n\t\t{\"0xffffffffffffffff\", 8},\n\t\t{\"0x10000000000000000\", 9},\n\t\t{\"0xffffffffffffffffffffffffffffffff\", 16},\n\t\t{\"0x100000000000000000000000000000000\", 17},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 32},\n\t}\n\n\tfor i, tt := range tests {\n\t\tz, _ := FromHex(tt.input)\n\t\tresult := z.ByteLen()\n\n\t\tif result != tt.expected {\n\t\t\tt.Errorf(\"Test case %d failed. Input: %s, Expected: %d, Got: %d\",\n\t\t\t\ti, tt.input, tt.expected, result)\n\t\t}\n\t}\n}\n\nfunc TestClone(t *testing.T) {\n\ttests := []struct {\n\t\tinput string\n\t\texpected string\n\t}{\n\t\t{\"0x1\", \"1\"},\n\t\t{\"0x100\", \"256\"},\n\t\t{\"0x10000000000000000\", \"18446744073709551616\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tz, _ := FromHex(tt.input)\n\t\tresult := z.Clone()\n\t\tif result.ToString() != tt.expected {\n\t\t\tt.Errorf(\"Test %s failed. Expected %s, got %s\", tt.input, tt.expected, result.ToString())\n\t\t}\n\t}\n}\n"},{"name":"utils.gno","body":"package uint256\n\nfunc checkNumberS(input string) error {\n\tconst fn = \"UnmarshalText\"\n\tl := len(input)\n\tif l == 0 {\n\t\treturn errEmptyString(fn, input)\n\t}\n\tif l \u003c 2 || input[0] != '0' ||\n\t\t(input[1] != 'x' \u0026\u0026 input[1] != 'X') {\n\t\treturn errMissingPrefix(fn, input)\n\t}\n\tif l == 2 {\n\t\treturn errEmptyNumber(fn, input)\n\t}\n\tif len(input) \u003e 3 \u0026\u0026 input[2] == '0' {\n\t\treturn errLeadingZero(fn, input)\n\t}\n\treturn nil\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}} @@ -98,7 +98,7 @@ {"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"eval","path":"gno.land/r/demo/math_eval","files":[{"name":"math_eval.gno","body":"// eval realm is capable of evaluating 32-bit integer\n// expressions as they would appear in Go. For example:\n// /r/demo/math_eval:(4+12)/2-1+11*15\npackage eval\n\nimport (\n\tevalint32 \"gno.land/p/demo/math_eval/int32\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nfunc Render(p string) string {\n\tif len(p) == 0 {\n\t\treturn `\nevaluates 32-bit integer expressions. for example:\n\t\t\n[(4+12)/2-1+11*15](/r/demo/math_eval:(4+12)/2-1+11*15)\n\n`\n\t}\n\texpr, err := evalint32.Parse(p)\n\tif err != nil {\n\t\treturn err.Error()\n\t}\n\tres, err := evalint32.Eval(expr, nil)\n\tif err != nil {\n\t\treturn err.Error()\n\t}\n\n\treturn ufmt.Sprintf(\"%s = %d\", p, res)\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":"memeland","path":"gno.land/r/demo/memeland","files":[{"name":"memeland.gno","body":"package memeland\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/demo/memeland\"\n)\n\nvar m *memeland.Memeland\n\nfunc init() {\n\tm = memeland.NewMemeland()\n\tm.TransferOwnership(\"g125em6arxsnj49vx35f0n0z34putv5ty3376fg5\")\n}\n\nfunc PostMeme(data string, timestamp int64) string {\n\treturn m.PostMeme(data, timestamp)\n}\n\nfunc Upvote(id string) string {\n\treturn m.Upvote(id)\n}\n\nfunc GetPostsInRange(startTimestamp, endTimestamp int64, page, pageSize int, sortBy string) string {\n\treturn m.GetPostsInRange(startTimestamp, endTimestamp, page, pageSize, sortBy)\n}\n\nfunc RemovePost(id string) string {\n\treturn m.RemovePost(id)\n}\n\nfunc GetOwner() std.Address {\n\treturn m.Owner()\n}\n\nfunc TransferOwnership(newOwner std.Address) {\n\tif err := m.TransferOwnership(newOwner); err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc Render(path string) string {\n\tnumOfMemes := int(m.MemeCounter)\n\tif numOfMemes == 0 {\n\t\treturn \"No memes posted yet! :/\"\n\t}\n\n\t// Default render is get Posts since year 2000 to now\n\treturn m.GetPostsInRange(0, time.Now().Unix(), 1, 10, \"DATE_CREATED\")\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":"microblog","path":"gno.land/r/demo/microblog","files":[{"name":"README.md","body":"# microblog realm\n\n## Getting started:\n\n(One-time) Add the microblog package:\n\n```\ngnokey maketx addpkg --pkgpath \"gno.land/p/demo/microblog\" --pkgdir \"examples/gno.land/p/demo/microblog\" \\\n --deposit 100000000ugnot --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast --chainid dev --remote localhost:26657 \u003cYOURKEY\u003e\n```\n\n(One-time) Add the microblog realm:\n\n```\ngnokey maketx addpkg --pkgpath \"gno.land/r/demo/microblog\" --pkgdir \"examples/gno.land/r/demo/microblog\" \\\n --deposit 100000000ugnot --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast --chainid dev --remote localhost:26657 \u003cYOURKEY\u003e\n```\n\nAdd a microblog post:\n\n```\ngnokey maketx call --pkgpath \"gno.land/r/demo/microblog\" --func \"NewPost\" --args \"hello, world\" \\\n --gas-fee \"1000000ugnot\" --gas-wanted \"2000000\" --broadcast --chainid dev --remote localhost:26657 \u003cYOURKEY\u003e\n```"},{"name":"microblog.gno","body":"// Microblog is a website with shortform posts from users.\n// The API is simple - \"AddPost\" takes markdown and\n// adds it to the users site.\n// The microblog location is determined by the user address\n// /r/demo/microblog:\u003cYOUR-ADDRESS\u003e\npackage microblog\n\nimport (\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/microblog\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\ttitle = \"gno-based microblog\"\n\tprefix = \"/r/demo/microblog:\"\n\tm *microblog.Microblog\n)\n\nfunc init() {\n\tm = microblog.NewMicroblog(title, prefix)\n}\n\nfunc renderHome() string {\n\toutput := ufmt.Sprintf(\"# %s\\n\\n\", m.Title)\n\toutput += \"# pages\\n\\n\"\n\n\tfor _, page := range m.GetPages() {\n\t\tif u := users.GetUserByAddress(page.Author); u != nil {\n\t\t\toutput += ufmt.Sprintf(\"- [%s (%s)](%s%s)\\n\", u.Name, page.Author.String(), m.Prefix, page.Author.String())\n\t\t} else {\n\t\t\toutput += ufmt.Sprintf(\"- [%s](%s%s)\\n\", page.Author.String(), m.Prefix, page.Author.String())\n\t\t}\n\t}\n\n\treturn output\n}\n\nfunc renderUser(user string) string {\n\tsilo, found := m.Pages.Get(user)\n\tif !found {\n\t\treturn \"404\" // StatusNotFound\n\t}\n\n\treturn PageToString((silo.(*microblog.Page)))\n}\n\nfunc Render(path string) string {\n\tparts := strings.Split(path, \"/\")\n\n\tisHome := path == \"\"\n\tisUser := len(parts) == 1\n\n\tswitch {\n\tcase isHome:\n\t\treturn renderHome()\n\n\tcase isUser:\n\t\treturn renderUser(parts[0])\n\t}\n\n\treturn \"404\" // StatusNotFound\n}\n\nfunc PageToString(p *microblog.Page) string {\n\to := \"\"\n\tif u := users.GetUserByAddress(p.Author); u != nil {\n\t\to += ufmt.Sprintf(\"# [%s](/r/demo/users:%s)\\n\\n\", u, u)\n\t\to += ufmt.Sprintf(\"%s\\n\\n\", u.Profile)\n\t}\n\to += ufmt.Sprintf(\"## [%s](/r/demo/microblog:%s)\\n\\n\", p.Author, p.Author)\n\n\to += ufmt.Sprintf(\"joined %s, last updated %s\\n\\n\", p.CreatedAt.Format(\"2006-02-01\"), p.LastPosted.Format(\"2006-02-01\"))\n\to += \"## feed\\n\\n\"\n\tfor _, u := range p.GetPosts() {\n\t\to += u.String() + \"\\n\\n\"\n\t}\n\treturn o\n}\n\n// NewPost takes a single argument (post markdown) and\n// adds a post to the address of the caller.\nfunc NewPost(text string) string {\n\tif err := m.NewPost(text); err != nil {\n\t\treturn \"unable to add new post\"\n\t}\n\treturn \"added new post\"\n}\n\nfunc Register(name, profile string) string {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(caller, name, profile)\n\treturn \"OK\"\n}\n"},{"name":"microblog_test.gno","body":"package microblog\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestMicroblog(t *testing.T) {\n\tconst (\n\t\tauthor1 std.Address = testutils.TestAddress(\"author1\")\n\t\tauthor2 std.Address = testutils.TestAddress(\"author2\")\n\t)\n\n\tstd.TestSetOriginCaller(author1)\n\n\turequire.Equal(t, \"404\", Render(\"/wrongpath\"), \"rendering not giving 404\")\n\turequire.NotEqual(t, \"404\", Render(\"\"), \"rendering / should not give 404\")\n\turequire.NoError(t, m.NewPost(\"goodbyte, web2\"), \"could not create post\")\n\n\t_, err := m.GetPage(author1.String())\n\turequire.NoError(t, err, \"silo should exist\")\n\n\t_, err = m.GetPage(\"no such author\")\n\turequire.Error(t, err, \"silo should not exist\")\n\n\tstd.TestSetOriginCaller(author2)\n\n\turequire.NoError(t, m.NewPost(\"hello, web3\"), \"could not create post\")\n\turequire.NoError(t, m.NewPost(\"hello again, web3\"), \"could not create post\")\n\turequire.NoError(t, m.NewPost(\"hi again,\\n web4?\"), \"could not create post\")\n\n\tprintln(\"--- MICROBLOG ---\\n\\n\")\n\n\texpected := `# gno-based microblog\n\n# pages\n\n- [g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6](/r/demo/microblog:g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6)\n- [g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00](/r/demo/microblog:g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00)\n`\n\turequire.Equal(t, expected, Render(\"\"), \"incorrect rendering\")\n\n\texpected = `## [g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6](/r/demo/microblog:g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6)\n\njoined 2009-13-02, last updated 2009-13-02\n\n## feed\n\n\u003e goodbyte, web2\n\u003e\n\u003e *Fri, 13 Feb 2009 23:31:30 UTC*`\n\n\turequire.Equal(t, expected, strings.TrimSpace(Render(author1.String())), \"incorrect rendering\")\n\n\texpected = `## [g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00](/r/demo/microblog:g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00)\n\njoined 2009-13-02, last updated 2009-13-02\n\n## feed\n\n\u003e hi again,\n\u003e\n\u003e web4?\n\u003e\n\u003e *Fri, 13 Feb 2009 23:31:30 UTC*\n\n\u003e hello again, web3\n\u003e\n\u003e *Fri, 13 Feb 2009 23:31:30 UTC*\n\n\u003e hello, web3\n\u003e\n\u003e *Fri, 13 Feb 2009 23:31:30 UTC*`\n\n\turequire.Equal(t, expected, strings.TrimSpace(Render(author2.String())), \"incorrect rendering\")\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":"nft","path":"gno.land/r/demo/nft","files":[{"name":"README.md","body":"NFT's are all the rage these days, for various reasons.\n\nI read over EIP-721 which appears to be the de-facto NFT standard on Ethereum. Then, made a sample implementation of EIP-721 (let's here called GRC-721). The implementation isn't complete, but it demonstrates the main functionality.\n\n- [EIP-721](https://eips.ethereum.org/EIPS/eip-721)\n- [gno.land/r/demo/nft/nft.go](https://gno.land/r/demo/nft/nft.go)\n- [zrealm_nft3.go test](https://github.com/gnolang/gno/blob/master/tests/files2/zrealm_nft3.gno)\n\nIn short, this demonstrates how to implement Ethereum contract interfaces in gno.land; by using only standard Go language features.\n\nPlease leave a comment ([guide](https://gno.land/r/demo/boards:gnolang/1)).\n"},{"name":"nft.gno","body":"package nft\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/grc/grc721\"\n)\n\ntype token struct {\n\tgrc721.IGRC721 // implements the GRC721 interface\n\n\ttokenCounter int\n\ttokens avl.Tree // grc721.TokenID -\u003e *NFToken{}\n\toperators avl.Tree // owner std.Address -\u003e operator std.Address\n}\n\ntype NFToken struct {\n\towner std.Address\n\tapproved std.Address\n\ttokenID grc721.TokenID\n\tdata string\n}\n\nvar gToken = \u0026token{}\n\nfunc GetToken() *token { return gToken }\n\nfunc (grc *token) nextTokenID() grc721.TokenID {\n\tgrc.tokenCounter++\n\ts := strconv.Itoa(grc.tokenCounter)\n\treturn grc721.TokenID(s)\n}\n\nfunc (grc *token) getToken(tid grc721.TokenID) (*NFToken, bool) {\n\ttoken, ok := grc.tokens.Get(string(tid))\n\tif !ok {\n\t\treturn nil, false\n\t}\n\treturn token.(*NFToken), true\n}\n\nfunc (grc *token) Mint(to std.Address, data string) grc721.TokenID {\n\ttid := grc.nextTokenID()\n\tgrc.tokens.Set(string(tid), \u0026NFToken{\n\t\towner: to,\n\t\ttokenID: tid,\n\t\tdata: data,\n\t})\n\treturn tid\n}\n\nfunc (grc *token) BalanceOf(owner std.Address) (count int64) {\n\tpanic(\"not yet implemented\")\n}\n\nfunc (grc *token) OwnerOf(tid grc721.TokenID) std.Address {\n\ttoken, ok := grc.getToken(tid)\n\tif !ok {\n\t\tpanic(\"token does not exist\")\n\t}\n\treturn token.owner\n}\n\n// XXX not fully implemented yet.\nfunc (grc *token) SafeTransferFrom(from, to std.Address, tid grc721.TokenID) {\n\tgrc.TransferFrom(from, to, tid)\n\t// When transfer is complete, this function checks if `_to` is a smart\n\t// contract (code size \u003e 0). If so, it calls `onERC721Received` on\n\t// `_to` and throws if the return value is not\n\t// `bytes4(keccak256(\"onERC721Received(address,address,uint256,bytes)\"))`.\n\t// XXX ensure \"to\" is a realm with onERC721Received() signature.\n}\n\nfunc (grc *token) TransferFrom(from, to std.Address, tid grc721.TokenID) {\n\tcaller := std.GetCallerAt(2)\n\ttoken, ok := grc.getToken(tid)\n\t// Throws if `_tokenId` is not a valid NFT.\n\tif !ok {\n\t\tpanic(\"token does not exist\")\n\t}\n\t// Throws unless `msg.sender` is the current owner, an authorized\n\t// operator, or the approved address for this NFT.\n\tif caller != token.owner \u0026\u0026 caller != token.approved {\n\t\toperator, ok := grc.operators.Get(token.owner.String())\n\t\tif !ok || caller != operator.(std.Address) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t}\n\t// Throws if `_from` is not the current owner.\n\tif from != token.owner {\n\t\tpanic(\"from is not the current owner\")\n\t}\n\t// Throws if `_to` is the zero address.\n\tif to == \"\" {\n\t\tpanic(\"to cannot be empty\")\n\t}\n\t// Good.\n\ttoken.owner = to\n}\n\nfunc (grc *token) Approve(approved std.Address, tid grc721.TokenID) {\n\tcaller := std.GetCallerAt(2)\n\ttoken, ok := grc.getToken(tid)\n\t// Throws if `_tokenId` is not a valid NFT.\n\tif !ok {\n\t\tpanic(\"token does not exist\")\n\t}\n\t// Throws unless `msg.sender` is the current owner,\n\t// or an authorized operator.\n\tif caller != token.owner {\n\t\toperator, ok := grc.operators.Get(token.owner.String())\n\t\tif !ok || caller != operator.(std.Address) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t}\n\t// Good.\n\ttoken.approved = approved\n}\n\n// XXX make it work for set of operators.\nfunc (grc *token) SetApprovalForAll(operator std.Address, approved bool) {\n\tcaller := std.GetCallerAt(2)\n\tgrc.operators.Set(caller.String(), operator)\n}\n\nfunc (grc *token) GetApproved(tid grc721.TokenID) std.Address {\n\ttoken, ok := grc.getToken(tid)\n\t// Throws if `_tokenId` is not a valid NFT.\n\tif !ok {\n\t\tpanic(\"token does not exist\")\n\t}\n\treturn token.approved\n}\n\n// XXX make it work for set of operators\nfunc (grc *token) IsApprovedForAll(owner, operator std.Address) bool {\n\toperator2, ok := grc.operators.Get(owner.String())\n\tif !ok {\n\t\treturn false\n\t}\n\treturn operator == operator2.(std.Address)\n}\n"},{"name":"z_0_filetest.gno","body":"// PKGPATH: gno.land/r/demo/nft_test\npackage nft_test\n\nimport (\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/nft\"\n)\n\nfunc main() {\n\taddr1 := testutils.TestAddress(\"addr1\")\n\t// addr2 := testutils.TestAddress(\"addr2\")\n\tgrc721 := nft.GetToken()\n\ttid := grc721.Mint(addr1, \"NFT#1\")\n\tprintln(grc721.OwnerOf(tid))\n\tprintln(addr1)\n}\n\n// Output:\n// g1v9jxgu33ta047h6lta047h6lta047h6l43dqc5\n// g1v9jxgu33ta047h6lta047h6lta047h6l43dqc5\n\n// Realm:\n// switchrealm[\"gno.land/r/demo/nft\"]\n// switchrealm[\"gno.land/r/demo/nft\"]\n// c[67c479d3d51d4056b2f4111d5352912a00be311e:11]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"std.Address\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"g1v9jxgu33ta047h6lta047h6lta047h6l43dqc5\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"std.Address\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/grc/grc721.TokenID\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"1\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"NFT#1\"\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:11\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:10\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[67c479d3d51d4056b2f4111d5352912a00be311e:10]={\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:10\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:9\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/nft.NFToken\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"564a9e78be869bd258fc3c9ad56f5a75ed68818f\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:11\"\n// }\n// }\n// }\n// c[67c479d3d51d4056b2f4111d5352912a00be311e:9]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"1\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/nft.NFToken\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"b53ffc464e1b5655d19b9d5277f3491717c24aca\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:10\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:9\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:8\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[67c479d3d51d4056b2f4111d5352912a00be311e:8]={\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:8\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:5\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"b1d928b3716b147c92730e8d234162bec2f0f2fc\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:9\"\n// }\n// }\n// }\n// u[67c479d3d51d4056b2f4111d5352912a00be311e:5]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"b229b824842ec3e7f2341e33d0fa0ca77af2f480\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:8\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:5\",\n// \"ModTime\": \"7\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:4\",\n// \"RefCount\": \"1\"\n// }\n// }\n// u[67c479d3d51d4056b2f4111d5352912a00be311e:4]={\n// \"Fields\": [\n// {},\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"1e0b9dddb406b4f50500a022266a4cb8a4ea38c6\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:5\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"05ab6746ea84b55ca133806af215d99a1c4b045e\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:6\"\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:4\",\n// \"ModTime\": \"7\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:3\",\n// \"RefCount\": \"1\"\n// }\n// }\n// switchrealm[\"gno.land/r/demo/nft\"]\n// switchrealm[\"gno.land/r/demo/nft_test\"]\n"},{"name":"z_1_filetest.gno","body":"// PKGPATH: gno.land/r/demo/nft_test\npackage nft_test\n\nimport (\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/nft\"\n)\n\nfunc main() {\n\taddr1 := testutils.TestAddress(\"addr1\")\n\taddr2 := testutils.TestAddress(\"addr2\")\n\tgrc721 := nft.GetToken()\n\ttid := grc721.Mint(addr1, \"NFT#1\")\n\tprintln(grc721.OwnerOf(tid))\n\tprintln(addr1)\n\tgrc721.TransferFrom(addr1, addr2, tid)\n}\n\n// Error:\n// unauthorized\n"},{"name":"z_2_filetest.gno","body":"// PKGPATH: gno.land/r/demo/nft_test\npackage nft_test\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/nft\"\n)\n\nfunc main() {\n\tcaller := std.GetCallerAt(1)\n\taddr1 := testutils.TestAddress(\"addr1\")\n\t// addr2 := testutils.TestAddress(\"addr2\")\n\tgrc721 := nft.GetToken()\n\ttid := grc721.Mint(caller, \"NFT#1\")\n\tprintln(grc721.OwnerOf(tid))\n\tprintln(addr1)\n\tgrc721.TransferFrom(caller, addr1, tid)\n}\n\n// Output:\n// g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n// g1v9jxgu33ta047h6lta047h6lta047h6l43dqc5\n"},{"name":"z_3_filetest.gno","body":"// PKGPATH: gno.land/r/demo/nft_test\npackage nft_test\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/nft\"\n)\n\nfunc main() {\n\tcaller := std.GetCallerAt(1)\n\taddr1 := testutils.TestAddress(\"addr1\")\n\taddr2 := testutils.TestAddress(\"addr2\")\n\tgrc721 := nft.GetToken()\n\ttid := grc721.Mint(caller, \"NFT#1\")\n\tprintln(grc721.OwnerOf(tid))\n\tprintln(addr1)\n\tgrc721.Approve(caller, tid) // approve self.\n\tgrc721.TransferFrom(caller, addr1, tid)\n\tgrc721.TransferFrom(addr1, addr2, tid)\n}\n\n// Output:\n// g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n// g1v9jxgu33ta047h6lta047h6lta047h6l43dqc5\n"},{"name":"z_4_filetest.gno","body":"// PKGPATH: gno.land/r/demo/nft_test\npackage nft_test\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/nft\"\n)\n\nfunc main() {\n\tcaller := std.GetCallerAt(1)\n\taddr1 := testutils.TestAddress(\"addr1\")\n\taddr2 := testutils.TestAddress(\"addr2\")\n\tgrc721 := nft.GetToken()\n\ttid := grc721.Mint(caller, \"NFT#1\")\n\tprintln(grc721.OwnerOf(tid))\n\tprintln(addr1)\n\tgrc721.Approve(caller, tid) // approve self.\n\tgrc721.TransferFrom(caller, addr1, tid)\n\tgrc721.Approve(\"\", tid) // approve addr1.\n\tgrc721.TransferFrom(addr1, addr2, tid)\n}\n\n// Error:\n// unauthorized\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":"nft","path":"gno.land/r/demo/nft","files":[{"name":"README.md","body":"NFT's are all the rage these days, for various reasons.\n\nI read over EIP-721 which appears to be the de-facto NFT standard on Ethereum. Then, made a sample implementation of EIP-721 (let's here called GRC-721). The implementation isn't complete, but it demonstrates the main functionality.\n\n- [EIP-721](https://eips.ethereum.org/EIPS/eip-721)\n- [gno.land/r/demo/nft/nft.go](https://gno.land/r/demo/nft/nft.go)\n- [zrealm_nft3.go test](https://github.com/gnolang/gno/blob/master/tests/files2/zrealm_nft3.gno)\n\nIn short, this demonstrates how to implement Ethereum contract interfaces in gno.land; by using only standard Go language features.\n\nPlease leave a comment ([guide](https://gno.land/r/demo/boards:gnolang/1)).\n"},{"name":"nft.gno","body":"package nft\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/grc/grc721\"\n)\n\ntype token struct {\n\tgrc721.IGRC721 // implements the GRC721 interface\n\n\ttokenCounter int\n\ttokens avl.Tree // grc721.TokenID -\u003e *NFToken{}\n\toperators avl.Tree // owner std.Address -\u003e operator std.Address\n}\n\ntype NFToken struct {\n\towner std.Address\n\tapproved std.Address\n\ttokenID grc721.TokenID\n\tdata string\n}\n\nvar gToken = \u0026token{}\n\nfunc GetToken() *token { return gToken }\n\nfunc (grc *token) nextTokenID() grc721.TokenID {\n\tgrc.tokenCounter++\n\ts := strconv.Itoa(grc.tokenCounter)\n\treturn grc721.TokenID(s)\n}\n\nfunc (grc *token) getToken(tid grc721.TokenID) (*NFToken, bool) {\n\ttoken, ok := grc.tokens.Get(string(tid))\n\tif !ok {\n\t\treturn nil, false\n\t}\n\treturn token.(*NFToken), true\n}\n\nfunc (grc *token) Mint(to std.Address, data string) grc721.TokenID {\n\ttid := grc.nextTokenID()\n\tgrc.tokens.Set(string(tid), \u0026NFToken{\n\t\towner: to,\n\t\ttokenID: tid,\n\t\tdata: data,\n\t})\n\treturn tid\n}\n\nfunc (grc *token) BalanceOf(owner std.Address) (count int64) {\n\tpanic(\"not yet implemented\")\n}\n\nfunc (grc *token) OwnerOf(tid grc721.TokenID) std.Address {\n\ttoken, ok := grc.getToken(tid)\n\tif !ok {\n\t\tpanic(\"token does not exist\")\n\t}\n\treturn token.owner\n}\n\n// XXX not fully implemented yet.\nfunc (grc *token) SafeTransferFrom(from, to std.Address, tid grc721.TokenID) {\n\tgrc.TransferFrom(from, to, tid)\n\t// When transfer is complete, this function checks if `_to` is a smart\n\t// contract (code size \u003e 0). If so, it calls `onERC721Received` on\n\t// `_to` and throws if the return value is not\n\t// `bytes4(keccak256(\"onERC721Received(address,address,uint256,bytes)\"))`.\n\t// XXX ensure \"to\" is a realm with onERC721Received() signature.\n}\n\nfunc (grc *token) TransferFrom(from, to std.Address, tid grc721.TokenID) {\n\tcaller := std.CallerAt(2)\n\ttoken, ok := grc.getToken(tid)\n\t// Throws if `_tokenId` is not a valid NFT.\n\tif !ok {\n\t\tpanic(\"token does not exist\")\n\t}\n\t// Throws unless `msg.sender` is the current owner, an authorized\n\t// operator, or the approved address for this NFT.\n\tif caller != token.owner \u0026\u0026 caller != token.approved {\n\t\toperator, ok := grc.operators.Get(token.owner.String())\n\t\tif !ok || caller != operator.(std.Address) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t}\n\t// Throws if `_from` is not the current owner.\n\tif from != token.owner {\n\t\tpanic(\"from is not the current owner\")\n\t}\n\t// Throws if `_to` is the zero address.\n\tif to == \"\" {\n\t\tpanic(\"to cannot be empty\")\n\t}\n\t// Good.\n\ttoken.owner = to\n}\n\nfunc (grc *token) Approve(approved std.Address, tid grc721.TokenID) {\n\tcaller := std.CallerAt(2)\n\ttoken, ok := grc.getToken(tid)\n\t// Throws if `_tokenId` is not a valid NFT.\n\tif !ok {\n\t\tpanic(\"token does not exist\")\n\t}\n\t// Throws unless `msg.sender` is the current owner,\n\t// or an authorized operator.\n\tif caller != token.owner {\n\t\toperator, ok := grc.operators.Get(token.owner.String())\n\t\tif !ok || caller != operator.(std.Address) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t}\n\t// Good.\n\ttoken.approved = approved\n}\n\n// XXX make it work for set of operators.\nfunc (grc *token) SetApprovalForAll(operator std.Address, approved bool) {\n\tcaller := std.CallerAt(2)\n\tgrc.operators.Set(caller.String(), operator)\n}\n\nfunc (grc *token) GetApproved(tid grc721.TokenID) std.Address {\n\ttoken, ok := grc.getToken(tid)\n\t// Throws if `_tokenId` is not a valid NFT.\n\tif !ok {\n\t\tpanic(\"token does not exist\")\n\t}\n\treturn token.approved\n}\n\n// XXX make it work for set of operators\nfunc (grc *token) IsApprovedForAll(owner, operator std.Address) bool {\n\toperator2, ok := grc.operators.Get(owner.String())\n\tif !ok {\n\t\treturn false\n\t}\n\treturn operator == operator2.(std.Address)\n}\n"},{"name":"z_0_filetest.gno","body":"// PKGPATH: gno.land/r/demo/nft_test\npackage nft_test\n\nimport (\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/nft\"\n)\n\nfunc main() {\n\taddr1 := testutils.TestAddress(\"addr1\")\n\t// addr2 := testutils.TestAddress(\"addr2\")\n\tgrc721 := nft.GetToken()\n\ttid := grc721.Mint(addr1, \"NFT#1\")\n\tprintln(grc721.OwnerOf(tid))\n\tprintln(addr1)\n}\n\n// Output:\n// g1v9jxgu33ta047h6lta047h6lta047h6l43dqc5\n// g1v9jxgu33ta047h6lta047h6lta047h6l43dqc5\n\n// Realm:\n// switchrealm[\"gno.land/r/demo/nft\"]\n// switchrealm[\"gno.land/r/demo/nft\"]\n// c[67c479d3d51d4056b2f4111d5352912a00be311e:11]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"std.Address\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"g1v9jxgu33ta047h6lta047h6lta047h6l43dqc5\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"std.Address\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/grc/grc721.TokenID\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"1\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"NFT#1\"\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:11\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:10\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[67c479d3d51d4056b2f4111d5352912a00be311e:10]={\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:10\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:9\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/nft.NFToken\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"564a9e78be869bd258fc3c9ad56f5a75ed68818f\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:11\"\n// }\n// }\n// }\n// c[67c479d3d51d4056b2f4111d5352912a00be311e:9]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"1\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/nft.NFToken\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"b53ffc464e1b5655d19b9d5277f3491717c24aca\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:10\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:9\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:8\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[67c479d3d51d4056b2f4111d5352912a00be311e:8]={\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:8\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:5\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"b1d928b3716b147c92730e8d234162bec2f0f2fc\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:9\"\n// }\n// }\n// }\n// u[67c479d3d51d4056b2f4111d5352912a00be311e:5]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"b229b824842ec3e7f2341e33d0fa0ca77af2f480\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:8\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:5\",\n// \"ModTime\": \"7\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:4\",\n// \"RefCount\": \"1\"\n// }\n// }\n// u[67c479d3d51d4056b2f4111d5352912a00be311e:4]={\n// \"Fields\": [\n// {},\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"1e0b9dddb406b4f50500a022266a4cb8a4ea38c6\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:5\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"05ab6746ea84b55ca133806af215d99a1c4b045e\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:6\"\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:4\",\n// \"ModTime\": \"7\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:3\",\n// \"RefCount\": \"1\"\n// }\n// }\n// switchrealm[\"gno.land/r/demo/nft\"]\n// switchrealm[\"gno.land/r/demo/nft_test\"]\n"},{"name":"z_1_filetest.gno","body":"// PKGPATH: gno.land/r/demo/nft_test\npackage nft_test\n\nimport (\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/nft\"\n)\n\nfunc main() {\n\taddr1 := testutils.TestAddress(\"addr1\")\n\taddr2 := testutils.TestAddress(\"addr2\")\n\tgrc721 := nft.GetToken()\n\ttid := grc721.Mint(addr1, \"NFT#1\")\n\tprintln(grc721.OwnerOf(tid))\n\tprintln(addr1)\n\tgrc721.TransferFrom(addr1, addr2, tid)\n}\n\n// Error:\n// unauthorized\n"},{"name":"z_2_filetest.gno","body":"// PKGPATH: gno.land/r/demo/nft_test\npackage nft_test\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/nft\"\n)\n\nfunc main() {\n\tcaller := std.CallerAt(1)\n\taddr1 := testutils.TestAddress(\"addr1\")\n\t// addr2 := testutils.TestAddress(\"addr2\")\n\tgrc721 := nft.GetToken()\n\ttid := grc721.Mint(caller, \"NFT#1\")\n\tprintln(grc721.OwnerOf(tid))\n\tprintln(addr1)\n\tgrc721.TransferFrom(caller, addr1, tid)\n}\n\n// Output:\n// g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n// g1v9jxgu33ta047h6lta047h6lta047h6l43dqc5\n"},{"name":"z_3_filetest.gno","body":"// PKGPATH: gno.land/r/demo/nft_test\npackage nft_test\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/nft\"\n)\n\nfunc main() {\n\tcaller := std.CallerAt(1)\n\taddr1 := testutils.TestAddress(\"addr1\")\n\taddr2 := testutils.TestAddress(\"addr2\")\n\tgrc721 := nft.GetToken()\n\ttid := grc721.Mint(caller, \"NFT#1\")\n\tprintln(grc721.OwnerOf(tid))\n\tprintln(addr1)\n\tgrc721.Approve(caller, tid) // approve self.\n\tgrc721.TransferFrom(caller, addr1, tid)\n\tgrc721.TransferFrom(addr1, addr2, tid)\n}\n\n// Output:\n// g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n// g1v9jxgu33ta047h6lta047h6lta047h6l43dqc5\n"},{"name":"z_4_filetest.gno","body":"// PKGPATH: gno.land/r/demo/nft_test\npackage nft_test\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/nft\"\n)\n\nfunc main() {\n\tcaller := std.CallerAt(1)\n\taddr1 := testutils.TestAddress(\"addr1\")\n\taddr2 := testutils.TestAddress(\"addr2\")\n\tgrc721 := nft.GetToken()\n\ttid := grc721.Mint(caller, \"NFT#1\")\n\tprintln(grc721.OwnerOf(tid))\n\tprintln(addr1)\n\tgrc721.Approve(caller, tid) // approve self.\n\tgrc721.TransferFrom(caller, addr1, tid)\n\tgrc721.Approve(\"\", tid) // approve addr1.\n\tgrc721.TransferFrom(addr1, addr2, tid)\n}\n\n// Error:\n// unauthorized\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":"profile","path":"gno.land/r/demo/profile","files":[{"name":"profile.gno","body":"package profile\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/mux\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar (\n\tfields = avl.NewTree()\n\trouter = mux.NewRouter()\n)\n\n// Standard fields\nconst (\n\tDisplayName = \"DisplayName\"\n\tHomepage = \"Homepage\"\n\tBio = \"Bio\"\n\tAge = \"Age\"\n\tLocation = \"Location\"\n\tAvatar = \"Avatar\"\n\tGravatarEmail = \"GravatarEmail\"\n\tAvailableForHiring = \"AvailableForHiring\"\n\tInvalidField = \"InvalidField\"\n)\n\n// Events\nconst (\n\tProfileFieldCreated = \"ProfileFieldCreated\"\n\tProfileFieldUpdated = \"ProfileFieldUpdated\"\n)\n\n// Field types used when emitting event\nconst FieldType = \"FieldType\"\n\nconst (\n\tBoolField = \"BoolField\"\n\tStringField = \"StringField\"\n\tIntField = \"IntField\"\n)\n\nfunc init() {\n\trouter.HandleFunc(\"\", homeHandler)\n\trouter.HandleFunc(\"u/{addr}\", profileHandler)\n\trouter.HandleFunc(\"f/{addr}/{field}\", fieldHandler)\n}\n\n// List of supported string fields\nvar stringFields = map[string]bool{\n\tDisplayName: true,\n\tHomepage: true,\n\tBio: true,\n\tLocation: true,\n\tAvatar: true,\n\tGravatarEmail: true,\n}\n\n// List of support int fields\nvar intFields = map[string]bool{\n\tAge: true,\n}\n\n// List of support bool fields\nvar boolFields = map[string]bool{\n\tAvailableForHiring: true,\n}\n\n// Setters\n\nfunc SetStringField(field, value string) bool {\n\taddr := std.PrevRealm().Addr()\n\tkey := addr.String() + \":\" + field\n\tupdated := fields.Set(key, value)\n\n\tevent := ProfileFieldCreated\n\tif updated {\n\t\tevent = ProfileFieldUpdated\n\t}\n\n\tstd.Emit(event, FieldType, StringField, field, value)\n\n\treturn updated\n}\n\nfunc SetIntField(field string, value int) bool {\n\taddr := std.PrevRealm().Addr()\n\tkey := addr.String() + \":\" + field\n\tupdated := fields.Set(key, value)\n\n\tevent := ProfileFieldCreated\n\tif updated {\n\t\tevent = ProfileFieldUpdated\n\t}\n\n\tstd.Emit(event, FieldType, IntField, field, string(value))\n\n\treturn updated\n}\n\nfunc SetBoolField(field string, value bool) bool {\n\taddr := std.PrevRealm().Addr()\n\tkey := addr.String() + \":\" + field\n\tupdated := fields.Set(key, value)\n\n\tevent := ProfileFieldCreated\n\tif updated {\n\t\tevent = ProfileFieldUpdated\n\t}\n\n\tstd.Emit(event, FieldType, BoolField, field, ufmt.Sprintf(\"%t\", value))\n\n\treturn updated\n}\n\n// Getters\n\nfunc GetStringField(addr std.Address, field, def string) string {\n\tkey := addr.String() + \":\" + field\n\tif value, ok := fields.Get(key); ok {\n\t\treturn value.(string)\n\t}\n\n\treturn def\n}\n\nfunc GetBoolField(addr std.Address, field string, def bool) bool {\n\tkey := addr.String() + \":\" + field\n\tif value, ok := fields.Get(key); ok {\n\t\treturn value.(bool)\n\t}\n\n\treturn def\n}\n\nfunc GetIntField(addr std.Address, field string, def int) int {\n\tkey := addr.String() + \":\" + field\n\tif value, ok := fields.Get(key); ok {\n\t\treturn value.(int)\n\t}\n\n\treturn def\n}\n"},{"name":"profile_test.gno","body":"package profile\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\n// Global addresses for test users\nvar (\n\talice = testutils.TestAddress(\"alice\")\n\tbob = testutils.TestAddress(\"bob\")\n\tcharlie = testutils.TestAddress(\"charlie\")\n\tdave = testutils.TestAddress(\"dave\")\n\teve = testutils.TestAddress(\"eve\")\n\tfrank = testutils.TestAddress(\"frank\")\n\tuser1 = testutils.TestAddress(\"user1\")\n\tuser2 = testutils.TestAddress(\"user2\")\n)\n\nfunc TestStringFields(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\n\t// Get before setting\n\tname := GetStringField(alice, DisplayName, \"anon\")\n\tuassert.Equal(t, \"anon\", name)\n\n\t// Set new key\n\tupdated := SetStringField(DisplayName, \"Alice foo\")\n\tuassert.Equal(t, updated, false)\n\tupdated = SetStringField(Homepage, \"https://example.com\")\n\tuassert.Equal(t, updated, false)\n\n\t// Update the key\n\tupdated = SetStringField(DisplayName, \"Alice foo\")\n\tuassert.Equal(t, updated, true)\n\n\t// Get after setting\n\tname = GetStringField(alice, DisplayName, \"anon\")\n\thomepage := GetStringField(alice, Homepage, \"\")\n\tbio := GetStringField(alice, Bio, \"42\")\n\n\tuassert.Equal(t, \"Alice foo\", name)\n\tuassert.Equal(t, \"https://example.com\", homepage)\n\tuassert.Equal(t, \"42\", bio)\n}\n\nfunc TestIntFields(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\n\t// Get before setting\n\tage := GetIntField(bob, Age, 25)\n\tuassert.Equal(t, 25, age)\n\n\t// Set new key\n\tupdated := SetIntField(Age, 30)\n\tuassert.Equal(t, updated, false)\n\n\t// Update the key\n\tupdated = SetIntField(Age, 30)\n\tuassert.Equal(t, updated, true)\n\n\t// Get after setting\n\tage = GetIntField(bob, Age, 25)\n\tuassert.Equal(t, 30, age)\n}\n\nfunc TestBoolFields(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(charlie))\n\n\t// Get before setting\n\thiring := GetBoolField(charlie, AvailableForHiring, false)\n\tuassert.Equal(t, false, hiring)\n\n\t// Set\n\tupdated := SetBoolField(AvailableForHiring, true)\n\tuassert.Equal(t, updated, false)\n\n\t// Update the key\n\tupdated = SetBoolField(AvailableForHiring, true)\n\tuassert.Equal(t, updated, true)\n\n\t// Get after setting\n\thiring = GetBoolField(charlie, AvailableForHiring, false)\n\tuassert.Equal(t, true, hiring)\n}\n\nfunc TestMultipleProfiles(t *testing.T) {\n\t// Set profile for user1\n\tstd.TestSetRealm(std.NewUserRealm(user1))\n\tupdated := SetStringField(DisplayName, \"User One\")\n\tuassert.Equal(t, updated, false)\n\n\t// Set profile for user2\n\tstd.TestSetRealm(std.NewUserRealm(user2))\n\tupdated = SetStringField(DisplayName, \"User Two\")\n\tuassert.Equal(t, updated, false)\n\n\t// Get profiles\n\tstd.TestSetRealm(std.NewUserRealm(user1)) // Switch back to user1\n\tname1 := GetStringField(user1, DisplayName, \"anon\")\n\tstd.TestSetRealm(std.NewUserRealm(user2)) // Switch back to user2\n\tname2 := GetStringField(user2, DisplayName, \"anon\")\n\n\tuassert.Equal(t, \"User One\", name1)\n\tuassert.Equal(t, \"User Two\", name2)\n}\n\nfunc TestArbitraryStringField(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(user1))\n\n\t// Set arbitrary string field\n\tupdated := SetStringField(\"MyEmail\", \"my@email.com\")\n\tuassert.Equal(t, updated, false)\n\n\tval := GetStringField(user1, \"MyEmail\", \"\")\n\tuassert.Equal(t, val, \"my@email.com\")\n}\n\nfunc TestArbitraryIntField(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(user1))\n\n\t// Set arbitrary int field\n\tupdated := SetIntField(\"MyIncome\", 100_000)\n\tuassert.Equal(t, updated, false)\n\n\tval := GetIntField(user1, \"MyIncome\", 0)\n\tuassert.Equal(t, val, 100_000)\n}\n\nfunc TestArbitraryBoolField(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(user1))\n\n\t// Set arbitrary int field\n\tupdated := SetBoolField(\"IsWinner\", true)\n\tuassert.Equal(t, updated, false)\n\n\tval := GetBoolField(user1, \"IsWinner\", false)\n\tuassert.Equal(t, val, true)\n}\n"},{"name":"render.gno","body":"package profile\n\nimport (\n\t\"bytes\"\n\t\"net/url\"\n\t\"std\"\n\n\t\"gno.land/p/demo/mux\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nconst (\n\tBaseURL = \"/r/demo/profile\"\n\tSetStringFieldURL = BaseURL + \"$help\u0026func=SetStringField\u0026field=%s\"\n\tSetIntFieldURL = BaseURL + \"$help\u0026func=SetIntField\u0026field=%s\"\n\tSetBoolFieldURL = BaseURL + \"$help\u0026func=SetBoolField\u0026field=%s\"\n\tViewAllFieldsURL = BaseURL + \":u/%s\"\n\tViewFieldURL = BaseURL + \":f/%s/%s\"\n)\n\nfunc homeHandler(res *mux.ResponseWriter, req *mux.Request) {\n\tvar b bytes.Buffer\n\n\tb.WriteString(\"## Setters\\n\")\n\tfor field := range stringFields {\n\t\tlink := ufmt.Sprintf(SetStringFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- [Set %s](%s)\\n\", field, link))\n\t}\n\n\tfor field := range intFields {\n\t\tlink := ufmt.Sprintf(SetIntFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- [Set %s](%s)\\n\", field, link))\n\t}\n\n\tfor field := range boolFields {\n\t\tlink := ufmt.Sprintf(SetBoolFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- [Set %s Field](%s)\\n\", field, link))\n\t}\n\n\tb.WriteString(\"\\n---\\n\\n\")\n\n\tres.Write(b.String())\n}\n\nfunc profileHandler(res *mux.ResponseWriter, req *mux.Request) {\n\tvar b bytes.Buffer\n\taddr := req.GetVar(\"addr\")\n\n\tb.WriteString(ufmt.Sprintf(\"# Profile %s\\n\", addr))\n\n\taddress := std.Address(addr)\n\n\tfor field := range stringFields {\n\t\tvalue := GetStringField(address, field, \"n/a\")\n\t\tlink := ufmt.Sprintf(SetStringFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- %s: %s [Edit](%s)\\n\", field, value, link))\n\t}\n\n\tfor field := range intFields {\n\t\tvalue := GetIntField(address, field, 0)\n\t\tlink := ufmt.Sprintf(SetIntFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- %s: %d [Edit](%s)\\n\", field, value, link))\n\t}\n\n\tfor field := range boolFields {\n\t\tvalue := GetBoolField(address, field, false)\n\t\tlink := ufmt.Sprintf(SetBoolFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- %s: %t [Edit](%s)\\n\", field, value, link))\n\t}\n\n\tres.Write(b.String())\n}\n\nfunc fieldHandler(res *mux.ResponseWriter, req *mux.Request) {\n\tvar b bytes.Buffer\n\taddr := req.GetVar(\"addr\")\n\tfield := req.GetVar(\"field\")\n\n\tb.WriteString(ufmt.Sprintf(\"# Field %s for %s\\n\", field, addr))\n\n\taddress := std.Address(addr)\n\tvalue := \"n/a\"\n\tvar editLink string\n\n\tif _, ok := stringFields[field]; ok {\n\t\tvalue = ufmt.Sprintf(\"%s\", GetStringField(address, field, \"n/a\"))\n\t\teditLink = ufmt.Sprintf(SetStringFieldURL+\"\u0026addr=%s\u0026value=%s\", field, addr, url.QueryEscape(value))\n\t} else if _, ok := intFields[field]; ok {\n\t\tvalue = ufmt.Sprintf(\"%d\", GetIntField(address, field, 0))\n\t\teditLink = ufmt.Sprintf(SetIntFieldURL+\"\u0026addr=%s\u0026value=%s\", field, addr, value)\n\t} else if _, ok := boolFields[field]; ok {\n\t\tvalue = ufmt.Sprintf(\"%t\", GetBoolField(address, field, false))\n\t\teditLink = ufmt.Sprintf(SetBoolFieldURL+\"\u0026addr=%s\u0026value=%s\", field, addr, value)\n\t}\n\n\tb.WriteString(ufmt.Sprintf(\"- %s: %s [Edit](%s)\\n\", field, value, editLink))\n\n\tres.Write(b.String())\n}\n\nfunc Render(path string) string {\n\treturn router.Render(path)\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":"releases_example","path":"gno.land/r/demo/releases_example","files":[{"name":"dummy.gno","body":"package releases_example\n\nfunc init() {\n\t// dummy example data\n\tchangelog.NewRelease(\n\t\t\"v1\",\n\t\t\"r/demo/examples_example_v1\",\n\t\t\"initial release\",\n\t)\n\tchangelog.NewRelease(\n\t\t\"v2\",\n\t\t\"r/demo/examples_example_v2\",\n\t\t\"various improvements\",\n\t)\n}\n"},{"name":"example.gno","body":"// this package demonstrates a way to manage contract releases.\npackage releases_example\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/releases\"\n)\n\nvar (\n\tchangelog = releases.NewChangelog(\"example_app\")\n\tadmin = std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\") // @administrator\n)\n\nfunc init() {\n\t// FIXME: admin = std.GetCreator()\n}\n\nfunc NewRelease(name, url, notes string) {\n\tcaller := std.OriginCaller()\n\tif caller != admin {\n\t\tpanic(\"restricted area\")\n\t}\n\tchangelog.NewRelease(name, url, notes)\n}\n\nfunc UpdateAdmin(address std.Address) {\n\tcaller := std.OriginCaller()\n\tif caller != admin {\n\t\tpanic(\"restricted area\")\n\t}\n\tadmin = address\n}\n\nfunc Render(path string) string {\n\treturn changelog.Render(path)\n}\n"},{"name":"releases0_filetest.gno","body":"package main\n\nimport (\n\t\"gno.land/p/demo/releases\"\n)\n\nfunc main() {\n\tprintln(\"-----------\")\n\tchangelog := releases.NewChangelog(\"example\")\n\tprint(changelog.Render(\"\"))\n\n\tprintln(\"-----------\")\n\tchangelog.NewRelease(\"v1\", \"r/blahblah\", \"* initial version\")\n\tprint(changelog.Render(\"\"))\n\n\tprintln(\"-----------\")\n\tchangelog.NewRelease(\"v2\", \"r/blahblah2\", \"* various improvements\\n* new shiny logo\")\n\tprint(changelog.Render(\"\"))\n\n\tprintln(\"-----------\")\n\tprint(changelog.Latest().Render())\n}\n\n// Output:\n// -----------\n// # example\n//\n// -----------\n// # example\n//\n// ## [example v1 (latest)](r/blahblah)\n//\n// * initial version\n//\n// -----------\n// # example\n//\n// ## [example v2 (latest)](r/blahblah2)\n//\n// * various improvements\n// * new shiny logo\n//\n// ## [example v1](r/blahblah)\n//\n// * initial version\n//\n// -----------\n// ## [example v2 (latest)](r/blahblah2)\n//\n// * various improvements\n// * new shiny logo\n"},{"name":"releases1_filetest.gno","body":"package main\n\nimport (\n\t\"gno.land/r/demo/releases_example\"\n)\n\nfunc main() {\n\tprintln(\"-----------\")\n\tprint(releases_example.Render(\"\"))\n\n\tprintln(\"-----------\")\n\tprint(releases_example.Render(\"v1\"))\n\n\tprintln(\"-----------\")\n\tprint(releases_example.Render(\"v42\"))\n}\n\n// Output:\n// -----------\n// # example_app\n//\n// ## [example_app v2 (latest)](r/demo/examples_example_v2)\n//\n// various improvements\n//\n// ## [example_app v1](r/demo/examples_example_v1)\n//\n// initial release\n//\n// -----------\n// ## [example_app v1](r/demo/examples_example_v1)\n//\n// initial release\n//\n// -----------\n// no such release\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":"tamagotchi","path":"gno.land/r/demo/tamagotchi","files":[{"name":"realm.gno","body":"package tamagotchi\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/tamagotchi\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar t *tamagotchi.Tamagotchi\n\nfunc init() {\n\tReset(\"gnome#0\")\n}\n\nfunc Reset(optionalName string) string {\n\tname := optionalName\n\tif name == \"\" {\n\t\theight := std.GetHeight()\n\t\tname = ufmt.Sprintf(\"gnome#%d\", height)\n\t}\n\n\tt = tamagotchi.New(name)\n\n\treturn ufmt.Sprintf(\"A new tamagotchi is born. Their name is %s %s.\", t.Name(), t.Face())\n}\n\nfunc Feed() string {\n\tt.Feed()\n\treturn t.Markdown()\n}\n\nfunc Play() string {\n\tt.Play()\n\treturn t.Markdown()\n}\n\nfunc Heal() string {\n\tt.Heal()\n\treturn t.Markdown()\n}\n\nfunc Render(path string) string {\n\ttama := t.Markdown()\n\tlinks := `Actions:\n* [Feed](/r/demo/tamagotchi$help\u0026func=Feed)\n* [Play](/r/demo/tamagotchi$help\u0026func=Play)\n* [Heal](/r/demo/tamagotchi$help\u0026func=Heal)\n* [Reset](/r/demo/tamagotchi$help\u0026func=Reset)\n`\n\n\treturn tama + \"\\n\\n\" + links\n}\n"},{"name":"z0_filetest.gno","body":"package main\n\nimport (\n\t\"gno.land/r/demo/tamagotchi\"\n)\n\nfunc main() {\n\ttamagotchi.Reset(\"tamagnotchi\")\n\tprintln(tamagotchi.Render(\"\"))\n}\n\n// Output:\n// # tamagnotchi 😃\n//\n// * age: 0\n// * hunger: 50\n// * happiness: 50\n// * health: 50\n// * sleepy: 0\n//\n// Actions:\n// * [Feed](/r/demo/tamagotchi$help\u0026func=Feed)\n// * [Play](/r/demo/tamagotchi$help\u0026func=Play)\n// * [Heal](/r/demo/tamagotchi$help\u0026func=Heal)\n// * [Reset](/r/demo/tamagotchi$help\u0026func=Reset)\n"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}} From fbc982d2688af3ad7af1f0b53ab17befe34af172 Mon Sep 17 00:00:00 2001 From: "hieu.ha" Date: Fri, 20 Dec 2024 11:27:16 +0700 Subject: [PATCH 04/14] refactor: PrevRealm => PreviousRealm --- .../creating-grc20/mytoken-1.gno | 2 +- .../creating-grc20/mytoken-2.gno | 4 +- docs/concepts/effective-gno.md | 26 ++++----- docs/how-to-guides/creating-grc20.md | 8 +-- docs/reference/stdlibs/std/chain.md | 6 +- docs/reference/stdlibs/std/testing.md | 2 +- .../p/demo/grc/grc20/examples_test.gno | 2 +- .../gno.land/p/demo/grc/grc20/tellers.gno | 8 +-- .../gno.land/p/demo/grc/grc721/basic_nft.gno | 10 ++-- .../p/demo/grc/grc721/basic_nft_test.gno | 8 +-- .../p/demo/grc/grc721/grc721_metadata.gno | 2 +- .../p/demo/grc/grc721/grc721_royalty.gno | 2 +- .../gno.land/p/demo/memeland/memeland.gno | 4 +- .../gno.land/p/demo/nestedpkg/nestedpkg.gno | 16 +++--- .../exts/authorizable/authorizable.gno | 4 +- examples/gno.land/p/demo/ownable/ownable.gno | 6 +- .../p/demo/subscription/lifetime/lifetime.gno | 2 +- .../subscription/lifetime/lifetime_test.gno | 2 +- .../demo/subscription/recurring/recurring.gno | 2 +- .../subscription/recurring/recurring_test.gno | 16 +++--- .../p/demo/tests/subtests/subtests.gno | 4 +- examples/gno.land/p/demo/tests/tests.gno | 8 +-- examples/gno.land/p/moul/debug/debug.gno | 4 +- .../gno.land/p/moul/debug/z1_filetest.gno | 4 +- .../gno.land/p/moul/debug/z2_filetest.gno | 4 +- examples/gno.land/p/n2p5/loci/loci.gno | 4 +- examples/gno.land/p/n2p5/mgroup/mgroup.gno | 2 +- examples/gno.land/r/demo/bar20/bar20.gno | 2 +- examples/gno.land/r/demo/boards/public.gno | 12 ++-- .../gno.land/r/demo/disperse/disperse.gno | 4 +- examples/gno.land/r/demo/foo20/foo20.gno | 2 +- examples/gno.land/r/demo/foo721/foo721.gno | 4 +- .../r/demo/games/dice_roller/dice_roller.gno | 4 +- .../gno.land/r/demo/games/shifumi/shifumi.gno | 4 +- .../r/demo/grc20factory/grc20factory.gno | 10 ++-- .../gno.land/r/demo/grc20reg/grc20reg.gno | 2 +- examples/gno.land/r/demo/profile/profile.gno | 6 +- .../r/demo/tests/crossrealm_b/crossrealm.gno | 2 +- .../r/demo/tests/subtests/subtests.gno | 4 +- examples/gno.land/r/demo/tests/tests.gno | 8 +-- examples/gno.land/r/demo/tests/tests_test.gno | 14 ++--- .../gno.land/r/demo/tests/z2_filetest.gno | 12 ++-- .../gno.land/r/demo/tests/z3_filetest.gno | 12 ++-- .../gno.land/r/demo/userbook/userbook.gno | 2 +- examples/gno.land/r/demo/wugnot/wugnot.gno | 4 +- examples/gno.land/r/docs/buttons/buttons.gno | 2 +- examples/gno.land/r/gnoland/blog/admin.gno | 2 +- examples/gno.land/r/gnoland/monit/monit.gno | 4 +- examples/gno.land/r/leon/config/config.gno | 2 +- examples/gno.land/r/leon/hof/hof.gno | 6 +- examples/gno.land/r/leon/home/home.gno | 4 +- .../gno.land/r/morgan/guestbook/guestbook.gno | 2 +- examples/gno.land/r/moul/home/z2_filetest.gno | 4 +- examples/gno.land/r/n2p5/home/home.gno | 2 +- examples/gno.land/r/n2p5/loci/loci.gno | 2 +- .../upgrade_c/root/root.gno | 2 +- .../integration/testdata/grc20_registry.txtar | 4 +- .../integration/testdata/grc721_emit.txtar | 4 +- .../pkg/integration/testdata/initctx.txtar | 2 +- .../pkg/integration/testdata/issue_2283.txtar | 2 +- .../testdata/issue_2283_cacheTypes.txtar | 2 +- .../pkg/integration/testdata/prevrealm.txtar | 18 +++--- gnovm/stdlibs/std/native.gno | 2 +- gnovm/stdlibs/std/native_test.go | 2 +- gnovm/tests/files/zrealm_crossrealm11.gno | 30 +++++----- gnovm/tests/files/zrealm_crossrealm13.gno | 20 +++---- gnovm/tests/files/zrealm_crossrealm13a.gno | 20 +++---- gnovm/tests/files/zrealm_tests0.gno | 4 +- gnovm/tests/stdlibs/std/std.gno | 2 +- gnovm/tests/stdlibs/std/std.go | 2 +- .../test5.gno.land/genesis_txs.jsonl | 56 +++++++++---------- 71 files changed, 236 insertions(+), 236 deletions(-) diff --git a/docs/assets/how-to-guides/creating-grc20/mytoken-1.gno b/docs/assets/how-to-guides/creating-grc20/mytoken-1.gno index bbdf84f8a9f..3f4cb2716d8 100644 --- a/docs/assets/how-to-guides/creating-grc20/mytoken-1.gno +++ b/docs/assets/how-to-guides/creating-grc20/mytoken-1.gno @@ -17,7 +17,7 @@ var ( // init is called once at time of deployment func init() { // Set deployer of Realm to admin - admin = std.PrevRealm().Addr() + admin = std.PreviousRealm().Addr() // Set token name, symbol and number of decimals banker = grc20.NewBanker("My Token", "TKN", 4) diff --git a/docs/assets/how-to-guides/creating-grc20/mytoken-2.gno b/docs/assets/how-to-guides/creating-grc20/mytoken-2.gno index 71616feba15..84d4de8e69e 100644 --- a/docs/assets/how-to-guides/creating-grc20/mytoken-2.gno +++ b/docs/assets/how-to-guides/creating-grc20/mytoken-2.gno @@ -44,13 +44,13 @@ func TransferFrom(from, to std.Address, amount uint64) { // Mint mints amount of tokens to address. Callable only by admin of token func Mint(address std.Address, amount uint64) { - assertIsAdmin(std.PrevRealm().Addr()) + assertIsAdmin(std.PreviousRealm().Addr()) checkErr(banker.Mint(address, amount)) } // Burn burns amount of tokens from address. Callable only by admin of token func Burn(address std.Address, amount uint64) { - assertIsAdmin(std.PrevRealm().Addr()) + assertIsAdmin(std.PreviousRealm().Addr()) checkErr(banker.Burn(address, amount)) } diff --git a/docs/concepts/effective-gno.md b/docs/concepts/effective-gno.md index f0e8863359e..d97a978ee40 100644 --- a/docs/concepts/effective-gno.md +++ b/docs/concepts/effective-gno.md @@ -115,7 +115,7 @@ that could lead to user frustration or the need to fork the code. import "std" func Foobar() { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() if caller != "g1xxxxx" { panic("permission denied") } @@ -399,7 +399,7 @@ certain operations. import "std" func PublicMethod(nb int) { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() privateMethod(caller, nb) } @@ -407,7 +407,7 @@ func privateMethod(caller std.Address, nb int) { /* ... */ } ``` In this example, `PublicMethod` is a public function that can be called by other -realms. It retrieves the caller's address using `std.PrevRealm().Addr()`, and +realms. It retrieves the caller's address using `std.PreviousRealm().Addr()`, and then passes it to `privateMethod`, which is a private function that performs the actual logic. This way, `privateMethod` can only be called from within the realm, and it can use the caller's address for authentication or authorization @@ -440,11 +440,11 @@ import ( var owner std.Address func init() { - owner = std.PrevRealm().Addr() + owner = std.PreviousRealm().Addr() } func ChangeOwner(newOwner std.Address) { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() if caller != owner { panic("access denied") @@ -494,7 +494,7 @@ whitelisted or not. Let's deep dive into the different access control mechanisms we can use: -One strategy is to look at the caller with `std.PrevRealm()`, which could be the +One strategy is to look at the caller with `std.PreviousRealm()`, which could be the EOA (Externally Owned Account), or the preceding realm in the call stack. Another approach is to look specifically at the EOA. For this, you should call @@ -516,7 +516,7 @@ import "std" var admin std.Address = "g1xxxxx" func AdminOnlyFunction() { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() if caller != admin { panic("permission denied") } @@ -527,7 +527,7 @@ func AdminOnlyFunction() { ``` In this example, `AdminOnlyFunction` is a function that can only be called by -the admin. It retrieves the caller's address using `std.PrevRealm().Addr()`, +the admin. It retrieves the caller's address using `std.PreviousRealm().Addr()`, this can be either another realm contract, or the calling user if there is no other intermediary realm. and then checks if the caller is the admin. If not, it panics and stops the execution. @@ -543,7 +543,7 @@ Here's an example: import "std" func TransferTokens(to std.Address, amount int64) { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() if caller != admin { panic("permission denied") } @@ -552,7 +552,7 @@ func TransferTokens(to std.Address, amount int64) { ``` In this example, `TransferTokens` is a function that can only be called by the -admin. It retrieves the caller's address using `std.PrevRealm().Addr()`, and +admin. It retrieves the caller's address using `std.PreviousRealm().Addr()`, and then checks if the caller is the admin. If not, the function panics and execution is stopped. By using these access control mechanisms, you can ensure that your contract's @@ -631,7 +631,7 @@ type MySafeStruct { } func NewSafeStruct() *MySafeStruct { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() return &MySafeStruct{ counter: 0, admin: caller, @@ -640,7 +640,7 @@ func NewSafeStruct() *MySafeStruct { func (s *MySafeStruct) Counter() int { return s.counter } func (s *MySafeStruct) Inc() { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() if caller != s.admin { panic("permission denied") } @@ -704,7 +704,7 @@ import "gno.land/p/demo/grc/grc20" var fooToken = grc20.NewBanker("Foo Token", "FOO", 4) func MyBalance() uint64 { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() return fooToken.BalanceOf(caller) } ``` diff --git a/docs/how-to-guides/creating-grc20.md b/docs/how-to-guides/creating-grc20.md index 13f22fcc6a2..b22e6eab8ad 100644 --- a/docs/how-to-guides/creating-grc20.md +++ b/docs/how-to-guides/creating-grc20.md @@ -41,7 +41,7 @@ var ( // init is called once at time of deployment func init() { // Set deployer of Realm to admin - admin = std.PrevRealm().Addr() + admin = std.PreviousRealm().Addr() // Set token name, symbol and number of decimals banker = grc20.NewBanker("My Token", "TKN", 4) @@ -142,7 +142,7 @@ caller’s allowance. ```go // Mint mints amount of tokens to address. Callable only by admin of token func Mint(address std.Address, amount uint64) { - assertIsAdmin(std.PrevRealm().Addr()) + assertIsAdmin(std.PreviousRealm().Addr()) checkErr(banker.Mint(address, amount)) } ``` @@ -153,7 +153,7 @@ increasing the total supply. ```go // Burn burns amount of tokens from address. Callable only by admin of token func Burn(address std.Address, amount uint64) { - assertIsAdmin(std.PrevRealm().Addr()) + assertIsAdmin(std.PreviousRealm().Addr()) checkErr(banker.Burn(address, amount)) } ``` @@ -162,7 +162,7 @@ decreasing the total supply. [embedmd]:# (../assets/how-to-guides/creating-grc20/mytoken-2.gno go /.*assertIsAdmin/ /^}/) ```go - assertIsAdmin(std.PrevRealm().Addr()) + assertIsAdmin(std.PreviousRealm().Addr()) checkErr(banker.Mint(address, amount)) } ``` diff --git a/docs/reference/stdlibs/std/chain.md b/docs/reference/stdlibs/std/chain.md index ef7627a0b55..d57139858d9 100644 --- a/docs/reference/stdlibs/std/chain.md +++ b/docs/reference/stdlibs/std/chain.md @@ -125,16 +125,16 @@ currentRealm := std.CurrentRealm() ``` --- -## PrevRealm +## PreviousRealm ```go -func PrevRealm() Realm +func PreviousRealm() Realm ``` Returns the previous caller [realm](realm.md) (can be code or user realm). If caller is a user realm, `pkgpath` will be empty. #### Usage ```go -prevRealm := std.PrevRealm() +prevRealm := std.PreviousRealm() ``` --- diff --git a/docs/reference/stdlibs/std/testing.md b/docs/reference/stdlibs/std/testing.md index 62683322ccb..a821edf4152 100644 --- a/docs/reference/stdlibs/std/testing.md +++ b/docs/reference/stdlibs/std/testing.md @@ -98,7 +98,7 @@ func TestSetRealm(rlm Realm) Sets the realm for the current frame. After calling `TestSetRealm()`, calling [`CurrentRealm()`](chain.md#currentrealm) in the same test function will yield the value of `rlm`, and -any `PrevRealm()` called from a function used after TestSetRealm will yield `rlm`. +any `PreviousRealm()` called from a function used after TestSetRealm will yield `rlm`. Should be used in combination with [`NewUserRealm`](#newuserrealm) & [`NewCodeRealm`](#newcoderealm). diff --git a/examples/gno.land/p/demo/grc/grc20/examples_test.gno b/examples/gno.land/p/demo/grc/grc20/examples_test.gno index 6a2bfa11d8c..726ed4dc284 100644 --- a/examples/gno.land/p/demo/grc/grc20/examples_test.gno +++ b/examples/gno.land/p/demo/grc/grc20/examples_test.gno @@ -7,7 +7,7 @@ func ExampleExposeBankForMaketxRunOrImports() {} func ExampleCustomTellerImpl() {} func ExampleAllowance() {} func ExampleRealmBanker() {} -func ExamplePrevRealmBanker() {} +func ExamplePreviousRealmBanker() {} func ExampleAccountBanker() {} func ExampleTransfer() {} func ExampleApprove() {} diff --git a/examples/gno.land/p/demo/grc/grc20/tellers.gno b/examples/gno.land/p/demo/grc/grc20/tellers.gno index ee5d2d7fcca..96db4847f7f 100644 --- a/examples/gno.land/p/demo/grc/grc20/tellers.gno +++ b/examples/gno.land/p/demo/grc/grc20/tellers.gno @@ -4,7 +4,7 @@ import ( "std" ) -// CallerTeller returns a GRC20 compatible teller that checks the PrevRealm +// CallerTeller returns a GRC20 compatible teller that checks the PreviousRealm // caller for each call. It's usually safe to expose it publicly to let users // manipulate their tokens directly, or for realms to use their allowance. func (tok *Token) CallerTeller() Teller { @@ -14,7 +14,7 @@ func (tok *Token) CallerTeller() Teller { return &fnTeller{ accountFn: func() std.Address { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() return caller }, Token: tok, @@ -44,7 +44,7 @@ func (tok *Token) RealmTeller() Teller { panic("Token cannot be nil") } - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() return &fnTeller{ accountFn: func() std.Address { @@ -61,7 +61,7 @@ func (tok *Token) RealmSubTeller(slug string) Teller { panic("Token cannot be nil") } - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() account := accountSlugAddr(caller, slug) return &fnTeller{ diff --git a/examples/gno.land/p/demo/grc/grc721/basic_nft.gno b/examples/gno.land/p/demo/grc/grc721/basic_nft.gno index 0505aaa1c26..24a7be57ea6 100644 --- a/examples/gno.land/p/demo/grc/grc721/basic_nft.gno +++ b/examples/gno.land/p/demo/grc/grc721/basic_nft.gno @@ -81,7 +81,7 @@ func (s *basicNFT) SetTokenURI(tid TokenID, tURI TokenURI) (bool, error) { if err != nil { return false, err } - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() if caller != owner { return false, ErrCallerIsNotOwner } @@ -115,7 +115,7 @@ func (s *basicNFT) Approve(to std.Address, tid TokenID) error { return ErrApprovalToCurrentOwner } - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() if caller != owner && !s.IsApprovedForAll(owner, caller) { return ErrCallerIsNotOwnerOrApproved } @@ -147,7 +147,7 @@ func (s *basicNFT) SetApprovalForAll(operator std.Address, approved bool) error return ErrInvalidAddress } - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() return s.setApprovalForAll(caller, operator, approved) } @@ -155,7 +155,7 @@ func (s *basicNFT) SetApprovalForAll(operator std.Address, approved bool) error // contract recipients are aware of the GRC721 protocol to prevent // tokens from being forever locked. func (s *basicNFT) SafeTransferFrom(from, to std.Address, tid TokenID) error { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() if !s.isApprovedOrOwner(caller, tid) { return ErrCallerIsNotOwnerOrApproved } @@ -174,7 +174,7 @@ func (s *basicNFT) SafeTransferFrom(from, to std.Address, tid TokenID) error { // Transfers `tokenId` token from `from` to `to`. func (s *basicNFT) TransferFrom(from, to std.Address, tid TokenID) error { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() if !s.isApprovedOrOwner(caller, tid) { return ErrCallerIsNotOwnerOrApproved } diff --git a/examples/gno.land/p/demo/grc/grc721/basic_nft_test.gno b/examples/gno.land/p/demo/grc/grc721/basic_nft_test.gno index 84f43f9457c..5f2a276a311 100644 --- a/examples/gno.land/p/demo/grc/grc721/basic_nft_test.gno +++ b/examples/gno.land/p/demo/grc/grc721/basic_nft_test.gno @@ -111,7 +111,7 @@ func TestSetApprovalForAll(t *testing.T) { dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) uassert.True(t, dummy != nil, "should not be nil") - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") isApprovedForAll := dummy.IsApprovedForAll(caller, addr) @@ -136,7 +136,7 @@ func TestApprove(t *testing.T) { dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) uassert.True(t, dummy != nil, "should not be nil") - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") dummy.mint(caller, TokenID("1")) @@ -156,7 +156,7 @@ func TestTransferFrom(t *testing.T) { dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) uassert.True(t, dummy != nil, "should not be nil") - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") dummy.mint(caller, TokenID("1")) @@ -185,7 +185,7 @@ func TestSafeTransferFrom(t *testing.T) { dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) uassert.True(t, dummy != nil, "should not be nil") - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") dummy.mint(caller, TokenID("1")) diff --git a/examples/gno.land/p/demo/grc/grc721/grc721_metadata.gno b/examples/gno.land/p/demo/grc/grc721/grc721_metadata.gno index 05fad41be18..5735ebeecee 100644 --- a/examples/gno.land/p/demo/grc/grc721/grc721_metadata.gno +++ b/examples/gno.land/p/demo/grc/grc721/grc721_metadata.gno @@ -34,7 +34,7 @@ func (s *metadataNFT) SetTokenMetadata(tid TokenID, metadata Metadata) error { if err != nil { return err } - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() if caller != owner { return ErrCallerIsNotOwner } diff --git a/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno b/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno index 9831c709121..4861fa93f2f 100644 --- a/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno +++ b/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno @@ -45,7 +45,7 @@ func (r *royaltyNFT) SetTokenRoyalty(tid TokenID, royaltyInfo RoyaltyInfo) error if err != nil { return err } - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() if caller != owner { return ErrCallerIsNotOwner } diff --git a/examples/gno.land/p/demo/memeland/memeland.gno b/examples/gno.land/p/demo/memeland/memeland.gno index 38f42239bec..589b37e4459 100644 --- a/examples/gno.land/p/demo/memeland/memeland.gno +++ b/examples/gno.land/p/demo/memeland/memeland.gno @@ -50,7 +50,7 @@ func (m *Memeland) PostMeme(data string, timestamp int64) string { newPost := &Post{ ID: id, Data: data, - Author: std.PrevRealm().Addr(), + Author: std.PreviousRealm().Addr(), Timestamp: time.Unix(timestamp, 0), UpvoteTracker: avl.NewTree(), } @@ -65,7 +65,7 @@ func (m *Memeland) Upvote(id string) string { panic("post with specified ID does not exist") } - caller := std.PrevRealm().Addr().String() + caller := std.PreviousRealm().Addr().String() if _, exists := post.UpvoteTracker.Get(caller); exists { panic("user has already upvoted this post") diff --git a/examples/gno.land/p/demo/nestedpkg/nestedpkg.gno b/examples/gno.land/p/demo/nestedpkg/nestedpkg.gno index 4c489f430f9..9ed8f1a155c 100644 --- a/examples/gno.land/p/demo/nestedpkg/nestedpkg.gno +++ b/examples/gno.land/p/demo/nestedpkg/nestedpkg.gno @@ -2,10 +2,10 @@ // It is useful for upgrade patterns relying on namespaces. package nestedpkg -// To test this from a realm and have std.CurrentRealm/PrevRealm work correctly, +// To test this from a realm and have std.CurrentRealm/PreviousRealm work correctly, // this file is tested from gno.land/r/demo/tests/nestedpkg_test.gno // XXX: move test to ths directory once we support testing a package and -// specifying values for both PrevRealm and CurrentRealm. +// specifying values for both PreviousRealm and CurrentRealm. import ( "std" @@ -16,7 +16,7 @@ import ( func IsCallerSubPath() bool { var ( cur = std.CurrentRealm().PkgPath() + "/" - prev = std.PrevRealm().PkgPath() + "/" + prev = std.PreviousRealm().PkgPath() + "/" ) return strings.HasPrefix(prev, cur) } @@ -25,7 +25,7 @@ func IsCallerSubPath() bool { func AssertCallerIsSubPath() { var ( cur = std.CurrentRealm().PkgPath() + "/" - prev = std.PrevRealm().PkgPath() + "/" + prev = std.PreviousRealm().PkgPath() + "/" ) if !strings.HasPrefix(prev, cur) { panic("call restricted to nested packages. current realm is " + cur + ", previous realm is " + prev) @@ -36,7 +36,7 @@ func AssertCallerIsSubPath() { func IsCallerParentPath() bool { var ( cur = std.CurrentRealm().PkgPath() + "/" - prev = std.PrevRealm().PkgPath() + "/" + prev = std.PreviousRealm().PkgPath() + "/" ) return strings.HasPrefix(cur, prev) } @@ -45,7 +45,7 @@ func IsCallerParentPath() bool { func AssertCallerIsParentPath() { var ( cur = std.CurrentRealm().PkgPath() + "/" - prev = std.PrevRealm().PkgPath() + "/" + prev = std.PreviousRealm().PkgPath() + "/" ) if !strings.HasPrefix(cur, prev) { panic("call restricted to parent packages. current realm is " + cur + ", previous realm is " + prev) @@ -56,7 +56,7 @@ func AssertCallerIsParentPath() { func IsSameNamespace() bool { var ( cur = nsFromPath(std.CurrentRealm().PkgPath()) + "/" - prev = nsFromPath(std.PrevRealm().PkgPath()) + "/" + prev = nsFromPath(std.PreviousRealm().PkgPath()) + "/" ) return cur == prev } @@ -65,7 +65,7 @@ func IsSameNamespace() bool { func AssertIsSameNamespace() { var ( cur = nsFromPath(std.CurrentRealm().PkgPath()) + "/" - prev = nsFromPath(std.PrevRealm().PkgPath()) + "/" + prev = nsFromPath(std.PreviousRealm().PkgPath()) + "/" ) if cur != prev { panic("call restricted to packages from the same namespace. current realm is " + cur + ", previous realm is " + prev) diff --git a/examples/gno.land/p/demo/ownable/exts/authorizable/authorizable.gno b/examples/gno.land/p/demo/ownable/exts/authorizable/authorizable.gno index 95bd2ac4959..f05325736da 100644 --- a/examples/gno.land/p/demo/ownable/exts/authorizable/authorizable.gno +++ b/examples/gno.land/p/demo/ownable/exts/authorizable/authorizable.gno @@ -72,7 +72,7 @@ func (a *Authorizable) DeleteFromAuthList(addr std.Address) error { } func (a Authorizable) CallerOnAuthList() error { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() if !a.authorized.Has(caller.String()) { return ErrNotInAuthList @@ -82,7 +82,7 @@ func (a Authorizable) CallerOnAuthList() error { } func (a Authorizable) AssertOnAuthList() { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() if !a.authorized.Has(caller.String()) { panic(ErrNotInAuthList) diff --git a/examples/gno.land/p/demo/ownable/ownable.gno b/examples/gno.land/p/demo/ownable/ownable.gno index f565e27c0f2..26e1c0d5cc1 100644 --- a/examples/gno.land/p/demo/ownable/ownable.gno +++ b/examples/gno.land/p/demo/ownable/ownable.gno @@ -13,7 +13,7 @@ type Ownable struct { func New() *Ownable { return &Ownable{ - owner: std.PrevRealm().Addr(), + owner: std.PreviousRealm().Addr(), } } @@ -71,12 +71,12 @@ func (o Ownable) Owner() std.Address { // CallerIsOwner checks if the caller of the function is the Realm's owner func (o Ownable) CallerIsOwner() bool { - return std.PrevRealm().Addr() == o.owner + return std.PreviousRealm().Addr() == o.owner } // AssertCallerIsOwner panics if the caller is not the owner func (o Ownable) AssertCallerIsOwner() { - if std.PrevRealm().Addr() != o.owner { + if std.PreviousRealm().Addr() != o.owner { panic(ErrUnauthorized) } } diff --git a/examples/gno.land/p/demo/subscription/lifetime/lifetime.gno b/examples/gno.land/p/demo/subscription/lifetime/lifetime.gno index f16fc3f8798..801e964b5ac 100644 --- a/examples/gno.land/p/demo/subscription/lifetime/lifetime.gno +++ b/examples/gno.land/p/demo/subscription/lifetime/lifetime.gno @@ -45,7 +45,7 @@ func (ls *LifetimeSubscription) processSubscription(receiver std.Address) error // Subscribe processes the payment for a lifetime subscription. func (ls *LifetimeSubscription) Subscribe() error { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() return ls.processSubscription(caller) } diff --git a/examples/gno.land/p/demo/subscription/lifetime/lifetime_test.gno b/examples/gno.land/p/demo/subscription/lifetime/lifetime_test.gno index 2c8ea41b566..9dcd08e604b 100644 --- a/examples/gno.land/p/demo/subscription/lifetime/lifetime_test.gno +++ b/examples/gno.land/p/demo/subscription/lifetime/lifetime_test.gno @@ -22,7 +22,7 @@ func TestLifetimeSubscription(t *testing.T) { err := ls.Subscribe() uassert.NoError(t, err, "Expected ProcessPayment to succeed") - err = ls.HasValidSubscription(std.PrevRealm().Addr()) + err = ls.HasValidSubscription(std.PreviousRealm().Addr()) uassert.NoError(t, err, "Expected Alice to have access") } diff --git a/examples/gno.land/p/demo/subscription/recurring/recurring.gno b/examples/gno.land/p/demo/subscription/recurring/recurring.gno index 4e5b7e9729c..0c8f7bec0a8 100644 --- a/examples/gno.land/p/demo/subscription/recurring/recurring.gno +++ b/examples/gno.land/p/demo/subscription/recurring/recurring.gno @@ -68,7 +68,7 @@ func (rs *RecurringSubscription) processSubscription(receiver std.Address) error // Subscribe handles the payment for the caller's subscription. func (rs *RecurringSubscription) Subscribe() error { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() return rs.processSubscription(caller) } diff --git a/examples/gno.land/p/demo/subscription/recurring/recurring_test.gno b/examples/gno.land/p/demo/subscription/recurring/recurring_test.gno index 3a24818ac49..4b71d681486 100644 --- a/examples/gno.land/p/demo/subscription/recurring/recurring_test.gno +++ b/examples/gno.land/p/demo/subscription/recurring/recurring_test.gno @@ -23,10 +23,10 @@ func TestRecurringSubscription(t *testing.T) { err := rs.Subscribe() uassert.NoError(t, err, "Expected ProcessPayment to succeed for Alice") - err = rs.HasValidSubscription(std.PrevRealm().Addr()) + err = rs.HasValidSubscription(std.PreviousRealm().Addr()) uassert.NoError(t, err, "Expected Alice to have access") - expiration, err := rs.GetExpiration(std.PrevRealm().Addr()) + expiration, err := rs.GetExpiration(std.PreviousRealm().Addr()) uassert.NoError(t, err, "Expected to get expiration for Alice") } @@ -53,13 +53,13 @@ func TestRecurringSubscriptionExpiration(t *testing.T) { err := rs.Subscribe() uassert.NoError(t, err, "Expected ProcessPayment to succeed for Alice") - err = rs.HasValidSubscription(std.PrevRealm().Addr()) + err = rs.HasValidSubscription(std.PreviousRealm().Addr()) uassert.NoError(t, err, "Expected Alice to have access") expiration := time.Now().Add(-time.Hour * 2) - rs.subs.Set(std.PrevRealm().Addr().String(), expiration) + rs.subs.Set(std.PreviousRealm().Addr().String(), expiration) - err = rs.HasValidSubscription(std.PrevRealm().Addr()) + err = rs.HasValidSubscription(std.PreviousRealm().Addr()) uassert.Error(t, err, "Expected Alice's subscription to be expired") } @@ -119,16 +119,16 @@ func TestRecurringSubscriptionWithMultiplePayments(t *testing.T) { err := rs.Subscribe() uassert.NoError(t, err, "Expected first ProcessPayment to succeed for Alice") - err = rs.HasValidSubscription(std.PrevRealm().Addr()) + err = rs.HasValidSubscription(std.PreviousRealm().Addr()) uassert.NoError(t, err, "Expected Alice to have access after first payment") expiration := time.Now().Add(-time.Hour * 2) - rs.subs.Set(std.PrevRealm().Addr().String(), expiration) + rs.subs.Set(std.PreviousRealm().Addr().String(), expiration) std.TestSetOriginSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) err = rs.Subscribe() uassert.NoError(t, err, "Expected second ProcessPayment to succeed for Alice") - err = rs.HasValidSubscription(std.PrevRealm().Addr()) + err = rs.HasValidSubscription(std.PreviousRealm().Addr()) uassert.NoError(t, err, "Expected Alice to have access after second payment") } diff --git a/examples/gno.land/p/demo/tests/subtests/subtests.gno b/examples/gno.land/p/demo/tests/subtests/subtests.gno index e8796a73081..8a7f3572cfa 100644 --- a/examples/gno.land/p/demo/tests/subtests/subtests.gno +++ b/examples/gno.land/p/demo/tests/subtests/subtests.gno @@ -8,8 +8,8 @@ func GetCurrentRealm() std.Realm { return std.CurrentRealm() } -func GetPrevRealm() std.Realm { - return std.PrevRealm() +func GetPreviousRealm() std.Realm { + return std.PreviousRealm() } func Exec(fn func()) { diff --git a/examples/gno.land/p/demo/tests/tests.gno b/examples/gno.land/p/demo/tests/tests.gno index ffad5b8c8cd..ae3468c5d48 100644 --- a/examples/gno.land/p/demo/tests/tests.gno +++ b/examples/gno.land/p/demo/tests/tests.gno @@ -47,12 +47,12 @@ func ModifyTestRealmObject2c() { SomeValue3.Field = "modified" } -func GetPrevRealm() std.Realm { - return std.PrevRealm() +func GetPreviousRealm() std.Realm { + return std.PreviousRealm() } -func GetPSubtestsPrevRealm() std.Realm { - return psubtests.GetPrevRealm() +func GetPSubtestsPreviousRealm() std.Realm { + return psubtests.GetPreviousRealm() } // Warning: unsafe pattern. diff --git a/examples/gno.land/p/moul/debug/debug.gno b/examples/gno.land/p/moul/debug/debug.gno index 9ba3dd36a98..69659ca0fd6 100644 --- a/examples/gno.land/p/moul/debug/debug.gno +++ b/examples/gno.land/p/moul/debug/debug.gno @@ -51,8 +51,8 @@ func (d Debug) Render(path string) string { } table.Append([]string{"`std.CurrentRealm().PkgPath()`", string(std.CurrentRealm().PkgPath())}) table.Append([]string{"`std.CurrentRealm().Addr()`", string(std.CurrentRealm().Addr())}) - table.Append([]string{"`std.PrevRealm().PkgPath()`", string(std.PrevRealm().PkgPath())}) - table.Append([]string{"`std.PrevRealm().Addr()`", string(std.PrevRealm().Addr())}) + table.Append([]string{"`std.PreviousRealm().PkgPath()`", string(std.PreviousRealm().PkgPath())}) + table.Append([]string{"`std.PreviousRealm().Addr()`", string(std.PreviousRealm().Addr())}) table.Append([]string{"`std.GetHeight()`", ufmt.Sprintf("%d", std.GetHeight())}) table.Append([]string{"`time.Now().Format(time.RFC3339)`", time.Now().Format(time.RFC3339)}) content += table.String() diff --git a/examples/gno.land/p/moul/debug/z1_filetest.gno b/examples/gno.land/p/moul/debug/z1_filetest.gno index 8203749d3c7..9aa6791e1c8 100644 --- a/examples/gno.land/p/moul/debug/z1_filetest.gno +++ b/examples/gno.land/p/moul/debug/z1_filetest.gno @@ -21,8 +21,8 @@ func main() { // | --- | --- | // | `std.CurrentRealm().PkgPath()` | | // | `std.CurrentRealm().Addr()` | g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm | -// | `std.PrevRealm().PkgPath()` | | -// | `std.PrevRealm().Addr()` | g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm | +// | `std.PreviousRealm().PkgPath()` | | +// | `std.PreviousRealm().Addr()` | g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm | // | `std.GetHeight()` | 123 | // | `time.Now().Format(time.RFC3339)` | 2009-02-13T23:31:30Z | // diff --git a/examples/gno.land/p/moul/debug/z2_filetest.gno b/examples/gno.land/p/moul/debug/z2_filetest.gno index 32c2fe49951..4c14fa94c5b 100644 --- a/examples/gno.land/p/moul/debug/z2_filetest.gno +++ b/examples/gno.land/p/moul/debug/z2_filetest.gno @@ -27,8 +27,8 @@ func main() { // | --- | --- | // | `std.CurrentRealm().PkgPath()` | | // | `std.CurrentRealm().Addr()` | g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm | -// | `std.PrevRealm().PkgPath()` | | -// | `std.PrevRealm().Addr()` | g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm | +// | `std.PreviousRealm().PkgPath()` | | +// | `std.PreviousRealm().Addr()` | g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm | // | `std.GetHeight()` | 123 | // | `time.Now().Format(time.RFC3339)` | 2009-02-13T23:31:30Z | // diff --git a/examples/gno.land/p/n2p5/loci/loci.gno b/examples/gno.land/p/n2p5/loci/loci.gno index 7bd5c29c3af..7fdd0de5c3d 100644 --- a/examples/gno.land/p/n2p5/loci/loci.gno +++ b/examples/gno.land/p/n2p5/loci/loci.gno @@ -25,10 +25,10 @@ func New() *LociStore { } } -// Set stores a byte slice in the AVL tree using the `std.PrevRealm().Addr()` +// Set stores a byte slice in the AVL tree using the `std.PreviousRealm().Addr()` // string as the key. func (s *LociStore) Set(value []byte) { - key := string(std.PrevRealm().Addr()) + key := string(std.PreviousRealm().Addr()) s.internal.Set(key, value) } diff --git a/examples/gno.land/p/n2p5/mgroup/mgroup.gno b/examples/gno.land/p/n2p5/mgroup/mgroup.gno index 566d625a003..727c1ca1e0b 100644 --- a/examples/gno.land/p/n2p5/mgroup/mgroup.gno +++ b/examples/gno.land/p/n2p5/mgroup/mgroup.gno @@ -74,7 +74,7 @@ func (g *ManagedGroup) RemoveBackupOwner(addr std.Address) error { // If the caller is not a backup owner, an error is returned. // The caller is automatically added as a member of the group. func (g *ManagedGroup) ClaimOwnership() error { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() // already owner, skip if caller == g.Owner() { return nil diff --git a/examples/gno.land/r/demo/bar20/bar20.gno b/examples/gno.land/r/demo/bar20/bar20.gno index 25636fcda78..979ae7d30de 100644 --- a/examples/gno.land/r/demo/bar20/bar20.gno +++ b/examples/gno.land/r/demo/bar20/bar20.gno @@ -23,7 +23,7 @@ func init() { } func Faucet() string { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() if err := adm.Mint(caller, 1_000_000); err != nil { return "error: " + err.Error() } diff --git a/examples/gno.land/r/demo/boards/public.gno b/examples/gno.land/r/demo/boards/public.gno index 152a7d21c70..0d0c40b9a1c 100644 --- a/examples/gno.land/r/demo/boards/public.gno +++ b/examples/gno.land/r/demo/boards/public.gno @@ -17,7 +17,7 @@ func GetBoardIDFromName(name string) (BoardID, bool) { } func CreateBoard(name string) BoardID { - if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { + if !(std.IsOriginCall() || std.PreviousRealm().IsUser()) { panic("invalid non-user call") } bid := incGetBoardID() @@ -43,7 +43,7 @@ func checkAnonFee() bool { } func CreateThread(bid BoardID, title string, body string) PostID { - if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { + if !(std.IsOriginCall() || std.PreviousRealm().IsUser()) { panic("invalid non-user call") } caller := std.OriginCaller() @@ -61,7 +61,7 @@ func CreateThread(bid BoardID, title string, body string) PostID { } func CreateReply(bid BoardID, threadid, postid PostID, body string) PostID { - if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { + if !(std.IsOriginCall() || std.PreviousRealm().IsUser()) { panic("invalid non-user call") } caller := std.OriginCaller() @@ -91,7 +91,7 @@ func CreateReply(bid BoardID, threadid, postid PostID, body string) PostID { // If dstBoard is private, does not ping back. // If board specified by bid is private, panics. func CreateRepost(bid BoardID, postid PostID, title string, body string, dstBoardID BoardID) PostID { - if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { + if !(std.IsOriginCall() || std.PreviousRealm().IsUser()) { panic("invalid non-user call") } caller := std.OriginCaller() @@ -121,7 +121,7 @@ func CreateRepost(bid BoardID, postid PostID, title string, body string, dstBoar } func DeletePost(bid BoardID, threadid, postid PostID, reason string) { - if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { + if !(std.IsOriginCall() || std.PreviousRealm().IsUser()) { panic("invalid non-user call") } caller := std.OriginCaller() @@ -153,7 +153,7 @@ func DeletePost(bid BoardID, threadid, postid PostID, reason string) { } func EditPost(bid BoardID, threadid, postid PostID, title, body string) { - if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { + if !(std.IsOriginCall() || std.PreviousRealm().IsUser()) { panic("invalid non-user call") } caller := std.OriginCaller() diff --git a/examples/gno.land/r/demo/disperse/disperse.gno b/examples/gno.land/r/demo/disperse/disperse.gno index acfb280f148..3be069f7f4b 100644 --- a/examples/gno.land/r/demo/disperse/disperse.gno +++ b/examples/gno.land/r/demo/disperse/disperse.gno @@ -14,7 +14,7 @@ var realmAddr = std.CurrentRealm().Addr() // if there are any to return func DisperseUgnot(addresses []std.Address, coins std.Coins) { coinSent := std.OriginSend() - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() banker := std.GetBanker(std.BankerTypeOriginSend) if len(addresses) != len(coins) { @@ -50,7 +50,7 @@ func DisperseUgnot(addresses []std.Address, coins std.Coins) { // Note that it is necessary to approve the realm to spend the tokens before calling this function // see the corresponding filetests for examples func DisperseGRC20(addresses []std.Address, amounts []uint64, symbols []string) { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() if (len(addresses) != len(amounts)) || (len(amounts) != len(symbols)) { panic(ErrArgLenAndSentLenMismatch) diff --git a/examples/gno.land/r/demo/foo20/foo20.gno b/examples/gno.land/r/demo/foo20/foo20.gno index 5c7d7f12b99..75158cb56f9 100644 --- a/examples/gno.land/r/demo/foo20/foo20.gno +++ b/examples/gno.land/r/demo/foo20/foo20.gno @@ -60,7 +60,7 @@ func TransferFrom(from, to pusers.AddressOrName, amount uint64) { // Faucet is distributing foo20 tokens without restriction (unsafe). // For a real token faucet, you should take care of setting limits are asking payment. func Faucet() { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() amount := uint64(1_000 * 10_000) // 1k checkErr(privateLedger.Mint(caller, amount)) } diff --git a/examples/gno.land/r/demo/foo721/foo721.gno b/examples/gno.land/r/demo/foo721/foo721.gno index f7364d4185f..00af042a465 100644 --- a/examples/gno.land/r/demo/foo721/foo721.gno +++ b/examples/gno.land/r/demo/foo721/foo721.gno @@ -87,7 +87,7 @@ func TransferFrom(from, to pusers.AddressOrName, tid grc721.TokenID) { // Admin func Mint(to pusers.AddressOrName, tid grc721.TokenID) { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() assertIsAdmin(caller) err := foo.Mint(users.Resolve(to), tid) if err != nil { @@ -96,7 +96,7 @@ func Mint(to pusers.AddressOrName, tid grc721.TokenID) { } func Burn(tid grc721.TokenID) { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() assertIsAdmin(caller) err := foo.Burn(tid) if err != nil { diff --git a/examples/gno.land/r/demo/games/dice_roller/dice_roller.gno b/examples/gno.land/r/demo/games/dice_roller/dice_roller.gno index 4dbbd6c7682..9bc7c31f375 100644 --- a/examples/gno.land/r/demo/games/dice_roller/dice_roller.gno +++ b/examples/gno.land/r/demo/games/dice_roller/dice_roller.gno @@ -62,7 +62,7 @@ func NewGame(addr std.Address) int { } games.Set(gameId.Next().String(), &game{ - player1: std.PrevRealm().Addr(), + player1: std.PreviousRealm().Addr(), player2: addr, }) @@ -79,7 +79,7 @@ func Play(idx int) int { roll := rollDice() // Random the player's dice roll // Play the game and update the player's roll - if err := g.play(std.PrevRealm().Addr(), roll); err != nil { + if err := g.play(std.PreviousRealm().Addr(), roll); err != nil { panic(err) } diff --git a/examples/gno.land/r/demo/games/shifumi/shifumi.gno b/examples/gno.land/r/demo/games/shifumi/shifumi.gno index 3de09196da1..7881fdb9765 100644 --- a/examples/gno.land/r/demo/games/shifumi/shifumi.gno +++ b/examples/gno.land/r/demo/games/shifumi/shifumi.gno @@ -63,7 +63,7 @@ func (g *game) winner() int { // NewGame creates a new game where player1 is the caller and player2 the argument. // A new game index is returned. func NewGame(player std.Address) int { - games.Set(id.Next().String(), &game{player1: std.PrevRealm().Addr(), player2: player}) + games.Set(id.Next().String(), &game{player1: std.PreviousRealm().Addr(), player2: player}) return int(id) } @@ -74,7 +74,7 @@ func Play(idx, move int) { if !ok { panic("game not found") } - if err := v.(*game).play(std.PrevRealm().Addr(), move); err != nil { + if err := v.(*game).play(std.PreviousRealm().Addr(), move); err != nil { panic(err) } } diff --git a/examples/gno.land/r/demo/grc20factory/grc20factory.gno b/examples/gno.land/r/demo/grc20factory/grc20factory.gno index 58874409d7f..caff31cd3b8 100644 --- a/examples/gno.land/r/demo/grc20factory/grc20factory.gno +++ b/examples/gno.land/r/demo/grc20factory/grc20factory.gno @@ -21,7 +21,7 @@ type instance struct { } func New(name, symbol string, decimals uint, initialMint, faucet uint64) { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() NewWithAdmin(name, symbol, decimals, initialMint, faucet, caller) } @@ -77,21 +77,21 @@ func Allowance(symbol string, owner, spender std.Address) uint64 { func Transfer(symbol string, to std.Address, amount uint64) { inst := mustGetInstance(symbol) - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() teller := inst.ledger.ImpersonateTeller(caller) checkErr(teller.Transfer(to, amount)) } func Approve(symbol string, spender std.Address, amount uint64) { inst := mustGetInstance(symbol) - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() teller := inst.ledger.ImpersonateTeller(caller) checkErr(teller.Approve(spender, amount)) } func TransferFrom(symbol string, from, to std.Address, amount uint64) { inst := mustGetInstance(symbol) - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() teller := inst.ledger.ImpersonateTeller(caller) checkErr(teller.TransferFrom(from, to, amount)) } @@ -104,7 +104,7 @@ func Faucet(symbol string) { } // FIXME: add limits? // FIXME: add payment in gnot? - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() checkErr(inst.ledger.Mint(caller, inst.faucet)) } diff --git a/examples/gno.land/r/demo/grc20reg/grc20reg.gno b/examples/gno.land/r/demo/grc20reg/grc20reg.gno index ff46ec94860..ba59019985a 100644 --- a/examples/gno.land/r/demo/grc20reg/grc20reg.gno +++ b/examples/gno.land/r/demo/grc20reg/grc20reg.gno @@ -12,7 +12,7 @@ import ( var registry = avl.NewTree() // rlmPath[.slug] -> TokenGetter (slug is optional) func Register(tokenGetter grc20.TokenGetter, slug string) { - rlmPath := std.PrevRealm().PkgPath() + rlmPath := std.PreviousRealm().PkgPath() key := fqname.Construct(rlmPath, slug) registry.Set(key, tokenGetter) std.Emit( diff --git a/examples/gno.land/r/demo/profile/profile.gno b/examples/gno.land/r/demo/profile/profile.gno index 1318e19eaf3..f5c8aa363ed 100644 --- a/examples/gno.land/r/demo/profile/profile.gno +++ b/examples/gno.land/r/demo/profile/profile.gno @@ -70,7 +70,7 @@ var boolFields = map[string]bool{ // Setters func SetStringField(field, value string) bool { - addr := std.PrevRealm().Addr() + addr := std.PreviousRealm().Addr() key := addr.String() + ":" + field updated := fields.Set(key, value) @@ -85,7 +85,7 @@ func SetStringField(field, value string) bool { } func SetIntField(field string, value int) bool { - addr := std.PrevRealm().Addr() + addr := std.PreviousRealm().Addr() key := addr.String() + ":" + field updated := fields.Set(key, value) @@ -100,7 +100,7 @@ func SetIntField(field string, value int) bool { } func SetBoolField(field string, value bool) bool { - addr := std.PrevRealm().Addr() + addr := std.PreviousRealm().Addr() key := addr.String() + ":" + field updated := fields.Set(key, value) diff --git a/examples/gno.land/r/demo/tests/crossrealm_b/crossrealm.gno b/examples/gno.land/r/demo/tests/crossrealm_b/crossrealm.gno index d412b6ee6b1..058c81ed034 100644 --- a/examples/gno.land/r/demo/tests/crossrealm_b/crossrealm.gno +++ b/examples/gno.land/r/demo/tests/crossrealm_b/crossrealm.gno @@ -15,7 +15,7 @@ func (f *fooer) SetS(newVal string) { } func (f *fooer) Foo() { - println("hello " + f.s + " cur=" + std.CurrentRealm().PkgPath() + " prev=" + std.PrevRealm().PkgPath()) + println("hello " + f.s + " cur=" + std.CurrentRealm().PkgPath() + " prev=" + std.PreviousRealm().PkgPath()) } var ( diff --git a/examples/gno.land/r/demo/tests/subtests/subtests.gno b/examples/gno.land/r/demo/tests/subtests/subtests.gno index 6bf43cba5eb..81d133dff38 100644 --- a/examples/gno.land/r/demo/tests/subtests/subtests.gno +++ b/examples/gno.land/r/demo/tests/subtests/subtests.gno @@ -8,8 +8,8 @@ func GetCurrentRealm() std.Realm { return std.CurrentRealm() } -func GetPrevRealm() std.Realm { - return std.PrevRealm() +func GetPreviousRealm() std.Realm { + return std.PreviousRealm() } func Exec(fn func()) { diff --git a/examples/gno.land/r/demo/tests/tests.gno b/examples/gno.land/r/demo/tests/tests.gno index 79d2a5477fe..465cc1aeeb3 100644 --- a/examples/gno.land/r/demo/tests/tests.gno +++ b/examples/gno.land/r/demo/tests/tests.gno @@ -91,12 +91,12 @@ func PrintTestNodes() { println(gTestNode2.Child.Name) } -func GetPrevRealm() std.Realm { - return std.PrevRealm() +func GetPreviousRealm() std.Realm { + return std.PreviousRealm() } -func GetRSubtestsPrevRealm() std.Realm { - return rsubtests.GetPrevRealm() +func GetRSubtestsPreviousRealm() std.Realm { + return rsubtests.GetPreviousRealm() } func Exec(fn func()) { diff --git a/examples/gno.land/r/demo/tests/tests_test.gno b/examples/gno.land/r/demo/tests/tests_test.gno index ccbc6b91265..476212f2387 100644 --- a/examples/gno.land/r/demo/tests/tests_test.gno +++ b/examples/gno.land/r/demo/tests/tests_test.gno @@ -42,17 +42,17 @@ func TestAssertOriginCall(t *testing.T) { CallSubtestsAssertOriginCall() } -func TestPrevRealm(t *testing.T) { +func TestPreviousRealm(t *testing.T) { var ( user1Addr = std.DerivePkgAddr("user1.gno") rTestsAddr = std.DerivePkgAddr("gno.land/r/demo/tests") ) - // When a single realm in the frames, PrevRealm returns the user - if addr := GetPrevRealm().Addr(); addr != user1Addr { - t.Errorf("want GetPrevRealm().Addr==%s, got %s", user1Addr, addr) + // When a single realm in the frames, PreviousRealm returns the user + if addr := GetPreviousRealm().Addr(); addr != user1Addr { + t.Errorf("want GetPreviousRealm().Addr==%s, got %s", user1Addr, addr) } - // When 2 or more realms in the frames, PrevRealm returns the second to last - if addr := GetRSubtestsPrevRealm().Addr(); addr != rTestsAddr { - t.Errorf("want GetRSubtestsPrevRealm().Addr==%s, got %s", rTestsAddr, addr) + // When 2 or more realms in the frames, PreviousRealm returns the second to last + if addr := GetRSubtestsPreviousRealm().Addr(); addr != rTestsAddr { + t.Errorf("want GetRSubtestsPreviousRealm().Addr==%s, got %s", rTestsAddr, addr) } } diff --git a/examples/gno.land/r/demo/tests/z2_filetest.gno b/examples/gno.land/r/demo/tests/z2_filetest.gno index bd83026e568..b40a9dbfe90 100644 --- a/examples/gno.land/r/demo/tests/z2_filetest.gno +++ b/examples/gno.land/r/demo/tests/z2_filetest.gno @@ -7,18 +7,18 @@ import ( "gno.land/r/demo/tests" ) -// When a single realm in the frames, PrevRealm returns the user -// When 2 or more realms in the frames, PrevRealm returns the second to last +// When a single realm in the frames, PreviousRealm returns the user +// When 2 or more realms in the frames, PreviousRealm returns the second to last func main() { var ( eoa = testutils.TestAddress("someone") rTestsAddr = std.DerivePkgAddr("gno.land/r/demo/tests") ) std.TestSetOriginCaller(eoa) - println("tests.GetPrevRealm().Addr(): ", tests.GetPrevRealm().Addr()) - println("tests.GetRSubtestsPrevRealm().Addr(): ", tests.GetRSubtestsPrevRealm().Addr()) + println("tests.GetPreviousRealm().Addr(): ", tests.GetPreviousRealm().Addr()) + println("tests.GetRSubtestsPreviousRealm().Addr(): ", tests.GetRSubtestsPreviousRealm().Addr()) } // Output: -// tests.GetPrevRealm().Addr(): g1wdhk6et0dej47h6lta047h6lta047h6lrnerlk -// tests.GetRSubtestsPrevRealm().Addr(): g1gz4ycmx0s6ln2wdrsh4e00l9fsel2wskqa3snq +// tests.GetPreviousRealm().Addr(): g1wdhk6et0dej47h6lta047h6lta047h6lrnerlk +// tests.GetRSubtestsPreviousRealm().Addr(): g1gz4ycmx0s6ln2wdrsh4e00l9fsel2wskqa3snq diff --git a/examples/gno.land/r/demo/tests/z3_filetest.gno b/examples/gno.land/r/demo/tests/z3_filetest.gno index a7c3a2a0000..2f6256b15bf 100644 --- a/examples/gno.land/r/demo/tests/z3_filetest.gno +++ b/examples/gno.land/r/demo/tests/z3_filetest.gno @@ -14,15 +14,15 @@ func main() { rTestsAddr = std.DerivePkgAddr("gno.land/r/demo/tests") ) std.TestSetOriginCaller(eoa) - // Contrarily to z2_filetest.gno we EXPECT GetPrevRealms != eoa (#1704) - if addr := tests.GetPrevRealm().Addr(); addr != eoa { - println("want tests.GetPrevRealm().Addr ==", eoa, "got", addr) + // Contrarily to z2_filetest.gno we EXPECT GetPreviousRealms != eoa (#1704) + if addr := tests.GetPreviousRealm().Addr(); addr != eoa { + println("want tests.GetPreviousRealm().Addr ==", eoa, "got", addr) } // When 2 or more realms in the frames, it is also different - if addr := tests.GetRSubtestsPrevRealm().Addr(); addr != rTestsAddr { - println("want GetRSubtestsPrevRealm().Addr ==", rTestsAddr, "got", addr) + if addr := tests.GetRSubtestsPreviousRealm().Addr(); addr != rTestsAddr { + println("want GetRSubtestsPreviousRealm().Addr ==", rTestsAddr, "got", addr) } } // Output: -// want tests.GetPrevRealm().Addr == g1wdhk6et0dej47h6lta047h6lta047h6lrnerlk got g1xufrdvnfk6zc9r0nqa23ld3tt2r5gkyvw76q63 +// want tests.GetPreviousRealm().Addr == g1wdhk6et0dej47h6lta047h6lta047h6lrnerlk got g1xufrdvnfk6zc9r0nqa23ld3tt2r5gkyvw76q63 diff --git a/examples/gno.land/r/demo/userbook/userbook.gno b/examples/gno.land/r/demo/userbook/userbook.gno index 03027f064b0..c5f7ef5ca8c 100644 --- a/examples/gno.land/r/demo/userbook/userbook.gno +++ b/examples/gno.land/r/demo/userbook/userbook.gno @@ -30,7 +30,7 @@ func init() { func SignUp() string { // Get transaction caller - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() // Check if the user is already signed up if _, exists := tracker.Get(caller.String()); exists { diff --git a/examples/gno.land/r/demo/wugnot/wugnot.gno b/examples/gno.land/r/demo/wugnot/wugnot.gno index 91617dfff27..4e95f20dd58 100644 --- a/examples/gno.land/r/demo/wugnot/wugnot.gno +++ b/examples/gno.land/r/demo/wugnot/wugnot.gno @@ -24,7 +24,7 @@ func init() { } func Deposit() { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() sent := std.OriginSend() amount := sent.AmountOf("ugnot") @@ -36,7 +36,7 @@ func Deposit() { func Withdraw(amount uint64) { require(amount >= wugnotMinDeposit, ufmt.Sprintf("Deposit below minimum: %d/%d wugnot.", amount, wugnotMinDeposit)) - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() pkgaddr := std.CurrentRealm().Addr() callerBal := Token.BalanceOf(caller) require(amount <= callerBal, ufmt.Sprintf("Insufficient balance: %d available, %d needed.", callerBal, amount)) diff --git a/examples/gno.land/r/docs/buttons/buttons.gno b/examples/gno.land/r/docs/buttons/buttons.gno index cb050b1bc38..6d3d4416a7f 100644 --- a/examples/gno.land/r/docs/buttons/buttons.gno +++ b/examples/gno.land/r/docs/buttons/buttons.gno @@ -14,7 +14,7 @@ var ( func UpdateMOTD(newmotd string) { motd = newmotd - lastCaller = std.PrevRealm().Addr() + lastCaller = std.PreviousRealm().Addr() } func Render(path string) string { diff --git a/examples/gno.land/r/gnoland/blog/admin.gno b/examples/gno.land/r/gnoland/blog/admin.gno index 046e957345f..7c04d030c76 100644 --- a/examples/gno.land/r/gnoland/blog/admin.gno +++ b/examples/gno.land/r/gnoland/blog/admin.gno @@ -43,7 +43,7 @@ func AdminRemoveModerator(addr std.Address) { func NewPostExecutor(slug, title, body, publicationDate, authors, tags string) dao.Executor { callback := func() error { - addPost(std.PrevRealm().Addr(), slug, title, body, publicationDate, authors, tags) + addPost(std.PreviousRealm().Addr(), slug, title, body, publicationDate, authors, tags) return nil } diff --git a/examples/gno.land/r/gnoland/monit/monit.gno b/examples/gno.land/r/gnoland/monit/monit.gno index be94fbdd2bb..92f8973e190 100644 --- a/examples/gno.land/r/gnoland/monit/monit.gno +++ b/examples/gno.land/r/gnoland/monit/monit.gno @@ -29,7 +29,7 @@ var ( func Incr() int { counter++ lastUpdate = time.Now() - lastCaller = std.PrevRealm().Addr() + lastCaller = std.PreviousRealm().Addr() wd.Alive() return counter } @@ -40,7 +40,7 @@ func Reset() { Ownable.AssertCallerIsOwner() counter = 0 - lastCaller = std.PrevRealm().Addr() + lastCaller = std.PreviousRealm().Addr() lastUpdate = time.Now() wd = watchdog.Watchdog{Duration: 5 * time.Minute} } diff --git a/examples/gno.land/r/leon/config/config.gno b/examples/gno.land/r/leon/config/config.gno index bc800ec8263..acacc6c9b0e 100644 --- a/examples/gno.land/r/leon/config/config.gno +++ b/examples/gno.land/r/leon/config/config.gno @@ -52,7 +52,7 @@ func SetBackup(a std.Address) error { } func checkAuthorized() error { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() isAuthorized := caller == main || caller == backup if !isAuthorized { diff --git a/examples/gno.land/r/leon/hof/hof.gno b/examples/gno.land/r/leon/hof/hof.gno index 147a0dd1a95..9b995aed160 100644 --- a/examples/gno.land/r/leon/hof/hof.gno +++ b/examples/gno.land/r/leon/hof/hof.gno @@ -54,7 +54,7 @@ func Register() { return } - submission := std.PrevRealm() + submission := std.PreviousRealm() pkgpath := submission.PkgPath() // Must be called from code @@ -89,7 +89,7 @@ func Upvote(pkgpath string) { } item := rawItem.(*Item) - caller := std.PrevRealm().Addr().String() + caller := std.PreviousRealm().Addr().String() if item.upvote.Has(caller) { panic(ErrDoubleUpvote.Error()) @@ -105,7 +105,7 @@ func Downvote(pkgpath string) { } item := rawItem.(*Item) - caller := std.PrevRealm().Addr().String() + caller := std.PreviousRealm().Addr().String() if item.downvote.Has(caller) { panic(ErrDoubleDownvote.Error()) diff --git a/examples/gno.land/r/leon/home/home.gno b/examples/gno.land/r/leon/home/home.gno index cf33260cc6b..64181aa9de2 100644 --- a/examples/gno.land/r/leon/home/home.gno +++ b/examples/gno.land/r/leon/home/home.gno @@ -39,7 +39,7 @@ TODO import r/gh } func UpdatePFP(url, caption string) { - if !isAuthorized(std.PrevRealm().Addr()) { + if !isAuthorized(std.PreviousRealm().Addr()) { panic(config.ErrUnauthorized) } @@ -48,7 +48,7 @@ func UpdatePFP(url, caption string) { } func UpdateAboutMe(col1, col2 string) { - if !isAuthorized(std.PrevRealm().Addr()) { + if !isAuthorized(std.PreviousRealm().Addr()) { panic(config.ErrUnauthorized) } diff --git a/examples/gno.land/r/morgan/guestbook/guestbook.gno b/examples/gno.land/r/morgan/guestbook/guestbook.gno index be9e9db6133..1aab054ea6f 100644 --- a/examples/gno.land/r/morgan/guestbook/guestbook.gno +++ b/examples/gno.land/r/morgan/guestbook/guestbook.gno @@ -43,7 +43,7 @@ const ( // Sign signs the guestbook, with the specified message. func Sign(message string) { - prev := std.PrevRealm() + prev := std.PreviousRealm() switch { case !prev.IsUser(): panic(errNotAUser) diff --git a/examples/gno.land/r/moul/home/z2_filetest.gno b/examples/gno.land/r/moul/home/z2_filetest.gno index 6055583c79d..23ee8ff882e 100644 --- a/examples/gno.land/r/moul/home/z2_filetest.gno +++ b/examples/gno.land/r/moul/home/z2_filetest.gno @@ -63,8 +63,8 @@ func main() { // | --- | --- | // | `std.CurrentRealm().PkgPath()` | gno.land/r/moul/home | // | `std.CurrentRealm().Addr()` | g1h8h57ntxadcze3f703skymfzdwa6t3ugf0nq3z | -// | `std.PrevRealm().PkgPath()` | | -// | `std.PrevRealm().Addr()` | g1manfred47kzduec920z88wfr64ylksmdcedlf5 | +// | `std.PreviousRealm().PkgPath()` | | +// | `std.PreviousRealm().Addr()` | g1manfred47kzduec920z88wfr64ylksmdcedlf5 | // | `std.GetHeight()` | 123 | // | `time.Now().Format(time.RFC3339)` | 2009-02-13T23:31:30Z | // diff --git a/examples/gno.land/r/n2p5/home/home.gno b/examples/gno.land/r/n2p5/home/home.gno index 69b82e86d68..cb025ccdd6b 100644 --- a/examples/gno.land/r/n2p5/home/home.gno +++ b/examples/gno.land/r/n2p5/home/home.gno @@ -52,7 +52,7 @@ func Render(path string) string { // assertAdmin panics if the caller is not an admin as defined in the config realm. func assertAdmin() { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() if !config.IsAdmin(caller) { panic("forbidden: must be admin") } diff --git a/examples/gno.land/r/n2p5/loci/loci.gno b/examples/gno.land/r/n2p5/loci/loci.gno index 36f282e729f..f1eb731398b 100644 --- a/examples/gno.land/r/n2p5/loci/loci.gno +++ b/examples/gno.land/r/n2p5/loci/loci.gno @@ -23,7 +23,7 @@ func Set(value string) { panic(err) } store.Set(b) - std.Emit("SetValue", "ForAddr", string(std.PrevRealm().Addr())) + std.Emit("SetValue", "ForAddr", string(std.PreviousRealm().Addr())) } // Get retrieves the value stored at the provided address and diff --git a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_c/root/root.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_c/root/root.gno index 0a610b0b196..d71f5ec3db5 100644 --- a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_c/root/root.gno +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_c/root/root.gno @@ -23,7 +23,7 @@ func SetCurrentImpl(pkgpath string) { } func assertIsCurrentImpl() { - if std.PrevRealm().PkgPath() != currentImpl { + if std.PreviousRealm().PkgPath() != currentImpl { panic("unauthorized") } } diff --git a/gno.land/pkg/integration/testdata/grc20_registry.txtar b/gno.land/pkg/integration/testdata/grc20_registry.txtar index df11e92f8db..edf8a6b90a3 100644 --- a/gno.land/pkg/integration/testdata/grc20_registry.txtar +++ b/gno.land/pkg/integration/testdata/grc20_registry.txtar @@ -58,6 +58,6 @@ package foo20 import "std" func Transfer(to std.Address, amount uint64) string { - println("transfer from=" + std.PrevRealm().Addr().String() + " to=" + to.String() + " some-amount") - return std.PrevRealm().Addr().String() + println("transfer from=" + std.PreviousRealm().Addr().String() + " to=" + to.String() + " some-amount") + return std.PreviousRealm().Addr().String() } diff --git a/gno.land/pkg/integration/testdata/grc721_emit.txtar b/gno.land/pkg/integration/testdata/grc721_emit.txtar index 6b4770e37c6..4829c6ac5d1 100644 --- a/gno.land/pkg/integration/testdata/grc721_emit.txtar +++ b/gno.land/pkg/integration/testdata/grc721_emit.txtar @@ -69,7 +69,7 @@ func TransferFrom(from, to pusers.AddressOrName, tid grc721.TokenID) { // Admin func Mint(to pusers.AddressOrName, tid grc721.TokenID) { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() assertIsAdmin(caller) err := foo.Mint(users.Resolve(to), tid) if err != nil { @@ -78,7 +78,7 @@ func Mint(to pusers.AddressOrName, tid grc721.TokenID) { } func Burn(tid grc721.TokenID) { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Addr() assertIsAdmin(caller) err := foo.Burn(tid) if err != nil { diff --git a/gno.land/pkg/integration/testdata/initctx.txtar b/gno.land/pkg/integration/testdata/initctx.txtar index 0dcbaf6d5b8..cd825aac1f3 100644 --- a/gno.land/pkg/integration/testdata/initctx.txtar +++ b/gno.land/pkg/integration/testdata/initctx.txtar @@ -19,7 +19,7 @@ var prev = std.Address("prev") func init() { orig = std.OriginCaller() - prev = std.PrevRealm().Addr() + prev = std.PreviousRealm().Addr() } func Render(addr string) string { diff --git a/gno.land/pkg/integration/testdata/issue_2283.txtar b/gno.land/pkg/integration/testdata/issue_2283.txtar index 92061d13b33..e579a864c40 100644 --- a/gno.land/pkg/integration/testdata/issue_2283.txtar +++ b/gno.land/pkg/integration/testdata/issue_2283.txtar @@ -70,7 +70,7 @@ import ( }, { "Name": "public.gno", - "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/teritori/flags_index\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Only registered user can create a new feed\n// For the flexibility when testing, allow all user to create feed\nfunc CreateFeed(name string) FeedID {\n\tpkgpath := std.CurrentRealmPath()\n\n\tfid := incGetFeedID()\n\tcaller := std.PrevRealm().Addr()\n\turl := strings.Replace(pkgpath, \"gno.land\", \"\", -1) + \":\" + name\n\tfeed := newFeed(fid, url, name, caller)\n\tfidkey := feedIDKey(fid)\n\tgFeeds.Set(fidkey, feed)\n\tgFeedsByName.Set(name, feed)\n\treturn feed.id\n}\n\n// Anyone can create a post in a existing feed, allow un-registered users also\nfunc CreatePost(fid FeedID, parentID PostID, catetory uint64, metadata string) PostID {\n\tcaller := std.PrevRealm().Addr()\n\n\tfeed := mustGetFeed(fid)\n\tpost := feed.AddPost(caller, parentID, catetory, metadata)\n\treturn post.id\n}\n\n// Only post's owner can edit post\nfunc EditPost(fid FeedID, pid PostID, category uint64, metadata string) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tif caller != post.creator {\n\t\tpanic(\"you are not creator of this post\")\n\t}\n\n\tpost.Update(category, metadata)\n}\n\n// Only feed creator/owner can call this\nfunc SetOwner(fid FeedID, newOwner std.Address) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tif caller != feed.creator && caller != feed.owner {\n\t\tpanic(\"you are not creator/owner of this feed\")\n\t}\n\n\tfeed.owner = newOwner\n}\n\n// Only feed creator/owner or post creator can delete the post\nfunc DeletePost(fid FeedID, pid PostID) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tif caller != post.creator && caller != feed.creator && caller != feed.owner {\n\t\tpanic(\"you are nor creator of this post neither creator/owner of the feed\")\n\t}\n\n\tpost.Delete()\n\n\t// If post is comment then decrease comments count on parent\n\tif uint64(post.parentID) != 0 {\n\t\tparent := feed.MustGetPost(post.parentID)\n\t\tparent.commentsCount -= 1\n\t}\n}\n\n// Only feed owner can ban the post\nfunc BanPost(fid FeedID, pid PostID, reason string) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\t_ = feed.MustGetPost(pid)\n\n\t// For experimenting, we ban only the post for now\n\t// TODO: recursive delete/ban comments\n\tif caller != feed.owner {\n\t\tpanic(\"you are owner of the feed\")\n\t}\n\n\tfeed.BanPost(pid)\n\n\tfeed.flags.ClearFlagCount(getFlagID(fid, pid))\n}\n\n// Any one can react post\nfunc ReactPost(fid FeedID, pid PostID, icon string, up bool) {\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tpost.React(icon, up)\n}\n\nfunc TipPost(fid FeedID, pid PostID) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tpost.Tip(caller, post.creator)\n}\n\n// Get a list of flagged posts\n// NOTE: We can support multi feeds in the future but for now we will have only 1 feed\n// Return stringified list in format: postStr-count,postStr-count\nfunc GetFlaggedPosts(fid FeedID, offset uint64, limit uint8) string {\n\tfeed := mustGetFeed(fid)\n\n\t// Already sorted by count descending\n\tflags := feed.flags.GetFlags(uint64(limit), offset)\n\n\tvar postList []string\n\tfor _, flagCount := range flags {\n\t\tflagID := flagCount.FlagID\n\n\t\tfeedID, postID := parseFlagID(flagID)\n\t\tif feedID != feed.id {\n\t\t\tcontinue\n\t\t}\n\n\t\tpost := feed.GetPost(postID)\n\t\tpostList = append(postList, ufmt.Sprintf(\"%s\", post))\n\t}\n\n\tSEPARATOR := \",\"\n\tres := strings.Join(postList, SEPARATOR)\n\treturn ufmt.Sprintf(\"[%s]\", res)\n}\n\n// NOTE: due to bug of std.PrevRealm().Addr() return \"\" when query so we user this proxy function temporary\n// in waiting of correct behaviour of std.PrevRealm().Addr()\nfunc GetPosts(fid FeedID, parentID PostID, user string, categories []uint64, offset uint64, limit uint8) string {\n\tcaller := std.PrevRealm().Addr()\n\tdata := GetPostsWithCaller(fid, parentID, caller.String(), user, categories, offset, limit)\n\treturn data\n}\n\nfunc GetPostsWithCaller(fid FeedID, parentID PostID, callerAddrStr string, user string, categories []uint64, offset uint64, limit uint8) string {\n\t// Return flagged posts, we process flagged posts differently using FlagIndex\n\tif len(categories) == 1 && categories[0] == uint64(9) {\n\t\treturn GetFlaggedPosts(fid, offset, limit)\n\t}\n\n\t// BUG: normally std.PrevRealm().Addr() should return a value instead of empty\n\t// Fix is in progress on Gno side\n\tfeed := mustGetFeed(fid)\n\tposts := getPosts(feed, parentID, callerAddrStr, user, categories, offset, limit)\n\n\tSEPARATOR := \",\"\n\tvar postListStr []string\n\n\tfor _, post := range posts {\n\t\tpostListStr = append(postListStr, post.String())\n\t}\n\n\tres := strings.Join(postListStr, SEPARATOR)\n\treturn ufmt.Sprintf(\"[%s]\", res)\n}\n\n// user here is: filter by user\nfunc getPosts(feed *Feed, parentID PostID, callerAddrStr string, user string, categories []uint64, offset uint64, limit uint8) []*Post {\n\tcaller := std.Address(callerAddrStr)\n\n\tvar posts []*Post\n\tvar skipped uint64\n\n\t// Create an avlTree for optimizing the check\n\trequestedCategories := avl.NewTree()\n\tfor _, category := range categories {\n\t\tcatStr := strconv.FormatUint(category, 10)\n\t\trequestedCategories.Set(catStr, true)\n\t}\n\n\tfeed.posts.ReverseIterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tpost := value.(*Post)\n\n\t\tpostCatStr := strconv.FormatUint(post.category, 10)\n\n\t\t// NOTE: this search mechanism is not efficient, only for demo purpose\n\t\tif post.parentID == parentID && post.deleted == false {\n\t\t\tif requestedCategories.Size() > 0 && !requestedCategories.Has(postCatStr) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tif user != \"\" && std.Address(user) != post.creator {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t// Filter hidden post\n\t\t\tflagID := getFlagID(feed.id, post.id)\n\t\t\tif feed.flags.HasFlagged(flagID, callerAddrStr) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t// Check if post is in hidden list\n\t\t\tvalue, exists := feed.hiddenPostsByUser.Get(caller.String())\n\t\t\tif exists {\n\t\t\t\thiddenPosts := value.(*avl.Tree)\n\t\t\t\t// If post.id exists in hiddenPosts tree => that post is hidden\n\t\t\t\tif hiddenPosts.Has(post.id.String()) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif skipped < offset {\n\t\t\t\tskipped++\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tposts = append(posts, post)\n\t\t}\n\n\t\tif len(posts) == int(limit) {\n\t\t\treturn true\n\t\t}\n\n\t\treturn false\n\t})\n\n\treturn posts\n}\n\n// Get comments list\nfunc GetComments(fid FeedID, parentID PostID, offset uint64, limit uint8) string {\n\treturn GetPosts(fid, parentID, \"\", []uint64{}, offset, limit)\n}\n\n// Get Post\nfunc GetPost(fid FeedID, pid PostID) string {\n\tfeed := mustGetFeed(fid)\n\n\tdata, ok := feed.posts.Get(postIDKey(pid))\n\tif !ok {\n\t\tpanic(\"Unable to get post\")\n\t}\n\n\tpost := data.(*Post)\n\treturn post.String()\n}\n\nfunc FlagPost(fid FeedID, pid PostID) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.FlagPost(caller, pid)\n}\n\nfunc HidePostForMe(fid FeedID, pid PostID) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.HidePostForUser(caller, pid)\n}\n\nfunc UnHidePostForMe(fid FeedID, pid PostID) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.UnHidePostForUser(caller, pid)\n}\n\nfunc GetFlags(fid FeedID, limit uint64, offset uint64) string {\n\tfeed := mustGetFeed(fid)\n\n\ttype FlagCount struct {\n\t\tFlagID flags_index.FlagID\n\t\tCount uint64\n\t}\n\n\tflags := feed.flags.GetFlags(limit, offset)\n\n\tvar res []string\n\tfor _, flag := range flags {\n\t\tres = append(res, ufmt.Sprintf(\"%s:%d\", flag.FlagID, flag.Count))\n\t}\n\n\treturn strings.Join(res, \"|\")\n}\n\n// TODO: allow only creator to call\nfunc GetFeedByID(fid FeedID) *Feed {\n\treturn mustGetFeed(fid)\n}\n\n// TODO: allow only admin to call\nfunc ExportFeedData(fid FeedID) string {\n\tfeed := mustGetFeed(fid)\n\tfeedJSON := feed.ToJSON()\n\treturn feedJSON\n}\n\n// TODO: allow only admin to call\nfunc ImportFeedData(fid FeedID, jsonData string) {\n\tfeed := mustGetFeed(fid)\n\tfeed.FromJSON(jsonData)\n}\n\n// func MigrateFromPreviousFeed(fid feedsV7.FeedID) {\n// \t// Get exported data from previous feeds\n// \tjsonData := feedsV7.ExportFeedData(fid)\n// \tImportFeedData(FeedID(uint64(fid)), jsonData)\n// }\n" + "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/teritori/flags_index\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Only registered user can create a new feed\n// For the flexibility when testing, allow all user to create feed\nfunc CreateFeed(name string) FeedID {\n\tpkgpath := std.CurrentRealmPath()\n\n\tfid := incGetFeedID()\n\tcaller := std.PreviousRealm().Addr()\n\turl := strings.Replace(pkgpath, \"gno.land\", \"\", -1) + \":\" + name\n\tfeed := newFeed(fid, url, name, caller)\n\tfidkey := feedIDKey(fid)\n\tgFeeds.Set(fidkey, feed)\n\tgFeedsByName.Set(name, feed)\n\treturn feed.id\n}\n\n// Anyone can create a post in a existing feed, allow un-registered users also\nfunc CreatePost(fid FeedID, parentID PostID, catetory uint64, metadata string) PostID {\n\tcaller := std.PreviousRealm().Addr()\n\n\tfeed := mustGetFeed(fid)\n\tpost := feed.AddPost(caller, parentID, catetory, metadata)\n\treturn post.id\n}\n\n// Only post's owner can edit post\nfunc EditPost(fid FeedID, pid PostID, category uint64, metadata string) {\n\tcaller := std.PreviousRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tif caller != post.creator {\n\t\tpanic(\"you are not creator of this post\")\n\t}\n\n\tpost.Update(category, metadata)\n}\n\n// Only feed creator/owner can call this\nfunc SetOwner(fid FeedID, newOwner std.Address) {\n\tcaller := std.PreviousRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tif caller != feed.creator && caller != feed.owner {\n\t\tpanic(\"you are not creator/owner of this feed\")\n\t}\n\n\tfeed.owner = newOwner\n}\n\n// Only feed creator/owner or post creator can delete the post\nfunc DeletePost(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tif caller != post.creator && caller != feed.creator && caller != feed.owner {\n\t\tpanic(\"you are nor creator of this post neither creator/owner of the feed\")\n\t}\n\n\tpost.Delete()\n\n\t// If post is comment then decrease comments count on parent\n\tif uint64(post.parentID) != 0 {\n\t\tparent := feed.MustGetPost(post.parentID)\n\t\tparent.commentsCount -= 1\n\t}\n}\n\n// Only feed owner can ban the post\nfunc BanPost(fid FeedID, pid PostID, reason string) {\n\tcaller := std.PreviousRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\t_ = feed.MustGetPost(pid)\n\n\t// For experimenting, we ban only the post for now\n\t// TODO: recursive delete/ban comments\n\tif caller != feed.owner {\n\t\tpanic(\"you are owner of the feed\")\n\t}\n\n\tfeed.BanPost(pid)\n\n\tfeed.flags.ClearFlagCount(getFlagID(fid, pid))\n}\n\n// Any one can react post\nfunc ReactPost(fid FeedID, pid PostID, icon string, up bool) {\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tpost.React(icon, up)\n}\n\nfunc TipPost(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tpost.Tip(caller, post.creator)\n}\n\n// Get a list of flagged posts\n// NOTE: We can support multi feeds in the future but for now we will have only 1 feed\n// Return stringified list in format: postStr-count,postStr-count\nfunc GetFlaggedPosts(fid FeedID, offset uint64, limit uint8) string {\n\tfeed := mustGetFeed(fid)\n\n\t// Already sorted by count descending\n\tflags := feed.flags.GetFlags(uint64(limit), offset)\n\n\tvar postList []string\n\tfor _, flagCount := range flags {\n\t\tflagID := flagCount.FlagID\n\n\t\tfeedID, postID := parseFlagID(flagID)\n\t\tif feedID != feed.id {\n\t\t\tcontinue\n\t\t}\n\n\t\tpost := feed.GetPost(postID)\n\t\tpostList = append(postList, ufmt.Sprintf(\"%s\", post))\n\t}\n\n\tSEPARATOR := \",\"\n\tres := strings.Join(postList, SEPARATOR)\n\treturn ufmt.Sprintf(\"[%s]\", res)\n}\n\n// NOTE: due to bug of std.PreviousRealm().Addr() return \"\" when query so we user this proxy function temporary\n// in waiting of correct behaviour of std.PreviousRealm().Addr()\nfunc GetPosts(fid FeedID, parentID PostID, user string, categories []uint64, offset uint64, limit uint8) string {\n\tcaller := std.PreviousRealm().Addr()\n\tdata := GetPostsWithCaller(fid, parentID, caller.String(), user, categories, offset, limit)\n\treturn data\n}\n\nfunc GetPostsWithCaller(fid FeedID, parentID PostID, callerAddrStr string, user string, categories []uint64, offset uint64, limit uint8) string {\n\t// Return flagged posts, we process flagged posts differently using FlagIndex\n\tif len(categories) == 1 && categories[0] == uint64(9) {\n\t\treturn GetFlaggedPosts(fid, offset, limit)\n\t}\n\n\t// BUG: normally std.PreviousRealm().Addr() should return a value instead of empty\n\t// Fix is in progress on Gno side\n\tfeed := mustGetFeed(fid)\n\tposts := getPosts(feed, parentID, callerAddrStr, user, categories, offset, limit)\n\n\tSEPARATOR := \",\"\n\tvar postListStr []string\n\n\tfor _, post := range posts {\n\t\tpostListStr = append(postListStr, post.String())\n\t}\n\n\tres := strings.Join(postListStr, SEPARATOR)\n\treturn ufmt.Sprintf(\"[%s]\", res)\n}\n\n// user here is: filter by user\nfunc getPosts(feed *Feed, parentID PostID, callerAddrStr string, user string, categories []uint64, offset uint64, limit uint8) []*Post {\n\tcaller := std.Address(callerAddrStr)\n\n\tvar posts []*Post\n\tvar skipped uint64\n\n\t// Create an avlTree for optimizing the check\n\trequestedCategories := avl.NewTree()\n\tfor _, category := range categories {\n\t\tcatStr := strconv.FormatUint(category, 10)\n\t\trequestedCategories.Set(catStr, true)\n\t}\n\n\tfeed.posts.ReverseIterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tpost := value.(*Post)\n\n\t\tpostCatStr := strconv.FormatUint(post.category, 10)\n\n\t\t// NOTE: this search mechanism is not efficient, only for demo purpose\n\t\tif post.parentID == parentID && post.deleted == false {\n\t\t\tif requestedCategories.Size() > 0 && !requestedCategories.Has(postCatStr) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tif user != \"\" && std.Address(user) != post.creator {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t// Filter hidden post\n\t\t\tflagID := getFlagID(feed.id, post.id)\n\t\t\tif feed.flags.HasFlagged(flagID, callerAddrStr) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t// Check if post is in hidden list\n\t\t\tvalue, exists := feed.hiddenPostsByUser.Get(caller.String())\n\t\t\tif exists {\n\t\t\t\thiddenPosts := value.(*avl.Tree)\n\t\t\t\t// If post.id exists in hiddenPosts tree => that post is hidden\n\t\t\t\tif hiddenPosts.Has(post.id.String()) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif skipped < offset {\n\t\t\t\tskipped++\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tposts = append(posts, post)\n\t\t}\n\n\t\tif len(posts) == int(limit) {\n\t\t\treturn true\n\t\t}\n\n\t\treturn false\n\t})\n\n\treturn posts\n}\n\n// Get comments list\nfunc GetComments(fid FeedID, parentID PostID, offset uint64, limit uint8) string {\n\treturn GetPosts(fid, parentID, \"\", []uint64{}, offset, limit)\n}\n\n// Get Post\nfunc GetPost(fid FeedID, pid PostID) string {\n\tfeed := mustGetFeed(fid)\n\n\tdata, ok := feed.posts.Get(postIDKey(pid))\n\tif !ok {\n\t\tpanic(\"Unable to get post\")\n\t}\n\n\tpost := data.(*Post)\n\treturn post.String()\n}\n\nfunc FlagPost(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.FlagPost(caller, pid)\n}\n\nfunc HidePostForMe(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.HidePostForUser(caller, pid)\n}\n\nfunc UnHidePostForMe(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.UnHidePostForUser(caller, pid)\n}\n\nfunc GetFlags(fid FeedID, limit uint64, offset uint64) string {\n\tfeed := mustGetFeed(fid)\n\n\ttype FlagCount struct {\n\t\tFlagID flags_index.FlagID\n\t\tCount uint64\n\t}\n\n\tflags := feed.flags.GetFlags(limit, offset)\n\n\tvar res []string\n\tfor _, flag := range flags {\n\t\tres = append(res, ufmt.Sprintf(\"%s:%d\", flag.FlagID, flag.Count))\n\t}\n\n\treturn strings.Join(res, \"|\")\n}\n\n// TODO: allow only creator to call\nfunc GetFeedByID(fid FeedID) *Feed {\n\treturn mustGetFeed(fid)\n}\n\n// TODO: allow only admin to call\nfunc ExportFeedData(fid FeedID) string {\n\tfeed := mustGetFeed(fid)\n\tfeedJSON := feed.ToJSON()\n\treturn feedJSON\n}\n\n// TODO: allow only admin to call\nfunc ImportFeedData(fid FeedID, jsonData string) {\n\tfeed := mustGetFeed(fid)\n\tfeed.FromJSON(jsonData)\n}\n\n// func MigrateFromPreviousFeed(fid feedsV7.FeedID) {\n// \t// Get exported data from previous feeds\n// \tjsonData := feedsV7.ExportFeedData(fid)\n// \tImportFeedData(FeedID(uint64(fid)), jsonData)\n// }\n" }, { "Name": "render.gno", diff --git a/gno.land/pkg/integration/testdata/issue_2283_cacheTypes.txtar b/gno.land/pkg/integration/testdata/issue_2283_cacheTypes.txtar index da7e105e1d5..0de65b58852 100644 --- a/gno.land/pkg/integration/testdata/issue_2283_cacheTypes.txtar +++ b/gno.land/pkg/integration/testdata/issue_2283_cacheTypes.txtar @@ -64,7 +64,7 @@ stdout OK! }, { "Name": "public.gno", - "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/teritori/flags_index\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Only registered user can create a new feed\n// For the flexibility when testing, allow all user to create feed\nfunc CreateFeed(name string) FeedID {\n\tpkgpath := std.CurrentRealmPath()\n\n\tfid := incGetFeedID()\n\tcaller := std.PrevRealm().Addr()\n\turl := strings.Replace(pkgpath, \"gno.land\", \"\", -1) + \":\" + name\n\tfeed := newFeed(fid, url, name, caller)\n\tfidkey := feedIDKey(fid)\n\tgFeeds.Set(fidkey, feed)\n\tgFeedsByName.Set(name, feed)\n\treturn feed.id\n}\n\n// Anyone can create a post in a existing feed, allow un-registered users also\nfunc CreatePost(fid FeedID, parentID PostID, catetory uint64, metadata string) PostID {\n\tcaller := std.PrevRealm().Addr()\n\n\tfeed := mustGetFeed(fid)\n\tpost := feed.AddPost(caller, parentID, catetory, metadata)\n\treturn post.id\n}\n\n// Only post's owner can edit post\nfunc EditPost(fid FeedID, pid PostID, category uint64, metadata string) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tif caller != post.creator {\n\t\tpanic(\"you are not creator of this post\")\n\t}\n\n\tpost.Update(category, metadata)\n}\n\n// Only feed creator/owner can call this\nfunc SetOwner(fid FeedID, newOwner std.Address) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tif caller != feed.creator && caller != feed.owner {\n\t\tpanic(\"you are not creator/owner of this feed\")\n\t}\n\n\tfeed.owner = newOwner\n}\n\n// Only feed creator/owner or post creator can delete the post\nfunc DeletePost(fid FeedID, pid PostID) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tif caller != post.creator && caller != feed.creator && caller != feed.owner {\n\t\tpanic(\"you are nor creator of this post neither creator/owner of the feed\")\n\t}\n\n\tpost.Delete()\n\n\t// If post is comment then decrease comments count on parent\n\tif uint64(post.parentID) != 0 {\n\t\tparent := feed.MustGetPost(post.parentID)\n\t\tparent.commentsCount -= 1\n\t}\n}\n\n// Only feed owner can ban the post\nfunc BanPost(fid FeedID, pid PostID, reason string) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\t_ = feed.MustGetPost(pid)\n\n\t// For experimenting, we ban only the post for now\n\t// TODO: recursive delete/ban comments\n\tif caller != feed.owner {\n\t\tpanic(\"you are owner of the feed\")\n\t}\n\n\tfeed.BanPost(pid)\n\n\tfeed.flags.ClearFlagCount(getFlagID(fid, pid))\n}\n\n// Any one can react post\nfunc ReactPost(fid FeedID, pid PostID, icon string, up bool) {\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tpost.React(icon, up)\n}\n\nfunc TipPost(fid FeedID, pid PostID) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tpost.Tip(caller, post.creator)\n}\n\n// Get a list of flagged posts\n// NOTE: We can support multi feeds in the future but for now we will have only 1 feed\n// Return stringified list in format: postStr-count,postStr-count\nfunc GetFlaggedPosts(fid FeedID, offset uint64, limit uint8) string {\n\tfeed := mustGetFeed(fid)\n\n\t// Already sorted by count descending\n\tflags := feed.flags.GetFlags(uint64(limit), offset)\n\n\tvar postList []string\n\tfor _, flagCount := range flags {\n\t\tflagID := flagCount.FlagID\n\n\t\tfeedID, postID := parseFlagID(flagID)\n\t\tif feedID != feed.id {\n\t\t\tcontinue\n\t\t}\n\n\t\tpost := feed.GetPost(postID)\n\t\tpostList = append(postList, ufmt.Sprintf(\"%s\", post))\n\t}\n\n\tSEPARATOR := \",\"\n\tres := strings.Join(postList, SEPARATOR)\n\treturn ufmt.Sprintf(\"[%s]\", res)\n}\n\n// NOTE: due to bug of std.PrevRealm().Addr() return \"\" when query so we user this proxy function temporary\n// in waiting of correct behaviour of std.PrevRealm().Addr()\nfunc GetPosts(fid FeedID, parentID PostID, user string, categories []uint64, offset uint64, limit uint8) string {\n\tcaller := std.PrevRealm().Addr()\n\tdata := GetPostsWithCaller(fid, parentID, caller.String(), user, categories, offset, limit)\n\treturn data\n}\n\nfunc GetPostsWithCaller(fid FeedID, parentID PostID, callerAddrStr string, user string, categories []uint64, offset uint64, limit uint8) string {\n\t// Return flagged posts, we process flagged posts differently using FlagIndex\n\tif len(categories) == 1 && categories[0] == uint64(9) {\n\t\treturn GetFlaggedPosts(fid, offset, limit)\n\t}\n\n\t// BUG: normally std.PrevRealm().Addr() should return a value instead of empty\n\t// Fix is in progress on Gno side\n\tfeed := mustGetFeed(fid)\n\tposts := getPosts(feed, parentID, callerAddrStr, user, categories, offset, limit)\n\n\tSEPARATOR := \",\"\n\tvar postListStr []string\n\n\tfor _, post := range posts {\n\t\tpostListStr = append(postListStr, post.String())\n\t}\n\n\tres := strings.Join(postListStr, SEPARATOR)\n\treturn ufmt.Sprintf(\"[%s]\", res)\n}\n\n// user here is: filter by user\nfunc getPosts(feed *Feed, parentID PostID, callerAddrStr string, user string, categories []uint64, offset uint64, limit uint8) []*Post {\n\tcaller := std.Address(callerAddrStr)\n\n\tvar posts []*Post\n\tvar skipped uint64\n\n\t// Create an avlTree for optimizing the check\n\trequestedCategories := avl.NewTree()\n\tfor _, category := range categories {\n\t\tcatStr := strconv.FormatUint(category, 10)\n\t\trequestedCategories.Set(catStr, true)\n\t}\n\n\tfeed.posts.ReverseIterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tpost := value.(*Post)\n\n\t\tpostCatStr := strconv.FormatUint(post.category, 10)\n\n\t\t// NOTE: this search mechanism is not efficient, only for demo purpose\n\t\tif post.parentID == parentID && post.deleted == false {\n\t\t\tif requestedCategories.Size() > 0 && !requestedCategories.Has(postCatStr) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tif user != \"\" && std.Address(user) != post.creator {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t// Filter hidden post\n\t\t\tflagID := getFlagID(feed.id, post.id)\n\t\t\tif feed.flags.HasFlagged(flagID, callerAddrStr) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t// Check if post is in hidden list\n\t\t\tvalue, exists := feed.hiddenPostsByUser.Get(caller.String())\n\t\t\tif exists {\n\t\t\t\thiddenPosts := value.(*avl.Tree)\n\t\t\t\t// If post.id exists in hiddenPosts tree => that post is hidden\n\t\t\t\tif hiddenPosts.Has(post.id.String()) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif skipped < offset {\n\t\t\t\tskipped++\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tposts = append(posts, post)\n\t\t}\n\n\t\tif len(posts) == int(limit) {\n\t\t\treturn true\n\t\t}\n\n\t\treturn false\n\t})\n\n\treturn posts\n}\n\n// Get comments list\nfunc GetComments(fid FeedID, parentID PostID, offset uint64, limit uint8) string {\n\treturn GetPosts(fid, parentID, \"\", []uint64{}, offset, limit)\n}\n\n// Get Post\nfunc GetPost(fid FeedID, pid PostID) string {\n\tfeed := mustGetFeed(fid)\n\n\tdata, ok := feed.posts.Get(postIDKey(pid))\n\tif !ok {\n\t\tpanic(\"Unable to get post\")\n\t}\n\n\tpost := data.(*Post)\n\treturn post.String()\n}\n\nfunc FlagPost(fid FeedID, pid PostID) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.FlagPost(caller, pid)\n}\n\nfunc HidePostForMe(fid FeedID, pid PostID) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.HidePostForUser(caller, pid)\n}\n\nfunc UnHidePostForMe(fid FeedID, pid PostID) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.UnHidePostForUser(caller, pid)\n}\n\nfunc GetFlags(fid FeedID, limit uint64, offset uint64) string {\n\tfeed := mustGetFeed(fid)\n\n\ttype FlagCount struct {\n\t\tFlagID flags_index.FlagID\n\t\tCount uint64\n\t}\n\n\tflags := feed.flags.GetFlags(limit, offset)\n\n\tvar res []string\n\tfor _, flag := range flags {\n\t\tres = append(res, ufmt.Sprintf(\"%s:%d\", flag.FlagID, flag.Count))\n\t}\n\n\treturn strings.Join(res, \"|\")\n}\n\n// TODO: allow only creator to call\nfunc GetFeedByID(fid FeedID) *Feed {\n\treturn mustGetFeed(fid)\n}\n\n// TODO: allow only admin to call\nfunc ExportFeedData(fid FeedID) string {\n\tfeed := mustGetFeed(fid)\n\tfeedJSON := feed.ToJSON()\n\treturn feedJSON\n}\n\n// TODO: allow only admin to call\nfunc ImportFeedData(fid FeedID, jsonData string) {\n\tfeed := mustGetFeed(fid)\n\tfeed.FromJSON(jsonData)\n}\n\n// func MigrateFromPreviousFeed(fid feedsV7.FeedID) {\n// \t// Get exported data from previous feeds\n// \tjsonData := feedsV7.ExportFeedData(fid)\n// \tImportFeedData(FeedID(uint64(fid)), jsonData)\n// }\n" + "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/teritori/flags_index\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Only registered user can create a new feed\n// For the flexibility when testing, allow all user to create feed\nfunc CreateFeed(name string) FeedID {\n\tpkgpath := std.CurrentRealmPath()\n\n\tfid := incGetFeedID()\n\tcaller := std.PreviousRealm().Addr()\n\turl := strings.Replace(pkgpath, \"gno.land\", \"\", -1) + \":\" + name\n\tfeed := newFeed(fid, url, name, caller)\n\tfidkey := feedIDKey(fid)\n\tgFeeds.Set(fidkey, feed)\n\tgFeedsByName.Set(name, feed)\n\treturn feed.id\n}\n\n// Anyone can create a post in a existing feed, allow un-registered users also\nfunc CreatePost(fid FeedID, parentID PostID, catetory uint64, metadata string) PostID {\n\tcaller := std.PreviousRealm().Addr()\n\n\tfeed := mustGetFeed(fid)\n\tpost := feed.AddPost(caller, parentID, catetory, metadata)\n\treturn post.id\n}\n\n// Only post's owner can edit post\nfunc EditPost(fid FeedID, pid PostID, category uint64, metadata string) {\n\tcaller := std.PreviousRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tif caller != post.creator {\n\t\tpanic(\"you are not creator of this post\")\n\t}\n\n\tpost.Update(category, metadata)\n}\n\n// Only feed creator/owner can call this\nfunc SetOwner(fid FeedID, newOwner std.Address) {\n\tcaller := std.PreviousRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tif caller != feed.creator && caller != feed.owner {\n\t\tpanic(\"you are not creator/owner of this feed\")\n\t}\n\n\tfeed.owner = newOwner\n}\n\n// Only feed creator/owner or post creator can delete the post\nfunc DeletePost(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tif caller != post.creator && caller != feed.creator && caller != feed.owner {\n\t\tpanic(\"you are nor creator of this post neither creator/owner of the feed\")\n\t}\n\n\tpost.Delete()\n\n\t// If post is comment then decrease comments count on parent\n\tif uint64(post.parentID) != 0 {\n\t\tparent := feed.MustGetPost(post.parentID)\n\t\tparent.commentsCount -= 1\n\t}\n}\n\n// Only feed owner can ban the post\nfunc BanPost(fid FeedID, pid PostID, reason string) {\n\tcaller := std.PreviousRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\t_ = feed.MustGetPost(pid)\n\n\t// For experimenting, we ban only the post for now\n\t// TODO: recursive delete/ban comments\n\tif caller != feed.owner {\n\t\tpanic(\"you are owner of the feed\")\n\t}\n\n\tfeed.BanPost(pid)\n\n\tfeed.flags.ClearFlagCount(getFlagID(fid, pid))\n}\n\n// Any one can react post\nfunc ReactPost(fid FeedID, pid PostID, icon string, up bool) {\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tpost.React(icon, up)\n}\n\nfunc TipPost(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tpost.Tip(caller, post.creator)\n}\n\n// Get a list of flagged posts\n// NOTE: We can support multi feeds in the future but for now we will have only 1 feed\n// Return stringified list in format: postStr-count,postStr-count\nfunc GetFlaggedPosts(fid FeedID, offset uint64, limit uint8) string {\n\tfeed := mustGetFeed(fid)\n\n\t// Already sorted by count descending\n\tflags := feed.flags.GetFlags(uint64(limit), offset)\n\n\tvar postList []string\n\tfor _, flagCount := range flags {\n\t\tflagID := flagCount.FlagID\n\n\t\tfeedID, postID := parseFlagID(flagID)\n\t\tif feedID != feed.id {\n\t\t\tcontinue\n\t\t}\n\n\t\tpost := feed.GetPost(postID)\n\t\tpostList = append(postList, ufmt.Sprintf(\"%s\", post))\n\t}\n\n\tSEPARATOR := \",\"\n\tres := strings.Join(postList, SEPARATOR)\n\treturn ufmt.Sprintf(\"[%s]\", res)\n}\n\n// NOTE: due to bug of std.PreviousRealm().Addr() return \"\" when query so we user this proxy function temporary\n// in waiting of correct behaviour of std.PreviousRealm().Addr()\nfunc GetPosts(fid FeedID, parentID PostID, user string, categories []uint64, offset uint64, limit uint8) string {\n\tcaller := std.PreviousRealm().Addr()\n\tdata := GetPostsWithCaller(fid, parentID, caller.String(), user, categories, offset, limit)\n\treturn data\n}\n\nfunc GetPostsWithCaller(fid FeedID, parentID PostID, callerAddrStr string, user string, categories []uint64, offset uint64, limit uint8) string {\n\t// Return flagged posts, we process flagged posts differently using FlagIndex\n\tif len(categories) == 1 && categories[0] == uint64(9) {\n\t\treturn GetFlaggedPosts(fid, offset, limit)\n\t}\n\n\t// BUG: normally std.PreviousRealm().Addr() should return a value instead of empty\n\t// Fix is in progress on Gno side\n\tfeed := mustGetFeed(fid)\n\tposts := getPosts(feed, parentID, callerAddrStr, user, categories, offset, limit)\n\n\tSEPARATOR := \",\"\n\tvar postListStr []string\n\n\tfor _, post := range posts {\n\t\tpostListStr = append(postListStr, post.String())\n\t}\n\n\tres := strings.Join(postListStr, SEPARATOR)\n\treturn ufmt.Sprintf(\"[%s]\", res)\n}\n\n// user here is: filter by user\nfunc getPosts(feed *Feed, parentID PostID, callerAddrStr string, user string, categories []uint64, offset uint64, limit uint8) []*Post {\n\tcaller := std.Address(callerAddrStr)\n\n\tvar posts []*Post\n\tvar skipped uint64\n\n\t// Create an avlTree for optimizing the check\n\trequestedCategories := avl.NewTree()\n\tfor _, category := range categories {\n\t\tcatStr := strconv.FormatUint(category, 10)\n\t\trequestedCategories.Set(catStr, true)\n\t}\n\n\tfeed.posts.ReverseIterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tpost := value.(*Post)\n\n\t\tpostCatStr := strconv.FormatUint(post.category, 10)\n\n\t\t// NOTE: this search mechanism is not efficient, only for demo purpose\n\t\tif post.parentID == parentID && post.deleted == false {\n\t\t\tif requestedCategories.Size() > 0 && !requestedCategories.Has(postCatStr) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tif user != \"\" && std.Address(user) != post.creator {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t// Filter hidden post\n\t\t\tflagID := getFlagID(feed.id, post.id)\n\t\t\tif feed.flags.HasFlagged(flagID, callerAddrStr) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t// Check if post is in hidden list\n\t\t\tvalue, exists := feed.hiddenPostsByUser.Get(caller.String())\n\t\t\tif exists {\n\t\t\t\thiddenPosts := value.(*avl.Tree)\n\t\t\t\t// If post.id exists in hiddenPosts tree => that post is hidden\n\t\t\t\tif hiddenPosts.Has(post.id.String()) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif skipped < offset {\n\t\t\t\tskipped++\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tposts = append(posts, post)\n\t\t}\n\n\t\tif len(posts) == int(limit) {\n\t\t\treturn true\n\t\t}\n\n\t\treturn false\n\t})\n\n\treturn posts\n}\n\n// Get comments list\nfunc GetComments(fid FeedID, parentID PostID, offset uint64, limit uint8) string {\n\treturn GetPosts(fid, parentID, \"\", []uint64{}, offset, limit)\n}\n\n// Get Post\nfunc GetPost(fid FeedID, pid PostID) string {\n\tfeed := mustGetFeed(fid)\n\n\tdata, ok := feed.posts.Get(postIDKey(pid))\n\tif !ok {\n\t\tpanic(\"Unable to get post\")\n\t}\n\n\tpost := data.(*Post)\n\treturn post.String()\n}\n\nfunc FlagPost(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.FlagPost(caller, pid)\n}\n\nfunc HidePostForMe(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.HidePostForUser(caller, pid)\n}\n\nfunc UnHidePostForMe(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.UnHidePostForUser(caller, pid)\n}\n\nfunc GetFlags(fid FeedID, limit uint64, offset uint64) string {\n\tfeed := mustGetFeed(fid)\n\n\ttype FlagCount struct {\n\t\tFlagID flags_index.FlagID\n\t\tCount uint64\n\t}\n\n\tflags := feed.flags.GetFlags(limit, offset)\n\n\tvar res []string\n\tfor _, flag := range flags {\n\t\tres = append(res, ufmt.Sprintf(\"%s:%d\", flag.FlagID, flag.Count))\n\t}\n\n\treturn strings.Join(res, \"|\")\n}\n\n// TODO: allow only creator to call\nfunc GetFeedByID(fid FeedID) *Feed {\n\treturn mustGetFeed(fid)\n}\n\n// TODO: allow only admin to call\nfunc ExportFeedData(fid FeedID) string {\n\tfeed := mustGetFeed(fid)\n\tfeedJSON := feed.ToJSON()\n\treturn feedJSON\n}\n\n// TODO: allow only admin to call\nfunc ImportFeedData(fid FeedID, jsonData string) {\n\tfeed := mustGetFeed(fid)\n\tfeed.FromJSON(jsonData)\n}\n\n// func MigrateFromPreviousFeed(fid feedsV7.FeedID) {\n// \t// Get exported data from previous feeds\n// \tjsonData := feedsV7.ExportFeedData(fid)\n// \tImportFeedData(FeedID(uint64(fid)), jsonData)\n// }\n" }, { "Name": "render.gno", diff --git a/gno.land/pkg/integration/testdata/prevrealm.txtar b/gno.land/pkg/integration/testdata/prevrealm.txtar index 20317d87345..492691a021b 100644 --- a/gno.land/pkg/integration/testdata/prevrealm.txtar +++ b/gno.land/pkg/integration/testdata/prevrealm.txtar @@ -1,4 +1,4 @@ -# This tests ensure the consistency of the std.PrevRealm function, in the +# This tests ensure the consistency of the std.PreviousRealm function, in the # following situations: # # @@ -16,8 +16,8 @@ # | 10 | | | myrlm.B() | r/foo | # | 11 | | through /p/demo/bar | bar.A() | user address | # | 12 | | | bar.B() | user address | -# | 13 | MsgCall | wallet direct | std.PrevRealm() | user address | -# | 14 | MsgRun | wallet direct | std.PrevRealm() | user address | +# | 13 | MsgCall | wallet direct | std.PreviousRealm() | user address | +# | 14 | MsgRun | wallet direct | std.PreviousRealm() | user address | # Init ## deploy myrlm @@ -82,11 +82,11 @@ stdout ${USER_ADDR_test1} gnokey maketx run -gas-fee 100000ugnot -gas-wanted 12000000 -broadcast -chainid tendermint_test test1 $WORK/run/barB.gno stdout ${USER_ADDR_test1} -## 13. MsgCall -> std.PrevRealm(): user address -## gnokey maketx call -pkgpath std -func PrevRealm -gas-fee 100000ugnot -gas-wanted 4000000 -broadcast -chainid tendermint_test test1 +## 13. MsgCall -> std.PreviousRealm(): user address +## gnokey maketx call -pkgpath std -func PreviousRealm -gas-fee 100000ugnot -gas-wanted 4000000 -broadcast -chainid tendermint_test test1 ## stdout ${USER_ADDR_test1} -## 14. MsgRun -> std.PrevRealm(): user address +## 14. MsgRun -> std.PreviousRealm(): user address gnokey maketx run -gas-fee 100000ugnot -gas-wanted 12000000 -broadcast -chainid tendermint_test test1 $WORK/run/baz.gno stdout ${USER_ADDR_test1} @@ -96,7 +96,7 @@ package myrlm import "std" func A() string { - return std.PrevRealm().Addr().String() + return std.PreviousRealm().Addr().String() } func B() string { @@ -120,7 +120,7 @@ package bar import "std" func A() string { - return std.PrevRealm().Addr().String() + return std.PreviousRealm().Addr().String() } func B() string { @@ -180,5 +180,5 @@ package main import "std" func main() { - println(std.PrevRealm().Addr().String()) + println(std.PreviousRealm().Addr().String()) } diff --git a/gnovm/stdlibs/std/native.gno b/gnovm/stdlibs/std/native.gno index 790de8aaaae..b8f38bf1681 100644 --- a/gnovm/stdlibs/std/native.gno +++ b/gnovm/stdlibs/std/native.gno @@ -32,7 +32,7 @@ func CurrentRealm() Realm { return Realm{Address(addr), path} } -func PrevRealm() Realm { +func PreviousRealm() Realm { addr, path := getRealm(1) return Realm{Address(addr), path} } diff --git a/gnovm/stdlibs/std/native_test.go b/gnovm/stdlibs/std/native_test.go index ff86861647c..e509b990d00 100644 --- a/gnovm/stdlibs/std/native_test.go +++ b/gnovm/stdlibs/std/native_test.go @@ -9,7 +9,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/crypto" ) -func TestPrevRealmIsOrigin(t *testing.T) { +func TestPreviousRealmIsOrigin(t *testing.T) { var ( user = gno.DerivePkgAddr("user1.gno").Bech32() ctx = ExecContext{ diff --git a/gnovm/tests/files/zrealm_crossrealm11.gno b/gnovm/tests/files/zrealm_crossrealm11.gno index 5936743ddc6..1c64f3b0cf6 100644 --- a/gnovm/tests/files/zrealm_crossrealm11.gno +++ b/gnovm/tests/files/zrealm_crossrealm11.gno @@ -9,8 +9,8 @@ import ( rtests "gno.land/r/demo/tests" ) -func getPrevRealm() std.Realm { - return std.PrevRealm() +func getPreviousRealm() std.Realm { + return std.PreviousRealm() } func Exec(fn func()) { @@ -43,32 +43,32 @@ func main() { }{ { callStackAdd: "", - callerFn: std.PrevRealm, + callerFn: std.PreviousRealm, }, { - callStackAdd: " -> r/crossrealm_test.getPrevRealm", - callerFn: getPrevRealm, + callStackAdd: " -> r/crossrealm_test.getPreviousRealm", + callerFn: getPreviousRealm, }, { callStackAdd: " -> p/demo/tests", - callerFn: ptests.GetPrevRealm, + callerFn: ptests.GetPreviousRealm, }, { callStackAdd: " -> p/demo/tests -> p/demo/tests/subtests", - callerFn: ptests.GetPSubtestsPrevRealm, + callerFn: ptests.GetPSubtestsPreviousRealm, }, { callStackAdd: " -> r/demo/tests", - callerFn: rtests.GetPrevRealm, + callerFn: rtests.GetPreviousRealm, }, { callStackAdd: " -> r/demo/tests -> r/demo/tests/subtests", - callerFn: rtests.GetRSubtestsPrevRealm, + callerFn: rtests.GetRSubtestsPreviousRealm, }, } println("---") // needed to have space prefixes - printColumns("STACK", "std.PrevRealm") + printColumns("STACK", "std.PreviousRealm") printColumns("-----", "------------------") baseCallStack := "user1.gno -> r/crossrealm_test.main" @@ -111,16 +111,16 @@ func printColumns(left, right string) { // Output: // --- -// STACK = std.PrevRealm +// STACK = std.PreviousRealm // ----- = ------------------ // user1.gno -> r/crossrealm_test.main = user1.gno // user1.gno -> r/crossrealm_test.main -> r/crossrealm_test.Exec = user1.gno // user1.gno -> r/crossrealm_test.main -> r/demo/tests.Exec = gno.land/r/demo/tests // user1.gno -> r/crossrealm_test.main -> p/demo/tests.Exec = user1.gno -// user1.gno -> r/crossrealm_test.main -> r/crossrealm_test.getPrevRealm = user1.gno -// user1.gno -> r/crossrealm_test.main -> r/crossrealm_test.Exec -> r/crossrealm_test.getPrevRealm = user1.gno -// user1.gno -> r/crossrealm_test.main -> r/demo/tests.Exec -> r/crossrealm_test.getPrevRealm = gno.land/r/demo/tests -// user1.gno -> r/crossrealm_test.main -> p/demo/tests.Exec -> r/crossrealm_test.getPrevRealm = user1.gno +// user1.gno -> r/crossrealm_test.main -> r/crossrealm_test.getPreviousRealm = user1.gno +// user1.gno -> r/crossrealm_test.main -> r/crossrealm_test.Exec -> r/crossrealm_test.getPreviousRealm = user1.gno +// user1.gno -> r/crossrealm_test.main -> r/demo/tests.Exec -> r/crossrealm_test.getPreviousRealm = gno.land/r/demo/tests +// user1.gno -> r/crossrealm_test.main -> p/demo/tests.Exec -> r/crossrealm_test.getPreviousRealm = user1.gno // user1.gno -> r/crossrealm_test.main -> p/demo/tests = user1.gno // user1.gno -> r/crossrealm_test.main -> r/crossrealm_test.Exec -> p/demo/tests = user1.gno // user1.gno -> r/crossrealm_test.main -> r/demo/tests.Exec -> p/demo/tests = gno.land/r/demo/tests diff --git a/gnovm/tests/files/zrealm_crossrealm13.gno b/gnovm/tests/files/zrealm_crossrealm13.gno index 4daeb6de366..63f8dec6357 100644 --- a/gnovm/tests/files/zrealm_crossrealm13.gno +++ b/gnovm/tests/files/zrealm_crossrealm13.gno @@ -7,15 +7,15 @@ import ( func main() { PrintRealm() println(pad("CurrentRealm:"), std.CurrentRealm()) - println(pad("PrevRealm:"), std.PrevRealm()) + println(pad("PreviousRealm:"), std.PreviousRealm()) std.TestSetRealm(std.NewUserRealm("g1user")) PrintRealm() println(pad("CurrentRealm:"), std.CurrentRealm()) - println(pad("PrevRealm:"), std.PrevRealm()) + println(pad("PreviousRealm:"), std.PreviousRealm()) std.TestSetRealm(std.NewCodeRealm("gno.land/r/demo/users")) PrintRealm() println(pad("CurrentRealm:"), std.CurrentRealm()) - println(pad("PrevRealm:"), std.PrevRealm()) + println(pad("PreviousRealm:"), std.PreviousRealm()) } func pad(s string) string { @@ -27,7 +27,7 @@ func pad(s string) string { func PrintRealm() { println(pad("PrintRealm: CurrentRealm:"), std.CurrentRealm()) - println(pad("PrintRealm: PrevRealm:"), std.PrevRealm()) + println(pad("PrintRealm: PreviousRealm:"), std.PreviousRealm()) } // Because this is the context of a package, using PrintRealm() @@ -35,14 +35,14 @@ func PrintRealm() { // Output: // PrintRealm: CurrentRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) -// PrintRealm: PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PrintRealm: PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) // CurrentRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) -// PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) // PrintRealm: CurrentRealm: (struct{("g1user" std.Address),("" string)} std.Realm) -// PrintRealm: PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PrintRealm: PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) // CurrentRealm: (struct{("g1user" std.Address),("" string)} std.Realm) -// PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) // PrintRealm: CurrentRealm: (struct{("g17m4ga9t9dxn8uf06p3cahdavzfexe33ecg8v2s" std.Address),("gno.land/r/demo/users" string)} std.Realm) -// PrintRealm: PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PrintRealm: PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) // CurrentRealm: (struct{("g17m4ga9t9dxn8uf06p3cahdavzfexe33ecg8v2s" std.Address),("gno.land/r/demo/users" string)} std.Realm) -// PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) diff --git a/gnovm/tests/files/zrealm_crossrealm13a.gno b/gnovm/tests/files/zrealm_crossrealm13a.gno index 2fc37804fce..c02711b7100 100644 --- a/gnovm/tests/files/zrealm_crossrealm13a.gno +++ b/gnovm/tests/files/zrealm_crossrealm13a.gno @@ -8,15 +8,15 @@ import ( func main() { PrintRealm() println(pad("CurrentRealm:"), std.CurrentRealm()) - println(pad("PrevRealm:"), std.PrevRealm()) + println(pad("PreviousRealm:"), std.PreviousRealm()) std.TestSetRealm(std.NewUserRealm("g1user")) PrintRealm() println(pad("CurrentRealm:"), std.CurrentRealm()) - println(pad("PrevRealm:"), std.PrevRealm()) + println(pad("PreviousRealm:"), std.PreviousRealm()) std.TestSetRealm(std.NewCodeRealm("gno.land/r/demo/users")) PrintRealm() println(pad("CurrentRealm:"), std.CurrentRealm()) - println(pad("PrevRealm:"), std.PrevRealm()) + println(pad("PreviousRealm:"), std.PreviousRealm()) } func pad(s string) string { @@ -28,19 +28,19 @@ func pad(s string) string { func PrintRealm() { println(pad("PrintRealm: CurrentRealm:"), std.CurrentRealm()) - println(pad("PrintRealm: PrevRealm:"), std.PrevRealm()) + println(pad("PrintRealm: PreviousRealm:"), std.PreviousRealm()) } // Output: // PrintRealm: CurrentRealm: (struct{("g1r0mlnkc05z0fv49km99z60qnp95tengyqfdr02" std.Address),("gno.land/r/demo/groups" string)} std.Realm) -// PrintRealm: PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PrintRealm: PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) // CurrentRealm: (struct{("g1r0mlnkc05z0fv49km99z60qnp95tengyqfdr02" std.Address),("gno.land/r/demo/groups" string)} std.Realm) -// PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) // PrintRealm: CurrentRealm: (struct{("g1r0mlnkc05z0fv49km99z60qnp95tengyqfdr02" std.Address),("gno.land/r/demo/groups" string)} std.Realm) -// PrintRealm: PrevRealm: (struct{("g1user" std.Address),("" string)} std.Realm) +// PrintRealm: PreviousRealm: (struct{("g1user" std.Address),("" string)} std.Realm) // CurrentRealm: (struct{("g1user" std.Address),("" string)} std.Realm) -// PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) // PrintRealm: CurrentRealm: (struct{("g1r0mlnkc05z0fv49km99z60qnp95tengyqfdr02" std.Address),("gno.land/r/demo/groups" string)} std.Realm) -// PrintRealm: PrevRealm: (struct{("g17m4ga9t9dxn8uf06p3cahdavzfexe33ecg8v2s" std.Address),("gno.land/r/demo/users" string)} std.Realm) +// PrintRealm: PreviousRealm: (struct{("g17m4ga9t9dxn8uf06p3cahdavzfexe33ecg8v2s" std.Address),("gno.land/r/demo/users" string)} std.Realm) // CurrentRealm: (struct{("g17m4ga9t9dxn8uf06p3cahdavzfexe33ecg8v2s" std.Address),("gno.land/r/demo/users" string)} std.Realm) -// PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) diff --git a/gnovm/tests/files/zrealm_tests0.gno b/gnovm/tests/files/zrealm_tests0.gno index a68139c3e21..2e3214a5878 100644 --- a/gnovm/tests/files/zrealm_tests0.gno +++ b/gnovm/tests/files/zrealm_tests0.gno @@ -1457,7 +1457,7 @@ func main() { // }, // "FileName": "tests.gno", // "IsMethod": false, -// "Name": "GetPrevRealm", +// "Name": "GetPreviousRealm", // "NativeName": "", // "NativePkg": "", // "PkgPath": "gno.land/r/demo/tests", @@ -1513,7 +1513,7 @@ func main() { // }, // "FileName": "tests.gno", // "IsMethod": false, -// "Name": "GetRSubtestsPrevRealm", +// "Name": "GetRSubtestsPreviousRealm", // "NativeName": "", // "NativePkg": "", // "PkgPath": "gno.land/r/demo/tests", diff --git a/gnovm/tests/stdlibs/std/std.gno b/gnovm/tests/stdlibs/std/std.gno index b1d85822d3c..3359ce460d3 100644 --- a/gnovm/tests/stdlibs/std/std.gno +++ b/gnovm/tests/stdlibs/std/std.gno @@ -9,7 +9,7 @@ func TestSetOrigPkgAddr(addr Address) { testSetOrigPkgAddr(string(addr)) } // TestSetRealm sets the realm for the current frame. // After calling TestSetRealm, calling CurrentRealm() in the test function will yield the value of -// rlm, while if a realm function is called, using PrevRealm() will yield rlm. +// rlm, while if a realm function is called, using PreviousRealm() will yield rlm. func TestSetRealm(rlm Realm) { testSetRealm(string(rlm.addr), rlm.pkgPath) } diff --git a/gnovm/tests/stdlibs/std/std.go b/gnovm/tests/stdlibs/std/std.go index 49daeb899de..4121dee1f7a 100644 --- a/gnovm/tests/stdlibs/std/std.go +++ b/gnovm/tests/stdlibs/std/std.go @@ -14,7 +14,7 @@ import ( type TestExecContext struct { std.ExecContext - // These are used to set up the result of CurrentRealm() and PrevRealm(). + // These are used to set up the result of CurrentRealm() and PreviousRealm(). RealmFrames map[*gno.Frame]RealmOverride } diff --git a/misc/deployments/test5.gno.land/genesis_txs.jsonl b/misc/deployments/test5.gno.land/genesis_txs.jsonl index 2a3c299c9e7..2e0a23e830a 100755 --- a/misc/deployments/test5.gno.land/genesis_txs.jsonl +++ b/misc/deployments/test5.gno.land/genesis_txs.jsonl @@ -31,40 +31,40 @@ {"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"static","path":"gno.land/p/demo/gnorkle/feeds/static","files":[{"name":"feed.gno","body":"package static\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"errors\"\n\n\t\"gno.land/p/demo/gnorkle/feed\"\n\t\"gno.land/p/demo/gnorkle/gnorkle\"\n\t\"gno.land/p/demo/gnorkle/ingesters/single\"\n\t\"gno.land/p/demo/gnorkle/message\"\n\t\"gno.land/p/demo/gnorkle/storage/simple\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Feed is a static feed.\ntype Feed struct {\n\tid string\n\tisLocked bool\n\tvalueDataType string\n\tingester gnorkle.Ingester\n\tstorage gnorkle.Storage\n\ttasks []feed.Task\n}\n\n// NewFeed creates a new static feed.\nfunc NewFeed(\n\tid string,\n\tvalueDataType string,\n\tingester gnorkle.Ingester,\n\tstorage gnorkle.Storage,\n\ttasks ...feed.Task,\n) *Feed {\n\treturn \u0026Feed{\n\t\tid: id,\n\t\tvalueDataType: valueDataType,\n\t\tingester: ingester,\n\t\tstorage: storage,\n\t\ttasks: tasks,\n\t}\n}\n\n// NewSingleValueFeed is a convenience function for creating a static feed\n// that autocommits a value after a single ingestion.\nfunc NewSingleValueFeed(\n\tid string,\n\tvalueDataType string,\n\ttasks ...feed.Task,\n) *Feed {\n\treturn NewFeed(\n\t\tid,\n\t\tvalueDataType,\n\t\t\u0026single.ValueIngester{},\n\t\tsimple.NewStorage(1),\n\t\ttasks...,\n\t)\n}\n\n// ID returns the feed's ID.\nfunc (f Feed) ID() string {\n\treturn f.id\n}\n\n// Type returns the feed's type.\nfunc (f Feed) Type() feed.Type {\n\treturn feed.TypeStatic\n}\n\n// Ingest ingests a message into the feed. It either adds the value to the ingester's\n// pending values or commits the value to the storage.\nfunc (f *Feed) Ingest(funcType message.FuncType, msg, providerAddress string) error {\n\tif f == nil {\n\t\treturn feed.ErrUndefined\n\t}\n\n\tif f.isLocked {\n\t\treturn errors.New(\"feed locked\")\n\t}\n\n\tswitch funcType {\n\tcase message.FuncTypeIngest:\n\t\t// Autocommit the ingester's value if it's a single value ingester\n\t\t// because this is a static feed and this is the only value it will ever have.\n\t\tif canAutoCommit, err := f.ingester.Ingest(msg, providerAddress); canAutoCommit \u0026\u0026 err == nil {\n\t\t\tif err := f.ingester.CommitValue(f.storage, providerAddress); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tf.isLocked = true\n\t\t} else if err != nil {\n\t\t\treturn err\n\t\t}\n\n\tcase message.FuncTypeCommit:\n\t\tif err := f.ingester.CommitValue(f.storage, providerAddress); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tf.isLocked = true\n\n\tdefault:\n\t\treturn errors.New(\"invalid message function \" + string(funcType))\n\t}\n\n\treturn nil\n}\n\n// Value returns the feed's latest value, it's data type, and whether or not it can\n// be safely consumed. In this case it uses `f.isLocked` because, this being a static\n// feed, it will only ever have one value; once that value is committed the feed is locked\n// and there is a valid, non-empty value to consume.\nfunc (f Feed) Value() (feed.Value, string, bool) {\n\treturn f.storage.GetLatest(), f.valueDataType, f.isLocked\n}\n\n// MarshalJSON marshals the components of the feed that are needed for\n// an agent to execute tasks and send values for ingestion.\nfunc (f Feed) MarshalJSON() ([]byte, error) {\n\tbuf := new(bytes.Buffer)\n\tw := bufio.NewWriter(buf)\n\n\tw.Write([]byte(\n\t\t`{\"id\":\"` + f.id +\n\t\t\t`\",\"type\":\"` + ufmt.Sprintf(\"%d\", int(f.Type())) +\n\t\t\t`\",\"value_type\":\"` + f.valueDataType +\n\t\t\t`\",\"tasks\":[`),\n\t)\n\n\tfirst := true\n\tfor _, task := range f.tasks {\n\t\tif !first {\n\t\t\tw.WriteString(\",\")\n\t\t}\n\n\t\ttaskJSON, err := task.MarshalJSON()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tw.Write(taskJSON)\n\t\tfirst = false\n\t}\n\n\tw.Write([]byte(\"]}\"))\n\tw.Flush()\n\n\treturn buf.Bytes(), nil\n}\n\n// Tasks returns the feed's tasks. This allows task consumers to extract task\n// contents without having to marshal the entire feed.\nfunc (f Feed) Tasks() []feed.Task {\n\treturn f.tasks\n}\n\n// IsActive returns true if the feed is accepting ingestion requests from agents.\nfunc (f Feed) IsActive() bool {\n\treturn !f.isLocked\n}\n"},{"name":"feed_test.gno","body":"package static_test\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/gnorkle/feed\"\n\t\"gno.land/p/demo/gnorkle/feeds/static\"\n\t\"gno.land/p/demo/gnorkle/gnorkle\"\n\t\"gno.land/p/demo/gnorkle/ingester\"\n\t\"gno.land/p/demo/gnorkle/message\"\n\t\"gno.land/p/demo/gnorkle/storage/simple\"\n\t\"gno.land/p/demo/uassert\"\n\t\"gno.land/p/demo/urequire\"\n)\n\ntype mockIngester struct {\n\tcanAutoCommit bool\n\tingestErr error\n\tcommitErr error\n\tvalue string\n\tproviderAddress string\n}\n\nfunc (i mockIngester) Type() ingester.Type {\n\treturn ingester.Type(0)\n}\n\nfunc (i *mockIngester) Ingest(value, providerAddress string) (bool, error) {\n\tif i.ingestErr != nil {\n\t\treturn false, i.ingestErr\n\t}\n\n\ti.value = value\n\ti.providerAddress = providerAddress\n\treturn i.canAutoCommit, nil\n}\n\nfunc (i *mockIngester) CommitValue(storage gnorkle.Storage, providerAddress string) error {\n\tif i.commitErr != nil {\n\t\treturn i.commitErr\n\t}\n\n\treturn storage.Put(i.value)\n}\n\nfunc TestNewSingleValueFeed(t *testing.T) {\n\tstaticFeed := static.NewSingleValueFeed(\"1\", \"\")\n\n\tuassert.Equal(t, \"1\", staticFeed.ID())\n\tuassert.Equal(t, int(feed.TypeStatic), int(staticFeed.Type()))\n}\n\nfunc TestFeed_Ingest(t *testing.T) {\n\tvar undefinedFeed *static.Feed\n\terr := undefinedFeed.Ingest(\"\", \"\", \"\")\n\tuassert.ErrorIs(t, err, feed.ErrUndefined)\n\n\ttests := []struct {\n\t\tname string\n\t\tingester *mockIngester\n\t\tverifyIsLocked bool\n\t\tdoCommit bool\n\t\tfuncType message.FuncType\n\t\tmsg string\n\t\tproviderAddress string\n\t\texpFeedValueString string\n\t\texpErrText string\n\t\texpIsActive bool\n\t}{\n\t\t{\n\t\t\tname: \"func invalid error\",\n\t\t\tingester: \u0026mockIngester{},\n\t\t\tfuncType: message.FuncType(\"derp\"),\n\t\t\texpErrText: \"invalid message function derp\",\n\t\t\texpIsActive: true,\n\t\t},\n\t\t{\n\t\t\tname: \"func ingest ingest error\",\n\t\t\tingester: \u0026mockIngester{\n\t\t\t\tingestErr: errors.New(\"ingest error\"),\n\t\t\t},\n\t\t\tfuncType: message.FuncTypeIngest,\n\t\t\texpErrText: \"ingest error\",\n\t\t\texpIsActive: true,\n\t\t},\n\t\t{\n\t\t\tname: \"func ingest commit error\",\n\t\t\tingester: \u0026mockIngester{\n\t\t\t\tcommitErr: errors.New(\"commit error\"),\n\t\t\t\tcanAutoCommit: true,\n\t\t\t},\n\t\t\tfuncType: message.FuncTypeIngest,\n\t\t\texpErrText: \"commit error\",\n\t\t\texpIsActive: true,\n\t\t},\n\t\t{\n\t\t\tname: \"func commit commit error\",\n\t\t\tingester: \u0026mockIngester{\n\t\t\t\tcommitErr: errors.New(\"commit error\"),\n\t\t\t\tcanAutoCommit: true,\n\t\t\t},\n\t\t\tfuncType: message.FuncTypeCommit,\n\t\t\texpErrText: \"commit error\",\n\t\t\texpIsActive: true,\n\t\t},\n\t\t{\n\t\t\tname: \"only ingest\",\n\t\t\tingester: \u0026mockIngester{},\n\t\t\tfuncType: message.FuncTypeIngest,\n\t\t\tmsg: \"still active feed\",\n\t\t\tproviderAddress: \"gno1234\",\n\t\t\texpIsActive: true,\n\t\t},\n\t\t{\n\t\t\tname: \"ingest autocommit\",\n\t\t\tingester: \u0026mockIngester{canAutoCommit: true},\n\t\t\tfuncType: message.FuncTypeIngest,\n\t\t\tmsg: \"still active feed\",\n\t\t\tproviderAddress: \"gno1234\",\n\t\t\texpFeedValueString: \"still active feed\",\n\t\t\tverifyIsLocked: true,\n\t\t},\n\t\t{\n\t\t\tname: \"commit no value\",\n\t\t\tingester: \u0026mockIngester{},\n\t\t\tfuncType: message.FuncTypeCommit,\n\t\t\tmsg: \"shouldn't be stored\",\n\t\t\tverifyIsLocked: true,\n\t\t},\n\t\t{\n\t\t\tname: \"ingest then commmit\",\n\t\t\tingester: \u0026mockIngester{},\n\t\t\tfuncType: message.FuncTypeIngest,\n\t\t\tmsg: \"blahblah\",\n\t\t\tdoCommit: true,\n\t\t\texpFeedValueString: \"blahblah\",\n\t\t\tverifyIsLocked: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tstaticFeed := static.NewFeed(\n\t\t\t\t\"1\",\n\t\t\t\t\"string\",\n\t\t\t\ttt.ingester,\n\t\t\t\tsimple.NewStorage(1),\n\t\t\t\tnil,\n\t\t\t)\n\n\t\t\tvar errText string\n\t\t\tif err := staticFeed.Ingest(tt.funcType, tt.msg, tt.providerAddress); err != nil {\n\t\t\t\terrText = err.Error()\n\t\t\t}\n\n\t\t\turequire.Equal(t, tt.expErrText, errText)\n\n\t\t\tif tt.doCommit {\n\t\t\t\terr := staticFeed.Ingest(message.FuncTypeCommit, \"\", \"\")\n\t\t\t\turequire.NoError(t, err, \"follow up commit failed\")\n\t\t\t}\n\n\t\t\tif tt.verifyIsLocked {\n\t\t\t\terrText = \"\"\n\t\t\t\tif err := staticFeed.Ingest(tt.funcType, tt.msg, tt.providerAddress); err != nil {\n\t\t\t\t\terrText = err.Error()\n\t\t\t\t}\n\n\t\t\t\turequire.Equal(t, \"feed locked\", errText)\n\t\t\t}\n\n\t\t\tuassert.Equal(t, tt.providerAddress, tt.ingester.providerAddress)\n\n\t\t\tfeedValue, dataType, isLocked := staticFeed.Value()\n\t\t\tuassert.Equal(t, tt.expFeedValueString, feedValue.String)\n\t\t\tuassert.Equal(t, \"string\", dataType)\n\t\t\tuassert.Equal(t, tt.verifyIsLocked, isLocked)\n\t\t\tuassert.Equal(t, tt.expIsActive, staticFeed.IsActive())\n\t\t})\n\t}\n}\n\ntype mockTask struct {\n\terr error\n\tvalue string\n}\n\nfunc (t mockTask) MarshalJSON() ([]byte, error) {\n\tif t.err != nil {\n\t\treturn nil, t.err\n\t}\n\n\treturn []byte(`{\"value\":\"` + t.value + `\"}`), nil\n}\n\nfunc TestFeed_Tasks(t *testing.T) {\n\tid := \"99\"\n\tvalueDataType := \"int\"\n\n\ttests := []struct {\n\t\tname string\n\t\ttasks []feed.Task\n\t\texpErrText string\n\t\texpJSON string\n\t}{\n\t\t{\n\t\t\tname: \"no tasks\",\n\t\t\texpJSON: `{\"id\":\"99\",\"type\":\"0\",\"value_type\":\"int\",\"tasks\":[]}`,\n\t\t},\n\t\t{\n\t\t\tname: \"marshal error\",\n\t\t\ttasks: []feed.Task{\n\t\t\t\tmockTask{err: errors.New(\"marshal error\")},\n\t\t\t},\n\t\t\texpErrText: \"marshal error\",\n\t\t},\n\t\t{\n\t\t\tname: \"one task\",\n\t\t\ttasks: []feed.Task{\n\t\t\t\tmockTask{value: \"single\"},\n\t\t\t},\n\t\t\texpJSON: `{\"id\":\"99\",\"type\":\"0\",\"value_type\":\"int\",\"tasks\":[{\"value\":\"single\"}]}`,\n\t\t},\n\t\t{\n\t\t\tname: \"two tasks\",\n\t\t\ttasks: []feed.Task{\n\t\t\t\tmockTask{value: \"first\"},\n\t\t\t\tmockTask{value: \"second\"},\n\t\t\t},\n\t\t\texpJSON: `{\"id\":\"99\",\"type\":\"0\",\"value_type\":\"int\",\"tasks\":[{\"value\":\"first\"},{\"value\":\"second\"}]}`,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tstaticFeed := static.NewSingleValueFeed(\n\t\t\t\tid,\n\t\t\t\tvalueDataType,\n\t\t\t\ttt.tasks...,\n\t\t\t)\n\n\t\t\turequire.Equal(t, len(tt.tasks), len(staticFeed.Tasks()))\n\n\t\t\tvar errText string\n\t\t\tjson, err := staticFeed.MarshalJSON()\n\t\t\tif err != nil {\n\t\t\t\terrText = err.Error()\n\t\t\t}\n\n\t\t\turequire.Equal(t, tt.expErrText, errText)\n\t\t\turequire.Equal(t, tt.expJSON, string(json))\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":"exts","path":"gno.land/p/demo/grc/exts","files":[{"name":"token_metadata.gno","body":"package exts\n\ntype TokenMetadata interface {\n\t// Returns the name of the token.\n\tGetName() string\n\n\t// Returns the symbol of the token, usually a shorter version of the\n\t// name.\n\tGetSymbol() string\n\n\t// Returns the decimals places of the token.\n\tGetDecimals() uint\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":"grc1155","path":"gno.land/p/demo/grc/grc1155","files":[{"name":"README.md","body":"# GRC-1155 Spec: Multi Token Standard\n\nGRC1155 is a specification for managing multiple tokens based on Gnoland. The name and design is based on Ethereum's ERC1155 standard.\n\n## See also:\n\n[ERC-1155 Spec][erc-1155]\n\n[erc-1155]: https://eips.ethereum.org/EIPS/eip-1155"},{"name":"basic_grc1155_token.gno","body":"package grc1155\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype basicGRC1155Token struct {\n\turi string\n\tbalances avl.Tree // \"TokenId:Address\" -\u003e uint64\n\toperatorApprovals avl.Tree // \"OwnerAddress:OperatorAddress\" -\u003e bool\n}\n\nvar _ IGRC1155 = (*basicGRC1155Token)(nil)\n\n// Returns new basic GRC1155 token\nfunc NewBasicGRC1155Token(uri string) *basicGRC1155Token {\n\treturn \u0026basicGRC1155Token{\n\t\turi: uri,\n\t\tbalances: avl.Tree{},\n\t\toperatorApprovals: avl.Tree{},\n\t}\n}\n\nfunc (s *basicGRC1155Token) Uri() string { return s.uri }\n\n// BalanceOf returns the input address's balance of the token type requested\nfunc (s *basicGRC1155Token) BalanceOf(addr std.Address, tid TokenID) (uint64, error) {\n\tif !isValidAddress(addr) {\n\t\treturn 0, ErrInvalidAddress\n\t}\n\n\tkey := string(tid) + \":\" + addr.String()\n\tbalance, found := s.balances.Get(key)\n\tif !found {\n\t\treturn 0, nil\n\t}\n\n\treturn balance.(uint64), nil\n}\n\n// BalanceOfBatch returns the balance of multiple account/token pairs\nfunc (s *basicGRC1155Token) BalanceOfBatch(owners []std.Address, batch []TokenID) ([]uint64, error) {\n\tif len(owners) != len(batch) {\n\t\treturn nil, ErrMismatchLength\n\t}\n\n\tbalanceOfBatch := make([]uint64, len(owners))\n\n\tfor i := 0; i \u003c len(owners); i++ {\n\t\tbalanceOfBatch[i], _ = s.BalanceOf(owners[i], batch[i])\n\t}\n\n\treturn balanceOfBatch, nil\n}\n\n// SetApprovalForAll can approve the operator to operate on all tokens\nfunc (s *basicGRC1155Token) SetApprovalForAll(operator std.Address, approved bool) error {\n\tif !isValidAddress(operator) {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tcaller := std.OriginCaller()\n\treturn s.setApprovalForAll(caller, operator, approved)\n}\n\n// IsApprovedForAll returns true if operator is the owner or is approved for all by the owner.\n// Otherwise, returns false\nfunc (s *basicGRC1155Token) IsApprovedForAll(owner, operator std.Address) bool {\n\tif operator == owner {\n\t\treturn true\n\t}\n\tkey := owner.String() + \":\" + operator.String()\n\t_, found := s.operatorApprovals.Get(key)\n\tif !found {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// Safely transfers `tokenId` token from `from` to `to`, checking that\n// contract recipients are aware of the GRC1155 protocol to prevent\n// tokens from being forever locked.\nfunc (s *basicGRC1155Token) SafeTransferFrom(from, to std.Address, tid TokenID, amount uint64) error {\n\tcaller := std.OriginCaller()\n\tif !s.IsApprovedForAll(caller, from) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.safeBatchTransferFrom(from, to, []TokenID{tid}, []uint64{amount})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.doSafeTransferAcceptanceCheck(caller, from, to, tid, amount) {\n\t\treturn ErrTransferToRejectedOrNonGRC1155Receiver\n\t}\n\n\temit(\u0026TransferSingleEvent{caller, from, to, tid, amount})\n\n\treturn nil\n}\n\n// Safely transfers a `batch` of tokens from `from` to `to`, checking that\n// contract recipients are aware of the GRC1155 protocol to prevent\n// tokens from being forever locked.\nfunc (s *basicGRC1155Token) SafeBatchTransferFrom(from, to std.Address, batch []TokenID, amounts []uint64) error {\n\tcaller := std.OriginCaller()\n\tif !s.IsApprovedForAll(caller, from) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.safeBatchTransferFrom(from, to, batch, amounts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.doSafeBatchTransferAcceptanceCheck(caller, from, to, batch, amounts) {\n\t\treturn ErrTransferToRejectedOrNonGRC1155Receiver\n\t}\n\n\temit(\u0026TransferBatchEvent{caller, from, to, batch, amounts})\n\n\treturn nil\n}\n\n// Creates `amount` tokens of token type `id`, and assigns them to `to`. Also checks that\n// contract recipients are using GRC1155 protocol.\nfunc (s *basicGRC1155Token) SafeMint(to std.Address, tid TokenID, amount uint64) error {\n\tcaller := std.OriginCaller()\n\n\terr := s.mintBatch(to, []TokenID{tid}, []uint64{amount})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.doSafeTransferAcceptanceCheck(caller, zeroAddress, to, tid, amount) {\n\t\treturn ErrTransferToRejectedOrNonGRC1155Receiver\n\t}\n\n\temit(\u0026TransferSingleEvent{caller, zeroAddress, to, tid, amount})\n\n\treturn nil\n}\n\n// Batch version of `SafeMint()`. Also checks that\n// contract recipients are using GRC1155 protocol.\nfunc (s *basicGRC1155Token) SafeBatchMint(to std.Address, batch []TokenID, amounts []uint64) error {\n\tcaller := std.OriginCaller()\n\n\terr := s.mintBatch(to, batch, amounts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.doSafeBatchTransferAcceptanceCheck(caller, zeroAddress, to, batch, amounts) {\n\t\treturn ErrTransferToRejectedOrNonGRC1155Receiver\n\t}\n\n\temit(\u0026TransferBatchEvent{caller, zeroAddress, to, batch, amounts})\n\n\treturn nil\n}\n\n// Destroys `amount` tokens of token type `id` from `from`.\nfunc (s *basicGRC1155Token) Burn(from std.Address, tid TokenID, amount uint64) error {\n\tcaller := std.OriginCaller()\n\n\terr := s.burnBatch(from, []TokenID{tid}, []uint64{amount})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\temit(\u0026TransferSingleEvent{caller, from, zeroAddress, tid, amount})\n\n\treturn nil\n}\n\n// Batch version of `Burn()`\nfunc (s *basicGRC1155Token) BatchBurn(from std.Address, batch []TokenID, amounts []uint64) error {\n\tcaller := std.OriginCaller()\n\n\terr := s.burnBatch(from, batch, amounts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\temit(\u0026TransferBatchEvent{caller, from, zeroAddress, batch, amounts})\n\n\treturn nil\n}\n\n/* Helper methods */\n\n// Helper for SetApprovalForAll(): approve `operator` to operate on all of `owner` tokens\nfunc (s *basicGRC1155Token) setApprovalForAll(owner, operator std.Address, approved bool) error {\n\tif owner == operator {\n\t\treturn nil\n\t}\n\n\tkey := owner.String() + \":\" + operator.String()\n\tif approved {\n\t\ts.operatorApprovals.Set(key, approved)\n\t} else {\n\t\ts.operatorApprovals.Remove(key)\n\t}\n\n\temit(\u0026ApprovalForAllEvent{owner, operator, approved})\n\n\treturn nil\n}\n\n// Helper for SafeTransferFrom() and SafeBatchTransferFrom()\nfunc (s *basicGRC1155Token) safeBatchTransferFrom(from, to std.Address, batch []TokenID, amounts []uint64) error {\n\tif len(batch) != len(amounts) {\n\t\treturn ErrMismatchLength\n\t}\n\tif !isValidAddress(from) || !isValidAddress(to) {\n\t\treturn ErrInvalidAddress\n\t}\n\tif from == to {\n\t\treturn ErrCannotTransferToSelf\n\t}\n\n\tcaller := std.OriginCaller()\n\ts.beforeTokenTransfer(caller, from, to, batch, amounts)\n\n\tfor i := 0; i \u003c len(batch); i++ {\n\t\ttid := batch[i]\n\t\tamount := amounts[i]\n\t\tfromBalance, err := s.BalanceOf(from, tid)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif fromBalance \u003c amount {\n\t\t\treturn ErrInsufficientBalance\n\t\t}\n\t\ttoBalance, err := s.BalanceOf(to, tid)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfromBalance -= amount\n\t\ttoBalance += amount\n\t\tfromBalanceKey := string(tid) + \":\" + from.String()\n\t\ttoBalanceKey := string(tid) + \":\" + to.String()\n\t\ts.balances.Set(fromBalanceKey, fromBalance)\n\t\ts.balances.Set(toBalanceKey, toBalance)\n\t}\n\n\ts.afterTokenTransfer(caller, from, to, batch, amounts)\n\n\treturn nil\n}\n\n// Helper for SafeMint() and SafeBatchMint()\nfunc (s *basicGRC1155Token) mintBatch(to std.Address, batch []TokenID, amounts []uint64) error {\n\tif len(batch) != len(amounts) {\n\t\treturn ErrMismatchLength\n\t}\n\tif !isValidAddress(to) {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tcaller := std.OriginCaller()\n\ts.beforeTokenTransfer(caller, zeroAddress, to, batch, amounts)\n\n\tfor i := 0; i \u003c len(batch); i++ {\n\t\ttid := batch[i]\n\t\tamount := amounts[i]\n\t\ttoBalance, err := s.BalanceOf(to, tid)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ttoBalance += amount\n\t\ttoBalanceKey := string(tid) + \":\" + to.String()\n\t\ts.balances.Set(toBalanceKey, toBalance)\n\t}\n\n\ts.afterTokenTransfer(caller, zeroAddress, to, batch, amounts)\n\n\treturn nil\n}\n\n// Helper for Burn() and BurnBatch()\nfunc (s *basicGRC1155Token) burnBatch(from std.Address, batch []TokenID, amounts []uint64) error {\n\tif len(batch) != len(amounts) {\n\t\treturn ErrMismatchLength\n\t}\n\tif !isValidAddress(from) {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tcaller := std.OriginCaller()\n\ts.beforeTokenTransfer(caller, from, zeroAddress, batch, amounts)\n\n\tfor i := 0; i \u003c len(batch); i++ {\n\t\ttid := batch[i]\n\t\tamount := amounts[i]\n\t\tfromBalance, err := s.BalanceOf(from, tid)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif fromBalance \u003c amount {\n\t\t\treturn ErrBurnAmountExceedsBalance\n\t\t}\n\t\tfromBalance -= amount\n\t\tfromBalanceKey := string(tid) + \":\" + from.String()\n\t\ts.balances.Set(fromBalanceKey, fromBalance)\n\t}\n\n\ts.afterTokenTransfer(caller, from, zeroAddress, batch, amounts)\n\n\treturn nil\n}\n\nfunc (s *basicGRC1155Token) setUri(newUri string) {\n\ts.uri = newUri\n\temit(\u0026UpdateURIEvent{newUri})\n}\n\nfunc (s *basicGRC1155Token) beforeTokenTransfer(operator, from, to std.Address, batch []TokenID, amounts []uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicGRC1155Token) afterTokenTransfer(operator, from, to std.Address, batch []TokenID, amounts []uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicGRC1155Token) doSafeTransferAcceptanceCheck(operator, from, to std.Address, tid TokenID, amount uint64) bool {\n\t// TODO: Implementation\n\treturn true\n}\n\nfunc (s *basicGRC1155Token) doSafeBatchTransferAcceptanceCheck(operator, from, to std.Address, batch []TokenID, amounts []uint64) bool {\n\t// TODO: Implementation\n\treturn true\n}\n\nfunc (s *basicGRC1155Token) RenderHome() (str string) {\n\tstr += ufmt.Sprintf(\"# URI:%s\\n\", s.uri)\n\n\treturn\n}\n"},{"name":"basic_grc1155_token_test.gno","body":"package grc1155\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nconst dummyURI = \"ipfs://xyz\"\n\nfunc TestNewBasicGRC1155Token(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n}\n\nfunc TestUri(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\tuassert.Equal(t, dummyURI, dummy.Uri())\n}\n\nfunc TestBalanceOf(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\tbalanceZeroAddressOfToken1, err := dummy.BalanceOf(zeroAddress, tid1)\n\tuassert.Error(t, err, \"should result in error\")\n\n\tbalanceAddr1OfToken1, err := dummy.BalanceOf(addr1, tid1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(0), balanceAddr1OfToken1)\n\n\tdummy.mintBatch(addr1, []TokenID{tid1, tid2}, []uint64{10, 100})\n\tdummy.mintBatch(addr2, []TokenID{tid1}, []uint64{20})\n\n\tbalanceAddr1OfToken1, err = dummy.BalanceOf(addr1, tid1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tbalanceAddr1OfToken2, err := dummy.BalanceOf(addr1, tid2)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tbalanceAddr2OfToken1, err := dummy.BalanceOf(addr2, tid1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tuassert.Equal(t, uint64(10), balanceAddr1OfToken1)\n\tuassert.Equal(t, uint64(100), balanceAddr1OfToken2)\n\tuassert.Equal(t, uint64(20), balanceAddr2OfToken1)\n}\n\nfunc TestBalanceOfBatch(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{addr1, addr2}, []TokenID{tid1, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(0), balanceBatch[0])\n\tuassert.Equal(t, uint64(0), balanceBatch[1])\n\n\tdummy.mintBatch(addr1, []TokenID{tid1}, []uint64{10})\n\tdummy.mintBatch(addr2, []TokenID{tid2}, []uint64{20})\n\n\tbalanceBatch, err = dummy.BalanceOfBatch([]std.Address{addr1, addr2}, []TokenID{tid1, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(10), balanceBatch[0])\n\tuassert.Equal(t, uint64(20), balanceBatch[1])\n}\n\nfunc TestIsApprovedForAll(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(addr1, addr2)\n\tuassert.False(t, isApprovedForAll)\n}\n\nfunc TestSetApprovalForAll(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.OriginCaller()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(caller, addr)\n\tuassert.False(t, isApprovedForAll)\n\n\terr := dummy.SetApprovalForAll(addr, true)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tisApprovedForAll = dummy.IsApprovedForAll(caller, addr)\n\tuassert.True(t, isApprovedForAll)\n\n\terr = dummy.SetApprovalForAll(addr, false)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tisApprovedForAll = dummy.IsApprovedForAll(caller, addr)\n\tuassert.False(t, isApprovedForAll)\n}\n\nfunc TestSafeTransferFrom(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.OriginCaller()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\ttid := TokenID(\"1\")\n\n\tdummy.mintBatch(caller, []TokenID{tid}, []uint64{100})\n\n\terr := dummy.SafeTransferFrom(caller, zeroAddress, tid, 10)\n\tuassert.Error(t, err, \"should result in error\")\n\n\terr = dummy.SafeTransferFrom(caller, addr, tid, 160)\n\tuassert.Error(t, err, \"should result in error\")\n\n\terr = dummy.SafeTransferFrom(caller, addr, tid, 60)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check balance of caller after transfer\n\tbalanceOfCaller, err := dummy.BalanceOf(caller, tid)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(40), balanceOfCaller)\n\n\t// Check balance of addr after transfer\n\tbalanceOfAddr, err := dummy.BalanceOf(addr, tid)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(60), balanceOfAddr)\n}\n\nfunc TestSafeBatchTransferFrom(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.OriginCaller()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\tdummy.mintBatch(caller, []TokenID{tid1, tid2}, []uint64{10, 100})\n\n\terr := dummy.SafeBatchTransferFrom(caller, zeroAddress, []TokenID{tid1, tid2}, []uint64{4, 60})\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.SafeBatchTransferFrom(caller, addr, []TokenID{tid1, tid2}, []uint64{40, 60})\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.SafeBatchTransferFrom(caller, addr, []TokenID{tid1}, []uint64{40, 60})\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.SafeBatchTransferFrom(caller, addr, []TokenID{tid1, tid2}, []uint64{4, 60})\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{caller, addr, caller, addr}, []TokenID{tid1, tid1, tid2, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check token1's balance of caller after batch transfer\n\tuassert.Equal(t, uint64(6), balanceBatch[0])\n\n\t// Check token1's balance of addr after batch transfer\n\tuassert.Equal(t, uint64(4), balanceBatch[1])\n\n\t// Check token2's balance of caller after batch transfer\n\tuassert.Equal(t, uint64(40), balanceBatch[2])\n\n\t// Check token2's balance of addr after batch transfer\n\tuassert.Equal(t, uint64(60), balanceBatch[3])\n}\n\nfunc TestSafeMint(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\terr := dummy.SafeMint(zeroAddress, tid1, 100)\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.SafeMint(addr1, tid1, 100)\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.SafeMint(addr1, tid2, 200)\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.SafeMint(addr2, tid1, 50)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{addr1, addr2, addr1}, []TokenID{tid1, tid1, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\t// Check token1's balance of addr1 after mint\n\tuassert.Equal(t, uint64(100), balanceBatch[0])\n\t// Check token1's balance of addr2 after mint\n\tuassert.Equal(t, uint64(50), balanceBatch[1])\n\t// Check token2's balance of addr1 after mint\n\tuassert.Equal(t, uint64(200), balanceBatch[2])\n}\n\nfunc TestSafeBatchMint(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\terr := dummy.SafeBatchMint(zeroAddress, []TokenID{tid1, tid2}, []uint64{100, 200})\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.SafeBatchMint(addr1, []TokenID{tid1, tid2}, []uint64{100, 200})\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.SafeBatchMint(addr2, []TokenID{tid1, tid2}, []uint64{300, 400})\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{addr1, addr2, addr1, addr2}, []TokenID{tid1, tid1, tid2, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\t// Check token1's balance of addr1 after batch mint\n\tuassert.Equal(t, uint64(100), balanceBatch[0])\n\t// Check token1's balance of addr2 after batch mint\n\tuassert.Equal(t, uint64(300), balanceBatch[1])\n\t// Check token2's balance of addr1 after batch mint\n\tuassert.Equal(t, uint64(200), balanceBatch[2])\n\t// Check token2's balance of addr2 after batch mint\n\tuassert.Equal(t, uint64(400), balanceBatch[3])\n}\n\nfunc TestBurn(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\tdummy.mintBatch(addr, []TokenID{tid1, tid2}, []uint64{100, 200})\n\terr := dummy.Burn(zeroAddress, tid1, uint64(60))\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.Burn(addr, tid1, uint64(160))\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.Burn(addr, tid1, uint64(60))\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.Burn(addr, tid2, uint64(60))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{addr, addr}, []TokenID{tid1, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check token1's balance of addr after burn\n\tuassert.Equal(t, uint64(40), balanceBatch[0])\n\t// Check token2's balance of addr after burn\n\tuassert.Equal(t, uint64(140), balanceBatch[1])\n}\n\nfunc TestBatchBurn(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\tdummy.mintBatch(addr, []TokenID{tid1, tid2}, []uint64{100, 200})\n\terr := dummy.BatchBurn(zeroAddress, []TokenID{tid1, tid2}, []uint64{60, 60})\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.BatchBurn(addr, []TokenID{tid1, tid2}, []uint64{160, 60})\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.BatchBurn(addr, []TokenID{tid1, tid2}, []uint64{60, 60})\n\tuassert.NoError(t, err, \"should not result in error\")\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{addr, addr}, []TokenID{tid1, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check token1's balance of addr after batch burn\n\tuassert.Equal(t, uint64(40), balanceBatch[0])\n\t// Check token2's balance of addr after batch burn\n\tuassert.Equal(t, uint64(140), balanceBatch[1])\n}\n"},{"name":"errors.gno","body":"package grc1155\n\nimport \"errors\"\n\nvar (\n\tErrInvalidAddress = errors.New(\"invalid address\")\n\tErrMismatchLength = errors.New(\"accounts and ids length mismatch\")\n\tErrCannotTransferToSelf = errors.New(\"cannot send transfer to self\")\n\tErrTransferToRejectedOrNonGRC1155Receiver = errors.New(\"transfer to rejected or non GRC1155Receiver implementer\")\n\tErrCallerIsNotOwnerOrApproved = errors.New(\"caller is not token owner or approved\")\n\tErrInsufficientBalance = errors.New(\"insufficient balance for transfer\")\n\tErrBurnAmountExceedsBalance = errors.New(\"burn amount exceeds balance\")\n)\n"},{"name":"igrc1155.gno","body":"package grc1155\n\nimport \"std\"\n\ntype IGRC1155 interface {\n\tSafeTransferFrom(from, to std.Address, tid TokenID, amount uint64) error\n\tSafeBatchTransferFrom(from, to std.Address, batch []TokenID, amounts []uint64) error\n\tBalanceOf(owner std.Address, tid TokenID) (uint64, error)\n\tBalanceOfBatch(owners []std.Address, batch []TokenID) ([]uint64, error)\n\tSetApprovalForAll(operator std.Address, approved bool) error\n\tIsApprovedForAll(owner, operator std.Address) bool\n}\n\ntype TokenID string\n\ntype TransferSingleEvent struct {\n\tOperator std.Address\n\tFrom std.Address\n\tTo std.Address\n\tTokenID TokenID\n\tAmount uint64\n}\n\ntype TransferBatchEvent struct {\n\tOperator std.Address\n\tFrom std.Address\n\tTo std.Address\n\tBatch []TokenID\n\tAmounts []uint64\n}\n\ntype ApprovalForAllEvent struct {\n\tOwner std.Address\n\tOperator std.Address\n\tApproved bool\n}\n\ntype UpdateURIEvent struct {\n\tURI string\n}\n"},{"name":"util.gno","body":"package grc1155\n\nimport (\n\t\"std\"\n)\n\nconst zeroAddress std.Address = \"\"\n\nfunc isValidAddress(addr std.Address) bool {\n\tif !addr.IsValid() {\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc emit(event interface{}) {\n\t// TODO: setup a pubsub system here?\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":"banker.gno","body":"package grc20\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Banker implements a token banker with admin privileges.\n//\n// The Banker is intended to be used in two main ways:\n// 1. as a temporary object used to make the initial minting, then deleted.\n// 2. preserved in an unexported variable to support conditional administrative\n// tasks protected by the contract.\ntype Banker struct {\n\tname string\n\tsymbol string\n\tdecimals uint\n\ttotalSupply uint64\n\tbalances avl.Tree // std.Address(owner) -\u003e uint64\n\tallowances avl.Tree // string(owner+\":\"+spender) -\u003e uint64\n\ttoken *token // to share the same pointer\n}\n\nfunc NewBanker(name, symbol string, decimals uint) *Banker {\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\tb := Banker{\n\t\tname: name,\n\t\tsymbol: symbol,\n\t\tdecimals: decimals,\n\t}\n\tt := \u0026token{banker: \u0026b}\n\tb.token = t\n\treturn \u0026b\n}\n\nfunc (b Banker) Token() Token { return b.token } // Token returns a grc20 safe-object implementation.\nfunc (b Banker) GetName() string { return b.name }\nfunc (b Banker) GetSymbol() string { return b.symbol }\nfunc (b Banker) GetDecimals() uint { return b.decimals }\nfunc (b Banker) TotalSupply() uint64 { return b.totalSupply }\nfunc (b Banker) KnownAccounts() int { return b.balances.Size() }\n\nfunc (b *Banker) Mint(address std.Address, amount uint64) error {\n\tif !address.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\n\t// TODO: check for overflow\n\n\tb.totalSupply += amount\n\tcurrentBalance := b.BalanceOf(address)\n\tnewBalance := currentBalance + amount\n\n\tb.balances.Set(string(address), newBalance)\n\n\tstd.Emit(\n\t\tMintEvent,\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\nfunc (b *Banker) Burn(address std.Address, amount uint64) error {\n\tif !address.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\t// TODO: check for overflow\n\n\tcurrentBalance := b.BalanceOf(address)\n\tif currentBalance \u003c amount {\n\t\treturn ErrInsufficientBalance\n\t}\n\n\tb.totalSupply -= amount\n\tnewBalance := currentBalance - amount\n\n\tb.balances.Set(string(address), newBalance)\n\n\tstd.Emit(\n\t\tBurnEvent,\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\nfunc (b Banker) BalanceOf(address std.Address) uint64 {\n\tbalance, found := b.balances.Get(address.String())\n\tif !found {\n\t\treturn 0\n\t}\n\treturn balance.(uint64)\n}\n\nfunc (b *Banker) 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 := b.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\tb.allowances.Remove(key)\n\t} else {\n\t\tb.allowances.Set(key, newAllowance)\n\t}\n\n\treturn nil\n}\n\nfunc (b *Banker) 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\ttoBalance := b.BalanceOf(to)\n\tfromBalance := b.BalanceOf(from)\n\n\tif fromBalance \u003c amount {\n\t\treturn ErrInsufficientBalance\n\t}\n\n\tnewToBalance := toBalance + amount\n\tnewFromBalance := fromBalance - amount\n\n\tb.balances.Set(string(to), newToBalance)\n\tb.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\nfunc (b *Banker) TransferFrom(spender, from, to std.Address, amount uint64) error {\n\tif err := b.SpendAllowance(from, spender, amount); err != nil {\n\t\treturn err\n\t}\n\treturn b.Transfer(from, to, amount)\n}\n\nfunc (b *Banker) Allowance(owner, spender std.Address) uint64 {\n\tallowance, found := b.allowances.Get(allowanceKey(owner, spender))\n\tif !found {\n\t\treturn 0\n\t}\n\treturn allowance.(uint64)\n}\n\nfunc (b *Banker) Approve(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\tb.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\nfunc (b *Banker) RenderHome() string {\n\tstr := \"\"\n\tstr += ufmt.Sprintf(\"# %s ($%s)\\n\\n\", b.name, b.symbol)\n\tstr += ufmt.Sprintf(\"* **Decimals**: %d\\n\", b.decimals)\n\tstr += ufmt.Sprintf(\"* **Total supply**: %d\\n\", b.totalSupply)\n\tstr += ufmt.Sprintf(\"* **Known accounts**: %d\\n\", b.KnownAccounts())\n\treturn str\n}\n\nfunc allowanceKey(owner, spender std.Address) string {\n\treturn owner.String() + \":\" + spender.String()\n}\n"},{"name":"banker_test.gno","body":"package grc20\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestBankerImpl(t *testing.T) {\n\tdummy := NewBanker(\"Dummy\", \"DUMMY\", 4)\n\turequire.False(t, dummy == nil, \"dummy should not be nil\")\n}\n\nfunc TestAllowance(t *testing.T) {\n\tvar (\n\t\towner = testutils.TestAddress(\"owner\")\n\t\tspender = testutils.TestAddress(\"spender\")\n\t\tdest = testutils.TestAddress(\"dest\")\n\t)\n\n\tb := NewBanker(\"Dummy\", \"DUMMY\", 6)\n\turequire.NoError(t, b.Mint(owner, 100000000))\n\turequire.NoError(t, b.Approve(owner, spender, 5000000))\n\turequire.Error(t, b.TransferFrom(spender, owner, dest, 10000000), ErrInsufficientAllowance.Error(), \"should not be able to transfer more than approved\")\n\n\ttests := []struct {\n\t\tspend uint64\n\t\texp uint64\n\t}{\n\t\t{3, 4999997},\n\t\t{999997, 4000000},\n\t\t{4000000, 0},\n\t}\n\n\tfor _, tt := range tests {\n\t\tb0 := b.BalanceOf(dest)\n\t\turequire.NoError(t, b.TransferFrom(spender, owner, dest, tt.spend))\n\t\ta := b.Allowance(owner, spender)\n\t\turequire.Equal(t, a, tt.exp, ufmt.Sprintf(\"allowance exp: %d, got %d\", tt.exp, a))\n\t\tb := b.BalanceOf(dest)\n\t\texpB := b0 + tt.spend\n\t\turequire.Equal(t, b, expB, ufmt.Sprintf(\"balance exp: %d, got %d\", expB, b))\n\t}\n\n\turequire.Error(t, b.TransferFrom(spender, owner, dest, 1), \"no allowance\")\n\tkey := allowanceKey(owner, spender)\n\turequire.False(t, b.allowances.Has(key), \"allowance should be removed\")\n\turequire.Equal(t, b.Allowance(owner, spender), uint64(0), \"allowance should be 0\")\n}\n"},{"name":"token.gno","body":"package grc20\n\nimport (\n\t\"std\"\n)\n\n// token implements the Token interface.\n//\n// It is generated with Banker.Token().\n// It can safely be exposed publicly.\ntype token struct {\n\tbanker *Banker\n}\n\n// var _ Token = (*token)(nil)\nfunc (t *token) GetName() string { return t.banker.name }\nfunc (t *token) GetSymbol() string { return t.banker.symbol }\nfunc (t *token) GetDecimals() uint { return t.banker.decimals }\nfunc (t *token) TotalSupply() uint64 { return t.banker.totalSupply }\n\nfunc (t *token) BalanceOf(owner std.Address) uint64 {\n\treturn t.banker.BalanceOf(owner)\n}\n\nfunc (t *token) Transfer(to std.Address, amount uint64) error {\n\tcaller := std.PrevRealm().Addr()\n\treturn t.banker.Transfer(caller, to, amount)\n}\n\nfunc (t *token) Allowance(owner, spender std.Address) uint64 {\n\treturn t.banker.Allowance(owner, spender)\n}\n\nfunc (t *token) Approve(spender std.Address, amount uint64) error {\n\tcaller := std.PrevRealm().Addr()\n\treturn t.banker.Approve(caller, spender, amount)\n}\n\nfunc (t *token) TransferFrom(from, to std.Address, amount uint64) error {\n\tspender := std.PrevRealm().Addr()\n\tif err := t.banker.SpendAllowance(from, spender, amount); err != nil {\n\t\treturn err\n\t}\n\treturn t.banker.Transfer(from, to, amount)\n}\n"},{"name":"token_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/ufmt\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestUserTokenImpl(t *testing.T) {\n\tbank := NewBanker(\"Dummy\", \"DUMMY\", 4)\n\ttok := bank.Token()\n\t_ = tok\n}\n\nfunc TestUserApprove(t *testing.T) {\n\towner := testutils.TestAddress(\"owner\")\n\tspender := testutils.TestAddress(\"spender\")\n\tdest := testutils.TestAddress(\"dest\")\n\n\tbank := NewBanker(\"Dummy\", \"DUMMY\", 6)\n\ttok := bank.Token()\n\n\t// Set owner as the original caller\n\tstd.TestSetOriginCaller(owner)\n\t// Mint 100000000 tokens for owner\n\turequire.NoError(t, bank.Mint(owner, 100000000))\n\n\t// Approve spender to spend 5000000 tokens\n\turequire.NoError(t, tok.Approve(spender, 5000000))\n\n\t// Set spender as the original caller\n\tstd.TestSetOriginCaller(spender)\n\t// Try to transfer 10000000 tokens from owner to dest, should fail because it exceeds allowance\n\turequire.Error(t,\n\t\ttok.TransferFrom(owner, dest, 10000000),\n\t\tErrInsufficientAllowance.Error(),\n\t\t\"should not be able to transfer more than approved\",\n\t)\n\n\t// Define a set of test data with spend amount and expected remaining allowance\n\ttests := []struct {\n\t\tspend uint64 // Spend amount\n\t\texp uint64 // Remaining allowance\n\t}{\n\t\t{3, 4999997},\n\t\t{999997, 4000000},\n\t\t{4000000, 0},\n\t}\n\n\t// perform transfer operation,and check if allowance and balance are correct\n\tfor _, tt := range tests {\n\t\tb0 := tok.BalanceOf(dest)\n\t\t// Perform transfer from owner to dest\n\t\turequire.NoError(t, tok.TransferFrom(owner, dest, tt.spend))\n\t\ta := tok.Allowance(owner, spender)\n\t\t// Check if allowance equals expected value\n\t\turequire.True(t, a == tt.exp, ufmt.Sprintf(\"allowance exp: %d,got %d\", tt.exp, a))\n\n\t\t// Get dest current balance\n\t\tb := tok.BalanceOf(dest)\n\t\t// Calculate expected balance ,should be initial balance plus transfer amount\n\t\texpB := b0 + tt.spend\n\t\t// Check if balance equals expected value\n\t\turequire.True(t, b == expB, ufmt.Sprintf(\"balance exp: %d,got %d\", expB, b))\n\t}\n\n\t// Try to transfer one token from owner to dest ,should fail because no allowance left\n\turequire.Error(t, tok.TransferFrom(owner, dest, 1), ErrInsufficientAllowance.Error(), \"no allowance\")\n}\n"},{"name":"types.gno","body":"package grc20\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/grc/exts\"\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)\n\ntype Token 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 the risk\n\t// that someone may use both the old and the new allowance by unfortunate\n\t// transaction ordering. One possible solution to mitigate this race\n\t// condition is to first reduce the spender's allowance to 0 and set the\n\t// 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\nconst (\n\tMintEvent = \"Mint\"\n\tBurnEvent = \"Burn\"\n\tTransferEvent = \"Transfer\"\n\tApprovalEvent = \"Approval\"\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":"grc721","path":"gno.land/p/demo/grc/grc721","files":[{"name":"basic_nft.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype basicNFT struct {\n\tname string\n\tsymbol string\n\towners avl.Tree // tokenId -\u003e OwnerAddress\n\tbalances avl.Tree // OwnerAddress -\u003e TokenCount\n\ttokenApprovals avl.Tree // TokenId -\u003e ApprovedAddress\n\ttokenURIs avl.Tree // TokenId -\u003e URIs\n\toperatorApprovals avl.Tree // \"OwnerAddress:OperatorAddress\" -\u003e bool\n}\n\n// Returns new basic NFT\nfunc NewBasicNFT(name string, symbol string) *basicNFT {\n\treturn \u0026basicNFT{\n\t\tname: name,\n\t\tsymbol: symbol,\n\n\t\towners: avl.Tree{},\n\t\tbalances: avl.Tree{},\n\t\ttokenApprovals: avl.Tree{},\n\t\ttokenURIs: avl.Tree{},\n\t\toperatorApprovals: avl.Tree{},\n\t}\n}\n\nfunc (s *basicNFT) Name() string { return s.name }\nfunc (s *basicNFT) Symbol() string { return s.symbol }\nfunc (s *basicNFT) TokenCount() uint64 { return uint64(s.owners.Size()) }\n\n// BalanceOf returns balance of input address\nfunc (s *basicNFT) BalanceOf(addr std.Address) (uint64, error) {\n\tif err := isValidAddress(addr); err != nil {\n\t\treturn 0, err\n\t}\n\n\tbalance, found := s.balances.Get(addr.String())\n\tif !found {\n\t\treturn 0, nil\n\t}\n\n\treturn balance.(uint64), nil\n}\n\n// OwnerOf returns owner of input token id\nfunc (s *basicNFT) OwnerOf(tid TokenID) (std.Address, error) {\n\towner, found := s.owners.Get(string(tid))\n\tif !found {\n\t\treturn \"\", ErrInvalidTokenId\n\t}\n\n\treturn owner.(std.Address), nil\n}\n\n// TokenURI returns the URI of input token id\nfunc (s *basicNFT) TokenURI(tid TokenID) (string, error) {\n\turi, found := s.tokenURIs.Get(string(tid))\n\tif !found {\n\t\treturn \"\", ErrInvalidTokenId\n\t}\n\n\treturn uri.(string), nil\n}\n\nfunc (s *basicNFT) SetTokenURI(tid TokenID, tURI TokenURI) (bool, error) {\n\t// check for invalid TokenID\n\tif !s.exists(tid) {\n\t\treturn false, ErrInvalidTokenId\n\t}\n\n\t// check for the right owner\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn false, ErrCallerIsNotOwner\n\t}\n\ts.tokenURIs.Set(string(tid), string(tURI))\n\treturn true, nil\n}\n\n// IsApprovedForAll returns true if operator is approved for all by the owner.\n// Otherwise, returns false\nfunc (s *basicNFT) IsApprovedForAll(owner, operator std.Address) bool {\n\tkey := owner.String() + \":\" + operator.String()\n\t_, found := s.operatorApprovals.Get(key)\n\tif !found {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// Approve approves the input address for particular token\nfunc (s *basicNFT) Approve(to std.Address, tid TokenID) error {\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner == to {\n\t\treturn ErrApprovalToCurrentOwner\n\t}\n\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner \u0026\u0026 !s.IsApprovedForAll(owner, caller) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\ts.tokenApprovals.Set(string(tid), to.String())\n\tevent := ApprovalEvent{owner, to, tid}\n\temit(\u0026event)\n\n\treturn nil\n}\n\n// GetApproved return the approved address for token\nfunc (s *basicNFT) GetApproved(tid TokenID) (std.Address, error) {\n\taddr, found := s.tokenApprovals.Get(string(tid))\n\tif !found {\n\t\treturn zeroAddress, ErrTokenIdNotHasApproved\n\t}\n\n\treturn std.Address(addr.(string)), nil\n}\n\n// SetApprovalForAll can approve the operator to operate on all tokens\nfunc (s *basicNFT) SetApprovalForAll(operator std.Address, approved bool) error {\n\tif err := isValidAddress(operator); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tcaller := std.PrevRealm().Addr()\n\treturn s.setApprovalForAll(caller, operator, approved)\n}\n\n// Safely transfers `tokenId` token from `from` to `to`, checking that\n// contract recipients are aware of the GRC721 protocol to prevent\n// tokens from being forever locked.\nfunc (s *basicNFT) SafeTransferFrom(from, to std.Address, tid TokenID) error {\n\tcaller := std.PrevRealm().Addr()\n\tif !s.isApprovedOrOwner(caller, tid) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.transfer(from, to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.checkOnGRC721Received(from, to, tid) {\n\t\treturn ErrTransferToNonGRC721Receiver\n\t}\n\n\treturn nil\n}\n\n// Transfers `tokenId` token from `from` to `to`.\nfunc (s *basicNFT) TransferFrom(from, to std.Address, tid TokenID) error {\n\tcaller := std.PrevRealm().Addr()\n\tif !s.isApprovedOrOwner(caller, tid) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.transfer(from, to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// Mints `tokenId` and transfers it to `to`.\nfunc (s *basicNFT) Mint(to std.Address, tid TokenID) error {\n\treturn s.mint(to, tid)\n}\n\n// Mints `tokenId` and transfers it to `to`. Also checks that\n// contract recipients are using GRC721 protocol\nfunc (s *basicNFT) SafeMint(to std.Address, tid TokenID) error {\n\terr := s.mint(to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.checkOnGRC721Received(zeroAddress, to, tid) {\n\t\treturn ErrTransferToNonGRC721Receiver\n\t}\n\n\treturn nil\n}\n\nfunc (s *basicNFT) Burn(tid TokenID) error {\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ts.beforeTokenTransfer(owner, zeroAddress, tid, 1)\n\n\ts.tokenApprovals.Remove(string(tid))\n\tbalance, err := s.BalanceOf(owner)\n\tif err != nil {\n\t\treturn err\n\t}\n\tbalance -= 1\n\ts.balances.Set(owner.String(), balance)\n\ts.owners.Remove(string(tid))\n\n\tevent := TransferEvent{owner, zeroAddress, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(owner, zeroAddress, tid, 1)\n\n\treturn nil\n}\n\n/* Helper methods */\n\n// Helper for SetApprovalForAll()\nfunc (s *basicNFT) setApprovalForAll(owner, operator std.Address, approved bool) error {\n\tif owner == operator {\n\t\treturn ErrApprovalToCurrentOwner\n\t}\n\n\tkey := owner.String() + \":\" + operator.String()\n\ts.operatorApprovals.Set(key, approved)\n\n\tevent := ApprovalForAllEvent{owner, operator, approved}\n\temit(\u0026event)\n\n\treturn nil\n}\n\n// Helper for TransferFrom() and SafeTransferFrom()\nfunc (s *basicNFT) transfer(from, to std.Address, tid TokenID) error {\n\tif err := isValidAddress(from); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\tif err := isValidAddress(to); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tif from == to {\n\t\treturn ErrCannotTransferToSelf\n\t}\n\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner != from {\n\t\treturn ErrTransferFromIncorrectOwner\n\t}\n\n\ts.beforeTokenTransfer(from, to, tid, 1)\n\n\t// Check that tokenId was not transferred by `beforeTokenTransfer`\n\towner, err = s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner != from {\n\t\treturn ErrTransferFromIncorrectOwner\n\t}\n\n\ts.tokenApprovals.Remove(string(tid))\n\tfromBalance, err := s.BalanceOf(from)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance, err := s.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfromBalance -= 1\n\ttoBalance += 1\n\ts.balances.Set(from.String(), fromBalance)\n\ts.balances.Set(to.String(), toBalance)\n\ts.owners.Set(string(tid), to)\n\n\tevent := TransferEvent{from, to, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(from, to, tid, 1)\n\n\treturn nil\n}\n\n// Helper for Mint() and SafeMint()\nfunc (s *basicNFT) mint(to std.Address, tid TokenID) error {\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\tif s.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ts.beforeTokenTransfer(zeroAddress, to, tid, 1)\n\n\t// Check that tokenId was not minted by `beforeTokenTransfer`\n\tif s.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ttoBalance, err := s.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance += 1\n\ts.balances.Set(to.String(), toBalance)\n\ts.owners.Set(string(tid), to)\n\n\tevent := TransferEvent{zeroAddress, to, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(zeroAddress, to, tid, 1)\n\n\treturn nil\n}\n\nfunc (s *basicNFT) isApprovedOrOwner(addr std.Address, tid TokenID) bool {\n\towner, found := s.owners.Get(string(tid))\n\tif !found {\n\t\treturn false\n\t}\n\n\tif addr == owner.(std.Address) || s.IsApprovedForAll(owner.(std.Address), addr) {\n\t\treturn true\n\t}\n\n\t_, err := s.GetApproved(tid)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// Checks if token id already exists\nfunc (s *basicNFT) exists(tid TokenID) bool {\n\t_, found := s.owners.Get(string(tid))\n\treturn found\n}\n\nfunc (s *basicNFT) beforeTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicNFT) afterTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicNFT) checkOnGRC721Received(from, to std.Address, tid TokenID) bool {\n\t// TODO: Implementation\n\treturn true\n}\n\nfunc (s *basicNFT) RenderHome() (str string) {\n\tstr += ufmt.Sprintf(\"# %s ($%s)\\n\\n\", s.name, s.symbol)\n\tstr += ufmt.Sprintf(\"* **Total supply**: %d\\n\", s.TokenCount())\n\tstr += ufmt.Sprintf(\"* **Known accounts**: %d\\n\", s.balances.Size())\n\n\treturn\n}\n"},{"name":"basic_nft_test.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\tdummyNFTName = \"DummyNFT\"\n\tdummyNFTSymbol = \"DNFT\"\n)\n\nfunc TestNewBasicNFT(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n}\n\nfunc TestName(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tname := dummy.Name()\n\tuassert.Equal(t, dummyNFTName, name)\n}\n\nfunc TestSymbol(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tsymbol := dummy.Symbol()\n\tuassert.Equal(t, dummyNFTSymbol, symbol)\n}\n\nfunc TestTokenCount(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcount := dummy.TokenCount()\n\tuassert.Equal(t, uint64(0), count)\n\n\tdummy.mint(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\", TokenID(\"1\"))\n\tdummy.mint(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\", TokenID(\"2\"))\n\n\tcount = dummy.TokenCount()\n\tuassert.Equal(t, uint64(2), count)\n}\n\nfunc TestBalanceOf(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\tbalanceAddr1, err := dummy.BalanceOf(addr1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(0), balanceAddr1)\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\tdummy.mint(addr1, TokenID(\"2\"))\n\tdummy.mint(addr2, TokenID(\"3\"))\n\n\tbalanceAddr1, err = dummy.BalanceOf(addr1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tbalanceAddr2, err := dummy.BalanceOf(addr2)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tuassert.Equal(t, uint64(2), balanceAddr1)\n\tuassert.Equal(t, uint64(1), balanceAddr2)\n}\n\nfunc TestOwnerOf(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\towner, err := dummy.OwnerOf(TokenID(\"invalid\"))\n\tuassert.Error(t, err, \"should not result in error\")\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\tdummy.mint(addr2, TokenID(\"2\"))\n\n\t// Checking for token id \"1\"\n\towner, err = dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr1.String(), owner.String())\n\n\t// Checking for token id \"2\"\n\towner, err = dummy.OwnerOf(TokenID(\"2\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr2.String(), owner.String())\n}\n\nfunc TestIsApprovedForAll(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(addr1, addr2)\n\tuassert.False(t, isApprovedForAll)\n}\n\nfunc TestSetApprovalForAll(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(caller, addr)\n\tuassert.False(t, isApprovedForAll)\n\n\terr := dummy.SetApprovalForAll(addr, true)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tisApprovedForAll = dummy.IsApprovedForAll(caller, addr)\n\tuassert.True(t, isApprovedForAll)\n}\n\nfunc TestGetApproved(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tapprovedAddr, err := dummy.GetApproved(TokenID(\"invalid\"))\n\tuassert.Error(t, err, \"should result in error\")\n}\n\nfunc TestApprove(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\n\t_, err := dummy.GetApproved(TokenID(\"1\"))\n\tuassert.Error(t, err, \"should result in error\")\n\n\terr = dummy.Approve(addr, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tapprovedAddr, err := dummy.GetApproved(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should result in error\")\n\tuassert.Equal(t, addr.String(), approvedAddr.String())\n}\n\nfunc TestTransferFrom(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\tdummy.mint(caller, TokenID(\"2\"))\n\n\terr := dummy.TransferFrom(caller, addr, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should result in error\")\n\n\t// Check balance of caller after transfer\n\tbalanceOfCaller, err := dummy.BalanceOf(caller)\n\tuassert.NoError(t, err, \"should result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfCaller)\n\n\t// Check balance of addr after transfer\n\tbalanceOfAddr, err := dummy.BalanceOf(addr)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfAddr)\n\n\t// Check Owner of transferred Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should result in error\")\n\tuassert.Equal(t, addr.String(), owner.String())\n}\n\nfunc TestSafeTransferFrom(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\tdummy.mint(caller, TokenID(\"2\"))\n\n\terr := dummy.SafeTransferFrom(caller, addr, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check balance of caller after transfer\n\tbalanceOfCaller, err := dummy.BalanceOf(caller)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfCaller)\n\n\t// Check balance of addr after transfer\n\tbalanceOfAddr, err := dummy.BalanceOf(addr)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfAddr)\n\n\t// Check Owner of transferred Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr.String(), owner.String())\n}\n\nfunc TestMint(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\terr := dummy.Mint(addr1, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.Mint(addr1, TokenID(\"2\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.Mint(addr2, TokenID(\"3\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Try minting duplicate token id\n\terr = dummy.Mint(addr2, TokenID(\"1\"))\n\tuassert.Error(t, err, \"should not result in error\")\n\n\t// Check Owner of Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr1.String(), owner.String())\n}\n\nfunc TestBurn(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(addr, TokenID(\"1\"))\n\tdummy.mint(addr, TokenID(\"2\"))\n\n\terr := dummy.Burn(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check Owner of Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.Error(t, err, \"should result in error\")\n}\n\nfunc TestSetTokenURI(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\ttokenURI := \"http://example.com/token\"\n\n\tstd.TestSetOriginCaller(std.Address(addr1)) // addr1\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\t_, derr := dummy.SetTokenURI(TokenID(\"1\"), TokenURI(tokenURI))\n\tuassert.NoError(t, derr, \"should not result in error\")\n\n\t// Test case: Invalid token ID\n\t_, err := dummy.SetTokenURI(TokenID(\"3\"), TokenURI(tokenURI))\n\tuassert.ErrorIs(t, err, ErrInvalidTokenId)\n\n\tstd.TestSetOriginCaller(std.Address(addr2)) // addr2\n\n\t_, cerr := dummy.SetTokenURI(TokenID(\"1\"), TokenURI(tokenURI)) // addr2 trying to set URI for token 1\n\tuassert.ErrorIs(t, cerr, ErrCallerIsNotOwner)\n\n\t// Test case: Retrieving TokenURI\n\tstd.TestSetOriginCaller(std.Address(addr1)) // addr1\n\n\tdummyTokenURI, err := dummy.TokenURI(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"TokenURI error\")\n\tuassert.Equal(t, string(tokenURI), string(dummyTokenURI))\n}\n"},{"name":"errors.gno","body":"package grc721\n\nimport \"errors\"\n\nvar (\n\tErrInvalidTokenId = errors.New(\"invalid token id\")\n\tErrInvalidAddress = errors.New(\"invalid address\")\n\tErrTokenIdNotHasApproved = errors.New(\"token id not approved for anyone\")\n\tErrApprovalToCurrentOwner = errors.New(\"approval to current owner\")\n\tErrCallerIsNotOwner = errors.New(\"caller is not token owner\")\n\tErrCallerNotApprovedForAll = errors.New(\"caller is not approved for all\")\n\tErrCannotTransferToSelf = errors.New(\"cannot send transfer to self\")\n\tErrTransferFromIncorrectOwner = errors.New(\"transfer from incorrect owner\")\n\tErrTransferToNonGRC721Receiver = errors.New(\"transfer to non GRC721Receiver implementer\")\n\tErrCallerIsNotOwnerOrApproved = errors.New(\"caller is not token owner or approved\")\n\tErrTokenIdAlreadyExists = errors.New(\"token id already exists\")\n\n\t// ERC721Royalty\n\tErrInvalidRoyaltyPercentage = errors.New(\"invalid royalty percentage\")\n\tErrInvalidRoyaltyPaymentAddress = errors.New(\"invalid royalty paymentAddress\")\n\tErrCannotCalculateRoyaltyAmount = errors.New(\"cannot calculate royalty amount\")\n)\n"},{"name":"grc721_metadata.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n// metadataNFT represents an NFT with metadata extensions.\ntype metadataNFT struct {\n\t*basicNFT // Embedded basicNFT struct for basic NFT functionality\n\textensions *avl.Tree // AVL tree for storing metadata extensions\n}\n\n// Ensure that metadataNFT implements the IGRC721MetadataOnchain interface.\nvar _ IGRC721MetadataOnchain = (*metadataNFT)(nil)\n\n// NewNFTWithMetadata creates a new basic NFT with metadata extensions.\nfunc NewNFTWithMetadata(name string, symbol string) *metadataNFT {\n\t// Create a new basic NFT\n\tnft := NewBasicNFT(name, symbol)\n\n\t// Return a metadataNFT with basicNFT embedded and an empty AVL tree for extensions\n\treturn \u0026metadataNFT{\n\t\tbasicNFT: nft,\n\t\textensions: avl.NewTree(),\n\t}\n}\n\n// SetTokenMetadata sets metadata for a given token ID.\nfunc (s *metadataNFT) SetTokenMetadata(tid TokenID, metadata Metadata) error {\n\t// Check if the caller is the owner of the token\n\towner, err := s.basicNFT.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn ErrCallerIsNotOwner\n\t}\n\n\t// Set the metadata for the token ID in the extensions AVL tree\n\ts.extensions.Set(string(tid), metadata)\n\treturn nil\n}\n\n// TokenMetadata retrieves metadata for a given token ID.\nfunc (s *metadataNFT) TokenMetadata(tid TokenID) (Metadata, error) {\n\t// Retrieve metadata from the extensions AVL tree\n\tmetadata, found := s.extensions.Get(string(tid))\n\tif !found {\n\t\treturn Metadata{}, ErrInvalidTokenId\n\t}\n\n\treturn metadata.(Metadata), nil\n}\n\n// mint mints a new token and assigns it to the specified address.\nfunc (s *metadataNFT) mint(to std.Address, tid TokenID) error {\n\t// Check if the address is valid\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\t// Check if the token ID already exists\n\tif s.basicNFT.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ts.basicNFT.beforeTokenTransfer(zeroAddress, to, tid, 1)\n\n\t// Check if the token ID was minted by beforeTokenTransfer\n\tif s.basicNFT.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\t// Increment balance of the recipient address\n\ttoBalance, err := s.basicNFT.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance += 1\n\ts.basicNFT.balances.Set(to.String(), toBalance)\n\n\t// Set owner of the token ID to the recipient address\n\ts.basicNFT.owners.Set(string(tid), to)\n\n\t// Emit transfer event\n\tevent := TransferEvent{zeroAddress, to, tid}\n\temit(\u0026event)\n\n\ts.basicNFT.afterTokenTransfer(zeroAddress, to, tid, 1)\n\n\treturn nil\n}\n"},{"name":"grc721_metadata_test.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestSetMetadata(t *testing.T) {\n\t// Create a new dummy NFT with metadata\n\tdummy := NewNFTWithMetadata(dummyNFTName, dummyNFTSymbol)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n\n\t// Define addresses for testing purposes\n\taddr1 := testutils.TestAddress(\"alice\")\n\taddr2 := testutils.TestAddress(\"bob\")\n\n\t// Define metadata attributes\n\tname := \"test\"\n\tdescription := \"test\"\n\timage := \"test\"\n\timageData := \"test\"\n\texternalURL := \"test\"\n\tattributes := []Trait{}\n\tbackgroundColor := \"test\"\n\tanimationURL := \"test\"\n\tyoutubeURL := \"test\"\n\n\t// Set the original caller to addr1\n\tstd.TestSetOriginCaller(addr1) // addr1\n\n\t// Mint a new token for addr1\n\tdummy.mint(addr1, TokenID(\"1\"))\n\n\t// Set metadata for token 1\n\tderr := dummy.SetTokenMetadata(TokenID(\"1\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if there was an error setting metadata\n\tuassert.NoError(t, derr, \"Should not result in error\")\n\n\t// Test case: Invalid token ID\n\terr := dummy.SetTokenMetadata(TokenID(\"3\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if the error returned matches the expected error\n\tuassert.ErrorIs(t, err, ErrInvalidTokenId)\n\n\t// Set the original caller to addr2\n\tstd.TestSetOriginCaller(addr2) // addr2\n\n\t// Try to set metadata for token 1 from addr2 (should fail)\n\tcerr := dummy.SetTokenMetadata(TokenID(\"1\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if the error returned matches the expected error\n\tuassert.ErrorIs(t, cerr, ErrCallerIsNotOwner)\n\n\t// Set the original caller back to addr1\n\tstd.TestSetOriginCaller(addr1) // addr1\n\n\t// Retrieve metadata for token 1\n\tdummyMetadata, err := dummy.TokenMetadata(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"Metadata error\")\n\n\t// Check if metadata attributes match expected values\n\tuassert.Equal(t, image, dummyMetadata.Image)\n\tuassert.Equal(t, imageData, dummyMetadata.ImageData)\n\tuassert.Equal(t, externalURL, dummyMetadata.ExternalURL)\n\tuassert.Equal(t, description, dummyMetadata.Description)\n\tuassert.Equal(t, name, dummyMetadata.Name)\n\tuassert.Equal(t, len(attributes), len(dummyMetadata.Attributes))\n\tuassert.Equal(t, backgroundColor, dummyMetadata.BackgroundColor)\n\tuassert.Equal(t, animationURL, dummyMetadata.AnimationURL)\n\tuassert.Equal(t, youtubeURL, dummyMetadata.YoutubeURL)\n}\n"},{"name":"grc721_royalty.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n// royaltyNFT represents a non-fungible token (NFT) with royalty functionality.\ntype royaltyNFT struct {\n\t*metadataNFT // Embedding metadataNFT for NFT functionality\n\ttokenRoyaltyInfo *avl.Tree // AVL tree to store royalty information for each token\n\tmaxRoyaltyPercentage uint64 // maxRoyaltyPercentage represents the maximum royalty percentage that can be charged every sale\n}\n\n// Ensure that royaltyNFT implements the IGRC2981 interface.\nvar _ IGRC2981 = (*royaltyNFT)(nil)\n\n// NewNFTWithRoyalty creates a new royalty NFT with the specified name, symbol, and royalty calculator.\nfunc NewNFTWithRoyalty(name string, symbol string) *royaltyNFT {\n\t// Create a new NFT with metadata\n\tnft := NewNFTWithMetadata(name, symbol)\n\n\treturn \u0026royaltyNFT{\n\t\tmetadataNFT: nft,\n\t\ttokenRoyaltyInfo: avl.NewTree(),\n\t\tmaxRoyaltyPercentage: 100,\n\t}\n}\n\n// SetTokenRoyalty sets the royalty information for a specific token ID.\nfunc (r *royaltyNFT) SetTokenRoyalty(tid TokenID, royaltyInfo RoyaltyInfo) error {\n\t// Validate the payment address\n\tif err := isValidAddress(royaltyInfo.PaymentAddress); err != nil {\n\t\treturn ErrInvalidRoyaltyPaymentAddress\n\t}\n\n\t// Check if royalty percentage exceeds maxRoyaltyPercentage\n\tif royaltyInfo.Percentage \u003e r.maxRoyaltyPercentage {\n\t\treturn ErrInvalidRoyaltyPercentage\n\t}\n\n\t// Check if the caller is the owner of the token\n\towner, err := r.metadataNFT.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn ErrCallerIsNotOwner\n\t}\n\n\t// Set royalty information for the token\n\tr.tokenRoyaltyInfo.Set(string(tid), royaltyInfo)\n\n\treturn nil\n}\n\n// RoyaltyInfo returns the royalty information for the given token ID and sale price.\nfunc (r *royaltyNFT) RoyaltyInfo(tid TokenID, salePrice uint64) (std.Address, uint64, error) {\n\t// Retrieve royalty information for the token\n\tval, found := r.tokenRoyaltyInfo.Get(string(tid))\n\tif !found {\n\t\treturn \"\", 0, ErrInvalidTokenId\n\t}\n\n\troyaltyInfo := val.(RoyaltyInfo)\n\n\t// Calculate royalty amount\n\troyaltyAmount, _ := r.calculateRoyaltyAmount(salePrice, royaltyInfo.Percentage)\n\n\treturn royaltyInfo.PaymentAddress, royaltyAmount, nil\n}\n\nfunc (r *royaltyNFT) calculateRoyaltyAmount(salePrice, percentage uint64) (uint64, error) {\n\troyaltyAmount := (salePrice * percentage) / 100\n\treturn royaltyAmount, nil\n}\n"},{"name":"grc721_royalty_test.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestSetTokenRoyalty(t *testing.T) {\n\tdummy := NewNFTWithRoyalty(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := testutils.TestAddress(\"alice\")\n\taddr2 := testutils.TestAddress(\"bob\")\n\n\tpaymentAddress := testutils.TestAddress(\"john\")\n\tpercentage := uint64(10) // 10%\n\n\tsalePrice := uint64(1000)\n\texpectRoyaltyAmount := uint64(100)\n\n\tstd.TestSetOriginCaller(addr1) // addr1\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\n\tderr := dummy.SetTokenRoyalty(TokenID(\"1\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tuassert.NoError(t, derr, \"Should not result in error\")\n\n\t// Test case: Invalid token ID\n\terr := dummy.SetTokenRoyalty(TokenID(\"3\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tuassert.ErrorIs(t, derr, ErrInvalidTokenId)\n\n\tstd.TestSetOriginCaller(addr2) // addr2\n\n\tcerr := dummy.SetTokenRoyalty(TokenID(\"1\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tuassert.ErrorIs(t, cerr, ErrCallerIsNotOwner)\n\n\t// Test case: Invalid payment address\n\taerr := dummy.SetTokenRoyalty(TokenID(\"4\"), RoyaltyInfo{\n\t\tPaymentAddress: std.Address(\"###\"), // invalid address\n\t\tPercentage: percentage,\n\t})\n\tuassert.ErrorIs(t, aerr, ErrInvalidRoyaltyPaymentAddress)\n\n\t// Test case: Invalid percentage\n\tperr := dummy.SetTokenRoyalty(TokenID(\"5\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: uint64(200), // over maxRoyaltyPercentage\n\t})\n\tuassert.ErrorIs(t, perr, ErrInvalidRoyaltyPercentage)\n\n\t// Test case: Retrieving Royalty Info\n\tstd.TestSetOriginCaller(addr1) // addr1\n\n\tdummyPaymentAddress, dummyRoyaltyAmount, rerr := dummy.RoyaltyInfo(TokenID(\"1\"), salePrice)\n\tuassert.NoError(t, rerr, \"RoyaltyInfo error\")\n\tuassert.Equal(t, paymentAddress, dummyPaymentAddress)\n\tuassert.Equal(t, expectRoyaltyAmount, dummyRoyaltyAmount)\n}\n"},{"name":"igrc721.gno","body":"package grc721\n\nimport \"std\"\n\ntype IGRC721 interface {\n\tBalanceOf(owner std.Address) (uint64, error)\n\tOwnerOf(tid TokenID) (std.Address, error)\n\tSetTokenURI(tid TokenID, tURI TokenURI) (bool, error)\n\tSafeTransferFrom(from, to std.Address, tid TokenID) error\n\tTransferFrom(from, to std.Address, tid TokenID) error\n\tApprove(approved std.Address, tid TokenID) error\n\tSetApprovalForAll(operator std.Address, approved bool) error\n\tGetApproved(tid TokenID) (std.Address, error)\n\tIsApprovedForAll(owner, operator std.Address) bool\n}\n\ntype (\n\tTokenID string\n\tTokenURI string\n)\n\ntype TransferEvent struct {\n\tFrom std.Address\n\tTo std.Address\n\tTokenID TokenID\n}\n\ntype ApprovalEvent struct {\n\tOwner std.Address\n\tApproved std.Address\n\tTokenID TokenID\n}\n\ntype ApprovalForAllEvent struct {\n\tOwner std.Address\n\tOperator std.Address\n\tApproved bool\n}\n"},{"name":"igrc721_metadata.gno","body":"package grc721\n\n// IGRC721CollectionMetadata describes basic information about an NFT collection.\ntype IGRC721CollectionMetadata interface {\n\tName() string // Name returns the name of the collection.\n\tSymbol() string // Symbol returns the symbol of the collection.\n}\n\n// IGRC721Metadata follows the Ethereum standard\ntype IGRC721Metadata interface {\n\tIGRC721CollectionMetadata\n\tTokenURI(tid TokenID) (string, error) // TokenURI returns the URI of a specific token.\n}\n\n// IGRC721Metadata follows the OpenSea metadata standard\ntype IGRC721MetadataOnchain interface {\n\tIGRC721CollectionMetadata\n\tTokenMetadata(tid TokenID) (Metadata, error)\n}\n\ntype Trait struct {\n\tDisplayType string\n\tTraitType string\n\tValue string\n}\n\n// see: https://docs.opensea.io/docs/metadata-standards\ntype Metadata struct {\n\tImage string // URL to the image of the item. Can be any type of image (including SVGs, which will be cached into PNGs by OpenSea), IPFS or Arweave URLs or paths. We recommend using a minimum 3000 x 3000 image.\n\tImageData string // Raw SVG image data, if you want to generate images on the fly (not recommended). Only use this if you're not including the image parameter.\n\tExternalURL string // URL that will appear below the asset's image on OpenSea and will allow users to leave OpenSea and view the item on your site.\n\tDescription string // Human-readable description of the item. Markdown is supported.\n\tName string // Name of the item.\n\tAttributes []Trait // Attributes for the item, which will show up on the OpenSea page for the item.\n\tBackgroundColor string // Background color of the item on OpenSea. Must be a six-character hexadecimal without a pre-pended #\n\tAnimationURL string // URL to a multimedia attachment for the item. Supported file extensions: GLTF, GLB, WEBM, MP4, M4V, OGV, OGG, MP3, WAV, OGA, HTML (for rich experiences and interactive NFTs using JavaScript canvas, WebGL, etc.). Scripts and relative paths within the HTML page are now supported. Access to browser extensions is not supported.\n\tYoutubeURL string // URL to a YouTube video (only used if animation_url is not provided).\n}\n"},{"name":"igrc721_royalty.gno","body":"package grc721\n\nimport \"std\"\n\n// IGRC2981 follows the Ethereum standard\ntype IGRC2981 interface {\n\t// RoyaltyInfo retrieves royalty information for a tokenID and salePrice.\n\t// It returns the payment address, royalty amount, and an error if any.\n\tRoyaltyInfo(tokenID TokenID, salePrice uint64) (std.Address, uint64, error)\n}\n\n// RoyaltyInfo represents royalty information for a token.\ntype RoyaltyInfo struct {\n\tPaymentAddress std.Address // PaymentAddress is the address where royalty payment should be sent.\n\tPercentage uint64 // Percentage is the royalty percentage. It indicates the percentage of royalty to be paid for each sale. For example : Percentage = 10 =\u003e 10%\n}\n"},{"name":"util.gno","body":"package grc721\n\nimport (\n\t\"std\"\n)\n\nvar zeroAddress = std.Address(\"\")\n\nfunc isValidAddress(addr std.Address) error {\n\tif !addr.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\treturn nil\n}\n\nfunc emit(event interface{}) {\n\t// TODO: setup a pubsub system here?\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":"banker.gno","body":"package grc20\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Banker implements a token banker with admin privileges.\n//\n// The Banker is intended to be used in two main ways:\n// 1. as a temporary object used to make the initial minting, then deleted.\n// 2. preserved in an unexported variable to support conditional administrative\n// tasks protected by the contract.\ntype Banker struct {\n\tname string\n\tsymbol string\n\tdecimals uint\n\ttotalSupply uint64\n\tbalances avl.Tree // std.Address(owner) -\u003e uint64\n\tallowances avl.Tree // string(owner+\":\"+spender) -\u003e uint64\n\ttoken *token // to share the same pointer\n}\n\nfunc NewBanker(name, symbol string, decimals uint) *Banker {\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\tb := Banker{\n\t\tname: name,\n\t\tsymbol: symbol,\n\t\tdecimals: decimals,\n\t}\n\tt := \u0026token{banker: \u0026b}\n\tb.token = t\n\treturn \u0026b\n}\n\nfunc (b Banker) Token() Token { return b.token } // Token returns a grc20 safe-object implementation.\nfunc (b Banker) GetName() string { return b.name }\nfunc (b Banker) GetSymbol() string { return b.symbol }\nfunc (b Banker) GetDecimals() uint { return b.decimals }\nfunc (b Banker) TotalSupply() uint64 { return b.totalSupply }\nfunc (b Banker) KnownAccounts() int { return b.balances.Size() }\n\nfunc (b *Banker) Mint(address std.Address, amount uint64) error {\n\tif !address.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\n\t// TODO: check for overflow\n\n\tb.totalSupply += amount\n\tcurrentBalance := b.BalanceOf(address)\n\tnewBalance := currentBalance + amount\n\n\tb.balances.Set(string(address), newBalance)\n\n\tstd.Emit(\n\t\tMintEvent,\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\nfunc (b *Banker) Burn(address std.Address, amount uint64) error {\n\tif !address.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\t// TODO: check for overflow\n\n\tcurrentBalance := b.BalanceOf(address)\n\tif currentBalance \u003c amount {\n\t\treturn ErrInsufficientBalance\n\t}\n\n\tb.totalSupply -= amount\n\tnewBalance := currentBalance - amount\n\n\tb.balances.Set(string(address), newBalance)\n\n\tstd.Emit(\n\t\tBurnEvent,\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\nfunc (b Banker) BalanceOf(address std.Address) uint64 {\n\tbalance, found := b.balances.Get(address.String())\n\tif !found {\n\t\treturn 0\n\t}\n\treturn balance.(uint64)\n}\n\nfunc (b *Banker) 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 := b.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\tb.allowances.Remove(key)\n\t} else {\n\t\tb.allowances.Set(key, newAllowance)\n\t}\n\n\treturn nil\n}\n\nfunc (b *Banker) 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\ttoBalance := b.BalanceOf(to)\n\tfromBalance := b.BalanceOf(from)\n\n\tif fromBalance \u003c amount {\n\t\treturn ErrInsufficientBalance\n\t}\n\n\tnewToBalance := toBalance + amount\n\tnewFromBalance := fromBalance - amount\n\n\tb.balances.Set(string(to), newToBalance)\n\tb.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\nfunc (b *Banker) TransferFrom(spender, from, to std.Address, amount uint64) error {\n\tif err := b.SpendAllowance(from, spender, amount); err != nil {\n\t\treturn err\n\t}\n\treturn b.Transfer(from, to, amount)\n}\n\nfunc (b *Banker) Allowance(owner, spender std.Address) uint64 {\n\tallowance, found := b.allowances.Get(allowanceKey(owner, spender))\n\tif !found {\n\t\treturn 0\n\t}\n\treturn allowance.(uint64)\n}\n\nfunc (b *Banker) Approve(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\tb.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\nfunc (b *Banker) RenderHome() string {\n\tstr := \"\"\n\tstr += ufmt.Sprintf(\"# %s ($%s)\\n\\n\", b.name, b.symbol)\n\tstr += ufmt.Sprintf(\"* **Decimals**: %d\\n\", b.decimals)\n\tstr += ufmt.Sprintf(\"* **Total supply**: %d\\n\", b.totalSupply)\n\tstr += ufmt.Sprintf(\"* **Known accounts**: %d\\n\", b.KnownAccounts())\n\treturn str\n}\n\nfunc allowanceKey(owner, spender std.Address) string {\n\treturn owner.String() + \":\" + spender.String()\n}\n"},{"name":"banker_test.gno","body":"package grc20\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestBankerImpl(t *testing.T) {\n\tdummy := NewBanker(\"Dummy\", \"DUMMY\", 4)\n\turequire.False(t, dummy == nil, \"dummy should not be nil\")\n}\n\nfunc TestAllowance(t *testing.T) {\n\tvar (\n\t\towner = testutils.TestAddress(\"owner\")\n\t\tspender = testutils.TestAddress(\"spender\")\n\t\tdest = testutils.TestAddress(\"dest\")\n\t)\n\n\tb := NewBanker(\"Dummy\", \"DUMMY\", 6)\n\turequire.NoError(t, b.Mint(owner, 100000000))\n\turequire.NoError(t, b.Approve(owner, spender, 5000000))\n\turequire.Error(t, b.TransferFrom(spender, owner, dest, 10000000), ErrInsufficientAllowance.Error(), \"should not be able to transfer more than approved\")\n\n\ttests := []struct {\n\t\tspend uint64\n\t\texp uint64\n\t}{\n\t\t{3, 4999997},\n\t\t{999997, 4000000},\n\t\t{4000000, 0},\n\t}\n\n\tfor _, tt := range tests {\n\t\tb0 := b.BalanceOf(dest)\n\t\turequire.NoError(t, b.TransferFrom(spender, owner, dest, tt.spend))\n\t\ta := b.Allowance(owner, spender)\n\t\turequire.Equal(t, a, tt.exp, ufmt.Sprintf(\"allowance exp: %d, got %d\", tt.exp, a))\n\t\tb := b.BalanceOf(dest)\n\t\texpB := b0 + tt.spend\n\t\turequire.Equal(t, b, expB, ufmt.Sprintf(\"balance exp: %d, got %d\", expB, b))\n\t}\n\n\turequire.Error(t, b.TransferFrom(spender, owner, dest, 1), \"no allowance\")\n\tkey := allowanceKey(owner, spender)\n\turequire.False(t, b.allowances.Has(key), \"allowance should be removed\")\n\turequire.Equal(t, b.Allowance(owner, spender), uint64(0), \"allowance should be 0\")\n}\n"},{"name":"token.gno","body":"package grc20\n\nimport (\n\t\"std\"\n)\n\n// token implements the Token interface.\n//\n// It is generated with Banker.Token().\n// It can safely be exposed publicly.\ntype token struct {\n\tbanker *Banker\n}\n\n// var _ Token = (*token)(nil)\nfunc (t *token) GetName() string { return t.banker.name }\nfunc (t *token) GetSymbol() string { return t.banker.symbol }\nfunc (t *token) GetDecimals() uint { return t.banker.decimals }\nfunc (t *token) TotalSupply() uint64 { return t.banker.totalSupply }\n\nfunc (t *token) BalanceOf(owner std.Address) uint64 {\n\treturn t.banker.BalanceOf(owner)\n}\n\nfunc (t *token) Transfer(to std.Address, amount uint64) error {\n\tcaller := std.PreviousRealm().Addr()\n\treturn t.banker.Transfer(caller, to, amount)\n}\n\nfunc (t *token) Allowance(owner, spender std.Address) uint64 {\n\treturn t.banker.Allowance(owner, spender)\n}\n\nfunc (t *token) Approve(spender std.Address, amount uint64) error {\n\tcaller := std.PreviousRealm().Addr()\n\treturn t.banker.Approve(caller, spender, amount)\n}\n\nfunc (t *token) TransferFrom(from, to std.Address, amount uint64) error {\n\tspender := std.PreviousRealm().Addr()\n\tif err := t.banker.SpendAllowance(from, spender, amount); err != nil {\n\t\treturn err\n\t}\n\treturn t.banker.Transfer(from, to, amount)\n}\n"},{"name":"token_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/ufmt\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestUserTokenImpl(t *testing.T) {\n\tbank := NewBanker(\"Dummy\", \"DUMMY\", 4)\n\ttok := bank.Token()\n\t_ = tok\n}\n\nfunc TestUserApprove(t *testing.T) {\n\towner := testutils.TestAddress(\"owner\")\n\tspender := testutils.TestAddress(\"spender\")\n\tdest := testutils.TestAddress(\"dest\")\n\n\tbank := NewBanker(\"Dummy\", \"DUMMY\", 6)\n\ttok := bank.Token()\n\n\t// Set owner as the original caller\n\tstd.TestSetOriginCaller(owner)\n\t// Mint 100000000 tokens for owner\n\turequire.NoError(t, bank.Mint(owner, 100000000))\n\n\t// Approve spender to spend 5000000 tokens\n\turequire.NoError(t, tok.Approve(spender, 5000000))\n\n\t// Set spender as the original caller\n\tstd.TestSetOriginCaller(spender)\n\t// Try to transfer 10000000 tokens from owner to dest, should fail because it exceeds allowance\n\turequire.Error(t,\n\t\ttok.TransferFrom(owner, dest, 10000000),\n\t\tErrInsufficientAllowance.Error(),\n\t\t\"should not be able to transfer more than approved\",\n\t)\n\n\t// Define a set of test data with spend amount and expected remaining allowance\n\ttests := []struct {\n\t\tspend uint64 // Spend amount\n\t\texp uint64 // Remaining allowance\n\t}{\n\t\t{3, 4999997},\n\t\t{999997, 4000000},\n\t\t{4000000, 0},\n\t}\n\n\t// perform transfer operation,and check if allowance and balance are correct\n\tfor _, tt := range tests {\n\t\tb0 := tok.BalanceOf(dest)\n\t\t// Perform transfer from owner to dest\n\t\turequire.NoError(t, tok.TransferFrom(owner, dest, tt.spend))\n\t\ta := tok.Allowance(owner, spender)\n\t\t// Check if allowance equals expected value\n\t\turequire.True(t, a == tt.exp, ufmt.Sprintf(\"allowance exp: %d,got %d\", tt.exp, a))\n\n\t\t// Get dest current balance\n\t\tb := tok.BalanceOf(dest)\n\t\t// Calculate expected balance ,should be initial balance plus transfer amount\n\t\texpB := b0 + tt.spend\n\t\t// Check if balance equals expected value\n\t\turequire.True(t, b == expB, ufmt.Sprintf(\"balance exp: %d,got %d\", expB, b))\n\t}\n\n\t// Try to transfer one token from owner to dest ,should fail because no allowance left\n\turequire.Error(t, tok.TransferFrom(owner, dest, 1), ErrInsufficientAllowance.Error(), \"no allowance\")\n}\n"},{"name":"types.gno","body":"package grc20\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/grc/exts\"\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)\n\ntype Token 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 the risk\n\t// that someone may use both the old and the new allowance by unfortunate\n\t// transaction ordering. One possible solution to mitigate this race\n\t// condition is to first reduce the spender's allowance to 0 and set the\n\t// 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\nconst (\n\tMintEvent = \"Mint\"\n\tBurnEvent = \"Burn\"\n\tTransferEvent = \"Transfer\"\n\tApprovalEvent = \"Approval\"\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":"grc721","path":"gno.land/p/demo/grc/grc721","files":[{"name":"basic_nft.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype basicNFT struct {\n\tname string\n\tsymbol string\n\towners avl.Tree // tokenId -\u003e OwnerAddress\n\tbalances avl.Tree // OwnerAddress -\u003e TokenCount\n\ttokenApprovals avl.Tree // TokenId -\u003e ApprovedAddress\n\ttokenURIs avl.Tree // TokenId -\u003e URIs\n\toperatorApprovals avl.Tree // \"OwnerAddress:OperatorAddress\" -\u003e bool\n}\n\n// Returns new basic NFT\nfunc NewBasicNFT(name string, symbol string) *basicNFT {\n\treturn \u0026basicNFT{\n\t\tname: name,\n\t\tsymbol: symbol,\n\n\t\towners: avl.Tree{},\n\t\tbalances: avl.Tree{},\n\t\ttokenApprovals: avl.Tree{},\n\t\ttokenURIs: avl.Tree{},\n\t\toperatorApprovals: avl.Tree{},\n\t}\n}\n\nfunc (s *basicNFT) Name() string { return s.name }\nfunc (s *basicNFT) Symbol() string { return s.symbol }\nfunc (s *basicNFT) TokenCount() uint64 { return uint64(s.owners.Size()) }\n\n// BalanceOf returns balance of input address\nfunc (s *basicNFT) BalanceOf(addr std.Address) (uint64, error) {\n\tif err := isValidAddress(addr); err != nil {\n\t\treturn 0, err\n\t}\n\n\tbalance, found := s.balances.Get(addr.String())\n\tif !found {\n\t\treturn 0, nil\n\t}\n\n\treturn balance.(uint64), nil\n}\n\n// OwnerOf returns owner of input token id\nfunc (s *basicNFT) OwnerOf(tid TokenID) (std.Address, error) {\n\towner, found := s.owners.Get(string(tid))\n\tif !found {\n\t\treturn \"\", ErrInvalidTokenId\n\t}\n\n\treturn owner.(std.Address), nil\n}\n\n// TokenURI returns the URI of input token id\nfunc (s *basicNFT) TokenURI(tid TokenID) (string, error) {\n\turi, found := s.tokenURIs.Get(string(tid))\n\tif !found {\n\t\treturn \"\", ErrInvalidTokenId\n\t}\n\n\treturn uri.(string), nil\n}\n\nfunc (s *basicNFT) SetTokenURI(tid TokenID, tURI TokenURI) (bool, error) {\n\t// check for invalid TokenID\n\tif !s.exists(tid) {\n\t\treturn false, ErrInvalidTokenId\n\t}\n\n\t// check for the right owner\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tcaller := std.PreviousRealm().Addr()\n\tif caller != owner {\n\t\treturn false, ErrCallerIsNotOwner\n\t}\n\ts.tokenURIs.Set(string(tid), string(tURI))\n\treturn true, nil\n}\n\n// IsApprovedForAll returns true if operator is approved for all by the owner.\n// Otherwise, returns false\nfunc (s *basicNFT) IsApprovedForAll(owner, operator std.Address) bool {\n\tkey := owner.String() + \":\" + operator.String()\n\t_, found := s.operatorApprovals.Get(key)\n\tif !found {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// Approve approves the input address for particular token\nfunc (s *basicNFT) Approve(to std.Address, tid TokenID) error {\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner == to {\n\t\treturn ErrApprovalToCurrentOwner\n\t}\n\n\tcaller := std.PreviousRealm().Addr()\n\tif caller != owner \u0026\u0026 !s.IsApprovedForAll(owner, caller) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\ts.tokenApprovals.Set(string(tid), to.String())\n\tevent := ApprovalEvent{owner, to, tid}\n\temit(\u0026event)\n\n\treturn nil\n}\n\n// GetApproved return the approved address for token\nfunc (s *basicNFT) GetApproved(tid TokenID) (std.Address, error) {\n\taddr, found := s.tokenApprovals.Get(string(tid))\n\tif !found {\n\t\treturn zeroAddress, ErrTokenIdNotHasApproved\n\t}\n\n\treturn std.Address(addr.(string)), nil\n}\n\n// SetApprovalForAll can approve the operator to operate on all tokens\nfunc (s *basicNFT) SetApprovalForAll(operator std.Address, approved bool) error {\n\tif err := isValidAddress(operator); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tcaller := std.PreviousRealm().Addr()\n\treturn s.setApprovalForAll(caller, operator, approved)\n}\n\n// Safely transfers `tokenId` token from `from` to `to`, checking that\n// contract recipients are aware of the GRC721 protocol to prevent\n// tokens from being forever locked.\nfunc (s *basicNFT) SafeTransferFrom(from, to std.Address, tid TokenID) error {\n\tcaller := std.PreviousRealm().Addr()\n\tif !s.isApprovedOrOwner(caller, tid) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.transfer(from, to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.checkOnGRC721Received(from, to, tid) {\n\t\treturn ErrTransferToNonGRC721Receiver\n\t}\n\n\treturn nil\n}\n\n// Transfers `tokenId` token from `from` to `to`.\nfunc (s *basicNFT) TransferFrom(from, to std.Address, tid TokenID) error {\n\tcaller := std.PreviousRealm().Addr()\n\tif !s.isApprovedOrOwner(caller, tid) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.transfer(from, to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// Mints `tokenId` and transfers it to `to`.\nfunc (s *basicNFT) Mint(to std.Address, tid TokenID) error {\n\treturn s.mint(to, tid)\n}\n\n// Mints `tokenId` and transfers it to `to`. Also checks that\n// contract recipients are using GRC721 protocol\nfunc (s *basicNFT) SafeMint(to std.Address, tid TokenID) error {\n\terr := s.mint(to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.checkOnGRC721Received(zeroAddress, to, tid) {\n\t\treturn ErrTransferToNonGRC721Receiver\n\t}\n\n\treturn nil\n}\n\nfunc (s *basicNFT) Burn(tid TokenID) error {\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ts.beforeTokenTransfer(owner, zeroAddress, tid, 1)\n\n\ts.tokenApprovals.Remove(string(tid))\n\tbalance, err := s.BalanceOf(owner)\n\tif err != nil {\n\t\treturn err\n\t}\n\tbalance -= 1\n\ts.balances.Set(owner.String(), balance)\n\ts.owners.Remove(string(tid))\n\n\tevent := TransferEvent{owner, zeroAddress, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(owner, zeroAddress, tid, 1)\n\n\treturn nil\n}\n\n/* Helper methods */\n\n// Helper for SetApprovalForAll()\nfunc (s *basicNFT) setApprovalForAll(owner, operator std.Address, approved bool) error {\n\tif owner == operator {\n\t\treturn ErrApprovalToCurrentOwner\n\t}\n\n\tkey := owner.String() + \":\" + operator.String()\n\ts.operatorApprovals.Set(key, approved)\n\n\tevent := ApprovalForAllEvent{owner, operator, approved}\n\temit(\u0026event)\n\n\treturn nil\n}\n\n// Helper for TransferFrom() and SafeTransferFrom()\nfunc (s *basicNFT) transfer(from, to std.Address, tid TokenID) error {\n\tif err := isValidAddress(from); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\tif err := isValidAddress(to); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tif from == to {\n\t\treturn ErrCannotTransferToSelf\n\t}\n\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner != from {\n\t\treturn ErrTransferFromIncorrectOwner\n\t}\n\n\ts.beforeTokenTransfer(from, to, tid, 1)\n\n\t// Check that tokenId was not transferred by `beforeTokenTransfer`\n\towner, err = s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner != from {\n\t\treturn ErrTransferFromIncorrectOwner\n\t}\n\n\ts.tokenApprovals.Remove(string(tid))\n\tfromBalance, err := s.BalanceOf(from)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance, err := s.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfromBalance -= 1\n\ttoBalance += 1\n\ts.balances.Set(from.String(), fromBalance)\n\ts.balances.Set(to.String(), toBalance)\n\ts.owners.Set(string(tid), to)\n\n\tevent := TransferEvent{from, to, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(from, to, tid, 1)\n\n\treturn nil\n}\n\n// Helper for Mint() and SafeMint()\nfunc (s *basicNFT) mint(to std.Address, tid TokenID) error {\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\tif s.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ts.beforeTokenTransfer(zeroAddress, to, tid, 1)\n\n\t// Check that tokenId was not minted by `beforeTokenTransfer`\n\tif s.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ttoBalance, err := s.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance += 1\n\ts.balances.Set(to.String(), toBalance)\n\ts.owners.Set(string(tid), to)\n\n\tevent := TransferEvent{zeroAddress, to, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(zeroAddress, to, tid, 1)\n\n\treturn nil\n}\n\nfunc (s *basicNFT) isApprovedOrOwner(addr std.Address, tid TokenID) bool {\n\towner, found := s.owners.Get(string(tid))\n\tif !found {\n\t\treturn false\n\t}\n\n\tif addr == owner.(std.Address) || s.IsApprovedForAll(owner.(std.Address), addr) {\n\t\treturn true\n\t}\n\n\t_, err := s.GetApproved(tid)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// Checks if token id already exists\nfunc (s *basicNFT) exists(tid TokenID) bool {\n\t_, found := s.owners.Get(string(tid))\n\treturn found\n}\n\nfunc (s *basicNFT) beforeTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicNFT) afterTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicNFT) checkOnGRC721Received(from, to std.Address, tid TokenID) bool {\n\t// TODO: Implementation\n\treturn true\n}\n\nfunc (s *basicNFT) RenderHome() (str string) {\n\tstr += ufmt.Sprintf(\"# %s ($%s)\\n\\n\", s.name, s.symbol)\n\tstr += ufmt.Sprintf(\"* **Total supply**: %d\\n\", s.TokenCount())\n\tstr += ufmt.Sprintf(\"* **Known accounts**: %d\\n\", s.balances.Size())\n\n\treturn\n}\n"},{"name":"basic_nft_test.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\tdummyNFTName = \"DummyNFT\"\n\tdummyNFTSymbol = \"DNFT\"\n)\n\nfunc TestNewBasicNFT(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n}\n\nfunc TestName(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tname := dummy.Name()\n\tuassert.Equal(t, dummyNFTName, name)\n}\n\nfunc TestSymbol(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tsymbol := dummy.Symbol()\n\tuassert.Equal(t, dummyNFTSymbol, symbol)\n}\n\nfunc TestTokenCount(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcount := dummy.TokenCount()\n\tuassert.Equal(t, uint64(0), count)\n\n\tdummy.mint(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\", TokenID(\"1\"))\n\tdummy.mint(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\", TokenID(\"2\"))\n\n\tcount = dummy.TokenCount()\n\tuassert.Equal(t, uint64(2), count)\n}\n\nfunc TestBalanceOf(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\tbalanceAddr1, err := dummy.BalanceOf(addr1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(0), balanceAddr1)\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\tdummy.mint(addr1, TokenID(\"2\"))\n\tdummy.mint(addr2, TokenID(\"3\"))\n\n\tbalanceAddr1, err = dummy.BalanceOf(addr1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tbalanceAddr2, err := dummy.BalanceOf(addr2)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tuassert.Equal(t, uint64(2), balanceAddr1)\n\tuassert.Equal(t, uint64(1), balanceAddr2)\n}\n\nfunc TestOwnerOf(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\towner, err := dummy.OwnerOf(TokenID(\"invalid\"))\n\tuassert.Error(t, err, \"should not result in error\")\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\tdummy.mint(addr2, TokenID(\"2\"))\n\n\t// Checking for token id \"1\"\n\towner, err = dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr1.String(), owner.String())\n\n\t// Checking for token id \"2\"\n\towner, err = dummy.OwnerOf(TokenID(\"2\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr2.String(), owner.String())\n}\n\nfunc TestIsApprovedForAll(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(addr1, addr2)\n\tuassert.False(t, isApprovedForAll)\n}\n\nfunc TestSetApprovalForAll(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PreviousRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(caller, addr)\n\tuassert.False(t, isApprovedForAll)\n\n\terr := dummy.SetApprovalForAll(addr, true)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tisApprovedForAll = dummy.IsApprovedForAll(caller, addr)\n\tuassert.True(t, isApprovedForAll)\n}\n\nfunc TestGetApproved(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tapprovedAddr, err := dummy.GetApproved(TokenID(\"invalid\"))\n\tuassert.Error(t, err, \"should result in error\")\n}\n\nfunc TestApprove(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PreviousRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\n\t_, err := dummy.GetApproved(TokenID(\"1\"))\n\tuassert.Error(t, err, \"should result in error\")\n\n\terr = dummy.Approve(addr, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tapprovedAddr, err := dummy.GetApproved(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should result in error\")\n\tuassert.Equal(t, addr.String(), approvedAddr.String())\n}\n\nfunc TestTransferFrom(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PreviousRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\tdummy.mint(caller, TokenID(\"2\"))\n\n\terr := dummy.TransferFrom(caller, addr, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should result in error\")\n\n\t// Check balance of caller after transfer\n\tbalanceOfCaller, err := dummy.BalanceOf(caller)\n\tuassert.NoError(t, err, \"should result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfCaller)\n\n\t// Check balance of addr after transfer\n\tbalanceOfAddr, err := dummy.BalanceOf(addr)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfAddr)\n\n\t// Check Owner of transferred Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should result in error\")\n\tuassert.Equal(t, addr.String(), owner.String())\n}\n\nfunc TestSafeTransferFrom(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PreviousRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\tdummy.mint(caller, TokenID(\"2\"))\n\n\terr := dummy.SafeTransferFrom(caller, addr, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check balance of caller after transfer\n\tbalanceOfCaller, err := dummy.BalanceOf(caller)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfCaller)\n\n\t// Check balance of addr after transfer\n\tbalanceOfAddr, err := dummy.BalanceOf(addr)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfAddr)\n\n\t// Check Owner of transferred Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr.String(), owner.String())\n}\n\nfunc TestMint(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\terr := dummy.Mint(addr1, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.Mint(addr1, TokenID(\"2\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.Mint(addr2, TokenID(\"3\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Try minting duplicate token id\n\terr = dummy.Mint(addr2, TokenID(\"1\"))\n\tuassert.Error(t, err, \"should not result in error\")\n\n\t// Check Owner of Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr1.String(), owner.String())\n}\n\nfunc TestBurn(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(addr, TokenID(\"1\"))\n\tdummy.mint(addr, TokenID(\"2\"))\n\n\terr := dummy.Burn(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check Owner of Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.Error(t, err, \"should result in error\")\n}\n\nfunc TestSetTokenURI(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\ttokenURI := \"http://example.com/token\"\n\n\tstd.TestSetOriginCaller(std.Address(addr1)) // addr1\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\t_, derr := dummy.SetTokenURI(TokenID(\"1\"), TokenURI(tokenURI))\n\tuassert.NoError(t, derr, \"should not result in error\")\n\n\t// Test case: Invalid token ID\n\t_, err := dummy.SetTokenURI(TokenID(\"3\"), TokenURI(tokenURI))\n\tuassert.ErrorIs(t, err, ErrInvalidTokenId)\n\n\tstd.TestSetOriginCaller(std.Address(addr2)) // addr2\n\n\t_, cerr := dummy.SetTokenURI(TokenID(\"1\"), TokenURI(tokenURI)) // addr2 trying to set URI for token 1\n\tuassert.ErrorIs(t, cerr, ErrCallerIsNotOwner)\n\n\t// Test case: Retrieving TokenURI\n\tstd.TestSetOriginCaller(std.Address(addr1)) // addr1\n\n\tdummyTokenURI, err := dummy.TokenURI(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"TokenURI error\")\n\tuassert.Equal(t, string(tokenURI), string(dummyTokenURI))\n}\n"},{"name":"errors.gno","body":"package grc721\n\nimport \"errors\"\n\nvar (\n\tErrInvalidTokenId = errors.New(\"invalid token id\")\n\tErrInvalidAddress = errors.New(\"invalid address\")\n\tErrTokenIdNotHasApproved = errors.New(\"token id not approved for anyone\")\n\tErrApprovalToCurrentOwner = errors.New(\"approval to current owner\")\n\tErrCallerIsNotOwner = errors.New(\"caller is not token owner\")\n\tErrCallerNotApprovedForAll = errors.New(\"caller is not approved for all\")\n\tErrCannotTransferToSelf = errors.New(\"cannot send transfer to self\")\n\tErrTransferFromIncorrectOwner = errors.New(\"transfer from incorrect owner\")\n\tErrTransferToNonGRC721Receiver = errors.New(\"transfer to non GRC721Receiver implementer\")\n\tErrCallerIsNotOwnerOrApproved = errors.New(\"caller is not token owner or approved\")\n\tErrTokenIdAlreadyExists = errors.New(\"token id already exists\")\n\n\t// ERC721Royalty\n\tErrInvalidRoyaltyPercentage = errors.New(\"invalid royalty percentage\")\n\tErrInvalidRoyaltyPaymentAddress = errors.New(\"invalid royalty paymentAddress\")\n\tErrCannotCalculateRoyaltyAmount = errors.New(\"cannot calculate royalty amount\")\n)\n"},{"name":"grc721_metadata.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n// metadataNFT represents an NFT with metadata extensions.\ntype metadataNFT struct {\n\t*basicNFT // Embedded basicNFT struct for basic NFT functionality\n\textensions *avl.Tree // AVL tree for storing metadata extensions\n}\n\n// Ensure that metadataNFT implements the IGRC721MetadataOnchain interface.\nvar _ IGRC721MetadataOnchain = (*metadataNFT)(nil)\n\n// NewNFTWithMetadata creates a new basic NFT with metadata extensions.\nfunc NewNFTWithMetadata(name string, symbol string) *metadataNFT {\n\t// Create a new basic NFT\n\tnft := NewBasicNFT(name, symbol)\n\n\t// Return a metadataNFT with basicNFT embedded and an empty AVL tree for extensions\n\treturn \u0026metadataNFT{\n\t\tbasicNFT: nft,\n\t\textensions: avl.NewTree(),\n\t}\n}\n\n// SetTokenMetadata sets metadata for a given token ID.\nfunc (s *metadataNFT) SetTokenMetadata(tid TokenID, metadata Metadata) error {\n\t// Check if the caller is the owner of the token\n\towner, err := s.basicNFT.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcaller := std.PreviousRealm().Addr()\n\tif caller != owner {\n\t\treturn ErrCallerIsNotOwner\n\t}\n\n\t// Set the metadata for the token ID in the extensions AVL tree\n\ts.extensions.Set(string(tid), metadata)\n\treturn nil\n}\n\n// TokenMetadata retrieves metadata for a given token ID.\nfunc (s *metadataNFT) TokenMetadata(tid TokenID) (Metadata, error) {\n\t// Retrieve metadata from the extensions AVL tree\n\tmetadata, found := s.extensions.Get(string(tid))\n\tif !found {\n\t\treturn Metadata{}, ErrInvalidTokenId\n\t}\n\n\treturn metadata.(Metadata), nil\n}\n\n// mint mints a new token and assigns it to the specified address.\nfunc (s *metadataNFT) mint(to std.Address, tid TokenID) error {\n\t// Check if the address is valid\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\t// Check if the token ID already exists\n\tif s.basicNFT.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ts.basicNFT.beforeTokenTransfer(zeroAddress, to, tid, 1)\n\n\t// Check if the token ID was minted by beforeTokenTransfer\n\tif s.basicNFT.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\t// Increment balance of the recipient address\n\ttoBalance, err := s.basicNFT.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance += 1\n\ts.basicNFT.balances.Set(to.String(), toBalance)\n\n\t// Set owner of the token ID to the recipient address\n\ts.basicNFT.owners.Set(string(tid), to)\n\n\t// Emit transfer event\n\tevent := TransferEvent{zeroAddress, to, tid}\n\temit(\u0026event)\n\n\ts.basicNFT.afterTokenTransfer(zeroAddress, to, tid, 1)\n\n\treturn nil\n}\n"},{"name":"grc721_metadata_test.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestSetMetadata(t *testing.T) {\n\t// Create a new dummy NFT with metadata\n\tdummy := NewNFTWithMetadata(dummyNFTName, dummyNFTSymbol)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n\n\t// Define addresses for testing purposes\n\taddr1 := testutils.TestAddress(\"alice\")\n\taddr2 := testutils.TestAddress(\"bob\")\n\n\t// Define metadata attributes\n\tname := \"test\"\n\tdescription := \"test\"\n\timage := \"test\"\n\timageData := \"test\"\n\texternalURL := \"test\"\n\tattributes := []Trait{}\n\tbackgroundColor := \"test\"\n\tanimationURL := \"test\"\n\tyoutubeURL := \"test\"\n\n\t// Set the original caller to addr1\n\tstd.TestSetOriginCaller(addr1) // addr1\n\n\t// Mint a new token for addr1\n\tdummy.mint(addr1, TokenID(\"1\"))\n\n\t// Set metadata for token 1\n\tderr := dummy.SetTokenMetadata(TokenID(\"1\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if there was an error setting metadata\n\tuassert.NoError(t, derr, \"Should not result in error\")\n\n\t// Test case: Invalid token ID\n\terr := dummy.SetTokenMetadata(TokenID(\"3\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if the error returned matches the expected error\n\tuassert.ErrorIs(t, err, ErrInvalidTokenId)\n\n\t// Set the original caller to addr2\n\tstd.TestSetOriginCaller(addr2) // addr2\n\n\t// Try to set metadata for token 1 from addr2 (should fail)\n\tcerr := dummy.SetTokenMetadata(TokenID(\"1\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if the error returned matches the expected error\n\tuassert.ErrorIs(t, cerr, ErrCallerIsNotOwner)\n\n\t// Set the original caller back to addr1\n\tstd.TestSetOriginCaller(addr1) // addr1\n\n\t// Retrieve metadata for token 1\n\tdummyMetadata, err := dummy.TokenMetadata(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"Metadata error\")\n\n\t// Check if metadata attributes match expected values\n\tuassert.Equal(t, image, dummyMetadata.Image)\n\tuassert.Equal(t, imageData, dummyMetadata.ImageData)\n\tuassert.Equal(t, externalURL, dummyMetadata.ExternalURL)\n\tuassert.Equal(t, description, dummyMetadata.Description)\n\tuassert.Equal(t, name, dummyMetadata.Name)\n\tuassert.Equal(t, len(attributes), len(dummyMetadata.Attributes))\n\tuassert.Equal(t, backgroundColor, dummyMetadata.BackgroundColor)\n\tuassert.Equal(t, animationURL, dummyMetadata.AnimationURL)\n\tuassert.Equal(t, youtubeURL, dummyMetadata.YoutubeURL)\n}\n"},{"name":"grc721_royalty.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n// royaltyNFT represents a non-fungible token (NFT) with royalty functionality.\ntype royaltyNFT struct {\n\t*metadataNFT // Embedding metadataNFT for NFT functionality\n\ttokenRoyaltyInfo *avl.Tree // AVL tree to store royalty information for each token\n\tmaxRoyaltyPercentage uint64 // maxRoyaltyPercentage represents the maximum royalty percentage that can be charged every sale\n}\n\n// Ensure that royaltyNFT implements the IGRC2981 interface.\nvar _ IGRC2981 = (*royaltyNFT)(nil)\n\n// NewNFTWithRoyalty creates a new royalty NFT with the specified name, symbol, and royalty calculator.\nfunc NewNFTWithRoyalty(name string, symbol string) *royaltyNFT {\n\t// Create a new NFT with metadata\n\tnft := NewNFTWithMetadata(name, symbol)\n\n\treturn \u0026royaltyNFT{\n\t\tmetadataNFT: nft,\n\t\ttokenRoyaltyInfo: avl.NewTree(),\n\t\tmaxRoyaltyPercentage: 100,\n\t}\n}\n\n// SetTokenRoyalty sets the royalty information for a specific token ID.\nfunc (r *royaltyNFT) SetTokenRoyalty(tid TokenID, royaltyInfo RoyaltyInfo) error {\n\t// Validate the payment address\n\tif err := isValidAddress(royaltyInfo.PaymentAddress); err != nil {\n\t\treturn ErrInvalidRoyaltyPaymentAddress\n\t}\n\n\t// Check if royalty percentage exceeds maxRoyaltyPercentage\n\tif royaltyInfo.Percentage \u003e r.maxRoyaltyPercentage {\n\t\treturn ErrInvalidRoyaltyPercentage\n\t}\n\n\t// Check if the caller is the owner of the token\n\towner, err := r.metadataNFT.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcaller := std.PreviousRealm().Addr()\n\tif caller != owner {\n\t\treturn ErrCallerIsNotOwner\n\t}\n\n\t// Set royalty information for the token\n\tr.tokenRoyaltyInfo.Set(string(tid), royaltyInfo)\n\n\treturn nil\n}\n\n// RoyaltyInfo returns the royalty information for the given token ID and sale price.\nfunc (r *royaltyNFT) RoyaltyInfo(tid TokenID, salePrice uint64) (std.Address, uint64, error) {\n\t// Retrieve royalty information for the token\n\tval, found := r.tokenRoyaltyInfo.Get(string(tid))\n\tif !found {\n\t\treturn \"\", 0, ErrInvalidTokenId\n\t}\n\n\troyaltyInfo := val.(RoyaltyInfo)\n\n\t// Calculate royalty amount\n\troyaltyAmount, _ := r.calculateRoyaltyAmount(salePrice, royaltyInfo.Percentage)\n\n\treturn royaltyInfo.PaymentAddress, royaltyAmount, nil\n}\n\nfunc (r *royaltyNFT) calculateRoyaltyAmount(salePrice, percentage uint64) (uint64, error) {\n\troyaltyAmount := (salePrice * percentage) / 100\n\treturn royaltyAmount, nil\n}\n"},{"name":"grc721_royalty_test.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestSetTokenRoyalty(t *testing.T) {\n\tdummy := NewNFTWithRoyalty(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := testutils.TestAddress(\"alice\")\n\taddr2 := testutils.TestAddress(\"bob\")\n\n\tpaymentAddress := testutils.TestAddress(\"john\")\n\tpercentage := uint64(10) // 10%\n\n\tsalePrice := uint64(1000)\n\texpectRoyaltyAmount := uint64(100)\n\n\tstd.TestSetOriginCaller(addr1) // addr1\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\n\tderr := dummy.SetTokenRoyalty(TokenID(\"1\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tuassert.NoError(t, derr, \"Should not result in error\")\n\n\t// Test case: Invalid token ID\n\terr := dummy.SetTokenRoyalty(TokenID(\"3\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tuassert.ErrorIs(t, derr, ErrInvalidTokenId)\n\n\tstd.TestSetOriginCaller(addr2) // addr2\n\n\tcerr := dummy.SetTokenRoyalty(TokenID(\"1\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tuassert.ErrorIs(t, cerr, ErrCallerIsNotOwner)\n\n\t// Test case: Invalid payment address\n\taerr := dummy.SetTokenRoyalty(TokenID(\"4\"), RoyaltyInfo{\n\t\tPaymentAddress: std.Address(\"###\"), // invalid address\n\t\tPercentage: percentage,\n\t})\n\tuassert.ErrorIs(t, aerr, ErrInvalidRoyaltyPaymentAddress)\n\n\t// Test case: Invalid percentage\n\tperr := dummy.SetTokenRoyalty(TokenID(\"5\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: uint64(200), // over maxRoyaltyPercentage\n\t})\n\tuassert.ErrorIs(t, perr, ErrInvalidRoyaltyPercentage)\n\n\t// Test case: Retrieving Royalty Info\n\tstd.TestSetOriginCaller(addr1) // addr1\n\n\tdummyPaymentAddress, dummyRoyaltyAmount, rerr := dummy.RoyaltyInfo(TokenID(\"1\"), salePrice)\n\tuassert.NoError(t, rerr, \"RoyaltyInfo error\")\n\tuassert.Equal(t, paymentAddress, dummyPaymentAddress)\n\tuassert.Equal(t, expectRoyaltyAmount, dummyRoyaltyAmount)\n}\n"},{"name":"igrc721.gno","body":"package grc721\n\nimport \"std\"\n\ntype IGRC721 interface {\n\tBalanceOf(owner std.Address) (uint64, error)\n\tOwnerOf(tid TokenID) (std.Address, error)\n\tSetTokenURI(tid TokenID, tURI TokenURI) (bool, error)\n\tSafeTransferFrom(from, to std.Address, tid TokenID) error\n\tTransferFrom(from, to std.Address, tid TokenID) error\n\tApprove(approved std.Address, tid TokenID) error\n\tSetApprovalForAll(operator std.Address, approved bool) error\n\tGetApproved(tid TokenID) (std.Address, error)\n\tIsApprovedForAll(owner, operator std.Address) bool\n}\n\ntype (\n\tTokenID string\n\tTokenURI string\n)\n\ntype TransferEvent struct {\n\tFrom std.Address\n\tTo std.Address\n\tTokenID TokenID\n}\n\ntype ApprovalEvent struct {\n\tOwner std.Address\n\tApproved std.Address\n\tTokenID TokenID\n}\n\ntype ApprovalForAllEvent struct {\n\tOwner std.Address\n\tOperator std.Address\n\tApproved bool\n}\n"},{"name":"igrc721_metadata.gno","body":"package grc721\n\n// IGRC721CollectionMetadata describes basic information about an NFT collection.\ntype IGRC721CollectionMetadata interface {\n\tName() string // Name returns the name of the collection.\n\tSymbol() string // Symbol returns the symbol of the collection.\n}\n\n// IGRC721Metadata follows the Ethereum standard\ntype IGRC721Metadata interface {\n\tIGRC721CollectionMetadata\n\tTokenURI(tid TokenID) (string, error) // TokenURI returns the URI of a specific token.\n}\n\n// IGRC721Metadata follows the OpenSea metadata standard\ntype IGRC721MetadataOnchain interface {\n\tIGRC721CollectionMetadata\n\tTokenMetadata(tid TokenID) (Metadata, error)\n}\n\ntype Trait struct {\n\tDisplayType string\n\tTraitType string\n\tValue string\n}\n\n// see: https://docs.opensea.io/docs/metadata-standards\ntype Metadata struct {\n\tImage string // URL to the image of the item. Can be any type of image (including SVGs, which will be cached into PNGs by OpenSea), IPFS or Arweave URLs or paths. We recommend using a minimum 3000 x 3000 image.\n\tImageData string // Raw SVG image data, if you want to generate images on the fly (not recommended). Only use this if you're not including the image parameter.\n\tExternalURL string // URL that will appear below the asset's image on OpenSea and will allow users to leave OpenSea and view the item on your site.\n\tDescription string // Human-readable description of the item. Markdown is supported.\n\tName string // Name of the item.\n\tAttributes []Trait // Attributes for the item, which will show up on the OpenSea page for the item.\n\tBackgroundColor string // Background color of the item on OpenSea. Must be a six-character hexadecimal without a pre-pended #\n\tAnimationURL string // URL to a multimedia attachment for the item. Supported file extensions: GLTF, GLB, WEBM, MP4, M4V, OGV, OGG, MP3, WAV, OGA, HTML (for rich experiences and interactive NFTs using JavaScript canvas, WebGL, etc.). Scripts and relative paths within the HTML page are now supported. Access to browser extensions is not supported.\n\tYoutubeURL string // URL to a YouTube video (only used if animation_url is not provided).\n}\n"},{"name":"igrc721_royalty.gno","body":"package grc721\n\nimport \"std\"\n\n// IGRC2981 follows the Ethereum standard\ntype IGRC2981 interface {\n\t// RoyaltyInfo retrieves royalty information for a tokenID and salePrice.\n\t// It returns the payment address, royalty amount, and an error if any.\n\tRoyaltyInfo(tokenID TokenID, salePrice uint64) (std.Address, uint64, error)\n}\n\n// RoyaltyInfo represents royalty information for a token.\ntype RoyaltyInfo struct {\n\tPaymentAddress std.Address // PaymentAddress is the address where royalty payment should be sent.\n\tPercentage uint64 // Percentage is the royalty percentage. It indicates the percentage of royalty to be paid for each sale. For example : Percentage = 10 =\u003e 10%\n}\n"},{"name":"util.gno","body":"package grc721\n\nimport (\n\t\"std\"\n)\n\nvar zeroAddress = std.Address(\"\")\n\nfunc isValidAddress(addr std.Address) error {\n\tif !addr.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\treturn nil\n}\n\nfunc emit(event interface{}) {\n\t// TODO: setup a pubsub system here?\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":"grc777","path":"gno.land/p/demo/grc/grc777","files":[{"name":"dummy_test.gno","body":"package grc777\n\nimport (\n\t\"std\"\n\t\"testing\"\n)\n\ntype dummyImpl struct{}\n\n// FIXME: this should fail.\nvar _ IGRC777 = (*dummyImpl)(nil)\n\nfunc TestInterface(t *testing.T) {\n\tvar dummy IGRC777 = \u0026dummyImpl{}\n}\n\nfunc (impl *dummyImpl) GetName() string { panic(\"not implemented\") }\nfunc (impl *dummyImpl) GetSymbol() string { panic(\"not implemented\") }\nfunc (impl *dummyImpl) GetDecimals() uint { panic(\"not implemented\") }\nfunc (impl *dummyImpl) Granularity() (granularity uint64) { panic(\"not implemented\") }\nfunc (impl *dummyImpl) TotalSupply() (supply uint64) { panic(\"not implemented\") }\nfunc (impl *dummyImpl) BalanceOf(address std.Address) uint64 { panic(\"not implemented\") }\nfunc (impl *dummyImpl) Burn(amount uint64, data []byte) { panic(\"not implemented\") }\nfunc (impl *dummyImpl) AuthorizeOperator(operator std.Address) { panic(\"not implemented\") }\nfunc (impl *dummyImpl) RevokeOperator(operators std.Address) { panic(\"not implemented\") }\nfunc (impl *dummyImpl) DefaultOperators() []std.Address { panic(\"not implemented\") }\nfunc (impl *dummyImpl) Send(recipient std.Address, amount uint64, data []byte) {\n\tpanic(\"not implemented\")\n}\n\nfunc (impl *dummyImpl) IsOperatorFor(operator, tokenHolder std.Address) bool {\n\tpanic(\"not implemented\")\n}\n\nfunc (impl *dummyImpl) OperatorSend(sender, recipient std.Address, amount uint64, data, operatorData []byte) {\n\tpanic(\"not implemented\")\n}\n\nfunc (impl *dummyImpl) OperatorBurn(account std.Address, amount uint64, data, operatorData []byte) {\n\tpanic(\"not implemented\")\n}\n"},{"name":"igrc777.gno","body":"package grc777\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/grc/exts\"\n)\n\n// TODO: use big.Int or a custom uint64 instead of uint64\n\ntype IGRC777 interface {\n\texts.TokenMetadata\n\n\t// Returns the smallest part of the token that is not divisible. This\n\t// means all token operations (creation, movement and destruction) must\n\t// have amounts that are a multiple of this number.\n\t//\n\t// For most token contracts, this value will equal 1.\n\tGranularity() (granularity uint64)\n\n\t// Returns the amount of tokens in existence.\n\tTotalSupply() (supply uint64)\n\n\t// Returns the amount of tokens owned by an account (`owner`).\n\tBalanceOf(address std.Address) uint64\n\n\t// Moves `amount` tokens from the caller's account to `recipient`.\n\t//\n\t// If send or receive hooks are registered for the caller and `recipient`,\n\t// the corresponding functions will be called with `data` and empty\n\t// `operatorData`. See {IERC777Sender} and {IERC777Recipient}.\n\t//\n\t// Emits a {Sent} event.\n\t//\n\t// Requirements\n\t//\n\t// - the caller must have at least `amount` tokens.\n\t// - `recipient` cannot be the zero address.\n\t// - if `recipient` is a contract, it must implement the {IERC777Recipient}\n\t// interface.\n\tSend(recipient std.Address, amount uint64, data []byte)\n\n\t// Destroys `amount` tokens from the caller's account, reducing the\n\t// total supply.\n\t//\n\t// If a send hook is registered for the caller, the corresponding function\n\t// will be called with `data` and empty `operatorData`. See {IERC777Sender}.\n\t//\n\t// Emits a {Burned} event.\n\t//\n\t// Requirements\n\t//\n\t// - the caller must have at least `amount` tokens.\n\tBurn(amount uint64, data []byte)\n\n\t// Returns true if an account is an operator of `tokenHolder`.\n\t// Operators can send and burn tokens on behalf of their owners. All\n\t// accounts are their own operator.\n\t//\n\t// See {operatorSend} and {operatorBurn}.\n\tIsOperatorFor(operator, tokenHolder std.Address) bool\n\n\t// Make an account an operator of the caller.\n\t//\n\t// See {isOperatorFor}.\n\t//\n\t// Emits an {AuthorizedOperator} event.\n\t//\n\t// Requirements\n\t//\n\t// - `operator` cannot be calling address.\n\tAuthorizeOperator(operator std.Address)\n\n\t// Revoke an account's operator status for the caller.\n\t//\n\t// See {isOperatorFor} and {defaultOperators}.\n\t//\n\t// Emits a {RevokedOperator} event.\n\t//\n\t// Requirements\n\t//\n\t// - `operator` cannot be calling address.\n\tRevokeOperator(operators std.Address)\n\n\t// Returns the list of default operators. These accounts are operators\n\t// for all token holders, even if {authorizeOperator} was never called on\n\t// them.\n\t//\n\t// This list is immutable, but individual holders may revoke these via\n\t// {revokeOperator}, in which case {isOperatorFor} will return false.\n\tDefaultOperators() []std.Address\n\n\t// Moves `amount` tokens from `sender` to `recipient`. The caller must\n\t// be an operator of `sender`.\n\t//\n\t// If send or receive hooks are registered for `sender` and `recipient`,\n\t// the corresponding functions will be called with `data` and\n\t// `operatorData`. See {IERC777Sender} and {IERC777Recipient}.\n\t//\n\t// Emits a {Sent} event.\n\t//\n\t// Requirements\n\t//\n\t// - `sender` cannot be the zero address.\n\t// - `sender` must have at least `amount` tokens.\n\t// - the caller must be an operator for `sender`.\n\t// - `recipient` cannot be the zero address.\n\t// - if `recipient` is a contract, it must implement the {IERC777Recipient}\n\t// interface.\n\tOperatorSend(sender, recipient std.Address, amount uint64, data, operatorData []byte)\n\n\t// Destroys `amount` tokens from `account`, reducing the total supply.\n\t// The caller must be an operator of `account`.\n\t//\n\t// If a send hook is registered for `account`, the corresponding function\n\t// will be called with `data` and `operatorData`. See {IERC777Sender}.\n\t//\n\t// Emits a {Burned} event.\n\t//\n\t// Requirements\n\t//\n\t// - `account` cannot be the zero address.\n\t// - `account` must have at least `amount` tokens.\n\t// - the caller must be an operator for `account`.\n\tOperatorBurn(account std.Address, amount uint64, data, operatorData []byte)\n}\n\n// Emitted when `amount` tokens are created by `operator` and assigned to `to`.\n//\n// Note that some additional user `data` and `operatorData` can be logged in the event.\ntype MintedEvent struct {\n\tOperator std.Address\n\tTo std.Address\n\tAmount uint64\n\tData []byte\n\tOperatorData []byte\n}\n\n// Emitted when `operator` destroys `amount` tokens from `account`.\n//\n// Note that some additional user `data` and `operatorData` can be logged in the event.\ntype BurnedEvent struct {\n\tOperator std.Address\n\tFrom std.Address\n\tAmount uint64\n\tData []byte\n\tOperatorData []byte\n}\n\n// Emitted when `operator` is made operator for `tokenHolder`\ntype AuthorizedOperatorEvent struct {\n\tOperator std.Address\n\tTokenHolder std.Address\n}\n\n// Emitted when `operator` is revoked its operator status for `tokenHolder`.\ntype RevokedOperatorEvent struct {\n\tOperator std.Address\n\tTokenHolder std.Address\n}\n\ntype SentEvent struct {\n\tOperator std.Address\n\tFrom std.Address\n\tTo std.Address\n\tAmount uint64\n\tData []byte\n\tOperatorData []byte\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":"rat","path":"gno.land/p/demo/rat","files":[{"name":"maths.gno","body":"package rat\n\nconst (\n\tintSize = 32 \u003c\u003c (^uint(0) \u003e\u003e 63) // 32 or 64\n\n\tMaxInt = 1\u003c\u003c(intSize-1) - 1\n\tMinInt = -1 \u003c\u003c (intSize - 1)\n\tMaxInt8 = 1\u003c\u003c7 - 1\n\tMinInt8 = -1 \u003c\u003c 7\n\tMaxInt16 = 1\u003c\u003c15 - 1\n\tMinInt16 = -1 \u003c\u003c 15\n\tMaxInt32 = 1\u003c\u003c31 - 1\n\tMinInt32 = -1 \u003c\u003c 31\n\tMaxInt64 = 1\u003c\u003c63 - 1\n\tMinInt64 = -1 \u003c\u003c 63\n\tMaxUint = 1\u003c\u003cintSize - 1\n\tMaxUint8 = 1\u003c\u003c8 - 1\n\tMaxUint16 = 1\u003c\u003c16 - 1\n\tMaxUint32 = 1\u003c\u003c32 - 1\n\tMaxUint64 = 1\u003c\u003c64 - 1\n)\n"},{"name":"rat.gno","body":"package rat\n\n//----------------------------------------\n// Rat fractions\n\n// represents a fraction.\ntype Rat struct {\n\tX int32\n\tY int32 // must be positive\n}\n\nfunc NewRat(x, y int32) Rat {\n\tif y \u003c= 0 {\n\t\tpanic(\"invalid std.Rat denominator\")\n\t}\n\treturn Rat{X: x, Y: y}\n}\n\nfunc (r1 Rat) IsValid() bool {\n\tif r1.Y \u003c= 0 {\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc (r1 Rat) Cmp(r2 Rat) int {\n\tif !r1.IsValid() {\n\t\tpanic(\"invalid std.Rat left operand\")\n\t}\n\tif !r2.IsValid() {\n\t\tpanic(\"invalid std.Rat right operand\")\n\t}\n\tvar p1, p2 int64\n\tp1 = int64(r1.X) * int64(r2.Y)\n\tp2 = int64(r1.Y) * int64(r2.X)\n\tif p1 \u003c p2 {\n\t\treturn -1\n\t} else if p1 == p2 {\n\t\treturn 0\n\t} else {\n\t\treturn 1\n\t}\n}\n\n//func (r1 Rat) Plus(r2 Rat) Rat {\n// XXX\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":"txlink","path":"gno.land/p/moul/txlink","files":[{"name":"txlink.gno","body":"// Package txlink provides utilities for creating transaction-related links\n// compatible with Gnoweb, Gnobro, and other clients within the Gno ecosystem.\n//\n// This package is optimized for generating lightweight transaction links with\n// flexible arguments, allowing users to build dynamic links that integrate\n// seamlessly with various Gno clients.\n//\n// The primary function, URL, is designed to produce markdown links for\n// transaction functions in the current \"relative realm\". By specifying a custom\n// Realm, you can generate links that either use the current realm path or a\n// fully qualified path for another realm.\n//\n// This package is a streamlined alternative to helplink, providing similar\n// functionality for transaction links without the full feature set of helplink.\npackage txlink\n\nimport (\n\t\"std\"\n\t\"strings\"\n)\n\nconst chainDomain = \"gno.land\" // XXX: std.ChainDomain (#2911)\n\n// URL returns a URL for the specified function with optional key-value\n// arguments, for the current realm.\nfunc URL(fn string, args ...string) string {\n\treturn Realm(\"\").URL(fn, args...)\n}\n\n// Realm represents a specific realm for generating tx links.\ntype Realm string\n\n// prefix returns the URL prefix for the realm.\nfunc (r Realm) prefix() string {\n\t// relative\n\tif r == \"\" {\n\t\tcurPath := std.CurrentRealm().PkgPath()\n\t\treturn strings.TrimPrefix(curPath, chainDomain)\n\t}\n\n\t// local realm -\u003e /realm\n\trealm := string(r)\n\tif strings.Contains(realm, chainDomain) {\n\t\treturn strings.TrimPrefix(realm, chainDomain)\n\t}\n\n\t// remote realm -\u003e https://remote.land/realm\n\treturn \"https://\" + string(r)\n}\n\n// URL returns a URL for the specified function with optional key-value\n// arguments.\nfunc (r Realm) URL(fn string, args ...string) string {\n\t// Start with the base query\n\turl := r.prefix() + \"$help\u0026func=\" + fn\n\n\t// Check if args length is even\n\tif len(args)%2 != 0 {\n\t\t// If not even, we can choose to handle the error here.\n\t\t// For example, we can just return the URL without appending\n\t\t// more args.\n\t\treturn url\n\t}\n\n\t// Append key-value pairs to the URL\n\tfor i := 0; i \u003c len(args); i += 2 {\n\t\tkey := args[i]\n\t\tvalue := args[i+1]\n\t\t// XXX: escape keys and args\n\t\turl += \"\u0026\" + key + \"=\" + value\n\t}\n\n\treturn url\n}\n"},{"name":"txlink_test.gno","body":"package txlink\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestURL(t *testing.T) {\n\ttests := []struct {\n\t\tfn string\n\t\targs []string\n\t\twant string\n\t\trealm Realm\n\t}{\n\t\t{\"foo\", []string{\"bar\", \"1\", \"baz\", \"2\"}, \"$help\u0026func=foo\u0026bar=1\u0026baz=2\", \"\"},\n\t\t{\"testFunc\", []string{\"key\", \"value\"}, \"$help\u0026func=testFunc\u0026key=value\", \"\"},\n\t\t{\"noArgsFunc\", []string{}, \"$help\u0026func=noArgsFunc\", \"\"},\n\t\t{\"oddArgsFunc\", []string{\"key\"}, \"$help\u0026func=oddArgsFunc\", \"\"},\n\t\t{\"foo\", []string{\"bar\", \"1\", \"baz\", \"2\"}, \"/r/lorem/ipsum$help\u0026func=foo\u0026bar=1\u0026baz=2\", \"gno.land/r/lorem/ipsum\"},\n\t\t{\"testFunc\", []string{\"key\", \"value\"}, \"/r/lorem/ipsum$help\u0026func=testFunc\u0026key=value\", \"gno.land/r/lorem/ipsum\"},\n\t\t{\"noArgsFunc\", []string{}, \"/r/lorem/ipsum$help\u0026func=noArgsFunc\", \"gno.land/r/lorem/ipsum\"},\n\t\t{\"oddArgsFunc\", []string{\"key\"}, \"/r/lorem/ipsum$help\u0026func=oddArgsFunc\", \"gno.land/r/lorem/ipsum\"},\n\t\t{\"foo\", []string{\"bar\", \"1\", \"baz\", \"2\"}, \"https://gno.world/r/lorem/ipsum$help\u0026func=foo\u0026bar=1\u0026baz=2\", \"gno.world/r/lorem/ipsum\"},\n\t\t{\"testFunc\", []string{\"key\", \"value\"}, \"https://gno.world/r/lorem/ipsum$help\u0026func=testFunc\u0026key=value\", \"gno.world/r/lorem/ipsum\"},\n\t\t{\"noArgsFunc\", []string{}, \"https://gno.world/r/lorem/ipsum$help\u0026func=noArgsFunc\", \"gno.world/r/lorem/ipsum\"},\n\t\t{\"oddArgsFunc\", []string{\"key\"}, \"https://gno.world/r/lorem/ipsum$help\u0026func=oddArgsFunc\", \"gno.world/r/lorem/ipsum\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\ttitle := tt.fn\n\t\tt.Run(title, func(t *testing.T) {\n\t\t\tgot := tt.realm.URL(tt.fn, tt.args...)\n\t\t\turequire.Equal(t, tt.want, got)\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":"users","path":"gno.land/p/demo/users","files":[{"name":"types.gno","body":"package users\n\ntype AddressOrName string\n\nfunc (aon AddressOrName) IsName() bool {\n\treturn aon != \"\" \u0026\u0026 aon[0] == '@'\n}\n\nfunc (aon AddressOrName) GetName() (string, bool) {\n\tif len(aon) \u003e= 2 \u0026\u0026 aon[0] == '@' {\n\t\treturn string(aon[1:]), true\n\t}\n\treturn \"\", false\n}\n"},{"name":"users.gno","body":"package users\n\nimport (\n\t\"std\"\n\t\"strconv\"\n)\n\n//----------------------------------------\n// Types\n\ntype User struct {\n\tAddress std.Address\n\tName string\n\tProfile string\n\tNumber int\n\tInvites int\n\tInviter std.Address\n}\n\nfunc (u *User) Render() string {\n\tstr := \"## user \" + u.Name + \"\\n\" +\n\t\t\"\\n\" +\n\t\t\" * address = \" + string(u.Address) + \"\\n\" +\n\t\t\" * \" + strconv.Itoa(u.Invites) + \" invites\\n\"\n\tif u.Inviter != \"\" {\n\t\tstr = str + \" * invited by \" + string(u.Inviter) + \"\\n\"\n\t}\n\tstr = str + \"\\n\" +\n\t\tu.Profile + \"\\n\"\n\treturn str\n}\n"},{"name":"users_test.gno","body":"package users\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":"users","path":"gno.land/r/demo/users","files":[{"name":"preregister.gno","body":"package users\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/users\"\n)\n\n// pre-restricted names\nvar preRestrictedNames = []string{\n\t\"bitcoin\", \"cosmos\", \"newtendermint\", \"ethereum\",\n}\n\n// pre-registered users\nvar preRegisteredUsers = []struct {\n\tName string\n\tAddress std.Address\n}{\n\t// system name\n\t{\"archives\", \"g1xlnyjrnf03ju82v0f98ruhpgnquk28knmjfe5k\"}, // -\u003e @r_archives\n\t{\"demo\", \"g13ek2zz9qurzynzvssyc4sthwppnruhnp0gdz8n\"}, // -\u003e @r_demo\n\t{\"gno\", \"g19602kd9tfxrfd60sgreadt9zvdyyuudcyxsz8a\"}, // -\u003e @r_gno\n\t{\"gnoland\", \"g1g3lsfxhvaqgdv4ccemwpnms4fv6t3aq3p5z6u7\"}, // -\u003e @r_gnoland\n\t{\"gnolang\", \"g1yjlnm3z2630gg5mryjd79907e0zx658wxs9hnd\"}, // -\u003e @r_gnolang\n\t{\"gov\", \"g1g73v2anukg4ej7axwqpthsatzrxjsh0wk797da\"}, // -\u003e @r_gov\n\t{\"nt\", \"g15ge0ae9077eh40erwrn2eq0xw6wupwqthpv34l\"}, // -\u003e @r_nt\n\t{\"sys\", \"g1r929wt2qplfawe4lvqv9zuwfdcz4vxdun7qh8l\"}, // -\u003e @r_sys\n\t{\"x\", \"g164sdpew3c2t3rvxj3kmfv7c7ujlvcw2punzzuz\"}, // -\u003e @r_x\n\n\t// test1 user\n\t{\"test1\", \"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\"}, // -\u003e @test1\n\n\t// Onbloc\n\t{\"gnoswap\", \"g1lmvrrrr4er2us84h2732sru76c9zl2nvknha8c\"}, // -\u003e @r_gnoswap\n\t{\"onbloc\", \"g12vx7dn3dqq89mz550zwunvg4qw6epq73d9csay\"}, // -\u003e @r_onbloc\n\n\t// Dragos\n\t{\"flippando\", \"g1z82x8mxa0pz5s9u7csy6zya4x0ut9uw6p7d8dk\"}, // -\u003e @r_flippando\n\t{\"zentasktic\", \"g1paxgmwy2wzhx0l6qvav2p8thvphc5c030xz35c\"}, // -\u003e @r_zentasktic\n}\n\nfunc init() {\n\t// add pre-registered users\n\tfor _, res := range preRegisteredUsers {\n\t\t// assert not already registered.\n\t\t_, ok := name2User.Get(res.Name)\n\t\tif ok {\n\t\t\tpanic(\"name already registered\")\n\t\t}\n\n\t\t_, ok = addr2User.Get(res.Address.String())\n\t\tif ok {\n\t\t\tpanic(\"address already registered\")\n\t\t}\n\n\t\tcounter++\n\t\tuser := \u0026users.User{\n\t\t\tAddress: res.Address,\n\t\t\tName: res.Name,\n\t\t\tProfile: \"\",\n\t\t\tNumber: counter,\n\t\t\tInvites: int(0),\n\t\t\tInviter: admin,\n\t\t}\n\t\tname2User.Set(res.Name, user)\n\t\taddr2User.Set(res.Address.String(), user)\n\t}\n\n\t// add pre-restricted names\n\tfor _, name := range preRestrictedNames {\n\t\tif _, ok := name2User.Get(name); ok {\n\t\t\tpanic(\"name already registered\")\n\t\t}\n\n\t\trestricted.Set(name, true)\n\t}\n}\n"},{"name":"users.gno","body":"package users\n\nimport (\n\t\"regexp\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/avl/pager\"\n\t\"gno.land/p/demo/avlhelpers\"\n\t\"gno.land/p/demo/users\"\n)\n\n//----------------------------------------\n// State\n\nvar (\n\tadmin std.Address = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\" // @moul\n\n\trestricted avl.Tree // Name -\u003e true - restricted name\n\tname2User avl.Tree // Name -\u003e *users.User\n\taddr2User avl.Tree // std.Address -\u003e *users.User\n\tinvites avl.Tree // string(inviter+\":\"+invited) -\u003e true\n\tcounter int // user id counter\n\tminFee int64 = 20 * 1_000_000 // minimum gnot must be paid to register.\n\tmaxFeeMult int64 = 10 // maximum multiples of minFee accepted.\n)\n\n//----------------------------------------\n// Top-level functions\n\nfunc Register(inviter std.Address, name string, profile string) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// assert invited or paid.\n\tcaller := std.CallerAt(2)\n\tif caller != std.OriginCaller() {\n\t\tpanic(\"should not happen\") // because std.AssertOrigCall().\n\t}\n\n\tsentCoins := std.OriginSend()\n\tminCoin := std.NewCoin(\"ugnot\", minFee)\n\n\tif inviter == \"\" {\n\t\t// banker := std.GetBanker(std.BankerTypeOriginSend)\n\t\tif len(sentCoins) == 1 \u0026\u0026 sentCoins[0].IsGTE(minCoin) {\n\t\t\tif sentCoins[0].Amount \u003e minFee*maxFeeMult {\n\t\t\t\tpanic(\"payment must not be greater than \" + strconv.Itoa(int(minFee*maxFeeMult)))\n\t\t\t} else {\n\t\t\t\t// ok\n\t\t\t}\n\t\t} else {\n\t\t\tpanic(\"payment must not be less than \" + strconv.Itoa(int(minFee)))\n\t\t}\n\t} else {\n\t\tinvitekey := inviter.String() + \":\" + caller.String()\n\t\t_, ok := invites.Get(invitekey)\n\t\tif !ok {\n\t\t\tpanic(\"invalid invitation\")\n\t\t}\n\t\tinvites.Remove(invitekey)\n\t}\n\n\t// assert not already registered.\n\t_, ok := name2User.Get(name)\n\tif ok {\n\t\tpanic(\"name already registered: \" + name)\n\t}\n\t_, ok = addr2User.Get(caller.String())\n\tif ok {\n\t\tpanic(\"address already registered: \" + caller.String())\n\t}\n\n\tisInviterAdmin := inviter == admin\n\n\t// check for restricted name\n\tif _, isRestricted := restricted.Get(name); isRestricted {\n\t\t// only address invite by the admin can register restricted name\n\t\tif !isInviterAdmin {\n\t\t\tpanic(\"restricted name: \" + name)\n\t\t}\n\n\t\trestricted.Remove(name)\n\t}\n\n\t// assert name is valid.\n\t// admin inviter can bypass name restriction\n\tif !isInviterAdmin \u0026\u0026 !reName.MatchString(name) {\n\t\tpanic(\"invalid name: \" + name + \" (must be at least 6 characters, lowercase alphanumeric with underscore)\")\n\t}\n\n\t// remainder of fees go toward invites.\n\tinvites := int(0)\n\tif len(sentCoins) == 1 {\n\t\tif sentCoins[0].Denom == \"ugnot\" \u0026\u0026 sentCoins[0].Amount \u003e= minFee {\n\t\t\tinvites = int(sentCoins[0].Amount / minFee)\n\t\t\tif inviter == \"\" \u0026\u0026 invites \u003e 0 {\n\t\t\t\tinvites -= 1\n\t\t\t}\n\t\t}\n\t}\n\t// register.\n\tcounter++\n\tuser := \u0026users.User{\n\t\tAddress: caller,\n\t\tName: name,\n\t\tProfile: profile,\n\t\tNumber: counter,\n\t\tInvites: invites,\n\t\tInviter: inviter,\n\t}\n\tname2User.Set(name, user)\n\taddr2User.Set(caller.String(), user)\n}\n\nfunc Invite(invitee string) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// get caller/inviter.\n\tcaller := std.CallerAt(2)\n\tif caller != std.OriginCaller() {\n\t\tpanic(\"should not happen\") // because std.AssertOrigCall().\n\t}\n\tlines := strings.Split(invitee, \"\\n\")\n\tif caller == admin {\n\t\t// nothing to do, all good\n\t} else {\n\t\t// ensure has invites.\n\t\tuserI, ok := addr2User.Get(caller.String())\n\t\tif !ok {\n\t\t\tpanic(\"user unknown\")\n\t\t}\n\t\tuser := userI.(*users.User)\n\t\tif user.Invites \u003c= 0 {\n\t\t\tpanic(\"user has no invite tokens\")\n\t\t}\n\t\tuser.Invites -= len(lines)\n\t\tif user.Invites \u003c 0 {\n\t\t\tpanic(\"user has insufficient invite tokens\")\n\t\t}\n\t}\n\t// for each line...\n\tfor _, line := range lines {\n\t\tif line == \"\" {\n\t\t\tcontinue // file bodies have a trailing newline.\n\t\t} else if strings.HasPrefix(line, `//`) {\n\t\t\tcontinue // comment\n\t\t}\n\t\t// record invite.\n\t\tinvitekey := string(caller) + \":\" + string(line)\n\t\tinvites.Set(invitekey, true)\n\t}\n}\n\nfunc GrantInvites(invites string) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// assert admin.\n\tcaller := std.CallerAt(2)\n\tif caller != std.OriginCaller() {\n\t\tpanic(\"should not happen\") // because std.AssertOrigCall().\n\t}\n\tif caller != admin {\n\t\tpanic(\"unauthorized\")\n\t}\n\t// for each line...\n\tlines := strings.Split(invites, \"\\n\")\n\tfor _, line := range lines {\n\t\tif line == \"\" {\n\t\t\tcontinue // file bodies have a trailing newline.\n\t\t} else if strings.HasPrefix(line, `//`) {\n\t\t\tcontinue // comment\n\t\t}\n\t\t// parse name and invites.\n\t\tvar name string\n\t\tvar invites int\n\t\tparts := strings.Split(line, \":\")\n\t\tif len(parts) == 1 { // short for :1.\n\t\t\tname = parts[0]\n\t\t\tinvites = 1\n\t\t} else if len(parts) == 2 {\n\t\t\tname = parts[0]\n\t\t\tinvites_, err := strconv.Atoi(parts[1])\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t\tinvites = int(invites_)\n\t\t} else {\n\t\t\tpanic(\"should not happen\")\n\t\t}\n\t\t// give invites.\n\t\tuserI, ok := name2User.Get(name)\n\t\tif !ok {\n\t\t\t// maybe address.\n\t\t\tuserI, ok = addr2User.Get(name)\n\t\t\tif !ok {\n\t\t\t\tpanic(\"invalid user \" + name)\n\t\t\t}\n\t\t}\n\t\tuser := userI.(*users.User)\n\t\tuser.Invites += invites\n\t}\n}\n\n// Any leftover fees go toward invitations.\nfunc SetMinFee(newMinFee int64) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// assert admin caller.\n\tcaller := std.CallerAt(2)\n\tif caller != admin {\n\t\tpanic(\"unauthorized\")\n\t}\n\t// update global variables.\n\tminFee = newMinFee\n}\n\n// This helps prevent fat finger accidents.\nfunc SetMaxFeeMultiple(newMaxFeeMult int64) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// assert admin caller.\n\tcaller := std.CallerAt(2)\n\tif caller != admin {\n\t\tpanic(\"unauthorized\")\n\t}\n\t// update global variables.\n\tmaxFeeMult = newMaxFeeMult\n}\n\n//----------------------------------------\n// Exposed public functions\n\nfunc GetUserByName(name string) *users.User {\n\tuserI, ok := name2User.Get(name)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn userI.(*users.User)\n}\n\nfunc GetUserByAddress(addr std.Address) *users.User {\n\tuserI, ok := addr2User.Get(addr.String())\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn userI.(*users.User)\n}\n\n// unlike GetUserByName, input must be \"@\" prefixed for names.\nfunc GetUserByAddressOrName(input users.AddressOrName) *users.User {\n\tname, isName := input.GetName()\n\tif isName {\n\t\treturn GetUserByName(name)\n\t}\n\treturn GetUserByAddress(std.Address(input))\n}\n\n// Get a list of user names starting from the given prefix. Limit the\n// number of results to maxResults. (This can be used for a name search tool.)\nfunc ListUsersByPrefix(prefix string, maxResults int) []string {\n\treturn avlhelpers.ListByteStringKeysByPrefix(name2User, prefix, maxResults)\n}\n\nfunc Resolve(input users.AddressOrName) std.Address {\n\tname, isName := input.GetName()\n\tif !isName {\n\t\treturn std.Address(input) // TODO check validity\n\t}\n\n\tuser := GetUserByName(name)\n\treturn user.Address\n}\n\n// Add restricted name to the list\nfunc AdminAddRestrictedName(name string) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// get caller\n\tcaller := std.OriginCaller()\n\t// assert admin\n\tif caller != admin {\n\t\tpanic(\"unauthorized\")\n\t}\n\n\tif user := GetUserByName(name); user != nil {\n\t\tpanic(\"already registered name\")\n\t}\n\n\t// register restricted name\n\n\trestricted.Set(name, true)\n}\n\n//----------------------------------------\n// Constants\n\n// NOTE: name length must be clearly distinguishable from a bech32 address.\nvar reName = regexp.MustCompile(`^[a-z]+[_a-z0-9]{5,16}$`)\n\n//----------------------------------------\n// Render main page\n\nfunc Render(fullPath string) string {\n\tpath, _ := splitPathAndQuery(fullPath)\n\tif path == \"\" {\n\t\treturn renderHome(fullPath)\n\t} else if len(path) \u003e= 38 { // 39? 40?\n\t\tif path[:2] != \"g1\" {\n\t\t\treturn \"invalid address \" + path\n\t\t}\n\t\tuser := GetUserByAddress(std.Address(path))\n\t\tif user == nil {\n\t\t\t// TODO: display basic information about account.\n\t\t\treturn \"unknown address \" + path\n\t\t}\n\t\treturn user.Render()\n\t} else {\n\t\tuser := GetUserByName(path)\n\t\tif user == nil {\n\t\t\treturn \"unknown username \" + path\n\t\t}\n\t\treturn user.Render()\n\t}\n}\n\nfunc renderHome(path string) string {\n\tdoc := \"\"\n\n\tpage := pager.NewPager(\u0026name2User, 50).MustGetPageByPath(path)\n\n\tfor _, item := range page.Items {\n\t\tuser := item.Value.(*users.User)\n\t\tdoc += \" * [\" + user.Name + \"](/r/demo/users:\" + user.Name + \")\\n\"\n\t}\n\tdoc += \"\\n\"\n\tdoc += page.Selector()\n\treturn doc\n}\n\nfunc splitPathAndQuery(fullPath string) (string, string) {\n\tparts := strings.SplitN(fullPath, \"?\", 2)\n\tpath := parts[0]\n\tqueryString := \"\"\n\tif len(parts) \u003e 1 {\n\t\tqueryString = \"?\" + parts[1]\n\t}\n\treturn path, queryString\n}\n"},{"name":"users_test.gno","body":"package users\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestPreRegisteredTest1(t *testing.T) {\n\tnames := ListUsersByPrefix(\"test1\", 1)\n\tuassert.Equal(t, len(names), 1)\n\tuassert.Equal(t, names[0], \"test1\")\n}\n"},{"name":"z_0_b_filetest.gno","body":"package main\n\n// SEND: 19900000ugnot\n\nimport (\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Error:\n// payment must not be less than 20000000\n"},{"name":"z_0_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tstd.TestSetOriginSend(std.Coins{std.NewCoin(\"dontcare\", 1)}, nil)\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Error:\n// incompatible coin denominations: dontcare, ugnot\n"},{"name":"z_10_filetest.gno","body":"// PKGPATH: gno.land/r/demo/users_test\npackage users_test\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc init() {\n\tcaller := std.OriginCaller() // main\n\ttest2 := testutils.TestAddress(\"test2\")\n\t// as admin, invite gnouser and test2\n\tstd.TestSetOriginCaller(admin)\n\tusers.Invite(caller.String() + \"\\n\" + test2.String())\n\t// register as caller\n\tstd.TestSetOriginCaller(caller)\n\tusers.Register(admin, \"gnouser\", \"my profile\")\n}\n\nfunc main() {\n\t// register as test2\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOriginCaller(test2)\n\tusers.Register(admin, \"test222\", \"my profile 2\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_11_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tstd.TestSetOriginCaller(admin)\n\tusers.AdminAddRestrictedName(\"superrestricted\")\n\n\t// test restricted name\n\tstd.TestSetOriginCaller(caller)\n\tusers.Register(\"\", \"superrestricted\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Error:\n// restricted name: superrestricted\n"},{"name":"z_11b_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tstd.TestSetOriginCaller(admin)\n\t// add restricted name\n\tusers.AdminAddRestrictedName(\"superrestricted\")\n\t// grant invite to caller\n\tusers.Invite(caller.String())\n\t// set back caller\n\tstd.TestSetOriginCaller(caller)\n\t// register restricted name with admin invite\n\tusers.Register(admin, \"superrestricted\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_12_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"alicia\", \"my profile\")\n\n\t{\n\t\t// Normal usage\n\t\tnames := users.ListUsersByPrefix(\"a\", 1)\n\t\tprintln(\"# names: \" + strconv.Itoa(len(names)))\n\t\tprintln(\"name: \" + names[0])\n\t}\n\n\t{\n\t\t// Empty prefix: match all\n\t\tnames := users.ListUsersByPrefix(\"\", 1)\n\t\tprintln(\"# names: \" + strconv.Itoa(len(names)))\n\t\tprintln(\"name: \" + names[0])\n\t}\n\n\t{\n\t\t// The prefix is before \"alicia\"\n\t\tnames := users.ListUsersByPrefix(\"alich\", 1)\n\t\tprintln(\"# names: \" + strconv.Itoa(len(names)))\n\t}\n\n\t{\n\t\t// The prefix is after the last name\n\t\tnames := users.ListUsersByPrefix(\"y\", 10)\n\t\tprintln(\"# names: \" + strconv.Itoa(len(names)))\n\t}\n\n\t// More tests are in p/demo/avlhelpers\n}\n\n// Output:\n// # names: 1\n// name: alicia\n// # names: 1\n// name: alicia\n// # names: 0\n// # names: 0\n"},{"name":"z_1_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_2_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_3_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tstd.TestSetOriginSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_4_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\ttest2 := testutils.TestAddress(\"test2\")\n\tusers.Invite(test1.String())\n\t// switch to test2 (not test1)\n\tstd.TestSetOriginCaller(test2)\n\tstd.TestSetOriginSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\tprintln(\"done\")\n}\n\n// Error:\n// invalid invitation\n"},{"name":"z_5_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tstd.TestSetOriginSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\tprintln(users.Render(\"\"))\n\tprintln(\"========================================\")\n\tprintln(users.Render(\"?page=2\"))\n\tprintln(\"========================================\")\n\tprintln(users.Render(\"gnouser\"))\n\tprintln(\"========================================\")\n\tprintln(users.Render(\"satoshi\"))\n\tprintln(\"========================================\")\n\tprintln(users.Render(\"badname\"))\n}\n\n// Output:\n// * [archives](/r/demo/users:archives)\n// * [demo](/r/demo/users:demo)\n// * [gno](/r/demo/users:gno)\n// * [gnoland](/r/demo/users:gnoland)\n// * [gnolang](/r/demo/users:gnolang)\n// * [gnouser](/r/demo/users:gnouser)\n// * [gov](/r/demo/users:gov)\n// * [nt](/r/demo/users:nt)\n// * [satoshi](/r/demo/users:satoshi)\n// * [sys](/r/demo/users:sys)\n// * [test1](/r/demo/users:test1)\n// * [x](/r/demo/users:x)\n//\n//\n// ========================================\n//\n//\n// ========================================\n// ## user gnouser\n//\n// * address = g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n// * 9 invites\n//\n// my profile\n//\n// ========================================\n// ## user satoshi\n//\n// * address = g1w3jhxap3ta047h6lta047h6lta047h6l4mfnm7\n// * 0 invites\n// * invited by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// my other profile\n//\n// ========================================\n// unknown username badname\n"},{"name":"z_6_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller()\n\t// as admin, grant invites to unregistered user.\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\tprintln(\"done\")\n}\n\n// Error:\n// invalid user g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n"},{"name":"z_7_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tstd.TestSetOriginSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\t// as admin, grant invites to gnouser(again) and satoshi.\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\\n\" + test1.String() + \":1\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_7b_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\\n\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tstd.TestSetOriginSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\t// as admin, grant invites to gnouser(again) and satoshi.\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\\n\" + test1.String() + \":1\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_8_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tstd.TestSetOriginSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\t// as admin, grant invites to gnouser(again) and nonexistent user.\n\tstd.TestSetOriginCaller(admin)\n\ttest2 := testutils.TestAddress(\"test2\")\n\tusers.GrantInvites(caller.String() + \":1\\n\" + test2.String() + \":1\")\n\tprintln(\"done\")\n}\n\n// Error:\n// invalid user g1w3jhxapjta047h6lta047h6lta047h6laqcyu4\n"},{"name":"z_9_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\ttest2 := testutils.TestAddress(\"test2\")\n\t// as admin, invite gnouser and test2\n\tstd.TestSetOriginCaller(admin)\n\tusers.Invite(caller.String() + \"\\n\" + test2.String())\n\t// register as caller\n\tstd.TestSetOriginCaller(caller)\n\tusers.Register(admin, \"gnouser\", \"my profile\")\n\t// register as test2\n\tstd.TestSetOriginCaller(test2)\n\tusers.Register(admin, \"test222\", \"my profile 2\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\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":"boards","path":"gno.land/r/demo/boards","files":[{"name":"README.md","body":"This is a demo of Gno smart contract programming. This document was\nconstructed by Gno onto a smart contract hosted on the data Realm\nname [\"gno.land/r/demo/boards\"](https://gno.land/r/demo/boards/)\n([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)).\n\n\n\n## Build `gnokey`, create your account, and interact with Gno.\n\nNOTE: Where you see `-remote localhost:26657` here, that flag can be replaced\nwith `-remote gno.land:26657` if you have $GNOT on the testnet.\n(To use the testnet, also replace `-chainid dev` with `-chainid portal-loop` .)\n\n### Build `gnokey` (and other tools).\n\n```bash\ngit clone git@github.com:gnolang/gno.git\ncd gno/gno.land\nmake build\n```\n\n### Generate a seed/mnemonic code.\n\n```bash\n./build/gnokey generate\n```\n\nNOTE: You can generate 24 words with any good bip39 generator.\n\n### Create a new account using your mnemonic.\n\n```bash\n./build/gnokey add -recover KEYNAME\n```\n\nNOTE: `KEYNAME` is your key identifier, and should be changed.\n\n### Verify that you can see your account locally.\n\n```bash\n./build/gnokey list\n```\n\nTake note of your `addr` which looks something like `g17sphqax3kasjptdkmuqvn740u8dhtx4kxl6ljf` .\nYou will use this as your `ACCOUNT_ADDR`.\n\n## Interact with the blockchain.\n\n### Add $GNOT for your account.\n\nBefore starting the `gnoland` node for the first time, your new account can be given $GNOT in the node genesis.\nEdit the file `gno.land/genesis/genesis_balances.txt` and add the following line (simlar to the others), using\nyour `ACCOUNT_ADDR` and `KEYNAME`\n\n`ACCOUNT_ADDR=10000000000ugnot # @KEYNAME`\n\n### Alternative: Run a faucet to add $GNOT.\n\nInstead of editing `gno.land/genesis/genesis_balances.txt`, a more general solution (with more steps)\nis to run a local \"faucet\" and use the web browser to add $GNOT. (This can be done at any time.)\nSee this page: https://github.com/gnolang/gno/blob/master/contribs/gnofaucet/README.md\n\n\n### Start the `gnoland` node.\n\n```bash\n./build/gnoland start\n```\n\nNOTE: The node already has the \"boards\" realm.\n\nLeave this running in the terminal. In a new terminal, cd to the same folder `gno/gno.land` .\n\n### Get your current balance, account number, and sequence number.\n\n```bash\n./build/gnokey query auth/accounts/ACCOUNT_ADDR -remote localhost:26657\n```\n\n### Register a board username with a smart contract call.\n\nThe `USERNAME` for posting can different than your `KEYNAME`. It is internally linked to your `ACCOUNT_ADDR`. It must be at least 6 characters, lowercase alphanumeric with underscore.\n\n```bash\n./build/gnokey maketx call -pkgpath \"gno.land/r/demo/users\" -func \"Register\" -args \"\" -args \"USERNAME\" -args \"Profile description\" -gas-fee \"10000000ugnot\" -gas-wanted \"2000000\" -send \"200000000ugnot\" -broadcast -chainid dev -remote 127.0.0.1:26657 KEYNAME\n```\n\nInteractive documentation: https://gno.land/r/demo/users$help\u0026func=Register\n\n### Create a board with a smart contract call.\n\n```bash\n./build/gnokey maketx call -pkgpath \"gno.land/r/demo/boards\" -func \"CreateBoard\" -args \"BOARDNAME\" -gas-fee \"1000000ugnot\" -gas-wanted \"10000000\" -broadcast -chainid dev -remote localhost:26657 KEYNAME\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateBoard\n\nNext, query for the permanent board ID by querying (you need this to create a new post):\n\n```bash\n./build/gnokey query \"vm/qeval\" -data 'gno.land/r/demo/boards.GetBoardIDFromName(\"BOARDNAME\")' -remote localhost:26657\n```\n\n### Create a post of a board with a smart contract call.\n\nNOTE: If a board was created successfully, your SEQUENCE_NUMBER would have increased.\n\n```bash\n./build/gnokey maketx call -pkgpath \"gno.land/r/demo/boards\" -func \"CreateThread\" -args BOARD_ID -args \"Hello gno.land\" -args \"Text of the post\" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid dev -remote localhost:26657 KEYNAME\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateThread\n\n### Create a comment to a post.\n\n```bash\n./build/gnokey maketx call -pkgpath \"gno.land/r/demo/boards\" -func \"CreateReply\" -args BOARD_ID -args \"1\" -args \"1\" -args \"Nice to meet you too.\" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid dev -remote localhost:26657 KEYNAME\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateReply\n\n```bash\n./build/gnokey query \"vm/qrender\" -data \"gno.land/r/demo/boards:BOARDNAME/1\" -remote localhost:26657\n```\n\n### Render page with optional path expression.\n\nThe contents of `https://gno.land/r/demo/boards:` and `https://gno.land/r/demo/boards:gnolang` are rendered by calling\nthe `Render(path string)` function like so:\n\n```bash\n./build/gnokey query \"vm/qrender\" -data \"gno.land/r/demo/boards:gnolang\"\n```\n## View the board in the browser.\n\n### Start the web server.\n\n```bash\n./build/gnoweb\n```\n\nThis should print something like `Running on http://127.0.0.1:8888` . Leave this running in the terminal.\n\n### View in the browser\n\nIn your browser, navigate to the printed address http://127.0.0.1:8888 .\nTo see you post, click on the package `/r/demo/boards` .\n"},{"name":"board.gno","body":"package boards\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/moul/txlink\"\n)\n\n//----------------------------------------\n// Board\n\ntype BoardID uint64\n\nfunc (bid BoardID) String() string {\n\treturn strconv.Itoa(int(bid))\n}\n\ntype Board struct {\n\tid BoardID // only set for public boards.\n\turl string\n\tname string\n\tcreator std.Address\n\tthreads avl.Tree // Post.id -\u003e *Post\n\tpostsCtr uint64 // increments Post.id\n\tcreatedAt time.Time\n\tdeleted avl.Tree // TODO reserved for fast-delete.\n}\n\nfunc newBoard(id BoardID, url string, name string, creator std.Address) *Board {\n\tif !reName.MatchString(name) {\n\t\tpanic(\"invalid name: \" + name)\n\t}\n\texists := gBoardsByName.Has(name)\n\tif exists {\n\t\tpanic(\"board already exists\")\n\t}\n\treturn \u0026Board{\n\t\tid: id,\n\t\turl: url,\n\t\tname: name,\n\t\tcreator: creator,\n\t\tthreads: avl.Tree{},\n\t\tcreatedAt: time.Now(),\n\t\tdeleted: avl.Tree{},\n\t}\n}\n\n/* TODO support this once we figure out how to ensure URL correctness.\n// A private board is not tracked by gBoards*,\n// but must be persisted by the caller's realm.\n// Private boards have 0 id and does not ping\n// back the remote board on reposts.\nfunc NewPrivateBoard(url string, name string, creator std.Address) *Board {\n\treturn newBoard(0, url, name, creator)\n}\n*/\n\nfunc (board *Board) IsPrivate() bool {\n\treturn board.id == 0\n}\n\nfunc (board *Board) GetThread(pid PostID) *Post {\n\tpidkey := postIDKey(pid)\n\tpostI, exists := board.threads.Get(pidkey)\n\tif !exists {\n\t\treturn nil\n\t}\n\treturn postI.(*Post)\n}\n\nfunc (board *Board) AddThread(creator std.Address, title string, body string) *Post {\n\tpid := board.incGetPostID()\n\tpidkey := postIDKey(pid)\n\tthread := newPost(board, pid, creator, title, body, pid, 0, 0)\n\tboard.threads.Set(pidkey, thread)\n\treturn thread\n}\n\n// NOTE: this can be potentially very expensive for threads with many replies.\n// TODO: implement optional fast-delete where thread is simply moved.\nfunc (board *Board) DeleteThread(pid PostID) {\n\tpidkey := postIDKey(pid)\n\t_, removed := board.threads.Remove(pidkey)\n\tif !removed {\n\t\tpanic(\"thread does not exist with id \" + pid.String())\n\t}\n}\n\nfunc (board *Board) HasPermission(addr std.Address, perm Permission) bool {\n\tif board.creator == addr {\n\t\tswitch perm {\n\t\tcase EditPermission:\n\t\t\treturn true\n\t\tcase DeletePermission:\n\t\t\treturn true\n\t\tdefault:\n\t\t\treturn false\n\t\t}\n\t}\n\treturn false\n}\n\n// Renders the board for display suitable as plaintext in\n// console. This is suitable for demonstration or tests,\n// but not for prod.\nfunc (board *Board) RenderBoard() string {\n\tstr := \"\"\n\tstr += \"\\\\[[post](\" + board.GetPostFormURL() + \")]\\n\\n\"\n\tif board.threads.Size() \u003e 0 {\n\t\tboard.threads.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tif str != \"\" {\n\t\t\t\tstr += \"----------------------------------------\\n\"\n\t\t\t}\n\t\t\tstr += value.(*Post).RenderSummary() + \"\\n\"\n\t\t\treturn false\n\t\t})\n\t}\n\treturn str\n}\n\nfunc (board *Board) incGetPostID() PostID {\n\tboard.postsCtr++\n\treturn PostID(board.postsCtr)\n}\n\nfunc (board *Board) GetURLFromThreadAndReplyID(threadID, replyID PostID) string {\n\tif replyID == 0 {\n\t\treturn board.url + \"/\" + threadID.String()\n\t} else {\n\t\treturn board.url + \"/\" + threadID.String() + \"/\" + replyID.String()\n\t}\n}\n\nfunc (board *Board) GetPostFormURL() string {\n\treturn txlink.URL(\"CreateThread\", \"bid\", board.id.String())\n}\n"},{"name":"boards.gno","body":"package boards\n\nimport (\n\t\"regexp\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n//----------------------------------------\n// Realm (package) state\n\nvar (\n\tgBoards avl.Tree // id -\u003e *Board\n\tgBoardsCtr int // increments Board.id\n\tgBoardsByName avl.Tree // name -\u003e *Board\n\tgDefaultAnonFee = 100000000 // minimum fee required if anonymous\n)\n\n//----------------------------------------\n// Constants\n\nvar reName = regexp.MustCompile(`^[a-z]+[_a-z0-9]{2,29}$`)\n"},{"name":"misc.gno","body":"package boards\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/r/demo/users\"\n)\n\n//----------------------------------------\n// private utility methods\n// XXX ensure these cannot be called from public.\n\nfunc getBoard(bid BoardID) *Board {\n\tbidkey := boardIDKey(bid)\n\tboard_, exists := gBoards.Get(bidkey)\n\tif !exists {\n\t\treturn nil\n\t}\n\tboard := board_.(*Board)\n\treturn board\n}\n\nfunc incGetBoardID() BoardID {\n\tgBoardsCtr++\n\treturn BoardID(gBoardsCtr)\n}\n\nfunc padLeft(str string, length int) string {\n\tif len(str) \u003e= length {\n\t\treturn str\n\t} else {\n\t\treturn strings.Repeat(\" \", length-len(str)) + str\n\t}\n}\n\nfunc padZero(u64 uint64, length int) string {\n\tstr := strconv.Itoa(int(u64))\n\tif len(str) \u003e= length {\n\t\treturn str\n\t} else {\n\t\treturn strings.Repeat(\"0\", length-len(str)) + str\n\t}\n}\n\nfunc boardIDKey(bid BoardID) string {\n\treturn padZero(uint64(bid), 10)\n}\n\nfunc postIDKey(pid PostID) string {\n\treturn padZero(uint64(pid), 10)\n}\n\nfunc indentBody(indent string, body string) string {\n\tlines := strings.Split(body, \"\\n\")\n\tres := \"\"\n\tfor i, line := range lines {\n\t\tif i \u003e 0 {\n\t\t\tres += \"\\n\"\n\t\t}\n\t\tres += indent + line\n\t}\n\treturn res\n}\n\n// NOTE: length must be greater than 3.\nfunc summaryOf(str string, length int) string {\n\tlines := strings.SplitN(str, \"\\n\", 2)\n\tline := lines[0]\n\tif len(line) \u003e length {\n\t\tline = line[:(length-3)] + \"...\"\n\t} else if len(lines) \u003e 1 {\n\t\t// len(line) \u003c= 80\n\t\tline = line + \"...\"\n\t}\n\treturn line\n}\n\nfunc displayAddressMD(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user == nil {\n\t\treturn \"[\" + addr.String() + \"](/r/demo/users:\" + addr.String() + \")\"\n\t} else {\n\t\treturn \"[@\" + user.Name + \"](/r/demo/users:\" + user.Name + \")\"\n\t}\n}\n\nfunc usernameOf(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user == nil {\n\t\treturn \"\"\n\t}\n\treturn user.Name\n}\n"},{"name":"post.gno","body":"package boards\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/moul/txlink\"\n)\n\n//----------------------------------------\n// Post\n\n// NOTE: a PostID is relative to the board.\ntype PostID uint64\n\nfunc (pid PostID) String() string {\n\treturn strconv.Itoa(int(pid))\n}\n\n// A Post is a \"thread\" or a \"reply\" depending on context.\n// A thread is a Post of a Board that holds other replies.\ntype Post struct {\n\tboard *Board\n\tid PostID\n\tcreator std.Address\n\ttitle string // optional\n\tbody string\n\treplies avl.Tree // Post.id -\u003e *Post\n\trepliesAll avl.Tree // Post.id -\u003e *Post (all replies, for top-level posts)\n\treposts avl.Tree // Board.id -\u003e Post.id\n\tthreadID PostID // original Post.id\n\tparentID PostID // parent Post.id (if reply or repost)\n\trepostBoard BoardID // original Board.id (if repost)\n\tcreatedAt time.Time\n\tupdatedAt time.Time\n}\n\nfunc newPost(board *Board, id PostID, creator std.Address, title, body string, threadID, parentID PostID, repostBoard BoardID) *Post {\n\treturn \u0026Post{\n\t\tboard: board,\n\t\tid: id,\n\t\tcreator: creator,\n\t\ttitle: title,\n\t\tbody: body,\n\t\treplies: avl.Tree{},\n\t\trepliesAll: avl.Tree{},\n\t\treposts: avl.Tree{},\n\t\tthreadID: threadID,\n\t\tparentID: parentID,\n\t\trepostBoard: repostBoard,\n\t\tcreatedAt: time.Now(),\n\t}\n}\n\nfunc (post *Post) IsThread() bool {\n\treturn post.parentID == 0\n}\n\nfunc (post *Post) GetPostID() PostID {\n\treturn post.id\n}\n\nfunc (post *Post) AddReply(creator std.Address, body string) *Post {\n\tboard := post.board\n\tpid := board.incGetPostID()\n\tpidkey := postIDKey(pid)\n\treply := newPost(board, pid, creator, \"\", body, post.threadID, post.id, 0)\n\tpost.replies.Set(pidkey, reply)\n\tif post.threadID == post.id {\n\t\tpost.repliesAll.Set(pidkey, reply)\n\t} else {\n\t\tthread := board.GetThread(post.threadID)\n\t\tthread.repliesAll.Set(pidkey, reply)\n\t}\n\treturn reply\n}\n\nfunc (post *Post) Update(title string, body string) {\n\tpost.title = title\n\tpost.body = body\n\tpost.updatedAt = time.Now()\n}\n\nfunc (thread *Post) GetReply(pid PostID) *Post {\n\tpidkey := postIDKey(pid)\n\treplyI, ok := thread.repliesAll.Get(pidkey)\n\tif !ok {\n\t\treturn nil\n\t} else {\n\t\treturn replyI.(*Post)\n\t}\n}\n\nfunc (post *Post) AddRepostTo(creator std.Address, title, body string, dst *Board) *Post {\n\tif !post.IsThread() {\n\t\tpanic(\"cannot repost non-thread post\")\n\t}\n\tpid := dst.incGetPostID()\n\tpidkey := postIDKey(pid)\n\trepost := newPost(dst, pid, creator, title, body, pid, post.id, post.board.id)\n\tdst.threads.Set(pidkey, repost)\n\tif !dst.IsPrivate() {\n\t\tbidkey := boardIDKey(dst.id)\n\t\tpost.reposts.Set(bidkey, pid)\n\t}\n\treturn repost\n}\n\nfunc (thread *Post) DeletePost(pid PostID) {\n\tif thread.id == pid {\n\t\tpanic(\"should not happen\")\n\t}\n\tpidkey := postIDKey(pid)\n\tpostI, removed := thread.repliesAll.Remove(pidkey)\n\tif !removed {\n\t\tpanic(\"post not found in thread\")\n\t}\n\tpost := postI.(*Post)\n\tif post.parentID != thread.id {\n\t\tparent := thread.GetReply(post.parentID)\n\t\tparent.replies.Remove(pidkey)\n\t} else {\n\t\tthread.replies.Remove(pidkey)\n\t}\n}\n\nfunc (post *Post) HasPermission(addr std.Address, perm Permission) bool {\n\tif post.creator == addr {\n\t\tswitch perm {\n\t\tcase EditPermission:\n\t\t\treturn true\n\t\tcase DeletePermission:\n\t\t\treturn true\n\t\tdefault:\n\t\t\treturn false\n\t\t}\n\t}\n\t// post notes inherit permissions of the board.\n\treturn post.board.HasPermission(addr, perm)\n}\n\nfunc (post *Post) GetSummary() string {\n\treturn summaryOf(post.body, 80)\n}\n\nfunc (post *Post) GetURL() string {\n\tif post.IsThread() {\n\t\treturn post.board.GetURLFromThreadAndReplyID(\n\t\t\tpost.id, 0)\n\t} else {\n\t\treturn post.board.GetURLFromThreadAndReplyID(\n\t\t\tpost.threadID, post.id)\n\t}\n}\n\nfunc (post *Post) GetReplyFormURL() string {\n\treturn txlink.URL(\"CreateReply\",\n\t\t\"bid\", post.board.id.String(),\n\t\t\"threadid\", post.threadID.String(),\n\t\t\"postid\", post.id.String(),\n\t)\n}\n\nfunc (post *Post) GetRepostFormURL() string {\n\treturn txlink.URL(\"CreateRepost\",\n\t\t\"bid\", post.board.id.String(),\n\t\t\"postid\", post.id.String(),\n\t)\n}\n\nfunc (post *Post) GetDeleteFormURL() string {\n\treturn txlink.URL(\"DeletePost\",\n\t\t\"bid\", post.board.id.String(),\n\t\t\"threadid\", post.threadID.String(),\n\t\t\"postid\", post.id.String(),\n\t)\n}\n\nfunc (post *Post) RenderSummary() string {\n\tif post.repostBoard != 0 {\n\t\tdstBoard := getBoard(post.repostBoard)\n\t\tif dstBoard == nil {\n\t\t\tpanic(\"repostBoard does not exist\")\n\t\t}\n\t\tthread := dstBoard.GetThread(PostID(post.parentID))\n\t\tif thread == nil {\n\t\t\treturn \"reposted post does not exist\"\n\t\t}\n\t\treturn \"Repost: \" + post.GetSummary() + \"\\n\" + thread.RenderSummary()\n\t}\n\tstr := \"\"\n\tif post.title != \"\" {\n\t\tstr += \"## [\" + summaryOf(post.title, 80) + \"](\" + post.GetURL() + \")\\n\"\n\t\tstr += \"\\n\"\n\t}\n\tstr += post.GetSummary() + \"\\n\"\n\tstr += \"\\\\- \" + displayAddressMD(post.creator) + \",\"\n\tstr += \" [\" + post.createdAt.Format(\"2006-01-02 3:04pm MST\") + \"](\" + post.GetURL() + \")\"\n\tstr += \" \\\\[[x](\" + post.GetDeleteFormURL() + \")]\"\n\tstr += \" (\" + strconv.Itoa(post.replies.Size()) + \" replies)\"\n\tstr += \" (\" + strconv.Itoa(post.reposts.Size()) + \" reposts)\" + \"\\n\"\n\treturn str\n}\n\nfunc (post *Post) RenderPost(indent string, levels int) string {\n\tif post == nil {\n\t\treturn \"nil post\"\n\t}\n\tstr := \"\"\n\tif post.title != \"\" {\n\t\tstr += indent + \"# \" + post.title + \"\\n\"\n\t\tstr += indent + \"\\n\"\n\t}\n\tstr += indentBody(indent, post.body) + \"\\n\" // TODO: indent body lines.\n\tstr += indent + \"\\\\- \" + displayAddressMD(post.creator) + \", \"\n\tstr += \"[\" + post.createdAt.Format(\"2006-01-02 3:04pm (MST)\") + \"](\" + post.GetURL() + \")\"\n\tstr += \" \\\\[[reply](\" + post.GetReplyFormURL() + \")]\"\n\tif post.IsThread() {\n\t\tstr += \" \\\\[[repost](\" + post.GetRepostFormURL() + \")]\"\n\t}\n\tstr += \" \\\\[[x](\" + post.GetDeleteFormURL() + \")]\\n\"\n\tif levels \u003e 0 {\n\t\tif post.replies.Size() \u003e 0 {\n\t\t\tpost.replies.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\t\tstr += indent + \"\\n\"\n\t\t\t\tstr += value.(*Post).RenderPost(indent+\"\u003e \", levels-1)\n\t\t\t\treturn false\n\t\t\t})\n\t\t}\n\t} else {\n\t\tif post.replies.Size() \u003e 0 {\n\t\t\tstr += indent + \"\\n\"\n\t\t\tstr += indent + \"_[see all \" + strconv.Itoa(post.replies.Size()) + \" replies](\" + post.GetURL() + \")_\\n\"\n\t\t}\n\t}\n\treturn str\n}\n\n// render reply and link to context thread\nfunc (post *Post) RenderInner() string {\n\tif post.IsThread() {\n\t\tpanic(\"unexpected thread\")\n\t}\n\tthreadID := post.threadID\n\t// replyID := post.id\n\tparentID := post.parentID\n\tstr := \"\"\n\tstr += \"_[see thread](\" + post.board.GetURLFromThreadAndReplyID(\n\t\tthreadID, 0) + \")_\\n\\n\"\n\tthread := post.board.GetThread(post.threadID)\n\tvar parent *Post\n\tif thread.id == parentID {\n\t\tparent = thread\n\t} else {\n\t\tparent = thread.GetReply(parentID)\n\t}\n\tstr += parent.RenderPost(\"\", 0)\n\tstr += \"\\n\"\n\tstr += post.RenderPost(\"\u003e \", 5)\n\treturn str\n}\n"},{"name":"public.gno","body":"package boards\n\nimport (\n\t\"std\"\n\t\"strconv\"\n)\n\n//----------------------------------------\n// Public facing functions\n\nfunc GetBoardIDFromName(name string) (BoardID, bool) {\n\tboardI, exists := gBoardsByName.Get(name)\n\tif !exists {\n\t\treturn 0, false\n\t}\n\treturn boardI.(*Board).id, true\n}\n\nfunc CreateBoard(name string) BoardID {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tbid := incGetBoardID()\n\tcaller := std.OriginCaller()\n\tif usernameOf(caller) == \"\" {\n\t\tpanic(\"unauthorized\")\n\t}\n\turl := \"/r/demo/boards:\" + name\n\tboard := newBoard(bid, url, name, caller)\n\tbidkey := boardIDKey(bid)\n\tgBoards.Set(bidkey, board)\n\tgBoardsByName.Set(name, board)\n\treturn board.id\n}\n\nfunc checkAnonFee() bool {\n\tsent := std.OriginSend()\n\tanonFeeCoin := std.NewCoin(\"ugnot\", int64(gDefaultAnonFee))\n\tif len(sent) == 1 \u0026\u0026 sent[0].IsGTE(anonFeeCoin) {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc CreateThread(bid BoardID, title string, body string) PostID {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.OriginCaller()\n\tif usernameOf(caller) == \"\" {\n\t\tif !checkAnonFee() {\n\t\t\tpanic(\"please register, otherwise minimum fee \" + strconv.Itoa(gDefaultAnonFee) + \" is required if anonymous\")\n\t\t}\n\t}\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"board not exist\")\n\t}\n\tthread := board.AddThread(caller, title, body)\n\treturn thread.id\n}\n\nfunc CreateReply(bid BoardID, threadid, postid PostID, body string) PostID {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.OriginCaller()\n\tif usernameOf(caller) == \"\" {\n\t\tif !checkAnonFee() {\n\t\t\tpanic(\"please register, otherwise minimum fee \" + strconv.Itoa(gDefaultAnonFee) + \" is required if anonymous\")\n\t\t}\n\t}\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"board not exist\")\n\t}\n\tthread := board.GetThread(threadid)\n\tif thread == nil {\n\t\tpanic(\"thread not exist\")\n\t}\n\tif postid == threadid {\n\t\treply := thread.AddReply(caller, body)\n\t\treturn reply.id\n\t} else {\n\t\tpost := thread.GetReply(postid)\n\t\treply := post.AddReply(caller, body)\n\t\treturn reply.id\n\t}\n}\n\n// If dstBoard is private, does not ping back.\n// If board specified by bid is private, panics.\nfunc CreateRepost(bid BoardID, postid PostID, title string, body string, dstBoardID BoardID) PostID {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.OriginCaller()\n\tif usernameOf(caller) == \"\" {\n\t\t// TODO: allow with gDefaultAnonFee payment.\n\t\tif !checkAnonFee() {\n\t\t\tpanic(\"please register, otherwise minimum fee \" + strconv.Itoa(gDefaultAnonFee) + \" is required if anonymous\")\n\t\t}\n\t}\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"src board not exist\")\n\t}\n\tif board.IsPrivate() {\n\t\tpanic(\"cannot repost from a private board\")\n\t}\n\tdst := getBoard(dstBoardID)\n\tif dst == nil {\n\t\tpanic(\"dst board not exist\")\n\t}\n\tthread := board.GetThread(postid)\n\tif thread == nil {\n\t\tpanic(\"thread not exist\")\n\t}\n\trepost := thread.AddRepostTo(caller, title, body, dst)\n\treturn repost.id\n}\n\nfunc DeletePost(bid BoardID, threadid, postid PostID, reason string) {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.OriginCaller()\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"board not exist\")\n\t}\n\tthread := board.GetThread(threadid)\n\tif thread == nil {\n\t\tpanic(\"thread not exist\")\n\t}\n\tif postid == threadid {\n\t\t// delete thread\n\t\tif !thread.HasPermission(caller, DeletePermission) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t\tboard.DeleteThread(threadid)\n\t} else {\n\t\t// delete thread's post\n\t\tpost := thread.GetReply(postid)\n\t\tif post == nil {\n\t\t\tpanic(\"post not exist\")\n\t\t}\n\t\tif !post.HasPermission(caller, DeletePermission) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t\tthread.DeletePost(postid)\n\t}\n}\n\nfunc EditPost(bid BoardID, threadid, postid PostID, title, body string) {\n\tif !(std.IsOriginCall() || std.PrevRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.OriginCaller()\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"board not exist\")\n\t}\n\tthread := board.GetThread(threadid)\n\tif thread == nil {\n\t\tpanic(\"thread not exist\")\n\t}\n\tif postid == threadid {\n\t\t// edit thread\n\t\tif !thread.HasPermission(caller, EditPermission) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t\tthread.Update(title, body)\n\t} else {\n\t\t// edit thread's post\n\t\tpost := thread.GetReply(postid)\n\t\tif post == nil {\n\t\t\tpanic(\"post not exist\")\n\t\t}\n\t\tif !post.HasPermission(caller, EditPermission) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t\tpost.Update(title, body)\n\t}\n}\n"},{"name":"render.gno","body":"package boards\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n)\n\n//----------------------------------------\n// Render functions\n\nfunc RenderBoard(bid BoardID) string {\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\treturn \"missing board\"\n\t}\n\treturn board.RenderBoard()\n}\n\nfunc Render(path string) string {\n\tif path == \"\" {\n\t\tstr := \"These are all the boards of this realm:\\n\\n\"\n\t\tgBoards.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tboard := value.(*Board)\n\t\t\tstr += \" * [\" + board.url + \"](\" + board.url + \")\\n\"\n\t\t\treturn false\n\t\t})\n\t\treturn str\n\t}\n\tparts := strings.Split(path, \"/\")\n\tif len(parts) == 1 {\n\t\t// /r/demo/boards:BOARD_NAME\n\t\tname := parts[0]\n\t\tboardI, exists := gBoardsByName.Get(name)\n\t\tif !exists {\n\t\t\treturn \"board does not exist: \" + name\n\t\t}\n\t\treturn boardI.(*Board).RenderBoard()\n\t} else if len(parts) == 2 {\n\t\t// /r/demo/boards:BOARD_NAME/THREAD_ID\n\t\tname := parts[0]\n\t\tboardI, exists := gBoardsByName.Get(name)\n\t\tif !exists {\n\t\t\treturn \"board does not exist: \" + name\n\t\t}\n\t\tpid, err := strconv.Atoi(parts[1])\n\t\tif err != nil {\n\t\t\treturn \"invalid thread id: \" + parts[1]\n\t\t}\n\t\tboard := boardI.(*Board)\n\t\tthread := board.GetThread(PostID(pid))\n\t\tif thread == nil {\n\t\t\treturn \"thread does not exist with id: \" + parts[1]\n\t\t}\n\t\treturn thread.RenderPost(\"\", 5)\n\t} else if len(parts) == 3 {\n\t\t// /r/demo/boards:BOARD_NAME/THREAD_ID/REPLY_ID\n\t\tname := parts[0]\n\t\tboardI, exists := gBoardsByName.Get(name)\n\t\tif !exists {\n\t\t\treturn \"board does not exist: \" + name\n\t\t}\n\t\tpid, err := strconv.Atoi(parts[1])\n\t\tif err != nil {\n\t\t\treturn \"invalid thread id: \" + parts[1]\n\t\t}\n\t\tboard := boardI.(*Board)\n\t\tthread := board.GetThread(PostID(pid))\n\t\tif thread == nil {\n\t\t\treturn \"thread does not exist with id: \" + parts[1]\n\t\t}\n\t\trid, err := strconv.Atoi(parts[2])\n\t\tif err != nil {\n\t\t\treturn \"invalid reply id: \" + parts[2]\n\t\t}\n\t\treply := thread.GetReply(PostID(rid))\n\t\tif reply == nil {\n\t\t\treturn \"reply does not exist with id: \" + parts[2]\n\t\t}\n\t\treturn reply.RenderInner()\n\t} else {\n\t\treturn \"unrecognized path \" + path\n\t}\n}\n"},{"name":"role.gno","body":"package boards\n\ntype Permission string\n\nconst (\n\tDeletePermission Permission = \"role:delete\"\n\tEditPermission Permission = \"role:edit\"\n)\n"},{"name":"z_0_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\nimport (\n\t\"gno.land/r/demo/boards\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid := boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// unauthorized\n"},{"name":"z_0_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 19900000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid = boards.CreateBoard(\"test_board\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// payment must not be less than 20000000\n"},{"name":"z_0_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tboards.CreateThread(1, \"First Post (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// board not exist\n"},{"name":"z_0_d_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateReply(bid, 0, 0, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// thread not exist\n"},{"name":"z_0_e_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tboards.CreateReply(bid, 0, 0, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// board not exist\n"},{"name":"z_0_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 20000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid := boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Output:\n// \\[[post](/r/demo/boards$help\u0026func=CreateThread\u0026bid=1)]\n//\n// ----------------------------------------\n// ## [First Post (title)](/r/demo/boards:test_board/1)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/1) \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)] (0 replies) (0 reposts)\n//\n// ----------------------------------------\n// ## [Second Post (title)](/r/demo/boards:test_board/2)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/2) \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)] (1 replies) (0 reposts)\n"},{"name":"z_10_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// boardId 2 not exist\n\tboards.DeletePost(2, pid, pid, \"\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// board not exist\n"},{"name":"z_10_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// pid of 2 not exist\n\tboards.DeletePost(bid, 2, 2, \"\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// thread not exist\n"},{"name":"z_10_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"First reply of the First post\\n\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.DeletePost(bid, pid, rid, \"\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e First reply of the First post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n//\n// ----------------------------------------------------\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n"},{"name":"z_10_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.DeletePost(bid, pid, pid, \"\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// ----------------------------------------------------\n// thread does not exist with id: 1\n"},{"name":"z_11_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// board 2 not exist\n\tboards.EditPost(2, pid, pid, \"Edited: First Post in (title)\", \"Edited: Body of the first post. (body)\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// board not exist\n"},{"name":"z_11_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// thread 2 not exist\n\tboards.EditPost(bid, 2, pid, \"Edited: First Post in (title)\", \"Edited: Body of the first post. (body)\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// thread not exist\n"},{"name":"z_11_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// post 2 not exist\n\tboards.EditPost(bid, pid, 2, \"Edited: First Post in (title)\", \"Edited: Body of the first post. (body)\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// post not exist\n"},{"name":"z_11_d_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"First reply of the First post\\n\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.EditPost(bid, pid, rid, \"\", \"Edited: First reply of the First post\\n\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e First reply of the First post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n//\n// ----------------------------------------------------\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e Edited: First reply of the First post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n"},{"name":"z_11_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.EditPost(bid, pid, pid, \"Edited: First Post in (title)\", \"Edited: Body of the first post. (body)\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// ----------------------------------------------------\n// # Edited: First Post in (title)\n//\n// Edited: Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n"},{"name":"z_12_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// create a post via registered user\n\tbid1 := boards.CreateBoard(\"test_board1\")\n\tpid := boards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tbid2 := boards.CreateBoard(\"test_board2\")\n\n\t// create a repost via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOriginCaller(test2)\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\n\trid := boards.CreateRepost(bid1, pid, \"\", \"Check this out\", bid2)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board1\"))\n}\n\n// Error:\n// please register, otherwise minimum fee 100000000 is required if anonymous\n"},{"name":"z_12_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid1 := boards.CreateBoard(\"test_board1\")\n\tpid := boards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tbid2 := boards.CreateBoard(\"test_board2\")\n\n\t// create a repost to a non-existing board\n\trid := boards.CreateRepost(5, pid, \"\", \"Check this out\", bid2)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board1\"))\n}\n\n// Error:\n// src board not exist\n"},{"name":"z_12_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid1 := boards.CreateBoard(\"test_board1\")\n\tboards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tbid2 := boards.CreateBoard(\"test_board2\")\n\n\t// create a repost to a non-existing thread\n\trid := boards.CreateRepost(bid1, 5, \"\", \"Check this out\", bid2)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board1\"))\n}\n\n// Error:\n// thread not exist\n"},{"name":"z_12_d_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid1 := boards.CreateBoard(\"test_board1\")\n\tpid := boards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tboards.CreateBoard(\"test_board2\")\n\n\t// create a repost to a non-existing destination board\n\trid := boards.CreateRepost(bid1, pid, \"\", \"Check this out\", 5)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board1\"))\n}\n\n// Error:\n// dst board not exist\n"},{"name":"z_12_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid1 boards.BoardID\n\tbid2 boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid1 = boards.CreateBoard(\"test_board1\")\n\tpid = boards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tbid2 = boards.CreateBoard(\"test_board2\")\n}\n\nfunc main() {\n\trid := boards.CreateRepost(bid1, pid, \"\", \"Check this out\", bid2)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board2\"))\n}\n\n// Output:\n// 1\n// \\[[post](/r/demo/boards$help\u0026func=CreateThread\u0026bid=2)]\n//\n// ----------------------------------------\n// Repost: Check this out\n// ## [First Post (title)](/r/demo/boards:test_board1/1)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board1/1) \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)] (0 replies) (1 reposts)\n"},{"name":"z_1_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar board *boards.Board\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\t_ = boards.CreateBoard(\"test_board_1\")\n\t_ = boards.CreateBoard(\"test_board_2\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"\"))\n}\n\n// Output:\n// These are all the boards of this realm:\n//\n// * [/r/demo/boards:test_board_1](/r/demo/boards:test_board_1)\n// * [/r/demo/boards:test_board_2](/r/demo/boards:test_board_2)\n"},{"name":"z_2_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n"},{"name":"z_3_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n}\n\nfunc main() {\n\trid := boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// 3\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n"},{"name":"z_4_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid := boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n\tprintln(rid)\n}\n\nfunc main() {\n\trid2 := boards.CreateReply(bid, pid, pid, \"Second reply of the second post\")\n\tprintln(rid2)\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// 3\n// 4\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n//\n// \u003e Second reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=4)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=4)]\n\n// Realm:\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/boards\"]\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111\",\n// \"ModTime\": \"123\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"68663c8895d37d479e417c11e21badfe21345c61\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:112\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Post\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"3f34ac77289aa1d5f9a2f8b6d083138325816fb0\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {},\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"94a6665a44bac6ede7f3e3b87173e537b12f9532\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"bc8e5b4e782a0bbc4ac9689681f119beb7b34d59\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"9957eadbc91dd32f33b0d815e041a32dbdea0671\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131]={\n// \"Fields\": [\n// {\n// \"N\": \"AAAAgJSeXbo=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"65536\"\n// }\n// },\n// {\n// \"N\": \"AbSNdvQQIhE=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"1024\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Location\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"336074805fc853987abe6f7fe3ad97a6a6f3077a:2\"\n// },\n// \"Index\": \"182\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"65536\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"1024\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Location\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Board\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:84\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"N\": \"BAAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.PostID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"std.Address\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"Second reply of the second post\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"f91e355bd19240f0f3350a7fa0e6a82b72225916\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"9ee9c4117be283fc51ffcc5ecd65b75ecef5a9dd\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"eb768b0140a5fe95f9c58747f0960d647dacfd42\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.PostID\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.PostID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.BoardID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Time\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"0fd3352422af0a56a77ef2c9e88f479054e3d51f\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Time\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"bed4afa8ffdbbf775451c947fc68b27a345ce32a\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132\"\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\",\n// \"IsEscaped\": true,\n// \"ModTime\": \"0\",\n// \"RefCount\": \"2\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Post\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"c45bbd47a46681a63af973db0ec2180922e4a8ae\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\"\n// }\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120\",\n// \"ModTime\": \"134\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"dc1f011553dc53e7a846049e08cc77fa35ea6a51\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:121\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Post\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"96b86b4585c7f1075d7794180a5581f72733a7ab\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {},\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"32274e1f28fb2b97d67a1262afd362d370de7faa\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"c2cfd6aec36a462f35bf02e5bf4a127aa1bb7ac2\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"5cb875179e86d32c517322af7a323b2a5f3e6cc5\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\"\n// }\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:85]={\n// \"Fields\": [\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.BoardID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"/r/demo/boards:test_board\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"test_board\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"std.Address\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"a416a751c3a45a1e5cba11e737c51340b081e372\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:86\"\n// }\n// },\n// {\n// \"N\": \"BAAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"65536\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Time\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"36299fccbc13f2a84c4629fad4cb940f0bd4b1c6\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:87\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"af6ed0268f99b7f369329094eb6dfaea7812708b\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:88\"\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:85\",\n// \"ModTime\": \"121\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:84\",\n// \"RefCount\": \"1\"\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"9809329dc1ddc5d3556f7a8fa3c2cebcbf65560b\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106\",\n// \"ModTime\": \"121\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:105\",\n// \"RefCount\": \"1\"\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"ceae9a1c4ed28bb51062e6ccdccfad0caafd1c4f\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107\",\n// \"ModTime\": \"121\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:105\",\n// \"RefCount\": \"1\"\n// }\n// }\n// switchrealm[\"gno.land/r/demo/boards\"]\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/boards\"]\n// switchrealm[\"gno.land/r/demo/boards_test\"]\n"},{"name":"z_5_b_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// create board via registered user\n\tbid := boards.CreateBoard(\"test_board\")\n\n\t// create post via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOriginCaller(test2)\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\n\tpid := boards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// please register, otherwise minimum fee 100000000 is required if anonymous\n"},{"name":"z_5_c_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// create board via registered user\n\tbid := boards.CreateBoard(\"test_board\")\n\n\t// create post via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOriginCaller(test2)\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 101000000}}, nil)\n\n\tpid := boards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the first post\")\n\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post (title)\n//\n// Body of the first post. (body)\n// \\- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/r/demo/users:g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e Reply of the first post\n// \u003e \\- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/r/demo/users:g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n"},{"name":"z_5_d_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// create board via registered user\n\tbid := boards.CreateBoard(\"test_board\")\n\tpid := boards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\n\t// create reply via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOriginCaller(test2)\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\tboards.CreateReply(bid, pid, pid, \"Reply of the first post\")\n\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// please register, otherwise minimum fee 100000000 is required if anonymous\n"},{"name":"z_5_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid := boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\trid2 := boards.CreateReply(bid, pid, pid, \"Second reply of the second post\\n\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n//\n// \u003e Second reply of the second post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=4)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=4)]\n"},{"name":"z_6_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tboards.CreateReply(bid, pid, pid, \"Second reply of the second post\\n\")\n\tboards.CreateReply(bid, pid, rid, \"First reply of the first reply\\n\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n// \u003e\n// \u003e \u003e First reply of the first reply\n// \u003e \u003e\n// \u003e \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/5) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=5)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=5)]\n//\n// \u003e Second reply of the second post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=4)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=4)]\n"},{"name":"z_7_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc init() {\n\t// register\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\t// create board and post\n\tbid := boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Output:\n// \\[[post](/r/demo/boards$help\u0026func=CreateThread\u0026bid=1)]\n//\n// ----------------------------------------\n// ## [First Post (title)](/r/demo/boards:test_board/1)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/1) \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)] (0 replies) (0 reposts)\n"},{"name":"z_8_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tboards.CreateReply(bid, pid, pid, \"Second reply of the second post\\n\")\n\trid2 := boards.CreateReply(bid, pid, rid, \"First reply of the first reply\\n\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid)) + \"/\" + strconv.Itoa(int(rid2))))\n}\n\n// Output:\n// _[see thread](/r/demo/boards:test_board/2)_\n//\n// Reply of the second post\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n//\n// _[see all 1 replies](/r/demo/boards:test_board/2/3)_\n//\n// \u003e First reply of the first reply\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/5) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=5)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=5)]\n"},{"name":"z_9_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar dstBoard boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tdstBoard = boards.CreateBoard(\"dst_board\")\n\n\tboards.CreateRepost(0, 0, \"First Post in (title)\", \"Body of the first post. (body)\", dstBoard)\n}\n\nfunc main() {\n}\n\n// Error:\n// src board not exist\n"},{"name":"z_9_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tsrcBoard boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tsrcBoard = boards.CreateBoard(\"first_board\")\n\tpid = boards.CreateThread(srcBoard, \"First Post in (title)\", \"Body of the first post. (body)\")\n\n\tboards.CreateRepost(srcBoard, pid, \"First Post in (title)\", \"Body of the first post. (body)\", 0)\n}\n\nfunc main() {\n}\n\n// Error:\n// dst board not exist\n"},{"name":"z_9_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tfirstBoard boards.BoardID\n\tsecondBoard boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tfirstBoard = boards.CreateBoard(\"first_board\")\n\tsecondBoard = boards.CreateBoard(\"second_board\")\n\tpid = boards.CreateThread(firstBoard, \"First Post in (title)\", \"Body of the first post. (body)\")\n\n\tboards.CreateRepost(firstBoard, pid, \"First Post in (title)\", \"Body of the first post. (body)\", secondBoard)\n}\n\nfunc main() {\n\tprintln(boards.Render(\"second_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:second_board/1/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=2\u0026threadid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=2\u0026threadid=1\u0026postid=1)]\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":"boards","path":"gno.land/r/demo/boards","files":[{"name":"README.md","body":"This is a demo of Gno smart contract programming. This document was\nconstructed by Gno onto a smart contract hosted on the data Realm\nname [\"gno.land/r/demo/boards\"](https://gno.land/r/demo/boards/)\n([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)).\n\n\n\n## Build `gnokey`, create your account, and interact with Gno.\n\nNOTE: Where you see `-remote localhost:26657` here, that flag can be replaced\nwith `-remote gno.land:26657` if you have $GNOT on the testnet.\n(To use the testnet, also replace `-chainid dev` with `-chainid portal-loop` .)\n\n### Build `gnokey` (and other tools).\n\n```bash\ngit clone git@github.com:gnolang/gno.git\ncd gno/gno.land\nmake build\n```\n\n### Generate a seed/mnemonic code.\n\n```bash\n./build/gnokey generate\n```\n\nNOTE: You can generate 24 words with any good bip39 generator.\n\n### Create a new account using your mnemonic.\n\n```bash\n./build/gnokey add -recover KEYNAME\n```\n\nNOTE: `KEYNAME` is your key identifier, and should be changed.\n\n### Verify that you can see your account locally.\n\n```bash\n./build/gnokey list\n```\n\nTake note of your `addr` which looks something like `g17sphqax3kasjptdkmuqvn740u8dhtx4kxl6ljf` .\nYou will use this as your `ACCOUNT_ADDR`.\n\n## Interact with the blockchain.\n\n### Add $GNOT for your account.\n\nBefore starting the `gnoland` node for the first time, your new account can be given $GNOT in the node genesis.\nEdit the file `gno.land/genesis/genesis_balances.txt` and add the following line (simlar to the others), using\nyour `ACCOUNT_ADDR` and `KEYNAME`\n\n`ACCOUNT_ADDR=10000000000ugnot # @KEYNAME`\n\n### Alternative: Run a faucet to add $GNOT.\n\nInstead of editing `gno.land/genesis/genesis_balances.txt`, a more general solution (with more steps)\nis to run a local \"faucet\" and use the web browser to add $GNOT. (This can be done at any time.)\nSee this page: https://github.com/gnolang/gno/blob/master/contribs/gnofaucet/README.md\n\n\n### Start the `gnoland` node.\n\n```bash\n./build/gnoland start\n```\n\nNOTE: The node already has the \"boards\" realm.\n\nLeave this running in the terminal. In a new terminal, cd to the same folder `gno/gno.land` .\n\n### Get your current balance, account number, and sequence number.\n\n```bash\n./build/gnokey query auth/accounts/ACCOUNT_ADDR -remote localhost:26657\n```\n\n### Register a board username with a smart contract call.\n\nThe `USERNAME` for posting can different than your `KEYNAME`. It is internally linked to your `ACCOUNT_ADDR`. It must be at least 6 characters, lowercase alphanumeric with underscore.\n\n```bash\n./build/gnokey maketx call -pkgpath \"gno.land/r/demo/users\" -func \"Register\" -args \"\" -args \"USERNAME\" -args \"Profile description\" -gas-fee \"10000000ugnot\" -gas-wanted \"2000000\" -send \"200000000ugnot\" -broadcast -chainid dev -remote 127.0.0.1:26657 KEYNAME\n```\n\nInteractive documentation: https://gno.land/r/demo/users$help\u0026func=Register\n\n### Create a board with a smart contract call.\n\n```bash\n./build/gnokey maketx call -pkgpath \"gno.land/r/demo/boards\" -func \"CreateBoard\" -args \"BOARDNAME\" -gas-fee \"1000000ugnot\" -gas-wanted \"10000000\" -broadcast -chainid dev -remote localhost:26657 KEYNAME\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateBoard\n\nNext, query for the permanent board ID by querying (you need this to create a new post):\n\n```bash\n./build/gnokey query \"vm/qeval\" -data 'gno.land/r/demo/boards.GetBoardIDFromName(\"BOARDNAME\")' -remote localhost:26657\n```\n\n### Create a post of a board with a smart contract call.\n\nNOTE: If a board was created successfully, your SEQUENCE_NUMBER would have increased.\n\n```bash\n./build/gnokey maketx call -pkgpath \"gno.land/r/demo/boards\" -func \"CreateThread\" -args BOARD_ID -args \"Hello gno.land\" -args \"Text of the post\" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid dev -remote localhost:26657 KEYNAME\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateThread\n\n### Create a comment to a post.\n\n```bash\n./build/gnokey maketx call -pkgpath \"gno.land/r/demo/boards\" -func \"CreateReply\" -args BOARD_ID -args \"1\" -args \"1\" -args \"Nice to meet you too.\" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid dev -remote localhost:26657 KEYNAME\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateReply\n\n```bash\n./build/gnokey query \"vm/qrender\" -data \"gno.land/r/demo/boards:BOARDNAME/1\" -remote localhost:26657\n```\n\n### Render page with optional path expression.\n\nThe contents of `https://gno.land/r/demo/boards:` and `https://gno.land/r/demo/boards:gnolang` are rendered by calling\nthe `Render(path string)` function like so:\n\n```bash\n./build/gnokey query \"vm/qrender\" -data \"gno.land/r/demo/boards:gnolang\"\n```\n## View the board in the browser.\n\n### Start the web server.\n\n```bash\n./build/gnoweb\n```\n\nThis should print something like `Running on http://127.0.0.1:8888` . Leave this running in the terminal.\n\n### View in the browser\n\nIn your browser, navigate to the printed address http://127.0.0.1:8888 .\nTo see you post, click on the package `/r/demo/boards` .\n"},{"name":"board.gno","body":"package boards\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/moul/txlink\"\n)\n\n//----------------------------------------\n// Board\n\ntype BoardID uint64\n\nfunc (bid BoardID) String() string {\n\treturn strconv.Itoa(int(bid))\n}\n\ntype Board struct {\n\tid BoardID // only set for public boards.\n\turl string\n\tname string\n\tcreator std.Address\n\tthreads avl.Tree // Post.id -\u003e *Post\n\tpostsCtr uint64 // increments Post.id\n\tcreatedAt time.Time\n\tdeleted avl.Tree // TODO reserved for fast-delete.\n}\n\nfunc newBoard(id BoardID, url string, name string, creator std.Address) *Board {\n\tif !reName.MatchString(name) {\n\t\tpanic(\"invalid name: \" + name)\n\t}\n\texists := gBoardsByName.Has(name)\n\tif exists {\n\t\tpanic(\"board already exists\")\n\t}\n\treturn \u0026Board{\n\t\tid: id,\n\t\turl: url,\n\t\tname: name,\n\t\tcreator: creator,\n\t\tthreads: avl.Tree{},\n\t\tcreatedAt: time.Now(),\n\t\tdeleted: avl.Tree{},\n\t}\n}\n\n/* TODO support this once we figure out how to ensure URL correctness.\n// A private board is not tracked by gBoards*,\n// but must be persisted by the caller's realm.\n// Private boards have 0 id and does not ping\n// back the remote board on reposts.\nfunc NewPrivateBoard(url string, name string, creator std.Address) *Board {\n\treturn newBoard(0, url, name, creator)\n}\n*/\n\nfunc (board *Board) IsPrivate() bool {\n\treturn board.id == 0\n}\n\nfunc (board *Board) GetThread(pid PostID) *Post {\n\tpidkey := postIDKey(pid)\n\tpostI, exists := board.threads.Get(pidkey)\n\tif !exists {\n\t\treturn nil\n\t}\n\treturn postI.(*Post)\n}\n\nfunc (board *Board) AddThread(creator std.Address, title string, body string) *Post {\n\tpid := board.incGetPostID()\n\tpidkey := postIDKey(pid)\n\tthread := newPost(board, pid, creator, title, body, pid, 0, 0)\n\tboard.threads.Set(pidkey, thread)\n\treturn thread\n}\n\n// NOTE: this can be potentially very expensive for threads with many replies.\n// TODO: implement optional fast-delete where thread is simply moved.\nfunc (board *Board) DeleteThread(pid PostID) {\n\tpidkey := postIDKey(pid)\n\t_, removed := board.threads.Remove(pidkey)\n\tif !removed {\n\t\tpanic(\"thread does not exist with id \" + pid.String())\n\t}\n}\n\nfunc (board *Board) HasPermission(addr std.Address, perm Permission) bool {\n\tif board.creator == addr {\n\t\tswitch perm {\n\t\tcase EditPermission:\n\t\t\treturn true\n\t\tcase DeletePermission:\n\t\t\treturn true\n\t\tdefault:\n\t\t\treturn false\n\t\t}\n\t}\n\treturn false\n}\n\n// Renders the board for display suitable as plaintext in\n// console. This is suitable for demonstration or tests,\n// but not for prod.\nfunc (board *Board) RenderBoard() string {\n\tstr := \"\"\n\tstr += \"\\\\[[post](\" + board.GetPostFormURL() + \")]\\n\\n\"\n\tif board.threads.Size() \u003e 0 {\n\t\tboard.threads.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tif str != \"\" {\n\t\t\t\tstr += \"----------------------------------------\\n\"\n\t\t\t}\n\t\t\tstr += value.(*Post).RenderSummary() + \"\\n\"\n\t\t\treturn false\n\t\t})\n\t}\n\treturn str\n}\n\nfunc (board *Board) incGetPostID() PostID {\n\tboard.postsCtr++\n\treturn PostID(board.postsCtr)\n}\n\nfunc (board *Board) GetURLFromThreadAndReplyID(threadID, replyID PostID) string {\n\tif replyID == 0 {\n\t\treturn board.url + \"/\" + threadID.String()\n\t} else {\n\t\treturn board.url + \"/\" + threadID.String() + \"/\" + replyID.String()\n\t}\n}\n\nfunc (board *Board) GetPostFormURL() string {\n\treturn txlink.URL(\"CreateThread\", \"bid\", board.id.String())\n}\n"},{"name":"boards.gno","body":"package boards\n\nimport (\n\t\"regexp\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n//----------------------------------------\n// Realm (package) state\n\nvar (\n\tgBoards avl.Tree // id -\u003e *Board\n\tgBoardsCtr int // increments Board.id\n\tgBoardsByName avl.Tree // name -\u003e *Board\n\tgDefaultAnonFee = 100000000 // minimum fee required if anonymous\n)\n\n//----------------------------------------\n// Constants\n\nvar reName = regexp.MustCompile(`^[a-z]+[_a-z0-9]{2,29}$`)\n"},{"name":"misc.gno","body":"package boards\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/r/demo/users\"\n)\n\n//----------------------------------------\n// private utility methods\n// XXX ensure these cannot be called from public.\n\nfunc getBoard(bid BoardID) *Board {\n\tbidkey := boardIDKey(bid)\n\tboard_, exists := gBoards.Get(bidkey)\n\tif !exists {\n\t\treturn nil\n\t}\n\tboard := board_.(*Board)\n\treturn board\n}\n\nfunc incGetBoardID() BoardID {\n\tgBoardsCtr++\n\treturn BoardID(gBoardsCtr)\n}\n\nfunc padLeft(str string, length int) string {\n\tif len(str) \u003e= length {\n\t\treturn str\n\t} else {\n\t\treturn strings.Repeat(\" \", length-len(str)) + str\n\t}\n}\n\nfunc padZero(u64 uint64, length int) string {\n\tstr := strconv.Itoa(int(u64))\n\tif len(str) \u003e= length {\n\t\treturn str\n\t} else {\n\t\treturn strings.Repeat(\"0\", length-len(str)) + str\n\t}\n}\n\nfunc boardIDKey(bid BoardID) string {\n\treturn padZero(uint64(bid), 10)\n}\n\nfunc postIDKey(pid PostID) string {\n\treturn padZero(uint64(pid), 10)\n}\n\nfunc indentBody(indent string, body string) string {\n\tlines := strings.Split(body, \"\\n\")\n\tres := \"\"\n\tfor i, line := range lines {\n\t\tif i \u003e 0 {\n\t\t\tres += \"\\n\"\n\t\t}\n\t\tres += indent + line\n\t}\n\treturn res\n}\n\n// NOTE: length must be greater than 3.\nfunc summaryOf(str string, length int) string {\n\tlines := strings.SplitN(str, \"\\n\", 2)\n\tline := lines[0]\n\tif len(line) \u003e length {\n\t\tline = line[:(length-3)] + \"...\"\n\t} else if len(lines) \u003e 1 {\n\t\t// len(line) \u003c= 80\n\t\tline = line + \"...\"\n\t}\n\treturn line\n}\n\nfunc displayAddressMD(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user == nil {\n\t\treturn \"[\" + addr.String() + \"](/r/demo/users:\" + addr.String() + \")\"\n\t} else {\n\t\treturn \"[@\" + user.Name + \"](/r/demo/users:\" + user.Name + \")\"\n\t}\n}\n\nfunc usernameOf(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user == nil {\n\t\treturn \"\"\n\t}\n\treturn user.Name\n}\n"},{"name":"post.gno","body":"package boards\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/moul/txlink\"\n)\n\n//----------------------------------------\n// Post\n\n// NOTE: a PostID is relative to the board.\ntype PostID uint64\n\nfunc (pid PostID) String() string {\n\treturn strconv.Itoa(int(pid))\n}\n\n// A Post is a \"thread\" or a \"reply\" depending on context.\n// A thread is a Post of a Board that holds other replies.\ntype Post struct {\n\tboard *Board\n\tid PostID\n\tcreator std.Address\n\ttitle string // optional\n\tbody string\n\treplies avl.Tree // Post.id -\u003e *Post\n\trepliesAll avl.Tree // Post.id -\u003e *Post (all replies, for top-level posts)\n\treposts avl.Tree // Board.id -\u003e Post.id\n\tthreadID PostID // original Post.id\n\tparentID PostID // parent Post.id (if reply or repost)\n\trepostBoard BoardID // original Board.id (if repost)\n\tcreatedAt time.Time\n\tupdatedAt time.Time\n}\n\nfunc newPost(board *Board, id PostID, creator std.Address, title, body string, threadID, parentID PostID, repostBoard BoardID) *Post {\n\treturn \u0026Post{\n\t\tboard: board,\n\t\tid: id,\n\t\tcreator: creator,\n\t\ttitle: title,\n\t\tbody: body,\n\t\treplies: avl.Tree{},\n\t\trepliesAll: avl.Tree{},\n\t\treposts: avl.Tree{},\n\t\tthreadID: threadID,\n\t\tparentID: parentID,\n\t\trepostBoard: repostBoard,\n\t\tcreatedAt: time.Now(),\n\t}\n}\n\nfunc (post *Post) IsThread() bool {\n\treturn post.parentID == 0\n}\n\nfunc (post *Post) GetPostID() PostID {\n\treturn post.id\n}\n\nfunc (post *Post) AddReply(creator std.Address, body string) *Post {\n\tboard := post.board\n\tpid := board.incGetPostID()\n\tpidkey := postIDKey(pid)\n\treply := newPost(board, pid, creator, \"\", body, post.threadID, post.id, 0)\n\tpost.replies.Set(pidkey, reply)\n\tif post.threadID == post.id {\n\t\tpost.repliesAll.Set(pidkey, reply)\n\t} else {\n\t\tthread := board.GetThread(post.threadID)\n\t\tthread.repliesAll.Set(pidkey, reply)\n\t}\n\treturn reply\n}\n\nfunc (post *Post) Update(title string, body string) {\n\tpost.title = title\n\tpost.body = body\n\tpost.updatedAt = time.Now()\n}\n\nfunc (thread *Post) GetReply(pid PostID) *Post {\n\tpidkey := postIDKey(pid)\n\treplyI, ok := thread.repliesAll.Get(pidkey)\n\tif !ok {\n\t\treturn nil\n\t} else {\n\t\treturn replyI.(*Post)\n\t}\n}\n\nfunc (post *Post) AddRepostTo(creator std.Address, title, body string, dst *Board) *Post {\n\tif !post.IsThread() {\n\t\tpanic(\"cannot repost non-thread post\")\n\t}\n\tpid := dst.incGetPostID()\n\tpidkey := postIDKey(pid)\n\trepost := newPost(dst, pid, creator, title, body, pid, post.id, post.board.id)\n\tdst.threads.Set(pidkey, repost)\n\tif !dst.IsPrivate() {\n\t\tbidkey := boardIDKey(dst.id)\n\t\tpost.reposts.Set(bidkey, pid)\n\t}\n\treturn repost\n}\n\nfunc (thread *Post) DeletePost(pid PostID) {\n\tif thread.id == pid {\n\t\tpanic(\"should not happen\")\n\t}\n\tpidkey := postIDKey(pid)\n\tpostI, removed := thread.repliesAll.Remove(pidkey)\n\tif !removed {\n\t\tpanic(\"post not found in thread\")\n\t}\n\tpost := postI.(*Post)\n\tif post.parentID != thread.id {\n\t\tparent := thread.GetReply(post.parentID)\n\t\tparent.replies.Remove(pidkey)\n\t} else {\n\t\tthread.replies.Remove(pidkey)\n\t}\n}\n\nfunc (post *Post) HasPermission(addr std.Address, perm Permission) bool {\n\tif post.creator == addr {\n\t\tswitch perm {\n\t\tcase EditPermission:\n\t\t\treturn true\n\t\tcase DeletePermission:\n\t\t\treturn true\n\t\tdefault:\n\t\t\treturn false\n\t\t}\n\t}\n\t// post notes inherit permissions of the board.\n\treturn post.board.HasPermission(addr, perm)\n}\n\nfunc (post *Post) GetSummary() string {\n\treturn summaryOf(post.body, 80)\n}\n\nfunc (post *Post) GetURL() string {\n\tif post.IsThread() {\n\t\treturn post.board.GetURLFromThreadAndReplyID(\n\t\t\tpost.id, 0)\n\t} else {\n\t\treturn post.board.GetURLFromThreadAndReplyID(\n\t\t\tpost.threadID, post.id)\n\t}\n}\n\nfunc (post *Post) GetReplyFormURL() string {\n\treturn txlink.URL(\"CreateReply\",\n\t\t\"bid\", post.board.id.String(),\n\t\t\"threadid\", post.threadID.String(),\n\t\t\"postid\", post.id.String(),\n\t)\n}\n\nfunc (post *Post) GetRepostFormURL() string {\n\treturn txlink.URL(\"CreateRepost\",\n\t\t\"bid\", post.board.id.String(),\n\t\t\"postid\", post.id.String(),\n\t)\n}\n\nfunc (post *Post) GetDeleteFormURL() string {\n\treturn txlink.URL(\"DeletePost\",\n\t\t\"bid\", post.board.id.String(),\n\t\t\"threadid\", post.threadID.String(),\n\t\t\"postid\", post.id.String(),\n\t)\n}\n\nfunc (post *Post) RenderSummary() string {\n\tif post.repostBoard != 0 {\n\t\tdstBoard := getBoard(post.repostBoard)\n\t\tif dstBoard == nil {\n\t\t\tpanic(\"repostBoard does not exist\")\n\t\t}\n\t\tthread := dstBoard.GetThread(PostID(post.parentID))\n\t\tif thread == nil {\n\t\t\treturn \"reposted post does not exist\"\n\t\t}\n\t\treturn \"Repost: \" + post.GetSummary() + \"\\n\" + thread.RenderSummary()\n\t}\n\tstr := \"\"\n\tif post.title != \"\" {\n\t\tstr += \"## [\" + summaryOf(post.title, 80) + \"](\" + post.GetURL() + \")\\n\"\n\t\tstr += \"\\n\"\n\t}\n\tstr += post.GetSummary() + \"\\n\"\n\tstr += \"\\\\- \" + displayAddressMD(post.creator) + \",\"\n\tstr += \" [\" + post.createdAt.Format(\"2006-01-02 3:04pm MST\") + \"](\" + post.GetURL() + \")\"\n\tstr += \" \\\\[[x](\" + post.GetDeleteFormURL() + \")]\"\n\tstr += \" (\" + strconv.Itoa(post.replies.Size()) + \" replies)\"\n\tstr += \" (\" + strconv.Itoa(post.reposts.Size()) + \" reposts)\" + \"\\n\"\n\treturn str\n}\n\nfunc (post *Post) RenderPost(indent string, levels int) string {\n\tif post == nil {\n\t\treturn \"nil post\"\n\t}\n\tstr := \"\"\n\tif post.title != \"\" {\n\t\tstr += indent + \"# \" + post.title + \"\\n\"\n\t\tstr += indent + \"\\n\"\n\t}\n\tstr += indentBody(indent, post.body) + \"\\n\" // TODO: indent body lines.\n\tstr += indent + \"\\\\- \" + displayAddressMD(post.creator) + \", \"\n\tstr += \"[\" + post.createdAt.Format(\"2006-01-02 3:04pm (MST)\") + \"](\" + post.GetURL() + \")\"\n\tstr += \" \\\\[[reply](\" + post.GetReplyFormURL() + \")]\"\n\tif post.IsThread() {\n\t\tstr += \" \\\\[[repost](\" + post.GetRepostFormURL() + \")]\"\n\t}\n\tstr += \" \\\\[[x](\" + post.GetDeleteFormURL() + \")]\\n\"\n\tif levels \u003e 0 {\n\t\tif post.replies.Size() \u003e 0 {\n\t\t\tpost.replies.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\t\tstr += indent + \"\\n\"\n\t\t\t\tstr += value.(*Post).RenderPost(indent+\"\u003e \", levels-1)\n\t\t\t\treturn false\n\t\t\t})\n\t\t}\n\t} else {\n\t\tif post.replies.Size() \u003e 0 {\n\t\t\tstr += indent + \"\\n\"\n\t\t\tstr += indent + \"_[see all \" + strconv.Itoa(post.replies.Size()) + \" replies](\" + post.GetURL() + \")_\\n\"\n\t\t}\n\t}\n\treturn str\n}\n\n// render reply and link to context thread\nfunc (post *Post) RenderInner() string {\n\tif post.IsThread() {\n\t\tpanic(\"unexpected thread\")\n\t}\n\tthreadID := post.threadID\n\t// replyID := post.id\n\tparentID := post.parentID\n\tstr := \"\"\n\tstr += \"_[see thread](\" + post.board.GetURLFromThreadAndReplyID(\n\t\tthreadID, 0) + \")_\\n\\n\"\n\tthread := post.board.GetThread(post.threadID)\n\tvar parent *Post\n\tif thread.id == parentID {\n\t\tparent = thread\n\t} else {\n\t\tparent = thread.GetReply(parentID)\n\t}\n\tstr += parent.RenderPost(\"\", 0)\n\tstr += \"\\n\"\n\tstr += post.RenderPost(\"\u003e \", 5)\n\treturn str\n}\n"},{"name":"public.gno","body":"package boards\n\nimport (\n\t\"std\"\n\t\"strconv\"\n)\n\n//----------------------------------------\n// Public facing functions\n\nfunc GetBoardIDFromName(name string) (BoardID, bool) {\n\tboardI, exists := gBoardsByName.Get(name)\n\tif !exists {\n\t\treturn 0, false\n\t}\n\treturn boardI.(*Board).id, true\n}\n\nfunc CreateBoard(name string) BoardID {\n\tif !(std.IsOriginCall() || std.PreviousRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tbid := incGetBoardID()\n\tcaller := std.OriginCaller()\n\tif usernameOf(caller) == \"\" {\n\t\tpanic(\"unauthorized\")\n\t}\n\turl := \"/r/demo/boards:\" + name\n\tboard := newBoard(bid, url, name, caller)\n\tbidkey := boardIDKey(bid)\n\tgBoards.Set(bidkey, board)\n\tgBoardsByName.Set(name, board)\n\treturn board.id\n}\n\nfunc checkAnonFee() bool {\n\tsent := std.OriginSend()\n\tanonFeeCoin := std.NewCoin(\"ugnot\", int64(gDefaultAnonFee))\n\tif len(sent) == 1 \u0026\u0026 sent[0].IsGTE(anonFeeCoin) {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc CreateThread(bid BoardID, title string, body string) PostID {\n\tif !(std.IsOriginCall() || std.PreviousRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.OriginCaller()\n\tif usernameOf(caller) == \"\" {\n\t\tif !checkAnonFee() {\n\t\t\tpanic(\"please register, otherwise minimum fee \" + strconv.Itoa(gDefaultAnonFee) + \" is required if anonymous\")\n\t\t}\n\t}\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"board not exist\")\n\t}\n\tthread := board.AddThread(caller, title, body)\n\treturn thread.id\n}\n\nfunc CreateReply(bid BoardID, threadid, postid PostID, body string) PostID {\n\tif !(std.IsOriginCall() || std.PreviousRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.OriginCaller()\n\tif usernameOf(caller) == \"\" {\n\t\tif !checkAnonFee() {\n\t\t\tpanic(\"please register, otherwise minimum fee \" + strconv.Itoa(gDefaultAnonFee) + \" is required if anonymous\")\n\t\t}\n\t}\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"board not exist\")\n\t}\n\tthread := board.GetThread(threadid)\n\tif thread == nil {\n\t\tpanic(\"thread not exist\")\n\t}\n\tif postid == threadid {\n\t\treply := thread.AddReply(caller, body)\n\t\treturn reply.id\n\t} else {\n\t\tpost := thread.GetReply(postid)\n\t\treply := post.AddReply(caller, body)\n\t\treturn reply.id\n\t}\n}\n\n// If dstBoard is private, does not ping back.\n// If board specified by bid is private, panics.\nfunc CreateRepost(bid BoardID, postid PostID, title string, body string, dstBoardID BoardID) PostID {\n\tif !(std.IsOriginCall() || std.PreviousRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.OriginCaller()\n\tif usernameOf(caller) == \"\" {\n\t\t// TODO: allow with gDefaultAnonFee payment.\n\t\tif !checkAnonFee() {\n\t\t\tpanic(\"please register, otherwise minimum fee \" + strconv.Itoa(gDefaultAnonFee) + \" is required if anonymous\")\n\t\t}\n\t}\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"src board not exist\")\n\t}\n\tif board.IsPrivate() {\n\t\tpanic(\"cannot repost from a private board\")\n\t}\n\tdst := getBoard(dstBoardID)\n\tif dst == nil {\n\t\tpanic(\"dst board not exist\")\n\t}\n\tthread := board.GetThread(postid)\n\tif thread == nil {\n\t\tpanic(\"thread not exist\")\n\t}\n\trepost := thread.AddRepostTo(caller, title, body, dst)\n\treturn repost.id\n}\n\nfunc DeletePost(bid BoardID, threadid, postid PostID, reason string) {\n\tif !(std.IsOriginCall() || std.PreviousRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.OriginCaller()\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"board not exist\")\n\t}\n\tthread := board.GetThread(threadid)\n\tif thread == nil {\n\t\tpanic(\"thread not exist\")\n\t}\n\tif postid == threadid {\n\t\t// delete thread\n\t\tif !thread.HasPermission(caller, DeletePermission) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t\tboard.DeleteThread(threadid)\n\t} else {\n\t\t// delete thread's post\n\t\tpost := thread.GetReply(postid)\n\t\tif post == nil {\n\t\t\tpanic(\"post not exist\")\n\t\t}\n\t\tif !post.HasPermission(caller, DeletePermission) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t\tthread.DeletePost(postid)\n\t}\n}\n\nfunc EditPost(bid BoardID, threadid, postid PostID, title, body string) {\n\tif !(std.IsOriginCall() || std.PreviousRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.OriginCaller()\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"board not exist\")\n\t}\n\tthread := board.GetThread(threadid)\n\tif thread == nil {\n\t\tpanic(\"thread not exist\")\n\t}\n\tif postid == threadid {\n\t\t// edit thread\n\t\tif !thread.HasPermission(caller, EditPermission) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t\tthread.Update(title, body)\n\t} else {\n\t\t// edit thread's post\n\t\tpost := thread.GetReply(postid)\n\t\tif post == nil {\n\t\t\tpanic(\"post not exist\")\n\t\t}\n\t\tif !post.HasPermission(caller, EditPermission) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t\tpost.Update(title, body)\n\t}\n}\n"},{"name":"render.gno","body":"package boards\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n)\n\n//----------------------------------------\n// Render functions\n\nfunc RenderBoard(bid BoardID) string {\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\treturn \"missing board\"\n\t}\n\treturn board.RenderBoard()\n}\n\nfunc Render(path string) string {\n\tif path == \"\" {\n\t\tstr := \"These are all the boards of this realm:\\n\\n\"\n\t\tgBoards.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tboard := value.(*Board)\n\t\t\tstr += \" * [\" + board.url + \"](\" + board.url + \")\\n\"\n\t\t\treturn false\n\t\t})\n\t\treturn str\n\t}\n\tparts := strings.Split(path, \"/\")\n\tif len(parts) == 1 {\n\t\t// /r/demo/boards:BOARD_NAME\n\t\tname := parts[0]\n\t\tboardI, exists := gBoardsByName.Get(name)\n\t\tif !exists {\n\t\t\treturn \"board does not exist: \" + name\n\t\t}\n\t\treturn boardI.(*Board).RenderBoard()\n\t} else if len(parts) == 2 {\n\t\t// /r/demo/boards:BOARD_NAME/THREAD_ID\n\t\tname := parts[0]\n\t\tboardI, exists := gBoardsByName.Get(name)\n\t\tif !exists {\n\t\t\treturn \"board does not exist: \" + name\n\t\t}\n\t\tpid, err := strconv.Atoi(parts[1])\n\t\tif err != nil {\n\t\t\treturn \"invalid thread id: \" + parts[1]\n\t\t}\n\t\tboard := boardI.(*Board)\n\t\tthread := board.GetThread(PostID(pid))\n\t\tif thread == nil {\n\t\t\treturn \"thread does not exist with id: \" + parts[1]\n\t\t}\n\t\treturn thread.RenderPost(\"\", 5)\n\t} else if len(parts) == 3 {\n\t\t// /r/demo/boards:BOARD_NAME/THREAD_ID/REPLY_ID\n\t\tname := parts[0]\n\t\tboardI, exists := gBoardsByName.Get(name)\n\t\tif !exists {\n\t\t\treturn \"board does not exist: \" + name\n\t\t}\n\t\tpid, err := strconv.Atoi(parts[1])\n\t\tif err != nil {\n\t\t\treturn \"invalid thread id: \" + parts[1]\n\t\t}\n\t\tboard := boardI.(*Board)\n\t\tthread := board.GetThread(PostID(pid))\n\t\tif thread == nil {\n\t\t\treturn \"thread does not exist with id: \" + parts[1]\n\t\t}\n\t\trid, err := strconv.Atoi(parts[2])\n\t\tif err != nil {\n\t\t\treturn \"invalid reply id: \" + parts[2]\n\t\t}\n\t\treply := thread.GetReply(PostID(rid))\n\t\tif reply == nil {\n\t\t\treturn \"reply does not exist with id: \" + parts[2]\n\t\t}\n\t\treturn reply.RenderInner()\n\t} else {\n\t\treturn \"unrecognized path \" + path\n\t}\n}\n"},{"name":"role.gno","body":"package boards\n\ntype Permission string\n\nconst (\n\tDeletePermission Permission = \"role:delete\"\n\tEditPermission Permission = \"role:edit\"\n)\n"},{"name":"z_0_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\nimport (\n\t\"gno.land/r/demo/boards\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid := boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// unauthorized\n"},{"name":"z_0_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 19900000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid = boards.CreateBoard(\"test_board\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// payment must not be less than 20000000\n"},{"name":"z_0_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tboards.CreateThread(1, \"First Post (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// board not exist\n"},{"name":"z_0_d_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateReply(bid, 0, 0, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// thread not exist\n"},{"name":"z_0_e_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tboards.CreateReply(bid, 0, 0, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// board not exist\n"},{"name":"z_0_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 20000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid := boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Output:\n// \\[[post](/r/demo/boards$help\u0026func=CreateThread\u0026bid=1)]\n//\n// ----------------------------------------\n// ## [First Post (title)](/r/demo/boards:test_board/1)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/1) \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)] (0 replies) (0 reposts)\n//\n// ----------------------------------------\n// ## [Second Post (title)](/r/demo/boards:test_board/2)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/2) \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)] (1 replies) (0 reposts)\n"},{"name":"z_10_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// boardId 2 not exist\n\tboards.DeletePost(2, pid, pid, \"\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// board not exist\n"},{"name":"z_10_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// pid of 2 not exist\n\tboards.DeletePost(bid, 2, 2, \"\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// thread not exist\n"},{"name":"z_10_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"First reply of the First post\\n\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.DeletePost(bid, pid, rid, \"\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e First reply of the First post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n//\n// ----------------------------------------------------\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n"},{"name":"z_10_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.DeletePost(bid, pid, pid, \"\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// ----------------------------------------------------\n// thread does not exist with id: 1\n"},{"name":"z_11_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// board 2 not exist\n\tboards.EditPost(2, pid, pid, \"Edited: First Post in (title)\", \"Edited: Body of the first post. (body)\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// board not exist\n"},{"name":"z_11_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// thread 2 not exist\n\tboards.EditPost(bid, 2, pid, \"Edited: First Post in (title)\", \"Edited: Body of the first post. (body)\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// thread not exist\n"},{"name":"z_11_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// post 2 not exist\n\tboards.EditPost(bid, pid, 2, \"Edited: First Post in (title)\", \"Edited: Body of the first post. (body)\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// post not exist\n"},{"name":"z_11_d_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"First reply of the First post\\n\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.EditPost(bid, pid, rid, \"\", \"Edited: First reply of the First post\\n\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e First reply of the First post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n//\n// ----------------------------------------------------\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e Edited: First reply of the First post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n"},{"name":"z_11_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.EditPost(bid, pid, pid, \"Edited: First Post in (title)\", \"Edited: Body of the first post. (body)\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// ----------------------------------------------------\n// # Edited: First Post in (title)\n//\n// Edited: Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n"},{"name":"z_12_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// create a post via registered user\n\tbid1 := boards.CreateBoard(\"test_board1\")\n\tpid := boards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tbid2 := boards.CreateBoard(\"test_board2\")\n\n\t// create a repost via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOriginCaller(test2)\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\n\trid := boards.CreateRepost(bid1, pid, \"\", \"Check this out\", bid2)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board1\"))\n}\n\n// Error:\n// please register, otherwise minimum fee 100000000 is required if anonymous\n"},{"name":"z_12_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid1 := boards.CreateBoard(\"test_board1\")\n\tpid := boards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tbid2 := boards.CreateBoard(\"test_board2\")\n\n\t// create a repost to a non-existing board\n\trid := boards.CreateRepost(5, pid, \"\", \"Check this out\", bid2)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board1\"))\n}\n\n// Error:\n// src board not exist\n"},{"name":"z_12_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid1 := boards.CreateBoard(\"test_board1\")\n\tboards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tbid2 := boards.CreateBoard(\"test_board2\")\n\n\t// create a repost to a non-existing thread\n\trid := boards.CreateRepost(bid1, 5, \"\", \"Check this out\", bid2)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board1\"))\n}\n\n// Error:\n// thread not exist\n"},{"name":"z_12_d_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid1 := boards.CreateBoard(\"test_board1\")\n\tpid := boards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tboards.CreateBoard(\"test_board2\")\n\n\t// create a repost to a non-existing destination board\n\trid := boards.CreateRepost(bid1, pid, \"\", \"Check this out\", 5)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board1\"))\n}\n\n// Error:\n// dst board not exist\n"},{"name":"z_12_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid1 boards.BoardID\n\tbid2 boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid1 = boards.CreateBoard(\"test_board1\")\n\tpid = boards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tbid2 = boards.CreateBoard(\"test_board2\")\n}\n\nfunc main() {\n\trid := boards.CreateRepost(bid1, pid, \"\", \"Check this out\", bid2)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board2\"))\n}\n\n// Output:\n// 1\n// \\[[post](/r/demo/boards$help\u0026func=CreateThread\u0026bid=2)]\n//\n// ----------------------------------------\n// Repost: Check this out\n// ## [First Post (title)](/r/demo/boards:test_board1/1)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board1/1) \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)] (0 replies) (1 reposts)\n"},{"name":"z_1_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar board *boards.Board\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\t_ = boards.CreateBoard(\"test_board_1\")\n\t_ = boards.CreateBoard(\"test_board_2\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"\"))\n}\n\n// Output:\n// These are all the boards of this realm:\n//\n// * [/r/demo/boards:test_board_1](/r/demo/boards:test_board_1)\n// * [/r/demo/boards:test_board_2](/r/demo/boards:test_board_2)\n"},{"name":"z_2_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n"},{"name":"z_3_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n}\n\nfunc main() {\n\trid := boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// 3\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n"},{"name":"z_4_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid := boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n\tprintln(rid)\n}\n\nfunc main() {\n\trid2 := boards.CreateReply(bid, pid, pid, \"Second reply of the second post\")\n\tprintln(rid2)\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// 3\n// 4\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n//\n// \u003e Second reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=4)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=4)]\n\n// Realm:\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/boards\"]\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111\",\n// \"ModTime\": \"123\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"68663c8895d37d479e417c11e21badfe21345c61\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:112\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Post\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"3f34ac77289aa1d5f9a2f8b6d083138325816fb0\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {},\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"94a6665a44bac6ede7f3e3b87173e537b12f9532\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"bc8e5b4e782a0bbc4ac9689681f119beb7b34d59\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"9957eadbc91dd32f33b0d815e041a32dbdea0671\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131]={\n// \"Fields\": [\n// {\n// \"N\": \"AAAAgJSeXbo=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"65536\"\n// }\n// },\n// {\n// \"N\": \"AbSNdvQQIhE=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"1024\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Location\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"336074805fc853987abe6f7fe3ad97a6a6f3077a:2\"\n// },\n// \"Index\": \"182\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"65536\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"1024\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Location\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Board\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:84\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"N\": \"BAAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.PostID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"std.Address\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"Second reply of the second post\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"f91e355bd19240f0f3350a7fa0e6a82b72225916\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"9ee9c4117be283fc51ffcc5ecd65b75ecef5a9dd\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"eb768b0140a5fe95f9c58747f0960d647dacfd42\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.PostID\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.PostID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.BoardID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Time\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"0fd3352422af0a56a77ef2c9e88f479054e3d51f\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Time\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"bed4afa8ffdbbf775451c947fc68b27a345ce32a\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132\"\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\",\n// \"IsEscaped\": true,\n// \"ModTime\": \"0\",\n// \"RefCount\": \"2\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Post\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"c45bbd47a46681a63af973db0ec2180922e4a8ae\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\"\n// }\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120\",\n// \"ModTime\": \"134\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"dc1f011553dc53e7a846049e08cc77fa35ea6a51\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:121\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Post\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"96b86b4585c7f1075d7794180a5581f72733a7ab\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {},\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"32274e1f28fb2b97d67a1262afd362d370de7faa\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"c2cfd6aec36a462f35bf02e5bf4a127aa1bb7ac2\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"5cb875179e86d32c517322af7a323b2a5f3e6cc5\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\"\n// }\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:85]={\n// \"Fields\": [\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.BoardID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"/r/demo/boards:test_board\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"test_board\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"std.Address\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"a416a751c3a45a1e5cba11e737c51340b081e372\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:86\"\n// }\n// },\n// {\n// \"N\": \"BAAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"65536\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Time\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"36299fccbc13f2a84c4629fad4cb940f0bd4b1c6\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:87\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"af6ed0268f99b7f369329094eb6dfaea7812708b\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:88\"\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:85\",\n// \"ModTime\": \"121\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:84\",\n// \"RefCount\": \"1\"\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"9809329dc1ddc5d3556f7a8fa3c2cebcbf65560b\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106\",\n// \"ModTime\": \"121\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:105\",\n// \"RefCount\": \"1\"\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"ceae9a1c4ed28bb51062e6ccdccfad0caafd1c4f\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107\",\n// \"ModTime\": \"121\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:105\",\n// \"RefCount\": \"1\"\n// }\n// }\n// switchrealm[\"gno.land/r/demo/boards\"]\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/boards\"]\n// switchrealm[\"gno.land/r/demo/boards_test\"]\n"},{"name":"z_5_b_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// create board via registered user\n\tbid := boards.CreateBoard(\"test_board\")\n\n\t// create post via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOriginCaller(test2)\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\n\tpid := boards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// please register, otherwise minimum fee 100000000 is required if anonymous\n"},{"name":"z_5_c_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// create board via registered user\n\tbid := boards.CreateBoard(\"test_board\")\n\n\t// create post via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOriginCaller(test2)\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 101000000}}, nil)\n\n\tpid := boards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the first post\")\n\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post (title)\n//\n// Body of the first post. (body)\n// \\- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/r/demo/users:g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e Reply of the first post\n// \u003e \\- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/r/demo/users:g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n"},{"name":"z_5_d_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// create board via registered user\n\tbid := boards.CreateBoard(\"test_board\")\n\tpid := boards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\n\t// create reply via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOriginCaller(test2)\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\tboards.CreateReply(bid, pid, pid, \"Reply of the first post\")\n\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// please register, otherwise minimum fee 100000000 is required if anonymous\n"},{"name":"z_5_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid := boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\trid2 := boards.CreateReply(bid, pid, pid, \"Second reply of the second post\\n\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n//\n// \u003e Second reply of the second post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=4)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=4)]\n"},{"name":"z_6_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tboards.CreateReply(bid, pid, pid, \"Second reply of the second post\\n\")\n\tboards.CreateReply(bid, pid, rid, \"First reply of the first reply\\n\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n// \u003e\n// \u003e \u003e First reply of the first reply\n// \u003e \u003e\n// \u003e \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/5) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=5)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=5)]\n//\n// \u003e Second reply of the second post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=4)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=4)]\n"},{"name":"z_7_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc init() {\n\t// register\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\t// create board and post\n\tbid := boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Output:\n// \\[[post](/r/demo/boards$help\u0026func=CreateThread\u0026bid=1)]\n//\n// ----------------------------------------\n// ## [First Post (title)](/r/demo/boards:test_board/1)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/1) \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)] (0 replies) (0 reposts)\n"},{"name":"z_8_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tboards.CreateReply(bid, pid, pid, \"Second reply of the second post\\n\")\n\trid2 := boards.CreateReply(bid, pid, rid, \"First reply of the first reply\\n\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid)) + \"/\" + strconv.Itoa(int(rid2))))\n}\n\n// Output:\n// _[see thread](/r/demo/boards:test_board/2)_\n//\n// Reply of the second post\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n//\n// _[see all 1 replies](/r/demo/boards:test_board/2/3)_\n//\n// \u003e First reply of the first reply\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/5) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=5)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=5)]\n"},{"name":"z_9_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar dstBoard boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tdstBoard = boards.CreateBoard(\"dst_board\")\n\n\tboards.CreateRepost(0, 0, \"First Post in (title)\", \"Body of the first post. (body)\", dstBoard)\n}\n\nfunc main() {\n}\n\n// Error:\n// src board not exist\n"},{"name":"z_9_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tsrcBoard boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tsrcBoard = boards.CreateBoard(\"first_board\")\n\tpid = boards.CreateThread(srcBoard, \"First Post in (title)\", \"Body of the first post. (body)\")\n\n\tboards.CreateRepost(srcBoard, pid, \"First Post in (title)\", \"Body of the first post. (body)\", 0)\n}\n\nfunc main() {\n}\n\n// Error:\n// dst board not exist\n"},{"name":"z_9_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tfirstBoard boards.BoardID\n\tsecondBoard boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tfirstBoard = boards.CreateBoard(\"first_board\")\n\tsecondBoard = boards.CreateBoard(\"second_board\")\n\tpid = boards.CreateThread(firstBoard, \"First Post in (title)\", \"Body of the first post. (body)\")\n\n\tboards.CreateRepost(firstBoard, pid, \"First Post in (title)\", \"Body of the first post. (body)\", secondBoard)\n}\n\nfunc main() {\n\tprintln(boards.Render(\"second_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:second_board/1/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=2\u0026threadid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=2\u0026threadid=1\u0026postid=1)]\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":"groups","path":"gno.land/p/demo/groups","files":[{"name":"groups.gno","body":"package groups\n\nimport \"gno.land/r/demo/boards\"\n\n// TODO implement something and test.\ntype Group struct {\n\tBoard *boards.Board\n}\n"},{"name":"vote_set.gno","body":"package groups\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/demo/rat\"\n)\n\n//----------------------------------------\n// VoteSet\n\ntype VoteSet interface {\n\t// number of present votes in set.\n\tSize() int\n\t// add or update vote for voter.\n\tSetVote(voter std.Address, value string) error\n\t// count the number of votes for value.\n\tCountVotes(value string) int\n}\n\n//----------------------------------------\n// VoteList\n\ntype Vote struct {\n\tVoter std.Address\n\tValue string\n}\n\ntype VoteList []Vote\n\nfunc NewVoteList() *VoteList {\n\treturn \u0026VoteList{}\n}\n\nfunc (vlist *VoteList) Size() int {\n\treturn len(*vlist)\n}\n\nfunc (vlist *VoteList) SetVote(voter std.Address, value string) error {\n\t// TODO optimize with binary algorithm\n\tfor i, vote := range *vlist {\n\t\tif vote.Voter == voter {\n\t\t\t// update vote\n\t\t\t(*vlist)[i] = Vote{\n\t\t\t\tVoter: voter,\n\t\t\t\tValue: value,\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t}\n\t*vlist = append(*vlist, Vote{\n\t\tVoter: voter,\n\t\tValue: value,\n\t})\n\treturn nil\n}\n\nfunc (vlist *VoteList) CountVotes(target string) int {\n\t// TODO optimize with binary algorithm\n\tvar count int\n\tfor _, vote := range *vlist {\n\t\tif vote.Value == target {\n\t\t\tcount++\n\t\t}\n\t}\n\treturn count\n}\n\n//----------------------------------------\n// Committee\n\ntype Committee struct {\n\tQuorum rat.Rat\n\tThreshold rat.Rat\n\tAddresses std.AddressSet\n}\n\n//----------------------------------------\n// VoteSession\n// NOTE: this seems a bit too formal and\n// complicated vs what might be possible;\n// something simpler, more informal.\n\ntype SessionStatus int\n\nconst (\n\tSessionNew SessionStatus = iota\n\tSessionStarted\n\tSessionCompleted\n\tSessionCanceled\n)\n\ntype VoteSession struct {\n\tName string\n\tCreator std.Address\n\tBody string\n\tStart time.Time\n\tDeadline time.Time\n\tStatus SessionStatus\n\tCommittee *Committee\n\tVotes VoteSet\n\tChoices []string\n\tResult string\n}\n"},{"name":"z_1_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\nimport (\n\t\"gno.land/p/demo/groups\"\n\t\"gno.land/p/demo/testutils\"\n)\n\nvar vset groups.VoteSet\n\nfunc init() {\n\taddr1 := testutils.TestAddress(\"test1\")\n\taddr2 := testutils.TestAddress(\"test2\")\n\tvset = groups.NewVoteList()\n\tvset.SetVote(addr1, \"yes\")\n\tvset.SetVote(addr2, \"yes\")\n}\n\nfunc main() {\n\tprintln(vset.Size())\n\tprintln(\"yes:\", vset.CountVotes(\"yes\"))\n\tprintln(\"no:\", vset.CountVotes(\"no\"))\n}\n\n// Output:\n// 2\n// yes: 2\n// no: 0\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":"uint256","path":"gno.land/p/demo/uint256","files":[{"name":"LICENSE","body":"BSD 3-Clause License\n\nCopyright 2020 uint256 Authors\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this\n list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright notice,\n this list of conditions and the following disclaimer in the documentation\n and/or other materials provided with the distribution.\n\n3. Neither the name of the copyright holder nor the names of its\n contributors may be used to endorse or promote products derived from\n this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"},{"name":"README.md","body":"# Fixed size 256-bit math library\n\nThis is a library specialized at replacing the `big.Int` library for math based on 256-bit types.\n\noriginal repository: [uint256](\u003chttps://github.com/holiman/uint256/tree/master\u003e)\n"},{"name":"arithmetic.gno","body":"// arithmetic provides arithmetic operations for Uint objects.\n// This includes basic binary operations such as addition, subtraction, multiplication, division, and modulo operations\n// as well as overflow checks, and negation. These functions are essential for numeric\n// calculations using 256-bit unsigned integers.\npackage uint256\n\nimport (\n\t\"math/bits\"\n)\n\n// Add sets z to the sum x+y\nfunc (z *Uint) Add(x, y *Uint) *Uint {\n\tvar carry uint64\n\tz.arr[0], carry = bits.Add64(x.arr[0], y.arr[0], 0)\n\tz.arr[1], carry = bits.Add64(x.arr[1], y.arr[1], carry)\n\tz.arr[2], carry = bits.Add64(x.arr[2], y.arr[2], carry)\n\tz.arr[3], _ = bits.Add64(x.arr[3], y.arr[3], carry)\n\treturn z\n}\n\n// AddOverflow sets z to the sum x+y, and returns z and whether overflow occurred\nfunc (z *Uint) AddOverflow(x, y *Uint) (*Uint, bool) {\n\tvar carry uint64\n\tz.arr[0], carry = bits.Add64(x.arr[0], y.arr[0], 0)\n\tz.arr[1], carry = bits.Add64(x.arr[1], y.arr[1], carry)\n\tz.arr[2], carry = bits.Add64(x.arr[2], y.arr[2], carry)\n\tz.arr[3], carry = bits.Add64(x.arr[3], y.arr[3], carry)\n\treturn z, carry != 0\n}\n\n// Sub sets z to the difference x-y\nfunc (z *Uint) Sub(x, y *Uint) *Uint {\n\tvar carry uint64\n\tz.arr[0], carry = bits.Sub64(x.arr[0], y.arr[0], 0)\n\tz.arr[1], carry = bits.Sub64(x.arr[1], y.arr[1], carry)\n\tz.arr[2], carry = bits.Sub64(x.arr[2], y.arr[2], carry)\n\tz.arr[3], _ = bits.Sub64(x.arr[3], y.arr[3], carry)\n\treturn z\n}\n\n// SubOverflow sets z to the difference x-y and returns z and true if the operation underflowed\nfunc (z *Uint) SubOverflow(x, y *Uint) (*Uint, bool) {\n\tvar carry uint64\n\tz.arr[0], carry = bits.Sub64(x.arr[0], y.arr[0], 0)\n\tz.arr[1], carry = bits.Sub64(x.arr[1], y.arr[1], carry)\n\tz.arr[2], carry = bits.Sub64(x.arr[2], y.arr[2], carry)\n\tz.arr[3], carry = bits.Sub64(x.arr[3], y.arr[3], carry)\n\treturn z, carry != 0\n}\n\n// Neg returns -x mod 2^256.\nfunc (z *Uint) Neg(x *Uint) *Uint {\n\treturn z.Sub(new(Uint), x)\n}\n\n// commented out for possible overflow\n// Mul sets z to the product x*y\nfunc (z *Uint) Mul(x, y *Uint) *Uint {\n\tvar (\n\t\tres Uint\n\t\tcarry uint64\n\t\tres1, res2, res3 uint64\n\t)\n\n\tcarry, res.arr[0] = bits.Mul64(x.arr[0], y.arr[0])\n\tcarry, res1 = umulHop(carry, x.arr[1], y.arr[0])\n\tcarry, res2 = umulHop(carry, x.arr[2], y.arr[0])\n\tres3 = x.arr[3]*y.arr[0] + carry\n\n\tcarry, res.arr[1] = umulHop(res1, x.arr[0], y.arr[1])\n\tcarry, res2 = umulStep(res2, x.arr[1], y.arr[1], carry)\n\tres3 = res3 + x.arr[2]*y.arr[1] + carry\n\n\tcarry, res.arr[2] = umulHop(res2, x.arr[0], y.arr[2])\n\tres3 = res3 + x.arr[1]*y.arr[2] + carry\n\n\tres.arr[3] = res3 + x.arr[0]*y.arr[3]\n\n\treturn z.Set(\u0026res)\n}\n\n// MulOverflow sets z to the product x*y, and returns z and whether overflow occurred\nfunc (z *Uint) MulOverflow(x, y *Uint) (*Uint, bool) {\n\tp := umul(x, y)\n\tcopy(z.arr[:], p[:4])\n\treturn z, (p[4] | p[5] | p[6] | p[7]) != 0\n}\n\n// commented out for possible overflow\n// Div sets z to the quotient x/y for returns z.\n// If y == 0, z is set to 0\nfunc (z *Uint) Div(x, y *Uint) *Uint {\n\tif y.IsZero() || y.Gt(x) {\n\t\treturn z.Clear()\n\t}\n\tif x.Eq(y) {\n\t\treturn z.SetOne()\n\t}\n\t// Shortcut some cases\n\tif x.IsUint64() {\n\t\treturn z.SetUint64(x.Uint64() / y.Uint64())\n\t}\n\n\t// At this point, we know\n\t// x/y ; x \u003e y \u003e 0\n\n\tvar quot Uint\n\tudivrem(quot.arr[:], x.arr[:], y)\n\treturn z.Set(\u0026quot)\n}\n\n// MulMod calculates the modulo-m multiplication of x and y and\n// returns z.\n// If m == 0, z is set to 0 (OBS: differs from the big.Int)\nfunc (z *Uint) MulMod(x, y, m *Uint) *Uint {\n\tif x.IsZero() || y.IsZero() || m.IsZero() {\n\t\treturn z.Clear()\n\t}\n\tp := umul(x, y)\n\n\tif m.arr[3] != 0 {\n\t\tmu := Reciprocal(m)\n\t\tr := reduce4(p, m, mu)\n\t\treturn z.Set(\u0026r)\n\t}\n\n\tvar (\n\t\tpl Uint\n\t\tph Uint\n\t)\n\n\tpl = Uint{arr: [4]uint64{p[0], p[1], p[2], p[3]}}\n\tph = Uint{arr: [4]uint64{p[4], p[5], p[6], p[7]}}\n\n\t// If the multiplication is within 256 bits use Mod().\n\tif ph.IsZero() {\n\t\treturn z.Mod(\u0026pl, m)\n\t}\n\n\tvar quot [8]uint64\n\trem := udivrem(quot[:], p[:], m)\n\treturn z.Set(\u0026rem)\n}\n\n// Mod sets z to the modulus x%y for y != 0 and returns z.\n// If y == 0, z is set to 0 (OBS: differs from the big.Uint)\nfunc (z *Uint) Mod(x, y *Uint) *Uint {\n\tif x.IsZero() || y.IsZero() {\n\t\treturn z.Clear()\n\t}\n\tswitch x.Cmp(y) {\n\tcase -1:\n\t\t// x \u003c y\n\t\tcopy(z.arr[:], x.arr[:])\n\t\treturn z\n\tcase 0:\n\t\t// x == y\n\t\treturn z.Clear() // They are equal\n\t}\n\n\t// At this point:\n\t// x != 0\n\t// y != 0\n\t// x \u003e y\n\n\t// Shortcut trivial case\n\tif x.IsUint64() {\n\t\treturn z.SetUint64(x.Uint64() % y.Uint64())\n\t}\n\n\tvar quot Uint\n\t*z = udivrem(quot.arr[:], x.arr[:], y)\n\treturn z\n}\n\n// DivMod sets z to the quotient x div y and m to the modulus x mod y and returns the pair (z, m) for y != 0.\n// If y == 0, both z and m are set to 0 (OBS: differs from the big.Int)\nfunc (z *Uint) DivMod(x, y, m *Uint) (*Uint, *Uint) {\n\tif y.IsZero() {\n\t\treturn z.Clear(), m.Clear()\n\t}\n\tvar quot Uint\n\t*m = udivrem(quot.arr[:], x.arr[:], y)\n\t*z = quot\n\treturn z, m\n}\n\n// Exp sets z = base**exponent mod 2**256, and returns z.\nfunc (z *Uint) Exp(base, exponent *Uint) *Uint {\n\tres := Uint{arr: [4]uint64{1, 0, 0, 0}}\n\tmultiplier := *base\n\texpBitLen := exponent.BitLen()\n\n\tcurBit := 0\n\tword := exponent.arr[0]\n\tfor ; curBit \u003c expBitLen \u0026\u0026 curBit \u003c 64; curBit++ {\n\t\tif word\u00261 == 1 {\n\t\t\tres.Mul(\u0026res, \u0026multiplier)\n\t\t}\n\t\tmultiplier.squared()\n\t\tword \u003e\u003e= 1\n\t}\n\n\tword = exponent.arr[1]\n\tfor ; curBit \u003c expBitLen \u0026\u0026 curBit \u003c 128; curBit++ {\n\t\tif word\u00261 == 1 {\n\t\t\tres.Mul(\u0026res, \u0026multiplier)\n\t\t}\n\t\tmultiplier.squared()\n\t\tword \u003e\u003e= 1\n\t}\n\n\tword = exponent.arr[2]\n\tfor ; curBit \u003c expBitLen \u0026\u0026 curBit \u003c 192; curBit++ {\n\t\tif word\u00261 == 1 {\n\t\t\tres.Mul(\u0026res, \u0026multiplier)\n\t\t}\n\t\tmultiplier.squared()\n\t\tword \u003e\u003e= 1\n\t}\n\n\tword = exponent.arr[3]\n\tfor ; curBit \u003c expBitLen \u0026\u0026 curBit \u003c 256; curBit++ {\n\t\tif word\u00261 == 1 {\n\t\t\tres.Mul(\u0026res, \u0026multiplier)\n\t\t}\n\t\tmultiplier.squared()\n\t\tword \u003e\u003e= 1\n\t}\n\treturn z.Set(\u0026res)\n}\n\nfunc (z *Uint) squared() {\n\tvar (\n\t\tres Uint\n\t\tcarry0, carry1, carry2 uint64\n\t\tres1, res2 uint64\n\t)\n\n\tcarry0, res.arr[0] = bits.Mul64(z.arr[0], z.arr[0])\n\tcarry0, res1 = umulHop(carry0, z.arr[0], z.arr[1])\n\tcarry0, res2 = umulHop(carry0, z.arr[0], z.arr[2])\n\n\tcarry1, res.arr[1] = umulHop(res1, z.arr[0], z.arr[1])\n\tcarry1, res2 = umulStep(res2, z.arr[1], z.arr[1], carry1)\n\n\tcarry2, res.arr[2] = umulHop(res2, z.arr[0], z.arr[2])\n\n\tres.arr[3] = 2*(z.arr[0]*z.arr[3]+z.arr[1]*z.arr[2]) + carry0 + carry1 + carry2\n\n\tz.Set(\u0026res)\n}\n\n// udivrem divides u by d and produces both quotient and remainder.\n// The quotient is stored in provided quot - len(u)-len(d)+1 words.\n// It loosely follows the Knuth's division algorithm (sometimes referenced as \"schoolbook\" division) using 64-bit words.\n// See Knuth, Volume 2, section 4.3.1, Algorithm D.\nfunc udivrem(quot, u []uint64, d *Uint) (rem Uint) {\n\tvar dLen int\n\tfor i := len(d.arr) - 1; i \u003e= 0; i-- {\n\t\tif d.arr[i] != 0 {\n\t\t\tdLen = i + 1\n\t\t\tbreak\n\t\t}\n\t}\n\n\tshift := uint(bits.LeadingZeros64(d.arr[dLen-1]))\n\n\tvar dnStorage Uint\n\tdn := dnStorage.arr[:dLen]\n\tfor i := dLen - 1; i \u003e 0; i-- {\n\t\tdn[i] = (d.arr[i] \u003c\u003c shift) | (d.arr[i-1] \u003e\u003e (64 - shift))\n\t}\n\tdn[0] = d.arr[0] \u003c\u003c shift\n\n\tvar uLen int\n\tfor i := len(u) - 1; i \u003e= 0; i-- {\n\t\tif u[i] != 0 {\n\t\t\tuLen = i + 1\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif uLen \u003c dLen {\n\t\tcopy(rem.arr[:], u)\n\t\treturn rem\n\t}\n\n\tvar unStorage [9]uint64\n\tun := unStorage[:uLen+1]\n\tun[uLen] = u[uLen-1] \u003e\u003e (64 - shift)\n\tfor i := uLen - 1; i \u003e 0; i-- {\n\t\tun[i] = (u[i] \u003c\u003c shift) | (u[i-1] \u003e\u003e (64 - shift))\n\t}\n\tun[0] = u[0] \u003c\u003c shift\n\n\t// TODO: Skip the highest word of numerator if not significant.\n\n\tif dLen == 1 {\n\t\tr := udivremBy1(quot, un, dn[0])\n\t\trem.SetUint64(r \u003e\u003e shift)\n\t\treturn rem\n\t}\n\n\tudivremKnuth(quot, un, dn)\n\n\tfor i := 0; i \u003c dLen-1; i++ {\n\t\trem.arr[i] = (un[i] \u003e\u003e shift) | (un[i+1] \u003c\u003c (64 - shift))\n\t}\n\trem.arr[dLen-1] = un[dLen-1] \u003e\u003e shift\n\n\treturn rem\n}\n\n// umul computes full 256 x 256 -\u003e 512 multiplication.\nfunc umul(x, y *Uint) [8]uint64 {\n\tvar (\n\t\tres [8]uint64\n\t\tcarry, carry4, carry5, carry6 uint64\n\t\tres1, res2, res3, res4, res5 uint64\n\t)\n\n\tcarry, res[0] = bits.Mul64(x.arr[0], y.arr[0])\n\tcarry, res1 = umulHop(carry, x.arr[1], y.arr[0])\n\tcarry, res2 = umulHop(carry, x.arr[2], y.arr[0])\n\tcarry4, res3 = umulHop(carry, x.arr[3], y.arr[0])\n\n\tcarry, res[1] = umulHop(res1, x.arr[0], y.arr[1])\n\tcarry, res2 = umulStep(res2, x.arr[1], y.arr[1], carry)\n\tcarry, res3 = umulStep(res3, x.arr[2], y.arr[1], carry)\n\tcarry5, res4 = umulStep(carry4, x.arr[3], y.arr[1], carry)\n\n\tcarry, res[2] = umulHop(res2, x.arr[0], y.arr[2])\n\tcarry, res3 = umulStep(res3, x.arr[1], y.arr[2], carry)\n\tcarry, res4 = umulStep(res4, x.arr[2], y.arr[2], carry)\n\tcarry6, res5 = umulStep(carry5, x.arr[3], y.arr[2], carry)\n\n\tcarry, res[3] = umulHop(res3, x.arr[0], y.arr[3])\n\tcarry, res[4] = umulStep(res4, x.arr[1], y.arr[3], carry)\n\tcarry, res[5] = umulStep(res5, x.arr[2], y.arr[3], carry)\n\tres[7], res[6] = umulStep(carry6, x.arr[3], y.arr[3], carry)\n\n\treturn res\n}\n\n// umulStep computes (hi * 2^64 + lo) = z + (x * y) + carry.\nfunc umulStep(z, x, y, carry uint64) (hi, lo uint64) {\n\thi, lo = bits.Mul64(x, y)\n\tlo, carry = bits.Add64(lo, carry, 0)\n\thi, _ = bits.Add64(hi, 0, carry)\n\tlo, carry = bits.Add64(lo, z, 0)\n\thi, _ = bits.Add64(hi, 0, carry)\n\treturn hi, lo\n}\n\n// umulHop computes (hi * 2^64 + lo) = z + (x * y)\nfunc umulHop(z, x, y uint64) (hi, lo uint64) {\n\thi, lo = bits.Mul64(x, y)\n\tlo, carry := bits.Add64(lo, z, 0)\n\thi, _ = bits.Add64(hi, 0, carry)\n\treturn hi, lo\n}\n\n// udivremBy1 divides u by single normalized word d and produces both quotient and remainder.\n// The quotient is stored in provided quot.\nfunc udivremBy1(quot, u []uint64, d uint64) (rem uint64) {\n\treciprocal := reciprocal2by1(d)\n\trem = u[len(u)-1] // Set the top word as remainder.\n\tfor j := len(u) - 2; j \u003e= 0; j-- {\n\t\tquot[j], rem = udivrem2by1(rem, u[j], d, reciprocal)\n\t}\n\treturn rem\n}\n\n// udivremKnuth implements the division of u by normalized multiple word d from the Knuth's division algorithm.\n// The quotient is stored in provided quot - len(u)-len(d) words.\n// Updates u to contain the remainder - len(d) words.\nfunc udivremKnuth(quot, u, d []uint64) {\n\tdh := d[len(d)-1]\n\tdl := d[len(d)-2]\n\treciprocal := reciprocal2by1(dh)\n\n\tfor j := len(u) - len(d) - 1; j \u003e= 0; j-- {\n\t\tu2 := u[j+len(d)]\n\t\tu1 := u[j+len(d)-1]\n\t\tu0 := u[j+len(d)-2]\n\n\t\tvar qhat, rhat uint64\n\t\tif u2 \u003e= dh { // Division overflows.\n\t\t\tqhat = ^uint64(0)\n\t\t\t// TODO: Add \"qhat one to big\" adjustment (not needed for correctness, but helps avoiding \"add back\" case).\n\t\t} else {\n\t\t\tqhat, rhat = udivrem2by1(u2, u1, dh, reciprocal)\n\t\t\tph, pl := bits.Mul64(qhat, dl)\n\t\t\tif ph \u003e rhat || (ph == rhat \u0026\u0026 pl \u003e u0) {\n\t\t\t\tqhat--\n\t\t\t\t// TODO: Add \"qhat one to big\" adjustment (not needed for correctness, but helps avoiding \"add back\" case).\n\t\t\t}\n\t\t}\n\n\t\t// Multiply and subtract.\n\t\tborrow := subMulTo(u[j:], d, qhat)\n\t\tu[j+len(d)] = u2 - borrow\n\t\tif u2 \u003c borrow { // Too much subtracted, add back.\n\t\t\tqhat--\n\t\t\tu[j+len(d)] += addTo(u[j:], d)\n\t\t}\n\n\t\tquot[j] = qhat // Store quotient digit.\n\t}\n}\n\n// isBitSet returns true if bit n-th is set, where n = 0 is LSB.\n// The n must be \u003c= 255.\nfunc (z *Uint) isBitSet(n uint) bool {\n\treturn (z.arr[n/64] \u0026 (1 \u003c\u003c (n % 64))) != 0\n}\n\n// addTo computes x += y.\n// Requires len(x) \u003e= len(y).\nfunc addTo(x, y []uint64) uint64 {\n\tvar carry uint64\n\tfor i := 0; i \u003c len(y); i++ {\n\t\tx[i], carry = bits.Add64(x[i], y[i], carry)\n\t}\n\treturn carry\n}\n\n// subMulTo computes x -= y * multiplier.\n// Requires len(x) \u003e= len(y).\nfunc subMulTo(x, y []uint64, multiplier uint64) uint64 {\n\tvar borrow uint64\n\tfor i := 0; i \u003c len(y); i++ {\n\t\ts, carry1 := bits.Sub64(x[i], borrow, 0)\n\t\tph, pl := bits.Mul64(y[i], multiplier)\n\t\tt, carry2 := bits.Sub64(s, pl, 0)\n\t\tx[i] = t\n\t\tborrow = ph + carry1 + carry2\n\t}\n\treturn borrow\n}\n\n// reciprocal2by1 computes \u003c^d, ^0\u003e / d.\nfunc reciprocal2by1(d uint64) uint64 {\n\treciprocal, _ := bits.Div64(^d, ^uint64(0), d)\n\treturn reciprocal\n}\n\n// udivrem2by1 divides \u003cuh, ul\u003e / d and produces both quotient and remainder.\n// It uses the provided d's reciprocal.\n// Implementation ported from https://github.com/chfast/intx and is based on\n// \"Improved division by invariant integers\", Algorithm 4.\nfunc udivrem2by1(uh, ul, d, reciprocal uint64) (quot, rem uint64) {\n\tqh, ql := bits.Mul64(reciprocal, uh)\n\tql, carry := bits.Add64(ql, ul, 0)\n\tqh, _ = bits.Add64(qh, uh, carry)\n\tqh++\n\n\tr := ul - qh*d\n\n\tif r \u003e ql {\n\t\tqh--\n\t\tr += d\n\t}\n\n\tif r \u003e= d {\n\t\tqh++\n\t\tr -= d\n\t}\n\n\treturn qh, r\n}\n"},{"name":"arithmetic_test.gno","body":"package uint256\n\nimport (\n\t\"testing\"\n)\n\ntype binOp2Test struct {\n\tx, y, want string\n}\n\nfunc TestAdd(t *testing.T) {\n\ttests := []binOp2Test{\n\t\t{\"0\", \"1\", \"1\"},\n\t\t{\"1\", \"0\", \"1\"},\n\t\t{\"1\", \"1\", \"2\"},\n\t\t{\"1\", \"3\", \"4\"},\n\t\t{\"10\", \"10\", \"20\"},\n\t\t{\"18446744073709551615\", \"18446744073709551615\", \"36893488147419103230\"}, // uint64 overflow\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\ty := MustFromDecimal(tt.y)\n\n\t\twant := MustFromDecimal(tt.want)\n\t\tgot := new(Uint).Add(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Add(%s, %s) = %v, want %v\", tt.x, tt.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestAddOverflow(t *testing.T) {\n\ttests := []struct {\n\t\tx, y string\n\t\twant string\n\t\toverflow bool\n\t}{\n\t\t{\"0\", \"1\", \"1\", false},\n\t\t{\"1\", \"0\", \"1\", false},\n\t\t{\"1\", \"1\", \"2\", false},\n\t\t{\"10\", \"10\", \"20\", false},\n\t\t{\"18446744073709551615\", \"18446744073709551615\", \"36893488147419103230\", false}, // uint64 overflow, but not Uint256 overflow\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"1\", \"0\", true}, // 2^256 - 1 + 1, should overflow\n\t\t{\"57896044618658097711785492504343953926634992332820282019728792003956564819967\", \"57896044618658097711785492504343953926634992332820282019728792003956564819968\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\", false}, // (2^255 - 1) + 2^255, no overflow\n\t\t{\"57896044618658097711785492504343953926634992332820282019728792003956564819967\", \"57896044618658097711785492504343953926634992332820282019728792003956564819969\", \"0\", true}, // (2^255 - 1) + (2^255 + 1), should overflow\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\ty := MustFromDecimal(tt.y)\n\t\twant, _ := FromDecimal(tt.want)\n\n\t\tgot, overflow := new(Uint).AddOverflow(x, y)\n\n\t\tif got.Cmp(want) != 0 || overflow != tt.overflow {\n\t\t\tt.Errorf(\"AddOverflow(%s, %s) = (%s, %v), want (%s, %v)\",\n\t\t\t\ttt.x, tt.y, got.ToString(), overflow, tt.want, tt.overflow)\n\t\t}\n\t}\n}\n\nfunc TestSub(t *testing.T) {\n\ttests := []binOp2Test{\n\t\t{\"1\", \"0\", \"1\"},\n\t\t{\"1\", \"1\", \"0\"},\n\t\t{\"10\", \"10\", \"0\"},\n\t\t{\"31337\", \"1337\", \"30000\"},\n\t\t{\"2\", \"3\", twoPow256Sub1}, // underflow\n\t}\n\n\tfor _, tc := range tests {\n\t\tx := MustFromDecimal(tc.x)\n\t\ty := MustFromDecimal(tc.y)\n\n\t\twant := MustFromDecimal(tc.want)\n\n\t\tgot := new(Uint).Sub(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\n\t\t\t\t\"Sub(%s, %s) = %v, want %v\",\n\t\t\t\ttc.x, tc.y, got.ToString(), want.ToString(),\n\t\t\t)\n\t\t}\n\t}\n}\n\nfunc TestSubOverflow(t *testing.T) {\n\ttests := []struct {\n\t\tx, y string\n\t\twant string\n\t\toverflow bool\n\t}{\n\t\t{\"1\", \"0\", \"1\", false},\n\t\t{\"1\", \"1\", \"0\", false},\n\t\t{\"10\", \"10\", \"0\", false},\n\t\t{\"31337\", \"1337\", \"30000\", false},\n\t\t{\"0\", \"1\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\", true}, // 0 - 1, should underflow\n\t\t{\"57896044618658097711785492504343953926634992332820282019728792003956564819968\", \"1\", \"57896044618658097711785492504343953926634992332820282019728792003956564819967\", false}, // 2^255 - 1, no underflow\n\t\t{\"57896044618658097711785492504343953926634992332820282019728792003956564819968\", \"57896044618658097711785492504343953926634992332820282019728792003956564819969\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\", true}, // 2^255 - (2^255 + 1), should underflow\n\t}\n\n\tfor _, tc := range tests {\n\t\tx := MustFromDecimal(tc.x)\n\t\ty := MustFromDecimal(tc.y)\n\t\twant := MustFromDecimal(tc.want)\n\n\t\tgot, overflow := new(Uint).SubOverflow(x, y)\n\n\t\tif got.Cmp(want) != 0 || overflow != tc.overflow {\n\t\t\tt.Errorf(\n\t\t\t\t\"SubOverflow(%s, %s) = (%s, %v), want (%s, %v)\",\n\t\t\t\ttc.x, tc.y, got.ToString(), overflow, tc.want, tc.overflow,\n\t\t\t)\n\t\t}\n\t}\n}\n\nfunc TestMul(t *testing.T) {\n\ttests := []binOp2Test{\n\t\t{\"1\", \"0\", \"0\"},\n\t\t{\"1\", \"1\", \"1\"},\n\t\t{\"10\", \"10\", \"100\"},\n\t\t{\"18446744073709551615\", \"2\", \"36893488147419103230\"}, // uint64 overflow\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\ty := MustFromDecimal(tt.y)\n\t\twant := MustFromDecimal(tt.want)\n\t\tgot := new(Uint).Mul(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Mul(%s, %s) = %v, want %v\", tt.x, tt.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestMulOverflow(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty string\n\t\twantZ string\n\t\twantOver bool\n\t}{\n\t\t{\"0x1\", \"0x1\", \"0x1\", false},\n\t\t{\"0x0\", \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0x0\", false},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0x2\", \"0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe\", true},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0x1\", true},\n\t\t{\"0x8000000000000000000000000000000000000000000000000000000000000000\", \"0x2\", \"0x0\", true},\n\t\t{\"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0x2\", \"0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe\", false},\n\t\t{\"0x100000000000000000\", \"0x100000000000000000\", \"0x10000000000000000000000000000000000\", false},\n\t\t{\"0x10000000000000000000000000000000\", \"0x10000000000000000000000000000000\", \"0x100000000000000000000000000000000000000000000000000000000000000\", false},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromHex(tt.x)\n\t\ty := MustFromHex(tt.y)\n\t\twantZ := MustFromHex(tt.wantZ)\n\n\t\tgotZ, gotOver := new(Uint).MulOverflow(x, y)\n\n\t\tif gotZ.Neq(wantZ) {\n\t\t\tt.Errorf(\n\t\t\t\t\"MulOverflow(%s, %s) = %s, want %s\",\n\t\t\t\ttt.x, tt.y, gotZ.ToString(), wantZ.ToString(),\n\t\t\t)\n\t\t}\n\t\tif gotOver != tt.wantOver {\n\t\t\tt.Errorf(\"MulOverflow(%s, %s) = %v, want %v\", tt.x, tt.y, gotOver, tt.wantOver)\n\t\t}\n\t}\n}\n\nfunc TestDiv(t *testing.T) {\n\ttests := []binOp2Test{\n\t\t{\"31337\", \"3\", \"10445\"},\n\t\t{\"31337\", \"0\", \"0\"},\n\t\t{\"0\", \"31337\", \"0\"},\n\t\t{\"1\", \"1\", \"1\"},\n\t\t{\"1000000000000000000\", \"3\", \"333333333333333333\"},\n\t\t{twoPow256Sub1, \"2\", \"57896044618658097711785492504343953926634992332820282019728792003956564819967\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\ty := MustFromDecimal(tt.y)\n\t\twant := MustFromDecimal(tt.want)\n\n\t\tgot := new(Uint).Div(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Div(%s, %s) = %v, want %v\", tt.x, tt.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestMod(t *testing.T) {\n\ttests := []binOp2Test{\n\t\t{\"31337\", \"3\", \"2\"},\n\t\t{\"31337\", \"0\", \"0\"},\n\t\t{\"0\", \"31337\", \"0\"},\n\t\t{\"2\", \"31337\", \"2\"},\n\t\t{\"1\", \"1\", \"0\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"2\", \"1\"}, // 2^256 - 1 mod 2\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"3\", \"0\"}, // 2^256 - 1 mod 3\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"57896044618658097711785492504343953926634992332820282019728792003956564819968\", \"57896044618658097711785492504343953926634992332820282019728792003956564819967\"}, // 2^256 - 1 mod 2^255\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\ty := MustFromDecimal(tt.y)\n\t\twant := MustFromDecimal(tt.want)\n\n\t\tgot := new(Uint).Mod(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Mod(%s, %s) = %v, want %v\", tt.x, tt.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestMulMod(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty string\n\t\tm string\n\t\twant string\n\t}{\n\t\t{\"0x1\", \"0x1\", \"0x2\", \"0x1\"},\n\t\t{\"0x10\", \"0x10\", \"0x7\", \"0x4\"},\n\t\t{\"0x100\", \"0x100\", \"0x17\", \"0x9\"},\n\t\t{\"0x31337\", \"0x31337\", \"0x31338\", \"0x1\"},\n\t\t{\"0x0\", \"0x31337\", \"0x31338\", \"0x0\"},\n\t\t{\"0x31337\", \"0x0\", \"0x31338\", \"0x0\"},\n\t\t{\"0x2\", \"0x3\", \"0x5\", \"0x1\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0x0\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe\", \"0x1\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0xffffffffffffffffffffffffffffffff\", \"0x0\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromHex(tt.x)\n\t\ty := MustFromHex(tt.y)\n\t\tm := MustFromHex(tt.m)\n\t\twant := MustFromHex(tt.want)\n\n\t\tgot := new(Uint).MulMod(x, y, m)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\n\t\t\t\t\"MulMod(%s, %s, %s) = %s, want %s\",\n\t\t\t\ttt.x, tt.y, tt.m, got.ToString(), want.ToString(),\n\t\t\t)\n\t\t}\n\t}\n}\n\nfunc TestDivMod(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty string\n\t\twantDiv string\n\t\twantMod string\n\t}{\n\t\t{\"1\", \"1\", \"1\", \"0\"},\n\t\t{\"10\", \"10\", \"1\", \"0\"},\n\t\t{\"100\", \"10\", \"10\", \"0\"},\n\t\t{\"31337\", \"3\", \"10445\", \"2\"},\n\t\t{\"31337\", \"0\", \"0\", \"0\"},\n\t\t{\"0\", \"31337\", \"0\", \"0\"},\n\t\t{\"2\", \"31337\", \"0\", \"2\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\ty := MustFromDecimal(tt.y)\n\t\twantDiv := MustFromDecimal(tt.wantDiv)\n\t\twantMod := MustFromDecimal(tt.wantMod)\n\n\t\tgotDiv := new(Uint)\n\t\tgotMod := new(Uint)\n\t\tgotDiv.DivMod(x, y, gotMod)\n\n\t\tfor i := range gotDiv.arr {\n\t\t\tif gotDiv.arr[i] != wantDiv.arr[i] {\n\t\t\t\tt.Errorf(\"DivMod(%s, %s) got Div %v, want Div %v\", tt.x, tt.y, gotDiv, wantDiv)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfor i := range gotMod.arr {\n\t\t\tif gotMod.arr[i] != wantMod.arr[i] {\n\t\t\t\tt.Errorf(\"DivMod(%s, %s) got Mod %v, want Mod %v\", tt.x, tt.y, gotMod, wantMod)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestNeg(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant string\n\t}{\n\t\t{\"31337\", \"115792089237316195423570985008687907853269984665640564039457584007913129608599\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129608599\", \"31337\"},\n\t\t{\"0\", \"0\"},\n\t\t{\"2\", \"115792089237316195423570985008687907853269984665640564039457584007913129639934\"},\n\t\t{\"1\", twoPow256Sub1},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\twant := MustFromDecimal(tt.want)\n\n\t\tgot := new(Uint).Neg(x)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Neg(%s) = %v, want %v\", tt.x, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestExp(t *testing.T) {\n\ttests := []binOp2Test{\n\t\t{\"31337\", \"3\", \"30773171189753\"},\n\t\t{\"31337\", \"0\", \"1\"},\n\t\t{\"0\", \"31337\", \"0\"},\n\t\t{\"1\", \"1\", \"1\"},\n\t\t{\"2\", \"3\", \"8\"},\n\t\t{\"2\", \"64\", \"18446744073709551616\"},\n\t\t{\"2\", \"128\", \"340282366920938463463374607431768211456\"},\n\t\t{\"2\", \"255\", \"57896044618658097711785492504343953926634992332820282019728792003956564819968\"},\n\t\t{\"2\", \"256\", \"0\"}, // overflow\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\ty := MustFromDecimal(tt.y)\n\t\twant := MustFromDecimal(tt.want)\n\n\t\tgot := new(Uint).Exp(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\n\t\t\t\t\"Exp(%s, %s) = %v, want %v\",\n\t\t\t\ttt.x, tt.y, got.ToString(), want.ToString(),\n\t\t\t)\n\t\t}\n\t}\n}\n\nfunc TestExp_LargeExponent(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tbase string\n\t\texponent string\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname: \"2^129\",\n\t\t\tbase: \"2\",\n\t\t\texponent: \"680564733841876926926749214863536422912\",\n\t\t\texpected: \"0\",\n\t\t},\n\t\t{\n\t\t\tname: \"2^193\",\n\t\t\tbase: \"2\",\n\t\t\texponent: \"12379400392853802746563808384000000000000000000\",\n\t\t\texpected: \"0\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tbase := MustFromDecimal(tt.base)\n\t\t\texponent := MustFromDecimal(tt.exponent)\n\t\t\texpected := MustFromDecimal(tt.expected)\n\n\t\t\tresult := new(Uint).Exp(base, exponent)\n\n\t\t\tif result.Neq(expected) {\n\t\t\t\tt.Errorf(\n\t\t\t\t\t\"Test %s failed. Expected %s, got %s\",\n\t\t\t\t\ttt.name, expected.ToString(), result.ToString(),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n}\n"},{"name":"bits_table.gno","body":"// Copyright 2017 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n// Code generated by go run make_tables.go. DO NOT EDIT.\n\npackage uint256\n\nconst ntz8tab = \"\" +\n\t\"\\x08\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x05\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x06\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x05\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x07\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x05\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x06\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x05\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\"\n\nconst pop8tab = \"\" +\n\t\"\\x00\\x01\\x01\\x02\\x01\\x02\\x02\\x03\\x01\\x02\\x02\\x03\\x02\\x03\\x03\\x04\" +\n\t\"\\x01\\x02\\x02\\x03\\x02\\x03\\x03\\x04\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\" +\n\t\"\\x01\\x02\\x02\\x03\\x02\\x03\\x03\\x04\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\" +\n\t\"\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\" +\n\t\"\\x01\\x02\\x02\\x03\\x02\\x03\\x03\\x04\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\" +\n\t\"\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\" +\n\t\"\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\" +\n\t\"\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\\x04\\x05\\x05\\x06\\x05\\x06\\x06\\x07\" +\n\t\"\\x01\\x02\\x02\\x03\\x02\\x03\\x03\\x04\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\" +\n\t\"\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\" +\n\t\"\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\" +\n\t\"\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\\x04\\x05\\x05\\x06\\x05\\x06\\x06\\x07\" +\n\t\"\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\" +\n\t\"\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\\x04\\x05\\x05\\x06\\x05\\x06\\x06\\x07\" +\n\t\"\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\\x04\\x05\\x05\\x06\\x05\\x06\\x06\\x07\" +\n\t\"\\x04\\x05\\x05\\x06\\x05\\x06\\x06\\x07\\x05\\x06\\x06\\x07\\x06\\x07\\x07\\x08\"\n\nconst rev8tab = \"\" +\n\t\"\\x00\\x80\\x40\\xc0\\x20\\xa0\\x60\\xe0\\x10\\x90\\x50\\xd0\\x30\\xb0\\x70\\xf0\" +\n\t\"\\x08\\x88\\x48\\xc8\\x28\\xa8\\x68\\xe8\\x18\\x98\\x58\\xd8\\x38\\xb8\\x78\\xf8\" +\n\t\"\\x04\\x84\\x44\\xc4\\x24\\xa4\\x64\\xe4\\x14\\x94\\x54\\xd4\\x34\\xb4\\x74\\xf4\" +\n\t\"\\x0c\\x8c\\x4c\\xcc\\x2c\\xac\\x6c\\xec\\x1c\\x9c\\x5c\\xdc\\x3c\\xbc\\x7c\\xfc\" +\n\t\"\\x02\\x82\\x42\\xc2\\x22\\xa2\\x62\\xe2\\x12\\x92\\x52\\xd2\\x32\\xb2\\x72\\xf2\" +\n\t\"\\x0a\\x8a\\x4a\\xca\\x2a\\xaa\\x6a\\xea\\x1a\\x9a\\x5a\\xda\\x3a\\xba\\x7a\\xfa\" +\n\t\"\\x06\\x86\\x46\\xc6\\x26\\xa6\\x66\\xe6\\x16\\x96\\x56\\xd6\\x36\\xb6\\x76\\xf6\" +\n\t\"\\x0e\\x8e\\x4e\\xce\\x2e\\xae\\x6e\\xee\\x1e\\x9e\\x5e\\xde\\x3e\\xbe\\x7e\\xfe\" +\n\t\"\\x01\\x81\\x41\\xc1\\x21\\xa1\\x61\\xe1\\x11\\x91\\x51\\xd1\\x31\\xb1\\x71\\xf1\" +\n\t\"\\x09\\x89\\x49\\xc9\\x29\\xa9\\x69\\xe9\\x19\\x99\\x59\\xd9\\x39\\xb9\\x79\\xf9\" +\n\t\"\\x05\\x85\\x45\\xc5\\x25\\xa5\\x65\\xe5\\x15\\x95\\x55\\xd5\\x35\\xb5\\x75\\xf5\" +\n\t\"\\x0d\\x8d\\x4d\\xcd\\x2d\\xad\\x6d\\xed\\x1d\\x9d\\x5d\\xdd\\x3d\\xbd\\x7d\\xfd\" +\n\t\"\\x03\\x83\\x43\\xc3\\x23\\xa3\\x63\\xe3\\x13\\x93\\x53\\xd3\\x33\\xb3\\x73\\xf3\" +\n\t\"\\x0b\\x8b\\x4b\\xcb\\x2b\\xab\\x6b\\xeb\\x1b\\x9b\\x5b\\xdb\\x3b\\xbb\\x7b\\xfb\" +\n\t\"\\x07\\x87\\x47\\xc7\\x27\\xa7\\x67\\xe7\\x17\\x97\\x57\\xd7\\x37\\xb7\\x77\\xf7\" +\n\t\"\\x0f\\x8f\\x4f\\xcf\\x2f\\xaf\\x6f\\xef\\x1f\\x9f\\x5f\\xdf\\x3f\\xbf\\x7f\\xff\"\n\nconst len8tab = \"\" +\n\t\"\\x00\\x01\\x02\\x02\\x03\\x03\\x03\\x03\\x04\\x04\\x04\\x04\\x04\\x04\\x04\\x04\" +\n\t\"\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\" +\n\t\"\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\" +\n\t\"\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\" +\n\t\"\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\" +\n\t\"\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\" +\n\t\"\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\" +\n\t\"\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\"\n"},{"name":"bitwise.gno","body":"// bitwise contains bitwise operations for Uint instances.\n// This file includes functions to perform bitwise AND, OR, XOR, and NOT operations, as well as bit shifting.\n// These operations are crucial for manipulating individual bits within a 256-bit unsigned integer.\npackage uint256\n\n// Or sets z = x | y and returns z.\nfunc (z *Uint) Or(x, y *Uint) *Uint {\n\tz.arr[0] = x.arr[0] | y.arr[0]\n\tz.arr[1] = x.arr[1] | y.arr[1]\n\tz.arr[2] = x.arr[2] | y.arr[2]\n\tz.arr[3] = x.arr[3] | y.arr[3]\n\treturn z\n}\n\n// And sets z = x \u0026 y and returns z.\nfunc (z *Uint) And(x, y *Uint) *Uint {\n\tz.arr[0] = x.arr[0] \u0026 y.arr[0]\n\tz.arr[1] = x.arr[1] \u0026 y.arr[1]\n\tz.arr[2] = x.arr[2] \u0026 y.arr[2]\n\tz.arr[3] = x.arr[3] \u0026 y.arr[3]\n\treturn z\n}\n\n// Not sets z = ^x and returns z.\nfunc (z *Uint) Not(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = ^x.arr[3], ^x.arr[2], ^x.arr[1], ^x.arr[0]\n\treturn z\n}\n\n// AndNot sets z = x \u0026^ y and returns z.\nfunc (z *Uint) AndNot(x, y *Uint) *Uint {\n\tz.arr[0] = x.arr[0] \u0026^ y.arr[0]\n\tz.arr[1] = x.arr[1] \u0026^ y.arr[1]\n\tz.arr[2] = x.arr[2] \u0026^ y.arr[2]\n\tz.arr[3] = x.arr[3] \u0026^ y.arr[3]\n\treturn z\n}\n\n// Xor sets z = x ^ y and returns z.\nfunc (z *Uint) Xor(x, y *Uint) *Uint {\n\tz.arr[0] = x.arr[0] ^ y.arr[0]\n\tz.arr[1] = x.arr[1] ^ y.arr[1]\n\tz.arr[2] = x.arr[2] ^ y.arr[2]\n\tz.arr[3] = x.arr[3] ^ y.arr[3]\n\treturn z\n}\n\n// Lsh sets z = x \u003c\u003c n and returns z.\nfunc (z *Uint) Lsh(x *Uint, n uint) *Uint {\n\t// n % 64 == 0\n\tif n\u00260x3f == 0 {\n\t\tswitch n {\n\t\tcase 0:\n\t\t\treturn z.Set(x)\n\t\tcase 64:\n\t\t\treturn z.lsh64(x)\n\t\tcase 128:\n\t\t\treturn z.lsh128(x)\n\t\tcase 192:\n\t\t\treturn z.lsh192(x)\n\t\tdefault:\n\t\t\treturn z.Clear()\n\t\t}\n\t}\n\tvar a, b uint64\n\t// Big swaps first\n\tswitch {\n\tcase n \u003e 192:\n\t\tif n \u003e 256 {\n\t\t\treturn z.Clear()\n\t\t}\n\t\tz.lsh192(x)\n\t\tn -= 192\n\t\tgoto sh192\n\tcase n \u003e 128:\n\t\tz.lsh128(x)\n\t\tn -= 128\n\t\tgoto sh128\n\tcase n \u003e 64:\n\t\tz.lsh64(x)\n\t\tn -= 64\n\t\tgoto sh64\n\tdefault:\n\t\tz.Set(x)\n\t}\n\n\t// remaining shifts\n\ta = z.arr[0] \u003e\u003e (64 - n)\n\tz.arr[0] = z.arr[0] \u003c\u003c n\n\nsh64:\n\tb = z.arr[1] \u003e\u003e (64 - n)\n\tz.arr[1] = (z.arr[1] \u003c\u003c n) | a\n\nsh128:\n\ta = z.arr[2] \u003e\u003e (64 - n)\n\tz.arr[2] = (z.arr[2] \u003c\u003c n) | b\n\nsh192:\n\tz.arr[3] = (z.arr[3] \u003c\u003c n) | a\n\n\treturn z\n}\n\n// Rsh sets z = x \u003e\u003e n and returns z.\nfunc (z *Uint) Rsh(x *Uint, n uint) *Uint {\n\t// n % 64 == 0\n\tif n\u00260x3f == 0 {\n\t\tswitch n {\n\t\tcase 0:\n\t\t\treturn z.Set(x)\n\t\tcase 64:\n\t\t\treturn z.rsh64(x)\n\t\tcase 128:\n\t\t\treturn z.rsh128(x)\n\t\tcase 192:\n\t\t\treturn z.rsh192(x)\n\t\tdefault:\n\t\t\treturn z.Clear()\n\t\t}\n\t}\n\tvar a, b uint64\n\t// Big swaps first\n\tswitch {\n\tcase n \u003e 192:\n\t\tif n \u003e 256 {\n\t\t\treturn z.Clear()\n\t\t}\n\t\tz.rsh192(x)\n\t\tn -= 192\n\t\tgoto sh192\n\tcase n \u003e 128:\n\t\tz.rsh128(x)\n\t\tn -= 128\n\t\tgoto sh128\n\tcase n \u003e 64:\n\t\tz.rsh64(x)\n\t\tn -= 64\n\t\tgoto sh64\n\tdefault:\n\t\tz.Set(x)\n\t}\n\n\t// remaining shifts\n\ta = z.arr[3] \u003c\u003c (64 - n)\n\tz.arr[3] = z.arr[3] \u003e\u003e n\n\nsh64:\n\tb = z.arr[2] \u003c\u003c (64 - n)\n\tz.arr[2] = (z.arr[2] \u003e\u003e n) | a\n\nsh128:\n\ta = z.arr[1] \u003c\u003c (64 - n)\n\tz.arr[1] = (z.arr[1] \u003e\u003e n) | b\n\nsh192:\n\tz.arr[0] = (z.arr[0] \u003e\u003e n) | a\n\n\treturn z\n}\n\n// SRsh (Signed/Arithmetic right shift)\n// considers z to be a signed integer, during right-shift\n// and sets z = x \u003e\u003e n and returns z.\nfunc (z *Uint) SRsh(x *Uint, n uint) *Uint {\n\t// If the MSB is 0, SRsh is same as Rsh.\n\tif !x.isBitSet(255) {\n\t\treturn z.Rsh(x, n)\n\t}\n\tif n%64 == 0 {\n\t\tswitch n {\n\t\tcase 0:\n\t\t\treturn z.Set(x)\n\t\tcase 64:\n\t\t\treturn z.srsh64(x)\n\t\tcase 128:\n\t\t\treturn z.srsh128(x)\n\t\tcase 192:\n\t\t\treturn z.srsh192(x)\n\t\tdefault:\n\t\t\treturn z.SetAllOne()\n\t\t}\n\t}\n\tvar a uint64 = MaxUint64 \u003c\u003c (64 - n%64)\n\t// Big swaps first\n\tswitch {\n\tcase n \u003e 192:\n\t\tif n \u003e 256 {\n\t\t\treturn z.SetAllOne()\n\t\t}\n\t\tz.srsh192(x)\n\t\tn -= 192\n\t\tgoto sh192\n\tcase n \u003e 128:\n\t\tz.srsh128(x)\n\t\tn -= 128\n\t\tgoto sh128\n\tcase n \u003e 64:\n\t\tz.srsh64(x)\n\t\tn -= 64\n\t\tgoto sh64\n\tdefault:\n\t\tz.Set(x)\n\t}\n\n\t// remaining shifts\n\tz.arr[3], a = (z.arr[3]\u003e\u003en)|a, z.arr[3]\u003c\u003c(64-n)\n\nsh64:\n\tz.arr[2], a = (z.arr[2]\u003e\u003en)|a, z.arr[2]\u003c\u003c(64-n)\n\nsh128:\n\tz.arr[1], a = (z.arr[1]\u003e\u003en)|a, z.arr[1]\u003c\u003c(64-n)\n\nsh192:\n\tz.arr[0] = (z.arr[0] \u003e\u003e n) | a\n\n\treturn z\n}\n\nfunc (z *Uint) lsh64(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = x.arr[2], x.arr[1], x.arr[0], 0\n\treturn z\n}\n\nfunc (z *Uint) lsh128(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = x.arr[1], x.arr[0], 0, 0\n\treturn z\n}\n\nfunc (z *Uint) lsh192(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = x.arr[0], 0, 0, 0\n\treturn z\n}\n\nfunc (z *Uint) rsh64(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, x.arr[3], x.arr[2], x.arr[1]\n\treturn z\n}\n\nfunc (z *Uint) rsh128(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, 0, x.arr[3], x.arr[2]\n\treturn z\n}\n\nfunc (z *Uint) rsh192(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, 0, 0, x.arr[3]\n\treturn z\n}\n\nfunc (z *Uint) srsh64(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = MaxUint64, x.arr[3], x.arr[2], x.arr[1]\n\treturn z\n}\n\nfunc (z *Uint) srsh128(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = MaxUint64, MaxUint64, x.arr[3], x.arr[2]\n\treturn z\n}\n\nfunc (z *Uint) srsh192(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = MaxUint64, MaxUint64, MaxUint64, x.arr[3]\n\treturn z\n}\n"},{"name":"bitwise_test.gno","body":"package uint256\n\nimport \"testing\"\n\ntype logicOpTest struct {\n\tname string\n\tx Uint\n\ty Uint\n\twant Uint\n}\n\nfunc TestOr(t *testing.T) {\n\ttests := []logicOpTest{\n\t\t{\n\t\t\tname: \"all zeros\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tres := new(Uint).Or(\u0026tt.x, \u0026tt.y)\n\t\t\tif *res != tt.want {\n\t\t\t\tt.Errorf(\n\t\t\t\t\t\"Or(%s, %s) = %s, want %s\",\n\t\t\t\t\ttt.x.ToString(), tt.y.ToString(), res.ToString(), (tt.want).ToString(),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestAnd(t *testing.T) {\n\ttests := []logicOpTest{\n\t\t{\n\t\t\tname: \"all zeros\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 2\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 3\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand zero\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}},\n\t\t\twant: Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tres := new(Uint).And(\u0026tt.x, \u0026tt.y)\n\t\t\tif *res != tt.want {\n\t\t\t\tt.Errorf(\n\t\t\t\t\t\"And(%s, %s) = %s, want %s\",\n\t\t\t\t\ttt.x.ToString(), tt.y.ToString(), res.ToString(), (tt.want).ToString(),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNot(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tx Uint\n\t\twant Uint\n\t}{\n\t\t{\n\t\t\tname: \"all zeros\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tres := new(Uint).Not(\u0026tt.x)\n\t\t\tif *res != tt.want {\n\t\t\t\tt.Errorf(\n\t\t\t\t\t\"Not(%s) = %s, want %s\",\n\t\t\t\t\ttt.x.ToString(), res.ToString(), (tt.want).ToString(),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestAndNot(t *testing.T) {\n\ttests := []logicOpTest{\n\t\t{\n\t\t\tname: \"all zeros\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 2\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 3\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand zero\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}},\n\t\t\twant: Uint{arr: [4]uint64{0xAAAAAAAAAAAAAAAA, 0x5555555555555555, 0x0000000000000000, ^uint64(0)}},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tres := new(Uint).AndNot(\u0026tt.x, \u0026tt.y)\n\t\t\tif *res != tt.want {\n\t\t\t\tt.Errorf(\n\t\t\t\t\t\"AndNot(%s, %s) = %s, want %s\",\n\t\t\t\t\ttt.x.ToString(), tt.y.ToString(), res.ToString(), (tt.want).ToString(),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestXor(t *testing.T) {\n\ttests := []logicOpTest{\n\t\t{\n\t\t\tname: \"all zeros\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 2\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 3\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand zero\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF}},\n\t\t\twant: Uint{arr: [4]uint64{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}},\n\t\t\twant: Uint{arr: [4]uint64{0xAAAAAAAAAAAAAAAA, 0x5555555555555555, 0x0000000000000000, ^uint64(0)}},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tres := new(Uint).Xor(\u0026tt.x, \u0026tt.y)\n\t\t\tif *res != tt.want {\n\t\t\t\tt.Errorf(\n\t\t\t\t\t\"Xor(%s, %s) = %s, want %s\",\n\t\t\t\t\ttt.x.ToString(), tt.y.ToString(), res.ToString(), (tt.want).ToString(),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestLsh(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty uint\n\t\twant string\n\t}{\n\t\t{\"0\", 0, \"0\"},\n\t\t{\"0\", 1, \"0\"},\n\t\t{\"0\", 64, \"0\"},\n\t\t{\"1\", 0, \"1\"},\n\t\t{\"1\", 1, \"2\"},\n\t\t{\"1\", 64, \"18446744073709551616\"},\n\t\t{\"1\", 128, \"340282366920938463463374607431768211456\"},\n\t\t{\"1\", 192, \"6277101735386680763835789423207666416102355444464034512896\"},\n\t\t{\"1\", 255, \"57896044618658097711785492504343953926634992332820282019728792003956564819968\"},\n\t\t{\"1\", 256, \"0\"},\n\t\t{\"31337\", 0, \"31337\"},\n\t\t{\"31337\", 1, \"62674\"},\n\t\t{\"31337\", 64, \"578065619037836218990592\"},\n\t\t{\"31337\", 128, \"10663428532201448629551770073089320442396672\"},\n\t\t{\"31337\", 192, \"196705537081812415096322133155058642481399512563169449530621952\"},\n\t\t{\"31337\", 193, \"393411074163624830192644266310117284962799025126338899061243904\"},\n\t\t{\"31337\", 255, \"57896044618658097711785492504343953926634992332820282019728792003956564819968\"},\n\t\t{\"31337\", 256, \"0\"},\n\t\t// 64 \u003c n \u003c 128\n\t\t{\"1\", 65, \"36893488147419103232\"},\n\t\t{\"31337\", 100, \"39724366859352024754702188346867712\"},\n\n\t\t// 128 \u003c n \u003c 192\n\t\t{\"1\", 129, \"680564733841876926926749214863536422912\"},\n\t\t{\"31337\", 150, \"44725660946326664792723507424638829088826130956288\"},\n\n\t\t// 192 \u003c n \u003c 256\n\t\t{\"1\", 193, \"12554203470773361527671578846415332832204710888928069025792\"},\n\t\t{\"31337\", 200, \"50356617492943978264658466087695012475238275216171379079839219712\"},\n\n\t\t// n \u003e 256\n\t\t{\"1\", 257, \"0\"},\n\t\t{\"31337\", 300, \"0\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\twant := MustFromDecimal(tt.want)\n\n\t\tgot := new(Uint).Lsh(x, tt.y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Lsh(%s, %d) = %s, want %s\", tt.x, tt.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestRsh(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty uint\n\t\twant string\n\t}{\n\t\t{\"0\", 0, \"0\"},\n\t\t{\"0\", 1, \"0\"},\n\t\t{\"0\", 64, \"0\"},\n\t\t{\"1\", 0, \"1\"},\n\t\t{\"1\", 1, \"0\"},\n\t\t{\"1\", 64, \"0\"},\n\t\t{\"1\", 128, \"0\"},\n\t\t{\"1\", 192, \"0\"},\n\t\t{\"1\", 255, \"0\"},\n\t\t{\"57896044618658097711785492504343953926634992332820282019728792003956564819968\", 255, \"1\"},\n\t\t{\"6277101735386680763835789423207666416102355444464034512896\", 192, \"1\"},\n\t\t{\"340282366920938463463374607431768211456\", 128, \"1\"},\n\t\t{\"18446744073709551616\", 64, \"1\"},\n\t\t{\"393411074163624830192644266310117284962799025126338899061243904\", 193, \"31337\"},\n\t\t{\"196705537081812415096322133155058642481399512563169449530621952\", 192, \"31337\"},\n\t\t{\"10663428532201448629551770073089320442396672\", 128, \"31337\"},\n\t\t{\"578065619037836218990592\", 64, \"31337\"},\n\t\t{twoPow256Sub1, 256, \"0\"},\n\t\t// outliers\n\t\t{\"340282366920938463463374607431768211455\", 129, \"0\"},\n\t\t{\"18446744073709551615\", 65, \"0\"},\n\t\t{twoPow256Sub1, 1, \"57896044618658097711785492504343953926634992332820282019728792003956564819967\"},\n\n\t\t// n \u003e 256\n\t\t{\"1\", 257, \"0\"},\n\t\t{\"31337\", 300, \"0\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\n\t\twant := MustFromDecimal(tt.want)\n\t\tgot := new(Uint).Rsh(x, tt.y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Rsh(%s, %d) = %s, want %s\", tt.x, tt.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestSRsh(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty uint\n\t\twant string\n\t}{\n\t\t// Positive numbers (behaves like Rsh)\n\t\t{\"0x0\", 0, \"0x0\"},\n\t\t{\"0x0\", 1, \"0x0\"},\n\t\t{\"0x1\", 0, \"0x1\"},\n\t\t{\"0x1\", 1, \"0x0\"},\n\t\t{\"0x31337\", 0, \"0x31337\"},\n\t\t{\"0x31337\", 4, \"0x3133\"},\n\t\t{\"0x31337\", 8, \"0x313\"},\n\t\t{\"0x31337\", 16, \"0x3\"},\n\t\t{\"0x10000000000000000\", 64, \"0x1\"}, // 2^64 \u003e\u003e 64\n\n\t\t// // Numbers with MSB set (negative numbers in two's complement)\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 0, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 1, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 4, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 64, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 128, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 192, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 255, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\n\t\t// Large positive number close to max value\n\t\t{\"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 1, \"0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 2, \"0x1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 64, \"0x7fffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 128, \"0x7fffffffffffffffffffffffffffffff\"},\n\t\t{\"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 192, \"0x7fffffffffffffff\"},\n\t\t{\"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 255, \"0x0\"},\n\n\t\t// Specific cases\n\t\t{\"0x8000000000000000000000000000000000000000000000000000000000000000\", 1, \"0xc000000000000000000000000000000000000000000000000000000000000000\"},\n\t\t{\"0x8000000000000000000000000000000000000000000000000000000000000001\", 1, \"0xc000000000000000000000000000000000000000000000000000000000000000\"},\n\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 65, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 127, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 129, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 193, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\n\t\t// n \u003e 256\n\t\t{\"0x1\", 257, \"0x0\"},\n\t\t{\"0x31337\", 300, \"0x0\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromHex(tt.x)\n\t\twant := MustFromHex(tt.want)\n\n\t\tgot := new(Uint).SRsh(x, tt.y)\n\n\t\tif !got.Eq(want) {\n\t\t\tt.Errorf(\"SRsh(%s, %d) = %s, want %s\", tt.x, tt.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n"},{"name":"cmp.gno","body":"// cmp (or, comparisons) includes methods for comparing Uint instances.\n// These comparison functions cover a range of operations including equality checks, less than/greater than\n// evaluations, and specialized comparisons such as signed greater than. These are fundamental for logical\n// decision making based on Uint values.\npackage uint256\n\nimport (\n\t\"math/bits\"\n)\n\n// Cmp compares z and x and returns:\n//\n//\t-1 if z \u003c x\n//\t 0 if z == x\n//\t+1 if z \u003e x\nfunc (z *Uint) Cmp(x *Uint) (r int) {\n\t// z \u003c x \u003c=\u003e z - x \u003c 0 i.e. when subtraction overflows.\n\td0, carry := bits.Sub64(z.arr[0], x.arr[0], 0)\n\td1, carry := bits.Sub64(z.arr[1], x.arr[1], carry)\n\td2, carry := bits.Sub64(z.arr[2], x.arr[2], carry)\n\td3, carry := bits.Sub64(z.arr[3], x.arr[3], carry)\n\tif carry == 1 {\n\t\treturn -1\n\t}\n\tif d0|d1|d2|d3 == 0 {\n\t\treturn 0\n\t}\n\treturn 1\n}\n\n// IsZero returns true if z == 0\nfunc (z *Uint) IsZero() bool {\n\treturn (z.arr[0] | z.arr[1] | z.arr[2] | z.arr[3]) == 0\n}\n\n// Sign returns:\n//\n//\t-1 if z \u003c 0\n//\t 0 if z == 0\n//\t+1 if z \u003e 0\n//\n// Where z is interpreted as a two's complement signed number\nfunc (z *Uint) Sign() int {\n\tif z.IsZero() {\n\t\treturn 0\n\t}\n\tif z.arr[3] \u003c 0x8000000000000000 {\n\t\treturn 1\n\t}\n\treturn -1\n}\n\n// LtUint64 returns true if z is smaller than n\nfunc (z *Uint) LtUint64(n uint64) bool {\n\treturn z.arr[0] \u003c n \u0026\u0026 (z.arr[1]|z.arr[2]|z.arr[3]) == 0\n}\n\n// GtUint64 returns true if z is larger than n\nfunc (z *Uint) GtUint64(n uint64) bool {\n\treturn z.arr[0] \u003e n || (z.arr[1]|z.arr[2]|z.arr[3]) != 0\n}\n\n// Lt returns true if z \u003c x\nfunc (z *Uint) Lt(x *Uint) bool {\n\t// z \u003c x \u003c=\u003e z - x \u003c 0 i.e. when subtraction overflows.\n\t_, carry := bits.Sub64(z.arr[0], x.arr[0], 0)\n\t_, carry = bits.Sub64(z.arr[1], x.arr[1], carry)\n\t_, carry = bits.Sub64(z.arr[2], x.arr[2], carry)\n\t_, carry = bits.Sub64(z.arr[3], x.arr[3], carry)\n\n\treturn carry != 0\n}\n\n// Gt returns true if z \u003e x\nfunc (z *Uint) Gt(x *Uint) bool {\n\treturn x.Lt(z)\n}\n\n// Lte returns true if z \u003c= x\nfunc (z *Uint) Lte(x *Uint) bool {\n\tcond1 := z.Lt(x)\n\tcond2 := z.Eq(x)\n\n\tif cond1 || cond2 {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// Gte returns true if z \u003e= x\nfunc (z *Uint) Gte(x *Uint) bool {\n\tcond1 := z.Gt(x)\n\tcond2 := z.Eq(x)\n\n\tif cond1 || cond2 {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// Eq returns true if z == x\nfunc (z *Uint) Eq(x *Uint) bool {\n\treturn (z.arr[0] == x.arr[0]) \u0026\u0026 (z.arr[1] == x.arr[1]) \u0026\u0026 (z.arr[2] == x.arr[2]) \u0026\u0026 (z.arr[3] == x.arr[3])\n}\n\n// Neq returns true if z != x\nfunc (z *Uint) Neq(x *Uint) bool {\n\treturn !z.Eq(x)\n}\n\n// Sgt interprets z and x as signed integers, and returns\n// true if z \u003e x\nfunc (z *Uint) Sgt(x *Uint) bool {\n\tzSign := z.Sign()\n\txSign := x.Sign()\n\n\tswitch {\n\tcase zSign \u003e= 0 \u0026\u0026 xSign \u003c 0:\n\t\treturn true\n\tcase zSign \u003c 0 \u0026\u0026 xSign \u003e= 0:\n\t\treturn false\n\tdefault:\n\t\treturn z.Gt(x)\n\t}\n}\n"},{"name":"cmp_test.gno","body":"package uint256\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestSign(t *testing.T) {\n\ttests := []struct {\n\t\tinput *Uint\n\t\texpected int\n\t}{\n\t\t{\n\t\t\tinput: NewUint(0),\n\t\t\texpected: 0,\n\t\t},\n\t\t{\n\t\t\tinput: NewUint(1),\n\t\t\texpected: 1,\n\t\t},\n\t\t{\n\t\t\tinput: NewUint(0x7fffffffffffffff),\n\t\t\texpected: 1,\n\t\t},\n\t\t{\n\t\t\tinput: NewUint(0x8000000000000000),\n\t\t\texpected: 1,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.input.ToString(), func(t *testing.T) {\n\t\t\tresult := tt.input.Sign()\n\t\t\tif result != tt.expected {\n\t\t\t\tt.Errorf(\"Sign() = %d; want %d\", result, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCmp(t *testing.T) {\n\ttests := []struct {\n\t\tx, y string\n\t\twant int\n\t}{\n\t\t{\"0\", \"0\", 0},\n\t\t{\"0\", \"1\", -1},\n\t\t{\"1\", \"0\", 1},\n\t\t{\"1\", \"1\", 0},\n\t\t{\"10\", \"10\", 0},\n\t\t{\"10\", \"11\", -1},\n\t\t{\"11\", \"10\", 1},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx := MustFromDecimal(tc.x)\n\t\ty := MustFromDecimal(tc.y)\n\n\t\tgot := x.Cmp(y)\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Cmp(%s, %s) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestIsZero(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant bool\n\t}{\n\t\t{\"0\", true},\n\t\t{\"1\", false},\n\t\t{\"10\", false},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\n\t\tgot := x.IsZero()\n\t\tif got != tt.want {\n\t\t\tt.Errorf(\"IsZero(%s) = %v, want %v\", tt.x, got, tt.want)\n\t\t}\n\t}\n}\n\nfunc TestLtUint64(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty uint64\n\t\twant bool\n\t}{\n\t\t{\"0\", 1, true},\n\t\t{\"1\", 0, false},\n\t\t{\"10\", 10, false},\n\t\t{\"0xffffffffffffffff\", 0, false},\n\t\t{\"0x10000000000000000\", 10000000000000000, false},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx := parseTestString(t, tc.x)\n\n\t\tgot := x.LtUint64(tc.y)\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"LtUint64(%s, %d) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestUint_GtUint64(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tz string\n\t\tn uint64\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"z \u003e n\",\n\t\t\tz: \"1\",\n\t\t\tn: 0,\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"z \u003c n\",\n\t\t\tz: \"18446744073709551615\",\n\t\t\tn: 0xFFFFFFFFFFFFFFFF,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"z == n\",\n\t\t\tz: \"18446744073709551615\",\n\t\t\tn: 0xFFFFFFFFFFFFFFFF,\n\t\t\twant: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tz := MustFromDecimal(tt.z)\n\n\t\t\tif got := z.GtUint64(tt.n); got != tt.want {\n\t\t\t\tt.Errorf(\"Uint.GtUint64() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSGT(t *testing.T) {\n\tx := MustFromHex(\"0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe\")\n\ty := MustFromHex(\"0x0\")\n\tactual := x.Sgt(y)\n\tif actual {\n\t\tt.Fatalf(\"Expected %v false\", actual)\n\t}\n\n\tx = MustFromHex(\"0x0\")\n\ty = MustFromHex(\"0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe\")\n\tactual = x.Sgt(y)\n\tif !actual {\n\t\tt.Fatalf(\"Expected %v true\", actual)\n\t}\n}\n\nfunc TestEq(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty string\n\t\twant bool\n\t}{\n\t\t{\"0xffffffffffffffff\", \"18446744073709551615\", true},\n\t\t{\"0x10000000000000000\", \"18446744073709551616\", true},\n\t\t{\"0\", \"0\", true},\n\t\t{twoPow256Sub1, twoPow256Sub1, true},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := parseTestString(t, tt.x)\n\n\t\ty, err := FromDecimal(tt.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.Eq(y)\n\n\t\tif got != tt.want {\n\t\t\tt.Errorf(\"Eq(%s, %s) = %v, want %v\", tt.x, tt.y, got, tt.want)\n\t\t}\n\t}\n}\n\nfunc TestUint_Lte(t *testing.T) {\n\ttests := []struct {\n\t\tz, x string\n\t\twant bool\n\t}{\n\t\t{\"10\", \"20\", true},\n\t\t{\"20\", \"10\", false},\n\t\t{\"10\", \"10\", true},\n\t\t{\"0\", \"0\", true},\n\t}\n\n\tfor _, tt := range tests {\n\t\tz, err := FromDecimal(tt.z)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\t\tx, err := FromDecimal(tt.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\t\tif got := z.Lte(x); got != tt.want {\n\t\t\tt.Errorf(\"Uint.Lte(%v, %v) = %v, want %v\", tt.z, tt.x, got, tt.want)\n\t\t}\n\t}\n}\n\nfunc TestUint_Gte(t *testing.T) {\n\ttests := []struct {\n\t\tz, x string\n\t\twant bool\n\t}{\n\t\t{\"20\", \"10\", true},\n\t\t{\"10\", \"20\", false},\n\t\t{\"10\", \"10\", true},\n\t\t{\"0\", \"0\", true},\n\t}\n\n\tfor _, tt := range tests {\n\t\tz := parseTestString(t, tt.z)\n\t\tx := parseTestString(t, tt.x)\n\n\t\tif got := z.Gte(x); got != tt.want {\n\t\t\tt.Errorf(\"Uint.Gte(%v, %v) = %v, want %v\", tt.z, tt.x, got, tt.want)\n\t\t}\n\t}\n}\n\nfunc parseTestString(_ *testing.T, s string) *Uint {\n\tvar x *Uint\n\n\tif strings.HasPrefix(s, \"0x\") {\n\t\tx = MustFromHex(s)\n\t} else {\n\t\tx = MustFromDecimal(s)\n\t}\n\n\treturn x\n}\n"},{"name":"conversion.gno","body":"// conversions contains methods for converting Uint instances to other types and vice versa.\n// This includes conversions to and from basic types such as uint64 and int32, as well as string representations\n// and byte slices. Additionally, it covers marshaling and unmarshaling for JSON and other text formats.\npackage uint256\n\nimport (\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// Uint64 returns the lower 64-bits of z\nfunc (z *Uint) Uint64() uint64 {\n\treturn z.arr[0]\n}\n\n// Uint64WithOverflow returns the lower 64-bits of z and bool whether overflow occurred\nfunc (z *Uint) Uint64WithOverflow() (uint64, bool) {\n\treturn z.arr[0], (z.arr[1] | z.arr[2] | z.arr[3]) != 0\n}\n\n// SetUint64 sets z to the value x\nfunc (z *Uint) SetUint64(x uint64) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, 0, 0, x\n\treturn z\n}\n\n// IsUint64 reports whether z can be represented as a uint64.\nfunc (z *Uint) IsUint64() bool {\n\treturn (z.arr[1] | z.arr[2] | z.arr[3]) == 0\n}\n\n// Dec returns the decimal representation of z.\nfunc (z *Uint) Dec() string {\n\tif z.IsZero() {\n\t\treturn \"0\"\n\t}\n\tif z.IsUint64() {\n\t\treturn strconv.FormatUint(z.Uint64(), 10)\n\t}\n\n\t// The max uint64 value being 18446744073709551615, the largest\n\t// power-of-ten below that is 10000000000000000000.\n\t// When we do a DivMod using that number, the remainder that we\n\t// get back is the lower part of the output.\n\t//\n\t// The ascii-output of remainder will never exceed 19 bytes (since it will be\n\t// below 10000000000000000000).\n\t//\n\t// Algorithm example using 100 as divisor\n\t//\n\t// 12345 % 100 = 45 (rem)\n\t// 12345 / 100 = 123 (quo)\n\t// -\u003e output '45', continue iterate on 123\n\tvar (\n\t\t// out is 98 bytes long: 78 (max size of a string without leading zeroes,\n\t\t// plus slack so we can copy 19 bytes every iteration).\n\t\t// We init it with zeroes, because when strconv appends the ascii representations,\n\t\t// it will omit leading zeroes.\n\t\tout = []byte(\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\")\n\t\tdivisor = NewUint(10000000000000000000) // 20 digits\n\t\ty = new(Uint).Set(z) // copy to avoid modifying z\n\t\tpos = len(out) // position to write to\n\t\tbuf = make([]byte, 0, 19) // buffer to write uint64:s to\n\t)\n\tfor {\n\t\t// Obtain Q and R for divisor\n\t\tvar quot Uint\n\t\trem := udivrem(quot.arr[:], y.arr[:], divisor)\n\t\ty.Set(\u0026quot) // Set Q for next loop\n\t\t// Convert the R to ascii representation\n\t\tbuf = strconv.AppendUint(buf[:0], rem.Uint64(), 10)\n\t\t// Copy in the ascii digits\n\t\tcopy(out[pos-len(buf):], buf)\n\t\tif y.IsZero() {\n\t\t\tbreak\n\t\t}\n\t\t// Move 19 digits left\n\t\tpos -= 19\n\t}\n\t// skip leading zeroes by only using the 'used size' of buf\n\treturn string(out[pos-len(buf):])\n}\n\nfunc (z *Uint) Scan(src interface{}) error {\n\tif src == nil {\n\t\tz.Clear()\n\t\treturn nil\n\t}\n\n\tswitch src := src.(type) {\n\tcase string:\n\t\treturn z.scanScientificFromString(src)\n\tcase []byte:\n\t\treturn z.scanScientificFromString(string(src))\n\t}\n\treturn errors.New(\"default // unsupported type: can't convert to uint256.Uint\")\n}\n\nfunc (z *Uint) scanScientificFromString(src string) error {\n\tif len(src) == 0 {\n\t\tz.Clear()\n\t\treturn nil\n\t}\n\n\tidx := strings.IndexByte(src, 'e')\n\tif idx == -1 {\n\t\treturn z.SetFromDecimal(src)\n\t}\n\tif err := z.SetFromDecimal(src[:idx]); err != nil {\n\t\treturn err\n\t}\n\tif src[(idx+1):] == \"0\" {\n\t\treturn nil\n\t}\n\texp := new(Uint)\n\tif err := exp.SetFromDecimal(src[(idx + 1):]); err != nil {\n\t\treturn err\n\t}\n\tif exp.GtUint64(77) { // 10**78 is larger than 2**256\n\t\treturn ErrBig256Range\n\t}\n\texp.Exp(NewUint(10), exp)\n\tif _, overflow := z.MulOverflow(z, exp); overflow {\n\t\treturn ErrBig256Range\n\t}\n\treturn nil\n}\n\n// ToString returns the decimal string representation of z. It returns an empty string if z is nil.\n// OBS: doesn't exist from holiman's uint256\nfunc (z *Uint) ToString() string {\n\tif z == nil {\n\t\treturn \"\"\n\t}\n\n\treturn z.Dec()\n}\n\n// MarshalJSON implements json.Marshaler.\n// MarshalJSON marshals using the 'decimal string' representation. This is _not_ compatible\n// with big.Uint: big.Uint marshals into JSON 'native' numeric format.\n//\n// The JSON native format is, on some platforms, (e.g. javascript), limited to 53-bit large\n// integer space. Thus, U256 uses string-format, which is not compatible with\n// big.int (big.Uint refuses to unmarshal a string representation).\nfunc (z *Uint) MarshalJSON() ([]byte, error) {\n\treturn []byte(`\"` + z.Dec() + `\"`), nil\n}\n\n// UnmarshalJSON implements json.Unmarshaler. UnmarshalJSON accepts either\n// - Quoted string: either hexadecimal OR decimal\n// - Not quoted string: only decimal\nfunc (z *Uint) UnmarshalJSON(input []byte) error {\n\tif len(input) \u003c 2 || input[0] != '\"' || input[len(input)-1] != '\"' {\n\t\t// if not quoted, it must be decimal\n\t\treturn z.fromDecimal(string(input))\n\t}\n\treturn z.UnmarshalText(input[1 : len(input)-1])\n}\n\n// MarshalText implements encoding.TextMarshaler\n// MarshalText marshals using the decimal representation (compatible with big.Uint)\nfunc (z *Uint) MarshalText() ([]byte, error) {\n\treturn []byte(z.Dec()), nil\n}\n\n// UnmarshalText implements encoding.TextUnmarshaler. This method\n// can unmarshal either hexadecimal or decimal.\n// - For hexadecimal, the input _must_ be prefixed with 0x or 0X\nfunc (z *Uint) UnmarshalText(input []byte) error {\n\tif len(input) \u003e= 2 \u0026\u0026 input[0] == '0' \u0026\u0026 (input[1] == 'x' || input[1] == 'X') {\n\t\treturn z.fromHex(string(input))\n\t}\n\treturn z.fromDecimal(string(input))\n}\n\n// SetBytes interprets buf as the bytes of a big-endian unsigned\n// integer, sets z to that value, and returns z.\n// If buf is larger than 32 bytes, the last 32 bytes is used.\nfunc (z *Uint) SetBytes(buf []byte) *Uint {\n\tswitch l := len(buf); l {\n\tcase 0:\n\t\tz.Clear()\n\tcase 1:\n\t\tz.SetBytes1(buf)\n\tcase 2:\n\t\tz.SetBytes2(buf)\n\tcase 3:\n\t\tz.SetBytes3(buf)\n\tcase 4:\n\t\tz.SetBytes4(buf)\n\tcase 5:\n\t\tz.SetBytes5(buf)\n\tcase 6:\n\t\tz.SetBytes6(buf)\n\tcase 7:\n\t\tz.SetBytes7(buf)\n\tcase 8:\n\t\tz.SetBytes8(buf)\n\tcase 9:\n\t\tz.SetBytes9(buf)\n\tcase 10:\n\t\tz.SetBytes10(buf)\n\tcase 11:\n\t\tz.SetBytes11(buf)\n\tcase 12:\n\t\tz.SetBytes12(buf)\n\tcase 13:\n\t\tz.SetBytes13(buf)\n\tcase 14:\n\t\tz.SetBytes14(buf)\n\tcase 15:\n\t\tz.SetBytes15(buf)\n\tcase 16:\n\t\tz.SetBytes16(buf)\n\tcase 17:\n\t\tz.SetBytes17(buf)\n\tcase 18:\n\t\tz.SetBytes18(buf)\n\tcase 19:\n\t\tz.SetBytes19(buf)\n\tcase 20:\n\t\tz.SetBytes20(buf)\n\tcase 21:\n\t\tz.SetBytes21(buf)\n\tcase 22:\n\t\tz.SetBytes22(buf)\n\tcase 23:\n\t\tz.SetBytes23(buf)\n\tcase 24:\n\t\tz.SetBytes24(buf)\n\tcase 25:\n\t\tz.SetBytes25(buf)\n\tcase 26:\n\t\tz.SetBytes26(buf)\n\tcase 27:\n\t\tz.SetBytes27(buf)\n\tcase 28:\n\t\tz.SetBytes28(buf)\n\tcase 29:\n\t\tz.SetBytes29(buf)\n\tcase 30:\n\t\tz.SetBytes30(buf)\n\tcase 31:\n\t\tz.SetBytes31(buf)\n\tdefault:\n\t\tz.SetBytes32(buf[l-32:])\n\t}\n\treturn z\n}\n\n// SetBytes1 is identical to SetBytes(in[:1]), but panics is input is too short\nfunc (z *Uint) SetBytes1(in []byte) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = uint64(in[0])\n\treturn z\n}\n\n// SetBytes2 is identical to SetBytes(in[:2]), but panics is input is too short\nfunc (z *Uint) SetBytes2(in []byte) *Uint {\n\t_ = in[1] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = uint64(binary.BigEndian.Uint16(in[0:2]))\n\treturn z\n}\n\n// SetBytes3 is identical to SetBytes(in[:3]), but panics is input is too short\nfunc (z *Uint) SetBytes3(in []byte) *Uint {\n\t_ = in[2] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = uint64(binary.BigEndian.Uint16(in[1:3])) | uint64(in[0])\u003c\u003c16\n\treturn z\n}\n\n// SetBytes4 is identical to SetBytes(in[:4]), but panics is input is too short\nfunc (z *Uint) SetBytes4(in []byte) *Uint {\n\t_ = in[3] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = uint64(binary.BigEndian.Uint32(in[0:4]))\n\treturn z\n}\n\n// SetBytes5 is identical to SetBytes(in[:5]), but panics is input is too short\nfunc (z *Uint) SetBytes5(in []byte) *Uint {\n\t_ = in[4] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = bigEndianUint40(in[0:5])\n\treturn z\n}\n\n// SetBytes6 is identical to SetBytes(in[:6]), but panics is input is too short\nfunc (z *Uint) SetBytes6(in []byte) *Uint {\n\t_ = in[5] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = bigEndianUint48(in[0:6])\n\treturn z\n}\n\n// SetBytes7 is identical to SetBytes(in[:7]), but panics is input is too short\nfunc (z *Uint) SetBytes7(in []byte) *Uint {\n\t_ = in[6] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = bigEndianUint56(in[0:7])\n\treturn z\n}\n\n// SetBytes8 is identical to SetBytes(in[:8]), but panics is input is too short\nfunc (z *Uint) SetBytes8(in []byte) *Uint {\n\t_ = in[7] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = binary.BigEndian.Uint64(in[0:8])\n\treturn z\n}\n\n// SetBytes9 is identical to SetBytes(in[:9]), but panics is input is too short\nfunc (z *Uint) SetBytes9(in []byte) *Uint {\n\t_ = in[8] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = uint64(in[0])\n\tz.arr[0] = binary.BigEndian.Uint64(in[1:9])\n\treturn z\n}\n\n// SetBytes10 is identical to SetBytes(in[:10]), but panics is input is too short\nfunc (z *Uint) SetBytes10(in []byte) *Uint {\n\t_ = in[9] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = uint64(binary.BigEndian.Uint16(in[0:2]))\n\tz.arr[0] = binary.BigEndian.Uint64(in[2:10])\n\treturn z\n}\n\n// SetBytes11 is identical to SetBytes(in[:11]), but panics is input is too short\nfunc (z *Uint) SetBytes11(in []byte) *Uint {\n\t_ = in[10] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = uint64(binary.BigEndian.Uint16(in[1:3])) | uint64(in[0])\u003c\u003c16\n\tz.arr[0] = binary.BigEndian.Uint64(in[3:11])\n\treturn z\n}\n\n// SetBytes12 is identical to SetBytes(in[:12]), but panics is input is too short\nfunc (z *Uint) SetBytes12(in []byte) *Uint {\n\t_ = in[11] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = uint64(binary.BigEndian.Uint32(in[0:4]))\n\tz.arr[0] = binary.BigEndian.Uint64(in[4:12])\n\treturn z\n}\n\n// SetBytes13 is identical to SetBytes(in[:13]), but panics is input is too short\nfunc (z *Uint) SetBytes13(in []byte) *Uint {\n\t_ = in[12] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = bigEndianUint40(in[0:5])\n\tz.arr[0] = binary.BigEndian.Uint64(in[5:13])\n\treturn z\n}\n\n// SetBytes14 is identical to SetBytes(in[:14]), but panics is input is too short\nfunc (z *Uint) SetBytes14(in []byte) *Uint {\n\t_ = in[13] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = bigEndianUint48(in[0:6])\n\tz.arr[0] = binary.BigEndian.Uint64(in[6:14])\n\treturn z\n}\n\n// SetBytes15 is identical to SetBytes(in[:15]), but panics is input is too short\nfunc (z *Uint) SetBytes15(in []byte) *Uint {\n\t_ = in[14] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = bigEndianUint56(in[0:7])\n\tz.arr[0] = binary.BigEndian.Uint64(in[7:15])\n\treturn z\n}\n\n// SetBytes16 is identical to SetBytes(in[:16]), but panics is input is too short\nfunc (z *Uint) SetBytes16(in []byte) *Uint {\n\t_ = in[15] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = binary.BigEndian.Uint64(in[0:8])\n\tz.arr[0] = binary.BigEndian.Uint64(in[8:16])\n\treturn z\n}\n\n// SetBytes17 is identical to SetBytes(in[:17]), but panics is input is too short\nfunc (z *Uint) SetBytes17(in []byte) *Uint {\n\t_ = in[16] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = uint64(in[0])\n\tz.arr[1] = binary.BigEndian.Uint64(in[1:9])\n\tz.arr[0] = binary.BigEndian.Uint64(in[9:17])\n\treturn z\n}\n\n// SetBytes18 is identical to SetBytes(in[:18]), but panics is input is too short\nfunc (z *Uint) SetBytes18(in []byte) *Uint {\n\t_ = in[17] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = uint64(binary.BigEndian.Uint16(in[0:2]))\n\tz.arr[1] = binary.BigEndian.Uint64(in[2:10])\n\tz.arr[0] = binary.BigEndian.Uint64(in[10:18])\n\treturn z\n}\n\n// SetBytes19 is identical to SetBytes(in[:19]), but panics is input is too short\nfunc (z *Uint) SetBytes19(in []byte) *Uint {\n\t_ = in[18] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = uint64(binary.BigEndian.Uint16(in[1:3])) | uint64(in[0])\u003c\u003c16\n\tz.arr[1] = binary.BigEndian.Uint64(in[3:11])\n\tz.arr[0] = binary.BigEndian.Uint64(in[11:19])\n\treturn z\n}\n\n// SetBytes20 is identical to SetBytes(in[:20]), but panics is input is too short\nfunc (z *Uint) SetBytes20(in []byte) *Uint {\n\t_ = in[19] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = uint64(binary.BigEndian.Uint32(in[0:4]))\n\tz.arr[1] = binary.BigEndian.Uint64(in[4:12])\n\tz.arr[0] = binary.BigEndian.Uint64(in[12:20])\n\treturn z\n}\n\n// SetBytes21 is identical to SetBytes(in[:21]), but panics is input is too short\nfunc (z *Uint) SetBytes21(in []byte) *Uint {\n\t_ = in[20] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = bigEndianUint40(in[0:5])\n\tz.arr[1] = binary.BigEndian.Uint64(in[5:13])\n\tz.arr[0] = binary.BigEndian.Uint64(in[13:21])\n\treturn z\n}\n\n// SetBytes22 is identical to SetBytes(in[:22]), but panics is input is too short\nfunc (z *Uint) SetBytes22(in []byte) *Uint {\n\t_ = in[21] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = bigEndianUint48(in[0:6])\n\tz.arr[1] = binary.BigEndian.Uint64(in[6:14])\n\tz.arr[0] = binary.BigEndian.Uint64(in[14:22])\n\treturn z\n}\n\n// SetBytes23 is identical to SetBytes(in[:23]), but panics is input is too short\nfunc (z *Uint) SetBytes23(in []byte) *Uint {\n\t_ = in[22] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = bigEndianUint56(in[0:7])\n\tz.arr[1] = binary.BigEndian.Uint64(in[7:15])\n\tz.arr[0] = binary.BigEndian.Uint64(in[15:23])\n\treturn z\n}\n\n// SetBytes24 is identical to SetBytes(in[:24]), but panics is input is too short\nfunc (z *Uint) SetBytes24(in []byte) *Uint {\n\t_ = in[23] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = binary.BigEndian.Uint64(in[0:8])\n\tz.arr[1] = binary.BigEndian.Uint64(in[8:16])\n\tz.arr[0] = binary.BigEndian.Uint64(in[16:24])\n\treturn z\n}\n\n// SetBytes25 is identical to SetBytes(in[:25]), but panics is input is too short\nfunc (z *Uint) SetBytes25(in []byte) *Uint {\n\t_ = in[24] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = uint64(in[0])\n\tz.arr[2] = binary.BigEndian.Uint64(in[1:9])\n\tz.arr[1] = binary.BigEndian.Uint64(in[9:17])\n\tz.arr[0] = binary.BigEndian.Uint64(in[17:25])\n\treturn z\n}\n\n// SetBytes26 is identical to SetBytes(in[:26]), but panics is input is too short\nfunc (z *Uint) SetBytes26(in []byte) *Uint {\n\t_ = in[25] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = uint64(binary.BigEndian.Uint16(in[0:2]))\n\tz.arr[2] = binary.BigEndian.Uint64(in[2:10])\n\tz.arr[1] = binary.BigEndian.Uint64(in[10:18])\n\tz.arr[0] = binary.BigEndian.Uint64(in[18:26])\n\treturn z\n}\n\n// SetBytes27 is identical to SetBytes(in[:27]), but panics is input is too short\nfunc (z *Uint) SetBytes27(in []byte) *Uint {\n\t_ = in[26] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = uint64(binary.BigEndian.Uint16(in[1:3])) | uint64(in[0])\u003c\u003c16\n\tz.arr[2] = binary.BigEndian.Uint64(in[3:11])\n\tz.arr[1] = binary.BigEndian.Uint64(in[11:19])\n\tz.arr[0] = binary.BigEndian.Uint64(in[19:27])\n\treturn z\n}\n\n// SetBytes28 is identical to SetBytes(in[:28]), but panics is input is too short\nfunc (z *Uint) SetBytes28(in []byte) *Uint {\n\t_ = in[27] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = uint64(binary.BigEndian.Uint32(in[0:4]))\n\tz.arr[2] = binary.BigEndian.Uint64(in[4:12])\n\tz.arr[1] = binary.BigEndian.Uint64(in[12:20])\n\tz.arr[0] = binary.BigEndian.Uint64(in[20:28])\n\treturn z\n}\n\n// SetBytes29 is identical to SetBytes(in[:29]), but panics is input is too short\nfunc (z *Uint) SetBytes29(in []byte) *Uint {\n\t_ = in[23] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = bigEndianUint40(in[0:5])\n\tz.arr[2] = binary.BigEndian.Uint64(in[5:13])\n\tz.arr[1] = binary.BigEndian.Uint64(in[13:21])\n\tz.arr[0] = binary.BigEndian.Uint64(in[21:29])\n\treturn z\n}\n\n// SetBytes30 is identical to SetBytes(in[:30]), but panics is input is too short\nfunc (z *Uint) SetBytes30(in []byte) *Uint {\n\t_ = in[29] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = bigEndianUint48(in[0:6])\n\tz.arr[2] = binary.BigEndian.Uint64(in[6:14])\n\tz.arr[1] = binary.BigEndian.Uint64(in[14:22])\n\tz.arr[0] = binary.BigEndian.Uint64(in[22:30])\n\treturn z\n}\n\n// SetBytes31 is identical to SetBytes(in[:31]), but panics is input is too short\nfunc (z *Uint) SetBytes31(in []byte) *Uint {\n\t_ = in[30] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = bigEndianUint56(in[0:7])\n\tz.arr[2] = binary.BigEndian.Uint64(in[7:15])\n\tz.arr[1] = binary.BigEndian.Uint64(in[15:23])\n\tz.arr[0] = binary.BigEndian.Uint64(in[23:31])\n\treturn z\n}\n\n// SetBytes32 sets z to the value of the big-endian 256-bit unsigned integer in.\nfunc (z *Uint) SetBytes32(in []byte) *Uint {\n\t_ = in[31] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = binary.BigEndian.Uint64(in[0:8])\n\tz.arr[2] = binary.BigEndian.Uint64(in[8:16])\n\tz.arr[1] = binary.BigEndian.Uint64(in[16:24])\n\tz.arr[0] = binary.BigEndian.Uint64(in[24:32])\n\treturn z\n}\n\n// Utility methods that are \"missing\" among the bigEndian.UintXX methods.\n\n// bigEndianUint40 returns the uint64 value represented by the 5 bytes in big-endian order.\nfunc bigEndianUint40(b []byte) uint64 {\n\t_ = b[4] // bounds check hint to compiler; see golang.org/issue/14808\n\treturn uint64(b[4]) | uint64(b[3])\u003c\u003c8 | uint64(b[2])\u003c\u003c16 | uint64(b[1])\u003c\u003c24 |\n\t\tuint64(b[0])\u003c\u003c32\n}\n\n// bigEndianUint56 returns the uint64 value represented by the 7 bytes in big-endian order.\nfunc bigEndianUint56(b []byte) uint64 {\n\t_ = b[6] // bounds check hint to compiler; see golang.org/issue/14808\n\treturn uint64(b[6]) | uint64(b[5])\u003c\u003c8 | uint64(b[4])\u003c\u003c16 | uint64(b[3])\u003c\u003c24 |\n\t\tuint64(b[2])\u003c\u003c32 | uint64(b[1])\u003c\u003c40 | uint64(b[0])\u003c\u003c48\n}\n\n// bigEndianUint48 returns the uint64 value represented by the 6 bytes in big-endian order.\nfunc bigEndianUint48(b []byte) uint64 {\n\t_ = b[5] // bounds check hint to compiler; see golang.org/issue/14808\n\treturn uint64(b[5]) | uint64(b[4])\u003c\u003c8 | uint64(b[3])\u003c\u003c16 | uint64(b[2])\u003c\u003c24 |\n\t\tuint64(b[1])\u003c\u003c32 | uint64(b[0])\u003c\u003c40\n}\n"},{"name":"conversion_test.gno","body":"package uint256\n\nimport \"testing\"\n\nfunc TestIsUint64(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant bool\n\t}{\n\t\t{\"0x0\", true},\n\t\t{\"0x1\", true},\n\t\t{\"0x10\", true},\n\t\t{\"0xffffffffffffffff\", true},\n\t\t{\"0x10000000000000000\", false},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromHex(tt.x)\n\t\tgot := x.IsUint64()\n\n\t\tif got != tt.want {\n\t\t\tt.Errorf(\"IsUint64(%s) = %v, want %v\", tt.x, got, tt.want)\n\t\t}\n\t}\n}\n\nfunc TestDec(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tz Uint\n\t\twant string\n\t}{\n\t\t{\n\t\t\tname: \"zero\",\n\t\t\tz: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: \"0\",\n\t\t},\n\t\t{\n\t\t\tname: \"less than 20 digits\",\n\t\t\tz: Uint{arr: [4]uint64{1234567890, 0, 0, 0}},\n\t\t\twant: \"1234567890\",\n\t\t},\n\t\t{\n\t\t\tname: \"max possible value\",\n\t\t\tz: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: twoPow256Sub1,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresult := tt.z.Dec()\n\t\t\tif result != tt.want {\n\t\t\t\tt.Errorf(\"Dec(%v) = %s, want %s\", tt.z, result, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUint_Scan(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput interface{}\n\t\twant *Uint\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"nil\",\n\t\t\tinput: nil,\n\t\t\twant: NewUint(0),\n\t\t},\n\t\t{\n\t\t\tname: \"valid scientific notation\",\n\t\t\tinput: \"1e4\",\n\t\t\twant: NewUint(10000),\n\t\t},\n\t\t{\n\t\t\tname: \"valid decimal string\",\n\t\t\tinput: \"12345\",\n\t\t\twant: NewUint(12345),\n\t\t},\n\t\t{\n\t\t\tname: \"valid byte slice\",\n\t\t\tinput: []byte(\"12345\"),\n\t\t\twant: NewUint(12345),\n\t\t},\n\t\t{\n\t\t\tname: \"invalid string\",\n\t\t\tinput: \"invalid\",\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"out of range\",\n\t\t\tinput: \"115792089237316195423570985008687907853269984665640564039457584007913129639936\", // 2^256\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"unsupported type\",\n\t\t\tinput: 123,\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tz := new(Uint)\n\t\t\terr := z.Scan(tt.input)\n\n\t\t\tif tt.wantErr {\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Errorf(\"Scan() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"Scan() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\t}\n\t\t\t\tif !z.Eq(tt.want) {\n\t\t\t\t\tt.Errorf(\"Scan() = %v, want %v\", z, tt.want)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSetBytes(t *testing.T) {\n\ttests := []struct {\n\t\tinput []byte\n\t\texpected string\n\t}{\n\t\t{[]byte{}, \"0\"},\n\t\t{[]byte{0x01}, \"1\"},\n\t\t{[]byte{0x12, 0x34}, \"4660\"},\n\t\t{[]byte{0x12, 0x34, 0x56}, \"1193046\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78}, \"305419896\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a}, \"78187493530\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc}, \"20015998343868\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde}, \"5124095576030430\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}, \"1311768467463790320\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12}, \"335812727670730321938\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34}, \"85968058283706962416180\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56}, \"22007822920628982378542166\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78}, \"5634002667681019488906794616\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a}, \"1442304682926340989160139421850\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc}, \"369229998829143293224995691993788\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde}, \"94522879700260683065598897150409950\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}, \"24197857203266734864793317670504947440\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12}, \"6194651444036284125387089323649266544658\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34}, \"1585830769673288736099094866854212235432500\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56}, \"405972677036361916441368285914678332270720086\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78}, \"103929005321308650608990281194157653061304342136\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a}, \"26605825362255014555901511985704359183693911586970\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc}, \"6811091292737283726310787068340315951025641366264508\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde}, \"1743639370940744633935561489495120883462564189763714270\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}, \"446371678960830626287503741310750946166416432579510853360\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12}, \"114271149813972640329600957775552242218602606740354778460178\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34}, \"29253414352376995924377845190541374007962267325530823285805620\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56}, \"7488874074208510956640728368778591746038340435335890761166238806\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78}, \"1917151762997378804900026462407319486985815151445988034858557134456\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a}, \"490790851327328974054406774376273788668368678770172936923790626420890\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc}, \"125642457939796217357928134240326089899102381765164271852490400363748028\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde}, \"32164469232587831643629602365523479014170209731882053594237542493119495390\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}, \"8234104123542484900769178205574010627627573691361805720124810878238590820080\"},\n\t\t// over 32 bytes (last 32 bytes are used)\n\t\t{append([]byte{0xff}, []byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}...), \"8234104123542484900769178205574010627627573691361805720124810878238590820080\"},\n\t}\n\n\tfor _, test := range tests {\n\t\tz := new(Uint)\n\t\tz.SetBytes(test.input)\n\t\texpected := MustFromDecimal(test.expected)\n\t\tif z.Cmp(expected) != 0 {\n\t\t\tt.Errorf(\"SetBytes(%x) = %s, expected %s\", test.input, z.ToString(), test.expected)\n\t\t}\n\t}\n}\n"},{"name":"error.gno","body":"package uint256\n\nimport (\n\t\"errors\"\n)\n\nvar (\n\tErrEmptyString = errors.New(\"empty hex string\")\n\tErrSyntax = errors.New(\"invalid hex string\")\n\tErrRange = errors.New(\"number out of range\")\n\tErrMissingPrefix = errors.New(\"hex string without 0x prefix\")\n\tErrEmptyNumber = errors.New(\"hex string \\\"0x\\\"\")\n\tErrLeadingZero = errors.New(\"hex number with leading zero digits\")\n\tErrBig256Range = errors.New(\"hex number \u003e 256 bits\")\n\tErrBadBufferLength = errors.New(\"bad ssz buffer length\")\n\tErrBadEncodedLength = errors.New(\"bad ssz encoded length\")\n\tErrInvalidBase = errors.New(\"invalid base\")\n\tErrInvalidBitSize = errors.New(\"invalid bit size\")\n)\n\ntype u256Error struct {\n\tfn string // function name\n\tinput string\n\terr error\n}\n\nfunc (e *u256Error) Error() string {\n\treturn e.fn + \": \" + e.input + \": \" + e.err.Error()\n}\n\nfunc (e *u256Error) Unwrap() error {\n\treturn e.err\n}\n\nfunc errEmptyString(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrEmptyString}\n}\n\nfunc errSyntax(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrSyntax}\n}\n\nfunc errMissingPrefix(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrMissingPrefix}\n}\n\nfunc errEmptyNumber(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrEmptyNumber}\n}\n\nfunc errLeadingZero(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrLeadingZero}\n}\n\nfunc errRange(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrRange}\n}\n\nfunc errBig256Range(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrBig256Range}\n}\n\nfunc errBadBufferLength(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrBadBufferLength}\n}\n\nfunc errInvalidBase(fn string, base int) error {\n\treturn \u0026u256Error{fn: fn, input: string(base), err: ErrInvalidBase}\n}\n\nfunc errInvalidBitSize(fn string, bitSize int) error {\n\treturn \u0026u256Error{fn: fn, input: string(bitSize), err: ErrInvalidBitSize}\n}\n"},{"name":"mod.gno","body":"package uint256\n\nimport (\n\t\"math/bits\"\n)\n\n// Some utility functions\n\n// Reciprocal computes a 320-bit value representing 1/m\n//\n// Notes:\n// - specialized for m.arr[3] != 0, hence limited to 2^192 \u003c= m \u003c 2^256\n// - returns zero if m.arr[3] == 0\n// - starts with a 32-bit division, refines with newton-raphson iterations\nfunc Reciprocal(m *Uint) (mu [5]uint64) {\n\tif m.arr[3] == 0 {\n\t\treturn mu\n\t}\n\n\ts := bits.LeadingZeros64(m.arr[3]) // Replace with leadingZeros(m) for general case\n\tp := 255 - s // floor(log_2(m)), m\u003e0\n\n\t// 0 or a power of 2?\n\n\t// Check if at least one bit is set in m.arr[2], m.arr[1] or m.arr[0],\n\t// or at least two bits in m.arr[3]\n\n\tif m.arr[0]|m.arr[1]|m.arr[2]|(m.arr[3]\u0026(m.arr[3]-1)) == 0 {\n\n\t\tmu[4] = ^uint64(0) \u003e\u003e uint(p\u002663)\n\t\tmu[3] = ^uint64(0)\n\t\tmu[2] = ^uint64(0)\n\t\tmu[1] = ^uint64(0)\n\t\tmu[0] = ^uint64(0)\n\n\t\treturn mu\n\t}\n\n\t// Maximise division precision by left-aligning divisor\n\n\tvar (\n\t\ty Uint // left-aligned copy of m\n\t\tr0 uint32 // estimate of 2^31/y\n\t)\n\n\ty.Lsh(m, uint(s)) // 1/2 \u003c y \u003c 1\n\n\t// Extract most significant 32 bits\n\n\tyh := uint32(y.arr[3] \u003e\u003e 32)\n\n\tif yh == 0x80000000 { // Avoid overflow in division\n\t\tr0 = 0xffffffff\n\t} else {\n\t\tr0, _ = bits.Div32(0x80000000, 0, yh)\n\t}\n\n\t// First iteration: 32 -\u003e 64\n\n\tt1 := uint64(r0) // 2^31/y\n\tt1 *= t1 // 2^62/y^2\n\tt1, _ = bits.Mul64(t1, y.arr[3]) // 2^62/y^2 * 2^64/y / 2^64 = 2^62/y\n\n\tr1 := uint64(r0) \u003c\u003c 32 // 2^63/y\n\tr1 -= t1 // 2^63/y - 2^62/y = 2^62/y\n\tr1 *= 2 // 2^63/y\n\n\tif (r1 | (y.arr[3] \u003c\u003c 1)) == 0 {\n\t\tr1 = ^uint64(0)\n\t}\n\n\t// Second iteration: 64 -\u003e 128\n\n\t// square: 2^126/y^2\n\ta2h, a2l := bits.Mul64(r1, r1)\n\n\t// multiply by y: e2h:e2l:b2h = 2^126/y^2 * 2^128/y / 2^128 = 2^126/y\n\tb2h, _ := bits.Mul64(a2l, y.arr[2])\n\tc2h, c2l := bits.Mul64(a2l, y.arr[3])\n\td2h, d2l := bits.Mul64(a2h, y.arr[2])\n\te2h, e2l := bits.Mul64(a2h, y.arr[3])\n\n\tb2h, c := bits.Add64(b2h, c2l, 0)\n\te2l, c = bits.Add64(e2l, c2h, c)\n\te2h, _ = bits.Add64(e2h, 0, c)\n\n\t_, c = bits.Add64(b2h, d2l, 0)\n\te2l, c = bits.Add64(e2l, d2h, c)\n\te2h, _ = bits.Add64(e2h, 0, c)\n\n\t// subtract: t2h:t2l = 2^127/y - 2^126/y = 2^126/y\n\tt2l, b := bits.Sub64(0, e2l, 0)\n\tt2h, _ := bits.Sub64(r1, e2h, b)\n\n\t// double: r2h:r2l = 2^127/y\n\tr2l, c := bits.Add64(t2l, t2l, 0)\n\tr2h, _ := bits.Add64(t2h, t2h, c)\n\n\tif (r2h | r2l | (y.arr[3] \u003c\u003c 1)) == 0 {\n\t\tr2h = ^uint64(0)\n\t\tr2l = ^uint64(0)\n\t}\n\n\t// Third iteration: 128 -\u003e 192\n\n\t// square r2 (keep 256 bits): 2^190/y^2\n\ta3h, a3l := bits.Mul64(r2l, r2l)\n\tb3h, b3l := bits.Mul64(r2l, r2h)\n\tc3h, c3l := bits.Mul64(r2h, r2h)\n\n\ta3h, c = bits.Add64(a3h, b3l, 0)\n\tc3l, c = bits.Add64(c3l, b3h, c)\n\tc3h, _ = bits.Add64(c3h, 0, c)\n\n\ta3h, c = bits.Add64(a3h, b3l, 0)\n\tc3l, c = bits.Add64(c3l, b3h, c)\n\tc3h, _ = bits.Add64(c3h, 0, c)\n\n\t// multiply by y: q = 2^190/y^2 * 2^192/y / 2^192 = 2^190/y\n\n\tx0 := a3l\n\tx1 := a3h\n\tx2 := c3l\n\tx3 := c3h\n\n\tvar q0, q1, q2, q3, q4, t0 uint64\n\n\tq0, _ = bits.Mul64(x2, y.arr[0])\n\tq1, t0 = bits.Mul64(x3, y.arr[0])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, _ = bits.Add64(q1, 0, c)\n\n\tt1, _ = bits.Mul64(x1, y.arr[1])\n\tq0, c = bits.Add64(q0, t1, 0)\n\tq2, t0 = bits.Mul64(x3, y.arr[1])\n\tq1, c = bits.Add64(q1, t0, c)\n\tq2, _ = bits.Add64(q2, 0, c)\n\n\tt1, t0 = bits.Mul64(x2, y.arr[1])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tq2, _ = bits.Add64(q2, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, y.arr[2])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tq3, t0 = bits.Mul64(x3, y.arr[2])\n\tq2, c = bits.Add64(q2, t0, c)\n\tq3, _ = bits.Add64(q3, 0, c)\n\n\tt1, _ = bits.Mul64(x0, y.arr[2])\n\tq0, c = bits.Add64(q0, t1, 0)\n\tt1, t0 = bits.Mul64(x2, y.arr[2])\n\tq1, c = bits.Add64(q1, t0, c)\n\tq2, c = bits.Add64(q2, t1, c)\n\tq3, _ = bits.Add64(q3, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, y.arr[3])\n\tq1, c = bits.Add64(q1, t0, 0)\n\tq2, c = bits.Add64(q2, t1, c)\n\tq4, t0 = bits.Mul64(x3, y.arr[3])\n\tq3, c = bits.Add64(q3, t0, c)\n\tq4, _ = bits.Add64(q4, 0, c)\n\n\tt1, t0 = bits.Mul64(x0, y.arr[3])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tt1, t0 = bits.Mul64(x2, y.arr[3])\n\tq2, c = bits.Add64(q2, t0, c)\n\tq3, c = bits.Add64(q3, t1, c)\n\tq4, _ = bits.Add64(q4, 0, c)\n\n\t// subtract: t3 = 2^191/y - 2^190/y = 2^190/y\n\t_, b = bits.Sub64(0, q0, 0)\n\t_, b = bits.Sub64(0, q1, b)\n\tt3l, b := bits.Sub64(0, q2, b)\n\tt3m, b := bits.Sub64(r2l, q3, b)\n\tt3h, _ := bits.Sub64(r2h, q4, b)\n\n\t// double: r3 = 2^191/y\n\tr3l, c := bits.Add64(t3l, t3l, 0)\n\tr3m, c := bits.Add64(t3m, t3m, c)\n\tr3h, _ := bits.Add64(t3h, t3h, c)\n\n\t// Fourth iteration: 192 -\u003e 320\n\n\t// square r3\n\n\ta4h, a4l := bits.Mul64(r3l, r3l)\n\tb4h, b4l := bits.Mul64(r3l, r3m)\n\tc4h, c4l := bits.Mul64(r3l, r3h)\n\td4h, d4l := bits.Mul64(r3m, r3m)\n\te4h, e4l := bits.Mul64(r3m, r3h)\n\tf4h, f4l := bits.Mul64(r3h, r3h)\n\n\tb4h, c = bits.Add64(b4h, c4l, 0)\n\te4l, c = bits.Add64(e4l, c4h, c)\n\te4h, _ = bits.Add64(e4h, 0, c)\n\n\ta4h, c = bits.Add64(a4h, b4l, 0)\n\td4l, c = bits.Add64(d4l, b4h, c)\n\td4h, c = bits.Add64(d4h, e4l, c)\n\tf4l, c = bits.Add64(f4l, e4h, c)\n\tf4h, _ = bits.Add64(f4h, 0, c)\n\n\ta4h, c = bits.Add64(a4h, b4l, 0)\n\td4l, c = bits.Add64(d4l, b4h, c)\n\td4h, c = bits.Add64(d4h, e4l, c)\n\tf4l, c = bits.Add64(f4l, e4h, c)\n\tf4h, _ = bits.Add64(f4h, 0, c)\n\n\t// multiply by y\n\n\tx1, x0 = bits.Mul64(d4h, y.arr[0])\n\tx3, x2 = bits.Mul64(f4h, y.arr[0])\n\tt1, t0 = bits.Mul64(f4l, y.arr[0])\n\tx1, c = bits.Add64(x1, t0, 0)\n\tx2, c = bits.Add64(x2, t1, c)\n\tx3, _ = bits.Add64(x3, 0, c)\n\n\tt1, t0 = bits.Mul64(d4h, y.arr[1])\n\tx1, c = bits.Add64(x1, t0, 0)\n\tx2, c = bits.Add64(x2, t1, c)\n\tx4, t0 := bits.Mul64(f4h, y.arr[1])\n\tx3, c = bits.Add64(x3, t0, c)\n\tx4, _ = bits.Add64(x4, 0, c)\n\tt1, t0 = bits.Mul64(d4l, y.arr[1])\n\tx0, c = bits.Add64(x0, t0, 0)\n\tx1, c = bits.Add64(x1, t1, c)\n\tt1, t0 = bits.Mul64(f4l, y.arr[1])\n\tx2, c = bits.Add64(x2, t0, c)\n\tx3, c = bits.Add64(x3, t1, c)\n\tx4, _ = bits.Add64(x4, 0, c)\n\n\tt1, t0 = bits.Mul64(a4h, y.arr[2])\n\tx0, c = bits.Add64(x0, t0, 0)\n\tx1, c = bits.Add64(x1, t1, c)\n\tt1, t0 = bits.Mul64(d4h, y.arr[2])\n\tx2, c = bits.Add64(x2, t0, c)\n\tx3, c = bits.Add64(x3, t1, c)\n\tx5, t0 := bits.Mul64(f4h, y.arr[2])\n\tx4, c = bits.Add64(x4, t0, c)\n\tx5, _ = bits.Add64(x5, 0, c)\n\tt1, t0 = bits.Mul64(d4l, y.arr[2])\n\tx1, c = bits.Add64(x1, t0, 0)\n\tx2, c = bits.Add64(x2, t1, c)\n\tt1, t0 = bits.Mul64(f4l, y.arr[2])\n\tx3, c = bits.Add64(x3, t0, c)\n\tx4, c = bits.Add64(x4, t1, c)\n\tx5, _ = bits.Add64(x5, 0, c)\n\n\tt1, t0 = bits.Mul64(a4h, y.arr[3])\n\tx1, c = bits.Add64(x1, t0, 0)\n\tx2, c = bits.Add64(x2, t1, c)\n\tt1, t0 = bits.Mul64(d4h, y.arr[3])\n\tx3, c = bits.Add64(x3, t0, c)\n\tx4, c = bits.Add64(x4, t1, c)\n\tx6, t0 := bits.Mul64(f4h, y.arr[3])\n\tx5, c = bits.Add64(x5, t0, c)\n\tx6, _ = bits.Add64(x6, 0, c)\n\tt1, t0 = bits.Mul64(a4l, y.arr[3])\n\tx0, c = bits.Add64(x0, t0, 0)\n\tx1, c = bits.Add64(x1, t1, c)\n\tt1, t0 = bits.Mul64(d4l, y.arr[3])\n\tx2, c = bits.Add64(x2, t0, c)\n\tx3, c = bits.Add64(x3, t1, c)\n\tt1, t0 = bits.Mul64(f4l, y.arr[3])\n\tx4, c = bits.Add64(x4, t0, c)\n\tx5, c = bits.Add64(x5, t1, c)\n\tx6, _ = bits.Add64(x6, 0, c)\n\n\t// subtract\n\t_, b = bits.Sub64(0, x0, 0)\n\t_, b = bits.Sub64(0, x1, b)\n\tr4l, b := bits.Sub64(0, x2, b)\n\tr4k, b := bits.Sub64(0, x3, b)\n\tr4j, b := bits.Sub64(r3l, x4, b)\n\tr4i, b := bits.Sub64(r3m, x5, b)\n\tr4h, _ := bits.Sub64(r3h, x6, b)\n\n\t// Multiply candidate for 1/4y by y, with full precision\n\n\tx0 = r4l\n\tx1 = r4k\n\tx2 = r4j\n\tx3 = r4i\n\tx4 = r4h\n\n\tq1, q0 = bits.Mul64(x0, y.arr[0])\n\tq3, q2 = bits.Mul64(x2, y.arr[0])\n\tq5, q4 := bits.Mul64(x4, y.arr[0])\n\n\tt1, t0 = bits.Mul64(x1, y.arr[0])\n\tq1, c = bits.Add64(q1, t0, 0)\n\tq2, c = bits.Add64(q2, t1, c)\n\tt1, t0 = bits.Mul64(x3, y.arr[0])\n\tq3, c = bits.Add64(q3, t0, c)\n\tq4, c = bits.Add64(q4, t1, c)\n\tq5, _ = bits.Add64(q5, 0, c)\n\n\tt1, t0 = bits.Mul64(x0, y.arr[1])\n\tq1, c = bits.Add64(q1, t0, 0)\n\tq2, c = bits.Add64(q2, t1, c)\n\tt1, t0 = bits.Mul64(x2, y.arr[1])\n\tq3, c = bits.Add64(q3, t0, c)\n\tq4, c = bits.Add64(q4, t1, c)\n\tq6, t0 := bits.Mul64(x4, y.arr[1])\n\tq5, c = bits.Add64(q5, t0, c)\n\tq6, _ = bits.Add64(q6, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, y.arr[1])\n\tq2, c = bits.Add64(q2, t0, 0)\n\tq3, c = bits.Add64(q3, t1, c)\n\tt1, t0 = bits.Mul64(x3, y.arr[1])\n\tq4, c = bits.Add64(q4, t0, c)\n\tq5, c = bits.Add64(q5, t1, c)\n\tq6, _ = bits.Add64(q6, 0, c)\n\n\tt1, t0 = bits.Mul64(x0, y.arr[2])\n\tq2, c = bits.Add64(q2, t0, 0)\n\tq3, c = bits.Add64(q3, t1, c)\n\tt1, t0 = bits.Mul64(x2, y.arr[2])\n\tq4, c = bits.Add64(q4, t0, c)\n\tq5, c = bits.Add64(q5, t1, c)\n\tq7, t0 := bits.Mul64(x4, y.arr[2])\n\tq6, c = bits.Add64(q6, t0, c)\n\tq7, _ = bits.Add64(q7, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, y.arr[2])\n\tq3, c = bits.Add64(q3, t0, 0)\n\tq4, c = bits.Add64(q4, t1, c)\n\tt1, t0 = bits.Mul64(x3, y.arr[2])\n\tq5, c = bits.Add64(q5, t0, c)\n\tq6, c = bits.Add64(q6, t1, c)\n\tq7, _ = bits.Add64(q7, 0, c)\n\n\tt1, t0 = bits.Mul64(x0, y.arr[3])\n\tq3, c = bits.Add64(q3, t0, 0)\n\tq4, c = bits.Add64(q4, t1, c)\n\tt1, t0 = bits.Mul64(x2, y.arr[3])\n\tq5, c = bits.Add64(q5, t0, c)\n\tq6, c = bits.Add64(q6, t1, c)\n\tq8, t0 := bits.Mul64(x4, y.arr[3])\n\tq7, c = bits.Add64(q7, t0, c)\n\tq8, _ = bits.Add64(q8, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, y.arr[3])\n\tq4, c = bits.Add64(q4, t0, 0)\n\tq5, c = bits.Add64(q5, t1, c)\n\tt1, t0 = bits.Mul64(x3, y.arr[3])\n\tq6, c = bits.Add64(q6, t0, c)\n\tq7, c = bits.Add64(q7, t1, c)\n\tq8, _ = bits.Add64(q8, 0, c)\n\n\t// Final adjustment\n\n\t// subtract q from 1/4\n\t_, b = bits.Sub64(0, q0, 0)\n\t_, b = bits.Sub64(0, q1, b)\n\t_, b = bits.Sub64(0, q2, b)\n\t_, b = bits.Sub64(0, q3, b)\n\t_, b = bits.Sub64(0, q4, b)\n\t_, b = bits.Sub64(0, q5, b)\n\t_, b = bits.Sub64(0, q6, b)\n\t_, b = bits.Sub64(0, q7, b)\n\t_, b = bits.Sub64(uint64(1)\u003c\u003c62, q8, b)\n\n\t// decrement the result\n\tx0, t := bits.Sub64(r4l, 1, 0)\n\tx1, t = bits.Sub64(r4k, 0, t)\n\tx2, t = bits.Sub64(r4j, 0, t)\n\tx3, t = bits.Sub64(r4i, 0, t)\n\tx4, _ = bits.Sub64(r4h, 0, t)\n\n\t// commit the decrement if the subtraction underflowed (reciprocal was too large)\n\tif b != 0 {\n\t\tr4h, r4i, r4j, r4k, r4l = x4, x3, x2, x1, x0\n\t}\n\n\t// Shift to correct bit alignment, truncating excess bits\n\n\tp = (p \u0026 63) - 1\n\n\tx0, c = bits.Add64(r4l, r4l, 0)\n\tx1, c = bits.Add64(r4k, r4k, c)\n\tx2, c = bits.Add64(r4j, r4j, c)\n\tx3, c = bits.Add64(r4i, r4i, c)\n\tx4, _ = bits.Add64(r4h, r4h, c)\n\n\tif p \u003c 0 {\n\t\tr4h, r4i, r4j, r4k, r4l = x4, x3, x2, x1, x0\n\t\tp = 0 // avoid negative shift below\n\t}\n\n\t{\n\t\tr := uint(p) // right shift\n\t\tl := uint(64 - r) // left shift\n\n\t\tx0 = (r4l \u003e\u003e r) | (r4k \u003c\u003c l)\n\t\tx1 = (r4k \u003e\u003e r) | (r4j \u003c\u003c l)\n\t\tx2 = (r4j \u003e\u003e r) | (r4i \u003c\u003c l)\n\t\tx3 = (r4i \u003e\u003e r) | (r4h \u003c\u003c l)\n\t\tx4 = (r4h \u003e\u003e r)\n\t}\n\n\tif p \u003e 0 {\n\t\tr4h, r4i, r4j, r4k, r4l = x4, x3, x2, x1, x0\n\t}\n\n\tmu[0] = r4l\n\tmu[1] = r4k\n\tmu[2] = r4j\n\tmu[3] = r4i\n\tmu[4] = r4h\n\n\treturn mu\n}\n\n// reduce4 computes the least non-negative residue of x modulo m\n//\n// requires a four-word modulus (m.arr[3] \u003e 1) and its inverse (mu)\nfunc reduce4(x [8]uint64, m *Uint, mu [5]uint64) (z Uint) {\n\t// NB: Most variable names in the comments match the pseudocode for\n\t// \tBarrett reduction in the Handbook of Applied Cryptography.\n\n\t// q1 = x/2^192\n\n\tx0 := x[3]\n\tx1 := x[4]\n\tx2 := x[5]\n\tx3 := x[6]\n\tx4 := x[7]\n\n\t// q2 = q1 * mu; q3 = q2 / 2^320\n\n\tvar q0, q1, q2, q3, q4, q5, t0, t1, c uint64\n\n\tq0, _ = bits.Mul64(x3, mu[0])\n\tq1, t0 = bits.Mul64(x4, mu[0])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, _ = bits.Add64(q1, 0, c)\n\n\tt1, _ = bits.Mul64(x2, mu[1])\n\tq0, c = bits.Add64(q0, t1, 0)\n\tq2, t0 = bits.Mul64(x4, mu[1])\n\tq1, c = bits.Add64(q1, t0, c)\n\tq2, _ = bits.Add64(q2, 0, c)\n\n\tt1, t0 = bits.Mul64(x3, mu[1])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tq2, _ = bits.Add64(q2, 0, c)\n\n\tt1, t0 = bits.Mul64(x2, mu[2])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tq3, t0 = bits.Mul64(x4, mu[2])\n\tq2, c = bits.Add64(q2, t0, c)\n\tq3, _ = bits.Add64(q3, 0, c)\n\n\tt1, _ = bits.Mul64(x1, mu[2])\n\tq0, c = bits.Add64(q0, t1, 0)\n\tt1, t0 = bits.Mul64(x3, mu[2])\n\tq1, c = bits.Add64(q1, t0, c)\n\tq2, c = bits.Add64(q2, t1, c)\n\tq3, _ = bits.Add64(q3, 0, c)\n\n\tt1, _ = bits.Mul64(x0, mu[3])\n\tq0, c = bits.Add64(q0, t1, 0)\n\tt1, t0 = bits.Mul64(x2, mu[3])\n\tq1, c = bits.Add64(q1, t0, c)\n\tq2, c = bits.Add64(q2, t1, c)\n\tq4, t0 = bits.Mul64(x4, mu[3])\n\tq3, c = bits.Add64(q3, t0, c)\n\tq4, _ = bits.Add64(q4, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, mu[3])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tt1, t0 = bits.Mul64(x3, mu[3])\n\tq2, c = bits.Add64(q2, t0, c)\n\tq3, c = bits.Add64(q3, t1, c)\n\tq4, _ = bits.Add64(q4, 0, c)\n\n\tt1, t0 = bits.Mul64(x0, mu[4])\n\t_, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tt1, t0 = bits.Mul64(x2, mu[4])\n\tq2, c = bits.Add64(q2, t0, c)\n\tq3, c = bits.Add64(q3, t1, c)\n\tq5, t0 = bits.Mul64(x4, mu[4])\n\tq4, c = bits.Add64(q4, t0, c)\n\tq5, _ = bits.Add64(q5, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, mu[4])\n\tq1, c = bits.Add64(q1, t0, 0)\n\tq2, c = bits.Add64(q2, t1, c)\n\tt1, t0 = bits.Mul64(x3, mu[4])\n\tq3, c = bits.Add64(q3, t0, c)\n\tq4, c = bits.Add64(q4, t1, c)\n\tq5, _ = bits.Add64(q5, 0, c)\n\n\t// Drop the fractional part of q3\n\n\tq0 = q1\n\tq1 = q2\n\tq2 = q3\n\tq3 = q4\n\tq4 = q5\n\n\t// r1 = x mod 2^320\n\n\tx0 = x[0]\n\tx1 = x[1]\n\tx2 = x[2]\n\tx3 = x[3]\n\tx4 = x[4]\n\n\t// r2 = q3 * m mod 2^320\n\n\tvar r0, r1, r2, r3, r4 uint64\n\n\tr4, r3 = bits.Mul64(q0, m.arr[3])\n\t_, t0 = bits.Mul64(q1, m.arr[3])\n\tr4, _ = bits.Add64(r4, t0, 0)\n\n\tt1, r2 = bits.Mul64(q0, m.arr[2])\n\tr3, c = bits.Add64(r3, t1, 0)\n\t_, t0 = bits.Mul64(q2, m.arr[2])\n\tr4, _ = bits.Add64(r4, t0, c)\n\n\tt1, t0 = bits.Mul64(q1, m.arr[2])\n\tr3, c = bits.Add64(r3, t0, 0)\n\tr4, _ = bits.Add64(r4, t1, c)\n\n\tt1, r1 = bits.Mul64(q0, m.arr[1])\n\tr2, c = bits.Add64(r2, t1, 0)\n\tt1, t0 = bits.Mul64(q2, m.arr[1])\n\tr3, c = bits.Add64(r3, t0, c)\n\tr4, _ = bits.Add64(r4, t1, c)\n\n\tt1, t0 = bits.Mul64(q1, m.arr[1])\n\tr2, c = bits.Add64(r2, t0, 0)\n\tr3, c = bits.Add64(r3, t1, c)\n\t_, t0 = bits.Mul64(q3, m.arr[1])\n\tr4, _ = bits.Add64(r4, t0, c)\n\n\tt1, r0 = bits.Mul64(q0, m.arr[0])\n\tr1, c = bits.Add64(r1, t1, 0)\n\tt1, t0 = bits.Mul64(q2, m.arr[0])\n\tr2, c = bits.Add64(r2, t0, c)\n\tr3, c = bits.Add64(r3, t1, c)\n\t_, t0 = bits.Mul64(q4, m.arr[0])\n\tr4, _ = bits.Add64(r4, t0, c)\n\n\tt1, t0 = bits.Mul64(q1, m.arr[0])\n\tr1, c = bits.Add64(r1, t0, 0)\n\tr2, c = bits.Add64(r2, t1, c)\n\tt1, t0 = bits.Mul64(q3, m.arr[0])\n\tr3, c = bits.Add64(r3, t0, c)\n\tr4, _ = bits.Add64(r4, t1, c)\n\n\t// r = r1 - r2\n\n\tvar b uint64\n\n\tr0, b = bits.Sub64(x0, r0, 0)\n\tr1, b = bits.Sub64(x1, r1, b)\n\tr2, b = bits.Sub64(x2, r2, b)\n\tr3, b = bits.Sub64(x3, r3, b)\n\tr4, b = bits.Sub64(x4, r4, b)\n\n\t// if r\u003c0 then r+=m\n\n\tif b != 0 {\n\t\tr0, c = bits.Add64(r0, m.arr[0], 0)\n\t\tr1, c = bits.Add64(r1, m.arr[1], c)\n\t\tr2, c = bits.Add64(r2, m.arr[2], c)\n\t\tr3, c = bits.Add64(r3, m.arr[3], c)\n\t\tr4, _ = bits.Add64(r4, 0, c)\n\t}\n\n\t// while (r\u003e=m) r-=m\n\n\tfor {\n\t\t// q = r - m\n\t\tq0, b = bits.Sub64(r0, m.arr[0], 0)\n\t\tq1, b = bits.Sub64(r1, m.arr[1], b)\n\t\tq2, b = bits.Sub64(r2, m.arr[2], b)\n\t\tq3, b = bits.Sub64(r3, m.arr[3], b)\n\t\tq4, b = bits.Sub64(r4, 0, b)\n\n\t\t// if borrow break\n\t\tif b != 0 {\n\t\t\tbreak\n\t\t}\n\n\t\t// r = q\n\t\tr4, r3, r2, r1, r0 = q4, q3, q2, q1, q0\n\t}\n\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = r3, r2, r1, r0\n\n\treturn z\n}\n"},{"name":"uint256.gno","body":"// Ported from https://github.com/holiman/uint256\n// This package provides a 256-bit unsigned integer type, Uint256, and associated functions.\npackage uint256\n\nimport (\n\t\"errors\"\n\t\"math/bits\"\n\t\"strconv\"\n)\n\nconst (\n\tMaxUint64 = 1\u003c\u003c64 - 1\n\tuintSize = 32 \u003c\u003c (^uint(0) \u003e\u003e 63)\n)\n\n// Uint is represented as an array of 4 uint64, in little-endian order,\n// so that Uint[3] is the most significant, and Uint[0] is the least significant\ntype Uint struct {\n\tarr [4]uint64\n}\n\n// NewUint returns a new initialized Uint.\nfunc NewUint(val uint64) *Uint {\n\tz := \u0026Uint{arr: [4]uint64{val, 0, 0, 0}}\n\treturn z\n}\n\n// Zero returns a new Uint initialized to zero.\nfunc Zero() *Uint {\n\treturn NewUint(0)\n}\n\n// One returns a new Uint initialized to one.\nfunc One() *Uint {\n\treturn NewUint(1)\n}\n\n// SetAllOne sets all the bits of z to 1\nfunc (z *Uint) SetAllOne() *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = MaxUint64, MaxUint64, MaxUint64, MaxUint64\n\treturn z\n}\n\n// Set sets z to x and returns z.\nfunc (z *Uint) Set(x *Uint) *Uint {\n\t*z = *x\n\n\treturn z\n}\n\n// SetOne sets z to 1\nfunc (z *Uint) SetOne() *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, 0, 0, 1\n\treturn z\n}\n\nconst twoPow256Sub1 = \"115792089237316195423570985008687907853269984665640564039457584007913129639935\"\n\n// SetFromDecimal sets z from the given string, interpreted as a decimal number.\n// OBS! This method is _not_ strictly identical to the (*big.Uint).SetString(..., 10) method.\n// Notable differences:\n// - This method does not accept underscore input, e.g. \"100_000\",\n// - This method does not accept negative zero as valid, e.g \"-0\",\n// - (this method does not accept any negative input as valid))\nfunc (z *Uint) SetFromDecimal(s string) (err error) {\n\t// Remove max one leading +\n\tif len(s) \u003e 0 \u0026\u0026 s[0] == '+' {\n\t\ts = s[1:]\n\t}\n\t// Remove any number of leading zeroes\n\tif len(s) \u003e 0 \u0026\u0026 s[0] == '0' {\n\t\tvar i int\n\t\tvar c rune\n\t\tfor i, c = range s {\n\t\t\tif c != '0' {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\ts = s[i:]\n\t}\n\tif len(s) \u003c len(twoPow256Sub1) {\n\t\treturn z.fromDecimal(s)\n\t}\n\tif len(s) == len(twoPow256Sub1) {\n\t\tif s \u003e twoPow256Sub1 {\n\t\t\treturn ErrBig256Range\n\t\t}\n\t\treturn z.fromDecimal(s)\n\t}\n\treturn ErrBig256Range\n}\n\n// FromDecimal is a convenience-constructor to create an Uint from a\n// decimal (base 10) string. Numbers larger than 256 bits are not accepted.\nfunc FromDecimal(decimal string) (*Uint, error) {\n\tvar z Uint\n\tif err := z.SetFromDecimal(decimal); err != nil {\n\t\treturn nil, err\n\t}\n\treturn \u0026z, nil\n}\n\n// MustFromDecimal is a convenience-constructor to create an Uint from a\n// decimal (base 10) string.\n// Returns a new Uint and panics if any error occurred.\nfunc MustFromDecimal(decimal string) *Uint {\n\tvar z Uint\n\tif err := z.SetFromDecimal(decimal); err != nil {\n\t\tpanic(err)\n\t}\n\treturn \u0026z\n}\n\n// multipliers holds the values that are needed for fromDecimal\nvar multipliers = [5]*Uint{\n\tnil, // represents first round, no multiplication needed\n\t{[4]uint64{10000000000000000000, 0, 0, 0}}, // 10 ^ 19\n\t{[4]uint64{687399551400673280, 5421010862427522170, 0, 0}}, // 10 ^ 38\n\t{[4]uint64{5332261958806667264, 17004971331911604867, 2938735877055718769, 0}}, // 10 ^ 57\n\t{[4]uint64{0, 8607968719199866880, 532749306367912313, 1593091911132452277}}, // 10 ^ 76\n}\n\n// fromDecimal is a helper function to only ever be called via SetFromDecimal\n// this function takes a string and chunks it up, calling ParseUint on it up to 5 times\n// these chunks are then multiplied by the proper power of 10, then added together.\nfunc (z *Uint) fromDecimal(bs string) error {\n\t// first clear the input\n\tz.Clear()\n\t// the maximum value of uint64 is 18446744073709551615, which is 20 characters\n\t// one less means that a string of 19 9's is always within the uint64 limit\n\tvar (\n\t\tnum uint64\n\t\terr error\n\t\tremaining = len(bs)\n\t)\n\tif remaining == 0 {\n\t\treturn errors.New(\"EOF\")\n\t}\n\t// We proceed in steps of 19 characters (nibbles), from least significant to most significant.\n\t// This means that the first (up to) 19 characters do not need to be multiplied.\n\t// In the second iteration, our slice of 19 characters needs to be multipleied\n\t// by a factor of 10^19. Et cetera.\n\tfor i, mult := range multipliers {\n\t\tif remaining \u003c= 0 {\n\t\t\treturn nil // Done\n\t\t} else if remaining \u003e 19 {\n\t\t\tnum, err = strconv.ParseUint(bs[remaining-19:remaining], 10, 64)\n\t\t} else {\n\t\t\t// Final round\n\t\t\tnum, err = strconv.ParseUint(bs, 10, 64)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t// add that number to our running total\n\t\tif i == 0 {\n\t\t\tz.SetUint64(num)\n\t\t} else {\n\t\t\tbase := NewUint(num)\n\t\t\tz.Add(z, base.Mul(base, mult))\n\t\t}\n\t\t// Chop off another 19 characters\n\t\tif remaining \u003e 19 {\n\t\t\tbs = bs[0 : remaining-19]\n\t\t}\n\t\tremaining -= 19\n\t}\n\treturn nil\n}\n\n// Byte sets z to the value of the byte at position n,\n// with 'z' considered as a big-endian 32-byte integer\n// if 'n' \u003e 32, f is set to 0\n// Example: f = '5', n=31 =\u003e 5\nfunc (z *Uint) Byte(n *Uint) *Uint {\n\t// in z, z.arr[0] is the least significant\n\tif number, overflow := n.Uint64WithOverflow(); !overflow {\n\t\tif number \u003c 32 {\n\t\t\tnumber := z.arr[4-1-number/8]\n\t\t\toffset := (n.arr[0] \u0026 0x7) \u003c\u003c 3 // 8*(n.d % 8)\n\t\t\tz.arr[0] = (number \u0026 (0xff00000000000000 \u003e\u003e offset)) \u003e\u003e (56 - offset)\n\t\t\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\t\t\treturn z\n\t\t}\n\t}\n\n\treturn z.Clear()\n}\n\n// BitLen returns the number of bits required to represent z\nfunc (z *Uint) BitLen() int {\n\tswitch {\n\tcase z.arr[3] != 0:\n\t\treturn 192 + bits.Len64(z.arr[3])\n\tcase z.arr[2] != 0:\n\t\treturn 128 + bits.Len64(z.arr[2])\n\tcase z.arr[1] != 0:\n\t\treturn 64 + bits.Len64(z.arr[1])\n\tdefault:\n\t\treturn bits.Len64(z.arr[0])\n\t}\n}\n\n// ByteLen returns the number of bytes required to represent z\nfunc (z *Uint) ByteLen() int {\n\treturn (z.BitLen() + 7) / 8\n}\n\n// Clear sets z to 0\nfunc (z *Uint) Clear() *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, 0, 0, 0\n\treturn z\n}\n\nconst (\n\t// hextable = \"0123456789abcdef\"\n\tbintable = \"\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\a\\b\\t\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\n\\v\\f\\r\\x0e\\x0f\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\n\\v\\f\\r\\x0e\\x0f\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\"\n\tbadNibble = 0xff\n)\n\n// SetFromHex sets z from the given string, interpreted as a hexadecimal number.\n// OBS! This method is _not_ strictly identical to the (*big.Int).SetString(..., 16) method.\n// Notable differences:\n// - This method _require_ \"0x\" or \"0X\" prefix.\n// - This method does not accept zero-prefixed hex, e.g. \"0x0001\"\n// - This method does not accept underscore input, e.g. \"100_000\",\n// - This method does not accept negative zero as valid, e.g \"-0x0\",\n// - (this method does not accept any negative input as valid)\nfunc (z *Uint) SetFromHex(hex string) error {\n\treturn z.fromHex(hex)\n}\n\n// fromHex is the internal implementation of parsing a hex-string.\nfunc (z *Uint) fromHex(hex string) error {\n\tif err := checkNumberS(hex); err != nil {\n\t\treturn err\n\t}\n\tif len(hex) \u003e 66 {\n\t\treturn ErrBig256Range\n\t}\n\tz.Clear()\n\tend := len(hex)\n\tfor i := 0; i \u003c 4; i++ {\n\t\tstart := end - 16\n\t\tif start \u003c 2 {\n\t\t\tstart = 2\n\t\t}\n\t\tfor ri := start; ri \u003c end; ri++ {\n\t\t\tnib := bintable[hex[ri]]\n\t\t\tif nib == badNibble {\n\t\t\t\treturn ErrSyntax\n\t\t\t}\n\t\t\tz.arr[i] = z.arr[i] \u003c\u003c 4\n\t\t\tz.arr[i] += uint64(nib)\n\t\t}\n\t\tend = start\n\t}\n\treturn nil\n}\n\n// FromHex is a convenience-constructor to create an Uint from\n// a hexadecimal string. The string is required to be '0x'-prefixed\n// Numbers larger than 256 bits are not accepted.\nfunc FromHex(hex string) (*Uint, error) {\n\tvar z Uint\n\tif err := z.fromHex(hex); err != nil {\n\t\treturn nil, err\n\t}\n\treturn \u0026z, nil\n}\n\n// MustFromHex is a convenience-constructor to create an Uint from\n// a hexadecimal string.\n// Returns a new Uint and panics if any error occurred.\nfunc MustFromHex(hex string) *Uint {\n\tvar z Uint\n\tif err := z.fromHex(hex); err != nil {\n\t\tpanic(err)\n\t}\n\treturn \u0026z\n}\n\n// Clone creates a new Uint identical to z\nfunc (z *Uint) Clone() *Uint {\n\tvar x Uint\n\tx.arr[0] = z.arr[0]\n\tx.arr[1] = z.arr[1]\n\tx.arr[2] = z.arr[2]\n\tx.arr[3] = z.arr[3]\n\n\treturn \u0026x\n}\n"},{"name":"uint256_test.gno","body":"package uint256\n\nimport (\n\t\"testing\"\n)\n\nfunc TestSetAllOne(t *testing.T) {\n\tz := Zero()\n\tz.SetAllOne()\n\tif z.ToString() != twoPow256Sub1 {\n\t\tt.Errorf(\"Expected all ones, got %s\", z.ToString())\n\t}\n}\n\nfunc TestByte(t *testing.T) {\n\ttests := []struct {\n\t\tinput string\n\t\tposition uint64\n\t\texpected byte\n\t}{\n\t\t{\"0x1000000000000000000000000000000000000000000000000000000000000000\", 0, 16},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 0, 255},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 31, 255},\n\t}\n\n\tfor i, tt := range tests {\n\t\tz, _ := FromHex(tt.input)\n\t\tn := NewUint(tt.position)\n\t\tresult := z.Byte(n)\n\n\t\tif result.arr[0] != uint64(tt.expected) {\n\t\t\tt.Errorf(\"Test case %d failed. Input: %s, Position: %d, Expected: %d, Got: %d\",\n\t\t\t\ti, tt.input, tt.position, tt.expected, result.arr[0])\n\t\t}\n\n\t\t// check other array elements are 0\n\t\tif result.arr[1] != 0 || result.arr[2] != 0 || result.arr[3] != 0 {\n\t\t\tt.Errorf(\"Test case %d failed. Non-zero values in upper bytes\", i)\n\t\t}\n\t}\n\n\t// overflow\n\tz, _ := FromHex(\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\")\n\tn := NewUint(32)\n\tresult := z.Byte(n)\n\n\tif !result.IsZero() {\n\t\tt.Errorf(\"Expected zero for position \u003e= 32, got %v\", result)\n\t}\n}\n\nfunc TestBitLen(t *testing.T) {\n\ttests := []struct {\n\t\tinput string\n\t\texpected int\n\t}{\n\t\t{\"0x0\", 0},\n\t\t{\"0x1\", 1},\n\t\t{\"0xff\", 8},\n\t\t{\"0x100\", 9},\n\t\t{\"0xffff\", 16},\n\t\t{\"0x10000\", 17},\n\t\t{\"0xffffffffffffffff\", 64},\n\t\t{\"0x10000000000000000\", 65},\n\t\t{\"0xffffffffffffffffffffffffffffffff\", 128},\n\t\t{\"0x100000000000000000000000000000000\", 129},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 256},\n\t}\n\n\tfor i, tt := range tests {\n\t\tz, _ := FromHex(tt.input)\n\t\tresult := z.BitLen()\n\n\t\tif result != tt.expected {\n\t\t\tt.Errorf(\"Test case %d failed. Input: %s, Expected: %d, Got: %d\",\n\t\t\t\ti, tt.input, tt.expected, result)\n\t\t}\n\t}\n}\n\nfunc TestByteLen(t *testing.T) {\n\ttests := []struct {\n\t\tinput string\n\t\texpected int\n\t}{\n\t\t{\"0x0\", 0},\n\t\t{\"0x1\", 1},\n\t\t{\"0xff\", 1},\n\t\t{\"0x100\", 2},\n\t\t{\"0xffff\", 2},\n\t\t{\"0x10000\", 3},\n\t\t{\"0xffffffffffffffff\", 8},\n\t\t{\"0x10000000000000000\", 9},\n\t\t{\"0xffffffffffffffffffffffffffffffff\", 16},\n\t\t{\"0x100000000000000000000000000000000\", 17},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 32},\n\t}\n\n\tfor i, tt := range tests {\n\t\tz, _ := FromHex(tt.input)\n\t\tresult := z.ByteLen()\n\n\t\tif result != tt.expected {\n\t\t\tt.Errorf(\"Test case %d failed. Input: %s, Expected: %d, Got: %d\",\n\t\t\t\ti, tt.input, tt.expected, result)\n\t\t}\n\t}\n}\n\nfunc TestClone(t *testing.T) {\n\ttests := []struct {\n\t\tinput string\n\t\texpected string\n\t}{\n\t\t{\"0x1\", \"1\"},\n\t\t{\"0x100\", \"256\"},\n\t\t{\"0x10000000000000000\", \"18446744073709551616\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tz, _ := FromHex(tt.input)\n\t\tresult := z.Clone()\n\t\tif result.ToString() != tt.expected {\n\t\t\tt.Errorf(\"Test %s failed. Expected %s, got %s\", tt.input, tt.expected, result.ToString())\n\t\t}\n\t}\n}\n"},{"name":"utils.gno","body":"package uint256\n\nfunc checkNumberS(input string) error {\n\tconst fn = \"UnmarshalText\"\n\tl := len(input)\n\tif l == 0 {\n\t\treturn errEmptyString(fn, input)\n\t}\n\tif l \u003c 2 || input[0] != '0' ||\n\t\t(input[1] != 'x' \u0026\u0026 input[1] != 'X') {\n\t\treturn errMissingPrefix(fn, input)\n\t}\n\tif l == 2 {\n\t\treturn errEmptyNumber(fn, input)\n\t}\n\tif len(input) \u003e 3 \u0026\u0026 input[2] == '0' {\n\t\treturn errLeadingZero(fn, input)\n\t}\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_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"int256","path":"gno.land/p/demo/int256","files":[{"name":"LICENSE","body":"MIT License\n\nCopyright (c) 2023 Trịnh Đức Bảo Linh(Kevin)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."},{"name":"README.md","body":"# Fixed size signed 256-bit math library\n\n1. This is a library specialized at replacing the big.Int library for math based on signed 256-bit types.\n2. It uses [uint256](https://github.com/gnolang/gno/tree/master/examples/gno.land/p/demo/uint256) as the underlying type.\n\nported from [mempooler/int256](https://github.com/mempooler/int256)\n"},{"name":"absolute.gno","body":"package int256\n\nimport \"gno.land/p/demo/uint256\"\n\n// Abs returns |z|\nfunc (z *Int) Abs() *uint256.Uint {\n\treturn z.abs.Clone()\n}\n\n// AbsGt returns true if |z| \u003e x, where x is a uint256\nfunc (z *Int) AbsGt(x *uint256.Uint) bool {\n\treturn z.abs.Gt(x)\n}\n\n// AbsLt returns true if |z| \u003c x, where x is a uint256\nfunc (z *Int) AbsLt(x *uint256.Uint) bool {\n\treturn z.abs.Lt(x)\n}\n"},{"name":"absolute_test.gno","body":"package int256\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uint256\"\n)\n\nfunc TestAbs(t *testing.T) {\n\ttests := []struct {\n\t\tx, want string\n\t}{\n\t\t{\"0\", \"0\"},\n\t\t{\"1\", \"1\"},\n\t\t{\"-1\", \"1\"},\n\t\t{\"-2\", \"2\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.Abs()\n\n\t\tif got.ToString() != tc.want {\n\t\t\tt.Errorf(\"Abs(%s) = %v, want %v\", tc.x, got.ToString(), tc.want)\n\t\t}\n\t}\n}\n\nfunc TestAbsGt(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"0\", \"false\"},\n\t\t{\"1\", \"0\", \"true\"},\n\t\t{\"-1\", \"0\", \"true\"},\n\t\t{\"-1\", \"1\", \"false\"},\n\t\t{\"-2\", \"1\", \"true\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"0\", \"true\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"1\", \"true\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"false\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := uint256.FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.AbsGt(y)\n\n\t\tif got != (tc.want == \"true\") {\n\t\t\tt.Errorf(\"AbsGt(%s, %s) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestAbsLt(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"0\", \"false\"},\n\t\t{\"1\", \"0\", \"false\"},\n\t\t{\"-1\", \"0\", \"false\"},\n\t\t{\"-1\", \"1\", \"false\"},\n\t\t{\"-2\", \"1\", \"false\"},\n\t\t{\"-5\", \"10\", \"true\"},\n\t\t{\"31330\", \"31337\", \"true\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"0\", \"false\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"1\", \"false\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"false\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := uint256.FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.AbsLt(y)\n\n\t\tif got != (tc.want == \"true\") {\n\t\t\tt.Errorf(\"AbsLt(%s, %s) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n"},{"name":"arithmetic.gno","body":"package int256\n\nimport \"gno.land/p/demo/uint256\"\n\nfunc (z *Int) Add(x, y *Int) *Int {\n\tz.initiateAbs()\n\n\tif x.neg == y.neg {\n\t\t// If both numbers have the same sign, add their absolute values\n\t\tz.abs.Add(x.abs, y.abs)\n\t\tz.neg = x.neg\n\t} else {\n\t\tswitch x.abs.Cmp(y.abs) {\n\t\tcase 1: // x \u003e y\n\t\t\tz.abs.Sub(x.abs, y.abs)\n\t\t\tz.neg = x.neg\n\t\tcase -1: // x \u003c y\n\t\t\tz.abs.Sub(y.abs, x.abs)\n\t\t\tz.neg = y.neg\n\t\tcase 0: // x == y\n\t\t\tz.abs = uint256.NewUint(0)\n\t\t}\n\t}\n\n\treturn z\n}\n\n// AddUint256 set z to the sum x + y, where y is a uint256, and returns z\nfunc (z *Int) AddUint256(x *Int, y *uint256.Uint) *Int {\n\tif x.neg {\n\t\tif x.abs.Gt(y) {\n\t\t\tz.abs.Sub(x.abs, y)\n\t\t\tz.neg = true\n\t\t} else {\n\t\t\tz.abs.Sub(y, x.abs)\n\t\t\tz.neg = false\n\t\t}\n\t} else {\n\t\tz.abs.Add(x.abs, y)\n\t\tz.neg = false\n\t}\n\treturn z\n}\n\n// Sets z to the sum x + y, where z and x are uint256s and y is an int256.\nfunc AddDelta(z, x *uint256.Uint, y *Int) {\n\tif y.neg {\n\t\tz.Sub(x, y.abs)\n\t} else {\n\t\tz.Add(x, y.abs)\n\t}\n}\n\n// Sets z to the sum x + y, where z and x are uint256s and y is an int256.\nfunc AddDeltaOverflow(z, x *uint256.Uint, y *Int) bool {\n\tvar overflow bool\n\tif y.neg {\n\t\t_, overflow = z.SubOverflow(x, y.abs)\n\t} else {\n\t\t_, overflow = z.AddOverflow(x, y.abs)\n\t}\n\treturn overflow\n}\n\n// Sub sets z to the difference x-y and returns z.\nfunc (z *Int) Sub(x, y *Int) *Int {\n\tz.initiateAbs()\n\n\tif x.neg != y.neg {\n\t\t// If sign are different, add the absolute values\n\t\tz.abs.Add(x.abs, y.abs)\n\t\tz.neg = x.neg\n\t} else {\n\t\tswitch x.abs.Cmp(y.abs) {\n\t\tcase 1: // x \u003e y\n\t\t\tz.abs.Sub(x.abs, y.abs)\n\t\t\tz.neg = x.neg\n\t\tcase -1: // x \u003c y\n\t\t\tz.abs.Sub(y.abs, x.abs)\n\t\t\tz.neg = !x.neg\n\t\tcase 0: // x == y\n\t\t\tz.abs = uint256.NewUint(0)\n\t\t}\n\t}\n\n\t// Ensure zero is always positive\n\tif z.abs.IsZero() {\n\t\tz.neg = false\n\t}\n\treturn z\n}\n\n// SubUint256 set z to the difference x - y, where y is a uint256, and returns z\nfunc (z *Int) SubUint256(x *Int, y *uint256.Uint) *Int {\n\tif x.neg {\n\t\tz.abs.Add(x.abs, y)\n\t\tz.neg = true\n\t} else {\n\t\tif x.abs.Lt(y) {\n\t\t\tz.abs.Sub(y, x.abs)\n\t\t\tz.neg = true\n\t\t} else {\n\t\t\tz.abs.Sub(x.abs, y)\n\t\t\tz.neg = false\n\t\t}\n\t}\n\treturn z\n}\n\n// Mul sets z to the product x*y and returns z.\nfunc (z *Int) Mul(x, y *Int) *Int {\n\tz.initiateAbs()\n\n\tz.abs = z.abs.Mul(x.abs, y.abs)\n\tz.neg = x.neg != y.neg \u0026\u0026 !z.abs.IsZero() // 0 has no sign\n\treturn z\n}\n\n// MulUint256 sets z to the product x*y, where y is a uint256, and returns z\nfunc (z *Int) MulUint256(x *Int, y *uint256.Uint) *Int {\n\tz.abs.Mul(x.abs, y)\n\tif z.abs.IsZero() {\n\t\tz.neg = false\n\t} else {\n\t\tz.neg = x.neg\n\t}\n\treturn z\n}\n\n// Div sets z to the quotient x/y for y != 0 and returns z.\nfunc (z *Int) Div(x, y *Int) *Int {\n\tz.initiateAbs()\n\n\tif y.abs.IsZero() {\n\t\tpanic(\"division by zero\")\n\t}\n\n\tz.abs.Div(x.abs, y.abs)\n\tz.neg = (x.neg != y.neg) \u0026\u0026 !z.abs.IsZero() // 0 has no sign\n\n\treturn z\n}\n\n// DivUint256 sets z to the quotient x/y, where y is a uint256, and returns z\n// If y == 0, z is set to 0\nfunc (z *Int) DivUint256(x *Int, y *uint256.Uint) *Int {\n\tz.abs.Div(x.abs, y)\n\tif z.abs.IsZero() {\n\t\tz.neg = false\n\t} else {\n\t\tz.neg = x.neg\n\t}\n\treturn z\n}\n\n// Quo sets z to the quotient x/y for y != 0 and returns z.\n// If y == 0, a division-by-zero run-time panic occurs.\n// OBS: differs from mempooler int256, we need to panic manually if y == 0\n// Quo implements truncated division (like Go); see QuoRem for more details.\nfunc (z *Int) Quo(x, y *Int) *Int {\n\tif y.IsZero() {\n\t\tpanic(\"division by zero\")\n\t}\n\n\tz.initiateAbs()\n\n\tz.abs = z.abs.Div(x.abs, y.abs)\n\tz.neg = !(z.abs.IsZero()) \u0026\u0026 x.neg != y.neg // 0 has no sign\n\treturn z\n}\n\n// Rem sets z to the remainder x%y for y != 0 and returns z.\n// If y == 0, a division-by-zero run-time panic occurs.\n// OBS: differs from mempooler int256, we need to panic manually if y == 0\n// Rem implements truncated modulus (like Go); see QuoRem for more details.\nfunc (z *Int) Rem(x, y *Int) *Int {\n\tif y.IsZero() {\n\t\tpanic(\"division by zero\")\n\t}\n\n\tz.initiateAbs()\n\n\tz.abs.Mod(x.abs, y.abs)\n\tz.neg = z.abs.Sign() \u003e 0 \u0026\u0026 x.neg // 0 has no sign\n\treturn z\n}\n\n// Mod sets z to the modulus x%y for y != 0 and returns z.\n// If y == 0, z is set to 0 (OBS: differs from the big.Int)\nfunc (z *Int) Mod(x, y *Int) *Int {\n\tif x.neg {\n\t\tz.abs.Div(x.abs, y.abs)\n\t\tz.abs.Add(z.abs, one)\n\t\tz.abs.Mul(z.abs, y.abs)\n\t\tz.abs.Sub(z.abs, x.abs)\n\t\tz.abs.Mod(z.abs, y.abs)\n\t} else {\n\t\tz.abs.Mod(x.abs, y.abs)\n\t}\n\tz.neg = false\n\treturn z\n}\n"},{"name":"arithmetic_test.gno","body":"package int256\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uint256\"\n)\n\nfunc TestAdd(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"1\", \"1\"},\n\t\t{\"1\", \"0\", \"1\"},\n\t\t{\"1\", \"1\", \"2\"},\n\t\t{\"1\", \"2\", \"3\"},\n\t\t// NEGATIVE\n\t\t{\"-1\", \"1\", \"0\"},\n\t\t{\"1\", \"-1\", \"0\"},\n\t\t{\"3\", \"-3\", \"0\"},\n\t\t{\"-1\", \"-1\", \"-2\"},\n\t\t{\"-1\", \"-2\", \"-3\"},\n\t\t{\"-1\", \"3\", \"2\"},\n\t\t{\"3\", \"-1\", \"2\"},\n\t\t// OVERFLOW\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"1\", \"0\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.Add(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Add(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestAddUint256(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"1\", \"1\"},\n\t\t{\"1\", \"0\", \"1\"},\n\t\t{\"1\", \"1\", \"2\"},\n\t\t{\"1\", \"2\", \"3\"},\n\t\t{\"-1\", \"1\", \"0\"},\n\t\t{\"-1\", \"3\", \"2\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639934\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"1\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"115792089237316195423570985008687907853269984665640564039457584007913129639934\", \"-1\"},\n\t\t// OVERFLOW\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"0\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := uint256.FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.AddUint256(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"AddUint256(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestAddDelta(t *testing.T) {\n\ttests := []struct {\n\t\tz, x, y, want string\n\t}{\n\t\t{\"0\", \"0\", \"0\", \"0\"},\n\t\t{\"0\", \"0\", \"1\", \"1\"},\n\t\t{\"0\", \"1\", \"0\", \"1\"},\n\t\t{\"0\", \"1\", \"1\", \"2\"},\n\t\t{\"1\", \"2\", \"3\", \"5\"},\n\t\t{\"5\", \"10\", \"-3\", \"7\"},\n\t\t// underflow\n\t\t{\"1\", \"2\", \"-3\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tz, err := uint256.FromDecimal(tc.z)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tx, err := uint256.FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := uint256.FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tAddDelta(z, x, y)\n\n\t\tif z.Neq(want) {\n\t\t\tt.Errorf(\"AddDelta(%s, %s, %s) = %v, want %v\", tc.z, tc.x, tc.y, z.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestAddDeltaOverflow(t *testing.T) {\n\ttests := []struct {\n\t\tz, x, y string\n\t\twant bool\n\t}{\n\t\t{\"0\", \"0\", \"0\", false},\n\t\t// underflow\n\t\t{\"1\", \"2\", \"-3\", true},\n\t}\n\n\tfor _, tc := range tests {\n\t\tz, err := uint256.FromDecimal(tc.z)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tx, err := uint256.FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tresult := AddDeltaOverflow(z, x, y)\n\t\tif result != tc.want {\n\t\t\tt.Errorf(\"AddDeltaOverflow(%s, %s, %s) = %v, want %v\", tc.z, tc.x, tc.y, result, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestSub(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"1\", \"0\", \"1\"},\n\t\t{\"1\", \"1\", \"0\"},\n\t\t{\"-1\", \"1\", \"-2\"},\n\t\t{\"1\", \"-1\", \"2\"},\n\t\t{\"-1\", \"-1\", \"0\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"0\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"0\", \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\"},\n\t\t{x: \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", y: \"1\", want: \"0\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.Sub(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Sub(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestSubUint256(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"1\", \"-1\"},\n\t\t{\"1\", \"0\", \"1\"},\n\t\t{\"1\", \"1\", \"0\"},\n\t\t{\"1\", \"2\", \"-1\"},\n\t\t{\"-1\", \"1\", \"-2\"},\n\t\t{\"-1\", \"3\", \"-4\"},\n\t\t// underflow\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"1\", \"-0\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"2\", \"-1\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"3\", \"-2\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := uint256.FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.SubUint256(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"SubUint256(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestMul(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"5\", \"3\", \"15\"},\n\t\t{\"-5\", \"3\", \"-15\"},\n\t\t{\"5\", \"-3\", \"-15\"},\n\t\t{\"0\", \"3\", \"0\"},\n\t\t{\"3\", \"0\", \"0\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.Mul(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Mul(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestMulUint256(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"1\", \"0\"},\n\t\t{\"1\", \"0\", \"0\"},\n\t\t{\"1\", \"1\", \"1\"},\n\t\t{\"1\", \"2\", \"2\"},\n\t\t{\"-1\", \"1\", \"-1\"},\n\t\t{\"-1\", \"3\", \"-3\"},\n\t\t{\"3\", \"4\", \"12\"},\n\t\t{\"-3\", \"4\", \"-12\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639934\", \"2\", \"-115792089237316195423570985008687907853269984665640564039457584007913129639932\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639934\", \"2\", \"115792089237316195423570985008687907853269984665640564039457584007913129639932\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := uint256.FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.MulUint256(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"MulUint256(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestDiv(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, expected string\n\t}{\n\t\t{\"1\", \"1\", \"1\"},\n\t\t{\"0\", \"1\", \"0\"},\n\t\t{\"-1\", \"1\", \"-1\"},\n\t\t{\"1\", \"-1\", \"-1\"},\n\t\t{\"-1\", \"-1\", \"1\"},\n\t\t{\"-6\", \"3\", \"-2\"},\n\t\t{\"10\", \"-2\", \"-5\"},\n\t\t{\"-10\", \"3\", \"-3\"},\n\t\t{\"7\", \"3\", \"2\"},\n\t\t{\"-7\", \"3\", \"-2\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"2\", \"57896044618658097711785492504343953926634992332820282019728792003956564819967\"}, // Max uint256 / 2\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.x+\"/\"+tt.y, func(t *testing.T) {\n\t\t\tx := MustFromDecimal(tt.x)\n\t\t\ty := MustFromDecimal(tt.y)\n\t\t\tresult := Zero().Div(x, y)\n\t\t\tif result.ToString() != tt.expected {\n\t\t\t\tt.Errorf(\"Div(%s, %s) = %s, want %s\", tt.x, tt.y, result.ToString(), tt.expected)\n\t\t\t}\n\t\t\tif result.abs.IsZero() \u0026\u0026 result.neg {\n\t\t\t\tt.Errorf(\"Div(%s, %s) resulted in negative zero\", tt.x, tt.y)\n\t\t\t}\n\t\t})\n\t}\n\n\tt.Run(\"Division by zero\", func(t *testing.T) {\n\t\tdefer func() {\n\t\t\tif r := recover(); r == nil {\n\t\t\t\tt.Errorf(\"Div(1, 0) did not panic\")\n\t\t\t}\n\t\t}()\n\t\tx := MustFromDecimal(\"1\")\n\t\ty := MustFromDecimal(\"0\")\n\t\tZero().Div(x, y)\n\t})\n}\n\nfunc TestDivUint256(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"1\", \"0\"},\n\t\t{\"1\", \"0\", \"0\"},\n\t\t{\"1\", \"1\", \"1\"},\n\t\t{\"1\", \"2\", \"0\"},\n\t\t{\"-1\", \"1\", \"-1\"},\n\t\t{\"-1\", \"3\", \"0\"},\n\t\t{\"4\", \"3\", \"1\"},\n\t\t{\"25\", \"5\", \"5\"},\n\t\t{\"25\", \"4\", \"6\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639934\", \"2\", \"-57896044618658097711785492504343953926634992332820282019728792003956564819967\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639934\", \"2\", \"57896044618658097711785492504343953926634992332820282019728792003956564819967\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := uint256.FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.DivUint256(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"DivUint256(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestQuo(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"1\", \"0\"},\n\t\t{\"0\", \"-1\", \"0\"},\n\t\t{\"10\", \"1\", \"10\"},\n\t\t{\"10\", \"-1\", \"-10\"},\n\t\t{\"-10\", \"1\", \"-10\"},\n\t\t{\"-10\", \"-1\", \"10\"},\n\t\t{\"10\", \"-3\", \"-3\"},\n\t\t{\"10\", \"3\", \"3\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.Quo(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Quo(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestRem(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"1\", \"0\"},\n\t\t{\"0\", \"-1\", \"0\"},\n\t\t{\"10\", \"1\", \"0\"},\n\t\t{\"10\", \"-1\", \"0\"},\n\t\t{\"-10\", \"1\", \"0\"},\n\t\t{\"-10\", \"-1\", \"0\"},\n\t\t{\"10\", \"3\", \"1\"},\n\t\t{\"10\", \"-3\", \"1\"},\n\t\t{\"-10\", \"3\", \"-1\"},\n\t\t{\"-10\", \"-3\", \"-1\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.Rem(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Rem(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestMod(t *testing.T) {\n\ttests := []struct {\n\t\tx, y, want string\n\t}{\n\t\t{\"0\", \"1\", \"0\"},\n\t\t{\"0\", \"-1\", \"0\"},\n\t\t{\"10\", \"0\", \"0\"},\n\t\t{\"10\", \"1\", \"0\"},\n\t\t{\"10\", \"-1\", \"0\"},\n\t\t{\"-10\", \"0\", \"0\"},\n\t\t{\"-10\", \"1\", \"0\"},\n\t\t{\"-10\", \"-1\", \"0\"},\n\t\t{\"10\", \"3\", \"1\"},\n\t\t{\"10\", \"-3\", \"1\"},\n\t\t{\"-10\", \"3\", \"2\"},\n\t\t{\"-10\", \"-3\", \"2\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\twant, err := FromDecimal(tc.want)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.Mod(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Mod(%s, %s) = %v, want %v\", tc.x, tc.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n"},{"name":"bitwise.gno","body":"package int256\n\nimport (\n\t\"gno.land/p/demo/uint256\"\n)\n\n// Or sets z = x | y and returns z.\nfunc (z *Int) Or(x, y *Int) *Int {\n\tif x.neg == y.neg {\n\t\tif x.neg {\n\t\t\t// (-x) | (-y) == ^(x-1) | ^(y-1) == ^((x-1) \u0026 (y-1)) == -(((x-1) \u0026 (y-1)) + 1)\n\t\t\tx1 := new(uint256.Uint).Sub(x.abs, one)\n\t\t\ty1 := new(uint256.Uint).Sub(y.abs, one)\n\t\t\tz.abs = z.abs.Add(z.abs.And(x1, y1), one)\n\t\t\tz.neg = true // z cannot be zero if x and y are negative\n\t\t\treturn z\n\t\t}\n\n\t\t// x | y == x | y\n\t\tz.abs = z.abs.Or(x.abs, y.abs)\n\t\tz.neg = false\n\t\treturn z\n\t}\n\n\t// x.neg != y.neg\n\tif x.neg {\n\t\tx, y = y, x // | is symmetric\n\t}\n\n\t// x | (-y) == x | ^(y-1) == ^((y-1) \u0026^ x) == -(^((y-1) \u0026^ x) + 1)\n\ty1 := new(uint256.Uint).Sub(y.abs, one)\n\tz.abs = z.abs.Add(z.abs.AndNot(y1, x.abs), one)\n\tz.neg = true // z cannot be zero if one of x or y is negative\n\n\treturn z\n}\n\n// And sets z = x \u0026 y and returns z.\nfunc (z *Int) And(x, y *Int) *Int {\n\tif x.neg == y.neg {\n\t\tif x.neg {\n\t\t\t// (-x) \u0026 (-y) == ^(x-1) \u0026 ^(y-1) == ^((x-1) | (y-1)) == -(((x-1) | (y-1)) + 1)\n\t\t\tx1 := new(uint256.Uint).Sub(x.abs, one)\n\t\t\ty1 := new(uint256.Uint).Sub(y.abs, one)\n\t\t\tz.abs = z.abs.Add(z.abs.Or(x1, y1), one)\n\t\t\tz.neg = true // z cannot be zero if x and y are negative\n\t\t\treturn z\n\t\t}\n\n\t\t// x \u0026 y == x \u0026 y\n\t\tz.abs = z.abs.And(x.abs, y.abs)\n\t\tz.neg = false\n\t\treturn z\n\t}\n\n\t// x.neg != y.neg\n\t// REF: https://cs.opensource.google/go/go/+/refs/tags/go1.22.1:src/math/big/int.go;l=1192-1202;drc=d57303e65f00b84b528ee682747dbe1fd3316d30\n\tif x.neg {\n\t\tx, y = y, x // \u0026 is symmetric\n\t}\n\n\t// x \u0026 (-y) == x \u0026 ^(y-1) == x \u0026^ (y-1)\n\ty1 := new(uint256.Uint).Sub(y.abs, uint256.One())\n\tz.abs = z.abs.AndNot(x.abs, y1)\n\tz.neg = false\n\treturn z\n}\n\n// Rsh sets z = x \u003e\u003e n and returns z.\n// OBS: Different from original implementation it was using math.Big\nfunc (z *Int) Rsh(x *Int, n uint) *Int {\n\tif !x.neg {\n\t\tz.abs.Rsh(x.abs, n)\n\t\tz.neg = x.neg\n\t\treturn z\n\t}\n\n\t// REF: https://cs.opensource.google/go/go/+/refs/tags/go1.22.1:src/math/big/int.go;l=1118-1126;drc=d57303e65f00b84b528ee682747dbe1fd3316d30\n\tt := NewInt(0).Sub(FromUint256(x.abs), NewInt(1))\n\tt = t.Rsh(t, n)\n\n\t_tmp := t.Add(t, NewInt(1))\n\tz.abs = _tmp.Abs()\n\tz.neg = true\n\n\treturn z\n}\n\n// Lsh sets z = x \u003c\u003c n and returns z.\nfunc (z *Int) Lsh(x *Int, n uint) *Int {\n\tz.abs.Lsh(x.abs, n)\n\tz.neg = x.neg\n\treturn z\n}\n"},{"name":"bitwise_test.gno","body":"package int256\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uint256\"\n)\n\nfunc TestOr(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tx, y, want Int\n\t}{\n\t\t{\n\t\t\tname: \"all zeroes\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t},\n\t\t{\n\t\t\tname: \"all ones\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand all ones\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := New()\n\t\t\tgot.Or(\u0026tc.x, \u0026tc.y)\n\n\t\t\tif got.Neq(\u0026tc.want) {\n\t\t\t\tt.Errorf(\"Or(%v, %v) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestAnd(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tx, y, want Int\n\t}{\n\t\t{\n\t\t\tname: \"all zeroes\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t},\n\t\t{\n\t\t\tname: \"all ones\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 2\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 3\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand zero\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand all ones\",\n\t\t\tx: Int{abs: \u0026uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false},\n\t\t\ty: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}}, neg: false},\n\t\t\twant: Int{abs: \u0026uint256.Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}}, neg: false},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := New()\n\t\t\tgot.And(\u0026tc.x, \u0026tc.y)\n\n\t\t\tif got.Neq(\u0026tc.want) {\n\t\t\t\tt.Errorf(\"And(%v, %v) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestRsh(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\tn uint\n\t\twant string\n\t}{\n\t\t{\"1024\", 0, \"1024\"},\n\t\t{\"1024\", 1, \"512\"},\n\t\t{\"1024\", 2, \"256\"},\n\t\t{\"1024\", 10, \"1\"},\n\t\t{\"1024\", 11, \"0\"},\n\t\t{\"18446744073709551615\", 0, \"18446744073709551615\"},\n\t\t{\"18446744073709551615\", 1, \"9223372036854775807\"},\n\t\t{\"18446744073709551615\", 62, \"3\"},\n\t\t{\"18446744073709551615\", 63, \"1\"},\n\t\t{\"18446744073709551615\", 64, \"0\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", 0, \"115792089237316195423570985008687907853269984665640564039457584007913129639935\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", 1, \"57896044618658097711785492504343953926634992332820282019728792003956564819967\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", 128, \"340282366920938463463374607431768211455\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", 255, \"1\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", 256, \"0\"},\n\t\t{\"-1024\", 0, \"-1024\"},\n\t\t{\"-1024\", 1, \"-512\"},\n\t\t{\"-1024\", 2, \"-256\"},\n\t\t{\"-1024\", 10, \"-1\"},\n\t\t{\"-1024\", 10, \"-1\"},\n\t\t{\"-9223372036854775808\", 0, \"-9223372036854775808\"},\n\t\t{\"-9223372036854775808\", 1, \"-4611686018427387904\"},\n\t\t{\"-9223372036854775808\", 62, \"-2\"},\n\t\t{\"-9223372036854775808\", 63, \"-1\"},\n\t\t{\"-9223372036854775808\", 64, \"-1\"},\n\t\t{\"-57896044618658097711785492504343953926634992332820282019728792003956564819968\", 0, \"-57896044618658097711785492504343953926634992332820282019728792003956564819968\"},\n\t\t{\"-57896044618658097711785492504343953926634992332820282019728792003956564819968\", 1, \"-28948022309329048855892746252171976963317496166410141009864396001978282409984\"},\n\t\t{\"-57896044618658097711785492504343953926634992332820282019728792003956564819968\", 253, \"-4\"},\n\t\t{\"-57896044618658097711785492504343953926634992332820282019728792003956564819968\", 254, \"-2\"},\n\t\t{\"-57896044618658097711785492504343953926634992332820282019728792003956564819968\", 255, \"-1\"},\n\t\t{\"-57896044618658097711785492504343953926634992332820282019728792003956564819968\", 256, \"-1\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.Rsh(x, tc.n)\n\n\t\tif got.ToString() != tc.want {\n\t\t\tt.Errorf(\"Rsh(%s, %d) = %v, want %v\", tc.x, tc.n, got.ToString(), tc.want)\n\t\t}\n\t}\n}\n\nfunc TestLsh(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\tn uint\n\t\twant string\n\t}{\n\t\t{\"1\", 0, \"1\"},\n\t\t{\"1\", 1, \"2\"},\n\t\t{\"1\", 2, \"4\"},\n\t\t{\"2\", 0, \"2\"},\n\t\t{\"2\", 1, \"4\"},\n\t\t{\"2\", 2, \"8\"},\n\t\t{\"-2\", 0, \"-2\"},\n\t\t{\"-4\", 0, \"-4\"},\n\t\t{\"-8\", 0, \"-8\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := New()\n\t\tgot.Lsh(x, tc.n)\n\n\t\tif got.ToString() != tc.want {\n\t\t\tt.Errorf(\"Lsh(%s, %d) = %v, want %v\", tc.x, tc.n, got.ToString(), tc.want)\n\t\t}\n\t}\n}\n"},{"name":"cmp.gno","body":"package int256\n\n// Eq returns true if z == x\nfunc (z *Int) Eq(x *Int) bool {\n\treturn (z.neg == x.neg) \u0026\u0026 z.abs.Eq(x.abs)\n}\n\n// Neq returns true if z != x\nfunc (z *Int) Neq(x *Int) bool {\n\treturn !z.Eq(x)\n}\n\n// Cmp compares x and y and returns:\n//\n//\t-1 if x \u003c y\n//\t 0 if x == y\n//\t+1 if x \u003e y\nfunc (z *Int) Cmp(x *Int) (r int) {\n\t// x cmp y == x cmp y\n\t// x cmp (-y) == x\n\t// (-x) cmp y == y\n\t// (-x) cmp (-y) == -(x cmp y)\n\tswitch {\n\tcase z == x:\n\t\t// nothing to do\n\tcase z.neg == x.neg:\n\t\tr = z.abs.Cmp(x.abs)\n\t\tif z.neg {\n\t\t\tr = -r\n\t\t}\n\tcase z.neg:\n\t\tr = -1\n\tdefault:\n\t\tr = 1\n\t}\n\treturn\n}\n\n// IsZero returns true if z == 0\nfunc (z *Int) IsZero() bool {\n\treturn z.abs.IsZero()\n}\n\n// IsNeg returns true if z \u003c 0\nfunc (z *Int) IsNeg() bool {\n\treturn z.neg\n}\n\n// Lt returns true if z \u003c x\nfunc (z *Int) Lt(x *Int) bool {\n\tif z.neg {\n\t\tif x.neg {\n\t\t\treturn z.abs.Gt(x.abs)\n\t\t} else {\n\t\t\treturn true\n\t\t}\n\t} else {\n\t\tif x.neg {\n\t\t\treturn false\n\t\t} else {\n\t\t\treturn z.abs.Lt(x.abs)\n\t\t}\n\t}\n}\n\n// Gt returns true if z \u003e x\nfunc (z *Int) Gt(x *Int) bool {\n\tif z.neg {\n\t\tif x.neg {\n\t\t\treturn z.abs.Lt(x.abs)\n\t\t} else {\n\t\t\treturn false\n\t\t}\n\t} else {\n\t\tif x.neg {\n\t\t\treturn true\n\t\t} else {\n\t\t\treturn z.abs.Gt(x.abs)\n\t\t}\n\t}\n}\n\n// Clone creates a new Int identical to z\nfunc (z *Int) Clone() *Int {\n\treturn \u0026Int{z.abs.Clone(), z.neg}\n}\n"},{"name":"cmp_test.gno","body":"package int256\n\nimport \"testing\"\n\nfunc TestEq(t *testing.T) {\n\ttests := []struct {\n\t\tx, y string\n\t\twant bool\n\t}{\n\t\t{\"0\", \"0\", true},\n\t\t{\"0\", \"1\", false},\n\t\t{\"1\", \"0\", false},\n\t\t{\"-1\", \"0\", false},\n\t\t{\"0\", \"-1\", false},\n\t\t{\"1\", \"1\", true},\n\t\t{\"-1\", \"-1\", true},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", false},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", true},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.Eq(y)\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Eq(%s, %s) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestNeq(t *testing.T) {\n\ttests := []struct {\n\t\tx, y string\n\t\twant bool\n\t}{\n\t\t{\"0\", \"0\", false},\n\t\t{\"0\", \"1\", true},\n\t\t{\"1\", \"0\", true},\n\t\t{\"-1\", \"0\", true},\n\t\t{\"0\", \"-1\", true},\n\t\t{\"1\", \"1\", false},\n\t\t{\"-1\", \"-1\", false},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", true},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", false},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.Neq(y)\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Neq(%s, %s) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestCmp(t *testing.T) {\n\ttests := []struct {\n\t\tx, y string\n\t\twant int\n\t}{\n\t\t{\"0\", \"0\", 0},\n\t\t{\"0\", \"1\", -1},\n\t\t{\"1\", \"0\", 1},\n\t\t{\"-1\", \"0\", -1},\n\t\t{\"0\", \"-1\", 1},\n\t\t{\"1\", \"1\", 0},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", 1},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.Cmp(y)\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Cmp(%s, %s) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestIsZero(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant bool\n\t}{\n\t\t{\"0\", true},\n\t\t{\"-0\", true},\n\t\t{\"1\", false},\n\t\t{\"-1\", false},\n\t\t{\"10\", false},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.IsZero()\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"IsZero(%s) = %v, want %v\", tc.x, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestIsNeg(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant bool\n\t}{\n\t\t{\"0\", false},\n\t\t{\"-0\", true}, // TODO: should this be false?\n\t\t{\"1\", false},\n\t\t{\"-1\", true},\n\t\t{\"10\", false},\n\t\t{\"-10\", true},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.IsNeg()\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"IsNeg(%s) = %v, want %v\", tc.x, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestLt(t *testing.T) {\n\ttests := []struct {\n\t\tx, y string\n\t\twant bool\n\t}{\n\t\t{\"0\", \"0\", false},\n\t\t{\"0\", \"1\", true},\n\t\t{\"1\", \"0\", false},\n\t\t{\"-1\", \"0\", true},\n\t\t{\"0\", \"-1\", false},\n\t\t{\"1\", \"1\", false},\n\t\t{\"-1\", \"-1\", false},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", false},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.Lt(y)\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Lt(%s, %s) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestGt(t *testing.T) {\n\ttests := []struct {\n\t\tx, y string\n\t\twant bool\n\t}{\n\t\t{\"0\", \"0\", false},\n\t\t{\"0\", \"1\", false},\n\t\t{\"1\", \"0\", true},\n\t\t{\"-1\", \"0\", false},\n\t\t{\"0\", \"-1\", true},\n\t\t{\"1\", \"1\", false},\n\t\t{\"-1\", \"-1\", false},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\", true},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty, err := FromDecimal(tc.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.Gt(y)\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Gt(%s, %s) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestClone(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t}{\n\t\t{\"0\"},\n\t\t{\"-0\"},\n\t\t{\"1\"},\n\t\t{\"-1\"},\n\t\t{\"10\"},\n\t\t{\"-10\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\"},\n\t\t{\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx, err := FromDecimal(tc.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\ty := x.Clone()\n\n\t\tif x.Cmp(y) != 0 {\n\t\t\tt.Errorf(\"Clone(%s) = %v, want %v\", tc.x, y, x)\n\t\t}\n\t}\n}\n"},{"name":"conversion.gno","body":"package int256\n\nimport \"gno.land/p/demo/uint256\"\n\n// SetInt64 sets z to x and returns z.\nfunc (z *Int) SetInt64(x int64) *Int {\n\tz.initiateAbs()\n\n\tneg := false\n\tif x \u003c 0 {\n\t\tneg = true\n\t\tx = -x\n\t}\n\tif z.abs == nil {\n\t\tpanic(\"abs is nil\")\n\t}\n\tz.abs = z.abs.SetUint64(uint64(x))\n\tz.neg = neg\n\treturn z\n}\n\n// SetUint64 sets z to x and returns z.\nfunc (z *Int) SetUint64(x uint64) *Int {\n\tz.initiateAbs()\n\n\tif z.abs == nil {\n\t\tpanic(\"abs is nil\")\n\t}\n\tz.abs = z.abs.SetUint64(x)\n\tz.neg = false\n\treturn z\n}\n\n// Uint64 returns the lower 64-bits of z\nfunc (z *Int) Uint64() uint64 {\n\treturn z.abs.Uint64()\n}\n\n// Int64 returns the lower 64-bits of z\nfunc (z *Int) Int64() int64 {\n\t_abs := z.abs.Clone()\n\n\tif z.neg {\n\t\treturn -int64(_abs.Uint64())\n\t}\n\treturn int64(_abs.Uint64())\n}\n\n// Neg sets z to -x and returns z.)\nfunc (z *Int) Neg(x *Int) *Int {\n\tz.abs.Set(x.abs)\n\tif z.abs.IsZero() {\n\t\tz.neg = false\n\t} else {\n\t\tz.neg = !x.neg\n\t}\n\treturn z\n}\n\n// Set sets z to x and returns z.\nfunc (z *Int) Set(x *Int) *Int {\n\tz.abs.Set(x.abs)\n\tz.neg = x.neg\n\treturn z\n}\n\n// SetFromUint256 converts a uint256.Uint to Int and sets the value to z.\nfunc (z *Int) SetUint256(x *uint256.Uint) *Int {\n\tz.abs.Set(x)\n\tz.neg = false\n\treturn z\n}\n\n// OBS, differs from original mempooler int256\n// ToString returns the decimal representation of z.\nfunc (z *Int) ToString() string {\n\tif z == nil {\n\t\tpanic(\"int256: nil pointer to ToString()\")\n\t}\n\n\tt := z.abs.Dec()\n\tif z.neg {\n\t\treturn \"-\" + t\n\t}\n\n\treturn t\n}\n"},{"name":"conversion_test.gno","body":"package int256\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uint256\"\n)\n\nfunc TestSetInt64(t *testing.T) {\n\ttests := []struct {\n\t\tx int64\n\t\twant string\n\t}{\n\t\t{0, \"0\"},\n\t\t{1, \"1\"},\n\t\t{-1, \"-1\"},\n\t\t{9223372036854775807, \"9223372036854775807\"},\n\t\t{-9223372036854775808, \"-9223372036854775808\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tvar z Int\n\t\tz.SetInt64(tc.x)\n\n\t\tgot := z.ToString()\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"SetInt64(%d) = %s, want %s\", tc.x, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestSetUint64(t *testing.T) {\n\ttests := []struct {\n\t\tx uint64\n\t\twant string\n\t}{\n\t\t{0, \"0\"},\n\t\t{1, \"1\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tvar z Int\n\t\tz.SetUint64(tc.x)\n\n\t\tgot := z.ToString()\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"SetUint64(%d) = %s, want %s\", tc.x, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestUint64(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant uint64\n\t}{\n\t\t{\"0\", 0},\n\t\t{\"1\", 1},\n\t\t{\"9223372036854775807\", 9223372036854775807},\n\t\t{\"9223372036854775808\", 9223372036854775808},\n\t\t{\"18446744073709551615\", 18446744073709551615},\n\t\t{\"18446744073709551616\", 0},\n\t\t{\"18446744073709551617\", 1},\n\t\t{\"-1\", 1},\n\t\t{\"-18446744073709551615\", 18446744073709551615},\n\t\t{\"-18446744073709551616\", 0},\n\t\t{\"-18446744073709551617\", 1},\n\t}\n\n\tfor _, tc := range tests {\n\t\tz := MustFromDecimal(tc.x)\n\n\t\tgot := z.Uint64()\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Uint64(%s) = %d, want %d\", tc.x, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestInt64(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant int64\n\t}{\n\t\t{\"0\", 0},\n\t\t{\"1\", 1},\n\t\t{\"9223372036854775807\", 9223372036854775807},\n\t\t{\"18446744073709551616\", 0},\n\t\t{\"18446744073709551617\", 1},\n\t\t{\"-1\", -1},\n\t\t{\"-9223372036854775808\", -9223372036854775808},\n\t}\n\n\tfor _, tc := range tests {\n\t\tz := MustFromDecimal(tc.x)\n\n\t\tgot := z.Int64()\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Uint64(%s) = %d, want %d\", tc.x, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestNeg(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant string\n\t}{\n\t\t{\"0\", \"0\"},\n\t\t{\"1\", \"-1\"},\n\t\t{\"-1\", \"1\"},\n\t\t{\"9223372036854775807\", \"-9223372036854775807\"},\n\t\t{\"-18446744073709551615\", \"18446744073709551615\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tz := MustFromDecimal(tc.x)\n\t\tz.Neg(z)\n\n\t\tgot := z.ToString()\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Neg(%s) = %s, want %s\", tc.x, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestSet(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant string\n\t}{\n\t\t{\"0\", \"0\"},\n\t\t{\"1\", \"1\"},\n\t\t{\"-1\", \"-1\"},\n\t\t{\"9223372036854775807\", \"9223372036854775807\"},\n\t\t{\"-18446744073709551615\", \"-18446744073709551615\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tz := MustFromDecimal(tc.x)\n\t\tz.Set(z)\n\n\t\tgot := z.ToString()\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Set(%s) = %s, want %s\", tc.x, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestSetUint256(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant string\n\t}{\n\t\t{\"0\", \"0\"},\n\t\t{\"1\", \"1\"},\n\t\t{\"9223372036854775807\", \"9223372036854775807\"},\n\t\t{\"18446744073709551615\", \"18446744073709551615\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tgot := New()\n\n\t\tz := uint256.MustFromDecimal(tc.x)\n\t\tgot.SetUint256(z)\n\n\t\tif got.ToString() != tc.want {\n\t\t\tt.Errorf(\"SetUint256(%s) = %s, want %s\", tc.x, got.ToString(), tc.want)\n\t\t}\n\t}\n}\n\nfunc TestToString(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tsetup func() *Int\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname: \"Zero from subtraction\",\n\t\t\tsetup: func() *Int {\n\t\t\t\tminusThree := MustFromDecimal(\"-3\")\n\t\t\t\tthree := MustFromDecimal(\"3\")\n\t\t\t\treturn Zero().Add(minusThree, three)\n\t\t\t},\n\t\t\texpected: \"0\",\n\t\t},\n\t\t{\n\t\t\tname: \"Zero from right shift\",\n\t\t\tsetup: func() *Int {\n\t\t\t\treturn Zero().Rsh(One(), 1234)\n\t\t\t},\n\t\t\texpected: \"0\",\n\t\t},\n\t\t{\n\t\t\tname: \"Positive number\",\n\t\t\tsetup: func() *Int {\n\t\t\t\treturn MustFromDecimal(\"42\")\n\t\t\t},\n\t\t\texpected: \"42\",\n\t\t},\n\t\t{\n\t\t\tname: \"Negative number\",\n\t\t\tsetup: func() *Int {\n\t\t\t\treturn MustFromDecimal(\"-42\")\n\t\t\t},\n\t\t\texpected: \"-42\",\n\t\t},\n\t\t{\n\t\t\tname: \"Large positive number\",\n\t\t\tsetup: func() *Int {\n\t\t\t\treturn MustFromDecimal(\"115792089237316195423570985008687907853269984665640564039457584007913129639935\")\n\t\t\t},\n\t\t\texpected: \"115792089237316195423570985008687907853269984665640564039457584007913129639935\",\n\t\t},\n\t\t{\n\t\t\tname: \"Large negative number\",\n\t\t\tsetup: func() *Int {\n\t\t\t\treturn MustFromDecimal(\"-115792089237316195423570985008687907853269984665640564039457584007913129639935\")\n\t\t\t},\n\t\t\texpected: \"-115792089237316195423570985008687907853269984665640564039457584007913129639935\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tz := tt.setup()\n\t\t\tresult := z.ToString()\n\t\t\tif result != tt.expected {\n\t\t\t\tt.Errorf(\"ToString() = %s, want %s\", result, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n"},{"name":"int256.gno","body":"// This package provides a 256-bit signed integer type, Int, and associated functions.\npackage int256\n\nimport (\n\t\"gno.land/p/demo/uint256\"\n)\n\nvar one = uint256.NewUint(1)\n\ntype Int struct {\n\tabs *uint256.Uint\n\tneg bool\n}\n\n// Zero returns a new Int set to 0.\nfunc Zero() *Int {\n\treturn NewInt(0)\n}\n\n// One returns a new Int set to 1.\nfunc One() *Int {\n\treturn NewInt(1)\n}\n\n// Sign returns:\n//\n//\t-1 if x \u003c 0\n//\t 0 if x == 0\n//\t+1 if x \u003e 0\nfunc (z *Int) Sign() int {\n\tz.initiateAbs()\n\n\tif z.abs.IsZero() {\n\t\treturn 0\n\t}\n\tif z.neg {\n\t\treturn -1\n\t}\n\treturn 1\n}\n\n// New returns a new Int set to 0.\nfunc New() *Int {\n\treturn \u0026Int{\n\t\tabs: new(uint256.Uint),\n\t}\n}\n\n// NewInt allocates and returns a new Int set to x.\nfunc NewInt(x int64) *Int {\n\treturn New().SetInt64(x)\n}\n\n// FromDecimal returns a new Int from a decimal string.\n// Returns a new Int and an error if the string is not a valid decimal.\nfunc FromDecimal(s string) (*Int, error) {\n\treturn new(Int).SetString(s)\n}\n\n// MustFromDecimal returns a new Int from a decimal string.\n// Panics if the string is not a valid decimal.\nfunc MustFromDecimal(s string) *Int {\n\tz, err := FromDecimal(s)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn z\n}\n\n// SetString sets s to the value of z and returns z and a boolean indicating success.\nfunc (z *Int) SetString(s string) (*Int, error) {\n\tneg := false\n\t// Remove max one leading +\n\tif len(s) \u003e 0 \u0026\u0026 s[0] == '+' {\n\t\tneg = false\n\t\ts = s[1:]\n\t}\n\n\tif len(s) \u003e 0 \u0026\u0026 s[0] == '-' {\n\t\tneg = true\n\t\ts = s[1:]\n\t}\n\tvar (\n\t\tabs *uint256.Uint\n\t\terr error\n\t)\n\tabs, err = uint256.FromDecimal(s)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn \u0026Int{\n\t\tabs,\n\t\tneg,\n\t}, nil\n}\n\n// FromUint256 is a convenience-constructor from uint256.Uint.\n// Returns a new Int and whether overflow occurred.\n// OBS: If u is `nil`, this method returns `nil, false`\nfunc FromUint256(x *uint256.Uint) *Int {\n\tif x == nil {\n\t\treturn nil\n\t}\n\tz := Zero()\n\n\tz.SetUint256(x)\n\treturn z\n}\n\n// OBS, differs from original mempooler int256\n// NilToZero sets z to 0 and return it if it's nil, otherwise it returns z\nfunc (z *Int) NilToZero() *Int {\n\tif z == nil {\n\t\treturn NewInt(0)\n\t}\n\treturn z\n}\n\n// initiateAbs sets default value for `z` or `z.abs` value if is nil\n// OBS: differs from mempooler int256. It checks not only `z.abs` but also `z`\nfunc (z *Int) initiateAbs() {\n\tif z == nil || z.abs == nil {\n\t\tz.abs = new(uint256.Uint)\n\t}\n}\n"},{"name":"int256_test.gno","body":"// ported from github.com/mempooler/int256\npackage int256\n\nimport \"testing\"\n\nfunc TestSign(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant int\n\t}{\n\t\t{\"0\", 0},\n\t\t{\"1\", 1},\n\t\t{\"-1\", -1},\n\t}\n\n\tfor _, tc := range tests {\n\t\tz := MustFromDecimal(tc.x)\n\t\tgot := z.Sign()\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Sign(%s) = %d, want %d\", tc.x, got, tc.want)\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":"json","path":"gno.land/p/demo/json","files":[{"name":"LICENSE","body":"# MIT License\n\nCopyright (c) 2019 Pyzhov Stepan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"},{"name":"README.md","body":"# JSON Parser\n\nThe JSON parser is a package that provides functionality for parsing and processing JSON strings. This package accepts JSON strings as byte slices.\n\nCurrently, gno does not [support the `reflect` package](https://docs.gno.land/concepts/effective-gno#reflection-is-never-clear), so it cannot retrieve type information at runtime. Therefore, it is designed to infer and handle type information when parsing JSON strings using a state machine approach.\n\nAfter passing through the state machine, JSON strings are represented as the `Node` type. The `Node` type represents nodes for JSON data, including various types such as `ObjectNode`, `ArrayNode`, `StringNode`, `NumberNode`, `BoolNode`, and `NullNode`.\n\nThis package provides methods for manipulating, searching, and extracting the Node type.\n\n## State Machine\n\nTo parse JSON strings, a [finite state machine](https://en.wikipedia.org/wiki/Finite-state_machine) approach is used. The state machine transitions to the next state based on the current state and the input character while parsing the JSON string. Through this method, type information can be inferred and processed without reflect, and the amount of parser code can be significantly reduced.\n\nThe image below shows the state transitions of the state machine according to the states and input characters.\n\n```mermaid\nstateDiagram-v2\n [*] --\u003e __: Start\n __ --\u003e ST: String\n __ --\u003e MI: Number\n __ --\u003e ZE: Zero\n __ --\u003e IN: Integer\n __ --\u003e T1: Boolean (true)\n __ --\u003e F1: Boolean (false)\n __ --\u003e N1: Null\n __ --\u003e ec: Empty Object End\n __ --\u003e cc: Object End\n __ --\u003e bc: Array End\n __ --\u003e co: Object Begin\n __ --\u003e bo: Array Begin\n __ --\u003e cm: Comma\n __ --\u003e cl: Colon\n __ --\u003e OK: Success/End\n ST --\u003e OK: String Complete\n MI --\u003e OK: Number Complete\n ZE --\u003e OK: Zero Complete\n IN --\u003e OK: Integer Complete\n T1 --\u003e OK: True Complete\n F1 --\u003e OK: False Complete\n N1 --\u003e OK: Null Complete\n ec --\u003e OK: Empty Object Complete\n cc --\u003e OK: Object Complete\n bc --\u003e OK: Array Complete\n co --\u003e OB: Inside Object\n bo --\u003e AR: Inside Array\n cm --\u003e KE: Expecting New Key\n cm --\u003e VA: Expecting New Value\n cl --\u003e VA: Expecting Value\n OB --\u003e ST: String in Object (Key)\n OB --\u003e ec: Empty Object\n OB --\u003e cc: End Object\n AR --\u003e ST: String in Array\n AR --\u003e bc: End Array\n KE --\u003e ST: String as Key\n VA --\u003e ST: String as Value\n VA --\u003e MI: Number as Value\n VA --\u003e T1: True as Value\n VA --\u003e F1: False as Value\n VA --\u003e N1: Null as Value\n OK --\u003e [*]: End\n```\n\n## Examples\n\nThis package provides parsing functionality along with encoding and decoding functionality. The following examples demonstrate how to use this package.\n\n### Decoding\n\nDecoding (or Unmarshaling) is the functionality that converts an input byte slice JSON string into a `Node` type.\n\nThe converted `Node` type allows you to modify the JSON data or search and extract data that meets specific conditions.\n\n```go\npackage main\n\nimport (\n \"gno.land/p/demo/json\"\n \"gno.land/p/demo/ufmt\"\n)\n\nfunc main() {\n node, err := json.Unmarshal([]byte(`{\"foo\": \"var\"}`))\n if err != nil {\n ufmt.Errorf(\"error: %v\", err)\n }\n\n ufmt.Sprintf(\"node: %v\", node)\n}\n```\n\n### Encoding\n\nEncoding (or Marshaling) is the functionality that converts JSON data represented as a Node type into a byte slice JSON string.\n\n\u003e ⚠️ Caution: Converting a large `Node` type into a JSON string may _impact performance_. or might be cause _unexpected behavior_.\n\n```go\npackage main\n\nimport (\n \"gno.land/p/demo/json\"\n \"gno.land/p/demo/ufmt\"\n)\n\nfunc main() {\n node := ObjectNode(\"\", map[string]*Node{\n \"foo\": StringNode(\"foo\", \"bar\"),\n \"baz\": NumberNode(\"baz\", 100500),\n \"qux\": NullNode(\"qux\"),\n })\n\n b, err := json.Marshal(node)\n if err != nil {\n ufmt.Errorf(\"error: %v\", err)\n }\n\n ufmt.Sprintf(\"json: %s\", string(b))\n}\n```\n\n### Searching\n\nOnce the JSON data converted into a `Node` type, you can **search** and **extract** data that satisfy specific conditions. For example, you can find data with a specific type or data with a specific key.\n\nTo use this functionality, you can use methods in the `GetXXX` prefixed methods. The `MustXXX` methods also provide the same functionality as the former methods, but they will **panic** if data doesn't satisfies the condition.\n\nHere is an example of finding data with a specific key. For more examples, please refer to the [node.gno](node.gno) file.\n\n```go\npackage main\n\nimport (\n \"gno.land/p/demo/json\"\n \"gno.land/p/demo/ufmt\"\n)\n\nfunc main() {\n root, err := Unmarshal([]byte(`{\"foo\": true, \"bar\": null}`))\n if err != nil {\n ufmt.Errorf(\"error: %v\", err)\n }\n\n value, err := root.GetKey(\"foo\")\n if err != nil {\n ufmt.Errorf(\"error occurred while getting key, %s\", err)\n }\n\n if value.MustBool() != true {\n ufmt.Errorf(\"value is not true\")\n }\n\n value, err = root.GetKey(\"bar\")\n if err != nil {\n t.Errorf(\"error occurred while getting key, %s\", err)\n }\n\n _, err = root.GetKey(\"baz\")\n if err == nil {\n t.Errorf(\"key baz is not exist. must be failed\")\n }\n}\n```\n\n## Contributing\n\nPlease submit any issues or pull requests for this package through the GitHub repository at [gnolang/gno](\u003chttps://github.com/gnolang/gno\u003e).\n"},{"name":"buffer.gno","body":"package json\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype buffer struct {\n\tdata []byte\n\tlength int\n\tindex int\n\n\tlast States\n\tstate States\n\tclass Classes\n}\n\n// newBuffer creates a new buffer with the given data\nfunc newBuffer(data []byte) *buffer {\n\treturn \u0026buffer{\n\t\tdata: data,\n\t\tlength: len(data),\n\t\tlast: GO,\n\t\tstate: GO,\n\t}\n}\n\n// first retrieves the first non-whitespace (or other escaped) character in the buffer.\nfunc (b *buffer) first() (byte, error) {\n\tfor ; b.index \u003c b.length; b.index++ {\n\t\tc := b.data[b.index]\n\n\t\tif !(c == whiteSpace || c == carriageReturn || c == newLine || c == tab) {\n\t\t\treturn c, nil\n\t\t}\n\t}\n\n\treturn 0, io.EOF\n}\n\n// current returns the byte of the current index.\nfunc (b *buffer) current() (byte, error) {\n\tif b.index \u003e= b.length {\n\t\treturn 0, io.EOF\n\t}\n\n\treturn b.data[b.index], nil\n}\n\n// next moves to the next byte and returns it.\nfunc (b *buffer) next() (byte, error) {\n\tb.index++\n\treturn b.current()\n}\n\n// step just moves to the next position.\nfunc (b *buffer) step() error {\n\t_, err := b.next()\n\treturn err\n}\n\n// move moves the index by the given position.\nfunc (b *buffer) move(pos int) error {\n\tnewIndex := b.index + pos\n\n\tif newIndex \u003e b.length {\n\t\treturn io.EOF\n\t}\n\n\tb.index = newIndex\n\n\treturn nil\n}\n\n// slice returns the slice from the current index to the given position.\nfunc (b *buffer) slice(pos int) ([]byte, error) {\n\tend := b.index + pos\n\n\tif end \u003e b.length {\n\t\treturn nil, io.EOF\n\t}\n\n\treturn b.data[b.index:end], nil\n}\n\n// sliceFromIndices returns a slice of the buffer's data starting from 'start' up to (but not including) 'stop'.\nfunc (b *buffer) sliceFromIndices(start, stop int) []byte {\n\tif start \u003e b.length {\n\t\tstart = b.length\n\t}\n\n\tif stop \u003e b.length {\n\t\tstop = b.length\n\t}\n\n\treturn b.data[start:stop]\n}\n\n// skip moves the index to skip the given byte.\nfunc (b *buffer) skip(bs byte) error {\n\tfor b.index \u003c b.length {\n\t\tif b.data[b.index] == bs \u0026\u0026 !b.backslash() {\n\t\t\treturn nil\n\t\t}\n\n\t\tb.index++\n\t}\n\n\treturn io.EOF\n}\n\n// skipAndReturnIndex moves the buffer index forward by one and returns the new index.\nfunc (b *buffer) skipAndReturnIndex() (int, error) {\n\terr := b.step()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn b.index, nil\n}\n\n// skipUntil moves the buffer index forward until it encounters a byte contained in the endTokens set.\nfunc (b *buffer) skipUntil(endTokens map[byte]bool) (int, error) {\n\tfor b.index \u003c b.length {\n\t\tcurrentByte, err := b.current()\n\t\tif err != nil {\n\t\t\treturn b.index, err\n\t\t}\n\n\t\t// Check if the current byte is in the set of end tokens.\n\t\tif _, exists := endTokens[currentByte]; exists {\n\t\t\treturn b.index, nil\n\t\t}\n\n\t\tb.index++\n\t}\n\n\treturn b.index, io.EOF\n}\n\n// significantTokens is a map where the keys are the significant characters in a JSON path.\n// The values in the map are all true, which allows us to use the map as a set for quick lookups.\nvar significantTokens = [256]bool{\n\tdot: true, // access properties of an object\n\tdollarSign: true, // root object\n\tatSign: true, // current object\n\tbracketOpen: true, // start of an array index or filter expression\n\tbracketClose: true, // end of an array index or filter expression\n}\n\n// filterTokens stores the filter expression tokens.\nvar filterTokens = [256]bool{\n\taesterisk: true, // wildcard\n\tandSign: true,\n\torSign: true,\n}\n\n// skipToNextSignificantToken advances the buffer index to the next significant character.\n// Significant characters are defined based on the JSON path syntax.\nfunc (b *buffer) skipToNextSignificantToken() {\n\tfor b.index \u003c b.length {\n\t\tcurrent := b.data[b.index]\n\n\t\tif significantTokens[current] {\n\t\t\tbreak\n\t\t}\n\n\t\tb.index++\n\t}\n}\n\n// backslash checks to see if the number of backslashes before the current index is odd.\n//\n// This is used to check if the current character is escaped. However, unlike the \"unescape\" function,\n// \"backslash\" only serves to check the number of backslashes.\nfunc (b *buffer) backslash() bool {\n\tif b.index == 0 {\n\t\treturn false\n\t}\n\n\tcount := 0\n\tfor i := b.index - 1; ; i-- {\n\t\tif b.data[i] != backSlash {\n\t\t\tbreak\n\t\t}\n\n\t\tcount++\n\n\t\tif i == 0 {\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn count%2 != 0\n}\n\n// numIndex holds a map of valid numeric characters\nvar numIndex = [256]bool{\n\t'0': true,\n\t'1': true,\n\t'2': true,\n\t'3': true,\n\t'4': true,\n\t'5': true,\n\t'6': true,\n\t'7': true,\n\t'8': true,\n\t'9': true,\n\t'.': true,\n\t'e': true,\n\t'E': true,\n}\n\n// pathToken checks if the current token is a valid JSON path token.\nfunc (b *buffer) pathToken() error {\n\tvar stack []byte\n\n\tinToken := false\n\tinNumber := false\n\tfirst := b.index\n\n\tfor b.index \u003c b.length {\n\t\tc := b.data[b.index]\n\n\t\tswitch {\n\t\tcase c == doubleQuote || c == singleQuote:\n\t\t\tinToken = true\n\t\t\tif err := b.step(); err != nil {\n\t\t\t\treturn errors.New(\"error stepping through buffer\")\n\t\t\t}\n\n\t\t\tif err := b.skip(c); err != nil {\n\t\t\t\treturn errUnmatchedQuotePath\n\t\t\t}\n\n\t\t\tif b.index \u003e= b.length {\n\t\t\t\treturn errUnmatchedQuotePath\n\t\t\t}\n\n\t\tcase c == bracketOpen || c == parenOpen:\n\t\t\tinToken = true\n\t\t\tstack = append(stack, c)\n\n\t\tcase c == bracketClose || c == parenClose:\n\t\t\tinToken = true\n\t\t\tif len(stack) == 0 || (c == bracketClose \u0026\u0026 stack[len(stack)-1] != bracketOpen) || (c == parenClose \u0026\u0026 stack[len(stack)-1] != parenOpen) {\n\t\t\t\treturn errUnmatchedParenthesis\n\t\t\t}\n\n\t\t\tstack = stack[:len(stack)-1]\n\n\t\tcase pathStateContainsValidPathToken(c):\n\t\t\tinToken = true\n\n\t\tcase c == plus || c == minus:\n\t\t\tif inNumber || (b.index \u003e 0 \u0026\u0026 numIndex[b.data[b.index-1]]) {\n\t\t\t\tinToken = true\n\t\t\t} else if !inToken \u0026\u0026 (b.index+1 \u003c b.length \u0026\u0026 numIndex[b.data[b.index+1]]) {\n\t\t\t\tinToken = true\n\t\t\t\tinNumber = true\n\t\t\t} else if !inToken {\n\t\t\t\treturn errInvalidToken\n\t\t\t}\n\n\t\tdefault:\n\t\t\tif len(stack) != 0 || inToken {\n\t\t\t\tinToken = true\n\t\t\t} else {\n\t\t\t\tgoto end\n\t\t\t}\n\t\t}\n\n\t\tb.index++\n\t}\n\nend:\n\tif len(stack) != 0 {\n\t\treturn errUnmatchedParenthesis\n\t}\n\n\tif first == b.index {\n\t\treturn errors.New(\"no token found\")\n\t}\n\n\tif inNumber \u0026\u0026 !numIndex[b.data[b.index-1]] {\n\t\tinNumber = false\n\t}\n\n\treturn nil\n}\n\nfunc pathStateContainsValidPathToken(c byte) bool {\n\tif significantTokens[c] {\n\t\treturn true\n\t}\n\n\tif filterTokens[c] {\n\t\treturn true\n\t}\n\n\tif numIndex[c] {\n\t\treturn true\n\t}\n\n\tif 'A' \u003c= c \u0026\u0026 c \u003c= 'Z' || 'a' \u003c= c \u0026\u0026 c \u003c= 'z' {\n\t\treturn true\n\t}\n\n\treturn false\n}\n\nfunc (b *buffer) numeric(token bool) error {\n\tif token {\n\t\tb.last = GO\n\t}\n\n\tfor ; b.index \u003c b.length; b.index++ {\n\t\tb.class = b.getClasses(doubleQuote)\n\t\tif b.class == __ {\n\t\t\treturn errInvalidToken\n\t\t}\n\n\t\tb.state = StateTransitionTable[b.last][b.class]\n\t\tif b.state == __ {\n\t\t\tif token {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\treturn errInvalidToken\n\t\t}\n\n\t\tif b.state \u003c __ {\n\t\t\treturn nil\n\t\t}\n\n\t\tif b.state \u003c MI || b.state \u003e E3 {\n\t\t\treturn nil\n\t\t}\n\n\t\tb.last = b.state\n\t}\n\n\tif b.last != ZE \u0026\u0026 b.last != IN \u0026\u0026 b.last != FR \u0026\u0026 b.last != E3 {\n\t\treturn errInvalidToken\n\t}\n\n\treturn nil\n}\n\nfunc (b *buffer) getClasses(c byte) Classes {\n\tif b.data[b.index] \u003e= 128 {\n\t\treturn C_ETC\n\t}\n\n\tif c == singleQuote {\n\t\treturn QuoteAsciiClasses[b.data[b.index]]\n\t}\n\n\treturn AsciiClasses[b.data[b.index]]\n}\n\nfunc (b *buffer) getState() States {\n\tb.last = b.state\n\n\tb.class = b.getClasses(doubleQuote)\n\tif b.class == __ {\n\t\treturn __\n\t}\n\n\tb.state = StateTransitionTable[b.last][b.class]\n\n\treturn b.state\n}\n\n// string parses a string token from the buffer.\nfunc (b *buffer) string(search byte, token bool) error {\n\tif token {\n\t\tb.last = GO\n\t}\n\n\tfor ; b.index \u003c b.length; b.index++ {\n\t\tb.class = b.getClasses(search)\n\n\t\tif b.class == __ {\n\t\t\treturn errInvalidToken\n\t\t}\n\n\t\tb.state = StateTransitionTable[b.last][b.class]\n\t\tif b.state == __ {\n\t\t\treturn errInvalidToken\n\t\t}\n\n\t\tif b.state \u003c __ {\n\t\t\tbreak\n\t\t}\n\n\t\tb.last = b.state\n\t}\n\n\treturn nil\n}\n\nfunc (b *buffer) word(bs []byte) error {\n\tvar c byte\n\n\tmax := len(bs)\n\tindex := 0\n\n\tfor ; b.index \u003c b.length \u0026\u0026 index \u003c max; b.index++ {\n\t\tc = b.data[b.index]\n\n\t\tif c != bs[index] {\n\t\t\treturn errInvalidToken\n\t\t}\n\n\t\tindex++\n\t\tif index \u003e= max {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif index != max {\n\t\treturn errInvalidToken\n\t}\n\n\treturn nil\n}\n\nfunc numberKind2f64(value interface{}) (result float64, err error) {\n\tswitch typed := value.(type) {\n\tcase float64:\n\t\tresult = typed\n\tcase float32:\n\t\tresult = float64(typed)\n\tcase int:\n\t\tresult = float64(typed)\n\tcase int8:\n\t\tresult = float64(typed)\n\tcase int16:\n\t\tresult = float64(typed)\n\tcase int32:\n\t\tresult = float64(typed)\n\tcase int64:\n\t\tresult = float64(typed)\n\tcase uint:\n\t\tresult = float64(typed)\n\tcase uint8:\n\t\tresult = float64(typed)\n\tcase uint16:\n\t\tresult = float64(typed)\n\tcase uint32:\n\t\tresult = float64(typed)\n\tcase uint64:\n\t\tresult = float64(typed)\n\tdefault:\n\t\terr = ufmt.Errorf(\"invalid number type: %T\", value)\n\t}\n\n\treturn\n}\n"},{"name":"buffer_test.gno","body":"package json\n\nimport (\n\t\"testing\"\n)\n\nfunc TestBufferCurrent(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tbuffer *buffer\n\t\texpected byte\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"Valid current byte\",\n\t\t\tbuffer: \u0026buffer{\n\t\t\t\tdata: []byte(\"test\"),\n\t\t\t\tlength: 4,\n\t\t\t\tindex: 1,\n\t\t\t},\n\t\t\texpected: 'e',\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"EOF\",\n\t\t\tbuffer: \u0026buffer{\n\t\t\t\tdata: []byte(\"test\"),\n\t\t\t\tlength: 4,\n\t\t\t\tindex: 4,\n\t\t\t},\n\t\t\texpected: 0,\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.buffer.current()\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"buffer.current() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif got != tt.expected {\n\t\t\t\tt.Errorf(\"buffer.current() = %v, want %v\", got, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBufferStep(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tbuffer *buffer\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"Valid step\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 0},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"EOF error\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 3},\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\terr := tt.buffer.step()\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"buffer.step() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBufferNext(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tbuffer *buffer\n\t\twant byte\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"Valid next byte\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 0},\n\t\t\twant: 'e',\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"EOF error\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 3},\n\t\t\twant: 0,\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.buffer.next()\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"buffer.next() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif got != tt.want {\n\t\t\t\tt.Errorf(\"buffer.next() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBufferSlice(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tbuffer *buffer\n\t\tpos int\n\t\twant []byte\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"Valid slice -- 0 characters\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 0},\n\t\t\tpos: 0,\n\t\t\twant: nil,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Valid slice -- 1 character\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 0},\n\t\t\tpos: 1,\n\t\t\twant: []byte(\"t\"),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Valid slice -- 2 characters\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 1},\n\t\t\tpos: 2,\n\t\t\twant: []byte(\"es\"),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Valid slice -- 3 characters\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 0},\n\t\t\tpos: 3,\n\t\t\twant: []byte(\"tes\"),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Valid slice -- 4 characters\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 0},\n\t\t\tpos: 4,\n\t\t\twant: []byte(\"test\"),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"EOF error\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 3},\n\t\t\tpos: 2,\n\t\t\twant: nil,\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.buffer.slice(tt.pos)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"buffer.slice() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif string(got) != string(tt.want) {\n\t\t\t\tt.Errorf(\"buffer.slice() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBufferMove(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tbuffer *buffer\n\t\tpos int\n\t\twantErr bool\n\t\twantIdx int\n\t}{\n\t\t{\n\t\t\tname: \"Valid move\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 1},\n\t\t\tpos: 2,\n\t\t\twantErr: false,\n\t\t\twantIdx: 3,\n\t\t},\n\t\t{\n\t\t\tname: \"Move beyond length\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 1},\n\t\t\tpos: 4,\n\t\t\twantErr: true,\n\t\t\twantIdx: 1,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\terr := tt.buffer.move(tt.pos)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"buffer.move() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t\tif tt.buffer.index != tt.wantIdx {\n\t\t\t\tt.Errorf(\"buffer.move() index = %v, want %v\", tt.buffer.index, tt.wantIdx)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBufferSkip(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tbuffer *buffer\n\t\tb byte\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"Skip byte\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 0},\n\t\t\tb: 'e',\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Skip to EOF\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 0},\n\t\t\tb: 'x',\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\terr := tt.buffer.skip(tt.b)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"buffer.skip() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSkipToNextSignificantToken(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput []byte\n\t\texpected int\n\t}{\n\t\t{\"No significant chars\", []byte(\"abc\"), 3},\n\t\t{\"One significant char at start\", []byte(\".abc\"), 0},\n\t\t{\"Significant char in middle\", []byte(\"ab.c\"), 2},\n\t\t{\"Multiple significant chars\", []byte(\"a$.c\"), 1},\n\t\t{\"Significant char at end\", []byte(\"abc$\"), 3},\n\t\t{\"Only significant chars\", []byte(\"$.\"), 0},\n\t\t{\"Empty string\", []byte(\"\"), 0},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tb := newBuffer(tt.input)\n\t\t\tb.skipToNextSignificantToken()\n\t\t\tif b.index != tt.expected {\n\t\t\t\tt.Errorf(\"after skipToNextSignificantToken(), got index = %v, want %v\", b.index, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc mockBuffer(s string) *buffer {\n\treturn newBuffer([]byte(s))\n}\n\nfunc TestSkipAndReturnIndex(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput string\n\t\texpected int\n\t}{\n\t\t{\"StartOfString\", \"\", 0},\n\t\t{\"MiddleOfString\", \"abcdef\", 1},\n\t\t{\"EndOfString\", \"abc\", 1},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tbuf := mockBuffer(tt.input)\n\t\t\tgot, err := buf.skipAndReturnIndex()\n\t\t\tif err != nil \u0026\u0026 tt.input != \"\" { // Expect no error unless input is empty\n\t\t\t\tt.Errorf(\"skipAndReturnIndex() error = %v\", err)\n\t\t\t}\n\t\t\tif got != tt.expected {\n\t\t\t\tt.Errorf(\"skipAndReturnIndex() = %v, want %v\", got, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSkipUntil(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput string\n\t\ttokens map[byte]bool\n\t\texpected int\n\t}{\n\t\t{\"SkipToToken\", \"abcdefg\", map[byte]bool{'c': true}, 2},\n\t\t{\"SkipToEnd\", \"abcdefg\", map[byte]bool{'h': true}, 7},\n\t\t{\"SkipNone\", \"abcdefg\", map[byte]bool{'a': true}, 0},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tbuf := mockBuffer(tt.input)\n\t\t\tgot, err := buf.skipUntil(tt.tokens)\n\t\t\tif err != nil \u0026\u0026 got != len(tt.input) { // Expect error only if reached end without finding token\n\t\t\t\tt.Errorf(\"skipUntil() error = %v\", err)\n\t\t\t}\n\t\t\tif got != tt.expected {\n\t\t\t\tt.Errorf(\"skipUntil() = %v, want %v\", got, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSliceFromIndices(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput string\n\t\tstart int\n\t\tend int\n\t\texpected string\n\t}{\n\t\t{\"FullString\", \"abcdefg\", 0, 7, \"abcdefg\"},\n\t\t{\"Substring\", \"abcdefg\", 2, 5, \"cde\"},\n\t\t{\"OutOfBounds\", \"abcdefg\", 5, 10, \"fg\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tbuf := mockBuffer(tt.input)\n\t\t\tgot := buf.sliceFromIndices(tt.start, tt.end)\n\t\t\tif string(got) != tt.expected {\n\t\t\t\tt.Errorf(\"sliceFromIndices() = %v, want %v\", string(got), tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBufferToken(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tpath string\n\t\tindex int\n\t\tisErr bool\n\t}{\n\t\t{\n\t\t\tname: \"Simple valid path\",\n\t\t\tpath: \"@.length\",\n\t\t\tindex: 8,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Path with array expr\",\n\t\t\tpath: \"@['foo'].0.bar\",\n\t\t\tindex: 14,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Path with array expr and simple fomula\",\n\t\t\tpath: \"@['foo'].[(@.length - 1)].*\",\n\t\t\tindex: 27,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Path with filter expr\",\n\t\t\tpath: \"@['foo'].[?(@.bar == 1 \u0026 @.baz \u003c @.length)].*\",\n\t\t\tindex: 45,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"addition of foo and bar\",\n\t\t\tpath: \"@.foo+@.bar\",\n\t\t\tindex: 11,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"logical AND of foo and bar\",\n\t\t\tpath: \"@.foo \u0026\u0026 @.bar\",\n\t\t\tindex: 14,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"logical OR of foo and bar\",\n\t\t\tpath: \"@.foo || @.bar\",\n\t\t\tindex: 14,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"accessing third element of foo\",\n\t\t\tpath: \"@.foo,3\",\n\t\t\tindex: 7,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"accessing last element of array\",\n\t\t\tpath: \"@.length-1\",\n\t\t\tindex: 10,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"number 1\",\n\t\t\tpath: \"1\",\n\t\t\tindex: 1,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"float\",\n\t\t\tpath: \"3.1e4\",\n\t\t\tindex: 5,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"float with minus\",\n\t\t\tpath: \"3.1e-4\",\n\t\t\tindex: 6,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"float with plus\",\n\t\t\tpath: \"3.1e+4\",\n\t\t\tindex: 6,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"negative number\",\n\t\t\tpath: \"-12345\",\n\t\t\tindex: 6,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"negative float\",\n\t\t\tpath: \"-3.1e4\",\n\t\t\tindex: 6,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"negative float with minus\",\n\t\t\tpath: \"-3.1e-4\",\n\t\t\tindex: 7,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"negative float with plus\",\n\t\t\tpath: \"-3.1e+4\",\n\t\t\tindex: 7,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"string number\",\n\t\t\tpath: \"'12345'\",\n\t\t\tindex: 7,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"string with backslash\",\n\t\t\tpath: \"'foo \\\\'bar '\",\n\t\t\tindex: 12,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"string with inner double quotes\",\n\t\t\tpath: \"'foo \\\"bar \\\"'\",\n\t\t\tindex: 12,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"parenthesis 1\",\n\t\t\tpath: \"(@abc)\",\n\t\t\tindex: 6,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"parenthesis 2\",\n\t\t\tpath: \"[()]\",\n\t\t\tindex: 4,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"parenthesis mismatch\",\n\t\t\tpath: \"[(])\",\n\t\t\tindex: 2,\n\t\t\tisErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"parenthesis mismatch 2\",\n\t\t\tpath: \"(\",\n\t\t\tindex: 1,\n\t\t\tisErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"parenthesis mismatch 3\",\n\t\t\tpath: \"())]\",\n\t\t\tindex: 2,\n\t\t\tisErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"bracket mismatch\",\n\t\t\tpath: \"[()\",\n\t\t\tindex: 3,\n\t\t\tisErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"bracket mismatch 2\",\n\t\t\tpath: \"()]\",\n\t\t\tindex: 2,\n\t\t\tisErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"path does not close bracket\",\n\t\t\tpath: \"@.foo[)\",\n\t\t\tindex: 6,\n\t\t\tisErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tbuf := newBuffer([]byte(tt.path))\n\n\t\t\terr := buf.pathToken()\n\t\t\tif tt.isErr {\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Errorf(\"Expected an error for path `%s`, but got none\", tt.path)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif err == nil \u0026\u0026 tt.isErr {\n\t\t\t\tt.Errorf(\"Expected an error for path `%s`, but got none\", tt.path)\n\t\t\t}\n\n\t\t\tif buf.index != tt.index {\n\t\t\t\tt.Errorf(\"Expected final index %d, got %d (token: `%s`) for path `%s`\", tt.index, buf.index, string(buf.data[buf.index]), tt.path)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBufferFirst(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tdata []byte\n\t\texpected byte\n\t}{\n\t\t{\n\t\t\tname: \"Valid first byte\",\n\t\t\tdata: []byte(\"test\"),\n\t\t\texpected: 't',\n\t\t},\n\t\t{\n\t\t\tname: \"Empty buffer\",\n\t\t\tdata: []byte(\"\"),\n\t\t\texpected: 0,\n\t\t},\n\t\t{\n\t\t\tname: \"Whitespace buffer\",\n\t\t\tdata: []byte(\" \"),\n\t\t\texpected: 0,\n\t\t},\n\t\t{\n\t\t\tname: \"whitespace in middle\",\n\t\t\tdata: []byte(\"hello world\"),\n\t\t\texpected: 'h',\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tb := newBuffer(tt.data)\n\n\t\t\tgot, err := b.first()\n\t\t\tif err != nil \u0026\u0026 tt.expected != 0 {\n\t\t\t\tt.Errorf(\"Unexpected error: %v\", err)\n\t\t\t}\n\n\t\t\tif got != tt.expected {\n\t\t\t\tt.Errorf(\"Expected first byte to be %q, got %q\", tt.expected, got)\n\t\t\t}\n\t\t})\n\t}\n}\n"},{"name":"builder.gno","body":"package json\n\ntype NodeBuilder struct {\n\tnode *Node\n}\n\nfunc Builder() *NodeBuilder {\n\treturn \u0026NodeBuilder{node: ObjectNode(\"\", nil)}\n}\n\nfunc (b *NodeBuilder) WriteString(key, value string) *NodeBuilder {\n\tb.node.AppendObject(key, StringNode(\"\", value))\n\treturn b\n}\n\nfunc (b *NodeBuilder) WriteNumber(key string, value float64) *NodeBuilder {\n\tb.node.AppendObject(key, NumberNode(\"\", value))\n\treturn b\n}\n\nfunc (b *NodeBuilder) WriteBool(key string, value bool) *NodeBuilder {\n\tb.node.AppendObject(key, BoolNode(\"\", value))\n\treturn b\n}\n\nfunc (b *NodeBuilder) WriteNull(key string) *NodeBuilder {\n\tb.node.AppendObject(key, NullNode(\"\"))\n\treturn b\n}\n\nfunc (b *NodeBuilder) WriteObject(key string, fn func(*NodeBuilder)) *NodeBuilder {\n\tnestedBuilder := \u0026NodeBuilder{node: ObjectNode(\"\", nil)}\n\tfn(nestedBuilder)\n\tb.node.AppendObject(key, nestedBuilder.node)\n\treturn b\n}\n\nfunc (b *NodeBuilder) WriteArray(key string, fn func(*ArrayBuilder)) *NodeBuilder {\n\tarrayBuilder := \u0026ArrayBuilder{nodes: []*Node{}}\n\tfn(arrayBuilder)\n\tb.node.AppendObject(key, ArrayNode(\"\", arrayBuilder.nodes))\n\treturn b\n}\n\nfunc (b *NodeBuilder) Node() *Node {\n\treturn b.node\n}\n\ntype ArrayBuilder struct {\n\tnodes []*Node\n}\n\nfunc (ab *ArrayBuilder) WriteString(value string) *ArrayBuilder {\n\tab.nodes = append(ab.nodes, StringNode(\"\", value))\n\treturn ab\n}\n\nfunc (ab *ArrayBuilder) WriteNumber(value float64) *ArrayBuilder {\n\tab.nodes = append(ab.nodes, NumberNode(\"\", value))\n\treturn ab\n}\n\nfunc (ab *ArrayBuilder) WriteInt(value int) *ArrayBuilder {\n\treturn ab.WriteNumber(float64(value))\n}\n\nfunc (ab *ArrayBuilder) WriteBool(value bool) *ArrayBuilder {\n\tab.nodes = append(ab.nodes, BoolNode(\"\", value))\n\treturn ab\n}\n\nfunc (ab *ArrayBuilder) WriteNull() *ArrayBuilder {\n\tab.nodes = append(ab.nodes, NullNode(\"\"))\n\treturn ab\n}\n\nfunc (ab *ArrayBuilder) WriteObject(fn func(*NodeBuilder)) *ArrayBuilder {\n\tnestedBuilder := \u0026NodeBuilder{node: ObjectNode(\"\", nil)}\n\tfn(nestedBuilder)\n\tab.nodes = append(ab.nodes, nestedBuilder.node)\n\treturn ab\n}\n\nfunc (ab *ArrayBuilder) WriteArray(fn func(*ArrayBuilder)) *ArrayBuilder {\n\tnestedArrayBuilder := \u0026ArrayBuilder{nodes: []*Node{}}\n\tfn(nestedArrayBuilder)\n\tab.nodes = append(ab.nodes, ArrayNode(\"\", nestedArrayBuilder.nodes))\n\treturn ab\n}\n"},{"name":"builder_test.gno","body":"package json\n\nimport (\n\t\"testing\"\n)\n\nfunc TestNodeBuilder(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tbuild func() *Node\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname: \"plain object\",\n\t\t\tbuild: func() *Node {\n\t\t\t\treturn Builder().\n\t\t\t\t\tWriteString(\"name\", \"Alice\").\n\t\t\t\t\tWriteNumber(\"age\", 30).\n\t\t\t\t\tWriteBool(\"is_student\", false).\n\t\t\t\t\tNode()\n\t\t\t},\n\t\t\texpected: `{\"name\":\"Alice\",\"age\":30,\"is_student\":false}`,\n\t\t},\n\t\t{\n\t\t\tname: \"nested object\",\n\t\t\tbuild: func() *Node {\n\t\t\t\treturn Builder().\n\t\t\t\t\tWriteString(\"name\", \"Alice\").\n\t\t\t\t\tWriteObject(\"address\", func(b *NodeBuilder) {\n\t\t\t\t\t\tb.WriteString(\"city\", \"New York\").\n\t\t\t\t\t\t\tWriteNumber(\"zipcode\", 10001)\n\t\t\t\t\t}).\n\t\t\t\t\tNode()\n\t\t\t},\n\t\t\texpected: `{\"name\":\"Alice\",\"address\":{\"city\":\"New York\",\"zipcode\":10001}}`,\n\t\t},\n\t\t{\n\t\t\tname: \"null node\",\n\t\t\tbuild: func() *Node {\n\t\t\t\treturn Builder().WriteNull(\"foo\").Node()\n\t\t\t},\n\t\t\texpected: `{\"foo\":null}`,\n\t\t},\n\t\t{\n\t\t\tname: \"array node\",\n\t\t\tbuild: func() *Node {\n\t\t\t\treturn Builder().\n\t\t\t\t\tWriteArray(\"items\", func(ab *ArrayBuilder) {\n\t\t\t\t\t\tab.WriteString(\"item1\").\n\t\t\t\t\t\t\tWriteString(\"item2\").\n\t\t\t\t\t\t\tWriteString(\"item3\")\n\t\t\t\t\t}).\n\t\t\t\t\tNode()\n\t\t\t},\n\t\t\texpected: `{\"items\":[\"item1\",\"item2\",\"item3\"]}`,\n\t\t},\n\t\t{\n\t\t\tname: \"array with objects\",\n\t\t\tbuild: func() *Node {\n\t\t\t\treturn Builder().\n\t\t\t\t\tWriteArray(\"users\", func(ab *ArrayBuilder) {\n\t\t\t\t\t\tab.WriteObject(func(b *NodeBuilder) {\n\t\t\t\t\t\t\tb.WriteString(\"name\", \"Bob\").\n\t\t\t\t\t\t\t\tWriteNumber(\"age\", 25)\n\t\t\t\t\t\t}).\n\t\t\t\t\t\t\tWriteObject(func(b *NodeBuilder) {\n\t\t\t\t\t\t\t\tb.WriteString(\"name\", \"Carol\").\n\t\t\t\t\t\t\t\t\tWriteNumber(\"age\", 27)\n\t\t\t\t\t\t\t})\n\t\t\t\t\t}).\n\t\t\t\t\tNode()\n\t\t\t},\n\t\t\texpected: `{\"users\":[{\"name\":\"Bob\",\"age\":25},{\"name\":\"Carol\",\"age\":27}]}`,\n\t\t},\n\t\t{\n\t\t\tname: \"array with various types\",\n\t\t\tbuild: func() *Node {\n\t\t\t\treturn Builder().\n\t\t\t\t\tWriteArray(\"values\", func(ab *ArrayBuilder) {\n\t\t\t\t\t\tab.WriteString(\"item1\").\n\t\t\t\t\t\t\tWriteNumber(123).\n\t\t\t\t\t\t\tWriteBool(true).\n\t\t\t\t\t\t\tWriteNull()\n\t\t\t\t\t}).\n\t\t\t\t\tNode()\n\t\t\t},\n\t\t\texpected: `{\"values\":[\"item1\",123,true,null]}`,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tnode := tt.build()\n\t\t\tvalue, err := Marshal(node)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t\tif string(value) != tt.expected {\n\t\t\t\tt.Errorf(\"expected %s, got %s\", tt.expected, string(value))\n\t\t\t}\n\t\t})\n\t}\n}\n"},{"name":"decode.gno","body":"// ref: https://github.com/spyzhov/ajson/blob/master/decode.go\n\npackage json\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// This limits the max nesting depth to prevent stack overflow.\n// This is permitted by https://tools.ietf.org/html/rfc7159#section-9\nconst maxNestingDepth = 10000\n\n// Unmarshal parses the JSON-encoded data and returns a Node.\n// The data must be a valid JSON-encoded value.\n//\n// Usage:\n//\n//\tnode, err := json.Unmarshal([]byte(`{\"key\": \"value\"}`))\n//\tif err != nil {\n//\t\tufmt.Println(err)\n//\t}\n//\tprintln(node) // {\"key\": \"value\"}\nfunc Unmarshal(data []byte) (*Node, error) {\n\tbuf := newBuffer(data)\n\n\tvar (\n\t\tstate States\n\t\tkey *string\n\t\tcurrent *Node\n\t\tnesting int\n\t\tuseKey = func() **string {\n\t\t\ttmp := cptrs(key)\n\t\t\tkey = nil\n\t\t\treturn \u0026tmp\n\t\t}\n\t\terr error\n\t)\n\n\tif _, err = buf.first(); err != nil {\n\t\treturn nil, io.EOF\n\t}\n\n\tfor {\n\t\tstate = buf.getState()\n\t\tif state == __ {\n\t\t\treturn nil, unexpectedTokenError(buf.data, buf.index)\n\t\t}\n\n\t\t// region state machine\n\t\tif state \u003e= GO {\n\t\t\tswitch buf.state {\n\t\t\tcase ST: // string\n\t\t\t\tif current != nil \u0026\u0026 current.IsObject() \u0026\u0026 key == nil {\n\t\t\t\t\t// key detected\n\t\t\t\t\tif key, err = getString(buf); err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\n\t\t\t\t\tbuf.state = CO\n\t\t\t\t} else {\n\t\t\t\t\tcurrent, nesting, err = createNestedNode(current, buf, String, nesting, useKey())\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\n\t\t\t\t\terr = buf.string(doubleQuote, false)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\n\t\t\t\t\tcurrent, nesting = updateNode(current, buf, nesting, true)\n\t\t\t\t\tbuf.state = OK\n\t\t\t\t}\n\n\t\t\tcase MI, ZE, IN: // number\n\t\t\t\tcurrent, err = processNumericNode(current, buf, useKey())\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\tcase T1, F1: // boolean\n\t\t\t\tliteral := falseLiteral\n\t\t\t\tif buf.state == T1 {\n\t\t\t\t\tliteral = trueLiteral\n\t\t\t\t}\n\n\t\t\t\tcurrent, nesting, err = processLiteralNode(current, buf, Boolean, literal, useKey(), nesting)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\tcase N1: // null\n\t\t\t\tcurrent, nesting, err = processLiteralNode(current, buf, Null, nullLiteral, useKey(), nesting)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t// region action\n\t\t\tswitch state {\n\t\t\tcase ec, cc: // \u003cempty\u003e }\n\t\t\t\tif key != nil {\n\t\t\t\t\treturn nil, unexpectedTokenError(buf.data, buf.index)\n\t\t\t\t}\n\n\t\t\t\tcurrent, nesting, err = updateNodeAndSetBufferState(current, buf, nesting, Object)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\tcase bc: // ]\n\t\t\t\tcurrent, nesting, err = updateNodeAndSetBufferState(current, buf, nesting, Array)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\tcase co, bo: // { [\n\t\t\t\tvalTyp, bState := Object, OB\n\t\t\t\tif state == bo {\n\t\t\t\t\tvalTyp, bState = Array, AR\n\t\t\t\t}\n\n\t\t\t\tcurrent, nesting, err = createNestedNode(current, buf, valTyp, nesting, useKey())\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\t\tbuf.state = bState\n\n\t\t\tcase cm: // ,\n\t\t\t\tif current == nil {\n\t\t\t\t\treturn nil, unexpectedTokenError(buf.data, buf.index)\n\t\t\t\t}\n\n\t\t\t\tif !current.isContainer() {\n\t\t\t\t\treturn nil, unexpectedTokenError(buf.data, buf.index)\n\t\t\t\t}\n\n\t\t\t\tif current.IsObject() {\n\t\t\t\t\tbuf.state = KE // key expected\n\t\t\t\t} else {\n\t\t\t\t\tbuf.state = VA // value expected\n\t\t\t\t}\n\n\t\t\tcase cl: // :\n\t\t\t\tif current == nil || !current.IsObject() || key == nil {\n\t\t\t\t\treturn nil, unexpectedTokenError(buf.data, buf.index)\n\t\t\t\t}\n\n\t\t\t\tbuf.state = VA\n\n\t\t\tdefault:\n\t\t\t\treturn nil, unexpectedTokenError(buf.data, buf.index)\n\t\t\t}\n\t\t}\n\n\t\tif buf.step() != nil {\n\t\t\tbreak\n\t\t}\n\n\t\tif _, err = buf.first(); err != nil {\n\t\t\terr = nil\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif current == nil || buf.state != OK {\n\t\treturn nil, io.EOF\n\t}\n\n\troot := current.root()\n\tif !root.ready() {\n\t\treturn nil, io.EOF\n\t}\n\n\treturn root, err\n}\n\n// UnmarshalSafe parses the JSON-encoded data and returns a Node.\nfunc UnmarshalSafe(data []byte) (*Node, error) {\n\tvar safe []byte\n\tsafe = append(safe, data...)\n\treturn Unmarshal(safe)\n}\n\n// processNumericNode creates a new node, processes a numeric value,\n// sets the node's borders, and moves to the previous node.\nfunc processNumericNode(current *Node, buf *buffer, key **string) (*Node, error) {\n\tvar err error\n\tcurrent, err = createNode(current, buf, Number, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err = buf.numeric(false); err != nil {\n\t\treturn nil, err\n\t}\n\n\tcurrent.borders[1] = buf.index\n\tif current.prev != nil {\n\t\tcurrent = current.prev\n\t}\n\n\tbuf.index -= 1\n\tbuf.state = OK\n\n\treturn current, nil\n}\n\n// processLiteralNode creates a new node, processes a literal value,\n// sets the node's borders, and moves to the previous node.\nfunc processLiteralNode(\n\tcurrent *Node,\n\tbuf *buffer,\n\tliteralType ValueType,\n\tliteralValue []byte,\n\tuseKey **string,\n\tnesting int,\n) (*Node, int, error) {\n\tvar err error\n\tcurrent, nesting, err = createLiteralNode(current, buf, literalType, literalValue, useKey, nesting)\n\tif err != nil {\n\t\treturn nil, nesting, err\n\t}\n\treturn current, nesting, nil\n}\n\n// isValidContainerType checks if the current node is a valid container (object or array).\n// The container must satisfy the following conditions:\n// 1. The current node must not be nil.\n// 2. The current node must be an object or array.\n// 3. The current node must not be ready.\nfunc isValidContainerType(current *Node, nodeType ValueType) bool {\n\tswitch nodeType {\n\tcase Object:\n\t\treturn current != nil \u0026\u0026 current.IsObject() \u0026\u0026 !current.ready()\n\tcase Array:\n\t\treturn current != nil \u0026\u0026 current.IsArray() \u0026\u0026 !current.ready()\n\tdefault:\n\t\treturn false\n\t}\n}\n\n// getString extracts a string from the buffer and advances the buffer index past the string.\nfunc getString(b *buffer) (*string, error) {\n\tstart := b.index\n\tif err := b.string(doubleQuote, false); err != nil {\n\t\treturn nil, err\n\t}\n\n\tvalue, ok := Unquote(b.data[start:b.index+1], doubleQuote)\n\tif !ok {\n\t\treturn nil, unexpectedTokenError(b.data, start)\n\t}\n\n\treturn \u0026value, nil\n}\n\n// createNode creates a new node and sets the key if it is not nil.\nfunc createNode(\n\tcurrent *Node,\n\tbuf *buffer,\n\tnodeType ValueType,\n\tkey **string,\n) (*Node, error) {\n\tvar err error\n\tcurrent, err = NewNode(current, buf, nodeType, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn current, nil\n}\n\n// createNestedNode creates a new nested node (array or object) and sets the key if it is not nil.\nfunc createNestedNode(\n\tcurrent *Node,\n\tbuf *buffer,\n\tnodeType ValueType,\n\tnesting int,\n\tkey **string,\n) (*Node, int, error) {\n\tvar err error\n\tif nesting, err = checkNestingDepth(nesting); err != nil {\n\t\treturn nil, nesting, err\n\t}\n\n\tif current, err = createNode(current, buf, nodeType, key); err != nil {\n\t\treturn nil, nesting, err\n\t}\n\n\treturn current, nesting, nil\n}\n\n// createLiteralNode creates a new literal node and sets the key if it is not nil.\n// The literal is a byte slice that represents a boolean or null value.\nfunc createLiteralNode(\n\tcurrent *Node,\n\tbuf *buffer,\n\tliteralType ValueType,\n\tliteral []byte,\n\tuseKey **string,\n\tnesting int,\n) (*Node, int, error) {\n\tvar err error\n\tif current, err = createNode(current, buf, literalType, useKey); err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\tif err = buf.word(literal); err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\tcurrent, nesting = updateNode(current, buf, nesting, false)\n\tbuf.state = OK\n\n\treturn current, nesting, nil\n}\n\n// updateNode updates the current node and returns the previous node.\nfunc updateNode(\n\tcurrent *Node, buf *buffer, nesting int, decreaseLevel bool,\n) (*Node, int) {\n\tcurrent.borders[1] = buf.index + 1\n\n\tprev := current.prev\n\tif prev == nil {\n\t\treturn current, nesting\n\t}\n\n\tcurrent = prev\n\tif decreaseLevel {\n\t\tnesting--\n\t}\n\n\treturn current, nesting\n}\n\n// updateNodeAndSetBufferState updates the current node and sets the buffer state to OK.\nfunc updateNodeAndSetBufferState(\n\tcurrent *Node,\n\tbuf *buffer,\n\tnesting int,\n\ttyp ValueType,\n) (*Node, int, error) {\n\tif !isValidContainerType(current, typ) {\n\t\treturn nil, nesting, unexpectedTokenError(buf.data, buf.index)\n\t}\n\n\tcurrent, nesting = updateNode(current, buf, nesting, true)\n\tbuf.state = OK\n\n\treturn current, nesting, nil\n}\n\n// checkNestingDepth checks if the nesting depth is within the maximum allowed depth.\nfunc checkNestingDepth(nesting int) (int, error) {\n\tif nesting \u003e= maxNestingDepth {\n\t\treturn nesting, errors.New(\"maximum nesting depth exceeded\")\n\t}\n\n\treturn nesting + 1, nil\n}\n\nfunc unexpectedTokenError(data []byte, index int) error {\n\treturn ufmt.Errorf(\"unexpected token at index %d. data %b\", index, data)\n}\n"},{"name":"decode_test.gno","body":"package json\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n)\n\ntype testNode struct {\n\tname string\n\tinput []byte\n\tvalue []byte\n\t_type ValueType\n}\n\nfunc simpleValid(test *testNode, t *testing.T) {\n\troot, err := Unmarshal(test.input)\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(%s): %s\", test.input, err.Error())\n\t} else if root == nil {\n\t\tt.Errorf(\"Error on Unmarshal(%s): root is nil\", test.name)\n\t} else if root.nodeType != test._type {\n\t\tt.Errorf(\"Error on Unmarshal(%s): wrong type\", test.name)\n\t} else if !bytes.Equal(root.source(), test.value) {\n\t\tt.Errorf(\"Error on Unmarshal(%s): %s != %s\", test.name, root.source(), test.value)\n\t}\n}\n\nfunc simpleInvalid(test *testNode, t *testing.T) {\n\troot, err := Unmarshal(test.input)\n\tif err == nil {\n\t\tt.Errorf(\"Error on Unmarshal(%s): error expected, got '%s'\", test.name, root.source())\n\t} else if root != nil {\n\t\tt.Errorf(\"Error on Unmarshal(%s): root is not nil\", test.name)\n\t}\n}\n\nfunc simpleCorrupted(name string) *testNode {\n\treturn \u0026testNode{name: name, input: []byte(name)}\n}\n\nfunc TestUnmarshal_StringSimpleSuccess(t *testing.T) {\n\ttests := []*testNode{\n\t\t{name: \"blank\", input: []byte(\"\\\"\\\"\"), _type: String, value: []byte(\"\\\"\\\"\")},\n\t\t{name: \"char\", input: []byte(\"\\\"c\\\"\"), _type: String, value: []byte(\"\\\"c\\\"\")},\n\t\t{name: \"word\", input: []byte(\"\\\"cat\\\"\"), _type: String, value: []byte(\"\\\"cat\\\"\")},\n\t\t{name: \"spaces\", input: []byte(\" \\\"good cat or dog\\\"\\r\\n \"), _type: String, value: []byte(\"\\\"good cat or dog\\\"\")},\n\t\t{name: \"backslash\", input: []byte(\"\\\"good \\\\\\\"cat\\\\\\\"\\\"\"), _type: String, value: []byte(\"\\\"good \\\\\\\"cat\\\\\\\"\\\"\")},\n\t\t{name: \"backslash 2\", input: []byte(\"\\\"good \\\\\\\\\\\\\\\"cat\\\\\\\"\\\"\"), _type: String, value: []byte(\"\\\"good \\\\\\\\\\\\\\\"cat\\\\\\\"\\\"\")},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tsimpleValid(test, t)\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal_NumericSimpleSuccess(t *testing.T) {\n\ttests := []*testNode{\n\t\t{name: \"1\", input: []byte(\"1\"), _type: Number, value: []byte(\"1\")},\n\t\t{name: \"-1\", input: []byte(\"-1\"), _type: Number, value: []byte(\"-1\")},\n\n\t\t{name: \"1234567890\", input: []byte(\"1234567890\"), _type: Number, value: []byte(\"1234567890\")},\n\t\t{name: \"-123\", input: []byte(\"-123\"), _type: Number, value: []byte(\"-123\")},\n\n\t\t{name: \"123.456\", input: []byte(\"123.456\"), _type: Number, value: []byte(\"123.456\")},\n\t\t{name: \"-123.456\", input: []byte(\"-123.456\"), _type: Number, value: []byte(\"-123.456\")},\n\n\t\t{name: \"1e3\", input: []byte(\"1e3\"), _type: Number, value: []byte(\"1e3\")},\n\t\t{name: \"1e+3\", input: []byte(\"1e+3\"), _type: Number, value: []byte(\"1e+3\")},\n\t\t{name: \"1e-3\", input: []byte(\"1e-3\"), _type: Number, value: []byte(\"1e-3\")},\n\t\t{name: \"-1e3\", input: []byte(\"-1e3\"), _type: Number, value: []byte(\"-1e3\")},\n\t\t{name: \"-1e-3\", input: []byte(\"-1e-3\"), _type: Number, value: []byte(\"-1e-3\")},\n\n\t\t{name: \"1.123e3456\", input: []byte(\"1.123e3456\"), _type: Number, value: []byte(\"1.123e3456\")},\n\t\t{name: \"1.123e-3456\", input: []byte(\"1.123e-3456\"), _type: Number, value: []byte(\"1.123e-3456\")},\n\t\t{name: \"-1.123e3456\", input: []byte(\"-1.123e3456\"), _type: Number, value: []byte(\"-1.123e3456\")},\n\t\t{name: \"-1.123e-3456\", input: []byte(\"-1.123e-3456\"), _type: Number, value: []byte(\"-1.123e-3456\")},\n\n\t\t{name: \"1E3\", input: []byte(\"1E3\"), _type: Number, value: []byte(\"1E3\")},\n\t\t{name: \"1E-3\", input: []byte(\"1E-3\"), _type: Number, value: []byte(\"1E-3\")},\n\t\t{name: \"-1E3\", input: []byte(\"-1E3\"), _type: Number, value: []byte(\"-1E3\")},\n\t\t{name: \"-1E-3\", input: []byte(\"-1E-3\"), _type: Number, value: []byte(\"-1E-3\")},\n\n\t\t{name: \"1.123E3456\", input: []byte(\"1.123E3456\"), _type: Number, value: []byte(\"1.123E3456\")},\n\t\t{name: \"1.123E-3456\", input: []byte(\"1.123E-3456\"), _type: Number, value: []byte(\"1.123E-3456\")},\n\t\t{name: \"-1.123E3456\", input: []byte(\"-1.123E3456\"), _type: Number, value: []byte(\"-1.123E3456\")},\n\t\t{name: \"-1.123E-3456\", input: []byte(\"-1.123E-3456\"), _type: Number, value: []byte(\"-1.123E-3456\")},\n\n\t\t{name: \"-1.123E-3456 with spaces\", input: []byte(\" \\r -1.123E-3456 \\t\\n\"), _type: Number, value: []byte(\"-1.123E-3456\")},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\troot, err := Unmarshal(test.input)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Error on Unmarshal(%s): %s\", test.name, err.Error())\n\t\t\t} else if root == nil {\n\t\t\t\tt.Errorf(\"Error on Unmarshal(%s): root is nil\", test.name)\n\t\t\t} else if root.nodeType != test._type {\n\t\t\t\tt.Errorf(\"Error on Unmarshal(%s): wrong type\", test.name)\n\t\t\t} else if !bytes.Equal(root.source(), test.value) {\n\t\t\t\tt.Errorf(\"Error on Unmarshal(%s): %s != %s\", test.name, root.source(), test.value)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal_StringSimpleCorrupted(t *testing.T) {\n\ttests := []*testNode{\n\t\t{name: \"white NL\", input: []byte(\"\\\"foo\\nbar\\\"\")},\n\t\t{name: \"white R\", input: []byte(\"\\\"foo\\rbar\\\"\")},\n\t\t{name: \"white Tab\", input: []byte(\"\\\"foo\\tbar\\\"\")},\n\t\t{name: \"wrong quotes\", input: []byte(\"'cat'\")},\n\t\t{name: \"double string\", input: []byte(\"\\\"Hello\\\" \\\"World\\\"\")},\n\t\t{name: \"quotes in quotes\", input: []byte(\"\\\"good \\\"cat\\\"\\\"\")},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tsimpleInvalid(test, t)\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal_ObjectSimpleSuccess(t *testing.T) {\n\ttests := []*testNode{\n\t\t{name: \"{}\", input: []byte(\"{}\"), _type: Object, value: []byte(\"{}\")},\n\t\t{name: `{ \\r\\n }`, input: []byte(\"{ \\r\\n }\"), _type: Object, value: []byte(\"{ \\r\\n }\")},\n\t\t{name: `{\"key\":1}`, input: []byte(`{\"key\":1}`), _type: Object, value: []byte(`{\"key\":1}`)},\n\t\t{name: `{\"key\":true}`, input: []byte(`{\"key\":true}`), _type: Object, value: []byte(`{\"key\":true}`)},\n\t\t{name: `{\"key\":\"value\"}`, input: []byte(`{\"key\":\"value\"}`), _type: Object, value: []byte(`{\"key\":\"value\"}`)},\n\t\t{name: `{\"foo\":\"bar\",\"baz\":\"foo\"}`, input: []byte(`{\"foo\":\"bar\", \"baz\":\"foo\"}`), _type: Object, value: []byte(`{\"foo\":\"bar\", \"baz\":\"foo\"}`)},\n\t\t{name: \"spaces\", input: []byte(` { \"foo\" : \"bar\" , \"baz\" : \"foo\" } `), _type: Object, value: []byte(`{ \"foo\" : \"bar\" , \"baz\" : \"foo\" }`)},\n\t\t{name: \"nested\", input: []byte(`{\"foo\":{\"bar\":{\"baz\":{}}}}`), _type: Object, value: []byte(`{\"foo\":{\"bar\":{\"baz\":{}}}}`)},\n\t\t{name: \"array\", input: []byte(`{\"array\":[{},{},{\"foo\":[{\"bar\":[\"baz\"]}]}]}`), _type: Object, value: []byte(`{\"array\":[{},{},{\"foo\":[{\"bar\":[\"baz\"]}]}]}`)},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tsimpleValid(test, t)\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal_ObjectSimpleCorrupted(t *testing.T) {\n\ttests := []*testNode{\n\t\tsimpleCorrupted(\"{{{\\\"key\\\": \\\"foo\\\"{{{{\"),\n\t\tsimpleCorrupted(\"}\"),\n\t\tsimpleCorrupted(\"{ }}}}}}}\"),\n\t\tsimpleCorrupted(\" }\"),\n\t\tsimpleCorrupted(\"{,}\"),\n\t\tsimpleCorrupted(\"{:}\"),\n\t\tsimpleCorrupted(\"{100000}\"),\n\t\tsimpleCorrupted(\"{1:1}\"),\n\t\tsimpleCorrupted(\"{'1:2,3:4'}\"),\n\t\tsimpleCorrupted(`{\"d\"}`),\n\t\tsimpleCorrupted(`{\"foo\"}`),\n\t\tsimpleCorrupted(`{\"foo\":}`),\n\t\tsimpleCorrupted(`{:\"foo\"}`),\n\t\tsimpleCorrupted(`{\"foo\":bar}`),\n\t\tsimpleCorrupted(`{\"foo\":\"bar\",}`),\n\t\tsimpleCorrupted(`{}{}`),\n\t\tsimpleCorrupted(`{},{}`),\n\t\tsimpleCorrupted(`{[},{]}`),\n\t\tsimpleCorrupted(`{[,]}`),\n\t\tsimpleCorrupted(`{[]}`),\n\t\tsimpleCorrupted(`{}1`),\n\t\tsimpleCorrupted(`1{}`),\n\t\tsimpleCorrupted(`{\"x\"::1}`),\n\t\tsimpleCorrupted(`{null:null}`),\n\t\tsimpleCorrupted(`{\"foo:\"bar\"}`),\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tsimpleInvalid(test, t)\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal_NullSimpleCorrupted(t *testing.T) {\n\ttests := []*testNode{\n\t\t{name: \"nul\", input: []byte(\"nul\")},\n\t\t{name: \"nil\", input: []byte(\"nil\")},\n\t\t{name: \"nill\", input: []byte(\"nill\")},\n\t\t{name: \"NILL\", input: []byte(\"NILL\")},\n\t\t{name: \"Null\", input: []byte(\"Null\")},\n\t\t{name: \"NULL\", input: []byte(\"NULL\")},\n\t\t{name: \"spaces\", input: []byte(\"Nu ll\")},\n\t\t{name: \"null1\", input: []byte(\"null1\")},\n\t\t{name: \"double\", input: []byte(\"null null\")},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tsimpleInvalid(test, t)\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal_BoolSimpleSuccess(t *testing.T) {\n\ttests := []*testNode{\n\t\t{name: \"lower true\", input: []byte(\"true\"), _type: Boolean, value: []byte(\"true\")},\n\t\t{name: \"lower false\", input: []byte(\"false\"), _type: Boolean, value: []byte(\"false\")},\n\t\t{name: \"spaces true\", input: []byte(\" true\\r\\n \"), _type: Boolean, value: []byte(\"true\")},\n\t\t{name: \"spaces false\", input: []byte(\" false\\r\\n \"), _type: Boolean, value: []byte(\"false\")},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tsimpleValid(test, t)\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal_BoolSimpleCorrupted(t *testing.T) {\n\ttests := []*testNode{\n\t\tsimpleCorrupted(\"tru\"),\n\t\tsimpleCorrupted(\"fals\"),\n\t\tsimpleCorrupted(\"tre\"),\n\t\tsimpleCorrupted(\"fal se\"),\n\t\tsimpleCorrupted(\"true false\"),\n\t\tsimpleCorrupted(\"True\"),\n\t\tsimpleCorrupted(\"TRUE\"),\n\t\tsimpleCorrupted(\"False\"),\n\t\tsimpleCorrupted(\"FALSE\"),\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tsimpleInvalid(test, t)\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal_ArraySimpleSuccess(t *testing.T) {\n\ttests := []*testNode{\n\t\t{name: \"[]\", input: []byte(\"[]\"), _type: Array, value: []byte(\"[]\")},\n\t\t{name: \"[1]\", input: []byte(\"[1]\"), _type: Array, value: []byte(\"[1]\")},\n\t\t{name: \"[1,2,3]\", input: []byte(\"[1,2,3]\"), _type: Array, value: []byte(\"[1,2,3]\")},\n\t\t{name: \"[1, 2, 3]\", input: []byte(\"[1, 2, 3]\"), _type: Array, value: []byte(\"[1, 2, 3]\")},\n\t\t{name: \"[1,[2],3]\", input: []byte(\"[1,[2],3]\"), _type: Array, value: []byte(\"[1,[2],3]\")},\n\t\t{name: \"[[],[],[]]\", input: []byte(\"[[],[],[]]\"), _type: Array, value: []byte(\"[[],[],[]]\")},\n\t\t{name: \"[[[[[]]]]]\", input: []byte(\"[[[[[]]]]]\"), _type: Array, value: []byte(\"[[[[[]]]]]\")},\n\t\t{name: \"[true,null,1,\\\"foo\\\",[]]\", input: []byte(\"[true,null,1,\\\"foo\\\",[]]\"), _type: Array, value: []byte(\"[true,null,1,\\\"foo\\\",[]]\")},\n\t\t{name: \"spaces\", input: []byte(\"\\n\\r [\\n1\\n ]\\r\\n\"), _type: Array, value: []byte(\"[\\n1\\n ]\")},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tsimpleValid(test, t)\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal_ArraySimpleCorrupted(t *testing.T) {\n\ttests := []*testNode{\n\t\tsimpleCorrupted(\"[,]\"),\n\t\tsimpleCorrupted(\"[]\\\\\"),\n\t\tsimpleCorrupted(\"[1,]\"),\n\t\tsimpleCorrupted(\"[[]\"),\n\t\tsimpleCorrupted(\"[]]\"),\n\t\tsimpleCorrupted(\"1[]\"),\n\t\tsimpleCorrupted(\"[]1\"),\n\t\tsimpleCorrupted(\"[[]1]\"),\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tsimpleInvalid(test, t)\n\t\t})\n\t}\n}\n\n// Examples from https://json.org/example.html\nfunc TestUnmarshal(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tvalue string\n\t}{\n\t\t{\n\t\t\tname: \"glossary\",\n\t\t\tvalue: `{\n\t\t\t\t\"glossary\": {\n\t\t\t\t\t\"title\": \"example glossary\",\n\t\t\t\t\t\"GlossDiv\": {\n\t\t\t\t\t\t\"title\": \"S\",\n\t\t\t\t\t\t\"GlossList\": {\n\t\t\t\t\t\t\t\"GlossEntry\": {\n\t\t\t\t\t\t\t\t\"ID\": \"SGML\",\n\t\t\t\t\t\t\t\t\"SortAs\": \"SGML\",\n\t\t\t\t\t\t\t\t\"GlossTerm\": \"Standard Generalized Markup Language\",\n\t\t\t\t\t\t\t\t\"Acronym\": \"SGML\",\n\t\t\t\t\t\t\t\t\"Abbrev\": \"ISO 8879:1986\",\n\t\t\t\t\t\t\t\t\"GlossDef\": {\n\t\t\t\t\t\t\t\t\t\"para\": \"A meta-markup language, used to create markup languages such as DocBook.\",\n\t\t\t\t\t\t\t\t\t\"GlossSeeAlso\": [\"GML\", \"XML\"]\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"GlossSee\": \"markup\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}`,\n\t\t},\n\t\t{\n\t\t\tname: \"menu\",\n\t\t\tvalue: `{\"menu\": {\n\t\t\t\t\"id\": \"file\",\n\t\t\t\t\"value\": \"File\",\n\t\t\t\t\"popup\": {\n\t\t\t\t \"menuitem\": [\n\t\t\t\t\t{\"value\": \"New\", \"onclick\": \"CreateNewDoc()\"},\n\t\t\t\t\t{\"value\": \"Open\", \"onclick\": \"OpenDoc()\"},\n\t\t\t\t\t{\"value\": \"Close\", \"onclick\": \"CloseDoc()\"}\n\t\t\t\t ]\n\t\t\t\t}\n\t\t\t}}`,\n\t\t},\n\t\t{\n\t\t\tname: \"widget\",\n\t\t\tvalue: `{\"widget\": {\n\t\t\t\t\"debug\": \"on\",\n\t\t\t\t\"window\": {\n\t\t\t\t\t\"title\": \"Sample Konfabulator Widget\",\n\t\t\t\t\t\"name\": \"main_window\",\n\t\t\t\t\t\"width\": 500,\n\t\t\t\t\t\"height\": 500\n\t\t\t\t},\n\t\t\t\t\"image\": { \n\t\t\t\t\t\"src\": \"Images/Sun.png\",\n\t\t\t\t\t\"name\": \"sun1\",\n\t\t\t\t\t\"hOffset\": 250,\n\t\t\t\t\t\"vOffset\": 250,\n\t\t\t\t\t\"alignment\": \"center\"\n\t\t\t\t},\n\t\t\t\t\"text\": {\n\t\t\t\t\t\"data\": \"Click Here\",\n\t\t\t\t\t\"size\": 36,\n\t\t\t\t\t\"style\": \"bold\",\n\t\t\t\t\t\"name\": \"text1\",\n\t\t\t\t\t\"hOffset\": 250,\n\t\t\t\t\t\"vOffset\": 100,\n\t\t\t\t\t\"alignment\": \"center\",\n\t\t\t\t\t\"onMouseUp\": \"sun1.opacity = (sun1.opacity / 100) * 90;\"\n\t\t\t\t}\n\t\t\t}} `,\n\t\t},\n\t\t{\n\t\t\tname: \"web-app\",\n\t\t\tvalue: `{\"web-app\": {\n\t\t\t\t\"servlet\": [ \n\t\t\t\t {\n\t\t\t\t\t\"servlet-name\": \"cofaxCDS\",\n\t\t\t\t\t\"servlet-class\": \"org.cofax.cds.CDSServlet\",\n\t\t\t\t\t\"init-param\": {\n\t\t\t\t\t \"configGlossary:installationAt\": \"Philadelphia, PA\",\n\t\t\t\t\t \"configGlossary:adminEmail\": \"ksm@pobox.com\",\n\t\t\t\t\t \"configGlossary:poweredBy\": \"Cofax\",\n\t\t\t\t\t \"configGlossary:poweredByIcon\": \"/images/cofax.gif\",\n\t\t\t\t\t \"configGlossary:staticPath\": \"/content/static\",\n\t\t\t\t\t \"templateProcessorClass\": \"org.cofax.WysiwygTemplate\",\n\t\t\t\t\t \"templateLoaderClass\": \"org.cofax.FilesTemplateLoader\",\n\t\t\t\t\t \"templatePath\": \"templates\",\n\t\t\t\t\t \"templateOverridePath\": \"\",\n\t\t\t\t\t \"defaultListTemplate\": \"listTemplate.htm\",\n\t\t\t\t\t \"defaultFileTemplate\": \"articleTemplate.htm\",\n\t\t\t\t\t \"useJSP\": false,\n\t\t\t\t\t \"jspListTemplate\": \"listTemplate.jsp\",\n\t\t\t\t\t \"jspFileTemplate\": \"articleTemplate.jsp\",\n\t\t\t\t\t \"cachePackageTagsTrack\": 200,\n\t\t\t\t\t \"cachePackageTagsStore\": 200,\n\t\t\t\t\t \"cachePackageTagsRefresh\": 60,\n\t\t\t\t\t \"cacheTemplatesTrack\": 100,\n\t\t\t\t\t \"cacheTemplatesStore\": 50,\n\t\t\t\t\t \"cacheTemplatesRefresh\": 15,\n\t\t\t\t\t \"cachePagesTrack\": 200,\n\t\t\t\t\t \"cachePagesStore\": 100,\n\t\t\t\t\t \"cachePagesRefresh\": 10,\n\t\t\t\t\t \"cachePagesDirtyRead\": 10,\n\t\t\t\t\t \"searchEngineListTemplate\": \"forSearchEnginesList.htm\",\n\t\t\t\t\t \"searchEngineFileTemplate\": \"forSearchEngines.htm\",\n\t\t\t\t\t \"searchEngineRobotsDb\": \"WEB-INF/robots.db\",\n\t\t\t\t\t \"useDataStore\": true,\n\t\t\t\t\t \"dataStoreClass\": \"org.cofax.SqlDataStore\",\n\t\t\t\t\t \"redirectionClass\": \"org.cofax.SqlRedirection\",\n\t\t\t\t\t \"dataStoreName\": \"cofax\",\n\t\t\t\t\t \"dataStoreDriver\": \"com.microsoft.jdbc.sqlserver.SQLServerDriver\",\n\t\t\t\t\t \"dataStoreUrl\": \"jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon\",\n\t\t\t\t\t \"dataStoreUser\": \"sa\",\n\t\t\t\t\t \"dataStorePassword\": \"dataStoreTestQuery\",\n\t\t\t\t\t \"dataStoreTestQuery\": \"SET NOCOUNT ON;select test='test';\",\n\t\t\t\t\t \"dataStoreLogFile\": \"/usr/local/tomcat/logs/datastore.log\",\n\t\t\t\t\t \"dataStoreInitConns\": 10,\n\t\t\t\t\t \"dataStoreMaxConns\": 100,\n\t\t\t\t\t \"dataStoreConnUsageLimit\": 100,\n\t\t\t\t\t \"dataStoreLogLevel\": \"debug\",\n\t\t\t\t\t \"maxUrlLength\": 500}},\n\t\t\t\t {\n\t\t\t\t\t\"servlet-name\": \"cofaxEmail\",\n\t\t\t\t\t\"servlet-class\": \"org.cofax.cds.EmailServlet\",\n\t\t\t\t\t\"init-param\": {\n\t\t\t\t\t\"mailHost\": \"mail1\",\n\t\t\t\t\t\"mailHostOverride\": \"mail2\"}},\n\t\t\t\t {\n\t\t\t\t\t\"servlet-name\": \"cofaxAdmin\",\n\t\t\t\t\t\"servlet-class\": \"org.cofax.cds.AdminServlet\"},\n\t\t\t \n\t\t\t\t {\n\t\t\t\t\t\"servlet-name\": \"fileServlet\",\n\t\t\t\t\t\"servlet-class\": \"org.cofax.cds.FileServlet\"},\n\t\t\t\t {\n\t\t\t\t\t\"servlet-name\": \"cofaxTools\",\n\t\t\t\t\t\"servlet-class\": \"org.cofax.cms.CofaxToolsServlet\",\n\t\t\t\t\t\"init-param\": {\n\t\t\t\t\t \"templatePath\": \"toolstemplates/\",\n\t\t\t\t\t \"log\": 1,\n\t\t\t\t\t \"logLocation\": \"/usr/local/tomcat/logs/CofaxTools.log\",\n\t\t\t\t\t \"logMaxSize\": \"\",\n\t\t\t\t\t \"dataLog\": 1,\n\t\t\t\t\t \"dataLogLocation\": \"/usr/local/tomcat/logs/dataLog.log\",\n\t\t\t\t\t \"dataLogMaxSize\": \"\",\n\t\t\t\t\t \"removePageCache\": \"/content/admin/remove?cache=pages\u0026id=\",\n\t\t\t\t\t \"removeTemplateCache\": \"/content/admin/remove?cache=templates\u0026id=\",\n\t\t\t\t\t \"fileTransferFolder\": \"/usr/local/tomcat/webapps/content/fileTransferFolder\",\n\t\t\t\t\t \"lookInContext\": 1,\n\t\t\t\t\t \"adminGroupID\": 4,\n\t\t\t\t\t \"betaServer\": true}}],\n\t\t\t\t\"servlet-mapping\": {\n\t\t\t\t \"cofaxCDS\": \"/\",\n\t\t\t\t \"cofaxEmail\": \"/cofaxutil/aemail/*\",\n\t\t\t\t \"cofaxAdmin\": \"/admin/*\",\n\t\t\t\t \"fileServlet\": \"/static/*\",\n\t\t\t\t \"cofaxTools\": \"/tools/*\"},\n\t\t\t \n\t\t\t\t\"taglib\": {\n\t\t\t\t \"taglib-uri\": \"cofax.tld\",\n\t\t\t\t \"taglib-location\": \"/WEB-INF/tlds/cofax.tld\"}}}`,\n\t\t},\n\t\t{\n\t\t\tname: \"SVG Viewer\",\n\t\t\tvalue: `{\"menu\": {\n\t\t\t\t\"header\": \"SVG Viewer\",\n\t\t\t\t\"items\": [\n\t\t\t\t\t{\"id\": \"Open\"},\n\t\t\t\t\t{\"id\": \"OpenNew\", \"label\": \"Open New\"},\n\t\t\t\t\tnull,\n\t\t\t\t\t{\"id\": \"ZoomIn\", \"label\": \"Zoom In\"},\n\t\t\t\t\t{\"id\": \"ZoomOut\", \"label\": \"Zoom Out\"},\n\t\t\t\t\t{\"id\": \"OriginalView\", \"label\": \"Original View\"},\n\t\t\t\t\tnull,\n\t\t\t\t\t{\"id\": \"Quality\"},\n\t\t\t\t\t{\"id\": \"Pause\"},\n\t\t\t\t\t{\"id\": \"Mute\"},\n\t\t\t\t\tnull,\n\t\t\t\t\t{\"id\": \"Find\", \"label\": \"Find...\"},\n\t\t\t\t\t{\"id\": \"FindAgain\", \"label\": \"Find Again\"},\n\t\t\t\t\t{\"id\": \"Copy\"},\n\t\t\t\t\t{\"id\": \"CopyAgain\", \"label\": \"Copy Again\"},\n\t\t\t\t\t{\"id\": \"CopySVG\", \"label\": \"Copy SVG\"},\n\t\t\t\t\t{\"id\": \"ViewSVG\", \"label\": \"View SVG\"},\n\t\t\t\t\t{\"id\": \"ViewSource\", \"label\": \"View Source\"},\n\t\t\t\t\t{\"id\": \"SaveAs\", \"label\": \"Save As\"},\n\t\t\t\t\tnull,\n\t\t\t\t\t{\"id\": \"Help\"},\n\t\t\t\t\t{\"id\": \"About\", \"label\": \"About Adobe CVG Viewer...\"}\n\t\t\t\t]\n\t\t\t}}`,\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\t_, err := Unmarshal([]byte(test.value))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Error on Unmarshal: %s\", err.Error())\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUnmarshalSafe(t *testing.T) {\n\tjson := []byte(`{ \"store\": {\n\t\t\"book\": [ \n\t\t { \"category\": \"reference\",\n\t\t\t\"author\": \"Nigel Rees\",\n\t\t\t\"title\": \"Sayings of the Century\",\n\t\t\t\"price\": 8.95\n\t\t },\n\t\t { \"category\": \"fiction\",\n\t\t\t\"author\": \"Evelyn Waugh\",\n\t\t\t\"title\": \"Sword of Honour\",\n\t\t\t\"price\": 12.99\n\t\t },\n\t\t { \"category\": \"fiction\",\n\t\t\t\"author\": \"Herman Melville\",\n\t\t\t\"title\": \"Moby Dick\",\n\t\t\t\"isbn\": \"0-553-21311-3\",\n\t\t\t\"price\": 8.99\n\t\t },\n\t\t { \"category\": \"fiction\",\n\t\t\t\"author\": \"J. R. R. Tolkien\",\n\t\t\t\"title\": \"The Lord of the Rings\",\n\t\t\t\"isbn\": \"0-395-19395-8\",\n\t\t\t\"price\": 22.99\n\t\t }\n\t\t],\n\t\t\"bicycle\": {\n\t\t \"color\": \"red\",\n\t\t \"price\": 19.95\n\t\t}\n\t }\n\t}`)\n\tsafe, err := UnmarshalSafe(json)\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal: %s\", err.Error())\n\t} else if safe == nil {\n\t\tt.Errorf(\"Error on Unmarshal: safe is nil\")\n\t} else {\n\t\troot, err := Unmarshal(json)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"Error on Unmarshal: %s\", err.Error())\n\t\t} else if root == nil {\n\t\t\tt.Errorf(\"Error on Unmarshal: root is nil\")\n\t\t} else if !bytes.Equal(root.source(), safe.source()) {\n\t\t\tt.Errorf(\"Error on UnmarshalSafe: values not same\")\n\t\t}\n\t}\n}\n\n// BenchmarkGoStdUnmarshal-8 \t 61698\t 19350 ns/op\t 288 B/op\t 6 allocs/op\n// BenchmarkUnmarshal-8 \t 45620\t 26165 ns/op\t 21889 B/op\t 367 allocs/op\n//\n// type bench struct {\n// \tName string `json:\"name\"`\n// \tValue int `json:\"value\"`\n// }\n\n// func BenchmarkGoStdUnmarshal(b *testing.B) {\n// \tdata := []byte(webApp)\n// \tfor i := 0; i \u003c b.N; i++ {\n// \t\terr := json.Unmarshal(data, \u0026bench{})\n// \t\tif err != nil {\n// \t\t\tb.Fatal(err)\n// \t\t}\n// \t}\n// }\n\n// func BenchmarkUnmarshal(b *testing.B) {\n// \tdata := []byte(webApp)\n// \tfor i := 0; i \u003c b.N; i++ {\n// \t\t_, err := Unmarshal(data)\n// \t\tif err != nil {\n// \t\t\tb.Fatal(err)\n// \t\t}\n// \t}\n// }\n"},{"name":"encode.gno","body":"package json\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Marshal returns the JSON encoding of a Node.\nfunc Marshal(node *Node) ([]byte, error) {\n\tvar (\n\t\tbuf bytes.Buffer\n\t\tsVal string\n\t\tbVal bool\n\t\tnVal float64\n\t\toVal []byte\n\t\terr error\n\t)\n\n\tif node == nil {\n\t\treturn nil, errors.New(\"node is nil\")\n\t}\n\n\tif !node.modified \u0026\u0026 !node.ready() {\n\t\treturn nil, errors.New(\"node is not ready\")\n\t}\n\n\tif !node.modified \u0026\u0026 node.ready() {\n\t\tbuf.Write(node.source())\n\t}\n\n\tif node.modified {\n\t\tswitch node.nodeType {\n\t\tcase Null:\n\t\t\tbuf.Write(nullLiteral)\n\n\t\tcase Number:\n\t\t\tnVal, err = node.GetNumeric()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tnum := strconv.FormatFloat(nVal, 'f', -1, 64)\n\t\t\tbuf.WriteString(num)\n\n\t\tcase String:\n\t\t\tsVal, err = node.GetString()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tquoted := ufmt.Sprintf(\"%s\", strconv.Quote(sVal))\n\t\t\tbuf.WriteString(quoted)\n\n\t\tcase Boolean:\n\t\t\tbVal, err = node.GetBool()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tbStr := ufmt.Sprintf(\"%t\", bVal)\n\t\t\tbuf.WriteString(bStr)\n\n\t\tcase Array:\n\t\t\tbuf.WriteByte(bracketOpen)\n\n\t\t\tfor i := 0; i \u003c len(node.next); i++ {\n\t\t\t\tif i != 0 {\n\t\t\t\t\tbuf.WriteByte(comma)\n\t\t\t\t}\n\n\t\t\t\telem, ok := node.next[strconv.Itoa(i)]\n\t\t\t\tif !ok {\n\t\t\t\t\treturn nil, ufmt.Errorf(\"array element %d is not found\", i)\n\t\t\t\t}\n\n\t\t\t\toVal, err = Marshal(elem)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\t\tbuf.Write(oVal)\n\t\t\t}\n\n\t\t\tbuf.WriteByte(bracketClose)\n\n\t\tcase Object:\n\t\t\tbuf.WriteByte(curlyOpen)\n\n\t\t\tbVal = false\n\t\t\tfor k, v := range node.next {\n\t\t\t\tif bVal {\n\t\t\t\t\tbuf.WriteByte(comma)\n\t\t\t\t} else {\n\t\t\t\t\tbVal = true\n\t\t\t\t}\n\n\t\t\t\tkey := ufmt.Sprintf(\"%s\", strconv.Quote(k))\n\t\t\t\tbuf.WriteString(key)\n\t\t\t\tbuf.WriteByte(colon)\n\n\t\t\t\toVal, err = Marshal(v)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\t\tbuf.Write(oVal)\n\t\t\t}\n\n\t\t\tbuf.WriteByte(curlyClose)\n\t\t}\n\t}\n\n\treturn buf.Bytes(), nil\n}\n"},{"name":"encode_test.gno","body":"package json\n\nimport \"testing\"\n\nfunc TestMarshal_Primitive(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tnode *Node\n\t}{\n\t\t{\n\t\t\tname: \"null\",\n\t\t\tnode: NullNode(\"\"),\n\t\t},\n\t\t{\n\t\t\tname: \"true\",\n\t\t\tnode: BoolNode(\"\", true),\n\t\t},\n\t\t{\n\t\t\tname: \"false\",\n\t\t\tnode: BoolNode(\"\", false),\n\t\t},\n\t\t{\n\t\t\tname: `\"string\"`,\n\t\t\tnode: StringNode(\"\", \"string\"),\n\t\t},\n\t\t{\n\t\t\tname: `\"one \\\"encoded\\\" string\"`,\n\t\t\tnode: StringNode(\"\", `one \"encoded\" string`),\n\t\t},\n\t\t{\n\t\t\tname: `{\"foo\":\"bar\"}`,\n\t\t\tnode: ObjectNode(\"\", map[string]*Node{\n\t\t\t\t\"foo\": StringNode(\"foo\", \"bar\"),\n\t\t\t}),\n\t\t},\n\t\t{\n\t\t\tname: \"42\",\n\t\t\tnode: NumberNode(\"\", 42),\n\t\t},\n\t\t{\n\t\t\tname: \"3.14\",\n\t\t\tnode: NumberNode(\"\", 3.14),\n\t\t},\n\t\t{\n\t\t\tname: `[1,2,3]`,\n\t\t\tnode: ArrayNode(\"\", []*Node{\n\t\t\t\tNumberNode(\"0\", 1),\n\t\t\t\tNumberNode(\"2\", 2),\n\t\t\t\tNumberNode(\"3\", 3),\n\t\t\t}),\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tvalue, err := Marshal(test.node)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t} else if string(value) != test.name {\n\t\t\t\tt.Errorf(\"wrong result: '%s', expected '%s'\", value, test.name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestMarshal_Object(t *testing.T) {\n\tnode := ObjectNode(\"\", map[string]*Node{\n\t\t\"foo\": StringNode(\"foo\", \"bar\"),\n\t\t\"baz\": NumberNode(\"baz\", 100500),\n\t\t\"qux\": NullNode(\"qux\"),\n\t})\n\n\tmustKey := []string{\"foo\", \"baz\", \"qux\"}\n\n\tvalue, err := Marshal(node)\n\tif err != nil {\n\t\tt.Errorf(\"unexpected error: %s\", err)\n\t}\n\n\t// the order of keys in the map is not guaranteed\n\t// so we need to unmarshal the result and check the keys\n\tdecoded, err := Unmarshal(value)\n\tif err != nil {\n\t\tt.Errorf(\"unexpected error: %s\", err)\n\t}\n\n\tfor _, key := range mustKey {\n\t\tif node, err := decoded.GetKey(key); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t} else {\n\t\t\tif node == nil {\n\t\t\t\tt.Errorf(\"node is nil\")\n\t\t\t} else if node.key == nil {\n\t\t\t\tt.Errorf(\"key is nil\")\n\t\t\t} else if *node.key != key {\n\t\t\t\tt.Errorf(\"wrong key: '%s', expected '%s'\", *node.key, key)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc valueNode(prev *Node, key string, typ ValueType, val interface{}) *Node {\n\tcurr := \u0026Node{\n\t\tprev: prev,\n\t\tdata: nil,\n\t\tkey: \u0026key,\n\t\tborders: [2]int{0, 0},\n\t\tvalue: val,\n\t\tmodified: true,\n\t}\n\n\tif val != nil {\n\t\tcurr.nodeType = typ\n\t}\n\n\treturn curr\n}\n\nfunc TestMarshal_Errors(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tnode func() (node *Node)\n\t}{\n\t\t{\n\t\t\tname: \"nil\",\n\t\t\tnode: func() (node *Node) {\n\t\t\t\treturn\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"broken\",\n\t\t\tnode: func() (node *Node) {\n\t\t\t\tnode = Must(Unmarshal([]byte(`{}`)))\n\t\t\t\tnode.borders[1] = 0\n\t\t\t\treturn\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Numeric\",\n\t\t\tnode: func() (node *Node) {\n\t\t\t\treturn valueNode(nil, \"\", Number, false)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"String\",\n\t\t\tnode: func() (node *Node) {\n\t\t\t\treturn valueNode(nil, \"\", String, false)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Bool\",\n\t\t\tnode: func() (node *Node) {\n\t\t\t\treturn valueNode(nil, \"\", Boolean, 1)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Array_1\",\n\t\t\tnode: func() (node *Node) {\n\t\t\t\tnode = ArrayNode(\"\", nil)\n\t\t\t\tnode.next[\"1\"] = NullNode(\"1\")\n\t\t\t\treturn\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Array_2\",\n\t\t\tnode: func() (node *Node) {\n\t\t\t\treturn ArrayNode(\"\", []*Node{valueNode(nil, \"\", Boolean, 1)})\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Object\",\n\t\t\tnode: func() (node *Node) {\n\t\t\t\treturn ObjectNode(\"\", map[string]*Node{\"key\": valueNode(nil, \"key\", Boolean, 1)})\n\t\t\t},\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tvalue, err := Marshal(test.node())\n\t\t\tif err == nil {\n\t\t\t\tt.Errorf(\"expected error\")\n\t\t\t} else if len(value) != 0 {\n\t\t\t\tt.Errorf(\"wrong result\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestMarshal_Nil(t *testing.T) {\n\t_, err := Marshal(nil)\n\tif err == nil {\n\t\tt.Error(\"Expected error for nil node, but got nil\")\n\t}\n}\n\nfunc TestMarshal_NotModified(t *testing.T) {\n\tnode := \u0026Node{}\n\t_, err := Marshal(node)\n\tif err == nil {\n\t\tt.Error(\"Expected error for not modified node, but got nil\")\n\t}\n}\n\nfunc TestMarshalCycleReference(t *testing.T) {\n\tnode1 := \u0026Node{\n\t\tkey: stringPtr(\"node1\"),\n\t\tnodeType: String,\n\t\tnext: map[string]*Node{\n\t\t\t\"next\": nil,\n\t\t},\n\t}\n\n\tnode2 := \u0026Node{\n\t\tkey: stringPtr(\"node2\"),\n\t\tnodeType: String,\n\t\tprev: node1,\n\t}\n\n\tnode1.next[\"next\"] = node2\n\n\t_, err := Marshal(node1)\n\tif err == nil {\n\t\tt.Error(\"Expected error for cycle reference, but got nil\")\n\t}\n}\n\nfunc TestMarshalNoCycleReference(t *testing.T) {\n\tnode1 := \u0026Node{\n\t\tkey: stringPtr(\"node1\"),\n\t\tnodeType: String,\n\t\tvalue: \"value1\",\n\t\tmodified: true,\n\t}\n\n\tnode2 := \u0026Node{\n\t\tkey: stringPtr(\"node2\"),\n\t\tnodeType: String,\n\t\tvalue: \"value2\",\n\t\tmodified: true,\n\t}\n\n\t_, err := Marshal(node1)\n\tif err != nil {\n\t\tt.Errorf(\"Unexpected error: %v\", err)\n\t}\n\n\t_, err = Marshal(node2)\n\tif err != nil {\n\t\tt.Errorf(\"Unexpected error: %v\", err)\n\t}\n}\n\nfunc stringPtr(s string) *string {\n\treturn \u0026s\n}\n"},{"name":"errors.gno","body":"package json\n\nimport \"errors\"\n\nvar (\n\terrNilNode = errors.New(\"node is nil\")\n\terrNotArrayNode = errors.New(\"node is not array\")\n\terrNotBoolNode = errors.New(\"node is not boolean\")\n\terrNotNullNode = errors.New(\"node is not null\")\n\terrNotNumberNode = errors.New(\"node is not number\")\n\terrNotObjectNode = errors.New(\"node is not object\")\n\terrNotStringNode = errors.New(\"node is not string\")\n\terrInvalidToken = errors.New(\"invalid token\")\n\terrIndexNotFound = errors.New(\"index not found\")\n\terrInvalidAppend = errors.New(\"can't append value to non-appendable node\")\n\terrInvalidAppendCycle = errors.New(\"appending value to itself or its children or parents will cause a cycle\")\n\terrInvalidEscapeSequence = errors.New(\"invalid escape sequence\")\n\terrInvalidStringValue = errors.New(\"invalid string value\")\n\terrEmptyBooleanNode = errors.New(\"boolean node is empty\")\n\terrEmptyStringNode = errors.New(\"string node is empty\")\n\terrKeyRequired = errors.New(\"key is required for object\")\n\terrUnmatchedParenthesis = errors.New(\"mismatched bracket or parenthesis\")\n\terrUnmatchedQuotePath = errors.New(\"unmatched quote in path\")\n)\n\nvar (\n\terrInvalidStringInput = errors.New(\"invalid string input\")\n\terrMalformedBooleanValue = errors.New(\"malformed boolean value\")\n\terrEmptyByteSlice = errors.New(\"empty byte slice\")\n\terrInvalidExponentValue = errors.New(\"invalid exponent value\")\n\terrNonDigitCharacters = errors.New(\"non-digit characters found\")\n\terrNumericRangeExceeded = errors.New(\"numeric value exceeds the range limit\")\n\terrMultipleDecimalPoints = errors.New(\"multiple decimal points found\")\n)\n"},{"name":"escape.gno","body":"package json\n\nimport (\n\t\"unicode/utf8\"\n)\n\nconst (\n\tsupplementalPlanesOffset = 0x10000\n\thighSurrogateOffset = 0xD800\n\tlowSurrogateOffset = 0xDC00\n\tsurrogateEnd = 0xDFFF\n\tbasicMultilingualPlaneOffset = 0xFFFF\n\tbadHex = -1\n\n\tsingleUnicodeEscapeLen = 6\n\tsurrogatePairLen = 12\n)\n\nvar hexLookupTable = [256]int{\n\t'0': 0x0, '1': 0x1, '2': 0x2, '3': 0x3, '4': 0x4,\n\t'5': 0x5, '6': 0x6, '7': 0x7, '8': 0x8, '9': 0x9,\n\t'A': 0xA, 'B': 0xB, 'C': 0xC, 'D': 0xD, 'E': 0xE, 'F': 0xF,\n\t'a': 0xA, 'b': 0xB, 'c': 0xC, 'd': 0xD, 'e': 0xE, 'f': 0xF,\n\t// Fill unspecified index-value pairs with key and value of -1\n\t'G': -1, 'H': -1, 'I': -1, 'J': -1,\n\t'K': -1, 'L': -1, 'M': -1, 'N': -1,\n\t'O': -1, 'P': -1, 'Q': -1, 'R': -1,\n\t'S': -1, 'T': -1, 'U': -1, 'V': -1,\n\t'W': -1, 'X': -1, 'Y': -1, 'Z': -1,\n\t'g': -1, 'h': -1, 'i': -1, 'j': -1,\n\t'k': -1, 'l': -1, 'm': -1, 'n': -1,\n\t'o': -1, 'p': -1, 'q': -1, 'r': -1,\n\t's': -1, 't': -1, 'u': -1, 'v': -1,\n\t'w': -1, 'x': -1, 'y': -1, 'z': -1,\n}\n\nfunc h2i(c byte) int {\n\treturn hexLookupTable[c]\n}\n\n// Unescape takes an input byte slice, processes it to Unescape certain characters,\n// and writes the result into an output byte slice.\n//\n// it returns the processed slice and any error encountered during the Unescape operation.\nfunc Unescape(input, output []byte) ([]byte, error) {\n\t// ensure the output slice has enough capacity to hold the input slice.\n\tinputLen := len(input)\n\tif cap(output) \u003c inputLen {\n\t\toutput = make([]byte, inputLen)\n\t}\n\n\tinPos, outPos := 0, 0\n\n\tfor inPos \u003c len(input) {\n\t\tc := input[inPos]\n\t\tif c != backSlash {\n\t\t\toutput[outPos] = c\n\t\t\tinPos++\n\t\t\toutPos++\n\t\t} else {\n\t\t\t// process escape sequence\n\t\t\tinLen, outLen, err := processEscapedUTF8(input[inPos:], output[outPos:])\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tinPos += inLen\n\t\t\toutPos += outLen\n\t\t}\n\t}\n\n\treturn output[:outPos], nil\n}\n\n// isSurrogatePair returns true if the rune is a surrogate pair.\n//\n// A surrogate pairs are used in UTF-16 encoding to encode characters\n// outside the Basic Multilingual Plane (BMP).\nfunc isSurrogatePair(r rune) bool {\n\treturn highSurrogateOffset \u003c= r \u0026\u0026 r \u003c= surrogateEnd\n}\n\n// isHighSurrogate checks if the rune is a high surrogate (U+D800 to U+DBFF).\nfunc isHighSurrogate(r rune) bool {\n\treturn r \u003e= highSurrogateOffset \u0026\u0026 r \u003c= 0xDBFF\n}\n\n// isLowSurrogate checks if the rune is a low surrogate (U+DC00 to U+DFFF).\nfunc isLowSurrogate(r rune) bool {\n\treturn r \u003e= lowSurrogateOffset \u0026\u0026 r \u003c= surrogateEnd\n}\n\n// combineSurrogates reconstruct the original unicode code points in the\n// supplemental plane by combinin the high and low surrogate.\n//\n// The hight surrogate in the range from U+D800 to U+DBFF,\n// and the low surrogate in the range from U+DC00 to U+DFFF.\n//\n// The formula to combine the surrogates is:\n// (high - 0xD800) * 0x400 + (low - 0xDC00) + 0x10000\nfunc combineSurrogates(high, low rune) rune {\n\treturn ((high - highSurrogateOffset) \u003c\u003c 10) + (low - lowSurrogateOffset) + supplementalPlanesOffset\n}\n\n// deocdeSingleUnicodeEscape decodes a unicode escape sequence (e.g., \\uXXXX) into a rune.\nfunc decodeSingleUnicodeEscape(b []byte) (rune, bool) {\n\tif len(b) \u003c 6 {\n\t\treturn utf8.RuneError, false\n\t}\n\n\t// convert hex to decimal\n\th1, h2, h3, h4 := h2i(b[2]), h2i(b[3]), h2i(b[4]), h2i(b[5])\n\tif h1 == badHex || h2 == badHex || h3 == badHex || h4 == badHex {\n\t\treturn utf8.RuneError, false\n\t}\n\n\treturn rune(h1\u003c\u003c12 + h2\u003c\u003c8 + h3\u003c\u003c4 + h4), true\n}\n\n// decodeUnicodeEscape decodes a Unicode escape sequence from a byte slice.\n// It handles both single Unicode escape sequences and surrogate pairs.\nfunc decodeUnicodeEscape(b []byte) (rune, int) {\n\t// decode the first Unicode escape sequence.\n\tr, ok := decodeSingleUnicodeEscape(b)\n\tif !ok {\n\t\treturn utf8.RuneError, -1\n\t}\n\n\t// if the rune is within the BMP and not a surrogate, return it\n\tif r \u003c= basicMultilingualPlaneOffset \u0026\u0026 !isSurrogatePair(r) {\n\t\treturn r, 6\n\t}\n\n\tif !isHighSurrogate(r) {\n\t\t// invalid surrogate pair.\n\t\treturn utf8.RuneError, -1\n\t}\n\n\t// if the rune is a high surrogate, need to decode the next escape sequence.\n\n\t// ensure there are enough bytes for the next escape sequence.\n\tif len(b) \u003c surrogatePairLen {\n\t\treturn utf8.RuneError, -1\n\t}\n\t// decode the second Unicode escape sequence.\n\tr2, ok := decodeSingleUnicodeEscape(b[singleUnicodeEscapeLen:])\n\tif !ok {\n\t\treturn utf8.RuneError, -1\n\t}\n\t// check if the second rune is a low surrogate.\n\tif isLowSurrogate(r2) {\n\t\tcombined := combineSurrogates(r, r2)\n\t\treturn combined, surrogatePairLen\n\t}\n\treturn utf8.RuneError, -1\n}\n\nvar escapeByteSet = [256]byte{\n\t'\"': doubleQuote,\n\t'\\\\': backSlash,\n\t'/': slash,\n\t'b': backSpace,\n\t'f': formFeed,\n\t'n': newLine,\n\t'r': carriageReturn,\n\t't': tab,\n}\n\n// Unquote takes a byte slice and unquotes it by removing\n// the surrounding quotes and unescaping the contents.\nfunc Unquote(s []byte, border byte) (string, bool) {\n\ts, ok := unquoteBytes(s, border)\n\treturn string(s), ok\n}\n\n// unquoteBytes takes a byte slice and unquotes it by removing\nfunc unquoteBytes(s []byte, border byte) ([]byte, bool) {\n\tif len(s) \u003c 2 || s[0] != border || s[len(s)-1] != border {\n\t\treturn nil, false\n\t}\n\n\ts = s[1 : len(s)-1]\n\n\tr := 0\n\tfor r \u003c len(s) {\n\t\tc := s[r]\n\n\t\tif c == backSlash || c == border || c \u003c 0x20 {\n\t\t\tbreak\n\t\t}\n\n\t\tif c \u003c utf8.RuneSelf {\n\t\t\tr++\n\t\t\tcontinue\n\t\t}\n\n\t\trr, size := utf8.DecodeRune(s[r:])\n\t\tif rr == utf8.RuneError \u0026\u0026 size == 1 {\n\t\t\tbreak\n\t\t}\n\n\t\tr += size\n\t}\n\n\tif r == len(s) {\n\t\treturn s, true\n\t}\n\n\tutfDoubleMax := utf8.UTFMax * 2\n\tb := make([]byte, len(s)+utfDoubleMax)\n\tw := copy(b, s[0:r])\n\n\tfor r \u003c len(s) {\n\t\tif w \u003e= len(b)-utf8.UTFMax {\n\t\t\tnb := make([]byte, utfDoubleMax+(2*len(b)))\n\t\t\tcopy(nb, b)\n\t\t\tb = nb\n\t\t}\n\n\t\tc := s[r]\n\t\tif c == backSlash {\n\t\t\tr++\n\t\t\tif r \u003e= len(s) {\n\t\t\t\treturn nil, false\n\t\t\t}\n\n\t\t\tif s[r] == 'u' {\n\t\t\t\trr, res := decodeUnicodeEscape(s[r-1:])\n\t\t\t\tif res \u003c 0 {\n\t\t\t\t\treturn nil, false\n\t\t\t\t}\n\n\t\t\t\tw += utf8.EncodeRune(b[w:], rr)\n\t\t\t\tr += 5\n\t\t\t} else {\n\t\t\t\tdecode := escapeByteSet[s[r]]\n\t\t\t\tif decode == 0 {\n\t\t\t\t\treturn nil, false\n\t\t\t\t}\n\n\t\t\t\tif decode == doubleQuote || decode == backSlash || decode == slash {\n\t\t\t\t\tdecode = s[r]\n\t\t\t\t}\n\n\t\t\t\tb[w] = decode\n\t\t\t\tr++\n\t\t\t\tw++\n\t\t\t}\n\t\t} else if c == border || c \u003c 0x20 {\n\t\t\treturn nil, false\n\t\t} else if c \u003c utf8.RuneSelf {\n\t\t\tb[w] = c\n\t\t\tr++\n\t\t\tw++\n\t\t} else {\n\t\t\trr, size := utf8.DecodeRune(s[r:])\n\n\t\t\tif rr == utf8.RuneError \u0026\u0026 size == 1 {\n\t\t\t\treturn nil, false\n\t\t\t}\n\n\t\t\tr += size\n\t\t\tw += utf8.EncodeRune(b[w:], rr)\n\t\t}\n\t}\n\n\treturn b[:w], true\n}\n\n// processEscapedUTF8 converts escape sequences to UTF-8 characters.\n// It decodes Unicode escape sequences (\\uXXXX) to UTF-8 and\n// converts standard escape sequences (e.g., \\n) to their corresponding special characters.\nfunc processEscapedUTF8(in, out []byte) (int, int, error) {\n\tif len(in) \u003c 2 || in[0] != backSlash {\n\t\treturn -1, -1, errInvalidEscapeSequence\n\t}\n\n\tescapeSeqLen := 2\n\tescapeChar := in[1]\n\n\tif escapeChar != 'u' {\n\t\tval := escapeByteSet[escapeChar]\n\t\tif val == 0 {\n\t\t\treturn -1, -1, errInvalidEscapeSequence\n\t\t}\n\n\t\tout[0] = val\n\t\treturn escapeSeqLen, 1, nil\n\t}\n\n\tr, size := decodeUnicodeEscape(in)\n\tif size == -1 {\n\t\treturn -1, -1, errInvalidEscapeSequence\n\t}\n\n\toutLen := utf8.EncodeRune(out, r)\n\n\treturn size, outLen, nil\n}\n"},{"name":"escape_test.gno","body":"package json\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\t\"unicode/utf8\"\n)\n\nfunc TestHexToInt(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tc byte\n\t\twant int\n\t}{\n\t\t{\"Digit 0\", '0', 0},\n\t\t{\"Digit 9\", '9', 9},\n\t\t{\"Uppercase A\", 'A', 10},\n\t\t{\"Uppercase F\", 'F', 15},\n\t\t{\"Lowercase a\", 'a', 10},\n\t\t{\"Lowercase f\", 'f', 15},\n\t\t{\"Invalid character1\", 'g', badHex},\n\t\t{\"Invalid character2\", 'G', badHex},\n\t\t{\"Invalid character3\", 'z', badHex},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := h2i(tt.c); got != tt.want {\n\t\t\t\tt.Errorf(\"h2i() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestIsSurrogatePair(t *testing.T) {\n\ttestCases := []struct {\n\t\tname string\n\t\tr rune\n\t\texpected bool\n\t}{\n\t\t{\"high surrogate start\", 0xD800, true},\n\t\t{\"high surrogate end\", 0xDBFF, true},\n\t\t{\"low surrogate start\", 0xDC00, true},\n\t\t{\"low surrogate end\", 0xDFFF, true},\n\t\t{\"Non-surrogate\", 0x0000, false},\n\t\t{\"Non-surrogate 2\", 0xE000, false},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif got := isSurrogatePair(tc.r); got != tc.expected {\n\t\t\t\tt.Errorf(\"isSurrogate() = %v, want %v\", got, tc.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCombineSurrogates(t *testing.T) {\n\ttestCases := []struct {\n\t\thigh, low rune\n\t\texpected rune\n\t}{\n\t\t{0xD83D, 0xDC36, 0x1F436}, // 🐶 U+1F436 DOG FACE\n\t\t{0xD83D, 0xDE00, 0x1F600}, // 😀 U+1F600 GRINNING FACE\n\t\t{0xD83C, 0xDF03, 0x1F303}, // 🌃 U+1F303 NIGHT WITH STARS\n\t}\n\n\tfor _, tc := range testCases {\n\t\tresult := combineSurrogates(tc.high, tc.low)\n\t\tif result != tc.expected {\n\t\t\tt.Errorf(\"combineSurrogates(%U, %U) = %U; want %U\", tc.high, tc.low, result, tc.expected)\n\t\t}\n\t}\n}\n\nfunc TestDecodeSingleUnicodeEscape(t *testing.T) {\n\ttestCases := []struct {\n\t\tinput []byte\n\t\texpected rune\n\t\tisValid bool\n\t}{\n\t\t// valid unicode escape sequences\n\t\t{[]byte(`\\u0041`), 'A', true},\n\t\t{[]byte(`\\u03B1`), 'α', true},\n\t\t{[]byte(`\\u00E9`), 'é', true}, // valid non-English character\n\t\t{[]byte(`\\u0021`), '!', true}, // valid special character\n\t\t{[]byte(`\\uFF11`), '1', true},\n\t\t{[]byte(`\\uD83D`), 0xD83D, true},\n\t\t{[]byte(`\\uDE03`), 0xDE03, true},\n\n\t\t// invalid unicode escape sequences\n\t\t{[]byte(`\\u004`), utf8.RuneError, false}, // too short\n\t\t{[]byte(`\\uXYZW`), utf8.RuneError, false}, // invalid hex\n\t\t{[]byte(`\\u00G1`), utf8.RuneError, false}, // non-hex character\n\t}\n\n\tfor _, tc := range testCases {\n\t\tresult, isValid := decodeSingleUnicodeEscape(tc.input)\n\t\tif result != tc.expected || isValid != tc.isValid {\n\t\t\tt.Errorf(\"decodeSingleUnicodeEscape(%s) = (%U, %v); want (%U, %v)\", tc.input, result, isValid, tc.expected, tc.isValid)\n\t\t}\n\t}\n}\n\nfunc TestDecodeUnicodeEscape(t *testing.T) {\n\ttests := []struct {\n\t\tinput []byte\n\t\texpected rune\n\t\tsize int\n\t}{\n\t\t{[]byte(`\\u0041`), 'A', 6},\n\t\t{[]byte(`\\uD83D\\uDE00`), 0x1F600, 12}, // 😀\n\t\t{[]byte(`\\uD834\\uDD1E`), 0x1D11E, 12}, // 𝄞\n\t\t{[]byte(`\\uFFFF`), '\\uFFFF', 6},\n\t\t{[]byte(`\\uXYZW`), utf8.RuneError, -1},\n\t\t{[]byte(`\\uD800`), utf8.RuneError, -1}, // single high surrogate\n\t\t{[]byte(`\\uDC00`), utf8.RuneError, -1}, // single low surrogate\n\t\t{[]byte(`\\uD800\\uDC00`), 0x10000, 12}, // First code point above U+FFFF\n\t\t{[]byte(`\\uDBFF\\uDFFF`), 0x10FFFF, 12}, // Maximum code point\n\t\t{[]byte(`\\uD83D\\u0041`), utf8.RuneError, -1}, // invalid surrogate pair\n\t}\n\n\tfor _, tc := range tests {\n\t\tr, size := decodeUnicodeEscape(tc.input)\n\t\tif r != tc.expected || size != tc.size {\n\t\t\tt.Errorf(\"decodeUnicodeEscape(%q) = (%U, %d); want (%U, %d)\", tc.input, r, size, tc.expected, tc.size)\n\t\t}\n\t}\n}\n\nfunc TestUnescapeToUTF8(t *testing.T) {\n\ttests := []struct {\n\t\tinput []byte\n\t\texpectedIn int\n\t\texpectedOut int\n\t\tisError bool\n\t}{\n\t\t// valid escape sequences\n\t\t{[]byte(`\\n`), 2, 1, false},\n\t\t{[]byte(`\\t`), 2, 1, false},\n\t\t{[]byte(`\\u0041`), 6, 1, false},\n\t\t{[]byte(`\\u03B1`), 6, 2, false},\n\t\t{[]byte(`\\uD830\\uDE03`), 12, 4, false},\n\n\t\t// invalid escape sequences\n\t\t{[]byte(`\\`), -1, -1, true}, // incomplete escape sequence\n\t\t{[]byte(`\\x`), -1, -1, true}, // invalid escape character\n\t\t{[]byte(`\\u`), -1, -1, true}, // incomplete unicode escape sequence\n\t\t{[]byte(`\\u004`), -1, -1, true}, // invalid unicode escape sequence\n\t\t{[]byte(`\\uXYZW`), -1, -1, true}, // invalid unicode escape sequence\n\t\t{[]byte(`\\uD83D\\u0041`), -1, -1, true}, // invalid unicode escape sequence\n\t}\n\n\tfor _, tc := range tests {\n\t\tinput := make([]byte, len(tc.input))\n\t\tcopy(input, tc.input)\n\t\toutput := make([]byte, utf8.UTFMax)\n\t\tinLen, outLen, err := processEscapedUTF8(input, output)\n\t\tif (err != nil) != tc.isError {\n\t\t\tt.Errorf(\"processEscapedUTF8(%q) = %v; want %v\", tc.input, err, tc.isError)\n\t\t}\n\n\t\tif inLen != tc.expectedIn || outLen != tc.expectedOut {\n\t\t\tt.Errorf(\"processEscapedUTF8(%q) = (%d, %d); want (%d, %d)\", tc.input, inLen, outLen, tc.expectedIn, tc.expectedOut)\n\t\t}\n\t}\n}\n\nfunc TestUnescape(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput []byte\n\t\texpected []byte\n\t\tisError bool\n\t}{\n\t\t{\"NoEscape\", []byte(\"hello world\"), []byte(\"hello world\"), false},\n\t\t{\"SingleEscape\", []byte(\"hello\\\\nworld\"), []byte(\"hello\\nworld\"), false},\n\t\t{\"MultipleEscapes\", []byte(\"line1\\\\nline2\\\\r\\\\nline3\"), []byte(\"line1\\nline2\\r\\nline3\"), false},\n\t\t{\"UnicodeEscape\", []byte(\"snowman:\\\\u2603\"), []byte(\"snowman:\\u2603\"), false},\n\t\t{\"SurrogatePair\", []byte(\"emoji:\\\\uD83D\\\\uDE00\"), []byte(\"emoji:😀\"), false},\n\t\t{\"InvalidEscape\", []byte(\"hello\\\\xworld\"), nil, true},\n\t\t{\"IncompleteUnicode\", []byte(\"incomplete:\\\\u123\"), nil, true},\n\t\t{\"InvalidSurrogatePair\", []byte(\"invalid:\\\\uD83D\\\\u0041\"), nil, true},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\toutput := make([]byte, len(tc.input)*2) // Allocate extra space for possible expansion\n\t\t\tresult, err := Unescape(tc.input, output)\n\t\t\tif (err != nil) != tc.isError {\n\t\t\t\tt.Errorf(\"Unescape(%q) error = %v; want error = %v\", tc.input, err, tc.isError)\n\t\t\t}\n\n\t\t\tif !tc.isError \u0026\u0026 !bytes.Equal(result, tc.expected) {\n\t\t\t\tt.Errorf(\"Unescape(%q) = %q; want %q\", tc.input, result, tc.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUnquoteBytes(t *testing.T) {\n\ttests := []struct {\n\t\tinput []byte\n\t\tborder byte\n\t\texpected []byte\n\t\tok bool\n\t}{\n\t\t{[]byte(\"\\\"hello\\\"\"), '\"', []byte(\"hello\"), true},\n\t\t{[]byte(\"'hello'\"), '\\'', []byte(\"hello\"), true},\n\t\t{[]byte(\"\\\"hello\"), '\"', nil, false},\n\t\t{[]byte(\"hello\\\"\"), '\"', nil, false},\n\t\t{[]byte(\"\\\"he\\\\\\\"llo\\\"\"), '\"', []byte(\"he\\\"llo\"), true},\n\t\t{[]byte(\"\\\"he\\\\nllo\\\"\"), '\"', []byte(\"he\\nllo\"), true},\n\t\t{[]byte(\"\\\"\\\"\"), '\"', []byte(\"\"), true},\n\t\t{[]byte(\"''\"), '\\'', []byte(\"\"), true},\n\t\t{[]byte(\"\\\"\\\\u0041\\\"\"), '\"', []byte(\"A\"), true},\n\t\t{[]byte(`\"Hello, 世界\"`), '\"', []byte(\"Hello, 世界\"), true},\n\t\t{[]byte(`\"Hello, \\x80\"`), '\"', nil, false},\n\t\t{[]byte(`\"invalid surrogate: \\uD83D\\u0041\"`), '\"', nil, false},\n\t}\n\n\tfor _, tc := range tests {\n\t\tresult, pass := unquoteBytes(tc.input, tc.border)\n\n\t\tif pass != tc.ok {\n\t\t\tt.Errorf(\"unquoteBytes(%q) = %v; want %v\", tc.input, pass, tc.ok)\n\t\t}\n\n\t\tif !bytes.Equal(result, tc.expected) {\n\t\t\tt.Errorf(\"unquoteBytes(%q) = %q; want %q\", tc.input, result, tc.expected)\n\t\t}\n\t}\n}\n"},{"name":"indent.gno","body":"package json\n\nimport (\n\t\"bytes\"\n\t\"strings\"\n)\n\n// indentGrowthFactor specifies the growth factor of indenting JSON input.\n// A factor no higher than 2 ensures that wasted space never exceeds 50%.\nconst indentGrowthFactor = 2\n\n// IndentJSON formats the JSON data with the specified indentation.\nfunc Indent(data []byte, indent string) ([]byte, error) {\n\tvar (\n\t\tout bytes.Buffer\n\t\tlevel int\n\t\tinArray bool\n\t\tarrayDepth int\n\t)\n\n\tfor i := 0; i \u003c len(data); i++ {\n\t\tc := data[i] // current character\n\n\t\tswitch c {\n\t\tcase bracketOpen:\n\t\t\tarrayDepth++\n\t\t\tif arrayDepth \u003e 1 {\n\t\t\t\tlevel++ // increase the level if it's nested array\n\t\t\t\tinArray = true\n\n\t\t\t\tif err := out.WriteByte(c); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\t\tif err := writeNewlineAndIndent(\u0026out, level, indent); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// case of the top-level array\n\t\t\t\tinArray = true\n\t\t\t\tif err := out.WriteByte(c); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\n\t\tcase bracketClose:\n\t\t\tif inArray \u0026\u0026 arrayDepth \u003e 1 { // nested array\n\t\t\t\tlevel--\n\t\t\t\tif err := writeNewlineAndIndent(\u0026out, level, indent); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tarrayDepth--\n\t\t\tif arrayDepth == 0 {\n\t\t\t\tinArray = false\n\t\t\t}\n\n\t\t\tif err := out.WriteByte(c); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\tcase curlyOpen:\n\t\t\t// check if the empty object or array\n\t\t\t// we don't need to apply the indent when it's empty containers.\n\t\t\tif i+1 \u003c len(data) \u0026\u0026 data[i+1] == curlyClose {\n\t\t\t\tif err := out.WriteByte(c); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\t\ti++ // skip next character\n\t\t\t\tif err := out.WriteByte(data[i]); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif err := out.WriteByte(c); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\t\tlevel++\n\t\t\t\tif err := writeNewlineAndIndent(\u0026out, level, indent); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\n\t\tcase curlyClose:\n\t\t\tlevel--\n\t\t\tif err := writeNewlineAndIndent(\u0026out, level, indent); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif err := out.WriteByte(c); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\tcase comma, colon:\n\t\t\tif err := out.WriteByte(c); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif inArray \u0026\u0026 arrayDepth \u003e 1 { // nested array\n\t\t\t\tif err := writeNewlineAndIndent(\u0026out, level, indent); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t} else if c == colon {\n\t\t\t\tif err := out.WriteByte(' '); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\n\t\tdefault:\n\t\t\tif err := out.WriteByte(c); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn out.Bytes(), nil\n}\n\nfunc writeNewlineAndIndent(out *bytes.Buffer, level int, indent string) error {\n\tif err := out.WriteByte('\\n'); err != nil {\n\t\treturn err\n\t}\n\n\tidt := strings.Repeat(indent, level*indentGrowthFactor)\n\tif _, err := out.WriteString(idt); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"},{"name":"indent_test.gno","body":"package json\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n)\n\nfunc TestIndentJSON(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput []byte\n\t\tindent string\n\t\texpected []byte\n\t}{\n\t\t{\n\t\t\tname: \"empty object\",\n\t\t\tinput: []byte(`{}`),\n\t\t\tindent: \" \",\n\t\t\texpected: []byte(`{}`),\n\t\t},\n\t\t{\n\t\t\tname: \"empty array\",\n\t\t\tinput: []byte(`[]`),\n\t\t\tindent: \" \",\n\t\t\texpected: []byte(`[]`),\n\t\t},\n\t\t{\n\t\t\tname: \"nested object\",\n\t\t\tinput: []byte(`{{}}`),\n\t\t\tindent: \"\\t\",\n\t\t\texpected: []byte(\"{\\n\\t\\t{}\\n}\"),\n\t\t},\n\t\t{\n\t\t\tname: \"nested array\",\n\t\t\tinput: []byte(`[[[]]]`),\n\t\t\tindent: \"\\t\",\n\t\t\texpected: []byte(\"[[\\n\\t\\t[\\n\\t\\t\\t\\t\\n\\t\\t]\\n]]\"),\n\t\t},\n\t\t{\n\t\t\tname: \"top-level array\",\n\t\t\tinput: []byte(`[\"apple\",\"banana\",\"cherry\"]`),\n\t\t\tindent: \"\\t\",\n\t\t\texpected: []byte(`[\"apple\",\"banana\",\"cherry\"]`),\n\t\t},\n\t\t{\n\t\t\tname: \"array of arrays\",\n\t\t\tinput: []byte(`[\"apple\",[\"banana\",\"cherry\"],\"date\"]`),\n\t\t\tindent: \" \",\n\t\t\texpected: []byte(\"[\\\"apple\\\",[\\n \\\"banana\\\",\\n \\\"cherry\\\"\\n],\\\"date\\\"]\"),\n\t\t},\n\n\t\t{\n\t\t\tname: \"nested array in object\",\n\t\t\tinput: []byte(`{\"fruits\":[\"apple\",[\"banana\",\"cherry\"],\"date\"]}`),\n\t\t\tindent: \" \",\n\t\t\texpected: []byte(\"{\\n \\\"fruits\\\": [\\\"apple\\\",[\\n \\\"banana\\\",\\n \\\"cherry\\\"\\n ],\\\"date\\\"]\\n}\"),\n\t\t},\n\t\t{\n\t\t\tname: \"complex nested structure\",\n\t\t\tinput: []byte(`{\"data\":{\"array\":[1,2,3],\"bool\":true,\"nestedArray\":[[\"a\",\"b\"],\"c\"]}}`),\n\t\t\tindent: \" \",\n\t\t\texpected: []byte(\"{\\n \\\"data\\\": {\\n \\\"array\\\": [1,2,3],\\\"bool\\\": true,\\\"nestedArray\\\": [[\\n \\\"a\\\",\\n \\\"b\\\"\\n ],\\\"c\\\"]\\n }\\n}\"),\n\t\t},\n\t\t{\n\t\t\tname: \"custom ident character\",\n\t\t\tinput: []byte(`{\"fruits\":[\"apple\",[\"banana\",\"cherry\"],\"date\"]}`),\n\t\t\tindent: \"*\",\n\t\t\texpected: []byte(\"{\\n**\\\"fruits\\\": [\\\"apple\\\",[\\n****\\\"banana\\\",\\n****\\\"cherry\\\"\\n**],\\\"date\\\"]\\n}\"),\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tactual, err := Indent(tt.input, tt.indent)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"IndentJSON() error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !bytes.Equal(actual, tt.expected) {\n\t\t\t\tt.Errorf(\"IndentJSON() = %q, want %q\", actual, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n"},{"name":"internal.gno","body":"package json\n\n// Reference: https://github.com/freddierice/php_source/blob/467ed5d6edff72219afd3e644516f131118ef48e/ext/json/JSON_parser.c\n// Copyright (c) 2005 JSON.org\n\n// Go implementation is taken from: https://github.com/spyzhov/ajson/blob/master/internal/state.go\n\ntype (\n\tStates int8 // possible states of the parser\n\tClasses int8 // JSON string character types\n)\n\nconst __ = -1\n\n// enum classes\nconst (\n\tC_SPACE Classes = iota /* space */\n\tC_WHITE /* other whitespace */\n\tC_LCURB /* { */\n\tC_RCURB /* } */\n\tC_LSQRB /* [ */\n\tC_RSQRB /* ] */\n\tC_COLON /* : */\n\tC_COMMA /* , */\n\tC_QUOTE /* \" */\n\tC_BACKS /* \\ */\n\tC_SLASH /* / */\n\tC_PLUS /* + */\n\tC_MINUS /* - */\n\tC_POINT /* . */\n\tC_ZERO /* 0 */\n\tC_DIGIT /* 123456789 */\n\tC_LOW_A /* a */\n\tC_LOW_B /* b */\n\tC_LOW_C /* c */\n\tC_LOW_D /* d */\n\tC_LOW_E /* e */\n\tC_LOW_F /* f */\n\tC_LOW_L /* l */\n\tC_LOW_N /* n */\n\tC_LOW_R /* r */\n\tC_LOW_S /* s */\n\tC_LOW_T /* t */\n\tC_LOW_U /* u */\n\tC_ABCDF /* ABCDF */\n\tC_E /* E */\n\tC_ETC /* everything else */\n)\n\n// AsciiClasses array maps the 128 ASCII characters into character classes.\nvar AsciiClasses = [128]Classes{\n\t/*\n\t This array maps the 128 ASCII characters into character classes.\n\t The remaining Unicode characters should be mapped to C_ETC.\n\t Non-whitespace control characters are errors.\n\t*/\n\t__, __, __, __, __, __, __, __,\n\t__, C_WHITE, C_WHITE, __, __, C_WHITE, __, __,\n\t__, __, __, __, __, __, __, __,\n\t__, __, __, __, __, __, __, __,\n\n\tC_SPACE, C_ETC, C_QUOTE, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_PLUS, C_COMMA, C_MINUS, C_POINT, C_SLASH,\n\tC_ZERO, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT,\n\tC_DIGIT, C_DIGIT, C_COLON, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC,\n\n\tC_ETC, C_ABCDF, C_ABCDF, C_ABCDF, C_ABCDF, C_E, C_ABCDF, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_LSQRB, C_BACKS, C_RSQRB, C_ETC, C_ETC,\n\n\tC_ETC, C_LOW_A, C_LOW_B, C_LOW_C, C_LOW_D, C_LOW_E, C_LOW_F, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_ETC, C_LOW_L, C_ETC, C_LOW_N, C_ETC,\n\tC_ETC, C_ETC, C_LOW_R, C_LOW_S, C_LOW_T, C_LOW_U, C_ETC, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_LCURB, C_ETC, C_RCURB, C_ETC, C_ETC,\n}\n\n// QuoteAsciiClasses is a HACK for single quote from AsciiClasses\nvar QuoteAsciiClasses = [128]Classes{\n\t/*\n\t This array maps the 128 ASCII characters into character classes.\n\t The remaining Unicode characters should be mapped to C_ETC.\n\t Non-whitespace control characters are errors.\n\t*/\n\t__, __, __, __, __, __, __, __,\n\t__, C_WHITE, C_WHITE, __, __, C_WHITE, __, __,\n\t__, __, __, __, __, __, __, __,\n\t__, __, __, __, __, __, __, __,\n\n\tC_SPACE, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_QUOTE,\n\tC_ETC, C_ETC, C_ETC, C_PLUS, C_COMMA, C_MINUS, C_POINT, C_SLASH,\n\tC_ZERO, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT,\n\tC_DIGIT, C_DIGIT, C_COLON, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC,\n\n\tC_ETC, C_ABCDF, C_ABCDF, C_ABCDF, C_ABCDF, C_E, C_ABCDF, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_LSQRB, C_BACKS, C_RSQRB, C_ETC, C_ETC,\n\n\tC_ETC, C_LOW_A, C_LOW_B, C_LOW_C, C_LOW_D, C_LOW_E, C_LOW_F, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_ETC, C_LOW_L, C_ETC, C_LOW_N, C_ETC,\n\tC_ETC, C_ETC, C_LOW_R, C_LOW_S, C_LOW_T, C_LOW_U, C_ETC, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_LCURB, C_ETC, C_RCURB, C_ETC, C_ETC,\n}\n\n/*\nThe state codes.\n*/\nconst (\n\tGO States = iota /* start */\n\tOK /* ok */\n\tOB /* object */\n\tKE /* key */\n\tCO /* colon */\n\tVA /* value */\n\tAR /* array */\n\tST /* string */\n\tES /* escape */\n\tU1 /* u1 */\n\tU2 /* u2 */\n\tU3 /* u3 */\n\tU4 /* u4 */\n\tMI /* minus */\n\tZE /* zero */\n\tIN /* integer */\n\tDT /* dot */\n\tFR /* fraction */\n\tE1 /* e */\n\tE2 /* ex */\n\tE3 /* exp */\n\tT1 /* tr */\n\tT2 /* tru */\n\tT3 /* true */\n\tF1 /* fa */\n\tF2 /* fal */\n\tF3 /* fals */\n\tF4 /* false */\n\tN1 /* nu */\n\tN2 /* nul */\n\tN3 /* null */\n)\n\n// List of action codes.\n// these constants are defining an action that should be performed under certain conditions.\nconst (\n\tcl States = -2 /* colon */\n\tcm States = -3 /* comma */\n\tqt States = -4 /* quote */\n\tbo States = -5 /* bracket open */\n\tco States = -6 /* curly bracket open */\n\tbc States = -7 /* bracket close */\n\tcc States = -8 /* curly bracket close */\n\tec States = -9 /* curly bracket empty */\n)\n\n// StateTransitionTable is the state transition table takes the current state and the current symbol, and returns either\n// a new state or an action. An action is represented as a negative number. A JSON text is accepted if at the end of the\n// text the state is OK and if the mode is DONE.\nvar StateTransitionTable = [31][31]States{\n\t/*\n\t The state transition table takes the current state and the current symbol,\n\t and returns either a new state or an action. An action is represented as a\n\t negative number. A JSON text is accepted if at the end of the text the\n\t state is OK and if the mode is DONE.\n\t white 1-9 ABCDF etc\n\t space | { } [ ] : , \" \\ / + - . 0 | a b c d e f l n r s t u | E |*/\n\t/*start GO*/ {GO, GO, co, __, bo, __, __, __, ST, __, __, __, MI, __, ZE, IN, __, __, __, __, __, F1, __, N1, __, __, T1, __, __, __, __},\n\t/*ok OK*/ {OK, OK, __, cc, __, bc, __, cm, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},\n\t/*object OB*/ {OB, OB, __, ec, __, __, __, __, ST, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},\n\t/*key KE*/ {KE, KE, __, __, __, __, __, __, ST, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},\n\t/*colon CO*/ {CO, CO, __, __, __, __, cl, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},\n\t/*value VA*/ {VA, VA, co, __, bo, __, __, __, ST, __, __, __, MI, __, ZE, IN, __, __, __, __, __, F1, __, N1, __, __, T1, __, __, __, __},\n\t/*array AR*/ {AR, AR, co, __, bo, bc, __, __, ST, __, __, __, MI, __, ZE, IN, __, __, __, __, __, F1, __, N1, __, __, T1, __, __, __, __},\n\t/*string ST*/ {ST, __, ST, ST, ST, ST, ST, ST, qt, ES, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST},\n\t/*escape ES*/ {__, __, __, __, __, __, __, __, ST, ST, ST, __, __, __, __, __, __, ST, __, __, __, ST, __, ST, ST, __, ST, U1, __, __, __},\n\t/*u1 U1*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, U2, U2, U2, U2, U2, U2, U2, U2, __, __, __, __, __, __, U2, U2, __},\n\t/*u2 U2*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, U3, U3, U3, U3, U3, U3, U3, U3, __, __, __, __, __, __, U3, U3, __},\n\t/*u3 U3*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, U4, U4, U4, U4, U4, U4, U4, U4, __, __, __, __, __, __, U4, U4, __},\n\t/*u4 U4*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, ST, ST, ST, ST, ST, ST, ST, ST, __, __, __, __, __, __, ST, ST, __},\n\t/*minus MI*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, ZE, IN, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},\n\t/*zero ZE*/ {OK, OK, __, cc, __, bc, __, cm, __, __, __, __, __, DT, __, __, __, __, __, __, E1, __, __, __, __, __, __, __, __, E1, __},\n\t/*int IN*/ {OK, OK, __, cc, __, bc, __, cm, __, __, __, __, __, DT, IN, IN, __, __, __, __, E1, __, __, __, __, __, __, __, __, E1, __},\n\t/*dot DT*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, FR, FR, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},\n\t/*frac FR*/ {OK, OK, __, cc, __, bc, __, cm, __, __, __, __, __, __, FR, FR, __, __, __, __, E1, __, __, __, __, __, __, __, __, E1, __},\n\t/*e E1*/ {__, __, __, __, __, __, __, __, __, __, __, E2, E2, __, E3, E3, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},\n\t/*ex E2*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, E3, E3, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},\n\t/*exp E3*/ {OK, OK, __, cc, __, bc, __, cm, __, __, __, __, __, __, E3, E3, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},\n\t/*tr T1*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, T2, __, __, __, __, __, __},\n\t/*tru T2*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, T3, __, __, __},\n\t/*true T3*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, OK, __, __, __, __, __, __, __, __, __, __},\n\t/*fa F1*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, F2, __, __, __, __, __, __, __, __, __, __, __, __, __, __},\n\t/*fal F2*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, F3, __, __, __, __, __, __, __, __},\n\t/*fals F3*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, F4, __, __, __, __, __},\n\t/*false F4*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, OK, __, __, __, __, __, __, __, __, __, __},\n\t/*nu N1*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, N2, __, __, __},\n\t/*nul N2*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, N3, __, __, __, __, __, __, __, __},\n\t/*null N3*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, OK, __, __, __, __, __, __, __, __},\n}\n"},{"name":"node.gno","body":"package json\n\nimport (\n\t\"errors\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Node represents a JSON node.\ntype Node struct {\n\tprev *Node // prev is the parent node of the current node.\n\tnext map[string]*Node // next is the child nodes of the current node.\n\tkey *string // key holds the key of the current node in the parent node.\n\tdata []byte // byte slice of JSON data\n\tvalue interface{} // value holds the value of the current node.\n\tnodeType ValueType // NodeType holds the type of the current node. (Object, Array, String, Number, Boolean, Null)\n\tindex *int // index holds the index of the current node in the parent array node.\n\tborders [2]int // borders stores the start and end index of the current node in the data.\n\tmodified bool // modified indicates the current node is changed or not.\n}\n\n// NewNode creates a new node instance with the given parent node, buffer, type, and key.\nfunc NewNode(prev *Node, b *buffer, typ ValueType, key **string) (*Node, error) {\n\tcurr := \u0026Node{\n\t\tprev: prev,\n\t\tdata: b.data,\n\t\tborders: [2]int{b.index, 0},\n\t\tkey: *key,\n\t\tnodeType: typ,\n\t\tmodified: false,\n\t}\n\n\tif typ == Object || typ == Array {\n\t\tcurr.next = make(map[string]*Node)\n\t}\n\n\tif prev != nil {\n\t\tif prev.IsArray() {\n\t\t\tsize := len(prev.next)\n\t\t\tcurr.index = \u0026size\n\n\t\t\tprev.next[strconv.Itoa(size)] = curr\n\t\t} else if prev.IsObject() {\n\t\t\tif key == nil {\n\t\t\t\treturn nil, errKeyRequired\n\t\t\t}\n\n\t\t\tprev.next[**key] = curr\n\t\t} else {\n\t\t\treturn nil, errors.New(\"invalid parent type\")\n\t\t}\n\t}\n\n\treturn curr, nil\n}\n\n// load retrieves the value of the current node.\nfunc (n *Node) load() interface{} {\n\treturn n.value\n}\n\n// Changed checks the current node is changed or not.\nfunc (n *Node) Changed() bool {\n\treturn n.modified\n}\n\n// Key returns the key of the current node.\nfunc (n *Node) Key() string {\n\tif n == nil || n.key == nil {\n\t\treturn \"\"\n\t}\n\n\treturn *n.key\n}\n\n// HasKey checks the current node has the given key or not.\nfunc (n *Node) HasKey(key string) bool {\n\tif n == nil {\n\t\treturn false\n\t}\n\n\t_, ok := n.next[key]\n\treturn ok\n}\n\n// GetKey returns the value of the given key from the current object node.\nfunc (n *Node) GetKey(key string) (*Node, error) {\n\tif n == nil {\n\t\treturn nil, errNilNode\n\t}\n\n\tif n.Type() != Object {\n\t\treturn nil, ufmt.Errorf(\"target node is not object type. got: %s\", n.Type().String())\n\t}\n\n\tvalue, ok := n.next[key]\n\tif !ok {\n\t\treturn nil, ufmt.Errorf(\"key not found: %s\", key)\n\t}\n\n\treturn value, nil\n}\n\n// MustKey returns the value of the given key from the current object node.\nfunc (n *Node) MustKey(key string) *Node {\n\tval, err := n.GetKey(key)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn val\n}\n\n// UniqueKeyLists traverses the current JSON nodes and collects all the unique keys.\nfunc (n *Node) UniqueKeyLists() []string {\n\tvar collectKeys func(*Node) []string\n\tcollectKeys = func(node *Node) []string {\n\t\tif node == nil || !node.IsObject() {\n\t\t\treturn nil\n\t\t}\n\n\t\tresult := make(map[string]bool)\n\t\tfor key, childNode := range node.next {\n\t\t\tresult[key] = true\n\t\t\tchildKeys := collectKeys(childNode)\n\t\t\tfor _, childKey := range childKeys {\n\t\t\t\tresult[childKey] = true\n\t\t\t}\n\t\t}\n\n\t\tkeys := make([]string, 0, len(result))\n\t\tfor key := range result {\n\t\t\tkeys = append(keys, key)\n\t\t}\n\t\treturn keys\n\t}\n\n\treturn collectKeys(n)\n}\n\n// Empty returns true if the current node is empty.\nfunc (n *Node) Empty() bool {\n\tif n == nil {\n\t\treturn false\n\t}\n\n\treturn len(n.next) == 0\n}\n\n// Type returns the type (ValueType) of the current node.\nfunc (n *Node) Type() ValueType {\n\treturn n.nodeType\n}\n\n// Value returns the value of the current node.\n//\n// Usage:\n//\n//\troot := Unmarshal([]byte(`{\"key\": \"value\"}`))\n//\tval, err := root.MustKey(\"key\").Value()\n//\tif err != nil {\n//\t\tt.Errorf(\"Value returns error: %v\", err)\n//\t}\n//\n//\tresult: \"value\"\nfunc (n *Node) Value() (value interface{}, err error) {\n\tvalue = n.load()\n\n\tif value == nil {\n\t\tswitch n.nodeType {\n\t\tcase Null:\n\t\t\treturn nil, nil\n\n\t\tcase Number:\n\t\t\tvalue, err = strconv.ParseFloat(string(n.source()), 64)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tn.value = value\n\n\t\tcase String:\n\t\t\tvar ok bool\n\t\t\tvalue, ok = Unquote(n.source(), doubleQuote)\n\t\t\tif !ok {\n\t\t\t\treturn \"\", errInvalidStringValue\n\t\t\t}\n\n\t\t\tn.value = value\n\n\t\tcase Boolean:\n\t\t\tif len(n.source()) == 0 {\n\t\t\t\treturn nil, errEmptyBooleanNode\n\t\t\t}\n\n\t\t\tb := n.source()[0]\n\t\t\tvalue = b == 't' || b == 'T'\n\t\t\tn.value = value\n\n\t\tcase Array:\n\t\t\telems := make([]*Node, len(n.next))\n\n\t\t\tfor _, e := range n.next {\n\t\t\t\telems[*e.index] = e\n\t\t\t}\n\n\t\t\tvalue = elems\n\t\t\tn.value = value\n\n\t\tcase Object:\n\t\t\tobj := make(map[string]*Node, len(n.next))\n\n\t\t\tfor k, v := range n.next {\n\t\t\t\tobj[k] = v\n\t\t\t}\n\n\t\t\tvalue = obj\n\t\t\tn.value = value\n\t\t}\n\t}\n\n\treturn value, nil\n}\n\n// Delete removes the current node from the parent node.\n//\n// Usage:\n//\n//\troot := Unmarshal([]byte(`{\"key\": \"value\"}`))\n//\tif err := root.MustKey(\"key\").Delete(); err != nil {\n//\t\tt.Errorf(\"Delete returns error: %v\", err)\n//\t}\n//\n//\tresult: {} (empty object)\nfunc (n *Node) Delete() error {\n\tif n == nil {\n\t\treturn errors.New(\"can't delete nil node\")\n\t}\n\n\tif n.prev == nil {\n\t\treturn nil\n\t}\n\n\treturn n.prev.remove(n)\n}\n\n// Size returns the size (length) of the current array node.\n//\n// Usage:\n//\n//\troot := ArrayNode(\"\", []*Node{StringNode(\"\", \"foo\"), NumberNode(\"\", 1)})\n//\tif root == nil {\n//\t\tt.Errorf(\"ArrayNode returns nil\")\n//\t}\n//\n//\tif root.Size() != 2 {\n//\t\tt.Errorf(\"ArrayNode returns wrong size: %d\", root.Size())\n//\t}\nfunc (n *Node) Size() int {\n\tif n == nil {\n\t\treturn 0\n\t}\n\n\treturn len(n.next)\n}\n\n// Index returns the index of the current node in the parent array node.\n//\n// Usage:\n//\n//\troot := ArrayNode(\"\", []*Node{StringNode(\"\", \"foo\"), NumberNode(\"\", 1)})\n//\tif root == nil {\n//\t\tt.Errorf(\"ArrayNode returns nil\")\n//\t}\n//\n//\tif root.MustIndex(1).Index() != 1 {\n//\t\tt.Errorf(\"Index returns wrong index: %d\", root.MustIndex(1).Index())\n//\t}\n//\n// We can also use the index to the byte slice of the JSON data directly.\n//\n// Example:\n//\n//\troot := Unmarshal([]byte(`[\"foo\", 1]`))\n//\tif root == nil {\n//\t\tt.Errorf(\"Unmarshal returns nil\")\n//\t}\n//\n//\tif string(root.MustIndex(1).source()) != \"1\" {\n//\t\tt.Errorf(\"source returns wrong result: %s\", root.MustIndex(1).source())\n//\t}\nfunc (n *Node) Index() int {\n\tif n == nil || n.index == nil {\n\t\treturn -1\n\t}\n\n\treturn *n.index\n}\n\n// MustIndex returns the array element at the given index.\n//\n// If the index is negative, it returns the index is from the end of the array.\n// Also, it panics if the index is not found.\n//\n// check the Index method for detailed usage.\nfunc (n *Node) MustIndex(expectIdx int) *Node {\n\tval, err := n.GetIndex(expectIdx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn val\n}\n\n// GetIndex returns the array element at the given index.\n//\n// if the index is negative, it returns the index is from the end of the array.\nfunc (n *Node) GetIndex(idx int) (*Node, error) {\n\tif n == nil {\n\t\treturn nil, errNilNode\n\t}\n\n\tif !n.IsArray() {\n\t\treturn nil, errNotArrayNode\n\t}\n\n\tif idx \u003e n.Size() {\n\t\treturn nil, errors.New(\"input index exceeds the array size\")\n\t}\n\n\tif idx \u003c 0 {\n\t\tidx += len(n.next)\n\t}\n\n\tchild, ok := n.next[strconv.Itoa(idx)]\n\tif !ok {\n\t\treturn nil, errIndexNotFound\n\t}\n\n\treturn child, nil\n}\n\n// DeleteIndex removes the array element at the given index.\nfunc (n *Node) DeleteIndex(idx int) error {\n\tnode, err := n.GetIndex(idx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn n.remove(node)\n}\n\n// NullNode creates a new null type node.\n//\n// Usage:\n//\n//\t_ := NullNode(\"\")\nfunc NullNode(key string) *Node {\n\treturn \u0026Node{\n\t\tkey: \u0026key,\n\t\tvalue: nil,\n\t\tnodeType: Null,\n\t\tmodified: true,\n\t}\n}\n\n// NumberNode creates a new number type node.\n//\n// Usage:\n//\n//\troot := NumberNode(\"\", 1)\n//\tif root == nil {\n//\t\tt.Errorf(\"NumberNode returns nil\")\n//\t}\nfunc NumberNode(key string, value float64) *Node {\n\treturn \u0026Node{\n\t\tkey: \u0026key,\n\t\tvalue: value,\n\t\tnodeType: Number,\n\t\tmodified: true,\n\t}\n}\n\n// StringNode creates a new string type node.\n//\n// Usage:\n//\n//\troot := StringNode(\"\", \"foo\")\n//\tif root == nil {\n//\t\tt.Errorf(\"StringNode returns nil\")\n//\t}\nfunc StringNode(key string, value string) *Node {\n\treturn \u0026Node{\n\t\tkey: \u0026key,\n\t\tvalue: value,\n\t\tnodeType: String,\n\t\tmodified: true,\n\t}\n}\n\n// BoolNode creates a new given boolean value node.\n//\n// Usage:\n//\n//\troot := BoolNode(\"\", true)\n//\tif root == nil {\n//\t\tt.Errorf(\"BoolNode returns nil\")\n//\t}\nfunc BoolNode(key string, value bool) *Node {\n\treturn \u0026Node{\n\t\tkey: \u0026key,\n\t\tvalue: value,\n\t\tnodeType: Boolean,\n\t\tmodified: true,\n\t}\n}\n\n// ArrayNode creates a new array type node.\n//\n// If the given value is nil, it creates an empty array node.\n//\n// Usage:\n//\n//\troot := ArrayNode(\"\", []*Node{StringNode(\"\", \"foo\"), NumberNode(\"\", 1)})\n//\tif root == nil {\n//\t\tt.Errorf(\"ArrayNode returns nil\")\n//\t}\nfunc ArrayNode(key string, value []*Node) *Node {\n\tcurr := \u0026Node{\n\t\tkey: \u0026key,\n\t\tnodeType: Array,\n\t\tmodified: true,\n\t}\n\n\tcurr.next = make(map[string]*Node, len(value))\n\tif value != nil {\n\t\tcurr.value = value\n\n\t\tfor i, v := range value {\n\t\t\tidx := i\n\t\t\tcurr.next[strconv.Itoa(i)] = v\n\n\t\t\tv.prev = curr\n\t\t\tv.index = \u0026idx\n\t\t}\n\t}\n\n\treturn curr\n}\n\n// ObjectNode creates a new object type node.\n//\n// If the given value is nil, it creates an empty object node.\n//\n// next is a map of key and value pairs of the object.\nfunc ObjectNode(key string, value map[string]*Node) *Node {\n\tcurr := \u0026Node{\n\t\tnodeType: Object,\n\t\tkey: \u0026key,\n\t\tnext: value,\n\t\tmodified: true,\n\t}\n\n\tif value != nil {\n\t\tcurr.value = value\n\n\t\tfor key, val := range value {\n\t\t\tvkey := key\n\t\t\tval.prev = curr\n\t\t\tval.key = \u0026vkey\n\t\t}\n\t} else {\n\t\tcurr.next = make(map[string]*Node)\n\t}\n\n\treturn curr\n}\n\n// IsArray returns true if the current node is array type.\nfunc (n *Node) IsArray() bool {\n\treturn n.nodeType == Array\n}\n\n// IsObject returns true if the current node is object type.\nfunc (n *Node) IsObject() bool {\n\treturn n.nodeType == Object\n}\n\n// IsNull returns true if the current node is null type.\nfunc (n *Node) IsNull() bool {\n\treturn n.nodeType == Null\n}\n\n// IsBool returns true if the current node is boolean type.\nfunc (n *Node) IsBool() bool {\n\treturn n.nodeType == Boolean\n}\n\n// IsString returns true if the current node is string type.\nfunc (n *Node) IsString() bool {\n\treturn n.nodeType == String\n}\n\n// IsNumber returns true if the current node is number type.\nfunc (n *Node) IsNumber() bool {\n\treturn n.nodeType == Number\n}\n\n// ready checks the current node is ready or not.\n//\n// the meaning of ready is the current node is parsed and has a valid value.\nfunc (n *Node) ready() bool {\n\treturn n.borders[1] != 0\n}\n\n// source returns the source of the current node.\nfunc (n *Node) source() []byte {\n\tif n == nil {\n\t\treturn nil\n\t}\n\n\tif n.ready() \u0026\u0026 !n.modified \u0026\u0026 n.data != nil {\n\t\treturn (n.data)[n.borders[0]:n.borders[1]]\n\t}\n\n\treturn nil\n}\n\n// root returns the root node of the current node.\nfunc (n *Node) root() *Node {\n\tif n == nil {\n\t\treturn nil\n\t}\n\n\tcurr := n\n\tfor curr.prev != nil {\n\t\tcurr = curr.prev\n\t}\n\n\treturn curr\n}\n\n// GetNull returns the null value if current node is null type.\n//\n// Usage:\n//\n//\troot := Unmarshal([]byte(\"null\"))\n//\tval, err := root.GetNull()\n//\tif err != nil {\n//\t\tt.Errorf(\"GetNull returns error: %v\", err)\n//\t}\n//\tif val != nil {\n//\t\tt.Errorf(\"GetNull returns wrong result: %v\", val)\n//\t}\nfunc (n *Node) GetNull() (interface{}, error) {\n\tif n == nil {\n\t\treturn nil, errNilNode\n\t}\n\n\tif !n.IsNull() {\n\t\treturn nil, errNotNullNode\n\t}\n\n\treturn nil, nil\n}\n\n// MustNull returns the null value if current node is null type.\n//\n// It panics if the current node is not null type.\nfunc (n *Node) MustNull() interface{} {\n\tv, err := n.GetNull()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn v\n}\n\n// GetNumeric returns the numeric (int/float) value if current node is number type.\n//\n// Usage:\n//\n//\troot := Unmarshal([]byte(\"10.5\"))\n//\tval, err := root.GetNumeric()\n//\tif err != nil {\n//\t\tt.Errorf(\"GetNumeric returns error: %v\", err)\n//\t}\n//\tprintln(val) // 10.5\nfunc (n *Node) GetNumeric() (float64, error) {\n\tif n == nil {\n\t\treturn 0, errNilNode\n\t}\n\n\tif n.nodeType != Number {\n\t\treturn 0, errNotNumberNode\n\t}\n\n\tval, err := n.Value()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tv, ok := val.(float64)\n\tif !ok {\n\t\treturn 0, errNotNumberNode\n\t}\n\n\treturn v, nil\n}\n\n// MustNumeric returns the numeric (int/float) value if current node is number type.\n//\n// It panics if the current node is not number type.\nfunc (n *Node) MustNumeric() float64 {\n\tv, err := n.GetNumeric()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn v\n}\n\n// GetString returns the string value if current node is string type.\n//\n// Usage:\n//\n//\troot, err := Unmarshal([]byte(\"foo\"))\n//\tif err != nil {\n//\t\tt.Errorf(\"Error on Unmarshal(): %s\", err)\n//\t}\n//\n//\tstr, err := root.GetString()\n//\tif err != nil {\n//\t\tt.Errorf(\"should retrieve string value: %s\", err)\n//\t}\n//\n//\tprintln(str) // \"foo\"\nfunc (n *Node) GetString() (string, error) {\n\tif n == nil {\n\t\treturn \"\", errEmptyStringNode\n\t}\n\n\tif !n.IsString() {\n\t\treturn \"\", errNotStringNode\n\t}\n\n\tval, err := n.Value()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tv, ok := val.(string)\n\tif !ok {\n\t\treturn \"\", errNotStringNode\n\t}\n\n\treturn v, nil\n}\n\n// MustString returns the string value if current node is string type.\n//\n// It panics if the current node is not string type.\nfunc (n *Node) MustString() string {\n\tv, err := n.GetString()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn v\n}\n\n// GetBool returns the boolean value if current node is boolean type.\n//\n// Usage:\n//\n//\troot := Unmarshal([]byte(\"true\"))\n//\tval, err := root.GetBool()\n//\tif err != nil {\n//\t\tt.Errorf(\"GetBool returns error: %v\", err)\n//\t}\n//\tprintln(val) // true\nfunc (n *Node) GetBool() (bool, error) {\n\tif n == nil {\n\t\treturn false, errNilNode\n\t}\n\n\tif n.nodeType != Boolean {\n\t\treturn false, errNotBoolNode\n\t}\n\n\tval, err := n.Value()\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tv, ok := val.(bool)\n\tif !ok {\n\t\treturn false, errNotBoolNode\n\t}\n\n\treturn v, nil\n}\n\n// MustBool returns the boolean value if current node is boolean type.\n//\n// It panics if the current node is not boolean type.\nfunc (n *Node) MustBool() bool {\n\tv, err := n.GetBool()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn v\n}\n\n// GetArray returns the array value if current node is array type.\n//\n// Usage:\n//\n//\t\troot := Must(Unmarshal([]byte(`[\"foo\", 1]`)))\n//\t\tarr, err := root.GetArray()\n//\t\tif err != nil {\n//\t\t\tt.Errorf(\"GetArray returns error: %v\", err)\n//\t\t}\n//\n//\t\tfor _, val := range arr {\n//\t\t\tprintln(val)\n//\t\t}\n//\n//\t result: \"foo\", 1\nfunc (n *Node) GetArray() ([]*Node, error) {\n\tif n == nil {\n\t\treturn nil, errNilNode\n\t}\n\n\tif n.nodeType != Array {\n\t\treturn nil, errNotArrayNode\n\t}\n\n\tval, err := n.Value()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tv, ok := val.([]*Node)\n\tif !ok {\n\t\treturn nil, errNotArrayNode\n\t}\n\n\treturn v, nil\n}\n\n// MustArray returns the array value if current node is array type.\n//\n// It panics if the current node is not array type.\nfunc (n *Node) MustArray() []*Node {\n\tv, err := n.GetArray()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn v\n}\n\n// AppendArray appends the given values to the current array node.\n//\n// If the current node is not array type, it returns an error.\n//\n// Example 1:\n//\n//\troot := Must(Unmarshal([]byte(`[{\"foo\":\"bar\"}]`)))\n//\tif err := root.AppendArray(NullNode(\"\")); err != nil {\n//\t\tt.Errorf(\"should not return error: %s\", err)\n//\t}\n//\n//\tresult: [{\"foo\":\"bar\"}, null]\n//\n// Example 2:\n//\n//\troot := Must(Unmarshal([]byte(`[\"bar\", \"baz\"]`)))\n//\terr := root.AppendArray(NumberNode(\"\", 1), StringNode(\"\", \"foo\"))\n//\tif err != nil {\n//\t\tt.Errorf(\"AppendArray returns error: %v\", err)\n//\t }\n//\n//\tresult: [\"bar\", \"baz\", 1, \"foo\"]\nfunc (n *Node) AppendArray(value ...*Node) error {\n\tif !n.IsArray() {\n\t\treturn errInvalidAppend\n\t}\n\n\tfor _, val := range value {\n\t\tif err := n.append(nil, val); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tn.mark()\n\treturn nil\n}\n\n// ArrayEach executes the callback for each element in the JSON array.\n//\n// Usage:\n//\n//\tjsonArrayNode.ArrayEach(func(i int, valueNode *Node) {\n//\t ufmt.Println(i, valueNode)\n//\t})\nfunc (n *Node) ArrayEach(callback func(i int, target *Node)) {\n\tif n == nil || !n.IsArray() {\n\t\treturn\n\t}\n\n\tfor idx := 0; idx \u003c len(n.next); idx++ {\n\t\telement, err := n.GetIndex(idx)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tcallback(idx, element)\n\t}\n}\n\n// GetObject returns the object value if current node is object type.\n//\n// Usage:\n//\n//\troot := Must(Unmarshal([]byte(`{\"key\": \"value\"}`)))\n//\tobj, err := root.GetObject()\n//\tif err != nil {\n//\t\tt.Errorf(\"GetObject returns error: %v\", err)\n//\t}\n//\n//\tresult: map[string]*Node{\"key\": StringNode(\"key\", \"value\")}\nfunc (n *Node) GetObject() (map[string]*Node, error) {\n\tif n == nil {\n\t\treturn nil, errNilNode\n\t}\n\n\tif !n.IsObject() {\n\t\treturn nil, errNotObjectNode\n\t}\n\n\tval, err := n.Value()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tv, ok := val.(map[string]*Node)\n\tif !ok {\n\t\treturn nil, errNotObjectNode\n\t}\n\n\treturn v, nil\n}\n\n// MustObject returns the object value if current node is object type.\n//\n// It panics if the current node is not object type.\nfunc (n *Node) MustObject() map[string]*Node {\n\tv, err := n.GetObject()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn v\n}\n\n// AppendObject appends the given key and value to the current object node.\n//\n// If the current node is not object type, it returns an error.\nfunc (n *Node) AppendObject(key string, value *Node) error {\n\tif !n.IsObject() {\n\t\treturn errInvalidAppend\n\t}\n\n\tif err := n.append(\u0026key, value); err != nil {\n\t\treturn err\n\t}\n\n\tn.mark()\n\treturn nil\n}\n\n// ObjectEach executes the callback for each key-value pair in the JSON object.\n//\n// Usage:\n//\n//\tjsonObjectNode.ObjectEach(func(key string, valueNode *Node) {\n//\t ufmt.Println(key, valueNode)\n//\t})\nfunc (n *Node) ObjectEach(callback func(key string, value *Node)) {\n\tif n == nil || !n.IsObject() {\n\t\treturn\n\t}\n\n\tfor key, child := range n.next {\n\t\tcallback(key, child)\n\t}\n}\n\n// String converts the node to a string representation.\nfunc (n *Node) String() string {\n\tif n == nil {\n\t\treturn \"\"\n\t}\n\n\tif n.ready() \u0026\u0026 !n.modified {\n\t\treturn string(n.source())\n\t}\n\n\tval, err := Marshal(n)\n\tif err != nil {\n\t\treturn \"error: \" + err.Error()\n\t}\n\n\treturn string(val)\n}\n\n// Path builds the path of the current node.\n//\n// For example:\n//\n//\t{ \"key\": { \"sub\": [ \"val1\", \"val2\" ] }}\n//\n// The path of \"val2\" is: $.key.sub[1]\nfunc (n *Node) Path() string {\n\tif n == nil {\n\t\treturn \"\"\n\t}\n\n\tvar sb strings.Builder\n\n\tif n.prev == nil {\n\t\tsb.WriteString(\"$\")\n\t} else {\n\t\tsb.WriteString(n.prev.Path())\n\n\t\tif n.key != nil {\n\t\t\tsb.WriteString(\"['\" + n.Key() + \"']\")\n\t\t} else {\n\t\t\tsb.WriteString(\"[\" + strconv.Itoa(n.Index()) + \"]\")\n\t\t}\n\t}\n\n\treturn sb.String()\n}\n\n// mark marks the current node as modified.\nfunc (n *Node) mark() {\n\tnode := n\n\tfor node != nil \u0026\u0026 !node.modified {\n\t\tnode.modified = true\n\t\tnode = node.prev\n\t}\n}\n\n// isContainer checks the current node type is array or object.\nfunc (n *Node) isContainer() bool {\n\treturn n.IsArray() || n.IsObject()\n}\n\n// remove removes the value from the current container type node.\nfunc (n *Node) remove(v *Node) error {\n\tif !n.isContainer() {\n\t\treturn ufmt.Errorf(\n\t\t\t\"can't remove value from non-array or non-object node. got=%s\",\n\t\t\tn.Type().String(),\n\t\t)\n\t}\n\n\tif v.prev != n {\n\t\treturn errors.New(\"invalid parent node\")\n\t}\n\n\tn.mark()\n\tif n.IsArray() {\n\t\tdelete(n.next, strconv.Itoa(*v.index))\n\t\tn.dropIndex(*v.index)\n\t} else {\n\t\tdelete(n.next, *v.key)\n\t}\n\n\tv.prev = nil\n\treturn nil\n}\n\n// dropIndex rebase the index of current array node values.\nfunc (n *Node) dropIndex(idx int) {\n\tfor i := idx + 1; i \u003c= len(n.next); i++ {\n\t\tprv := i - 1\n\t\tif curr, ok := n.next[strconv.Itoa(i)]; ok {\n\t\t\tcurr.index = \u0026prv\n\t\t\tn.next[strconv.Itoa(prv)] = curr\n\t\t}\n\n\t\tdelete(n.next, strconv.Itoa(i))\n\t}\n}\n\n// append is a helper function to append the given value to the current container type node.\nfunc (n *Node) append(key *string, val *Node) error {\n\tif n.isSameOrParentNode(val) {\n\t\treturn errInvalidAppendCycle\n\t}\n\n\tif val.prev != nil {\n\t\tif err := val.prev.remove(val); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tval.prev = n\n\tval.key = key\n\n\tif key == nil {\n\t\tsize := len(n.next)\n\t\tval.index = \u0026size\n\t\tn.next[strconv.Itoa(size)] = val\n\t} else {\n\t\tif old, ok := n.next[*key]; ok {\n\t\t\tif err := n.remove(old); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tn.next[*key] = val\n\t}\n\n\treturn nil\n}\n\nfunc (n *Node) isSameOrParentNode(nd *Node) bool {\n\treturn n == nd || n.isParentNode(nd)\n}\n\nfunc (n *Node) isParentNode(nd *Node) bool {\n\tif n == nil {\n\t\treturn false\n\t}\n\n\tfor curr := nd.prev; curr != nil; curr = curr.prev {\n\t\tif curr == n {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n// cptrs returns the pointer of the given string value.\nfunc cptrs(cpy *string) *string {\n\tif cpy == nil {\n\t\treturn nil\n\t}\n\n\tval := *cpy\n\n\treturn \u0026val\n}\n\n// cptri returns the pointer of the given integer value.\nfunc cptri(i *int) *int {\n\tif i == nil {\n\t\treturn nil\n\t}\n\n\tval := *i\n\treturn \u0026val\n}\n\n// Must panics if the given node is not fulfilled the expectation.\n// Usage:\n//\n//\tnode := Must(Unmarshal([]byte(`{\"key\": \"value\"}`))\nfunc Must(root *Node, expect error) *Node {\n\tif expect != nil {\n\t\tpanic(expect)\n\t}\n\n\treturn root\n}\n"},{"name":"node_test.gno","body":"package json\n\nimport (\n\t\"bytes\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar (\n\tnilKey *string\n\tdummyKey = \"key\"\n)\n\ntype _args struct {\n\tprev *Node\n\tbuf *buffer\n\ttyp ValueType\n\tkey **string\n}\n\ntype simpleNode struct {\n\tname string\n\tnode *Node\n}\n\nfunc TestNode_CreateNewNode(t *testing.T) {\n\trel := \u0026dummyKey\n\n\ttests := []struct {\n\t\tname string\n\t\targs _args\n\t\texpectCurr *Node\n\t\texpectErr bool\n\t\texpectPanic bool\n\t}{\n\t\t{\n\t\t\tname: \"child for non container type\",\n\t\t\targs: _args{\n\t\t\t\tprev: BoolNode(\"\", true),\n\t\t\t\tbuf: newBuffer(make([]byte, 10)),\n\t\t\t\ttyp: Boolean,\n\t\t\t\tkey: \u0026rel,\n\t\t\t},\n\t\t\texpectCurr: nil,\n\t\t\texpectErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tdefer func() {\n\t\t\t\tif r := recover(); r != nil {\n\t\t\t\t\tif tt.expectPanic {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tt.Errorf(\"%s panic occurred when not expected: %v\", tt.name, r)\n\t\t\t\t} else if tt.expectPanic {\n\t\t\t\t\tt.Errorf(\"%s expected panic but didn't occur\", tt.name)\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\tgot, err := NewNode(tt.args.prev, tt.args.buf, tt.args.typ, tt.args.key)\n\t\t\tif (err != nil) != tt.expectErr {\n\t\t\t\tt.Errorf(\"%s error = %v, expect error %v\", tt.name, err, tt.expectErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif tt.expectErr {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif !compareNodes(got, tt.expectCurr) {\n\t\t\t\tt.Errorf(\"%s got = %v, want %v\", tt.name, got, tt.expectCurr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_Value(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tdata []byte\n\t\t_type ValueType\n\t\texpected interface{}\n\t\terrExpected bool\n\t}{\n\t\t{name: \"null\", data: []byte(\"null\"), _type: Null, expected: nil},\n\t\t{name: \"1\", data: []byte(\"1\"), _type: Number, expected: float64(1)},\n\t\t{name: \".1\", data: []byte(\".1\"), _type: Number, expected: float64(.1)},\n\t\t{name: \"-.1e1\", data: []byte(\"-.1e1\"), _type: Number, expected: float64(-1)},\n\t\t{name: \"string\", data: []byte(\"\\\"foo\\\"\"), _type: String, expected: \"foo\"},\n\t\t{name: \"space\", data: []byte(\"\\\"foo bar\\\"\"), _type: String, expected: \"foo bar\"},\n\t\t{name: \"true\", data: []byte(\"true\"), _type: Boolean, expected: true},\n\t\t{name: \"invalid true\", data: []byte(\"tru\"), _type: Unknown, errExpected: true},\n\t\t{name: \"invalid false\", data: []byte(\"fals\"), _type: Unknown, errExpected: true},\n\t\t{name: \"false\", data: []byte(\"false\"), _type: Boolean, expected: false},\n\t\t{name: \"e1\", data: []byte(\"e1\"), _type: Unknown, errExpected: true},\n\t\t{name: \"1a\", data: []byte(\"1a\"), _type: Unknown, errExpected: true},\n\t\t{name: \"string error\", data: []byte(\"\\\"foo\\nbar\\\"\"), _type: String, errExpected: true},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tcurr := \u0026Node{\n\t\t\t\tdata: tt.data,\n\t\t\t\tnodeType: tt._type,\n\t\t\t\tborders: [2]int{0, len(tt.data)},\n\t\t\t}\n\n\t\t\tgot, err := curr.Value()\n\t\t\tif err != nil {\n\t\t\t\tif !tt.errExpected {\n\t\t\t\t\tt.Errorf(\"%s error = %v, expect error %v\", tt.name, err, tt.errExpected)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif got != tt.expected {\n\t\t\t\tt.Errorf(\"%s got = %v, want %v\", tt.name, got, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_Delete(t *testing.T) {\n\troot := Must(Unmarshal([]byte(`{\"foo\":\"bar\"}`)))\n\tif err := root.Delete(); err != nil {\n\t\tt.Errorf(\"Delete returns error: %v\", err)\n\t}\n\n\tif value, err := Marshal(root); err != nil {\n\t\tt.Errorf(\"Marshal returns error: %v\", err)\n\t} else if string(value) != `{\"foo\":\"bar\"}` {\n\t\tt.Errorf(\"Marshal returns wrong value: %s\", string(value))\n\t}\n\n\tfoo := root.MustKey(\"foo\")\n\tif err := foo.Delete(); err != nil {\n\t\tt.Errorf(\"Delete returns error while handling foo: %v\", err)\n\t}\n\n\tif value, err := Marshal(root); err != nil {\n\t\tt.Errorf(\"Marshal returns error: %v\", err)\n\t} else if string(value) != `{}` {\n\t\tt.Errorf(\"Marshal returns wrong value: %s\", string(value))\n\t}\n\n\tif value, err := Marshal(foo); err != nil {\n\t\tt.Errorf(\"Marshal returns error: %v\", err)\n\t} else if string(value) != `\"bar\"` {\n\t\tt.Errorf(\"Marshal returns wrong value: %s\", string(value))\n\t}\n\n\tif foo.prev != nil {\n\t\tt.Errorf(\"foo.prev should be nil\")\n\t}\n}\n\nfunc TestNode_ObjectNode(t *testing.T) {\n\tobjs := map[string]*Node{\n\t\t\"key1\": NullNode(\"null\"),\n\t\t\"key2\": NumberNode(\"answer\", 42),\n\t\t\"key3\": StringNode(\"string\", \"foobar\"),\n\t\t\"key4\": BoolNode(\"bool\", true),\n\t}\n\n\tnode := ObjectNode(\"test\", objs)\n\n\tif len(node.next) != len(objs) {\n\t\tt.Errorf(\"ObjectNode: want %v got %v\", len(objs), len(node.next))\n\t}\n\n\tfor k, v := range objs {\n\t\tif node.next[k] == nil {\n\t\t\tt.Errorf(\"ObjectNode: want %v got %v\", v, node.next[k])\n\t\t}\n\t}\n}\n\nfunc TestNode_AppendObject(t *testing.T) {\n\tif err := Must(Unmarshal([]byte(`{\"foo\":\"bar\",\"baz\":null}`))).AppendObject(\"biz\", NullNode(\"\")); err != nil {\n\t\tt.Errorf(\"AppendArray should return error\")\n\t}\n\n\troot := Must(Unmarshal([]byte(`{\"foo\":\"bar\"}`)))\n\tif err := root.AppendObject(\"baz\", NullNode(\"\")); err != nil {\n\t\tt.Errorf(\"AppendObject should not return error: %s\", err)\n\t}\n\n\tif value, err := Marshal(root); err != nil {\n\t\tt.Errorf(\"Marshal returns error: %v\", err)\n\t} else if isSameObject(string(value), `\"{\"foo\":\"bar\",\"baz\":null}\"`) {\n\t\tt.Errorf(\"Marshal returns wrong value: %s\", string(value))\n\t}\n\n\t// FIXME: this may fail if execute test in more than 3 times in a row.\n\tif err := root.AppendObject(\"biz\", NumberNode(\"\", 42)); err != nil {\n\t\tt.Errorf(\"AppendObject returns error: %v\", err)\n\t}\n\n\tval, err := Marshal(root)\n\tif err != nil {\n\t\tt.Errorf(\"Marshal returns error: %v\", err)\n\t}\n\n\t// FIXME: this may fail if execute test in more than 3 times in a row.\n\tif isSameObject(string(val), `\"{\"foo\":\"bar\",\"baz\":null,\"biz\":42}\"`) {\n\t\tt.Errorf(\"Marshal returns wrong value: %s\", string(val))\n\t}\n}\n\nfunc TestNode_ArrayNode(t *testing.T) {\n\tarr := []*Node{\n\t\tNullNode(\"nil\"),\n\t\tNumberNode(\"num\", 42),\n\t\tStringNode(\"str\", \"foobar\"),\n\t\tBoolNode(\"bool\", true),\n\t}\n\n\tnode := ArrayNode(\"test\", arr)\n\n\tif len(node.next) != len(arr) {\n\t\tt.Errorf(\"ArrayNode: want %v got %v\", len(arr), len(node.next))\n\t}\n\n\tfor i, v := range arr {\n\t\tif node.next[strconv.Itoa(i)] == nil {\n\t\t\tt.Errorf(\"ArrayNode: want %v got %v\", v, node.next[strconv.Itoa(i)])\n\t\t}\n\t}\n}\n\nfunc TestNode_AppendArray(t *testing.T) {\n\tif err := Must(Unmarshal([]byte(`[{\"foo\":\"bar\"}]`))).AppendArray(NullNode(\"\")); err != nil {\n\t\tt.Errorf(\"should return error\")\n\t}\n\n\troot := Must(Unmarshal([]byte(`[{\"foo\":\"bar\"}]`)))\n\tif err := root.AppendArray(NullNode(\"\")); err != nil {\n\t\tt.Errorf(\"should not return error: %s\", err)\n\t}\n\n\tif value, err := Marshal(root); err != nil {\n\t\tt.Errorf(\"Marshal returns error: %v\", err)\n\t} else if string(value) != `[{\"foo\":\"bar\"},null]` {\n\t\tt.Errorf(\"Marshal returns wrong value: %s\", string(value))\n\t}\n\n\tif err := root.AppendArray(\n\t\tNumberNode(\"\", 1),\n\t\tStringNode(\"\", \"foo\"),\n\t\tMust(Unmarshal([]byte(`[0,1,null,true,\"example\"]`))),\n\t\tMust(Unmarshal([]byte(`{\"foo\": true, \"bar\": null, \"baz\": 123}`))),\n\t); err != nil {\n\t\tt.Errorf(\"AppendArray returns error: %v\", err)\n\t}\n\n\tif value, err := Marshal(root); err != nil {\n\t\tt.Errorf(\"Marshal returns error: %v\", err)\n\t} else if string(value) != `[{\"foo\":\"bar\"},null,1,\"foo\",[0,1,null,true,\"example\"],{\"foo\": true, \"bar\": null, \"baz\": 123}]` {\n\t\tt.Errorf(\"Marshal returns wrong value: %s\", string(value))\n\t}\n}\n\n/******** value getter ********/\n\nfunc TestNode_GetBool(t *testing.T) {\n\troot, err := Unmarshal([]byte(`true`))\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err.Error())\n\t\treturn\n\t}\n\n\tvalue, err := root.GetBool()\n\tif err != nil {\n\t\tt.Errorf(\"Error on root.GetBool(): %s\", err.Error())\n\t}\n\n\tif !value {\n\t\tt.Errorf(\"root.GetBool() is corrupted\")\n\t}\n}\n\nfunc TestNode_GetBool_Fail(t *testing.T) {\n\ttests := []simpleNode{\n\t\t{\"nil node\", (*Node)(nil)},\n\t\t{\"literally null node\", NullNode(\"\")},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif _, err := tt.node.GetBool(); err == nil {\n\t\t\t\tt.Errorf(\"%s should be an error\", tt.name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_IsBool(t *testing.T) {\n\ttests := []simpleNode{\n\t\t{\"true\", BoolNode(\"\", true)},\n\t\t{\"false\", BoolNode(\"\", false)},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif !tt.node.IsBool() {\n\t\t\t\tt.Errorf(\"%s should be a bool\", tt.name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_IsBool_With_Unmarshal(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tjson []byte\n\t\twant bool\n\t}{\n\t\t{\"true\", []byte(\"true\"), true},\n\t\t{\"false\", []byte(\"false\"), true},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\troot, err := Unmarshal(tt.json)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Error on Unmarshal(): %s\", err.Error())\n\t\t\t}\n\n\t\t\tif root.IsBool() != tt.want {\n\t\t\t\tt.Errorf(\"%s should be a bool\", tt.name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nvar nullJson = []byte(`null`)\n\nfunc TestNode_GetNull(t *testing.T) {\n\troot, err := Unmarshal(nullJson)\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err.Error())\n\t}\n\n\tvalue, err := root.GetNull()\n\tif err != nil {\n\t\tt.Errorf(\"error occurred while getting null, %s\", err)\n\t}\n\n\tif value != nil {\n\t\tt.Errorf(\"value is not matched. expected: nil, got: %v\", value)\n\t}\n}\n\nfunc TestNode_GetNull_Fail(t *testing.T) {\n\ttests := []simpleNode{\n\t\t{\"nil node\", (*Node)(nil)},\n\t\t{\"number node is null\", NumberNode(\"\", 42)},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif _, err := tt.node.GetNull(); err == nil {\n\t\t\t\tt.Errorf(\"%s should be an error\", tt.name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_MustNull(t *testing.T) {\n\troot, err := Unmarshal(nullJson)\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err.Error())\n\t}\n\n\tvalue := root.MustNull()\n\tif value != nil {\n\t\tt.Errorf(\"value is not matched. expected: nil, got: %v\", value)\n\t}\n}\n\nfunc TestNode_GetNumeric_Float(t *testing.T) {\n\troot, err := Unmarshal([]byte(`123.456`))\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err)\n\t\treturn\n\t}\n\n\tvalue, err := root.GetNumeric()\n\tif err != nil {\n\t\tt.Errorf(\"Error on root.GetNumeric(): %s\", err)\n\t}\n\n\tif value != float64(123.456) {\n\t\tt.Errorf(ufmt.Sprintf(\"value is not matched. expected: 123.456, got: %v\", value))\n\t}\n}\n\nfunc TestNode_GetNumeric_Scientific_Notation(t *testing.T) {\n\troot, err := Unmarshal([]byte(`1e3`))\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err)\n\t\treturn\n\t}\n\n\tvalue, err := root.GetNumeric()\n\tif err != nil {\n\t\tt.Errorf(\"Error on root.GetNumeric(): %s\", err)\n\t}\n\n\tif value != float64(1000) {\n\t\tt.Errorf(ufmt.Sprintf(\"value is not matched. expected: 1000, got: %v\", value))\n\t}\n}\n\nfunc TestNode_GetNumeric_With_Unmarshal(t *testing.T) {\n\troot, err := Unmarshal([]byte(`123`))\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err)\n\t\treturn\n\t}\n\n\tvalue, err := root.GetNumeric()\n\tif err != nil {\n\t\tt.Errorf(\"Error on root.GetNumeric(): %s\", err)\n\t}\n\n\tif value != float64(123) {\n\t\tt.Errorf(ufmt.Sprintf(\"value is not matched. expected: 123, got: %v\", value))\n\t}\n}\n\nfunc TestNode_GetNumeric_Fail(t *testing.T) {\n\ttests := []simpleNode{\n\t\t{\"nil node\", (*Node)(nil)},\n\t\t{\"null node\", NullNode(\"\")},\n\t\t{\"string node\", StringNode(\"\", \"123\")},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif _, err := tt.node.GetNumeric(); err == nil {\n\t\t\t\tt.Errorf(\"%s should be an error\", tt.name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_GetString(t *testing.T) {\n\troot, err := Unmarshal([]byte(`\"123foobar 3456\"`))\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err)\n\t}\n\n\tvalue, err := root.GetString()\n\tif err != nil {\n\t\tt.Errorf(\"Error on root.GetString(): %s\", err)\n\t}\n\n\tif value != \"123foobar 3456\" {\n\t\tt.Errorf(ufmt.Sprintf(\"value is not matched. expected: 123, got: %s\", value))\n\t}\n}\n\nfunc TestNode_GetString_Fail(t *testing.T) {\n\ttests := []simpleNode{\n\t\t{\"nil node\", (*Node)(nil)},\n\t\t{\"null node\", NullNode(\"\")},\n\t\t{\"number node\", NumberNode(\"\", 123)},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif _, err := tt.node.GetString(); err == nil {\n\t\t\t\tt.Errorf(\"%s should be an error\", tt.name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_MustString(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tdata []byte\n\t}{\n\t\t{\"foo\", []byte(`\"foo\"`)},\n\t\t{\"foo bar\", []byte(`\"foo bar\"`)},\n\t\t{\"\", []byte(`\"\"`)},\n\t\t{\"안녕하세요\", []byte(`\"안녕하세요\"`)},\n\t\t{\"こんにちは\", []byte(`\"こんにちは\"`)},\n\t\t{\"你好\", []byte(`\"你好\"`)},\n\t\t{\"one \\\"encoded\\\" string\", []byte(`\"one \\\"encoded\\\" string\"`)},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\troot, err := Unmarshal(tt.data)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Error on Unmarshal(): %s\", err)\n\t\t\t}\n\n\t\t\tvalue := root.MustString()\n\t\t\tif value != tt.name {\n\t\t\t\tt.Errorf(\"value is not matched. expected: %s, got: %s\", tt.name, value)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal_Array(t *testing.T) {\n\troot, err := Unmarshal([]byte(\" [1,[\\\"1\\\",[1,[1,2,3]]]]\\r\\n\"))\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal: %s\", err.Error())\n\t}\n\n\tif root == nil {\n\t\tt.Errorf(\"Error on Unmarshal: root is nil\")\n\t}\n\n\tif root.Type() != Array {\n\t\tt.Errorf(\"Error on Unmarshal: wrong type\")\n\t}\n\n\tarray, err := root.GetArray()\n\tif err != nil {\n\t\tt.Errorf(\"error occurred while getting array, %s\", err)\n\t} else if len(array) != 2 {\n\t\tt.Errorf(\"expected 2 elements, got %d\", len(array))\n\t} else if val, err := array[0].GetNumeric(); err != nil {\n\t\tt.Errorf(\"value of array[0] is not numeric. got: %v\", array[0].value)\n\t} else if val != 1 {\n\t\tt.Errorf(\"Error on array[0].GetNumeric(): expected to be '1', got: %v\", val)\n\t} else if val, err := array[1].GetArray(); err != nil {\n\t\tt.Errorf(\"error occurred while getting array, %s\", err.Error())\n\t} else if len(val) != 2 {\n\t\tt.Errorf(\"Error on array[1].GetArray(): expected 2 elements, got %d\", len(val))\n\t} else if el, err := val[0].GetString(); err != nil {\n\t\tt.Errorf(\"error occurred while getting string, %s\", err.Error())\n\t} else if el != \"1\" {\n\t\tt.Errorf(\"Error on val[0].GetString(): expected to be '1', got: %s\", el)\n\t}\n}\n\nvar sampleArr = []byte(`[-1, 2, 3, 4, 5, 6]`)\n\nfunc TestNode_GetArray(t *testing.T) {\n\troot, err := Unmarshal(sampleArr)\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err)\n\t\treturn\n\t}\n\n\tarray, err := root.GetArray()\n\tif err != nil {\n\t\tt.Errorf(\"Error on root.GetArray(): %s\", err)\n\t}\n\n\tif len(array) != 6 {\n\t\tt.Errorf(ufmt.Sprintf(\"length is not matched. expected: 3, got: %d\", len(array)))\n\t}\n\n\tfor i, node := range array {\n\t\tfor j, val := range []int{-1, 2, 3, 4, 5, 6} {\n\t\t\tif i == j {\n\t\t\t\tif v, err := node.GetNumeric(); err != nil {\n\t\t\t\t\tt.Errorf(ufmt.Sprintf(\"Error on node.GetNumeric(): %s\", err))\n\t\t\t\t} else if v != float64(val) {\n\t\t\t\t\tt.Errorf(ufmt.Sprintf(\"value is not matched. expected: %d, got: %v\", val, v))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestNode_GetArray_Fail(t *testing.T) {\n\ttests := []simpleNode{\n\t\t{\"nil node\", (*Node)(nil)},\n\t\t{\"null node\", NullNode(\"\")},\n\t\t{\"number node\", NumberNode(\"\", 123)},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif _, err := tt.node.GetArray(); err == nil {\n\t\t\t\tt.Errorf(\"%s should be an error\", tt.name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_IsArray(t *testing.T) {\n\troot, err := Unmarshal(sampleArr)\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err)\n\t\treturn\n\t}\n\n\tif root.Type() != Array {\n\t\tt.Errorf(ufmt.Sprintf(\"Must be an array. got: %s\", root.Type().String()))\n\t}\n}\n\nfunc TestNode_ArrayEach(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tjson string\n\t\texpected []int\n\t}{\n\t\t{\n\t\t\tname: \"empty array\",\n\t\t\tjson: `[]`,\n\t\t\texpected: []int{},\n\t\t},\n\t\t{\n\t\t\tname: \"single element\",\n\t\t\tjson: `[42]`,\n\t\t\texpected: []int{42},\n\t\t},\n\t\t{\n\t\t\tname: \"multiple elements\",\n\t\t\tjson: `[1, 2, 3, 4, 5]`,\n\t\t\texpected: []int{1, 2, 3, 4, 5},\n\t\t},\n\t\t{\n\t\t\tname: \"multiple elements but all values are same\",\n\t\t\tjson: `[1, 1, 1, 1, 1]`,\n\t\t\texpected: []int{1, 1, 1, 1, 1},\n\t\t},\n\t\t{\n\t\t\tname: \"multiple elements with non-numeric values\",\n\t\t\tjson: `[\"a\", \"b\", \"c\", \"d\", \"e\"]`,\n\t\t\texpected: []int{},\n\t\t},\n\t\t{\n\t\t\tname: \"non-array node\",\n\t\t\tjson: `{\"not\": \"an array\"}`,\n\t\t\texpected: []int{},\n\t\t},\n\t\t{\n\t\t\tname: \"array containing numeric and non-numeric elements\",\n\t\t\tjson: `[\"1\", 2, 3, \"4\", 5, \"6\"]`,\n\t\t\texpected: []int{2, 3, 5},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\troot, err := Unmarshal([]byte(tc.json))\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Unmarshal failed: %v\", err)\n\t\t\t}\n\n\t\t\tvar result []int // callback result\n\t\t\troot.ArrayEach(func(index int, element *Node) {\n\t\t\t\tif val, err := strconv.Atoi(element.String()); err == nil {\n\t\t\t\t\tresult = append(result, val)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tif len(result) != len(tc.expected) {\n\t\t\t\tt.Errorf(\"%s: expected %d elements, got %d\", tc.name, len(tc.expected), len(result))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfor i, val := range result {\n\t\t\t\tif val != tc.expected[i] {\n\t\t\t\t\tt.Errorf(\"%s: expected value at index %d to be %d, got %d\", tc.name, i, tc.expected[i], val)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_Key(t *testing.T) {\n\troot, err := Unmarshal([]byte(`{\"foo\": true, \"bar\": null, \"baz\": 123, \"biz\": [1,2,3]}`))\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err.Error())\n\t}\n\n\tobj := root.MustObject()\n\tfor key, node := range obj {\n\t\tif key != node.Key() {\n\t\t\tt.Errorf(\"Key() = %v, want %v\", node.Key(), key)\n\t\t}\n\t}\n\n\tkeys := []string{\"foo\", \"bar\", \"baz\", \"biz\"}\n\tfor _, key := range keys {\n\t\tif obj[key].Key() != key {\n\t\t\tt.Errorf(\"Key() = %v, want %v\", obj[key].Key(), key)\n\t\t}\n\t}\n\n\t// TODO: resolve stack overflow\n\t// if root.MustKey(\"foo\").Clone().Key() != \"\" {\n\t// \tt.Errorf(\"wrong key found for cloned key\")\n\t// }\n\n\tif (*Node)(nil).Key() != \"\" {\n\t\tt.Errorf(\"wrong key found for nil node\")\n\t}\n}\n\nfunc TestNode_Size(t *testing.T) {\n\troot, err := Unmarshal(sampleArr)\n\tif err != nil {\n\t\tt.Errorf(\"error occurred while unmarshal\")\n\t}\n\n\tsize := root.Size()\n\tif size != 6 {\n\t\tt.Errorf(ufmt.Sprintf(\"Size() must be 6. got: %v\", size))\n\t}\n\n\tif (*Node)(nil).Size() != 0 {\n\t\tt.Errorf(ufmt.Sprintf(\"Size() must be 0. got: %v\", (*Node)(nil).Size()))\n\t}\n}\n\nfunc TestNode_Index(t *testing.T) {\n\troot, err := Unmarshal([]byte(`[1, 2, 3, 4, 5, 6]`))\n\tif err != nil {\n\t\tt.Error(\"error occurred while unmarshal\")\n\t}\n\n\tarr := root.MustArray()\n\tfor i, node := range arr {\n\t\tif i != node.Index() {\n\t\t\tt.Errorf(ufmt.Sprintf(\"Index() must be nil. got: %v\", i))\n\t\t}\n\t}\n}\n\nfunc TestNode_Index_Fail(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tnode *Node\n\t\twant int\n\t}{\n\t\t{\"nil node\", (*Node)(nil), -1},\n\t\t{\"null node\", NullNode(\"\"), -1},\n\t\t{\"object node\", ObjectNode(\"\", nil), -1},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := tt.node.Index(); got != tt.want {\n\t\t\t\tt.Errorf(\"Index() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_GetIndex(t *testing.T) {\n\troot := Must(Unmarshal([]byte(`[1, 2, 3, 4, 5, 6]`)))\n\texpected := []int{1, 2, 3, 4, 5, 6}\n\n\tif len(expected) != root.Size() {\n\t\tt.Errorf(\"length is not matched. expected: %d, got: %d\", len(expected), root.Size())\n\t}\n\n\t// TODO: if length exceeds, stack overflow occurs. need to fix\n\tfor i, v := range expected {\n\t\tval, err := root.GetIndex(i)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"error occurred while getting index %d, %s\", i, err)\n\t\t}\n\n\t\tif val.MustNumeric() != float64(v) {\n\t\t\tt.Errorf(\"value is not matched. expected: %d, got: %v\", v, val.MustNumeric())\n\t\t}\n\t}\n}\n\nfunc TestNode_GetIndex_InputIndex_Exceed_Original_Node_Index(t *testing.T) {\n\troot, err := Unmarshal([]byte(`[1, 2, 3, 4, 5, 6]`))\n\tif err != nil {\n\t\tt.Errorf(\"error occurred while unmarshal\")\n\t}\n\n\t_, err = root.GetIndex(10)\n\tif err == nil {\n\t\tt.Errorf(\"GetIndex should return error\")\n\t}\n}\n\nfunc TestNode_DeleteIndex(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\texpected string\n\t\tindex int\n\t\tok bool\n\t}{\n\t\t{`null`, ``, 0, false},\n\t\t{`1`, ``, 0, false},\n\t\t{`{}`, ``, 0, false},\n\t\t{`{\"foo\":\"bar\"}`, ``, 0, false},\n\t\t{`true`, ``, 0, false},\n\t\t{`[]`, ``, 0, false},\n\t\t{`[]`, ``, -1, false},\n\t\t{`[1]`, `[]`, 0, true},\n\t\t{`[{}]`, `[]`, 0, true},\n\t\t{`[{}, [], 42]`, `[{}, []]`, -1, true},\n\t\t{`[{}, [], 42]`, `[[], 42]`, 0, true},\n\t\t{`[{}, [], 42]`, `[{}, 42]`, 1, true},\n\t\t{`[{}, [], 42]`, `[{}, []]`, 2, true},\n\t\t{`[{}, [], 42]`, ``, 10, false},\n\t\t{`[{}, [], 42]`, ``, -10, false},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\troot := Must(Unmarshal([]byte(tt.name)))\n\t\t\terr := root.DeleteIndex(tt.index)\n\t\t\tif err != nil \u0026\u0026 tt.ok {\n\t\t\t\tt.Errorf(\"DeleteIndex returns error: %v\", err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_GetKey(t *testing.T) {\n\troot, err := Unmarshal([]byte(`{\"foo\": true, \"bar\": null}`))\n\tif err != nil {\n\t\tt.Error(\"error occurred while unmarshal\")\n\t}\n\n\tvalue, err := root.GetKey(\"foo\")\n\tif err != nil {\n\t\tt.Errorf(\"error occurred while getting key, %s\", err)\n\t}\n\n\tif value.MustBool() != true {\n\t\tt.Errorf(\"value is not matched. expected: true, got: %v\", value.MustBool())\n\t}\n\n\tvalue, err = root.GetKey(\"bar\")\n\tif err != nil {\n\t\tt.Errorf(\"error occurred while getting key, %s\", err)\n\t}\n\n\t_, err = root.GetKey(\"baz\")\n\tif err == nil {\n\t\tt.Errorf(\"key baz is not exist. must be failed\")\n\t}\n\n\tif value.MustNull() != nil {\n\t\tt.Errorf(\"value is not matched. expected: nil, got: %v\", value.MustNull())\n\t}\n}\n\nfunc TestNode_GetKey_Fail(t *testing.T) {\n\ttests := []simpleNode{\n\t\t{\"nil node\", (*Node)(nil)},\n\t\t{\"null node\", NullNode(\"\")},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif _, err := tt.node.GetKey(\"\"); err == nil {\n\t\t\t\tt.Errorf(\"%s should be an error\", tt.name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_GetUniqueKeyList(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tjson string\n\t\texpected []string\n\t}{\n\t\t{\n\t\t\tname: \"simple foo/bar\",\n\t\t\tjson: `{\"foo\": true, \"bar\": null}`,\n\t\t\texpected: []string{\"foo\", \"bar\"},\n\t\t},\n\t\t{\n\t\t\tname: \"empty object\",\n\t\t\tjson: `{}`,\n\t\t\texpected: []string{},\n\t\t},\n\t\t{\n\t\t\tname: \"nested object\",\n\t\t\tjson: `{\n\t\t\t\t\"outer\": {\n\t\t\t\t\t\"inner\": {\n\t\t\t\t\t\t\"key\": \"value\"\n\t\t\t\t\t},\n\t\t\t\t\t\"array\": [1, 2, 3]\n\t\t\t\t},\n\t\t\t\t\"another\": \"item\"\n\t\t\t}`,\n\t\t\texpected: []string{\"outer\", \"inner\", \"key\", \"array\", \"another\"},\n\t\t},\n\t\t{\n\t\t\tname: \"complex object\",\n\t\t\tjson: `{\n\t\t\t\t\"Image\": {\n\t\t\t\t\t\"Width\": 800,\n\t\t\t\t\t\"Height\": 600,\n\t\t\t\t\t\"Title\": \"View from 15th Floor\",\n\t\t\t\t\t\"Thumbnail\": {\n\t\t\t\t\t\t\"Url\": \"http://www.example.com/image/481989943\",\n\t\t\t\t\t\t\"Height\": 125,\n\t\t\t\t\t\t\"Width\": 100\n\t\t\t\t\t},\n\t\t\t\t\t\"Animated\": false,\n\t\t\t\t\t\"IDs\": [116, 943, 234, 38793]\n\t\t\t\t}\n\t\t\t}`,\n\t\t\texpected: []string{\"Image\", \"Width\", \"Height\", \"Title\", \"Thumbnail\", \"Url\", \"Animated\", \"IDs\"},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\troot, err := Unmarshal([]byte(tt.json))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error occurred while unmarshal\")\n\t\t\t}\n\n\t\t\tvalue := root.UniqueKeyLists()\n\t\t\tif len(value) != len(tt.expected) {\n\t\t\t\tt.Errorf(\"%s length must be %v. got: %v. retrieved keys: %s\", tt.name, len(tt.expected), len(value), value)\n\t\t\t}\n\n\t\t\tfor _, key := range value {\n\t\t\t\tif !contains(tt.expected, key) {\n\t\t\t\t\tt.Errorf(\"EachKey() must be in %v. got: %v\", tt.expected, key)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TODO: resolve stack overflow\nfunc TestNode_IsEmpty(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tnode *Node\n\t\texpected bool\n\t}{\n\t\t{\"nil node\", (*Node)(nil), false}, // nil node is not empty.\n\t\t// {\"null node\", NullNode(\"\"), true},\n\t\t{\"empty object\", ObjectNode(\"\", nil), true},\n\t\t{\"empty array\", ArrayNode(\"\", nil), true},\n\t\t{\"non-empty object\", ObjectNode(\"\", map[string]*Node{\"foo\": BoolNode(\"foo\", true)}), false},\n\t\t{\"non-empty array\", ArrayNode(\"\", []*Node{BoolNode(\"0\", true)}), false},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := tt.node.Empty(); got != tt.expected {\n\t\t\t\tt.Errorf(\"%s = %v, want %v\", tt.name, got, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_Index_EmptyList(t *testing.T) {\n\troot, err := Unmarshal([]byte(`[]`))\n\tif err != nil {\n\t\tt.Errorf(\"error occurred while unmarshal\")\n\t}\n\n\tarray := root.MustArray()\n\tfor i, node := range array {\n\t\tif i != node.Index() {\n\t\t\tt.Errorf(ufmt.Sprintf(\"Index() must be nil. got: %v\", i))\n\t\t}\n\t}\n}\n\nfunc TestNode_GetObject(t *testing.T) {\n\troot, err := Unmarshal([]byte(`{\"foo\": true,\"bar\": null}`))\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err.Error())\n\t\treturn\n\t}\n\n\tvalue, err := root.GetObject()\n\tif err != nil {\n\t\tt.Errorf(\"Error on root.GetObject(): %s\", err.Error())\n\t}\n\n\tif _, ok := value[\"foo\"]; !ok {\n\t\tt.Errorf(\"root.GetObject() is corrupted: foo\")\n\t}\n\n\tif _, ok := value[\"bar\"]; !ok {\n\t\tt.Errorf(\"root.GetObject() is corrupted: bar\")\n\t}\n}\n\nfunc TestNode_GetObject_Fail(t *testing.T) {\n\ttests := []simpleNode{\n\t\t{\"nil node\", (*Node)(nil)},\n\t\t{\"get object from null node\", NullNode(\"\")},\n\t\t{\"not object node\", NumberNode(\"\", 123)},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif _, err := tt.node.GetObject(); err == nil {\n\t\t\t\tt.Errorf(\"%s should be an error\", tt.name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_ObjectEach(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tjson string\n\t\texpected map[string]int\n\t}{\n\t\t{\n\t\t\tname: \"empty object\",\n\t\t\tjson: `{}`,\n\t\t\texpected: make(map[string]int),\n\t\t},\n\t\t{\n\t\t\tname: \"single key-value pair\",\n\t\t\tjson: `{\"key\": 42}`,\n\t\t\texpected: map[string]int{\"key\": 42},\n\t\t},\n\t\t{\n\t\t\tname: \"multiple key-value pairs\",\n\t\t\tjson: `{\"one\": 1, \"two\": 2, \"three\": 3}`,\n\t\t\texpected: map[string]int{\"one\": 1, \"two\": 2, \"three\": 3},\n\t\t},\n\t\t{\n\t\t\tname: \"multiple key-value pairs with some non-numeric values\",\n\t\t\tjson: `{\"one\": 1, \"two\": \"2\", \"three\": 3, \"four\": \"4\"}`,\n\t\t\texpected: map[string]int{\"one\": 1, \"three\": 3},\n\t\t},\n\t\t{\n\t\t\tname: \"non-object node\",\n\t\t\tjson: `[\"not\", \"an\", \"object\"]`,\n\t\t\texpected: make(map[string]int),\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\troot, err := Unmarshal([]byte(tc.json))\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Unmarshal failed: %v\", err)\n\t\t\t}\n\n\t\t\tresult := make(map[string]int)\n\t\t\troot.ObjectEach(func(key string, value *Node) {\n\t\t\t\t// extract integer values from the object\n\t\t\t\tif val, err := strconv.Atoi(value.String()); err == nil {\n\t\t\t\t\tresult[key] = val\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tif len(result) != len(tc.expected) {\n\t\t\t\tt.Errorf(\"%s: expected %d key-value pairs, got %d\", tc.name, len(tc.expected), len(result))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfor key, val := range tc.expected {\n\t\t\t\tif result[key] != val {\n\t\t\t\t\tt.Errorf(\"%s: expected value for key %s to be %d, got %d\", tc.name, key, val, result[key])\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_ExampleMust(t *testing.T) {\n\tdata := []byte(`{\n \"Image\": {\n \"Width\": 800,\n \"Height\": 600,\n \"Title\": \"View from 15th Floor\",\n \"Thumbnail\": {\n \"Url\": \"http://www.example.com/image/481989943\",\n \"Height\": 125,\n \"Width\": 100\n },\n \"Animated\" : false,\n \"IDs\": [116, 943, 234, 38793]\n }\n }`)\n\n\troot := Must(Unmarshal(data))\n\tif root.Size() != 1 {\n\t\tt.Errorf(\"root.Size() must be 1. got: %v\", root.Size())\n\t}\n\n\tufmt.Sprintf(\"Object has %d inheritors inside\", root.Size())\n\t// Output:\n\t// Object has 1 inheritors inside\n}\n\n// Calculate AVG price from different types of objects, JSON from: https://goessner.net/articles/JsonPath/index.html#e3\nfunc TestExampleUnmarshal(t *testing.T) {\n\tdata := []byte(`{ \"store\": {\n \"book\": [ \n { \"category\": \"reference\",\n \"author\": \"Nigel Rees\",\n \"title\": \"Sayings of the Century\",\n \"price\": 8.95\n },\n { \"category\": \"fiction\",\n \"author\": \"Evelyn Waugh\",\n \"title\": \"Sword of Honour\",\n \"price\": 12.99\n },\n { \"category\": \"fiction\",\n \"author\": \"Herman Melville\",\n \"title\": \"Moby Dick\",\n \"isbn\": \"0-553-21311-3\",\n \"price\": 8.99\n },\n { \"category\": \"fiction\",\n \"author\": \"J. R. R. Tolkien\",\n \"title\": \"The Lord of the Rings\",\n \"isbn\": \"0-395-19395-8\",\n \"price\": 22.99\n }\n ],\n \"bicycle\": { \"color\": \"red\",\n \"price\": 19.95\n },\n \"tools\": null\n }\n}`)\n\n\troot, err := Unmarshal(data)\n\tif err != nil {\n\t\tt.Errorf(\"error occurred when unmarshal\")\n\t}\n\n\tstore := root.MustKey(\"store\").MustObject()\n\n\tvar prices float64\n\tsize := 0\n\tfor _, objects := range store {\n\t\tif objects.IsArray() \u0026\u0026 objects.Size() \u003e 0 {\n\t\t\tsize += objects.Size()\n\t\t\tfor _, object := range objects.MustArray() {\n\t\t\t\tprices += object.MustKey(\"price\").MustNumeric()\n\t\t\t}\n\t\t} else if objects.IsObject() \u0026\u0026 objects.HasKey(\"price\") {\n\t\t\tsize++\n\t\t\tprices += objects.MustKey(\"price\").MustNumeric()\n\t\t}\n\t}\n\n\tresult := int(prices / float64(size))\n\tufmt.Sprintf(\"AVG price: %d\", result)\n}\n\nfunc TestNode_ExampleMust_panic(t *testing.T) {\n\tdefer func() {\n\t\tif r := recover(); r == nil {\n\t\t\tt.Errorf(\"The code did not panic\")\n\t\t}\n\t}()\n\tdata := []byte(`{]`)\n\troot := Must(Unmarshal(data))\n\tufmt.Sprintf(\"Object has %d inheritors inside\", root.Size())\n}\n\nfunc TestNode_Path(t *testing.T) {\n\tdata := []byte(`{\n \"Image\": {\n \"Width\": 800,\n \"Height\": 600,\n \"Title\": \"View from 15th Floor\",\n \"Thumbnail\": {\n \"Url\": \"http://www.example.com/image/481989943\",\n \"Height\": 125,\n \"Width\": 100\n },\n \"Animated\" : false,\n \"IDs\": [116, 943, 234, 38793]\n }\n }`)\n\n\troot, err := Unmarshal(data)\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err.Error())\n\t\treturn\n\t}\n\n\tif root.Path() != \"$\" {\n\t\tt.Errorf(\"Wrong root.Path()\")\n\t}\n\n\telement := root.MustKey(\"Image\").MustKey(\"Thumbnail\").MustKey(\"Url\")\n\tif element.Path() != \"$['Image']['Thumbnail']['Url']\" {\n\t\tt.Errorf(\"Wrong path found: %s\", element.Path())\n\t}\n\n\tif (*Node)(nil).Path() != \"\" {\n\t\tt.Errorf(\"Wrong (nil).Path()\")\n\t}\n}\n\nfunc TestNode_Path2(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tnode *Node\n\t\twant string\n\t}{\n\t\t{\n\t\t\tname: \"Node with key\",\n\t\t\tnode: \u0026Node{\n\t\t\t\tprev: \u0026Node{},\n\t\t\t\tkey: func() *string { s := \"key\"; return \u0026s }(),\n\t\t\t},\n\t\t\twant: \"$['key']\",\n\t\t},\n\t\t{\n\t\t\tname: \"Node with index\",\n\t\t\tnode: \u0026Node{\n\t\t\t\tprev: \u0026Node{},\n\t\t\t\tindex: func() *int { i := 1; return \u0026i }(),\n\t\t\t},\n\t\t\twant: \"$[1]\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := tt.node.Path(); got != tt.want {\n\t\t\t\tt.Errorf(\"Path() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_Root(t *testing.T) {\n\troot := \u0026Node{}\n\tchild := \u0026Node{prev: root}\n\tgrandChild := \u0026Node{prev: child}\n\n\ttests := []struct {\n\t\tname string\n\t\tnode *Node\n\t\twant *Node\n\t}{\n\t\t{\n\t\t\tname: \"Root node\",\n\t\t\tnode: root,\n\t\t\twant: root,\n\t\t},\n\t\t{\n\t\t\tname: \"Child node\",\n\t\t\tnode: child,\n\t\t\twant: root,\n\t\t},\n\t\t{\n\t\t\tname: \"Grandchild node\",\n\t\t\tnode: grandChild,\n\t\t\twant: root,\n\t\t},\n\t\t{\n\t\t\tname: \"Node is nil\",\n\t\t\tnode: nil,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := tt.node.root(); got != tt.want {\n\t\t\t\tt.Errorf(\"root() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc contains(slice []string, item string) bool {\n\tfor _, a := range slice {\n\t\tif a == item {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n// ignore the sequence of keys by ordering them.\n// need to avoid import encoding/json and reflect package.\n// because gno does not support them for now.\n// TODO: use encoding/json to compare the result after if possible in gno.\nfunc isSameObject(a, b string) bool {\n\taPairs := strings.Split(strings.Trim(a, \"{}\"), \",\")\n\tbPairs := strings.Split(strings.Trim(b, \"{}\"), \",\")\n\n\taMap := make(map[string]string)\n\tbMap := make(map[string]string)\n\tfor _, pair := range aPairs {\n\t\tkv := strings.Split(pair, \":\")\n\t\tkey := strings.Trim(kv[0], `\"`)\n\t\tvalue := strings.Trim(kv[1], `\"`)\n\t\taMap[key] = value\n\t}\n\tfor _, pair := range bPairs {\n\t\tkv := strings.Split(pair, \":\")\n\t\tkey := strings.Trim(kv[0], `\"`)\n\t\tvalue := strings.Trim(kv[1], `\"`)\n\t\tbMap[key] = value\n\t}\n\n\taKeys := make([]string, 0, len(aMap))\n\tbKeys := make([]string, 0, len(bMap))\n\tfor k := range aMap {\n\t\taKeys = append(aKeys, k)\n\t}\n\n\tfor k := range bMap {\n\t\tbKeys = append(bKeys, k)\n\t}\n\n\tsort.Strings(aKeys)\n\tsort.Strings(bKeys)\n\n\tif len(aKeys) != len(bKeys) {\n\t\treturn false\n\t}\n\n\tfor i := range aKeys {\n\t\tif aKeys[i] != bKeys[i] {\n\t\t\treturn false\n\t\t}\n\n\t\tif aMap[aKeys[i]] != bMap[bKeys[i]] {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc compareNodes(n1, n2 *Node) bool {\n\tif n1 == nil || n2 == nil {\n\t\treturn n1 == n2\n\t}\n\n\tif n1.key != n2.key {\n\t\treturn false\n\t}\n\n\tif !bytes.Equal(n1.data, n2.data) {\n\t\treturn false\n\t}\n\n\tif n1.index != n2.index {\n\t\treturn false\n\t}\n\n\tif n1.borders != n2.borders {\n\t\treturn false\n\t}\n\n\tif n1.modified != n2.modified {\n\t\treturn false\n\t}\n\n\tif n1.nodeType != n2.nodeType {\n\t\treturn false\n\t}\n\n\tif !compareNodes(n1.prev, n2.prev) {\n\t\treturn false\n\t}\n\n\tif len(n1.next) != len(n2.next) {\n\t\treturn false\n\t}\n\n\tfor k, v := range n1.next {\n\t\tif !compareNodes(v, n2.next[k]) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n"},{"name":"parser.gno","body":"package json\n\nimport (\n\t\"bytes\"\n)\n\nconst (\n\tunescapeStackBufSize = 64\n\tabsMinInt64 = 1 \u003c\u003c 63\n\tmaxInt64 = absMinInt64 - 1\n\tmaxUint64 = 1\u003c\u003c64 - 1\n)\n\n// PaseStringLiteral parses a string from the given byte slice.\nfunc ParseStringLiteral(data []byte) (string, error) {\n\tvar buf [unescapeStackBufSize]byte\n\n\tbf, err := Unescape(data, buf[:])\n\tif err != nil {\n\t\treturn \"\", errInvalidStringInput\n\t}\n\n\treturn string(bf), nil\n}\n\n// ParseBoolLiteral parses a boolean value from the given byte slice.\nfunc ParseBoolLiteral(data []byte) (bool, error) {\n\tswitch {\n\tcase bytes.Equal(data, trueLiteral):\n\t\treturn true, nil\n\tcase bytes.Equal(data, falseLiteral):\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, errMalformedBooleanValue\n\t}\n}\n"},{"name":"parser_test.gno","body":"package json\n\nimport \"testing\"\n\nfunc TestParseStringLiteral(t *testing.T) {\n\ttests := []struct {\n\t\tinput string\n\t\texpected string\n\t\tisError bool\n\t}{\n\t\t{`\"Hello, World!\"`, \"\\\"Hello, World!\\\"\", false},\n\t\t{`\\uFF11`, \"\\uFF11\", false},\n\t\t{`\\uFFFF`, \"\\uFFFF\", false},\n\t\t{`true`, \"true\", false},\n\t\t{`false`, \"false\", false},\n\t\t{`\\uDF00`, \"\", true},\n\t}\n\n\tfor i, tt := range tests {\n\t\ts, err := ParseStringLiteral([]byte(tt.input))\n\n\t\tif !tt.isError \u0026\u0026 err != nil {\n\t\t\tt.Errorf(\"%d. unexpected error: %s\", i, err)\n\t\t}\n\n\t\tif tt.isError \u0026\u0026 err == nil {\n\t\t\tt.Errorf(\"%d. expected error, but not error\", i)\n\t\t}\n\n\t\tif s != tt.expected {\n\t\t\tt.Errorf(\"%d. expected=%s, but actual=%s\", i, tt.expected, s)\n\t\t}\n\t}\n}\n\nfunc TestParseBoolLiteral(t *testing.T) {\n\ttests := []struct {\n\t\tinput string\n\t\texpected bool\n\t\tisError bool\n\t}{\n\t\t{`true`, true, false},\n\t\t{`false`, false, false},\n\t\t{`TRUE`, false, true},\n\t\t{`FALSE`, false, true},\n\t\t{`foo`, false, true},\n\t\t{`\"true\"`, false, true},\n\t\t{`\"false\"`, false, true},\n\t}\n\n\tfor i, tt := range tests {\n\t\tb, err := ParseBoolLiteral([]byte(tt.input))\n\n\t\tif !tt.isError \u0026\u0026 err != nil {\n\t\t\tt.Errorf(\"%d. unexpected error: %s\", i, err)\n\t\t}\n\n\t\tif tt.isError \u0026\u0026 err == nil {\n\t\t\tt.Errorf(\"%d. expected error, but not error\", i)\n\t\t}\n\n\t\tif b != tt.expected {\n\t\t\tt.Errorf(\"%d. expected=%t, but actual=%t\", i, tt.expected, b)\n\t\t}\n\t}\n}\n"},{"name":"path.gno","body":"package json\n\nimport (\n\t\"errors\"\n)\n\n// ParsePath takes a JSONPath string and returns a slice of strings representing the path segments.\nfunc ParsePath(path string) ([]string, error) {\n\tbuf := newBuffer([]byte(path))\n\tresult := make([]string, 0)\n\n\tfor {\n\t\tb, err := buf.current()\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\n\t\tswitch {\n\t\tcase b == dollarSign || b == atSign:\n\t\t\tresult = append(result, string(b))\n\t\t\tbuf.step()\n\n\t\tcase b == dot:\n\t\t\tbuf.step()\n\n\t\t\tif next, _ := buf.current(); next == dot {\n\t\t\t\tbuf.step()\n\t\t\t\tresult = append(result, \"..\")\n\n\t\t\t\textractNextSegment(buf, \u0026result)\n\t\t\t} else {\n\t\t\t\textractNextSegment(buf, \u0026result)\n\t\t\t}\n\n\t\tcase b == bracketOpen:\n\t\t\tstart := buf.index\n\t\t\tbuf.step()\n\n\t\t\tfor {\n\t\t\t\tif buf.index \u003e= buf.length || buf.data[buf.index] == bracketClose {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tbuf.step()\n\t\t\t}\n\n\t\t\tif buf.index \u003e= buf.length {\n\t\t\t\treturn nil, errors.New(\"unexpected end of path\")\n\t\t\t}\n\n\t\t\tsegment := string(buf.sliceFromIndices(start+1, buf.index))\n\t\t\tresult = append(result, segment)\n\n\t\t\tbuf.step()\n\n\t\tdefault:\n\t\t\tbuf.step()\n\t\t}\n\t}\n\n\treturn result, nil\n}\n\n// extractNextSegment extracts the segment from the current index\n// to the next significant character and adds it to the resulting slice.\nfunc extractNextSegment(buf *buffer, result *[]string) {\n\tstart := buf.index\n\tbuf.skipToNextSignificantToken()\n\n\tif buf.index \u003c= start {\n\t\treturn\n\t}\n\n\tsegment := string(buf.sliceFromIndices(start, buf.index))\n\tif segment != \"\" {\n\t\t*result = append(*result, segment)\n\t}\n}\n"},{"name":"path_test.gno","body":"package json\n\nimport \"testing\"\n\nfunc TestParseJSONPath(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tpath string\n\t\texpected []string\n\t}{\n\t\t{name: \"Empty string path\", path: \"\", expected: []string{}},\n\t\t{name: \"Root only path\", path: \"$\", expected: []string{\"$\"}},\n\t\t{name: \"Root with dot path\", path: \"$.\", expected: []string{\"$\"}},\n\t\t{name: \"All objects in path\", path: \"$..\", expected: []string{\"$\", \"..\"}},\n\t\t{name: \"Only children in path\", path: \"$.*\", expected: []string{\"$\", \"*\"}},\n\t\t{name: \"All objects' children in path\", path: \"$..*\", expected: []string{\"$\", \"..\", \"*\"}},\n\t\t{name: \"Simple dot notation path\", path: \"$.root.element\", expected: []string{\"$\", \"root\", \"element\"}},\n\t\t{name: \"Complex dot notation path with wildcard\", path: \"$.root.*.element\", expected: []string{\"$\", \"root\", \"*\", \"element\"}},\n\t\t{name: \"Path with array wildcard\", path: \"$.phoneNumbers[*].type\", expected: []string{\"$\", \"phoneNumbers\", \"*\", \"type\"}},\n\t\t{name: \"Path with filter expression\", path: \"$.store.book[?(@.price \u003c 10)].title\", expected: []string{\"$\", \"store\", \"book\", \"?(@.price \u003c 10)\", \"title\"}},\n\t\t{name: \"Path with formula\", path: \"$..phoneNumbers..('ty' + 'pe')\", expected: []string{\"$\", \"..\", \"phoneNumbers\", \"..\", \"('ty' + 'pe')\"}},\n\t\t{name: \"Simple bracket notation path\", path: \"$['root']['element']\", expected: []string{\"$\", \"'root'\", \"'element'\"}},\n\t\t{name: \"Complex bracket notation path with wildcard\", path: \"$['root'][*]['element']\", expected: []string{\"$\", \"'root'\", \"*\", \"'element'\"}},\n\t\t{name: \"Bracket notation path with integer index\", path: \"$['store']['book'][0]['title']\", expected: []string{\"$\", \"'store'\", \"'book'\", \"0\", \"'title'\"}},\n\t\t{name: \"Complex path with wildcard in bracket notation\", path: \"$['root'].*['element']\", expected: []string{\"$\", \"'root'\", \"*\", \"'element'\"}},\n\t\t{name: \"Mixed notation path with dot after bracket\", path: \"$.['root'].*.['element']\", expected: []string{\"$\", \"'root'\", \"*\", \"'element'\"}},\n\t\t{name: \"Mixed notation path with dot before bracket\", path: \"$['root'].*.['element']\", expected: []string{\"$\", \"'root'\", \"*\", \"'element'\"}},\n\t\t{name: \"Single character path with root\", path: \"$.a\", expected: []string{\"$\", \"a\"}},\n\t\t{name: \"Multiple characters path with root\", path: \"$.abc\", expected: []string{\"$\", \"abc\"}},\n\t\t{name: \"Multiple segments path with root\", path: \"$.a.b.c\", expected: []string{\"$\", \"a\", \"b\", \"c\"}},\n\t\t{name: \"Multiple segments path with wildcard and root\", path: \"$.a.*.c\", expected: []string{\"$\", \"a\", \"*\", \"c\"}},\n\t\t{name: \"Multiple segments path with filter and root\", path: \"$.a[?(@.b == 'c')].d\", expected: []string{\"$\", \"a\", \"?(@.b == 'c')\", \"d\"}},\n\t\t{name: \"Complex path with multiple filters\", path: \"$.a[?(@.b == 'c')].d[?(@.e == 'f')].g\", expected: []string{\"$\", \"a\", \"?(@.b == 'c')\", \"d\", \"?(@.e == 'f')\", \"g\"}},\n\t\t{name: \"Complex path with multiple filters and wildcards\", path: \"$.a[?(@.b == 'c')].*.d[?(@.e == 'f')].g\", expected: []string{\"$\", \"a\", \"?(@.b == 'c')\", \"*\", \"d\", \"?(@.e == 'f')\", \"g\"}},\n\t\t{name: \"Path with array index and root\", path: \"$.a[0].b\", expected: []string{\"$\", \"a\", \"0\", \"b\"}},\n\t\t{name: \"Path with multiple array indices and root\", path: \"$.a[0].b[1].c\", expected: []string{\"$\", \"a\", \"0\", \"b\", \"1\", \"c\"}},\n\t\t{name: \"Path with array index, wildcard and root\", path: \"$.a[0].*.c\", expected: []string{\"$\", \"a\", \"0\", \"*\", \"c\"}},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\treult, _ := ParsePath(tt.path)\n\t\t\tif !isEqualSlice(reult, tt.expected) {\n\t\t\t\tt.Errorf(\"ParsePath(%s) expected: %v, got: %v\", tt.path, tt.expected, reult)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc isEqualSlice(a, b []string) bool {\n\tif len(a) != len(b) {\n\t\treturn false\n\t}\n\n\tfor i, v := range a {\n\t\tif v != b[i] {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n"},{"name":"token.gno","body":"package json\n\nconst (\n\tbracketOpen = '['\n\tbracketClose = ']'\n\tparenOpen = '('\n\tparenClose = ')'\n\tcurlyOpen = '{'\n\tcurlyClose = '}'\n\tcomma = ','\n\tdot = '.'\n\tcolon = ':'\n\tbackTick = '`'\n\tsingleQuote = '\\''\n\tdoubleQuote = '\"'\n\temptyString = \"\"\n\twhiteSpace = ' '\n\tplus = '+'\n\tminus = '-'\n\taesterisk = '*'\n\tbang = '!'\n\tquestion = '?'\n\tnewLine = '\\n'\n\ttab = '\\t'\n\tcarriageReturn = '\\r'\n\tformFeed = '\\f'\n\tbackSpace = '\\b'\n\tslash = '/'\n\tbackSlash = '\\\\'\n\tunderScore = '_'\n\tdollarSign = '$'\n\tatSign = '@'\n\tandSign = '\u0026'\n\torSign = '|'\n)\n\nvar (\n\ttrueLiteral = []byte(\"true\")\n\tfalseLiteral = []byte(\"false\")\n\tnullLiteral = []byte(\"null\")\n)\n\ntype ValueType int\n\nconst (\n\tNotExist ValueType = iota\n\tString\n\tNumber\n\tFloat\n\tObject\n\tArray\n\tBoolean\n\tNull\n\tUnknown\n)\n\nfunc (v ValueType) String() string {\n\tswitch v {\n\tcase NotExist:\n\t\treturn \"not-exist\"\n\tcase String:\n\t\treturn \"string\"\n\tcase Number:\n\t\treturn \"number\"\n\tcase Object:\n\t\treturn \"object\"\n\tcase Array:\n\t\treturn \"array\"\n\tcase Boolean:\n\t\treturn \"boolean\"\n\tcase Null:\n\t\treturn \"null\"\n\tdefault:\n\t\treturn \"unknown\"\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":"int32","path":"gno.land/p/demo/math_eval/int32","files":[{"name":"int32.gno","body":"// eval/int32 is a evaluator for int32 expressions.\n// This code is heavily forked from https://github.com/dengsgo/math-engine\n// which is licensed under Apache 2.0:\n// https://raw.githubusercontent.com/dengsgo/math-engine/298e2b57b7e7350d0f67bd036916efd5709abe25/LICENSE\npackage int32\n\nimport (\n\t\"errors\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\nconst (\n\tIdentifier = iota\n\tNumber // numbers\n\tOperator // +, -, *, /, etc.\n\tVariable // x, y, z, etc. (one-letter only)\n)\n\ntype expression interface {\n\tString() string\n}\n\ntype expressionRaw struct {\n\texpression string\n\tType int\n\tFlag int\n\tOffset int\n}\n\ntype parser struct {\n\tInput string\n\tch byte\n\toffset int\n\terr error\n}\n\ntype expressionNumber struct {\n\tVal int\n\tStr string\n}\n\ntype expressionVariable struct {\n\tVal int\n\tStr string\n}\n\ntype expressionOperation struct {\n\tOp string\n\tLhs,\n\tRhs expression\n}\n\ntype ast struct {\n\trawexpressions []*expressionRaw\n\tsource string\n\tcurrentexpression *expressionRaw\n\tcurrentIndex int\n\tdepth int\n\terr error\n}\n\n// Parse takes an expression string, e.g. \"1+2\" and returns\n// a parsed expression. If there is an error it will return.\nfunc Parse(s string) (ar expression, err error) {\n\ttoks, err := lexer(s)\n\tif err != nil {\n\t\treturn\n\t}\n\tast, err := newAST(toks, s)\n\tif err != nil {\n\t\treturn\n\t}\n\tar, err = ast.parseExpression()\n\treturn\n}\n\n// Eval takes a parsed expression and a map of variables (or nil). The parsed\n// expression is evaluated using any variables and returns the\n// resulting int and/or error.\nfunc Eval(expr expression, variables map[string]int) (res int, err error) {\n\tif err != nil {\n\t\treturn\n\t}\n\tvar l, r int\n\tswitch expr.(type) {\n\tcase expressionVariable:\n\t\tast := expr.(expressionVariable)\n\t\tok := false\n\t\tif variables != nil {\n\t\t\tres, ok = variables[ast.Str]\n\t\t}\n\t\tif !ok {\n\t\t\terr = ufmt.Errorf(\"variable '%s' not found\", ast.Str)\n\t\t}\n\t\treturn\n\tcase expressionOperation:\n\t\tast := expr.(expressionOperation)\n\t\tl, err = Eval(ast.Lhs, variables)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tr, err = Eval(ast.Rhs, variables)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tswitch ast.Op {\n\t\tcase \"+\":\n\t\t\tres = l + r\n\t\tcase \"-\":\n\t\t\tres = l - r\n\t\tcase \"*\":\n\t\t\tres = l * r\n\t\tcase \"/\":\n\t\t\tif r == 0 {\n\t\t\t\terr = ufmt.Errorf(\"violation of arithmetic specification: a division by zero in Eval: [%d/%d]\", l, r)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tres = l / r\n\t\tcase \"%\":\n\t\t\tif r == 0 {\n\t\t\t\tres = 0\n\t\t\t} else {\n\t\t\t\tres = l % r\n\t\t\t}\n\t\tcase \"^\":\n\t\t\tres = l ^ r\n\t\tcase \"\u003e\u003e\":\n\t\t\tres = l \u003e\u003e r\n\t\tcase \"\u003c\u003c\":\n\t\t\tres = l \u003c\u003c r\n\t\tcase \"\u003e\":\n\t\t\tif l \u003e r {\n\t\t\t\tres = 1\n\t\t\t} else {\n\t\t\t\tres = 0\n\t\t\t}\n\t\tcase \"\u003c\":\n\t\t\tif l \u003c r {\n\t\t\t\tres = 1\n\t\t\t} else {\n\t\t\t\tres = 0\n\t\t\t}\n\t\tcase \"\u0026\":\n\t\t\tres = l \u0026 r\n\t\tcase \"|\":\n\t\t\tres = l | r\n\t\tdefault:\n\n\t\t}\n\tcase expressionNumber:\n\t\tres = expr.(expressionNumber).Val\n\t}\n\n\treturn\n}\n\nfunc expressionError(s string, pos int) string {\n\tr := strings.Repeat(\"-\", len(s)) + \"\\n\"\n\ts += \"\\n\"\n\tfor i := 0; i \u003c pos; i++ {\n\t\ts += \" \"\n\t}\n\ts += \"^\\n\"\n\treturn r + s + r\n}\n\nfunc (n expressionVariable) String() string {\n\treturn ufmt.Sprintf(\n\t\t\"expressionVariable: %s\",\n\t\tn.Str,\n\t)\n}\n\nfunc (n expressionNumber) String() string {\n\treturn ufmt.Sprintf(\n\t\t\"expressionNumber: %s\",\n\t\tn.Str,\n\t)\n}\n\nfunc (b expressionOperation) String() string {\n\treturn ufmt.Sprintf(\n\t\t\"expressionOperation: (%s %s %s)\",\n\t\tb.Op,\n\t\tb.Lhs.String(),\n\t\tb.Rhs.String(),\n\t)\n}\n\nfunc newAST(toks []*expressionRaw, s string) (*ast, error) {\n\ta := \u0026ast{\n\t\trawexpressions: toks,\n\t\tsource: s,\n\t}\n\tif a.rawexpressions == nil || len(a.rawexpressions) == 0 {\n\t\treturn a, errors.New(\"empty token\")\n\t} else {\n\t\ta.currentIndex = 0\n\t\ta.currentexpression = a.rawexpressions[0]\n\t}\n\treturn a, nil\n}\n\nfunc (a *ast) parseExpression() (expression, error) {\n\ta.depth++ // called depth\n\tlhs := a.parsePrimary()\n\tr := a.parseBinOpRHS(0, lhs)\n\ta.depth--\n\tif a.depth == 0 \u0026\u0026 a.currentIndex != len(a.rawexpressions) \u0026\u0026 a.err == nil {\n\t\treturn r, ufmt.Errorf(\"bad expression, reaching the end or missing the operator\\n%s\",\n\t\t\texpressionError(a.source, a.currentexpression.Offset))\n\t}\n\treturn r, nil\n}\n\nfunc (a *ast) getNextexpressionRaw() *expressionRaw {\n\ta.currentIndex++\n\tif a.currentIndex \u003c len(a.rawexpressions) {\n\t\ta.currentexpression = a.rawexpressions[a.currentIndex]\n\t\treturn a.currentexpression\n\t}\n\treturn nil\n}\n\nfunc (a *ast) getTokPrecedence() int {\n\tswitch a.currentexpression.expression {\n\tcase \"/\", \"%\", \"*\":\n\t\treturn 100\n\tcase \"\u003c\u003c\", \"\u003e\u003e\":\n\t\treturn 80\n\tcase \"+\", \"-\":\n\t\treturn 75\n\tcase \"\u003c\", \"\u003e\":\n\t\treturn 70\n\tcase \"\u0026\":\n\t\treturn 60\n\tcase \"^\":\n\t\treturn 50\n\tcase \"|\":\n\t\treturn 40\n\t}\n\treturn -1\n}\n\nfunc (a *ast) parseNumber() expressionNumber {\n\tf64, err := strconv.Atoi(a.currentexpression.expression)\n\tif err != nil {\n\t\ta.err = ufmt.Errorf(\"%v\\nwant '(' or '0-9' but get '%s'\\n%s\",\n\t\t\terr.Error(),\n\t\t\ta.currentexpression.expression,\n\t\t\texpressionError(a.source, a.currentexpression.Offset))\n\t\treturn expressionNumber{}\n\t}\n\tn := expressionNumber{\n\t\tVal: f64,\n\t\tStr: a.currentexpression.expression,\n\t}\n\ta.getNextexpressionRaw()\n\treturn n\n}\n\nfunc (a *ast) parseVariable() expressionVariable {\n\tn := expressionVariable{\n\t\tVal: 0,\n\t\tStr: a.currentexpression.expression,\n\t}\n\ta.getNextexpressionRaw()\n\treturn n\n}\n\nfunc (a *ast) parsePrimary() expression {\n\tswitch a.currentexpression.Type {\n\tcase Variable:\n\t\treturn a.parseVariable()\n\tcase Number:\n\t\treturn a.parseNumber()\n\tcase Operator:\n\t\tif a.currentexpression.expression == \"(\" {\n\t\t\tt := a.getNextexpressionRaw()\n\t\t\tif t == nil {\n\t\t\t\ta.err = ufmt.Errorf(\"want '(' or '0-9' but get EOF\\n%s\",\n\t\t\t\t\texpressionError(a.source, a.currentexpression.Offset))\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\te, _ := a.parseExpression()\n\t\t\tif e == nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tif a.currentexpression.expression != \")\" {\n\t\t\t\ta.err = ufmt.Errorf(\"want ')' but get %s\\n%s\",\n\t\t\t\t\ta.currentexpression.expression,\n\t\t\t\t\texpressionError(a.source, a.currentexpression.Offset))\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\ta.getNextexpressionRaw()\n\t\t\treturn e\n\t\t} else if a.currentexpression.expression == \"-\" {\n\t\t\tif a.getNextexpressionRaw() == nil {\n\t\t\t\ta.err = ufmt.Errorf(\"want '0-9' but get '-'\\n%s\",\n\t\t\t\t\texpressionError(a.source, a.currentexpression.Offset))\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tbin := expressionOperation{\n\t\t\t\tOp: \"-\",\n\t\t\t\tLhs: expressionNumber{},\n\t\t\t\tRhs: a.parsePrimary(),\n\t\t\t}\n\t\t\treturn bin\n\t\t} else {\n\t\t\treturn a.parseNumber()\n\t\t}\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc (a *ast) parseBinOpRHS(execPrec int, lhs expression) expression {\n\tfor {\n\t\ttokPrec := a.getTokPrecedence()\n\t\tif tokPrec \u003c execPrec {\n\t\t\treturn lhs\n\t\t}\n\t\tbinOp := a.currentexpression.expression\n\t\tif a.getNextexpressionRaw() == nil {\n\t\t\ta.err = ufmt.Errorf(\"want '(' or '0-9' but get EOF\\n%s\",\n\t\t\t\texpressionError(a.source, a.currentexpression.Offset))\n\t\t\treturn nil\n\t\t}\n\t\trhs := a.parsePrimary()\n\t\tif rhs == nil {\n\t\t\treturn nil\n\t\t}\n\t\tnextPrec := a.getTokPrecedence()\n\t\tif tokPrec \u003c nextPrec {\n\t\t\trhs = a.parseBinOpRHS(tokPrec+1, rhs)\n\t\t\tif rhs == nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tlhs = expressionOperation{\n\t\t\tOp: binOp,\n\t\t\tLhs: lhs,\n\t\t\tRhs: rhs,\n\t\t}\n\t}\n}\n\nfunc lexer(s string) ([]*expressionRaw, error) {\n\tp := \u0026parser{\n\t\tInput: s,\n\t\terr: nil,\n\t\tch: s[0],\n\t}\n\ttoks := p.parse()\n\tif p.err != nil {\n\t\treturn nil, p.err\n\t}\n\treturn toks, nil\n}\n\nfunc (p *parser) parse() []*expressionRaw {\n\ttoks := make([]*expressionRaw, 0)\n\tfor {\n\t\ttok := p.nextTok()\n\t\tif tok == nil {\n\t\t\tbreak\n\t\t}\n\t\ttoks = append(toks, tok)\n\t}\n\treturn toks\n}\n\nfunc (p *parser) nextTok() *expressionRaw {\n\tif p.offset \u003e= len(p.Input) || p.err != nil {\n\t\treturn nil\n\t}\n\tvar err error\n\tfor p.isWhitespace(p.ch) \u0026\u0026 err == nil {\n\t\terr = p.nextCh()\n\t}\n\tstart := p.offset\n\tvar tok *expressionRaw\n\tswitch p.ch {\n\tcase\n\t\t'(',\n\t\t')',\n\t\t'+',\n\t\t'-',\n\t\t'*',\n\t\t'/',\n\t\t'^',\n\t\t'\u0026',\n\t\t'|',\n\t\t'%':\n\t\ttok = \u0026expressionRaw{\n\t\t\texpression: string(p.ch),\n\t\t\tType: Operator,\n\t\t}\n\t\ttok.Offset = start\n\t\terr = p.nextCh()\n\tcase '\u003e', '\u003c':\n\t\ttokS := string(p.ch)\n\t\tbb, be := p.nextChPeek()\n\t\tif be == nil \u0026\u0026 string(bb) == tokS {\n\t\t\ttokS += string(p.ch)\n\t\t}\n\t\ttok = \u0026expressionRaw{\n\t\t\texpression: tokS,\n\t\t\tType: Operator,\n\t\t}\n\t\ttok.Offset = start\n\t\tif len(tokS) \u003e 1 {\n\t\t\tp.nextCh()\n\t\t}\n\t\terr = p.nextCh()\n\tcase\n\t\t'0',\n\t\t'1',\n\t\t'2',\n\t\t'3',\n\t\t'4',\n\t\t'5',\n\t\t'6',\n\t\t'7',\n\t\t'8',\n\t\t'9':\n\t\tfor p.isDigitNum(p.ch) \u0026\u0026 p.nextCh() == nil {\n\t\t\tif (p.ch == '-' || p.ch == '+') \u0026\u0026 p.Input[p.offset-1] != 'e' {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\ttok = \u0026expressionRaw{\n\t\t\texpression: strings.ReplaceAll(p.Input[start:p.offset], \"_\", \"\"),\n\t\t\tType: Number,\n\t\t}\n\t\ttok.Offset = start\n\tdefault:\n\t\tif p.isChar(p.ch) {\n\t\t\ttok = \u0026expressionRaw{\n\t\t\t\texpression: string(p.ch),\n\t\t\t\tType: Variable,\n\t\t\t}\n\t\t\ttok.Offset = start\n\t\t\terr = p.nextCh()\n\t\t} else if p.ch != ' ' {\n\t\t\tp.err = ufmt.Errorf(\"symbol error: unknown '%v', pos [%v:]\\n%s\",\n\t\t\t\tstring(p.ch),\n\t\t\t\tstart,\n\t\t\t\texpressionError(p.Input, start))\n\t\t}\n\t}\n\treturn tok\n}\n\nfunc (p *parser) nextChPeek() (byte, error) {\n\toffset := p.offset + 1\n\tif offset \u003c len(p.Input) {\n\t\treturn p.Input[offset], nil\n\t}\n\treturn byte(0), errors.New(\"no byte\")\n}\n\nfunc (p *parser) nextCh() error {\n\tp.offset++\n\tif p.offset \u003c len(p.Input) {\n\t\tp.ch = p.Input[p.offset]\n\t\treturn nil\n\t}\n\treturn errors.New(\"EOF\")\n}\n\nfunc (p *parser) isWhitespace(c byte) bool {\n\treturn c == ' ' ||\n\t\tc == '\\t' ||\n\t\tc == '\\n' ||\n\t\tc == '\\v' ||\n\t\tc == '\\f' ||\n\t\tc == '\\r'\n}\n\nfunc (p *parser) isDigitNum(c byte) bool {\n\treturn '0' \u003c= c \u0026\u0026 c \u003c= '9' || c == '.' || c == '_' || c == 'e' || c == '-' || c == '+'\n}\n\nfunc (p *parser) isChar(c byte) bool {\n\treturn 'a' \u003c= c \u0026\u0026 c \u003c= 'z' || 'A' \u003c= c \u0026\u0026 c \u003c= 'Z'\n}\n\nfunc (p *parser) isWordChar(c byte) bool {\n\treturn p.isChar(c) || '0' \u003c= c \u0026\u0026 c \u003c= '9'\n}\n"},{"name":"int32_test.gno","body":"package int32\n\nimport \"testing\"\n\nfunc TestOne(t *testing.T) {\n\tttt := []struct {\n\t\texp string\n\t\tres int\n\t}{\n\t\t{\"1\", 1},\n\t\t{\"--1\", 1},\n\t\t{\"1+2\", 3},\n\t\t{\"-1+2\", 1},\n\t\t{\"-(1+2)\", -3},\n\t\t{\"-(1+2)*5\", -15},\n\t\t{\"-(1+2)*5/3\", -5},\n\t\t{\"1+(-(1+2)*5/3)\", -4},\n\t\t{\"3^4\", 3 ^ 4},\n\t\t{\"8%2\", 8 % 2},\n\t\t{\"8%3\", 8 % 3},\n\t\t{\"8|3\", 8 | 3},\n\t\t{\"10%2\", 0},\n\t\t{\"(4 + 3)/2-1+11*15\", (4+3)/2 - 1 + 11*15},\n\t\t{\n\t\t\t\"(30099\u003e\u003e10^30099\u003e\u003e11)%5*((30099\u003e\u003e14\u00263^30099\u003e\u003e15\u00261)+1)*30099%99 + ((3 + (30099 \u003e\u003e 14 \u0026 3) - (30099 \u003e\u003e 16 \u0026 1)) / 3 * 30099 % 99 \u0026 64)\",\n\t\t\t(30099\u003e\u003e10^30099\u003e\u003e11)%5*((30099\u003e\u003e14\u00263^30099\u003e\u003e15\u00261)+1)*30099%99 + ((3 + (30099 \u003e\u003e 14 \u0026 3) - (30099 \u003e\u003e 16 \u0026 1)) / 3 * 30099 % 99 \u0026 64),\n\t\t},\n\t\t{\n\t\t\t\"(1023850\u003e\u003e10^1023850\u003e\u003e11)%5*((1023850\u003e\u003e14\u00263^1023850\u003e\u003e15\u00261)+1)*1023850%99 + ((3 + (1023850 \u003e\u003e 14 \u0026 3) - (1023850 \u003e\u003e 16 \u0026 1)) / 3 * 1023850 % 99 \u0026 64)\",\n\t\t\t(1023850\u003e\u003e10^1023850\u003e\u003e11)%5*((1023850\u003e\u003e14\u00263^1023850\u003e\u003e15\u00261)+1)*1023850%99 + ((3 + (1023850 \u003e\u003e 14 \u0026 3) - (1023850 \u003e\u003e 16 \u0026 1)) / 3 * 1023850 % 99 \u0026 64),\n\t\t},\n\t\t{\"((0000+1)*0000)\", 0},\n\t}\n\tfor _, tc := range ttt {\n\t\tt.Run(tc.exp, func(t *testing.T) {\n\t\t\texp, err := Parse(tc.exp)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"%s:\\n%s\", tc.exp, err.Error())\n\t\t\t} else {\n\t\t\t\tres, errEval := Eval(exp, nil)\n\t\t\t\tif errEval != nil {\n\t\t\t\t\tt.Errorf(\"eval error: %s\", errEval.Error())\n\t\t\t\t} else if res != tc.res {\n\t\t\t\t\tt.Errorf(\"%s:\\nexpected %d, got %d\", tc.exp, tc.res, res)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestVariables(t *testing.T) {\n\tfn := func(x, y int) int {\n\t\treturn 1 + ((x*3+1)*(x*2))\u003e\u003ey + 1\n\t}\n\texpr := \"1 + ((x*3+1)*(x*2))\u003e\u003ey + 1\"\n\texp, err := Parse(expr)\n\tif err != nil {\n\t\tt.Errorf(\"could not parse: %s\", err.Error())\n\t}\n\tvariables := make(map[string]int)\n\tfor i := 0; i \u003c 10; i++ {\n\t\tvariables[\"x\"] = i\n\t\tvariables[\"y\"] = 2\n\t\tres, errEval := Eval(exp, variables)\n\t\tif errEval != nil {\n\t\t\tt.Errorf(\"could not evaluate: %s\", err.Error())\n\t\t}\n\t\texpected := fn(variables[\"x\"], variables[\"y\"])\n\t\tif res != expected {\n\t\t\tt.Errorf(\"expected: %d, actual: %d\", expected, res)\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":"membstore","path":"gno.land/p/demo/membstore","files":[{"name":"members.gno","body":"package membstore\n\nimport (\n\t\"std\"\n)\n\n// MemberStore defines the member storage abstraction\ntype MemberStore interface {\n\t// Members returns all members in the store\n\tMembers(offset, count uint64) []Member\n\n\t// Size returns the current size of the store\n\tSize() int\n\n\t// IsMember returns a flag indicating if the given address\n\t// belongs to a member\n\tIsMember(address std.Address) bool\n\n\t// TotalPower returns the total voting power of the member store\n\tTotalPower() uint64\n\n\t// Member returns the requested member\n\tMember(address std.Address) (Member, error)\n\n\t// AddMember adds a member to the store\n\tAddMember(member Member) error\n\n\t// UpdateMember updates the member in the store.\n\t// If updating a member's voting power to 0,\n\t// the member will be removed\n\tUpdateMember(address std.Address, member Member) error\n}\n\n// Member holds the relevant member information\ntype Member struct {\n\tAddress std.Address // bech32 gno address of the member (unique)\n\tVotingPower uint64 // the voting power of the member\n}\n"},{"name":"membstore.gno","body":"package membstore\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar (\n\tErrAlreadyMember = errors.New(\"address is already a member\")\n\tErrMissingMember = errors.New(\"address is not a member\")\n\tErrInvalidAddressUpdate = errors.New(\"invalid address update\")\n\tErrNotGovDAO = errors.New(\"caller not correct govdao instance\")\n)\n\n// maxRequestMembers is the maximum number of\n// paginated members that can be requested\nconst maxRequestMembers = 50\n\ntype Option func(*MembStore)\n\n// WithInitialMembers initializes the member store\n// with an initial member list\nfunc WithInitialMembers(members []Member) Option {\n\treturn func(store *MembStore) {\n\t\tfor _, m := range members {\n\t\t\tmemberAddr := m.Address.String()\n\n\t\t\t// Check if the member already exists\n\t\t\tif store.members.Has(memberAddr) {\n\t\t\t\tpanic(ufmt.Errorf(\"%s, %s\", memberAddr, ErrAlreadyMember))\n\t\t\t}\n\n\t\t\tstore.members.Set(memberAddr, m)\n\t\t\tstore.totalVotingPower += m.VotingPower\n\t\t}\n\t}\n}\n\n// WithDAOPkgPath initializes the member store\n// with a dao package path guard\nfunc WithDAOPkgPath(daoPkgPath string) Option {\n\treturn func(store *MembStore) {\n\t\tstore.daoPkgPath = daoPkgPath\n\t}\n}\n\n// MembStore implements the dao.MembStore abstraction\ntype MembStore struct {\n\tdaoPkgPath string // active dao pkg path, if any\n\tmembers *avl.Tree // std.Address -\u003e Member\n\ttotalVotingPower uint64 // cached value for quick lookups\n}\n\n// NewMembStore creates a new member store\nfunc NewMembStore(opts ...Option) *MembStore {\n\tm := \u0026MembStore{\n\t\tmembers: avl.NewTree(), // empty set\n\t\tdaoPkgPath: \"\", // no dao guard\n\t\ttotalVotingPower: 0,\n\t}\n\n\t// Apply the options\n\tfor _, opt := range opts {\n\t\topt(m)\n\t}\n\n\treturn m\n}\n\n// AddMember adds member to the member store `m`.\n// It fails if the caller is not GovDAO or\n// if the member is already present\nfunc (m *MembStore) AddMember(member Member) error {\n\tif !m.isCallerDAORealm() {\n\t\treturn ErrNotGovDAO\n\t}\n\n\t// Check if the member exists\n\tif m.IsMember(member.Address) {\n\t\treturn ErrAlreadyMember\n\t}\n\n\t// Add the member\n\tm.members.Set(member.Address.String(), member)\n\n\t// Update the total voting power\n\tm.totalVotingPower += member.VotingPower\n\n\treturn nil\n}\n\n// UpdateMember updates the member with the given address.\n// Updating fails if the caller is not GovDAO.\nfunc (m *MembStore) UpdateMember(address std.Address, member Member) error {\n\tif !m.isCallerDAORealm() {\n\t\treturn ErrNotGovDAO\n\t}\n\n\t// Get the member\n\toldMember, err := m.Member(address)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Check if this is a removal request\n\tif member.VotingPower == 0 {\n\t\tm.members.Remove(address.String())\n\n\t\t// Update the total voting power\n\t\tm.totalVotingPower -= oldMember.VotingPower\n\n\t\treturn nil\n\t}\n\n\t// Check that the member wouldn't be\n\t// overwriting an existing one\n\tisAddressUpdate := address != member.Address\n\tif isAddressUpdate \u0026\u0026 m.IsMember(member.Address) {\n\t\treturn ErrInvalidAddressUpdate\n\t}\n\n\t// Remove the old member info\n\t// in case the address changed\n\tif address != member.Address {\n\t\tm.members.Remove(address.String())\n\t}\n\n\t// Save the new member info\n\tm.members.Set(member.Address.String(), member)\n\n\t// Update the total voting power\n\tdifference := member.VotingPower - oldMember.VotingPower\n\tm.totalVotingPower += difference\n\n\treturn nil\n}\n\n// IsMember returns a flag indicating if the given\n// address belongs to a member of the member store\nfunc (m *MembStore) IsMember(address std.Address) bool {\n\t_, exists := m.members.Get(address.String())\n\n\treturn exists\n}\n\n// Member returns the member associated with the given address\nfunc (m *MembStore) Member(address std.Address) (Member, error) {\n\tmember, exists := m.members.Get(address.String())\n\tif !exists {\n\t\treturn Member{}, ErrMissingMember\n\t}\n\n\treturn member.(Member), nil\n}\n\n// Members returns a paginated list of members from\n// the member store. If the store is empty, an empty slice\n// is returned instead\nfunc (m *MembStore) Members(offset, count uint64) []Member {\n\t// Calculate the left and right bounds\n\tif count \u003c 1 || offset \u003e= uint64(m.members.Size()) {\n\t\treturn []Member{}\n\t}\n\n\t// Limit the maximum number of returned members\n\tif count \u003e maxRequestMembers {\n\t\tcount = maxRequestMembers\n\t}\n\n\t// Gather the members\n\tmembers := make([]Member, 0)\n\tm.members.IterateByOffset(\n\t\tint(offset),\n\t\tint(count),\n\t\tfunc(_ string, val interface{}) bool {\n\t\t\tmember := val.(Member)\n\n\t\t\t// Save the member\n\t\t\tmembers = append(members, member)\n\n\t\t\treturn false\n\t\t})\n\n\treturn members\n}\n\n// Size returns the number of active members in the member store\nfunc (m *MembStore) Size() int {\n\treturn m.members.Size()\n}\n\n// TotalPower returns the total voting power\n// of the member store\nfunc (m *MembStore) TotalPower() uint64 {\n\treturn m.totalVotingPower\n}\n\n// isCallerDAORealm returns a flag indicating if the\n// current caller context is the active DAO Realm.\n// We need to include a dao guard, even if the\n// executor guarantees it, because\n// the API of the member store is public and callable\n// by anyone who has a reference to the member store instance.\nfunc (m *MembStore) isCallerDAORealm() bool {\n\treturn m.daoPkgPath == \"\" || std.CurrentRealm().PkgPath() == m.daoPkgPath\n}\n"},{"name":"membstore_test.gno","body":"package membstore\n\nimport (\n\t\"testing\"\n\n\t\"std\"\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\n// generateMembers generates dummy govdao members\nfunc generateMembers(t *testing.T, count int) []Member {\n\tt.Helper()\n\n\tmembers := make([]Member, 0, count)\n\n\tfor i := 0; i \u003c count; i++ {\n\t\tmembers = append(members, 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 TestMembStore_GetMember(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"member not found\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Create an empty store\n\t\tm := NewMembStore()\n\n\t\t_, err := m.Member(testutils.TestAddress(\"random\"))\n\t\tuassert.ErrorIs(t, err, ErrMissingMember)\n\t})\n\n\tt.Run(\"valid member fetched\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Create a non-empty store\n\t\tmembers := generateMembers(t, 1)\n\n\t\tm := NewMembStore(WithInitialMembers(members))\n\n\t\t_, err := m.Member(members[0].Address)\n\t\tuassert.NoError(t, err)\n\t})\n}\n\nfunc TestMembStore_GetMembers(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"no members\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Create an empty store\n\t\tm := NewMembStore()\n\n\t\tmembers := m.Members(0, 10)\n\t\tuassert.Equal(t, 0, len(members))\n\t})\n\n\tt.Run(\"proper pagination\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tnumMembers = maxRequestMembers * 2\n\t\t\thalfRange = numMembers / 2\n\n\t\t\tmembers = generateMembers(t, numMembers)\n\t\t\tm = NewMembStore(WithInitialMembers(members))\n\n\t\t\tverifyMembersPresent = func(members, fetchedMembers []Member) {\n\t\t\t\tfor _, fetchedMember := range fetchedMembers {\n\t\t\t\t\tfor _, member := range members {\n\t\t\t\t\t\tif member.Address != fetchedMember.Address {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tuassert.Equal(t, member.VotingPower, fetchedMember.VotingPower)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t)\n\n\t\turequire.Equal(t, numMembers, m.Size())\n\n\t\tfetchedMembers := m.Members(0, uint64(halfRange))\n\t\turequire.Equal(t, halfRange, len(fetchedMembers))\n\n\t\t// Verify the members\n\t\tverifyMembersPresent(members, fetchedMembers)\n\n\t\t// Fetch the other half\n\t\tfetchedMembers = m.Members(uint64(halfRange), uint64(halfRange))\n\t\turequire.Equal(t, halfRange, len(fetchedMembers))\n\n\t\t// Verify the members\n\t\tverifyMembersPresent(members, fetchedMembers)\n\t})\n}\n\nfunc TestMembStore_IsMember(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"non-existing member\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Create an empty store\n\t\tm := NewMembStore()\n\n\t\tuassert.False(t, m.IsMember(testutils.TestAddress(\"random\")))\n\t})\n\n\tt.Run(\"existing member\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Create a non-empty store\n\t\tmembers := generateMembers(t, 50)\n\n\t\tm := NewMembStore(WithInitialMembers(members))\n\n\t\tfor _, member := range members {\n\t\t\tuassert.True(t, m.IsMember(member.Address))\n\t\t}\n\t})\n}\n\nfunc TestMembStore_AddMember(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"caller not govdao\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Create an empty store\n\t\tm := NewMembStore(WithDAOPkgPath(\"gno.land/r/gov/dao\"))\n\n\t\t// Attempt to add a member\n\t\tmember := generateMembers(t, 1)[0]\n\t\tuassert.ErrorIs(t, m.AddMember(member), ErrNotGovDAO)\n\t})\n\n\tt.Run(\"member already exists\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\t// Execute as the /r/gov/dao caller\n\t\t\tdaoPkgPath = \"gno.land/r/gov/dao\"\n\t\t\tr = std.NewCodeRealm(daoPkgPath)\n\t\t)\n\n\t\tstd.TestSetRealm(r)\n\n\t\t// Create a non-empty store\n\t\tmembers := generateMembers(t, 1)\n\t\tm := NewMembStore(WithDAOPkgPath(daoPkgPath), WithInitialMembers(members))\n\n\t\t// Attempt to add a member\n\t\tuassert.ErrorIs(t, m.AddMember(members[0]), ErrAlreadyMember)\n\t})\n\n\tt.Run(\"new member added\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\t// Execute as the /r/gov/dao caller\n\t\t\tdaoPkgPath = \"gno.land/r/gov/dao\"\n\t\t\tr = std.NewCodeRealm(daoPkgPath)\n\t\t)\n\n\t\tstd.TestSetRealm(r)\n\n\t\t// Create an empty store\n\t\tmembers := generateMembers(t, 1)\n\t\tm := NewMembStore(WithDAOPkgPath(daoPkgPath))\n\n\t\t// Attempt to add a member\n\t\turequire.NoError(t, m.AddMember(members[0]))\n\n\t\t// Make sure the member is added\n\t\tuassert.True(t, m.IsMember(members[0].Address))\n\t})\n}\n\nfunc TestMembStore_Size(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"empty govdao\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Create an empty store\n\t\tm := NewMembStore()\n\n\t\tuassert.Equal(t, 0, m.Size())\n\t})\n\n\tt.Run(\"non-empty govdao\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Create a non-empty store\n\t\tmembers := generateMembers(t, 50)\n\t\tm := NewMembStore(WithInitialMembers(members))\n\n\t\tuassert.Equal(t, len(members), m.Size())\n\t})\n}\n\nfunc TestMembStore_UpdateMember(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"caller not govdao\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Create an empty store\n\t\tm := NewMembStore(WithDAOPkgPath(\"gno.land/r/gov/dao\"))\n\n\t\t// Attempt to update a member\n\t\tmember := generateMembers(t, 1)[0]\n\t\tuassert.ErrorIs(t, m.UpdateMember(member.Address, member), ErrNotGovDAO)\n\t})\n\n\tt.Run(\"non-existing member\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\t// Execute as the /r/gov/dao caller\n\t\t\tdaoPkgPath = \"gno.land/r/gov/dao\"\n\t\t\tr = std.NewCodeRealm(daoPkgPath)\n\t\t)\n\n\t\tstd.TestSetRealm(r)\n\n\t\t// Create an empty store\n\t\tmembers := generateMembers(t, 1)\n\t\tm := NewMembStore(WithDAOPkgPath(daoPkgPath))\n\n\t\t// Attempt to update a member\n\t\tuassert.ErrorIs(t, m.UpdateMember(members[0].Address, members[0]), ErrMissingMember)\n\t})\n\n\tt.Run(\"overwrite member attempt\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\t// Execute as the /r/gov/dao caller\n\t\t\tdaoPkgPath = \"gno.land/r/gov/dao\"\n\t\t\tr = std.NewCodeRealm(daoPkgPath)\n\t\t)\n\n\t\tstd.TestSetRealm(r)\n\n\t\t// Create a non-empty store\n\t\tmembers := generateMembers(t, 2)\n\t\tm := NewMembStore(WithDAOPkgPath(daoPkgPath), WithInitialMembers(members))\n\n\t\t// Attempt to update a member\n\t\tuassert.ErrorIs(t, m.UpdateMember(members[0].Address, members[1]), ErrInvalidAddressUpdate)\n\t})\n\n\tt.Run(\"successful update\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\t// Execute as the /r/gov/dao caller\n\t\t\tdaoPkgPath = \"gno.land/r/gov/dao\"\n\t\t\tr = std.NewCodeRealm(daoPkgPath)\n\t\t)\n\n\t\tstd.TestSetRealm(r)\n\n\t\t// Create a non-empty store\n\t\tmembers := generateMembers(t, 1)\n\t\tm := NewMembStore(WithDAOPkgPath(daoPkgPath), WithInitialMembers(members))\n\n\t\toldVotingPower := m.totalVotingPower\n\t\turequire.Equal(t, members[0].VotingPower, oldVotingPower)\n\n\t\tvotingPower := uint64(300)\n\t\tmembers[0].VotingPower = votingPower\n\n\t\t// Attempt to update a member\n\t\tuassert.NoError(t, m.UpdateMember(members[0].Address, members[0]))\n\t\tuassert.Equal(t, votingPower, m.Members(0, 10)[0].VotingPower)\n\t\turequire.Equal(t, votingPower, m.totalVotingPower)\n\t})\n\n\tt.Run(\"member removed\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\t// Execute as the /r/gov/dao caller\n\t\t\tdaoPkgPath = \"gno.land/r/gov/dao\"\n\t\t\tr = std.NewCodeRealm(daoPkgPath)\n\t\t)\n\n\t\tstd.TestSetRealm(r)\n\n\t\t// Create a non-empty store\n\t\tmembers := generateMembers(t, 1)\n\t\tm := NewMembStore(WithDAOPkgPath(daoPkgPath), WithInitialMembers(members))\n\n\t\tvotingPower := uint64(0)\n\t\tmembers[0].VotingPower = votingPower\n\n\t\t// Attempt to update a member\n\t\tuassert.NoError(t, m.UpdateMember(members[0].Address, members[0]))\n\n\t\t// Make sure the member was removed\n\t\tuassert.False(t, m.IsMember(members[0].Address))\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":"ownable","path":"gno.land/p/demo/ownable","files":[{"name":"errors.gno","body":"package ownable\n\nimport \"errors\"\n\nvar (\n\tErrUnauthorized = errors.New(\"ownable: caller is not owner\")\n\tErrInvalidAddress = errors.New(\"ownable: new owner address is invalid\")\n)\n"},{"name":"ownable.gno","body":"package ownable\n\nimport \"std\"\n\nconst OwnershipTransferEvent = \"OwnershipTransfer\"\n\n// Ownable is meant to be used as a top-level object to make your contract ownable OR\n// being embedded in a Gno object to manage per-object ownership.\ntype Ownable struct {\n\towner std.Address\n}\n\nfunc New() *Ownable {\n\treturn \u0026Ownable{\n\t\towner: std.PrevRealm().Addr(),\n\t}\n}\n\nfunc NewWithAddress(addr std.Address) *Ownable {\n\treturn \u0026Ownable{\n\t\towner: addr,\n\t}\n}\n\n// TransferOwnership transfers ownership of the Ownable struct to a new address\nfunc (o *Ownable) TransferOwnership(newOwner std.Address) error {\n\terr := o.CallerIsOwner()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !newOwner.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tprevOwner := o.owner\n\to.owner = newOwner\n\tstd.Emit(\n\t\tOwnershipTransferEvent,\n\t\t\"from\", string(prevOwner),\n\t\t\"to\", string(newOwner),\n\t)\n\n\treturn nil\n}\n\n// DropOwnership removes the owner, effectively disabling any owner-related actions\n// Top-level usage: disables all only-owner actions/functions,\n// Embedded usage: behaves like a burn functionality, removing the owner from the struct\nfunc (o *Ownable) DropOwnership() error {\n\terr := o.CallerIsOwner()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tprevOwner := o.owner\n\to.owner = \"\"\n\n\tstd.Emit(\n\t\tOwnershipTransferEvent,\n\t\t\"from\", string(prevOwner),\n\t\t\"to\", \"\",\n\t)\n\n\treturn nil\n}\n\n// Owner returns the owner address from Ownable\nfunc (o Ownable) Owner() std.Address {\n\treturn o.owner\n}\n\n// CallerIsOwner checks if the caller of the function is the Realm's owner\nfunc (o Ownable) CallerIsOwner() error {\n\tif std.PrevRealm().Addr() == o.owner {\n\t\treturn nil\n\t}\n\n\treturn ErrUnauthorized\n}\n\n// AssertCallerIsOwner panics if the caller is not the owner\nfunc (o Ownable) AssertCallerIsOwner() {\n\tif std.PrevRealm().Addr() != o.owner {\n\t\tpanic(ErrUnauthorized)\n\t}\n}\n"},{"name":"ownable_test.gno","body":"package ownable\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\talice = testutils.TestAddress(\"alice\")\n\tbob = testutils.TestAddress(\"bob\")\n)\n\nfunc TestNew(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOriginCaller(alice) // TODO(bug): should not be needed\n\n\to := New()\n\tgot := o.Owner()\n\tif alice != got {\n\t\tt.Fatalf(\"Expected %s, got: %s\", alice, got)\n\t}\n}\n\nfunc TestNewWithAddress(t *testing.T) {\n\to := NewWithAddress(alice)\n\n\tgot := o.Owner()\n\tif alice != got {\n\t\tt.Fatalf(\"Expected %s, got: %s\", alice, got)\n\t}\n}\n\nfunc TestTransferOwnership(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\n\to := New()\n\n\terr := o.TransferOwnership(bob)\n\tif err != nil {\n\t\tt.Fatalf(\"TransferOwnership failed, %v\", err)\n\t}\n\n\tgot := o.Owner()\n\tif bob != got {\n\t\tt.Fatalf(\"Expected: %s, got: %s\", bob, got)\n\t}\n}\n\nfunc TestCallerIsOwner(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\n\to := New()\n\tunauthorizedCaller := bob\n\n\tstd.TestSetRealm(std.NewUserRealm(unauthorizedCaller))\n\tstd.TestSetOriginCaller(unauthorizedCaller) // TODO(bug): should not be needed\n\n\terr := o.CallerIsOwner()\n\tuassert.Error(t, err) // XXX: IsError(..., unauthorizedCaller)\n}\n\nfunc TestDropOwnership(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\n\to := New()\n\n\terr := o.DropOwnership()\n\tuassert.NoError(t, err, \"DropOwnership failed\")\n\n\towner := o.Owner()\n\tuassert.Empty(t, owner, \"owner should be empty\")\n}\n\n// Errors\n\nfunc TestErrUnauthorized(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOriginCaller(alice) // TODO(bug): should not be needed\n\n\to := New()\n\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\tstd.TestSetOriginCaller(bob) // TODO(bug): should not be needed\n\n\terr := o.TransferOwnership(alice)\n\tif err != ErrUnauthorized {\n\t\tt.Fatalf(\"Should've been ErrUnauthorized, was %v\", err)\n\t}\n\n\terr = o.DropOwnership()\n\tuassert.ErrorContains(t, err, ErrUnauthorized.Error())\n}\n\nfunc TestErrInvalidAddress(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\n\to := New()\n\n\terr := o.TransferOwnership(\"\")\n\tuassert.ErrorContains(t, err, ErrInvalidAddress.Error())\n\n\terr = o.TransferOwnership(\"10000000001000000000100000000010000000001000000000\")\n\tuassert.ErrorContains(t, err, ErrInvalidAddress.Error())\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":"ownable","path":"gno.land/p/demo/ownable","files":[{"name":"errors.gno","body":"package ownable\n\nimport \"errors\"\n\nvar (\n\tErrUnauthorized = errors.New(\"ownable: caller is not owner\")\n\tErrInvalidAddress = errors.New(\"ownable: new owner address is invalid\")\n)\n"},{"name":"ownable.gno","body":"package ownable\n\nimport \"std\"\n\nconst OwnershipTransferEvent = \"OwnershipTransfer\"\n\n// Ownable is meant to be used as a top-level object to make your contract ownable OR\n// being embedded in a Gno object to manage per-object ownership.\ntype Ownable struct {\n\towner std.Address\n}\n\nfunc New() *Ownable {\n\treturn \u0026Ownable{\n\t\towner: std.PreviousRealm().Addr(),\n\t}\n}\n\nfunc NewWithAddress(addr std.Address) *Ownable {\n\treturn \u0026Ownable{\n\t\towner: addr,\n\t}\n}\n\n// TransferOwnership transfers ownership of the Ownable struct to a new address\nfunc (o *Ownable) TransferOwnership(newOwner std.Address) error {\n\terr := o.CallerIsOwner()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !newOwner.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tprevOwner := o.owner\n\to.owner = newOwner\n\tstd.Emit(\n\t\tOwnershipTransferEvent,\n\t\t\"from\", string(prevOwner),\n\t\t\"to\", string(newOwner),\n\t)\n\n\treturn nil\n}\n\n// DropOwnership removes the owner, effectively disabling any owner-related actions\n// Top-level usage: disables all only-owner actions/functions,\n// Embedded usage: behaves like a burn functionality, removing the owner from the struct\nfunc (o *Ownable) DropOwnership() error {\n\terr := o.CallerIsOwner()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tprevOwner := o.owner\n\to.owner = \"\"\n\n\tstd.Emit(\n\t\tOwnershipTransferEvent,\n\t\t\"from\", string(prevOwner),\n\t\t\"to\", \"\",\n\t)\n\n\treturn nil\n}\n\n// Owner returns the owner address from Ownable\nfunc (o Ownable) Owner() std.Address {\n\treturn o.owner\n}\n\n// CallerIsOwner checks if the caller of the function is the Realm's owner\nfunc (o Ownable) CallerIsOwner() error {\n\tif std.PreviousRealm().Addr() == o.owner {\n\t\treturn nil\n\t}\n\n\treturn ErrUnauthorized\n}\n\n// AssertCallerIsOwner panics if the caller is not the owner\nfunc (o Ownable) AssertCallerIsOwner() {\n\tif std.PreviousRealm().Addr() != o.owner {\n\t\tpanic(ErrUnauthorized)\n\t}\n}\n"},{"name":"ownable_test.gno","body":"package ownable\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\talice = testutils.TestAddress(\"alice\")\n\tbob = testutils.TestAddress(\"bob\")\n)\n\nfunc TestNew(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOriginCaller(alice) // TODO(bug): should not be needed\n\n\to := New()\n\tgot := o.Owner()\n\tif alice != got {\n\t\tt.Fatalf(\"Expected %s, got: %s\", alice, got)\n\t}\n}\n\nfunc TestNewWithAddress(t *testing.T) {\n\to := NewWithAddress(alice)\n\n\tgot := o.Owner()\n\tif alice != got {\n\t\tt.Fatalf(\"Expected %s, got: %s\", alice, got)\n\t}\n}\n\nfunc TestTransferOwnership(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\n\to := New()\n\n\terr := o.TransferOwnership(bob)\n\tif err != nil {\n\t\tt.Fatalf(\"TransferOwnership failed, %v\", err)\n\t}\n\n\tgot := o.Owner()\n\tif bob != got {\n\t\tt.Fatalf(\"Expected: %s, got: %s\", bob, got)\n\t}\n}\n\nfunc TestCallerIsOwner(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\n\to := New()\n\tunauthorizedCaller := bob\n\n\tstd.TestSetRealm(std.NewUserRealm(unauthorizedCaller))\n\tstd.TestSetOriginCaller(unauthorizedCaller) // TODO(bug): should not be needed\n\n\terr := o.CallerIsOwner()\n\tuassert.Error(t, err) // XXX: IsError(..., unauthorizedCaller)\n}\n\nfunc TestDropOwnership(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\n\to := New()\n\n\terr := o.DropOwnership()\n\tuassert.NoError(t, err, \"DropOwnership failed\")\n\n\towner := o.Owner()\n\tuassert.Empty(t, owner, \"owner should be empty\")\n}\n\n// Errors\n\nfunc TestErrUnauthorized(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOriginCaller(alice) // TODO(bug): should not be needed\n\n\to := New()\n\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\tstd.TestSetOriginCaller(bob) // TODO(bug): should not be needed\n\n\terr := o.TransferOwnership(alice)\n\tif err != ErrUnauthorized {\n\t\tt.Fatalf(\"Should've been ErrUnauthorized, was %v\", err)\n\t}\n\n\terr = o.DropOwnership()\n\tuassert.ErrorContains(t, err, ErrUnauthorized.Error())\n}\n\nfunc TestErrInvalidAddress(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\n\to := New()\n\n\terr := o.TransferOwnership(\"\")\n\tuassert.ErrorContains(t, err, ErrInvalidAddress.Error())\n\n\terr = o.TransferOwnership(\"10000000001000000000100000000010000000001000000000\")\n\tuassert.ErrorContains(t, err, ErrInvalidAddress.Error())\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":"seqid","path":"gno.land/p/demo/seqid","files":[{"name":"README.md","body":"# seqid\n\n```\npackage seqid // import \"gno.land/p/demo/seqid\"\n\nPackage seqid provides a simple way to have sequential IDs which will be ordered\ncorrectly when inserted in an AVL tree.\n\nSample usage:\n\n var id seqid.ID\n var users avl.Tree\n\n func NewUser() {\n \tusers.Set(id.Next().Binary(), \u0026User{ ... })\n }\n\nTYPES\n\ntype ID uint64\n An ID is a simple sequential ID generator.\n\nfunc FromBinary(b string) (ID, bool)\n FromBinary creates a new ID from the given string.\n\nfunc (i ID) Binary() string\n Binary returns a big-endian binary representation of the ID, suitable to be\n used as an AVL key.\n\nfunc (i *ID) Next() ID\n Next advances the ID i. It will panic if increasing ID would overflow.\n\nfunc (i *ID) TryNext() (ID, bool)\n TryNext increases i by 1 and returns its value. It returns true if\n successful, or false if the increment would result in an overflow.\n```\n"},{"name":"seqid.gno","body":"// Package seqid provides a simple way to have sequential IDs which will be\n// ordered correctly when inserted in an AVL tree.\n//\n// Sample usage:\n//\n//\tvar id seqid.ID\n//\tvar users avl.Tree\n//\n//\tfunc NewUser() {\n//\t\tusers.Set(id.Next().String(), \u0026User{ ... })\n//\t}\npackage seqid\n\nimport (\n\t\"encoding/binary\"\n\n\t\"gno.land/p/demo/cford32\"\n)\n\n// An ID is a simple sequential ID generator.\ntype ID uint64\n\n// Next advances the ID i.\n// It will panic if increasing ID would overflow.\nfunc (i *ID) Next() ID {\n\tnext, ok := i.TryNext()\n\tif !ok {\n\t\tpanic(\"seqid: next ID overflows uint64\")\n\t}\n\treturn next\n}\n\nconst maxID ID = 1\u003c\u003c64 - 1\n\n// TryNext increases i by 1 and returns its value.\n// It returns true if successful, or false if the increment would result in\n// an overflow.\nfunc (i *ID) TryNext() (ID, bool) {\n\tif *i == maxID {\n\t\t// Addition will overflow.\n\t\treturn 0, false\n\t}\n\t*i++\n\treturn *i, true\n}\n\n// Binary returns a big-endian binary representation of the ID,\n// suitable to be used as an AVL key.\nfunc (i ID) Binary() string {\n\tbuf := make([]byte, 8)\n\tbinary.BigEndian.PutUint64(buf, uint64(i))\n\treturn string(buf)\n}\n\n// String encodes i using cford32's compact encoding. For more information,\n// see the documentation for package [gno.land/p/demo/cford32].\n//\n// The result of String will be a 7-byte string for IDs [0,2^34), and a\n// 13-byte string for all values following that. All generated string IDs\n// follow the same lexicographic order as their number values; that is, for any\n// two IDs (x, y) such that x \u003c y, x.String() \u003c y.String().\n// As such, this string representation is suitable to be used as an AVL key.\nfunc (i ID) String() string {\n\treturn string(cford32.PutCompact(uint64(i)))\n}\n\n// FromBinary creates a new ID from the given string, expected to be a binary\n// big-endian encoding of an ID (such as that of [ID.Binary]).\n// The second return value is true if the conversion was successful.\nfunc FromBinary(b string) (ID, bool) {\n\tif len(b) != 8 {\n\t\treturn 0, false\n\t}\n\treturn ID(binary.BigEndian.Uint64([]byte(b))), true\n}\n\n// FromString creates a new ID from the given string, expected to be a string\n// representation using cford32, such as that returned by [ID.String].\n//\n// The encoding scheme used by cford32 allows the same ID to have many\n// different representations (though the one returned by [ID.String] is only\n// one, deterministic and safe to be used in AVL). The encoding scheme is\n// \"human-centric\" and is thus case insensitive, and maps some ambiguous\n// characters to be the same, ie. L = I = 1, O = 0. For this reason, when\n// parsing user input to retrieve a key (encoded as a string), always sanitize\n// it first using FromString, then run String(), instead of using the user's\n// input directly.\nfunc FromString(b string) (ID, error) {\n\tn, err := cford32.Uint64([]byte(b))\n\treturn ID(n), err\n}\n"},{"name":"seqid_test.gno","body":"package seqid\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestID(t *testing.T) {\n\tvar i ID\n\n\tfor j := 0; j \u003c 100; j++ {\n\t\ti.Next()\n\t}\n\tif i != 100 {\n\t\tt.Fatalf(\"invalid: wanted %d got %d\", 100, i)\n\t}\n}\n\nfunc TestID_Overflow(t *testing.T) {\n\ti := ID(maxID)\n\n\tdefer func() {\n\t\terr := recover()\n\t\tif !strings.Contains(fmt.Sprint(err), \"next ID overflows\") {\n\t\t\tt.Errorf(\"did not overflow\")\n\t\t}\n\t}()\n\n\ti.Next()\n}\n\nfunc TestID_Binary(t *testing.T) {\n\tvar i ID\n\tprev := i.Binary()\n\n\tfor j := 0; j \u003c 1000; j++ {\n\t\tcur := i.Next().Binary()\n\t\tif cur \u003c= prev {\n\t\t\tt.Fatalf(\"cur %x \u003e prev %x\", cur, prev)\n\t\t}\n\t\tprev = cur\n\t}\n}\n\nfunc TestID_String(t *testing.T) {\n\tvar i ID\n\tprev := i.String()\n\n\tfor j := 0; j \u003c 1000; j++ {\n\t\tcur := i.Next().String()\n\t\tif cur \u003c= prev {\n\t\t\tt.Fatalf(\"cur %s \u003e prev %s\", cur, prev)\n\t\t}\n\t\tprev = cur\n\t}\n\n\t// Test for when cford32 switches over to the long encoding.\n\ti = 1\u003c\u003c34 - 512\n\tfor j := 0; j \u003c 1024; j++ {\n\t\tcur := i.Next().String()\n\t\t// println(cur)\n\t\tif cur \u003c= prev {\n\t\t\tt.Fatalf(\"cur %s \u003e prev %s\", cur, prev)\n\t\t}\n\t\tprev = cur\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":"memeland","path":"gno.land/p/demo/memeland","files":[{"name":"memeland.gno","body":"package memeland\n\nimport (\n\t\"sort\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/seqid\"\n)\n\nconst (\n\tDATE_CREATED = \"DATE_CREATED\"\n\tUPVOTES = \"UPVOTES\"\n)\n\ntype Post struct {\n\tID string\n\tData string\n\tAuthor std.Address\n\tTimestamp time.Time\n\tUpvoteTracker *avl.Tree // address \u003e struct{}{}\n}\n\ntype Memeland struct {\n\t*ownable.Ownable\n\tPosts []*Post\n\tMemeCounter seqid.ID\n}\n\nfunc NewMemeland() *Memeland {\n\treturn \u0026Memeland{\n\t\tOwnable: ownable.New(),\n\t\tPosts: make([]*Post, 0),\n\t}\n}\n\n// PostMeme - Adds a new post\nfunc (m *Memeland) PostMeme(data string, timestamp int64) string {\n\tif data == \"\" || timestamp \u003c= 0 {\n\t\tpanic(\"timestamp or data cannot be empty\")\n\t}\n\n\t// Generate ID\n\tid := m.MemeCounter.Next().String()\n\n\tnewPost := \u0026Post{\n\t\tID: id,\n\t\tData: data,\n\t\tAuthor: std.PrevRealm().Addr(),\n\t\tTimestamp: time.Unix(timestamp, 0),\n\t\tUpvoteTracker: avl.NewTree(),\n\t}\n\n\tm.Posts = append(m.Posts, newPost)\n\treturn id\n}\n\nfunc (m *Memeland) Upvote(id string) string {\n\tpost := m.getPost(id)\n\tif post == nil {\n\t\tpanic(\"post with specified ID does not exist\")\n\t}\n\n\tcaller := std.PrevRealm().Addr().String()\n\n\tif _, exists := post.UpvoteTracker.Get(caller); exists {\n\t\tpanic(\"user has already upvoted this post\")\n\t}\n\n\tpost.UpvoteTracker.Set(caller, struct{}{})\n\n\treturn \"upvote successful\"\n}\n\n// GetPostsInRange returns a JSON string of posts within the given timestamp range, supporting pagination\nfunc (m *Memeland) GetPostsInRange(startTimestamp, endTimestamp int64, page, pageSize int, sortBy string) string {\n\tif len(m.Posts) == 0 {\n\t\treturn \"[]\"\n\t}\n\n\tif page \u003c 1 {\n\t\tpanic(\"page number cannot be less than 1\")\n\t}\n\n\t// No empty pages\n\tif pageSize \u003c 1 {\n\t\tpanic(\"page size cannot be less than 1\")\n\t}\n\n\t// No pages larger than 10\n\tif pageSize \u003e 10 {\n\t\tpanic(\"page size cannot be larger than 10\")\n\t}\n\n\t// Need to pass in a sort parameter\n\tif sortBy == \"\" {\n\t\tpanic(\"sort order cannot be empty\")\n\t}\n\n\tvar filteredPosts []*Post\n\n\tstart := time.Unix(startTimestamp, 0)\n\tend := time.Unix(endTimestamp, 0)\n\n\t// Filtering posts\n\tfor _, p := range m.Posts {\n\t\tif !p.Timestamp.Before(start) \u0026\u0026 !p.Timestamp.After(end) {\n\t\t\tfilteredPosts = append(filteredPosts, p)\n\t\t}\n\t}\n\n\tswitch sortBy {\n\t// Sort by upvote descending\n\tcase UPVOTES:\n\t\tdateSorter := PostSorter{\n\t\t\tPosts: filteredPosts,\n\t\t\tLessF: func(i, j int) bool {\n\t\t\t\treturn filteredPosts[i].UpvoteTracker.Size() \u003e filteredPosts[j].UpvoteTracker.Size()\n\t\t\t},\n\t\t}\n\t\tsort.Sort(dateSorter)\n\tcase DATE_CREATED:\n\t\t// Sort by timestamp, beginning with newest\n\t\tdateSorter := PostSorter{\n\t\t\tPosts: filteredPosts,\n\t\t\tLessF: func(i, j int) bool {\n\t\t\t\treturn filteredPosts[i].Timestamp.After(filteredPosts[j].Timestamp)\n\t\t\t},\n\t\t}\n\t\tsort.Sort(dateSorter)\n\tdefault:\n\t\tpanic(\"sort order can only be \\\"UPVOTES\\\" or \\\"DATE_CREATED\\\"\")\n\t}\n\n\t// Pagination\n\tstartIndex := (page - 1) * pageSize\n\tendIndex := startIndex + pageSize\n\n\t// If page does not contain any posts\n\tif startIndex \u003e= len(filteredPosts) {\n\t\treturn \"[]\"\n\t}\n\n\t// If page contains fewer posts than the page size\n\tif endIndex \u003e len(filteredPosts) {\n\t\tendIndex = len(filteredPosts)\n\t}\n\n\t// Return JSON representation of paginated and sorted posts\n\treturn PostsToJSONString(filteredPosts[startIndex:endIndex])\n}\n\n// RemovePost allows the owner to remove a post with a specific ID\nfunc (m *Memeland) RemovePost(id string) string {\n\tif id == \"\" {\n\t\tpanic(\"id cannot be empty\")\n\t}\n\n\tif err := m.CallerIsOwner(); err != nil {\n\t\tpanic(err)\n\t}\n\n\tfor i, post := range m.Posts {\n\t\tif post.ID == id {\n\t\t\tm.Posts = append(m.Posts[:i], m.Posts[i+1:]...)\n\t\t\treturn id\n\t\t}\n\t}\n\n\tpanic(\"post with specified id does not exist\")\n}\n\n// PostsToJSONString converts a slice of Post structs into a JSON string\nfunc PostsToJSONString(posts []*Post) string {\n\tvar sb strings.Builder\n\tsb.WriteString(\"[\")\n\n\tfor i, post := range posts {\n\t\tif i \u003e 0 {\n\t\t\tsb.WriteString(\",\")\n\t\t}\n\n\t\tsb.WriteString(PostToJSONString(post))\n\t}\n\tsb.WriteString(\"]\")\n\n\treturn sb.String()\n}\n\n// PostToJSONString returns a Post formatted as a JSON string\nfunc PostToJSONString(post *Post) string {\n\tvar sb strings.Builder\n\n\tsb.WriteString(\"{\")\n\tsb.WriteString(`\"id\":\"` + post.ID + `\",`)\n\tsb.WriteString(`\"data\":\"` + escapeString(post.Data) + `\",`)\n\tsb.WriteString(`\"author\":\"` + escapeString(post.Author.String()) + `\",`)\n\tsb.WriteString(`\"timestamp\":\"` + strconv.Itoa(int(post.Timestamp.Unix())) + `\",`)\n\tsb.WriteString(`\"upvotes\":` + strconv.Itoa(post.UpvoteTracker.Size()))\n\tsb.WriteString(\"}\")\n\n\treturn sb.String()\n}\n\n// escapeString escapes quotes in a string for JSON compatibility.\nfunc escapeString(s string) string {\n\treturn strings.ReplaceAll(s, `\"`, `\\\"`)\n}\n\nfunc (m *Memeland) getPost(id string) *Post {\n\tfor _, p := range m.Posts {\n\t\tif p.ID == id {\n\t\t\treturn p\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// PostSorter is a flexible sorter for the *Post slice\ntype PostSorter struct {\n\tPosts []*Post\n\tLessF func(i, j int) bool\n}\n\nfunc (p PostSorter) Len() int {\n\treturn len(p.Posts)\n}\n\nfunc (p PostSorter) Swap(i, j int) {\n\tp.Posts[i], p.Posts[j] = p.Posts[j], p.Posts[i]\n}\n\nfunc (p PostSorter) Less(i, j int) bool {\n\treturn p.LessF(i, j)\n}\n"},{"name":"memeland_test.gno","body":"package memeland\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\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 TestPostMeme(t *testing.T) {\n\tm := NewMemeland()\n\tid := m.PostMeme(\"Test meme data\", time.Now().Unix())\n\tuassert.NotEqual(t, \"\", string(id), \"Expected valid ID, got empty string\")\n}\n\nfunc TestGetPostsInRangePagination(t *testing.T) {\n\tm := NewMemeland()\n\tnow := time.Now()\n\n\tnumOfPosts := 5\n\tvar memeData []string\n\tfor i := 1; i \u003c= numOfPosts; i++ {\n\t\t// Prepare meme data\n\t\tnextTime := now.Add(time.Duration(i) * time.Minute)\n\t\tdata := ufmt.Sprintf(\"Meme #%d\", i)\n\t\tmemeData = append(memeData, data)\n\n\t\tm.PostMeme(data, nextTime.Unix())\n\t}\n\n\t// Get timestamps\n\tbeforeEarliest := now.Add(-1 * time.Minute)\n\tafterLatest := now.Add(time.Duration(numOfPosts)*time.Minute + time.Minute)\n\n\ttestCases := []struct {\n\t\tpage int\n\t\tpageSize int\n\t\texpectedNumOfPosts int\n\t}{\n\t\t{page: 1, pageSize: 1, expectedNumOfPosts: 1}, // one per page\n\t\t{page: 2, pageSize: 1, expectedNumOfPosts: 1}, // one on second page\n\t\t{page: 1, pageSize: numOfPosts, expectedNumOfPosts: numOfPosts}, // all posts on single page\n\t\t{page: 12, pageSize: 1, expectedNumOfPosts: 0}, // empty page\n\t\t{page: 1, pageSize: numOfPosts + 1, expectedNumOfPosts: numOfPosts}, // page with fewer posts than its size\n\t\t{page: 5, pageSize: numOfPosts / 5, expectedNumOfPosts: 1}, // evenly distribute posts per page\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(ufmt.Sprintf(\"Page%d_Size%d\", tc.page, tc.pageSize), func(t *testing.T) {\n\t\t\tresult := m.GetPostsInRange(beforeEarliest.Unix(), afterLatest.Unix(), tc.page, tc.pageSize, \"DATE_CREATED\")\n\n\t\t\t// Count posts by how many times id: shows up in JSON string\n\t\t\tpostCount := strings.Count(result, `\"id\":\"`)\n\t\t\tuassert.Equal(t, tc.expectedNumOfPosts, postCount)\n\t\t})\n\t}\n}\n\nfunc TestGetPostsInRangeByTimestamp(t *testing.T) {\n\tm := NewMemeland()\n\tnow := time.Now()\n\n\tnumOfPosts := 5\n\tvar memeData []string\n\tfor i := 1; i \u003c= numOfPosts; i++ {\n\t\t// Prepare meme data\n\t\tnextTime := now.Add(time.Duration(i) * time.Minute)\n\t\tdata := ufmt.Sprintf(\"Meme #%d\", i)\n\t\tmemeData = append(memeData, data)\n\n\t\tm.PostMeme(data, nextTime.Unix())\n\t}\n\n\t// Get timestamps\n\tbeforeEarliest := now.Add(-1 * time.Minute)\n\tafterLatest := now.Add(time.Duration(numOfPosts)*time.Minute + time.Minute)\n\n\t// Default sort is by addition order/timestamp\n\tjsonStr := m.GetPostsInRange(\n\t\tbeforeEarliest.Unix(), // start at earliest post\n\t\tafterLatest.Unix(), // end at latest post\n\t\t1, // first page\n\t\tnumOfPosts, // all memes on the page\n\t\t\"DATE_CREATED\", // sort by newest first\n\t)\n\n\tuassert.NotEmpty(t, jsonStr, \"Expected non-empty JSON string, got empty string\")\n\n\t// Count the number of posts returned in the JSON string as a rudimentary check for correct pagination/filtering\n\tpostCount := strings.Count(jsonStr, `\"id\":\"`)\n\tuassert.Equal(t, uint64(m.MemeCounter), uint64(postCount))\n\n\t// Check if data is there\n\tfor _, expData := range memeData {\n\t\tcheck := strings.Contains(jsonStr, expData)\n\t\tuassert.True(t, check, ufmt.Sprintf(\"Expected %s in the JSON string, but counld't find it\", expData))\n\t}\n\n\t// Check if ordering is correct, sort by created date\n\tfor i := 0; i \u003c len(memeData)-2; i++ {\n\t\tcheck := strings.Index(jsonStr, memeData[i]) \u003e= strings.Index(jsonStr, memeData[i+1])\n\t\tuassert.True(t, check, ufmt.Sprintf(\"Expected %s to be before %s, but was at %d, and %d\", memeData[i], memeData[i+1], i, i+1))\n\t}\n}\n\nfunc TestGetPostsInRangeByUpvote(t *testing.T) {\n\tm := NewMemeland()\n\tnow := time.Now()\n\n\tmemeData1 := \"Meme #1\"\n\tmemeData2 := \"Meme #2\"\n\n\t// Create posts at specific times for testing\n\tid1 := m.PostMeme(memeData1, now.Unix())\n\tid2 := m.PostMeme(memeData2, now.Add(time.Minute).Unix())\n\n\tm.Upvote(id1)\n\tm.Upvote(id2)\n\n\t// Change caller so avoid double upvote panic\n\tstd.TestSetOriginCaller(testutils.TestAddress(\"alice\"))\n\tm.Upvote(id1)\n\n\t// Final upvote count:\n\t// Meme #1 - 2 upvote\n\t// Meme #2 - 1 upvotes\n\n\t// Get timestamps\n\tbeforeEarliest := now.Add(-time.Minute)\n\tafterLatest := now.Add(time.Hour)\n\n\t// Default sort is by addition order/timestamp\n\tjsonStr := m.GetPostsInRange(\n\t\tbeforeEarliest.Unix(), // start at earliest post\n\t\tafterLatest.Unix(), // end at latest post\n\t\t1, // first page\n\t\t2, // all memes on the page\n\t\t\"UPVOTES\", // sort by upvote\n\t)\n\n\tuassert.NotEmpty(t, jsonStr, \"Expected non-empty JSON string, got empty string\")\n\n\t// Count the number of posts returned in the JSON string as a rudimentary check for correct pagination/filtering\n\tpostCount := strings.Count(jsonStr, `\"id\":\"`)\n\tuassert.Equal(t, uint64(m.MemeCounter), uint64(postCount))\n\n\t// Check if ordering is correct\n\tcheck := strings.Index(jsonStr, \"Meme #1\") \u003c= strings.Index(jsonStr, \"Meme #2\")\n\tuassert.True(t, check, ufmt.Sprintf(\"Expected %s to be before %s\", memeData1, memeData2))\n}\n\nfunc TestBadSortBy(t *testing.T) {\n\tm := NewMemeland()\n\tnow := time.Now()\n\n\tnumOfPosts := 5\n\tvar memeData []string\n\tfor i := 1; i \u003c= numOfPosts; i++ {\n\t\t// Prepare meme data\n\t\tnextTime := now.Add(time.Duration(i) * time.Minute)\n\t\tdata := ufmt.Sprintf(\"Meme #%d\", i)\n\t\tmemeData = append(memeData, data)\n\n\t\tm.PostMeme(data, nextTime.Unix())\n\t}\n\n\t// Get timestamps\n\tbeforeEarliest := now.Add(-1 * time.Minute)\n\tafterLatest := now.Add(time.Duration(numOfPosts)*time.Minute + time.Minute)\n\n\ttests := []struct {\n\t\tname string\n\t\tsortBy string\n\t\twantPanic string\n\t}{\n\t\t{\n\t\t\tname: \"Empty sortBy\",\n\t\t\tsortBy: \"\",\n\t\t\twantPanic: \"runtime error: index out of range\",\n\t\t},\n\t\t{\n\t\t\tname: \"Wrong sortBy\",\n\t\t\tsortBy: \"random string\",\n\t\t\twantPanic: \"\",\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tdefer func() {\n\t\t\t\tif r := recover(); r == nil {\n\t\t\t\t\tt.Errorf(\"code did not panic when it should have\")\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\t// Panics should be caught\n\t\t\t_ = m.GetPostsInRange(beforeEarliest.Unix(), afterLatest.Unix(), 1, 1, tc.sortBy)\n\t\t})\n\t}\n}\n\nfunc TestNoPosts(t *testing.T) {\n\tm := NewMemeland()\n\n\t// Add a post to Memeland\n\tnow := time.Now().Unix()\n\n\tjsonStr := m.GetPostsInRange(0, now, 1, 1, \"DATE_CREATED\")\n\n\tuassert.Equal(t, jsonStr, \"[]\")\n}\n\nfunc TestUpvote(t *testing.T) {\n\tm := NewMemeland()\n\n\t// Add a post to Memeland\n\tnow := time.Now().Unix()\n\tpostID := m.PostMeme(\"Test meme data\", now)\n\n\t// Initial upvote count should be 0\n\tpost := m.getPost(postID)\n\tuassert.Equal(t, 0, post.UpvoteTracker.Size())\n\n\t// Upvote the post\n\tupvoteResult := m.Upvote(postID)\n\tuassert.Equal(t, \"upvote successful\", upvoteResult)\n\n\t// Retrieve the post again and check the upvote count\n\tpost = m.getPost(postID)\n\tuassert.Equal(t, 1, post.UpvoteTracker.Size())\n}\n\nfunc TestDelete(t *testing.T) {\n\talice := testutils.TestAddress(\"alice\")\n\tstd.TestSetOriginCaller(alice)\n\n\t// Alice is admin\n\tm := NewMemeland()\n\n\t// Set caller to Bob\n\tbob := testutils.TestAddress(\"bob\")\n\tstd.TestSetOriginCaller(bob)\n\n\t// Bob adds post to Memeland\n\tnow := time.Now()\n\tpostID := m.PostMeme(\"Meme #1\", now.Unix())\n\n\t// Alice removes Bob's post\n\tstd.TestSetOriginCaller(alice)\n\n\tid := m.RemovePost(postID)\n\tuassert.Equal(t, postID, id, \"post IDs not matching\")\n\tuassert.Equal(t, 0, len(m.Posts), \"there should be 0 posts after removing\")\n}\n\nfunc TestDeleteByNonAdmin(t *testing.T) {\n\talice := testutils.TestAddress(\"alice\")\n\tstd.TestSetOriginCaller(alice)\n\n\tm := NewMemeland()\n\n\t// Add a post to Memeland\n\tnow := time.Now()\n\tpostID := m.PostMeme(\"Meme #1\", now.Unix())\n\n\t// Bob will try to delete meme posted by Alice, which should fail\n\tbob := testutils.TestAddress(\"bob\")\n\tstd.TestSetOriginCaller(bob)\n\n\tdefer func() {\n\t\tif r := recover(); r == nil {\n\t\t\tt.Errorf(\"code did not panic when it should have\")\n\t\t}\n\t}()\n\n\t// Should panic - caught by defer\n\tm.RemovePost(postID)\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":"memeland","path":"gno.land/p/demo/memeland","files":[{"name":"memeland.gno","body":"package memeland\n\nimport (\n\t\"sort\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/seqid\"\n)\n\nconst (\n\tDATE_CREATED = \"DATE_CREATED\"\n\tUPVOTES = \"UPVOTES\"\n)\n\ntype Post struct {\n\tID string\n\tData string\n\tAuthor std.Address\n\tTimestamp time.Time\n\tUpvoteTracker *avl.Tree // address \u003e struct{}{}\n}\n\ntype Memeland struct {\n\t*ownable.Ownable\n\tPosts []*Post\n\tMemeCounter seqid.ID\n}\n\nfunc NewMemeland() *Memeland {\n\treturn \u0026Memeland{\n\t\tOwnable: ownable.New(),\n\t\tPosts: make([]*Post, 0),\n\t}\n}\n\n// PostMeme - Adds a new post\nfunc (m *Memeland) PostMeme(data string, timestamp int64) string {\n\tif data == \"\" || timestamp \u003c= 0 {\n\t\tpanic(\"timestamp or data cannot be empty\")\n\t}\n\n\t// Generate ID\n\tid := m.MemeCounter.Next().String()\n\n\tnewPost := \u0026Post{\n\t\tID: id,\n\t\tData: data,\n\t\tAuthor: std.PreviousRealm().Addr(),\n\t\tTimestamp: time.Unix(timestamp, 0),\n\t\tUpvoteTracker: avl.NewTree(),\n\t}\n\n\tm.Posts = append(m.Posts, newPost)\n\treturn id\n}\n\nfunc (m *Memeland) Upvote(id string) string {\n\tpost := m.getPost(id)\n\tif post == nil {\n\t\tpanic(\"post with specified ID does not exist\")\n\t}\n\n\tcaller := std.PreviousRealm().Addr().String()\n\n\tif _, exists := post.UpvoteTracker.Get(caller); exists {\n\t\tpanic(\"user has already upvoted this post\")\n\t}\n\n\tpost.UpvoteTracker.Set(caller, struct{}{})\n\n\treturn \"upvote successful\"\n}\n\n// GetPostsInRange returns a JSON string of posts within the given timestamp range, supporting pagination\nfunc (m *Memeland) GetPostsInRange(startTimestamp, endTimestamp int64, page, pageSize int, sortBy string) string {\n\tif len(m.Posts) == 0 {\n\t\treturn \"[]\"\n\t}\n\n\tif page \u003c 1 {\n\t\tpanic(\"page number cannot be less than 1\")\n\t}\n\n\t// No empty pages\n\tif pageSize \u003c 1 {\n\t\tpanic(\"page size cannot be less than 1\")\n\t}\n\n\t// No pages larger than 10\n\tif pageSize \u003e 10 {\n\t\tpanic(\"page size cannot be larger than 10\")\n\t}\n\n\t// Need to pass in a sort parameter\n\tif sortBy == \"\" {\n\t\tpanic(\"sort order cannot be empty\")\n\t}\n\n\tvar filteredPosts []*Post\n\n\tstart := time.Unix(startTimestamp, 0)\n\tend := time.Unix(endTimestamp, 0)\n\n\t// Filtering posts\n\tfor _, p := range m.Posts {\n\t\tif !p.Timestamp.Before(start) \u0026\u0026 !p.Timestamp.After(end) {\n\t\t\tfilteredPosts = append(filteredPosts, p)\n\t\t}\n\t}\n\n\tswitch sortBy {\n\t// Sort by upvote descending\n\tcase UPVOTES:\n\t\tdateSorter := PostSorter{\n\t\t\tPosts: filteredPosts,\n\t\t\tLessF: func(i, j int) bool {\n\t\t\t\treturn filteredPosts[i].UpvoteTracker.Size() \u003e filteredPosts[j].UpvoteTracker.Size()\n\t\t\t},\n\t\t}\n\t\tsort.Sort(dateSorter)\n\tcase DATE_CREATED:\n\t\t// Sort by timestamp, beginning with newest\n\t\tdateSorter := PostSorter{\n\t\t\tPosts: filteredPosts,\n\t\t\tLessF: func(i, j int) bool {\n\t\t\t\treturn filteredPosts[i].Timestamp.After(filteredPosts[j].Timestamp)\n\t\t\t},\n\t\t}\n\t\tsort.Sort(dateSorter)\n\tdefault:\n\t\tpanic(\"sort order can only be \\\"UPVOTES\\\" or \\\"DATE_CREATED\\\"\")\n\t}\n\n\t// Pagination\n\tstartIndex := (page - 1) * pageSize\n\tendIndex := startIndex + pageSize\n\n\t// If page does not contain any posts\n\tif startIndex \u003e= len(filteredPosts) {\n\t\treturn \"[]\"\n\t}\n\n\t// If page contains fewer posts than the page size\n\tif endIndex \u003e len(filteredPosts) {\n\t\tendIndex = len(filteredPosts)\n\t}\n\n\t// Return JSON representation of paginated and sorted posts\n\treturn PostsToJSONString(filteredPosts[startIndex:endIndex])\n}\n\n// RemovePost allows the owner to remove a post with a specific ID\nfunc (m *Memeland) RemovePost(id string) string {\n\tif id == \"\" {\n\t\tpanic(\"id cannot be empty\")\n\t}\n\n\tif err := m.CallerIsOwner(); err != nil {\n\t\tpanic(err)\n\t}\n\n\tfor i, post := range m.Posts {\n\t\tif post.ID == id {\n\t\t\tm.Posts = append(m.Posts[:i], m.Posts[i+1:]...)\n\t\t\treturn id\n\t\t}\n\t}\n\n\tpanic(\"post with specified id does not exist\")\n}\n\n// PostsToJSONString converts a slice of Post structs into a JSON string\nfunc PostsToJSONString(posts []*Post) string {\n\tvar sb strings.Builder\n\tsb.WriteString(\"[\")\n\n\tfor i, post := range posts {\n\t\tif i \u003e 0 {\n\t\t\tsb.WriteString(\",\")\n\t\t}\n\n\t\tsb.WriteString(PostToJSONString(post))\n\t}\n\tsb.WriteString(\"]\")\n\n\treturn sb.String()\n}\n\n// PostToJSONString returns a Post formatted as a JSON string\nfunc PostToJSONString(post *Post) string {\n\tvar sb strings.Builder\n\n\tsb.WriteString(\"{\")\n\tsb.WriteString(`\"id\":\"` + post.ID + `\",`)\n\tsb.WriteString(`\"data\":\"` + escapeString(post.Data) + `\",`)\n\tsb.WriteString(`\"author\":\"` + escapeString(post.Author.String()) + `\",`)\n\tsb.WriteString(`\"timestamp\":\"` + strconv.Itoa(int(post.Timestamp.Unix())) + `\",`)\n\tsb.WriteString(`\"upvotes\":` + strconv.Itoa(post.UpvoteTracker.Size()))\n\tsb.WriteString(\"}\")\n\n\treturn sb.String()\n}\n\n// escapeString escapes quotes in a string for JSON compatibility.\nfunc escapeString(s string) string {\n\treturn strings.ReplaceAll(s, `\"`, `\\\"`)\n}\n\nfunc (m *Memeland) getPost(id string) *Post {\n\tfor _, p := range m.Posts {\n\t\tif p.ID == id {\n\t\t\treturn p\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// PostSorter is a flexible sorter for the *Post slice\ntype PostSorter struct {\n\tPosts []*Post\n\tLessF func(i, j int) bool\n}\n\nfunc (p PostSorter) Len() int {\n\treturn len(p.Posts)\n}\n\nfunc (p PostSorter) Swap(i, j int) {\n\tp.Posts[i], p.Posts[j] = p.Posts[j], p.Posts[i]\n}\n\nfunc (p PostSorter) Less(i, j int) bool {\n\treturn p.LessF(i, j)\n}\n"},{"name":"memeland_test.gno","body":"package memeland\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\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 TestPostMeme(t *testing.T) {\n\tm := NewMemeland()\n\tid := m.PostMeme(\"Test meme data\", time.Now().Unix())\n\tuassert.NotEqual(t, \"\", string(id), \"Expected valid ID, got empty string\")\n}\n\nfunc TestGetPostsInRangePagination(t *testing.T) {\n\tm := NewMemeland()\n\tnow := time.Now()\n\n\tnumOfPosts := 5\n\tvar memeData []string\n\tfor i := 1; i \u003c= numOfPosts; i++ {\n\t\t// Prepare meme data\n\t\tnextTime := now.Add(time.Duration(i) * time.Minute)\n\t\tdata := ufmt.Sprintf(\"Meme #%d\", i)\n\t\tmemeData = append(memeData, data)\n\n\t\tm.PostMeme(data, nextTime.Unix())\n\t}\n\n\t// Get timestamps\n\tbeforeEarliest := now.Add(-1 * time.Minute)\n\tafterLatest := now.Add(time.Duration(numOfPosts)*time.Minute + time.Minute)\n\n\ttestCases := []struct {\n\t\tpage int\n\t\tpageSize int\n\t\texpectedNumOfPosts int\n\t}{\n\t\t{page: 1, pageSize: 1, expectedNumOfPosts: 1}, // one per page\n\t\t{page: 2, pageSize: 1, expectedNumOfPosts: 1}, // one on second page\n\t\t{page: 1, pageSize: numOfPosts, expectedNumOfPosts: numOfPosts}, // all posts on single page\n\t\t{page: 12, pageSize: 1, expectedNumOfPosts: 0}, // empty page\n\t\t{page: 1, pageSize: numOfPosts + 1, expectedNumOfPosts: numOfPosts}, // page with fewer posts than its size\n\t\t{page: 5, pageSize: numOfPosts / 5, expectedNumOfPosts: 1}, // evenly distribute posts per page\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(ufmt.Sprintf(\"Page%d_Size%d\", tc.page, tc.pageSize), func(t *testing.T) {\n\t\t\tresult := m.GetPostsInRange(beforeEarliest.Unix(), afterLatest.Unix(), tc.page, tc.pageSize, \"DATE_CREATED\")\n\n\t\t\t// Count posts by how many times id: shows up in JSON string\n\t\t\tpostCount := strings.Count(result, `\"id\":\"`)\n\t\t\tuassert.Equal(t, tc.expectedNumOfPosts, postCount)\n\t\t})\n\t}\n}\n\nfunc TestGetPostsInRangeByTimestamp(t *testing.T) {\n\tm := NewMemeland()\n\tnow := time.Now()\n\n\tnumOfPosts := 5\n\tvar memeData []string\n\tfor i := 1; i \u003c= numOfPosts; i++ {\n\t\t// Prepare meme data\n\t\tnextTime := now.Add(time.Duration(i) * time.Minute)\n\t\tdata := ufmt.Sprintf(\"Meme #%d\", i)\n\t\tmemeData = append(memeData, data)\n\n\t\tm.PostMeme(data, nextTime.Unix())\n\t}\n\n\t// Get timestamps\n\tbeforeEarliest := now.Add(-1 * time.Minute)\n\tafterLatest := now.Add(time.Duration(numOfPosts)*time.Minute + time.Minute)\n\n\t// Default sort is by addition order/timestamp\n\tjsonStr := m.GetPostsInRange(\n\t\tbeforeEarliest.Unix(), // start at earliest post\n\t\tafterLatest.Unix(), // end at latest post\n\t\t1, // first page\n\t\tnumOfPosts, // all memes on the page\n\t\t\"DATE_CREATED\", // sort by newest first\n\t)\n\n\tuassert.NotEmpty(t, jsonStr, \"Expected non-empty JSON string, got empty string\")\n\n\t// Count the number of posts returned in the JSON string as a rudimentary check for correct pagination/filtering\n\tpostCount := strings.Count(jsonStr, `\"id\":\"`)\n\tuassert.Equal(t, uint64(m.MemeCounter), uint64(postCount))\n\n\t// Check if data is there\n\tfor _, expData := range memeData {\n\t\tcheck := strings.Contains(jsonStr, expData)\n\t\tuassert.True(t, check, ufmt.Sprintf(\"Expected %s in the JSON string, but counld't find it\", expData))\n\t}\n\n\t// Check if ordering is correct, sort by created date\n\tfor i := 0; i \u003c len(memeData)-2; i++ {\n\t\tcheck := strings.Index(jsonStr, memeData[i]) \u003e= strings.Index(jsonStr, memeData[i+1])\n\t\tuassert.True(t, check, ufmt.Sprintf(\"Expected %s to be before %s, but was at %d, and %d\", memeData[i], memeData[i+1], i, i+1))\n\t}\n}\n\nfunc TestGetPostsInRangeByUpvote(t *testing.T) {\n\tm := NewMemeland()\n\tnow := time.Now()\n\n\tmemeData1 := \"Meme #1\"\n\tmemeData2 := \"Meme #2\"\n\n\t// Create posts at specific times for testing\n\tid1 := m.PostMeme(memeData1, now.Unix())\n\tid2 := m.PostMeme(memeData2, now.Add(time.Minute).Unix())\n\n\tm.Upvote(id1)\n\tm.Upvote(id2)\n\n\t// Change caller so avoid double upvote panic\n\tstd.TestSetOriginCaller(testutils.TestAddress(\"alice\"))\n\tm.Upvote(id1)\n\n\t// Final upvote count:\n\t// Meme #1 - 2 upvote\n\t// Meme #2 - 1 upvotes\n\n\t// Get timestamps\n\tbeforeEarliest := now.Add(-time.Minute)\n\tafterLatest := now.Add(time.Hour)\n\n\t// Default sort is by addition order/timestamp\n\tjsonStr := m.GetPostsInRange(\n\t\tbeforeEarliest.Unix(), // start at earliest post\n\t\tafterLatest.Unix(), // end at latest post\n\t\t1, // first page\n\t\t2, // all memes on the page\n\t\t\"UPVOTES\", // sort by upvote\n\t)\n\n\tuassert.NotEmpty(t, jsonStr, \"Expected non-empty JSON string, got empty string\")\n\n\t// Count the number of posts returned in the JSON string as a rudimentary check for correct pagination/filtering\n\tpostCount := strings.Count(jsonStr, `\"id\":\"`)\n\tuassert.Equal(t, uint64(m.MemeCounter), uint64(postCount))\n\n\t// Check if ordering is correct\n\tcheck := strings.Index(jsonStr, \"Meme #1\") \u003c= strings.Index(jsonStr, \"Meme #2\")\n\tuassert.True(t, check, ufmt.Sprintf(\"Expected %s to be before %s\", memeData1, memeData2))\n}\n\nfunc TestBadSortBy(t *testing.T) {\n\tm := NewMemeland()\n\tnow := time.Now()\n\n\tnumOfPosts := 5\n\tvar memeData []string\n\tfor i := 1; i \u003c= numOfPosts; i++ {\n\t\t// Prepare meme data\n\t\tnextTime := now.Add(time.Duration(i) * time.Minute)\n\t\tdata := ufmt.Sprintf(\"Meme #%d\", i)\n\t\tmemeData = append(memeData, data)\n\n\t\tm.PostMeme(data, nextTime.Unix())\n\t}\n\n\t// Get timestamps\n\tbeforeEarliest := now.Add(-1 * time.Minute)\n\tafterLatest := now.Add(time.Duration(numOfPosts)*time.Minute + time.Minute)\n\n\ttests := []struct {\n\t\tname string\n\t\tsortBy string\n\t\twantPanic string\n\t}{\n\t\t{\n\t\t\tname: \"Empty sortBy\",\n\t\t\tsortBy: \"\",\n\t\t\twantPanic: \"runtime error: index out of range\",\n\t\t},\n\t\t{\n\t\t\tname: \"Wrong sortBy\",\n\t\t\tsortBy: \"random string\",\n\t\t\twantPanic: \"\",\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tdefer func() {\n\t\t\t\tif r := recover(); r == nil {\n\t\t\t\t\tt.Errorf(\"code did not panic when it should have\")\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\t// Panics should be caught\n\t\t\t_ = m.GetPostsInRange(beforeEarliest.Unix(), afterLatest.Unix(), 1, 1, tc.sortBy)\n\t\t})\n\t}\n}\n\nfunc TestNoPosts(t *testing.T) {\n\tm := NewMemeland()\n\n\t// Add a post to Memeland\n\tnow := time.Now().Unix()\n\n\tjsonStr := m.GetPostsInRange(0, now, 1, 1, \"DATE_CREATED\")\n\n\tuassert.Equal(t, jsonStr, \"[]\")\n}\n\nfunc TestUpvote(t *testing.T) {\n\tm := NewMemeland()\n\n\t// Add a post to Memeland\n\tnow := time.Now().Unix()\n\tpostID := m.PostMeme(\"Test meme data\", now)\n\n\t// Initial upvote count should be 0\n\tpost := m.getPost(postID)\n\tuassert.Equal(t, 0, post.UpvoteTracker.Size())\n\n\t// Upvote the post\n\tupvoteResult := m.Upvote(postID)\n\tuassert.Equal(t, \"upvote successful\", upvoteResult)\n\n\t// Retrieve the post again and check the upvote count\n\tpost = m.getPost(postID)\n\tuassert.Equal(t, 1, post.UpvoteTracker.Size())\n}\n\nfunc TestDelete(t *testing.T) {\n\talice := testutils.TestAddress(\"alice\")\n\tstd.TestSetOriginCaller(alice)\n\n\t// Alice is admin\n\tm := NewMemeland()\n\n\t// Set caller to Bob\n\tbob := testutils.TestAddress(\"bob\")\n\tstd.TestSetOriginCaller(bob)\n\n\t// Bob adds post to Memeland\n\tnow := time.Now()\n\tpostID := m.PostMeme(\"Meme #1\", now.Unix())\n\n\t// Alice removes Bob's post\n\tstd.TestSetOriginCaller(alice)\n\n\tid := m.RemovePost(postID)\n\tuassert.Equal(t, postID, id, \"post IDs not matching\")\n\tuassert.Equal(t, 0, len(m.Posts), \"there should be 0 posts after removing\")\n}\n\nfunc TestDeleteByNonAdmin(t *testing.T) {\n\talice := testutils.TestAddress(\"alice\")\n\tstd.TestSetOriginCaller(alice)\n\n\tm := NewMemeland()\n\n\t// Add a post to Memeland\n\tnow := time.Now()\n\tpostID := m.PostMeme(\"Meme #1\", now.Unix())\n\n\t// Bob will try to delete meme posted by Alice, which should fail\n\tbob := testutils.TestAddress(\"bob\")\n\tstd.TestSetOriginCaller(bob)\n\n\tdefer func() {\n\t\tif r := recover(); r == nil {\n\t\t\tt.Errorf(\"code did not panic when it should have\")\n\t\t}\n\t}()\n\n\t// Should panic - caught by defer\n\tm.RemovePost(postID)\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":"merkle","path":"gno.land/p/demo/merkle","files":[{"name":"README.md","body":"# p/demo/merkle\n\nThis package implement a merkle tree that is complient with [merkletreejs](https://github.com/merkletreejs/merkletreejs)\n\n## [merkletreejs](https://github.com/merkletreejs/merkletreejs)\n\n```javascript\nconst { MerkleTree } = require(\"merkletreejs\");\nconst SHA256 = require(\"crypto-js/sha256\");\n\nlet leaves = [];\nfor (let i = 0; i \u003c 10; i++) {\n leaves.push(SHA256(`node_${i}`));\n}\n\nconst tree = new MerkleTree(leaves, SHA256);\nconst root = tree.getRoot().toString(\"hex\");\n\nconsole.log(root); // cd8a40502b0b92bf58e7432a5abb2d8b60121cf2b7966d6ebaf103f907a1bc21\n```\n"},{"name":"merkle.gno","body":"package merkle\n\nimport (\n\t\"bytes\"\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n\t\"errors\"\n)\n\ntype Hashable interface {\n\tBytes() []byte\n}\n\ntype nodes []Node\n\ntype Node struct {\n\thash []byte\n\n\tposition uint8\n}\n\nfunc NewNode(hash []byte, position uint8) Node {\n\treturn Node{\n\t\thash: hash,\n\t\tposition: position,\n\t}\n}\n\nfunc (n Node) Position() uint8 {\n\treturn n.position\n}\n\nfunc (n Node) Hash() string {\n\treturn hex.EncodeToString(n.hash[:])\n}\n\ntype Tree struct {\n\tlayers []nodes\n}\n\n// Root return the merkle root of the tree\nfunc (t *Tree) Root() string {\n\tfor _, l := range t.layers {\n\t\tif len(l) == 1 {\n\t\t\treturn l[0].Hash()\n\t\t}\n\t}\n\treturn \"\"\n}\n\n// NewTree create a new Merkle Tree\nfunc NewTree(data []Hashable) *Tree {\n\ttree := \u0026Tree{}\n\n\tleaves := make([]Node, len(data))\n\n\tfor i, d := range data {\n\t\thash := sha256.Sum256(d.Bytes())\n\t\tleaves[i] = Node{hash: hash[:]}\n\t}\n\n\ttree.layers = []nodes{nodes(leaves)}\n\n\tvar buff bytes.Buffer\n\tfor len(leaves) \u003e 1 {\n\t\tlevel := make([]Node, 0, len(leaves)/2+1)\n\t\tfor i := 0; i \u003c len(leaves); i += 2 {\n\t\t\tbuff.Reset()\n\n\t\t\tif i \u003c len(leaves)-1 {\n\t\t\t\tbuff.Write(leaves[i].hash)\n\t\t\t\tbuff.Write(leaves[i+1].hash)\n\t\t\t\thash := sha256.Sum256(buff.Bytes())\n\t\t\t\tlevel = append(level, Node{\n\t\t\t\t\thash: hash[:],\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tlevel = append(level, leaves[i])\n\t\t\t}\n\t\t}\n\t\tleaves = level\n\t\ttree.layers = append(tree.layers, level)\n\t}\n\treturn tree\n}\n\n// Proof return a MerkleProof\nfunc (t *Tree) Proof(data Hashable) ([]Node, error) {\n\ttargetHash := sha256.Sum256(data.Bytes())\n\ttargetIndex := -1\n\n\tfor i, layer := range t.layers[0] {\n\t\tif bytes.Equal(targetHash[:], layer.hash) {\n\t\t\ttargetIndex = i\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif targetIndex == -1 {\n\t\treturn nil, errors.New(\"target not found\")\n\t}\n\n\tproofs := make([]Node, 0, len(t.layers))\n\n\tfor _, layer := range t.layers {\n\t\tvar pairIndex int\n\n\t\tif targetIndex%2 == 0 {\n\t\t\tpairIndex = targetIndex + 1\n\t\t} else {\n\t\t\tpairIndex = targetIndex - 1\n\t\t}\n\t\tif pairIndex \u003c len(layer) {\n\t\t\tproofs = append(proofs, Node{\n\t\t\t\thash: layer[pairIndex].hash,\n\t\t\t\tposition: uint8(targetIndex) % 2,\n\t\t\t})\n\t\t}\n\t\ttargetIndex /= 2\n\t}\n\treturn proofs, nil\n}\n\n// Verify if a merkle proof is valid\nfunc (t *Tree) Verify(leaf Hashable, proofs []Node) bool {\n\treturn Verify(t.Root(), leaf, proofs)\n}\n\n// Verify if a merkle proof is valid\nfunc Verify(root string, leaf Hashable, proofs []Node) bool {\n\thash := sha256.Sum256(leaf.Bytes())\n\n\tfor i := 0; i \u003c len(proofs); i += 1 {\n\t\tvar h []byte\n\t\tif proofs[i].position == 0 {\n\t\t\th = append(hash[:], proofs[i].hash...)\n\t\t} else {\n\t\t\th = append(proofs[i].hash, hash[:]...)\n\t\t}\n\t\thash = sha256.Sum256(h)\n\t}\n\treturn hex.EncodeToString(hash[:]) == root\n}\n"},{"name":"merkle_test.gno","body":"package merkle\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n)\n\ntype testData struct {\n\tcontent string\n}\n\nfunc (d testData) Bytes() []byte {\n\treturn []byte(d.content)\n}\n\nfunc TestMerkleTree(t *testing.T) {\n\ttests := []struct {\n\t\tsize int\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tsize: 1,\n\t\t\texpected: \"cf9f824bce7f5bc63d557b23591f58577f53fe29f974a615bdddbd0140f912f4\",\n\t\t},\n\t\t{\n\t\t\tsize: 3,\n\t\t\texpected: \"1a4a5f0fa267244bf9f74a63fdf2a87eed5e97e4bd104a9e94728c8fb5442177\",\n\t\t},\n\t\t{\n\t\t\tsize: 10,\n\t\t\texpected: \"cd8a40502b0b92bf58e7432a5abb2d8b60121cf2b7966d6ebaf103f907a1bc21\",\n\t\t},\n\t\t{\n\t\t\tsize: 1000,\n\t\t\texpected: \"fa533d2efdf12be26bc410dfa42936ac63361324e35e9b1ff54d422a1dd2388b\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tvar leaves []Hashable\n\t\tfor i := 0; i \u003c test.size; i++ {\n\t\t\tleaves = append(leaves, testData{fmt.Sprintf(\"node_%d\", i)})\n\t\t}\n\n\t\ttree := NewTree(leaves)\n\n\t\tif tree == nil {\n\t\t\tt.Error(\"Merkle tree creation failed\")\n\t\t}\n\n\t\troot := tree.Root()\n\n\t\tif root != test.expected {\n\t\t\tt.Fatalf(\"merkle.Tree.Root(), expected: %s; got: %s\", test.expected, root)\n\t\t}\n\n\t\tfor _, leaf := range leaves {\n\t\t\tproofs, err := tree.Proof(leaf)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(\"failed to proof leaf: %v, on tree: %v\", leaf, test)\n\t\t\t}\n\n\t\t\tok := Verify(root, leaf, proofs)\n\t\t\tif !ok {\n\t\t\t\tt.Fatal(\"failed to verify leaf: %v, on tree: %v\", leaf, tree)\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":"microblog","path":"gno.land/p/demo/microblog","files":[{"name":"microblog.gno","body":"package microblog\n\nimport (\n\t\"errors\"\n\t\"sort\"\n\t\"std\"\n\t\"strings\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar (\n\tErrNotFound = errors.New(\"not found\")\n\tStatusNotFound = \"404\"\n)\n\ntype Microblog struct {\n\tTitle string\n\tPrefix string // i.e. r/gnoland/blog:\n\tPages avl.Tree // author (string) -\u003e Page\n}\n\nfunc NewMicroblog(title string, prefix string) (m *Microblog) {\n\treturn \u0026Microblog{\n\t\tTitle: title,\n\t\tPrefix: prefix,\n\t\tPages: avl.Tree{},\n\t}\n}\n\nfunc (m *Microblog) GetPages() []*Page {\n\tvar (\n\t\tpages = make([]*Page, m.Pages.Size())\n\t\tindex = 0\n\t)\n\n\tm.Pages.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tpages[index] = value.(*Page)\n\t\tindex++\n\t\treturn false\n\t})\n\n\tsort.Sort(byLastPosted(pages))\n\n\treturn pages\n}\n\nfunc (m *Microblog) NewPost(text string) error {\n\tauthor := std.OriginCaller()\n\t_, found := m.Pages.Get(author.String())\n\tif !found {\n\t\t// make a new page for the new author\n\t\tm.Pages.Set(author.String(), \u0026Page{\n\t\t\tAuthor: author,\n\t\t\tCreatedAt: time.Now(),\n\t\t})\n\t}\n\n\tpage, err := m.GetPage(author.String())\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn page.NewPost(text)\n}\n\nfunc (m *Microblog) GetPage(author string) (*Page, error) {\n\tsilo, found := m.Pages.Get(author)\n\tif !found {\n\t\treturn nil, ErrNotFound\n\t}\n\treturn silo.(*Page), nil\n}\n\ntype Page struct {\n\tID int\n\tAuthor std.Address\n\tCreatedAt time.Time\n\tLastPosted time.Time\n\tPosts avl.Tree // time -\u003e Post\n}\n\n// byLastPosted implements sort.Interface for []Page based on\n// the LastPosted field.\ntype byLastPosted []*Page\n\nfunc (a byLastPosted) Len() int { return len(a) }\nfunc (a byLastPosted) Swap(i, j int) { a[i], a[j] = a[j], a[i] }\nfunc (a byLastPosted) Less(i, j int) bool { return a[i].LastPosted.After(a[j].LastPosted) }\n\nfunc (p *Page) NewPost(text string) error {\n\tnow := time.Now()\n\tp.LastPosted = now\n\tp.Posts.Set(ufmt.Sprintf(\"%s%d\", now.Format(time.RFC3339), p.Posts.Size()), \u0026Post{\n\t\tID: p.Posts.Size(),\n\t\tText: text,\n\t\tCreatedAt: now,\n\t})\n\treturn nil\n}\n\nfunc (p *Page) GetPosts() []*Post {\n\tposts := make([]*Post, p.Posts.Size())\n\ti := 0\n\tp.Posts.ReverseIterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tpostParsed := value.(*Post)\n\t\tposts[i] = postParsed\n\t\ti++\n\t\treturn false\n\t})\n\treturn posts\n}\n\n// Post lists the specific update\ntype Post struct {\n\tID int\n\tCreatedAt time.Time\n\tText string\n}\n\nfunc (p *Post) String() string {\n\treturn \"\u003e \" + strings.ReplaceAll(p.Text, \"\\n\", \"\\n\u003e\\n\u003e\") + \"\\n\u003e\\n\u003e *\" + p.CreatedAt.Format(time.RFC1123) + \"*\"\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":"nestedpkg","path":"gno.land/p/demo/nestedpkg","files":[{"name":"nestedpkg.gno","body":"// Package nestedpkg provides helpers for package-path based access control.\n// It is useful for upgrade patterns relying on namespaces.\npackage nestedpkg\n\n// To test this from a realm and have std.CurrentRealm/PrevRealm work correctly,\n// this file is tested from gno.land/r/demo/tests/nestedpkg_test.gno\n// XXX: move test to ths directory once we support testing a package and\n// specifying values for both PrevRealm and CurrentRealm.\n\nimport (\n\t\"std\"\n\t\"strings\"\n)\n\n// IsCallerSubPath checks if the caller realm is located in a subfolder of the current realm.\nfunc IsCallerSubPath() bool {\n\tvar (\n\t\tcur = std.CurrentRealm().PkgPath() + \"/\"\n\t\tprev = std.PrevRealm().PkgPath() + \"/\"\n\t)\n\treturn strings.HasPrefix(prev, cur)\n}\n\n// AssertCallerIsSubPath panics if IsCallerSubPath returns false.\nfunc AssertCallerIsSubPath() {\n\tvar (\n\t\tcur = std.CurrentRealm().PkgPath() + \"/\"\n\t\tprev = std.PrevRealm().PkgPath() + \"/\"\n\t)\n\tif !strings.HasPrefix(prev, cur) {\n\t\tpanic(\"call restricted to nested packages. current realm is \" + cur + \", previous realm is \" + prev)\n\t}\n}\n\n// IsCallerParentPath checks if the caller realm is located in a parent location of the current realm.\nfunc IsCallerParentPath() bool {\n\tvar (\n\t\tcur = std.CurrentRealm().PkgPath() + \"/\"\n\t\tprev = std.PrevRealm().PkgPath() + \"/\"\n\t)\n\treturn strings.HasPrefix(cur, prev)\n}\n\n// AssertCallerIsParentPath panics if IsCallerParentPath returns false.\nfunc AssertCallerIsParentPath() {\n\tvar (\n\t\tcur = std.CurrentRealm().PkgPath() + \"/\"\n\t\tprev = std.PrevRealm().PkgPath() + \"/\"\n\t)\n\tif !strings.HasPrefix(cur, prev) {\n\t\tpanic(\"call restricted to parent packages. current realm is \" + cur + \", previous realm is \" + prev)\n\t}\n}\n\n// IsSameNamespace checks if the caller realm and the current realm are in the same namespace.\nfunc IsSameNamespace() bool {\n\tvar (\n\t\tcur = nsFromPath(std.CurrentRealm().PkgPath()) + \"/\"\n\t\tprev = nsFromPath(std.PrevRealm().PkgPath()) + \"/\"\n\t)\n\treturn cur == prev\n}\n\n// AssertIsSameNamespace panics if IsSameNamespace returns false.\nfunc AssertIsSameNamespace() {\n\tvar (\n\t\tcur = nsFromPath(std.CurrentRealm().PkgPath()) + \"/\"\n\t\tprev = nsFromPath(std.PrevRealm().PkgPath()) + \"/\"\n\t)\n\tif cur != prev {\n\t\tpanic(\"call restricted to packages from the same namespace. current realm is \" + cur + \", previous realm is \" + prev)\n\t}\n}\n\n// nsFromPath extracts the namespace from a package path.\nfunc nsFromPath(pkgpath string) string {\n\tparts := strings.Split(pkgpath, \"/\")\n\n\t// Specifically for gno.land, potential paths are in the form of DOMAIN/r/NAMESPACE/...\n\t// XXX: Consider extra checks.\n\t// XXX: Support non gno.land domains, where p/ and r/ won't be enforced.\n\tif len(parts) \u003e= 3 {\n\t\treturn parts[2]\n\t}\n\treturn \"\"\n}\n\n// XXX: Consider adding IsCallerDirectlySubPath\n// XXX: Consider adding IsCallerDirectlyParentPath\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":"authorizable","path":"gno.land/p/demo/ownable/exts/authorizable","files":[{"name":"authorizable.gno","body":"// Package authorizable is an extension of p/demo/ownable;\n// It allows the user to instantiate an Authorizable struct, which extends\n// p/demo/ownable with a list of users that are authorized for something.\n// By using authorizable, you have a superuser (ownable), as well as another\n// authorization level, which can be used for adding moderators or similar to your realm.\npackage authorizable\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype Authorizable struct {\n\t*ownable.Ownable // owner in ownable is superuser\n\tauthorized *avl.Tree // std.Addr \u003e struct{}{}\n}\n\nfunc NewAuthorizable() *Authorizable {\n\ta := \u0026Authorizable{\n\t\townable.New(),\n\t\tavl.NewTree(),\n\t}\n\n\t// Add owner to auth list\n\ta.authorized.Set(a.Owner().String(), struct{}{})\n\treturn a\n}\n\nfunc NewAuthorizableWithAddress(addr std.Address) *Authorizable {\n\ta := \u0026Authorizable{\n\t\townable.NewWithAddress(addr),\n\t\tavl.NewTree(),\n\t}\n\n\t// Add owner to auth list\n\ta.authorized.Set(a.Owner().String(), struct{}{})\n\treturn a\n}\n\nfunc (a *Authorizable) AddToAuthList(addr std.Address) error {\n\tif err := a.CallerIsOwner(); err != nil {\n\t\treturn ErrNotSuperuser\n\t}\n\n\tif _, exists := a.authorized.Get(addr.String()); exists {\n\t\treturn ErrAlreadyInList\n\t}\n\n\ta.authorized.Set(addr.String(), struct{}{})\n\n\treturn nil\n}\n\nfunc (a *Authorizable) DeleteFromAuthList(addr std.Address) error {\n\tif err := a.CallerIsOwner(); err != nil {\n\t\treturn ErrNotSuperuser\n\t}\n\n\tif !a.authorized.Has(addr.String()) {\n\t\treturn ErrNotInAuthList\n\t}\n\n\tif _, removed := a.authorized.Remove(addr.String()); !removed {\n\t\tstr := ufmt.Sprintf(\"authorizable: could not remove %s from auth list\", addr.String())\n\t\tpanic(str)\n\t}\n\n\treturn nil\n}\n\nfunc (a Authorizable) CallerOnAuthList() error {\n\tcaller := std.PrevRealm().Addr()\n\n\tif !a.authorized.Has(caller.String()) {\n\t\treturn ErrNotInAuthList\n\t}\n\n\treturn nil\n}\n\nfunc (a Authorizable) AssertOnAuthList() {\n\tcaller := std.PrevRealm().Addr()\n\n\tif !a.authorized.Has(caller.String()) {\n\t\tpanic(ErrNotInAuthList)\n\t}\n}\n"},{"name":"authorizable_test.gno","body":"package authorizable\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\talice = testutils.TestAddress(\"alice\")\n\tbob = testutils.TestAddress(\"bob\")\n\tcharlie = testutils.TestAddress(\"charlie\")\n)\n\nfunc TestNewAuthorizable(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOriginCaller(alice) // TODO(bug, issue #2371): should not be needed\n\n\ta := NewAuthorizable()\n\tgot := a.Owner()\n\n\tif alice != got {\n\t\tt.Fatalf(\"Expected %s, got: %s\", alice, got)\n\t}\n}\n\nfunc TestNewAuthorizableWithAddress(t *testing.T) {\n\ta := NewAuthorizableWithAddress(alice)\n\n\tgot := a.Owner()\n\n\tif alice != got {\n\t\tt.Fatalf(\"Expected %s, got: %s\", alice, got)\n\t}\n}\n\nfunc TestCallerOnAuthList(t *testing.T) {\n\ta := NewAuthorizableWithAddress(alice)\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOriginCaller(alice)\n\n\tif err := a.CallerOnAuthList(); err == ErrNotInAuthList {\n\t\tt.Fatalf(\"expected alice to be on the list\")\n\t}\n}\n\nfunc TestNotCallerOnAuthList(t *testing.T) {\n\ta := NewAuthorizableWithAddress(alice)\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\tstd.TestSetOriginCaller(bob)\n\n\tif err := a.CallerOnAuthList(); err == nil {\n\t\tt.Fatalf(\"expected bob to not be on the list\")\n\t}\n}\n\nfunc TestAddToAuthList(t *testing.T) {\n\ta := NewAuthorizableWithAddress(alice)\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOriginCaller(alice)\n\n\tif err := a.AddToAuthList(bob); err != nil {\n\t\tt.Fatalf(\"Expected no error, got %v\", err)\n\t}\n\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\tstd.TestSetOriginCaller(bob)\n\n\tif err := a.AddToAuthList(bob); err == nil {\n\t\tt.Fatalf(\"Expected AddToAuth to error while bob called it, but it didn't\")\n\t}\n}\n\nfunc TestDeleteFromList(t *testing.T) {\n\ta := NewAuthorizableWithAddress(alice)\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOriginCaller(alice)\n\n\tif err := a.AddToAuthList(bob); err != nil {\n\t\tt.Fatalf(\"Expected no error, got %v\", err)\n\t}\n\n\tif err := a.AddToAuthList(charlie); err != nil {\n\t\tt.Fatalf(\"Expected no error, got %v\", err)\n\t}\n\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\tstd.TestSetOriginCaller(bob)\n\n\t// Try an unauthorized deletion\n\tif err := a.DeleteFromAuthList(alice); err == nil {\n\t\tt.Fatalf(\"Expected DelFromAuth to error with %v\", err)\n\t}\n\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOriginCaller(alice)\n\n\tif err := a.DeleteFromAuthList(charlie); err != nil {\n\t\tt.Fatalf(\"Expected no error, got %v\", err)\n\t}\n}\n\nfunc TestAssertOnList(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOriginCaller(alice)\n\ta := NewAuthorizableWithAddress(alice)\n\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\tstd.TestSetOriginCaller(bob)\n\n\tuassert.PanicsWithMessage(t, ErrNotInAuthList.Error(), func() {\n\t\ta.AssertOnAuthList()\n\t})\n}\n"},{"name":"errors.gno","body":"package authorizable\n\nimport \"errors\"\n\nvar (\n\tErrNotInAuthList = errors.New(\"authorizable: caller is not in authorized list\")\n\tErrNotSuperuser = errors.New(\"authorizable: caller is not superuser\")\n\tErrAlreadyInList = errors.New(\"authorizable: address is already in authorized list\")\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":"nestedpkg","path":"gno.land/p/demo/nestedpkg","files":[{"name":"nestedpkg.gno","body":"// Package nestedpkg provides helpers for package-path based access control.\n// It is useful for upgrade patterns relying on namespaces.\npackage nestedpkg\n\n// To test this from a realm and have std.CurrentRealm/PreviousRealm work correctly,\n// this file is tested from gno.land/r/demo/tests/nestedpkg_test.gno\n// XXX: move test to ths directory once we support testing a package and\n// specifying values for both PreviousRealm and CurrentRealm.\n\nimport (\n\t\"std\"\n\t\"strings\"\n)\n\n// IsCallerSubPath checks if the caller realm is located in a subfolder of the current realm.\nfunc IsCallerSubPath() bool {\n\tvar (\n\t\tcur = std.CurrentRealm().PkgPath() + \"/\"\n\t\tprev = std.PreviousRealm().PkgPath() + \"/\"\n\t)\n\treturn strings.HasPrefix(prev, cur)\n}\n\n// AssertCallerIsSubPath panics if IsCallerSubPath returns false.\nfunc AssertCallerIsSubPath() {\n\tvar (\n\t\tcur = std.CurrentRealm().PkgPath() + \"/\"\n\t\tprev = std.PreviousRealm().PkgPath() + \"/\"\n\t)\n\tif !strings.HasPrefix(prev, cur) {\n\t\tpanic(\"call restricted to nested packages. current realm is \" + cur + \", previous realm is \" + prev)\n\t}\n}\n\n// IsCallerParentPath checks if the caller realm is located in a parent location of the current realm.\nfunc IsCallerParentPath() bool {\n\tvar (\n\t\tcur = std.CurrentRealm().PkgPath() + \"/\"\n\t\tprev = std.PreviousRealm().PkgPath() + \"/\"\n\t)\n\treturn strings.HasPrefix(cur, prev)\n}\n\n// AssertCallerIsParentPath panics if IsCallerParentPath returns false.\nfunc AssertCallerIsParentPath() {\n\tvar (\n\t\tcur = std.CurrentRealm().PkgPath() + \"/\"\n\t\tprev = std.PreviousRealm().PkgPath() + \"/\"\n\t)\n\tif !strings.HasPrefix(cur, prev) {\n\t\tpanic(\"call restricted to parent packages. current realm is \" + cur + \", previous realm is \" + prev)\n\t}\n}\n\n// IsSameNamespace checks if the caller realm and the current realm are in the same namespace.\nfunc IsSameNamespace() bool {\n\tvar (\n\t\tcur = nsFromPath(std.CurrentRealm().PkgPath()) + \"/\"\n\t\tprev = nsFromPath(std.PreviousRealm().PkgPath()) + \"/\"\n\t)\n\treturn cur == prev\n}\n\n// AssertIsSameNamespace panics if IsSameNamespace returns false.\nfunc AssertIsSameNamespace() {\n\tvar (\n\t\tcur = nsFromPath(std.CurrentRealm().PkgPath()) + \"/\"\n\t\tprev = nsFromPath(std.PreviousRealm().PkgPath()) + \"/\"\n\t)\n\tif cur != prev {\n\t\tpanic(\"call restricted to packages from the same namespace. current realm is \" + cur + \", previous realm is \" + prev)\n\t}\n}\n\n// nsFromPath extracts the namespace from a package path.\nfunc nsFromPath(pkgpath string) string {\n\tparts := strings.Split(pkgpath, \"/\")\n\n\t// Specifically for gno.land, potential paths are in the form of DOMAIN/r/NAMESPACE/...\n\t// XXX: Consider extra checks.\n\t// XXX: Support non gno.land domains, where p/ and r/ won't be enforced.\n\tif len(parts) \u003e= 3 {\n\t\treturn parts[2]\n\t}\n\treturn \"\"\n}\n\n// XXX: Consider adding IsCallerDirectlySubPath\n// XXX: Consider adding IsCallerDirectlyParentPath\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":"authorizable","path":"gno.land/p/demo/ownable/exts/authorizable","files":[{"name":"authorizable.gno","body":"// Package authorizable is an extension of p/demo/ownable;\n// It allows the user to instantiate an Authorizable struct, which extends\n// p/demo/ownable with a list of users that are authorized for something.\n// By using authorizable, you have a superuser (ownable), as well as another\n// authorization level, which can be used for adding moderators or similar to your realm.\npackage authorizable\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype Authorizable struct {\n\t*ownable.Ownable // owner in ownable is superuser\n\tauthorized *avl.Tree // std.Addr \u003e struct{}{}\n}\n\nfunc NewAuthorizable() *Authorizable {\n\ta := \u0026Authorizable{\n\t\townable.New(),\n\t\tavl.NewTree(),\n\t}\n\n\t// Add owner to auth list\n\ta.authorized.Set(a.Owner().String(), struct{}{})\n\treturn a\n}\n\nfunc NewAuthorizableWithAddress(addr std.Address) *Authorizable {\n\ta := \u0026Authorizable{\n\t\townable.NewWithAddress(addr),\n\t\tavl.NewTree(),\n\t}\n\n\t// Add owner to auth list\n\ta.authorized.Set(a.Owner().String(), struct{}{})\n\treturn a\n}\n\nfunc (a *Authorizable) AddToAuthList(addr std.Address) error {\n\tif err := a.CallerIsOwner(); err != nil {\n\t\treturn ErrNotSuperuser\n\t}\n\n\tif _, exists := a.authorized.Get(addr.String()); exists {\n\t\treturn ErrAlreadyInList\n\t}\n\n\ta.authorized.Set(addr.String(), struct{}{})\n\n\treturn nil\n}\n\nfunc (a *Authorizable) DeleteFromAuthList(addr std.Address) error {\n\tif err := a.CallerIsOwner(); err != nil {\n\t\treturn ErrNotSuperuser\n\t}\n\n\tif !a.authorized.Has(addr.String()) {\n\t\treturn ErrNotInAuthList\n\t}\n\n\tif _, removed := a.authorized.Remove(addr.String()); !removed {\n\t\tstr := ufmt.Sprintf(\"authorizable: could not remove %s from auth list\", addr.String())\n\t\tpanic(str)\n\t}\n\n\treturn nil\n}\n\nfunc (a Authorizable) CallerOnAuthList() error {\n\tcaller := std.PreviousRealm().Addr()\n\n\tif !a.authorized.Has(caller.String()) {\n\t\treturn ErrNotInAuthList\n\t}\n\n\treturn nil\n}\n\nfunc (a Authorizable) AssertOnAuthList() {\n\tcaller := std.PreviousRealm().Addr()\n\n\tif !a.authorized.Has(caller.String()) {\n\t\tpanic(ErrNotInAuthList)\n\t}\n}\n"},{"name":"authorizable_test.gno","body":"package authorizable\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\talice = testutils.TestAddress(\"alice\")\n\tbob = testutils.TestAddress(\"bob\")\n\tcharlie = testutils.TestAddress(\"charlie\")\n)\n\nfunc TestNewAuthorizable(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOriginCaller(alice) // TODO(bug, issue #2371): should not be needed\n\n\ta := NewAuthorizable()\n\tgot := a.Owner()\n\n\tif alice != got {\n\t\tt.Fatalf(\"Expected %s, got: %s\", alice, got)\n\t}\n}\n\nfunc TestNewAuthorizableWithAddress(t *testing.T) {\n\ta := NewAuthorizableWithAddress(alice)\n\n\tgot := a.Owner()\n\n\tif alice != got {\n\t\tt.Fatalf(\"Expected %s, got: %s\", alice, got)\n\t}\n}\n\nfunc TestCallerOnAuthList(t *testing.T) {\n\ta := NewAuthorizableWithAddress(alice)\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOriginCaller(alice)\n\n\tif err := a.CallerOnAuthList(); err == ErrNotInAuthList {\n\t\tt.Fatalf(\"expected alice to be on the list\")\n\t}\n}\n\nfunc TestNotCallerOnAuthList(t *testing.T) {\n\ta := NewAuthorizableWithAddress(alice)\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\tstd.TestSetOriginCaller(bob)\n\n\tif err := a.CallerOnAuthList(); err == nil {\n\t\tt.Fatalf(\"expected bob to not be on the list\")\n\t}\n}\n\nfunc TestAddToAuthList(t *testing.T) {\n\ta := NewAuthorizableWithAddress(alice)\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOriginCaller(alice)\n\n\tif err := a.AddToAuthList(bob); err != nil {\n\t\tt.Fatalf(\"Expected no error, got %v\", err)\n\t}\n\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\tstd.TestSetOriginCaller(bob)\n\n\tif err := a.AddToAuthList(bob); err == nil {\n\t\tt.Fatalf(\"Expected AddToAuth to error while bob called it, but it didn't\")\n\t}\n}\n\nfunc TestDeleteFromList(t *testing.T) {\n\ta := NewAuthorizableWithAddress(alice)\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOriginCaller(alice)\n\n\tif err := a.AddToAuthList(bob); err != nil {\n\t\tt.Fatalf(\"Expected no error, got %v\", err)\n\t}\n\n\tif err := a.AddToAuthList(charlie); err != nil {\n\t\tt.Fatalf(\"Expected no error, got %v\", err)\n\t}\n\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\tstd.TestSetOriginCaller(bob)\n\n\t// Try an unauthorized deletion\n\tif err := a.DeleteFromAuthList(alice); err == nil {\n\t\tt.Fatalf(\"Expected DelFromAuth to error with %v\", err)\n\t}\n\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOriginCaller(alice)\n\n\tif err := a.DeleteFromAuthList(charlie); err != nil {\n\t\tt.Fatalf(\"Expected no error, got %v\", err)\n\t}\n}\n\nfunc TestAssertOnList(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOriginCaller(alice)\n\ta := NewAuthorizableWithAddress(alice)\n\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\tstd.TestSetOriginCaller(bob)\n\n\tuassert.PanicsWithMessage(t, ErrNotInAuthList.Error(), func() {\n\t\ta.AssertOnAuthList()\n\t})\n}\n"},{"name":"errors.gno","body":"package authorizable\n\nimport \"errors\"\n\nvar (\n\tErrNotInAuthList = errors.New(\"authorizable: caller is not in authorized list\")\n\tErrNotSuperuser = errors.New(\"authorizable: caller is not superuser\")\n\tErrAlreadyInList = errors.New(\"authorizable: address is already in authorized list\")\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":"pausable","path":"gno.land/p/demo/pausable","files":[{"name":"pausable.gno","body":"package pausable\n\nimport \"gno.land/p/demo/ownable\"\n\ntype Pausable struct {\n\t*ownable.Ownable\n\tpaused bool\n}\n\n// New returns a new Pausable struct with non-paused state as default\nfunc New() *Pausable {\n\treturn \u0026Pausable{\n\t\tOwnable: ownable.New(),\n\t\tpaused: false,\n\t}\n}\n\n// NewFromOwnable is the same as New, but with a pre-existing top-level ownable\nfunc NewFromOwnable(ownable *ownable.Ownable) *Pausable {\n\treturn \u0026Pausable{\n\t\tOwnable: ownable,\n\t\tpaused: false,\n\t}\n}\n\n// IsPaused checks if Pausable is paused\nfunc (p Pausable) IsPaused() bool {\n\treturn p.paused\n}\n\n// Pause sets the state of Pausable to true, meaning all pausable functions are paused\nfunc (p *Pausable) Pause() error {\n\tif err := p.CallerIsOwner(); err != nil {\n\t\treturn err\n\t}\n\n\tp.paused = true\n\treturn nil\n}\n\n// Unpause sets the state of Pausable to false, meaning all pausable functions are resumed\nfunc (p *Pausable) Unpause() error {\n\tif err := p.CallerIsOwner(); err != nil {\n\t\treturn err\n\t}\n\n\tp.paused = false\n\treturn nil\n}\n"},{"name":"pausable_test.gno","body":"package pausable\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nvar (\n\tfirstCaller = std.Address(\"g1l9aypkr8xfvs82zeux486ddzec88ty69lue9de\")\n\tsecondCaller = std.Address(\"g127jydsh6cms3lrtdenydxsckh23a8d6emqcvfa\")\n)\n\nfunc TestNew(t *testing.T) {\n\tstd.TestSetOriginCaller(firstCaller)\n\n\tresult := New()\n\n\turequire.False(t, result.paused, \"Expected result to be unpaused\")\n\turequire.Equal(t, firstCaller.String(), result.Owner().String())\n}\n\nfunc TestNewFromOwnable(t *testing.T) {\n\tstd.TestSetOriginCaller(firstCaller)\n\to := ownable.New()\n\n\tstd.TestSetOriginCaller(secondCaller)\n\tresult := NewFromOwnable(o)\n\n\turequire.Equal(t, firstCaller.String(), result.Owner().String())\n}\n\nfunc TestSetUnpaused(t *testing.T) {\n\tstd.TestSetOriginCaller(firstCaller)\n\n\tresult := New()\n\tresult.Unpause()\n\n\turequire.False(t, result.IsPaused(), \"Expected result to be unpaused\")\n}\n\nfunc TestSetPaused(t *testing.T) {\n\tstd.TestSetOriginCaller(firstCaller)\n\n\tresult := New()\n\tresult.Pause()\n\n\turequire.True(t, result.IsPaused(), \"Expected result to be paused\")\n}\n\nfunc TestIsPaused(t *testing.T) {\n\tstd.TestSetOriginCaller(firstCaller)\n\n\tresult := New()\n\turequire.False(t, result.IsPaused(), \"Expected result to be unpaused\")\n\n\tresult.Pause()\n\turequire.True(t, result.IsPaused(), \"Expected result to be paused\")\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":"releases","path":"gno.land/p/demo/releases","files":[{"name":"changelog.gno","body":"package releases\n\ntype changelog struct {\n\tname string\n\treleases []release\n}\n\nfunc NewChangelog(name string) *changelog {\n\treturn \u0026changelog{\n\t\tname: name,\n\t\treleases: make([]release, 0),\n\t}\n}\n\nfunc (c *changelog) NewRelease(version, url, notes string) {\n\tif latest := c.Latest(); latest != nil {\n\t\tlatest.isLatest = false\n\t}\n\n\trelease := release{\n\t\t// manual\n\t\tversion: version,\n\t\turl: url,\n\t\tnotes: notes,\n\n\t\t// internal\n\t\tchangelog: c,\n\t\tisLatest: true,\n\t}\n\n\tc.releases = append(c.releases, release)\n}\n\nfunc (c *changelog) Render(path string) string {\n\tif path == \"\" {\n\t\toutput := \"# \" + c.name + \"\\n\\n\"\n\t\tmax := len(c.releases) - 1\n\t\tmin := 0\n\t\tif max-min \u003e 10 {\n\t\t\tmin = max - 10\n\t\t}\n\t\tfor i := max; i \u003e= min; i-- {\n\t\t\trelease := c.releases[i]\n\t\t\toutput += release.Render()\n\t\t}\n\t\treturn output\n\t}\n\n\trelease := c.ByVersion(path)\n\tif release != nil {\n\t\treturn release.Render()\n\t}\n\n\treturn \"no such release\"\n}\n\nfunc (c *changelog) Latest() *release {\n\tif len(c.releases) \u003e 0 {\n\t\tpos := len(c.releases) - 1\n\t\treturn \u0026c.releases[pos]\n\t}\n\treturn nil\n}\n\nfunc (c *changelog) ByVersion(version string) *release {\n\tfor _, release := range c.releases {\n\t\tif release.version == version {\n\t\t\treturn \u0026release\n\t\t}\n\t}\n\treturn nil\n}\n"},{"name":"release.gno","body":"package releases\n\ntype release struct {\n\t// manual\n\tversion string\n\turl string\n\tnotes string\n\n\t// internal\n\tisLatest bool\n\tchangelog *changelog\n}\n\nfunc (r *release) URL() string { return r.url }\nfunc (r *release) Version() string { return r.version }\nfunc (r *release) Notes() string { return r.notes }\nfunc (r *release) IsLatest() bool { return r.isLatest }\n\nfunc (r *release) Title() string {\n\toutput := r.changelog.name + \" \" + r.version\n\tif r.isLatest {\n\t\toutput += \" (latest)\"\n\t}\n\treturn output\n}\n\nfunc (r *release) Link() string {\n\treturn \"[\" + r.Title() + \"](\" + r.url + \")\"\n}\n\nfunc (r *release) Render() string {\n\toutput := \"\"\n\toutput += \"## \" + r.Link() + \"\\n\\n\"\n\tif r.notes != \"\" {\n\t\toutput += r.notes + \"\\n\\n\"\n\t}\n\treturn output\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":"simpledao","path":"gno.land/p/demo/simpledao","files":[{"name":"dao.gno","body":"package simpledao\n\nimport (\n\t\"errors\"\n\t\"std\"\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\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\tvar (\n\t\tcaller = getDAOCaller()\n\t\tsentCoins = std.OriginSend() // 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\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.OriginSend() // 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.OriginCaller()\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(\"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\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.TestSetOriginSend(sentCoins, std.Coins{})\n\n\t\t_, err := s.Propose(dao.ProposalRequest{\n\t\t\tExecutor: ex,\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\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.TestSetOriginSend(sentCoins, std.Coins{})\n\t\tstd.TestSetOriginCaller(proposer)\n\n\t\t// Make sure the proposal was added\n\t\tid, err := s.Propose(dao.ProposalRequest{\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, 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.TestSetOriginCaller(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.TestSetOriginCaller(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.TestSetOriginCaller(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.TestSetOriginCaller(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.TestSetOriginCaller(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.TestSetOriginCaller(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.TestSetOriginCaller(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.TestSetOriginCaller(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.TestSetOriginCaller(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.TestSetOriginCaller(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.TestSetOriginCaller(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.TestSetOriginSend(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.TestSetOriginSend(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.TestSetOriginCaller(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.TestSetOriginCaller(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.TestSetOriginCaller(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.TestSetOriginCaller(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\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\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) 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\toutput := \"\"\n\toutput += ufmt.Sprintf(\"Author: %s\", p.Author().String())\n\toutput += \"\\n\\n\"\n\toutput += p.Description()\n\toutput += \"\\n\\n\"\n\toutput += ufmt.Sprintf(\"Status: %s\", p.Status().String())\n\toutput += \"\\n\\n\"\n\toutput += ufmt.Sprintf(\n\t\t\"Voting stats: YES %d (%d%%), NO %d (%d%%), ABSTAIN %d (%d%%), MISSING VOTE %d (%d%%)\",\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\toutput += \"\\n\\n\"\n\toutput += ufmt.Sprintf(\"Threshold met: %t\", stats.YayVotes \u003e (2*stats.TotalVotingPower)/3)\n\n\treturn output\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_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"stack","path":"gno.land/p/demo/stack","files":[{"name":"stack.gno","body":"package stack\n\ntype Stack struct {\n\ttop *node\n\tlength int\n}\n\ntype node struct {\n\tvalue interface{}\n\tprev *node\n}\n\nfunc New() *Stack {\n\treturn \u0026Stack{nil, 0}\n}\n\nfunc (s *Stack) Len() int {\n\treturn s.length\n}\n\nfunc (s *Stack) Top() interface{} {\n\tif s.length == 0 {\n\t\treturn nil\n\t}\n\treturn s.top.value\n}\n\nfunc (s *Stack) Pop() interface{} {\n\tif s.length == 0 {\n\t\treturn nil\n\t}\n\n\tnode := s.top\n\ts.top = node.prev\n\ts.length -= 1\n\treturn node.value\n}\n\nfunc (s *Stack) Push(value interface{}) {\n\tnode := \u0026node{value, s.top}\n\ts.top = node\n\ts.length += 1\n}\n"},{"name":"stack_test.gno","body":"package stack\n\nimport \"testing\"\n\nfunc TestStack(t *testing.T) {\n\ts := New() // Empty stack\n\n\tif s.Len() != 0 {\n\t\tt.Errorf(\"s.Len(): expected 0; got %d\", s.Len())\n\t}\n\n\ts.Push(1)\n\n\tif s.Len() != 1 {\n\t\tt.Errorf(\"s.Len(): expected 1; got %d\", s.Len())\n\t}\n\n\tif top := s.Top(); top.(int) != 1 {\n\t\tt.Errorf(\"s.Top(): expected 1; got %v\", top.(int))\n\t}\n\n\tif elem := s.Pop(); elem.(int) != 1 {\n\t\tt.Errorf(\"s.Pop(): expected 1; got %v\", elem.(int))\n\t}\n\tif s.Len() != 0 {\n\t\tt.Errorf(\"s.Len(): expected 0; got %d\", s.Len())\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":"subscription","path":"gno.land/p/demo/subscription","files":[{"name":"doc.gno","body":"// Package subscription provides a flexible system for managing both recurring and\n// lifetime subscriptions in Gno applications. It enables developers to handle\n// payment-based access control for services or products. The library supports\n// both subscriptions requiring periodic payments (recurring) and one-time payments\n// (lifetime). Subscriptions are tracked using an AVL tree for efficient management\n// of subscription statuses.\n//\n// Usage:\n//\n// Import the required sub-packages (`recurring` and/or `lifetime`) to manage specific\n// subscription types. The methods provided allow users to subscribe, check subscription\n// status, and manage payments.\n//\n// Recurring Subscription:\n//\n// Recurring subscriptions require periodic payments to maintain access.\n// Users pay to extend their access for a specific duration.\n//\n// Example:\n//\n//\t// Create a recurring subscription requiring 100 ugnot every 30 days\n//\trecSub := recurring.NewRecurringSubscription(time.Hour * 24 * 30, 100)\n//\n//\t// Process payment for the recurring subscription\n//\trecSub.Subscribe()\n//\n//\t// Gift a recurring subscription to another user\n//\trecSub.GiftSubscription(recipientAddress)\n//\n//\t// Check if a user has a valid subscription\n//\trecSub.HasValidSubscription(addr)\n//\n//\t// Get the expiration date of the subscription\n//\trecSub.GetExpiration(caller)\n//\n//\t// Update the subscription amount to 200 ugnot\n//\trecSub.UpdateAmount(200)\n//\n//\t// Get the current subscription amount\n//\trecSub.GetAmount()\n//\n// Lifetime Subscription:\n//\n// Lifetime subscriptions require a one-time payment for permanent access.\n// Once paid, users have indefinite access without further payments.\n//\n// Example:\n//\n//\t// Create a lifetime subscription costing 500 ugnot\n//\tlifeSub := lifetime.NewLifetimeSubscription(500)\n//\n//\t// Process payment for lifetime access\n//\tlifeSub.Subscribe()\n//\n//\t// Gift a lifetime subscription to another user\n//\tlifeSub.GiftSubscription(recipientAddress)\n//\n//\t// Check if a user has a valid subscription\n//\tlifeSub.HasValidSubscription(addr)\n//\n//\t// Update the lifetime subscription amount to 1000 ugnot\n//\tlifeSub.UpdateAmount(1000)\n//\n//\t// Get the current lifetime subscription amount\n//\tlifeSub.GetAmount()\npackage subscription\n"},{"name":"subscription.gno","body":"package subscription\n\nimport (\n\t\"std\"\n)\n\n// Subscription interface defines standard methods that all subscription types must implement.\ntype Subscription interface {\n\tHasValidSubscription(std.Address) error\n\tSubscribe() error\n\tUpdateAmount(newAmount int64) error\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":"lifetime","path":"gno.land/p/demo/subscription/lifetime","files":[{"name":"errors.gno","body":"package lifetime\n\nimport \"errors\"\n\nvar (\n\tErrNoSub = errors.New(\"lifetime subscription: no active subscription found\")\n\tErrAmt = errors.New(\"lifetime subscription: payment amount does not match the required subscription amount\")\n\tErrAlreadySub = errors.New(\"lifetime subscription: this address already has an active lifetime subscription\")\n\tErrNotAuthorized = errors.New(\"lifetime subscription: action not authorized\")\n)\n"},{"name":"lifetime.gno","body":"package lifetime\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n)\n\n// LifetimeSubscription represents a subscription that requires only a one-time payment.\n// It grants permanent access to a service or product.\ntype LifetimeSubscription struct {\n\townable.Ownable\n\tamount int64\n\tsubs *avl.Tree // std.Address -\u003e bool\n}\n\n// NewLifetimeSubscription creates and returns a new lifetime subscription.\nfunc NewLifetimeSubscription(amount int64) *LifetimeSubscription {\n\treturn \u0026LifetimeSubscription{\n\t\tOwnable: *ownable.New(),\n\t\tamount: amount,\n\t\tsubs: avl.NewTree(),\n\t}\n}\n\n// processSubscription handles the subscription process for a given receiver.\nfunc (ls *LifetimeSubscription) processSubscription(receiver std.Address) error {\n\tamount := std.OriginSend()\n\n\tif amount.AmountOf(\"ugnot\") != ls.amount {\n\t\treturn ErrAmt\n\t}\n\n\t_, exists := ls.subs.Get(receiver.String())\n\n\tif exists {\n\t\treturn ErrAlreadySub\n\t}\n\n\tls.subs.Set(receiver.String(), true)\n\n\treturn nil\n}\n\n// Subscribe processes the payment for a lifetime subscription.\nfunc (ls *LifetimeSubscription) Subscribe() error {\n\tcaller := std.PrevRealm().Addr()\n\treturn ls.processSubscription(caller)\n}\n\n// GiftSubscription allows the caller to pay for a lifetime subscription for another user.\nfunc (ls *LifetimeSubscription) GiftSubscription(receiver std.Address) error {\n\treturn ls.processSubscription(receiver)\n}\n\n// HasValidSubscription checks if the given address has an active lifetime subscription.\nfunc (ls *LifetimeSubscription) HasValidSubscription(addr std.Address) error {\n\t_, exists := ls.subs.Get(addr.String())\n\n\tif !exists {\n\t\treturn ErrNoSub\n\t}\n\n\treturn nil\n}\n\n// UpdateAmount allows the owner of the LifetimeSubscription contract to update the subscription price.\nfunc (ls *LifetimeSubscription) UpdateAmount(newAmount int64) error {\n\tif err := ls.CallerIsOwner(); err != nil {\n\t\treturn ErrNotAuthorized\n\t}\n\n\tls.amount = newAmount\n\treturn nil\n}\n\n// GetAmount returns the current subscription price.\nfunc (ls *LifetimeSubscription) GetAmount() int64 {\n\treturn ls.amount\n}\n"},{"name":"lifetime_test.gno","body":"package lifetime\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\talice = testutils.TestAddress(\"alice\")\n\tbob = testutils.TestAddress(\"bob\")\n\tcharlie = testutils.TestAddress(\"charlie\")\n)\n\nfunc TestLifetimeSubscription(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := ls.Subscribe()\n\tuassert.NoError(t, err, \"Expected ProcessPayment to succeed\")\n\n\terr = ls.HasValidSubscription(std.PrevRealm().Addr())\n\tuassert.NoError(t, err, \"Expected Alice to have access\")\n}\n\nfunc TestLifetimeSubscriptionGift(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := ls.GiftSubscription(bob)\n\tuassert.NoError(t, err, \"Expected ProcessPaymentGift to succeed for Bob\")\n\n\terr = ls.HasValidSubscription(bob)\n\tuassert.NoError(t, err, \"Expected Bob to have access\")\n\n\terr = ls.HasValidSubscription(charlie)\n\tuassert.Error(t, err, \"Expected Charlie to fail access check\")\n}\n\nfunc TestUpdateAmountAuthorization(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\terr := ls.UpdateAmount(2000)\n\tuassert.NoError(t, err, \"Expected Alice to succeed in updating amount\")\n\n\tstd.TestSetOriginCaller(bob)\n\n\terr = ls.UpdateAmount(3000)\n\tuassert.Error(t, err, \"Expected Bob to fail when updating amount\")\n}\n\nfunc TestIncorrectPaymentAmount(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 500}}, nil)\n\terr := ls.Subscribe()\n\tuassert.Error(t, err, \"Expected payment to fail with incorrect amount\")\n}\n\nfunc TestMultipleSubscriptionAttempts(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := ls.Subscribe()\n\tuassert.NoError(t, err, \"Expected first subscription to succeed\")\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr = ls.Subscribe()\n\tuassert.Error(t, err, \"Expected second subscription to fail as Alice is already subscribed\")\n}\n\nfunc TestGiftSubscriptionWithIncorrectAmount(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 500}}, nil)\n\terr := ls.GiftSubscription(bob)\n\tuassert.Error(t, err, \"Expected gift subscription to fail with incorrect amount\")\n\n\terr = ls.HasValidSubscription(bob)\n\tuassert.Error(t, err, \"Expected Bob to not have access after incorrect gift subscription\")\n}\n\nfunc TestUpdateAmountEffectiveness(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\terr := ls.UpdateAmount(2000)\n\tuassert.NoError(t, err, \"Expected Alice to succeed in updating amount\")\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr = ls.Subscribe()\n\tuassert.Error(t, err, \"Expected subscription to fail with old amount after update\")\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 2000}}, nil)\n\terr = ls.Subscribe()\n\tuassert.NoError(t, err, \"Expected subscription to succeed with new amount\")\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":"recurring","path":"gno.land/p/demo/subscription/recurring","files":[{"name":"errors.gno","body":"package recurring\n\nimport \"errors\"\n\nvar (\n\tErrNoSub = errors.New(\"recurring subscription: no active subscription found\")\n\tErrSubExpired = errors.New(\"recurring subscription: your subscription has expired\")\n\tErrAmt = errors.New(\"recurring subscription: payment amount does not match the required subscription amount\")\n\tErrAlreadySub = errors.New(\"recurring subscription: this address already has an active subscription\")\n\tErrNotAuthorized = errors.New(\"recurring subscription: action not authorized\")\n)\n"},{"name":"recurring.gno","body":"package recurring\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n)\n\n// RecurringSubscription represents a subscription that requires periodic payments.\n// It includes the duration of the subscription and the amount required per period.\ntype RecurringSubscription struct {\n\townable.Ownable\n\tduration time.Duration\n\tamount int64\n\tsubs *avl.Tree // std.Address -\u003e time.Time\n}\n\n// NewRecurringSubscription creates and returns a new recurring subscription.\nfunc NewRecurringSubscription(duration time.Duration, amount int64) *RecurringSubscription {\n\treturn \u0026RecurringSubscription{\n\t\tOwnable: *ownable.New(),\n\t\tduration: duration,\n\t\tamount: amount,\n\t\tsubs: avl.NewTree(),\n\t}\n}\n\n// HasValidSubscription verifies if the caller has an active recurring subscription.\nfunc (rs *RecurringSubscription) HasValidSubscription(addr std.Address) error {\n\texpTime, exists := rs.subs.Get(addr.String())\n\tif !exists {\n\t\treturn ErrNoSub\n\t}\n\n\tif time.Now().After(expTime.(time.Time)) {\n\t\treturn ErrSubExpired\n\t}\n\n\treturn nil\n}\n\n// processSubscription processes the payment for a given receiver and renews or adds their subscription.\nfunc (rs *RecurringSubscription) processSubscription(receiver std.Address) error {\n\tamount := std.OriginSend()\n\n\tif amount.AmountOf(\"ugnot\") != rs.amount {\n\t\treturn ErrAmt\n\t}\n\n\texpTime, exists := rs.subs.Get(receiver.String())\n\n\t// If the user is already a subscriber but his subscription has expired, authorize renewal\n\tif exists {\n\t\texpiration := expTime.(time.Time)\n\t\tif time.Now().Before(expiration) {\n\t\t\treturn ErrAlreadySub\n\t\t}\n\t}\n\n\t// Renew or add subscription\n\tnewExpiration := time.Now().Add(rs.duration)\n\trs.subs.Set(receiver.String(), newExpiration)\n\n\treturn nil\n}\n\n// Subscribe handles the payment for the caller's subscription.\nfunc (rs *RecurringSubscription) Subscribe() error {\n\tcaller := std.PrevRealm().Addr()\n\n\treturn rs.processSubscription(caller)\n}\n\n// GiftSubscription allows the user to pay for a subscription for another user (receiver).\nfunc (rs *RecurringSubscription) GiftSubscription(receiver std.Address) error {\n\treturn rs.processSubscription(receiver)\n}\n\n// GetExpiration returns the expiration date of the recurring subscription for a given caller.\nfunc (rs *RecurringSubscription) GetExpiration(addr std.Address) (time.Time, error) {\n\texpTime, exists := rs.subs.Get(addr.String())\n\tif !exists {\n\t\treturn time.Time{}, ErrNoSub\n\t}\n\n\treturn expTime.(time.Time), nil\n}\n\n// UpdateAmount allows the owner of the subscription contract to change the required subscription amount.\nfunc (rs *RecurringSubscription) UpdateAmount(newAmount int64) error {\n\tif err := rs.CallerIsOwner(); err != nil {\n\t\treturn ErrNotAuthorized\n\t}\n\n\trs.amount = newAmount\n\treturn nil\n}\n\n// GetAmount returns the current amount required for each subscription period.\nfunc (rs *RecurringSubscription) GetAmount() int64 {\n\treturn rs.amount\n}\n"},{"name":"recurring_test.gno","body":"package recurring\n\nimport (\n\t\"std\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\talice = testutils.TestAddress(\"alice\")\n\tbob = testutils.TestAddress(\"bob\")\n\tcharlie = testutils.TestAddress(\"charlie\")\n)\n\nfunc TestRecurringSubscription(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := rs.Subscribe()\n\tuassert.NoError(t, err, \"Expected ProcessPayment to succeed for Alice\")\n\n\terr = rs.HasValidSubscription(std.PrevRealm().Addr())\n\tuassert.NoError(t, err, \"Expected Alice to have access\")\n\n\texpiration, err := rs.GetExpiration(std.PrevRealm().Addr())\n\tuassert.NoError(t, err, \"Expected to get expiration for Alice\")\n}\n\nfunc TestRecurringSubscriptionGift(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := rs.GiftSubscription(bob)\n\tuassert.NoError(t, err, \"Expected ProcessPaymentGift to succeed for Bob\")\n\n\terr = rs.HasValidSubscription(bob)\n\tuassert.NoError(t, err, \"Expected Bob to have access\")\n\n\terr = rs.HasValidSubscription(charlie)\n\tuassert.Error(t, err, \"Expected Charlie to fail access check\")\n}\n\nfunc TestRecurringSubscriptionExpiration(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour, 1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := rs.Subscribe()\n\tuassert.NoError(t, err, \"Expected ProcessPayment to succeed for Alice\")\n\n\terr = rs.HasValidSubscription(std.PrevRealm().Addr())\n\tuassert.NoError(t, err, \"Expected Alice to have access\")\n\n\texpiration := time.Now().Add(-time.Hour * 2)\n\trs.subs.Set(std.PrevRealm().Addr().String(), expiration)\n\n\terr = rs.HasValidSubscription(std.PrevRealm().Addr())\n\tuassert.Error(t, err, \"Expected Alice's subscription to be expired\")\n}\n\nfunc TestUpdateAmountAuthorization(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\terr := rs.UpdateAmount(2000)\n\tuassert.NoError(t, err, \"Expected Alice to succeed in updating amount\")\n\n\tstd.TestSetOriginCaller(bob)\n\terr = rs.UpdateAmount(3000)\n\tuassert.Error(t, err, \"Expected Bob to fail when updating amount\")\n}\n\nfunc TestGetAmount(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\tamount := rs.GetAmount()\n\tuassert.Equal(t, amount, int64(1000), \"Expected the initial amount to be 1000 ugnot\")\n\n\terr := rs.UpdateAmount(2000)\n\tuassert.NoError(t, err, \"Expected Alice to succeed in updating amount\")\n\n\tamount = rs.GetAmount()\n\tuassert.Equal(t, amount, int64(2000), \"Expected the updated amount to be 2000 ugnot\")\n}\n\nfunc TestIncorrectPaymentAmount(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 500}}, nil)\n\terr := rs.Subscribe()\n\tuassert.Error(t, err, \"Expected payment with incorrect amount to fail\")\n}\n\nfunc TestMultiplePaymentsForSameUser(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := rs.Subscribe()\n\tuassert.NoError(t, err, \"Expected first ProcessPayment to succeed for Alice\")\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr = rs.Subscribe()\n\tuassert.Error(t, err, \"Expected second ProcessPayment to fail for Alice due to existing subscription\")\n}\n\nfunc TestRecurringSubscriptionWithMultiplePayments(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour, 1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := rs.Subscribe()\n\tuassert.NoError(t, err, \"Expected first ProcessPayment to succeed for Alice\")\n\n\terr = rs.HasValidSubscription(std.PrevRealm().Addr())\n\tuassert.NoError(t, err, \"Expected Alice to have access after first payment\")\n\n\texpiration := time.Now().Add(-time.Hour * 2)\n\trs.subs.Set(std.PrevRealm().Addr().String(), expiration)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr = rs.Subscribe()\n\tuassert.NoError(t, err, \"Expected second ProcessPayment to succeed for Alice\")\n\n\terr = rs.HasValidSubscription(std.PrevRealm().Addr())\n\tuassert.NoError(t, err, \"Expected Alice to have access after second payment\")\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":"lifetime","path":"gno.land/p/demo/subscription/lifetime","files":[{"name":"errors.gno","body":"package lifetime\n\nimport \"errors\"\n\nvar (\n\tErrNoSub = errors.New(\"lifetime subscription: no active subscription found\")\n\tErrAmt = errors.New(\"lifetime subscription: payment amount does not match the required subscription amount\")\n\tErrAlreadySub = errors.New(\"lifetime subscription: this address already has an active lifetime subscription\")\n\tErrNotAuthorized = errors.New(\"lifetime subscription: action not authorized\")\n)\n"},{"name":"lifetime.gno","body":"package lifetime\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n)\n\n// LifetimeSubscription represents a subscription that requires only a one-time payment.\n// It grants permanent access to a service or product.\ntype LifetimeSubscription struct {\n\townable.Ownable\n\tamount int64\n\tsubs *avl.Tree // std.Address -\u003e bool\n}\n\n// NewLifetimeSubscription creates and returns a new lifetime subscription.\nfunc NewLifetimeSubscription(amount int64) *LifetimeSubscription {\n\treturn \u0026LifetimeSubscription{\n\t\tOwnable: *ownable.New(),\n\t\tamount: amount,\n\t\tsubs: avl.NewTree(),\n\t}\n}\n\n// processSubscription handles the subscription process for a given receiver.\nfunc (ls *LifetimeSubscription) processSubscription(receiver std.Address) error {\n\tamount := std.OriginSend()\n\n\tif amount.AmountOf(\"ugnot\") != ls.amount {\n\t\treturn ErrAmt\n\t}\n\n\t_, exists := ls.subs.Get(receiver.String())\n\n\tif exists {\n\t\treturn ErrAlreadySub\n\t}\n\n\tls.subs.Set(receiver.String(), true)\n\n\treturn nil\n}\n\n// Subscribe processes the payment for a lifetime subscription.\nfunc (ls *LifetimeSubscription) Subscribe() error {\n\tcaller := std.PreviousRealm().Addr()\n\treturn ls.processSubscription(caller)\n}\n\n// GiftSubscription allows the caller to pay for a lifetime subscription for another user.\nfunc (ls *LifetimeSubscription) GiftSubscription(receiver std.Address) error {\n\treturn ls.processSubscription(receiver)\n}\n\n// HasValidSubscription checks if the given address has an active lifetime subscription.\nfunc (ls *LifetimeSubscription) HasValidSubscription(addr std.Address) error {\n\t_, exists := ls.subs.Get(addr.String())\n\n\tif !exists {\n\t\treturn ErrNoSub\n\t}\n\n\treturn nil\n}\n\n// UpdateAmount allows the owner of the LifetimeSubscription contract to update the subscription price.\nfunc (ls *LifetimeSubscription) UpdateAmount(newAmount int64) error {\n\tif err := ls.CallerIsOwner(); err != nil {\n\t\treturn ErrNotAuthorized\n\t}\n\n\tls.amount = newAmount\n\treturn nil\n}\n\n// GetAmount returns the current subscription price.\nfunc (ls *LifetimeSubscription) GetAmount() int64 {\n\treturn ls.amount\n}\n"},{"name":"lifetime_test.gno","body":"package lifetime\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\talice = testutils.TestAddress(\"alice\")\n\tbob = testutils.TestAddress(\"bob\")\n\tcharlie = testutils.TestAddress(\"charlie\")\n)\n\nfunc TestLifetimeSubscription(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := ls.Subscribe()\n\tuassert.NoError(t, err, \"Expected ProcessPayment to succeed\")\n\n\terr = ls.HasValidSubscription(std.PreviousRealm().Addr())\n\tuassert.NoError(t, err, \"Expected Alice to have access\")\n}\n\nfunc TestLifetimeSubscriptionGift(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := ls.GiftSubscription(bob)\n\tuassert.NoError(t, err, \"Expected ProcessPaymentGift to succeed for Bob\")\n\n\terr = ls.HasValidSubscription(bob)\n\tuassert.NoError(t, err, \"Expected Bob to have access\")\n\n\terr = ls.HasValidSubscription(charlie)\n\tuassert.Error(t, err, \"Expected Charlie to fail access check\")\n}\n\nfunc TestUpdateAmountAuthorization(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\terr := ls.UpdateAmount(2000)\n\tuassert.NoError(t, err, \"Expected Alice to succeed in updating amount\")\n\n\tstd.TestSetOriginCaller(bob)\n\n\terr = ls.UpdateAmount(3000)\n\tuassert.Error(t, err, \"Expected Bob to fail when updating amount\")\n}\n\nfunc TestIncorrectPaymentAmount(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 500}}, nil)\n\terr := ls.Subscribe()\n\tuassert.Error(t, err, \"Expected payment to fail with incorrect amount\")\n}\n\nfunc TestMultipleSubscriptionAttempts(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := ls.Subscribe()\n\tuassert.NoError(t, err, \"Expected first subscription to succeed\")\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr = ls.Subscribe()\n\tuassert.Error(t, err, \"Expected second subscription to fail as Alice is already subscribed\")\n}\n\nfunc TestGiftSubscriptionWithIncorrectAmount(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 500}}, nil)\n\terr := ls.GiftSubscription(bob)\n\tuassert.Error(t, err, \"Expected gift subscription to fail with incorrect amount\")\n\n\terr = ls.HasValidSubscription(bob)\n\tuassert.Error(t, err, \"Expected Bob to not have access after incorrect gift subscription\")\n}\n\nfunc TestUpdateAmountEffectiveness(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\terr := ls.UpdateAmount(2000)\n\tuassert.NoError(t, err, \"Expected Alice to succeed in updating amount\")\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr = ls.Subscribe()\n\tuassert.Error(t, err, \"Expected subscription to fail with old amount after update\")\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 2000}}, nil)\n\terr = ls.Subscribe()\n\tuassert.NoError(t, err, \"Expected subscription to succeed with new amount\")\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":"recurring","path":"gno.land/p/demo/subscription/recurring","files":[{"name":"errors.gno","body":"package recurring\n\nimport \"errors\"\n\nvar (\n\tErrNoSub = errors.New(\"recurring subscription: no active subscription found\")\n\tErrSubExpired = errors.New(\"recurring subscription: your subscription has expired\")\n\tErrAmt = errors.New(\"recurring subscription: payment amount does not match the required subscription amount\")\n\tErrAlreadySub = errors.New(\"recurring subscription: this address already has an active subscription\")\n\tErrNotAuthorized = errors.New(\"recurring subscription: action not authorized\")\n)\n"},{"name":"recurring.gno","body":"package recurring\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n)\n\n// RecurringSubscription represents a subscription that requires periodic payments.\n// It includes the duration of the subscription and the amount required per period.\ntype RecurringSubscription struct {\n\townable.Ownable\n\tduration time.Duration\n\tamount int64\n\tsubs *avl.Tree // std.Address -\u003e time.Time\n}\n\n// NewRecurringSubscription creates and returns a new recurring subscription.\nfunc NewRecurringSubscription(duration time.Duration, amount int64) *RecurringSubscription {\n\treturn \u0026RecurringSubscription{\n\t\tOwnable: *ownable.New(),\n\t\tduration: duration,\n\t\tamount: amount,\n\t\tsubs: avl.NewTree(),\n\t}\n}\n\n// HasValidSubscription verifies if the caller has an active recurring subscription.\nfunc (rs *RecurringSubscription) HasValidSubscription(addr std.Address) error {\n\texpTime, exists := rs.subs.Get(addr.String())\n\tif !exists {\n\t\treturn ErrNoSub\n\t}\n\n\tif time.Now().After(expTime.(time.Time)) {\n\t\treturn ErrSubExpired\n\t}\n\n\treturn nil\n}\n\n// processSubscription processes the payment for a given receiver and renews or adds their subscription.\nfunc (rs *RecurringSubscription) processSubscription(receiver std.Address) error {\n\tamount := std.OriginSend()\n\n\tif amount.AmountOf(\"ugnot\") != rs.amount {\n\t\treturn ErrAmt\n\t}\n\n\texpTime, exists := rs.subs.Get(receiver.String())\n\n\t// If the user is already a subscriber but his subscription has expired, authorize renewal\n\tif exists {\n\t\texpiration := expTime.(time.Time)\n\t\tif time.Now().Before(expiration) {\n\t\t\treturn ErrAlreadySub\n\t\t}\n\t}\n\n\t// Renew or add subscription\n\tnewExpiration := time.Now().Add(rs.duration)\n\trs.subs.Set(receiver.String(), newExpiration)\n\n\treturn nil\n}\n\n// Subscribe handles the payment for the caller's subscription.\nfunc (rs *RecurringSubscription) Subscribe() error {\n\tcaller := std.PreviousRealm().Addr()\n\n\treturn rs.processSubscription(caller)\n}\n\n// GiftSubscription allows the user to pay for a subscription for another user (receiver).\nfunc (rs *RecurringSubscription) GiftSubscription(receiver std.Address) error {\n\treturn rs.processSubscription(receiver)\n}\n\n// GetExpiration returns the expiration date of the recurring subscription for a given caller.\nfunc (rs *RecurringSubscription) GetExpiration(addr std.Address) (time.Time, error) {\n\texpTime, exists := rs.subs.Get(addr.String())\n\tif !exists {\n\t\treturn time.Time{}, ErrNoSub\n\t}\n\n\treturn expTime.(time.Time), nil\n}\n\n// UpdateAmount allows the owner of the subscription contract to change the required subscription amount.\nfunc (rs *RecurringSubscription) UpdateAmount(newAmount int64) error {\n\tif err := rs.CallerIsOwner(); err != nil {\n\t\treturn ErrNotAuthorized\n\t}\n\n\trs.amount = newAmount\n\treturn nil\n}\n\n// GetAmount returns the current amount required for each subscription period.\nfunc (rs *RecurringSubscription) GetAmount() int64 {\n\treturn rs.amount\n}\n"},{"name":"recurring_test.gno","body":"package recurring\n\nimport (\n\t\"std\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\talice = testutils.TestAddress(\"alice\")\n\tbob = testutils.TestAddress(\"bob\")\n\tcharlie = testutils.TestAddress(\"charlie\")\n)\n\nfunc TestRecurringSubscription(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := rs.Subscribe()\n\tuassert.NoError(t, err, \"Expected ProcessPayment to succeed for Alice\")\n\n\terr = rs.HasValidSubscription(std.PreviousRealm().Addr())\n\tuassert.NoError(t, err, \"Expected Alice to have access\")\n\n\texpiration, err := rs.GetExpiration(std.PreviousRealm().Addr())\n\tuassert.NoError(t, err, \"Expected to get expiration for Alice\")\n}\n\nfunc TestRecurringSubscriptionGift(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := rs.GiftSubscription(bob)\n\tuassert.NoError(t, err, \"Expected ProcessPaymentGift to succeed for Bob\")\n\n\terr = rs.HasValidSubscription(bob)\n\tuassert.NoError(t, err, \"Expected Bob to have access\")\n\n\terr = rs.HasValidSubscription(charlie)\n\tuassert.Error(t, err, \"Expected Charlie to fail access check\")\n}\n\nfunc TestRecurringSubscriptionExpiration(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour, 1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := rs.Subscribe()\n\tuassert.NoError(t, err, \"Expected ProcessPayment to succeed for Alice\")\n\n\terr = rs.HasValidSubscription(std.PreviousRealm().Addr())\n\tuassert.NoError(t, err, \"Expected Alice to have access\")\n\n\texpiration := time.Now().Add(-time.Hour * 2)\n\trs.subs.Set(std.PreviousRealm().Addr().String(), expiration)\n\n\terr = rs.HasValidSubscription(std.PreviousRealm().Addr())\n\tuassert.Error(t, err, \"Expected Alice's subscription to be expired\")\n}\n\nfunc TestUpdateAmountAuthorization(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\terr := rs.UpdateAmount(2000)\n\tuassert.NoError(t, err, \"Expected Alice to succeed in updating amount\")\n\n\tstd.TestSetOriginCaller(bob)\n\terr = rs.UpdateAmount(3000)\n\tuassert.Error(t, err, \"Expected Bob to fail when updating amount\")\n}\n\nfunc TestGetAmount(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\tamount := rs.GetAmount()\n\tuassert.Equal(t, amount, int64(1000), \"Expected the initial amount to be 1000 ugnot\")\n\n\terr := rs.UpdateAmount(2000)\n\tuassert.NoError(t, err, \"Expected Alice to succeed in updating amount\")\n\n\tamount = rs.GetAmount()\n\tuassert.Equal(t, amount, int64(2000), \"Expected the updated amount to be 2000 ugnot\")\n}\n\nfunc TestIncorrectPaymentAmount(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 500}}, nil)\n\terr := rs.Subscribe()\n\tuassert.Error(t, err, \"Expected payment with incorrect amount to fail\")\n}\n\nfunc TestMultiplePaymentsForSameUser(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := rs.Subscribe()\n\tuassert.NoError(t, err, \"Expected first ProcessPayment to succeed for Alice\")\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr = rs.Subscribe()\n\tuassert.Error(t, err, \"Expected second ProcessPayment to fail for Alice due to existing subscription\")\n}\n\nfunc TestRecurringSubscriptionWithMultiplePayments(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour, 1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := rs.Subscribe()\n\tuassert.NoError(t, err, \"Expected first ProcessPayment to succeed for Alice\")\n\n\terr = rs.HasValidSubscription(std.PreviousRealm().Addr())\n\tuassert.NoError(t, err, \"Expected Alice to have access after first payment\")\n\n\texpiration := time.Now().Add(-time.Hour * 2)\n\trs.subs.Set(std.PreviousRealm().Addr().String(), expiration)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr = rs.Subscribe()\n\tuassert.NoError(t, err, \"Expected second ProcessPayment to succeed for Alice\")\n\n\terr = rs.HasValidSubscription(std.PreviousRealm().Addr())\n\tuassert.NoError(t, err, \"Expected Alice to have access after second payment\")\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":"svg","path":"gno.land/p/demo/svg","files":[{"name":"doc.gno","body":"/*\nPackage svg is a minimalist SVG generation library for Gno.\n\nThe svg package provides a simple and lightweight solution for programmatically generating SVG (Scalable Vector Graphics) markup in Gno. It allows you to create basic shapes like rectangles and circles, and output the generated SVG to a\n\nExample:\n\n\timport \"gno.land/p/demo/svg\"\"\n\n\tfunc Foo() string {\n\t canvas := svg.Canvas{Width: 200, Height: 200}\n\t canvas.DrawRectangle(50, 50, 100, 100, \"red\")\n\t canvas.DrawCircle(100, 100, 50, \"blue\")\n\t return canvas.String()\n\t}\n*/\npackage svg // import \"gno.land/p/demo/svg\"\n"},{"name":"svg.gno","body":"package svg\n\nimport \"gno.land/p/demo/ufmt\"\n\ntype Canvas struct {\n\tWidth int\n\tHeight int\n\tElems []Elem\n}\n\ntype Elem interface{ String() string }\n\nfunc (c Canvas) String() string {\n\toutput := \"\"\n\toutput += ufmt.Sprintf(`\u003csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"%d\" height=\"%d\"\u003e`, c.Width, c.Height)\n\tfor _, elem := range c.Elems {\n\t\toutput += elem.String()\n\t}\n\toutput += \"\u003c/svg\u003e\"\n\treturn output\n}\n\nfunc (c *Canvas) Append(elem Elem) {\n\tc.Elems = append(c.Elems, elem)\n}\n\ntype Circle struct {\n\tCX int // center X\n\tCY int // center Y\n\tR int // radius\n\tFill string\n}\n\nfunc (c Circle) String() string {\n\treturn ufmt.Sprintf(`\u003ccircle cx=\"%d\" cy=\"%d\" r=\"%d\" fill=\"%s\" /\u003e`, c.CX, c.CY, c.R, c.Fill)\n}\n\nfunc (c *Canvas) DrawCircle(cx, cy, r int, fill string) {\n\tc.Append(Circle{\n\t\tCX: cx,\n\t\tCY: cy,\n\t\tR: r,\n\t\tFill: fill,\n\t})\n}\n\ntype Rectangle struct {\n\tX, Y, Width, Height int\n\tFill string\n}\n\nfunc (c Rectangle) String() string {\n\treturn ufmt.Sprintf(`\u003crect x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\" fill=\"%s\" /\u003e`, c.X, c.Y, c.Width, c.Height, c.Fill)\n}\n\nfunc (c *Canvas) DrawRectangle(x, y, width, height int, fill string) {\n\tc.Append(Rectangle{\n\t\tX: x,\n\t\tY: y,\n\t\tWidth: width,\n\t\tHeight: height,\n\t\tFill: fill,\n\t})\n}\n\ntype Text struct {\n\tX, Y int\n\tText, Fill string\n}\n\nfunc (c Text) String() string {\n\treturn ufmt.Sprintf(`\u003ctext x=\"%d\" y=\"%d\" fill=\"%s\"\u003e%s\u003c/text\u003e`, c.X, c.Y, c.Fill, c.Text)\n}\n\nfunc (c *Canvas) DrawText(x, y int, text, fill string) {\n\tc.Append(Text{\n\t\tX: x,\n\t\tY: y,\n\t\tText: text,\n\t\tFill: fill,\n\t})\n}\n"},{"name":"z0_filetest.gno","body":"// PKGPATH: gno.land/p/demo/svg_test\npackage svg_test\n\nimport \"gno.land/p/demo/svg\"\n\nfunc main() {\n\tcanvas := svg.Canvas{Width: 500, Height: 500}\n\tcanvas.DrawRectangle(50, 50, 100, 100, \"red\")\n\tcanvas.DrawCircle(100, 100, 50, \"blue\")\n\tcanvas.DrawText(100, 100, \"hello world!\", \"magenta\")\n\tprintln(canvas)\n}\n\n// Output:\n// \u003csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"500\" height=\"500\"\u003e\u003crect x=\"50\" y=\"50\" width=\"100\" height=\"100\" fill=\"red\" /\u003e\u003ccircle cx=\"100\" cy=\"100\" r=\"50\" fill=\"blue\" /\u003e\u003ctext x=\"100\" y=\"100\" fill=\"magenta\"\u003ehello world!\u003c/text\u003e\u003c/svg\u003e\n"},{"name":"z1_filetest.gno","body":"// PKGPATH: gno.land/p/demo/svg_test\npackage svg_test\n\nimport \"gno.land/p/demo/svg\"\n\nfunc main() {\n\tcanvas := svg.Canvas{\n\t\tWidth: 500, Height: 500,\n\t\tElems: []svg.Elem{\n\t\t\tsvg.Rectangle{50, 50, 100, 100, \"red\"},\n\t\t\tsvg.Circle{50, 50, 100, \"red\"},\n\t\t\tsvg.Text{100, 100, \"hello world!\", \"magenta\"},\n\t\t},\n\t}\n\tprintln(canvas)\n}\n\n// Output:\n// \u003csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"500\" height=\"500\"\u003e\u003crect x=\"50\" y=\"50\" width=\"100\" height=\"100\" fill=\"red\" /\u003e\u003ccircle cx=\"50\" cy=\"50\" r=\"100\" fill=\"red\" /\u003e\u003ctext x=\"100\" y=\"100\" fill=\"magenta\"\u003ehello world!\u003c/text\u003e\u003c/svg\u003e\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":"tamagotchi","path":"gno.land/p/demo/tamagotchi","files":[{"name":"tamagotchi.gno","body":"package tamagotchi\n\nimport (\n\t\"time\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Tamagotchi structure\ntype Tamagotchi struct {\n\tname string\n\thunger int\n\thappiness int\n\thealth int\n\tage int\n\tmaxAge int\n\tsleepy int\n\tcreated time.Time\n\tlastUpdated time.Time\n}\n\nfunc New(name string) *Tamagotchi {\n\tnow := time.Now()\n\treturn \u0026Tamagotchi{\n\t\tname: name,\n\t\thunger: 50,\n\t\thappiness: 50,\n\t\thealth: 50,\n\t\tmaxAge: 100,\n\t\tlastUpdated: now,\n\t\tcreated: now,\n\t}\n}\n\nfunc (t *Tamagotchi) Name() string {\n\tt.update()\n\treturn t.name\n}\n\nfunc (t *Tamagotchi) Hunger() int {\n\tt.update()\n\treturn t.hunger\n}\n\nfunc (t *Tamagotchi) Happiness() int {\n\tt.update()\n\treturn t.happiness\n}\n\nfunc (t *Tamagotchi) Health() int {\n\tt.update()\n\treturn t.health\n}\n\nfunc (t *Tamagotchi) Age() int {\n\tt.update()\n\treturn t.age\n}\n\nfunc (t *Tamagotchi) Sleepy() int {\n\tt.update()\n\treturn t.sleepy\n}\n\n// Feed method for Tamagotchi\nfunc (t *Tamagotchi) Feed() {\n\tt.update()\n\tif t.dead() {\n\t\treturn\n\t}\n\tt.hunger = bound(t.hunger-10, 0, 100)\n}\n\n// Play method for Tamagotchi\nfunc (t *Tamagotchi) Play() {\n\tt.update()\n\tif t.dead() {\n\t\treturn\n\t}\n\tt.happiness = bound(t.happiness+10, 0, 100)\n}\n\n// Heal method for Tamagotchi\nfunc (t *Tamagotchi) Heal() {\n\tt.update()\n\n\tif t.dead() {\n\t\treturn\n\t}\n\tt.health = bound(t.health+10, 0, 100)\n}\n\nfunc (t Tamagotchi) dead() bool { return t.health == 0 }\n\n// Update applies changes based on the duration since the last update\nfunc (t *Tamagotchi) update() {\n\tif t.dead() {\n\t\treturn\n\t}\n\n\tnow := time.Now()\n\tif t.lastUpdated == now {\n\t\treturn\n\t}\n\n\tduration := now.Sub(t.lastUpdated)\n\telapsedMins := int(duration.Minutes())\n\n\tt.hunger = bound(t.hunger+elapsedMins, 0, 100)\n\tt.happiness = bound(t.happiness-elapsedMins, 0, 100)\n\tt.health = bound(t.health-elapsedMins, 0, 100)\n\tt.sleepy = bound(t.sleepy+elapsedMins, 0, 100)\n\n\t// age is hours since created\n\tt.age = int(now.Sub(t.created).Hours())\n\tif t.age \u003e t.maxAge {\n\t\tt.age = t.maxAge\n\t\tt.health = 0\n\t}\n\tif t.health == 0 {\n\t\tt.sleepy = 0\n\t\tt.happiness = 0\n\t\tt.hunger = 0\n\t}\n\n\tt.lastUpdated = now\n}\n\n// Face returns an ASCII art representation of the Tamagotchi's current state\nfunc (t *Tamagotchi) Face() string {\n\tt.update()\n\treturn t.face()\n}\n\nfunc (t *Tamagotchi) face() string {\n\tswitch {\n\tcase t.health == 0:\n\t\treturn \"😵\" // dead face\n\tcase t.health \u003c 30:\n\t\treturn \"😷\" // sick face\n\tcase t.happiness \u003c 30:\n\t\treturn \"😢\" // sad face\n\tcase t.hunger \u003e 70:\n\t\treturn \"😫\" // hungry face\n\tcase t.sleepy \u003e 70:\n\t\treturn \"😴\" // sleepy face\n\tdefault:\n\t\treturn \"😃\" // happy face\n\t}\n}\n\n// Markdown method for Tamagotchi\nfunc (t *Tamagotchi) Markdown() string {\n\tt.update()\n\treturn ufmt.Sprintf(`# %s %s\n\n* age: %d\n* hunger: %d\n* happiness: %d\n* health: %d\n* sleepy: %d`,\n\t\tt.name, t.Face(),\n\t\tt.age, t.hunger, t.happiness, t.health, t.sleepy,\n\t)\n}\n\nfunc bound(n, min, max int) int {\n\tif n \u003c min {\n\t\treturn min\n\t}\n\tif n \u003e max {\n\t\treturn max\n\t}\n\treturn n\n}\n"},{"name":"z0_filetest.gno","body":"package main\n\nimport (\n\t\"time\"\n\n\t\"internal/os_test\"\n\n\t\"gno.land/p/demo/tamagotchi\"\n)\n\nfunc main() {\n\tt := tamagotchi.New(\"Gnome\")\n\n\tprintln(\"\\n-- INITIAL\\n\")\n\tprintln(t.Markdown())\n\n\tprintln(\"\\n-- WAIT 20 minutes\\n\")\n\tos_test.Sleep(20 * time.Minute)\n\tprintln(t.Markdown())\n\n\tprintln(\"\\n-- FEEDx3, PLAYx2, HEALx4\\n\")\n\tt.Feed()\n\tt.Feed()\n\tt.Feed()\n\tt.Play()\n\tt.Play()\n\tt.Heal()\n\tt.Heal()\n\tt.Heal()\n\tt.Heal()\n\tprintln(t.Markdown())\n\n\tprintln(\"\\n-- WAIT 20 minutes\\n\")\n\tos_test.Sleep(20 * time.Minute)\n\tprintln(t.Markdown())\n\n\tprintln(\"\\n-- WAIT 20 hours\\n\")\n\tos_test.Sleep(20 * time.Hour)\n\tprintln(t.Markdown())\n\n\tprintln(\"\\n-- WAIT 20 hours\\n\")\n\tos_test.Sleep(20 * time.Hour)\n\tprintln(t.Markdown())\n}\n\n// Output:\n// -- INITIAL\n//\n// # Gnome 😃\n//\n// * age: 0\n// * hunger: 50\n// * happiness: 50\n// * health: 50\n// * sleepy: 0\n//\n// -- WAIT 20 minutes\n//\n// # Gnome 😃\n//\n// * age: 0\n// * hunger: 70\n// * happiness: 30\n// * health: 30\n// * sleepy: 20\n//\n// -- FEEDx3, PLAYx2, HEALx4\n//\n// # Gnome 😃\n//\n// * age: 0\n// * hunger: 40\n// * happiness: 50\n// * health: 70\n// * sleepy: 20\n//\n// -- WAIT 20 minutes\n//\n// # Gnome 😃\n//\n// * age: 0\n// * hunger: 60\n// * happiness: 30\n// * health: 50\n// * sleepy: 40\n//\n// -- WAIT 20 hours\n//\n// # Gnome 😵\n//\n// * age: 20\n// * hunger: 0\n// * happiness: 0\n// * health: 0\n// * sleepy: 0\n//\n// -- WAIT 20 hours\n//\n// # Gnome 😵\n//\n// * age: 20\n// * hunger: 0\n// * happiness: 0\n// * health: 0\n// * sleepy: 0\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":"subtests","path":"gno.land/p/demo/tests/subtests","files":[{"name":"subtests.gno","body":"package subtests\n\nimport (\n\t\"std\"\n)\n\nfunc GetCurrentRealm() std.Realm {\n\treturn std.CurrentRealm()\n}\n\nfunc GetPrevRealm() std.Realm {\n\treturn std.PrevRealm()\n}\n\nfunc Exec(fn func()) {\n\tfn()\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":"subtests","path":"gno.land/r/demo/tests/subtests","files":[{"name":"subtests.gno","body":"package subtests\n\nimport (\n\t\"std\"\n)\n\nfunc GetCurrentRealm() std.Realm {\n\treturn std.CurrentRealm()\n}\n\nfunc GetPrevRealm() std.Realm {\n\treturn std.PrevRealm()\n}\n\nfunc Exec(fn func()) {\n\tfn()\n}\n\nfunc CallAssertOriginCall() {\n\tstd.AssertOriginCall()\n}\n\nfunc CallIsOriginCall() bool {\n\treturn std.IsOriginCall()\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 initOriginCaller = std.OriginCaller()\n\nfunc InitOriginCaller() std.Address {\n\treturn initOriginCaller\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\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.TestSetOriginCaller(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.TestSetOriginCaller(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":"tests","path":"gno.land/p/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":"tests.gno","body":"package tests\n\nimport (\n\t\"std\"\n\n\tpsubtests \"gno.land/p/demo/tests/subtests\"\n\t\"gno.land/r/demo/tests\"\n\trtests \"gno.land/r/demo/tests\"\n)\n\nconst World = \"world\"\n\n// IncCounter demonstrates that it's possible to call a realm function from\n// a package. So a package can potentially write into the store, by calling\n// an other realm.\nfunc IncCounter() {\n\ttests.IncCounter()\n}\n\nfunc CurrentRealmPath() string {\n\treturn std.CurrentRealm().PkgPath()\n}\n\n//----------------------------------------\n// cross realm test vars\n\ntype TestRealmObject2 struct {\n\tField string\n}\n\nfunc (o2 *TestRealmObject2) Modify() {\n\to2.Field = \"modified\"\n}\n\nvar (\n\tsomevalue1 TestRealmObject2\n\tSomeValue2 TestRealmObject2\n\tSomeValue3 *TestRealmObject2\n)\n\nfunc init() {\n\tsomevalue1 = TestRealmObject2{Field: \"init\"}\n\tSomeValue2 = TestRealmObject2{Field: \"init\"}\n\tSomeValue3 = \u0026TestRealmObject2{Field: \"init\"}\n}\n\nfunc ModifyTestRealmObject2a() {\n\tsomevalue1.Field = \"modified\"\n}\n\nfunc ModifyTestRealmObject2b() {\n\tSomeValue2.Field = \"modified\"\n}\n\nfunc ModifyTestRealmObject2c() {\n\tSomeValue3.Field = \"modified\"\n}\n\nfunc GetPrevRealm() std.Realm {\n\treturn std.PrevRealm()\n}\n\nfunc GetPSubtestsPrevRealm() std.Realm {\n\treturn psubtests.GetPrevRealm()\n}\n\nfunc GetRTestsGetPrevRealm() std.Realm {\n\treturn rtests.GetPrevRealm()\n}\n\n// Warning: unsafe pattern.\nfunc Exec(fn func()) {\n\tfn()\n}\n"},{"name":"tests_test.gno","body":"package tests_test\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/tests\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar World = \"WORLD\"\n\nfunc TestGetHelloWorld(t *testing.T) {\n\t// tests.World is 'world'\n\ts := \"hello \" + tests.World + World\n\tconst want = \"hello worldWORLD\"\n\n\tuassert.Equal(t, want, s)\n}\n"},{"name":"z0_filetest.gno","body":"package main\n\nimport (\n\tptests \"gno.land/p/demo/tests\"\n\trtests \"gno.land/r/demo/tests\"\n)\n\nfunc main() {\n\tprintln(rtests.Counter())\n\tptests.IncCounter()\n\tprintln(rtests.Counter())\n}\n\n// Output:\n// 0\n// 1\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":"subtests","path":"gno.land/p/demo/tests/subtests","files":[{"name":"subtests.gno","body":"package subtests\n\nimport (\n\t\"std\"\n)\n\nfunc GetCurrentRealm() std.Realm {\n\treturn std.CurrentRealm()\n}\n\nfunc GetPreviousRealm() std.Realm {\n\treturn std.PreviousRealm()\n}\n\nfunc Exec(fn func()) {\n\tfn()\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":"subtests","path":"gno.land/r/demo/tests/subtests","files":[{"name":"subtests.gno","body":"package subtests\n\nimport (\n\t\"std\"\n)\n\nfunc GetCurrentRealm() std.Realm {\n\treturn std.CurrentRealm()\n}\n\nfunc GetPreviousRealm() std.Realm {\n\treturn std.PreviousRealm()\n}\n\nfunc Exec(fn func()) {\n\tfn()\n}\n\nfunc CallAssertOriginCall() {\n\tstd.AssertOriginCall()\n}\n\nfunc CallIsOriginCall() bool {\n\treturn std.IsOriginCall()\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 initOriginCaller = std.OriginCaller()\n\nfunc InitOriginCaller() std.Address {\n\treturn initOriginCaller\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\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 GetPreviousRealm() std.Realm {\n\treturn std.PreviousRealm()\n}\n\nfunc GetRSubtestsPreviousRealm() std.Realm {\n\treturn rsubtests.GetPreviousRealm()\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 TestPreviousRealm(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, PreviousRealm returns the user\n\tif addr := GetPreviousRealm().Addr(); addr != user1Addr {\n\t\tt.Errorf(\"want GetPreviousRealm().Addr==%s, got %s\", user1Addr, addr)\n\t}\n\t// When 2 or more realms in the frames, PreviousRealm returns the second to last\n\tif addr := GetRSubtestsPreviousRealm().Addr(); addr != rTestsAddr {\n\t\tt.Errorf(\"want GetRSubtestsPreviousRealm().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, PreviousRealm returns the user\n// When 2 or more realms in the frames, PreviousRealm 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.TestSetOriginCaller(eoa)\n\tprintln(\"tests.GetPreviousRealm().Addr(): \", tests.GetPreviousRealm().Addr())\n\tprintln(\"tests.GetRSubtestsPreviousRealm().Addr(): \", tests.GetRSubtestsPreviousRealm().Addr())\n}\n\n// Output:\n// tests.GetPreviousRealm().Addr(): g1wdhk6et0dej47h6lta047h6lta047h6lrnerlk\n// tests.GetRSubtestsPreviousRealm().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.TestSetOriginCaller(eoa)\n\t// Contrarily to z2_filetest.gno we EXPECT GetPreviousRealms != eoa (#1704)\n\tif addr := tests.GetPreviousRealm().Addr(); addr != eoa {\n\t\tprintln(\"want tests.GetPreviousRealm().Addr ==\", eoa, \"got\", addr)\n\t}\n\t// When 2 or more realms in the frames, it is also different\n\tif addr := tests.GetRSubtestsPreviousRealm().Addr(); addr != rTestsAddr {\n\t\tprintln(\"want GetRSubtestsPreviousRealm().Addr ==\", rTestsAddr, \"got\", addr)\n\t}\n}\n\n// Output:\n// want tests.GetPreviousRealm().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":"tests","path":"gno.land/p/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":"tests.gno","body":"package tests\n\nimport (\n\t\"std\"\n\n\tpsubtests \"gno.land/p/demo/tests/subtests\"\n\t\"gno.land/r/demo/tests\"\n\trtests \"gno.land/r/demo/tests\"\n)\n\nconst World = \"world\"\n\n// IncCounter demonstrates that it's possible to call a realm function from\n// a package. So a package can potentially write into the store, by calling\n// an other realm.\nfunc IncCounter() {\n\ttests.IncCounter()\n}\n\nfunc CurrentRealmPath() string {\n\treturn std.CurrentRealm().PkgPath()\n}\n\n//----------------------------------------\n// cross realm test vars\n\ntype TestRealmObject2 struct {\n\tField string\n}\n\nfunc (o2 *TestRealmObject2) Modify() {\n\to2.Field = \"modified\"\n}\n\nvar (\n\tsomevalue1 TestRealmObject2\n\tSomeValue2 TestRealmObject2\n\tSomeValue3 *TestRealmObject2\n)\n\nfunc init() {\n\tsomevalue1 = TestRealmObject2{Field: \"init\"}\n\tSomeValue2 = TestRealmObject2{Field: \"init\"}\n\tSomeValue3 = \u0026TestRealmObject2{Field: \"init\"}\n}\n\nfunc ModifyTestRealmObject2a() {\n\tsomevalue1.Field = \"modified\"\n}\n\nfunc ModifyTestRealmObject2b() {\n\tSomeValue2.Field = \"modified\"\n}\n\nfunc ModifyTestRealmObject2c() {\n\tSomeValue3.Field = \"modified\"\n}\n\nfunc GetPreviousRealm() std.Realm {\n\treturn std.PreviousRealm()\n}\n\nfunc GetPSubtestsPreviousRealm() std.Realm {\n\treturn psubtests.GetPreviousRealm()\n}\n\nfunc GetRTestsGetPreviousRealm() std.Realm {\n\treturn rtests.GetPreviousRealm()\n}\n\n// Warning: unsafe pattern.\nfunc Exec(fn func()) {\n\tfn()\n}\n"},{"name":"tests_test.gno","body":"package tests_test\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/tests\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar World = \"WORLD\"\n\nfunc TestGetHelloWorld(t *testing.T) {\n\t// tests.World is 'world'\n\ts := \"hello \" + tests.World + World\n\tconst want = \"hello worldWORLD\"\n\n\tuassert.Equal(t, want, s)\n}\n"},{"name":"z0_filetest.gno","body":"package main\n\nimport (\n\tptests \"gno.land/p/demo/tests\"\n\trtests \"gno.land/r/demo/tests\"\n)\n\nfunc main() {\n\tprintln(rtests.Counter())\n\tptests.IncCounter()\n\tprintln(rtests.Counter())\n}\n\n// Output:\n// 0\n// 1\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":"p_crossrealm","path":"gno.land/p/demo/tests/p_crossrealm","files":[{"name":"p_crossrealm.gno","body":"package p_crossrealm\n\ntype Stringer interface {\n\tString() string\n}\n\ntype Container struct {\n\tA int\n\tB Stringer\n}\n\nfunc (c *Container) Touch() *Container {\n\tc.A += 1\n\treturn c\n}\n\nfunc (c *Container) Print() {\n\tprintln(\"A:\", c.A)\n\tif c.B == nil {\n\t\tprintln(\"B: undefined\")\n\t} else {\n\t\tprintln(\"B:\", c.B.String())\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":"todolist","path":"gno.land/p/demo/todolist","files":[{"name":"todolist.gno","body":"package todolist\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\ntype TodoList struct {\n\tTitle string\n\tTasks *avl.Tree\n\tOwner std.Address\n}\n\ntype Task struct {\n\tTitle string\n\tDone bool\n}\n\nfunc NewTodoList(title string) *TodoList {\n\treturn \u0026TodoList{\n\t\tTitle: title,\n\t\tTasks: avl.NewTree(),\n\t\tOwner: std.OriginCaller(),\n\t}\n}\n\nfunc NewTask(title string) *Task {\n\treturn \u0026Task{\n\t\tTitle: title,\n\t\tDone: false,\n\t}\n}\n\nfunc (tl *TodoList) AddTask(id int, task *Task) {\n\ttl.Tasks.Set(strconv.Itoa(id), task)\n}\n\nfunc ToggleTaskStatus(task *Task) {\n\ttask.Done = !task.Done\n}\n\nfunc (tl *TodoList) RemoveTask(taskId string) {\n\ttl.Tasks.Remove(taskId)\n}\n\nfunc (tl *TodoList) GetTasks() []*Task {\n\ttasks := make([]*Task, 0, tl.Tasks.Size())\n\ttl.Tasks.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\ttasks = append(tasks, value.(*Task))\n\t\treturn false\n\t})\n\treturn tasks\n}\n\nfunc (tl *TodoList) GetTodolistOwner() std.Address {\n\treturn tl.Owner\n}\n\nfunc (tl *TodoList) GetTodolistTitle() string {\n\treturn tl.Title\n}\n"},{"name":"todolist_test.gno","body":"package todolist\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestNewTodoList(t *testing.T) {\n\ttitle := \"My Todo List\"\n\ttodoList := NewTodoList(title)\n\n\tuassert.Equal(t, title, todoList.GetTodolistTitle())\n\tuassert.Equal(t, 0, len(todoList.GetTasks()))\n\tuassert.Equal(t, std.OriginCaller().String(), todoList.GetTodolistOwner().String())\n}\n\nfunc TestNewTask(t *testing.T) {\n\ttitle := \"My Task\"\n\ttask := NewTask(title)\n\n\tuassert.Equal(t, title, task.Title)\n\tuassert.False(t, task.Done, \"Expected task to be not done, but it is done\")\n}\n\nfunc TestAddTask(t *testing.T) {\n\ttodoList := NewTodoList(\"My Todo List\")\n\ttask := NewTask(\"My Task\")\n\n\ttodoList.AddTask(1, task)\n\n\ttasks := todoList.GetTasks()\n\n\tuassert.Equal(t, 1, len(tasks))\n\tuassert.True(t, tasks[0] == task, \"Task does not match\")\n}\n\nfunc TestToggleTaskStatus(t *testing.T) {\n\ttask := NewTask(\"My Task\")\n\n\tToggleTaskStatus(task)\n\tuassert.True(t, task.Done, \"Expected task to be done, but it is not done\")\n\n\tToggleTaskStatus(task)\n\tuassert.False(t, task.Done, \"Expected task to be done, but it is not done\")\n}\n\nfunc TestRemoveTask(t *testing.T) {\n\ttodoList := NewTodoList(\"My Todo List\")\n\ttask := NewTask(\"My Task\")\n\ttodoList.AddTask(1, task)\n\n\ttodoList.RemoveTask(\"1\")\n\n\ttasks := todoList.GetTasks()\n\tuassert.Equal(t, 0, len(tasks))\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":"ui","path":"gno.land/p/demo/ui","files":[{"name":"ui.gno","body":"package ui\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n)\n\ntype DOM struct {\n\t// metadata\n\tPrefix string\n\tTitle string\n\tWithComments bool\n\tClasses []string\n\n\t// elements\n\tHeader Element\n\tBody Element\n\tFooter Element\n}\n\nfunc (dom DOM) String() string {\n\tclasses := strings.Join(dom.Classes, \" \")\n\n\toutput := \"\"\n\n\tif classes != \"\" {\n\t\toutput += \"\u003cmain class='\" + classes + \"'\u003e\" + \"\\n\\n\"\n\t}\n\n\tif dom.Title != \"\" {\n\t\toutput += H1(dom.Title).String(dom) + \"\\n\"\n\t}\n\n\tif header := dom.Header.String(dom); header != \"\" {\n\t\tif dom.WithComments {\n\t\t\toutput += \"\u003c!-- header --\u003e\"\n\t\t}\n\t\toutput += header + \"\\n\"\n\t\tif dom.WithComments {\n\t\t\toutput += \"\u003c!-- /header --\u003e\"\n\t\t}\n\t}\n\n\tif body := dom.Body.String(dom); body != \"\" {\n\t\tif dom.WithComments {\n\t\t\toutput += \"\u003c!-- body --\u003e\"\n\t\t}\n\t\toutput += body + \"\\n\"\n\t\tif dom.WithComments {\n\t\t\toutput += \"\u003c!-- /body --\u003e\"\n\t\t}\n\t}\n\n\tif footer := dom.Footer.String(dom); footer != \"\" {\n\t\tif dom.WithComments {\n\t\t\toutput += \"\u003c!-- footer --\u003e\"\n\t\t}\n\t\toutput += footer + \"\\n\"\n\t\tif dom.WithComments {\n\t\t\toutput += \"\u003c!-- /footer --\u003e\"\n\t\t}\n\t}\n\n\tif classes != \"\" {\n\t\toutput += \"\u003c/main\u003e\"\n\t}\n\n\t// TODO: cleanup double new-lines.\n\n\treturn output\n}\n\ntype Jumbotron []DomStringer\n\nfunc (j Jumbotron) String(dom DOM) string {\n\toutput := `\u003cdiv class=\"jumbotron\"\u003e` + \"\\n\\n\"\n\tfor _, elem := range j {\n\t\toutput += elem.String(dom) + \"\\n\"\n\t}\n\toutput += `\u003c/div\u003e\u003c!-- /jumbotron --\u003e` + \"\\n\"\n\treturn output\n}\n\n// XXX: rename Element to Div?\ntype Element []DomStringer\n\nfunc (e *Element) Append(elems ...DomStringer) {\n\t*e = append(*e, elems...)\n}\n\nfunc (e *Element) String(dom DOM) string {\n\toutput := \"\"\n\tfor _, elem := range *e {\n\t\toutput += elem.String(dom) + \"\\n\"\n\t}\n\treturn output\n}\n\ntype Breadcrumb []DomStringer\n\nfunc (b *Breadcrumb) Append(elems ...DomStringer) {\n\t*b = append(*b, elems...)\n}\n\nfunc (b Breadcrumb) String(dom DOM) string {\n\toutput := \"\"\n\tfor idx, entry := range b {\n\t\tif idx \u003e 0 {\n\t\t\toutput += \" / \"\n\t\t}\n\t\toutput += entry.String(dom)\n\t}\n\treturn output\n}\n\ntype Columns struct {\n\tMaxWidth int\n\tColumns []Element\n}\n\nfunc (c *Columns) Append(elems ...Element) {\n\tc.Columns = append(c.Columns, elems...)\n}\n\nfunc (c Columns) String(dom DOM) string {\n\toutput := `\u003cdiv class=\"columns-` + strconv.Itoa(c.MaxWidth) + `\"\u003e` + \"\\n\"\n\tfor _, entry := range c.Columns {\n\t\toutput += `\u003cdiv class=\"column\"\u003e` + \"\\n\\n\"\n\t\toutput += entry.String(dom)\n\t\toutput += \"\u003c/div\u003e\u003c!-- /column--\u003e\\n\"\n\t}\n\toutput += \"\u003c/div\u003e\u003c!-- /columns-\" + strconv.Itoa(c.MaxWidth) + \" --\u003e\\n\"\n\treturn output\n}\n\ntype Link struct {\n\tText string\n\tPath string\n\tURL string\n}\n\n// TODO: image\n\n// TODO: pager\n\nfunc (l Link) String(dom DOM) string {\n\turl := \"\"\n\tswitch {\n\tcase l.Path != \"\" \u0026\u0026 l.URL != \"\":\n\t\tpanic(\"a link should have a path or a URL, not both.\")\n\tcase l.Path != \"\":\n\t\tif l.Text == \"\" {\n\t\t\tl.Text = l.Path\n\t\t}\n\t\turl = dom.Prefix + l.Path\n\tcase l.URL != \"\":\n\t\tif l.Text == \"\" {\n\t\t\tl.Text = l.URL\n\t\t}\n\t\turl = l.URL\n\t}\n\n\treturn \"[\" + l.Text + \"](\" + url + \")\"\n}\n\ntype BulletList []DomStringer\n\nfunc (bl BulletList) String(dom DOM) string {\n\toutput := \"\"\n\n\tfor _, entry := range bl {\n\t\toutput += \"- \" + entry.String(dom) + \"\\n\"\n\t}\n\n\treturn output\n}\n\nfunc Text(s string) DomStringer {\n\treturn Raw{Content: s}\n}\n\ntype DomStringer interface {\n\tString(dom DOM) string\n}\n\ntype Raw struct {\n\tContent string\n}\n\nfunc (r Raw) String(_ DOM) string {\n\treturn r.Content\n}\n\ntype (\n\tH1 string\n\tH2 string\n\tH3 string\n\tH4 string\n\tH5 string\n\tH6 string\n\tBold string\n\tItalic string\n\tCode string\n\tParagraph string\n\tQuote string\n\tHR struct{}\n)\n\nfunc (text H1) String(_ DOM) string { return \"# \" + string(text) + \"\\n\" }\nfunc (text H2) String(_ DOM) string { return \"## \" + string(text) + \"\\n\" }\nfunc (text H3) String(_ DOM) string { return \"### \" + string(text) + \"\\n\" }\nfunc (text H4) String(_ DOM) string { return \"#### \" + string(text) + \"\\n\" }\nfunc (text H5) String(_ DOM) string { return \"##### \" + string(text) + \"\\n\" }\nfunc (text H6) String(_ DOM) string { return \"###### \" + string(text) + \"\\n\" }\nfunc (text Quote) String(_ DOM) string { return \"\u003e \" + string(text) + \"\\n\" }\nfunc (text Bold) String(_ DOM) string { return \"**\" + string(text) + \"**\" }\nfunc (text Italic) String(_ DOM) string { return \"_\" + string(text) + \"_\" }\nfunc (text Paragraph) String(_ DOM) string { return \"\\n\" + string(text) + \"\\n\" }\nfunc (_ HR) String(_ DOM) string { return \"\\n---\\n\" }\n\nfunc (text Code) String(_ DOM) string {\n\t// multiline\n\tif strings.Contains(string(text), \"\\n\") {\n\t\treturn \"\\n```\\n\" + string(text) + \"\\n```\\n\"\n\t}\n\n\t// single line\n\treturn \"`\" + string(text) + \"`\"\n}\n"},{"name":"ui_test.gno","body":"package ui\n"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}} @@ -77,21 +77,21 @@ {"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"gnoface","path":"gno.land/r/demo/art/gnoface","files":[{"name":"gnoface.gno","body":"package gnoface\n\nimport (\n\t\"math/rand\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/entropy\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nfunc Render(path string) string {\n\tseed := uint64(entropy.New().Value())\n\n\tpath = strings.TrimSpace(path)\n\tif path != \"\" {\n\t\ts, err := strconv.Atoi(path)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tseed = uint64(s)\n\t}\n\n\toutput := ufmt.Sprintf(\"Gnoface #%d\\n\", seed)\n\toutput += \"```\\n\" + Draw(seed) + \"```\\n\"\n\treturn output\n}\n\nfunc Draw(seed uint64) string {\n\tvar (\n\t\thairs = []string{\n\t\t\t\" s\",\n\t\t\t\" .......\",\n\t\t\t\" s s s\",\n\t\t\t\" /\\\\ /\\\\\",\n\t\t\t\" |||||||\",\n\t\t}\n\t\theadtop = []string{\n\t\t\t\" /-------\\\\\",\n\t\t\t\" /~~~~~~~\\\\\",\n\t\t\t\" /|||||||\\\\\",\n\t\t\t\" ////////\\\\\",\n\t\t\t\" |||||||||\",\n\t\t\t\" /\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\",\n\t\t}\n\t\theadspace = []string{\n\t\t\t\" | |\",\n\t\t}\n\t\teyebrow = []string{\n\t\t\t\"~\",\n\t\t\t\"*\",\n\t\t\t\"_\",\n\t\t\t\".\",\n\t\t}\n\t\tear = []string{\n\t\t\t\"o\",\n\t\t\t\" \",\n\t\t\t\"D\",\n\t\t\t\"O\",\n\t\t\t\"\u003c\",\n\t\t\t\"\u003e\",\n\t\t\t\".\",\n\t\t\t\"|\",\n\t\t\t\")\",\n\t\t\t\"(\",\n\t\t}\n\t\teyesmiddle = []string{\n\t\t\t\"| o o |\",\n\t\t\t\"| o _ |\",\n\t\t\t\"| _ o |\",\n\t\t\t\"| . . |\",\n\t\t\t\"| O O |\",\n\t\t\t\"| v v |\",\n\t\t\t\"| X X |\",\n\t\t\t\"| x X |\",\n\t\t\t\"| X D |\",\n\t\t\t\"| ~ ~ |\",\n\t\t}\n\t\tnose = []string{\n\t\t\t\" | o |\",\n\t\t\t\" | O |\",\n\t\t\t\" | V |\",\n\t\t\t\" | L |\",\n\t\t\t\" | C |\",\n\t\t\t\" | ~ |\",\n\t\t\t\" | . . |\",\n\t\t\t\" | . |\",\n\t\t}\n\t\tmouth = []string{\n\t\t\t\" | __/ |\",\n\t\t\t\" | \\\\_/ |\",\n\t\t\t\" | . |\",\n\t\t\t\" | ___ |\",\n\t\t\t\" | ~~~ |\",\n\t\t\t\" | === |\",\n\t\t\t\" | \u003c=\u003e |\",\n\t\t}\n\t\theadbottom = []string{\n\t\t\t\" \\\\-------/\",\n\t\t\t\" \\\\~~~~~~~/\",\n\t\t\t\" \\\\_______/\",\n\t\t}\n\t)\n\n\tr := rand.New(rand.NewPCG(seed, 0xdeadbeef))\n\n\treturn pick(r, hairs) + \"\\n\" +\n\t\tpick(r, headtop) + \"\\n\" +\n\t\tpick(r, headspace) + \"\\n\" +\n\t\t\" | \" + pick(r, eyebrow) + \" \" + pick(r, eyebrow) + \" |\\n\" +\n\t\tpick(r, ear) + pick(r, eyesmiddle) + pick(r, ear) + \"\\n\" +\n\t\tpick(r, headspace) + \"\\n\" +\n\t\tpick(r, nose) + \"\\n\" +\n\t\tpick(r, headspace) + \"\\n\" +\n\t\tpick(r, mouth) + \"\\n\" +\n\t\tpick(r, headspace) + \"\\n\" +\n\t\tpick(r, headbottom) + \"\\n\"\n}\n\nfunc pick(r *rand.Rand, slice []string) string {\n\treturn slice[r.IntN(len(slice))]\n}\n\n// based on https://github.com/moul/pipotron/blob/master/dict/ascii-face.yml\n"},{"name":"gnoface_test.gno","body":"package gnoface\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nfunc TestDraw(t *testing.T) {\n\tcases := []struct {\n\t\tseed uint64\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tseed: 42,\n\t\t\texpected: `\n |||||||\n |||||||||\n | |\n | . ~ |\n)| v v |O\n | |\n | L |\n | |\n | ___ |\n | |\n \\~~~~~~~/\n`[1:],\n\t\t},\n\t\t{\n\t\t\tseed: 1337,\n\t\t\texpected: `\n .......\n |||||||||\n | |\n | . _ |\nD| x X |O\n | |\n | ~ |\n | |\n | ~~~ |\n | |\n \\~~~~~~~/\n`[1:],\n\t\t},\n\t\t{\n\t\t\tseed: 123456789,\n\t\t\texpected: `\n .......\n ////////\\\n | |\n | ~ * |\n|| x X |o\n | |\n | V |\n | |\n | . |\n | |\n \\-------/\n`[1:],\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tname := ufmt.Sprintf(\"%d\", tc.seed)\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tgot := Draw(tc.seed)\n\t\t\tuassert.Equal(t, string(tc.expected), got)\n\t\t})\n\t}\n}\n\nfunc TestRender(t *testing.T) {\n\tcases := []struct {\n\t\tpath string\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tpath: \"42\",\n\t\t\texpected: \"Gnoface #42\\n```\" + `\n |||||||\n |||||||||\n | |\n | . ~ |\n)| v v |O\n | |\n | L |\n | |\n | ___ |\n | |\n \\~~~~~~~/\n` + \"```\\n\",\n\t\t},\n\t\t{\n\t\t\tpath: \"1337\",\n\t\t\texpected: \"Gnoface #1337\\n```\" + `\n .......\n |||||||||\n | |\n | . _ |\nD| x X |O\n | |\n | ~ |\n | |\n | ~~~ |\n | |\n \\~~~~~~~/\n` + \"```\\n\",\n\t\t},\n\t\t{\n\t\t\tpath: \"123456789\",\n\t\t\texpected: \"Gnoface #123456789\\n```\" + `\n .......\n ////////\\\n | |\n | ~ * |\n|| x X |o\n | |\n | V |\n | |\n | . |\n | |\n \\-------/\n` + \"```\\n\",\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tt.Run(tc.path, func(t *testing.T) {\n\t\t\tgot := Render(tc.path)\n\t\t\tuassert.Equal(t, tc.expected, got)\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":"millipede","path":"gno.land/r/demo/art/millipede","files":[{"name":"millipede.gno","body":"package millipede\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\nconst (\n\tminSize = 1\n\tdefaultSize = 20\n\tmaxSize = 100\n)\n\nfunc Draw(size int) string {\n\tif size \u003c minSize || size \u003e maxSize {\n\t\tpanic(\"invalid millipede size\")\n\t}\n\tpaddings := []string{\" \", \" \", \"\", \" \", \" \", \" \", \" \", \" \", \" \"}\n\tvar b strings.Builder\n\tb.WriteString(\" ╚⊙ ⊙╝\\n\")\n\tfor i := 0; i \u003c size; i++ {\n\t\tb.WriteString(paddings[i%9] + \"╚═(███)═╝\\n\")\n\t}\n\treturn b.String()\n}\n\nfunc Render(path string) string {\n\tsize := defaultSize\n\n\tpath = strings.TrimSpace(path)\n\tif path != \"\" {\n\t\tvar err error\n\t\tsize, err = strconv.Atoi(path)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}\n\n\toutput := \"```\\n\" + Draw(size) + \"```\\n\"\n\tif size \u003e minSize {\n\t\toutput += ufmt.Sprintf(\"[%d](/r/demo/art/millipede:%d)\u003c \", size-1, size-1)\n\t}\n\tif size \u003c maxSize {\n\t\toutput += ufmt.Sprintf(\" \u003e[%d](/r/demo/art/millipede:%d)\", size+1, size+1)\n\t}\n\treturn output\n}\n\n// based on https://github.com/getmillipede/millipede-go/blob/977f046c39c35a650eac0fd30245e96b22c7803c/main.go\n"},{"name":"millipede_test.gno","body":"package millipede\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestRender(t *testing.T) {\n\tcases := []struct {\n\t\tpath string\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tpath: \"\",\n\t\t\texpected: \"```\" + `\n ╚⊙ ⊙╝\n ╚═(███)═╝\n ╚═(███)═╝\n╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n` + \"```\\n[19](/r/demo/art/millipede:19)\u003c \u003e[21](/r/demo/art/millipede:21)\",\n\t\t},\n\t\t{\n\t\t\tpath: \"4\",\n\t\t\texpected: \"```\" + `\n ╚⊙ ⊙╝\n ╚═(███)═╝\n ╚═(███)═╝\n╚═(███)═╝\n ╚═(███)═╝\n` + \"```\\n[3](/r/demo/art/millipede:3)\u003c \u003e[5](/r/demo/art/millipede:5)\",\n\t\t},\n\t}\n\n\tfor _, tc := range cases {\n\t\tt.Run(tc.path, func(t *testing.T) {\n\t\t\tgot := Render(tc.path)\n\t\t\tuassert.Equal(t, tc.expected, got)\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":"banktest","path":"gno.land/r/demo/banktest","files":[{"name":"README.md","body":"This is a simple test realm contract that demonstrates how to use the banker.\n\nSee [gno.land/r/demo/banktest/banktest.go](/r/demo/banktest/banktest.go) to see the original contract code.\n\nThis article will go through each line to explain how it works.\n\n```go\npackage banktest\n```\n\nThis package is locally named \"banktest\" (could be anything).\n\n```go\nimport (\n \"std\"\n)\n```\n\nThe \"std\" package is defined by the gno code in stdlibs/std/. \u003c/br\u003e Self explanatory; and you'll see more usage from std later.\n\n```go\ntype activity struct {\n caller std.Address\n sent std.Coins\n returned std.Coins\n time time.Time\n}\n\nfunc (act *activity) String() string {\n return act.caller.String() + \" \" +\n act.sent.String() + \" sent, \" +\n act.returned.String() + \" returned, at \" +\n act.time.Format(\"2006-01-02 3:04pm MST\")\n}\n\nvar latest [10]*activity\n```\n\nThis is just maintaining a list of recent activity to this contract. Notice that the \"latest\" variable is defined \"globally\" within the context of the realm with path \"gno.land/r/demo/banktest\".\n\nThis means that calls to functions defined within this package are encapsulated within this \"data realm\", where the data is mutated based on transactions that can potentially cross many realm and non-realm package boundaries (in the call stack).\n\n```go\n// Deposit will take the coins (to the realm's pkgaddr) or return them to user.\nfunc Deposit(returnDenom string, returnAmount int64) string {\n std.AssertOriginCall()\n caller := std.OriginCaller()\n send := std.Coins{{returnDenom, returnAmount}}\n```\n\nThis is the beginning of the definition of the contract function named \"Deposit\". `std.AssertOriginCall() asserts that this function was called by a gno transactional Message. The caller is the user who signed off on this transactional message. Send is the amount of deposit sent along with this message.\n\n```go\n // record activity\n act := \u0026activity{\n caller: caller,\n sent: std.OriginSend(),\n returned: send,\n time: time.Now(),\n }\n for i := len(latest) - 2; i \u003e= 0; i-- {\n latest[i+1] = latest[i] // shift by +1.\n }\n latest[0] = act\n```\n\nUpdating the \"latest\" array for viewing at gno.land/r/demo/banktest: (w/ trailing colon).\n\n```go\n // return if any.\n if returnAmount \u003e 0 {\n```\n\nIf the user requested the return of coins...\n\n```go\n banker := std.GetBanker(std.BankerTypeOriginSend)\n```\n\nuse a std.Banker instance to return any deposited coins to the original sender.\n\n```go\n pkgaddr := std.GetOrigPkgAddr()\n // TODO: use std.Coins constructors, this isn't generally safe.\n banker.SendCoins(pkgaddr, caller, send)\n return \"returned!\"\n```\n\nNotice that each realm package has an associated Cosmos address.\n\nFinally, the results are rendered via an ABCI query call when you visit [/r/demo/banktest:](/r/demo/banktest:).\n\n```go\nfunc Render(path string) string {\n // get realm coins.\n banker := std.GetBanker(std.BankerTypeReadonly)\n coins := banker.GetCoins(std.GetOrigPkgAddr())\n\n // render\n res := \"\"\n res += \"## recent activity\\n\"\n res += \"\\n\"\n for _, act := range latest {\n if act == nil {\n break\n }\n res += \" * \" + act.String() + \"\\n\"\n }\n res += \"\\n\"\n res += \"## total deposits\\n\"\n res += coins.String()\n return res\n}\n```\n\nYou can call this contract yourself, by vistiing [/r/demo/banktest](/r/demo/banktest) and the [quickstart guide](/r/demo/boards:gnolang/4).\n"},{"name":"banktest.gno","body":"package banktest\n\nimport (\n\t\"std\"\n\t\"time\"\n)\n\ntype activity struct {\n\tcaller std.Address\n\tsent std.Coins\n\treturned std.Coins\n\ttime time.Time\n}\n\nfunc (act *activity) String() string {\n\treturn act.caller.String() + \" \" +\n\t\tact.sent.String() + \" sent, \" +\n\t\tact.returned.String() + \" returned, at \" +\n\t\tact.time.Format(\"2006-01-02 3:04pm MST\")\n}\n\nvar latest [10]*activity\n\n// Deposit will take the coins (to the realm's pkgaddr) or return them to user.\nfunc Deposit(returnDenom string, returnAmount int64) string {\n\tstd.AssertOriginCall()\n\tcaller := std.OriginCaller()\n\tsend := std.Coins{{returnDenom, returnAmount}}\n\t// record activity\n\tact := \u0026activity{\n\t\tcaller: caller,\n\t\tsent: std.OriginSend(),\n\t\treturned: send,\n\t\ttime: time.Now(),\n\t}\n\tfor i := len(latest) - 2; i \u003e= 0; i-- {\n\t\tlatest[i+1] = latest[i] // shift by +1.\n\t}\n\tlatest[0] = act\n\t// return if any.\n\tif returnAmount \u003e 0 {\n\t\tbanker := std.GetBanker(std.BankerTypeOriginSend)\n\t\tpkgaddr := std.GetOrigPkgAddr()\n\t\t// TODO: use std.Coins constructors, this isn't generally safe.\n\t\tbanker.SendCoins(pkgaddr, caller, send)\n\t\treturn \"returned!\"\n\t} else {\n\t\treturn \"thank you!\"\n\t}\n}\n\nfunc Render(path string) string {\n\t// get realm coins.\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tcoins := banker.GetCoins(std.GetOrigPkgAddr())\n\n\t// render\n\tres := \"\"\n\tres += \"## recent activity\\n\"\n\tres += \"\\n\"\n\tfor _, act := range latest {\n\t\tif act == nil {\n\t\t\tbreak\n\t\t}\n\t\tres += \" * \" + act.String() + \"\\n\"\n\t}\n\tres += \"\\n\"\n\tres += \"## total deposits\\n\"\n\tres += coins.String()\n\treturn res\n}\n"},{"name":"z_0_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\n// SEND: 100000000ugnot\n\npackage bank1\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/banktest\"\n)\n\nfunc main() {\n\t// set up main address and banktest addr.\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/bank1\")\n\tstd.TestSetOriginCaller(mainaddr)\n\tstd.TestSetOrigPkgAddr(banktestAddr)\n\n\t// get and print balance of mainaddr.\n\t// with the SEND, + 200 gnot given by the TestContext, main should have 300gnot.\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\t// simulate a Deposit call. use Send + OriginSend to simulate -send.\n\tbanker.SendCoins(mainaddr, banktestAddr, std.Coins{{\"ugnot\", 100_000_000}})\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 100_000_000}}, nil)\n\tres := banktest.Deposit(\"ugnot\", 50_000_000)\n\tprintln(\"Deposit():\", res)\n\n\t// print main balance after.\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal)\n\n\t// simulate a Render(). banker should have given back all coins.\n\tres = banktest.Render(\"\")\n\tprintln(res)\n}\n\n// Output:\n// main before: 300000000ugnot\n// Deposit(): returned!\n// main after: 250000000ugnot\n// ## recent activity\n//\n// * g1tnpdmvrmtgql8fmxgsq9rwtst5hsxahk3f05dk 100000000ugnot sent, 50000000ugnot returned, at 2009-02-13 11:31pm UTC\n//\n// ## total deposits\n// 50000000ugnot\n"},{"name":"z_1_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\npackage bank1\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/banktest\"\n)\n\nfunc main() {\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\n\t// simulate a Deposit call.\n\tstd.TestSetOrigPkgAddr(banktestAddr)\n\tstd.TestIssueCoins(banktestAddr, std.Coins{{\"ugnot\", 100000000}})\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 100000000}}, nil)\n\tres := banktest.Deposit(\"ugnot\", 101000000)\n\tprintln(res)\n}\n\n// Error:\n// cannot send \"101000000ugnot\", limit \"100000000ugnot\" exceeded with \"\" already spent\n"},{"name":"z_2_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\npackage bank1\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/banktest\"\n)\n\nfunc main() {\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\n\t// print main balance before.\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/bank1\")\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal) // plus OriginSend equals 300.\n\n\t// simulate a Deposit call.\n\tstd.TestSetOrigPkgAddr(banktestAddr)\n\tstd.TestIssueCoins(banktestAddr, std.Coins{{\"ugnot\", 100000000}})\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 100000000}}, nil)\n\tres := banktest.Deposit(\"ugnot\", 55000000)\n\tprintln(\"Deposit():\", res)\n\n\t// print main balance after.\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal) // now 255.\n\n\t// simulate a Render().\n\tres = banktest.Render(\"\")\n\tprintln(res)\n}\n\n// Output:\n// main before: 200000000ugnot\n// Deposit(): returned!\n// main after: 255000000ugnot\n// ## recent activity\n//\n// * g1tnpdmvrmtgql8fmxgsq9rwtst5hsxahk3f05dk 100000000ugnot sent, 55000000ugnot returned, at 2009-02-13 11:31pm UTC\n//\n// ## total deposits\n// 45000000ugnot\n"},{"name":"z_3_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\npackage bank1\n\nimport (\n\t\"std\"\n)\n\nfunc main() {\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/bank1\")\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tsend := std.Coins{{\"ugnot\", 123}}\n\tbanker.SendCoins(banktestAddr, mainaddr, send)\n\n}\n\n// Error:\n// can only send coins from realm that created banker \"g1tnpdmvrmtgql8fmxgsq9rwtst5hsxahk3f05dk\", not \"g1dv3435088tlrgggf745kaud0ptrkc9v42k8llz\"\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":"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)\n\nvar (\n\tbanker *grc20.Banker // private banker.\n\tToken grc20.Token // public safe-object.\n)\n\nfunc init() {\n\tbanker = grc20.NewBanker(\"Bar\", \"BAR\", 4)\n\tToken = banker.Token()\n}\n\nfunc Faucet() string {\n\tcaller := std.PrevRealm().Addr()\n\tif err := banker.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 banker.RenderHome() // XXX: should be 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.TestSetOriginCaller(alice) // XXX: should not need this\n\n\turequire.Equal(t, Token.BalanceOf(alice), uint64(0))\n\turequire.Equal(t, Faucet(), \"OK\")\n\turequire.Equal(t, Token.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":"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)\n\nvar (\n\tbanker *grc20.Banker // private banker.\n\tToken grc20.Token // public safe-object.\n)\n\nfunc init() {\n\tbanker = grc20.NewBanker(\"Bar\", \"BAR\", 4)\n\tToken = banker.Token()\n}\n\nfunc Faucet() string {\n\tcaller := std.PreviousRealm().Addr()\n\tif err := banker.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 banker.RenderHome() // XXX: should be 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.TestSetOriginCaller(alice) // XXX: should not need this\n\n\turequire.Equal(t, Token.BalanceOf(alice), uint64(0))\n\turequire.Equal(t, Faucet(), \"OK\")\n\turequire.Equal(t, Token.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":"counter","path":"gno.land/r/demo/counter","files":[{"name":"counter.gno","body":"package counter\n\nimport \"strconv\"\n\nvar counter int\n\nfunc Increment() int {\n\tcounter++\n\treturn counter\n}\n\nfunc Render(_ string) string {\n\treturn strconv.Itoa(counter)\n}\n"},{"name":"counter_test.gno","body":"package counter\n\nimport \"testing\"\n\nfunc TestIncrement(t *testing.T) {\n\tcounter = 0\n\tval := Increment()\n\tif val != 1 {\n\t\tt.Fatalf(\"result from Increment(): %d != 1\", val)\n\t}\n\tif counter != val {\n\t\tt.Fatalf(\"counter (%d) != val (%d)\", counter, val)\n\t}\n}\n\nfunc TestRender(t *testing.T) {\n\tcounter = 1337\n\tres := Render(\"\")\n\tif res != \"1337\" {\n\t\tt.Fatalf(\"render result %q != %q\", res, \"1337\")\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":"govdao","path":"gno.land/r/gov/dao/v2","files":[{"name":"dao.gno","body":"package govdao\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/membstore\"\n\t\"gno.land/p/demo/simpledao\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar (\n\td *simpledao.SimpleDAO // the current active DAO implementation\n\tmembers membstore.MemberStore // the member store\n)\n\nfunc init() {\n\tvar (\n\t\tset = []membstore.Member{\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\"), // Jae\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"), // Manfred\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1e6gxg5tvc55mwsn7t7dymmlasratv7mkv0rap2\"), // Milos\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1jazghxvvgz3egnr2fc8uf72z4g0l03596y9ls7\"), // Nemanja\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1qhskthp2uycmg4zsdc9squ2jds7yv3t0qyrlnp\"), // Petar\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g18amm3fc00t43dcxsys6udug0czyvqt9e7p23rd\"), // Marc\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1dfr24yhk5ztwtqn2a36m8f6ud8cx5hww4dkjfl\"), // Antonio\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g19p3yzr3cuhzqa02j0ce6kzvyjqfzwemw3vam0x\"), // Guilhem\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1mx4pum9976th863jgry4sdjzfwu03qan5w2v9j\"), // Ray\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g127l4gkhk0emwsx5tmxe96sp86c05h8vg5tufzq\"), // Maxwell\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1cpx59z5r8vzeww2fm4ezpz7yvjs7kptywkm864\"), // Morgan\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1ker4vvggvsyatexxn3hkthp2hu80pkhrwmuczr\"), // Sergio\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g18x425qmujg99cfz3q97y4uep5pxjq3z8lmpt25\"), // Antoine\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t// GNO DEVX\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g16tfrrul20g4jzt3z303raqw8vs8s2pqqh5clwu\"), // Ilker\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1hy6zry03hg5d8le9s2w4fxme6236hkgd928dun\"), // Jerónimo\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g15ruzptpql4dpuyzej0wkt5rq6r26kw4nxu9fwd\"), // Denis\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1dnllrdzwfhxv3evyk09y48mgn5phfjvtyrlzm7\"), // Danny\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1778y2yphxs2wpuaflsy5y9qwcd4gttn4g5yjx5\"), // Michelle\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1mq7g0jszdmn4qdpc9tq94w0gyex37su892n80m\"), // Alan\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g197q5e9v00vuz256ly7fq7v3ekaun5cr7wmjgfh\"), // Salvo\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1mpkp5lm8lwpm0pym4388836d009zfe4maxlqsq\"), // Alexis\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g125em6arxsnj49vx35f0n0z34putv5ty3376fg5\"), // Leon\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1whzkakk4hzjkvy60d5pwfk484xu67ar2cl62h2\"), // Kirk\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t// AiB\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1sw5xklxjjuv0yvuxy5f5s3l3mnj0nqq626a9wr\"), // Albert\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t// ONBLOC\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g12vx7dn3dqq89mz550zwunvg4qw6epq73d9csay\"), // Dongwon\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1r04aw56fgvzy859fachr8hzzhqkulkaemltr76\"), // Blake\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g17n4y745s08awwq4e0a38lagsgtntna0749tnxe\"), // Jinwoo\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1ckae7tc5sez8ul3ssne75sk4muwgttp6ks2ky9\"), // ByeongJun\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t\t// TERITORI\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g14u5eaheavy0ux4dmpykg2gvxpvqvexm9cyg58a\"), // Norman\n\t\t\t\tVotingPower: 5,\n\t\t\t},\n\t\t\t// BERTY\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1qynsu9dwj9lq0m5fkje7jh6qy3md80ztqnshhm\"), // Rémi\n\t\t\t\tVotingPower: 5,\n\t\t\t},\n\t\t\t// FLIPPANDO / ZENTASKTIC\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g17ernafy6ctpcz6uepfsq2js8x2vz0wladh5yc3\"), // Dragos\n\t\t\t\tVotingPower: 5,\n\t\t\t},\n\t\t}\n\t)\n\n\t// Set the member store\n\tmembers = membstore.NewMembStore(membstore.WithInitialMembers(set))\n\n\t// Set the DAO implementation\n\td = simpledao.New(members)\n}\n\n// Propose is designed to be called by another contract or with\n// `maketx run`, not by a `maketx call`.\nfunc Propose(request dao.ProposalRequest) uint64 {\n\tidx, err := d.Propose(request)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn idx\n}\n\n// VoteOnProposal casts a vote for the given proposal\nfunc VoteOnProposal(id uint64, option dao.VoteOption) {\n\tif err := d.VoteOnProposal(id, option); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// ExecuteProposal executes the proposal\nfunc ExecuteProposal(id uint64) {\n\tif err := d.ExecuteProposal(id); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// GetPropStore returns the active proposal store\nfunc GetPropStore() dao.PropStore {\n\treturn d\n}\n\n// GetMembStore returns the active member store\nfunc GetMembStore() membstore.MemberStore {\n\treturn members\n}\n\nfunc Render(path string) string {\n\tif path == \"\" {\n\t\tnumProposals := d.Size()\n\n\t\tif numProposals == 0 {\n\t\t\treturn \"No proposals found :(\" // corner case\n\t\t}\n\n\t\toutput := \"\"\n\n\t\toffset := uint64(0)\n\t\tif numProposals \u003e= 10 {\n\t\t\toffset = uint64(numProposals) - 10\n\t\t}\n\n\t\t// Fetch the last 10 proposals\n\t\tfor idx, prop := range d.Proposals(offset, uint64(10)) {\n\t\t\toutput += ufmt.Sprintf(\n\t\t\t\t\"- [Proposal #%d](%s:%d) - (**%s**)(by %s)\\n\",\n\t\t\t\tidx,\n\t\t\t\t\"/r/gov/dao/v2\",\n\t\t\t\tidx,\n\t\t\t\tprop.Status().String(),\n\t\t\t\tprop.Author().String(),\n\t\t\t)\n\t\t}\n\n\t\treturn output\n\t}\n\n\t// Display the detailed proposal\n\tidx, err := strconv.Atoi(path)\n\tif err != nil {\n\t\treturn \"404: Invalid proposal ID\"\n\t}\n\n\t// Fetch the proposal\n\tprop, err := d.ProposalByID(uint64(idx))\n\tif err != nil {\n\t\treturn ufmt.Sprintf(\"unable to fetch proposal, %s\", err.Error())\n\t}\n\n\t// Render the proposal\n\toutput := \"\"\n\toutput += ufmt.Sprintf(\"# Prop #%d\", idx)\n\toutput += \"\\n\\n\"\n\toutput += prop.Render()\n\toutput += \"\\n\\n\"\n\n\treturn output\n}\n"},{"name":"poc.gno","body":"package govdao\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/combinederr\"\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/membstore\"\n\t\"gno.land/p/gov/executor\"\n)\n\nvar errNoChangesProposed = errors.New(\"no set changes proposed\")\n\n// NewGovDAOExecutor creates the govdao wrapped callback executor\nfunc NewGovDAOExecutor(cb func() error) dao.Executor {\n\tif cb == nil {\n\t\tpanic(errNoChangesProposed)\n\t}\n\n\treturn executor.NewCallbackExecutor(\n\t\tcb,\n\t\tstd.CurrentRealm().PkgPath(),\n\t)\n}\n\n// NewMemberPropExecutor returns the GOVDAO member change executor\nfunc NewMemberPropExecutor(changesFn func() []membstore.Member) dao.Executor {\n\tif changesFn == nil {\n\t\tpanic(errNoChangesProposed)\n\t}\n\n\tcallback := func() error {\n\t\terrs := \u0026combinederr.CombinedError{}\n\t\tcbMembers := changesFn()\n\n\t\tfor _, member := range cbMembers {\n\t\t\tswitch {\n\t\t\tcase !members.IsMember(member.Address):\n\t\t\t\t// Addition request\n\t\t\t\terr := members.AddMember(member)\n\n\t\t\t\terrs.Add(err)\n\t\t\tcase member.VotingPower == 0:\n\t\t\t\t// Remove request\n\t\t\t\terr := members.UpdateMember(member.Address, membstore.Member{\n\t\t\t\t\tAddress: member.Address,\n\t\t\t\t\tVotingPower: 0, // 0 indicated removal\n\t\t\t\t})\n\n\t\t\t\terrs.Add(err)\n\t\t\tdefault:\n\t\t\t\t// Update request\n\t\t\t\terr := members.UpdateMember(member.Address, member)\n\n\t\t\t\terrs.Add(err)\n\t\t\t}\n\t\t}\n\n\t\t// Check if there were any execution errors\n\t\tif errs.Size() == 0 {\n\t\t\treturn nil\n\t\t}\n\n\t\treturn errs\n\t}\n\n\treturn NewGovDAOExecutor(callback)\n}\n\nfunc NewMembStoreImplExecutor(changeFn func() membstore.MemberStore) dao.Executor {\n\tif changeFn == nil {\n\t\tpanic(errNoChangesProposed)\n\t}\n\n\tcallback := func() error {\n\t\tsetMembStoreImpl(changeFn())\n\n\t\treturn nil\n\t}\n\n\treturn NewGovDAOExecutor(callback)\n}\n\n// setMembStoreImpl sets a new dao.MembStore implementation\nfunc setMembStoreImpl(impl membstore.MemberStore) {\n\tif impl == nil {\n\t\tpanic(\"invalid member store\")\n\t}\n\n\tmembers = impl\n}\n"},{"name":"prop1_filetest.gno","body":"// Please note that this package is intended for demonstration purposes only.\n// You could execute this code (the init part) by running a `maketx run` command\n// or by uploading a similar package to a personal namespace.\n//\n// For the specific case of validators, a `r/gnoland/valopers` will be used to\n// organize the lifecycle of validators (register, etc), and this more complex\n// contract will be responsible to generate proposals.\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/dao\"\n\tpVals \"gno.land/p/sys/validators\"\n\tgovdao \"gno.land/r/gov/dao/v2\"\n\tvalidators \"gno.land/r/sys/validators/v2\"\n)\n\nfunc init() {\n\tchangesFn := func() []pVals.Validator {\n\t\treturn []pVals.Validator{\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g12345678\"),\n\t\t\t\tPubKey: \"pubkey\",\n\t\t\t\tVotingPower: 10, // add a new validator\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g000000000\"),\n\t\t\t\tPubKey: \"pubkey\",\n\t\t\t\tVotingPower: 10, // add a new validator\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g000000000\"),\n\t\t\t\tPubKey: \"pubkey\",\n\t\t\t\tVotingPower: 0, // remove an existing validator\n\t\t\t},\n\t\t}\n\t}\n\n\t// Wraps changesFn to emit a certified event only if executed from a\n\t// complete governance proposal process.\n\texecutor := validators.NewPropExecutor(changesFn)\n\n\t// Create a proposal\n\tdescription := \"manual valset changes proposal example\"\n\n\tprop := dao.ProposalRequest{\n\t\tDescription: description,\n\t\tExecutor: executor,\n\t}\n\n\tgovdao.Propose(prop)\n}\n\nfunc main() {\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"\"))\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tgovdao.VoteOnProposal(0, dao.YesVote)\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tprintln(validators.Render(\"\"))\n\tprintln(\"--\")\n\tgovdao.ExecuteProposal(0)\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tprintln(validators.Render(\"\"))\n}\n\n// Output:\n// --\n// - [Proposal #0](/r/gov/dao/v2:0) - (**active**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm)\n//\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// manual valset changes proposal example\n//\n// Status: active\n//\n// Voting stats: YES 0 (0%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 10 (100%)\n//\n// Threshold met: false\n//\n//\n// --\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// manual valset changes proposal example\n//\n// Status: accepted\n//\n// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%)\n//\n// Threshold met: true\n//\n//\n// --\n// No valset changes to apply.\n// --\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// manual valset changes proposal example\n//\n// Status: execution successful\n//\n// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%)\n//\n// Threshold met: true\n//\n//\n// --\n// Valset changes:\n// - #123: g12345678 (10)\n// - #123: g000000000 (10)\n// - #123: g000000000 (0)\n"},{"name":"prop2_filetest.gno","body":"package main\n\nimport (\n\t\"time\"\n\n\t\"gno.land/p/demo/dao\"\n\tgnoblog \"gno.land/r/gnoland/blog\"\n\tgovdao \"gno.land/r/gov/dao/v2\"\n)\n\nfunc init() {\n\tex := gnoblog.NewPostExecutor(\n\t\t\"hello-from-govdao\", // slug\n\t\t\"Hello from GovDAO!\", // title\n\t\t\"This post was published by a GovDAO proposal.\", // body\n\t\ttime.Now().Format(time.RFC3339), // publication date\n\t\t\"moul\", // authors\n\t\t\"govdao,example\", // tags\n\t)\n\n\t// Create a proposal\n\tdescription := \"post a new blogpost about govdao\"\n\n\tprop := dao.ProposalRequest{\n\t\tDescription: description,\n\t\tExecutor: ex,\n\t}\n\n\tgovdao.Propose(prop)\n}\n\nfunc main() {\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"\"))\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tgovdao.VoteOnProposal(0, \"YES\")\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tprintln(gnoblog.Render(\"\"))\n\tprintln(\"--\")\n\tgovdao.ExecuteProposal(0)\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tprintln(gnoblog.Render(\"\"))\n}\n\n// Output:\n// --\n// - [Proposal #0](/r/gov/dao/v2:0) - (**active**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm)\n//\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// post a new blogpost about govdao\n//\n// Status: active\n//\n// Voting stats: YES 0 (0%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 10 (100%)\n//\n// Threshold met: false\n//\n//\n// --\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// post a new blogpost about govdao\n//\n// Status: accepted\n//\n// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%)\n//\n// Threshold met: true\n//\n//\n// --\n// # Gnoland's Blog\n//\n// No posts.\n// --\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// post a new blogpost about govdao\n//\n// Status: execution successful\n//\n// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%)\n//\n// Threshold met: true\n//\n//\n// --\n// # Gnoland's Blog\n//\n// \u003cdiv class='columns-3'\u003e\u003cdiv\u003e\n//\n// ### [Hello from GovDAO!](/r/gnoland/blog:p/hello-from-govdao)\n// 13 Feb 2009\n// \u003c/div\u003e\u003c/div\u003e\n"},{"name":"prop3_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/membstore\"\n\tgovdao \"gno.land/r/gov/dao/v2\"\n)\n\nfunc init() {\n\tmemberFn := func() []membstore.Member {\n\t\treturn []membstore.Member{\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g123\"),\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g456\"),\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g789\"),\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t}\n\t}\n\n\t// Create a proposal\n\tdescription := \"add new members to the govdao\"\n\n\tprop := dao.ProposalRequest{\n\t\tDescription: description,\n\t\tExecutor: govdao.NewMemberPropExecutor(memberFn),\n\t}\n\n\tgovdao.Propose(prop)\n}\n\nfunc main() {\n\tprintln(\"--\")\n\tprintln(govdao.GetMembStore().Size())\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"\"))\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tgovdao.VoteOnProposal(0, \"YES\")\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"\"))\n\tprintln(\"--\")\n\tgovdao.ExecuteProposal(0)\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"\"))\n\tprintln(\"--\")\n\tprintln(govdao.GetMembStore().Size())\n}\n\n// Output:\n// --\n// 1\n// --\n// - [Proposal #0](/r/gov/dao/v2:0) - (**active**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm)\n//\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// add new members to the govdao\n//\n// Status: active\n//\n// Voting stats: YES 0 (0%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 10 (100%)\n//\n// Threshold met: false\n//\n//\n// --\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// add new members to the govdao\n//\n// Status: accepted\n//\n// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%)\n//\n// Threshold met: true\n//\n//\n// --\n// - [Proposal #0](/r/gov/dao/v2:0) - (**accepted**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm)\n//\n// --\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// add new members to the govdao\n//\n// Status: execution successful\n//\n// Voting stats: YES 10 (25%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 30 (75%)\n//\n// Threshold met: false\n//\n//\n// --\n// - [Proposal #0](/r/gov/dao/v2:0) - (**execution successful**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm)\n//\n// --\n// 4\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":"bridge","path":"gno.land/r/gov/dao/bridge","files":[{"name":"bridge.gno","body":"package bridge\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/ownable\"\n)\n\nconst initialOwner = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\") // @moul\n\nvar b *Bridge\n\n// Bridge is the active GovDAO\n// implementation bridge\ntype Bridge struct {\n\t*ownable.Ownable\n\n\tdao DAO\n}\n\n// init constructs the initial GovDAO implementation\nfunc init() {\n\tb = \u0026Bridge{\n\t\tOwnable: ownable.NewWithAddress(initialOwner),\n\t\tdao: \u0026govdaoV2{},\n\t}\n}\n\n// SetDAO sets the currently active GovDAO implementation\nfunc SetDAO(dao DAO) {\n\tb.AssertCallerIsOwner()\n\n\tb.dao = dao\n}\n\n// GovDAO returns the current GovDAO implementation\nfunc GovDAO() DAO {\n\treturn b.dao\n}\n"},{"name":"bridge_test.gno","body":"package bridge\n\nimport (\n\t\"testing\"\n\n\t\"std\"\n\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestBridge_DAO(t *testing.T) {\n\tvar (\n\t\tproposalID = uint64(10)\n\t\tmockDAO = \u0026mockDAO{\n\t\t\tproposeFn: func(_ dao.ProposalRequest) uint64 {\n\t\t\t\treturn proposalID\n\t\t\t},\n\t\t}\n\t)\n\n\tb.dao = mockDAO\n\n\tuassert.Equal(t, proposalID, GovDAO().Propose(dao.ProposalRequest{}))\n}\n\nfunc TestBridge_SetDAO(t *testing.T) {\n\tt.Run(\"invalid owner\", func(t *testing.T) {\n\t\t// Attempt to set a new DAO implementation\n\t\tuassert.PanicsWithMessage(t, ownable.ErrUnauthorized.Error(), func() {\n\t\t\tSetDAO(\u0026mockDAO{})\n\t\t})\n\t})\n\n\tt.Run(\"valid owner\", func(t *testing.T) {\n\t\tvar (\n\t\t\taddr = testutils.TestAddress(\"owner\")\n\n\t\t\tproposalID = uint64(10)\n\t\t\tmockDAO = \u0026mockDAO{\n\t\t\t\tproposeFn: func(_ dao.ProposalRequest) uint64 {\n\t\t\t\t\treturn proposalID\n\t\t\t\t},\n\t\t\t}\n\t\t)\n\n\t\tstd.TestSetOriginCaller(addr)\n\n\t\tb.Ownable = ownable.NewWithAddress(addr)\n\n\t\turequire.NotPanics(t, func() {\n\t\t\tSetDAO(mockDAO)\n\t\t})\n\n\t\tuassert.Equal(\n\t\t\tt,\n\t\t\tmockDAO.Propose(dao.ProposalRequest{}),\n\t\t\tGovDAO().Propose(dao.ProposalRequest{}),\n\t\t)\n\t})\n}\n"},{"name":"doc.gno","body":"// Package bridge represents a GovDAO implementation wrapper, used by other Realms and Packages to\n// always fetch the most active GovDAO implementation, instead of directly referencing it, and having to\n// update it each time the GovDAO implementation changes\npackage bridge\n"},{"name":"mock_test.gno","body":"package bridge\n\nimport (\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/membstore\"\n)\n\ntype (\n\tproposeDelegate func(dao.ProposalRequest) uint64\n\tvoteOnProposalDelegate func(uint64, dao.VoteOption)\n\texecuteProposalDelegate func(uint64)\n\tgetPropStoreDelegate func() dao.PropStore\n\tgetMembStoreDelegate func() membstore.MemberStore\n\tnewGovDAOExecutorDelegate func(func() error) dao.Executor\n)\n\ntype mockDAO struct {\n\tproposeFn proposeDelegate\n\tvoteOnProposalFn voteOnProposalDelegate\n\texecuteProposalFn executeProposalDelegate\n\tgetPropStoreFn getPropStoreDelegate\n\tgetMembStoreFn getMembStoreDelegate\n\tnewGovDAOExecutorFn newGovDAOExecutorDelegate\n}\n\nfunc (m *mockDAO) Propose(request dao.ProposalRequest) uint64 {\n\tif m.proposeFn != nil {\n\t\treturn m.proposeFn(request)\n\t}\n\n\treturn 0\n}\n\nfunc (m *mockDAO) VoteOnProposal(id uint64, option dao.VoteOption) {\n\tif m.voteOnProposalFn != nil {\n\t\tm.voteOnProposalFn(id, option)\n\t}\n}\n\nfunc (m *mockDAO) ExecuteProposal(id uint64) {\n\tif m.executeProposalFn != nil {\n\t\tm.executeProposalFn(id)\n\t}\n}\n\nfunc (m *mockDAO) GetPropStore() dao.PropStore {\n\tif m.getPropStoreFn != nil {\n\t\treturn m.getPropStoreFn()\n\t}\n\n\treturn nil\n}\n\nfunc (m *mockDAO) GetMembStore() membstore.MemberStore {\n\tif m.getMembStoreFn != nil {\n\t\treturn m.getMembStoreFn()\n\t}\n\n\treturn nil\n}\n\nfunc (m *mockDAO) NewGovDAOExecutor(cb func() error) dao.Executor {\n\tif m.newGovDAOExecutorFn != nil {\n\t\treturn m.newGovDAOExecutorFn(cb)\n\t}\n\n\treturn nil\n}\n"},{"name":"types.gno","body":"package bridge\n\nimport (\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/membstore\"\n)\n\n// DAO abstracts the commonly used DAO interface\ntype DAO interface {\n\tPropose(dao.ProposalRequest) uint64\n\tVoteOnProposal(uint64, dao.VoteOption)\n\tExecuteProposal(uint64)\n\tGetPropStore() dao.PropStore\n\tGetMembStore() membstore.MemberStore\n\n\tNewGovDAOExecutor(func() error) dao.Executor\n}\n"},{"name":"v2.gno","body":"package bridge\n\nimport (\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/membstore\"\n\tgovdao \"gno.land/r/gov/dao/v2\"\n)\n\n// govdaoV2 is a wrapper for interacting with the /r/gov/dao/v2 Realm\ntype govdaoV2 struct{}\n\nfunc (g *govdaoV2) Propose(request dao.ProposalRequest) uint64 {\n\treturn govdao.Propose(request)\n}\n\nfunc (g *govdaoV2) VoteOnProposal(id uint64, option dao.VoteOption) {\n\tgovdao.VoteOnProposal(id, option)\n}\n\nfunc (g *govdaoV2) ExecuteProposal(id uint64) {\n\tgovdao.ExecuteProposal(id)\n}\n\nfunc (g *govdaoV2) GetPropStore() dao.PropStore {\n\treturn govdao.GetPropStore()\n}\n\nfunc (g *govdaoV2) GetMembStore() membstore.MemberStore {\n\treturn govdao.GetMembStore()\n}\n\nfunc (g *govdaoV2) NewGovDAOExecutor(cb func() error) dao.Executor {\n\treturn govdao.NewGovDAOExecutor(cb)\n}\n\nfunc (g *govdaoV2) NewMemberPropExecutor(cb func() []membstore.Member) dao.Executor {\n\treturn govdao.NewMemberPropExecutor(cb)\n}\n\nfunc (g *govdaoV2) NewMembStoreImplExecutor(cb func() membstore.MemberStore) dao.Executor {\n\treturn govdao.NewMembStoreImplExecutor(cb)\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":"daoweb","path":"gno.land/r/demo/daoweb","files":[{"name":"daoweb.gno","body":"package daoweb\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/json\"\n\t\"gno.land/r/gov/dao/bridge\"\n)\n\n// Proposals returns the paginated GovDAO proposals\nfunc Proposals(offset, count uint64) string {\n\tvar (\n\t\tpropStore = bridge.GovDAO().GetPropStore()\n\t\tsize = propStore.Size()\n\t)\n\n\t// Get the props\n\tprops := propStore.Proposals(offset, count)\n\n\tresp := ProposalsResponse{\n\t\tProposals: make([]Proposal, 0, count),\n\t\tTotal: uint64(size),\n\t}\n\n\tfor _, p := range props {\n\t\tprop := Proposal{\n\t\t\tAuthor: p.Author(),\n\t\t\tDescription: p.Description(),\n\t\t\tStatus: p.Status(),\n\t\t\tStats: p.Stats(),\n\t\t\tIsExpired: p.IsExpired(),\n\t\t}\n\n\t\tresp.Proposals = append(resp.Proposals, prop)\n\t}\n\n\t// Encode the response into JSON\n\tencodedProps, err := json.Marshal(encodeProposalsResponse(resp))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn string(encodedProps)\n}\n\n// ProposalByID fetches the proposal using the given ID\nfunc ProposalByID(id uint64) string {\n\tpropStore := bridge.GovDAO().GetPropStore()\n\n\tp, err := propStore.ProposalByID(id)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Encode the response into JSON\n\tprop := Proposal{\n\t\tAuthor: p.Author(),\n\t\tDescription: p.Description(),\n\t\tStatus: p.Status(),\n\t\tStats: p.Stats(),\n\t\tIsExpired: p.IsExpired(),\n\t}\n\n\tencodedProp, err := json.Marshal(encodeProposal(prop))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn string(encodedProp)\n}\n\n// encodeProposal encodes a proposal into a json node\nfunc encodeProposal(p Proposal) *json.Node {\n\treturn json.ObjectNode(\"\", map[string]*json.Node{\n\t\t\"author\": json.StringNode(\"author\", p.Author.String()),\n\t\t\"description\": json.StringNode(\"description\", p.Description),\n\t\t\"status\": json.StringNode(\"status\", p.Status.String()),\n\t\t\"stats\": json.ObjectNode(\"stats\", map[string]*json.Node{\n\t\t\t\"yay_votes\": json.NumberNode(\"yay_votes\", float64(p.Stats.YayVotes)),\n\t\t\t\"nay_votes\": json.NumberNode(\"nay_votes\", float64(p.Stats.NayVotes)),\n\t\t\t\"abstain_votes\": json.NumberNode(\"abstain_votes\", float64(p.Stats.AbstainVotes)),\n\t\t\t\"total_voting_power\": json.NumberNode(\"total_voting_power\", float64(p.Stats.TotalVotingPower)),\n\t\t}),\n\t\t\"is_expired\": json.BoolNode(\"is_expired\", p.IsExpired),\n\t})\n}\n\n// encodeProposalsResponse encodes a proposal response into a JSON node\nfunc encodeProposalsResponse(props ProposalsResponse) *json.Node {\n\tproposals := make([]*json.Node, 0, len(props.Proposals))\n\n\tfor _, p := range props.Proposals {\n\t\tproposals = append(proposals, encodeProposal(p))\n\t}\n\n\treturn json.ObjectNode(\"\", map[string]*json.Node{\n\t\t\"proposals\": json.ArrayNode(\"proposals\", proposals),\n\t\t\"total\": json.NumberNode(\"total\", float64(props.Total)),\n\t})\n}\n\n// ProposalsResponse is a paginated proposal response\ntype ProposalsResponse struct {\n\tProposals []Proposal `json:\"proposals\"`\n\tTotal uint64 `json:\"total\"`\n}\n\n// Proposal is a single GovDAO proposal\ntype Proposal struct {\n\tAuthor std.Address `json:\"author\"`\n\tDescription string `json:\"description\"`\n\tStatus dao.ProposalStatus `json:\"status\"`\n\tStats dao.Stats `json:\"stats\"`\n\tIsExpired bool `json:\"is_expired\"`\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":"deep","path":"gno.land/r/demo/deep/very/deep","files":[{"name":"render.gno","body":"package deep\n\nfunc Render(path string) string {\n\tif path == \"\" {\n\t\treturn \"it works!\"\n\t} else {\n\t\treturn \"hi \" + path\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)\n\nvar instances avl.Tree // symbol -\u003e instance\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\tbanker := grc20.NewBanker(name, symbol, decimals)\n\tif initialMint \u003e 0 {\n\t\tbanker.Mint(admin, initialMint)\n\t}\n\n\tinst := instance{\n\t\tbanker: banker,\n\t\tadmin: ownable.NewWithAddress(admin),\n\t\tfaucet: faucet,\n\t}\n\n\tinstances.Set(symbol, \u0026inst)\n}\n\ntype instance struct {\n\tbanker *grc20.Banker\n\tadmin *ownable.Ownable\n\tfaucet uint64 // per-request amount. disabled if 0.\n}\n\nfunc (inst instance) Token() grc20.Token { return inst.banker.Token() }\n\nfunc TotalSupply(symbol string) uint64 {\n\tinst := mustGetInstance(symbol)\n\treturn inst.Token().TotalSupply()\n}\n\nfunc BalanceOf(symbol string, owner std.Address) uint64 {\n\tinst := mustGetInstance(symbol)\n\treturn inst.Token().BalanceOf(owner)\n}\n\nfunc Allowance(symbol string, owner, spender std.Address) uint64 {\n\tinst := mustGetInstance(symbol)\n\treturn inst.Token().Allowance(owner, spender)\n}\n\nfunc Transfer(symbol string, to std.Address, amount uint64) {\n\tinst := mustGetInstance(symbol)\n\tcheckErr(inst.Token().Transfer(to, amount))\n}\n\nfunc Approve(symbol string, spender std.Address, amount uint64) {\n\tinst := mustGetInstance(symbol)\n\tcheckErr(inst.Token().Approve(spender, amount))\n}\n\nfunc TransferFrom(symbol string, from, to std.Address, amount uint64) {\n\tinst := mustGetInstance(symbol)\n\tcheckErr(inst.Token().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.banker.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.banker.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.banker.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.banker.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().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)\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/uassert\"\n)\n\nfunc TestReadOnlyPublicMethods(t *testing.T) {\n\tadmin := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\tmanfred := std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\tunknown := std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\") // valid but never used.\n\tNewWithAdmin(\"Foo\", \"FOO\", 4, 10_000*1_000_000, 0, admin)\n\tNewWithAdmin(\"Bar\", \"BAR\", 4, 10_000*1_000, 0, admin)\n\tmustGetInstance(\"FOO\").banker.Mint(manfred, 100_000_000)\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_100_000_000, func() uint64 { return TotalSupply(\"FOO\") }},\n\t\t\t{\"BalanceOf(admin)\", 10_000_000_000, func() uint64 { return BalanceOf(\"FOO\", admin) }},\n\t\t\t{\"BalanceOf(manfred)\", 100_000_000, func() uint64 { return BalanceOf(\"FOO\", manfred) }},\n\t\t\t{\"Allowance(admin, manfred)\", 0, func() uint64 { return Allowance(\"FOO\", admin, manfred) }},\n\t\t\t{\"BalanceOf(unknown)\", 0, func() uint64 { return BalanceOf(\"FOO\", unknown) }},\n\t\t}\n\t\tfor _, tc := range tests {\n\t\t\tuassert.Equal(t, tc.balance, tc.fn(), \"balance does not match\")\n\t\t}\n\t}\n\treturn\n\n\t// unknown uses the faucet.\n\tstd.TestSetOriginCaller(unknown)\n\tFaucet(\"FOO\")\n\n\t// check balances #2.\n\t{\n\t\ttests := []test{\n\t\t\t{\"TotalSupply\", 10_110_000_000, func() uint64 { return TotalSupply(\"FOO\") }},\n\t\t\t{\"BalanceOf(admin)\", 10_000_000_000, func() uint64 { return BalanceOf(\"FOO\", admin) }},\n\t\t\t{\"BalanceOf(manfred)\", 100_000_000, func() uint64 { return BalanceOf(\"FOO\", manfred) }},\n\t\t\t{\"Allowance(admin, manfred)\", 0, func() uint64 { return Allowance(\"FOO\", admin, manfred) }},\n\t\t\t{\"BalanceOf(unknown)\", 10_000_000, func() uint64 { return BalanceOf(\"FOO\", unknown) }},\n\t\t}\n\t\tfor _, tc := range tests {\n\t\t\tuassert.Equal(t, tc.balance, tc.fn(), \"balance does not match\")\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":"disperse","path":"gno.land/r/demo/disperse","files":[{"name":"disperse.gno","body":"package disperse\n\nimport (\n\t\"std\"\n\n\ttokens \"gno.land/r/demo/grc20factory\"\n)\n\n// Get address of Disperse realm\nvar realmAddr = std.CurrentRealm().Addr()\n\n// DisperseUgnot parses receivers and amounts and sends out ugnot\n// The function will send out the coins to the addresses and return the leftover coins to the caller\n// if there are any to return\nfunc DisperseUgnot(addresses []std.Address, coins std.Coins) {\n\tcoinSent := std.OriginSend()\n\tcaller := std.PrevRealm().Addr()\n\tbanker := std.GetBanker(std.BankerTypeOriginSend)\n\n\tif len(addresses) != len(coins) {\n\t\tpanic(ErrNumAddrValMismatch)\n\t}\n\n\tfor _, coin := range coins {\n\t\tif coin.Amount \u003c= 0 {\n\t\t\tpanic(ErrNegativeCoinAmount)\n\t\t}\n\n\t\tif banker.GetCoins(realmAddr).AmountOf(coin.Denom) \u003c coin.Amount {\n\t\t\tpanic(ErrMismatchBetweenSentAndParams)\n\t\t}\n\t}\n\n\t// Send coins\n\tfor i, _ := range addresses {\n\t\tbanker.SendCoins(realmAddr, addresses[i], std.NewCoins(coins[i]))\n\t}\n\n\t// Return possible leftover coins\n\tfor _, coin := range coinSent {\n\t\tleftoverAmt := banker.GetCoins(realmAddr).AmountOf(coin.Denom)\n\t\tif leftoverAmt \u003e 0 {\n\t\t\tsend := std.Coins{std.NewCoin(coin.Denom, leftoverAmt)}\n\t\t\tbanker.SendCoins(realmAddr, caller, send)\n\t\t}\n\t}\n}\n\n// DisperseGRC20 disperses tokens to multiple addresses\n// Note that it is necessary to approve the realm to spend the tokens before calling this function\n// see the corresponding filetests for examples\nfunc DisperseGRC20(addresses []std.Address, amounts []uint64, symbols []string) {\n\tcaller := std.PrevRealm().Addr()\n\n\tif (len(addresses) != len(amounts)) || (len(amounts) != len(symbols)) {\n\t\tpanic(ErrArgLenAndSentLenMismatch)\n\t}\n\n\tfor i := 0; i \u003c len(addresses); i++ {\n\t\ttokens.TransferFrom(symbols[i], caller, addresses[i], amounts[i])\n\t}\n}\n\n// DisperseGRC20String receives a string of addresses and a string of tokens\n// and parses them to be used in DisperseGRC20\nfunc DisperseGRC20String(addresses string, tokens string) {\n\tparsedAddresses, err := parseAddresses(addresses)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tparsedAmounts, parsedSymbols, err := parseTokens(tokens)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tDisperseGRC20(parsedAddresses, parsedAmounts, parsedSymbols)\n}\n\n// DisperseUgnotString receives a string of addresses and a string of amounts\n// and parses them to be used in DisperseUgnot\nfunc DisperseUgnotString(addresses string, amounts string) {\n\tparsedAddresses, err := parseAddresses(addresses)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tparsedAmounts, err := parseAmounts(amounts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tcoins := make(std.Coins, len(parsedAmounts))\n\tfor i, amount := range parsedAmounts {\n\t\tcoins[i] = std.NewCoin(\"ugnot\", amount)\n\t}\n\n\tDisperseUgnot(parsedAddresses, coins)\n}\n"},{"name":"doc.gno","body":"// Package disperse provides methods to disperse coins or GRC20 tokens among multiple addresses.\n//\n// The disperse package is an implementation of an existing service that allows users to send coins or GRC20 tokens to multiple addresses\n// on the Ethereum blockchain.\n//\n// Usage:\n// To use disperse, you can either use `DisperseUgnot` to send coins or `DisperseGRC20` to send GRC20 tokens to multiple addresses.\n//\n// Example:\n// Dispersing 200 coins to two addresses:\n// - DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n// Dispersing 200 worth of a GRC20 token \"TEST\" to two addresses:\n// - DisperseGRC20String(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150TEST,50TEST\")\n//\n// Reference:\n// - [the original dispere app](https://disperse.app/)\n// - [the original disperse app on etherscan](https://etherscan.io/address/0xd152f549545093347a162dce210e7293f1452150#code)\n// - [the gno disperse web app](https://gno-disperse.netlify.app/)\npackage disperse // import \"gno.land/r/demo/disperse\"\n"},{"name":"errors.gno","body":"package disperse\n\nimport \"errors\"\n\nvar (\n\tErrNotEnoughCoin = errors.New(\"disperse: not enough coin sent in\")\n\tErrNumAddrValMismatch = errors.New(\"disperse: number of addresses and values to send doesn't match\")\n\tErrInvalidAddress = errors.New(\"disperse: invalid address\")\n\tErrNegativeCoinAmount = errors.New(\"disperse: coin amount cannot be negative\")\n\tErrMismatchBetweenSentAndParams = errors.New(\"disperse: mismatch between coins sent and params called\")\n\tErrArgLenAndSentLenMismatch = errors.New(\"disperse: mismatch between coins sent and args called\")\n)\n"},{"name":"util.gno","body":"package disperse\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"unicode\"\n)\n\nfunc parseAddresses(addresses string) ([]std.Address, error) {\n\tvar ret []std.Address\n\n\tfor _, str := range strings.Split(addresses, \",\") {\n\t\taddr := std.Address(str)\n\t\tif !addr.IsValid() {\n\t\t\treturn nil, ErrInvalidAddress\n\t\t}\n\n\t\tret = append(ret, addr)\n\t}\n\n\treturn ret, nil\n}\n\nfunc splitString(input string) (string, string) {\n\tvar pos int\n\tfor i, char := range input {\n\t\tif !unicode.IsDigit(char) {\n\t\t\tpos = i\n\t\t\tbreak\n\t\t}\n\t}\n\treturn input[:pos], input[pos:]\n}\n\nfunc parseTokens(tokens string) ([]uint64, []string, error) {\n\tvar amounts []uint64\n\tvar symbols []string\n\n\tfor _, token := range strings.Split(tokens, \",\") {\n\t\tamountStr, symbol := splitString(token)\n\t\tamount, _ := strconv.Atoi(amountStr)\n\t\tif amount \u003c 0 {\n\t\t\treturn nil, nil, ErrNegativeCoinAmount\n\t\t}\n\n\t\tamounts = append(amounts, uint64(amount))\n\t\tsymbols = append(symbols, symbol)\n\t}\n\n\treturn amounts, symbols, nil\n}\n\nfunc parseAmounts(amounts string) ([]int64, error) {\n\tvar ret []int64\n\n\tfor _, amt := range strings.Split(amounts, \",\") {\n\t\tamount, _ := strconv.Atoi(amt)\n\t\tif amount \u003c 0 {\n\t\t\treturn nil, ErrNegativeCoinAmount\n\t\t}\n\n\t\tret = append(ret, int64(amount))\n\t}\n\n\treturn ret, nil\n}\n"},{"name":"z_0_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 200ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\n\tstd.TestSetOrigPkgAddr(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\tbanker.SendCoins(mainaddr, disperseAddr, std.Coins{{\"ugnot\", 200}})\n\tdisperse.DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal)\n}\n\n// Output:\n// main before: 200000200ugnot\n// main after: 200000000ugnot\n"},{"name":"z_1_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\n\tstd.TestSetOrigPkgAddr(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\tbanker.SendCoins(mainaddr, disperseAddr, std.Coins{{\"ugnot\", 300}})\n\tdisperse.DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal)\n}\n\n// Output:\n// main before: 200000300ugnot\n// main after: 200000100ugnot\n"},{"name":"z_2_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\n\tstd.TestSetOrigPkgAddr(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\tbanker.SendCoins(mainaddr, disperseAddr, std.Coins{{\"ugnot\", 100}})\n\tdisperse.DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n}\n\n// Error:\n// disperse: mismatch between coins sent and params called\n"},{"name":"z_3_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n\ttokens \"gno.land/r/demo/grc20factory\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\tbeneficiary1 := std.Address(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0\")\n\tbeneficiary2 := std.Address(\"g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\")\n\n\tstd.TestSetOrigPkgAddr(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\ttokens.New(\"test\", \"TEST\", 4, 0, 0)\n\ttokens.Mint(\"TEST\", mainaddr, 200)\n\n\tmainbal := tokens.BalanceOf(\"TEST\", mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\ttokens.Approve(\"TEST\", disperseAddr, 200)\n\n\tdisperse.DisperseGRC20String(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150TEST,50TEST\")\n\n\tmainbal = tokens.BalanceOf(\"TEST\", mainaddr)\n\tprintln(\"main after:\", mainbal)\n\tben1bal := tokens.BalanceOf(\"TEST\", beneficiary1)\n\tprintln(\"beneficiary1:\", ben1bal)\n\tben2bal := tokens.BalanceOf(\"TEST\", beneficiary2)\n\tprintln(\"beneficiary2:\", ben2bal)\n}\n\n// Output:\n// main before: 200\n// main after: 0\n// beneficiary1: 150\n// beneficiary2: 50\n"},{"name":"z_4_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n\ttokens \"gno.land/r/demo/grc20factory\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\tbeneficiary1 := std.Address(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0\")\n\tbeneficiary2 := std.Address(\"g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\")\n\n\tstd.TestSetOrigPkgAddr(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\ttokens.New(\"test1\", \"TEST1\", 4, 0, 0)\n\ttokens.Mint(\"TEST1\", mainaddr, 200)\n\ttokens.New(\"test2\", \"TEST2\", 4, 0, 0)\n\ttokens.Mint(\"TEST2\", mainaddr, 200)\n\n\tmainbal := tokens.BalanceOf(\"TEST1\", mainaddr) + tokens.BalanceOf(\"TEST2\", mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\ttokens.Approve(\"TEST1\", disperseAddr, 200)\n\ttokens.Approve(\"TEST2\", disperseAddr, 200)\n\n\tdisperse.DisperseGRC20String(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"200TEST1,200TEST2\")\n\n\tmainbal = tokens.BalanceOf(\"TEST1\", mainaddr) + tokens.BalanceOf(\"TEST2\", mainaddr)\n\tprintln(\"main after:\", mainbal)\n\tben1bal := tokens.BalanceOf(\"TEST1\", beneficiary1) + tokens.BalanceOf(\"TEST2\", beneficiary1)\n\tprintln(\"beneficiary1:\", ben1bal)\n\tben2bal := tokens.BalanceOf(\"TEST1\", beneficiary2) + tokens.BalanceOf(\"TEST2\", beneficiary2)\n\tprintln(\"beneficiary2:\", ben2bal)\n}\n\n// Output:\n// main before: 400\n// main after: 0\n// beneficiary1: 200\n// beneficiary2: 200\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)\n\nvar instances avl.Tree // symbol -\u003e instance\n\nfunc New(name, symbol string, decimals uint, initialMint, faucet uint64) {\n\tcaller := std.PreviousRealm().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\tbanker := grc20.NewBanker(name, symbol, decimals)\n\tif initialMint \u003e 0 {\n\t\tbanker.Mint(admin, initialMint)\n\t}\n\n\tinst := instance{\n\t\tbanker: banker,\n\t\tadmin: ownable.NewWithAddress(admin),\n\t\tfaucet: faucet,\n\t}\n\n\tinstances.Set(symbol, \u0026inst)\n}\n\ntype instance struct {\n\tbanker *grc20.Banker\n\tadmin *ownable.Ownable\n\tfaucet uint64 // per-request amount. disabled if 0.\n}\n\nfunc (inst instance) Token() grc20.Token { return inst.banker.Token() }\n\nfunc TotalSupply(symbol string) uint64 {\n\tinst := mustGetInstance(symbol)\n\treturn inst.Token().TotalSupply()\n}\n\nfunc BalanceOf(symbol string, owner std.Address) uint64 {\n\tinst := mustGetInstance(symbol)\n\treturn inst.Token().BalanceOf(owner)\n}\n\nfunc Allowance(symbol string, owner, spender std.Address) uint64 {\n\tinst := mustGetInstance(symbol)\n\treturn inst.Token().Allowance(owner, spender)\n}\n\nfunc Transfer(symbol string, to std.Address, amount uint64) {\n\tinst := mustGetInstance(symbol)\n\tcheckErr(inst.Token().Transfer(to, amount))\n}\n\nfunc Approve(symbol string, spender std.Address, amount uint64) {\n\tinst := mustGetInstance(symbol)\n\tcheckErr(inst.Token().Approve(spender, amount))\n}\n\nfunc TransferFrom(symbol string, from, to std.Address, amount uint64) {\n\tinst := mustGetInstance(symbol)\n\tcheckErr(inst.Token().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.PreviousRealm().Addr()\n\tcheckErr(inst.banker.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.banker.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.banker.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.banker.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().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)\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/uassert\"\n)\n\nfunc TestReadOnlyPublicMethods(t *testing.T) {\n\tadmin := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\tmanfred := std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\tunknown := std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\") // valid but never used.\n\tNewWithAdmin(\"Foo\", \"FOO\", 4, 10_000*1_000_000, 0, admin)\n\tNewWithAdmin(\"Bar\", \"BAR\", 4, 10_000*1_000, 0, admin)\n\tmustGetInstance(\"FOO\").banker.Mint(manfred, 100_000_000)\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_100_000_000, func() uint64 { return TotalSupply(\"FOO\") }},\n\t\t\t{\"BalanceOf(admin)\", 10_000_000_000, func() uint64 { return BalanceOf(\"FOO\", admin) }},\n\t\t\t{\"BalanceOf(manfred)\", 100_000_000, func() uint64 { return BalanceOf(\"FOO\", manfred) }},\n\t\t\t{\"Allowance(admin, manfred)\", 0, func() uint64 { return Allowance(\"FOO\", admin, manfred) }},\n\t\t\t{\"BalanceOf(unknown)\", 0, func() uint64 { return BalanceOf(\"FOO\", unknown) }},\n\t\t}\n\t\tfor _, tc := range tests {\n\t\t\tuassert.Equal(t, tc.balance, tc.fn(), \"balance does not match\")\n\t\t}\n\t}\n\treturn\n\n\t// unknown uses the faucet.\n\tstd.TestSetOriginCaller(unknown)\n\tFaucet(\"FOO\")\n\n\t// check balances #2.\n\t{\n\t\ttests := []test{\n\t\t\t{\"TotalSupply\", 10_110_000_000, func() uint64 { return TotalSupply(\"FOO\") }},\n\t\t\t{\"BalanceOf(admin)\", 10_000_000_000, func() uint64 { return BalanceOf(\"FOO\", admin) }},\n\t\t\t{\"BalanceOf(manfred)\", 100_000_000, func() uint64 { return BalanceOf(\"FOO\", manfred) }},\n\t\t\t{\"Allowance(admin, manfred)\", 0, func() uint64 { return Allowance(\"FOO\", admin, manfred) }},\n\t\t\t{\"BalanceOf(unknown)\", 10_000_000, func() uint64 { return BalanceOf(\"FOO\", unknown) }},\n\t\t}\n\t\tfor _, tc := range tests {\n\t\t\tuassert.Equal(t, tc.balance, tc.fn(), \"balance does not match\")\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":"disperse","path":"gno.land/r/demo/disperse","files":[{"name":"disperse.gno","body":"package disperse\n\nimport (\n\t\"std\"\n\n\ttokens \"gno.land/r/demo/grc20factory\"\n)\n\n// Get address of Disperse realm\nvar realmAddr = std.CurrentRealm().Addr()\n\n// DisperseUgnot parses receivers and amounts and sends out ugnot\n// The function will send out the coins to the addresses and return the leftover coins to the caller\n// if there are any to return\nfunc DisperseUgnot(addresses []std.Address, coins std.Coins) {\n\tcoinSent := std.OriginSend()\n\tcaller := std.PreviousRealm().Addr()\n\tbanker := std.GetBanker(std.BankerTypeOriginSend)\n\n\tif len(addresses) != len(coins) {\n\t\tpanic(ErrNumAddrValMismatch)\n\t}\n\n\tfor _, coin := range coins {\n\t\tif coin.Amount \u003c= 0 {\n\t\t\tpanic(ErrNegativeCoinAmount)\n\t\t}\n\n\t\tif banker.GetCoins(realmAddr).AmountOf(coin.Denom) \u003c coin.Amount {\n\t\t\tpanic(ErrMismatchBetweenSentAndParams)\n\t\t}\n\t}\n\n\t// Send coins\n\tfor i, _ := range addresses {\n\t\tbanker.SendCoins(realmAddr, addresses[i], std.NewCoins(coins[i]))\n\t}\n\n\t// Return possible leftover coins\n\tfor _, coin := range coinSent {\n\t\tleftoverAmt := banker.GetCoins(realmAddr).AmountOf(coin.Denom)\n\t\tif leftoverAmt \u003e 0 {\n\t\t\tsend := std.Coins{std.NewCoin(coin.Denom, leftoverAmt)}\n\t\t\tbanker.SendCoins(realmAddr, caller, send)\n\t\t}\n\t}\n}\n\n// DisperseGRC20 disperses tokens to multiple addresses\n// Note that it is necessary to approve the realm to spend the tokens before calling this function\n// see the corresponding filetests for examples\nfunc DisperseGRC20(addresses []std.Address, amounts []uint64, symbols []string) {\n\tcaller := std.PreviousRealm().Addr()\n\n\tif (len(addresses) != len(amounts)) || (len(amounts) != len(symbols)) {\n\t\tpanic(ErrArgLenAndSentLenMismatch)\n\t}\n\n\tfor i := 0; i \u003c len(addresses); i++ {\n\t\ttokens.TransferFrom(symbols[i], caller, addresses[i], amounts[i])\n\t}\n}\n\n// DisperseGRC20String receives a string of addresses and a string of tokens\n// and parses them to be used in DisperseGRC20\nfunc DisperseGRC20String(addresses string, tokens string) {\n\tparsedAddresses, err := parseAddresses(addresses)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tparsedAmounts, parsedSymbols, err := parseTokens(tokens)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tDisperseGRC20(parsedAddresses, parsedAmounts, parsedSymbols)\n}\n\n// DisperseUgnotString receives a string of addresses and a string of amounts\n// and parses them to be used in DisperseUgnot\nfunc DisperseUgnotString(addresses string, amounts string) {\n\tparsedAddresses, err := parseAddresses(addresses)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tparsedAmounts, err := parseAmounts(amounts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tcoins := make(std.Coins, len(parsedAmounts))\n\tfor i, amount := range parsedAmounts {\n\t\tcoins[i] = std.NewCoin(\"ugnot\", amount)\n\t}\n\n\tDisperseUgnot(parsedAddresses, coins)\n}\n"},{"name":"doc.gno","body":"// Package disperse provides methods to disperse coins or GRC20 tokens among multiple addresses.\n//\n// The disperse package is an implementation of an existing service that allows users to send coins or GRC20 tokens to multiple addresses\n// on the Ethereum blockchain.\n//\n// Usage:\n// To use disperse, you can either use `DisperseUgnot` to send coins or `DisperseGRC20` to send GRC20 tokens to multiple addresses.\n//\n// Example:\n// Dispersing 200 coins to two addresses:\n// - DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n// Dispersing 200 worth of a GRC20 token \"TEST\" to two addresses:\n// - DisperseGRC20String(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150TEST,50TEST\")\n//\n// Reference:\n// - [the original dispere app](https://disperse.app/)\n// - [the original disperse app on etherscan](https://etherscan.io/address/0xd152f549545093347a162dce210e7293f1452150#code)\n// - [the gno disperse web app](https://gno-disperse.netlify.app/)\npackage disperse // import \"gno.land/r/demo/disperse\"\n"},{"name":"errors.gno","body":"package disperse\n\nimport \"errors\"\n\nvar (\n\tErrNotEnoughCoin = errors.New(\"disperse: not enough coin sent in\")\n\tErrNumAddrValMismatch = errors.New(\"disperse: number of addresses and values to send doesn't match\")\n\tErrInvalidAddress = errors.New(\"disperse: invalid address\")\n\tErrNegativeCoinAmount = errors.New(\"disperse: coin amount cannot be negative\")\n\tErrMismatchBetweenSentAndParams = errors.New(\"disperse: mismatch between coins sent and params called\")\n\tErrArgLenAndSentLenMismatch = errors.New(\"disperse: mismatch between coins sent and args called\")\n)\n"},{"name":"util.gno","body":"package disperse\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"unicode\"\n)\n\nfunc parseAddresses(addresses string) ([]std.Address, error) {\n\tvar ret []std.Address\n\n\tfor _, str := range strings.Split(addresses, \",\") {\n\t\taddr := std.Address(str)\n\t\tif !addr.IsValid() {\n\t\t\treturn nil, ErrInvalidAddress\n\t\t}\n\n\t\tret = append(ret, addr)\n\t}\n\n\treturn ret, nil\n}\n\nfunc splitString(input string) (string, string) {\n\tvar pos int\n\tfor i, char := range input {\n\t\tif !unicode.IsDigit(char) {\n\t\t\tpos = i\n\t\t\tbreak\n\t\t}\n\t}\n\treturn input[:pos], input[pos:]\n}\n\nfunc parseTokens(tokens string) ([]uint64, []string, error) {\n\tvar amounts []uint64\n\tvar symbols []string\n\n\tfor _, token := range strings.Split(tokens, \",\") {\n\t\tamountStr, symbol := splitString(token)\n\t\tamount, _ := strconv.Atoi(amountStr)\n\t\tif amount \u003c 0 {\n\t\t\treturn nil, nil, ErrNegativeCoinAmount\n\t\t}\n\n\t\tamounts = append(amounts, uint64(amount))\n\t\tsymbols = append(symbols, symbol)\n\t}\n\n\treturn amounts, symbols, nil\n}\n\nfunc parseAmounts(amounts string) ([]int64, error) {\n\tvar ret []int64\n\n\tfor _, amt := range strings.Split(amounts, \",\") {\n\t\tamount, _ := strconv.Atoi(amt)\n\t\tif amount \u003c 0 {\n\t\t\treturn nil, ErrNegativeCoinAmount\n\t\t}\n\n\t\tret = append(ret, int64(amount))\n\t}\n\n\treturn ret, nil\n}\n"},{"name":"z_0_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 200ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\n\tstd.TestSetOrigPkgAddr(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\tbanker.SendCoins(mainaddr, disperseAddr, std.Coins{{\"ugnot\", 200}})\n\tdisperse.DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal)\n}\n\n// Output:\n// main before: 200000200ugnot\n// main after: 200000000ugnot\n"},{"name":"z_1_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\n\tstd.TestSetOrigPkgAddr(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\tbanker.SendCoins(mainaddr, disperseAddr, std.Coins{{\"ugnot\", 300}})\n\tdisperse.DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal)\n}\n\n// Output:\n// main before: 200000300ugnot\n// main after: 200000100ugnot\n"},{"name":"z_2_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\n\tstd.TestSetOrigPkgAddr(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\tbanker.SendCoins(mainaddr, disperseAddr, std.Coins{{\"ugnot\", 100}})\n\tdisperse.DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n}\n\n// Error:\n// disperse: mismatch between coins sent and params called\n"},{"name":"z_3_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n\ttokens \"gno.land/r/demo/grc20factory\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\tbeneficiary1 := std.Address(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0\")\n\tbeneficiary2 := std.Address(\"g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\")\n\n\tstd.TestSetOrigPkgAddr(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\ttokens.New(\"test\", \"TEST\", 4, 0, 0)\n\ttokens.Mint(\"TEST\", mainaddr, 200)\n\n\tmainbal := tokens.BalanceOf(\"TEST\", mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\ttokens.Approve(\"TEST\", disperseAddr, 200)\n\n\tdisperse.DisperseGRC20String(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150TEST,50TEST\")\n\n\tmainbal = tokens.BalanceOf(\"TEST\", mainaddr)\n\tprintln(\"main after:\", mainbal)\n\tben1bal := tokens.BalanceOf(\"TEST\", beneficiary1)\n\tprintln(\"beneficiary1:\", ben1bal)\n\tben2bal := tokens.BalanceOf(\"TEST\", beneficiary2)\n\tprintln(\"beneficiary2:\", ben2bal)\n}\n\n// Output:\n// main before: 200\n// main after: 0\n// beneficiary1: 150\n// beneficiary2: 50\n"},{"name":"z_4_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n\ttokens \"gno.land/r/demo/grc20factory\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\tbeneficiary1 := std.Address(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0\")\n\tbeneficiary2 := std.Address(\"g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\")\n\n\tstd.TestSetOrigPkgAddr(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\ttokens.New(\"test1\", \"TEST1\", 4, 0, 0)\n\ttokens.Mint(\"TEST1\", mainaddr, 200)\n\ttokens.New(\"test2\", \"TEST2\", 4, 0, 0)\n\ttokens.Mint(\"TEST2\", mainaddr, 200)\n\n\tmainbal := tokens.BalanceOf(\"TEST1\", mainaddr) + tokens.BalanceOf(\"TEST2\", mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\ttokens.Approve(\"TEST1\", disperseAddr, 200)\n\ttokens.Approve(\"TEST2\", disperseAddr, 200)\n\n\tdisperse.DisperseGRC20String(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"200TEST1,200TEST2\")\n\n\tmainbal = tokens.BalanceOf(\"TEST1\", mainaddr) + tokens.BalanceOf(\"TEST2\", mainaddr)\n\tprintln(\"main after:\", mainbal)\n\tben1bal := tokens.BalanceOf(\"TEST1\", beneficiary1) + tokens.BalanceOf(\"TEST2\", beneficiary1)\n\tprintln(\"beneficiary1:\", ben1bal)\n\tben2bal := tokens.BalanceOf(\"TEST1\", beneficiary2) + tokens.BalanceOf(\"TEST2\", beneficiary2)\n\tprintln(\"beneficiary2:\", ben2bal)\n}\n\n// Output:\n// main before: 400\n// main after: 0\n// beneficiary1: 200\n// beneficiary2: 200\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":"echo","path":"gno.land/r/demo/echo","files":[{"name":"echo.gno","body":"package echo\n\n/*\n * This realm echoes the `path` argument it received.\n * Can be used by developers as a simple endpoint to test\n * forbidden characters, for pentesting or simply to\n * test it works.\n *\n * See also r/demo/print (to print various thing like user address)\n */\nfunc Render(path string) string {\n\treturn path\n}\n"},{"name":"echo_test.gno","body":"package echo\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc Test(t *testing.T) {\n\turequire.Equal(t, \"aa\", Render(\"aa\"))\n\turequire.Equal(t, \"\", Render(\"\"))\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":"event","path":"gno.land/r/demo/event","files":[{"name":"event.gno","body":"package event\n\nimport (\n\t\"std\"\n)\n\nfunc Emit(value string) {\n\tstd.Emit(\"TAG\", \"key\", value)\n}\n"},{"name":"z1_filetest.gno","body":"package main\n\nimport \"gno.land/r/demo/event\"\n\nfunc main() {\n\tevent.Emit(\"foo\")\n\tevent.Emit(\"bar\")\n}\n\n// Events:\n// [\n// {\n// \"type\": \"TAG\",\n// \"attrs\": [\n// {\n// \"key\": \"key\",\n// \"value\": \"foo\"\n// }\n// ],\n// \"pkg_path\": \"gno.land/r/demo/event\",\n// \"func\": \"Emit\"\n// },\n// {\n// \"type\": \"TAG\",\n// \"attrs\": [\n// {\n// \"key\": \"key\",\n// \"value\": \"bar\"\n// }\n// ],\n// \"pkg_path\": \"gno.land/r/demo/event\",\n// \"func\": \"Emit\"\n// }\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":"foo1155","path":"gno.land/r/demo/foo1155","files":[{"name":"foo1155.gno","body":"package foo1155\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/grc/grc1155\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/r/demo/users\"\n\n\tpusers \"gno.land/p/demo/users\"\n)\n\nvar (\n\tdummyURI = \"ipfs://xyz\"\n\tadmin std.Address = \"g10x5phu0k6p64cwrhfpsc8tk43st9kug6wft530\"\n\tfoo = grc1155.NewBasicGRC1155Token(dummyURI)\n)\n\nfunc init() {\n\tmintGRC1155Token(admin) // @administrator (10)\n}\n\nfunc mintGRC1155Token(owner std.Address) {\n\tfor i := 1; i \u003c= 10; i++ {\n\t\ttid := grc1155.TokenID(ufmt.Sprintf(\"%d\", i))\n\t\tfoo.SafeMint(owner, tid, 100)\n\t}\n}\n\n// Getters\n\nfunc BalanceOf(user pusers.AddressOrName, tid grc1155.TokenID) uint64 {\n\tbalance, err := foo.BalanceOf(users.Resolve(user), tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn balance\n}\n\nfunc BalanceOfBatch(ul []pusers.AddressOrName, batch []grc1155.TokenID) []uint64 {\n\tvar usersResolved []std.Address\n\n\tfor i := 0; i \u003c len(ul); i++ {\n\t\tusersResolved[i] = users.Resolve(ul[i])\n\t}\n\tbalanceBatch, err := foo.BalanceOfBatch(usersResolved, batch)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn balanceBatch\n}\n\nfunc IsApprovedForAll(owner, user pusers.AddressOrName) bool {\n\treturn foo.IsApprovedForAll(users.Resolve(owner), users.Resolve(user))\n}\n\n// Setters\n\nfunc SetApprovalForAll(user pusers.AddressOrName, approved bool) {\n\terr := foo.SetApprovalForAll(users.Resolve(user), approved)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc TransferFrom(from, to pusers.AddressOrName, tid grc1155.TokenID, amount uint64) {\n\terr := foo.SafeTransferFrom(users.Resolve(from), users.Resolve(to), tid, amount)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc BatchTransferFrom(from, to pusers.AddressOrName, batch []grc1155.TokenID, amounts []uint64) {\n\terr := foo.SafeBatchTransferFrom(users.Resolve(from), users.Resolve(to), batch, amounts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// Admin\n\nfunc Mint(to pusers.AddressOrName, tid grc1155.TokenID, amount uint64) {\n\tcaller := std.OriginCaller()\n\tassertIsAdmin(caller)\n\terr := foo.SafeMint(users.Resolve(to), tid, amount)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc MintBatch(to pusers.AddressOrName, batch []grc1155.TokenID, amounts []uint64) {\n\tcaller := std.OriginCaller()\n\tassertIsAdmin(caller)\n\terr := foo.SafeBatchMint(users.Resolve(to), batch, amounts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc Burn(from pusers.AddressOrName, tid grc1155.TokenID, amount uint64) {\n\tcaller := std.OriginCaller()\n\tassertIsAdmin(caller)\n\terr := foo.Burn(users.Resolve(from), tid, amount)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc BurnBatch(from pusers.AddressOrName, batch []grc1155.TokenID, amounts []uint64) {\n\tcaller := std.OriginCaller()\n\tassertIsAdmin(caller)\n\terr := foo.BatchBurn(users.Resolve(from), batch, amounts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// Render\n\nfunc Render(path string) string {\n\tswitch {\n\tcase path == \"\":\n\t\treturn foo.RenderHome()\n\tdefault:\n\t\treturn \"404\\n\"\n\t}\n}\n\n// Util\n\nfunc assertIsAdmin(address std.Address) {\n\tif address != admin {\n\t\tpanic(\"restricted access\")\n\t}\n}\n"},{"name":"foo1155_test.gno","body":"package foo1155\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/grc/grc1155\"\n\t\"gno.land/p/demo/users\"\n)\n\nfunc TestFoo721(t *testing.T) {\n\tadmin := users.AddressOrName(\"g10x5phu0k6p64cwrhfpsc8tk43st9kug6wft530\")\n\tbob := users.AddressOrName(\"g1ze6et22ces5atv79y4xh38s4kuraey4y2fr6tw\")\n\ttid1 := grc1155.TokenID(\"1\")\n\ttid2 := grc1155.TokenID(\"2\")\n\n\tfor i, tc := range []struct {\n\t\tname string\n\t\texpected interface{}\n\t\tfn func() interface{}\n\t}{\n\t\t{\"BalanceOf(admin, tid1)\", uint64(100), func() interface{} { return BalanceOf(admin, tid1) }},\n\t\t{\"BalanceOf(bob, tid1)\", uint64(0), func() interface{} { return BalanceOf(bob, tid1) }},\n\t\t{\"IsApprovedForAll(admin, bob)\", false, func() interface{} { return IsApprovedForAll(admin, bob) }},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := tc.fn()\n\t\t\tif tc.expected != got {\n\t\t\t\tt.Errorf(\"expected: %v got: %v\", tc.expected, got)\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/foo20","files":[{"name":"foo20.gno","body":"// foo20 is a GRC20 token contract where all the GRC20 methods are proxified\n// 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/users\"\n)\n\nvar (\n\tbanker *grc20.Banker\n\tadmin *ownable.Ownable\n\ttoken grc20.Token\n)\n\nfunc init() {\n\tadmin = ownable.NewWithAddress(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\") // @manfred\n\tbanker = grc20.NewBanker(\"Foo\", \"FOO\", 4)\n\tbanker.Mint(admin.Owner(), 1000000*10000) // @administrator (1M)\n\ttoken = banker.Token()\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\tcheckErr(token.Transfer(toAddr, amount))\n}\n\nfunc Approve(spender pusers.AddressOrName, amount uint64) {\n\tspenderAddr := users.Resolve(spender)\n\tcheckErr(token.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(token.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(banker.Mint(caller, amount))\n}\n\nfunc Mint(to pusers.AddressOrName, amount uint64) {\n\tadmin.AssertCallerIsOwner()\n\ttoAddr := users.Resolve(to)\n\tcheckErr(banker.Mint(toAddr, amount))\n}\n\nfunc Burn(from pusers.AddressOrName, amount uint64) {\n\tadmin.AssertCallerIsOwner()\n\tfromAddr := users.Resolve(from)\n\tcheckErr(banker.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 banker.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 := banker.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(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\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.TestSetOriginCaller(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(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\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\tstd.TestSetOriginCaller(users.Resolve(admin))\n\t{\n\t\ttests := []test{\n\t\t\t{\"Transfer(admin, 1)\", \"cannot send transfer to self\", func() { Transfer(admin, 1) }},\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":"foo721","path":"gno.land/r/demo/foo721","files":[{"name":"foo721.gno","body":"package foo721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/grc/grc721\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/r/demo/users\"\n\n\tpusers \"gno.land/p/demo/users\"\n)\n\nvar (\n\tadmin std.Address = \"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\"\n\tfoo = grc721.NewBasicNFT(\"FooNFT\", \"FNFT\")\n)\n\nfunc init() {\n\tmintNNFT(admin, 10) // @administrator (10)\n\tmintNNFT(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\", 5) // @hariom (5)\n}\n\nfunc mintNNFT(owner std.Address, n uint64) {\n\tcount := foo.TokenCount()\n\tfor i := count; i \u003c count+n; i++ {\n\t\ttid := grc721.TokenID(ufmt.Sprintf(\"%d\", i))\n\t\tfoo.Mint(owner, tid)\n\t}\n}\n\n// Getters\n\nfunc BalanceOf(user pusers.AddressOrName) uint64 {\n\tbalance, err := foo.BalanceOf(users.Resolve(user))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn balance\n}\n\nfunc OwnerOf(tid grc721.TokenID) std.Address {\n\towner, err := foo.OwnerOf(tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn owner\n}\n\nfunc IsApprovedForAll(owner, user pusers.AddressOrName) bool {\n\treturn foo.IsApprovedForAll(users.Resolve(owner), users.Resolve(user))\n}\n\nfunc GetApproved(tid grc721.TokenID) std.Address {\n\taddr, err := foo.GetApproved(tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn addr\n}\n\n// Setters\n\nfunc Approve(user pusers.AddressOrName, tid grc721.TokenID) {\n\terr := foo.Approve(users.Resolve(user), tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc SetApprovalForAll(user pusers.AddressOrName, approved bool) {\n\terr := foo.SetApprovalForAll(users.Resolve(user), approved)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc TransferFrom(from, to pusers.AddressOrName, tid grc721.TokenID) {\n\terr := foo.TransferFrom(users.Resolve(from), users.Resolve(to), tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// Admin\n\nfunc Mint(to pusers.AddressOrName, tid grc721.TokenID) {\n\tcaller := std.PrevRealm().Addr()\n\tassertIsAdmin(caller)\n\terr := foo.Mint(users.Resolve(to), tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc Burn(tid grc721.TokenID) {\n\tcaller := std.PrevRealm().Addr()\n\tassertIsAdmin(caller)\n\terr := foo.Burn(tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// Render\n\nfunc Render(path string) string {\n\tswitch {\n\tcase path == \"\":\n\t\treturn foo.RenderHome()\n\tdefault:\n\t\treturn \"404\\n\"\n\t}\n}\n\n// Util\n\nfunc assertIsAdmin(address std.Address) {\n\tif address != admin {\n\t\tpanic(\"restricted access\")\n\t}\n}\n"},{"name":"foo721_test.gno","body":"package foo721\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/grc/grc721\"\n\t\"gno.land/r/demo/users\"\n\n\tpusers \"gno.land/p/demo/users\"\n)\n\nfunc TestFoo721(t *testing.T) {\n\tadmin := pusers.AddressOrName(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\thariom := pusers.AddressOrName(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tfor i, tc := range []struct {\n\t\tname string\n\t\texpected interface{}\n\t\tfn func() interface{}\n\t}{\n\t\t{\"BalanceOf(admin)\", uint64(10), func() interface{} { return BalanceOf(admin) }},\n\t\t{\"BalanceOf(hariom)\", uint64(5), func() interface{} { return BalanceOf(hariom) }},\n\t\t{\"OwnerOf(0)\", users.Resolve(admin), func() interface{} { return OwnerOf(grc721.TokenID(\"0\")) }},\n\t\t{\"IsApprovedForAll(admin, hariom)\", false, func() interface{} { return IsApprovedForAll(admin, hariom) }},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := tc.fn()\n\t\t\tif tc.expected != got {\n\t\t\t\tt.Errorf(\"expected: %v got: %v\", tc.expected, got)\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":"dice_roller","path":"gno.land/r/demo/games/dice_roller","files":[{"name":"dice_roller.gno","body":"package dice_roller\n\nimport (\n\t\"errors\"\n\t\"math/rand\"\n\t\"sort\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/entropy\"\n\t\"gno.land/p/demo/seqid\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/r/demo/users\"\n)\n\ntype (\n\t// game represents a Dice Roller game between two players\n\tgame struct {\n\t\tplayer1, player2 std.Address\n\t\troll1, roll2 int\n\t}\n\n\t// player holds the information about each player including their stats\n\tplayer struct {\n\t\taddr std.Address\n\t\twins, losses, draws, points int\n\t}\n\n\t// leaderBoard is a slice of players, used to sort players by rank\n\tleaderBoard []player\n)\n\nconst (\n\t// Constants to represent game result outcomes\n\tongoing = iota\n\twin\n\tdraw\n\tloss\n)\n\nvar (\n\tgames avl.Tree // AVL tree for storing game states\n\tgameId seqid.ID // Sequence ID for games\n\n\tplayers avl.Tree // AVL tree for storing player data\n\n\tseed = uint64(entropy.New().Seed())\n\tr = rand.New(rand.NewPCG(seed, 0xdeadbeef))\n)\n\n// rollDice generates a random dice roll between 1 and 6\nfunc rollDice() int {\n\treturn r.IntN(6) + 1\n}\n\n// NewGame initializes a new game with the provided opponent's address\nfunc NewGame(addr std.Address) int {\n\tif !addr.IsValid() {\n\t\tpanic(\"invalid opponent's address\")\n\t}\n\n\tgames.Set(gameId.Next().String(), \u0026game{\n\t\tplayer1: std.PrevRealm().Addr(),\n\t\tplayer2: addr,\n\t})\n\n\treturn int(gameId)\n}\n\n// Play allows a player to roll the dice and updates the game state accordingly\nfunc Play(idx int) int {\n\tg, err := getGame(idx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\troll := rollDice() // Random the player's dice roll\n\n\t// Play the game and update the player's roll\n\tif err := g.play(std.PrevRealm().Addr(), roll); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// If both players have rolled, update the results and leaderboard\n\tif g.isFinished() {\n\t\t// If the player is playing against themselves, no points are awarded\n\t\tif g.player1 == g.player2 {\n\t\t\treturn roll\n\t\t}\n\n\t\tplayer1 := getPlayer(g.player1)\n\t\tplayer2 := getPlayer(g.player2)\n\n\t\tif g.roll1 \u003e g.roll2 {\n\t\t\tplayer1.updateStats(win)\n\t\t\tplayer2.updateStats(loss)\n\t\t} else if g.roll2 \u003e g.roll1 {\n\t\t\tplayer2.updateStats(win)\n\t\t\tplayer1.updateStats(loss)\n\t\t} else {\n\t\t\tplayer1.updateStats(draw)\n\t\t\tplayer2.updateStats(draw)\n\t\t}\n\t}\n\n\treturn roll\n}\n\n// play processes a player's roll and updates their score\nfunc (g *game) play(player std.Address, roll int) error {\n\tif player != g.player1 \u0026\u0026 player != g.player2 {\n\t\treturn errors.New(\"invalid player\")\n\t}\n\n\tif g.isFinished() {\n\t\treturn errors.New(\"game over\")\n\t}\n\n\tif player == g.player1 \u0026\u0026 g.roll1 == 0 {\n\t\tg.roll1 = roll\n\t\treturn nil\n\t}\n\n\tif player == g.player2 \u0026\u0026 g.roll2 == 0 {\n\t\tg.roll2 = roll\n\t\treturn nil\n\t}\n\n\treturn errors.New(\"already played\")\n}\n\n// isFinished checks if the game has ended\nfunc (g *game) isFinished() bool {\n\treturn g.roll1 != 0 \u0026\u0026 g.roll2 != 0\n}\n\n// checkResult returns the game status as a formatted string\nfunc (g *game) status() string {\n\tif !g.isFinished() {\n\t\treturn resultIcon(ongoing) + \" Game still in progress\"\n\t}\n\n\tif g.roll1 \u003e g.roll2 {\n\t\treturn resultIcon(win) + \" Player1 Wins !\"\n\t} else if g.roll2 \u003e g.roll1 {\n\t\treturn resultIcon(win) + \" Player2 Wins !\"\n\t} else {\n\t\treturn resultIcon(draw) + \" It's a Draw !\"\n\t}\n}\n\n// Render provides a summary of the current state of games and leader board\nfunc Render(path string) string {\n\tvar sb strings.Builder\n\n\tsb.WriteString(`# 🎲 **Dice Roller Game**\n\nWelcome to Dice Roller! Challenge your friends to a simple yet exciting dice rolling game. Roll the dice and see who gets the highest score !\n\n---\n\n## **How to Play**:\n1. **Create a game**: Challenge an opponent using [NewGame](./dice_roller$help\u0026func=NewGame)\n2. **Roll the dice**: Play your turn by rolling a dice using [Play](./dice_roller$help\u0026func=Play)\n\n---\n\n## **Scoring Rules**:\n- **Win** 🏆: +3 points\n- **Draw** 🤝: +1 point each\n- **Lose** ❌: No points\n- **Playing against yourself**: No points or stats changes for you\n\n---\n\n## **Recent Games**:\nBelow are the results from the most recent games. Up to 10 recent games are displayed\n\n| Game | Player 1 | 🎲 Roll 1 | Player 2 | 🎲 Roll 2 | 🏆 Winner |\n|------|----------|-----------|----------|-----------|-----------|\n`)\n\n\tmaxGames := 10\n\tfor n := int(gameId); n \u003e 0 \u0026\u0026 int(gameId)-n \u003c maxGames; n-- {\n\t\tg, err := getGame(n)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tsb.WriteString(strconv.Itoa(n) + \" | \" +\n\t\t\t\"\u003cspan title=\\\"\" + string(g.player1) + \"\\\"\u003e\" + shortName(g.player1) + \"\u003c/span\u003e\" + \" | \" + diceIcon(g.roll1) + \" | \" +\n\t\t\t\"\u003cspan title=\\\"\" + string(g.player2) + \"\\\"\u003e\" + shortName(g.player2) + \"\u003c/span\u003e\" + \" | \" + diceIcon(g.roll2) + \" | \" +\n\t\t\tg.status() + \"\\n\")\n\t}\n\n\tsb.WriteString(`\n---\n\n## **Leaderboard**:\nThe top players are ranked by performance. Games played against oneself are not counted in the leaderboard\n\n| Rank | Player | Wins | Losses | Draws | Points |\n|------|-----------------------|------|--------|-------|--------|\n`)\n\n\tfor i, player := range getLeaderBoard() {\n\t\tsb.WriteString(ufmt.Sprintf(\"| %s | \u003cspan title=\\\"\"+string(player.addr)+\"\\\"\u003e**%s**\u003c/span\u003e | %d | %d | %d | %d |\\n\",\n\t\t\trankIcon(i+1),\n\t\t\tshortName(player.addr),\n\t\t\tplayer.wins,\n\t\t\tplayer.losses,\n\t\t\tplayer.draws,\n\t\t\tplayer.points,\n\t\t))\n\t}\n\n\tsb.WriteString(\"\\n---\\n**Good luck and have fun !** 🎉\")\n\treturn sb.String()\n}\n\n// shortName returns a shortened name for the given address\nfunc shortName(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user != nil {\n\t\treturn user.Name\n\t}\n\tif len(addr) \u003c 10 {\n\t\treturn string(addr)\n\t}\n\treturn string(addr)[:10] + \"...\"\n}\n\n// getGame retrieves the game state by its ID\nfunc getGame(idx int) (*game, error) {\n\tv, ok := games.Get(seqid.ID(idx).String())\n\tif !ok {\n\t\treturn nil, errors.New(\"game not found\")\n\t}\n\treturn v.(*game), nil\n}\n\n// updateResult updates the player's stats and points based on the game outcome\nfunc (p *player) updateStats(result int) {\n\tswitch result {\n\tcase win:\n\t\tp.wins++\n\t\tp.points += 3\n\tcase loss:\n\t\tp.losses++\n\tcase draw:\n\t\tp.draws++\n\t\tp.points++\n\t}\n}\n\n// getPlayer retrieves a player or initializes a new one if they don't exist\nfunc getPlayer(addr std.Address) *player {\n\tv, ok := players.Get(addr.String())\n\tif !ok {\n\t\tplayer := \u0026player{\n\t\t\taddr: addr,\n\t\t}\n\t\tplayers.Set(addr.String(), player)\n\t\treturn player\n\t}\n\n\treturn v.(*player)\n}\n\n// getLeaderBoard generates a leaderboard sorted by points\nfunc getLeaderBoard() leaderBoard {\n\tboard := leaderBoard{}\n\tplayers.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tplayer := value.(*player)\n\t\tboard = append(board, *player)\n\t\treturn false\n\t})\n\n\tsort.Sort(board)\n\n\treturn board\n}\n\n// Methods for sorting the leaderboard\nfunc (r leaderBoard) Len() int {\n\treturn len(r)\n}\n\nfunc (r leaderBoard) Less(i, j int) bool {\n\tif r[i].points != r[j].points {\n\t\treturn r[i].points \u003e r[j].points\n\t}\n\n\tif r[i].wins != r[j].wins {\n\t\treturn r[i].wins \u003e r[j].wins\n\t}\n\n\tif r[i].draws != r[j].draws {\n\t\treturn r[i].draws \u003e r[j].draws\n\t}\n\n\treturn false\n}\n\nfunc (r leaderBoard) Swap(i, j int) {\n\tr[i], r[j] = r[j], r[i]\n}\n"},{"name":"dice_roller_test.gno","body":"package dice_roller\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/seqid\"\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nvar (\n\tplayer1 = testutils.TestAddress(\"alice\")\n\tplayer2 = testutils.TestAddress(\"bob\")\n\tunknownPlayer = testutils.TestAddress(\"unknown\")\n)\n\n// resetGameState resets the game state for testing\nfunc resetGameState() {\n\tgames = avl.Tree{}\n\tgameId = seqid.ID(0)\n\tplayers = avl.Tree{}\n}\n\n// TestNewGame tests the initialization of a new game\nfunc TestNewGame(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOriginCaller(player1)\n\tgameID := NewGame(player2)\n\n\t// Verify that the game has been correctly initialized\n\tg, err := getGame(gameID)\n\turequire.NoError(t, err)\n\turequire.Equal(t, player1.String(), g.player1.String())\n\turequire.Equal(t, player2.String(), g.player2.String())\n\turequire.Equal(t, 0, g.roll1)\n\turequire.Equal(t, 0, g.roll2)\n}\n\n// TestPlay tests the dice rolling functionality for both players\nfunc TestPlay(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOriginCaller(player1)\n\tgameID := NewGame(player2)\n\n\tg, err := getGame(gameID)\n\turequire.NoError(t, err)\n\n\t// Simulate rolling dice for player 1\n\troll1 := Play(gameID)\n\n\t// Verify player 1's roll\n\turequire.NotEqual(t, 0, g.roll1)\n\turequire.Equal(t, g.roll1, roll1)\n\turequire.Equal(t, 0, g.roll2) // Player 2 hasn't rolled yet\n\n\t// Simulate rolling dice for player 2\n\tstd.TestSetOriginCaller(player2)\n\troll2 := Play(gameID)\n\n\t// Verify player 2's roll\n\turequire.NotEqual(t, 0, g.roll2)\n\turequire.Equal(t, g.roll1, roll1)\n\turequire.Equal(t, g.roll2, roll2)\n}\n\n// TestPlayAgainstSelf tests the scenario where a player plays against themselves\nfunc TestPlayAgainstSelf(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOriginCaller(player1)\n\tgameID := NewGame(player1)\n\n\t// Simulate rolling dice twice by the same player\n\troll1 := Play(gameID)\n\troll2 := Play(gameID)\n\n\tg, err := getGame(gameID)\n\turequire.NoError(t, err)\n\turequire.Equal(t, g.roll1, roll1)\n\turequire.Equal(t, g.roll2, roll2)\n}\n\n// TestPlayInvalidPlayer tests the scenario where an invalid player tries to play\nfunc TestPlayInvalidPlayer(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOriginCaller(player1)\n\tgameID := NewGame(player1)\n\n\t// Attempt to play as an invalid player\n\tstd.TestSetOriginCaller(unknownPlayer)\n\turequire.PanicsWithMessage(t, \"invalid player\", func() {\n\t\tPlay(gameID)\n\t})\n}\n\n// TestPlayAlreadyPlayed tests the scenario where a player tries to play again after already playing\nfunc TestPlayAlreadyPlayed(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOriginCaller(player1)\n\tgameID := NewGame(player2)\n\n\t// Player 1 rolls\n\tPlay(gameID)\n\n\t// Player 1 tries to roll again\n\turequire.PanicsWithMessage(t, \"already played\", func() {\n\t\tPlay(gameID)\n\t})\n}\n\n// TestPlayBeyondGameEnd tests that playing after both players have finished their rolls fails\nfunc TestPlayBeyondGameEnd(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOriginCaller(player1)\n\tgameID := NewGame(player2)\n\n\t// Play for both players\n\tstd.TestSetOriginCaller(player1)\n\tPlay(gameID)\n\tstd.TestSetOriginCaller(player2)\n\tPlay(gameID)\n\n\t// Check if the game is over\n\tg, err := getGame(gameID)\n\turequire.NoError(t, err)\n\n\t// Attempt to play more should fail\n\tstd.TestSetOriginCaller(player1)\n\turequire.PanicsWithMessage(t, \"game over\", func() {\n\t\tPlay(gameID)\n\t})\n}\n"},{"name":"icon.gno","body":"package dice_roller\n\nimport (\n\t\"strconv\"\n)\n\n// diceIcon returns an icon of the dice roll\nfunc diceIcon(roll int) string {\n\tswitch roll {\n\tcase 1:\n\t\treturn \"🎲1\"\n\tcase 2:\n\t\treturn \"🎲2\"\n\tcase 3:\n\t\treturn \"🎲3\"\n\tcase 4:\n\t\treturn \"🎲4\"\n\tcase 5:\n\t\treturn \"🎲5\"\n\tcase 6:\n\t\treturn \"🎲6\"\n\tdefault:\n\t\treturn \"❓\"\n\t}\n}\n\n// resultIcon returns the icon representing the result of a game\nfunc resultIcon(result int) string {\n\tswitch result {\n\tcase ongoing:\n\t\treturn \"🔄\"\n\tcase win:\n\t\treturn \"🏆\"\n\tcase loss:\n\t\treturn \"❌\"\n\tcase draw:\n\t\treturn \"🤝\"\n\tdefault:\n\t\treturn \"❓\"\n\t}\n}\n\n// rankIcon returns the icon for a player's rank\nfunc rankIcon(rank int) string {\n\tswitch rank {\n\tcase 1:\n\t\treturn \"🥇\"\n\tcase 2:\n\t\treturn \"🥈\"\n\tcase 3:\n\t\treturn \"🥉\"\n\tdefault:\n\t\treturn strconv.Itoa(rank)\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":"shifumi","path":"gno.land/r/demo/games/shifumi","files":[{"name":"shifumi.gno","body":"package shifumi\n\nimport (\n\t\"errors\"\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/seqid\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nconst (\n\tempty = iota\n\trock\n\tpaper\n\tscissors\n\tlast\n)\n\ntype game struct {\n\tplayer1, player2 std.Address // shifumi is a 2 players game\n\tmove1, move2 int // can be empty, rock, paper, or scissors\n}\n\nvar games avl.Tree\nvar id seqid.ID\n\nfunc (g *game) play(player std.Address, move int) error {\n\tif !(move \u003e empty \u0026\u0026 move \u003c last) {\n\t\treturn errors.New(\"invalid move\")\n\t}\n\tif player != g.player1 \u0026\u0026 player != g.player2 {\n\t\treturn errors.New(\"invalid player\")\n\t}\n\tif player == g.player1 \u0026\u0026 g.move1 == empty {\n\t\tg.move1 = move\n\t\treturn nil\n\t}\n\tif player == g.player2 \u0026\u0026 g.move2 == empty {\n\t\tg.move2 = move\n\t\treturn nil\n\t}\n\treturn errors.New(\"already played\")\n}\n\nfunc (g *game) winner() int {\n\tif g.move1 == empty || g.move2 == empty {\n\t\treturn -1\n\t}\n\tif g.move1 == g.move2 {\n\t\treturn 0\n\t}\n\tif g.move1 == rock \u0026\u0026 g.move2 == scissors ||\n\t\tg.move1 == paper \u0026\u0026 g.move2 == rock ||\n\t\tg.move1 == scissors \u0026\u0026 g.move2 == paper {\n\t\treturn 1\n\t}\n\treturn 2\n}\n\n// NewGame creates a new game where player1 is the caller and player2 the argument.\n// A new game index is returned.\nfunc NewGame(player std.Address) int {\n\tgames.Set(id.Next().String(), \u0026game{player1: std.PrevRealm().Addr(), player2: player})\n\treturn int(id)\n}\n\n// Play executes a move for the game at index idx, where move can be:\n// 1 (rock), 2 (paper), 3 (scissors).\nfunc Play(idx, move int) {\n\tv, ok := games.Get(seqid.ID(idx).String())\n\tif !ok {\n\t\tpanic(\"game not found\")\n\t}\n\tif err := v.(*game).play(std.PrevRealm().Addr(), move); err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc Render(path string) string {\n\tmov1 := []string{\"\", \" 🤜 \", \" 🫱 \", \" 👉 \"}\n\tmov2 := []string{\"\", \" 🤛 \", \" 🫲 \", \" 👈 \"}\n\twin := []string{\"pending\", \"draw\", \"player1\", \"player2\"}\n\n\toutput := `# 👊 ✋ ✌️ Shifumi\nActions:\n* [NewGame](shifumi$help\u0026func=NewGame) opponentAddress\n* [Play](shifumi$help\u0026func=Play) gameIndex move (1=rock, 2=paper, 3=scissors)\n\n game | player1 | | player2 | | win \n --- | --- | --- | --- | --- | ---\n`\n\t// Output the 100 most recent games.\n\tmaxGames := 100\n\tfor n := int(id); n \u003e 0 \u0026\u0026 int(id)-n \u003c maxGames; n-- {\n\t\tv, ok := games.Get(seqid.ID(n).String())\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tg := v.(*game)\n\t\toutput += strconv.Itoa(n) + \" | \" +\n\t\t\tshortName(g.player1) + \" | \" + mov1[g.move1] + \" | \" +\n\t\t\tshortName(g.player2) + \" | \" + mov2[g.move2] + \" | \" +\n\t\t\twin[g.winner()+1] + \"\\n\"\n\t}\n\treturn output\n}\n\nfunc shortName(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user != nil {\n\t\treturn user.Name\n\t}\n\tif len(addr) \u003c 10 {\n\t\treturn string(addr)\n\t}\n\treturn string(addr)[:10] + \"...\"\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 methods are proxified\n// 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/users\"\n)\n\nvar (\n\tbanker *grc20.Banker\n\tadmin *ownable.Ownable\n\ttoken grc20.Token\n)\n\nfunc init() {\n\tadmin = ownable.NewWithAddress(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\") // @manfred\n\tbanker = grc20.NewBanker(\"Foo\", \"FOO\", 4)\n\tbanker.Mint(admin.Owner(), 1000000*10000) // @administrator (1M)\n\ttoken = banker.Token()\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\tcheckErr(token.Transfer(toAddr, amount))\n}\n\nfunc Approve(spender pusers.AddressOrName, amount uint64) {\n\tspenderAddr := users.Resolve(spender)\n\tcheckErr(token.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(token.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.PreviousRealm().Addr()\n\tamount := uint64(1_000 * 10_000) // 1k\n\tcheckErr(banker.Mint(caller, amount))\n}\n\nfunc Mint(to pusers.AddressOrName, amount uint64) {\n\tadmin.AssertCallerIsOwner()\n\ttoAddr := users.Resolve(to)\n\tcheckErr(banker.Mint(toAddr, amount))\n}\n\nfunc Burn(from pusers.AddressOrName, amount uint64) {\n\tadmin.AssertCallerIsOwner()\n\tfromAddr := users.Resolve(from)\n\tcheckErr(banker.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 banker.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 := banker.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(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\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.TestSetOriginCaller(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(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\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\tstd.TestSetOriginCaller(users.Resolve(admin))\n\t{\n\t\ttests := []test{\n\t\t\t{\"Transfer(admin, 1)\", \"cannot send transfer to self\", func() { Transfer(admin, 1) }},\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":"foo721","path":"gno.land/r/demo/foo721","files":[{"name":"foo721.gno","body":"package foo721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/grc/grc721\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/r/demo/users\"\n\n\tpusers \"gno.land/p/demo/users\"\n)\n\nvar (\n\tadmin std.Address = \"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\"\n\tfoo = grc721.NewBasicNFT(\"FooNFT\", \"FNFT\")\n)\n\nfunc init() {\n\tmintNNFT(admin, 10) // @administrator (10)\n\tmintNNFT(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\", 5) // @hariom (5)\n}\n\nfunc mintNNFT(owner std.Address, n uint64) {\n\tcount := foo.TokenCount()\n\tfor i := count; i \u003c count+n; i++ {\n\t\ttid := grc721.TokenID(ufmt.Sprintf(\"%d\", i))\n\t\tfoo.Mint(owner, tid)\n\t}\n}\n\n// Getters\n\nfunc BalanceOf(user pusers.AddressOrName) uint64 {\n\tbalance, err := foo.BalanceOf(users.Resolve(user))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn balance\n}\n\nfunc OwnerOf(tid grc721.TokenID) std.Address {\n\towner, err := foo.OwnerOf(tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn owner\n}\n\nfunc IsApprovedForAll(owner, user pusers.AddressOrName) bool {\n\treturn foo.IsApprovedForAll(users.Resolve(owner), users.Resolve(user))\n}\n\nfunc GetApproved(tid grc721.TokenID) std.Address {\n\taddr, err := foo.GetApproved(tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn addr\n}\n\n// Setters\n\nfunc Approve(user pusers.AddressOrName, tid grc721.TokenID) {\n\terr := foo.Approve(users.Resolve(user), tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc SetApprovalForAll(user pusers.AddressOrName, approved bool) {\n\terr := foo.SetApprovalForAll(users.Resolve(user), approved)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc TransferFrom(from, to pusers.AddressOrName, tid grc721.TokenID) {\n\terr := foo.TransferFrom(users.Resolve(from), users.Resolve(to), tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// Admin\n\nfunc Mint(to pusers.AddressOrName, tid grc721.TokenID) {\n\tcaller := std.PreviousRealm().Addr()\n\tassertIsAdmin(caller)\n\terr := foo.Mint(users.Resolve(to), tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc Burn(tid grc721.TokenID) {\n\tcaller := std.PreviousRealm().Addr()\n\tassertIsAdmin(caller)\n\terr := foo.Burn(tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// Render\n\nfunc Render(path string) string {\n\tswitch {\n\tcase path == \"\":\n\t\treturn foo.RenderHome()\n\tdefault:\n\t\treturn \"404\\n\"\n\t}\n}\n\n// Util\n\nfunc assertIsAdmin(address std.Address) {\n\tif address != admin {\n\t\tpanic(\"restricted access\")\n\t}\n}\n"},{"name":"foo721_test.gno","body":"package foo721\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/grc/grc721\"\n\t\"gno.land/r/demo/users\"\n\n\tpusers \"gno.land/p/demo/users\"\n)\n\nfunc TestFoo721(t *testing.T) {\n\tadmin := pusers.AddressOrName(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\thariom := pusers.AddressOrName(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tfor i, tc := range []struct {\n\t\tname string\n\t\texpected interface{}\n\t\tfn func() interface{}\n\t}{\n\t\t{\"BalanceOf(admin)\", uint64(10), func() interface{} { return BalanceOf(admin) }},\n\t\t{\"BalanceOf(hariom)\", uint64(5), func() interface{} { return BalanceOf(hariom) }},\n\t\t{\"OwnerOf(0)\", users.Resolve(admin), func() interface{} { return OwnerOf(grc721.TokenID(\"0\")) }},\n\t\t{\"IsApprovedForAll(admin, hariom)\", false, func() interface{} { return IsApprovedForAll(admin, hariom) }},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := tc.fn()\n\t\t\tif tc.expected != got {\n\t\t\t\tt.Errorf(\"expected: %v got: %v\", tc.expected, got)\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":"dice_roller","path":"gno.land/r/demo/games/dice_roller","files":[{"name":"dice_roller.gno","body":"package dice_roller\n\nimport (\n\t\"errors\"\n\t\"math/rand\"\n\t\"sort\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/entropy\"\n\t\"gno.land/p/demo/seqid\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/r/demo/users\"\n)\n\ntype (\n\t// game represents a Dice Roller game between two players\n\tgame struct {\n\t\tplayer1, player2 std.Address\n\t\troll1, roll2 int\n\t}\n\n\t// player holds the information about each player including their stats\n\tplayer struct {\n\t\taddr std.Address\n\t\twins, losses, draws, points int\n\t}\n\n\t// leaderBoard is a slice of players, used to sort players by rank\n\tleaderBoard []player\n)\n\nconst (\n\t// Constants to represent game result outcomes\n\tongoing = iota\n\twin\n\tdraw\n\tloss\n)\n\nvar (\n\tgames avl.Tree // AVL tree for storing game states\n\tgameId seqid.ID // Sequence ID for games\n\n\tplayers avl.Tree // AVL tree for storing player data\n\n\tseed = uint64(entropy.New().Seed())\n\tr = rand.New(rand.NewPCG(seed, 0xdeadbeef))\n)\n\n// rollDice generates a random dice roll between 1 and 6\nfunc rollDice() int {\n\treturn r.IntN(6) + 1\n}\n\n// NewGame initializes a new game with the provided opponent's address\nfunc NewGame(addr std.Address) int {\n\tif !addr.IsValid() {\n\t\tpanic(\"invalid opponent's address\")\n\t}\n\n\tgames.Set(gameId.Next().String(), \u0026game{\n\t\tplayer1: std.PreviousRealm().Addr(),\n\t\tplayer2: addr,\n\t})\n\n\treturn int(gameId)\n}\n\n// Play allows a player to roll the dice and updates the game state accordingly\nfunc Play(idx int) int {\n\tg, err := getGame(idx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\troll := rollDice() // Random the player's dice roll\n\n\t// Play the game and update the player's roll\n\tif err := g.play(std.PreviousRealm().Addr(), roll); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// If both players have rolled, update the results and leaderboard\n\tif g.isFinished() {\n\t\t// If the player is playing against themselves, no points are awarded\n\t\tif g.player1 == g.player2 {\n\t\t\treturn roll\n\t\t}\n\n\t\tplayer1 := getPlayer(g.player1)\n\t\tplayer2 := getPlayer(g.player2)\n\n\t\tif g.roll1 \u003e g.roll2 {\n\t\t\tplayer1.updateStats(win)\n\t\t\tplayer2.updateStats(loss)\n\t\t} else if g.roll2 \u003e g.roll1 {\n\t\t\tplayer2.updateStats(win)\n\t\t\tplayer1.updateStats(loss)\n\t\t} else {\n\t\t\tplayer1.updateStats(draw)\n\t\t\tplayer2.updateStats(draw)\n\t\t}\n\t}\n\n\treturn roll\n}\n\n// play processes a player's roll and updates their score\nfunc (g *game) play(player std.Address, roll int) error {\n\tif player != g.player1 \u0026\u0026 player != g.player2 {\n\t\treturn errors.New(\"invalid player\")\n\t}\n\n\tif g.isFinished() {\n\t\treturn errors.New(\"game over\")\n\t}\n\n\tif player == g.player1 \u0026\u0026 g.roll1 == 0 {\n\t\tg.roll1 = roll\n\t\treturn nil\n\t}\n\n\tif player == g.player2 \u0026\u0026 g.roll2 == 0 {\n\t\tg.roll2 = roll\n\t\treturn nil\n\t}\n\n\treturn errors.New(\"already played\")\n}\n\n// isFinished checks if the game has ended\nfunc (g *game) isFinished() bool {\n\treturn g.roll1 != 0 \u0026\u0026 g.roll2 != 0\n}\n\n// checkResult returns the game status as a formatted string\nfunc (g *game) status() string {\n\tif !g.isFinished() {\n\t\treturn resultIcon(ongoing) + \" Game still in progress\"\n\t}\n\n\tif g.roll1 \u003e g.roll2 {\n\t\treturn resultIcon(win) + \" Player1 Wins !\"\n\t} else if g.roll2 \u003e g.roll1 {\n\t\treturn resultIcon(win) + \" Player2 Wins !\"\n\t} else {\n\t\treturn resultIcon(draw) + \" It's a Draw !\"\n\t}\n}\n\n// Render provides a summary of the current state of games and leader board\nfunc Render(path string) string {\n\tvar sb strings.Builder\n\n\tsb.WriteString(`# 🎲 **Dice Roller Game**\n\nWelcome to Dice Roller! Challenge your friends to a simple yet exciting dice rolling game. Roll the dice and see who gets the highest score !\n\n---\n\n## **How to Play**:\n1. **Create a game**: Challenge an opponent using [NewGame](./dice_roller$help\u0026func=NewGame)\n2. **Roll the dice**: Play your turn by rolling a dice using [Play](./dice_roller$help\u0026func=Play)\n\n---\n\n## **Scoring Rules**:\n- **Win** 🏆: +3 points\n- **Draw** 🤝: +1 point each\n- **Lose** ❌: No points\n- **Playing against yourself**: No points or stats changes for you\n\n---\n\n## **Recent Games**:\nBelow are the results from the most recent games. Up to 10 recent games are displayed\n\n| Game | Player 1 | 🎲 Roll 1 | Player 2 | 🎲 Roll 2 | 🏆 Winner |\n|------|----------|-----------|----------|-----------|-----------|\n`)\n\n\tmaxGames := 10\n\tfor n := int(gameId); n \u003e 0 \u0026\u0026 int(gameId)-n \u003c maxGames; n-- {\n\t\tg, err := getGame(n)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tsb.WriteString(strconv.Itoa(n) + \" | \" +\n\t\t\t\"\u003cspan title=\\\"\" + string(g.player1) + \"\\\"\u003e\" + shortName(g.player1) + \"\u003c/span\u003e\" + \" | \" + diceIcon(g.roll1) + \" | \" +\n\t\t\t\"\u003cspan title=\\\"\" + string(g.player2) + \"\\\"\u003e\" + shortName(g.player2) + \"\u003c/span\u003e\" + \" | \" + diceIcon(g.roll2) + \" | \" +\n\t\t\tg.status() + \"\\n\")\n\t}\n\n\tsb.WriteString(`\n---\n\n## **Leaderboard**:\nThe top players are ranked by performance. Games played against oneself are not counted in the leaderboard\n\n| Rank | Player | Wins | Losses | Draws | Points |\n|------|-----------------------|------|--------|-------|--------|\n`)\n\n\tfor i, player := range getLeaderBoard() {\n\t\tsb.WriteString(ufmt.Sprintf(\"| %s | \u003cspan title=\\\"\"+string(player.addr)+\"\\\"\u003e**%s**\u003c/span\u003e | %d | %d | %d | %d |\\n\",\n\t\t\trankIcon(i+1),\n\t\t\tshortName(player.addr),\n\t\t\tplayer.wins,\n\t\t\tplayer.losses,\n\t\t\tplayer.draws,\n\t\t\tplayer.points,\n\t\t))\n\t}\n\n\tsb.WriteString(\"\\n---\\n**Good luck and have fun !** 🎉\")\n\treturn sb.String()\n}\n\n// shortName returns a shortened name for the given address\nfunc shortName(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user != nil {\n\t\treturn user.Name\n\t}\n\tif len(addr) \u003c 10 {\n\t\treturn string(addr)\n\t}\n\treturn string(addr)[:10] + \"...\"\n}\n\n// getGame retrieves the game state by its ID\nfunc getGame(idx int) (*game, error) {\n\tv, ok := games.Get(seqid.ID(idx).String())\n\tif !ok {\n\t\treturn nil, errors.New(\"game not found\")\n\t}\n\treturn v.(*game), nil\n}\n\n// updateResult updates the player's stats and points based on the game outcome\nfunc (p *player) updateStats(result int) {\n\tswitch result {\n\tcase win:\n\t\tp.wins++\n\t\tp.points += 3\n\tcase loss:\n\t\tp.losses++\n\tcase draw:\n\t\tp.draws++\n\t\tp.points++\n\t}\n}\n\n// getPlayer retrieves a player or initializes a new one if they don't exist\nfunc getPlayer(addr std.Address) *player {\n\tv, ok := players.Get(addr.String())\n\tif !ok {\n\t\tplayer := \u0026player{\n\t\t\taddr: addr,\n\t\t}\n\t\tplayers.Set(addr.String(), player)\n\t\treturn player\n\t}\n\n\treturn v.(*player)\n}\n\n// getLeaderBoard generates a leaderboard sorted by points\nfunc getLeaderBoard() leaderBoard {\n\tboard := leaderBoard{}\n\tplayers.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tplayer := value.(*player)\n\t\tboard = append(board, *player)\n\t\treturn false\n\t})\n\n\tsort.Sort(board)\n\n\treturn board\n}\n\n// Methods for sorting the leaderboard\nfunc (r leaderBoard) Len() int {\n\treturn len(r)\n}\n\nfunc (r leaderBoard) Less(i, j int) bool {\n\tif r[i].points != r[j].points {\n\t\treturn r[i].points \u003e r[j].points\n\t}\n\n\tif r[i].wins != r[j].wins {\n\t\treturn r[i].wins \u003e r[j].wins\n\t}\n\n\tif r[i].draws != r[j].draws {\n\t\treturn r[i].draws \u003e r[j].draws\n\t}\n\n\treturn false\n}\n\nfunc (r leaderBoard) Swap(i, j int) {\n\tr[i], r[j] = r[j], r[i]\n}\n"},{"name":"dice_roller_test.gno","body":"package dice_roller\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/seqid\"\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nvar (\n\tplayer1 = testutils.TestAddress(\"alice\")\n\tplayer2 = testutils.TestAddress(\"bob\")\n\tunknownPlayer = testutils.TestAddress(\"unknown\")\n)\n\n// resetGameState resets the game state for testing\nfunc resetGameState() {\n\tgames = avl.Tree{}\n\tgameId = seqid.ID(0)\n\tplayers = avl.Tree{}\n}\n\n// TestNewGame tests the initialization of a new game\nfunc TestNewGame(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOriginCaller(player1)\n\tgameID := NewGame(player2)\n\n\t// Verify that the game has been correctly initialized\n\tg, err := getGame(gameID)\n\turequire.NoError(t, err)\n\turequire.Equal(t, player1.String(), g.player1.String())\n\turequire.Equal(t, player2.String(), g.player2.String())\n\turequire.Equal(t, 0, g.roll1)\n\turequire.Equal(t, 0, g.roll2)\n}\n\n// TestPlay tests the dice rolling functionality for both players\nfunc TestPlay(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOriginCaller(player1)\n\tgameID := NewGame(player2)\n\n\tg, err := getGame(gameID)\n\turequire.NoError(t, err)\n\n\t// Simulate rolling dice for player 1\n\troll1 := Play(gameID)\n\n\t// Verify player 1's roll\n\turequire.NotEqual(t, 0, g.roll1)\n\turequire.Equal(t, g.roll1, roll1)\n\turequire.Equal(t, 0, g.roll2) // Player 2 hasn't rolled yet\n\n\t// Simulate rolling dice for player 2\n\tstd.TestSetOriginCaller(player2)\n\troll2 := Play(gameID)\n\n\t// Verify player 2's roll\n\turequire.NotEqual(t, 0, g.roll2)\n\turequire.Equal(t, g.roll1, roll1)\n\turequire.Equal(t, g.roll2, roll2)\n}\n\n// TestPlayAgainstSelf tests the scenario where a player plays against themselves\nfunc TestPlayAgainstSelf(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOriginCaller(player1)\n\tgameID := NewGame(player1)\n\n\t// Simulate rolling dice twice by the same player\n\troll1 := Play(gameID)\n\troll2 := Play(gameID)\n\n\tg, err := getGame(gameID)\n\turequire.NoError(t, err)\n\turequire.Equal(t, g.roll1, roll1)\n\turequire.Equal(t, g.roll2, roll2)\n}\n\n// TestPlayInvalidPlayer tests the scenario where an invalid player tries to play\nfunc TestPlayInvalidPlayer(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOriginCaller(player1)\n\tgameID := NewGame(player1)\n\n\t// Attempt to play as an invalid player\n\tstd.TestSetOriginCaller(unknownPlayer)\n\turequire.PanicsWithMessage(t, \"invalid player\", func() {\n\t\tPlay(gameID)\n\t})\n}\n\n// TestPlayAlreadyPlayed tests the scenario where a player tries to play again after already playing\nfunc TestPlayAlreadyPlayed(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOriginCaller(player1)\n\tgameID := NewGame(player2)\n\n\t// Player 1 rolls\n\tPlay(gameID)\n\n\t// Player 1 tries to roll again\n\turequire.PanicsWithMessage(t, \"already played\", func() {\n\t\tPlay(gameID)\n\t})\n}\n\n// TestPlayBeyondGameEnd tests that playing after both players have finished their rolls fails\nfunc TestPlayBeyondGameEnd(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOriginCaller(player1)\n\tgameID := NewGame(player2)\n\n\t// Play for both players\n\tstd.TestSetOriginCaller(player1)\n\tPlay(gameID)\n\tstd.TestSetOriginCaller(player2)\n\tPlay(gameID)\n\n\t// Check if the game is over\n\tg, err := getGame(gameID)\n\turequire.NoError(t, err)\n\n\t// Attempt to play more should fail\n\tstd.TestSetOriginCaller(player1)\n\turequire.PanicsWithMessage(t, \"game over\", func() {\n\t\tPlay(gameID)\n\t})\n}\n"},{"name":"icon.gno","body":"package dice_roller\n\nimport (\n\t\"strconv\"\n)\n\n// diceIcon returns an icon of the dice roll\nfunc diceIcon(roll int) string {\n\tswitch roll {\n\tcase 1:\n\t\treturn \"🎲1\"\n\tcase 2:\n\t\treturn \"🎲2\"\n\tcase 3:\n\t\treturn \"🎲3\"\n\tcase 4:\n\t\treturn \"🎲4\"\n\tcase 5:\n\t\treturn \"🎲5\"\n\tcase 6:\n\t\treturn \"🎲6\"\n\tdefault:\n\t\treturn \"❓\"\n\t}\n}\n\n// resultIcon returns the icon representing the result of a game\nfunc resultIcon(result int) string {\n\tswitch result {\n\tcase ongoing:\n\t\treturn \"🔄\"\n\tcase win:\n\t\treturn \"🏆\"\n\tcase loss:\n\t\treturn \"❌\"\n\tcase draw:\n\t\treturn \"🤝\"\n\tdefault:\n\t\treturn \"❓\"\n\t}\n}\n\n// rankIcon returns the icon for a player's rank\nfunc rankIcon(rank int) string {\n\tswitch rank {\n\tcase 1:\n\t\treturn \"🥇\"\n\tcase 2:\n\t\treturn \"🥈\"\n\tcase 3:\n\t\treturn \"🥉\"\n\tdefault:\n\t\treturn strconv.Itoa(rank)\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":"shifumi","path":"gno.land/r/demo/games/shifumi","files":[{"name":"shifumi.gno","body":"package shifumi\n\nimport (\n\t\"errors\"\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/seqid\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nconst (\n\tempty = iota\n\trock\n\tpaper\n\tscissors\n\tlast\n)\n\ntype game struct {\n\tplayer1, player2 std.Address // shifumi is a 2 players game\n\tmove1, move2 int // can be empty, rock, paper, or scissors\n}\n\nvar games avl.Tree\nvar id seqid.ID\n\nfunc (g *game) play(player std.Address, move int) error {\n\tif !(move \u003e empty \u0026\u0026 move \u003c last) {\n\t\treturn errors.New(\"invalid move\")\n\t}\n\tif player != g.player1 \u0026\u0026 player != g.player2 {\n\t\treturn errors.New(\"invalid player\")\n\t}\n\tif player == g.player1 \u0026\u0026 g.move1 == empty {\n\t\tg.move1 = move\n\t\treturn nil\n\t}\n\tif player == g.player2 \u0026\u0026 g.move2 == empty {\n\t\tg.move2 = move\n\t\treturn nil\n\t}\n\treturn errors.New(\"already played\")\n}\n\nfunc (g *game) winner() int {\n\tif g.move1 == empty || g.move2 == empty {\n\t\treturn -1\n\t}\n\tif g.move1 == g.move2 {\n\t\treturn 0\n\t}\n\tif g.move1 == rock \u0026\u0026 g.move2 == scissors ||\n\t\tg.move1 == paper \u0026\u0026 g.move2 == rock ||\n\t\tg.move1 == scissors \u0026\u0026 g.move2 == paper {\n\t\treturn 1\n\t}\n\treturn 2\n}\n\n// NewGame creates a new game where player1 is the caller and player2 the argument.\n// A new game index is returned.\nfunc NewGame(player std.Address) int {\n\tgames.Set(id.Next().String(), \u0026game{player1: std.PreviousRealm().Addr(), player2: player})\n\treturn int(id)\n}\n\n// Play executes a move for the game at index idx, where move can be:\n// 1 (rock), 2 (paper), 3 (scissors).\nfunc Play(idx, move int) {\n\tv, ok := games.Get(seqid.ID(idx).String())\n\tif !ok {\n\t\tpanic(\"game not found\")\n\t}\n\tif err := v.(*game).play(std.PreviousRealm().Addr(), move); err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc Render(path string) string {\n\tmov1 := []string{\"\", \" 🤜 \", \" 🫱 \", \" 👉 \"}\n\tmov2 := []string{\"\", \" 🤛 \", \" 🫲 \", \" 👈 \"}\n\twin := []string{\"pending\", \"draw\", \"player1\", \"player2\"}\n\n\toutput := `# 👊 ✋ ✌️ Shifumi\nActions:\n* [NewGame](shifumi$help\u0026func=NewGame) opponentAddress\n* [Play](shifumi$help\u0026func=Play) gameIndex move (1=rock, 2=paper, 3=scissors)\n\n game | player1 | | player2 | | win \n --- | --- | --- | --- | --- | ---\n`\n\t// Output the 100 most recent games.\n\tmaxGames := 100\n\tfor n := int(id); n \u003e 0 \u0026\u0026 int(id)-n \u003c maxGames; n-- {\n\t\tv, ok := games.Get(seqid.ID(n).String())\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tg := v.(*game)\n\t\toutput += strconv.Itoa(n) + \" | \" +\n\t\t\tshortName(g.player1) + \" | \" + mov1[g.move1] + \" | \" +\n\t\t\tshortName(g.player2) + \" | \" + mov2[g.move2] + \" | \" +\n\t\t\twin[g.winner()+1] + \"\\n\"\n\t}\n\treturn output\n}\n\nfunc shortName(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user != nil {\n\t\treturn user.Name\n\t}\n\tif len(addr) \u003c 10 {\n\t\treturn string(addr)\n\t}\n\treturn string(addr)[:10] + \"...\"\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":"groups","path":"gno.land/r/demo/groups","files":[{"name":"README.md","body":"### - test package\n\n ./build/gno test examples/gno.land/r/demo/groups/\n\n### - add pkg\n\n ./build/gnokey maketx addpkg -pkgdir \"examples/gno.land/r/demo/groups\" -deposit 100000000ugnot -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid dev -remote 0.0.0.0:26657 -pkgpath \"gno.land/r/demo/groups\" test1 \n\n### - create group\n\n ./build/gnokey maketx call -func \"CreateGroup\" -args \"dao_trinity_ngo\" -gas-fee \"1000000ugnot\" -gas-wanted 4000000 -broadcast -chainid dev -remote 0.0.0.0:26657 -pkgpath \"gno.land/r/demo/groups\" test1 \n\n### - add member\n\n ./build/gnokey maketx call -func \"AddMember\" -args \"1\" -args \"g1hd3gwzevxlqmd3jsf64mpfczag8a8e5j2wdn3c\" -args 12 -args \"i am new user\" -gas-fee \"1000000ugnot\" -gas-wanted \"4000000\" -broadcast -chainid dev -remote 0.0.0.0:26657 -pkgpath \"gno.land/r/demo/groups\" test1\n\n### - delete member\n\n ./build/gnokey maketx call -func \"DeleteMember\" -args \"1\" -args \"0\" -gas-fee \"1000000ugnot\" -gas-wanted \"4000000\" -broadcast -chainid dev -remote 0.0.0.0:26657 -pkgpath \"gno.land/r/demo/groups\" test1\n\n### - delete group\n\n ./build/gnokey maketx call -func \"DeleteGroup\" -args \"1\" -gas-fee \"1000000ugnot\" -gas-wanted \"4000000\" -broadcast -chainid dev -remote 0.0.0.0:26657 -pkgpath \"gno.land/r/demo/groups\" test1\n\n"},{"name":"group.gno","body":"package groups\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\ntype GroupID uint64\n\nfunc (gid GroupID) String() string {\n\treturn strconv.Itoa(int(gid))\n}\n\ntype Group struct {\n\tid GroupID\n\turl string\n\tname string\n\tlastMemberID MemberID\n\tmembers avl.Tree\n\tcreator std.Address\n\tcreatedAt time.Time\n}\n\nfunc newGroup(url string, name string, creator std.Address) *Group {\n\tif !reName.MatchString(name) {\n\t\tpanic(\"invalid name: \" + name)\n\t}\n\tif gGroupsByName.Has(name) {\n\t\tpanic(\"Group with such name already exists\")\n\t}\n\treturn \u0026Group{\n\t\tid: incGetGroupID(),\n\t\turl: url,\n\t\tname: name,\n\t\tcreator: creator,\n\t\tmembers: avl.Tree{},\n\t\tcreatedAt: time.Now(),\n\t}\n}\n\nfunc (group *Group) newMember(id MemberID, address std.Address, weight int, metadata string) *Member {\n\tif group.members.Has(address.String()) {\n\t\tpanic(\"this member for this group already exists\")\n\t}\n\treturn \u0026Member{\n\t\tid: id,\n\t\taddress: address,\n\t\tweight: weight,\n\t\tmetadata: metadata,\n\t\tcreatedAt: time.Now(),\n\t}\n}\n\nfunc (group *Group) HasPermission(addr std.Address, perm Permission) bool {\n\tif group.creator != addr {\n\t\treturn false\n\t}\n\treturn isValidPermission(perm)\n}\n\nfunc (group *Group) RenderGroup() string {\n\tstr := \"Group ID: \" + groupIDKey(group.id) + \"\\n\\n\" +\n\t\t\"Group Name: \" + group.name + \"\\n\\n\" +\n\t\t\"Group Creator: \" + usernameOf(group.creator) + \"\\n\\n\" +\n\t\t\"Group createdAt: \" + group.createdAt.String() + \"\\n\\n\" +\n\t\t\"Group Last MemberID: \" + memberIDKey(group.lastMemberID) + \"\\n\\n\"\n\n\tstr += \"Group Members: \\n\\n\"\n\tgroup.members.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tmember := value.(*Member)\n\t\tstr += member.getMemberStr()\n\t\treturn false\n\t})\n\treturn str\n}\n\nfunc (group *Group) deleteGroup() {\n\tgidkey := groupIDKey(group.id)\n\t_, gGroupsRemoved := gGroups.Remove(gidkey)\n\tif !gGroupsRemoved {\n\t\tpanic(\"group does not exist with id \" + group.id.String())\n\t}\n\tgGroupsByName.Remove(group.name)\n}\n\nfunc (group *Group) deleteMember(mid MemberID) {\n\tgidkey := groupIDKey(group.id)\n\tif !gGroups.Has(gidkey) {\n\t\tpanic(\"group does not exist with id \" + group.id.String())\n\t}\n\n\tg := getGroup(group.id)\n\tmidkey := memberIDKey(mid)\n\tg.members.Remove(midkey)\n}\n"},{"name":"groups.gno","body":"package groups\n\nimport (\n\t\"regexp\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n//----------------------------------------\n// Realm (package) state\n\nvar (\n\tgGroups avl.Tree // id -\u003e *Group\n\tgGroupsCtr int // increments Group.id\n\tgGroupsByName avl.Tree // name -\u003e *Group\n)\n\n//----------------------------------------\n// Constants\n\nvar reName = regexp.MustCompile(`^[a-z]+[_a-z0-9]{2,29}$`)\n"},{"name":"member.gno","body":"package groups\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n)\n\ntype MemberID uint64\n\ntype Member struct {\n\tid MemberID\n\taddress std.Address\n\tweight int\n\tmetadata string\n\tcreatedAt time.Time\n}\n\nfunc (mid MemberID) String() string {\n\treturn strconv.Itoa(int(mid))\n}\n\nfunc (member *Member) getMemberStr() string {\n\tmemberDataStr := \"\"\n\tmemberDataStr += \"\\t\\t\\t[\" + memberIDKey(member.id) + \", \" + member.address.String() + \", \" + strconv.Itoa(member.weight) + \", \" + member.metadata + \", \" + member.createdAt.String() + \"],\\n\\n\"\n\treturn memberDataStr\n}\n"},{"name":"misc.gno","body":"package groups\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/r/demo/users\"\n)\n\n//----------------------------------------\n// private utility methods\n// XXX ensure these cannot be called from public.\n\nfunc getGroup(gid GroupID) *Group {\n\tgidkey := groupIDKey(gid)\n\tgroup_, exists := gGroups.Get(gidkey)\n\tif !exists {\n\t\tpanic(\"group id (\" + gid.String() + \") does not exists\")\n\t}\n\tgroup := group_.(*Group)\n\treturn group\n}\n\nfunc incGetGroupID() GroupID {\n\tgGroupsCtr++\n\treturn GroupID(gGroupsCtr)\n}\n\nfunc padLeft(str string, length int) string {\n\tif len(str) \u003e= length {\n\t\treturn str\n\t}\n\treturn strings.Repeat(\" \", length-len(str)) + str\n}\n\nfunc padZero(u64 uint64, length int) string {\n\tstr := strconv.Itoa(int(u64))\n\tif len(str) \u003e= length {\n\t\treturn str\n\t}\n\treturn strings.Repeat(\"0\", length-len(str)) + str\n}\n\nfunc groupIDKey(gid GroupID) string {\n\treturn padZero(uint64(gid), 10)\n}\n\nfunc memberIDKey(mid MemberID) string {\n\treturn padZero(uint64(mid), 10)\n}\n\nfunc indentBody(indent string, body string) string {\n\tlines := strings.Split(body, \"\\n\")\n\tres := \"\"\n\tfor i, line := range lines {\n\t\tif i \u003e 0 {\n\t\t\tres += \"\\n\"\n\t\t}\n\t\tres += indent + line\n\t}\n\treturn res\n}\n\n// NOTE: length must be greater than 3.\nfunc summaryOf(str string, length int) string {\n\tlines := strings.SplitN(str, \"\\n\", 2)\n\tline := lines[0]\n\tif len(line) \u003e length {\n\t\tline = line[:(length-3)] + \"...\"\n\t} else if len(lines) \u003e 1 {\n\t\t// len(line) \u003c= 80\n\t\tline = line + \"...\"\n\t}\n\treturn line\n}\n\nfunc displayAddressMD(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user == nil {\n\t\treturn \"[\" + addr.String() + \"](/r/demo/users:\" + addr.String() + \")\"\n\t}\n\treturn \"[@\" + user.Name + \"](/r/demo/users:\" + user.Name + \")\"\n}\n\nfunc usernameOf(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user == nil {\n\t\tpanic(\"user not found\")\n\t}\n\treturn user.Name\n}\n\nfunc isValidPermission(perm Permission) bool {\n\treturn perm == EditPermission || perm == DeletePermission\n}\n"},{"name":"public.gno","body":"package groups\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\n//----------------------------------------\n// Public facing functions\n\nfunc GetGroupIDFromName(name string) (GroupID, bool) {\n\tgroupI, exists := gGroupsByName.Get(name)\n\tif !exists {\n\t\treturn 0, false\n\t}\n\treturn groupI.(*Group).id, true\n}\n\nfunc CreateGroup(name string) GroupID {\n\tstd.AssertOriginCall()\n\tcaller := std.OriginCaller()\n\tusernameOf(caller)\n\turl := \"/r/demo/groups:\" + name\n\tgroup := newGroup(url, name, caller)\n\tgidkey := groupIDKey(group.id)\n\tgGroups.Set(gidkey, group)\n\tgGroupsByName.Set(name, group)\n\treturn group.id\n}\n\nfunc AddMember(gid GroupID, address string, weight int, metadata string) MemberID {\n\tstd.AssertOriginCall()\n\tcaller := std.OriginCaller()\n\tusernameOf(caller)\n\tgroup := getGroup(gid)\n\tif !group.HasPermission(caller, EditPermission) {\n\t\tpanic(\"unauthorized to edit group\")\n\t}\n\tuser := users.GetUserByAddress(std.Address(address))\n\tif user == nil {\n\t\tpanic(\"unknown address \" + address)\n\t}\n\tmid := group.lastMemberID\n\tmember := group.newMember(mid, std.Address(address), weight, metadata)\n\tmidkey := memberIDKey(mid)\n\tgroup.members.Set(midkey, member)\n\tmid++\n\tgroup.lastMemberID = mid\n\treturn member.id\n}\n\nfunc DeleteGroup(gid GroupID) {\n\tstd.AssertOriginCall()\n\tcaller := std.OriginCaller()\n\tgroup := getGroup(gid)\n\tif !group.HasPermission(caller, DeletePermission) {\n\t\tpanic(\"unauthorized to delete group\")\n\t}\n\tgroup.deleteGroup()\n}\n\nfunc DeleteMember(gid GroupID, mid MemberID) {\n\tstd.AssertOriginCall()\n\tcaller := std.OriginCaller()\n\tgroup := getGroup(gid)\n\tif !group.HasPermission(caller, DeletePermission) {\n\t\tpanic(\"unauthorized to delete member\")\n\t}\n\tgroup.deleteMember(mid)\n}\n"},{"name":"render.gno","body":"package groups\n\nimport (\n\t\"strings\"\n)\n\n//----------------------------------------\n// Render functions\n\nfunc RenderGroup(gid GroupID) string {\n\tgroup := getGroup(gid)\n\tif group == nil {\n\t\treturn \"missing Group\"\n\t}\n\treturn group.RenderGroup()\n}\n\nfunc Render(path string) string {\n\tif path == \"\" {\n\t\tstr := \"List of all Groups:\\n\\n\"\n\t\tgGroups.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tgroup := value.(*Group)\n\t\t\tstr += \" * [\" + group.name + \"](\" + group.url + \")\\n\"\n\t\t\treturn false\n\t\t})\n\t\treturn str\n\t}\n\tparts := strings.Split(path, \"/\")\n\tif len(parts) == 1 {\n\t\t// /r/demo/groups:Group_NAME\n\t\tname := parts[0]\n\t\tgroupI, exists := gGroupsByName.Get(name)\n\t\tif !exists {\n\t\t\treturn \"Group does not exist: \" + name\n\t\t}\n\t\treturn groupI.(*Group).RenderGroup()\n\t} else {\n\t\treturn \"unrecognized path \" + path\n\t}\n}\n"},{"name":"role.gno","body":"package groups\n\ntype Permission string\n\nconst (\n\tDeletePermission Permission = \"role:delete\"\n\tEditPermission Permission = \"role:edit\"\n)\n"},{"name":"z_0_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\nimport (\n\t\"gno.land/r/demo/groups\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// user not found\n"},{"name":"z_0_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// payment must not be less than 20000000\n"},{"name":"z_0_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tprintln(groups.Render(\"\"))\n}\n\n// Output:\n// 1\n// List of all Groups:\n//\n// * [test_group](/r/demo/groups:test_group)\n"},{"name":"z_1_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser0\", \"my profile 1\")\n\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"gnouser1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tusers.Register(caller, \"gnouser1\", \"my other profile 1\")\n\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest2 := testutils.TestAddress(\"gnouser2\")\n\tusers.Invite(test2.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test2)\n\tusers.Register(caller, \"gnouser2\", \"my other profile 2\")\n\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest3 := testutils.TestAddress(\"gnouser3\")\n\tusers.Invite(test3.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test3)\n\tusers.Register(caller, \"gnouser3\", \"my other profile 3\")\n\n\tstd.TestSetOriginCaller(caller)\n\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\n\tgroups.AddMember(gid, test3.String(), 32, \"i am from UAE\")\n\tprintln(groups.Render(\"test_group\"))\n}\n\n// Output:\n// 1\n// Group ID: 0000000001\n//\n// Group Name: test_group\n//\n// Group Creator: gnouser0\n//\n// Group createdAt: 2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001\n//\n// Group Last MemberID: 0000000001\n//\n// Group Members:\n//\n// \t\t\t[0000000000, g1vahx7atnv4erxh6lta047h6lta047h6ll85gpy, 32, i am from UAE, 2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001],\n"},{"name":"z_1_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tgroups.AddMember(2, \"g1vahx7atnv4erxh6lta047h6lta047h6ll85gpy\", 55, \"metadata3\")\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// group id (2) does not exists\n"},{"name":"z_1_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/groups\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\n\t// add member via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOriginCaller(test2)\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\n\tgroups.AddMember(gid, test2.String(), 42, \"metadata3\")\n}\n\n// Error:\n// user not found\n"},{"name":"z_2_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser0\", \"my profile 1\")\n\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"gnouser1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tusers.Register(caller, \"gnouser1\", \"my other profile 1\")\n\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest2 := testutils.TestAddress(\"gnouser2\")\n\tusers.Invite(test2.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test2)\n\tusers.Register(caller, \"gnouser2\", \"my other profile 2\")\n\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest3 := testutils.TestAddress(\"gnouser3\")\n\tusers.Invite(test3.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test3)\n\tusers.Register(caller, \"gnouser3\", \"my other profile 3\")\n\n\tstd.TestSetOriginCaller(caller)\n\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\n\tgroups.AddMember(gid, test2.String(), 42, \"metadata3\")\n\n\tgroups.DeleteMember(gid, 0)\n\tprintln(groups.RenderGroup(gid))\n}\n\n// Output:\n// 1\n// Group ID: 0000000001\n//\n// Group Name: test_group\n//\n// Group Creator: gnouser0\n//\n// Group createdAt: 2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001\n//\n// Group Last MemberID: 0000000001\n//\n// Group Members:\n"},{"name":"z_2_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tgroups.DeleteMember(2, 0)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// group id (2) does not exists\n"},{"name":"z_2_d_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\n\t// delete member via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOriginCaller(test2)\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\n\tgroups.DeleteMember(gid, 0)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// unauthorized to delete member\n"},{"name":"z_2_e_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tgroups.DeleteGroup(gid)\n\tprintln(groups.Render(\"\"))\n}\n\n// Output:\n// 1\n// List of all Groups:\n"},{"name":"z_2_f_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tgroups.DeleteGroup(20)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// group id (20) does not exists\n"},{"name":"z_2_g_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\n\t// delete group via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOriginCaller(test2)\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\n\tgroups.DeleteGroup(gid)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// unauthorized to delete group\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":"keystore","path":"gno.land/r/demo/keystore","files":[{"name":"keystore.gno","body":"package keystore\n\nimport (\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar data avl.Tree\n\nconst (\n\tBaseURL = \"/r/demo/keystore\"\n\tStatusOK = \"ok\"\n\tStatusNoUser = \"user not found\"\n\tStatusNotFound = \"key not found\"\n\tStatusNoWriteAccess = \"no write access\"\n\tStatusCouldNotExecute = \"could not execute\"\n\tStatusNoDatabases = \"no databases\"\n)\n\nfunc init() {\n\tdata = avl.Tree{} // user -\u003e avl.Tree\n}\n\n// KeyStore stores the owner-specific avl.Tree\ntype KeyStore struct {\n\tOwner std.Address\n\tData avl.Tree\n}\n\n// Set will set a value to a key\n// requires write-access (original caller must be caller)\nfunc Set(k, v string) string {\n\torigOwner := std.OriginCaller()\n\treturn set(origOwner.String(), k, v)\n}\n\n// set (private) will set a key to value\n// requires write-access (original caller must be caller)\nfunc set(owner, k, v string) string {\n\torigOwner := std.OriginCaller()\n\tif origOwner.String() != owner {\n\t\treturn StatusNoWriteAccess\n\t}\n\tvar keystore *KeyStore\n\tkeystoreInterface, exists := data.Get(owner)\n\tif !exists {\n\t\tkeystore = \u0026KeyStore{\n\t\t\tOwner: origOwner,\n\t\t\tData: avl.Tree{},\n\t\t}\n\t\tdata.Set(owner, keystore)\n\t} else {\n\t\tkeystore = keystoreInterface.(*KeyStore)\n\t}\n\tkeystore.Data.Set(k, v)\n\treturn StatusOK\n}\n\n// Remove removes a key\n// requires write-access (original owner must be caller)\nfunc Remove(k string) string {\n\torigOwner := std.OriginCaller()\n\treturn remove(origOwner.String(), k)\n}\n\n// remove (private) removes a key\n// requires write-access (original owner must be caller)\nfunc remove(owner, k string) string {\n\torigOwner := std.OriginCaller()\n\tif origOwner.String() != owner {\n\t\treturn StatusNoWriteAccess\n\t}\n\tvar keystore *KeyStore\n\tkeystoreInterface, exists := data.Get(owner)\n\tif !exists {\n\t\tkeystore = \u0026KeyStore{\n\t\t\tOwner: origOwner,\n\t\t\tData: avl.Tree{},\n\t\t}\n\t\tdata.Set(owner, keystore)\n\t} else {\n\t\tkeystore = keystoreInterface.(*KeyStore)\n\t}\n\t_, removed := keystore.Data.Remove(k)\n\tif !removed {\n\t\treturn StatusCouldNotExecute\n\t}\n\treturn StatusOK\n}\n\n// Get returns a value for a key\n// read-only\nfunc Get(k string) string {\n\torigOwner := std.OriginCaller()\n\treturn remove(origOwner.String(), k)\n}\n\n// get (private) returns a value for a key\n// read-only\nfunc get(owner, k string) string {\n\tkeystoreInterface, exists := data.Get(owner)\n\tif !exists {\n\t\treturn StatusNoUser\n\t}\n\tkeystore := keystoreInterface.(*KeyStore)\n\tval, found := keystore.Data.Get(k)\n\tif !found {\n\t\treturn StatusNotFound\n\t}\n\treturn val.(string)\n}\n\n// Size returns size of database\n// read-only\nfunc Size() string {\n\torigOwner := std.OriginCaller()\n\treturn size(origOwner.String())\n}\n\nfunc size(owner string) string {\n\tkeystoreInterface, exists := data.Get(owner)\n\tif !exists {\n\t\treturn StatusNoUser\n\t}\n\tkeystore := keystoreInterface.(*KeyStore)\n\treturn ufmt.Sprintf(\"%d\", keystore.Data.Size())\n}\n\n// Render provides read-only url access to the functions of the keystore\n// \"\" -\u003e show all keystores listed by owner\n// \"owner\" -\u003e show all keys for that owner's keystore\n// \"owner:size\" -\u003e returns size of owner's keystore\n// \"owner:get:key\" -\u003e show value for that key in owner's keystore\nfunc Render(p string) string {\n\tvar response string\n\targs := strings.Split(p, \":\")\n\tnumArgs := len(args)\n\tif p == \"\" {\n\t\tnumArgs = 0\n\t}\n\tswitch numArgs {\n\tcase 0:\n\t\tif data.Size() == 0 {\n\t\t\treturn StatusNoDatabases\n\t\t}\n\t\tdata.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tks := value.(*KeyStore)\n\t\t\tresponse += ufmt.Sprintf(\"- [%s](%s:%s) (%d keys)\\n\", ks.Owner, BaseURL, ks.Owner, ks.Data.Size())\n\t\t\treturn false\n\t\t})\n\tcase 1:\n\t\towner := args[0]\n\t\tkeystoreInterface, exists := data.Get(owner)\n\t\tif !exists {\n\t\t\treturn StatusNoUser\n\t\t}\n\t\tks := keystoreInterface.(*KeyStore)\n\t\ti := 0\n\t\tresponse += ufmt.Sprintf(\"# %s database\\n\\n\", ks.Owner)\n\t\tks.Data.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tresponse += ufmt.Sprintf(\"- %d [%s](%s:%s:get:%s)\\n\", i, key, BaseURL, ks.Owner, key)\n\t\t\ti++\n\t\t\treturn false\n\t\t})\n\tcase 2:\n\t\towner := args[0]\n\t\tcmd := args[1]\n\t\tif cmd == \"size\" {\n\t\t\treturn size(owner)\n\t\t}\n\tcase 3:\n\t\towner := args[0]\n\t\tcmd := args[1]\n\t\tkey := args[2]\n\t\tif cmd == \"get\" {\n\t\t\treturn get(owner, key)\n\t\t}\n\t}\n\n\treturn response\n}\n"},{"name":"keystore_test.gno","body":"package keystore\n\nimport (\n\t\"std\"\n\t\"strings\"\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 TestRender(t *testing.T) {\n\tconst (\n\t\tauthor1 std.Address = testutils.TestAddress(\"author1\")\n\t\tauthor2 std.Address = testutils.TestAddress(\"author2\")\n\t)\n\n\ttt := []struct {\n\t\tcaller std.Address\n\t\towner std.Address\n\t\tps []string\n\t\texp string\n\t}{\n\t\t// can set database if the owner is the caller\n\t\t{author1, author1, []string{\"set\", \"hello\", \"gno\"}, StatusOK},\n\t\t{author1, author1, []string{\"size\"}, \"1\"},\n\t\t{author1, author1, []string{\"set\", \"hello\", \"world\"}, StatusOK},\n\t\t{author1, author1, []string{\"size\"}, \"1\"},\n\t\t{author1, author1, []string{\"set\", \"hi\", \"gno\"}, StatusOK},\n\t\t{author1, author1, []string{\"size\"}, \"2\"},\n\t\t// only owner can remove\n\t\t{author1, author1, []string{\"remove\", \"hi\"}, StatusOK},\n\t\t{author1, author1, []string{\"get\", \"hi\"}, StatusNotFound},\n\t\t{author1, author1, []string{\"size\"}, \"1\"},\n\t\t// add back\n\t\t{author1, author1, []string{\"set\", \"hi\", \"gno\"}, StatusOK},\n\t\t{author1, author1, []string{\"size\"}, \"2\"},\n\n\t\t// different owner has different database\n\t\t{author2, author2, []string{\"set\", \"hello\", \"universe\"}, StatusOK},\n\t\t// either author can get the other info\n\t\t{author1, author2, []string{\"get\", \"hello\"}, \"universe\"},\n\t\t// either author can get the other info\n\t\t{author2, author1, []string{\"get\", \"hello\"}, \"world\"},\n\t\t{author1, author2, []string{\"get\", \"hello\"}, \"universe\"},\n\t\t// anyone can view the databases\n\t\t{author1, author2, []string{}, `- [g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6](/r/demo/keystore:g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6) (2 keys)\n- [g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00](/r/demo/keystore:g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00) (1 keys)`},\n\t\t// anyone can view the keys in a database\n\t\t{author1, author2, []string{\"\"}, `# g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00 database\n\n- 0 [hello](/r/demo/keystore:g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00:get:hello)`},\n\t}\n\tfor _, tc := range tt {\n\t\tp := \"\"\n\t\tif len(tc.ps) \u003e 0 {\n\t\t\tp = tc.owner.String()\n\t\t\tfor i, psv := range tc.ps {\n\t\t\t\tp += \":\" + psv\n\t\t\t}\n\t\t}\n\t\tp = strings.TrimSuffix(p, \":\")\n\t\tt.Run(p, func(t *testing.T) {\n\t\t\tstd.TestSetOriginCaller(tc.caller)\n\t\t\tvar act string\n\t\t\tif len(tc.ps) \u003e 0 \u0026\u0026 tc.ps[0] == \"set\" {\n\t\t\t\tact = strings.TrimSpace(Set(tc.ps[1], tc.ps[2]))\n\t\t\t} else if len(tc.ps) \u003e 0 \u0026\u0026 tc.ps[0] == \"remove\" {\n\t\t\t\tact = strings.TrimSpace(Remove(tc.ps[1]))\n\t\t\t} else {\n\t\t\t\tact = strings.TrimSpace(Render(p))\n\t\t\t}\n\n\t\t\tuassert.Equal(t, tc.exp, act, ufmt.Sprintf(\"%v -\u003e '%s'\", tc.ps, p))\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":"markdown","path":"gno.land/r/demo/markdown_test","files":[{"name":"markdown.gno","body":"package markdown\n\n// this package can be used to test markdown rendering engines.\n\nfunc Render(path string) string {\n\toutput := `_imported from https://github.com/markedjs/marked/blob/master/docs/demo/quickref.md_\n\nMarkdown Quick Reference\n========================\n\nThis guide is a very brief overview, with examples, of the syntax that [Markdown] supports. It is itself written in Markdown and you can copy the samples over to the left-hand pane for experimentation. It's shown as *text* and not *rendered HTML*.\n\n[Markdown]: http://daringfireball.net/projects/markdown/\n\n\nSimple Text Formatting\n======================\n\nFirst thing is first. You can use *stars* or _underscores_ for italics. **Double stars** and __double underscores__ for bold. ***Three together*** for ___both___.\n\nParagraphs are pretty easy too. Just have a blank line between chunks of text.\n\n\u003e This chunk of text is in a block quote. Its multiple lines will all be\n\u003e indented a bit from the rest of the text.\n\u003e\n\u003e \u003e Multiple levels of block quotes also work.\n\nSometimes you want to include code, such as when you are explaining how ` + \"`\u003ch1\u003e`\" + ` HTML tags work, or maybe you are a programmer and you are discussing ` + \"`someMethod()`\" + `.\n\nIf you want to include code and have new\nlines preserved, indent the line with a tab\nor at least four spaces:\n\n Extra spaces work here too.\n This is also called preformatted text and it is useful for showing examples.\n The text will stay as text, so any *markdown* or \u003cu\u003eHTML\u003c/u\u003e you add will\n not show up formatted. This way you can show markdown examples in a\n markdown document.\n\n\u003e You can also use preformatted text with your blockquotes\n\u003e as long as you add at least five spaces.\n\n\nHeadings\n========\n\nThere are a couple of ways to make headings. Using three or more equals signs on a line under a heading makes it into an \"h1\" style. Three or more hyphens under a line makes it \"h2\" (slightly smaller). You can also use multiple pound symbols (` + \"`#`\" + `) before and after a heading. Pounds after the title are ignored. Here are some examples:\n\nThis is H1\n==========\n\nThis is H2\n----------\n\n# This is H1\n## This is H2\n### This is H3 with some extra pounds ###\n#### You get the idea ####\n##### I don't need extra pounds at the end\n###### H6 is the max\n\n\nLinks\n=====\n\nLet's link to a few sites. First, let's use the bare URL, like \u003chttps://www.github.com\u003e. Great for text, but ugly for HTML.\nNext is an inline link to [Google](https://www.google.com). A little nicer.\nThis is a reference-style link to [Wikipedia] [1].\nLastly, here's a pretty link to [Yahoo]. The reference-style and pretty links both automatically use the links defined below, but they could be defined *anywhere* in the markdown and are removed from the HTML. The names are also case insensitive, so you can use [YaHoO] and have it link properly.\n\n[1]: https://www.wikipedia.org\n[Yahoo]: https://www.yahoo.com\n\nTitle attributes may be added to links by adding text after a link.\nThis is the [inline link](https://www.bing.com \"Bing\") with a \"Bing\" title.\nYou can also go to [W3C] [2] and maybe visit a [friend].\n\n[2]: https://w3c.org (The W3C puts out specs for web-based things)\n[Friend]: https://facebook.com \"Facebook!\"\n\nEmail addresses in plain text are not linked: test@example.com.\nEmail addresses wrapped in angle brackets are linked: \u003ctest@example.com\u003e.\nThey are also obfuscated so that email harvesting spam robots hopefully won't get them.\n\n\nLists\n=====\n\n* This is a bulleted list\n* Great for shopping lists\n- You can also use hyphens\n+ Or plus symbols\n\nThe above is an \"unordered\" list. Now, on for a bit of order.\n\n1. Numbered lists are also easy\n2. Just start with a number\n3738762. However, the actual number doesn't matter when converted to HTML.\n1. This will still show up as 4.\n\nYou might want a few advanced lists:\n\n- This top-level list is wrapped in paragraph tags\n- This generates an extra space between each top-level item.\n\n- You do it by adding a blank line\n\n- This nested list also has blank lines between the list items.\n\n- How to create nested lists\n 1. Start your regular list\n 2. Indent nested lists with two spaces\n 3. Further nesting means you should indent with two more spaces\n * This line is indented with four spaces.\n\n- List items can be quite lengthy. You can keep typing and either continue\nthem on the next line with no indentation.\n\n- Alternately, if that looks ugly, you can also\n indent the next line a bit for a prettier look.\n\n- You can put large blocks of text in your list by just indenting with two spaces.\n\n This is formatted the same as code, but you can inspect the HTML\n and find that it's just wrapped in a ` + \"`\u003cp\u003e`\" + ` tag and *won't* be shown\n as preformatted text.\n\n You can keep adding more and more paragraphs to a single\n list item by adding the traditional blank line and then keep\n on indenting the paragraphs with two spaces.\n\n You really only need to indent the first line,\nbut that looks ugly.\n\n- Lists support blockquotes\n\n \u003e Just like this example here. By the way, you can\n \u003e nest lists inside blockquotes!\n \u003e - Fantastic!\n\n- Lists support preformatted text\n\n You just need to indent an additional four spaces.\n\n\nEven More\n=========\n\nHorizontal Rule\n---------------\n\nIf you need a horizontal rule you just need to put at least three hyphens, asterisks, or underscores on a line by themselves. You can also even put spaces between the characters.\n\n---\n****************************\n_ _ _ _ _ _ _\n\nThose three all produced horizontal lines. Keep in mind that three hyphens under any text turns that text into a heading, so add a blank like if you use hyphens.\n\nImages\n------\n\nImages work exactly like links, but they have exclamation points in front. They work with references and titles too.\n\n![Google Logo](https://www.google.com/images/errors/logo_sm.gif) and ![Happy].\n\n[Happy]: https://wpclipart.com/smiley/happy/simple_colors/smiley_face_simple_green_small.png (\"Smiley face\")\n\n\nInline HTML\n-----------\n\nIf markdown is too limiting, you can just insert your own \u003cstrike\u003ecrazy\u003c/strike\u003e HTML. Span-level HTML \u003cu\u003ecan *still* use markdown\u003c/u\u003e. Block level elements must be separated from text by a blank line and must not have any spaces before the opening and closing HTML.\n\n\u003cdiv style='font-family: \"Comic Sans MS\", \"Comic Sans\", cursive;'\u003e\nIt is a pity, but markdown does **not** work in here for most markdown parsers.\n[Marked] handles it pretty well.\n\u003c/div\u003e`\n\treturn output\n}\n"},{"name":"markdown_test.gno","body":"package markdown\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestRender(t *testing.T) {\n\toutput := Render(\"\")\n\tif !strings.Contains(output, \"\\nMarkdown Quick Reference\\n\") {\n\t\tt.Errorf(\"invalid output\")\n\t}\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}} @@ -99,7 +99,7 @@ {"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"memeland","path":"gno.land/r/demo/memeland","files":[{"name":"memeland.gno","body":"package memeland\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/demo/memeland\"\n)\n\nvar m *memeland.Memeland\n\nfunc init() {\n\tm = memeland.NewMemeland()\n\tm.TransferOwnership(\"g125em6arxsnj49vx35f0n0z34putv5ty3376fg5\")\n}\n\nfunc PostMeme(data string, timestamp int64) string {\n\treturn m.PostMeme(data, timestamp)\n}\n\nfunc Upvote(id string) string {\n\treturn m.Upvote(id)\n}\n\nfunc GetPostsInRange(startTimestamp, endTimestamp int64, page, pageSize int, sortBy string) string {\n\treturn m.GetPostsInRange(startTimestamp, endTimestamp, page, pageSize, sortBy)\n}\n\nfunc RemovePost(id string) string {\n\treturn m.RemovePost(id)\n}\n\nfunc GetOwner() std.Address {\n\treturn m.Owner()\n}\n\nfunc TransferOwnership(newOwner std.Address) {\n\tif err := m.TransferOwnership(newOwner); err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc Render(path string) string {\n\tnumOfMemes := int(m.MemeCounter)\n\tif numOfMemes == 0 {\n\t\treturn \"No memes posted yet! :/\"\n\t}\n\n\t// Default render is get Posts since year 2000 to now\n\treturn m.GetPostsInRange(0, time.Now().Unix(), 1, 10, \"DATE_CREATED\")\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":"microblog","path":"gno.land/r/demo/microblog","files":[{"name":"README.md","body":"# microblog realm\n\n## Getting started:\n\n(One-time) Add the microblog package:\n\n```\ngnokey maketx addpkg --pkgpath \"gno.land/p/demo/microblog\" --pkgdir \"examples/gno.land/p/demo/microblog\" \\\n --deposit 100000000ugnot --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast --chainid dev --remote localhost:26657 \u003cYOURKEY\u003e\n```\n\n(One-time) Add the microblog realm:\n\n```\ngnokey maketx addpkg --pkgpath \"gno.land/r/demo/microblog\" --pkgdir \"examples/gno.land/r/demo/microblog\" \\\n --deposit 100000000ugnot --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast --chainid dev --remote localhost:26657 \u003cYOURKEY\u003e\n```\n\nAdd a microblog post:\n\n```\ngnokey maketx call --pkgpath \"gno.land/r/demo/microblog\" --func \"NewPost\" --args \"hello, world\" \\\n --gas-fee \"1000000ugnot\" --gas-wanted \"2000000\" --broadcast --chainid dev --remote localhost:26657 \u003cYOURKEY\u003e\n```"},{"name":"microblog.gno","body":"// Microblog is a website with shortform posts from users.\n// The API is simple - \"AddPost\" takes markdown and\n// adds it to the users site.\n// The microblog location is determined by the user address\n// /r/demo/microblog:\u003cYOUR-ADDRESS\u003e\npackage microblog\n\nimport (\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/microblog\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\ttitle = \"gno-based microblog\"\n\tprefix = \"/r/demo/microblog:\"\n\tm *microblog.Microblog\n)\n\nfunc init() {\n\tm = microblog.NewMicroblog(title, prefix)\n}\n\nfunc renderHome() string {\n\toutput := ufmt.Sprintf(\"# %s\\n\\n\", m.Title)\n\toutput += \"# pages\\n\\n\"\n\n\tfor _, page := range m.GetPages() {\n\t\tif u := users.GetUserByAddress(page.Author); u != nil {\n\t\t\toutput += ufmt.Sprintf(\"- [%s (%s)](%s%s)\\n\", u.Name, page.Author.String(), m.Prefix, page.Author.String())\n\t\t} else {\n\t\t\toutput += ufmt.Sprintf(\"- [%s](%s%s)\\n\", page.Author.String(), m.Prefix, page.Author.String())\n\t\t}\n\t}\n\n\treturn output\n}\n\nfunc renderUser(user string) string {\n\tsilo, found := m.Pages.Get(user)\n\tif !found {\n\t\treturn \"404\" // StatusNotFound\n\t}\n\n\treturn PageToString((silo.(*microblog.Page)))\n}\n\nfunc Render(path string) string {\n\tparts := strings.Split(path, \"/\")\n\n\tisHome := path == \"\"\n\tisUser := len(parts) == 1\n\n\tswitch {\n\tcase isHome:\n\t\treturn renderHome()\n\n\tcase isUser:\n\t\treturn renderUser(parts[0])\n\t}\n\n\treturn \"404\" // StatusNotFound\n}\n\nfunc PageToString(p *microblog.Page) string {\n\to := \"\"\n\tif u := users.GetUserByAddress(p.Author); u != nil {\n\t\to += ufmt.Sprintf(\"# [%s](/r/demo/users:%s)\\n\\n\", u, u)\n\t\to += ufmt.Sprintf(\"%s\\n\\n\", u.Profile)\n\t}\n\to += ufmt.Sprintf(\"## [%s](/r/demo/microblog:%s)\\n\\n\", p.Author, p.Author)\n\n\to += ufmt.Sprintf(\"joined %s, last updated %s\\n\\n\", p.CreatedAt.Format(\"2006-02-01\"), p.LastPosted.Format(\"2006-02-01\"))\n\to += \"## feed\\n\\n\"\n\tfor _, u := range p.GetPosts() {\n\t\to += u.String() + \"\\n\\n\"\n\t}\n\treturn o\n}\n\n// NewPost takes a single argument (post markdown) and\n// adds a post to the address of the caller.\nfunc NewPost(text string) string {\n\tif err := m.NewPost(text); err != nil {\n\t\treturn \"unable to add new post\"\n\t}\n\treturn \"added new post\"\n}\n\nfunc Register(name, profile string) string {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(caller, name, profile)\n\treturn \"OK\"\n}\n"},{"name":"microblog_test.gno","body":"package microblog\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestMicroblog(t *testing.T) {\n\tconst (\n\t\tauthor1 std.Address = testutils.TestAddress(\"author1\")\n\t\tauthor2 std.Address = testutils.TestAddress(\"author2\")\n\t)\n\n\tstd.TestSetOriginCaller(author1)\n\n\turequire.Equal(t, \"404\", Render(\"/wrongpath\"), \"rendering not giving 404\")\n\turequire.NotEqual(t, \"404\", Render(\"\"), \"rendering / should not give 404\")\n\turequire.NoError(t, m.NewPost(\"goodbyte, web2\"), \"could not create post\")\n\n\t_, err := m.GetPage(author1.String())\n\turequire.NoError(t, err, \"silo should exist\")\n\n\t_, err = m.GetPage(\"no such author\")\n\turequire.Error(t, err, \"silo should not exist\")\n\n\tstd.TestSetOriginCaller(author2)\n\n\turequire.NoError(t, m.NewPost(\"hello, web3\"), \"could not create post\")\n\turequire.NoError(t, m.NewPost(\"hello again, web3\"), \"could not create post\")\n\turequire.NoError(t, m.NewPost(\"hi again,\\n web4?\"), \"could not create post\")\n\n\tprintln(\"--- MICROBLOG ---\\n\\n\")\n\n\texpected := `# gno-based microblog\n\n# pages\n\n- [g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6](/r/demo/microblog:g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6)\n- [g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00](/r/demo/microblog:g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00)\n`\n\turequire.Equal(t, expected, Render(\"\"), \"incorrect rendering\")\n\n\texpected = `## [g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6](/r/demo/microblog:g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6)\n\njoined 2009-13-02, last updated 2009-13-02\n\n## feed\n\n\u003e goodbyte, web2\n\u003e\n\u003e *Fri, 13 Feb 2009 23:31:30 UTC*`\n\n\turequire.Equal(t, expected, strings.TrimSpace(Render(author1.String())), \"incorrect rendering\")\n\n\texpected = `## [g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00](/r/demo/microblog:g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00)\n\njoined 2009-13-02, last updated 2009-13-02\n\n## feed\n\n\u003e hi again,\n\u003e\n\u003e web4?\n\u003e\n\u003e *Fri, 13 Feb 2009 23:31:30 UTC*\n\n\u003e hello again, web3\n\u003e\n\u003e *Fri, 13 Feb 2009 23:31:30 UTC*\n\n\u003e hello, web3\n\u003e\n\u003e *Fri, 13 Feb 2009 23:31:30 UTC*`\n\n\turequire.Equal(t, expected, strings.TrimSpace(Render(author2.String())), \"incorrect rendering\")\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":"nft","path":"gno.land/r/demo/nft","files":[{"name":"README.md","body":"NFT's are all the rage these days, for various reasons.\n\nI read over EIP-721 which appears to be the de-facto NFT standard on Ethereum. Then, made a sample implementation of EIP-721 (let's here called GRC-721). The implementation isn't complete, but it demonstrates the main functionality.\n\n- [EIP-721](https://eips.ethereum.org/EIPS/eip-721)\n- [gno.land/r/demo/nft/nft.go](https://gno.land/r/demo/nft/nft.go)\n- [zrealm_nft3.go test](https://github.com/gnolang/gno/blob/master/tests/files2/zrealm_nft3.gno)\n\nIn short, this demonstrates how to implement Ethereum contract interfaces in gno.land; by using only standard Go language features.\n\nPlease leave a comment ([guide](https://gno.land/r/demo/boards:gnolang/1)).\n"},{"name":"nft.gno","body":"package nft\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/grc/grc721\"\n)\n\ntype token struct {\n\tgrc721.IGRC721 // implements the GRC721 interface\n\n\ttokenCounter int\n\ttokens avl.Tree // grc721.TokenID -\u003e *NFToken{}\n\toperators avl.Tree // owner std.Address -\u003e operator std.Address\n}\n\ntype NFToken struct {\n\towner std.Address\n\tapproved std.Address\n\ttokenID grc721.TokenID\n\tdata string\n}\n\nvar gToken = \u0026token{}\n\nfunc GetToken() *token { return gToken }\n\nfunc (grc *token) nextTokenID() grc721.TokenID {\n\tgrc.tokenCounter++\n\ts := strconv.Itoa(grc.tokenCounter)\n\treturn grc721.TokenID(s)\n}\n\nfunc (grc *token) getToken(tid grc721.TokenID) (*NFToken, bool) {\n\ttoken, ok := grc.tokens.Get(string(tid))\n\tif !ok {\n\t\treturn nil, false\n\t}\n\treturn token.(*NFToken), true\n}\n\nfunc (grc *token) Mint(to std.Address, data string) grc721.TokenID {\n\ttid := grc.nextTokenID()\n\tgrc.tokens.Set(string(tid), \u0026NFToken{\n\t\towner: to,\n\t\ttokenID: tid,\n\t\tdata: data,\n\t})\n\treturn tid\n}\n\nfunc (grc *token) BalanceOf(owner std.Address) (count int64) {\n\tpanic(\"not yet implemented\")\n}\n\nfunc (grc *token) OwnerOf(tid grc721.TokenID) std.Address {\n\ttoken, ok := grc.getToken(tid)\n\tif !ok {\n\t\tpanic(\"token does not exist\")\n\t}\n\treturn token.owner\n}\n\n// XXX not fully implemented yet.\nfunc (grc *token) SafeTransferFrom(from, to std.Address, tid grc721.TokenID) {\n\tgrc.TransferFrom(from, to, tid)\n\t// When transfer is complete, this function checks if `_to` is a smart\n\t// contract (code size \u003e 0). If so, it calls `onERC721Received` on\n\t// `_to` and throws if the return value is not\n\t// `bytes4(keccak256(\"onERC721Received(address,address,uint256,bytes)\"))`.\n\t// XXX ensure \"to\" is a realm with onERC721Received() signature.\n}\n\nfunc (grc *token) TransferFrom(from, to std.Address, tid grc721.TokenID) {\n\tcaller := std.CallerAt(2)\n\ttoken, ok := grc.getToken(tid)\n\t// Throws if `_tokenId` is not a valid NFT.\n\tif !ok {\n\t\tpanic(\"token does not exist\")\n\t}\n\t// Throws unless `msg.sender` is the current owner, an authorized\n\t// operator, or the approved address for this NFT.\n\tif caller != token.owner \u0026\u0026 caller != token.approved {\n\t\toperator, ok := grc.operators.Get(token.owner.String())\n\t\tif !ok || caller != operator.(std.Address) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t}\n\t// Throws if `_from` is not the current owner.\n\tif from != token.owner {\n\t\tpanic(\"from is not the current owner\")\n\t}\n\t// Throws if `_to` is the zero address.\n\tif to == \"\" {\n\t\tpanic(\"to cannot be empty\")\n\t}\n\t// Good.\n\ttoken.owner = to\n}\n\nfunc (grc *token) Approve(approved std.Address, tid grc721.TokenID) {\n\tcaller := std.CallerAt(2)\n\ttoken, ok := grc.getToken(tid)\n\t// Throws if `_tokenId` is not a valid NFT.\n\tif !ok {\n\t\tpanic(\"token does not exist\")\n\t}\n\t// Throws unless `msg.sender` is the current owner,\n\t// or an authorized operator.\n\tif caller != token.owner {\n\t\toperator, ok := grc.operators.Get(token.owner.String())\n\t\tif !ok || caller != operator.(std.Address) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t}\n\t// Good.\n\ttoken.approved = approved\n}\n\n// XXX make it work for set of operators.\nfunc (grc *token) SetApprovalForAll(operator std.Address, approved bool) {\n\tcaller := std.CallerAt(2)\n\tgrc.operators.Set(caller.String(), operator)\n}\n\nfunc (grc *token) GetApproved(tid grc721.TokenID) std.Address {\n\ttoken, ok := grc.getToken(tid)\n\t// Throws if `_tokenId` is not a valid NFT.\n\tif !ok {\n\t\tpanic(\"token does not exist\")\n\t}\n\treturn token.approved\n}\n\n// XXX make it work for set of operators\nfunc (grc *token) IsApprovedForAll(owner, operator std.Address) bool {\n\toperator2, ok := grc.operators.Get(owner.String())\n\tif !ok {\n\t\treturn false\n\t}\n\treturn operator == operator2.(std.Address)\n}\n"},{"name":"z_0_filetest.gno","body":"// PKGPATH: gno.land/r/demo/nft_test\npackage nft_test\n\nimport (\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/nft\"\n)\n\nfunc main() {\n\taddr1 := testutils.TestAddress(\"addr1\")\n\t// addr2 := testutils.TestAddress(\"addr2\")\n\tgrc721 := nft.GetToken()\n\ttid := grc721.Mint(addr1, \"NFT#1\")\n\tprintln(grc721.OwnerOf(tid))\n\tprintln(addr1)\n}\n\n// Output:\n// g1v9jxgu33ta047h6lta047h6lta047h6l43dqc5\n// g1v9jxgu33ta047h6lta047h6lta047h6l43dqc5\n\n// Realm:\n// switchrealm[\"gno.land/r/demo/nft\"]\n// switchrealm[\"gno.land/r/demo/nft\"]\n// c[67c479d3d51d4056b2f4111d5352912a00be311e:11]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"std.Address\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"g1v9jxgu33ta047h6lta047h6lta047h6l43dqc5\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"std.Address\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/grc/grc721.TokenID\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"1\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"NFT#1\"\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:11\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:10\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[67c479d3d51d4056b2f4111d5352912a00be311e:10]={\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:10\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:9\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/nft.NFToken\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"564a9e78be869bd258fc3c9ad56f5a75ed68818f\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:11\"\n// }\n// }\n// }\n// c[67c479d3d51d4056b2f4111d5352912a00be311e:9]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"1\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/nft.NFToken\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"b53ffc464e1b5655d19b9d5277f3491717c24aca\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:10\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:9\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:8\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[67c479d3d51d4056b2f4111d5352912a00be311e:8]={\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:8\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:5\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"b1d928b3716b147c92730e8d234162bec2f0f2fc\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:9\"\n// }\n// }\n// }\n// u[67c479d3d51d4056b2f4111d5352912a00be311e:5]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"b229b824842ec3e7f2341e33d0fa0ca77af2f480\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:8\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:5\",\n// \"ModTime\": \"7\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:4\",\n// \"RefCount\": \"1\"\n// }\n// }\n// u[67c479d3d51d4056b2f4111d5352912a00be311e:4]={\n// \"Fields\": [\n// {},\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"1e0b9dddb406b4f50500a022266a4cb8a4ea38c6\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:5\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"05ab6746ea84b55ca133806af215d99a1c4b045e\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:6\"\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:4\",\n// \"ModTime\": \"7\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:3\",\n// \"RefCount\": \"1\"\n// }\n// }\n// switchrealm[\"gno.land/r/demo/nft\"]\n// switchrealm[\"gno.land/r/demo/nft_test\"]\n"},{"name":"z_1_filetest.gno","body":"// PKGPATH: gno.land/r/demo/nft_test\npackage nft_test\n\nimport (\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/nft\"\n)\n\nfunc main() {\n\taddr1 := testutils.TestAddress(\"addr1\")\n\taddr2 := testutils.TestAddress(\"addr2\")\n\tgrc721 := nft.GetToken()\n\ttid := grc721.Mint(addr1, \"NFT#1\")\n\tprintln(grc721.OwnerOf(tid))\n\tprintln(addr1)\n\tgrc721.TransferFrom(addr1, addr2, tid)\n}\n\n// Error:\n// unauthorized\n"},{"name":"z_2_filetest.gno","body":"// PKGPATH: gno.land/r/demo/nft_test\npackage nft_test\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/nft\"\n)\n\nfunc main() {\n\tcaller := std.CallerAt(1)\n\taddr1 := testutils.TestAddress(\"addr1\")\n\t// addr2 := testutils.TestAddress(\"addr2\")\n\tgrc721 := nft.GetToken()\n\ttid := grc721.Mint(caller, \"NFT#1\")\n\tprintln(grc721.OwnerOf(tid))\n\tprintln(addr1)\n\tgrc721.TransferFrom(caller, addr1, tid)\n}\n\n// Output:\n// g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n// g1v9jxgu33ta047h6lta047h6lta047h6l43dqc5\n"},{"name":"z_3_filetest.gno","body":"// PKGPATH: gno.land/r/demo/nft_test\npackage nft_test\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/nft\"\n)\n\nfunc main() {\n\tcaller := std.CallerAt(1)\n\taddr1 := testutils.TestAddress(\"addr1\")\n\taddr2 := testutils.TestAddress(\"addr2\")\n\tgrc721 := nft.GetToken()\n\ttid := grc721.Mint(caller, \"NFT#1\")\n\tprintln(grc721.OwnerOf(tid))\n\tprintln(addr1)\n\tgrc721.Approve(caller, tid) // approve self.\n\tgrc721.TransferFrom(caller, addr1, tid)\n\tgrc721.TransferFrom(addr1, addr2, tid)\n}\n\n// Output:\n// g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n// g1v9jxgu33ta047h6lta047h6lta047h6l43dqc5\n"},{"name":"z_4_filetest.gno","body":"// PKGPATH: gno.land/r/demo/nft_test\npackage nft_test\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/nft\"\n)\n\nfunc main() {\n\tcaller := std.CallerAt(1)\n\taddr1 := testutils.TestAddress(\"addr1\")\n\taddr2 := testutils.TestAddress(\"addr2\")\n\tgrc721 := nft.GetToken()\n\ttid := grc721.Mint(caller, \"NFT#1\")\n\tprintln(grc721.OwnerOf(tid))\n\tprintln(addr1)\n\tgrc721.Approve(caller, tid) // approve self.\n\tgrc721.TransferFrom(caller, addr1, tid)\n\tgrc721.Approve(\"\", tid) // approve addr1.\n\tgrc721.TransferFrom(addr1, addr2, tid)\n}\n\n// Error:\n// unauthorized\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":"profile","path":"gno.land/r/demo/profile","files":[{"name":"profile.gno","body":"package profile\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/mux\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar (\n\tfields = avl.NewTree()\n\trouter = mux.NewRouter()\n)\n\n// Standard fields\nconst (\n\tDisplayName = \"DisplayName\"\n\tHomepage = \"Homepage\"\n\tBio = \"Bio\"\n\tAge = \"Age\"\n\tLocation = \"Location\"\n\tAvatar = \"Avatar\"\n\tGravatarEmail = \"GravatarEmail\"\n\tAvailableForHiring = \"AvailableForHiring\"\n\tInvalidField = \"InvalidField\"\n)\n\n// Events\nconst (\n\tProfileFieldCreated = \"ProfileFieldCreated\"\n\tProfileFieldUpdated = \"ProfileFieldUpdated\"\n)\n\n// Field types used when emitting event\nconst FieldType = \"FieldType\"\n\nconst (\n\tBoolField = \"BoolField\"\n\tStringField = \"StringField\"\n\tIntField = \"IntField\"\n)\n\nfunc init() {\n\trouter.HandleFunc(\"\", homeHandler)\n\trouter.HandleFunc(\"u/{addr}\", profileHandler)\n\trouter.HandleFunc(\"f/{addr}/{field}\", fieldHandler)\n}\n\n// List of supported string fields\nvar stringFields = map[string]bool{\n\tDisplayName: true,\n\tHomepage: true,\n\tBio: true,\n\tLocation: true,\n\tAvatar: true,\n\tGravatarEmail: true,\n}\n\n// List of support int fields\nvar intFields = map[string]bool{\n\tAge: true,\n}\n\n// List of support bool fields\nvar boolFields = map[string]bool{\n\tAvailableForHiring: true,\n}\n\n// Setters\n\nfunc SetStringField(field, value string) bool {\n\taddr := std.PrevRealm().Addr()\n\tkey := addr.String() + \":\" + field\n\tupdated := fields.Set(key, value)\n\n\tevent := ProfileFieldCreated\n\tif updated {\n\t\tevent = ProfileFieldUpdated\n\t}\n\n\tstd.Emit(event, FieldType, StringField, field, value)\n\n\treturn updated\n}\n\nfunc SetIntField(field string, value int) bool {\n\taddr := std.PrevRealm().Addr()\n\tkey := addr.String() + \":\" + field\n\tupdated := fields.Set(key, value)\n\n\tevent := ProfileFieldCreated\n\tif updated {\n\t\tevent = ProfileFieldUpdated\n\t}\n\n\tstd.Emit(event, FieldType, IntField, field, string(value))\n\n\treturn updated\n}\n\nfunc SetBoolField(field string, value bool) bool {\n\taddr := std.PrevRealm().Addr()\n\tkey := addr.String() + \":\" + field\n\tupdated := fields.Set(key, value)\n\n\tevent := ProfileFieldCreated\n\tif updated {\n\t\tevent = ProfileFieldUpdated\n\t}\n\n\tstd.Emit(event, FieldType, BoolField, field, ufmt.Sprintf(\"%t\", value))\n\n\treturn updated\n}\n\n// Getters\n\nfunc GetStringField(addr std.Address, field, def string) string {\n\tkey := addr.String() + \":\" + field\n\tif value, ok := fields.Get(key); ok {\n\t\treturn value.(string)\n\t}\n\n\treturn def\n}\n\nfunc GetBoolField(addr std.Address, field string, def bool) bool {\n\tkey := addr.String() + \":\" + field\n\tif value, ok := fields.Get(key); ok {\n\t\treturn value.(bool)\n\t}\n\n\treturn def\n}\n\nfunc GetIntField(addr std.Address, field string, def int) int {\n\tkey := addr.String() + \":\" + field\n\tif value, ok := fields.Get(key); ok {\n\t\treturn value.(int)\n\t}\n\n\treturn def\n}\n"},{"name":"profile_test.gno","body":"package profile\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\n// Global addresses for test users\nvar (\n\talice = testutils.TestAddress(\"alice\")\n\tbob = testutils.TestAddress(\"bob\")\n\tcharlie = testutils.TestAddress(\"charlie\")\n\tdave = testutils.TestAddress(\"dave\")\n\teve = testutils.TestAddress(\"eve\")\n\tfrank = testutils.TestAddress(\"frank\")\n\tuser1 = testutils.TestAddress(\"user1\")\n\tuser2 = testutils.TestAddress(\"user2\")\n)\n\nfunc TestStringFields(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\n\t// Get before setting\n\tname := GetStringField(alice, DisplayName, \"anon\")\n\tuassert.Equal(t, \"anon\", name)\n\n\t// Set new key\n\tupdated := SetStringField(DisplayName, \"Alice foo\")\n\tuassert.Equal(t, updated, false)\n\tupdated = SetStringField(Homepage, \"https://example.com\")\n\tuassert.Equal(t, updated, false)\n\n\t// Update the key\n\tupdated = SetStringField(DisplayName, \"Alice foo\")\n\tuassert.Equal(t, updated, true)\n\n\t// Get after setting\n\tname = GetStringField(alice, DisplayName, \"anon\")\n\thomepage := GetStringField(alice, Homepage, \"\")\n\tbio := GetStringField(alice, Bio, \"42\")\n\n\tuassert.Equal(t, \"Alice foo\", name)\n\tuassert.Equal(t, \"https://example.com\", homepage)\n\tuassert.Equal(t, \"42\", bio)\n}\n\nfunc TestIntFields(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\n\t// Get before setting\n\tage := GetIntField(bob, Age, 25)\n\tuassert.Equal(t, 25, age)\n\n\t// Set new key\n\tupdated := SetIntField(Age, 30)\n\tuassert.Equal(t, updated, false)\n\n\t// Update the key\n\tupdated = SetIntField(Age, 30)\n\tuassert.Equal(t, updated, true)\n\n\t// Get after setting\n\tage = GetIntField(bob, Age, 25)\n\tuassert.Equal(t, 30, age)\n}\n\nfunc TestBoolFields(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(charlie))\n\n\t// Get before setting\n\thiring := GetBoolField(charlie, AvailableForHiring, false)\n\tuassert.Equal(t, false, hiring)\n\n\t// Set\n\tupdated := SetBoolField(AvailableForHiring, true)\n\tuassert.Equal(t, updated, false)\n\n\t// Update the key\n\tupdated = SetBoolField(AvailableForHiring, true)\n\tuassert.Equal(t, updated, true)\n\n\t// Get after setting\n\thiring = GetBoolField(charlie, AvailableForHiring, false)\n\tuassert.Equal(t, true, hiring)\n}\n\nfunc TestMultipleProfiles(t *testing.T) {\n\t// Set profile for user1\n\tstd.TestSetRealm(std.NewUserRealm(user1))\n\tupdated := SetStringField(DisplayName, \"User One\")\n\tuassert.Equal(t, updated, false)\n\n\t// Set profile for user2\n\tstd.TestSetRealm(std.NewUserRealm(user2))\n\tupdated = SetStringField(DisplayName, \"User Two\")\n\tuassert.Equal(t, updated, false)\n\n\t// Get profiles\n\tstd.TestSetRealm(std.NewUserRealm(user1)) // Switch back to user1\n\tname1 := GetStringField(user1, DisplayName, \"anon\")\n\tstd.TestSetRealm(std.NewUserRealm(user2)) // Switch back to user2\n\tname2 := GetStringField(user2, DisplayName, \"anon\")\n\n\tuassert.Equal(t, \"User One\", name1)\n\tuassert.Equal(t, \"User Two\", name2)\n}\n\nfunc TestArbitraryStringField(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(user1))\n\n\t// Set arbitrary string field\n\tupdated := SetStringField(\"MyEmail\", \"my@email.com\")\n\tuassert.Equal(t, updated, false)\n\n\tval := GetStringField(user1, \"MyEmail\", \"\")\n\tuassert.Equal(t, val, \"my@email.com\")\n}\n\nfunc TestArbitraryIntField(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(user1))\n\n\t// Set arbitrary int field\n\tupdated := SetIntField(\"MyIncome\", 100_000)\n\tuassert.Equal(t, updated, false)\n\n\tval := GetIntField(user1, \"MyIncome\", 0)\n\tuassert.Equal(t, val, 100_000)\n}\n\nfunc TestArbitraryBoolField(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(user1))\n\n\t// Set arbitrary int field\n\tupdated := SetBoolField(\"IsWinner\", true)\n\tuassert.Equal(t, updated, false)\n\n\tval := GetBoolField(user1, \"IsWinner\", false)\n\tuassert.Equal(t, val, true)\n}\n"},{"name":"render.gno","body":"package profile\n\nimport (\n\t\"bytes\"\n\t\"net/url\"\n\t\"std\"\n\n\t\"gno.land/p/demo/mux\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nconst (\n\tBaseURL = \"/r/demo/profile\"\n\tSetStringFieldURL = BaseURL + \"$help\u0026func=SetStringField\u0026field=%s\"\n\tSetIntFieldURL = BaseURL + \"$help\u0026func=SetIntField\u0026field=%s\"\n\tSetBoolFieldURL = BaseURL + \"$help\u0026func=SetBoolField\u0026field=%s\"\n\tViewAllFieldsURL = BaseURL + \":u/%s\"\n\tViewFieldURL = BaseURL + \":f/%s/%s\"\n)\n\nfunc homeHandler(res *mux.ResponseWriter, req *mux.Request) {\n\tvar b bytes.Buffer\n\n\tb.WriteString(\"## Setters\\n\")\n\tfor field := range stringFields {\n\t\tlink := ufmt.Sprintf(SetStringFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- [Set %s](%s)\\n\", field, link))\n\t}\n\n\tfor field := range intFields {\n\t\tlink := ufmt.Sprintf(SetIntFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- [Set %s](%s)\\n\", field, link))\n\t}\n\n\tfor field := range boolFields {\n\t\tlink := ufmt.Sprintf(SetBoolFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- [Set %s Field](%s)\\n\", field, link))\n\t}\n\n\tb.WriteString(\"\\n---\\n\\n\")\n\n\tres.Write(b.String())\n}\n\nfunc profileHandler(res *mux.ResponseWriter, req *mux.Request) {\n\tvar b bytes.Buffer\n\taddr := req.GetVar(\"addr\")\n\n\tb.WriteString(ufmt.Sprintf(\"# Profile %s\\n\", addr))\n\n\taddress := std.Address(addr)\n\n\tfor field := range stringFields {\n\t\tvalue := GetStringField(address, field, \"n/a\")\n\t\tlink := ufmt.Sprintf(SetStringFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- %s: %s [Edit](%s)\\n\", field, value, link))\n\t}\n\n\tfor field := range intFields {\n\t\tvalue := GetIntField(address, field, 0)\n\t\tlink := ufmt.Sprintf(SetIntFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- %s: %d [Edit](%s)\\n\", field, value, link))\n\t}\n\n\tfor field := range boolFields {\n\t\tvalue := GetBoolField(address, field, false)\n\t\tlink := ufmt.Sprintf(SetBoolFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- %s: %t [Edit](%s)\\n\", field, value, link))\n\t}\n\n\tres.Write(b.String())\n}\n\nfunc fieldHandler(res *mux.ResponseWriter, req *mux.Request) {\n\tvar b bytes.Buffer\n\taddr := req.GetVar(\"addr\")\n\tfield := req.GetVar(\"field\")\n\n\tb.WriteString(ufmt.Sprintf(\"# Field %s for %s\\n\", field, addr))\n\n\taddress := std.Address(addr)\n\tvalue := \"n/a\"\n\tvar editLink string\n\n\tif _, ok := stringFields[field]; ok {\n\t\tvalue = ufmt.Sprintf(\"%s\", GetStringField(address, field, \"n/a\"))\n\t\teditLink = ufmt.Sprintf(SetStringFieldURL+\"\u0026addr=%s\u0026value=%s\", field, addr, url.QueryEscape(value))\n\t} else if _, ok := intFields[field]; ok {\n\t\tvalue = ufmt.Sprintf(\"%d\", GetIntField(address, field, 0))\n\t\teditLink = ufmt.Sprintf(SetIntFieldURL+\"\u0026addr=%s\u0026value=%s\", field, addr, value)\n\t} else if _, ok := boolFields[field]; ok {\n\t\tvalue = ufmt.Sprintf(\"%t\", GetBoolField(address, field, false))\n\t\teditLink = ufmt.Sprintf(SetBoolFieldURL+\"\u0026addr=%s\u0026value=%s\", field, addr, value)\n\t}\n\n\tb.WriteString(ufmt.Sprintf(\"- %s: %s [Edit](%s)\\n\", field, value, editLink))\n\n\tres.Write(b.String())\n}\n\nfunc Render(path string) string {\n\treturn router.Render(path)\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":"profile","path":"gno.land/r/demo/profile","files":[{"name":"profile.gno","body":"package profile\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/mux\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar (\n\tfields = avl.NewTree()\n\trouter = mux.NewRouter()\n)\n\n// Standard fields\nconst (\n\tDisplayName = \"DisplayName\"\n\tHomepage = \"Homepage\"\n\tBio = \"Bio\"\n\tAge = \"Age\"\n\tLocation = \"Location\"\n\tAvatar = \"Avatar\"\n\tGravatarEmail = \"GravatarEmail\"\n\tAvailableForHiring = \"AvailableForHiring\"\n\tInvalidField = \"InvalidField\"\n)\n\n// Events\nconst (\n\tProfileFieldCreated = \"ProfileFieldCreated\"\n\tProfileFieldUpdated = \"ProfileFieldUpdated\"\n)\n\n// Field types used when emitting event\nconst FieldType = \"FieldType\"\n\nconst (\n\tBoolField = \"BoolField\"\n\tStringField = \"StringField\"\n\tIntField = \"IntField\"\n)\n\nfunc init() {\n\trouter.HandleFunc(\"\", homeHandler)\n\trouter.HandleFunc(\"u/{addr}\", profileHandler)\n\trouter.HandleFunc(\"f/{addr}/{field}\", fieldHandler)\n}\n\n// List of supported string fields\nvar stringFields = map[string]bool{\n\tDisplayName: true,\n\tHomepage: true,\n\tBio: true,\n\tLocation: true,\n\tAvatar: true,\n\tGravatarEmail: true,\n}\n\n// List of support int fields\nvar intFields = map[string]bool{\n\tAge: true,\n}\n\n// List of support bool fields\nvar boolFields = map[string]bool{\n\tAvailableForHiring: true,\n}\n\n// Setters\n\nfunc SetStringField(field, value string) bool {\n\taddr := std.PreviousRealm().Addr()\n\tkey := addr.String() + \":\" + field\n\tupdated := fields.Set(key, value)\n\n\tevent := ProfileFieldCreated\n\tif updated {\n\t\tevent = ProfileFieldUpdated\n\t}\n\n\tstd.Emit(event, FieldType, StringField, field, value)\n\n\treturn updated\n}\n\nfunc SetIntField(field string, value int) bool {\n\taddr := std.PreviousRealm().Addr()\n\tkey := addr.String() + \":\" + field\n\tupdated := fields.Set(key, value)\n\n\tevent := ProfileFieldCreated\n\tif updated {\n\t\tevent = ProfileFieldUpdated\n\t}\n\n\tstd.Emit(event, FieldType, IntField, field, string(value))\n\n\treturn updated\n}\n\nfunc SetBoolField(field string, value bool) bool {\n\taddr := std.PreviousRealm().Addr()\n\tkey := addr.String() + \":\" + field\n\tupdated := fields.Set(key, value)\n\n\tevent := ProfileFieldCreated\n\tif updated {\n\t\tevent = ProfileFieldUpdated\n\t}\n\n\tstd.Emit(event, FieldType, BoolField, field, ufmt.Sprintf(\"%t\", value))\n\n\treturn updated\n}\n\n// Getters\n\nfunc GetStringField(addr std.Address, field, def string) string {\n\tkey := addr.String() + \":\" + field\n\tif value, ok := fields.Get(key); ok {\n\t\treturn value.(string)\n\t}\n\n\treturn def\n}\n\nfunc GetBoolField(addr std.Address, field string, def bool) bool {\n\tkey := addr.String() + \":\" + field\n\tif value, ok := fields.Get(key); ok {\n\t\treturn value.(bool)\n\t}\n\n\treturn def\n}\n\nfunc GetIntField(addr std.Address, field string, def int) int {\n\tkey := addr.String() + \":\" + field\n\tif value, ok := fields.Get(key); ok {\n\t\treturn value.(int)\n\t}\n\n\treturn def\n}\n"},{"name":"profile_test.gno","body":"package profile\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\n// Global addresses for test users\nvar (\n\talice = testutils.TestAddress(\"alice\")\n\tbob = testutils.TestAddress(\"bob\")\n\tcharlie = testutils.TestAddress(\"charlie\")\n\tdave = testutils.TestAddress(\"dave\")\n\teve = testutils.TestAddress(\"eve\")\n\tfrank = testutils.TestAddress(\"frank\")\n\tuser1 = testutils.TestAddress(\"user1\")\n\tuser2 = testutils.TestAddress(\"user2\")\n)\n\nfunc TestStringFields(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\n\t// Get before setting\n\tname := GetStringField(alice, DisplayName, \"anon\")\n\tuassert.Equal(t, \"anon\", name)\n\n\t// Set new key\n\tupdated := SetStringField(DisplayName, \"Alice foo\")\n\tuassert.Equal(t, updated, false)\n\tupdated = SetStringField(Homepage, \"https://example.com\")\n\tuassert.Equal(t, updated, false)\n\n\t// Update the key\n\tupdated = SetStringField(DisplayName, \"Alice foo\")\n\tuassert.Equal(t, updated, true)\n\n\t// Get after setting\n\tname = GetStringField(alice, DisplayName, \"anon\")\n\thomepage := GetStringField(alice, Homepage, \"\")\n\tbio := GetStringField(alice, Bio, \"42\")\n\n\tuassert.Equal(t, \"Alice foo\", name)\n\tuassert.Equal(t, \"https://example.com\", homepage)\n\tuassert.Equal(t, \"42\", bio)\n}\n\nfunc TestIntFields(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\n\t// Get before setting\n\tage := GetIntField(bob, Age, 25)\n\tuassert.Equal(t, 25, age)\n\n\t// Set new key\n\tupdated := SetIntField(Age, 30)\n\tuassert.Equal(t, updated, false)\n\n\t// Update the key\n\tupdated = SetIntField(Age, 30)\n\tuassert.Equal(t, updated, true)\n\n\t// Get after setting\n\tage = GetIntField(bob, Age, 25)\n\tuassert.Equal(t, 30, age)\n}\n\nfunc TestBoolFields(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(charlie))\n\n\t// Get before setting\n\thiring := GetBoolField(charlie, AvailableForHiring, false)\n\tuassert.Equal(t, false, hiring)\n\n\t// Set\n\tupdated := SetBoolField(AvailableForHiring, true)\n\tuassert.Equal(t, updated, false)\n\n\t// Update the key\n\tupdated = SetBoolField(AvailableForHiring, true)\n\tuassert.Equal(t, updated, true)\n\n\t// Get after setting\n\thiring = GetBoolField(charlie, AvailableForHiring, false)\n\tuassert.Equal(t, true, hiring)\n}\n\nfunc TestMultipleProfiles(t *testing.T) {\n\t// Set profile for user1\n\tstd.TestSetRealm(std.NewUserRealm(user1))\n\tupdated := SetStringField(DisplayName, \"User One\")\n\tuassert.Equal(t, updated, false)\n\n\t// Set profile for user2\n\tstd.TestSetRealm(std.NewUserRealm(user2))\n\tupdated = SetStringField(DisplayName, \"User Two\")\n\tuassert.Equal(t, updated, false)\n\n\t// Get profiles\n\tstd.TestSetRealm(std.NewUserRealm(user1)) // Switch back to user1\n\tname1 := GetStringField(user1, DisplayName, \"anon\")\n\tstd.TestSetRealm(std.NewUserRealm(user2)) // Switch back to user2\n\tname2 := GetStringField(user2, DisplayName, \"anon\")\n\n\tuassert.Equal(t, \"User One\", name1)\n\tuassert.Equal(t, \"User Two\", name2)\n}\n\nfunc TestArbitraryStringField(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(user1))\n\n\t// Set arbitrary string field\n\tupdated := SetStringField(\"MyEmail\", \"my@email.com\")\n\tuassert.Equal(t, updated, false)\n\n\tval := GetStringField(user1, \"MyEmail\", \"\")\n\tuassert.Equal(t, val, \"my@email.com\")\n}\n\nfunc TestArbitraryIntField(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(user1))\n\n\t// Set arbitrary int field\n\tupdated := SetIntField(\"MyIncome\", 100_000)\n\tuassert.Equal(t, updated, false)\n\n\tval := GetIntField(user1, \"MyIncome\", 0)\n\tuassert.Equal(t, val, 100_000)\n}\n\nfunc TestArbitraryBoolField(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(user1))\n\n\t// Set arbitrary int field\n\tupdated := SetBoolField(\"IsWinner\", true)\n\tuassert.Equal(t, updated, false)\n\n\tval := GetBoolField(user1, \"IsWinner\", false)\n\tuassert.Equal(t, val, true)\n}\n"},{"name":"render.gno","body":"package profile\n\nimport (\n\t\"bytes\"\n\t\"net/url\"\n\t\"std\"\n\n\t\"gno.land/p/demo/mux\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nconst (\n\tBaseURL = \"/r/demo/profile\"\n\tSetStringFieldURL = BaseURL + \"$help\u0026func=SetStringField\u0026field=%s\"\n\tSetIntFieldURL = BaseURL + \"$help\u0026func=SetIntField\u0026field=%s\"\n\tSetBoolFieldURL = BaseURL + \"$help\u0026func=SetBoolField\u0026field=%s\"\n\tViewAllFieldsURL = BaseURL + \":u/%s\"\n\tViewFieldURL = BaseURL + \":f/%s/%s\"\n)\n\nfunc homeHandler(res *mux.ResponseWriter, req *mux.Request) {\n\tvar b bytes.Buffer\n\n\tb.WriteString(\"## Setters\\n\")\n\tfor field := range stringFields {\n\t\tlink := ufmt.Sprintf(SetStringFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- [Set %s](%s)\\n\", field, link))\n\t}\n\n\tfor field := range intFields {\n\t\tlink := ufmt.Sprintf(SetIntFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- [Set %s](%s)\\n\", field, link))\n\t}\n\n\tfor field := range boolFields {\n\t\tlink := ufmt.Sprintf(SetBoolFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- [Set %s Field](%s)\\n\", field, link))\n\t}\n\n\tb.WriteString(\"\\n---\\n\\n\")\n\n\tres.Write(b.String())\n}\n\nfunc profileHandler(res *mux.ResponseWriter, req *mux.Request) {\n\tvar b bytes.Buffer\n\taddr := req.GetVar(\"addr\")\n\n\tb.WriteString(ufmt.Sprintf(\"# Profile %s\\n\", addr))\n\n\taddress := std.Address(addr)\n\n\tfor field := range stringFields {\n\t\tvalue := GetStringField(address, field, \"n/a\")\n\t\tlink := ufmt.Sprintf(SetStringFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- %s: %s [Edit](%s)\\n\", field, value, link))\n\t}\n\n\tfor field := range intFields {\n\t\tvalue := GetIntField(address, field, 0)\n\t\tlink := ufmt.Sprintf(SetIntFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- %s: %d [Edit](%s)\\n\", field, value, link))\n\t}\n\n\tfor field := range boolFields {\n\t\tvalue := GetBoolField(address, field, false)\n\t\tlink := ufmt.Sprintf(SetBoolFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- %s: %t [Edit](%s)\\n\", field, value, link))\n\t}\n\n\tres.Write(b.String())\n}\n\nfunc fieldHandler(res *mux.ResponseWriter, req *mux.Request) {\n\tvar b bytes.Buffer\n\taddr := req.GetVar(\"addr\")\n\tfield := req.GetVar(\"field\")\n\n\tb.WriteString(ufmt.Sprintf(\"# Field %s for %s\\n\", field, addr))\n\n\taddress := std.Address(addr)\n\tvalue := \"n/a\"\n\tvar editLink string\n\n\tif _, ok := stringFields[field]; ok {\n\t\tvalue = ufmt.Sprintf(\"%s\", GetStringField(address, field, \"n/a\"))\n\t\teditLink = ufmt.Sprintf(SetStringFieldURL+\"\u0026addr=%s\u0026value=%s\", field, addr, url.QueryEscape(value))\n\t} else if _, ok := intFields[field]; ok {\n\t\tvalue = ufmt.Sprintf(\"%d\", GetIntField(address, field, 0))\n\t\teditLink = ufmt.Sprintf(SetIntFieldURL+\"\u0026addr=%s\u0026value=%s\", field, addr, value)\n\t} else if _, ok := boolFields[field]; ok {\n\t\tvalue = ufmt.Sprintf(\"%t\", GetBoolField(address, field, false))\n\t\teditLink = ufmt.Sprintf(SetBoolFieldURL+\"\u0026addr=%s\u0026value=%s\", field, addr, value)\n\t}\n\n\tb.WriteString(ufmt.Sprintf(\"- %s: %s [Edit](%s)\\n\", field, value, editLink))\n\n\tres.Write(b.String())\n}\n\nfunc Render(path string) string {\n\treturn router.Render(path)\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":"releases_example","path":"gno.land/r/demo/releases_example","files":[{"name":"dummy.gno","body":"package releases_example\n\nfunc init() {\n\t// dummy example data\n\tchangelog.NewRelease(\n\t\t\"v1\",\n\t\t\"r/demo/examples_example_v1\",\n\t\t\"initial release\",\n\t)\n\tchangelog.NewRelease(\n\t\t\"v2\",\n\t\t\"r/demo/examples_example_v2\",\n\t\t\"various improvements\",\n\t)\n}\n"},{"name":"example.gno","body":"// this package demonstrates a way to manage contract releases.\npackage releases_example\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/releases\"\n)\n\nvar (\n\tchangelog = releases.NewChangelog(\"example_app\")\n\tadmin = std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\") // @administrator\n)\n\nfunc init() {\n\t// FIXME: admin = std.GetCreator()\n}\n\nfunc NewRelease(name, url, notes string) {\n\tcaller := std.OriginCaller()\n\tif caller != admin {\n\t\tpanic(\"restricted area\")\n\t}\n\tchangelog.NewRelease(name, url, notes)\n}\n\nfunc UpdateAdmin(address std.Address) {\n\tcaller := std.OriginCaller()\n\tif caller != admin {\n\t\tpanic(\"restricted area\")\n\t}\n\tadmin = address\n}\n\nfunc Render(path string) string {\n\treturn changelog.Render(path)\n}\n"},{"name":"releases0_filetest.gno","body":"package main\n\nimport (\n\t\"gno.land/p/demo/releases\"\n)\n\nfunc main() {\n\tprintln(\"-----------\")\n\tchangelog := releases.NewChangelog(\"example\")\n\tprint(changelog.Render(\"\"))\n\n\tprintln(\"-----------\")\n\tchangelog.NewRelease(\"v1\", \"r/blahblah\", \"* initial version\")\n\tprint(changelog.Render(\"\"))\n\n\tprintln(\"-----------\")\n\tchangelog.NewRelease(\"v2\", \"r/blahblah2\", \"* various improvements\\n* new shiny logo\")\n\tprint(changelog.Render(\"\"))\n\n\tprintln(\"-----------\")\n\tprint(changelog.Latest().Render())\n}\n\n// Output:\n// -----------\n// # example\n//\n// -----------\n// # example\n//\n// ## [example v1 (latest)](r/blahblah)\n//\n// * initial version\n//\n// -----------\n// # example\n//\n// ## [example v2 (latest)](r/blahblah2)\n//\n// * various improvements\n// * new shiny logo\n//\n// ## [example v1](r/blahblah)\n//\n// * initial version\n//\n// -----------\n// ## [example v2 (latest)](r/blahblah2)\n//\n// * various improvements\n// * new shiny logo\n"},{"name":"releases1_filetest.gno","body":"package main\n\nimport (\n\t\"gno.land/r/demo/releases_example\"\n)\n\nfunc main() {\n\tprintln(\"-----------\")\n\tprint(releases_example.Render(\"\"))\n\n\tprintln(\"-----------\")\n\tprint(releases_example.Render(\"v1\"))\n\n\tprintln(\"-----------\")\n\tprint(releases_example.Render(\"v42\"))\n}\n\n// Output:\n// -----------\n// # example_app\n//\n// ## [example_app v2 (latest)](r/demo/examples_example_v2)\n//\n// various improvements\n//\n// ## [example_app v1](r/demo/examples_example_v1)\n//\n// initial release\n//\n// -----------\n// ## [example_app v1](r/demo/examples_example_v1)\n//\n// initial release\n//\n// -----------\n// no such release\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":"tamagotchi","path":"gno.land/r/demo/tamagotchi","files":[{"name":"realm.gno","body":"package tamagotchi\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/tamagotchi\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar t *tamagotchi.Tamagotchi\n\nfunc init() {\n\tReset(\"gnome#0\")\n}\n\nfunc Reset(optionalName string) string {\n\tname := optionalName\n\tif name == \"\" {\n\t\theight := std.GetHeight()\n\t\tname = ufmt.Sprintf(\"gnome#%d\", height)\n\t}\n\n\tt = tamagotchi.New(name)\n\n\treturn ufmt.Sprintf(\"A new tamagotchi is born. Their name is %s %s.\", t.Name(), t.Face())\n}\n\nfunc Feed() string {\n\tt.Feed()\n\treturn t.Markdown()\n}\n\nfunc Play() string {\n\tt.Play()\n\treturn t.Markdown()\n}\n\nfunc Heal() string {\n\tt.Heal()\n\treturn t.Markdown()\n}\n\nfunc Render(path string) string {\n\ttama := t.Markdown()\n\tlinks := `Actions:\n* [Feed](/r/demo/tamagotchi$help\u0026func=Feed)\n* [Play](/r/demo/tamagotchi$help\u0026func=Play)\n* [Heal](/r/demo/tamagotchi$help\u0026func=Heal)\n* [Reset](/r/demo/tamagotchi$help\u0026func=Reset)\n`\n\n\treturn tama + \"\\n\\n\" + links\n}\n"},{"name":"z0_filetest.gno","body":"package main\n\nimport (\n\t\"gno.land/r/demo/tamagotchi\"\n)\n\nfunc main() {\n\ttamagotchi.Reset(\"tamagnotchi\")\n\tprintln(tamagotchi.Render(\"\"))\n}\n\n// Output:\n// # tamagnotchi 😃\n//\n// * age: 0\n// * hunger: 50\n// * happiness: 50\n// * health: 50\n// * sleepy: 0\n//\n// Actions:\n// * [Feed](/r/demo/tamagotchi$help\u0026func=Feed)\n// * [Play](/r/demo/tamagotchi$help\u0026func=Play)\n// * [Heal](/r/demo/tamagotchi$help\u0026func=Heal)\n// * [Reset](/r/demo/tamagotchi$help\u0026func=Reset)\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"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}} @@ -107,23 +107,23 @@ {"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"todolistrealm","path":"gno.land/r/demo/todolist","files":[{"name":"todolist.gno","body":"package todolistrealm\n\nimport (\n\t\"bytes\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/seqid\"\n\t\"gno.land/p/demo/todolist\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// State variables\nvar (\n\ttodolistTree *avl.Tree\n\ttlid seqid.ID\n)\n\n// Constructor\nfunc init() {\n\ttodolistTree = avl.NewTree()\n}\n\nfunc NewTodoList(title string) (int, string) {\n\t// Create new Todolist\n\ttl := todolist.NewTodoList(title)\n\t// Update AVL tree with new state\n\ttlid.Next()\n\ttodolistTree.Set(strconv.Itoa(int(tlid)), tl)\n\treturn int(tlid), \"created successfully\"\n}\n\nfunc AddTask(todolistID int, title string) string {\n\t// Get Todolist from AVL tree\n\ttl, ok := todolistTree.Get(strconv.Itoa(todolistID))\n\tif !ok {\n\t\tpanic(\"Todolist not found\")\n\t}\n\n\t// get the number of tasks in the todolist\n\tid := tl.(*todolist.TodoList).Tasks.Size()\n\n\t// create the task\n\ttask := todolist.NewTask(title)\n\n\t// Cast raw data from tree into Todolist struct\n\ttl.(*todolist.TodoList).AddTask(id, task)\n\n\treturn \"task added successfully\"\n}\n\nfunc ToggleTaskStatus(todolistID int, taskID int) string {\n\t// Get Todolist from AVL tree\n\ttl, ok := todolistTree.Get(strconv.Itoa(todolistID))\n\tif !ok {\n\t\tpanic(\"Todolist not found\")\n\t}\n\n\t// Get the task from the todolist\n\ttask, found := tl.(*todolist.TodoList).Tasks.Get(strconv.Itoa(taskID))\n\tif !found {\n\t\tpanic(\"Task not found\")\n\t}\n\n\t// Change the status of the task\n\ttodolist.ToggleTaskStatus(task.(*todolist.Task))\n\n\treturn \"task status changed successfully\"\n}\n\nfunc RemoveTask(todolistID int, taskID int) string {\n\t// Get Todolist from AVL tree\n\ttl, ok := todolistTree.Get(strconv.Itoa(todolistID))\n\tif !ok {\n\t\tpanic(\"Todolist not found\")\n\t}\n\n\t// Get the task from the todolist\n\t_, ok = tl.(*todolist.TodoList).Tasks.Get(strconv.Itoa(taskID))\n\tif !ok {\n\t\tpanic(\"Task not found\")\n\t}\n\n\t// Change the status of the task\n\ttl.(*todolist.TodoList).RemoveTask(strconv.Itoa(taskID))\n\n\treturn \"task status changed successfully\"\n}\n\nfunc RemoveTodoList(todolistID int) string {\n\t// Get Todolist from AVL tree\n\t_, ok := todolistTree.Get(strconv.Itoa(todolistID))\n\tif !ok {\n\t\tpanic(\"Todolist not found\")\n\t}\n\n\t// Remove the todolist\n\ttodolistTree.Remove(strconv.Itoa(todolistID))\n\n\treturn \"Todolist removed successfully\"\n}\n\nfunc Render(path string) string {\n\tif path == \"\" {\n\t\treturn renderHomepage()\n\t}\n\n\treturn \"unknown page\"\n}\n\nfunc renderHomepage() string {\n\t// Define empty buffer\n\tvar b bytes.Buffer\n\n\tb.WriteString(\"# Welcome to ToDolist\\n\\n\")\n\n\t// If no todolists have been created\n\tif todolistTree.Size() == 0 {\n\t\tb.WriteString(\"### No todolists available currently!\")\n\t\treturn b.String()\n\t}\n\n\t// Iterate through AVL tree\n\ttodolistTree.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t// cast raw data from tree into Todolist struct\n\t\ttl := value.(*todolist.TodoList)\n\n\t\t// Add Todolist name\n\t\tb.WriteString(\n\t\t\tufmt.Sprintf(\n\t\t\t\t\"## Todolist #%s: %s\\n\",\n\t\t\t\tkey, // Todolist ID\n\t\t\t\ttl.GetTodolistTitle(),\n\t\t\t),\n\t\t)\n\n\t\t// Add Todolist owner\n\t\tb.WriteString(\n\t\t\tufmt.Sprintf(\n\t\t\t\t\"#### Todolist owner : %s\\n\",\n\t\t\t\ttl.GetTodolistOwner(),\n\t\t\t),\n\t\t)\n\n\t\t// List all todos that are currently Todolisted\n\t\tif todos := tl.GetTasks(); len(todos) \u003e 0 {\n\t\t\tb.WriteString(\n\t\t\t\tufmt.Sprintf(\"Currently Todo tasks: %d\\n\\n\", len(todos)),\n\t\t\t)\n\n\t\t\tfor index, todo := range todos {\n\t\t\t\tb.WriteString(\n\t\t\t\t\tufmt.Sprintf(\"#%d - %s \", index, todo.Title),\n\t\t\t\t)\n\t\t\t\t// displays a checked box if task is marked as done, an empty box if not\n\t\t\t\tif todo.Done {\n\t\t\t\t\tb.WriteString(\n\t\t\t\t\t\t\"☑\\n\\n\",\n\t\t\t\t\t)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tb.WriteString(\n\t\t\t\t\t\"☐\\n\\n\",\n\t\t\t\t)\n\t\t\t}\n\t\t} else {\n\t\t\tb.WriteString(\"No tasks in this list currently\\n\")\n\t\t}\n\n\t\tb.WriteString(\"\\n\")\n\t\treturn false\n\t})\n\n\treturn b.String()\n}\n"},{"name":"todolist_test.gno","body":"package todolistrealm\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/todolist\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\tnode interface{}\n\ttdl *todolist.TodoList\n)\n\nfunc TestNewTodoList(t *testing.T) {\n\ttitle := \"My Todo List\"\n\ttlid, _ := NewTodoList(title)\n\tuassert.Equal(t, 1, tlid, \"tlid does not match\")\n\n\t// get the todolist node from the tree\n\tnode, _ = todolistTree.Get(strconv.Itoa(tlid))\n\t// convert the node to a TodoList struct\n\ttdl = node.(*todolist.TodoList)\n\n\tuassert.Equal(t, title, tdl.Title, \"title does not match\")\n\tuassert.Equal(t, 1, tlid, \"tlid does not match\")\n\tuassert.Equal(t, tdl.Owner.String(), std.OriginCaller().String(), \"owner does not match\")\n\tuassert.Equal(t, 0, len(tdl.GetTasks()), \"Expected no tasks in the todo list\")\n}\n\nfunc TestAddTask(t *testing.T) {\n\tAddTask(1, \"Task 1\")\n\n\ttasks := tdl.GetTasks()\n\tuassert.Equal(t, 1, len(tasks), \"total task does not match\")\n\tuassert.Equal(t, \"Task 1\", tasks[0].Title, \"task title does not match\")\n\tuassert.False(t, tasks[0].Done, \"Expected task to be not done\")\n}\n\nfunc TestToggleTaskStatus(t *testing.T) {\n\tToggleTaskStatus(1, 0)\n\ttask := tdl.GetTasks()[0]\n\tuassert.True(t, task.Done, \"Expected task to be done, but it is not marked as done\")\n\n\tToggleTaskStatus(1, 0)\n\tuassert.False(t, task.Done, \"Expected task to be not done, but it is marked as done\")\n}\n\nfunc TestRemoveTask(t *testing.T) {\n\tRemoveTask(1, 0)\n\ttasks := tdl.GetTasks()\n\tuassert.Equal(t, 0, len(tasks), \"Expected no tasks in the todo list\")\n}\n\nfunc TestRemoveTodoList(t *testing.T) {\n\tRemoveTodoList(1)\n\tuassert.Equal(t, 0, todolistTree.Size(), \"Expected no tasks in the todo list\")\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":"types","path":"gno.land/r/demo/types","files":[{"name":"types.gno","body":"// package to test types behavior in various conditions (TXs, imports).\npackage types\n\nimport (\n\t\"errors\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\nvar (\n\tgInt int = -42\n\tgUint uint = 42\n\tgString string = \"a string\"\n\tgStringSlice []string = []string{\"a\", \"string\", \"slice\"}\n\tgError error = errors.New(\"an error\")\n\tgIntSlice []int = []int{-42, 0, 42}\n\tgUintSlice []uint = []uint{0, 42, 84}\n\tgTree avl.Tree\n\t// gInterface = interface{}{-42, \"a string\", uint(42)}\n)\n\nfunc init() {\n\tgTree.Set(\"a\", \"content of A\")\n\tgTree.Set(\"b\", \"content of B\")\n}\n\nfunc Noop() {}\nfunc RetTimeNow() time.Time { return time.Now() }\nfunc RetString() string { return gString }\nfunc RetStringPointer() *string { return \u0026gString }\nfunc RetUint() uint { return gUint }\nfunc RetInt() int { return gInt }\nfunc RetUintPointer() *uint { return \u0026gUint }\nfunc RetIntPointer() *int { return \u0026gInt }\nfunc RetTree() avl.Tree { return gTree }\nfunc RetIntSlice() []int { return gIntSlice }\nfunc RetUintSlice() []uint { return gUintSlice }\nfunc RetStringSlice() []string { return gStringSlice }\nfunc RetError() error { return gError }\nfunc Panic() { panic(\"PANIC!\") }\n\n// TODO: floats\n// TODO: typed errors\n// TODO: ret interface\n// TODO: recover\n// TODO: take types as input\n\nfunc Render(path string) string {\n\treturn \"package to test data types.\"\n}\n"},{"name":"types_test.gno","body":"package types\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":"ui","path":"gno.land/r/demo/ui","files":[{"name":"ui.gno","body":"package ui\n\nimport \"gno.land/p/demo/ui\"\n\nfunc Render(path string) string {\n\t// TODO: build this realm as a demo one with one page per feature.\n\n\t// TODO: pagination\n\t// TODO: non-standard markdown\n\t// TODO: error, warn\n\t// TODO: header\n\t// TODO: HTML\n\t// TODO: toc\n\t// TODO: forms\n\t// TODO: comments\n\n\tdom := ui.DOM{\n\t\tPrefix: \"r/demo/ui:\",\n\t}\n\n\tdom.Title = \"UI Demo\"\n\n\tdom.Header.Append(ui.Breadcrumb{\n\t\tui.Link{Text: \"foo\", Path: \"foo\"},\n\t\tui.Link{Text: \"bar\", Path: \"foo/bar\"},\n\t})\n\n\tdom.Body.Append(\n\t\tui.Paragraph(\"Simple UI demonstration.\"),\n\t\tui.BulletList{\n\t\t\tui.Text(\"a text\"),\n\t\t\tui.Link{Text: \"a relative link\", Path: \"foobar\"},\n\t\t\tui.Text(\"another text\"),\n\t\t\t// ui.H1(\"a H1 text\"),\n\t\t\tui.Bold(\"a bold text\"),\n\t\t\tui.Italic(\"italic text\"),\n\t\t\tui.Text(\"raw markdown with **bold** text in the middle.\"),\n\t\t\tui.Code(\"some inline code\"),\n\t\t\tui.Link{Text: \"a remote link\", URL: \"https://gno.land\"},\n\t\t},\n\t)\n\n\tdom.Footer.Append(ui.Text(\"I'm the footer.\"))\n\tdom.Body.Append(ui.Text(\"another string.\"))\n\tdom.Body.Append(ui.Paragraph(\"a paragraph.\"), ui.HR{})\n\n\treturn dom.String()\n}\n"},{"name":"ui_test.gno","body":"package ui\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestRender(t *testing.T) {\n\tgot := Render(\"\")\n\texpected := \"# UI Demo\\n\\n[foo](r/demo/ui:foo) / [bar](r/demo/ui:foo/bar)\\n\\n\\nSimple UI demonstration.\\n\\n- a text\\n- [a relative link](r/demo/ui:foobar)\\n- another text\\n- **a bold text**\\n- _italic text_\\n- raw markdown with **bold** text in the middle.\\n- `some inline code`\\n- [a remote link](https://gno.land)\\n\\nanother string.\\n\\na paragraph.\\n\\n\\n---\\n\\n\\nI'm the footer.\\n\\n\"\n\tuassert.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":"userbook","path":"gno.land/r/demo/userbook","files":[{"name":"userbook.gno","body":"// This realm demonstrates a small userbook system working with gnoweb\npackage userbook\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/mux\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype Signup struct {\n\taccount string\n\theight int64\n}\n\n// signups - keep a slice of signed up addresses efficient pagination\nvar signups []Signup\n\n// tracker - keep track of who signed up\nvar (\n\ttracker *avl.Tree\n\trouter *mux.Router\n)\n\nconst (\n\tdefaultPageSize = 20\n\tpathArgument = \"number\"\n\tsubPath = \"page/{\" + pathArgument + \"}\"\n\tsignUpEvent = \"SignUp\"\n)\n\nfunc init() {\n\t// Set up tracker tree\n\ttracker = avl.NewTree()\n\n\t// Set up route handling\n\trouter = mux.NewRouter()\n\trouter.HandleFunc(\"\", renderHelper)\n\trouter.HandleFunc(subPath, renderHelper)\n\n\t// Sign up the deployer\n\tSignUp()\n}\n\nfunc SignUp() string {\n\t// Get transaction caller\n\tcaller := std.PrevRealm().Addr().String()\n\theight := std.GetHeight()\n\n\t// Check if the user is already signed up\n\tif _, exists := tracker.Get(caller); exists {\n\t\tpanic(caller + \" is already signed up!\")\n\t}\n\n\t// Sign up the user\n\ttracker.Set(caller, struct{}{})\n\tsignup := Signup{\n\t\tcaller,\n\t\theight,\n\t}\n\n\tsignups = append(signups, signup)\n\tstd.Emit(signUpEvent, \"SignedUpAccount\", signup.account)\n\n\treturn ufmt.Sprintf(\"%s added to userbook up at block #%d!\", signup.account, signup.height)\n}\n\nfunc GetSignupsInRange(page, pageSize int) ([]Signup, int) {\n\tif page \u003c 1 {\n\t\tpanic(\"page number cannot be less than 1\")\n\t}\n\n\tif pageSize \u003c 1 || pageSize \u003e 50 {\n\t\tpanic(\"page size must be from 1 to 50\")\n\t}\n\n\t// Pagination\n\t// Calculate indexes\n\tstartIndex := (page - 1) * pageSize\n\tendIndex := startIndex + pageSize\n\n\t// If page does not contain any users\n\tif startIndex \u003e= len(signups) {\n\t\treturn nil, -1\n\t}\n\n\t// If page contains fewer users than the page size\n\tif endIndex \u003e len(signups) {\n\t\tendIndex = len(signups)\n\t}\n\n\treturn signups[startIndex:endIndex], endIndex\n}\n\nfunc renderHelper(res *mux.ResponseWriter, req *mux.Request) {\n\ttotalSignups := len(signups)\n\tres.Write(\"# Welcome to UserBook!\\n\\n\")\n\n\t// Get URL parameter\n\tpage, err := strconv.Atoi(req.GetVar(\"number\"))\n\tif err != nil {\n\t\tpage = 1 // render first page on bad input\n\t}\n\n\t// Fetch paginated signups\n\tfetchedSignups, endIndex := GetSignupsInRange(page, defaultPageSize)\n\t// Handle empty page case\n\tif len(fetchedSignups) == 0 {\n\t\tres.Write(\"No users on this page!\\n\\n\")\n\t\tres.Write(\"---\\n\\n\")\n\t\tres.Write(\"[Back to Page #1](/r/demo/userbook:page/1)\\n\\n\")\n\t\treturn\n\t}\n\n\t// Write page title\n\tres.Write(ufmt.Sprintf(\"## UserBook - Page #%d:\\n\\n\", page))\n\n\t// Write signups\n\tpageStartIndex := defaultPageSize * (page - 1)\n\tfor i, signup := range fetchedSignups {\n\t\tout := ufmt.Sprintf(\"#### User #%d - %s - signed up at Block #%d\\n\", pageStartIndex+i, signup.account, signup.height)\n\t\tres.Write(out)\n\t}\n\n\tres.Write(\"---\\n\\n\")\n\n\t// Write UserBook info\n\tlatestSignupIndex := totalSignups - 1\n\tres.Write(ufmt.Sprintf(\"#### Total users: %d\\n\", totalSignups))\n\tres.Write(ufmt.Sprintf(\"#### Latest signup: User #%d at Block #%d\\n\", latestSignupIndex, signups[latestSignupIndex].height))\n\n\tres.Write(\"---\\n\\n\")\n\n\t// Write page number\n\tres.Write(ufmt.Sprintf(\"You're viewing page #%d\", page))\n\n\t// Write navigation buttons\n\tvar prevPage string\n\tvar nextPage string\n\t// If we are on any page that is not the first page\n\tif page \u003e 1 {\n\t\tprevPage = ufmt.Sprintf(\" - [Previous page](/r/demo/userbook:page/%d)\", page-1)\n\t}\n\n\t// If there are more pages after the current one\n\tif endIndex \u003c totalSignups {\n\t\tnextPage = ufmt.Sprintf(\" - [Next page](/r/demo/userbook:page/%d)\\n\\n\", page+1)\n\t}\n\n\tres.Write(prevPage)\n\tres.Write(nextPage)\n}\n\nfunc Render(path string) string {\n\treturn router.Render(path)\n}\n"},{"name":"userbook_test.gno","body":"package userbook\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nfunc TestRender(t *testing.T) {\n\t// Sign up 20 users + deployer\n\tfor i := 0; i \u003c 20; i++ {\n\t\taddrName := ufmt.Sprintf(\"test%d\", i)\n\t\tcaller := testutils.TestAddress(addrName)\n\t\tstd.TestSetOriginCaller(caller)\n\t\tSignUp()\n\t}\n\n\ttestCases := []struct {\n\t\tname string\n\t\tnextPage bool\n\t\tprevPage bool\n\t\tpath string\n\t\texpectedNumberOfUsers int\n\t}{\n\t\t{\n\t\t\tname: \"1st page render\",\n\t\t\tnextPage: true,\n\t\t\tprevPage: false,\n\t\t\tpath: \"page/1\",\n\t\t\texpectedNumberOfUsers: 20,\n\t\t},\n\t\t{\n\t\t\tname: \"2nd page render\",\n\t\t\tnextPage: false,\n\t\t\tprevPage: true,\n\t\t\tpath: \"page/2\",\n\t\t\texpectedNumberOfUsers: 1,\n\t\t},\n\t\t{\n\t\t\tname: \"Invalid path render\",\n\t\t\tnextPage: true,\n\t\t\tprevPage: false,\n\t\t\tpath: \"page/invalidtext\",\n\t\t\texpectedNumberOfUsers: 20,\n\t\t},\n\t\t{\n\t\t\tname: \"Empty Page\",\n\t\t\tnextPage: false,\n\t\t\tprevPage: false,\n\t\t\tpath: \"page/1000\",\n\t\t\texpectedNumberOfUsers: 0,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := Render(tc.path)\n\t\t\tnumUsers := countUsers(got)\n\n\t\t\tif tc.prevPage \u0026\u0026 !strings.Contains(got, \"Previous page\") {\n\t\t\t\tt.Fatalf(\"expected to find Previous page, didn't find it\")\n\t\t\t}\n\t\t\tif tc.nextPage \u0026\u0026 !strings.Contains(got, \"Next page\") {\n\t\t\t\tt.Fatalf(\"expected to find Next page, didn't find it\")\n\t\t\t}\n\n\t\t\tif tc.expectedNumberOfUsers != numUsers {\n\t\t\t\tt.Fatalf(\"expected %d, got %d users\", tc.expectedNumberOfUsers, numUsers)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc countUsers(input string) int {\n\treturn strings.Count(input, \"#### User #\")\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":"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/users\"\n)\n\nvar (\n\tbanker *grc20.Banker = grc20.NewBanker(\"wrapped GNOT\", \"wugnot\", 0)\n\tToken = banker.Token()\n)\n\nconst (\n\tugnotMinDeposit uint64 = 1000\n\twugnotMinDeposit uint64 = 1\n)\n\nfunc Deposit() {\n\tcaller := std.PrevRealm().Addr()\n\tsent := std.OriginSend()\n\tamount := sent.AmountOf(\"ugnot\")\n\n\trequire(uint64(amount) \u003e= ugnotMinDeposit, ufmt.Sprintf(\"Deposit below minimum: %d/%d ugnot.\", amount, ugnotMinDeposit))\n\tcheckErr(banker.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(banker.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 banker.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\tcheckErr(Token.Transfer(toAddr, amount))\n}\n\nfunc Approve(spender pusers.AddressOrName, amount uint64) {\n\tspenderAddr := users.Resolve(spender)\n\tcheckErr(Token.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(Token.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.TestSetOriginCaller(addr1)\n\tstd.TestSetOriginSend(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.TestSetOriginCaller(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=200000000 |\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=200000000 |\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=200004242 |\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":""}} -{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"gnoblog","path":"gno.land/r/gnoland/blog","files":[{"name":"admin.gno","body":"package gnoblog\n\nimport (\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/r/gov/dao/bridge\"\n)\n\nvar (\n\tadminAddr std.Address\n\tmoderatorList avl.Tree\n\tcommenterList avl.Tree\n\tinPause bool\n)\n\nfunc init() {\n\t// adminAddr = std.OriginCaller() // FIXME: find a way to use this from the main's genesis.\n\tadminAddr = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"\n}\n\nfunc AdminSetAdminAddr(addr std.Address) {\n\tassertIsAdmin()\n\tadminAddr = addr\n}\n\nfunc AdminSetInPause(state bool) {\n\tassertIsAdmin()\n\tinPause = state\n}\n\nfunc AdminAddModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), true)\n}\n\nfunc AdminRemoveModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), false) // FIXME: delete instead?\n}\n\nfunc NewPostExecutor(slug, title, body, publicationDate, authors, tags string) dao.Executor {\n\tcallback := func() error {\n\t\taddPost(std.PrevRealm().Addr(), slug, title, body, publicationDate, authors, tags)\n\n\t\treturn nil\n\t}\n\n\treturn bridge.GovDAO().NewGovDAOExecutor(callback)\n}\n\nfunc ModAddPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\tcaller := std.OriginCaller()\n\taddPost(caller, slug, title, body, publicationDate, authors, tags)\n}\n\nfunc addPost(caller std.Address, slug, title, body, publicationDate, authors, tags string) {\n\tvar tagList []string\n\tif tags != \"\" {\n\t\ttagList = strings.Split(tags, \",\")\n\t}\n\tvar authorList []string\n\tif authors != \"\" {\n\t\tauthorList = strings.Split(authors, \",\")\n\t}\n\n\terr := b.NewPost(caller, slug, title, body, publicationDate, authorList, tagList)\n\n\tcheckErr(err)\n}\n\nfunc ModEditPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.GetPost(slug).Update(title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc ModRemovePost(slug string) {\n\tassertIsModerator()\n\n\tb.RemovePost(slug)\n}\n\nfunc ModAddCommenter(addr std.Address) {\n\tassertIsModerator()\n\tcommenterList.Set(addr.String(), true)\n}\n\nfunc ModDelCommenter(addr std.Address) {\n\tassertIsModerator()\n\tcommenterList.Set(addr.String(), false) // FIXME: delete instead?\n}\n\nfunc ModDelComment(slug string, index int) {\n\tassertIsModerator()\n\n\terr := b.GetPost(slug).DeleteComment(index)\n\tcheckErr(err)\n}\n\nfunc isAdmin(addr std.Address) bool {\n\treturn addr == adminAddr\n}\n\nfunc isModerator(addr std.Address) bool {\n\t_, found := moderatorList.Get(addr.String())\n\treturn found\n}\n\nfunc isCommenter(addr std.Address) bool {\n\t_, found := commenterList.Get(addr.String())\n\treturn found\n}\n\nfunc assertIsAdmin() {\n\tcaller := std.OriginCaller()\n\tif !isAdmin(caller) {\n\t\tpanic(\"access restricted.\")\n\t}\n}\n\nfunc assertIsModerator() {\n\tcaller := std.OriginCaller()\n\tif isAdmin(caller) || isModerator(caller) {\n\t\treturn\n\t}\n\tpanic(\"access restricted\")\n}\n\nfunc assertIsCommenter() {\n\tcaller := std.OriginCaller()\n\tif isAdmin(caller) || isModerator(caller) || isCommenter(caller) {\n\t\treturn\n\t}\n\tpanic(\"access restricted\")\n}\n\nfunc assertNotInPause() {\n\tif inPause {\n\t\tpanic(\"access restricted (pause)\")\n\t}\n}\n"},{"name":"gnoblog.gno","body":"package gnoblog\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/blog\"\n)\n\nvar b = \u0026blog.Blog{\n\tTitle: \"Gnoland's Blog\",\n\tPrefix: \"/r/gnoland/blog:\",\n}\n\nfunc AddComment(postSlug, comment string) {\n\tassertIsCommenter()\n\tassertNotInPause()\n\n\tcaller := std.OriginCaller()\n\terr := b.GetPost(postSlug).AddComment(caller, comment)\n\tcheckErr(err)\n}\n\nfunc Render(path string) string {\n\treturn b.Render(path)\n}\n\nfunc RenderLastPostsWidget(limit int) string {\n\treturn b.RenderLastPostsWidget(limit)\n}\n\nfunc PostExists(slug string) bool {\n\tif b.GetPost(slug) == nil {\n\t\treturn false\n\t}\n\treturn true\n}\n"},{"name":"gnoblog_test.gno","body":"package gnoblog\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestPackage(t *testing.T) {\n\tstd.TestSetOriginCaller(std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"))\n\n\tauthor := std.OriginCaller()\n\n\t// by default, no posts.\n\t{\n\t\tgot := Render(\"\")\n\t\texpected := `\n# Gnoland's Blog\n\nNo posts.\n`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// create two posts, list post.\n\t{\n\t\tModAddPost(\"slug1\", \"title1\", \"body1\", \"2022-05-20T13:17:22Z\", \"moul\", \"tag1,tag2\")\n\t\tModAddPost(\"slug2\", \"title2\", \"body2\", \"2022-05-20T13:17:23Z\", \"moul\", \"tag1,tag3\")\n\t\tgot := Render(\"\")\n\t\texpected := `\n\t# Gnoland's Blog\n\n\u003cdiv class='columns-3'\u003e\u003cdiv\u003e\n\n### [title2](/r/gnoland/blog:p/slug2)\n 20 May 2022\n\u003c/div\u003e\u003cdiv\u003e\n\n### [title1](/r/gnoland/blog:p/slug1)\n 20 May 2022\n\u003c/div\u003e\u003c/div\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// view post.\n\t{\n\t\tgot := Render(\"p/slug2\")\n\t\texpected := `\n\u003cmain class='gno-tmpl-page'\u003e\n\n# title2\n\nbody2\n\n---\n\nTags: [#tag1](/r/gnoland/blog:t/tag1) [#tag3](/r/gnoland/blog:t/tag3)\n\nWritten by moul on 20 May 2022\n\nPublished by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq to Gnoland's Blog\n\n---\n\u003cdetails\u003e\u003csummary\u003eComment section\u003c/summary\u003e\n\n\u003c/details\u003e\n\u003c/main\u003e\n\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// list by tags.\n\t{\n\t\tgot := Render(\"t/invalid\")\n\t\texpected := \"# [Gnoland's Blog](/r/gnoland/blog:) / t / invalid\\n\\nNo posts.\"\n\t\tassertMDEquals(t, got, expected)\n\n\t\tgot = Render(\"t/tag2\")\n\t\texpected = `\n# [Gnoland's Blog](/r/gnoland/blog:) / t / tag2\n\n\u003cdiv\u003e\n\n### [title1](/r/gnoland/blog:p/slug1)\n 20 May 2022\n\u003c/div\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// add comments.\n\t{\n\t\tAddComment(\"slug1\", \"comment1\")\n\t\tAddComment(\"slug2\", \"comment2\")\n\t\tAddComment(\"slug1\", \"comment3\")\n\t\tAddComment(\"slug2\", \"comment4\")\n\t\tAddComment(\"slug1\", \"comment5\")\n\t\tgot := Render(\"p/slug2\")\n\t\texpected := `\u003cmain class='gno-tmpl-page'\u003e\n\n# title2\n\nbody2\n\n---\n\nTags: [#tag1](/r/gnoland/blog:t/tag1) [#tag3](/r/gnoland/blog:t/tag3)\n\nWritten by moul on 20 May 2022\n\nPublished by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq to Gnoland's Blog\n\n---\n\u003cdetails\u003e\u003csummary\u003eComment section\u003c/summary\u003e\n\n\u003ch5\u003ecomment4\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003ch5\u003ecomment2\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003c/details\u003e\n\u003c/main\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// edit post.\n\t{\n\t\toldTitle := \"title2\"\n\t\toldDate := \"2022-05-20T13:17:23Z\"\n\n\t\tModEditPost(\"slug2\", oldTitle, \"body2++\", oldDate, \"manfred\", \"tag1,tag4\")\n\t\tgot := Render(\"p/slug2\")\n\t\texpected := `\u003cmain class='gno-tmpl-page'\u003e\n\n# title2\n\nbody2++\n\n---\n\nTags: [#tag1](/r/gnoland/blog:t/tag1) [#tag4](/r/gnoland/blog:t/tag4)\n\nWritten by manfred on 20 May 2022\n\nPublished by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq to Gnoland's Blog\n\n---\n\u003cdetails\u003e\u003csummary\u003eComment section\u003c/summary\u003e\n\n\u003ch5\u003ecomment4\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003ch5\u003ecomment2\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003c/details\u003e\n\u003c/main\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\n\t\thome := Render(\"\")\n\n\t\tif strings.Count(home, oldTitle) != 1 {\n\t\t\tt.Errorf(\"post not edited properly\")\n\t\t}\n\t\t// Edits work everything except title, slug, and publicationDate\n\t\t// Edits to the above will cause duplication on the blog home page\n\t}\n\n\t{ // Test remove functionality\n\t\ttitle := \"example title\"\n\t\tslug := \"testSlug1\"\n\t\tModAddPost(slug, title, \"body1\", \"2022-05-25T13:17:22Z\", \"moul\", \"tag1,tag2\")\n\n\t\tgot := Render(\"\")\n\n\t\tif !strings.Contains(got, title) {\n\t\t\tt.Errorf(\"post was not added properly\")\n\t\t}\n\n\t\tpostRender := Render(\"p/\" + slug)\n\n\t\tif !strings.Contains(postRender, title) {\n\t\t\tt.Errorf(\"post not rendered properly\")\n\t\t}\n\n\t\tModRemovePost(slug)\n\t\tgot = Render(\"\")\n\n\t\tif strings.Contains(got, title) {\n\t\t\tt.Errorf(\"post was not removed\")\n\t\t}\n\n\t\tpostRender = Render(\"p/\" + slug)\n\n\t\tassertMDEquals(t, postRender, \"404\")\n\t}\n\n\t// TODO: pagination.\n\t// TODO: ?format=...\n\n\t// all 404s\n\t{\n\t\tnotFoundPaths := []string{\n\t\t\t\"p/slug3\",\n\t\t\t\"p\",\n\t\t\t\"p/\",\n\t\t\t\"x/x\",\n\t\t\t\"t\",\n\t\t\t\"t/\",\n\t\t\t\"/\",\n\t\t\t\"p/slug1/\",\n\t\t}\n\t\tfor _, notFoundPath := range notFoundPaths {\n\t\t\tgot := Render(notFoundPath)\n\t\t\texpected := \"404\"\n\t\t\tif got != expected {\n\t\t\t\tt.Errorf(\"path %q: expected %q, got %q.\", notFoundPath, expected, got)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc assertMDEquals(t *testing.T, got, expected string) {\n\tt.Helper()\n\texpected = strings.TrimSpace(expected)\n\tgot = strings.TrimSpace(got)\n\tif expected != got {\n\t\tt.Errorf(\"invalid render output.\\nexpected %q.\\ngot %q.\", expected, got)\n\t}\n}\n"},{"name":"util.gno","body":"package gnoblog\n\nfunc checkErr(err error) {\n\tif err != nil {\n\t\tpanic(err)\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":"userbook","path":"gno.land/r/demo/userbook","files":[{"name":"userbook.gno","body":"// This realm demonstrates a small userbook system working with gnoweb\npackage userbook\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/mux\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype Signup struct {\n\taccount string\n\theight int64\n}\n\n// signups - keep a slice of signed up addresses efficient pagination\nvar signups []Signup\n\n// tracker - keep track of who signed up\nvar (\n\ttracker *avl.Tree\n\trouter *mux.Router\n)\n\nconst (\n\tdefaultPageSize = 20\n\tpathArgument = \"number\"\n\tsubPath = \"page/{\" + pathArgument + \"}\"\n\tsignUpEvent = \"SignUp\"\n)\n\nfunc init() {\n\t// Set up tracker tree\n\ttracker = avl.NewTree()\n\n\t// Set up route handling\n\trouter = mux.NewRouter()\n\trouter.HandleFunc(\"\", renderHelper)\n\trouter.HandleFunc(subPath, renderHelper)\n\n\t// Sign up the deployer\n\tSignUp()\n}\n\nfunc SignUp() string {\n\t// Get transaction caller\n\tcaller := std.PreviousRealm().Addr().String()\n\theight := std.GetHeight()\n\n\t// Check if the user is already signed up\n\tif _, exists := tracker.Get(caller); exists {\n\t\tpanic(caller + \" is already signed up!\")\n\t}\n\n\t// Sign up the user\n\ttracker.Set(caller, struct{}{})\n\tsignup := Signup{\n\t\tcaller,\n\t\theight,\n\t}\n\n\tsignups = append(signups, signup)\n\tstd.Emit(signUpEvent, \"SignedUpAccount\", signup.account)\n\n\treturn ufmt.Sprintf(\"%s added to userbook up at block #%d!\", signup.account, signup.height)\n}\n\nfunc GetSignupsInRange(page, pageSize int) ([]Signup, int) {\n\tif page \u003c 1 {\n\t\tpanic(\"page number cannot be less than 1\")\n\t}\n\n\tif pageSize \u003c 1 || pageSize \u003e 50 {\n\t\tpanic(\"page size must be from 1 to 50\")\n\t}\n\n\t// Pagination\n\t// Calculate indexes\n\tstartIndex := (page - 1) * pageSize\n\tendIndex := startIndex + pageSize\n\n\t// If page does not contain any users\n\tif startIndex \u003e= len(signups) {\n\t\treturn nil, -1\n\t}\n\n\t// If page contains fewer users than the page size\n\tif endIndex \u003e len(signups) {\n\t\tendIndex = len(signups)\n\t}\n\n\treturn signups[startIndex:endIndex], endIndex\n}\n\nfunc renderHelper(res *mux.ResponseWriter, req *mux.Request) {\n\ttotalSignups := len(signups)\n\tres.Write(\"# Welcome to UserBook!\\n\\n\")\n\n\t// Get URL parameter\n\tpage, err := strconv.Atoi(req.GetVar(\"number\"))\n\tif err != nil {\n\t\tpage = 1 // render first page on bad input\n\t}\n\n\t// Fetch paginated signups\n\tfetchedSignups, endIndex := GetSignupsInRange(page, defaultPageSize)\n\t// Handle empty page case\n\tif len(fetchedSignups) == 0 {\n\t\tres.Write(\"No users on this page!\\n\\n\")\n\t\tres.Write(\"---\\n\\n\")\n\t\tres.Write(\"[Back to Page #1](/r/demo/userbook:page/1)\\n\\n\")\n\t\treturn\n\t}\n\n\t// Write page title\n\tres.Write(ufmt.Sprintf(\"## UserBook - Page #%d:\\n\\n\", page))\n\n\t// Write signups\n\tpageStartIndex := defaultPageSize * (page - 1)\n\tfor i, signup := range fetchedSignups {\n\t\tout := ufmt.Sprintf(\"#### User #%d - %s - signed up at Block #%d\\n\", pageStartIndex+i, signup.account, signup.height)\n\t\tres.Write(out)\n\t}\n\n\tres.Write(\"---\\n\\n\")\n\n\t// Write UserBook info\n\tlatestSignupIndex := totalSignups - 1\n\tres.Write(ufmt.Sprintf(\"#### Total users: %d\\n\", totalSignups))\n\tres.Write(ufmt.Sprintf(\"#### Latest signup: User #%d at Block #%d\\n\", latestSignupIndex, signups[latestSignupIndex].height))\n\n\tres.Write(\"---\\n\\n\")\n\n\t// Write page number\n\tres.Write(ufmt.Sprintf(\"You're viewing page #%d\", page))\n\n\t// Write navigation buttons\n\tvar prevPage string\n\tvar nextPage string\n\t// If we are on any page that is not the first page\n\tif page \u003e 1 {\n\t\tprevPage = ufmt.Sprintf(\" - [Previous page](/r/demo/userbook:page/%d)\", page-1)\n\t}\n\n\t// If there are more pages after the current one\n\tif endIndex \u003c totalSignups {\n\t\tnextPage = ufmt.Sprintf(\" - [Next page](/r/demo/userbook:page/%d)\\n\\n\", page+1)\n\t}\n\n\tres.Write(prevPage)\n\tres.Write(nextPage)\n}\n\nfunc Render(path string) string {\n\treturn router.Render(path)\n}\n"},{"name":"userbook_test.gno","body":"package userbook\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nfunc TestRender(t *testing.T) {\n\t// Sign up 20 users + deployer\n\tfor i := 0; i \u003c 20; i++ {\n\t\taddrName := ufmt.Sprintf(\"test%d\", i)\n\t\tcaller := testutils.TestAddress(addrName)\n\t\tstd.TestSetOriginCaller(caller)\n\t\tSignUp()\n\t}\n\n\ttestCases := []struct {\n\t\tname string\n\t\tnextPage bool\n\t\tprevPage bool\n\t\tpath string\n\t\texpectedNumberOfUsers int\n\t}{\n\t\t{\n\t\t\tname: \"1st page render\",\n\t\t\tnextPage: true,\n\t\t\tprevPage: false,\n\t\t\tpath: \"page/1\",\n\t\t\texpectedNumberOfUsers: 20,\n\t\t},\n\t\t{\n\t\t\tname: \"2nd page render\",\n\t\t\tnextPage: false,\n\t\t\tprevPage: true,\n\t\t\tpath: \"page/2\",\n\t\t\texpectedNumberOfUsers: 1,\n\t\t},\n\t\t{\n\t\t\tname: \"Invalid path render\",\n\t\t\tnextPage: true,\n\t\t\tprevPage: false,\n\t\t\tpath: \"page/invalidtext\",\n\t\t\texpectedNumberOfUsers: 20,\n\t\t},\n\t\t{\n\t\t\tname: \"Empty Page\",\n\t\t\tnextPage: false,\n\t\t\tprevPage: false,\n\t\t\tpath: \"page/1000\",\n\t\t\texpectedNumberOfUsers: 0,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := Render(tc.path)\n\t\t\tnumUsers := countUsers(got)\n\n\t\t\tif tc.prevPage \u0026\u0026 !strings.Contains(got, \"Previous page\") {\n\t\t\t\tt.Fatalf(\"expected to find Previous page, didn't find it\")\n\t\t\t}\n\t\t\tif tc.nextPage \u0026\u0026 !strings.Contains(got, \"Next page\") {\n\t\t\t\tt.Fatalf(\"expected to find Next page, didn't find it\")\n\t\t\t}\n\n\t\t\tif tc.expectedNumberOfUsers != numUsers {\n\t\t\t\tt.Fatalf(\"expected %d, got %d users\", tc.expectedNumberOfUsers, numUsers)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc countUsers(input string) int {\n\treturn strings.Count(input, \"#### User #\")\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":"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/users\"\n)\n\nvar (\n\tbanker *grc20.Banker = grc20.NewBanker(\"wrapped GNOT\", \"wugnot\", 0)\n\tToken = banker.Token()\n)\n\nconst (\n\tugnotMinDeposit uint64 = 1000\n\twugnotMinDeposit uint64 = 1\n)\n\nfunc Deposit() {\n\tcaller := std.PreviousRealm().Addr()\n\tsent := std.OriginSend()\n\tamount := sent.AmountOf(\"ugnot\")\n\n\trequire(uint64(amount) \u003e= ugnotMinDeposit, ufmt.Sprintf(\"Deposit below minimum: %d/%d ugnot.\", amount, ugnotMinDeposit))\n\tcheckErr(banker.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.PreviousRealm().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(banker.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 banker.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\tcheckErr(Token.Transfer(toAddr, amount))\n}\n\nfunc Approve(spender pusers.AddressOrName, amount uint64) {\n\tspenderAddr := users.Resolve(spender)\n\tcheckErr(Token.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(Token.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.TestSetOriginCaller(addr1)\n\tstd.TestSetOriginSend(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.TestSetOriginCaller(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=200000000 |\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=200000000 |\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=200004242 |\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":""}} +{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"gnoblog","path":"gno.land/r/gnoland/blog","files":[{"name":"admin.gno","body":"package gnoblog\n\nimport (\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/r/gov/dao/bridge\"\n)\n\nvar (\n\tadminAddr std.Address\n\tmoderatorList avl.Tree\n\tcommenterList avl.Tree\n\tinPause bool\n)\n\nfunc init() {\n\t// adminAddr = std.OriginCaller() // FIXME: find a way to use this from the main's genesis.\n\tadminAddr = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"\n}\n\nfunc AdminSetAdminAddr(addr std.Address) {\n\tassertIsAdmin()\n\tadminAddr = addr\n}\n\nfunc AdminSetInPause(state bool) {\n\tassertIsAdmin()\n\tinPause = state\n}\n\nfunc AdminAddModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), true)\n}\n\nfunc AdminRemoveModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), false) // FIXME: delete instead?\n}\n\nfunc NewPostExecutor(slug, title, body, publicationDate, authors, tags string) dao.Executor {\n\tcallback := func() error {\n\t\taddPost(std.PreviousRealm().Addr(), slug, title, body, publicationDate, authors, tags)\n\n\t\treturn nil\n\t}\n\n\treturn bridge.GovDAO().NewGovDAOExecutor(callback)\n}\n\nfunc ModAddPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\tcaller := std.OriginCaller()\n\taddPost(caller, slug, title, body, publicationDate, authors, tags)\n}\n\nfunc addPost(caller std.Address, slug, title, body, publicationDate, authors, tags string) {\n\tvar tagList []string\n\tif tags != \"\" {\n\t\ttagList = strings.Split(tags, \",\")\n\t}\n\tvar authorList []string\n\tif authors != \"\" {\n\t\tauthorList = strings.Split(authors, \",\")\n\t}\n\n\terr := b.NewPost(caller, slug, title, body, publicationDate, authorList, tagList)\n\n\tcheckErr(err)\n}\n\nfunc ModEditPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.GetPost(slug).Update(title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc ModRemovePost(slug string) {\n\tassertIsModerator()\n\n\tb.RemovePost(slug)\n}\n\nfunc ModAddCommenter(addr std.Address) {\n\tassertIsModerator()\n\tcommenterList.Set(addr.String(), true)\n}\n\nfunc ModDelCommenter(addr std.Address) {\n\tassertIsModerator()\n\tcommenterList.Set(addr.String(), false) // FIXME: delete instead?\n}\n\nfunc ModDelComment(slug string, index int) {\n\tassertIsModerator()\n\n\terr := b.GetPost(slug).DeleteComment(index)\n\tcheckErr(err)\n}\n\nfunc isAdmin(addr std.Address) bool {\n\treturn addr == adminAddr\n}\n\nfunc isModerator(addr std.Address) bool {\n\t_, found := moderatorList.Get(addr.String())\n\treturn found\n}\n\nfunc isCommenter(addr std.Address) bool {\n\t_, found := commenterList.Get(addr.String())\n\treturn found\n}\n\nfunc assertIsAdmin() {\n\tcaller := std.OriginCaller()\n\tif !isAdmin(caller) {\n\t\tpanic(\"access restricted.\")\n\t}\n}\n\nfunc assertIsModerator() {\n\tcaller := std.OriginCaller()\n\tif isAdmin(caller) || isModerator(caller) {\n\t\treturn\n\t}\n\tpanic(\"access restricted\")\n}\n\nfunc assertIsCommenter() {\n\tcaller := std.OriginCaller()\n\tif isAdmin(caller) || isModerator(caller) || isCommenter(caller) {\n\t\treturn\n\t}\n\tpanic(\"access restricted\")\n}\n\nfunc assertNotInPause() {\n\tif inPause {\n\t\tpanic(\"access restricted (pause)\")\n\t}\n}\n"},{"name":"gnoblog.gno","body":"package gnoblog\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/blog\"\n)\n\nvar b = \u0026blog.Blog{\n\tTitle: \"Gnoland's Blog\",\n\tPrefix: \"/r/gnoland/blog:\",\n}\n\nfunc AddComment(postSlug, comment string) {\n\tassertIsCommenter()\n\tassertNotInPause()\n\n\tcaller := std.OriginCaller()\n\terr := b.GetPost(postSlug).AddComment(caller, comment)\n\tcheckErr(err)\n}\n\nfunc Render(path string) string {\n\treturn b.Render(path)\n}\n\nfunc RenderLastPostsWidget(limit int) string {\n\treturn b.RenderLastPostsWidget(limit)\n}\n\nfunc PostExists(slug string) bool {\n\tif b.GetPost(slug) == nil {\n\t\treturn false\n\t}\n\treturn true\n}\n"},{"name":"gnoblog_test.gno","body":"package gnoblog\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestPackage(t *testing.T) {\n\tstd.TestSetOriginCaller(std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"))\n\n\tauthor := std.OriginCaller()\n\n\t// by default, no posts.\n\t{\n\t\tgot := Render(\"\")\n\t\texpected := `\n# Gnoland's Blog\n\nNo posts.\n`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// create two posts, list post.\n\t{\n\t\tModAddPost(\"slug1\", \"title1\", \"body1\", \"2022-05-20T13:17:22Z\", \"moul\", \"tag1,tag2\")\n\t\tModAddPost(\"slug2\", \"title2\", \"body2\", \"2022-05-20T13:17:23Z\", \"moul\", \"tag1,tag3\")\n\t\tgot := Render(\"\")\n\t\texpected := `\n\t# Gnoland's Blog\n\n\u003cdiv class='columns-3'\u003e\u003cdiv\u003e\n\n### [title2](/r/gnoland/blog:p/slug2)\n 20 May 2022\n\u003c/div\u003e\u003cdiv\u003e\n\n### [title1](/r/gnoland/blog:p/slug1)\n 20 May 2022\n\u003c/div\u003e\u003c/div\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// view post.\n\t{\n\t\tgot := Render(\"p/slug2\")\n\t\texpected := `\n\u003cmain class='gno-tmpl-page'\u003e\n\n# title2\n\nbody2\n\n---\n\nTags: [#tag1](/r/gnoland/blog:t/tag1) [#tag3](/r/gnoland/blog:t/tag3)\n\nWritten by moul on 20 May 2022\n\nPublished by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq to Gnoland's Blog\n\n---\n\u003cdetails\u003e\u003csummary\u003eComment section\u003c/summary\u003e\n\n\u003c/details\u003e\n\u003c/main\u003e\n\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// list by tags.\n\t{\n\t\tgot := Render(\"t/invalid\")\n\t\texpected := \"# [Gnoland's Blog](/r/gnoland/blog:) / t / invalid\\n\\nNo posts.\"\n\t\tassertMDEquals(t, got, expected)\n\n\t\tgot = Render(\"t/tag2\")\n\t\texpected = `\n# [Gnoland's Blog](/r/gnoland/blog:) / t / tag2\n\n\u003cdiv\u003e\n\n### [title1](/r/gnoland/blog:p/slug1)\n 20 May 2022\n\u003c/div\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// add comments.\n\t{\n\t\tAddComment(\"slug1\", \"comment1\")\n\t\tAddComment(\"slug2\", \"comment2\")\n\t\tAddComment(\"slug1\", \"comment3\")\n\t\tAddComment(\"slug2\", \"comment4\")\n\t\tAddComment(\"slug1\", \"comment5\")\n\t\tgot := Render(\"p/slug2\")\n\t\texpected := `\u003cmain class='gno-tmpl-page'\u003e\n\n# title2\n\nbody2\n\n---\n\nTags: [#tag1](/r/gnoland/blog:t/tag1) [#tag3](/r/gnoland/blog:t/tag3)\n\nWritten by moul on 20 May 2022\n\nPublished by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq to Gnoland's Blog\n\n---\n\u003cdetails\u003e\u003csummary\u003eComment section\u003c/summary\u003e\n\n\u003ch5\u003ecomment4\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003ch5\u003ecomment2\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003c/details\u003e\n\u003c/main\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// edit post.\n\t{\n\t\toldTitle := \"title2\"\n\t\toldDate := \"2022-05-20T13:17:23Z\"\n\n\t\tModEditPost(\"slug2\", oldTitle, \"body2++\", oldDate, \"manfred\", \"tag1,tag4\")\n\t\tgot := Render(\"p/slug2\")\n\t\texpected := `\u003cmain class='gno-tmpl-page'\u003e\n\n# title2\n\nbody2++\n\n---\n\nTags: [#tag1](/r/gnoland/blog:t/tag1) [#tag4](/r/gnoland/blog:t/tag4)\n\nWritten by manfred on 20 May 2022\n\nPublished by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq to Gnoland's Blog\n\n---\n\u003cdetails\u003e\u003csummary\u003eComment section\u003c/summary\u003e\n\n\u003ch5\u003ecomment4\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003ch5\u003ecomment2\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003c/details\u003e\n\u003c/main\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\n\t\thome := Render(\"\")\n\n\t\tif strings.Count(home, oldTitle) != 1 {\n\t\t\tt.Errorf(\"post not edited properly\")\n\t\t}\n\t\t// Edits work everything except title, slug, and publicationDate\n\t\t// Edits to the above will cause duplication on the blog home page\n\t}\n\n\t{ // Test remove functionality\n\t\ttitle := \"example title\"\n\t\tslug := \"testSlug1\"\n\t\tModAddPost(slug, title, \"body1\", \"2022-05-25T13:17:22Z\", \"moul\", \"tag1,tag2\")\n\n\t\tgot := Render(\"\")\n\n\t\tif !strings.Contains(got, title) {\n\t\t\tt.Errorf(\"post was not added properly\")\n\t\t}\n\n\t\tpostRender := Render(\"p/\" + slug)\n\n\t\tif !strings.Contains(postRender, title) {\n\t\t\tt.Errorf(\"post not rendered properly\")\n\t\t}\n\n\t\tModRemovePost(slug)\n\t\tgot = Render(\"\")\n\n\t\tif strings.Contains(got, title) {\n\t\t\tt.Errorf(\"post was not removed\")\n\t\t}\n\n\t\tpostRender = Render(\"p/\" + slug)\n\n\t\tassertMDEquals(t, postRender, \"404\")\n\t}\n\n\t// TODO: pagination.\n\t// TODO: ?format=...\n\n\t// all 404s\n\t{\n\t\tnotFoundPaths := []string{\n\t\t\t\"p/slug3\",\n\t\t\t\"p\",\n\t\t\t\"p/\",\n\t\t\t\"x/x\",\n\t\t\t\"t\",\n\t\t\t\"t/\",\n\t\t\t\"/\",\n\t\t\t\"p/slug1/\",\n\t\t}\n\t\tfor _, notFoundPath := range notFoundPaths {\n\t\t\tgot := Render(notFoundPath)\n\t\t\texpected := \"404\"\n\t\t\tif got != expected {\n\t\t\t\tt.Errorf(\"path %q: expected %q, got %q.\", notFoundPath, expected, got)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc assertMDEquals(t *testing.T, got, expected string) {\n\tt.Helper()\n\texpected = strings.TrimSpace(expected)\n\tgot = strings.TrimSpace(got)\n\tif expected != got {\n\t\tt.Errorf(\"invalid render output.\\nexpected %q.\\ngot %q.\", expected, got)\n\t}\n}\n"},{"name":"util.gno","body":"package gnoblog\n\nfunc checkErr(err error) {\n\tif err != nil {\n\t\tpanic(err)\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":"events","path":"gno.land/r/gnoland/events","files":[{"name":"administration.gno","body":"package events\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/ownable/exts/authorizable\"\n)\n\nvar (\n\tsu = std.Address(\"g125em6arxsnj49vx35f0n0z34putv5ty3376fg5\") // @leohhhn\n\tauth = authorizable.NewAuthorizableWithAddress(su)\n)\n\n// GetOwner gets the owner of the events realm\nfunc GetOwner() std.Address {\n\treturn auth.Owner()\n}\n\n// AddModerator adds a moderator to the events realm\nfunc AddModerator(mod std.Address) {\n\tauth.AssertCallerIsOwner()\n\n\tif err := auth.AddToAuthList(mod); err != nil {\n\t\tpanic(err)\n\t}\n}\n"},{"name":"errors.gno","body":"package events\n\nimport (\n\t\"errors\"\n\t\"strconv\"\n)\n\nvar (\n\tErrEmptyName = errors.New(\"event name cannot be empty\")\n\tErrNoSuchID = errors.New(\"event with specified ID does not exist\")\n\tErrMinWidgetSize = errors.New(\"you need to request at least 1 event to render\")\n\tErrMaxWidgetSize = errors.New(\"maximum number of events in widget is\" + strconv.Itoa(MaxWidgetSize))\n\tErrDescriptionTooLong = errors.New(\"event description is too long\")\n\tErrInvalidStartTime = errors.New(\"invalid start time format\")\n\tErrInvalidEndTime = errors.New(\"invalid end time format\")\n\tErrEndBeforeStart = errors.New(\"end time cannot be before start time\")\n\tErrStartEndTimezonemMismatch = errors.New(\"start and end timezones are not the same\")\n)\n"},{"name":"events.gno","body":"// Package events allows you to upload data about specific IRL/online events\n// It includes dynamic support for updating rendering events based on their\n// status, ie if they are upcoming, in progress, or in the past.\npackage events\n\nimport (\n\t\"sort\"\n\t\"std\"\n\t\"strings\"\n\t\"time\"\n\n\t\"gno.land/p/demo/seqid\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype (\n\tEvent struct {\n\t\tid string\n\t\tname string // name of event\n\t\tdescription string // short description of event\n\t\tlink string // link to auth corresponding web2 page, ie eventbrite/luma or conference page\n\t\tlocation string // location of the event\n\t\tstartTime time.Time // given in RFC3339\n\t\tendTime time.Time // end time of the event, given in RFC3339\n\t}\n\n\teventsSlice []*Event\n)\n\nvar (\n\tevents = make(eventsSlice, 0) // sorted\n\tidCounter seqid.ID\n)\n\nconst (\n\tmaxDescLength = 100\n\tEventAdded = \"EventAdded\"\n\tEventDeleted = \"EventDeleted\"\n\tEventEdited = \"EventEdited\"\n)\n\n// AddEvent adds auth new event\n// Start time \u0026 end time need to be specified in RFC3339, ie 2024-08-08T12:00:00+02:00\nfunc AddEvent(name, description, link, location, startTime, endTime string) (string, error) {\n\tauth.AssertOnAuthList()\n\n\tif strings.TrimSpace(name) == \"\" {\n\t\treturn \"\", ErrEmptyName\n\t}\n\n\tif len(description) \u003e maxDescLength {\n\t\treturn \"\", ufmt.Errorf(\"%s: provided length is %d, maximum is %d\", ErrDescriptionTooLong, len(description), maxDescLength)\n\t}\n\n\t// Parse times\n\tst, et, err := parseTimes(startTime, endTime)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tid := idCounter.Next().String()\n\te := \u0026Event{\n\t\tid: id,\n\t\tname: name,\n\t\tdescription: description,\n\t\tlink: link,\n\t\tlocation: location,\n\t\tstartTime: st,\n\t\tendTime: et,\n\t}\n\n\tevents = append(events, e)\n\tsort.Sort(events)\n\n\tstd.Emit(EventAdded,\n\t\t\"id\", e.id,\n\t)\n\n\treturn id, nil\n}\n\n// DeleteEvent deletes an event with auth given ID\nfunc DeleteEvent(id string) {\n\tauth.AssertOnAuthList()\n\n\te, idx, err := GetEventByID(id)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tevents = append(events[:idx], events[idx+1:]...)\n\n\tstd.Emit(EventDeleted,\n\t\t\"id\", e.id,\n\t)\n}\n\n// EditEvent edits an event with auth given ID\n// It only updates values corresponding to non-empty arguments sent with the call\n// Note: if you need to update the start time or end time, you need to provide both every time\nfunc EditEvent(id string, name, description, link, location, startTime, endTime string) {\n\tauth.AssertOnAuthList()\n\n\te, _, err := GetEventByID(id)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Set only valid values\n\tif strings.TrimSpace(name) != \"\" {\n\t\te.name = name\n\t}\n\n\tif strings.TrimSpace(description) != \"\" {\n\t\te.description = description\n\t}\n\n\tif strings.TrimSpace(link) != \"\" {\n\t\te.link = link\n\t}\n\n\tif strings.TrimSpace(location) != \"\" {\n\t\te.location = location\n\t}\n\n\tif strings.TrimSpace(startTime) != \"\" || strings.TrimSpace(endTime) != \"\" {\n\t\tst, et, err := parseTimes(startTime, endTime)\n\t\tif err != nil {\n\t\t\tpanic(err) // need to also revert other state changes\n\t\t}\n\n\t\toldStartTime := e.startTime\n\t\te.startTime = st\n\t\te.endTime = et\n\n\t\t// If sort order was disrupted, sort again\n\t\tif oldStartTime != e.startTime {\n\t\t\tsort.Sort(events)\n\t\t}\n\t}\n\n\tstd.Emit(EventEdited,\n\t\t\"id\", e.id,\n\t)\n}\n\nfunc GetEventByID(id string) (*Event, int, error) {\n\tfor i, event := range events {\n\t\tif event.id == id {\n\t\t\treturn event, i, nil\n\t\t}\n\t}\n\n\treturn nil, -1, ErrNoSuchID\n}\n\n// Len returns the length of the slice\nfunc (m eventsSlice) Len() int {\n\treturn len(m)\n}\n\n// Less compares the startTime fields of two elements\n// In this case, events will be sorted by largest startTime first (upcoming \u003e past)\nfunc (m eventsSlice) Less(i, j int) bool {\n\treturn m[i].startTime.After(m[j].startTime)\n}\n\n// Swap swaps two elements in the slice\nfunc (m eventsSlice) Swap(i, j int) {\n\tm[i], m[j] = m[j], m[i]\n}\n\n// parseTimes parses the start and end time for an event and checks for possible errors\nfunc parseTimes(startTime, endTime string) (time.Time, time.Time, error) {\n\tst, err := time.Parse(time.RFC3339, startTime)\n\tif err != nil {\n\t\treturn time.Time{}, time.Time{}, ufmt.Errorf(\"%s: %s\", ErrInvalidStartTime, err.Error())\n\t}\n\n\tet, err := time.Parse(time.RFC3339, endTime)\n\tif err != nil {\n\t\treturn time.Time{}, time.Time{}, ufmt.Errorf(\"%s: %s\", ErrInvalidEndTime, err.Error())\n\t}\n\n\tif et.Before(st) {\n\t\treturn time.Time{}, time.Time{}, ErrEndBeforeStart\n\t}\n\n\t_, stOffset := st.Zone()\n\t_, etOffset := et.Zone()\n\tif stOffset != etOffset {\n\t\treturn time.Time{}, time.Time{}, ErrStartEndTimezonemMismatch\n\t}\n\n\treturn st, et, nil\n}\n"},{"name":"events_test.gno","body":"package events\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gno.land/p/demo/uassert\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nvar (\n\tsuRealm = std.NewUserRealm(su)\n\n\tnow = \"2009-02-13T23:31:30Z\" // time.Now() is hardcoded to this value in the gno test machine currently\n\tparsedTimeNow, _ = time.Parse(time.RFC3339, now)\n)\n\nfunc TestAddEvent(t *testing.T) {\n\tstd.TestSetOriginCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\te1Start := parsedTimeNow.Add(time.Hour * 24 * 5)\n\te1End := e1Start.Add(time.Hour * 4)\n\n\tAddEvent(\"Event 1\", \"this event is upcoming\", \"gno.land\", \"gnome land\", e1Start.Format(time.RFC3339), e1End.Format(time.RFC3339))\n\n\tgot := renderHome(false)\n\n\tif !strings.Contains(got, \"Event 1\") {\n\t\tt.Fatalf(\"Expected to find Event 1 in render\")\n\t}\n\n\te2Start := parsedTimeNow.Add(-time.Hour * 24 * 5)\n\te2End := e2Start.Add(time.Hour * 4)\n\n\tAddEvent(\"Event 2\", \"this event is in the past\", \"gno.land\", \"gnome land\", e2Start.Format(time.RFC3339), e2End.Format(time.RFC3339))\n\n\tgot = renderHome(false)\n\n\tupcomingPos := strings.Index(got, \"## Upcoming events\")\n\tpastPos := strings.Index(got, \"## Past events\")\n\n\te1Pos := strings.Index(got, \"Event 1\")\n\te2Pos := strings.Index(got, \"Event 2\")\n\n\t// expected index ordering: upcoming \u003c e1 \u003c past \u003c e2\n\tif e1Pos \u003c upcomingPos || e1Pos \u003e pastPos {\n\t\tt.Fatalf(\"Expected to find Event 1 in Upcoming events\")\n\t}\n\n\tif e2Pos \u003c upcomingPos || e2Pos \u003c pastPos || e2Pos \u003c e1Pos {\n\t\tt.Fatalf(\"Expected to find Event 2 on auth different pos\")\n\t}\n\n\t// larger index =\u003e smaller startTime (future =\u003e past)\n\tif events[0].startTime.Unix() \u003c events[1].startTime.Unix() {\n\t\tt.Fatalf(\"expected ordering to be different\")\n\t}\n}\n\nfunc TestAddEventErrors(t *testing.T) {\n\tstd.TestSetOriginCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\t_, err := AddEvent(\"\", \"sample desc\", \"gno.land\", \"gnome land\", \"2009-02-13T23:31:31Z\", \"2009-02-13T23:33:31Z\")\n\tuassert.ErrorIs(t, err, ErrEmptyName)\n\n\t_, err = AddEvent(\"sample name\", \"sample desc\", \"gno.land\", \"gnome land\", \"\", \"2009-02-13T23:33:31Z\")\n\tuassert.ErrorContains(t, err, ErrInvalidStartTime.Error())\n\n\t_, err = AddEvent(\"sample name\", \"sample desc\", \"gno.land\", \"gnome land\", \"2009-02-13T23:31:31Z\", \"\")\n\tuassert.ErrorContains(t, err, ErrInvalidEndTime.Error())\n\n\t_, err = AddEvent(\"sample name\", \"sample desc\", \"gno.land\", \"gnome land\", \"2009-02-13T23:31:31Z\", \"2009-02-13T23:30:31Z\")\n\tuassert.ErrorIs(t, err, ErrEndBeforeStart)\n\n\t_, err = AddEvent(\"sample name\", \"sample desc\", \"gno.land\", \"gnome land\", \"2009-02-13T23:31:31+06:00\", \"2009-02-13T23:33:31+02:00\")\n\tuassert.ErrorIs(t, err, ErrStartEndTimezonemMismatch)\n\n\ttooLongDesc := `Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean ma`\n\t_, err = AddEvent(\"sample name\", tooLongDesc, \"gno.land\", \"gnome land\", \"2009-02-13T23:31:31Z\", \"2009-02-13T23:33:31Z\")\n\tuassert.ErrorContains(t, err, ErrDescriptionTooLong.Error())\n}\n\nfunc TestDeleteEvent(t *testing.T) {\n\tstd.TestSetOriginCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\te1Start := parsedTimeNow.Add(time.Hour * 24 * 5)\n\te1End := e1Start.Add(time.Hour * 4)\n\n\tid, _ := AddEvent(\"ToDelete\", \"description\", \"gno.land\", \"gnome land\", e1Start.Format(time.RFC3339), e1End.Format(time.RFC3339))\n\n\tgot := renderHome(false)\n\n\tif !strings.Contains(got, \"ToDelete\") {\n\t\tt.Fatalf(\"Expected to find ToDelete event in render\")\n\t}\n\n\tDeleteEvent(id)\n\tgot = renderHome(false)\n\n\tif strings.Contains(got, \"ToDelete\") {\n\t\tt.Fatalf(\"Did not expect to find ToDelete event in render\")\n\t}\n}\n\nfunc TestEditEvent(t *testing.T) {\n\tstd.TestSetOriginCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\te1Start := parsedTimeNow.Add(time.Hour * 24 * 5)\n\te1End := e1Start.Add(time.Hour * 4)\n\tloc := \"gnome land\"\n\n\tid, _ := AddEvent(\"ToDelete\", \"description\", \"gno.land\", loc, e1Start.Format(time.RFC3339), e1End.Format(time.RFC3339))\n\n\tnewName := \"New Name\"\n\tnewDesc := \"Normal description\"\n\tnewLink := \"new Link\"\n\tnewST := e1Start.Add(time.Hour)\n\tnewET := newST.Add(time.Hour)\n\n\tEditEvent(id, newName, newDesc, newLink, \"\", newST.Format(time.RFC3339), newET.Format(time.RFC3339))\n\tedited, _, _ := GetEventByID(id)\n\n\t// Check updated values\n\tuassert.Equal(t, edited.name, newName)\n\tuassert.Equal(t, edited.description, newDesc)\n\tuassert.Equal(t, edited.link, newLink)\n\tuassert.True(t, edited.startTime.Equal(newST))\n\tuassert.True(t, edited.endTime.Equal(newET))\n\n\t// Check if the old values are the same\n\tuassert.Equal(t, edited.location, loc)\n}\n\nfunc TestInvalidEdit(t *testing.T) {\n\tstd.TestSetOriginCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\tuassert.PanicsWithMessage(t, ErrNoSuchID.Error(), func() {\n\t\tEditEvent(\"123123\", \"\", \"\", \"\", \"\", \"\", \"\")\n\t})\n}\n\nfunc TestParseTimes(t *testing.T) {\n\t// times not provided\n\t// end time before start time\n\t// timezone Missmatch\n\n\t_, _, err := parseTimes(\"\", \"\")\n\tuassert.ErrorContains(t, err, ErrInvalidStartTime.Error())\n\n\t_, _, err = parseTimes(now, \"\")\n\tuassert.ErrorContains(t, err, ErrInvalidEndTime.Error())\n\n\t_, _, err = parseTimes(\"2009-02-13T23:30:30Z\", \"2009-02-13T21:30:30Z\")\n\tuassert.ErrorContains(t, err, ErrEndBeforeStart.Error())\n\n\t_, _, err = parseTimes(\"2009-02-10T23:30:30+02:00\", \"2009-02-13T21:30:33+05:00\")\n\tuassert.ErrorContains(t, err, ErrStartEndTimezonemMismatch.Error())\n}\n\nfunc TestRenderEventWidget(t *testing.T) {\n\tstd.TestSetOriginCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\t// No events yet\n\tevents = nil\n\tout, err := RenderEventWidget(1)\n\tuassert.NoError(t, err)\n\tuassert.Equal(t, out, \"No events.\")\n\n\t// Too many events\n\tout, err = RenderEventWidget(MaxWidgetSize + 1)\n\tuassert.ErrorIs(t, err, ErrMaxWidgetSize)\n\n\t// Too little events\n\tout, err = RenderEventWidget(0)\n\tuassert.ErrorIs(t, err, ErrMinWidgetSize)\n\n\t// Ordering \u0026 if requested amt is larger than the num of events that exist\n\te1Start := parsedTimeNow.Add(time.Hour * 24 * 5)\n\te1End := e1Start.Add(time.Hour * 4)\n\n\te2Start := parsedTimeNow.Add(time.Hour * 24 * 10) // event 2 is after event 1\n\te2End := e2Start.Add(time.Hour * 4)\n\n\t_, err = AddEvent(\"Event 1\", \"description\", \"gno.land\", \"loc\", e1Start.Format(time.RFC3339), e1End.Format(time.RFC3339))\n\turequire.NoError(t, err)\n\n\t_, err = AddEvent(\"Event 2\", \"description\", \"gno.land\", \"loc\", e2Start.Format(time.RFC3339), e2End.Format(time.RFC3339))\n\turequire.NoError(t, err)\n\n\tout, err = RenderEventWidget(MaxWidgetSize)\n\turequire.NoError(t, err)\n\n\tuniqueSequence := \"- [\" // sequence that is displayed once per each event as per the RenderEventWidget function\n\tuassert.Equal(t, 2, strings.Count(out, uniqueSequence))\n\n\tuassert.True(t, strings.Index(out, \"Event 1\") \u003e strings.Index(out, \"Event 2\"))\n}\n"},{"name":"rendering.gno","body":"package events\n\nimport (\n\t\"bytes\"\n\t\"time\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\nconst (\n\tMaxWidgetSize = 5\n)\n\n// RenderEventWidget shows up to eventsToRender of the latest events to a caller\nfunc RenderEventWidget(eventsToRender int) (string, error) {\n\tnumOfEvents := len(events)\n\tif numOfEvents == 0 {\n\t\treturn \"No events.\", nil\n\t}\n\n\tif eventsToRender \u003e MaxWidgetSize {\n\t\treturn \"\", ErrMaxWidgetSize\n\t}\n\n\tif eventsToRender \u003c 1 {\n\t\treturn \"\", ErrMinWidgetSize\n\t}\n\n\tif eventsToRender \u003e numOfEvents {\n\t\teventsToRender = numOfEvents\n\t}\n\n\toutput := \"\"\n\n\tfor _, event := range events[:eventsToRender] {\n\t\toutput += ufmt.Sprintf(\"- [%s](%s)\\n\", event.name, event.link)\n\t}\n\n\treturn output, nil\n}\n\n// renderHome renders the home page of the events realm\nfunc renderHome(admin bool) string {\n\toutput := \"# gno.land events\\n\\n\"\n\n\tif len(events) == 0 {\n\t\toutput += \"No upcoming or past events.\"\n\t\treturn output\n\t}\n\n\toutput += \"Below is a list of all gno.land events, including in progress, upcoming, and past ones.\\n\\n\"\n\toutput += \"---\\n\\n\"\n\n\tvar (\n\t\tinProgress = \"\"\n\t\tupcoming = \"\"\n\t\tpast = \"\"\n\t\tnow = time.Now()\n\t)\n\n\tfor _, e := range events {\n\t\tif now.Before(e.startTime) {\n\t\t\tupcoming += e.Render(admin)\n\t\t} else if now.After(e.endTime) {\n\t\t\tpast += e.Render(admin)\n\t\t} else {\n\t\t\tinProgress += e.Render(admin)\n\t\t}\n\t}\n\n\tif upcoming != \"\" {\n\t\t// Add upcoming events\n\t\toutput += \"## Upcoming events\\n\\n\"\n\t\toutput += \"\u003cdiv class='columns-3'\u003e\"\n\n\t\toutput += upcoming\n\n\t\toutput += \"\u003c/div\u003e\\n\\n\"\n\t\toutput += \"---\\n\\n\"\n\t}\n\n\tif inProgress != \"\" {\n\t\toutput += \"## Currently in progress\\n\\n\"\n\t\toutput += \"\u003cdiv class='columns-3'\u003e\"\n\n\t\toutput += inProgress\n\n\t\toutput += \"\u003c/div\u003e\\n\\n\"\n\t\toutput += \"---\\n\\n\"\n\t}\n\n\tif past != \"\" {\n\t\t// Add past events\n\t\toutput += \"## Past events\\n\\n\"\n\t\toutput += \"\u003cdiv class='columns-3'\u003e\"\n\n\t\toutput += past\n\n\t\toutput += \"\u003c/div\u003e\\n\\n\"\n\t}\n\n\treturn output\n}\n\n// Render returns the markdown representation of a single event instance\nfunc (e Event) Render(admin bool) string {\n\tvar buf bytes.Buffer\n\n\tbuf.WriteString(\"\u003cdiv\u003e\\n\\n\")\n\tbuf.WriteString(ufmt.Sprintf(\"### %s\\n\\n\", e.name))\n\tbuf.WriteString(ufmt.Sprintf(\"%s\\n\\n\", e.description))\n\tbuf.WriteString(ufmt.Sprintf(\"**Location:** %s\\n\\n\", e.location))\n\n\t_, offset := e.startTime.Zone() // offset is in seconds\n\thoursOffset := offset / (60 * 60)\n\tsign := \"\"\n\tif offset \u003e= 0 {\n\t\tsign = \"+\"\n\t}\n\n\tbuf.WriteString(ufmt.Sprintf(\"**Starts:** %s UTC%s%d\\n\\n\", e.startTime.Format(\"02 Jan 2006, 03:04 PM\"), sign, hoursOffset))\n\tbuf.WriteString(ufmt.Sprintf(\"**Ends:** %s UTC%s%d\\n\\n\", e.endTime.Format(\"02 Jan 2006, 03:04 PM\"), sign, hoursOffset))\n\n\tif admin {\n\t\tbuf.WriteString(ufmt.Sprintf(\"[EDIT](/r/gnoland/events$help\u0026func=EditEvent\u0026id=%s)\\n\\n\", e.id))\n\t\tbuf.WriteString(ufmt.Sprintf(\"[DELETE](/r/gnoland/events$help\u0026func=DeleteEvent\u0026id=%s)\\n\\n\", e.id))\n\t}\n\n\tif e.link != \"\" {\n\t\tbuf.WriteString(ufmt.Sprintf(\"[See more](%s)\\n\\n\", e.link))\n\t}\n\n\tbuf.WriteString(\"\u003c/div\u003e\")\n\n\treturn buf.String()\n}\n\n// Render is the main rendering entry point\nfunc Render(path string) string {\n\tif path == \"admin\" {\n\t\treturn renderHome(true)\n\t}\n\n\treturn renderHome(false)\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":"faucet","path":"gno.land/r/gnoland/faucet","files":[{"name":"admin.gno","body":"package faucet\n\nimport (\n\t\"errors\"\n\t\"std\"\n)\n\nfunc AdminSetInPause(inPause bool) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\tgInPause = inPause\n\treturn \"\"\n}\n\nfunc AdminSetMessage(message string) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\tgMessage = message\n\treturn \"\"\n}\n\nfunc AdminSetTransferLimit(amount int64) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\tgLimit = std.NewCoin(\"ugnot\", amount)\n\treturn \"\"\n}\n\nfunc AdminSetAdminAddr(addr std.Address) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\tgAdminAddr = addr\n\treturn \"\"\n}\n\nfunc AdminAddController(addr std.Address) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\n\tsize := gControllers.Size()\n\n\tif size \u003e= gControllersMaxSize {\n\t\treturn \"can not add more controllers than allowed\"\n\t}\n\n\tif gControllers.Has(addr.String()) {\n\t\treturn addr.String() + \" exists, no need to add.\"\n\t}\n\n\tgControllers.Set(addr.String(), addr)\n\n\treturn \"\"\n}\n\nfunc AdminRemoveController(addr std.Address) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\n\tif !gControllers.Has(addr.String()) {\n\t\treturn addr.String() + \" is not on the controller list\"\n\t}\n\n\t_, ok := gControllers.Remove(addr.String())\n\n\t// it not should happen.\n\t// we will check anyway to prevent issues in the underline implementation.\n\n\tif !ok {\n\t\treturn addr.String() + \" is not on the controller list\"\n\t}\n\n\treturn \"\"\n}\n\nfunc assertIsAdmin() error {\n\tcaller := std.OriginCaller()\n\tif caller != gAdminAddr {\n\t\treturn errors.New(\"restricted for admin\")\n\t}\n\treturn nil\n}\n"},{"name":"faucet.gno","body":"package faucet\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar (\n\t// configurable by admin.\n\tgAdminAddr std.Address = std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")\n\tgControllers = avl.NewTree()\n\tgControllersMaxSize = 10 // limit it to 10\n\tgInPause = false\n\tgMessage = \"# Community Faucet.\\n\\n\"\n\n\t// internal vars, for stats.\n\tgTotalTransferred std.Coins\n\tgTotalTransfers = uint(0)\n\n\t// per request limit, 350 gnot\n\tgLimit std.Coin = std.NewCoin(\"ugnot\", 350000000)\n)\n\nfunc Transfer(to std.Address, send int64) string {\n\tif err := assertIsController(); err != nil {\n\t\treturn err.Error()\n\t}\n\n\tif gInPause {\n\t\treturn errors.New(\"faucet in pause\").Error()\n\t}\n\n\t// limit the per request\n\tif send \u003e gLimit.Amount {\n\t\treturn errors.New(\"Per request limit \" + gLimit.String() + \" exceed\").Error()\n\t}\n\tsendCoins := std.Coins{std.NewCoin(\"ugnot\", send)}\n\n\tgTotalTransferred = gTotalTransferred.Add(sendCoins)\n\tgTotalTransfers++\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tpkgaddr := std.CurrentRealm().Addr()\n\tbanker.SendCoins(pkgaddr, to, sendCoins)\n\treturn \"\"\n}\n\nfunc GetPerTransferLimit() int64 {\n\treturn gLimit.Amount\n}\n\nfunc Render(_ string) string {\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tbalance := banker.GetCoins(std.CurrentRealm().Addr())\n\n\toutput := gMessage\n\tif gInPause {\n\t\toutput += \"Status: inactive.\\n\"\n\t} else {\n\t\toutput += \"Status: active.\\n\"\n\t}\n\toutput += ufmt.Sprintf(\"Balance: %s.\\n\", balance.String())\n\toutput += ufmt.Sprintf(\"Total transfers: %s (in %d times).\\n\\n\", gTotalTransferred.String(), gTotalTransfers)\n\n\toutput += \"Package address: \" + std.CurrentRealm().Addr().String() + \"\\n\\n\"\n\toutput += ufmt.Sprintf(\"Admin: %s\\n\\n \", gAdminAddr.String())\n\toutput += ufmt.Sprintf(\"Controllers:\\n\\n \")\n\n\tfor i := 0; i \u003c gControllers.Size(); i++ {\n\t\t_, v := gControllers.GetByIndex(i)\n\t\toutput += ufmt.Sprintf(\"%s \", v.(std.Address))\n\t}\n\n\toutput += \"\\n\\n\"\n\toutput += ufmt.Sprintf(\"Per request limit: %s\\n\\n\", gLimit.String())\n\n\treturn output\n}\n\nfunc assertIsController() error {\n\tcaller := std.OriginCaller()\n\n\tok := gControllers.Has(caller.String())\n\tif !ok {\n\t\treturn errors.New(caller.String() + \" is not on the controller list\")\n\t}\n\treturn nil\n}\n"},{"name":"faucet_test.gno","body":"package faucet\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/gnoland/faucet\"\n)\n\nfunc TestPackage(t *testing.T) {\n\tvar (\n\t\tadminaddr = std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")\n\t\tfaucetaddr = std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\t\tcontrolleraddr1 = testutils.TestAddress(\"controller1\")\n\t\tcontrolleraddr2 = testutils.TestAddress(\"controller2\")\n\t\tcontrolleraddr3 = testutils.TestAddress(\"controller3\")\n\t\tcontrolleraddr4 = testutils.TestAddress(\"controller4\")\n\t\tcontrolleraddr5 = testutils.TestAddress(\"controller5\")\n\t\tcontrolleraddr6 = testutils.TestAddress(\"controller6\")\n\t\tcontrolleraddr7 = testutils.TestAddress(\"controller7\")\n\t\tcontrolleraddr8 = testutils.TestAddress(\"controller8\")\n\t\tcontrolleraddr9 = testutils.TestAddress(\"controller9\")\n\t\tcontrolleraddr10 = testutils.TestAddress(\"controller10\")\n\t\tcontrolleraddr11 = testutils.TestAddress(\"controller11\")\n\n\t\ttest1addr = testutils.TestAddress(\"test1\")\n\t)\n\t// deposit 1000gnot to faucet contract\n\tstd.TestIssueCoins(faucetaddr, std.Coins{{\"ugnot\", 1000000000}})\n\tassertBalance(t, faucetaddr, 1200000000)\n\n\t// by default, balance is empty, and as a user I cannot call Transfer, or Admin commands.\n\n\tassertBalance(t, test1addr, 0)\n\tstd.TestSetOriginCaller(test1addr)\n\tassertErr(t, faucet.Transfer(test1addr, 1000000))\n\n\tassertErr(t, faucet.AdminAddController(controlleraddr1))\n\tstd.TestSetOriginCaller(controlleraddr1)\n\tassertErr(t, faucet.Transfer(test1addr, 1000000))\n\n\t// as an admin, add the controller to contract and deposit more 2000gnot to contract\n\tstd.TestSetOriginCaller(adminaddr)\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr1))\n\tassertBalance(t, faucetaddr, 1200000000)\n\n\t// now, send some tokens as controller.\n\tstd.TestSetOriginCaller(controlleraddr1)\n\tassertNoErr(t, faucet.Transfer(test1addr, 1000000))\n\tassertBalance(t, test1addr, 1000000)\n\tassertNoErr(t, faucet.Transfer(test1addr, 1000000))\n\tassertBalance(t, test1addr, 2000000)\n\tassertBalance(t, faucetaddr, 1198000000)\n\n\t// remove controller\n\t// as an admin, remove controller\n\tstd.TestSetOriginCaller(adminaddr)\n\tassertNoErr(t, faucet.AdminRemoveController(controlleraddr1))\n\tstd.TestSetOriginCaller(controlleraddr1)\n\tassertErr(t, faucet.Transfer(test1addr, 1000000))\n\n\t// duplicate controller\n\tstd.TestSetOriginCaller(adminaddr)\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr1))\n\tassertErr(t, faucet.AdminAddController(controlleraddr1))\n\t// add more than more than allowed controllers\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr2))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr3))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr4))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr5))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr6))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr7))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr8))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr9))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr10))\n\tassertErr(t, faucet.AdminAddController(controlleraddr11))\n\n\t// send more than per transfer limit\n\tstd.TestSetOriginCaller(adminaddr)\n\tfaucet.AdminSetTransferLimit(300000000)\n\tstd.TestSetOriginCaller(controlleraddr1)\n\tassertErr(t, faucet.Transfer(test1addr, 301000000))\n\n\t// block transefer from the address not on the controllers list.\n\tstd.TestSetOriginCaller(controlleraddr11)\n\tassertErr(t, faucet.Transfer(test1addr, 1000000))\n}\n\nfunc assertErr(t *testing.T, err string) {\n\tt.Helper()\n\n\tif err == \"\" {\n\t\tt.Logf(\"info: got err: %v\", err)\n\t\tt.Errorf(\"expected an error, got nil.\")\n\t}\n}\n\nfunc assertNoErr(t *testing.T, err string) {\n\tt.Helper()\n\tif err != \"\" {\n\t\tt.Errorf(\"got err: %v.\", err)\n\t}\n}\n\nfunc assertBalance(t *testing.T, addr std.Address, expectedBal int64) {\n\tt.Helper()\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tcoins := banker.GetCoins(addr)\n\tgot := coins.AmountOf(\"ugnot\")\n\n\tif expectedBal != got {\n\t\tt.Errorf(\"invalid balance: expected %d, got %d.\", expectedBal, got)\n\t}\n}\n"},{"name":"z0_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/gnoland/faucet\"\n)\n\n// mints ugnot to current realm\nfunc init() {\n\tfacuetaddr := std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\tstd.TestIssueCoins(facuetaddr, std.Coins{{\"ugnot\", 200000000}})\n}\n\n// assert render with empty path and no controllers\nfunc main() {\n\tprintln(faucet.Render(\"\"))\n}\n\n// Output:\n// # Community Faucet.\n//\n// Status: active.\n// Balance: 200000000ugnot.\n// Total transfers: (in 0 times).\n//\n// Package address: g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7\n//\n// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n//\n// Controllers:\n//\n//\n//\n// Per request limit: 350000000ugnot\n"},{"name":"z1_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/gnoland/faucet\"\n)\n\n// mints ugnot to current realm\nfunc init() {\n\tfacuetaddr := std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\tstd.TestIssueCoins(facuetaddr, std.Coins{{\"ugnot\", 200000000}})\n}\n\n// assert render with a path and no controllers\nfunc main() {\n\tprintln(faucet.Render(\"path\"))\n}\n\n// Output:\n// # Community Faucet.\n//\n// Status: active.\n// Balance: 200000000ugnot.\n// Total transfers: (in 0 times).\n//\n// Package address: g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7\n//\n// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n//\n// Controllers:\n//\n//\n//\n// Per request limit: 350000000ugnot\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/gnoland/faucet\"\n)\n\n// mints ugnot to current realm\nfunc init() {\n\tfacuetaddr := std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\tstd.TestIssueCoins(facuetaddr, std.Coins{{\"ugnot\", 200000000}})\n}\n\n// assert render with empty path and 2 controllers\nfunc main() {\n\tvar (\n\t\tadminaddr = std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")\n\t\tcontrolleraddr1 = testutils.TestAddress(\"controller1\")\n\t\tcontrolleraddr2 = testutils.TestAddress(\"controller2\")\n\t)\n\tstd.TestSetOriginCaller(adminaddr)\n\terr := faucet.AdminAddController(controlleraddr1)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\terr = faucet.AdminAddController(controlleraddr2)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\tprintln(faucet.Render(\"\"))\n}\n\n// Output:\n// # Community Faucet.\n//\n// Status: active.\n// Balance: 200000000ugnot.\n// Total transfers: (in 0 times).\n//\n// Package address: g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7\n//\n// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n//\n// Controllers:\n//\n// g1vdhkuarjdakxcetjx9047h6lta047h6lsdacav g1vdhkuarjdakxcetjxf047h6lta047h6lnrev3v\n//\n// Per request limit: 350000000ugnot\n"},{"name":"z3_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/gnoland/faucet\"\n)\n\n// mints coints to current realm\nfunc init() {\n\tfacuetaddr := std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\tstd.TestIssueCoins(facuetaddr, std.Coins{{\"ugnot\", 200000000}})\n}\n\n// assert render with 2 controllers and 2 transfers\nfunc main() {\n\tvar (\n\t\tadminaddr = std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")\n\t\tcontrolleraddr1 = testutils.TestAddress(\"controller1\")\n\t\tcontrolleraddr2 = testutils.TestAddress(\"controller2\")\n\t\ttestaddr1 = testutils.TestAddress(\"test1\")\n\t\ttestaddr2 = testutils.TestAddress(\"test2\")\n\t)\n\tstd.TestSetOriginCaller(adminaddr)\n\terr := faucet.AdminAddController(controlleraddr1)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\terr = faucet.AdminAddController(controlleraddr2)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\tstd.TestSetOriginCaller(controlleraddr1)\n\terr = faucet.Transfer(testaddr1, 1000000)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\tstd.TestSetOriginCaller(controlleraddr2)\n\terr = faucet.Transfer(testaddr1, 2000000)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\tprintln(faucet.Render(\"\"))\n}\n\n// Output:\n// # Community Faucet.\n//\n// Status: active.\n// Balance: 197000000ugnot.\n// Total transfers: 3000000ugnot (in 2 times).\n//\n// Package address: g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7\n//\n// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n//\n// Controllers:\n//\n// g1vdhkuarjdakxcetjx9047h6lta047h6lsdacav g1vdhkuarjdakxcetjxf047h6lta047h6lnrev3v\n//\n// Per request limit: 350000000ugnot\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":"ghverify","path":"gno.land/r/gnoland/ghverify","files":[{"name":"README.md","body":"# ghverify\n\nThis realm is intended to enable off chain gno address to github handle verification.\nThe steps are as follows:\n- A user calls `RequestVerification` and provides a github handle. This creates a new static oracle feed.\n- An off-chain agent controlled by the owner of this realm requests current feeds using the `GnorkleEntrypoint` function and provides a message of `\"request\"`\n- The agent receives the task information that includes the github handle and the gno address. It performs the verification step by checking whether this github user has the address in a github repository it controls.\n- The agent publishes the result of the verification by calling `GnorkleEntrypoint` with a message structured like: `\"ingest,\u003ctask id\u003e,\u003cverification status\u003e\"`. The verification status is `OK` if verification succeeded and any other value if it failed.\n- The oracle feed's ingester processes the verification and the handle to address mapping is written to the avl trees that exist as ghverify realm variables."},{"name":"contract.gno","body":"package ghverify\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/gnorkle/feeds/static\"\n\t\"gno.land/p/demo/gnorkle/gnorkle\"\n\t\"gno.land/p/demo/gnorkle/message\"\n)\n\nconst (\n\t// The agent should send this value if it has verified the github handle.\n\tverifiedResult = \"OK\"\n)\n\nvar (\n\townerAddress = std.OriginCaller()\n\toracle *gnorkle.Instance\n\tpostHandler postGnorkleMessageHandler\n\n\thandleToAddressMap = avl.NewTree()\n\taddressToHandleMap = avl.NewTree()\n)\n\nfunc init() {\n\toracle = gnorkle.NewInstance()\n\toracle.AddToWhitelist(\"\", []string{string(ownerAddress)})\n}\n\ntype postGnorkleMessageHandler struct{}\n\n// Handle does post processing after a message is ingested by the oracle feed. It extracts the value to realm\n// storage and removes the feed from the oracle.\nfunc (h postGnorkleMessageHandler) Handle(i *gnorkle.Instance, funcType message.FuncType, feed gnorkle.Feed) error {\n\tif funcType != message.FuncTypeIngest {\n\t\treturn nil\n\t}\n\n\tresult, _, consumable := feed.Value()\n\tif !consumable {\n\t\treturn nil\n\t}\n\n\t// The value is consumable, meaning the ingestion occurred, so we can remove the feed from the oracle\n\t// after saving it to realm storage.\n\tdefer oracle.RemoveFeed(feed.ID())\n\n\t// Couldn't verify; nothing to do.\n\tif result.String != verifiedResult {\n\t\treturn nil\n\t}\n\n\tfeedTasks := feed.Tasks()\n\tif len(feedTasks) != 1 {\n\t\treturn errors.New(\"expected feed to have exactly one task\")\n\t}\n\n\ttask, ok := feedTasks[0].(*verificationTask)\n\tif !ok {\n\t\treturn errors.New(\"expected ghverify task\")\n\t}\n\n\thandleToAddressMap.Set(task.githubHandle, task.gnoAddress)\n\taddressToHandleMap.Set(task.gnoAddress, task.githubHandle)\n\treturn nil\n}\n\n// RequestVerification creates a new static feed with a single task that will\n// instruct an agent to verify the github handle / gno address pair.\nfunc RequestVerification(githubHandle string) {\n\tgnoAddress := string(std.OriginCaller())\n\tif err := oracle.AddFeeds(\n\t\tstatic.NewSingleValueFeed(\n\t\t\tgnoAddress,\n\t\t\t\"string\",\n\t\t\t\u0026verificationTask{\n\t\t\t\tgnoAddress: gnoAddress,\n\t\t\t\tgithubHandle: githubHandle,\n\t\t\t},\n\t\t),\n\t); err != nil {\n\t\tpanic(err)\n\t}\n\tstd.Emit(\n\t\t\"verification_requested\",\n\t\t\"from\", gnoAddress,\n\t\t\"handle\", githubHandle,\n\t)\n}\n\n// GnorkleEntrypoint is the entrypoint to the gnorkle oracle handler.\nfunc GnorkleEntrypoint(message string) string {\n\tresult, err := oracle.HandleMessage(message, postHandler)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn result\n}\n\n// SetOwner transfers ownership of the contract to the given address.\nfunc SetOwner(owner std.Address) {\n\tif ownerAddress != std.OriginCaller() {\n\t\tpanic(\"only the owner can set a new owner\")\n\t}\n\n\townerAddress = owner\n\n\t// In the context of this contract, the owner is the only one that can\n\t// add new feeds to the oracle.\n\toracle.ClearWhitelist(\"\")\n\toracle.AddToWhitelist(\"\", []string{string(ownerAddress)})\n}\n\n// GetHandleByAddress returns the github handle associated with the given gno address.\nfunc GetHandleByAddress(address string) string {\n\tif value, ok := addressToHandleMap.Get(address); ok {\n\t\treturn value.(string)\n\t}\n\n\treturn \"\"\n}\n\n// GetAddressByHandle returns the gno address associated with the given github handle.\nfunc GetAddressByHandle(handle string) string {\n\tif value, ok := handleToAddressMap.Get(handle); ok {\n\t\treturn value.(string)\n\t}\n\n\treturn \"\"\n}\n\n// Render returns a json object string will all verified handle -\u003e address mappings.\nfunc Render(_ string) string {\n\tresult := \"{\"\n\tvar appendComma bool\n\thandleToAddressMap.Iterate(\"\", \"\", func(handle string, address interface{}) bool {\n\t\tif appendComma {\n\t\t\tresult += \",\"\n\t\t}\n\n\t\tresult += `\"` + handle + `\": \"` + address.(string) + `\"`\n\t\tappendComma = true\n\n\t\treturn false\n\t})\n\n\treturn result + \"}\"\n}\n"},{"name":"contract_test.gno","body":"package ghverify\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n)\n\nfunc TestVerificationLifecycle(t *testing.T) {\n\tdefaultAddress := std.OriginCaller()\n\tuser1Address := std.Address(testutils.TestAddress(\"user 1\"))\n\tuser2Address := std.Address(testutils.TestAddress(\"user 2\"))\n\n\t// Verify request returns no feeds.\n\tresult := GnorkleEntrypoint(\"request\")\n\tif result != \"[]\" {\n\t\tt.Fatalf(\"expected empty request result, got %s\", result)\n\t}\n\n\t// Make a verification request with the created user.\n\tstd.TestSetOriginCaller(user1Address)\n\tRequestVerification(\"deelawn\")\n\n\t// A subsequent request from the same address should panic because there is\n\t// already a feed with an ID of this user's address.\n\tvar errMsg string\n\tfunc() {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\terrMsg = r.(error).Error()\n\t\t\t}\n\t\t}()\n\t\tRequestVerification(\"deelawn\")\n\t}()\n\tif errMsg != \"feed already exists\" {\n\t\tt.Fatalf(\"expected feed already exists, got %s\", errMsg)\n\t}\n\n\t// Verify the request returns no feeds for this non-whitelisted user.\n\tresult = GnorkleEntrypoint(\"request\")\n\tif result != \"[]\" {\n\t\tt.Fatalf(\"expected empty request result, got %s\", result)\n\t}\n\n\t// Make a verification request with the created user.\n\tstd.TestSetOriginCaller(user2Address)\n\tRequestVerification(\"omarsy\")\n\n\t// Set the caller back to the whitelisted user and verify that the feed data\n\t// returned matches what should have been created by the `RequestVerification`\n\t// invocation.\n\tstd.TestSetOriginCaller(defaultAddress)\n\tresult = GnorkleEntrypoint(\"request\")\n\texpResult := `[{\"id\":\"` + string(user1Address) + `\",\"type\":\"0\",\"value_type\":\"string\",\"tasks\":[{\"gno_address\":\"` +\n\t\tstring(user1Address) + `\",\"github_handle\":\"deelawn\"}]},` +\n\t\t`{\"id\":\"` + string(user2Address) + `\",\"type\":\"0\",\"value_type\":\"string\",\"tasks\":[{\"gno_address\":\"` +\n\t\tstring(user2Address) + `\",\"github_handle\":\"omarsy\"}]}]`\n\tif result != expResult {\n\t\tt.Fatalf(\"expected request result %s, got %s\", expResult, result)\n\t}\n\n\t// Try to trigger feed ingestion from the non-authorized user.\n\tstd.TestSetOriginCaller(user1Address)\n\tfunc() {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\terrMsg = r.(error).Error()\n\t\t\t}\n\t\t}()\n\t\tGnorkleEntrypoint(\"ingest,\" + string(user1Address) + \",OK\")\n\t}()\n\tif errMsg != \"caller not whitelisted\" {\n\t\tt.Fatalf(\"expected caller not whitelisted, got %s\", errMsg)\n\t}\n\n\t// Set the caller back to the whitelisted user and transfer contract ownership.\n\tstd.TestSetOriginCaller(defaultAddress)\n\tSetOwner(defaultAddress)\n\n\t// Now trigger the feed ingestion from the user and new owner and only whitelisted address.\n\tGnorkleEntrypoint(\"ingest,\" + string(user1Address) + \",OK\")\n\tGnorkleEntrypoint(\"ingest,\" + string(user2Address) + \",OK\")\n\n\t// Verify the ingestion autocommitted the value and triggered the post handler.\n\tdata := Render(\"\")\n\texpResult = `{\"deelawn\": \"` + string(user1Address) + `\",\"omarsy\": \"` + string(user2Address) + `\"}`\n\tif data != expResult {\n\t\tt.Fatalf(\"expected render data %s, got %s\", expResult, data)\n\t}\n\n\t// Finally make sure the feed was cleaned up after the data was committed.\n\tresult = GnorkleEntrypoint(\"request\")\n\tif result != \"[]\" {\n\t\tt.Fatalf(\"expected empty request result, got %s\", result)\n\t}\n\n\t// Check that the accessor functions are working as expected.\n\tif handle := GetHandleByAddress(string(user1Address)); handle != \"deelawn\" {\n\t\tt.Fatalf(\"expected deelawn, got %s\", handle)\n\t}\n\tif address := GetAddressByHandle(\"deelawn\"); address != string(user1Address) {\n\t\tt.Fatalf(\"expected %s, got %s\", string(user1Address), address)\n\t}\n}\n"},{"name":"task.gno","body":"package ghverify\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n)\n\ntype verificationTask struct {\n\tgnoAddress string\n\tgithubHandle string\n}\n\n// MarshalJSON marshals the task contents to JSON.\nfunc (t *verificationTask) MarshalJSON() ([]byte, error) {\n\tbuf := new(bytes.Buffer)\n\tw := bufio.NewWriter(buf)\n\n\tw.Write(\n\t\t[]byte(`{\"gno_address\":\"` + t.gnoAddress + `\",\"github_handle\":\"` + t.githubHandle + `\"}`),\n\t)\n\n\tw.Flush()\n\treturn buf.Bytes(), nil\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":"home","path":"gno.land/r/gnoland/home","files":[{"name":"home.gno","body":"package home\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/p/demo/ui\"\n\tblog \"gno.land/r/gnoland/blog\"\n\tevents \"gno.land/r/gnoland/events\"\n)\n\n// XXX: p/demo/ui API is crappy, we need to make it more idiomatic\n// XXX: use an updatable block system to update content from a DAO\n// XXX: var blocks avl.Tree\n\nvar (\n\toverride string\n\tadmin = ownable.NewWithAddress(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\") // @manfred by default\n)\n\nfunc Render(_ string) string {\n\tif override != \"\" {\n\t\treturn override\n\t}\n\n\tdom := ui.DOM{Prefix: \"r/gnoland/home:\"}\n\tdom.Title = \"Welcome to gno.land\"\n\tdom.Classes = []string{\"gno-tmpl-section\"}\n\n\t// body\n\tdom.Body.Append(introSection()...)\n\n\tdom.Body.Append(ui.Jumbotron(discoverLinks()))\n\n\tdom.Body.Append(\n\t\tui.Columns{3, []ui.Element{\n\t\t\tlastBlogposts(4),\n\t\t\tupcomingEvents(),\n\t\t\tlastContributions(4),\n\t\t}},\n\t)\n\n\tdom.Body.Append(ui.HR{})\n\tdom.Body.Append(playgroundSection()...)\n\tdom.Body.Append(ui.HR{})\n\tdom.Body.Append(packageStaffPicks()...)\n\tdom.Body.Append(ui.HR{})\n\tdom.Body.Append(worxDAO()...)\n\tdom.Body.Append(ui.HR{})\n\t// footer\n\tdom.Footer.Append(\n\t\tui.Columns{2, []ui.Element{\n\t\t\tsocialLinks(),\n\t\t\tquoteOfTheBlock(),\n\t\t}},\n\t)\n\n\t// Testnet disclaimer\n\tdom.Footer.Append(\n\t\tui.HR{},\n\t\tui.Bold(\"This is a testnet.\"),\n\t\tui.Text(\"Package names are not guaranteed to be available for production.\"),\n\t)\n\n\treturn dom.String()\n}\n\nfunc lastBlogposts(limit int) ui.Element {\n\tposts := blog.RenderLastPostsWidget(limit)\n\treturn ui.Element{\n\t\tui.H3(\"[Latest Blogposts](/r/gnoland/blog)\"),\n\t\tui.Text(posts),\n\t}\n}\n\nfunc lastContributions(limit int) ui.Element {\n\treturn ui.Element{\n\t\tui.H3(\"Latest Contributions\"),\n\t\t// TODO: import r/gh to\n\t\tui.Link{Text: \"View latest contributions\", URL: \"https://github.com/gnolang/gno/pulls\"},\n\t}\n}\n\nfunc upcomingEvents() ui.Element {\n\tout, _ := events.RenderEventWidget(events.MaxWidgetSize)\n\treturn ui.Element{\n\t\tui.H3(\"[Latest Events](/r/gnoland/events)\"),\n\t\tui.Text(out),\n\t}\n}\n\nfunc introSection() ui.Element {\n\treturn ui.Element{\n\t\tui.H3(\"We’re building gno.land, set to become the leading open-source smart contract platform, using Gno, an interpreted and fully deterministic variation of the Go programming language for succinct and composable smart contracts.\"),\n\t\tui.Paragraph(\"With transparent and timeless code, gno.land is the next generation of smart contract platforms, serving as the “GitHub” of the ecosystem, with realms built using fully transparent, auditable code that anyone can inspect and reuse.\"),\n\t\tui.Paragraph(\"Intuitive and easy to use, gno.land lowers the barrier to web3 and makes censorship-resistant platforms accessible to everyone. If you want to help lay the foundations of a fairer and freer world, join us today.\"),\n\t}\n}\n\nfunc worxDAO() ui.Element {\n\t// WorxDAO\n\t// XXX(manfred): please, let me finish a v0, then we can iterate\n\t// highest level == highest responsibility\n\t// teams are responsible for components they don't owne\n\t// flag : realm maintainers VS facilitators\n\t// teams\n\t// committee of trustees to create the directory\n\t// each directory is a name, has a parent and have groups\n\t// homepage team - blocks aggregating events\n\t// XXX: TODO\n\t/*`\n\t# Directory\n\n\t* gno.land (owned by group)\n\t *\n\t* gnovm\n\t * gnolang (language)\n\t * gnovm\n\t - current challenges / concerns / issues\n\t* tm2\n\t * amino\n\t *\n\n\t## Contributors\n\t``*/\n\treturn ui.Element{\n\t\tui.H3(\"Contributions (WorxDAO \u0026 GoR)\"),\n\t\t// TODO: GoR dashboard + WorxDAO topics\n\t\tui.Text(`coming soon`),\n\t}\n}\n\nfunc quoteOfTheBlock() ui.Element {\n\tquotes := []string{\n\t\t\"Gno is for Truth.\",\n\t\t\"Gno is for Social Coordination.\",\n\t\t\"Gno is _not only_ for DeFi.\",\n\t\t\"Now, you Gno.\",\n\t\t\"Come for the Go, Stay for the Gno.\",\n\t}\n\theight := std.GetHeight()\n\tidx := int(height) % len(quotes)\n\tqotb := quotes[idx]\n\n\treturn ui.Element{\n\t\tui.H3(ufmt.Sprintf(\"Quote of the ~Day~ Block#%d\", height)),\n\t\tui.Quote(qotb),\n\t}\n}\n\nfunc socialLinks() ui.Element {\n\treturn ui.Element{\n\t\tui.H3(\"Socials\"),\n\t\tui.BulletList{\n\t\t\t// XXX: improve UI to support a nice GO api for such links\n\t\t\tui.Text(\"Check out our [community projects](https://github.com/gnolang/awesome-gno)\"),\n\t\t\tui.Text(\"![Discord](static/img/ico-discord.svg) [Discord](https://discord.gg/S8nKUqwkPn)\"),\n\t\t\tui.Text(\"![Twitter](static/img/ico-twitter.svg) [Twitter](https://twitter.com/_gnoland)\"),\n\t\t\tui.Text(\"![Youtube](static/img/ico-youtube.svg) [Youtube](https://www.youtube.com/@_gnoland)\"),\n\t\t\tui.Text(\"![Telegram](static/img/ico-telegram.svg) [Telegram](https://t.me/gnoland)\"),\n\t\t},\n\t}\n}\n\nfunc playgroundSection() ui.Element {\n\treturn ui.Element{\n\t\tui.H3(\"[Gno Playground](https://play.gno.land)\"),\n\t\tui.Paragraph(`Gno Playground is a web application designed for building, running, testing, and interacting\nwith your Gno code, enhancing your understanding of the Gno language. With Gno Playground, you can share your code,\nexecute tests, deploy your realms and packages to gno.land, and explore a multitude of other features.`),\n\t\tui.Paragraph(\"Experience the convenience of code sharing and rapid experimentation with [Gno Playground](https://play.gno.land).\"),\n\t}\n}\n\nfunc packageStaffPicks() ui.Element {\n\t// XXX: make it modifiable from a DAO\n\treturn ui.Element{\n\t\tui.H3(\"Explore New Packages and Realms\"),\n\t\tui.Columns{\n\t\t\t3,\n\t\t\t[]ui.Element{\n\t\t\t\t{\n\t\t\t\t\tui.H4(\"[r/gnoland](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/gnoland)\"),\n\t\t\t\t\tui.BulletList{\n\t\t\t\t\t\tui.Link{URL: \"r/gnoland/blog\"},\n\t\t\t\t\t\tui.Link{URL: \"r/gnoland/dao\"},\n\t\t\t\t\t\tui.Link{URL: \"r/gnoland/faucet\"},\n\t\t\t\t\t\tui.Link{URL: \"r/gnoland/home\"},\n\t\t\t\t\t\tui.Link{URL: \"r/gnoland/pages\"},\n\t\t\t\t\t},\n\t\t\t\t\tui.H4(\"[r/sys](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/sys)\"),\n\t\t\t\t\tui.BulletList{\n\t\t\t\t\t\tui.Link{URL: \"r/sys/names\"},\n\t\t\t\t\t\tui.Link{URL: \"r/sys/rewards\"},\n\t\t\t\t\t\tui.Link{URL: \"/r/sys/validators/v2\"},\n\t\t\t\t\t},\n\t\t\t\t}, {\n\t\t\t\t\tui.H4(\"[r/demo](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo)\"),\n\t\t\t\t\tui.BulletList{\n\t\t\t\t\t\tui.Link{URL: \"r/demo/boards\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/users\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/banktest\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/foo20\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/foo721\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/microblog\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/nft\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/types\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/art/gnoface\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/art/millipede\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/groups\"},\n\t\t\t\t\t\tui.Text(\"...\"),\n\t\t\t\t\t},\n\t\t\t\t}, {\n\t\t\t\t\tui.H4(\"[p/demo](https://github.com/gnolang/gno/tree/master/examples/gno.land/p/demo)\"),\n\t\t\t\t\tui.BulletList{\n\t\t\t\t\t\tui.Link{URL: \"p/demo/avl\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/blog\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/ui\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/ufmt\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/merkle\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/bf\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/flow\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/gnode\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/grc/grc20\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/grc/grc721\"},\n\t\t\t\t\t\tui.Text(\"...\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc discoverLinks() ui.Element {\n\treturn ui.Element{\n\t\tui.Text(`\u003cdiv class=\"columns-3\"\u003e\n\u003cdiv class=\"column\"\u003e\n\n### Learn about gno.land\n\n- [About](/about)\n- [GitHub](https://github.com/gnolang)\n- [Blog](/blog)\n- [Events](/events)\n- Tokenomics (soon)\n- [Partners, Fund, Grants](/partners)\n- [Explore the Ecosystem](/ecosystem)\n- [Careers](https://jobs.ashbyhq.com/allinbits)\n\n\u003c/div\u003e\u003c!-- end column--\u003e\n\n\u003cdiv class=\"column\"\u003e\n\n### Build with Gno\n\n- [Write Gno in the browser](https://play.gno.land)\n- [Read about the Gno Language](/gnolang)\n- [Visit the official documentation](https://docs.gno.land)\n- [Gno by Example](https://gno-by-example.com/)\n- [Efficient local development for Gno](https://docs.gno.land/gno-tooling/cli/gno-tooling-gnodev)\n- [Get testnet GNOTs](https://faucet.gno.land)\n\n\u003c/div\u003e\u003c!-- end column--\u003e\n\u003cdiv class=\"column\"\u003e\n\n### Explore the universe\n\n- [Discover demo packages](https://github.com/gnolang/gno/tree/master/examples)\n- [Gnoscan](https://gnoscan.io)\n- [Portal Loop](https://docs.gno.land/concepts/portal-loop)\n- [Testnet 4](https://test4.gno.land/)\n- Testnet Faucet Hub (soon)\n\n\u003c/div\u003e\u003c!-- end column--\u003e\n\u003c/div\u003e\u003c!-- end columns-3--\u003e`),\n\t}\n}\n\nfunc AdminSetOverride(content string) {\n\tadmin.AssertCallerIsOwner()\n\toverride = content\n}\n\nfunc AdminTransferOwnership(newAdmin std.Address) {\n\tadmin.AssertCallerIsOwner()\n\tadmin.TransferOwnership(newAdmin)\n}\n"},{"name":"home_filetest.gno","body":"package main\n\nimport \"gno.land/r/gnoland/home\"\n\nfunc main() {\n\tprintln(home.Render(\"\"))\n}\n\n// Output:\n// \u003cmain class='gno-tmpl-section'\u003e\n//\n// # Welcome to gno.land\n//\n// ### We’re building gno.land, set to become the leading open-source smart contract platform, using Gno, an interpreted and fully deterministic variation of the Go programming language for succinct and composable smart contracts.\n//\n//\n// With transparent and timeless code, gno.land is the next generation of smart contract platforms, serving as the “GitHub” of the ecosystem, with realms built using fully transparent, auditable code that anyone can inspect and reuse.\n//\n//\n// Intuitive and easy to use, gno.land lowers the barrier to web3 and makes censorship-resistant platforms accessible to everyone. If you want to help lay the foundations of a fairer and freer world, join us today.\n//\n// \u003cdiv class=\"jumbotron\"\u003e\n//\n// \u003cdiv class=\"columns-3\"\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Learn about gno.land\n//\n// - [About](/about)\n// - [GitHub](https://github.com/gnolang)\n// - [Blog](/blog)\n// - [Events](/events)\n// - Tokenomics (soon)\n// - [Partners, Fund, Grants](/partners)\n// - [Explore the Ecosystem](/ecosystem)\n// - [Careers](https://jobs.ashbyhq.com/allinbits)\n//\n// \u003c/div\u003e\u003c!-- end column--\u003e\n//\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Build with Gno\n//\n// - [Write Gno in the browser](https://play.gno.land)\n// - [Read about the Gno Language](/gnolang)\n// - [Visit the official documentation](https://docs.gno.land)\n// - [Gno by Example](https://gno-by-example.com/)\n// - [Efficient local development for Gno](https://docs.gno.land/gno-tooling/cli/gno-tooling-gnodev)\n// - [Get testnet GNOTs](https://faucet.gno.land)\n//\n// \u003c/div\u003e\u003c!-- end column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Explore the universe\n//\n// - [Discover demo packages](https://github.com/gnolang/gno/tree/master/examples)\n// - [Gnoscan](https://gnoscan.io)\n// - [Portal Loop](https://docs.gno.land/concepts/portal-loop)\n// - [Testnet 4](https://test4.gno.land/)\n// - Testnet Faucet Hub (soon)\n//\n// \u003c/div\u003e\u003c!-- end column--\u003e\n// \u003c/div\u003e\u003c!-- end columns-3--\u003e\n// \u003c/div\u003e\u003c!-- /jumbotron --\u003e\n//\n// \u003cdiv class=\"columns-3\"\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### [Latest Blogposts](/r/gnoland/blog)\n//\n// No posts.\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### [Latest Events](/r/gnoland/events)\n//\n// No events.\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Latest Contributions\n//\n// [View latest contributions](https://github.com/gnolang/gno/pulls)\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003c/div\u003e\u003c!-- /columns-3 --\u003e\n//\n//\n// ---\n//\n// ### [Gno Playground](https://play.gno.land)\n//\n//\n// Gno Playground is a web application designed for building, running, testing, and interacting\n// with your Gno code, enhancing your understanding of the Gno language. With Gno Playground, you can share your code,\n// execute tests, deploy your realms and packages to gno.land, and explore a multitude of other features.\n//\n//\n// Experience the convenience of code sharing and rapid experimentation with [Gno Playground](https://play.gno.land).\n//\n//\n// ---\n//\n// ### Explore New Packages and Realms\n//\n// \u003cdiv class=\"columns-3\"\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// #### [r/gnoland](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/gnoland)\n//\n// - [r/gnoland/blog](r/gnoland/blog)\n// - [r/gnoland/dao](r/gnoland/dao)\n// - [r/gnoland/faucet](r/gnoland/faucet)\n// - [r/gnoland/home](r/gnoland/home)\n// - [r/gnoland/pages](r/gnoland/pages)\n//\n// #### [r/sys](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/sys)\n//\n// - [r/sys/names](r/sys/names)\n// - [r/sys/rewards](r/sys/rewards)\n// - [/r/sys/validators/v2](/r/sys/validators/v2)\n//\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// #### [r/demo](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo)\n//\n// - [r/demo/boards](r/demo/boards)\n// - [r/demo/users](r/demo/users)\n// - [r/demo/banktest](r/demo/banktest)\n// - [r/demo/foo20](r/demo/foo20)\n// - [r/demo/foo721](r/demo/foo721)\n// - [r/demo/microblog](r/demo/microblog)\n// - [r/demo/nft](r/demo/nft)\n// - [r/demo/types](r/demo/types)\n// - [r/demo/art/gnoface](r/demo/art/gnoface)\n// - [r/demo/art/millipede](r/demo/art/millipede)\n// - [r/demo/groups](r/demo/groups)\n// - ...\n//\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// #### [p/demo](https://github.com/gnolang/gno/tree/master/examples/gno.land/p/demo)\n//\n// - [p/demo/avl](p/demo/avl)\n// - [p/demo/blog](p/demo/blog)\n// - [p/demo/ui](p/demo/ui)\n// - [p/demo/ufmt](p/demo/ufmt)\n// - [p/demo/merkle](p/demo/merkle)\n// - [p/demo/bf](p/demo/bf)\n// - [p/demo/flow](p/demo/flow)\n// - [p/demo/gnode](p/demo/gnode)\n// - [p/demo/grc/grc20](p/demo/grc/grc20)\n// - [p/demo/grc/grc721](p/demo/grc/grc721)\n// - ...\n//\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003c/div\u003e\u003c!-- /columns-3 --\u003e\n//\n//\n// ---\n//\n// ### Contributions (WorxDAO \u0026 GoR)\n//\n// coming soon\n//\n// ---\n//\n//\n// \u003cdiv class=\"columns-2\"\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Socials\n//\n// - Check out our [community projects](https://github.com/gnolang/awesome-gno)\n// - ![Discord](static/img/ico-discord.svg) [Discord](https://discord.gg/S8nKUqwkPn)\n// - ![Twitter](static/img/ico-twitter.svg) [Twitter](https://twitter.com/_gnoland)\n// - ![Youtube](static/img/ico-youtube.svg) [Youtube](https://www.youtube.com/@_gnoland)\n// - ![Telegram](static/img/ico-telegram.svg) [Telegram](https://t.me/gnoland)\n//\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Quote of the ~Day~ Block#123\n//\n// \u003e Now, you Gno.\n//\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003c/div\u003e\u003c!-- /columns-2 --\u003e\n//\n//\n// ---\n//\n// **This is a testnet.**\n// Package names are not guaranteed to be available for production.\n//\n// \u003c/main\u003e\n"},{"name":"overide_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/gnoland/home\"\n)\n\nfunc main() {\n\tstd.TestSetOriginCaller(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\thome.AdminSetOverride(\"Hello World!\")\n\tprintln(home.Render(\"\"))\n\thome.AdminTransferOwnership(testutils.TestAddress(\"newAdmin\"))\n\tdefer func() {\n\t\tr := recover()\n\t\tprintln(\"r: \", r)\n\t}()\n\thome.AdminSetOverride(\"Not admin anymore\")\n}\n\n// Output:\n// Hello World!\n// r: ownable: caller is not owner\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":"monit","path":"gno.land/r/gnoland/monit","files":[{"name":"monit.gno","body":"// Package monit links a monitoring system with the chain in both directions.\n//\n// The agent will periodically call Incr() and verify that the value is always\n// higher than the previously known one. The contract will store the last update\n// time and use it to detect whether or not the monitoring agent is functioning\n// correctly.\npackage monit\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/p/demo/watchdog\"\n)\n\nvar (\n\tcounter int\n\tlastUpdate time.Time\n\tlastCaller std.Address\n\twd = watchdog.Watchdog{Duration: 5 * time.Minute}\n\towner = ownable.New() // TODO: replace with -\u003e ownable.NewWithAddress...\n\twatchdogDuration = 5 * time.Minute\n)\n\n// Incr increments the counter and informs the watchdog that we're alive.\n// This function can be called by anyone.\nfunc Incr() int {\n\tcounter++\n\tlastUpdate = time.Now()\n\tlastCaller = std.PrevRealm().Addr()\n\twd.Alive()\n\treturn counter\n}\n\n// Reset resets the realm state.\n// This function can only be called by the admin.\nfunc Reset() {\n\tif owner.CallerIsOwner() != nil { // TODO: replace with owner.AssertCallerIsOwner\n\t\tpanic(\"unauthorized\")\n\t}\n\tcounter = 0\n\tlastCaller = std.PrevRealm().Addr()\n\tlastUpdate = time.Now()\n\twd = watchdog.Watchdog{Duration: 5 * time.Minute}\n}\n\nfunc Render(_ string) string {\n\tstatus := wd.Status()\n\treturn ufmt.Sprintf(\n\t\t\"counter=%d\\nlast update=%s\\nlast caller=%s\\nstatus=%s\",\n\t\tcounter, lastUpdate, lastCaller, status,\n\t)\n}\n\n// TransferOwnership transfers ownership to a new owner. This is a proxy to\n// ownable.Ownable.TransferOwnership.\nfunc TransferOwnership(newOwner std.Address) { owner.TransferOwnership(newOwner) }\n"},{"name":"monit_test.gno","body":"package monit\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestPackage(t *testing.T) {\n\t// initial state, watchdog is KO.\n\t{\n\t\texpected := `counter=0\nlast update=0001-01-01 00:00:00 +0000 UTC\nlast caller=\nstatus=KO`\n\t\tgot := Render(\"\")\n\t\tuassert.Equal(t, expected, got)\n\t}\n\n\t// call Incr(), watchdog is OK.\n\tIncr()\n\tIncr()\n\tIncr()\n\t{\n\t\texpected := `counter=3\nlast update=2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001\nlast caller=g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\nstatus=OK`\n\t\tgot := Render(\"\")\n\t\tuassert.Equal(t, expected, got)\n\t}\n\n\t/* XXX: improve tests once we've the missing std.TestSkipTime feature\n\t\t// wait 1h, watchdog is KO.\n\t\tuse std.TestSkipTime(time.Hour)\n\t\t{\n\t\t\texpected := `counter=3\n\tlast update=2009-02-13 22:31:30 +0000 UTC m=+1234564290.000000001\n\tlast caller=g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n\tstatus=KO`\n\t\t\tgot := Render(\"\")\n\t\t\tuassert.Equal(t, expected, got)\n\t\t}\n\n\t\t// call Incr(), watchdog is OK.\n\t\tIncr()\n\t\t{\n\t\t\texpected := `counter=4\n\tlast update=2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001\n\tlast caller=g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n\tstatus=OK`\n\t\t\tgot := Render(\"\")\n\t\t\tuassert.Equal(t, expected, got)\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":"monit","path":"gno.land/r/gnoland/monit","files":[{"name":"monit.gno","body":"// Package monit links a monitoring system with the chain in both directions.\n//\n// The agent will periodically call Incr() and verify that the value is always\n// higher than the previously known one. The contract will store the last update\n// time and use it to detect whether or not the monitoring agent is functioning\n// correctly.\npackage monit\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/p/demo/watchdog\"\n)\n\nvar (\n\tcounter int\n\tlastUpdate time.Time\n\tlastCaller std.Address\n\twd = watchdog.Watchdog{Duration: 5 * time.Minute}\n\towner = ownable.New() // TODO: replace with -\u003e ownable.NewWithAddress...\n\twatchdogDuration = 5 * time.Minute\n)\n\n// Incr increments the counter and informs the watchdog that we're alive.\n// This function can be called by anyone.\nfunc Incr() int {\n\tcounter++\n\tlastUpdate = time.Now()\n\tlastCaller = std.PreviousRealm().Addr()\n\twd.Alive()\n\treturn counter\n}\n\n// Reset resets the realm state.\n// This function can only be called by the admin.\nfunc Reset() {\n\tif owner.CallerIsOwner() != nil { // TODO: replace with owner.AssertCallerIsOwner\n\t\tpanic(\"unauthorized\")\n\t}\n\tcounter = 0\n\tlastCaller = std.PreviousRealm().Addr()\n\tlastUpdate = time.Now()\n\twd = watchdog.Watchdog{Duration: 5 * time.Minute}\n}\n\nfunc Render(_ string) string {\n\tstatus := wd.Status()\n\treturn ufmt.Sprintf(\n\t\t\"counter=%d\\nlast update=%s\\nlast caller=%s\\nstatus=%s\",\n\t\tcounter, lastUpdate, lastCaller, status,\n\t)\n}\n\n// TransferOwnership transfers ownership to a new owner. This is a proxy to\n// ownable.Ownable.TransferOwnership.\nfunc TransferOwnership(newOwner std.Address) { owner.TransferOwnership(newOwner) }\n"},{"name":"monit_test.gno","body":"package monit\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestPackage(t *testing.T) {\n\t// initial state, watchdog is KO.\n\t{\n\t\texpected := `counter=0\nlast update=0001-01-01 00:00:00 +0000 UTC\nlast caller=\nstatus=KO`\n\t\tgot := Render(\"\")\n\t\tuassert.Equal(t, expected, got)\n\t}\n\n\t// call Incr(), watchdog is OK.\n\tIncr()\n\tIncr()\n\tIncr()\n\t{\n\t\texpected := `counter=3\nlast update=2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001\nlast caller=g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\nstatus=OK`\n\t\tgot := Render(\"\")\n\t\tuassert.Equal(t, expected, got)\n\t}\n\n\t/* XXX: improve tests once we've the missing std.TestSkipTime feature\n\t\t// wait 1h, watchdog is KO.\n\t\tuse std.TestSkipTime(time.Hour)\n\t\t{\n\t\t\texpected := `counter=3\n\tlast update=2009-02-13 22:31:30 +0000 UTC m=+1234564290.000000001\n\tlast caller=g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n\tstatus=KO`\n\t\t\tgot := Render(\"\")\n\t\t\tuassert.Equal(t, expected, got)\n\t\t}\n\n\t\t// call Incr(), watchdog is OK.\n\t\tIncr()\n\t\t{\n\t\t\texpected := `counter=4\n\tlast update=2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001\n\tlast caller=g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n\tstatus=OK`\n\t\t\tgot := Render(\"\")\n\t\t\tuassert.Equal(t, expected, got)\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":"gnopages","path":"gno.land/r/gnoland/pages","files":[{"name":"admin.gno","body":"package gnopages\n\nimport (\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\nvar (\n\tadminAddr std.Address\n\tmoderatorList avl.Tree\n\tinPause bool\n)\n\nfunc init() {\n\t// adminAddr = std.OriginCaller() // FIXME: find a way to use this from the main's genesis.\n\tadminAddr = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"\n}\n\nfunc AdminSetAdminAddr(addr std.Address) {\n\tassertIsAdmin()\n\tadminAddr = addr\n}\n\nfunc AdminSetInPause(state bool) {\n\tassertIsAdmin()\n\tinPause = state\n}\n\nfunc AdminAddModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), true)\n}\n\nfunc AdminRemoveModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), false) // XXX: delete instead?\n}\n\nfunc ModAddPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\tcaller := std.OriginCaller()\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.NewPost(caller, slug, title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc ModEditPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.GetPost(slug).Update(title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc isAdmin(addr std.Address) bool {\n\treturn addr == adminAddr\n}\n\nfunc isModerator(addr std.Address) bool {\n\t_, found := moderatorList.Get(addr.String())\n\treturn found\n}\n\nfunc assertIsAdmin() {\n\tcaller := std.OriginCaller()\n\tif !isAdmin(caller) {\n\t\tpanic(\"access restricted.\")\n\t}\n}\n\nfunc assertIsModerator() {\n\tcaller := std.OriginCaller()\n\tif isAdmin(caller) || isModerator(caller) {\n\t\treturn\n\t}\n\tpanic(\"access restricted\")\n}\n\nfunc assertNotInPause() {\n\tif inPause {\n\t\tpanic(\"access restricted (pause)\")\n\t}\n}\n"},{"name":"page_about.gno","body":"package gnopages\n\nfunc init() {\n\tpath := \"about\"\n\ttitle := \"gno.land Is A Platform To Write Smart Contracts In Gno\"\n\t// XXX: description := \"On gno.land, developers write smart contracts and other blockchain apps using Gno without learning a language that’s exclusive to a single ecosystem.\"\n\tbody := `\ngno.land is a next-generation smart contract platform using Gno, an interpreted version of the general-purpose Go\nprogramming language. On gno.land, smart contracts can be uploaded on-chain only by publishing their full source code,\nmaking it trivial to verify the contract or fork it into an improved version. With a system to publish reusable code\nlibraries on-chain, gno.land serves as the “GitHub” of the ecosystem, with realms built using fully transparent,\nauditable code that anyone can inspect and reuse.\n\ngno.land addresses many pressing issues in the blockchain space, starting with the ease of use and intuitiveness of\nsmart contract platforms. Developers can write smart contracts without having to learn a new language that’s exclusive\nto a single ecosystem or limited by design. Go developers can easily port their existing web apps to gno.land or build\nnew ones from scratch, making web3 vastly more accessible.\n\nSecured by Proof of Contribution (PoC), a DAO-managed Proof-of-Authority consensus mechanism, gno.land prioritizes\nfairness and merit, rewarding the people most active on the platform. PoC restructures the financial incentives that\noften corrupt blockchain projects, opting instead to reward contributors for their work based on expertise, commitment, and\nalignment.\n\nOne of our inspirations for gno.land is the gospels, which built a system of moral code that lasted thousands of years.\nBy observing a minimal production implementation, gno.land’s design will endure over time and serve as a reference for\nfuture generations with censorship-resistant tools that improve their understanding of the world.\n`\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:22Z\", nil, nil)\n}\n"},{"name":"page_contribute.gno","body":"package gnopages\n\nfunc init() {\n\tpath := \"contribute\"\n\ttitle := \"Contributor Ecosystem: Call for Contributions\"\n\tbody := `\n\ngno.land puts at the center of its identity the contributors that help to create and shape the project into what it is; incentivizing those who contribute the most and help advance its vision. Eventually, contributions will be incentivized directly on-chain; in the meantime, this page serves to illustrate our current off-chain initiatives.\n\ngno.land is still in full-steam development. For now, we're looking for the earliest of adopters; curious to explore a new way to build smart contracts and eager to make an impact. Joining gno.land's development now means you can help to shape the base of its development ecosystem, which will pave the way for the next generation of blockchain programming.\n\nAs an open-source project, we welcome all contributions. On this page you can find some pointers on where to get started; as well as some incentives for the most valuable and important contributions.\n\n## Where to get started\n\nIf you are interested in contributing to gno.land, you can jump on in on our [GitHub monorepo](https://github.com/gnolang/gno/blob/master/CONTRIBUTING.md) - where most development happens.\n\nA good place where to start are the issues tagged [\"good first issue\"](https://github.com/gnolang/gno/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22). They should allow you to make some impact on the Gno repository while you're still exploring the details of how everything works.\n\n## Gno Bounties\n\nAdditionally, you can look out to help on specific issues labeled as bounties. All contributions will then concur to form your profile for Game of Realms.\n\nThe Gno bounty program is a good way to find interesting challenges in Gno, and get rewarded for helping us advance the project. We will maintain open and rewardable bounties in the gnolang/gno repository, and you can search all available bounties by using the [\"bounty\" label](https://github.com/gnolang/gno/labels/bounty).\n\nRecommendations on participating in the gno.land Bounty Program:\n\n- Identify the bounty you want to work on, and join in the discussion on the issue for anything that is unclear; or where you want to more clearly define the work to be done. At this stage, you can also start working on an initial implementation in your local enviornment.\n- Once you have spent time on the code related to the bounty, we recommend submitting a 'draft' PR as soon as possible.\n - The draft PR doesn't indicate that the bounty has been assigned to you, others are free to work on other draft PRs for the bounty.\n - Make sure to reference the bounty issue on the PR description you're writing.\n - After submitting the 'draft' PR, continue working until you are ready to mark the PR as \"ready for review\".\n - The core team will review the bounty PR submission after the work on the bounty has been completed, and determine if it qualifies for the bounty reward.\n- Ask for clarification early if an element on the requirements or implementation design is unclear.\n - Aside from publishing the PR early, keeping regular updates with the core team on the bounty issue is key to being on the right track.\n - As part of the requirements, you must adhere to the [contributing guidelines](https://github.com/gnolang/gno/blob/master/CONTRIBUTING.md); additionally, it is expected that any newly added code or functionality is properly documented, tested and covered, at least in 80% of added code.\n - You're welcome to propose additional features and work on an issue should you envision a plausible expansion or change in scope. The core team may assign a bounty to the additional work, or change the bounty with respect to the changed scope.\n\nYou may make your submission at any time; however we invite you to publish your draft PR very early in the development process. This will make your work public, so you can easily get help by the core team and other community members. Additionally, your work can be continued by other people should you get stuck or no longer be willing to work on the bounty. Likewise, you can continue the abandoned or stuck work that someone else worked on.\n\nDon't fear your work being \"stolen\": if a submission is the result of multiple people's efforts, we will look to split the bounty in a way that is fair and recognises each participant in creating the final outcome. Here are some examples of how that can happen:\n\n- If Alice does most of the work and abandons it; then Bob comes around and finishes the job, then Bob's PR will be merged. But the core team will propose a split like 70% for Alice and 30% for Bob (depending, of course, on the relative effort undertaken by both).\n- If Alice makes a PR that does only 50% of the work outlined in the requirements for the original issue, she will get 50%. Someone can still come up and finish the job; and claim the remaining part.\n\t- If you, for instance, cannot complete the entirety of the task or, as a non-developer, can only contribute a part of the specification/implementation, you may still be awarded a bounty for your input in the contribution.\n- If Alice makes a PR that aside from implementing what's required, also undertakes creating useful tools among the way, she may qualify for an \"outstanding contribution\"; and may be awarded up to 25% more of the original bounty's value. Or she may also ask if the team would be willing to offer a different bounty for the implementation of the tools.\n\nParticipants in the gno.land Bounty Program must meet the legal Terms and Conditions referenced [here](https://docs.google.com/document/d/e/2PACX-1vSUF-JwIXGscrNsc5QBD7Pa6i83mXUGogAEIf1wkeb_w42UgL3Lj6jFKMlNTdwEMUnhsLkjRlhe25K4/pub).\n\n### Bounty sizes\n\nEach bounty is associated with a size, to which corresponds the maximum compensation for the work involved on the bounty. A bounty size may under rare occasion be revisited to a bigger or smaller size; hence why it's important to talk about your proposed solution with the core team ahead of time.\n\nIn some cases, the work associated with a bounty may be outstanding. When that happens, the core team can decide to award up to 25% of the bounty's value to the recipient.\n\nThe value of the bounty, aside from the material completion of the task, considers the involved time in managing the created pull request and iterating on feedback.\n\n\nt-shirt size | expected compensation\n-------------|-----------------------\n[XS] | $ 500\n[S] | $ 1000\n[M] | $ 2000\n[L] | $ 4000\n[XL] | $ 8000\n_[XXL]_ \\* | $ 16000\n_[3XL]_ \\* | $ 32000\n\n[XS]: https://github.com/gnolang/gno/labels/bounty%2FXS\n[S]: https://github.com/gnolang/gno/labels/bounty%2FS\n[M]: https://github.com/gnolang/gno/labels/bounty%2FM\n[L]: https://github.com/gnolang/gno/labels/bounty%2FL\n[XL]: https://github.com/gnolang/gno/labels/bounty%2FXL\n[XXL]: https://github.com/gnolang/gno/labels/bounty%2FXXL\n[3XL]: https://github.com/gnolang/gno/labels/bounty%2F3XL\n\n\\*: XXL and 3XL bounties are exceptional. Almost no issues will have these sizes; most will be broken down into smaller bounties.\n\n## gno.land Grants\n\nThe gno.land grants program is to encourage and support the growth of the gno.land contributor community, and build out the usability of the platform and smart contract library. The program provides financial resources to contributors to explore the Gno tech stack, and build dApps, tooling, infrastructure, products, and smart contract libraries in gno.land.\n\nFor more details on gno.land grants, suggested topics, and how to apply, visit our grants [repository](https://github.com/gnolang/grants). \n\n## Join Game of Realms\n\nGame of Realms is the overarching contributor network of gnomes, currently running off-chain, and will eventually transition on-chain. At this stage, a Game of Realms contribution is comprised of high-impact contributions identified as ['notable contributions'](https://github.com/gnolang/game-of-realms/tree/main/contributors).\n\nThese contributions are not linked to immediate financial rewards, but are notable in nature, in the sense they are a challenge, make a significant addition to the project, and require persistence, with minimal feedback loops from the core team.\n\nThe selection of a notable contribution or the sum of contributions that equal 'notable' is based on the impact it has on the development of the project. For now, it is focused on code contributions, and will evolve over time. The Gno development teams will initially qualify and evaluate notable contributions, and vote off-chain on adding them to the 'notable contributions' folder on GitHub.\n\nYou can always contribute to the project, and all contributions will be noticed. Contributing now is a way to build your personal contributor profile in gno.land early on in the ecosystem, and signal your commitment to the project, the community, and its future.\n\nThere are a variety of ways to make your contributions count:\n\n- Core code contributions\n- Realm and pure package development\n- Validator tooling\n- Developer tooling\n- Tutorials and documentation\n\nTo start, we recommend you create a PR in the Game of Realms [repository](https://github.com/gnolang/game-of-realms) to create your profile page for all your contributions.`\n\n\t_ = b.NewPost(\"\", path, title, body, \"2024-09-05T00:00:00Z\", nil, nil)\n}\n"},{"name":"page_ecosystem.gno","body":"package gnopages\n\nfunc init() {\n\tvar (\n\t\tpath = \"ecosystem\"\n\t\ttitle = \"Discover gno.land Ecosystem Projects \u0026 Initiatives\"\n\t\t// XXX: description = \"Dive further into the gno.land ecosystem and discover the core infrastructure, projects, smart contracts, and tooling we’re building.\"\n\t\tbody = `\n### [Gno Playground](https://play.gno.land)\n\nGno Playground is a simple web interface that lets you write, test, and experiment with your Gno code to improve your\nunderstanding of the Gno language. You can share your code, run unit tests, deploy your realms and packages, and execute\nfunctions in your code using the repo.\n\nVisit the playground at [play.gno.land](https://play.gno.land)!\n\n### [Gno Studio Connect](https://gno.studio/connect)\n\nGno Studio Connect provides seamless access to realms, making it simple to explore, interact, and engage\nwith gno.land’s smart contracts through function calls. Connect focuses on function calls, enabling users to interact\nwith any realm’s exposed function(s) on gno.land.\n\nSee your realm interactions in [Gno Studio Connect](https://gno.studio/connect)\n\n### [Gnoscan](https://gnoscan.io)\n\nDeveloped by the Onbloc team, Gnoscan is gno.land’s blockchain explorer. Anyone can use Gnoscan to easily find\ninformation that resides on the gno.land blockchain, such as wallet addresses, TX hashes, blocks, and contracts.\nGnoscan makes our on-chain data easy to read and intuitive to discover.\n\nExplore the gno.land blockchain at [gnoscan.io](https://gnoscan.io)!\n\n### Adena\n\nAdena is a user-friendly non-custodial wallet for gno.land. Open-source and developed by Onbloc, Adena allows gnomes to\ninteract easily with the chain. With an emphasis on UX, Adena is built to handle millions of realms and tokens with a\nhigh-quality interface, support for NFTs and custom tokens, and seamless integration. Install Adena via the [official website](https://www.adena.app/)\n\n### Gnoswap\n\nGnoswap is currently under development and led by the Onbloc team. Gnoswap will be the first DEX on gno.land and is an\nautomated market maker (AMM) protocol written in Gno that allows for permissionless token exchanges on the platform.\n\n### Flippando\n\nFlippando is a simple on-chain memory game, ported from Solidity to Gno, which starts with an empty matrix to flip tiles\non to see what’s underneath. If the tiles match, they remain uncovered; if not, they are briefly shown, and the player\nmust memorize their colors until the entire matrix is uncovered. The end result can be minted as an NFT, which can later\nbe assembled into bigger, more complex NFTs, creating a digital “painting” with the uncovered tiles. Play the game at [Flippando](https://gno.flippando.xyz/flip)\n\n### Gno Native Kit\n\n[Gno Native Kit](https://github.com/gnolang/gnonative) is a framework that allows developers to build and port gno.land (d)apps written in the (d)app's native language.\n\n\n`\n\t)\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:23Z\", nil, nil)\n}\n"},{"name":"page_gnolang.gno","body":"package gnopages\n\nfunc init() {\n\tvar (\n\t\tpath = \"gnolang\"\n\t\ttitle = \"About the Gno, the Language for gno.land\"\n\t\t// TODO fix broken images\n\t\tbody = `\n\n[Gno](https://github.com/gnolang/gno) is an interpretation of the widely-used Go (Golang) programming language for blockchain created by Cosmos co-founder Jae Kwon in 2022 to mark a new era in smart contracting. Gno is ~99% identical to Go, so Go programmers can start coding in Gno right away, with a minimal learning curve. For example, Gno comes with blockchain-specific standard libraries, but any code that doesn’t use blockchain-specific logic can run in Go with minimal processing. Libraries that don’t make sense in the blockchain context, such as network or operating-system access, are not available in Gno. Otherwise, Gno loads and uses many standard libraries that power Go, so most of the parsing of the source code is the same.\n\nUnder the hood, the Gno code is parsed into an abstract syntax tree (AST) and the AST itself is used in the interpreter, rather than bytecode as in many virtual machines such as Java, Python, or Wasm. This makes even the GnoVM accessible to any Go programmer. The novel design of the intuitive GnoVM interpreter allows Gno to freeze and resume the program by persisting and loading the entire memory state. Gno is deterministic, auto-persisted, and auto-Merkle-ized, allowing (smart contract) programs to be succinct, as the programmer doesn’t have to serialize and deserialize objects to persist them into a database (unlike programming applications with the Cosmos SDK).\n\n## How Gno Differs from Go\n\n![Gno and Go differences](static/img/gno-language/go-and-gno.jpg)\n\nThe composable nature of Go/Gno allows for type-checked interactions between contracts, making gno.land safer and more powerful, as well as operationally cheaper and faster. Smart contracts on gno.land are light, simple, more focused, and easily interoperable—a network of interconnected contracts rather than siloed monoliths that limit interactions with other contracts.\n\n![Example of Gno code](static/img/gno-language/code-example.jpg)\n\n## Gno Inherits Go’s Built-in Security Features\n\nGo supports secure programming through exported/non-exported fields, enabling a “least-authority” design. It is easy to create objects and APIs that expose only what should be accessible to callers while hiding what should not be simply by the capitalization of letters, thus allowing a succinct representation of secure logic that can be called by multiple users.\n\nAnother major advantage of Go is that the language comes with an ecosystem of great tooling, like the compiler and third-party tools that statically analyze code. Gno inherits these advantages from Go directly to create a smart contract programming language that provides embedding, composability, type-check safety, and garbage collection, helping developers to write secure code relying on the compiler, parser, and interpreter to give warning alerts for common mistakes.\n\n## Gno vs Solidity\n\nThe most widely-adopted smart contract language today is Ethereum’s EVM-compatible Solidity. With bytecode built from the ground up and Turing complete, Solidity opened up a world of possibilities for decentralized applications (dApps) and there are currently more than 10 million contracts deployed on Ethereum. However, Solidity provides limited tooling and its EVM has a stack limit and computational inefficiencies.\n\nSolidity is designed for one purpose only (writing smart contracts) and is bound by the limitations of the EVM. In addition, developers have to learn several languages if they want to understand the whole stack or work across different ecosystems. Gno aspires to exceed Solidity on multiple fronts (and other smart contract languages like CosmWasm or Substrate) as every part of the stack is written in Gno. It’s easy for developers to understand the entire system just by studying a relatively small code base.\n\n## Gno Is Essential for the Wider Adoption of Web3\n\nGno makes imports as easy as they are in web2 with runtime-based imports for seamless dependency flow comprehension, and support for complex structs, beyond primitive types. Gno is ultimately cost-effective as dependencies are loaded once, enabling remote function calls as local, and providing automatic and independent per-realm state persistence.\n\nUsing Gno, developers can rapidly accelerate application development and adopt a modular structure by reusing and reassembling existing modules without building from scratch. They can embed one structure inside another in an intuitive way while preserving localism, and the language specification is simple, successfully balancing practicality and minimalism.\n\nThe Go language is so well designed that the Gno smart contract system will become the new gold standard for smart contract development and other blockchain applications. As a programming language that is universally adopted, secure, composable, and complete, Gno is essential for the broader adoption of web3 and its sustainable growth.`\n\t)\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:25Z\", nil, nil)\n}\n"},{"name":"page_license.gno","body":"package gnopages\n\nfunc init() {\n\tvar (\n\t\tpath = \"license\"\n\t\ttitle = \"Gno Network General Public License\"\n\t\tbody = `Copyright (C) 2024 NewTendermint, LLC\n\nThis program is free software: you can redistribute it and/or modify it under\nthe terms of the GNO Network General Public License as published by\nNewTendermint, LLC, either version 4 of the License, or (at your option) any\nlater version published by NewTendermint, LLC.\n\nThis program is distributed in the hope that it will be useful, but is provided\nas-is and WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNO Network\nGeneral Public License for more details.\n\nYou should have received a copy of the GNO Network General Public License along\nwith this program. If not, see \u003chttps://gno.land/license\u003e.\n\nAttached below are the terms of the GNO Network General Public License, Version\n4 (a fork of the GNU Affero General Public License 3).\n\n## Additional Terms\n\n### Strong Attribution\n\nIf any of your user interfaces, such as websites and mobile applications, serve\nas the primary point of entry to a platform or blockchain that 1) offers users\nthe ability to upload their own smart contracts to the platform or blockchain,\nand 2) leverages any Covered Work (including the GNO virtual machine) to run\nthose smart contracts on the platform or blockchain (\"Applicable Work\"), then\nthe Applicable Work must prominently link to (1) gno.land or (2) any other URL\ndesignated by NewTendermint, LLC that has not been rejected by the governance of\nthe first chain known as gno.land, provided that the identity of the first chain\nis not ambiguous. In the event the identity of the first chain is ambiguous,\nthen NewTendermint, LLC's designation shall control. Such link must appear\nconspicuously in the header or footer of the Applicable Work, such that all\nusers may learn of gno.land or the URL designated by NewTendermint, LLC.\n\nThis additional attribution requirement shall remain in effect for (1) 7\nyears from the date of publication of the Applicable Work, or (2) 7 years from\nthe date of publication of the Covered Work (including republication of new\nversions), whichever is later, but no later than 12 years after the application\nof this strong attribution requirement to the publication of the Applicable\nWork. For purposes of this Strong Attribution requirement, Covered Work shall\nmean any work that is licensed under the GNO Network General Public License,\nVersion 4 or later, by NewTendermint, LLC.\n\n\n# GNO NETWORK GENERAL PUBLIC LICENSE\n\nVersion 4, 7 May 2024\n\nModified from the GNU AFFERO GENERAL PUBLIC LICENSE.\nGNU is not affiliated with GNO or NewTendermint, LLC.\nCopyright (C) 2022 NewTendermint, LLC.\n\n## Preamble\n\nThe GNO Network General Public License is a free, copyleft license for\nsoftware and other kinds of works, specifically designed to ensure\ncooperation with the community in the case of network server software.\n\nThe licenses for most software and other practical works are designed\nto take away your freedom to share and change the works. By contrast,\nour General Public Licenses are intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.\n\nWhen we speak of free software, we are referring to freedom, not\nprice. Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\nDevelopers that use our General Public Licenses protect your rights\nwith two steps: (1) assert copyright on the software, and (2) offer\nyou this License which gives you legal permission to copy, distribute\nand/or modify the software.\n\nA secondary benefit of defending all users' freedom is that\nimprovements made in alternate versions of the program, if they\nreceive widespread use, become available for other developers to\nincorporate. Many developers of free software are heartened and\nencouraged by the resulting cooperation. However, in the case of\nsoftware used on network servers, this result may fail to come about.\nThe GNU General Public License permits making a modified version and\nletting the public access it on a server without ever releasing its\nsource code to the public.\n\nThe GNO Network General Public License is designed specifically to\nensure that, in such cases, the modified source code becomes available\nto the community. It requires the operator of a network server to\nprovide the source code of the modified version running there to the\nusers of that server. Therefore, public use of a modified version, on\na publicly accessible server, gives the public access to the source\ncode of the modified version.\n\nThe precise terms and conditions for copying, distribution and\nmodification follow.\n\n## TERMS AND CONDITIONS\n\n### 0. Definitions.\n\n\"This License\" refers to version 4 of the GNO Network General Public License.\n\n\"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n\"The Program\" refers to any copyrightable work licensed under this\nLicense. Each licensee is addressed as \"you\". \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\nTo \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy. The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\nA \"covered work\" means either the unmodified Program or a work based\non the Program.\n\nTo \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy. Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\nTo \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies. Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\nAn interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License. If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n### 1. Source Code.\n\nThe \"source code\" for a work means the preferred form of the work\nfor making modifications to it. \"Object code\" means any non-source\nform of a work.\n\nA \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\nThe \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form. A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\nThe \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities. However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work. For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\nThe Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\nThe Corresponding Source for a work in source code form is that\nsame work.\n\n### 2. Basic Permissions.\n\nAll rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met. This License explicitly affirms your unlimited\npermission to run the unmodified Program. The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work. This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\nYou may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force. You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright. Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\nConveying under any other circumstances is permitted solely under\nthe conditions stated below. Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n### 3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\nNo covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\nWhen you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n### 4. Conveying Verbatim Copies.\n\nYou may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\nYou may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n### 5. Conveying Modified Source Versions.\n\nYou may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n- a) The work must carry prominent notices stating that you modified\n it, and giving a relevant date.\n- b) The work must carry prominent notices stating that it is\n released under this License and any conditions added under section\n 7. This requirement modifies the requirement in section 4 to\n \"keep intact all notices\".\n- c) You must license the entire work, as a whole, under this\n License to anyone who comes into possession of a copy. This\n License will therefore apply, along with any applicable section 7\n additional terms, to the whole of the work, and all its parts,\n regardless of how they are packaged. This License gives no\n permission to license the work in any other way, but it does not\n invalidate such permission if you have separately received it.\n- d) If the work has interactive user interfaces, each must display\n Appropriate Legal Notices; however, if the Program has interactive\n interfaces that do not display Appropriate Legal Notices, your\n work need not make them do so.\n\nA compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit. Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n### 6. Conveying Non-Source Forms.\n\n You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n- a) Convey the object code in, or embodied in, a physical product\n (including a physical distribution medium), accompanied by the\n Corresponding Source fixed on a durable physical medium\n customarily used for software interchange.\n- b) Convey the object code in, or embodied in, a physical product\n (including a physical distribution medium), accompanied by a\n written offer, valid for at least three years and valid for as\n long as you offer spare parts or customer support for that product\n model, to give anyone who possesses the object code either (1) a\n copy of the Corresponding Source for all the software in the\n product that is covered by this License, on a durable physical\n medium customarily used for software interchange, for a price no\n more than your reasonable cost of physically performing this\n conveying of source, or (2) access to copy the\n Corresponding Source from a network server at no charge.\n- c) Convey individual copies of the object code with a copy of the\n written offer to provide the Corresponding Source. This\n alternative is allowed only occasionally and noncommercially, and\n only if you received the object code with such an offer, in accord\n with subsection 6b.\n- d) Convey the object code by offering access from a designated\n place (gratis or for a charge), and offer equivalent access to the\n Corresponding Source in the same way through the same place at no\n further charge. You need not require recipients to copy the\n Corresponding Source along with the object code. If the place to\n copy the object code is a network server, the Corresponding Source\n may be on a different server (operated by you or a third party)\n that supports equivalent copying facilities, provided you maintain\n clear directions next to the object code saying where to find the\n Corresponding Source. Regardless of what server hosts the\n Corresponding Source, you remain obligated to ensure that it is\n available for as long as needed to satisfy these requirements.\n- e) Convey the object code using peer-to-peer transmission, provided\n you inform other peers where the object code and Corresponding\n Source of the work are being offered to the general public at no\n charge under subsection 6d.\n\nA separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\nA \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling. In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage. For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product. A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n\"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source. The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\nIf you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information. But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\nThe requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed. Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\nCorresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n### 7. Additional Terms.\n\n\"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law. If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\nWhen you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit. (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.) You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\nNotwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n- a) Disclaiming warranty or limiting liability differently from the\n terms of sections 15 and 16 of this License; or\n- b) Requiring preservation of specified reasonable legal notices or\n author attributions in that material or in the Appropriate Legal\n Notices displayed by works containing it; or\n- c) Prohibiting misrepresentation of the origin of that material, or\n requiring that modified versions of such material be marked in\n reasonable ways as different from the original version; or\n- d) Limiting the use for publicity purposes of names of licensors or\n authors of the material; or\n- e) Declining to grant rights under trademark law for use of some\n trade names, trademarks, or service marks; or\n- f) Requiring indemnification of licensors and authors of that\n material by anyone who conveys the material (or modified versions of\n it) with contractual assumptions of liability to the recipient, for\n any liability that these contractual assumptions directly impose on\n those licensors and authors; or\n- g) Requiring strong attribution such as notices on any user interfaces\n that run or convey any covered work, such as a prominent link to a URL\n on the header of a website, such that all users of the covered work may\n become aware of the notice, for a period no longer than 20 years.\n\nAll other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10. If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term. If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\nIf you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\nAdditional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n### 8. Termination.\n\nYou may not propagate or modify a covered work except as expressly\nprovided under this License. Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\nHowever, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\nMoreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\nTermination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License. If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n### 9. Acceptance Not Required for Having Copies.\n\nYou are not required to accept this License in order to receive or\nrun a copy of the Program. Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance. However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work. These actions infringe copyright if you do\nnot accept this License. Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n### 10. Automatic Licensing of Downstream Recipients.\n\nEach time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License. You are not responsible\nfor enforcing compliance by third parties with this License.\n\nAn \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations. If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\nYou may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License. For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n### 11. Patents.\n\nA \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based. The\nwork thus licensed is called the contributor's \"contributor version\".\n\nA contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version. For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\nEach contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\nIn the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement). To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\nIf you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients. \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\nIf, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\nA patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License. You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\nNothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n### 12. No Surrender of Others' Freedom.\n\nIf conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License. If you cannot convey a\ncovered work so as to simultaneously satisfy your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all. For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n### 13. Remote Network Interaction; Use with the GNU General Public License.\n\nNotwithstanding any other provision of this License, if you modify the\nProgram, your modified version must prominently offer all users\ninteracting with it remotely through a computer network (if your version\nsupports such interaction) an opportunity to receive the Corresponding\nSource of your version by providing access to the Corresponding Source\nfrom a network server at no charge, through some standard or customary\nmeans of facilitating copying of software. This Corresponding Source\nshall include the Corresponding Source for any work covered by version 3\nof the GNU General Public License that is incorporated pursuant to the\nfollowing paragraph.\n\nNotwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU General Public License into a single\ncombined work, and to convey the resulting work. The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the work with which it is combined will remain governed by version\n3 of the GNU General Public License.\n\n### 14. Revised Versions of this License.\n\nNewTendermint LLC may publish revised and/or new versions of\nthe GNO Network General Public License from time to time. Such new versions\nwill be similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\nEach version is given a distinguishing version number. If the\nProgram specifies that a certain numbered version of the GNO Network General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Gno Software\nFoundation. If the Program does not specify a version number of the\nGNO Network General Public License, you may choose any version ever published\nby NewTendermint LLC.\n\nIf the Program specifies that a proxy can decide which future\nversions of the GNO Network General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\nLater license versions may give you additional or different\npermissions. However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n### 15. Disclaimer of Warranty.\n\nTHERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n### 16. Limitation of Liability.\n\nIN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n### 17. Interpretation of Sections 15 and 16.\n\nIf the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\nEND OF TERMS AND CONDITIONS\n\n## How to Apply These Terms to Your New Programs\n\nIf you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\nTo do so, attach the following notices to the program. It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n \u003cone line to give the program's name and a brief idea of what it does.\u003e\n Copyright (C) \u003cyear\u003e \u003cname of author\u003e\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNO Network General Public License as published by\n NewTendermint LLC, either version 4 of the License, or\n (at your option) any later version.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNO Network General Public License for more details.\n\n You should have received a copy of the GNO Network General Public License\n along with this program. If not, see \u003chttps://gno.land/license\u003e.\n\nAlso add information on how to contact you by electronic and paper mail.\n\nIf your software can interact with users remotely through a computer\nnetwork, you should also make sure that it provides a way for users to\nget its source. For example, if your program is a web application, its\ninterface could display a \"Source\" link that leads users to an archive\nof the code. There are many ways you could offer source, and different\nsolutions will be better for different programs; see section 13 for the\nspecific requirements.\n`\n\t)\n\t_ = b.NewPost(\"\", path, title, body, \"2024-04-22T00:00:00Z\", nil, nil)\n}\n"},{"name":"page_partners.gno","body":"package gnopages\n\nfunc init() {\n\tpath := \"partners\"\n\ttitle := \"Partnerships\"\n\tbody := `### Fund and Grants Program\n\nAre you a builder, tinkerer, or researcher? If you’re looking to create awesome dApps, tooling, infrastructure, \nor smart contract libraries on gno.land, you can apply for a grant. The gno.land Ecosystem Fund and Grants program \nprovides financial contributions for individuals and teams to innovate on the platform.\n\nRead more about our Funds and Grants program [here](https://github.com/gnolang/ecosystem-fund-grants).\n`\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:27Z\", nil, nil)\n}\n"},{"name":"page_start.gno","body":"package gnopages\n\nfunc init() {\n\tpath := \"start\"\n\ttitle := \"Getting Started with Gno\"\n\t// XXX: description := \"\"\n\n\t// TODO: codegen to use README files here\n\n\t/* TODO: port previous message: This is a demo of Gno smart contract programming. This document was\n\tconstructed by Gno onto a smart contract hosted on the data Realm\n\tname [\"gno.land/r/demo/boards\"](https://gno.land/r/demo/boards/)\n\t([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)).\n\t*/\n\tbody := `## Getting Started with Gno\n\n- [Install Gno Key](/r/demo/boards:testboard/5)\n- TODO: add more links\n`\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:28Z\", nil, nil)\n}\n"},{"name":"page_testnets.gno","body":"package gnopages\n\nfunc init() {\n\tpath := \"testnets\"\n\ttitle := \"gno.land Testnet List\"\n\tbody := `\n- [Portal Loop](https://docs.gno.land/concepts/portal-loop) - a rolling testnet\n- [staging.gno.land](https://staging.gno.land) - wiped every commit to monorepo master\n- _[test4.gno.land](https://test4.gno.land) (latest)_\n\nFor a list of RPC endpoints, see the [reference documentation](https://docs.gno.land/reference/rpc-endpoints).\n\n## Local development\n\nSee the \"Getting started\" section in the [official documentation](https://docs.gno.land/getting-started/local-setup).\n`\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:29Z\", nil, nil)\n}\n"},{"name":"page_tokenomics.gno","body":"package gnopages\n\nfunc init() {\n\tvar (\n\t\tpath = \"tokenomics\"\n\t\ttitle = \"gno.land Tokenomics\"\n\t\t// XXX: description = \"\"\"\n\t\tbody = `Lorem Ipsum`\n\t)\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:30Z\", nil, nil)\n}\n"},{"name":"pages.gno","body":"package gnopages\n\nimport (\n\t\"gno.land/p/demo/blog\"\n)\n\n// TODO: switch from p/blog to p/pages\n\nvar b = \u0026blog.Blog{\n\tTitle: \"Gnoland's Pages\",\n\tPrefix: \"/r/gnoland/pages:\",\n\tNoBreadcrumb: true,\n}\n\nfunc Render(path string) string {\n\treturn b.Render(path)\n}\n"},{"name":"pages_test.gno","body":"package gnopages\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestHome(t *testing.T) {\n\tprintedOnce := false\n\tgot := Render(\"\")\n\texpectedSubtrings := []string{\n\t\t\"/r/gnoland/pages:p/tokenomics\",\n\t\t\"/r/gnoland/pages:p/start\",\n\t\t\"/r/gnoland/pages:p/contribute\",\n\t\t\"/r/gnoland/pages:p/about\",\n\t\t\"/r/gnoland/pages:p/gnolang\",\n\t}\n\tfor _, substring := range expectedSubtrings {\n\t\tif !strings.Contains(got, substring) {\n\t\t\tif !printedOnce {\n\t\t\t\tprintln(got)\n\t\t\t\tprintedOnce = true\n\t\t\t}\n\t\t\tt.Errorf(\"expected %q, but not found.\", substring)\n\t\t}\n\t}\n}\n\nfunc TestAbout(t *testing.T) {\n\tprintedOnce := false\n\tgot := Render(\"p/about\")\n\texpectedSubtrings := []string{\n\t\t\"gno.land Is A Platform To Write Smart Contracts In Gno\",\n\t\t\"gno.land is a next-generation smart contract platform using Gno, an interpreted version of the general-purpose Go\\nprogramming language.\",\n\t}\n\tfor _, substring := range expectedSubtrings {\n\t\tif !strings.Contains(got, substring) {\n\t\t\tif !printedOnce {\n\t\t\t\tprintln(got)\n\t\t\t\tprintedOnce = true\n\t\t\t}\n\t\t\tt.Errorf(\"expected %q, but not found.\", substring)\n\t\t}\n\t}\n}\n"},{"name":"util.gno","body":"package gnopages\n\nfunc checkErr(err error) {\n\tif err != nil {\n\t\tpanic(err)\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":"validators","path":"gno.land/r/sys/validators/v2","files":[{"name":"doc.gno","body":"// Package validators implements the on-chain validator set management through Proof of Contribution.\n// The Realm exposes only a public executor for govdao proposals, that can suggest validator set changes.\npackage validators\n"},{"name":"gnosdk.gno","body":"package validators\n\nimport (\n\t\"gno.land/p/sys/validators\"\n)\n\n// GetChanges returns the validator changes stored on the realm, since the given block number.\n// This function is intended to be called by gno.land through the GnoSDK\nfunc GetChanges(from int64) []validators.Validator {\n\tvalsetChanges := make([]validators.Validator, 0)\n\n\t// Gather the changes from the specified block\n\tchanges.Iterate(getBlockID(from), \"\", func(_ string, value interface{}) bool {\n\t\tchs := value.([]change)\n\n\t\tfor _, ch := range chs {\n\t\t\tvalsetChanges = append(valsetChanges, ch.validator)\n\t\t}\n\n\t\treturn false\n\t})\n\n\treturn valsetChanges\n}\n"},{"name":"init.gno","body":"package validators\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/nt/poa\"\n\t\"gno.land/p/sys/validators\"\n)\n\nfunc init() {\n\t// Prepare the initial validator set\n\tset := []validators.Validator{\n\t\t// core-val-1\n\t\t{\n\t\t\tAddress: std.Address(\"g1qn3jwvdpva622j3fyudqy65zstnqx2wnqhrs3s\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zpndqtjh5dcsnd0gcez3frs3w6rsttmlekj4cyywegyh0n8uprwvj5n8688\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// core-val-2\n\t\t{\n\t\t\tAddress: std.Address(\"g1gtu9czw9qavrtdnf936usvwjwyjz0x0jk243au\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zq4y0ppxhxazdwxhnsxxzdmh9rxht888n4fl0mskwcpq7y2404dm2h0lamk\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// core-val-3\n\t\t{\n\t\t\tAddress: std.Address(\"g19emxxnzzfa0pkffvthrss5drgccjnwj8mdme4f\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zq288fe7pd2yy3h2h8qjh0elu3pxuamf3wpa9qt9s6jja0r3k64ue4mh636\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// core-val-4\n\t\t{\n\t\t\tAddress: std.Address(\"g1hyxtsgjr5zt06jcx4z0xenn3u442ad2xgzu7lp\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zpy4mst534500z7k6xk5u7c9ex8zs44rjjhmxaxtw9zzjv82qkfhkhx2rfs\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// core-val-5\n\t\t{\n\t\t\tAddress: std.Address(\"g1l072ma0vfhx7s4vpevfvuxd6wzkv5ztt7gh99w\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zqtvz3g6nvu3d6wdz97w7jdw2sjc65us5u8gj8pm4mhasw7zxakjhjn9qkm\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// core-val-6\n\t\t{\n\t\t\tAddress: std.Address(\"g1uwqd3284kuzm56auwyc9d87jf3953tp9pnt506\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zp8xm09ura7mwyntee78cl64hgzq0x75f05tv7fkxpqvc797j37hsr3vgjg\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// berty-val-1\n\t\t{\n\t\t\tAddress: std.Address(\"g1ut590acnamvhkrh4qz6dz9zt9e3hyu499u0gvl\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zq2gncppkfzmx7s22mn60mf0uxzzpl23yx97hwmwm8yc6lupepqqnlexfch\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// onbloc-val-1\n\t\t{\n\t\t\tAddress: std.Address(\"g1arkzjfrte9l97v9q2qye07v0lw07039gaa3hfy\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zpkvdy7n9744qay76fzekpu9l6g3mp4hzhqjmp6k2as72ghlzc546ju3a09\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// onbloc-val-2\n\t\t{\n\t\t\tAddress: std.Address(\"g1x0m33nyne064xdx7tvlfcjwd4xkajjar6h523z\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zp6s70v4wurhg699w6f9emkwxdlm2eyf2uv64annj47npq85tjeucedmky9\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// devx-val-1\n\t\t{\n\t\t\tAddress: std.Address(\"g1mxguhd5zacar64txhfm0v7hhtph5wur5hx86vs\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zqz6fwulsygvu9xypka3zqxhkxllm467e3adphmj6y44vn3yy8qq34vxnse\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// devx-val-2\n\t\t{\n\t\t\tAddress: std.Address(\"g1t9ctfa468hn6czff8kazw08crazehcxaqa2uaa\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zpsq650w975vqsf6ajj5x4wdzfnrh64kmw7sljqz7wts6k0p6l36d0huls3\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// devx-val-3\n\t\t{\n\t\t\tAddress: std.Address(\"g1sll4rtvrepdyzcvg5ml0kjtl7fnwgcsxgg9s5q\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zplr4zg2smgha4n9huwcywm6pnkuny2x2j44kk4srxcf0rrmpql3035k8s2\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// devx-val-4\n\t\t{\n\t\t\tAddress: std.Address(\"g1aa5pp94eaextkump38766hpdra74xtfh805msv\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zqe85el3ardhel5vruywsdjw0vj2zjyhqhsyhcnuh0dy8xhuj8mxjg5h7uw\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// tori-val-1\n\t\t{\n\t\t\tAddress: std.Address(\"g1r2lwzu0y0na4686a0lz4f2zqxlffqkfm7lqqqp\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zq2quztlp2pffjsun3jeqyesru8rx9yc6tfj9na3hnw9qgn4zlrpul5mhd0\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// aib-val-1\n\t\t{\n\t\t\tAddress: std.Address(\"g1ecdu2gwz9d46srrhpu7k60pnrquvle5z2a5nn0\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zqnrer4hlsq7q22egx9ur357hg8ftsntyh4z2g7x69u2s4ay25vdw4tredd\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// aib-val-2\n\t\t{\n\t\t\tAddress: std.Address(\"g169wsuqlrscnvxtsu6wrc0zuwn39tmctw7q9f0q\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zpv6k4a2r6x6gt7eqp70l5vxluk9zkdmlqvkxztnc8zp2llq73e6ukxvsf6\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// aib-val-3\n\t\t{\n\t\t\tAddress: std.Address(\"g1hfwh3ufph3zczs5wu4qvpgtv79fzh30rgzdux8\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zqxx3qdzl9f6lee42vhtka5luujhxg22tesyww52af68f75zzp0snyhl8mw\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t}\n\n\t// The default valset protocol is PoA\n\tvp = poa.NewPoA(poa.WithInitialSet(set))\n\n\t// No changes to apply initially\n\tchanges = avl.NewTree()\n}\n"},{"name":"poc.gno","body":"package validators\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/sys/validators\"\n\t\"gno.land/r/gov/dao/bridge\"\n)\n\nconst errNoChangesProposed = \"no set changes proposed\"\n\n// NewPropExecutor creates a new executor that wraps a changes closure\n// proposal. This wrapper is required to ensure the GovDAO Realm actually\n// executed the callback.\n//\n// Concept adapted from:\n// https://github.com/gnolang/gno/pull/1945\nfunc NewPropExecutor(changesFn func() []validators.Validator) dao.Executor {\n\tif changesFn == nil {\n\t\tpanic(errNoChangesProposed)\n\t}\n\n\tcallback := func() error {\n\t\tfor _, change := range changesFn() {\n\t\t\tif change.VotingPower == 0 {\n\t\t\t\t// This change request is to remove the validator\n\t\t\t\tremoveValidator(change.Address)\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// This change request is to add the validator\n\t\t\taddValidator(change)\n\t\t}\n\n\t\treturn nil\n\t}\n\n\treturn bridge.GovDAO().NewGovDAOExecutor(callback)\n}\n\n// IsValidator returns a flag indicating if the given bech32 address\n// is part of the validator set\nfunc IsValidator(addr std.Address) bool {\n\treturn vp.IsValidator(addr)\n}\n\n// GetValidators returns the typed validator set\nfunc GetValidators() []validators.Validator {\n\treturn vp.GetValidators()\n}\n"},{"name":"validators.gno","body":"package validators\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/seqid\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/p/sys/validators\"\n)\n\nvar (\n\tvp validators.ValsetProtocol // p is the underlying validator set protocol\n\tchanges *avl.Tree // changes holds any valset changes; seqid(block number) -\u003e []change\n)\n\n// change represents a single valset change, tied to a specific block number\ntype change struct {\n\tblockNum int64 // the block number associated with the valset change\n\tvalidator validators.Validator // the validator update\n}\n\n// addValidator adds a new validator to the validator set.\n// If the validator is already present, the method errors out\nfunc addValidator(validator validators.Validator) {\n\tval, err := vp.AddValidator(validator.Address, validator.PubKey, validator.VotingPower)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Validator added, note the change\n\tch := change{\n\t\tblockNum: std.GetHeight(),\n\t\tvalidator: val,\n\t}\n\n\tsaveChange(ch)\n\n\t// Emit the validator set change\n\tstd.Emit(validators.ValidatorAddedEvent)\n}\n\n// removeValidator removes the given validator from the set.\n// If the validator is not present in the set, the method errors out\nfunc removeValidator(address std.Address) {\n\tval, err := vp.RemoveValidator(address)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Validator removed, note the change\n\tch := change{\n\t\tblockNum: std.GetHeight(),\n\t\tvalidator: validators.Validator{\n\t\t\tAddress: val.Address,\n\t\t\tPubKey: val.PubKey,\n\t\t\tVotingPower: 0, // nullified the voting power indicates removal\n\t\t},\n\t}\n\n\tsaveChange(ch)\n\n\t// Emit the validator set change\n\tstd.Emit(validators.ValidatorRemovedEvent)\n}\n\n// saveChange saves the valset change\nfunc saveChange(ch change) {\n\tid := getBlockID(ch.blockNum)\n\n\tsetRaw, exists := changes.Get(id)\n\tif !exists {\n\t\tchanges.Set(id, []change{ch})\n\n\t\treturn\n\t}\n\n\t// Save the change\n\tset := setRaw.([]change)\n\tset = append(set, ch)\n\n\tchanges.Set(id, set)\n}\n\n// getBlockID converts the block number to a sequential ID\nfunc getBlockID(blockNum int64) string {\n\treturn seqid.ID(uint64(blockNum)).String()\n}\n\nfunc Render(_ string) string {\n\tvar (\n\t\tsize = changes.Size()\n\t\tmaxDisplay = 10\n\t)\n\n\tif size == 0 {\n\t\treturn \"No valset changes to apply.\"\n\t}\n\n\toutput := \"Valset changes:\\n\"\n\tchanges.ReverseIterateByOffset(size-maxDisplay, maxDisplay, func(_ string, value interface{}) bool {\n\t\tchs := value.([]change)\n\n\t\tfor _, ch := range chs {\n\t\t\toutput += ufmt.Sprintf(\n\t\t\t\t\"- #%d: %s (%d)\\n\",\n\t\t\t\tch.blockNum,\n\t\t\t\tch.validator.Address.String(),\n\t\t\t\tch.validator.VotingPower,\n\t\t\t)\n\t\t}\n\n\t\treturn false\n\t})\n\n\treturn output\n}\n"},{"name":"validators_test.gno","body":"package validators\n\nimport (\n\t\"testing\"\n\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\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/sys/validators\"\n)\n\n// generateTestValidators generates a dummy validator set\nfunc generateTestValidators(count int) []validators.Validator {\n\tvals := make([]validators.Validator, 0, count)\n\n\tfor i := 0; i \u003c count; i++ {\n\t\tval := validators.Validator{\n\t\t\tAddress: testutils.TestAddress(ufmt.Sprintf(\"%d\", i)),\n\t\t\tPubKey: \"public-key\",\n\t\t\tVotingPower: 10,\n\t\t}\n\n\t\tvals = append(vals, val)\n\t}\n\n\treturn vals\n}\n\nfunc TestValidators_AddRemove(t *testing.T) {\n\t// Clear any changes\n\tchanges = avl.NewTree()\n\n\tvar (\n\t\tvals = generateTestValidators(100)\n\t\tinitialHeight = int64(123)\n\t)\n\n\t// Add in the validators\n\tfor _, val := range vals {\n\t\taddValidator(val)\n\n\t\t// Make sure the validator is added\n\t\tuassert.True(t, vp.IsValidator(val.Address))\n\n\t\tstd.TestSkipHeights(1)\n\t}\n\n\tfor i := initialHeight; i \u003c initialHeight+int64(len(vals)); i++ {\n\t\t// Make sure the changes are saved\n\t\tchs := GetChanges(i)\n\n\t\t// We use the funky index calculation to make sure\n\t\t// changes are properly handled for each block span\n\t\tuassert.Equal(t, initialHeight+int64(len(vals))-i, int64(len(chs)))\n\n\t\tfor index, val := range vals[i-initialHeight:] {\n\t\t\t// Make sure the changes are equal to the additions\n\t\t\tch := chs[index]\n\n\t\t\tuassert.Equal(t, val.Address, ch.Address)\n\t\t\tuassert.Equal(t, val.PubKey, ch.PubKey)\n\t\t\tuassert.Equal(t, val.VotingPower, ch.VotingPower)\n\t\t}\n\t}\n\n\t// Save the beginning height for the removal\n\tinitialRemoveHeight := std.GetHeight()\n\n\t// Clear any changes\n\tchanges = avl.NewTree()\n\n\t// Remove the validators\n\tfor _, val := range vals {\n\t\tremoveValidator(val.Address)\n\n\t\t// Make sure the validator is removed\n\t\tuassert.False(t, vp.IsValidator(val.Address))\n\n\t\tstd.TestSkipHeights(1)\n\t}\n\n\tfor i := initialRemoveHeight; i \u003c initialRemoveHeight+int64(len(vals)); i++ {\n\t\t// Make sure the changes are saved\n\t\tchs := GetChanges(i)\n\n\t\t// We use the funky index calculation to make sure\n\t\t// changes are properly handled for each block span\n\t\tuassert.Equal(t, initialRemoveHeight+int64(len(vals))-i, int64(len(chs)))\n\n\t\tfor index, val := range vals[i-initialRemoveHeight:] {\n\t\t\t// Make sure the changes are equal to the additions\n\t\t\tch := chs[index]\n\n\t\t\tuassert.Equal(t, val.Address, ch.Address)\n\t\t\tuassert.Equal(t, val.PubKey, ch.PubKey)\n\t\t\tuassert.Equal(t, uint64(0), ch.VotingPower)\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":"valopers","path":"gno.land/r/gnoland/valopers/v2","files":[{"name":"init.gno","body":"package valopers\n\nimport \"gno.land/p/demo/avl\"\n\nfunc init() {\n\tvalopers = avl.NewTree()\n}\n"},{"name":"valopers.gno","body":"// Package valopers is designed around the permissionless lifecycle of valoper profiles.\n// It also includes parts designed for govdao to propose valset changes based on registered valopers.\npackage valopers\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/ufmt\"\n\tpVals \"gno.land/p/sys/validators\"\n\t\"gno.land/r/gov/dao/bridge\"\n\tvalidators \"gno.land/r/sys/validators/v2\"\n)\n\nconst (\n\terrValoperExists = \"valoper already exists\"\n\terrValoperMissing = \"valoper does not exist\"\n\terrInvalidAddressUpdate = \"valoper updated address exists\"\n\terrValoperNotCaller = \"valoper is not the caller\"\n)\n\n// valopers keeps track of all the active validator operators\nvar valopers *avl.Tree // Address -\u003e Valoper\n\n// Valoper represents a validator operator profile\ntype Valoper struct {\n\tName string // the display name of the valoper\n\tMoniker string // the moniker of the valoper\n\tDescription string // the description of the valoper\n\n\tAddress std.Address // The bech32 gno address of the validator\n\tPubKey string // the bech32 public key of the validator\n\tP2PAddresses []string // the publicly reachable P2P addresses of the validator\n\tActive bool // flag indicating if the valoper is active\n}\n\n// Register registers a new valoper\nfunc Register(v Valoper) {\n\t// Check if the valoper is already registered\n\tif isValoper(v.Address) {\n\t\tpanic(errValoperExists)\n\t}\n\n\t// TODO add address derivation from public key\n\t// (when the laws of gno make it possible)\n\n\t// Save the valoper to the set\n\tvalopers.Set(v.Address.String(), v)\n}\n\n// Update updates an existing valoper\nfunc Update(address std.Address, v Valoper) {\n\t// Check if the valoper is present\n\tif !isValoper(address) {\n\t\tpanic(errValoperMissing)\n\t}\n\n\t// Check that the valoper wouldn't be\n\t// overwriting an existing one\n\tisAddressUpdate := address != v.Address\n\tif isAddressUpdate \u0026\u0026 isValoper(v.Address) {\n\t\tpanic(errInvalidAddressUpdate)\n\t}\n\n\t// Remove the old valoper info\n\t// in case the address changed\n\tif address != v.Address {\n\t\tvalopers.Remove(address.String())\n\t}\n\n\t// Save the new valoper info\n\tvalopers.Set(v.Address.String(), v)\n}\n\n// GetByAddr fetches the valoper using the address, if present\nfunc GetByAddr(address std.Address) Valoper {\n\tvaloperRaw, exists := valopers.Get(address.String())\n\tif !exists {\n\t\tpanic(errValoperMissing)\n\t}\n\n\treturn valoperRaw.(Valoper)\n}\n\n// Render renders the current valoper set\nfunc Render(_ string) string {\n\tif valopers.Size() == 0 {\n\t\treturn \"No valopers to display.\"\n\t}\n\n\toutput := \"Valset changes to apply:\\n\"\n\tvalopers.Iterate(\"\", \"\", func(_ string, value interface{}) bool {\n\t\tvaloper := value.(Valoper)\n\n\t\toutput += valoper.Render()\n\n\t\treturn false\n\t})\n\n\treturn output\n}\n\n// Render renders a single valoper with their information\nfunc (v Valoper) Render() string {\n\toutput := ufmt.Sprintf(\"## %s (%s)\\n\", v.Name, v.Moniker)\n\toutput += ufmt.Sprintf(\"%s\\n\\n\", v.Description)\n\toutput += ufmt.Sprintf(\"- Address: %s\\n\", v.Address.String())\n\toutput += ufmt.Sprintf(\"- PubKey: %s\\n\", v.PubKey)\n\toutput += \"- P2P Addresses: [\\n\"\n\n\tif len(v.P2PAddresses) == 0 {\n\t\toutput += \"]\\n\"\n\n\t\treturn output\n\t}\n\n\tfor index, addr := range v.P2PAddresses {\n\t\toutput += addr\n\n\t\tif index == len(v.P2PAddresses)-1 {\n\t\t\toutput += \"]\\n\"\n\n\t\t\tcontinue\n\t\t}\n\n\t\toutput += \",\\n\"\n\t}\n\n\treturn output\n}\n\n// isValoper checks if the valoper exists\nfunc isValoper(address std.Address) bool {\n\t_, exists := valopers.Get(address.String())\n\n\treturn exists\n}\n\n// GovDAOProposal creates a proposal to the GovDAO\n// for adding the given valoper to the validator set.\n// This function is meant to serve as a helper\n// for generating the govdao proposal\nfunc GovDAOProposal(address std.Address) {\n\tvar (\n\t\tvaloper = GetByAddr(address)\n\t\tvotingPower = uint64(1)\n\t)\n\n\t// Make sure the valoper is the caller\n\tif std.OriginCaller() != address {\n\t\tpanic(errValoperNotCaller)\n\t}\n\n\t// Determine the voting power\n\tif !valoper.Active {\n\t\tvotingPower = uint64(0)\n\t}\n\n\tchangesFn := func() []pVals.Validator {\n\t\treturn []pVals.Validator{\n\t\t\t{\n\t\t\t\tAddress: valoper.Address,\n\t\t\t\tPubKey: valoper.PubKey,\n\t\t\t\tVotingPower: votingPower,\n\t\t\t},\n\t\t}\n\t}\n\n\t// Create the executor\n\texecutor := validators.NewPropExecutor(changesFn)\n\n\t// Craft the proposal description\n\tdescription := ufmt.Sprintf(\n\t\t\"Add valoper %s (Address: %s; PubKey: %s) to the valset\",\n\t\tvaloper.Name,\n\t\tvaloper.Address.String(),\n\t\tvaloper.PubKey,\n\t)\n\n\tprop := dao.ProposalRequest{\n\t\tDescription: description,\n\t\tExecutor: executor,\n\t}\n\n\t// Create the govdao proposal\n\tbridge.GovDAO().Propose(prop)\n}\n"},{"name":"valopers_test.gno","body":"package valopers\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestValopers_Register(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"already a valoper\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Clear the set for the test\n\t\tvalopers = avl.NewTree()\n\n\t\tv := Valoper{\n\t\t\tAddress: testutils.TestAddress(\"valoper\"),\n\t\t}\n\n\t\t// Add the valoper\n\t\tvalopers.Set(v.Address.String(), v)\n\n\t\tuassert.PanicsWithMessage(t, errValoperExists, func() {\n\t\t\tRegister(v)\n\t\t})\n\t})\n\n\tt.Run(\"successful registration\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Clear the set for the test\n\t\tvalopers = avl.NewTree()\n\n\t\tv := Valoper{\n\t\t\tAddress: testutils.TestAddress(\"valoper\"),\n\t\t\tName: \"new valoper\",\n\t\t\tMoniker: \"val-1\",\n\t\t\tPubKey: \"pub key\",\n\t\t}\n\n\t\tuassert.NotPanics(t, func() {\n\t\t\tRegister(v)\n\t\t})\n\n\t\tuassert.NotPanics(t, func() {\n\t\t\tvaloper := GetByAddr(v.Address)\n\n\t\t\tuassert.Equal(t, v.Address, valoper.Address)\n\t\t\tuassert.Equal(t, v.Name, valoper.Name)\n\t\t\tuassert.Equal(t, v.Moniker, valoper.Moniker)\n\t\t\tuassert.Equal(t, v.PubKey, valoper.PubKey)\n\t\t})\n\t})\n}\n\nfunc TestValopers_Update(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"non-existing valoper\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Clear the set for the test\n\t\tvalopers = avl.NewTree()\n\n\t\tv := Valoper{}\n\n\t\t// Update the valoper\n\t\tuassert.PanicsWithMessage(t, errValoperMissing, func() {\n\t\t\tUpdate(v.Address, v)\n\t\t})\n\t})\n\n\tt.Run(\"overwrite valoper\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Clear the set for the test\n\t\tvalopers = avl.NewTree()\n\n\t\tone := Valoper{\n\t\t\tAddress: testutils.TestAddress(\"valoper 1\"),\n\t\t}\n\n\t\t// Add the valoper\n\t\tuassert.NotPanics(t, func() {\n\t\t\tRegister(one)\n\t\t})\n\n\t\tinitialAddress := testutils.TestAddress(\"valoper 2\")\n\t\ttwo := Valoper{\n\t\t\tAddress: initialAddress,\n\t\t}\n\n\t\t// Add the valoper\n\t\tuassert.NotPanics(t, func() {\n\t\t\tRegister(two)\n\t\t})\n\n\t\t// Update the valoper address\n\t\t// so it overlaps\n\t\ttwo = Valoper{\n\t\t\tAddress: one.Address,\n\t\t}\n\n\t\t// Update the valoper\n\t\tuassert.PanicsWithMessage(t, errInvalidAddressUpdate, func() {\n\t\t\tUpdate(initialAddress, two)\n\t\t})\n\t})\n\n\tt.Run(\"successful update\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Clear the set for the test\n\t\tvalopers = avl.NewTree()\n\n\t\tvar (\n\t\t\tname = \"new valoper\"\n\t\t\tv = Valoper{\n\t\t\t\tAddress: testutils.TestAddress(\"valoper\"),\n\t\t\t\tName: name,\n\t\t\t\tPubKey: \"pub key\",\n\t\t\t}\n\t\t)\n\n\t\t// Add the valoper\n\t\tuassert.NotPanics(t, func() {\n\t\t\tRegister(v)\n\t\t})\n\n\t\t// Update the valoper name\n\t\tv.Name = \"new name\"\n\t\tv.Active = false\n\n\t\t// Update the valoper\n\t\tuassert.NotPanics(t, func() {\n\t\t\tUpdate(v.Address, v)\n\t\t})\n\n\t\t// Make sure the valoper is updated\n\t\tuassert.NotPanics(t, func() {\n\t\t\tvaloper := GetByAddr(v.Address)\n\n\t\t\tuassert.Equal(t, v.Name, valoper.Name)\n\t\t\tuassert.Equal(t, v.Active, valoper.Active)\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":"config","path":"gno.land/r/leon/config","files":[{"name":"config.gno","body":"package config\n\nimport (\n\t\"errors\"\n\t\"std\"\n)\n\nvar (\n\tmain std.Address // leon's main address\n\tbackup std.Address // backup address\n\n\tErrInvalidAddr = errors.New(\"leon's config: invalid address\")\n\tErrUnauthorized = errors.New(\"leon's config: unauthorized\")\n)\n\nfunc init() {\n\tmain = \"g125em6arxsnj49vx35f0n0z34putv5ty3376fg5\"\n}\n\nfunc Address() std.Address {\n\treturn main\n}\n\nfunc Backup() std.Address {\n\treturn backup\n}\n\nfunc SetAddress(a std.Address) error {\n\tif !a.IsValid() {\n\t\treturn ErrInvalidAddr\n\t}\n\n\tif err := checkAuthorized(); err != nil {\n\t\treturn err\n\t}\n\n\tmain = a\n\treturn nil\n}\n\nfunc SetBackup(a std.Address) error {\n\tif !a.IsValid() {\n\t\treturn ErrInvalidAddr\n\t}\n\n\tif err := checkAuthorized(); err != nil {\n\t\treturn err\n\t}\n\n\tbackup = a\n\treturn nil\n}\n\nfunc checkAuthorized() error {\n\tcaller := std.PrevRealm().Addr()\n\tisAuthorized := caller == main || caller == backup\n\n\tif !isAuthorized {\n\t\treturn ErrUnauthorized\n\t}\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_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"home","path":"gno.land/r/leon/home","files":[{"name":"home.gno","body":"package home\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/ufmt\"\n\n\t\"gno.land/r/demo/art/gnoface\"\n\t\"gno.land/r/demo/art/millipede\"\n\t\"gno.land/r/leon/config\"\n)\n\nvar (\n\tpfp string // link to profile picture\n\tpfpCaption string // profile picture caption\n\tabtMe [2]string\n)\n\nfunc init() {\n\tpfp = \"https://i.imgflip.com/91vskx.jpg\"\n\tpfpCaption = \"[My favourite painting \u0026 pfp](https://en.wikipedia.org/wiki/Wanderer_above_the_Sea_of_Fog)\"\n\tabtMe = [2]string{\n\t\t`### About me\nHi, I'm Leon, a DevRel Engineer at gno.land. I am a tech enthusiast, \nlife-long learner, and sharer of knowledge.`,\n\t\t`### Contributions\nMy contributions to gno.land can mainly be found \n[here](https://github.com/gnolang/gno/issues?q=sort:updated-desc+author:leohhhn).\n\nTODO import r/gh\n`,\n\t}\n}\n\nfunc UpdatePFP(url, caption string) {\n\tif !isAuthorized(std.PrevRealm().Addr()) {\n\t\tpanic(config.ErrUnauthorized)\n\t}\n\n\tpfp = url\n\tpfpCaption = caption\n}\n\nfunc UpdateAboutMe(col1, col2 string) {\n\tif !isAuthorized(std.PrevRealm().Addr()) {\n\t\tpanic(config.ErrUnauthorized)\n\t}\n\n\tabtMe[0] = col1\n\tabtMe[1] = col2\n}\n\nfunc Render(path string) string {\n\tout := \"# Leon's Homepage\\n\\n\"\n\n\tout += renderAboutMe()\n\tout += renderBlogPosts()\n\tout += \"\\n\\n\"\n\tout += renderArt()\n\n\treturn out\n}\n\nfunc renderBlogPosts() string {\n\tout := \"\"\n\t//out += \"## Leon's Blog Posts\"\n\n\t// todo fetch blog posts authored by @leohhhn\n\t// and render them\n\treturn out\n}\n\nfunc renderAboutMe() string {\n\tout := \"\u003cdiv class='columns-3'\u003e\"\n\n\tout += \"\u003cdiv\u003e\\n\\n\"\n\tout += ufmt.Sprintf(\"![my profile pic](%s)\\n\\n%s\\n\\n\", pfp, pfpCaption)\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\tout += \"\u003cdiv\u003e\\n\\n\"\n\tout += abtMe[0] + \"\\n\\n\"\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\tout += \"\u003cdiv\u003e\\n\\n\"\n\tout += abtMe[1] + \"\\n\\n\"\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\tout += \"\u003c/div\u003e\u003c!-- /columns-3 --\u003e\\n\\n\"\n\n\treturn out\n}\n\nfunc renderArt() string {\n\tout := `\u003cdiv class=\"jumbotron\"\u003e` + \"\\n\\n\"\n\tout += \"# Gno Art\\n\\n\"\n\n\tout += \"\u003cdiv class='columns-3'\u003e\"\n\n\tout += renderGnoFace()\n\tout += renderMillipede()\n\tout += \"Empty spot :/\"\n\n\tout += \"\u003c/div\u003e\u003c!-- /columns-3 --\u003e\\n\\n\"\n\n\tout += \"This art is dynamic; it will change with every new block.\\n\\n\"\n\tout += `\u003c/div\u003e\u003c!-- /jumbotron --\u003e` + \"\\n\"\n\n\treturn out\n}\n\nfunc renderGnoFace() string {\n\tout := \"\u003cdiv\u003e\\n\\n\"\n\tout += gnoface.Render(strconv.Itoa(int(std.GetHeight())))\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\treturn out\n}\n\nfunc renderMillipede() string {\n\tout := \"\u003cdiv\u003e\\n\\n\"\n\tout += \"Millipede\\n\\n\"\n\tout += \"```\\n\" + millipede.Draw(int(std.GetHeight())%10+1) + \"```\\n\"\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\treturn out\n}\n\nfunc isAuthorized(addr std.Address) bool {\n\treturn addr == config.Address() || addr == config.Backup()\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":"config","path":"gno.land/r/leon/config","files":[{"name":"config.gno","body":"package config\n\nimport (\n\t\"errors\"\n\t\"std\"\n)\n\nvar (\n\tmain std.Address // leon's main address\n\tbackup std.Address // backup address\n\n\tErrInvalidAddr = errors.New(\"leon's config: invalid address\")\n\tErrUnauthorized = errors.New(\"leon's config: unauthorized\")\n)\n\nfunc init() {\n\tmain = \"g125em6arxsnj49vx35f0n0z34putv5ty3376fg5\"\n}\n\nfunc Address() std.Address {\n\treturn main\n}\n\nfunc Backup() std.Address {\n\treturn backup\n}\n\nfunc SetAddress(a std.Address) error {\n\tif !a.IsValid() {\n\t\treturn ErrInvalidAddr\n\t}\n\n\tif err := checkAuthorized(); err != nil {\n\t\treturn err\n\t}\n\n\tmain = a\n\treturn nil\n}\n\nfunc SetBackup(a std.Address) error {\n\tif !a.IsValid() {\n\t\treturn ErrInvalidAddr\n\t}\n\n\tif err := checkAuthorized(); err != nil {\n\t\treturn err\n\t}\n\n\tbackup = a\n\treturn nil\n}\n\nfunc checkAuthorized() error {\n\tcaller := std.PreviousRealm().Addr()\n\tisAuthorized := caller == main || caller == backup\n\n\tif !isAuthorized {\n\t\treturn ErrUnauthorized\n\t}\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_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"home","path":"gno.land/r/leon/home","files":[{"name":"home.gno","body":"package home\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/ufmt\"\n\n\t\"gno.land/r/demo/art/gnoface\"\n\t\"gno.land/r/demo/art/millipede\"\n\t\"gno.land/r/leon/config\"\n)\n\nvar (\n\tpfp string // link to profile picture\n\tpfpCaption string // profile picture caption\n\tabtMe [2]string\n)\n\nfunc init() {\n\tpfp = \"https://i.imgflip.com/91vskx.jpg\"\n\tpfpCaption = \"[My favourite painting \u0026 pfp](https://en.wikipedia.org/wiki/Wanderer_above_the_Sea_of_Fog)\"\n\tabtMe = [2]string{\n\t\t`### About me\nHi, I'm Leon, a DevRel Engineer at gno.land. I am a tech enthusiast, \nlife-long learner, and sharer of knowledge.`,\n\t\t`### Contributions\nMy contributions to gno.land can mainly be found \n[here](https://github.com/gnolang/gno/issues?q=sort:updated-desc+author:leohhhn).\n\nTODO import r/gh\n`,\n\t}\n}\n\nfunc UpdatePFP(url, caption string) {\n\tif !isAuthorized(std.PreviousRealm().Addr()) {\n\t\tpanic(config.ErrUnauthorized)\n\t}\n\n\tpfp = url\n\tpfpCaption = caption\n}\n\nfunc UpdateAboutMe(col1, col2 string) {\n\tif !isAuthorized(std.PreviousRealm().Addr()) {\n\t\tpanic(config.ErrUnauthorized)\n\t}\n\n\tabtMe[0] = col1\n\tabtMe[1] = col2\n}\n\nfunc Render(path string) string {\n\tout := \"# Leon's Homepage\\n\\n\"\n\n\tout += renderAboutMe()\n\tout += renderBlogPosts()\n\tout += \"\\n\\n\"\n\tout += renderArt()\n\n\treturn out\n}\n\nfunc renderBlogPosts() string {\n\tout := \"\"\n\t//out += \"## Leon's Blog Posts\"\n\n\t// todo fetch blog posts authored by @leohhhn\n\t// and render them\n\treturn out\n}\n\nfunc renderAboutMe() string {\n\tout := \"\u003cdiv class='columns-3'\u003e\"\n\n\tout += \"\u003cdiv\u003e\\n\\n\"\n\tout += ufmt.Sprintf(\"![my profile pic](%s)\\n\\n%s\\n\\n\", pfp, pfpCaption)\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\tout += \"\u003cdiv\u003e\\n\\n\"\n\tout += abtMe[0] + \"\\n\\n\"\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\tout += \"\u003cdiv\u003e\\n\\n\"\n\tout += abtMe[1] + \"\\n\\n\"\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\tout += \"\u003c/div\u003e\u003c!-- /columns-3 --\u003e\\n\\n\"\n\n\treturn out\n}\n\nfunc renderArt() string {\n\tout := `\u003cdiv class=\"jumbotron\"\u003e` + \"\\n\\n\"\n\tout += \"# Gno Art\\n\\n\"\n\n\tout += \"\u003cdiv class='columns-3'\u003e\"\n\n\tout += renderGnoFace()\n\tout += renderMillipede()\n\tout += \"Empty spot :/\"\n\n\tout += \"\u003c/div\u003e\u003c!-- /columns-3 --\u003e\\n\\n\"\n\n\tout += \"This art is dynamic; it will change with every new block.\\n\\n\"\n\tout += `\u003c/div\u003e\u003c!-- /jumbotron --\u003e` + \"\\n\"\n\n\treturn out\n}\n\nfunc renderGnoFace() string {\n\tout := \"\u003cdiv\u003e\\n\\n\"\n\tout += gnoface.Render(strconv.Itoa(int(std.GetHeight())))\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\treturn out\n}\n\nfunc renderMillipede() string {\n\tout := \"\u003cdiv\u003e\\n\\n\"\n\tout += \"Millipede\\n\\n\"\n\tout += \"```\\n\" + millipede.Draw(int(std.GetHeight())%10+1) + \"```\\n\"\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\treturn out\n}\n\nfunc isAuthorized(addr std.Address) bool {\n\treturn addr == config.Address() || addr == config.Backup()\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":"config","path":"gno.land/r/manfred/config","files":[{"name":"config.gno","body":"package config\n\nimport \"std\"\n\nvar addr = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc Addr() std.Address {\n\treturn addr\n}\n\nfunc UpdateAddr(newAddr std.Address) {\n\tAssertIsAdmin()\n\taddr = newAddr\n}\n\nfunc AssertIsAdmin() {\n\tif std.OriginCaller() != addr {\n\t\tpanic(\"restricted area\")\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":"home","path":"gno.land/r/manfred/home","files":[{"name":"home.gno","body":"package home\n\nimport \"gno.land/r/manfred/config\"\n\nvar (\n\ttodos []string\n\tstatus string\n\tmemeImgURL string\n)\n\nfunc init() {\n\ttodos = append(todos, \"fill this todo list...\")\n\tstatus = \"Online\" // Initial status set to \"Online\"\n\tmemeImgURL = \"https://i.imgflip.com/7ze8dc.jpg\"\n}\n\nfunc Render(path string) string {\n\tcontent := \"# Manfred's (gn)home Dashboard\\n\\n\"\n\n\tcontent += \"## Meme\\n\"\n\tcontent += \"![](\" + memeImgURL + \")\\n\\n\"\n\n\tcontent += \"## Status\\n\"\n\tcontent += status + \"\\n\\n\"\n\n\tcontent += \"## Personal ToDo List\\n\"\n\tfor _, todo := range todos {\n\t\tcontent += \"- [ ] \" + todo + \"\\n\"\n\t}\n\tcontent += \"\\n\"\n\n\t// TODO: Implement a feature to list replies on r/boards on my posts\n\t// TODO: Maybe integrate a calendar feature for upcoming events?\n\n\treturn content\n}\n\nfunc AddNewTodo(todo string) {\n\tconfig.AssertIsAdmin()\n\ttodos = append(todos, todo)\n}\n\nfunc DeleteTodo(todoIndex int) {\n\tconfig.AssertIsAdmin()\n\tif todoIndex \u003e= 0 \u0026\u0026 todoIndex \u003c len(todos) {\n\t\t// Remove the todo from the list by merging slices from before and after the todo\n\t\ttodos = append(todos[:todoIndex], todos[todoIndex+1:]...)\n\t} else {\n\t\tpanic(\"Invalid todo index\")\n\t}\n}\n\nfunc UpdateStatus(newStatus string) {\n\tconfig.AssertIsAdmin()\n\tstatus = newStatus\n}\n"},{"name":"z1_filetest.gno","body":"package main\n\nimport \"gno.land/r/manfred/home\"\n\nfunc main() {\n\tprintln(home.Render(\"\"))\n}\n\n// Output:\n// # Manfred's (gn)home Dashboard\n//\n// ## Meme\n// ![](https://i.imgflip.com/7ze8dc.jpg)\n//\n// ## Status\n// Online\n//\n// ## Personal ToDo List\n// - [ ] fill this todo list...\n"},{"name":"z2_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/manfred/home\"\n)\n\nfunc main() {\n\tstd.TestSetOriginCaller(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\thome.AddNewTodo(\"aaa\")\n\thome.AddNewTodo(\"bbb\")\n\thome.AddNewTodo(\"ccc\")\n\thome.AddNewTodo(\"ddd\")\n\thome.AddNewTodo(\"eee\")\n\thome.UpdateStatus(\"Lorem Ipsum\")\n\thome.DeleteTodo(3)\n\tprintln(home.Render(\"\"))\n}\n\n// Output:\n// # Manfred's (gn)home Dashboard\n//\n// ## Meme\n// ![](https://i.imgflip.com/7ze8dc.jpg)\n//\n// ## Status\n// Lorem Ipsum\n//\n// ## Personal ToDo List\n// - [ ] fill this todo list...\n// - [ ] aaa\n// - [ ] bbb\n// - [ ] ddd\n// - [ ] eee\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":"present","path":"gno.land/r/manfred/present","files":[{"name":"admin.gno","body":"package present\n\nimport (\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\nvar (\n\tadminAddr std.Address\n\tmoderatorList avl.Tree\n\tinPause bool\n)\n\nfunc init() {\n\t// adminAddr = std.OriginCaller() // FIXME: find a way to use this from the main's genesis.\n\tadminAddr = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"\n}\n\nfunc AdminSetAdminAddr(addr std.Address) {\n\tassertIsAdmin()\n\tadminAddr = addr\n}\n\nfunc AdminSetInPause(state bool) {\n\tassertIsAdmin()\n\tinPause = state\n}\n\nfunc AdminAddModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), true)\n}\n\nfunc AdminRemoveModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), false) // XXX: delete instead?\n}\n\nfunc ModAddPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\tcaller := std.OriginCaller()\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.NewPost(caller, slug, title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc ModEditPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.GetPost(slug).Update(title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc isAdmin(addr std.Address) bool {\n\treturn addr == adminAddr\n}\n\nfunc isModerator(addr std.Address) bool {\n\t_, found := moderatorList.Get(addr.String())\n\treturn found\n}\n\nfunc assertIsAdmin() {\n\tcaller := std.OriginCaller()\n\tif !isAdmin(caller) {\n\t\tpanic(\"access restricted.\")\n\t}\n}\n\nfunc assertIsModerator() {\n\tcaller := std.OriginCaller()\n\tif isAdmin(caller) || isModerator(caller) {\n\t\treturn\n\t}\n\tpanic(\"access restricted\")\n}\n\nfunc assertNotInPause() {\n\tif inPause {\n\t\tpanic(\"access restricted (pause)\")\n\t}\n}\n\nfunc checkErr(err error) {\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n"},{"name":"present_miami23.gno","body":"package present\n\nfunc init() {\n\tpath := \"miami23\"\n\ttitle := \"Portal Loop Demo (Miami 2023)\"\n\tbody := `\nRendered by Gno.\n\n[Source (WIP)](https://github.com/gnolang/gno/pull/1176)\n\n## Portal Loop\n\n- DONE: Dynamic homepage, key pages, aliases, and redirects.\n- TODO: Deploy with history, complete worxdao v0.\n- Will replace the static gno.land site.\n- Enhances local development.\n\n[GitHub Issue](https://github.com/gnolang/gno/issues/1108)\n\n## Roadmap\n\n- Crafting the roadmap this week, open to collaboration.\n- Combining onchain (portal loop) and offchain (GitHub).\n- Next week: Unveiling the official v0 roadmap.\n\n## Teams, DAOs, Projects\n\n- Developing worxDAO contracts for directories of projects and teams.\n- GitHub teams and projects align with this structure.\n- CODEOWNER file updates coming.\n- Initial teams announced next week.\n\n## Tech Team Retreat Plan\n\n- Continue Portal Loop.\n- Consider dApp development.\n- Explore new topics [here](https://github.com/orgs/gnolang/projects/15/).\n- Engage in workshops.\n- Connect and have fun with colleagues.\n`\n\t_ = b.NewPost(adminAddr, path, title, body, \"2023-10-15T13:17:24Z\", []string{\"moul\"}, []string{\"demo\", \"portal-loop\", \"miami\"})\n}\n"},{"name":"present_miami23_filetest.gno","body":"package main\n\nimport (\n\t\"gno.land/r/manfred/present\"\n)\n\nfunc main() {\n\tprintln(present.Render(\"\"))\n\tprintln(\"------------------------------------\")\n\tprintln(present.Render(\"p/miami23\"))\n}\n"},{"name":"presentations.gno","body":"package present\n\nimport (\n\t\"gno.land/p/demo/blog\"\n)\n\n// TODO: switch from p/blog to p/present\n\nvar b = \u0026blog.Blog{\n\tTitle: \"Manfred's Presentations\",\n\tPrefix: \"/r/manfred/present:\",\n\tNoBreadcrumb: true,\n}\n\nfunc Render(path string) string {\n\treturn b.Render(path)\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":"guestbook","path":"gno.land/r/morgan/guestbook","files":[{"name":"admin.gno","body":"package guestbook\n\nimport (\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/seqid\"\n)\n\nvar owner = ownable.New()\n\n// AdminDelete removes the guestbook message with the given ID.\n// The user will still be marked as having submitted a message, so they\n// won't be able to re-submit a new message.\nfunc AdminDelete(signatureID string) {\n\towner.AssertCallerIsOwner()\n\n\tid, err := seqid.FromString(signatureID)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tidb := id.Binary()\n\tif !guestbook.Has(idb) {\n\t\tpanic(\"signature does not exist\")\n\t}\n\tguestbook.Remove(idb)\n}\n"},{"name":"guestbook.gno","body":"// Realm guestbook contains an implementation of a simple guestbook.\n// Come and sign yourself up!\npackage guestbook\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\t\"unicode\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/seqid\"\n)\n\n// Signature is a single entry in the guestbook.\ntype Signature struct {\n\tMessage string\n\tAuthor std.Address\n\tTime time.Time\n}\n\nconst (\n\tmaxMessageLength = 140\n\tmaxPerPage = 25\n)\n\nvar (\n\tsignatureID seqid.ID\n\tguestbook avl.Tree // id -\u003e Signature\n\thasSigned avl.Tree // address -\u003e struct{}\n)\n\nfunc init() {\n\tSign(\"You reached the end of the guestbook!\")\n}\n\nconst (\n\terrNotAUser = \"this guestbook can only be signed by users\"\n\terrAlreadySigned = \"you already signed the guestbook!\"\n\terrInvalidCharacterInMessage = \"invalid character in message\"\n)\n\n// Sign signs the guestbook, with the specified message.\nfunc Sign(message string) {\n\tprev := std.PrevRealm()\n\tswitch {\n\tcase !prev.IsUser():\n\t\tpanic(errNotAUser)\n\tcase hasSigned.Has(prev.Addr().String()):\n\t\tpanic(errAlreadySigned)\n\t}\n\tmessage = validateMessage(message)\n\n\tguestbook.Set(signatureID.Next().Binary(), Signature{\n\t\tMessage: message,\n\t\tAuthor: prev.Addr(),\n\t\t// NOTE: time.Now() will yield the \"block time\", which is deterministic.\n\t\tTime: time.Now(),\n\t})\n\thasSigned.Set(prev.Addr().String(), struct{}{})\n}\n\nfunc validateMessage(msg string) string {\n\tif len(msg) \u003e maxMessageLength {\n\t\tpanic(\"Keep it brief! (max \" + strconv.Itoa(maxMessageLength) + \" bytes!)\")\n\t}\n\tout := \"\"\n\tfor _, ch := range msg {\n\t\tswitch {\n\t\tcase unicode.IsLetter(ch),\n\t\t\tunicode.IsNumber(ch),\n\t\t\tunicode.IsSpace(ch),\n\t\t\tunicode.IsPunct(ch):\n\t\t\tout += string(ch)\n\t\tdefault:\n\t\t\tpanic(errInvalidCharacterInMessage)\n\t\t}\n\t}\n\treturn out\n}\n\nfunc Render(maxID string) string {\n\tvar bld strings.Builder\n\n\tbld.WriteString(\"# Guestbook 📝\\n\\n[Come sign the guestbook!](./guestbook$help\u0026func=Sign)\\n\\n---\\n\\n\")\n\n\tvar maxIDBinary string\n\tif maxID != \"\" {\n\t\tmid, err := seqid.FromString(maxID)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\t// AVL iteration is exclusive, so we need to decrease the ID value to get the \"true\" maximum.\n\t\tmid--\n\t\tmaxIDBinary = mid.Binary()\n\t}\n\n\tvar lastID seqid.ID\n\tvar printed int\n\tguestbook.ReverseIterate(\"\", maxIDBinary, func(key string, val interface{}) bool {\n\t\tsig := val.(Signature)\n\t\tmessage := strings.ReplaceAll(sig.Message, \"\\n\", \"\\n\u003e \")\n\t\tbld.WriteString(\"\u003e \" + message + \"\\n\u003e\\n\")\n\t\tidValue, ok := seqid.FromBinary(key)\n\t\tif !ok {\n\t\t\tpanic(\"invalid seqid id\")\n\t\t}\n\n\t\tbld.WriteString(\"\u003e _Written by \" + sig.Author.String() + \" at \" + sig.Time.Format(time.DateTime) + \"_ (#\" + idValue.String() + \")\\n\\n---\\n\\n\")\n\t\tlastID = idValue\n\n\t\tprinted++\n\t\t// stop after exceeding limit\n\t\treturn printed \u003e= maxPerPage\n\t})\n\n\tif printed == 0 {\n\t\tbld.WriteString(\"No messages!\")\n\t} else if printed \u003e= maxPerPage {\n\t\tbld.WriteString(\"\u003cp style='text-align:right'\u003e\u003ca href='./guestbook:\" + lastID.String() + \"'\u003eNext page\u003c/a\u003e\u003c/p\u003e\")\n\t}\n\n\treturn bld.String()\n}\n"},{"name":"guestbook_test.gno","body":"package guestbook\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n)\n\nfunc TestSign(t *testing.T) {\n\tguestbook = avl.Tree{}\n\thasSigned = avl.Tree{}\n\n\tstd.TestSetRealm(std.NewUserRealm(\"g1user\"))\n\tSign(\"Hello!\")\n\n\tstd.TestSetRealm(std.NewUserRealm(\"g1user2\"))\n\tSign(\"Hello2!\")\n\n\tres := Render(\"\")\n\tt.Log(res)\n\tif !strings.Contains(res, \"\u003e Hello!\\n\u003e\\n\u003e _Written by g1user \") {\n\t\tt.Error(\"does not contain first user's message\")\n\t}\n\tif !strings.Contains(res, \"\u003e Hello2!\\n\u003e\\n\u003e _Written by g1user2 \") {\n\t\tt.Error(\"does not contain second user's message\")\n\t}\n\tif guestbook.Size() != 2 {\n\t\tt.Error(\"invalid guestbook size\")\n\t}\n}\n\nfunc TestSign_FromRealm(t *testing.T) {\n\tstd.TestSetRealm(std.NewCodeRealm(\"gno.land/r/demo/users\"))\n\n\tdefer func() {\n\t\trec := recover()\n\t\tif rec == nil {\n\t\t\tt.Fatal(\"expected panic\")\n\t\t}\n\t\trecString, ok := rec.(string)\n\t\tif !ok {\n\t\t\tt.Fatal(\"not a string\", rec)\n\t\t} else if recString != errNotAUser {\n\t\t\tt.Fatal(\"invalid error\", recString)\n\t\t}\n\t}()\n\tSign(\"Hey!\")\n}\n\nfunc TestSign_Double(t *testing.T) {\n\t// Should not allow signing twice.\n\tguestbook = avl.Tree{}\n\thasSigned = avl.Tree{}\n\n\tstd.TestSetRealm(std.NewUserRealm(\"g1user\"))\n\tSign(\"Hello!\")\n\n\tdefer func() {\n\t\trec := recover()\n\t\tif rec == nil {\n\t\t\tt.Fatal(\"expected panic\")\n\t\t}\n\t\trecString, ok := rec.(string)\n\t\tif !ok {\n\t\t\tt.Error(\"type assertion failed\", rec)\n\t\t} else if recString != errAlreadySigned {\n\t\t\tt.Error(\"invalid error message\", recString)\n\t\t}\n\t}()\n\n\tSign(\"Hello again!\")\n}\n\nfunc TestSign_InvalidMessage(t *testing.T) {\n\t// Should not allow control characters in message.\n\tguestbook = avl.Tree{}\n\thasSigned = avl.Tree{}\n\n\tstd.TestSetRealm(std.NewUserRealm(\"g1user\"))\n\n\tdefer func() {\n\t\trec := recover()\n\t\tif rec == nil {\n\t\t\tt.Fatal(\"expected panic\")\n\t\t}\n\t\trecString, ok := rec.(string)\n\t\tif !ok {\n\t\t\tt.Error(\"type assertion failed\", rec)\n\t\t} else if recString != errInvalidCharacterInMessage {\n\t\t\tt.Error(\"invalid error message\", recString)\n\t\t}\n\t}()\n\tSign(\"\\x00Hello!\")\n}\n\nfunc TestAdminDelete(t *testing.T) {\n\tconst (\n\t\tuserAddr std.Address = \"g1user\"\n\t\tadminAddr std.Address = \"g1admin\"\n\t)\n\n\tguestbook = avl.Tree{}\n\thasSigned = avl.Tree{}\n\towner = ownable.NewWithAddress(adminAddr)\n\tsignatureID = 0\n\n\tstd.TestSetRealm(std.NewUserRealm(userAddr))\n\n\tconst bad = \"Very Bad Message! Nyeh heh heh!\"\n\tSign(bad)\n\n\tif rnd := Render(\"\"); !strings.Contains(rnd, bad) {\n\t\tt.Fatal(\"render does not contain bad message\", rnd)\n\t}\n\n\tstd.TestSetRealm(std.NewUserRealm(adminAddr))\n\tAdminDelete(signatureID.String())\n\n\tif rnd := Render(\"\"); strings.Contains(rnd, bad) {\n\t\tt.Error(\"render contains bad message\", rnd)\n\t}\n\tif guestbook.Size() != 0 {\n\t\tt.Error(\"invalid guestbook size\")\n\t}\n\tif hasSigned.Size() != 1 {\n\t\tt.Error(\"invalid hasSigned size\")\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":"guestbook","path":"gno.land/r/morgan/guestbook","files":[{"name":"admin.gno","body":"package guestbook\n\nimport (\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/seqid\"\n)\n\nvar owner = ownable.New()\n\n// AdminDelete removes the guestbook message with the given ID.\n// The user will still be marked as having submitted a message, so they\n// won't be able to re-submit a new message.\nfunc AdminDelete(signatureID string) {\n\towner.AssertCallerIsOwner()\n\n\tid, err := seqid.FromString(signatureID)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tidb := id.Binary()\n\tif !guestbook.Has(idb) {\n\t\tpanic(\"signature does not exist\")\n\t}\n\tguestbook.Remove(idb)\n}\n"},{"name":"guestbook.gno","body":"// Realm guestbook contains an implementation of a simple guestbook.\n// Come and sign yourself up!\npackage guestbook\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\t\"unicode\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/seqid\"\n)\n\n// Signature is a single entry in the guestbook.\ntype Signature struct {\n\tMessage string\n\tAuthor std.Address\n\tTime time.Time\n}\n\nconst (\n\tmaxMessageLength = 140\n\tmaxPerPage = 25\n)\n\nvar (\n\tsignatureID seqid.ID\n\tguestbook avl.Tree // id -\u003e Signature\n\thasSigned avl.Tree // address -\u003e struct{}\n)\n\nfunc init() {\n\tSign(\"You reached the end of the guestbook!\")\n}\n\nconst (\n\terrNotAUser = \"this guestbook can only be signed by users\"\n\terrAlreadySigned = \"you already signed the guestbook!\"\n\terrInvalidCharacterInMessage = \"invalid character in message\"\n)\n\n// Sign signs the guestbook, with the specified message.\nfunc Sign(message string) {\n\tprev := std.PreviousRealm()\n\tswitch {\n\tcase !prev.IsUser():\n\t\tpanic(errNotAUser)\n\tcase hasSigned.Has(prev.Addr().String()):\n\t\tpanic(errAlreadySigned)\n\t}\n\tmessage = validateMessage(message)\n\n\tguestbook.Set(signatureID.Next().Binary(), Signature{\n\t\tMessage: message,\n\t\tAuthor: prev.Addr(),\n\t\t// NOTE: time.Now() will yield the \"block time\", which is deterministic.\n\t\tTime: time.Now(),\n\t})\n\thasSigned.Set(prev.Addr().String(), struct{}{})\n}\n\nfunc validateMessage(msg string) string {\n\tif len(msg) \u003e maxMessageLength {\n\t\tpanic(\"Keep it brief! (max \" + strconv.Itoa(maxMessageLength) + \" bytes!)\")\n\t}\n\tout := \"\"\n\tfor _, ch := range msg {\n\t\tswitch {\n\t\tcase unicode.IsLetter(ch),\n\t\t\tunicode.IsNumber(ch),\n\t\t\tunicode.IsSpace(ch),\n\t\t\tunicode.IsPunct(ch):\n\t\t\tout += string(ch)\n\t\tdefault:\n\t\t\tpanic(errInvalidCharacterInMessage)\n\t\t}\n\t}\n\treturn out\n}\n\nfunc Render(maxID string) string {\n\tvar bld strings.Builder\n\n\tbld.WriteString(\"# Guestbook 📝\\n\\n[Come sign the guestbook!](./guestbook$help\u0026func=Sign)\\n\\n---\\n\\n\")\n\n\tvar maxIDBinary string\n\tif maxID != \"\" {\n\t\tmid, err := seqid.FromString(maxID)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\t// AVL iteration is exclusive, so we need to decrease the ID value to get the \"true\" maximum.\n\t\tmid--\n\t\tmaxIDBinary = mid.Binary()\n\t}\n\n\tvar lastID seqid.ID\n\tvar printed int\n\tguestbook.ReverseIterate(\"\", maxIDBinary, func(key string, val interface{}) bool {\n\t\tsig := val.(Signature)\n\t\tmessage := strings.ReplaceAll(sig.Message, \"\\n\", \"\\n\u003e \")\n\t\tbld.WriteString(\"\u003e \" + message + \"\\n\u003e\\n\")\n\t\tidValue, ok := seqid.FromBinary(key)\n\t\tif !ok {\n\t\t\tpanic(\"invalid seqid id\")\n\t\t}\n\n\t\tbld.WriteString(\"\u003e _Written by \" + sig.Author.String() + \" at \" + sig.Time.Format(time.DateTime) + \"_ (#\" + idValue.String() + \")\\n\\n---\\n\\n\")\n\t\tlastID = idValue\n\n\t\tprinted++\n\t\t// stop after exceeding limit\n\t\treturn printed \u003e= maxPerPage\n\t})\n\n\tif printed == 0 {\n\t\tbld.WriteString(\"No messages!\")\n\t} else if printed \u003e= maxPerPage {\n\t\tbld.WriteString(\"\u003cp style='text-align:right'\u003e\u003ca href='./guestbook:\" + lastID.String() + \"'\u003eNext page\u003c/a\u003e\u003c/p\u003e\")\n\t}\n\n\treturn bld.String()\n}\n"},{"name":"guestbook_test.gno","body":"package guestbook\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n)\n\nfunc TestSign(t *testing.T) {\n\tguestbook = avl.Tree{}\n\thasSigned = avl.Tree{}\n\n\tstd.TestSetRealm(std.NewUserRealm(\"g1user\"))\n\tSign(\"Hello!\")\n\n\tstd.TestSetRealm(std.NewUserRealm(\"g1user2\"))\n\tSign(\"Hello2!\")\n\n\tres := Render(\"\")\n\tt.Log(res)\n\tif !strings.Contains(res, \"\u003e Hello!\\n\u003e\\n\u003e _Written by g1user \") {\n\t\tt.Error(\"does not contain first user's message\")\n\t}\n\tif !strings.Contains(res, \"\u003e Hello2!\\n\u003e\\n\u003e _Written by g1user2 \") {\n\t\tt.Error(\"does not contain second user's message\")\n\t}\n\tif guestbook.Size() != 2 {\n\t\tt.Error(\"invalid guestbook size\")\n\t}\n}\n\nfunc TestSign_FromRealm(t *testing.T) {\n\tstd.TestSetRealm(std.NewCodeRealm(\"gno.land/r/demo/users\"))\n\n\tdefer func() {\n\t\trec := recover()\n\t\tif rec == nil {\n\t\t\tt.Fatal(\"expected panic\")\n\t\t}\n\t\trecString, ok := rec.(string)\n\t\tif !ok {\n\t\t\tt.Fatal(\"not a string\", rec)\n\t\t} else if recString != errNotAUser {\n\t\t\tt.Fatal(\"invalid error\", recString)\n\t\t}\n\t}()\n\tSign(\"Hey!\")\n}\n\nfunc TestSign_Double(t *testing.T) {\n\t// Should not allow signing twice.\n\tguestbook = avl.Tree{}\n\thasSigned = avl.Tree{}\n\n\tstd.TestSetRealm(std.NewUserRealm(\"g1user\"))\n\tSign(\"Hello!\")\n\n\tdefer func() {\n\t\trec := recover()\n\t\tif rec == nil {\n\t\t\tt.Fatal(\"expected panic\")\n\t\t}\n\t\trecString, ok := rec.(string)\n\t\tif !ok {\n\t\t\tt.Error(\"type assertion failed\", rec)\n\t\t} else if recString != errAlreadySigned {\n\t\t\tt.Error(\"invalid error message\", recString)\n\t\t}\n\t}()\n\n\tSign(\"Hello again!\")\n}\n\nfunc TestSign_InvalidMessage(t *testing.T) {\n\t// Should not allow control characters in message.\n\tguestbook = avl.Tree{}\n\thasSigned = avl.Tree{}\n\n\tstd.TestSetRealm(std.NewUserRealm(\"g1user\"))\n\n\tdefer func() {\n\t\trec := recover()\n\t\tif rec == nil {\n\t\t\tt.Fatal(\"expected panic\")\n\t\t}\n\t\trecString, ok := rec.(string)\n\t\tif !ok {\n\t\t\tt.Error(\"type assertion failed\", rec)\n\t\t} else if recString != errInvalidCharacterInMessage {\n\t\t\tt.Error(\"invalid error message\", recString)\n\t\t}\n\t}()\n\tSign(\"\\x00Hello!\")\n}\n\nfunc TestAdminDelete(t *testing.T) {\n\tconst (\n\t\tuserAddr std.Address = \"g1user\"\n\t\tadminAddr std.Address = \"g1admin\"\n\t)\n\n\tguestbook = avl.Tree{}\n\thasSigned = avl.Tree{}\n\towner = ownable.NewWithAddress(adminAddr)\n\tsignatureID = 0\n\n\tstd.TestSetRealm(std.NewUserRealm(userAddr))\n\n\tconst bad = \"Very Bad Message! Nyeh heh heh!\"\n\tSign(bad)\n\n\tif rnd := Render(\"\"); !strings.Contains(rnd, bad) {\n\t\tt.Fatal(\"render does not contain bad message\", rnd)\n\t}\n\n\tstd.TestSetRealm(std.NewUserRealm(adminAddr))\n\tAdminDelete(signatureID.String())\n\n\tif rnd := Render(\"\"); strings.Contains(rnd, bad) {\n\t\tt.Error(\"render contains bad message\", rnd)\n\t}\n\tif guestbook.Size() != 0 {\n\t\tt.Error(\"invalid guestbook size\")\n\t}\n\tif hasSigned.Size() != 1 {\n\t\tt.Error(\"invalid hasSigned size\")\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":"home","path":"gno.land/r/morgan/home","files":[{"name":"home.gno","body":"package home\n\nconst staticHome = `# morgan's (gn)home\n\n- [📝 sign my guestbook](/r/morgan/guestbook)\n`\n\nfunc Render(path string) string {\n\treturn staticHome\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":"registry","path":"gno.land/r/stefann/registry","files":[{"name":"registry.gno","body":"package registry\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/ownable\"\n)\n\nvar (\n\tmainAddr std.Address\n\tbackupAddr std.Address\n\towner *ownable.Ownable\n)\n\nfunc init() {\n\tmainAddr = \"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\"\n\tbackupAddr = \"g13awn2575t8s2vf3svlprc4dg0e9z5wchejdxk8\"\n\n\towner = ownable.NewWithAddress(mainAddr)\n}\n\nfunc MainAddr() std.Address {\n\treturn mainAddr\n}\n\nfunc BackupAddr() std.Address {\n\treturn backupAddr\n}\n\nfunc SetMainAddr(addr std.Address) error {\n\tif !addr.IsValid() {\n\t\treturn errors.New(\"config: invalid address\")\n\t}\n\n\towner.AssertCallerIsOwner()\n\n\tmainAddr = addr\n\treturn nil\n}\n\nfunc SetBackupAddr(addr std.Address) error {\n\tif !addr.IsValid() {\n\t\treturn errors.New(\"config: invalid address\")\n\t}\n\n\towner.AssertCallerIsOwner()\n\n\tbackupAddr = addr\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_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"home","path":"gno.land/r/stefann/home","files":[{"name":"home.gno","body":"package home\n\nimport (\n\t\"sort\"\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/ufmt\"\n\n\t\"gno.land/r/stefann/registry\"\n)\n\ntype City struct {\n\tName string\n\tURL string\n}\n\ntype Sponsor struct {\n\tAddress std.Address\n\tAmount std.Coins\n}\n\ntype Profile struct {\n\tpfp string\n\taboutMe []string\n}\n\ntype Travel struct {\n\tcities []City\n\tcurrentCityIndex int\n\tjarLink string\n}\n\ntype Sponsorship struct {\n\tmaxSponsors int\n\tsponsors *avl.Tree\n\tDonationsCount int\n\tsponsorsCount int\n}\n\nvar (\n\tprofile Profile\n\ttravel Travel\n\tsponsorship Sponsorship\n\towner *ownable.Ownable\n)\n\nfunc init() {\n\towner = ownable.NewWithAddress(registry.MainAddr())\n\n\tprofile = Profile{\n\t\tpfp: \"https://i.ibb.co/Bc5YNCx/DSC-0095a.jpg\",\n\t\taboutMe: []string{\n\t\t\t`### About Me`,\n\t\t\t`Hey there! I’m Stefan, a student of Computer Science. I’m all about exploring and adventure — whether it’s diving into the latest tech or discovering a new city, I’m always up for the challenge!`,\n\n\t\t\t`### Contributions`,\n\t\t\t`I'm just getting started, but you can follow my journey through gno.land right [here](https://github.com/gnolang/hackerspace/issues/94) 🔗`,\n\t\t},\n\t}\n\n\ttravel = Travel{\n\t\tcities: []City{\n\t\t\t{Name: \"Venice\", URL: \"https://i.ibb.co/1mcZ7b1/venice.jpg\"},\n\t\t\t{Name: \"Tokyo\", URL: \"https://i.ibb.co/wNDJv3H/tokyo.jpg\"},\n\t\t\t{Name: \"São Paulo\", URL: \"https://i.ibb.co/yWMq2Sn/sao-paulo.jpg\"},\n\t\t\t{Name: \"Toronto\", URL: \"https://i.ibb.co/pb95HJB/toronto.jpg\"},\n\t\t\t{Name: \"Bangkok\", URL: \"https://i.ibb.co/pQy3w2g/bangkok.jpg\"},\n\t\t\t{Name: \"New York\", URL: \"https://i.ibb.co/6JWLm0h/new-york.jpg\"},\n\t\t\t{Name: \"Paris\", URL: \"https://i.ibb.co/q9vf6Hs/paris.jpg\"},\n\t\t\t{Name: \"Kandersteg\", URL: \"https://i.ibb.co/60DzywD/kandersteg.jpg\"},\n\t\t\t{Name: \"Rothenburg\", URL: \"https://i.ibb.co/cr8d2rQ/rothenburg.jpg\"},\n\t\t\t{Name: \"Capetown\", URL: \"https://i.ibb.co/bPGn0v3/capetown.jpg\"},\n\t\t\t{Name: \"Sydney\", URL: \"https://i.ibb.co/TBNzqfy/sydney.jpg\"},\n\t\t\t{Name: \"Oeschinen Lake\", URL: \"https://i.ibb.co/QJQwp2y/oeschinen-lake.jpg\"},\n\t\t\t{Name: \"Barra Grande\", URL: \"https://i.ibb.co/z4RXKc1/barra-grande.jpg\"},\n\t\t\t{Name: \"London\", URL: \"https://i.ibb.co/CPGtvgr/london.jpg\"},\n\t\t},\n\t\tcurrentCityIndex: 0,\n\t\tjarLink: \"https://TODO\", // This value should be injected through UpdateJarLink after deployment.\n\t}\n\n\tsponsorship = Sponsorship{\n\t\tmaxSponsors: 5,\n\t\tsponsors: avl.NewTree(),\n\t\tDonationsCount: 0,\n\t\tsponsorsCount: 0,\n\t}\n}\n\nfunc UpdateCities(newCities []City) {\n\towner.AssertCallerIsOwner()\n\ttravel.cities = newCities\n}\n\nfunc AddCities(newCities ...City) {\n\towner.AssertCallerIsOwner()\n\n\ttravel.cities = append(travel.cities, newCities...)\n}\n\nfunc UpdateJarLink(newLink string) {\n\towner.AssertCallerIsOwner()\n\ttravel.jarLink = newLink\n}\n\nfunc UpdatePFP(url string) {\n\towner.AssertCallerIsOwner()\n\tprofile.pfp = url\n}\n\nfunc UpdateAboutMe(aboutMeStr string) {\n\towner.AssertCallerIsOwner()\n\tprofile.aboutMe = strings.Split(aboutMeStr, \"|\")\n}\n\nfunc AddAboutMeRows(newRows ...string) {\n\towner.AssertCallerIsOwner()\n\n\tprofile.aboutMe = append(profile.aboutMe, newRows...)\n}\n\nfunc UpdateMaxSponsors(newMax int) {\n\towner.AssertCallerIsOwner()\n\tif newMax \u003c= 0 {\n\t\tpanic(\"maxSponsors must be greater than zero\")\n\t}\n\tsponsorship.maxSponsors = newMax\n}\n\nfunc Donate() {\n\taddress := std.OriginCaller()\n\tamount := std.OriginSend()\n\n\tif amount.AmountOf(\"ugnot\") == 0 {\n\t\tpanic(\"Donation must include GNOT\")\n\t}\n\n\texistingAmount, exists := sponsorship.sponsors.Get(address.String())\n\tif exists {\n\t\tupdatedAmount := existingAmount.(std.Coins).Add(amount)\n\t\tsponsorship.sponsors.Set(address.String(), updatedAmount)\n\t} else {\n\t\tsponsorship.sponsors.Set(address.String(), amount)\n\t\tsponsorship.sponsorsCount++\n\t}\n\n\ttravel.currentCityIndex++\n\tsponsorship.DonationsCount++\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\townerAddr := registry.MainAddr()\n\tbanker.SendCoins(std.CurrentRealm().Addr(), ownerAddr, banker.GetCoins(std.CurrentRealm().Addr()))\n}\n\ntype SponsorSlice []Sponsor\n\nfunc (s SponsorSlice) Len() int {\n\treturn len(s)\n}\n\nfunc (s SponsorSlice) Less(i, j int) bool {\n\treturn s[i].Amount.AmountOf(\"ugnot\") \u003e s[j].Amount.AmountOf(\"ugnot\")\n}\n\nfunc (s SponsorSlice) Swap(i, j int) {\n\ts[i], s[j] = s[j], s[i]\n}\n\nfunc GetTopSponsors() []Sponsor {\n\tvar sponsorSlice SponsorSlice\n\n\tsponsorship.sponsors.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\taddr := std.Address(key)\n\t\tamount := value.(std.Coins)\n\t\tsponsorSlice = append(sponsorSlice, Sponsor{Address: addr, Amount: amount})\n\t\treturn false\n\t})\n\n\tsort.Sort(sponsorSlice)\n\treturn sponsorSlice\n}\n\nfunc GetTotalDonations() int {\n\ttotal := 0\n\tsponsorship.sponsors.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\ttotal += int(value.(std.Coins).AmountOf(\"ugnot\"))\n\t\treturn false\n\t})\n\treturn total\n}\n\nfunc Render(path string) string {\n\tout := ufmt.Sprintf(\"# Exploring %s!\\n\\n\", travel.cities[travel.currentCityIndex].Name)\n\n\tout += renderAboutMe()\n\tout += \"\\n\\n\"\n\tout += renderTips()\n\n\treturn out\n}\n\nfunc renderAboutMe() string {\n\tout := \"\u003cdiv class='rows-3'\u003e\"\n\n\tout += \"\u003cdiv style='position: relative; text-align: center;'\u003e\\n\\n\"\n\n\tout += ufmt.Sprintf(\"\u003cdiv style='background-image: url(%s); background-size: cover; background-position: center; width: 100%%; height: 600px; position: relative; border-radius: 15px; overflow: hidden;'\u003e\\n\\n\", travel.cities[travel.currentCityIndex%len(travel.cities)].URL)\n\n\tout += ufmt.Sprintf(\"\u003cimg src='%s' alt='my profile pic' style='width: 250px; height: auto; aspect-ratio: 1 / 1; object-fit: cover; border-radius: 50%%; border: 3px solid #1e1e1e; position: absolute; top: 75%%; left: 50%%; transform: translate(-50%%, -50%%);'\u003e\\n\\n\", profile.pfp)\n\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\tfor _, rows := range profile.aboutMe {\n\t\tout += \"\u003cdiv\u003e\\n\\n\"\n\t\tout += rows + \"\\n\\n\"\n\t\tout += \"\u003c/div\u003e\\n\\n\"\n\t}\n\n\tout += \"\u003c/div\u003e\u003c!-- /rows-3 --\u003e\\n\\n\"\n\n\treturn out\n}\n\nfunc renderTips() string {\n\tout := `\u003cdiv class=\"jumbotron\" style=\"display: flex; flex-direction: column; justify-content: flex-start; align-items: center; padding-top: 40px; padding-bottom: 50px; text-align: center;\"\u003e` + \"\\n\\n\"\n\n\tout += `\u003cdiv class=\"rows-2\" style=\"max-width: 500px; width: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center;\"\u003e` + \"\\n\"\n\n\tout += `\u003ch1 style=\"margin-bottom: 50px;\"\u003eHelp Me Travel The World\u003c/h1\u003e` + \"\\n\\n\"\n\n\tout += renderTipsJar() + \"\\n\"\n\n\tout += ufmt.Sprintf(`\u003cstrong style=\"font-size: 1.2em;\"\u003eI am currently in %s, \u003cbr\u003e tip the jar to send me somewhere else!\u003c/strong\u003e`, travel.cities[travel.currentCityIndex].Name)\n\n\tout += `\u003cbr\u003e\u003cspan style=\"font-size: 1.2em; font-style: italic; margin-top: 10px; display: inline-block;\"\u003eClick the jar, tip in GNOT coins, and watch my background change as I head to a new adventure!\u003c/span\u003e\u003c/p\u003e` + \"\\n\\n\"\n\n\tout += renderSponsors()\n\n\tout += `\u003c/div\u003e\u003c!-- /rows-2 --\u003e` + \"\\n\\n\"\n\n\tout += `\u003c/div\u003e\u003c!-- /jumbotron --\u003e` + \"\\n\"\n\n\treturn out\n}\n\nfunc formatAddress(address string) string {\n\tif len(address) \u003c= 8 {\n\t\treturn address\n\t}\n\treturn address[:4] + \"...\" + address[len(address)-4:]\n}\n\nfunc renderSponsors() string {\n\tout := `\u003ch3 style=\"margin-top: 5px; margin-bottom: 20px\"\u003eSponsor Leaderboard\u003c/h3\u003e` + \"\\n\"\n\n\tif sponsorship.sponsorsCount == 0 {\n\t\treturn out + `\u003cp style=\"text-align: center;\"\u003eNo sponsors yet. Be the first to tip the jar!\u003c/p\u003e` + \"\\n\"\n\t}\n\n\ttopSponsors := GetTopSponsors()\n\tnumSponsors := len(topSponsors)\n\tif numSponsors \u003e sponsorship.maxSponsors {\n\t\tnumSponsors = sponsorship.maxSponsors\n\t}\n\n\tout += `\u003cul style=\"list-style-type: none; padding: 0; border: 1px solid #ddd; border-radius: 8px; width: 100%; max-width: 300px; margin: 0 auto;\"\u003e` + \"\\n\"\n\n\tfor i := 0; i \u003c numSponsors; i++ {\n\t\tsponsor := topSponsors[i]\n\t\tisLastItem := (i == numSponsors-1)\n\n\t\tpadding := \"10px 5px\"\n\t\tborder := \"border-bottom: 1px solid #ddd;\"\n\n\t\tif isLastItem {\n\t\t\tpadding = \"8px 5px\"\n\t\t\tborder = \"\"\n\t\t}\n\n\t\tout += ufmt.Sprintf(\n\t\t\t`\u003cli style=\"padding: %s; %s text-align: left;\"\u003e\n\t\t\t\t\u003cstrong style=\"padding-left: 5px;\"\u003e%d. %s\u003c/strong\u003e\n\t\t\t\t\u003cspan style=\"float: right; padding-right: 5px;\"\u003e%s\u003c/span\u003e\n\t\t\t\u003c/li\u003e`,\n\t\t\tpadding, border, i+1, formatAddress(sponsor.Address.String()), sponsor.Amount.String(),\n\t\t)\n\t}\n\n\treturn out\n}\n\nfunc renderTipsJar() string {\n\tout := ufmt.Sprintf(`\u003ca href=\"%s\" target=\"_blank\" style=\"display: block; text-decoration: none;\"\u003e`, travel.jarLink) + \"\\n\"\n\n\tout += `\u003cimg src=\"https://i.ibb.co/4TH9zbw/tips-jar.png\" alt=\"Tips Jar\" style=\"width: 300px; height: auto; display: block; margin: 0 auto;\"\u003e` + \"\\n\"\n\n\tout += `\u003c/a\u003e` + \"\\n\"\n\n\treturn out\n}\n"},{"name":"home_test.gno","body":"package home\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/testutils\"\n)\n\nfunc TestUpdatePFP(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOriginCaller(owner)\n\n\tprofile.pfp = \"\"\n\n\tUpdatePFP(\"https://example.com/pic.png\")\n\n\tif profile.pfp != \"https://example.com/pic.png\" {\n\t\tt.Fatalf(\"expected pfp to be https://example.com/pic.png, got %s\", profile.pfp)\n\t}\n}\n\nfunc TestUpdateAboutMe(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOriginCaller(owner)\n\n\tprofile.aboutMe = []string{}\n\n\tUpdateAboutMe(\"This is my new bio.|I love coding!\")\n\n\texpected := []string{\"This is my new bio.\", \"I love coding!\"}\n\n\tif len(profile.aboutMe) != len(expected) {\n\t\tt.Fatalf(\"expected aboutMe to have length %d, got %d\", len(expected), len(profile.aboutMe))\n\t}\n\n\tfor i := range profile.aboutMe {\n\t\tif profile.aboutMe[i] != expected[i] {\n\t\t\tt.Fatalf(\"expected aboutMe[%d] to be %s, got %s\", i, expected[i], profile.aboutMe[i])\n\t\t}\n\t}\n}\n\nfunc TestUpdateCities(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOriginCaller(owner)\n\n\ttravel.cities = []City{}\n\n\tnewCities := []City{\n\t\t{Name: \"Berlin\", URL: \"https://example.com/berlin.jpg\"},\n\t\t{Name: \"Vienna\", URL: \"https://example.com/vienna.jpg\"},\n\t}\n\n\tUpdateCities(newCities)\n\n\tif len(travel.cities) != 2 {\n\t\tt.Fatalf(\"expected 2 cities, got %d\", len(travel.cities))\n\t}\n\n\tif travel.cities[0].Name != \"Berlin\" || travel.cities[1].Name != \"Vienna\" {\n\t\tt.Fatalf(\"expected cities to be updated to Berlin and Vienna, got %+v\", travel.cities)\n\t}\n}\n\nfunc TestUpdateJarLink(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOriginCaller(owner)\n\n\ttravel.jarLink = \"\"\n\n\tUpdateJarLink(\"https://example.com/jar\")\n\n\tif travel.jarLink != \"https://example.com/jar\" {\n\t\tt.Fatalf(\"expected jarLink to be https://example.com/jar, got %s\", travel.jarLink)\n\t}\n}\n\nfunc TestUpdateMaxSponsors(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOriginCaller(owner)\n\n\tsponsorship.maxSponsors = 0\n\n\tUpdateMaxSponsors(10)\n\n\tif sponsorship.maxSponsors != 10 {\n\t\tt.Fatalf(\"expected maxSponsors to be 10, got %d\", sponsorship.maxSponsors)\n\t}\n\n\tdefer func() {\n\t\tif r := recover(); r == nil {\n\t\t\tt.Fatalf(\"expected panic for setting maxSponsors to 0\")\n\t\t}\n\t}()\n\tUpdateMaxSponsors(0)\n}\n\nfunc TestAddCities(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOriginCaller(owner)\n\n\ttravel.cities = []City{}\n\n\tAddCities(City{Name: \"Berlin\", URL: \"https://example.com/berlin.jpg\"})\n\n\tif len(travel.cities) != 1 {\n\t\tt.Fatalf(\"expected 1 city, got %d\", len(travel.cities))\n\t}\n\tif travel.cities[0].Name != \"Berlin\" || travel.cities[0].URL != \"https://example.com/berlin.jpg\" {\n\t\tt.Fatalf(\"expected city to be Berlin, got %+v\", travel.cities[0])\n\t}\n\n\tAddCities(\n\t\tCity{Name: \"Paris\", URL: \"https://example.com/paris.jpg\"},\n\t\tCity{Name: \"Tokyo\", URL: \"https://example.com/tokyo.jpg\"},\n\t)\n\n\tif len(travel.cities) != 3 {\n\t\tt.Fatalf(\"expected 3 cities, got %d\", len(travel.cities))\n\t}\n\tif travel.cities[1].Name != \"Paris\" || travel.cities[2].Name != \"Tokyo\" {\n\t\tt.Fatalf(\"expected cities to be Paris and Tokyo, got %+v\", travel.cities[1:])\n\t}\n}\n\nfunc TestAddAboutMeRows(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOriginCaller(owner)\n\n\tprofile.aboutMe = []string{}\n\n\tAddAboutMeRows(\"I love exploring new technologies!\")\n\n\tif len(profile.aboutMe) != 1 {\n\t\tt.Fatalf(\"expected 1 aboutMe row, got %d\", len(profile.aboutMe))\n\t}\n\tif profile.aboutMe[0] != \"I love exploring new technologies!\" {\n\t\tt.Fatalf(\"expected first aboutMe row to be 'I love exploring new technologies!', got %s\", profile.aboutMe[0])\n\t}\n\n\tAddAboutMeRows(\"Travel is my passion!\", \"Always learning.\")\n\n\tif len(profile.aboutMe) != 3 {\n\t\tt.Fatalf(\"expected 3 aboutMe rows, got %d\", len(profile.aboutMe))\n\t}\n\tif profile.aboutMe[1] != \"Travel is my passion!\" || profile.aboutMe[2] != \"Always learning.\" {\n\t\tt.Fatalf(\"expected aboutMe rows to be 'Travel is my passion!' and 'Always learning.', got %+v\", profile.aboutMe[1:])\n\t}\n}\n\nfunc TestDonate(t *testing.T) {\n\tvar user = testutils.TestAddress(\"user\")\n\tstd.TestSetOriginCaller(user)\n\n\tsponsorship.sponsors = avl.NewTree()\n\tsponsorship.DonationsCount = 0\n\tsponsorship.sponsorsCount = 0\n\ttravel.currentCityIndex = 0\n\n\tcoinsSent := std.NewCoins(std.NewCoin(\"ugnot\", 500))\n\tstd.TestSetOriginSend(coinsSent, std.NewCoins())\n\tDonate()\n\n\texistingAmount, exists := sponsorship.sponsors.Get(string(user))\n\tif !exists {\n\t\tt.Fatalf(\"expected sponsor to be added, but it was not found\")\n\t}\n\n\tif existingAmount.(std.Coins).AmountOf(\"ugnot\") != 500 {\n\t\tt.Fatalf(\"expected donation amount to be 500ugnot, got %d\", existingAmount.(std.Coins).AmountOf(\"ugnot\"))\n\t}\n\n\tif sponsorship.DonationsCount != 1 {\n\t\tt.Fatalf(\"expected DonationsCount to be 1, got %d\", sponsorship.DonationsCount)\n\t}\n\n\tif sponsorship.sponsorsCount != 1 {\n\t\tt.Fatalf(\"expected sponsorsCount to be 1, got %d\", sponsorship.sponsorsCount)\n\t}\n\n\tif travel.currentCityIndex != 1 {\n\t\tt.Fatalf(\"expected currentCityIndex to be 1, got %d\", travel.currentCityIndex)\n\t}\n\n\tcoinsSent = std.NewCoins(std.NewCoin(\"ugnot\", 300))\n\tstd.TestSetOriginSend(coinsSent, std.NewCoins())\n\tDonate()\n\n\texistingAmount, exists = sponsorship.sponsors.Get(string(user))\n\tif !exists {\n\t\tt.Fatalf(\"expected sponsor to exist after second donation, but it was not found\")\n\t}\n\n\tif existingAmount.(std.Coins).AmountOf(\"ugnot\") != 800 {\n\t\tt.Fatalf(\"expected total donation amount to be 800ugnot, got %d\", existingAmount.(std.Coins).AmountOf(\"ugnot\"))\n\t}\n\n\tif sponsorship.DonationsCount != 2 {\n\t\tt.Fatalf(\"expected DonationsCount to be 2 after second donation, got %d\", sponsorship.DonationsCount)\n\t}\n\n\tif travel.currentCityIndex != 2 {\n\t\tt.Fatalf(\"expected currentCityIndex to be 2 after second donation, got %d\", travel.currentCityIndex)\n\t}\n}\n\nfunc TestGetTopSponsors(t *testing.T) {\n\tvar user = testutils.TestAddress(\"user\")\n\tstd.TestSetOriginCaller(user)\n\n\tsponsorship.sponsors = avl.NewTree()\n\tsponsorship.sponsorsCount = 0\n\n\tsponsorship.sponsors.Set(\"g1address1\", std.NewCoins(std.NewCoin(\"ugnot\", 300)))\n\tsponsorship.sponsors.Set(\"g1address2\", std.NewCoins(std.NewCoin(\"ugnot\", 500)))\n\tsponsorship.sponsors.Set(\"g1address3\", std.NewCoins(std.NewCoin(\"ugnot\", 200)))\n\tsponsorship.sponsorsCount = 3\n\n\ttopSponsors := GetTopSponsors()\n\n\tif len(topSponsors) != 3 {\n\t\tt.Fatalf(\"expected 3 sponsors, got %d\", len(topSponsors))\n\t}\n\n\tif topSponsors[0].Address.String() != \"g1address2\" || topSponsors[0].Amount.AmountOf(\"ugnot\") != 500 {\n\t\tt.Fatalf(\"expected top sponsor to be g1address2 with 500ugnot, got %s with %dugnot\", topSponsors[0].Address.String(), topSponsors[0].Amount.AmountOf(\"ugnot\"))\n\t}\n\n\tif topSponsors[1].Address.String() != \"g1address1\" || topSponsors[1].Amount.AmountOf(\"ugnot\") != 300 {\n\t\tt.Fatalf(\"expected second sponsor to be g1address1 with 300ugnot, got %s with %dugnot\", topSponsors[1].Address.String(), topSponsors[1].Amount.AmountOf(\"ugnot\"))\n\t}\n\n\tif topSponsors[2].Address.String() != \"g1address3\" || topSponsors[2].Amount.AmountOf(\"ugnot\") != 200 {\n\t\tt.Fatalf(\"expected third sponsor to be g1address3 with 200ugnot, got %s with %dugnot\", topSponsors[2].Address.String(), topSponsors[2].Amount.AmountOf(\"ugnot\"))\n\t}\n}\n\nfunc TestGetTotalDonations(t *testing.T) {\n\tvar user = testutils.TestAddress(\"user\")\n\tstd.TestSetOriginCaller(user)\n\n\tsponsorship.sponsors = avl.NewTree()\n\tsponsorship.sponsorsCount = 0\n\n\tsponsorship.sponsors.Set(\"g1address1\", std.NewCoins(std.NewCoin(\"ugnot\", 300)))\n\tsponsorship.sponsors.Set(\"g1address2\", std.NewCoins(std.NewCoin(\"ugnot\", 500)))\n\tsponsorship.sponsors.Set(\"g1address3\", std.NewCoins(std.NewCoin(\"ugnot\", 200)))\n\tsponsorship.sponsorsCount = 3\n\n\ttotalDonations := GetTotalDonations()\n\n\tif totalDonations != 1000 {\n\t\tt.Fatalf(\"expected total donations to be 1000ugnot, got %dugnot\", totalDonations)\n\t}\n}\n\nfunc TestRender(t *testing.T) {\n\ttravel.currentCityIndex = 0\n\ttravel.cities = []City{\n\t\t{Name: \"Venice\", URL: \"https://example.com/venice.jpg\"},\n\t\t{Name: \"Paris\", URL: \"https://example.com/paris.jpg\"},\n\t}\n\n\toutput := Render(\"\")\n\n\texpectedCity := \"Venice\"\n\tif !strings.Contains(output, expectedCity) {\n\t\tt.Fatalf(\"expected output to contain city name '%s', got %s\", expectedCity, output)\n\t}\n\n\texpectedURL := \"https://example.com/venice.jpg\"\n\tif !strings.Contains(output, expectedURL) {\n\t\tt.Fatalf(\"expected output to contain city URL '%s', got %s\", expectedURL, output)\n\t}\n\n\ttravel.currentCityIndex = 1\n\toutput = Render(\"\")\n\n\texpectedCity = \"Paris\"\n\tif !strings.Contains(output, expectedCity) {\n\t\tt.Fatalf(\"expected output to contain city name '%s', got %s\", expectedCity, output)\n\t}\n\n\texpectedURL = \"https://example.com/paris.jpg\"\n\tif !strings.Contains(output, expectedURL) {\n\t\tt.Fatalf(\"expected output to contain city URL '%s', got %s\", expectedURL, output)\n\t}\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}} From 0f79638737daade772376b0d38e598c4e89f7bc5 Mon Sep 17 00:00:00 2001 From: "hieu.ha" Date: Fri, 20 Dec 2024 11:41:09 +0700 Subject: [PATCH 05/14] refactor: Orig => Origin --- .../porting-solidity-to-gno/porting-11.gno | 2 +- .../porting-solidity-to-gno/porting-8.gno | 2 +- .../porting-solidity-to-gno/porting-9.gno | 2 +- docs/how-to-guides/porting-solidity-to-gno.md | 6 ++--- docs/reference/stdlibs/std/chain.md | 6 ++--- docs/reference/stdlibs/std/testing.md | 8 +++---- examples/gno.land/r/demo/banktest/README.md | 4 ++-- .../gno.land/r/demo/banktest/banktest.gno | 4 ++-- .../gno.land/r/demo/banktest/z_0_filetest.gno | 2 +- .../gno.land/r/demo/banktest/z_1_filetest.gno | 2 +- .../gno.land/r/demo/banktest/z_2_filetest.gno | 2 +- .../gno.land/r/demo/disperse/z_0_filetest.gno | 2 +- .../gno.land/r/demo/disperse/z_1_filetest.gno | 2 +- .../gno.land/r/demo/disperse/z_2_filetest.gno | 2 +- .../gno.land/r/demo/disperse/z_3_filetest.gno | 2 +- .../gno.land/r/demo/disperse/z_4_filetest.gno | 2 +- .../r/demo/grc20factory/grc20factory_test.gno | 2 +- examples/gno.land/r/demo/users/users.gno | 6 ++--- .../gno.land/r/demo/wugnot/z0_filetest.gno | 2 +- .../upgrade_b/v1/v1.gno | 2 +- .../upgrade_b/v2/v2.gno | 2 +- gno.land/genesis/genesis_txs.jsonl | 2 +- .../pkg/integration/testdata/issue_2283.txtar | 2 +- .../testdata/issue_2283_cacheTypes.txtar | 2 +- gno.land/pkg/sdk/vm/keeper.go | 24 +++++++++---------- gno.land/pkg/sdk/vm/keeper_test.go | 12 +++++----- gnovm/pkg/test/test.go | 2 +- gnovm/stdlibs/std/banker.gno | 2 +- gnovm/stdlibs/std/context.go | 2 +- gnovm/stdlibs/std/native.gno | 2 +- gnovm/stdlibs/std/native.go | 2 +- gnovm/tests/files/zrealm_std6.gno | 2 +- gnovm/tests/stdlibs/generated.go | 4 ++-- gnovm/tests/stdlibs/std/std.gno | 4 ++-- gnovm/tests/stdlibs/std/std.go | 4 ++-- .../test5.gno.land/genesis_txs.jsonl | 8 +++---- 36 files changed, 69 insertions(+), 69 deletions(-) diff --git a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-11.gno b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-11.gno index e48ebf919a0..e8eb785ccc9 100644 --- a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-11.gno +++ b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-11.gno @@ -11,7 +11,7 @@ func AuctionEnd() { // Send the highest bid to the recipient banker := std.GetBanker(std.BankerTypeRealmSend) - pkgAddr := std.GetOrigPkgAddr() + pkgAddr := std.GetOriginPkgAddr() banker.SendCoins(pkgAddr, receiver, std.Coins{{"ugnot", int64(highestBid)}}) } diff --git a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-8.gno b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-8.gno index 868fc0bf7fc..ed2e2d7fcde 100644 --- a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-8.gno +++ b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-8.gno @@ -8,7 +8,7 @@ func Withdraw() { // Return the exceeded amount banker := std.GetBanker(std.BankerTypeRealmSend) - pkgAddr := std.GetOrigPkgAddr() + pkgAddr := std.GetOriginPkgAddr() banker.SendCoins(pkgAddr, std.OriginCaller(), std.Coins{{"ugnot", amount.(int64)}}) } diff --git a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-9.gno b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-9.gno index fbc06792ce4..4056145e041 100644 --- a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-9.gno +++ b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-9.gno @@ -11,7 +11,7 @@ func TestWithdraw(t *testing.T) { shouldEqual(t, pendingReturns.Has(returnAddr), true) banker := std.GetBanker(std.BankerTypeRealmSend) - pkgAddr := std.GetOrigPkgAddr() + pkgAddr := std.GetOriginPkgAddr() banker.SendCoins(pkgAddr, std.Address(returnAddr), std.Coins{{"ugnot", returnAmount}}) shouldEqual(t, banker.GetCoins(std.Address(returnAddr)).String(), "3ugnot") } diff --git a/docs/how-to-guides/porting-solidity-to-gno.md b/docs/how-to-guides/porting-solidity-to-gno.md index 64acd30b426..cd6989cd476 100644 --- a/docs/how-to-guides/porting-solidity-to-gno.md +++ b/docs/how-to-guides/porting-solidity-to-gno.md @@ -474,7 +474,7 @@ func Withdraw() { // Return the exceeded amount banker := std.GetBanker(std.BankerTypeRealmSend) - pkgAddr := std.GetOrigPkgAddr() + pkgAddr := std.GetOriginPkgAddr() banker.SendCoins(pkgAddr, std.OriginCaller(), std.Coins{{"ugnot", amount.(int64)}}) } @@ -500,7 +500,7 @@ func TestWithdraw(t *testing.T) { shouldEqual(t, pendingReturns.Has(returnAddr), true) banker := std.GetBanker(std.BankerTypeRealmSend) - pkgAddr := std.GetOrigPkgAddr() + pkgAddr := std.GetOriginPkgAddr() banker.SendCoins(pkgAddr, std.Address(returnAddr), std.Coins{{"ugnot", returnAmount}}) shouldEqual(t, banker.GetCoins(std.Address(returnAddr)).String(), "3ugnot") } @@ -565,7 +565,7 @@ func AuctionEnd() { // Send the highest bid to the recipient banker := std.GetBanker(std.BankerTypeRealmSend) - pkgAddr := std.GetOrigPkgAddr() + pkgAddr := std.GetOriginPkgAddr() banker.SendCoins(pkgAddr, receiver, std.Coins{{"ugnot", int64(highestBid)}}) } diff --git a/docs/reference/stdlibs/std/chain.md b/docs/reference/stdlibs/std/chain.md index d57139858d9..53a4e3ce13c 100644 --- a/docs/reference/stdlibs/std/chain.md +++ b/docs/reference/stdlibs/std/chain.md @@ -101,15 +101,15 @@ caller := std.OriginCaller() ``` --- -## GetOrigPkgAddr +## GetOriginPkgAddr ```go -func GetOrigPkgAddr() string +func GetOriginPkgAddr() string ``` Returns the address of the first (entry point) realm/package in a sequence of realm/package calls. #### Usage ```go -origPkgAddr := std.GetOrigPkgAddr() +origPkgAddr := std.GetOriginPkgAddr() ``` --- diff --git a/docs/reference/stdlibs/std/testing.md b/docs/reference/stdlibs/std/testing.md index a821edf4152..ff625df745b 100644 --- a/docs/reference/stdlibs/std/testing.md +++ b/docs/reference/stdlibs/std/testing.md @@ -7,7 +7,7 @@ id: testing ```go func TestSkipHeights(count int64) func TestSetOriginCaller(addr Address) -func TestSetOrigPkgAddr(addr Address) +func TestSetOriginPkgAddr(addr Address) func TestSetOriginSend(sent, spent Coins) func TestIssueCoins(addr Address, coins Coins) func TestSetRealm(realm Realm) @@ -45,16 +45,16 @@ std.TestSetOriginCaller(std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")) ``` --- -## TestSetOrigPkgAddr +## TestSetOriginPkgAddr ```go -func TestSetOrigPkgAddr(addr Address) +func TestSetOriginPkgAddr(addr Address) ``` Sets the call entry realm address to **addr**. #### Usage ```go -std.TestSetOrigPkgAddr(std.Address("g1ecely4gjy0yl6s9kt409ll330q9hk2lj9ls3ec")) +std.TestSetOriginPkgAddr(std.Address("g1ecely4gjy0yl6s9kt409ll330q9hk2lj9ls3ec")) ``` --- diff --git a/examples/gno.land/r/demo/banktest/README.md b/examples/gno.land/r/demo/banktest/README.md index 43e52e3b394..6dff58c90c9 100644 --- a/examples/gno.land/r/demo/banktest/README.md +++ b/examples/gno.land/r/demo/banktest/README.md @@ -80,7 +80,7 @@ If the user requested the return of coins... use a std.Banker instance to return any deposited coins to the original sender. ```go - pkgaddr := std.GetOrigPkgAddr() + pkgaddr := std.GetOriginPkgAddr() // TODO: use std.Coins constructors, this isn't generally safe. banker.SendCoins(pkgaddr, caller, send) return "returned!" @@ -94,7 +94,7 @@ Finally, the results are rendered via an ABCI query call when you visit [/r/demo func Render(path string) string { // get realm coins. banker := std.GetBanker(std.BankerTypeReadonly) - coins := banker.GetCoins(std.GetOrigPkgAddr()) + coins := banker.GetCoins(std.GetOriginPkgAddr()) // render res := "" diff --git a/examples/gno.land/r/demo/banktest/banktest.gno b/examples/gno.land/r/demo/banktest/banktest.gno index 566209b4af7..359cca93004 100644 --- a/examples/gno.land/r/demo/banktest/banktest.gno +++ b/examples/gno.land/r/demo/banktest/banktest.gno @@ -40,7 +40,7 @@ func Deposit(returnDenom string, returnAmount int64) string { // return if any. if returnAmount > 0 { banker := std.GetBanker(std.BankerTypeOriginSend) - pkgaddr := std.GetOrigPkgAddr() + pkgaddr := std.GetOriginPkgAddr() // TODO: use std.Coins constructors, this isn't generally safe. banker.SendCoins(pkgaddr, caller, send) return "returned!" @@ -52,7 +52,7 @@ func Deposit(returnDenom string, returnAmount int64) string { func Render(path string) string { // get realm coins. banker := std.GetBanker(std.BankerTypeReadonly) - coins := banker.GetCoins(std.GetOrigPkgAddr()) + coins := banker.GetCoins(std.GetOriginPkgAddr()) // render res := "" diff --git a/examples/gno.land/r/demo/banktest/z_0_filetest.gno b/examples/gno.land/r/demo/banktest/z_0_filetest.gno index e9a10e72d6d..d41d1cbb851 100644 --- a/examples/gno.land/r/demo/banktest/z_0_filetest.gno +++ b/examples/gno.land/r/demo/banktest/z_0_filetest.gno @@ -18,7 +18,7 @@ func main() { banktestAddr := std.DerivePkgAddr("gno.land/r/demo/banktest") mainaddr := std.DerivePkgAddr("gno.land/r/demo/bank1") std.TestSetOriginCaller(mainaddr) - std.TestSetOrigPkgAddr(banktestAddr) + std.TestSetOriginPkgAddr(banktestAddr) // get and print balance of mainaddr. // with the SEND, + 200 gnot given by the TestContext, main should have 300gnot. diff --git a/examples/gno.land/r/demo/banktest/z_1_filetest.gno b/examples/gno.land/r/demo/banktest/z_1_filetest.gno index 87be4dbdb3e..2f34d85e766 100644 --- a/examples/gno.land/r/demo/banktest/z_1_filetest.gno +++ b/examples/gno.land/r/demo/banktest/z_1_filetest.gno @@ -15,7 +15,7 @@ func main() { banktestAddr := std.DerivePkgAddr("gno.land/r/demo/banktest") // simulate a Deposit call. - std.TestSetOrigPkgAddr(banktestAddr) + std.TestSetOriginPkgAddr(banktestAddr) std.TestIssueCoins(banktestAddr, std.Coins{{"ugnot", 100000000}}) std.TestSetOriginSend(std.Coins{{"ugnot", 100000000}}, nil) res := banktest.Deposit("ugnot", 101000000) diff --git a/examples/gno.land/r/demo/banktest/z_2_filetest.gno b/examples/gno.land/r/demo/banktest/z_2_filetest.gno index 3dd8d6a4bd5..6d4d511c243 100644 --- a/examples/gno.land/r/demo/banktest/z_2_filetest.gno +++ b/examples/gno.land/r/demo/banktest/z_2_filetest.gno @@ -23,7 +23,7 @@ func main() { println("main before:", mainbal) // plus OriginSend equals 300. // simulate a Deposit call. - std.TestSetOrigPkgAddr(banktestAddr) + std.TestSetOriginPkgAddr(banktestAddr) std.TestIssueCoins(banktestAddr, std.Coins{{"ugnot", 100000000}}) std.TestSetOriginSend(std.Coins{{"ugnot", 100000000}}, nil) res := banktest.Deposit("ugnot", 55000000) diff --git a/examples/gno.land/r/demo/disperse/z_0_filetest.gno b/examples/gno.land/r/demo/disperse/z_0_filetest.gno index 53169c075fe..5779eea71f4 100644 --- a/examples/gno.land/r/demo/disperse/z_0_filetest.gno +++ b/examples/gno.land/r/demo/disperse/z_0_filetest.gno @@ -14,7 +14,7 @@ func main() { disperseAddr := std.DerivePkgAddr("gno.land/r/demo/disperse") mainaddr := std.DerivePkgAddr("gno.land/r/demo/main") - std.TestSetOrigPkgAddr(disperseAddr) + std.TestSetOriginPkgAddr(disperseAddr) std.TestSetOriginCaller(mainaddr) banker := std.GetBanker(std.BankerTypeRealmSend) diff --git a/examples/gno.land/r/demo/disperse/z_1_filetest.gno b/examples/gno.land/r/demo/disperse/z_1_filetest.gno index 36b0dab6f87..41388e22eb4 100644 --- a/examples/gno.land/r/demo/disperse/z_1_filetest.gno +++ b/examples/gno.land/r/demo/disperse/z_1_filetest.gno @@ -14,7 +14,7 @@ func main() { disperseAddr := std.DerivePkgAddr("gno.land/r/demo/disperse") mainaddr := std.DerivePkgAddr("gno.land/r/demo/main") - std.TestSetOrigPkgAddr(disperseAddr) + std.TestSetOriginPkgAddr(disperseAddr) std.TestSetOriginCaller(mainaddr) banker := std.GetBanker(std.BankerTypeRealmSend) diff --git a/examples/gno.land/r/demo/disperse/z_2_filetest.gno b/examples/gno.land/r/demo/disperse/z_2_filetest.gno index c50738e6402..018fdac827c 100644 --- a/examples/gno.land/r/demo/disperse/z_2_filetest.gno +++ b/examples/gno.land/r/demo/disperse/z_2_filetest.gno @@ -14,7 +14,7 @@ func main() { disperseAddr := std.DerivePkgAddr("gno.land/r/demo/disperse") mainaddr := std.DerivePkgAddr("gno.land/r/demo/main") - std.TestSetOrigPkgAddr(disperseAddr) + std.TestSetOriginPkgAddr(disperseAddr) std.TestSetOriginCaller(mainaddr) banker := std.GetBanker(std.BankerTypeRealmSend) diff --git a/examples/gno.land/r/demo/disperse/z_3_filetest.gno b/examples/gno.land/r/demo/disperse/z_3_filetest.gno index 7c572cca7d6..70128d47cf2 100644 --- a/examples/gno.land/r/demo/disperse/z_3_filetest.gno +++ b/examples/gno.land/r/demo/disperse/z_3_filetest.gno @@ -17,7 +17,7 @@ func main() { beneficiary1 := std.Address("g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0") beneficiary2 := std.Address("g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c") - std.TestSetOrigPkgAddr(disperseAddr) + std.TestSetOriginPkgAddr(disperseAddr) std.TestSetOriginCaller(mainaddr) banker := std.GetBanker(std.BankerTypeRealmSend) diff --git a/examples/gno.land/r/demo/disperse/z_4_filetest.gno b/examples/gno.land/r/demo/disperse/z_4_filetest.gno index a03cfa08fa6..7ca890c5b8b 100644 --- a/examples/gno.land/r/demo/disperse/z_4_filetest.gno +++ b/examples/gno.land/r/demo/disperse/z_4_filetest.gno @@ -17,7 +17,7 @@ func main() { beneficiary1 := std.Address("g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0") beneficiary2 := std.Address("g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c") - std.TestSetOrigPkgAddr(disperseAddr) + std.TestSetOriginPkgAddr(disperseAddr) std.TestSetOriginCaller(mainaddr) banker := std.GetBanker(std.BankerTypeRealmSend) diff --git a/examples/gno.land/r/demo/grc20factory/grc20factory_test.gno b/examples/gno.land/r/demo/grc20factory/grc20factory_test.gno index ec251f9c4a0..5b53340b609 100644 --- a/examples/gno.land/r/demo/grc20factory/grc20factory_test.gno +++ b/examples/gno.land/r/demo/grc20factory/grc20factory_test.gno @@ -10,7 +10,7 @@ import ( ) func TestReadOnlyPublicMethods(t *testing.T) { - std.TestSetOrigPkgAddr("gno.land/r/demo/grc20factory") + std.TestSetOriginPkgAddr("gno.land/r/demo/grc20factory") admin := testutils.TestAddress("admin") bob := testutils.TestAddress("bob") carl := testutils.TestAddress("carl") diff --git a/examples/gno.land/r/demo/users/users.gno b/examples/gno.land/r/demo/users/users.gno index 3f3edd82064..eba601584ba 100644 --- a/examples/gno.land/r/demo/users/users.gno +++ b/examples/gno.land/r/demo/users/users.gno @@ -36,7 +36,7 @@ func Register(inviter std.Address, name string, profile string) { // assert invited or paid. caller := std.CallerAt(2) if caller != std.OriginCaller() { - panic("should not happen") // because std.AssertOrigCall(). + panic("should not happen") // because std.AssertOriginCall(). } sentCoins := std.OriginSend() @@ -120,7 +120,7 @@ func Invite(invitee string) { // get caller/inviter. caller := std.CallerAt(2) if caller != std.OriginCaller() { - panic("should not happen") // because std.AssertOrigCall(). + panic("should not happen") // because std.AssertOriginCall(). } lines := strings.Split(invitee, "\n") if caller == admin { @@ -159,7 +159,7 @@ func GrantInvites(invites string) { // assert admin. caller := std.CallerAt(2) if caller != std.OriginCaller() { - panic("should not happen") // because std.AssertOrigCall(). + panic("should not happen") // because std.AssertOriginCall(). } if caller != admin { panic("unauthorized") diff --git a/examples/gno.land/r/demo/wugnot/z0_filetest.gno b/examples/gno.land/r/demo/wugnot/z0_filetest.gno index 72a9cd4e95b..f7db3aea66d 100644 --- a/examples/gno.land/r/demo/wugnot/z0_filetest.gno +++ b/examples/gno.land/r/demo/wugnot/z0_filetest.gno @@ -18,7 +18,7 @@ var ( ) func main() { - std.TestSetOrigPkgAddr(addrc) + std.TestSetOriginPkgAddr(addrc) std.TestIssueCoins(addrc, std.Coins{{"ugnot", 100000001}}) // TODO: remove this // issue ugnots diff --git a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v1/v1.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v1/v1.gno index 7317fee9478..170aba1c71a 100644 --- a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v1/v1.gno +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v1/v1.gno @@ -26,7 +26,7 @@ func SetNextVersion(addr string) { // assert admin. caller := std.CallerAt(2) if caller != std.OriginCaller() { - panic("should not happen") // because std.AssertOrigCall(). + panic("should not happen") // because std.AssertOriginCall(). } if caller != admin { panic("unauthorized") diff --git a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v2/v2.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v2/v2.gno index 3bb5606ec84..80723ad01f1 100644 --- a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v2/v2.gno +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v2/v2.gno @@ -30,7 +30,7 @@ func SetNextVersion(addr string) { // assert admin. caller := std.CallerAt(2) if caller != std.OriginCaller() { - panic("should not happen") // because std.AssertOrigCall(). + panic("should not happen") // because std.AssertOriginCall(). } if caller != admin { panic("unauthorized") diff --git a/gno.land/genesis/genesis_txs.jsonl b/gno.land/genesis/genesis_txs.jsonl index 036d4686f72..1eaa3543773 100644 --- a/gno.land/genesis/genesis_txs.jsonl +++ b/gno.land/genesis/genesis_txs.jsonl @@ -10,7 +10,7 @@ {"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateBoard","args":["testboard"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"vzlSxEFh5jOkaSdv3rsV91v/OJKEF2qSuoCpri1u5tRWq62T7xr3KHRCF5qFnn4aQX/yE8g8f/Y//WPOCUGhJw=="}],"memo":""}} {"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","Hello World","This is a demo of Gno smart contract programming. This document was\nconstructed by Gno onto a smart contract hosted on the data Realm \nname [\"gno.land/r/demo/boards\"](https://gno.land/r/demo/boards/)\n([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)).\n\n## Starting the `gnoland` node node/validator.\n\nNOTE: Where you see `--remote %%REMOTE%%` here, that flag can be replaced\nwith `--remote localhost:26657` for local testnets.\n\n### build gnoland.\n\n```bash\ngit clone git@github.com:gnolang/gno.git\ncd ./gno\nmake \n```\n\n### add test account.\n\n```bash\n./build/gnokey add test1 --recover\n```\n\nUse this mnemonic:\n\u003e source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast\n\n### start gnoland validator node.\n\n```bash\n./build/gnoland\n```\n\n(This can be reset with `make reset`).\n\n### start gnoland web server (optional).\n\n```bash\ngo run ./gnoland/website\n```\n\n## Signing and broadcasting transactions.\n\n### publish the \"gno.land/p/demo/avl\" package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/p/demo/avl\" --pkgdir \"examples/gno.land/p/demo/avl\" --deposit 100ugnot --gas-fee 1ugnot --gas-wanted 2000000 \u003e addpkg.avl.unsigned.txt\n./build/gnokey query \"auth/accounts/g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\"\n./build/gnokey sign test1 --txpath addpkg.avl.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 0 \u003e addpkg.avl.signed.txt\n./build/gnokey broadcast addpkg.avl.signed.txt --remote %%REMOTE%%\n```\n\n### publish the \"gno.land/r/demo/boards\" realm package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/r/demo/boards\" --pkgdir \"examples/gno.land/r/demo/boards\" --deposit 100ugnot --gas-fee 1ugnot --gas-wanted 300000000 \u003e addpkg.boards.unsigned.txt\n./build/gnokey sign test1 --txpath addpkg.boards.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 1 \u003e addpkg.boards.signed.txt\n./build/gnokey broadcast addpkg.boards.signed.txt --remote %%REMOTE%%\n```\n\n### create a board with a smart contract call.\n\n```bash\n./build/gnokey maketx call test1 --pkgpath \"gno.land/r/demo/boards\" --func CreateBoard --args \"testboard\" --gas-fee 1ugnot --gas-wanted 2000000 \u003e createboard.unsigned.txt\n./build/gnokey sign test1 --txpath createboard.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 2 \u003e createboard.signed.txt\n./build/gnokey broadcast createboard.signed.txt --remote %%REMOTE%%\n```\nNext, query for the permanent board ID by querying (you need this to create a new post):\n\n```bash\n./build/gnokey query \"vm/qeval\" --data \"gno.land/r/demo/boards.GetBoardIDFromName(\\\"testboard\\\")\"\n```\n\n### create a post of a board with a smart contract call.\n\n```bash\n./build/gnokey maketx call test1 --pkgpath \"gno.land/r/demo/boards\" --func CreatePost --args 1 --args \"Hello World\" --args#file \"./examples/gno.land/r/demo/boards/README.md\" --gas-fee 1ugnot --gas-wanted 2000000 \u003e createpost.unsigned.txt\n./build/gnokey sign test1 --txpath createpost.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 3 \u003e createpost.signed.txt\n./build/gnokey broadcast createpost.signed.txt --remote %%REMOTE%%\n```\n\n### create a comment to a post.\n\n```bash\n./build/gnokey maketx call test1 --pkgpath \"gno.land/r/demo/boards\" --func CreateReply --args 1 --args 1 --args \"A comment\" --gas-fee 1ugnot --gas-wanted 2000000 \u003e createcomment.unsigned.txt\n./build/gnokey sign test1 --txpath createcomment.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 4 \u003e createcomment.signed.txt\n./build/gnokey broadcast createcomment.signed.txt --remote %%REMOTE%%\n```\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards:testboard/1\"\n```\n\n### render page with optional path expression.\n\nThe contents of `https://gno.land/r/demo/boards:` and `https://gno.land/r/demo/boards:testboard` are rendered by calling\nthe `Render(path string)` function like so:\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards:testboard\"\n```\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"V43B1waFxhzheW9TfmCpjLdrC4dC1yjUGES5y3J6QsNar6hRpNz4G1thzWmWK7xXhg8u1PCIpxLxGczKQYhuPw=="}],"memo":""}} {"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","NFT example","NFT's are all the rage these days, for various reasons.\n\nI read over EIP-721 which appears to be the de-facto NFT standard on Ethereum. Then, made a sample implementation of EIP-721 (let's here called GRC-721). The implementation isn't complete, but it demonstrates the main functionality.\n\n - [EIP-721](https://eips.ethereum.org/EIPS/eip-721)\n - [gno.land/r/demo/nft/nft.gno](https://gno.land/r/demo/nft/nft.gno)\n - [zrealm_nft3.gno test](https://github.com/gnolang/gno/blob/master/examples/gno.land/r/demo/nft/z_3_filetest.gno)\n\nIn short, this demonstrates how to implement Ethereum contract interfaces in gno.land; by using only standard Go language features.\n\nPlease leave a comment ([guide](https://gno.land/r/demo/boards:testboard/1)).\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"ZXfrTiHxPFQL8uSm+Tv7WXIHPMca9okhm94RAlC6YgNbB1VHQYYpoP4w+cnL3YskVzGrOZxensXa9CAZ+cNNeg=="}],"memo":""}} -{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","Simple echo example with coins","This is a simple test realm contract that demonstrates how to use the banker.\n\nSee [gno.land/r/demo/banktest/banktest.gno](/r/demo/banktest/banktest.gno) to see the original contract code.\n\nThis article will go through each line to explain how it works.\n\n```go\npackage banktest\n```\n\nThis package is locally named \"banktest\" (could be anything).\n\n```go\nimport (\n\t\"std\"\n)\n```\n\nThe \"std\" package is defined by the gno code in stdlibs/std/. \u003c/br\u003e\nSelf explanatory; and you'll see more usage from std later.\n\n```go\ntype activity struct {\n\tcaller std.Address\n\tsent std.Coins\n\treturned std.Coins\n\ttime std.Time\n}\n\nfunc (act *activity) String() string {\n\treturn act.caller.String() + \" \" +\n\t\tact.sent.String() + \" sent, \" +\n\t\tact.returned.String() + \" returned, at \" +\n\t\tstd.FormatTimestamp(act.time, \"2006-01-02 3:04pm MST\")\n}\n\nvar latest [10]*activity\n```\n\nThis is just maintaining a list of recent activity to this contract.\nNotice that the \"latest\" variable is defined \"globally\" within\nthe context of the realm with path \"gno.land/r/demo/banktest\".\n\nThis means that calls to functions defined within this package\nare encapsulated within this \"data realm\", where the data is \nmutated based on transactions that can potentially cross many\nrealm and non-realm packge boundaries (in the call stack).\n\n```go\n// Deposit will take the coins (to the realm's pkgaddr) or return them to user.\nfunc Deposit(returnDenom string, returnAmount int64) string {\n\tstd.AssertOriginCall()\n\tcaller := std.OriginCaller()\n\tsend := std.Coins{{returnDenom, returnAmount}}\n```\n\nThis is the beginning of the definition of the contract function named\n\"Deposit\". `std.AssertOriginCall() asserts that this function was called by a\ngno transactional Message. The caller is the user who signed off on this\ntransactional message. Send is the amount of deposit sent along with this\nmessage.\n\n```go\n\t// record activity\n\tact := \u0026activity{\n\t\tcaller: caller,\n\t\tsent: std.OriginSend(),\n\t\treturned: send,\n\t\ttime: std.GetTimestamp(),\n\t}\n\tfor i := len(latest) - 2; i \u003e= 0; i-- {\n\t\tlatest[i+1] = latest[i] // shift by +1.\n\t}\n\tlatest[0] = act\n```\n\nUpdating the \"latest\" array for viewing at gno.land/r/demo/banktest: (w/ trailing colon).\n\n```go\n\t// return if any.\n\tif returnAmount \u003e 0 {\n```\n\nIf the user requested the return of coins...\n\n```go\n\t\tbanker := std.GetBanker(std.BankerTypeOriginSend)\n```\n\nuse a std.Banker instance to return any deposited coins to the original sender.\n\n```go\n\t\tpkgaddr := std.GetOrigPkgAddr()\n\t\t// TODO: use std.Coins constructors, this isn't generally safe.\n\t\tbanker.SendCoins(pkgaddr, caller, send)\n\t\treturn \"returned!\"\n```\n\nNotice that each realm package has an associated Cosmos address.\n\n\nFinally, the results are rendered via an ABCI query call when you visit [/r/demo/banktest:](/r/demo/banktest:).\n\n```go\nfunc Render(path string) string {\n\t// get realm coins.\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tcoins := banker.GetCoins(std.GetOrigPkgAddr())\n\n\t// render\n\tres := \"\"\n\tres += \"## recent activity\\n\"\n\tres += \"\\n\"\n\tfor _, act := range latest {\n\t\tif act == nil {\n\t\t\tbreak\n\t\t}\n\t\tres += \" * \" + act.String() + \"\\n\"\n\t}\n\tres += \"\\n\"\n\tres += \"## total deposits\\n\"\n\tres += coins.String()\n\treturn res\n}\n```\n\nYou can call this contract yourself, by vistiing [/r/demo/banktest](/r/demo/banktest) and the [quickstart guide](/r/demo/boards:testboard/4).\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"iZX/llZlNTdZMLv1goCTgK2bWqzT8enlTq56wMTCpVxJGA0BTvuEM5Nnt9vrnlG6Taqj2GuTrmEnJBkDFTmt9g=="}],"memo":""}} +{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","Simple echo example with coins","This is a simple test realm contract that demonstrates how to use the banker.\n\nSee [gno.land/r/demo/banktest/banktest.gno](/r/demo/banktest/banktest.gno) to see the original contract code.\n\nThis article will go through each line to explain how it works.\n\n```go\npackage banktest\n```\n\nThis package is locally named \"banktest\" (could be anything).\n\n```go\nimport (\n\t\"std\"\n)\n```\n\nThe \"std\" package is defined by the gno code in stdlibs/std/. \u003c/br\u003e\nSelf explanatory; and you'll see more usage from std later.\n\n```go\ntype activity struct {\n\tcaller std.Address\n\tsent std.Coins\n\treturned std.Coins\n\ttime std.Time\n}\n\nfunc (act *activity) String() string {\n\treturn act.caller.String() + \" \" +\n\t\tact.sent.String() + \" sent, \" +\n\t\tact.returned.String() + \" returned, at \" +\n\t\tstd.FormatTimestamp(act.time, \"2006-01-02 3:04pm MST\")\n}\n\nvar latest [10]*activity\n```\n\nThis is just maintaining a list of recent activity to this contract.\nNotice that the \"latest\" variable is defined \"globally\" within\nthe context of the realm with path \"gno.land/r/demo/banktest\".\n\nThis means that calls to functions defined within this package\nare encapsulated within this \"data realm\", where the data is \nmutated based on transactions that can potentially cross many\nrealm and non-realm packge boundaries (in the call stack).\n\n```go\n// Deposit will take the coins (to the realm's pkgaddr) or return them to user.\nfunc Deposit(returnDenom string, returnAmount int64) string {\n\tstd.AssertOriginCall()\n\tcaller := std.OriginCaller()\n\tsend := std.Coins{{returnDenom, returnAmount}}\n```\n\nThis is the beginning of the definition of the contract function named\n\"Deposit\". `std.AssertOriginCall() asserts that this function was called by a\ngno transactional Message. The caller is the user who signed off on this\ntransactional message. Send is the amount of deposit sent along with this\nmessage.\n\n```go\n\t// record activity\n\tact := \u0026activity{\n\t\tcaller: caller,\n\t\tsent: std.OriginSend(),\n\t\treturned: send,\n\t\ttime: std.GetTimestamp(),\n\t}\n\tfor i := len(latest) - 2; i \u003e= 0; i-- {\n\t\tlatest[i+1] = latest[i] // shift by +1.\n\t}\n\tlatest[0] = act\n```\n\nUpdating the \"latest\" array for viewing at gno.land/r/demo/banktest: (w/ trailing colon).\n\n```go\n\t// return if any.\n\tif returnAmount \u003e 0 {\n```\n\nIf the user requested the return of coins...\n\n```go\n\t\tbanker := std.GetBanker(std.BankerTypeOriginSend)\n```\n\nuse a std.Banker instance to return any deposited coins to the original sender.\n\n```go\n\t\tpkgaddr := std.GetOriginPkgAddr()\n\t\t// TODO: use std.Coins constructors, this isn't generally safe.\n\t\tbanker.SendCoins(pkgaddr, caller, send)\n\t\treturn \"returned!\"\n```\n\nNotice that each realm package has an associated Cosmos address.\n\n\nFinally, the results are rendered via an ABCI query call when you visit [/r/demo/banktest:](/r/demo/banktest:).\n\n```go\nfunc Render(path string) string {\n\t// get realm coins.\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tcoins := banker.GetCoins(std.GetOriginPkgAddr())\n\n\t// render\n\tres := \"\"\n\tres += \"## recent activity\\n\"\n\tres += \"\\n\"\n\tfor _, act := range latest {\n\t\tif act == nil {\n\t\t\tbreak\n\t\t}\n\t\tres += \" * \" + act.String() + \"\\n\"\n\t}\n\tres += \"\\n\"\n\tres += \"## total deposits\\n\"\n\tres += coins.String()\n\treturn res\n}\n```\n\nYou can call this contract yourself, by vistiing [/r/demo/banktest](/r/demo/banktest) and the [quickstart guide](/r/demo/boards:testboard/4).\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"iZX/llZlNTdZMLv1goCTgK2bWqzT8enlTq56wMTCpVxJGA0BTvuEM5Nnt9vrnlG6Taqj2GuTrmEnJBkDFTmt9g=="}],"memo":""}} {"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","TASK: Describe in your words","Describe in an essay (250+ words), on your favorite medium, why you are interested in gno.land and gnolang.\n\nReply here with a URL link to your written piece as a comment, for rewards.\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AmG6kzznyo1uNqWPAYU6wDpsmzQKDaEOrVRaZ08vOyX0"},"signature":"4HBNtrta8HdeHj4JTN56PBTRK8GOe31NMRRXDiyYtjozuyRdWfOGEsGjGgHWcoBUJq6DepBgD4FetdqfhZ6TNQ=="}],"memo":""}} {"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1manfred47kzduec920z88wfr64ylksmdcedlf5","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","Getting Started","This is a demo of Gno smart contract programming. This document was\nconstructed by Gno onto a smart contract hosted on the data Realm\nname [\"gno.land/r/demo/boards\"](https://gno.land/r/demo/boards/)\n([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)).\n\n\n\n## Build `gnokey`, create your account, and interact with Gno.\n\nNOTE: Where you see `--remote %%REMOTE%%` here, that flag can be replaced\nwith `--remote localhost:26657` for local testnets.\n\n### Build `gnokey`.\n\n```bash\ngit clone git@github.com:gnolang/gno.git\ncd ./gno\nmake\n```\n\n### Generate a seed/mnemonic code.\n\n```bash\n./build/gnokey generate\n```\n\nNOTE: You can generate 24 words with any good bip39 generator.\n\n### Create a new account using your mnemonic.\n\n```bash\n./build/gnokey add KEYNAME --recover\n```\n\nNOTE: `KEYNAME` is your key identifier, and should be changed.\n\n### Verify that you can see your account locally.\n\n```bash\n./build/gnokey list\n```\n\n## Interact with the blockchain:\n\n### Get your current balance, account number, and sequence number.\n\n```bash\n./build/gnokey query auth/accounts/ACCOUNT_ADDR --remote %%REMOTE%%\n```\n\nNOTE: you can retrieve your `ACCOUNT_ADDR` with `./build/gnokey list`.\n\n### Acquire testnet tokens using the official faucet.\n\nGo to https://gno.land/faucet\n\n### Create a board with a smart contract call.\n\nNOTE: `BOARDNAME` will be the slug of the board, and should be changed.\n\n```bash\n./build/gnokey maketx call KEYNAME --pkgpath \"gno.land/r/demo/boards\" --func \"CreateBoard\" --args \"BOARDNAME\" --gas-fee \"1000000ugnot\" --gas-wanted \"2000000\" --broadcast=true --chainid %%CHAINID%% --remote %%REMOTE%%\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateBoard\n\nNext, query for the permanent board ID by querying (you need this to create a new post):\n\n```bash\n./build/gnokey query \"vm/qeval\" --data \"gno.land/r/demo/boards.GetBoardIDFromName(\\\"BOARDNAME\\\")\" --remote %%REMOTE%%\n```\n\n### Create a post of a board with a smart contract call.\n\nNOTE: If a board was created successfully, your SEQUENCE_NUMBER would have increased.\n\n```bash\n./build/gnokey maketx call KEYNAME --pkgpath \"gno.land/r/demo/boards\" --func \"CreateThread\" --args BOARD_ID --args \"Hello gno.land\" --args\\#file \"./examples/gno.land/r/demo/boards/example_post.md\" --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast=true --chainid %%CHAINID%% --remote %%REMOTE%%\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateThread\n\n### Create a comment to a post.\n\n```bash\n./build/gnokey maketx call KEYNAME --pkgpath \"gno.land/r/demo/boards\" --func \"CreateReply\" --args \"BOARD_ID\" --args \"1\" --args \"1\" --args \"Nice to meet you too.\" --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast=true --chainid %%CHAINID%% --remote %%REMOTE%%\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateReply\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards:BOARDNAME/1\" --remote %%REMOTE%%\n```\n\n### Render page with optional path expression.\n\nThe contents of `https://gno.land/r/demo/boards:` and `https://gno.land/r/demo/boards:gnolang` are rendered by calling\nthe `Render(path string)` function like so:\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards:gnolang\"\n```\n\n## Starting a local `gnoland` node:\n\n### Add test account.\n\n```bash\n./build/gnokey add test1 --recover\n```\n\nUse this mneonic:\n\u003e source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast\n\n### Start `gnoland` node.\n\n```bash\n./build/gnoland\n```\n\nNOTE: This can be reset with `make reset`\n\n### Publish the \"gno.land/p/demo/avl\" package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/p/demo/avl\" --pkgdir \"examples/gno.land/p/demo/avl\" --deposit 100000000ugnot --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast=true --chainid %%CHAINID%% --remote localhost:26657\n```\n\n### Publish the \"gno.land/r/demo/boards\" realm package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/r/demo/boards\" --pkgdir \"examples/gno.land/r/demo/boards\" --deposit 100000000ugnot --gas-fee 1000000ugnot --gas-wanted 300000000 --broadcast=true --chainid %%CHAINID%% --remote localhost:26657\n```\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AnK+a6mcFDjY6b/v6p7r8QFW1M1PgIoQxBgrwOoyY7v3"},"signature":"sHjOGXZEi9wt2FSXFHmkDDoVQyepvFHKRDDU0zgedHYnCYPx5/YndyihsDD5Y2Z7/RgNYBh4JlJwDMGFNStzBQ=="}],"memo":""}} {"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1manfred47kzduec920z88wfr64ylksmdcedlf5","send":"","pkg_path":"gno.land/r/gnoland/blog","func":"ModAddPost","args":["post1","First post","Lorem Ipsum","2022-05-20T13:17:22Z","","tag1,tag2"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AnK+a6mcFDjY6b/v6p7r8QFW1M1PgIoQxBgrwOoyY7v3"},"signature":"sHjOGXZEi9wt2FSXFHmkDDoVQyepvFHKRDDU0zgedHYnCYPx5/YndyihsDD5Y2Z7/RgNYBh4JlJwDMGFNStzBQ=="}],"memo":""}} diff --git a/gno.land/pkg/integration/testdata/issue_2283.txtar b/gno.land/pkg/integration/testdata/issue_2283.txtar index e579a864c40..e53a8cc1a31 100644 --- a/gno.land/pkg/integration/testdata/issue_2283.txtar +++ b/gno.land/pkg/integration/testdata/issue_2283.txtar @@ -66,7 +66,7 @@ import ( }, { "Name": "post.gno", - "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n)\n\ntype PostID uint64\n\nfunc (pid PostID) String() string {\n\treturn strconv.Itoa(int(pid))\n}\n\nfunc (pid *PostID) FromJSON(ast *ujson.JSONASTNode) {\n\tval, err := strconv.Atoi(ast.Value)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t*pid = PostID(val)\n}\n\nfunc (pid PostID) ToJSON() string {\n\treturn strconv.Itoa(int(pid))\n}\n\ntype Reaction struct {\n\ticon string\n\tcount uint64\n}\n\nvar Categories []string = []string{\n\t\"Reaction\",\n\t\"Comment\",\n\t\"Normal\",\n\t\"Article\",\n\t\"Picture\",\n\t\"Audio\",\n\t\"Video\",\n}\n\ntype Post struct {\n\tid PostID\n\tparentID PostID\n\tfeedID FeedID\n\tcategory uint64\n\tmetadata string\n\treactions avl.Tree // icon -> count\n\tcomments avl.Tree // Post.id -> *Post\n\tcreator std.Address\n\ttipAmount uint64\n\tdeleted bool\n\tcommentsCount uint64\n\n\tcreatedAt int64\n\tupdatedAt int64\n\tdeletedAt int64\n}\n\nfunc newPost(feed *Feed, id PostID, creator std.Address, parentID PostID, category uint64, metadata string) *Post {\n\treturn &Post{\n\t\tid: id,\n\t\tparentID: parentID,\n\t\tfeedID: feed.id,\n\t\tcategory: category,\n\t\tmetadata: metadata,\n\t\treactions: avl.Tree{},\n\t\tcreator: creator,\n\t\tcreatedAt: time.Now().Unix(),\n\t}\n}\n\nfunc (post *Post) String() string {\n\treturn post.ToJSON()\n}\n\nfunc (post *Post) Update(category uint64, metadata string) {\n\tpost.category = category\n\tpost.metadata = metadata\n\tpost.updatedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Delete() {\n\tpost.deleted = true\n\tpost.deletedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Tip(from std.Address, to std.Address) {\n\treceivedCoins := std.OriginSend()\n\tamount := receivedCoins[0].Amount\n\n\tbanker := std.GetBanker(std.BankerTypeOriginSend)\n\t// banker := std.GetBanker(std.BankerTypeRealmSend)\n\tcoinsToSend := std.Coins{std.Coin{Denom: \"ugnot\", Amount: amount}}\n\tpkgaddr := std.GetOrigPkgAddr()\n\n\tbanker.SendCoins(pkgaddr, to, coinsToSend)\n\n\t// Update tip amount\n\tpost.tipAmount += uint64(amount)\n}\n\n// Always remove reaction if count = 0\nfunc (post *Post) React(icon string, up bool) {\n\tcount_, ok := post.reactions.Get(icon)\n\tcount := 0\n\n\tif ok {\n\t\tcount = count_.(int)\n\t}\n\n\tif up {\n\t\tcount++\n\t} else {\n\t\tcount--\n\t}\n\n\tif count <= 0 {\n\t\tpost.reactions.Remove(icon)\n\t} else {\n\t\tpost.reactions.Set(icon, count)\n\t}\n}\n\nfunc (post *Post) Render() string {\n\treturn post.metadata\n}\n\nfunc (post *Post) FromJSON(jsonData string) {\n\tast := ujson.TokenizeAndParse(jsonData)\n\tast.ParseObject([]*ujson.ParseKV{\n\t\t{Key: \"id\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.id = PostID(pid)\n\t\t}},\n\t\t{Key: \"parentID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.parentID = PostID(pid)\n\t\t}},\n\t\t{Key: \"feedID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tfid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.feedID = FeedID(fid)\n\t\t}},\n\t\t{Key: \"category\", Value: &post.category},\n\t\t{Key: \"metadata\", Value: &post.metadata},\n\t\t{Key: \"reactions\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\treactions := avl.NewTree()\n\t\t\tfor _, child := range node.ObjectChildren {\n\t\t\t\treactionCount := child.Value\n\t\t\t\treactions.Set(child.Key, reactionCount)\n\t\t\t}\n\t\t\tpost.reactions = *reactions\n\t\t}},\n\t\t{Key: \"commentsCount\", Value: &post.commentsCount},\n\t\t{Key: \"creator\", Value: &post.creator},\n\t\t{Key: \"tipAmount\", Value: &post.tipAmount},\n\t\t{Key: \"deleted\", Value: &post.deleted},\n\t\t{Key: \"createdAt\", Value: &post.createdAt},\n\t\t{Key: \"updatedAt\", Value: &post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: &post.deletedAt},\n\t})\n}\n\nfunc (post *Post) ToJSON() string {\n\treactionsKV := []ujson.FormatKV{}\n\tpost.reactions.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tcount := value.(int)\n\t\tdata := ujson.FormatKV{Key: key, Value: count}\n\t\treactionsKV = append(reactionsKV, data)\n\t\treturn false\n\t})\n\treactions := ujson.FormatObject(reactionsKV)\n\n\tpostJSON := ujson.FormatObject([]ujson.FormatKV{\n\t\t{Key: \"id\", Value: uint64(post.id)},\n\t\t{Key: \"parentID\", Value: uint64(post.parentID)},\n\t\t{Key: \"feedID\", Value: uint64(post.feedID)},\n\t\t{Key: \"category\", Value: post.category},\n\t\t{Key: \"metadata\", Value: post.metadata},\n\t\t{Key: \"reactions\", Value: reactions, Raw: true},\n\t\t{Key: \"creator\", Value: post.creator},\n\t\t{Key: \"tipAmount\", Value: post.tipAmount},\n\t\t{Key: \"deleted\", Value: post.deleted},\n\t\t{Key: \"commentsCount\", Value: post.commentsCount},\n\t\t{Key: \"createdAt\", Value: post.createdAt},\n\t\t{Key: \"updatedAt\", Value: post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: post.deletedAt},\n\t})\n\treturn postJSON\n}\n" + "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n)\n\ntype PostID uint64\n\nfunc (pid PostID) String() string {\n\treturn strconv.Itoa(int(pid))\n}\n\nfunc (pid *PostID) FromJSON(ast *ujson.JSONASTNode) {\n\tval, err := strconv.Atoi(ast.Value)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t*pid = PostID(val)\n}\n\nfunc (pid PostID) ToJSON() string {\n\treturn strconv.Itoa(int(pid))\n}\n\ntype Reaction struct {\n\ticon string\n\tcount uint64\n}\n\nvar Categories []string = []string{\n\t\"Reaction\",\n\t\"Comment\",\n\t\"Normal\",\n\t\"Article\",\n\t\"Picture\",\n\t\"Audio\",\n\t\"Video\",\n}\n\ntype Post struct {\n\tid PostID\n\tparentID PostID\n\tfeedID FeedID\n\tcategory uint64\n\tmetadata string\n\treactions avl.Tree // icon -> count\n\tcomments avl.Tree // Post.id -> *Post\n\tcreator std.Address\n\ttipAmount uint64\n\tdeleted bool\n\tcommentsCount uint64\n\n\tcreatedAt int64\n\tupdatedAt int64\n\tdeletedAt int64\n}\n\nfunc newPost(feed *Feed, id PostID, creator std.Address, parentID PostID, category uint64, metadata string) *Post {\n\treturn &Post{\n\t\tid: id,\n\t\tparentID: parentID,\n\t\tfeedID: feed.id,\n\t\tcategory: category,\n\t\tmetadata: metadata,\n\t\treactions: avl.Tree{},\n\t\tcreator: creator,\n\t\tcreatedAt: time.Now().Unix(),\n\t}\n}\n\nfunc (post *Post) String() string {\n\treturn post.ToJSON()\n}\n\nfunc (post *Post) Update(category uint64, metadata string) {\n\tpost.category = category\n\tpost.metadata = metadata\n\tpost.updatedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Delete() {\n\tpost.deleted = true\n\tpost.deletedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Tip(from std.Address, to std.Address) {\n\treceivedCoins := std.OriginSend()\n\tamount := receivedCoins[0].Amount\n\n\tbanker := std.GetBanker(std.BankerTypeOriginSend)\n\t// banker := std.GetBanker(std.BankerTypeRealmSend)\n\tcoinsToSend := std.Coins{std.Coin{Denom: \"ugnot\", Amount: amount}}\n\tpkgaddr := std.GetOriginPkgAddr()\n\n\tbanker.SendCoins(pkgaddr, to, coinsToSend)\n\n\t// Update tip amount\n\tpost.tipAmount += uint64(amount)\n}\n\n// Always remove reaction if count = 0\nfunc (post *Post) React(icon string, up bool) {\n\tcount_, ok := post.reactions.Get(icon)\n\tcount := 0\n\n\tif ok {\n\t\tcount = count_.(int)\n\t}\n\n\tif up {\n\t\tcount++\n\t} else {\n\t\tcount--\n\t}\n\n\tif count <= 0 {\n\t\tpost.reactions.Remove(icon)\n\t} else {\n\t\tpost.reactions.Set(icon, count)\n\t}\n}\n\nfunc (post *Post) Render() string {\n\treturn post.metadata\n}\n\nfunc (post *Post) FromJSON(jsonData string) {\n\tast := ujson.TokenizeAndParse(jsonData)\n\tast.ParseObject([]*ujson.ParseKV{\n\t\t{Key: \"id\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.id = PostID(pid)\n\t\t}},\n\t\t{Key: \"parentID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.parentID = PostID(pid)\n\t\t}},\n\t\t{Key: \"feedID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tfid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.feedID = FeedID(fid)\n\t\t}},\n\t\t{Key: \"category\", Value: &post.category},\n\t\t{Key: \"metadata\", Value: &post.metadata},\n\t\t{Key: \"reactions\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\treactions := avl.NewTree()\n\t\t\tfor _, child := range node.ObjectChildren {\n\t\t\t\treactionCount := child.Value\n\t\t\t\treactions.Set(child.Key, reactionCount)\n\t\t\t}\n\t\t\tpost.reactions = *reactions\n\t\t}},\n\t\t{Key: \"commentsCount\", Value: &post.commentsCount},\n\t\t{Key: \"creator\", Value: &post.creator},\n\t\t{Key: \"tipAmount\", Value: &post.tipAmount},\n\t\t{Key: \"deleted\", Value: &post.deleted},\n\t\t{Key: \"createdAt\", Value: &post.createdAt},\n\t\t{Key: \"updatedAt\", Value: &post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: &post.deletedAt},\n\t})\n}\n\nfunc (post *Post) ToJSON() string {\n\treactionsKV := []ujson.FormatKV{}\n\tpost.reactions.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tcount := value.(int)\n\t\tdata := ujson.FormatKV{Key: key, Value: count}\n\t\treactionsKV = append(reactionsKV, data)\n\t\treturn false\n\t})\n\treactions := ujson.FormatObject(reactionsKV)\n\n\tpostJSON := ujson.FormatObject([]ujson.FormatKV{\n\t\t{Key: \"id\", Value: uint64(post.id)},\n\t\t{Key: \"parentID\", Value: uint64(post.parentID)},\n\t\t{Key: \"feedID\", Value: uint64(post.feedID)},\n\t\t{Key: \"category\", Value: post.category},\n\t\t{Key: \"metadata\", Value: post.metadata},\n\t\t{Key: \"reactions\", Value: reactions, Raw: true},\n\t\t{Key: \"creator\", Value: post.creator},\n\t\t{Key: \"tipAmount\", Value: post.tipAmount},\n\t\t{Key: \"deleted\", Value: post.deleted},\n\t\t{Key: \"commentsCount\", Value: post.commentsCount},\n\t\t{Key: \"createdAt\", Value: post.createdAt},\n\t\t{Key: \"updatedAt\", Value: post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: post.deletedAt},\n\t})\n\treturn postJSON\n}\n" }, { "Name": "public.gno", diff --git a/gno.land/pkg/integration/testdata/issue_2283_cacheTypes.txtar b/gno.land/pkg/integration/testdata/issue_2283_cacheTypes.txtar index 0de65b58852..00359f0caf3 100644 --- a/gno.land/pkg/integration/testdata/issue_2283_cacheTypes.txtar +++ b/gno.land/pkg/integration/testdata/issue_2283_cacheTypes.txtar @@ -60,7 +60,7 @@ stdout OK! }, { "Name": "post.gno", - "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n)\n\ntype PostID uint64\n\nfunc (pid PostID) String() string {\n\treturn strconv.Itoa(int(pid))\n}\n\nfunc (pid *PostID) FromJSON(ast *ujson.JSONASTNode) {\n\tval, err := strconv.Atoi(ast.Value)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t*pid = PostID(val)\n}\n\nfunc (pid PostID) ToJSON() string {\n\treturn strconv.Itoa(int(pid))\n}\n\ntype Reaction struct {\n\ticon string\n\tcount uint64\n}\n\nvar Categories []string = []string{\n\t\"Reaction\",\n\t\"Comment\",\n\t\"Normal\",\n\t\"Article\",\n\t\"Picture\",\n\t\"Audio\",\n\t\"Video\",\n}\n\ntype Post struct {\n\tid PostID\n\tparentID PostID\n\tfeedID FeedID\n\tcategory uint64\n\tmetadata string\n\treactions avl.Tree // icon -> count\n\tcomments avl.Tree // Post.id -> *Post\n\tcreator std.Address\n\ttipAmount uint64\n\tdeleted bool\n\tcommentsCount uint64\n\n\tcreatedAt int64\n\tupdatedAt int64\n\tdeletedAt int64\n}\n\nfunc newPost(feed *Feed, id PostID, creator std.Address, parentID PostID, category uint64, metadata string) *Post {\n\treturn &Post{\n\t\tid: id,\n\t\tparentID: parentID,\n\t\tfeedID: feed.id,\n\t\tcategory: category,\n\t\tmetadata: metadata,\n\t\treactions: avl.Tree{},\n\t\tcreator: creator,\n\t\tcreatedAt: time.Now().Unix(),\n\t}\n}\n\nfunc (post *Post) String() string {\n\treturn post.ToJSON()\n}\n\nfunc (post *Post) Update(category uint64, metadata string) {\n\tpost.category = category\n\tpost.metadata = metadata\n\tpost.updatedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Delete() {\n\tpost.deleted = true\n\tpost.deletedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Tip(from std.Address, to std.Address) {\n\treceivedCoins := std.OriginSend()\n\tamount := receivedCoins[0].Amount\n\n\tbanker := std.GetBanker(std.BankerTypeOriginSend)\n\t// banker := std.GetBanker(std.BankerTypeRealmSend)\n\tcoinsToSend := std.Coins{std.Coin{Denom: \"ugnot\", Amount: amount}}\n\tpkgaddr := std.GetOrigPkgAddr()\n\n\tbanker.SendCoins(pkgaddr, to, coinsToSend)\n\n\t// Update tip amount\n\tpost.tipAmount += uint64(amount)\n}\n\n// Always remove reaction if count = 0\nfunc (post *Post) React(icon string, up bool) {\n\tcount_, ok := post.reactions.Get(icon)\n\tcount := 0\n\n\tif ok {\n\t\tcount = count_.(int)\n\t}\n\n\tif up {\n\t\tcount++\n\t} else {\n\t\tcount--\n\t}\n\n\tif count <= 0 {\n\t\tpost.reactions.Remove(icon)\n\t} else {\n\t\tpost.reactions.Set(icon, count)\n\t}\n}\n\nfunc (post *Post) Render() string {\n\treturn post.metadata\n}\n\nfunc (post *Post) FromJSON(jsonData string) {\n\tast := ujson.TokenizeAndParse(jsonData)\n\tast.ParseObject([]*ujson.ParseKV{\n\t\t{Key: \"id\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.id = PostID(pid)\n\t\t}},\n\t\t{Key: \"parentID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.parentID = PostID(pid)\n\t\t}},\n\t\t{Key: \"feedID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tfid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.feedID = FeedID(fid)\n\t\t}},\n\t\t{Key: \"category\", Value: &post.category},\n\t\t{Key: \"metadata\", Value: &post.metadata},\n\t\t{Key: \"reactions\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\treactions := avl.NewTree()\n\t\t\tfor _, child := range node.ObjectChildren {\n\t\t\t\treactionCount := child.Value\n\t\t\t\treactions.Set(child.Key, reactionCount)\n\t\t\t}\n\t\t\tpost.reactions = *reactions\n\t\t}},\n\t\t{Key: \"commentsCount\", Value: &post.commentsCount},\n\t\t{Key: \"creator\", Value: &post.creator},\n\t\t{Key: \"tipAmount\", Value: &post.tipAmount},\n\t\t{Key: \"deleted\", Value: &post.deleted},\n\t\t{Key: \"createdAt\", Value: &post.createdAt},\n\t\t{Key: \"updatedAt\", Value: &post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: &post.deletedAt},\n\t})\n}\n\nfunc (post *Post) ToJSON() string {\n\treactionsKV := []ujson.FormatKV{}\n\tpost.reactions.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tcount := value.(int)\n\t\tdata := ujson.FormatKV{Key: key, Value: count}\n\t\treactionsKV = append(reactionsKV, data)\n\t\treturn false\n\t})\n\treactions := ujson.FormatObject(reactionsKV)\n\n\tpostJSON := ujson.FormatObject([]ujson.FormatKV{\n\t\t{Key: \"id\", Value: uint64(post.id)},\n\t\t{Key: \"parentID\", Value: uint64(post.parentID)},\n\t\t{Key: \"feedID\", Value: uint64(post.feedID)},\n\t\t{Key: \"category\", Value: post.category},\n\t\t{Key: \"metadata\", Value: post.metadata},\n\t\t{Key: \"reactions\", Value: reactions, Raw: true},\n\t\t{Key: \"creator\", Value: post.creator},\n\t\t{Key: \"tipAmount\", Value: post.tipAmount},\n\t\t{Key: \"deleted\", Value: post.deleted},\n\t\t{Key: \"commentsCount\", Value: post.commentsCount},\n\t\t{Key: \"createdAt\", Value: post.createdAt},\n\t\t{Key: \"updatedAt\", Value: post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: post.deletedAt},\n\t})\n\treturn postJSON\n}\n" + "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n)\n\ntype PostID uint64\n\nfunc (pid PostID) String() string {\n\treturn strconv.Itoa(int(pid))\n}\n\nfunc (pid *PostID) FromJSON(ast *ujson.JSONASTNode) {\n\tval, err := strconv.Atoi(ast.Value)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t*pid = PostID(val)\n}\n\nfunc (pid PostID) ToJSON() string {\n\treturn strconv.Itoa(int(pid))\n}\n\ntype Reaction struct {\n\ticon string\n\tcount uint64\n}\n\nvar Categories []string = []string{\n\t\"Reaction\",\n\t\"Comment\",\n\t\"Normal\",\n\t\"Article\",\n\t\"Picture\",\n\t\"Audio\",\n\t\"Video\",\n}\n\ntype Post struct {\n\tid PostID\n\tparentID PostID\n\tfeedID FeedID\n\tcategory uint64\n\tmetadata string\n\treactions avl.Tree // icon -> count\n\tcomments avl.Tree // Post.id -> *Post\n\tcreator std.Address\n\ttipAmount uint64\n\tdeleted bool\n\tcommentsCount uint64\n\n\tcreatedAt int64\n\tupdatedAt int64\n\tdeletedAt int64\n}\n\nfunc newPost(feed *Feed, id PostID, creator std.Address, parentID PostID, category uint64, metadata string) *Post {\n\treturn &Post{\n\t\tid: id,\n\t\tparentID: parentID,\n\t\tfeedID: feed.id,\n\t\tcategory: category,\n\t\tmetadata: metadata,\n\t\treactions: avl.Tree{},\n\t\tcreator: creator,\n\t\tcreatedAt: time.Now().Unix(),\n\t}\n}\n\nfunc (post *Post) String() string {\n\treturn post.ToJSON()\n}\n\nfunc (post *Post) Update(category uint64, metadata string) {\n\tpost.category = category\n\tpost.metadata = metadata\n\tpost.updatedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Delete() {\n\tpost.deleted = true\n\tpost.deletedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Tip(from std.Address, to std.Address) {\n\treceivedCoins := std.OriginSend()\n\tamount := receivedCoins[0].Amount\n\n\tbanker := std.GetBanker(std.BankerTypeOriginSend)\n\t// banker := std.GetBanker(std.BankerTypeRealmSend)\n\tcoinsToSend := std.Coins{std.Coin{Denom: \"ugnot\", Amount: amount}}\n\tpkgaddr := std.GetOriginPkgAddr()\n\n\tbanker.SendCoins(pkgaddr, to, coinsToSend)\n\n\t// Update tip amount\n\tpost.tipAmount += uint64(amount)\n}\n\n// Always remove reaction if count = 0\nfunc (post *Post) React(icon string, up bool) {\n\tcount_, ok := post.reactions.Get(icon)\n\tcount := 0\n\n\tif ok {\n\t\tcount = count_.(int)\n\t}\n\n\tif up {\n\t\tcount++\n\t} else {\n\t\tcount--\n\t}\n\n\tif count <= 0 {\n\t\tpost.reactions.Remove(icon)\n\t} else {\n\t\tpost.reactions.Set(icon, count)\n\t}\n}\n\nfunc (post *Post) Render() string {\n\treturn post.metadata\n}\n\nfunc (post *Post) FromJSON(jsonData string) {\n\tast := ujson.TokenizeAndParse(jsonData)\n\tast.ParseObject([]*ujson.ParseKV{\n\t\t{Key: \"id\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.id = PostID(pid)\n\t\t}},\n\t\t{Key: \"parentID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.parentID = PostID(pid)\n\t\t}},\n\t\t{Key: \"feedID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tfid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.feedID = FeedID(fid)\n\t\t}},\n\t\t{Key: \"category\", Value: &post.category},\n\t\t{Key: \"metadata\", Value: &post.metadata},\n\t\t{Key: \"reactions\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\treactions := avl.NewTree()\n\t\t\tfor _, child := range node.ObjectChildren {\n\t\t\t\treactionCount := child.Value\n\t\t\t\treactions.Set(child.Key, reactionCount)\n\t\t\t}\n\t\t\tpost.reactions = *reactions\n\t\t}},\n\t\t{Key: \"commentsCount\", Value: &post.commentsCount},\n\t\t{Key: \"creator\", Value: &post.creator},\n\t\t{Key: \"tipAmount\", Value: &post.tipAmount},\n\t\t{Key: \"deleted\", Value: &post.deleted},\n\t\t{Key: \"createdAt\", Value: &post.createdAt},\n\t\t{Key: \"updatedAt\", Value: &post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: &post.deletedAt},\n\t})\n}\n\nfunc (post *Post) ToJSON() string {\n\treactionsKV := []ujson.FormatKV{}\n\tpost.reactions.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tcount := value.(int)\n\t\tdata := ujson.FormatKV{Key: key, Value: count}\n\t\treactionsKV = append(reactionsKV, data)\n\t\treturn false\n\t})\n\treactions := ujson.FormatObject(reactionsKV)\n\n\tpostJSON := ujson.FormatObject([]ujson.FormatKV{\n\t\t{Key: \"id\", Value: uint64(post.id)},\n\t\t{Key: \"parentID\", Value: uint64(post.parentID)},\n\t\t{Key: \"feedID\", Value: uint64(post.feedID)},\n\t\t{Key: \"category\", Value: post.category},\n\t\t{Key: \"metadata\", Value: post.metadata},\n\t\t{Key: \"reactions\", Value: reactions, Raw: true},\n\t\t{Key: \"creator\", Value: post.creator},\n\t\t{Key: \"tipAmount\", Value: post.tipAmount},\n\t\t{Key: \"deleted\", Value: post.deleted},\n\t\t{Key: \"commentsCount\", Value: post.commentsCount},\n\t\t{Key: \"createdAt\", Value: post.createdAt},\n\t\t{Key: \"updatedAt\", Value: post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: post.deletedAt},\n\t})\n\treturn postJSON\n}\n" }, { "Name": "public.gno", diff --git a/gno.land/pkg/sdk/vm/keeper.go b/gno.land/pkg/sdk/vm/keeper.go index 0da8987dae2..08b88a143e3 100644 --- a/gno.land/pkg/sdk/vm/keeper.go +++ b/gno.land/pkg/sdk/vm/keeper.go @@ -271,7 +271,7 @@ func (vm *VMKeeper) checkNamespacePermission(ctx sdk.Context, creator crypto.Add Timestamp: ctx.BlockTime().Unix(), OriginCaller: creator.Bech32(), OriginSendSpent: new(std.Coins), - OrigPkgAddr: pkgAddr.Bech32(), + OriginPkgAddr: pkgAddr.Bech32(), // XXX: should we remove the banker ? Banker: NewSDKBanker(vm, ctx), Params: NewSDKParams(vm, ctx), @@ -377,7 +377,7 @@ func (vm *VMKeeper) AddPackage(ctx sdk.Context, msg MsgAddPackage) (err error) { OriginCaller: creator.Bech32(), OriginSend: deposit, OriginSendSpent: new(std.Coins), - OrigPkgAddr: pkgAddr.Bech32(), + OriginPkgAddr: pkgAddr.Bech32(), Banker: NewSDKBanker(vm, ctx), Params: NewSDKParams(vm, ctx), EventLogger: ctx.EventLogger(), @@ -468,7 +468,7 @@ func (vm *VMKeeper) Call(ctx sdk.Context, msg MsgCall) (res string, err error) { OriginCaller: caller.Bech32(), OriginSend: send, OriginSendSpent: new(std.Coins), - OrigPkgAddr: pkgAddr.Bech32(), + OriginPkgAddr: pkgAddr.Bech32(), Banker: NewSDKBanker(vm, ctx), Params: NewSDKParams(vm, ctx), EventLogger: ctx.EventLogger(), @@ -583,7 +583,7 @@ func (vm *VMKeeper) Run(ctx sdk.Context, msg MsgRun) (res string, err error) { OriginCaller: caller.Bech32(), OriginSend: send, OriginSendSpent: new(std.Coins), - OrigPkgAddr: pkgAddr.Bech32(), + OriginPkgAddr: pkgAddr.Bech32(), Banker: NewSDKBanker(vm, ctx), Params: NewSDKParams(vm, ctx), EventLogger: ctx.EventLogger(), @@ -736,10 +736,10 @@ func (vm *VMKeeper) QueryEval(ctx sdk.Context, pkgPath string, expr string) (res // OriginCaller: caller, // OriginSend: send, // OriginSendSpent: nil, - OrigPkgAddr: pkgAddr.Bech32(), - Banker: NewSDKBanker(vm, ctx), // safe as long as ctx is a fork to be discarded. - Params: NewSDKParams(vm, ctx), - EventLogger: ctx.EventLogger(), + OriginPkgAddr: pkgAddr.Bech32(), + Banker: NewSDKBanker(vm, ctx), // safe as long as ctx is a fork to be discarded. + Params: NewSDKParams(vm, ctx), + EventLogger: ctx.EventLogger(), } m := gno.NewMachineWithOptions( gno.MachineOptions{ @@ -793,10 +793,10 @@ func (vm *VMKeeper) QueryEvalString(ctx sdk.Context, pkgPath string, expr string // OriginCaller: caller, // OriginSend: jsend, // OriginSendSpent: nil, - OrigPkgAddr: pkgAddr.Bech32(), - Banker: NewSDKBanker(vm, ctx), // safe as long as ctx is a fork to be discarded. - Params: NewSDKParams(vm, ctx), - EventLogger: ctx.EventLogger(), + OriginPkgAddr: pkgAddr.Bech32(), + Banker: NewSDKBanker(vm, ctx), // safe as long as ctx is a fork to be discarded. + Params: NewSDKParams(vm, ctx), + EventLogger: ctx.EventLogger(), } m := gno.NewMachineWithOptions( gno.MachineOptions{ diff --git a/gno.land/pkg/sdk/vm/keeper_test.go b/gno.land/pkg/sdk/vm/keeper_test.go index 15514b69ab1..0656818b2eb 100644 --- a/gno.land/pkg/sdk/vm/keeper_test.go +++ b/gno.land/pkg/sdk/vm/keeper_test.go @@ -129,7 +129,7 @@ func init() { func Echo(msg string) string { addr := std.OriginCaller() - pkgAddr := std.GetOrigPkgAddr() + pkgAddr := std.GetOriginPkgAddr() send := std.OriginSend() banker := std.GetBanker(std.BankerTypeOriginSend) banker.SendCoins(pkgAddr, addr, send) // send back @@ -177,7 +177,7 @@ func init() { func Echo(msg string) string { addr := std.OriginCaller() - pkgAddr := std.GetOrigPkgAddr() + pkgAddr := std.GetOriginPkgAddr() send := std.OriginSend() banker := std.GetBanker(std.BankerTypeOriginSend) banker.SendCoins(pkgAddr, addr, send) // send back @@ -228,7 +228,7 @@ func init() { func Echo(msg string) string { addr := std.OriginCaller() - pkgAddr := std.GetOrigPkgAddr() + pkgAddr := std.GetOriginPkgAddr() send := std.Coins{{"ugnot", 10000000}} banker := std.GetBanker(std.BankerTypeOriginSend) banker.SendCoins(pkgAddr, addr, send) // send back @@ -272,7 +272,7 @@ func init() { func Echo(msg string) string { addr := std.OriginCaller() - pkgAddr := std.GetOrigPkgAddr() + pkgAddr := std.GetOriginPkgAddr() send := std.Coins{{"ugnot", 10000000}} banker := std.GetBanker(std.BankerTypeRealmSend) banker.SendCoins(pkgAddr, addr, send) // send back @@ -316,7 +316,7 @@ func init() { func Echo(msg string) string { addr := std.OriginCaller() - pkgAddr := std.GetOrigPkgAddr() + pkgAddr := std.GetOriginPkgAddr() send := std.Coins{{"ugnot", 10000000}} banker := std.GetBanker(std.BankerTypeRealmSend) banker.SendCoins(pkgAddr, addr, send) // send back @@ -417,7 +417,7 @@ func init() { func Echo(msg string) string { addr := std.OriginCaller() - pkgAddr := std.GetOrigPkgAddr() + pkgAddr := std.GetOriginPkgAddr() send := std.OriginSend() banker := std.GetBanker(std.BankerTypeOriginSend) banker.SendCoins(pkgAddr, addr, send) // send back diff --git a/gnovm/pkg/test/test.go b/gnovm/pkg/test/test.go index be32a7379ab..e7e12131b9d 100644 --- a/gnovm/pkg/test/test.go +++ b/gnovm/pkg/test/test.go @@ -56,7 +56,7 @@ func Context(pkgPath string, send std.Coins) *teststd.TestExecContext { Height: DefaultHeight, Timestamp: DefaultTimestamp, OriginCaller: DefaultCaller, - OrigPkgAddr: pkgAddr.Bech32(), + OriginPkgAddr: pkgAddr.Bech32(), OriginSend: send, OriginSendSpent: new(std.Coins), Banker: banker, diff --git a/gnovm/stdlibs/std/banker.gno b/gnovm/stdlibs/std/banker.gno index c48e65ead6e..8fb24f355f0 100644 --- a/gnovm/stdlibs/std/banker.gno +++ b/gnovm/stdlibs/std/banker.gno @@ -73,7 +73,7 @@ func GetBanker(bt BankerType) Banker { var pkgAddr Address if bt == BankerTypeOriginSend { - pkgAddr = GetOrigPkgAddr() + pkgAddr = GetOriginPkgAddr() if pkgAddr != CurrentRealm().Addr() { panic("banker with type BankerTypeOriginSend can only be instantiated by the origin package") } diff --git a/gnovm/stdlibs/std/context.go b/gnovm/stdlibs/std/context.go index 177fc9003a8..37d246b0f1a 100644 --- a/gnovm/stdlibs/std/context.go +++ b/gnovm/stdlibs/std/context.go @@ -14,7 +14,7 @@ type ExecContext struct { Timestamp int64 // seconds TimestampNano int64 // nanoseconds, only used for testing. OriginCaller crypto.Bech32Address - OrigPkgAddr crypto.Bech32Address + OriginPkgAddr crypto.Bech32Address OriginSend std.Coins OriginSendSpent *std.Coins // mutable Banker BankerInterface diff --git a/gnovm/stdlibs/std/native.gno b/gnovm/stdlibs/std/native.gno index b8f38bf1681..eb61280777e 100644 --- a/gnovm/stdlibs/std/native.gno +++ b/gnovm/stdlibs/std/native.gno @@ -37,7 +37,7 @@ func PreviousRealm() Realm { return Realm{Address(addr), path} } -func GetOrigPkgAddr() Address { +func GetOriginPkgAddr() Address { return Address(origPkgAddr()) } diff --git a/gnovm/stdlibs/std/native.go b/gnovm/stdlibs/std/native.go index 7335ee321ad..e17c3e135df 100644 --- a/gnovm/stdlibs/std/native.go +++ b/gnovm/stdlibs/std/native.go @@ -77,7 +77,7 @@ func X_originCaller(m *gno.Machine) string { } func X_origPkgAddr(m *gno.Machine) string { - return string(GetContext(m).OrigPkgAddr) + return string(GetContext(m).OriginPkgAddr) } func X_callerAt(m *gno.Machine, n int) string { diff --git a/gnovm/tests/files/zrealm_std6.gno b/gnovm/tests/files/zrealm_std6.gno index 17a79c1d43b..bb4c7c941d7 100644 --- a/gnovm/tests/files/zrealm_std6.gno +++ b/gnovm/tests/files/zrealm_std6.gno @@ -6,7 +6,7 @@ import ( ) var ( - realmAddr std.Address = std.GetOrigPkgAddr() + realmAddr std.Address = std.GetOriginPkgAddr() ) func main() { diff --git a/gnovm/tests/stdlibs/generated.go b/gnovm/tests/stdlibs/generated.go index 9bce3c5f08b..ecf054301c1 100644 --- a/gnovm/tests/stdlibs/generated.go +++ b/gnovm/tests/stdlibs/generated.go @@ -139,7 +139,7 @@ var nativeFuncs = [...]NativeFunc{ }, { "std", - "testSetOrigPkgAddr", + "testSetOriginPkgAddr", []gno.FieldTypeExpr{ {Name: gno.N("p0"), Type: gno.X("string")}, }, @@ -154,7 +154,7 @@ var nativeFuncs = [...]NativeFunc{ gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - testlibs_std.X_testSetOrigPkgAddr( + testlibs_std.X_testSetOriginPkgAddr( m, p0) }, diff --git a/gnovm/tests/stdlibs/std/std.gno b/gnovm/tests/stdlibs/std/std.gno index 3359ce460d3..2fd5f38c0d9 100644 --- a/gnovm/tests/stdlibs/std/std.gno +++ b/gnovm/tests/stdlibs/std/std.gno @@ -5,7 +5,7 @@ func IsOriginCall() bool // injected func TestSkipHeights(count int64) // injected func TestSetOriginCaller(addr Address) { testSetOriginCaller(string(addr)) } -func TestSetOrigPkgAddr(addr Address) { testSetOrigPkgAddr(string(addr)) } +func TestSetOriginPkgAddr(addr Address) { testSetOriginPkgAddr(string(addr)) } // TestSetRealm sets the realm for the current frame. // After calling TestSetRealm, calling CurrentRealm() in the test function will yield the value of @@ -30,7 +30,7 @@ func callerAt(n int) string // native bindings func testSetOriginCaller(s string) -func testSetOrigPkgAddr(s string) +func testSetOriginPkgAddr(s string) func testSetRealm(addr, pkgPath string) func testSetOriginSend( sentDenom []string, sentAmt []int64, diff --git a/gnovm/tests/stdlibs/std/std.go b/gnovm/tests/stdlibs/std/std.go index 4121dee1f7a..e83c35a0787 100644 --- a/gnovm/tests/stdlibs/std/std.go +++ b/gnovm/tests/stdlibs/std/std.go @@ -97,9 +97,9 @@ func X_testSetOriginCaller(m *gno.Machine, addr string) { m.Context = ctx } -func X_testSetOrigPkgAddr(m *gno.Machine, addr string) { +func X_testSetOriginPkgAddr(m *gno.Machine, addr string) { ctx := m.Context.(*TestExecContext) - ctx.OrigPkgAddr = crypto.Bech32Address(addr) + ctx.OriginPkgAddr = crypto.Bech32Address(addr) m.Context = ctx } diff --git a/misc/deployments/test5.gno.land/genesis_txs.jsonl b/misc/deployments/test5.gno.land/genesis_txs.jsonl index 2e0a23e830a..aa00b15752d 100755 --- a/misc/deployments/test5.gno.land/genesis_txs.jsonl +++ b/misc/deployments/test5.gno.land/genesis_txs.jsonl @@ -37,7 +37,7 @@ {"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"rat","path":"gno.land/p/demo/rat","files":[{"name":"maths.gno","body":"package rat\n\nconst (\n\tintSize = 32 \u003c\u003c (^uint(0) \u003e\u003e 63) // 32 or 64\n\n\tMaxInt = 1\u003c\u003c(intSize-1) - 1\n\tMinInt = -1 \u003c\u003c (intSize - 1)\n\tMaxInt8 = 1\u003c\u003c7 - 1\n\tMinInt8 = -1 \u003c\u003c 7\n\tMaxInt16 = 1\u003c\u003c15 - 1\n\tMinInt16 = -1 \u003c\u003c 15\n\tMaxInt32 = 1\u003c\u003c31 - 1\n\tMinInt32 = -1 \u003c\u003c 31\n\tMaxInt64 = 1\u003c\u003c63 - 1\n\tMinInt64 = -1 \u003c\u003c 63\n\tMaxUint = 1\u003c\u003cintSize - 1\n\tMaxUint8 = 1\u003c\u003c8 - 1\n\tMaxUint16 = 1\u003c\u003c16 - 1\n\tMaxUint32 = 1\u003c\u003c32 - 1\n\tMaxUint64 = 1\u003c\u003c64 - 1\n)\n"},{"name":"rat.gno","body":"package rat\n\n//----------------------------------------\n// Rat fractions\n\n// represents a fraction.\ntype Rat struct {\n\tX int32\n\tY int32 // must be positive\n}\n\nfunc NewRat(x, y int32) Rat {\n\tif y \u003c= 0 {\n\t\tpanic(\"invalid std.Rat denominator\")\n\t}\n\treturn Rat{X: x, Y: y}\n}\n\nfunc (r1 Rat) IsValid() bool {\n\tif r1.Y \u003c= 0 {\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc (r1 Rat) Cmp(r2 Rat) int {\n\tif !r1.IsValid() {\n\t\tpanic(\"invalid std.Rat left operand\")\n\t}\n\tif !r2.IsValid() {\n\t\tpanic(\"invalid std.Rat right operand\")\n\t}\n\tvar p1, p2 int64\n\tp1 = int64(r1.X) * int64(r2.Y)\n\tp2 = int64(r1.Y) * int64(r2.X)\n\tif p1 \u003c p2 {\n\t\treturn -1\n\t} else if p1 == p2 {\n\t\treturn 0\n\t} else {\n\t\treturn 1\n\t}\n}\n\n//func (r1 Rat) Plus(r2 Rat) Rat {\n// XXX\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":"txlink","path":"gno.land/p/moul/txlink","files":[{"name":"txlink.gno","body":"// Package txlink provides utilities for creating transaction-related links\n// compatible with Gnoweb, Gnobro, and other clients within the Gno ecosystem.\n//\n// This package is optimized for generating lightweight transaction links with\n// flexible arguments, allowing users to build dynamic links that integrate\n// seamlessly with various Gno clients.\n//\n// The primary function, URL, is designed to produce markdown links for\n// transaction functions in the current \"relative realm\". By specifying a custom\n// Realm, you can generate links that either use the current realm path or a\n// fully qualified path for another realm.\n//\n// This package is a streamlined alternative to helplink, providing similar\n// functionality for transaction links without the full feature set of helplink.\npackage txlink\n\nimport (\n\t\"std\"\n\t\"strings\"\n)\n\nconst chainDomain = \"gno.land\" // XXX: std.ChainDomain (#2911)\n\n// URL returns a URL for the specified function with optional key-value\n// arguments, for the current realm.\nfunc URL(fn string, args ...string) string {\n\treturn Realm(\"\").URL(fn, args...)\n}\n\n// Realm represents a specific realm for generating tx links.\ntype Realm string\n\n// prefix returns the URL prefix for the realm.\nfunc (r Realm) prefix() string {\n\t// relative\n\tif r == \"\" {\n\t\tcurPath := std.CurrentRealm().PkgPath()\n\t\treturn strings.TrimPrefix(curPath, chainDomain)\n\t}\n\n\t// local realm -\u003e /realm\n\trealm := string(r)\n\tif strings.Contains(realm, chainDomain) {\n\t\treturn strings.TrimPrefix(realm, chainDomain)\n\t}\n\n\t// remote realm -\u003e https://remote.land/realm\n\treturn \"https://\" + string(r)\n}\n\n// URL returns a URL for the specified function with optional key-value\n// arguments.\nfunc (r Realm) URL(fn string, args ...string) string {\n\t// Start with the base query\n\turl := r.prefix() + \"$help\u0026func=\" + fn\n\n\t// Check if args length is even\n\tif len(args)%2 != 0 {\n\t\t// If not even, we can choose to handle the error here.\n\t\t// For example, we can just return the URL without appending\n\t\t// more args.\n\t\treturn url\n\t}\n\n\t// Append key-value pairs to the URL\n\tfor i := 0; i \u003c len(args); i += 2 {\n\t\tkey := args[i]\n\t\tvalue := args[i+1]\n\t\t// XXX: escape keys and args\n\t\turl += \"\u0026\" + key + \"=\" + value\n\t}\n\n\treturn url\n}\n"},{"name":"txlink_test.gno","body":"package txlink\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestURL(t *testing.T) {\n\ttests := []struct {\n\t\tfn string\n\t\targs []string\n\t\twant string\n\t\trealm Realm\n\t}{\n\t\t{\"foo\", []string{\"bar\", \"1\", \"baz\", \"2\"}, \"$help\u0026func=foo\u0026bar=1\u0026baz=2\", \"\"},\n\t\t{\"testFunc\", []string{\"key\", \"value\"}, \"$help\u0026func=testFunc\u0026key=value\", \"\"},\n\t\t{\"noArgsFunc\", []string{}, \"$help\u0026func=noArgsFunc\", \"\"},\n\t\t{\"oddArgsFunc\", []string{\"key\"}, \"$help\u0026func=oddArgsFunc\", \"\"},\n\t\t{\"foo\", []string{\"bar\", \"1\", \"baz\", \"2\"}, \"/r/lorem/ipsum$help\u0026func=foo\u0026bar=1\u0026baz=2\", \"gno.land/r/lorem/ipsum\"},\n\t\t{\"testFunc\", []string{\"key\", \"value\"}, \"/r/lorem/ipsum$help\u0026func=testFunc\u0026key=value\", \"gno.land/r/lorem/ipsum\"},\n\t\t{\"noArgsFunc\", []string{}, \"/r/lorem/ipsum$help\u0026func=noArgsFunc\", \"gno.land/r/lorem/ipsum\"},\n\t\t{\"oddArgsFunc\", []string{\"key\"}, \"/r/lorem/ipsum$help\u0026func=oddArgsFunc\", \"gno.land/r/lorem/ipsum\"},\n\t\t{\"foo\", []string{\"bar\", \"1\", \"baz\", \"2\"}, \"https://gno.world/r/lorem/ipsum$help\u0026func=foo\u0026bar=1\u0026baz=2\", \"gno.world/r/lorem/ipsum\"},\n\t\t{\"testFunc\", []string{\"key\", \"value\"}, \"https://gno.world/r/lorem/ipsum$help\u0026func=testFunc\u0026key=value\", \"gno.world/r/lorem/ipsum\"},\n\t\t{\"noArgsFunc\", []string{}, \"https://gno.world/r/lorem/ipsum$help\u0026func=noArgsFunc\", \"gno.world/r/lorem/ipsum\"},\n\t\t{\"oddArgsFunc\", []string{\"key\"}, \"https://gno.world/r/lorem/ipsum$help\u0026func=oddArgsFunc\", \"gno.world/r/lorem/ipsum\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\ttitle := tt.fn\n\t\tt.Run(title, func(t *testing.T) {\n\t\t\tgot := tt.realm.URL(tt.fn, tt.args...)\n\t\t\turequire.Equal(t, tt.want, got)\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":"users","path":"gno.land/p/demo/users","files":[{"name":"types.gno","body":"package users\n\ntype AddressOrName string\n\nfunc (aon AddressOrName) IsName() bool {\n\treturn aon != \"\" \u0026\u0026 aon[0] == '@'\n}\n\nfunc (aon AddressOrName) GetName() (string, bool) {\n\tif len(aon) \u003e= 2 \u0026\u0026 aon[0] == '@' {\n\t\treturn string(aon[1:]), true\n\t}\n\treturn \"\", false\n}\n"},{"name":"users.gno","body":"package users\n\nimport (\n\t\"std\"\n\t\"strconv\"\n)\n\n//----------------------------------------\n// Types\n\ntype User struct {\n\tAddress std.Address\n\tName string\n\tProfile string\n\tNumber int\n\tInvites int\n\tInviter std.Address\n}\n\nfunc (u *User) Render() string {\n\tstr := \"## user \" + u.Name + \"\\n\" +\n\t\t\"\\n\" +\n\t\t\" * address = \" + string(u.Address) + \"\\n\" +\n\t\t\" * \" + strconv.Itoa(u.Invites) + \" invites\\n\"\n\tif u.Inviter != \"\" {\n\t\tstr = str + \" * invited by \" + string(u.Inviter) + \"\\n\"\n\t}\n\tstr = str + \"\\n\" +\n\t\tu.Profile + \"\\n\"\n\treturn str\n}\n"},{"name":"users_test.gno","body":"package users\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":"users","path":"gno.land/r/demo/users","files":[{"name":"preregister.gno","body":"package users\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/users\"\n)\n\n// pre-restricted names\nvar preRestrictedNames = []string{\n\t\"bitcoin\", \"cosmos\", \"newtendermint\", \"ethereum\",\n}\n\n// pre-registered users\nvar preRegisteredUsers = []struct {\n\tName string\n\tAddress std.Address\n}{\n\t// system name\n\t{\"archives\", \"g1xlnyjrnf03ju82v0f98ruhpgnquk28knmjfe5k\"}, // -\u003e @r_archives\n\t{\"demo\", \"g13ek2zz9qurzynzvssyc4sthwppnruhnp0gdz8n\"}, // -\u003e @r_demo\n\t{\"gno\", \"g19602kd9tfxrfd60sgreadt9zvdyyuudcyxsz8a\"}, // -\u003e @r_gno\n\t{\"gnoland\", \"g1g3lsfxhvaqgdv4ccemwpnms4fv6t3aq3p5z6u7\"}, // -\u003e @r_gnoland\n\t{\"gnolang\", \"g1yjlnm3z2630gg5mryjd79907e0zx658wxs9hnd\"}, // -\u003e @r_gnolang\n\t{\"gov\", \"g1g73v2anukg4ej7axwqpthsatzrxjsh0wk797da\"}, // -\u003e @r_gov\n\t{\"nt\", \"g15ge0ae9077eh40erwrn2eq0xw6wupwqthpv34l\"}, // -\u003e @r_nt\n\t{\"sys\", \"g1r929wt2qplfawe4lvqv9zuwfdcz4vxdun7qh8l\"}, // -\u003e @r_sys\n\t{\"x\", \"g164sdpew3c2t3rvxj3kmfv7c7ujlvcw2punzzuz\"}, // -\u003e @r_x\n\n\t// test1 user\n\t{\"test1\", \"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\"}, // -\u003e @test1\n\n\t// Onbloc\n\t{\"gnoswap\", \"g1lmvrrrr4er2us84h2732sru76c9zl2nvknha8c\"}, // -\u003e @r_gnoswap\n\t{\"onbloc\", \"g12vx7dn3dqq89mz550zwunvg4qw6epq73d9csay\"}, // -\u003e @r_onbloc\n\n\t// Dragos\n\t{\"flippando\", \"g1z82x8mxa0pz5s9u7csy6zya4x0ut9uw6p7d8dk\"}, // -\u003e @r_flippando\n\t{\"zentasktic\", \"g1paxgmwy2wzhx0l6qvav2p8thvphc5c030xz35c\"}, // -\u003e @r_zentasktic\n}\n\nfunc init() {\n\t// add pre-registered users\n\tfor _, res := range preRegisteredUsers {\n\t\t// assert not already registered.\n\t\t_, ok := name2User.Get(res.Name)\n\t\tif ok {\n\t\t\tpanic(\"name already registered\")\n\t\t}\n\n\t\t_, ok = addr2User.Get(res.Address.String())\n\t\tif ok {\n\t\t\tpanic(\"address already registered\")\n\t\t}\n\n\t\tcounter++\n\t\tuser := \u0026users.User{\n\t\t\tAddress: res.Address,\n\t\t\tName: res.Name,\n\t\t\tProfile: \"\",\n\t\t\tNumber: counter,\n\t\t\tInvites: int(0),\n\t\t\tInviter: admin,\n\t\t}\n\t\tname2User.Set(res.Name, user)\n\t\taddr2User.Set(res.Address.String(), user)\n\t}\n\n\t// add pre-restricted names\n\tfor _, name := range preRestrictedNames {\n\t\tif _, ok := name2User.Get(name); ok {\n\t\t\tpanic(\"name already registered\")\n\t\t}\n\n\t\trestricted.Set(name, true)\n\t}\n}\n"},{"name":"users.gno","body":"package users\n\nimport (\n\t\"regexp\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/avl/pager\"\n\t\"gno.land/p/demo/avlhelpers\"\n\t\"gno.land/p/demo/users\"\n)\n\n//----------------------------------------\n// State\n\nvar (\n\tadmin std.Address = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\" // @moul\n\n\trestricted avl.Tree // Name -\u003e true - restricted name\n\tname2User avl.Tree // Name -\u003e *users.User\n\taddr2User avl.Tree // std.Address -\u003e *users.User\n\tinvites avl.Tree // string(inviter+\":\"+invited) -\u003e true\n\tcounter int // user id counter\n\tminFee int64 = 20 * 1_000_000 // minimum gnot must be paid to register.\n\tmaxFeeMult int64 = 10 // maximum multiples of minFee accepted.\n)\n\n//----------------------------------------\n// Top-level functions\n\nfunc Register(inviter std.Address, name string, profile string) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// assert invited or paid.\n\tcaller := std.CallerAt(2)\n\tif caller != std.OriginCaller() {\n\t\tpanic(\"should not happen\") // because std.AssertOrigCall().\n\t}\n\n\tsentCoins := std.OriginSend()\n\tminCoin := std.NewCoin(\"ugnot\", minFee)\n\n\tif inviter == \"\" {\n\t\t// banker := std.GetBanker(std.BankerTypeOriginSend)\n\t\tif len(sentCoins) == 1 \u0026\u0026 sentCoins[0].IsGTE(minCoin) {\n\t\t\tif sentCoins[0].Amount \u003e minFee*maxFeeMult {\n\t\t\t\tpanic(\"payment must not be greater than \" + strconv.Itoa(int(minFee*maxFeeMult)))\n\t\t\t} else {\n\t\t\t\t// ok\n\t\t\t}\n\t\t} else {\n\t\t\tpanic(\"payment must not be less than \" + strconv.Itoa(int(minFee)))\n\t\t}\n\t} else {\n\t\tinvitekey := inviter.String() + \":\" + caller.String()\n\t\t_, ok := invites.Get(invitekey)\n\t\tif !ok {\n\t\t\tpanic(\"invalid invitation\")\n\t\t}\n\t\tinvites.Remove(invitekey)\n\t}\n\n\t// assert not already registered.\n\t_, ok := name2User.Get(name)\n\tif ok {\n\t\tpanic(\"name already registered: \" + name)\n\t}\n\t_, ok = addr2User.Get(caller.String())\n\tif ok {\n\t\tpanic(\"address already registered: \" + caller.String())\n\t}\n\n\tisInviterAdmin := inviter == admin\n\n\t// check for restricted name\n\tif _, isRestricted := restricted.Get(name); isRestricted {\n\t\t// only address invite by the admin can register restricted name\n\t\tif !isInviterAdmin {\n\t\t\tpanic(\"restricted name: \" + name)\n\t\t}\n\n\t\trestricted.Remove(name)\n\t}\n\n\t// assert name is valid.\n\t// admin inviter can bypass name restriction\n\tif !isInviterAdmin \u0026\u0026 !reName.MatchString(name) {\n\t\tpanic(\"invalid name: \" + name + \" (must be at least 6 characters, lowercase alphanumeric with underscore)\")\n\t}\n\n\t// remainder of fees go toward invites.\n\tinvites := int(0)\n\tif len(sentCoins) == 1 {\n\t\tif sentCoins[0].Denom == \"ugnot\" \u0026\u0026 sentCoins[0].Amount \u003e= minFee {\n\t\t\tinvites = int(sentCoins[0].Amount / minFee)\n\t\t\tif inviter == \"\" \u0026\u0026 invites \u003e 0 {\n\t\t\t\tinvites -= 1\n\t\t\t}\n\t\t}\n\t}\n\t// register.\n\tcounter++\n\tuser := \u0026users.User{\n\t\tAddress: caller,\n\t\tName: name,\n\t\tProfile: profile,\n\t\tNumber: counter,\n\t\tInvites: invites,\n\t\tInviter: inviter,\n\t}\n\tname2User.Set(name, user)\n\taddr2User.Set(caller.String(), user)\n}\n\nfunc Invite(invitee string) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// get caller/inviter.\n\tcaller := std.CallerAt(2)\n\tif caller != std.OriginCaller() {\n\t\tpanic(\"should not happen\") // because std.AssertOrigCall().\n\t}\n\tlines := strings.Split(invitee, \"\\n\")\n\tif caller == admin {\n\t\t// nothing to do, all good\n\t} else {\n\t\t// ensure has invites.\n\t\tuserI, ok := addr2User.Get(caller.String())\n\t\tif !ok {\n\t\t\tpanic(\"user unknown\")\n\t\t}\n\t\tuser := userI.(*users.User)\n\t\tif user.Invites \u003c= 0 {\n\t\t\tpanic(\"user has no invite tokens\")\n\t\t}\n\t\tuser.Invites -= len(lines)\n\t\tif user.Invites \u003c 0 {\n\t\t\tpanic(\"user has insufficient invite tokens\")\n\t\t}\n\t}\n\t// for each line...\n\tfor _, line := range lines {\n\t\tif line == \"\" {\n\t\t\tcontinue // file bodies have a trailing newline.\n\t\t} else if strings.HasPrefix(line, `//`) {\n\t\t\tcontinue // comment\n\t\t}\n\t\t// record invite.\n\t\tinvitekey := string(caller) + \":\" + string(line)\n\t\tinvites.Set(invitekey, true)\n\t}\n}\n\nfunc GrantInvites(invites string) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// assert admin.\n\tcaller := std.CallerAt(2)\n\tif caller != std.OriginCaller() {\n\t\tpanic(\"should not happen\") // because std.AssertOrigCall().\n\t}\n\tif caller != admin {\n\t\tpanic(\"unauthorized\")\n\t}\n\t// for each line...\n\tlines := strings.Split(invites, \"\\n\")\n\tfor _, line := range lines {\n\t\tif line == \"\" {\n\t\t\tcontinue // file bodies have a trailing newline.\n\t\t} else if strings.HasPrefix(line, `//`) {\n\t\t\tcontinue // comment\n\t\t}\n\t\t// parse name and invites.\n\t\tvar name string\n\t\tvar invites int\n\t\tparts := strings.Split(line, \":\")\n\t\tif len(parts) == 1 { // short for :1.\n\t\t\tname = parts[0]\n\t\t\tinvites = 1\n\t\t} else if len(parts) == 2 {\n\t\t\tname = parts[0]\n\t\t\tinvites_, err := strconv.Atoi(parts[1])\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t\tinvites = int(invites_)\n\t\t} else {\n\t\t\tpanic(\"should not happen\")\n\t\t}\n\t\t// give invites.\n\t\tuserI, ok := name2User.Get(name)\n\t\tif !ok {\n\t\t\t// maybe address.\n\t\t\tuserI, ok = addr2User.Get(name)\n\t\t\tif !ok {\n\t\t\t\tpanic(\"invalid user \" + name)\n\t\t\t}\n\t\t}\n\t\tuser := userI.(*users.User)\n\t\tuser.Invites += invites\n\t}\n}\n\n// Any leftover fees go toward invitations.\nfunc SetMinFee(newMinFee int64) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// assert admin caller.\n\tcaller := std.CallerAt(2)\n\tif caller != admin {\n\t\tpanic(\"unauthorized\")\n\t}\n\t// update global variables.\n\tminFee = newMinFee\n}\n\n// This helps prevent fat finger accidents.\nfunc SetMaxFeeMultiple(newMaxFeeMult int64) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// assert admin caller.\n\tcaller := std.CallerAt(2)\n\tif caller != admin {\n\t\tpanic(\"unauthorized\")\n\t}\n\t// update global variables.\n\tmaxFeeMult = newMaxFeeMult\n}\n\n//----------------------------------------\n// Exposed public functions\n\nfunc GetUserByName(name string) *users.User {\n\tuserI, ok := name2User.Get(name)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn userI.(*users.User)\n}\n\nfunc GetUserByAddress(addr std.Address) *users.User {\n\tuserI, ok := addr2User.Get(addr.String())\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn userI.(*users.User)\n}\n\n// unlike GetUserByName, input must be \"@\" prefixed for names.\nfunc GetUserByAddressOrName(input users.AddressOrName) *users.User {\n\tname, isName := input.GetName()\n\tif isName {\n\t\treturn GetUserByName(name)\n\t}\n\treturn GetUserByAddress(std.Address(input))\n}\n\n// Get a list of user names starting from the given prefix. Limit the\n// number of results to maxResults. (This can be used for a name search tool.)\nfunc ListUsersByPrefix(prefix string, maxResults int) []string {\n\treturn avlhelpers.ListByteStringKeysByPrefix(name2User, prefix, maxResults)\n}\n\nfunc Resolve(input users.AddressOrName) std.Address {\n\tname, isName := input.GetName()\n\tif !isName {\n\t\treturn std.Address(input) // TODO check validity\n\t}\n\n\tuser := GetUserByName(name)\n\treturn user.Address\n}\n\n// Add restricted name to the list\nfunc AdminAddRestrictedName(name string) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// get caller\n\tcaller := std.OriginCaller()\n\t// assert admin\n\tif caller != admin {\n\t\tpanic(\"unauthorized\")\n\t}\n\n\tif user := GetUserByName(name); user != nil {\n\t\tpanic(\"already registered name\")\n\t}\n\n\t// register restricted name\n\n\trestricted.Set(name, true)\n}\n\n//----------------------------------------\n// Constants\n\n// NOTE: name length must be clearly distinguishable from a bech32 address.\nvar reName = regexp.MustCompile(`^[a-z]+[_a-z0-9]{5,16}$`)\n\n//----------------------------------------\n// Render main page\n\nfunc Render(fullPath string) string {\n\tpath, _ := splitPathAndQuery(fullPath)\n\tif path == \"\" {\n\t\treturn renderHome(fullPath)\n\t} else if len(path) \u003e= 38 { // 39? 40?\n\t\tif path[:2] != \"g1\" {\n\t\t\treturn \"invalid address \" + path\n\t\t}\n\t\tuser := GetUserByAddress(std.Address(path))\n\t\tif user == nil {\n\t\t\t// TODO: display basic information about account.\n\t\t\treturn \"unknown address \" + path\n\t\t}\n\t\treturn user.Render()\n\t} else {\n\t\tuser := GetUserByName(path)\n\t\tif user == nil {\n\t\t\treturn \"unknown username \" + path\n\t\t}\n\t\treturn user.Render()\n\t}\n}\n\nfunc renderHome(path string) string {\n\tdoc := \"\"\n\n\tpage := pager.NewPager(\u0026name2User, 50).MustGetPageByPath(path)\n\n\tfor _, item := range page.Items {\n\t\tuser := item.Value.(*users.User)\n\t\tdoc += \" * [\" + user.Name + \"](/r/demo/users:\" + user.Name + \")\\n\"\n\t}\n\tdoc += \"\\n\"\n\tdoc += page.Selector()\n\treturn doc\n}\n\nfunc splitPathAndQuery(fullPath string) (string, string) {\n\tparts := strings.SplitN(fullPath, \"?\", 2)\n\tpath := parts[0]\n\tqueryString := \"\"\n\tif len(parts) \u003e 1 {\n\t\tqueryString = \"?\" + parts[1]\n\t}\n\treturn path, queryString\n}\n"},{"name":"users_test.gno","body":"package users\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestPreRegisteredTest1(t *testing.T) {\n\tnames := ListUsersByPrefix(\"test1\", 1)\n\tuassert.Equal(t, len(names), 1)\n\tuassert.Equal(t, names[0], \"test1\")\n}\n"},{"name":"z_0_b_filetest.gno","body":"package main\n\n// SEND: 19900000ugnot\n\nimport (\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Error:\n// payment must not be less than 20000000\n"},{"name":"z_0_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tstd.TestSetOriginSend(std.Coins{std.NewCoin(\"dontcare\", 1)}, nil)\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Error:\n// incompatible coin denominations: dontcare, ugnot\n"},{"name":"z_10_filetest.gno","body":"// PKGPATH: gno.land/r/demo/users_test\npackage users_test\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc init() {\n\tcaller := std.OriginCaller() // main\n\ttest2 := testutils.TestAddress(\"test2\")\n\t// as admin, invite gnouser and test2\n\tstd.TestSetOriginCaller(admin)\n\tusers.Invite(caller.String() + \"\\n\" + test2.String())\n\t// register as caller\n\tstd.TestSetOriginCaller(caller)\n\tusers.Register(admin, \"gnouser\", \"my profile\")\n}\n\nfunc main() {\n\t// register as test2\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOriginCaller(test2)\n\tusers.Register(admin, \"test222\", \"my profile 2\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_11_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tstd.TestSetOriginCaller(admin)\n\tusers.AdminAddRestrictedName(\"superrestricted\")\n\n\t// test restricted name\n\tstd.TestSetOriginCaller(caller)\n\tusers.Register(\"\", \"superrestricted\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Error:\n// restricted name: superrestricted\n"},{"name":"z_11b_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tstd.TestSetOriginCaller(admin)\n\t// add restricted name\n\tusers.AdminAddRestrictedName(\"superrestricted\")\n\t// grant invite to caller\n\tusers.Invite(caller.String())\n\t// set back caller\n\tstd.TestSetOriginCaller(caller)\n\t// register restricted name with admin invite\n\tusers.Register(admin, \"superrestricted\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_12_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"alicia\", \"my profile\")\n\n\t{\n\t\t// Normal usage\n\t\tnames := users.ListUsersByPrefix(\"a\", 1)\n\t\tprintln(\"# names: \" + strconv.Itoa(len(names)))\n\t\tprintln(\"name: \" + names[0])\n\t}\n\n\t{\n\t\t// Empty prefix: match all\n\t\tnames := users.ListUsersByPrefix(\"\", 1)\n\t\tprintln(\"# names: \" + strconv.Itoa(len(names)))\n\t\tprintln(\"name: \" + names[0])\n\t}\n\n\t{\n\t\t// The prefix is before \"alicia\"\n\t\tnames := users.ListUsersByPrefix(\"alich\", 1)\n\t\tprintln(\"# names: \" + strconv.Itoa(len(names)))\n\t}\n\n\t{\n\t\t// The prefix is after the last name\n\t\tnames := users.ListUsersByPrefix(\"y\", 10)\n\t\tprintln(\"# names: \" + strconv.Itoa(len(names)))\n\t}\n\n\t// More tests are in p/demo/avlhelpers\n}\n\n// Output:\n// # names: 1\n// name: alicia\n// # names: 1\n// name: alicia\n// # names: 0\n// # names: 0\n"},{"name":"z_1_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_2_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_3_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tstd.TestSetOriginSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_4_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\ttest2 := testutils.TestAddress(\"test2\")\n\tusers.Invite(test1.String())\n\t// switch to test2 (not test1)\n\tstd.TestSetOriginCaller(test2)\n\tstd.TestSetOriginSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\tprintln(\"done\")\n}\n\n// Error:\n// invalid invitation\n"},{"name":"z_5_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tstd.TestSetOriginSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\tprintln(users.Render(\"\"))\n\tprintln(\"========================================\")\n\tprintln(users.Render(\"?page=2\"))\n\tprintln(\"========================================\")\n\tprintln(users.Render(\"gnouser\"))\n\tprintln(\"========================================\")\n\tprintln(users.Render(\"satoshi\"))\n\tprintln(\"========================================\")\n\tprintln(users.Render(\"badname\"))\n}\n\n// Output:\n// * [archives](/r/demo/users:archives)\n// * [demo](/r/demo/users:demo)\n// * [gno](/r/demo/users:gno)\n// * [gnoland](/r/demo/users:gnoland)\n// * [gnolang](/r/demo/users:gnolang)\n// * [gnouser](/r/demo/users:gnouser)\n// * [gov](/r/demo/users:gov)\n// * [nt](/r/demo/users:nt)\n// * [satoshi](/r/demo/users:satoshi)\n// * [sys](/r/demo/users:sys)\n// * [test1](/r/demo/users:test1)\n// * [x](/r/demo/users:x)\n//\n//\n// ========================================\n//\n//\n// ========================================\n// ## user gnouser\n//\n// * address = g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n// * 9 invites\n//\n// my profile\n//\n// ========================================\n// ## user satoshi\n//\n// * address = g1w3jhxap3ta047h6lta047h6lta047h6l4mfnm7\n// * 0 invites\n// * invited by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// my other profile\n//\n// ========================================\n// unknown username badname\n"},{"name":"z_6_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller()\n\t// as admin, grant invites to unregistered user.\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\tprintln(\"done\")\n}\n\n// Error:\n// invalid user g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n"},{"name":"z_7_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tstd.TestSetOriginSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\t// as admin, grant invites to gnouser(again) and satoshi.\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\\n\" + test1.String() + \":1\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_7b_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\\n\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tstd.TestSetOriginSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\t// as admin, grant invites to gnouser(again) and satoshi.\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\\n\" + test1.String() + \":1\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_8_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tstd.TestSetOriginSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\t// as admin, grant invites to gnouser(again) and nonexistent user.\n\tstd.TestSetOriginCaller(admin)\n\ttest2 := testutils.TestAddress(\"test2\")\n\tusers.GrantInvites(caller.String() + \":1\\n\" + test2.String() + \":1\")\n\tprintln(\"done\")\n}\n\n// Error:\n// invalid user g1w3jhxapjta047h6lta047h6lta047h6laqcyu4\n"},{"name":"z_9_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\ttest2 := testutils.TestAddress(\"test2\")\n\t// as admin, invite gnouser and test2\n\tstd.TestSetOriginCaller(admin)\n\tusers.Invite(caller.String() + \"\\n\" + test2.String())\n\t// register as caller\n\tstd.TestSetOriginCaller(caller)\n\tusers.Register(admin, \"gnouser\", \"my profile\")\n\t// register as test2\n\tstd.TestSetOriginCaller(test2)\n\tusers.Register(admin, \"test222\", \"my profile 2\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\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":"users","path":"gno.land/r/demo/users","files":[{"name":"preregister.gno","body":"package users\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/users\"\n)\n\n// pre-restricted names\nvar preRestrictedNames = []string{\n\t\"bitcoin\", \"cosmos\", \"newtendermint\", \"ethereum\",\n}\n\n// pre-registered users\nvar preRegisteredUsers = []struct {\n\tName string\n\tAddress std.Address\n}{\n\t// system name\n\t{\"archives\", \"g1xlnyjrnf03ju82v0f98ruhpgnquk28knmjfe5k\"}, // -\u003e @r_archives\n\t{\"demo\", \"g13ek2zz9qurzynzvssyc4sthwppnruhnp0gdz8n\"}, // -\u003e @r_demo\n\t{\"gno\", \"g19602kd9tfxrfd60sgreadt9zvdyyuudcyxsz8a\"}, // -\u003e @r_gno\n\t{\"gnoland\", \"g1g3lsfxhvaqgdv4ccemwpnms4fv6t3aq3p5z6u7\"}, // -\u003e @r_gnoland\n\t{\"gnolang\", \"g1yjlnm3z2630gg5mryjd79907e0zx658wxs9hnd\"}, // -\u003e @r_gnolang\n\t{\"gov\", \"g1g73v2anukg4ej7axwqpthsatzrxjsh0wk797da\"}, // -\u003e @r_gov\n\t{\"nt\", \"g15ge0ae9077eh40erwrn2eq0xw6wupwqthpv34l\"}, // -\u003e @r_nt\n\t{\"sys\", \"g1r929wt2qplfawe4lvqv9zuwfdcz4vxdun7qh8l\"}, // -\u003e @r_sys\n\t{\"x\", \"g164sdpew3c2t3rvxj3kmfv7c7ujlvcw2punzzuz\"}, // -\u003e @r_x\n\n\t// test1 user\n\t{\"test1\", \"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\"}, // -\u003e @test1\n\n\t// Onbloc\n\t{\"gnoswap\", \"g1lmvrrrr4er2us84h2732sru76c9zl2nvknha8c\"}, // -\u003e @r_gnoswap\n\t{\"onbloc\", \"g12vx7dn3dqq89mz550zwunvg4qw6epq73d9csay\"}, // -\u003e @r_onbloc\n\n\t// Dragos\n\t{\"flippando\", \"g1z82x8mxa0pz5s9u7csy6zya4x0ut9uw6p7d8dk\"}, // -\u003e @r_flippando\n\t{\"zentasktic\", \"g1paxgmwy2wzhx0l6qvav2p8thvphc5c030xz35c\"}, // -\u003e @r_zentasktic\n}\n\nfunc init() {\n\t// add pre-registered users\n\tfor _, res := range preRegisteredUsers {\n\t\t// assert not already registered.\n\t\t_, ok := name2User.Get(res.Name)\n\t\tif ok {\n\t\t\tpanic(\"name already registered\")\n\t\t}\n\n\t\t_, ok = addr2User.Get(res.Address.String())\n\t\tif ok {\n\t\t\tpanic(\"address already registered\")\n\t\t}\n\n\t\tcounter++\n\t\tuser := \u0026users.User{\n\t\t\tAddress: res.Address,\n\t\t\tName: res.Name,\n\t\t\tProfile: \"\",\n\t\t\tNumber: counter,\n\t\t\tInvites: int(0),\n\t\t\tInviter: admin,\n\t\t}\n\t\tname2User.Set(res.Name, user)\n\t\taddr2User.Set(res.Address.String(), user)\n\t}\n\n\t// add pre-restricted names\n\tfor _, name := range preRestrictedNames {\n\t\tif _, ok := name2User.Get(name); ok {\n\t\t\tpanic(\"name already registered\")\n\t\t}\n\n\t\trestricted.Set(name, true)\n\t}\n}\n"},{"name":"users.gno","body":"package users\n\nimport (\n\t\"regexp\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/avl/pager\"\n\t\"gno.land/p/demo/avlhelpers\"\n\t\"gno.land/p/demo/users\"\n)\n\n//----------------------------------------\n// State\n\nvar (\n\tadmin std.Address = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\" // @moul\n\n\trestricted avl.Tree // Name -\u003e true - restricted name\n\tname2User avl.Tree // Name -\u003e *users.User\n\taddr2User avl.Tree // std.Address -\u003e *users.User\n\tinvites avl.Tree // string(inviter+\":\"+invited) -\u003e true\n\tcounter int // user id counter\n\tminFee int64 = 20 * 1_000_000 // minimum gnot must be paid to register.\n\tmaxFeeMult int64 = 10 // maximum multiples of minFee accepted.\n)\n\n//----------------------------------------\n// Top-level functions\n\nfunc Register(inviter std.Address, name string, profile string) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// assert invited or paid.\n\tcaller := std.CallerAt(2)\n\tif caller != std.OriginCaller() {\n\t\tpanic(\"should not happen\") // because std.AssertOriginCall().\n\t}\n\n\tsentCoins := std.OriginSend()\n\tminCoin := std.NewCoin(\"ugnot\", minFee)\n\n\tif inviter == \"\" {\n\t\t// banker := std.GetBanker(std.BankerTypeOriginSend)\n\t\tif len(sentCoins) == 1 \u0026\u0026 sentCoins[0].IsGTE(minCoin) {\n\t\t\tif sentCoins[0].Amount \u003e minFee*maxFeeMult {\n\t\t\t\tpanic(\"payment must not be greater than \" + strconv.Itoa(int(minFee*maxFeeMult)))\n\t\t\t} else {\n\t\t\t\t// ok\n\t\t\t}\n\t\t} else {\n\t\t\tpanic(\"payment must not be less than \" + strconv.Itoa(int(minFee)))\n\t\t}\n\t} else {\n\t\tinvitekey := inviter.String() + \":\" + caller.String()\n\t\t_, ok := invites.Get(invitekey)\n\t\tif !ok {\n\t\t\tpanic(\"invalid invitation\")\n\t\t}\n\t\tinvites.Remove(invitekey)\n\t}\n\n\t// assert not already registered.\n\t_, ok := name2User.Get(name)\n\tif ok {\n\t\tpanic(\"name already registered: \" + name)\n\t}\n\t_, ok = addr2User.Get(caller.String())\n\tif ok {\n\t\tpanic(\"address already registered: \" + caller.String())\n\t}\n\n\tisInviterAdmin := inviter == admin\n\n\t// check for restricted name\n\tif _, isRestricted := restricted.Get(name); isRestricted {\n\t\t// only address invite by the admin can register restricted name\n\t\tif !isInviterAdmin {\n\t\t\tpanic(\"restricted name: \" + name)\n\t\t}\n\n\t\trestricted.Remove(name)\n\t}\n\n\t// assert name is valid.\n\t// admin inviter can bypass name restriction\n\tif !isInviterAdmin \u0026\u0026 !reName.MatchString(name) {\n\t\tpanic(\"invalid name: \" + name + \" (must be at least 6 characters, lowercase alphanumeric with underscore)\")\n\t}\n\n\t// remainder of fees go toward invites.\n\tinvites := int(0)\n\tif len(sentCoins) == 1 {\n\t\tif sentCoins[0].Denom == \"ugnot\" \u0026\u0026 sentCoins[0].Amount \u003e= minFee {\n\t\t\tinvites = int(sentCoins[0].Amount / minFee)\n\t\t\tif inviter == \"\" \u0026\u0026 invites \u003e 0 {\n\t\t\t\tinvites -= 1\n\t\t\t}\n\t\t}\n\t}\n\t// register.\n\tcounter++\n\tuser := \u0026users.User{\n\t\tAddress: caller,\n\t\tName: name,\n\t\tProfile: profile,\n\t\tNumber: counter,\n\t\tInvites: invites,\n\t\tInviter: inviter,\n\t}\n\tname2User.Set(name, user)\n\taddr2User.Set(caller.String(), user)\n}\n\nfunc Invite(invitee string) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// get caller/inviter.\n\tcaller := std.CallerAt(2)\n\tif caller != std.OriginCaller() {\n\t\tpanic(\"should not happen\") // because std.AssertOriginCall().\n\t}\n\tlines := strings.Split(invitee, \"\\n\")\n\tif caller == admin {\n\t\t// nothing to do, all good\n\t} else {\n\t\t// ensure has invites.\n\t\tuserI, ok := addr2User.Get(caller.String())\n\t\tif !ok {\n\t\t\tpanic(\"user unknown\")\n\t\t}\n\t\tuser := userI.(*users.User)\n\t\tif user.Invites \u003c= 0 {\n\t\t\tpanic(\"user has no invite tokens\")\n\t\t}\n\t\tuser.Invites -= len(lines)\n\t\tif user.Invites \u003c 0 {\n\t\t\tpanic(\"user has insufficient invite tokens\")\n\t\t}\n\t}\n\t// for each line...\n\tfor _, line := range lines {\n\t\tif line == \"\" {\n\t\t\tcontinue // file bodies have a trailing newline.\n\t\t} else if strings.HasPrefix(line, `//`) {\n\t\t\tcontinue // comment\n\t\t}\n\t\t// record invite.\n\t\tinvitekey := string(caller) + \":\" + string(line)\n\t\tinvites.Set(invitekey, true)\n\t}\n}\n\nfunc GrantInvites(invites string) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// assert admin.\n\tcaller := std.CallerAt(2)\n\tif caller != std.OriginCaller() {\n\t\tpanic(\"should not happen\") // because std.AssertOriginCall().\n\t}\n\tif caller != admin {\n\t\tpanic(\"unauthorized\")\n\t}\n\t// for each line...\n\tlines := strings.Split(invites, \"\\n\")\n\tfor _, line := range lines {\n\t\tif line == \"\" {\n\t\t\tcontinue // file bodies have a trailing newline.\n\t\t} else if strings.HasPrefix(line, `//`) {\n\t\t\tcontinue // comment\n\t\t}\n\t\t// parse name and invites.\n\t\tvar name string\n\t\tvar invites int\n\t\tparts := strings.Split(line, \":\")\n\t\tif len(parts) == 1 { // short for :1.\n\t\t\tname = parts[0]\n\t\t\tinvites = 1\n\t\t} else if len(parts) == 2 {\n\t\t\tname = parts[0]\n\t\t\tinvites_, err := strconv.Atoi(parts[1])\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t\tinvites = int(invites_)\n\t\t} else {\n\t\t\tpanic(\"should not happen\")\n\t\t}\n\t\t// give invites.\n\t\tuserI, ok := name2User.Get(name)\n\t\tif !ok {\n\t\t\t// maybe address.\n\t\t\tuserI, ok = addr2User.Get(name)\n\t\t\tif !ok {\n\t\t\t\tpanic(\"invalid user \" + name)\n\t\t\t}\n\t\t}\n\t\tuser := userI.(*users.User)\n\t\tuser.Invites += invites\n\t}\n}\n\n// Any leftover fees go toward invitations.\nfunc SetMinFee(newMinFee int64) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// assert admin caller.\n\tcaller := std.CallerAt(2)\n\tif caller != admin {\n\t\tpanic(\"unauthorized\")\n\t}\n\t// update global variables.\n\tminFee = newMinFee\n}\n\n// This helps prevent fat finger accidents.\nfunc SetMaxFeeMultiple(newMaxFeeMult int64) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// assert admin caller.\n\tcaller := std.CallerAt(2)\n\tif caller != admin {\n\t\tpanic(\"unauthorized\")\n\t}\n\t// update global variables.\n\tmaxFeeMult = newMaxFeeMult\n}\n\n//----------------------------------------\n// Exposed public functions\n\nfunc GetUserByName(name string) *users.User {\n\tuserI, ok := name2User.Get(name)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn userI.(*users.User)\n}\n\nfunc GetUserByAddress(addr std.Address) *users.User {\n\tuserI, ok := addr2User.Get(addr.String())\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn userI.(*users.User)\n}\n\n// unlike GetUserByName, input must be \"@\" prefixed for names.\nfunc GetUserByAddressOrName(input users.AddressOrName) *users.User {\n\tname, isName := input.GetName()\n\tif isName {\n\t\treturn GetUserByName(name)\n\t}\n\treturn GetUserByAddress(std.Address(input))\n}\n\n// Get a list of user names starting from the given prefix. Limit the\n// number of results to maxResults. (This can be used for a name search tool.)\nfunc ListUsersByPrefix(prefix string, maxResults int) []string {\n\treturn avlhelpers.ListByteStringKeysByPrefix(name2User, prefix, maxResults)\n}\n\nfunc Resolve(input users.AddressOrName) std.Address {\n\tname, isName := input.GetName()\n\tif !isName {\n\t\treturn std.Address(input) // TODO check validity\n\t}\n\n\tuser := GetUserByName(name)\n\treturn user.Address\n}\n\n// Add restricted name to the list\nfunc AdminAddRestrictedName(name string) {\n\t// assert CallTx call.\n\tstd.AssertOriginCall()\n\t// get caller\n\tcaller := std.OriginCaller()\n\t// assert admin\n\tif caller != admin {\n\t\tpanic(\"unauthorized\")\n\t}\n\n\tif user := GetUserByName(name); user != nil {\n\t\tpanic(\"already registered name\")\n\t}\n\n\t// register restricted name\n\n\trestricted.Set(name, true)\n}\n\n//----------------------------------------\n// Constants\n\n// NOTE: name length must be clearly distinguishable from a bech32 address.\nvar reName = regexp.MustCompile(`^[a-z]+[_a-z0-9]{5,16}$`)\n\n//----------------------------------------\n// Render main page\n\nfunc Render(fullPath string) string {\n\tpath, _ := splitPathAndQuery(fullPath)\n\tif path == \"\" {\n\t\treturn renderHome(fullPath)\n\t} else if len(path) \u003e= 38 { // 39? 40?\n\t\tif path[:2] != \"g1\" {\n\t\t\treturn \"invalid address \" + path\n\t\t}\n\t\tuser := GetUserByAddress(std.Address(path))\n\t\tif user == nil {\n\t\t\t// TODO: display basic information about account.\n\t\t\treturn \"unknown address \" + path\n\t\t}\n\t\treturn user.Render()\n\t} else {\n\t\tuser := GetUserByName(path)\n\t\tif user == nil {\n\t\t\treturn \"unknown username \" + path\n\t\t}\n\t\treturn user.Render()\n\t}\n}\n\nfunc renderHome(path string) string {\n\tdoc := \"\"\n\n\tpage := pager.NewPager(\u0026name2User, 50).MustGetPageByPath(path)\n\n\tfor _, item := range page.Items {\n\t\tuser := item.Value.(*users.User)\n\t\tdoc += \" * [\" + user.Name + \"](/r/demo/users:\" + user.Name + \")\\n\"\n\t}\n\tdoc += \"\\n\"\n\tdoc += page.Selector()\n\treturn doc\n}\n\nfunc splitPathAndQuery(fullPath string) (string, string) {\n\tparts := strings.SplitN(fullPath, \"?\", 2)\n\tpath := parts[0]\n\tqueryString := \"\"\n\tif len(parts) \u003e 1 {\n\t\tqueryString = \"?\" + parts[1]\n\t}\n\treturn path, queryString\n}\n"},{"name":"users_test.gno","body":"package users\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestPreRegisteredTest1(t *testing.T) {\n\tnames := ListUsersByPrefix(\"test1\", 1)\n\tuassert.Equal(t, len(names), 1)\n\tuassert.Equal(t, names[0], \"test1\")\n}\n"},{"name":"z_0_b_filetest.gno","body":"package main\n\n// SEND: 19900000ugnot\n\nimport (\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Error:\n// payment must not be less than 20000000\n"},{"name":"z_0_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tstd.TestSetOriginSend(std.Coins{std.NewCoin(\"dontcare\", 1)}, nil)\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Error:\n// incompatible coin denominations: dontcare, ugnot\n"},{"name":"z_10_filetest.gno","body":"// PKGPATH: gno.land/r/demo/users_test\npackage users_test\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc init() {\n\tcaller := std.OriginCaller() // main\n\ttest2 := testutils.TestAddress(\"test2\")\n\t// as admin, invite gnouser and test2\n\tstd.TestSetOriginCaller(admin)\n\tusers.Invite(caller.String() + \"\\n\" + test2.String())\n\t// register as caller\n\tstd.TestSetOriginCaller(caller)\n\tusers.Register(admin, \"gnouser\", \"my profile\")\n}\n\nfunc main() {\n\t// register as test2\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOriginCaller(test2)\n\tusers.Register(admin, \"test222\", \"my profile 2\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_11_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tstd.TestSetOriginCaller(admin)\n\tusers.AdminAddRestrictedName(\"superrestricted\")\n\n\t// test restricted name\n\tstd.TestSetOriginCaller(caller)\n\tusers.Register(\"\", \"superrestricted\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Error:\n// restricted name: superrestricted\n"},{"name":"z_11b_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tstd.TestSetOriginCaller(admin)\n\t// add restricted name\n\tusers.AdminAddRestrictedName(\"superrestricted\")\n\t// grant invite to caller\n\tusers.Invite(caller.String())\n\t// set back caller\n\tstd.TestSetOriginCaller(caller)\n\t// register restricted name with admin invite\n\tusers.Register(admin, \"superrestricted\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_12_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"alicia\", \"my profile\")\n\n\t{\n\t\t// Normal usage\n\t\tnames := users.ListUsersByPrefix(\"a\", 1)\n\t\tprintln(\"# names: \" + strconv.Itoa(len(names)))\n\t\tprintln(\"name: \" + names[0])\n\t}\n\n\t{\n\t\t// Empty prefix: match all\n\t\tnames := users.ListUsersByPrefix(\"\", 1)\n\t\tprintln(\"# names: \" + strconv.Itoa(len(names)))\n\t\tprintln(\"name: \" + names[0])\n\t}\n\n\t{\n\t\t// The prefix is before \"alicia\"\n\t\tnames := users.ListUsersByPrefix(\"alich\", 1)\n\t\tprintln(\"# names: \" + strconv.Itoa(len(names)))\n\t}\n\n\t{\n\t\t// The prefix is after the last name\n\t\tnames := users.ListUsersByPrefix(\"y\", 10)\n\t\tprintln(\"# names: \" + strconv.Itoa(len(names)))\n\t}\n\n\t// More tests are in p/demo/avlhelpers\n}\n\n// Output:\n// # names: 1\n// name: alicia\n// # names: 1\n// name: alicia\n// # names: 0\n// # names: 0\n"},{"name":"z_1_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_2_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_3_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tstd.TestSetOriginSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_4_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\ttest2 := testutils.TestAddress(\"test2\")\n\tusers.Invite(test1.String())\n\t// switch to test2 (not test1)\n\tstd.TestSetOriginCaller(test2)\n\tstd.TestSetOriginSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\tprintln(\"done\")\n}\n\n// Error:\n// invalid invitation\n"},{"name":"z_5_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tstd.TestSetOriginSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\tprintln(users.Render(\"\"))\n\tprintln(\"========================================\")\n\tprintln(users.Render(\"?page=2\"))\n\tprintln(\"========================================\")\n\tprintln(users.Render(\"gnouser\"))\n\tprintln(\"========================================\")\n\tprintln(users.Render(\"satoshi\"))\n\tprintln(\"========================================\")\n\tprintln(users.Render(\"badname\"))\n}\n\n// Output:\n// * [archives](/r/demo/users:archives)\n// * [demo](/r/demo/users:demo)\n// * [gno](/r/demo/users:gno)\n// * [gnoland](/r/demo/users:gnoland)\n// * [gnolang](/r/demo/users:gnolang)\n// * [gnouser](/r/demo/users:gnouser)\n// * [gov](/r/demo/users:gov)\n// * [nt](/r/demo/users:nt)\n// * [satoshi](/r/demo/users:satoshi)\n// * [sys](/r/demo/users:sys)\n// * [test1](/r/demo/users:test1)\n// * [x](/r/demo/users:x)\n//\n//\n// ========================================\n//\n//\n// ========================================\n// ## user gnouser\n//\n// * address = g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n// * 9 invites\n//\n// my profile\n//\n// ========================================\n// ## user satoshi\n//\n// * address = g1w3jhxap3ta047h6lta047h6lta047h6l4mfnm7\n// * 0 invites\n// * invited by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// my other profile\n//\n// ========================================\n// unknown username badname\n"},{"name":"z_6_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller()\n\t// as admin, grant invites to unregistered user.\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\tprintln(\"done\")\n}\n\n// Error:\n// invalid user g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n"},{"name":"z_7_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tstd.TestSetOriginSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\t// as admin, grant invites to gnouser(again) and satoshi.\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\\n\" + test1.String() + \":1\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_7b_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\\n\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tstd.TestSetOriginSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\t// as admin, grant invites to gnouser(again) and satoshi.\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\\n\" + test1.String() + \":1\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\n"},{"name":"z_8_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// as admin, grant invites to gnouser\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"test1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tstd.TestSetOriginSend(std.Coins{{\"dontcare\", 1}}, nil)\n\tusers.Register(caller, \"satoshi\", \"my other profile\")\n\t// as admin, grant invites to gnouser(again) and nonexistent user.\n\tstd.TestSetOriginCaller(admin)\n\ttest2 := testutils.TestAddress(\"test2\")\n\tusers.GrantInvites(caller.String() + \":1\\n\" + test2.String() + \":1\")\n\tprintln(\"done\")\n}\n\n// Error:\n// invalid user g1w3jhxapjta047h6lta047h6lta047h6laqcyu4\n"},{"name":"z_9_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\ttest2 := testutils.TestAddress(\"test2\")\n\t// as admin, invite gnouser and test2\n\tstd.TestSetOriginCaller(admin)\n\tusers.Invite(caller.String() + \"\\n\" + test2.String())\n\t// register as caller\n\tstd.TestSetOriginCaller(caller)\n\tusers.Register(admin, \"gnouser\", \"my profile\")\n\t// register as test2\n\tstd.TestSetOriginCaller(test2)\n\tusers.Register(admin, \"test222\", \"my profile 2\")\n\tprintln(\"done\")\n}\n\n// Output:\n// done\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":"boards","path":"gno.land/r/demo/boards","files":[{"name":"README.md","body":"This is a demo of Gno smart contract programming. This document was\nconstructed by Gno onto a smart contract hosted on the data Realm\nname [\"gno.land/r/demo/boards\"](https://gno.land/r/demo/boards/)\n([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)).\n\n\n\n## Build `gnokey`, create your account, and interact with Gno.\n\nNOTE: Where you see `-remote localhost:26657` here, that flag can be replaced\nwith `-remote gno.land:26657` if you have $GNOT on the testnet.\n(To use the testnet, also replace `-chainid dev` with `-chainid portal-loop` .)\n\n### Build `gnokey` (and other tools).\n\n```bash\ngit clone git@github.com:gnolang/gno.git\ncd gno/gno.land\nmake build\n```\n\n### Generate a seed/mnemonic code.\n\n```bash\n./build/gnokey generate\n```\n\nNOTE: You can generate 24 words with any good bip39 generator.\n\n### Create a new account using your mnemonic.\n\n```bash\n./build/gnokey add -recover KEYNAME\n```\n\nNOTE: `KEYNAME` is your key identifier, and should be changed.\n\n### Verify that you can see your account locally.\n\n```bash\n./build/gnokey list\n```\n\nTake note of your `addr` which looks something like `g17sphqax3kasjptdkmuqvn740u8dhtx4kxl6ljf` .\nYou will use this as your `ACCOUNT_ADDR`.\n\n## Interact with the blockchain.\n\n### Add $GNOT for your account.\n\nBefore starting the `gnoland` node for the first time, your new account can be given $GNOT in the node genesis.\nEdit the file `gno.land/genesis/genesis_balances.txt` and add the following line (simlar to the others), using\nyour `ACCOUNT_ADDR` and `KEYNAME`\n\n`ACCOUNT_ADDR=10000000000ugnot # @KEYNAME`\n\n### Alternative: Run a faucet to add $GNOT.\n\nInstead of editing `gno.land/genesis/genesis_balances.txt`, a more general solution (with more steps)\nis to run a local \"faucet\" and use the web browser to add $GNOT. (This can be done at any time.)\nSee this page: https://github.com/gnolang/gno/blob/master/contribs/gnofaucet/README.md\n\n\n### Start the `gnoland` node.\n\n```bash\n./build/gnoland start\n```\n\nNOTE: The node already has the \"boards\" realm.\n\nLeave this running in the terminal. In a new terminal, cd to the same folder `gno/gno.land` .\n\n### Get your current balance, account number, and sequence number.\n\n```bash\n./build/gnokey query auth/accounts/ACCOUNT_ADDR -remote localhost:26657\n```\n\n### Register a board username with a smart contract call.\n\nThe `USERNAME` for posting can different than your `KEYNAME`. It is internally linked to your `ACCOUNT_ADDR`. It must be at least 6 characters, lowercase alphanumeric with underscore.\n\n```bash\n./build/gnokey maketx call -pkgpath \"gno.land/r/demo/users\" -func \"Register\" -args \"\" -args \"USERNAME\" -args \"Profile description\" -gas-fee \"10000000ugnot\" -gas-wanted \"2000000\" -send \"200000000ugnot\" -broadcast -chainid dev -remote 127.0.0.1:26657 KEYNAME\n```\n\nInteractive documentation: https://gno.land/r/demo/users$help\u0026func=Register\n\n### Create a board with a smart contract call.\n\n```bash\n./build/gnokey maketx call -pkgpath \"gno.land/r/demo/boards\" -func \"CreateBoard\" -args \"BOARDNAME\" -gas-fee \"1000000ugnot\" -gas-wanted \"10000000\" -broadcast -chainid dev -remote localhost:26657 KEYNAME\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateBoard\n\nNext, query for the permanent board ID by querying (you need this to create a new post):\n\n```bash\n./build/gnokey query \"vm/qeval\" -data 'gno.land/r/demo/boards.GetBoardIDFromName(\"BOARDNAME\")' -remote localhost:26657\n```\n\n### Create a post of a board with a smart contract call.\n\nNOTE: If a board was created successfully, your SEQUENCE_NUMBER would have increased.\n\n```bash\n./build/gnokey maketx call -pkgpath \"gno.land/r/demo/boards\" -func \"CreateThread\" -args BOARD_ID -args \"Hello gno.land\" -args \"Text of the post\" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid dev -remote localhost:26657 KEYNAME\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateThread\n\n### Create a comment to a post.\n\n```bash\n./build/gnokey maketx call -pkgpath \"gno.land/r/demo/boards\" -func \"CreateReply\" -args BOARD_ID -args \"1\" -args \"1\" -args \"Nice to meet you too.\" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid dev -remote localhost:26657 KEYNAME\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateReply\n\n```bash\n./build/gnokey query \"vm/qrender\" -data \"gno.land/r/demo/boards:BOARDNAME/1\" -remote localhost:26657\n```\n\n### Render page with optional path expression.\n\nThe contents of `https://gno.land/r/demo/boards:` and `https://gno.land/r/demo/boards:gnolang` are rendered by calling\nthe `Render(path string)` function like so:\n\n```bash\n./build/gnokey query \"vm/qrender\" -data \"gno.land/r/demo/boards:gnolang\"\n```\n## View the board in the browser.\n\n### Start the web server.\n\n```bash\n./build/gnoweb\n```\n\nThis should print something like `Running on http://127.0.0.1:8888` . Leave this running in the terminal.\n\n### View in the browser\n\nIn your browser, navigate to the printed address http://127.0.0.1:8888 .\nTo see you post, click on the package `/r/demo/boards` .\n"},{"name":"board.gno","body":"package boards\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/moul/txlink\"\n)\n\n//----------------------------------------\n// Board\n\ntype BoardID uint64\n\nfunc (bid BoardID) String() string {\n\treturn strconv.Itoa(int(bid))\n}\n\ntype Board struct {\n\tid BoardID // only set for public boards.\n\turl string\n\tname string\n\tcreator std.Address\n\tthreads avl.Tree // Post.id -\u003e *Post\n\tpostsCtr uint64 // increments Post.id\n\tcreatedAt time.Time\n\tdeleted avl.Tree // TODO reserved for fast-delete.\n}\n\nfunc newBoard(id BoardID, url string, name string, creator std.Address) *Board {\n\tif !reName.MatchString(name) {\n\t\tpanic(\"invalid name: \" + name)\n\t}\n\texists := gBoardsByName.Has(name)\n\tif exists {\n\t\tpanic(\"board already exists\")\n\t}\n\treturn \u0026Board{\n\t\tid: id,\n\t\turl: url,\n\t\tname: name,\n\t\tcreator: creator,\n\t\tthreads: avl.Tree{},\n\t\tcreatedAt: time.Now(),\n\t\tdeleted: avl.Tree{},\n\t}\n}\n\n/* TODO support this once we figure out how to ensure URL correctness.\n// A private board is not tracked by gBoards*,\n// but must be persisted by the caller's realm.\n// Private boards have 0 id and does not ping\n// back the remote board on reposts.\nfunc NewPrivateBoard(url string, name string, creator std.Address) *Board {\n\treturn newBoard(0, url, name, creator)\n}\n*/\n\nfunc (board *Board) IsPrivate() bool {\n\treturn board.id == 0\n}\n\nfunc (board *Board) GetThread(pid PostID) *Post {\n\tpidkey := postIDKey(pid)\n\tpostI, exists := board.threads.Get(pidkey)\n\tif !exists {\n\t\treturn nil\n\t}\n\treturn postI.(*Post)\n}\n\nfunc (board *Board) AddThread(creator std.Address, title string, body string) *Post {\n\tpid := board.incGetPostID()\n\tpidkey := postIDKey(pid)\n\tthread := newPost(board, pid, creator, title, body, pid, 0, 0)\n\tboard.threads.Set(pidkey, thread)\n\treturn thread\n}\n\n// NOTE: this can be potentially very expensive for threads with many replies.\n// TODO: implement optional fast-delete where thread is simply moved.\nfunc (board *Board) DeleteThread(pid PostID) {\n\tpidkey := postIDKey(pid)\n\t_, removed := board.threads.Remove(pidkey)\n\tif !removed {\n\t\tpanic(\"thread does not exist with id \" + pid.String())\n\t}\n}\n\nfunc (board *Board) HasPermission(addr std.Address, perm Permission) bool {\n\tif board.creator == addr {\n\t\tswitch perm {\n\t\tcase EditPermission:\n\t\t\treturn true\n\t\tcase DeletePermission:\n\t\t\treturn true\n\t\tdefault:\n\t\t\treturn false\n\t\t}\n\t}\n\treturn false\n}\n\n// Renders the board for display suitable as plaintext in\n// console. This is suitable for demonstration or tests,\n// but not for prod.\nfunc (board *Board) RenderBoard() string {\n\tstr := \"\"\n\tstr += \"\\\\[[post](\" + board.GetPostFormURL() + \")]\\n\\n\"\n\tif board.threads.Size() \u003e 0 {\n\t\tboard.threads.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tif str != \"\" {\n\t\t\t\tstr += \"----------------------------------------\\n\"\n\t\t\t}\n\t\t\tstr += value.(*Post).RenderSummary() + \"\\n\"\n\t\t\treturn false\n\t\t})\n\t}\n\treturn str\n}\n\nfunc (board *Board) incGetPostID() PostID {\n\tboard.postsCtr++\n\treturn PostID(board.postsCtr)\n}\n\nfunc (board *Board) GetURLFromThreadAndReplyID(threadID, replyID PostID) string {\n\tif replyID == 0 {\n\t\treturn board.url + \"/\" + threadID.String()\n\t} else {\n\t\treturn board.url + \"/\" + threadID.String() + \"/\" + replyID.String()\n\t}\n}\n\nfunc (board *Board) GetPostFormURL() string {\n\treturn txlink.URL(\"CreateThread\", \"bid\", board.id.String())\n}\n"},{"name":"boards.gno","body":"package boards\n\nimport (\n\t\"regexp\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n//----------------------------------------\n// Realm (package) state\n\nvar (\n\tgBoards avl.Tree // id -\u003e *Board\n\tgBoardsCtr int // increments Board.id\n\tgBoardsByName avl.Tree // name -\u003e *Board\n\tgDefaultAnonFee = 100000000 // minimum fee required if anonymous\n)\n\n//----------------------------------------\n// Constants\n\nvar reName = regexp.MustCompile(`^[a-z]+[_a-z0-9]{2,29}$`)\n"},{"name":"misc.gno","body":"package boards\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/r/demo/users\"\n)\n\n//----------------------------------------\n// private utility methods\n// XXX ensure these cannot be called from public.\n\nfunc getBoard(bid BoardID) *Board {\n\tbidkey := boardIDKey(bid)\n\tboard_, exists := gBoards.Get(bidkey)\n\tif !exists {\n\t\treturn nil\n\t}\n\tboard := board_.(*Board)\n\treturn board\n}\n\nfunc incGetBoardID() BoardID {\n\tgBoardsCtr++\n\treturn BoardID(gBoardsCtr)\n}\n\nfunc padLeft(str string, length int) string {\n\tif len(str) \u003e= length {\n\t\treturn str\n\t} else {\n\t\treturn strings.Repeat(\" \", length-len(str)) + str\n\t}\n}\n\nfunc padZero(u64 uint64, length int) string {\n\tstr := strconv.Itoa(int(u64))\n\tif len(str) \u003e= length {\n\t\treturn str\n\t} else {\n\t\treturn strings.Repeat(\"0\", length-len(str)) + str\n\t}\n}\n\nfunc boardIDKey(bid BoardID) string {\n\treturn padZero(uint64(bid), 10)\n}\n\nfunc postIDKey(pid PostID) string {\n\treturn padZero(uint64(pid), 10)\n}\n\nfunc indentBody(indent string, body string) string {\n\tlines := strings.Split(body, \"\\n\")\n\tres := \"\"\n\tfor i, line := range lines {\n\t\tif i \u003e 0 {\n\t\t\tres += \"\\n\"\n\t\t}\n\t\tres += indent + line\n\t}\n\treturn res\n}\n\n// NOTE: length must be greater than 3.\nfunc summaryOf(str string, length int) string {\n\tlines := strings.SplitN(str, \"\\n\", 2)\n\tline := lines[0]\n\tif len(line) \u003e length {\n\t\tline = line[:(length-3)] + \"...\"\n\t} else if len(lines) \u003e 1 {\n\t\t// len(line) \u003c= 80\n\t\tline = line + \"...\"\n\t}\n\treturn line\n}\n\nfunc displayAddressMD(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user == nil {\n\t\treturn \"[\" + addr.String() + \"](/r/demo/users:\" + addr.String() + \")\"\n\t} else {\n\t\treturn \"[@\" + user.Name + \"](/r/demo/users:\" + user.Name + \")\"\n\t}\n}\n\nfunc usernameOf(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user == nil {\n\t\treturn \"\"\n\t}\n\treturn user.Name\n}\n"},{"name":"post.gno","body":"package boards\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/moul/txlink\"\n)\n\n//----------------------------------------\n// Post\n\n// NOTE: a PostID is relative to the board.\ntype PostID uint64\n\nfunc (pid PostID) String() string {\n\treturn strconv.Itoa(int(pid))\n}\n\n// A Post is a \"thread\" or a \"reply\" depending on context.\n// A thread is a Post of a Board that holds other replies.\ntype Post struct {\n\tboard *Board\n\tid PostID\n\tcreator std.Address\n\ttitle string // optional\n\tbody string\n\treplies avl.Tree // Post.id -\u003e *Post\n\trepliesAll avl.Tree // Post.id -\u003e *Post (all replies, for top-level posts)\n\treposts avl.Tree // Board.id -\u003e Post.id\n\tthreadID PostID // original Post.id\n\tparentID PostID // parent Post.id (if reply or repost)\n\trepostBoard BoardID // original Board.id (if repost)\n\tcreatedAt time.Time\n\tupdatedAt time.Time\n}\n\nfunc newPost(board *Board, id PostID, creator std.Address, title, body string, threadID, parentID PostID, repostBoard BoardID) *Post {\n\treturn \u0026Post{\n\t\tboard: board,\n\t\tid: id,\n\t\tcreator: creator,\n\t\ttitle: title,\n\t\tbody: body,\n\t\treplies: avl.Tree{},\n\t\trepliesAll: avl.Tree{},\n\t\treposts: avl.Tree{},\n\t\tthreadID: threadID,\n\t\tparentID: parentID,\n\t\trepostBoard: repostBoard,\n\t\tcreatedAt: time.Now(),\n\t}\n}\n\nfunc (post *Post) IsThread() bool {\n\treturn post.parentID == 0\n}\n\nfunc (post *Post) GetPostID() PostID {\n\treturn post.id\n}\n\nfunc (post *Post) AddReply(creator std.Address, body string) *Post {\n\tboard := post.board\n\tpid := board.incGetPostID()\n\tpidkey := postIDKey(pid)\n\treply := newPost(board, pid, creator, \"\", body, post.threadID, post.id, 0)\n\tpost.replies.Set(pidkey, reply)\n\tif post.threadID == post.id {\n\t\tpost.repliesAll.Set(pidkey, reply)\n\t} else {\n\t\tthread := board.GetThread(post.threadID)\n\t\tthread.repliesAll.Set(pidkey, reply)\n\t}\n\treturn reply\n}\n\nfunc (post *Post) Update(title string, body string) {\n\tpost.title = title\n\tpost.body = body\n\tpost.updatedAt = time.Now()\n}\n\nfunc (thread *Post) GetReply(pid PostID) *Post {\n\tpidkey := postIDKey(pid)\n\treplyI, ok := thread.repliesAll.Get(pidkey)\n\tif !ok {\n\t\treturn nil\n\t} else {\n\t\treturn replyI.(*Post)\n\t}\n}\n\nfunc (post *Post) AddRepostTo(creator std.Address, title, body string, dst *Board) *Post {\n\tif !post.IsThread() {\n\t\tpanic(\"cannot repost non-thread post\")\n\t}\n\tpid := dst.incGetPostID()\n\tpidkey := postIDKey(pid)\n\trepost := newPost(dst, pid, creator, title, body, pid, post.id, post.board.id)\n\tdst.threads.Set(pidkey, repost)\n\tif !dst.IsPrivate() {\n\t\tbidkey := boardIDKey(dst.id)\n\t\tpost.reposts.Set(bidkey, pid)\n\t}\n\treturn repost\n}\n\nfunc (thread *Post) DeletePost(pid PostID) {\n\tif thread.id == pid {\n\t\tpanic(\"should not happen\")\n\t}\n\tpidkey := postIDKey(pid)\n\tpostI, removed := thread.repliesAll.Remove(pidkey)\n\tif !removed {\n\t\tpanic(\"post not found in thread\")\n\t}\n\tpost := postI.(*Post)\n\tif post.parentID != thread.id {\n\t\tparent := thread.GetReply(post.parentID)\n\t\tparent.replies.Remove(pidkey)\n\t} else {\n\t\tthread.replies.Remove(pidkey)\n\t}\n}\n\nfunc (post *Post) HasPermission(addr std.Address, perm Permission) bool {\n\tif post.creator == addr {\n\t\tswitch perm {\n\t\tcase EditPermission:\n\t\t\treturn true\n\t\tcase DeletePermission:\n\t\t\treturn true\n\t\tdefault:\n\t\t\treturn false\n\t\t}\n\t}\n\t// post notes inherit permissions of the board.\n\treturn post.board.HasPermission(addr, perm)\n}\n\nfunc (post *Post) GetSummary() string {\n\treturn summaryOf(post.body, 80)\n}\n\nfunc (post *Post) GetURL() string {\n\tif post.IsThread() {\n\t\treturn post.board.GetURLFromThreadAndReplyID(\n\t\t\tpost.id, 0)\n\t} else {\n\t\treturn post.board.GetURLFromThreadAndReplyID(\n\t\t\tpost.threadID, post.id)\n\t}\n}\n\nfunc (post *Post) GetReplyFormURL() string {\n\treturn txlink.URL(\"CreateReply\",\n\t\t\"bid\", post.board.id.String(),\n\t\t\"threadid\", post.threadID.String(),\n\t\t\"postid\", post.id.String(),\n\t)\n}\n\nfunc (post *Post) GetRepostFormURL() string {\n\treturn txlink.URL(\"CreateRepost\",\n\t\t\"bid\", post.board.id.String(),\n\t\t\"postid\", post.id.String(),\n\t)\n}\n\nfunc (post *Post) GetDeleteFormURL() string {\n\treturn txlink.URL(\"DeletePost\",\n\t\t\"bid\", post.board.id.String(),\n\t\t\"threadid\", post.threadID.String(),\n\t\t\"postid\", post.id.String(),\n\t)\n}\n\nfunc (post *Post) RenderSummary() string {\n\tif post.repostBoard != 0 {\n\t\tdstBoard := getBoard(post.repostBoard)\n\t\tif dstBoard == nil {\n\t\t\tpanic(\"repostBoard does not exist\")\n\t\t}\n\t\tthread := dstBoard.GetThread(PostID(post.parentID))\n\t\tif thread == nil {\n\t\t\treturn \"reposted post does not exist\"\n\t\t}\n\t\treturn \"Repost: \" + post.GetSummary() + \"\\n\" + thread.RenderSummary()\n\t}\n\tstr := \"\"\n\tif post.title != \"\" {\n\t\tstr += \"## [\" + summaryOf(post.title, 80) + \"](\" + post.GetURL() + \")\\n\"\n\t\tstr += \"\\n\"\n\t}\n\tstr += post.GetSummary() + \"\\n\"\n\tstr += \"\\\\- \" + displayAddressMD(post.creator) + \",\"\n\tstr += \" [\" + post.createdAt.Format(\"2006-01-02 3:04pm MST\") + \"](\" + post.GetURL() + \")\"\n\tstr += \" \\\\[[x](\" + post.GetDeleteFormURL() + \")]\"\n\tstr += \" (\" + strconv.Itoa(post.replies.Size()) + \" replies)\"\n\tstr += \" (\" + strconv.Itoa(post.reposts.Size()) + \" reposts)\" + \"\\n\"\n\treturn str\n}\n\nfunc (post *Post) RenderPost(indent string, levels int) string {\n\tif post == nil {\n\t\treturn \"nil post\"\n\t}\n\tstr := \"\"\n\tif post.title != \"\" {\n\t\tstr += indent + \"# \" + post.title + \"\\n\"\n\t\tstr += indent + \"\\n\"\n\t}\n\tstr += indentBody(indent, post.body) + \"\\n\" // TODO: indent body lines.\n\tstr += indent + \"\\\\- \" + displayAddressMD(post.creator) + \", \"\n\tstr += \"[\" + post.createdAt.Format(\"2006-01-02 3:04pm (MST)\") + \"](\" + post.GetURL() + \")\"\n\tstr += \" \\\\[[reply](\" + post.GetReplyFormURL() + \")]\"\n\tif post.IsThread() {\n\t\tstr += \" \\\\[[repost](\" + post.GetRepostFormURL() + \")]\"\n\t}\n\tstr += \" \\\\[[x](\" + post.GetDeleteFormURL() + \")]\\n\"\n\tif levels \u003e 0 {\n\t\tif post.replies.Size() \u003e 0 {\n\t\t\tpost.replies.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\t\tstr += indent + \"\\n\"\n\t\t\t\tstr += value.(*Post).RenderPost(indent+\"\u003e \", levels-1)\n\t\t\t\treturn false\n\t\t\t})\n\t\t}\n\t} else {\n\t\tif post.replies.Size() \u003e 0 {\n\t\t\tstr += indent + \"\\n\"\n\t\t\tstr += indent + \"_[see all \" + strconv.Itoa(post.replies.Size()) + \" replies](\" + post.GetURL() + \")_\\n\"\n\t\t}\n\t}\n\treturn str\n}\n\n// render reply and link to context thread\nfunc (post *Post) RenderInner() string {\n\tif post.IsThread() {\n\t\tpanic(\"unexpected thread\")\n\t}\n\tthreadID := post.threadID\n\t// replyID := post.id\n\tparentID := post.parentID\n\tstr := \"\"\n\tstr += \"_[see thread](\" + post.board.GetURLFromThreadAndReplyID(\n\t\tthreadID, 0) + \")_\\n\\n\"\n\tthread := post.board.GetThread(post.threadID)\n\tvar parent *Post\n\tif thread.id == parentID {\n\t\tparent = thread\n\t} else {\n\t\tparent = thread.GetReply(parentID)\n\t}\n\tstr += parent.RenderPost(\"\", 0)\n\tstr += \"\\n\"\n\tstr += post.RenderPost(\"\u003e \", 5)\n\treturn str\n}\n"},{"name":"public.gno","body":"package boards\n\nimport (\n\t\"std\"\n\t\"strconv\"\n)\n\n//----------------------------------------\n// Public facing functions\n\nfunc GetBoardIDFromName(name string) (BoardID, bool) {\n\tboardI, exists := gBoardsByName.Get(name)\n\tif !exists {\n\t\treturn 0, false\n\t}\n\treturn boardI.(*Board).id, true\n}\n\nfunc CreateBoard(name string) BoardID {\n\tif !(std.IsOriginCall() || std.PreviousRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tbid := incGetBoardID()\n\tcaller := std.OriginCaller()\n\tif usernameOf(caller) == \"\" {\n\t\tpanic(\"unauthorized\")\n\t}\n\turl := \"/r/demo/boards:\" + name\n\tboard := newBoard(bid, url, name, caller)\n\tbidkey := boardIDKey(bid)\n\tgBoards.Set(bidkey, board)\n\tgBoardsByName.Set(name, board)\n\treturn board.id\n}\n\nfunc checkAnonFee() bool {\n\tsent := std.OriginSend()\n\tanonFeeCoin := std.NewCoin(\"ugnot\", int64(gDefaultAnonFee))\n\tif len(sent) == 1 \u0026\u0026 sent[0].IsGTE(anonFeeCoin) {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc CreateThread(bid BoardID, title string, body string) PostID {\n\tif !(std.IsOriginCall() || std.PreviousRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.OriginCaller()\n\tif usernameOf(caller) == \"\" {\n\t\tif !checkAnonFee() {\n\t\t\tpanic(\"please register, otherwise minimum fee \" + strconv.Itoa(gDefaultAnonFee) + \" is required if anonymous\")\n\t\t}\n\t}\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"board not exist\")\n\t}\n\tthread := board.AddThread(caller, title, body)\n\treturn thread.id\n}\n\nfunc CreateReply(bid BoardID, threadid, postid PostID, body string) PostID {\n\tif !(std.IsOriginCall() || std.PreviousRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.OriginCaller()\n\tif usernameOf(caller) == \"\" {\n\t\tif !checkAnonFee() {\n\t\t\tpanic(\"please register, otherwise minimum fee \" + strconv.Itoa(gDefaultAnonFee) + \" is required if anonymous\")\n\t\t}\n\t}\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"board not exist\")\n\t}\n\tthread := board.GetThread(threadid)\n\tif thread == nil {\n\t\tpanic(\"thread not exist\")\n\t}\n\tif postid == threadid {\n\t\treply := thread.AddReply(caller, body)\n\t\treturn reply.id\n\t} else {\n\t\tpost := thread.GetReply(postid)\n\t\treply := post.AddReply(caller, body)\n\t\treturn reply.id\n\t}\n}\n\n// If dstBoard is private, does not ping back.\n// If board specified by bid is private, panics.\nfunc CreateRepost(bid BoardID, postid PostID, title string, body string, dstBoardID BoardID) PostID {\n\tif !(std.IsOriginCall() || std.PreviousRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.OriginCaller()\n\tif usernameOf(caller) == \"\" {\n\t\t// TODO: allow with gDefaultAnonFee payment.\n\t\tif !checkAnonFee() {\n\t\t\tpanic(\"please register, otherwise minimum fee \" + strconv.Itoa(gDefaultAnonFee) + \" is required if anonymous\")\n\t\t}\n\t}\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"src board not exist\")\n\t}\n\tif board.IsPrivate() {\n\t\tpanic(\"cannot repost from a private board\")\n\t}\n\tdst := getBoard(dstBoardID)\n\tif dst == nil {\n\t\tpanic(\"dst board not exist\")\n\t}\n\tthread := board.GetThread(postid)\n\tif thread == nil {\n\t\tpanic(\"thread not exist\")\n\t}\n\trepost := thread.AddRepostTo(caller, title, body, dst)\n\treturn repost.id\n}\n\nfunc DeletePost(bid BoardID, threadid, postid PostID, reason string) {\n\tif !(std.IsOriginCall() || std.PreviousRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.OriginCaller()\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"board not exist\")\n\t}\n\tthread := board.GetThread(threadid)\n\tif thread == nil {\n\t\tpanic(\"thread not exist\")\n\t}\n\tif postid == threadid {\n\t\t// delete thread\n\t\tif !thread.HasPermission(caller, DeletePermission) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t\tboard.DeleteThread(threadid)\n\t} else {\n\t\t// delete thread's post\n\t\tpost := thread.GetReply(postid)\n\t\tif post == nil {\n\t\t\tpanic(\"post not exist\")\n\t\t}\n\t\tif !post.HasPermission(caller, DeletePermission) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t\tthread.DeletePost(postid)\n\t}\n}\n\nfunc EditPost(bid BoardID, threadid, postid PostID, title, body string) {\n\tif !(std.IsOriginCall() || std.PreviousRealm().IsUser()) {\n\t\tpanic(\"invalid non-user call\")\n\t}\n\tcaller := std.OriginCaller()\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\tpanic(\"board not exist\")\n\t}\n\tthread := board.GetThread(threadid)\n\tif thread == nil {\n\t\tpanic(\"thread not exist\")\n\t}\n\tif postid == threadid {\n\t\t// edit thread\n\t\tif !thread.HasPermission(caller, EditPermission) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t\tthread.Update(title, body)\n\t} else {\n\t\t// edit thread's post\n\t\tpost := thread.GetReply(postid)\n\t\tif post == nil {\n\t\t\tpanic(\"post not exist\")\n\t\t}\n\t\tif !post.HasPermission(caller, EditPermission) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t\tpost.Update(title, body)\n\t}\n}\n"},{"name":"render.gno","body":"package boards\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n)\n\n//----------------------------------------\n// Render functions\n\nfunc RenderBoard(bid BoardID) string {\n\tboard := getBoard(bid)\n\tif board == nil {\n\t\treturn \"missing board\"\n\t}\n\treturn board.RenderBoard()\n}\n\nfunc Render(path string) string {\n\tif path == \"\" {\n\t\tstr := \"These are all the boards of this realm:\\n\\n\"\n\t\tgBoards.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tboard := value.(*Board)\n\t\t\tstr += \" * [\" + board.url + \"](\" + board.url + \")\\n\"\n\t\t\treturn false\n\t\t})\n\t\treturn str\n\t}\n\tparts := strings.Split(path, \"/\")\n\tif len(parts) == 1 {\n\t\t// /r/demo/boards:BOARD_NAME\n\t\tname := parts[0]\n\t\tboardI, exists := gBoardsByName.Get(name)\n\t\tif !exists {\n\t\t\treturn \"board does not exist: \" + name\n\t\t}\n\t\treturn boardI.(*Board).RenderBoard()\n\t} else if len(parts) == 2 {\n\t\t// /r/demo/boards:BOARD_NAME/THREAD_ID\n\t\tname := parts[0]\n\t\tboardI, exists := gBoardsByName.Get(name)\n\t\tif !exists {\n\t\t\treturn \"board does not exist: \" + name\n\t\t}\n\t\tpid, err := strconv.Atoi(parts[1])\n\t\tif err != nil {\n\t\t\treturn \"invalid thread id: \" + parts[1]\n\t\t}\n\t\tboard := boardI.(*Board)\n\t\tthread := board.GetThread(PostID(pid))\n\t\tif thread == nil {\n\t\t\treturn \"thread does not exist with id: \" + parts[1]\n\t\t}\n\t\treturn thread.RenderPost(\"\", 5)\n\t} else if len(parts) == 3 {\n\t\t// /r/demo/boards:BOARD_NAME/THREAD_ID/REPLY_ID\n\t\tname := parts[0]\n\t\tboardI, exists := gBoardsByName.Get(name)\n\t\tif !exists {\n\t\t\treturn \"board does not exist: \" + name\n\t\t}\n\t\tpid, err := strconv.Atoi(parts[1])\n\t\tif err != nil {\n\t\t\treturn \"invalid thread id: \" + parts[1]\n\t\t}\n\t\tboard := boardI.(*Board)\n\t\tthread := board.GetThread(PostID(pid))\n\t\tif thread == nil {\n\t\t\treturn \"thread does not exist with id: \" + parts[1]\n\t\t}\n\t\trid, err := strconv.Atoi(parts[2])\n\t\tif err != nil {\n\t\t\treturn \"invalid reply id: \" + parts[2]\n\t\t}\n\t\treply := thread.GetReply(PostID(rid))\n\t\tif reply == nil {\n\t\t\treturn \"reply does not exist with id: \" + parts[2]\n\t\t}\n\t\treturn reply.RenderInner()\n\t} else {\n\t\treturn \"unrecognized path \" + path\n\t}\n}\n"},{"name":"role.gno","body":"package boards\n\ntype Permission string\n\nconst (\n\tDeletePermission Permission = \"role:delete\"\n\tEditPermission Permission = \"role:edit\"\n)\n"},{"name":"z_0_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\nimport (\n\t\"gno.land/r/demo/boards\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid := boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// unauthorized\n"},{"name":"z_0_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 19900000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid = boards.CreateBoard(\"test_board\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// payment must not be less than 20000000\n"},{"name":"z_0_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tboards.CreateThread(1, \"First Post (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// board not exist\n"},{"name":"z_0_d_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateReply(bid, 0, 0, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// thread not exist\n"},{"name":"z_0_e_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tboards.CreateReply(bid, 0, 0, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Error:\n// board not exist\n"},{"name":"z_0_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 20000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar bid boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid := boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Output:\n// \\[[post](/r/demo/boards$help\u0026func=CreateThread\u0026bid=1)]\n//\n// ----------------------------------------\n// ## [First Post (title)](/r/demo/boards:test_board/1)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/1) \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)] (0 replies) (0 reposts)\n//\n// ----------------------------------------\n// ## [Second Post (title)](/r/demo/boards:test_board/2)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/2) \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)] (1 replies) (0 reposts)\n"},{"name":"z_10_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// boardId 2 not exist\n\tboards.DeletePost(2, pid, pid, \"\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// board not exist\n"},{"name":"z_10_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// pid of 2 not exist\n\tboards.DeletePost(bid, 2, 2, \"\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// thread not exist\n"},{"name":"z_10_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"First reply of the First post\\n\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.DeletePost(bid, pid, rid, \"\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e First reply of the First post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n//\n// ----------------------------------------------------\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n"},{"name":"z_10_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.DeletePost(bid, pid, pid, \"\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// ----------------------------------------------------\n// thread does not exist with id: 1\n"},{"name":"z_11_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// board 2 not exist\n\tboards.EditPost(2, pid, pid, \"Edited: First Post in (title)\", \"Edited: Body of the first post. (body)\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// board not exist\n"},{"name":"z_11_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// thread 2 not exist\n\tboards.EditPost(bid, 2, pid, \"Edited: First Post in (title)\", \"Edited: Body of the first post. (body)\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// thread not exist\n"},{"name":"z_11_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\t// post 2 not exist\n\tboards.EditPost(bid, pid, 2, \"Edited: First Post in (title)\", \"Edited: Body of the first post. (body)\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// post not exist\n"},{"name":"z_11_d_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"First reply of the First post\\n\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.EditPost(bid, pid, rid, \"\", \"Edited: First reply of the First post\\n\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e First reply of the First post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n//\n// ----------------------------------------------------\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e Edited: First reply of the First post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n"},{"name":"z_11_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tpid = boards.CreateThread(bid, \"First Post in (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n\tboards.EditPost(bid, pid, pid, \"Edited: First Post in (title)\", \"Edited: Body of the first post. (body)\")\n\tprintln(\"----------------------------------------------------\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// ----------------------------------------------------\n// # Edited: First Post in (title)\n//\n// Edited: Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n"},{"name":"z_12_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// create a post via registered user\n\tbid1 := boards.CreateBoard(\"test_board1\")\n\tpid := boards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tbid2 := boards.CreateBoard(\"test_board2\")\n\n\t// create a repost via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOriginCaller(test2)\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\n\trid := boards.CreateRepost(bid1, pid, \"\", \"Check this out\", bid2)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board1\"))\n}\n\n// Error:\n// please register, otherwise minimum fee 100000000 is required if anonymous\n"},{"name":"z_12_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid1 := boards.CreateBoard(\"test_board1\")\n\tpid := boards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tbid2 := boards.CreateBoard(\"test_board2\")\n\n\t// create a repost to a non-existing board\n\trid := boards.CreateRepost(5, pid, \"\", \"Check this out\", bid2)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board1\"))\n}\n\n// Error:\n// src board not exist\n"},{"name":"z_12_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid1 := boards.CreateBoard(\"test_board1\")\n\tboards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tbid2 := boards.CreateBoard(\"test_board2\")\n\n\t// create a repost to a non-existing thread\n\trid := boards.CreateRepost(bid1, 5, \"\", \"Check this out\", bid2)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board1\"))\n}\n\n// Error:\n// thread not exist\n"},{"name":"z_12_d_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tbid1 := boards.CreateBoard(\"test_board1\")\n\tpid := boards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tboards.CreateBoard(\"test_board2\")\n\n\t// create a repost to a non-existing destination board\n\trid := boards.CreateRepost(bid1, pid, \"\", \"Check this out\", 5)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board1\"))\n}\n\n// Error:\n// dst board not exist\n"},{"name":"z_12_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid1 boards.BoardID\n\tbid2 boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid1 = boards.CreateBoard(\"test_board1\")\n\tpid = boards.CreateThread(bid1, \"First Post (title)\", \"Body of the first post. (body)\")\n\tbid2 = boards.CreateBoard(\"test_board2\")\n}\n\nfunc main() {\n\trid := boards.CreateRepost(bid1, pid, \"\", \"Check this out\", bid2)\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board2\"))\n}\n\n// Output:\n// 1\n// \\[[post](/r/demo/boards$help\u0026func=CreateThread\u0026bid=2)]\n//\n// ----------------------------------------\n// Repost: Check this out\n// ## [First Post (title)](/r/demo/boards:test_board1/1)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board1/1) \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)] (0 replies) (1 reposts)\n"},{"name":"z_1_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar board *boards.Board\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\t_ = boards.CreateBoard(\"test_board_1\")\n\t_ = boards.CreateBoard(\"test_board_2\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"\"))\n}\n\n// Output:\n// These are all the boards of this realm:\n//\n// * [/r/demo/boards:test_board_1](/r/demo/boards:test_board_1)\n// * [/r/demo/boards:test_board_2](/r/demo/boards:test_board_2)\n"},{"name":"z_2_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n"},{"name":"z_3_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n}\n\nfunc main() {\n\trid := boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n\tprintln(rid)\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// 3\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n"},{"name":"z_4_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid := boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n\tprintln(rid)\n}\n\nfunc main() {\n\trid2 := boards.CreateReply(bid, pid, pid, \"Second reply of the second post\")\n\tprintln(rid2)\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// 3\n// 4\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n//\n// \u003e Second reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=4)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=4)]\n\n// Realm:\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/boards\"]\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111\",\n// \"ModTime\": \"123\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"68663c8895d37d479e417c11e21badfe21345c61\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:112\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Post\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"3f34ac77289aa1d5f9a2f8b6d083138325816fb0\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {},\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"94a6665a44bac6ede7f3e3b87173e537b12f9532\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"bc8e5b4e782a0bbc4ac9689681f119beb7b34d59\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"9957eadbc91dd32f33b0d815e041a32dbdea0671\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131]={\n// \"Fields\": [\n// {\n// \"N\": \"AAAAgJSeXbo=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"65536\"\n// }\n// },\n// {\n// \"N\": \"AbSNdvQQIhE=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"1024\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Location\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"336074805fc853987abe6f7fe3ad97a6a6f3077a:2\"\n// },\n// \"Index\": \"182\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"65536\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"1024\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Location\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Board\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:84\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"N\": \"BAAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.PostID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"std.Address\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"Second reply of the second post\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"f91e355bd19240f0f3350a7fa0e6a82b72225916\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"9ee9c4117be283fc51ffcc5ecd65b75ecef5a9dd\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"eb768b0140a5fe95f9c58747f0960d647dacfd42\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.PostID\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.PostID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.BoardID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Time\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"0fd3352422af0a56a77ef2c9e88f479054e3d51f\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Time\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"bed4afa8ffdbbf775451c947fc68b27a345ce32a\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132\"\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\",\n// \"IsEscaped\": true,\n// \"ModTime\": \"0\",\n// \"RefCount\": \"2\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Post\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"c45bbd47a46681a63af973db0ec2180922e4a8ae\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127\"\n// }\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120\",\n// \"ModTime\": \"134\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"dc1f011553dc53e7a846049e08cc77fa35ea6a51\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:121\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.Post\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Escaped\": true,\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"96b86b4585c7f1075d7794180a5581f72733a7ab\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136\"\n// }\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"0000000004\"\n// }\n// },\n// {},\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AgAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"32274e1f28fb2b97d67a1262afd362d370de7faa\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"c2cfd6aec36a462f35bf02e5bf4a127aa1bb7ac2\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133]={\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"5cb875179e86d32c517322af7a323b2a5f3e6cc5\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134\"\n// }\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:85]={\n// \"Fields\": [\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/boards.BoardID\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"/r/demo/boards:test_board\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"test_board\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"std.Address\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"a416a751c3a45a1e5cba11e737c51340b081e372\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:86\"\n// }\n// },\n// {\n// \"N\": \"BAAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"65536\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"time.Time\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"36299fccbc13f2a84c4629fad4cb940f0bd4b1c6\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:87\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"af6ed0268f99b7f369329094eb6dfaea7812708b\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:88\"\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:85\",\n// \"ModTime\": \"121\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:84\",\n// \"RefCount\": \"1\"\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"9809329dc1ddc5d3556f7a8fa3c2cebcbf65560b\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106\",\n// \"ModTime\": \"121\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:105\",\n// \"RefCount\": \"1\"\n// }\n// }\n// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"ceae9a1c4ed28bb51062e6ccdccfad0caafd1c4f\",\n// \"ObjectID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107\",\n// \"ModTime\": \"121\",\n// \"OwnerID\": \"f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:105\",\n// \"RefCount\": \"1\"\n// }\n// }\n// switchrealm[\"gno.land/r/demo/boards\"]\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/users\"]\n// switchrealm[\"gno.land/r/demo/boards\"]\n// switchrealm[\"gno.land/r/demo/boards_test\"]\n"},{"name":"z_5_b_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// create board via registered user\n\tbid := boards.CreateBoard(\"test_board\")\n\n\t// create post via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOriginCaller(test2)\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\n\tpid := boards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// please register, otherwise minimum fee 100000000 is required if anonymous\n"},{"name":"z_5_c_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// create board via registered user\n\tbid := boards.CreateBoard(\"test_board\")\n\n\t// create post via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOriginCaller(test2)\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 101000000}}, nil)\n\n\tpid := boards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tboards.CreateReply(bid, pid, pid, \"Reply of the first post\")\n\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post (title)\n//\n// Body of the first post. (body)\n// \\- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/r/demo/users:g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=1)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)]\n//\n// \u003e Reply of the first post\n// \u003e \\- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/r/demo/users:g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=2)]\n"},{"name":"z_5_d_filetest.gno","body":"package main\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\t// create board via registered user\n\tbid := boards.CreateBoard(\"test_board\")\n\tpid := boards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\n\t// create reply via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOriginCaller(test2)\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\tboards.CreateReply(bid, pid, pid, \"Reply of the first post\")\n\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Error:\n// please register, otherwise minimum fee 100000000 is required if anonymous\n"},{"name":"z_5_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid := boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\trid2 := boards.CreateReply(bid, pid, pid, \"Second reply of the second post\\n\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n//\n// \u003e Second reply of the second post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=4)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=4)]\n"},{"name":"z_6_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tboards.CreateReply(bid, pid, pid, \"Second reply of the second post\\n\")\n\tboards.CreateReply(bid, pid, rid, \"First reply of the first reply\\n\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # Second Post (title)\n//\n// Body of the second post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=2)] \\[[repost](/r/demo/boards$help\u0026func=CreateRepost\u0026bid=1\u0026postid=2)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=2)]\n//\n// \u003e Reply of the second post\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n// \u003e\n// \u003e \u003e First reply of the first reply\n// \u003e \u003e\n// \u003e \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/5) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=5)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=5)]\n//\n// \u003e Second reply of the second post\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=4)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=4)]\n"},{"name":"z_7_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nfunc init() {\n\t// register\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\t// create board and post\n\tbid := boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n}\n\nfunc main() {\n\tprintln(boards.Render(\"test_board\"))\n}\n\n// Output:\n// \\[[post](/r/demo/boards$help\u0026func=CreateThread\u0026bid=1)]\n//\n// ----------------------------------------\n// ## [First Post (title)](/r/demo/boards:test_board/1)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/1) \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=1\u0026postid=1)] (0 replies) (0 reposts)\n"},{"name":"z_8_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tbid boards.BoardID\n\tpid boards.PostID\n\trid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tbid = boards.CreateBoard(\"test_board\")\n\tboards.CreateThread(bid, \"First Post (title)\", \"Body of the first post. (body)\")\n\tpid = boards.CreateThread(bid, \"Second Post (title)\", \"Body of the second post. (body)\")\n\trid = boards.CreateReply(bid, pid, pid, \"Reply of the second post\")\n}\n\nfunc main() {\n\tboards.CreateReply(bid, pid, pid, \"Second reply of the second post\\n\")\n\trid2 := boards.CreateReply(bid, pid, rid, \"First reply of the first reply\\n\")\n\tprintln(boards.Render(\"test_board/\" + strconv.Itoa(int(pid)) + \"/\" + strconv.Itoa(int(rid2))))\n}\n\n// Output:\n// _[see thread](/r/demo/boards:test_board/2)_\n//\n// Reply of the second post\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=3)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=3)]\n//\n// _[see all 1 replies](/r/demo/boards:test_board/2/3)_\n//\n// \u003e First reply of the first reply\n// \u003e\n// \u003e \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/5) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=1\u0026threadid=2\u0026postid=5)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=1\u0026threadid=2\u0026postid=5)]\n"},{"name":"z_9_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar dstBoard boards.BoardID\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tdstBoard = boards.CreateBoard(\"dst_board\")\n\n\tboards.CreateRepost(0, 0, \"First Post in (title)\", \"Body of the first post. (body)\", dstBoard)\n}\n\nfunc main() {\n}\n\n// Error:\n// src board not exist\n"},{"name":"z_9_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tsrcBoard boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tsrcBoard = boards.CreateBoard(\"first_board\")\n\tpid = boards.CreateThread(srcBoard, \"First Post in (title)\", \"Body of the first post. (body)\")\n\n\tboards.CreateRepost(srcBoard, pid, \"First Post in (title)\", \"Body of the first post. (body)\", 0)\n}\n\nfunc main() {\n}\n\n// Error:\n// dst board not exist\n"},{"name":"z_9_filetest.gno","body":"// PKGPATH: gno.land/r/demo/boards_test\npackage boards_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"strconv\"\n\n\t\"gno.land/r/demo/boards\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\tfirstBoard boards.BoardID\n\tsecondBoard boards.BoardID\n\tpid boards.PostID\n)\n\nfunc init() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\n\tfirstBoard = boards.CreateBoard(\"first_board\")\n\tsecondBoard = boards.CreateBoard(\"second_board\")\n\tpid = boards.CreateThread(firstBoard, \"First Post in (title)\", \"Body of the first post. (body)\")\n\n\tboards.CreateRepost(firstBoard, pid, \"First Post in (title)\", \"Body of the first post. (body)\", secondBoard)\n}\n\nfunc main() {\n\tprintln(boards.Render(\"second_board/\" + strconv.Itoa(int(pid))))\n}\n\n// Output:\n// # First Post in (title)\n//\n// Body of the first post. (body)\n// \\- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:second_board/1/1) \\[[reply](/r/demo/boards$help\u0026func=CreateReply\u0026bid=2\u0026threadid=1\u0026postid=1)] \\[[x](/r/demo/boards$help\u0026func=DeletePost\u0026bid=2\u0026threadid=1\u0026postid=1)]\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":"groups","path":"gno.land/p/demo/groups","files":[{"name":"groups.gno","body":"package groups\n\nimport \"gno.land/r/demo/boards\"\n\n// TODO implement something and test.\ntype Group struct {\n\tBoard *boards.Board\n}\n"},{"name":"vote_set.gno","body":"package groups\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/demo/rat\"\n)\n\n//----------------------------------------\n// VoteSet\n\ntype VoteSet interface {\n\t// number of present votes in set.\n\tSize() int\n\t// add or update vote for voter.\n\tSetVote(voter std.Address, value string) error\n\t// count the number of votes for value.\n\tCountVotes(value string) int\n}\n\n//----------------------------------------\n// VoteList\n\ntype Vote struct {\n\tVoter std.Address\n\tValue string\n}\n\ntype VoteList []Vote\n\nfunc NewVoteList() *VoteList {\n\treturn \u0026VoteList{}\n}\n\nfunc (vlist *VoteList) Size() int {\n\treturn len(*vlist)\n}\n\nfunc (vlist *VoteList) SetVote(voter std.Address, value string) error {\n\t// TODO optimize with binary algorithm\n\tfor i, vote := range *vlist {\n\t\tif vote.Voter == voter {\n\t\t\t// update vote\n\t\t\t(*vlist)[i] = Vote{\n\t\t\t\tVoter: voter,\n\t\t\t\tValue: value,\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t}\n\t*vlist = append(*vlist, Vote{\n\t\tVoter: voter,\n\t\tValue: value,\n\t})\n\treturn nil\n}\n\nfunc (vlist *VoteList) CountVotes(target string) int {\n\t// TODO optimize with binary algorithm\n\tvar count int\n\tfor _, vote := range *vlist {\n\t\tif vote.Value == target {\n\t\t\tcount++\n\t\t}\n\t}\n\treturn count\n}\n\n//----------------------------------------\n// Committee\n\ntype Committee struct {\n\tQuorum rat.Rat\n\tThreshold rat.Rat\n\tAddresses std.AddressSet\n}\n\n//----------------------------------------\n// VoteSession\n// NOTE: this seems a bit too formal and\n// complicated vs what might be possible;\n// something simpler, more informal.\n\ntype SessionStatus int\n\nconst (\n\tSessionNew SessionStatus = iota\n\tSessionStarted\n\tSessionCompleted\n\tSessionCanceled\n)\n\ntype VoteSession struct {\n\tName string\n\tCreator std.Address\n\tBody string\n\tStart time.Time\n\tDeadline time.Time\n\tStatus SessionStatus\n\tCommittee *Committee\n\tVotes VoteSet\n\tChoices []string\n\tResult string\n}\n"},{"name":"z_1_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\nimport (\n\t\"gno.land/p/demo/groups\"\n\t\"gno.land/p/demo/testutils\"\n)\n\nvar vset groups.VoteSet\n\nfunc init() {\n\taddr1 := testutils.TestAddress(\"test1\")\n\taddr2 := testutils.TestAddress(\"test2\")\n\tvset = groups.NewVoteList()\n\tvset.SetVote(addr1, \"yes\")\n\tvset.SetVote(addr2, \"yes\")\n}\n\nfunc main() {\n\tprintln(vset.Size())\n\tprintln(\"yes:\", vset.CountVotes(\"yes\"))\n\tprintln(\"no:\", vset.CountVotes(\"no\"))\n}\n\n// Output:\n// 2\n// yes: 2\n// no: 0\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":"uint256","path":"gno.land/p/demo/uint256","files":[{"name":"LICENSE","body":"BSD 3-Clause License\n\nCopyright 2020 uint256 Authors\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this\n list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright notice,\n this list of conditions and the following disclaimer in the documentation\n and/or other materials provided with the distribution.\n\n3. Neither the name of the copyright holder nor the names of its\n contributors may be used to endorse or promote products derived from\n this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"},{"name":"README.md","body":"# Fixed size 256-bit math library\n\nThis is a library specialized at replacing the `big.Int` library for math based on 256-bit types.\n\noriginal repository: [uint256](\u003chttps://github.com/holiman/uint256/tree/master\u003e)\n"},{"name":"arithmetic.gno","body":"// arithmetic provides arithmetic operations for Uint objects.\n// This includes basic binary operations such as addition, subtraction, multiplication, division, and modulo operations\n// as well as overflow checks, and negation. These functions are essential for numeric\n// calculations using 256-bit unsigned integers.\npackage uint256\n\nimport (\n\t\"math/bits\"\n)\n\n// Add sets z to the sum x+y\nfunc (z *Uint) Add(x, y *Uint) *Uint {\n\tvar carry uint64\n\tz.arr[0], carry = bits.Add64(x.arr[0], y.arr[0], 0)\n\tz.arr[1], carry = bits.Add64(x.arr[1], y.arr[1], carry)\n\tz.arr[2], carry = bits.Add64(x.arr[2], y.arr[2], carry)\n\tz.arr[3], _ = bits.Add64(x.arr[3], y.arr[3], carry)\n\treturn z\n}\n\n// AddOverflow sets z to the sum x+y, and returns z and whether overflow occurred\nfunc (z *Uint) AddOverflow(x, y *Uint) (*Uint, bool) {\n\tvar carry uint64\n\tz.arr[0], carry = bits.Add64(x.arr[0], y.arr[0], 0)\n\tz.arr[1], carry = bits.Add64(x.arr[1], y.arr[1], carry)\n\tz.arr[2], carry = bits.Add64(x.arr[2], y.arr[2], carry)\n\tz.arr[3], carry = bits.Add64(x.arr[3], y.arr[3], carry)\n\treturn z, carry != 0\n}\n\n// Sub sets z to the difference x-y\nfunc (z *Uint) Sub(x, y *Uint) *Uint {\n\tvar carry uint64\n\tz.arr[0], carry = bits.Sub64(x.arr[0], y.arr[0], 0)\n\tz.arr[1], carry = bits.Sub64(x.arr[1], y.arr[1], carry)\n\tz.arr[2], carry = bits.Sub64(x.arr[2], y.arr[2], carry)\n\tz.arr[3], _ = bits.Sub64(x.arr[3], y.arr[3], carry)\n\treturn z\n}\n\n// SubOverflow sets z to the difference x-y and returns z and true if the operation underflowed\nfunc (z *Uint) SubOverflow(x, y *Uint) (*Uint, bool) {\n\tvar carry uint64\n\tz.arr[0], carry = bits.Sub64(x.arr[0], y.arr[0], 0)\n\tz.arr[1], carry = bits.Sub64(x.arr[1], y.arr[1], carry)\n\tz.arr[2], carry = bits.Sub64(x.arr[2], y.arr[2], carry)\n\tz.arr[3], carry = bits.Sub64(x.arr[3], y.arr[3], carry)\n\treturn z, carry != 0\n}\n\n// Neg returns -x mod 2^256.\nfunc (z *Uint) Neg(x *Uint) *Uint {\n\treturn z.Sub(new(Uint), x)\n}\n\n// commented out for possible overflow\n// Mul sets z to the product x*y\nfunc (z *Uint) Mul(x, y *Uint) *Uint {\n\tvar (\n\t\tres Uint\n\t\tcarry uint64\n\t\tres1, res2, res3 uint64\n\t)\n\n\tcarry, res.arr[0] = bits.Mul64(x.arr[0], y.arr[0])\n\tcarry, res1 = umulHop(carry, x.arr[1], y.arr[0])\n\tcarry, res2 = umulHop(carry, x.arr[2], y.arr[0])\n\tres3 = x.arr[3]*y.arr[0] + carry\n\n\tcarry, res.arr[1] = umulHop(res1, x.arr[0], y.arr[1])\n\tcarry, res2 = umulStep(res2, x.arr[1], y.arr[1], carry)\n\tres3 = res3 + x.arr[2]*y.arr[1] + carry\n\n\tcarry, res.arr[2] = umulHop(res2, x.arr[0], y.arr[2])\n\tres3 = res3 + x.arr[1]*y.arr[2] + carry\n\n\tres.arr[3] = res3 + x.arr[0]*y.arr[3]\n\n\treturn z.Set(\u0026res)\n}\n\n// MulOverflow sets z to the product x*y, and returns z and whether overflow occurred\nfunc (z *Uint) MulOverflow(x, y *Uint) (*Uint, bool) {\n\tp := umul(x, y)\n\tcopy(z.arr[:], p[:4])\n\treturn z, (p[4] | p[5] | p[6] | p[7]) != 0\n}\n\n// commented out for possible overflow\n// Div sets z to the quotient x/y for returns z.\n// If y == 0, z is set to 0\nfunc (z *Uint) Div(x, y *Uint) *Uint {\n\tif y.IsZero() || y.Gt(x) {\n\t\treturn z.Clear()\n\t}\n\tif x.Eq(y) {\n\t\treturn z.SetOne()\n\t}\n\t// Shortcut some cases\n\tif x.IsUint64() {\n\t\treturn z.SetUint64(x.Uint64() / y.Uint64())\n\t}\n\n\t// At this point, we know\n\t// x/y ; x \u003e y \u003e 0\n\n\tvar quot Uint\n\tudivrem(quot.arr[:], x.arr[:], y)\n\treturn z.Set(\u0026quot)\n}\n\n// MulMod calculates the modulo-m multiplication of x and y and\n// returns z.\n// If m == 0, z is set to 0 (OBS: differs from the big.Int)\nfunc (z *Uint) MulMod(x, y, m *Uint) *Uint {\n\tif x.IsZero() || y.IsZero() || m.IsZero() {\n\t\treturn z.Clear()\n\t}\n\tp := umul(x, y)\n\n\tif m.arr[3] != 0 {\n\t\tmu := Reciprocal(m)\n\t\tr := reduce4(p, m, mu)\n\t\treturn z.Set(\u0026r)\n\t}\n\n\tvar (\n\t\tpl Uint\n\t\tph Uint\n\t)\n\n\tpl = Uint{arr: [4]uint64{p[0], p[1], p[2], p[3]}}\n\tph = Uint{arr: [4]uint64{p[4], p[5], p[6], p[7]}}\n\n\t// If the multiplication is within 256 bits use Mod().\n\tif ph.IsZero() {\n\t\treturn z.Mod(\u0026pl, m)\n\t}\n\n\tvar quot [8]uint64\n\trem := udivrem(quot[:], p[:], m)\n\treturn z.Set(\u0026rem)\n}\n\n// Mod sets z to the modulus x%y for y != 0 and returns z.\n// If y == 0, z is set to 0 (OBS: differs from the big.Uint)\nfunc (z *Uint) Mod(x, y *Uint) *Uint {\n\tif x.IsZero() || y.IsZero() {\n\t\treturn z.Clear()\n\t}\n\tswitch x.Cmp(y) {\n\tcase -1:\n\t\t// x \u003c y\n\t\tcopy(z.arr[:], x.arr[:])\n\t\treturn z\n\tcase 0:\n\t\t// x == y\n\t\treturn z.Clear() // They are equal\n\t}\n\n\t// At this point:\n\t// x != 0\n\t// y != 0\n\t// x \u003e y\n\n\t// Shortcut trivial case\n\tif x.IsUint64() {\n\t\treturn z.SetUint64(x.Uint64() % y.Uint64())\n\t}\n\n\tvar quot Uint\n\t*z = udivrem(quot.arr[:], x.arr[:], y)\n\treturn z\n}\n\n// DivMod sets z to the quotient x div y and m to the modulus x mod y and returns the pair (z, m) for y != 0.\n// If y == 0, both z and m are set to 0 (OBS: differs from the big.Int)\nfunc (z *Uint) DivMod(x, y, m *Uint) (*Uint, *Uint) {\n\tif y.IsZero() {\n\t\treturn z.Clear(), m.Clear()\n\t}\n\tvar quot Uint\n\t*m = udivrem(quot.arr[:], x.arr[:], y)\n\t*z = quot\n\treturn z, m\n}\n\n// Exp sets z = base**exponent mod 2**256, and returns z.\nfunc (z *Uint) Exp(base, exponent *Uint) *Uint {\n\tres := Uint{arr: [4]uint64{1, 0, 0, 0}}\n\tmultiplier := *base\n\texpBitLen := exponent.BitLen()\n\n\tcurBit := 0\n\tword := exponent.arr[0]\n\tfor ; curBit \u003c expBitLen \u0026\u0026 curBit \u003c 64; curBit++ {\n\t\tif word\u00261 == 1 {\n\t\t\tres.Mul(\u0026res, \u0026multiplier)\n\t\t}\n\t\tmultiplier.squared()\n\t\tword \u003e\u003e= 1\n\t}\n\n\tword = exponent.arr[1]\n\tfor ; curBit \u003c expBitLen \u0026\u0026 curBit \u003c 128; curBit++ {\n\t\tif word\u00261 == 1 {\n\t\t\tres.Mul(\u0026res, \u0026multiplier)\n\t\t}\n\t\tmultiplier.squared()\n\t\tword \u003e\u003e= 1\n\t}\n\n\tword = exponent.arr[2]\n\tfor ; curBit \u003c expBitLen \u0026\u0026 curBit \u003c 192; curBit++ {\n\t\tif word\u00261 == 1 {\n\t\t\tres.Mul(\u0026res, \u0026multiplier)\n\t\t}\n\t\tmultiplier.squared()\n\t\tword \u003e\u003e= 1\n\t}\n\n\tword = exponent.arr[3]\n\tfor ; curBit \u003c expBitLen \u0026\u0026 curBit \u003c 256; curBit++ {\n\t\tif word\u00261 == 1 {\n\t\t\tres.Mul(\u0026res, \u0026multiplier)\n\t\t}\n\t\tmultiplier.squared()\n\t\tword \u003e\u003e= 1\n\t}\n\treturn z.Set(\u0026res)\n}\n\nfunc (z *Uint) squared() {\n\tvar (\n\t\tres Uint\n\t\tcarry0, carry1, carry2 uint64\n\t\tres1, res2 uint64\n\t)\n\n\tcarry0, res.arr[0] = bits.Mul64(z.arr[0], z.arr[0])\n\tcarry0, res1 = umulHop(carry0, z.arr[0], z.arr[1])\n\tcarry0, res2 = umulHop(carry0, z.arr[0], z.arr[2])\n\n\tcarry1, res.arr[1] = umulHop(res1, z.arr[0], z.arr[1])\n\tcarry1, res2 = umulStep(res2, z.arr[1], z.arr[1], carry1)\n\n\tcarry2, res.arr[2] = umulHop(res2, z.arr[0], z.arr[2])\n\n\tres.arr[3] = 2*(z.arr[0]*z.arr[3]+z.arr[1]*z.arr[2]) + carry0 + carry1 + carry2\n\n\tz.Set(\u0026res)\n}\n\n// udivrem divides u by d and produces both quotient and remainder.\n// The quotient is stored in provided quot - len(u)-len(d)+1 words.\n// It loosely follows the Knuth's division algorithm (sometimes referenced as \"schoolbook\" division) using 64-bit words.\n// See Knuth, Volume 2, section 4.3.1, Algorithm D.\nfunc udivrem(quot, u []uint64, d *Uint) (rem Uint) {\n\tvar dLen int\n\tfor i := len(d.arr) - 1; i \u003e= 0; i-- {\n\t\tif d.arr[i] != 0 {\n\t\t\tdLen = i + 1\n\t\t\tbreak\n\t\t}\n\t}\n\n\tshift := uint(bits.LeadingZeros64(d.arr[dLen-1]))\n\n\tvar dnStorage Uint\n\tdn := dnStorage.arr[:dLen]\n\tfor i := dLen - 1; i \u003e 0; i-- {\n\t\tdn[i] = (d.arr[i] \u003c\u003c shift) | (d.arr[i-1] \u003e\u003e (64 - shift))\n\t}\n\tdn[0] = d.arr[0] \u003c\u003c shift\n\n\tvar uLen int\n\tfor i := len(u) - 1; i \u003e= 0; i-- {\n\t\tif u[i] != 0 {\n\t\t\tuLen = i + 1\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif uLen \u003c dLen {\n\t\tcopy(rem.arr[:], u)\n\t\treturn rem\n\t}\n\n\tvar unStorage [9]uint64\n\tun := unStorage[:uLen+1]\n\tun[uLen] = u[uLen-1] \u003e\u003e (64 - shift)\n\tfor i := uLen - 1; i \u003e 0; i-- {\n\t\tun[i] = (u[i] \u003c\u003c shift) | (u[i-1] \u003e\u003e (64 - shift))\n\t}\n\tun[0] = u[0] \u003c\u003c shift\n\n\t// TODO: Skip the highest word of numerator if not significant.\n\n\tif dLen == 1 {\n\t\tr := udivremBy1(quot, un, dn[0])\n\t\trem.SetUint64(r \u003e\u003e shift)\n\t\treturn rem\n\t}\n\n\tudivremKnuth(quot, un, dn)\n\n\tfor i := 0; i \u003c dLen-1; i++ {\n\t\trem.arr[i] = (un[i] \u003e\u003e shift) | (un[i+1] \u003c\u003c (64 - shift))\n\t}\n\trem.arr[dLen-1] = un[dLen-1] \u003e\u003e shift\n\n\treturn rem\n}\n\n// umul computes full 256 x 256 -\u003e 512 multiplication.\nfunc umul(x, y *Uint) [8]uint64 {\n\tvar (\n\t\tres [8]uint64\n\t\tcarry, carry4, carry5, carry6 uint64\n\t\tres1, res2, res3, res4, res5 uint64\n\t)\n\n\tcarry, res[0] = bits.Mul64(x.arr[0], y.arr[0])\n\tcarry, res1 = umulHop(carry, x.arr[1], y.arr[0])\n\tcarry, res2 = umulHop(carry, x.arr[2], y.arr[0])\n\tcarry4, res3 = umulHop(carry, x.arr[3], y.arr[0])\n\n\tcarry, res[1] = umulHop(res1, x.arr[0], y.arr[1])\n\tcarry, res2 = umulStep(res2, x.arr[1], y.arr[1], carry)\n\tcarry, res3 = umulStep(res3, x.arr[2], y.arr[1], carry)\n\tcarry5, res4 = umulStep(carry4, x.arr[3], y.arr[1], carry)\n\n\tcarry, res[2] = umulHop(res2, x.arr[0], y.arr[2])\n\tcarry, res3 = umulStep(res3, x.arr[1], y.arr[2], carry)\n\tcarry, res4 = umulStep(res4, x.arr[2], y.arr[2], carry)\n\tcarry6, res5 = umulStep(carry5, x.arr[3], y.arr[2], carry)\n\n\tcarry, res[3] = umulHop(res3, x.arr[0], y.arr[3])\n\tcarry, res[4] = umulStep(res4, x.arr[1], y.arr[3], carry)\n\tcarry, res[5] = umulStep(res5, x.arr[2], y.arr[3], carry)\n\tres[7], res[6] = umulStep(carry6, x.arr[3], y.arr[3], carry)\n\n\treturn res\n}\n\n// umulStep computes (hi * 2^64 + lo) = z + (x * y) + carry.\nfunc umulStep(z, x, y, carry uint64) (hi, lo uint64) {\n\thi, lo = bits.Mul64(x, y)\n\tlo, carry = bits.Add64(lo, carry, 0)\n\thi, _ = bits.Add64(hi, 0, carry)\n\tlo, carry = bits.Add64(lo, z, 0)\n\thi, _ = bits.Add64(hi, 0, carry)\n\treturn hi, lo\n}\n\n// umulHop computes (hi * 2^64 + lo) = z + (x * y)\nfunc umulHop(z, x, y uint64) (hi, lo uint64) {\n\thi, lo = bits.Mul64(x, y)\n\tlo, carry := bits.Add64(lo, z, 0)\n\thi, _ = bits.Add64(hi, 0, carry)\n\treturn hi, lo\n}\n\n// udivremBy1 divides u by single normalized word d and produces both quotient and remainder.\n// The quotient is stored in provided quot.\nfunc udivremBy1(quot, u []uint64, d uint64) (rem uint64) {\n\treciprocal := reciprocal2by1(d)\n\trem = u[len(u)-1] // Set the top word as remainder.\n\tfor j := len(u) - 2; j \u003e= 0; j-- {\n\t\tquot[j], rem = udivrem2by1(rem, u[j], d, reciprocal)\n\t}\n\treturn rem\n}\n\n// udivremKnuth implements the division of u by normalized multiple word d from the Knuth's division algorithm.\n// The quotient is stored in provided quot - len(u)-len(d) words.\n// Updates u to contain the remainder - len(d) words.\nfunc udivremKnuth(quot, u, d []uint64) {\n\tdh := d[len(d)-1]\n\tdl := d[len(d)-2]\n\treciprocal := reciprocal2by1(dh)\n\n\tfor j := len(u) - len(d) - 1; j \u003e= 0; j-- {\n\t\tu2 := u[j+len(d)]\n\t\tu1 := u[j+len(d)-1]\n\t\tu0 := u[j+len(d)-2]\n\n\t\tvar qhat, rhat uint64\n\t\tif u2 \u003e= dh { // Division overflows.\n\t\t\tqhat = ^uint64(0)\n\t\t\t// TODO: Add \"qhat one to big\" adjustment (not needed for correctness, but helps avoiding \"add back\" case).\n\t\t} else {\n\t\t\tqhat, rhat = udivrem2by1(u2, u1, dh, reciprocal)\n\t\t\tph, pl := bits.Mul64(qhat, dl)\n\t\t\tif ph \u003e rhat || (ph == rhat \u0026\u0026 pl \u003e u0) {\n\t\t\t\tqhat--\n\t\t\t\t// TODO: Add \"qhat one to big\" adjustment (not needed for correctness, but helps avoiding \"add back\" case).\n\t\t\t}\n\t\t}\n\n\t\t// Multiply and subtract.\n\t\tborrow := subMulTo(u[j:], d, qhat)\n\t\tu[j+len(d)] = u2 - borrow\n\t\tif u2 \u003c borrow { // Too much subtracted, add back.\n\t\t\tqhat--\n\t\t\tu[j+len(d)] += addTo(u[j:], d)\n\t\t}\n\n\t\tquot[j] = qhat // Store quotient digit.\n\t}\n}\n\n// isBitSet returns true if bit n-th is set, where n = 0 is LSB.\n// The n must be \u003c= 255.\nfunc (z *Uint) isBitSet(n uint) bool {\n\treturn (z.arr[n/64] \u0026 (1 \u003c\u003c (n % 64))) != 0\n}\n\n// addTo computes x += y.\n// Requires len(x) \u003e= len(y).\nfunc addTo(x, y []uint64) uint64 {\n\tvar carry uint64\n\tfor i := 0; i \u003c len(y); i++ {\n\t\tx[i], carry = bits.Add64(x[i], y[i], carry)\n\t}\n\treturn carry\n}\n\n// subMulTo computes x -= y * multiplier.\n// Requires len(x) \u003e= len(y).\nfunc subMulTo(x, y []uint64, multiplier uint64) uint64 {\n\tvar borrow uint64\n\tfor i := 0; i \u003c len(y); i++ {\n\t\ts, carry1 := bits.Sub64(x[i], borrow, 0)\n\t\tph, pl := bits.Mul64(y[i], multiplier)\n\t\tt, carry2 := bits.Sub64(s, pl, 0)\n\t\tx[i] = t\n\t\tborrow = ph + carry1 + carry2\n\t}\n\treturn borrow\n}\n\n// reciprocal2by1 computes \u003c^d, ^0\u003e / d.\nfunc reciprocal2by1(d uint64) uint64 {\n\treciprocal, _ := bits.Div64(^d, ^uint64(0), d)\n\treturn reciprocal\n}\n\n// udivrem2by1 divides \u003cuh, ul\u003e / d and produces both quotient and remainder.\n// It uses the provided d's reciprocal.\n// Implementation ported from https://github.com/chfast/intx and is based on\n// \"Improved division by invariant integers\", Algorithm 4.\nfunc udivrem2by1(uh, ul, d, reciprocal uint64) (quot, rem uint64) {\n\tqh, ql := bits.Mul64(reciprocal, uh)\n\tql, carry := bits.Add64(ql, ul, 0)\n\tqh, _ = bits.Add64(qh, uh, carry)\n\tqh++\n\n\tr := ul - qh*d\n\n\tif r \u003e ql {\n\t\tqh--\n\t\tr += d\n\t}\n\n\tif r \u003e= d {\n\t\tqh++\n\t\tr -= d\n\t}\n\n\treturn qh, r\n}\n"},{"name":"arithmetic_test.gno","body":"package uint256\n\nimport (\n\t\"testing\"\n)\n\ntype binOp2Test struct {\n\tx, y, want string\n}\n\nfunc TestAdd(t *testing.T) {\n\ttests := []binOp2Test{\n\t\t{\"0\", \"1\", \"1\"},\n\t\t{\"1\", \"0\", \"1\"},\n\t\t{\"1\", \"1\", \"2\"},\n\t\t{\"1\", \"3\", \"4\"},\n\t\t{\"10\", \"10\", \"20\"},\n\t\t{\"18446744073709551615\", \"18446744073709551615\", \"36893488147419103230\"}, // uint64 overflow\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\ty := MustFromDecimal(tt.y)\n\n\t\twant := MustFromDecimal(tt.want)\n\t\tgot := new(Uint).Add(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Add(%s, %s) = %v, want %v\", tt.x, tt.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestAddOverflow(t *testing.T) {\n\ttests := []struct {\n\t\tx, y string\n\t\twant string\n\t\toverflow bool\n\t}{\n\t\t{\"0\", \"1\", \"1\", false},\n\t\t{\"1\", \"0\", \"1\", false},\n\t\t{\"1\", \"1\", \"2\", false},\n\t\t{\"10\", \"10\", \"20\", false},\n\t\t{\"18446744073709551615\", \"18446744073709551615\", \"36893488147419103230\", false}, // uint64 overflow, but not Uint256 overflow\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"1\", \"0\", true}, // 2^256 - 1 + 1, should overflow\n\t\t{\"57896044618658097711785492504343953926634992332820282019728792003956564819967\", \"57896044618658097711785492504343953926634992332820282019728792003956564819968\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\", false}, // (2^255 - 1) + 2^255, no overflow\n\t\t{\"57896044618658097711785492504343953926634992332820282019728792003956564819967\", \"57896044618658097711785492504343953926634992332820282019728792003956564819969\", \"0\", true}, // (2^255 - 1) + (2^255 + 1), should overflow\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\ty := MustFromDecimal(tt.y)\n\t\twant, _ := FromDecimal(tt.want)\n\n\t\tgot, overflow := new(Uint).AddOverflow(x, y)\n\n\t\tif got.Cmp(want) != 0 || overflow != tt.overflow {\n\t\t\tt.Errorf(\"AddOverflow(%s, %s) = (%s, %v), want (%s, %v)\",\n\t\t\t\ttt.x, tt.y, got.ToString(), overflow, tt.want, tt.overflow)\n\t\t}\n\t}\n}\n\nfunc TestSub(t *testing.T) {\n\ttests := []binOp2Test{\n\t\t{\"1\", \"0\", \"1\"},\n\t\t{\"1\", \"1\", \"0\"},\n\t\t{\"10\", \"10\", \"0\"},\n\t\t{\"31337\", \"1337\", \"30000\"},\n\t\t{\"2\", \"3\", twoPow256Sub1}, // underflow\n\t}\n\n\tfor _, tc := range tests {\n\t\tx := MustFromDecimal(tc.x)\n\t\ty := MustFromDecimal(tc.y)\n\n\t\twant := MustFromDecimal(tc.want)\n\n\t\tgot := new(Uint).Sub(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\n\t\t\t\t\"Sub(%s, %s) = %v, want %v\",\n\t\t\t\ttc.x, tc.y, got.ToString(), want.ToString(),\n\t\t\t)\n\t\t}\n\t}\n}\n\nfunc TestSubOverflow(t *testing.T) {\n\ttests := []struct {\n\t\tx, y string\n\t\twant string\n\t\toverflow bool\n\t}{\n\t\t{\"1\", \"0\", \"1\", false},\n\t\t{\"1\", \"1\", \"0\", false},\n\t\t{\"10\", \"10\", \"0\", false},\n\t\t{\"31337\", \"1337\", \"30000\", false},\n\t\t{\"0\", \"1\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\", true}, // 0 - 1, should underflow\n\t\t{\"57896044618658097711785492504343953926634992332820282019728792003956564819968\", \"1\", \"57896044618658097711785492504343953926634992332820282019728792003956564819967\", false}, // 2^255 - 1, no underflow\n\t\t{\"57896044618658097711785492504343953926634992332820282019728792003956564819968\", \"57896044618658097711785492504343953926634992332820282019728792003956564819969\", \"115792089237316195423570985008687907853269984665640564039457584007913129639935\", true}, // 2^255 - (2^255 + 1), should underflow\n\t}\n\n\tfor _, tc := range tests {\n\t\tx := MustFromDecimal(tc.x)\n\t\ty := MustFromDecimal(tc.y)\n\t\twant := MustFromDecimal(tc.want)\n\n\t\tgot, overflow := new(Uint).SubOverflow(x, y)\n\n\t\tif got.Cmp(want) != 0 || overflow != tc.overflow {\n\t\t\tt.Errorf(\n\t\t\t\t\"SubOverflow(%s, %s) = (%s, %v), want (%s, %v)\",\n\t\t\t\ttc.x, tc.y, got.ToString(), overflow, tc.want, tc.overflow,\n\t\t\t)\n\t\t}\n\t}\n}\n\nfunc TestMul(t *testing.T) {\n\ttests := []binOp2Test{\n\t\t{\"1\", \"0\", \"0\"},\n\t\t{\"1\", \"1\", \"1\"},\n\t\t{\"10\", \"10\", \"100\"},\n\t\t{\"18446744073709551615\", \"2\", \"36893488147419103230\"}, // uint64 overflow\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\ty := MustFromDecimal(tt.y)\n\t\twant := MustFromDecimal(tt.want)\n\t\tgot := new(Uint).Mul(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Mul(%s, %s) = %v, want %v\", tt.x, tt.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestMulOverflow(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty string\n\t\twantZ string\n\t\twantOver bool\n\t}{\n\t\t{\"0x1\", \"0x1\", \"0x1\", false},\n\t\t{\"0x0\", \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0x0\", false},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0x2\", \"0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe\", true},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0x1\", true},\n\t\t{\"0x8000000000000000000000000000000000000000000000000000000000000000\", \"0x2\", \"0x0\", true},\n\t\t{\"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0x2\", \"0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe\", false},\n\t\t{\"0x100000000000000000\", \"0x100000000000000000\", \"0x10000000000000000000000000000000000\", false},\n\t\t{\"0x10000000000000000000000000000000\", \"0x10000000000000000000000000000000\", \"0x100000000000000000000000000000000000000000000000000000000000000\", false},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromHex(tt.x)\n\t\ty := MustFromHex(tt.y)\n\t\twantZ := MustFromHex(tt.wantZ)\n\n\t\tgotZ, gotOver := new(Uint).MulOverflow(x, y)\n\n\t\tif gotZ.Neq(wantZ) {\n\t\t\tt.Errorf(\n\t\t\t\t\"MulOverflow(%s, %s) = %s, want %s\",\n\t\t\t\ttt.x, tt.y, gotZ.ToString(), wantZ.ToString(),\n\t\t\t)\n\t\t}\n\t\tif gotOver != tt.wantOver {\n\t\t\tt.Errorf(\"MulOverflow(%s, %s) = %v, want %v\", tt.x, tt.y, gotOver, tt.wantOver)\n\t\t}\n\t}\n}\n\nfunc TestDiv(t *testing.T) {\n\ttests := []binOp2Test{\n\t\t{\"31337\", \"3\", \"10445\"},\n\t\t{\"31337\", \"0\", \"0\"},\n\t\t{\"0\", \"31337\", \"0\"},\n\t\t{\"1\", \"1\", \"1\"},\n\t\t{\"1000000000000000000\", \"3\", \"333333333333333333\"},\n\t\t{twoPow256Sub1, \"2\", \"57896044618658097711785492504343953926634992332820282019728792003956564819967\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\ty := MustFromDecimal(tt.y)\n\t\twant := MustFromDecimal(tt.want)\n\n\t\tgot := new(Uint).Div(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Div(%s, %s) = %v, want %v\", tt.x, tt.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestMod(t *testing.T) {\n\ttests := []binOp2Test{\n\t\t{\"31337\", \"3\", \"2\"},\n\t\t{\"31337\", \"0\", \"0\"},\n\t\t{\"0\", \"31337\", \"0\"},\n\t\t{\"2\", \"31337\", \"2\"},\n\t\t{\"1\", \"1\", \"0\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"2\", \"1\"}, // 2^256 - 1 mod 2\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"3\", \"0\"}, // 2^256 - 1 mod 3\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129639935\", \"57896044618658097711785492504343953926634992332820282019728792003956564819968\", \"57896044618658097711785492504343953926634992332820282019728792003956564819967\"}, // 2^256 - 1 mod 2^255\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\ty := MustFromDecimal(tt.y)\n\t\twant := MustFromDecimal(tt.want)\n\n\t\tgot := new(Uint).Mod(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Mod(%s, %s) = %v, want %v\", tt.x, tt.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestMulMod(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty string\n\t\tm string\n\t\twant string\n\t}{\n\t\t{\"0x1\", \"0x1\", \"0x2\", \"0x1\"},\n\t\t{\"0x10\", \"0x10\", \"0x7\", \"0x4\"},\n\t\t{\"0x100\", \"0x100\", \"0x17\", \"0x9\"},\n\t\t{\"0x31337\", \"0x31337\", \"0x31338\", \"0x1\"},\n\t\t{\"0x0\", \"0x31337\", \"0x31338\", \"0x0\"},\n\t\t{\"0x31337\", \"0x0\", \"0x31338\", \"0x0\"},\n\t\t{\"0x2\", \"0x3\", \"0x5\", \"0x1\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0x0\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe\", \"0x1\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"0xffffffffffffffffffffffffffffffff\", \"0x0\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromHex(tt.x)\n\t\ty := MustFromHex(tt.y)\n\t\tm := MustFromHex(tt.m)\n\t\twant := MustFromHex(tt.want)\n\n\t\tgot := new(Uint).MulMod(x, y, m)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\n\t\t\t\t\"MulMod(%s, %s, %s) = %s, want %s\",\n\t\t\t\ttt.x, tt.y, tt.m, got.ToString(), want.ToString(),\n\t\t\t)\n\t\t}\n\t}\n}\n\nfunc TestDivMod(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty string\n\t\twantDiv string\n\t\twantMod string\n\t}{\n\t\t{\"1\", \"1\", \"1\", \"0\"},\n\t\t{\"10\", \"10\", \"1\", \"0\"},\n\t\t{\"100\", \"10\", \"10\", \"0\"},\n\t\t{\"31337\", \"3\", \"10445\", \"2\"},\n\t\t{\"31337\", \"0\", \"0\", \"0\"},\n\t\t{\"0\", \"31337\", \"0\", \"0\"},\n\t\t{\"2\", \"31337\", \"0\", \"2\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\ty := MustFromDecimal(tt.y)\n\t\twantDiv := MustFromDecimal(tt.wantDiv)\n\t\twantMod := MustFromDecimal(tt.wantMod)\n\n\t\tgotDiv := new(Uint)\n\t\tgotMod := new(Uint)\n\t\tgotDiv.DivMod(x, y, gotMod)\n\n\t\tfor i := range gotDiv.arr {\n\t\t\tif gotDiv.arr[i] != wantDiv.arr[i] {\n\t\t\t\tt.Errorf(\"DivMod(%s, %s) got Div %v, want Div %v\", tt.x, tt.y, gotDiv, wantDiv)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfor i := range gotMod.arr {\n\t\t\tif gotMod.arr[i] != wantMod.arr[i] {\n\t\t\t\tt.Errorf(\"DivMod(%s, %s) got Mod %v, want Mod %v\", tt.x, tt.y, gotMod, wantMod)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestNeg(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant string\n\t}{\n\t\t{\"31337\", \"115792089237316195423570985008687907853269984665640564039457584007913129608599\"},\n\t\t{\"115792089237316195423570985008687907853269984665640564039457584007913129608599\", \"31337\"},\n\t\t{\"0\", \"0\"},\n\t\t{\"2\", \"115792089237316195423570985008687907853269984665640564039457584007913129639934\"},\n\t\t{\"1\", twoPow256Sub1},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\twant := MustFromDecimal(tt.want)\n\n\t\tgot := new(Uint).Neg(x)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Neg(%s) = %v, want %v\", tt.x, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestExp(t *testing.T) {\n\ttests := []binOp2Test{\n\t\t{\"31337\", \"3\", \"30773171189753\"},\n\t\t{\"31337\", \"0\", \"1\"},\n\t\t{\"0\", \"31337\", \"0\"},\n\t\t{\"1\", \"1\", \"1\"},\n\t\t{\"2\", \"3\", \"8\"},\n\t\t{\"2\", \"64\", \"18446744073709551616\"},\n\t\t{\"2\", \"128\", \"340282366920938463463374607431768211456\"},\n\t\t{\"2\", \"255\", \"57896044618658097711785492504343953926634992332820282019728792003956564819968\"},\n\t\t{\"2\", \"256\", \"0\"}, // overflow\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\ty := MustFromDecimal(tt.y)\n\t\twant := MustFromDecimal(tt.want)\n\n\t\tgot := new(Uint).Exp(x, y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\n\t\t\t\t\"Exp(%s, %s) = %v, want %v\",\n\t\t\t\ttt.x, tt.y, got.ToString(), want.ToString(),\n\t\t\t)\n\t\t}\n\t}\n}\n\nfunc TestExp_LargeExponent(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tbase string\n\t\texponent string\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname: \"2^129\",\n\t\t\tbase: \"2\",\n\t\t\texponent: \"680564733841876926926749214863536422912\",\n\t\t\texpected: \"0\",\n\t\t},\n\t\t{\n\t\t\tname: \"2^193\",\n\t\t\tbase: \"2\",\n\t\t\texponent: \"12379400392853802746563808384000000000000000000\",\n\t\t\texpected: \"0\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tbase := MustFromDecimal(tt.base)\n\t\t\texponent := MustFromDecimal(tt.exponent)\n\t\t\texpected := MustFromDecimal(tt.expected)\n\n\t\t\tresult := new(Uint).Exp(base, exponent)\n\n\t\t\tif result.Neq(expected) {\n\t\t\t\tt.Errorf(\n\t\t\t\t\t\"Test %s failed. Expected %s, got %s\",\n\t\t\t\t\ttt.name, expected.ToString(), result.ToString(),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n}\n"},{"name":"bits_table.gno","body":"// Copyright 2017 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n// Code generated by go run make_tables.go. DO NOT EDIT.\n\npackage uint256\n\nconst ntz8tab = \"\" +\n\t\"\\x08\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x05\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x06\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x05\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x07\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x05\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x06\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x05\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\" +\n\t\"\\x04\\x00\\x01\\x00\\x02\\x00\\x01\\x00\\x03\\x00\\x01\\x00\\x02\\x00\\x01\\x00\"\n\nconst pop8tab = \"\" +\n\t\"\\x00\\x01\\x01\\x02\\x01\\x02\\x02\\x03\\x01\\x02\\x02\\x03\\x02\\x03\\x03\\x04\" +\n\t\"\\x01\\x02\\x02\\x03\\x02\\x03\\x03\\x04\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\" +\n\t\"\\x01\\x02\\x02\\x03\\x02\\x03\\x03\\x04\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\" +\n\t\"\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\" +\n\t\"\\x01\\x02\\x02\\x03\\x02\\x03\\x03\\x04\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\" +\n\t\"\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\" +\n\t\"\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\" +\n\t\"\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\\x04\\x05\\x05\\x06\\x05\\x06\\x06\\x07\" +\n\t\"\\x01\\x02\\x02\\x03\\x02\\x03\\x03\\x04\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\" +\n\t\"\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\" +\n\t\"\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\" +\n\t\"\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\\x04\\x05\\x05\\x06\\x05\\x06\\x06\\x07\" +\n\t\"\\x02\\x03\\x03\\x04\\x03\\x04\\x04\\x05\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\" +\n\t\"\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\\x04\\x05\\x05\\x06\\x05\\x06\\x06\\x07\" +\n\t\"\\x03\\x04\\x04\\x05\\x04\\x05\\x05\\x06\\x04\\x05\\x05\\x06\\x05\\x06\\x06\\x07\" +\n\t\"\\x04\\x05\\x05\\x06\\x05\\x06\\x06\\x07\\x05\\x06\\x06\\x07\\x06\\x07\\x07\\x08\"\n\nconst rev8tab = \"\" +\n\t\"\\x00\\x80\\x40\\xc0\\x20\\xa0\\x60\\xe0\\x10\\x90\\x50\\xd0\\x30\\xb0\\x70\\xf0\" +\n\t\"\\x08\\x88\\x48\\xc8\\x28\\xa8\\x68\\xe8\\x18\\x98\\x58\\xd8\\x38\\xb8\\x78\\xf8\" +\n\t\"\\x04\\x84\\x44\\xc4\\x24\\xa4\\x64\\xe4\\x14\\x94\\x54\\xd4\\x34\\xb4\\x74\\xf4\" +\n\t\"\\x0c\\x8c\\x4c\\xcc\\x2c\\xac\\x6c\\xec\\x1c\\x9c\\x5c\\xdc\\x3c\\xbc\\x7c\\xfc\" +\n\t\"\\x02\\x82\\x42\\xc2\\x22\\xa2\\x62\\xe2\\x12\\x92\\x52\\xd2\\x32\\xb2\\x72\\xf2\" +\n\t\"\\x0a\\x8a\\x4a\\xca\\x2a\\xaa\\x6a\\xea\\x1a\\x9a\\x5a\\xda\\x3a\\xba\\x7a\\xfa\" +\n\t\"\\x06\\x86\\x46\\xc6\\x26\\xa6\\x66\\xe6\\x16\\x96\\x56\\xd6\\x36\\xb6\\x76\\xf6\" +\n\t\"\\x0e\\x8e\\x4e\\xce\\x2e\\xae\\x6e\\xee\\x1e\\x9e\\x5e\\xde\\x3e\\xbe\\x7e\\xfe\" +\n\t\"\\x01\\x81\\x41\\xc1\\x21\\xa1\\x61\\xe1\\x11\\x91\\x51\\xd1\\x31\\xb1\\x71\\xf1\" +\n\t\"\\x09\\x89\\x49\\xc9\\x29\\xa9\\x69\\xe9\\x19\\x99\\x59\\xd9\\x39\\xb9\\x79\\xf9\" +\n\t\"\\x05\\x85\\x45\\xc5\\x25\\xa5\\x65\\xe5\\x15\\x95\\x55\\xd5\\x35\\xb5\\x75\\xf5\" +\n\t\"\\x0d\\x8d\\x4d\\xcd\\x2d\\xad\\x6d\\xed\\x1d\\x9d\\x5d\\xdd\\x3d\\xbd\\x7d\\xfd\" +\n\t\"\\x03\\x83\\x43\\xc3\\x23\\xa3\\x63\\xe3\\x13\\x93\\x53\\xd3\\x33\\xb3\\x73\\xf3\" +\n\t\"\\x0b\\x8b\\x4b\\xcb\\x2b\\xab\\x6b\\xeb\\x1b\\x9b\\x5b\\xdb\\x3b\\xbb\\x7b\\xfb\" +\n\t\"\\x07\\x87\\x47\\xc7\\x27\\xa7\\x67\\xe7\\x17\\x97\\x57\\xd7\\x37\\xb7\\x77\\xf7\" +\n\t\"\\x0f\\x8f\\x4f\\xcf\\x2f\\xaf\\x6f\\xef\\x1f\\x9f\\x5f\\xdf\\x3f\\xbf\\x7f\\xff\"\n\nconst len8tab = \"\" +\n\t\"\\x00\\x01\\x02\\x02\\x03\\x03\\x03\\x03\\x04\\x04\\x04\\x04\\x04\\x04\\x04\\x04\" +\n\t\"\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\\x05\" +\n\t\"\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\" +\n\t\"\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\\x06\" +\n\t\"\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\" +\n\t\"\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\" +\n\t\"\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\" +\n\t\"\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\\x07\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\" +\n\t\"\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08\"\n"},{"name":"bitwise.gno","body":"// bitwise contains bitwise operations for Uint instances.\n// This file includes functions to perform bitwise AND, OR, XOR, and NOT operations, as well as bit shifting.\n// These operations are crucial for manipulating individual bits within a 256-bit unsigned integer.\npackage uint256\n\n// Or sets z = x | y and returns z.\nfunc (z *Uint) Or(x, y *Uint) *Uint {\n\tz.arr[0] = x.arr[0] | y.arr[0]\n\tz.arr[1] = x.arr[1] | y.arr[1]\n\tz.arr[2] = x.arr[2] | y.arr[2]\n\tz.arr[3] = x.arr[3] | y.arr[3]\n\treturn z\n}\n\n// And sets z = x \u0026 y and returns z.\nfunc (z *Uint) And(x, y *Uint) *Uint {\n\tz.arr[0] = x.arr[0] \u0026 y.arr[0]\n\tz.arr[1] = x.arr[1] \u0026 y.arr[1]\n\tz.arr[2] = x.arr[2] \u0026 y.arr[2]\n\tz.arr[3] = x.arr[3] \u0026 y.arr[3]\n\treturn z\n}\n\n// Not sets z = ^x and returns z.\nfunc (z *Uint) Not(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = ^x.arr[3], ^x.arr[2], ^x.arr[1], ^x.arr[0]\n\treturn z\n}\n\n// AndNot sets z = x \u0026^ y and returns z.\nfunc (z *Uint) AndNot(x, y *Uint) *Uint {\n\tz.arr[0] = x.arr[0] \u0026^ y.arr[0]\n\tz.arr[1] = x.arr[1] \u0026^ y.arr[1]\n\tz.arr[2] = x.arr[2] \u0026^ y.arr[2]\n\tz.arr[3] = x.arr[3] \u0026^ y.arr[3]\n\treturn z\n}\n\n// Xor sets z = x ^ y and returns z.\nfunc (z *Uint) Xor(x, y *Uint) *Uint {\n\tz.arr[0] = x.arr[0] ^ y.arr[0]\n\tz.arr[1] = x.arr[1] ^ y.arr[1]\n\tz.arr[2] = x.arr[2] ^ y.arr[2]\n\tz.arr[3] = x.arr[3] ^ y.arr[3]\n\treturn z\n}\n\n// Lsh sets z = x \u003c\u003c n and returns z.\nfunc (z *Uint) Lsh(x *Uint, n uint) *Uint {\n\t// n % 64 == 0\n\tif n\u00260x3f == 0 {\n\t\tswitch n {\n\t\tcase 0:\n\t\t\treturn z.Set(x)\n\t\tcase 64:\n\t\t\treturn z.lsh64(x)\n\t\tcase 128:\n\t\t\treturn z.lsh128(x)\n\t\tcase 192:\n\t\t\treturn z.lsh192(x)\n\t\tdefault:\n\t\t\treturn z.Clear()\n\t\t}\n\t}\n\tvar a, b uint64\n\t// Big swaps first\n\tswitch {\n\tcase n \u003e 192:\n\t\tif n \u003e 256 {\n\t\t\treturn z.Clear()\n\t\t}\n\t\tz.lsh192(x)\n\t\tn -= 192\n\t\tgoto sh192\n\tcase n \u003e 128:\n\t\tz.lsh128(x)\n\t\tn -= 128\n\t\tgoto sh128\n\tcase n \u003e 64:\n\t\tz.lsh64(x)\n\t\tn -= 64\n\t\tgoto sh64\n\tdefault:\n\t\tz.Set(x)\n\t}\n\n\t// remaining shifts\n\ta = z.arr[0] \u003e\u003e (64 - n)\n\tz.arr[0] = z.arr[0] \u003c\u003c n\n\nsh64:\n\tb = z.arr[1] \u003e\u003e (64 - n)\n\tz.arr[1] = (z.arr[1] \u003c\u003c n) | a\n\nsh128:\n\ta = z.arr[2] \u003e\u003e (64 - n)\n\tz.arr[2] = (z.arr[2] \u003c\u003c n) | b\n\nsh192:\n\tz.arr[3] = (z.arr[3] \u003c\u003c n) | a\n\n\treturn z\n}\n\n// Rsh sets z = x \u003e\u003e n and returns z.\nfunc (z *Uint) Rsh(x *Uint, n uint) *Uint {\n\t// n % 64 == 0\n\tif n\u00260x3f == 0 {\n\t\tswitch n {\n\t\tcase 0:\n\t\t\treturn z.Set(x)\n\t\tcase 64:\n\t\t\treturn z.rsh64(x)\n\t\tcase 128:\n\t\t\treturn z.rsh128(x)\n\t\tcase 192:\n\t\t\treturn z.rsh192(x)\n\t\tdefault:\n\t\t\treturn z.Clear()\n\t\t}\n\t}\n\tvar a, b uint64\n\t// Big swaps first\n\tswitch {\n\tcase n \u003e 192:\n\t\tif n \u003e 256 {\n\t\t\treturn z.Clear()\n\t\t}\n\t\tz.rsh192(x)\n\t\tn -= 192\n\t\tgoto sh192\n\tcase n \u003e 128:\n\t\tz.rsh128(x)\n\t\tn -= 128\n\t\tgoto sh128\n\tcase n \u003e 64:\n\t\tz.rsh64(x)\n\t\tn -= 64\n\t\tgoto sh64\n\tdefault:\n\t\tz.Set(x)\n\t}\n\n\t// remaining shifts\n\ta = z.arr[3] \u003c\u003c (64 - n)\n\tz.arr[3] = z.arr[3] \u003e\u003e n\n\nsh64:\n\tb = z.arr[2] \u003c\u003c (64 - n)\n\tz.arr[2] = (z.arr[2] \u003e\u003e n) | a\n\nsh128:\n\ta = z.arr[1] \u003c\u003c (64 - n)\n\tz.arr[1] = (z.arr[1] \u003e\u003e n) | b\n\nsh192:\n\tz.arr[0] = (z.arr[0] \u003e\u003e n) | a\n\n\treturn z\n}\n\n// SRsh (Signed/Arithmetic right shift)\n// considers z to be a signed integer, during right-shift\n// and sets z = x \u003e\u003e n and returns z.\nfunc (z *Uint) SRsh(x *Uint, n uint) *Uint {\n\t// If the MSB is 0, SRsh is same as Rsh.\n\tif !x.isBitSet(255) {\n\t\treturn z.Rsh(x, n)\n\t}\n\tif n%64 == 0 {\n\t\tswitch n {\n\t\tcase 0:\n\t\t\treturn z.Set(x)\n\t\tcase 64:\n\t\t\treturn z.srsh64(x)\n\t\tcase 128:\n\t\t\treturn z.srsh128(x)\n\t\tcase 192:\n\t\t\treturn z.srsh192(x)\n\t\tdefault:\n\t\t\treturn z.SetAllOne()\n\t\t}\n\t}\n\tvar a uint64 = MaxUint64 \u003c\u003c (64 - n%64)\n\t// Big swaps first\n\tswitch {\n\tcase n \u003e 192:\n\t\tif n \u003e 256 {\n\t\t\treturn z.SetAllOne()\n\t\t}\n\t\tz.srsh192(x)\n\t\tn -= 192\n\t\tgoto sh192\n\tcase n \u003e 128:\n\t\tz.srsh128(x)\n\t\tn -= 128\n\t\tgoto sh128\n\tcase n \u003e 64:\n\t\tz.srsh64(x)\n\t\tn -= 64\n\t\tgoto sh64\n\tdefault:\n\t\tz.Set(x)\n\t}\n\n\t// remaining shifts\n\tz.arr[3], a = (z.arr[3]\u003e\u003en)|a, z.arr[3]\u003c\u003c(64-n)\n\nsh64:\n\tz.arr[2], a = (z.arr[2]\u003e\u003en)|a, z.arr[2]\u003c\u003c(64-n)\n\nsh128:\n\tz.arr[1], a = (z.arr[1]\u003e\u003en)|a, z.arr[1]\u003c\u003c(64-n)\n\nsh192:\n\tz.arr[0] = (z.arr[0] \u003e\u003e n) | a\n\n\treturn z\n}\n\nfunc (z *Uint) lsh64(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = x.arr[2], x.arr[1], x.arr[0], 0\n\treturn z\n}\n\nfunc (z *Uint) lsh128(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = x.arr[1], x.arr[0], 0, 0\n\treturn z\n}\n\nfunc (z *Uint) lsh192(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = x.arr[0], 0, 0, 0\n\treturn z\n}\n\nfunc (z *Uint) rsh64(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, x.arr[3], x.arr[2], x.arr[1]\n\treturn z\n}\n\nfunc (z *Uint) rsh128(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, 0, x.arr[3], x.arr[2]\n\treturn z\n}\n\nfunc (z *Uint) rsh192(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, 0, 0, x.arr[3]\n\treturn z\n}\n\nfunc (z *Uint) srsh64(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = MaxUint64, x.arr[3], x.arr[2], x.arr[1]\n\treturn z\n}\n\nfunc (z *Uint) srsh128(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = MaxUint64, MaxUint64, x.arr[3], x.arr[2]\n\treturn z\n}\n\nfunc (z *Uint) srsh192(x *Uint) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = MaxUint64, MaxUint64, MaxUint64, x.arr[3]\n\treturn z\n}\n"},{"name":"bitwise_test.gno","body":"package uint256\n\nimport \"testing\"\n\ntype logicOpTest struct {\n\tname string\n\tx Uint\n\ty Uint\n\twant Uint\n}\n\nfunc TestOr(t *testing.T) {\n\ttests := []logicOpTest{\n\t\t{\n\t\t\tname: \"all zeros\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tres := new(Uint).Or(\u0026tt.x, \u0026tt.y)\n\t\t\tif *res != tt.want {\n\t\t\t\tt.Errorf(\n\t\t\t\t\t\"Or(%s, %s) = %s, want %s\",\n\t\t\t\t\ttt.x.ToString(), tt.y.ToString(), res.ToString(), (tt.want).ToString(),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestAnd(t *testing.T) {\n\ttests := []logicOpTest{\n\t\t{\n\t\t\tname: \"all zeros\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 2\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 3\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand zero\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}},\n\t\t\twant: Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tres := new(Uint).And(\u0026tt.x, \u0026tt.y)\n\t\t\tif *res != tt.want {\n\t\t\t\tt.Errorf(\n\t\t\t\t\t\"And(%s, %s) = %s, want %s\",\n\t\t\t\t\ttt.x.ToString(), tt.y.ToString(), res.ToString(), (tt.want).ToString(),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNot(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tx Uint\n\t\twant Uint\n\t}{\n\t\t{\n\t\t\tname: \"all zeros\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tres := new(Uint).Not(\u0026tt.x)\n\t\t\tif *res != tt.want {\n\t\t\t\tt.Errorf(\n\t\t\t\t\t\"Not(%s) = %s, want %s\",\n\t\t\t\t\ttt.x.ToString(), res.ToString(), (tt.want).ToString(),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestAndNot(t *testing.T) {\n\ttests := []logicOpTest{\n\t\t{\n\t\t\tname: \"all zeros\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 2\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 3\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand zero\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}},\n\t\t\twant: Uint{arr: [4]uint64{0xAAAAAAAAAAAAAAAA, 0x5555555555555555, 0x0000000000000000, ^uint64(0)}},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tres := new(Uint).AndNot(\u0026tt.x, \u0026tt.y)\n\t\t\tif *res != tt.want {\n\t\t\t\tt.Errorf(\n\t\t\t\t\t\"AndNot(%s, %s) = %s, want %s\",\n\t\t\t\t\ttt.x.ToString(), tt.y.ToString(), res.ToString(), (tt.want).ToString(),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestXor(t *testing.T) {\n\ttests := []logicOpTest{\n\t\t{\n\t\t\tname: \"all zeros\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 2\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed 3\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}},\n\t\t\twant: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand zero\",\n\t\t\tx: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\ty: Uint{arr: [4]uint64{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF}},\n\t\t\twant: Uint{arr: [4]uint64{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF}},\n\t\t},\n\t\t{\n\t\t\tname: \"one operand all ones\",\n\t\t\tx: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\ty: Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}},\n\t\t\twant: Uint{arr: [4]uint64{0xAAAAAAAAAAAAAAAA, 0x5555555555555555, 0x0000000000000000, ^uint64(0)}},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tres := new(Uint).Xor(\u0026tt.x, \u0026tt.y)\n\t\t\tif *res != tt.want {\n\t\t\t\tt.Errorf(\n\t\t\t\t\t\"Xor(%s, %s) = %s, want %s\",\n\t\t\t\t\ttt.x.ToString(), tt.y.ToString(), res.ToString(), (tt.want).ToString(),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestLsh(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty uint\n\t\twant string\n\t}{\n\t\t{\"0\", 0, \"0\"},\n\t\t{\"0\", 1, \"0\"},\n\t\t{\"0\", 64, \"0\"},\n\t\t{\"1\", 0, \"1\"},\n\t\t{\"1\", 1, \"2\"},\n\t\t{\"1\", 64, \"18446744073709551616\"},\n\t\t{\"1\", 128, \"340282366920938463463374607431768211456\"},\n\t\t{\"1\", 192, \"6277101735386680763835789423207666416102355444464034512896\"},\n\t\t{\"1\", 255, \"57896044618658097711785492504343953926634992332820282019728792003956564819968\"},\n\t\t{\"1\", 256, \"0\"},\n\t\t{\"31337\", 0, \"31337\"},\n\t\t{\"31337\", 1, \"62674\"},\n\t\t{\"31337\", 64, \"578065619037836218990592\"},\n\t\t{\"31337\", 128, \"10663428532201448629551770073089320442396672\"},\n\t\t{\"31337\", 192, \"196705537081812415096322133155058642481399512563169449530621952\"},\n\t\t{\"31337\", 193, \"393411074163624830192644266310117284962799025126338899061243904\"},\n\t\t{\"31337\", 255, \"57896044618658097711785492504343953926634992332820282019728792003956564819968\"},\n\t\t{\"31337\", 256, \"0\"},\n\t\t// 64 \u003c n \u003c 128\n\t\t{\"1\", 65, \"36893488147419103232\"},\n\t\t{\"31337\", 100, \"39724366859352024754702188346867712\"},\n\n\t\t// 128 \u003c n \u003c 192\n\t\t{\"1\", 129, \"680564733841876926926749214863536422912\"},\n\t\t{\"31337\", 150, \"44725660946326664792723507424638829088826130956288\"},\n\n\t\t// 192 \u003c n \u003c 256\n\t\t{\"1\", 193, \"12554203470773361527671578846415332832204710888928069025792\"},\n\t\t{\"31337\", 200, \"50356617492943978264658466087695012475238275216171379079839219712\"},\n\n\t\t// n \u003e 256\n\t\t{\"1\", 257, \"0\"},\n\t\t{\"31337\", 300, \"0\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\t\twant := MustFromDecimal(tt.want)\n\n\t\tgot := new(Uint).Lsh(x, tt.y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Lsh(%s, %d) = %s, want %s\", tt.x, tt.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestRsh(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty uint\n\t\twant string\n\t}{\n\t\t{\"0\", 0, \"0\"},\n\t\t{\"0\", 1, \"0\"},\n\t\t{\"0\", 64, \"0\"},\n\t\t{\"1\", 0, \"1\"},\n\t\t{\"1\", 1, \"0\"},\n\t\t{\"1\", 64, \"0\"},\n\t\t{\"1\", 128, \"0\"},\n\t\t{\"1\", 192, \"0\"},\n\t\t{\"1\", 255, \"0\"},\n\t\t{\"57896044618658097711785492504343953926634992332820282019728792003956564819968\", 255, \"1\"},\n\t\t{\"6277101735386680763835789423207666416102355444464034512896\", 192, \"1\"},\n\t\t{\"340282366920938463463374607431768211456\", 128, \"1\"},\n\t\t{\"18446744073709551616\", 64, \"1\"},\n\t\t{\"393411074163624830192644266310117284962799025126338899061243904\", 193, \"31337\"},\n\t\t{\"196705537081812415096322133155058642481399512563169449530621952\", 192, \"31337\"},\n\t\t{\"10663428532201448629551770073089320442396672\", 128, \"31337\"},\n\t\t{\"578065619037836218990592\", 64, \"31337\"},\n\t\t{twoPow256Sub1, 256, \"0\"},\n\t\t// outliers\n\t\t{\"340282366920938463463374607431768211455\", 129, \"0\"},\n\t\t{\"18446744073709551615\", 65, \"0\"},\n\t\t{twoPow256Sub1, 1, \"57896044618658097711785492504343953926634992332820282019728792003956564819967\"},\n\n\t\t// n \u003e 256\n\t\t{\"1\", 257, \"0\"},\n\t\t{\"31337\", 300, \"0\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\n\t\twant := MustFromDecimal(tt.want)\n\t\tgot := new(Uint).Rsh(x, tt.y)\n\n\t\tif got.Neq(want) {\n\t\t\tt.Errorf(\"Rsh(%s, %d) = %s, want %s\", tt.x, tt.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n\nfunc TestSRsh(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty uint\n\t\twant string\n\t}{\n\t\t// Positive numbers (behaves like Rsh)\n\t\t{\"0x0\", 0, \"0x0\"},\n\t\t{\"0x0\", 1, \"0x0\"},\n\t\t{\"0x1\", 0, \"0x1\"},\n\t\t{\"0x1\", 1, \"0x0\"},\n\t\t{\"0x31337\", 0, \"0x31337\"},\n\t\t{\"0x31337\", 4, \"0x3133\"},\n\t\t{\"0x31337\", 8, \"0x313\"},\n\t\t{\"0x31337\", 16, \"0x3\"},\n\t\t{\"0x10000000000000000\", 64, \"0x1\"}, // 2^64 \u003e\u003e 64\n\n\t\t// // Numbers with MSB set (negative numbers in two's complement)\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 0, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 1, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 4, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 64, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 128, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 192, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 255, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\n\t\t// Large positive number close to max value\n\t\t{\"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 1, \"0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 2, \"0x1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 64, \"0x7fffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 128, \"0x7fffffffffffffffffffffffffffffff\"},\n\t\t{\"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 192, \"0x7fffffffffffffff\"},\n\t\t{\"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 255, \"0x0\"},\n\n\t\t// Specific cases\n\t\t{\"0x8000000000000000000000000000000000000000000000000000000000000000\", 1, \"0xc000000000000000000000000000000000000000000000000000000000000000\"},\n\t\t{\"0x8000000000000000000000000000000000000000000000000000000000000001\", 1, \"0xc000000000000000000000000000000000000000000000000000000000000000\"},\n\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 65, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 127, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 129, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 193, \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"},\n\n\t\t// n \u003e 256\n\t\t{\"0x1\", 257, \"0x0\"},\n\t\t{\"0x31337\", 300, \"0x0\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromHex(tt.x)\n\t\twant := MustFromHex(tt.want)\n\n\t\tgot := new(Uint).SRsh(x, tt.y)\n\n\t\tif !got.Eq(want) {\n\t\t\tt.Errorf(\"SRsh(%s, %d) = %s, want %s\", tt.x, tt.y, got.ToString(), want.ToString())\n\t\t}\n\t}\n}\n"},{"name":"cmp.gno","body":"// cmp (or, comparisons) includes methods for comparing Uint instances.\n// These comparison functions cover a range of operations including equality checks, less than/greater than\n// evaluations, and specialized comparisons such as signed greater than. These are fundamental for logical\n// decision making based on Uint values.\npackage uint256\n\nimport (\n\t\"math/bits\"\n)\n\n// Cmp compares z and x and returns:\n//\n//\t-1 if z \u003c x\n//\t 0 if z == x\n//\t+1 if z \u003e x\nfunc (z *Uint) Cmp(x *Uint) (r int) {\n\t// z \u003c x \u003c=\u003e z - x \u003c 0 i.e. when subtraction overflows.\n\td0, carry := bits.Sub64(z.arr[0], x.arr[0], 0)\n\td1, carry := bits.Sub64(z.arr[1], x.arr[1], carry)\n\td2, carry := bits.Sub64(z.arr[2], x.arr[2], carry)\n\td3, carry := bits.Sub64(z.arr[3], x.arr[3], carry)\n\tif carry == 1 {\n\t\treturn -1\n\t}\n\tif d0|d1|d2|d3 == 0 {\n\t\treturn 0\n\t}\n\treturn 1\n}\n\n// IsZero returns true if z == 0\nfunc (z *Uint) IsZero() bool {\n\treturn (z.arr[0] | z.arr[1] | z.arr[2] | z.arr[3]) == 0\n}\n\n// Sign returns:\n//\n//\t-1 if z \u003c 0\n//\t 0 if z == 0\n//\t+1 if z \u003e 0\n//\n// Where z is interpreted as a two's complement signed number\nfunc (z *Uint) Sign() int {\n\tif z.IsZero() {\n\t\treturn 0\n\t}\n\tif z.arr[3] \u003c 0x8000000000000000 {\n\t\treturn 1\n\t}\n\treturn -1\n}\n\n// LtUint64 returns true if z is smaller than n\nfunc (z *Uint) LtUint64(n uint64) bool {\n\treturn z.arr[0] \u003c n \u0026\u0026 (z.arr[1]|z.arr[2]|z.arr[3]) == 0\n}\n\n// GtUint64 returns true if z is larger than n\nfunc (z *Uint) GtUint64(n uint64) bool {\n\treturn z.arr[0] \u003e n || (z.arr[1]|z.arr[2]|z.arr[3]) != 0\n}\n\n// Lt returns true if z \u003c x\nfunc (z *Uint) Lt(x *Uint) bool {\n\t// z \u003c x \u003c=\u003e z - x \u003c 0 i.e. when subtraction overflows.\n\t_, carry := bits.Sub64(z.arr[0], x.arr[0], 0)\n\t_, carry = bits.Sub64(z.arr[1], x.arr[1], carry)\n\t_, carry = bits.Sub64(z.arr[2], x.arr[2], carry)\n\t_, carry = bits.Sub64(z.arr[3], x.arr[3], carry)\n\n\treturn carry != 0\n}\n\n// Gt returns true if z \u003e x\nfunc (z *Uint) Gt(x *Uint) bool {\n\treturn x.Lt(z)\n}\n\n// Lte returns true if z \u003c= x\nfunc (z *Uint) Lte(x *Uint) bool {\n\tcond1 := z.Lt(x)\n\tcond2 := z.Eq(x)\n\n\tif cond1 || cond2 {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// Gte returns true if z \u003e= x\nfunc (z *Uint) Gte(x *Uint) bool {\n\tcond1 := z.Gt(x)\n\tcond2 := z.Eq(x)\n\n\tif cond1 || cond2 {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// Eq returns true if z == x\nfunc (z *Uint) Eq(x *Uint) bool {\n\treturn (z.arr[0] == x.arr[0]) \u0026\u0026 (z.arr[1] == x.arr[1]) \u0026\u0026 (z.arr[2] == x.arr[2]) \u0026\u0026 (z.arr[3] == x.arr[3])\n}\n\n// Neq returns true if z != x\nfunc (z *Uint) Neq(x *Uint) bool {\n\treturn !z.Eq(x)\n}\n\n// Sgt interprets z and x as signed integers, and returns\n// true if z \u003e x\nfunc (z *Uint) Sgt(x *Uint) bool {\n\tzSign := z.Sign()\n\txSign := x.Sign()\n\n\tswitch {\n\tcase zSign \u003e= 0 \u0026\u0026 xSign \u003c 0:\n\t\treturn true\n\tcase zSign \u003c 0 \u0026\u0026 xSign \u003e= 0:\n\t\treturn false\n\tdefault:\n\t\treturn z.Gt(x)\n\t}\n}\n"},{"name":"cmp_test.gno","body":"package uint256\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestSign(t *testing.T) {\n\ttests := []struct {\n\t\tinput *Uint\n\t\texpected int\n\t}{\n\t\t{\n\t\t\tinput: NewUint(0),\n\t\t\texpected: 0,\n\t\t},\n\t\t{\n\t\t\tinput: NewUint(1),\n\t\t\texpected: 1,\n\t\t},\n\t\t{\n\t\t\tinput: NewUint(0x7fffffffffffffff),\n\t\t\texpected: 1,\n\t\t},\n\t\t{\n\t\t\tinput: NewUint(0x8000000000000000),\n\t\t\texpected: 1,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.input.ToString(), func(t *testing.T) {\n\t\t\tresult := tt.input.Sign()\n\t\t\tif result != tt.expected {\n\t\t\t\tt.Errorf(\"Sign() = %d; want %d\", result, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCmp(t *testing.T) {\n\ttests := []struct {\n\t\tx, y string\n\t\twant int\n\t}{\n\t\t{\"0\", \"0\", 0},\n\t\t{\"0\", \"1\", -1},\n\t\t{\"1\", \"0\", 1},\n\t\t{\"1\", \"1\", 0},\n\t\t{\"10\", \"10\", 0},\n\t\t{\"10\", \"11\", -1},\n\t\t{\"11\", \"10\", 1},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx := MustFromDecimal(tc.x)\n\t\ty := MustFromDecimal(tc.y)\n\n\t\tgot := x.Cmp(y)\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"Cmp(%s, %s) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestIsZero(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant bool\n\t}{\n\t\t{\"0\", true},\n\t\t{\"1\", false},\n\t\t{\"10\", false},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromDecimal(tt.x)\n\n\t\tgot := x.IsZero()\n\t\tif got != tt.want {\n\t\t\tt.Errorf(\"IsZero(%s) = %v, want %v\", tt.x, got, tt.want)\n\t\t}\n\t}\n}\n\nfunc TestLtUint64(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty uint64\n\t\twant bool\n\t}{\n\t\t{\"0\", 1, true},\n\t\t{\"1\", 0, false},\n\t\t{\"10\", 10, false},\n\t\t{\"0xffffffffffffffff\", 0, false},\n\t\t{\"0x10000000000000000\", 10000000000000000, false},\n\t}\n\n\tfor _, tc := range tests {\n\t\tx := parseTestString(t, tc.x)\n\n\t\tgot := x.LtUint64(tc.y)\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"LtUint64(%s, %d) = %v, want %v\", tc.x, tc.y, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestUint_GtUint64(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tz string\n\t\tn uint64\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"z \u003e n\",\n\t\t\tz: \"1\",\n\t\t\tn: 0,\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"z \u003c n\",\n\t\t\tz: \"18446744073709551615\",\n\t\t\tn: 0xFFFFFFFFFFFFFFFF,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"z == n\",\n\t\t\tz: \"18446744073709551615\",\n\t\t\tn: 0xFFFFFFFFFFFFFFFF,\n\t\t\twant: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tz := MustFromDecimal(tt.z)\n\n\t\t\tif got := z.GtUint64(tt.n); got != tt.want {\n\t\t\t\tt.Errorf(\"Uint.GtUint64() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSGT(t *testing.T) {\n\tx := MustFromHex(\"0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe\")\n\ty := MustFromHex(\"0x0\")\n\tactual := x.Sgt(y)\n\tif actual {\n\t\tt.Fatalf(\"Expected %v false\", actual)\n\t}\n\n\tx = MustFromHex(\"0x0\")\n\ty = MustFromHex(\"0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe\")\n\tactual = x.Sgt(y)\n\tif !actual {\n\t\tt.Fatalf(\"Expected %v true\", actual)\n\t}\n}\n\nfunc TestEq(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\ty string\n\t\twant bool\n\t}{\n\t\t{\"0xffffffffffffffff\", \"18446744073709551615\", true},\n\t\t{\"0x10000000000000000\", \"18446744073709551616\", true},\n\t\t{\"0\", \"0\", true},\n\t\t{twoPow256Sub1, twoPow256Sub1, true},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := parseTestString(t, tt.x)\n\n\t\ty, err := FromDecimal(tt.y)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := x.Eq(y)\n\n\t\tif got != tt.want {\n\t\t\tt.Errorf(\"Eq(%s, %s) = %v, want %v\", tt.x, tt.y, got, tt.want)\n\t\t}\n\t}\n}\n\nfunc TestUint_Lte(t *testing.T) {\n\ttests := []struct {\n\t\tz, x string\n\t\twant bool\n\t}{\n\t\t{\"10\", \"20\", true},\n\t\t{\"20\", \"10\", false},\n\t\t{\"10\", \"10\", true},\n\t\t{\"0\", \"0\", true},\n\t}\n\n\tfor _, tt := range tests {\n\t\tz, err := FromDecimal(tt.z)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\t\tx, err := FromDecimal(tt.x)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\tcontinue\n\t\t}\n\t\tif got := z.Lte(x); got != tt.want {\n\t\t\tt.Errorf(\"Uint.Lte(%v, %v) = %v, want %v\", tt.z, tt.x, got, tt.want)\n\t\t}\n\t}\n}\n\nfunc TestUint_Gte(t *testing.T) {\n\ttests := []struct {\n\t\tz, x string\n\t\twant bool\n\t}{\n\t\t{\"20\", \"10\", true},\n\t\t{\"10\", \"20\", false},\n\t\t{\"10\", \"10\", true},\n\t\t{\"0\", \"0\", true},\n\t}\n\n\tfor _, tt := range tests {\n\t\tz := parseTestString(t, tt.z)\n\t\tx := parseTestString(t, tt.x)\n\n\t\tif got := z.Gte(x); got != tt.want {\n\t\t\tt.Errorf(\"Uint.Gte(%v, %v) = %v, want %v\", tt.z, tt.x, got, tt.want)\n\t\t}\n\t}\n}\n\nfunc parseTestString(_ *testing.T, s string) *Uint {\n\tvar x *Uint\n\n\tif strings.HasPrefix(s, \"0x\") {\n\t\tx = MustFromHex(s)\n\t} else {\n\t\tx = MustFromDecimal(s)\n\t}\n\n\treturn x\n}\n"},{"name":"conversion.gno","body":"// conversions contains methods for converting Uint instances to other types and vice versa.\n// This includes conversions to and from basic types such as uint64 and int32, as well as string representations\n// and byte slices. Additionally, it covers marshaling and unmarshaling for JSON and other text formats.\npackage uint256\n\nimport (\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// Uint64 returns the lower 64-bits of z\nfunc (z *Uint) Uint64() uint64 {\n\treturn z.arr[0]\n}\n\n// Uint64WithOverflow returns the lower 64-bits of z and bool whether overflow occurred\nfunc (z *Uint) Uint64WithOverflow() (uint64, bool) {\n\treturn z.arr[0], (z.arr[1] | z.arr[2] | z.arr[3]) != 0\n}\n\n// SetUint64 sets z to the value x\nfunc (z *Uint) SetUint64(x uint64) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, 0, 0, x\n\treturn z\n}\n\n// IsUint64 reports whether z can be represented as a uint64.\nfunc (z *Uint) IsUint64() bool {\n\treturn (z.arr[1] | z.arr[2] | z.arr[3]) == 0\n}\n\n// Dec returns the decimal representation of z.\nfunc (z *Uint) Dec() string {\n\tif z.IsZero() {\n\t\treturn \"0\"\n\t}\n\tif z.IsUint64() {\n\t\treturn strconv.FormatUint(z.Uint64(), 10)\n\t}\n\n\t// The max uint64 value being 18446744073709551615, the largest\n\t// power-of-ten below that is 10000000000000000000.\n\t// When we do a DivMod using that number, the remainder that we\n\t// get back is the lower part of the output.\n\t//\n\t// The ascii-output of remainder will never exceed 19 bytes (since it will be\n\t// below 10000000000000000000).\n\t//\n\t// Algorithm example using 100 as divisor\n\t//\n\t// 12345 % 100 = 45 (rem)\n\t// 12345 / 100 = 123 (quo)\n\t// -\u003e output '45', continue iterate on 123\n\tvar (\n\t\t// out is 98 bytes long: 78 (max size of a string without leading zeroes,\n\t\t// plus slack so we can copy 19 bytes every iteration).\n\t\t// We init it with zeroes, because when strconv appends the ascii representations,\n\t\t// it will omit leading zeroes.\n\t\tout = []byte(\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\")\n\t\tdivisor = NewUint(10000000000000000000) // 20 digits\n\t\ty = new(Uint).Set(z) // copy to avoid modifying z\n\t\tpos = len(out) // position to write to\n\t\tbuf = make([]byte, 0, 19) // buffer to write uint64:s to\n\t)\n\tfor {\n\t\t// Obtain Q and R for divisor\n\t\tvar quot Uint\n\t\trem := udivrem(quot.arr[:], y.arr[:], divisor)\n\t\ty.Set(\u0026quot) // Set Q for next loop\n\t\t// Convert the R to ascii representation\n\t\tbuf = strconv.AppendUint(buf[:0], rem.Uint64(), 10)\n\t\t// Copy in the ascii digits\n\t\tcopy(out[pos-len(buf):], buf)\n\t\tif y.IsZero() {\n\t\t\tbreak\n\t\t}\n\t\t// Move 19 digits left\n\t\tpos -= 19\n\t}\n\t// skip leading zeroes by only using the 'used size' of buf\n\treturn string(out[pos-len(buf):])\n}\n\nfunc (z *Uint) Scan(src interface{}) error {\n\tif src == nil {\n\t\tz.Clear()\n\t\treturn nil\n\t}\n\n\tswitch src := src.(type) {\n\tcase string:\n\t\treturn z.scanScientificFromString(src)\n\tcase []byte:\n\t\treturn z.scanScientificFromString(string(src))\n\t}\n\treturn errors.New(\"default // unsupported type: can't convert to uint256.Uint\")\n}\n\nfunc (z *Uint) scanScientificFromString(src string) error {\n\tif len(src) == 0 {\n\t\tz.Clear()\n\t\treturn nil\n\t}\n\n\tidx := strings.IndexByte(src, 'e')\n\tif idx == -1 {\n\t\treturn z.SetFromDecimal(src)\n\t}\n\tif err := z.SetFromDecimal(src[:idx]); err != nil {\n\t\treturn err\n\t}\n\tif src[(idx+1):] == \"0\" {\n\t\treturn nil\n\t}\n\texp := new(Uint)\n\tif err := exp.SetFromDecimal(src[(idx + 1):]); err != nil {\n\t\treturn err\n\t}\n\tif exp.GtUint64(77) { // 10**78 is larger than 2**256\n\t\treturn ErrBig256Range\n\t}\n\texp.Exp(NewUint(10), exp)\n\tif _, overflow := z.MulOverflow(z, exp); overflow {\n\t\treturn ErrBig256Range\n\t}\n\treturn nil\n}\n\n// ToString returns the decimal string representation of z. It returns an empty string if z is nil.\n// OBS: doesn't exist from holiman's uint256\nfunc (z *Uint) ToString() string {\n\tif z == nil {\n\t\treturn \"\"\n\t}\n\n\treturn z.Dec()\n}\n\n// MarshalJSON implements json.Marshaler.\n// MarshalJSON marshals using the 'decimal string' representation. This is _not_ compatible\n// with big.Uint: big.Uint marshals into JSON 'native' numeric format.\n//\n// The JSON native format is, on some platforms, (e.g. javascript), limited to 53-bit large\n// integer space. Thus, U256 uses string-format, which is not compatible with\n// big.int (big.Uint refuses to unmarshal a string representation).\nfunc (z *Uint) MarshalJSON() ([]byte, error) {\n\treturn []byte(`\"` + z.Dec() + `\"`), nil\n}\n\n// UnmarshalJSON implements json.Unmarshaler. UnmarshalJSON accepts either\n// - Quoted string: either hexadecimal OR decimal\n// - Not quoted string: only decimal\nfunc (z *Uint) UnmarshalJSON(input []byte) error {\n\tif len(input) \u003c 2 || input[0] != '\"' || input[len(input)-1] != '\"' {\n\t\t// if not quoted, it must be decimal\n\t\treturn z.fromDecimal(string(input))\n\t}\n\treturn z.UnmarshalText(input[1 : len(input)-1])\n}\n\n// MarshalText implements encoding.TextMarshaler\n// MarshalText marshals using the decimal representation (compatible with big.Uint)\nfunc (z *Uint) MarshalText() ([]byte, error) {\n\treturn []byte(z.Dec()), nil\n}\n\n// UnmarshalText implements encoding.TextUnmarshaler. This method\n// can unmarshal either hexadecimal or decimal.\n// - For hexadecimal, the input _must_ be prefixed with 0x or 0X\nfunc (z *Uint) UnmarshalText(input []byte) error {\n\tif len(input) \u003e= 2 \u0026\u0026 input[0] == '0' \u0026\u0026 (input[1] == 'x' || input[1] == 'X') {\n\t\treturn z.fromHex(string(input))\n\t}\n\treturn z.fromDecimal(string(input))\n}\n\n// SetBytes interprets buf as the bytes of a big-endian unsigned\n// integer, sets z to that value, and returns z.\n// If buf is larger than 32 bytes, the last 32 bytes is used.\nfunc (z *Uint) SetBytes(buf []byte) *Uint {\n\tswitch l := len(buf); l {\n\tcase 0:\n\t\tz.Clear()\n\tcase 1:\n\t\tz.SetBytes1(buf)\n\tcase 2:\n\t\tz.SetBytes2(buf)\n\tcase 3:\n\t\tz.SetBytes3(buf)\n\tcase 4:\n\t\tz.SetBytes4(buf)\n\tcase 5:\n\t\tz.SetBytes5(buf)\n\tcase 6:\n\t\tz.SetBytes6(buf)\n\tcase 7:\n\t\tz.SetBytes7(buf)\n\tcase 8:\n\t\tz.SetBytes8(buf)\n\tcase 9:\n\t\tz.SetBytes9(buf)\n\tcase 10:\n\t\tz.SetBytes10(buf)\n\tcase 11:\n\t\tz.SetBytes11(buf)\n\tcase 12:\n\t\tz.SetBytes12(buf)\n\tcase 13:\n\t\tz.SetBytes13(buf)\n\tcase 14:\n\t\tz.SetBytes14(buf)\n\tcase 15:\n\t\tz.SetBytes15(buf)\n\tcase 16:\n\t\tz.SetBytes16(buf)\n\tcase 17:\n\t\tz.SetBytes17(buf)\n\tcase 18:\n\t\tz.SetBytes18(buf)\n\tcase 19:\n\t\tz.SetBytes19(buf)\n\tcase 20:\n\t\tz.SetBytes20(buf)\n\tcase 21:\n\t\tz.SetBytes21(buf)\n\tcase 22:\n\t\tz.SetBytes22(buf)\n\tcase 23:\n\t\tz.SetBytes23(buf)\n\tcase 24:\n\t\tz.SetBytes24(buf)\n\tcase 25:\n\t\tz.SetBytes25(buf)\n\tcase 26:\n\t\tz.SetBytes26(buf)\n\tcase 27:\n\t\tz.SetBytes27(buf)\n\tcase 28:\n\t\tz.SetBytes28(buf)\n\tcase 29:\n\t\tz.SetBytes29(buf)\n\tcase 30:\n\t\tz.SetBytes30(buf)\n\tcase 31:\n\t\tz.SetBytes31(buf)\n\tdefault:\n\t\tz.SetBytes32(buf[l-32:])\n\t}\n\treturn z\n}\n\n// SetBytes1 is identical to SetBytes(in[:1]), but panics is input is too short\nfunc (z *Uint) SetBytes1(in []byte) *Uint {\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = uint64(in[0])\n\treturn z\n}\n\n// SetBytes2 is identical to SetBytes(in[:2]), but panics is input is too short\nfunc (z *Uint) SetBytes2(in []byte) *Uint {\n\t_ = in[1] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = uint64(binary.BigEndian.Uint16(in[0:2]))\n\treturn z\n}\n\n// SetBytes3 is identical to SetBytes(in[:3]), but panics is input is too short\nfunc (z *Uint) SetBytes3(in []byte) *Uint {\n\t_ = in[2] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = uint64(binary.BigEndian.Uint16(in[1:3])) | uint64(in[0])\u003c\u003c16\n\treturn z\n}\n\n// SetBytes4 is identical to SetBytes(in[:4]), but panics is input is too short\nfunc (z *Uint) SetBytes4(in []byte) *Uint {\n\t_ = in[3] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = uint64(binary.BigEndian.Uint32(in[0:4]))\n\treturn z\n}\n\n// SetBytes5 is identical to SetBytes(in[:5]), but panics is input is too short\nfunc (z *Uint) SetBytes5(in []byte) *Uint {\n\t_ = in[4] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = bigEndianUint40(in[0:5])\n\treturn z\n}\n\n// SetBytes6 is identical to SetBytes(in[:6]), but panics is input is too short\nfunc (z *Uint) SetBytes6(in []byte) *Uint {\n\t_ = in[5] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = bigEndianUint48(in[0:6])\n\treturn z\n}\n\n// SetBytes7 is identical to SetBytes(in[:7]), but panics is input is too short\nfunc (z *Uint) SetBytes7(in []byte) *Uint {\n\t_ = in[6] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = bigEndianUint56(in[0:7])\n\treturn z\n}\n\n// SetBytes8 is identical to SetBytes(in[:8]), but panics is input is too short\nfunc (z *Uint) SetBytes8(in []byte) *Uint {\n\t_ = in[7] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\tz.arr[0] = binary.BigEndian.Uint64(in[0:8])\n\treturn z\n}\n\n// SetBytes9 is identical to SetBytes(in[:9]), but panics is input is too short\nfunc (z *Uint) SetBytes9(in []byte) *Uint {\n\t_ = in[8] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = uint64(in[0])\n\tz.arr[0] = binary.BigEndian.Uint64(in[1:9])\n\treturn z\n}\n\n// SetBytes10 is identical to SetBytes(in[:10]), but panics is input is too short\nfunc (z *Uint) SetBytes10(in []byte) *Uint {\n\t_ = in[9] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = uint64(binary.BigEndian.Uint16(in[0:2]))\n\tz.arr[0] = binary.BigEndian.Uint64(in[2:10])\n\treturn z\n}\n\n// SetBytes11 is identical to SetBytes(in[:11]), but panics is input is too short\nfunc (z *Uint) SetBytes11(in []byte) *Uint {\n\t_ = in[10] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = uint64(binary.BigEndian.Uint16(in[1:3])) | uint64(in[0])\u003c\u003c16\n\tz.arr[0] = binary.BigEndian.Uint64(in[3:11])\n\treturn z\n}\n\n// SetBytes12 is identical to SetBytes(in[:12]), but panics is input is too short\nfunc (z *Uint) SetBytes12(in []byte) *Uint {\n\t_ = in[11] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = uint64(binary.BigEndian.Uint32(in[0:4]))\n\tz.arr[0] = binary.BigEndian.Uint64(in[4:12])\n\treturn z\n}\n\n// SetBytes13 is identical to SetBytes(in[:13]), but panics is input is too short\nfunc (z *Uint) SetBytes13(in []byte) *Uint {\n\t_ = in[12] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = bigEndianUint40(in[0:5])\n\tz.arr[0] = binary.BigEndian.Uint64(in[5:13])\n\treturn z\n}\n\n// SetBytes14 is identical to SetBytes(in[:14]), but panics is input is too short\nfunc (z *Uint) SetBytes14(in []byte) *Uint {\n\t_ = in[13] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = bigEndianUint48(in[0:6])\n\tz.arr[0] = binary.BigEndian.Uint64(in[6:14])\n\treturn z\n}\n\n// SetBytes15 is identical to SetBytes(in[:15]), but panics is input is too short\nfunc (z *Uint) SetBytes15(in []byte) *Uint {\n\t_ = in[14] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = bigEndianUint56(in[0:7])\n\tz.arr[0] = binary.BigEndian.Uint64(in[7:15])\n\treturn z\n}\n\n// SetBytes16 is identical to SetBytes(in[:16]), but panics is input is too short\nfunc (z *Uint) SetBytes16(in []byte) *Uint {\n\t_ = in[15] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3], z.arr[2] = 0, 0\n\tz.arr[1] = binary.BigEndian.Uint64(in[0:8])\n\tz.arr[0] = binary.BigEndian.Uint64(in[8:16])\n\treturn z\n}\n\n// SetBytes17 is identical to SetBytes(in[:17]), but panics is input is too short\nfunc (z *Uint) SetBytes17(in []byte) *Uint {\n\t_ = in[16] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = uint64(in[0])\n\tz.arr[1] = binary.BigEndian.Uint64(in[1:9])\n\tz.arr[0] = binary.BigEndian.Uint64(in[9:17])\n\treturn z\n}\n\n// SetBytes18 is identical to SetBytes(in[:18]), but panics is input is too short\nfunc (z *Uint) SetBytes18(in []byte) *Uint {\n\t_ = in[17] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = uint64(binary.BigEndian.Uint16(in[0:2]))\n\tz.arr[1] = binary.BigEndian.Uint64(in[2:10])\n\tz.arr[0] = binary.BigEndian.Uint64(in[10:18])\n\treturn z\n}\n\n// SetBytes19 is identical to SetBytes(in[:19]), but panics is input is too short\nfunc (z *Uint) SetBytes19(in []byte) *Uint {\n\t_ = in[18] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = uint64(binary.BigEndian.Uint16(in[1:3])) | uint64(in[0])\u003c\u003c16\n\tz.arr[1] = binary.BigEndian.Uint64(in[3:11])\n\tz.arr[0] = binary.BigEndian.Uint64(in[11:19])\n\treturn z\n}\n\n// SetBytes20 is identical to SetBytes(in[:20]), but panics is input is too short\nfunc (z *Uint) SetBytes20(in []byte) *Uint {\n\t_ = in[19] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = uint64(binary.BigEndian.Uint32(in[0:4]))\n\tz.arr[1] = binary.BigEndian.Uint64(in[4:12])\n\tz.arr[0] = binary.BigEndian.Uint64(in[12:20])\n\treturn z\n}\n\n// SetBytes21 is identical to SetBytes(in[:21]), but panics is input is too short\nfunc (z *Uint) SetBytes21(in []byte) *Uint {\n\t_ = in[20] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = bigEndianUint40(in[0:5])\n\tz.arr[1] = binary.BigEndian.Uint64(in[5:13])\n\tz.arr[0] = binary.BigEndian.Uint64(in[13:21])\n\treturn z\n}\n\n// SetBytes22 is identical to SetBytes(in[:22]), but panics is input is too short\nfunc (z *Uint) SetBytes22(in []byte) *Uint {\n\t_ = in[21] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = bigEndianUint48(in[0:6])\n\tz.arr[1] = binary.BigEndian.Uint64(in[6:14])\n\tz.arr[0] = binary.BigEndian.Uint64(in[14:22])\n\treturn z\n}\n\n// SetBytes23 is identical to SetBytes(in[:23]), but panics is input is too short\nfunc (z *Uint) SetBytes23(in []byte) *Uint {\n\t_ = in[22] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = bigEndianUint56(in[0:7])\n\tz.arr[1] = binary.BigEndian.Uint64(in[7:15])\n\tz.arr[0] = binary.BigEndian.Uint64(in[15:23])\n\treturn z\n}\n\n// SetBytes24 is identical to SetBytes(in[:24]), but panics is input is too short\nfunc (z *Uint) SetBytes24(in []byte) *Uint {\n\t_ = in[23] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = 0\n\tz.arr[2] = binary.BigEndian.Uint64(in[0:8])\n\tz.arr[1] = binary.BigEndian.Uint64(in[8:16])\n\tz.arr[0] = binary.BigEndian.Uint64(in[16:24])\n\treturn z\n}\n\n// SetBytes25 is identical to SetBytes(in[:25]), but panics is input is too short\nfunc (z *Uint) SetBytes25(in []byte) *Uint {\n\t_ = in[24] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = uint64(in[0])\n\tz.arr[2] = binary.BigEndian.Uint64(in[1:9])\n\tz.arr[1] = binary.BigEndian.Uint64(in[9:17])\n\tz.arr[0] = binary.BigEndian.Uint64(in[17:25])\n\treturn z\n}\n\n// SetBytes26 is identical to SetBytes(in[:26]), but panics is input is too short\nfunc (z *Uint) SetBytes26(in []byte) *Uint {\n\t_ = in[25] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = uint64(binary.BigEndian.Uint16(in[0:2]))\n\tz.arr[2] = binary.BigEndian.Uint64(in[2:10])\n\tz.arr[1] = binary.BigEndian.Uint64(in[10:18])\n\tz.arr[0] = binary.BigEndian.Uint64(in[18:26])\n\treturn z\n}\n\n// SetBytes27 is identical to SetBytes(in[:27]), but panics is input is too short\nfunc (z *Uint) SetBytes27(in []byte) *Uint {\n\t_ = in[26] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = uint64(binary.BigEndian.Uint16(in[1:3])) | uint64(in[0])\u003c\u003c16\n\tz.arr[2] = binary.BigEndian.Uint64(in[3:11])\n\tz.arr[1] = binary.BigEndian.Uint64(in[11:19])\n\tz.arr[0] = binary.BigEndian.Uint64(in[19:27])\n\treturn z\n}\n\n// SetBytes28 is identical to SetBytes(in[:28]), but panics is input is too short\nfunc (z *Uint) SetBytes28(in []byte) *Uint {\n\t_ = in[27] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = uint64(binary.BigEndian.Uint32(in[0:4]))\n\tz.arr[2] = binary.BigEndian.Uint64(in[4:12])\n\tz.arr[1] = binary.BigEndian.Uint64(in[12:20])\n\tz.arr[0] = binary.BigEndian.Uint64(in[20:28])\n\treturn z\n}\n\n// SetBytes29 is identical to SetBytes(in[:29]), but panics is input is too short\nfunc (z *Uint) SetBytes29(in []byte) *Uint {\n\t_ = in[23] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = bigEndianUint40(in[0:5])\n\tz.arr[2] = binary.BigEndian.Uint64(in[5:13])\n\tz.arr[1] = binary.BigEndian.Uint64(in[13:21])\n\tz.arr[0] = binary.BigEndian.Uint64(in[21:29])\n\treturn z\n}\n\n// SetBytes30 is identical to SetBytes(in[:30]), but panics is input is too short\nfunc (z *Uint) SetBytes30(in []byte) *Uint {\n\t_ = in[29] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = bigEndianUint48(in[0:6])\n\tz.arr[2] = binary.BigEndian.Uint64(in[6:14])\n\tz.arr[1] = binary.BigEndian.Uint64(in[14:22])\n\tz.arr[0] = binary.BigEndian.Uint64(in[22:30])\n\treturn z\n}\n\n// SetBytes31 is identical to SetBytes(in[:31]), but panics is input is too short\nfunc (z *Uint) SetBytes31(in []byte) *Uint {\n\t_ = in[30] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = bigEndianUint56(in[0:7])\n\tz.arr[2] = binary.BigEndian.Uint64(in[7:15])\n\tz.arr[1] = binary.BigEndian.Uint64(in[15:23])\n\tz.arr[0] = binary.BigEndian.Uint64(in[23:31])\n\treturn z\n}\n\n// SetBytes32 sets z to the value of the big-endian 256-bit unsigned integer in.\nfunc (z *Uint) SetBytes32(in []byte) *Uint {\n\t_ = in[31] // bounds check hint to compiler; see golang.org/issue/14808\n\tz.arr[3] = binary.BigEndian.Uint64(in[0:8])\n\tz.arr[2] = binary.BigEndian.Uint64(in[8:16])\n\tz.arr[1] = binary.BigEndian.Uint64(in[16:24])\n\tz.arr[0] = binary.BigEndian.Uint64(in[24:32])\n\treturn z\n}\n\n// Utility methods that are \"missing\" among the bigEndian.UintXX methods.\n\n// bigEndianUint40 returns the uint64 value represented by the 5 bytes in big-endian order.\nfunc bigEndianUint40(b []byte) uint64 {\n\t_ = b[4] // bounds check hint to compiler; see golang.org/issue/14808\n\treturn uint64(b[4]) | uint64(b[3])\u003c\u003c8 | uint64(b[2])\u003c\u003c16 | uint64(b[1])\u003c\u003c24 |\n\t\tuint64(b[0])\u003c\u003c32\n}\n\n// bigEndianUint56 returns the uint64 value represented by the 7 bytes in big-endian order.\nfunc bigEndianUint56(b []byte) uint64 {\n\t_ = b[6] // bounds check hint to compiler; see golang.org/issue/14808\n\treturn uint64(b[6]) | uint64(b[5])\u003c\u003c8 | uint64(b[4])\u003c\u003c16 | uint64(b[3])\u003c\u003c24 |\n\t\tuint64(b[2])\u003c\u003c32 | uint64(b[1])\u003c\u003c40 | uint64(b[0])\u003c\u003c48\n}\n\n// bigEndianUint48 returns the uint64 value represented by the 6 bytes in big-endian order.\nfunc bigEndianUint48(b []byte) uint64 {\n\t_ = b[5] // bounds check hint to compiler; see golang.org/issue/14808\n\treturn uint64(b[5]) | uint64(b[4])\u003c\u003c8 | uint64(b[3])\u003c\u003c16 | uint64(b[2])\u003c\u003c24 |\n\t\tuint64(b[1])\u003c\u003c32 | uint64(b[0])\u003c\u003c40\n}\n"},{"name":"conversion_test.gno","body":"package uint256\n\nimport \"testing\"\n\nfunc TestIsUint64(t *testing.T) {\n\ttests := []struct {\n\t\tx string\n\t\twant bool\n\t}{\n\t\t{\"0x0\", true},\n\t\t{\"0x1\", true},\n\t\t{\"0x10\", true},\n\t\t{\"0xffffffffffffffff\", true},\n\t\t{\"0x10000000000000000\", false},\n\t}\n\n\tfor _, tt := range tests {\n\t\tx := MustFromHex(tt.x)\n\t\tgot := x.IsUint64()\n\n\t\tif got != tt.want {\n\t\t\tt.Errorf(\"IsUint64(%s) = %v, want %v\", tt.x, got, tt.want)\n\t\t}\n\t}\n}\n\nfunc TestDec(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tz Uint\n\t\twant string\n\t}{\n\t\t{\n\t\t\tname: \"zero\",\n\t\t\tz: Uint{arr: [4]uint64{0, 0, 0, 0}},\n\t\t\twant: \"0\",\n\t\t},\n\t\t{\n\t\t\tname: \"less than 20 digits\",\n\t\t\tz: Uint{arr: [4]uint64{1234567890, 0, 0, 0}},\n\t\t\twant: \"1234567890\",\n\t\t},\n\t\t{\n\t\t\tname: \"max possible value\",\n\t\t\tz: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}},\n\t\t\twant: twoPow256Sub1,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresult := tt.z.Dec()\n\t\t\tif result != tt.want {\n\t\t\t\tt.Errorf(\"Dec(%v) = %s, want %s\", tt.z, result, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUint_Scan(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput interface{}\n\t\twant *Uint\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"nil\",\n\t\t\tinput: nil,\n\t\t\twant: NewUint(0),\n\t\t},\n\t\t{\n\t\t\tname: \"valid scientific notation\",\n\t\t\tinput: \"1e4\",\n\t\t\twant: NewUint(10000),\n\t\t},\n\t\t{\n\t\t\tname: \"valid decimal string\",\n\t\t\tinput: \"12345\",\n\t\t\twant: NewUint(12345),\n\t\t},\n\t\t{\n\t\t\tname: \"valid byte slice\",\n\t\t\tinput: []byte(\"12345\"),\n\t\t\twant: NewUint(12345),\n\t\t},\n\t\t{\n\t\t\tname: \"invalid string\",\n\t\t\tinput: \"invalid\",\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"out of range\",\n\t\t\tinput: \"115792089237316195423570985008687907853269984665640564039457584007913129639936\", // 2^256\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"unsupported type\",\n\t\t\tinput: 123,\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tz := new(Uint)\n\t\t\terr := z.Scan(tt.input)\n\n\t\t\tif tt.wantErr {\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Errorf(\"Scan() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"Scan() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\t}\n\t\t\t\tif !z.Eq(tt.want) {\n\t\t\t\t\tt.Errorf(\"Scan() = %v, want %v\", z, tt.want)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSetBytes(t *testing.T) {\n\ttests := []struct {\n\t\tinput []byte\n\t\texpected string\n\t}{\n\t\t{[]byte{}, \"0\"},\n\t\t{[]byte{0x01}, \"1\"},\n\t\t{[]byte{0x12, 0x34}, \"4660\"},\n\t\t{[]byte{0x12, 0x34, 0x56}, \"1193046\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78}, \"305419896\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a}, \"78187493530\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc}, \"20015998343868\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde}, \"5124095576030430\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}, \"1311768467463790320\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12}, \"335812727670730321938\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34}, \"85968058283706962416180\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56}, \"22007822920628982378542166\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78}, \"5634002667681019488906794616\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a}, \"1442304682926340989160139421850\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc}, \"369229998829143293224995691993788\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde}, \"94522879700260683065598897150409950\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}, \"24197857203266734864793317670504947440\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12}, \"6194651444036284125387089323649266544658\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34}, \"1585830769673288736099094866854212235432500\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56}, \"405972677036361916441368285914678332270720086\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78}, \"103929005321308650608990281194157653061304342136\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a}, \"26605825362255014555901511985704359183693911586970\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc}, \"6811091292737283726310787068340315951025641366264508\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde}, \"1743639370940744633935561489495120883462564189763714270\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}, \"446371678960830626287503741310750946166416432579510853360\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12}, \"114271149813972640329600957775552242218602606740354778460178\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34}, \"29253414352376995924377845190541374007962267325530823285805620\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56}, \"7488874074208510956640728368778591746038340435335890761166238806\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78}, \"1917151762997378804900026462407319486985815151445988034858557134456\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a}, \"490790851327328974054406774376273788668368678770172936923790626420890\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc}, \"125642457939796217357928134240326089899102381765164271852490400363748028\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde}, \"32164469232587831643629602365523479014170209731882053594237542493119495390\"},\n\t\t{[]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}, \"8234104123542484900769178205574010627627573691361805720124810878238590820080\"},\n\t\t// over 32 bytes (last 32 bytes are used)\n\t\t{append([]byte{0xff}, []byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}...), \"8234104123542484900769178205574010627627573691361805720124810878238590820080\"},\n\t}\n\n\tfor _, test := range tests {\n\t\tz := new(Uint)\n\t\tz.SetBytes(test.input)\n\t\texpected := MustFromDecimal(test.expected)\n\t\tif z.Cmp(expected) != 0 {\n\t\t\tt.Errorf(\"SetBytes(%x) = %s, expected %s\", test.input, z.ToString(), test.expected)\n\t\t}\n\t}\n}\n"},{"name":"error.gno","body":"package uint256\n\nimport (\n\t\"errors\"\n)\n\nvar (\n\tErrEmptyString = errors.New(\"empty hex string\")\n\tErrSyntax = errors.New(\"invalid hex string\")\n\tErrRange = errors.New(\"number out of range\")\n\tErrMissingPrefix = errors.New(\"hex string without 0x prefix\")\n\tErrEmptyNumber = errors.New(\"hex string \\\"0x\\\"\")\n\tErrLeadingZero = errors.New(\"hex number with leading zero digits\")\n\tErrBig256Range = errors.New(\"hex number \u003e 256 bits\")\n\tErrBadBufferLength = errors.New(\"bad ssz buffer length\")\n\tErrBadEncodedLength = errors.New(\"bad ssz encoded length\")\n\tErrInvalidBase = errors.New(\"invalid base\")\n\tErrInvalidBitSize = errors.New(\"invalid bit size\")\n)\n\ntype u256Error struct {\n\tfn string // function name\n\tinput string\n\terr error\n}\n\nfunc (e *u256Error) Error() string {\n\treturn e.fn + \": \" + e.input + \": \" + e.err.Error()\n}\n\nfunc (e *u256Error) Unwrap() error {\n\treturn e.err\n}\n\nfunc errEmptyString(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrEmptyString}\n}\n\nfunc errSyntax(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrSyntax}\n}\n\nfunc errMissingPrefix(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrMissingPrefix}\n}\n\nfunc errEmptyNumber(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrEmptyNumber}\n}\n\nfunc errLeadingZero(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrLeadingZero}\n}\n\nfunc errRange(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrRange}\n}\n\nfunc errBig256Range(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrBig256Range}\n}\n\nfunc errBadBufferLength(fn, input string) error {\n\treturn \u0026u256Error{fn: fn, input: input, err: ErrBadBufferLength}\n}\n\nfunc errInvalidBase(fn string, base int) error {\n\treturn \u0026u256Error{fn: fn, input: string(base), err: ErrInvalidBase}\n}\n\nfunc errInvalidBitSize(fn string, bitSize int) error {\n\treturn \u0026u256Error{fn: fn, input: string(bitSize), err: ErrInvalidBitSize}\n}\n"},{"name":"mod.gno","body":"package uint256\n\nimport (\n\t\"math/bits\"\n)\n\n// Some utility functions\n\n// Reciprocal computes a 320-bit value representing 1/m\n//\n// Notes:\n// - specialized for m.arr[3] != 0, hence limited to 2^192 \u003c= m \u003c 2^256\n// - returns zero if m.arr[3] == 0\n// - starts with a 32-bit division, refines with newton-raphson iterations\nfunc Reciprocal(m *Uint) (mu [5]uint64) {\n\tif m.arr[3] == 0 {\n\t\treturn mu\n\t}\n\n\ts := bits.LeadingZeros64(m.arr[3]) // Replace with leadingZeros(m) for general case\n\tp := 255 - s // floor(log_2(m)), m\u003e0\n\n\t// 0 or a power of 2?\n\n\t// Check if at least one bit is set in m.arr[2], m.arr[1] or m.arr[0],\n\t// or at least two bits in m.arr[3]\n\n\tif m.arr[0]|m.arr[1]|m.arr[2]|(m.arr[3]\u0026(m.arr[3]-1)) == 0 {\n\n\t\tmu[4] = ^uint64(0) \u003e\u003e uint(p\u002663)\n\t\tmu[3] = ^uint64(0)\n\t\tmu[2] = ^uint64(0)\n\t\tmu[1] = ^uint64(0)\n\t\tmu[0] = ^uint64(0)\n\n\t\treturn mu\n\t}\n\n\t// Maximise division precision by left-aligning divisor\n\n\tvar (\n\t\ty Uint // left-aligned copy of m\n\t\tr0 uint32 // estimate of 2^31/y\n\t)\n\n\ty.Lsh(m, uint(s)) // 1/2 \u003c y \u003c 1\n\n\t// Extract most significant 32 bits\n\n\tyh := uint32(y.arr[3] \u003e\u003e 32)\n\n\tif yh == 0x80000000 { // Avoid overflow in division\n\t\tr0 = 0xffffffff\n\t} else {\n\t\tr0, _ = bits.Div32(0x80000000, 0, yh)\n\t}\n\n\t// First iteration: 32 -\u003e 64\n\n\tt1 := uint64(r0) // 2^31/y\n\tt1 *= t1 // 2^62/y^2\n\tt1, _ = bits.Mul64(t1, y.arr[3]) // 2^62/y^2 * 2^64/y / 2^64 = 2^62/y\n\n\tr1 := uint64(r0) \u003c\u003c 32 // 2^63/y\n\tr1 -= t1 // 2^63/y - 2^62/y = 2^62/y\n\tr1 *= 2 // 2^63/y\n\n\tif (r1 | (y.arr[3] \u003c\u003c 1)) == 0 {\n\t\tr1 = ^uint64(0)\n\t}\n\n\t// Second iteration: 64 -\u003e 128\n\n\t// square: 2^126/y^2\n\ta2h, a2l := bits.Mul64(r1, r1)\n\n\t// multiply by y: e2h:e2l:b2h = 2^126/y^2 * 2^128/y / 2^128 = 2^126/y\n\tb2h, _ := bits.Mul64(a2l, y.arr[2])\n\tc2h, c2l := bits.Mul64(a2l, y.arr[3])\n\td2h, d2l := bits.Mul64(a2h, y.arr[2])\n\te2h, e2l := bits.Mul64(a2h, y.arr[3])\n\n\tb2h, c := bits.Add64(b2h, c2l, 0)\n\te2l, c = bits.Add64(e2l, c2h, c)\n\te2h, _ = bits.Add64(e2h, 0, c)\n\n\t_, c = bits.Add64(b2h, d2l, 0)\n\te2l, c = bits.Add64(e2l, d2h, c)\n\te2h, _ = bits.Add64(e2h, 0, c)\n\n\t// subtract: t2h:t2l = 2^127/y - 2^126/y = 2^126/y\n\tt2l, b := bits.Sub64(0, e2l, 0)\n\tt2h, _ := bits.Sub64(r1, e2h, b)\n\n\t// double: r2h:r2l = 2^127/y\n\tr2l, c := bits.Add64(t2l, t2l, 0)\n\tr2h, _ := bits.Add64(t2h, t2h, c)\n\n\tif (r2h | r2l | (y.arr[3] \u003c\u003c 1)) == 0 {\n\t\tr2h = ^uint64(0)\n\t\tr2l = ^uint64(0)\n\t}\n\n\t// Third iteration: 128 -\u003e 192\n\n\t// square r2 (keep 256 bits): 2^190/y^2\n\ta3h, a3l := bits.Mul64(r2l, r2l)\n\tb3h, b3l := bits.Mul64(r2l, r2h)\n\tc3h, c3l := bits.Mul64(r2h, r2h)\n\n\ta3h, c = bits.Add64(a3h, b3l, 0)\n\tc3l, c = bits.Add64(c3l, b3h, c)\n\tc3h, _ = bits.Add64(c3h, 0, c)\n\n\ta3h, c = bits.Add64(a3h, b3l, 0)\n\tc3l, c = bits.Add64(c3l, b3h, c)\n\tc3h, _ = bits.Add64(c3h, 0, c)\n\n\t// multiply by y: q = 2^190/y^2 * 2^192/y / 2^192 = 2^190/y\n\n\tx0 := a3l\n\tx1 := a3h\n\tx2 := c3l\n\tx3 := c3h\n\n\tvar q0, q1, q2, q3, q4, t0 uint64\n\n\tq0, _ = bits.Mul64(x2, y.arr[0])\n\tq1, t0 = bits.Mul64(x3, y.arr[0])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, _ = bits.Add64(q1, 0, c)\n\n\tt1, _ = bits.Mul64(x1, y.arr[1])\n\tq0, c = bits.Add64(q0, t1, 0)\n\tq2, t0 = bits.Mul64(x3, y.arr[1])\n\tq1, c = bits.Add64(q1, t0, c)\n\tq2, _ = bits.Add64(q2, 0, c)\n\n\tt1, t0 = bits.Mul64(x2, y.arr[1])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tq2, _ = bits.Add64(q2, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, y.arr[2])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tq3, t0 = bits.Mul64(x3, y.arr[2])\n\tq2, c = bits.Add64(q2, t0, c)\n\tq3, _ = bits.Add64(q3, 0, c)\n\n\tt1, _ = bits.Mul64(x0, y.arr[2])\n\tq0, c = bits.Add64(q0, t1, 0)\n\tt1, t0 = bits.Mul64(x2, y.arr[2])\n\tq1, c = bits.Add64(q1, t0, c)\n\tq2, c = bits.Add64(q2, t1, c)\n\tq3, _ = bits.Add64(q3, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, y.arr[3])\n\tq1, c = bits.Add64(q1, t0, 0)\n\tq2, c = bits.Add64(q2, t1, c)\n\tq4, t0 = bits.Mul64(x3, y.arr[3])\n\tq3, c = bits.Add64(q3, t0, c)\n\tq4, _ = bits.Add64(q4, 0, c)\n\n\tt1, t0 = bits.Mul64(x0, y.arr[3])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tt1, t0 = bits.Mul64(x2, y.arr[3])\n\tq2, c = bits.Add64(q2, t0, c)\n\tq3, c = bits.Add64(q3, t1, c)\n\tq4, _ = bits.Add64(q4, 0, c)\n\n\t// subtract: t3 = 2^191/y - 2^190/y = 2^190/y\n\t_, b = bits.Sub64(0, q0, 0)\n\t_, b = bits.Sub64(0, q1, b)\n\tt3l, b := bits.Sub64(0, q2, b)\n\tt3m, b := bits.Sub64(r2l, q3, b)\n\tt3h, _ := bits.Sub64(r2h, q4, b)\n\n\t// double: r3 = 2^191/y\n\tr3l, c := bits.Add64(t3l, t3l, 0)\n\tr3m, c := bits.Add64(t3m, t3m, c)\n\tr3h, _ := bits.Add64(t3h, t3h, c)\n\n\t// Fourth iteration: 192 -\u003e 320\n\n\t// square r3\n\n\ta4h, a4l := bits.Mul64(r3l, r3l)\n\tb4h, b4l := bits.Mul64(r3l, r3m)\n\tc4h, c4l := bits.Mul64(r3l, r3h)\n\td4h, d4l := bits.Mul64(r3m, r3m)\n\te4h, e4l := bits.Mul64(r3m, r3h)\n\tf4h, f4l := bits.Mul64(r3h, r3h)\n\n\tb4h, c = bits.Add64(b4h, c4l, 0)\n\te4l, c = bits.Add64(e4l, c4h, c)\n\te4h, _ = bits.Add64(e4h, 0, c)\n\n\ta4h, c = bits.Add64(a4h, b4l, 0)\n\td4l, c = bits.Add64(d4l, b4h, c)\n\td4h, c = bits.Add64(d4h, e4l, c)\n\tf4l, c = bits.Add64(f4l, e4h, c)\n\tf4h, _ = bits.Add64(f4h, 0, c)\n\n\ta4h, c = bits.Add64(a4h, b4l, 0)\n\td4l, c = bits.Add64(d4l, b4h, c)\n\td4h, c = bits.Add64(d4h, e4l, c)\n\tf4l, c = bits.Add64(f4l, e4h, c)\n\tf4h, _ = bits.Add64(f4h, 0, c)\n\n\t// multiply by y\n\n\tx1, x0 = bits.Mul64(d4h, y.arr[0])\n\tx3, x2 = bits.Mul64(f4h, y.arr[0])\n\tt1, t0 = bits.Mul64(f4l, y.arr[0])\n\tx1, c = bits.Add64(x1, t0, 0)\n\tx2, c = bits.Add64(x2, t1, c)\n\tx3, _ = bits.Add64(x3, 0, c)\n\n\tt1, t0 = bits.Mul64(d4h, y.arr[1])\n\tx1, c = bits.Add64(x1, t0, 0)\n\tx2, c = bits.Add64(x2, t1, c)\n\tx4, t0 := bits.Mul64(f4h, y.arr[1])\n\tx3, c = bits.Add64(x3, t0, c)\n\tx4, _ = bits.Add64(x4, 0, c)\n\tt1, t0 = bits.Mul64(d4l, y.arr[1])\n\tx0, c = bits.Add64(x0, t0, 0)\n\tx1, c = bits.Add64(x1, t1, c)\n\tt1, t0 = bits.Mul64(f4l, y.arr[1])\n\tx2, c = bits.Add64(x2, t0, c)\n\tx3, c = bits.Add64(x3, t1, c)\n\tx4, _ = bits.Add64(x4, 0, c)\n\n\tt1, t0 = bits.Mul64(a4h, y.arr[2])\n\tx0, c = bits.Add64(x0, t0, 0)\n\tx1, c = bits.Add64(x1, t1, c)\n\tt1, t0 = bits.Mul64(d4h, y.arr[2])\n\tx2, c = bits.Add64(x2, t0, c)\n\tx3, c = bits.Add64(x3, t1, c)\n\tx5, t0 := bits.Mul64(f4h, y.arr[2])\n\tx4, c = bits.Add64(x4, t0, c)\n\tx5, _ = bits.Add64(x5, 0, c)\n\tt1, t0 = bits.Mul64(d4l, y.arr[2])\n\tx1, c = bits.Add64(x1, t0, 0)\n\tx2, c = bits.Add64(x2, t1, c)\n\tt1, t0 = bits.Mul64(f4l, y.arr[2])\n\tx3, c = bits.Add64(x3, t0, c)\n\tx4, c = bits.Add64(x4, t1, c)\n\tx5, _ = bits.Add64(x5, 0, c)\n\n\tt1, t0 = bits.Mul64(a4h, y.arr[3])\n\tx1, c = bits.Add64(x1, t0, 0)\n\tx2, c = bits.Add64(x2, t1, c)\n\tt1, t0 = bits.Mul64(d4h, y.arr[3])\n\tx3, c = bits.Add64(x3, t0, c)\n\tx4, c = bits.Add64(x4, t1, c)\n\tx6, t0 := bits.Mul64(f4h, y.arr[3])\n\tx5, c = bits.Add64(x5, t0, c)\n\tx6, _ = bits.Add64(x6, 0, c)\n\tt1, t0 = bits.Mul64(a4l, y.arr[3])\n\tx0, c = bits.Add64(x0, t0, 0)\n\tx1, c = bits.Add64(x1, t1, c)\n\tt1, t0 = bits.Mul64(d4l, y.arr[3])\n\tx2, c = bits.Add64(x2, t0, c)\n\tx3, c = bits.Add64(x3, t1, c)\n\tt1, t0 = bits.Mul64(f4l, y.arr[3])\n\tx4, c = bits.Add64(x4, t0, c)\n\tx5, c = bits.Add64(x5, t1, c)\n\tx6, _ = bits.Add64(x6, 0, c)\n\n\t// subtract\n\t_, b = bits.Sub64(0, x0, 0)\n\t_, b = bits.Sub64(0, x1, b)\n\tr4l, b := bits.Sub64(0, x2, b)\n\tr4k, b := bits.Sub64(0, x3, b)\n\tr4j, b := bits.Sub64(r3l, x4, b)\n\tr4i, b := bits.Sub64(r3m, x5, b)\n\tr4h, _ := bits.Sub64(r3h, x6, b)\n\n\t// Multiply candidate for 1/4y by y, with full precision\n\n\tx0 = r4l\n\tx1 = r4k\n\tx2 = r4j\n\tx3 = r4i\n\tx4 = r4h\n\n\tq1, q0 = bits.Mul64(x0, y.arr[0])\n\tq3, q2 = bits.Mul64(x2, y.arr[0])\n\tq5, q4 := bits.Mul64(x4, y.arr[0])\n\n\tt1, t0 = bits.Mul64(x1, y.arr[0])\n\tq1, c = bits.Add64(q1, t0, 0)\n\tq2, c = bits.Add64(q2, t1, c)\n\tt1, t0 = bits.Mul64(x3, y.arr[0])\n\tq3, c = bits.Add64(q3, t0, c)\n\tq4, c = bits.Add64(q4, t1, c)\n\tq5, _ = bits.Add64(q5, 0, c)\n\n\tt1, t0 = bits.Mul64(x0, y.arr[1])\n\tq1, c = bits.Add64(q1, t0, 0)\n\tq2, c = bits.Add64(q2, t1, c)\n\tt1, t0 = bits.Mul64(x2, y.arr[1])\n\tq3, c = bits.Add64(q3, t0, c)\n\tq4, c = bits.Add64(q4, t1, c)\n\tq6, t0 := bits.Mul64(x4, y.arr[1])\n\tq5, c = bits.Add64(q5, t0, c)\n\tq6, _ = bits.Add64(q6, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, y.arr[1])\n\tq2, c = bits.Add64(q2, t0, 0)\n\tq3, c = bits.Add64(q3, t1, c)\n\tt1, t0 = bits.Mul64(x3, y.arr[1])\n\tq4, c = bits.Add64(q4, t0, c)\n\tq5, c = bits.Add64(q5, t1, c)\n\tq6, _ = bits.Add64(q6, 0, c)\n\n\tt1, t0 = bits.Mul64(x0, y.arr[2])\n\tq2, c = bits.Add64(q2, t0, 0)\n\tq3, c = bits.Add64(q3, t1, c)\n\tt1, t0 = bits.Mul64(x2, y.arr[2])\n\tq4, c = bits.Add64(q4, t0, c)\n\tq5, c = bits.Add64(q5, t1, c)\n\tq7, t0 := bits.Mul64(x4, y.arr[2])\n\tq6, c = bits.Add64(q6, t0, c)\n\tq7, _ = bits.Add64(q7, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, y.arr[2])\n\tq3, c = bits.Add64(q3, t0, 0)\n\tq4, c = bits.Add64(q4, t1, c)\n\tt1, t0 = bits.Mul64(x3, y.arr[2])\n\tq5, c = bits.Add64(q5, t0, c)\n\tq6, c = bits.Add64(q6, t1, c)\n\tq7, _ = bits.Add64(q7, 0, c)\n\n\tt1, t0 = bits.Mul64(x0, y.arr[3])\n\tq3, c = bits.Add64(q3, t0, 0)\n\tq4, c = bits.Add64(q4, t1, c)\n\tt1, t0 = bits.Mul64(x2, y.arr[3])\n\tq5, c = bits.Add64(q5, t0, c)\n\tq6, c = bits.Add64(q6, t1, c)\n\tq8, t0 := bits.Mul64(x4, y.arr[3])\n\tq7, c = bits.Add64(q7, t0, c)\n\tq8, _ = bits.Add64(q8, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, y.arr[3])\n\tq4, c = bits.Add64(q4, t0, 0)\n\tq5, c = bits.Add64(q5, t1, c)\n\tt1, t0 = bits.Mul64(x3, y.arr[3])\n\tq6, c = bits.Add64(q6, t0, c)\n\tq7, c = bits.Add64(q7, t1, c)\n\tq8, _ = bits.Add64(q8, 0, c)\n\n\t// Final adjustment\n\n\t// subtract q from 1/4\n\t_, b = bits.Sub64(0, q0, 0)\n\t_, b = bits.Sub64(0, q1, b)\n\t_, b = bits.Sub64(0, q2, b)\n\t_, b = bits.Sub64(0, q3, b)\n\t_, b = bits.Sub64(0, q4, b)\n\t_, b = bits.Sub64(0, q5, b)\n\t_, b = bits.Sub64(0, q6, b)\n\t_, b = bits.Sub64(0, q7, b)\n\t_, b = bits.Sub64(uint64(1)\u003c\u003c62, q8, b)\n\n\t// decrement the result\n\tx0, t := bits.Sub64(r4l, 1, 0)\n\tx1, t = bits.Sub64(r4k, 0, t)\n\tx2, t = bits.Sub64(r4j, 0, t)\n\tx3, t = bits.Sub64(r4i, 0, t)\n\tx4, _ = bits.Sub64(r4h, 0, t)\n\n\t// commit the decrement if the subtraction underflowed (reciprocal was too large)\n\tif b != 0 {\n\t\tr4h, r4i, r4j, r4k, r4l = x4, x3, x2, x1, x0\n\t}\n\n\t// Shift to correct bit alignment, truncating excess bits\n\n\tp = (p \u0026 63) - 1\n\n\tx0, c = bits.Add64(r4l, r4l, 0)\n\tx1, c = bits.Add64(r4k, r4k, c)\n\tx2, c = bits.Add64(r4j, r4j, c)\n\tx3, c = bits.Add64(r4i, r4i, c)\n\tx4, _ = bits.Add64(r4h, r4h, c)\n\n\tif p \u003c 0 {\n\t\tr4h, r4i, r4j, r4k, r4l = x4, x3, x2, x1, x0\n\t\tp = 0 // avoid negative shift below\n\t}\n\n\t{\n\t\tr := uint(p) // right shift\n\t\tl := uint(64 - r) // left shift\n\n\t\tx0 = (r4l \u003e\u003e r) | (r4k \u003c\u003c l)\n\t\tx1 = (r4k \u003e\u003e r) | (r4j \u003c\u003c l)\n\t\tx2 = (r4j \u003e\u003e r) | (r4i \u003c\u003c l)\n\t\tx3 = (r4i \u003e\u003e r) | (r4h \u003c\u003c l)\n\t\tx4 = (r4h \u003e\u003e r)\n\t}\n\n\tif p \u003e 0 {\n\t\tr4h, r4i, r4j, r4k, r4l = x4, x3, x2, x1, x0\n\t}\n\n\tmu[0] = r4l\n\tmu[1] = r4k\n\tmu[2] = r4j\n\tmu[3] = r4i\n\tmu[4] = r4h\n\n\treturn mu\n}\n\n// reduce4 computes the least non-negative residue of x modulo m\n//\n// requires a four-word modulus (m.arr[3] \u003e 1) and its inverse (mu)\nfunc reduce4(x [8]uint64, m *Uint, mu [5]uint64) (z Uint) {\n\t// NB: Most variable names in the comments match the pseudocode for\n\t// \tBarrett reduction in the Handbook of Applied Cryptography.\n\n\t// q1 = x/2^192\n\n\tx0 := x[3]\n\tx1 := x[4]\n\tx2 := x[5]\n\tx3 := x[6]\n\tx4 := x[7]\n\n\t// q2 = q1 * mu; q3 = q2 / 2^320\n\n\tvar q0, q1, q2, q3, q4, q5, t0, t1, c uint64\n\n\tq0, _ = bits.Mul64(x3, mu[0])\n\tq1, t0 = bits.Mul64(x4, mu[0])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, _ = bits.Add64(q1, 0, c)\n\n\tt1, _ = bits.Mul64(x2, mu[1])\n\tq0, c = bits.Add64(q0, t1, 0)\n\tq2, t0 = bits.Mul64(x4, mu[1])\n\tq1, c = bits.Add64(q1, t0, c)\n\tq2, _ = bits.Add64(q2, 0, c)\n\n\tt1, t0 = bits.Mul64(x3, mu[1])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tq2, _ = bits.Add64(q2, 0, c)\n\n\tt1, t0 = bits.Mul64(x2, mu[2])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tq3, t0 = bits.Mul64(x4, mu[2])\n\tq2, c = bits.Add64(q2, t0, c)\n\tq3, _ = bits.Add64(q3, 0, c)\n\n\tt1, _ = bits.Mul64(x1, mu[2])\n\tq0, c = bits.Add64(q0, t1, 0)\n\tt1, t0 = bits.Mul64(x3, mu[2])\n\tq1, c = bits.Add64(q1, t0, c)\n\tq2, c = bits.Add64(q2, t1, c)\n\tq3, _ = bits.Add64(q3, 0, c)\n\n\tt1, _ = bits.Mul64(x0, mu[3])\n\tq0, c = bits.Add64(q0, t1, 0)\n\tt1, t0 = bits.Mul64(x2, mu[3])\n\tq1, c = bits.Add64(q1, t0, c)\n\tq2, c = bits.Add64(q2, t1, c)\n\tq4, t0 = bits.Mul64(x4, mu[3])\n\tq3, c = bits.Add64(q3, t0, c)\n\tq4, _ = bits.Add64(q4, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, mu[3])\n\tq0, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tt1, t0 = bits.Mul64(x3, mu[3])\n\tq2, c = bits.Add64(q2, t0, c)\n\tq3, c = bits.Add64(q3, t1, c)\n\tq4, _ = bits.Add64(q4, 0, c)\n\n\tt1, t0 = bits.Mul64(x0, mu[4])\n\t_, c = bits.Add64(q0, t0, 0)\n\tq1, c = bits.Add64(q1, t1, c)\n\tt1, t0 = bits.Mul64(x2, mu[4])\n\tq2, c = bits.Add64(q2, t0, c)\n\tq3, c = bits.Add64(q3, t1, c)\n\tq5, t0 = bits.Mul64(x4, mu[4])\n\tq4, c = bits.Add64(q4, t0, c)\n\tq5, _ = bits.Add64(q5, 0, c)\n\n\tt1, t0 = bits.Mul64(x1, mu[4])\n\tq1, c = bits.Add64(q1, t0, 0)\n\tq2, c = bits.Add64(q2, t1, c)\n\tt1, t0 = bits.Mul64(x3, mu[4])\n\tq3, c = bits.Add64(q3, t0, c)\n\tq4, c = bits.Add64(q4, t1, c)\n\tq5, _ = bits.Add64(q5, 0, c)\n\n\t// Drop the fractional part of q3\n\n\tq0 = q1\n\tq1 = q2\n\tq2 = q3\n\tq3 = q4\n\tq4 = q5\n\n\t// r1 = x mod 2^320\n\n\tx0 = x[0]\n\tx1 = x[1]\n\tx2 = x[2]\n\tx3 = x[3]\n\tx4 = x[4]\n\n\t// r2 = q3 * m mod 2^320\n\n\tvar r0, r1, r2, r3, r4 uint64\n\n\tr4, r3 = bits.Mul64(q0, m.arr[3])\n\t_, t0 = bits.Mul64(q1, m.arr[3])\n\tr4, _ = bits.Add64(r4, t0, 0)\n\n\tt1, r2 = bits.Mul64(q0, m.arr[2])\n\tr3, c = bits.Add64(r3, t1, 0)\n\t_, t0 = bits.Mul64(q2, m.arr[2])\n\tr4, _ = bits.Add64(r4, t0, c)\n\n\tt1, t0 = bits.Mul64(q1, m.arr[2])\n\tr3, c = bits.Add64(r3, t0, 0)\n\tr4, _ = bits.Add64(r4, t1, c)\n\n\tt1, r1 = bits.Mul64(q0, m.arr[1])\n\tr2, c = bits.Add64(r2, t1, 0)\n\tt1, t0 = bits.Mul64(q2, m.arr[1])\n\tr3, c = bits.Add64(r3, t0, c)\n\tr4, _ = bits.Add64(r4, t1, c)\n\n\tt1, t0 = bits.Mul64(q1, m.arr[1])\n\tr2, c = bits.Add64(r2, t0, 0)\n\tr3, c = bits.Add64(r3, t1, c)\n\t_, t0 = bits.Mul64(q3, m.arr[1])\n\tr4, _ = bits.Add64(r4, t0, c)\n\n\tt1, r0 = bits.Mul64(q0, m.arr[0])\n\tr1, c = bits.Add64(r1, t1, 0)\n\tt1, t0 = bits.Mul64(q2, m.arr[0])\n\tr2, c = bits.Add64(r2, t0, c)\n\tr3, c = bits.Add64(r3, t1, c)\n\t_, t0 = bits.Mul64(q4, m.arr[0])\n\tr4, _ = bits.Add64(r4, t0, c)\n\n\tt1, t0 = bits.Mul64(q1, m.arr[0])\n\tr1, c = bits.Add64(r1, t0, 0)\n\tr2, c = bits.Add64(r2, t1, c)\n\tt1, t0 = bits.Mul64(q3, m.arr[0])\n\tr3, c = bits.Add64(r3, t0, c)\n\tr4, _ = bits.Add64(r4, t1, c)\n\n\t// r = r1 - r2\n\n\tvar b uint64\n\n\tr0, b = bits.Sub64(x0, r0, 0)\n\tr1, b = bits.Sub64(x1, r1, b)\n\tr2, b = bits.Sub64(x2, r2, b)\n\tr3, b = bits.Sub64(x3, r3, b)\n\tr4, b = bits.Sub64(x4, r4, b)\n\n\t// if r\u003c0 then r+=m\n\n\tif b != 0 {\n\t\tr0, c = bits.Add64(r0, m.arr[0], 0)\n\t\tr1, c = bits.Add64(r1, m.arr[1], c)\n\t\tr2, c = bits.Add64(r2, m.arr[2], c)\n\t\tr3, c = bits.Add64(r3, m.arr[3], c)\n\t\tr4, _ = bits.Add64(r4, 0, c)\n\t}\n\n\t// while (r\u003e=m) r-=m\n\n\tfor {\n\t\t// q = r - m\n\t\tq0, b = bits.Sub64(r0, m.arr[0], 0)\n\t\tq1, b = bits.Sub64(r1, m.arr[1], b)\n\t\tq2, b = bits.Sub64(r2, m.arr[2], b)\n\t\tq3, b = bits.Sub64(r3, m.arr[3], b)\n\t\tq4, b = bits.Sub64(r4, 0, b)\n\n\t\t// if borrow break\n\t\tif b != 0 {\n\t\t\tbreak\n\t\t}\n\n\t\t// r = q\n\t\tr4, r3, r2, r1, r0 = q4, q3, q2, q1, q0\n\t}\n\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = r3, r2, r1, r0\n\n\treturn z\n}\n"},{"name":"uint256.gno","body":"// Ported from https://github.com/holiman/uint256\n// This package provides a 256-bit unsigned integer type, Uint256, and associated functions.\npackage uint256\n\nimport (\n\t\"errors\"\n\t\"math/bits\"\n\t\"strconv\"\n)\n\nconst (\n\tMaxUint64 = 1\u003c\u003c64 - 1\n\tuintSize = 32 \u003c\u003c (^uint(0) \u003e\u003e 63)\n)\n\n// Uint is represented as an array of 4 uint64, in little-endian order,\n// so that Uint[3] is the most significant, and Uint[0] is the least significant\ntype Uint struct {\n\tarr [4]uint64\n}\n\n// NewUint returns a new initialized Uint.\nfunc NewUint(val uint64) *Uint {\n\tz := \u0026Uint{arr: [4]uint64{val, 0, 0, 0}}\n\treturn z\n}\n\n// Zero returns a new Uint initialized to zero.\nfunc Zero() *Uint {\n\treturn NewUint(0)\n}\n\n// One returns a new Uint initialized to one.\nfunc One() *Uint {\n\treturn NewUint(1)\n}\n\n// SetAllOne sets all the bits of z to 1\nfunc (z *Uint) SetAllOne() *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = MaxUint64, MaxUint64, MaxUint64, MaxUint64\n\treturn z\n}\n\n// Set sets z to x and returns z.\nfunc (z *Uint) Set(x *Uint) *Uint {\n\t*z = *x\n\n\treturn z\n}\n\n// SetOne sets z to 1\nfunc (z *Uint) SetOne() *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, 0, 0, 1\n\treturn z\n}\n\nconst twoPow256Sub1 = \"115792089237316195423570985008687907853269984665640564039457584007913129639935\"\n\n// SetFromDecimal sets z from the given string, interpreted as a decimal number.\n// OBS! This method is _not_ strictly identical to the (*big.Uint).SetString(..., 10) method.\n// Notable differences:\n// - This method does not accept underscore input, e.g. \"100_000\",\n// - This method does not accept negative zero as valid, e.g \"-0\",\n// - (this method does not accept any negative input as valid))\nfunc (z *Uint) SetFromDecimal(s string) (err error) {\n\t// Remove max one leading +\n\tif len(s) \u003e 0 \u0026\u0026 s[0] == '+' {\n\t\ts = s[1:]\n\t}\n\t// Remove any number of leading zeroes\n\tif len(s) \u003e 0 \u0026\u0026 s[0] == '0' {\n\t\tvar i int\n\t\tvar c rune\n\t\tfor i, c = range s {\n\t\t\tif c != '0' {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\ts = s[i:]\n\t}\n\tif len(s) \u003c len(twoPow256Sub1) {\n\t\treturn z.fromDecimal(s)\n\t}\n\tif len(s) == len(twoPow256Sub1) {\n\t\tif s \u003e twoPow256Sub1 {\n\t\t\treturn ErrBig256Range\n\t\t}\n\t\treturn z.fromDecimal(s)\n\t}\n\treturn ErrBig256Range\n}\n\n// FromDecimal is a convenience-constructor to create an Uint from a\n// decimal (base 10) string. Numbers larger than 256 bits are not accepted.\nfunc FromDecimal(decimal string) (*Uint, error) {\n\tvar z Uint\n\tif err := z.SetFromDecimal(decimal); err != nil {\n\t\treturn nil, err\n\t}\n\treturn \u0026z, nil\n}\n\n// MustFromDecimal is a convenience-constructor to create an Uint from a\n// decimal (base 10) string.\n// Returns a new Uint and panics if any error occurred.\nfunc MustFromDecimal(decimal string) *Uint {\n\tvar z Uint\n\tif err := z.SetFromDecimal(decimal); err != nil {\n\t\tpanic(err)\n\t}\n\treturn \u0026z\n}\n\n// multipliers holds the values that are needed for fromDecimal\nvar multipliers = [5]*Uint{\n\tnil, // represents first round, no multiplication needed\n\t{[4]uint64{10000000000000000000, 0, 0, 0}}, // 10 ^ 19\n\t{[4]uint64{687399551400673280, 5421010862427522170, 0, 0}}, // 10 ^ 38\n\t{[4]uint64{5332261958806667264, 17004971331911604867, 2938735877055718769, 0}}, // 10 ^ 57\n\t{[4]uint64{0, 8607968719199866880, 532749306367912313, 1593091911132452277}}, // 10 ^ 76\n}\n\n// fromDecimal is a helper function to only ever be called via SetFromDecimal\n// this function takes a string and chunks it up, calling ParseUint on it up to 5 times\n// these chunks are then multiplied by the proper power of 10, then added together.\nfunc (z *Uint) fromDecimal(bs string) error {\n\t// first clear the input\n\tz.Clear()\n\t// the maximum value of uint64 is 18446744073709551615, which is 20 characters\n\t// one less means that a string of 19 9's is always within the uint64 limit\n\tvar (\n\t\tnum uint64\n\t\terr error\n\t\tremaining = len(bs)\n\t)\n\tif remaining == 0 {\n\t\treturn errors.New(\"EOF\")\n\t}\n\t// We proceed in steps of 19 characters (nibbles), from least significant to most significant.\n\t// This means that the first (up to) 19 characters do not need to be multiplied.\n\t// In the second iteration, our slice of 19 characters needs to be multipleied\n\t// by a factor of 10^19. Et cetera.\n\tfor i, mult := range multipliers {\n\t\tif remaining \u003c= 0 {\n\t\t\treturn nil // Done\n\t\t} else if remaining \u003e 19 {\n\t\t\tnum, err = strconv.ParseUint(bs[remaining-19:remaining], 10, 64)\n\t\t} else {\n\t\t\t// Final round\n\t\t\tnum, err = strconv.ParseUint(bs, 10, 64)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t// add that number to our running total\n\t\tif i == 0 {\n\t\t\tz.SetUint64(num)\n\t\t} else {\n\t\t\tbase := NewUint(num)\n\t\t\tz.Add(z, base.Mul(base, mult))\n\t\t}\n\t\t// Chop off another 19 characters\n\t\tif remaining \u003e 19 {\n\t\t\tbs = bs[0 : remaining-19]\n\t\t}\n\t\tremaining -= 19\n\t}\n\treturn nil\n}\n\n// Byte sets z to the value of the byte at position n,\n// with 'z' considered as a big-endian 32-byte integer\n// if 'n' \u003e 32, f is set to 0\n// Example: f = '5', n=31 =\u003e 5\nfunc (z *Uint) Byte(n *Uint) *Uint {\n\t// in z, z.arr[0] is the least significant\n\tif number, overflow := n.Uint64WithOverflow(); !overflow {\n\t\tif number \u003c 32 {\n\t\t\tnumber := z.arr[4-1-number/8]\n\t\t\toffset := (n.arr[0] \u0026 0x7) \u003c\u003c 3 // 8*(n.d % 8)\n\t\t\tz.arr[0] = (number \u0026 (0xff00000000000000 \u003e\u003e offset)) \u003e\u003e (56 - offset)\n\t\t\tz.arr[3], z.arr[2], z.arr[1] = 0, 0, 0\n\t\t\treturn z\n\t\t}\n\t}\n\n\treturn z.Clear()\n}\n\n// BitLen returns the number of bits required to represent z\nfunc (z *Uint) BitLen() int {\n\tswitch {\n\tcase z.arr[3] != 0:\n\t\treturn 192 + bits.Len64(z.arr[3])\n\tcase z.arr[2] != 0:\n\t\treturn 128 + bits.Len64(z.arr[2])\n\tcase z.arr[1] != 0:\n\t\treturn 64 + bits.Len64(z.arr[1])\n\tdefault:\n\t\treturn bits.Len64(z.arr[0])\n\t}\n}\n\n// ByteLen returns the number of bytes required to represent z\nfunc (z *Uint) ByteLen() int {\n\treturn (z.BitLen() + 7) / 8\n}\n\n// Clear sets z to 0\nfunc (z *Uint) Clear() *Uint {\n\tz.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, 0, 0, 0\n\treturn z\n}\n\nconst (\n\t// hextable = \"0123456789abcdef\"\n\tbintable = \"\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\a\\b\\t\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\n\\v\\f\\r\\x0e\\x0f\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\n\\v\\f\\r\\x0e\\x0f\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\"\n\tbadNibble = 0xff\n)\n\n// SetFromHex sets z from the given string, interpreted as a hexadecimal number.\n// OBS! This method is _not_ strictly identical to the (*big.Int).SetString(..., 16) method.\n// Notable differences:\n// - This method _require_ \"0x\" or \"0X\" prefix.\n// - This method does not accept zero-prefixed hex, e.g. \"0x0001\"\n// - This method does not accept underscore input, e.g. \"100_000\",\n// - This method does not accept negative zero as valid, e.g \"-0x0\",\n// - (this method does not accept any negative input as valid)\nfunc (z *Uint) SetFromHex(hex string) error {\n\treturn z.fromHex(hex)\n}\n\n// fromHex is the internal implementation of parsing a hex-string.\nfunc (z *Uint) fromHex(hex string) error {\n\tif err := checkNumberS(hex); err != nil {\n\t\treturn err\n\t}\n\tif len(hex) \u003e 66 {\n\t\treturn ErrBig256Range\n\t}\n\tz.Clear()\n\tend := len(hex)\n\tfor i := 0; i \u003c 4; i++ {\n\t\tstart := end - 16\n\t\tif start \u003c 2 {\n\t\t\tstart = 2\n\t\t}\n\t\tfor ri := start; ri \u003c end; ri++ {\n\t\t\tnib := bintable[hex[ri]]\n\t\t\tif nib == badNibble {\n\t\t\t\treturn ErrSyntax\n\t\t\t}\n\t\t\tz.arr[i] = z.arr[i] \u003c\u003c 4\n\t\t\tz.arr[i] += uint64(nib)\n\t\t}\n\t\tend = start\n\t}\n\treturn nil\n}\n\n// FromHex is a convenience-constructor to create an Uint from\n// a hexadecimal string. The string is required to be '0x'-prefixed\n// Numbers larger than 256 bits are not accepted.\nfunc FromHex(hex string) (*Uint, error) {\n\tvar z Uint\n\tif err := z.fromHex(hex); err != nil {\n\t\treturn nil, err\n\t}\n\treturn \u0026z, nil\n}\n\n// MustFromHex is a convenience-constructor to create an Uint from\n// a hexadecimal string.\n// Returns a new Uint and panics if any error occurred.\nfunc MustFromHex(hex string) *Uint {\n\tvar z Uint\n\tif err := z.fromHex(hex); err != nil {\n\t\tpanic(err)\n\t}\n\treturn \u0026z\n}\n\n// Clone creates a new Uint identical to z\nfunc (z *Uint) Clone() *Uint {\n\tvar x Uint\n\tx.arr[0] = z.arr[0]\n\tx.arr[1] = z.arr[1]\n\tx.arr[2] = z.arr[2]\n\tx.arr[3] = z.arr[3]\n\n\treturn \u0026x\n}\n"},{"name":"uint256_test.gno","body":"package uint256\n\nimport (\n\t\"testing\"\n)\n\nfunc TestSetAllOne(t *testing.T) {\n\tz := Zero()\n\tz.SetAllOne()\n\tif z.ToString() != twoPow256Sub1 {\n\t\tt.Errorf(\"Expected all ones, got %s\", z.ToString())\n\t}\n}\n\nfunc TestByte(t *testing.T) {\n\ttests := []struct {\n\t\tinput string\n\t\tposition uint64\n\t\texpected byte\n\t}{\n\t\t{\"0x1000000000000000000000000000000000000000000000000000000000000000\", 0, 16},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 0, 255},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 31, 255},\n\t}\n\n\tfor i, tt := range tests {\n\t\tz, _ := FromHex(tt.input)\n\t\tn := NewUint(tt.position)\n\t\tresult := z.Byte(n)\n\n\t\tif result.arr[0] != uint64(tt.expected) {\n\t\t\tt.Errorf(\"Test case %d failed. Input: %s, Position: %d, Expected: %d, Got: %d\",\n\t\t\t\ti, tt.input, tt.position, tt.expected, result.arr[0])\n\t\t}\n\n\t\t// check other array elements are 0\n\t\tif result.arr[1] != 0 || result.arr[2] != 0 || result.arr[3] != 0 {\n\t\t\tt.Errorf(\"Test case %d failed. Non-zero values in upper bytes\", i)\n\t\t}\n\t}\n\n\t// overflow\n\tz, _ := FromHex(\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\")\n\tn := NewUint(32)\n\tresult := z.Byte(n)\n\n\tif !result.IsZero() {\n\t\tt.Errorf(\"Expected zero for position \u003e= 32, got %v\", result)\n\t}\n}\n\nfunc TestBitLen(t *testing.T) {\n\ttests := []struct {\n\t\tinput string\n\t\texpected int\n\t}{\n\t\t{\"0x0\", 0},\n\t\t{\"0x1\", 1},\n\t\t{\"0xff\", 8},\n\t\t{\"0x100\", 9},\n\t\t{\"0xffff\", 16},\n\t\t{\"0x10000\", 17},\n\t\t{\"0xffffffffffffffff\", 64},\n\t\t{\"0x10000000000000000\", 65},\n\t\t{\"0xffffffffffffffffffffffffffffffff\", 128},\n\t\t{\"0x100000000000000000000000000000000\", 129},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 256},\n\t}\n\n\tfor i, tt := range tests {\n\t\tz, _ := FromHex(tt.input)\n\t\tresult := z.BitLen()\n\n\t\tif result != tt.expected {\n\t\t\tt.Errorf(\"Test case %d failed. Input: %s, Expected: %d, Got: %d\",\n\t\t\t\ti, tt.input, tt.expected, result)\n\t\t}\n\t}\n}\n\nfunc TestByteLen(t *testing.T) {\n\ttests := []struct {\n\t\tinput string\n\t\texpected int\n\t}{\n\t\t{\"0x0\", 0},\n\t\t{\"0x1\", 1},\n\t\t{\"0xff\", 1},\n\t\t{\"0x100\", 2},\n\t\t{\"0xffff\", 2},\n\t\t{\"0x10000\", 3},\n\t\t{\"0xffffffffffffffff\", 8},\n\t\t{\"0x10000000000000000\", 9},\n\t\t{\"0xffffffffffffffffffffffffffffffff\", 16},\n\t\t{\"0x100000000000000000000000000000000\", 17},\n\t\t{\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 32},\n\t}\n\n\tfor i, tt := range tests {\n\t\tz, _ := FromHex(tt.input)\n\t\tresult := z.ByteLen()\n\n\t\tif result != tt.expected {\n\t\t\tt.Errorf(\"Test case %d failed. Input: %s, Expected: %d, Got: %d\",\n\t\t\t\ti, tt.input, tt.expected, result)\n\t\t}\n\t}\n}\n\nfunc TestClone(t *testing.T) {\n\ttests := []struct {\n\t\tinput string\n\t\texpected string\n\t}{\n\t\t{\"0x1\", \"1\"},\n\t\t{\"0x100\", \"256\"},\n\t\t{\"0x10000000000000000\", \"18446744073709551616\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tz, _ := FromHex(tt.input)\n\t\tresult := z.Clone()\n\t\tif result.ToString() != tt.expected {\n\t\t\tt.Errorf(\"Test %s failed. Expected %s, got %s\", tt.input, tt.expected, result.ToString())\n\t\t}\n\t}\n}\n"},{"name":"utils.gno","body":"package uint256\n\nfunc checkNumberS(input string) error {\n\tconst fn = \"UnmarshalText\"\n\tl := len(input)\n\tif l == 0 {\n\t\treturn errEmptyString(fn, input)\n\t}\n\tif l \u003c 2 || input[0] != '0' ||\n\t\t(input[1] != 'x' \u0026\u0026 input[1] != 'X') {\n\t\treturn errMissingPrefix(fn, input)\n\t}\n\tif l == 2 {\n\t\treturn errEmptyNumber(fn, input)\n\t}\n\tif len(input) \u003e 3 \u0026\u0026 input[2] == '0' {\n\t\treturn errLeadingZero(fn, input)\n\t}\n\treturn nil\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}} @@ -76,7 +76,7 @@ {"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"poa","path":"gno.land/p/nt/poa","files":[{"name":"option.gno","body":"package poa\n\nimport \"gno.land/p/sys/validators\"\n\ntype Option func(*PoA)\n\n// WithInitialSet sets the initial PoA validator set\nfunc WithInitialSet(validators []validators.Validator) Option {\n\treturn func(p *PoA) {\n\t\tfor _, validator := range validators {\n\t\t\tp.validators.Set(validator.Address.String(), validator)\n\t\t}\n\t}\n}\n"},{"name":"poa.gno","body":"package poa\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/sys/validators\"\n)\n\nvar ErrInvalidVotingPower = errors.New(\"invalid voting power\")\n\n// PoA specifies the Proof of Authority validator set, with simple add / remove constraints.\n//\n// To add:\n// - proposed validator must not be part of the set already\n// - proposed validator voting power must be \u003e 0\n//\n// To remove:\n// - proposed validator must be part of the set already\ntype PoA struct {\n\tvalidators *avl.Tree // std.Address -\u003e validators.Validator\n}\n\n// NewPoA creates a new empty Proof of Authority validator set\nfunc NewPoA(opts ...Option) *PoA {\n\t// Create the empty set\n\tp := \u0026PoA{\n\t\tvalidators: avl.NewTree(),\n\t}\n\n\t// Apply the options\n\tfor _, opt := range opts {\n\t\topt(p)\n\t}\n\n\treturn p\n}\n\nfunc (p *PoA) AddValidator(address std.Address, pubKey string, power uint64) (validators.Validator, error) {\n\t// Validate that the operation is a valid call.\n\t// Check if the validator is already in the set\n\tif p.IsValidator(address) {\n\t\treturn validators.Validator{}, validators.ErrValidatorExists\n\t}\n\n\t// Make sure the voting power \u003e 0\n\tif power == 0 {\n\t\treturn validators.Validator{}, ErrInvalidVotingPower\n\t}\n\n\tv := validators.Validator{\n\t\tAddress: address,\n\t\tPubKey: pubKey, // TODO: in the future, verify the public key\n\t\tVotingPower: power,\n\t}\n\n\t// Add the validator to the set\n\tp.validators.Set(address.String(), v)\n\n\treturn v, nil\n}\n\nfunc (p *PoA) RemoveValidator(address std.Address) (validators.Validator, error) {\n\t// Validate that the operation is a valid call\n\t// Fetch the validator\n\tvalidator, err := p.GetValidator(address)\n\tif err != nil {\n\t\treturn validators.Validator{}, err\n\t}\n\n\t// Remove the validator from the set\n\tp.validators.Remove(address.String())\n\n\treturn validator, nil\n}\n\nfunc (p *PoA) IsValidator(address std.Address) bool {\n\t_, exists := p.validators.Get(address.String())\n\n\treturn exists\n}\n\nfunc (p *PoA) GetValidator(address std.Address) (validators.Validator, error) {\n\tvalidatorRaw, exists := p.validators.Get(address.String())\n\tif !exists {\n\t\treturn validators.Validator{}, validators.ErrValidatorMissing\n\t}\n\n\tvalidator := validatorRaw.(validators.Validator)\n\n\treturn validator, nil\n}\n\nfunc (p *PoA) GetValidators() []validators.Validator {\n\tvals := make([]validators.Validator, 0, p.validators.Size())\n\n\tp.validators.Iterate(\"\", \"\", func(_ string, value interface{}) bool {\n\t\tvalidator := value.(validators.Validator)\n\t\tvals = append(vals, validator)\n\n\t\treturn false\n\t})\n\n\treturn vals\n}\n"},{"name":"poa_test.gno","body":"package poa\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/urequire\"\n\t\"gno.land/p/sys/validators\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// generateTestValidators generates a dummy validator set\nfunc generateTestValidators(count int) []validators.Validator {\n\tvals := make([]validators.Validator, 0, count)\n\n\tfor i := 0; i \u003c count; i++ {\n\t\tval := validators.Validator{\n\t\t\tAddress: testutils.TestAddress(ufmt.Sprintf(\"%d\", i)),\n\t\t\tPubKey: \"public-key\",\n\t\t\tVotingPower: 1,\n\t\t}\n\n\t\tvals = append(vals, val)\n\t}\n\n\treturn vals\n}\n\nfunc TestPoA_AddValidator_Invalid(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"validator already in set\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tproposalAddress = testutils.TestAddress(\"caller\")\n\t\t\tproposalKey = \"public-key\"\n\n\t\t\tinitialSet = generateTestValidators(1)\n\t\t)\n\n\t\tinitialSet[0].Address = proposalAddress\n\t\tinitialSet[0].PubKey = proposalKey\n\n\t\t// Create the protocol with an initial set\n\t\tp := NewPoA(WithInitialSet(initialSet))\n\n\t\t// Attempt to add the validator\n\t\t_, err := p.AddValidator(proposalAddress, proposalKey, 1)\n\t\tuassert.ErrorIs(t, err, validators.ErrValidatorExists)\n\t})\n\n\tt.Run(\"invalid voting power\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tproposalAddress = testutils.TestAddress(\"caller\")\n\t\t\tproposalKey = \"public-key\"\n\t\t)\n\n\t\t// Create the protocol with no initial set\n\t\tp := NewPoA()\n\n\t\t// Attempt to add the validator\n\t\t_, err := p.AddValidator(proposalAddress, proposalKey, 0)\n\t\tuassert.ErrorIs(t, err, ErrInvalidVotingPower)\n\t})\n}\n\nfunc TestPoA_AddValidator(t *testing.T) {\n\tt.Parallel()\n\n\tvar (\n\t\tproposalAddress = testutils.TestAddress(\"caller\")\n\t\tproposalKey = \"public-key\"\n\t)\n\n\t// Create the protocol with no initial set\n\tp := NewPoA()\n\n\t// Attempt to add the validator\n\t_, err := p.AddValidator(proposalAddress, proposalKey, 1)\n\tuassert.NoError(t, err)\n\n\t// Make sure the validator is added\n\tif !p.IsValidator(proposalAddress) || p.validators.Size() != 1 {\n\t\tt.Fatal(\"address is not validator\")\n\t}\n}\n\nfunc TestPoA_RemoveValidator_Invalid(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"proposed removal not in set\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tproposalAddress = testutils.TestAddress(\"caller\")\n\t\t\tinitialSet = generateTestValidators(1)\n\t\t)\n\n\t\tinitialSet[0].Address = proposalAddress\n\n\t\t// Create the protocol with an initial set\n\t\tp := NewPoA(WithInitialSet(initialSet))\n\n\t\t// Attempt to remove the validator\n\t\t_, err := p.RemoveValidator(testutils.TestAddress(\"totally random\"))\n\t\tuassert.ErrorIs(t, err, validators.ErrValidatorMissing)\n\t})\n}\n\nfunc TestPoA_RemoveValidator(t *testing.T) {\n\tt.Parallel()\n\n\tvar (\n\t\tproposalAddress = testutils.TestAddress(\"caller\")\n\t\tinitialSet = generateTestValidators(1)\n\t)\n\n\tinitialSet[0].Address = proposalAddress\n\n\t// Create the protocol with an initial set\n\tp := NewPoA(WithInitialSet(initialSet))\n\n\t// Attempt to remove the validator\n\t_, err := p.RemoveValidator(proposalAddress)\n\turequire.NoError(t, err)\n\n\t// Make sure the validator is removed\n\tif p.IsValidator(proposalAddress) || p.validators.Size() != 0 {\n\t\tt.Fatal(\"address is validator\")\n\t}\n}\n\nfunc TestPoA_GetValidator(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"validator not in set\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Create the protocol with no initial set\n\t\tp := NewPoA()\n\n\t\t// Attempt to get the voting power\n\t\t_, err := p.GetValidator(testutils.TestAddress(\"caller\"))\n\t\tuassert.ErrorIs(t, err, validators.ErrValidatorMissing)\n\t})\n\n\tt.Run(\"validator fetched\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\taddress = testutils.TestAddress(\"caller\")\n\t\t\tpubKey = \"public-key\"\n\t\t\tvotingPower = uint64(10)\n\n\t\t\tinitialSet = generateTestValidators(1)\n\t\t)\n\n\t\tinitialSet[0].Address = address\n\t\tinitialSet[0].PubKey = pubKey\n\t\tinitialSet[0].VotingPower = votingPower\n\n\t\t// Create the protocol with an initial set\n\t\tp := NewPoA(WithInitialSet(initialSet))\n\n\t\t// Get the validator\n\t\tval, err := p.GetValidator(address)\n\t\turequire.NoError(t, err)\n\n\t\t// Validate the address\n\t\tif val.Address != address {\n\t\t\tt.Fatal(\"invalid address\")\n\t\t}\n\n\t\t// Validate the voting power\n\t\tif val.VotingPower != votingPower {\n\t\t\tt.Fatal(\"invalid voting power\")\n\t\t}\n\n\t\t// Validate the public key\n\t\tif val.PubKey != pubKey {\n\t\t\tt.Fatal(\"invalid public key\")\n\t\t}\n\t})\n}\n\nfunc TestPoA_GetValidators(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"empty set\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Create the protocol with no initial set\n\t\tp := NewPoA()\n\n\t\t// Attempt to get the voting power\n\t\tvals := p.GetValidators()\n\n\t\tif len(vals) != 0 {\n\t\t\tt.Fatal(\"validator set is not empty\")\n\t\t}\n\t})\n\n\tt.Run(\"validator set fetched\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tinitialSet := generateTestValidators(10)\n\n\t\t// Create the protocol with an initial set\n\t\tp := NewPoA(WithInitialSet(initialSet))\n\n\t\t// Get the validator set\n\t\tvals := p.GetValidators()\n\n\t\tif len(vals) != len(initialSet) {\n\t\t\tt.Fatal(\"returned validator set mismatch\")\n\t\t}\n\n\t\tfor _, val := range vals {\n\t\t\tfor _, initialVal := range initialSet {\n\t\t\t\tif val.Address != initialVal.Address {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\t// Validate the voting power\n\t\t\t\tuassert.Equal(t, val.VotingPower, initialVal.VotingPower)\n\n\t\t\t\t// Validate the public key\n\t\t\t\tuassert.Equal(t, val.PubKey, initialVal.PubKey)\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":"gnoface","path":"gno.land/r/demo/art/gnoface","files":[{"name":"gnoface.gno","body":"package gnoface\n\nimport (\n\t\"math/rand\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/entropy\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nfunc Render(path string) string {\n\tseed := uint64(entropy.New().Value())\n\n\tpath = strings.TrimSpace(path)\n\tif path != \"\" {\n\t\ts, err := strconv.Atoi(path)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tseed = uint64(s)\n\t}\n\n\toutput := ufmt.Sprintf(\"Gnoface #%d\\n\", seed)\n\toutput += \"```\\n\" + Draw(seed) + \"```\\n\"\n\treturn output\n}\n\nfunc Draw(seed uint64) string {\n\tvar (\n\t\thairs = []string{\n\t\t\t\" s\",\n\t\t\t\" .......\",\n\t\t\t\" s s s\",\n\t\t\t\" /\\\\ /\\\\\",\n\t\t\t\" |||||||\",\n\t\t}\n\t\theadtop = []string{\n\t\t\t\" /-------\\\\\",\n\t\t\t\" /~~~~~~~\\\\\",\n\t\t\t\" /|||||||\\\\\",\n\t\t\t\" ////////\\\\\",\n\t\t\t\" |||||||||\",\n\t\t\t\" /\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\",\n\t\t}\n\t\theadspace = []string{\n\t\t\t\" | |\",\n\t\t}\n\t\teyebrow = []string{\n\t\t\t\"~\",\n\t\t\t\"*\",\n\t\t\t\"_\",\n\t\t\t\".\",\n\t\t}\n\t\tear = []string{\n\t\t\t\"o\",\n\t\t\t\" \",\n\t\t\t\"D\",\n\t\t\t\"O\",\n\t\t\t\"\u003c\",\n\t\t\t\"\u003e\",\n\t\t\t\".\",\n\t\t\t\"|\",\n\t\t\t\")\",\n\t\t\t\"(\",\n\t\t}\n\t\teyesmiddle = []string{\n\t\t\t\"| o o |\",\n\t\t\t\"| o _ |\",\n\t\t\t\"| _ o |\",\n\t\t\t\"| . . |\",\n\t\t\t\"| O O |\",\n\t\t\t\"| v v |\",\n\t\t\t\"| X X |\",\n\t\t\t\"| x X |\",\n\t\t\t\"| X D |\",\n\t\t\t\"| ~ ~ |\",\n\t\t}\n\t\tnose = []string{\n\t\t\t\" | o |\",\n\t\t\t\" | O |\",\n\t\t\t\" | V |\",\n\t\t\t\" | L |\",\n\t\t\t\" | C |\",\n\t\t\t\" | ~ |\",\n\t\t\t\" | . . |\",\n\t\t\t\" | . |\",\n\t\t}\n\t\tmouth = []string{\n\t\t\t\" | __/ |\",\n\t\t\t\" | \\\\_/ |\",\n\t\t\t\" | . |\",\n\t\t\t\" | ___ |\",\n\t\t\t\" | ~~~ |\",\n\t\t\t\" | === |\",\n\t\t\t\" | \u003c=\u003e |\",\n\t\t}\n\t\theadbottom = []string{\n\t\t\t\" \\\\-------/\",\n\t\t\t\" \\\\~~~~~~~/\",\n\t\t\t\" \\\\_______/\",\n\t\t}\n\t)\n\n\tr := rand.New(rand.NewPCG(seed, 0xdeadbeef))\n\n\treturn pick(r, hairs) + \"\\n\" +\n\t\tpick(r, headtop) + \"\\n\" +\n\t\tpick(r, headspace) + \"\\n\" +\n\t\t\" | \" + pick(r, eyebrow) + \" \" + pick(r, eyebrow) + \" |\\n\" +\n\t\tpick(r, ear) + pick(r, eyesmiddle) + pick(r, ear) + \"\\n\" +\n\t\tpick(r, headspace) + \"\\n\" +\n\t\tpick(r, nose) + \"\\n\" +\n\t\tpick(r, headspace) + \"\\n\" +\n\t\tpick(r, mouth) + \"\\n\" +\n\t\tpick(r, headspace) + \"\\n\" +\n\t\tpick(r, headbottom) + \"\\n\"\n}\n\nfunc pick(r *rand.Rand, slice []string) string {\n\treturn slice[r.IntN(len(slice))]\n}\n\n// based on https://github.com/moul/pipotron/blob/master/dict/ascii-face.yml\n"},{"name":"gnoface_test.gno","body":"package gnoface\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nfunc TestDraw(t *testing.T) {\n\tcases := []struct {\n\t\tseed uint64\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tseed: 42,\n\t\t\texpected: `\n |||||||\n |||||||||\n | |\n | . ~ |\n)| v v |O\n | |\n | L |\n | |\n | ___ |\n | |\n \\~~~~~~~/\n`[1:],\n\t\t},\n\t\t{\n\t\t\tseed: 1337,\n\t\t\texpected: `\n .......\n |||||||||\n | |\n | . _ |\nD| x X |O\n | |\n | ~ |\n | |\n | ~~~ |\n | |\n \\~~~~~~~/\n`[1:],\n\t\t},\n\t\t{\n\t\t\tseed: 123456789,\n\t\t\texpected: `\n .......\n ////////\\\n | |\n | ~ * |\n|| x X |o\n | |\n | V |\n | |\n | . |\n | |\n \\-------/\n`[1:],\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tname := ufmt.Sprintf(\"%d\", tc.seed)\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tgot := Draw(tc.seed)\n\t\t\tuassert.Equal(t, string(tc.expected), got)\n\t\t})\n\t}\n}\n\nfunc TestRender(t *testing.T) {\n\tcases := []struct {\n\t\tpath string\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tpath: \"42\",\n\t\t\texpected: \"Gnoface #42\\n```\" + `\n |||||||\n |||||||||\n | |\n | . ~ |\n)| v v |O\n | |\n | L |\n | |\n | ___ |\n | |\n \\~~~~~~~/\n` + \"```\\n\",\n\t\t},\n\t\t{\n\t\t\tpath: \"1337\",\n\t\t\texpected: \"Gnoface #1337\\n```\" + `\n .......\n |||||||||\n | |\n | . _ |\nD| x X |O\n | |\n | ~ |\n | |\n | ~~~ |\n | |\n \\~~~~~~~/\n` + \"```\\n\",\n\t\t},\n\t\t{\n\t\t\tpath: \"123456789\",\n\t\t\texpected: \"Gnoface #123456789\\n```\" + `\n .......\n ////////\\\n | |\n | ~ * |\n|| x X |o\n | |\n | V |\n | |\n | . |\n | |\n \\-------/\n` + \"```\\n\",\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tt.Run(tc.path, func(t *testing.T) {\n\t\t\tgot := Render(tc.path)\n\t\t\tuassert.Equal(t, tc.expected, got)\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":"millipede","path":"gno.land/r/demo/art/millipede","files":[{"name":"millipede.gno","body":"package millipede\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\nconst (\n\tminSize = 1\n\tdefaultSize = 20\n\tmaxSize = 100\n)\n\nfunc Draw(size int) string {\n\tif size \u003c minSize || size \u003e maxSize {\n\t\tpanic(\"invalid millipede size\")\n\t}\n\tpaddings := []string{\" \", \" \", \"\", \" \", \" \", \" \", \" \", \" \", \" \"}\n\tvar b strings.Builder\n\tb.WriteString(\" ╚⊙ ⊙╝\\n\")\n\tfor i := 0; i \u003c size; i++ {\n\t\tb.WriteString(paddings[i%9] + \"╚═(███)═╝\\n\")\n\t}\n\treturn b.String()\n}\n\nfunc Render(path string) string {\n\tsize := defaultSize\n\n\tpath = strings.TrimSpace(path)\n\tif path != \"\" {\n\t\tvar err error\n\t\tsize, err = strconv.Atoi(path)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}\n\n\toutput := \"```\\n\" + Draw(size) + \"```\\n\"\n\tif size \u003e minSize {\n\t\toutput += ufmt.Sprintf(\"[%d](/r/demo/art/millipede:%d)\u003c \", size-1, size-1)\n\t}\n\tif size \u003c maxSize {\n\t\toutput += ufmt.Sprintf(\" \u003e[%d](/r/demo/art/millipede:%d)\", size+1, size+1)\n\t}\n\treturn output\n}\n\n// based on https://github.com/getmillipede/millipede-go/blob/977f046c39c35a650eac0fd30245e96b22c7803c/main.go\n"},{"name":"millipede_test.gno","body":"package millipede\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestRender(t *testing.T) {\n\tcases := []struct {\n\t\tpath string\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tpath: \"\",\n\t\t\texpected: \"```\" + `\n ╚⊙ ⊙╝\n ╚═(███)═╝\n ╚═(███)═╝\n╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n` + \"```\\n[19](/r/demo/art/millipede:19)\u003c \u003e[21](/r/demo/art/millipede:21)\",\n\t\t},\n\t\t{\n\t\t\tpath: \"4\",\n\t\t\texpected: \"```\" + `\n ╚⊙ ⊙╝\n ╚═(███)═╝\n ╚═(███)═╝\n╚═(███)═╝\n ╚═(███)═╝\n` + \"```\\n[3](/r/demo/art/millipede:3)\u003c \u003e[5](/r/demo/art/millipede:5)\",\n\t\t},\n\t}\n\n\tfor _, tc := range cases {\n\t\tt.Run(tc.path, func(t *testing.T) {\n\t\t\tgot := Render(tc.path)\n\t\t\tuassert.Equal(t, tc.expected, got)\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":"banktest","path":"gno.land/r/demo/banktest","files":[{"name":"README.md","body":"This is a simple test realm contract that demonstrates how to use the banker.\n\nSee [gno.land/r/demo/banktest/banktest.go](/r/demo/banktest/banktest.go) to see the original contract code.\n\nThis article will go through each line to explain how it works.\n\n```go\npackage banktest\n```\n\nThis package is locally named \"banktest\" (could be anything).\n\n```go\nimport (\n \"std\"\n)\n```\n\nThe \"std\" package is defined by the gno code in stdlibs/std/. \u003c/br\u003e Self explanatory; and you'll see more usage from std later.\n\n```go\ntype activity struct {\n caller std.Address\n sent std.Coins\n returned std.Coins\n time time.Time\n}\n\nfunc (act *activity) String() string {\n return act.caller.String() + \" \" +\n act.sent.String() + \" sent, \" +\n act.returned.String() + \" returned, at \" +\n act.time.Format(\"2006-01-02 3:04pm MST\")\n}\n\nvar latest [10]*activity\n```\n\nThis is just maintaining a list of recent activity to this contract. Notice that the \"latest\" variable is defined \"globally\" within the context of the realm with path \"gno.land/r/demo/banktest\".\n\nThis means that calls to functions defined within this package are encapsulated within this \"data realm\", where the data is mutated based on transactions that can potentially cross many realm and non-realm package boundaries (in the call stack).\n\n```go\n// Deposit will take the coins (to the realm's pkgaddr) or return them to user.\nfunc Deposit(returnDenom string, returnAmount int64) string {\n std.AssertOriginCall()\n caller := std.OriginCaller()\n send := std.Coins{{returnDenom, returnAmount}}\n```\n\nThis is the beginning of the definition of the contract function named \"Deposit\". `std.AssertOriginCall() asserts that this function was called by a gno transactional Message. The caller is the user who signed off on this transactional message. Send is the amount of deposit sent along with this message.\n\n```go\n // record activity\n act := \u0026activity{\n caller: caller,\n sent: std.OriginSend(),\n returned: send,\n time: time.Now(),\n }\n for i := len(latest) - 2; i \u003e= 0; i-- {\n latest[i+1] = latest[i] // shift by +1.\n }\n latest[0] = act\n```\n\nUpdating the \"latest\" array for viewing at gno.land/r/demo/banktest: (w/ trailing colon).\n\n```go\n // return if any.\n if returnAmount \u003e 0 {\n```\n\nIf the user requested the return of coins...\n\n```go\n banker := std.GetBanker(std.BankerTypeOriginSend)\n```\n\nuse a std.Banker instance to return any deposited coins to the original sender.\n\n```go\n pkgaddr := std.GetOrigPkgAddr()\n // TODO: use std.Coins constructors, this isn't generally safe.\n banker.SendCoins(pkgaddr, caller, send)\n return \"returned!\"\n```\n\nNotice that each realm package has an associated Cosmos address.\n\nFinally, the results are rendered via an ABCI query call when you visit [/r/demo/banktest:](/r/demo/banktest:).\n\n```go\nfunc Render(path string) string {\n // get realm coins.\n banker := std.GetBanker(std.BankerTypeReadonly)\n coins := banker.GetCoins(std.GetOrigPkgAddr())\n\n // render\n res := \"\"\n res += \"## recent activity\\n\"\n res += \"\\n\"\n for _, act := range latest {\n if act == nil {\n break\n }\n res += \" * \" + act.String() + \"\\n\"\n }\n res += \"\\n\"\n res += \"## total deposits\\n\"\n res += coins.String()\n return res\n}\n```\n\nYou can call this contract yourself, by vistiing [/r/demo/banktest](/r/demo/banktest) and the [quickstart guide](/r/demo/boards:gnolang/4).\n"},{"name":"banktest.gno","body":"package banktest\n\nimport (\n\t\"std\"\n\t\"time\"\n)\n\ntype activity struct {\n\tcaller std.Address\n\tsent std.Coins\n\treturned std.Coins\n\ttime time.Time\n}\n\nfunc (act *activity) String() string {\n\treturn act.caller.String() + \" \" +\n\t\tact.sent.String() + \" sent, \" +\n\t\tact.returned.String() + \" returned, at \" +\n\t\tact.time.Format(\"2006-01-02 3:04pm MST\")\n}\n\nvar latest [10]*activity\n\n// Deposit will take the coins (to the realm's pkgaddr) or return them to user.\nfunc Deposit(returnDenom string, returnAmount int64) string {\n\tstd.AssertOriginCall()\n\tcaller := std.OriginCaller()\n\tsend := std.Coins{{returnDenom, returnAmount}}\n\t// record activity\n\tact := \u0026activity{\n\t\tcaller: caller,\n\t\tsent: std.OriginSend(),\n\t\treturned: send,\n\t\ttime: time.Now(),\n\t}\n\tfor i := len(latest) - 2; i \u003e= 0; i-- {\n\t\tlatest[i+1] = latest[i] // shift by +1.\n\t}\n\tlatest[0] = act\n\t// return if any.\n\tif returnAmount \u003e 0 {\n\t\tbanker := std.GetBanker(std.BankerTypeOriginSend)\n\t\tpkgaddr := std.GetOrigPkgAddr()\n\t\t// TODO: use std.Coins constructors, this isn't generally safe.\n\t\tbanker.SendCoins(pkgaddr, caller, send)\n\t\treturn \"returned!\"\n\t} else {\n\t\treturn \"thank you!\"\n\t}\n}\n\nfunc Render(path string) string {\n\t// get realm coins.\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tcoins := banker.GetCoins(std.GetOrigPkgAddr())\n\n\t// render\n\tres := \"\"\n\tres += \"## recent activity\\n\"\n\tres += \"\\n\"\n\tfor _, act := range latest {\n\t\tif act == nil {\n\t\t\tbreak\n\t\t}\n\t\tres += \" * \" + act.String() + \"\\n\"\n\t}\n\tres += \"\\n\"\n\tres += \"## total deposits\\n\"\n\tres += coins.String()\n\treturn res\n}\n"},{"name":"z_0_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\n// SEND: 100000000ugnot\n\npackage bank1\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/banktest\"\n)\n\nfunc main() {\n\t// set up main address and banktest addr.\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/bank1\")\n\tstd.TestSetOriginCaller(mainaddr)\n\tstd.TestSetOrigPkgAddr(banktestAddr)\n\n\t// get and print balance of mainaddr.\n\t// with the SEND, + 200 gnot given by the TestContext, main should have 300gnot.\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\t// simulate a Deposit call. use Send + OriginSend to simulate -send.\n\tbanker.SendCoins(mainaddr, banktestAddr, std.Coins{{\"ugnot\", 100_000_000}})\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 100_000_000}}, nil)\n\tres := banktest.Deposit(\"ugnot\", 50_000_000)\n\tprintln(\"Deposit():\", res)\n\n\t// print main balance after.\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal)\n\n\t// simulate a Render(). banker should have given back all coins.\n\tres = banktest.Render(\"\")\n\tprintln(res)\n}\n\n// Output:\n// main before: 300000000ugnot\n// Deposit(): returned!\n// main after: 250000000ugnot\n// ## recent activity\n//\n// * g1tnpdmvrmtgql8fmxgsq9rwtst5hsxahk3f05dk 100000000ugnot sent, 50000000ugnot returned, at 2009-02-13 11:31pm UTC\n//\n// ## total deposits\n// 50000000ugnot\n"},{"name":"z_1_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\npackage bank1\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/banktest\"\n)\n\nfunc main() {\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\n\t// simulate a Deposit call.\n\tstd.TestSetOrigPkgAddr(banktestAddr)\n\tstd.TestIssueCoins(banktestAddr, std.Coins{{\"ugnot\", 100000000}})\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 100000000}}, nil)\n\tres := banktest.Deposit(\"ugnot\", 101000000)\n\tprintln(res)\n}\n\n// Error:\n// cannot send \"101000000ugnot\", limit \"100000000ugnot\" exceeded with \"\" already spent\n"},{"name":"z_2_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\npackage bank1\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/banktest\"\n)\n\nfunc main() {\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\n\t// print main balance before.\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/bank1\")\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal) // plus OriginSend equals 300.\n\n\t// simulate a Deposit call.\n\tstd.TestSetOrigPkgAddr(banktestAddr)\n\tstd.TestIssueCoins(banktestAddr, std.Coins{{\"ugnot\", 100000000}})\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 100000000}}, nil)\n\tres := banktest.Deposit(\"ugnot\", 55000000)\n\tprintln(\"Deposit():\", res)\n\n\t// print main balance after.\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal) // now 255.\n\n\t// simulate a Render().\n\tres = banktest.Render(\"\")\n\tprintln(res)\n}\n\n// Output:\n// main before: 200000000ugnot\n// Deposit(): returned!\n// main after: 255000000ugnot\n// ## recent activity\n//\n// * g1tnpdmvrmtgql8fmxgsq9rwtst5hsxahk3f05dk 100000000ugnot sent, 55000000ugnot returned, at 2009-02-13 11:31pm UTC\n//\n// ## total deposits\n// 45000000ugnot\n"},{"name":"z_3_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\npackage bank1\n\nimport (\n\t\"std\"\n)\n\nfunc main() {\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/bank1\")\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tsend := std.Coins{{\"ugnot\", 123}}\n\tbanker.SendCoins(banktestAddr, mainaddr, send)\n\n}\n\n// Error:\n// can only send coins from realm that created banker \"g1tnpdmvrmtgql8fmxgsq9rwtst5hsxahk3f05dk\", not \"g1dv3435088tlrgggf745kaud0ptrkc9v42k8llz\"\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":"banktest","path":"gno.land/r/demo/banktest","files":[{"name":"README.md","body":"This is a simple test realm contract that demonstrates how to use the banker.\n\nSee [gno.land/r/demo/banktest/banktest.go](/r/demo/banktest/banktest.go) to see the original contract code.\n\nThis article will go through each line to explain how it works.\n\n```go\npackage banktest\n```\n\nThis package is locally named \"banktest\" (could be anything).\n\n```go\nimport (\n \"std\"\n)\n```\n\nThe \"std\" package is defined by the gno code in stdlibs/std/. \u003c/br\u003e Self explanatory; and you'll see more usage from std later.\n\n```go\ntype activity struct {\n caller std.Address\n sent std.Coins\n returned std.Coins\n time time.Time\n}\n\nfunc (act *activity) String() string {\n return act.caller.String() + \" \" +\n act.sent.String() + \" sent, \" +\n act.returned.String() + \" returned, at \" +\n act.time.Format(\"2006-01-02 3:04pm MST\")\n}\n\nvar latest [10]*activity\n```\n\nThis is just maintaining a list of recent activity to this contract. Notice that the \"latest\" variable is defined \"globally\" within the context of the realm with path \"gno.land/r/demo/banktest\".\n\nThis means that calls to functions defined within this package are encapsulated within this \"data realm\", where the data is mutated based on transactions that can potentially cross many realm and non-realm package boundaries (in the call stack).\n\n```go\n// Deposit will take the coins (to the realm's pkgaddr) or return them to user.\nfunc Deposit(returnDenom string, returnAmount int64) string {\n std.AssertOriginCall()\n caller := std.OriginCaller()\n send := std.Coins{{returnDenom, returnAmount}}\n```\n\nThis is the beginning of the definition of the contract function named \"Deposit\". `std.AssertOriginCall() asserts that this function was called by a gno transactional Message. The caller is the user who signed off on this transactional message. Send is the amount of deposit sent along with this message.\n\n```go\n // record activity\n act := \u0026activity{\n caller: caller,\n sent: std.OriginSend(),\n returned: send,\n time: time.Now(),\n }\n for i := len(latest) - 2; i \u003e= 0; i-- {\n latest[i+1] = latest[i] // shift by +1.\n }\n latest[0] = act\n```\n\nUpdating the \"latest\" array for viewing at gno.land/r/demo/banktest: (w/ trailing colon).\n\n```go\n // return if any.\n if returnAmount \u003e 0 {\n```\n\nIf the user requested the return of coins...\n\n```go\n banker := std.GetBanker(std.BankerTypeOriginSend)\n```\n\nuse a std.Banker instance to return any deposited coins to the original sender.\n\n```go\n pkgaddr := std.GetOriginPkgAddr()\n // TODO: use std.Coins constructors, this isn't generally safe.\n banker.SendCoins(pkgaddr, caller, send)\n return \"returned!\"\n```\n\nNotice that each realm package has an associated Cosmos address.\n\nFinally, the results are rendered via an ABCI query call when you visit [/r/demo/banktest:](/r/demo/banktest:).\n\n```go\nfunc Render(path string) string {\n // get realm coins.\n banker := std.GetBanker(std.BankerTypeReadonly)\n coins := banker.GetCoins(std.GetOriginPkgAddr())\n\n // render\n res := \"\"\n res += \"## recent activity\\n\"\n res += \"\\n\"\n for _, act := range latest {\n if act == nil {\n break\n }\n res += \" * \" + act.String() + \"\\n\"\n }\n res += \"\\n\"\n res += \"## total deposits\\n\"\n res += coins.String()\n return res\n}\n```\n\nYou can call this contract yourself, by vistiing [/r/demo/banktest](/r/demo/banktest) and the [quickstart guide](/r/demo/boards:gnolang/4).\n"},{"name":"banktest.gno","body":"package banktest\n\nimport (\n\t\"std\"\n\t\"time\"\n)\n\ntype activity struct {\n\tcaller std.Address\n\tsent std.Coins\n\treturned std.Coins\n\ttime time.Time\n}\n\nfunc (act *activity) String() string {\n\treturn act.caller.String() + \" \" +\n\t\tact.sent.String() + \" sent, \" +\n\t\tact.returned.String() + \" returned, at \" +\n\t\tact.time.Format(\"2006-01-02 3:04pm MST\")\n}\n\nvar latest [10]*activity\n\n// Deposit will take the coins (to the realm's pkgaddr) or return them to user.\nfunc Deposit(returnDenom string, returnAmount int64) string {\n\tstd.AssertOriginCall()\n\tcaller := std.OriginCaller()\n\tsend := std.Coins{{returnDenom, returnAmount}}\n\t// record activity\n\tact := \u0026activity{\n\t\tcaller: caller,\n\t\tsent: std.OriginSend(),\n\t\treturned: send,\n\t\ttime: time.Now(),\n\t}\n\tfor i := len(latest) - 2; i \u003e= 0; i-- {\n\t\tlatest[i+1] = latest[i] // shift by +1.\n\t}\n\tlatest[0] = act\n\t// return if any.\n\tif returnAmount \u003e 0 {\n\t\tbanker := std.GetBanker(std.BankerTypeOriginSend)\n\t\tpkgaddr := std.GetOriginPkgAddr()\n\t\t// TODO: use std.Coins constructors, this isn't generally safe.\n\t\tbanker.SendCoins(pkgaddr, caller, send)\n\t\treturn \"returned!\"\n\t} else {\n\t\treturn \"thank you!\"\n\t}\n}\n\nfunc Render(path string) string {\n\t// get realm coins.\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tcoins := banker.GetCoins(std.GetOriginPkgAddr())\n\n\t// render\n\tres := \"\"\n\tres += \"## recent activity\\n\"\n\tres += \"\\n\"\n\tfor _, act := range latest {\n\t\tif act == nil {\n\t\t\tbreak\n\t\t}\n\t\tres += \" * \" + act.String() + \"\\n\"\n\t}\n\tres += \"\\n\"\n\tres += \"## total deposits\\n\"\n\tres += coins.String()\n\treturn res\n}\n"},{"name":"z_0_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\n// SEND: 100000000ugnot\n\npackage bank1\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/banktest\"\n)\n\nfunc main() {\n\t// set up main address and banktest addr.\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/bank1\")\n\tstd.TestSetOriginCaller(mainaddr)\n\tstd.TestSetOriginPkgAddr(banktestAddr)\n\n\t// get and print balance of mainaddr.\n\t// with the SEND, + 200 gnot given by the TestContext, main should have 300gnot.\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\t// simulate a Deposit call. use Send + OriginSend to simulate -send.\n\tbanker.SendCoins(mainaddr, banktestAddr, std.Coins{{\"ugnot\", 100_000_000}})\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 100_000_000}}, nil)\n\tres := banktest.Deposit(\"ugnot\", 50_000_000)\n\tprintln(\"Deposit():\", res)\n\n\t// print main balance after.\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal)\n\n\t// simulate a Render(). banker should have given back all coins.\n\tres = banktest.Render(\"\")\n\tprintln(res)\n}\n\n// Output:\n// main before: 300000000ugnot\n// Deposit(): returned!\n// main after: 250000000ugnot\n// ## recent activity\n//\n// * g1tnpdmvrmtgql8fmxgsq9rwtst5hsxahk3f05dk 100000000ugnot sent, 50000000ugnot returned, at 2009-02-13 11:31pm UTC\n//\n// ## total deposits\n// 50000000ugnot\n"},{"name":"z_1_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\npackage bank1\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/banktest\"\n)\n\nfunc main() {\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\n\t// simulate a Deposit call.\n\tstd.TestSetOriginPkgAddr(banktestAddr)\n\tstd.TestIssueCoins(banktestAddr, std.Coins{{\"ugnot\", 100000000}})\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 100000000}}, nil)\n\tres := banktest.Deposit(\"ugnot\", 101000000)\n\tprintln(res)\n}\n\n// Error:\n// cannot send \"101000000ugnot\", limit \"100000000ugnot\" exceeded with \"\" already spent\n"},{"name":"z_2_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\npackage bank1\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/banktest\"\n)\n\nfunc main() {\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\n\t// print main balance before.\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/bank1\")\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal) // plus OriginSend equals 300.\n\n\t// simulate a Deposit call.\n\tstd.TestSetOriginPkgAddr(banktestAddr)\n\tstd.TestIssueCoins(banktestAddr, std.Coins{{\"ugnot\", 100000000}})\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 100000000}}, nil)\n\tres := banktest.Deposit(\"ugnot\", 55000000)\n\tprintln(\"Deposit():\", res)\n\n\t// print main balance after.\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal) // now 255.\n\n\t// simulate a Render().\n\tres = banktest.Render(\"\")\n\tprintln(res)\n}\n\n// Output:\n// main before: 200000000ugnot\n// Deposit(): returned!\n// main after: 255000000ugnot\n// ## recent activity\n//\n// * g1tnpdmvrmtgql8fmxgsq9rwtst5hsxahk3f05dk 100000000ugnot sent, 55000000ugnot returned, at 2009-02-13 11:31pm UTC\n//\n// ## total deposits\n// 45000000ugnot\n"},{"name":"z_3_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\npackage bank1\n\nimport (\n\t\"std\"\n)\n\nfunc main() {\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/bank1\")\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tsend := std.Coins{{\"ugnot\", 123}}\n\tbanker.SendCoins(banktestAddr, mainaddr, send)\n\n}\n\n// Error:\n// can only send coins from realm that created banker \"g1tnpdmvrmtgql8fmxgsq9rwtst5hsxahk3f05dk\", not \"g1dv3435088tlrgggf745kaud0ptrkc9v42k8llz\"\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":"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)\n\nvar (\n\tbanker *grc20.Banker // private banker.\n\tToken grc20.Token // public safe-object.\n)\n\nfunc init() {\n\tbanker = grc20.NewBanker(\"Bar\", \"BAR\", 4)\n\tToken = banker.Token()\n}\n\nfunc Faucet() string {\n\tcaller := std.PreviousRealm().Addr()\n\tif err := banker.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 banker.RenderHome() // XXX: should be 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.TestSetOriginCaller(alice) // XXX: should not need this\n\n\turequire.Equal(t, Token.BalanceOf(alice), uint64(0))\n\turequire.Equal(t, Faucet(), \"OK\")\n\turequire.Equal(t, Token.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":"counter","path":"gno.land/r/demo/counter","files":[{"name":"counter.gno","body":"package counter\n\nimport \"strconv\"\n\nvar counter int\n\nfunc Increment() int {\n\tcounter++\n\treturn counter\n}\n\nfunc Render(_ string) string {\n\treturn strconv.Itoa(counter)\n}\n"},{"name":"counter_test.gno","body":"package counter\n\nimport \"testing\"\n\nfunc TestIncrement(t *testing.T) {\n\tcounter = 0\n\tval := Increment()\n\tif val != 1 {\n\t\tt.Fatalf(\"result from Increment(): %d != 1\", val)\n\t}\n\tif counter != val {\n\t\tt.Fatalf(\"counter (%d) != val (%d)\", counter, val)\n\t}\n}\n\nfunc TestRender(t *testing.T) {\n\tcounter = 1337\n\tres := Render(\"\")\n\tif res != \"1337\" {\n\t\tt.Fatalf(\"render result %q != %q\", res, \"1337\")\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":"govdao","path":"gno.land/r/gov/dao/v2","files":[{"name":"dao.gno","body":"package govdao\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/membstore\"\n\t\"gno.land/p/demo/simpledao\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar (\n\td *simpledao.SimpleDAO // the current active DAO implementation\n\tmembers membstore.MemberStore // the member store\n)\n\nfunc init() {\n\tvar (\n\t\tset = []membstore.Member{\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\"), // Jae\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"), // Manfred\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1e6gxg5tvc55mwsn7t7dymmlasratv7mkv0rap2\"), // Milos\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1jazghxvvgz3egnr2fc8uf72z4g0l03596y9ls7\"), // Nemanja\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1qhskthp2uycmg4zsdc9squ2jds7yv3t0qyrlnp\"), // Petar\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g18amm3fc00t43dcxsys6udug0czyvqt9e7p23rd\"), // Marc\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1dfr24yhk5ztwtqn2a36m8f6ud8cx5hww4dkjfl\"), // Antonio\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g19p3yzr3cuhzqa02j0ce6kzvyjqfzwemw3vam0x\"), // Guilhem\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1mx4pum9976th863jgry4sdjzfwu03qan5w2v9j\"), // Ray\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g127l4gkhk0emwsx5tmxe96sp86c05h8vg5tufzq\"), // Maxwell\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1cpx59z5r8vzeww2fm4ezpz7yvjs7kptywkm864\"), // Morgan\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1ker4vvggvsyatexxn3hkthp2hu80pkhrwmuczr\"), // Sergio\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g18x425qmujg99cfz3q97y4uep5pxjq3z8lmpt25\"), // Antoine\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t// GNO DEVX\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g16tfrrul20g4jzt3z303raqw8vs8s2pqqh5clwu\"), // Ilker\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1hy6zry03hg5d8le9s2w4fxme6236hkgd928dun\"), // Jerónimo\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g15ruzptpql4dpuyzej0wkt5rq6r26kw4nxu9fwd\"), // Denis\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1dnllrdzwfhxv3evyk09y48mgn5phfjvtyrlzm7\"), // Danny\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1778y2yphxs2wpuaflsy5y9qwcd4gttn4g5yjx5\"), // Michelle\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1mq7g0jszdmn4qdpc9tq94w0gyex37su892n80m\"), // Alan\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g197q5e9v00vuz256ly7fq7v3ekaun5cr7wmjgfh\"), // Salvo\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1mpkp5lm8lwpm0pym4388836d009zfe4maxlqsq\"), // Alexis\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g125em6arxsnj49vx35f0n0z34putv5ty3376fg5\"), // Leon\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1whzkakk4hzjkvy60d5pwfk484xu67ar2cl62h2\"), // Kirk\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t// AiB\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1sw5xklxjjuv0yvuxy5f5s3l3mnj0nqq626a9wr\"), // Albert\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t// ONBLOC\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g12vx7dn3dqq89mz550zwunvg4qw6epq73d9csay\"), // Dongwon\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1r04aw56fgvzy859fachr8hzzhqkulkaemltr76\"), // Blake\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g17n4y745s08awwq4e0a38lagsgtntna0749tnxe\"), // Jinwoo\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1ckae7tc5sez8ul3ssne75sk4muwgttp6ks2ky9\"), // ByeongJun\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t\t// TERITORI\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g14u5eaheavy0ux4dmpykg2gvxpvqvexm9cyg58a\"), // Norman\n\t\t\t\tVotingPower: 5,\n\t\t\t},\n\t\t\t// BERTY\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1qynsu9dwj9lq0m5fkje7jh6qy3md80ztqnshhm\"), // Rémi\n\t\t\t\tVotingPower: 5,\n\t\t\t},\n\t\t\t// FLIPPANDO / ZENTASKTIC\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g17ernafy6ctpcz6uepfsq2js8x2vz0wladh5yc3\"), // Dragos\n\t\t\t\tVotingPower: 5,\n\t\t\t},\n\t\t}\n\t)\n\n\t// Set the member store\n\tmembers = membstore.NewMembStore(membstore.WithInitialMembers(set))\n\n\t// Set the DAO implementation\n\td = simpledao.New(members)\n}\n\n// Propose is designed to be called by another contract or with\n// `maketx run`, not by a `maketx call`.\nfunc Propose(request dao.ProposalRequest) uint64 {\n\tidx, err := d.Propose(request)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn idx\n}\n\n// VoteOnProposal casts a vote for the given proposal\nfunc VoteOnProposal(id uint64, option dao.VoteOption) {\n\tif err := d.VoteOnProposal(id, option); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// ExecuteProposal executes the proposal\nfunc ExecuteProposal(id uint64) {\n\tif err := d.ExecuteProposal(id); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// GetPropStore returns the active proposal store\nfunc GetPropStore() dao.PropStore {\n\treturn d\n}\n\n// GetMembStore returns the active member store\nfunc GetMembStore() membstore.MemberStore {\n\treturn members\n}\n\nfunc Render(path string) string {\n\tif path == \"\" {\n\t\tnumProposals := d.Size()\n\n\t\tif numProposals == 0 {\n\t\t\treturn \"No proposals found :(\" // corner case\n\t\t}\n\n\t\toutput := \"\"\n\n\t\toffset := uint64(0)\n\t\tif numProposals \u003e= 10 {\n\t\t\toffset = uint64(numProposals) - 10\n\t\t}\n\n\t\t// Fetch the last 10 proposals\n\t\tfor idx, prop := range d.Proposals(offset, uint64(10)) {\n\t\t\toutput += ufmt.Sprintf(\n\t\t\t\t\"- [Proposal #%d](%s:%d) - (**%s**)(by %s)\\n\",\n\t\t\t\tidx,\n\t\t\t\t\"/r/gov/dao/v2\",\n\t\t\t\tidx,\n\t\t\t\tprop.Status().String(),\n\t\t\t\tprop.Author().String(),\n\t\t\t)\n\t\t}\n\n\t\treturn output\n\t}\n\n\t// Display the detailed proposal\n\tidx, err := strconv.Atoi(path)\n\tif err != nil {\n\t\treturn \"404: Invalid proposal ID\"\n\t}\n\n\t// Fetch the proposal\n\tprop, err := d.ProposalByID(uint64(idx))\n\tif err != nil {\n\t\treturn ufmt.Sprintf(\"unable to fetch proposal, %s\", err.Error())\n\t}\n\n\t// Render the proposal\n\toutput := \"\"\n\toutput += ufmt.Sprintf(\"# Prop #%d\", idx)\n\toutput += \"\\n\\n\"\n\toutput += prop.Render()\n\toutput += \"\\n\\n\"\n\n\treturn output\n}\n"},{"name":"poc.gno","body":"package govdao\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/combinederr\"\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/membstore\"\n\t\"gno.land/p/gov/executor\"\n)\n\nvar errNoChangesProposed = errors.New(\"no set changes proposed\")\n\n// NewGovDAOExecutor creates the govdao wrapped callback executor\nfunc NewGovDAOExecutor(cb func() error) dao.Executor {\n\tif cb == nil {\n\t\tpanic(errNoChangesProposed)\n\t}\n\n\treturn executor.NewCallbackExecutor(\n\t\tcb,\n\t\tstd.CurrentRealm().PkgPath(),\n\t)\n}\n\n// NewMemberPropExecutor returns the GOVDAO member change executor\nfunc NewMemberPropExecutor(changesFn func() []membstore.Member) dao.Executor {\n\tif changesFn == nil {\n\t\tpanic(errNoChangesProposed)\n\t}\n\n\tcallback := func() error {\n\t\terrs := \u0026combinederr.CombinedError{}\n\t\tcbMembers := changesFn()\n\n\t\tfor _, member := range cbMembers {\n\t\t\tswitch {\n\t\t\tcase !members.IsMember(member.Address):\n\t\t\t\t// Addition request\n\t\t\t\terr := members.AddMember(member)\n\n\t\t\t\terrs.Add(err)\n\t\t\tcase member.VotingPower == 0:\n\t\t\t\t// Remove request\n\t\t\t\terr := members.UpdateMember(member.Address, membstore.Member{\n\t\t\t\t\tAddress: member.Address,\n\t\t\t\t\tVotingPower: 0, // 0 indicated removal\n\t\t\t\t})\n\n\t\t\t\terrs.Add(err)\n\t\t\tdefault:\n\t\t\t\t// Update request\n\t\t\t\terr := members.UpdateMember(member.Address, member)\n\n\t\t\t\terrs.Add(err)\n\t\t\t}\n\t\t}\n\n\t\t// Check if there were any execution errors\n\t\tif errs.Size() == 0 {\n\t\t\treturn nil\n\t\t}\n\n\t\treturn errs\n\t}\n\n\treturn NewGovDAOExecutor(callback)\n}\n\nfunc NewMembStoreImplExecutor(changeFn func() membstore.MemberStore) dao.Executor {\n\tif changeFn == nil {\n\t\tpanic(errNoChangesProposed)\n\t}\n\n\tcallback := func() error {\n\t\tsetMembStoreImpl(changeFn())\n\n\t\treturn nil\n\t}\n\n\treturn NewGovDAOExecutor(callback)\n}\n\n// setMembStoreImpl sets a new dao.MembStore implementation\nfunc setMembStoreImpl(impl membstore.MemberStore) {\n\tif impl == nil {\n\t\tpanic(\"invalid member store\")\n\t}\n\n\tmembers = impl\n}\n"},{"name":"prop1_filetest.gno","body":"// Please note that this package is intended for demonstration purposes only.\n// You could execute this code (the init part) by running a `maketx run` command\n// or by uploading a similar package to a personal namespace.\n//\n// For the specific case of validators, a `r/gnoland/valopers` will be used to\n// organize the lifecycle of validators (register, etc), and this more complex\n// contract will be responsible to generate proposals.\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/dao\"\n\tpVals \"gno.land/p/sys/validators\"\n\tgovdao \"gno.land/r/gov/dao/v2\"\n\tvalidators \"gno.land/r/sys/validators/v2\"\n)\n\nfunc init() {\n\tchangesFn := func() []pVals.Validator {\n\t\treturn []pVals.Validator{\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g12345678\"),\n\t\t\t\tPubKey: \"pubkey\",\n\t\t\t\tVotingPower: 10, // add a new validator\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g000000000\"),\n\t\t\t\tPubKey: \"pubkey\",\n\t\t\t\tVotingPower: 10, // add a new validator\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g000000000\"),\n\t\t\t\tPubKey: \"pubkey\",\n\t\t\t\tVotingPower: 0, // remove an existing validator\n\t\t\t},\n\t\t}\n\t}\n\n\t// Wraps changesFn to emit a certified event only if executed from a\n\t// complete governance proposal process.\n\texecutor := validators.NewPropExecutor(changesFn)\n\n\t// Create a proposal\n\tdescription := \"manual valset changes proposal example\"\n\n\tprop := dao.ProposalRequest{\n\t\tDescription: description,\n\t\tExecutor: executor,\n\t}\n\n\tgovdao.Propose(prop)\n}\n\nfunc main() {\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"\"))\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tgovdao.VoteOnProposal(0, dao.YesVote)\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tprintln(validators.Render(\"\"))\n\tprintln(\"--\")\n\tgovdao.ExecuteProposal(0)\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tprintln(validators.Render(\"\"))\n}\n\n// Output:\n// --\n// - [Proposal #0](/r/gov/dao/v2:0) - (**active**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm)\n//\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// manual valset changes proposal example\n//\n// Status: active\n//\n// Voting stats: YES 0 (0%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 10 (100%)\n//\n// Threshold met: false\n//\n//\n// --\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// manual valset changes proposal example\n//\n// Status: accepted\n//\n// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%)\n//\n// Threshold met: true\n//\n//\n// --\n// No valset changes to apply.\n// --\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// manual valset changes proposal example\n//\n// Status: execution successful\n//\n// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%)\n//\n// Threshold met: true\n//\n//\n// --\n// Valset changes:\n// - #123: g12345678 (10)\n// - #123: g000000000 (10)\n// - #123: g000000000 (0)\n"},{"name":"prop2_filetest.gno","body":"package main\n\nimport (\n\t\"time\"\n\n\t\"gno.land/p/demo/dao\"\n\tgnoblog \"gno.land/r/gnoland/blog\"\n\tgovdao \"gno.land/r/gov/dao/v2\"\n)\n\nfunc init() {\n\tex := gnoblog.NewPostExecutor(\n\t\t\"hello-from-govdao\", // slug\n\t\t\"Hello from GovDAO!\", // title\n\t\t\"This post was published by a GovDAO proposal.\", // body\n\t\ttime.Now().Format(time.RFC3339), // publication date\n\t\t\"moul\", // authors\n\t\t\"govdao,example\", // tags\n\t)\n\n\t// Create a proposal\n\tdescription := \"post a new blogpost about govdao\"\n\n\tprop := dao.ProposalRequest{\n\t\tDescription: description,\n\t\tExecutor: ex,\n\t}\n\n\tgovdao.Propose(prop)\n}\n\nfunc main() {\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"\"))\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tgovdao.VoteOnProposal(0, \"YES\")\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tprintln(gnoblog.Render(\"\"))\n\tprintln(\"--\")\n\tgovdao.ExecuteProposal(0)\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tprintln(gnoblog.Render(\"\"))\n}\n\n// Output:\n// --\n// - [Proposal #0](/r/gov/dao/v2:0) - (**active**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm)\n//\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// post a new blogpost about govdao\n//\n// Status: active\n//\n// Voting stats: YES 0 (0%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 10 (100%)\n//\n// Threshold met: false\n//\n//\n// --\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// post a new blogpost about govdao\n//\n// Status: accepted\n//\n// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%)\n//\n// Threshold met: true\n//\n//\n// --\n// # Gnoland's Blog\n//\n// No posts.\n// --\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// post a new blogpost about govdao\n//\n// Status: execution successful\n//\n// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%)\n//\n// Threshold met: true\n//\n//\n// --\n// # Gnoland's Blog\n//\n// \u003cdiv class='columns-3'\u003e\u003cdiv\u003e\n//\n// ### [Hello from GovDAO!](/r/gnoland/blog:p/hello-from-govdao)\n// 13 Feb 2009\n// \u003c/div\u003e\u003c/div\u003e\n"},{"name":"prop3_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/membstore\"\n\tgovdao \"gno.land/r/gov/dao/v2\"\n)\n\nfunc init() {\n\tmemberFn := func() []membstore.Member {\n\t\treturn []membstore.Member{\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g123\"),\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g456\"),\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g789\"),\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t}\n\t}\n\n\t// Create a proposal\n\tdescription := \"add new members to the govdao\"\n\n\tprop := dao.ProposalRequest{\n\t\tDescription: description,\n\t\tExecutor: govdao.NewMemberPropExecutor(memberFn),\n\t}\n\n\tgovdao.Propose(prop)\n}\n\nfunc main() {\n\tprintln(\"--\")\n\tprintln(govdao.GetMembStore().Size())\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"\"))\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tgovdao.VoteOnProposal(0, \"YES\")\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"\"))\n\tprintln(\"--\")\n\tgovdao.ExecuteProposal(0)\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"\"))\n\tprintln(\"--\")\n\tprintln(govdao.GetMembStore().Size())\n}\n\n// Output:\n// --\n// 1\n// --\n// - [Proposal #0](/r/gov/dao/v2:0) - (**active**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm)\n//\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// add new members to the govdao\n//\n// Status: active\n//\n// Voting stats: YES 0 (0%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 10 (100%)\n//\n// Threshold met: false\n//\n//\n// --\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// add new members to the govdao\n//\n// Status: accepted\n//\n// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%)\n//\n// Threshold met: true\n//\n//\n// --\n// - [Proposal #0](/r/gov/dao/v2:0) - (**accepted**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm)\n//\n// --\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// add new members to the govdao\n//\n// Status: execution successful\n//\n// Voting stats: YES 10 (25%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 30 (75%)\n//\n// Threshold met: false\n//\n//\n// --\n// - [Proposal #0](/r/gov/dao/v2:0) - (**execution successful**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm)\n//\n// --\n// 4\n"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}} @@ -84,7 +84,7 @@ {"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"daoweb","path":"gno.land/r/demo/daoweb","files":[{"name":"daoweb.gno","body":"package daoweb\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/json\"\n\t\"gno.land/r/gov/dao/bridge\"\n)\n\n// Proposals returns the paginated GovDAO proposals\nfunc Proposals(offset, count uint64) string {\n\tvar (\n\t\tpropStore = bridge.GovDAO().GetPropStore()\n\t\tsize = propStore.Size()\n\t)\n\n\t// Get the props\n\tprops := propStore.Proposals(offset, count)\n\n\tresp := ProposalsResponse{\n\t\tProposals: make([]Proposal, 0, count),\n\t\tTotal: uint64(size),\n\t}\n\n\tfor _, p := range props {\n\t\tprop := Proposal{\n\t\t\tAuthor: p.Author(),\n\t\t\tDescription: p.Description(),\n\t\t\tStatus: p.Status(),\n\t\t\tStats: p.Stats(),\n\t\t\tIsExpired: p.IsExpired(),\n\t\t}\n\n\t\tresp.Proposals = append(resp.Proposals, prop)\n\t}\n\n\t// Encode the response into JSON\n\tencodedProps, err := json.Marshal(encodeProposalsResponse(resp))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn string(encodedProps)\n}\n\n// ProposalByID fetches the proposal using the given ID\nfunc ProposalByID(id uint64) string {\n\tpropStore := bridge.GovDAO().GetPropStore()\n\n\tp, err := propStore.ProposalByID(id)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Encode the response into JSON\n\tprop := Proposal{\n\t\tAuthor: p.Author(),\n\t\tDescription: p.Description(),\n\t\tStatus: p.Status(),\n\t\tStats: p.Stats(),\n\t\tIsExpired: p.IsExpired(),\n\t}\n\n\tencodedProp, err := json.Marshal(encodeProposal(prop))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn string(encodedProp)\n}\n\n// encodeProposal encodes a proposal into a json node\nfunc encodeProposal(p Proposal) *json.Node {\n\treturn json.ObjectNode(\"\", map[string]*json.Node{\n\t\t\"author\": json.StringNode(\"author\", p.Author.String()),\n\t\t\"description\": json.StringNode(\"description\", p.Description),\n\t\t\"status\": json.StringNode(\"status\", p.Status.String()),\n\t\t\"stats\": json.ObjectNode(\"stats\", map[string]*json.Node{\n\t\t\t\"yay_votes\": json.NumberNode(\"yay_votes\", float64(p.Stats.YayVotes)),\n\t\t\t\"nay_votes\": json.NumberNode(\"nay_votes\", float64(p.Stats.NayVotes)),\n\t\t\t\"abstain_votes\": json.NumberNode(\"abstain_votes\", float64(p.Stats.AbstainVotes)),\n\t\t\t\"total_voting_power\": json.NumberNode(\"total_voting_power\", float64(p.Stats.TotalVotingPower)),\n\t\t}),\n\t\t\"is_expired\": json.BoolNode(\"is_expired\", p.IsExpired),\n\t})\n}\n\n// encodeProposalsResponse encodes a proposal response into a JSON node\nfunc encodeProposalsResponse(props ProposalsResponse) *json.Node {\n\tproposals := make([]*json.Node, 0, len(props.Proposals))\n\n\tfor _, p := range props.Proposals {\n\t\tproposals = append(proposals, encodeProposal(p))\n\t}\n\n\treturn json.ObjectNode(\"\", map[string]*json.Node{\n\t\t\"proposals\": json.ArrayNode(\"proposals\", proposals),\n\t\t\"total\": json.NumberNode(\"total\", float64(props.Total)),\n\t})\n}\n\n// ProposalsResponse is a paginated proposal response\ntype ProposalsResponse struct {\n\tProposals []Proposal `json:\"proposals\"`\n\tTotal uint64 `json:\"total\"`\n}\n\n// Proposal is a single GovDAO proposal\ntype Proposal struct {\n\tAuthor std.Address `json:\"author\"`\n\tDescription string `json:\"description\"`\n\tStatus dao.ProposalStatus `json:\"status\"`\n\tStats dao.Stats `json:\"stats\"`\n\tIsExpired bool `json:\"is_expired\"`\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":"deep","path":"gno.land/r/demo/deep/very/deep","files":[{"name":"render.gno","body":"package deep\n\nfunc Render(path string) string {\n\tif path == \"\" {\n\t\treturn \"it works!\"\n\t} else {\n\t\treturn \"hi \" + path\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)\n\nvar instances avl.Tree // symbol -\u003e instance\n\nfunc New(name, symbol string, decimals uint, initialMint, faucet uint64) {\n\tcaller := std.PreviousRealm().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\tbanker := grc20.NewBanker(name, symbol, decimals)\n\tif initialMint \u003e 0 {\n\t\tbanker.Mint(admin, initialMint)\n\t}\n\n\tinst := instance{\n\t\tbanker: banker,\n\t\tadmin: ownable.NewWithAddress(admin),\n\t\tfaucet: faucet,\n\t}\n\n\tinstances.Set(symbol, \u0026inst)\n}\n\ntype instance struct {\n\tbanker *grc20.Banker\n\tadmin *ownable.Ownable\n\tfaucet uint64 // per-request amount. disabled if 0.\n}\n\nfunc (inst instance) Token() grc20.Token { return inst.banker.Token() }\n\nfunc TotalSupply(symbol string) uint64 {\n\tinst := mustGetInstance(symbol)\n\treturn inst.Token().TotalSupply()\n}\n\nfunc BalanceOf(symbol string, owner std.Address) uint64 {\n\tinst := mustGetInstance(symbol)\n\treturn inst.Token().BalanceOf(owner)\n}\n\nfunc Allowance(symbol string, owner, spender std.Address) uint64 {\n\tinst := mustGetInstance(symbol)\n\treturn inst.Token().Allowance(owner, spender)\n}\n\nfunc Transfer(symbol string, to std.Address, amount uint64) {\n\tinst := mustGetInstance(symbol)\n\tcheckErr(inst.Token().Transfer(to, amount))\n}\n\nfunc Approve(symbol string, spender std.Address, amount uint64) {\n\tinst := mustGetInstance(symbol)\n\tcheckErr(inst.Token().Approve(spender, amount))\n}\n\nfunc TransferFrom(symbol string, from, to std.Address, amount uint64) {\n\tinst := mustGetInstance(symbol)\n\tcheckErr(inst.Token().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.PreviousRealm().Addr()\n\tcheckErr(inst.banker.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.banker.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.banker.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.banker.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().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)\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/uassert\"\n)\n\nfunc TestReadOnlyPublicMethods(t *testing.T) {\n\tadmin := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\tmanfred := std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\tunknown := std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\") // valid but never used.\n\tNewWithAdmin(\"Foo\", \"FOO\", 4, 10_000*1_000_000, 0, admin)\n\tNewWithAdmin(\"Bar\", \"BAR\", 4, 10_000*1_000, 0, admin)\n\tmustGetInstance(\"FOO\").banker.Mint(manfred, 100_000_000)\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_100_000_000, func() uint64 { return TotalSupply(\"FOO\") }},\n\t\t\t{\"BalanceOf(admin)\", 10_000_000_000, func() uint64 { return BalanceOf(\"FOO\", admin) }},\n\t\t\t{\"BalanceOf(manfred)\", 100_000_000, func() uint64 { return BalanceOf(\"FOO\", manfred) }},\n\t\t\t{\"Allowance(admin, manfred)\", 0, func() uint64 { return Allowance(\"FOO\", admin, manfred) }},\n\t\t\t{\"BalanceOf(unknown)\", 0, func() uint64 { return BalanceOf(\"FOO\", unknown) }},\n\t\t}\n\t\tfor _, tc := range tests {\n\t\t\tuassert.Equal(t, tc.balance, tc.fn(), \"balance does not match\")\n\t\t}\n\t}\n\treturn\n\n\t// unknown uses the faucet.\n\tstd.TestSetOriginCaller(unknown)\n\tFaucet(\"FOO\")\n\n\t// check balances #2.\n\t{\n\t\ttests := []test{\n\t\t\t{\"TotalSupply\", 10_110_000_000, func() uint64 { return TotalSupply(\"FOO\") }},\n\t\t\t{\"BalanceOf(admin)\", 10_000_000_000, func() uint64 { return BalanceOf(\"FOO\", admin) }},\n\t\t\t{\"BalanceOf(manfred)\", 100_000_000, func() uint64 { return BalanceOf(\"FOO\", manfred) }},\n\t\t\t{\"Allowance(admin, manfred)\", 0, func() uint64 { return Allowance(\"FOO\", admin, manfred) }},\n\t\t\t{\"BalanceOf(unknown)\", 10_000_000, func() uint64 { return BalanceOf(\"FOO\", unknown) }},\n\t\t}\n\t\tfor _, tc := range tests {\n\t\t\tuassert.Equal(t, tc.balance, tc.fn(), \"balance does not match\")\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":"disperse","path":"gno.land/r/demo/disperse","files":[{"name":"disperse.gno","body":"package disperse\n\nimport (\n\t\"std\"\n\n\ttokens \"gno.land/r/demo/grc20factory\"\n)\n\n// Get address of Disperse realm\nvar realmAddr = std.CurrentRealm().Addr()\n\n// DisperseUgnot parses receivers and amounts and sends out ugnot\n// The function will send out the coins to the addresses and return the leftover coins to the caller\n// if there are any to return\nfunc DisperseUgnot(addresses []std.Address, coins std.Coins) {\n\tcoinSent := std.OriginSend()\n\tcaller := std.PreviousRealm().Addr()\n\tbanker := std.GetBanker(std.BankerTypeOriginSend)\n\n\tif len(addresses) != len(coins) {\n\t\tpanic(ErrNumAddrValMismatch)\n\t}\n\n\tfor _, coin := range coins {\n\t\tif coin.Amount \u003c= 0 {\n\t\t\tpanic(ErrNegativeCoinAmount)\n\t\t}\n\n\t\tif banker.GetCoins(realmAddr).AmountOf(coin.Denom) \u003c coin.Amount {\n\t\t\tpanic(ErrMismatchBetweenSentAndParams)\n\t\t}\n\t}\n\n\t// Send coins\n\tfor i, _ := range addresses {\n\t\tbanker.SendCoins(realmAddr, addresses[i], std.NewCoins(coins[i]))\n\t}\n\n\t// Return possible leftover coins\n\tfor _, coin := range coinSent {\n\t\tleftoverAmt := banker.GetCoins(realmAddr).AmountOf(coin.Denom)\n\t\tif leftoverAmt \u003e 0 {\n\t\t\tsend := std.Coins{std.NewCoin(coin.Denom, leftoverAmt)}\n\t\t\tbanker.SendCoins(realmAddr, caller, send)\n\t\t}\n\t}\n}\n\n// DisperseGRC20 disperses tokens to multiple addresses\n// Note that it is necessary to approve the realm to spend the tokens before calling this function\n// see the corresponding filetests for examples\nfunc DisperseGRC20(addresses []std.Address, amounts []uint64, symbols []string) {\n\tcaller := std.PreviousRealm().Addr()\n\n\tif (len(addresses) != len(amounts)) || (len(amounts) != len(symbols)) {\n\t\tpanic(ErrArgLenAndSentLenMismatch)\n\t}\n\n\tfor i := 0; i \u003c len(addresses); i++ {\n\t\ttokens.TransferFrom(symbols[i], caller, addresses[i], amounts[i])\n\t}\n}\n\n// DisperseGRC20String receives a string of addresses and a string of tokens\n// and parses them to be used in DisperseGRC20\nfunc DisperseGRC20String(addresses string, tokens string) {\n\tparsedAddresses, err := parseAddresses(addresses)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tparsedAmounts, parsedSymbols, err := parseTokens(tokens)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tDisperseGRC20(parsedAddresses, parsedAmounts, parsedSymbols)\n}\n\n// DisperseUgnotString receives a string of addresses and a string of amounts\n// and parses them to be used in DisperseUgnot\nfunc DisperseUgnotString(addresses string, amounts string) {\n\tparsedAddresses, err := parseAddresses(addresses)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tparsedAmounts, err := parseAmounts(amounts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tcoins := make(std.Coins, len(parsedAmounts))\n\tfor i, amount := range parsedAmounts {\n\t\tcoins[i] = std.NewCoin(\"ugnot\", amount)\n\t}\n\n\tDisperseUgnot(parsedAddresses, coins)\n}\n"},{"name":"doc.gno","body":"// Package disperse provides methods to disperse coins or GRC20 tokens among multiple addresses.\n//\n// The disperse package is an implementation of an existing service that allows users to send coins or GRC20 tokens to multiple addresses\n// on the Ethereum blockchain.\n//\n// Usage:\n// To use disperse, you can either use `DisperseUgnot` to send coins or `DisperseGRC20` to send GRC20 tokens to multiple addresses.\n//\n// Example:\n// Dispersing 200 coins to two addresses:\n// - DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n// Dispersing 200 worth of a GRC20 token \"TEST\" to two addresses:\n// - DisperseGRC20String(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150TEST,50TEST\")\n//\n// Reference:\n// - [the original dispere app](https://disperse.app/)\n// - [the original disperse app on etherscan](https://etherscan.io/address/0xd152f549545093347a162dce210e7293f1452150#code)\n// - [the gno disperse web app](https://gno-disperse.netlify.app/)\npackage disperse // import \"gno.land/r/demo/disperse\"\n"},{"name":"errors.gno","body":"package disperse\n\nimport \"errors\"\n\nvar (\n\tErrNotEnoughCoin = errors.New(\"disperse: not enough coin sent in\")\n\tErrNumAddrValMismatch = errors.New(\"disperse: number of addresses and values to send doesn't match\")\n\tErrInvalidAddress = errors.New(\"disperse: invalid address\")\n\tErrNegativeCoinAmount = errors.New(\"disperse: coin amount cannot be negative\")\n\tErrMismatchBetweenSentAndParams = errors.New(\"disperse: mismatch between coins sent and params called\")\n\tErrArgLenAndSentLenMismatch = errors.New(\"disperse: mismatch between coins sent and args called\")\n)\n"},{"name":"util.gno","body":"package disperse\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"unicode\"\n)\n\nfunc parseAddresses(addresses string) ([]std.Address, error) {\n\tvar ret []std.Address\n\n\tfor _, str := range strings.Split(addresses, \",\") {\n\t\taddr := std.Address(str)\n\t\tif !addr.IsValid() {\n\t\t\treturn nil, ErrInvalidAddress\n\t\t}\n\n\t\tret = append(ret, addr)\n\t}\n\n\treturn ret, nil\n}\n\nfunc splitString(input string) (string, string) {\n\tvar pos int\n\tfor i, char := range input {\n\t\tif !unicode.IsDigit(char) {\n\t\t\tpos = i\n\t\t\tbreak\n\t\t}\n\t}\n\treturn input[:pos], input[pos:]\n}\n\nfunc parseTokens(tokens string) ([]uint64, []string, error) {\n\tvar amounts []uint64\n\tvar symbols []string\n\n\tfor _, token := range strings.Split(tokens, \",\") {\n\t\tamountStr, symbol := splitString(token)\n\t\tamount, _ := strconv.Atoi(amountStr)\n\t\tif amount \u003c 0 {\n\t\t\treturn nil, nil, ErrNegativeCoinAmount\n\t\t}\n\n\t\tamounts = append(amounts, uint64(amount))\n\t\tsymbols = append(symbols, symbol)\n\t}\n\n\treturn amounts, symbols, nil\n}\n\nfunc parseAmounts(amounts string) ([]int64, error) {\n\tvar ret []int64\n\n\tfor _, amt := range strings.Split(amounts, \",\") {\n\t\tamount, _ := strconv.Atoi(amt)\n\t\tif amount \u003c 0 {\n\t\t\treturn nil, ErrNegativeCoinAmount\n\t\t}\n\n\t\tret = append(ret, int64(amount))\n\t}\n\n\treturn ret, nil\n}\n"},{"name":"z_0_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 200ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\n\tstd.TestSetOrigPkgAddr(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\tbanker.SendCoins(mainaddr, disperseAddr, std.Coins{{\"ugnot\", 200}})\n\tdisperse.DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal)\n}\n\n// Output:\n// main before: 200000200ugnot\n// main after: 200000000ugnot\n"},{"name":"z_1_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\n\tstd.TestSetOrigPkgAddr(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\tbanker.SendCoins(mainaddr, disperseAddr, std.Coins{{\"ugnot\", 300}})\n\tdisperse.DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal)\n}\n\n// Output:\n// main before: 200000300ugnot\n// main after: 200000100ugnot\n"},{"name":"z_2_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\n\tstd.TestSetOrigPkgAddr(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\tbanker.SendCoins(mainaddr, disperseAddr, std.Coins{{\"ugnot\", 100}})\n\tdisperse.DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n}\n\n// Error:\n// disperse: mismatch between coins sent and params called\n"},{"name":"z_3_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n\ttokens \"gno.land/r/demo/grc20factory\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\tbeneficiary1 := std.Address(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0\")\n\tbeneficiary2 := std.Address(\"g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\")\n\n\tstd.TestSetOrigPkgAddr(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\ttokens.New(\"test\", \"TEST\", 4, 0, 0)\n\ttokens.Mint(\"TEST\", mainaddr, 200)\n\n\tmainbal := tokens.BalanceOf(\"TEST\", mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\ttokens.Approve(\"TEST\", disperseAddr, 200)\n\n\tdisperse.DisperseGRC20String(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150TEST,50TEST\")\n\n\tmainbal = tokens.BalanceOf(\"TEST\", mainaddr)\n\tprintln(\"main after:\", mainbal)\n\tben1bal := tokens.BalanceOf(\"TEST\", beneficiary1)\n\tprintln(\"beneficiary1:\", ben1bal)\n\tben2bal := tokens.BalanceOf(\"TEST\", beneficiary2)\n\tprintln(\"beneficiary2:\", ben2bal)\n}\n\n// Output:\n// main before: 200\n// main after: 0\n// beneficiary1: 150\n// beneficiary2: 50\n"},{"name":"z_4_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n\ttokens \"gno.land/r/demo/grc20factory\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\tbeneficiary1 := std.Address(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0\")\n\tbeneficiary2 := std.Address(\"g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\")\n\n\tstd.TestSetOrigPkgAddr(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\ttokens.New(\"test1\", \"TEST1\", 4, 0, 0)\n\ttokens.Mint(\"TEST1\", mainaddr, 200)\n\ttokens.New(\"test2\", \"TEST2\", 4, 0, 0)\n\ttokens.Mint(\"TEST2\", mainaddr, 200)\n\n\tmainbal := tokens.BalanceOf(\"TEST1\", mainaddr) + tokens.BalanceOf(\"TEST2\", mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\ttokens.Approve(\"TEST1\", disperseAddr, 200)\n\ttokens.Approve(\"TEST2\", disperseAddr, 200)\n\n\tdisperse.DisperseGRC20String(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"200TEST1,200TEST2\")\n\n\tmainbal = tokens.BalanceOf(\"TEST1\", mainaddr) + tokens.BalanceOf(\"TEST2\", mainaddr)\n\tprintln(\"main after:\", mainbal)\n\tben1bal := tokens.BalanceOf(\"TEST1\", beneficiary1) + tokens.BalanceOf(\"TEST2\", beneficiary1)\n\tprintln(\"beneficiary1:\", ben1bal)\n\tben2bal := tokens.BalanceOf(\"TEST1\", beneficiary2) + tokens.BalanceOf(\"TEST2\", beneficiary2)\n\tprintln(\"beneficiary2:\", ben2bal)\n}\n\n// Output:\n// main before: 400\n// main after: 0\n// beneficiary1: 200\n// beneficiary2: 200\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":"disperse","path":"gno.land/r/demo/disperse","files":[{"name":"disperse.gno","body":"package disperse\n\nimport (\n\t\"std\"\n\n\ttokens \"gno.land/r/demo/grc20factory\"\n)\n\n// Get address of Disperse realm\nvar realmAddr = std.CurrentRealm().Addr()\n\n// DisperseUgnot parses receivers and amounts and sends out ugnot\n// The function will send out the coins to the addresses and return the leftover coins to the caller\n// if there are any to return\nfunc DisperseUgnot(addresses []std.Address, coins std.Coins) {\n\tcoinSent := std.OriginSend()\n\tcaller := std.PreviousRealm().Addr()\n\tbanker := std.GetBanker(std.BankerTypeOriginSend)\n\n\tif len(addresses) != len(coins) {\n\t\tpanic(ErrNumAddrValMismatch)\n\t}\n\n\tfor _, coin := range coins {\n\t\tif coin.Amount \u003c= 0 {\n\t\t\tpanic(ErrNegativeCoinAmount)\n\t\t}\n\n\t\tif banker.GetCoins(realmAddr).AmountOf(coin.Denom) \u003c coin.Amount {\n\t\t\tpanic(ErrMismatchBetweenSentAndParams)\n\t\t}\n\t}\n\n\t// Send coins\n\tfor i, _ := range addresses {\n\t\tbanker.SendCoins(realmAddr, addresses[i], std.NewCoins(coins[i]))\n\t}\n\n\t// Return possible leftover coins\n\tfor _, coin := range coinSent {\n\t\tleftoverAmt := banker.GetCoins(realmAddr).AmountOf(coin.Denom)\n\t\tif leftoverAmt \u003e 0 {\n\t\t\tsend := std.Coins{std.NewCoin(coin.Denom, leftoverAmt)}\n\t\t\tbanker.SendCoins(realmAddr, caller, send)\n\t\t}\n\t}\n}\n\n// DisperseGRC20 disperses tokens to multiple addresses\n// Note that it is necessary to approve the realm to spend the tokens before calling this function\n// see the corresponding filetests for examples\nfunc DisperseGRC20(addresses []std.Address, amounts []uint64, symbols []string) {\n\tcaller := std.PreviousRealm().Addr()\n\n\tif (len(addresses) != len(amounts)) || (len(amounts) != len(symbols)) {\n\t\tpanic(ErrArgLenAndSentLenMismatch)\n\t}\n\n\tfor i := 0; i \u003c len(addresses); i++ {\n\t\ttokens.TransferFrom(symbols[i], caller, addresses[i], amounts[i])\n\t}\n}\n\n// DisperseGRC20String receives a string of addresses and a string of tokens\n// and parses them to be used in DisperseGRC20\nfunc DisperseGRC20String(addresses string, tokens string) {\n\tparsedAddresses, err := parseAddresses(addresses)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tparsedAmounts, parsedSymbols, err := parseTokens(tokens)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tDisperseGRC20(parsedAddresses, parsedAmounts, parsedSymbols)\n}\n\n// DisperseUgnotString receives a string of addresses and a string of amounts\n// and parses them to be used in DisperseUgnot\nfunc DisperseUgnotString(addresses string, amounts string) {\n\tparsedAddresses, err := parseAddresses(addresses)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tparsedAmounts, err := parseAmounts(amounts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tcoins := make(std.Coins, len(parsedAmounts))\n\tfor i, amount := range parsedAmounts {\n\t\tcoins[i] = std.NewCoin(\"ugnot\", amount)\n\t}\n\n\tDisperseUgnot(parsedAddresses, coins)\n}\n"},{"name":"doc.gno","body":"// Package disperse provides methods to disperse coins or GRC20 tokens among multiple addresses.\n//\n// The disperse package is an implementation of an existing service that allows users to send coins or GRC20 tokens to multiple addresses\n// on the Ethereum blockchain.\n//\n// Usage:\n// To use disperse, you can either use `DisperseUgnot` to send coins or `DisperseGRC20` to send GRC20 tokens to multiple addresses.\n//\n// Example:\n// Dispersing 200 coins to two addresses:\n// - DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n// Dispersing 200 worth of a GRC20 token \"TEST\" to two addresses:\n// - DisperseGRC20String(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150TEST,50TEST\")\n//\n// Reference:\n// - [the original dispere app](https://disperse.app/)\n// - [the original disperse app on etherscan](https://etherscan.io/address/0xd152f549545093347a162dce210e7293f1452150#code)\n// - [the gno disperse web app](https://gno-disperse.netlify.app/)\npackage disperse // import \"gno.land/r/demo/disperse\"\n"},{"name":"errors.gno","body":"package disperse\n\nimport \"errors\"\n\nvar (\n\tErrNotEnoughCoin = errors.New(\"disperse: not enough coin sent in\")\n\tErrNumAddrValMismatch = errors.New(\"disperse: number of addresses and values to send doesn't match\")\n\tErrInvalidAddress = errors.New(\"disperse: invalid address\")\n\tErrNegativeCoinAmount = errors.New(\"disperse: coin amount cannot be negative\")\n\tErrMismatchBetweenSentAndParams = errors.New(\"disperse: mismatch between coins sent and params called\")\n\tErrArgLenAndSentLenMismatch = errors.New(\"disperse: mismatch between coins sent and args called\")\n)\n"},{"name":"util.gno","body":"package disperse\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"unicode\"\n)\n\nfunc parseAddresses(addresses string) ([]std.Address, error) {\n\tvar ret []std.Address\n\n\tfor _, str := range strings.Split(addresses, \",\") {\n\t\taddr := std.Address(str)\n\t\tif !addr.IsValid() {\n\t\t\treturn nil, ErrInvalidAddress\n\t\t}\n\n\t\tret = append(ret, addr)\n\t}\n\n\treturn ret, nil\n}\n\nfunc splitString(input string) (string, string) {\n\tvar pos int\n\tfor i, char := range input {\n\t\tif !unicode.IsDigit(char) {\n\t\t\tpos = i\n\t\t\tbreak\n\t\t}\n\t}\n\treturn input[:pos], input[pos:]\n}\n\nfunc parseTokens(tokens string) ([]uint64, []string, error) {\n\tvar amounts []uint64\n\tvar symbols []string\n\n\tfor _, token := range strings.Split(tokens, \",\") {\n\t\tamountStr, symbol := splitString(token)\n\t\tamount, _ := strconv.Atoi(amountStr)\n\t\tif amount \u003c 0 {\n\t\t\treturn nil, nil, ErrNegativeCoinAmount\n\t\t}\n\n\t\tamounts = append(amounts, uint64(amount))\n\t\tsymbols = append(symbols, symbol)\n\t}\n\n\treturn amounts, symbols, nil\n}\n\nfunc parseAmounts(amounts string) ([]int64, error) {\n\tvar ret []int64\n\n\tfor _, amt := range strings.Split(amounts, \",\") {\n\t\tamount, _ := strconv.Atoi(amt)\n\t\tif amount \u003c 0 {\n\t\t\treturn nil, ErrNegativeCoinAmount\n\t\t}\n\n\t\tret = append(ret, int64(amount))\n\t}\n\n\treturn ret, nil\n}\n"},{"name":"z_0_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 200ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\n\tstd.TestSetOriginPkgAddr(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\tbanker.SendCoins(mainaddr, disperseAddr, std.Coins{{\"ugnot\", 200}})\n\tdisperse.DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal)\n}\n\n// Output:\n// main before: 200000200ugnot\n// main after: 200000000ugnot\n"},{"name":"z_1_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\n\tstd.TestSetOriginPkgAddr(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\tbanker.SendCoins(mainaddr, disperseAddr, std.Coins{{\"ugnot\", 300}})\n\tdisperse.DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal)\n}\n\n// Output:\n// main before: 200000300ugnot\n// main after: 200000100ugnot\n"},{"name":"z_2_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\n\tstd.TestSetOriginPkgAddr(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\tbanker.SendCoins(mainaddr, disperseAddr, std.Coins{{\"ugnot\", 100}})\n\tdisperse.DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n}\n\n// Error:\n// disperse: mismatch between coins sent and params called\n"},{"name":"z_3_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n\ttokens \"gno.land/r/demo/grc20factory\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\tbeneficiary1 := std.Address(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0\")\n\tbeneficiary2 := std.Address(\"g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\")\n\n\tstd.TestSetOriginPkgAddr(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\ttokens.New(\"test\", \"TEST\", 4, 0, 0)\n\ttokens.Mint(\"TEST\", mainaddr, 200)\n\n\tmainbal := tokens.BalanceOf(\"TEST\", mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\ttokens.Approve(\"TEST\", disperseAddr, 200)\n\n\tdisperse.DisperseGRC20String(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150TEST,50TEST\")\n\n\tmainbal = tokens.BalanceOf(\"TEST\", mainaddr)\n\tprintln(\"main after:\", mainbal)\n\tben1bal := tokens.BalanceOf(\"TEST\", beneficiary1)\n\tprintln(\"beneficiary1:\", ben1bal)\n\tben2bal := tokens.BalanceOf(\"TEST\", beneficiary2)\n\tprintln(\"beneficiary2:\", ben2bal)\n}\n\n// Output:\n// main before: 200\n// main after: 0\n// beneficiary1: 150\n// beneficiary2: 50\n"},{"name":"z_4_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n\ttokens \"gno.land/r/demo/grc20factory\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\tbeneficiary1 := std.Address(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0\")\n\tbeneficiary2 := std.Address(\"g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\")\n\n\tstd.TestSetOriginPkgAddr(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\ttokens.New(\"test1\", \"TEST1\", 4, 0, 0)\n\ttokens.Mint(\"TEST1\", mainaddr, 200)\n\ttokens.New(\"test2\", \"TEST2\", 4, 0, 0)\n\ttokens.Mint(\"TEST2\", mainaddr, 200)\n\n\tmainbal := tokens.BalanceOf(\"TEST1\", mainaddr) + tokens.BalanceOf(\"TEST2\", mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\ttokens.Approve(\"TEST1\", disperseAddr, 200)\n\ttokens.Approve(\"TEST2\", disperseAddr, 200)\n\n\tdisperse.DisperseGRC20String(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"200TEST1,200TEST2\")\n\n\tmainbal = tokens.BalanceOf(\"TEST1\", mainaddr) + tokens.BalanceOf(\"TEST2\", mainaddr)\n\tprintln(\"main after:\", mainbal)\n\tben1bal := tokens.BalanceOf(\"TEST1\", beneficiary1) + tokens.BalanceOf(\"TEST2\", beneficiary1)\n\tprintln(\"beneficiary1:\", ben1bal)\n\tben2bal := tokens.BalanceOf(\"TEST1\", beneficiary2) + tokens.BalanceOf(\"TEST2\", beneficiary2)\n\tprintln(\"beneficiary2:\", ben2bal)\n}\n\n// Output:\n// main before: 400\n// main after: 0\n// beneficiary1: 200\n// beneficiary2: 200\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":"echo","path":"gno.land/r/demo/echo","files":[{"name":"echo.gno","body":"package echo\n\n/*\n * This realm echoes the `path` argument it received.\n * Can be used by developers as a simple endpoint to test\n * forbidden characters, for pentesting or simply to\n * test it works.\n *\n * See also r/demo/print (to print various thing like user address)\n */\nfunc Render(path string) string {\n\treturn path\n}\n"},{"name":"echo_test.gno","body":"package echo\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc Test(t *testing.T) {\n\turequire.Equal(t, \"aa\", Render(\"aa\"))\n\turequire.Equal(t, \"\", Render(\"\"))\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":"event","path":"gno.land/r/demo/event","files":[{"name":"event.gno","body":"package event\n\nimport (\n\t\"std\"\n)\n\nfunc Emit(value string) {\n\tstd.Emit(\"TAG\", \"key\", value)\n}\n"},{"name":"z1_filetest.gno","body":"package main\n\nimport \"gno.land/r/demo/event\"\n\nfunc main() {\n\tevent.Emit(\"foo\")\n\tevent.Emit(\"bar\")\n}\n\n// Events:\n// [\n// {\n// \"type\": \"TAG\",\n// \"attrs\": [\n// {\n// \"key\": \"key\",\n// \"value\": \"foo\"\n// }\n// ],\n// \"pkg_path\": \"gno.land/r/demo/event\",\n// \"func\": \"Emit\"\n// },\n// {\n// \"type\": \"TAG\",\n// \"attrs\": [\n// {\n// \"key\": \"key\",\n// \"value\": \"bar\"\n// }\n// ],\n// \"pkg_path\": \"gno.land/r/demo/event\",\n// \"func\": \"Emit\"\n// }\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":"foo1155","path":"gno.land/r/demo/foo1155","files":[{"name":"foo1155.gno","body":"package foo1155\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/grc/grc1155\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/r/demo/users\"\n\n\tpusers \"gno.land/p/demo/users\"\n)\n\nvar (\n\tdummyURI = \"ipfs://xyz\"\n\tadmin std.Address = \"g10x5phu0k6p64cwrhfpsc8tk43st9kug6wft530\"\n\tfoo = grc1155.NewBasicGRC1155Token(dummyURI)\n)\n\nfunc init() {\n\tmintGRC1155Token(admin) // @administrator (10)\n}\n\nfunc mintGRC1155Token(owner std.Address) {\n\tfor i := 1; i \u003c= 10; i++ {\n\t\ttid := grc1155.TokenID(ufmt.Sprintf(\"%d\", i))\n\t\tfoo.SafeMint(owner, tid, 100)\n\t}\n}\n\n// Getters\n\nfunc BalanceOf(user pusers.AddressOrName, tid grc1155.TokenID) uint64 {\n\tbalance, err := foo.BalanceOf(users.Resolve(user), tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn balance\n}\n\nfunc BalanceOfBatch(ul []pusers.AddressOrName, batch []grc1155.TokenID) []uint64 {\n\tvar usersResolved []std.Address\n\n\tfor i := 0; i \u003c len(ul); i++ {\n\t\tusersResolved[i] = users.Resolve(ul[i])\n\t}\n\tbalanceBatch, err := foo.BalanceOfBatch(usersResolved, batch)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn balanceBatch\n}\n\nfunc IsApprovedForAll(owner, user pusers.AddressOrName) bool {\n\treturn foo.IsApprovedForAll(users.Resolve(owner), users.Resolve(user))\n}\n\n// Setters\n\nfunc SetApprovalForAll(user pusers.AddressOrName, approved bool) {\n\terr := foo.SetApprovalForAll(users.Resolve(user), approved)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc TransferFrom(from, to pusers.AddressOrName, tid grc1155.TokenID, amount uint64) {\n\terr := foo.SafeTransferFrom(users.Resolve(from), users.Resolve(to), tid, amount)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc BatchTransferFrom(from, to pusers.AddressOrName, batch []grc1155.TokenID, amounts []uint64) {\n\terr := foo.SafeBatchTransferFrom(users.Resolve(from), users.Resolve(to), batch, amounts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// Admin\n\nfunc Mint(to pusers.AddressOrName, tid grc1155.TokenID, amount uint64) {\n\tcaller := std.OriginCaller()\n\tassertIsAdmin(caller)\n\terr := foo.SafeMint(users.Resolve(to), tid, amount)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc MintBatch(to pusers.AddressOrName, batch []grc1155.TokenID, amounts []uint64) {\n\tcaller := std.OriginCaller()\n\tassertIsAdmin(caller)\n\terr := foo.SafeBatchMint(users.Resolve(to), batch, amounts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc Burn(from pusers.AddressOrName, tid grc1155.TokenID, amount uint64) {\n\tcaller := std.OriginCaller()\n\tassertIsAdmin(caller)\n\terr := foo.Burn(users.Resolve(from), tid, amount)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc BurnBatch(from pusers.AddressOrName, batch []grc1155.TokenID, amounts []uint64) {\n\tcaller := std.OriginCaller()\n\tassertIsAdmin(caller)\n\terr := foo.BatchBurn(users.Resolve(from), batch, amounts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// Render\n\nfunc Render(path string) string {\n\tswitch {\n\tcase path == \"\":\n\t\treturn foo.RenderHome()\n\tdefault:\n\t\treturn \"404\\n\"\n\t}\n}\n\n// Util\n\nfunc assertIsAdmin(address std.Address) {\n\tif address != admin {\n\t\tpanic(\"restricted access\")\n\t}\n}\n"},{"name":"foo1155_test.gno","body":"package foo1155\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/grc/grc1155\"\n\t\"gno.land/p/demo/users\"\n)\n\nfunc TestFoo721(t *testing.T) {\n\tadmin := users.AddressOrName(\"g10x5phu0k6p64cwrhfpsc8tk43st9kug6wft530\")\n\tbob := users.AddressOrName(\"g1ze6et22ces5atv79y4xh38s4kuraey4y2fr6tw\")\n\ttid1 := grc1155.TokenID(\"1\")\n\ttid2 := grc1155.TokenID(\"2\")\n\n\tfor i, tc := range []struct {\n\t\tname string\n\t\texpected interface{}\n\t\tfn func() interface{}\n\t}{\n\t\t{\"BalanceOf(admin, tid1)\", uint64(100), func() interface{} { return BalanceOf(admin, tid1) }},\n\t\t{\"BalanceOf(bob, tid1)\", uint64(0), func() interface{} { return BalanceOf(bob, tid1) }},\n\t\t{\"IsApprovedForAll(admin, bob)\", false, func() interface{} { return IsApprovedForAll(admin, bob) }},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := tc.fn()\n\t\t\tif tc.expected != got {\n\t\t\t\tt.Errorf(\"expected: %v got: %v\", tc.expected, got)\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":""}} @@ -108,7 +108,7 @@ {"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"types","path":"gno.land/r/demo/types","files":[{"name":"types.gno","body":"// package to test types behavior in various conditions (TXs, imports).\npackage types\n\nimport (\n\t\"errors\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\nvar (\n\tgInt int = -42\n\tgUint uint = 42\n\tgString string = \"a string\"\n\tgStringSlice []string = []string{\"a\", \"string\", \"slice\"}\n\tgError error = errors.New(\"an error\")\n\tgIntSlice []int = []int{-42, 0, 42}\n\tgUintSlice []uint = []uint{0, 42, 84}\n\tgTree avl.Tree\n\t// gInterface = interface{}{-42, \"a string\", uint(42)}\n)\n\nfunc init() {\n\tgTree.Set(\"a\", \"content of A\")\n\tgTree.Set(\"b\", \"content of B\")\n}\n\nfunc Noop() {}\nfunc RetTimeNow() time.Time { return time.Now() }\nfunc RetString() string { return gString }\nfunc RetStringPointer() *string { return \u0026gString }\nfunc RetUint() uint { return gUint }\nfunc RetInt() int { return gInt }\nfunc RetUintPointer() *uint { return \u0026gUint }\nfunc RetIntPointer() *int { return \u0026gInt }\nfunc RetTree() avl.Tree { return gTree }\nfunc RetIntSlice() []int { return gIntSlice }\nfunc RetUintSlice() []uint { return gUintSlice }\nfunc RetStringSlice() []string { return gStringSlice }\nfunc RetError() error { return gError }\nfunc Panic() { panic(\"PANIC!\") }\n\n// TODO: floats\n// TODO: typed errors\n// TODO: ret interface\n// TODO: recover\n// TODO: take types as input\n\nfunc Render(path string) string {\n\treturn \"package to test data types.\"\n}\n"},{"name":"types_test.gno","body":"package types\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":"ui","path":"gno.land/r/demo/ui","files":[{"name":"ui.gno","body":"package ui\n\nimport \"gno.land/p/demo/ui\"\n\nfunc Render(path string) string {\n\t// TODO: build this realm as a demo one with one page per feature.\n\n\t// TODO: pagination\n\t// TODO: non-standard markdown\n\t// TODO: error, warn\n\t// TODO: header\n\t// TODO: HTML\n\t// TODO: toc\n\t// TODO: forms\n\t// TODO: comments\n\n\tdom := ui.DOM{\n\t\tPrefix: \"r/demo/ui:\",\n\t}\n\n\tdom.Title = \"UI Demo\"\n\n\tdom.Header.Append(ui.Breadcrumb{\n\t\tui.Link{Text: \"foo\", Path: \"foo\"},\n\t\tui.Link{Text: \"bar\", Path: \"foo/bar\"},\n\t})\n\n\tdom.Body.Append(\n\t\tui.Paragraph(\"Simple UI demonstration.\"),\n\t\tui.BulletList{\n\t\t\tui.Text(\"a text\"),\n\t\t\tui.Link{Text: \"a relative link\", Path: \"foobar\"},\n\t\t\tui.Text(\"another text\"),\n\t\t\t// ui.H1(\"a H1 text\"),\n\t\t\tui.Bold(\"a bold text\"),\n\t\t\tui.Italic(\"italic text\"),\n\t\t\tui.Text(\"raw markdown with **bold** text in the middle.\"),\n\t\t\tui.Code(\"some inline code\"),\n\t\t\tui.Link{Text: \"a remote link\", URL: \"https://gno.land\"},\n\t\t},\n\t)\n\n\tdom.Footer.Append(ui.Text(\"I'm the footer.\"))\n\tdom.Body.Append(ui.Text(\"another string.\"))\n\tdom.Body.Append(ui.Paragraph(\"a paragraph.\"), ui.HR{})\n\n\treturn dom.String()\n}\n"},{"name":"ui_test.gno","body":"package ui\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestRender(t *testing.T) {\n\tgot := Render(\"\")\n\texpected := \"# UI Demo\\n\\n[foo](r/demo/ui:foo) / [bar](r/demo/ui:foo/bar)\\n\\n\\nSimple UI demonstration.\\n\\n- a text\\n- [a relative link](r/demo/ui:foobar)\\n- another text\\n- **a bold text**\\n- _italic text_\\n- raw markdown with **bold** text in the middle.\\n- `some inline code`\\n- [a remote link](https://gno.land)\\n\\nanother string.\\n\\na paragraph.\\n\\n\\n---\\n\\n\\nI'm the footer.\\n\\n\"\n\tuassert.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":"userbook","path":"gno.land/r/demo/userbook","files":[{"name":"userbook.gno","body":"// This realm demonstrates a small userbook system working with gnoweb\npackage userbook\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/mux\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype Signup struct {\n\taccount string\n\theight int64\n}\n\n// signups - keep a slice of signed up addresses efficient pagination\nvar signups []Signup\n\n// tracker - keep track of who signed up\nvar (\n\ttracker *avl.Tree\n\trouter *mux.Router\n)\n\nconst (\n\tdefaultPageSize = 20\n\tpathArgument = \"number\"\n\tsubPath = \"page/{\" + pathArgument + \"}\"\n\tsignUpEvent = \"SignUp\"\n)\n\nfunc init() {\n\t// Set up tracker tree\n\ttracker = avl.NewTree()\n\n\t// Set up route handling\n\trouter = mux.NewRouter()\n\trouter.HandleFunc(\"\", renderHelper)\n\trouter.HandleFunc(subPath, renderHelper)\n\n\t// Sign up the deployer\n\tSignUp()\n}\n\nfunc SignUp() string {\n\t// Get transaction caller\n\tcaller := std.PreviousRealm().Addr().String()\n\theight := std.GetHeight()\n\n\t// Check if the user is already signed up\n\tif _, exists := tracker.Get(caller); exists {\n\t\tpanic(caller + \" is already signed up!\")\n\t}\n\n\t// Sign up the user\n\ttracker.Set(caller, struct{}{})\n\tsignup := Signup{\n\t\tcaller,\n\t\theight,\n\t}\n\n\tsignups = append(signups, signup)\n\tstd.Emit(signUpEvent, \"SignedUpAccount\", signup.account)\n\n\treturn ufmt.Sprintf(\"%s added to userbook up at block #%d!\", signup.account, signup.height)\n}\n\nfunc GetSignupsInRange(page, pageSize int) ([]Signup, int) {\n\tif page \u003c 1 {\n\t\tpanic(\"page number cannot be less than 1\")\n\t}\n\n\tif pageSize \u003c 1 || pageSize \u003e 50 {\n\t\tpanic(\"page size must be from 1 to 50\")\n\t}\n\n\t// Pagination\n\t// Calculate indexes\n\tstartIndex := (page - 1) * pageSize\n\tendIndex := startIndex + pageSize\n\n\t// If page does not contain any users\n\tif startIndex \u003e= len(signups) {\n\t\treturn nil, -1\n\t}\n\n\t// If page contains fewer users than the page size\n\tif endIndex \u003e len(signups) {\n\t\tendIndex = len(signups)\n\t}\n\n\treturn signups[startIndex:endIndex], endIndex\n}\n\nfunc renderHelper(res *mux.ResponseWriter, req *mux.Request) {\n\ttotalSignups := len(signups)\n\tres.Write(\"# Welcome to UserBook!\\n\\n\")\n\n\t// Get URL parameter\n\tpage, err := strconv.Atoi(req.GetVar(\"number\"))\n\tif err != nil {\n\t\tpage = 1 // render first page on bad input\n\t}\n\n\t// Fetch paginated signups\n\tfetchedSignups, endIndex := GetSignupsInRange(page, defaultPageSize)\n\t// Handle empty page case\n\tif len(fetchedSignups) == 0 {\n\t\tres.Write(\"No users on this page!\\n\\n\")\n\t\tres.Write(\"---\\n\\n\")\n\t\tres.Write(\"[Back to Page #1](/r/demo/userbook:page/1)\\n\\n\")\n\t\treturn\n\t}\n\n\t// Write page title\n\tres.Write(ufmt.Sprintf(\"## UserBook - Page #%d:\\n\\n\", page))\n\n\t// Write signups\n\tpageStartIndex := defaultPageSize * (page - 1)\n\tfor i, signup := range fetchedSignups {\n\t\tout := ufmt.Sprintf(\"#### User #%d - %s - signed up at Block #%d\\n\", pageStartIndex+i, signup.account, signup.height)\n\t\tres.Write(out)\n\t}\n\n\tres.Write(\"---\\n\\n\")\n\n\t// Write UserBook info\n\tlatestSignupIndex := totalSignups - 1\n\tres.Write(ufmt.Sprintf(\"#### Total users: %d\\n\", totalSignups))\n\tres.Write(ufmt.Sprintf(\"#### Latest signup: User #%d at Block #%d\\n\", latestSignupIndex, signups[latestSignupIndex].height))\n\n\tres.Write(\"---\\n\\n\")\n\n\t// Write page number\n\tres.Write(ufmt.Sprintf(\"You're viewing page #%d\", page))\n\n\t// Write navigation buttons\n\tvar prevPage string\n\tvar nextPage string\n\t// If we are on any page that is not the first page\n\tif page \u003e 1 {\n\t\tprevPage = ufmt.Sprintf(\" - [Previous page](/r/demo/userbook:page/%d)\", page-1)\n\t}\n\n\t// If there are more pages after the current one\n\tif endIndex \u003c totalSignups {\n\t\tnextPage = ufmt.Sprintf(\" - [Next page](/r/demo/userbook:page/%d)\\n\\n\", page+1)\n\t}\n\n\tres.Write(prevPage)\n\tres.Write(nextPage)\n}\n\nfunc Render(path string) string {\n\treturn router.Render(path)\n}\n"},{"name":"userbook_test.gno","body":"package userbook\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nfunc TestRender(t *testing.T) {\n\t// Sign up 20 users + deployer\n\tfor i := 0; i \u003c 20; i++ {\n\t\taddrName := ufmt.Sprintf(\"test%d\", i)\n\t\tcaller := testutils.TestAddress(addrName)\n\t\tstd.TestSetOriginCaller(caller)\n\t\tSignUp()\n\t}\n\n\ttestCases := []struct {\n\t\tname string\n\t\tnextPage bool\n\t\tprevPage bool\n\t\tpath string\n\t\texpectedNumberOfUsers int\n\t}{\n\t\t{\n\t\t\tname: \"1st page render\",\n\t\t\tnextPage: true,\n\t\t\tprevPage: false,\n\t\t\tpath: \"page/1\",\n\t\t\texpectedNumberOfUsers: 20,\n\t\t},\n\t\t{\n\t\t\tname: \"2nd page render\",\n\t\t\tnextPage: false,\n\t\t\tprevPage: true,\n\t\t\tpath: \"page/2\",\n\t\t\texpectedNumberOfUsers: 1,\n\t\t},\n\t\t{\n\t\t\tname: \"Invalid path render\",\n\t\t\tnextPage: true,\n\t\t\tprevPage: false,\n\t\t\tpath: \"page/invalidtext\",\n\t\t\texpectedNumberOfUsers: 20,\n\t\t},\n\t\t{\n\t\t\tname: \"Empty Page\",\n\t\t\tnextPage: false,\n\t\t\tprevPage: false,\n\t\t\tpath: \"page/1000\",\n\t\t\texpectedNumberOfUsers: 0,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := Render(tc.path)\n\t\t\tnumUsers := countUsers(got)\n\n\t\t\tif tc.prevPage \u0026\u0026 !strings.Contains(got, \"Previous page\") {\n\t\t\t\tt.Fatalf(\"expected to find Previous page, didn't find it\")\n\t\t\t}\n\t\t\tif tc.nextPage \u0026\u0026 !strings.Contains(got, \"Next page\") {\n\t\t\t\tt.Fatalf(\"expected to find Next page, didn't find it\")\n\t\t\t}\n\n\t\t\tif tc.expectedNumberOfUsers != numUsers {\n\t\t\t\tt.Fatalf(\"expected %d, got %d users\", tc.expectedNumberOfUsers, numUsers)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc countUsers(input string) int {\n\treturn strings.Count(input, \"#### User #\")\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":"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/users\"\n)\n\nvar (\n\tbanker *grc20.Banker = grc20.NewBanker(\"wrapped GNOT\", \"wugnot\", 0)\n\tToken = banker.Token()\n)\n\nconst (\n\tugnotMinDeposit uint64 = 1000\n\twugnotMinDeposit uint64 = 1\n)\n\nfunc Deposit() {\n\tcaller := std.PreviousRealm().Addr()\n\tsent := std.OriginSend()\n\tamount := sent.AmountOf(\"ugnot\")\n\n\trequire(uint64(amount) \u003e= ugnotMinDeposit, ufmt.Sprintf(\"Deposit below minimum: %d/%d ugnot.\", amount, ugnotMinDeposit))\n\tcheckErr(banker.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.PreviousRealm().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(banker.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 banker.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\tcheckErr(Token.Transfer(toAddr, amount))\n}\n\nfunc Approve(spender pusers.AddressOrName, amount uint64) {\n\tspenderAddr := users.Resolve(spender)\n\tcheckErr(Token.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(Token.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.TestSetOriginCaller(addr1)\n\tstd.TestSetOriginSend(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.TestSetOriginCaller(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=200000000 |\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=200000000 |\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=200004242 |\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":""}} +{"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/users\"\n)\n\nvar (\n\tbanker *grc20.Banker = grc20.NewBanker(\"wrapped GNOT\", \"wugnot\", 0)\n\tToken = banker.Token()\n)\n\nconst (\n\tugnotMinDeposit uint64 = 1000\n\twugnotMinDeposit uint64 = 1\n)\n\nfunc Deposit() {\n\tcaller := std.PreviousRealm().Addr()\n\tsent := std.OriginSend()\n\tamount := sent.AmountOf(\"ugnot\")\n\n\trequire(uint64(amount) \u003e= ugnotMinDeposit, ufmt.Sprintf(\"Deposit below minimum: %d/%d ugnot.\", amount, ugnotMinDeposit))\n\tcheckErr(banker.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.PreviousRealm().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(banker.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 banker.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\tcheckErr(Token.Transfer(toAddr, amount))\n}\n\nfunc Approve(spender pusers.AddressOrName, amount uint64) {\n\tspenderAddr := users.Resolve(spender)\n\tcheckErr(Token.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(Token.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.TestSetOriginPkgAddr(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.TestSetOriginCaller(addr1)\n\tstd.TestSetOriginSend(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.TestSetOriginCaller(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=200000000 |\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=200000000 |\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=200004242 |\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":""}} {"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"gnoblog","path":"gno.land/r/gnoland/blog","files":[{"name":"admin.gno","body":"package gnoblog\n\nimport (\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/r/gov/dao/bridge\"\n)\n\nvar (\n\tadminAddr std.Address\n\tmoderatorList avl.Tree\n\tcommenterList avl.Tree\n\tinPause bool\n)\n\nfunc init() {\n\t// adminAddr = std.OriginCaller() // FIXME: find a way to use this from the main's genesis.\n\tadminAddr = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"\n}\n\nfunc AdminSetAdminAddr(addr std.Address) {\n\tassertIsAdmin()\n\tadminAddr = addr\n}\n\nfunc AdminSetInPause(state bool) {\n\tassertIsAdmin()\n\tinPause = state\n}\n\nfunc AdminAddModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), true)\n}\n\nfunc AdminRemoveModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), false) // FIXME: delete instead?\n}\n\nfunc NewPostExecutor(slug, title, body, publicationDate, authors, tags string) dao.Executor {\n\tcallback := func() error {\n\t\taddPost(std.PreviousRealm().Addr(), slug, title, body, publicationDate, authors, tags)\n\n\t\treturn nil\n\t}\n\n\treturn bridge.GovDAO().NewGovDAOExecutor(callback)\n}\n\nfunc ModAddPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\tcaller := std.OriginCaller()\n\taddPost(caller, slug, title, body, publicationDate, authors, tags)\n}\n\nfunc addPost(caller std.Address, slug, title, body, publicationDate, authors, tags string) {\n\tvar tagList []string\n\tif tags != \"\" {\n\t\ttagList = strings.Split(tags, \",\")\n\t}\n\tvar authorList []string\n\tif authors != \"\" {\n\t\tauthorList = strings.Split(authors, \",\")\n\t}\n\n\terr := b.NewPost(caller, slug, title, body, publicationDate, authorList, tagList)\n\n\tcheckErr(err)\n}\n\nfunc ModEditPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.GetPost(slug).Update(title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc ModRemovePost(slug string) {\n\tassertIsModerator()\n\n\tb.RemovePost(slug)\n}\n\nfunc ModAddCommenter(addr std.Address) {\n\tassertIsModerator()\n\tcommenterList.Set(addr.String(), true)\n}\n\nfunc ModDelCommenter(addr std.Address) {\n\tassertIsModerator()\n\tcommenterList.Set(addr.String(), false) // FIXME: delete instead?\n}\n\nfunc ModDelComment(slug string, index int) {\n\tassertIsModerator()\n\n\terr := b.GetPost(slug).DeleteComment(index)\n\tcheckErr(err)\n}\n\nfunc isAdmin(addr std.Address) bool {\n\treturn addr == adminAddr\n}\n\nfunc isModerator(addr std.Address) bool {\n\t_, found := moderatorList.Get(addr.String())\n\treturn found\n}\n\nfunc isCommenter(addr std.Address) bool {\n\t_, found := commenterList.Get(addr.String())\n\treturn found\n}\n\nfunc assertIsAdmin() {\n\tcaller := std.OriginCaller()\n\tif !isAdmin(caller) {\n\t\tpanic(\"access restricted.\")\n\t}\n}\n\nfunc assertIsModerator() {\n\tcaller := std.OriginCaller()\n\tif isAdmin(caller) || isModerator(caller) {\n\t\treturn\n\t}\n\tpanic(\"access restricted\")\n}\n\nfunc assertIsCommenter() {\n\tcaller := std.OriginCaller()\n\tif isAdmin(caller) || isModerator(caller) || isCommenter(caller) {\n\t\treturn\n\t}\n\tpanic(\"access restricted\")\n}\n\nfunc assertNotInPause() {\n\tif inPause {\n\t\tpanic(\"access restricted (pause)\")\n\t}\n}\n"},{"name":"gnoblog.gno","body":"package gnoblog\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/blog\"\n)\n\nvar b = \u0026blog.Blog{\n\tTitle: \"Gnoland's Blog\",\n\tPrefix: \"/r/gnoland/blog:\",\n}\n\nfunc AddComment(postSlug, comment string) {\n\tassertIsCommenter()\n\tassertNotInPause()\n\n\tcaller := std.OriginCaller()\n\terr := b.GetPost(postSlug).AddComment(caller, comment)\n\tcheckErr(err)\n}\n\nfunc Render(path string) string {\n\treturn b.Render(path)\n}\n\nfunc RenderLastPostsWidget(limit int) string {\n\treturn b.RenderLastPostsWidget(limit)\n}\n\nfunc PostExists(slug string) bool {\n\tif b.GetPost(slug) == nil {\n\t\treturn false\n\t}\n\treturn true\n}\n"},{"name":"gnoblog_test.gno","body":"package gnoblog\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestPackage(t *testing.T) {\n\tstd.TestSetOriginCaller(std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"))\n\n\tauthor := std.OriginCaller()\n\n\t// by default, no posts.\n\t{\n\t\tgot := Render(\"\")\n\t\texpected := `\n# Gnoland's Blog\n\nNo posts.\n`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// create two posts, list post.\n\t{\n\t\tModAddPost(\"slug1\", \"title1\", \"body1\", \"2022-05-20T13:17:22Z\", \"moul\", \"tag1,tag2\")\n\t\tModAddPost(\"slug2\", \"title2\", \"body2\", \"2022-05-20T13:17:23Z\", \"moul\", \"tag1,tag3\")\n\t\tgot := Render(\"\")\n\t\texpected := `\n\t# Gnoland's Blog\n\n\u003cdiv class='columns-3'\u003e\u003cdiv\u003e\n\n### [title2](/r/gnoland/blog:p/slug2)\n 20 May 2022\n\u003c/div\u003e\u003cdiv\u003e\n\n### [title1](/r/gnoland/blog:p/slug1)\n 20 May 2022\n\u003c/div\u003e\u003c/div\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// view post.\n\t{\n\t\tgot := Render(\"p/slug2\")\n\t\texpected := `\n\u003cmain class='gno-tmpl-page'\u003e\n\n# title2\n\nbody2\n\n---\n\nTags: [#tag1](/r/gnoland/blog:t/tag1) [#tag3](/r/gnoland/blog:t/tag3)\n\nWritten by moul on 20 May 2022\n\nPublished by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq to Gnoland's Blog\n\n---\n\u003cdetails\u003e\u003csummary\u003eComment section\u003c/summary\u003e\n\n\u003c/details\u003e\n\u003c/main\u003e\n\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// list by tags.\n\t{\n\t\tgot := Render(\"t/invalid\")\n\t\texpected := \"# [Gnoland's Blog](/r/gnoland/blog:) / t / invalid\\n\\nNo posts.\"\n\t\tassertMDEquals(t, got, expected)\n\n\t\tgot = Render(\"t/tag2\")\n\t\texpected = `\n# [Gnoland's Blog](/r/gnoland/blog:) / t / tag2\n\n\u003cdiv\u003e\n\n### [title1](/r/gnoland/blog:p/slug1)\n 20 May 2022\n\u003c/div\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// add comments.\n\t{\n\t\tAddComment(\"slug1\", \"comment1\")\n\t\tAddComment(\"slug2\", \"comment2\")\n\t\tAddComment(\"slug1\", \"comment3\")\n\t\tAddComment(\"slug2\", \"comment4\")\n\t\tAddComment(\"slug1\", \"comment5\")\n\t\tgot := Render(\"p/slug2\")\n\t\texpected := `\u003cmain class='gno-tmpl-page'\u003e\n\n# title2\n\nbody2\n\n---\n\nTags: [#tag1](/r/gnoland/blog:t/tag1) [#tag3](/r/gnoland/blog:t/tag3)\n\nWritten by moul on 20 May 2022\n\nPublished by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq to Gnoland's Blog\n\n---\n\u003cdetails\u003e\u003csummary\u003eComment section\u003c/summary\u003e\n\n\u003ch5\u003ecomment4\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003ch5\u003ecomment2\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003c/details\u003e\n\u003c/main\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// edit post.\n\t{\n\t\toldTitle := \"title2\"\n\t\toldDate := \"2022-05-20T13:17:23Z\"\n\n\t\tModEditPost(\"slug2\", oldTitle, \"body2++\", oldDate, \"manfred\", \"tag1,tag4\")\n\t\tgot := Render(\"p/slug2\")\n\t\texpected := `\u003cmain class='gno-tmpl-page'\u003e\n\n# title2\n\nbody2++\n\n---\n\nTags: [#tag1](/r/gnoland/blog:t/tag1) [#tag4](/r/gnoland/blog:t/tag4)\n\nWritten by manfred on 20 May 2022\n\nPublished by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq to Gnoland's Blog\n\n---\n\u003cdetails\u003e\u003csummary\u003eComment section\u003c/summary\u003e\n\n\u003ch5\u003ecomment4\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003ch5\u003ecomment2\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003c/details\u003e\n\u003c/main\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\n\t\thome := Render(\"\")\n\n\t\tif strings.Count(home, oldTitle) != 1 {\n\t\t\tt.Errorf(\"post not edited properly\")\n\t\t}\n\t\t// Edits work everything except title, slug, and publicationDate\n\t\t// Edits to the above will cause duplication on the blog home page\n\t}\n\n\t{ // Test remove functionality\n\t\ttitle := \"example title\"\n\t\tslug := \"testSlug1\"\n\t\tModAddPost(slug, title, \"body1\", \"2022-05-25T13:17:22Z\", \"moul\", \"tag1,tag2\")\n\n\t\tgot := Render(\"\")\n\n\t\tif !strings.Contains(got, title) {\n\t\t\tt.Errorf(\"post was not added properly\")\n\t\t}\n\n\t\tpostRender := Render(\"p/\" + slug)\n\n\t\tif !strings.Contains(postRender, title) {\n\t\t\tt.Errorf(\"post not rendered properly\")\n\t\t}\n\n\t\tModRemovePost(slug)\n\t\tgot = Render(\"\")\n\n\t\tif strings.Contains(got, title) {\n\t\t\tt.Errorf(\"post was not removed\")\n\t\t}\n\n\t\tpostRender = Render(\"p/\" + slug)\n\n\t\tassertMDEquals(t, postRender, \"404\")\n\t}\n\n\t// TODO: pagination.\n\t// TODO: ?format=...\n\n\t// all 404s\n\t{\n\t\tnotFoundPaths := []string{\n\t\t\t\"p/slug3\",\n\t\t\t\"p\",\n\t\t\t\"p/\",\n\t\t\t\"x/x\",\n\t\t\t\"t\",\n\t\t\t\"t/\",\n\t\t\t\"/\",\n\t\t\t\"p/slug1/\",\n\t\t}\n\t\tfor _, notFoundPath := range notFoundPaths {\n\t\t\tgot := Render(notFoundPath)\n\t\t\texpected := \"404\"\n\t\t\tif got != expected {\n\t\t\t\tt.Errorf(\"path %q: expected %q, got %q.\", notFoundPath, expected, got)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc assertMDEquals(t *testing.T, got, expected string) {\n\tt.Helper()\n\texpected = strings.TrimSpace(expected)\n\tgot = strings.TrimSpace(got)\n\tif expected != got {\n\t\tt.Errorf(\"invalid render output.\\nexpected %q.\\ngot %q.\", expected, got)\n\t}\n}\n"},{"name":"util.gno","body":"package gnoblog\n\nfunc checkErr(err error) {\n\tif err != nil {\n\t\tpanic(err)\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":"events","path":"gno.land/r/gnoland/events","files":[{"name":"administration.gno","body":"package events\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/ownable/exts/authorizable\"\n)\n\nvar (\n\tsu = std.Address(\"g125em6arxsnj49vx35f0n0z34putv5ty3376fg5\") // @leohhhn\n\tauth = authorizable.NewAuthorizableWithAddress(su)\n)\n\n// GetOwner gets the owner of the events realm\nfunc GetOwner() std.Address {\n\treturn auth.Owner()\n}\n\n// AddModerator adds a moderator to the events realm\nfunc AddModerator(mod std.Address) {\n\tauth.AssertCallerIsOwner()\n\n\tif err := auth.AddToAuthList(mod); err != nil {\n\t\tpanic(err)\n\t}\n}\n"},{"name":"errors.gno","body":"package events\n\nimport (\n\t\"errors\"\n\t\"strconv\"\n)\n\nvar (\n\tErrEmptyName = errors.New(\"event name cannot be empty\")\n\tErrNoSuchID = errors.New(\"event with specified ID does not exist\")\n\tErrMinWidgetSize = errors.New(\"you need to request at least 1 event to render\")\n\tErrMaxWidgetSize = errors.New(\"maximum number of events in widget is\" + strconv.Itoa(MaxWidgetSize))\n\tErrDescriptionTooLong = errors.New(\"event description is too long\")\n\tErrInvalidStartTime = errors.New(\"invalid start time format\")\n\tErrInvalidEndTime = errors.New(\"invalid end time format\")\n\tErrEndBeforeStart = errors.New(\"end time cannot be before start time\")\n\tErrStartEndTimezonemMismatch = errors.New(\"start and end timezones are not the same\")\n)\n"},{"name":"events.gno","body":"// Package events allows you to upload data about specific IRL/online events\n// It includes dynamic support for updating rendering events based on their\n// status, ie if they are upcoming, in progress, or in the past.\npackage events\n\nimport (\n\t\"sort\"\n\t\"std\"\n\t\"strings\"\n\t\"time\"\n\n\t\"gno.land/p/demo/seqid\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype (\n\tEvent struct {\n\t\tid string\n\t\tname string // name of event\n\t\tdescription string // short description of event\n\t\tlink string // link to auth corresponding web2 page, ie eventbrite/luma or conference page\n\t\tlocation string // location of the event\n\t\tstartTime time.Time // given in RFC3339\n\t\tendTime time.Time // end time of the event, given in RFC3339\n\t}\n\n\teventsSlice []*Event\n)\n\nvar (\n\tevents = make(eventsSlice, 0) // sorted\n\tidCounter seqid.ID\n)\n\nconst (\n\tmaxDescLength = 100\n\tEventAdded = \"EventAdded\"\n\tEventDeleted = \"EventDeleted\"\n\tEventEdited = \"EventEdited\"\n)\n\n// AddEvent adds auth new event\n// Start time \u0026 end time need to be specified in RFC3339, ie 2024-08-08T12:00:00+02:00\nfunc AddEvent(name, description, link, location, startTime, endTime string) (string, error) {\n\tauth.AssertOnAuthList()\n\n\tif strings.TrimSpace(name) == \"\" {\n\t\treturn \"\", ErrEmptyName\n\t}\n\n\tif len(description) \u003e maxDescLength {\n\t\treturn \"\", ufmt.Errorf(\"%s: provided length is %d, maximum is %d\", ErrDescriptionTooLong, len(description), maxDescLength)\n\t}\n\n\t// Parse times\n\tst, et, err := parseTimes(startTime, endTime)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tid := idCounter.Next().String()\n\te := \u0026Event{\n\t\tid: id,\n\t\tname: name,\n\t\tdescription: description,\n\t\tlink: link,\n\t\tlocation: location,\n\t\tstartTime: st,\n\t\tendTime: et,\n\t}\n\n\tevents = append(events, e)\n\tsort.Sort(events)\n\n\tstd.Emit(EventAdded,\n\t\t\"id\", e.id,\n\t)\n\n\treturn id, nil\n}\n\n// DeleteEvent deletes an event with auth given ID\nfunc DeleteEvent(id string) {\n\tauth.AssertOnAuthList()\n\n\te, idx, err := GetEventByID(id)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tevents = append(events[:idx], events[idx+1:]...)\n\n\tstd.Emit(EventDeleted,\n\t\t\"id\", e.id,\n\t)\n}\n\n// EditEvent edits an event with auth given ID\n// It only updates values corresponding to non-empty arguments sent with the call\n// Note: if you need to update the start time or end time, you need to provide both every time\nfunc EditEvent(id string, name, description, link, location, startTime, endTime string) {\n\tauth.AssertOnAuthList()\n\n\te, _, err := GetEventByID(id)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Set only valid values\n\tif strings.TrimSpace(name) != \"\" {\n\t\te.name = name\n\t}\n\n\tif strings.TrimSpace(description) != \"\" {\n\t\te.description = description\n\t}\n\n\tif strings.TrimSpace(link) != \"\" {\n\t\te.link = link\n\t}\n\n\tif strings.TrimSpace(location) != \"\" {\n\t\te.location = location\n\t}\n\n\tif strings.TrimSpace(startTime) != \"\" || strings.TrimSpace(endTime) != \"\" {\n\t\tst, et, err := parseTimes(startTime, endTime)\n\t\tif err != nil {\n\t\t\tpanic(err) // need to also revert other state changes\n\t\t}\n\n\t\toldStartTime := e.startTime\n\t\te.startTime = st\n\t\te.endTime = et\n\n\t\t// If sort order was disrupted, sort again\n\t\tif oldStartTime != e.startTime {\n\t\t\tsort.Sort(events)\n\t\t}\n\t}\n\n\tstd.Emit(EventEdited,\n\t\t\"id\", e.id,\n\t)\n}\n\nfunc GetEventByID(id string) (*Event, int, error) {\n\tfor i, event := range events {\n\t\tif event.id == id {\n\t\t\treturn event, i, nil\n\t\t}\n\t}\n\n\treturn nil, -1, ErrNoSuchID\n}\n\n// Len returns the length of the slice\nfunc (m eventsSlice) Len() int {\n\treturn len(m)\n}\n\n// Less compares the startTime fields of two elements\n// In this case, events will be sorted by largest startTime first (upcoming \u003e past)\nfunc (m eventsSlice) Less(i, j int) bool {\n\treturn m[i].startTime.After(m[j].startTime)\n}\n\n// Swap swaps two elements in the slice\nfunc (m eventsSlice) Swap(i, j int) {\n\tm[i], m[j] = m[j], m[i]\n}\n\n// parseTimes parses the start and end time for an event and checks for possible errors\nfunc parseTimes(startTime, endTime string) (time.Time, time.Time, error) {\n\tst, err := time.Parse(time.RFC3339, startTime)\n\tif err != nil {\n\t\treturn time.Time{}, time.Time{}, ufmt.Errorf(\"%s: %s\", ErrInvalidStartTime, err.Error())\n\t}\n\n\tet, err := time.Parse(time.RFC3339, endTime)\n\tif err != nil {\n\t\treturn time.Time{}, time.Time{}, ufmt.Errorf(\"%s: %s\", ErrInvalidEndTime, err.Error())\n\t}\n\n\tif et.Before(st) {\n\t\treturn time.Time{}, time.Time{}, ErrEndBeforeStart\n\t}\n\n\t_, stOffset := st.Zone()\n\t_, etOffset := et.Zone()\n\tif stOffset != etOffset {\n\t\treturn time.Time{}, time.Time{}, ErrStartEndTimezonemMismatch\n\t}\n\n\treturn st, et, nil\n}\n"},{"name":"events_test.gno","body":"package events\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gno.land/p/demo/uassert\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nvar (\n\tsuRealm = std.NewUserRealm(su)\n\n\tnow = \"2009-02-13T23:31:30Z\" // time.Now() is hardcoded to this value in the gno test machine currently\n\tparsedTimeNow, _ = time.Parse(time.RFC3339, now)\n)\n\nfunc TestAddEvent(t *testing.T) {\n\tstd.TestSetOriginCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\te1Start := parsedTimeNow.Add(time.Hour * 24 * 5)\n\te1End := e1Start.Add(time.Hour * 4)\n\n\tAddEvent(\"Event 1\", \"this event is upcoming\", \"gno.land\", \"gnome land\", e1Start.Format(time.RFC3339), e1End.Format(time.RFC3339))\n\n\tgot := renderHome(false)\n\n\tif !strings.Contains(got, \"Event 1\") {\n\t\tt.Fatalf(\"Expected to find Event 1 in render\")\n\t}\n\n\te2Start := parsedTimeNow.Add(-time.Hour * 24 * 5)\n\te2End := e2Start.Add(time.Hour * 4)\n\n\tAddEvent(\"Event 2\", \"this event is in the past\", \"gno.land\", \"gnome land\", e2Start.Format(time.RFC3339), e2End.Format(time.RFC3339))\n\n\tgot = renderHome(false)\n\n\tupcomingPos := strings.Index(got, \"## Upcoming events\")\n\tpastPos := strings.Index(got, \"## Past events\")\n\n\te1Pos := strings.Index(got, \"Event 1\")\n\te2Pos := strings.Index(got, \"Event 2\")\n\n\t// expected index ordering: upcoming \u003c e1 \u003c past \u003c e2\n\tif e1Pos \u003c upcomingPos || e1Pos \u003e pastPos {\n\t\tt.Fatalf(\"Expected to find Event 1 in Upcoming events\")\n\t}\n\n\tif e2Pos \u003c upcomingPos || e2Pos \u003c pastPos || e2Pos \u003c e1Pos {\n\t\tt.Fatalf(\"Expected to find Event 2 on auth different pos\")\n\t}\n\n\t// larger index =\u003e smaller startTime (future =\u003e past)\n\tif events[0].startTime.Unix() \u003c events[1].startTime.Unix() {\n\t\tt.Fatalf(\"expected ordering to be different\")\n\t}\n}\n\nfunc TestAddEventErrors(t *testing.T) {\n\tstd.TestSetOriginCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\t_, err := AddEvent(\"\", \"sample desc\", \"gno.land\", \"gnome land\", \"2009-02-13T23:31:31Z\", \"2009-02-13T23:33:31Z\")\n\tuassert.ErrorIs(t, err, ErrEmptyName)\n\n\t_, err = AddEvent(\"sample name\", \"sample desc\", \"gno.land\", \"gnome land\", \"\", \"2009-02-13T23:33:31Z\")\n\tuassert.ErrorContains(t, err, ErrInvalidStartTime.Error())\n\n\t_, err = AddEvent(\"sample name\", \"sample desc\", \"gno.land\", \"gnome land\", \"2009-02-13T23:31:31Z\", \"\")\n\tuassert.ErrorContains(t, err, ErrInvalidEndTime.Error())\n\n\t_, err = AddEvent(\"sample name\", \"sample desc\", \"gno.land\", \"gnome land\", \"2009-02-13T23:31:31Z\", \"2009-02-13T23:30:31Z\")\n\tuassert.ErrorIs(t, err, ErrEndBeforeStart)\n\n\t_, err = AddEvent(\"sample name\", \"sample desc\", \"gno.land\", \"gnome land\", \"2009-02-13T23:31:31+06:00\", \"2009-02-13T23:33:31+02:00\")\n\tuassert.ErrorIs(t, err, ErrStartEndTimezonemMismatch)\n\n\ttooLongDesc := `Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean ma`\n\t_, err = AddEvent(\"sample name\", tooLongDesc, \"gno.land\", \"gnome land\", \"2009-02-13T23:31:31Z\", \"2009-02-13T23:33:31Z\")\n\tuassert.ErrorContains(t, err, ErrDescriptionTooLong.Error())\n}\n\nfunc TestDeleteEvent(t *testing.T) {\n\tstd.TestSetOriginCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\te1Start := parsedTimeNow.Add(time.Hour * 24 * 5)\n\te1End := e1Start.Add(time.Hour * 4)\n\n\tid, _ := AddEvent(\"ToDelete\", \"description\", \"gno.land\", \"gnome land\", e1Start.Format(time.RFC3339), e1End.Format(time.RFC3339))\n\n\tgot := renderHome(false)\n\n\tif !strings.Contains(got, \"ToDelete\") {\n\t\tt.Fatalf(\"Expected to find ToDelete event in render\")\n\t}\n\n\tDeleteEvent(id)\n\tgot = renderHome(false)\n\n\tif strings.Contains(got, \"ToDelete\") {\n\t\tt.Fatalf(\"Did not expect to find ToDelete event in render\")\n\t}\n}\n\nfunc TestEditEvent(t *testing.T) {\n\tstd.TestSetOriginCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\te1Start := parsedTimeNow.Add(time.Hour * 24 * 5)\n\te1End := e1Start.Add(time.Hour * 4)\n\tloc := \"gnome land\"\n\n\tid, _ := AddEvent(\"ToDelete\", \"description\", \"gno.land\", loc, e1Start.Format(time.RFC3339), e1End.Format(time.RFC3339))\n\n\tnewName := \"New Name\"\n\tnewDesc := \"Normal description\"\n\tnewLink := \"new Link\"\n\tnewST := e1Start.Add(time.Hour)\n\tnewET := newST.Add(time.Hour)\n\n\tEditEvent(id, newName, newDesc, newLink, \"\", newST.Format(time.RFC3339), newET.Format(time.RFC3339))\n\tedited, _, _ := GetEventByID(id)\n\n\t// Check updated values\n\tuassert.Equal(t, edited.name, newName)\n\tuassert.Equal(t, edited.description, newDesc)\n\tuassert.Equal(t, edited.link, newLink)\n\tuassert.True(t, edited.startTime.Equal(newST))\n\tuassert.True(t, edited.endTime.Equal(newET))\n\n\t// Check if the old values are the same\n\tuassert.Equal(t, edited.location, loc)\n}\n\nfunc TestInvalidEdit(t *testing.T) {\n\tstd.TestSetOriginCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\tuassert.PanicsWithMessage(t, ErrNoSuchID.Error(), func() {\n\t\tEditEvent(\"123123\", \"\", \"\", \"\", \"\", \"\", \"\")\n\t})\n}\n\nfunc TestParseTimes(t *testing.T) {\n\t// times not provided\n\t// end time before start time\n\t// timezone Missmatch\n\n\t_, _, err := parseTimes(\"\", \"\")\n\tuassert.ErrorContains(t, err, ErrInvalidStartTime.Error())\n\n\t_, _, err = parseTimes(now, \"\")\n\tuassert.ErrorContains(t, err, ErrInvalidEndTime.Error())\n\n\t_, _, err = parseTimes(\"2009-02-13T23:30:30Z\", \"2009-02-13T21:30:30Z\")\n\tuassert.ErrorContains(t, err, ErrEndBeforeStart.Error())\n\n\t_, _, err = parseTimes(\"2009-02-10T23:30:30+02:00\", \"2009-02-13T21:30:33+05:00\")\n\tuassert.ErrorContains(t, err, ErrStartEndTimezonemMismatch.Error())\n}\n\nfunc TestRenderEventWidget(t *testing.T) {\n\tstd.TestSetOriginCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\t// No events yet\n\tevents = nil\n\tout, err := RenderEventWidget(1)\n\tuassert.NoError(t, err)\n\tuassert.Equal(t, out, \"No events.\")\n\n\t// Too many events\n\tout, err = RenderEventWidget(MaxWidgetSize + 1)\n\tuassert.ErrorIs(t, err, ErrMaxWidgetSize)\n\n\t// Too little events\n\tout, err = RenderEventWidget(0)\n\tuassert.ErrorIs(t, err, ErrMinWidgetSize)\n\n\t// Ordering \u0026 if requested amt is larger than the num of events that exist\n\te1Start := parsedTimeNow.Add(time.Hour * 24 * 5)\n\te1End := e1Start.Add(time.Hour * 4)\n\n\te2Start := parsedTimeNow.Add(time.Hour * 24 * 10) // event 2 is after event 1\n\te2End := e2Start.Add(time.Hour * 4)\n\n\t_, err = AddEvent(\"Event 1\", \"description\", \"gno.land\", \"loc\", e1Start.Format(time.RFC3339), e1End.Format(time.RFC3339))\n\turequire.NoError(t, err)\n\n\t_, err = AddEvent(\"Event 2\", \"description\", \"gno.land\", \"loc\", e2Start.Format(time.RFC3339), e2End.Format(time.RFC3339))\n\turequire.NoError(t, err)\n\n\tout, err = RenderEventWidget(MaxWidgetSize)\n\turequire.NoError(t, err)\n\n\tuniqueSequence := \"- [\" // sequence that is displayed once per each event as per the RenderEventWidget function\n\tuassert.Equal(t, 2, strings.Count(out, uniqueSequence))\n\n\tuassert.True(t, strings.Index(out, \"Event 1\") \u003e strings.Index(out, \"Event 2\"))\n}\n"},{"name":"rendering.gno","body":"package events\n\nimport (\n\t\"bytes\"\n\t\"time\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\nconst (\n\tMaxWidgetSize = 5\n)\n\n// RenderEventWidget shows up to eventsToRender of the latest events to a caller\nfunc RenderEventWidget(eventsToRender int) (string, error) {\n\tnumOfEvents := len(events)\n\tif numOfEvents == 0 {\n\t\treturn \"No events.\", nil\n\t}\n\n\tif eventsToRender \u003e MaxWidgetSize {\n\t\treturn \"\", ErrMaxWidgetSize\n\t}\n\n\tif eventsToRender \u003c 1 {\n\t\treturn \"\", ErrMinWidgetSize\n\t}\n\n\tif eventsToRender \u003e numOfEvents {\n\t\teventsToRender = numOfEvents\n\t}\n\n\toutput := \"\"\n\n\tfor _, event := range events[:eventsToRender] {\n\t\toutput += ufmt.Sprintf(\"- [%s](%s)\\n\", event.name, event.link)\n\t}\n\n\treturn output, nil\n}\n\n// renderHome renders the home page of the events realm\nfunc renderHome(admin bool) string {\n\toutput := \"# gno.land events\\n\\n\"\n\n\tif len(events) == 0 {\n\t\toutput += \"No upcoming or past events.\"\n\t\treturn output\n\t}\n\n\toutput += \"Below is a list of all gno.land events, including in progress, upcoming, and past ones.\\n\\n\"\n\toutput += \"---\\n\\n\"\n\n\tvar (\n\t\tinProgress = \"\"\n\t\tupcoming = \"\"\n\t\tpast = \"\"\n\t\tnow = time.Now()\n\t)\n\n\tfor _, e := range events {\n\t\tif now.Before(e.startTime) {\n\t\t\tupcoming += e.Render(admin)\n\t\t} else if now.After(e.endTime) {\n\t\t\tpast += e.Render(admin)\n\t\t} else {\n\t\t\tinProgress += e.Render(admin)\n\t\t}\n\t}\n\n\tif upcoming != \"\" {\n\t\t// Add upcoming events\n\t\toutput += \"## Upcoming events\\n\\n\"\n\t\toutput += \"\u003cdiv class='columns-3'\u003e\"\n\n\t\toutput += upcoming\n\n\t\toutput += \"\u003c/div\u003e\\n\\n\"\n\t\toutput += \"---\\n\\n\"\n\t}\n\n\tif inProgress != \"\" {\n\t\toutput += \"## Currently in progress\\n\\n\"\n\t\toutput += \"\u003cdiv class='columns-3'\u003e\"\n\n\t\toutput += inProgress\n\n\t\toutput += \"\u003c/div\u003e\\n\\n\"\n\t\toutput += \"---\\n\\n\"\n\t}\n\n\tif past != \"\" {\n\t\t// Add past events\n\t\toutput += \"## Past events\\n\\n\"\n\t\toutput += \"\u003cdiv class='columns-3'\u003e\"\n\n\t\toutput += past\n\n\t\toutput += \"\u003c/div\u003e\\n\\n\"\n\t}\n\n\treturn output\n}\n\n// Render returns the markdown representation of a single event instance\nfunc (e Event) Render(admin bool) string {\n\tvar buf bytes.Buffer\n\n\tbuf.WriteString(\"\u003cdiv\u003e\\n\\n\")\n\tbuf.WriteString(ufmt.Sprintf(\"### %s\\n\\n\", e.name))\n\tbuf.WriteString(ufmt.Sprintf(\"%s\\n\\n\", e.description))\n\tbuf.WriteString(ufmt.Sprintf(\"**Location:** %s\\n\\n\", e.location))\n\n\t_, offset := e.startTime.Zone() // offset is in seconds\n\thoursOffset := offset / (60 * 60)\n\tsign := \"\"\n\tif offset \u003e= 0 {\n\t\tsign = \"+\"\n\t}\n\n\tbuf.WriteString(ufmt.Sprintf(\"**Starts:** %s UTC%s%d\\n\\n\", e.startTime.Format(\"02 Jan 2006, 03:04 PM\"), sign, hoursOffset))\n\tbuf.WriteString(ufmt.Sprintf(\"**Ends:** %s UTC%s%d\\n\\n\", e.endTime.Format(\"02 Jan 2006, 03:04 PM\"), sign, hoursOffset))\n\n\tif admin {\n\t\tbuf.WriteString(ufmt.Sprintf(\"[EDIT](/r/gnoland/events$help\u0026func=EditEvent\u0026id=%s)\\n\\n\", e.id))\n\t\tbuf.WriteString(ufmt.Sprintf(\"[DELETE](/r/gnoland/events$help\u0026func=DeleteEvent\u0026id=%s)\\n\\n\", e.id))\n\t}\n\n\tif e.link != \"\" {\n\t\tbuf.WriteString(ufmt.Sprintf(\"[See more](%s)\\n\\n\", e.link))\n\t}\n\n\tbuf.WriteString(\"\u003c/div\u003e\")\n\n\treturn buf.String()\n}\n\n// Render is the main rendering entry point\nfunc Render(path string) string {\n\tif path == \"admin\" {\n\t\treturn renderHome(true)\n\t}\n\n\treturn renderHome(false)\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":"faucet","path":"gno.land/r/gnoland/faucet","files":[{"name":"admin.gno","body":"package faucet\n\nimport (\n\t\"errors\"\n\t\"std\"\n)\n\nfunc AdminSetInPause(inPause bool) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\tgInPause = inPause\n\treturn \"\"\n}\n\nfunc AdminSetMessage(message string) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\tgMessage = message\n\treturn \"\"\n}\n\nfunc AdminSetTransferLimit(amount int64) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\tgLimit = std.NewCoin(\"ugnot\", amount)\n\treturn \"\"\n}\n\nfunc AdminSetAdminAddr(addr std.Address) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\tgAdminAddr = addr\n\treturn \"\"\n}\n\nfunc AdminAddController(addr std.Address) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\n\tsize := gControllers.Size()\n\n\tif size \u003e= gControllersMaxSize {\n\t\treturn \"can not add more controllers than allowed\"\n\t}\n\n\tif gControllers.Has(addr.String()) {\n\t\treturn addr.String() + \" exists, no need to add.\"\n\t}\n\n\tgControllers.Set(addr.String(), addr)\n\n\treturn \"\"\n}\n\nfunc AdminRemoveController(addr std.Address) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\n\tif !gControllers.Has(addr.String()) {\n\t\treturn addr.String() + \" is not on the controller list\"\n\t}\n\n\t_, ok := gControllers.Remove(addr.String())\n\n\t// it not should happen.\n\t// we will check anyway to prevent issues in the underline implementation.\n\n\tif !ok {\n\t\treturn addr.String() + \" is not on the controller list\"\n\t}\n\n\treturn \"\"\n}\n\nfunc assertIsAdmin() error {\n\tcaller := std.OriginCaller()\n\tif caller != gAdminAddr {\n\t\treturn errors.New(\"restricted for admin\")\n\t}\n\treturn nil\n}\n"},{"name":"faucet.gno","body":"package faucet\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar (\n\t// configurable by admin.\n\tgAdminAddr std.Address = std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")\n\tgControllers = avl.NewTree()\n\tgControllersMaxSize = 10 // limit it to 10\n\tgInPause = false\n\tgMessage = \"# Community Faucet.\\n\\n\"\n\n\t// internal vars, for stats.\n\tgTotalTransferred std.Coins\n\tgTotalTransfers = uint(0)\n\n\t// per request limit, 350 gnot\n\tgLimit std.Coin = std.NewCoin(\"ugnot\", 350000000)\n)\n\nfunc Transfer(to std.Address, send int64) string {\n\tif err := assertIsController(); err != nil {\n\t\treturn err.Error()\n\t}\n\n\tif gInPause {\n\t\treturn errors.New(\"faucet in pause\").Error()\n\t}\n\n\t// limit the per request\n\tif send \u003e gLimit.Amount {\n\t\treturn errors.New(\"Per request limit \" + gLimit.String() + \" exceed\").Error()\n\t}\n\tsendCoins := std.Coins{std.NewCoin(\"ugnot\", send)}\n\n\tgTotalTransferred = gTotalTransferred.Add(sendCoins)\n\tgTotalTransfers++\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tpkgaddr := std.CurrentRealm().Addr()\n\tbanker.SendCoins(pkgaddr, to, sendCoins)\n\treturn \"\"\n}\n\nfunc GetPerTransferLimit() int64 {\n\treturn gLimit.Amount\n}\n\nfunc Render(_ string) string {\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tbalance := banker.GetCoins(std.CurrentRealm().Addr())\n\n\toutput := gMessage\n\tif gInPause {\n\t\toutput += \"Status: inactive.\\n\"\n\t} else {\n\t\toutput += \"Status: active.\\n\"\n\t}\n\toutput += ufmt.Sprintf(\"Balance: %s.\\n\", balance.String())\n\toutput += ufmt.Sprintf(\"Total transfers: %s (in %d times).\\n\\n\", gTotalTransferred.String(), gTotalTransfers)\n\n\toutput += \"Package address: \" + std.CurrentRealm().Addr().String() + \"\\n\\n\"\n\toutput += ufmt.Sprintf(\"Admin: %s\\n\\n \", gAdminAddr.String())\n\toutput += ufmt.Sprintf(\"Controllers:\\n\\n \")\n\n\tfor i := 0; i \u003c gControllers.Size(); i++ {\n\t\t_, v := gControllers.GetByIndex(i)\n\t\toutput += ufmt.Sprintf(\"%s \", v.(std.Address))\n\t}\n\n\toutput += \"\\n\\n\"\n\toutput += ufmt.Sprintf(\"Per request limit: %s\\n\\n\", gLimit.String())\n\n\treturn output\n}\n\nfunc assertIsController() error {\n\tcaller := std.OriginCaller()\n\n\tok := gControllers.Has(caller.String())\n\tif !ok {\n\t\treturn errors.New(caller.String() + \" is not on the controller list\")\n\t}\n\treturn nil\n}\n"},{"name":"faucet_test.gno","body":"package faucet\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/gnoland/faucet\"\n)\n\nfunc TestPackage(t *testing.T) {\n\tvar (\n\t\tadminaddr = std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")\n\t\tfaucetaddr = std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\t\tcontrolleraddr1 = testutils.TestAddress(\"controller1\")\n\t\tcontrolleraddr2 = testutils.TestAddress(\"controller2\")\n\t\tcontrolleraddr3 = testutils.TestAddress(\"controller3\")\n\t\tcontrolleraddr4 = testutils.TestAddress(\"controller4\")\n\t\tcontrolleraddr5 = testutils.TestAddress(\"controller5\")\n\t\tcontrolleraddr6 = testutils.TestAddress(\"controller6\")\n\t\tcontrolleraddr7 = testutils.TestAddress(\"controller7\")\n\t\tcontrolleraddr8 = testutils.TestAddress(\"controller8\")\n\t\tcontrolleraddr9 = testutils.TestAddress(\"controller9\")\n\t\tcontrolleraddr10 = testutils.TestAddress(\"controller10\")\n\t\tcontrolleraddr11 = testutils.TestAddress(\"controller11\")\n\n\t\ttest1addr = testutils.TestAddress(\"test1\")\n\t)\n\t// deposit 1000gnot to faucet contract\n\tstd.TestIssueCoins(faucetaddr, std.Coins{{\"ugnot\", 1000000000}})\n\tassertBalance(t, faucetaddr, 1200000000)\n\n\t// by default, balance is empty, and as a user I cannot call Transfer, or Admin commands.\n\n\tassertBalance(t, test1addr, 0)\n\tstd.TestSetOriginCaller(test1addr)\n\tassertErr(t, faucet.Transfer(test1addr, 1000000))\n\n\tassertErr(t, faucet.AdminAddController(controlleraddr1))\n\tstd.TestSetOriginCaller(controlleraddr1)\n\tassertErr(t, faucet.Transfer(test1addr, 1000000))\n\n\t// as an admin, add the controller to contract and deposit more 2000gnot to contract\n\tstd.TestSetOriginCaller(adminaddr)\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr1))\n\tassertBalance(t, faucetaddr, 1200000000)\n\n\t// now, send some tokens as controller.\n\tstd.TestSetOriginCaller(controlleraddr1)\n\tassertNoErr(t, faucet.Transfer(test1addr, 1000000))\n\tassertBalance(t, test1addr, 1000000)\n\tassertNoErr(t, faucet.Transfer(test1addr, 1000000))\n\tassertBalance(t, test1addr, 2000000)\n\tassertBalance(t, faucetaddr, 1198000000)\n\n\t// remove controller\n\t// as an admin, remove controller\n\tstd.TestSetOriginCaller(adminaddr)\n\tassertNoErr(t, faucet.AdminRemoveController(controlleraddr1))\n\tstd.TestSetOriginCaller(controlleraddr1)\n\tassertErr(t, faucet.Transfer(test1addr, 1000000))\n\n\t// duplicate controller\n\tstd.TestSetOriginCaller(adminaddr)\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr1))\n\tassertErr(t, faucet.AdminAddController(controlleraddr1))\n\t// add more than more than allowed controllers\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr2))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr3))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr4))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr5))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr6))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr7))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr8))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr9))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr10))\n\tassertErr(t, faucet.AdminAddController(controlleraddr11))\n\n\t// send more than per transfer limit\n\tstd.TestSetOriginCaller(adminaddr)\n\tfaucet.AdminSetTransferLimit(300000000)\n\tstd.TestSetOriginCaller(controlleraddr1)\n\tassertErr(t, faucet.Transfer(test1addr, 301000000))\n\n\t// block transefer from the address not on the controllers list.\n\tstd.TestSetOriginCaller(controlleraddr11)\n\tassertErr(t, faucet.Transfer(test1addr, 1000000))\n}\n\nfunc assertErr(t *testing.T, err string) {\n\tt.Helper()\n\n\tif err == \"\" {\n\t\tt.Logf(\"info: got err: %v\", err)\n\t\tt.Errorf(\"expected an error, got nil.\")\n\t}\n}\n\nfunc assertNoErr(t *testing.T, err string) {\n\tt.Helper()\n\tif err != \"\" {\n\t\tt.Errorf(\"got err: %v.\", err)\n\t}\n}\n\nfunc assertBalance(t *testing.T, addr std.Address, expectedBal int64) {\n\tt.Helper()\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tcoins := banker.GetCoins(addr)\n\tgot := coins.AmountOf(\"ugnot\")\n\n\tif expectedBal != got {\n\t\tt.Errorf(\"invalid balance: expected %d, got %d.\", expectedBal, got)\n\t}\n}\n"},{"name":"z0_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/gnoland/faucet\"\n)\n\n// mints ugnot to current realm\nfunc init() {\n\tfacuetaddr := std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\tstd.TestIssueCoins(facuetaddr, std.Coins{{\"ugnot\", 200000000}})\n}\n\n// assert render with empty path and no controllers\nfunc main() {\n\tprintln(faucet.Render(\"\"))\n}\n\n// Output:\n// # Community Faucet.\n//\n// Status: active.\n// Balance: 200000000ugnot.\n// Total transfers: (in 0 times).\n//\n// Package address: g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7\n//\n// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n//\n// Controllers:\n//\n//\n//\n// Per request limit: 350000000ugnot\n"},{"name":"z1_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/gnoland/faucet\"\n)\n\n// mints ugnot to current realm\nfunc init() {\n\tfacuetaddr := std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\tstd.TestIssueCoins(facuetaddr, std.Coins{{\"ugnot\", 200000000}})\n}\n\n// assert render with a path and no controllers\nfunc main() {\n\tprintln(faucet.Render(\"path\"))\n}\n\n// Output:\n// # Community Faucet.\n//\n// Status: active.\n// Balance: 200000000ugnot.\n// Total transfers: (in 0 times).\n//\n// Package address: g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7\n//\n// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n//\n// Controllers:\n//\n//\n//\n// Per request limit: 350000000ugnot\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/gnoland/faucet\"\n)\n\n// mints ugnot to current realm\nfunc init() {\n\tfacuetaddr := std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\tstd.TestIssueCoins(facuetaddr, std.Coins{{\"ugnot\", 200000000}})\n}\n\n// assert render with empty path and 2 controllers\nfunc main() {\n\tvar (\n\t\tadminaddr = std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")\n\t\tcontrolleraddr1 = testutils.TestAddress(\"controller1\")\n\t\tcontrolleraddr2 = testutils.TestAddress(\"controller2\")\n\t)\n\tstd.TestSetOriginCaller(adminaddr)\n\terr := faucet.AdminAddController(controlleraddr1)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\terr = faucet.AdminAddController(controlleraddr2)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\tprintln(faucet.Render(\"\"))\n}\n\n// Output:\n// # Community Faucet.\n//\n// Status: active.\n// Balance: 200000000ugnot.\n// Total transfers: (in 0 times).\n//\n// Package address: g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7\n//\n// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n//\n// Controllers:\n//\n// g1vdhkuarjdakxcetjx9047h6lta047h6lsdacav g1vdhkuarjdakxcetjxf047h6lta047h6lnrev3v\n//\n// Per request limit: 350000000ugnot\n"},{"name":"z3_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/gnoland/faucet\"\n)\n\n// mints coints to current realm\nfunc init() {\n\tfacuetaddr := std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\tstd.TestIssueCoins(facuetaddr, std.Coins{{\"ugnot\", 200000000}})\n}\n\n// assert render with 2 controllers and 2 transfers\nfunc main() {\n\tvar (\n\t\tadminaddr = std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")\n\t\tcontrolleraddr1 = testutils.TestAddress(\"controller1\")\n\t\tcontrolleraddr2 = testutils.TestAddress(\"controller2\")\n\t\ttestaddr1 = testutils.TestAddress(\"test1\")\n\t\ttestaddr2 = testutils.TestAddress(\"test2\")\n\t)\n\tstd.TestSetOriginCaller(adminaddr)\n\terr := faucet.AdminAddController(controlleraddr1)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\terr = faucet.AdminAddController(controlleraddr2)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\tstd.TestSetOriginCaller(controlleraddr1)\n\terr = faucet.Transfer(testaddr1, 1000000)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\tstd.TestSetOriginCaller(controlleraddr2)\n\terr = faucet.Transfer(testaddr1, 2000000)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\tprintln(faucet.Render(\"\"))\n}\n\n// Output:\n// # Community Faucet.\n//\n// Status: active.\n// Balance: 197000000ugnot.\n// Total transfers: 3000000ugnot (in 2 times).\n//\n// Package address: g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7\n//\n// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n//\n// Controllers:\n//\n// g1vdhkuarjdakxcetjx9047h6lta047h6lsdacav g1vdhkuarjdakxcetjxf047h6lta047h6lnrev3v\n//\n// Per request limit: 350000000ugnot\n"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}} From 627d0007e2db0deda5cfe9aed3262c0b70087bd4 Mon Sep 17 00:00:00 2001 From: "hieu.ha" Date: Fri, 20 Dec 2024 12:55:47 +0700 Subject: [PATCH 06/14] refactor: orig => origin --- docs/reference/stdlibs/std/chain.md | 2 +- gnovm/stdlibs/generated.go | 4 ++-- gnovm/stdlibs/std/native.gno | 4 ++-- gnovm/stdlibs/std/native.go | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/reference/stdlibs/std/chain.md b/docs/reference/stdlibs/std/chain.md index 53a4e3ce13c..74a85e13353 100644 --- a/docs/reference/stdlibs/std/chain.md +++ b/docs/reference/stdlibs/std/chain.md @@ -109,7 +109,7 @@ Returns the address of the first (entry point) realm/package in a sequence of re #### Usage ```go -origPkgAddr := std.GetOriginPkgAddr() +originPkgAddr := std.GetOriginPkgAddr() ``` --- diff --git a/gnovm/stdlibs/generated.go b/gnovm/stdlibs/generated.go index d89a9a5f87c..4953d881032 100644 --- a/gnovm/stdlibs/generated.go +++ b/gnovm/stdlibs/generated.go @@ -556,14 +556,14 @@ var nativeFuncs = [...]NativeFunc{ }, { "std", - "origPkgAddr", + "originPkgAddr", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, true, func(m *gno.Machine) { - r0 := libs_std.X_origPkgAddr( + r0 := libs_std.X_originPkgAddr( m, ) diff --git a/gnovm/stdlibs/std/native.gno b/gnovm/stdlibs/std/native.gno index eb61280777e..e4e34d96e29 100644 --- a/gnovm/stdlibs/std/native.gno +++ b/gnovm/stdlibs/std/native.gno @@ -38,7 +38,7 @@ func PreviousRealm() Realm { } func GetOriginPkgAddr() Address { - return Address(origPkgAddr()) + return Address(originPkgAddr()) } func CallerAt(n int) Address { @@ -60,7 +60,7 @@ func DecodeBech32(addr Address) (prefix string, bz [20]byte, ok bool) { // Variations which don't use named types. func originSend() (denoms []string, amounts []int64) func originCaller() string -func origPkgAddr() string +func originPkgAddr() string func callerAt(n int) string func getRealm(height int) (address string, pkgPath string) func derivePkgAddr(pkgPath string) string diff --git a/gnovm/stdlibs/std/native.go b/gnovm/stdlibs/std/native.go index e17c3e135df..fb539120a71 100644 --- a/gnovm/stdlibs/std/native.go +++ b/gnovm/stdlibs/std/native.go @@ -76,7 +76,7 @@ func X_originCaller(m *gno.Machine) string { return string(GetContext(m).OriginCaller) } -func X_origPkgAddr(m *gno.Machine) string { +func X_originPkgAddr(m *gno.Machine) string { return string(GetContext(m).OriginPkgAddr) } From 8489ec453bd9a03f5fa9f90772fea8d0afca888f Mon Sep 17 00:00:00 2001 From: "hieu.ha" Date: Fri, 20 Dec 2024 13:25:29 +0700 Subject: [PATCH 07/14] refactor: Addr => Address --- .../creating-grc20/mytoken-1.gno | 2 +- .../creating-grc20/mytoken-2.gno | 4 +- .../porting-solidity-to-gno/porting-11.gno | 2 +- .../porting-solidity-to-gno/porting-8.gno | 2 +- .../porting-solidity-to-gno/porting-9.gno | 2 +- docs/concepts/effective-gno.md | 24 ++++----- docs/how-to-guides/creating-grc20.md | 8 +-- docs/how-to-guides/porting-solidity-to-gno.md | 6 +-- docs/reference/stdlibs/std/chain.md | 4 +- docs/reference/stdlibs/std/realm.md | 2 +- .../gno.land/p/demo/grc/grc20/tellers.gno | 6 +-- .../gno.land/p/demo/grc/grc721/basic_nft.gno | 10 ++-- .../p/demo/grc/grc721/basic_nft_test.gno | 8 +-- .../p/demo/grc/grc721/grc721_metadata.gno | 2 +- .../p/demo/grc/grc721/grc721_royalty.gno | 2 +- .../gno.land/p/demo/memeland/memeland.gno | 4 +- .../exts/authorizable/authorizable.gno | 4 +- examples/gno.land/p/demo/ownable/ownable.gno | 6 +-- .../p/demo/subscription/lifetime/lifetime.gno | 2 +- .../subscription/lifetime/lifetime_test.gno | 2 +- .../demo/subscription/recurring/recurring.gno | 2 +- .../subscription/recurring/recurring_test.gno | 16 +++--- examples/gno.land/p/moul/debug/debug.gno | 4 +- .../gno.land/p/moul/debug/z1_filetest.gno | 4 +- .../gno.land/p/moul/debug/z2_filetest.gno | 4 +- examples/gno.land/p/n2p5/loci/loci.gno | 4 +- examples/gno.land/p/n2p5/mgroup/mgroup.gno | 2 +- examples/gno.land/r/demo/banktest/README.md | 4 +- .../gno.land/r/demo/banktest/banktest.gno | 4 +- examples/gno.land/r/demo/bar20/bar20.gno | 2 +- .../gno.land/r/demo/disperse/disperse.gno | 6 +-- examples/gno.land/r/demo/foo20/foo20.gno | 2 +- examples/gno.land/r/demo/foo721/foo721.gno | 4 +- .../r/demo/games/dice_roller/dice_roller.gno | 4 +- .../gno.land/r/demo/games/shifumi/shifumi.gno | 4 +- .../r/demo/grc20factory/grc20factory.gno | 10 ++-- .../r/demo/grc20reg/grc20reg_test.gno | 2 +- examples/gno.land/r/demo/profile/profile.gno | 6 +-- examples/gno.land/r/demo/tests/tests_test.gno | 4 +- .../gno.land/r/demo/tests/z2_filetest.gno | 8 +-- .../gno.land/r/demo/tests/z3_filetest.gno | 4 +- .../gno.land/r/demo/userbook/userbook.gno | 2 +- examples/gno.land/r/demo/wugnot/wugnot.gno | 6 +-- examples/gno.land/r/docs/buttons/buttons.gno | 2 +- examples/gno.land/r/gnoland/blog/admin.gno | 2 +- examples/gno.land/r/gnoland/faucet/faucet.gno | 6 +-- examples/gno.land/r/gnoland/monit/monit.gno | 4 +- examples/gno.land/r/leon/config/config.gno | 2 +- examples/gno.land/r/leon/hof/hof.gno | 4 +- examples/gno.land/r/leon/home/home.gno | 4 +- .../gno.land/r/matijamarjanovic/home/home.gno | 2 +- .../gno.land/r/morgan/guestbook/guestbook.gno | 6 +-- examples/gno.land/r/moul/home/z2_filetest.gno | 4 +- examples/gno.land/r/n2p5/home/home.gno | 2 +- examples/gno.land/r/n2p5/loci/loci.gno | 2 +- examples/gno.land/r/stefann/home/home.gno | 2 +- gno.land/genesis/genesis_txs.jsonl | 2 +- .../integration/testdata/grc20_registry.txtar | 6 +-- .../integration/testdata/grc721_emit.txtar | 4 +- .../pkg/integration/testdata/initctx.txtar | 2 +- .../pkg/integration/testdata/issue_1786.txtar | 6 +-- .../pkg/integration/testdata/issue_2283.txtar | 4 +- .../testdata/issue_2283_cacheTypes.txtar | 4 +- .../pkg/integration/testdata/prevrealm.txtar | 6 +-- gno.land/pkg/sdk/vm/keeper_test.go | 12 ++--- gnovm/stdlibs/std/banker.gno | 6 +-- gnovm/stdlibs/std/native.gno | 2 +- gnovm/tests/files/zrealm_crossrealm11.gno | 10 ++-- gnovm/tests/files/zrealm_crossrealm12.gno | 6 +-- gnovm/tests/files/zrealm_std6.gno | 2 +- .../test5.gno.land/genesis_txs.jsonl | 52 +++++++++---------- 71 files changed, 188 insertions(+), 188 deletions(-) diff --git a/docs/assets/how-to-guides/creating-grc20/mytoken-1.gno b/docs/assets/how-to-guides/creating-grc20/mytoken-1.gno index 3f4cb2716d8..df1fcac406c 100644 --- a/docs/assets/how-to-guides/creating-grc20/mytoken-1.gno +++ b/docs/assets/how-to-guides/creating-grc20/mytoken-1.gno @@ -17,7 +17,7 @@ var ( // init is called once at time of deployment func init() { // Set deployer of Realm to admin - admin = std.PreviousRealm().Addr() + admin = std.PreviousRealm().Address() // Set token name, symbol and number of decimals banker = grc20.NewBanker("My Token", "TKN", 4) diff --git a/docs/assets/how-to-guides/creating-grc20/mytoken-2.gno b/docs/assets/how-to-guides/creating-grc20/mytoken-2.gno index 84d4de8e69e..6f5d206f4b3 100644 --- a/docs/assets/how-to-guides/creating-grc20/mytoken-2.gno +++ b/docs/assets/how-to-guides/creating-grc20/mytoken-2.gno @@ -44,13 +44,13 @@ func TransferFrom(from, to std.Address, amount uint64) { // Mint mints amount of tokens to address. Callable only by admin of token func Mint(address std.Address, amount uint64) { - assertIsAdmin(std.PreviousRealm().Addr()) + assertIsAdmin(std.PreviousRealm().Address()) checkErr(banker.Mint(address, amount)) } // Burn burns amount of tokens from address. Callable only by admin of token func Burn(address std.Address, amount uint64) { - assertIsAdmin(std.PreviousRealm().Addr()) + assertIsAdmin(std.PreviousRealm().Address()) checkErr(banker.Burn(address, amount)) } diff --git a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-11.gno b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-11.gno index e8eb785ccc9..eece8634e07 100644 --- a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-11.gno +++ b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-11.gno @@ -11,7 +11,7 @@ func AuctionEnd() { // Send the highest bid to the recipient banker := std.GetBanker(std.BankerTypeRealmSend) - pkgAddr := std.GetOriginPkgAddr() + pkgAddr := std.GetOriginPkgAddress() banker.SendCoins(pkgAddr, receiver, std.Coins{{"ugnot", int64(highestBid)}}) } diff --git a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-8.gno b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-8.gno index ed2e2d7fcde..f3e6ec22cde 100644 --- a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-8.gno +++ b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-8.gno @@ -8,7 +8,7 @@ func Withdraw() { // Return the exceeded amount banker := std.GetBanker(std.BankerTypeRealmSend) - pkgAddr := std.GetOriginPkgAddr() + pkgAddr := std.GetOriginPkgAddress() banker.SendCoins(pkgAddr, std.OriginCaller(), std.Coins{{"ugnot", amount.(int64)}}) } diff --git a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-9.gno b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-9.gno index 4056145e041..0b102627192 100644 --- a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-9.gno +++ b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-9.gno @@ -11,7 +11,7 @@ func TestWithdraw(t *testing.T) { shouldEqual(t, pendingReturns.Has(returnAddr), true) banker := std.GetBanker(std.BankerTypeRealmSend) - pkgAddr := std.GetOriginPkgAddr() + pkgAddr := std.GetOriginPkgAddress() banker.SendCoins(pkgAddr, std.Address(returnAddr), std.Coins{{"ugnot", returnAmount}}) shouldEqual(t, banker.GetCoins(std.Address(returnAddr)).String(), "3ugnot") } diff --git a/docs/concepts/effective-gno.md b/docs/concepts/effective-gno.md index d97a978ee40..4ed6a70e5a2 100644 --- a/docs/concepts/effective-gno.md +++ b/docs/concepts/effective-gno.md @@ -115,7 +115,7 @@ that could lead to user frustration or the need to fork the code. import "std" func Foobar() { - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() if caller != "g1xxxxx" { panic("permission denied") } @@ -399,7 +399,7 @@ certain operations. import "std" func PublicMethod(nb int) { - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() privateMethod(caller, nb) } @@ -407,7 +407,7 @@ func privateMethod(caller std.Address, nb int) { /* ... */ } ``` In this example, `PublicMethod` is a public function that can be called by other -realms. It retrieves the caller's address using `std.PreviousRealm().Addr()`, and +realms. It retrieves the caller's address using `std.PreviousRealm().Address()`, and then passes it to `privateMethod`, which is a private function that performs the actual logic. This way, `privateMethod` can only be called from within the realm, and it can use the caller's address for authentication or authorization @@ -440,11 +440,11 @@ import ( var owner std.Address func init() { - owner = std.PreviousRealm().Addr() + owner = std.PreviousRealm().Address() } func ChangeOwner(newOwner std.Address) { - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() if caller != owner { panic("access denied") @@ -516,7 +516,7 @@ import "std" var admin std.Address = "g1xxxxx" func AdminOnlyFunction() { - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() if caller != admin { panic("permission denied") } @@ -527,7 +527,7 @@ func AdminOnlyFunction() { ``` In this example, `AdminOnlyFunction` is a function that can only be called by -the admin. It retrieves the caller's address using `std.PreviousRealm().Addr()`, +the admin. It retrieves the caller's address using `std.PreviousRealm().Address()`, this can be either another realm contract, or the calling user if there is no other intermediary realm. and then checks if the caller is the admin. If not, it panics and stops the execution. @@ -543,7 +543,7 @@ Here's an example: import "std" func TransferTokens(to std.Address, amount int64) { - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() if caller != admin { panic("permission denied") } @@ -552,7 +552,7 @@ func TransferTokens(to std.Address, amount int64) { ``` In this example, `TransferTokens` is a function that can only be called by the -admin. It retrieves the caller's address using `std.PreviousRealm().Addr()`, and +admin. It retrieves the caller's address using `std.PreviousRealm().Address()`, and then checks if the caller is the admin. If not, the function panics and execution is stopped. By using these access control mechanisms, you can ensure that your contract's @@ -631,7 +631,7 @@ type MySafeStruct { } func NewSafeStruct() *MySafeStruct { - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() return &MySafeStruct{ counter: 0, admin: caller, @@ -640,7 +640,7 @@ func NewSafeStruct() *MySafeStruct { func (s *MySafeStruct) Counter() int { return s.counter } func (s *MySafeStruct) Inc() { - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() if caller != s.admin { panic("permission denied") } @@ -704,7 +704,7 @@ import "gno.land/p/demo/grc/grc20" var fooToken = grc20.NewBanker("Foo Token", "FOO", 4) func MyBalance() uint64 { - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() return fooToken.BalanceOf(caller) } ``` diff --git a/docs/how-to-guides/creating-grc20.md b/docs/how-to-guides/creating-grc20.md index b22e6eab8ad..86718d5b89c 100644 --- a/docs/how-to-guides/creating-grc20.md +++ b/docs/how-to-guides/creating-grc20.md @@ -41,7 +41,7 @@ var ( // init is called once at time of deployment func init() { // Set deployer of Realm to admin - admin = std.PreviousRealm().Addr() + admin = std.PreviousRealm().Address() // Set token name, symbol and number of decimals banker = grc20.NewBanker("My Token", "TKN", 4) @@ -142,7 +142,7 @@ caller’s allowance. ```go // Mint mints amount of tokens to address. Callable only by admin of token func Mint(address std.Address, amount uint64) { - assertIsAdmin(std.PreviousRealm().Addr()) + assertIsAdmin(std.PreviousRealm().Address()) checkErr(banker.Mint(address, amount)) } ``` @@ -153,7 +153,7 @@ increasing the total supply. ```go // Burn burns amount of tokens from address. Callable only by admin of token func Burn(address std.Address, amount uint64) { - assertIsAdmin(std.PreviousRealm().Addr()) + assertIsAdmin(std.PreviousRealm().Address()) checkErr(banker.Burn(address, amount)) } ``` @@ -162,7 +162,7 @@ decreasing the total supply. [embedmd]:# (../assets/how-to-guides/creating-grc20/mytoken-2.gno go /.*assertIsAdmin/ /^}/) ```go - assertIsAdmin(std.PreviousRealm().Addr()) + assertIsAdmin(std.PreviousRealm().Address()) checkErr(banker.Mint(address, amount)) } ``` diff --git a/docs/how-to-guides/porting-solidity-to-gno.md b/docs/how-to-guides/porting-solidity-to-gno.md index cd6989cd476..6eb8dbcd734 100644 --- a/docs/how-to-guides/porting-solidity-to-gno.md +++ b/docs/how-to-guides/porting-solidity-to-gno.md @@ -474,7 +474,7 @@ func Withdraw() { // Return the exceeded amount banker := std.GetBanker(std.BankerTypeRealmSend) - pkgAddr := std.GetOriginPkgAddr() + pkgAddr := std.GetOriginPkgAddress() banker.SendCoins(pkgAddr, std.OriginCaller(), std.Coins{{"ugnot", amount.(int64)}}) } @@ -500,7 +500,7 @@ func TestWithdraw(t *testing.T) { shouldEqual(t, pendingReturns.Has(returnAddr), true) banker := std.GetBanker(std.BankerTypeRealmSend) - pkgAddr := std.GetOriginPkgAddr() + pkgAddr := std.GetOriginPkgAddress() banker.SendCoins(pkgAddr, std.Address(returnAddr), std.Coins{{"ugnot", returnAmount}}) shouldEqual(t, banker.GetCoins(std.Address(returnAddr)).String(), "3ugnot") } @@ -565,7 +565,7 @@ func AuctionEnd() { // Send the highest bid to the recipient banker := std.GetBanker(std.BankerTypeRealmSend) - pkgAddr := std.GetOriginPkgAddr() + pkgAddr := std.GetOriginPkgAddress() banker.SendCoins(pkgAddr, receiver, std.Coins{{"ugnot", int64(highestBid)}}) } diff --git a/docs/reference/stdlibs/std/chain.md b/docs/reference/stdlibs/std/chain.md index 74a85e13353..69a1a77c8b6 100644 --- a/docs/reference/stdlibs/std/chain.md +++ b/docs/reference/stdlibs/std/chain.md @@ -103,13 +103,13 @@ caller := std.OriginCaller() ## GetOriginPkgAddr ```go -func GetOriginPkgAddr() string +func GetOriginPkgAddress() string ``` Returns the address of the first (entry point) realm/package in a sequence of realm/package calls. #### Usage ```go -originPkgAddr := std.GetOriginPkgAddr() +originPkgAddr := std.GetOriginPkgAddress() ``` --- diff --git a/docs/reference/stdlibs/std/realm.md b/docs/reference/stdlibs/std/realm.md index f69cd874c75..9c5aad86819 100644 --- a/docs/reference/stdlibs/std/realm.md +++ b/docs/reference/stdlibs/std/realm.md @@ -22,7 +22,7 @@ Returns the **Address** field of the realm it was called upon. #### Usage ```go -realmAddr := r.Addr() // eg. g1n2j0gdyv45aem9p0qsfk5d2gqjupv5z536na3d +realmAddr := r.Address() // eg. g1n2j0gdyv45aem9p0qsfk5d2gqjupv5z536na3d ``` --- ## PkgPath diff --git a/examples/gno.land/p/demo/grc/grc20/tellers.gno b/examples/gno.land/p/demo/grc/grc20/tellers.gno index 96db4847f7f..733d10148e3 100644 --- a/examples/gno.land/p/demo/grc/grc20/tellers.gno +++ b/examples/gno.land/p/demo/grc/grc20/tellers.gno @@ -14,7 +14,7 @@ func (tok *Token) CallerTeller() Teller { return &fnTeller{ accountFn: func() std.Address { - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() return caller }, Token: tok, @@ -44,7 +44,7 @@ func (tok *Token) RealmTeller() Teller { panic("Token cannot be nil") } - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() return &fnTeller{ accountFn: func() std.Address { @@ -61,7 +61,7 @@ func (tok *Token) RealmSubTeller(slug string) Teller { panic("Token cannot be nil") } - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() account := accountSlugAddr(caller, slug) return &fnTeller{ diff --git a/examples/gno.land/p/demo/grc/grc721/basic_nft.gno b/examples/gno.land/p/demo/grc/grc721/basic_nft.gno index 24a7be57ea6..ed7f96dd598 100644 --- a/examples/gno.land/p/demo/grc/grc721/basic_nft.gno +++ b/examples/gno.land/p/demo/grc/grc721/basic_nft.gno @@ -81,7 +81,7 @@ func (s *basicNFT) SetTokenURI(tid TokenID, tURI TokenURI) (bool, error) { if err != nil { return false, err } - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() if caller != owner { return false, ErrCallerIsNotOwner } @@ -115,7 +115,7 @@ func (s *basicNFT) Approve(to std.Address, tid TokenID) error { return ErrApprovalToCurrentOwner } - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() if caller != owner && !s.IsApprovedForAll(owner, caller) { return ErrCallerIsNotOwnerOrApproved } @@ -147,7 +147,7 @@ func (s *basicNFT) SetApprovalForAll(operator std.Address, approved bool) error return ErrInvalidAddress } - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() return s.setApprovalForAll(caller, operator, approved) } @@ -155,7 +155,7 @@ func (s *basicNFT) SetApprovalForAll(operator std.Address, approved bool) error // contract recipients are aware of the GRC721 protocol to prevent // tokens from being forever locked. func (s *basicNFT) SafeTransferFrom(from, to std.Address, tid TokenID) error { - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() if !s.isApprovedOrOwner(caller, tid) { return ErrCallerIsNotOwnerOrApproved } @@ -174,7 +174,7 @@ func (s *basicNFT) SafeTransferFrom(from, to std.Address, tid TokenID) error { // Transfers `tokenId` token from `from` to `to`. func (s *basicNFT) TransferFrom(from, to std.Address, tid TokenID) error { - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() if !s.isApprovedOrOwner(caller, tid) { return ErrCallerIsNotOwnerOrApproved } diff --git a/examples/gno.land/p/demo/grc/grc721/basic_nft_test.gno b/examples/gno.land/p/demo/grc/grc721/basic_nft_test.gno index 5f2a276a311..34e918c5097 100644 --- a/examples/gno.land/p/demo/grc/grc721/basic_nft_test.gno +++ b/examples/gno.land/p/demo/grc/grc721/basic_nft_test.gno @@ -111,7 +111,7 @@ func TestSetApprovalForAll(t *testing.T) { dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) uassert.True(t, dummy != nil, "should not be nil") - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") isApprovedForAll := dummy.IsApprovedForAll(caller, addr) @@ -136,7 +136,7 @@ func TestApprove(t *testing.T) { dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) uassert.True(t, dummy != nil, "should not be nil") - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") dummy.mint(caller, TokenID("1")) @@ -156,7 +156,7 @@ func TestTransferFrom(t *testing.T) { dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) uassert.True(t, dummy != nil, "should not be nil") - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") dummy.mint(caller, TokenID("1")) @@ -185,7 +185,7 @@ func TestSafeTransferFrom(t *testing.T) { dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) uassert.True(t, dummy != nil, "should not be nil") - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") dummy.mint(caller, TokenID("1")) diff --git a/examples/gno.land/p/demo/grc/grc721/grc721_metadata.gno b/examples/gno.land/p/demo/grc/grc721/grc721_metadata.gno index 5735ebeecee..7b1cb11ca86 100644 --- a/examples/gno.land/p/demo/grc/grc721/grc721_metadata.gno +++ b/examples/gno.land/p/demo/grc/grc721/grc721_metadata.gno @@ -34,7 +34,7 @@ func (s *metadataNFT) SetTokenMetadata(tid TokenID, metadata Metadata) error { if err != nil { return err } - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() if caller != owner { return ErrCallerIsNotOwner } diff --git a/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno b/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno index 4861fa93f2f..df13ae76d20 100644 --- a/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno +++ b/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno @@ -45,7 +45,7 @@ func (r *royaltyNFT) SetTokenRoyalty(tid TokenID, royaltyInfo RoyaltyInfo) error if err != nil { return err } - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() if caller != owner { return ErrCallerIsNotOwner } diff --git a/examples/gno.land/p/demo/memeland/memeland.gno b/examples/gno.land/p/demo/memeland/memeland.gno index 589b37e4459..55a8dbb27b1 100644 --- a/examples/gno.land/p/demo/memeland/memeland.gno +++ b/examples/gno.land/p/demo/memeland/memeland.gno @@ -50,7 +50,7 @@ func (m *Memeland) PostMeme(data string, timestamp int64) string { newPost := &Post{ ID: id, Data: data, - Author: std.PreviousRealm().Addr(), + Author: std.PreviousRealm().Address(), Timestamp: time.Unix(timestamp, 0), UpvoteTracker: avl.NewTree(), } @@ -65,7 +65,7 @@ func (m *Memeland) Upvote(id string) string { panic("post with specified ID does not exist") } - caller := std.PreviousRealm().Addr().String() + caller := std.PreviousRealm().Address().String() if _, exists := post.UpvoteTracker.Get(caller); exists { panic("user has already upvoted this post") diff --git a/examples/gno.land/p/demo/ownable/exts/authorizable/authorizable.gno b/examples/gno.land/p/demo/ownable/exts/authorizable/authorizable.gno index f05325736da..ef2428ab2ed 100644 --- a/examples/gno.land/p/demo/ownable/exts/authorizable/authorizable.gno +++ b/examples/gno.land/p/demo/ownable/exts/authorizable/authorizable.gno @@ -72,7 +72,7 @@ func (a *Authorizable) DeleteFromAuthList(addr std.Address) error { } func (a Authorizable) CallerOnAuthList() error { - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() if !a.authorized.Has(caller.String()) { return ErrNotInAuthList @@ -82,7 +82,7 @@ func (a Authorizable) CallerOnAuthList() error { } func (a Authorizable) AssertOnAuthList() { - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() if !a.authorized.Has(caller.String()) { panic(ErrNotInAuthList) diff --git a/examples/gno.land/p/demo/ownable/ownable.gno b/examples/gno.land/p/demo/ownable/ownable.gno index 26e1c0d5cc1..132aba42f8e 100644 --- a/examples/gno.land/p/demo/ownable/ownable.gno +++ b/examples/gno.land/p/demo/ownable/ownable.gno @@ -13,7 +13,7 @@ type Ownable struct { func New() *Ownable { return &Ownable{ - owner: std.PreviousRealm().Addr(), + owner: std.PreviousRealm().Address(), } } @@ -71,12 +71,12 @@ func (o Ownable) Owner() std.Address { // CallerIsOwner checks if the caller of the function is the Realm's owner func (o Ownable) CallerIsOwner() bool { - return std.PreviousRealm().Addr() == o.owner + return std.PreviousRealm().Address() == o.owner } // AssertCallerIsOwner panics if the caller is not the owner func (o Ownable) AssertCallerIsOwner() { - if std.PreviousRealm().Addr() != o.owner { + if std.PreviousRealm().Address() != o.owner { panic(ErrUnauthorized) } } diff --git a/examples/gno.land/p/demo/subscription/lifetime/lifetime.gno b/examples/gno.land/p/demo/subscription/lifetime/lifetime.gno index 801e964b5ac..cbd7fde04a4 100644 --- a/examples/gno.land/p/demo/subscription/lifetime/lifetime.gno +++ b/examples/gno.land/p/demo/subscription/lifetime/lifetime.gno @@ -45,7 +45,7 @@ func (ls *LifetimeSubscription) processSubscription(receiver std.Address) error // Subscribe processes the payment for a lifetime subscription. func (ls *LifetimeSubscription) Subscribe() error { - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() return ls.processSubscription(caller) } diff --git a/examples/gno.land/p/demo/subscription/lifetime/lifetime_test.gno b/examples/gno.land/p/demo/subscription/lifetime/lifetime_test.gno index 9dcd08e604b..eba1f2b5367 100644 --- a/examples/gno.land/p/demo/subscription/lifetime/lifetime_test.gno +++ b/examples/gno.land/p/demo/subscription/lifetime/lifetime_test.gno @@ -22,7 +22,7 @@ func TestLifetimeSubscription(t *testing.T) { err := ls.Subscribe() uassert.NoError(t, err, "Expected ProcessPayment to succeed") - err = ls.HasValidSubscription(std.PreviousRealm().Addr()) + err = ls.HasValidSubscription(std.PreviousRealm().Address()) uassert.NoError(t, err, "Expected Alice to have access") } diff --git a/examples/gno.land/p/demo/subscription/recurring/recurring.gno b/examples/gno.land/p/demo/subscription/recurring/recurring.gno index 0c8f7bec0a8..2582c959bc6 100644 --- a/examples/gno.land/p/demo/subscription/recurring/recurring.gno +++ b/examples/gno.land/p/demo/subscription/recurring/recurring.gno @@ -68,7 +68,7 @@ func (rs *RecurringSubscription) processSubscription(receiver std.Address) error // Subscribe handles the payment for the caller's subscription. func (rs *RecurringSubscription) Subscribe() error { - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() return rs.processSubscription(caller) } diff --git a/examples/gno.land/p/demo/subscription/recurring/recurring_test.gno b/examples/gno.land/p/demo/subscription/recurring/recurring_test.gno index 4b71d681486..c0bfe5f1c98 100644 --- a/examples/gno.land/p/demo/subscription/recurring/recurring_test.gno +++ b/examples/gno.land/p/demo/subscription/recurring/recurring_test.gno @@ -23,10 +23,10 @@ func TestRecurringSubscription(t *testing.T) { err := rs.Subscribe() uassert.NoError(t, err, "Expected ProcessPayment to succeed for Alice") - err = rs.HasValidSubscription(std.PreviousRealm().Addr()) + err = rs.HasValidSubscription(std.PreviousRealm().Address()) uassert.NoError(t, err, "Expected Alice to have access") - expiration, err := rs.GetExpiration(std.PreviousRealm().Addr()) + expiration, err := rs.GetExpiration(std.PreviousRealm().Address()) uassert.NoError(t, err, "Expected to get expiration for Alice") } @@ -53,13 +53,13 @@ func TestRecurringSubscriptionExpiration(t *testing.T) { err := rs.Subscribe() uassert.NoError(t, err, "Expected ProcessPayment to succeed for Alice") - err = rs.HasValidSubscription(std.PreviousRealm().Addr()) + err = rs.HasValidSubscription(std.PreviousRealm().Address()) uassert.NoError(t, err, "Expected Alice to have access") expiration := time.Now().Add(-time.Hour * 2) - rs.subs.Set(std.PreviousRealm().Addr().String(), expiration) + rs.subs.Set(std.PreviousRealm().Address().String(), expiration) - err = rs.HasValidSubscription(std.PreviousRealm().Addr()) + err = rs.HasValidSubscription(std.PreviousRealm().Address()) uassert.Error(t, err, "Expected Alice's subscription to be expired") } @@ -119,16 +119,16 @@ func TestRecurringSubscriptionWithMultiplePayments(t *testing.T) { err := rs.Subscribe() uassert.NoError(t, err, "Expected first ProcessPayment to succeed for Alice") - err = rs.HasValidSubscription(std.PreviousRealm().Addr()) + err = rs.HasValidSubscription(std.PreviousRealm().Address()) uassert.NoError(t, err, "Expected Alice to have access after first payment") expiration := time.Now().Add(-time.Hour * 2) - rs.subs.Set(std.PreviousRealm().Addr().String(), expiration) + rs.subs.Set(std.PreviousRealm().Address().String(), expiration) std.TestSetOriginSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) err = rs.Subscribe() uassert.NoError(t, err, "Expected second ProcessPayment to succeed for Alice") - err = rs.HasValidSubscription(std.PreviousRealm().Addr()) + err = rs.HasValidSubscription(std.PreviousRealm().Address()) uassert.NoError(t, err, "Expected Alice to have access after second payment") } diff --git a/examples/gno.land/p/moul/debug/debug.gno b/examples/gno.land/p/moul/debug/debug.gno index 69659ca0fd6..a950d5a8d89 100644 --- a/examples/gno.land/p/moul/debug/debug.gno +++ b/examples/gno.land/p/moul/debug/debug.gno @@ -50,9 +50,9 @@ func (d Debug) Render(path string) string { Headers: []string{"Key", "Value"}, } table.Append([]string{"`std.CurrentRealm().PkgPath()`", string(std.CurrentRealm().PkgPath())}) - table.Append([]string{"`std.CurrentRealm().Addr()`", string(std.CurrentRealm().Addr())}) + table.Append([]string{"`std.CurrentRealm().Address()`", string(std.CurrentRealm().Address())}) table.Append([]string{"`std.PreviousRealm().PkgPath()`", string(std.PreviousRealm().PkgPath())}) - table.Append([]string{"`std.PreviousRealm().Addr()`", string(std.PreviousRealm().Addr())}) + table.Append([]string{"`std.PreviousRealm().Address()`", string(std.PreviousRealm().Address())}) table.Append([]string{"`std.GetHeight()`", ufmt.Sprintf("%d", std.GetHeight())}) table.Append([]string{"`time.Now().Format(time.RFC3339)`", time.Now().Format(time.RFC3339)}) content += table.String() diff --git a/examples/gno.land/p/moul/debug/z1_filetest.gno b/examples/gno.land/p/moul/debug/z1_filetest.gno index 9aa6791e1c8..83879ad0403 100644 --- a/examples/gno.land/p/moul/debug/z1_filetest.gno +++ b/examples/gno.land/p/moul/debug/z1_filetest.gno @@ -20,9 +20,9 @@ func main() { // | Key | Value | // | --- | --- | // | `std.CurrentRealm().PkgPath()` | | -// | `std.CurrentRealm().Addr()` | g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm | +// | `std.CurrentRealm().Address()` | g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm | // | `std.PreviousRealm().PkgPath()` | | -// | `std.PreviousRealm().Addr()` | g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm | +// | `std.PreviousRealm().Address()` | g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm | // | `std.GetHeight()` | 123 | // | `time.Now().Format(time.RFC3339)` | 2009-02-13T23:31:30Z | // diff --git a/examples/gno.land/p/moul/debug/z2_filetest.gno b/examples/gno.land/p/moul/debug/z2_filetest.gno index 4c14fa94c5b..0cac34704f3 100644 --- a/examples/gno.land/p/moul/debug/z2_filetest.gno +++ b/examples/gno.land/p/moul/debug/z2_filetest.gno @@ -26,9 +26,9 @@ func main() { // | Key | Value | // | --- | --- | // | `std.CurrentRealm().PkgPath()` | | -// | `std.CurrentRealm().Addr()` | g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm | +// | `std.CurrentRealm().Address()` | g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm | // | `std.PreviousRealm().PkgPath()` | | -// | `std.PreviousRealm().Addr()` | g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm | +// | `std.PreviousRealm().Address()` | g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm | // | `std.GetHeight()` | 123 | // | `time.Now().Format(time.RFC3339)` | 2009-02-13T23:31:30Z | // diff --git a/examples/gno.land/p/n2p5/loci/loci.gno b/examples/gno.land/p/n2p5/loci/loci.gno index 7fdd0de5c3d..a50c537a8f4 100644 --- a/examples/gno.land/p/n2p5/loci/loci.gno +++ b/examples/gno.land/p/n2p5/loci/loci.gno @@ -25,10 +25,10 @@ func New() *LociStore { } } -// Set stores a byte slice in the AVL tree using the `std.PreviousRealm().Addr()` +// Set stores a byte slice in the AVL tree using the `std.PreviousRealm().Address()` // string as the key. func (s *LociStore) Set(value []byte) { - key := string(std.PreviousRealm().Addr()) + key := string(std.PreviousRealm().Address()) s.internal.Set(key, value) } diff --git a/examples/gno.land/p/n2p5/mgroup/mgroup.gno b/examples/gno.land/p/n2p5/mgroup/mgroup.gno index 727c1ca1e0b..6e14ca6d38b 100644 --- a/examples/gno.land/p/n2p5/mgroup/mgroup.gno +++ b/examples/gno.land/p/n2p5/mgroup/mgroup.gno @@ -74,7 +74,7 @@ func (g *ManagedGroup) RemoveBackupOwner(addr std.Address) error { // If the caller is not a backup owner, an error is returned. // The caller is automatically added as a member of the group. func (g *ManagedGroup) ClaimOwnership() error { - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() // already owner, skip if caller == g.Owner() { return nil diff --git a/examples/gno.land/r/demo/banktest/README.md b/examples/gno.land/r/demo/banktest/README.md index 6dff58c90c9..3a8f18e026a 100644 --- a/examples/gno.land/r/demo/banktest/README.md +++ b/examples/gno.land/r/demo/banktest/README.md @@ -80,7 +80,7 @@ If the user requested the return of coins... use a std.Banker instance to return any deposited coins to the original sender. ```go - pkgaddr := std.GetOriginPkgAddr() + pkgaddr := std.GetOriginPkgAddress() // TODO: use std.Coins constructors, this isn't generally safe. banker.SendCoins(pkgaddr, caller, send) return "returned!" @@ -94,7 +94,7 @@ Finally, the results are rendered via an ABCI query call when you visit [/r/demo func Render(path string) string { // get realm coins. banker := std.GetBanker(std.BankerTypeReadonly) - coins := banker.GetCoins(std.GetOriginPkgAddr()) + coins := banker.GetCoins(std.GetOriginPkgAddress()) // render res := "" diff --git a/examples/gno.land/r/demo/banktest/banktest.gno b/examples/gno.land/r/demo/banktest/banktest.gno index 359cca93004..8798b5066cb 100644 --- a/examples/gno.land/r/demo/banktest/banktest.gno +++ b/examples/gno.land/r/demo/banktest/banktest.gno @@ -40,7 +40,7 @@ func Deposit(returnDenom string, returnAmount int64) string { // return if any. if returnAmount > 0 { banker := std.GetBanker(std.BankerTypeOriginSend) - pkgaddr := std.GetOriginPkgAddr() + pkgaddr := std.GetOriginPkgAddress() // TODO: use std.Coins constructors, this isn't generally safe. banker.SendCoins(pkgaddr, caller, send) return "returned!" @@ -52,7 +52,7 @@ func Deposit(returnDenom string, returnAmount int64) string { func Render(path string) string { // get realm coins. banker := std.GetBanker(std.BankerTypeReadonly) - coins := banker.GetCoins(std.GetOriginPkgAddr()) + coins := banker.GetCoins(std.GetOriginPkgAddress()) // render res := "" diff --git a/examples/gno.land/r/demo/bar20/bar20.gno b/examples/gno.land/r/demo/bar20/bar20.gno index 979ae7d30de..c4fae79c4d0 100644 --- a/examples/gno.land/r/demo/bar20/bar20.gno +++ b/examples/gno.land/r/demo/bar20/bar20.gno @@ -23,7 +23,7 @@ func init() { } func Faucet() string { - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() if err := adm.Mint(caller, 1_000_000); err != nil { return "error: " + err.Error() } diff --git a/examples/gno.land/r/demo/disperse/disperse.gno b/examples/gno.land/r/demo/disperse/disperse.gno index 3be069f7f4b..62fc842118b 100644 --- a/examples/gno.land/r/demo/disperse/disperse.gno +++ b/examples/gno.land/r/demo/disperse/disperse.gno @@ -7,14 +7,14 @@ import ( ) // Get address of Disperse realm -var realmAddr = std.CurrentRealm().Addr() +var realmAddr = std.CurrentRealm().Address() // DisperseUgnot parses receivers and amounts and sends out ugnot // The function will send out the coins to the addresses and return the leftover coins to the caller // if there are any to return func DisperseUgnot(addresses []std.Address, coins std.Coins) { coinSent := std.OriginSend() - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() banker := std.GetBanker(std.BankerTypeOriginSend) if len(addresses) != len(coins) { @@ -50,7 +50,7 @@ func DisperseUgnot(addresses []std.Address, coins std.Coins) { // Note that it is necessary to approve the realm to spend the tokens before calling this function // see the corresponding filetests for examples func DisperseGRC20(addresses []std.Address, amounts []uint64, symbols []string) { - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() if (len(addresses) != len(amounts)) || (len(amounts) != len(symbols)) { panic(ErrArgLenAndSentLenMismatch) diff --git a/examples/gno.land/r/demo/foo20/foo20.gno b/examples/gno.land/r/demo/foo20/foo20.gno index 75158cb56f9..4de59c38eb2 100644 --- a/examples/gno.land/r/demo/foo20/foo20.gno +++ b/examples/gno.land/r/demo/foo20/foo20.gno @@ -60,7 +60,7 @@ func TransferFrom(from, to pusers.AddressOrName, amount uint64) { // Faucet is distributing foo20 tokens without restriction (unsafe). // For a real token faucet, you should take care of setting limits are asking payment. func Faucet() { - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() amount := uint64(1_000 * 10_000) // 1k checkErr(privateLedger.Mint(caller, amount)) } diff --git a/examples/gno.land/r/demo/foo721/foo721.gno b/examples/gno.land/r/demo/foo721/foo721.gno index 00af042a465..b88647101ca 100644 --- a/examples/gno.land/r/demo/foo721/foo721.gno +++ b/examples/gno.land/r/demo/foo721/foo721.gno @@ -87,7 +87,7 @@ func TransferFrom(from, to pusers.AddressOrName, tid grc721.TokenID) { // Admin func Mint(to pusers.AddressOrName, tid grc721.TokenID) { - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() assertIsAdmin(caller) err := foo.Mint(users.Resolve(to), tid) if err != nil { @@ -96,7 +96,7 @@ func Mint(to pusers.AddressOrName, tid grc721.TokenID) { } func Burn(tid grc721.TokenID) { - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() assertIsAdmin(caller) err := foo.Burn(tid) if err != nil { diff --git a/examples/gno.land/r/demo/games/dice_roller/dice_roller.gno b/examples/gno.land/r/demo/games/dice_roller/dice_roller.gno index 9bc7c31f375..18ead8e5fa3 100644 --- a/examples/gno.land/r/demo/games/dice_roller/dice_roller.gno +++ b/examples/gno.land/r/demo/games/dice_roller/dice_roller.gno @@ -62,7 +62,7 @@ func NewGame(addr std.Address) int { } games.Set(gameId.Next().String(), &game{ - player1: std.PreviousRealm().Addr(), + player1: std.PreviousRealm().Address(), player2: addr, }) @@ -79,7 +79,7 @@ func Play(idx int) int { roll := rollDice() // Random the player's dice roll // Play the game and update the player's roll - if err := g.play(std.PreviousRealm().Addr(), roll); err != nil { + if err := g.play(std.PreviousRealm().Address(), roll); err != nil { panic(err) } diff --git a/examples/gno.land/r/demo/games/shifumi/shifumi.gno b/examples/gno.land/r/demo/games/shifumi/shifumi.gno index 7881fdb9765..7ce3eb69653 100644 --- a/examples/gno.land/r/demo/games/shifumi/shifumi.gno +++ b/examples/gno.land/r/demo/games/shifumi/shifumi.gno @@ -63,7 +63,7 @@ func (g *game) winner() int { // NewGame creates a new game where player1 is the caller and player2 the argument. // A new game index is returned. func NewGame(player std.Address) int { - games.Set(id.Next().String(), &game{player1: std.PreviousRealm().Addr(), player2: player}) + games.Set(id.Next().String(), &game{player1: std.PreviousRealm().Address(), player2: player}) return int(id) } @@ -74,7 +74,7 @@ func Play(idx, move int) { if !ok { panic("game not found") } - if err := v.(*game).play(std.PreviousRealm().Addr(), move); err != nil { + if err := v.(*game).play(std.PreviousRealm().Address(), move); err != nil { panic(err) } } diff --git a/examples/gno.land/r/demo/grc20factory/grc20factory.gno b/examples/gno.land/r/demo/grc20factory/grc20factory.gno index caff31cd3b8..e0ccd2d0737 100644 --- a/examples/gno.land/r/demo/grc20factory/grc20factory.gno +++ b/examples/gno.land/r/demo/grc20factory/grc20factory.gno @@ -21,7 +21,7 @@ type instance struct { } func New(name, symbol string, decimals uint, initialMint, faucet uint64) { - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() NewWithAdmin(name, symbol, decimals, initialMint, faucet, caller) } @@ -77,21 +77,21 @@ func Allowance(symbol string, owner, spender std.Address) uint64 { func Transfer(symbol string, to std.Address, amount uint64) { inst := mustGetInstance(symbol) - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() teller := inst.ledger.ImpersonateTeller(caller) checkErr(teller.Transfer(to, amount)) } func Approve(symbol string, spender std.Address, amount uint64) { inst := mustGetInstance(symbol) - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() teller := inst.ledger.ImpersonateTeller(caller) checkErr(teller.Approve(spender, amount)) } func TransferFrom(symbol string, from, to std.Address, amount uint64) { inst := mustGetInstance(symbol) - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() teller := inst.ledger.ImpersonateTeller(caller) checkErr(teller.TransferFrom(from, to, amount)) } @@ -104,7 +104,7 @@ func Faucet(symbol string) { } // FIXME: add limits? // FIXME: add payment in gnot? - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() checkErr(inst.ledger.Mint(caller, inst.faucet)) } diff --git a/examples/gno.land/r/demo/grc20reg/grc20reg_test.gno b/examples/gno.land/r/demo/grc20reg/grc20reg_test.gno index c93365ff7a1..35aad13abcf 100644 --- a/examples/gno.land/r/demo/grc20reg/grc20reg_test.gno +++ b/examples/gno.land/r/demo/grc20reg/grc20reg_test.gno @@ -13,7 +13,7 @@ func TestRegistry(t *testing.T) { std.TestSetRealm(std.NewCodeRealm("gno.land/r/demo/foo")) realmAddr := std.CurrentRealm().PkgPath() token, ledger := grc20.NewToken("TestToken", "TST", 4) - ledger.Mint(std.CurrentRealm().Addr(), 1234567) + ledger.Mint(std.CurrentRealm().Address(), 1234567) tokenGetter := func() *grc20.Token { return token } // register Register(tokenGetter, "") diff --git a/examples/gno.land/r/demo/profile/profile.gno b/examples/gno.land/r/demo/profile/profile.gno index f5c8aa363ed..6e0baa10929 100644 --- a/examples/gno.land/r/demo/profile/profile.gno +++ b/examples/gno.land/r/demo/profile/profile.gno @@ -70,7 +70,7 @@ var boolFields = map[string]bool{ // Setters func SetStringField(field, value string) bool { - addr := std.PreviousRealm().Addr() + addr := std.PreviousRealm().Address() key := addr.String() + ":" + field updated := fields.Set(key, value) @@ -85,7 +85,7 @@ func SetStringField(field, value string) bool { } func SetIntField(field string, value int) bool { - addr := std.PreviousRealm().Addr() + addr := std.PreviousRealm().Address() key := addr.String() + ":" + field updated := fields.Set(key, value) @@ -100,7 +100,7 @@ func SetIntField(field string, value int) bool { } func SetBoolField(field string, value bool) bool { - addr := std.PreviousRealm().Addr() + addr := std.PreviousRealm().Address() key := addr.String() + ":" + field updated := fields.Set(key, value) diff --git a/examples/gno.land/r/demo/tests/tests_test.gno b/examples/gno.land/r/demo/tests/tests_test.gno index 476212f2387..ac73ac09450 100644 --- a/examples/gno.land/r/demo/tests/tests_test.gno +++ b/examples/gno.land/r/demo/tests/tests_test.gno @@ -48,11 +48,11 @@ func TestPreviousRealm(t *testing.T) { rTestsAddr = std.DerivePkgAddr("gno.land/r/demo/tests") ) // When a single realm in the frames, PreviousRealm returns the user - if addr := GetPreviousRealm().Addr(); addr != user1Addr { + if addr := GetPreviousRealm().Address(); addr != user1Addr { t.Errorf("want GetPreviousRealm().Addr==%s, got %s", user1Addr, addr) } // When 2 or more realms in the frames, PreviousRealm returns the second to last - if addr := GetRSubtestsPreviousRealm().Addr(); addr != rTestsAddr { + if addr := GetRSubtestsPreviousRealm().Address(); addr != rTestsAddr { t.Errorf("want GetRSubtestsPreviousRealm().Addr==%s, got %s", rTestsAddr, addr) } } diff --git a/examples/gno.land/r/demo/tests/z2_filetest.gno b/examples/gno.land/r/demo/tests/z2_filetest.gno index b40a9dbfe90..bad59e0a32b 100644 --- a/examples/gno.land/r/demo/tests/z2_filetest.gno +++ b/examples/gno.land/r/demo/tests/z2_filetest.gno @@ -15,10 +15,10 @@ func main() { rTestsAddr = std.DerivePkgAddr("gno.land/r/demo/tests") ) std.TestSetOriginCaller(eoa) - println("tests.GetPreviousRealm().Addr(): ", tests.GetPreviousRealm().Addr()) - println("tests.GetRSubtestsPreviousRealm().Addr(): ", tests.GetRSubtestsPreviousRealm().Addr()) + println("tests.GetPreviousRealm().Address(): ", tests.GetPreviousRealm().Address()) + println("tests.GetRSubtestsPreviousRealm().Address(): ", tests.GetRSubtestsPreviousRealm().Address()) } // Output: -// tests.GetPreviousRealm().Addr(): g1wdhk6et0dej47h6lta047h6lta047h6lrnerlk -// tests.GetRSubtestsPreviousRealm().Addr(): g1gz4ycmx0s6ln2wdrsh4e00l9fsel2wskqa3snq +// tests.GetPreviousRealm().Address(): g1wdhk6et0dej47h6lta047h6lta047h6lrnerlk +// tests.GetRSubtestsPreviousRealm().Address(): g1gz4ycmx0s6ln2wdrsh4e00l9fsel2wskqa3snq diff --git a/examples/gno.land/r/demo/tests/z3_filetest.gno b/examples/gno.land/r/demo/tests/z3_filetest.gno index 2f6256b15bf..430486e2818 100644 --- a/examples/gno.land/r/demo/tests/z3_filetest.gno +++ b/examples/gno.land/r/demo/tests/z3_filetest.gno @@ -15,11 +15,11 @@ func main() { ) std.TestSetOriginCaller(eoa) // Contrarily to z2_filetest.gno we EXPECT GetPreviousRealms != eoa (#1704) - if addr := tests.GetPreviousRealm().Addr(); addr != eoa { + if addr := tests.GetPreviousRealm().Address(); addr != eoa { println("want tests.GetPreviousRealm().Addr ==", eoa, "got", addr) } // When 2 or more realms in the frames, it is also different - if addr := tests.GetRSubtestsPreviousRealm().Addr(); addr != rTestsAddr { + if addr := tests.GetRSubtestsPreviousRealm().Address(); addr != rTestsAddr { println("want GetRSubtestsPreviousRealm().Addr ==", rTestsAddr, "got", addr) } } diff --git a/examples/gno.land/r/demo/userbook/userbook.gno b/examples/gno.land/r/demo/userbook/userbook.gno index c5f7ef5ca8c..412f9410318 100644 --- a/examples/gno.land/r/demo/userbook/userbook.gno +++ b/examples/gno.land/r/demo/userbook/userbook.gno @@ -30,7 +30,7 @@ func init() { func SignUp() string { // Get transaction caller - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() // Check if the user is already signed up if _, exists := tracker.Get(caller.String()); exists { diff --git a/examples/gno.land/r/demo/wugnot/wugnot.gno b/examples/gno.land/r/demo/wugnot/wugnot.gno index 4e95f20dd58..228cf288d5a 100644 --- a/examples/gno.land/r/demo/wugnot/wugnot.gno +++ b/examples/gno.land/r/demo/wugnot/wugnot.gno @@ -24,7 +24,7 @@ func init() { } func Deposit() { - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() sent := std.OriginSend() amount := sent.AmountOf("ugnot") @@ -36,8 +36,8 @@ func Deposit() { func Withdraw(amount uint64) { require(amount >= wugnotMinDeposit, ufmt.Sprintf("Deposit below minimum: %d/%d wugnot.", amount, wugnotMinDeposit)) - caller := std.PreviousRealm().Addr() - pkgaddr := std.CurrentRealm().Addr() + caller := std.PreviousRealm().Address() + pkgaddr := std.CurrentRealm().Address() callerBal := Token.BalanceOf(caller) require(amount <= callerBal, ufmt.Sprintf("Insufficient balance: %d available, %d needed.", callerBal, amount)) diff --git a/examples/gno.land/r/docs/buttons/buttons.gno b/examples/gno.land/r/docs/buttons/buttons.gno index 6d3d4416a7f..129ffc149e2 100644 --- a/examples/gno.land/r/docs/buttons/buttons.gno +++ b/examples/gno.land/r/docs/buttons/buttons.gno @@ -14,7 +14,7 @@ var ( func UpdateMOTD(newmotd string) { motd = newmotd - lastCaller = std.PreviousRealm().Addr() + lastCaller = std.PreviousRealm().Address() } func Render(path string) string { diff --git a/examples/gno.land/r/gnoland/blog/admin.gno b/examples/gno.land/r/gnoland/blog/admin.gno index 7c04d030c76..664b905fba8 100644 --- a/examples/gno.land/r/gnoland/blog/admin.gno +++ b/examples/gno.land/r/gnoland/blog/admin.gno @@ -43,7 +43,7 @@ func AdminRemoveModerator(addr std.Address) { func NewPostExecutor(slug, title, body, publicationDate, authors, tags string) dao.Executor { callback := func() error { - addPost(std.PreviousRealm().Addr(), slug, title, body, publicationDate, authors, tags) + addPost(std.PreviousRealm().Address(), slug, title, body, publicationDate, authors, tags) return nil } diff --git a/examples/gno.land/r/gnoland/faucet/faucet.gno b/examples/gno.land/r/gnoland/faucet/faucet.gno index 0297e25ca86..b039401dfa8 100644 --- a/examples/gno.land/r/gnoland/faucet/faucet.gno +++ b/examples/gno.land/r/gnoland/faucet/faucet.gno @@ -43,7 +43,7 @@ func Transfer(to std.Address, send int64) string { gTotalTransfers++ banker := std.GetBanker(std.BankerTypeRealmSend) - pkgaddr := std.CurrentRealm().Addr() + pkgaddr := std.CurrentRealm().Address() banker.SendCoins(pkgaddr, to, sendCoins) return "" } @@ -54,7 +54,7 @@ func GetPerTransferLimit() int64 { func Render(_ string) string { banker := std.GetBanker(std.BankerTypeRealmSend) - balance := banker.GetCoins(std.CurrentRealm().Addr()) + balance := banker.GetCoins(std.CurrentRealm().Address()) output := gMessage if gInPause { @@ -65,7 +65,7 @@ func Render(_ string) string { output += ufmt.Sprintf("Balance: %s.\n", balance.String()) output += ufmt.Sprintf("Total transfers: %s (in %d times).\n\n", gTotalTransferred.String(), gTotalTransfers) - output += "Package address: " + std.CurrentRealm().Addr().String() + "\n\n" + output += "Package address: " + std.CurrentRealm().Address().String() + "\n\n" output += ufmt.Sprintf("Admin: %s\n\n ", gAdminAddr.String()) output += ufmt.Sprintf("Controllers:\n\n ") diff --git a/examples/gno.land/r/gnoland/monit/monit.gno b/examples/gno.land/r/gnoland/monit/monit.gno index 92f8973e190..7c74e2f5733 100644 --- a/examples/gno.land/r/gnoland/monit/monit.gno +++ b/examples/gno.land/r/gnoland/monit/monit.gno @@ -29,7 +29,7 @@ var ( func Incr() int { counter++ lastUpdate = time.Now() - lastCaller = std.PreviousRealm().Addr() + lastCaller = std.PreviousRealm().Address() wd.Alive() return counter } @@ -40,7 +40,7 @@ func Reset() { Ownable.AssertCallerIsOwner() counter = 0 - lastCaller = std.PreviousRealm().Addr() + lastCaller = std.PreviousRealm().Address() lastUpdate = time.Now() wd = watchdog.Watchdog{Duration: 5 * time.Minute} } diff --git a/examples/gno.land/r/leon/config/config.gno b/examples/gno.land/r/leon/config/config.gno index acacc6c9b0e..690f033997a 100644 --- a/examples/gno.land/r/leon/config/config.gno +++ b/examples/gno.land/r/leon/config/config.gno @@ -52,7 +52,7 @@ func SetBackup(a std.Address) error { } func checkAuthorized() error { - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() isAuthorized := caller == main || caller == backup if !isAuthorized { diff --git a/examples/gno.land/r/leon/hof/hof.gno b/examples/gno.land/r/leon/hof/hof.gno index 9b995aed160..e7c0c4f15fb 100644 --- a/examples/gno.land/r/leon/hof/hof.gno +++ b/examples/gno.land/r/leon/hof/hof.gno @@ -89,7 +89,7 @@ func Upvote(pkgpath string) { } item := rawItem.(*Item) - caller := std.PreviousRealm().Addr().String() + caller := std.PreviousRealm().Address().String() if item.upvote.Has(caller) { panic(ErrDoubleUpvote.Error()) @@ -105,7 +105,7 @@ func Downvote(pkgpath string) { } item := rawItem.(*Item) - caller := std.PreviousRealm().Addr().String() + caller := std.PreviousRealm().Address().String() if item.downvote.Has(caller) { panic(ErrDoubleDownvote.Error()) diff --git a/examples/gno.land/r/leon/home/home.gno b/examples/gno.land/r/leon/home/home.gno index 64181aa9de2..9f03041b63a 100644 --- a/examples/gno.land/r/leon/home/home.gno +++ b/examples/gno.land/r/leon/home/home.gno @@ -39,7 +39,7 @@ TODO import r/gh } func UpdatePFP(url, caption string) { - if !isAuthorized(std.PreviousRealm().Addr()) { + if !isAuthorized(std.PreviousRealm().Address()) { panic(config.ErrUnauthorized) } @@ -48,7 +48,7 @@ func UpdatePFP(url, caption string) { } func UpdateAboutMe(col1, col2 string) { - if !isAuthorized(std.PreviousRealm().Addr()) { + if !isAuthorized(std.PreviousRealm().Address()) { panic(config.ErrUnauthorized) } diff --git a/examples/gno.land/r/matijamarjanovic/home/home.gno b/examples/gno.land/r/matijamarjanovic/home/home.gno index bcb4f85ac44..6041c43f9d9 100644 --- a/examples/gno.land/r/matijamarjanovic/home/home.gno +++ b/examples/gno.land/r/matijamarjanovic/home/home.gno @@ -109,7 +109,7 @@ func CollectBalance() { banker := std.GetBanker(std.BankerTypeRealmSend) ownerAddr := Address() - banker.SendCoins(std.CurrentRealm().Addr(), ownerAddr, banker.GetCoins(std.CurrentRealm().Addr())) + banker.SendCoins(std.CurrentRealm().Address(), ownerAddr, banker.GetCoins(std.CurrentRealm().Address())) } func Render(path string) string { diff --git a/examples/gno.land/r/morgan/guestbook/guestbook.gno b/examples/gno.land/r/morgan/guestbook/guestbook.gno index 1aab054ea6f..4930b0af209 100644 --- a/examples/gno.land/r/morgan/guestbook/guestbook.gno +++ b/examples/gno.land/r/morgan/guestbook/guestbook.gno @@ -47,18 +47,18 @@ func Sign(message string) { switch { case !prev.IsUser(): panic(errNotAUser) - case hasSigned.Has(prev.Addr().String()): + case hasSigned.Has(prev.Address().String()): panic(errAlreadySigned) } message = validateMessage(message) guestbook.Set(signatureID.Next().Binary(), Signature{ Message: message, - Author: prev.Addr(), + Author: prev.Address(), // NOTE: time.Now() will yield the "block time", which is deterministic. Time: time.Now(), }) - hasSigned.Set(prev.Addr().String(), struct{}{}) + hasSigned.Set(prev.Address().String(), struct{}{}) } func validateMessage(msg string) string { diff --git a/examples/gno.land/r/moul/home/z2_filetest.gno b/examples/gno.land/r/moul/home/z2_filetest.gno index 23ee8ff882e..5005652d627 100644 --- a/examples/gno.land/r/moul/home/z2_filetest.gno +++ b/examples/gno.land/r/moul/home/z2_filetest.gno @@ -62,9 +62,9 @@ func main() { // | Key | Value | // | --- | --- | // | `std.CurrentRealm().PkgPath()` | gno.land/r/moul/home | -// | `std.CurrentRealm().Addr()` | g1h8h57ntxadcze3f703skymfzdwa6t3ugf0nq3z | +// | `std.CurrentRealm().Address()` | g1h8h57ntxadcze3f703skymfzdwa6t3ugf0nq3z | // | `std.PreviousRealm().PkgPath()` | | -// | `std.PreviousRealm().Addr()` | g1manfred47kzduec920z88wfr64ylksmdcedlf5 | +// | `std.PreviousRealm().Address()` | g1manfred47kzduec920z88wfr64ylksmdcedlf5 | // | `std.GetHeight()` | 123 | // | `time.Now().Format(time.RFC3339)` | 2009-02-13T23:31:30Z | // diff --git a/examples/gno.land/r/n2p5/home/home.gno b/examples/gno.land/r/n2p5/home/home.gno index cb025ccdd6b..d99ec4d3b7d 100644 --- a/examples/gno.land/r/n2p5/home/home.gno +++ b/examples/gno.land/r/n2p5/home/home.gno @@ -52,7 +52,7 @@ func Render(path string) string { // assertAdmin panics if the caller is not an admin as defined in the config realm. func assertAdmin() { - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() if !config.IsAdmin(caller) { panic("forbidden: must be admin") } diff --git a/examples/gno.land/r/n2p5/loci/loci.gno b/examples/gno.land/r/n2p5/loci/loci.gno index f1eb731398b..232de1e6459 100644 --- a/examples/gno.land/r/n2p5/loci/loci.gno +++ b/examples/gno.land/r/n2p5/loci/loci.gno @@ -23,7 +23,7 @@ func Set(value string) { panic(err) } store.Set(b) - std.Emit("SetValue", "ForAddr", string(std.PreviousRealm().Addr())) + std.Emit("SetValue", "ForAddr", string(std.PreviousRealm().Address())) } // Get retrieves the value stored at the provided address and diff --git a/examples/gno.land/r/stefann/home/home.gno b/examples/gno.land/r/stefann/home/home.gno index 0aea795c4ca..7e3ff40184e 100644 --- a/examples/gno.land/r/stefann/home/home.gno +++ b/examples/gno.land/r/stefann/home/home.gno @@ -152,7 +152,7 @@ func Donate() { banker := std.GetBanker(std.BankerTypeRealmSend) ownerAddr := registry.MainAddr() - banker.SendCoins(std.CurrentRealm().Addr(), ownerAddr, banker.GetCoins(std.CurrentRealm().Addr())) + banker.SendCoins(std.CurrentRealm().Address(), ownerAddr, banker.GetCoins(std.CurrentRealm().Address())) } type SponsorSlice []Sponsor diff --git a/gno.land/genesis/genesis_txs.jsonl b/gno.land/genesis/genesis_txs.jsonl index 1eaa3543773..f0bc99268f5 100644 --- a/gno.land/genesis/genesis_txs.jsonl +++ b/gno.land/genesis/genesis_txs.jsonl @@ -10,7 +10,7 @@ {"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateBoard","args":["testboard"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"vzlSxEFh5jOkaSdv3rsV91v/OJKEF2qSuoCpri1u5tRWq62T7xr3KHRCF5qFnn4aQX/yE8g8f/Y//WPOCUGhJw=="}],"memo":""}} {"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","Hello World","This is a demo of Gno smart contract programming. This document was\nconstructed by Gno onto a smart contract hosted on the data Realm \nname [\"gno.land/r/demo/boards\"](https://gno.land/r/demo/boards/)\n([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)).\n\n## Starting the `gnoland` node node/validator.\n\nNOTE: Where you see `--remote %%REMOTE%%` here, that flag can be replaced\nwith `--remote localhost:26657` for local testnets.\n\n### build gnoland.\n\n```bash\ngit clone git@github.com:gnolang/gno.git\ncd ./gno\nmake \n```\n\n### add test account.\n\n```bash\n./build/gnokey add test1 --recover\n```\n\nUse this mnemonic:\n\u003e source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast\n\n### start gnoland validator node.\n\n```bash\n./build/gnoland\n```\n\n(This can be reset with `make reset`).\n\n### start gnoland web server (optional).\n\n```bash\ngo run ./gnoland/website\n```\n\n## Signing and broadcasting transactions.\n\n### publish the \"gno.land/p/demo/avl\" package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/p/demo/avl\" --pkgdir \"examples/gno.land/p/demo/avl\" --deposit 100ugnot --gas-fee 1ugnot --gas-wanted 2000000 \u003e addpkg.avl.unsigned.txt\n./build/gnokey query \"auth/accounts/g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\"\n./build/gnokey sign test1 --txpath addpkg.avl.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 0 \u003e addpkg.avl.signed.txt\n./build/gnokey broadcast addpkg.avl.signed.txt --remote %%REMOTE%%\n```\n\n### publish the \"gno.land/r/demo/boards\" realm package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/r/demo/boards\" --pkgdir \"examples/gno.land/r/demo/boards\" --deposit 100ugnot --gas-fee 1ugnot --gas-wanted 300000000 \u003e addpkg.boards.unsigned.txt\n./build/gnokey sign test1 --txpath addpkg.boards.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 1 \u003e addpkg.boards.signed.txt\n./build/gnokey broadcast addpkg.boards.signed.txt --remote %%REMOTE%%\n```\n\n### create a board with a smart contract call.\n\n```bash\n./build/gnokey maketx call test1 --pkgpath \"gno.land/r/demo/boards\" --func CreateBoard --args \"testboard\" --gas-fee 1ugnot --gas-wanted 2000000 \u003e createboard.unsigned.txt\n./build/gnokey sign test1 --txpath createboard.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 2 \u003e createboard.signed.txt\n./build/gnokey broadcast createboard.signed.txt --remote %%REMOTE%%\n```\nNext, query for the permanent board ID by querying (you need this to create a new post):\n\n```bash\n./build/gnokey query \"vm/qeval\" --data \"gno.land/r/demo/boards.GetBoardIDFromName(\\\"testboard\\\")\"\n```\n\n### create a post of a board with a smart contract call.\n\n```bash\n./build/gnokey maketx call test1 --pkgpath \"gno.land/r/demo/boards\" --func CreatePost --args 1 --args \"Hello World\" --args#file \"./examples/gno.land/r/demo/boards/README.md\" --gas-fee 1ugnot --gas-wanted 2000000 \u003e createpost.unsigned.txt\n./build/gnokey sign test1 --txpath createpost.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 3 \u003e createpost.signed.txt\n./build/gnokey broadcast createpost.signed.txt --remote %%REMOTE%%\n```\n\n### create a comment to a post.\n\n```bash\n./build/gnokey maketx call test1 --pkgpath \"gno.land/r/demo/boards\" --func CreateReply --args 1 --args 1 --args \"A comment\" --gas-fee 1ugnot --gas-wanted 2000000 \u003e createcomment.unsigned.txt\n./build/gnokey sign test1 --txpath createcomment.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 4 \u003e createcomment.signed.txt\n./build/gnokey broadcast createcomment.signed.txt --remote %%REMOTE%%\n```\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards:testboard/1\"\n```\n\n### render page with optional path expression.\n\nThe contents of `https://gno.land/r/demo/boards:` and `https://gno.land/r/demo/boards:testboard` are rendered by calling\nthe `Render(path string)` function like so:\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards:testboard\"\n```\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"V43B1waFxhzheW9TfmCpjLdrC4dC1yjUGES5y3J6QsNar6hRpNz4G1thzWmWK7xXhg8u1PCIpxLxGczKQYhuPw=="}],"memo":""}} {"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","NFT example","NFT's are all the rage these days, for various reasons.\n\nI read over EIP-721 which appears to be the de-facto NFT standard on Ethereum. Then, made a sample implementation of EIP-721 (let's here called GRC-721). The implementation isn't complete, but it demonstrates the main functionality.\n\n - [EIP-721](https://eips.ethereum.org/EIPS/eip-721)\n - [gno.land/r/demo/nft/nft.gno](https://gno.land/r/demo/nft/nft.gno)\n - [zrealm_nft3.gno test](https://github.com/gnolang/gno/blob/master/examples/gno.land/r/demo/nft/z_3_filetest.gno)\n\nIn short, this demonstrates how to implement Ethereum contract interfaces in gno.land; by using only standard Go language features.\n\nPlease leave a comment ([guide](https://gno.land/r/demo/boards:testboard/1)).\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"ZXfrTiHxPFQL8uSm+Tv7WXIHPMca9okhm94RAlC6YgNbB1VHQYYpoP4w+cnL3YskVzGrOZxensXa9CAZ+cNNeg=="}],"memo":""}} -{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","Simple echo example with coins","This is a simple test realm contract that demonstrates how to use the banker.\n\nSee [gno.land/r/demo/banktest/banktest.gno](/r/demo/banktest/banktest.gno) to see the original contract code.\n\nThis article will go through each line to explain how it works.\n\n```go\npackage banktest\n```\n\nThis package is locally named \"banktest\" (could be anything).\n\n```go\nimport (\n\t\"std\"\n)\n```\n\nThe \"std\" package is defined by the gno code in stdlibs/std/. \u003c/br\u003e\nSelf explanatory; and you'll see more usage from std later.\n\n```go\ntype activity struct {\n\tcaller std.Address\n\tsent std.Coins\n\treturned std.Coins\n\ttime std.Time\n}\n\nfunc (act *activity) String() string {\n\treturn act.caller.String() + \" \" +\n\t\tact.sent.String() + \" sent, \" +\n\t\tact.returned.String() + \" returned, at \" +\n\t\tstd.FormatTimestamp(act.time, \"2006-01-02 3:04pm MST\")\n}\n\nvar latest [10]*activity\n```\n\nThis is just maintaining a list of recent activity to this contract.\nNotice that the \"latest\" variable is defined \"globally\" within\nthe context of the realm with path \"gno.land/r/demo/banktest\".\n\nThis means that calls to functions defined within this package\nare encapsulated within this \"data realm\", where the data is \nmutated based on transactions that can potentially cross many\nrealm and non-realm packge boundaries (in the call stack).\n\n```go\n// Deposit will take the coins (to the realm's pkgaddr) or return them to user.\nfunc Deposit(returnDenom string, returnAmount int64) string {\n\tstd.AssertOriginCall()\n\tcaller := std.OriginCaller()\n\tsend := std.Coins{{returnDenom, returnAmount}}\n```\n\nThis is the beginning of the definition of the contract function named\n\"Deposit\". `std.AssertOriginCall() asserts that this function was called by a\ngno transactional Message. The caller is the user who signed off on this\ntransactional message. Send is the amount of deposit sent along with this\nmessage.\n\n```go\n\t// record activity\n\tact := \u0026activity{\n\t\tcaller: caller,\n\t\tsent: std.OriginSend(),\n\t\treturned: send,\n\t\ttime: std.GetTimestamp(),\n\t}\n\tfor i := len(latest) - 2; i \u003e= 0; i-- {\n\t\tlatest[i+1] = latest[i] // shift by +1.\n\t}\n\tlatest[0] = act\n```\n\nUpdating the \"latest\" array for viewing at gno.land/r/demo/banktest: (w/ trailing colon).\n\n```go\n\t// return if any.\n\tif returnAmount \u003e 0 {\n```\n\nIf the user requested the return of coins...\n\n```go\n\t\tbanker := std.GetBanker(std.BankerTypeOriginSend)\n```\n\nuse a std.Banker instance to return any deposited coins to the original sender.\n\n```go\n\t\tpkgaddr := std.GetOriginPkgAddr()\n\t\t// TODO: use std.Coins constructors, this isn't generally safe.\n\t\tbanker.SendCoins(pkgaddr, caller, send)\n\t\treturn \"returned!\"\n```\n\nNotice that each realm package has an associated Cosmos address.\n\n\nFinally, the results are rendered via an ABCI query call when you visit [/r/demo/banktest:](/r/demo/banktest:).\n\n```go\nfunc Render(path string) string {\n\t// get realm coins.\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tcoins := banker.GetCoins(std.GetOriginPkgAddr())\n\n\t// render\n\tres := \"\"\n\tres += \"## recent activity\\n\"\n\tres += \"\\n\"\n\tfor _, act := range latest {\n\t\tif act == nil {\n\t\t\tbreak\n\t\t}\n\t\tres += \" * \" + act.String() + \"\\n\"\n\t}\n\tres += \"\\n\"\n\tres += \"## total deposits\\n\"\n\tres += coins.String()\n\treturn res\n}\n```\n\nYou can call this contract yourself, by vistiing [/r/demo/banktest](/r/demo/banktest) and the [quickstart guide](/r/demo/boards:testboard/4).\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"iZX/llZlNTdZMLv1goCTgK2bWqzT8enlTq56wMTCpVxJGA0BTvuEM5Nnt9vrnlG6Taqj2GuTrmEnJBkDFTmt9g=="}],"memo":""}} +{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","Simple echo example with coins","This is a simple test realm contract that demonstrates how to use the banker.\n\nSee [gno.land/r/demo/banktest/banktest.gno](/r/demo/banktest/banktest.gno) to see the original contract code.\n\nThis article will go through each line to explain how it works.\n\n```go\npackage banktest\n```\n\nThis package is locally named \"banktest\" (could be anything).\n\n```go\nimport (\n\t\"std\"\n)\n```\n\nThe \"std\" package is defined by the gno code in stdlibs/std/. \u003c/br\u003e\nSelf explanatory; and you'll see more usage from std later.\n\n```go\ntype activity struct {\n\tcaller std.Address\n\tsent std.Coins\n\treturned std.Coins\n\ttime std.Time\n}\n\nfunc (act *activity) String() string {\n\treturn act.caller.String() + \" \" +\n\t\tact.sent.String() + \" sent, \" +\n\t\tact.returned.String() + \" returned, at \" +\n\t\tstd.FormatTimestamp(act.time, \"2006-01-02 3:04pm MST\")\n}\n\nvar latest [10]*activity\n```\n\nThis is just maintaining a list of recent activity to this contract.\nNotice that the \"latest\" variable is defined \"globally\" within\nthe context of the realm with path \"gno.land/r/demo/banktest\".\n\nThis means that calls to functions defined within this package\nare encapsulated within this \"data realm\", where the data is \nmutated based on transactions that can potentially cross many\nrealm and non-realm packge boundaries (in the call stack).\n\n```go\n// Deposit will take the coins (to the realm's pkgaddr) or return them to user.\nfunc Deposit(returnDenom string, returnAmount int64) string {\n\tstd.AssertOriginCall()\n\tcaller := std.OriginCaller()\n\tsend := std.Coins{{returnDenom, returnAmount}}\n```\n\nThis is the beginning of the definition of the contract function named\n\"Deposit\". `std.AssertOriginCall() asserts that this function was called by a\ngno transactional Message. The caller is the user who signed off on this\ntransactional message. Send is the amount of deposit sent along with this\nmessage.\n\n```go\n\t// record activity\n\tact := \u0026activity{\n\t\tcaller: caller,\n\t\tsent: std.OriginSend(),\n\t\treturned: send,\n\t\ttime: std.GetTimestamp(),\n\t}\n\tfor i := len(latest) - 2; i \u003e= 0; i-- {\n\t\tlatest[i+1] = latest[i] // shift by +1.\n\t}\n\tlatest[0] = act\n```\n\nUpdating the \"latest\" array for viewing at gno.land/r/demo/banktest: (w/ trailing colon).\n\n```go\n\t// return if any.\n\tif returnAmount \u003e 0 {\n```\n\nIf the user requested the return of coins...\n\n```go\n\t\tbanker := std.GetBanker(std.BankerTypeOriginSend)\n```\n\nuse a std.Banker instance to return any deposited coins to the original sender.\n\n```go\n\t\tpkgaddr := std.GetOriginPkgAddress()\n\t\t// TODO: use std.Coins constructors, this isn't generally safe.\n\t\tbanker.SendCoins(pkgaddr, caller, send)\n\t\treturn \"returned!\"\n```\n\nNotice that each realm package has an associated Cosmos address.\n\n\nFinally, the results are rendered via an ABCI query call when you visit [/r/demo/banktest:](/r/demo/banktest:).\n\n```go\nfunc Render(path string) string {\n\t// get realm coins.\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tcoins := banker.GetCoins(std.GetOriginPkgAddress())\n\n\t// render\n\tres := \"\"\n\tres += \"## recent activity\\n\"\n\tres += \"\\n\"\n\tfor _, act := range latest {\n\t\tif act == nil {\n\t\t\tbreak\n\t\t}\n\t\tres += \" * \" + act.String() + \"\\n\"\n\t}\n\tres += \"\\n\"\n\tres += \"## total deposits\\n\"\n\tres += coins.String()\n\treturn res\n}\n```\n\nYou can call this contract yourself, by vistiing [/r/demo/banktest](/r/demo/banktest) and the [quickstart guide](/r/demo/boards:testboard/4).\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"iZX/llZlNTdZMLv1goCTgK2bWqzT8enlTq56wMTCpVxJGA0BTvuEM5Nnt9vrnlG6Taqj2GuTrmEnJBkDFTmt9g=="}],"memo":""}} {"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","TASK: Describe in your words","Describe in an essay (250+ words), on your favorite medium, why you are interested in gno.land and gnolang.\n\nReply here with a URL link to your written piece as a comment, for rewards.\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AmG6kzznyo1uNqWPAYU6wDpsmzQKDaEOrVRaZ08vOyX0"},"signature":"4HBNtrta8HdeHj4JTN56PBTRK8GOe31NMRRXDiyYtjozuyRdWfOGEsGjGgHWcoBUJq6DepBgD4FetdqfhZ6TNQ=="}],"memo":""}} {"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1manfred47kzduec920z88wfr64ylksmdcedlf5","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","Getting Started","This is a demo of Gno smart contract programming. This document was\nconstructed by Gno onto a smart contract hosted on the data Realm\nname [\"gno.land/r/demo/boards\"](https://gno.land/r/demo/boards/)\n([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)).\n\n\n\n## Build `gnokey`, create your account, and interact with Gno.\n\nNOTE: Where you see `--remote %%REMOTE%%` here, that flag can be replaced\nwith `--remote localhost:26657` for local testnets.\n\n### Build `gnokey`.\n\n```bash\ngit clone git@github.com:gnolang/gno.git\ncd ./gno\nmake\n```\n\n### Generate a seed/mnemonic code.\n\n```bash\n./build/gnokey generate\n```\n\nNOTE: You can generate 24 words with any good bip39 generator.\n\n### Create a new account using your mnemonic.\n\n```bash\n./build/gnokey add KEYNAME --recover\n```\n\nNOTE: `KEYNAME` is your key identifier, and should be changed.\n\n### Verify that you can see your account locally.\n\n```bash\n./build/gnokey list\n```\n\n## Interact with the blockchain:\n\n### Get your current balance, account number, and sequence number.\n\n```bash\n./build/gnokey query auth/accounts/ACCOUNT_ADDR --remote %%REMOTE%%\n```\n\nNOTE: you can retrieve your `ACCOUNT_ADDR` with `./build/gnokey list`.\n\n### Acquire testnet tokens using the official faucet.\n\nGo to https://gno.land/faucet\n\n### Create a board with a smart contract call.\n\nNOTE: `BOARDNAME` will be the slug of the board, and should be changed.\n\n```bash\n./build/gnokey maketx call KEYNAME --pkgpath \"gno.land/r/demo/boards\" --func \"CreateBoard\" --args \"BOARDNAME\" --gas-fee \"1000000ugnot\" --gas-wanted \"2000000\" --broadcast=true --chainid %%CHAINID%% --remote %%REMOTE%%\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateBoard\n\nNext, query for the permanent board ID by querying (you need this to create a new post):\n\n```bash\n./build/gnokey query \"vm/qeval\" --data \"gno.land/r/demo/boards.GetBoardIDFromName(\\\"BOARDNAME\\\")\" --remote %%REMOTE%%\n```\n\n### Create a post of a board with a smart contract call.\n\nNOTE: If a board was created successfully, your SEQUENCE_NUMBER would have increased.\n\n```bash\n./build/gnokey maketx call KEYNAME --pkgpath \"gno.land/r/demo/boards\" --func \"CreateThread\" --args BOARD_ID --args \"Hello gno.land\" --args\\#file \"./examples/gno.land/r/demo/boards/example_post.md\" --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast=true --chainid %%CHAINID%% --remote %%REMOTE%%\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateThread\n\n### Create a comment to a post.\n\n```bash\n./build/gnokey maketx call KEYNAME --pkgpath \"gno.land/r/demo/boards\" --func \"CreateReply\" --args \"BOARD_ID\" --args \"1\" --args \"1\" --args \"Nice to meet you too.\" --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast=true --chainid %%CHAINID%% --remote %%REMOTE%%\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateReply\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards:BOARDNAME/1\" --remote %%REMOTE%%\n```\n\n### Render page with optional path expression.\n\nThe contents of `https://gno.land/r/demo/boards:` and `https://gno.land/r/demo/boards:gnolang` are rendered by calling\nthe `Render(path string)` function like so:\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards:gnolang\"\n```\n\n## Starting a local `gnoland` node:\n\n### Add test account.\n\n```bash\n./build/gnokey add test1 --recover\n```\n\nUse this mneonic:\n\u003e source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast\n\n### Start `gnoland` node.\n\n```bash\n./build/gnoland\n```\n\nNOTE: This can be reset with `make reset`\n\n### Publish the \"gno.land/p/demo/avl\" package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/p/demo/avl\" --pkgdir \"examples/gno.land/p/demo/avl\" --deposit 100000000ugnot --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast=true --chainid %%CHAINID%% --remote localhost:26657\n```\n\n### Publish the \"gno.land/r/demo/boards\" realm package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/r/demo/boards\" --pkgdir \"examples/gno.land/r/demo/boards\" --deposit 100000000ugnot --gas-fee 1000000ugnot --gas-wanted 300000000 --broadcast=true --chainid %%CHAINID%% --remote localhost:26657\n```\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AnK+a6mcFDjY6b/v6p7r8QFW1M1PgIoQxBgrwOoyY7v3"},"signature":"sHjOGXZEi9wt2FSXFHmkDDoVQyepvFHKRDDU0zgedHYnCYPx5/YndyihsDD5Y2Z7/RgNYBh4JlJwDMGFNStzBQ=="}],"memo":""}} {"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1manfred47kzduec920z88wfr64ylksmdcedlf5","send":"","pkg_path":"gno.land/r/gnoland/blog","func":"ModAddPost","args":["post1","First post","Lorem Ipsum","2022-05-20T13:17:22Z","","tag1,tag2"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AnK+a6mcFDjY6b/v6p7r8QFW1M1PgIoQxBgrwOoyY7v3"},"signature":"sHjOGXZEi9wt2FSXFHmkDDoVQyepvFHKRDDU0zgedHYnCYPx5/YndyihsDD5Y2Z7/RgNYBh4JlJwDMGFNStzBQ=="}],"memo":""}} diff --git a/gno.land/pkg/integration/testdata/grc20_registry.txtar b/gno.land/pkg/integration/testdata/grc20_registry.txtar index edf8a6b90a3..162e977b8aa 100644 --- a/gno.land/pkg/integration/testdata/grc20_registry.txtar +++ b/gno.land/pkg/integration/testdata/grc20_registry.txtar @@ -34,7 +34,7 @@ func TransferByName(name string, to string, amount uint64) string { if pair.name != name { continue } - if std.CurrentRealm().Addr().String() != pair.cb(std.Address(to), amount) { + if std.CurrentRealm().Address().String() != pair.cb(std.Address(to), amount) { return "invalid address, ownership issue :(" } return "same address, success!" @@ -58,6 +58,6 @@ package foo20 import "std" func Transfer(to std.Address, amount uint64) string { - println("transfer from=" + std.PreviousRealm().Addr().String() + " to=" + to.String() + " some-amount") - return std.PreviousRealm().Addr().String() + println("transfer from=" + std.PreviousRealm().Address().String() + " to=" + to.String() + " some-amount") + return std.PreviousRealm().Address().String() } diff --git a/gno.land/pkg/integration/testdata/grc721_emit.txtar b/gno.land/pkg/integration/testdata/grc721_emit.txtar index 4829c6ac5d1..741325793fc 100644 --- a/gno.land/pkg/integration/testdata/grc721_emit.txtar +++ b/gno.land/pkg/integration/testdata/grc721_emit.txtar @@ -69,7 +69,7 @@ func TransferFrom(from, to pusers.AddressOrName, tid grc721.TokenID) { // Admin func Mint(to pusers.AddressOrName, tid grc721.TokenID) { - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() assertIsAdmin(caller) err := foo.Mint(users.Resolve(to), tid) if err != nil { @@ -78,7 +78,7 @@ func Mint(to pusers.AddressOrName, tid grc721.TokenID) { } func Burn(tid grc721.TokenID) { - caller := std.PreviousRealm().Addr() + caller := std.PreviousRealm().Address() assertIsAdmin(caller) err := foo.Burn(tid) if err != nil { diff --git a/gno.land/pkg/integration/testdata/initctx.txtar b/gno.land/pkg/integration/testdata/initctx.txtar index cd825aac1f3..82e27dba642 100644 --- a/gno.land/pkg/integration/testdata/initctx.txtar +++ b/gno.land/pkg/integration/testdata/initctx.txtar @@ -19,7 +19,7 @@ var prev = std.Address("prev") func init() { orig = std.OriginCaller() - prev = std.PreviousRealm().Addr() + prev = std.PreviousRealm().Address() } func Render(addr string) string { diff --git a/gno.land/pkg/integration/testdata/issue_1786.txtar b/gno.land/pkg/integration/testdata/issue_1786.txtar index 9e789c723b3..1c4383ab71e 100644 --- a/gno.land/pkg/integration/testdata/issue_1786.txtar +++ b/gno.land/pkg/integration/testdata/issue_1786.txtar @@ -60,7 +60,7 @@ func ProxyWrap() { // WRAP IT wugnotAddr := std.DerivePkgAddr("gno.land/r/demo/wugnot") banker := std.GetBanker(std.BankerTypeRealmSend) - banker.SendCoins(std.CurrentRealm().Addr(), wugnotAddr, std.Coins{{"ugnot", int64(ugnotSent)}}) + banker.SendCoins(std.CurrentRealm().Address(), wugnotAddr, std.Coins{{"ugnot", int64(ugnotSent)}}) wugnot.Deposit() // `proxywugnot` has ugnot // SEND WUGNOT: PROXY_WUGNOT -> USER @@ -73,12 +73,12 @@ func ProxyUnwrap(wugnotAmount uint64) { } // SEND WUGNOT: USER -> PROXY_WUGNOT - wugnot.TransferFrom(pusers.AddressOrName(std.OriginCaller()), pusers.AddressOrName(std.CurrentRealm().Addr()), wugnotAmount) + wugnot.TransferFrom(pusers.AddressOrName(std.OriginCaller()), pusers.AddressOrName(std.CurrentRealm().Address()), wugnotAmount) // UNWRAP IT wugnot.Withdraw(wugnotAmount) // SEND GNOT: PROXY_WUGNOT -> USER banker := std.GetBanker(std.BankerTypeRealmSend) - banker.SendCoins(std.CurrentRealm().Addr(), std.OriginCaller(), std.Coins{{"ugnot", int64(wugnotAmount)}}) + banker.SendCoins(std.CurrentRealm().Address(), std.OriginCaller(), std.Coins{{"ugnot", int64(wugnotAmount)}}) } diff --git a/gno.land/pkg/integration/testdata/issue_2283.txtar b/gno.land/pkg/integration/testdata/issue_2283.txtar index e53a8cc1a31..17c1fba98ce 100644 --- a/gno.land/pkg/integration/testdata/issue_2283.txtar +++ b/gno.land/pkg/integration/testdata/issue_2283.txtar @@ -66,11 +66,11 @@ import ( }, { "Name": "post.gno", - "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n)\n\ntype PostID uint64\n\nfunc (pid PostID) String() string {\n\treturn strconv.Itoa(int(pid))\n}\n\nfunc (pid *PostID) FromJSON(ast *ujson.JSONASTNode) {\n\tval, err := strconv.Atoi(ast.Value)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t*pid = PostID(val)\n}\n\nfunc (pid PostID) ToJSON() string {\n\treturn strconv.Itoa(int(pid))\n}\n\ntype Reaction struct {\n\ticon string\n\tcount uint64\n}\n\nvar Categories []string = []string{\n\t\"Reaction\",\n\t\"Comment\",\n\t\"Normal\",\n\t\"Article\",\n\t\"Picture\",\n\t\"Audio\",\n\t\"Video\",\n}\n\ntype Post struct {\n\tid PostID\n\tparentID PostID\n\tfeedID FeedID\n\tcategory uint64\n\tmetadata string\n\treactions avl.Tree // icon -> count\n\tcomments avl.Tree // Post.id -> *Post\n\tcreator std.Address\n\ttipAmount uint64\n\tdeleted bool\n\tcommentsCount uint64\n\n\tcreatedAt int64\n\tupdatedAt int64\n\tdeletedAt int64\n}\n\nfunc newPost(feed *Feed, id PostID, creator std.Address, parentID PostID, category uint64, metadata string) *Post {\n\treturn &Post{\n\t\tid: id,\n\t\tparentID: parentID,\n\t\tfeedID: feed.id,\n\t\tcategory: category,\n\t\tmetadata: metadata,\n\t\treactions: avl.Tree{},\n\t\tcreator: creator,\n\t\tcreatedAt: time.Now().Unix(),\n\t}\n}\n\nfunc (post *Post) String() string {\n\treturn post.ToJSON()\n}\n\nfunc (post *Post) Update(category uint64, metadata string) {\n\tpost.category = category\n\tpost.metadata = metadata\n\tpost.updatedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Delete() {\n\tpost.deleted = true\n\tpost.deletedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Tip(from std.Address, to std.Address) {\n\treceivedCoins := std.OriginSend()\n\tamount := receivedCoins[0].Amount\n\n\tbanker := std.GetBanker(std.BankerTypeOriginSend)\n\t// banker := std.GetBanker(std.BankerTypeRealmSend)\n\tcoinsToSend := std.Coins{std.Coin{Denom: \"ugnot\", Amount: amount}}\n\tpkgaddr := std.GetOriginPkgAddr()\n\n\tbanker.SendCoins(pkgaddr, to, coinsToSend)\n\n\t// Update tip amount\n\tpost.tipAmount += uint64(amount)\n}\n\n// Always remove reaction if count = 0\nfunc (post *Post) React(icon string, up bool) {\n\tcount_, ok := post.reactions.Get(icon)\n\tcount := 0\n\n\tif ok {\n\t\tcount = count_.(int)\n\t}\n\n\tif up {\n\t\tcount++\n\t} else {\n\t\tcount--\n\t}\n\n\tif count <= 0 {\n\t\tpost.reactions.Remove(icon)\n\t} else {\n\t\tpost.reactions.Set(icon, count)\n\t}\n}\n\nfunc (post *Post) Render() string {\n\treturn post.metadata\n}\n\nfunc (post *Post) FromJSON(jsonData string) {\n\tast := ujson.TokenizeAndParse(jsonData)\n\tast.ParseObject([]*ujson.ParseKV{\n\t\t{Key: \"id\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.id = PostID(pid)\n\t\t}},\n\t\t{Key: \"parentID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.parentID = PostID(pid)\n\t\t}},\n\t\t{Key: \"feedID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tfid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.feedID = FeedID(fid)\n\t\t}},\n\t\t{Key: \"category\", Value: &post.category},\n\t\t{Key: \"metadata\", Value: &post.metadata},\n\t\t{Key: \"reactions\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\treactions := avl.NewTree()\n\t\t\tfor _, child := range node.ObjectChildren {\n\t\t\t\treactionCount := child.Value\n\t\t\t\treactions.Set(child.Key, reactionCount)\n\t\t\t}\n\t\t\tpost.reactions = *reactions\n\t\t}},\n\t\t{Key: \"commentsCount\", Value: &post.commentsCount},\n\t\t{Key: \"creator\", Value: &post.creator},\n\t\t{Key: \"tipAmount\", Value: &post.tipAmount},\n\t\t{Key: \"deleted\", Value: &post.deleted},\n\t\t{Key: \"createdAt\", Value: &post.createdAt},\n\t\t{Key: \"updatedAt\", Value: &post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: &post.deletedAt},\n\t})\n}\n\nfunc (post *Post) ToJSON() string {\n\treactionsKV := []ujson.FormatKV{}\n\tpost.reactions.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tcount := value.(int)\n\t\tdata := ujson.FormatKV{Key: key, Value: count}\n\t\treactionsKV = append(reactionsKV, data)\n\t\treturn false\n\t})\n\treactions := ujson.FormatObject(reactionsKV)\n\n\tpostJSON := ujson.FormatObject([]ujson.FormatKV{\n\t\t{Key: \"id\", Value: uint64(post.id)},\n\t\t{Key: \"parentID\", Value: uint64(post.parentID)},\n\t\t{Key: \"feedID\", Value: uint64(post.feedID)},\n\t\t{Key: \"category\", Value: post.category},\n\t\t{Key: \"metadata\", Value: post.metadata},\n\t\t{Key: \"reactions\", Value: reactions, Raw: true},\n\t\t{Key: \"creator\", Value: post.creator},\n\t\t{Key: \"tipAmount\", Value: post.tipAmount},\n\t\t{Key: \"deleted\", Value: post.deleted},\n\t\t{Key: \"commentsCount\", Value: post.commentsCount},\n\t\t{Key: \"createdAt\", Value: post.createdAt},\n\t\t{Key: \"updatedAt\", Value: post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: post.deletedAt},\n\t})\n\treturn postJSON\n}\n" + "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n)\n\ntype PostID uint64\n\nfunc (pid PostID) String() string {\n\treturn strconv.Itoa(int(pid))\n}\n\nfunc (pid *PostID) FromJSON(ast *ujson.JSONASTNode) {\n\tval, err := strconv.Atoi(ast.Value)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t*pid = PostID(val)\n}\n\nfunc (pid PostID) ToJSON() string {\n\treturn strconv.Itoa(int(pid))\n}\n\ntype Reaction struct {\n\ticon string\n\tcount uint64\n}\n\nvar Categories []string = []string{\n\t\"Reaction\",\n\t\"Comment\",\n\t\"Normal\",\n\t\"Article\",\n\t\"Picture\",\n\t\"Audio\",\n\t\"Video\",\n}\n\ntype Post struct {\n\tid PostID\n\tparentID PostID\n\tfeedID FeedID\n\tcategory uint64\n\tmetadata string\n\treactions avl.Tree // icon -> count\n\tcomments avl.Tree // Post.id -> *Post\n\tcreator std.Address\n\ttipAmount uint64\n\tdeleted bool\n\tcommentsCount uint64\n\n\tcreatedAt int64\n\tupdatedAt int64\n\tdeletedAt int64\n}\n\nfunc newPost(feed *Feed, id PostID, creator std.Address, parentID PostID, category uint64, metadata string) *Post {\n\treturn &Post{\n\t\tid: id,\n\t\tparentID: parentID,\n\t\tfeedID: feed.id,\n\t\tcategory: category,\n\t\tmetadata: metadata,\n\t\treactions: avl.Tree{},\n\t\tcreator: creator,\n\t\tcreatedAt: time.Now().Unix(),\n\t}\n}\n\nfunc (post *Post) String() string {\n\treturn post.ToJSON()\n}\n\nfunc (post *Post) Update(category uint64, metadata string) {\n\tpost.category = category\n\tpost.metadata = metadata\n\tpost.updatedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Delete() {\n\tpost.deleted = true\n\tpost.deletedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Tip(from std.Address, to std.Address) {\n\treceivedCoins := std.OriginSend()\n\tamount := receivedCoins[0].Amount\n\n\tbanker := std.GetBanker(std.BankerTypeOriginSend)\n\t// banker := std.GetBanker(std.BankerTypeRealmSend)\n\tcoinsToSend := std.Coins{std.Coin{Denom: \"ugnot\", Amount: amount}}\n\tpkgaddr := std.GetOriginPkgAddress()\n\n\tbanker.SendCoins(pkgaddr, to, coinsToSend)\n\n\t// Update tip amount\n\tpost.tipAmount += uint64(amount)\n}\n\n// Always remove reaction if count = 0\nfunc (post *Post) React(icon string, up bool) {\n\tcount_, ok := post.reactions.Get(icon)\n\tcount := 0\n\n\tif ok {\n\t\tcount = count_.(int)\n\t}\n\n\tif up {\n\t\tcount++\n\t} else {\n\t\tcount--\n\t}\n\n\tif count <= 0 {\n\t\tpost.reactions.Remove(icon)\n\t} else {\n\t\tpost.reactions.Set(icon, count)\n\t}\n}\n\nfunc (post *Post) Render() string {\n\treturn post.metadata\n}\n\nfunc (post *Post) FromJSON(jsonData string) {\n\tast := ujson.TokenizeAndParse(jsonData)\n\tast.ParseObject([]*ujson.ParseKV{\n\t\t{Key: \"id\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.id = PostID(pid)\n\t\t}},\n\t\t{Key: \"parentID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.parentID = PostID(pid)\n\t\t}},\n\t\t{Key: \"feedID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tfid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.feedID = FeedID(fid)\n\t\t}},\n\t\t{Key: \"category\", Value: &post.category},\n\t\t{Key: \"metadata\", Value: &post.metadata},\n\t\t{Key: \"reactions\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\treactions := avl.NewTree()\n\t\t\tfor _, child := range node.ObjectChildren {\n\t\t\t\treactionCount := child.Value\n\t\t\t\treactions.Set(child.Key, reactionCount)\n\t\t\t}\n\t\t\tpost.reactions = *reactions\n\t\t}},\n\t\t{Key: \"commentsCount\", Value: &post.commentsCount},\n\t\t{Key: \"creator\", Value: &post.creator},\n\t\t{Key: \"tipAmount\", Value: &post.tipAmount},\n\t\t{Key: \"deleted\", Value: &post.deleted},\n\t\t{Key: \"createdAt\", Value: &post.createdAt},\n\t\t{Key: \"updatedAt\", Value: &post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: &post.deletedAt},\n\t})\n}\n\nfunc (post *Post) ToJSON() string {\n\treactionsKV := []ujson.FormatKV{}\n\tpost.reactions.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tcount := value.(int)\n\t\tdata := ujson.FormatKV{Key: key, Value: count}\n\t\treactionsKV = append(reactionsKV, data)\n\t\treturn false\n\t})\n\treactions := ujson.FormatObject(reactionsKV)\n\n\tpostJSON := ujson.FormatObject([]ujson.FormatKV{\n\t\t{Key: \"id\", Value: uint64(post.id)},\n\t\t{Key: \"parentID\", Value: uint64(post.parentID)},\n\t\t{Key: \"feedID\", Value: uint64(post.feedID)},\n\t\t{Key: \"category\", Value: post.category},\n\t\t{Key: \"metadata\", Value: post.metadata},\n\t\t{Key: \"reactions\", Value: reactions, Raw: true},\n\t\t{Key: \"creator\", Value: post.creator},\n\t\t{Key: \"tipAmount\", Value: post.tipAmount},\n\t\t{Key: \"deleted\", Value: post.deleted},\n\t\t{Key: \"commentsCount\", Value: post.commentsCount},\n\t\t{Key: \"createdAt\", Value: post.createdAt},\n\t\t{Key: \"updatedAt\", Value: post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: post.deletedAt},\n\t})\n\treturn postJSON\n}\n" }, { "Name": "public.gno", - "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/teritori/flags_index\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Only registered user can create a new feed\n// For the flexibility when testing, allow all user to create feed\nfunc CreateFeed(name string) FeedID {\n\tpkgpath := std.CurrentRealmPath()\n\n\tfid := incGetFeedID()\n\tcaller := std.PreviousRealm().Addr()\n\turl := strings.Replace(pkgpath, \"gno.land\", \"\", -1) + \":\" + name\n\tfeed := newFeed(fid, url, name, caller)\n\tfidkey := feedIDKey(fid)\n\tgFeeds.Set(fidkey, feed)\n\tgFeedsByName.Set(name, feed)\n\treturn feed.id\n}\n\n// Anyone can create a post in a existing feed, allow un-registered users also\nfunc CreatePost(fid FeedID, parentID PostID, catetory uint64, metadata string) PostID {\n\tcaller := std.PreviousRealm().Addr()\n\n\tfeed := mustGetFeed(fid)\n\tpost := feed.AddPost(caller, parentID, catetory, metadata)\n\treturn post.id\n}\n\n// Only post's owner can edit post\nfunc EditPost(fid FeedID, pid PostID, category uint64, metadata string) {\n\tcaller := std.PreviousRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tif caller != post.creator {\n\t\tpanic(\"you are not creator of this post\")\n\t}\n\n\tpost.Update(category, metadata)\n}\n\n// Only feed creator/owner can call this\nfunc SetOwner(fid FeedID, newOwner std.Address) {\n\tcaller := std.PreviousRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tif caller != feed.creator && caller != feed.owner {\n\t\tpanic(\"you are not creator/owner of this feed\")\n\t}\n\n\tfeed.owner = newOwner\n}\n\n// Only feed creator/owner or post creator can delete the post\nfunc DeletePost(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tif caller != post.creator && caller != feed.creator && caller != feed.owner {\n\t\tpanic(\"you are nor creator of this post neither creator/owner of the feed\")\n\t}\n\n\tpost.Delete()\n\n\t// If post is comment then decrease comments count on parent\n\tif uint64(post.parentID) != 0 {\n\t\tparent := feed.MustGetPost(post.parentID)\n\t\tparent.commentsCount -= 1\n\t}\n}\n\n// Only feed owner can ban the post\nfunc BanPost(fid FeedID, pid PostID, reason string) {\n\tcaller := std.PreviousRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\t_ = feed.MustGetPost(pid)\n\n\t// For experimenting, we ban only the post for now\n\t// TODO: recursive delete/ban comments\n\tif caller != feed.owner {\n\t\tpanic(\"you are owner of the feed\")\n\t}\n\n\tfeed.BanPost(pid)\n\n\tfeed.flags.ClearFlagCount(getFlagID(fid, pid))\n}\n\n// Any one can react post\nfunc ReactPost(fid FeedID, pid PostID, icon string, up bool) {\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tpost.React(icon, up)\n}\n\nfunc TipPost(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tpost.Tip(caller, post.creator)\n}\n\n// Get a list of flagged posts\n// NOTE: We can support multi feeds in the future but for now we will have only 1 feed\n// Return stringified list in format: postStr-count,postStr-count\nfunc GetFlaggedPosts(fid FeedID, offset uint64, limit uint8) string {\n\tfeed := mustGetFeed(fid)\n\n\t// Already sorted by count descending\n\tflags := feed.flags.GetFlags(uint64(limit), offset)\n\n\tvar postList []string\n\tfor _, flagCount := range flags {\n\t\tflagID := flagCount.FlagID\n\n\t\tfeedID, postID := parseFlagID(flagID)\n\t\tif feedID != feed.id {\n\t\t\tcontinue\n\t\t}\n\n\t\tpost := feed.GetPost(postID)\n\t\tpostList = append(postList, ufmt.Sprintf(\"%s\", post))\n\t}\n\n\tSEPARATOR := \",\"\n\tres := strings.Join(postList, SEPARATOR)\n\treturn ufmt.Sprintf(\"[%s]\", res)\n}\n\n// NOTE: due to bug of std.PreviousRealm().Addr() return \"\" when query so we user this proxy function temporary\n// in waiting of correct behaviour of std.PreviousRealm().Addr()\nfunc GetPosts(fid FeedID, parentID PostID, user string, categories []uint64, offset uint64, limit uint8) string {\n\tcaller := std.PreviousRealm().Addr()\n\tdata := GetPostsWithCaller(fid, parentID, caller.String(), user, categories, offset, limit)\n\treturn data\n}\n\nfunc GetPostsWithCaller(fid FeedID, parentID PostID, callerAddrStr string, user string, categories []uint64, offset uint64, limit uint8) string {\n\t// Return flagged posts, we process flagged posts differently using FlagIndex\n\tif len(categories) == 1 && categories[0] == uint64(9) {\n\t\treturn GetFlaggedPosts(fid, offset, limit)\n\t}\n\n\t// BUG: normally std.PreviousRealm().Addr() should return a value instead of empty\n\t// Fix is in progress on Gno side\n\tfeed := mustGetFeed(fid)\n\tposts := getPosts(feed, parentID, callerAddrStr, user, categories, offset, limit)\n\n\tSEPARATOR := \",\"\n\tvar postListStr []string\n\n\tfor _, post := range posts {\n\t\tpostListStr = append(postListStr, post.String())\n\t}\n\n\tres := strings.Join(postListStr, SEPARATOR)\n\treturn ufmt.Sprintf(\"[%s]\", res)\n}\n\n// user here is: filter by user\nfunc getPosts(feed *Feed, parentID PostID, callerAddrStr string, user string, categories []uint64, offset uint64, limit uint8) []*Post {\n\tcaller := std.Address(callerAddrStr)\n\n\tvar posts []*Post\n\tvar skipped uint64\n\n\t// Create an avlTree for optimizing the check\n\trequestedCategories := avl.NewTree()\n\tfor _, category := range categories {\n\t\tcatStr := strconv.FormatUint(category, 10)\n\t\trequestedCategories.Set(catStr, true)\n\t}\n\n\tfeed.posts.ReverseIterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tpost := value.(*Post)\n\n\t\tpostCatStr := strconv.FormatUint(post.category, 10)\n\n\t\t// NOTE: this search mechanism is not efficient, only for demo purpose\n\t\tif post.parentID == parentID && post.deleted == false {\n\t\t\tif requestedCategories.Size() > 0 && !requestedCategories.Has(postCatStr) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tif user != \"\" && std.Address(user) != post.creator {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t// Filter hidden post\n\t\t\tflagID := getFlagID(feed.id, post.id)\n\t\t\tif feed.flags.HasFlagged(flagID, callerAddrStr) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t// Check if post is in hidden list\n\t\t\tvalue, exists := feed.hiddenPostsByUser.Get(caller.String())\n\t\t\tif exists {\n\t\t\t\thiddenPosts := value.(*avl.Tree)\n\t\t\t\t// If post.id exists in hiddenPosts tree => that post is hidden\n\t\t\t\tif hiddenPosts.Has(post.id.String()) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif skipped < offset {\n\t\t\t\tskipped++\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tposts = append(posts, post)\n\t\t}\n\n\t\tif len(posts) == int(limit) {\n\t\t\treturn true\n\t\t}\n\n\t\treturn false\n\t})\n\n\treturn posts\n}\n\n// Get comments list\nfunc GetComments(fid FeedID, parentID PostID, offset uint64, limit uint8) string {\n\treturn GetPosts(fid, parentID, \"\", []uint64{}, offset, limit)\n}\n\n// Get Post\nfunc GetPost(fid FeedID, pid PostID) string {\n\tfeed := mustGetFeed(fid)\n\n\tdata, ok := feed.posts.Get(postIDKey(pid))\n\tif !ok {\n\t\tpanic(\"Unable to get post\")\n\t}\n\n\tpost := data.(*Post)\n\treturn post.String()\n}\n\nfunc FlagPost(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.FlagPost(caller, pid)\n}\n\nfunc HidePostForMe(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.HidePostForUser(caller, pid)\n}\n\nfunc UnHidePostForMe(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.UnHidePostForUser(caller, pid)\n}\n\nfunc GetFlags(fid FeedID, limit uint64, offset uint64) string {\n\tfeed := mustGetFeed(fid)\n\n\ttype FlagCount struct {\n\t\tFlagID flags_index.FlagID\n\t\tCount uint64\n\t}\n\n\tflags := feed.flags.GetFlags(limit, offset)\n\n\tvar res []string\n\tfor _, flag := range flags {\n\t\tres = append(res, ufmt.Sprintf(\"%s:%d\", flag.FlagID, flag.Count))\n\t}\n\n\treturn strings.Join(res, \"|\")\n}\n\n// TODO: allow only creator to call\nfunc GetFeedByID(fid FeedID) *Feed {\n\treturn mustGetFeed(fid)\n}\n\n// TODO: allow only admin to call\nfunc ExportFeedData(fid FeedID) string {\n\tfeed := mustGetFeed(fid)\n\tfeedJSON := feed.ToJSON()\n\treturn feedJSON\n}\n\n// TODO: allow only admin to call\nfunc ImportFeedData(fid FeedID, jsonData string) {\n\tfeed := mustGetFeed(fid)\n\tfeed.FromJSON(jsonData)\n}\n\n// func MigrateFromPreviousFeed(fid feedsV7.FeedID) {\n// \t// Get exported data from previous feeds\n// \tjsonData := feedsV7.ExportFeedData(fid)\n// \tImportFeedData(FeedID(uint64(fid)), jsonData)\n// }\n" + "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/teritori/flags_index\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Only registered user can create a new feed\n// For the flexibility when testing, allow all user to create feed\nfunc CreateFeed(name string) FeedID {\n\tpkgpath := std.CurrentRealmPath()\n\n\tfid := incGetFeedID()\n\tcaller := std.PreviousRealm().Address()\n\turl := strings.Replace(pkgpath, \"gno.land\", \"\", -1) + \":\" + name\n\tfeed := newFeed(fid, url, name, caller)\n\tfidkey := feedIDKey(fid)\n\tgFeeds.Set(fidkey, feed)\n\tgFeedsByName.Set(name, feed)\n\treturn feed.id\n}\n\n// Anyone can create a post in a existing feed, allow un-registered users also\nfunc CreatePost(fid FeedID, parentID PostID, catetory uint64, metadata string) PostID {\n\tcaller := std.PreviousRealm().Address()\n\n\tfeed := mustGetFeed(fid)\n\tpost := feed.AddPost(caller, parentID, catetory, metadata)\n\treturn post.id\n}\n\n// Only post's owner can edit post\nfunc EditPost(fid FeedID, pid PostID, category uint64, metadata string) {\n\tcaller := std.PreviousRealm().Address()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tif caller != post.creator {\n\t\tpanic(\"you are not creator of this post\")\n\t}\n\n\tpost.Update(category, metadata)\n}\n\n// Only feed creator/owner can call this\nfunc SetOwner(fid FeedID, newOwner std.Address) {\n\tcaller := std.PreviousRealm().Address()\n\tfeed := mustGetFeed(fid)\n\n\tif caller != feed.creator && caller != feed.owner {\n\t\tpanic(\"you are not creator/owner of this feed\")\n\t}\n\n\tfeed.owner = newOwner\n}\n\n// Only feed creator/owner or post creator can delete the post\nfunc DeletePost(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Address()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tif caller != post.creator && caller != feed.creator && caller != feed.owner {\n\t\tpanic(\"you are nor creator of this post neither creator/owner of the feed\")\n\t}\n\n\tpost.Delete()\n\n\t// If post is comment then decrease comments count on parent\n\tif uint64(post.parentID) != 0 {\n\t\tparent := feed.MustGetPost(post.parentID)\n\t\tparent.commentsCount -= 1\n\t}\n}\n\n// Only feed owner can ban the post\nfunc BanPost(fid FeedID, pid PostID, reason string) {\n\tcaller := std.PreviousRealm().Address()\n\tfeed := mustGetFeed(fid)\n\t_ = feed.MustGetPost(pid)\n\n\t// For experimenting, we ban only the post for now\n\t// TODO: recursive delete/ban comments\n\tif caller != feed.owner {\n\t\tpanic(\"you are owner of the feed\")\n\t}\n\n\tfeed.BanPost(pid)\n\n\tfeed.flags.ClearFlagCount(getFlagID(fid, pid))\n}\n\n// Any one can react post\nfunc ReactPost(fid FeedID, pid PostID, icon string, up bool) {\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tpost.React(icon, up)\n}\n\nfunc TipPost(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Address()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tpost.Tip(caller, post.creator)\n}\n\n// Get a list of flagged posts\n// NOTE: We can support multi feeds in the future but for now we will have only 1 feed\n// Return stringified list in format: postStr-count,postStr-count\nfunc GetFlaggedPosts(fid FeedID, offset uint64, limit uint8) string {\n\tfeed := mustGetFeed(fid)\n\n\t// Already sorted by count descending\n\tflags := feed.flags.GetFlags(uint64(limit), offset)\n\n\tvar postList []string\n\tfor _, flagCount := range flags {\n\t\tflagID := flagCount.FlagID\n\n\t\tfeedID, postID := parseFlagID(flagID)\n\t\tif feedID != feed.id {\n\t\t\tcontinue\n\t\t}\n\n\t\tpost := feed.GetPost(postID)\n\t\tpostList = append(postList, ufmt.Sprintf(\"%s\", post))\n\t}\n\n\tSEPARATOR := \",\"\n\tres := strings.Join(postList, SEPARATOR)\n\treturn ufmt.Sprintf(\"[%s]\", res)\n}\n\n// NOTE: due to bug of std.PreviousRealm().Address() return \"\" when query so we user this proxy function temporary\n// in waiting of correct behaviour of std.PreviousRealm().Address()\nfunc GetPosts(fid FeedID, parentID PostID, user string, categories []uint64, offset uint64, limit uint8) string {\n\tcaller := std.PreviousRealm().Address()\n\tdata := GetPostsWithCaller(fid, parentID, caller.String(), user, categories, offset, limit)\n\treturn data\n}\n\nfunc GetPostsWithCaller(fid FeedID, parentID PostID, callerAddrStr string, user string, categories []uint64, offset uint64, limit uint8) string {\n\t// Return flagged posts, we process flagged posts differently using FlagIndex\n\tif len(categories) == 1 && categories[0] == uint64(9) {\n\t\treturn GetFlaggedPosts(fid, offset, limit)\n\t}\n\n\t// BUG: normally std.PreviousRealm().Address() should return a value instead of empty\n\t// Fix is in progress on Gno side\n\tfeed := mustGetFeed(fid)\n\tposts := getPosts(feed, parentID, callerAddrStr, user, categories, offset, limit)\n\n\tSEPARATOR := \",\"\n\tvar postListStr []string\n\n\tfor _, post := range posts {\n\t\tpostListStr = append(postListStr, post.String())\n\t}\n\n\tres := strings.Join(postListStr, SEPARATOR)\n\treturn ufmt.Sprintf(\"[%s]\", res)\n}\n\n// user here is: filter by user\nfunc getPosts(feed *Feed, parentID PostID, callerAddrStr string, user string, categories []uint64, offset uint64, limit uint8) []*Post {\n\tcaller := std.Address(callerAddrStr)\n\n\tvar posts []*Post\n\tvar skipped uint64\n\n\t// Create an avlTree for optimizing the check\n\trequestedCategories := avl.NewTree()\n\tfor _, category := range categories {\n\t\tcatStr := strconv.FormatUint(category, 10)\n\t\trequestedCategories.Set(catStr, true)\n\t}\n\n\tfeed.posts.ReverseIterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tpost := value.(*Post)\n\n\t\tpostCatStr := strconv.FormatUint(post.category, 10)\n\n\t\t// NOTE: this search mechanism is not efficient, only for demo purpose\n\t\tif post.parentID == parentID && post.deleted == false {\n\t\t\tif requestedCategories.Size() > 0 && !requestedCategories.Has(postCatStr) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tif user != \"\" && std.Address(user) != post.creator {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t// Filter hidden post\n\t\t\tflagID := getFlagID(feed.id, post.id)\n\t\t\tif feed.flags.HasFlagged(flagID, callerAddrStr) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t// Check if post is in hidden list\n\t\t\tvalue, exists := feed.hiddenPostsByUser.Get(caller.String())\n\t\t\tif exists {\n\t\t\t\thiddenPosts := value.(*avl.Tree)\n\t\t\t\t// If post.id exists in hiddenPosts tree => that post is hidden\n\t\t\t\tif hiddenPosts.Has(post.id.String()) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif skipped < offset {\n\t\t\t\tskipped++\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tposts = append(posts, post)\n\t\t}\n\n\t\tif len(posts) == int(limit) {\n\t\t\treturn true\n\t\t}\n\n\t\treturn false\n\t})\n\n\treturn posts\n}\n\n// Get comments list\nfunc GetComments(fid FeedID, parentID PostID, offset uint64, limit uint8) string {\n\treturn GetPosts(fid, parentID, \"\", []uint64{}, offset, limit)\n}\n\n// Get Post\nfunc GetPost(fid FeedID, pid PostID) string {\n\tfeed := mustGetFeed(fid)\n\n\tdata, ok := feed.posts.Get(postIDKey(pid))\n\tif !ok {\n\t\tpanic(\"Unable to get post\")\n\t}\n\n\tpost := data.(*Post)\n\treturn post.String()\n}\n\nfunc FlagPost(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Address()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.FlagPost(caller, pid)\n}\n\nfunc HidePostForMe(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Address()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.HidePostForUser(caller, pid)\n}\n\nfunc UnHidePostForMe(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Address()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.UnHidePostForUser(caller, pid)\n}\n\nfunc GetFlags(fid FeedID, limit uint64, offset uint64) string {\n\tfeed := mustGetFeed(fid)\n\n\ttype FlagCount struct {\n\t\tFlagID flags_index.FlagID\n\t\tCount uint64\n\t}\n\n\tflags := feed.flags.GetFlags(limit, offset)\n\n\tvar res []string\n\tfor _, flag := range flags {\n\t\tres = append(res, ufmt.Sprintf(\"%s:%d\", flag.FlagID, flag.Count))\n\t}\n\n\treturn strings.Join(res, \"|\")\n}\n\n// TODO: allow only creator to call\nfunc GetFeedByID(fid FeedID) *Feed {\n\treturn mustGetFeed(fid)\n}\n\n// TODO: allow only admin to call\nfunc ExportFeedData(fid FeedID) string {\n\tfeed := mustGetFeed(fid)\n\tfeedJSON := feed.ToJSON()\n\treturn feedJSON\n}\n\n// TODO: allow only admin to call\nfunc ImportFeedData(fid FeedID, jsonData string) {\n\tfeed := mustGetFeed(fid)\n\tfeed.FromJSON(jsonData)\n}\n\n// func MigrateFromPreviousFeed(fid feedsV7.FeedID) {\n// \t// Get exported data from previous feeds\n// \tjsonData := feedsV7.ExportFeedData(fid)\n// \tImportFeedData(FeedID(uint64(fid)), jsonData)\n// }\n" }, { "Name": "render.gno", diff --git a/gno.land/pkg/integration/testdata/issue_2283_cacheTypes.txtar b/gno.land/pkg/integration/testdata/issue_2283_cacheTypes.txtar index 00359f0caf3..e5dafabf9f0 100644 --- a/gno.land/pkg/integration/testdata/issue_2283_cacheTypes.txtar +++ b/gno.land/pkg/integration/testdata/issue_2283_cacheTypes.txtar @@ -60,11 +60,11 @@ stdout OK! }, { "Name": "post.gno", - "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n)\n\ntype PostID uint64\n\nfunc (pid PostID) String() string {\n\treturn strconv.Itoa(int(pid))\n}\n\nfunc (pid *PostID) FromJSON(ast *ujson.JSONASTNode) {\n\tval, err := strconv.Atoi(ast.Value)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t*pid = PostID(val)\n}\n\nfunc (pid PostID) ToJSON() string {\n\treturn strconv.Itoa(int(pid))\n}\n\ntype Reaction struct {\n\ticon string\n\tcount uint64\n}\n\nvar Categories []string = []string{\n\t\"Reaction\",\n\t\"Comment\",\n\t\"Normal\",\n\t\"Article\",\n\t\"Picture\",\n\t\"Audio\",\n\t\"Video\",\n}\n\ntype Post struct {\n\tid PostID\n\tparentID PostID\n\tfeedID FeedID\n\tcategory uint64\n\tmetadata string\n\treactions avl.Tree // icon -> count\n\tcomments avl.Tree // Post.id -> *Post\n\tcreator std.Address\n\ttipAmount uint64\n\tdeleted bool\n\tcommentsCount uint64\n\n\tcreatedAt int64\n\tupdatedAt int64\n\tdeletedAt int64\n}\n\nfunc newPost(feed *Feed, id PostID, creator std.Address, parentID PostID, category uint64, metadata string) *Post {\n\treturn &Post{\n\t\tid: id,\n\t\tparentID: parentID,\n\t\tfeedID: feed.id,\n\t\tcategory: category,\n\t\tmetadata: metadata,\n\t\treactions: avl.Tree{},\n\t\tcreator: creator,\n\t\tcreatedAt: time.Now().Unix(),\n\t}\n}\n\nfunc (post *Post) String() string {\n\treturn post.ToJSON()\n}\n\nfunc (post *Post) Update(category uint64, metadata string) {\n\tpost.category = category\n\tpost.metadata = metadata\n\tpost.updatedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Delete() {\n\tpost.deleted = true\n\tpost.deletedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Tip(from std.Address, to std.Address) {\n\treceivedCoins := std.OriginSend()\n\tamount := receivedCoins[0].Amount\n\n\tbanker := std.GetBanker(std.BankerTypeOriginSend)\n\t// banker := std.GetBanker(std.BankerTypeRealmSend)\n\tcoinsToSend := std.Coins{std.Coin{Denom: \"ugnot\", Amount: amount}}\n\tpkgaddr := std.GetOriginPkgAddr()\n\n\tbanker.SendCoins(pkgaddr, to, coinsToSend)\n\n\t// Update tip amount\n\tpost.tipAmount += uint64(amount)\n}\n\n// Always remove reaction if count = 0\nfunc (post *Post) React(icon string, up bool) {\n\tcount_, ok := post.reactions.Get(icon)\n\tcount := 0\n\n\tif ok {\n\t\tcount = count_.(int)\n\t}\n\n\tif up {\n\t\tcount++\n\t} else {\n\t\tcount--\n\t}\n\n\tif count <= 0 {\n\t\tpost.reactions.Remove(icon)\n\t} else {\n\t\tpost.reactions.Set(icon, count)\n\t}\n}\n\nfunc (post *Post) Render() string {\n\treturn post.metadata\n}\n\nfunc (post *Post) FromJSON(jsonData string) {\n\tast := ujson.TokenizeAndParse(jsonData)\n\tast.ParseObject([]*ujson.ParseKV{\n\t\t{Key: \"id\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.id = PostID(pid)\n\t\t}},\n\t\t{Key: \"parentID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.parentID = PostID(pid)\n\t\t}},\n\t\t{Key: \"feedID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tfid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.feedID = FeedID(fid)\n\t\t}},\n\t\t{Key: \"category\", Value: &post.category},\n\t\t{Key: \"metadata\", Value: &post.metadata},\n\t\t{Key: \"reactions\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\treactions := avl.NewTree()\n\t\t\tfor _, child := range node.ObjectChildren {\n\t\t\t\treactionCount := child.Value\n\t\t\t\treactions.Set(child.Key, reactionCount)\n\t\t\t}\n\t\t\tpost.reactions = *reactions\n\t\t}},\n\t\t{Key: \"commentsCount\", Value: &post.commentsCount},\n\t\t{Key: \"creator\", Value: &post.creator},\n\t\t{Key: \"tipAmount\", Value: &post.tipAmount},\n\t\t{Key: \"deleted\", Value: &post.deleted},\n\t\t{Key: \"createdAt\", Value: &post.createdAt},\n\t\t{Key: \"updatedAt\", Value: &post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: &post.deletedAt},\n\t})\n}\n\nfunc (post *Post) ToJSON() string {\n\treactionsKV := []ujson.FormatKV{}\n\tpost.reactions.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tcount := value.(int)\n\t\tdata := ujson.FormatKV{Key: key, Value: count}\n\t\treactionsKV = append(reactionsKV, data)\n\t\treturn false\n\t})\n\treactions := ujson.FormatObject(reactionsKV)\n\n\tpostJSON := ujson.FormatObject([]ujson.FormatKV{\n\t\t{Key: \"id\", Value: uint64(post.id)},\n\t\t{Key: \"parentID\", Value: uint64(post.parentID)},\n\t\t{Key: \"feedID\", Value: uint64(post.feedID)},\n\t\t{Key: \"category\", Value: post.category},\n\t\t{Key: \"metadata\", Value: post.metadata},\n\t\t{Key: \"reactions\", Value: reactions, Raw: true},\n\t\t{Key: \"creator\", Value: post.creator},\n\t\t{Key: \"tipAmount\", Value: post.tipAmount},\n\t\t{Key: \"deleted\", Value: post.deleted},\n\t\t{Key: \"commentsCount\", Value: post.commentsCount},\n\t\t{Key: \"createdAt\", Value: post.createdAt},\n\t\t{Key: \"updatedAt\", Value: post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: post.deletedAt},\n\t})\n\treturn postJSON\n}\n" + "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n)\n\ntype PostID uint64\n\nfunc (pid PostID) String() string {\n\treturn strconv.Itoa(int(pid))\n}\n\nfunc (pid *PostID) FromJSON(ast *ujson.JSONASTNode) {\n\tval, err := strconv.Atoi(ast.Value)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t*pid = PostID(val)\n}\n\nfunc (pid PostID) ToJSON() string {\n\treturn strconv.Itoa(int(pid))\n}\n\ntype Reaction struct {\n\ticon string\n\tcount uint64\n}\n\nvar Categories []string = []string{\n\t\"Reaction\",\n\t\"Comment\",\n\t\"Normal\",\n\t\"Article\",\n\t\"Picture\",\n\t\"Audio\",\n\t\"Video\",\n}\n\ntype Post struct {\n\tid PostID\n\tparentID PostID\n\tfeedID FeedID\n\tcategory uint64\n\tmetadata string\n\treactions avl.Tree // icon -> count\n\tcomments avl.Tree // Post.id -> *Post\n\tcreator std.Address\n\ttipAmount uint64\n\tdeleted bool\n\tcommentsCount uint64\n\n\tcreatedAt int64\n\tupdatedAt int64\n\tdeletedAt int64\n}\n\nfunc newPost(feed *Feed, id PostID, creator std.Address, parentID PostID, category uint64, metadata string) *Post {\n\treturn &Post{\n\t\tid: id,\n\t\tparentID: parentID,\n\t\tfeedID: feed.id,\n\t\tcategory: category,\n\t\tmetadata: metadata,\n\t\treactions: avl.Tree{},\n\t\tcreator: creator,\n\t\tcreatedAt: time.Now().Unix(),\n\t}\n}\n\nfunc (post *Post) String() string {\n\treturn post.ToJSON()\n}\n\nfunc (post *Post) Update(category uint64, metadata string) {\n\tpost.category = category\n\tpost.metadata = metadata\n\tpost.updatedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Delete() {\n\tpost.deleted = true\n\tpost.deletedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Tip(from std.Address, to std.Address) {\n\treceivedCoins := std.OriginSend()\n\tamount := receivedCoins[0].Amount\n\n\tbanker := std.GetBanker(std.BankerTypeOriginSend)\n\t// banker := std.GetBanker(std.BankerTypeRealmSend)\n\tcoinsToSend := std.Coins{std.Coin{Denom: \"ugnot\", Amount: amount}}\n\tpkgaddr := std.GetOriginPkgAddress()\n\n\tbanker.SendCoins(pkgaddr, to, coinsToSend)\n\n\t// Update tip amount\n\tpost.tipAmount += uint64(amount)\n}\n\n// Always remove reaction if count = 0\nfunc (post *Post) React(icon string, up bool) {\n\tcount_, ok := post.reactions.Get(icon)\n\tcount := 0\n\n\tif ok {\n\t\tcount = count_.(int)\n\t}\n\n\tif up {\n\t\tcount++\n\t} else {\n\t\tcount--\n\t}\n\n\tif count <= 0 {\n\t\tpost.reactions.Remove(icon)\n\t} else {\n\t\tpost.reactions.Set(icon, count)\n\t}\n}\n\nfunc (post *Post) Render() string {\n\treturn post.metadata\n}\n\nfunc (post *Post) FromJSON(jsonData string) {\n\tast := ujson.TokenizeAndParse(jsonData)\n\tast.ParseObject([]*ujson.ParseKV{\n\t\t{Key: \"id\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.id = PostID(pid)\n\t\t}},\n\t\t{Key: \"parentID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.parentID = PostID(pid)\n\t\t}},\n\t\t{Key: \"feedID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tfid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.feedID = FeedID(fid)\n\t\t}},\n\t\t{Key: \"category\", Value: &post.category},\n\t\t{Key: \"metadata\", Value: &post.metadata},\n\t\t{Key: \"reactions\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\treactions := avl.NewTree()\n\t\t\tfor _, child := range node.ObjectChildren {\n\t\t\t\treactionCount := child.Value\n\t\t\t\treactions.Set(child.Key, reactionCount)\n\t\t\t}\n\t\t\tpost.reactions = *reactions\n\t\t}},\n\t\t{Key: \"commentsCount\", Value: &post.commentsCount},\n\t\t{Key: \"creator\", Value: &post.creator},\n\t\t{Key: \"tipAmount\", Value: &post.tipAmount},\n\t\t{Key: \"deleted\", Value: &post.deleted},\n\t\t{Key: \"createdAt\", Value: &post.createdAt},\n\t\t{Key: \"updatedAt\", Value: &post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: &post.deletedAt},\n\t})\n}\n\nfunc (post *Post) ToJSON() string {\n\treactionsKV := []ujson.FormatKV{}\n\tpost.reactions.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tcount := value.(int)\n\t\tdata := ujson.FormatKV{Key: key, Value: count}\n\t\treactionsKV = append(reactionsKV, data)\n\t\treturn false\n\t})\n\treactions := ujson.FormatObject(reactionsKV)\n\n\tpostJSON := ujson.FormatObject([]ujson.FormatKV{\n\t\t{Key: \"id\", Value: uint64(post.id)},\n\t\t{Key: \"parentID\", Value: uint64(post.parentID)},\n\t\t{Key: \"feedID\", Value: uint64(post.feedID)},\n\t\t{Key: \"category\", Value: post.category},\n\t\t{Key: \"metadata\", Value: post.metadata},\n\t\t{Key: \"reactions\", Value: reactions, Raw: true},\n\t\t{Key: \"creator\", Value: post.creator},\n\t\t{Key: \"tipAmount\", Value: post.tipAmount},\n\t\t{Key: \"deleted\", Value: post.deleted},\n\t\t{Key: \"commentsCount\", Value: post.commentsCount},\n\t\t{Key: \"createdAt\", Value: post.createdAt},\n\t\t{Key: \"updatedAt\", Value: post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: post.deletedAt},\n\t})\n\treturn postJSON\n}\n" }, { "Name": "public.gno", - "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/teritori/flags_index\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Only registered user can create a new feed\n// For the flexibility when testing, allow all user to create feed\nfunc CreateFeed(name string) FeedID {\n\tpkgpath := std.CurrentRealmPath()\n\n\tfid := incGetFeedID()\n\tcaller := std.PreviousRealm().Addr()\n\turl := strings.Replace(pkgpath, \"gno.land\", \"\", -1) + \":\" + name\n\tfeed := newFeed(fid, url, name, caller)\n\tfidkey := feedIDKey(fid)\n\tgFeeds.Set(fidkey, feed)\n\tgFeedsByName.Set(name, feed)\n\treturn feed.id\n}\n\n// Anyone can create a post in a existing feed, allow un-registered users also\nfunc CreatePost(fid FeedID, parentID PostID, catetory uint64, metadata string) PostID {\n\tcaller := std.PreviousRealm().Addr()\n\n\tfeed := mustGetFeed(fid)\n\tpost := feed.AddPost(caller, parentID, catetory, metadata)\n\treturn post.id\n}\n\n// Only post's owner can edit post\nfunc EditPost(fid FeedID, pid PostID, category uint64, metadata string) {\n\tcaller := std.PreviousRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tif caller != post.creator {\n\t\tpanic(\"you are not creator of this post\")\n\t}\n\n\tpost.Update(category, metadata)\n}\n\n// Only feed creator/owner can call this\nfunc SetOwner(fid FeedID, newOwner std.Address) {\n\tcaller := std.PreviousRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tif caller != feed.creator && caller != feed.owner {\n\t\tpanic(\"you are not creator/owner of this feed\")\n\t}\n\n\tfeed.owner = newOwner\n}\n\n// Only feed creator/owner or post creator can delete the post\nfunc DeletePost(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tif caller != post.creator && caller != feed.creator && caller != feed.owner {\n\t\tpanic(\"you are nor creator of this post neither creator/owner of the feed\")\n\t}\n\n\tpost.Delete()\n\n\t// If post is comment then decrease comments count on parent\n\tif uint64(post.parentID) != 0 {\n\t\tparent := feed.MustGetPost(post.parentID)\n\t\tparent.commentsCount -= 1\n\t}\n}\n\n// Only feed owner can ban the post\nfunc BanPost(fid FeedID, pid PostID, reason string) {\n\tcaller := std.PreviousRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\t_ = feed.MustGetPost(pid)\n\n\t// For experimenting, we ban only the post for now\n\t// TODO: recursive delete/ban comments\n\tif caller != feed.owner {\n\t\tpanic(\"you are owner of the feed\")\n\t}\n\n\tfeed.BanPost(pid)\n\n\tfeed.flags.ClearFlagCount(getFlagID(fid, pid))\n}\n\n// Any one can react post\nfunc ReactPost(fid FeedID, pid PostID, icon string, up bool) {\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tpost.React(icon, up)\n}\n\nfunc TipPost(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tpost.Tip(caller, post.creator)\n}\n\n// Get a list of flagged posts\n// NOTE: We can support multi feeds in the future but for now we will have only 1 feed\n// Return stringified list in format: postStr-count,postStr-count\nfunc GetFlaggedPosts(fid FeedID, offset uint64, limit uint8) string {\n\tfeed := mustGetFeed(fid)\n\n\t// Already sorted by count descending\n\tflags := feed.flags.GetFlags(uint64(limit), offset)\n\n\tvar postList []string\n\tfor _, flagCount := range flags {\n\t\tflagID := flagCount.FlagID\n\n\t\tfeedID, postID := parseFlagID(flagID)\n\t\tif feedID != feed.id {\n\t\t\tcontinue\n\t\t}\n\n\t\tpost := feed.GetPost(postID)\n\t\tpostList = append(postList, ufmt.Sprintf(\"%s\", post))\n\t}\n\n\tSEPARATOR := \",\"\n\tres := strings.Join(postList, SEPARATOR)\n\treturn ufmt.Sprintf(\"[%s]\", res)\n}\n\n// NOTE: due to bug of std.PreviousRealm().Addr() return \"\" when query so we user this proxy function temporary\n// in waiting of correct behaviour of std.PreviousRealm().Addr()\nfunc GetPosts(fid FeedID, parentID PostID, user string, categories []uint64, offset uint64, limit uint8) string {\n\tcaller := std.PreviousRealm().Addr()\n\tdata := GetPostsWithCaller(fid, parentID, caller.String(), user, categories, offset, limit)\n\treturn data\n}\n\nfunc GetPostsWithCaller(fid FeedID, parentID PostID, callerAddrStr string, user string, categories []uint64, offset uint64, limit uint8) string {\n\t// Return flagged posts, we process flagged posts differently using FlagIndex\n\tif len(categories) == 1 && categories[0] == uint64(9) {\n\t\treturn GetFlaggedPosts(fid, offset, limit)\n\t}\n\n\t// BUG: normally std.PreviousRealm().Addr() should return a value instead of empty\n\t// Fix is in progress on Gno side\n\tfeed := mustGetFeed(fid)\n\tposts := getPosts(feed, parentID, callerAddrStr, user, categories, offset, limit)\n\n\tSEPARATOR := \",\"\n\tvar postListStr []string\n\n\tfor _, post := range posts {\n\t\tpostListStr = append(postListStr, post.String())\n\t}\n\n\tres := strings.Join(postListStr, SEPARATOR)\n\treturn ufmt.Sprintf(\"[%s]\", res)\n}\n\n// user here is: filter by user\nfunc getPosts(feed *Feed, parentID PostID, callerAddrStr string, user string, categories []uint64, offset uint64, limit uint8) []*Post {\n\tcaller := std.Address(callerAddrStr)\n\n\tvar posts []*Post\n\tvar skipped uint64\n\n\t// Create an avlTree for optimizing the check\n\trequestedCategories := avl.NewTree()\n\tfor _, category := range categories {\n\t\tcatStr := strconv.FormatUint(category, 10)\n\t\trequestedCategories.Set(catStr, true)\n\t}\n\n\tfeed.posts.ReverseIterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tpost := value.(*Post)\n\n\t\tpostCatStr := strconv.FormatUint(post.category, 10)\n\n\t\t// NOTE: this search mechanism is not efficient, only for demo purpose\n\t\tif post.parentID == parentID && post.deleted == false {\n\t\t\tif requestedCategories.Size() > 0 && !requestedCategories.Has(postCatStr) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tif user != \"\" && std.Address(user) != post.creator {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t// Filter hidden post\n\t\t\tflagID := getFlagID(feed.id, post.id)\n\t\t\tif feed.flags.HasFlagged(flagID, callerAddrStr) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t// Check if post is in hidden list\n\t\t\tvalue, exists := feed.hiddenPostsByUser.Get(caller.String())\n\t\t\tif exists {\n\t\t\t\thiddenPosts := value.(*avl.Tree)\n\t\t\t\t// If post.id exists in hiddenPosts tree => that post is hidden\n\t\t\t\tif hiddenPosts.Has(post.id.String()) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif skipped < offset {\n\t\t\t\tskipped++\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tposts = append(posts, post)\n\t\t}\n\n\t\tif len(posts) == int(limit) {\n\t\t\treturn true\n\t\t}\n\n\t\treturn false\n\t})\n\n\treturn posts\n}\n\n// Get comments list\nfunc GetComments(fid FeedID, parentID PostID, offset uint64, limit uint8) string {\n\treturn GetPosts(fid, parentID, \"\", []uint64{}, offset, limit)\n}\n\n// Get Post\nfunc GetPost(fid FeedID, pid PostID) string {\n\tfeed := mustGetFeed(fid)\n\n\tdata, ok := feed.posts.Get(postIDKey(pid))\n\tif !ok {\n\t\tpanic(\"Unable to get post\")\n\t}\n\n\tpost := data.(*Post)\n\treturn post.String()\n}\n\nfunc FlagPost(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.FlagPost(caller, pid)\n}\n\nfunc HidePostForMe(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.HidePostForUser(caller, pid)\n}\n\nfunc UnHidePostForMe(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.UnHidePostForUser(caller, pid)\n}\n\nfunc GetFlags(fid FeedID, limit uint64, offset uint64) string {\n\tfeed := mustGetFeed(fid)\n\n\ttype FlagCount struct {\n\t\tFlagID flags_index.FlagID\n\t\tCount uint64\n\t}\n\n\tflags := feed.flags.GetFlags(limit, offset)\n\n\tvar res []string\n\tfor _, flag := range flags {\n\t\tres = append(res, ufmt.Sprintf(\"%s:%d\", flag.FlagID, flag.Count))\n\t}\n\n\treturn strings.Join(res, \"|\")\n}\n\n// TODO: allow only creator to call\nfunc GetFeedByID(fid FeedID) *Feed {\n\treturn mustGetFeed(fid)\n}\n\n// TODO: allow only admin to call\nfunc ExportFeedData(fid FeedID) string {\n\tfeed := mustGetFeed(fid)\n\tfeedJSON := feed.ToJSON()\n\treturn feedJSON\n}\n\n// TODO: allow only admin to call\nfunc ImportFeedData(fid FeedID, jsonData string) {\n\tfeed := mustGetFeed(fid)\n\tfeed.FromJSON(jsonData)\n}\n\n// func MigrateFromPreviousFeed(fid feedsV7.FeedID) {\n// \t// Get exported data from previous feeds\n// \tjsonData := feedsV7.ExportFeedData(fid)\n// \tImportFeedData(FeedID(uint64(fid)), jsonData)\n// }\n" + "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/teritori/flags_index\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Only registered user can create a new feed\n// For the flexibility when testing, allow all user to create feed\nfunc CreateFeed(name string) FeedID {\n\tpkgpath := std.CurrentRealmPath()\n\n\tfid := incGetFeedID()\n\tcaller := std.PreviousRealm().Address()\n\turl := strings.Replace(pkgpath, \"gno.land\", \"\", -1) + \":\" + name\n\tfeed := newFeed(fid, url, name, caller)\n\tfidkey := feedIDKey(fid)\n\tgFeeds.Set(fidkey, feed)\n\tgFeedsByName.Set(name, feed)\n\treturn feed.id\n}\n\n// Anyone can create a post in a existing feed, allow un-registered users also\nfunc CreatePost(fid FeedID, parentID PostID, catetory uint64, metadata string) PostID {\n\tcaller := std.PreviousRealm().Address()\n\n\tfeed := mustGetFeed(fid)\n\tpost := feed.AddPost(caller, parentID, catetory, metadata)\n\treturn post.id\n}\n\n// Only post's owner can edit post\nfunc EditPost(fid FeedID, pid PostID, category uint64, metadata string) {\n\tcaller := std.PreviousRealm().Address()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tif caller != post.creator {\n\t\tpanic(\"you are not creator of this post\")\n\t}\n\n\tpost.Update(category, metadata)\n}\n\n// Only feed creator/owner can call this\nfunc SetOwner(fid FeedID, newOwner std.Address) {\n\tcaller := std.PreviousRealm().Address()\n\tfeed := mustGetFeed(fid)\n\n\tif caller != feed.creator && caller != feed.owner {\n\t\tpanic(\"you are not creator/owner of this feed\")\n\t}\n\n\tfeed.owner = newOwner\n}\n\n// Only feed creator/owner or post creator can delete the post\nfunc DeletePost(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Address()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tif caller != post.creator && caller != feed.creator && caller != feed.owner {\n\t\tpanic(\"you are nor creator of this post neither creator/owner of the feed\")\n\t}\n\n\tpost.Delete()\n\n\t// If post is comment then decrease comments count on parent\n\tif uint64(post.parentID) != 0 {\n\t\tparent := feed.MustGetPost(post.parentID)\n\t\tparent.commentsCount -= 1\n\t}\n}\n\n// Only feed owner can ban the post\nfunc BanPost(fid FeedID, pid PostID, reason string) {\n\tcaller := std.PreviousRealm().Address()\n\tfeed := mustGetFeed(fid)\n\t_ = feed.MustGetPost(pid)\n\n\t// For experimenting, we ban only the post for now\n\t// TODO: recursive delete/ban comments\n\tif caller != feed.owner {\n\t\tpanic(\"you are owner of the feed\")\n\t}\n\n\tfeed.BanPost(pid)\n\n\tfeed.flags.ClearFlagCount(getFlagID(fid, pid))\n}\n\n// Any one can react post\nfunc ReactPost(fid FeedID, pid PostID, icon string, up bool) {\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tpost.React(icon, up)\n}\n\nfunc TipPost(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Address()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tpost.Tip(caller, post.creator)\n}\n\n// Get a list of flagged posts\n// NOTE: We can support multi feeds in the future but for now we will have only 1 feed\n// Return stringified list in format: postStr-count,postStr-count\nfunc GetFlaggedPosts(fid FeedID, offset uint64, limit uint8) string {\n\tfeed := mustGetFeed(fid)\n\n\t// Already sorted by count descending\n\tflags := feed.flags.GetFlags(uint64(limit), offset)\n\n\tvar postList []string\n\tfor _, flagCount := range flags {\n\t\tflagID := flagCount.FlagID\n\n\t\tfeedID, postID := parseFlagID(flagID)\n\t\tif feedID != feed.id {\n\t\t\tcontinue\n\t\t}\n\n\t\tpost := feed.GetPost(postID)\n\t\tpostList = append(postList, ufmt.Sprintf(\"%s\", post))\n\t}\n\n\tSEPARATOR := \",\"\n\tres := strings.Join(postList, SEPARATOR)\n\treturn ufmt.Sprintf(\"[%s]\", res)\n}\n\n// NOTE: due to bug of std.PreviousRealm().Address() return \"\" when query so we user this proxy function temporary\n// in waiting of correct behaviour of std.PreviousRealm().Address()\nfunc GetPosts(fid FeedID, parentID PostID, user string, categories []uint64, offset uint64, limit uint8) string {\n\tcaller := std.PreviousRealm().Address()\n\tdata := GetPostsWithCaller(fid, parentID, caller.String(), user, categories, offset, limit)\n\treturn data\n}\n\nfunc GetPostsWithCaller(fid FeedID, parentID PostID, callerAddrStr string, user string, categories []uint64, offset uint64, limit uint8) string {\n\t// Return flagged posts, we process flagged posts differently using FlagIndex\n\tif len(categories) == 1 && categories[0] == uint64(9) {\n\t\treturn GetFlaggedPosts(fid, offset, limit)\n\t}\n\n\t// BUG: normally std.PreviousRealm().Address() should return a value instead of empty\n\t// Fix is in progress on Gno side\n\tfeed := mustGetFeed(fid)\n\tposts := getPosts(feed, parentID, callerAddrStr, user, categories, offset, limit)\n\n\tSEPARATOR := \",\"\n\tvar postListStr []string\n\n\tfor _, post := range posts {\n\t\tpostListStr = append(postListStr, post.String())\n\t}\n\n\tres := strings.Join(postListStr, SEPARATOR)\n\treturn ufmt.Sprintf(\"[%s]\", res)\n}\n\n// user here is: filter by user\nfunc getPosts(feed *Feed, parentID PostID, callerAddrStr string, user string, categories []uint64, offset uint64, limit uint8) []*Post {\n\tcaller := std.Address(callerAddrStr)\n\n\tvar posts []*Post\n\tvar skipped uint64\n\n\t// Create an avlTree for optimizing the check\n\trequestedCategories := avl.NewTree()\n\tfor _, category := range categories {\n\t\tcatStr := strconv.FormatUint(category, 10)\n\t\trequestedCategories.Set(catStr, true)\n\t}\n\n\tfeed.posts.ReverseIterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tpost := value.(*Post)\n\n\t\tpostCatStr := strconv.FormatUint(post.category, 10)\n\n\t\t// NOTE: this search mechanism is not efficient, only for demo purpose\n\t\tif post.parentID == parentID && post.deleted == false {\n\t\t\tif requestedCategories.Size() > 0 && !requestedCategories.Has(postCatStr) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tif user != \"\" && std.Address(user) != post.creator {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t// Filter hidden post\n\t\t\tflagID := getFlagID(feed.id, post.id)\n\t\t\tif feed.flags.HasFlagged(flagID, callerAddrStr) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t// Check if post is in hidden list\n\t\t\tvalue, exists := feed.hiddenPostsByUser.Get(caller.String())\n\t\t\tif exists {\n\t\t\t\thiddenPosts := value.(*avl.Tree)\n\t\t\t\t// If post.id exists in hiddenPosts tree => that post is hidden\n\t\t\t\tif hiddenPosts.Has(post.id.String()) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif skipped < offset {\n\t\t\t\tskipped++\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tposts = append(posts, post)\n\t\t}\n\n\t\tif len(posts) == int(limit) {\n\t\t\treturn true\n\t\t}\n\n\t\treturn false\n\t})\n\n\treturn posts\n}\n\n// Get comments list\nfunc GetComments(fid FeedID, parentID PostID, offset uint64, limit uint8) string {\n\treturn GetPosts(fid, parentID, \"\", []uint64{}, offset, limit)\n}\n\n// Get Post\nfunc GetPost(fid FeedID, pid PostID) string {\n\tfeed := mustGetFeed(fid)\n\n\tdata, ok := feed.posts.Get(postIDKey(pid))\n\tif !ok {\n\t\tpanic(\"Unable to get post\")\n\t}\n\n\tpost := data.(*Post)\n\treturn post.String()\n}\n\nfunc FlagPost(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Address()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.FlagPost(caller, pid)\n}\n\nfunc HidePostForMe(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Address()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.HidePostForUser(caller, pid)\n}\n\nfunc UnHidePostForMe(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Address()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.UnHidePostForUser(caller, pid)\n}\n\nfunc GetFlags(fid FeedID, limit uint64, offset uint64) string {\n\tfeed := mustGetFeed(fid)\n\n\ttype FlagCount struct {\n\t\tFlagID flags_index.FlagID\n\t\tCount uint64\n\t}\n\n\tflags := feed.flags.GetFlags(limit, offset)\n\n\tvar res []string\n\tfor _, flag := range flags {\n\t\tres = append(res, ufmt.Sprintf(\"%s:%d\", flag.FlagID, flag.Count))\n\t}\n\n\treturn strings.Join(res, \"|\")\n}\n\n// TODO: allow only creator to call\nfunc GetFeedByID(fid FeedID) *Feed {\n\treturn mustGetFeed(fid)\n}\n\n// TODO: allow only admin to call\nfunc ExportFeedData(fid FeedID) string {\n\tfeed := mustGetFeed(fid)\n\tfeedJSON := feed.ToJSON()\n\treturn feedJSON\n}\n\n// TODO: allow only admin to call\nfunc ImportFeedData(fid FeedID, jsonData string) {\n\tfeed := mustGetFeed(fid)\n\tfeed.FromJSON(jsonData)\n}\n\n// func MigrateFromPreviousFeed(fid feedsV7.FeedID) {\n// \t// Get exported data from previous feeds\n// \tjsonData := feedsV7.ExportFeedData(fid)\n// \tImportFeedData(FeedID(uint64(fid)), jsonData)\n// }\n" }, { "Name": "render.gno", diff --git a/gno.land/pkg/integration/testdata/prevrealm.txtar b/gno.land/pkg/integration/testdata/prevrealm.txtar index 492691a021b..dba18da6e73 100644 --- a/gno.land/pkg/integration/testdata/prevrealm.txtar +++ b/gno.land/pkg/integration/testdata/prevrealm.txtar @@ -96,7 +96,7 @@ package myrlm import "std" func A() string { - return std.PreviousRealm().Addr().String() + return std.PreviousRealm().Address().String() } func B() string { @@ -120,7 +120,7 @@ package bar import "std" func A() string { - return std.PreviousRealm().Addr().String() + return std.PreviousRealm().Address().String() } func B() string { @@ -180,5 +180,5 @@ package main import "std" func main() { - println(std.PreviousRealm().Addr().String()) + println(std.PreviousRealm().Address().String()) } diff --git a/gno.land/pkg/sdk/vm/keeper_test.go b/gno.land/pkg/sdk/vm/keeper_test.go index 0656818b2eb..b912715fd3d 100644 --- a/gno.land/pkg/sdk/vm/keeper_test.go +++ b/gno.land/pkg/sdk/vm/keeper_test.go @@ -129,7 +129,7 @@ func init() { func Echo(msg string) string { addr := std.OriginCaller() - pkgAddr := std.GetOriginPkgAddr() + pkgAddr := std.GetOriginPkgAddress() send := std.OriginSend() banker := std.GetBanker(std.BankerTypeOriginSend) banker.SendCoins(pkgAddr, addr, send) // send back @@ -177,7 +177,7 @@ func init() { func Echo(msg string) string { addr := std.OriginCaller() - pkgAddr := std.GetOriginPkgAddr() + pkgAddr := std.GetOriginPkgAddress() send := std.OriginSend() banker := std.GetBanker(std.BankerTypeOriginSend) banker.SendCoins(pkgAddr, addr, send) // send back @@ -228,7 +228,7 @@ func init() { func Echo(msg string) string { addr := std.OriginCaller() - pkgAddr := std.GetOriginPkgAddr() + pkgAddr := std.GetOriginPkgAddress() send := std.Coins{{"ugnot", 10000000}} banker := std.GetBanker(std.BankerTypeOriginSend) banker.SendCoins(pkgAddr, addr, send) // send back @@ -272,7 +272,7 @@ func init() { func Echo(msg string) string { addr := std.OriginCaller() - pkgAddr := std.GetOriginPkgAddr() + pkgAddr := std.GetOriginPkgAddress() send := std.Coins{{"ugnot", 10000000}} banker := std.GetBanker(std.BankerTypeRealmSend) banker.SendCoins(pkgAddr, addr, send) // send back @@ -316,7 +316,7 @@ func init() { func Echo(msg string) string { addr := std.OriginCaller() - pkgAddr := std.GetOriginPkgAddr() + pkgAddr := std.GetOriginPkgAddress() send := std.Coins{{"ugnot", 10000000}} banker := std.GetBanker(std.BankerTypeRealmSend) banker.SendCoins(pkgAddr, addr, send) // send back @@ -417,7 +417,7 @@ func init() { func Echo(msg string) string { addr := std.OriginCaller() - pkgAddr := std.GetOriginPkgAddr() + pkgAddr := std.GetOriginPkgAddress() send := std.OriginSend() banker := std.GetBanker(std.BankerTypeOriginSend) banker.SendCoins(pkgAddr, addr, send) // send back diff --git a/gnovm/stdlibs/std/banker.gno b/gnovm/stdlibs/std/banker.gno index 8fb24f355f0..c53d7f12330 100644 --- a/gnovm/stdlibs/std/banker.gno +++ b/gnovm/stdlibs/std/banker.gno @@ -73,12 +73,12 @@ func GetBanker(bt BankerType) Banker { var pkgAddr Address if bt == BankerTypeOriginSend { - pkgAddr = GetOriginPkgAddr() - if pkgAddr != CurrentRealm().Addr() { + pkgAddr = GetOriginPkgAddress() + if pkgAddr != CurrentRealm().Address() { panic("banker with type BankerTypeOriginSend can only be instantiated by the origin package") } } else if bt == BankerTypeRealmSend || bt == BankerTypeRealmIssue { - pkgAddr = CurrentRealm().Addr() + pkgAddr = CurrentRealm().Address() } return banker{ bt, diff --git a/gnovm/stdlibs/std/native.gno b/gnovm/stdlibs/std/native.gno index e4e34d96e29..0bbc151f06b 100644 --- a/gnovm/stdlibs/std/native.gno +++ b/gnovm/stdlibs/std/native.gno @@ -37,7 +37,7 @@ func PreviousRealm() Realm { return Realm{Address(addr), path} } -func GetOriginPkgAddr() Address { +func GetOriginPkgAddress() Address { return Address(originPkgAddr()) } diff --git a/gnovm/tests/files/zrealm_crossrealm11.gno b/gnovm/tests/files/zrealm_crossrealm11.gno index 1c64f3b0cf6..c565c1847cc 100644 --- a/gnovm/tests/files/zrealm_crossrealm11.gno +++ b/gnovm/tests/files/zrealm_crossrealm11.gno @@ -29,7 +29,7 @@ func main() { } assertRealm := func(r std.Realm) { - pkgPath := callersByAddr[r.Addr()] + pkgPath := callersByAddr[r.Address()] if r.IsUser() && pkgPath != "user1.gno" { panic(ufmt.Sprintf("ERROR: expected: 'user1.gno', got:'%s'", pkgPath)) } else if !r.IsUser() && pkgPath != r.PkgPath() { @@ -73,21 +73,21 @@ func main() { baseCallStack := "user1.gno -> r/crossrealm_test.main" for i, tt := range tests { - printColumns(baseCallStack+tt.callStackAdd, callersByAddr[tt.callerFn().Addr()]) + printColumns(baseCallStack+tt.callStackAdd, callersByAddr[tt.callerFn().Address()]) Exec(func() { r := tt.callerFn() assertRealm(r) - printColumns(baseCallStack+" -> r/crossrealm_test.Exec"+tt.callStackAdd, callersByAddr[r.Addr()]) + printColumns(baseCallStack+" -> r/crossrealm_test.Exec"+tt.callStackAdd, callersByAddr[r.Address()]) }) rtests.Exec(func() { r := tt.callerFn() assertRealm(r) - printColumns(baseCallStack+" -> r/demo/tests.Exec"+tt.callStackAdd, callersByAddr[r.Addr()]) + printColumns(baseCallStack+" -> r/demo/tests.Exec"+tt.callStackAdd, callersByAddr[r.Address()]) }) ptests.Exec(func() { r := tt.callerFn() assertRealm(r) - printColumns(baseCallStack+" -> p/demo/tests.Exec"+tt.callStackAdd, callersByAddr[r.Addr()]) + printColumns(baseCallStack+" -> p/demo/tests.Exec"+tt.callStackAdd, callersByAddr[r.Address()]) }) } } diff --git a/gnovm/tests/files/zrealm_crossrealm12.gno b/gnovm/tests/files/zrealm_crossrealm12.gno index f2f229cd5de..5ae4d69879a 100644 --- a/gnovm/tests/files/zrealm_crossrealm12.gno +++ b/gnovm/tests/files/zrealm_crossrealm12.gno @@ -21,13 +21,13 @@ func main() { for _, test := range tests { r := test.fn() - if std.DerivePkgAddr(r.PkgPath()) != r.Addr() { + if std.DerivePkgAddr(r.PkgPath()) != r.Address() { panic(fmt.Sprintf("ERROR: expected: %v, got: %v", - std.DerivePkgAddr(r.PkgPath()), r.Addr(), + std.DerivePkgAddr(r.PkgPath()), r.Address(), )) } - println(r.PkgPath(), r.Addr()) + println(r.PkgPath(), r.Address()) } } diff --git a/gnovm/tests/files/zrealm_std6.gno b/gnovm/tests/files/zrealm_std6.gno index bb4c7c941d7..e5e8b861055 100644 --- a/gnovm/tests/files/zrealm_std6.gno +++ b/gnovm/tests/files/zrealm_std6.gno @@ -6,7 +6,7 @@ import ( ) var ( - realmAddr std.Address = std.GetOriginPkgAddr() + realmAddr std.Address = std.GetOriginPkgAddress() ) func main() { diff --git a/misc/deployments/test5.gno.land/genesis_txs.jsonl b/misc/deployments/test5.gno.land/genesis_txs.jsonl index aa00b15752d..adbca225e5a 100755 --- a/misc/deployments/test5.gno.land/genesis_txs.jsonl +++ b/misc/deployments/test5.gno.land/genesis_txs.jsonl @@ -31,8 +31,8 @@ {"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"static","path":"gno.land/p/demo/gnorkle/feeds/static","files":[{"name":"feed.gno","body":"package static\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"errors\"\n\n\t\"gno.land/p/demo/gnorkle/feed\"\n\t\"gno.land/p/demo/gnorkle/gnorkle\"\n\t\"gno.land/p/demo/gnorkle/ingesters/single\"\n\t\"gno.land/p/demo/gnorkle/message\"\n\t\"gno.land/p/demo/gnorkle/storage/simple\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Feed is a static feed.\ntype Feed struct {\n\tid string\n\tisLocked bool\n\tvalueDataType string\n\tingester gnorkle.Ingester\n\tstorage gnorkle.Storage\n\ttasks []feed.Task\n}\n\n// NewFeed creates a new static feed.\nfunc NewFeed(\n\tid string,\n\tvalueDataType string,\n\tingester gnorkle.Ingester,\n\tstorage gnorkle.Storage,\n\ttasks ...feed.Task,\n) *Feed {\n\treturn \u0026Feed{\n\t\tid: id,\n\t\tvalueDataType: valueDataType,\n\t\tingester: ingester,\n\t\tstorage: storage,\n\t\ttasks: tasks,\n\t}\n}\n\n// NewSingleValueFeed is a convenience function for creating a static feed\n// that autocommits a value after a single ingestion.\nfunc NewSingleValueFeed(\n\tid string,\n\tvalueDataType string,\n\ttasks ...feed.Task,\n) *Feed {\n\treturn NewFeed(\n\t\tid,\n\t\tvalueDataType,\n\t\t\u0026single.ValueIngester{},\n\t\tsimple.NewStorage(1),\n\t\ttasks...,\n\t)\n}\n\n// ID returns the feed's ID.\nfunc (f Feed) ID() string {\n\treturn f.id\n}\n\n// Type returns the feed's type.\nfunc (f Feed) Type() feed.Type {\n\treturn feed.TypeStatic\n}\n\n// Ingest ingests a message into the feed. It either adds the value to the ingester's\n// pending values or commits the value to the storage.\nfunc (f *Feed) Ingest(funcType message.FuncType, msg, providerAddress string) error {\n\tif f == nil {\n\t\treturn feed.ErrUndefined\n\t}\n\n\tif f.isLocked {\n\t\treturn errors.New(\"feed locked\")\n\t}\n\n\tswitch funcType {\n\tcase message.FuncTypeIngest:\n\t\t// Autocommit the ingester's value if it's a single value ingester\n\t\t// because this is a static feed and this is the only value it will ever have.\n\t\tif canAutoCommit, err := f.ingester.Ingest(msg, providerAddress); canAutoCommit \u0026\u0026 err == nil {\n\t\t\tif err := f.ingester.CommitValue(f.storage, providerAddress); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tf.isLocked = true\n\t\t} else if err != nil {\n\t\t\treturn err\n\t\t}\n\n\tcase message.FuncTypeCommit:\n\t\tif err := f.ingester.CommitValue(f.storage, providerAddress); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tf.isLocked = true\n\n\tdefault:\n\t\treturn errors.New(\"invalid message function \" + string(funcType))\n\t}\n\n\treturn nil\n}\n\n// Value returns the feed's latest value, it's data type, and whether or not it can\n// be safely consumed. In this case it uses `f.isLocked` because, this being a static\n// feed, it will only ever have one value; once that value is committed the feed is locked\n// and there is a valid, non-empty value to consume.\nfunc (f Feed) Value() (feed.Value, string, bool) {\n\treturn f.storage.GetLatest(), f.valueDataType, f.isLocked\n}\n\n// MarshalJSON marshals the components of the feed that are needed for\n// an agent to execute tasks and send values for ingestion.\nfunc (f Feed) MarshalJSON() ([]byte, error) {\n\tbuf := new(bytes.Buffer)\n\tw := bufio.NewWriter(buf)\n\n\tw.Write([]byte(\n\t\t`{\"id\":\"` + f.id +\n\t\t\t`\",\"type\":\"` + ufmt.Sprintf(\"%d\", int(f.Type())) +\n\t\t\t`\",\"value_type\":\"` + f.valueDataType +\n\t\t\t`\",\"tasks\":[`),\n\t)\n\n\tfirst := true\n\tfor _, task := range f.tasks {\n\t\tif !first {\n\t\t\tw.WriteString(\",\")\n\t\t}\n\n\t\ttaskJSON, err := task.MarshalJSON()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tw.Write(taskJSON)\n\t\tfirst = false\n\t}\n\n\tw.Write([]byte(\"]}\"))\n\tw.Flush()\n\n\treturn buf.Bytes(), nil\n}\n\n// Tasks returns the feed's tasks. This allows task consumers to extract task\n// contents without having to marshal the entire feed.\nfunc (f Feed) Tasks() []feed.Task {\n\treturn f.tasks\n}\n\n// IsActive returns true if the feed is accepting ingestion requests from agents.\nfunc (f Feed) IsActive() bool {\n\treturn !f.isLocked\n}\n"},{"name":"feed_test.gno","body":"package static_test\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/gnorkle/feed\"\n\t\"gno.land/p/demo/gnorkle/feeds/static\"\n\t\"gno.land/p/demo/gnorkle/gnorkle\"\n\t\"gno.land/p/demo/gnorkle/ingester\"\n\t\"gno.land/p/demo/gnorkle/message\"\n\t\"gno.land/p/demo/gnorkle/storage/simple\"\n\t\"gno.land/p/demo/uassert\"\n\t\"gno.land/p/demo/urequire\"\n)\n\ntype mockIngester struct {\n\tcanAutoCommit bool\n\tingestErr error\n\tcommitErr error\n\tvalue string\n\tproviderAddress string\n}\n\nfunc (i mockIngester) Type() ingester.Type {\n\treturn ingester.Type(0)\n}\n\nfunc (i *mockIngester) Ingest(value, providerAddress string) (bool, error) {\n\tif i.ingestErr != nil {\n\t\treturn false, i.ingestErr\n\t}\n\n\ti.value = value\n\ti.providerAddress = providerAddress\n\treturn i.canAutoCommit, nil\n}\n\nfunc (i *mockIngester) CommitValue(storage gnorkle.Storage, providerAddress string) error {\n\tif i.commitErr != nil {\n\t\treturn i.commitErr\n\t}\n\n\treturn storage.Put(i.value)\n}\n\nfunc TestNewSingleValueFeed(t *testing.T) {\n\tstaticFeed := static.NewSingleValueFeed(\"1\", \"\")\n\n\tuassert.Equal(t, \"1\", staticFeed.ID())\n\tuassert.Equal(t, int(feed.TypeStatic), int(staticFeed.Type()))\n}\n\nfunc TestFeed_Ingest(t *testing.T) {\n\tvar undefinedFeed *static.Feed\n\terr := undefinedFeed.Ingest(\"\", \"\", \"\")\n\tuassert.ErrorIs(t, err, feed.ErrUndefined)\n\n\ttests := []struct {\n\t\tname string\n\t\tingester *mockIngester\n\t\tverifyIsLocked bool\n\t\tdoCommit bool\n\t\tfuncType message.FuncType\n\t\tmsg string\n\t\tproviderAddress string\n\t\texpFeedValueString string\n\t\texpErrText string\n\t\texpIsActive bool\n\t}{\n\t\t{\n\t\t\tname: \"func invalid error\",\n\t\t\tingester: \u0026mockIngester{},\n\t\t\tfuncType: message.FuncType(\"derp\"),\n\t\t\texpErrText: \"invalid message function derp\",\n\t\t\texpIsActive: true,\n\t\t},\n\t\t{\n\t\t\tname: \"func ingest ingest error\",\n\t\t\tingester: \u0026mockIngester{\n\t\t\t\tingestErr: errors.New(\"ingest error\"),\n\t\t\t},\n\t\t\tfuncType: message.FuncTypeIngest,\n\t\t\texpErrText: \"ingest error\",\n\t\t\texpIsActive: true,\n\t\t},\n\t\t{\n\t\t\tname: \"func ingest commit error\",\n\t\t\tingester: \u0026mockIngester{\n\t\t\t\tcommitErr: errors.New(\"commit error\"),\n\t\t\t\tcanAutoCommit: true,\n\t\t\t},\n\t\t\tfuncType: message.FuncTypeIngest,\n\t\t\texpErrText: \"commit error\",\n\t\t\texpIsActive: true,\n\t\t},\n\t\t{\n\t\t\tname: \"func commit commit error\",\n\t\t\tingester: \u0026mockIngester{\n\t\t\t\tcommitErr: errors.New(\"commit error\"),\n\t\t\t\tcanAutoCommit: true,\n\t\t\t},\n\t\t\tfuncType: message.FuncTypeCommit,\n\t\t\texpErrText: \"commit error\",\n\t\t\texpIsActive: true,\n\t\t},\n\t\t{\n\t\t\tname: \"only ingest\",\n\t\t\tingester: \u0026mockIngester{},\n\t\t\tfuncType: message.FuncTypeIngest,\n\t\t\tmsg: \"still active feed\",\n\t\t\tproviderAddress: \"gno1234\",\n\t\t\texpIsActive: true,\n\t\t},\n\t\t{\n\t\t\tname: \"ingest autocommit\",\n\t\t\tingester: \u0026mockIngester{canAutoCommit: true},\n\t\t\tfuncType: message.FuncTypeIngest,\n\t\t\tmsg: \"still active feed\",\n\t\t\tproviderAddress: \"gno1234\",\n\t\t\texpFeedValueString: \"still active feed\",\n\t\t\tverifyIsLocked: true,\n\t\t},\n\t\t{\n\t\t\tname: \"commit no value\",\n\t\t\tingester: \u0026mockIngester{},\n\t\t\tfuncType: message.FuncTypeCommit,\n\t\t\tmsg: \"shouldn't be stored\",\n\t\t\tverifyIsLocked: true,\n\t\t},\n\t\t{\n\t\t\tname: \"ingest then commmit\",\n\t\t\tingester: \u0026mockIngester{},\n\t\t\tfuncType: message.FuncTypeIngest,\n\t\t\tmsg: \"blahblah\",\n\t\t\tdoCommit: true,\n\t\t\texpFeedValueString: \"blahblah\",\n\t\t\tverifyIsLocked: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tstaticFeed := static.NewFeed(\n\t\t\t\t\"1\",\n\t\t\t\t\"string\",\n\t\t\t\ttt.ingester,\n\t\t\t\tsimple.NewStorage(1),\n\t\t\t\tnil,\n\t\t\t)\n\n\t\t\tvar errText string\n\t\t\tif err := staticFeed.Ingest(tt.funcType, tt.msg, tt.providerAddress); err != nil {\n\t\t\t\terrText = err.Error()\n\t\t\t}\n\n\t\t\turequire.Equal(t, tt.expErrText, errText)\n\n\t\t\tif tt.doCommit {\n\t\t\t\terr := staticFeed.Ingest(message.FuncTypeCommit, \"\", \"\")\n\t\t\t\turequire.NoError(t, err, \"follow up commit failed\")\n\t\t\t}\n\n\t\t\tif tt.verifyIsLocked {\n\t\t\t\terrText = \"\"\n\t\t\t\tif err := staticFeed.Ingest(tt.funcType, tt.msg, tt.providerAddress); err != nil {\n\t\t\t\t\terrText = err.Error()\n\t\t\t\t}\n\n\t\t\t\turequire.Equal(t, \"feed locked\", errText)\n\t\t\t}\n\n\t\t\tuassert.Equal(t, tt.providerAddress, tt.ingester.providerAddress)\n\n\t\t\tfeedValue, dataType, isLocked := staticFeed.Value()\n\t\t\tuassert.Equal(t, tt.expFeedValueString, feedValue.String)\n\t\t\tuassert.Equal(t, \"string\", dataType)\n\t\t\tuassert.Equal(t, tt.verifyIsLocked, isLocked)\n\t\t\tuassert.Equal(t, tt.expIsActive, staticFeed.IsActive())\n\t\t})\n\t}\n}\n\ntype mockTask struct {\n\terr error\n\tvalue string\n}\n\nfunc (t mockTask) MarshalJSON() ([]byte, error) {\n\tif t.err != nil {\n\t\treturn nil, t.err\n\t}\n\n\treturn []byte(`{\"value\":\"` + t.value + `\"}`), nil\n}\n\nfunc TestFeed_Tasks(t *testing.T) {\n\tid := \"99\"\n\tvalueDataType := \"int\"\n\n\ttests := []struct {\n\t\tname string\n\t\ttasks []feed.Task\n\t\texpErrText string\n\t\texpJSON string\n\t}{\n\t\t{\n\t\t\tname: \"no tasks\",\n\t\t\texpJSON: `{\"id\":\"99\",\"type\":\"0\",\"value_type\":\"int\",\"tasks\":[]}`,\n\t\t},\n\t\t{\n\t\t\tname: \"marshal error\",\n\t\t\ttasks: []feed.Task{\n\t\t\t\tmockTask{err: errors.New(\"marshal error\")},\n\t\t\t},\n\t\t\texpErrText: \"marshal error\",\n\t\t},\n\t\t{\n\t\t\tname: \"one task\",\n\t\t\ttasks: []feed.Task{\n\t\t\t\tmockTask{value: \"single\"},\n\t\t\t},\n\t\t\texpJSON: `{\"id\":\"99\",\"type\":\"0\",\"value_type\":\"int\",\"tasks\":[{\"value\":\"single\"}]}`,\n\t\t},\n\t\t{\n\t\t\tname: \"two tasks\",\n\t\t\ttasks: []feed.Task{\n\t\t\t\tmockTask{value: \"first\"},\n\t\t\t\tmockTask{value: \"second\"},\n\t\t\t},\n\t\t\texpJSON: `{\"id\":\"99\",\"type\":\"0\",\"value_type\":\"int\",\"tasks\":[{\"value\":\"first\"},{\"value\":\"second\"}]}`,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tstaticFeed := static.NewSingleValueFeed(\n\t\t\t\tid,\n\t\t\t\tvalueDataType,\n\t\t\t\ttt.tasks...,\n\t\t\t)\n\n\t\t\turequire.Equal(t, len(tt.tasks), len(staticFeed.Tasks()))\n\n\t\t\tvar errText string\n\t\t\tjson, err := staticFeed.MarshalJSON()\n\t\t\tif err != nil {\n\t\t\t\terrText = err.Error()\n\t\t\t}\n\n\t\t\turequire.Equal(t, tt.expErrText, errText)\n\t\t\turequire.Equal(t, tt.expJSON, string(json))\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":"exts","path":"gno.land/p/demo/grc/exts","files":[{"name":"token_metadata.gno","body":"package exts\n\ntype TokenMetadata interface {\n\t// Returns the name of the token.\n\tGetName() string\n\n\t// Returns the symbol of the token, usually a shorter version of the\n\t// name.\n\tGetSymbol() string\n\n\t// Returns the decimals places of the token.\n\tGetDecimals() uint\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":"grc1155","path":"gno.land/p/demo/grc/grc1155","files":[{"name":"README.md","body":"# GRC-1155 Spec: Multi Token Standard\n\nGRC1155 is a specification for managing multiple tokens based on Gnoland. The name and design is based on Ethereum's ERC1155 standard.\n\n## See also:\n\n[ERC-1155 Spec][erc-1155]\n\n[erc-1155]: https://eips.ethereum.org/EIPS/eip-1155"},{"name":"basic_grc1155_token.gno","body":"package grc1155\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype basicGRC1155Token struct {\n\turi string\n\tbalances avl.Tree // \"TokenId:Address\" -\u003e uint64\n\toperatorApprovals avl.Tree // \"OwnerAddress:OperatorAddress\" -\u003e bool\n}\n\nvar _ IGRC1155 = (*basicGRC1155Token)(nil)\n\n// Returns new basic GRC1155 token\nfunc NewBasicGRC1155Token(uri string) *basicGRC1155Token {\n\treturn \u0026basicGRC1155Token{\n\t\turi: uri,\n\t\tbalances: avl.Tree{},\n\t\toperatorApprovals: avl.Tree{},\n\t}\n}\n\nfunc (s *basicGRC1155Token) Uri() string { return s.uri }\n\n// BalanceOf returns the input address's balance of the token type requested\nfunc (s *basicGRC1155Token) BalanceOf(addr std.Address, tid TokenID) (uint64, error) {\n\tif !isValidAddress(addr) {\n\t\treturn 0, ErrInvalidAddress\n\t}\n\n\tkey := string(tid) + \":\" + addr.String()\n\tbalance, found := s.balances.Get(key)\n\tif !found {\n\t\treturn 0, nil\n\t}\n\n\treturn balance.(uint64), nil\n}\n\n// BalanceOfBatch returns the balance of multiple account/token pairs\nfunc (s *basicGRC1155Token) BalanceOfBatch(owners []std.Address, batch []TokenID) ([]uint64, error) {\n\tif len(owners) != len(batch) {\n\t\treturn nil, ErrMismatchLength\n\t}\n\n\tbalanceOfBatch := make([]uint64, len(owners))\n\n\tfor i := 0; i \u003c len(owners); i++ {\n\t\tbalanceOfBatch[i], _ = s.BalanceOf(owners[i], batch[i])\n\t}\n\n\treturn balanceOfBatch, nil\n}\n\n// SetApprovalForAll can approve the operator to operate on all tokens\nfunc (s *basicGRC1155Token) SetApprovalForAll(operator std.Address, approved bool) error {\n\tif !isValidAddress(operator) {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tcaller := std.OriginCaller()\n\treturn s.setApprovalForAll(caller, operator, approved)\n}\n\n// IsApprovedForAll returns true if operator is the owner or is approved for all by the owner.\n// Otherwise, returns false\nfunc (s *basicGRC1155Token) IsApprovedForAll(owner, operator std.Address) bool {\n\tif operator == owner {\n\t\treturn true\n\t}\n\tkey := owner.String() + \":\" + operator.String()\n\t_, found := s.operatorApprovals.Get(key)\n\tif !found {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// Safely transfers `tokenId` token from `from` to `to`, checking that\n// contract recipients are aware of the GRC1155 protocol to prevent\n// tokens from being forever locked.\nfunc (s *basicGRC1155Token) SafeTransferFrom(from, to std.Address, tid TokenID, amount uint64) error {\n\tcaller := std.OriginCaller()\n\tif !s.IsApprovedForAll(caller, from) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.safeBatchTransferFrom(from, to, []TokenID{tid}, []uint64{amount})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.doSafeTransferAcceptanceCheck(caller, from, to, tid, amount) {\n\t\treturn ErrTransferToRejectedOrNonGRC1155Receiver\n\t}\n\n\temit(\u0026TransferSingleEvent{caller, from, to, tid, amount})\n\n\treturn nil\n}\n\n// Safely transfers a `batch` of tokens from `from` to `to`, checking that\n// contract recipients are aware of the GRC1155 protocol to prevent\n// tokens from being forever locked.\nfunc (s *basicGRC1155Token) SafeBatchTransferFrom(from, to std.Address, batch []TokenID, amounts []uint64) error {\n\tcaller := std.OriginCaller()\n\tif !s.IsApprovedForAll(caller, from) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.safeBatchTransferFrom(from, to, batch, amounts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.doSafeBatchTransferAcceptanceCheck(caller, from, to, batch, amounts) {\n\t\treturn ErrTransferToRejectedOrNonGRC1155Receiver\n\t}\n\n\temit(\u0026TransferBatchEvent{caller, from, to, batch, amounts})\n\n\treturn nil\n}\n\n// Creates `amount` tokens of token type `id`, and assigns them to `to`. Also checks that\n// contract recipients are using GRC1155 protocol.\nfunc (s *basicGRC1155Token) SafeMint(to std.Address, tid TokenID, amount uint64) error {\n\tcaller := std.OriginCaller()\n\n\terr := s.mintBatch(to, []TokenID{tid}, []uint64{amount})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.doSafeTransferAcceptanceCheck(caller, zeroAddress, to, tid, amount) {\n\t\treturn ErrTransferToRejectedOrNonGRC1155Receiver\n\t}\n\n\temit(\u0026TransferSingleEvent{caller, zeroAddress, to, tid, amount})\n\n\treturn nil\n}\n\n// Batch version of `SafeMint()`. Also checks that\n// contract recipients are using GRC1155 protocol.\nfunc (s *basicGRC1155Token) SafeBatchMint(to std.Address, batch []TokenID, amounts []uint64) error {\n\tcaller := std.OriginCaller()\n\n\terr := s.mintBatch(to, batch, amounts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.doSafeBatchTransferAcceptanceCheck(caller, zeroAddress, to, batch, amounts) {\n\t\treturn ErrTransferToRejectedOrNonGRC1155Receiver\n\t}\n\n\temit(\u0026TransferBatchEvent{caller, zeroAddress, to, batch, amounts})\n\n\treturn nil\n}\n\n// Destroys `amount` tokens of token type `id` from `from`.\nfunc (s *basicGRC1155Token) Burn(from std.Address, tid TokenID, amount uint64) error {\n\tcaller := std.OriginCaller()\n\n\terr := s.burnBatch(from, []TokenID{tid}, []uint64{amount})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\temit(\u0026TransferSingleEvent{caller, from, zeroAddress, tid, amount})\n\n\treturn nil\n}\n\n// Batch version of `Burn()`\nfunc (s *basicGRC1155Token) BatchBurn(from std.Address, batch []TokenID, amounts []uint64) error {\n\tcaller := std.OriginCaller()\n\n\terr := s.burnBatch(from, batch, amounts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\temit(\u0026TransferBatchEvent{caller, from, zeroAddress, batch, amounts})\n\n\treturn nil\n}\n\n/* Helper methods */\n\n// Helper for SetApprovalForAll(): approve `operator` to operate on all of `owner` tokens\nfunc (s *basicGRC1155Token) setApprovalForAll(owner, operator std.Address, approved bool) error {\n\tif owner == operator {\n\t\treturn nil\n\t}\n\n\tkey := owner.String() + \":\" + operator.String()\n\tif approved {\n\t\ts.operatorApprovals.Set(key, approved)\n\t} else {\n\t\ts.operatorApprovals.Remove(key)\n\t}\n\n\temit(\u0026ApprovalForAllEvent{owner, operator, approved})\n\n\treturn nil\n}\n\n// Helper for SafeTransferFrom() and SafeBatchTransferFrom()\nfunc (s *basicGRC1155Token) safeBatchTransferFrom(from, to std.Address, batch []TokenID, amounts []uint64) error {\n\tif len(batch) != len(amounts) {\n\t\treturn ErrMismatchLength\n\t}\n\tif !isValidAddress(from) || !isValidAddress(to) {\n\t\treturn ErrInvalidAddress\n\t}\n\tif from == to {\n\t\treturn ErrCannotTransferToSelf\n\t}\n\n\tcaller := std.OriginCaller()\n\ts.beforeTokenTransfer(caller, from, to, batch, amounts)\n\n\tfor i := 0; i \u003c len(batch); i++ {\n\t\ttid := batch[i]\n\t\tamount := amounts[i]\n\t\tfromBalance, err := s.BalanceOf(from, tid)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif fromBalance \u003c amount {\n\t\t\treturn ErrInsufficientBalance\n\t\t}\n\t\ttoBalance, err := s.BalanceOf(to, tid)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfromBalance -= amount\n\t\ttoBalance += amount\n\t\tfromBalanceKey := string(tid) + \":\" + from.String()\n\t\ttoBalanceKey := string(tid) + \":\" + to.String()\n\t\ts.balances.Set(fromBalanceKey, fromBalance)\n\t\ts.balances.Set(toBalanceKey, toBalance)\n\t}\n\n\ts.afterTokenTransfer(caller, from, to, batch, amounts)\n\n\treturn nil\n}\n\n// Helper for SafeMint() and SafeBatchMint()\nfunc (s *basicGRC1155Token) mintBatch(to std.Address, batch []TokenID, amounts []uint64) error {\n\tif len(batch) != len(amounts) {\n\t\treturn ErrMismatchLength\n\t}\n\tif !isValidAddress(to) {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tcaller := std.OriginCaller()\n\ts.beforeTokenTransfer(caller, zeroAddress, to, batch, amounts)\n\n\tfor i := 0; i \u003c len(batch); i++ {\n\t\ttid := batch[i]\n\t\tamount := amounts[i]\n\t\ttoBalance, err := s.BalanceOf(to, tid)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ttoBalance += amount\n\t\ttoBalanceKey := string(tid) + \":\" + to.String()\n\t\ts.balances.Set(toBalanceKey, toBalance)\n\t}\n\n\ts.afterTokenTransfer(caller, zeroAddress, to, batch, amounts)\n\n\treturn nil\n}\n\n// Helper for Burn() and BurnBatch()\nfunc (s *basicGRC1155Token) burnBatch(from std.Address, batch []TokenID, amounts []uint64) error {\n\tif len(batch) != len(amounts) {\n\t\treturn ErrMismatchLength\n\t}\n\tif !isValidAddress(from) {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tcaller := std.OriginCaller()\n\ts.beforeTokenTransfer(caller, from, zeroAddress, batch, amounts)\n\n\tfor i := 0; i \u003c len(batch); i++ {\n\t\ttid := batch[i]\n\t\tamount := amounts[i]\n\t\tfromBalance, err := s.BalanceOf(from, tid)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif fromBalance \u003c amount {\n\t\t\treturn ErrBurnAmountExceedsBalance\n\t\t}\n\t\tfromBalance -= amount\n\t\tfromBalanceKey := string(tid) + \":\" + from.String()\n\t\ts.balances.Set(fromBalanceKey, fromBalance)\n\t}\n\n\ts.afterTokenTransfer(caller, from, zeroAddress, batch, amounts)\n\n\treturn nil\n}\n\nfunc (s *basicGRC1155Token) setUri(newUri string) {\n\ts.uri = newUri\n\temit(\u0026UpdateURIEvent{newUri})\n}\n\nfunc (s *basicGRC1155Token) beforeTokenTransfer(operator, from, to std.Address, batch []TokenID, amounts []uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicGRC1155Token) afterTokenTransfer(operator, from, to std.Address, batch []TokenID, amounts []uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicGRC1155Token) doSafeTransferAcceptanceCheck(operator, from, to std.Address, tid TokenID, amount uint64) bool {\n\t// TODO: Implementation\n\treturn true\n}\n\nfunc (s *basicGRC1155Token) doSafeBatchTransferAcceptanceCheck(operator, from, to std.Address, batch []TokenID, amounts []uint64) bool {\n\t// TODO: Implementation\n\treturn true\n}\n\nfunc (s *basicGRC1155Token) RenderHome() (str string) {\n\tstr += ufmt.Sprintf(\"# URI:%s\\n\", s.uri)\n\n\treturn\n}\n"},{"name":"basic_grc1155_token_test.gno","body":"package grc1155\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nconst dummyURI = \"ipfs://xyz\"\n\nfunc TestNewBasicGRC1155Token(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n}\n\nfunc TestUri(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\tuassert.Equal(t, dummyURI, dummy.Uri())\n}\n\nfunc TestBalanceOf(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\tbalanceZeroAddressOfToken1, err := dummy.BalanceOf(zeroAddress, tid1)\n\tuassert.Error(t, err, \"should result in error\")\n\n\tbalanceAddr1OfToken1, err := dummy.BalanceOf(addr1, tid1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(0), balanceAddr1OfToken1)\n\n\tdummy.mintBatch(addr1, []TokenID{tid1, tid2}, []uint64{10, 100})\n\tdummy.mintBatch(addr2, []TokenID{tid1}, []uint64{20})\n\n\tbalanceAddr1OfToken1, err = dummy.BalanceOf(addr1, tid1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tbalanceAddr1OfToken2, err := dummy.BalanceOf(addr1, tid2)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tbalanceAddr2OfToken1, err := dummy.BalanceOf(addr2, tid1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tuassert.Equal(t, uint64(10), balanceAddr1OfToken1)\n\tuassert.Equal(t, uint64(100), balanceAddr1OfToken2)\n\tuassert.Equal(t, uint64(20), balanceAddr2OfToken1)\n}\n\nfunc TestBalanceOfBatch(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{addr1, addr2}, []TokenID{tid1, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(0), balanceBatch[0])\n\tuassert.Equal(t, uint64(0), balanceBatch[1])\n\n\tdummy.mintBatch(addr1, []TokenID{tid1}, []uint64{10})\n\tdummy.mintBatch(addr2, []TokenID{tid2}, []uint64{20})\n\n\tbalanceBatch, err = dummy.BalanceOfBatch([]std.Address{addr1, addr2}, []TokenID{tid1, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(10), balanceBatch[0])\n\tuassert.Equal(t, uint64(20), balanceBatch[1])\n}\n\nfunc TestIsApprovedForAll(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(addr1, addr2)\n\tuassert.False(t, isApprovedForAll)\n}\n\nfunc TestSetApprovalForAll(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.OriginCaller()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(caller, addr)\n\tuassert.False(t, isApprovedForAll)\n\n\terr := dummy.SetApprovalForAll(addr, true)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tisApprovedForAll = dummy.IsApprovedForAll(caller, addr)\n\tuassert.True(t, isApprovedForAll)\n\n\terr = dummy.SetApprovalForAll(addr, false)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tisApprovedForAll = dummy.IsApprovedForAll(caller, addr)\n\tuassert.False(t, isApprovedForAll)\n}\n\nfunc TestSafeTransferFrom(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.OriginCaller()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\ttid := TokenID(\"1\")\n\n\tdummy.mintBatch(caller, []TokenID{tid}, []uint64{100})\n\n\terr := dummy.SafeTransferFrom(caller, zeroAddress, tid, 10)\n\tuassert.Error(t, err, \"should result in error\")\n\n\terr = dummy.SafeTransferFrom(caller, addr, tid, 160)\n\tuassert.Error(t, err, \"should result in error\")\n\n\terr = dummy.SafeTransferFrom(caller, addr, tid, 60)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check balance of caller after transfer\n\tbalanceOfCaller, err := dummy.BalanceOf(caller, tid)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(40), balanceOfCaller)\n\n\t// Check balance of addr after transfer\n\tbalanceOfAddr, err := dummy.BalanceOf(addr, tid)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(60), balanceOfAddr)\n}\n\nfunc TestSafeBatchTransferFrom(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.OriginCaller()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\tdummy.mintBatch(caller, []TokenID{tid1, tid2}, []uint64{10, 100})\n\n\terr := dummy.SafeBatchTransferFrom(caller, zeroAddress, []TokenID{tid1, tid2}, []uint64{4, 60})\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.SafeBatchTransferFrom(caller, addr, []TokenID{tid1, tid2}, []uint64{40, 60})\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.SafeBatchTransferFrom(caller, addr, []TokenID{tid1}, []uint64{40, 60})\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.SafeBatchTransferFrom(caller, addr, []TokenID{tid1, tid2}, []uint64{4, 60})\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{caller, addr, caller, addr}, []TokenID{tid1, tid1, tid2, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check token1's balance of caller after batch transfer\n\tuassert.Equal(t, uint64(6), balanceBatch[0])\n\n\t// Check token1's balance of addr after batch transfer\n\tuassert.Equal(t, uint64(4), balanceBatch[1])\n\n\t// Check token2's balance of caller after batch transfer\n\tuassert.Equal(t, uint64(40), balanceBatch[2])\n\n\t// Check token2's balance of addr after batch transfer\n\tuassert.Equal(t, uint64(60), balanceBatch[3])\n}\n\nfunc TestSafeMint(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\terr := dummy.SafeMint(zeroAddress, tid1, 100)\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.SafeMint(addr1, tid1, 100)\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.SafeMint(addr1, tid2, 200)\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.SafeMint(addr2, tid1, 50)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{addr1, addr2, addr1}, []TokenID{tid1, tid1, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\t// Check token1's balance of addr1 after mint\n\tuassert.Equal(t, uint64(100), balanceBatch[0])\n\t// Check token1's balance of addr2 after mint\n\tuassert.Equal(t, uint64(50), balanceBatch[1])\n\t// Check token2's balance of addr1 after mint\n\tuassert.Equal(t, uint64(200), balanceBatch[2])\n}\n\nfunc TestSafeBatchMint(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\terr := dummy.SafeBatchMint(zeroAddress, []TokenID{tid1, tid2}, []uint64{100, 200})\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.SafeBatchMint(addr1, []TokenID{tid1, tid2}, []uint64{100, 200})\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.SafeBatchMint(addr2, []TokenID{tid1, tid2}, []uint64{300, 400})\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{addr1, addr2, addr1, addr2}, []TokenID{tid1, tid1, tid2, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\t// Check token1's balance of addr1 after batch mint\n\tuassert.Equal(t, uint64(100), balanceBatch[0])\n\t// Check token1's balance of addr2 after batch mint\n\tuassert.Equal(t, uint64(300), balanceBatch[1])\n\t// Check token2's balance of addr1 after batch mint\n\tuassert.Equal(t, uint64(200), balanceBatch[2])\n\t// Check token2's balance of addr2 after batch mint\n\tuassert.Equal(t, uint64(400), balanceBatch[3])\n}\n\nfunc TestBurn(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\tdummy.mintBatch(addr, []TokenID{tid1, tid2}, []uint64{100, 200})\n\terr := dummy.Burn(zeroAddress, tid1, uint64(60))\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.Burn(addr, tid1, uint64(160))\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.Burn(addr, tid1, uint64(60))\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.Burn(addr, tid2, uint64(60))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{addr, addr}, []TokenID{tid1, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check token1's balance of addr after burn\n\tuassert.Equal(t, uint64(40), balanceBatch[0])\n\t// Check token2's balance of addr after burn\n\tuassert.Equal(t, uint64(140), balanceBatch[1])\n}\n\nfunc TestBatchBurn(t *testing.T) {\n\tdummy := NewBasicGRC1155Token(dummyURI)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\ttid1 := TokenID(\"1\")\n\ttid2 := TokenID(\"2\")\n\n\tdummy.mintBatch(addr, []TokenID{tid1, tid2}, []uint64{100, 200})\n\terr := dummy.BatchBurn(zeroAddress, []TokenID{tid1, tid2}, []uint64{60, 60})\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.BatchBurn(addr, []TokenID{tid1, tid2}, []uint64{160, 60})\n\tuassert.Error(t, err, \"should result in error\")\n\terr = dummy.BatchBurn(addr, []TokenID{tid1, tid2}, []uint64{60, 60})\n\tuassert.NoError(t, err, \"should not result in error\")\n\tbalanceBatch, err := dummy.BalanceOfBatch([]std.Address{addr, addr}, []TokenID{tid1, tid2})\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check token1's balance of addr after batch burn\n\tuassert.Equal(t, uint64(40), balanceBatch[0])\n\t// Check token2's balance of addr after batch burn\n\tuassert.Equal(t, uint64(140), balanceBatch[1])\n}\n"},{"name":"errors.gno","body":"package grc1155\n\nimport \"errors\"\n\nvar (\n\tErrInvalidAddress = errors.New(\"invalid address\")\n\tErrMismatchLength = errors.New(\"accounts and ids length mismatch\")\n\tErrCannotTransferToSelf = errors.New(\"cannot send transfer to self\")\n\tErrTransferToRejectedOrNonGRC1155Receiver = errors.New(\"transfer to rejected or non GRC1155Receiver implementer\")\n\tErrCallerIsNotOwnerOrApproved = errors.New(\"caller is not token owner or approved\")\n\tErrInsufficientBalance = errors.New(\"insufficient balance for transfer\")\n\tErrBurnAmountExceedsBalance = errors.New(\"burn amount exceeds balance\")\n)\n"},{"name":"igrc1155.gno","body":"package grc1155\n\nimport \"std\"\n\ntype IGRC1155 interface {\n\tSafeTransferFrom(from, to std.Address, tid TokenID, amount uint64) error\n\tSafeBatchTransferFrom(from, to std.Address, batch []TokenID, amounts []uint64) error\n\tBalanceOf(owner std.Address, tid TokenID) (uint64, error)\n\tBalanceOfBatch(owners []std.Address, batch []TokenID) ([]uint64, error)\n\tSetApprovalForAll(operator std.Address, approved bool) error\n\tIsApprovedForAll(owner, operator std.Address) bool\n}\n\ntype TokenID string\n\ntype TransferSingleEvent struct {\n\tOperator std.Address\n\tFrom std.Address\n\tTo std.Address\n\tTokenID TokenID\n\tAmount uint64\n}\n\ntype TransferBatchEvent struct {\n\tOperator std.Address\n\tFrom std.Address\n\tTo std.Address\n\tBatch []TokenID\n\tAmounts []uint64\n}\n\ntype ApprovalForAllEvent struct {\n\tOwner std.Address\n\tOperator std.Address\n\tApproved bool\n}\n\ntype UpdateURIEvent struct {\n\tURI string\n}\n"},{"name":"util.gno","body":"package grc1155\n\nimport (\n\t\"std\"\n)\n\nconst zeroAddress std.Address = \"\"\n\nfunc isValidAddress(addr std.Address) bool {\n\tif !addr.IsValid() {\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc emit(event interface{}) {\n\t// TODO: setup a pubsub system here?\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":"banker.gno","body":"package grc20\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Banker implements a token banker with admin privileges.\n//\n// The Banker is intended to be used in two main ways:\n// 1. as a temporary object used to make the initial minting, then deleted.\n// 2. preserved in an unexported variable to support conditional administrative\n// tasks protected by the contract.\ntype Banker struct {\n\tname string\n\tsymbol string\n\tdecimals uint\n\ttotalSupply uint64\n\tbalances avl.Tree // std.Address(owner) -\u003e uint64\n\tallowances avl.Tree // string(owner+\":\"+spender) -\u003e uint64\n\ttoken *token // to share the same pointer\n}\n\nfunc NewBanker(name, symbol string, decimals uint) *Banker {\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\tb := Banker{\n\t\tname: name,\n\t\tsymbol: symbol,\n\t\tdecimals: decimals,\n\t}\n\tt := \u0026token{banker: \u0026b}\n\tb.token = t\n\treturn \u0026b\n}\n\nfunc (b Banker) Token() Token { return b.token } // Token returns a grc20 safe-object implementation.\nfunc (b Banker) GetName() string { return b.name }\nfunc (b Banker) GetSymbol() string { return b.symbol }\nfunc (b Banker) GetDecimals() uint { return b.decimals }\nfunc (b Banker) TotalSupply() uint64 { return b.totalSupply }\nfunc (b Banker) KnownAccounts() int { return b.balances.Size() }\n\nfunc (b *Banker) Mint(address std.Address, amount uint64) error {\n\tif !address.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\n\t// TODO: check for overflow\n\n\tb.totalSupply += amount\n\tcurrentBalance := b.BalanceOf(address)\n\tnewBalance := currentBalance + amount\n\n\tb.balances.Set(string(address), newBalance)\n\n\tstd.Emit(\n\t\tMintEvent,\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\nfunc (b *Banker) Burn(address std.Address, amount uint64) error {\n\tif !address.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\t// TODO: check for overflow\n\n\tcurrentBalance := b.BalanceOf(address)\n\tif currentBalance \u003c amount {\n\t\treturn ErrInsufficientBalance\n\t}\n\n\tb.totalSupply -= amount\n\tnewBalance := currentBalance - amount\n\n\tb.balances.Set(string(address), newBalance)\n\n\tstd.Emit(\n\t\tBurnEvent,\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\nfunc (b Banker) BalanceOf(address std.Address) uint64 {\n\tbalance, found := b.balances.Get(address.String())\n\tif !found {\n\t\treturn 0\n\t}\n\treturn balance.(uint64)\n}\n\nfunc (b *Banker) 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 := b.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\tb.allowances.Remove(key)\n\t} else {\n\t\tb.allowances.Set(key, newAllowance)\n\t}\n\n\treturn nil\n}\n\nfunc (b *Banker) 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\ttoBalance := b.BalanceOf(to)\n\tfromBalance := b.BalanceOf(from)\n\n\tif fromBalance \u003c amount {\n\t\treturn ErrInsufficientBalance\n\t}\n\n\tnewToBalance := toBalance + amount\n\tnewFromBalance := fromBalance - amount\n\n\tb.balances.Set(string(to), newToBalance)\n\tb.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\nfunc (b *Banker) TransferFrom(spender, from, to std.Address, amount uint64) error {\n\tif err := b.SpendAllowance(from, spender, amount); err != nil {\n\t\treturn err\n\t}\n\treturn b.Transfer(from, to, amount)\n}\n\nfunc (b *Banker) Allowance(owner, spender std.Address) uint64 {\n\tallowance, found := b.allowances.Get(allowanceKey(owner, spender))\n\tif !found {\n\t\treturn 0\n\t}\n\treturn allowance.(uint64)\n}\n\nfunc (b *Banker) Approve(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\tb.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\nfunc (b *Banker) RenderHome() string {\n\tstr := \"\"\n\tstr += ufmt.Sprintf(\"# %s ($%s)\\n\\n\", b.name, b.symbol)\n\tstr += ufmt.Sprintf(\"* **Decimals**: %d\\n\", b.decimals)\n\tstr += ufmt.Sprintf(\"* **Total supply**: %d\\n\", b.totalSupply)\n\tstr += ufmt.Sprintf(\"* **Known accounts**: %d\\n\", b.KnownAccounts())\n\treturn str\n}\n\nfunc allowanceKey(owner, spender std.Address) string {\n\treturn owner.String() + \":\" + spender.String()\n}\n"},{"name":"banker_test.gno","body":"package grc20\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestBankerImpl(t *testing.T) {\n\tdummy := NewBanker(\"Dummy\", \"DUMMY\", 4)\n\turequire.False(t, dummy == nil, \"dummy should not be nil\")\n}\n\nfunc TestAllowance(t *testing.T) {\n\tvar (\n\t\towner = testutils.TestAddress(\"owner\")\n\t\tspender = testutils.TestAddress(\"spender\")\n\t\tdest = testutils.TestAddress(\"dest\")\n\t)\n\n\tb := NewBanker(\"Dummy\", \"DUMMY\", 6)\n\turequire.NoError(t, b.Mint(owner, 100000000))\n\turequire.NoError(t, b.Approve(owner, spender, 5000000))\n\turequire.Error(t, b.TransferFrom(spender, owner, dest, 10000000), ErrInsufficientAllowance.Error(), \"should not be able to transfer more than approved\")\n\n\ttests := []struct {\n\t\tspend uint64\n\t\texp uint64\n\t}{\n\t\t{3, 4999997},\n\t\t{999997, 4000000},\n\t\t{4000000, 0},\n\t}\n\n\tfor _, tt := range tests {\n\t\tb0 := b.BalanceOf(dest)\n\t\turequire.NoError(t, b.TransferFrom(spender, owner, dest, tt.spend))\n\t\ta := b.Allowance(owner, spender)\n\t\turequire.Equal(t, a, tt.exp, ufmt.Sprintf(\"allowance exp: %d, got %d\", tt.exp, a))\n\t\tb := b.BalanceOf(dest)\n\t\texpB := b0 + tt.spend\n\t\turequire.Equal(t, b, expB, ufmt.Sprintf(\"balance exp: %d, got %d\", expB, b))\n\t}\n\n\turequire.Error(t, b.TransferFrom(spender, owner, dest, 1), \"no allowance\")\n\tkey := allowanceKey(owner, spender)\n\turequire.False(t, b.allowances.Has(key), \"allowance should be removed\")\n\turequire.Equal(t, b.Allowance(owner, spender), uint64(0), \"allowance should be 0\")\n}\n"},{"name":"token.gno","body":"package grc20\n\nimport (\n\t\"std\"\n)\n\n// token implements the Token interface.\n//\n// It is generated with Banker.Token().\n// It can safely be exposed publicly.\ntype token struct {\n\tbanker *Banker\n}\n\n// var _ Token = (*token)(nil)\nfunc (t *token) GetName() string { return t.banker.name }\nfunc (t *token) GetSymbol() string { return t.banker.symbol }\nfunc (t *token) GetDecimals() uint { return t.banker.decimals }\nfunc (t *token) TotalSupply() uint64 { return t.banker.totalSupply }\n\nfunc (t *token) BalanceOf(owner std.Address) uint64 {\n\treturn t.banker.BalanceOf(owner)\n}\n\nfunc (t *token) Transfer(to std.Address, amount uint64) error {\n\tcaller := std.PreviousRealm().Addr()\n\treturn t.banker.Transfer(caller, to, amount)\n}\n\nfunc (t *token) Allowance(owner, spender std.Address) uint64 {\n\treturn t.banker.Allowance(owner, spender)\n}\n\nfunc (t *token) Approve(spender std.Address, amount uint64) error {\n\tcaller := std.PreviousRealm().Addr()\n\treturn t.banker.Approve(caller, spender, amount)\n}\n\nfunc (t *token) TransferFrom(from, to std.Address, amount uint64) error {\n\tspender := std.PreviousRealm().Addr()\n\tif err := t.banker.SpendAllowance(from, spender, amount); err != nil {\n\t\treturn err\n\t}\n\treturn t.banker.Transfer(from, to, amount)\n}\n"},{"name":"token_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/ufmt\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestUserTokenImpl(t *testing.T) {\n\tbank := NewBanker(\"Dummy\", \"DUMMY\", 4)\n\ttok := bank.Token()\n\t_ = tok\n}\n\nfunc TestUserApprove(t *testing.T) {\n\towner := testutils.TestAddress(\"owner\")\n\tspender := testutils.TestAddress(\"spender\")\n\tdest := testutils.TestAddress(\"dest\")\n\n\tbank := NewBanker(\"Dummy\", \"DUMMY\", 6)\n\ttok := bank.Token()\n\n\t// Set owner as the original caller\n\tstd.TestSetOriginCaller(owner)\n\t// Mint 100000000 tokens for owner\n\turequire.NoError(t, bank.Mint(owner, 100000000))\n\n\t// Approve spender to spend 5000000 tokens\n\turequire.NoError(t, tok.Approve(spender, 5000000))\n\n\t// Set spender as the original caller\n\tstd.TestSetOriginCaller(spender)\n\t// Try to transfer 10000000 tokens from owner to dest, should fail because it exceeds allowance\n\turequire.Error(t,\n\t\ttok.TransferFrom(owner, dest, 10000000),\n\t\tErrInsufficientAllowance.Error(),\n\t\t\"should not be able to transfer more than approved\",\n\t)\n\n\t// Define a set of test data with spend amount and expected remaining allowance\n\ttests := []struct {\n\t\tspend uint64 // Spend amount\n\t\texp uint64 // Remaining allowance\n\t}{\n\t\t{3, 4999997},\n\t\t{999997, 4000000},\n\t\t{4000000, 0},\n\t}\n\n\t// perform transfer operation,and check if allowance and balance are correct\n\tfor _, tt := range tests {\n\t\tb0 := tok.BalanceOf(dest)\n\t\t// Perform transfer from owner to dest\n\t\turequire.NoError(t, tok.TransferFrom(owner, dest, tt.spend))\n\t\ta := tok.Allowance(owner, spender)\n\t\t// Check if allowance equals expected value\n\t\turequire.True(t, a == tt.exp, ufmt.Sprintf(\"allowance exp: %d,got %d\", tt.exp, a))\n\n\t\t// Get dest current balance\n\t\tb := tok.BalanceOf(dest)\n\t\t// Calculate expected balance ,should be initial balance plus transfer amount\n\t\texpB := b0 + tt.spend\n\t\t// Check if balance equals expected value\n\t\turequire.True(t, b == expB, ufmt.Sprintf(\"balance exp: %d,got %d\", expB, b))\n\t}\n\n\t// Try to transfer one token from owner to dest ,should fail because no allowance left\n\turequire.Error(t, tok.TransferFrom(owner, dest, 1), ErrInsufficientAllowance.Error(), \"no allowance\")\n}\n"},{"name":"types.gno","body":"package grc20\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/grc/exts\"\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)\n\ntype Token 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 the risk\n\t// that someone may use both the old and the new allowance by unfortunate\n\t// transaction ordering. One possible solution to mitigate this race\n\t// condition is to first reduce the spender's allowance to 0 and set the\n\t// 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\nconst (\n\tMintEvent = \"Mint\"\n\tBurnEvent = \"Burn\"\n\tTransferEvent = \"Transfer\"\n\tApprovalEvent = \"Approval\"\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":"grc721","path":"gno.land/p/demo/grc/grc721","files":[{"name":"basic_nft.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype basicNFT struct {\n\tname string\n\tsymbol string\n\towners avl.Tree // tokenId -\u003e OwnerAddress\n\tbalances avl.Tree // OwnerAddress -\u003e TokenCount\n\ttokenApprovals avl.Tree // TokenId -\u003e ApprovedAddress\n\ttokenURIs avl.Tree // TokenId -\u003e URIs\n\toperatorApprovals avl.Tree // \"OwnerAddress:OperatorAddress\" -\u003e bool\n}\n\n// Returns new basic NFT\nfunc NewBasicNFT(name string, symbol string) *basicNFT {\n\treturn \u0026basicNFT{\n\t\tname: name,\n\t\tsymbol: symbol,\n\n\t\towners: avl.Tree{},\n\t\tbalances: avl.Tree{},\n\t\ttokenApprovals: avl.Tree{},\n\t\ttokenURIs: avl.Tree{},\n\t\toperatorApprovals: avl.Tree{},\n\t}\n}\n\nfunc (s *basicNFT) Name() string { return s.name }\nfunc (s *basicNFT) Symbol() string { return s.symbol }\nfunc (s *basicNFT) TokenCount() uint64 { return uint64(s.owners.Size()) }\n\n// BalanceOf returns balance of input address\nfunc (s *basicNFT) BalanceOf(addr std.Address) (uint64, error) {\n\tif err := isValidAddress(addr); err != nil {\n\t\treturn 0, err\n\t}\n\n\tbalance, found := s.balances.Get(addr.String())\n\tif !found {\n\t\treturn 0, nil\n\t}\n\n\treturn balance.(uint64), nil\n}\n\n// OwnerOf returns owner of input token id\nfunc (s *basicNFT) OwnerOf(tid TokenID) (std.Address, error) {\n\towner, found := s.owners.Get(string(tid))\n\tif !found {\n\t\treturn \"\", ErrInvalidTokenId\n\t}\n\n\treturn owner.(std.Address), nil\n}\n\n// TokenURI returns the URI of input token id\nfunc (s *basicNFT) TokenURI(tid TokenID) (string, error) {\n\turi, found := s.tokenURIs.Get(string(tid))\n\tif !found {\n\t\treturn \"\", ErrInvalidTokenId\n\t}\n\n\treturn uri.(string), nil\n}\n\nfunc (s *basicNFT) SetTokenURI(tid TokenID, tURI TokenURI) (bool, error) {\n\t// check for invalid TokenID\n\tif !s.exists(tid) {\n\t\treturn false, ErrInvalidTokenId\n\t}\n\n\t// check for the right owner\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tcaller := std.PreviousRealm().Addr()\n\tif caller != owner {\n\t\treturn false, ErrCallerIsNotOwner\n\t}\n\ts.tokenURIs.Set(string(tid), string(tURI))\n\treturn true, nil\n}\n\n// IsApprovedForAll returns true if operator is approved for all by the owner.\n// Otherwise, returns false\nfunc (s *basicNFT) IsApprovedForAll(owner, operator std.Address) bool {\n\tkey := owner.String() + \":\" + operator.String()\n\t_, found := s.operatorApprovals.Get(key)\n\tif !found {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// Approve approves the input address for particular token\nfunc (s *basicNFT) Approve(to std.Address, tid TokenID) error {\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner == to {\n\t\treturn ErrApprovalToCurrentOwner\n\t}\n\n\tcaller := std.PreviousRealm().Addr()\n\tif caller != owner \u0026\u0026 !s.IsApprovedForAll(owner, caller) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\ts.tokenApprovals.Set(string(tid), to.String())\n\tevent := ApprovalEvent{owner, to, tid}\n\temit(\u0026event)\n\n\treturn nil\n}\n\n// GetApproved return the approved address for token\nfunc (s *basicNFT) GetApproved(tid TokenID) (std.Address, error) {\n\taddr, found := s.tokenApprovals.Get(string(tid))\n\tif !found {\n\t\treturn zeroAddress, ErrTokenIdNotHasApproved\n\t}\n\n\treturn std.Address(addr.(string)), nil\n}\n\n// SetApprovalForAll can approve the operator to operate on all tokens\nfunc (s *basicNFT) SetApprovalForAll(operator std.Address, approved bool) error {\n\tif err := isValidAddress(operator); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tcaller := std.PreviousRealm().Addr()\n\treturn s.setApprovalForAll(caller, operator, approved)\n}\n\n// Safely transfers `tokenId` token from `from` to `to`, checking that\n// contract recipients are aware of the GRC721 protocol to prevent\n// tokens from being forever locked.\nfunc (s *basicNFT) SafeTransferFrom(from, to std.Address, tid TokenID) error {\n\tcaller := std.PreviousRealm().Addr()\n\tif !s.isApprovedOrOwner(caller, tid) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.transfer(from, to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.checkOnGRC721Received(from, to, tid) {\n\t\treturn ErrTransferToNonGRC721Receiver\n\t}\n\n\treturn nil\n}\n\n// Transfers `tokenId` token from `from` to `to`.\nfunc (s *basicNFT) TransferFrom(from, to std.Address, tid TokenID) error {\n\tcaller := std.PreviousRealm().Addr()\n\tif !s.isApprovedOrOwner(caller, tid) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.transfer(from, to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// Mints `tokenId` and transfers it to `to`.\nfunc (s *basicNFT) Mint(to std.Address, tid TokenID) error {\n\treturn s.mint(to, tid)\n}\n\n// Mints `tokenId` and transfers it to `to`. Also checks that\n// contract recipients are using GRC721 protocol\nfunc (s *basicNFT) SafeMint(to std.Address, tid TokenID) error {\n\terr := s.mint(to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.checkOnGRC721Received(zeroAddress, to, tid) {\n\t\treturn ErrTransferToNonGRC721Receiver\n\t}\n\n\treturn nil\n}\n\nfunc (s *basicNFT) Burn(tid TokenID) error {\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ts.beforeTokenTransfer(owner, zeroAddress, tid, 1)\n\n\ts.tokenApprovals.Remove(string(tid))\n\tbalance, err := s.BalanceOf(owner)\n\tif err != nil {\n\t\treturn err\n\t}\n\tbalance -= 1\n\ts.balances.Set(owner.String(), balance)\n\ts.owners.Remove(string(tid))\n\n\tevent := TransferEvent{owner, zeroAddress, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(owner, zeroAddress, tid, 1)\n\n\treturn nil\n}\n\n/* Helper methods */\n\n// Helper for SetApprovalForAll()\nfunc (s *basicNFT) setApprovalForAll(owner, operator std.Address, approved bool) error {\n\tif owner == operator {\n\t\treturn ErrApprovalToCurrentOwner\n\t}\n\n\tkey := owner.String() + \":\" + operator.String()\n\ts.operatorApprovals.Set(key, approved)\n\n\tevent := ApprovalForAllEvent{owner, operator, approved}\n\temit(\u0026event)\n\n\treturn nil\n}\n\n// Helper for TransferFrom() and SafeTransferFrom()\nfunc (s *basicNFT) transfer(from, to std.Address, tid TokenID) error {\n\tif err := isValidAddress(from); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\tif err := isValidAddress(to); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tif from == to {\n\t\treturn ErrCannotTransferToSelf\n\t}\n\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner != from {\n\t\treturn ErrTransferFromIncorrectOwner\n\t}\n\n\ts.beforeTokenTransfer(from, to, tid, 1)\n\n\t// Check that tokenId was not transferred by `beforeTokenTransfer`\n\towner, err = s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner != from {\n\t\treturn ErrTransferFromIncorrectOwner\n\t}\n\n\ts.tokenApprovals.Remove(string(tid))\n\tfromBalance, err := s.BalanceOf(from)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance, err := s.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfromBalance -= 1\n\ttoBalance += 1\n\ts.balances.Set(from.String(), fromBalance)\n\ts.balances.Set(to.String(), toBalance)\n\ts.owners.Set(string(tid), to)\n\n\tevent := TransferEvent{from, to, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(from, to, tid, 1)\n\n\treturn nil\n}\n\n// Helper for Mint() and SafeMint()\nfunc (s *basicNFT) mint(to std.Address, tid TokenID) error {\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\tif s.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ts.beforeTokenTransfer(zeroAddress, to, tid, 1)\n\n\t// Check that tokenId was not minted by `beforeTokenTransfer`\n\tif s.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ttoBalance, err := s.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance += 1\n\ts.balances.Set(to.String(), toBalance)\n\ts.owners.Set(string(tid), to)\n\n\tevent := TransferEvent{zeroAddress, to, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(zeroAddress, to, tid, 1)\n\n\treturn nil\n}\n\nfunc (s *basicNFT) isApprovedOrOwner(addr std.Address, tid TokenID) bool {\n\towner, found := s.owners.Get(string(tid))\n\tif !found {\n\t\treturn false\n\t}\n\n\tif addr == owner.(std.Address) || s.IsApprovedForAll(owner.(std.Address), addr) {\n\t\treturn true\n\t}\n\n\t_, err := s.GetApproved(tid)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// Checks if token id already exists\nfunc (s *basicNFT) exists(tid TokenID) bool {\n\t_, found := s.owners.Get(string(tid))\n\treturn found\n}\n\nfunc (s *basicNFT) beforeTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicNFT) afterTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicNFT) checkOnGRC721Received(from, to std.Address, tid TokenID) bool {\n\t// TODO: Implementation\n\treturn true\n}\n\nfunc (s *basicNFT) RenderHome() (str string) {\n\tstr += ufmt.Sprintf(\"# %s ($%s)\\n\\n\", s.name, s.symbol)\n\tstr += ufmt.Sprintf(\"* **Total supply**: %d\\n\", s.TokenCount())\n\tstr += ufmt.Sprintf(\"* **Known accounts**: %d\\n\", s.balances.Size())\n\n\treturn\n}\n"},{"name":"basic_nft_test.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\tdummyNFTName = \"DummyNFT\"\n\tdummyNFTSymbol = \"DNFT\"\n)\n\nfunc TestNewBasicNFT(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n}\n\nfunc TestName(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tname := dummy.Name()\n\tuassert.Equal(t, dummyNFTName, name)\n}\n\nfunc TestSymbol(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tsymbol := dummy.Symbol()\n\tuassert.Equal(t, dummyNFTSymbol, symbol)\n}\n\nfunc TestTokenCount(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcount := dummy.TokenCount()\n\tuassert.Equal(t, uint64(0), count)\n\n\tdummy.mint(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\", TokenID(\"1\"))\n\tdummy.mint(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\", TokenID(\"2\"))\n\n\tcount = dummy.TokenCount()\n\tuassert.Equal(t, uint64(2), count)\n}\n\nfunc TestBalanceOf(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\tbalanceAddr1, err := dummy.BalanceOf(addr1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(0), balanceAddr1)\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\tdummy.mint(addr1, TokenID(\"2\"))\n\tdummy.mint(addr2, TokenID(\"3\"))\n\n\tbalanceAddr1, err = dummy.BalanceOf(addr1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tbalanceAddr2, err := dummy.BalanceOf(addr2)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tuassert.Equal(t, uint64(2), balanceAddr1)\n\tuassert.Equal(t, uint64(1), balanceAddr2)\n}\n\nfunc TestOwnerOf(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\towner, err := dummy.OwnerOf(TokenID(\"invalid\"))\n\tuassert.Error(t, err, \"should not result in error\")\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\tdummy.mint(addr2, TokenID(\"2\"))\n\n\t// Checking for token id \"1\"\n\towner, err = dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr1.String(), owner.String())\n\n\t// Checking for token id \"2\"\n\towner, err = dummy.OwnerOf(TokenID(\"2\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr2.String(), owner.String())\n}\n\nfunc TestIsApprovedForAll(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(addr1, addr2)\n\tuassert.False(t, isApprovedForAll)\n}\n\nfunc TestSetApprovalForAll(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PreviousRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(caller, addr)\n\tuassert.False(t, isApprovedForAll)\n\n\terr := dummy.SetApprovalForAll(addr, true)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tisApprovedForAll = dummy.IsApprovedForAll(caller, addr)\n\tuassert.True(t, isApprovedForAll)\n}\n\nfunc TestGetApproved(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tapprovedAddr, err := dummy.GetApproved(TokenID(\"invalid\"))\n\tuassert.Error(t, err, \"should result in error\")\n}\n\nfunc TestApprove(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PreviousRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\n\t_, err := dummy.GetApproved(TokenID(\"1\"))\n\tuassert.Error(t, err, \"should result in error\")\n\n\terr = dummy.Approve(addr, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tapprovedAddr, err := dummy.GetApproved(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should result in error\")\n\tuassert.Equal(t, addr.String(), approvedAddr.String())\n}\n\nfunc TestTransferFrom(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PreviousRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\tdummy.mint(caller, TokenID(\"2\"))\n\n\terr := dummy.TransferFrom(caller, addr, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should result in error\")\n\n\t// Check balance of caller after transfer\n\tbalanceOfCaller, err := dummy.BalanceOf(caller)\n\tuassert.NoError(t, err, \"should result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfCaller)\n\n\t// Check balance of addr after transfer\n\tbalanceOfAddr, err := dummy.BalanceOf(addr)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfAddr)\n\n\t// Check Owner of transferred Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should result in error\")\n\tuassert.Equal(t, addr.String(), owner.String())\n}\n\nfunc TestSafeTransferFrom(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PreviousRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\tdummy.mint(caller, TokenID(\"2\"))\n\n\terr := dummy.SafeTransferFrom(caller, addr, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check balance of caller after transfer\n\tbalanceOfCaller, err := dummy.BalanceOf(caller)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfCaller)\n\n\t// Check balance of addr after transfer\n\tbalanceOfAddr, err := dummy.BalanceOf(addr)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfAddr)\n\n\t// Check Owner of transferred Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr.String(), owner.String())\n}\n\nfunc TestMint(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\terr := dummy.Mint(addr1, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.Mint(addr1, TokenID(\"2\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.Mint(addr2, TokenID(\"3\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Try minting duplicate token id\n\terr = dummy.Mint(addr2, TokenID(\"1\"))\n\tuassert.Error(t, err, \"should not result in error\")\n\n\t// Check Owner of Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr1.String(), owner.String())\n}\n\nfunc TestBurn(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(addr, TokenID(\"1\"))\n\tdummy.mint(addr, TokenID(\"2\"))\n\n\terr := dummy.Burn(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check Owner of Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.Error(t, err, \"should result in error\")\n}\n\nfunc TestSetTokenURI(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\ttokenURI := \"http://example.com/token\"\n\n\tstd.TestSetOriginCaller(std.Address(addr1)) // addr1\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\t_, derr := dummy.SetTokenURI(TokenID(\"1\"), TokenURI(tokenURI))\n\tuassert.NoError(t, derr, \"should not result in error\")\n\n\t// Test case: Invalid token ID\n\t_, err := dummy.SetTokenURI(TokenID(\"3\"), TokenURI(tokenURI))\n\tuassert.ErrorIs(t, err, ErrInvalidTokenId)\n\n\tstd.TestSetOriginCaller(std.Address(addr2)) // addr2\n\n\t_, cerr := dummy.SetTokenURI(TokenID(\"1\"), TokenURI(tokenURI)) // addr2 trying to set URI for token 1\n\tuassert.ErrorIs(t, cerr, ErrCallerIsNotOwner)\n\n\t// Test case: Retrieving TokenURI\n\tstd.TestSetOriginCaller(std.Address(addr1)) // addr1\n\n\tdummyTokenURI, err := dummy.TokenURI(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"TokenURI error\")\n\tuassert.Equal(t, string(tokenURI), string(dummyTokenURI))\n}\n"},{"name":"errors.gno","body":"package grc721\n\nimport \"errors\"\n\nvar (\n\tErrInvalidTokenId = errors.New(\"invalid token id\")\n\tErrInvalidAddress = errors.New(\"invalid address\")\n\tErrTokenIdNotHasApproved = errors.New(\"token id not approved for anyone\")\n\tErrApprovalToCurrentOwner = errors.New(\"approval to current owner\")\n\tErrCallerIsNotOwner = errors.New(\"caller is not token owner\")\n\tErrCallerNotApprovedForAll = errors.New(\"caller is not approved for all\")\n\tErrCannotTransferToSelf = errors.New(\"cannot send transfer to self\")\n\tErrTransferFromIncorrectOwner = errors.New(\"transfer from incorrect owner\")\n\tErrTransferToNonGRC721Receiver = errors.New(\"transfer to non GRC721Receiver implementer\")\n\tErrCallerIsNotOwnerOrApproved = errors.New(\"caller is not token owner or approved\")\n\tErrTokenIdAlreadyExists = errors.New(\"token id already exists\")\n\n\t// ERC721Royalty\n\tErrInvalidRoyaltyPercentage = errors.New(\"invalid royalty percentage\")\n\tErrInvalidRoyaltyPaymentAddress = errors.New(\"invalid royalty paymentAddress\")\n\tErrCannotCalculateRoyaltyAmount = errors.New(\"cannot calculate royalty amount\")\n)\n"},{"name":"grc721_metadata.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n// metadataNFT represents an NFT with metadata extensions.\ntype metadataNFT struct {\n\t*basicNFT // Embedded basicNFT struct for basic NFT functionality\n\textensions *avl.Tree // AVL tree for storing metadata extensions\n}\n\n// Ensure that metadataNFT implements the IGRC721MetadataOnchain interface.\nvar _ IGRC721MetadataOnchain = (*metadataNFT)(nil)\n\n// NewNFTWithMetadata creates a new basic NFT with metadata extensions.\nfunc NewNFTWithMetadata(name string, symbol string) *metadataNFT {\n\t// Create a new basic NFT\n\tnft := NewBasicNFT(name, symbol)\n\n\t// Return a metadataNFT with basicNFT embedded and an empty AVL tree for extensions\n\treturn \u0026metadataNFT{\n\t\tbasicNFT: nft,\n\t\textensions: avl.NewTree(),\n\t}\n}\n\n// SetTokenMetadata sets metadata for a given token ID.\nfunc (s *metadataNFT) SetTokenMetadata(tid TokenID, metadata Metadata) error {\n\t// Check if the caller is the owner of the token\n\towner, err := s.basicNFT.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcaller := std.PreviousRealm().Addr()\n\tif caller != owner {\n\t\treturn ErrCallerIsNotOwner\n\t}\n\n\t// Set the metadata for the token ID in the extensions AVL tree\n\ts.extensions.Set(string(tid), metadata)\n\treturn nil\n}\n\n// TokenMetadata retrieves metadata for a given token ID.\nfunc (s *metadataNFT) TokenMetadata(tid TokenID) (Metadata, error) {\n\t// Retrieve metadata from the extensions AVL tree\n\tmetadata, found := s.extensions.Get(string(tid))\n\tif !found {\n\t\treturn Metadata{}, ErrInvalidTokenId\n\t}\n\n\treturn metadata.(Metadata), nil\n}\n\n// mint mints a new token and assigns it to the specified address.\nfunc (s *metadataNFT) mint(to std.Address, tid TokenID) error {\n\t// Check if the address is valid\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\t// Check if the token ID already exists\n\tif s.basicNFT.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ts.basicNFT.beforeTokenTransfer(zeroAddress, to, tid, 1)\n\n\t// Check if the token ID was minted by beforeTokenTransfer\n\tif s.basicNFT.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\t// Increment balance of the recipient address\n\ttoBalance, err := s.basicNFT.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance += 1\n\ts.basicNFT.balances.Set(to.String(), toBalance)\n\n\t// Set owner of the token ID to the recipient address\n\ts.basicNFT.owners.Set(string(tid), to)\n\n\t// Emit transfer event\n\tevent := TransferEvent{zeroAddress, to, tid}\n\temit(\u0026event)\n\n\ts.basicNFT.afterTokenTransfer(zeroAddress, to, tid, 1)\n\n\treturn nil\n}\n"},{"name":"grc721_metadata_test.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestSetMetadata(t *testing.T) {\n\t// Create a new dummy NFT with metadata\n\tdummy := NewNFTWithMetadata(dummyNFTName, dummyNFTSymbol)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n\n\t// Define addresses for testing purposes\n\taddr1 := testutils.TestAddress(\"alice\")\n\taddr2 := testutils.TestAddress(\"bob\")\n\n\t// Define metadata attributes\n\tname := \"test\"\n\tdescription := \"test\"\n\timage := \"test\"\n\timageData := \"test\"\n\texternalURL := \"test\"\n\tattributes := []Trait{}\n\tbackgroundColor := \"test\"\n\tanimationURL := \"test\"\n\tyoutubeURL := \"test\"\n\n\t// Set the original caller to addr1\n\tstd.TestSetOriginCaller(addr1) // addr1\n\n\t// Mint a new token for addr1\n\tdummy.mint(addr1, TokenID(\"1\"))\n\n\t// Set metadata for token 1\n\tderr := dummy.SetTokenMetadata(TokenID(\"1\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if there was an error setting metadata\n\tuassert.NoError(t, derr, \"Should not result in error\")\n\n\t// Test case: Invalid token ID\n\terr := dummy.SetTokenMetadata(TokenID(\"3\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if the error returned matches the expected error\n\tuassert.ErrorIs(t, err, ErrInvalidTokenId)\n\n\t// Set the original caller to addr2\n\tstd.TestSetOriginCaller(addr2) // addr2\n\n\t// Try to set metadata for token 1 from addr2 (should fail)\n\tcerr := dummy.SetTokenMetadata(TokenID(\"1\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if the error returned matches the expected error\n\tuassert.ErrorIs(t, cerr, ErrCallerIsNotOwner)\n\n\t// Set the original caller back to addr1\n\tstd.TestSetOriginCaller(addr1) // addr1\n\n\t// Retrieve metadata for token 1\n\tdummyMetadata, err := dummy.TokenMetadata(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"Metadata error\")\n\n\t// Check if metadata attributes match expected values\n\tuassert.Equal(t, image, dummyMetadata.Image)\n\tuassert.Equal(t, imageData, dummyMetadata.ImageData)\n\tuassert.Equal(t, externalURL, dummyMetadata.ExternalURL)\n\tuassert.Equal(t, description, dummyMetadata.Description)\n\tuassert.Equal(t, name, dummyMetadata.Name)\n\tuassert.Equal(t, len(attributes), len(dummyMetadata.Attributes))\n\tuassert.Equal(t, backgroundColor, dummyMetadata.BackgroundColor)\n\tuassert.Equal(t, animationURL, dummyMetadata.AnimationURL)\n\tuassert.Equal(t, youtubeURL, dummyMetadata.YoutubeURL)\n}\n"},{"name":"grc721_royalty.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n// royaltyNFT represents a non-fungible token (NFT) with royalty functionality.\ntype royaltyNFT struct {\n\t*metadataNFT // Embedding metadataNFT for NFT functionality\n\ttokenRoyaltyInfo *avl.Tree // AVL tree to store royalty information for each token\n\tmaxRoyaltyPercentage uint64 // maxRoyaltyPercentage represents the maximum royalty percentage that can be charged every sale\n}\n\n// Ensure that royaltyNFT implements the IGRC2981 interface.\nvar _ IGRC2981 = (*royaltyNFT)(nil)\n\n// NewNFTWithRoyalty creates a new royalty NFT with the specified name, symbol, and royalty calculator.\nfunc NewNFTWithRoyalty(name string, symbol string) *royaltyNFT {\n\t// Create a new NFT with metadata\n\tnft := NewNFTWithMetadata(name, symbol)\n\n\treturn \u0026royaltyNFT{\n\t\tmetadataNFT: nft,\n\t\ttokenRoyaltyInfo: avl.NewTree(),\n\t\tmaxRoyaltyPercentage: 100,\n\t}\n}\n\n// SetTokenRoyalty sets the royalty information for a specific token ID.\nfunc (r *royaltyNFT) SetTokenRoyalty(tid TokenID, royaltyInfo RoyaltyInfo) error {\n\t// Validate the payment address\n\tif err := isValidAddress(royaltyInfo.PaymentAddress); err != nil {\n\t\treturn ErrInvalidRoyaltyPaymentAddress\n\t}\n\n\t// Check if royalty percentage exceeds maxRoyaltyPercentage\n\tif royaltyInfo.Percentage \u003e r.maxRoyaltyPercentage {\n\t\treturn ErrInvalidRoyaltyPercentage\n\t}\n\n\t// Check if the caller is the owner of the token\n\towner, err := r.metadataNFT.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcaller := std.PreviousRealm().Addr()\n\tif caller != owner {\n\t\treturn ErrCallerIsNotOwner\n\t}\n\n\t// Set royalty information for the token\n\tr.tokenRoyaltyInfo.Set(string(tid), royaltyInfo)\n\n\treturn nil\n}\n\n// RoyaltyInfo returns the royalty information for the given token ID and sale price.\nfunc (r *royaltyNFT) RoyaltyInfo(tid TokenID, salePrice uint64) (std.Address, uint64, error) {\n\t// Retrieve royalty information for the token\n\tval, found := r.tokenRoyaltyInfo.Get(string(tid))\n\tif !found {\n\t\treturn \"\", 0, ErrInvalidTokenId\n\t}\n\n\troyaltyInfo := val.(RoyaltyInfo)\n\n\t// Calculate royalty amount\n\troyaltyAmount, _ := r.calculateRoyaltyAmount(salePrice, royaltyInfo.Percentage)\n\n\treturn royaltyInfo.PaymentAddress, royaltyAmount, nil\n}\n\nfunc (r *royaltyNFT) calculateRoyaltyAmount(salePrice, percentage uint64) (uint64, error) {\n\troyaltyAmount := (salePrice * percentage) / 100\n\treturn royaltyAmount, nil\n}\n"},{"name":"grc721_royalty_test.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestSetTokenRoyalty(t *testing.T) {\n\tdummy := NewNFTWithRoyalty(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := testutils.TestAddress(\"alice\")\n\taddr2 := testutils.TestAddress(\"bob\")\n\n\tpaymentAddress := testutils.TestAddress(\"john\")\n\tpercentage := uint64(10) // 10%\n\n\tsalePrice := uint64(1000)\n\texpectRoyaltyAmount := uint64(100)\n\n\tstd.TestSetOriginCaller(addr1) // addr1\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\n\tderr := dummy.SetTokenRoyalty(TokenID(\"1\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tuassert.NoError(t, derr, \"Should not result in error\")\n\n\t// Test case: Invalid token ID\n\terr := dummy.SetTokenRoyalty(TokenID(\"3\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tuassert.ErrorIs(t, derr, ErrInvalidTokenId)\n\n\tstd.TestSetOriginCaller(addr2) // addr2\n\n\tcerr := dummy.SetTokenRoyalty(TokenID(\"1\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tuassert.ErrorIs(t, cerr, ErrCallerIsNotOwner)\n\n\t// Test case: Invalid payment address\n\taerr := dummy.SetTokenRoyalty(TokenID(\"4\"), RoyaltyInfo{\n\t\tPaymentAddress: std.Address(\"###\"), // invalid address\n\t\tPercentage: percentage,\n\t})\n\tuassert.ErrorIs(t, aerr, ErrInvalidRoyaltyPaymentAddress)\n\n\t// Test case: Invalid percentage\n\tperr := dummy.SetTokenRoyalty(TokenID(\"5\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: uint64(200), // over maxRoyaltyPercentage\n\t})\n\tuassert.ErrorIs(t, perr, ErrInvalidRoyaltyPercentage)\n\n\t// Test case: Retrieving Royalty Info\n\tstd.TestSetOriginCaller(addr1) // addr1\n\n\tdummyPaymentAddress, dummyRoyaltyAmount, rerr := dummy.RoyaltyInfo(TokenID(\"1\"), salePrice)\n\tuassert.NoError(t, rerr, \"RoyaltyInfo error\")\n\tuassert.Equal(t, paymentAddress, dummyPaymentAddress)\n\tuassert.Equal(t, expectRoyaltyAmount, dummyRoyaltyAmount)\n}\n"},{"name":"igrc721.gno","body":"package grc721\n\nimport \"std\"\n\ntype IGRC721 interface {\n\tBalanceOf(owner std.Address) (uint64, error)\n\tOwnerOf(tid TokenID) (std.Address, error)\n\tSetTokenURI(tid TokenID, tURI TokenURI) (bool, error)\n\tSafeTransferFrom(from, to std.Address, tid TokenID) error\n\tTransferFrom(from, to std.Address, tid TokenID) error\n\tApprove(approved std.Address, tid TokenID) error\n\tSetApprovalForAll(operator std.Address, approved bool) error\n\tGetApproved(tid TokenID) (std.Address, error)\n\tIsApprovedForAll(owner, operator std.Address) bool\n}\n\ntype (\n\tTokenID string\n\tTokenURI string\n)\n\ntype TransferEvent struct {\n\tFrom std.Address\n\tTo std.Address\n\tTokenID TokenID\n}\n\ntype ApprovalEvent struct {\n\tOwner std.Address\n\tApproved std.Address\n\tTokenID TokenID\n}\n\ntype ApprovalForAllEvent struct {\n\tOwner std.Address\n\tOperator std.Address\n\tApproved bool\n}\n"},{"name":"igrc721_metadata.gno","body":"package grc721\n\n// IGRC721CollectionMetadata describes basic information about an NFT collection.\ntype IGRC721CollectionMetadata interface {\n\tName() string // Name returns the name of the collection.\n\tSymbol() string // Symbol returns the symbol of the collection.\n}\n\n// IGRC721Metadata follows the Ethereum standard\ntype IGRC721Metadata interface {\n\tIGRC721CollectionMetadata\n\tTokenURI(tid TokenID) (string, error) // TokenURI returns the URI of a specific token.\n}\n\n// IGRC721Metadata follows the OpenSea metadata standard\ntype IGRC721MetadataOnchain interface {\n\tIGRC721CollectionMetadata\n\tTokenMetadata(tid TokenID) (Metadata, error)\n}\n\ntype Trait struct {\n\tDisplayType string\n\tTraitType string\n\tValue string\n}\n\n// see: https://docs.opensea.io/docs/metadata-standards\ntype Metadata struct {\n\tImage string // URL to the image of the item. Can be any type of image (including SVGs, which will be cached into PNGs by OpenSea), IPFS or Arweave URLs or paths. We recommend using a minimum 3000 x 3000 image.\n\tImageData string // Raw SVG image data, if you want to generate images on the fly (not recommended). Only use this if you're not including the image parameter.\n\tExternalURL string // URL that will appear below the asset's image on OpenSea and will allow users to leave OpenSea and view the item on your site.\n\tDescription string // Human-readable description of the item. Markdown is supported.\n\tName string // Name of the item.\n\tAttributes []Trait // Attributes for the item, which will show up on the OpenSea page for the item.\n\tBackgroundColor string // Background color of the item on OpenSea. Must be a six-character hexadecimal without a pre-pended #\n\tAnimationURL string // URL to a multimedia attachment for the item. Supported file extensions: GLTF, GLB, WEBM, MP4, M4V, OGV, OGG, MP3, WAV, OGA, HTML (for rich experiences and interactive NFTs using JavaScript canvas, WebGL, etc.). Scripts and relative paths within the HTML page are now supported. Access to browser extensions is not supported.\n\tYoutubeURL string // URL to a YouTube video (only used if animation_url is not provided).\n}\n"},{"name":"igrc721_royalty.gno","body":"package grc721\n\nimport \"std\"\n\n// IGRC2981 follows the Ethereum standard\ntype IGRC2981 interface {\n\t// RoyaltyInfo retrieves royalty information for a tokenID and salePrice.\n\t// It returns the payment address, royalty amount, and an error if any.\n\tRoyaltyInfo(tokenID TokenID, salePrice uint64) (std.Address, uint64, error)\n}\n\n// RoyaltyInfo represents royalty information for a token.\ntype RoyaltyInfo struct {\n\tPaymentAddress std.Address // PaymentAddress is the address where royalty payment should be sent.\n\tPercentage uint64 // Percentage is the royalty percentage. It indicates the percentage of royalty to be paid for each sale. For example : Percentage = 10 =\u003e 10%\n}\n"},{"name":"util.gno","body":"package grc721\n\nimport (\n\t\"std\"\n)\n\nvar zeroAddress = std.Address(\"\")\n\nfunc isValidAddress(addr std.Address) error {\n\tif !addr.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\treturn nil\n}\n\nfunc emit(event interface{}) {\n\t// TODO: setup a pubsub system here?\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":"banker.gno","body":"package grc20\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Banker implements a token banker with admin privileges.\n//\n// The Banker is intended to be used in two main ways:\n// 1. as a temporary object used to make the initial minting, then deleted.\n// 2. preserved in an unexported variable to support conditional administrative\n// tasks protected by the contract.\ntype Banker struct {\n\tname string\n\tsymbol string\n\tdecimals uint\n\ttotalSupply uint64\n\tbalances avl.Tree // std.Address(owner) -\u003e uint64\n\tallowances avl.Tree // string(owner+\":\"+spender) -\u003e uint64\n\ttoken *token // to share the same pointer\n}\n\nfunc NewBanker(name, symbol string, decimals uint) *Banker {\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\tb := Banker{\n\t\tname: name,\n\t\tsymbol: symbol,\n\t\tdecimals: decimals,\n\t}\n\tt := \u0026token{banker: \u0026b}\n\tb.token = t\n\treturn \u0026b\n}\n\nfunc (b Banker) Token() Token { return b.token } // Token returns a grc20 safe-object implementation.\nfunc (b Banker) GetName() string { return b.name }\nfunc (b Banker) GetSymbol() string { return b.symbol }\nfunc (b Banker) GetDecimals() uint { return b.decimals }\nfunc (b Banker) TotalSupply() uint64 { return b.totalSupply }\nfunc (b Banker) KnownAccounts() int { return b.balances.Size() }\n\nfunc (b *Banker) Mint(address std.Address, amount uint64) error {\n\tif !address.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\n\t// TODO: check for overflow\n\n\tb.totalSupply += amount\n\tcurrentBalance := b.BalanceOf(address)\n\tnewBalance := currentBalance + amount\n\n\tb.balances.Set(string(address), newBalance)\n\n\tstd.Emit(\n\t\tMintEvent,\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\nfunc (b *Banker) Burn(address std.Address, amount uint64) error {\n\tif !address.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\t// TODO: check for overflow\n\n\tcurrentBalance := b.BalanceOf(address)\n\tif currentBalance \u003c amount {\n\t\treturn ErrInsufficientBalance\n\t}\n\n\tb.totalSupply -= amount\n\tnewBalance := currentBalance - amount\n\n\tb.balances.Set(string(address), newBalance)\n\n\tstd.Emit(\n\t\tBurnEvent,\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\nfunc (b Banker) BalanceOf(address std.Address) uint64 {\n\tbalance, found := b.balances.Get(address.String())\n\tif !found {\n\t\treturn 0\n\t}\n\treturn balance.(uint64)\n}\n\nfunc (b *Banker) 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 := b.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\tb.allowances.Remove(key)\n\t} else {\n\t\tb.allowances.Set(key, newAllowance)\n\t}\n\n\treturn nil\n}\n\nfunc (b *Banker) 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\ttoBalance := b.BalanceOf(to)\n\tfromBalance := b.BalanceOf(from)\n\n\tif fromBalance \u003c amount {\n\t\treturn ErrInsufficientBalance\n\t}\n\n\tnewToBalance := toBalance + amount\n\tnewFromBalance := fromBalance - amount\n\n\tb.balances.Set(string(to), newToBalance)\n\tb.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\nfunc (b *Banker) TransferFrom(spender, from, to std.Address, amount uint64) error {\n\tif err := b.SpendAllowance(from, spender, amount); err != nil {\n\t\treturn err\n\t}\n\treturn b.Transfer(from, to, amount)\n}\n\nfunc (b *Banker) Allowance(owner, spender std.Address) uint64 {\n\tallowance, found := b.allowances.Get(allowanceKey(owner, spender))\n\tif !found {\n\t\treturn 0\n\t}\n\treturn allowance.(uint64)\n}\n\nfunc (b *Banker) Approve(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\tb.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\nfunc (b *Banker) RenderHome() string {\n\tstr := \"\"\n\tstr += ufmt.Sprintf(\"# %s ($%s)\\n\\n\", b.name, b.symbol)\n\tstr += ufmt.Sprintf(\"* **Decimals**: %d\\n\", b.decimals)\n\tstr += ufmt.Sprintf(\"* **Total supply**: %d\\n\", b.totalSupply)\n\tstr += ufmt.Sprintf(\"* **Known accounts**: %d\\n\", b.KnownAccounts())\n\treturn str\n}\n\nfunc allowanceKey(owner, spender std.Address) string {\n\treturn owner.String() + \":\" + spender.String()\n}\n"},{"name":"banker_test.gno","body":"package grc20\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestBankerImpl(t *testing.T) {\n\tdummy := NewBanker(\"Dummy\", \"DUMMY\", 4)\n\turequire.False(t, dummy == nil, \"dummy should not be nil\")\n}\n\nfunc TestAllowance(t *testing.T) {\n\tvar (\n\t\towner = testutils.TestAddress(\"owner\")\n\t\tspender = testutils.TestAddress(\"spender\")\n\t\tdest = testutils.TestAddress(\"dest\")\n\t)\n\n\tb := NewBanker(\"Dummy\", \"DUMMY\", 6)\n\turequire.NoError(t, b.Mint(owner, 100000000))\n\turequire.NoError(t, b.Approve(owner, spender, 5000000))\n\turequire.Error(t, b.TransferFrom(spender, owner, dest, 10000000), ErrInsufficientAllowance.Error(), \"should not be able to transfer more than approved\")\n\n\ttests := []struct {\n\t\tspend uint64\n\t\texp uint64\n\t}{\n\t\t{3, 4999997},\n\t\t{999997, 4000000},\n\t\t{4000000, 0},\n\t}\n\n\tfor _, tt := range tests {\n\t\tb0 := b.BalanceOf(dest)\n\t\turequire.NoError(t, b.TransferFrom(spender, owner, dest, tt.spend))\n\t\ta := b.Allowance(owner, spender)\n\t\turequire.Equal(t, a, tt.exp, ufmt.Sprintf(\"allowance exp: %d, got %d\", tt.exp, a))\n\t\tb := b.BalanceOf(dest)\n\t\texpB := b0 + tt.spend\n\t\turequire.Equal(t, b, expB, ufmt.Sprintf(\"balance exp: %d, got %d\", expB, b))\n\t}\n\n\turequire.Error(t, b.TransferFrom(spender, owner, dest, 1), \"no allowance\")\n\tkey := allowanceKey(owner, spender)\n\turequire.False(t, b.allowances.Has(key), \"allowance should be removed\")\n\turequire.Equal(t, b.Allowance(owner, spender), uint64(0), \"allowance should be 0\")\n}\n"},{"name":"token.gno","body":"package grc20\n\nimport (\n\t\"std\"\n)\n\n// token implements the Token interface.\n//\n// It is generated with Banker.Token().\n// It can safely be exposed publicly.\ntype token struct {\n\tbanker *Banker\n}\n\n// var _ Token = (*token)(nil)\nfunc (t *token) GetName() string { return t.banker.name }\nfunc (t *token) GetSymbol() string { return t.banker.symbol }\nfunc (t *token) GetDecimals() uint { return t.banker.decimals }\nfunc (t *token) TotalSupply() uint64 { return t.banker.totalSupply }\n\nfunc (t *token) BalanceOf(owner std.Address) uint64 {\n\treturn t.banker.BalanceOf(owner)\n}\n\nfunc (t *token) Transfer(to std.Address, amount uint64) error {\n\tcaller := std.PreviousRealm().Address()\n\treturn t.banker.Transfer(caller, to, amount)\n}\n\nfunc (t *token) Allowance(owner, spender std.Address) uint64 {\n\treturn t.banker.Allowance(owner, spender)\n}\n\nfunc (t *token) Approve(spender std.Address, amount uint64) error {\n\tcaller := std.PreviousRealm().Address()\n\treturn t.banker.Approve(caller, spender, amount)\n}\n\nfunc (t *token) TransferFrom(from, to std.Address, amount uint64) error {\n\tspender := std.PreviousRealm().Address()\n\tif err := t.banker.SpendAllowance(from, spender, amount); err != nil {\n\t\treturn err\n\t}\n\treturn t.banker.Transfer(from, to, amount)\n}\n"},{"name":"token_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/ufmt\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestUserTokenImpl(t *testing.T) {\n\tbank := NewBanker(\"Dummy\", \"DUMMY\", 4)\n\ttok := bank.Token()\n\t_ = tok\n}\n\nfunc TestUserApprove(t *testing.T) {\n\towner := testutils.TestAddress(\"owner\")\n\tspender := testutils.TestAddress(\"spender\")\n\tdest := testutils.TestAddress(\"dest\")\n\n\tbank := NewBanker(\"Dummy\", \"DUMMY\", 6)\n\ttok := bank.Token()\n\n\t// Set owner as the original caller\n\tstd.TestSetOriginCaller(owner)\n\t// Mint 100000000 tokens for owner\n\turequire.NoError(t, bank.Mint(owner, 100000000))\n\n\t// Approve spender to spend 5000000 tokens\n\turequire.NoError(t, tok.Approve(spender, 5000000))\n\n\t// Set spender as the original caller\n\tstd.TestSetOriginCaller(spender)\n\t// Try to transfer 10000000 tokens from owner to dest, should fail because it exceeds allowance\n\turequire.Error(t,\n\t\ttok.TransferFrom(owner, dest, 10000000),\n\t\tErrInsufficientAllowance.Error(),\n\t\t\"should not be able to transfer more than approved\",\n\t)\n\n\t// Define a set of test data with spend amount and expected remaining allowance\n\ttests := []struct {\n\t\tspend uint64 // Spend amount\n\t\texp uint64 // Remaining allowance\n\t}{\n\t\t{3, 4999997},\n\t\t{999997, 4000000},\n\t\t{4000000, 0},\n\t}\n\n\t// perform transfer operation,and check if allowance and balance are correct\n\tfor _, tt := range tests {\n\t\tb0 := tok.BalanceOf(dest)\n\t\t// Perform transfer from owner to dest\n\t\turequire.NoError(t, tok.TransferFrom(owner, dest, tt.spend))\n\t\ta := tok.Allowance(owner, spender)\n\t\t// Check if allowance equals expected value\n\t\turequire.True(t, a == tt.exp, ufmt.Sprintf(\"allowance exp: %d,got %d\", tt.exp, a))\n\n\t\t// Get dest current balance\n\t\tb := tok.BalanceOf(dest)\n\t\t// Calculate expected balance ,should be initial balance plus transfer amount\n\t\texpB := b0 + tt.spend\n\t\t// Check if balance equals expected value\n\t\turequire.True(t, b == expB, ufmt.Sprintf(\"balance exp: %d,got %d\", expB, b))\n\t}\n\n\t// Try to transfer one token from owner to dest ,should fail because no allowance left\n\turequire.Error(t, tok.TransferFrom(owner, dest, 1), ErrInsufficientAllowance.Error(), \"no allowance\")\n}\n"},{"name":"types.gno","body":"package grc20\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/grc/exts\"\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)\n\ntype Token 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 the risk\n\t// that someone may use both the old and the new allowance by unfortunate\n\t// transaction ordering. One possible solution to mitigate this race\n\t// condition is to first reduce the spender's allowance to 0 and set the\n\t// 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\nconst (\n\tMintEvent = \"Mint\"\n\tBurnEvent = \"Burn\"\n\tTransferEvent = \"Transfer\"\n\tApprovalEvent = \"Approval\"\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":"grc721","path":"gno.land/p/demo/grc/grc721","files":[{"name":"basic_nft.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype basicNFT struct {\n\tname string\n\tsymbol string\n\towners avl.Tree // tokenId -\u003e OwnerAddress\n\tbalances avl.Tree // OwnerAddress -\u003e TokenCount\n\ttokenApprovals avl.Tree // TokenId -\u003e ApprovedAddress\n\ttokenURIs avl.Tree // TokenId -\u003e URIs\n\toperatorApprovals avl.Tree // \"OwnerAddress:OperatorAddress\" -\u003e bool\n}\n\n// Returns new basic NFT\nfunc NewBasicNFT(name string, symbol string) *basicNFT {\n\treturn \u0026basicNFT{\n\t\tname: name,\n\t\tsymbol: symbol,\n\n\t\towners: avl.Tree{},\n\t\tbalances: avl.Tree{},\n\t\ttokenApprovals: avl.Tree{},\n\t\ttokenURIs: avl.Tree{},\n\t\toperatorApprovals: avl.Tree{},\n\t}\n}\n\nfunc (s *basicNFT) Name() string { return s.name }\nfunc (s *basicNFT) Symbol() string { return s.symbol }\nfunc (s *basicNFT) TokenCount() uint64 { return uint64(s.owners.Size()) }\n\n// BalanceOf returns balance of input address\nfunc (s *basicNFT) BalanceOf(addr std.Address) (uint64, error) {\n\tif err := isValidAddress(addr); err != nil {\n\t\treturn 0, err\n\t}\n\n\tbalance, found := s.balances.Get(addr.String())\n\tif !found {\n\t\treturn 0, nil\n\t}\n\n\treturn balance.(uint64), nil\n}\n\n// OwnerOf returns owner of input token id\nfunc (s *basicNFT) OwnerOf(tid TokenID) (std.Address, error) {\n\towner, found := s.owners.Get(string(tid))\n\tif !found {\n\t\treturn \"\", ErrInvalidTokenId\n\t}\n\n\treturn owner.(std.Address), nil\n}\n\n// TokenURI returns the URI of input token id\nfunc (s *basicNFT) TokenURI(tid TokenID) (string, error) {\n\turi, found := s.tokenURIs.Get(string(tid))\n\tif !found {\n\t\treturn \"\", ErrInvalidTokenId\n\t}\n\n\treturn uri.(string), nil\n}\n\nfunc (s *basicNFT) SetTokenURI(tid TokenID, tURI TokenURI) (bool, error) {\n\t// check for invalid TokenID\n\tif !s.exists(tid) {\n\t\treturn false, ErrInvalidTokenId\n\t}\n\n\t// check for the right owner\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tcaller := std.PreviousRealm().Address()\n\tif caller != owner {\n\t\treturn false, ErrCallerIsNotOwner\n\t}\n\ts.tokenURIs.Set(string(tid), string(tURI))\n\treturn true, nil\n}\n\n// IsApprovedForAll returns true if operator is approved for all by the owner.\n// Otherwise, returns false\nfunc (s *basicNFT) IsApprovedForAll(owner, operator std.Address) bool {\n\tkey := owner.String() + \":\" + operator.String()\n\t_, found := s.operatorApprovals.Get(key)\n\tif !found {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// Approve approves the input address for particular token\nfunc (s *basicNFT) Approve(to std.Address, tid TokenID) error {\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner == to {\n\t\treturn ErrApprovalToCurrentOwner\n\t}\n\n\tcaller := std.PreviousRealm().Address()\n\tif caller != owner \u0026\u0026 !s.IsApprovedForAll(owner, caller) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\ts.tokenApprovals.Set(string(tid), to.String())\n\tevent := ApprovalEvent{owner, to, tid}\n\temit(\u0026event)\n\n\treturn nil\n}\n\n// GetApproved return the approved address for token\nfunc (s *basicNFT) GetApproved(tid TokenID) (std.Address, error) {\n\taddr, found := s.tokenApprovals.Get(string(tid))\n\tif !found {\n\t\treturn zeroAddress, ErrTokenIdNotHasApproved\n\t}\n\n\treturn std.Address(addr.(string)), nil\n}\n\n// SetApprovalForAll can approve the operator to operate on all tokens\nfunc (s *basicNFT) SetApprovalForAll(operator std.Address, approved bool) error {\n\tif err := isValidAddress(operator); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tcaller := std.PreviousRealm().Address()\n\treturn s.setApprovalForAll(caller, operator, approved)\n}\n\n// Safely transfers `tokenId` token from `from` to `to`, checking that\n// contract recipients are aware of the GRC721 protocol to prevent\n// tokens from being forever locked.\nfunc (s *basicNFT) SafeTransferFrom(from, to std.Address, tid TokenID) error {\n\tcaller := std.PreviousRealm().Address()\n\tif !s.isApprovedOrOwner(caller, tid) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.transfer(from, to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.checkOnGRC721Received(from, to, tid) {\n\t\treturn ErrTransferToNonGRC721Receiver\n\t}\n\n\treturn nil\n}\n\n// Transfers `tokenId` token from `from` to `to`.\nfunc (s *basicNFT) TransferFrom(from, to std.Address, tid TokenID) error {\n\tcaller := std.PreviousRealm().Address()\n\tif !s.isApprovedOrOwner(caller, tid) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.transfer(from, to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// Mints `tokenId` and transfers it to `to`.\nfunc (s *basicNFT) Mint(to std.Address, tid TokenID) error {\n\treturn s.mint(to, tid)\n}\n\n// Mints `tokenId` and transfers it to `to`. Also checks that\n// contract recipients are using GRC721 protocol\nfunc (s *basicNFT) SafeMint(to std.Address, tid TokenID) error {\n\terr := s.mint(to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.checkOnGRC721Received(zeroAddress, to, tid) {\n\t\treturn ErrTransferToNonGRC721Receiver\n\t}\n\n\treturn nil\n}\n\nfunc (s *basicNFT) Burn(tid TokenID) error {\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ts.beforeTokenTransfer(owner, zeroAddress, tid, 1)\n\n\ts.tokenApprovals.Remove(string(tid))\n\tbalance, err := s.BalanceOf(owner)\n\tif err != nil {\n\t\treturn err\n\t}\n\tbalance -= 1\n\ts.balances.Set(owner.String(), balance)\n\ts.owners.Remove(string(tid))\n\n\tevent := TransferEvent{owner, zeroAddress, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(owner, zeroAddress, tid, 1)\n\n\treturn nil\n}\n\n/* Helper methods */\n\n// Helper for SetApprovalForAll()\nfunc (s *basicNFT) setApprovalForAll(owner, operator std.Address, approved bool) error {\n\tif owner == operator {\n\t\treturn ErrApprovalToCurrentOwner\n\t}\n\n\tkey := owner.String() + \":\" + operator.String()\n\ts.operatorApprovals.Set(key, approved)\n\n\tevent := ApprovalForAllEvent{owner, operator, approved}\n\temit(\u0026event)\n\n\treturn nil\n}\n\n// Helper for TransferFrom() and SafeTransferFrom()\nfunc (s *basicNFT) transfer(from, to std.Address, tid TokenID) error {\n\tif err := isValidAddress(from); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\tif err := isValidAddress(to); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tif from == to {\n\t\treturn ErrCannotTransferToSelf\n\t}\n\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner != from {\n\t\treturn ErrTransferFromIncorrectOwner\n\t}\n\n\ts.beforeTokenTransfer(from, to, tid, 1)\n\n\t// Check that tokenId was not transferred by `beforeTokenTransfer`\n\towner, err = s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner != from {\n\t\treturn ErrTransferFromIncorrectOwner\n\t}\n\n\ts.tokenApprovals.Remove(string(tid))\n\tfromBalance, err := s.BalanceOf(from)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance, err := s.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfromBalance -= 1\n\ttoBalance += 1\n\ts.balances.Set(from.String(), fromBalance)\n\ts.balances.Set(to.String(), toBalance)\n\ts.owners.Set(string(tid), to)\n\n\tevent := TransferEvent{from, to, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(from, to, tid, 1)\n\n\treturn nil\n}\n\n// Helper for Mint() and SafeMint()\nfunc (s *basicNFT) mint(to std.Address, tid TokenID) error {\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\tif s.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ts.beforeTokenTransfer(zeroAddress, to, tid, 1)\n\n\t// Check that tokenId was not minted by `beforeTokenTransfer`\n\tif s.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ttoBalance, err := s.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance += 1\n\ts.balances.Set(to.String(), toBalance)\n\ts.owners.Set(string(tid), to)\n\n\tevent := TransferEvent{zeroAddress, to, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(zeroAddress, to, tid, 1)\n\n\treturn nil\n}\n\nfunc (s *basicNFT) isApprovedOrOwner(addr std.Address, tid TokenID) bool {\n\towner, found := s.owners.Get(string(tid))\n\tif !found {\n\t\treturn false\n\t}\n\n\tif addr == owner.(std.Address) || s.IsApprovedForAll(owner.(std.Address), addr) {\n\t\treturn true\n\t}\n\n\t_, err := s.GetApproved(tid)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// Checks if token id already exists\nfunc (s *basicNFT) exists(tid TokenID) bool {\n\t_, found := s.owners.Get(string(tid))\n\treturn found\n}\n\nfunc (s *basicNFT) beforeTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicNFT) afterTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicNFT) checkOnGRC721Received(from, to std.Address, tid TokenID) bool {\n\t// TODO: Implementation\n\treturn true\n}\n\nfunc (s *basicNFT) RenderHome() (str string) {\n\tstr += ufmt.Sprintf(\"# %s ($%s)\\n\\n\", s.name, s.symbol)\n\tstr += ufmt.Sprintf(\"* **Total supply**: %d\\n\", s.TokenCount())\n\tstr += ufmt.Sprintf(\"* **Known accounts**: %d\\n\", s.balances.Size())\n\n\treturn\n}\n"},{"name":"basic_nft_test.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\tdummyNFTName = \"DummyNFT\"\n\tdummyNFTSymbol = \"DNFT\"\n)\n\nfunc TestNewBasicNFT(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n}\n\nfunc TestName(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tname := dummy.Name()\n\tuassert.Equal(t, dummyNFTName, name)\n}\n\nfunc TestSymbol(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tsymbol := dummy.Symbol()\n\tuassert.Equal(t, dummyNFTSymbol, symbol)\n}\n\nfunc TestTokenCount(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcount := dummy.TokenCount()\n\tuassert.Equal(t, uint64(0), count)\n\n\tdummy.mint(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\", TokenID(\"1\"))\n\tdummy.mint(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\", TokenID(\"2\"))\n\n\tcount = dummy.TokenCount()\n\tuassert.Equal(t, uint64(2), count)\n}\n\nfunc TestBalanceOf(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\tbalanceAddr1, err := dummy.BalanceOf(addr1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(0), balanceAddr1)\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\tdummy.mint(addr1, TokenID(\"2\"))\n\tdummy.mint(addr2, TokenID(\"3\"))\n\n\tbalanceAddr1, err = dummy.BalanceOf(addr1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tbalanceAddr2, err := dummy.BalanceOf(addr2)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tuassert.Equal(t, uint64(2), balanceAddr1)\n\tuassert.Equal(t, uint64(1), balanceAddr2)\n}\n\nfunc TestOwnerOf(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\towner, err := dummy.OwnerOf(TokenID(\"invalid\"))\n\tuassert.Error(t, err, \"should not result in error\")\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\tdummy.mint(addr2, TokenID(\"2\"))\n\n\t// Checking for token id \"1\"\n\towner, err = dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr1.String(), owner.String())\n\n\t// Checking for token id \"2\"\n\towner, err = dummy.OwnerOf(TokenID(\"2\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr2.String(), owner.String())\n}\n\nfunc TestIsApprovedForAll(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(addr1, addr2)\n\tuassert.False(t, isApprovedForAll)\n}\n\nfunc TestSetApprovalForAll(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PreviousRealm().Address()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(caller, addr)\n\tuassert.False(t, isApprovedForAll)\n\n\terr := dummy.SetApprovalForAll(addr, true)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tisApprovedForAll = dummy.IsApprovedForAll(caller, addr)\n\tuassert.True(t, isApprovedForAll)\n}\n\nfunc TestGetApproved(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tapprovedAddr, err := dummy.GetApproved(TokenID(\"invalid\"))\n\tuassert.Error(t, err, \"should result in error\")\n}\n\nfunc TestApprove(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PreviousRealm().Address()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\n\t_, err := dummy.GetApproved(TokenID(\"1\"))\n\tuassert.Error(t, err, \"should result in error\")\n\n\terr = dummy.Approve(addr, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tapprovedAddr, err := dummy.GetApproved(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should result in error\")\n\tuassert.Equal(t, addr.String(), approvedAddr.String())\n}\n\nfunc TestTransferFrom(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PreviousRealm().Address()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\tdummy.mint(caller, TokenID(\"2\"))\n\n\terr := dummy.TransferFrom(caller, addr, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should result in error\")\n\n\t// Check balance of caller after transfer\n\tbalanceOfCaller, err := dummy.BalanceOf(caller)\n\tuassert.NoError(t, err, \"should result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfCaller)\n\n\t// Check balance of addr after transfer\n\tbalanceOfAddr, err := dummy.BalanceOf(addr)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfAddr)\n\n\t// Check Owner of transferred Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should result in error\")\n\tuassert.Equal(t, addr.String(), owner.String())\n}\n\nfunc TestSafeTransferFrom(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PreviousRealm().Address()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\tdummy.mint(caller, TokenID(\"2\"))\n\n\terr := dummy.SafeTransferFrom(caller, addr, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check balance of caller after transfer\n\tbalanceOfCaller, err := dummy.BalanceOf(caller)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfCaller)\n\n\t// Check balance of addr after transfer\n\tbalanceOfAddr, err := dummy.BalanceOf(addr)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfAddr)\n\n\t// Check Owner of transferred Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr.String(), owner.String())\n}\n\nfunc TestMint(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\terr := dummy.Mint(addr1, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.Mint(addr1, TokenID(\"2\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.Mint(addr2, TokenID(\"3\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Try minting duplicate token id\n\terr = dummy.Mint(addr2, TokenID(\"1\"))\n\tuassert.Error(t, err, \"should not result in error\")\n\n\t// Check Owner of Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr1.String(), owner.String())\n}\n\nfunc TestBurn(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(addr, TokenID(\"1\"))\n\tdummy.mint(addr, TokenID(\"2\"))\n\n\terr := dummy.Burn(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check Owner of Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.Error(t, err, \"should result in error\")\n}\n\nfunc TestSetTokenURI(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\ttokenURI := \"http://example.com/token\"\n\n\tstd.TestSetOriginCaller(std.Address(addr1)) // addr1\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\t_, derr := dummy.SetTokenURI(TokenID(\"1\"), TokenURI(tokenURI))\n\tuassert.NoError(t, derr, \"should not result in error\")\n\n\t// Test case: Invalid token ID\n\t_, err := dummy.SetTokenURI(TokenID(\"3\"), TokenURI(tokenURI))\n\tuassert.ErrorIs(t, err, ErrInvalidTokenId)\n\n\tstd.TestSetOriginCaller(std.Address(addr2)) // addr2\n\n\t_, cerr := dummy.SetTokenURI(TokenID(\"1\"), TokenURI(tokenURI)) // addr2 trying to set URI for token 1\n\tuassert.ErrorIs(t, cerr, ErrCallerIsNotOwner)\n\n\t// Test case: Retrieving TokenURI\n\tstd.TestSetOriginCaller(std.Address(addr1)) // addr1\n\n\tdummyTokenURI, err := dummy.TokenURI(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"TokenURI error\")\n\tuassert.Equal(t, string(tokenURI), string(dummyTokenURI))\n}\n"},{"name":"errors.gno","body":"package grc721\n\nimport \"errors\"\n\nvar (\n\tErrInvalidTokenId = errors.New(\"invalid token id\")\n\tErrInvalidAddress = errors.New(\"invalid address\")\n\tErrTokenIdNotHasApproved = errors.New(\"token id not approved for anyone\")\n\tErrApprovalToCurrentOwner = errors.New(\"approval to current owner\")\n\tErrCallerIsNotOwner = errors.New(\"caller is not token owner\")\n\tErrCallerNotApprovedForAll = errors.New(\"caller is not approved for all\")\n\tErrCannotTransferToSelf = errors.New(\"cannot send transfer to self\")\n\tErrTransferFromIncorrectOwner = errors.New(\"transfer from incorrect owner\")\n\tErrTransferToNonGRC721Receiver = errors.New(\"transfer to non GRC721Receiver implementer\")\n\tErrCallerIsNotOwnerOrApproved = errors.New(\"caller is not token owner or approved\")\n\tErrTokenIdAlreadyExists = errors.New(\"token id already exists\")\n\n\t// ERC721Royalty\n\tErrInvalidRoyaltyPercentage = errors.New(\"invalid royalty percentage\")\n\tErrInvalidRoyaltyPaymentAddress = errors.New(\"invalid royalty paymentAddress\")\n\tErrCannotCalculateRoyaltyAmount = errors.New(\"cannot calculate royalty amount\")\n)\n"},{"name":"grc721_metadata.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n// metadataNFT represents an NFT with metadata extensions.\ntype metadataNFT struct {\n\t*basicNFT // Embedded basicNFT struct for basic NFT functionality\n\textensions *avl.Tree // AVL tree for storing metadata extensions\n}\n\n// Ensure that metadataNFT implements the IGRC721MetadataOnchain interface.\nvar _ IGRC721MetadataOnchain = (*metadataNFT)(nil)\n\n// NewNFTWithMetadata creates a new basic NFT with metadata extensions.\nfunc NewNFTWithMetadata(name string, symbol string) *metadataNFT {\n\t// Create a new basic NFT\n\tnft := NewBasicNFT(name, symbol)\n\n\t// Return a metadataNFT with basicNFT embedded and an empty AVL tree for extensions\n\treturn \u0026metadataNFT{\n\t\tbasicNFT: nft,\n\t\textensions: avl.NewTree(),\n\t}\n}\n\n// SetTokenMetadata sets metadata for a given token ID.\nfunc (s *metadataNFT) SetTokenMetadata(tid TokenID, metadata Metadata) error {\n\t// Check if the caller is the owner of the token\n\towner, err := s.basicNFT.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcaller := std.PreviousRealm().Address()\n\tif caller != owner {\n\t\treturn ErrCallerIsNotOwner\n\t}\n\n\t// Set the metadata for the token ID in the extensions AVL tree\n\ts.extensions.Set(string(tid), metadata)\n\treturn nil\n}\n\n// TokenMetadata retrieves metadata for a given token ID.\nfunc (s *metadataNFT) TokenMetadata(tid TokenID) (Metadata, error) {\n\t// Retrieve metadata from the extensions AVL tree\n\tmetadata, found := s.extensions.Get(string(tid))\n\tif !found {\n\t\treturn Metadata{}, ErrInvalidTokenId\n\t}\n\n\treturn metadata.(Metadata), nil\n}\n\n// mint mints a new token and assigns it to the specified address.\nfunc (s *metadataNFT) mint(to std.Address, tid TokenID) error {\n\t// Check if the address is valid\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\t// Check if the token ID already exists\n\tif s.basicNFT.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ts.basicNFT.beforeTokenTransfer(zeroAddress, to, tid, 1)\n\n\t// Check if the token ID was minted by beforeTokenTransfer\n\tif s.basicNFT.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\t// Increment balance of the recipient address\n\ttoBalance, err := s.basicNFT.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance += 1\n\ts.basicNFT.balances.Set(to.String(), toBalance)\n\n\t// Set owner of the token ID to the recipient address\n\ts.basicNFT.owners.Set(string(tid), to)\n\n\t// Emit transfer event\n\tevent := TransferEvent{zeroAddress, to, tid}\n\temit(\u0026event)\n\n\ts.basicNFT.afterTokenTransfer(zeroAddress, to, tid, 1)\n\n\treturn nil\n}\n"},{"name":"grc721_metadata_test.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestSetMetadata(t *testing.T) {\n\t// Create a new dummy NFT with metadata\n\tdummy := NewNFTWithMetadata(dummyNFTName, dummyNFTSymbol)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n\n\t// Define addresses for testing purposes\n\taddr1 := testutils.TestAddress(\"alice\")\n\taddr2 := testutils.TestAddress(\"bob\")\n\n\t// Define metadata attributes\n\tname := \"test\"\n\tdescription := \"test\"\n\timage := \"test\"\n\timageData := \"test\"\n\texternalURL := \"test\"\n\tattributes := []Trait{}\n\tbackgroundColor := \"test\"\n\tanimationURL := \"test\"\n\tyoutubeURL := \"test\"\n\n\t// Set the original caller to addr1\n\tstd.TestSetOriginCaller(addr1) // addr1\n\n\t// Mint a new token for addr1\n\tdummy.mint(addr1, TokenID(\"1\"))\n\n\t// Set metadata for token 1\n\tderr := dummy.SetTokenMetadata(TokenID(\"1\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if there was an error setting metadata\n\tuassert.NoError(t, derr, \"Should not result in error\")\n\n\t// Test case: Invalid token ID\n\terr := dummy.SetTokenMetadata(TokenID(\"3\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if the error returned matches the expected error\n\tuassert.ErrorIs(t, err, ErrInvalidTokenId)\n\n\t// Set the original caller to addr2\n\tstd.TestSetOriginCaller(addr2) // addr2\n\n\t// Try to set metadata for token 1 from addr2 (should fail)\n\tcerr := dummy.SetTokenMetadata(TokenID(\"1\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if the error returned matches the expected error\n\tuassert.ErrorIs(t, cerr, ErrCallerIsNotOwner)\n\n\t// Set the original caller back to addr1\n\tstd.TestSetOriginCaller(addr1) // addr1\n\n\t// Retrieve metadata for token 1\n\tdummyMetadata, err := dummy.TokenMetadata(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"Metadata error\")\n\n\t// Check if metadata attributes match expected values\n\tuassert.Equal(t, image, dummyMetadata.Image)\n\tuassert.Equal(t, imageData, dummyMetadata.ImageData)\n\tuassert.Equal(t, externalURL, dummyMetadata.ExternalURL)\n\tuassert.Equal(t, description, dummyMetadata.Description)\n\tuassert.Equal(t, name, dummyMetadata.Name)\n\tuassert.Equal(t, len(attributes), len(dummyMetadata.Attributes))\n\tuassert.Equal(t, backgroundColor, dummyMetadata.BackgroundColor)\n\tuassert.Equal(t, animationURL, dummyMetadata.AnimationURL)\n\tuassert.Equal(t, youtubeURL, dummyMetadata.YoutubeURL)\n}\n"},{"name":"grc721_royalty.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n// royaltyNFT represents a non-fungible token (NFT) with royalty functionality.\ntype royaltyNFT struct {\n\t*metadataNFT // Embedding metadataNFT for NFT functionality\n\ttokenRoyaltyInfo *avl.Tree // AVL tree to store royalty information for each token\n\tmaxRoyaltyPercentage uint64 // maxRoyaltyPercentage represents the maximum royalty percentage that can be charged every sale\n}\n\n// Ensure that royaltyNFT implements the IGRC2981 interface.\nvar _ IGRC2981 = (*royaltyNFT)(nil)\n\n// NewNFTWithRoyalty creates a new royalty NFT with the specified name, symbol, and royalty calculator.\nfunc NewNFTWithRoyalty(name string, symbol string) *royaltyNFT {\n\t// Create a new NFT with metadata\n\tnft := NewNFTWithMetadata(name, symbol)\n\n\treturn \u0026royaltyNFT{\n\t\tmetadataNFT: nft,\n\t\ttokenRoyaltyInfo: avl.NewTree(),\n\t\tmaxRoyaltyPercentage: 100,\n\t}\n}\n\n// SetTokenRoyalty sets the royalty information for a specific token ID.\nfunc (r *royaltyNFT) SetTokenRoyalty(tid TokenID, royaltyInfo RoyaltyInfo) error {\n\t// Validate the payment address\n\tif err := isValidAddress(royaltyInfo.PaymentAddress); err != nil {\n\t\treturn ErrInvalidRoyaltyPaymentAddress\n\t}\n\n\t// Check if royalty percentage exceeds maxRoyaltyPercentage\n\tif royaltyInfo.Percentage \u003e r.maxRoyaltyPercentage {\n\t\treturn ErrInvalidRoyaltyPercentage\n\t}\n\n\t// Check if the caller is the owner of the token\n\towner, err := r.metadataNFT.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcaller := std.PreviousRealm().Address()\n\tif caller != owner {\n\t\treturn ErrCallerIsNotOwner\n\t}\n\n\t// Set royalty information for the token\n\tr.tokenRoyaltyInfo.Set(string(tid), royaltyInfo)\n\n\treturn nil\n}\n\n// RoyaltyInfo returns the royalty information for the given token ID and sale price.\nfunc (r *royaltyNFT) RoyaltyInfo(tid TokenID, salePrice uint64) (std.Address, uint64, error) {\n\t// Retrieve royalty information for the token\n\tval, found := r.tokenRoyaltyInfo.Get(string(tid))\n\tif !found {\n\t\treturn \"\", 0, ErrInvalidTokenId\n\t}\n\n\troyaltyInfo := val.(RoyaltyInfo)\n\n\t// Calculate royalty amount\n\troyaltyAmount, _ := r.calculateRoyaltyAmount(salePrice, royaltyInfo.Percentage)\n\n\treturn royaltyInfo.PaymentAddress, royaltyAmount, nil\n}\n\nfunc (r *royaltyNFT) calculateRoyaltyAmount(salePrice, percentage uint64) (uint64, error) {\n\troyaltyAmount := (salePrice * percentage) / 100\n\treturn royaltyAmount, nil\n}\n"},{"name":"grc721_royalty_test.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestSetTokenRoyalty(t *testing.T) {\n\tdummy := NewNFTWithRoyalty(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := testutils.TestAddress(\"alice\")\n\taddr2 := testutils.TestAddress(\"bob\")\n\n\tpaymentAddress := testutils.TestAddress(\"john\")\n\tpercentage := uint64(10) // 10%\n\n\tsalePrice := uint64(1000)\n\texpectRoyaltyAmount := uint64(100)\n\n\tstd.TestSetOriginCaller(addr1) // addr1\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\n\tderr := dummy.SetTokenRoyalty(TokenID(\"1\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tuassert.NoError(t, derr, \"Should not result in error\")\n\n\t// Test case: Invalid token ID\n\terr := dummy.SetTokenRoyalty(TokenID(\"3\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tuassert.ErrorIs(t, derr, ErrInvalidTokenId)\n\n\tstd.TestSetOriginCaller(addr2) // addr2\n\n\tcerr := dummy.SetTokenRoyalty(TokenID(\"1\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tuassert.ErrorIs(t, cerr, ErrCallerIsNotOwner)\n\n\t// Test case: Invalid payment address\n\taerr := dummy.SetTokenRoyalty(TokenID(\"4\"), RoyaltyInfo{\n\t\tPaymentAddress: std.Address(\"###\"), // invalid address\n\t\tPercentage: percentage,\n\t})\n\tuassert.ErrorIs(t, aerr, ErrInvalidRoyaltyPaymentAddress)\n\n\t// Test case: Invalid percentage\n\tperr := dummy.SetTokenRoyalty(TokenID(\"5\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: uint64(200), // over maxRoyaltyPercentage\n\t})\n\tuassert.ErrorIs(t, perr, ErrInvalidRoyaltyPercentage)\n\n\t// Test case: Retrieving Royalty Info\n\tstd.TestSetOriginCaller(addr1) // addr1\n\n\tdummyPaymentAddress, dummyRoyaltyAmount, rerr := dummy.RoyaltyInfo(TokenID(\"1\"), salePrice)\n\tuassert.NoError(t, rerr, \"RoyaltyInfo error\")\n\tuassert.Equal(t, paymentAddress, dummyPaymentAddress)\n\tuassert.Equal(t, expectRoyaltyAmount, dummyRoyaltyAmount)\n}\n"},{"name":"igrc721.gno","body":"package grc721\n\nimport \"std\"\n\ntype IGRC721 interface {\n\tBalanceOf(owner std.Address) (uint64, error)\n\tOwnerOf(tid TokenID) (std.Address, error)\n\tSetTokenURI(tid TokenID, tURI TokenURI) (bool, error)\n\tSafeTransferFrom(from, to std.Address, tid TokenID) error\n\tTransferFrom(from, to std.Address, tid TokenID) error\n\tApprove(approved std.Address, tid TokenID) error\n\tSetApprovalForAll(operator std.Address, approved bool) error\n\tGetApproved(tid TokenID) (std.Address, error)\n\tIsApprovedForAll(owner, operator std.Address) bool\n}\n\ntype (\n\tTokenID string\n\tTokenURI string\n)\n\ntype TransferEvent struct {\n\tFrom std.Address\n\tTo std.Address\n\tTokenID TokenID\n}\n\ntype ApprovalEvent struct {\n\tOwner std.Address\n\tApproved std.Address\n\tTokenID TokenID\n}\n\ntype ApprovalForAllEvent struct {\n\tOwner std.Address\n\tOperator std.Address\n\tApproved bool\n}\n"},{"name":"igrc721_metadata.gno","body":"package grc721\n\n// IGRC721CollectionMetadata describes basic information about an NFT collection.\ntype IGRC721CollectionMetadata interface {\n\tName() string // Name returns the name of the collection.\n\tSymbol() string // Symbol returns the symbol of the collection.\n}\n\n// IGRC721Metadata follows the Ethereum standard\ntype IGRC721Metadata interface {\n\tIGRC721CollectionMetadata\n\tTokenURI(tid TokenID) (string, error) // TokenURI returns the URI of a specific token.\n}\n\n// IGRC721Metadata follows the OpenSea metadata standard\ntype IGRC721MetadataOnchain interface {\n\tIGRC721CollectionMetadata\n\tTokenMetadata(tid TokenID) (Metadata, error)\n}\n\ntype Trait struct {\n\tDisplayType string\n\tTraitType string\n\tValue string\n}\n\n// see: https://docs.opensea.io/docs/metadata-standards\ntype Metadata struct {\n\tImage string // URL to the image of the item. Can be any type of image (including SVGs, which will be cached into PNGs by OpenSea), IPFS or Arweave URLs or paths. We recommend using a minimum 3000 x 3000 image.\n\tImageData string // Raw SVG image data, if you want to generate images on the fly (not recommended). Only use this if you're not including the image parameter.\n\tExternalURL string // URL that will appear below the asset's image on OpenSea and will allow users to leave OpenSea and view the item on your site.\n\tDescription string // Human-readable description of the item. Markdown is supported.\n\tName string // Name of the item.\n\tAttributes []Trait // Attributes for the item, which will show up on the OpenSea page for the item.\n\tBackgroundColor string // Background color of the item on OpenSea. Must be a six-character hexadecimal without a pre-pended #\n\tAnimationURL string // URL to a multimedia attachment for the item. Supported file extensions: GLTF, GLB, WEBM, MP4, M4V, OGV, OGG, MP3, WAV, OGA, HTML (for rich experiences and interactive NFTs using JavaScript canvas, WebGL, etc.). Scripts and relative paths within the HTML page are now supported. Access to browser extensions is not supported.\n\tYoutubeURL string // URL to a YouTube video (only used if animation_url is not provided).\n}\n"},{"name":"igrc721_royalty.gno","body":"package grc721\n\nimport \"std\"\n\n// IGRC2981 follows the Ethereum standard\ntype IGRC2981 interface {\n\t// RoyaltyInfo retrieves royalty information for a tokenID and salePrice.\n\t// It returns the payment address, royalty amount, and an error if any.\n\tRoyaltyInfo(tokenID TokenID, salePrice uint64) (std.Address, uint64, error)\n}\n\n// RoyaltyInfo represents royalty information for a token.\ntype RoyaltyInfo struct {\n\tPaymentAddress std.Address // PaymentAddress is the address where royalty payment should be sent.\n\tPercentage uint64 // Percentage is the royalty percentage. It indicates the percentage of royalty to be paid for each sale. For example : Percentage = 10 =\u003e 10%\n}\n"},{"name":"util.gno","body":"package grc721\n\nimport (\n\t\"std\"\n)\n\nvar zeroAddress = std.Address(\"\")\n\nfunc isValidAddress(addr std.Address) error {\n\tif !addr.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\treturn nil\n}\n\nfunc emit(event interface{}) {\n\t// TODO: setup a pubsub system here?\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":"grc777","path":"gno.land/p/demo/grc/grc777","files":[{"name":"dummy_test.gno","body":"package grc777\n\nimport (\n\t\"std\"\n\t\"testing\"\n)\n\ntype dummyImpl struct{}\n\n// FIXME: this should fail.\nvar _ IGRC777 = (*dummyImpl)(nil)\n\nfunc TestInterface(t *testing.T) {\n\tvar dummy IGRC777 = \u0026dummyImpl{}\n}\n\nfunc (impl *dummyImpl) GetName() string { panic(\"not implemented\") }\nfunc (impl *dummyImpl) GetSymbol() string { panic(\"not implemented\") }\nfunc (impl *dummyImpl) GetDecimals() uint { panic(\"not implemented\") }\nfunc (impl *dummyImpl) Granularity() (granularity uint64) { panic(\"not implemented\") }\nfunc (impl *dummyImpl) TotalSupply() (supply uint64) { panic(\"not implemented\") }\nfunc (impl *dummyImpl) BalanceOf(address std.Address) uint64 { panic(\"not implemented\") }\nfunc (impl *dummyImpl) Burn(amount uint64, data []byte) { panic(\"not implemented\") }\nfunc (impl *dummyImpl) AuthorizeOperator(operator std.Address) { panic(\"not implemented\") }\nfunc (impl *dummyImpl) RevokeOperator(operators std.Address) { panic(\"not implemented\") }\nfunc (impl *dummyImpl) DefaultOperators() []std.Address { panic(\"not implemented\") }\nfunc (impl *dummyImpl) Send(recipient std.Address, amount uint64, data []byte) {\n\tpanic(\"not implemented\")\n}\n\nfunc (impl *dummyImpl) IsOperatorFor(operator, tokenHolder std.Address) bool {\n\tpanic(\"not implemented\")\n}\n\nfunc (impl *dummyImpl) OperatorSend(sender, recipient std.Address, amount uint64, data, operatorData []byte) {\n\tpanic(\"not implemented\")\n}\n\nfunc (impl *dummyImpl) OperatorBurn(account std.Address, amount uint64, data, operatorData []byte) {\n\tpanic(\"not implemented\")\n}\n"},{"name":"igrc777.gno","body":"package grc777\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/grc/exts\"\n)\n\n// TODO: use big.Int or a custom uint64 instead of uint64\n\ntype IGRC777 interface {\n\texts.TokenMetadata\n\n\t// Returns the smallest part of the token that is not divisible. This\n\t// means all token operations (creation, movement and destruction) must\n\t// have amounts that are a multiple of this number.\n\t//\n\t// For most token contracts, this value will equal 1.\n\tGranularity() (granularity uint64)\n\n\t// Returns the amount of tokens in existence.\n\tTotalSupply() (supply uint64)\n\n\t// Returns the amount of tokens owned by an account (`owner`).\n\tBalanceOf(address std.Address) uint64\n\n\t// Moves `amount` tokens from the caller's account to `recipient`.\n\t//\n\t// If send or receive hooks are registered for the caller and `recipient`,\n\t// the corresponding functions will be called with `data` and empty\n\t// `operatorData`. See {IERC777Sender} and {IERC777Recipient}.\n\t//\n\t// Emits a {Sent} event.\n\t//\n\t// Requirements\n\t//\n\t// - the caller must have at least `amount` tokens.\n\t// - `recipient` cannot be the zero address.\n\t// - if `recipient` is a contract, it must implement the {IERC777Recipient}\n\t// interface.\n\tSend(recipient std.Address, amount uint64, data []byte)\n\n\t// Destroys `amount` tokens from the caller's account, reducing the\n\t// total supply.\n\t//\n\t// If a send hook is registered for the caller, the corresponding function\n\t// will be called with `data` and empty `operatorData`. See {IERC777Sender}.\n\t//\n\t// Emits a {Burned} event.\n\t//\n\t// Requirements\n\t//\n\t// - the caller must have at least `amount` tokens.\n\tBurn(amount uint64, data []byte)\n\n\t// Returns true if an account is an operator of `tokenHolder`.\n\t// Operators can send and burn tokens on behalf of their owners. All\n\t// accounts are their own operator.\n\t//\n\t// See {operatorSend} and {operatorBurn}.\n\tIsOperatorFor(operator, tokenHolder std.Address) bool\n\n\t// Make an account an operator of the caller.\n\t//\n\t// See {isOperatorFor}.\n\t//\n\t// Emits an {AuthorizedOperator} event.\n\t//\n\t// Requirements\n\t//\n\t// - `operator` cannot be calling address.\n\tAuthorizeOperator(operator std.Address)\n\n\t// Revoke an account's operator status for the caller.\n\t//\n\t// See {isOperatorFor} and {defaultOperators}.\n\t//\n\t// Emits a {RevokedOperator} event.\n\t//\n\t// Requirements\n\t//\n\t// - `operator` cannot be calling address.\n\tRevokeOperator(operators std.Address)\n\n\t// Returns the list of default operators. These accounts are operators\n\t// for all token holders, even if {authorizeOperator} was never called on\n\t// them.\n\t//\n\t// This list is immutable, but individual holders may revoke these via\n\t// {revokeOperator}, in which case {isOperatorFor} will return false.\n\tDefaultOperators() []std.Address\n\n\t// Moves `amount` tokens from `sender` to `recipient`. The caller must\n\t// be an operator of `sender`.\n\t//\n\t// If send or receive hooks are registered for `sender` and `recipient`,\n\t// the corresponding functions will be called with `data` and\n\t// `operatorData`. See {IERC777Sender} and {IERC777Recipient}.\n\t//\n\t// Emits a {Sent} event.\n\t//\n\t// Requirements\n\t//\n\t// - `sender` cannot be the zero address.\n\t// - `sender` must have at least `amount` tokens.\n\t// - the caller must be an operator for `sender`.\n\t// - `recipient` cannot be the zero address.\n\t// - if `recipient` is a contract, it must implement the {IERC777Recipient}\n\t// interface.\n\tOperatorSend(sender, recipient std.Address, amount uint64, data, operatorData []byte)\n\n\t// Destroys `amount` tokens from `account`, reducing the total supply.\n\t// The caller must be an operator of `account`.\n\t//\n\t// If a send hook is registered for `account`, the corresponding function\n\t// will be called with `data` and `operatorData`. See {IERC777Sender}.\n\t//\n\t// Emits a {Burned} event.\n\t//\n\t// Requirements\n\t//\n\t// - `account` cannot be the zero address.\n\t// - `account` must have at least `amount` tokens.\n\t// - the caller must be an operator for `account`.\n\tOperatorBurn(account std.Address, amount uint64, data, operatorData []byte)\n}\n\n// Emitted when `amount` tokens are created by `operator` and assigned to `to`.\n//\n// Note that some additional user `data` and `operatorData` can be logged in the event.\ntype MintedEvent struct {\n\tOperator std.Address\n\tTo std.Address\n\tAmount uint64\n\tData []byte\n\tOperatorData []byte\n}\n\n// Emitted when `operator` destroys `amount` tokens from `account`.\n//\n// Note that some additional user `data` and `operatorData` can be logged in the event.\ntype BurnedEvent struct {\n\tOperator std.Address\n\tFrom std.Address\n\tAmount uint64\n\tData []byte\n\tOperatorData []byte\n}\n\n// Emitted when `operator` is made operator for `tokenHolder`\ntype AuthorizedOperatorEvent struct {\n\tOperator std.Address\n\tTokenHolder std.Address\n}\n\n// Emitted when `operator` is revoked its operator status for `tokenHolder`.\ntype RevokedOperatorEvent struct {\n\tOperator std.Address\n\tTokenHolder std.Address\n}\n\ntype SentEvent struct {\n\tOperator std.Address\n\tFrom std.Address\n\tTo std.Address\n\tAmount uint64\n\tData []byte\n\tOperatorData []byte\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":"rat","path":"gno.land/p/demo/rat","files":[{"name":"maths.gno","body":"package rat\n\nconst (\n\tintSize = 32 \u003c\u003c (^uint(0) \u003e\u003e 63) // 32 or 64\n\n\tMaxInt = 1\u003c\u003c(intSize-1) - 1\n\tMinInt = -1 \u003c\u003c (intSize - 1)\n\tMaxInt8 = 1\u003c\u003c7 - 1\n\tMinInt8 = -1 \u003c\u003c 7\n\tMaxInt16 = 1\u003c\u003c15 - 1\n\tMinInt16 = -1 \u003c\u003c 15\n\tMaxInt32 = 1\u003c\u003c31 - 1\n\tMinInt32 = -1 \u003c\u003c 31\n\tMaxInt64 = 1\u003c\u003c63 - 1\n\tMinInt64 = -1 \u003c\u003c 63\n\tMaxUint = 1\u003c\u003cintSize - 1\n\tMaxUint8 = 1\u003c\u003c8 - 1\n\tMaxUint16 = 1\u003c\u003c16 - 1\n\tMaxUint32 = 1\u003c\u003c32 - 1\n\tMaxUint64 = 1\u003c\u003c64 - 1\n)\n"},{"name":"rat.gno","body":"package rat\n\n//----------------------------------------\n// Rat fractions\n\n// represents a fraction.\ntype Rat struct {\n\tX int32\n\tY int32 // must be positive\n}\n\nfunc NewRat(x, y int32) Rat {\n\tif y \u003c= 0 {\n\t\tpanic(\"invalid std.Rat denominator\")\n\t}\n\treturn Rat{X: x, Y: y}\n}\n\nfunc (r1 Rat) IsValid() bool {\n\tif r1.Y \u003c= 0 {\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc (r1 Rat) Cmp(r2 Rat) int {\n\tif !r1.IsValid() {\n\t\tpanic(\"invalid std.Rat left operand\")\n\t}\n\tif !r2.IsValid() {\n\t\tpanic(\"invalid std.Rat right operand\")\n\t}\n\tvar p1, p2 int64\n\tp1 = int64(r1.X) * int64(r2.Y)\n\tp2 = int64(r1.Y) * int64(r2.X)\n\tif p1 \u003c p2 {\n\t\treturn -1\n\t} else if p1 == p2 {\n\t\treturn 0\n\t} else {\n\t\treturn 1\n\t}\n}\n\n//func (r1 Rat) Plus(r2 Rat) Rat {\n// XXX\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":"txlink","path":"gno.land/p/moul/txlink","files":[{"name":"txlink.gno","body":"// Package txlink provides utilities for creating transaction-related links\n// compatible with Gnoweb, Gnobro, and other clients within the Gno ecosystem.\n//\n// This package is optimized for generating lightweight transaction links with\n// flexible arguments, allowing users to build dynamic links that integrate\n// seamlessly with various Gno clients.\n//\n// The primary function, URL, is designed to produce markdown links for\n// transaction functions in the current \"relative realm\". By specifying a custom\n// Realm, you can generate links that either use the current realm path or a\n// fully qualified path for another realm.\n//\n// This package is a streamlined alternative to helplink, providing similar\n// functionality for transaction links without the full feature set of helplink.\npackage txlink\n\nimport (\n\t\"std\"\n\t\"strings\"\n)\n\nconst chainDomain = \"gno.land\" // XXX: std.ChainDomain (#2911)\n\n// URL returns a URL for the specified function with optional key-value\n// arguments, for the current realm.\nfunc URL(fn string, args ...string) string {\n\treturn Realm(\"\").URL(fn, args...)\n}\n\n// Realm represents a specific realm for generating tx links.\ntype Realm string\n\n// prefix returns the URL prefix for the realm.\nfunc (r Realm) prefix() string {\n\t// relative\n\tif r == \"\" {\n\t\tcurPath := std.CurrentRealm().PkgPath()\n\t\treturn strings.TrimPrefix(curPath, chainDomain)\n\t}\n\n\t// local realm -\u003e /realm\n\trealm := string(r)\n\tif strings.Contains(realm, chainDomain) {\n\t\treturn strings.TrimPrefix(realm, chainDomain)\n\t}\n\n\t// remote realm -\u003e https://remote.land/realm\n\treturn \"https://\" + string(r)\n}\n\n// URL returns a URL for the specified function with optional key-value\n// arguments.\nfunc (r Realm) URL(fn string, args ...string) string {\n\t// Start with the base query\n\turl := r.prefix() + \"$help\u0026func=\" + fn\n\n\t// Check if args length is even\n\tif len(args)%2 != 0 {\n\t\t// If not even, we can choose to handle the error here.\n\t\t// For example, we can just return the URL without appending\n\t\t// more args.\n\t\treturn url\n\t}\n\n\t// Append key-value pairs to the URL\n\tfor i := 0; i \u003c len(args); i += 2 {\n\t\tkey := args[i]\n\t\tvalue := args[i+1]\n\t\t// XXX: escape keys and args\n\t\turl += \"\u0026\" + key + \"=\" + value\n\t}\n\n\treturn url\n}\n"},{"name":"txlink_test.gno","body":"package txlink\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestURL(t *testing.T) {\n\ttests := []struct {\n\t\tfn string\n\t\targs []string\n\t\twant string\n\t\trealm Realm\n\t}{\n\t\t{\"foo\", []string{\"bar\", \"1\", \"baz\", \"2\"}, \"$help\u0026func=foo\u0026bar=1\u0026baz=2\", \"\"},\n\t\t{\"testFunc\", []string{\"key\", \"value\"}, \"$help\u0026func=testFunc\u0026key=value\", \"\"},\n\t\t{\"noArgsFunc\", []string{}, \"$help\u0026func=noArgsFunc\", \"\"},\n\t\t{\"oddArgsFunc\", []string{\"key\"}, \"$help\u0026func=oddArgsFunc\", \"\"},\n\t\t{\"foo\", []string{\"bar\", \"1\", \"baz\", \"2\"}, \"/r/lorem/ipsum$help\u0026func=foo\u0026bar=1\u0026baz=2\", \"gno.land/r/lorem/ipsum\"},\n\t\t{\"testFunc\", []string{\"key\", \"value\"}, \"/r/lorem/ipsum$help\u0026func=testFunc\u0026key=value\", \"gno.land/r/lorem/ipsum\"},\n\t\t{\"noArgsFunc\", []string{}, \"/r/lorem/ipsum$help\u0026func=noArgsFunc\", \"gno.land/r/lorem/ipsum\"},\n\t\t{\"oddArgsFunc\", []string{\"key\"}, \"/r/lorem/ipsum$help\u0026func=oddArgsFunc\", \"gno.land/r/lorem/ipsum\"},\n\t\t{\"foo\", []string{\"bar\", \"1\", \"baz\", \"2\"}, \"https://gno.world/r/lorem/ipsum$help\u0026func=foo\u0026bar=1\u0026baz=2\", \"gno.world/r/lorem/ipsum\"},\n\t\t{\"testFunc\", []string{\"key\", \"value\"}, \"https://gno.world/r/lorem/ipsum$help\u0026func=testFunc\u0026key=value\", \"gno.world/r/lorem/ipsum\"},\n\t\t{\"noArgsFunc\", []string{}, \"https://gno.world/r/lorem/ipsum$help\u0026func=noArgsFunc\", \"gno.world/r/lorem/ipsum\"},\n\t\t{\"oddArgsFunc\", []string{\"key\"}, \"https://gno.world/r/lorem/ipsum$help\u0026func=oddArgsFunc\", \"gno.world/r/lorem/ipsum\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\ttitle := tt.fn\n\t\tt.Run(title, func(t *testing.T) {\n\t\t\tgot := tt.realm.URL(tt.fn, tt.args...)\n\t\t\turequire.Equal(t, tt.want, got)\n\t\t})\n\t}\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}} @@ -45,25 +45,25 @@ {"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"json","path":"gno.land/p/demo/json","files":[{"name":"LICENSE","body":"# MIT License\n\nCopyright (c) 2019 Pyzhov Stepan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"},{"name":"README.md","body":"# JSON Parser\n\nThe JSON parser is a package that provides functionality for parsing and processing JSON strings. This package accepts JSON strings as byte slices.\n\nCurrently, gno does not [support the `reflect` package](https://docs.gno.land/concepts/effective-gno#reflection-is-never-clear), so it cannot retrieve type information at runtime. Therefore, it is designed to infer and handle type information when parsing JSON strings using a state machine approach.\n\nAfter passing through the state machine, JSON strings are represented as the `Node` type. The `Node` type represents nodes for JSON data, including various types such as `ObjectNode`, `ArrayNode`, `StringNode`, `NumberNode`, `BoolNode`, and `NullNode`.\n\nThis package provides methods for manipulating, searching, and extracting the Node type.\n\n## State Machine\n\nTo parse JSON strings, a [finite state machine](https://en.wikipedia.org/wiki/Finite-state_machine) approach is used. The state machine transitions to the next state based on the current state and the input character while parsing the JSON string. Through this method, type information can be inferred and processed without reflect, and the amount of parser code can be significantly reduced.\n\nThe image below shows the state transitions of the state machine according to the states and input characters.\n\n```mermaid\nstateDiagram-v2\n [*] --\u003e __: Start\n __ --\u003e ST: String\n __ --\u003e MI: Number\n __ --\u003e ZE: Zero\n __ --\u003e IN: Integer\n __ --\u003e T1: Boolean (true)\n __ --\u003e F1: Boolean (false)\n __ --\u003e N1: Null\n __ --\u003e ec: Empty Object End\n __ --\u003e cc: Object End\n __ --\u003e bc: Array End\n __ --\u003e co: Object Begin\n __ --\u003e bo: Array Begin\n __ --\u003e cm: Comma\n __ --\u003e cl: Colon\n __ --\u003e OK: Success/End\n ST --\u003e OK: String Complete\n MI --\u003e OK: Number Complete\n ZE --\u003e OK: Zero Complete\n IN --\u003e OK: Integer Complete\n T1 --\u003e OK: True Complete\n F1 --\u003e OK: False Complete\n N1 --\u003e OK: Null Complete\n ec --\u003e OK: Empty Object Complete\n cc --\u003e OK: Object Complete\n bc --\u003e OK: Array Complete\n co --\u003e OB: Inside Object\n bo --\u003e AR: Inside Array\n cm --\u003e KE: Expecting New Key\n cm --\u003e VA: Expecting New Value\n cl --\u003e VA: Expecting Value\n OB --\u003e ST: String in Object (Key)\n OB --\u003e ec: Empty Object\n OB --\u003e cc: End Object\n AR --\u003e ST: String in Array\n AR --\u003e bc: End Array\n KE --\u003e ST: String as Key\n VA --\u003e ST: String as Value\n VA --\u003e MI: Number as Value\n VA --\u003e T1: True as Value\n VA --\u003e F1: False as Value\n VA --\u003e N1: Null as Value\n OK --\u003e [*]: End\n```\n\n## Examples\n\nThis package provides parsing functionality along with encoding and decoding functionality. The following examples demonstrate how to use this package.\n\n### Decoding\n\nDecoding (or Unmarshaling) is the functionality that converts an input byte slice JSON string into a `Node` type.\n\nThe converted `Node` type allows you to modify the JSON data or search and extract data that meets specific conditions.\n\n```go\npackage main\n\nimport (\n \"gno.land/p/demo/json\"\n \"gno.land/p/demo/ufmt\"\n)\n\nfunc main() {\n node, err := json.Unmarshal([]byte(`{\"foo\": \"var\"}`))\n if err != nil {\n ufmt.Errorf(\"error: %v\", err)\n }\n\n ufmt.Sprintf(\"node: %v\", node)\n}\n```\n\n### Encoding\n\nEncoding (or Marshaling) is the functionality that converts JSON data represented as a Node type into a byte slice JSON string.\n\n\u003e ⚠️ Caution: Converting a large `Node` type into a JSON string may _impact performance_. or might be cause _unexpected behavior_.\n\n```go\npackage main\n\nimport (\n \"gno.land/p/demo/json\"\n \"gno.land/p/demo/ufmt\"\n)\n\nfunc main() {\n node := ObjectNode(\"\", map[string]*Node{\n \"foo\": StringNode(\"foo\", \"bar\"),\n \"baz\": NumberNode(\"baz\", 100500),\n \"qux\": NullNode(\"qux\"),\n })\n\n b, err := json.Marshal(node)\n if err != nil {\n ufmt.Errorf(\"error: %v\", err)\n }\n\n ufmt.Sprintf(\"json: %s\", string(b))\n}\n```\n\n### Searching\n\nOnce the JSON data converted into a `Node` type, you can **search** and **extract** data that satisfy specific conditions. For example, you can find data with a specific type or data with a specific key.\n\nTo use this functionality, you can use methods in the `GetXXX` prefixed methods. The `MustXXX` methods also provide the same functionality as the former methods, but they will **panic** if data doesn't satisfies the condition.\n\nHere is an example of finding data with a specific key. For more examples, please refer to the [node.gno](node.gno) file.\n\n```go\npackage main\n\nimport (\n \"gno.land/p/demo/json\"\n \"gno.land/p/demo/ufmt\"\n)\n\nfunc main() {\n root, err := Unmarshal([]byte(`{\"foo\": true, \"bar\": null}`))\n if err != nil {\n ufmt.Errorf(\"error: %v\", err)\n }\n\n value, err := root.GetKey(\"foo\")\n if err != nil {\n ufmt.Errorf(\"error occurred while getting key, %s\", err)\n }\n\n if value.MustBool() != true {\n ufmt.Errorf(\"value is not true\")\n }\n\n value, err = root.GetKey(\"bar\")\n if err != nil {\n t.Errorf(\"error occurred while getting key, %s\", err)\n }\n\n _, err = root.GetKey(\"baz\")\n if err == nil {\n t.Errorf(\"key baz is not exist. must be failed\")\n }\n}\n```\n\n## Contributing\n\nPlease submit any issues or pull requests for this package through the GitHub repository at [gnolang/gno](\u003chttps://github.com/gnolang/gno\u003e).\n"},{"name":"buffer.gno","body":"package json\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype buffer struct {\n\tdata []byte\n\tlength int\n\tindex int\n\n\tlast States\n\tstate States\n\tclass Classes\n}\n\n// newBuffer creates a new buffer with the given data\nfunc newBuffer(data []byte) *buffer {\n\treturn \u0026buffer{\n\t\tdata: data,\n\t\tlength: len(data),\n\t\tlast: GO,\n\t\tstate: GO,\n\t}\n}\n\n// first retrieves the first non-whitespace (or other escaped) character in the buffer.\nfunc (b *buffer) first() (byte, error) {\n\tfor ; b.index \u003c b.length; b.index++ {\n\t\tc := b.data[b.index]\n\n\t\tif !(c == whiteSpace || c == carriageReturn || c == newLine || c == tab) {\n\t\t\treturn c, nil\n\t\t}\n\t}\n\n\treturn 0, io.EOF\n}\n\n// current returns the byte of the current index.\nfunc (b *buffer) current() (byte, error) {\n\tif b.index \u003e= b.length {\n\t\treturn 0, io.EOF\n\t}\n\n\treturn b.data[b.index], nil\n}\n\n// next moves to the next byte and returns it.\nfunc (b *buffer) next() (byte, error) {\n\tb.index++\n\treturn b.current()\n}\n\n// step just moves to the next position.\nfunc (b *buffer) step() error {\n\t_, err := b.next()\n\treturn err\n}\n\n// move moves the index by the given position.\nfunc (b *buffer) move(pos int) error {\n\tnewIndex := b.index + pos\n\n\tif newIndex \u003e b.length {\n\t\treturn io.EOF\n\t}\n\n\tb.index = newIndex\n\n\treturn nil\n}\n\n// slice returns the slice from the current index to the given position.\nfunc (b *buffer) slice(pos int) ([]byte, error) {\n\tend := b.index + pos\n\n\tif end \u003e b.length {\n\t\treturn nil, io.EOF\n\t}\n\n\treturn b.data[b.index:end], nil\n}\n\n// sliceFromIndices returns a slice of the buffer's data starting from 'start' up to (but not including) 'stop'.\nfunc (b *buffer) sliceFromIndices(start, stop int) []byte {\n\tif start \u003e b.length {\n\t\tstart = b.length\n\t}\n\n\tif stop \u003e b.length {\n\t\tstop = b.length\n\t}\n\n\treturn b.data[start:stop]\n}\n\n// skip moves the index to skip the given byte.\nfunc (b *buffer) skip(bs byte) error {\n\tfor b.index \u003c b.length {\n\t\tif b.data[b.index] == bs \u0026\u0026 !b.backslash() {\n\t\t\treturn nil\n\t\t}\n\n\t\tb.index++\n\t}\n\n\treturn io.EOF\n}\n\n// skipAndReturnIndex moves the buffer index forward by one and returns the new index.\nfunc (b *buffer) skipAndReturnIndex() (int, error) {\n\terr := b.step()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn b.index, nil\n}\n\n// skipUntil moves the buffer index forward until it encounters a byte contained in the endTokens set.\nfunc (b *buffer) skipUntil(endTokens map[byte]bool) (int, error) {\n\tfor b.index \u003c b.length {\n\t\tcurrentByte, err := b.current()\n\t\tif err != nil {\n\t\t\treturn b.index, err\n\t\t}\n\n\t\t// Check if the current byte is in the set of end tokens.\n\t\tif _, exists := endTokens[currentByte]; exists {\n\t\t\treturn b.index, nil\n\t\t}\n\n\t\tb.index++\n\t}\n\n\treturn b.index, io.EOF\n}\n\n// significantTokens is a map where the keys are the significant characters in a JSON path.\n// The values in the map are all true, which allows us to use the map as a set for quick lookups.\nvar significantTokens = [256]bool{\n\tdot: true, // access properties of an object\n\tdollarSign: true, // root object\n\tatSign: true, // current object\n\tbracketOpen: true, // start of an array index or filter expression\n\tbracketClose: true, // end of an array index or filter expression\n}\n\n// filterTokens stores the filter expression tokens.\nvar filterTokens = [256]bool{\n\taesterisk: true, // wildcard\n\tandSign: true,\n\torSign: true,\n}\n\n// skipToNextSignificantToken advances the buffer index to the next significant character.\n// Significant characters are defined based on the JSON path syntax.\nfunc (b *buffer) skipToNextSignificantToken() {\n\tfor b.index \u003c b.length {\n\t\tcurrent := b.data[b.index]\n\n\t\tif significantTokens[current] {\n\t\t\tbreak\n\t\t}\n\n\t\tb.index++\n\t}\n}\n\n// backslash checks to see if the number of backslashes before the current index is odd.\n//\n// This is used to check if the current character is escaped. However, unlike the \"unescape\" function,\n// \"backslash\" only serves to check the number of backslashes.\nfunc (b *buffer) backslash() bool {\n\tif b.index == 0 {\n\t\treturn false\n\t}\n\n\tcount := 0\n\tfor i := b.index - 1; ; i-- {\n\t\tif b.data[i] != backSlash {\n\t\t\tbreak\n\t\t}\n\n\t\tcount++\n\n\t\tif i == 0 {\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn count%2 != 0\n}\n\n// numIndex holds a map of valid numeric characters\nvar numIndex = [256]bool{\n\t'0': true,\n\t'1': true,\n\t'2': true,\n\t'3': true,\n\t'4': true,\n\t'5': true,\n\t'6': true,\n\t'7': true,\n\t'8': true,\n\t'9': true,\n\t'.': true,\n\t'e': true,\n\t'E': true,\n}\n\n// pathToken checks if the current token is a valid JSON path token.\nfunc (b *buffer) pathToken() error {\n\tvar stack []byte\n\n\tinToken := false\n\tinNumber := false\n\tfirst := b.index\n\n\tfor b.index \u003c b.length {\n\t\tc := b.data[b.index]\n\n\t\tswitch {\n\t\tcase c == doubleQuote || c == singleQuote:\n\t\t\tinToken = true\n\t\t\tif err := b.step(); err != nil {\n\t\t\t\treturn errors.New(\"error stepping through buffer\")\n\t\t\t}\n\n\t\t\tif err := b.skip(c); err != nil {\n\t\t\t\treturn errUnmatchedQuotePath\n\t\t\t}\n\n\t\t\tif b.index \u003e= b.length {\n\t\t\t\treturn errUnmatchedQuotePath\n\t\t\t}\n\n\t\tcase c == bracketOpen || c == parenOpen:\n\t\t\tinToken = true\n\t\t\tstack = append(stack, c)\n\n\t\tcase c == bracketClose || c == parenClose:\n\t\t\tinToken = true\n\t\t\tif len(stack) == 0 || (c == bracketClose \u0026\u0026 stack[len(stack)-1] != bracketOpen) || (c == parenClose \u0026\u0026 stack[len(stack)-1] != parenOpen) {\n\t\t\t\treturn errUnmatchedParenthesis\n\t\t\t}\n\n\t\t\tstack = stack[:len(stack)-1]\n\n\t\tcase pathStateContainsValidPathToken(c):\n\t\t\tinToken = true\n\n\t\tcase c == plus || c == minus:\n\t\t\tif inNumber || (b.index \u003e 0 \u0026\u0026 numIndex[b.data[b.index-1]]) {\n\t\t\t\tinToken = true\n\t\t\t} else if !inToken \u0026\u0026 (b.index+1 \u003c b.length \u0026\u0026 numIndex[b.data[b.index+1]]) {\n\t\t\t\tinToken = true\n\t\t\t\tinNumber = true\n\t\t\t} else if !inToken {\n\t\t\t\treturn errInvalidToken\n\t\t\t}\n\n\t\tdefault:\n\t\t\tif len(stack) != 0 || inToken {\n\t\t\t\tinToken = true\n\t\t\t} else {\n\t\t\t\tgoto end\n\t\t\t}\n\t\t}\n\n\t\tb.index++\n\t}\n\nend:\n\tif len(stack) != 0 {\n\t\treturn errUnmatchedParenthesis\n\t}\n\n\tif first == b.index {\n\t\treturn errors.New(\"no token found\")\n\t}\n\n\tif inNumber \u0026\u0026 !numIndex[b.data[b.index-1]] {\n\t\tinNumber = false\n\t}\n\n\treturn nil\n}\n\nfunc pathStateContainsValidPathToken(c byte) bool {\n\tif significantTokens[c] {\n\t\treturn true\n\t}\n\n\tif filterTokens[c] {\n\t\treturn true\n\t}\n\n\tif numIndex[c] {\n\t\treturn true\n\t}\n\n\tif 'A' \u003c= c \u0026\u0026 c \u003c= 'Z' || 'a' \u003c= c \u0026\u0026 c \u003c= 'z' {\n\t\treturn true\n\t}\n\n\treturn false\n}\n\nfunc (b *buffer) numeric(token bool) error {\n\tif token {\n\t\tb.last = GO\n\t}\n\n\tfor ; b.index \u003c b.length; b.index++ {\n\t\tb.class = b.getClasses(doubleQuote)\n\t\tif b.class == __ {\n\t\t\treturn errInvalidToken\n\t\t}\n\n\t\tb.state = StateTransitionTable[b.last][b.class]\n\t\tif b.state == __ {\n\t\t\tif token {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\treturn errInvalidToken\n\t\t}\n\n\t\tif b.state \u003c __ {\n\t\t\treturn nil\n\t\t}\n\n\t\tif b.state \u003c MI || b.state \u003e E3 {\n\t\t\treturn nil\n\t\t}\n\n\t\tb.last = b.state\n\t}\n\n\tif b.last != ZE \u0026\u0026 b.last != IN \u0026\u0026 b.last != FR \u0026\u0026 b.last != E3 {\n\t\treturn errInvalidToken\n\t}\n\n\treturn nil\n}\n\nfunc (b *buffer) getClasses(c byte) Classes {\n\tif b.data[b.index] \u003e= 128 {\n\t\treturn C_ETC\n\t}\n\n\tif c == singleQuote {\n\t\treturn QuoteAsciiClasses[b.data[b.index]]\n\t}\n\n\treturn AsciiClasses[b.data[b.index]]\n}\n\nfunc (b *buffer) getState() States {\n\tb.last = b.state\n\n\tb.class = b.getClasses(doubleQuote)\n\tif b.class == __ {\n\t\treturn __\n\t}\n\n\tb.state = StateTransitionTable[b.last][b.class]\n\n\treturn b.state\n}\n\n// string parses a string token from the buffer.\nfunc (b *buffer) string(search byte, token bool) error {\n\tif token {\n\t\tb.last = GO\n\t}\n\n\tfor ; b.index \u003c b.length; b.index++ {\n\t\tb.class = b.getClasses(search)\n\n\t\tif b.class == __ {\n\t\t\treturn errInvalidToken\n\t\t}\n\n\t\tb.state = StateTransitionTable[b.last][b.class]\n\t\tif b.state == __ {\n\t\t\treturn errInvalidToken\n\t\t}\n\n\t\tif b.state \u003c __ {\n\t\t\tbreak\n\t\t}\n\n\t\tb.last = b.state\n\t}\n\n\treturn nil\n}\n\nfunc (b *buffer) word(bs []byte) error {\n\tvar c byte\n\n\tmax := len(bs)\n\tindex := 0\n\n\tfor ; b.index \u003c b.length \u0026\u0026 index \u003c max; b.index++ {\n\t\tc = b.data[b.index]\n\n\t\tif c != bs[index] {\n\t\t\treturn errInvalidToken\n\t\t}\n\n\t\tindex++\n\t\tif index \u003e= max {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif index != max {\n\t\treturn errInvalidToken\n\t}\n\n\treturn nil\n}\n\nfunc numberKind2f64(value interface{}) (result float64, err error) {\n\tswitch typed := value.(type) {\n\tcase float64:\n\t\tresult = typed\n\tcase float32:\n\t\tresult = float64(typed)\n\tcase int:\n\t\tresult = float64(typed)\n\tcase int8:\n\t\tresult = float64(typed)\n\tcase int16:\n\t\tresult = float64(typed)\n\tcase int32:\n\t\tresult = float64(typed)\n\tcase int64:\n\t\tresult = float64(typed)\n\tcase uint:\n\t\tresult = float64(typed)\n\tcase uint8:\n\t\tresult = float64(typed)\n\tcase uint16:\n\t\tresult = float64(typed)\n\tcase uint32:\n\t\tresult = float64(typed)\n\tcase uint64:\n\t\tresult = float64(typed)\n\tdefault:\n\t\terr = ufmt.Errorf(\"invalid number type: %T\", value)\n\t}\n\n\treturn\n}\n"},{"name":"buffer_test.gno","body":"package json\n\nimport (\n\t\"testing\"\n)\n\nfunc TestBufferCurrent(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tbuffer *buffer\n\t\texpected byte\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"Valid current byte\",\n\t\t\tbuffer: \u0026buffer{\n\t\t\t\tdata: []byte(\"test\"),\n\t\t\t\tlength: 4,\n\t\t\t\tindex: 1,\n\t\t\t},\n\t\t\texpected: 'e',\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"EOF\",\n\t\t\tbuffer: \u0026buffer{\n\t\t\t\tdata: []byte(\"test\"),\n\t\t\t\tlength: 4,\n\t\t\t\tindex: 4,\n\t\t\t},\n\t\t\texpected: 0,\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.buffer.current()\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"buffer.current() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif got != tt.expected {\n\t\t\t\tt.Errorf(\"buffer.current() = %v, want %v\", got, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBufferStep(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tbuffer *buffer\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"Valid step\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 0},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"EOF error\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 3},\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\terr := tt.buffer.step()\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"buffer.step() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBufferNext(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tbuffer *buffer\n\t\twant byte\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"Valid next byte\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 0},\n\t\t\twant: 'e',\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"EOF error\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 3},\n\t\t\twant: 0,\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.buffer.next()\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"buffer.next() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif got != tt.want {\n\t\t\t\tt.Errorf(\"buffer.next() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBufferSlice(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tbuffer *buffer\n\t\tpos int\n\t\twant []byte\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"Valid slice -- 0 characters\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 0},\n\t\t\tpos: 0,\n\t\t\twant: nil,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Valid slice -- 1 character\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 0},\n\t\t\tpos: 1,\n\t\t\twant: []byte(\"t\"),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Valid slice -- 2 characters\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 1},\n\t\t\tpos: 2,\n\t\t\twant: []byte(\"es\"),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Valid slice -- 3 characters\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 0},\n\t\t\tpos: 3,\n\t\t\twant: []byte(\"tes\"),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Valid slice -- 4 characters\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 0},\n\t\t\tpos: 4,\n\t\t\twant: []byte(\"test\"),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"EOF error\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 3},\n\t\t\tpos: 2,\n\t\t\twant: nil,\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.buffer.slice(tt.pos)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"buffer.slice() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif string(got) != string(tt.want) {\n\t\t\t\tt.Errorf(\"buffer.slice() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBufferMove(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tbuffer *buffer\n\t\tpos int\n\t\twantErr bool\n\t\twantIdx int\n\t}{\n\t\t{\n\t\t\tname: \"Valid move\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 1},\n\t\t\tpos: 2,\n\t\t\twantErr: false,\n\t\t\twantIdx: 3,\n\t\t},\n\t\t{\n\t\t\tname: \"Move beyond length\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 1},\n\t\t\tpos: 4,\n\t\t\twantErr: true,\n\t\t\twantIdx: 1,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\terr := tt.buffer.move(tt.pos)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"buffer.move() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t\tif tt.buffer.index != tt.wantIdx {\n\t\t\t\tt.Errorf(\"buffer.move() index = %v, want %v\", tt.buffer.index, tt.wantIdx)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBufferSkip(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tbuffer *buffer\n\t\tb byte\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"Skip byte\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 0},\n\t\t\tb: 'e',\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Skip to EOF\",\n\t\t\tbuffer: \u0026buffer{data: []byte(\"test\"), length: 4, index: 0},\n\t\t\tb: 'x',\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\terr := tt.buffer.skip(tt.b)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"buffer.skip() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSkipToNextSignificantToken(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput []byte\n\t\texpected int\n\t}{\n\t\t{\"No significant chars\", []byte(\"abc\"), 3},\n\t\t{\"One significant char at start\", []byte(\".abc\"), 0},\n\t\t{\"Significant char in middle\", []byte(\"ab.c\"), 2},\n\t\t{\"Multiple significant chars\", []byte(\"a$.c\"), 1},\n\t\t{\"Significant char at end\", []byte(\"abc$\"), 3},\n\t\t{\"Only significant chars\", []byte(\"$.\"), 0},\n\t\t{\"Empty string\", []byte(\"\"), 0},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tb := newBuffer(tt.input)\n\t\t\tb.skipToNextSignificantToken()\n\t\t\tif b.index != tt.expected {\n\t\t\t\tt.Errorf(\"after skipToNextSignificantToken(), got index = %v, want %v\", b.index, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc mockBuffer(s string) *buffer {\n\treturn newBuffer([]byte(s))\n}\n\nfunc TestSkipAndReturnIndex(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput string\n\t\texpected int\n\t}{\n\t\t{\"StartOfString\", \"\", 0},\n\t\t{\"MiddleOfString\", \"abcdef\", 1},\n\t\t{\"EndOfString\", \"abc\", 1},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tbuf := mockBuffer(tt.input)\n\t\t\tgot, err := buf.skipAndReturnIndex()\n\t\t\tif err != nil \u0026\u0026 tt.input != \"\" { // Expect no error unless input is empty\n\t\t\t\tt.Errorf(\"skipAndReturnIndex() error = %v\", err)\n\t\t\t}\n\t\t\tif got != tt.expected {\n\t\t\t\tt.Errorf(\"skipAndReturnIndex() = %v, want %v\", got, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSkipUntil(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput string\n\t\ttokens map[byte]bool\n\t\texpected int\n\t}{\n\t\t{\"SkipToToken\", \"abcdefg\", map[byte]bool{'c': true}, 2},\n\t\t{\"SkipToEnd\", \"abcdefg\", map[byte]bool{'h': true}, 7},\n\t\t{\"SkipNone\", \"abcdefg\", map[byte]bool{'a': true}, 0},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tbuf := mockBuffer(tt.input)\n\t\t\tgot, err := buf.skipUntil(tt.tokens)\n\t\t\tif err != nil \u0026\u0026 got != len(tt.input) { // Expect error only if reached end without finding token\n\t\t\t\tt.Errorf(\"skipUntil() error = %v\", err)\n\t\t\t}\n\t\t\tif got != tt.expected {\n\t\t\t\tt.Errorf(\"skipUntil() = %v, want %v\", got, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSliceFromIndices(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput string\n\t\tstart int\n\t\tend int\n\t\texpected string\n\t}{\n\t\t{\"FullString\", \"abcdefg\", 0, 7, \"abcdefg\"},\n\t\t{\"Substring\", \"abcdefg\", 2, 5, \"cde\"},\n\t\t{\"OutOfBounds\", \"abcdefg\", 5, 10, \"fg\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tbuf := mockBuffer(tt.input)\n\t\t\tgot := buf.sliceFromIndices(tt.start, tt.end)\n\t\t\tif string(got) != tt.expected {\n\t\t\t\tt.Errorf(\"sliceFromIndices() = %v, want %v\", string(got), tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBufferToken(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tpath string\n\t\tindex int\n\t\tisErr bool\n\t}{\n\t\t{\n\t\t\tname: \"Simple valid path\",\n\t\t\tpath: \"@.length\",\n\t\t\tindex: 8,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Path with array expr\",\n\t\t\tpath: \"@['foo'].0.bar\",\n\t\t\tindex: 14,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Path with array expr and simple fomula\",\n\t\t\tpath: \"@['foo'].[(@.length - 1)].*\",\n\t\t\tindex: 27,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Path with filter expr\",\n\t\t\tpath: \"@['foo'].[?(@.bar == 1 \u0026 @.baz \u003c @.length)].*\",\n\t\t\tindex: 45,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"addition of foo and bar\",\n\t\t\tpath: \"@.foo+@.bar\",\n\t\t\tindex: 11,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"logical AND of foo and bar\",\n\t\t\tpath: \"@.foo \u0026\u0026 @.bar\",\n\t\t\tindex: 14,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"logical OR of foo and bar\",\n\t\t\tpath: \"@.foo || @.bar\",\n\t\t\tindex: 14,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"accessing third element of foo\",\n\t\t\tpath: \"@.foo,3\",\n\t\t\tindex: 7,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"accessing last element of array\",\n\t\t\tpath: \"@.length-1\",\n\t\t\tindex: 10,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"number 1\",\n\t\t\tpath: \"1\",\n\t\t\tindex: 1,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"float\",\n\t\t\tpath: \"3.1e4\",\n\t\t\tindex: 5,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"float with minus\",\n\t\t\tpath: \"3.1e-4\",\n\t\t\tindex: 6,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"float with plus\",\n\t\t\tpath: \"3.1e+4\",\n\t\t\tindex: 6,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"negative number\",\n\t\t\tpath: \"-12345\",\n\t\t\tindex: 6,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"negative float\",\n\t\t\tpath: \"-3.1e4\",\n\t\t\tindex: 6,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"negative float with minus\",\n\t\t\tpath: \"-3.1e-4\",\n\t\t\tindex: 7,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"negative float with plus\",\n\t\t\tpath: \"-3.1e+4\",\n\t\t\tindex: 7,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"string number\",\n\t\t\tpath: \"'12345'\",\n\t\t\tindex: 7,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"string with backslash\",\n\t\t\tpath: \"'foo \\\\'bar '\",\n\t\t\tindex: 12,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"string with inner double quotes\",\n\t\t\tpath: \"'foo \\\"bar \\\"'\",\n\t\t\tindex: 12,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"parenthesis 1\",\n\t\t\tpath: \"(@abc)\",\n\t\t\tindex: 6,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"parenthesis 2\",\n\t\t\tpath: \"[()]\",\n\t\t\tindex: 4,\n\t\t\tisErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"parenthesis mismatch\",\n\t\t\tpath: \"[(])\",\n\t\t\tindex: 2,\n\t\t\tisErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"parenthesis mismatch 2\",\n\t\t\tpath: \"(\",\n\t\t\tindex: 1,\n\t\t\tisErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"parenthesis mismatch 3\",\n\t\t\tpath: \"())]\",\n\t\t\tindex: 2,\n\t\t\tisErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"bracket mismatch\",\n\t\t\tpath: \"[()\",\n\t\t\tindex: 3,\n\t\t\tisErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"bracket mismatch 2\",\n\t\t\tpath: \"()]\",\n\t\t\tindex: 2,\n\t\t\tisErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"path does not close bracket\",\n\t\t\tpath: \"@.foo[)\",\n\t\t\tindex: 6,\n\t\t\tisErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tbuf := newBuffer([]byte(tt.path))\n\n\t\t\terr := buf.pathToken()\n\t\t\tif tt.isErr {\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Errorf(\"Expected an error for path `%s`, but got none\", tt.path)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif err == nil \u0026\u0026 tt.isErr {\n\t\t\t\tt.Errorf(\"Expected an error for path `%s`, but got none\", tt.path)\n\t\t\t}\n\n\t\t\tif buf.index != tt.index {\n\t\t\t\tt.Errorf(\"Expected final index %d, got %d (token: `%s`) for path `%s`\", tt.index, buf.index, string(buf.data[buf.index]), tt.path)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBufferFirst(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tdata []byte\n\t\texpected byte\n\t}{\n\t\t{\n\t\t\tname: \"Valid first byte\",\n\t\t\tdata: []byte(\"test\"),\n\t\t\texpected: 't',\n\t\t},\n\t\t{\n\t\t\tname: \"Empty buffer\",\n\t\t\tdata: []byte(\"\"),\n\t\t\texpected: 0,\n\t\t},\n\t\t{\n\t\t\tname: \"Whitespace buffer\",\n\t\t\tdata: []byte(\" \"),\n\t\t\texpected: 0,\n\t\t},\n\t\t{\n\t\t\tname: \"whitespace in middle\",\n\t\t\tdata: []byte(\"hello world\"),\n\t\t\texpected: 'h',\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tb := newBuffer(tt.data)\n\n\t\t\tgot, err := b.first()\n\t\t\tif err != nil \u0026\u0026 tt.expected != 0 {\n\t\t\t\tt.Errorf(\"Unexpected error: %v\", err)\n\t\t\t}\n\n\t\t\tif got != tt.expected {\n\t\t\t\tt.Errorf(\"Expected first byte to be %q, got %q\", tt.expected, got)\n\t\t\t}\n\t\t})\n\t}\n}\n"},{"name":"builder.gno","body":"package json\n\ntype NodeBuilder struct {\n\tnode *Node\n}\n\nfunc Builder() *NodeBuilder {\n\treturn \u0026NodeBuilder{node: ObjectNode(\"\", nil)}\n}\n\nfunc (b *NodeBuilder) WriteString(key, value string) *NodeBuilder {\n\tb.node.AppendObject(key, StringNode(\"\", value))\n\treturn b\n}\n\nfunc (b *NodeBuilder) WriteNumber(key string, value float64) *NodeBuilder {\n\tb.node.AppendObject(key, NumberNode(\"\", value))\n\treturn b\n}\n\nfunc (b *NodeBuilder) WriteBool(key string, value bool) *NodeBuilder {\n\tb.node.AppendObject(key, BoolNode(\"\", value))\n\treturn b\n}\n\nfunc (b *NodeBuilder) WriteNull(key string) *NodeBuilder {\n\tb.node.AppendObject(key, NullNode(\"\"))\n\treturn b\n}\n\nfunc (b *NodeBuilder) WriteObject(key string, fn func(*NodeBuilder)) *NodeBuilder {\n\tnestedBuilder := \u0026NodeBuilder{node: ObjectNode(\"\", nil)}\n\tfn(nestedBuilder)\n\tb.node.AppendObject(key, nestedBuilder.node)\n\treturn b\n}\n\nfunc (b *NodeBuilder) WriteArray(key string, fn func(*ArrayBuilder)) *NodeBuilder {\n\tarrayBuilder := \u0026ArrayBuilder{nodes: []*Node{}}\n\tfn(arrayBuilder)\n\tb.node.AppendObject(key, ArrayNode(\"\", arrayBuilder.nodes))\n\treturn b\n}\n\nfunc (b *NodeBuilder) Node() *Node {\n\treturn b.node\n}\n\ntype ArrayBuilder struct {\n\tnodes []*Node\n}\n\nfunc (ab *ArrayBuilder) WriteString(value string) *ArrayBuilder {\n\tab.nodes = append(ab.nodes, StringNode(\"\", value))\n\treturn ab\n}\n\nfunc (ab *ArrayBuilder) WriteNumber(value float64) *ArrayBuilder {\n\tab.nodes = append(ab.nodes, NumberNode(\"\", value))\n\treturn ab\n}\n\nfunc (ab *ArrayBuilder) WriteInt(value int) *ArrayBuilder {\n\treturn ab.WriteNumber(float64(value))\n}\n\nfunc (ab *ArrayBuilder) WriteBool(value bool) *ArrayBuilder {\n\tab.nodes = append(ab.nodes, BoolNode(\"\", value))\n\treturn ab\n}\n\nfunc (ab *ArrayBuilder) WriteNull() *ArrayBuilder {\n\tab.nodes = append(ab.nodes, NullNode(\"\"))\n\treturn ab\n}\n\nfunc (ab *ArrayBuilder) WriteObject(fn func(*NodeBuilder)) *ArrayBuilder {\n\tnestedBuilder := \u0026NodeBuilder{node: ObjectNode(\"\", nil)}\n\tfn(nestedBuilder)\n\tab.nodes = append(ab.nodes, nestedBuilder.node)\n\treturn ab\n}\n\nfunc (ab *ArrayBuilder) WriteArray(fn func(*ArrayBuilder)) *ArrayBuilder {\n\tnestedArrayBuilder := \u0026ArrayBuilder{nodes: []*Node{}}\n\tfn(nestedArrayBuilder)\n\tab.nodes = append(ab.nodes, ArrayNode(\"\", nestedArrayBuilder.nodes))\n\treturn ab\n}\n"},{"name":"builder_test.gno","body":"package json\n\nimport (\n\t\"testing\"\n)\n\nfunc TestNodeBuilder(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tbuild func() *Node\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname: \"plain object\",\n\t\t\tbuild: func() *Node {\n\t\t\t\treturn Builder().\n\t\t\t\t\tWriteString(\"name\", \"Alice\").\n\t\t\t\t\tWriteNumber(\"age\", 30).\n\t\t\t\t\tWriteBool(\"is_student\", false).\n\t\t\t\t\tNode()\n\t\t\t},\n\t\t\texpected: `{\"name\":\"Alice\",\"age\":30,\"is_student\":false}`,\n\t\t},\n\t\t{\n\t\t\tname: \"nested object\",\n\t\t\tbuild: func() *Node {\n\t\t\t\treturn Builder().\n\t\t\t\t\tWriteString(\"name\", \"Alice\").\n\t\t\t\t\tWriteObject(\"address\", func(b *NodeBuilder) {\n\t\t\t\t\t\tb.WriteString(\"city\", \"New York\").\n\t\t\t\t\t\t\tWriteNumber(\"zipcode\", 10001)\n\t\t\t\t\t}).\n\t\t\t\t\tNode()\n\t\t\t},\n\t\t\texpected: `{\"name\":\"Alice\",\"address\":{\"city\":\"New York\",\"zipcode\":10001}}`,\n\t\t},\n\t\t{\n\t\t\tname: \"null node\",\n\t\t\tbuild: func() *Node {\n\t\t\t\treturn Builder().WriteNull(\"foo\").Node()\n\t\t\t},\n\t\t\texpected: `{\"foo\":null}`,\n\t\t},\n\t\t{\n\t\t\tname: \"array node\",\n\t\t\tbuild: func() *Node {\n\t\t\t\treturn Builder().\n\t\t\t\t\tWriteArray(\"items\", func(ab *ArrayBuilder) {\n\t\t\t\t\t\tab.WriteString(\"item1\").\n\t\t\t\t\t\t\tWriteString(\"item2\").\n\t\t\t\t\t\t\tWriteString(\"item3\")\n\t\t\t\t\t}).\n\t\t\t\t\tNode()\n\t\t\t},\n\t\t\texpected: `{\"items\":[\"item1\",\"item2\",\"item3\"]}`,\n\t\t},\n\t\t{\n\t\t\tname: \"array with objects\",\n\t\t\tbuild: func() *Node {\n\t\t\t\treturn Builder().\n\t\t\t\t\tWriteArray(\"users\", func(ab *ArrayBuilder) {\n\t\t\t\t\t\tab.WriteObject(func(b *NodeBuilder) {\n\t\t\t\t\t\t\tb.WriteString(\"name\", \"Bob\").\n\t\t\t\t\t\t\t\tWriteNumber(\"age\", 25)\n\t\t\t\t\t\t}).\n\t\t\t\t\t\t\tWriteObject(func(b *NodeBuilder) {\n\t\t\t\t\t\t\t\tb.WriteString(\"name\", \"Carol\").\n\t\t\t\t\t\t\t\t\tWriteNumber(\"age\", 27)\n\t\t\t\t\t\t\t})\n\t\t\t\t\t}).\n\t\t\t\t\tNode()\n\t\t\t},\n\t\t\texpected: `{\"users\":[{\"name\":\"Bob\",\"age\":25},{\"name\":\"Carol\",\"age\":27}]}`,\n\t\t},\n\t\t{\n\t\t\tname: \"array with various types\",\n\t\t\tbuild: func() *Node {\n\t\t\t\treturn Builder().\n\t\t\t\t\tWriteArray(\"values\", func(ab *ArrayBuilder) {\n\t\t\t\t\t\tab.WriteString(\"item1\").\n\t\t\t\t\t\t\tWriteNumber(123).\n\t\t\t\t\t\t\tWriteBool(true).\n\t\t\t\t\t\t\tWriteNull()\n\t\t\t\t\t}).\n\t\t\t\t\tNode()\n\t\t\t},\n\t\t\texpected: `{\"values\":[\"item1\",123,true,null]}`,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tnode := tt.build()\n\t\t\tvalue, err := Marshal(node)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t\tif string(value) != tt.expected {\n\t\t\t\tt.Errorf(\"expected %s, got %s\", tt.expected, string(value))\n\t\t\t}\n\t\t})\n\t}\n}\n"},{"name":"decode.gno","body":"// ref: https://github.com/spyzhov/ajson/blob/master/decode.go\n\npackage json\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// This limits the max nesting depth to prevent stack overflow.\n// This is permitted by https://tools.ietf.org/html/rfc7159#section-9\nconst maxNestingDepth = 10000\n\n// Unmarshal parses the JSON-encoded data and returns a Node.\n// The data must be a valid JSON-encoded value.\n//\n// Usage:\n//\n//\tnode, err := json.Unmarshal([]byte(`{\"key\": \"value\"}`))\n//\tif err != nil {\n//\t\tufmt.Println(err)\n//\t}\n//\tprintln(node) // {\"key\": \"value\"}\nfunc Unmarshal(data []byte) (*Node, error) {\n\tbuf := newBuffer(data)\n\n\tvar (\n\t\tstate States\n\t\tkey *string\n\t\tcurrent *Node\n\t\tnesting int\n\t\tuseKey = func() **string {\n\t\t\ttmp := cptrs(key)\n\t\t\tkey = nil\n\t\t\treturn \u0026tmp\n\t\t}\n\t\terr error\n\t)\n\n\tif _, err = buf.first(); err != nil {\n\t\treturn nil, io.EOF\n\t}\n\n\tfor {\n\t\tstate = buf.getState()\n\t\tif state == __ {\n\t\t\treturn nil, unexpectedTokenError(buf.data, buf.index)\n\t\t}\n\n\t\t// region state machine\n\t\tif state \u003e= GO {\n\t\t\tswitch buf.state {\n\t\t\tcase ST: // string\n\t\t\t\tif current != nil \u0026\u0026 current.IsObject() \u0026\u0026 key == nil {\n\t\t\t\t\t// key detected\n\t\t\t\t\tif key, err = getString(buf); err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\n\t\t\t\t\tbuf.state = CO\n\t\t\t\t} else {\n\t\t\t\t\tcurrent, nesting, err = createNestedNode(current, buf, String, nesting, useKey())\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\n\t\t\t\t\terr = buf.string(doubleQuote, false)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\n\t\t\t\t\tcurrent, nesting = updateNode(current, buf, nesting, true)\n\t\t\t\t\tbuf.state = OK\n\t\t\t\t}\n\n\t\t\tcase MI, ZE, IN: // number\n\t\t\t\tcurrent, err = processNumericNode(current, buf, useKey())\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\tcase T1, F1: // boolean\n\t\t\t\tliteral := falseLiteral\n\t\t\t\tif buf.state == T1 {\n\t\t\t\t\tliteral = trueLiteral\n\t\t\t\t}\n\n\t\t\t\tcurrent, nesting, err = processLiteralNode(current, buf, Boolean, literal, useKey(), nesting)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\tcase N1: // null\n\t\t\t\tcurrent, nesting, err = processLiteralNode(current, buf, Null, nullLiteral, useKey(), nesting)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t// region action\n\t\t\tswitch state {\n\t\t\tcase ec, cc: // \u003cempty\u003e }\n\t\t\t\tif key != nil {\n\t\t\t\t\treturn nil, unexpectedTokenError(buf.data, buf.index)\n\t\t\t\t}\n\n\t\t\t\tcurrent, nesting, err = updateNodeAndSetBufferState(current, buf, nesting, Object)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\tcase bc: // ]\n\t\t\t\tcurrent, nesting, err = updateNodeAndSetBufferState(current, buf, nesting, Array)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\tcase co, bo: // { [\n\t\t\t\tvalTyp, bState := Object, OB\n\t\t\t\tif state == bo {\n\t\t\t\t\tvalTyp, bState = Array, AR\n\t\t\t\t}\n\n\t\t\t\tcurrent, nesting, err = createNestedNode(current, buf, valTyp, nesting, useKey())\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\t\tbuf.state = bState\n\n\t\t\tcase cm: // ,\n\t\t\t\tif current == nil {\n\t\t\t\t\treturn nil, unexpectedTokenError(buf.data, buf.index)\n\t\t\t\t}\n\n\t\t\t\tif !current.isContainer() {\n\t\t\t\t\treturn nil, unexpectedTokenError(buf.data, buf.index)\n\t\t\t\t}\n\n\t\t\t\tif current.IsObject() {\n\t\t\t\t\tbuf.state = KE // key expected\n\t\t\t\t} else {\n\t\t\t\t\tbuf.state = VA // value expected\n\t\t\t\t}\n\n\t\t\tcase cl: // :\n\t\t\t\tif current == nil || !current.IsObject() || key == nil {\n\t\t\t\t\treturn nil, unexpectedTokenError(buf.data, buf.index)\n\t\t\t\t}\n\n\t\t\t\tbuf.state = VA\n\n\t\t\tdefault:\n\t\t\t\treturn nil, unexpectedTokenError(buf.data, buf.index)\n\t\t\t}\n\t\t}\n\n\t\tif buf.step() != nil {\n\t\t\tbreak\n\t\t}\n\n\t\tif _, err = buf.first(); err != nil {\n\t\t\terr = nil\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif current == nil || buf.state != OK {\n\t\treturn nil, io.EOF\n\t}\n\n\troot := current.root()\n\tif !root.ready() {\n\t\treturn nil, io.EOF\n\t}\n\n\treturn root, err\n}\n\n// UnmarshalSafe parses the JSON-encoded data and returns a Node.\nfunc UnmarshalSafe(data []byte) (*Node, error) {\n\tvar safe []byte\n\tsafe = append(safe, data...)\n\treturn Unmarshal(safe)\n}\n\n// processNumericNode creates a new node, processes a numeric value,\n// sets the node's borders, and moves to the previous node.\nfunc processNumericNode(current *Node, buf *buffer, key **string) (*Node, error) {\n\tvar err error\n\tcurrent, err = createNode(current, buf, Number, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err = buf.numeric(false); err != nil {\n\t\treturn nil, err\n\t}\n\n\tcurrent.borders[1] = buf.index\n\tif current.prev != nil {\n\t\tcurrent = current.prev\n\t}\n\n\tbuf.index -= 1\n\tbuf.state = OK\n\n\treturn current, nil\n}\n\n// processLiteralNode creates a new node, processes a literal value,\n// sets the node's borders, and moves to the previous node.\nfunc processLiteralNode(\n\tcurrent *Node,\n\tbuf *buffer,\n\tliteralType ValueType,\n\tliteralValue []byte,\n\tuseKey **string,\n\tnesting int,\n) (*Node, int, error) {\n\tvar err error\n\tcurrent, nesting, err = createLiteralNode(current, buf, literalType, literalValue, useKey, nesting)\n\tif err != nil {\n\t\treturn nil, nesting, err\n\t}\n\treturn current, nesting, nil\n}\n\n// isValidContainerType checks if the current node is a valid container (object or array).\n// The container must satisfy the following conditions:\n// 1. The current node must not be nil.\n// 2. The current node must be an object or array.\n// 3. The current node must not be ready.\nfunc isValidContainerType(current *Node, nodeType ValueType) bool {\n\tswitch nodeType {\n\tcase Object:\n\t\treturn current != nil \u0026\u0026 current.IsObject() \u0026\u0026 !current.ready()\n\tcase Array:\n\t\treturn current != nil \u0026\u0026 current.IsArray() \u0026\u0026 !current.ready()\n\tdefault:\n\t\treturn false\n\t}\n}\n\n// getString extracts a string from the buffer and advances the buffer index past the string.\nfunc getString(b *buffer) (*string, error) {\n\tstart := b.index\n\tif err := b.string(doubleQuote, false); err != nil {\n\t\treturn nil, err\n\t}\n\n\tvalue, ok := Unquote(b.data[start:b.index+1], doubleQuote)\n\tif !ok {\n\t\treturn nil, unexpectedTokenError(b.data, start)\n\t}\n\n\treturn \u0026value, nil\n}\n\n// createNode creates a new node and sets the key if it is not nil.\nfunc createNode(\n\tcurrent *Node,\n\tbuf *buffer,\n\tnodeType ValueType,\n\tkey **string,\n) (*Node, error) {\n\tvar err error\n\tcurrent, err = NewNode(current, buf, nodeType, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn current, nil\n}\n\n// createNestedNode creates a new nested node (array or object) and sets the key if it is not nil.\nfunc createNestedNode(\n\tcurrent *Node,\n\tbuf *buffer,\n\tnodeType ValueType,\n\tnesting int,\n\tkey **string,\n) (*Node, int, error) {\n\tvar err error\n\tif nesting, err = checkNestingDepth(nesting); err != nil {\n\t\treturn nil, nesting, err\n\t}\n\n\tif current, err = createNode(current, buf, nodeType, key); err != nil {\n\t\treturn nil, nesting, err\n\t}\n\n\treturn current, nesting, nil\n}\n\n// createLiteralNode creates a new literal node and sets the key if it is not nil.\n// The literal is a byte slice that represents a boolean or null value.\nfunc createLiteralNode(\n\tcurrent *Node,\n\tbuf *buffer,\n\tliteralType ValueType,\n\tliteral []byte,\n\tuseKey **string,\n\tnesting int,\n) (*Node, int, error) {\n\tvar err error\n\tif current, err = createNode(current, buf, literalType, useKey); err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\tif err = buf.word(literal); err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\tcurrent, nesting = updateNode(current, buf, nesting, false)\n\tbuf.state = OK\n\n\treturn current, nesting, nil\n}\n\n// updateNode updates the current node and returns the previous node.\nfunc updateNode(\n\tcurrent *Node, buf *buffer, nesting int, decreaseLevel bool,\n) (*Node, int) {\n\tcurrent.borders[1] = buf.index + 1\n\n\tprev := current.prev\n\tif prev == nil {\n\t\treturn current, nesting\n\t}\n\n\tcurrent = prev\n\tif decreaseLevel {\n\t\tnesting--\n\t}\n\n\treturn current, nesting\n}\n\n// updateNodeAndSetBufferState updates the current node and sets the buffer state to OK.\nfunc updateNodeAndSetBufferState(\n\tcurrent *Node,\n\tbuf *buffer,\n\tnesting int,\n\ttyp ValueType,\n) (*Node, int, error) {\n\tif !isValidContainerType(current, typ) {\n\t\treturn nil, nesting, unexpectedTokenError(buf.data, buf.index)\n\t}\n\n\tcurrent, nesting = updateNode(current, buf, nesting, true)\n\tbuf.state = OK\n\n\treturn current, nesting, nil\n}\n\n// checkNestingDepth checks if the nesting depth is within the maximum allowed depth.\nfunc checkNestingDepth(nesting int) (int, error) {\n\tif nesting \u003e= maxNestingDepth {\n\t\treturn nesting, errors.New(\"maximum nesting depth exceeded\")\n\t}\n\n\treturn nesting + 1, nil\n}\n\nfunc unexpectedTokenError(data []byte, index int) error {\n\treturn ufmt.Errorf(\"unexpected token at index %d. data %b\", index, data)\n}\n"},{"name":"decode_test.gno","body":"package json\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n)\n\ntype testNode struct {\n\tname string\n\tinput []byte\n\tvalue []byte\n\t_type ValueType\n}\n\nfunc simpleValid(test *testNode, t *testing.T) {\n\troot, err := Unmarshal(test.input)\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(%s): %s\", test.input, err.Error())\n\t} else if root == nil {\n\t\tt.Errorf(\"Error on Unmarshal(%s): root is nil\", test.name)\n\t} else if root.nodeType != test._type {\n\t\tt.Errorf(\"Error on Unmarshal(%s): wrong type\", test.name)\n\t} else if !bytes.Equal(root.source(), test.value) {\n\t\tt.Errorf(\"Error on Unmarshal(%s): %s != %s\", test.name, root.source(), test.value)\n\t}\n}\n\nfunc simpleInvalid(test *testNode, t *testing.T) {\n\troot, err := Unmarshal(test.input)\n\tif err == nil {\n\t\tt.Errorf(\"Error on Unmarshal(%s): error expected, got '%s'\", test.name, root.source())\n\t} else if root != nil {\n\t\tt.Errorf(\"Error on Unmarshal(%s): root is not nil\", test.name)\n\t}\n}\n\nfunc simpleCorrupted(name string) *testNode {\n\treturn \u0026testNode{name: name, input: []byte(name)}\n}\n\nfunc TestUnmarshal_StringSimpleSuccess(t *testing.T) {\n\ttests := []*testNode{\n\t\t{name: \"blank\", input: []byte(\"\\\"\\\"\"), _type: String, value: []byte(\"\\\"\\\"\")},\n\t\t{name: \"char\", input: []byte(\"\\\"c\\\"\"), _type: String, value: []byte(\"\\\"c\\\"\")},\n\t\t{name: \"word\", input: []byte(\"\\\"cat\\\"\"), _type: String, value: []byte(\"\\\"cat\\\"\")},\n\t\t{name: \"spaces\", input: []byte(\" \\\"good cat or dog\\\"\\r\\n \"), _type: String, value: []byte(\"\\\"good cat or dog\\\"\")},\n\t\t{name: \"backslash\", input: []byte(\"\\\"good \\\\\\\"cat\\\\\\\"\\\"\"), _type: String, value: []byte(\"\\\"good \\\\\\\"cat\\\\\\\"\\\"\")},\n\t\t{name: \"backslash 2\", input: []byte(\"\\\"good \\\\\\\\\\\\\\\"cat\\\\\\\"\\\"\"), _type: String, value: []byte(\"\\\"good \\\\\\\\\\\\\\\"cat\\\\\\\"\\\"\")},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tsimpleValid(test, t)\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal_NumericSimpleSuccess(t *testing.T) {\n\ttests := []*testNode{\n\t\t{name: \"1\", input: []byte(\"1\"), _type: Number, value: []byte(\"1\")},\n\t\t{name: \"-1\", input: []byte(\"-1\"), _type: Number, value: []byte(\"-1\")},\n\n\t\t{name: \"1234567890\", input: []byte(\"1234567890\"), _type: Number, value: []byte(\"1234567890\")},\n\t\t{name: \"-123\", input: []byte(\"-123\"), _type: Number, value: []byte(\"-123\")},\n\n\t\t{name: \"123.456\", input: []byte(\"123.456\"), _type: Number, value: []byte(\"123.456\")},\n\t\t{name: \"-123.456\", input: []byte(\"-123.456\"), _type: Number, value: []byte(\"-123.456\")},\n\n\t\t{name: \"1e3\", input: []byte(\"1e3\"), _type: Number, value: []byte(\"1e3\")},\n\t\t{name: \"1e+3\", input: []byte(\"1e+3\"), _type: Number, value: []byte(\"1e+3\")},\n\t\t{name: \"1e-3\", input: []byte(\"1e-3\"), _type: Number, value: []byte(\"1e-3\")},\n\t\t{name: \"-1e3\", input: []byte(\"-1e3\"), _type: Number, value: []byte(\"-1e3\")},\n\t\t{name: \"-1e-3\", input: []byte(\"-1e-3\"), _type: Number, value: []byte(\"-1e-3\")},\n\n\t\t{name: \"1.123e3456\", input: []byte(\"1.123e3456\"), _type: Number, value: []byte(\"1.123e3456\")},\n\t\t{name: \"1.123e-3456\", input: []byte(\"1.123e-3456\"), _type: Number, value: []byte(\"1.123e-3456\")},\n\t\t{name: \"-1.123e3456\", input: []byte(\"-1.123e3456\"), _type: Number, value: []byte(\"-1.123e3456\")},\n\t\t{name: \"-1.123e-3456\", input: []byte(\"-1.123e-3456\"), _type: Number, value: []byte(\"-1.123e-3456\")},\n\n\t\t{name: \"1E3\", input: []byte(\"1E3\"), _type: Number, value: []byte(\"1E3\")},\n\t\t{name: \"1E-3\", input: []byte(\"1E-3\"), _type: Number, value: []byte(\"1E-3\")},\n\t\t{name: \"-1E3\", input: []byte(\"-1E3\"), _type: Number, value: []byte(\"-1E3\")},\n\t\t{name: \"-1E-3\", input: []byte(\"-1E-3\"), _type: Number, value: []byte(\"-1E-3\")},\n\n\t\t{name: \"1.123E3456\", input: []byte(\"1.123E3456\"), _type: Number, value: []byte(\"1.123E3456\")},\n\t\t{name: \"1.123E-3456\", input: []byte(\"1.123E-3456\"), _type: Number, value: []byte(\"1.123E-3456\")},\n\t\t{name: \"-1.123E3456\", input: []byte(\"-1.123E3456\"), _type: Number, value: []byte(\"-1.123E3456\")},\n\t\t{name: \"-1.123E-3456\", input: []byte(\"-1.123E-3456\"), _type: Number, value: []byte(\"-1.123E-3456\")},\n\n\t\t{name: \"-1.123E-3456 with spaces\", input: []byte(\" \\r -1.123E-3456 \\t\\n\"), _type: Number, value: []byte(\"-1.123E-3456\")},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\troot, err := Unmarshal(test.input)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Error on Unmarshal(%s): %s\", test.name, err.Error())\n\t\t\t} else if root == nil {\n\t\t\t\tt.Errorf(\"Error on Unmarshal(%s): root is nil\", test.name)\n\t\t\t} else if root.nodeType != test._type {\n\t\t\t\tt.Errorf(\"Error on Unmarshal(%s): wrong type\", test.name)\n\t\t\t} else if !bytes.Equal(root.source(), test.value) {\n\t\t\t\tt.Errorf(\"Error on Unmarshal(%s): %s != %s\", test.name, root.source(), test.value)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal_StringSimpleCorrupted(t *testing.T) {\n\ttests := []*testNode{\n\t\t{name: \"white NL\", input: []byte(\"\\\"foo\\nbar\\\"\")},\n\t\t{name: \"white R\", input: []byte(\"\\\"foo\\rbar\\\"\")},\n\t\t{name: \"white Tab\", input: []byte(\"\\\"foo\\tbar\\\"\")},\n\t\t{name: \"wrong quotes\", input: []byte(\"'cat'\")},\n\t\t{name: \"double string\", input: []byte(\"\\\"Hello\\\" \\\"World\\\"\")},\n\t\t{name: \"quotes in quotes\", input: []byte(\"\\\"good \\\"cat\\\"\\\"\")},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tsimpleInvalid(test, t)\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal_ObjectSimpleSuccess(t *testing.T) {\n\ttests := []*testNode{\n\t\t{name: \"{}\", input: []byte(\"{}\"), _type: Object, value: []byte(\"{}\")},\n\t\t{name: `{ \\r\\n }`, input: []byte(\"{ \\r\\n }\"), _type: Object, value: []byte(\"{ \\r\\n }\")},\n\t\t{name: `{\"key\":1}`, input: []byte(`{\"key\":1}`), _type: Object, value: []byte(`{\"key\":1}`)},\n\t\t{name: `{\"key\":true}`, input: []byte(`{\"key\":true}`), _type: Object, value: []byte(`{\"key\":true}`)},\n\t\t{name: `{\"key\":\"value\"}`, input: []byte(`{\"key\":\"value\"}`), _type: Object, value: []byte(`{\"key\":\"value\"}`)},\n\t\t{name: `{\"foo\":\"bar\",\"baz\":\"foo\"}`, input: []byte(`{\"foo\":\"bar\", \"baz\":\"foo\"}`), _type: Object, value: []byte(`{\"foo\":\"bar\", \"baz\":\"foo\"}`)},\n\t\t{name: \"spaces\", input: []byte(` { \"foo\" : \"bar\" , \"baz\" : \"foo\" } `), _type: Object, value: []byte(`{ \"foo\" : \"bar\" , \"baz\" : \"foo\" }`)},\n\t\t{name: \"nested\", input: []byte(`{\"foo\":{\"bar\":{\"baz\":{}}}}`), _type: Object, value: []byte(`{\"foo\":{\"bar\":{\"baz\":{}}}}`)},\n\t\t{name: \"array\", input: []byte(`{\"array\":[{},{},{\"foo\":[{\"bar\":[\"baz\"]}]}]}`), _type: Object, value: []byte(`{\"array\":[{},{},{\"foo\":[{\"bar\":[\"baz\"]}]}]}`)},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tsimpleValid(test, t)\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal_ObjectSimpleCorrupted(t *testing.T) {\n\ttests := []*testNode{\n\t\tsimpleCorrupted(\"{{{\\\"key\\\": \\\"foo\\\"{{{{\"),\n\t\tsimpleCorrupted(\"}\"),\n\t\tsimpleCorrupted(\"{ }}}}}}}\"),\n\t\tsimpleCorrupted(\" }\"),\n\t\tsimpleCorrupted(\"{,}\"),\n\t\tsimpleCorrupted(\"{:}\"),\n\t\tsimpleCorrupted(\"{100000}\"),\n\t\tsimpleCorrupted(\"{1:1}\"),\n\t\tsimpleCorrupted(\"{'1:2,3:4'}\"),\n\t\tsimpleCorrupted(`{\"d\"}`),\n\t\tsimpleCorrupted(`{\"foo\"}`),\n\t\tsimpleCorrupted(`{\"foo\":}`),\n\t\tsimpleCorrupted(`{:\"foo\"}`),\n\t\tsimpleCorrupted(`{\"foo\":bar}`),\n\t\tsimpleCorrupted(`{\"foo\":\"bar\",}`),\n\t\tsimpleCorrupted(`{}{}`),\n\t\tsimpleCorrupted(`{},{}`),\n\t\tsimpleCorrupted(`{[},{]}`),\n\t\tsimpleCorrupted(`{[,]}`),\n\t\tsimpleCorrupted(`{[]}`),\n\t\tsimpleCorrupted(`{}1`),\n\t\tsimpleCorrupted(`1{}`),\n\t\tsimpleCorrupted(`{\"x\"::1}`),\n\t\tsimpleCorrupted(`{null:null}`),\n\t\tsimpleCorrupted(`{\"foo:\"bar\"}`),\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tsimpleInvalid(test, t)\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal_NullSimpleCorrupted(t *testing.T) {\n\ttests := []*testNode{\n\t\t{name: \"nul\", input: []byte(\"nul\")},\n\t\t{name: \"nil\", input: []byte(\"nil\")},\n\t\t{name: \"nill\", input: []byte(\"nill\")},\n\t\t{name: \"NILL\", input: []byte(\"NILL\")},\n\t\t{name: \"Null\", input: []byte(\"Null\")},\n\t\t{name: \"NULL\", input: []byte(\"NULL\")},\n\t\t{name: \"spaces\", input: []byte(\"Nu ll\")},\n\t\t{name: \"null1\", input: []byte(\"null1\")},\n\t\t{name: \"double\", input: []byte(\"null null\")},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tsimpleInvalid(test, t)\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal_BoolSimpleSuccess(t *testing.T) {\n\ttests := []*testNode{\n\t\t{name: \"lower true\", input: []byte(\"true\"), _type: Boolean, value: []byte(\"true\")},\n\t\t{name: \"lower false\", input: []byte(\"false\"), _type: Boolean, value: []byte(\"false\")},\n\t\t{name: \"spaces true\", input: []byte(\" true\\r\\n \"), _type: Boolean, value: []byte(\"true\")},\n\t\t{name: \"spaces false\", input: []byte(\" false\\r\\n \"), _type: Boolean, value: []byte(\"false\")},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tsimpleValid(test, t)\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal_BoolSimpleCorrupted(t *testing.T) {\n\ttests := []*testNode{\n\t\tsimpleCorrupted(\"tru\"),\n\t\tsimpleCorrupted(\"fals\"),\n\t\tsimpleCorrupted(\"tre\"),\n\t\tsimpleCorrupted(\"fal se\"),\n\t\tsimpleCorrupted(\"true false\"),\n\t\tsimpleCorrupted(\"True\"),\n\t\tsimpleCorrupted(\"TRUE\"),\n\t\tsimpleCorrupted(\"False\"),\n\t\tsimpleCorrupted(\"FALSE\"),\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tsimpleInvalid(test, t)\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal_ArraySimpleSuccess(t *testing.T) {\n\ttests := []*testNode{\n\t\t{name: \"[]\", input: []byte(\"[]\"), _type: Array, value: []byte(\"[]\")},\n\t\t{name: \"[1]\", input: []byte(\"[1]\"), _type: Array, value: []byte(\"[1]\")},\n\t\t{name: \"[1,2,3]\", input: []byte(\"[1,2,3]\"), _type: Array, value: []byte(\"[1,2,3]\")},\n\t\t{name: \"[1, 2, 3]\", input: []byte(\"[1, 2, 3]\"), _type: Array, value: []byte(\"[1, 2, 3]\")},\n\t\t{name: \"[1,[2],3]\", input: []byte(\"[1,[2],3]\"), _type: Array, value: []byte(\"[1,[2],3]\")},\n\t\t{name: \"[[],[],[]]\", input: []byte(\"[[],[],[]]\"), _type: Array, value: []byte(\"[[],[],[]]\")},\n\t\t{name: \"[[[[[]]]]]\", input: []byte(\"[[[[[]]]]]\"), _type: Array, value: []byte(\"[[[[[]]]]]\")},\n\t\t{name: \"[true,null,1,\\\"foo\\\",[]]\", input: []byte(\"[true,null,1,\\\"foo\\\",[]]\"), _type: Array, value: []byte(\"[true,null,1,\\\"foo\\\",[]]\")},\n\t\t{name: \"spaces\", input: []byte(\"\\n\\r [\\n1\\n ]\\r\\n\"), _type: Array, value: []byte(\"[\\n1\\n ]\")},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tsimpleValid(test, t)\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal_ArraySimpleCorrupted(t *testing.T) {\n\ttests := []*testNode{\n\t\tsimpleCorrupted(\"[,]\"),\n\t\tsimpleCorrupted(\"[]\\\\\"),\n\t\tsimpleCorrupted(\"[1,]\"),\n\t\tsimpleCorrupted(\"[[]\"),\n\t\tsimpleCorrupted(\"[]]\"),\n\t\tsimpleCorrupted(\"1[]\"),\n\t\tsimpleCorrupted(\"[]1\"),\n\t\tsimpleCorrupted(\"[[]1]\"),\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tsimpleInvalid(test, t)\n\t\t})\n\t}\n}\n\n// Examples from https://json.org/example.html\nfunc TestUnmarshal(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tvalue string\n\t}{\n\t\t{\n\t\t\tname: \"glossary\",\n\t\t\tvalue: `{\n\t\t\t\t\"glossary\": {\n\t\t\t\t\t\"title\": \"example glossary\",\n\t\t\t\t\t\"GlossDiv\": {\n\t\t\t\t\t\t\"title\": \"S\",\n\t\t\t\t\t\t\"GlossList\": {\n\t\t\t\t\t\t\t\"GlossEntry\": {\n\t\t\t\t\t\t\t\t\"ID\": \"SGML\",\n\t\t\t\t\t\t\t\t\"SortAs\": \"SGML\",\n\t\t\t\t\t\t\t\t\"GlossTerm\": \"Standard Generalized Markup Language\",\n\t\t\t\t\t\t\t\t\"Acronym\": \"SGML\",\n\t\t\t\t\t\t\t\t\"Abbrev\": \"ISO 8879:1986\",\n\t\t\t\t\t\t\t\t\"GlossDef\": {\n\t\t\t\t\t\t\t\t\t\"para\": \"A meta-markup language, used to create markup languages such as DocBook.\",\n\t\t\t\t\t\t\t\t\t\"GlossSeeAlso\": [\"GML\", \"XML\"]\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"GlossSee\": \"markup\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}`,\n\t\t},\n\t\t{\n\t\t\tname: \"menu\",\n\t\t\tvalue: `{\"menu\": {\n\t\t\t\t\"id\": \"file\",\n\t\t\t\t\"value\": \"File\",\n\t\t\t\t\"popup\": {\n\t\t\t\t \"menuitem\": [\n\t\t\t\t\t{\"value\": \"New\", \"onclick\": \"CreateNewDoc()\"},\n\t\t\t\t\t{\"value\": \"Open\", \"onclick\": \"OpenDoc()\"},\n\t\t\t\t\t{\"value\": \"Close\", \"onclick\": \"CloseDoc()\"}\n\t\t\t\t ]\n\t\t\t\t}\n\t\t\t}}`,\n\t\t},\n\t\t{\n\t\t\tname: \"widget\",\n\t\t\tvalue: `{\"widget\": {\n\t\t\t\t\"debug\": \"on\",\n\t\t\t\t\"window\": {\n\t\t\t\t\t\"title\": \"Sample Konfabulator Widget\",\n\t\t\t\t\t\"name\": \"main_window\",\n\t\t\t\t\t\"width\": 500,\n\t\t\t\t\t\"height\": 500\n\t\t\t\t},\n\t\t\t\t\"image\": { \n\t\t\t\t\t\"src\": \"Images/Sun.png\",\n\t\t\t\t\t\"name\": \"sun1\",\n\t\t\t\t\t\"hOffset\": 250,\n\t\t\t\t\t\"vOffset\": 250,\n\t\t\t\t\t\"alignment\": \"center\"\n\t\t\t\t},\n\t\t\t\t\"text\": {\n\t\t\t\t\t\"data\": \"Click Here\",\n\t\t\t\t\t\"size\": 36,\n\t\t\t\t\t\"style\": \"bold\",\n\t\t\t\t\t\"name\": \"text1\",\n\t\t\t\t\t\"hOffset\": 250,\n\t\t\t\t\t\"vOffset\": 100,\n\t\t\t\t\t\"alignment\": \"center\",\n\t\t\t\t\t\"onMouseUp\": \"sun1.opacity = (sun1.opacity / 100) * 90;\"\n\t\t\t\t}\n\t\t\t}} `,\n\t\t},\n\t\t{\n\t\t\tname: \"web-app\",\n\t\t\tvalue: `{\"web-app\": {\n\t\t\t\t\"servlet\": [ \n\t\t\t\t {\n\t\t\t\t\t\"servlet-name\": \"cofaxCDS\",\n\t\t\t\t\t\"servlet-class\": \"org.cofax.cds.CDSServlet\",\n\t\t\t\t\t\"init-param\": {\n\t\t\t\t\t \"configGlossary:installationAt\": \"Philadelphia, PA\",\n\t\t\t\t\t \"configGlossary:adminEmail\": \"ksm@pobox.com\",\n\t\t\t\t\t \"configGlossary:poweredBy\": \"Cofax\",\n\t\t\t\t\t \"configGlossary:poweredByIcon\": \"/images/cofax.gif\",\n\t\t\t\t\t \"configGlossary:staticPath\": \"/content/static\",\n\t\t\t\t\t \"templateProcessorClass\": \"org.cofax.WysiwygTemplate\",\n\t\t\t\t\t \"templateLoaderClass\": \"org.cofax.FilesTemplateLoader\",\n\t\t\t\t\t \"templatePath\": \"templates\",\n\t\t\t\t\t \"templateOverridePath\": \"\",\n\t\t\t\t\t \"defaultListTemplate\": \"listTemplate.htm\",\n\t\t\t\t\t \"defaultFileTemplate\": \"articleTemplate.htm\",\n\t\t\t\t\t \"useJSP\": false,\n\t\t\t\t\t \"jspListTemplate\": \"listTemplate.jsp\",\n\t\t\t\t\t \"jspFileTemplate\": \"articleTemplate.jsp\",\n\t\t\t\t\t \"cachePackageTagsTrack\": 200,\n\t\t\t\t\t \"cachePackageTagsStore\": 200,\n\t\t\t\t\t \"cachePackageTagsRefresh\": 60,\n\t\t\t\t\t \"cacheTemplatesTrack\": 100,\n\t\t\t\t\t \"cacheTemplatesStore\": 50,\n\t\t\t\t\t \"cacheTemplatesRefresh\": 15,\n\t\t\t\t\t \"cachePagesTrack\": 200,\n\t\t\t\t\t \"cachePagesStore\": 100,\n\t\t\t\t\t \"cachePagesRefresh\": 10,\n\t\t\t\t\t \"cachePagesDirtyRead\": 10,\n\t\t\t\t\t \"searchEngineListTemplate\": \"forSearchEnginesList.htm\",\n\t\t\t\t\t \"searchEngineFileTemplate\": \"forSearchEngines.htm\",\n\t\t\t\t\t \"searchEngineRobotsDb\": \"WEB-INF/robots.db\",\n\t\t\t\t\t \"useDataStore\": true,\n\t\t\t\t\t \"dataStoreClass\": \"org.cofax.SqlDataStore\",\n\t\t\t\t\t \"redirectionClass\": \"org.cofax.SqlRedirection\",\n\t\t\t\t\t \"dataStoreName\": \"cofax\",\n\t\t\t\t\t \"dataStoreDriver\": \"com.microsoft.jdbc.sqlserver.SQLServerDriver\",\n\t\t\t\t\t \"dataStoreUrl\": \"jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon\",\n\t\t\t\t\t \"dataStoreUser\": \"sa\",\n\t\t\t\t\t \"dataStorePassword\": \"dataStoreTestQuery\",\n\t\t\t\t\t \"dataStoreTestQuery\": \"SET NOCOUNT ON;select test='test';\",\n\t\t\t\t\t \"dataStoreLogFile\": \"/usr/local/tomcat/logs/datastore.log\",\n\t\t\t\t\t \"dataStoreInitConns\": 10,\n\t\t\t\t\t \"dataStoreMaxConns\": 100,\n\t\t\t\t\t \"dataStoreConnUsageLimit\": 100,\n\t\t\t\t\t \"dataStoreLogLevel\": \"debug\",\n\t\t\t\t\t \"maxUrlLength\": 500}},\n\t\t\t\t {\n\t\t\t\t\t\"servlet-name\": \"cofaxEmail\",\n\t\t\t\t\t\"servlet-class\": \"org.cofax.cds.EmailServlet\",\n\t\t\t\t\t\"init-param\": {\n\t\t\t\t\t\"mailHost\": \"mail1\",\n\t\t\t\t\t\"mailHostOverride\": \"mail2\"}},\n\t\t\t\t {\n\t\t\t\t\t\"servlet-name\": \"cofaxAdmin\",\n\t\t\t\t\t\"servlet-class\": \"org.cofax.cds.AdminServlet\"},\n\t\t\t \n\t\t\t\t {\n\t\t\t\t\t\"servlet-name\": \"fileServlet\",\n\t\t\t\t\t\"servlet-class\": \"org.cofax.cds.FileServlet\"},\n\t\t\t\t {\n\t\t\t\t\t\"servlet-name\": \"cofaxTools\",\n\t\t\t\t\t\"servlet-class\": \"org.cofax.cms.CofaxToolsServlet\",\n\t\t\t\t\t\"init-param\": {\n\t\t\t\t\t \"templatePath\": \"toolstemplates/\",\n\t\t\t\t\t \"log\": 1,\n\t\t\t\t\t \"logLocation\": \"/usr/local/tomcat/logs/CofaxTools.log\",\n\t\t\t\t\t \"logMaxSize\": \"\",\n\t\t\t\t\t \"dataLog\": 1,\n\t\t\t\t\t \"dataLogLocation\": \"/usr/local/tomcat/logs/dataLog.log\",\n\t\t\t\t\t \"dataLogMaxSize\": \"\",\n\t\t\t\t\t \"removePageCache\": \"/content/admin/remove?cache=pages\u0026id=\",\n\t\t\t\t\t \"removeTemplateCache\": \"/content/admin/remove?cache=templates\u0026id=\",\n\t\t\t\t\t \"fileTransferFolder\": \"/usr/local/tomcat/webapps/content/fileTransferFolder\",\n\t\t\t\t\t \"lookInContext\": 1,\n\t\t\t\t\t \"adminGroupID\": 4,\n\t\t\t\t\t \"betaServer\": true}}],\n\t\t\t\t\"servlet-mapping\": {\n\t\t\t\t \"cofaxCDS\": \"/\",\n\t\t\t\t \"cofaxEmail\": \"/cofaxutil/aemail/*\",\n\t\t\t\t \"cofaxAdmin\": \"/admin/*\",\n\t\t\t\t \"fileServlet\": \"/static/*\",\n\t\t\t\t \"cofaxTools\": \"/tools/*\"},\n\t\t\t \n\t\t\t\t\"taglib\": {\n\t\t\t\t \"taglib-uri\": \"cofax.tld\",\n\t\t\t\t \"taglib-location\": \"/WEB-INF/tlds/cofax.tld\"}}}`,\n\t\t},\n\t\t{\n\t\t\tname: \"SVG Viewer\",\n\t\t\tvalue: `{\"menu\": {\n\t\t\t\t\"header\": \"SVG Viewer\",\n\t\t\t\t\"items\": [\n\t\t\t\t\t{\"id\": \"Open\"},\n\t\t\t\t\t{\"id\": \"OpenNew\", \"label\": \"Open New\"},\n\t\t\t\t\tnull,\n\t\t\t\t\t{\"id\": \"ZoomIn\", \"label\": \"Zoom In\"},\n\t\t\t\t\t{\"id\": \"ZoomOut\", \"label\": \"Zoom Out\"},\n\t\t\t\t\t{\"id\": \"OriginalView\", \"label\": \"Original View\"},\n\t\t\t\t\tnull,\n\t\t\t\t\t{\"id\": \"Quality\"},\n\t\t\t\t\t{\"id\": \"Pause\"},\n\t\t\t\t\t{\"id\": \"Mute\"},\n\t\t\t\t\tnull,\n\t\t\t\t\t{\"id\": \"Find\", \"label\": \"Find...\"},\n\t\t\t\t\t{\"id\": \"FindAgain\", \"label\": \"Find Again\"},\n\t\t\t\t\t{\"id\": \"Copy\"},\n\t\t\t\t\t{\"id\": \"CopyAgain\", \"label\": \"Copy Again\"},\n\t\t\t\t\t{\"id\": \"CopySVG\", \"label\": \"Copy SVG\"},\n\t\t\t\t\t{\"id\": \"ViewSVG\", \"label\": \"View SVG\"},\n\t\t\t\t\t{\"id\": \"ViewSource\", \"label\": \"View Source\"},\n\t\t\t\t\t{\"id\": \"SaveAs\", \"label\": \"Save As\"},\n\t\t\t\t\tnull,\n\t\t\t\t\t{\"id\": \"Help\"},\n\t\t\t\t\t{\"id\": \"About\", \"label\": \"About Adobe CVG Viewer...\"}\n\t\t\t\t]\n\t\t\t}}`,\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\t_, err := Unmarshal([]byte(test.value))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Error on Unmarshal: %s\", err.Error())\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUnmarshalSafe(t *testing.T) {\n\tjson := []byte(`{ \"store\": {\n\t\t\"book\": [ \n\t\t { \"category\": \"reference\",\n\t\t\t\"author\": \"Nigel Rees\",\n\t\t\t\"title\": \"Sayings of the Century\",\n\t\t\t\"price\": 8.95\n\t\t },\n\t\t { \"category\": \"fiction\",\n\t\t\t\"author\": \"Evelyn Waugh\",\n\t\t\t\"title\": \"Sword of Honour\",\n\t\t\t\"price\": 12.99\n\t\t },\n\t\t { \"category\": \"fiction\",\n\t\t\t\"author\": \"Herman Melville\",\n\t\t\t\"title\": \"Moby Dick\",\n\t\t\t\"isbn\": \"0-553-21311-3\",\n\t\t\t\"price\": 8.99\n\t\t },\n\t\t { \"category\": \"fiction\",\n\t\t\t\"author\": \"J. R. R. Tolkien\",\n\t\t\t\"title\": \"The Lord of the Rings\",\n\t\t\t\"isbn\": \"0-395-19395-8\",\n\t\t\t\"price\": 22.99\n\t\t }\n\t\t],\n\t\t\"bicycle\": {\n\t\t \"color\": \"red\",\n\t\t \"price\": 19.95\n\t\t}\n\t }\n\t}`)\n\tsafe, err := UnmarshalSafe(json)\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal: %s\", err.Error())\n\t} else if safe == nil {\n\t\tt.Errorf(\"Error on Unmarshal: safe is nil\")\n\t} else {\n\t\troot, err := Unmarshal(json)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"Error on Unmarshal: %s\", err.Error())\n\t\t} else if root == nil {\n\t\t\tt.Errorf(\"Error on Unmarshal: root is nil\")\n\t\t} else if !bytes.Equal(root.source(), safe.source()) {\n\t\t\tt.Errorf(\"Error on UnmarshalSafe: values not same\")\n\t\t}\n\t}\n}\n\n// BenchmarkGoStdUnmarshal-8 \t 61698\t 19350 ns/op\t 288 B/op\t 6 allocs/op\n// BenchmarkUnmarshal-8 \t 45620\t 26165 ns/op\t 21889 B/op\t 367 allocs/op\n//\n// type bench struct {\n// \tName string `json:\"name\"`\n// \tValue int `json:\"value\"`\n// }\n\n// func BenchmarkGoStdUnmarshal(b *testing.B) {\n// \tdata := []byte(webApp)\n// \tfor i := 0; i \u003c b.N; i++ {\n// \t\terr := json.Unmarshal(data, \u0026bench{})\n// \t\tif err != nil {\n// \t\t\tb.Fatal(err)\n// \t\t}\n// \t}\n// }\n\n// func BenchmarkUnmarshal(b *testing.B) {\n// \tdata := []byte(webApp)\n// \tfor i := 0; i \u003c b.N; i++ {\n// \t\t_, err := Unmarshal(data)\n// \t\tif err != nil {\n// \t\t\tb.Fatal(err)\n// \t\t}\n// \t}\n// }\n"},{"name":"encode.gno","body":"package json\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Marshal returns the JSON encoding of a Node.\nfunc Marshal(node *Node) ([]byte, error) {\n\tvar (\n\t\tbuf bytes.Buffer\n\t\tsVal string\n\t\tbVal bool\n\t\tnVal float64\n\t\toVal []byte\n\t\terr error\n\t)\n\n\tif node == nil {\n\t\treturn nil, errors.New(\"node is nil\")\n\t}\n\n\tif !node.modified \u0026\u0026 !node.ready() {\n\t\treturn nil, errors.New(\"node is not ready\")\n\t}\n\n\tif !node.modified \u0026\u0026 node.ready() {\n\t\tbuf.Write(node.source())\n\t}\n\n\tif node.modified {\n\t\tswitch node.nodeType {\n\t\tcase Null:\n\t\t\tbuf.Write(nullLiteral)\n\n\t\tcase Number:\n\t\t\tnVal, err = node.GetNumeric()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tnum := strconv.FormatFloat(nVal, 'f', -1, 64)\n\t\t\tbuf.WriteString(num)\n\n\t\tcase String:\n\t\t\tsVal, err = node.GetString()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tquoted := ufmt.Sprintf(\"%s\", strconv.Quote(sVal))\n\t\t\tbuf.WriteString(quoted)\n\n\t\tcase Boolean:\n\t\t\tbVal, err = node.GetBool()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tbStr := ufmt.Sprintf(\"%t\", bVal)\n\t\t\tbuf.WriteString(bStr)\n\n\t\tcase Array:\n\t\t\tbuf.WriteByte(bracketOpen)\n\n\t\t\tfor i := 0; i \u003c len(node.next); i++ {\n\t\t\t\tif i != 0 {\n\t\t\t\t\tbuf.WriteByte(comma)\n\t\t\t\t}\n\n\t\t\t\telem, ok := node.next[strconv.Itoa(i)]\n\t\t\t\tif !ok {\n\t\t\t\t\treturn nil, ufmt.Errorf(\"array element %d is not found\", i)\n\t\t\t\t}\n\n\t\t\t\toVal, err = Marshal(elem)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\t\tbuf.Write(oVal)\n\t\t\t}\n\n\t\t\tbuf.WriteByte(bracketClose)\n\n\t\tcase Object:\n\t\t\tbuf.WriteByte(curlyOpen)\n\n\t\t\tbVal = false\n\t\t\tfor k, v := range node.next {\n\t\t\t\tif bVal {\n\t\t\t\t\tbuf.WriteByte(comma)\n\t\t\t\t} else {\n\t\t\t\t\tbVal = true\n\t\t\t\t}\n\n\t\t\t\tkey := ufmt.Sprintf(\"%s\", strconv.Quote(k))\n\t\t\t\tbuf.WriteString(key)\n\t\t\t\tbuf.WriteByte(colon)\n\n\t\t\t\toVal, err = Marshal(v)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\t\tbuf.Write(oVal)\n\t\t\t}\n\n\t\t\tbuf.WriteByte(curlyClose)\n\t\t}\n\t}\n\n\treturn buf.Bytes(), nil\n}\n"},{"name":"encode_test.gno","body":"package json\n\nimport \"testing\"\n\nfunc TestMarshal_Primitive(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tnode *Node\n\t}{\n\t\t{\n\t\t\tname: \"null\",\n\t\t\tnode: NullNode(\"\"),\n\t\t},\n\t\t{\n\t\t\tname: \"true\",\n\t\t\tnode: BoolNode(\"\", true),\n\t\t},\n\t\t{\n\t\t\tname: \"false\",\n\t\t\tnode: BoolNode(\"\", false),\n\t\t},\n\t\t{\n\t\t\tname: `\"string\"`,\n\t\t\tnode: StringNode(\"\", \"string\"),\n\t\t},\n\t\t{\n\t\t\tname: `\"one \\\"encoded\\\" string\"`,\n\t\t\tnode: StringNode(\"\", `one \"encoded\" string`),\n\t\t},\n\t\t{\n\t\t\tname: `{\"foo\":\"bar\"}`,\n\t\t\tnode: ObjectNode(\"\", map[string]*Node{\n\t\t\t\t\"foo\": StringNode(\"foo\", \"bar\"),\n\t\t\t}),\n\t\t},\n\t\t{\n\t\t\tname: \"42\",\n\t\t\tnode: NumberNode(\"\", 42),\n\t\t},\n\t\t{\n\t\t\tname: \"3.14\",\n\t\t\tnode: NumberNode(\"\", 3.14),\n\t\t},\n\t\t{\n\t\t\tname: `[1,2,3]`,\n\t\t\tnode: ArrayNode(\"\", []*Node{\n\t\t\t\tNumberNode(\"0\", 1),\n\t\t\t\tNumberNode(\"2\", 2),\n\t\t\t\tNumberNode(\"3\", 3),\n\t\t\t}),\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tvalue, err := Marshal(test.node)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t} else if string(value) != test.name {\n\t\t\t\tt.Errorf(\"wrong result: '%s', expected '%s'\", value, test.name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestMarshal_Object(t *testing.T) {\n\tnode := ObjectNode(\"\", map[string]*Node{\n\t\t\"foo\": StringNode(\"foo\", \"bar\"),\n\t\t\"baz\": NumberNode(\"baz\", 100500),\n\t\t\"qux\": NullNode(\"qux\"),\n\t})\n\n\tmustKey := []string{\"foo\", \"baz\", \"qux\"}\n\n\tvalue, err := Marshal(node)\n\tif err != nil {\n\t\tt.Errorf(\"unexpected error: %s\", err)\n\t}\n\n\t// the order of keys in the map is not guaranteed\n\t// so we need to unmarshal the result and check the keys\n\tdecoded, err := Unmarshal(value)\n\tif err != nil {\n\t\tt.Errorf(\"unexpected error: %s\", err)\n\t}\n\n\tfor _, key := range mustKey {\n\t\tif node, err := decoded.GetKey(key); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t} else {\n\t\t\tif node == nil {\n\t\t\t\tt.Errorf(\"node is nil\")\n\t\t\t} else if node.key == nil {\n\t\t\t\tt.Errorf(\"key is nil\")\n\t\t\t} else if *node.key != key {\n\t\t\t\tt.Errorf(\"wrong key: '%s', expected '%s'\", *node.key, key)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc valueNode(prev *Node, key string, typ ValueType, val interface{}) *Node {\n\tcurr := \u0026Node{\n\t\tprev: prev,\n\t\tdata: nil,\n\t\tkey: \u0026key,\n\t\tborders: [2]int{0, 0},\n\t\tvalue: val,\n\t\tmodified: true,\n\t}\n\n\tif val != nil {\n\t\tcurr.nodeType = typ\n\t}\n\n\treturn curr\n}\n\nfunc TestMarshal_Errors(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tnode func() (node *Node)\n\t}{\n\t\t{\n\t\t\tname: \"nil\",\n\t\t\tnode: func() (node *Node) {\n\t\t\t\treturn\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"broken\",\n\t\t\tnode: func() (node *Node) {\n\t\t\t\tnode = Must(Unmarshal([]byte(`{}`)))\n\t\t\t\tnode.borders[1] = 0\n\t\t\t\treturn\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Numeric\",\n\t\t\tnode: func() (node *Node) {\n\t\t\t\treturn valueNode(nil, \"\", Number, false)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"String\",\n\t\t\tnode: func() (node *Node) {\n\t\t\t\treturn valueNode(nil, \"\", String, false)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Bool\",\n\t\t\tnode: func() (node *Node) {\n\t\t\t\treturn valueNode(nil, \"\", Boolean, 1)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Array_1\",\n\t\t\tnode: func() (node *Node) {\n\t\t\t\tnode = ArrayNode(\"\", nil)\n\t\t\t\tnode.next[\"1\"] = NullNode(\"1\")\n\t\t\t\treturn\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Array_2\",\n\t\t\tnode: func() (node *Node) {\n\t\t\t\treturn ArrayNode(\"\", []*Node{valueNode(nil, \"\", Boolean, 1)})\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Object\",\n\t\t\tnode: func() (node *Node) {\n\t\t\t\treturn ObjectNode(\"\", map[string]*Node{\"key\": valueNode(nil, \"key\", Boolean, 1)})\n\t\t\t},\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tvalue, err := Marshal(test.node())\n\t\t\tif err == nil {\n\t\t\t\tt.Errorf(\"expected error\")\n\t\t\t} else if len(value) != 0 {\n\t\t\t\tt.Errorf(\"wrong result\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestMarshal_Nil(t *testing.T) {\n\t_, err := Marshal(nil)\n\tif err == nil {\n\t\tt.Error(\"Expected error for nil node, but got nil\")\n\t}\n}\n\nfunc TestMarshal_NotModified(t *testing.T) {\n\tnode := \u0026Node{}\n\t_, err := Marshal(node)\n\tif err == nil {\n\t\tt.Error(\"Expected error for not modified node, but got nil\")\n\t}\n}\n\nfunc TestMarshalCycleReference(t *testing.T) {\n\tnode1 := \u0026Node{\n\t\tkey: stringPtr(\"node1\"),\n\t\tnodeType: String,\n\t\tnext: map[string]*Node{\n\t\t\t\"next\": nil,\n\t\t},\n\t}\n\n\tnode2 := \u0026Node{\n\t\tkey: stringPtr(\"node2\"),\n\t\tnodeType: String,\n\t\tprev: node1,\n\t}\n\n\tnode1.next[\"next\"] = node2\n\n\t_, err := Marshal(node1)\n\tif err == nil {\n\t\tt.Error(\"Expected error for cycle reference, but got nil\")\n\t}\n}\n\nfunc TestMarshalNoCycleReference(t *testing.T) {\n\tnode1 := \u0026Node{\n\t\tkey: stringPtr(\"node1\"),\n\t\tnodeType: String,\n\t\tvalue: \"value1\",\n\t\tmodified: true,\n\t}\n\n\tnode2 := \u0026Node{\n\t\tkey: stringPtr(\"node2\"),\n\t\tnodeType: String,\n\t\tvalue: \"value2\",\n\t\tmodified: true,\n\t}\n\n\t_, err := Marshal(node1)\n\tif err != nil {\n\t\tt.Errorf(\"Unexpected error: %v\", err)\n\t}\n\n\t_, err = Marshal(node2)\n\tif err != nil {\n\t\tt.Errorf(\"Unexpected error: %v\", err)\n\t}\n}\n\nfunc stringPtr(s string) *string {\n\treturn \u0026s\n}\n"},{"name":"errors.gno","body":"package json\n\nimport \"errors\"\n\nvar (\n\terrNilNode = errors.New(\"node is nil\")\n\terrNotArrayNode = errors.New(\"node is not array\")\n\terrNotBoolNode = errors.New(\"node is not boolean\")\n\terrNotNullNode = errors.New(\"node is not null\")\n\terrNotNumberNode = errors.New(\"node is not number\")\n\terrNotObjectNode = errors.New(\"node is not object\")\n\terrNotStringNode = errors.New(\"node is not string\")\n\terrInvalidToken = errors.New(\"invalid token\")\n\terrIndexNotFound = errors.New(\"index not found\")\n\terrInvalidAppend = errors.New(\"can't append value to non-appendable node\")\n\terrInvalidAppendCycle = errors.New(\"appending value to itself or its children or parents will cause a cycle\")\n\terrInvalidEscapeSequence = errors.New(\"invalid escape sequence\")\n\terrInvalidStringValue = errors.New(\"invalid string value\")\n\terrEmptyBooleanNode = errors.New(\"boolean node is empty\")\n\terrEmptyStringNode = errors.New(\"string node is empty\")\n\terrKeyRequired = errors.New(\"key is required for object\")\n\terrUnmatchedParenthesis = errors.New(\"mismatched bracket or parenthesis\")\n\terrUnmatchedQuotePath = errors.New(\"unmatched quote in path\")\n)\n\nvar (\n\terrInvalidStringInput = errors.New(\"invalid string input\")\n\terrMalformedBooleanValue = errors.New(\"malformed boolean value\")\n\terrEmptyByteSlice = errors.New(\"empty byte slice\")\n\terrInvalidExponentValue = errors.New(\"invalid exponent value\")\n\terrNonDigitCharacters = errors.New(\"non-digit characters found\")\n\terrNumericRangeExceeded = errors.New(\"numeric value exceeds the range limit\")\n\terrMultipleDecimalPoints = errors.New(\"multiple decimal points found\")\n)\n"},{"name":"escape.gno","body":"package json\n\nimport (\n\t\"unicode/utf8\"\n)\n\nconst (\n\tsupplementalPlanesOffset = 0x10000\n\thighSurrogateOffset = 0xD800\n\tlowSurrogateOffset = 0xDC00\n\tsurrogateEnd = 0xDFFF\n\tbasicMultilingualPlaneOffset = 0xFFFF\n\tbadHex = -1\n\n\tsingleUnicodeEscapeLen = 6\n\tsurrogatePairLen = 12\n)\n\nvar hexLookupTable = [256]int{\n\t'0': 0x0, '1': 0x1, '2': 0x2, '3': 0x3, '4': 0x4,\n\t'5': 0x5, '6': 0x6, '7': 0x7, '8': 0x8, '9': 0x9,\n\t'A': 0xA, 'B': 0xB, 'C': 0xC, 'D': 0xD, 'E': 0xE, 'F': 0xF,\n\t'a': 0xA, 'b': 0xB, 'c': 0xC, 'd': 0xD, 'e': 0xE, 'f': 0xF,\n\t// Fill unspecified index-value pairs with key and value of -1\n\t'G': -1, 'H': -1, 'I': -1, 'J': -1,\n\t'K': -1, 'L': -1, 'M': -1, 'N': -1,\n\t'O': -1, 'P': -1, 'Q': -1, 'R': -1,\n\t'S': -1, 'T': -1, 'U': -1, 'V': -1,\n\t'W': -1, 'X': -1, 'Y': -1, 'Z': -1,\n\t'g': -1, 'h': -1, 'i': -1, 'j': -1,\n\t'k': -1, 'l': -1, 'm': -1, 'n': -1,\n\t'o': -1, 'p': -1, 'q': -1, 'r': -1,\n\t's': -1, 't': -1, 'u': -1, 'v': -1,\n\t'w': -1, 'x': -1, 'y': -1, 'z': -1,\n}\n\nfunc h2i(c byte) int {\n\treturn hexLookupTable[c]\n}\n\n// Unescape takes an input byte slice, processes it to Unescape certain characters,\n// and writes the result into an output byte slice.\n//\n// it returns the processed slice and any error encountered during the Unescape operation.\nfunc Unescape(input, output []byte) ([]byte, error) {\n\t// ensure the output slice has enough capacity to hold the input slice.\n\tinputLen := len(input)\n\tif cap(output) \u003c inputLen {\n\t\toutput = make([]byte, inputLen)\n\t}\n\n\tinPos, outPos := 0, 0\n\n\tfor inPos \u003c len(input) {\n\t\tc := input[inPos]\n\t\tif c != backSlash {\n\t\t\toutput[outPos] = c\n\t\t\tinPos++\n\t\t\toutPos++\n\t\t} else {\n\t\t\t// process escape sequence\n\t\t\tinLen, outLen, err := processEscapedUTF8(input[inPos:], output[outPos:])\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tinPos += inLen\n\t\t\toutPos += outLen\n\t\t}\n\t}\n\n\treturn output[:outPos], nil\n}\n\n// isSurrogatePair returns true if the rune is a surrogate pair.\n//\n// A surrogate pairs are used in UTF-16 encoding to encode characters\n// outside the Basic Multilingual Plane (BMP).\nfunc isSurrogatePair(r rune) bool {\n\treturn highSurrogateOffset \u003c= r \u0026\u0026 r \u003c= surrogateEnd\n}\n\n// isHighSurrogate checks if the rune is a high surrogate (U+D800 to U+DBFF).\nfunc isHighSurrogate(r rune) bool {\n\treturn r \u003e= highSurrogateOffset \u0026\u0026 r \u003c= 0xDBFF\n}\n\n// isLowSurrogate checks if the rune is a low surrogate (U+DC00 to U+DFFF).\nfunc isLowSurrogate(r rune) bool {\n\treturn r \u003e= lowSurrogateOffset \u0026\u0026 r \u003c= surrogateEnd\n}\n\n// combineSurrogates reconstruct the original unicode code points in the\n// supplemental plane by combinin the high and low surrogate.\n//\n// The hight surrogate in the range from U+D800 to U+DBFF,\n// and the low surrogate in the range from U+DC00 to U+DFFF.\n//\n// The formula to combine the surrogates is:\n// (high - 0xD800) * 0x400 + (low - 0xDC00) + 0x10000\nfunc combineSurrogates(high, low rune) rune {\n\treturn ((high - highSurrogateOffset) \u003c\u003c 10) + (low - lowSurrogateOffset) + supplementalPlanesOffset\n}\n\n// deocdeSingleUnicodeEscape decodes a unicode escape sequence (e.g., \\uXXXX) into a rune.\nfunc decodeSingleUnicodeEscape(b []byte) (rune, bool) {\n\tif len(b) \u003c 6 {\n\t\treturn utf8.RuneError, false\n\t}\n\n\t// convert hex to decimal\n\th1, h2, h3, h4 := h2i(b[2]), h2i(b[3]), h2i(b[4]), h2i(b[5])\n\tif h1 == badHex || h2 == badHex || h3 == badHex || h4 == badHex {\n\t\treturn utf8.RuneError, false\n\t}\n\n\treturn rune(h1\u003c\u003c12 + h2\u003c\u003c8 + h3\u003c\u003c4 + h4), true\n}\n\n// decodeUnicodeEscape decodes a Unicode escape sequence from a byte slice.\n// It handles both single Unicode escape sequences and surrogate pairs.\nfunc decodeUnicodeEscape(b []byte) (rune, int) {\n\t// decode the first Unicode escape sequence.\n\tr, ok := decodeSingleUnicodeEscape(b)\n\tif !ok {\n\t\treturn utf8.RuneError, -1\n\t}\n\n\t// if the rune is within the BMP and not a surrogate, return it\n\tif r \u003c= basicMultilingualPlaneOffset \u0026\u0026 !isSurrogatePair(r) {\n\t\treturn r, 6\n\t}\n\n\tif !isHighSurrogate(r) {\n\t\t// invalid surrogate pair.\n\t\treturn utf8.RuneError, -1\n\t}\n\n\t// if the rune is a high surrogate, need to decode the next escape sequence.\n\n\t// ensure there are enough bytes for the next escape sequence.\n\tif len(b) \u003c surrogatePairLen {\n\t\treturn utf8.RuneError, -1\n\t}\n\t// decode the second Unicode escape sequence.\n\tr2, ok := decodeSingleUnicodeEscape(b[singleUnicodeEscapeLen:])\n\tif !ok {\n\t\treturn utf8.RuneError, -1\n\t}\n\t// check if the second rune is a low surrogate.\n\tif isLowSurrogate(r2) {\n\t\tcombined := combineSurrogates(r, r2)\n\t\treturn combined, surrogatePairLen\n\t}\n\treturn utf8.RuneError, -1\n}\n\nvar escapeByteSet = [256]byte{\n\t'\"': doubleQuote,\n\t'\\\\': backSlash,\n\t'/': slash,\n\t'b': backSpace,\n\t'f': formFeed,\n\t'n': newLine,\n\t'r': carriageReturn,\n\t't': tab,\n}\n\n// Unquote takes a byte slice and unquotes it by removing\n// the surrounding quotes and unescaping the contents.\nfunc Unquote(s []byte, border byte) (string, bool) {\n\ts, ok := unquoteBytes(s, border)\n\treturn string(s), ok\n}\n\n// unquoteBytes takes a byte slice and unquotes it by removing\nfunc unquoteBytes(s []byte, border byte) ([]byte, bool) {\n\tif len(s) \u003c 2 || s[0] != border || s[len(s)-1] != border {\n\t\treturn nil, false\n\t}\n\n\ts = s[1 : len(s)-1]\n\n\tr := 0\n\tfor r \u003c len(s) {\n\t\tc := s[r]\n\n\t\tif c == backSlash || c == border || c \u003c 0x20 {\n\t\t\tbreak\n\t\t}\n\n\t\tif c \u003c utf8.RuneSelf {\n\t\t\tr++\n\t\t\tcontinue\n\t\t}\n\n\t\trr, size := utf8.DecodeRune(s[r:])\n\t\tif rr == utf8.RuneError \u0026\u0026 size == 1 {\n\t\t\tbreak\n\t\t}\n\n\t\tr += size\n\t}\n\n\tif r == len(s) {\n\t\treturn s, true\n\t}\n\n\tutfDoubleMax := utf8.UTFMax * 2\n\tb := make([]byte, len(s)+utfDoubleMax)\n\tw := copy(b, s[0:r])\n\n\tfor r \u003c len(s) {\n\t\tif w \u003e= len(b)-utf8.UTFMax {\n\t\t\tnb := make([]byte, utfDoubleMax+(2*len(b)))\n\t\t\tcopy(nb, b)\n\t\t\tb = nb\n\t\t}\n\n\t\tc := s[r]\n\t\tif c == backSlash {\n\t\t\tr++\n\t\t\tif r \u003e= len(s) {\n\t\t\t\treturn nil, false\n\t\t\t}\n\n\t\t\tif s[r] == 'u' {\n\t\t\t\trr, res := decodeUnicodeEscape(s[r-1:])\n\t\t\t\tif res \u003c 0 {\n\t\t\t\t\treturn nil, false\n\t\t\t\t}\n\n\t\t\t\tw += utf8.EncodeRune(b[w:], rr)\n\t\t\t\tr += 5\n\t\t\t} else {\n\t\t\t\tdecode := escapeByteSet[s[r]]\n\t\t\t\tif decode == 0 {\n\t\t\t\t\treturn nil, false\n\t\t\t\t}\n\n\t\t\t\tif decode == doubleQuote || decode == backSlash || decode == slash {\n\t\t\t\t\tdecode = s[r]\n\t\t\t\t}\n\n\t\t\t\tb[w] = decode\n\t\t\t\tr++\n\t\t\t\tw++\n\t\t\t}\n\t\t} else if c == border || c \u003c 0x20 {\n\t\t\treturn nil, false\n\t\t} else if c \u003c utf8.RuneSelf {\n\t\t\tb[w] = c\n\t\t\tr++\n\t\t\tw++\n\t\t} else {\n\t\t\trr, size := utf8.DecodeRune(s[r:])\n\n\t\t\tif rr == utf8.RuneError \u0026\u0026 size == 1 {\n\t\t\t\treturn nil, false\n\t\t\t}\n\n\t\t\tr += size\n\t\t\tw += utf8.EncodeRune(b[w:], rr)\n\t\t}\n\t}\n\n\treturn b[:w], true\n}\n\n// processEscapedUTF8 converts escape sequences to UTF-8 characters.\n// It decodes Unicode escape sequences (\\uXXXX) to UTF-8 and\n// converts standard escape sequences (e.g., \\n) to their corresponding special characters.\nfunc processEscapedUTF8(in, out []byte) (int, int, error) {\n\tif len(in) \u003c 2 || in[0] != backSlash {\n\t\treturn -1, -1, errInvalidEscapeSequence\n\t}\n\n\tescapeSeqLen := 2\n\tescapeChar := in[1]\n\n\tif escapeChar != 'u' {\n\t\tval := escapeByteSet[escapeChar]\n\t\tif val == 0 {\n\t\t\treturn -1, -1, errInvalidEscapeSequence\n\t\t}\n\n\t\tout[0] = val\n\t\treturn escapeSeqLen, 1, nil\n\t}\n\n\tr, size := decodeUnicodeEscape(in)\n\tif size == -1 {\n\t\treturn -1, -1, errInvalidEscapeSequence\n\t}\n\n\toutLen := utf8.EncodeRune(out, r)\n\n\treturn size, outLen, nil\n}\n"},{"name":"escape_test.gno","body":"package json\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\t\"unicode/utf8\"\n)\n\nfunc TestHexToInt(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tc byte\n\t\twant int\n\t}{\n\t\t{\"Digit 0\", '0', 0},\n\t\t{\"Digit 9\", '9', 9},\n\t\t{\"Uppercase A\", 'A', 10},\n\t\t{\"Uppercase F\", 'F', 15},\n\t\t{\"Lowercase a\", 'a', 10},\n\t\t{\"Lowercase f\", 'f', 15},\n\t\t{\"Invalid character1\", 'g', badHex},\n\t\t{\"Invalid character2\", 'G', badHex},\n\t\t{\"Invalid character3\", 'z', badHex},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := h2i(tt.c); got != tt.want {\n\t\t\t\tt.Errorf(\"h2i() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestIsSurrogatePair(t *testing.T) {\n\ttestCases := []struct {\n\t\tname string\n\t\tr rune\n\t\texpected bool\n\t}{\n\t\t{\"high surrogate start\", 0xD800, true},\n\t\t{\"high surrogate end\", 0xDBFF, true},\n\t\t{\"low surrogate start\", 0xDC00, true},\n\t\t{\"low surrogate end\", 0xDFFF, true},\n\t\t{\"Non-surrogate\", 0x0000, false},\n\t\t{\"Non-surrogate 2\", 0xE000, false},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif got := isSurrogatePair(tc.r); got != tc.expected {\n\t\t\t\tt.Errorf(\"isSurrogate() = %v, want %v\", got, tc.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCombineSurrogates(t *testing.T) {\n\ttestCases := []struct {\n\t\thigh, low rune\n\t\texpected rune\n\t}{\n\t\t{0xD83D, 0xDC36, 0x1F436}, // 🐶 U+1F436 DOG FACE\n\t\t{0xD83D, 0xDE00, 0x1F600}, // 😀 U+1F600 GRINNING FACE\n\t\t{0xD83C, 0xDF03, 0x1F303}, // 🌃 U+1F303 NIGHT WITH STARS\n\t}\n\n\tfor _, tc := range testCases {\n\t\tresult := combineSurrogates(tc.high, tc.low)\n\t\tif result != tc.expected {\n\t\t\tt.Errorf(\"combineSurrogates(%U, %U) = %U; want %U\", tc.high, tc.low, result, tc.expected)\n\t\t}\n\t}\n}\n\nfunc TestDecodeSingleUnicodeEscape(t *testing.T) {\n\ttestCases := []struct {\n\t\tinput []byte\n\t\texpected rune\n\t\tisValid bool\n\t}{\n\t\t// valid unicode escape sequences\n\t\t{[]byte(`\\u0041`), 'A', true},\n\t\t{[]byte(`\\u03B1`), 'α', true},\n\t\t{[]byte(`\\u00E9`), 'é', true}, // valid non-English character\n\t\t{[]byte(`\\u0021`), '!', true}, // valid special character\n\t\t{[]byte(`\\uFF11`), '1', true},\n\t\t{[]byte(`\\uD83D`), 0xD83D, true},\n\t\t{[]byte(`\\uDE03`), 0xDE03, true},\n\n\t\t// invalid unicode escape sequences\n\t\t{[]byte(`\\u004`), utf8.RuneError, false}, // too short\n\t\t{[]byte(`\\uXYZW`), utf8.RuneError, false}, // invalid hex\n\t\t{[]byte(`\\u00G1`), utf8.RuneError, false}, // non-hex character\n\t}\n\n\tfor _, tc := range testCases {\n\t\tresult, isValid := decodeSingleUnicodeEscape(tc.input)\n\t\tif result != tc.expected || isValid != tc.isValid {\n\t\t\tt.Errorf(\"decodeSingleUnicodeEscape(%s) = (%U, %v); want (%U, %v)\", tc.input, result, isValid, tc.expected, tc.isValid)\n\t\t}\n\t}\n}\n\nfunc TestDecodeUnicodeEscape(t *testing.T) {\n\ttests := []struct {\n\t\tinput []byte\n\t\texpected rune\n\t\tsize int\n\t}{\n\t\t{[]byte(`\\u0041`), 'A', 6},\n\t\t{[]byte(`\\uD83D\\uDE00`), 0x1F600, 12}, // 😀\n\t\t{[]byte(`\\uD834\\uDD1E`), 0x1D11E, 12}, // 𝄞\n\t\t{[]byte(`\\uFFFF`), '\\uFFFF', 6},\n\t\t{[]byte(`\\uXYZW`), utf8.RuneError, -1},\n\t\t{[]byte(`\\uD800`), utf8.RuneError, -1}, // single high surrogate\n\t\t{[]byte(`\\uDC00`), utf8.RuneError, -1}, // single low surrogate\n\t\t{[]byte(`\\uD800\\uDC00`), 0x10000, 12}, // First code point above U+FFFF\n\t\t{[]byte(`\\uDBFF\\uDFFF`), 0x10FFFF, 12}, // Maximum code point\n\t\t{[]byte(`\\uD83D\\u0041`), utf8.RuneError, -1}, // invalid surrogate pair\n\t}\n\n\tfor _, tc := range tests {\n\t\tr, size := decodeUnicodeEscape(tc.input)\n\t\tif r != tc.expected || size != tc.size {\n\t\t\tt.Errorf(\"decodeUnicodeEscape(%q) = (%U, %d); want (%U, %d)\", tc.input, r, size, tc.expected, tc.size)\n\t\t}\n\t}\n}\n\nfunc TestUnescapeToUTF8(t *testing.T) {\n\ttests := []struct {\n\t\tinput []byte\n\t\texpectedIn int\n\t\texpectedOut int\n\t\tisError bool\n\t}{\n\t\t// valid escape sequences\n\t\t{[]byte(`\\n`), 2, 1, false},\n\t\t{[]byte(`\\t`), 2, 1, false},\n\t\t{[]byte(`\\u0041`), 6, 1, false},\n\t\t{[]byte(`\\u03B1`), 6, 2, false},\n\t\t{[]byte(`\\uD830\\uDE03`), 12, 4, false},\n\n\t\t// invalid escape sequences\n\t\t{[]byte(`\\`), -1, -1, true}, // incomplete escape sequence\n\t\t{[]byte(`\\x`), -1, -1, true}, // invalid escape character\n\t\t{[]byte(`\\u`), -1, -1, true}, // incomplete unicode escape sequence\n\t\t{[]byte(`\\u004`), -1, -1, true}, // invalid unicode escape sequence\n\t\t{[]byte(`\\uXYZW`), -1, -1, true}, // invalid unicode escape sequence\n\t\t{[]byte(`\\uD83D\\u0041`), -1, -1, true}, // invalid unicode escape sequence\n\t}\n\n\tfor _, tc := range tests {\n\t\tinput := make([]byte, len(tc.input))\n\t\tcopy(input, tc.input)\n\t\toutput := make([]byte, utf8.UTFMax)\n\t\tinLen, outLen, err := processEscapedUTF8(input, output)\n\t\tif (err != nil) != tc.isError {\n\t\t\tt.Errorf(\"processEscapedUTF8(%q) = %v; want %v\", tc.input, err, tc.isError)\n\t\t}\n\n\t\tif inLen != tc.expectedIn || outLen != tc.expectedOut {\n\t\t\tt.Errorf(\"processEscapedUTF8(%q) = (%d, %d); want (%d, %d)\", tc.input, inLen, outLen, tc.expectedIn, tc.expectedOut)\n\t\t}\n\t}\n}\n\nfunc TestUnescape(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput []byte\n\t\texpected []byte\n\t\tisError bool\n\t}{\n\t\t{\"NoEscape\", []byte(\"hello world\"), []byte(\"hello world\"), false},\n\t\t{\"SingleEscape\", []byte(\"hello\\\\nworld\"), []byte(\"hello\\nworld\"), false},\n\t\t{\"MultipleEscapes\", []byte(\"line1\\\\nline2\\\\r\\\\nline3\"), []byte(\"line1\\nline2\\r\\nline3\"), false},\n\t\t{\"UnicodeEscape\", []byte(\"snowman:\\\\u2603\"), []byte(\"snowman:\\u2603\"), false},\n\t\t{\"SurrogatePair\", []byte(\"emoji:\\\\uD83D\\\\uDE00\"), []byte(\"emoji:😀\"), false},\n\t\t{\"InvalidEscape\", []byte(\"hello\\\\xworld\"), nil, true},\n\t\t{\"IncompleteUnicode\", []byte(\"incomplete:\\\\u123\"), nil, true},\n\t\t{\"InvalidSurrogatePair\", []byte(\"invalid:\\\\uD83D\\\\u0041\"), nil, true},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\toutput := make([]byte, len(tc.input)*2) // Allocate extra space for possible expansion\n\t\t\tresult, err := Unescape(tc.input, output)\n\t\t\tif (err != nil) != tc.isError {\n\t\t\t\tt.Errorf(\"Unescape(%q) error = %v; want error = %v\", tc.input, err, tc.isError)\n\t\t\t}\n\n\t\t\tif !tc.isError \u0026\u0026 !bytes.Equal(result, tc.expected) {\n\t\t\t\tt.Errorf(\"Unescape(%q) = %q; want %q\", tc.input, result, tc.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUnquoteBytes(t *testing.T) {\n\ttests := []struct {\n\t\tinput []byte\n\t\tborder byte\n\t\texpected []byte\n\t\tok bool\n\t}{\n\t\t{[]byte(\"\\\"hello\\\"\"), '\"', []byte(\"hello\"), true},\n\t\t{[]byte(\"'hello'\"), '\\'', []byte(\"hello\"), true},\n\t\t{[]byte(\"\\\"hello\"), '\"', nil, false},\n\t\t{[]byte(\"hello\\\"\"), '\"', nil, false},\n\t\t{[]byte(\"\\\"he\\\\\\\"llo\\\"\"), '\"', []byte(\"he\\\"llo\"), true},\n\t\t{[]byte(\"\\\"he\\\\nllo\\\"\"), '\"', []byte(\"he\\nllo\"), true},\n\t\t{[]byte(\"\\\"\\\"\"), '\"', []byte(\"\"), true},\n\t\t{[]byte(\"''\"), '\\'', []byte(\"\"), true},\n\t\t{[]byte(\"\\\"\\\\u0041\\\"\"), '\"', []byte(\"A\"), true},\n\t\t{[]byte(`\"Hello, 世界\"`), '\"', []byte(\"Hello, 世界\"), true},\n\t\t{[]byte(`\"Hello, \\x80\"`), '\"', nil, false},\n\t\t{[]byte(`\"invalid surrogate: \\uD83D\\u0041\"`), '\"', nil, false},\n\t}\n\n\tfor _, tc := range tests {\n\t\tresult, pass := unquoteBytes(tc.input, tc.border)\n\n\t\tif pass != tc.ok {\n\t\t\tt.Errorf(\"unquoteBytes(%q) = %v; want %v\", tc.input, pass, tc.ok)\n\t\t}\n\n\t\tif !bytes.Equal(result, tc.expected) {\n\t\t\tt.Errorf(\"unquoteBytes(%q) = %q; want %q\", tc.input, result, tc.expected)\n\t\t}\n\t}\n}\n"},{"name":"indent.gno","body":"package json\n\nimport (\n\t\"bytes\"\n\t\"strings\"\n)\n\n// indentGrowthFactor specifies the growth factor of indenting JSON input.\n// A factor no higher than 2 ensures that wasted space never exceeds 50%.\nconst indentGrowthFactor = 2\n\n// IndentJSON formats the JSON data with the specified indentation.\nfunc Indent(data []byte, indent string) ([]byte, error) {\n\tvar (\n\t\tout bytes.Buffer\n\t\tlevel int\n\t\tinArray bool\n\t\tarrayDepth int\n\t)\n\n\tfor i := 0; i \u003c len(data); i++ {\n\t\tc := data[i] // current character\n\n\t\tswitch c {\n\t\tcase bracketOpen:\n\t\t\tarrayDepth++\n\t\t\tif arrayDepth \u003e 1 {\n\t\t\t\tlevel++ // increase the level if it's nested array\n\t\t\t\tinArray = true\n\n\t\t\t\tif err := out.WriteByte(c); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\t\tif err := writeNewlineAndIndent(\u0026out, level, indent); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// case of the top-level array\n\t\t\t\tinArray = true\n\t\t\t\tif err := out.WriteByte(c); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\n\t\tcase bracketClose:\n\t\t\tif inArray \u0026\u0026 arrayDepth \u003e 1 { // nested array\n\t\t\t\tlevel--\n\t\t\t\tif err := writeNewlineAndIndent(\u0026out, level, indent); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tarrayDepth--\n\t\t\tif arrayDepth == 0 {\n\t\t\t\tinArray = false\n\t\t\t}\n\n\t\t\tif err := out.WriteByte(c); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\tcase curlyOpen:\n\t\t\t// check if the empty object or array\n\t\t\t// we don't need to apply the indent when it's empty containers.\n\t\t\tif i+1 \u003c len(data) \u0026\u0026 data[i+1] == curlyClose {\n\t\t\t\tif err := out.WriteByte(c); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\t\ti++ // skip next character\n\t\t\t\tif err := out.WriteByte(data[i]); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif err := out.WriteByte(c); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\t\tlevel++\n\t\t\t\tif err := writeNewlineAndIndent(\u0026out, level, indent); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\n\t\tcase curlyClose:\n\t\t\tlevel--\n\t\t\tif err := writeNewlineAndIndent(\u0026out, level, indent); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif err := out.WriteByte(c); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\tcase comma, colon:\n\t\t\tif err := out.WriteByte(c); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif inArray \u0026\u0026 arrayDepth \u003e 1 { // nested array\n\t\t\t\tif err := writeNewlineAndIndent(\u0026out, level, indent); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t} else if c == colon {\n\t\t\t\tif err := out.WriteByte(' '); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\n\t\tdefault:\n\t\t\tif err := out.WriteByte(c); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn out.Bytes(), nil\n}\n\nfunc writeNewlineAndIndent(out *bytes.Buffer, level int, indent string) error {\n\tif err := out.WriteByte('\\n'); err != nil {\n\t\treturn err\n\t}\n\n\tidt := strings.Repeat(indent, level*indentGrowthFactor)\n\tif _, err := out.WriteString(idt); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"},{"name":"indent_test.gno","body":"package json\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n)\n\nfunc TestIndentJSON(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinput []byte\n\t\tindent string\n\t\texpected []byte\n\t}{\n\t\t{\n\t\t\tname: \"empty object\",\n\t\t\tinput: []byte(`{}`),\n\t\t\tindent: \" \",\n\t\t\texpected: []byte(`{}`),\n\t\t},\n\t\t{\n\t\t\tname: \"empty array\",\n\t\t\tinput: []byte(`[]`),\n\t\t\tindent: \" \",\n\t\t\texpected: []byte(`[]`),\n\t\t},\n\t\t{\n\t\t\tname: \"nested object\",\n\t\t\tinput: []byte(`{{}}`),\n\t\t\tindent: \"\\t\",\n\t\t\texpected: []byte(\"{\\n\\t\\t{}\\n}\"),\n\t\t},\n\t\t{\n\t\t\tname: \"nested array\",\n\t\t\tinput: []byte(`[[[]]]`),\n\t\t\tindent: \"\\t\",\n\t\t\texpected: []byte(\"[[\\n\\t\\t[\\n\\t\\t\\t\\t\\n\\t\\t]\\n]]\"),\n\t\t},\n\t\t{\n\t\t\tname: \"top-level array\",\n\t\t\tinput: []byte(`[\"apple\",\"banana\",\"cherry\"]`),\n\t\t\tindent: \"\\t\",\n\t\t\texpected: []byte(`[\"apple\",\"banana\",\"cherry\"]`),\n\t\t},\n\t\t{\n\t\t\tname: \"array of arrays\",\n\t\t\tinput: []byte(`[\"apple\",[\"banana\",\"cherry\"],\"date\"]`),\n\t\t\tindent: \" \",\n\t\t\texpected: []byte(\"[\\\"apple\\\",[\\n \\\"banana\\\",\\n \\\"cherry\\\"\\n],\\\"date\\\"]\"),\n\t\t},\n\n\t\t{\n\t\t\tname: \"nested array in object\",\n\t\t\tinput: []byte(`{\"fruits\":[\"apple\",[\"banana\",\"cherry\"],\"date\"]}`),\n\t\t\tindent: \" \",\n\t\t\texpected: []byte(\"{\\n \\\"fruits\\\": [\\\"apple\\\",[\\n \\\"banana\\\",\\n \\\"cherry\\\"\\n ],\\\"date\\\"]\\n}\"),\n\t\t},\n\t\t{\n\t\t\tname: \"complex nested structure\",\n\t\t\tinput: []byte(`{\"data\":{\"array\":[1,2,3],\"bool\":true,\"nestedArray\":[[\"a\",\"b\"],\"c\"]}}`),\n\t\t\tindent: \" \",\n\t\t\texpected: []byte(\"{\\n \\\"data\\\": {\\n \\\"array\\\": [1,2,3],\\\"bool\\\": true,\\\"nestedArray\\\": [[\\n \\\"a\\\",\\n \\\"b\\\"\\n ],\\\"c\\\"]\\n }\\n}\"),\n\t\t},\n\t\t{\n\t\t\tname: \"custom ident character\",\n\t\t\tinput: []byte(`{\"fruits\":[\"apple\",[\"banana\",\"cherry\"],\"date\"]}`),\n\t\t\tindent: \"*\",\n\t\t\texpected: []byte(\"{\\n**\\\"fruits\\\": [\\\"apple\\\",[\\n****\\\"banana\\\",\\n****\\\"cherry\\\"\\n**],\\\"date\\\"]\\n}\"),\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tactual, err := Indent(tt.input, tt.indent)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"IndentJSON() error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !bytes.Equal(actual, tt.expected) {\n\t\t\t\tt.Errorf(\"IndentJSON() = %q, want %q\", actual, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n"},{"name":"internal.gno","body":"package json\n\n// Reference: https://github.com/freddierice/php_source/blob/467ed5d6edff72219afd3e644516f131118ef48e/ext/json/JSON_parser.c\n// Copyright (c) 2005 JSON.org\n\n// Go implementation is taken from: https://github.com/spyzhov/ajson/blob/master/internal/state.go\n\ntype (\n\tStates int8 // possible states of the parser\n\tClasses int8 // JSON string character types\n)\n\nconst __ = -1\n\n// enum classes\nconst (\n\tC_SPACE Classes = iota /* space */\n\tC_WHITE /* other whitespace */\n\tC_LCURB /* { */\n\tC_RCURB /* } */\n\tC_LSQRB /* [ */\n\tC_RSQRB /* ] */\n\tC_COLON /* : */\n\tC_COMMA /* , */\n\tC_QUOTE /* \" */\n\tC_BACKS /* \\ */\n\tC_SLASH /* / */\n\tC_PLUS /* + */\n\tC_MINUS /* - */\n\tC_POINT /* . */\n\tC_ZERO /* 0 */\n\tC_DIGIT /* 123456789 */\n\tC_LOW_A /* a */\n\tC_LOW_B /* b */\n\tC_LOW_C /* c */\n\tC_LOW_D /* d */\n\tC_LOW_E /* e */\n\tC_LOW_F /* f */\n\tC_LOW_L /* l */\n\tC_LOW_N /* n */\n\tC_LOW_R /* r */\n\tC_LOW_S /* s */\n\tC_LOW_T /* t */\n\tC_LOW_U /* u */\n\tC_ABCDF /* ABCDF */\n\tC_E /* E */\n\tC_ETC /* everything else */\n)\n\n// AsciiClasses array maps the 128 ASCII characters into character classes.\nvar AsciiClasses = [128]Classes{\n\t/*\n\t This array maps the 128 ASCII characters into character classes.\n\t The remaining Unicode characters should be mapped to C_ETC.\n\t Non-whitespace control characters are errors.\n\t*/\n\t__, __, __, __, __, __, __, __,\n\t__, C_WHITE, C_WHITE, __, __, C_WHITE, __, __,\n\t__, __, __, __, __, __, __, __,\n\t__, __, __, __, __, __, __, __,\n\n\tC_SPACE, C_ETC, C_QUOTE, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_PLUS, C_COMMA, C_MINUS, C_POINT, C_SLASH,\n\tC_ZERO, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT,\n\tC_DIGIT, C_DIGIT, C_COLON, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC,\n\n\tC_ETC, C_ABCDF, C_ABCDF, C_ABCDF, C_ABCDF, C_E, C_ABCDF, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_LSQRB, C_BACKS, C_RSQRB, C_ETC, C_ETC,\n\n\tC_ETC, C_LOW_A, C_LOW_B, C_LOW_C, C_LOW_D, C_LOW_E, C_LOW_F, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_ETC, C_LOW_L, C_ETC, C_LOW_N, C_ETC,\n\tC_ETC, C_ETC, C_LOW_R, C_LOW_S, C_LOW_T, C_LOW_U, C_ETC, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_LCURB, C_ETC, C_RCURB, C_ETC, C_ETC,\n}\n\n// QuoteAsciiClasses is a HACK for single quote from AsciiClasses\nvar QuoteAsciiClasses = [128]Classes{\n\t/*\n\t This array maps the 128 ASCII characters into character classes.\n\t The remaining Unicode characters should be mapped to C_ETC.\n\t Non-whitespace control characters are errors.\n\t*/\n\t__, __, __, __, __, __, __, __,\n\t__, C_WHITE, C_WHITE, __, __, C_WHITE, __, __,\n\t__, __, __, __, __, __, __, __,\n\t__, __, __, __, __, __, __, __,\n\n\tC_SPACE, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_QUOTE,\n\tC_ETC, C_ETC, C_ETC, C_PLUS, C_COMMA, C_MINUS, C_POINT, C_SLASH,\n\tC_ZERO, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT,\n\tC_DIGIT, C_DIGIT, C_COLON, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC,\n\n\tC_ETC, C_ABCDF, C_ABCDF, C_ABCDF, C_ABCDF, C_E, C_ABCDF, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_LSQRB, C_BACKS, C_RSQRB, C_ETC, C_ETC,\n\n\tC_ETC, C_LOW_A, C_LOW_B, C_LOW_C, C_LOW_D, C_LOW_E, C_LOW_F, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_ETC, C_LOW_L, C_ETC, C_LOW_N, C_ETC,\n\tC_ETC, C_ETC, C_LOW_R, C_LOW_S, C_LOW_T, C_LOW_U, C_ETC, C_ETC,\n\tC_ETC, C_ETC, C_ETC, C_LCURB, C_ETC, C_RCURB, C_ETC, C_ETC,\n}\n\n/*\nThe state codes.\n*/\nconst (\n\tGO States = iota /* start */\n\tOK /* ok */\n\tOB /* object */\n\tKE /* key */\n\tCO /* colon */\n\tVA /* value */\n\tAR /* array */\n\tST /* string */\n\tES /* escape */\n\tU1 /* u1 */\n\tU2 /* u2 */\n\tU3 /* u3 */\n\tU4 /* u4 */\n\tMI /* minus */\n\tZE /* zero */\n\tIN /* integer */\n\tDT /* dot */\n\tFR /* fraction */\n\tE1 /* e */\n\tE2 /* ex */\n\tE3 /* exp */\n\tT1 /* tr */\n\tT2 /* tru */\n\tT3 /* true */\n\tF1 /* fa */\n\tF2 /* fal */\n\tF3 /* fals */\n\tF4 /* false */\n\tN1 /* nu */\n\tN2 /* nul */\n\tN3 /* null */\n)\n\n// List of action codes.\n// these constants are defining an action that should be performed under certain conditions.\nconst (\n\tcl States = -2 /* colon */\n\tcm States = -3 /* comma */\n\tqt States = -4 /* quote */\n\tbo States = -5 /* bracket open */\n\tco States = -6 /* curly bracket open */\n\tbc States = -7 /* bracket close */\n\tcc States = -8 /* curly bracket close */\n\tec States = -9 /* curly bracket empty */\n)\n\n// StateTransitionTable is the state transition table takes the current state and the current symbol, and returns either\n// a new state or an action. An action is represented as a negative number. A JSON text is accepted if at the end of the\n// text the state is OK and if the mode is DONE.\nvar StateTransitionTable = [31][31]States{\n\t/*\n\t The state transition table takes the current state and the current symbol,\n\t and returns either a new state or an action. An action is represented as a\n\t negative number. A JSON text is accepted if at the end of the text the\n\t state is OK and if the mode is DONE.\n\t white 1-9 ABCDF etc\n\t space | { } [ ] : , \" \\ / + - . 0 | a b c d e f l n r s t u | E |*/\n\t/*start GO*/ {GO, GO, co, __, bo, __, __, __, ST, __, __, __, MI, __, ZE, IN, __, __, __, __, __, F1, __, N1, __, __, T1, __, __, __, __},\n\t/*ok OK*/ {OK, OK, __, cc, __, bc, __, cm, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},\n\t/*object OB*/ {OB, OB, __, ec, __, __, __, __, ST, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},\n\t/*key KE*/ {KE, KE, __, __, __, __, __, __, ST, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},\n\t/*colon CO*/ {CO, CO, __, __, __, __, cl, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},\n\t/*value VA*/ {VA, VA, co, __, bo, __, __, __, ST, __, __, __, MI, __, ZE, IN, __, __, __, __, __, F1, __, N1, __, __, T1, __, __, __, __},\n\t/*array AR*/ {AR, AR, co, __, bo, bc, __, __, ST, __, __, __, MI, __, ZE, IN, __, __, __, __, __, F1, __, N1, __, __, T1, __, __, __, __},\n\t/*string ST*/ {ST, __, ST, ST, ST, ST, ST, ST, qt, ES, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST},\n\t/*escape ES*/ {__, __, __, __, __, __, __, __, ST, ST, ST, __, __, __, __, __, __, ST, __, __, __, ST, __, ST, ST, __, ST, U1, __, __, __},\n\t/*u1 U1*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, U2, U2, U2, U2, U2, U2, U2, U2, __, __, __, __, __, __, U2, U2, __},\n\t/*u2 U2*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, U3, U3, U3, U3, U3, U3, U3, U3, __, __, __, __, __, __, U3, U3, __},\n\t/*u3 U3*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, U4, U4, U4, U4, U4, U4, U4, U4, __, __, __, __, __, __, U4, U4, __},\n\t/*u4 U4*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, ST, ST, ST, ST, ST, ST, ST, ST, __, __, __, __, __, __, ST, ST, __},\n\t/*minus MI*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, ZE, IN, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},\n\t/*zero ZE*/ {OK, OK, __, cc, __, bc, __, cm, __, __, __, __, __, DT, __, __, __, __, __, __, E1, __, __, __, __, __, __, __, __, E1, __},\n\t/*int IN*/ {OK, OK, __, cc, __, bc, __, cm, __, __, __, __, __, DT, IN, IN, __, __, __, __, E1, __, __, __, __, __, __, __, __, E1, __},\n\t/*dot DT*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, FR, FR, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},\n\t/*frac FR*/ {OK, OK, __, cc, __, bc, __, cm, __, __, __, __, __, __, FR, FR, __, __, __, __, E1, __, __, __, __, __, __, __, __, E1, __},\n\t/*e E1*/ {__, __, __, __, __, __, __, __, __, __, __, E2, E2, __, E3, E3, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},\n\t/*ex E2*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, E3, E3, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},\n\t/*exp E3*/ {OK, OK, __, cc, __, bc, __, cm, __, __, __, __, __, __, E3, E3, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},\n\t/*tr T1*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, T2, __, __, __, __, __, __},\n\t/*tru T2*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, T3, __, __, __},\n\t/*true T3*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, OK, __, __, __, __, __, __, __, __, __, __},\n\t/*fa F1*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, F2, __, __, __, __, __, __, __, __, __, __, __, __, __, __},\n\t/*fal F2*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, F3, __, __, __, __, __, __, __, __},\n\t/*fals F3*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, F4, __, __, __, __, __},\n\t/*false F4*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, OK, __, __, __, __, __, __, __, __, __, __},\n\t/*nu N1*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, N2, __, __, __},\n\t/*nul N2*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, N3, __, __, __, __, __, __, __, __},\n\t/*null N3*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, OK, __, __, __, __, __, __, __, __},\n}\n"},{"name":"node.gno","body":"package json\n\nimport (\n\t\"errors\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Node represents a JSON node.\ntype Node struct {\n\tprev *Node // prev is the parent node of the current node.\n\tnext map[string]*Node // next is the child nodes of the current node.\n\tkey *string // key holds the key of the current node in the parent node.\n\tdata []byte // byte slice of JSON data\n\tvalue interface{} // value holds the value of the current node.\n\tnodeType ValueType // NodeType holds the type of the current node. (Object, Array, String, Number, Boolean, Null)\n\tindex *int // index holds the index of the current node in the parent array node.\n\tborders [2]int // borders stores the start and end index of the current node in the data.\n\tmodified bool // modified indicates the current node is changed or not.\n}\n\n// NewNode creates a new node instance with the given parent node, buffer, type, and key.\nfunc NewNode(prev *Node, b *buffer, typ ValueType, key **string) (*Node, error) {\n\tcurr := \u0026Node{\n\t\tprev: prev,\n\t\tdata: b.data,\n\t\tborders: [2]int{b.index, 0},\n\t\tkey: *key,\n\t\tnodeType: typ,\n\t\tmodified: false,\n\t}\n\n\tif typ == Object || typ == Array {\n\t\tcurr.next = make(map[string]*Node)\n\t}\n\n\tif prev != nil {\n\t\tif prev.IsArray() {\n\t\t\tsize := len(prev.next)\n\t\t\tcurr.index = \u0026size\n\n\t\t\tprev.next[strconv.Itoa(size)] = curr\n\t\t} else if prev.IsObject() {\n\t\t\tif key == nil {\n\t\t\t\treturn nil, errKeyRequired\n\t\t\t}\n\n\t\t\tprev.next[**key] = curr\n\t\t} else {\n\t\t\treturn nil, errors.New(\"invalid parent type\")\n\t\t}\n\t}\n\n\treturn curr, nil\n}\n\n// load retrieves the value of the current node.\nfunc (n *Node) load() interface{} {\n\treturn n.value\n}\n\n// Changed checks the current node is changed or not.\nfunc (n *Node) Changed() bool {\n\treturn n.modified\n}\n\n// Key returns the key of the current node.\nfunc (n *Node) Key() string {\n\tif n == nil || n.key == nil {\n\t\treturn \"\"\n\t}\n\n\treturn *n.key\n}\n\n// HasKey checks the current node has the given key or not.\nfunc (n *Node) HasKey(key string) bool {\n\tif n == nil {\n\t\treturn false\n\t}\n\n\t_, ok := n.next[key]\n\treturn ok\n}\n\n// GetKey returns the value of the given key from the current object node.\nfunc (n *Node) GetKey(key string) (*Node, error) {\n\tif n == nil {\n\t\treturn nil, errNilNode\n\t}\n\n\tif n.Type() != Object {\n\t\treturn nil, ufmt.Errorf(\"target node is not object type. got: %s\", n.Type().String())\n\t}\n\n\tvalue, ok := n.next[key]\n\tif !ok {\n\t\treturn nil, ufmt.Errorf(\"key not found: %s\", key)\n\t}\n\n\treturn value, nil\n}\n\n// MustKey returns the value of the given key from the current object node.\nfunc (n *Node) MustKey(key string) *Node {\n\tval, err := n.GetKey(key)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn val\n}\n\n// UniqueKeyLists traverses the current JSON nodes and collects all the unique keys.\nfunc (n *Node) UniqueKeyLists() []string {\n\tvar collectKeys func(*Node) []string\n\tcollectKeys = func(node *Node) []string {\n\t\tif node == nil || !node.IsObject() {\n\t\t\treturn nil\n\t\t}\n\n\t\tresult := make(map[string]bool)\n\t\tfor key, childNode := range node.next {\n\t\t\tresult[key] = true\n\t\t\tchildKeys := collectKeys(childNode)\n\t\t\tfor _, childKey := range childKeys {\n\t\t\t\tresult[childKey] = true\n\t\t\t}\n\t\t}\n\n\t\tkeys := make([]string, 0, len(result))\n\t\tfor key := range result {\n\t\t\tkeys = append(keys, key)\n\t\t}\n\t\treturn keys\n\t}\n\n\treturn collectKeys(n)\n}\n\n// Empty returns true if the current node is empty.\nfunc (n *Node) Empty() bool {\n\tif n == nil {\n\t\treturn false\n\t}\n\n\treturn len(n.next) == 0\n}\n\n// Type returns the type (ValueType) of the current node.\nfunc (n *Node) Type() ValueType {\n\treturn n.nodeType\n}\n\n// Value returns the value of the current node.\n//\n// Usage:\n//\n//\troot := Unmarshal([]byte(`{\"key\": \"value\"}`))\n//\tval, err := root.MustKey(\"key\").Value()\n//\tif err != nil {\n//\t\tt.Errorf(\"Value returns error: %v\", err)\n//\t}\n//\n//\tresult: \"value\"\nfunc (n *Node) Value() (value interface{}, err error) {\n\tvalue = n.load()\n\n\tif value == nil {\n\t\tswitch n.nodeType {\n\t\tcase Null:\n\t\t\treturn nil, nil\n\n\t\tcase Number:\n\t\t\tvalue, err = strconv.ParseFloat(string(n.source()), 64)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tn.value = value\n\n\t\tcase String:\n\t\t\tvar ok bool\n\t\t\tvalue, ok = Unquote(n.source(), doubleQuote)\n\t\t\tif !ok {\n\t\t\t\treturn \"\", errInvalidStringValue\n\t\t\t}\n\n\t\t\tn.value = value\n\n\t\tcase Boolean:\n\t\t\tif len(n.source()) == 0 {\n\t\t\t\treturn nil, errEmptyBooleanNode\n\t\t\t}\n\n\t\t\tb := n.source()[0]\n\t\t\tvalue = b == 't' || b == 'T'\n\t\t\tn.value = value\n\n\t\tcase Array:\n\t\t\telems := make([]*Node, len(n.next))\n\n\t\t\tfor _, e := range n.next {\n\t\t\t\telems[*e.index] = e\n\t\t\t}\n\n\t\t\tvalue = elems\n\t\t\tn.value = value\n\n\t\tcase Object:\n\t\t\tobj := make(map[string]*Node, len(n.next))\n\n\t\t\tfor k, v := range n.next {\n\t\t\t\tobj[k] = v\n\t\t\t}\n\n\t\t\tvalue = obj\n\t\t\tn.value = value\n\t\t}\n\t}\n\n\treturn value, nil\n}\n\n// Delete removes the current node from the parent node.\n//\n// Usage:\n//\n//\troot := Unmarshal([]byte(`{\"key\": \"value\"}`))\n//\tif err := root.MustKey(\"key\").Delete(); err != nil {\n//\t\tt.Errorf(\"Delete returns error: %v\", err)\n//\t}\n//\n//\tresult: {} (empty object)\nfunc (n *Node) Delete() error {\n\tif n == nil {\n\t\treturn errors.New(\"can't delete nil node\")\n\t}\n\n\tif n.prev == nil {\n\t\treturn nil\n\t}\n\n\treturn n.prev.remove(n)\n}\n\n// Size returns the size (length) of the current array node.\n//\n// Usage:\n//\n//\troot := ArrayNode(\"\", []*Node{StringNode(\"\", \"foo\"), NumberNode(\"\", 1)})\n//\tif root == nil {\n//\t\tt.Errorf(\"ArrayNode returns nil\")\n//\t}\n//\n//\tif root.Size() != 2 {\n//\t\tt.Errorf(\"ArrayNode returns wrong size: %d\", root.Size())\n//\t}\nfunc (n *Node) Size() int {\n\tif n == nil {\n\t\treturn 0\n\t}\n\n\treturn len(n.next)\n}\n\n// Index returns the index of the current node in the parent array node.\n//\n// Usage:\n//\n//\troot := ArrayNode(\"\", []*Node{StringNode(\"\", \"foo\"), NumberNode(\"\", 1)})\n//\tif root == nil {\n//\t\tt.Errorf(\"ArrayNode returns nil\")\n//\t}\n//\n//\tif root.MustIndex(1).Index() != 1 {\n//\t\tt.Errorf(\"Index returns wrong index: %d\", root.MustIndex(1).Index())\n//\t}\n//\n// We can also use the index to the byte slice of the JSON data directly.\n//\n// Example:\n//\n//\troot := Unmarshal([]byte(`[\"foo\", 1]`))\n//\tif root == nil {\n//\t\tt.Errorf(\"Unmarshal returns nil\")\n//\t}\n//\n//\tif string(root.MustIndex(1).source()) != \"1\" {\n//\t\tt.Errorf(\"source returns wrong result: %s\", root.MustIndex(1).source())\n//\t}\nfunc (n *Node) Index() int {\n\tif n == nil || n.index == nil {\n\t\treturn -1\n\t}\n\n\treturn *n.index\n}\n\n// MustIndex returns the array element at the given index.\n//\n// If the index is negative, it returns the index is from the end of the array.\n// Also, it panics if the index is not found.\n//\n// check the Index method for detailed usage.\nfunc (n *Node) MustIndex(expectIdx int) *Node {\n\tval, err := n.GetIndex(expectIdx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn val\n}\n\n// GetIndex returns the array element at the given index.\n//\n// if the index is negative, it returns the index is from the end of the array.\nfunc (n *Node) GetIndex(idx int) (*Node, error) {\n\tif n == nil {\n\t\treturn nil, errNilNode\n\t}\n\n\tif !n.IsArray() {\n\t\treturn nil, errNotArrayNode\n\t}\n\n\tif idx \u003e n.Size() {\n\t\treturn nil, errors.New(\"input index exceeds the array size\")\n\t}\n\n\tif idx \u003c 0 {\n\t\tidx += len(n.next)\n\t}\n\n\tchild, ok := n.next[strconv.Itoa(idx)]\n\tif !ok {\n\t\treturn nil, errIndexNotFound\n\t}\n\n\treturn child, nil\n}\n\n// DeleteIndex removes the array element at the given index.\nfunc (n *Node) DeleteIndex(idx int) error {\n\tnode, err := n.GetIndex(idx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn n.remove(node)\n}\n\n// NullNode creates a new null type node.\n//\n// Usage:\n//\n//\t_ := NullNode(\"\")\nfunc NullNode(key string) *Node {\n\treturn \u0026Node{\n\t\tkey: \u0026key,\n\t\tvalue: nil,\n\t\tnodeType: Null,\n\t\tmodified: true,\n\t}\n}\n\n// NumberNode creates a new number type node.\n//\n// Usage:\n//\n//\troot := NumberNode(\"\", 1)\n//\tif root == nil {\n//\t\tt.Errorf(\"NumberNode returns nil\")\n//\t}\nfunc NumberNode(key string, value float64) *Node {\n\treturn \u0026Node{\n\t\tkey: \u0026key,\n\t\tvalue: value,\n\t\tnodeType: Number,\n\t\tmodified: true,\n\t}\n}\n\n// StringNode creates a new string type node.\n//\n// Usage:\n//\n//\troot := StringNode(\"\", \"foo\")\n//\tif root == nil {\n//\t\tt.Errorf(\"StringNode returns nil\")\n//\t}\nfunc StringNode(key string, value string) *Node {\n\treturn \u0026Node{\n\t\tkey: \u0026key,\n\t\tvalue: value,\n\t\tnodeType: String,\n\t\tmodified: true,\n\t}\n}\n\n// BoolNode creates a new given boolean value node.\n//\n// Usage:\n//\n//\troot := BoolNode(\"\", true)\n//\tif root == nil {\n//\t\tt.Errorf(\"BoolNode returns nil\")\n//\t}\nfunc BoolNode(key string, value bool) *Node {\n\treturn \u0026Node{\n\t\tkey: \u0026key,\n\t\tvalue: value,\n\t\tnodeType: Boolean,\n\t\tmodified: true,\n\t}\n}\n\n// ArrayNode creates a new array type node.\n//\n// If the given value is nil, it creates an empty array node.\n//\n// Usage:\n//\n//\troot := ArrayNode(\"\", []*Node{StringNode(\"\", \"foo\"), NumberNode(\"\", 1)})\n//\tif root == nil {\n//\t\tt.Errorf(\"ArrayNode returns nil\")\n//\t}\nfunc ArrayNode(key string, value []*Node) *Node {\n\tcurr := \u0026Node{\n\t\tkey: \u0026key,\n\t\tnodeType: Array,\n\t\tmodified: true,\n\t}\n\n\tcurr.next = make(map[string]*Node, len(value))\n\tif value != nil {\n\t\tcurr.value = value\n\n\t\tfor i, v := range value {\n\t\t\tidx := i\n\t\t\tcurr.next[strconv.Itoa(i)] = v\n\n\t\t\tv.prev = curr\n\t\t\tv.index = \u0026idx\n\t\t}\n\t}\n\n\treturn curr\n}\n\n// ObjectNode creates a new object type node.\n//\n// If the given value is nil, it creates an empty object node.\n//\n// next is a map of key and value pairs of the object.\nfunc ObjectNode(key string, value map[string]*Node) *Node {\n\tcurr := \u0026Node{\n\t\tnodeType: Object,\n\t\tkey: \u0026key,\n\t\tnext: value,\n\t\tmodified: true,\n\t}\n\n\tif value != nil {\n\t\tcurr.value = value\n\n\t\tfor key, val := range value {\n\t\t\tvkey := key\n\t\t\tval.prev = curr\n\t\t\tval.key = \u0026vkey\n\t\t}\n\t} else {\n\t\tcurr.next = make(map[string]*Node)\n\t}\n\n\treturn curr\n}\n\n// IsArray returns true if the current node is array type.\nfunc (n *Node) IsArray() bool {\n\treturn n.nodeType == Array\n}\n\n// IsObject returns true if the current node is object type.\nfunc (n *Node) IsObject() bool {\n\treturn n.nodeType == Object\n}\n\n// IsNull returns true if the current node is null type.\nfunc (n *Node) IsNull() bool {\n\treturn n.nodeType == Null\n}\n\n// IsBool returns true if the current node is boolean type.\nfunc (n *Node) IsBool() bool {\n\treturn n.nodeType == Boolean\n}\n\n// IsString returns true if the current node is string type.\nfunc (n *Node) IsString() bool {\n\treturn n.nodeType == String\n}\n\n// IsNumber returns true if the current node is number type.\nfunc (n *Node) IsNumber() bool {\n\treturn n.nodeType == Number\n}\n\n// ready checks the current node is ready or not.\n//\n// the meaning of ready is the current node is parsed and has a valid value.\nfunc (n *Node) ready() bool {\n\treturn n.borders[1] != 0\n}\n\n// source returns the source of the current node.\nfunc (n *Node) source() []byte {\n\tif n == nil {\n\t\treturn nil\n\t}\n\n\tif n.ready() \u0026\u0026 !n.modified \u0026\u0026 n.data != nil {\n\t\treturn (n.data)[n.borders[0]:n.borders[1]]\n\t}\n\n\treturn nil\n}\n\n// root returns the root node of the current node.\nfunc (n *Node) root() *Node {\n\tif n == nil {\n\t\treturn nil\n\t}\n\n\tcurr := n\n\tfor curr.prev != nil {\n\t\tcurr = curr.prev\n\t}\n\n\treturn curr\n}\n\n// GetNull returns the null value if current node is null type.\n//\n// Usage:\n//\n//\troot := Unmarshal([]byte(\"null\"))\n//\tval, err := root.GetNull()\n//\tif err != nil {\n//\t\tt.Errorf(\"GetNull returns error: %v\", err)\n//\t}\n//\tif val != nil {\n//\t\tt.Errorf(\"GetNull returns wrong result: %v\", val)\n//\t}\nfunc (n *Node) GetNull() (interface{}, error) {\n\tif n == nil {\n\t\treturn nil, errNilNode\n\t}\n\n\tif !n.IsNull() {\n\t\treturn nil, errNotNullNode\n\t}\n\n\treturn nil, nil\n}\n\n// MustNull returns the null value if current node is null type.\n//\n// It panics if the current node is not null type.\nfunc (n *Node) MustNull() interface{} {\n\tv, err := n.GetNull()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn v\n}\n\n// GetNumeric returns the numeric (int/float) value if current node is number type.\n//\n// Usage:\n//\n//\troot := Unmarshal([]byte(\"10.5\"))\n//\tval, err := root.GetNumeric()\n//\tif err != nil {\n//\t\tt.Errorf(\"GetNumeric returns error: %v\", err)\n//\t}\n//\tprintln(val) // 10.5\nfunc (n *Node) GetNumeric() (float64, error) {\n\tif n == nil {\n\t\treturn 0, errNilNode\n\t}\n\n\tif n.nodeType != Number {\n\t\treturn 0, errNotNumberNode\n\t}\n\n\tval, err := n.Value()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tv, ok := val.(float64)\n\tif !ok {\n\t\treturn 0, errNotNumberNode\n\t}\n\n\treturn v, nil\n}\n\n// MustNumeric returns the numeric (int/float) value if current node is number type.\n//\n// It panics if the current node is not number type.\nfunc (n *Node) MustNumeric() float64 {\n\tv, err := n.GetNumeric()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn v\n}\n\n// GetString returns the string value if current node is string type.\n//\n// Usage:\n//\n//\troot, err := Unmarshal([]byte(\"foo\"))\n//\tif err != nil {\n//\t\tt.Errorf(\"Error on Unmarshal(): %s\", err)\n//\t}\n//\n//\tstr, err := root.GetString()\n//\tif err != nil {\n//\t\tt.Errorf(\"should retrieve string value: %s\", err)\n//\t}\n//\n//\tprintln(str) // \"foo\"\nfunc (n *Node) GetString() (string, error) {\n\tif n == nil {\n\t\treturn \"\", errEmptyStringNode\n\t}\n\n\tif !n.IsString() {\n\t\treturn \"\", errNotStringNode\n\t}\n\n\tval, err := n.Value()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tv, ok := val.(string)\n\tif !ok {\n\t\treturn \"\", errNotStringNode\n\t}\n\n\treturn v, nil\n}\n\n// MustString returns the string value if current node is string type.\n//\n// It panics if the current node is not string type.\nfunc (n *Node) MustString() string {\n\tv, err := n.GetString()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn v\n}\n\n// GetBool returns the boolean value if current node is boolean type.\n//\n// Usage:\n//\n//\troot := Unmarshal([]byte(\"true\"))\n//\tval, err := root.GetBool()\n//\tif err != nil {\n//\t\tt.Errorf(\"GetBool returns error: %v\", err)\n//\t}\n//\tprintln(val) // true\nfunc (n *Node) GetBool() (bool, error) {\n\tif n == nil {\n\t\treturn false, errNilNode\n\t}\n\n\tif n.nodeType != Boolean {\n\t\treturn false, errNotBoolNode\n\t}\n\n\tval, err := n.Value()\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tv, ok := val.(bool)\n\tif !ok {\n\t\treturn false, errNotBoolNode\n\t}\n\n\treturn v, nil\n}\n\n// MustBool returns the boolean value if current node is boolean type.\n//\n// It panics if the current node is not boolean type.\nfunc (n *Node) MustBool() bool {\n\tv, err := n.GetBool()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn v\n}\n\n// GetArray returns the array value if current node is array type.\n//\n// Usage:\n//\n//\t\troot := Must(Unmarshal([]byte(`[\"foo\", 1]`)))\n//\t\tarr, err := root.GetArray()\n//\t\tif err != nil {\n//\t\t\tt.Errorf(\"GetArray returns error: %v\", err)\n//\t\t}\n//\n//\t\tfor _, val := range arr {\n//\t\t\tprintln(val)\n//\t\t}\n//\n//\t result: \"foo\", 1\nfunc (n *Node) GetArray() ([]*Node, error) {\n\tif n == nil {\n\t\treturn nil, errNilNode\n\t}\n\n\tif n.nodeType != Array {\n\t\treturn nil, errNotArrayNode\n\t}\n\n\tval, err := n.Value()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tv, ok := val.([]*Node)\n\tif !ok {\n\t\treturn nil, errNotArrayNode\n\t}\n\n\treturn v, nil\n}\n\n// MustArray returns the array value if current node is array type.\n//\n// It panics if the current node is not array type.\nfunc (n *Node) MustArray() []*Node {\n\tv, err := n.GetArray()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn v\n}\n\n// AppendArray appends the given values to the current array node.\n//\n// If the current node is not array type, it returns an error.\n//\n// Example 1:\n//\n//\troot := Must(Unmarshal([]byte(`[{\"foo\":\"bar\"}]`)))\n//\tif err := root.AppendArray(NullNode(\"\")); err != nil {\n//\t\tt.Errorf(\"should not return error: %s\", err)\n//\t}\n//\n//\tresult: [{\"foo\":\"bar\"}, null]\n//\n// Example 2:\n//\n//\troot := Must(Unmarshal([]byte(`[\"bar\", \"baz\"]`)))\n//\terr := root.AppendArray(NumberNode(\"\", 1), StringNode(\"\", \"foo\"))\n//\tif err != nil {\n//\t\tt.Errorf(\"AppendArray returns error: %v\", err)\n//\t }\n//\n//\tresult: [\"bar\", \"baz\", 1, \"foo\"]\nfunc (n *Node) AppendArray(value ...*Node) error {\n\tif !n.IsArray() {\n\t\treturn errInvalidAppend\n\t}\n\n\tfor _, val := range value {\n\t\tif err := n.append(nil, val); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tn.mark()\n\treturn nil\n}\n\n// ArrayEach executes the callback for each element in the JSON array.\n//\n// Usage:\n//\n//\tjsonArrayNode.ArrayEach(func(i int, valueNode *Node) {\n//\t ufmt.Println(i, valueNode)\n//\t})\nfunc (n *Node) ArrayEach(callback func(i int, target *Node)) {\n\tif n == nil || !n.IsArray() {\n\t\treturn\n\t}\n\n\tfor idx := 0; idx \u003c len(n.next); idx++ {\n\t\telement, err := n.GetIndex(idx)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tcallback(idx, element)\n\t}\n}\n\n// GetObject returns the object value if current node is object type.\n//\n// Usage:\n//\n//\troot := Must(Unmarshal([]byte(`{\"key\": \"value\"}`)))\n//\tobj, err := root.GetObject()\n//\tif err != nil {\n//\t\tt.Errorf(\"GetObject returns error: %v\", err)\n//\t}\n//\n//\tresult: map[string]*Node{\"key\": StringNode(\"key\", \"value\")}\nfunc (n *Node) GetObject() (map[string]*Node, error) {\n\tif n == nil {\n\t\treturn nil, errNilNode\n\t}\n\n\tif !n.IsObject() {\n\t\treturn nil, errNotObjectNode\n\t}\n\n\tval, err := n.Value()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tv, ok := val.(map[string]*Node)\n\tif !ok {\n\t\treturn nil, errNotObjectNode\n\t}\n\n\treturn v, nil\n}\n\n// MustObject returns the object value if current node is object type.\n//\n// It panics if the current node is not object type.\nfunc (n *Node) MustObject() map[string]*Node {\n\tv, err := n.GetObject()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn v\n}\n\n// AppendObject appends the given key and value to the current object node.\n//\n// If the current node is not object type, it returns an error.\nfunc (n *Node) AppendObject(key string, value *Node) error {\n\tif !n.IsObject() {\n\t\treturn errInvalidAppend\n\t}\n\n\tif err := n.append(\u0026key, value); err != nil {\n\t\treturn err\n\t}\n\n\tn.mark()\n\treturn nil\n}\n\n// ObjectEach executes the callback for each key-value pair in the JSON object.\n//\n// Usage:\n//\n//\tjsonObjectNode.ObjectEach(func(key string, valueNode *Node) {\n//\t ufmt.Println(key, valueNode)\n//\t})\nfunc (n *Node) ObjectEach(callback func(key string, value *Node)) {\n\tif n == nil || !n.IsObject() {\n\t\treturn\n\t}\n\n\tfor key, child := range n.next {\n\t\tcallback(key, child)\n\t}\n}\n\n// String converts the node to a string representation.\nfunc (n *Node) String() string {\n\tif n == nil {\n\t\treturn \"\"\n\t}\n\n\tif n.ready() \u0026\u0026 !n.modified {\n\t\treturn string(n.source())\n\t}\n\n\tval, err := Marshal(n)\n\tif err != nil {\n\t\treturn \"error: \" + err.Error()\n\t}\n\n\treturn string(val)\n}\n\n// Path builds the path of the current node.\n//\n// For example:\n//\n//\t{ \"key\": { \"sub\": [ \"val1\", \"val2\" ] }}\n//\n// The path of \"val2\" is: $.key.sub[1]\nfunc (n *Node) Path() string {\n\tif n == nil {\n\t\treturn \"\"\n\t}\n\n\tvar sb strings.Builder\n\n\tif n.prev == nil {\n\t\tsb.WriteString(\"$\")\n\t} else {\n\t\tsb.WriteString(n.prev.Path())\n\n\t\tif n.key != nil {\n\t\t\tsb.WriteString(\"['\" + n.Key() + \"']\")\n\t\t} else {\n\t\t\tsb.WriteString(\"[\" + strconv.Itoa(n.Index()) + \"]\")\n\t\t}\n\t}\n\n\treturn sb.String()\n}\n\n// mark marks the current node as modified.\nfunc (n *Node) mark() {\n\tnode := n\n\tfor node != nil \u0026\u0026 !node.modified {\n\t\tnode.modified = true\n\t\tnode = node.prev\n\t}\n}\n\n// isContainer checks the current node type is array or object.\nfunc (n *Node) isContainer() bool {\n\treturn n.IsArray() || n.IsObject()\n}\n\n// remove removes the value from the current container type node.\nfunc (n *Node) remove(v *Node) error {\n\tif !n.isContainer() {\n\t\treturn ufmt.Errorf(\n\t\t\t\"can't remove value from non-array or non-object node. got=%s\",\n\t\t\tn.Type().String(),\n\t\t)\n\t}\n\n\tif v.prev != n {\n\t\treturn errors.New(\"invalid parent node\")\n\t}\n\n\tn.mark()\n\tif n.IsArray() {\n\t\tdelete(n.next, strconv.Itoa(*v.index))\n\t\tn.dropIndex(*v.index)\n\t} else {\n\t\tdelete(n.next, *v.key)\n\t}\n\n\tv.prev = nil\n\treturn nil\n}\n\n// dropIndex rebase the index of current array node values.\nfunc (n *Node) dropIndex(idx int) {\n\tfor i := idx + 1; i \u003c= len(n.next); i++ {\n\t\tprv := i - 1\n\t\tif curr, ok := n.next[strconv.Itoa(i)]; ok {\n\t\t\tcurr.index = \u0026prv\n\t\t\tn.next[strconv.Itoa(prv)] = curr\n\t\t}\n\n\t\tdelete(n.next, strconv.Itoa(i))\n\t}\n}\n\n// append is a helper function to append the given value to the current container type node.\nfunc (n *Node) append(key *string, val *Node) error {\n\tif n.isSameOrParentNode(val) {\n\t\treturn errInvalidAppendCycle\n\t}\n\n\tif val.prev != nil {\n\t\tif err := val.prev.remove(val); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tval.prev = n\n\tval.key = key\n\n\tif key == nil {\n\t\tsize := len(n.next)\n\t\tval.index = \u0026size\n\t\tn.next[strconv.Itoa(size)] = val\n\t} else {\n\t\tif old, ok := n.next[*key]; ok {\n\t\t\tif err := n.remove(old); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tn.next[*key] = val\n\t}\n\n\treturn nil\n}\n\nfunc (n *Node) isSameOrParentNode(nd *Node) bool {\n\treturn n == nd || n.isParentNode(nd)\n}\n\nfunc (n *Node) isParentNode(nd *Node) bool {\n\tif n == nil {\n\t\treturn false\n\t}\n\n\tfor curr := nd.prev; curr != nil; curr = curr.prev {\n\t\tif curr == n {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n// cptrs returns the pointer of the given string value.\nfunc cptrs(cpy *string) *string {\n\tif cpy == nil {\n\t\treturn nil\n\t}\n\n\tval := *cpy\n\n\treturn \u0026val\n}\n\n// cptri returns the pointer of the given integer value.\nfunc cptri(i *int) *int {\n\tif i == nil {\n\t\treturn nil\n\t}\n\n\tval := *i\n\treturn \u0026val\n}\n\n// Must panics if the given node is not fulfilled the expectation.\n// Usage:\n//\n//\tnode := Must(Unmarshal([]byte(`{\"key\": \"value\"}`))\nfunc Must(root *Node, expect error) *Node {\n\tif expect != nil {\n\t\tpanic(expect)\n\t}\n\n\treturn root\n}\n"},{"name":"node_test.gno","body":"package json\n\nimport (\n\t\"bytes\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar (\n\tnilKey *string\n\tdummyKey = \"key\"\n)\n\ntype _args struct {\n\tprev *Node\n\tbuf *buffer\n\ttyp ValueType\n\tkey **string\n}\n\ntype simpleNode struct {\n\tname string\n\tnode *Node\n}\n\nfunc TestNode_CreateNewNode(t *testing.T) {\n\trel := \u0026dummyKey\n\n\ttests := []struct {\n\t\tname string\n\t\targs _args\n\t\texpectCurr *Node\n\t\texpectErr bool\n\t\texpectPanic bool\n\t}{\n\t\t{\n\t\t\tname: \"child for non container type\",\n\t\t\targs: _args{\n\t\t\t\tprev: BoolNode(\"\", true),\n\t\t\t\tbuf: newBuffer(make([]byte, 10)),\n\t\t\t\ttyp: Boolean,\n\t\t\t\tkey: \u0026rel,\n\t\t\t},\n\t\t\texpectCurr: nil,\n\t\t\texpectErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tdefer func() {\n\t\t\t\tif r := recover(); r != nil {\n\t\t\t\t\tif tt.expectPanic {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tt.Errorf(\"%s panic occurred when not expected: %v\", tt.name, r)\n\t\t\t\t} else if tt.expectPanic {\n\t\t\t\t\tt.Errorf(\"%s expected panic but didn't occur\", tt.name)\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\tgot, err := NewNode(tt.args.prev, tt.args.buf, tt.args.typ, tt.args.key)\n\t\t\tif (err != nil) != tt.expectErr {\n\t\t\t\tt.Errorf(\"%s error = %v, expect error %v\", tt.name, err, tt.expectErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif tt.expectErr {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif !compareNodes(got, tt.expectCurr) {\n\t\t\t\tt.Errorf(\"%s got = %v, want %v\", tt.name, got, tt.expectCurr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_Value(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tdata []byte\n\t\t_type ValueType\n\t\texpected interface{}\n\t\terrExpected bool\n\t}{\n\t\t{name: \"null\", data: []byte(\"null\"), _type: Null, expected: nil},\n\t\t{name: \"1\", data: []byte(\"1\"), _type: Number, expected: float64(1)},\n\t\t{name: \".1\", data: []byte(\".1\"), _type: Number, expected: float64(.1)},\n\t\t{name: \"-.1e1\", data: []byte(\"-.1e1\"), _type: Number, expected: float64(-1)},\n\t\t{name: \"string\", data: []byte(\"\\\"foo\\\"\"), _type: String, expected: \"foo\"},\n\t\t{name: \"space\", data: []byte(\"\\\"foo bar\\\"\"), _type: String, expected: \"foo bar\"},\n\t\t{name: \"true\", data: []byte(\"true\"), _type: Boolean, expected: true},\n\t\t{name: \"invalid true\", data: []byte(\"tru\"), _type: Unknown, errExpected: true},\n\t\t{name: \"invalid false\", data: []byte(\"fals\"), _type: Unknown, errExpected: true},\n\t\t{name: \"false\", data: []byte(\"false\"), _type: Boolean, expected: false},\n\t\t{name: \"e1\", data: []byte(\"e1\"), _type: Unknown, errExpected: true},\n\t\t{name: \"1a\", data: []byte(\"1a\"), _type: Unknown, errExpected: true},\n\t\t{name: \"string error\", data: []byte(\"\\\"foo\\nbar\\\"\"), _type: String, errExpected: true},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tcurr := \u0026Node{\n\t\t\t\tdata: tt.data,\n\t\t\t\tnodeType: tt._type,\n\t\t\t\tborders: [2]int{0, len(tt.data)},\n\t\t\t}\n\n\t\t\tgot, err := curr.Value()\n\t\t\tif err != nil {\n\t\t\t\tif !tt.errExpected {\n\t\t\t\t\tt.Errorf(\"%s error = %v, expect error %v\", tt.name, err, tt.errExpected)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif got != tt.expected {\n\t\t\t\tt.Errorf(\"%s got = %v, want %v\", tt.name, got, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_Delete(t *testing.T) {\n\troot := Must(Unmarshal([]byte(`{\"foo\":\"bar\"}`)))\n\tif err := root.Delete(); err != nil {\n\t\tt.Errorf(\"Delete returns error: %v\", err)\n\t}\n\n\tif value, err := Marshal(root); err != nil {\n\t\tt.Errorf(\"Marshal returns error: %v\", err)\n\t} else if string(value) != `{\"foo\":\"bar\"}` {\n\t\tt.Errorf(\"Marshal returns wrong value: %s\", string(value))\n\t}\n\n\tfoo := root.MustKey(\"foo\")\n\tif err := foo.Delete(); err != nil {\n\t\tt.Errorf(\"Delete returns error while handling foo: %v\", err)\n\t}\n\n\tif value, err := Marshal(root); err != nil {\n\t\tt.Errorf(\"Marshal returns error: %v\", err)\n\t} else if string(value) != `{}` {\n\t\tt.Errorf(\"Marshal returns wrong value: %s\", string(value))\n\t}\n\n\tif value, err := Marshal(foo); err != nil {\n\t\tt.Errorf(\"Marshal returns error: %v\", err)\n\t} else if string(value) != `\"bar\"` {\n\t\tt.Errorf(\"Marshal returns wrong value: %s\", string(value))\n\t}\n\n\tif foo.prev != nil {\n\t\tt.Errorf(\"foo.prev should be nil\")\n\t}\n}\n\nfunc TestNode_ObjectNode(t *testing.T) {\n\tobjs := map[string]*Node{\n\t\t\"key1\": NullNode(\"null\"),\n\t\t\"key2\": NumberNode(\"answer\", 42),\n\t\t\"key3\": StringNode(\"string\", \"foobar\"),\n\t\t\"key4\": BoolNode(\"bool\", true),\n\t}\n\n\tnode := ObjectNode(\"test\", objs)\n\n\tif len(node.next) != len(objs) {\n\t\tt.Errorf(\"ObjectNode: want %v got %v\", len(objs), len(node.next))\n\t}\n\n\tfor k, v := range objs {\n\t\tif node.next[k] == nil {\n\t\t\tt.Errorf(\"ObjectNode: want %v got %v\", v, node.next[k])\n\t\t}\n\t}\n}\n\nfunc TestNode_AppendObject(t *testing.T) {\n\tif err := Must(Unmarshal([]byte(`{\"foo\":\"bar\",\"baz\":null}`))).AppendObject(\"biz\", NullNode(\"\")); err != nil {\n\t\tt.Errorf(\"AppendArray should return error\")\n\t}\n\n\troot := Must(Unmarshal([]byte(`{\"foo\":\"bar\"}`)))\n\tif err := root.AppendObject(\"baz\", NullNode(\"\")); err != nil {\n\t\tt.Errorf(\"AppendObject should not return error: %s\", err)\n\t}\n\n\tif value, err := Marshal(root); err != nil {\n\t\tt.Errorf(\"Marshal returns error: %v\", err)\n\t} else if isSameObject(string(value), `\"{\"foo\":\"bar\",\"baz\":null}\"`) {\n\t\tt.Errorf(\"Marshal returns wrong value: %s\", string(value))\n\t}\n\n\t// FIXME: this may fail if execute test in more than 3 times in a row.\n\tif err := root.AppendObject(\"biz\", NumberNode(\"\", 42)); err != nil {\n\t\tt.Errorf(\"AppendObject returns error: %v\", err)\n\t}\n\n\tval, err := Marshal(root)\n\tif err != nil {\n\t\tt.Errorf(\"Marshal returns error: %v\", err)\n\t}\n\n\t// FIXME: this may fail if execute test in more than 3 times in a row.\n\tif isSameObject(string(val), `\"{\"foo\":\"bar\",\"baz\":null,\"biz\":42}\"`) {\n\t\tt.Errorf(\"Marshal returns wrong value: %s\", string(val))\n\t}\n}\n\nfunc TestNode_ArrayNode(t *testing.T) {\n\tarr := []*Node{\n\t\tNullNode(\"nil\"),\n\t\tNumberNode(\"num\", 42),\n\t\tStringNode(\"str\", \"foobar\"),\n\t\tBoolNode(\"bool\", true),\n\t}\n\n\tnode := ArrayNode(\"test\", arr)\n\n\tif len(node.next) != len(arr) {\n\t\tt.Errorf(\"ArrayNode: want %v got %v\", len(arr), len(node.next))\n\t}\n\n\tfor i, v := range arr {\n\t\tif node.next[strconv.Itoa(i)] == nil {\n\t\t\tt.Errorf(\"ArrayNode: want %v got %v\", v, node.next[strconv.Itoa(i)])\n\t\t}\n\t}\n}\n\nfunc TestNode_AppendArray(t *testing.T) {\n\tif err := Must(Unmarshal([]byte(`[{\"foo\":\"bar\"}]`))).AppendArray(NullNode(\"\")); err != nil {\n\t\tt.Errorf(\"should return error\")\n\t}\n\n\troot := Must(Unmarshal([]byte(`[{\"foo\":\"bar\"}]`)))\n\tif err := root.AppendArray(NullNode(\"\")); err != nil {\n\t\tt.Errorf(\"should not return error: %s\", err)\n\t}\n\n\tif value, err := Marshal(root); err != nil {\n\t\tt.Errorf(\"Marshal returns error: %v\", err)\n\t} else if string(value) != `[{\"foo\":\"bar\"},null]` {\n\t\tt.Errorf(\"Marshal returns wrong value: %s\", string(value))\n\t}\n\n\tif err := root.AppendArray(\n\t\tNumberNode(\"\", 1),\n\t\tStringNode(\"\", \"foo\"),\n\t\tMust(Unmarshal([]byte(`[0,1,null,true,\"example\"]`))),\n\t\tMust(Unmarshal([]byte(`{\"foo\": true, \"bar\": null, \"baz\": 123}`))),\n\t); err != nil {\n\t\tt.Errorf(\"AppendArray returns error: %v\", err)\n\t}\n\n\tif value, err := Marshal(root); err != nil {\n\t\tt.Errorf(\"Marshal returns error: %v\", err)\n\t} else if string(value) != `[{\"foo\":\"bar\"},null,1,\"foo\",[0,1,null,true,\"example\"],{\"foo\": true, \"bar\": null, \"baz\": 123}]` {\n\t\tt.Errorf(\"Marshal returns wrong value: %s\", string(value))\n\t}\n}\n\n/******** value getter ********/\n\nfunc TestNode_GetBool(t *testing.T) {\n\troot, err := Unmarshal([]byte(`true`))\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err.Error())\n\t\treturn\n\t}\n\n\tvalue, err := root.GetBool()\n\tif err != nil {\n\t\tt.Errorf(\"Error on root.GetBool(): %s\", err.Error())\n\t}\n\n\tif !value {\n\t\tt.Errorf(\"root.GetBool() is corrupted\")\n\t}\n}\n\nfunc TestNode_GetBool_Fail(t *testing.T) {\n\ttests := []simpleNode{\n\t\t{\"nil node\", (*Node)(nil)},\n\t\t{\"literally null node\", NullNode(\"\")},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif _, err := tt.node.GetBool(); err == nil {\n\t\t\t\tt.Errorf(\"%s should be an error\", tt.name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_IsBool(t *testing.T) {\n\ttests := []simpleNode{\n\t\t{\"true\", BoolNode(\"\", true)},\n\t\t{\"false\", BoolNode(\"\", false)},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif !tt.node.IsBool() {\n\t\t\t\tt.Errorf(\"%s should be a bool\", tt.name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_IsBool_With_Unmarshal(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tjson []byte\n\t\twant bool\n\t}{\n\t\t{\"true\", []byte(\"true\"), true},\n\t\t{\"false\", []byte(\"false\"), true},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\troot, err := Unmarshal(tt.json)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Error on Unmarshal(): %s\", err.Error())\n\t\t\t}\n\n\t\t\tif root.IsBool() != tt.want {\n\t\t\t\tt.Errorf(\"%s should be a bool\", tt.name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nvar nullJson = []byte(`null`)\n\nfunc TestNode_GetNull(t *testing.T) {\n\troot, err := Unmarshal(nullJson)\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err.Error())\n\t}\n\n\tvalue, err := root.GetNull()\n\tif err != nil {\n\t\tt.Errorf(\"error occurred while getting null, %s\", err)\n\t}\n\n\tif value != nil {\n\t\tt.Errorf(\"value is not matched. expected: nil, got: %v\", value)\n\t}\n}\n\nfunc TestNode_GetNull_Fail(t *testing.T) {\n\ttests := []simpleNode{\n\t\t{\"nil node\", (*Node)(nil)},\n\t\t{\"number node is null\", NumberNode(\"\", 42)},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif _, err := tt.node.GetNull(); err == nil {\n\t\t\t\tt.Errorf(\"%s should be an error\", tt.name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_MustNull(t *testing.T) {\n\troot, err := Unmarshal(nullJson)\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err.Error())\n\t}\n\n\tvalue := root.MustNull()\n\tif value != nil {\n\t\tt.Errorf(\"value is not matched. expected: nil, got: %v\", value)\n\t}\n}\n\nfunc TestNode_GetNumeric_Float(t *testing.T) {\n\troot, err := Unmarshal([]byte(`123.456`))\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err)\n\t\treturn\n\t}\n\n\tvalue, err := root.GetNumeric()\n\tif err != nil {\n\t\tt.Errorf(\"Error on root.GetNumeric(): %s\", err)\n\t}\n\n\tif value != float64(123.456) {\n\t\tt.Errorf(ufmt.Sprintf(\"value is not matched. expected: 123.456, got: %v\", value))\n\t}\n}\n\nfunc TestNode_GetNumeric_Scientific_Notation(t *testing.T) {\n\troot, err := Unmarshal([]byte(`1e3`))\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err)\n\t\treturn\n\t}\n\n\tvalue, err := root.GetNumeric()\n\tif err != nil {\n\t\tt.Errorf(\"Error on root.GetNumeric(): %s\", err)\n\t}\n\n\tif value != float64(1000) {\n\t\tt.Errorf(ufmt.Sprintf(\"value is not matched. expected: 1000, got: %v\", value))\n\t}\n}\n\nfunc TestNode_GetNumeric_With_Unmarshal(t *testing.T) {\n\troot, err := Unmarshal([]byte(`123`))\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err)\n\t\treturn\n\t}\n\n\tvalue, err := root.GetNumeric()\n\tif err != nil {\n\t\tt.Errorf(\"Error on root.GetNumeric(): %s\", err)\n\t}\n\n\tif value != float64(123) {\n\t\tt.Errorf(ufmt.Sprintf(\"value is not matched. expected: 123, got: %v\", value))\n\t}\n}\n\nfunc TestNode_GetNumeric_Fail(t *testing.T) {\n\ttests := []simpleNode{\n\t\t{\"nil node\", (*Node)(nil)},\n\t\t{\"null node\", NullNode(\"\")},\n\t\t{\"string node\", StringNode(\"\", \"123\")},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif _, err := tt.node.GetNumeric(); err == nil {\n\t\t\t\tt.Errorf(\"%s should be an error\", tt.name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_GetString(t *testing.T) {\n\troot, err := Unmarshal([]byte(`\"123foobar 3456\"`))\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err)\n\t}\n\n\tvalue, err := root.GetString()\n\tif err != nil {\n\t\tt.Errorf(\"Error on root.GetString(): %s\", err)\n\t}\n\n\tif value != \"123foobar 3456\" {\n\t\tt.Errorf(ufmt.Sprintf(\"value is not matched. expected: 123, got: %s\", value))\n\t}\n}\n\nfunc TestNode_GetString_Fail(t *testing.T) {\n\ttests := []simpleNode{\n\t\t{\"nil node\", (*Node)(nil)},\n\t\t{\"null node\", NullNode(\"\")},\n\t\t{\"number node\", NumberNode(\"\", 123)},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif _, err := tt.node.GetString(); err == nil {\n\t\t\t\tt.Errorf(\"%s should be an error\", tt.name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_MustString(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tdata []byte\n\t}{\n\t\t{\"foo\", []byte(`\"foo\"`)},\n\t\t{\"foo bar\", []byte(`\"foo bar\"`)},\n\t\t{\"\", []byte(`\"\"`)},\n\t\t{\"안녕하세요\", []byte(`\"안녕하세요\"`)},\n\t\t{\"こんにちは\", []byte(`\"こんにちは\"`)},\n\t\t{\"你好\", []byte(`\"你好\"`)},\n\t\t{\"one \\\"encoded\\\" string\", []byte(`\"one \\\"encoded\\\" string\"`)},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\troot, err := Unmarshal(tt.data)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Error on Unmarshal(): %s\", err)\n\t\t\t}\n\n\t\t\tvalue := root.MustString()\n\t\t\tif value != tt.name {\n\t\t\t\tt.Errorf(\"value is not matched. expected: %s, got: %s\", tt.name, value)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal_Array(t *testing.T) {\n\troot, err := Unmarshal([]byte(\" [1,[\\\"1\\\",[1,[1,2,3]]]]\\r\\n\"))\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal: %s\", err.Error())\n\t}\n\n\tif root == nil {\n\t\tt.Errorf(\"Error on Unmarshal: root is nil\")\n\t}\n\n\tif root.Type() != Array {\n\t\tt.Errorf(\"Error on Unmarshal: wrong type\")\n\t}\n\n\tarray, err := root.GetArray()\n\tif err != nil {\n\t\tt.Errorf(\"error occurred while getting array, %s\", err)\n\t} else if len(array) != 2 {\n\t\tt.Errorf(\"expected 2 elements, got %d\", len(array))\n\t} else if val, err := array[0].GetNumeric(); err != nil {\n\t\tt.Errorf(\"value of array[0] is not numeric. got: %v\", array[0].value)\n\t} else if val != 1 {\n\t\tt.Errorf(\"Error on array[0].GetNumeric(): expected to be '1', got: %v\", val)\n\t} else if val, err := array[1].GetArray(); err != nil {\n\t\tt.Errorf(\"error occurred while getting array, %s\", err.Error())\n\t} else if len(val) != 2 {\n\t\tt.Errorf(\"Error on array[1].GetArray(): expected 2 elements, got %d\", len(val))\n\t} else if el, err := val[0].GetString(); err != nil {\n\t\tt.Errorf(\"error occurred while getting string, %s\", err.Error())\n\t} else if el != \"1\" {\n\t\tt.Errorf(\"Error on val[0].GetString(): expected to be '1', got: %s\", el)\n\t}\n}\n\nvar sampleArr = []byte(`[-1, 2, 3, 4, 5, 6]`)\n\nfunc TestNode_GetArray(t *testing.T) {\n\troot, err := Unmarshal(sampleArr)\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err)\n\t\treturn\n\t}\n\n\tarray, err := root.GetArray()\n\tif err != nil {\n\t\tt.Errorf(\"Error on root.GetArray(): %s\", err)\n\t}\n\n\tif len(array) != 6 {\n\t\tt.Errorf(ufmt.Sprintf(\"length is not matched. expected: 3, got: %d\", len(array)))\n\t}\n\n\tfor i, node := range array {\n\t\tfor j, val := range []int{-1, 2, 3, 4, 5, 6} {\n\t\t\tif i == j {\n\t\t\t\tif v, err := node.GetNumeric(); err != nil {\n\t\t\t\t\tt.Errorf(ufmt.Sprintf(\"Error on node.GetNumeric(): %s\", err))\n\t\t\t\t} else if v != float64(val) {\n\t\t\t\t\tt.Errorf(ufmt.Sprintf(\"value is not matched. expected: %d, got: %v\", val, v))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestNode_GetArray_Fail(t *testing.T) {\n\ttests := []simpleNode{\n\t\t{\"nil node\", (*Node)(nil)},\n\t\t{\"null node\", NullNode(\"\")},\n\t\t{\"number node\", NumberNode(\"\", 123)},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif _, err := tt.node.GetArray(); err == nil {\n\t\t\t\tt.Errorf(\"%s should be an error\", tt.name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_IsArray(t *testing.T) {\n\troot, err := Unmarshal(sampleArr)\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err)\n\t\treturn\n\t}\n\n\tif root.Type() != Array {\n\t\tt.Errorf(ufmt.Sprintf(\"Must be an array. got: %s\", root.Type().String()))\n\t}\n}\n\nfunc TestNode_ArrayEach(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tjson string\n\t\texpected []int\n\t}{\n\t\t{\n\t\t\tname: \"empty array\",\n\t\t\tjson: `[]`,\n\t\t\texpected: []int{},\n\t\t},\n\t\t{\n\t\t\tname: \"single element\",\n\t\t\tjson: `[42]`,\n\t\t\texpected: []int{42},\n\t\t},\n\t\t{\n\t\t\tname: \"multiple elements\",\n\t\t\tjson: `[1, 2, 3, 4, 5]`,\n\t\t\texpected: []int{1, 2, 3, 4, 5},\n\t\t},\n\t\t{\n\t\t\tname: \"multiple elements but all values are same\",\n\t\t\tjson: `[1, 1, 1, 1, 1]`,\n\t\t\texpected: []int{1, 1, 1, 1, 1},\n\t\t},\n\t\t{\n\t\t\tname: \"multiple elements with non-numeric values\",\n\t\t\tjson: `[\"a\", \"b\", \"c\", \"d\", \"e\"]`,\n\t\t\texpected: []int{},\n\t\t},\n\t\t{\n\t\t\tname: \"non-array node\",\n\t\t\tjson: `{\"not\": \"an array\"}`,\n\t\t\texpected: []int{},\n\t\t},\n\t\t{\n\t\t\tname: \"array containing numeric and non-numeric elements\",\n\t\t\tjson: `[\"1\", 2, 3, \"4\", 5, \"6\"]`,\n\t\t\texpected: []int{2, 3, 5},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\troot, err := Unmarshal([]byte(tc.json))\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Unmarshal failed: %v\", err)\n\t\t\t}\n\n\t\t\tvar result []int // callback result\n\t\t\troot.ArrayEach(func(index int, element *Node) {\n\t\t\t\tif val, err := strconv.Atoi(element.String()); err == nil {\n\t\t\t\t\tresult = append(result, val)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tif len(result) != len(tc.expected) {\n\t\t\t\tt.Errorf(\"%s: expected %d elements, got %d\", tc.name, len(tc.expected), len(result))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfor i, val := range result {\n\t\t\t\tif val != tc.expected[i] {\n\t\t\t\t\tt.Errorf(\"%s: expected value at index %d to be %d, got %d\", tc.name, i, tc.expected[i], val)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_Key(t *testing.T) {\n\troot, err := Unmarshal([]byte(`{\"foo\": true, \"bar\": null, \"baz\": 123, \"biz\": [1,2,3]}`))\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err.Error())\n\t}\n\n\tobj := root.MustObject()\n\tfor key, node := range obj {\n\t\tif key != node.Key() {\n\t\t\tt.Errorf(\"Key() = %v, want %v\", node.Key(), key)\n\t\t}\n\t}\n\n\tkeys := []string{\"foo\", \"bar\", \"baz\", \"biz\"}\n\tfor _, key := range keys {\n\t\tif obj[key].Key() != key {\n\t\t\tt.Errorf(\"Key() = %v, want %v\", obj[key].Key(), key)\n\t\t}\n\t}\n\n\t// TODO: resolve stack overflow\n\t// if root.MustKey(\"foo\").Clone().Key() != \"\" {\n\t// \tt.Errorf(\"wrong key found for cloned key\")\n\t// }\n\n\tif (*Node)(nil).Key() != \"\" {\n\t\tt.Errorf(\"wrong key found for nil node\")\n\t}\n}\n\nfunc TestNode_Size(t *testing.T) {\n\troot, err := Unmarshal(sampleArr)\n\tif err != nil {\n\t\tt.Errorf(\"error occurred while unmarshal\")\n\t}\n\n\tsize := root.Size()\n\tif size != 6 {\n\t\tt.Errorf(ufmt.Sprintf(\"Size() must be 6. got: %v\", size))\n\t}\n\n\tif (*Node)(nil).Size() != 0 {\n\t\tt.Errorf(ufmt.Sprintf(\"Size() must be 0. got: %v\", (*Node)(nil).Size()))\n\t}\n}\n\nfunc TestNode_Index(t *testing.T) {\n\troot, err := Unmarshal([]byte(`[1, 2, 3, 4, 5, 6]`))\n\tif err != nil {\n\t\tt.Error(\"error occurred while unmarshal\")\n\t}\n\n\tarr := root.MustArray()\n\tfor i, node := range arr {\n\t\tif i != node.Index() {\n\t\t\tt.Errorf(ufmt.Sprintf(\"Index() must be nil. got: %v\", i))\n\t\t}\n\t}\n}\n\nfunc TestNode_Index_Fail(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tnode *Node\n\t\twant int\n\t}{\n\t\t{\"nil node\", (*Node)(nil), -1},\n\t\t{\"null node\", NullNode(\"\"), -1},\n\t\t{\"object node\", ObjectNode(\"\", nil), -1},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := tt.node.Index(); got != tt.want {\n\t\t\t\tt.Errorf(\"Index() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_GetIndex(t *testing.T) {\n\troot := Must(Unmarshal([]byte(`[1, 2, 3, 4, 5, 6]`)))\n\texpected := []int{1, 2, 3, 4, 5, 6}\n\n\tif len(expected) != root.Size() {\n\t\tt.Errorf(\"length is not matched. expected: %d, got: %d\", len(expected), root.Size())\n\t}\n\n\t// TODO: if length exceeds, stack overflow occurs. need to fix\n\tfor i, v := range expected {\n\t\tval, err := root.GetIndex(i)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"error occurred while getting index %d, %s\", i, err)\n\t\t}\n\n\t\tif val.MustNumeric() != float64(v) {\n\t\t\tt.Errorf(\"value is not matched. expected: %d, got: %v\", v, val.MustNumeric())\n\t\t}\n\t}\n}\n\nfunc TestNode_GetIndex_InputIndex_Exceed_Original_Node_Index(t *testing.T) {\n\troot, err := Unmarshal([]byte(`[1, 2, 3, 4, 5, 6]`))\n\tif err != nil {\n\t\tt.Errorf(\"error occurred while unmarshal\")\n\t}\n\n\t_, err = root.GetIndex(10)\n\tif err == nil {\n\t\tt.Errorf(\"GetIndex should return error\")\n\t}\n}\n\nfunc TestNode_DeleteIndex(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\texpected string\n\t\tindex int\n\t\tok bool\n\t}{\n\t\t{`null`, ``, 0, false},\n\t\t{`1`, ``, 0, false},\n\t\t{`{}`, ``, 0, false},\n\t\t{`{\"foo\":\"bar\"}`, ``, 0, false},\n\t\t{`true`, ``, 0, false},\n\t\t{`[]`, ``, 0, false},\n\t\t{`[]`, ``, -1, false},\n\t\t{`[1]`, `[]`, 0, true},\n\t\t{`[{}]`, `[]`, 0, true},\n\t\t{`[{}, [], 42]`, `[{}, []]`, -1, true},\n\t\t{`[{}, [], 42]`, `[[], 42]`, 0, true},\n\t\t{`[{}, [], 42]`, `[{}, 42]`, 1, true},\n\t\t{`[{}, [], 42]`, `[{}, []]`, 2, true},\n\t\t{`[{}, [], 42]`, ``, 10, false},\n\t\t{`[{}, [], 42]`, ``, -10, false},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\troot := Must(Unmarshal([]byte(tt.name)))\n\t\t\terr := root.DeleteIndex(tt.index)\n\t\t\tif err != nil \u0026\u0026 tt.ok {\n\t\t\t\tt.Errorf(\"DeleteIndex returns error: %v\", err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_GetKey(t *testing.T) {\n\troot, err := Unmarshal([]byte(`{\"foo\": true, \"bar\": null}`))\n\tif err != nil {\n\t\tt.Error(\"error occurred while unmarshal\")\n\t}\n\n\tvalue, err := root.GetKey(\"foo\")\n\tif err != nil {\n\t\tt.Errorf(\"error occurred while getting key, %s\", err)\n\t}\n\n\tif value.MustBool() != true {\n\t\tt.Errorf(\"value is not matched. expected: true, got: %v\", value.MustBool())\n\t}\n\n\tvalue, err = root.GetKey(\"bar\")\n\tif err != nil {\n\t\tt.Errorf(\"error occurred while getting key, %s\", err)\n\t}\n\n\t_, err = root.GetKey(\"baz\")\n\tif err == nil {\n\t\tt.Errorf(\"key baz is not exist. must be failed\")\n\t}\n\n\tif value.MustNull() != nil {\n\t\tt.Errorf(\"value is not matched. expected: nil, got: %v\", value.MustNull())\n\t}\n}\n\nfunc TestNode_GetKey_Fail(t *testing.T) {\n\ttests := []simpleNode{\n\t\t{\"nil node\", (*Node)(nil)},\n\t\t{\"null node\", NullNode(\"\")},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif _, err := tt.node.GetKey(\"\"); err == nil {\n\t\t\t\tt.Errorf(\"%s should be an error\", tt.name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_GetUniqueKeyList(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tjson string\n\t\texpected []string\n\t}{\n\t\t{\n\t\t\tname: \"simple foo/bar\",\n\t\t\tjson: `{\"foo\": true, \"bar\": null}`,\n\t\t\texpected: []string{\"foo\", \"bar\"},\n\t\t},\n\t\t{\n\t\t\tname: \"empty object\",\n\t\t\tjson: `{}`,\n\t\t\texpected: []string{},\n\t\t},\n\t\t{\n\t\t\tname: \"nested object\",\n\t\t\tjson: `{\n\t\t\t\t\"outer\": {\n\t\t\t\t\t\"inner\": {\n\t\t\t\t\t\t\"key\": \"value\"\n\t\t\t\t\t},\n\t\t\t\t\t\"array\": [1, 2, 3]\n\t\t\t\t},\n\t\t\t\t\"another\": \"item\"\n\t\t\t}`,\n\t\t\texpected: []string{\"outer\", \"inner\", \"key\", \"array\", \"another\"},\n\t\t},\n\t\t{\n\t\t\tname: \"complex object\",\n\t\t\tjson: `{\n\t\t\t\t\"Image\": {\n\t\t\t\t\t\"Width\": 800,\n\t\t\t\t\t\"Height\": 600,\n\t\t\t\t\t\"Title\": \"View from 15th Floor\",\n\t\t\t\t\t\"Thumbnail\": {\n\t\t\t\t\t\t\"Url\": \"http://www.example.com/image/481989943\",\n\t\t\t\t\t\t\"Height\": 125,\n\t\t\t\t\t\t\"Width\": 100\n\t\t\t\t\t},\n\t\t\t\t\t\"Animated\": false,\n\t\t\t\t\t\"IDs\": [116, 943, 234, 38793]\n\t\t\t\t}\n\t\t\t}`,\n\t\t\texpected: []string{\"Image\", \"Width\", \"Height\", \"Title\", \"Thumbnail\", \"Url\", \"Animated\", \"IDs\"},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\troot, err := Unmarshal([]byte(tt.json))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error occurred while unmarshal\")\n\t\t\t}\n\n\t\t\tvalue := root.UniqueKeyLists()\n\t\t\tif len(value) != len(tt.expected) {\n\t\t\t\tt.Errorf(\"%s length must be %v. got: %v. retrieved keys: %s\", tt.name, len(tt.expected), len(value), value)\n\t\t\t}\n\n\t\t\tfor _, key := range value {\n\t\t\t\tif !contains(tt.expected, key) {\n\t\t\t\t\tt.Errorf(\"EachKey() must be in %v. got: %v\", tt.expected, key)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TODO: resolve stack overflow\nfunc TestNode_IsEmpty(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tnode *Node\n\t\texpected bool\n\t}{\n\t\t{\"nil node\", (*Node)(nil), false}, // nil node is not empty.\n\t\t// {\"null node\", NullNode(\"\"), true},\n\t\t{\"empty object\", ObjectNode(\"\", nil), true},\n\t\t{\"empty array\", ArrayNode(\"\", nil), true},\n\t\t{\"non-empty object\", ObjectNode(\"\", map[string]*Node{\"foo\": BoolNode(\"foo\", true)}), false},\n\t\t{\"non-empty array\", ArrayNode(\"\", []*Node{BoolNode(\"0\", true)}), false},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := tt.node.Empty(); got != tt.expected {\n\t\t\t\tt.Errorf(\"%s = %v, want %v\", tt.name, got, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_Index_EmptyList(t *testing.T) {\n\troot, err := Unmarshal([]byte(`[]`))\n\tif err != nil {\n\t\tt.Errorf(\"error occurred while unmarshal\")\n\t}\n\n\tarray := root.MustArray()\n\tfor i, node := range array {\n\t\tif i != node.Index() {\n\t\t\tt.Errorf(ufmt.Sprintf(\"Index() must be nil. got: %v\", i))\n\t\t}\n\t}\n}\n\nfunc TestNode_GetObject(t *testing.T) {\n\troot, err := Unmarshal([]byte(`{\"foo\": true,\"bar\": null}`))\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err.Error())\n\t\treturn\n\t}\n\n\tvalue, err := root.GetObject()\n\tif err != nil {\n\t\tt.Errorf(\"Error on root.GetObject(): %s\", err.Error())\n\t}\n\n\tif _, ok := value[\"foo\"]; !ok {\n\t\tt.Errorf(\"root.GetObject() is corrupted: foo\")\n\t}\n\n\tif _, ok := value[\"bar\"]; !ok {\n\t\tt.Errorf(\"root.GetObject() is corrupted: bar\")\n\t}\n}\n\nfunc TestNode_GetObject_Fail(t *testing.T) {\n\ttests := []simpleNode{\n\t\t{\"nil node\", (*Node)(nil)},\n\t\t{\"get object from null node\", NullNode(\"\")},\n\t\t{\"not object node\", NumberNode(\"\", 123)},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif _, err := tt.node.GetObject(); err == nil {\n\t\t\t\tt.Errorf(\"%s should be an error\", tt.name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_ObjectEach(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tjson string\n\t\texpected map[string]int\n\t}{\n\t\t{\n\t\t\tname: \"empty object\",\n\t\t\tjson: `{}`,\n\t\t\texpected: make(map[string]int),\n\t\t},\n\t\t{\n\t\t\tname: \"single key-value pair\",\n\t\t\tjson: `{\"key\": 42}`,\n\t\t\texpected: map[string]int{\"key\": 42},\n\t\t},\n\t\t{\n\t\t\tname: \"multiple key-value pairs\",\n\t\t\tjson: `{\"one\": 1, \"two\": 2, \"three\": 3}`,\n\t\t\texpected: map[string]int{\"one\": 1, \"two\": 2, \"three\": 3},\n\t\t},\n\t\t{\n\t\t\tname: \"multiple key-value pairs with some non-numeric values\",\n\t\t\tjson: `{\"one\": 1, \"two\": \"2\", \"three\": 3, \"four\": \"4\"}`,\n\t\t\texpected: map[string]int{\"one\": 1, \"three\": 3},\n\t\t},\n\t\t{\n\t\t\tname: \"non-object node\",\n\t\t\tjson: `[\"not\", \"an\", \"object\"]`,\n\t\t\texpected: make(map[string]int),\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\troot, err := Unmarshal([]byte(tc.json))\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Unmarshal failed: %v\", err)\n\t\t\t}\n\n\t\t\tresult := make(map[string]int)\n\t\t\troot.ObjectEach(func(key string, value *Node) {\n\t\t\t\t// extract integer values from the object\n\t\t\t\tif val, err := strconv.Atoi(value.String()); err == nil {\n\t\t\t\t\tresult[key] = val\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tif len(result) != len(tc.expected) {\n\t\t\t\tt.Errorf(\"%s: expected %d key-value pairs, got %d\", tc.name, len(tc.expected), len(result))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfor key, val := range tc.expected {\n\t\t\t\tif result[key] != val {\n\t\t\t\t\tt.Errorf(\"%s: expected value for key %s to be %d, got %d\", tc.name, key, val, result[key])\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_ExampleMust(t *testing.T) {\n\tdata := []byte(`{\n \"Image\": {\n \"Width\": 800,\n \"Height\": 600,\n \"Title\": \"View from 15th Floor\",\n \"Thumbnail\": {\n \"Url\": \"http://www.example.com/image/481989943\",\n \"Height\": 125,\n \"Width\": 100\n },\n \"Animated\" : false,\n \"IDs\": [116, 943, 234, 38793]\n }\n }`)\n\n\troot := Must(Unmarshal(data))\n\tif root.Size() != 1 {\n\t\tt.Errorf(\"root.Size() must be 1. got: %v\", root.Size())\n\t}\n\n\tufmt.Sprintf(\"Object has %d inheritors inside\", root.Size())\n\t// Output:\n\t// Object has 1 inheritors inside\n}\n\n// Calculate AVG price from different types of objects, JSON from: https://goessner.net/articles/JsonPath/index.html#e3\nfunc TestExampleUnmarshal(t *testing.T) {\n\tdata := []byte(`{ \"store\": {\n \"book\": [ \n { \"category\": \"reference\",\n \"author\": \"Nigel Rees\",\n \"title\": \"Sayings of the Century\",\n \"price\": 8.95\n },\n { \"category\": \"fiction\",\n \"author\": \"Evelyn Waugh\",\n \"title\": \"Sword of Honour\",\n \"price\": 12.99\n },\n { \"category\": \"fiction\",\n \"author\": \"Herman Melville\",\n \"title\": \"Moby Dick\",\n \"isbn\": \"0-553-21311-3\",\n \"price\": 8.99\n },\n { \"category\": \"fiction\",\n \"author\": \"J. R. R. Tolkien\",\n \"title\": \"The Lord of the Rings\",\n \"isbn\": \"0-395-19395-8\",\n \"price\": 22.99\n }\n ],\n \"bicycle\": { \"color\": \"red\",\n \"price\": 19.95\n },\n \"tools\": null\n }\n}`)\n\n\troot, err := Unmarshal(data)\n\tif err != nil {\n\t\tt.Errorf(\"error occurred when unmarshal\")\n\t}\n\n\tstore := root.MustKey(\"store\").MustObject()\n\n\tvar prices float64\n\tsize := 0\n\tfor _, objects := range store {\n\t\tif objects.IsArray() \u0026\u0026 objects.Size() \u003e 0 {\n\t\t\tsize += objects.Size()\n\t\t\tfor _, object := range objects.MustArray() {\n\t\t\t\tprices += object.MustKey(\"price\").MustNumeric()\n\t\t\t}\n\t\t} else if objects.IsObject() \u0026\u0026 objects.HasKey(\"price\") {\n\t\t\tsize++\n\t\t\tprices += objects.MustKey(\"price\").MustNumeric()\n\t\t}\n\t}\n\n\tresult := int(prices / float64(size))\n\tufmt.Sprintf(\"AVG price: %d\", result)\n}\n\nfunc TestNode_ExampleMust_panic(t *testing.T) {\n\tdefer func() {\n\t\tif r := recover(); r == nil {\n\t\t\tt.Errorf(\"The code did not panic\")\n\t\t}\n\t}()\n\tdata := []byte(`{]`)\n\troot := Must(Unmarshal(data))\n\tufmt.Sprintf(\"Object has %d inheritors inside\", root.Size())\n}\n\nfunc TestNode_Path(t *testing.T) {\n\tdata := []byte(`{\n \"Image\": {\n \"Width\": 800,\n \"Height\": 600,\n \"Title\": \"View from 15th Floor\",\n \"Thumbnail\": {\n \"Url\": \"http://www.example.com/image/481989943\",\n \"Height\": 125,\n \"Width\": 100\n },\n \"Animated\" : false,\n \"IDs\": [116, 943, 234, 38793]\n }\n }`)\n\n\troot, err := Unmarshal(data)\n\tif err != nil {\n\t\tt.Errorf(\"Error on Unmarshal(): %s\", err.Error())\n\t\treturn\n\t}\n\n\tif root.Path() != \"$\" {\n\t\tt.Errorf(\"Wrong root.Path()\")\n\t}\n\n\telement := root.MustKey(\"Image\").MustKey(\"Thumbnail\").MustKey(\"Url\")\n\tif element.Path() != \"$['Image']['Thumbnail']['Url']\" {\n\t\tt.Errorf(\"Wrong path found: %s\", element.Path())\n\t}\n\n\tif (*Node)(nil).Path() != \"\" {\n\t\tt.Errorf(\"Wrong (nil).Path()\")\n\t}\n}\n\nfunc TestNode_Path2(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tnode *Node\n\t\twant string\n\t}{\n\t\t{\n\t\t\tname: \"Node with key\",\n\t\t\tnode: \u0026Node{\n\t\t\t\tprev: \u0026Node{},\n\t\t\t\tkey: func() *string { s := \"key\"; return \u0026s }(),\n\t\t\t},\n\t\t\twant: \"$['key']\",\n\t\t},\n\t\t{\n\t\t\tname: \"Node with index\",\n\t\t\tnode: \u0026Node{\n\t\t\t\tprev: \u0026Node{},\n\t\t\t\tindex: func() *int { i := 1; return \u0026i }(),\n\t\t\t},\n\t\t\twant: \"$[1]\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := tt.node.Path(); got != tt.want {\n\t\t\t\tt.Errorf(\"Path() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNode_Root(t *testing.T) {\n\troot := \u0026Node{}\n\tchild := \u0026Node{prev: root}\n\tgrandChild := \u0026Node{prev: child}\n\n\ttests := []struct {\n\t\tname string\n\t\tnode *Node\n\t\twant *Node\n\t}{\n\t\t{\n\t\t\tname: \"Root node\",\n\t\t\tnode: root,\n\t\t\twant: root,\n\t\t},\n\t\t{\n\t\t\tname: \"Child node\",\n\t\t\tnode: child,\n\t\t\twant: root,\n\t\t},\n\t\t{\n\t\t\tname: \"Grandchild node\",\n\t\t\tnode: grandChild,\n\t\t\twant: root,\n\t\t},\n\t\t{\n\t\t\tname: \"Node is nil\",\n\t\t\tnode: nil,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := tt.node.root(); got != tt.want {\n\t\t\t\tt.Errorf(\"root() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc contains(slice []string, item string) bool {\n\tfor _, a := range slice {\n\t\tif a == item {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n// ignore the sequence of keys by ordering them.\n// need to avoid import encoding/json and reflect package.\n// because gno does not support them for now.\n// TODO: use encoding/json to compare the result after if possible in gno.\nfunc isSameObject(a, b string) bool {\n\taPairs := strings.Split(strings.Trim(a, \"{}\"), \",\")\n\tbPairs := strings.Split(strings.Trim(b, \"{}\"), \",\")\n\n\taMap := make(map[string]string)\n\tbMap := make(map[string]string)\n\tfor _, pair := range aPairs {\n\t\tkv := strings.Split(pair, \":\")\n\t\tkey := strings.Trim(kv[0], `\"`)\n\t\tvalue := strings.Trim(kv[1], `\"`)\n\t\taMap[key] = value\n\t}\n\tfor _, pair := range bPairs {\n\t\tkv := strings.Split(pair, \":\")\n\t\tkey := strings.Trim(kv[0], `\"`)\n\t\tvalue := strings.Trim(kv[1], `\"`)\n\t\tbMap[key] = value\n\t}\n\n\taKeys := make([]string, 0, len(aMap))\n\tbKeys := make([]string, 0, len(bMap))\n\tfor k := range aMap {\n\t\taKeys = append(aKeys, k)\n\t}\n\n\tfor k := range bMap {\n\t\tbKeys = append(bKeys, k)\n\t}\n\n\tsort.Strings(aKeys)\n\tsort.Strings(bKeys)\n\n\tif len(aKeys) != len(bKeys) {\n\t\treturn false\n\t}\n\n\tfor i := range aKeys {\n\t\tif aKeys[i] != bKeys[i] {\n\t\t\treturn false\n\t\t}\n\n\t\tif aMap[aKeys[i]] != bMap[bKeys[i]] {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc compareNodes(n1, n2 *Node) bool {\n\tif n1 == nil || n2 == nil {\n\t\treturn n1 == n2\n\t}\n\n\tif n1.key != n2.key {\n\t\treturn false\n\t}\n\n\tif !bytes.Equal(n1.data, n2.data) {\n\t\treturn false\n\t}\n\n\tif n1.index != n2.index {\n\t\treturn false\n\t}\n\n\tif n1.borders != n2.borders {\n\t\treturn false\n\t}\n\n\tif n1.modified != n2.modified {\n\t\treturn false\n\t}\n\n\tif n1.nodeType != n2.nodeType {\n\t\treturn false\n\t}\n\n\tif !compareNodes(n1.prev, n2.prev) {\n\t\treturn false\n\t}\n\n\tif len(n1.next) != len(n2.next) {\n\t\treturn false\n\t}\n\n\tfor k, v := range n1.next {\n\t\tif !compareNodes(v, n2.next[k]) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n"},{"name":"parser.gno","body":"package json\n\nimport (\n\t\"bytes\"\n)\n\nconst (\n\tunescapeStackBufSize = 64\n\tabsMinInt64 = 1 \u003c\u003c 63\n\tmaxInt64 = absMinInt64 - 1\n\tmaxUint64 = 1\u003c\u003c64 - 1\n)\n\n// PaseStringLiteral parses a string from the given byte slice.\nfunc ParseStringLiteral(data []byte) (string, error) {\n\tvar buf [unescapeStackBufSize]byte\n\n\tbf, err := Unescape(data, buf[:])\n\tif err != nil {\n\t\treturn \"\", errInvalidStringInput\n\t}\n\n\treturn string(bf), nil\n}\n\n// ParseBoolLiteral parses a boolean value from the given byte slice.\nfunc ParseBoolLiteral(data []byte) (bool, error) {\n\tswitch {\n\tcase bytes.Equal(data, trueLiteral):\n\t\treturn true, nil\n\tcase bytes.Equal(data, falseLiteral):\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, errMalformedBooleanValue\n\t}\n}\n"},{"name":"parser_test.gno","body":"package json\n\nimport \"testing\"\n\nfunc TestParseStringLiteral(t *testing.T) {\n\ttests := []struct {\n\t\tinput string\n\t\texpected string\n\t\tisError bool\n\t}{\n\t\t{`\"Hello, World!\"`, \"\\\"Hello, World!\\\"\", false},\n\t\t{`\\uFF11`, \"\\uFF11\", false},\n\t\t{`\\uFFFF`, \"\\uFFFF\", false},\n\t\t{`true`, \"true\", false},\n\t\t{`false`, \"false\", false},\n\t\t{`\\uDF00`, \"\", true},\n\t}\n\n\tfor i, tt := range tests {\n\t\ts, err := ParseStringLiteral([]byte(tt.input))\n\n\t\tif !tt.isError \u0026\u0026 err != nil {\n\t\t\tt.Errorf(\"%d. unexpected error: %s\", i, err)\n\t\t}\n\n\t\tif tt.isError \u0026\u0026 err == nil {\n\t\t\tt.Errorf(\"%d. expected error, but not error\", i)\n\t\t}\n\n\t\tif s != tt.expected {\n\t\t\tt.Errorf(\"%d. expected=%s, but actual=%s\", i, tt.expected, s)\n\t\t}\n\t}\n}\n\nfunc TestParseBoolLiteral(t *testing.T) {\n\ttests := []struct {\n\t\tinput string\n\t\texpected bool\n\t\tisError bool\n\t}{\n\t\t{`true`, true, false},\n\t\t{`false`, false, false},\n\t\t{`TRUE`, false, true},\n\t\t{`FALSE`, false, true},\n\t\t{`foo`, false, true},\n\t\t{`\"true\"`, false, true},\n\t\t{`\"false\"`, false, true},\n\t}\n\n\tfor i, tt := range tests {\n\t\tb, err := ParseBoolLiteral([]byte(tt.input))\n\n\t\tif !tt.isError \u0026\u0026 err != nil {\n\t\t\tt.Errorf(\"%d. unexpected error: %s\", i, err)\n\t\t}\n\n\t\tif tt.isError \u0026\u0026 err == nil {\n\t\t\tt.Errorf(\"%d. expected error, but not error\", i)\n\t\t}\n\n\t\tif b != tt.expected {\n\t\t\tt.Errorf(\"%d. expected=%t, but actual=%t\", i, tt.expected, b)\n\t\t}\n\t}\n}\n"},{"name":"path.gno","body":"package json\n\nimport (\n\t\"errors\"\n)\n\n// ParsePath takes a JSONPath string and returns a slice of strings representing the path segments.\nfunc ParsePath(path string) ([]string, error) {\n\tbuf := newBuffer([]byte(path))\n\tresult := make([]string, 0)\n\n\tfor {\n\t\tb, err := buf.current()\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\n\t\tswitch {\n\t\tcase b == dollarSign || b == atSign:\n\t\t\tresult = append(result, string(b))\n\t\t\tbuf.step()\n\n\t\tcase b == dot:\n\t\t\tbuf.step()\n\n\t\t\tif next, _ := buf.current(); next == dot {\n\t\t\t\tbuf.step()\n\t\t\t\tresult = append(result, \"..\")\n\n\t\t\t\textractNextSegment(buf, \u0026result)\n\t\t\t} else {\n\t\t\t\textractNextSegment(buf, \u0026result)\n\t\t\t}\n\n\t\tcase b == bracketOpen:\n\t\t\tstart := buf.index\n\t\t\tbuf.step()\n\n\t\t\tfor {\n\t\t\t\tif buf.index \u003e= buf.length || buf.data[buf.index] == bracketClose {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tbuf.step()\n\t\t\t}\n\n\t\t\tif buf.index \u003e= buf.length {\n\t\t\t\treturn nil, errors.New(\"unexpected end of path\")\n\t\t\t}\n\n\t\t\tsegment := string(buf.sliceFromIndices(start+1, buf.index))\n\t\t\tresult = append(result, segment)\n\n\t\t\tbuf.step()\n\n\t\tdefault:\n\t\t\tbuf.step()\n\t\t}\n\t}\n\n\treturn result, nil\n}\n\n// extractNextSegment extracts the segment from the current index\n// to the next significant character and adds it to the resulting slice.\nfunc extractNextSegment(buf *buffer, result *[]string) {\n\tstart := buf.index\n\tbuf.skipToNextSignificantToken()\n\n\tif buf.index \u003c= start {\n\t\treturn\n\t}\n\n\tsegment := string(buf.sliceFromIndices(start, buf.index))\n\tif segment != \"\" {\n\t\t*result = append(*result, segment)\n\t}\n}\n"},{"name":"path_test.gno","body":"package json\n\nimport \"testing\"\n\nfunc TestParseJSONPath(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tpath string\n\t\texpected []string\n\t}{\n\t\t{name: \"Empty string path\", path: \"\", expected: []string{}},\n\t\t{name: \"Root only path\", path: \"$\", expected: []string{\"$\"}},\n\t\t{name: \"Root with dot path\", path: \"$.\", expected: []string{\"$\"}},\n\t\t{name: \"All objects in path\", path: \"$..\", expected: []string{\"$\", \"..\"}},\n\t\t{name: \"Only children in path\", path: \"$.*\", expected: []string{\"$\", \"*\"}},\n\t\t{name: \"All objects' children in path\", path: \"$..*\", expected: []string{\"$\", \"..\", \"*\"}},\n\t\t{name: \"Simple dot notation path\", path: \"$.root.element\", expected: []string{\"$\", \"root\", \"element\"}},\n\t\t{name: \"Complex dot notation path with wildcard\", path: \"$.root.*.element\", expected: []string{\"$\", \"root\", \"*\", \"element\"}},\n\t\t{name: \"Path with array wildcard\", path: \"$.phoneNumbers[*].type\", expected: []string{\"$\", \"phoneNumbers\", \"*\", \"type\"}},\n\t\t{name: \"Path with filter expression\", path: \"$.store.book[?(@.price \u003c 10)].title\", expected: []string{\"$\", \"store\", \"book\", \"?(@.price \u003c 10)\", \"title\"}},\n\t\t{name: \"Path with formula\", path: \"$..phoneNumbers..('ty' + 'pe')\", expected: []string{\"$\", \"..\", \"phoneNumbers\", \"..\", \"('ty' + 'pe')\"}},\n\t\t{name: \"Simple bracket notation path\", path: \"$['root']['element']\", expected: []string{\"$\", \"'root'\", \"'element'\"}},\n\t\t{name: \"Complex bracket notation path with wildcard\", path: \"$['root'][*]['element']\", expected: []string{\"$\", \"'root'\", \"*\", \"'element'\"}},\n\t\t{name: \"Bracket notation path with integer index\", path: \"$['store']['book'][0]['title']\", expected: []string{\"$\", \"'store'\", \"'book'\", \"0\", \"'title'\"}},\n\t\t{name: \"Complex path with wildcard in bracket notation\", path: \"$['root'].*['element']\", expected: []string{\"$\", \"'root'\", \"*\", \"'element'\"}},\n\t\t{name: \"Mixed notation path with dot after bracket\", path: \"$.['root'].*.['element']\", expected: []string{\"$\", \"'root'\", \"*\", \"'element'\"}},\n\t\t{name: \"Mixed notation path with dot before bracket\", path: \"$['root'].*.['element']\", expected: []string{\"$\", \"'root'\", \"*\", \"'element'\"}},\n\t\t{name: \"Single character path with root\", path: \"$.a\", expected: []string{\"$\", \"a\"}},\n\t\t{name: \"Multiple characters path with root\", path: \"$.abc\", expected: []string{\"$\", \"abc\"}},\n\t\t{name: \"Multiple segments path with root\", path: \"$.a.b.c\", expected: []string{\"$\", \"a\", \"b\", \"c\"}},\n\t\t{name: \"Multiple segments path with wildcard and root\", path: \"$.a.*.c\", expected: []string{\"$\", \"a\", \"*\", \"c\"}},\n\t\t{name: \"Multiple segments path with filter and root\", path: \"$.a[?(@.b == 'c')].d\", expected: []string{\"$\", \"a\", \"?(@.b == 'c')\", \"d\"}},\n\t\t{name: \"Complex path with multiple filters\", path: \"$.a[?(@.b == 'c')].d[?(@.e == 'f')].g\", expected: []string{\"$\", \"a\", \"?(@.b == 'c')\", \"d\", \"?(@.e == 'f')\", \"g\"}},\n\t\t{name: \"Complex path with multiple filters and wildcards\", path: \"$.a[?(@.b == 'c')].*.d[?(@.e == 'f')].g\", expected: []string{\"$\", \"a\", \"?(@.b == 'c')\", \"*\", \"d\", \"?(@.e == 'f')\", \"g\"}},\n\t\t{name: \"Path with array index and root\", path: \"$.a[0].b\", expected: []string{\"$\", \"a\", \"0\", \"b\"}},\n\t\t{name: \"Path with multiple array indices and root\", path: \"$.a[0].b[1].c\", expected: []string{\"$\", \"a\", \"0\", \"b\", \"1\", \"c\"}},\n\t\t{name: \"Path with array index, wildcard and root\", path: \"$.a[0].*.c\", expected: []string{\"$\", \"a\", \"0\", \"*\", \"c\"}},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\treult, _ := ParsePath(tt.path)\n\t\t\tif !isEqualSlice(reult, tt.expected) {\n\t\t\t\tt.Errorf(\"ParsePath(%s) expected: %v, got: %v\", tt.path, tt.expected, reult)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc isEqualSlice(a, b []string) bool {\n\tif len(a) != len(b) {\n\t\treturn false\n\t}\n\n\tfor i, v := range a {\n\t\tif v != b[i] {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n"},{"name":"token.gno","body":"package json\n\nconst (\n\tbracketOpen = '['\n\tbracketClose = ']'\n\tparenOpen = '('\n\tparenClose = ')'\n\tcurlyOpen = '{'\n\tcurlyClose = '}'\n\tcomma = ','\n\tdot = '.'\n\tcolon = ':'\n\tbackTick = '`'\n\tsingleQuote = '\\''\n\tdoubleQuote = '\"'\n\temptyString = \"\"\n\twhiteSpace = ' '\n\tplus = '+'\n\tminus = '-'\n\taesterisk = '*'\n\tbang = '!'\n\tquestion = '?'\n\tnewLine = '\\n'\n\ttab = '\\t'\n\tcarriageReturn = '\\r'\n\tformFeed = '\\f'\n\tbackSpace = '\\b'\n\tslash = '/'\n\tbackSlash = '\\\\'\n\tunderScore = '_'\n\tdollarSign = '$'\n\tatSign = '@'\n\tandSign = '\u0026'\n\torSign = '|'\n)\n\nvar (\n\ttrueLiteral = []byte(\"true\")\n\tfalseLiteral = []byte(\"false\")\n\tnullLiteral = []byte(\"null\")\n)\n\ntype ValueType int\n\nconst (\n\tNotExist ValueType = iota\n\tString\n\tNumber\n\tFloat\n\tObject\n\tArray\n\tBoolean\n\tNull\n\tUnknown\n)\n\nfunc (v ValueType) String() string {\n\tswitch v {\n\tcase NotExist:\n\t\treturn \"not-exist\"\n\tcase String:\n\t\treturn \"string\"\n\tcase Number:\n\t\treturn \"number\"\n\tcase Object:\n\t\treturn \"object\"\n\tcase Array:\n\t\treturn \"array\"\n\tcase Boolean:\n\t\treturn \"boolean\"\n\tcase Null:\n\t\treturn \"null\"\n\tdefault:\n\t\treturn \"unknown\"\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":"int32","path":"gno.land/p/demo/math_eval/int32","files":[{"name":"int32.gno","body":"// eval/int32 is a evaluator for int32 expressions.\n// This code is heavily forked from https://github.com/dengsgo/math-engine\n// which is licensed under Apache 2.0:\n// https://raw.githubusercontent.com/dengsgo/math-engine/298e2b57b7e7350d0f67bd036916efd5709abe25/LICENSE\npackage int32\n\nimport (\n\t\"errors\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\nconst (\n\tIdentifier = iota\n\tNumber // numbers\n\tOperator // +, -, *, /, etc.\n\tVariable // x, y, z, etc. (one-letter only)\n)\n\ntype expression interface {\n\tString() string\n}\n\ntype expressionRaw struct {\n\texpression string\n\tType int\n\tFlag int\n\tOffset int\n}\n\ntype parser struct {\n\tInput string\n\tch byte\n\toffset int\n\terr error\n}\n\ntype expressionNumber struct {\n\tVal int\n\tStr string\n}\n\ntype expressionVariable struct {\n\tVal int\n\tStr string\n}\n\ntype expressionOperation struct {\n\tOp string\n\tLhs,\n\tRhs expression\n}\n\ntype ast struct {\n\trawexpressions []*expressionRaw\n\tsource string\n\tcurrentexpression *expressionRaw\n\tcurrentIndex int\n\tdepth int\n\terr error\n}\n\n// Parse takes an expression string, e.g. \"1+2\" and returns\n// a parsed expression. If there is an error it will return.\nfunc Parse(s string) (ar expression, err error) {\n\ttoks, err := lexer(s)\n\tif err != nil {\n\t\treturn\n\t}\n\tast, err := newAST(toks, s)\n\tif err != nil {\n\t\treturn\n\t}\n\tar, err = ast.parseExpression()\n\treturn\n}\n\n// Eval takes a parsed expression and a map of variables (or nil). The parsed\n// expression is evaluated using any variables and returns the\n// resulting int and/or error.\nfunc Eval(expr expression, variables map[string]int) (res int, err error) {\n\tif err != nil {\n\t\treturn\n\t}\n\tvar l, r int\n\tswitch expr.(type) {\n\tcase expressionVariable:\n\t\tast := expr.(expressionVariable)\n\t\tok := false\n\t\tif variables != nil {\n\t\t\tres, ok = variables[ast.Str]\n\t\t}\n\t\tif !ok {\n\t\t\terr = ufmt.Errorf(\"variable '%s' not found\", ast.Str)\n\t\t}\n\t\treturn\n\tcase expressionOperation:\n\t\tast := expr.(expressionOperation)\n\t\tl, err = Eval(ast.Lhs, variables)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tr, err = Eval(ast.Rhs, variables)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tswitch ast.Op {\n\t\tcase \"+\":\n\t\t\tres = l + r\n\t\tcase \"-\":\n\t\t\tres = l - r\n\t\tcase \"*\":\n\t\t\tres = l * r\n\t\tcase \"/\":\n\t\t\tif r == 0 {\n\t\t\t\terr = ufmt.Errorf(\"violation of arithmetic specification: a division by zero in Eval: [%d/%d]\", l, r)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tres = l / r\n\t\tcase \"%\":\n\t\t\tif r == 0 {\n\t\t\t\tres = 0\n\t\t\t} else {\n\t\t\t\tres = l % r\n\t\t\t}\n\t\tcase \"^\":\n\t\t\tres = l ^ r\n\t\tcase \"\u003e\u003e\":\n\t\t\tres = l \u003e\u003e r\n\t\tcase \"\u003c\u003c\":\n\t\t\tres = l \u003c\u003c r\n\t\tcase \"\u003e\":\n\t\t\tif l \u003e r {\n\t\t\t\tres = 1\n\t\t\t} else {\n\t\t\t\tres = 0\n\t\t\t}\n\t\tcase \"\u003c\":\n\t\t\tif l \u003c r {\n\t\t\t\tres = 1\n\t\t\t} else {\n\t\t\t\tres = 0\n\t\t\t}\n\t\tcase \"\u0026\":\n\t\t\tres = l \u0026 r\n\t\tcase \"|\":\n\t\t\tres = l | r\n\t\tdefault:\n\n\t\t}\n\tcase expressionNumber:\n\t\tres = expr.(expressionNumber).Val\n\t}\n\n\treturn\n}\n\nfunc expressionError(s string, pos int) string {\n\tr := strings.Repeat(\"-\", len(s)) + \"\\n\"\n\ts += \"\\n\"\n\tfor i := 0; i \u003c pos; i++ {\n\t\ts += \" \"\n\t}\n\ts += \"^\\n\"\n\treturn r + s + r\n}\n\nfunc (n expressionVariable) String() string {\n\treturn ufmt.Sprintf(\n\t\t\"expressionVariable: %s\",\n\t\tn.Str,\n\t)\n}\n\nfunc (n expressionNumber) String() string {\n\treturn ufmt.Sprintf(\n\t\t\"expressionNumber: %s\",\n\t\tn.Str,\n\t)\n}\n\nfunc (b expressionOperation) String() string {\n\treturn ufmt.Sprintf(\n\t\t\"expressionOperation: (%s %s %s)\",\n\t\tb.Op,\n\t\tb.Lhs.String(),\n\t\tb.Rhs.String(),\n\t)\n}\n\nfunc newAST(toks []*expressionRaw, s string) (*ast, error) {\n\ta := \u0026ast{\n\t\trawexpressions: toks,\n\t\tsource: s,\n\t}\n\tif a.rawexpressions == nil || len(a.rawexpressions) == 0 {\n\t\treturn a, errors.New(\"empty token\")\n\t} else {\n\t\ta.currentIndex = 0\n\t\ta.currentexpression = a.rawexpressions[0]\n\t}\n\treturn a, nil\n}\n\nfunc (a *ast) parseExpression() (expression, error) {\n\ta.depth++ // called depth\n\tlhs := a.parsePrimary()\n\tr := a.parseBinOpRHS(0, lhs)\n\ta.depth--\n\tif a.depth == 0 \u0026\u0026 a.currentIndex != len(a.rawexpressions) \u0026\u0026 a.err == nil {\n\t\treturn r, ufmt.Errorf(\"bad expression, reaching the end or missing the operator\\n%s\",\n\t\t\texpressionError(a.source, a.currentexpression.Offset))\n\t}\n\treturn r, nil\n}\n\nfunc (a *ast) getNextexpressionRaw() *expressionRaw {\n\ta.currentIndex++\n\tif a.currentIndex \u003c len(a.rawexpressions) {\n\t\ta.currentexpression = a.rawexpressions[a.currentIndex]\n\t\treturn a.currentexpression\n\t}\n\treturn nil\n}\n\nfunc (a *ast) getTokPrecedence() int {\n\tswitch a.currentexpression.expression {\n\tcase \"/\", \"%\", \"*\":\n\t\treturn 100\n\tcase \"\u003c\u003c\", \"\u003e\u003e\":\n\t\treturn 80\n\tcase \"+\", \"-\":\n\t\treturn 75\n\tcase \"\u003c\", \"\u003e\":\n\t\treturn 70\n\tcase \"\u0026\":\n\t\treturn 60\n\tcase \"^\":\n\t\treturn 50\n\tcase \"|\":\n\t\treturn 40\n\t}\n\treturn -1\n}\n\nfunc (a *ast) parseNumber() expressionNumber {\n\tf64, err := strconv.Atoi(a.currentexpression.expression)\n\tif err != nil {\n\t\ta.err = ufmt.Errorf(\"%v\\nwant '(' or '0-9' but get '%s'\\n%s\",\n\t\t\terr.Error(),\n\t\t\ta.currentexpression.expression,\n\t\t\texpressionError(a.source, a.currentexpression.Offset))\n\t\treturn expressionNumber{}\n\t}\n\tn := expressionNumber{\n\t\tVal: f64,\n\t\tStr: a.currentexpression.expression,\n\t}\n\ta.getNextexpressionRaw()\n\treturn n\n}\n\nfunc (a *ast) parseVariable() expressionVariable {\n\tn := expressionVariable{\n\t\tVal: 0,\n\t\tStr: a.currentexpression.expression,\n\t}\n\ta.getNextexpressionRaw()\n\treturn n\n}\n\nfunc (a *ast) parsePrimary() expression {\n\tswitch a.currentexpression.Type {\n\tcase Variable:\n\t\treturn a.parseVariable()\n\tcase Number:\n\t\treturn a.parseNumber()\n\tcase Operator:\n\t\tif a.currentexpression.expression == \"(\" {\n\t\t\tt := a.getNextexpressionRaw()\n\t\t\tif t == nil {\n\t\t\t\ta.err = ufmt.Errorf(\"want '(' or '0-9' but get EOF\\n%s\",\n\t\t\t\t\texpressionError(a.source, a.currentexpression.Offset))\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\te, _ := a.parseExpression()\n\t\t\tif e == nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tif a.currentexpression.expression != \")\" {\n\t\t\t\ta.err = ufmt.Errorf(\"want ')' but get %s\\n%s\",\n\t\t\t\t\ta.currentexpression.expression,\n\t\t\t\t\texpressionError(a.source, a.currentexpression.Offset))\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\ta.getNextexpressionRaw()\n\t\t\treturn e\n\t\t} else if a.currentexpression.expression == \"-\" {\n\t\t\tif a.getNextexpressionRaw() == nil {\n\t\t\t\ta.err = ufmt.Errorf(\"want '0-9' but get '-'\\n%s\",\n\t\t\t\t\texpressionError(a.source, a.currentexpression.Offset))\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tbin := expressionOperation{\n\t\t\t\tOp: \"-\",\n\t\t\t\tLhs: expressionNumber{},\n\t\t\t\tRhs: a.parsePrimary(),\n\t\t\t}\n\t\t\treturn bin\n\t\t} else {\n\t\t\treturn a.parseNumber()\n\t\t}\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc (a *ast) parseBinOpRHS(execPrec int, lhs expression) expression {\n\tfor {\n\t\ttokPrec := a.getTokPrecedence()\n\t\tif tokPrec \u003c execPrec {\n\t\t\treturn lhs\n\t\t}\n\t\tbinOp := a.currentexpression.expression\n\t\tif a.getNextexpressionRaw() == nil {\n\t\t\ta.err = ufmt.Errorf(\"want '(' or '0-9' but get EOF\\n%s\",\n\t\t\t\texpressionError(a.source, a.currentexpression.Offset))\n\t\t\treturn nil\n\t\t}\n\t\trhs := a.parsePrimary()\n\t\tif rhs == nil {\n\t\t\treturn nil\n\t\t}\n\t\tnextPrec := a.getTokPrecedence()\n\t\tif tokPrec \u003c nextPrec {\n\t\t\trhs = a.parseBinOpRHS(tokPrec+1, rhs)\n\t\t\tif rhs == nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tlhs = expressionOperation{\n\t\t\tOp: binOp,\n\t\t\tLhs: lhs,\n\t\t\tRhs: rhs,\n\t\t}\n\t}\n}\n\nfunc lexer(s string) ([]*expressionRaw, error) {\n\tp := \u0026parser{\n\t\tInput: s,\n\t\terr: nil,\n\t\tch: s[0],\n\t}\n\ttoks := p.parse()\n\tif p.err != nil {\n\t\treturn nil, p.err\n\t}\n\treturn toks, nil\n}\n\nfunc (p *parser) parse() []*expressionRaw {\n\ttoks := make([]*expressionRaw, 0)\n\tfor {\n\t\ttok := p.nextTok()\n\t\tif tok == nil {\n\t\t\tbreak\n\t\t}\n\t\ttoks = append(toks, tok)\n\t}\n\treturn toks\n}\n\nfunc (p *parser) nextTok() *expressionRaw {\n\tif p.offset \u003e= len(p.Input) || p.err != nil {\n\t\treturn nil\n\t}\n\tvar err error\n\tfor p.isWhitespace(p.ch) \u0026\u0026 err == nil {\n\t\terr = p.nextCh()\n\t}\n\tstart := p.offset\n\tvar tok *expressionRaw\n\tswitch p.ch {\n\tcase\n\t\t'(',\n\t\t')',\n\t\t'+',\n\t\t'-',\n\t\t'*',\n\t\t'/',\n\t\t'^',\n\t\t'\u0026',\n\t\t'|',\n\t\t'%':\n\t\ttok = \u0026expressionRaw{\n\t\t\texpression: string(p.ch),\n\t\t\tType: Operator,\n\t\t}\n\t\ttok.Offset = start\n\t\terr = p.nextCh()\n\tcase '\u003e', '\u003c':\n\t\ttokS := string(p.ch)\n\t\tbb, be := p.nextChPeek()\n\t\tif be == nil \u0026\u0026 string(bb) == tokS {\n\t\t\ttokS += string(p.ch)\n\t\t}\n\t\ttok = \u0026expressionRaw{\n\t\t\texpression: tokS,\n\t\t\tType: Operator,\n\t\t}\n\t\ttok.Offset = start\n\t\tif len(tokS) \u003e 1 {\n\t\t\tp.nextCh()\n\t\t}\n\t\terr = p.nextCh()\n\tcase\n\t\t'0',\n\t\t'1',\n\t\t'2',\n\t\t'3',\n\t\t'4',\n\t\t'5',\n\t\t'6',\n\t\t'7',\n\t\t'8',\n\t\t'9':\n\t\tfor p.isDigitNum(p.ch) \u0026\u0026 p.nextCh() == nil {\n\t\t\tif (p.ch == '-' || p.ch == '+') \u0026\u0026 p.Input[p.offset-1] != 'e' {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\ttok = \u0026expressionRaw{\n\t\t\texpression: strings.ReplaceAll(p.Input[start:p.offset], \"_\", \"\"),\n\t\t\tType: Number,\n\t\t}\n\t\ttok.Offset = start\n\tdefault:\n\t\tif p.isChar(p.ch) {\n\t\t\ttok = \u0026expressionRaw{\n\t\t\t\texpression: string(p.ch),\n\t\t\t\tType: Variable,\n\t\t\t}\n\t\t\ttok.Offset = start\n\t\t\terr = p.nextCh()\n\t\t} else if p.ch != ' ' {\n\t\t\tp.err = ufmt.Errorf(\"symbol error: unknown '%v', pos [%v:]\\n%s\",\n\t\t\t\tstring(p.ch),\n\t\t\t\tstart,\n\t\t\t\texpressionError(p.Input, start))\n\t\t}\n\t}\n\treturn tok\n}\n\nfunc (p *parser) nextChPeek() (byte, error) {\n\toffset := p.offset + 1\n\tif offset \u003c len(p.Input) {\n\t\treturn p.Input[offset], nil\n\t}\n\treturn byte(0), errors.New(\"no byte\")\n}\n\nfunc (p *parser) nextCh() error {\n\tp.offset++\n\tif p.offset \u003c len(p.Input) {\n\t\tp.ch = p.Input[p.offset]\n\t\treturn nil\n\t}\n\treturn errors.New(\"EOF\")\n}\n\nfunc (p *parser) isWhitespace(c byte) bool {\n\treturn c == ' ' ||\n\t\tc == '\\t' ||\n\t\tc == '\\n' ||\n\t\tc == '\\v' ||\n\t\tc == '\\f' ||\n\t\tc == '\\r'\n}\n\nfunc (p *parser) isDigitNum(c byte) bool {\n\treturn '0' \u003c= c \u0026\u0026 c \u003c= '9' || c == '.' || c == '_' || c == 'e' || c == '-' || c == '+'\n}\n\nfunc (p *parser) isChar(c byte) bool {\n\treturn 'a' \u003c= c \u0026\u0026 c \u003c= 'z' || 'A' \u003c= c \u0026\u0026 c \u003c= 'Z'\n}\n\nfunc (p *parser) isWordChar(c byte) bool {\n\treturn p.isChar(c) || '0' \u003c= c \u0026\u0026 c \u003c= '9'\n}\n"},{"name":"int32_test.gno","body":"package int32\n\nimport \"testing\"\n\nfunc TestOne(t *testing.T) {\n\tttt := []struct {\n\t\texp string\n\t\tres int\n\t}{\n\t\t{\"1\", 1},\n\t\t{\"--1\", 1},\n\t\t{\"1+2\", 3},\n\t\t{\"-1+2\", 1},\n\t\t{\"-(1+2)\", -3},\n\t\t{\"-(1+2)*5\", -15},\n\t\t{\"-(1+2)*5/3\", -5},\n\t\t{\"1+(-(1+2)*5/3)\", -4},\n\t\t{\"3^4\", 3 ^ 4},\n\t\t{\"8%2\", 8 % 2},\n\t\t{\"8%3\", 8 % 3},\n\t\t{\"8|3\", 8 | 3},\n\t\t{\"10%2\", 0},\n\t\t{\"(4 + 3)/2-1+11*15\", (4+3)/2 - 1 + 11*15},\n\t\t{\n\t\t\t\"(30099\u003e\u003e10^30099\u003e\u003e11)%5*((30099\u003e\u003e14\u00263^30099\u003e\u003e15\u00261)+1)*30099%99 + ((3 + (30099 \u003e\u003e 14 \u0026 3) - (30099 \u003e\u003e 16 \u0026 1)) / 3 * 30099 % 99 \u0026 64)\",\n\t\t\t(30099\u003e\u003e10^30099\u003e\u003e11)%5*((30099\u003e\u003e14\u00263^30099\u003e\u003e15\u00261)+1)*30099%99 + ((3 + (30099 \u003e\u003e 14 \u0026 3) - (30099 \u003e\u003e 16 \u0026 1)) / 3 * 30099 % 99 \u0026 64),\n\t\t},\n\t\t{\n\t\t\t\"(1023850\u003e\u003e10^1023850\u003e\u003e11)%5*((1023850\u003e\u003e14\u00263^1023850\u003e\u003e15\u00261)+1)*1023850%99 + ((3 + (1023850 \u003e\u003e 14 \u0026 3) - (1023850 \u003e\u003e 16 \u0026 1)) / 3 * 1023850 % 99 \u0026 64)\",\n\t\t\t(1023850\u003e\u003e10^1023850\u003e\u003e11)%5*((1023850\u003e\u003e14\u00263^1023850\u003e\u003e15\u00261)+1)*1023850%99 + ((3 + (1023850 \u003e\u003e 14 \u0026 3) - (1023850 \u003e\u003e 16 \u0026 1)) / 3 * 1023850 % 99 \u0026 64),\n\t\t},\n\t\t{\"((0000+1)*0000)\", 0},\n\t}\n\tfor _, tc := range ttt {\n\t\tt.Run(tc.exp, func(t *testing.T) {\n\t\t\texp, err := Parse(tc.exp)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"%s:\\n%s\", tc.exp, err.Error())\n\t\t\t} else {\n\t\t\t\tres, errEval := Eval(exp, nil)\n\t\t\t\tif errEval != nil {\n\t\t\t\t\tt.Errorf(\"eval error: %s\", errEval.Error())\n\t\t\t\t} else if res != tc.res {\n\t\t\t\t\tt.Errorf(\"%s:\\nexpected %d, got %d\", tc.exp, tc.res, res)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestVariables(t *testing.T) {\n\tfn := func(x, y int) int {\n\t\treturn 1 + ((x*3+1)*(x*2))\u003e\u003ey + 1\n\t}\n\texpr := \"1 + ((x*3+1)*(x*2))\u003e\u003ey + 1\"\n\texp, err := Parse(expr)\n\tif err != nil {\n\t\tt.Errorf(\"could not parse: %s\", err.Error())\n\t}\n\tvariables := make(map[string]int)\n\tfor i := 0; i \u003c 10; i++ {\n\t\tvariables[\"x\"] = i\n\t\tvariables[\"y\"] = 2\n\t\tres, errEval := Eval(exp, variables)\n\t\tif errEval != nil {\n\t\t\tt.Errorf(\"could not evaluate: %s\", err.Error())\n\t\t}\n\t\texpected := fn(variables[\"x\"], variables[\"y\"])\n\t\tif res != expected {\n\t\t\tt.Errorf(\"expected: %d, actual: %d\", expected, res)\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":"membstore","path":"gno.land/p/demo/membstore","files":[{"name":"members.gno","body":"package membstore\n\nimport (\n\t\"std\"\n)\n\n// MemberStore defines the member storage abstraction\ntype MemberStore interface {\n\t// Members returns all members in the store\n\tMembers(offset, count uint64) []Member\n\n\t// Size returns the current size of the store\n\tSize() int\n\n\t// IsMember returns a flag indicating if the given address\n\t// belongs to a member\n\tIsMember(address std.Address) bool\n\n\t// TotalPower returns the total voting power of the member store\n\tTotalPower() uint64\n\n\t// Member returns the requested member\n\tMember(address std.Address) (Member, error)\n\n\t// AddMember adds a member to the store\n\tAddMember(member Member) error\n\n\t// UpdateMember updates the member in the store.\n\t// If updating a member's voting power to 0,\n\t// the member will be removed\n\tUpdateMember(address std.Address, member Member) error\n}\n\n// Member holds the relevant member information\ntype Member struct {\n\tAddress std.Address // bech32 gno address of the member (unique)\n\tVotingPower uint64 // the voting power of the member\n}\n"},{"name":"membstore.gno","body":"package membstore\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar (\n\tErrAlreadyMember = errors.New(\"address is already a member\")\n\tErrMissingMember = errors.New(\"address is not a member\")\n\tErrInvalidAddressUpdate = errors.New(\"invalid address update\")\n\tErrNotGovDAO = errors.New(\"caller not correct govdao instance\")\n)\n\n// maxRequestMembers is the maximum number of\n// paginated members that can be requested\nconst maxRequestMembers = 50\n\ntype Option func(*MembStore)\n\n// WithInitialMembers initializes the member store\n// with an initial member list\nfunc WithInitialMembers(members []Member) Option {\n\treturn func(store *MembStore) {\n\t\tfor _, m := range members {\n\t\t\tmemberAddr := m.Address.String()\n\n\t\t\t// Check if the member already exists\n\t\t\tif store.members.Has(memberAddr) {\n\t\t\t\tpanic(ufmt.Errorf(\"%s, %s\", memberAddr, ErrAlreadyMember))\n\t\t\t}\n\n\t\t\tstore.members.Set(memberAddr, m)\n\t\t\tstore.totalVotingPower += m.VotingPower\n\t\t}\n\t}\n}\n\n// WithDAOPkgPath initializes the member store\n// with a dao package path guard\nfunc WithDAOPkgPath(daoPkgPath string) Option {\n\treturn func(store *MembStore) {\n\t\tstore.daoPkgPath = daoPkgPath\n\t}\n}\n\n// MembStore implements the dao.MembStore abstraction\ntype MembStore struct {\n\tdaoPkgPath string // active dao pkg path, if any\n\tmembers *avl.Tree // std.Address -\u003e Member\n\ttotalVotingPower uint64 // cached value for quick lookups\n}\n\n// NewMembStore creates a new member store\nfunc NewMembStore(opts ...Option) *MembStore {\n\tm := \u0026MembStore{\n\t\tmembers: avl.NewTree(), // empty set\n\t\tdaoPkgPath: \"\", // no dao guard\n\t\ttotalVotingPower: 0,\n\t}\n\n\t// Apply the options\n\tfor _, opt := range opts {\n\t\topt(m)\n\t}\n\n\treturn m\n}\n\n// AddMember adds member to the member store `m`.\n// It fails if the caller is not GovDAO or\n// if the member is already present\nfunc (m *MembStore) AddMember(member Member) error {\n\tif !m.isCallerDAORealm() {\n\t\treturn ErrNotGovDAO\n\t}\n\n\t// Check if the member exists\n\tif m.IsMember(member.Address) {\n\t\treturn ErrAlreadyMember\n\t}\n\n\t// Add the member\n\tm.members.Set(member.Address.String(), member)\n\n\t// Update the total voting power\n\tm.totalVotingPower += member.VotingPower\n\n\treturn nil\n}\n\n// UpdateMember updates the member with the given address.\n// Updating fails if the caller is not GovDAO.\nfunc (m *MembStore) UpdateMember(address std.Address, member Member) error {\n\tif !m.isCallerDAORealm() {\n\t\treturn ErrNotGovDAO\n\t}\n\n\t// Get the member\n\toldMember, err := m.Member(address)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Check if this is a removal request\n\tif member.VotingPower == 0 {\n\t\tm.members.Remove(address.String())\n\n\t\t// Update the total voting power\n\t\tm.totalVotingPower -= oldMember.VotingPower\n\n\t\treturn nil\n\t}\n\n\t// Check that the member wouldn't be\n\t// overwriting an existing one\n\tisAddressUpdate := address != member.Address\n\tif isAddressUpdate \u0026\u0026 m.IsMember(member.Address) {\n\t\treturn ErrInvalidAddressUpdate\n\t}\n\n\t// Remove the old member info\n\t// in case the address changed\n\tif address != member.Address {\n\t\tm.members.Remove(address.String())\n\t}\n\n\t// Save the new member info\n\tm.members.Set(member.Address.String(), member)\n\n\t// Update the total voting power\n\tdifference := member.VotingPower - oldMember.VotingPower\n\tm.totalVotingPower += difference\n\n\treturn nil\n}\n\n// IsMember returns a flag indicating if the given\n// address belongs to a member of the member store\nfunc (m *MembStore) IsMember(address std.Address) bool {\n\t_, exists := m.members.Get(address.String())\n\n\treturn exists\n}\n\n// Member returns the member associated with the given address\nfunc (m *MembStore) Member(address std.Address) (Member, error) {\n\tmember, exists := m.members.Get(address.String())\n\tif !exists {\n\t\treturn Member{}, ErrMissingMember\n\t}\n\n\treturn member.(Member), nil\n}\n\n// Members returns a paginated list of members from\n// the member store. If the store is empty, an empty slice\n// is returned instead\nfunc (m *MembStore) Members(offset, count uint64) []Member {\n\t// Calculate the left and right bounds\n\tif count \u003c 1 || offset \u003e= uint64(m.members.Size()) {\n\t\treturn []Member{}\n\t}\n\n\t// Limit the maximum number of returned members\n\tif count \u003e maxRequestMembers {\n\t\tcount = maxRequestMembers\n\t}\n\n\t// Gather the members\n\tmembers := make([]Member, 0)\n\tm.members.IterateByOffset(\n\t\tint(offset),\n\t\tint(count),\n\t\tfunc(_ string, val interface{}) bool {\n\t\t\tmember := val.(Member)\n\n\t\t\t// Save the member\n\t\t\tmembers = append(members, member)\n\n\t\t\treturn false\n\t\t})\n\n\treturn members\n}\n\n// Size returns the number of active members in the member store\nfunc (m *MembStore) Size() int {\n\treturn m.members.Size()\n}\n\n// TotalPower returns the total voting power\n// of the member store\nfunc (m *MembStore) TotalPower() uint64 {\n\treturn m.totalVotingPower\n}\n\n// isCallerDAORealm returns a flag indicating if the\n// current caller context is the active DAO Realm.\n// We need to include a dao guard, even if the\n// executor guarantees it, because\n// the API of the member store is public and callable\n// by anyone who has a reference to the member store instance.\nfunc (m *MembStore) isCallerDAORealm() bool {\n\treturn m.daoPkgPath == \"\" || std.CurrentRealm().PkgPath() == m.daoPkgPath\n}\n"},{"name":"membstore_test.gno","body":"package membstore\n\nimport (\n\t\"testing\"\n\n\t\"std\"\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\n// generateMembers generates dummy govdao members\nfunc generateMembers(t *testing.T, count int) []Member {\n\tt.Helper()\n\n\tmembers := make([]Member, 0, count)\n\n\tfor i := 0; i \u003c count; i++ {\n\t\tmembers = append(members, 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 TestMembStore_GetMember(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"member not found\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Create an empty store\n\t\tm := NewMembStore()\n\n\t\t_, err := m.Member(testutils.TestAddress(\"random\"))\n\t\tuassert.ErrorIs(t, err, ErrMissingMember)\n\t})\n\n\tt.Run(\"valid member fetched\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Create a non-empty store\n\t\tmembers := generateMembers(t, 1)\n\n\t\tm := NewMembStore(WithInitialMembers(members))\n\n\t\t_, err := m.Member(members[0].Address)\n\t\tuassert.NoError(t, err)\n\t})\n}\n\nfunc TestMembStore_GetMembers(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"no members\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Create an empty store\n\t\tm := NewMembStore()\n\n\t\tmembers := m.Members(0, 10)\n\t\tuassert.Equal(t, 0, len(members))\n\t})\n\n\tt.Run(\"proper pagination\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tnumMembers = maxRequestMembers * 2\n\t\t\thalfRange = numMembers / 2\n\n\t\t\tmembers = generateMembers(t, numMembers)\n\t\t\tm = NewMembStore(WithInitialMembers(members))\n\n\t\t\tverifyMembersPresent = func(members, fetchedMembers []Member) {\n\t\t\t\tfor _, fetchedMember := range fetchedMembers {\n\t\t\t\t\tfor _, member := range members {\n\t\t\t\t\t\tif member.Address != fetchedMember.Address {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tuassert.Equal(t, member.VotingPower, fetchedMember.VotingPower)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t)\n\n\t\turequire.Equal(t, numMembers, m.Size())\n\n\t\tfetchedMembers := m.Members(0, uint64(halfRange))\n\t\turequire.Equal(t, halfRange, len(fetchedMembers))\n\n\t\t// Verify the members\n\t\tverifyMembersPresent(members, fetchedMembers)\n\n\t\t// Fetch the other half\n\t\tfetchedMembers = m.Members(uint64(halfRange), uint64(halfRange))\n\t\turequire.Equal(t, halfRange, len(fetchedMembers))\n\n\t\t// Verify the members\n\t\tverifyMembersPresent(members, fetchedMembers)\n\t})\n}\n\nfunc TestMembStore_IsMember(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"non-existing member\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Create an empty store\n\t\tm := NewMembStore()\n\n\t\tuassert.False(t, m.IsMember(testutils.TestAddress(\"random\")))\n\t})\n\n\tt.Run(\"existing member\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Create a non-empty store\n\t\tmembers := generateMembers(t, 50)\n\n\t\tm := NewMembStore(WithInitialMembers(members))\n\n\t\tfor _, member := range members {\n\t\t\tuassert.True(t, m.IsMember(member.Address))\n\t\t}\n\t})\n}\n\nfunc TestMembStore_AddMember(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"caller not govdao\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Create an empty store\n\t\tm := NewMembStore(WithDAOPkgPath(\"gno.land/r/gov/dao\"))\n\n\t\t// Attempt to add a member\n\t\tmember := generateMembers(t, 1)[0]\n\t\tuassert.ErrorIs(t, m.AddMember(member), ErrNotGovDAO)\n\t})\n\n\tt.Run(\"member already exists\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\t// Execute as the /r/gov/dao caller\n\t\t\tdaoPkgPath = \"gno.land/r/gov/dao\"\n\t\t\tr = std.NewCodeRealm(daoPkgPath)\n\t\t)\n\n\t\tstd.TestSetRealm(r)\n\n\t\t// Create a non-empty store\n\t\tmembers := generateMembers(t, 1)\n\t\tm := NewMembStore(WithDAOPkgPath(daoPkgPath), WithInitialMembers(members))\n\n\t\t// Attempt to add a member\n\t\tuassert.ErrorIs(t, m.AddMember(members[0]), ErrAlreadyMember)\n\t})\n\n\tt.Run(\"new member added\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\t// Execute as the /r/gov/dao caller\n\t\t\tdaoPkgPath = \"gno.land/r/gov/dao\"\n\t\t\tr = std.NewCodeRealm(daoPkgPath)\n\t\t)\n\n\t\tstd.TestSetRealm(r)\n\n\t\t// Create an empty store\n\t\tmembers := generateMembers(t, 1)\n\t\tm := NewMembStore(WithDAOPkgPath(daoPkgPath))\n\n\t\t// Attempt to add a member\n\t\turequire.NoError(t, m.AddMember(members[0]))\n\n\t\t// Make sure the member is added\n\t\tuassert.True(t, m.IsMember(members[0].Address))\n\t})\n}\n\nfunc TestMembStore_Size(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"empty govdao\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Create an empty store\n\t\tm := NewMembStore()\n\n\t\tuassert.Equal(t, 0, m.Size())\n\t})\n\n\tt.Run(\"non-empty govdao\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Create a non-empty store\n\t\tmembers := generateMembers(t, 50)\n\t\tm := NewMembStore(WithInitialMembers(members))\n\n\t\tuassert.Equal(t, len(members), m.Size())\n\t})\n}\n\nfunc TestMembStore_UpdateMember(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"caller not govdao\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Create an empty store\n\t\tm := NewMembStore(WithDAOPkgPath(\"gno.land/r/gov/dao\"))\n\n\t\t// Attempt to update a member\n\t\tmember := generateMembers(t, 1)[0]\n\t\tuassert.ErrorIs(t, m.UpdateMember(member.Address, member), ErrNotGovDAO)\n\t})\n\n\tt.Run(\"non-existing member\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\t// Execute as the /r/gov/dao caller\n\t\t\tdaoPkgPath = \"gno.land/r/gov/dao\"\n\t\t\tr = std.NewCodeRealm(daoPkgPath)\n\t\t)\n\n\t\tstd.TestSetRealm(r)\n\n\t\t// Create an empty store\n\t\tmembers := generateMembers(t, 1)\n\t\tm := NewMembStore(WithDAOPkgPath(daoPkgPath))\n\n\t\t// Attempt to update a member\n\t\tuassert.ErrorIs(t, m.UpdateMember(members[0].Address, members[0]), ErrMissingMember)\n\t})\n\n\tt.Run(\"overwrite member attempt\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\t// Execute as the /r/gov/dao caller\n\t\t\tdaoPkgPath = \"gno.land/r/gov/dao\"\n\t\t\tr = std.NewCodeRealm(daoPkgPath)\n\t\t)\n\n\t\tstd.TestSetRealm(r)\n\n\t\t// Create a non-empty store\n\t\tmembers := generateMembers(t, 2)\n\t\tm := NewMembStore(WithDAOPkgPath(daoPkgPath), WithInitialMembers(members))\n\n\t\t// Attempt to update a member\n\t\tuassert.ErrorIs(t, m.UpdateMember(members[0].Address, members[1]), ErrInvalidAddressUpdate)\n\t})\n\n\tt.Run(\"successful update\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\t// Execute as the /r/gov/dao caller\n\t\t\tdaoPkgPath = \"gno.land/r/gov/dao\"\n\t\t\tr = std.NewCodeRealm(daoPkgPath)\n\t\t)\n\n\t\tstd.TestSetRealm(r)\n\n\t\t// Create a non-empty store\n\t\tmembers := generateMembers(t, 1)\n\t\tm := NewMembStore(WithDAOPkgPath(daoPkgPath), WithInitialMembers(members))\n\n\t\toldVotingPower := m.totalVotingPower\n\t\turequire.Equal(t, members[0].VotingPower, oldVotingPower)\n\n\t\tvotingPower := uint64(300)\n\t\tmembers[0].VotingPower = votingPower\n\n\t\t// Attempt to update a member\n\t\tuassert.NoError(t, m.UpdateMember(members[0].Address, members[0]))\n\t\tuassert.Equal(t, votingPower, m.Members(0, 10)[0].VotingPower)\n\t\turequire.Equal(t, votingPower, m.totalVotingPower)\n\t})\n\n\tt.Run(\"member removed\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\t// Execute as the /r/gov/dao caller\n\t\t\tdaoPkgPath = \"gno.land/r/gov/dao\"\n\t\t\tr = std.NewCodeRealm(daoPkgPath)\n\t\t)\n\n\t\tstd.TestSetRealm(r)\n\n\t\t// Create a non-empty store\n\t\tmembers := generateMembers(t, 1)\n\t\tm := NewMembStore(WithDAOPkgPath(daoPkgPath), WithInitialMembers(members))\n\n\t\tvotingPower := uint64(0)\n\t\tmembers[0].VotingPower = votingPower\n\n\t\t// Attempt to update a member\n\t\tuassert.NoError(t, m.UpdateMember(members[0].Address, members[0]))\n\n\t\t// Make sure the member was removed\n\t\tuassert.False(t, m.IsMember(members[0].Address))\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":"ownable","path":"gno.land/p/demo/ownable","files":[{"name":"errors.gno","body":"package ownable\n\nimport \"errors\"\n\nvar (\n\tErrUnauthorized = errors.New(\"ownable: caller is not owner\")\n\tErrInvalidAddress = errors.New(\"ownable: new owner address is invalid\")\n)\n"},{"name":"ownable.gno","body":"package ownable\n\nimport \"std\"\n\nconst OwnershipTransferEvent = \"OwnershipTransfer\"\n\n// Ownable is meant to be used as a top-level object to make your contract ownable OR\n// being embedded in a Gno object to manage per-object ownership.\ntype Ownable struct {\n\towner std.Address\n}\n\nfunc New() *Ownable {\n\treturn \u0026Ownable{\n\t\towner: std.PreviousRealm().Addr(),\n\t}\n}\n\nfunc NewWithAddress(addr std.Address) *Ownable {\n\treturn \u0026Ownable{\n\t\towner: addr,\n\t}\n}\n\n// TransferOwnership transfers ownership of the Ownable struct to a new address\nfunc (o *Ownable) TransferOwnership(newOwner std.Address) error {\n\terr := o.CallerIsOwner()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !newOwner.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tprevOwner := o.owner\n\to.owner = newOwner\n\tstd.Emit(\n\t\tOwnershipTransferEvent,\n\t\t\"from\", string(prevOwner),\n\t\t\"to\", string(newOwner),\n\t)\n\n\treturn nil\n}\n\n// DropOwnership removes the owner, effectively disabling any owner-related actions\n// Top-level usage: disables all only-owner actions/functions,\n// Embedded usage: behaves like a burn functionality, removing the owner from the struct\nfunc (o *Ownable) DropOwnership() error {\n\terr := o.CallerIsOwner()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tprevOwner := o.owner\n\to.owner = \"\"\n\n\tstd.Emit(\n\t\tOwnershipTransferEvent,\n\t\t\"from\", string(prevOwner),\n\t\t\"to\", \"\",\n\t)\n\n\treturn nil\n}\n\n// Owner returns the owner address from Ownable\nfunc (o Ownable) Owner() std.Address {\n\treturn o.owner\n}\n\n// CallerIsOwner checks if the caller of the function is the Realm's owner\nfunc (o Ownable) CallerIsOwner() error {\n\tif std.PreviousRealm().Addr() == o.owner {\n\t\treturn nil\n\t}\n\n\treturn ErrUnauthorized\n}\n\n// AssertCallerIsOwner panics if the caller is not the owner\nfunc (o Ownable) AssertCallerIsOwner() {\n\tif std.PreviousRealm().Addr() != o.owner {\n\t\tpanic(ErrUnauthorized)\n\t}\n}\n"},{"name":"ownable_test.gno","body":"package ownable\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\talice = testutils.TestAddress(\"alice\")\n\tbob = testutils.TestAddress(\"bob\")\n)\n\nfunc TestNew(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOriginCaller(alice) // TODO(bug): should not be needed\n\n\to := New()\n\tgot := o.Owner()\n\tif alice != got {\n\t\tt.Fatalf(\"Expected %s, got: %s\", alice, got)\n\t}\n}\n\nfunc TestNewWithAddress(t *testing.T) {\n\to := NewWithAddress(alice)\n\n\tgot := o.Owner()\n\tif alice != got {\n\t\tt.Fatalf(\"Expected %s, got: %s\", alice, got)\n\t}\n}\n\nfunc TestTransferOwnership(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\n\to := New()\n\n\terr := o.TransferOwnership(bob)\n\tif err != nil {\n\t\tt.Fatalf(\"TransferOwnership failed, %v\", err)\n\t}\n\n\tgot := o.Owner()\n\tif bob != got {\n\t\tt.Fatalf(\"Expected: %s, got: %s\", bob, got)\n\t}\n}\n\nfunc TestCallerIsOwner(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\n\to := New()\n\tunauthorizedCaller := bob\n\n\tstd.TestSetRealm(std.NewUserRealm(unauthorizedCaller))\n\tstd.TestSetOriginCaller(unauthorizedCaller) // TODO(bug): should not be needed\n\n\terr := o.CallerIsOwner()\n\tuassert.Error(t, err) // XXX: IsError(..., unauthorizedCaller)\n}\n\nfunc TestDropOwnership(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\n\to := New()\n\n\terr := o.DropOwnership()\n\tuassert.NoError(t, err, \"DropOwnership failed\")\n\n\towner := o.Owner()\n\tuassert.Empty(t, owner, \"owner should be empty\")\n}\n\n// Errors\n\nfunc TestErrUnauthorized(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOriginCaller(alice) // TODO(bug): should not be needed\n\n\to := New()\n\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\tstd.TestSetOriginCaller(bob) // TODO(bug): should not be needed\n\n\terr := o.TransferOwnership(alice)\n\tif err != ErrUnauthorized {\n\t\tt.Fatalf(\"Should've been ErrUnauthorized, was %v\", err)\n\t}\n\n\terr = o.DropOwnership()\n\tuassert.ErrorContains(t, err, ErrUnauthorized.Error())\n}\n\nfunc TestErrInvalidAddress(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\n\to := New()\n\n\terr := o.TransferOwnership(\"\")\n\tuassert.ErrorContains(t, err, ErrInvalidAddress.Error())\n\n\terr = o.TransferOwnership(\"10000000001000000000100000000010000000001000000000\")\n\tuassert.ErrorContains(t, err, ErrInvalidAddress.Error())\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":"ownable","path":"gno.land/p/demo/ownable","files":[{"name":"errors.gno","body":"package ownable\n\nimport \"errors\"\n\nvar (\n\tErrUnauthorized = errors.New(\"ownable: caller is not owner\")\n\tErrInvalidAddress = errors.New(\"ownable: new owner address is invalid\")\n)\n"},{"name":"ownable.gno","body":"package ownable\n\nimport \"std\"\n\nconst OwnershipTransferEvent = \"OwnershipTransfer\"\n\n// Ownable is meant to be used as a top-level object to make your contract ownable OR\n// being embedded in a Gno object to manage per-object ownership.\ntype Ownable struct {\n\towner std.Address\n}\n\nfunc New() *Ownable {\n\treturn \u0026Ownable{\n\t\towner: std.PreviousRealm().Address(),\n\t}\n}\n\nfunc NewWithAddress(addr std.Address) *Ownable {\n\treturn \u0026Ownable{\n\t\towner: addr,\n\t}\n}\n\n// TransferOwnership transfers ownership of the Ownable struct to a new address\nfunc (o *Ownable) TransferOwnership(newOwner std.Address) error {\n\terr := o.CallerIsOwner()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !newOwner.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tprevOwner := o.owner\n\to.owner = newOwner\n\tstd.Emit(\n\t\tOwnershipTransferEvent,\n\t\t\"from\", string(prevOwner),\n\t\t\"to\", string(newOwner),\n\t)\n\n\treturn nil\n}\n\n// DropOwnership removes the owner, effectively disabling any owner-related actions\n// Top-level usage: disables all only-owner actions/functions,\n// Embedded usage: behaves like a burn functionality, removing the owner from the struct\nfunc (o *Ownable) DropOwnership() error {\n\terr := o.CallerIsOwner()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tprevOwner := o.owner\n\to.owner = \"\"\n\n\tstd.Emit(\n\t\tOwnershipTransferEvent,\n\t\t\"from\", string(prevOwner),\n\t\t\"to\", \"\",\n\t)\n\n\treturn nil\n}\n\n// Owner returns the owner address from Ownable\nfunc (o Ownable) Owner() std.Address {\n\treturn o.owner\n}\n\n// CallerIsOwner checks if the caller of the function is the Realm's owner\nfunc (o Ownable) CallerIsOwner() error {\n\tif std.PreviousRealm().Address() == o.owner {\n\t\treturn nil\n\t}\n\n\treturn ErrUnauthorized\n}\n\n// AssertCallerIsOwner panics if the caller is not the owner\nfunc (o Ownable) AssertCallerIsOwner() {\n\tif std.PreviousRealm().Address() != o.owner {\n\t\tpanic(ErrUnauthorized)\n\t}\n}\n"},{"name":"ownable_test.gno","body":"package ownable\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\talice = testutils.TestAddress(\"alice\")\n\tbob = testutils.TestAddress(\"bob\")\n)\n\nfunc TestNew(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOriginCaller(alice) // TODO(bug): should not be needed\n\n\to := New()\n\tgot := o.Owner()\n\tif alice != got {\n\t\tt.Fatalf(\"Expected %s, got: %s\", alice, got)\n\t}\n}\n\nfunc TestNewWithAddress(t *testing.T) {\n\to := NewWithAddress(alice)\n\n\tgot := o.Owner()\n\tif alice != got {\n\t\tt.Fatalf(\"Expected %s, got: %s\", alice, got)\n\t}\n}\n\nfunc TestTransferOwnership(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\n\to := New()\n\n\terr := o.TransferOwnership(bob)\n\tif err != nil {\n\t\tt.Fatalf(\"TransferOwnership failed, %v\", err)\n\t}\n\n\tgot := o.Owner()\n\tif bob != got {\n\t\tt.Fatalf(\"Expected: %s, got: %s\", bob, got)\n\t}\n}\n\nfunc TestCallerIsOwner(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\n\to := New()\n\tunauthorizedCaller := bob\n\n\tstd.TestSetRealm(std.NewUserRealm(unauthorizedCaller))\n\tstd.TestSetOriginCaller(unauthorizedCaller) // TODO(bug): should not be needed\n\n\terr := o.CallerIsOwner()\n\tuassert.Error(t, err) // XXX: IsError(..., unauthorizedCaller)\n}\n\nfunc TestDropOwnership(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\n\to := New()\n\n\terr := o.DropOwnership()\n\tuassert.NoError(t, err, \"DropOwnership failed\")\n\n\towner := o.Owner()\n\tuassert.Empty(t, owner, \"owner should be empty\")\n}\n\n// Errors\n\nfunc TestErrUnauthorized(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOriginCaller(alice) // TODO(bug): should not be needed\n\n\to := New()\n\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\tstd.TestSetOriginCaller(bob) // TODO(bug): should not be needed\n\n\terr := o.TransferOwnership(alice)\n\tif err != ErrUnauthorized {\n\t\tt.Fatalf(\"Should've been ErrUnauthorized, was %v\", err)\n\t}\n\n\terr = o.DropOwnership()\n\tuassert.ErrorContains(t, err, ErrUnauthorized.Error())\n}\n\nfunc TestErrInvalidAddress(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\n\to := New()\n\n\terr := o.TransferOwnership(\"\")\n\tuassert.ErrorContains(t, err, ErrInvalidAddress.Error())\n\n\terr = o.TransferOwnership(\"10000000001000000000100000000010000000001000000000\")\n\tuassert.ErrorContains(t, err, ErrInvalidAddress.Error())\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":"seqid","path":"gno.land/p/demo/seqid","files":[{"name":"README.md","body":"# seqid\n\n```\npackage seqid // import \"gno.land/p/demo/seqid\"\n\nPackage seqid provides a simple way to have sequential IDs which will be ordered\ncorrectly when inserted in an AVL tree.\n\nSample usage:\n\n var id seqid.ID\n var users avl.Tree\n\n func NewUser() {\n \tusers.Set(id.Next().Binary(), \u0026User{ ... })\n }\n\nTYPES\n\ntype ID uint64\n An ID is a simple sequential ID generator.\n\nfunc FromBinary(b string) (ID, bool)\n FromBinary creates a new ID from the given string.\n\nfunc (i ID) Binary() string\n Binary returns a big-endian binary representation of the ID, suitable to be\n used as an AVL key.\n\nfunc (i *ID) Next() ID\n Next advances the ID i. It will panic if increasing ID would overflow.\n\nfunc (i *ID) TryNext() (ID, bool)\n TryNext increases i by 1 and returns its value. It returns true if\n successful, or false if the increment would result in an overflow.\n```\n"},{"name":"seqid.gno","body":"// Package seqid provides a simple way to have sequential IDs which will be\n// ordered correctly when inserted in an AVL tree.\n//\n// Sample usage:\n//\n//\tvar id seqid.ID\n//\tvar users avl.Tree\n//\n//\tfunc NewUser() {\n//\t\tusers.Set(id.Next().String(), \u0026User{ ... })\n//\t}\npackage seqid\n\nimport (\n\t\"encoding/binary\"\n\n\t\"gno.land/p/demo/cford32\"\n)\n\n// An ID is a simple sequential ID generator.\ntype ID uint64\n\n// Next advances the ID i.\n// It will panic if increasing ID would overflow.\nfunc (i *ID) Next() ID {\n\tnext, ok := i.TryNext()\n\tif !ok {\n\t\tpanic(\"seqid: next ID overflows uint64\")\n\t}\n\treturn next\n}\n\nconst maxID ID = 1\u003c\u003c64 - 1\n\n// TryNext increases i by 1 and returns its value.\n// It returns true if successful, or false if the increment would result in\n// an overflow.\nfunc (i *ID) TryNext() (ID, bool) {\n\tif *i == maxID {\n\t\t// Addition will overflow.\n\t\treturn 0, false\n\t}\n\t*i++\n\treturn *i, true\n}\n\n// Binary returns a big-endian binary representation of the ID,\n// suitable to be used as an AVL key.\nfunc (i ID) Binary() string {\n\tbuf := make([]byte, 8)\n\tbinary.BigEndian.PutUint64(buf, uint64(i))\n\treturn string(buf)\n}\n\n// String encodes i using cford32's compact encoding. For more information,\n// see the documentation for package [gno.land/p/demo/cford32].\n//\n// The result of String will be a 7-byte string for IDs [0,2^34), and a\n// 13-byte string for all values following that. All generated string IDs\n// follow the same lexicographic order as their number values; that is, for any\n// two IDs (x, y) such that x \u003c y, x.String() \u003c y.String().\n// As such, this string representation is suitable to be used as an AVL key.\nfunc (i ID) String() string {\n\treturn string(cford32.PutCompact(uint64(i)))\n}\n\n// FromBinary creates a new ID from the given string, expected to be a binary\n// big-endian encoding of an ID (such as that of [ID.Binary]).\n// The second return value is true if the conversion was successful.\nfunc FromBinary(b string) (ID, bool) {\n\tif len(b) != 8 {\n\t\treturn 0, false\n\t}\n\treturn ID(binary.BigEndian.Uint64([]byte(b))), true\n}\n\n// FromString creates a new ID from the given string, expected to be a string\n// representation using cford32, such as that returned by [ID.String].\n//\n// The encoding scheme used by cford32 allows the same ID to have many\n// different representations (though the one returned by [ID.String] is only\n// one, deterministic and safe to be used in AVL). The encoding scheme is\n// \"human-centric\" and is thus case insensitive, and maps some ambiguous\n// characters to be the same, ie. L = I = 1, O = 0. For this reason, when\n// parsing user input to retrieve a key (encoded as a string), always sanitize\n// it first using FromString, then run String(), instead of using the user's\n// input directly.\nfunc FromString(b string) (ID, error) {\n\tn, err := cford32.Uint64([]byte(b))\n\treturn ID(n), err\n}\n"},{"name":"seqid_test.gno","body":"package seqid\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestID(t *testing.T) {\n\tvar i ID\n\n\tfor j := 0; j \u003c 100; j++ {\n\t\ti.Next()\n\t}\n\tif i != 100 {\n\t\tt.Fatalf(\"invalid: wanted %d got %d\", 100, i)\n\t}\n}\n\nfunc TestID_Overflow(t *testing.T) {\n\ti := ID(maxID)\n\n\tdefer func() {\n\t\terr := recover()\n\t\tif !strings.Contains(fmt.Sprint(err), \"next ID overflows\") {\n\t\t\tt.Errorf(\"did not overflow\")\n\t\t}\n\t}()\n\n\ti.Next()\n}\n\nfunc TestID_Binary(t *testing.T) {\n\tvar i ID\n\tprev := i.Binary()\n\n\tfor j := 0; j \u003c 1000; j++ {\n\t\tcur := i.Next().Binary()\n\t\tif cur \u003c= prev {\n\t\t\tt.Fatalf(\"cur %x \u003e prev %x\", cur, prev)\n\t\t}\n\t\tprev = cur\n\t}\n}\n\nfunc TestID_String(t *testing.T) {\n\tvar i ID\n\tprev := i.String()\n\n\tfor j := 0; j \u003c 1000; j++ {\n\t\tcur := i.Next().String()\n\t\tif cur \u003c= prev {\n\t\t\tt.Fatalf(\"cur %s \u003e prev %s\", cur, prev)\n\t\t}\n\t\tprev = cur\n\t}\n\n\t// Test for when cford32 switches over to the long encoding.\n\ti = 1\u003c\u003c34 - 512\n\tfor j := 0; j \u003c 1024; j++ {\n\t\tcur := i.Next().String()\n\t\t// println(cur)\n\t\tif cur \u003c= prev {\n\t\t\tt.Fatalf(\"cur %s \u003e prev %s\", cur, prev)\n\t\t}\n\t\tprev = cur\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":"memeland","path":"gno.land/p/demo/memeland","files":[{"name":"memeland.gno","body":"package memeland\n\nimport (\n\t\"sort\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/seqid\"\n)\n\nconst (\n\tDATE_CREATED = \"DATE_CREATED\"\n\tUPVOTES = \"UPVOTES\"\n)\n\ntype Post struct {\n\tID string\n\tData string\n\tAuthor std.Address\n\tTimestamp time.Time\n\tUpvoteTracker *avl.Tree // address \u003e struct{}{}\n}\n\ntype Memeland struct {\n\t*ownable.Ownable\n\tPosts []*Post\n\tMemeCounter seqid.ID\n}\n\nfunc NewMemeland() *Memeland {\n\treturn \u0026Memeland{\n\t\tOwnable: ownable.New(),\n\t\tPosts: make([]*Post, 0),\n\t}\n}\n\n// PostMeme - Adds a new post\nfunc (m *Memeland) PostMeme(data string, timestamp int64) string {\n\tif data == \"\" || timestamp \u003c= 0 {\n\t\tpanic(\"timestamp or data cannot be empty\")\n\t}\n\n\t// Generate ID\n\tid := m.MemeCounter.Next().String()\n\n\tnewPost := \u0026Post{\n\t\tID: id,\n\t\tData: data,\n\t\tAuthor: std.PreviousRealm().Addr(),\n\t\tTimestamp: time.Unix(timestamp, 0),\n\t\tUpvoteTracker: avl.NewTree(),\n\t}\n\n\tm.Posts = append(m.Posts, newPost)\n\treturn id\n}\n\nfunc (m *Memeland) Upvote(id string) string {\n\tpost := m.getPost(id)\n\tif post == nil {\n\t\tpanic(\"post with specified ID does not exist\")\n\t}\n\n\tcaller := std.PreviousRealm().Addr().String()\n\n\tif _, exists := post.UpvoteTracker.Get(caller); exists {\n\t\tpanic(\"user has already upvoted this post\")\n\t}\n\n\tpost.UpvoteTracker.Set(caller, struct{}{})\n\n\treturn \"upvote successful\"\n}\n\n// GetPostsInRange returns a JSON string of posts within the given timestamp range, supporting pagination\nfunc (m *Memeland) GetPostsInRange(startTimestamp, endTimestamp int64, page, pageSize int, sortBy string) string {\n\tif len(m.Posts) == 0 {\n\t\treturn \"[]\"\n\t}\n\n\tif page \u003c 1 {\n\t\tpanic(\"page number cannot be less than 1\")\n\t}\n\n\t// No empty pages\n\tif pageSize \u003c 1 {\n\t\tpanic(\"page size cannot be less than 1\")\n\t}\n\n\t// No pages larger than 10\n\tif pageSize \u003e 10 {\n\t\tpanic(\"page size cannot be larger than 10\")\n\t}\n\n\t// Need to pass in a sort parameter\n\tif sortBy == \"\" {\n\t\tpanic(\"sort order cannot be empty\")\n\t}\n\n\tvar filteredPosts []*Post\n\n\tstart := time.Unix(startTimestamp, 0)\n\tend := time.Unix(endTimestamp, 0)\n\n\t// Filtering posts\n\tfor _, p := range m.Posts {\n\t\tif !p.Timestamp.Before(start) \u0026\u0026 !p.Timestamp.After(end) {\n\t\t\tfilteredPosts = append(filteredPosts, p)\n\t\t}\n\t}\n\n\tswitch sortBy {\n\t// Sort by upvote descending\n\tcase UPVOTES:\n\t\tdateSorter := PostSorter{\n\t\t\tPosts: filteredPosts,\n\t\t\tLessF: func(i, j int) bool {\n\t\t\t\treturn filteredPosts[i].UpvoteTracker.Size() \u003e filteredPosts[j].UpvoteTracker.Size()\n\t\t\t},\n\t\t}\n\t\tsort.Sort(dateSorter)\n\tcase DATE_CREATED:\n\t\t// Sort by timestamp, beginning with newest\n\t\tdateSorter := PostSorter{\n\t\t\tPosts: filteredPosts,\n\t\t\tLessF: func(i, j int) bool {\n\t\t\t\treturn filteredPosts[i].Timestamp.After(filteredPosts[j].Timestamp)\n\t\t\t},\n\t\t}\n\t\tsort.Sort(dateSorter)\n\tdefault:\n\t\tpanic(\"sort order can only be \\\"UPVOTES\\\" or \\\"DATE_CREATED\\\"\")\n\t}\n\n\t// Pagination\n\tstartIndex := (page - 1) * pageSize\n\tendIndex := startIndex + pageSize\n\n\t// If page does not contain any posts\n\tif startIndex \u003e= len(filteredPosts) {\n\t\treturn \"[]\"\n\t}\n\n\t// If page contains fewer posts than the page size\n\tif endIndex \u003e len(filteredPosts) {\n\t\tendIndex = len(filteredPosts)\n\t}\n\n\t// Return JSON representation of paginated and sorted posts\n\treturn PostsToJSONString(filteredPosts[startIndex:endIndex])\n}\n\n// RemovePost allows the owner to remove a post with a specific ID\nfunc (m *Memeland) RemovePost(id string) string {\n\tif id == \"\" {\n\t\tpanic(\"id cannot be empty\")\n\t}\n\n\tif err := m.CallerIsOwner(); err != nil {\n\t\tpanic(err)\n\t}\n\n\tfor i, post := range m.Posts {\n\t\tif post.ID == id {\n\t\t\tm.Posts = append(m.Posts[:i], m.Posts[i+1:]...)\n\t\t\treturn id\n\t\t}\n\t}\n\n\tpanic(\"post with specified id does not exist\")\n}\n\n// PostsToJSONString converts a slice of Post structs into a JSON string\nfunc PostsToJSONString(posts []*Post) string {\n\tvar sb strings.Builder\n\tsb.WriteString(\"[\")\n\n\tfor i, post := range posts {\n\t\tif i \u003e 0 {\n\t\t\tsb.WriteString(\",\")\n\t\t}\n\n\t\tsb.WriteString(PostToJSONString(post))\n\t}\n\tsb.WriteString(\"]\")\n\n\treturn sb.String()\n}\n\n// PostToJSONString returns a Post formatted as a JSON string\nfunc PostToJSONString(post *Post) string {\n\tvar sb strings.Builder\n\n\tsb.WriteString(\"{\")\n\tsb.WriteString(`\"id\":\"` + post.ID + `\",`)\n\tsb.WriteString(`\"data\":\"` + escapeString(post.Data) + `\",`)\n\tsb.WriteString(`\"author\":\"` + escapeString(post.Author.String()) + `\",`)\n\tsb.WriteString(`\"timestamp\":\"` + strconv.Itoa(int(post.Timestamp.Unix())) + `\",`)\n\tsb.WriteString(`\"upvotes\":` + strconv.Itoa(post.UpvoteTracker.Size()))\n\tsb.WriteString(\"}\")\n\n\treturn sb.String()\n}\n\n// escapeString escapes quotes in a string for JSON compatibility.\nfunc escapeString(s string) string {\n\treturn strings.ReplaceAll(s, `\"`, `\\\"`)\n}\n\nfunc (m *Memeland) getPost(id string) *Post {\n\tfor _, p := range m.Posts {\n\t\tif p.ID == id {\n\t\t\treturn p\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// PostSorter is a flexible sorter for the *Post slice\ntype PostSorter struct {\n\tPosts []*Post\n\tLessF func(i, j int) bool\n}\n\nfunc (p PostSorter) Len() int {\n\treturn len(p.Posts)\n}\n\nfunc (p PostSorter) Swap(i, j int) {\n\tp.Posts[i], p.Posts[j] = p.Posts[j], p.Posts[i]\n}\n\nfunc (p PostSorter) Less(i, j int) bool {\n\treturn p.LessF(i, j)\n}\n"},{"name":"memeland_test.gno","body":"package memeland\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\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 TestPostMeme(t *testing.T) {\n\tm := NewMemeland()\n\tid := m.PostMeme(\"Test meme data\", time.Now().Unix())\n\tuassert.NotEqual(t, \"\", string(id), \"Expected valid ID, got empty string\")\n}\n\nfunc TestGetPostsInRangePagination(t *testing.T) {\n\tm := NewMemeland()\n\tnow := time.Now()\n\n\tnumOfPosts := 5\n\tvar memeData []string\n\tfor i := 1; i \u003c= numOfPosts; i++ {\n\t\t// Prepare meme data\n\t\tnextTime := now.Add(time.Duration(i) * time.Minute)\n\t\tdata := ufmt.Sprintf(\"Meme #%d\", i)\n\t\tmemeData = append(memeData, data)\n\n\t\tm.PostMeme(data, nextTime.Unix())\n\t}\n\n\t// Get timestamps\n\tbeforeEarliest := now.Add(-1 * time.Minute)\n\tafterLatest := now.Add(time.Duration(numOfPosts)*time.Minute + time.Minute)\n\n\ttestCases := []struct {\n\t\tpage int\n\t\tpageSize int\n\t\texpectedNumOfPosts int\n\t}{\n\t\t{page: 1, pageSize: 1, expectedNumOfPosts: 1}, // one per page\n\t\t{page: 2, pageSize: 1, expectedNumOfPosts: 1}, // one on second page\n\t\t{page: 1, pageSize: numOfPosts, expectedNumOfPosts: numOfPosts}, // all posts on single page\n\t\t{page: 12, pageSize: 1, expectedNumOfPosts: 0}, // empty page\n\t\t{page: 1, pageSize: numOfPosts + 1, expectedNumOfPosts: numOfPosts}, // page with fewer posts than its size\n\t\t{page: 5, pageSize: numOfPosts / 5, expectedNumOfPosts: 1}, // evenly distribute posts per page\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(ufmt.Sprintf(\"Page%d_Size%d\", tc.page, tc.pageSize), func(t *testing.T) {\n\t\t\tresult := m.GetPostsInRange(beforeEarliest.Unix(), afterLatest.Unix(), tc.page, tc.pageSize, \"DATE_CREATED\")\n\n\t\t\t// Count posts by how many times id: shows up in JSON string\n\t\t\tpostCount := strings.Count(result, `\"id\":\"`)\n\t\t\tuassert.Equal(t, tc.expectedNumOfPosts, postCount)\n\t\t})\n\t}\n}\n\nfunc TestGetPostsInRangeByTimestamp(t *testing.T) {\n\tm := NewMemeland()\n\tnow := time.Now()\n\n\tnumOfPosts := 5\n\tvar memeData []string\n\tfor i := 1; i \u003c= numOfPosts; i++ {\n\t\t// Prepare meme data\n\t\tnextTime := now.Add(time.Duration(i) * time.Minute)\n\t\tdata := ufmt.Sprintf(\"Meme #%d\", i)\n\t\tmemeData = append(memeData, data)\n\n\t\tm.PostMeme(data, nextTime.Unix())\n\t}\n\n\t// Get timestamps\n\tbeforeEarliest := now.Add(-1 * time.Minute)\n\tafterLatest := now.Add(time.Duration(numOfPosts)*time.Minute + time.Minute)\n\n\t// Default sort is by addition order/timestamp\n\tjsonStr := m.GetPostsInRange(\n\t\tbeforeEarliest.Unix(), // start at earliest post\n\t\tafterLatest.Unix(), // end at latest post\n\t\t1, // first page\n\t\tnumOfPosts, // all memes on the page\n\t\t\"DATE_CREATED\", // sort by newest first\n\t)\n\n\tuassert.NotEmpty(t, jsonStr, \"Expected non-empty JSON string, got empty string\")\n\n\t// Count the number of posts returned in the JSON string as a rudimentary check for correct pagination/filtering\n\tpostCount := strings.Count(jsonStr, `\"id\":\"`)\n\tuassert.Equal(t, uint64(m.MemeCounter), uint64(postCount))\n\n\t// Check if data is there\n\tfor _, expData := range memeData {\n\t\tcheck := strings.Contains(jsonStr, expData)\n\t\tuassert.True(t, check, ufmt.Sprintf(\"Expected %s in the JSON string, but counld't find it\", expData))\n\t}\n\n\t// Check if ordering is correct, sort by created date\n\tfor i := 0; i \u003c len(memeData)-2; i++ {\n\t\tcheck := strings.Index(jsonStr, memeData[i]) \u003e= strings.Index(jsonStr, memeData[i+1])\n\t\tuassert.True(t, check, ufmt.Sprintf(\"Expected %s to be before %s, but was at %d, and %d\", memeData[i], memeData[i+1], i, i+1))\n\t}\n}\n\nfunc TestGetPostsInRangeByUpvote(t *testing.T) {\n\tm := NewMemeland()\n\tnow := time.Now()\n\n\tmemeData1 := \"Meme #1\"\n\tmemeData2 := \"Meme #2\"\n\n\t// Create posts at specific times for testing\n\tid1 := m.PostMeme(memeData1, now.Unix())\n\tid2 := m.PostMeme(memeData2, now.Add(time.Minute).Unix())\n\n\tm.Upvote(id1)\n\tm.Upvote(id2)\n\n\t// Change caller so avoid double upvote panic\n\tstd.TestSetOriginCaller(testutils.TestAddress(\"alice\"))\n\tm.Upvote(id1)\n\n\t// Final upvote count:\n\t// Meme #1 - 2 upvote\n\t// Meme #2 - 1 upvotes\n\n\t// Get timestamps\n\tbeforeEarliest := now.Add(-time.Minute)\n\tafterLatest := now.Add(time.Hour)\n\n\t// Default sort is by addition order/timestamp\n\tjsonStr := m.GetPostsInRange(\n\t\tbeforeEarliest.Unix(), // start at earliest post\n\t\tafterLatest.Unix(), // end at latest post\n\t\t1, // first page\n\t\t2, // all memes on the page\n\t\t\"UPVOTES\", // sort by upvote\n\t)\n\n\tuassert.NotEmpty(t, jsonStr, \"Expected non-empty JSON string, got empty string\")\n\n\t// Count the number of posts returned in the JSON string as a rudimentary check for correct pagination/filtering\n\tpostCount := strings.Count(jsonStr, `\"id\":\"`)\n\tuassert.Equal(t, uint64(m.MemeCounter), uint64(postCount))\n\n\t// Check if ordering is correct\n\tcheck := strings.Index(jsonStr, \"Meme #1\") \u003c= strings.Index(jsonStr, \"Meme #2\")\n\tuassert.True(t, check, ufmt.Sprintf(\"Expected %s to be before %s\", memeData1, memeData2))\n}\n\nfunc TestBadSortBy(t *testing.T) {\n\tm := NewMemeland()\n\tnow := time.Now()\n\n\tnumOfPosts := 5\n\tvar memeData []string\n\tfor i := 1; i \u003c= numOfPosts; i++ {\n\t\t// Prepare meme data\n\t\tnextTime := now.Add(time.Duration(i) * time.Minute)\n\t\tdata := ufmt.Sprintf(\"Meme #%d\", i)\n\t\tmemeData = append(memeData, data)\n\n\t\tm.PostMeme(data, nextTime.Unix())\n\t}\n\n\t// Get timestamps\n\tbeforeEarliest := now.Add(-1 * time.Minute)\n\tafterLatest := now.Add(time.Duration(numOfPosts)*time.Minute + time.Minute)\n\n\ttests := []struct {\n\t\tname string\n\t\tsortBy string\n\t\twantPanic string\n\t}{\n\t\t{\n\t\t\tname: \"Empty sortBy\",\n\t\t\tsortBy: \"\",\n\t\t\twantPanic: \"runtime error: index out of range\",\n\t\t},\n\t\t{\n\t\t\tname: \"Wrong sortBy\",\n\t\t\tsortBy: \"random string\",\n\t\t\twantPanic: \"\",\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tdefer func() {\n\t\t\t\tif r := recover(); r == nil {\n\t\t\t\t\tt.Errorf(\"code did not panic when it should have\")\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\t// Panics should be caught\n\t\t\t_ = m.GetPostsInRange(beforeEarliest.Unix(), afterLatest.Unix(), 1, 1, tc.sortBy)\n\t\t})\n\t}\n}\n\nfunc TestNoPosts(t *testing.T) {\n\tm := NewMemeland()\n\n\t// Add a post to Memeland\n\tnow := time.Now().Unix()\n\n\tjsonStr := m.GetPostsInRange(0, now, 1, 1, \"DATE_CREATED\")\n\n\tuassert.Equal(t, jsonStr, \"[]\")\n}\n\nfunc TestUpvote(t *testing.T) {\n\tm := NewMemeland()\n\n\t// Add a post to Memeland\n\tnow := time.Now().Unix()\n\tpostID := m.PostMeme(\"Test meme data\", now)\n\n\t// Initial upvote count should be 0\n\tpost := m.getPost(postID)\n\tuassert.Equal(t, 0, post.UpvoteTracker.Size())\n\n\t// Upvote the post\n\tupvoteResult := m.Upvote(postID)\n\tuassert.Equal(t, \"upvote successful\", upvoteResult)\n\n\t// Retrieve the post again and check the upvote count\n\tpost = m.getPost(postID)\n\tuassert.Equal(t, 1, post.UpvoteTracker.Size())\n}\n\nfunc TestDelete(t *testing.T) {\n\talice := testutils.TestAddress(\"alice\")\n\tstd.TestSetOriginCaller(alice)\n\n\t// Alice is admin\n\tm := NewMemeland()\n\n\t// Set caller to Bob\n\tbob := testutils.TestAddress(\"bob\")\n\tstd.TestSetOriginCaller(bob)\n\n\t// Bob adds post to Memeland\n\tnow := time.Now()\n\tpostID := m.PostMeme(\"Meme #1\", now.Unix())\n\n\t// Alice removes Bob's post\n\tstd.TestSetOriginCaller(alice)\n\n\tid := m.RemovePost(postID)\n\tuassert.Equal(t, postID, id, \"post IDs not matching\")\n\tuassert.Equal(t, 0, len(m.Posts), \"there should be 0 posts after removing\")\n}\n\nfunc TestDeleteByNonAdmin(t *testing.T) {\n\talice := testutils.TestAddress(\"alice\")\n\tstd.TestSetOriginCaller(alice)\n\n\tm := NewMemeland()\n\n\t// Add a post to Memeland\n\tnow := time.Now()\n\tpostID := m.PostMeme(\"Meme #1\", now.Unix())\n\n\t// Bob will try to delete meme posted by Alice, which should fail\n\tbob := testutils.TestAddress(\"bob\")\n\tstd.TestSetOriginCaller(bob)\n\n\tdefer func() {\n\t\tif r := recover(); r == nil {\n\t\t\tt.Errorf(\"code did not panic when it should have\")\n\t\t}\n\t}()\n\n\t// Should panic - caught by defer\n\tm.RemovePost(postID)\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":"memeland","path":"gno.land/p/demo/memeland","files":[{"name":"memeland.gno","body":"package memeland\n\nimport (\n\t\"sort\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/seqid\"\n)\n\nconst (\n\tDATE_CREATED = \"DATE_CREATED\"\n\tUPVOTES = \"UPVOTES\"\n)\n\ntype Post struct {\n\tID string\n\tData string\n\tAuthor std.Address\n\tTimestamp time.Time\n\tUpvoteTracker *avl.Tree // address \u003e struct{}{}\n}\n\ntype Memeland struct {\n\t*ownable.Ownable\n\tPosts []*Post\n\tMemeCounter seqid.ID\n}\n\nfunc NewMemeland() *Memeland {\n\treturn \u0026Memeland{\n\t\tOwnable: ownable.New(),\n\t\tPosts: make([]*Post, 0),\n\t}\n}\n\n// PostMeme - Adds a new post\nfunc (m *Memeland) PostMeme(data string, timestamp int64) string {\n\tif data == \"\" || timestamp \u003c= 0 {\n\t\tpanic(\"timestamp or data cannot be empty\")\n\t}\n\n\t// Generate ID\n\tid := m.MemeCounter.Next().String()\n\n\tnewPost := \u0026Post{\n\t\tID: id,\n\t\tData: data,\n\t\tAuthor: std.PreviousRealm().Address(),\n\t\tTimestamp: time.Unix(timestamp, 0),\n\t\tUpvoteTracker: avl.NewTree(),\n\t}\n\n\tm.Posts = append(m.Posts, newPost)\n\treturn id\n}\n\nfunc (m *Memeland) Upvote(id string) string {\n\tpost := m.getPost(id)\n\tif post == nil {\n\t\tpanic(\"post with specified ID does not exist\")\n\t}\n\n\tcaller := std.PreviousRealm().Address().String()\n\n\tif _, exists := post.UpvoteTracker.Get(caller); exists {\n\t\tpanic(\"user has already upvoted this post\")\n\t}\n\n\tpost.UpvoteTracker.Set(caller, struct{}{})\n\n\treturn \"upvote successful\"\n}\n\n// GetPostsInRange returns a JSON string of posts within the given timestamp range, supporting pagination\nfunc (m *Memeland) GetPostsInRange(startTimestamp, endTimestamp int64, page, pageSize int, sortBy string) string {\n\tif len(m.Posts) == 0 {\n\t\treturn \"[]\"\n\t}\n\n\tif page \u003c 1 {\n\t\tpanic(\"page number cannot be less than 1\")\n\t}\n\n\t// No empty pages\n\tif pageSize \u003c 1 {\n\t\tpanic(\"page size cannot be less than 1\")\n\t}\n\n\t// No pages larger than 10\n\tif pageSize \u003e 10 {\n\t\tpanic(\"page size cannot be larger than 10\")\n\t}\n\n\t// Need to pass in a sort parameter\n\tif sortBy == \"\" {\n\t\tpanic(\"sort order cannot be empty\")\n\t}\n\n\tvar filteredPosts []*Post\n\n\tstart := time.Unix(startTimestamp, 0)\n\tend := time.Unix(endTimestamp, 0)\n\n\t// Filtering posts\n\tfor _, p := range m.Posts {\n\t\tif !p.Timestamp.Before(start) \u0026\u0026 !p.Timestamp.After(end) {\n\t\t\tfilteredPosts = append(filteredPosts, p)\n\t\t}\n\t}\n\n\tswitch sortBy {\n\t// Sort by upvote descending\n\tcase UPVOTES:\n\t\tdateSorter := PostSorter{\n\t\t\tPosts: filteredPosts,\n\t\t\tLessF: func(i, j int) bool {\n\t\t\t\treturn filteredPosts[i].UpvoteTracker.Size() \u003e filteredPosts[j].UpvoteTracker.Size()\n\t\t\t},\n\t\t}\n\t\tsort.Sort(dateSorter)\n\tcase DATE_CREATED:\n\t\t// Sort by timestamp, beginning with newest\n\t\tdateSorter := PostSorter{\n\t\t\tPosts: filteredPosts,\n\t\t\tLessF: func(i, j int) bool {\n\t\t\t\treturn filteredPosts[i].Timestamp.After(filteredPosts[j].Timestamp)\n\t\t\t},\n\t\t}\n\t\tsort.Sort(dateSorter)\n\tdefault:\n\t\tpanic(\"sort order can only be \\\"UPVOTES\\\" or \\\"DATE_CREATED\\\"\")\n\t}\n\n\t// Pagination\n\tstartIndex := (page - 1) * pageSize\n\tendIndex := startIndex + pageSize\n\n\t// If page does not contain any posts\n\tif startIndex \u003e= len(filteredPosts) {\n\t\treturn \"[]\"\n\t}\n\n\t// If page contains fewer posts than the page size\n\tif endIndex \u003e len(filteredPosts) {\n\t\tendIndex = len(filteredPosts)\n\t}\n\n\t// Return JSON representation of paginated and sorted posts\n\treturn PostsToJSONString(filteredPosts[startIndex:endIndex])\n}\n\n// RemovePost allows the owner to remove a post with a specific ID\nfunc (m *Memeland) RemovePost(id string) string {\n\tif id == \"\" {\n\t\tpanic(\"id cannot be empty\")\n\t}\n\n\tif err := m.CallerIsOwner(); err != nil {\n\t\tpanic(err)\n\t}\n\n\tfor i, post := range m.Posts {\n\t\tif post.ID == id {\n\t\t\tm.Posts = append(m.Posts[:i], m.Posts[i+1:]...)\n\t\t\treturn id\n\t\t}\n\t}\n\n\tpanic(\"post with specified id does not exist\")\n}\n\n// PostsToJSONString converts a slice of Post structs into a JSON string\nfunc PostsToJSONString(posts []*Post) string {\n\tvar sb strings.Builder\n\tsb.WriteString(\"[\")\n\n\tfor i, post := range posts {\n\t\tif i \u003e 0 {\n\t\t\tsb.WriteString(\",\")\n\t\t}\n\n\t\tsb.WriteString(PostToJSONString(post))\n\t}\n\tsb.WriteString(\"]\")\n\n\treturn sb.String()\n}\n\n// PostToJSONString returns a Post formatted as a JSON string\nfunc PostToJSONString(post *Post) string {\n\tvar sb strings.Builder\n\n\tsb.WriteString(\"{\")\n\tsb.WriteString(`\"id\":\"` + post.ID + `\",`)\n\tsb.WriteString(`\"data\":\"` + escapeString(post.Data) + `\",`)\n\tsb.WriteString(`\"author\":\"` + escapeString(post.Author.String()) + `\",`)\n\tsb.WriteString(`\"timestamp\":\"` + strconv.Itoa(int(post.Timestamp.Unix())) + `\",`)\n\tsb.WriteString(`\"upvotes\":` + strconv.Itoa(post.UpvoteTracker.Size()))\n\tsb.WriteString(\"}\")\n\n\treturn sb.String()\n}\n\n// escapeString escapes quotes in a string for JSON compatibility.\nfunc escapeString(s string) string {\n\treturn strings.ReplaceAll(s, `\"`, `\\\"`)\n}\n\nfunc (m *Memeland) getPost(id string) *Post {\n\tfor _, p := range m.Posts {\n\t\tif p.ID == id {\n\t\t\treturn p\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// PostSorter is a flexible sorter for the *Post slice\ntype PostSorter struct {\n\tPosts []*Post\n\tLessF func(i, j int) bool\n}\n\nfunc (p PostSorter) Len() int {\n\treturn len(p.Posts)\n}\n\nfunc (p PostSorter) Swap(i, j int) {\n\tp.Posts[i], p.Posts[j] = p.Posts[j], p.Posts[i]\n}\n\nfunc (p PostSorter) Less(i, j int) bool {\n\treturn p.LessF(i, j)\n}\n"},{"name":"memeland_test.gno","body":"package memeland\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\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 TestPostMeme(t *testing.T) {\n\tm := NewMemeland()\n\tid := m.PostMeme(\"Test meme data\", time.Now().Unix())\n\tuassert.NotEqual(t, \"\", string(id), \"Expected valid ID, got empty string\")\n}\n\nfunc TestGetPostsInRangePagination(t *testing.T) {\n\tm := NewMemeland()\n\tnow := time.Now()\n\n\tnumOfPosts := 5\n\tvar memeData []string\n\tfor i := 1; i \u003c= numOfPosts; i++ {\n\t\t// Prepare meme data\n\t\tnextTime := now.Add(time.Duration(i) * time.Minute)\n\t\tdata := ufmt.Sprintf(\"Meme #%d\", i)\n\t\tmemeData = append(memeData, data)\n\n\t\tm.PostMeme(data, nextTime.Unix())\n\t}\n\n\t// Get timestamps\n\tbeforeEarliest := now.Add(-1 * time.Minute)\n\tafterLatest := now.Add(time.Duration(numOfPosts)*time.Minute + time.Minute)\n\n\ttestCases := []struct {\n\t\tpage int\n\t\tpageSize int\n\t\texpectedNumOfPosts int\n\t}{\n\t\t{page: 1, pageSize: 1, expectedNumOfPosts: 1}, // one per page\n\t\t{page: 2, pageSize: 1, expectedNumOfPosts: 1}, // one on second page\n\t\t{page: 1, pageSize: numOfPosts, expectedNumOfPosts: numOfPosts}, // all posts on single page\n\t\t{page: 12, pageSize: 1, expectedNumOfPosts: 0}, // empty page\n\t\t{page: 1, pageSize: numOfPosts + 1, expectedNumOfPosts: numOfPosts}, // page with fewer posts than its size\n\t\t{page: 5, pageSize: numOfPosts / 5, expectedNumOfPosts: 1}, // evenly distribute posts per page\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(ufmt.Sprintf(\"Page%d_Size%d\", tc.page, tc.pageSize), func(t *testing.T) {\n\t\t\tresult := m.GetPostsInRange(beforeEarliest.Unix(), afterLatest.Unix(), tc.page, tc.pageSize, \"DATE_CREATED\")\n\n\t\t\t// Count posts by how many times id: shows up in JSON string\n\t\t\tpostCount := strings.Count(result, `\"id\":\"`)\n\t\t\tuassert.Equal(t, tc.expectedNumOfPosts, postCount)\n\t\t})\n\t}\n}\n\nfunc TestGetPostsInRangeByTimestamp(t *testing.T) {\n\tm := NewMemeland()\n\tnow := time.Now()\n\n\tnumOfPosts := 5\n\tvar memeData []string\n\tfor i := 1; i \u003c= numOfPosts; i++ {\n\t\t// Prepare meme data\n\t\tnextTime := now.Add(time.Duration(i) * time.Minute)\n\t\tdata := ufmt.Sprintf(\"Meme #%d\", i)\n\t\tmemeData = append(memeData, data)\n\n\t\tm.PostMeme(data, nextTime.Unix())\n\t}\n\n\t// Get timestamps\n\tbeforeEarliest := now.Add(-1 * time.Minute)\n\tafterLatest := now.Add(time.Duration(numOfPosts)*time.Minute + time.Minute)\n\n\t// Default sort is by addition order/timestamp\n\tjsonStr := m.GetPostsInRange(\n\t\tbeforeEarliest.Unix(), // start at earliest post\n\t\tafterLatest.Unix(), // end at latest post\n\t\t1, // first page\n\t\tnumOfPosts, // all memes on the page\n\t\t\"DATE_CREATED\", // sort by newest first\n\t)\n\n\tuassert.NotEmpty(t, jsonStr, \"Expected non-empty JSON string, got empty string\")\n\n\t// Count the number of posts returned in the JSON string as a rudimentary check for correct pagination/filtering\n\tpostCount := strings.Count(jsonStr, `\"id\":\"`)\n\tuassert.Equal(t, uint64(m.MemeCounter), uint64(postCount))\n\n\t// Check if data is there\n\tfor _, expData := range memeData {\n\t\tcheck := strings.Contains(jsonStr, expData)\n\t\tuassert.True(t, check, ufmt.Sprintf(\"Expected %s in the JSON string, but counld't find it\", expData))\n\t}\n\n\t// Check if ordering is correct, sort by created date\n\tfor i := 0; i \u003c len(memeData)-2; i++ {\n\t\tcheck := strings.Index(jsonStr, memeData[i]) \u003e= strings.Index(jsonStr, memeData[i+1])\n\t\tuassert.True(t, check, ufmt.Sprintf(\"Expected %s to be before %s, but was at %d, and %d\", memeData[i], memeData[i+1], i, i+1))\n\t}\n}\n\nfunc TestGetPostsInRangeByUpvote(t *testing.T) {\n\tm := NewMemeland()\n\tnow := time.Now()\n\n\tmemeData1 := \"Meme #1\"\n\tmemeData2 := \"Meme #2\"\n\n\t// Create posts at specific times for testing\n\tid1 := m.PostMeme(memeData1, now.Unix())\n\tid2 := m.PostMeme(memeData2, now.Add(time.Minute).Unix())\n\n\tm.Upvote(id1)\n\tm.Upvote(id2)\n\n\t// Change caller so avoid double upvote panic\n\tstd.TestSetOriginCaller(testutils.TestAddress(\"alice\"))\n\tm.Upvote(id1)\n\n\t// Final upvote count:\n\t// Meme #1 - 2 upvote\n\t// Meme #2 - 1 upvotes\n\n\t// Get timestamps\n\tbeforeEarliest := now.Add(-time.Minute)\n\tafterLatest := now.Add(time.Hour)\n\n\t// Default sort is by addition order/timestamp\n\tjsonStr := m.GetPostsInRange(\n\t\tbeforeEarliest.Unix(), // start at earliest post\n\t\tafterLatest.Unix(), // end at latest post\n\t\t1, // first page\n\t\t2, // all memes on the page\n\t\t\"UPVOTES\", // sort by upvote\n\t)\n\n\tuassert.NotEmpty(t, jsonStr, \"Expected non-empty JSON string, got empty string\")\n\n\t// Count the number of posts returned in the JSON string as a rudimentary check for correct pagination/filtering\n\tpostCount := strings.Count(jsonStr, `\"id\":\"`)\n\tuassert.Equal(t, uint64(m.MemeCounter), uint64(postCount))\n\n\t// Check if ordering is correct\n\tcheck := strings.Index(jsonStr, \"Meme #1\") \u003c= strings.Index(jsonStr, \"Meme #2\")\n\tuassert.True(t, check, ufmt.Sprintf(\"Expected %s to be before %s\", memeData1, memeData2))\n}\n\nfunc TestBadSortBy(t *testing.T) {\n\tm := NewMemeland()\n\tnow := time.Now()\n\n\tnumOfPosts := 5\n\tvar memeData []string\n\tfor i := 1; i \u003c= numOfPosts; i++ {\n\t\t// Prepare meme data\n\t\tnextTime := now.Add(time.Duration(i) * time.Minute)\n\t\tdata := ufmt.Sprintf(\"Meme #%d\", i)\n\t\tmemeData = append(memeData, data)\n\n\t\tm.PostMeme(data, nextTime.Unix())\n\t}\n\n\t// Get timestamps\n\tbeforeEarliest := now.Add(-1 * time.Minute)\n\tafterLatest := now.Add(time.Duration(numOfPosts)*time.Minute + time.Minute)\n\n\ttests := []struct {\n\t\tname string\n\t\tsortBy string\n\t\twantPanic string\n\t}{\n\t\t{\n\t\t\tname: \"Empty sortBy\",\n\t\t\tsortBy: \"\",\n\t\t\twantPanic: \"runtime error: index out of range\",\n\t\t},\n\t\t{\n\t\t\tname: \"Wrong sortBy\",\n\t\t\tsortBy: \"random string\",\n\t\t\twantPanic: \"\",\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tdefer func() {\n\t\t\t\tif r := recover(); r == nil {\n\t\t\t\t\tt.Errorf(\"code did not panic when it should have\")\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\t// Panics should be caught\n\t\t\t_ = m.GetPostsInRange(beforeEarliest.Unix(), afterLatest.Unix(), 1, 1, tc.sortBy)\n\t\t})\n\t}\n}\n\nfunc TestNoPosts(t *testing.T) {\n\tm := NewMemeland()\n\n\t// Add a post to Memeland\n\tnow := time.Now().Unix()\n\n\tjsonStr := m.GetPostsInRange(0, now, 1, 1, \"DATE_CREATED\")\n\n\tuassert.Equal(t, jsonStr, \"[]\")\n}\n\nfunc TestUpvote(t *testing.T) {\n\tm := NewMemeland()\n\n\t// Add a post to Memeland\n\tnow := time.Now().Unix()\n\tpostID := m.PostMeme(\"Test meme data\", now)\n\n\t// Initial upvote count should be 0\n\tpost := m.getPost(postID)\n\tuassert.Equal(t, 0, post.UpvoteTracker.Size())\n\n\t// Upvote the post\n\tupvoteResult := m.Upvote(postID)\n\tuassert.Equal(t, \"upvote successful\", upvoteResult)\n\n\t// Retrieve the post again and check the upvote count\n\tpost = m.getPost(postID)\n\tuassert.Equal(t, 1, post.UpvoteTracker.Size())\n}\n\nfunc TestDelete(t *testing.T) {\n\talice := testutils.TestAddress(\"alice\")\n\tstd.TestSetOriginCaller(alice)\n\n\t// Alice is admin\n\tm := NewMemeland()\n\n\t// Set caller to Bob\n\tbob := testutils.TestAddress(\"bob\")\n\tstd.TestSetOriginCaller(bob)\n\n\t// Bob adds post to Memeland\n\tnow := time.Now()\n\tpostID := m.PostMeme(\"Meme #1\", now.Unix())\n\n\t// Alice removes Bob's post\n\tstd.TestSetOriginCaller(alice)\n\n\tid := m.RemovePost(postID)\n\tuassert.Equal(t, postID, id, \"post IDs not matching\")\n\tuassert.Equal(t, 0, len(m.Posts), \"there should be 0 posts after removing\")\n}\n\nfunc TestDeleteByNonAdmin(t *testing.T) {\n\talice := testutils.TestAddress(\"alice\")\n\tstd.TestSetOriginCaller(alice)\n\n\tm := NewMemeland()\n\n\t// Add a post to Memeland\n\tnow := time.Now()\n\tpostID := m.PostMeme(\"Meme #1\", now.Unix())\n\n\t// Bob will try to delete meme posted by Alice, which should fail\n\tbob := testutils.TestAddress(\"bob\")\n\tstd.TestSetOriginCaller(bob)\n\n\tdefer func() {\n\t\tif r := recover(); r == nil {\n\t\t\tt.Errorf(\"code did not panic when it should have\")\n\t\t}\n\t}()\n\n\t// Should panic - caught by defer\n\tm.RemovePost(postID)\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":"merkle","path":"gno.land/p/demo/merkle","files":[{"name":"README.md","body":"# p/demo/merkle\n\nThis package implement a merkle tree that is complient with [merkletreejs](https://github.com/merkletreejs/merkletreejs)\n\n## [merkletreejs](https://github.com/merkletreejs/merkletreejs)\n\n```javascript\nconst { MerkleTree } = require(\"merkletreejs\");\nconst SHA256 = require(\"crypto-js/sha256\");\n\nlet leaves = [];\nfor (let i = 0; i \u003c 10; i++) {\n leaves.push(SHA256(`node_${i}`));\n}\n\nconst tree = new MerkleTree(leaves, SHA256);\nconst root = tree.getRoot().toString(\"hex\");\n\nconsole.log(root); // cd8a40502b0b92bf58e7432a5abb2d8b60121cf2b7966d6ebaf103f907a1bc21\n```\n"},{"name":"merkle.gno","body":"package merkle\n\nimport (\n\t\"bytes\"\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n\t\"errors\"\n)\n\ntype Hashable interface {\n\tBytes() []byte\n}\n\ntype nodes []Node\n\ntype Node struct {\n\thash []byte\n\n\tposition uint8\n}\n\nfunc NewNode(hash []byte, position uint8) Node {\n\treturn Node{\n\t\thash: hash,\n\t\tposition: position,\n\t}\n}\n\nfunc (n Node) Position() uint8 {\n\treturn n.position\n}\n\nfunc (n Node) Hash() string {\n\treturn hex.EncodeToString(n.hash[:])\n}\n\ntype Tree struct {\n\tlayers []nodes\n}\n\n// Root return the merkle root of the tree\nfunc (t *Tree) Root() string {\n\tfor _, l := range t.layers {\n\t\tif len(l) == 1 {\n\t\t\treturn l[0].Hash()\n\t\t}\n\t}\n\treturn \"\"\n}\n\n// NewTree create a new Merkle Tree\nfunc NewTree(data []Hashable) *Tree {\n\ttree := \u0026Tree{}\n\n\tleaves := make([]Node, len(data))\n\n\tfor i, d := range data {\n\t\thash := sha256.Sum256(d.Bytes())\n\t\tleaves[i] = Node{hash: hash[:]}\n\t}\n\n\ttree.layers = []nodes{nodes(leaves)}\n\n\tvar buff bytes.Buffer\n\tfor len(leaves) \u003e 1 {\n\t\tlevel := make([]Node, 0, len(leaves)/2+1)\n\t\tfor i := 0; i \u003c len(leaves); i += 2 {\n\t\t\tbuff.Reset()\n\n\t\t\tif i \u003c len(leaves)-1 {\n\t\t\t\tbuff.Write(leaves[i].hash)\n\t\t\t\tbuff.Write(leaves[i+1].hash)\n\t\t\t\thash := sha256.Sum256(buff.Bytes())\n\t\t\t\tlevel = append(level, Node{\n\t\t\t\t\thash: hash[:],\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tlevel = append(level, leaves[i])\n\t\t\t}\n\t\t}\n\t\tleaves = level\n\t\ttree.layers = append(tree.layers, level)\n\t}\n\treturn tree\n}\n\n// Proof return a MerkleProof\nfunc (t *Tree) Proof(data Hashable) ([]Node, error) {\n\ttargetHash := sha256.Sum256(data.Bytes())\n\ttargetIndex := -1\n\n\tfor i, layer := range t.layers[0] {\n\t\tif bytes.Equal(targetHash[:], layer.hash) {\n\t\t\ttargetIndex = i\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif targetIndex == -1 {\n\t\treturn nil, errors.New(\"target not found\")\n\t}\n\n\tproofs := make([]Node, 0, len(t.layers))\n\n\tfor _, layer := range t.layers {\n\t\tvar pairIndex int\n\n\t\tif targetIndex%2 == 0 {\n\t\t\tpairIndex = targetIndex + 1\n\t\t} else {\n\t\t\tpairIndex = targetIndex - 1\n\t\t}\n\t\tif pairIndex \u003c len(layer) {\n\t\t\tproofs = append(proofs, Node{\n\t\t\t\thash: layer[pairIndex].hash,\n\t\t\t\tposition: uint8(targetIndex) % 2,\n\t\t\t})\n\t\t}\n\t\ttargetIndex /= 2\n\t}\n\treturn proofs, nil\n}\n\n// Verify if a merkle proof is valid\nfunc (t *Tree) Verify(leaf Hashable, proofs []Node) bool {\n\treturn Verify(t.Root(), leaf, proofs)\n}\n\n// Verify if a merkle proof is valid\nfunc Verify(root string, leaf Hashable, proofs []Node) bool {\n\thash := sha256.Sum256(leaf.Bytes())\n\n\tfor i := 0; i \u003c len(proofs); i += 1 {\n\t\tvar h []byte\n\t\tif proofs[i].position == 0 {\n\t\t\th = append(hash[:], proofs[i].hash...)\n\t\t} else {\n\t\t\th = append(proofs[i].hash, hash[:]...)\n\t\t}\n\t\thash = sha256.Sum256(h)\n\t}\n\treturn hex.EncodeToString(hash[:]) == root\n}\n"},{"name":"merkle_test.gno","body":"package merkle\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n)\n\ntype testData struct {\n\tcontent string\n}\n\nfunc (d testData) Bytes() []byte {\n\treturn []byte(d.content)\n}\n\nfunc TestMerkleTree(t *testing.T) {\n\ttests := []struct {\n\t\tsize int\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tsize: 1,\n\t\t\texpected: \"cf9f824bce7f5bc63d557b23591f58577f53fe29f974a615bdddbd0140f912f4\",\n\t\t},\n\t\t{\n\t\t\tsize: 3,\n\t\t\texpected: \"1a4a5f0fa267244bf9f74a63fdf2a87eed5e97e4bd104a9e94728c8fb5442177\",\n\t\t},\n\t\t{\n\t\t\tsize: 10,\n\t\t\texpected: \"cd8a40502b0b92bf58e7432a5abb2d8b60121cf2b7966d6ebaf103f907a1bc21\",\n\t\t},\n\t\t{\n\t\t\tsize: 1000,\n\t\t\texpected: \"fa533d2efdf12be26bc410dfa42936ac63361324e35e9b1ff54d422a1dd2388b\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tvar leaves []Hashable\n\t\tfor i := 0; i \u003c test.size; i++ {\n\t\t\tleaves = append(leaves, testData{fmt.Sprintf(\"node_%d\", i)})\n\t\t}\n\n\t\ttree := NewTree(leaves)\n\n\t\tif tree == nil {\n\t\t\tt.Error(\"Merkle tree creation failed\")\n\t\t}\n\n\t\troot := tree.Root()\n\n\t\tif root != test.expected {\n\t\t\tt.Fatalf(\"merkle.Tree.Root(), expected: %s; got: %s\", test.expected, root)\n\t\t}\n\n\t\tfor _, leaf := range leaves {\n\t\t\tproofs, err := tree.Proof(leaf)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(\"failed to proof leaf: %v, on tree: %v\", leaf, test)\n\t\t\t}\n\n\t\t\tok := Verify(root, leaf, proofs)\n\t\t\tif !ok {\n\t\t\t\tt.Fatal(\"failed to verify leaf: %v, on tree: %v\", leaf, tree)\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":"microblog","path":"gno.land/p/demo/microblog","files":[{"name":"microblog.gno","body":"package microblog\n\nimport (\n\t\"errors\"\n\t\"sort\"\n\t\"std\"\n\t\"strings\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar (\n\tErrNotFound = errors.New(\"not found\")\n\tStatusNotFound = \"404\"\n)\n\ntype Microblog struct {\n\tTitle string\n\tPrefix string // i.e. r/gnoland/blog:\n\tPages avl.Tree // author (string) -\u003e Page\n}\n\nfunc NewMicroblog(title string, prefix string) (m *Microblog) {\n\treturn \u0026Microblog{\n\t\tTitle: title,\n\t\tPrefix: prefix,\n\t\tPages: avl.Tree{},\n\t}\n}\n\nfunc (m *Microblog) GetPages() []*Page {\n\tvar (\n\t\tpages = make([]*Page, m.Pages.Size())\n\t\tindex = 0\n\t)\n\n\tm.Pages.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tpages[index] = value.(*Page)\n\t\tindex++\n\t\treturn false\n\t})\n\n\tsort.Sort(byLastPosted(pages))\n\n\treturn pages\n}\n\nfunc (m *Microblog) NewPost(text string) error {\n\tauthor := std.OriginCaller()\n\t_, found := m.Pages.Get(author.String())\n\tif !found {\n\t\t// make a new page for the new author\n\t\tm.Pages.Set(author.String(), \u0026Page{\n\t\t\tAuthor: author,\n\t\t\tCreatedAt: time.Now(),\n\t\t})\n\t}\n\n\tpage, err := m.GetPage(author.String())\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn page.NewPost(text)\n}\n\nfunc (m *Microblog) GetPage(author string) (*Page, error) {\n\tsilo, found := m.Pages.Get(author)\n\tif !found {\n\t\treturn nil, ErrNotFound\n\t}\n\treturn silo.(*Page), nil\n}\n\ntype Page struct {\n\tID int\n\tAuthor std.Address\n\tCreatedAt time.Time\n\tLastPosted time.Time\n\tPosts avl.Tree // time -\u003e Post\n}\n\n// byLastPosted implements sort.Interface for []Page based on\n// the LastPosted field.\ntype byLastPosted []*Page\n\nfunc (a byLastPosted) Len() int { return len(a) }\nfunc (a byLastPosted) Swap(i, j int) { a[i], a[j] = a[j], a[i] }\nfunc (a byLastPosted) Less(i, j int) bool { return a[i].LastPosted.After(a[j].LastPosted) }\n\nfunc (p *Page) NewPost(text string) error {\n\tnow := time.Now()\n\tp.LastPosted = now\n\tp.Posts.Set(ufmt.Sprintf(\"%s%d\", now.Format(time.RFC3339), p.Posts.Size()), \u0026Post{\n\t\tID: p.Posts.Size(),\n\t\tText: text,\n\t\tCreatedAt: now,\n\t})\n\treturn nil\n}\n\nfunc (p *Page) GetPosts() []*Post {\n\tposts := make([]*Post, p.Posts.Size())\n\ti := 0\n\tp.Posts.ReverseIterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tpostParsed := value.(*Post)\n\t\tposts[i] = postParsed\n\t\ti++\n\t\treturn false\n\t})\n\treturn posts\n}\n\n// Post lists the specific update\ntype Post struct {\n\tID int\n\tCreatedAt time.Time\n\tText string\n}\n\nfunc (p *Post) String() string {\n\treturn \"\u003e \" + strings.ReplaceAll(p.Text, \"\\n\", \"\\n\u003e\\n\u003e\") + \"\\n\u003e\\n\u003e *\" + p.CreatedAt.Format(time.RFC1123) + \"*\"\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":"nestedpkg","path":"gno.land/p/demo/nestedpkg","files":[{"name":"nestedpkg.gno","body":"// Package nestedpkg provides helpers for package-path based access control.\n// It is useful for upgrade patterns relying on namespaces.\npackage nestedpkg\n\n// To test this from a realm and have std.CurrentRealm/PreviousRealm work correctly,\n// this file is tested from gno.land/r/demo/tests/nestedpkg_test.gno\n// XXX: move test to ths directory once we support testing a package and\n// specifying values for both PreviousRealm and CurrentRealm.\n\nimport (\n\t\"std\"\n\t\"strings\"\n)\n\n// IsCallerSubPath checks if the caller realm is located in a subfolder of the current realm.\nfunc IsCallerSubPath() bool {\n\tvar (\n\t\tcur = std.CurrentRealm().PkgPath() + \"/\"\n\t\tprev = std.PreviousRealm().PkgPath() + \"/\"\n\t)\n\treturn strings.HasPrefix(prev, cur)\n}\n\n// AssertCallerIsSubPath panics if IsCallerSubPath returns false.\nfunc AssertCallerIsSubPath() {\n\tvar (\n\t\tcur = std.CurrentRealm().PkgPath() + \"/\"\n\t\tprev = std.PreviousRealm().PkgPath() + \"/\"\n\t)\n\tif !strings.HasPrefix(prev, cur) {\n\t\tpanic(\"call restricted to nested packages. current realm is \" + cur + \", previous realm is \" + prev)\n\t}\n}\n\n// IsCallerParentPath checks if the caller realm is located in a parent location of the current realm.\nfunc IsCallerParentPath() bool {\n\tvar (\n\t\tcur = std.CurrentRealm().PkgPath() + \"/\"\n\t\tprev = std.PreviousRealm().PkgPath() + \"/\"\n\t)\n\treturn strings.HasPrefix(cur, prev)\n}\n\n// AssertCallerIsParentPath panics if IsCallerParentPath returns false.\nfunc AssertCallerIsParentPath() {\n\tvar (\n\t\tcur = std.CurrentRealm().PkgPath() + \"/\"\n\t\tprev = std.PreviousRealm().PkgPath() + \"/\"\n\t)\n\tif !strings.HasPrefix(cur, prev) {\n\t\tpanic(\"call restricted to parent packages. current realm is \" + cur + \", previous realm is \" + prev)\n\t}\n}\n\n// IsSameNamespace checks if the caller realm and the current realm are in the same namespace.\nfunc IsSameNamespace() bool {\n\tvar (\n\t\tcur = nsFromPath(std.CurrentRealm().PkgPath()) + \"/\"\n\t\tprev = nsFromPath(std.PreviousRealm().PkgPath()) + \"/\"\n\t)\n\treturn cur == prev\n}\n\n// AssertIsSameNamespace panics if IsSameNamespace returns false.\nfunc AssertIsSameNamespace() {\n\tvar (\n\t\tcur = nsFromPath(std.CurrentRealm().PkgPath()) + \"/\"\n\t\tprev = nsFromPath(std.PreviousRealm().PkgPath()) + \"/\"\n\t)\n\tif cur != prev {\n\t\tpanic(\"call restricted to packages from the same namespace. current realm is \" + cur + \", previous realm is \" + prev)\n\t}\n}\n\n// nsFromPath extracts the namespace from a package path.\nfunc nsFromPath(pkgpath string) string {\n\tparts := strings.Split(pkgpath, \"/\")\n\n\t// Specifically for gno.land, potential paths are in the form of DOMAIN/r/NAMESPACE/...\n\t// XXX: Consider extra checks.\n\t// XXX: Support non gno.land domains, where p/ and r/ won't be enforced.\n\tif len(parts) \u003e= 3 {\n\t\treturn parts[2]\n\t}\n\treturn \"\"\n}\n\n// XXX: Consider adding IsCallerDirectlySubPath\n// XXX: Consider adding IsCallerDirectlyParentPath\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":"authorizable","path":"gno.land/p/demo/ownable/exts/authorizable","files":[{"name":"authorizable.gno","body":"// Package authorizable is an extension of p/demo/ownable;\n// It allows the user to instantiate an Authorizable struct, which extends\n// p/demo/ownable with a list of users that are authorized for something.\n// By using authorizable, you have a superuser (ownable), as well as another\n// authorization level, which can be used for adding moderators or similar to your realm.\npackage authorizable\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype Authorizable struct {\n\t*ownable.Ownable // owner in ownable is superuser\n\tauthorized *avl.Tree // std.Addr \u003e struct{}{}\n}\n\nfunc NewAuthorizable() *Authorizable {\n\ta := \u0026Authorizable{\n\t\townable.New(),\n\t\tavl.NewTree(),\n\t}\n\n\t// Add owner to auth list\n\ta.authorized.Set(a.Owner().String(), struct{}{})\n\treturn a\n}\n\nfunc NewAuthorizableWithAddress(addr std.Address) *Authorizable {\n\ta := \u0026Authorizable{\n\t\townable.NewWithAddress(addr),\n\t\tavl.NewTree(),\n\t}\n\n\t// Add owner to auth list\n\ta.authorized.Set(a.Owner().String(), struct{}{})\n\treturn a\n}\n\nfunc (a *Authorizable) AddToAuthList(addr std.Address) error {\n\tif err := a.CallerIsOwner(); err != nil {\n\t\treturn ErrNotSuperuser\n\t}\n\n\tif _, exists := a.authorized.Get(addr.String()); exists {\n\t\treturn ErrAlreadyInList\n\t}\n\n\ta.authorized.Set(addr.String(), struct{}{})\n\n\treturn nil\n}\n\nfunc (a *Authorizable) DeleteFromAuthList(addr std.Address) error {\n\tif err := a.CallerIsOwner(); err != nil {\n\t\treturn ErrNotSuperuser\n\t}\n\n\tif !a.authorized.Has(addr.String()) {\n\t\treturn ErrNotInAuthList\n\t}\n\n\tif _, removed := a.authorized.Remove(addr.String()); !removed {\n\t\tstr := ufmt.Sprintf(\"authorizable: could not remove %s from auth list\", addr.String())\n\t\tpanic(str)\n\t}\n\n\treturn nil\n}\n\nfunc (a Authorizable) CallerOnAuthList() error {\n\tcaller := std.PreviousRealm().Addr()\n\n\tif !a.authorized.Has(caller.String()) {\n\t\treturn ErrNotInAuthList\n\t}\n\n\treturn nil\n}\n\nfunc (a Authorizable) AssertOnAuthList() {\n\tcaller := std.PreviousRealm().Addr()\n\n\tif !a.authorized.Has(caller.String()) {\n\t\tpanic(ErrNotInAuthList)\n\t}\n}\n"},{"name":"authorizable_test.gno","body":"package authorizable\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\talice = testutils.TestAddress(\"alice\")\n\tbob = testutils.TestAddress(\"bob\")\n\tcharlie = testutils.TestAddress(\"charlie\")\n)\n\nfunc TestNewAuthorizable(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOriginCaller(alice) // TODO(bug, issue #2371): should not be needed\n\n\ta := NewAuthorizable()\n\tgot := a.Owner()\n\n\tif alice != got {\n\t\tt.Fatalf(\"Expected %s, got: %s\", alice, got)\n\t}\n}\n\nfunc TestNewAuthorizableWithAddress(t *testing.T) {\n\ta := NewAuthorizableWithAddress(alice)\n\n\tgot := a.Owner()\n\n\tif alice != got {\n\t\tt.Fatalf(\"Expected %s, got: %s\", alice, got)\n\t}\n}\n\nfunc TestCallerOnAuthList(t *testing.T) {\n\ta := NewAuthorizableWithAddress(alice)\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOriginCaller(alice)\n\n\tif err := a.CallerOnAuthList(); err == ErrNotInAuthList {\n\t\tt.Fatalf(\"expected alice to be on the list\")\n\t}\n}\n\nfunc TestNotCallerOnAuthList(t *testing.T) {\n\ta := NewAuthorizableWithAddress(alice)\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\tstd.TestSetOriginCaller(bob)\n\n\tif err := a.CallerOnAuthList(); err == nil {\n\t\tt.Fatalf(\"expected bob to not be on the list\")\n\t}\n}\n\nfunc TestAddToAuthList(t *testing.T) {\n\ta := NewAuthorizableWithAddress(alice)\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOriginCaller(alice)\n\n\tif err := a.AddToAuthList(bob); err != nil {\n\t\tt.Fatalf(\"Expected no error, got %v\", err)\n\t}\n\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\tstd.TestSetOriginCaller(bob)\n\n\tif err := a.AddToAuthList(bob); err == nil {\n\t\tt.Fatalf(\"Expected AddToAuth to error while bob called it, but it didn't\")\n\t}\n}\n\nfunc TestDeleteFromList(t *testing.T) {\n\ta := NewAuthorizableWithAddress(alice)\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOriginCaller(alice)\n\n\tif err := a.AddToAuthList(bob); err != nil {\n\t\tt.Fatalf(\"Expected no error, got %v\", err)\n\t}\n\n\tif err := a.AddToAuthList(charlie); err != nil {\n\t\tt.Fatalf(\"Expected no error, got %v\", err)\n\t}\n\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\tstd.TestSetOriginCaller(bob)\n\n\t// Try an unauthorized deletion\n\tif err := a.DeleteFromAuthList(alice); err == nil {\n\t\tt.Fatalf(\"Expected DelFromAuth to error with %v\", err)\n\t}\n\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOriginCaller(alice)\n\n\tif err := a.DeleteFromAuthList(charlie); err != nil {\n\t\tt.Fatalf(\"Expected no error, got %v\", err)\n\t}\n}\n\nfunc TestAssertOnList(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOriginCaller(alice)\n\ta := NewAuthorizableWithAddress(alice)\n\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\tstd.TestSetOriginCaller(bob)\n\n\tuassert.PanicsWithMessage(t, ErrNotInAuthList.Error(), func() {\n\t\ta.AssertOnAuthList()\n\t})\n}\n"},{"name":"errors.gno","body":"package authorizable\n\nimport \"errors\"\n\nvar (\n\tErrNotInAuthList = errors.New(\"authorizable: caller is not in authorized list\")\n\tErrNotSuperuser = errors.New(\"authorizable: caller is not superuser\")\n\tErrAlreadyInList = errors.New(\"authorizable: address is already in authorized list\")\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":"authorizable","path":"gno.land/p/demo/ownable/exts/authorizable","files":[{"name":"authorizable.gno","body":"// Package authorizable is an extension of p/demo/ownable;\n// It allows the user to instantiate an Authorizable struct, which extends\n// p/demo/ownable with a list of users that are authorized for something.\n// By using authorizable, you have a superuser (ownable), as well as another\n// authorization level, which can be used for adding moderators or similar to your realm.\npackage authorizable\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype Authorizable struct {\n\t*ownable.Ownable // owner in ownable is superuser\n\tauthorized *avl.Tree // std.Addr \u003e struct{}{}\n}\n\nfunc NewAuthorizable() *Authorizable {\n\ta := \u0026Authorizable{\n\t\townable.New(),\n\t\tavl.NewTree(),\n\t}\n\n\t// Add owner to auth list\n\ta.authorized.Set(a.Owner().String(), struct{}{})\n\treturn a\n}\n\nfunc NewAuthorizableWithAddress(addr std.Address) *Authorizable {\n\ta := \u0026Authorizable{\n\t\townable.NewWithAddress(addr),\n\t\tavl.NewTree(),\n\t}\n\n\t// Add owner to auth list\n\ta.authorized.Set(a.Owner().String(), struct{}{})\n\treturn a\n}\n\nfunc (a *Authorizable) AddToAuthList(addr std.Address) error {\n\tif err := a.CallerIsOwner(); err != nil {\n\t\treturn ErrNotSuperuser\n\t}\n\n\tif _, exists := a.authorized.Get(addr.String()); exists {\n\t\treturn ErrAlreadyInList\n\t}\n\n\ta.authorized.Set(addr.String(), struct{}{})\n\n\treturn nil\n}\n\nfunc (a *Authorizable) DeleteFromAuthList(addr std.Address) error {\n\tif err := a.CallerIsOwner(); err != nil {\n\t\treturn ErrNotSuperuser\n\t}\n\n\tif !a.authorized.Has(addr.String()) {\n\t\treturn ErrNotInAuthList\n\t}\n\n\tif _, removed := a.authorized.Remove(addr.String()); !removed {\n\t\tstr := ufmt.Sprintf(\"authorizable: could not remove %s from auth list\", addr.String())\n\t\tpanic(str)\n\t}\n\n\treturn nil\n}\n\nfunc (a Authorizable) CallerOnAuthList() error {\n\tcaller := std.PreviousRealm().Address()\n\n\tif !a.authorized.Has(caller.String()) {\n\t\treturn ErrNotInAuthList\n\t}\n\n\treturn nil\n}\n\nfunc (a Authorizable) AssertOnAuthList() {\n\tcaller := std.PreviousRealm().Address()\n\n\tif !a.authorized.Has(caller.String()) {\n\t\tpanic(ErrNotInAuthList)\n\t}\n}\n"},{"name":"authorizable_test.gno","body":"package authorizable\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\talice = testutils.TestAddress(\"alice\")\n\tbob = testutils.TestAddress(\"bob\")\n\tcharlie = testutils.TestAddress(\"charlie\")\n)\n\nfunc TestNewAuthorizable(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOriginCaller(alice) // TODO(bug, issue #2371): should not be needed\n\n\ta := NewAuthorizable()\n\tgot := a.Owner()\n\n\tif alice != got {\n\t\tt.Fatalf(\"Expected %s, got: %s\", alice, got)\n\t}\n}\n\nfunc TestNewAuthorizableWithAddress(t *testing.T) {\n\ta := NewAuthorizableWithAddress(alice)\n\n\tgot := a.Owner()\n\n\tif alice != got {\n\t\tt.Fatalf(\"Expected %s, got: %s\", alice, got)\n\t}\n}\n\nfunc TestCallerOnAuthList(t *testing.T) {\n\ta := NewAuthorizableWithAddress(alice)\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOriginCaller(alice)\n\n\tif err := a.CallerOnAuthList(); err == ErrNotInAuthList {\n\t\tt.Fatalf(\"expected alice to be on the list\")\n\t}\n}\n\nfunc TestNotCallerOnAuthList(t *testing.T) {\n\ta := NewAuthorizableWithAddress(alice)\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\tstd.TestSetOriginCaller(bob)\n\n\tif err := a.CallerOnAuthList(); err == nil {\n\t\tt.Fatalf(\"expected bob to not be on the list\")\n\t}\n}\n\nfunc TestAddToAuthList(t *testing.T) {\n\ta := NewAuthorizableWithAddress(alice)\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOriginCaller(alice)\n\n\tif err := a.AddToAuthList(bob); err != nil {\n\t\tt.Fatalf(\"Expected no error, got %v\", err)\n\t}\n\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\tstd.TestSetOriginCaller(bob)\n\n\tif err := a.AddToAuthList(bob); err == nil {\n\t\tt.Fatalf(\"Expected AddToAuth to error while bob called it, but it didn't\")\n\t}\n}\n\nfunc TestDeleteFromList(t *testing.T) {\n\ta := NewAuthorizableWithAddress(alice)\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOriginCaller(alice)\n\n\tif err := a.AddToAuthList(bob); err != nil {\n\t\tt.Fatalf(\"Expected no error, got %v\", err)\n\t}\n\n\tif err := a.AddToAuthList(charlie); err != nil {\n\t\tt.Fatalf(\"Expected no error, got %v\", err)\n\t}\n\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\tstd.TestSetOriginCaller(bob)\n\n\t// Try an unauthorized deletion\n\tif err := a.DeleteFromAuthList(alice); err == nil {\n\t\tt.Fatalf(\"Expected DelFromAuth to error with %v\", err)\n\t}\n\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOriginCaller(alice)\n\n\tif err := a.DeleteFromAuthList(charlie); err != nil {\n\t\tt.Fatalf(\"Expected no error, got %v\", err)\n\t}\n}\n\nfunc TestAssertOnList(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tstd.TestSetOriginCaller(alice)\n\ta := NewAuthorizableWithAddress(alice)\n\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\tstd.TestSetOriginCaller(bob)\n\n\tuassert.PanicsWithMessage(t, ErrNotInAuthList.Error(), func() {\n\t\ta.AssertOnAuthList()\n\t})\n}\n"},{"name":"errors.gno","body":"package authorizable\n\nimport \"errors\"\n\nvar (\n\tErrNotInAuthList = errors.New(\"authorizable: caller is not in authorized list\")\n\tErrNotSuperuser = errors.New(\"authorizable: caller is not superuser\")\n\tErrAlreadyInList = errors.New(\"authorizable: address is already in authorized list\")\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":"pausable","path":"gno.land/p/demo/pausable","files":[{"name":"pausable.gno","body":"package pausable\n\nimport \"gno.land/p/demo/ownable\"\n\ntype Pausable struct {\n\t*ownable.Ownable\n\tpaused bool\n}\n\n// New returns a new Pausable struct with non-paused state as default\nfunc New() *Pausable {\n\treturn \u0026Pausable{\n\t\tOwnable: ownable.New(),\n\t\tpaused: false,\n\t}\n}\n\n// NewFromOwnable is the same as New, but with a pre-existing top-level ownable\nfunc NewFromOwnable(ownable *ownable.Ownable) *Pausable {\n\treturn \u0026Pausable{\n\t\tOwnable: ownable,\n\t\tpaused: false,\n\t}\n}\n\n// IsPaused checks if Pausable is paused\nfunc (p Pausable) IsPaused() bool {\n\treturn p.paused\n}\n\n// Pause sets the state of Pausable to true, meaning all pausable functions are paused\nfunc (p *Pausable) Pause() error {\n\tif err := p.CallerIsOwner(); err != nil {\n\t\treturn err\n\t}\n\n\tp.paused = true\n\treturn nil\n}\n\n// Unpause sets the state of Pausable to false, meaning all pausable functions are resumed\nfunc (p *Pausable) Unpause() error {\n\tif err := p.CallerIsOwner(); err != nil {\n\t\treturn err\n\t}\n\n\tp.paused = false\n\treturn nil\n}\n"},{"name":"pausable_test.gno","body":"package pausable\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nvar (\n\tfirstCaller = std.Address(\"g1l9aypkr8xfvs82zeux486ddzec88ty69lue9de\")\n\tsecondCaller = std.Address(\"g127jydsh6cms3lrtdenydxsckh23a8d6emqcvfa\")\n)\n\nfunc TestNew(t *testing.T) {\n\tstd.TestSetOriginCaller(firstCaller)\n\n\tresult := New()\n\n\turequire.False(t, result.paused, \"Expected result to be unpaused\")\n\turequire.Equal(t, firstCaller.String(), result.Owner().String())\n}\n\nfunc TestNewFromOwnable(t *testing.T) {\n\tstd.TestSetOriginCaller(firstCaller)\n\to := ownable.New()\n\n\tstd.TestSetOriginCaller(secondCaller)\n\tresult := NewFromOwnable(o)\n\n\turequire.Equal(t, firstCaller.String(), result.Owner().String())\n}\n\nfunc TestSetUnpaused(t *testing.T) {\n\tstd.TestSetOriginCaller(firstCaller)\n\n\tresult := New()\n\tresult.Unpause()\n\n\turequire.False(t, result.IsPaused(), \"Expected result to be unpaused\")\n}\n\nfunc TestSetPaused(t *testing.T) {\n\tstd.TestSetOriginCaller(firstCaller)\n\n\tresult := New()\n\tresult.Pause()\n\n\turequire.True(t, result.IsPaused(), \"Expected result to be paused\")\n}\n\nfunc TestIsPaused(t *testing.T) {\n\tstd.TestSetOriginCaller(firstCaller)\n\n\tresult := New()\n\turequire.False(t, result.IsPaused(), \"Expected result to be unpaused\")\n\n\tresult.Pause()\n\turequire.True(t, result.IsPaused(), \"Expected result to be paused\")\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":"releases","path":"gno.land/p/demo/releases","files":[{"name":"changelog.gno","body":"package releases\n\ntype changelog struct {\n\tname string\n\treleases []release\n}\n\nfunc NewChangelog(name string) *changelog {\n\treturn \u0026changelog{\n\t\tname: name,\n\t\treleases: make([]release, 0),\n\t}\n}\n\nfunc (c *changelog) NewRelease(version, url, notes string) {\n\tif latest := c.Latest(); latest != nil {\n\t\tlatest.isLatest = false\n\t}\n\n\trelease := release{\n\t\t// manual\n\t\tversion: version,\n\t\turl: url,\n\t\tnotes: notes,\n\n\t\t// internal\n\t\tchangelog: c,\n\t\tisLatest: true,\n\t}\n\n\tc.releases = append(c.releases, release)\n}\n\nfunc (c *changelog) Render(path string) string {\n\tif path == \"\" {\n\t\toutput := \"# \" + c.name + \"\\n\\n\"\n\t\tmax := len(c.releases) - 1\n\t\tmin := 0\n\t\tif max-min \u003e 10 {\n\t\t\tmin = max - 10\n\t\t}\n\t\tfor i := max; i \u003e= min; i-- {\n\t\t\trelease := c.releases[i]\n\t\t\toutput += release.Render()\n\t\t}\n\t\treturn output\n\t}\n\n\trelease := c.ByVersion(path)\n\tif release != nil {\n\t\treturn release.Render()\n\t}\n\n\treturn \"no such release\"\n}\n\nfunc (c *changelog) Latest() *release {\n\tif len(c.releases) \u003e 0 {\n\t\tpos := len(c.releases) - 1\n\t\treturn \u0026c.releases[pos]\n\t}\n\treturn nil\n}\n\nfunc (c *changelog) ByVersion(version string) *release {\n\tfor _, release := range c.releases {\n\t\tif release.version == version {\n\t\t\treturn \u0026release\n\t\t}\n\t}\n\treturn nil\n}\n"},{"name":"release.gno","body":"package releases\n\ntype release struct {\n\t// manual\n\tversion string\n\turl string\n\tnotes string\n\n\t// internal\n\tisLatest bool\n\tchangelog *changelog\n}\n\nfunc (r *release) URL() string { return r.url }\nfunc (r *release) Version() string { return r.version }\nfunc (r *release) Notes() string { return r.notes }\nfunc (r *release) IsLatest() bool { return r.isLatest }\n\nfunc (r *release) Title() string {\n\toutput := r.changelog.name + \" \" + r.version\n\tif r.isLatest {\n\t\toutput += \" (latest)\"\n\t}\n\treturn output\n}\n\nfunc (r *release) Link() string {\n\treturn \"[\" + r.Title() + \"](\" + r.url + \")\"\n}\n\nfunc (r *release) Render() string {\n\toutput := \"\"\n\toutput += \"## \" + r.Link() + \"\\n\\n\"\n\tif r.notes != \"\" {\n\t\toutput += r.notes + \"\\n\\n\"\n\t}\n\treturn output\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":"simpledao","path":"gno.land/p/demo/simpledao","files":[{"name":"dao.gno","body":"package simpledao\n\nimport (\n\t\"errors\"\n\t\"std\"\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\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\tvar (\n\t\tcaller = getDAOCaller()\n\t\tsentCoins = std.OriginSend() // 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\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.OriginSend() // 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.OriginCaller()\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(\"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\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.TestSetOriginSend(sentCoins, std.Coins{})\n\n\t\t_, err := s.Propose(dao.ProposalRequest{\n\t\t\tExecutor: ex,\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\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.TestSetOriginSend(sentCoins, std.Coins{})\n\t\tstd.TestSetOriginCaller(proposer)\n\n\t\t// Make sure the proposal was added\n\t\tid, err := s.Propose(dao.ProposalRequest{\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, 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.TestSetOriginCaller(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.TestSetOriginCaller(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.TestSetOriginCaller(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.TestSetOriginCaller(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.TestSetOriginCaller(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.TestSetOriginCaller(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.TestSetOriginCaller(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.TestSetOriginCaller(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.TestSetOriginCaller(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.TestSetOriginCaller(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.TestSetOriginCaller(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.TestSetOriginSend(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.TestSetOriginSend(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.TestSetOriginCaller(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.TestSetOriginCaller(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.TestSetOriginCaller(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.TestSetOriginCaller(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\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\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) 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\toutput := \"\"\n\toutput += ufmt.Sprintf(\"Author: %s\", p.Author().String())\n\toutput += \"\\n\\n\"\n\toutput += p.Description()\n\toutput += \"\\n\\n\"\n\toutput += ufmt.Sprintf(\"Status: %s\", p.Status().String())\n\toutput += \"\\n\\n\"\n\toutput += ufmt.Sprintf(\n\t\t\"Voting stats: YES %d (%d%%), NO %d (%d%%), ABSTAIN %d (%d%%), MISSING VOTE %d (%d%%)\",\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\toutput += \"\\n\\n\"\n\toutput += ufmt.Sprintf(\"Threshold met: %t\", stats.YayVotes \u003e (2*stats.TotalVotingPower)/3)\n\n\treturn output\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_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"stack","path":"gno.land/p/demo/stack","files":[{"name":"stack.gno","body":"package stack\n\ntype Stack struct {\n\ttop *node\n\tlength int\n}\n\ntype node struct {\n\tvalue interface{}\n\tprev *node\n}\n\nfunc New() *Stack {\n\treturn \u0026Stack{nil, 0}\n}\n\nfunc (s *Stack) Len() int {\n\treturn s.length\n}\n\nfunc (s *Stack) Top() interface{} {\n\tif s.length == 0 {\n\t\treturn nil\n\t}\n\treturn s.top.value\n}\n\nfunc (s *Stack) Pop() interface{} {\n\tif s.length == 0 {\n\t\treturn nil\n\t}\n\n\tnode := s.top\n\ts.top = node.prev\n\ts.length -= 1\n\treturn node.value\n}\n\nfunc (s *Stack) Push(value interface{}) {\n\tnode := \u0026node{value, s.top}\n\ts.top = node\n\ts.length += 1\n}\n"},{"name":"stack_test.gno","body":"package stack\n\nimport \"testing\"\n\nfunc TestStack(t *testing.T) {\n\ts := New() // Empty stack\n\n\tif s.Len() != 0 {\n\t\tt.Errorf(\"s.Len(): expected 0; got %d\", s.Len())\n\t}\n\n\ts.Push(1)\n\n\tif s.Len() != 1 {\n\t\tt.Errorf(\"s.Len(): expected 1; got %d\", s.Len())\n\t}\n\n\tif top := s.Top(); top.(int) != 1 {\n\t\tt.Errorf(\"s.Top(): expected 1; got %v\", top.(int))\n\t}\n\n\tif elem := s.Pop(); elem.(int) != 1 {\n\t\tt.Errorf(\"s.Pop(): expected 1; got %v\", elem.(int))\n\t}\n\tif s.Len() != 0 {\n\t\tt.Errorf(\"s.Len(): expected 0; got %d\", s.Len())\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":"subscription","path":"gno.land/p/demo/subscription","files":[{"name":"doc.gno","body":"// Package subscription provides a flexible system for managing both recurring and\n// lifetime subscriptions in Gno applications. It enables developers to handle\n// payment-based access control for services or products. The library supports\n// both subscriptions requiring periodic payments (recurring) and one-time payments\n// (lifetime). Subscriptions are tracked using an AVL tree for efficient management\n// of subscription statuses.\n//\n// Usage:\n//\n// Import the required sub-packages (`recurring` and/or `lifetime`) to manage specific\n// subscription types. The methods provided allow users to subscribe, check subscription\n// status, and manage payments.\n//\n// Recurring Subscription:\n//\n// Recurring subscriptions require periodic payments to maintain access.\n// Users pay to extend their access for a specific duration.\n//\n// Example:\n//\n//\t// Create a recurring subscription requiring 100 ugnot every 30 days\n//\trecSub := recurring.NewRecurringSubscription(time.Hour * 24 * 30, 100)\n//\n//\t// Process payment for the recurring subscription\n//\trecSub.Subscribe()\n//\n//\t// Gift a recurring subscription to another user\n//\trecSub.GiftSubscription(recipientAddress)\n//\n//\t// Check if a user has a valid subscription\n//\trecSub.HasValidSubscription(addr)\n//\n//\t// Get the expiration date of the subscription\n//\trecSub.GetExpiration(caller)\n//\n//\t// Update the subscription amount to 200 ugnot\n//\trecSub.UpdateAmount(200)\n//\n//\t// Get the current subscription amount\n//\trecSub.GetAmount()\n//\n// Lifetime Subscription:\n//\n// Lifetime subscriptions require a one-time payment for permanent access.\n// Once paid, users have indefinite access without further payments.\n//\n// Example:\n//\n//\t// Create a lifetime subscription costing 500 ugnot\n//\tlifeSub := lifetime.NewLifetimeSubscription(500)\n//\n//\t// Process payment for lifetime access\n//\tlifeSub.Subscribe()\n//\n//\t// Gift a lifetime subscription to another user\n//\tlifeSub.GiftSubscription(recipientAddress)\n//\n//\t// Check if a user has a valid subscription\n//\tlifeSub.HasValidSubscription(addr)\n//\n//\t// Update the lifetime subscription amount to 1000 ugnot\n//\tlifeSub.UpdateAmount(1000)\n//\n//\t// Get the current lifetime subscription amount\n//\tlifeSub.GetAmount()\npackage subscription\n"},{"name":"subscription.gno","body":"package subscription\n\nimport (\n\t\"std\"\n)\n\n// Subscription interface defines standard methods that all subscription types must implement.\ntype Subscription interface {\n\tHasValidSubscription(std.Address) error\n\tSubscribe() error\n\tUpdateAmount(newAmount int64) error\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":"lifetime","path":"gno.land/p/demo/subscription/lifetime","files":[{"name":"errors.gno","body":"package lifetime\n\nimport \"errors\"\n\nvar (\n\tErrNoSub = errors.New(\"lifetime subscription: no active subscription found\")\n\tErrAmt = errors.New(\"lifetime subscription: payment amount does not match the required subscription amount\")\n\tErrAlreadySub = errors.New(\"lifetime subscription: this address already has an active lifetime subscription\")\n\tErrNotAuthorized = errors.New(\"lifetime subscription: action not authorized\")\n)\n"},{"name":"lifetime.gno","body":"package lifetime\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n)\n\n// LifetimeSubscription represents a subscription that requires only a one-time payment.\n// It grants permanent access to a service or product.\ntype LifetimeSubscription struct {\n\townable.Ownable\n\tamount int64\n\tsubs *avl.Tree // std.Address -\u003e bool\n}\n\n// NewLifetimeSubscription creates and returns a new lifetime subscription.\nfunc NewLifetimeSubscription(amount int64) *LifetimeSubscription {\n\treturn \u0026LifetimeSubscription{\n\t\tOwnable: *ownable.New(),\n\t\tamount: amount,\n\t\tsubs: avl.NewTree(),\n\t}\n}\n\n// processSubscription handles the subscription process for a given receiver.\nfunc (ls *LifetimeSubscription) processSubscription(receiver std.Address) error {\n\tamount := std.OriginSend()\n\n\tif amount.AmountOf(\"ugnot\") != ls.amount {\n\t\treturn ErrAmt\n\t}\n\n\t_, exists := ls.subs.Get(receiver.String())\n\n\tif exists {\n\t\treturn ErrAlreadySub\n\t}\n\n\tls.subs.Set(receiver.String(), true)\n\n\treturn nil\n}\n\n// Subscribe processes the payment for a lifetime subscription.\nfunc (ls *LifetimeSubscription) Subscribe() error {\n\tcaller := std.PreviousRealm().Addr()\n\treturn ls.processSubscription(caller)\n}\n\n// GiftSubscription allows the caller to pay for a lifetime subscription for another user.\nfunc (ls *LifetimeSubscription) GiftSubscription(receiver std.Address) error {\n\treturn ls.processSubscription(receiver)\n}\n\n// HasValidSubscription checks if the given address has an active lifetime subscription.\nfunc (ls *LifetimeSubscription) HasValidSubscription(addr std.Address) error {\n\t_, exists := ls.subs.Get(addr.String())\n\n\tif !exists {\n\t\treturn ErrNoSub\n\t}\n\n\treturn nil\n}\n\n// UpdateAmount allows the owner of the LifetimeSubscription contract to update the subscription price.\nfunc (ls *LifetimeSubscription) UpdateAmount(newAmount int64) error {\n\tif err := ls.CallerIsOwner(); err != nil {\n\t\treturn ErrNotAuthorized\n\t}\n\n\tls.amount = newAmount\n\treturn nil\n}\n\n// GetAmount returns the current subscription price.\nfunc (ls *LifetimeSubscription) GetAmount() int64 {\n\treturn ls.amount\n}\n"},{"name":"lifetime_test.gno","body":"package lifetime\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\talice = testutils.TestAddress(\"alice\")\n\tbob = testutils.TestAddress(\"bob\")\n\tcharlie = testutils.TestAddress(\"charlie\")\n)\n\nfunc TestLifetimeSubscription(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := ls.Subscribe()\n\tuassert.NoError(t, err, \"Expected ProcessPayment to succeed\")\n\n\terr = ls.HasValidSubscription(std.PreviousRealm().Addr())\n\tuassert.NoError(t, err, \"Expected Alice to have access\")\n}\n\nfunc TestLifetimeSubscriptionGift(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := ls.GiftSubscription(bob)\n\tuassert.NoError(t, err, \"Expected ProcessPaymentGift to succeed for Bob\")\n\n\terr = ls.HasValidSubscription(bob)\n\tuassert.NoError(t, err, \"Expected Bob to have access\")\n\n\terr = ls.HasValidSubscription(charlie)\n\tuassert.Error(t, err, \"Expected Charlie to fail access check\")\n}\n\nfunc TestUpdateAmountAuthorization(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\terr := ls.UpdateAmount(2000)\n\tuassert.NoError(t, err, \"Expected Alice to succeed in updating amount\")\n\n\tstd.TestSetOriginCaller(bob)\n\n\terr = ls.UpdateAmount(3000)\n\tuassert.Error(t, err, \"Expected Bob to fail when updating amount\")\n}\n\nfunc TestIncorrectPaymentAmount(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 500}}, nil)\n\terr := ls.Subscribe()\n\tuassert.Error(t, err, \"Expected payment to fail with incorrect amount\")\n}\n\nfunc TestMultipleSubscriptionAttempts(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := ls.Subscribe()\n\tuassert.NoError(t, err, \"Expected first subscription to succeed\")\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr = ls.Subscribe()\n\tuassert.Error(t, err, \"Expected second subscription to fail as Alice is already subscribed\")\n}\n\nfunc TestGiftSubscriptionWithIncorrectAmount(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 500}}, nil)\n\terr := ls.GiftSubscription(bob)\n\tuassert.Error(t, err, \"Expected gift subscription to fail with incorrect amount\")\n\n\terr = ls.HasValidSubscription(bob)\n\tuassert.Error(t, err, \"Expected Bob to not have access after incorrect gift subscription\")\n}\n\nfunc TestUpdateAmountEffectiveness(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\terr := ls.UpdateAmount(2000)\n\tuassert.NoError(t, err, \"Expected Alice to succeed in updating amount\")\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr = ls.Subscribe()\n\tuassert.Error(t, err, \"Expected subscription to fail with old amount after update\")\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 2000}}, nil)\n\terr = ls.Subscribe()\n\tuassert.NoError(t, err, \"Expected subscription to succeed with new amount\")\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":"recurring","path":"gno.land/p/demo/subscription/recurring","files":[{"name":"errors.gno","body":"package recurring\n\nimport \"errors\"\n\nvar (\n\tErrNoSub = errors.New(\"recurring subscription: no active subscription found\")\n\tErrSubExpired = errors.New(\"recurring subscription: your subscription has expired\")\n\tErrAmt = errors.New(\"recurring subscription: payment amount does not match the required subscription amount\")\n\tErrAlreadySub = errors.New(\"recurring subscription: this address already has an active subscription\")\n\tErrNotAuthorized = errors.New(\"recurring subscription: action not authorized\")\n)\n"},{"name":"recurring.gno","body":"package recurring\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n)\n\n// RecurringSubscription represents a subscription that requires periodic payments.\n// It includes the duration of the subscription and the amount required per period.\ntype RecurringSubscription struct {\n\townable.Ownable\n\tduration time.Duration\n\tamount int64\n\tsubs *avl.Tree // std.Address -\u003e time.Time\n}\n\n// NewRecurringSubscription creates and returns a new recurring subscription.\nfunc NewRecurringSubscription(duration time.Duration, amount int64) *RecurringSubscription {\n\treturn \u0026RecurringSubscription{\n\t\tOwnable: *ownable.New(),\n\t\tduration: duration,\n\t\tamount: amount,\n\t\tsubs: avl.NewTree(),\n\t}\n}\n\n// HasValidSubscription verifies if the caller has an active recurring subscription.\nfunc (rs *RecurringSubscription) HasValidSubscription(addr std.Address) error {\n\texpTime, exists := rs.subs.Get(addr.String())\n\tif !exists {\n\t\treturn ErrNoSub\n\t}\n\n\tif time.Now().After(expTime.(time.Time)) {\n\t\treturn ErrSubExpired\n\t}\n\n\treturn nil\n}\n\n// processSubscription processes the payment for a given receiver and renews or adds their subscription.\nfunc (rs *RecurringSubscription) processSubscription(receiver std.Address) error {\n\tamount := std.OriginSend()\n\n\tif amount.AmountOf(\"ugnot\") != rs.amount {\n\t\treturn ErrAmt\n\t}\n\n\texpTime, exists := rs.subs.Get(receiver.String())\n\n\t// If the user is already a subscriber but his subscription has expired, authorize renewal\n\tif exists {\n\t\texpiration := expTime.(time.Time)\n\t\tif time.Now().Before(expiration) {\n\t\t\treturn ErrAlreadySub\n\t\t}\n\t}\n\n\t// Renew or add subscription\n\tnewExpiration := time.Now().Add(rs.duration)\n\trs.subs.Set(receiver.String(), newExpiration)\n\n\treturn nil\n}\n\n// Subscribe handles the payment for the caller's subscription.\nfunc (rs *RecurringSubscription) Subscribe() error {\n\tcaller := std.PreviousRealm().Addr()\n\n\treturn rs.processSubscription(caller)\n}\n\n// GiftSubscription allows the user to pay for a subscription for another user (receiver).\nfunc (rs *RecurringSubscription) GiftSubscription(receiver std.Address) error {\n\treturn rs.processSubscription(receiver)\n}\n\n// GetExpiration returns the expiration date of the recurring subscription for a given caller.\nfunc (rs *RecurringSubscription) GetExpiration(addr std.Address) (time.Time, error) {\n\texpTime, exists := rs.subs.Get(addr.String())\n\tif !exists {\n\t\treturn time.Time{}, ErrNoSub\n\t}\n\n\treturn expTime.(time.Time), nil\n}\n\n// UpdateAmount allows the owner of the subscription contract to change the required subscription amount.\nfunc (rs *RecurringSubscription) UpdateAmount(newAmount int64) error {\n\tif err := rs.CallerIsOwner(); err != nil {\n\t\treturn ErrNotAuthorized\n\t}\n\n\trs.amount = newAmount\n\treturn nil\n}\n\n// GetAmount returns the current amount required for each subscription period.\nfunc (rs *RecurringSubscription) GetAmount() int64 {\n\treturn rs.amount\n}\n"},{"name":"recurring_test.gno","body":"package recurring\n\nimport (\n\t\"std\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\talice = testutils.TestAddress(\"alice\")\n\tbob = testutils.TestAddress(\"bob\")\n\tcharlie = testutils.TestAddress(\"charlie\")\n)\n\nfunc TestRecurringSubscription(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := rs.Subscribe()\n\tuassert.NoError(t, err, \"Expected ProcessPayment to succeed for Alice\")\n\n\terr = rs.HasValidSubscription(std.PreviousRealm().Addr())\n\tuassert.NoError(t, err, \"Expected Alice to have access\")\n\n\texpiration, err := rs.GetExpiration(std.PreviousRealm().Addr())\n\tuassert.NoError(t, err, \"Expected to get expiration for Alice\")\n}\n\nfunc TestRecurringSubscriptionGift(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := rs.GiftSubscription(bob)\n\tuassert.NoError(t, err, \"Expected ProcessPaymentGift to succeed for Bob\")\n\n\terr = rs.HasValidSubscription(bob)\n\tuassert.NoError(t, err, \"Expected Bob to have access\")\n\n\terr = rs.HasValidSubscription(charlie)\n\tuassert.Error(t, err, \"Expected Charlie to fail access check\")\n}\n\nfunc TestRecurringSubscriptionExpiration(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour, 1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := rs.Subscribe()\n\tuassert.NoError(t, err, \"Expected ProcessPayment to succeed for Alice\")\n\n\terr = rs.HasValidSubscription(std.PreviousRealm().Addr())\n\tuassert.NoError(t, err, \"Expected Alice to have access\")\n\n\texpiration := time.Now().Add(-time.Hour * 2)\n\trs.subs.Set(std.PreviousRealm().Addr().String(), expiration)\n\n\terr = rs.HasValidSubscription(std.PreviousRealm().Addr())\n\tuassert.Error(t, err, \"Expected Alice's subscription to be expired\")\n}\n\nfunc TestUpdateAmountAuthorization(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\terr := rs.UpdateAmount(2000)\n\tuassert.NoError(t, err, \"Expected Alice to succeed in updating amount\")\n\n\tstd.TestSetOriginCaller(bob)\n\terr = rs.UpdateAmount(3000)\n\tuassert.Error(t, err, \"Expected Bob to fail when updating amount\")\n}\n\nfunc TestGetAmount(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\tamount := rs.GetAmount()\n\tuassert.Equal(t, amount, int64(1000), \"Expected the initial amount to be 1000 ugnot\")\n\n\terr := rs.UpdateAmount(2000)\n\tuassert.NoError(t, err, \"Expected Alice to succeed in updating amount\")\n\n\tamount = rs.GetAmount()\n\tuassert.Equal(t, amount, int64(2000), \"Expected the updated amount to be 2000 ugnot\")\n}\n\nfunc TestIncorrectPaymentAmount(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 500}}, nil)\n\terr := rs.Subscribe()\n\tuassert.Error(t, err, \"Expected payment with incorrect amount to fail\")\n}\n\nfunc TestMultiplePaymentsForSameUser(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := rs.Subscribe()\n\tuassert.NoError(t, err, \"Expected first ProcessPayment to succeed for Alice\")\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr = rs.Subscribe()\n\tuassert.Error(t, err, \"Expected second ProcessPayment to fail for Alice due to existing subscription\")\n}\n\nfunc TestRecurringSubscriptionWithMultiplePayments(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour, 1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := rs.Subscribe()\n\tuassert.NoError(t, err, \"Expected first ProcessPayment to succeed for Alice\")\n\n\terr = rs.HasValidSubscription(std.PreviousRealm().Addr())\n\tuassert.NoError(t, err, \"Expected Alice to have access after first payment\")\n\n\texpiration := time.Now().Add(-time.Hour * 2)\n\trs.subs.Set(std.PreviousRealm().Addr().String(), expiration)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr = rs.Subscribe()\n\tuassert.NoError(t, err, \"Expected second ProcessPayment to succeed for Alice\")\n\n\terr = rs.HasValidSubscription(std.PreviousRealm().Addr())\n\tuassert.NoError(t, err, \"Expected Alice to have access after second payment\")\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":"lifetime","path":"gno.land/p/demo/subscription/lifetime","files":[{"name":"errors.gno","body":"package lifetime\n\nimport \"errors\"\n\nvar (\n\tErrNoSub = errors.New(\"lifetime subscription: no active subscription found\")\n\tErrAmt = errors.New(\"lifetime subscription: payment amount does not match the required subscription amount\")\n\tErrAlreadySub = errors.New(\"lifetime subscription: this address already has an active lifetime subscription\")\n\tErrNotAuthorized = errors.New(\"lifetime subscription: action not authorized\")\n)\n"},{"name":"lifetime.gno","body":"package lifetime\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n)\n\n// LifetimeSubscription represents a subscription that requires only a one-time payment.\n// It grants permanent access to a service or product.\ntype LifetimeSubscription struct {\n\townable.Ownable\n\tamount int64\n\tsubs *avl.Tree // std.Address -\u003e bool\n}\n\n// NewLifetimeSubscription creates and returns a new lifetime subscription.\nfunc NewLifetimeSubscription(amount int64) *LifetimeSubscription {\n\treturn \u0026LifetimeSubscription{\n\t\tOwnable: *ownable.New(),\n\t\tamount: amount,\n\t\tsubs: avl.NewTree(),\n\t}\n}\n\n// processSubscription handles the subscription process for a given receiver.\nfunc (ls *LifetimeSubscription) processSubscription(receiver std.Address) error {\n\tamount := std.OriginSend()\n\n\tif amount.AmountOf(\"ugnot\") != ls.amount {\n\t\treturn ErrAmt\n\t}\n\n\t_, exists := ls.subs.Get(receiver.String())\n\n\tif exists {\n\t\treturn ErrAlreadySub\n\t}\n\n\tls.subs.Set(receiver.String(), true)\n\n\treturn nil\n}\n\n// Subscribe processes the payment for a lifetime subscription.\nfunc (ls *LifetimeSubscription) Subscribe() error {\n\tcaller := std.PreviousRealm().Address()\n\treturn ls.processSubscription(caller)\n}\n\n// GiftSubscription allows the caller to pay for a lifetime subscription for another user.\nfunc (ls *LifetimeSubscription) GiftSubscription(receiver std.Address) error {\n\treturn ls.processSubscription(receiver)\n}\n\n// HasValidSubscription checks if the given address has an active lifetime subscription.\nfunc (ls *LifetimeSubscription) HasValidSubscription(addr std.Address) error {\n\t_, exists := ls.subs.Get(addr.String())\n\n\tif !exists {\n\t\treturn ErrNoSub\n\t}\n\n\treturn nil\n}\n\n// UpdateAmount allows the owner of the LifetimeSubscription contract to update the subscription price.\nfunc (ls *LifetimeSubscription) UpdateAmount(newAmount int64) error {\n\tif err := ls.CallerIsOwner(); err != nil {\n\t\treturn ErrNotAuthorized\n\t}\n\n\tls.amount = newAmount\n\treturn nil\n}\n\n// GetAmount returns the current subscription price.\nfunc (ls *LifetimeSubscription) GetAmount() int64 {\n\treturn ls.amount\n}\n"},{"name":"lifetime_test.gno","body":"package lifetime\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\talice = testutils.TestAddress(\"alice\")\n\tbob = testutils.TestAddress(\"bob\")\n\tcharlie = testutils.TestAddress(\"charlie\")\n)\n\nfunc TestLifetimeSubscription(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := ls.Subscribe()\n\tuassert.NoError(t, err, \"Expected ProcessPayment to succeed\")\n\n\terr = ls.HasValidSubscription(std.PreviousRealm().Address())\n\tuassert.NoError(t, err, \"Expected Alice to have access\")\n}\n\nfunc TestLifetimeSubscriptionGift(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := ls.GiftSubscription(bob)\n\tuassert.NoError(t, err, \"Expected ProcessPaymentGift to succeed for Bob\")\n\n\terr = ls.HasValidSubscription(bob)\n\tuassert.NoError(t, err, \"Expected Bob to have access\")\n\n\terr = ls.HasValidSubscription(charlie)\n\tuassert.Error(t, err, \"Expected Charlie to fail access check\")\n}\n\nfunc TestUpdateAmountAuthorization(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\terr := ls.UpdateAmount(2000)\n\tuassert.NoError(t, err, \"Expected Alice to succeed in updating amount\")\n\n\tstd.TestSetOriginCaller(bob)\n\n\terr = ls.UpdateAmount(3000)\n\tuassert.Error(t, err, \"Expected Bob to fail when updating amount\")\n}\n\nfunc TestIncorrectPaymentAmount(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 500}}, nil)\n\terr := ls.Subscribe()\n\tuassert.Error(t, err, \"Expected payment to fail with incorrect amount\")\n}\n\nfunc TestMultipleSubscriptionAttempts(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := ls.Subscribe()\n\tuassert.NoError(t, err, \"Expected first subscription to succeed\")\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr = ls.Subscribe()\n\tuassert.Error(t, err, \"Expected second subscription to fail as Alice is already subscribed\")\n}\n\nfunc TestGiftSubscriptionWithIncorrectAmount(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 500}}, nil)\n\terr := ls.GiftSubscription(bob)\n\tuassert.Error(t, err, \"Expected gift subscription to fail with incorrect amount\")\n\n\terr = ls.HasValidSubscription(bob)\n\tuassert.Error(t, err, \"Expected Bob to not have access after incorrect gift subscription\")\n}\n\nfunc TestUpdateAmountEffectiveness(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\tls := NewLifetimeSubscription(1000)\n\n\terr := ls.UpdateAmount(2000)\n\tuassert.NoError(t, err, \"Expected Alice to succeed in updating amount\")\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr = ls.Subscribe()\n\tuassert.Error(t, err, \"Expected subscription to fail with old amount after update\")\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 2000}}, nil)\n\terr = ls.Subscribe()\n\tuassert.NoError(t, err, \"Expected subscription to succeed with new amount\")\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":"recurring","path":"gno.land/p/demo/subscription/recurring","files":[{"name":"errors.gno","body":"package recurring\n\nimport \"errors\"\n\nvar (\n\tErrNoSub = errors.New(\"recurring subscription: no active subscription found\")\n\tErrSubExpired = errors.New(\"recurring subscription: your subscription has expired\")\n\tErrAmt = errors.New(\"recurring subscription: payment amount does not match the required subscription amount\")\n\tErrAlreadySub = errors.New(\"recurring subscription: this address already has an active subscription\")\n\tErrNotAuthorized = errors.New(\"recurring subscription: action not authorized\")\n)\n"},{"name":"recurring.gno","body":"package recurring\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n)\n\n// RecurringSubscription represents a subscription that requires periodic payments.\n// It includes the duration of the subscription and the amount required per period.\ntype RecurringSubscription struct {\n\townable.Ownable\n\tduration time.Duration\n\tamount int64\n\tsubs *avl.Tree // std.Address -\u003e time.Time\n}\n\n// NewRecurringSubscription creates and returns a new recurring subscription.\nfunc NewRecurringSubscription(duration time.Duration, amount int64) *RecurringSubscription {\n\treturn \u0026RecurringSubscription{\n\t\tOwnable: *ownable.New(),\n\t\tduration: duration,\n\t\tamount: amount,\n\t\tsubs: avl.NewTree(),\n\t}\n}\n\n// HasValidSubscription verifies if the caller has an active recurring subscription.\nfunc (rs *RecurringSubscription) HasValidSubscription(addr std.Address) error {\n\texpTime, exists := rs.subs.Get(addr.String())\n\tif !exists {\n\t\treturn ErrNoSub\n\t}\n\n\tif time.Now().After(expTime.(time.Time)) {\n\t\treturn ErrSubExpired\n\t}\n\n\treturn nil\n}\n\n// processSubscription processes the payment for a given receiver and renews or adds their subscription.\nfunc (rs *RecurringSubscription) processSubscription(receiver std.Address) error {\n\tamount := std.OriginSend()\n\n\tif amount.AmountOf(\"ugnot\") != rs.amount {\n\t\treturn ErrAmt\n\t}\n\n\texpTime, exists := rs.subs.Get(receiver.String())\n\n\t// If the user is already a subscriber but his subscription has expired, authorize renewal\n\tif exists {\n\t\texpiration := expTime.(time.Time)\n\t\tif time.Now().Before(expiration) {\n\t\t\treturn ErrAlreadySub\n\t\t}\n\t}\n\n\t// Renew or add subscription\n\tnewExpiration := time.Now().Add(rs.duration)\n\trs.subs.Set(receiver.String(), newExpiration)\n\n\treturn nil\n}\n\n// Subscribe handles the payment for the caller's subscription.\nfunc (rs *RecurringSubscription) Subscribe() error {\n\tcaller := std.PreviousRealm().Address()\n\n\treturn rs.processSubscription(caller)\n}\n\n// GiftSubscription allows the user to pay for a subscription for another user (receiver).\nfunc (rs *RecurringSubscription) GiftSubscription(receiver std.Address) error {\n\treturn rs.processSubscription(receiver)\n}\n\n// GetExpiration returns the expiration date of the recurring subscription for a given caller.\nfunc (rs *RecurringSubscription) GetExpiration(addr std.Address) (time.Time, error) {\n\texpTime, exists := rs.subs.Get(addr.String())\n\tif !exists {\n\t\treturn time.Time{}, ErrNoSub\n\t}\n\n\treturn expTime.(time.Time), nil\n}\n\n// UpdateAmount allows the owner of the subscription contract to change the required subscription amount.\nfunc (rs *RecurringSubscription) UpdateAmount(newAmount int64) error {\n\tif err := rs.CallerIsOwner(); err != nil {\n\t\treturn ErrNotAuthorized\n\t}\n\n\trs.amount = newAmount\n\treturn nil\n}\n\n// GetAmount returns the current amount required for each subscription period.\nfunc (rs *RecurringSubscription) GetAmount() int64 {\n\treturn rs.amount\n}\n"},{"name":"recurring_test.gno","body":"package recurring\n\nimport (\n\t\"std\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\talice = testutils.TestAddress(\"alice\")\n\tbob = testutils.TestAddress(\"bob\")\n\tcharlie = testutils.TestAddress(\"charlie\")\n)\n\nfunc TestRecurringSubscription(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := rs.Subscribe()\n\tuassert.NoError(t, err, \"Expected ProcessPayment to succeed for Alice\")\n\n\terr = rs.HasValidSubscription(std.PreviousRealm().Address())\n\tuassert.NoError(t, err, \"Expected Alice to have access\")\n\n\texpiration, err := rs.GetExpiration(std.PreviousRealm().Address())\n\tuassert.NoError(t, err, \"Expected to get expiration for Alice\")\n}\n\nfunc TestRecurringSubscriptionGift(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := rs.GiftSubscription(bob)\n\tuassert.NoError(t, err, \"Expected ProcessPaymentGift to succeed for Bob\")\n\n\terr = rs.HasValidSubscription(bob)\n\tuassert.NoError(t, err, \"Expected Bob to have access\")\n\n\terr = rs.HasValidSubscription(charlie)\n\tuassert.Error(t, err, \"Expected Charlie to fail access check\")\n}\n\nfunc TestRecurringSubscriptionExpiration(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour, 1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := rs.Subscribe()\n\tuassert.NoError(t, err, \"Expected ProcessPayment to succeed for Alice\")\n\n\terr = rs.HasValidSubscription(std.PreviousRealm().Address())\n\tuassert.NoError(t, err, \"Expected Alice to have access\")\n\n\texpiration := time.Now().Add(-time.Hour * 2)\n\trs.subs.Set(std.PreviousRealm().Address().String(), expiration)\n\n\terr = rs.HasValidSubscription(std.PreviousRealm().Address())\n\tuassert.Error(t, err, \"Expected Alice's subscription to be expired\")\n}\n\nfunc TestUpdateAmountAuthorization(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\terr := rs.UpdateAmount(2000)\n\tuassert.NoError(t, err, \"Expected Alice to succeed in updating amount\")\n\n\tstd.TestSetOriginCaller(bob)\n\terr = rs.UpdateAmount(3000)\n\tuassert.Error(t, err, \"Expected Bob to fail when updating amount\")\n}\n\nfunc TestGetAmount(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\tamount := rs.GetAmount()\n\tuassert.Equal(t, amount, int64(1000), \"Expected the initial amount to be 1000 ugnot\")\n\n\terr := rs.UpdateAmount(2000)\n\tuassert.NoError(t, err, \"Expected Alice to succeed in updating amount\")\n\n\tamount = rs.GetAmount()\n\tuassert.Equal(t, amount, int64(2000), \"Expected the updated amount to be 2000 ugnot\")\n}\n\nfunc TestIncorrectPaymentAmount(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 500}}, nil)\n\terr := rs.Subscribe()\n\tuassert.Error(t, err, \"Expected payment with incorrect amount to fail\")\n}\n\nfunc TestMultiplePaymentsForSameUser(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour*24, 1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := rs.Subscribe()\n\tuassert.NoError(t, err, \"Expected first ProcessPayment to succeed for Alice\")\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr = rs.Subscribe()\n\tuassert.Error(t, err, \"Expected second ProcessPayment to fail for Alice due to existing subscription\")\n}\n\nfunc TestRecurringSubscriptionWithMultiplePayments(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\trs := NewRecurringSubscription(time.Hour, 1000)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr := rs.Subscribe()\n\tuassert.NoError(t, err, \"Expected first ProcessPayment to succeed for Alice\")\n\n\terr = rs.HasValidSubscription(std.PreviousRealm().Address())\n\tuassert.NoError(t, err, \"Expected Alice to have access after first payment\")\n\n\texpiration := time.Now().Add(-time.Hour * 2)\n\trs.subs.Set(std.PreviousRealm().Address().String(), expiration)\n\n\tstd.TestSetOriginSend([]std.Coin{{Denom: \"ugnot\", Amount: 1000}}, nil)\n\terr = rs.Subscribe()\n\tuassert.NoError(t, err, \"Expected second ProcessPayment to succeed for Alice\")\n\n\terr = rs.HasValidSubscription(std.PreviousRealm().Address())\n\tuassert.NoError(t, err, \"Expected Alice to have access after second payment\")\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":"svg","path":"gno.land/p/demo/svg","files":[{"name":"doc.gno","body":"/*\nPackage svg is a minimalist SVG generation library for Gno.\n\nThe svg package provides a simple and lightweight solution for programmatically generating SVG (Scalable Vector Graphics) markup in Gno. It allows you to create basic shapes like rectangles and circles, and output the generated SVG to a\n\nExample:\n\n\timport \"gno.land/p/demo/svg\"\"\n\n\tfunc Foo() string {\n\t canvas := svg.Canvas{Width: 200, Height: 200}\n\t canvas.DrawRectangle(50, 50, 100, 100, \"red\")\n\t canvas.DrawCircle(100, 100, 50, \"blue\")\n\t return canvas.String()\n\t}\n*/\npackage svg // import \"gno.land/p/demo/svg\"\n"},{"name":"svg.gno","body":"package svg\n\nimport \"gno.land/p/demo/ufmt\"\n\ntype Canvas struct {\n\tWidth int\n\tHeight int\n\tElems []Elem\n}\n\ntype Elem interface{ String() string }\n\nfunc (c Canvas) String() string {\n\toutput := \"\"\n\toutput += ufmt.Sprintf(`\u003csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"%d\" height=\"%d\"\u003e`, c.Width, c.Height)\n\tfor _, elem := range c.Elems {\n\t\toutput += elem.String()\n\t}\n\toutput += \"\u003c/svg\u003e\"\n\treturn output\n}\n\nfunc (c *Canvas) Append(elem Elem) {\n\tc.Elems = append(c.Elems, elem)\n}\n\ntype Circle struct {\n\tCX int // center X\n\tCY int // center Y\n\tR int // radius\n\tFill string\n}\n\nfunc (c Circle) String() string {\n\treturn ufmt.Sprintf(`\u003ccircle cx=\"%d\" cy=\"%d\" r=\"%d\" fill=\"%s\" /\u003e`, c.CX, c.CY, c.R, c.Fill)\n}\n\nfunc (c *Canvas) DrawCircle(cx, cy, r int, fill string) {\n\tc.Append(Circle{\n\t\tCX: cx,\n\t\tCY: cy,\n\t\tR: r,\n\t\tFill: fill,\n\t})\n}\n\ntype Rectangle struct {\n\tX, Y, Width, Height int\n\tFill string\n}\n\nfunc (c Rectangle) String() string {\n\treturn ufmt.Sprintf(`\u003crect x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\" fill=\"%s\" /\u003e`, c.X, c.Y, c.Width, c.Height, c.Fill)\n}\n\nfunc (c *Canvas) DrawRectangle(x, y, width, height int, fill string) {\n\tc.Append(Rectangle{\n\t\tX: x,\n\t\tY: y,\n\t\tWidth: width,\n\t\tHeight: height,\n\t\tFill: fill,\n\t})\n}\n\ntype Text struct {\n\tX, Y int\n\tText, Fill string\n}\n\nfunc (c Text) String() string {\n\treturn ufmt.Sprintf(`\u003ctext x=\"%d\" y=\"%d\" fill=\"%s\"\u003e%s\u003c/text\u003e`, c.X, c.Y, c.Fill, c.Text)\n}\n\nfunc (c *Canvas) DrawText(x, y int, text, fill string) {\n\tc.Append(Text{\n\t\tX: x,\n\t\tY: y,\n\t\tText: text,\n\t\tFill: fill,\n\t})\n}\n"},{"name":"z0_filetest.gno","body":"// PKGPATH: gno.land/p/demo/svg_test\npackage svg_test\n\nimport \"gno.land/p/demo/svg\"\n\nfunc main() {\n\tcanvas := svg.Canvas{Width: 500, Height: 500}\n\tcanvas.DrawRectangle(50, 50, 100, 100, \"red\")\n\tcanvas.DrawCircle(100, 100, 50, \"blue\")\n\tcanvas.DrawText(100, 100, \"hello world!\", \"magenta\")\n\tprintln(canvas)\n}\n\n// Output:\n// \u003csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"500\" height=\"500\"\u003e\u003crect x=\"50\" y=\"50\" width=\"100\" height=\"100\" fill=\"red\" /\u003e\u003ccircle cx=\"100\" cy=\"100\" r=\"50\" fill=\"blue\" /\u003e\u003ctext x=\"100\" y=\"100\" fill=\"magenta\"\u003ehello world!\u003c/text\u003e\u003c/svg\u003e\n"},{"name":"z1_filetest.gno","body":"// PKGPATH: gno.land/p/demo/svg_test\npackage svg_test\n\nimport \"gno.land/p/demo/svg\"\n\nfunc main() {\n\tcanvas := svg.Canvas{\n\t\tWidth: 500, Height: 500,\n\t\tElems: []svg.Elem{\n\t\t\tsvg.Rectangle{50, 50, 100, 100, \"red\"},\n\t\t\tsvg.Circle{50, 50, 100, \"red\"},\n\t\t\tsvg.Text{100, 100, \"hello world!\", \"magenta\"},\n\t\t},\n\t}\n\tprintln(canvas)\n}\n\n// Output:\n// \u003csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"500\" height=\"500\"\u003e\u003crect x=\"50\" y=\"50\" width=\"100\" height=\"100\" fill=\"red\" /\u003e\u003ccircle cx=\"50\" cy=\"50\" r=\"100\" fill=\"red\" /\u003e\u003ctext x=\"100\" y=\"100\" fill=\"magenta\"\u003ehello world!\u003c/text\u003e\u003c/svg\u003e\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":"tamagotchi","path":"gno.land/p/demo/tamagotchi","files":[{"name":"tamagotchi.gno","body":"package tamagotchi\n\nimport (\n\t\"time\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Tamagotchi structure\ntype Tamagotchi struct {\n\tname string\n\thunger int\n\thappiness int\n\thealth int\n\tage int\n\tmaxAge int\n\tsleepy int\n\tcreated time.Time\n\tlastUpdated time.Time\n}\n\nfunc New(name string) *Tamagotchi {\n\tnow := time.Now()\n\treturn \u0026Tamagotchi{\n\t\tname: name,\n\t\thunger: 50,\n\t\thappiness: 50,\n\t\thealth: 50,\n\t\tmaxAge: 100,\n\t\tlastUpdated: now,\n\t\tcreated: now,\n\t}\n}\n\nfunc (t *Tamagotchi) Name() string {\n\tt.update()\n\treturn t.name\n}\n\nfunc (t *Tamagotchi) Hunger() int {\n\tt.update()\n\treturn t.hunger\n}\n\nfunc (t *Tamagotchi) Happiness() int {\n\tt.update()\n\treturn t.happiness\n}\n\nfunc (t *Tamagotchi) Health() int {\n\tt.update()\n\treturn t.health\n}\n\nfunc (t *Tamagotchi) Age() int {\n\tt.update()\n\treturn t.age\n}\n\nfunc (t *Tamagotchi) Sleepy() int {\n\tt.update()\n\treturn t.sleepy\n}\n\n// Feed method for Tamagotchi\nfunc (t *Tamagotchi) Feed() {\n\tt.update()\n\tif t.dead() {\n\t\treturn\n\t}\n\tt.hunger = bound(t.hunger-10, 0, 100)\n}\n\n// Play method for Tamagotchi\nfunc (t *Tamagotchi) Play() {\n\tt.update()\n\tif t.dead() {\n\t\treturn\n\t}\n\tt.happiness = bound(t.happiness+10, 0, 100)\n}\n\n// Heal method for Tamagotchi\nfunc (t *Tamagotchi) Heal() {\n\tt.update()\n\n\tif t.dead() {\n\t\treturn\n\t}\n\tt.health = bound(t.health+10, 0, 100)\n}\n\nfunc (t Tamagotchi) dead() bool { return t.health == 0 }\n\n// Update applies changes based on the duration since the last update\nfunc (t *Tamagotchi) update() {\n\tif t.dead() {\n\t\treturn\n\t}\n\n\tnow := time.Now()\n\tif t.lastUpdated == now {\n\t\treturn\n\t}\n\n\tduration := now.Sub(t.lastUpdated)\n\telapsedMins := int(duration.Minutes())\n\n\tt.hunger = bound(t.hunger+elapsedMins, 0, 100)\n\tt.happiness = bound(t.happiness-elapsedMins, 0, 100)\n\tt.health = bound(t.health-elapsedMins, 0, 100)\n\tt.sleepy = bound(t.sleepy+elapsedMins, 0, 100)\n\n\t// age is hours since created\n\tt.age = int(now.Sub(t.created).Hours())\n\tif t.age \u003e t.maxAge {\n\t\tt.age = t.maxAge\n\t\tt.health = 0\n\t}\n\tif t.health == 0 {\n\t\tt.sleepy = 0\n\t\tt.happiness = 0\n\t\tt.hunger = 0\n\t}\n\n\tt.lastUpdated = now\n}\n\n// Face returns an ASCII art representation of the Tamagotchi's current state\nfunc (t *Tamagotchi) Face() string {\n\tt.update()\n\treturn t.face()\n}\n\nfunc (t *Tamagotchi) face() string {\n\tswitch {\n\tcase t.health == 0:\n\t\treturn \"😵\" // dead face\n\tcase t.health \u003c 30:\n\t\treturn \"😷\" // sick face\n\tcase t.happiness \u003c 30:\n\t\treturn \"😢\" // sad face\n\tcase t.hunger \u003e 70:\n\t\treturn \"😫\" // hungry face\n\tcase t.sleepy \u003e 70:\n\t\treturn \"😴\" // sleepy face\n\tdefault:\n\t\treturn \"😃\" // happy face\n\t}\n}\n\n// Markdown method for Tamagotchi\nfunc (t *Tamagotchi) Markdown() string {\n\tt.update()\n\treturn ufmt.Sprintf(`# %s %s\n\n* age: %d\n* hunger: %d\n* happiness: %d\n* health: %d\n* sleepy: %d`,\n\t\tt.name, t.Face(),\n\t\tt.age, t.hunger, t.happiness, t.health, t.sleepy,\n\t)\n}\n\nfunc bound(n, min, max int) int {\n\tif n \u003c min {\n\t\treturn min\n\t}\n\tif n \u003e max {\n\t\treturn max\n\t}\n\treturn n\n}\n"},{"name":"z0_filetest.gno","body":"package main\n\nimport (\n\t\"time\"\n\n\t\"internal/os_test\"\n\n\t\"gno.land/p/demo/tamagotchi\"\n)\n\nfunc main() {\n\tt := tamagotchi.New(\"Gnome\")\n\n\tprintln(\"\\n-- INITIAL\\n\")\n\tprintln(t.Markdown())\n\n\tprintln(\"\\n-- WAIT 20 minutes\\n\")\n\tos_test.Sleep(20 * time.Minute)\n\tprintln(t.Markdown())\n\n\tprintln(\"\\n-- FEEDx3, PLAYx2, HEALx4\\n\")\n\tt.Feed()\n\tt.Feed()\n\tt.Feed()\n\tt.Play()\n\tt.Play()\n\tt.Heal()\n\tt.Heal()\n\tt.Heal()\n\tt.Heal()\n\tprintln(t.Markdown())\n\n\tprintln(\"\\n-- WAIT 20 minutes\\n\")\n\tos_test.Sleep(20 * time.Minute)\n\tprintln(t.Markdown())\n\n\tprintln(\"\\n-- WAIT 20 hours\\n\")\n\tos_test.Sleep(20 * time.Hour)\n\tprintln(t.Markdown())\n\n\tprintln(\"\\n-- WAIT 20 hours\\n\")\n\tos_test.Sleep(20 * time.Hour)\n\tprintln(t.Markdown())\n}\n\n// Output:\n// -- INITIAL\n//\n// # Gnome 😃\n//\n// * age: 0\n// * hunger: 50\n// * happiness: 50\n// * health: 50\n// * sleepy: 0\n//\n// -- WAIT 20 minutes\n//\n// # Gnome 😃\n//\n// * age: 0\n// * hunger: 70\n// * happiness: 30\n// * health: 30\n// * sleepy: 20\n//\n// -- FEEDx3, PLAYx2, HEALx4\n//\n// # Gnome 😃\n//\n// * age: 0\n// * hunger: 40\n// * happiness: 50\n// * health: 70\n// * sleepy: 20\n//\n// -- WAIT 20 minutes\n//\n// # Gnome 😃\n//\n// * age: 0\n// * hunger: 60\n// * happiness: 30\n// * health: 50\n// * sleepy: 40\n//\n// -- WAIT 20 hours\n//\n// # Gnome 😵\n//\n// * age: 20\n// * hunger: 0\n// * happiness: 0\n// * health: 0\n// * sleepy: 0\n//\n// -- WAIT 20 hours\n//\n// # Gnome 😵\n//\n// * age: 20\n// * hunger: 0\n// * happiness: 0\n// * health: 0\n// * sleepy: 0\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":"subtests","path":"gno.land/p/demo/tests/subtests","files":[{"name":"subtests.gno","body":"package subtests\n\nimport (\n\t\"std\"\n)\n\nfunc GetCurrentRealm() std.Realm {\n\treturn std.CurrentRealm()\n}\n\nfunc GetPreviousRealm() std.Realm {\n\treturn std.PreviousRealm()\n}\n\nfunc Exec(fn func()) {\n\tfn()\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":"subtests","path":"gno.land/r/demo/tests/subtests","files":[{"name":"subtests.gno","body":"package subtests\n\nimport (\n\t\"std\"\n)\n\nfunc GetCurrentRealm() std.Realm {\n\treturn std.CurrentRealm()\n}\n\nfunc GetPreviousRealm() std.Realm {\n\treturn std.PreviousRealm()\n}\n\nfunc Exec(fn func()) {\n\tfn()\n}\n\nfunc CallAssertOriginCall() {\n\tstd.AssertOriginCall()\n}\n\nfunc CallIsOriginCall() bool {\n\treturn std.IsOriginCall()\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 initOriginCaller = std.OriginCaller()\n\nfunc InitOriginCaller() std.Address {\n\treturn initOriginCaller\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\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 GetPreviousRealm() std.Realm {\n\treturn std.PreviousRealm()\n}\n\nfunc GetRSubtestsPreviousRealm() std.Realm {\n\treturn rsubtests.GetPreviousRealm()\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 TestPreviousRealm(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, PreviousRealm returns the user\n\tif addr := GetPreviousRealm().Addr(); addr != user1Addr {\n\t\tt.Errorf(\"want GetPreviousRealm().Addr==%s, got %s\", user1Addr, addr)\n\t}\n\t// When 2 or more realms in the frames, PreviousRealm returns the second to last\n\tif addr := GetRSubtestsPreviousRealm().Addr(); addr != rTestsAddr {\n\t\tt.Errorf(\"want GetRSubtestsPreviousRealm().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, PreviousRealm returns the user\n// When 2 or more realms in the frames, PreviousRealm 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.TestSetOriginCaller(eoa)\n\tprintln(\"tests.GetPreviousRealm().Addr(): \", tests.GetPreviousRealm().Addr())\n\tprintln(\"tests.GetRSubtestsPreviousRealm().Addr(): \", tests.GetRSubtestsPreviousRealm().Addr())\n}\n\n// Output:\n// tests.GetPreviousRealm().Addr(): g1wdhk6et0dej47h6lta047h6lta047h6lrnerlk\n// tests.GetRSubtestsPreviousRealm().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.TestSetOriginCaller(eoa)\n\t// Contrarily to z2_filetest.gno we EXPECT GetPreviousRealms != eoa (#1704)\n\tif addr := tests.GetPreviousRealm().Addr(); addr != eoa {\n\t\tprintln(\"want tests.GetPreviousRealm().Addr ==\", eoa, \"got\", addr)\n\t}\n\t// When 2 or more realms in the frames, it is also different\n\tif addr := tests.GetRSubtestsPreviousRealm().Addr(); addr != rTestsAddr {\n\t\tprintln(\"want GetRSubtestsPreviousRealm().Addr ==\", rTestsAddr, \"got\", addr)\n\t}\n}\n\n// Output:\n// want tests.GetPreviousRealm().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":"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 initOriginCaller = std.OriginCaller()\n\nfunc InitOriginCaller() std.Address {\n\treturn initOriginCaller\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\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 GetPreviousRealm() std.Realm {\n\treturn std.PreviousRealm()\n}\n\nfunc GetRSubtestsPreviousRealm() std.Realm {\n\treturn rsubtests.GetPreviousRealm()\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 TestPreviousRealm(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, PreviousRealm returns the user\n\tif addr := GetPreviousRealm().Address(); addr != user1Addr {\n\t\tt.Errorf(\"want GetPreviousRealm().Addr==%s, got %s\", user1Addr, addr)\n\t}\n\t// When 2 or more realms in the frames, PreviousRealm returns the second to last\n\tif addr := GetRSubtestsPreviousRealm().Address(); addr != rTestsAddr {\n\t\tt.Errorf(\"want GetRSubtestsPreviousRealm().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, PreviousRealm returns the user\n// When 2 or more realms in the frames, PreviousRealm 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.TestSetOriginCaller(eoa)\n\tprintln(\"tests.GetPreviousRealm().Address(): \", tests.GetPreviousRealm().Address())\n\tprintln(\"tests.GetRSubtestsPreviousRealm().Address(): \", tests.GetRSubtestsPreviousRealm().Address())\n}\n\n// Output:\n// tests.GetPreviousRealm().Address(): g1wdhk6et0dej47h6lta047h6lta047h6lrnerlk\n// tests.GetRSubtestsPreviousRealm().Address(): 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.TestSetOriginCaller(eoa)\n\t// Contrarily to z2_filetest.gno we EXPECT GetPreviousRealms != eoa (#1704)\n\tif addr := tests.GetPreviousRealm().Address(); addr != eoa {\n\t\tprintln(\"want tests.GetPreviousRealm().Addr ==\", eoa, \"got\", addr)\n\t}\n\t// When 2 or more realms in the frames, it is also different\n\tif addr := tests.GetRSubtestsPreviousRealm().Address(); addr != rTestsAddr {\n\t\tprintln(\"want GetRSubtestsPreviousRealm().Addr ==\", rTestsAddr, \"got\", addr)\n\t}\n}\n\n// Output:\n// want tests.GetPreviousRealm().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":"tests","path":"gno.land/p/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":"tests.gno","body":"package tests\n\nimport (\n\t\"std\"\n\n\tpsubtests \"gno.land/p/demo/tests/subtests\"\n\t\"gno.land/r/demo/tests\"\n\trtests \"gno.land/r/demo/tests\"\n)\n\nconst World = \"world\"\n\n// IncCounter demonstrates that it's possible to call a realm function from\n// a package. So a package can potentially write into the store, by calling\n// an other realm.\nfunc IncCounter() {\n\ttests.IncCounter()\n}\n\nfunc CurrentRealmPath() string {\n\treturn std.CurrentRealm().PkgPath()\n}\n\n//----------------------------------------\n// cross realm test vars\n\ntype TestRealmObject2 struct {\n\tField string\n}\n\nfunc (o2 *TestRealmObject2) Modify() {\n\to2.Field = \"modified\"\n}\n\nvar (\n\tsomevalue1 TestRealmObject2\n\tSomeValue2 TestRealmObject2\n\tSomeValue3 *TestRealmObject2\n)\n\nfunc init() {\n\tsomevalue1 = TestRealmObject2{Field: \"init\"}\n\tSomeValue2 = TestRealmObject2{Field: \"init\"}\n\tSomeValue3 = \u0026TestRealmObject2{Field: \"init\"}\n}\n\nfunc ModifyTestRealmObject2a() {\n\tsomevalue1.Field = \"modified\"\n}\n\nfunc ModifyTestRealmObject2b() {\n\tSomeValue2.Field = \"modified\"\n}\n\nfunc ModifyTestRealmObject2c() {\n\tSomeValue3.Field = \"modified\"\n}\n\nfunc GetPreviousRealm() std.Realm {\n\treturn std.PreviousRealm()\n}\n\nfunc GetPSubtestsPreviousRealm() std.Realm {\n\treturn psubtests.GetPreviousRealm()\n}\n\nfunc GetRTestsGetPreviousRealm() std.Realm {\n\treturn rtests.GetPreviousRealm()\n}\n\n// Warning: unsafe pattern.\nfunc Exec(fn func()) {\n\tfn()\n}\n"},{"name":"tests_test.gno","body":"package tests_test\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/tests\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar World = \"WORLD\"\n\nfunc TestGetHelloWorld(t *testing.T) {\n\t// tests.World is 'world'\n\ts := \"hello \" + tests.World + World\n\tconst want = \"hello worldWORLD\"\n\n\tuassert.Equal(t, want, s)\n}\n"},{"name":"z0_filetest.gno","body":"package main\n\nimport (\n\tptests \"gno.land/p/demo/tests\"\n\trtests \"gno.land/r/demo/tests\"\n)\n\nfunc main() {\n\tprintln(rtests.Counter())\n\tptests.IncCounter()\n\tprintln(rtests.Counter())\n}\n\n// Output:\n// 0\n// 1\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":"p_crossrealm","path":"gno.land/p/demo/tests/p_crossrealm","files":[{"name":"p_crossrealm.gno","body":"package p_crossrealm\n\ntype Stringer interface {\n\tString() string\n}\n\ntype Container struct {\n\tA int\n\tB Stringer\n}\n\nfunc (c *Container) Touch() *Container {\n\tc.A += 1\n\treturn c\n}\n\nfunc (c *Container) Print() {\n\tprintln(\"A:\", c.A)\n\tif c.B == nil {\n\t\tprintln(\"B: undefined\")\n\t} else {\n\t\tprintln(\"B:\", c.B.String())\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":"todolist","path":"gno.land/p/demo/todolist","files":[{"name":"todolist.gno","body":"package todolist\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\ntype TodoList struct {\n\tTitle string\n\tTasks *avl.Tree\n\tOwner std.Address\n}\n\ntype Task struct {\n\tTitle string\n\tDone bool\n}\n\nfunc NewTodoList(title string) *TodoList {\n\treturn \u0026TodoList{\n\t\tTitle: title,\n\t\tTasks: avl.NewTree(),\n\t\tOwner: std.OriginCaller(),\n\t}\n}\n\nfunc NewTask(title string) *Task {\n\treturn \u0026Task{\n\t\tTitle: title,\n\t\tDone: false,\n\t}\n}\n\nfunc (tl *TodoList) AddTask(id int, task *Task) {\n\ttl.Tasks.Set(strconv.Itoa(id), task)\n}\n\nfunc ToggleTaskStatus(task *Task) {\n\ttask.Done = !task.Done\n}\n\nfunc (tl *TodoList) RemoveTask(taskId string) {\n\ttl.Tasks.Remove(taskId)\n}\n\nfunc (tl *TodoList) GetTasks() []*Task {\n\ttasks := make([]*Task, 0, tl.Tasks.Size())\n\ttl.Tasks.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\ttasks = append(tasks, value.(*Task))\n\t\treturn false\n\t})\n\treturn tasks\n}\n\nfunc (tl *TodoList) GetTodolistOwner() std.Address {\n\treturn tl.Owner\n}\n\nfunc (tl *TodoList) GetTodolistTitle() string {\n\treturn tl.Title\n}\n"},{"name":"todolist_test.gno","body":"package todolist\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestNewTodoList(t *testing.T) {\n\ttitle := \"My Todo List\"\n\ttodoList := NewTodoList(title)\n\n\tuassert.Equal(t, title, todoList.GetTodolistTitle())\n\tuassert.Equal(t, 0, len(todoList.GetTasks()))\n\tuassert.Equal(t, std.OriginCaller().String(), todoList.GetTodolistOwner().String())\n}\n\nfunc TestNewTask(t *testing.T) {\n\ttitle := \"My Task\"\n\ttask := NewTask(title)\n\n\tuassert.Equal(t, title, task.Title)\n\tuassert.False(t, task.Done, \"Expected task to be not done, but it is done\")\n}\n\nfunc TestAddTask(t *testing.T) {\n\ttodoList := NewTodoList(\"My Todo List\")\n\ttask := NewTask(\"My Task\")\n\n\ttodoList.AddTask(1, task)\n\n\ttasks := todoList.GetTasks()\n\n\tuassert.Equal(t, 1, len(tasks))\n\tuassert.True(t, tasks[0] == task, \"Task does not match\")\n}\n\nfunc TestToggleTaskStatus(t *testing.T) {\n\ttask := NewTask(\"My Task\")\n\n\tToggleTaskStatus(task)\n\tuassert.True(t, task.Done, \"Expected task to be done, but it is not done\")\n\n\tToggleTaskStatus(task)\n\tuassert.False(t, task.Done, \"Expected task to be done, but it is not done\")\n}\n\nfunc TestRemoveTask(t *testing.T) {\n\ttodoList := NewTodoList(\"My Todo List\")\n\ttask := NewTask(\"My Task\")\n\ttodoList.AddTask(1, task)\n\n\ttodoList.RemoveTask(\"1\")\n\n\ttasks := todoList.GetTasks()\n\tuassert.Equal(t, 0, len(tasks))\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}} @@ -76,22 +76,22 @@ {"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"poa","path":"gno.land/p/nt/poa","files":[{"name":"option.gno","body":"package poa\n\nimport \"gno.land/p/sys/validators\"\n\ntype Option func(*PoA)\n\n// WithInitialSet sets the initial PoA validator set\nfunc WithInitialSet(validators []validators.Validator) Option {\n\treturn func(p *PoA) {\n\t\tfor _, validator := range validators {\n\t\t\tp.validators.Set(validator.Address.String(), validator)\n\t\t}\n\t}\n}\n"},{"name":"poa.gno","body":"package poa\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/sys/validators\"\n)\n\nvar ErrInvalidVotingPower = errors.New(\"invalid voting power\")\n\n// PoA specifies the Proof of Authority validator set, with simple add / remove constraints.\n//\n// To add:\n// - proposed validator must not be part of the set already\n// - proposed validator voting power must be \u003e 0\n//\n// To remove:\n// - proposed validator must be part of the set already\ntype PoA struct {\n\tvalidators *avl.Tree // std.Address -\u003e validators.Validator\n}\n\n// NewPoA creates a new empty Proof of Authority validator set\nfunc NewPoA(opts ...Option) *PoA {\n\t// Create the empty set\n\tp := \u0026PoA{\n\t\tvalidators: avl.NewTree(),\n\t}\n\n\t// Apply the options\n\tfor _, opt := range opts {\n\t\topt(p)\n\t}\n\n\treturn p\n}\n\nfunc (p *PoA) AddValidator(address std.Address, pubKey string, power uint64) (validators.Validator, error) {\n\t// Validate that the operation is a valid call.\n\t// Check if the validator is already in the set\n\tif p.IsValidator(address) {\n\t\treturn validators.Validator{}, validators.ErrValidatorExists\n\t}\n\n\t// Make sure the voting power \u003e 0\n\tif power == 0 {\n\t\treturn validators.Validator{}, ErrInvalidVotingPower\n\t}\n\n\tv := validators.Validator{\n\t\tAddress: address,\n\t\tPubKey: pubKey, // TODO: in the future, verify the public key\n\t\tVotingPower: power,\n\t}\n\n\t// Add the validator to the set\n\tp.validators.Set(address.String(), v)\n\n\treturn v, nil\n}\n\nfunc (p *PoA) RemoveValidator(address std.Address) (validators.Validator, error) {\n\t// Validate that the operation is a valid call\n\t// Fetch the validator\n\tvalidator, err := p.GetValidator(address)\n\tif err != nil {\n\t\treturn validators.Validator{}, err\n\t}\n\n\t// Remove the validator from the set\n\tp.validators.Remove(address.String())\n\n\treturn validator, nil\n}\n\nfunc (p *PoA) IsValidator(address std.Address) bool {\n\t_, exists := p.validators.Get(address.String())\n\n\treturn exists\n}\n\nfunc (p *PoA) GetValidator(address std.Address) (validators.Validator, error) {\n\tvalidatorRaw, exists := p.validators.Get(address.String())\n\tif !exists {\n\t\treturn validators.Validator{}, validators.ErrValidatorMissing\n\t}\n\n\tvalidator := validatorRaw.(validators.Validator)\n\n\treturn validator, nil\n}\n\nfunc (p *PoA) GetValidators() []validators.Validator {\n\tvals := make([]validators.Validator, 0, p.validators.Size())\n\n\tp.validators.Iterate(\"\", \"\", func(_ string, value interface{}) bool {\n\t\tvalidator := value.(validators.Validator)\n\t\tvals = append(vals, validator)\n\n\t\treturn false\n\t})\n\n\treturn vals\n}\n"},{"name":"poa_test.gno","body":"package poa\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/urequire\"\n\t\"gno.land/p/sys/validators\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// generateTestValidators generates a dummy validator set\nfunc generateTestValidators(count int) []validators.Validator {\n\tvals := make([]validators.Validator, 0, count)\n\n\tfor i := 0; i \u003c count; i++ {\n\t\tval := validators.Validator{\n\t\t\tAddress: testutils.TestAddress(ufmt.Sprintf(\"%d\", i)),\n\t\t\tPubKey: \"public-key\",\n\t\t\tVotingPower: 1,\n\t\t}\n\n\t\tvals = append(vals, val)\n\t}\n\n\treturn vals\n}\n\nfunc TestPoA_AddValidator_Invalid(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"validator already in set\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tproposalAddress = testutils.TestAddress(\"caller\")\n\t\t\tproposalKey = \"public-key\"\n\n\t\t\tinitialSet = generateTestValidators(1)\n\t\t)\n\n\t\tinitialSet[0].Address = proposalAddress\n\t\tinitialSet[0].PubKey = proposalKey\n\n\t\t// Create the protocol with an initial set\n\t\tp := NewPoA(WithInitialSet(initialSet))\n\n\t\t// Attempt to add the validator\n\t\t_, err := p.AddValidator(proposalAddress, proposalKey, 1)\n\t\tuassert.ErrorIs(t, err, validators.ErrValidatorExists)\n\t})\n\n\tt.Run(\"invalid voting power\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tproposalAddress = testutils.TestAddress(\"caller\")\n\t\t\tproposalKey = \"public-key\"\n\t\t)\n\n\t\t// Create the protocol with no initial set\n\t\tp := NewPoA()\n\n\t\t// Attempt to add the validator\n\t\t_, err := p.AddValidator(proposalAddress, proposalKey, 0)\n\t\tuassert.ErrorIs(t, err, ErrInvalidVotingPower)\n\t})\n}\n\nfunc TestPoA_AddValidator(t *testing.T) {\n\tt.Parallel()\n\n\tvar (\n\t\tproposalAddress = testutils.TestAddress(\"caller\")\n\t\tproposalKey = \"public-key\"\n\t)\n\n\t// Create the protocol with no initial set\n\tp := NewPoA()\n\n\t// Attempt to add the validator\n\t_, err := p.AddValidator(proposalAddress, proposalKey, 1)\n\tuassert.NoError(t, err)\n\n\t// Make sure the validator is added\n\tif !p.IsValidator(proposalAddress) || p.validators.Size() != 1 {\n\t\tt.Fatal(\"address is not validator\")\n\t}\n}\n\nfunc TestPoA_RemoveValidator_Invalid(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"proposed removal not in set\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tproposalAddress = testutils.TestAddress(\"caller\")\n\t\t\tinitialSet = generateTestValidators(1)\n\t\t)\n\n\t\tinitialSet[0].Address = proposalAddress\n\n\t\t// Create the protocol with an initial set\n\t\tp := NewPoA(WithInitialSet(initialSet))\n\n\t\t// Attempt to remove the validator\n\t\t_, err := p.RemoveValidator(testutils.TestAddress(\"totally random\"))\n\t\tuassert.ErrorIs(t, err, validators.ErrValidatorMissing)\n\t})\n}\n\nfunc TestPoA_RemoveValidator(t *testing.T) {\n\tt.Parallel()\n\n\tvar (\n\t\tproposalAddress = testutils.TestAddress(\"caller\")\n\t\tinitialSet = generateTestValidators(1)\n\t)\n\n\tinitialSet[0].Address = proposalAddress\n\n\t// Create the protocol with an initial set\n\tp := NewPoA(WithInitialSet(initialSet))\n\n\t// Attempt to remove the validator\n\t_, err := p.RemoveValidator(proposalAddress)\n\turequire.NoError(t, err)\n\n\t// Make sure the validator is removed\n\tif p.IsValidator(proposalAddress) || p.validators.Size() != 0 {\n\t\tt.Fatal(\"address is validator\")\n\t}\n}\n\nfunc TestPoA_GetValidator(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"validator not in set\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Create the protocol with no initial set\n\t\tp := NewPoA()\n\n\t\t// Attempt to get the voting power\n\t\t_, err := p.GetValidator(testutils.TestAddress(\"caller\"))\n\t\tuassert.ErrorIs(t, err, validators.ErrValidatorMissing)\n\t})\n\n\tt.Run(\"validator fetched\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\taddress = testutils.TestAddress(\"caller\")\n\t\t\tpubKey = \"public-key\"\n\t\t\tvotingPower = uint64(10)\n\n\t\t\tinitialSet = generateTestValidators(1)\n\t\t)\n\n\t\tinitialSet[0].Address = address\n\t\tinitialSet[0].PubKey = pubKey\n\t\tinitialSet[0].VotingPower = votingPower\n\n\t\t// Create the protocol with an initial set\n\t\tp := NewPoA(WithInitialSet(initialSet))\n\n\t\t// Get the validator\n\t\tval, err := p.GetValidator(address)\n\t\turequire.NoError(t, err)\n\n\t\t// Validate the address\n\t\tif val.Address != address {\n\t\t\tt.Fatal(\"invalid address\")\n\t\t}\n\n\t\t// Validate the voting power\n\t\tif val.VotingPower != votingPower {\n\t\t\tt.Fatal(\"invalid voting power\")\n\t\t}\n\n\t\t// Validate the public key\n\t\tif val.PubKey != pubKey {\n\t\t\tt.Fatal(\"invalid public key\")\n\t\t}\n\t})\n}\n\nfunc TestPoA_GetValidators(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"empty set\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Create the protocol with no initial set\n\t\tp := NewPoA()\n\n\t\t// Attempt to get the voting power\n\t\tvals := p.GetValidators()\n\n\t\tif len(vals) != 0 {\n\t\t\tt.Fatal(\"validator set is not empty\")\n\t\t}\n\t})\n\n\tt.Run(\"validator set fetched\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tinitialSet := generateTestValidators(10)\n\n\t\t// Create the protocol with an initial set\n\t\tp := NewPoA(WithInitialSet(initialSet))\n\n\t\t// Get the validator set\n\t\tvals := p.GetValidators()\n\n\t\tif len(vals) != len(initialSet) {\n\t\t\tt.Fatal(\"returned validator set mismatch\")\n\t\t}\n\n\t\tfor _, val := range vals {\n\t\t\tfor _, initialVal := range initialSet {\n\t\t\t\tif val.Address != initialVal.Address {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\t// Validate the voting power\n\t\t\t\tuassert.Equal(t, val.VotingPower, initialVal.VotingPower)\n\n\t\t\t\t// Validate the public key\n\t\t\t\tuassert.Equal(t, val.PubKey, initialVal.PubKey)\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":"gnoface","path":"gno.land/r/demo/art/gnoface","files":[{"name":"gnoface.gno","body":"package gnoface\n\nimport (\n\t\"math/rand\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/entropy\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nfunc Render(path string) string {\n\tseed := uint64(entropy.New().Value())\n\n\tpath = strings.TrimSpace(path)\n\tif path != \"\" {\n\t\ts, err := strconv.Atoi(path)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tseed = uint64(s)\n\t}\n\n\toutput := ufmt.Sprintf(\"Gnoface #%d\\n\", seed)\n\toutput += \"```\\n\" + Draw(seed) + \"```\\n\"\n\treturn output\n}\n\nfunc Draw(seed uint64) string {\n\tvar (\n\t\thairs = []string{\n\t\t\t\" s\",\n\t\t\t\" .......\",\n\t\t\t\" s s s\",\n\t\t\t\" /\\\\ /\\\\\",\n\t\t\t\" |||||||\",\n\t\t}\n\t\theadtop = []string{\n\t\t\t\" /-------\\\\\",\n\t\t\t\" /~~~~~~~\\\\\",\n\t\t\t\" /|||||||\\\\\",\n\t\t\t\" ////////\\\\\",\n\t\t\t\" |||||||||\",\n\t\t\t\" /\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\",\n\t\t}\n\t\theadspace = []string{\n\t\t\t\" | |\",\n\t\t}\n\t\teyebrow = []string{\n\t\t\t\"~\",\n\t\t\t\"*\",\n\t\t\t\"_\",\n\t\t\t\".\",\n\t\t}\n\t\tear = []string{\n\t\t\t\"o\",\n\t\t\t\" \",\n\t\t\t\"D\",\n\t\t\t\"O\",\n\t\t\t\"\u003c\",\n\t\t\t\"\u003e\",\n\t\t\t\".\",\n\t\t\t\"|\",\n\t\t\t\")\",\n\t\t\t\"(\",\n\t\t}\n\t\teyesmiddle = []string{\n\t\t\t\"| o o |\",\n\t\t\t\"| o _ |\",\n\t\t\t\"| _ o |\",\n\t\t\t\"| . . |\",\n\t\t\t\"| O O |\",\n\t\t\t\"| v v |\",\n\t\t\t\"| X X |\",\n\t\t\t\"| x X |\",\n\t\t\t\"| X D |\",\n\t\t\t\"| ~ ~ |\",\n\t\t}\n\t\tnose = []string{\n\t\t\t\" | o |\",\n\t\t\t\" | O |\",\n\t\t\t\" | V |\",\n\t\t\t\" | L |\",\n\t\t\t\" | C |\",\n\t\t\t\" | ~ |\",\n\t\t\t\" | . . |\",\n\t\t\t\" | . |\",\n\t\t}\n\t\tmouth = []string{\n\t\t\t\" | __/ |\",\n\t\t\t\" | \\\\_/ |\",\n\t\t\t\" | . |\",\n\t\t\t\" | ___ |\",\n\t\t\t\" | ~~~ |\",\n\t\t\t\" | === |\",\n\t\t\t\" | \u003c=\u003e |\",\n\t\t}\n\t\theadbottom = []string{\n\t\t\t\" \\\\-------/\",\n\t\t\t\" \\\\~~~~~~~/\",\n\t\t\t\" \\\\_______/\",\n\t\t}\n\t)\n\n\tr := rand.New(rand.NewPCG(seed, 0xdeadbeef))\n\n\treturn pick(r, hairs) + \"\\n\" +\n\t\tpick(r, headtop) + \"\\n\" +\n\t\tpick(r, headspace) + \"\\n\" +\n\t\t\" | \" + pick(r, eyebrow) + \" \" + pick(r, eyebrow) + \" |\\n\" +\n\t\tpick(r, ear) + pick(r, eyesmiddle) + pick(r, ear) + \"\\n\" +\n\t\tpick(r, headspace) + \"\\n\" +\n\t\tpick(r, nose) + \"\\n\" +\n\t\tpick(r, headspace) + \"\\n\" +\n\t\tpick(r, mouth) + \"\\n\" +\n\t\tpick(r, headspace) + \"\\n\" +\n\t\tpick(r, headbottom) + \"\\n\"\n}\n\nfunc pick(r *rand.Rand, slice []string) string {\n\treturn slice[r.IntN(len(slice))]\n}\n\n// based on https://github.com/moul/pipotron/blob/master/dict/ascii-face.yml\n"},{"name":"gnoface_test.gno","body":"package gnoface\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nfunc TestDraw(t *testing.T) {\n\tcases := []struct {\n\t\tseed uint64\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tseed: 42,\n\t\t\texpected: `\n |||||||\n |||||||||\n | |\n | . ~ |\n)| v v |O\n | |\n | L |\n | |\n | ___ |\n | |\n \\~~~~~~~/\n`[1:],\n\t\t},\n\t\t{\n\t\t\tseed: 1337,\n\t\t\texpected: `\n .......\n |||||||||\n | |\n | . _ |\nD| x X |O\n | |\n | ~ |\n | |\n | ~~~ |\n | |\n \\~~~~~~~/\n`[1:],\n\t\t},\n\t\t{\n\t\t\tseed: 123456789,\n\t\t\texpected: `\n .......\n ////////\\\n | |\n | ~ * |\n|| x X |o\n | |\n | V |\n | |\n | . |\n | |\n \\-------/\n`[1:],\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tname := ufmt.Sprintf(\"%d\", tc.seed)\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tgot := Draw(tc.seed)\n\t\t\tuassert.Equal(t, string(tc.expected), got)\n\t\t})\n\t}\n}\n\nfunc TestRender(t *testing.T) {\n\tcases := []struct {\n\t\tpath string\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tpath: \"42\",\n\t\t\texpected: \"Gnoface #42\\n```\" + `\n |||||||\n |||||||||\n | |\n | . ~ |\n)| v v |O\n | |\n | L |\n | |\n | ___ |\n | |\n \\~~~~~~~/\n` + \"```\\n\",\n\t\t},\n\t\t{\n\t\t\tpath: \"1337\",\n\t\t\texpected: \"Gnoface #1337\\n```\" + `\n .......\n |||||||||\n | |\n | . _ |\nD| x X |O\n | |\n | ~ |\n | |\n | ~~~ |\n | |\n \\~~~~~~~/\n` + \"```\\n\",\n\t\t},\n\t\t{\n\t\t\tpath: \"123456789\",\n\t\t\texpected: \"Gnoface #123456789\\n```\" + `\n .......\n ////////\\\n | |\n | ~ * |\n|| x X |o\n | |\n | V |\n | |\n | . |\n | |\n \\-------/\n` + \"```\\n\",\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tt.Run(tc.path, func(t *testing.T) {\n\t\t\tgot := Render(tc.path)\n\t\t\tuassert.Equal(t, tc.expected, got)\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":"millipede","path":"gno.land/r/demo/art/millipede","files":[{"name":"millipede.gno","body":"package millipede\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\nconst (\n\tminSize = 1\n\tdefaultSize = 20\n\tmaxSize = 100\n)\n\nfunc Draw(size int) string {\n\tif size \u003c minSize || size \u003e maxSize {\n\t\tpanic(\"invalid millipede size\")\n\t}\n\tpaddings := []string{\" \", \" \", \"\", \" \", \" \", \" \", \" \", \" \", \" \"}\n\tvar b strings.Builder\n\tb.WriteString(\" ╚⊙ ⊙╝\\n\")\n\tfor i := 0; i \u003c size; i++ {\n\t\tb.WriteString(paddings[i%9] + \"╚═(███)═╝\\n\")\n\t}\n\treturn b.String()\n}\n\nfunc Render(path string) string {\n\tsize := defaultSize\n\n\tpath = strings.TrimSpace(path)\n\tif path != \"\" {\n\t\tvar err error\n\t\tsize, err = strconv.Atoi(path)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}\n\n\toutput := \"```\\n\" + Draw(size) + \"```\\n\"\n\tif size \u003e minSize {\n\t\toutput += ufmt.Sprintf(\"[%d](/r/demo/art/millipede:%d)\u003c \", size-1, size-1)\n\t}\n\tif size \u003c maxSize {\n\t\toutput += ufmt.Sprintf(\" \u003e[%d](/r/demo/art/millipede:%d)\", size+1, size+1)\n\t}\n\treturn output\n}\n\n// based on https://github.com/getmillipede/millipede-go/blob/977f046c39c35a650eac0fd30245e96b22c7803c/main.go\n"},{"name":"millipede_test.gno","body":"package millipede\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestRender(t *testing.T) {\n\tcases := []struct {\n\t\tpath string\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tpath: \"\",\n\t\t\texpected: \"```\" + `\n ╚⊙ ⊙╝\n ╚═(███)═╝\n ╚═(███)═╝\n╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n` + \"```\\n[19](/r/demo/art/millipede:19)\u003c \u003e[21](/r/demo/art/millipede:21)\",\n\t\t},\n\t\t{\n\t\t\tpath: \"4\",\n\t\t\texpected: \"```\" + `\n ╚⊙ ⊙╝\n ╚═(███)═╝\n ╚═(███)═╝\n╚═(███)═╝\n ╚═(███)═╝\n` + \"```\\n[3](/r/demo/art/millipede:3)\u003c \u003e[5](/r/demo/art/millipede:5)\",\n\t\t},\n\t}\n\n\tfor _, tc := range cases {\n\t\tt.Run(tc.path, func(t *testing.T) {\n\t\t\tgot := Render(tc.path)\n\t\t\tuassert.Equal(t, tc.expected, got)\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":"banktest","path":"gno.land/r/demo/banktest","files":[{"name":"README.md","body":"This is a simple test realm contract that demonstrates how to use the banker.\n\nSee [gno.land/r/demo/banktest/banktest.go](/r/demo/banktest/banktest.go) to see the original contract code.\n\nThis article will go through each line to explain how it works.\n\n```go\npackage banktest\n```\n\nThis package is locally named \"banktest\" (could be anything).\n\n```go\nimport (\n \"std\"\n)\n```\n\nThe \"std\" package is defined by the gno code in stdlibs/std/. \u003c/br\u003e Self explanatory; and you'll see more usage from std later.\n\n```go\ntype activity struct {\n caller std.Address\n sent std.Coins\n returned std.Coins\n time time.Time\n}\n\nfunc (act *activity) String() string {\n return act.caller.String() + \" \" +\n act.sent.String() + \" sent, \" +\n act.returned.String() + \" returned, at \" +\n act.time.Format(\"2006-01-02 3:04pm MST\")\n}\n\nvar latest [10]*activity\n```\n\nThis is just maintaining a list of recent activity to this contract. Notice that the \"latest\" variable is defined \"globally\" within the context of the realm with path \"gno.land/r/demo/banktest\".\n\nThis means that calls to functions defined within this package are encapsulated within this \"data realm\", where the data is mutated based on transactions that can potentially cross many realm and non-realm package boundaries (in the call stack).\n\n```go\n// Deposit will take the coins (to the realm's pkgaddr) or return them to user.\nfunc Deposit(returnDenom string, returnAmount int64) string {\n std.AssertOriginCall()\n caller := std.OriginCaller()\n send := std.Coins{{returnDenom, returnAmount}}\n```\n\nThis is the beginning of the definition of the contract function named \"Deposit\". `std.AssertOriginCall() asserts that this function was called by a gno transactional Message. The caller is the user who signed off on this transactional message. Send is the amount of deposit sent along with this message.\n\n```go\n // record activity\n act := \u0026activity{\n caller: caller,\n sent: std.OriginSend(),\n returned: send,\n time: time.Now(),\n }\n for i := len(latest) - 2; i \u003e= 0; i-- {\n latest[i+1] = latest[i] // shift by +1.\n }\n latest[0] = act\n```\n\nUpdating the \"latest\" array for viewing at gno.land/r/demo/banktest: (w/ trailing colon).\n\n```go\n // return if any.\n if returnAmount \u003e 0 {\n```\n\nIf the user requested the return of coins...\n\n```go\n banker := std.GetBanker(std.BankerTypeOriginSend)\n```\n\nuse a std.Banker instance to return any deposited coins to the original sender.\n\n```go\n pkgaddr := std.GetOriginPkgAddr()\n // TODO: use std.Coins constructors, this isn't generally safe.\n banker.SendCoins(pkgaddr, caller, send)\n return \"returned!\"\n```\n\nNotice that each realm package has an associated Cosmos address.\n\nFinally, the results are rendered via an ABCI query call when you visit [/r/demo/banktest:](/r/demo/banktest:).\n\n```go\nfunc Render(path string) string {\n // get realm coins.\n banker := std.GetBanker(std.BankerTypeReadonly)\n coins := banker.GetCoins(std.GetOriginPkgAddr())\n\n // render\n res := \"\"\n res += \"## recent activity\\n\"\n res += \"\\n\"\n for _, act := range latest {\n if act == nil {\n break\n }\n res += \" * \" + act.String() + \"\\n\"\n }\n res += \"\\n\"\n res += \"## total deposits\\n\"\n res += coins.String()\n return res\n}\n```\n\nYou can call this contract yourself, by vistiing [/r/demo/banktest](/r/demo/banktest) and the [quickstart guide](/r/demo/boards:gnolang/4).\n"},{"name":"banktest.gno","body":"package banktest\n\nimport (\n\t\"std\"\n\t\"time\"\n)\n\ntype activity struct {\n\tcaller std.Address\n\tsent std.Coins\n\treturned std.Coins\n\ttime time.Time\n}\n\nfunc (act *activity) String() string {\n\treturn act.caller.String() + \" \" +\n\t\tact.sent.String() + \" sent, \" +\n\t\tact.returned.String() + \" returned, at \" +\n\t\tact.time.Format(\"2006-01-02 3:04pm MST\")\n}\n\nvar latest [10]*activity\n\n// Deposit will take the coins (to the realm's pkgaddr) or return them to user.\nfunc Deposit(returnDenom string, returnAmount int64) string {\n\tstd.AssertOriginCall()\n\tcaller := std.OriginCaller()\n\tsend := std.Coins{{returnDenom, returnAmount}}\n\t// record activity\n\tact := \u0026activity{\n\t\tcaller: caller,\n\t\tsent: std.OriginSend(),\n\t\treturned: send,\n\t\ttime: time.Now(),\n\t}\n\tfor i := len(latest) - 2; i \u003e= 0; i-- {\n\t\tlatest[i+1] = latest[i] // shift by +1.\n\t}\n\tlatest[0] = act\n\t// return if any.\n\tif returnAmount \u003e 0 {\n\t\tbanker := std.GetBanker(std.BankerTypeOriginSend)\n\t\tpkgaddr := std.GetOriginPkgAddr()\n\t\t// TODO: use std.Coins constructors, this isn't generally safe.\n\t\tbanker.SendCoins(pkgaddr, caller, send)\n\t\treturn \"returned!\"\n\t} else {\n\t\treturn \"thank you!\"\n\t}\n}\n\nfunc Render(path string) string {\n\t// get realm coins.\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tcoins := banker.GetCoins(std.GetOriginPkgAddr())\n\n\t// render\n\tres := \"\"\n\tres += \"## recent activity\\n\"\n\tres += \"\\n\"\n\tfor _, act := range latest {\n\t\tif act == nil {\n\t\t\tbreak\n\t\t}\n\t\tres += \" * \" + act.String() + \"\\n\"\n\t}\n\tres += \"\\n\"\n\tres += \"## total deposits\\n\"\n\tres += coins.String()\n\treturn res\n}\n"},{"name":"z_0_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\n// SEND: 100000000ugnot\n\npackage bank1\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/banktest\"\n)\n\nfunc main() {\n\t// set up main address and banktest addr.\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/bank1\")\n\tstd.TestSetOriginCaller(mainaddr)\n\tstd.TestSetOriginPkgAddr(banktestAddr)\n\n\t// get and print balance of mainaddr.\n\t// with the SEND, + 200 gnot given by the TestContext, main should have 300gnot.\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\t// simulate a Deposit call. use Send + OriginSend to simulate -send.\n\tbanker.SendCoins(mainaddr, banktestAddr, std.Coins{{\"ugnot\", 100_000_000}})\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 100_000_000}}, nil)\n\tres := banktest.Deposit(\"ugnot\", 50_000_000)\n\tprintln(\"Deposit():\", res)\n\n\t// print main balance after.\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal)\n\n\t// simulate a Render(). banker should have given back all coins.\n\tres = banktest.Render(\"\")\n\tprintln(res)\n}\n\n// Output:\n// main before: 300000000ugnot\n// Deposit(): returned!\n// main after: 250000000ugnot\n// ## recent activity\n//\n// * g1tnpdmvrmtgql8fmxgsq9rwtst5hsxahk3f05dk 100000000ugnot sent, 50000000ugnot returned, at 2009-02-13 11:31pm UTC\n//\n// ## total deposits\n// 50000000ugnot\n"},{"name":"z_1_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\npackage bank1\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/banktest\"\n)\n\nfunc main() {\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\n\t// simulate a Deposit call.\n\tstd.TestSetOriginPkgAddr(banktestAddr)\n\tstd.TestIssueCoins(banktestAddr, std.Coins{{\"ugnot\", 100000000}})\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 100000000}}, nil)\n\tres := banktest.Deposit(\"ugnot\", 101000000)\n\tprintln(res)\n}\n\n// Error:\n// cannot send \"101000000ugnot\", limit \"100000000ugnot\" exceeded with \"\" already spent\n"},{"name":"z_2_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\npackage bank1\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/banktest\"\n)\n\nfunc main() {\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\n\t// print main balance before.\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/bank1\")\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal) // plus OriginSend equals 300.\n\n\t// simulate a Deposit call.\n\tstd.TestSetOriginPkgAddr(banktestAddr)\n\tstd.TestIssueCoins(banktestAddr, std.Coins{{\"ugnot\", 100000000}})\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 100000000}}, nil)\n\tres := banktest.Deposit(\"ugnot\", 55000000)\n\tprintln(\"Deposit():\", res)\n\n\t// print main balance after.\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal) // now 255.\n\n\t// simulate a Render().\n\tres = banktest.Render(\"\")\n\tprintln(res)\n}\n\n// Output:\n// main before: 200000000ugnot\n// Deposit(): returned!\n// main after: 255000000ugnot\n// ## recent activity\n//\n// * g1tnpdmvrmtgql8fmxgsq9rwtst5hsxahk3f05dk 100000000ugnot sent, 55000000ugnot returned, at 2009-02-13 11:31pm UTC\n//\n// ## total deposits\n// 45000000ugnot\n"},{"name":"z_3_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\npackage bank1\n\nimport (\n\t\"std\"\n)\n\nfunc main() {\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/bank1\")\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tsend := std.Coins{{\"ugnot\", 123}}\n\tbanker.SendCoins(banktestAddr, mainaddr, send)\n\n}\n\n// Error:\n// can only send coins from realm that created banker \"g1tnpdmvrmtgql8fmxgsq9rwtst5hsxahk3f05dk\", not \"g1dv3435088tlrgggf745kaud0ptrkc9v42k8llz\"\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":"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)\n\nvar (\n\tbanker *grc20.Banker // private banker.\n\tToken grc20.Token // public safe-object.\n)\n\nfunc init() {\n\tbanker = grc20.NewBanker(\"Bar\", \"BAR\", 4)\n\tToken = banker.Token()\n}\n\nfunc Faucet() string {\n\tcaller := std.PreviousRealm().Addr()\n\tif err := banker.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 banker.RenderHome() // XXX: should be 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.TestSetOriginCaller(alice) // XXX: should not need this\n\n\turequire.Equal(t, Token.BalanceOf(alice), uint64(0))\n\turequire.Equal(t, Faucet(), \"OK\")\n\turequire.Equal(t, Token.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":"banktest","path":"gno.land/r/demo/banktest","files":[{"name":"README.md","body":"This is a simple test realm contract that demonstrates how to use the banker.\n\nSee [gno.land/r/demo/banktest/banktest.go](/r/demo/banktest/banktest.go) to see the original contract code.\n\nThis article will go through each line to explain how it works.\n\n```go\npackage banktest\n```\n\nThis package is locally named \"banktest\" (could be anything).\n\n```go\nimport (\n \"std\"\n)\n```\n\nThe \"std\" package is defined by the gno code in stdlibs/std/. \u003c/br\u003e Self explanatory; and you'll see more usage from std later.\n\n```go\ntype activity struct {\n caller std.Address\n sent std.Coins\n returned std.Coins\n time time.Time\n}\n\nfunc (act *activity) String() string {\n return act.caller.String() + \" \" +\n act.sent.String() + \" sent, \" +\n act.returned.String() + \" returned, at \" +\n act.time.Format(\"2006-01-02 3:04pm MST\")\n}\n\nvar latest [10]*activity\n```\n\nThis is just maintaining a list of recent activity to this contract. Notice that the \"latest\" variable is defined \"globally\" within the context of the realm with path \"gno.land/r/demo/banktest\".\n\nThis means that calls to functions defined within this package are encapsulated within this \"data realm\", where the data is mutated based on transactions that can potentially cross many realm and non-realm package boundaries (in the call stack).\n\n```go\n// Deposit will take the coins (to the realm's pkgaddr) or return them to user.\nfunc Deposit(returnDenom string, returnAmount int64) string {\n std.AssertOriginCall()\n caller := std.OriginCaller()\n send := std.Coins{{returnDenom, returnAmount}}\n```\n\nThis is the beginning of the definition of the contract function named \"Deposit\". `std.AssertOriginCall() asserts that this function was called by a gno transactional Message. The caller is the user who signed off on this transactional message. Send is the amount of deposit sent along with this message.\n\n```go\n // record activity\n act := \u0026activity{\n caller: caller,\n sent: std.OriginSend(),\n returned: send,\n time: time.Now(),\n }\n for i := len(latest) - 2; i \u003e= 0; i-- {\n latest[i+1] = latest[i] // shift by +1.\n }\n latest[0] = act\n```\n\nUpdating the \"latest\" array for viewing at gno.land/r/demo/banktest: (w/ trailing colon).\n\n```go\n // return if any.\n if returnAmount \u003e 0 {\n```\n\nIf the user requested the return of coins...\n\n```go\n banker := std.GetBanker(std.BankerTypeOriginSend)\n```\n\nuse a std.Banker instance to return any deposited coins to the original sender.\n\n```go\n pkgaddr := std.GetOriginPkgAddress()\n // TODO: use std.Coins constructors, this isn't generally safe.\n banker.SendCoins(pkgaddr, caller, send)\n return \"returned!\"\n```\n\nNotice that each realm package has an associated Cosmos address.\n\nFinally, the results are rendered via an ABCI query call when you visit [/r/demo/banktest:](/r/demo/banktest:).\n\n```go\nfunc Render(path string) string {\n // get realm coins.\n banker := std.GetBanker(std.BankerTypeReadonly)\n coins := banker.GetCoins(std.GetOriginPkgAddress())\n\n // render\n res := \"\"\n res += \"## recent activity\\n\"\n res += \"\\n\"\n for _, act := range latest {\n if act == nil {\n break\n }\n res += \" * \" + act.String() + \"\\n\"\n }\n res += \"\\n\"\n res += \"## total deposits\\n\"\n res += coins.String()\n return res\n}\n```\n\nYou can call this contract yourself, by vistiing [/r/demo/banktest](/r/demo/banktest) and the [quickstart guide](/r/demo/boards:gnolang/4).\n"},{"name":"banktest.gno","body":"package banktest\n\nimport (\n\t\"std\"\n\t\"time\"\n)\n\ntype activity struct {\n\tcaller std.Address\n\tsent std.Coins\n\treturned std.Coins\n\ttime time.Time\n}\n\nfunc (act *activity) String() string {\n\treturn act.caller.String() + \" \" +\n\t\tact.sent.String() + \" sent, \" +\n\t\tact.returned.String() + \" returned, at \" +\n\t\tact.time.Format(\"2006-01-02 3:04pm MST\")\n}\n\nvar latest [10]*activity\n\n// Deposit will take the coins (to the realm's pkgaddr) or return them to user.\nfunc Deposit(returnDenom string, returnAmount int64) string {\n\tstd.AssertOriginCall()\n\tcaller := std.OriginCaller()\n\tsend := std.Coins{{returnDenom, returnAmount}}\n\t// record activity\n\tact := \u0026activity{\n\t\tcaller: caller,\n\t\tsent: std.OriginSend(),\n\t\treturned: send,\n\t\ttime: time.Now(),\n\t}\n\tfor i := len(latest) - 2; i \u003e= 0; i-- {\n\t\tlatest[i+1] = latest[i] // shift by +1.\n\t}\n\tlatest[0] = act\n\t// return if any.\n\tif returnAmount \u003e 0 {\n\t\tbanker := std.GetBanker(std.BankerTypeOriginSend)\n\t\tpkgaddr := std.GetOriginPkgAddress()\n\t\t// TODO: use std.Coins constructors, this isn't generally safe.\n\t\tbanker.SendCoins(pkgaddr, caller, send)\n\t\treturn \"returned!\"\n\t} else {\n\t\treturn \"thank you!\"\n\t}\n}\n\nfunc Render(path string) string {\n\t// get realm coins.\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tcoins := banker.GetCoins(std.GetOriginPkgAddress())\n\n\t// render\n\tres := \"\"\n\tres += \"## recent activity\\n\"\n\tres += \"\\n\"\n\tfor _, act := range latest {\n\t\tif act == nil {\n\t\t\tbreak\n\t\t}\n\t\tres += \" * \" + act.String() + \"\\n\"\n\t}\n\tres += \"\\n\"\n\tres += \"## total deposits\\n\"\n\tres += coins.String()\n\treturn res\n}\n"},{"name":"z_0_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\n// SEND: 100000000ugnot\n\npackage bank1\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/banktest\"\n)\n\nfunc main() {\n\t// set up main address and banktest addr.\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/bank1\")\n\tstd.TestSetOriginCaller(mainaddr)\n\tstd.TestSetOriginPkgAddr(banktestAddr)\n\n\t// get and print balance of mainaddr.\n\t// with the SEND, + 200 gnot given by the TestContext, main should have 300gnot.\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\t// simulate a Deposit call. use Send + OriginSend to simulate -send.\n\tbanker.SendCoins(mainaddr, banktestAddr, std.Coins{{\"ugnot\", 100_000_000}})\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 100_000_000}}, nil)\n\tres := banktest.Deposit(\"ugnot\", 50_000_000)\n\tprintln(\"Deposit():\", res)\n\n\t// print main balance after.\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal)\n\n\t// simulate a Render(). banker should have given back all coins.\n\tres = banktest.Render(\"\")\n\tprintln(res)\n}\n\n// Output:\n// main before: 300000000ugnot\n// Deposit(): returned!\n// main after: 250000000ugnot\n// ## recent activity\n//\n// * g1tnpdmvrmtgql8fmxgsq9rwtst5hsxahk3f05dk 100000000ugnot sent, 50000000ugnot returned, at 2009-02-13 11:31pm UTC\n//\n// ## total deposits\n// 50000000ugnot\n"},{"name":"z_1_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\npackage bank1\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/banktest\"\n)\n\nfunc main() {\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\n\t// simulate a Deposit call.\n\tstd.TestSetOriginPkgAddr(banktestAddr)\n\tstd.TestIssueCoins(banktestAddr, std.Coins{{\"ugnot\", 100000000}})\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 100000000}}, nil)\n\tres := banktest.Deposit(\"ugnot\", 101000000)\n\tprintln(res)\n}\n\n// Error:\n// cannot send \"101000000ugnot\", limit \"100000000ugnot\" exceeded with \"\" already spent\n"},{"name":"z_2_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\npackage bank1\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/banktest\"\n)\n\nfunc main() {\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\n\t// print main balance before.\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/bank1\")\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal) // plus OriginSend equals 300.\n\n\t// simulate a Deposit call.\n\tstd.TestSetOriginPkgAddr(banktestAddr)\n\tstd.TestIssueCoins(banktestAddr, std.Coins{{\"ugnot\", 100000000}})\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 100000000}}, nil)\n\tres := banktest.Deposit(\"ugnot\", 55000000)\n\tprintln(\"Deposit():\", res)\n\n\t// print main balance after.\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal) // now 255.\n\n\t// simulate a Render().\n\tres = banktest.Render(\"\")\n\tprintln(res)\n}\n\n// Output:\n// main before: 200000000ugnot\n// Deposit(): returned!\n// main after: 255000000ugnot\n// ## recent activity\n//\n// * g1tnpdmvrmtgql8fmxgsq9rwtst5hsxahk3f05dk 100000000ugnot sent, 55000000ugnot returned, at 2009-02-13 11:31pm UTC\n//\n// ## total deposits\n// 45000000ugnot\n"},{"name":"z_3_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\npackage bank1\n\nimport (\n\t\"std\"\n)\n\nfunc main() {\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/bank1\")\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tsend := std.Coins{{\"ugnot\", 123}}\n\tbanker.SendCoins(banktestAddr, mainaddr, send)\n\n}\n\n// Error:\n// can only send coins from realm that created banker \"g1tnpdmvrmtgql8fmxgsq9rwtst5hsxahk3f05dk\", not \"g1dv3435088tlrgggf745kaud0ptrkc9v42k8llz\"\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":"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)\n\nvar (\n\tbanker *grc20.Banker // private banker.\n\tToken grc20.Token // public safe-object.\n)\n\nfunc init() {\n\tbanker = grc20.NewBanker(\"Bar\", \"BAR\", 4)\n\tToken = banker.Token()\n}\n\nfunc Faucet() string {\n\tcaller := std.PreviousRealm().Address()\n\tif err := banker.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 banker.RenderHome() // XXX: should be 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.TestSetOriginCaller(alice) // XXX: should not need this\n\n\turequire.Equal(t, Token.BalanceOf(alice), uint64(0))\n\turequire.Equal(t, Faucet(), \"OK\")\n\turequire.Equal(t, Token.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":"counter","path":"gno.land/r/demo/counter","files":[{"name":"counter.gno","body":"package counter\n\nimport \"strconv\"\n\nvar counter int\n\nfunc Increment() int {\n\tcounter++\n\treturn counter\n}\n\nfunc Render(_ string) string {\n\treturn strconv.Itoa(counter)\n}\n"},{"name":"counter_test.gno","body":"package counter\n\nimport \"testing\"\n\nfunc TestIncrement(t *testing.T) {\n\tcounter = 0\n\tval := Increment()\n\tif val != 1 {\n\t\tt.Fatalf(\"result from Increment(): %d != 1\", val)\n\t}\n\tif counter != val {\n\t\tt.Fatalf(\"counter (%d) != val (%d)\", counter, val)\n\t}\n}\n\nfunc TestRender(t *testing.T) {\n\tcounter = 1337\n\tres := Render(\"\")\n\tif res != \"1337\" {\n\t\tt.Fatalf(\"render result %q != %q\", res, \"1337\")\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":"govdao","path":"gno.land/r/gov/dao/v2","files":[{"name":"dao.gno","body":"package govdao\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/membstore\"\n\t\"gno.land/p/demo/simpledao\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar (\n\td *simpledao.SimpleDAO // the current active DAO implementation\n\tmembers membstore.MemberStore // the member store\n)\n\nfunc init() {\n\tvar (\n\t\tset = []membstore.Member{\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\"), // Jae\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"), // Manfred\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1e6gxg5tvc55mwsn7t7dymmlasratv7mkv0rap2\"), // Milos\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1jazghxvvgz3egnr2fc8uf72z4g0l03596y9ls7\"), // Nemanja\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1qhskthp2uycmg4zsdc9squ2jds7yv3t0qyrlnp\"), // Petar\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g18amm3fc00t43dcxsys6udug0czyvqt9e7p23rd\"), // Marc\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1dfr24yhk5ztwtqn2a36m8f6ud8cx5hww4dkjfl\"), // Antonio\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g19p3yzr3cuhzqa02j0ce6kzvyjqfzwemw3vam0x\"), // Guilhem\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1mx4pum9976th863jgry4sdjzfwu03qan5w2v9j\"), // Ray\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g127l4gkhk0emwsx5tmxe96sp86c05h8vg5tufzq\"), // Maxwell\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1cpx59z5r8vzeww2fm4ezpz7yvjs7kptywkm864\"), // Morgan\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1ker4vvggvsyatexxn3hkthp2hu80pkhrwmuczr\"), // Sergio\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g18x425qmujg99cfz3q97y4uep5pxjq3z8lmpt25\"), // Antoine\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t// GNO DEVX\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g16tfrrul20g4jzt3z303raqw8vs8s2pqqh5clwu\"), // Ilker\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1hy6zry03hg5d8le9s2w4fxme6236hkgd928dun\"), // Jerónimo\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g15ruzptpql4dpuyzej0wkt5rq6r26kw4nxu9fwd\"), // Denis\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1dnllrdzwfhxv3evyk09y48mgn5phfjvtyrlzm7\"), // Danny\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1778y2yphxs2wpuaflsy5y9qwcd4gttn4g5yjx5\"), // Michelle\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1mq7g0jszdmn4qdpc9tq94w0gyex37su892n80m\"), // Alan\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g197q5e9v00vuz256ly7fq7v3ekaun5cr7wmjgfh\"), // Salvo\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1mpkp5lm8lwpm0pym4388836d009zfe4maxlqsq\"), // Alexis\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g125em6arxsnj49vx35f0n0z34putv5ty3376fg5\"), // Leon\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1whzkakk4hzjkvy60d5pwfk484xu67ar2cl62h2\"), // Kirk\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t// AiB\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1sw5xklxjjuv0yvuxy5f5s3l3mnj0nqq626a9wr\"), // Albert\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t// ONBLOC\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g12vx7dn3dqq89mz550zwunvg4qw6epq73d9csay\"), // Dongwon\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1r04aw56fgvzy859fachr8hzzhqkulkaemltr76\"), // Blake\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g17n4y745s08awwq4e0a38lagsgtntna0749tnxe\"), // Jinwoo\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1ckae7tc5sez8ul3ssne75sk4muwgttp6ks2ky9\"), // ByeongJun\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t\t// TERITORI\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g14u5eaheavy0ux4dmpykg2gvxpvqvexm9cyg58a\"), // Norman\n\t\t\t\tVotingPower: 5,\n\t\t\t},\n\t\t\t// BERTY\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1qynsu9dwj9lq0m5fkje7jh6qy3md80ztqnshhm\"), // Rémi\n\t\t\t\tVotingPower: 5,\n\t\t\t},\n\t\t\t// FLIPPANDO / ZENTASKTIC\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g17ernafy6ctpcz6uepfsq2js8x2vz0wladh5yc3\"), // Dragos\n\t\t\t\tVotingPower: 5,\n\t\t\t},\n\t\t}\n\t)\n\n\t// Set the member store\n\tmembers = membstore.NewMembStore(membstore.WithInitialMembers(set))\n\n\t// Set the DAO implementation\n\td = simpledao.New(members)\n}\n\n// Propose is designed to be called by another contract or with\n// `maketx run`, not by a `maketx call`.\nfunc Propose(request dao.ProposalRequest) uint64 {\n\tidx, err := d.Propose(request)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn idx\n}\n\n// VoteOnProposal casts a vote for the given proposal\nfunc VoteOnProposal(id uint64, option dao.VoteOption) {\n\tif err := d.VoteOnProposal(id, option); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// ExecuteProposal executes the proposal\nfunc ExecuteProposal(id uint64) {\n\tif err := d.ExecuteProposal(id); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// GetPropStore returns the active proposal store\nfunc GetPropStore() dao.PropStore {\n\treturn d\n}\n\n// GetMembStore returns the active member store\nfunc GetMembStore() membstore.MemberStore {\n\treturn members\n}\n\nfunc Render(path string) string {\n\tif path == \"\" {\n\t\tnumProposals := d.Size()\n\n\t\tif numProposals == 0 {\n\t\t\treturn \"No proposals found :(\" // corner case\n\t\t}\n\n\t\toutput := \"\"\n\n\t\toffset := uint64(0)\n\t\tif numProposals \u003e= 10 {\n\t\t\toffset = uint64(numProposals) - 10\n\t\t}\n\n\t\t// Fetch the last 10 proposals\n\t\tfor idx, prop := range d.Proposals(offset, uint64(10)) {\n\t\t\toutput += ufmt.Sprintf(\n\t\t\t\t\"- [Proposal #%d](%s:%d) - (**%s**)(by %s)\\n\",\n\t\t\t\tidx,\n\t\t\t\t\"/r/gov/dao/v2\",\n\t\t\t\tidx,\n\t\t\t\tprop.Status().String(),\n\t\t\t\tprop.Author().String(),\n\t\t\t)\n\t\t}\n\n\t\treturn output\n\t}\n\n\t// Display the detailed proposal\n\tidx, err := strconv.Atoi(path)\n\tif err != nil {\n\t\treturn \"404: Invalid proposal ID\"\n\t}\n\n\t// Fetch the proposal\n\tprop, err := d.ProposalByID(uint64(idx))\n\tif err != nil {\n\t\treturn ufmt.Sprintf(\"unable to fetch proposal, %s\", err.Error())\n\t}\n\n\t// Render the proposal\n\toutput := \"\"\n\toutput += ufmt.Sprintf(\"# Prop #%d\", idx)\n\toutput += \"\\n\\n\"\n\toutput += prop.Render()\n\toutput += \"\\n\\n\"\n\n\treturn output\n}\n"},{"name":"poc.gno","body":"package govdao\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/combinederr\"\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/membstore\"\n\t\"gno.land/p/gov/executor\"\n)\n\nvar errNoChangesProposed = errors.New(\"no set changes proposed\")\n\n// NewGovDAOExecutor creates the govdao wrapped callback executor\nfunc NewGovDAOExecutor(cb func() error) dao.Executor {\n\tif cb == nil {\n\t\tpanic(errNoChangesProposed)\n\t}\n\n\treturn executor.NewCallbackExecutor(\n\t\tcb,\n\t\tstd.CurrentRealm().PkgPath(),\n\t)\n}\n\n// NewMemberPropExecutor returns the GOVDAO member change executor\nfunc NewMemberPropExecutor(changesFn func() []membstore.Member) dao.Executor {\n\tif changesFn == nil {\n\t\tpanic(errNoChangesProposed)\n\t}\n\n\tcallback := func() error {\n\t\terrs := \u0026combinederr.CombinedError{}\n\t\tcbMembers := changesFn()\n\n\t\tfor _, member := range cbMembers {\n\t\t\tswitch {\n\t\t\tcase !members.IsMember(member.Address):\n\t\t\t\t// Addition request\n\t\t\t\terr := members.AddMember(member)\n\n\t\t\t\terrs.Add(err)\n\t\t\tcase member.VotingPower == 0:\n\t\t\t\t// Remove request\n\t\t\t\terr := members.UpdateMember(member.Address, membstore.Member{\n\t\t\t\t\tAddress: member.Address,\n\t\t\t\t\tVotingPower: 0, // 0 indicated removal\n\t\t\t\t})\n\n\t\t\t\terrs.Add(err)\n\t\t\tdefault:\n\t\t\t\t// Update request\n\t\t\t\terr := members.UpdateMember(member.Address, member)\n\n\t\t\t\terrs.Add(err)\n\t\t\t}\n\t\t}\n\n\t\t// Check if there were any execution errors\n\t\tif errs.Size() == 0 {\n\t\t\treturn nil\n\t\t}\n\n\t\treturn errs\n\t}\n\n\treturn NewGovDAOExecutor(callback)\n}\n\nfunc NewMembStoreImplExecutor(changeFn func() membstore.MemberStore) dao.Executor {\n\tif changeFn == nil {\n\t\tpanic(errNoChangesProposed)\n\t}\n\n\tcallback := func() error {\n\t\tsetMembStoreImpl(changeFn())\n\n\t\treturn nil\n\t}\n\n\treturn NewGovDAOExecutor(callback)\n}\n\n// setMembStoreImpl sets a new dao.MembStore implementation\nfunc setMembStoreImpl(impl membstore.MemberStore) {\n\tif impl == nil {\n\t\tpanic(\"invalid member store\")\n\t}\n\n\tmembers = impl\n}\n"},{"name":"prop1_filetest.gno","body":"// Please note that this package is intended for demonstration purposes only.\n// You could execute this code (the init part) by running a `maketx run` command\n// or by uploading a similar package to a personal namespace.\n//\n// For the specific case of validators, a `r/gnoland/valopers` will be used to\n// organize the lifecycle of validators (register, etc), and this more complex\n// contract will be responsible to generate proposals.\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/dao\"\n\tpVals \"gno.land/p/sys/validators\"\n\tgovdao \"gno.land/r/gov/dao/v2\"\n\tvalidators \"gno.land/r/sys/validators/v2\"\n)\n\nfunc init() {\n\tchangesFn := func() []pVals.Validator {\n\t\treturn []pVals.Validator{\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g12345678\"),\n\t\t\t\tPubKey: \"pubkey\",\n\t\t\t\tVotingPower: 10, // add a new validator\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g000000000\"),\n\t\t\t\tPubKey: \"pubkey\",\n\t\t\t\tVotingPower: 10, // add a new validator\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g000000000\"),\n\t\t\t\tPubKey: \"pubkey\",\n\t\t\t\tVotingPower: 0, // remove an existing validator\n\t\t\t},\n\t\t}\n\t}\n\n\t// Wraps changesFn to emit a certified event only if executed from a\n\t// complete governance proposal process.\n\texecutor := validators.NewPropExecutor(changesFn)\n\n\t// Create a proposal\n\tdescription := \"manual valset changes proposal example\"\n\n\tprop := dao.ProposalRequest{\n\t\tDescription: description,\n\t\tExecutor: executor,\n\t}\n\n\tgovdao.Propose(prop)\n}\n\nfunc main() {\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"\"))\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tgovdao.VoteOnProposal(0, dao.YesVote)\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tprintln(validators.Render(\"\"))\n\tprintln(\"--\")\n\tgovdao.ExecuteProposal(0)\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tprintln(validators.Render(\"\"))\n}\n\n// Output:\n// --\n// - [Proposal #0](/r/gov/dao/v2:0) - (**active**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm)\n//\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// manual valset changes proposal example\n//\n// Status: active\n//\n// Voting stats: YES 0 (0%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 10 (100%)\n//\n// Threshold met: false\n//\n//\n// --\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// manual valset changes proposal example\n//\n// Status: accepted\n//\n// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%)\n//\n// Threshold met: true\n//\n//\n// --\n// No valset changes to apply.\n// --\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// manual valset changes proposal example\n//\n// Status: execution successful\n//\n// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%)\n//\n// Threshold met: true\n//\n//\n// --\n// Valset changes:\n// - #123: g12345678 (10)\n// - #123: g000000000 (10)\n// - #123: g000000000 (0)\n"},{"name":"prop2_filetest.gno","body":"package main\n\nimport (\n\t\"time\"\n\n\t\"gno.land/p/demo/dao\"\n\tgnoblog \"gno.land/r/gnoland/blog\"\n\tgovdao \"gno.land/r/gov/dao/v2\"\n)\n\nfunc init() {\n\tex := gnoblog.NewPostExecutor(\n\t\t\"hello-from-govdao\", // slug\n\t\t\"Hello from GovDAO!\", // title\n\t\t\"This post was published by a GovDAO proposal.\", // body\n\t\ttime.Now().Format(time.RFC3339), // publication date\n\t\t\"moul\", // authors\n\t\t\"govdao,example\", // tags\n\t)\n\n\t// Create a proposal\n\tdescription := \"post a new blogpost about govdao\"\n\n\tprop := dao.ProposalRequest{\n\t\tDescription: description,\n\t\tExecutor: ex,\n\t}\n\n\tgovdao.Propose(prop)\n}\n\nfunc main() {\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"\"))\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tgovdao.VoteOnProposal(0, \"YES\")\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tprintln(gnoblog.Render(\"\"))\n\tprintln(\"--\")\n\tgovdao.ExecuteProposal(0)\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tprintln(gnoblog.Render(\"\"))\n}\n\n// Output:\n// --\n// - [Proposal #0](/r/gov/dao/v2:0) - (**active**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm)\n//\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// post a new blogpost about govdao\n//\n// Status: active\n//\n// Voting stats: YES 0 (0%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 10 (100%)\n//\n// Threshold met: false\n//\n//\n// --\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// post a new blogpost about govdao\n//\n// Status: accepted\n//\n// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%)\n//\n// Threshold met: true\n//\n//\n// --\n// # Gnoland's Blog\n//\n// No posts.\n// --\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// post a new blogpost about govdao\n//\n// Status: execution successful\n//\n// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%)\n//\n// Threshold met: true\n//\n//\n// --\n// # Gnoland's Blog\n//\n// \u003cdiv class='columns-3'\u003e\u003cdiv\u003e\n//\n// ### [Hello from GovDAO!](/r/gnoland/blog:p/hello-from-govdao)\n// 13 Feb 2009\n// \u003c/div\u003e\u003c/div\u003e\n"},{"name":"prop3_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/membstore\"\n\tgovdao \"gno.land/r/gov/dao/v2\"\n)\n\nfunc init() {\n\tmemberFn := func() []membstore.Member {\n\t\treturn []membstore.Member{\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g123\"),\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g456\"),\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g789\"),\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t}\n\t}\n\n\t// Create a proposal\n\tdescription := \"add new members to the govdao\"\n\n\tprop := dao.ProposalRequest{\n\t\tDescription: description,\n\t\tExecutor: govdao.NewMemberPropExecutor(memberFn),\n\t}\n\n\tgovdao.Propose(prop)\n}\n\nfunc main() {\n\tprintln(\"--\")\n\tprintln(govdao.GetMembStore().Size())\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"\"))\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tgovdao.VoteOnProposal(0, \"YES\")\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"\"))\n\tprintln(\"--\")\n\tgovdao.ExecuteProposal(0)\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"\"))\n\tprintln(\"--\")\n\tprintln(govdao.GetMembStore().Size())\n}\n\n// Output:\n// --\n// 1\n// --\n// - [Proposal #0](/r/gov/dao/v2:0) - (**active**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm)\n//\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// add new members to the govdao\n//\n// Status: active\n//\n// Voting stats: YES 0 (0%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 10 (100%)\n//\n// Threshold met: false\n//\n//\n// --\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// add new members to the govdao\n//\n// Status: accepted\n//\n// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%)\n//\n// Threshold met: true\n//\n//\n// --\n// - [Proposal #0](/r/gov/dao/v2:0) - (**accepted**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm)\n//\n// --\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// add new members to the govdao\n//\n// Status: execution successful\n//\n// Voting stats: YES 10 (25%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 30 (75%)\n//\n// Threshold met: false\n//\n//\n// --\n// - [Proposal #0](/r/gov/dao/v2:0) - (**execution successful**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm)\n//\n// --\n// 4\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":"bridge","path":"gno.land/r/gov/dao/bridge","files":[{"name":"bridge.gno","body":"package bridge\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/ownable\"\n)\n\nconst initialOwner = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\") // @moul\n\nvar b *Bridge\n\n// Bridge is the active GovDAO\n// implementation bridge\ntype Bridge struct {\n\t*ownable.Ownable\n\n\tdao DAO\n}\n\n// init constructs the initial GovDAO implementation\nfunc init() {\n\tb = \u0026Bridge{\n\t\tOwnable: ownable.NewWithAddress(initialOwner),\n\t\tdao: \u0026govdaoV2{},\n\t}\n}\n\n// SetDAO sets the currently active GovDAO implementation\nfunc SetDAO(dao DAO) {\n\tb.AssertCallerIsOwner()\n\n\tb.dao = dao\n}\n\n// GovDAO returns the current GovDAO implementation\nfunc GovDAO() DAO {\n\treturn b.dao\n}\n"},{"name":"bridge_test.gno","body":"package bridge\n\nimport (\n\t\"testing\"\n\n\t\"std\"\n\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestBridge_DAO(t *testing.T) {\n\tvar (\n\t\tproposalID = uint64(10)\n\t\tmockDAO = \u0026mockDAO{\n\t\t\tproposeFn: func(_ dao.ProposalRequest) uint64 {\n\t\t\t\treturn proposalID\n\t\t\t},\n\t\t}\n\t)\n\n\tb.dao = mockDAO\n\n\tuassert.Equal(t, proposalID, GovDAO().Propose(dao.ProposalRequest{}))\n}\n\nfunc TestBridge_SetDAO(t *testing.T) {\n\tt.Run(\"invalid owner\", func(t *testing.T) {\n\t\t// Attempt to set a new DAO implementation\n\t\tuassert.PanicsWithMessage(t, ownable.ErrUnauthorized.Error(), func() {\n\t\t\tSetDAO(\u0026mockDAO{})\n\t\t})\n\t})\n\n\tt.Run(\"valid owner\", func(t *testing.T) {\n\t\tvar (\n\t\t\taddr = testutils.TestAddress(\"owner\")\n\n\t\t\tproposalID = uint64(10)\n\t\t\tmockDAO = \u0026mockDAO{\n\t\t\t\tproposeFn: func(_ dao.ProposalRequest) uint64 {\n\t\t\t\t\treturn proposalID\n\t\t\t\t},\n\t\t\t}\n\t\t)\n\n\t\tstd.TestSetOriginCaller(addr)\n\n\t\tb.Ownable = ownable.NewWithAddress(addr)\n\n\t\turequire.NotPanics(t, func() {\n\t\t\tSetDAO(mockDAO)\n\t\t})\n\n\t\tuassert.Equal(\n\t\t\tt,\n\t\t\tmockDAO.Propose(dao.ProposalRequest{}),\n\t\t\tGovDAO().Propose(dao.ProposalRequest{}),\n\t\t)\n\t})\n}\n"},{"name":"doc.gno","body":"// Package bridge represents a GovDAO implementation wrapper, used by other Realms and Packages to\n// always fetch the most active GovDAO implementation, instead of directly referencing it, and having to\n// update it each time the GovDAO implementation changes\npackage bridge\n"},{"name":"mock_test.gno","body":"package bridge\n\nimport (\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/membstore\"\n)\n\ntype (\n\tproposeDelegate func(dao.ProposalRequest) uint64\n\tvoteOnProposalDelegate func(uint64, dao.VoteOption)\n\texecuteProposalDelegate func(uint64)\n\tgetPropStoreDelegate func() dao.PropStore\n\tgetMembStoreDelegate func() membstore.MemberStore\n\tnewGovDAOExecutorDelegate func(func() error) dao.Executor\n)\n\ntype mockDAO struct {\n\tproposeFn proposeDelegate\n\tvoteOnProposalFn voteOnProposalDelegate\n\texecuteProposalFn executeProposalDelegate\n\tgetPropStoreFn getPropStoreDelegate\n\tgetMembStoreFn getMembStoreDelegate\n\tnewGovDAOExecutorFn newGovDAOExecutorDelegate\n}\n\nfunc (m *mockDAO) Propose(request dao.ProposalRequest) uint64 {\n\tif m.proposeFn != nil {\n\t\treturn m.proposeFn(request)\n\t}\n\n\treturn 0\n}\n\nfunc (m *mockDAO) VoteOnProposal(id uint64, option dao.VoteOption) {\n\tif m.voteOnProposalFn != nil {\n\t\tm.voteOnProposalFn(id, option)\n\t}\n}\n\nfunc (m *mockDAO) ExecuteProposal(id uint64) {\n\tif m.executeProposalFn != nil {\n\t\tm.executeProposalFn(id)\n\t}\n}\n\nfunc (m *mockDAO) GetPropStore() dao.PropStore {\n\tif m.getPropStoreFn != nil {\n\t\treturn m.getPropStoreFn()\n\t}\n\n\treturn nil\n}\n\nfunc (m *mockDAO) GetMembStore() membstore.MemberStore {\n\tif m.getMembStoreFn != nil {\n\t\treturn m.getMembStoreFn()\n\t}\n\n\treturn nil\n}\n\nfunc (m *mockDAO) NewGovDAOExecutor(cb func() error) dao.Executor {\n\tif m.newGovDAOExecutorFn != nil {\n\t\treturn m.newGovDAOExecutorFn(cb)\n\t}\n\n\treturn nil\n}\n"},{"name":"types.gno","body":"package bridge\n\nimport (\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/membstore\"\n)\n\n// DAO abstracts the commonly used DAO interface\ntype DAO interface {\n\tPropose(dao.ProposalRequest) uint64\n\tVoteOnProposal(uint64, dao.VoteOption)\n\tExecuteProposal(uint64)\n\tGetPropStore() dao.PropStore\n\tGetMembStore() membstore.MemberStore\n\n\tNewGovDAOExecutor(func() error) dao.Executor\n}\n"},{"name":"v2.gno","body":"package bridge\n\nimport (\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/membstore\"\n\tgovdao \"gno.land/r/gov/dao/v2\"\n)\n\n// govdaoV2 is a wrapper for interacting with the /r/gov/dao/v2 Realm\ntype govdaoV2 struct{}\n\nfunc (g *govdaoV2) Propose(request dao.ProposalRequest) uint64 {\n\treturn govdao.Propose(request)\n}\n\nfunc (g *govdaoV2) VoteOnProposal(id uint64, option dao.VoteOption) {\n\tgovdao.VoteOnProposal(id, option)\n}\n\nfunc (g *govdaoV2) ExecuteProposal(id uint64) {\n\tgovdao.ExecuteProposal(id)\n}\n\nfunc (g *govdaoV2) GetPropStore() dao.PropStore {\n\treturn govdao.GetPropStore()\n}\n\nfunc (g *govdaoV2) GetMembStore() membstore.MemberStore {\n\treturn govdao.GetMembStore()\n}\n\nfunc (g *govdaoV2) NewGovDAOExecutor(cb func() error) dao.Executor {\n\treturn govdao.NewGovDAOExecutor(cb)\n}\n\nfunc (g *govdaoV2) NewMemberPropExecutor(cb func() []membstore.Member) dao.Executor {\n\treturn govdao.NewMemberPropExecutor(cb)\n}\n\nfunc (g *govdaoV2) NewMembStoreImplExecutor(cb func() membstore.MemberStore) dao.Executor {\n\treturn govdao.NewMembStoreImplExecutor(cb)\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":"daoweb","path":"gno.land/r/demo/daoweb","files":[{"name":"daoweb.gno","body":"package daoweb\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/json\"\n\t\"gno.land/r/gov/dao/bridge\"\n)\n\n// Proposals returns the paginated GovDAO proposals\nfunc Proposals(offset, count uint64) string {\n\tvar (\n\t\tpropStore = bridge.GovDAO().GetPropStore()\n\t\tsize = propStore.Size()\n\t)\n\n\t// Get the props\n\tprops := propStore.Proposals(offset, count)\n\n\tresp := ProposalsResponse{\n\t\tProposals: make([]Proposal, 0, count),\n\t\tTotal: uint64(size),\n\t}\n\n\tfor _, p := range props {\n\t\tprop := Proposal{\n\t\t\tAuthor: p.Author(),\n\t\t\tDescription: p.Description(),\n\t\t\tStatus: p.Status(),\n\t\t\tStats: p.Stats(),\n\t\t\tIsExpired: p.IsExpired(),\n\t\t}\n\n\t\tresp.Proposals = append(resp.Proposals, prop)\n\t}\n\n\t// Encode the response into JSON\n\tencodedProps, err := json.Marshal(encodeProposalsResponse(resp))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn string(encodedProps)\n}\n\n// ProposalByID fetches the proposal using the given ID\nfunc ProposalByID(id uint64) string {\n\tpropStore := bridge.GovDAO().GetPropStore()\n\n\tp, err := propStore.ProposalByID(id)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Encode the response into JSON\n\tprop := Proposal{\n\t\tAuthor: p.Author(),\n\t\tDescription: p.Description(),\n\t\tStatus: p.Status(),\n\t\tStats: p.Stats(),\n\t\tIsExpired: p.IsExpired(),\n\t}\n\n\tencodedProp, err := json.Marshal(encodeProposal(prop))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn string(encodedProp)\n}\n\n// encodeProposal encodes a proposal into a json node\nfunc encodeProposal(p Proposal) *json.Node {\n\treturn json.ObjectNode(\"\", map[string]*json.Node{\n\t\t\"author\": json.StringNode(\"author\", p.Author.String()),\n\t\t\"description\": json.StringNode(\"description\", p.Description),\n\t\t\"status\": json.StringNode(\"status\", p.Status.String()),\n\t\t\"stats\": json.ObjectNode(\"stats\", map[string]*json.Node{\n\t\t\t\"yay_votes\": json.NumberNode(\"yay_votes\", float64(p.Stats.YayVotes)),\n\t\t\t\"nay_votes\": json.NumberNode(\"nay_votes\", float64(p.Stats.NayVotes)),\n\t\t\t\"abstain_votes\": json.NumberNode(\"abstain_votes\", float64(p.Stats.AbstainVotes)),\n\t\t\t\"total_voting_power\": json.NumberNode(\"total_voting_power\", float64(p.Stats.TotalVotingPower)),\n\t\t}),\n\t\t\"is_expired\": json.BoolNode(\"is_expired\", p.IsExpired),\n\t})\n}\n\n// encodeProposalsResponse encodes a proposal response into a JSON node\nfunc encodeProposalsResponse(props ProposalsResponse) *json.Node {\n\tproposals := make([]*json.Node, 0, len(props.Proposals))\n\n\tfor _, p := range props.Proposals {\n\t\tproposals = append(proposals, encodeProposal(p))\n\t}\n\n\treturn json.ObjectNode(\"\", map[string]*json.Node{\n\t\t\"proposals\": json.ArrayNode(\"proposals\", proposals),\n\t\t\"total\": json.NumberNode(\"total\", float64(props.Total)),\n\t})\n}\n\n// ProposalsResponse is a paginated proposal response\ntype ProposalsResponse struct {\n\tProposals []Proposal `json:\"proposals\"`\n\tTotal uint64 `json:\"total\"`\n}\n\n// Proposal is a single GovDAO proposal\ntype Proposal struct {\n\tAuthor std.Address `json:\"author\"`\n\tDescription string `json:\"description\"`\n\tStatus dao.ProposalStatus `json:\"status\"`\n\tStats dao.Stats `json:\"stats\"`\n\tIsExpired bool `json:\"is_expired\"`\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":"deep","path":"gno.land/r/demo/deep/very/deep","files":[{"name":"render.gno","body":"package deep\n\nfunc Render(path string) string {\n\tif path == \"\" {\n\t\treturn \"it works!\"\n\t} else {\n\t\treturn \"hi \" + path\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)\n\nvar instances avl.Tree // symbol -\u003e instance\n\nfunc New(name, symbol string, decimals uint, initialMint, faucet uint64) {\n\tcaller := std.PreviousRealm().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\tbanker := grc20.NewBanker(name, symbol, decimals)\n\tif initialMint \u003e 0 {\n\t\tbanker.Mint(admin, initialMint)\n\t}\n\n\tinst := instance{\n\t\tbanker: banker,\n\t\tadmin: ownable.NewWithAddress(admin),\n\t\tfaucet: faucet,\n\t}\n\n\tinstances.Set(symbol, \u0026inst)\n}\n\ntype instance struct {\n\tbanker *grc20.Banker\n\tadmin *ownable.Ownable\n\tfaucet uint64 // per-request amount. disabled if 0.\n}\n\nfunc (inst instance) Token() grc20.Token { return inst.banker.Token() }\n\nfunc TotalSupply(symbol string) uint64 {\n\tinst := mustGetInstance(symbol)\n\treturn inst.Token().TotalSupply()\n}\n\nfunc BalanceOf(symbol string, owner std.Address) uint64 {\n\tinst := mustGetInstance(symbol)\n\treturn inst.Token().BalanceOf(owner)\n}\n\nfunc Allowance(symbol string, owner, spender std.Address) uint64 {\n\tinst := mustGetInstance(symbol)\n\treturn inst.Token().Allowance(owner, spender)\n}\n\nfunc Transfer(symbol string, to std.Address, amount uint64) {\n\tinst := mustGetInstance(symbol)\n\tcheckErr(inst.Token().Transfer(to, amount))\n}\n\nfunc Approve(symbol string, spender std.Address, amount uint64) {\n\tinst := mustGetInstance(symbol)\n\tcheckErr(inst.Token().Approve(spender, amount))\n}\n\nfunc TransferFrom(symbol string, from, to std.Address, amount uint64) {\n\tinst := mustGetInstance(symbol)\n\tcheckErr(inst.Token().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.PreviousRealm().Addr()\n\tcheckErr(inst.banker.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.banker.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.banker.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.banker.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().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)\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/uassert\"\n)\n\nfunc TestReadOnlyPublicMethods(t *testing.T) {\n\tadmin := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\tmanfred := std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\tunknown := std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\") // valid but never used.\n\tNewWithAdmin(\"Foo\", \"FOO\", 4, 10_000*1_000_000, 0, admin)\n\tNewWithAdmin(\"Bar\", \"BAR\", 4, 10_000*1_000, 0, admin)\n\tmustGetInstance(\"FOO\").banker.Mint(manfred, 100_000_000)\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_100_000_000, func() uint64 { return TotalSupply(\"FOO\") }},\n\t\t\t{\"BalanceOf(admin)\", 10_000_000_000, func() uint64 { return BalanceOf(\"FOO\", admin) }},\n\t\t\t{\"BalanceOf(manfred)\", 100_000_000, func() uint64 { return BalanceOf(\"FOO\", manfred) }},\n\t\t\t{\"Allowance(admin, manfred)\", 0, func() uint64 { return Allowance(\"FOO\", admin, manfred) }},\n\t\t\t{\"BalanceOf(unknown)\", 0, func() uint64 { return BalanceOf(\"FOO\", unknown) }},\n\t\t}\n\t\tfor _, tc := range tests {\n\t\t\tuassert.Equal(t, tc.balance, tc.fn(), \"balance does not match\")\n\t\t}\n\t}\n\treturn\n\n\t// unknown uses the faucet.\n\tstd.TestSetOriginCaller(unknown)\n\tFaucet(\"FOO\")\n\n\t// check balances #2.\n\t{\n\t\ttests := []test{\n\t\t\t{\"TotalSupply\", 10_110_000_000, func() uint64 { return TotalSupply(\"FOO\") }},\n\t\t\t{\"BalanceOf(admin)\", 10_000_000_000, func() uint64 { return BalanceOf(\"FOO\", admin) }},\n\t\t\t{\"BalanceOf(manfred)\", 100_000_000, func() uint64 { return BalanceOf(\"FOO\", manfred) }},\n\t\t\t{\"Allowance(admin, manfred)\", 0, func() uint64 { return Allowance(\"FOO\", admin, manfred) }},\n\t\t\t{\"BalanceOf(unknown)\", 10_000_000, func() uint64 { return BalanceOf(\"FOO\", unknown) }},\n\t\t}\n\t\tfor _, tc := range tests {\n\t\t\tuassert.Equal(t, tc.balance, tc.fn(), \"balance does not match\")\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":"disperse","path":"gno.land/r/demo/disperse","files":[{"name":"disperse.gno","body":"package disperse\n\nimport (\n\t\"std\"\n\n\ttokens \"gno.land/r/demo/grc20factory\"\n)\n\n// Get address of Disperse realm\nvar realmAddr = std.CurrentRealm().Addr()\n\n// DisperseUgnot parses receivers and amounts and sends out ugnot\n// The function will send out the coins to the addresses and return the leftover coins to the caller\n// if there are any to return\nfunc DisperseUgnot(addresses []std.Address, coins std.Coins) {\n\tcoinSent := std.OriginSend()\n\tcaller := std.PreviousRealm().Addr()\n\tbanker := std.GetBanker(std.BankerTypeOriginSend)\n\n\tif len(addresses) != len(coins) {\n\t\tpanic(ErrNumAddrValMismatch)\n\t}\n\n\tfor _, coin := range coins {\n\t\tif coin.Amount \u003c= 0 {\n\t\t\tpanic(ErrNegativeCoinAmount)\n\t\t}\n\n\t\tif banker.GetCoins(realmAddr).AmountOf(coin.Denom) \u003c coin.Amount {\n\t\t\tpanic(ErrMismatchBetweenSentAndParams)\n\t\t}\n\t}\n\n\t// Send coins\n\tfor i, _ := range addresses {\n\t\tbanker.SendCoins(realmAddr, addresses[i], std.NewCoins(coins[i]))\n\t}\n\n\t// Return possible leftover coins\n\tfor _, coin := range coinSent {\n\t\tleftoverAmt := banker.GetCoins(realmAddr).AmountOf(coin.Denom)\n\t\tif leftoverAmt \u003e 0 {\n\t\t\tsend := std.Coins{std.NewCoin(coin.Denom, leftoverAmt)}\n\t\t\tbanker.SendCoins(realmAddr, caller, send)\n\t\t}\n\t}\n}\n\n// DisperseGRC20 disperses tokens to multiple addresses\n// Note that it is necessary to approve the realm to spend the tokens before calling this function\n// see the corresponding filetests for examples\nfunc DisperseGRC20(addresses []std.Address, amounts []uint64, symbols []string) {\n\tcaller := std.PreviousRealm().Addr()\n\n\tif (len(addresses) != len(amounts)) || (len(amounts) != len(symbols)) {\n\t\tpanic(ErrArgLenAndSentLenMismatch)\n\t}\n\n\tfor i := 0; i \u003c len(addresses); i++ {\n\t\ttokens.TransferFrom(symbols[i], caller, addresses[i], amounts[i])\n\t}\n}\n\n// DisperseGRC20String receives a string of addresses and a string of tokens\n// and parses them to be used in DisperseGRC20\nfunc DisperseGRC20String(addresses string, tokens string) {\n\tparsedAddresses, err := parseAddresses(addresses)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tparsedAmounts, parsedSymbols, err := parseTokens(tokens)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tDisperseGRC20(parsedAddresses, parsedAmounts, parsedSymbols)\n}\n\n// DisperseUgnotString receives a string of addresses and a string of amounts\n// and parses them to be used in DisperseUgnot\nfunc DisperseUgnotString(addresses string, amounts string) {\n\tparsedAddresses, err := parseAddresses(addresses)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tparsedAmounts, err := parseAmounts(amounts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tcoins := make(std.Coins, len(parsedAmounts))\n\tfor i, amount := range parsedAmounts {\n\t\tcoins[i] = std.NewCoin(\"ugnot\", amount)\n\t}\n\n\tDisperseUgnot(parsedAddresses, coins)\n}\n"},{"name":"doc.gno","body":"// Package disperse provides methods to disperse coins or GRC20 tokens among multiple addresses.\n//\n// The disperse package is an implementation of an existing service that allows users to send coins or GRC20 tokens to multiple addresses\n// on the Ethereum blockchain.\n//\n// Usage:\n// To use disperse, you can either use `DisperseUgnot` to send coins or `DisperseGRC20` to send GRC20 tokens to multiple addresses.\n//\n// Example:\n// Dispersing 200 coins to two addresses:\n// - DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n// Dispersing 200 worth of a GRC20 token \"TEST\" to two addresses:\n// - DisperseGRC20String(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150TEST,50TEST\")\n//\n// Reference:\n// - [the original dispere app](https://disperse.app/)\n// - [the original disperse app on etherscan](https://etherscan.io/address/0xd152f549545093347a162dce210e7293f1452150#code)\n// - [the gno disperse web app](https://gno-disperse.netlify.app/)\npackage disperse // import \"gno.land/r/demo/disperse\"\n"},{"name":"errors.gno","body":"package disperse\n\nimport \"errors\"\n\nvar (\n\tErrNotEnoughCoin = errors.New(\"disperse: not enough coin sent in\")\n\tErrNumAddrValMismatch = errors.New(\"disperse: number of addresses and values to send doesn't match\")\n\tErrInvalidAddress = errors.New(\"disperse: invalid address\")\n\tErrNegativeCoinAmount = errors.New(\"disperse: coin amount cannot be negative\")\n\tErrMismatchBetweenSentAndParams = errors.New(\"disperse: mismatch between coins sent and params called\")\n\tErrArgLenAndSentLenMismatch = errors.New(\"disperse: mismatch between coins sent and args called\")\n)\n"},{"name":"util.gno","body":"package disperse\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"unicode\"\n)\n\nfunc parseAddresses(addresses string) ([]std.Address, error) {\n\tvar ret []std.Address\n\n\tfor _, str := range strings.Split(addresses, \",\") {\n\t\taddr := std.Address(str)\n\t\tif !addr.IsValid() {\n\t\t\treturn nil, ErrInvalidAddress\n\t\t}\n\n\t\tret = append(ret, addr)\n\t}\n\n\treturn ret, nil\n}\n\nfunc splitString(input string) (string, string) {\n\tvar pos int\n\tfor i, char := range input {\n\t\tif !unicode.IsDigit(char) {\n\t\t\tpos = i\n\t\t\tbreak\n\t\t}\n\t}\n\treturn input[:pos], input[pos:]\n}\n\nfunc parseTokens(tokens string) ([]uint64, []string, error) {\n\tvar amounts []uint64\n\tvar symbols []string\n\n\tfor _, token := range strings.Split(tokens, \",\") {\n\t\tamountStr, symbol := splitString(token)\n\t\tamount, _ := strconv.Atoi(amountStr)\n\t\tif amount \u003c 0 {\n\t\t\treturn nil, nil, ErrNegativeCoinAmount\n\t\t}\n\n\t\tamounts = append(amounts, uint64(amount))\n\t\tsymbols = append(symbols, symbol)\n\t}\n\n\treturn amounts, symbols, nil\n}\n\nfunc parseAmounts(amounts string) ([]int64, error) {\n\tvar ret []int64\n\n\tfor _, amt := range strings.Split(amounts, \",\") {\n\t\tamount, _ := strconv.Atoi(amt)\n\t\tif amount \u003c 0 {\n\t\t\treturn nil, ErrNegativeCoinAmount\n\t\t}\n\n\t\tret = append(ret, int64(amount))\n\t}\n\n\treturn ret, nil\n}\n"},{"name":"z_0_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 200ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\n\tstd.TestSetOriginPkgAddr(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\tbanker.SendCoins(mainaddr, disperseAddr, std.Coins{{\"ugnot\", 200}})\n\tdisperse.DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal)\n}\n\n// Output:\n// main before: 200000200ugnot\n// main after: 200000000ugnot\n"},{"name":"z_1_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\n\tstd.TestSetOriginPkgAddr(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\tbanker.SendCoins(mainaddr, disperseAddr, std.Coins{{\"ugnot\", 300}})\n\tdisperse.DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal)\n}\n\n// Output:\n// main before: 200000300ugnot\n// main after: 200000100ugnot\n"},{"name":"z_2_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\n\tstd.TestSetOriginPkgAddr(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\tbanker.SendCoins(mainaddr, disperseAddr, std.Coins{{\"ugnot\", 100}})\n\tdisperse.DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n}\n\n// Error:\n// disperse: mismatch between coins sent and params called\n"},{"name":"z_3_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n\ttokens \"gno.land/r/demo/grc20factory\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\tbeneficiary1 := std.Address(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0\")\n\tbeneficiary2 := std.Address(\"g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\")\n\n\tstd.TestSetOriginPkgAddr(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\ttokens.New(\"test\", \"TEST\", 4, 0, 0)\n\ttokens.Mint(\"TEST\", mainaddr, 200)\n\n\tmainbal := tokens.BalanceOf(\"TEST\", mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\ttokens.Approve(\"TEST\", disperseAddr, 200)\n\n\tdisperse.DisperseGRC20String(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150TEST,50TEST\")\n\n\tmainbal = tokens.BalanceOf(\"TEST\", mainaddr)\n\tprintln(\"main after:\", mainbal)\n\tben1bal := tokens.BalanceOf(\"TEST\", beneficiary1)\n\tprintln(\"beneficiary1:\", ben1bal)\n\tben2bal := tokens.BalanceOf(\"TEST\", beneficiary2)\n\tprintln(\"beneficiary2:\", ben2bal)\n}\n\n// Output:\n// main before: 200\n// main after: 0\n// beneficiary1: 150\n// beneficiary2: 50\n"},{"name":"z_4_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n\ttokens \"gno.land/r/demo/grc20factory\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\tbeneficiary1 := std.Address(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0\")\n\tbeneficiary2 := std.Address(\"g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\")\n\n\tstd.TestSetOriginPkgAddr(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\ttokens.New(\"test1\", \"TEST1\", 4, 0, 0)\n\ttokens.Mint(\"TEST1\", mainaddr, 200)\n\ttokens.New(\"test2\", \"TEST2\", 4, 0, 0)\n\ttokens.Mint(\"TEST2\", mainaddr, 200)\n\n\tmainbal := tokens.BalanceOf(\"TEST1\", mainaddr) + tokens.BalanceOf(\"TEST2\", mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\ttokens.Approve(\"TEST1\", disperseAddr, 200)\n\ttokens.Approve(\"TEST2\", disperseAddr, 200)\n\n\tdisperse.DisperseGRC20String(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"200TEST1,200TEST2\")\n\n\tmainbal = tokens.BalanceOf(\"TEST1\", mainaddr) + tokens.BalanceOf(\"TEST2\", mainaddr)\n\tprintln(\"main after:\", mainbal)\n\tben1bal := tokens.BalanceOf(\"TEST1\", beneficiary1) + tokens.BalanceOf(\"TEST2\", beneficiary1)\n\tprintln(\"beneficiary1:\", ben1bal)\n\tben2bal := tokens.BalanceOf(\"TEST1\", beneficiary2) + tokens.BalanceOf(\"TEST2\", beneficiary2)\n\tprintln(\"beneficiary2:\", ben2bal)\n}\n\n// Output:\n// main before: 400\n// main after: 0\n// beneficiary1: 200\n// beneficiary2: 200\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)\n\nvar instances avl.Tree // symbol -\u003e instance\n\nfunc New(name, symbol string, decimals uint, initialMint, faucet uint64) {\n\tcaller := std.PreviousRealm().Address()\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\tbanker := grc20.NewBanker(name, symbol, decimals)\n\tif initialMint \u003e 0 {\n\t\tbanker.Mint(admin, initialMint)\n\t}\n\n\tinst := instance{\n\t\tbanker: banker,\n\t\tadmin: ownable.NewWithAddress(admin),\n\t\tfaucet: faucet,\n\t}\n\n\tinstances.Set(symbol, \u0026inst)\n}\n\ntype instance struct {\n\tbanker *grc20.Banker\n\tadmin *ownable.Ownable\n\tfaucet uint64 // per-request amount. disabled if 0.\n}\n\nfunc (inst instance) Token() grc20.Token { return inst.banker.Token() }\n\nfunc TotalSupply(symbol string) uint64 {\n\tinst := mustGetInstance(symbol)\n\treturn inst.Token().TotalSupply()\n}\n\nfunc BalanceOf(symbol string, owner std.Address) uint64 {\n\tinst := mustGetInstance(symbol)\n\treturn inst.Token().BalanceOf(owner)\n}\n\nfunc Allowance(symbol string, owner, spender std.Address) uint64 {\n\tinst := mustGetInstance(symbol)\n\treturn inst.Token().Allowance(owner, spender)\n}\n\nfunc Transfer(symbol string, to std.Address, amount uint64) {\n\tinst := mustGetInstance(symbol)\n\tcheckErr(inst.Token().Transfer(to, amount))\n}\n\nfunc Approve(symbol string, spender std.Address, amount uint64) {\n\tinst := mustGetInstance(symbol)\n\tcheckErr(inst.Token().Approve(spender, amount))\n}\n\nfunc TransferFrom(symbol string, from, to std.Address, amount uint64) {\n\tinst := mustGetInstance(symbol)\n\tcheckErr(inst.Token().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.PreviousRealm().Address()\n\tcheckErr(inst.banker.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.banker.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.banker.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.banker.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().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)\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/uassert\"\n)\n\nfunc TestReadOnlyPublicMethods(t *testing.T) {\n\tadmin := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\tmanfred := std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\tunknown := std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\") // valid but never used.\n\tNewWithAdmin(\"Foo\", \"FOO\", 4, 10_000*1_000_000, 0, admin)\n\tNewWithAdmin(\"Bar\", \"BAR\", 4, 10_000*1_000, 0, admin)\n\tmustGetInstance(\"FOO\").banker.Mint(manfred, 100_000_000)\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_100_000_000, func() uint64 { return TotalSupply(\"FOO\") }},\n\t\t\t{\"BalanceOf(admin)\", 10_000_000_000, func() uint64 { return BalanceOf(\"FOO\", admin) }},\n\t\t\t{\"BalanceOf(manfred)\", 100_000_000, func() uint64 { return BalanceOf(\"FOO\", manfred) }},\n\t\t\t{\"Allowance(admin, manfred)\", 0, func() uint64 { return Allowance(\"FOO\", admin, manfred) }},\n\t\t\t{\"BalanceOf(unknown)\", 0, func() uint64 { return BalanceOf(\"FOO\", unknown) }},\n\t\t}\n\t\tfor _, tc := range tests {\n\t\t\tuassert.Equal(t, tc.balance, tc.fn(), \"balance does not match\")\n\t\t}\n\t}\n\treturn\n\n\t// unknown uses the faucet.\n\tstd.TestSetOriginCaller(unknown)\n\tFaucet(\"FOO\")\n\n\t// check balances #2.\n\t{\n\t\ttests := []test{\n\t\t\t{\"TotalSupply\", 10_110_000_000, func() uint64 { return TotalSupply(\"FOO\") }},\n\t\t\t{\"BalanceOf(admin)\", 10_000_000_000, func() uint64 { return BalanceOf(\"FOO\", admin) }},\n\t\t\t{\"BalanceOf(manfred)\", 100_000_000, func() uint64 { return BalanceOf(\"FOO\", manfred) }},\n\t\t\t{\"Allowance(admin, manfred)\", 0, func() uint64 { return Allowance(\"FOO\", admin, manfred) }},\n\t\t\t{\"BalanceOf(unknown)\", 10_000_000, func() uint64 { return BalanceOf(\"FOO\", unknown) }},\n\t\t}\n\t\tfor _, tc := range tests {\n\t\t\tuassert.Equal(t, tc.balance, tc.fn(), \"balance does not match\")\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":"disperse","path":"gno.land/r/demo/disperse","files":[{"name":"disperse.gno","body":"package disperse\n\nimport (\n\t\"std\"\n\n\ttokens \"gno.land/r/demo/grc20factory\"\n)\n\n// Get address of Disperse realm\nvar realmAddr = std.CurrentRealm().Address()\n\n// DisperseUgnot parses receivers and amounts and sends out ugnot\n// The function will send out the coins to the addresses and return the leftover coins to the caller\n// if there are any to return\nfunc DisperseUgnot(addresses []std.Address, coins std.Coins) {\n\tcoinSent := std.OriginSend()\n\tcaller := std.PreviousRealm().Address()\n\tbanker := std.GetBanker(std.BankerTypeOriginSend)\n\n\tif len(addresses) != len(coins) {\n\t\tpanic(ErrNumAddrValMismatch)\n\t}\n\n\tfor _, coin := range coins {\n\t\tif coin.Amount \u003c= 0 {\n\t\t\tpanic(ErrNegativeCoinAmount)\n\t\t}\n\n\t\tif banker.GetCoins(realmAddr).AmountOf(coin.Denom) \u003c coin.Amount {\n\t\t\tpanic(ErrMismatchBetweenSentAndParams)\n\t\t}\n\t}\n\n\t// Send coins\n\tfor i, _ := range addresses {\n\t\tbanker.SendCoins(realmAddr, addresses[i], std.NewCoins(coins[i]))\n\t}\n\n\t// Return possible leftover coins\n\tfor _, coin := range coinSent {\n\t\tleftoverAmt := banker.GetCoins(realmAddr).AmountOf(coin.Denom)\n\t\tif leftoverAmt \u003e 0 {\n\t\t\tsend := std.Coins{std.NewCoin(coin.Denom, leftoverAmt)}\n\t\t\tbanker.SendCoins(realmAddr, caller, send)\n\t\t}\n\t}\n}\n\n// DisperseGRC20 disperses tokens to multiple addresses\n// Note that it is necessary to approve the realm to spend the tokens before calling this function\n// see the corresponding filetests for examples\nfunc DisperseGRC20(addresses []std.Address, amounts []uint64, symbols []string) {\n\tcaller := std.PreviousRealm().Address()\n\n\tif (len(addresses) != len(amounts)) || (len(amounts) != len(symbols)) {\n\t\tpanic(ErrArgLenAndSentLenMismatch)\n\t}\n\n\tfor i := 0; i \u003c len(addresses); i++ {\n\t\ttokens.TransferFrom(symbols[i], caller, addresses[i], amounts[i])\n\t}\n}\n\n// DisperseGRC20String receives a string of addresses and a string of tokens\n// and parses them to be used in DisperseGRC20\nfunc DisperseGRC20String(addresses string, tokens string) {\n\tparsedAddresses, err := parseAddresses(addresses)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tparsedAmounts, parsedSymbols, err := parseTokens(tokens)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tDisperseGRC20(parsedAddresses, parsedAmounts, parsedSymbols)\n}\n\n// DisperseUgnotString receives a string of addresses and a string of amounts\n// and parses them to be used in DisperseUgnot\nfunc DisperseUgnotString(addresses string, amounts string) {\n\tparsedAddresses, err := parseAddresses(addresses)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tparsedAmounts, err := parseAmounts(amounts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tcoins := make(std.Coins, len(parsedAmounts))\n\tfor i, amount := range parsedAmounts {\n\t\tcoins[i] = std.NewCoin(\"ugnot\", amount)\n\t}\n\n\tDisperseUgnot(parsedAddresses, coins)\n}\n"},{"name":"doc.gno","body":"// Package disperse provides methods to disperse coins or GRC20 tokens among multiple addresses.\n//\n// The disperse package is an implementation of an existing service that allows users to send coins or GRC20 tokens to multiple addresses\n// on the Ethereum blockchain.\n//\n// Usage:\n// To use disperse, you can either use `DisperseUgnot` to send coins or `DisperseGRC20` to send GRC20 tokens to multiple addresses.\n//\n// Example:\n// Dispersing 200 coins to two addresses:\n// - DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n// Dispersing 200 worth of a GRC20 token \"TEST\" to two addresses:\n// - DisperseGRC20String(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150TEST,50TEST\")\n//\n// Reference:\n// - [the original dispere app](https://disperse.app/)\n// - [the original disperse app on etherscan](https://etherscan.io/address/0xd152f549545093347a162dce210e7293f1452150#code)\n// - [the gno disperse web app](https://gno-disperse.netlify.app/)\npackage disperse // import \"gno.land/r/demo/disperse\"\n"},{"name":"errors.gno","body":"package disperse\n\nimport \"errors\"\n\nvar (\n\tErrNotEnoughCoin = errors.New(\"disperse: not enough coin sent in\")\n\tErrNumAddrValMismatch = errors.New(\"disperse: number of addresses and values to send doesn't match\")\n\tErrInvalidAddress = errors.New(\"disperse: invalid address\")\n\tErrNegativeCoinAmount = errors.New(\"disperse: coin amount cannot be negative\")\n\tErrMismatchBetweenSentAndParams = errors.New(\"disperse: mismatch between coins sent and params called\")\n\tErrArgLenAndSentLenMismatch = errors.New(\"disperse: mismatch between coins sent and args called\")\n)\n"},{"name":"util.gno","body":"package disperse\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"unicode\"\n)\n\nfunc parseAddresses(addresses string) ([]std.Address, error) {\n\tvar ret []std.Address\n\n\tfor _, str := range strings.Split(addresses, \",\") {\n\t\taddr := std.Address(str)\n\t\tif !addr.IsValid() {\n\t\t\treturn nil, ErrInvalidAddress\n\t\t}\n\n\t\tret = append(ret, addr)\n\t}\n\n\treturn ret, nil\n}\n\nfunc splitString(input string) (string, string) {\n\tvar pos int\n\tfor i, char := range input {\n\t\tif !unicode.IsDigit(char) {\n\t\t\tpos = i\n\t\t\tbreak\n\t\t}\n\t}\n\treturn input[:pos], input[pos:]\n}\n\nfunc parseTokens(tokens string) ([]uint64, []string, error) {\n\tvar amounts []uint64\n\tvar symbols []string\n\n\tfor _, token := range strings.Split(tokens, \",\") {\n\t\tamountStr, symbol := splitString(token)\n\t\tamount, _ := strconv.Atoi(amountStr)\n\t\tif amount \u003c 0 {\n\t\t\treturn nil, nil, ErrNegativeCoinAmount\n\t\t}\n\n\t\tamounts = append(amounts, uint64(amount))\n\t\tsymbols = append(symbols, symbol)\n\t}\n\n\treturn amounts, symbols, nil\n}\n\nfunc parseAmounts(amounts string) ([]int64, error) {\n\tvar ret []int64\n\n\tfor _, amt := range strings.Split(amounts, \",\") {\n\t\tamount, _ := strconv.Atoi(amt)\n\t\tif amount \u003c 0 {\n\t\t\treturn nil, ErrNegativeCoinAmount\n\t\t}\n\n\t\tret = append(ret, int64(amount))\n\t}\n\n\treturn ret, nil\n}\n"},{"name":"z_0_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 200ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\n\tstd.TestSetOriginPkgAddr(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\tbanker.SendCoins(mainaddr, disperseAddr, std.Coins{{\"ugnot\", 200}})\n\tdisperse.DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal)\n}\n\n// Output:\n// main before: 200000200ugnot\n// main after: 200000000ugnot\n"},{"name":"z_1_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\n\tstd.TestSetOriginPkgAddr(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\tbanker.SendCoins(mainaddr, disperseAddr, std.Coins{{\"ugnot\", 300}})\n\tdisperse.DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal)\n}\n\n// Output:\n// main before: 200000300ugnot\n// main after: 200000100ugnot\n"},{"name":"z_2_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\n\tstd.TestSetOriginPkgAddr(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\tbanker.SendCoins(mainaddr, disperseAddr, std.Coins{{\"ugnot\", 100}})\n\tdisperse.DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n}\n\n// Error:\n// disperse: mismatch between coins sent and params called\n"},{"name":"z_3_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n\ttokens \"gno.land/r/demo/grc20factory\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\tbeneficiary1 := std.Address(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0\")\n\tbeneficiary2 := std.Address(\"g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\")\n\n\tstd.TestSetOriginPkgAddr(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\ttokens.New(\"test\", \"TEST\", 4, 0, 0)\n\ttokens.Mint(\"TEST\", mainaddr, 200)\n\n\tmainbal := tokens.BalanceOf(\"TEST\", mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\ttokens.Approve(\"TEST\", disperseAddr, 200)\n\n\tdisperse.DisperseGRC20String(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150TEST,50TEST\")\n\n\tmainbal = tokens.BalanceOf(\"TEST\", mainaddr)\n\tprintln(\"main after:\", mainbal)\n\tben1bal := tokens.BalanceOf(\"TEST\", beneficiary1)\n\tprintln(\"beneficiary1:\", ben1bal)\n\tben2bal := tokens.BalanceOf(\"TEST\", beneficiary2)\n\tprintln(\"beneficiary2:\", ben2bal)\n}\n\n// Output:\n// main before: 200\n// main after: 0\n// beneficiary1: 150\n// beneficiary2: 50\n"},{"name":"z_4_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n\ttokens \"gno.land/r/demo/grc20factory\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\tbeneficiary1 := std.Address(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0\")\n\tbeneficiary2 := std.Address(\"g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\")\n\n\tstd.TestSetOriginPkgAddr(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\ttokens.New(\"test1\", \"TEST1\", 4, 0, 0)\n\ttokens.Mint(\"TEST1\", mainaddr, 200)\n\ttokens.New(\"test2\", \"TEST2\", 4, 0, 0)\n\ttokens.Mint(\"TEST2\", mainaddr, 200)\n\n\tmainbal := tokens.BalanceOf(\"TEST1\", mainaddr) + tokens.BalanceOf(\"TEST2\", mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\ttokens.Approve(\"TEST1\", disperseAddr, 200)\n\ttokens.Approve(\"TEST2\", disperseAddr, 200)\n\n\tdisperse.DisperseGRC20String(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"200TEST1,200TEST2\")\n\n\tmainbal = tokens.BalanceOf(\"TEST1\", mainaddr) + tokens.BalanceOf(\"TEST2\", mainaddr)\n\tprintln(\"main after:\", mainbal)\n\tben1bal := tokens.BalanceOf(\"TEST1\", beneficiary1) + tokens.BalanceOf(\"TEST2\", beneficiary1)\n\tprintln(\"beneficiary1:\", ben1bal)\n\tben2bal := tokens.BalanceOf(\"TEST1\", beneficiary2) + tokens.BalanceOf(\"TEST2\", beneficiary2)\n\tprintln(\"beneficiary2:\", ben2bal)\n}\n\n// Output:\n// main before: 400\n// main after: 0\n// beneficiary1: 200\n// beneficiary2: 200\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":"echo","path":"gno.land/r/demo/echo","files":[{"name":"echo.gno","body":"package echo\n\n/*\n * This realm echoes the `path` argument it received.\n * Can be used by developers as a simple endpoint to test\n * forbidden characters, for pentesting or simply to\n * test it works.\n *\n * See also r/demo/print (to print various thing like user address)\n */\nfunc Render(path string) string {\n\treturn path\n}\n"},{"name":"echo_test.gno","body":"package echo\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc Test(t *testing.T) {\n\turequire.Equal(t, \"aa\", Render(\"aa\"))\n\turequire.Equal(t, \"\", Render(\"\"))\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":"event","path":"gno.land/r/demo/event","files":[{"name":"event.gno","body":"package event\n\nimport (\n\t\"std\"\n)\n\nfunc Emit(value string) {\n\tstd.Emit(\"TAG\", \"key\", value)\n}\n"},{"name":"z1_filetest.gno","body":"package main\n\nimport \"gno.land/r/demo/event\"\n\nfunc main() {\n\tevent.Emit(\"foo\")\n\tevent.Emit(\"bar\")\n}\n\n// Events:\n// [\n// {\n// \"type\": \"TAG\",\n// \"attrs\": [\n// {\n// \"key\": \"key\",\n// \"value\": \"foo\"\n// }\n// ],\n// \"pkg_path\": \"gno.land/r/demo/event\",\n// \"func\": \"Emit\"\n// },\n// {\n// \"type\": \"TAG\",\n// \"attrs\": [\n// {\n// \"key\": \"key\",\n// \"value\": \"bar\"\n// }\n// ],\n// \"pkg_path\": \"gno.land/r/demo/event\",\n// \"func\": \"Emit\"\n// }\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":"foo1155","path":"gno.land/r/demo/foo1155","files":[{"name":"foo1155.gno","body":"package foo1155\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/grc/grc1155\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/r/demo/users\"\n\n\tpusers \"gno.land/p/demo/users\"\n)\n\nvar (\n\tdummyURI = \"ipfs://xyz\"\n\tadmin std.Address = \"g10x5phu0k6p64cwrhfpsc8tk43st9kug6wft530\"\n\tfoo = grc1155.NewBasicGRC1155Token(dummyURI)\n)\n\nfunc init() {\n\tmintGRC1155Token(admin) // @administrator (10)\n}\n\nfunc mintGRC1155Token(owner std.Address) {\n\tfor i := 1; i \u003c= 10; i++ {\n\t\ttid := grc1155.TokenID(ufmt.Sprintf(\"%d\", i))\n\t\tfoo.SafeMint(owner, tid, 100)\n\t}\n}\n\n// Getters\n\nfunc BalanceOf(user pusers.AddressOrName, tid grc1155.TokenID) uint64 {\n\tbalance, err := foo.BalanceOf(users.Resolve(user), tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn balance\n}\n\nfunc BalanceOfBatch(ul []pusers.AddressOrName, batch []grc1155.TokenID) []uint64 {\n\tvar usersResolved []std.Address\n\n\tfor i := 0; i \u003c len(ul); i++ {\n\t\tusersResolved[i] = users.Resolve(ul[i])\n\t}\n\tbalanceBatch, err := foo.BalanceOfBatch(usersResolved, batch)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn balanceBatch\n}\n\nfunc IsApprovedForAll(owner, user pusers.AddressOrName) bool {\n\treturn foo.IsApprovedForAll(users.Resolve(owner), users.Resolve(user))\n}\n\n// Setters\n\nfunc SetApprovalForAll(user pusers.AddressOrName, approved bool) {\n\terr := foo.SetApprovalForAll(users.Resolve(user), approved)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc TransferFrom(from, to pusers.AddressOrName, tid grc1155.TokenID, amount uint64) {\n\terr := foo.SafeTransferFrom(users.Resolve(from), users.Resolve(to), tid, amount)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc BatchTransferFrom(from, to pusers.AddressOrName, batch []grc1155.TokenID, amounts []uint64) {\n\terr := foo.SafeBatchTransferFrom(users.Resolve(from), users.Resolve(to), batch, amounts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// Admin\n\nfunc Mint(to pusers.AddressOrName, tid grc1155.TokenID, amount uint64) {\n\tcaller := std.OriginCaller()\n\tassertIsAdmin(caller)\n\terr := foo.SafeMint(users.Resolve(to), tid, amount)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc MintBatch(to pusers.AddressOrName, batch []grc1155.TokenID, amounts []uint64) {\n\tcaller := std.OriginCaller()\n\tassertIsAdmin(caller)\n\terr := foo.SafeBatchMint(users.Resolve(to), batch, amounts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc Burn(from pusers.AddressOrName, tid grc1155.TokenID, amount uint64) {\n\tcaller := std.OriginCaller()\n\tassertIsAdmin(caller)\n\terr := foo.Burn(users.Resolve(from), tid, amount)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc BurnBatch(from pusers.AddressOrName, batch []grc1155.TokenID, amounts []uint64) {\n\tcaller := std.OriginCaller()\n\tassertIsAdmin(caller)\n\terr := foo.BatchBurn(users.Resolve(from), batch, amounts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// Render\n\nfunc Render(path string) string {\n\tswitch {\n\tcase path == \"\":\n\t\treturn foo.RenderHome()\n\tdefault:\n\t\treturn \"404\\n\"\n\t}\n}\n\n// Util\n\nfunc assertIsAdmin(address std.Address) {\n\tif address != admin {\n\t\tpanic(\"restricted access\")\n\t}\n}\n"},{"name":"foo1155_test.gno","body":"package foo1155\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/grc/grc1155\"\n\t\"gno.land/p/demo/users\"\n)\n\nfunc TestFoo721(t *testing.T) {\n\tadmin := users.AddressOrName(\"g10x5phu0k6p64cwrhfpsc8tk43st9kug6wft530\")\n\tbob := users.AddressOrName(\"g1ze6et22ces5atv79y4xh38s4kuraey4y2fr6tw\")\n\ttid1 := grc1155.TokenID(\"1\")\n\ttid2 := grc1155.TokenID(\"2\")\n\n\tfor i, tc := range []struct {\n\t\tname string\n\t\texpected interface{}\n\t\tfn func() interface{}\n\t}{\n\t\t{\"BalanceOf(admin, tid1)\", uint64(100), func() interface{} { return BalanceOf(admin, tid1) }},\n\t\t{\"BalanceOf(bob, tid1)\", uint64(0), func() interface{} { return BalanceOf(bob, tid1) }},\n\t\t{\"IsApprovedForAll(admin, bob)\", false, func() interface{} { return IsApprovedForAll(admin, bob) }},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := tc.fn()\n\t\t\tif tc.expected != got {\n\t\t\t\tt.Errorf(\"expected: %v got: %v\", tc.expected, got)\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/foo20","files":[{"name":"foo20.gno","body":"// foo20 is a GRC20 token contract where all the GRC20 methods are proxified\n// 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/users\"\n)\n\nvar (\n\tbanker *grc20.Banker\n\tadmin *ownable.Ownable\n\ttoken grc20.Token\n)\n\nfunc init() {\n\tadmin = ownable.NewWithAddress(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\") // @manfred\n\tbanker = grc20.NewBanker(\"Foo\", \"FOO\", 4)\n\tbanker.Mint(admin.Owner(), 1000000*10000) // @administrator (1M)\n\ttoken = banker.Token()\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\tcheckErr(token.Transfer(toAddr, amount))\n}\n\nfunc Approve(spender pusers.AddressOrName, amount uint64) {\n\tspenderAddr := users.Resolve(spender)\n\tcheckErr(token.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(token.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.PreviousRealm().Addr()\n\tamount := uint64(1_000 * 10_000) // 1k\n\tcheckErr(banker.Mint(caller, amount))\n}\n\nfunc Mint(to pusers.AddressOrName, amount uint64) {\n\tadmin.AssertCallerIsOwner()\n\ttoAddr := users.Resolve(to)\n\tcheckErr(banker.Mint(toAddr, amount))\n}\n\nfunc Burn(from pusers.AddressOrName, amount uint64) {\n\tadmin.AssertCallerIsOwner()\n\tfromAddr := users.Resolve(from)\n\tcheckErr(banker.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 banker.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 := banker.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(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\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.TestSetOriginCaller(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(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\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\tstd.TestSetOriginCaller(users.Resolve(admin))\n\t{\n\t\ttests := []test{\n\t\t\t{\"Transfer(admin, 1)\", \"cannot send transfer to self\", func() { Transfer(admin, 1) }},\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":"foo721","path":"gno.land/r/demo/foo721","files":[{"name":"foo721.gno","body":"package foo721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/grc/grc721\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/r/demo/users\"\n\n\tpusers \"gno.land/p/demo/users\"\n)\n\nvar (\n\tadmin std.Address = \"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\"\n\tfoo = grc721.NewBasicNFT(\"FooNFT\", \"FNFT\")\n)\n\nfunc init() {\n\tmintNNFT(admin, 10) // @administrator (10)\n\tmintNNFT(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\", 5) // @hariom (5)\n}\n\nfunc mintNNFT(owner std.Address, n uint64) {\n\tcount := foo.TokenCount()\n\tfor i := count; i \u003c count+n; i++ {\n\t\ttid := grc721.TokenID(ufmt.Sprintf(\"%d\", i))\n\t\tfoo.Mint(owner, tid)\n\t}\n}\n\n// Getters\n\nfunc BalanceOf(user pusers.AddressOrName) uint64 {\n\tbalance, err := foo.BalanceOf(users.Resolve(user))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn balance\n}\n\nfunc OwnerOf(tid grc721.TokenID) std.Address {\n\towner, err := foo.OwnerOf(tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn owner\n}\n\nfunc IsApprovedForAll(owner, user pusers.AddressOrName) bool {\n\treturn foo.IsApprovedForAll(users.Resolve(owner), users.Resolve(user))\n}\n\nfunc GetApproved(tid grc721.TokenID) std.Address {\n\taddr, err := foo.GetApproved(tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn addr\n}\n\n// Setters\n\nfunc Approve(user pusers.AddressOrName, tid grc721.TokenID) {\n\terr := foo.Approve(users.Resolve(user), tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc SetApprovalForAll(user pusers.AddressOrName, approved bool) {\n\terr := foo.SetApprovalForAll(users.Resolve(user), approved)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc TransferFrom(from, to pusers.AddressOrName, tid grc721.TokenID) {\n\terr := foo.TransferFrom(users.Resolve(from), users.Resolve(to), tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// Admin\n\nfunc Mint(to pusers.AddressOrName, tid grc721.TokenID) {\n\tcaller := std.PreviousRealm().Addr()\n\tassertIsAdmin(caller)\n\terr := foo.Mint(users.Resolve(to), tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc Burn(tid grc721.TokenID) {\n\tcaller := std.PreviousRealm().Addr()\n\tassertIsAdmin(caller)\n\terr := foo.Burn(tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// Render\n\nfunc Render(path string) string {\n\tswitch {\n\tcase path == \"\":\n\t\treturn foo.RenderHome()\n\tdefault:\n\t\treturn \"404\\n\"\n\t}\n}\n\n// Util\n\nfunc assertIsAdmin(address std.Address) {\n\tif address != admin {\n\t\tpanic(\"restricted access\")\n\t}\n}\n"},{"name":"foo721_test.gno","body":"package foo721\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/grc/grc721\"\n\t\"gno.land/r/demo/users\"\n\n\tpusers \"gno.land/p/demo/users\"\n)\n\nfunc TestFoo721(t *testing.T) {\n\tadmin := pusers.AddressOrName(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\thariom := pusers.AddressOrName(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tfor i, tc := range []struct {\n\t\tname string\n\t\texpected interface{}\n\t\tfn func() interface{}\n\t}{\n\t\t{\"BalanceOf(admin)\", uint64(10), func() interface{} { return BalanceOf(admin) }},\n\t\t{\"BalanceOf(hariom)\", uint64(5), func() interface{} { return BalanceOf(hariom) }},\n\t\t{\"OwnerOf(0)\", users.Resolve(admin), func() interface{} { return OwnerOf(grc721.TokenID(\"0\")) }},\n\t\t{\"IsApprovedForAll(admin, hariom)\", false, func() interface{} { return IsApprovedForAll(admin, hariom) }},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := tc.fn()\n\t\t\tif tc.expected != got {\n\t\t\t\tt.Errorf(\"expected: %v got: %v\", tc.expected, got)\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":"dice_roller","path":"gno.land/r/demo/games/dice_roller","files":[{"name":"dice_roller.gno","body":"package dice_roller\n\nimport (\n\t\"errors\"\n\t\"math/rand\"\n\t\"sort\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/entropy\"\n\t\"gno.land/p/demo/seqid\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/r/demo/users\"\n)\n\ntype (\n\t// game represents a Dice Roller game between two players\n\tgame struct {\n\t\tplayer1, player2 std.Address\n\t\troll1, roll2 int\n\t}\n\n\t// player holds the information about each player including their stats\n\tplayer struct {\n\t\taddr std.Address\n\t\twins, losses, draws, points int\n\t}\n\n\t// leaderBoard is a slice of players, used to sort players by rank\n\tleaderBoard []player\n)\n\nconst (\n\t// Constants to represent game result outcomes\n\tongoing = iota\n\twin\n\tdraw\n\tloss\n)\n\nvar (\n\tgames avl.Tree // AVL tree for storing game states\n\tgameId seqid.ID // Sequence ID for games\n\n\tplayers avl.Tree // AVL tree for storing player data\n\n\tseed = uint64(entropy.New().Seed())\n\tr = rand.New(rand.NewPCG(seed, 0xdeadbeef))\n)\n\n// rollDice generates a random dice roll between 1 and 6\nfunc rollDice() int {\n\treturn r.IntN(6) + 1\n}\n\n// NewGame initializes a new game with the provided opponent's address\nfunc NewGame(addr std.Address) int {\n\tif !addr.IsValid() {\n\t\tpanic(\"invalid opponent's address\")\n\t}\n\n\tgames.Set(gameId.Next().String(), \u0026game{\n\t\tplayer1: std.PreviousRealm().Addr(),\n\t\tplayer2: addr,\n\t})\n\n\treturn int(gameId)\n}\n\n// Play allows a player to roll the dice and updates the game state accordingly\nfunc Play(idx int) int {\n\tg, err := getGame(idx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\troll := rollDice() // Random the player's dice roll\n\n\t// Play the game and update the player's roll\n\tif err := g.play(std.PreviousRealm().Addr(), roll); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// If both players have rolled, update the results and leaderboard\n\tif g.isFinished() {\n\t\t// If the player is playing against themselves, no points are awarded\n\t\tif g.player1 == g.player2 {\n\t\t\treturn roll\n\t\t}\n\n\t\tplayer1 := getPlayer(g.player1)\n\t\tplayer2 := getPlayer(g.player2)\n\n\t\tif g.roll1 \u003e g.roll2 {\n\t\t\tplayer1.updateStats(win)\n\t\t\tplayer2.updateStats(loss)\n\t\t} else if g.roll2 \u003e g.roll1 {\n\t\t\tplayer2.updateStats(win)\n\t\t\tplayer1.updateStats(loss)\n\t\t} else {\n\t\t\tplayer1.updateStats(draw)\n\t\t\tplayer2.updateStats(draw)\n\t\t}\n\t}\n\n\treturn roll\n}\n\n// play processes a player's roll and updates their score\nfunc (g *game) play(player std.Address, roll int) error {\n\tif player != g.player1 \u0026\u0026 player != g.player2 {\n\t\treturn errors.New(\"invalid player\")\n\t}\n\n\tif g.isFinished() {\n\t\treturn errors.New(\"game over\")\n\t}\n\n\tif player == g.player1 \u0026\u0026 g.roll1 == 0 {\n\t\tg.roll1 = roll\n\t\treturn nil\n\t}\n\n\tif player == g.player2 \u0026\u0026 g.roll2 == 0 {\n\t\tg.roll2 = roll\n\t\treturn nil\n\t}\n\n\treturn errors.New(\"already played\")\n}\n\n// isFinished checks if the game has ended\nfunc (g *game) isFinished() bool {\n\treturn g.roll1 != 0 \u0026\u0026 g.roll2 != 0\n}\n\n// checkResult returns the game status as a formatted string\nfunc (g *game) status() string {\n\tif !g.isFinished() {\n\t\treturn resultIcon(ongoing) + \" Game still in progress\"\n\t}\n\n\tif g.roll1 \u003e g.roll2 {\n\t\treturn resultIcon(win) + \" Player1 Wins !\"\n\t} else if g.roll2 \u003e g.roll1 {\n\t\treturn resultIcon(win) + \" Player2 Wins !\"\n\t} else {\n\t\treturn resultIcon(draw) + \" It's a Draw !\"\n\t}\n}\n\n// Render provides a summary of the current state of games and leader board\nfunc Render(path string) string {\n\tvar sb strings.Builder\n\n\tsb.WriteString(`# 🎲 **Dice Roller Game**\n\nWelcome to Dice Roller! Challenge your friends to a simple yet exciting dice rolling game. Roll the dice and see who gets the highest score !\n\n---\n\n## **How to Play**:\n1. **Create a game**: Challenge an opponent using [NewGame](./dice_roller$help\u0026func=NewGame)\n2. **Roll the dice**: Play your turn by rolling a dice using [Play](./dice_roller$help\u0026func=Play)\n\n---\n\n## **Scoring Rules**:\n- **Win** 🏆: +3 points\n- **Draw** 🤝: +1 point each\n- **Lose** ❌: No points\n- **Playing against yourself**: No points or stats changes for you\n\n---\n\n## **Recent Games**:\nBelow are the results from the most recent games. Up to 10 recent games are displayed\n\n| Game | Player 1 | 🎲 Roll 1 | Player 2 | 🎲 Roll 2 | 🏆 Winner |\n|------|----------|-----------|----------|-----------|-----------|\n`)\n\n\tmaxGames := 10\n\tfor n := int(gameId); n \u003e 0 \u0026\u0026 int(gameId)-n \u003c maxGames; n-- {\n\t\tg, err := getGame(n)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tsb.WriteString(strconv.Itoa(n) + \" | \" +\n\t\t\t\"\u003cspan title=\\\"\" + string(g.player1) + \"\\\"\u003e\" + shortName(g.player1) + \"\u003c/span\u003e\" + \" | \" + diceIcon(g.roll1) + \" | \" +\n\t\t\t\"\u003cspan title=\\\"\" + string(g.player2) + \"\\\"\u003e\" + shortName(g.player2) + \"\u003c/span\u003e\" + \" | \" + diceIcon(g.roll2) + \" | \" +\n\t\t\tg.status() + \"\\n\")\n\t}\n\n\tsb.WriteString(`\n---\n\n## **Leaderboard**:\nThe top players are ranked by performance. Games played against oneself are not counted in the leaderboard\n\n| Rank | Player | Wins | Losses | Draws | Points |\n|------|-----------------------|------|--------|-------|--------|\n`)\n\n\tfor i, player := range getLeaderBoard() {\n\t\tsb.WriteString(ufmt.Sprintf(\"| %s | \u003cspan title=\\\"\"+string(player.addr)+\"\\\"\u003e**%s**\u003c/span\u003e | %d | %d | %d | %d |\\n\",\n\t\t\trankIcon(i+1),\n\t\t\tshortName(player.addr),\n\t\t\tplayer.wins,\n\t\t\tplayer.losses,\n\t\t\tplayer.draws,\n\t\t\tplayer.points,\n\t\t))\n\t}\n\n\tsb.WriteString(\"\\n---\\n**Good luck and have fun !** 🎉\")\n\treturn sb.String()\n}\n\n// shortName returns a shortened name for the given address\nfunc shortName(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user != nil {\n\t\treturn user.Name\n\t}\n\tif len(addr) \u003c 10 {\n\t\treturn string(addr)\n\t}\n\treturn string(addr)[:10] + \"...\"\n}\n\n// getGame retrieves the game state by its ID\nfunc getGame(idx int) (*game, error) {\n\tv, ok := games.Get(seqid.ID(idx).String())\n\tif !ok {\n\t\treturn nil, errors.New(\"game not found\")\n\t}\n\treturn v.(*game), nil\n}\n\n// updateResult updates the player's stats and points based on the game outcome\nfunc (p *player) updateStats(result int) {\n\tswitch result {\n\tcase win:\n\t\tp.wins++\n\t\tp.points += 3\n\tcase loss:\n\t\tp.losses++\n\tcase draw:\n\t\tp.draws++\n\t\tp.points++\n\t}\n}\n\n// getPlayer retrieves a player or initializes a new one if they don't exist\nfunc getPlayer(addr std.Address) *player {\n\tv, ok := players.Get(addr.String())\n\tif !ok {\n\t\tplayer := \u0026player{\n\t\t\taddr: addr,\n\t\t}\n\t\tplayers.Set(addr.String(), player)\n\t\treturn player\n\t}\n\n\treturn v.(*player)\n}\n\n// getLeaderBoard generates a leaderboard sorted by points\nfunc getLeaderBoard() leaderBoard {\n\tboard := leaderBoard{}\n\tplayers.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tplayer := value.(*player)\n\t\tboard = append(board, *player)\n\t\treturn false\n\t})\n\n\tsort.Sort(board)\n\n\treturn board\n}\n\n// Methods for sorting the leaderboard\nfunc (r leaderBoard) Len() int {\n\treturn len(r)\n}\n\nfunc (r leaderBoard) Less(i, j int) bool {\n\tif r[i].points != r[j].points {\n\t\treturn r[i].points \u003e r[j].points\n\t}\n\n\tif r[i].wins != r[j].wins {\n\t\treturn r[i].wins \u003e r[j].wins\n\t}\n\n\tif r[i].draws != r[j].draws {\n\t\treturn r[i].draws \u003e r[j].draws\n\t}\n\n\treturn false\n}\n\nfunc (r leaderBoard) Swap(i, j int) {\n\tr[i], r[j] = r[j], r[i]\n}\n"},{"name":"dice_roller_test.gno","body":"package dice_roller\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/seqid\"\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nvar (\n\tplayer1 = testutils.TestAddress(\"alice\")\n\tplayer2 = testutils.TestAddress(\"bob\")\n\tunknownPlayer = testutils.TestAddress(\"unknown\")\n)\n\n// resetGameState resets the game state for testing\nfunc resetGameState() {\n\tgames = avl.Tree{}\n\tgameId = seqid.ID(0)\n\tplayers = avl.Tree{}\n}\n\n// TestNewGame tests the initialization of a new game\nfunc TestNewGame(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOriginCaller(player1)\n\tgameID := NewGame(player2)\n\n\t// Verify that the game has been correctly initialized\n\tg, err := getGame(gameID)\n\turequire.NoError(t, err)\n\turequire.Equal(t, player1.String(), g.player1.String())\n\turequire.Equal(t, player2.String(), g.player2.String())\n\turequire.Equal(t, 0, g.roll1)\n\turequire.Equal(t, 0, g.roll2)\n}\n\n// TestPlay tests the dice rolling functionality for both players\nfunc TestPlay(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOriginCaller(player1)\n\tgameID := NewGame(player2)\n\n\tg, err := getGame(gameID)\n\turequire.NoError(t, err)\n\n\t// Simulate rolling dice for player 1\n\troll1 := Play(gameID)\n\n\t// Verify player 1's roll\n\turequire.NotEqual(t, 0, g.roll1)\n\turequire.Equal(t, g.roll1, roll1)\n\turequire.Equal(t, 0, g.roll2) // Player 2 hasn't rolled yet\n\n\t// Simulate rolling dice for player 2\n\tstd.TestSetOriginCaller(player2)\n\troll2 := Play(gameID)\n\n\t// Verify player 2's roll\n\turequire.NotEqual(t, 0, g.roll2)\n\turequire.Equal(t, g.roll1, roll1)\n\turequire.Equal(t, g.roll2, roll2)\n}\n\n// TestPlayAgainstSelf tests the scenario where a player plays against themselves\nfunc TestPlayAgainstSelf(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOriginCaller(player1)\n\tgameID := NewGame(player1)\n\n\t// Simulate rolling dice twice by the same player\n\troll1 := Play(gameID)\n\troll2 := Play(gameID)\n\n\tg, err := getGame(gameID)\n\turequire.NoError(t, err)\n\turequire.Equal(t, g.roll1, roll1)\n\turequire.Equal(t, g.roll2, roll2)\n}\n\n// TestPlayInvalidPlayer tests the scenario where an invalid player tries to play\nfunc TestPlayInvalidPlayer(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOriginCaller(player1)\n\tgameID := NewGame(player1)\n\n\t// Attempt to play as an invalid player\n\tstd.TestSetOriginCaller(unknownPlayer)\n\turequire.PanicsWithMessage(t, \"invalid player\", func() {\n\t\tPlay(gameID)\n\t})\n}\n\n// TestPlayAlreadyPlayed tests the scenario where a player tries to play again after already playing\nfunc TestPlayAlreadyPlayed(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOriginCaller(player1)\n\tgameID := NewGame(player2)\n\n\t// Player 1 rolls\n\tPlay(gameID)\n\n\t// Player 1 tries to roll again\n\turequire.PanicsWithMessage(t, \"already played\", func() {\n\t\tPlay(gameID)\n\t})\n}\n\n// TestPlayBeyondGameEnd tests that playing after both players have finished their rolls fails\nfunc TestPlayBeyondGameEnd(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOriginCaller(player1)\n\tgameID := NewGame(player2)\n\n\t// Play for both players\n\tstd.TestSetOriginCaller(player1)\n\tPlay(gameID)\n\tstd.TestSetOriginCaller(player2)\n\tPlay(gameID)\n\n\t// Check if the game is over\n\tg, err := getGame(gameID)\n\turequire.NoError(t, err)\n\n\t// Attempt to play more should fail\n\tstd.TestSetOriginCaller(player1)\n\turequire.PanicsWithMessage(t, \"game over\", func() {\n\t\tPlay(gameID)\n\t})\n}\n"},{"name":"icon.gno","body":"package dice_roller\n\nimport (\n\t\"strconv\"\n)\n\n// diceIcon returns an icon of the dice roll\nfunc diceIcon(roll int) string {\n\tswitch roll {\n\tcase 1:\n\t\treturn \"🎲1\"\n\tcase 2:\n\t\treturn \"🎲2\"\n\tcase 3:\n\t\treturn \"🎲3\"\n\tcase 4:\n\t\treturn \"🎲4\"\n\tcase 5:\n\t\treturn \"🎲5\"\n\tcase 6:\n\t\treturn \"🎲6\"\n\tdefault:\n\t\treturn \"❓\"\n\t}\n}\n\n// resultIcon returns the icon representing the result of a game\nfunc resultIcon(result int) string {\n\tswitch result {\n\tcase ongoing:\n\t\treturn \"🔄\"\n\tcase win:\n\t\treturn \"🏆\"\n\tcase loss:\n\t\treturn \"❌\"\n\tcase draw:\n\t\treturn \"🤝\"\n\tdefault:\n\t\treturn \"❓\"\n\t}\n}\n\n// rankIcon returns the icon for a player's rank\nfunc rankIcon(rank int) string {\n\tswitch rank {\n\tcase 1:\n\t\treturn \"🥇\"\n\tcase 2:\n\t\treturn \"🥈\"\n\tcase 3:\n\t\treturn \"🥉\"\n\tdefault:\n\t\treturn strconv.Itoa(rank)\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":"shifumi","path":"gno.land/r/demo/games/shifumi","files":[{"name":"shifumi.gno","body":"package shifumi\n\nimport (\n\t\"errors\"\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/seqid\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nconst (\n\tempty = iota\n\trock\n\tpaper\n\tscissors\n\tlast\n)\n\ntype game struct {\n\tplayer1, player2 std.Address // shifumi is a 2 players game\n\tmove1, move2 int // can be empty, rock, paper, or scissors\n}\n\nvar games avl.Tree\nvar id seqid.ID\n\nfunc (g *game) play(player std.Address, move int) error {\n\tif !(move \u003e empty \u0026\u0026 move \u003c last) {\n\t\treturn errors.New(\"invalid move\")\n\t}\n\tif player != g.player1 \u0026\u0026 player != g.player2 {\n\t\treturn errors.New(\"invalid player\")\n\t}\n\tif player == g.player1 \u0026\u0026 g.move1 == empty {\n\t\tg.move1 = move\n\t\treturn nil\n\t}\n\tif player == g.player2 \u0026\u0026 g.move2 == empty {\n\t\tg.move2 = move\n\t\treturn nil\n\t}\n\treturn errors.New(\"already played\")\n}\n\nfunc (g *game) winner() int {\n\tif g.move1 == empty || g.move2 == empty {\n\t\treturn -1\n\t}\n\tif g.move1 == g.move2 {\n\t\treturn 0\n\t}\n\tif g.move1 == rock \u0026\u0026 g.move2 == scissors ||\n\t\tg.move1 == paper \u0026\u0026 g.move2 == rock ||\n\t\tg.move1 == scissors \u0026\u0026 g.move2 == paper {\n\t\treturn 1\n\t}\n\treturn 2\n}\n\n// NewGame creates a new game where player1 is the caller and player2 the argument.\n// A new game index is returned.\nfunc NewGame(player std.Address) int {\n\tgames.Set(id.Next().String(), \u0026game{player1: std.PreviousRealm().Addr(), player2: player})\n\treturn int(id)\n}\n\n// Play executes a move for the game at index idx, where move can be:\n// 1 (rock), 2 (paper), 3 (scissors).\nfunc Play(idx, move int) {\n\tv, ok := games.Get(seqid.ID(idx).String())\n\tif !ok {\n\t\tpanic(\"game not found\")\n\t}\n\tif err := v.(*game).play(std.PreviousRealm().Addr(), move); err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc Render(path string) string {\n\tmov1 := []string{\"\", \" 🤜 \", \" 🫱 \", \" 👉 \"}\n\tmov2 := []string{\"\", \" 🤛 \", \" 🫲 \", \" 👈 \"}\n\twin := []string{\"pending\", \"draw\", \"player1\", \"player2\"}\n\n\toutput := `# 👊 ✋ ✌️ Shifumi\nActions:\n* [NewGame](shifumi$help\u0026func=NewGame) opponentAddress\n* [Play](shifumi$help\u0026func=Play) gameIndex move (1=rock, 2=paper, 3=scissors)\n\n game | player1 | | player2 | | win \n --- | --- | --- | --- | --- | ---\n`\n\t// Output the 100 most recent games.\n\tmaxGames := 100\n\tfor n := int(id); n \u003e 0 \u0026\u0026 int(id)-n \u003c maxGames; n-- {\n\t\tv, ok := games.Get(seqid.ID(n).String())\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tg := v.(*game)\n\t\toutput += strconv.Itoa(n) + \" | \" +\n\t\t\tshortName(g.player1) + \" | \" + mov1[g.move1] + \" | \" +\n\t\t\tshortName(g.player2) + \" | \" + mov2[g.move2] + \" | \" +\n\t\t\twin[g.winner()+1] + \"\\n\"\n\t}\n\treturn output\n}\n\nfunc shortName(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user != nil {\n\t\treturn user.Name\n\t}\n\tif len(addr) \u003c 10 {\n\t\treturn string(addr)\n\t}\n\treturn string(addr)[:10] + \"...\"\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 methods are proxified\n// 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/users\"\n)\n\nvar (\n\tbanker *grc20.Banker\n\tadmin *ownable.Ownable\n\ttoken grc20.Token\n)\n\nfunc init() {\n\tadmin = ownable.NewWithAddress(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\") // @manfred\n\tbanker = grc20.NewBanker(\"Foo\", \"FOO\", 4)\n\tbanker.Mint(admin.Owner(), 1000000*10000) // @administrator (1M)\n\ttoken = banker.Token()\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\tcheckErr(token.Transfer(toAddr, amount))\n}\n\nfunc Approve(spender pusers.AddressOrName, amount uint64) {\n\tspenderAddr := users.Resolve(spender)\n\tcheckErr(token.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(token.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.PreviousRealm().Address()\n\tamount := uint64(1_000 * 10_000) // 1k\n\tcheckErr(banker.Mint(caller, amount))\n}\n\nfunc Mint(to pusers.AddressOrName, amount uint64) {\n\tadmin.AssertCallerIsOwner()\n\ttoAddr := users.Resolve(to)\n\tcheckErr(banker.Mint(toAddr, amount))\n}\n\nfunc Burn(from pusers.AddressOrName, amount uint64) {\n\tadmin.AssertCallerIsOwner()\n\tfromAddr := users.Resolve(from)\n\tcheckErr(banker.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 banker.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 := banker.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(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\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.TestSetOriginCaller(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(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\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\tstd.TestSetOriginCaller(users.Resolve(admin))\n\t{\n\t\ttests := []test{\n\t\t\t{\"Transfer(admin, 1)\", \"cannot send transfer to self\", func() { Transfer(admin, 1) }},\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":"foo721","path":"gno.land/r/demo/foo721","files":[{"name":"foo721.gno","body":"package foo721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/grc/grc721\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/r/demo/users\"\n\n\tpusers \"gno.land/p/demo/users\"\n)\n\nvar (\n\tadmin std.Address = \"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\"\n\tfoo = grc721.NewBasicNFT(\"FooNFT\", \"FNFT\")\n)\n\nfunc init() {\n\tmintNNFT(admin, 10) // @administrator (10)\n\tmintNNFT(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\", 5) // @hariom (5)\n}\n\nfunc mintNNFT(owner std.Address, n uint64) {\n\tcount := foo.TokenCount()\n\tfor i := count; i \u003c count+n; i++ {\n\t\ttid := grc721.TokenID(ufmt.Sprintf(\"%d\", i))\n\t\tfoo.Mint(owner, tid)\n\t}\n}\n\n// Getters\n\nfunc BalanceOf(user pusers.AddressOrName) uint64 {\n\tbalance, err := foo.BalanceOf(users.Resolve(user))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn balance\n}\n\nfunc OwnerOf(tid grc721.TokenID) std.Address {\n\towner, err := foo.OwnerOf(tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn owner\n}\n\nfunc IsApprovedForAll(owner, user pusers.AddressOrName) bool {\n\treturn foo.IsApprovedForAll(users.Resolve(owner), users.Resolve(user))\n}\n\nfunc GetApproved(tid grc721.TokenID) std.Address {\n\taddr, err := foo.GetApproved(tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn addr\n}\n\n// Setters\n\nfunc Approve(user pusers.AddressOrName, tid grc721.TokenID) {\n\terr := foo.Approve(users.Resolve(user), tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc SetApprovalForAll(user pusers.AddressOrName, approved bool) {\n\terr := foo.SetApprovalForAll(users.Resolve(user), approved)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc TransferFrom(from, to pusers.AddressOrName, tid grc721.TokenID) {\n\terr := foo.TransferFrom(users.Resolve(from), users.Resolve(to), tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// Admin\n\nfunc Mint(to pusers.AddressOrName, tid grc721.TokenID) {\n\tcaller := std.PreviousRealm().Address()\n\tassertIsAdmin(caller)\n\terr := foo.Mint(users.Resolve(to), tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc Burn(tid grc721.TokenID) {\n\tcaller := std.PreviousRealm().Address()\n\tassertIsAdmin(caller)\n\terr := foo.Burn(tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// Render\n\nfunc Render(path string) string {\n\tswitch {\n\tcase path == \"\":\n\t\treturn foo.RenderHome()\n\tdefault:\n\t\treturn \"404\\n\"\n\t}\n}\n\n// Util\n\nfunc assertIsAdmin(address std.Address) {\n\tif address != admin {\n\t\tpanic(\"restricted access\")\n\t}\n}\n"},{"name":"foo721_test.gno","body":"package foo721\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/grc/grc721\"\n\t\"gno.land/r/demo/users\"\n\n\tpusers \"gno.land/p/demo/users\"\n)\n\nfunc TestFoo721(t *testing.T) {\n\tadmin := pusers.AddressOrName(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\thariom := pusers.AddressOrName(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tfor i, tc := range []struct {\n\t\tname string\n\t\texpected interface{}\n\t\tfn func() interface{}\n\t}{\n\t\t{\"BalanceOf(admin)\", uint64(10), func() interface{} { return BalanceOf(admin) }},\n\t\t{\"BalanceOf(hariom)\", uint64(5), func() interface{} { return BalanceOf(hariom) }},\n\t\t{\"OwnerOf(0)\", users.Resolve(admin), func() interface{} { return OwnerOf(grc721.TokenID(\"0\")) }},\n\t\t{\"IsApprovedForAll(admin, hariom)\", false, func() interface{} { return IsApprovedForAll(admin, hariom) }},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := tc.fn()\n\t\t\tif tc.expected != got {\n\t\t\t\tt.Errorf(\"expected: %v got: %v\", tc.expected, got)\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":"dice_roller","path":"gno.land/r/demo/games/dice_roller","files":[{"name":"dice_roller.gno","body":"package dice_roller\n\nimport (\n\t\"errors\"\n\t\"math/rand\"\n\t\"sort\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/entropy\"\n\t\"gno.land/p/demo/seqid\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/r/demo/users\"\n)\n\ntype (\n\t// game represents a Dice Roller game between two players\n\tgame struct {\n\t\tplayer1, player2 std.Address\n\t\troll1, roll2 int\n\t}\n\n\t// player holds the information about each player including their stats\n\tplayer struct {\n\t\taddr std.Address\n\t\twins, losses, draws, points int\n\t}\n\n\t// leaderBoard is a slice of players, used to sort players by rank\n\tleaderBoard []player\n)\n\nconst (\n\t// Constants to represent game result outcomes\n\tongoing = iota\n\twin\n\tdraw\n\tloss\n)\n\nvar (\n\tgames avl.Tree // AVL tree for storing game states\n\tgameId seqid.ID // Sequence ID for games\n\n\tplayers avl.Tree // AVL tree for storing player data\n\n\tseed = uint64(entropy.New().Seed())\n\tr = rand.New(rand.NewPCG(seed, 0xdeadbeef))\n)\n\n// rollDice generates a random dice roll between 1 and 6\nfunc rollDice() int {\n\treturn r.IntN(6) + 1\n}\n\n// NewGame initializes a new game with the provided opponent's address\nfunc NewGame(addr std.Address) int {\n\tif !addr.IsValid() {\n\t\tpanic(\"invalid opponent's address\")\n\t}\n\n\tgames.Set(gameId.Next().String(), \u0026game{\n\t\tplayer1: std.PreviousRealm().Address(),\n\t\tplayer2: addr,\n\t})\n\n\treturn int(gameId)\n}\n\n// Play allows a player to roll the dice and updates the game state accordingly\nfunc Play(idx int) int {\n\tg, err := getGame(idx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\troll := rollDice() // Random the player's dice roll\n\n\t// Play the game and update the player's roll\n\tif err := g.play(std.PreviousRealm().Address(), roll); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// If both players have rolled, update the results and leaderboard\n\tif g.isFinished() {\n\t\t// If the player is playing against themselves, no points are awarded\n\t\tif g.player1 == g.player2 {\n\t\t\treturn roll\n\t\t}\n\n\t\tplayer1 := getPlayer(g.player1)\n\t\tplayer2 := getPlayer(g.player2)\n\n\t\tif g.roll1 \u003e g.roll2 {\n\t\t\tplayer1.updateStats(win)\n\t\t\tplayer2.updateStats(loss)\n\t\t} else if g.roll2 \u003e g.roll1 {\n\t\t\tplayer2.updateStats(win)\n\t\t\tplayer1.updateStats(loss)\n\t\t} else {\n\t\t\tplayer1.updateStats(draw)\n\t\t\tplayer2.updateStats(draw)\n\t\t}\n\t}\n\n\treturn roll\n}\n\n// play processes a player's roll and updates their score\nfunc (g *game) play(player std.Address, roll int) error {\n\tif player != g.player1 \u0026\u0026 player != g.player2 {\n\t\treturn errors.New(\"invalid player\")\n\t}\n\n\tif g.isFinished() {\n\t\treturn errors.New(\"game over\")\n\t}\n\n\tif player == g.player1 \u0026\u0026 g.roll1 == 0 {\n\t\tg.roll1 = roll\n\t\treturn nil\n\t}\n\n\tif player == g.player2 \u0026\u0026 g.roll2 == 0 {\n\t\tg.roll2 = roll\n\t\treturn nil\n\t}\n\n\treturn errors.New(\"already played\")\n}\n\n// isFinished checks if the game has ended\nfunc (g *game) isFinished() bool {\n\treturn g.roll1 != 0 \u0026\u0026 g.roll2 != 0\n}\n\n// checkResult returns the game status as a formatted string\nfunc (g *game) status() string {\n\tif !g.isFinished() {\n\t\treturn resultIcon(ongoing) + \" Game still in progress\"\n\t}\n\n\tif g.roll1 \u003e g.roll2 {\n\t\treturn resultIcon(win) + \" Player1 Wins !\"\n\t} else if g.roll2 \u003e g.roll1 {\n\t\treturn resultIcon(win) + \" Player2 Wins !\"\n\t} else {\n\t\treturn resultIcon(draw) + \" It's a Draw !\"\n\t}\n}\n\n// Render provides a summary of the current state of games and leader board\nfunc Render(path string) string {\n\tvar sb strings.Builder\n\n\tsb.WriteString(`# 🎲 **Dice Roller Game**\n\nWelcome to Dice Roller! Challenge your friends to a simple yet exciting dice rolling game. Roll the dice and see who gets the highest score !\n\n---\n\n## **How to Play**:\n1. **Create a game**: Challenge an opponent using [NewGame](./dice_roller$help\u0026func=NewGame)\n2. **Roll the dice**: Play your turn by rolling a dice using [Play](./dice_roller$help\u0026func=Play)\n\n---\n\n## **Scoring Rules**:\n- **Win** 🏆: +3 points\n- **Draw** 🤝: +1 point each\n- **Lose** ❌: No points\n- **Playing against yourself**: No points or stats changes for you\n\n---\n\n## **Recent Games**:\nBelow are the results from the most recent games. Up to 10 recent games are displayed\n\n| Game | Player 1 | 🎲 Roll 1 | Player 2 | 🎲 Roll 2 | 🏆 Winner |\n|------|----------|-----------|----------|-----------|-----------|\n`)\n\n\tmaxGames := 10\n\tfor n := int(gameId); n \u003e 0 \u0026\u0026 int(gameId)-n \u003c maxGames; n-- {\n\t\tg, err := getGame(n)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tsb.WriteString(strconv.Itoa(n) + \" | \" +\n\t\t\t\"\u003cspan title=\\\"\" + string(g.player1) + \"\\\"\u003e\" + shortName(g.player1) + \"\u003c/span\u003e\" + \" | \" + diceIcon(g.roll1) + \" | \" +\n\t\t\t\"\u003cspan title=\\\"\" + string(g.player2) + \"\\\"\u003e\" + shortName(g.player2) + \"\u003c/span\u003e\" + \" | \" + diceIcon(g.roll2) + \" | \" +\n\t\t\tg.status() + \"\\n\")\n\t}\n\n\tsb.WriteString(`\n---\n\n## **Leaderboard**:\nThe top players are ranked by performance. Games played against oneself are not counted in the leaderboard\n\n| Rank | Player | Wins | Losses | Draws | Points |\n|------|-----------------------|------|--------|-------|--------|\n`)\n\n\tfor i, player := range getLeaderBoard() {\n\t\tsb.WriteString(ufmt.Sprintf(\"| %s | \u003cspan title=\\\"\"+string(player.addr)+\"\\\"\u003e**%s**\u003c/span\u003e | %d | %d | %d | %d |\\n\",\n\t\t\trankIcon(i+1),\n\t\t\tshortName(player.addr),\n\t\t\tplayer.wins,\n\t\t\tplayer.losses,\n\t\t\tplayer.draws,\n\t\t\tplayer.points,\n\t\t))\n\t}\n\n\tsb.WriteString(\"\\n---\\n**Good luck and have fun !** 🎉\")\n\treturn sb.String()\n}\n\n// shortName returns a shortened name for the given address\nfunc shortName(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user != nil {\n\t\treturn user.Name\n\t}\n\tif len(addr) \u003c 10 {\n\t\treturn string(addr)\n\t}\n\treturn string(addr)[:10] + \"...\"\n}\n\n// getGame retrieves the game state by its ID\nfunc getGame(idx int) (*game, error) {\n\tv, ok := games.Get(seqid.ID(idx).String())\n\tif !ok {\n\t\treturn nil, errors.New(\"game not found\")\n\t}\n\treturn v.(*game), nil\n}\n\n// updateResult updates the player's stats and points based on the game outcome\nfunc (p *player) updateStats(result int) {\n\tswitch result {\n\tcase win:\n\t\tp.wins++\n\t\tp.points += 3\n\tcase loss:\n\t\tp.losses++\n\tcase draw:\n\t\tp.draws++\n\t\tp.points++\n\t}\n}\n\n// getPlayer retrieves a player or initializes a new one if they don't exist\nfunc getPlayer(addr std.Address) *player {\n\tv, ok := players.Get(addr.String())\n\tif !ok {\n\t\tplayer := \u0026player{\n\t\t\taddr: addr,\n\t\t}\n\t\tplayers.Set(addr.String(), player)\n\t\treturn player\n\t}\n\n\treturn v.(*player)\n}\n\n// getLeaderBoard generates a leaderboard sorted by points\nfunc getLeaderBoard() leaderBoard {\n\tboard := leaderBoard{}\n\tplayers.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tplayer := value.(*player)\n\t\tboard = append(board, *player)\n\t\treturn false\n\t})\n\n\tsort.Sort(board)\n\n\treturn board\n}\n\n// Methods for sorting the leaderboard\nfunc (r leaderBoard) Len() int {\n\treturn len(r)\n}\n\nfunc (r leaderBoard) Less(i, j int) bool {\n\tif r[i].points != r[j].points {\n\t\treturn r[i].points \u003e r[j].points\n\t}\n\n\tif r[i].wins != r[j].wins {\n\t\treturn r[i].wins \u003e r[j].wins\n\t}\n\n\tif r[i].draws != r[j].draws {\n\t\treturn r[i].draws \u003e r[j].draws\n\t}\n\n\treturn false\n}\n\nfunc (r leaderBoard) Swap(i, j int) {\n\tr[i], r[j] = r[j], r[i]\n}\n"},{"name":"dice_roller_test.gno","body":"package dice_roller\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/seqid\"\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nvar (\n\tplayer1 = testutils.TestAddress(\"alice\")\n\tplayer2 = testutils.TestAddress(\"bob\")\n\tunknownPlayer = testutils.TestAddress(\"unknown\")\n)\n\n// resetGameState resets the game state for testing\nfunc resetGameState() {\n\tgames = avl.Tree{}\n\tgameId = seqid.ID(0)\n\tplayers = avl.Tree{}\n}\n\n// TestNewGame tests the initialization of a new game\nfunc TestNewGame(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOriginCaller(player1)\n\tgameID := NewGame(player2)\n\n\t// Verify that the game has been correctly initialized\n\tg, err := getGame(gameID)\n\turequire.NoError(t, err)\n\turequire.Equal(t, player1.String(), g.player1.String())\n\turequire.Equal(t, player2.String(), g.player2.String())\n\turequire.Equal(t, 0, g.roll1)\n\turequire.Equal(t, 0, g.roll2)\n}\n\n// TestPlay tests the dice rolling functionality for both players\nfunc TestPlay(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOriginCaller(player1)\n\tgameID := NewGame(player2)\n\n\tg, err := getGame(gameID)\n\turequire.NoError(t, err)\n\n\t// Simulate rolling dice for player 1\n\troll1 := Play(gameID)\n\n\t// Verify player 1's roll\n\turequire.NotEqual(t, 0, g.roll1)\n\turequire.Equal(t, g.roll1, roll1)\n\turequire.Equal(t, 0, g.roll2) // Player 2 hasn't rolled yet\n\n\t// Simulate rolling dice for player 2\n\tstd.TestSetOriginCaller(player2)\n\troll2 := Play(gameID)\n\n\t// Verify player 2's roll\n\turequire.NotEqual(t, 0, g.roll2)\n\turequire.Equal(t, g.roll1, roll1)\n\turequire.Equal(t, g.roll2, roll2)\n}\n\n// TestPlayAgainstSelf tests the scenario where a player plays against themselves\nfunc TestPlayAgainstSelf(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOriginCaller(player1)\n\tgameID := NewGame(player1)\n\n\t// Simulate rolling dice twice by the same player\n\troll1 := Play(gameID)\n\troll2 := Play(gameID)\n\n\tg, err := getGame(gameID)\n\turequire.NoError(t, err)\n\turequire.Equal(t, g.roll1, roll1)\n\turequire.Equal(t, g.roll2, roll2)\n}\n\n// TestPlayInvalidPlayer tests the scenario where an invalid player tries to play\nfunc TestPlayInvalidPlayer(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOriginCaller(player1)\n\tgameID := NewGame(player1)\n\n\t// Attempt to play as an invalid player\n\tstd.TestSetOriginCaller(unknownPlayer)\n\turequire.PanicsWithMessage(t, \"invalid player\", func() {\n\t\tPlay(gameID)\n\t})\n}\n\n// TestPlayAlreadyPlayed tests the scenario where a player tries to play again after already playing\nfunc TestPlayAlreadyPlayed(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOriginCaller(player1)\n\tgameID := NewGame(player2)\n\n\t// Player 1 rolls\n\tPlay(gameID)\n\n\t// Player 1 tries to roll again\n\turequire.PanicsWithMessage(t, \"already played\", func() {\n\t\tPlay(gameID)\n\t})\n}\n\n// TestPlayBeyondGameEnd tests that playing after both players have finished their rolls fails\nfunc TestPlayBeyondGameEnd(t *testing.T) {\n\tresetGameState()\n\n\tstd.TestSetOriginCaller(player1)\n\tgameID := NewGame(player2)\n\n\t// Play for both players\n\tstd.TestSetOriginCaller(player1)\n\tPlay(gameID)\n\tstd.TestSetOriginCaller(player2)\n\tPlay(gameID)\n\n\t// Check if the game is over\n\tg, err := getGame(gameID)\n\turequire.NoError(t, err)\n\n\t// Attempt to play more should fail\n\tstd.TestSetOriginCaller(player1)\n\turequire.PanicsWithMessage(t, \"game over\", func() {\n\t\tPlay(gameID)\n\t})\n}\n"},{"name":"icon.gno","body":"package dice_roller\n\nimport (\n\t\"strconv\"\n)\n\n// diceIcon returns an icon of the dice roll\nfunc diceIcon(roll int) string {\n\tswitch roll {\n\tcase 1:\n\t\treturn \"🎲1\"\n\tcase 2:\n\t\treturn \"🎲2\"\n\tcase 3:\n\t\treturn \"🎲3\"\n\tcase 4:\n\t\treturn \"🎲4\"\n\tcase 5:\n\t\treturn \"🎲5\"\n\tcase 6:\n\t\treturn \"🎲6\"\n\tdefault:\n\t\treturn \"❓\"\n\t}\n}\n\n// resultIcon returns the icon representing the result of a game\nfunc resultIcon(result int) string {\n\tswitch result {\n\tcase ongoing:\n\t\treturn \"🔄\"\n\tcase win:\n\t\treturn \"🏆\"\n\tcase loss:\n\t\treturn \"❌\"\n\tcase draw:\n\t\treturn \"🤝\"\n\tdefault:\n\t\treturn \"❓\"\n\t}\n}\n\n// rankIcon returns the icon for a player's rank\nfunc rankIcon(rank int) string {\n\tswitch rank {\n\tcase 1:\n\t\treturn \"🥇\"\n\tcase 2:\n\t\treturn \"🥈\"\n\tcase 3:\n\t\treturn \"🥉\"\n\tdefault:\n\t\treturn strconv.Itoa(rank)\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":"shifumi","path":"gno.land/r/demo/games/shifumi","files":[{"name":"shifumi.gno","body":"package shifumi\n\nimport (\n\t\"errors\"\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/seqid\"\n\n\t\"gno.land/r/demo/users\"\n)\n\nconst (\n\tempty = iota\n\trock\n\tpaper\n\tscissors\n\tlast\n)\n\ntype game struct {\n\tplayer1, player2 std.Address // shifumi is a 2 players game\n\tmove1, move2 int // can be empty, rock, paper, or scissors\n}\n\nvar games avl.Tree\nvar id seqid.ID\n\nfunc (g *game) play(player std.Address, move int) error {\n\tif !(move \u003e empty \u0026\u0026 move \u003c last) {\n\t\treturn errors.New(\"invalid move\")\n\t}\n\tif player != g.player1 \u0026\u0026 player != g.player2 {\n\t\treturn errors.New(\"invalid player\")\n\t}\n\tif player == g.player1 \u0026\u0026 g.move1 == empty {\n\t\tg.move1 = move\n\t\treturn nil\n\t}\n\tif player == g.player2 \u0026\u0026 g.move2 == empty {\n\t\tg.move2 = move\n\t\treturn nil\n\t}\n\treturn errors.New(\"already played\")\n}\n\nfunc (g *game) winner() int {\n\tif g.move1 == empty || g.move2 == empty {\n\t\treturn -1\n\t}\n\tif g.move1 == g.move2 {\n\t\treturn 0\n\t}\n\tif g.move1 == rock \u0026\u0026 g.move2 == scissors ||\n\t\tg.move1 == paper \u0026\u0026 g.move2 == rock ||\n\t\tg.move1 == scissors \u0026\u0026 g.move2 == paper {\n\t\treturn 1\n\t}\n\treturn 2\n}\n\n// NewGame creates a new game where player1 is the caller and player2 the argument.\n// A new game index is returned.\nfunc NewGame(player std.Address) int {\n\tgames.Set(id.Next().String(), \u0026game{player1: std.PreviousRealm().Address(), player2: player})\n\treturn int(id)\n}\n\n// Play executes a move for the game at index idx, where move can be:\n// 1 (rock), 2 (paper), 3 (scissors).\nfunc Play(idx, move int) {\n\tv, ok := games.Get(seqid.ID(idx).String())\n\tif !ok {\n\t\tpanic(\"game not found\")\n\t}\n\tif err := v.(*game).play(std.PreviousRealm().Address(), move); err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc Render(path string) string {\n\tmov1 := []string{\"\", \" 🤜 \", \" 🫱 \", \" 👉 \"}\n\tmov2 := []string{\"\", \" 🤛 \", \" 🫲 \", \" 👈 \"}\n\twin := []string{\"pending\", \"draw\", \"player1\", \"player2\"}\n\n\toutput := `# 👊 ✋ ✌️ Shifumi\nActions:\n* [NewGame](shifumi$help\u0026func=NewGame) opponentAddress\n* [Play](shifumi$help\u0026func=Play) gameIndex move (1=rock, 2=paper, 3=scissors)\n\n game | player1 | | player2 | | win \n --- | --- | --- | --- | --- | ---\n`\n\t// Output the 100 most recent games.\n\tmaxGames := 100\n\tfor n := int(id); n \u003e 0 \u0026\u0026 int(id)-n \u003c maxGames; n-- {\n\t\tv, ok := games.Get(seqid.ID(n).String())\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tg := v.(*game)\n\t\toutput += strconv.Itoa(n) + \" | \" +\n\t\t\tshortName(g.player1) + \" | \" + mov1[g.move1] + \" | \" +\n\t\t\tshortName(g.player2) + \" | \" + mov2[g.move2] + \" | \" +\n\t\t\twin[g.winner()+1] + \"\\n\"\n\t}\n\treturn output\n}\n\nfunc shortName(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user != nil {\n\t\treturn user.Name\n\t}\n\tif len(addr) \u003c 10 {\n\t\treturn string(addr)\n\t}\n\treturn string(addr)[:10] + \"...\"\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":"groups","path":"gno.land/r/demo/groups","files":[{"name":"README.md","body":"### - test package\n\n ./build/gno test examples/gno.land/r/demo/groups/\n\n### - add pkg\n\n ./build/gnokey maketx addpkg -pkgdir \"examples/gno.land/r/demo/groups\" -deposit 100000000ugnot -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid dev -remote 0.0.0.0:26657 -pkgpath \"gno.land/r/demo/groups\" test1 \n\n### - create group\n\n ./build/gnokey maketx call -func \"CreateGroup\" -args \"dao_trinity_ngo\" -gas-fee \"1000000ugnot\" -gas-wanted 4000000 -broadcast -chainid dev -remote 0.0.0.0:26657 -pkgpath \"gno.land/r/demo/groups\" test1 \n\n### - add member\n\n ./build/gnokey maketx call -func \"AddMember\" -args \"1\" -args \"g1hd3gwzevxlqmd3jsf64mpfczag8a8e5j2wdn3c\" -args 12 -args \"i am new user\" -gas-fee \"1000000ugnot\" -gas-wanted \"4000000\" -broadcast -chainid dev -remote 0.0.0.0:26657 -pkgpath \"gno.land/r/demo/groups\" test1\n\n### - delete member\n\n ./build/gnokey maketx call -func \"DeleteMember\" -args \"1\" -args \"0\" -gas-fee \"1000000ugnot\" -gas-wanted \"4000000\" -broadcast -chainid dev -remote 0.0.0.0:26657 -pkgpath \"gno.land/r/demo/groups\" test1\n\n### - delete group\n\n ./build/gnokey maketx call -func \"DeleteGroup\" -args \"1\" -gas-fee \"1000000ugnot\" -gas-wanted \"4000000\" -broadcast -chainid dev -remote 0.0.0.0:26657 -pkgpath \"gno.land/r/demo/groups\" test1\n\n"},{"name":"group.gno","body":"package groups\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\ntype GroupID uint64\n\nfunc (gid GroupID) String() string {\n\treturn strconv.Itoa(int(gid))\n}\n\ntype Group struct {\n\tid GroupID\n\turl string\n\tname string\n\tlastMemberID MemberID\n\tmembers avl.Tree\n\tcreator std.Address\n\tcreatedAt time.Time\n}\n\nfunc newGroup(url string, name string, creator std.Address) *Group {\n\tif !reName.MatchString(name) {\n\t\tpanic(\"invalid name: \" + name)\n\t}\n\tif gGroupsByName.Has(name) {\n\t\tpanic(\"Group with such name already exists\")\n\t}\n\treturn \u0026Group{\n\t\tid: incGetGroupID(),\n\t\turl: url,\n\t\tname: name,\n\t\tcreator: creator,\n\t\tmembers: avl.Tree{},\n\t\tcreatedAt: time.Now(),\n\t}\n}\n\nfunc (group *Group) newMember(id MemberID, address std.Address, weight int, metadata string) *Member {\n\tif group.members.Has(address.String()) {\n\t\tpanic(\"this member for this group already exists\")\n\t}\n\treturn \u0026Member{\n\t\tid: id,\n\t\taddress: address,\n\t\tweight: weight,\n\t\tmetadata: metadata,\n\t\tcreatedAt: time.Now(),\n\t}\n}\n\nfunc (group *Group) HasPermission(addr std.Address, perm Permission) bool {\n\tif group.creator != addr {\n\t\treturn false\n\t}\n\treturn isValidPermission(perm)\n}\n\nfunc (group *Group) RenderGroup() string {\n\tstr := \"Group ID: \" + groupIDKey(group.id) + \"\\n\\n\" +\n\t\t\"Group Name: \" + group.name + \"\\n\\n\" +\n\t\t\"Group Creator: \" + usernameOf(group.creator) + \"\\n\\n\" +\n\t\t\"Group createdAt: \" + group.createdAt.String() + \"\\n\\n\" +\n\t\t\"Group Last MemberID: \" + memberIDKey(group.lastMemberID) + \"\\n\\n\"\n\n\tstr += \"Group Members: \\n\\n\"\n\tgroup.members.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tmember := value.(*Member)\n\t\tstr += member.getMemberStr()\n\t\treturn false\n\t})\n\treturn str\n}\n\nfunc (group *Group) deleteGroup() {\n\tgidkey := groupIDKey(group.id)\n\t_, gGroupsRemoved := gGroups.Remove(gidkey)\n\tif !gGroupsRemoved {\n\t\tpanic(\"group does not exist with id \" + group.id.String())\n\t}\n\tgGroupsByName.Remove(group.name)\n}\n\nfunc (group *Group) deleteMember(mid MemberID) {\n\tgidkey := groupIDKey(group.id)\n\tif !gGroups.Has(gidkey) {\n\t\tpanic(\"group does not exist with id \" + group.id.String())\n\t}\n\n\tg := getGroup(group.id)\n\tmidkey := memberIDKey(mid)\n\tg.members.Remove(midkey)\n}\n"},{"name":"groups.gno","body":"package groups\n\nimport (\n\t\"regexp\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n//----------------------------------------\n// Realm (package) state\n\nvar (\n\tgGroups avl.Tree // id -\u003e *Group\n\tgGroupsCtr int // increments Group.id\n\tgGroupsByName avl.Tree // name -\u003e *Group\n)\n\n//----------------------------------------\n// Constants\n\nvar reName = regexp.MustCompile(`^[a-z]+[_a-z0-9]{2,29}$`)\n"},{"name":"member.gno","body":"package groups\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n)\n\ntype MemberID uint64\n\ntype Member struct {\n\tid MemberID\n\taddress std.Address\n\tweight int\n\tmetadata string\n\tcreatedAt time.Time\n}\n\nfunc (mid MemberID) String() string {\n\treturn strconv.Itoa(int(mid))\n}\n\nfunc (member *Member) getMemberStr() string {\n\tmemberDataStr := \"\"\n\tmemberDataStr += \"\\t\\t\\t[\" + memberIDKey(member.id) + \", \" + member.address.String() + \", \" + strconv.Itoa(member.weight) + \", \" + member.metadata + \", \" + member.createdAt.String() + \"],\\n\\n\"\n\treturn memberDataStr\n}\n"},{"name":"misc.gno","body":"package groups\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/r/demo/users\"\n)\n\n//----------------------------------------\n// private utility methods\n// XXX ensure these cannot be called from public.\n\nfunc getGroup(gid GroupID) *Group {\n\tgidkey := groupIDKey(gid)\n\tgroup_, exists := gGroups.Get(gidkey)\n\tif !exists {\n\t\tpanic(\"group id (\" + gid.String() + \") does not exists\")\n\t}\n\tgroup := group_.(*Group)\n\treturn group\n}\n\nfunc incGetGroupID() GroupID {\n\tgGroupsCtr++\n\treturn GroupID(gGroupsCtr)\n}\n\nfunc padLeft(str string, length int) string {\n\tif len(str) \u003e= length {\n\t\treturn str\n\t}\n\treturn strings.Repeat(\" \", length-len(str)) + str\n}\n\nfunc padZero(u64 uint64, length int) string {\n\tstr := strconv.Itoa(int(u64))\n\tif len(str) \u003e= length {\n\t\treturn str\n\t}\n\treturn strings.Repeat(\"0\", length-len(str)) + str\n}\n\nfunc groupIDKey(gid GroupID) string {\n\treturn padZero(uint64(gid), 10)\n}\n\nfunc memberIDKey(mid MemberID) string {\n\treturn padZero(uint64(mid), 10)\n}\n\nfunc indentBody(indent string, body string) string {\n\tlines := strings.Split(body, \"\\n\")\n\tres := \"\"\n\tfor i, line := range lines {\n\t\tif i \u003e 0 {\n\t\t\tres += \"\\n\"\n\t\t}\n\t\tres += indent + line\n\t}\n\treturn res\n}\n\n// NOTE: length must be greater than 3.\nfunc summaryOf(str string, length int) string {\n\tlines := strings.SplitN(str, \"\\n\", 2)\n\tline := lines[0]\n\tif len(line) \u003e length {\n\t\tline = line[:(length-3)] + \"...\"\n\t} else if len(lines) \u003e 1 {\n\t\t// len(line) \u003c= 80\n\t\tline = line + \"...\"\n\t}\n\treturn line\n}\n\nfunc displayAddressMD(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user == nil {\n\t\treturn \"[\" + addr.String() + \"](/r/demo/users:\" + addr.String() + \")\"\n\t}\n\treturn \"[@\" + user.Name + \"](/r/demo/users:\" + user.Name + \")\"\n}\n\nfunc usernameOf(addr std.Address) string {\n\tuser := users.GetUserByAddress(addr)\n\tif user == nil {\n\t\tpanic(\"user not found\")\n\t}\n\treturn user.Name\n}\n\nfunc isValidPermission(perm Permission) bool {\n\treturn perm == EditPermission || perm == DeletePermission\n}\n"},{"name":"public.gno","body":"package groups\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/users\"\n)\n\n//----------------------------------------\n// Public facing functions\n\nfunc GetGroupIDFromName(name string) (GroupID, bool) {\n\tgroupI, exists := gGroupsByName.Get(name)\n\tif !exists {\n\t\treturn 0, false\n\t}\n\treturn groupI.(*Group).id, true\n}\n\nfunc CreateGroup(name string) GroupID {\n\tstd.AssertOriginCall()\n\tcaller := std.OriginCaller()\n\tusernameOf(caller)\n\turl := \"/r/demo/groups:\" + name\n\tgroup := newGroup(url, name, caller)\n\tgidkey := groupIDKey(group.id)\n\tgGroups.Set(gidkey, group)\n\tgGroupsByName.Set(name, group)\n\treturn group.id\n}\n\nfunc AddMember(gid GroupID, address string, weight int, metadata string) MemberID {\n\tstd.AssertOriginCall()\n\tcaller := std.OriginCaller()\n\tusernameOf(caller)\n\tgroup := getGroup(gid)\n\tif !group.HasPermission(caller, EditPermission) {\n\t\tpanic(\"unauthorized to edit group\")\n\t}\n\tuser := users.GetUserByAddress(std.Address(address))\n\tif user == nil {\n\t\tpanic(\"unknown address \" + address)\n\t}\n\tmid := group.lastMemberID\n\tmember := group.newMember(mid, std.Address(address), weight, metadata)\n\tmidkey := memberIDKey(mid)\n\tgroup.members.Set(midkey, member)\n\tmid++\n\tgroup.lastMemberID = mid\n\treturn member.id\n}\n\nfunc DeleteGroup(gid GroupID) {\n\tstd.AssertOriginCall()\n\tcaller := std.OriginCaller()\n\tgroup := getGroup(gid)\n\tif !group.HasPermission(caller, DeletePermission) {\n\t\tpanic(\"unauthorized to delete group\")\n\t}\n\tgroup.deleteGroup()\n}\n\nfunc DeleteMember(gid GroupID, mid MemberID) {\n\tstd.AssertOriginCall()\n\tcaller := std.OriginCaller()\n\tgroup := getGroup(gid)\n\tif !group.HasPermission(caller, DeletePermission) {\n\t\tpanic(\"unauthorized to delete member\")\n\t}\n\tgroup.deleteMember(mid)\n}\n"},{"name":"render.gno","body":"package groups\n\nimport (\n\t\"strings\"\n)\n\n//----------------------------------------\n// Render functions\n\nfunc RenderGroup(gid GroupID) string {\n\tgroup := getGroup(gid)\n\tif group == nil {\n\t\treturn \"missing Group\"\n\t}\n\treturn group.RenderGroup()\n}\n\nfunc Render(path string) string {\n\tif path == \"\" {\n\t\tstr := \"List of all Groups:\\n\\n\"\n\t\tgGroups.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tgroup := value.(*Group)\n\t\t\tstr += \" * [\" + group.name + \"](\" + group.url + \")\\n\"\n\t\t\treturn false\n\t\t})\n\t\treturn str\n\t}\n\tparts := strings.Split(path, \"/\")\n\tif len(parts) == 1 {\n\t\t// /r/demo/groups:Group_NAME\n\t\tname := parts[0]\n\t\tgroupI, exists := gGroupsByName.Get(name)\n\t\tif !exists {\n\t\t\treturn \"Group does not exist: \" + name\n\t\t}\n\t\treturn groupI.(*Group).RenderGroup()\n\t} else {\n\t\treturn \"unrecognized path \" + path\n\t}\n}\n"},{"name":"role.gno","body":"package groups\n\ntype Permission string\n\nconst (\n\tDeletePermission Permission = \"role:delete\"\n\tEditPermission Permission = \"role:edit\"\n)\n"},{"name":"z_0_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\nimport (\n\t\"gno.land/r/demo/groups\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// user not found\n"},{"name":"z_0_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// payment must not be less than 20000000\n"},{"name":"z_0_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tprintln(groups.Render(\"\"))\n}\n\n// Output:\n// 1\n// List of all Groups:\n//\n// * [test_group](/r/demo/groups:test_group)\n"},{"name":"z_1_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser0\", \"my profile 1\")\n\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"gnouser1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tusers.Register(caller, \"gnouser1\", \"my other profile 1\")\n\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest2 := testutils.TestAddress(\"gnouser2\")\n\tusers.Invite(test2.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test2)\n\tusers.Register(caller, \"gnouser2\", \"my other profile 2\")\n\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest3 := testutils.TestAddress(\"gnouser3\")\n\tusers.Invite(test3.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test3)\n\tusers.Register(caller, \"gnouser3\", \"my other profile 3\")\n\n\tstd.TestSetOriginCaller(caller)\n\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\n\tgroups.AddMember(gid, test3.String(), 32, \"i am from UAE\")\n\tprintln(groups.Render(\"test_group\"))\n}\n\n// Output:\n// 1\n// Group ID: 0000000001\n//\n// Group Name: test_group\n//\n// Group Creator: gnouser0\n//\n// Group createdAt: 2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001\n//\n// Group Last MemberID: 0000000001\n//\n// Group Members:\n//\n// \t\t\t[0000000000, g1vahx7atnv4erxh6lta047h6lta047h6ll85gpy, 32, i am from UAE, 2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001],\n"},{"name":"z_1_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tgroups.AddMember(2, \"g1vahx7atnv4erxh6lta047h6lta047h6ll85gpy\", 55, \"metadata3\")\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// group id (2) does not exists\n"},{"name":"z_1_c_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/groups\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\n\t// add member via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOriginCaller(test2)\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\n\tgroups.AddMember(gid, test2.String(), 42, \"metadata3\")\n}\n\n// Error:\n// user not found\n"},{"name":"z_2_a_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nconst admin = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc main() {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(\"\", \"gnouser0\", \"my profile 1\")\n\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest1 := testutils.TestAddress(\"gnouser1\")\n\tusers.Invite(test1.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test1)\n\tusers.Register(caller, \"gnouser1\", \"my other profile 1\")\n\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest2 := testutils.TestAddress(\"gnouser2\")\n\tusers.Invite(test2.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test2)\n\tusers.Register(caller, \"gnouser2\", \"my other profile 2\")\n\n\tstd.TestSetOriginCaller(admin)\n\tusers.GrantInvites(caller.String() + \":1\")\n\t// switch back to caller\n\tstd.TestSetOriginCaller(caller)\n\t// invite another addr\n\ttest3 := testutils.TestAddress(\"gnouser3\")\n\tusers.Invite(test3.String())\n\t// switch to test1\n\tstd.TestSetOriginCaller(test3)\n\tusers.Register(caller, \"gnouser3\", \"my other profile 3\")\n\n\tstd.TestSetOriginCaller(caller)\n\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\n\tgroups.AddMember(gid, test2.String(), 42, \"metadata3\")\n\n\tgroups.DeleteMember(gid, 0)\n\tprintln(groups.RenderGroup(gid))\n}\n\n// Output:\n// 1\n// Group ID: 0000000001\n//\n// Group Name: test_group\n//\n// Group Creator: gnouser0\n//\n// Group createdAt: 2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001\n//\n// Group Last MemberID: 0000000001\n//\n// Group Members:\n"},{"name":"z_2_b_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tgroups.DeleteMember(2, 0)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// group id (2) does not exists\n"},{"name":"z_2_d_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\n\t// delete member via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOriginCaller(test2)\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\n\tgroups.DeleteMember(gid, 0)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// unauthorized to delete member\n"},{"name":"z_2_e_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tgroups.DeleteGroup(gid)\n\tprintln(groups.Render(\"\"))\n}\n\n// Output:\n// 1\n// List of all Groups:\n"},{"name":"z_2_f_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\tgroups.DeleteGroup(20)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// group id (20) does not exists\n"},{"name":"z_2_g_filetest.gno","body":"// PKGPATH: gno.land/r/demo/groups_test\npackage groups_test\n\n// SEND: 200000000ugnot\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/groups\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar gid groups.GroupID\n\nfunc main() {\n\tusers.Register(\"\", \"gnouser\", \"my profile\")\n\tgid = groups.CreateGroup(\"test_group\")\n\tprintln(gid)\n\n\t// delete group via anon user\n\ttest2 := testutils.TestAddress(\"test2\")\n\tstd.TestSetOriginCaller(test2)\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 9000000}}, nil)\n\n\tgroups.DeleteGroup(gid)\n\tprintln(groups.Render(\"\"))\n}\n\n// Error:\n// unauthorized to delete group\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":"keystore","path":"gno.land/r/demo/keystore","files":[{"name":"keystore.gno","body":"package keystore\n\nimport (\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar data avl.Tree\n\nconst (\n\tBaseURL = \"/r/demo/keystore\"\n\tStatusOK = \"ok\"\n\tStatusNoUser = \"user not found\"\n\tStatusNotFound = \"key not found\"\n\tStatusNoWriteAccess = \"no write access\"\n\tStatusCouldNotExecute = \"could not execute\"\n\tStatusNoDatabases = \"no databases\"\n)\n\nfunc init() {\n\tdata = avl.Tree{} // user -\u003e avl.Tree\n}\n\n// KeyStore stores the owner-specific avl.Tree\ntype KeyStore struct {\n\tOwner std.Address\n\tData avl.Tree\n}\n\n// Set will set a value to a key\n// requires write-access (original caller must be caller)\nfunc Set(k, v string) string {\n\torigOwner := std.OriginCaller()\n\treturn set(origOwner.String(), k, v)\n}\n\n// set (private) will set a key to value\n// requires write-access (original caller must be caller)\nfunc set(owner, k, v string) string {\n\torigOwner := std.OriginCaller()\n\tif origOwner.String() != owner {\n\t\treturn StatusNoWriteAccess\n\t}\n\tvar keystore *KeyStore\n\tkeystoreInterface, exists := data.Get(owner)\n\tif !exists {\n\t\tkeystore = \u0026KeyStore{\n\t\t\tOwner: origOwner,\n\t\t\tData: avl.Tree{},\n\t\t}\n\t\tdata.Set(owner, keystore)\n\t} else {\n\t\tkeystore = keystoreInterface.(*KeyStore)\n\t}\n\tkeystore.Data.Set(k, v)\n\treturn StatusOK\n}\n\n// Remove removes a key\n// requires write-access (original owner must be caller)\nfunc Remove(k string) string {\n\torigOwner := std.OriginCaller()\n\treturn remove(origOwner.String(), k)\n}\n\n// remove (private) removes a key\n// requires write-access (original owner must be caller)\nfunc remove(owner, k string) string {\n\torigOwner := std.OriginCaller()\n\tif origOwner.String() != owner {\n\t\treturn StatusNoWriteAccess\n\t}\n\tvar keystore *KeyStore\n\tkeystoreInterface, exists := data.Get(owner)\n\tif !exists {\n\t\tkeystore = \u0026KeyStore{\n\t\t\tOwner: origOwner,\n\t\t\tData: avl.Tree{},\n\t\t}\n\t\tdata.Set(owner, keystore)\n\t} else {\n\t\tkeystore = keystoreInterface.(*KeyStore)\n\t}\n\t_, removed := keystore.Data.Remove(k)\n\tif !removed {\n\t\treturn StatusCouldNotExecute\n\t}\n\treturn StatusOK\n}\n\n// Get returns a value for a key\n// read-only\nfunc Get(k string) string {\n\torigOwner := std.OriginCaller()\n\treturn remove(origOwner.String(), k)\n}\n\n// get (private) returns a value for a key\n// read-only\nfunc get(owner, k string) string {\n\tkeystoreInterface, exists := data.Get(owner)\n\tif !exists {\n\t\treturn StatusNoUser\n\t}\n\tkeystore := keystoreInterface.(*KeyStore)\n\tval, found := keystore.Data.Get(k)\n\tif !found {\n\t\treturn StatusNotFound\n\t}\n\treturn val.(string)\n}\n\n// Size returns size of database\n// read-only\nfunc Size() string {\n\torigOwner := std.OriginCaller()\n\treturn size(origOwner.String())\n}\n\nfunc size(owner string) string {\n\tkeystoreInterface, exists := data.Get(owner)\n\tif !exists {\n\t\treturn StatusNoUser\n\t}\n\tkeystore := keystoreInterface.(*KeyStore)\n\treturn ufmt.Sprintf(\"%d\", keystore.Data.Size())\n}\n\n// Render provides read-only url access to the functions of the keystore\n// \"\" -\u003e show all keystores listed by owner\n// \"owner\" -\u003e show all keys for that owner's keystore\n// \"owner:size\" -\u003e returns size of owner's keystore\n// \"owner:get:key\" -\u003e show value for that key in owner's keystore\nfunc Render(p string) string {\n\tvar response string\n\targs := strings.Split(p, \":\")\n\tnumArgs := len(args)\n\tif p == \"\" {\n\t\tnumArgs = 0\n\t}\n\tswitch numArgs {\n\tcase 0:\n\t\tif data.Size() == 0 {\n\t\t\treturn StatusNoDatabases\n\t\t}\n\t\tdata.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tks := value.(*KeyStore)\n\t\t\tresponse += ufmt.Sprintf(\"- [%s](%s:%s) (%d keys)\\n\", ks.Owner, BaseURL, ks.Owner, ks.Data.Size())\n\t\t\treturn false\n\t\t})\n\tcase 1:\n\t\towner := args[0]\n\t\tkeystoreInterface, exists := data.Get(owner)\n\t\tif !exists {\n\t\t\treturn StatusNoUser\n\t\t}\n\t\tks := keystoreInterface.(*KeyStore)\n\t\ti := 0\n\t\tresponse += ufmt.Sprintf(\"# %s database\\n\\n\", ks.Owner)\n\t\tks.Data.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t\tresponse += ufmt.Sprintf(\"- %d [%s](%s:%s:get:%s)\\n\", i, key, BaseURL, ks.Owner, key)\n\t\t\ti++\n\t\t\treturn false\n\t\t})\n\tcase 2:\n\t\towner := args[0]\n\t\tcmd := args[1]\n\t\tif cmd == \"size\" {\n\t\t\treturn size(owner)\n\t\t}\n\tcase 3:\n\t\towner := args[0]\n\t\tcmd := args[1]\n\t\tkey := args[2]\n\t\tif cmd == \"get\" {\n\t\t\treturn get(owner, key)\n\t\t}\n\t}\n\n\treturn response\n}\n"},{"name":"keystore_test.gno","body":"package keystore\n\nimport (\n\t\"std\"\n\t\"strings\"\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 TestRender(t *testing.T) {\n\tconst (\n\t\tauthor1 std.Address = testutils.TestAddress(\"author1\")\n\t\tauthor2 std.Address = testutils.TestAddress(\"author2\")\n\t)\n\n\ttt := []struct {\n\t\tcaller std.Address\n\t\towner std.Address\n\t\tps []string\n\t\texp string\n\t}{\n\t\t// can set database if the owner is the caller\n\t\t{author1, author1, []string{\"set\", \"hello\", \"gno\"}, StatusOK},\n\t\t{author1, author1, []string{\"size\"}, \"1\"},\n\t\t{author1, author1, []string{\"set\", \"hello\", \"world\"}, StatusOK},\n\t\t{author1, author1, []string{\"size\"}, \"1\"},\n\t\t{author1, author1, []string{\"set\", \"hi\", \"gno\"}, StatusOK},\n\t\t{author1, author1, []string{\"size\"}, \"2\"},\n\t\t// only owner can remove\n\t\t{author1, author1, []string{\"remove\", \"hi\"}, StatusOK},\n\t\t{author1, author1, []string{\"get\", \"hi\"}, StatusNotFound},\n\t\t{author1, author1, []string{\"size\"}, \"1\"},\n\t\t// add back\n\t\t{author1, author1, []string{\"set\", \"hi\", \"gno\"}, StatusOK},\n\t\t{author1, author1, []string{\"size\"}, \"2\"},\n\n\t\t// different owner has different database\n\t\t{author2, author2, []string{\"set\", \"hello\", \"universe\"}, StatusOK},\n\t\t// either author can get the other info\n\t\t{author1, author2, []string{\"get\", \"hello\"}, \"universe\"},\n\t\t// either author can get the other info\n\t\t{author2, author1, []string{\"get\", \"hello\"}, \"world\"},\n\t\t{author1, author2, []string{\"get\", \"hello\"}, \"universe\"},\n\t\t// anyone can view the databases\n\t\t{author1, author2, []string{}, `- [g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6](/r/demo/keystore:g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6) (2 keys)\n- [g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00](/r/demo/keystore:g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00) (1 keys)`},\n\t\t// anyone can view the keys in a database\n\t\t{author1, author2, []string{\"\"}, `# g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00 database\n\n- 0 [hello](/r/demo/keystore:g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00:get:hello)`},\n\t}\n\tfor _, tc := range tt {\n\t\tp := \"\"\n\t\tif len(tc.ps) \u003e 0 {\n\t\t\tp = tc.owner.String()\n\t\t\tfor i, psv := range tc.ps {\n\t\t\t\tp += \":\" + psv\n\t\t\t}\n\t\t}\n\t\tp = strings.TrimSuffix(p, \":\")\n\t\tt.Run(p, func(t *testing.T) {\n\t\t\tstd.TestSetOriginCaller(tc.caller)\n\t\t\tvar act string\n\t\t\tif len(tc.ps) \u003e 0 \u0026\u0026 tc.ps[0] == \"set\" {\n\t\t\t\tact = strings.TrimSpace(Set(tc.ps[1], tc.ps[2]))\n\t\t\t} else if len(tc.ps) \u003e 0 \u0026\u0026 tc.ps[0] == \"remove\" {\n\t\t\t\tact = strings.TrimSpace(Remove(tc.ps[1]))\n\t\t\t} else {\n\t\t\t\tact = strings.TrimSpace(Render(p))\n\t\t\t}\n\n\t\t\tuassert.Equal(t, tc.exp, act, ufmt.Sprintf(\"%v -\u003e '%s'\", tc.ps, p))\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":"markdown","path":"gno.land/r/demo/markdown_test","files":[{"name":"markdown.gno","body":"package markdown\n\n// this package can be used to test markdown rendering engines.\n\nfunc Render(path string) string {\n\toutput := `_imported from https://github.com/markedjs/marked/blob/master/docs/demo/quickref.md_\n\nMarkdown Quick Reference\n========================\n\nThis guide is a very brief overview, with examples, of the syntax that [Markdown] supports. It is itself written in Markdown and you can copy the samples over to the left-hand pane for experimentation. It's shown as *text* and not *rendered HTML*.\n\n[Markdown]: http://daringfireball.net/projects/markdown/\n\n\nSimple Text Formatting\n======================\n\nFirst thing is first. You can use *stars* or _underscores_ for italics. **Double stars** and __double underscores__ for bold. ***Three together*** for ___both___.\n\nParagraphs are pretty easy too. Just have a blank line between chunks of text.\n\n\u003e This chunk of text is in a block quote. Its multiple lines will all be\n\u003e indented a bit from the rest of the text.\n\u003e\n\u003e \u003e Multiple levels of block quotes also work.\n\nSometimes you want to include code, such as when you are explaining how ` + \"`\u003ch1\u003e`\" + ` HTML tags work, or maybe you are a programmer and you are discussing ` + \"`someMethod()`\" + `.\n\nIf you want to include code and have new\nlines preserved, indent the line with a tab\nor at least four spaces:\n\n Extra spaces work here too.\n This is also called preformatted text and it is useful for showing examples.\n The text will stay as text, so any *markdown* or \u003cu\u003eHTML\u003c/u\u003e you add will\n not show up formatted. This way you can show markdown examples in a\n markdown document.\n\n\u003e You can also use preformatted text with your blockquotes\n\u003e as long as you add at least five spaces.\n\n\nHeadings\n========\n\nThere are a couple of ways to make headings. Using three or more equals signs on a line under a heading makes it into an \"h1\" style. Three or more hyphens under a line makes it \"h2\" (slightly smaller). You can also use multiple pound symbols (` + \"`#`\" + `) before and after a heading. Pounds after the title are ignored. Here are some examples:\n\nThis is H1\n==========\n\nThis is H2\n----------\n\n# This is H1\n## This is H2\n### This is H3 with some extra pounds ###\n#### You get the idea ####\n##### I don't need extra pounds at the end\n###### H6 is the max\n\n\nLinks\n=====\n\nLet's link to a few sites. First, let's use the bare URL, like \u003chttps://www.github.com\u003e. Great for text, but ugly for HTML.\nNext is an inline link to [Google](https://www.google.com). A little nicer.\nThis is a reference-style link to [Wikipedia] [1].\nLastly, here's a pretty link to [Yahoo]. The reference-style and pretty links both automatically use the links defined below, but they could be defined *anywhere* in the markdown and are removed from the HTML. The names are also case insensitive, so you can use [YaHoO] and have it link properly.\n\n[1]: https://www.wikipedia.org\n[Yahoo]: https://www.yahoo.com\n\nTitle attributes may be added to links by adding text after a link.\nThis is the [inline link](https://www.bing.com \"Bing\") with a \"Bing\" title.\nYou can also go to [W3C] [2] and maybe visit a [friend].\n\n[2]: https://w3c.org (The W3C puts out specs for web-based things)\n[Friend]: https://facebook.com \"Facebook!\"\n\nEmail addresses in plain text are not linked: test@example.com.\nEmail addresses wrapped in angle brackets are linked: \u003ctest@example.com\u003e.\nThey are also obfuscated so that email harvesting spam robots hopefully won't get them.\n\n\nLists\n=====\n\n* This is a bulleted list\n* Great for shopping lists\n- You can also use hyphens\n+ Or plus symbols\n\nThe above is an \"unordered\" list. Now, on for a bit of order.\n\n1. Numbered lists are also easy\n2. Just start with a number\n3738762. However, the actual number doesn't matter when converted to HTML.\n1. This will still show up as 4.\n\nYou might want a few advanced lists:\n\n- This top-level list is wrapped in paragraph tags\n- This generates an extra space between each top-level item.\n\n- You do it by adding a blank line\n\n- This nested list also has blank lines between the list items.\n\n- How to create nested lists\n 1. Start your regular list\n 2. Indent nested lists with two spaces\n 3. Further nesting means you should indent with two more spaces\n * This line is indented with four spaces.\n\n- List items can be quite lengthy. You can keep typing and either continue\nthem on the next line with no indentation.\n\n- Alternately, if that looks ugly, you can also\n indent the next line a bit for a prettier look.\n\n- You can put large blocks of text in your list by just indenting with two spaces.\n\n This is formatted the same as code, but you can inspect the HTML\n and find that it's just wrapped in a ` + \"`\u003cp\u003e`\" + ` tag and *won't* be shown\n as preformatted text.\n\n You can keep adding more and more paragraphs to a single\n list item by adding the traditional blank line and then keep\n on indenting the paragraphs with two spaces.\n\n You really only need to indent the first line,\nbut that looks ugly.\n\n- Lists support blockquotes\n\n \u003e Just like this example here. By the way, you can\n \u003e nest lists inside blockquotes!\n \u003e - Fantastic!\n\n- Lists support preformatted text\n\n You just need to indent an additional four spaces.\n\n\nEven More\n=========\n\nHorizontal Rule\n---------------\n\nIf you need a horizontal rule you just need to put at least three hyphens, asterisks, or underscores on a line by themselves. You can also even put spaces between the characters.\n\n---\n****************************\n_ _ _ _ _ _ _\n\nThose three all produced horizontal lines. Keep in mind that three hyphens under any text turns that text into a heading, so add a blank like if you use hyphens.\n\nImages\n------\n\nImages work exactly like links, but they have exclamation points in front. They work with references and titles too.\n\n![Google Logo](https://www.google.com/images/errors/logo_sm.gif) and ![Happy].\n\n[Happy]: https://wpclipart.com/smiley/happy/simple_colors/smiley_face_simple_green_small.png (\"Smiley face\")\n\n\nInline HTML\n-----------\n\nIf markdown is too limiting, you can just insert your own \u003cstrike\u003ecrazy\u003c/strike\u003e HTML. Span-level HTML \u003cu\u003ecan *still* use markdown\u003c/u\u003e. Block level elements must be separated from text by a blank line and must not have any spaces before the opening and closing HTML.\n\n\u003cdiv style='font-family: \"Comic Sans MS\", \"Comic Sans\", cursive;'\u003e\nIt is a pity, but markdown does **not** work in here for most markdown parsers.\n[Marked] handles it pretty well.\n\u003c/div\u003e`\n\treturn output\n}\n"},{"name":"markdown_test.gno","body":"package markdown\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestRender(t *testing.T) {\n\toutput := Render(\"\")\n\tif !strings.Contains(output, \"\\nMarkdown Quick Reference\\n\") {\n\t\tt.Errorf(\"invalid output\")\n\t}\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}} @@ -99,7 +99,7 @@ {"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"memeland","path":"gno.land/r/demo/memeland","files":[{"name":"memeland.gno","body":"package memeland\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/demo/memeland\"\n)\n\nvar m *memeland.Memeland\n\nfunc init() {\n\tm = memeland.NewMemeland()\n\tm.TransferOwnership(\"g125em6arxsnj49vx35f0n0z34putv5ty3376fg5\")\n}\n\nfunc PostMeme(data string, timestamp int64) string {\n\treturn m.PostMeme(data, timestamp)\n}\n\nfunc Upvote(id string) string {\n\treturn m.Upvote(id)\n}\n\nfunc GetPostsInRange(startTimestamp, endTimestamp int64, page, pageSize int, sortBy string) string {\n\treturn m.GetPostsInRange(startTimestamp, endTimestamp, page, pageSize, sortBy)\n}\n\nfunc RemovePost(id string) string {\n\treturn m.RemovePost(id)\n}\n\nfunc GetOwner() std.Address {\n\treturn m.Owner()\n}\n\nfunc TransferOwnership(newOwner std.Address) {\n\tif err := m.TransferOwnership(newOwner); err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc Render(path string) string {\n\tnumOfMemes := int(m.MemeCounter)\n\tif numOfMemes == 0 {\n\t\treturn \"No memes posted yet! :/\"\n\t}\n\n\t// Default render is get Posts since year 2000 to now\n\treturn m.GetPostsInRange(0, time.Now().Unix(), 1, 10, \"DATE_CREATED\")\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":"microblog","path":"gno.land/r/demo/microblog","files":[{"name":"README.md","body":"# microblog realm\n\n## Getting started:\n\n(One-time) Add the microblog package:\n\n```\ngnokey maketx addpkg --pkgpath \"gno.land/p/demo/microblog\" --pkgdir \"examples/gno.land/p/demo/microblog\" \\\n --deposit 100000000ugnot --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast --chainid dev --remote localhost:26657 \u003cYOURKEY\u003e\n```\n\n(One-time) Add the microblog realm:\n\n```\ngnokey maketx addpkg --pkgpath \"gno.land/r/demo/microblog\" --pkgdir \"examples/gno.land/r/demo/microblog\" \\\n --deposit 100000000ugnot --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast --chainid dev --remote localhost:26657 \u003cYOURKEY\u003e\n```\n\nAdd a microblog post:\n\n```\ngnokey maketx call --pkgpath \"gno.land/r/demo/microblog\" --func \"NewPost\" --args \"hello, world\" \\\n --gas-fee \"1000000ugnot\" --gas-wanted \"2000000\" --broadcast --chainid dev --remote localhost:26657 \u003cYOURKEY\u003e\n```"},{"name":"microblog.gno","body":"// Microblog is a website with shortform posts from users.\n// The API is simple - \"AddPost\" takes markdown and\n// adds it to the users site.\n// The microblog location is determined by the user address\n// /r/demo/microblog:\u003cYOUR-ADDRESS\u003e\npackage microblog\n\nimport (\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/microblog\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\ttitle = \"gno-based microblog\"\n\tprefix = \"/r/demo/microblog:\"\n\tm *microblog.Microblog\n)\n\nfunc init() {\n\tm = microblog.NewMicroblog(title, prefix)\n}\n\nfunc renderHome() string {\n\toutput := ufmt.Sprintf(\"# %s\\n\\n\", m.Title)\n\toutput += \"# pages\\n\\n\"\n\n\tfor _, page := range m.GetPages() {\n\t\tif u := users.GetUserByAddress(page.Author); u != nil {\n\t\t\toutput += ufmt.Sprintf(\"- [%s (%s)](%s%s)\\n\", u.Name, page.Author.String(), m.Prefix, page.Author.String())\n\t\t} else {\n\t\t\toutput += ufmt.Sprintf(\"- [%s](%s%s)\\n\", page.Author.String(), m.Prefix, page.Author.String())\n\t\t}\n\t}\n\n\treturn output\n}\n\nfunc renderUser(user string) string {\n\tsilo, found := m.Pages.Get(user)\n\tif !found {\n\t\treturn \"404\" // StatusNotFound\n\t}\n\n\treturn PageToString((silo.(*microblog.Page)))\n}\n\nfunc Render(path string) string {\n\tparts := strings.Split(path, \"/\")\n\n\tisHome := path == \"\"\n\tisUser := len(parts) == 1\n\n\tswitch {\n\tcase isHome:\n\t\treturn renderHome()\n\n\tcase isUser:\n\t\treturn renderUser(parts[0])\n\t}\n\n\treturn \"404\" // StatusNotFound\n}\n\nfunc PageToString(p *microblog.Page) string {\n\to := \"\"\n\tif u := users.GetUserByAddress(p.Author); u != nil {\n\t\to += ufmt.Sprintf(\"# [%s](/r/demo/users:%s)\\n\\n\", u, u)\n\t\to += ufmt.Sprintf(\"%s\\n\\n\", u.Profile)\n\t}\n\to += ufmt.Sprintf(\"## [%s](/r/demo/microblog:%s)\\n\\n\", p.Author, p.Author)\n\n\to += ufmt.Sprintf(\"joined %s, last updated %s\\n\\n\", p.CreatedAt.Format(\"2006-02-01\"), p.LastPosted.Format(\"2006-02-01\"))\n\to += \"## feed\\n\\n\"\n\tfor _, u := range p.GetPosts() {\n\t\to += u.String() + \"\\n\\n\"\n\t}\n\treturn o\n}\n\n// NewPost takes a single argument (post markdown) and\n// adds a post to the address of the caller.\nfunc NewPost(text string) string {\n\tif err := m.NewPost(text); err != nil {\n\t\treturn \"unable to add new post\"\n\t}\n\treturn \"added new post\"\n}\n\nfunc Register(name, profile string) string {\n\tcaller := std.OriginCaller() // main\n\tusers.Register(caller, name, profile)\n\treturn \"OK\"\n}\n"},{"name":"microblog_test.gno","body":"package microblog\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc TestMicroblog(t *testing.T) {\n\tconst (\n\t\tauthor1 std.Address = testutils.TestAddress(\"author1\")\n\t\tauthor2 std.Address = testutils.TestAddress(\"author2\")\n\t)\n\n\tstd.TestSetOriginCaller(author1)\n\n\turequire.Equal(t, \"404\", Render(\"/wrongpath\"), \"rendering not giving 404\")\n\turequire.NotEqual(t, \"404\", Render(\"\"), \"rendering / should not give 404\")\n\turequire.NoError(t, m.NewPost(\"goodbyte, web2\"), \"could not create post\")\n\n\t_, err := m.GetPage(author1.String())\n\turequire.NoError(t, err, \"silo should exist\")\n\n\t_, err = m.GetPage(\"no such author\")\n\turequire.Error(t, err, \"silo should not exist\")\n\n\tstd.TestSetOriginCaller(author2)\n\n\turequire.NoError(t, m.NewPost(\"hello, web3\"), \"could not create post\")\n\turequire.NoError(t, m.NewPost(\"hello again, web3\"), \"could not create post\")\n\turequire.NoError(t, m.NewPost(\"hi again,\\n web4?\"), \"could not create post\")\n\n\tprintln(\"--- MICROBLOG ---\\n\\n\")\n\n\texpected := `# gno-based microblog\n\n# pages\n\n- [g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6](/r/demo/microblog:g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6)\n- [g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00](/r/demo/microblog:g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00)\n`\n\turequire.Equal(t, expected, Render(\"\"), \"incorrect rendering\")\n\n\texpected = `## [g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6](/r/demo/microblog:g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6)\n\njoined 2009-13-02, last updated 2009-13-02\n\n## feed\n\n\u003e goodbyte, web2\n\u003e\n\u003e *Fri, 13 Feb 2009 23:31:30 UTC*`\n\n\turequire.Equal(t, expected, strings.TrimSpace(Render(author1.String())), \"incorrect rendering\")\n\n\texpected = `## [g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00](/r/demo/microblog:g1v96hg6r0wge97h6lta047h6lta047h6lyz7c00)\n\njoined 2009-13-02, last updated 2009-13-02\n\n## feed\n\n\u003e hi again,\n\u003e\n\u003e web4?\n\u003e\n\u003e *Fri, 13 Feb 2009 23:31:30 UTC*\n\n\u003e hello again, web3\n\u003e\n\u003e *Fri, 13 Feb 2009 23:31:30 UTC*\n\n\u003e hello, web3\n\u003e\n\u003e *Fri, 13 Feb 2009 23:31:30 UTC*`\n\n\turequire.Equal(t, expected, strings.TrimSpace(Render(author2.String())), \"incorrect rendering\")\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":"nft","path":"gno.land/r/demo/nft","files":[{"name":"README.md","body":"NFT's are all the rage these days, for various reasons.\n\nI read over EIP-721 which appears to be the de-facto NFT standard on Ethereum. Then, made a sample implementation of EIP-721 (let's here called GRC-721). The implementation isn't complete, but it demonstrates the main functionality.\n\n- [EIP-721](https://eips.ethereum.org/EIPS/eip-721)\n- [gno.land/r/demo/nft/nft.go](https://gno.land/r/demo/nft/nft.go)\n- [zrealm_nft3.go test](https://github.com/gnolang/gno/blob/master/tests/files2/zrealm_nft3.gno)\n\nIn short, this demonstrates how to implement Ethereum contract interfaces in gno.land; by using only standard Go language features.\n\nPlease leave a comment ([guide](https://gno.land/r/demo/boards:gnolang/1)).\n"},{"name":"nft.gno","body":"package nft\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/grc/grc721\"\n)\n\ntype token struct {\n\tgrc721.IGRC721 // implements the GRC721 interface\n\n\ttokenCounter int\n\ttokens avl.Tree // grc721.TokenID -\u003e *NFToken{}\n\toperators avl.Tree // owner std.Address -\u003e operator std.Address\n}\n\ntype NFToken struct {\n\towner std.Address\n\tapproved std.Address\n\ttokenID grc721.TokenID\n\tdata string\n}\n\nvar gToken = \u0026token{}\n\nfunc GetToken() *token { return gToken }\n\nfunc (grc *token) nextTokenID() grc721.TokenID {\n\tgrc.tokenCounter++\n\ts := strconv.Itoa(grc.tokenCounter)\n\treturn grc721.TokenID(s)\n}\n\nfunc (grc *token) getToken(tid grc721.TokenID) (*NFToken, bool) {\n\ttoken, ok := grc.tokens.Get(string(tid))\n\tif !ok {\n\t\treturn nil, false\n\t}\n\treturn token.(*NFToken), true\n}\n\nfunc (grc *token) Mint(to std.Address, data string) grc721.TokenID {\n\ttid := grc.nextTokenID()\n\tgrc.tokens.Set(string(tid), \u0026NFToken{\n\t\towner: to,\n\t\ttokenID: tid,\n\t\tdata: data,\n\t})\n\treturn tid\n}\n\nfunc (grc *token) BalanceOf(owner std.Address) (count int64) {\n\tpanic(\"not yet implemented\")\n}\n\nfunc (grc *token) OwnerOf(tid grc721.TokenID) std.Address {\n\ttoken, ok := grc.getToken(tid)\n\tif !ok {\n\t\tpanic(\"token does not exist\")\n\t}\n\treturn token.owner\n}\n\n// XXX not fully implemented yet.\nfunc (grc *token) SafeTransferFrom(from, to std.Address, tid grc721.TokenID) {\n\tgrc.TransferFrom(from, to, tid)\n\t// When transfer is complete, this function checks if `_to` is a smart\n\t// contract (code size \u003e 0). If so, it calls `onERC721Received` on\n\t// `_to` and throws if the return value is not\n\t// `bytes4(keccak256(\"onERC721Received(address,address,uint256,bytes)\"))`.\n\t// XXX ensure \"to\" is a realm with onERC721Received() signature.\n}\n\nfunc (grc *token) TransferFrom(from, to std.Address, tid grc721.TokenID) {\n\tcaller := std.CallerAt(2)\n\ttoken, ok := grc.getToken(tid)\n\t// Throws if `_tokenId` is not a valid NFT.\n\tif !ok {\n\t\tpanic(\"token does not exist\")\n\t}\n\t// Throws unless `msg.sender` is the current owner, an authorized\n\t// operator, or the approved address for this NFT.\n\tif caller != token.owner \u0026\u0026 caller != token.approved {\n\t\toperator, ok := grc.operators.Get(token.owner.String())\n\t\tif !ok || caller != operator.(std.Address) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t}\n\t// Throws if `_from` is not the current owner.\n\tif from != token.owner {\n\t\tpanic(\"from is not the current owner\")\n\t}\n\t// Throws if `_to` is the zero address.\n\tif to == \"\" {\n\t\tpanic(\"to cannot be empty\")\n\t}\n\t// Good.\n\ttoken.owner = to\n}\n\nfunc (grc *token) Approve(approved std.Address, tid grc721.TokenID) {\n\tcaller := std.CallerAt(2)\n\ttoken, ok := grc.getToken(tid)\n\t// Throws if `_tokenId` is not a valid NFT.\n\tif !ok {\n\t\tpanic(\"token does not exist\")\n\t}\n\t// Throws unless `msg.sender` is the current owner,\n\t// or an authorized operator.\n\tif caller != token.owner {\n\t\toperator, ok := grc.operators.Get(token.owner.String())\n\t\tif !ok || caller != operator.(std.Address) {\n\t\t\tpanic(\"unauthorized\")\n\t\t}\n\t}\n\t// Good.\n\ttoken.approved = approved\n}\n\n// XXX make it work for set of operators.\nfunc (grc *token) SetApprovalForAll(operator std.Address, approved bool) {\n\tcaller := std.CallerAt(2)\n\tgrc.operators.Set(caller.String(), operator)\n}\n\nfunc (grc *token) GetApproved(tid grc721.TokenID) std.Address {\n\ttoken, ok := grc.getToken(tid)\n\t// Throws if `_tokenId` is not a valid NFT.\n\tif !ok {\n\t\tpanic(\"token does not exist\")\n\t}\n\treturn token.approved\n}\n\n// XXX make it work for set of operators\nfunc (grc *token) IsApprovedForAll(owner, operator std.Address) bool {\n\toperator2, ok := grc.operators.Get(owner.String())\n\tif !ok {\n\t\treturn false\n\t}\n\treturn operator == operator2.(std.Address)\n}\n"},{"name":"z_0_filetest.gno","body":"// PKGPATH: gno.land/r/demo/nft_test\npackage nft_test\n\nimport (\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/nft\"\n)\n\nfunc main() {\n\taddr1 := testutils.TestAddress(\"addr1\")\n\t// addr2 := testutils.TestAddress(\"addr2\")\n\tgrc721 := nft.GetToken()\n\ttid := grc721.Mint(addr1, \"NFT#1\")\n\tprintln(grc721.OwnerOf(tid))\n\tprintln(addr1)\n}\n\n// Output:\n// g1v9jxgu33ta047h6lta047h6lta047h6l43dqc5\n// g1v9jxgu33ta047h6lta047h6lta047h6l43dqc5\n\n// Realm:\n// switchrealm[\"gno.land/r/demo/nft\"]\n// switchrealm[\"gno.land/r/demo/nft\"]\n// c[67c479d3d51d4056b2f4111d5352912a00be311e:11]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"std.Address\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"g1v9jxgu33ta047h6lta047h6lta047h6l43dqc5\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"std.Address\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/grc/grc721.TokenID\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"1\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"NFT#1\"\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:11\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:10\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[67c479d3d51d4056b2f4111d5352912a00be311e:10]={\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:10\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:9\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/nft.NFToken\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"564a9e78be869bd258fc3c9ad56f5a75ed68818f\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:11\"\n// }\n// }\n// }\n// c[67c479d3d51d4056b2f4111d5352912a00be311e:9]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"16\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.StringValue\",\n// \"value\": \"1\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/r/demo/nft.NFToken\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"b53ffc464e1b5655d19b9d5277f3491717c24aca\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:10\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"64\"\n// }\n// },\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:9\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:8\",\n// \"RefCount\": \"1\"\n// }\n// }\n// c[67c479d3d51d4056b2f4111d5352912a00be311e:8]={\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:8\",\n// \"ModTime\": \"0\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:5\",\n// \"RefCount\": \"1\"\n// },\n// \"Value\": {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"b1d928b3716b147c92730e8d234162bec2f0f2fc\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:9\"\n// }\n// }\n// }\n// u[67c479d3d51d4056b2f4111d5352912a00be311e:5]={\n// \"Fields\": [\n// {\n// \"T\": {\n// \"@type\": \"/gno.PointerType\",\n// \"Elt\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Node\"\n// }\n// },\n// \"V\": {\n// \"@type\": \"/gno.PointerValue\",\n// \"Base\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"b229b824842ec3e7f2341e33d0fa0ca77af2f480\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:8\"\n// },\n// \"Index\": \"0\",\n// \"TV\": null\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:5\",\n// \"ModTime\": \"7\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:4\",\n// \"RefCount\": \"1\"\n// }\n// }\n// u[67c479d3d51d4056b2f4111d5352912a00be311e:4]={\n// \"Fields\": [\n// {},\n// {\n// \"N\": \"AQAAAAAAAAA=\",\n// \"T\": {\n// \"@type\": \"/gno.PrimitiveType\",\n// \"value\": \"32\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"1e0b9dddb406b4f50500a022266a4cb8a4ea38c6\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:5\"\n// }\n// },\n// {\n// \"T\": {\n// \"@type\": \"/gno.RefType\",\n// \"ID\": \"gno.land/p/demo/avl.Tree\"\n// },\n// \"V\": {\n// \"@type\": \"/gno.RefValue\",\n// \"Hash\": \"05ab6746ea84b55ca133806af215d99a1c4b045e\",\n// \"ObjectID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:6\"\n// }\n// }\n// ],\n// \"ObjectInfo\": {\n// \"ID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:4\",\n// \"ModTime\": \"7\",\n// \"OwnerID\": \"67c479d3d51d4056b2f4111d5352912a00be311e:3\",\n// \"RefCount\": \"1\"\n// }\n// }\n// switchrealm[\"gno.land/r/demo/nft\"]\n// switchrealm[\"gno.land/r/demo/nft_test\"]\n"},{"name":"z_1_filetest.gno","body":"// PKGPATH: gno.land/r/demo/nft_test\npackage nft_test\n\nimport (\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/nft\"\n)\n\nfunc main() {\n\taddr1 := testutils.TestAddress(\"addr1\")\n\taddr2 := testutils.TestAddress(\"addr2\")\n\tgrc721 := nft.GetToken()\n\ttid := grc721.Mint(addr1, \"NFT#1\")\n\tprintln(grc721.OwnerOf(tid))\n\tprintln(addr1)\n\tgrc721.TransferFrom(addr1, addr2, tid)\n}\n\n// Error:\n// unauthorized\n"},{"name":"z_2_filetest.gno","body":"// PKGPATH: gno.land/r/demo/nft_test\npackage nft_test\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/nft\"\n)\n\nfunc main() {\n\tcaller := std.CallerAt(1)\n\taddr1 := testutils.TestAddress(\"addr1\")\n\t// addr2 := testutils.TestAddress(\"addr2\")\n\tgrc721 := nft.GetToken()\n\ttid := grc721.Mint(caller, \"NFT#1\")\n\tprintln(grc721.OwnerOf(tid))\n\tprintln(addr1)\n\tgrc721.TransferFrom(caller, addr1, tid)\n}\n\n// Output:\n// g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n// g1v9jxgu33ta047h6lta047h6lta047h6l43dqc5\n"},{"name":"z_3_filetest.gno","body":"// PKGPATH: gno.land/r/demo/nft_test\npackage nft_test\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/nft\"\n)\n\nfunc main() {\n\tcaller := std.CallerAt(1)\n\taddr1 := testutils.TestAddress(\"addr1\")\n\taddr2 := testutils.TestAddress(\"addr2\")\n\tgrc721 := nft.GetToken()\n\ttid := grc721.Mint(caller, \"NFT#1\")\n\tprintln(grc721.OwnerOf(tid))\n\tprintln(addr1)\n\tgrc721.Approve(caller, tid) // approve self.\n\tgrc721.TransferFrom(caller, addr1, tid)\n\tgrc721.TransferFrom(addr1, addr2, tid)\n}\n\n// Output:\n// g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n// g1v9jxgu33ta047h6lta047h6lta047h6l43dqc5\n"},{"name":"z_4_filetest.gno","body":"// PKGPATH: gno.land/r/demo/nft_test\npackage nft_test\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/nft\"\n)\n\nfunc main() {\n\tcaller := std.CallerAt(1)\n\taddr1 := testutils.TestAddress(\"addr1\")\n\taddr2 := testutils.TestAddress(\"addr2\")\n\tgrc721 := nft.GetToken()\n\ttid := grc721.Mint(caller, \"NFT#1\")\n\tprintln(grc721.OwnerOf(tid))\n\tprintln(addr1)\n\tgrc721.Approve(caller, tid) // approve self.\n\tgrc721.TransferFrom(caller, addr1, tid)\n\tgrc721.Approve(\"\", tid) // approve addr1.\n\tgrc721.TransferFrom(addr1, addr2, tid)\n}\n\n// Error:\n// unauthorized\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":"profile","path":"gno.land/r/demo/profile","files":[{"name":"profile.gno","body":"package profile\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/mux\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar (\n\tfields = avl.NewTree()\n\trouter = mux.NewRouter()\n)\n\n// Standard fields\nconst (\n\tDisplayName = \"DisplayName\"\n\tHomepage = \"Homepage\"\n\tBio = \"Bio\"\n\tAge = \"Age\"\n\tLocation = \"Location\"\n\tAvatar = \"Avatar\"\n\tGravatarEmail = \"GravatarEmail\"\n\tAvailableForHiring = \"AvailableForHiring\"\n\tInvalidField = \"InvalidField\"\n)\n\n// Events\nconst (\n\tProfileFieldCreated = \"ProfileFieldCreated\"\n\tProfileFieldUpdated = \"ProfileFieldUpdated\"\n)\n\n// Field types used when emitting event\nconst FieldType = \"FieldType\"\n\nconst (\n\tBoolField = \"BoolField\"\n\tStringField = \"StringField\"\n\tIntField = \"IntField\"\n)\n\nfunc init() {\n\trouter.HandleFunc(\"\", homeHandler)\n\trouter.HandleFunc(\"u/{addr}\", profileHandler)\n\trouter.HandleFunc(\"f/{addr}/{field}\", fieldHandler)\n}\n\n// List of supported string fields\nvar stringFields = map[string]bool{\n\tDisplayName: true,\n\tHomepage: true,\n\tBio: true,\n\tLocation: true,\n\tAvatar: true,\n\tGravatarEmail: true,\n}\n\n// List of support int fields\nvar intFields = map[string]bool{\n\tAge: true,\n}\n\n// List of support bool fields\nvar boolFields = map[string]bool{\n\tAvailableForHiring: true,\n}\n\n// Setters\n\nfunc SetStringField(field, value string) bool {\n\taddr := std.PreviousRealm().Addr()\n\tkey := addr.String() + \":\" + field\n\tupdated := fields.Set(key, value)\n\n\tevent := ProfileFieldCreated\n\tif updated {\n\t\tevent = ProfileFieldUpdated\n\t}\n\n\tstd.Emit(event, FieldType, StringField, field, value)\n\n\treturn updated\n}\n\nfunc SetIntField(field string, value int) bool {\n\taddr := std.PreviousRealm().Addr()\n\tkey := addr.String() + \":\" + field\n\tupdated := fields.Set(key, value)\n\n\tevent := ProfileFieldCreated\n\tif updated {\n\t\tevent = ProfileFieldUpdated\n\t}\n\n\tstd.Emit(event, FieldType, IntField, field, string(value))\n\n\treturn updated\n}\n\nfunc SetBoolField(field string, value bool) bool {\n\taddr := std.PreviousRealm().Addr()\n\tkey := addr.String() + \":\" + field\n\tupdated := fields.Set(key, value)\n\n\tevent := ProfileFieldCreated\n\tif updated {\n\t\tevent = ProfileFieldUpdated\n\t}\n\n\tstd.Emit(event, FieldType, BoolField, field, ufmt.Sprintf(\"%t\", value))\n\n\treturn updated\n}\n\n// Getters\n\nfunc GetStringField(addr std.Address, field, def string) string {\n\tkey := addr.String() + \":\" + field\n\tif value, ok := fields.Get(key); ok {\n\t\treturn value.(string)\n\t}\n\n\treturn def\n}\n\nfunc GetBoolField(addr std.Address, field string, def bool) bool {\n\tkey := addr.String() + \":\" + field\n\tif value, ok := fields.Get(key); ok {\n\t\treturn value.(bool)\n\t}\n\n\treturn def\n}\n\nfunc GetIntField(addr std.Address, field string, def int) int {\n\tkey := addr.String() + \":\" + field\n\tif value, ok := fields.Get(key); ok {\n\t\treturn value.(int)\n\t}\n\n\treturn def\n}\n"},{"name":"profile_test.gno","body":"package profile\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\n// Global addresses for test users\nvar (\n\talice = testutils.TestAddress(\"alice\")\n\tbob = testutils.TestAddress(\"bob\")\n\tcharlie = testutils.TestAddress(\"charlie\")\n\tdave = testutils.TestAddress(\"dave\")\n\teve = testutils.TestAddress(\"eve\")\n\tfrank = testutils.TestAddress(\"frank\")\n\tuser1 = testutils.TestAddress(\"user1\")\n\tuser2 = testutils.TestAddress(\"user2\")\n)\n\nfunc TestStringFields(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\n\t// Get before setting\n\tname := GetStringField(alice, DisplayName, \"anon\")\n\tuassert.Equal(t, \"anon\", name)\n\n\t// Set new key\n\tupdated := SetStringField(DisplayName, \"Alice foo\")\n\tuassert.Equal(t, updated, false)\n\tupdated = SetStringField(Homepage, \"https://example.com\")\n\tuassert.Equal(t, updated, false)\n\n\t// Update the key\n\tupdated = SetStringField(DisplayName, \"Alice foo\")\n\tuassert.Equal(t, updated, true)\n\n\t// Get after setting\n\tname = GetStringField(alice, DisplayName, \"anon\")\n\thomepage := GetStringField(alice, Homepage, \"\")\n\tbio := GetStringField(alice, Bio, \"42\")\n\n\tuassert.Equal(t, \"Alice foo\", name)\n\tuassert.Equal(t, \"https://example.com\", homepage)\n\tuassert.Equal(t, \"42\", bio)\n}\n\nfunc TestIntFields(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\n\t// Get before setting\n\tage := GetIntField(bob, Age, 25)\n\tuassert.Equal(t, 25, age)\n\n\t// Set new key\n\tupdated := SetIntField(Age, 30)\n\tuassert.Equal(t, updated, false)\n\n\t// Update the key\n\tupdated = SetIntField(Age, 30)\n\tuassert.Equal(t, updated, true)\n\n\t// Get after setting\n\tage = GetIntField(bob, Age, 25)\n\tuassert.Equal(t, 30, age)\n}\n\nfunc TestBoolFields(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(charlie))\n\n\t// Get before setting\n\thiring := GetBoolField(charlie, AvailableForHiring, false)\n\tuassert.Equal(t, false, hiring)\n\n\t// Set\n\tupdated := SetBoolField(AvailableForHiring, true)\n\tuassert.Equal(t, updated, false)\n\n\t// Update the key\n\tupdated = SetBoolField(AvailableForHiring, true)\n\tuassert.Equal(t, updated, true)\n\n\t// Get after setting\n\thiring = GetBoolField(charlie, AvailableForHiring, false)\n\tuassert.Equal(t, true, hiring)\n}\n\nfunc TestMultipleProfiles(t *testing.T) {\n\t// Set profile for user1\n\tstd.TestSetRealm(std.NewUserRealm(user1))\n\tupdated := SetStringField(DisplayName, \"User One\")\n\tuassert.Equal(t, updated, false)\n\n\t// Set profile for user2\n\tstd.TestSetRealm(std.NewUserRealm(user2))\n\tupdated = SetStringField(DisplayName, \"User Two\")\n\tuassert.Equal(t, updated, false)\n\n\t// Get profiles\n\tstd.TestSetRealm(std.NewUserRealm(user1)) // Switch back to user1\n\tname1 := GetStringField(user1, DisplayName, \"anon\")\n\tstd.TestSetRealm(std.NewUserRealm(user2)) // Switch back to user2\n\tname2 := GetStringField(user2, DisplayName, \"anon\")\n\n\tuassert.Equal(t, \"User One\", name1)\n\tuassert.Equal(t, \"User Two\", name2)\n}\n\nfunc TestArbitraryStringField(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(user1))\n\n\t// Set arbitrary string field\n\tupdated := SetStringField(\"MyEmail\", \"my@email.com\")\n\tuassert.Equal(t, updated, false)\n\n\tval := GetStringField(user1, \"MyEmail\", \"\")\n\tuassert.Equal(t, val, \"my@email.com\")\n}\n\nfunc TestArbitraryIntField(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(user1))\n\n\t// Set arbitrary int field\n\tupdated := SetIntField(\"MyIncome\", 100_000)\n\tuassert.Equal(t, updated, false)\n\n\tval := GetIntField(user1, \"MyIncome\", 0)\n\tuassert.Equal(t, val, 100_000)\n}\n\nfunc TestArbitraryBoolField(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(user1))\n\n\t// Set arbitrary int field\n\tupdated := SetBoolField(\"IsWinner\", true)\n\tuassert.Equal(t, updated, false)\n\n\tval := GetBoolField(user1, \"IsWinner\", false)\n\tuassert.Equal(t, val, true)\n}\n"},{"name":"render.gno","body":"package profile\n\nimport (\n\t\"bytes\"\n\t\"net/url\"\n\t\"std\"\n\n\t\"gno.land/p/demo/mux\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nconst (\n\tBaseURL = \"/r/demo/profile\"\n\tSetStringFieldURL = BaseURL + \"$help\u0026func=SetStringField\u0026field=%s\"\n\tSetIntFieldURL = BaseURL + \"$help\u0026func=SetIntField\u0026field=%s\"\n\tSetBoolFieldURL = BaseURL + \"$help\u0026func=SetBoolField\u0026field=%s\"\n\tViewAllFieldsURL = BaseURL + \":u/%s\"\n\tViewFieldURL = BaseURL + \":f/%s/%s\"\n)\n\nfunc homeHandler(res *mux.ResponseWriter, req *mux.Request) {\n\tvar b bytes.Buffer\n\n\tb.WriteString(\"## Setters\\n\")\n\tfor field := range stringFields {\n\t\tlink := ufmt.Sprintf(SetStringFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- [Set %s](%s)\\n\", field, link))\n\t}\n\n\tfor field := range intFields {\n\t\tlink := ufmt.Sprintf(SetIntFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- [Set %s](%s)\\n\", field, link))\n\t}\n\n\tfor field := range boolFields {\n\t\tlink := ufmt.Sprintf(SetBoolFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- [Set %s Field](%s)\\n\", field, link))\n\t}\n\n\tb.WriteString(\"\\n---\\n\\n\")\n\n\tres.Write(b.String())\n}\n\nfunc profileHandler(res *mux.ResponseWriter, req *mux.Request) {\n\tvar b bytes.Buffer\n\taddr := req.GetVar(\"addr\")\n\n\tb.WriteString(ufmt.Sprintf(\"# Profile %s\\n\", addr))\n\n\taddress := std.Address(addr)\n\n\tfor field := range stringFields {\n\t\tvalue := GetStringField(address, field, \"n/a\")\n\t\tlink := ufmt.Sprintf(SetStringFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- %s: %s [Edit](%s)\\n\", field, value, link))\n\t}\n\n\tfor field := range intFields {\n\t\tvalue := GetIntField(address, field, 0)\n\t\tlink := ufmt.Sprintf(SetIntFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- %s: %d [Edit](%s)\\n\", field, value, link))\n\t}\n\n\tfor field := range boolFields {\n\t\tvalue := GetBoolField(address, field, false)\n\t\tlink := ufmt.Sprintf(SetBoolFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- %s: %t [Edit](%s)\\n\", field, value, link))\n\t}\n\n\tres.Write(b.String())\n}\n\nfunc fieldHandler(res *mux.ResponseWriter, req *mux.Request) {\n\tvar b bytes.Buffer\n\taddr := req.GetVar(\"addr\")\n\tfield := req.GetVar(\"field\")\n\n\tb.WriteString(ufmt.Sprintf(\"# Field %s for %s\\n\", field, addr))\n\n\taddress := std.Address(addr)\n\tvalue := \"n/a\"\n\tvar editLink string\n\n\tif _, ok := stringFields[field]; ok {\n\t\tvalue = ufmt.Sprintf(\"%s\", GetStringField(address, field, \"n/a\"))\n\t\teditLink = ufmt.Sprintf(SetStringFieldURL+\"\u0026addr=%s\u0026value=%s\", field, addr, url.QueryEscape(value))\n\t} else if _, ok := intFields[field]; ok {\n\t\tvalue = ufmt.Sprintf(\"%d\", GetIntField(address, field, 0))\n\t\teditLink = ufmt.Sprintf(SetIntFieldURL+\"\u0026addr=%s\u0026value=%s\", field, addr, value)\n\t} else if _, ok := boolFields[field]; ok {\n\t\tvalue = ufmt.Sprintf(\"%t\", GetBoolField(address, field, false))\n\t\teditLink = ufmt.Sprintf(SetBoolFieldURL+\"\u0026addr=%s\u0026value=%s\", field, addr, value)\n\t}\n\n\tb.WriteString(ufmt.Sprintf(\"- %s: %s [Edit](%s)\\n\", field, value, editLink))\n\n\tres.Write(b.String())\n}\n\nfunc Render(path string) string {\n\treturn router.Render(path)\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":"profile","path":"gno.land/r/demo/profile","files":[{"name":"profile.gno","body":"package profile\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/mux\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar (\n\tfields = avl.NewTree()\n\trouter = mux.NewRouter()\n)\n\n// Standard fields\nconst (\n\tDisplayName = \"DisplayName\"\n\tHomepage = \"Homepage\"\n\tBio = \"Bio\"\n\tAge = \"Age\"\n\tLocation = \"Location\"\n\tAvatar = \"Avatar\"\n\tGravatarEmail = \"GravatarEmail\"\n\tAvailableForHiring = \"AvailableForHiring\"\n\tInvalidField = \"InvalidField\"\n)\n\n// Events\nconst (\n\tProfileFieldCreated = \"ProfileFieldCreated\"\n\tProfileFieldUpdated = \"ProfileFieldUpdated\"\n)\n\n// Field types used when emitting event\nconst FieldType = \"FieldType\"\n\nconst (\n\tBoolField = \"BoolField\"\n\tStringField = \"StringField\"\n\tIntField = \"IntField\"\n)\n\nfunc init() {\n\trouter.HandleFunc(\"\", homeHandler)\n\trouter.HandleFunc(\"u/{addr}\", profileHandler)\n\trouter.HandleFunc(\"f/{addr}/{field}\", fieldHandler)\n}\n\n// List of supported string fields\nvar stringFields = map[string]bool{\n\tDisplayName: true,\n\tHomepage: true,\n\tBio: true,\n\tLocation: true,\n\tAvatar: true,\n\tGravatarEmail: true,\n}\n\n// List of support int fields\nvar intFields = map[string]bool{\n\tAge: true,\n}\n\n// List of support bool fields\nvar boolFields = map[string]bool{\n\tAvailableForHiring: true,\n}\n\n// Setters\n\nfunc SetStringField(field, value string) bool {\n\taddr := std.PreviousRealm().Address()\n\tkey := addr.String() + \":\" + field\n\tupdated := fields.Set(key, value)\n\n\tevent := ProfileFieldCreated\n\tif updated {\n\t\tevent = ProfileFieldUpdated\n\t}\n\n\tstd.Emit(event, FieldType, StringField, field, value)\n\n\treturn updated\n}\n\nfunc SetIntField(field string, value int) bool {\n\taddr := std.PreviousRealm().Address()\n\tkey := addr.String() + \":\" + field\n\tupdated := fields.Set(key, value)\n\n\tevent := ProfileFieldCreated\n\tif updated {\n\t\tevent = ProfileFieldUpdated\n\t}\n\n\tstd.Emit(event, FieldType, IntField, field, string(value))\n\n\treturn updated\n}\n\nfunc SetBoolField(field string, value bool) bool {\n\taddr := std.PreviousRealm().Address()\n\tkey := addr.String() + \":\" + field\n\tupdated := fields.Set(key, value)\n\n\tevent := ProfileFieldCreated\n\tif updated {\n\t\tevent = ProfileFieldUpdated\n\t}\n\n\tstd.Emit(event, FieldType, BoolField, field, ufmt.Sprintf(\"%t\", value))\n\n\treturn updated\n}\n\n// Getters\n\nfunc GetStringField(addr std.Address, field, def string) string {\n\tkey := addr.String() + \":\" + field\n\tif value, ok := fields.Get(key); ok {\n\t\treturn value.(string)\n\t}\n\n\treturn def\n}\n\nfunc GetBoolField(addr std.Address, field string, def bool) bool {\n\tkey := addr.String() + \":\" + field\n\tif value, ok := fields.Get(key); ok {\n\t\treturn value.(bool)\n\t}\n\n\treturn def\n}\n\nfunc GetIntField(addr std.Address, field string, def int) int {\n\tkey := addr.String() + \":\" + field\n\tif value, ok := fields.Get(key); ok {\n\t\treturn value.(int)\n\t}\n\n\treturn def\n}\n"},{"name":"profile_test.gno","body":"package profile\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\n// Global addresses for test users\nvar (\n\talice = testutils.TestAddress(\"alice\")\n\tbob = testutils.TestAddress(\"bob\")\n\tcharlie = testutils.TestAddress(\"charlie\")\n\tdave = testutils.TestAddress(\"dave\")\n\teve = testutils.TestAddress(\"eve\")\n\tfrank = testutils.TestAddress(\"frank\")\n\tuser1 = testutils.TestAddress(\"user1\")\n\tuser2 = testutils.TestAddress(\"user2\")\n)\n\nfunc TestStringFields(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(alice))\n\n\t// Get before setting\n\tname := GetStringField(alice, DisplayName, \"anon\")\n\tuassert.Equal(t, \"anon\", name)\n\n\t// Set new key\n\tupdated := SetStringField(DisplayName, \"Alice foo\")\n\tuassert.Equal(t, updated, false)\n\tupdated = SetStringField(Homepage, \"https://example.com\")\n\tuassert.Equal(t, updated, false)\n\n\t// Update the key\n\tupdated = SetStringField(DisplayName, \"Alice foo\")\n\tuassert.Equal(t, updated, true)\n\n\t// Get after setting\n\tname = GetStringField(alice, DisplayName, \"anon\")\n\thomepage := GetStringField(alice, Homepage, \"\")\n\tbio := GetStringField(alice, Bio, \"42\")\n\n\tuassert.Equal(t, \"Alice foo\", name)\n\tuassert.Equal(t, \"https://example.com\", homepage)\n\tuassert.Equal(t, \"42\", bio)\n}\n\nfunc TestIntFields(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(bob))\n\n\t// Get before setting\n\tage := GetIntField(bob, Age, 25)\n\tuassert.Equal(t, 25, age)\n\n\t// Set new key\n\tupdated := SetIntField(Age, 30)\n\tuassert.Equal(t, updated, false)\n\n\t// Update the key\n\tupdated = SetIntField(Age, 30)\n\tuassert.Equal(t, updated, true)\n\n\t// Get after setting\n\tage = GetIntField(bob, Age, 25)\n\tuassert.Equal(t, 30, age)\n}\n\nfunc TestBoolFields(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(charlie))\n\n\t// Get before setting\n\thiring := GetBoolField(charlie, AvailableForHiring, false)\n\tuassert.Equal(t, false, hiring)\n\n\t// Set\n\tupdated := SetBoolField(AvailableForHiring, true)\n\tuassert.Equal(t, updated, false)\n\n\t// Update the key\n\tupdated = SetBoolField(AvailableForHiring, true)\n\tuassert.Equal(t, updated, true)\n\n\t// Get after setting\n\thiring = GetBoolField(charlie, AvailableForHiring, false)\n\tuassert.Equal(t, true, hiring)\n}\n\nfunc TestMultipleProfiles(t *testing.T) {\n\t// Set profile for user1\n\tstd.TestSetRealm(std.NewUserRealm(user1))\n\tupdated := SetStringField(DisplayName, \"User One\")\n\tuassert.Equal(t, updated, false)\n\n\t// Set profile for user2\n\tstd.TestSetRealm(std.NewUserRealm(user2))\n\tupdated = SetStringField(DisplayName, \"User Two\")\n\tuassert.Equal(t, updated, false)\n\n\t// Get profiles\n\tstd.TestSetRealm(std.NewUserRealm(user1)) // Switch back to user1\n\tname1 := GetStringField(user1, DisplayName, \"anon\")\n\tstd.TestSetRealm(std.NewUserRealm(user2)) // Switch back to user2\n\tname2 := GetStringField(user2, DisplayName, \"anon\")\n\n\tuassert.Equal(t, \"User One\", name1)\n\tuassert.Equal(t, \"User Two\", name2)\n}\n\nfunc TestArbitraryStringField(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(user1))\n\n\t// Set arbitrary string field\n\tupdated := SetStringField(\"MyEmail\", \"my@email.com\")\n\tuassert.Equal(t, updated, false)\n\n\tval := GetStringField(user1, \"MyEmail\", \"\")\n\tuassert.Equal(t, val, \"my@email.com\")\n}\n\nfunc TestArbitraryIntField(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(user1))\n\n\t// Set arbitrary int field\n\tupdated := SetIntField(\"MyIncome\", 100_000)\n\tuassert.Equal(t, updated, false)\n\n\tval := GetIntField(user1, \"MyIncome\", 0)\n\tuassert.Equal(t, val, 100_000)\n}\n\nfunc TestArbitraryBoolField(t *testing.T) {\n\tstd.TestSetRealm(std.NewUserRealm(user1))\n\n\t// Set arbitrary int field\n\tupdated := SetBoolField(\"IsWinner\", true)\n\tuassert.Equal(t, updated, false)\n\n\tval := GetBoolField(user1, \"IsWinner\", false)\n\tuassert.Equal(t, val, true)\n}\n"},{"name":"render.gno","body":"package profile\n\nimport (\n\t\"bytes\"\n\t\"net/url\"\n\t\"std\"\n\n\t\"gno.land/p/demo/mux\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nconst (\n\tBaseURL = \"/r/demo/profile\"\n\tSetStringFieldURL = BaseURL + \"$help\u0026func=SetStringField\u0026field=%s\"\n\tSetIntFieldURL = BaseURL + \"$help\u0026func=SetIntField\u0026field=%s\"\n\tSetBoolFieldURL = BaseURL + \"$help\u0026func=SetBoolField\u0026field=%s\"\n\tViewAllFieldsURL = BaseURL + \":u/%s\"\n\tViewFieldURL = BaseURL + \":f/%s/%s\"\n)\n\nfunc homeHandler(res *mux.ResponseWriter, req *mux.Request) {\n\tvar b bytes.Buffer\n\n\tb.WriteString(\"## Setters\\n\")\n\tfor field := range stringFields {\n\t\tlink := ufmt.Sprintf(SetStringFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- [Set %s](%s)\\n\", field, link))\n\t}\n\n\tfor field := range intFields {\n\t\tlink := ufmt.Sprintf(SetIntFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- [Set %s](%s)\\n\", field, link))\n\t}\n\n\tfor field := range boolFields {\n\t\tlink := ufmt.Sprintf(SetBoolFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- [Set %s Field](%s)\\n\", field, link))\n\t}\n\n\tb.WriteString(\"\\n---\\n\\n\")\n\n\tres.Write(b.String())\n}\n\nfunc profileHandler(res *mux.ResponseWriter, req *mux.Request) {\n\tvar b bytes.Buffer\n\taddr := req.GetVar(\"addr\")\n\n\tb.WriteString(ufmt.Sprintf(\"# Profile %s\\n\", addr))\n\n\taddress := std.Address(addr)\n\n\tfor field := range stringFields {\n\t\tvalue := GetStringField(address, field, \"n/a\")\n\t\tlink := ufmt.Sprintf(SetStringFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- %s: %s [Edit](%s)\\n\", field, value, link))\n\t}\n\n\tfor field := range intFields {\n\t\tvalue := GetIntField(address, field, 0)\n\t\tlink := ufmt.Sprintf(SetIntFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- %s: %d [Edit](%s)\\n\", field, value, link))\n\t}\n\n\tfor field := range boolFields {\n\t\tvalue := GetBoolField(address, field, false)\n\t\tlink := ufmt.Sprintf(SetBoolFieldURL, field)\n\t\tb.WriteString(ufmt.Sprintf(\"- %s: %t [Edit](%s)\\n\", field, value, link))\n\t}\n\n\tres.Write(b.String())\n}\n\nfunc fieldHandler(res *mux.ResponseWriter, req *mux.Request) {\n\tvar b bytes.Buffer\n\taddr := req.GetVar(\"addr\")\n\tfield := req.GetVar(\"field\")\n\n\tb.WriteString(ufmt.Sprintf(\"# Field %s for %s\\n\", field, addr))\n\n\taddress := std.Address(addr)\n\tvalue := \"n/a\"\n\tvar editLink string\n\n\tif _, ok := stringFields[field]; ok {\n\t\tvalue = ufmt.Sprintf(\"%s\", GetStringField(address, field, \"n/a\"))\n\t\teditLink = ufmt.Sprintf(SetStringFieldURL+\"\u0026addr=%s\u0026value=%s\", field, addr, url.QueryEscape(value))\n\t} else if _, ok := intFields[field]; ok {\n\t\tvalue = ufmt.Sprintf(\"%d\", GetIntField(address, field, 0))\n\t\teditLink = ufmt.Sprintf(SetIntFieldURL+\"\u0026addr=%s\u0026value=%s\", field, addr, value)\n\t} else if _, ok := boolFields[field]; ok {\n\t\tvalue = ufmt.Sprintf(\"%t\", GetBoolField(address, field, false))\n\t\teditLink = ufmt.Sprintf(SetBoolFieldURL+\"\u0026addr=%s\u0026value=%s\", field, addr, value)\n\t}\n\n\tb.WriteString(ufmt.Sprintf(\"- %s: %s [Edit](%s)\\n\", field, value, editLink))\n\n\tres.Write(b.String())\n}\n\nfunc Render(path string) string {\n\treturn router.Render(path)\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":"releases_example","path":"gno.land/r/demo/releases_example","files":[{"name":"dummy.gno","body":"package releases_example\n\nfunc init() {\n\t// dummy example data\n\tchangelog.NewRelease(\n\t\t\"v1\",\n\t\t\"r/demo/examples_example_v1\",\n\t\t\"initial release\",\n\t)\n\tchangelog.NewRelease(\n\t\t\"v2\",\n\t\t\"r/demo/examples_example_v2\",\n\t\t\"various improvements\",\n\t)\n}\n"},{"name":"example.gno","body":"// this package demonstrates a way to manage contract releases.\npackage releases_example\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/releases\"\n)\n\nvar (\n\tchangelog = releases.NewChangelog(\"example_app\")\n\tadmin = std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\") // @administrator\n)\n\nfunc init() {\n\t// FIXME: admin = std.GetCreator()\n}\n\nfunc NewRelease(name, url, notes string) {\n\tcaller := std.OriginCaller()\n\tif caller != admin {\n\t\tpanic(\"restricted area\")\n\t}\n\tchangelog.NewRelease(name, url, notes)\n}\n\nfunc UpdateAdmin(address std.Address) {\n\tcaller := std.OriginCaller()\n\tif caller != admin {\n\t\tpanic(\"restricted area\")\n\t}\n\tadmin = address\n}\n\nfunc Render(path string) string {\n\treturn changelog.Render(path)\n}\n"},{"name":"releases0_filetest.gno","body":"package main\n\nimport (\n\t\"gno.land/p/demo/releases\"\n)\n\nfunc main() {\n\tprintln(\"-----------\")\n\tchangelog := releases.NewChangelog(\"example\")\n\tprint(changelog.Render(\"\"))\n\n\tprintln(\"-----------\")\n\tchangelog.NewRelease(\"v1\", \"r/blahblah\", \"* initial version\")\n\tprint(changelog.Render(\"\"))\n\n\tprintln(\"-----------\")\n\tchangelog.NewRelease(\"v2\", \"r/blahblah2\", \"* various improvements\\n* new shiny logo\")\n\tprint(changelog.Render(\"\"))\n\n\tprintln(\"-----------\")\n\tprint(changelog.Latest().Render())\n}\n\n// Output:\n// -----------\n// # example\n//\n// -----------\n// # example\n//\n// ## [example v1 (latest)](r/blahblah)\n//\n// * initial version\n//\n// -----------\n// # example\n//\n// ## [example v2 (latest)](r/blahblah2)\n//\n// * various improvements\n// * new shiny logo\n//\n// ## [example v1](r/blahblah)\n//\n// * initial version\n//\n// -----------\n// ## [example v2 (latest)](r/blahblah2)\n//\n// * various improvements\n// * new shiny logo\n"},{"name":"releases1_filetest.gno","body":"package main\n\nimport (\n\t\"gno.land/r/demo/releases_example\"\n)\n\nfunc main() {\n\tprintln(\"-----------\")\n\tprint(releases_example.Render(\"\"))\n\n\tprintln(\"-----------\")\n\tprint(releases_example.Render(\"v1\"))\n\n\tprintln(\"-----------\")\n\tprint(releases_example.Render(\"v42\"))\n}\n\n// Output:\n// -----------\n// # example_app\n//\n// ## [example_app v2 (latest)](r/demo/examples_example_v2)\n//\n// various improvements\n//\n// ## [example_app v1](r/demo/examples_example_v1)\n//\n// initial release\n//\n// -----------\n// ## [example_app v1](r/demo/examples_example_v1)\n//\n// initial release\n//\n// -----------\n// no such release\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":"tamagotchi","path":"gno.land/r/demo/tamagotchi","files":[{"name":"realm.gno","body":"package tamagotchi\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/tamagotchi\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar t *tamagotchi.Tamagotchi\n\nfunc init() {\n\tReset(\"gnome#0\")\n}\n\nfunc Reset(optionalName string) string {\n\tname := optionalName\n\tif name == \"\" {\n\t\theight := std.GetHeight()\n\t\tname = ufmt.Sprintf(\"gnome#%d\", height)\n\t}\n\n\tt = tamagotchi.New(name)\n\n\treturn ufmt.Sprintf(\"A new tamagotchi is born. Their name is %s %s.\", t.Name(), t.Face())\n}\n\nfunc Feed() string {\n\tt.Feed()\n\treturn t.Markdown()\n}\n\nfunc Play() string {\n\tt.Play()\n\treturn t.Markdown()\n}\n\nfunc Heal() string {\n\tt.Heal()\n\treturn t.Markdown()\n}\n\nfunc Render(path string) string {\n\ttama := t.Markdown()\n\tlinks := `Actions:\n* [Feed](/r/demo/tamagotchi$help\u0026func=Feed)\n* [Play](/r/demo/tamagotchi$help\u0026func=Play)\n* [Heal](/r/demo/tamagotchi$help\u0026func=Heal)\n* [Reset](/r/demo/tamagotchi$help\u0026func=Reset)\n`\n\n\treturn tama + \"\\n\\n\" + links\n}\n"},{"name":"z0_filetest.gno","body":"package main\n\nimport (\n\t\"gno.land/r/demo/tamagotchi\"\n)\n\nfunc main() {\n\ttamagotchi.Reset(\"tamagnotchi\")\n\tprintln(tamagotchi.Render(\"\"))\n}\n\n// Output:\n// # tamagnotchi 😃\n//\n// * age: 0\n// * hunger: 50\n// * happiness: 50\n// * health: 50\n// * sleepy: 0\n//\n// Actions:\n// * [Feed](/r/demo/tamagotchi$help\u0026func=Feed)\n// * [Play](/r/demo/tamagotchi$help\u0026func=Play)\n// * [Heal](/r/demo/tamagotchi$help\u0026func=Heal)\n// * [Reset](/r/demo/tamagotchi$help\u0026func=Reset)\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"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}} @@ -107,25 +107,25 @@ {"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"todolistrealm","path":"gno.land/r/demo/todolist","files":[{"name":"todolist.gno","body":"package todolistrealm\n\nimport (\n\t\"bytes\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/seqid\"\n\t\"gno.land/p/demo/todolist\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// State variables\nvar (\n\ttodolistTree *avl.Tree\n\ttlid seqid.ID\n)\n\n// Constructor\nfunc init() {\n\ttodolistTree = avl.NewTree()\n}\n\nfunc NewTodoList(title string) (int, string) {\n\t// Create new Todolist\n\ttl := todolist.NewTodoList(title)\n\t// Update AVL tree with new state\n\ttlid.Next()\n\ttodolistTree.Set(strconv.Itoa(int(tlid)), tl)\n\treturn int(tlid), \"created successfully\"\n}\n\nfunc AddTask(todolistID int, title string) string {\n\t// Get Todolist from AVL tree\n\ttl, ok := todolistTree.Get(strconv.Itoa(todolistID))\n\tif !ok {\n\t\tpanic(\"Todolist not found\")\n\t}\n\n\t// get the number of tasks in the todolist\n\tid := tl.(*todolist.TodoList).Tasks.Size()\n\n\t// create the task\n\ttask := todolist.NewTask(title)\n\n\t// Cast raw data from tree into Todolist struct\n\ttl.(*todolist.TodoList).AddTask(id, task)\n\n\treturn \"task added successfully\"\n}\n\nfunc ToggleTaskStatus(todolistID int, taskID int) string {\n\t// Get Todolist from AVL tree\n\ttl, ok := todolistTree.Get(strconv.Itoa(todolistID))\n\tif !ok {\n\t\tpanic(\"Todolist not found\")\n\t}\n\n\t// Get the task from the todolist\n\ttask, found := tl.(*todolist.TodoList).Tasks.Get(strconv.Itoa(taskID))\n\tif !found {\n\t\tpanic(\"Task not found\")\n\t}\n\n\t// Change the status of the task\n\ttodolist.ToggleTaskStatus(task.(*todolist.Task))\n\n\treturn \"task status changed successfully\"\n}\n\nfunc RemoveTask(todolistID int, taskID int) string {\n\t// Get Todolist from AVL tree\n\ttl, ok := todolistTree.Get(strconv.Itoa(todolistID))\n\tif !ok {\n\t\tpanic(\"Todolist not found\")\n\t}\n\n\t// Get the task from the todolist\n\t_, ok = tl.(*todolist.TodoList).Tasks.Get(strconv.Itoa(taskID))\n\tif !ok {\n\t\tpanic(\"Task not found\")\n\t}\n\n\t// Change the status of the task\n\ttl.(*todolist.TodoList).RemoveTask(strconv.Itoa(taskID))\n\n\treturn \"task status changed successfully\"\n}\n\nfunc RemoveTodoList(todolistID int) string {\n\t// Get Todolist from AVL tree\n\t_, ok := todolistTree.Get(strconv.Itoa(todolistID))\n\tif !ok {\n\t\tpanic(\"Todolist not found\")\n\t}\n\n\t// Remove the todolist\n\ttodolistTree.Remove(strconv.Itoa(todolistID))\n\n\treturn \"Todolist removed successfully\"\n}\n\nfunc Render(path string) string {\n\tif path == \"\" {\n\t\treturn renderHomepage()\n\t}\n\n\treturn \"unknown page\"\n}\n\nfunc renderHomepage() string {\n\t// Define empty buffer\n\tvar b bytes.Buffer\n\n\tb.WriteString(\"# Welcome to ToDolist\\n\\n\")\n\n\t// If no todolists have been created\n\tif todolistTree.Size() == 0 {\n\t\tb.WriteString(\"### No todolists available currently!\")\n\t\treturn b.String()\n\t}\n\n\t// Iterate through AVL tree\n\ttodolistTree.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\t// cast raw data from tree into Todolist struct\n\t\ttl := value.(*todolist.TodoList)\n\n\t\t// Add Todolist name\n\t\tb.WriteString(\n\t\t\tufmt.Sprintf(\n\t\t\t\t\"## Todolist #%s: %s\\n\",\n\t\t\t\tkey, // Todolist ID\n\t\t\t\ttl.GetTodolistTitle(),\n\t\t\t),\n\t\t)\n\n\t\t// Add Todolist owner\n\t\tb.WriteString(\n\t\t\tufmt.Sprintf(\n\t\t\t\t\"#### Todolist owner : %s\\n\",\n\t\t\t\ttl.GetTodolistOwner(),\n\t\t\t),\n\t\t)\n\n\t\t// List all todos that are currently Todolisted\n\t\tif todos := tl.GetTasks(); len(todos) \u003e 0 {\n\t\t\tb.WriteString(\n\t\t\t\tufmt.Sprintf(\"Currently Todo tasks: %d\\n\\n\", len(todos)),\n\t\t\t)\n\n\t\t\tfor index, todo := range todos {\n\t\t\t\tb.WriteString(\n\t\t\t\t\tufmt.Sprintf(\"#%d - %s \", index, todo.Title),\n\t\t\t\t)\n\t\t\t\t// displays a checked box if task is marked as done, an empty box if not\n\t\t\t\tif todo.Done {\n\t\t\t\t\tb.WriteString(\n\t\t\t\t\t\t\"☑\\n\\n\",\n\t\t\t\t\t)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tb.WriteString(\n\t\t\t\t\t\"☐\\n\\n\",\n\t\t\t\t)\n\t\t\t}\n\t\t} else {\n\t\t\tb.WriteString(\"No tasks in this list currently\\n\")\n\t\t}\n\n\t\tb.WriteString(\"\\n\")\n\t\treturn false\n\t})\n\n\treturn b.String()\n}\n"},{"name":"todolist_test.gno","body":"package todolistrealm\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/todolist\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\tnode interface{}\n\ttdl *todolist.TodoList\n)\n\nfunc TestNewTodoList(t *testing.T) {\n\ttitle := \"My Todo List\"\n\ttlid, _ := NewTodoList(title)\n\tuassert.Equal(t, 1, tlid, \"tlid does not match\")\n\n\t// get the todolist node from the tree\n\tnode, _ = todolistTree.Get(strconv.Itoa(tlid))\n\t// convert the node to a TodoList struct\n\ttdl = node.(*todolist.TodoList)\n\n\tuassert.Equal(t, title, tdl.Title, \"title does not match\")\n\tuassert.Equal(t, 1, tlid, \"tlid does not match\")\n\tuassert.Equal(t, tdl.Owner.String(), std.OriginCaller().String(), \"owner does not match\")\n\tuassert.Equal(t, 0, len(tdl.GetTasks()), \"Expected no tasks in the todo list\")\n}\n\nfunc TestAddTask(t *testing.T) {\n\tAddTask(1, \"Task 1\")\n\n\ttasks := tdl.GetTasks()\n\tuassert.Equal(t, 1, len(tasks), \"total task does not match\")\n\tuassert.Equal(t, \"Task 1\", tasks[0].Title, \"task title does not match\")\n\tuassert.False(t, tasks[0].Done, \"Expected task to be not done\")\n}\n\nfunc TestToggleTaskStatus(t *testing.T) {\n\tToggleTaskStatus(1, 0)\n\ttask := tdl.GetTasks()[0]\n\tuassert.True(t, task.Done, \"Expected task to be done, but it is not marked as done\")\n\n\tToggleTaskStatus(1, 0)\n\tuassert.False(t, task.Done, \"Expected task to be not done, but it is marked as done\")\n}\n\nfunc TestRemoveTask(t *testing.T) {\n\tRemoveTask(1, 0)\n\ttasks := tdl.GetTasks()\n\tuassert.Equal(t, 0, len(tasks), \"Expected no tasks in the todo list\")\n}\n\nfunc TestRemoveTodoList(t *testing.T) {\n\tRemoveTodoList(1)\n\tuassert.Equal(t, 0, todolistTree.Size(), \"Expected no tasks in the todo list\")\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":"types","path":"gno.land/r/demo/types","files":[{"name":"types.gno","body":"// package to test types behavior in various conditions (TXs, imports).\npackage types\n\nimport (\n\t\"errors\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\nvar (\n\tgInt int = -42\n\tgUint uint = 42\n\tgString string = \"a string\"\n\tgStringSlice []string = []string{\"a\", \"string\", \"slice\"}\n\tgError error = errors.New(\"an error\")\n\tgIntSlice []int = []int{-42, 0, 42}\n\tgUintSlice []uint = []uint{0, 42, 84}\n\tgTree avl.Tree\n\t// gInterface = interface{}{-42, \"a string\", uint(42)}\n)\n\nfunc init() {\n\tgTree.Set(\"a\", \"content of A\")\n\tgTree.Set(\"b\", \"content of B\")\n}\n\nfunc Noop() {}\nfunc RetTimeNow() time.Time { return time.Now() }\nfunc RetString() string { return gString }\nfunc RetStringPointer() *string { return \u0026gString }\nfunc RetUint() uint { return gUint }\nfunc RetInt() int { return gInt }\nfunc RetUintPointer() *uint { return \u0026gUint }\nfunc RetIntPointer() *int { return \u0026gInt }\nfunc RetTree() avl.Tree { return gTree }\nfunc RetIntSlice() []int { return gIntSlice }\nfunc RetUintSlice() []uint { return gUintSlice }\nfunc RetStringSlice() []string { return gStringSlice }\nfunc RetError() error { return gError }\nfunc Panic() { panic(\"PANIC!\") }\n\n// TODO: floats\n// TODO: typed errors\n// TODO: ret interface\n// TODO: recover\n// TODO: take types as input\n\nfunc Render(path string) string {\n\treturn \"package to test data types.\"\n}\n"},{"name":"types_test.gno","body":"package types\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":"ui","path":"gno.land/r/demo/ui","files":[{"name":"ui.gno","body":"package ui\n\nimport \"gno.land/p/demo/ui\"\n\nfunc Render(path string) string {\n\t// TODO: build this realm as a demo one with one page per feature.\n\n\t// TODO: pagination\n\t// TODO: non-standard markdown\n\t// TODO: error, warn\n\t// TODO: header\n\t// TODO: HTML\n\t// TODO: toc\n\t// TODO: forms\n\t// TODO: comments\n\n\tdom := ui.DOM{\n\t\tPrefix: \"r/demo/ui:\",\n\t}\n\n\tdom.Title = \"UI Demo\"\n\n\tdom.Header.Append(ui.Breadcrumb{\n\t\tui.Link{Text: \"foo\", Path: \"foo\"},\n\t\tui.Link{Text: \"bar\", Path: \"foo/bar\"},\n\t})\n\n\tdom.Body.Append(\n\t\tui.Paragraph(\"Simple UI demonstration.\"),\n\t\tui.BulletList{\n\t\t\tui.Text(\"a text\"),\n\t\t\tui.Link{Text: \"a relative link\", Path: \"foobar\"},\n\t\t\tui.Text(\"another text\"),\n\t\t\t// ui.H1(\"a H1 text\"),\n\t\t\tui.Bold(\"a bold text\"),\n\t\t\tui.Italic(\"italic text\"),\n\t\t\tui.Text(\"raw markdown with **bold** text in the middle.\"),\n\t\t\tui.Code(\"some inline code\"),\n\t\t\tui.Link{Text: \"a remote link\", URL: \"https://gno.land\"},\n\t\t},\n\t)\n\n\tdom.Footer.Append(ui.Text(\"I'm the footer.\"))\n\tdom.Body.Append(ui.Text(\"another string.\"))\n\tdom.Body.Append(ui.Paragraph(\"a paragraph.\"), ui.HR{})\n\n\treturn dom.String()\n}\n"},{"name":"ui_test.gno","body":"package ui\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestRender(t *testing.T) {\n\tgot := Render(\"\")\n\texpected := \"# UI Demo\\n\\n[foo](r/demo/ui:foo) / [bar](r/demo/ui:foo/bar)\\n\\n\\nSimple UI demonstration.\\n\\n- a text\\n- [a relative link](r/demo/ui:foobar)\\n- another text\\n- **a bold text**\\n- _italic text_\\n- raw markdown with **bold** text in the middle.\\n- `some inline code`\\n- [a remote link](https://gno.land)\\n\\nanother string.\\n\\na paragraph.\\n\\n\\n---\\n\\n\\nI'm the footer.\\n\\n\"\n\tuassert.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":"userbook","path":"gno.land/r/demo/userbook","files":[{"name":"userbook.gno","body":"// This realm demonstrates a small userbook system working with gnoweb\npackage userbook\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/mux\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype Signup struct {\n\taccount string\n\theight int64\n}\n\n// signups - keep a slice of signed up addresses efficient pagination\nvar signups []Signup\n\n// tracker - keep track of who signed up\nvar (\n\ttracker *avl.Tree\n\trouter *mux.Router\n)\n\nconst (\n\tdefaultPageSize = 20\n\tpathArgument = \"number\"\n\tsubPath = \"page/{\" + pathArgument + \"}\"\n\tsignUpEvent = \"SignUp\"\n)\n\nfunc init() {\n\t// Set up tracker tree\n\ttracker = avl.NewTree()\n\n\t// Set up route handling\n\trouter = mux.NewRouter()\n\trouter.HandleFunc(\"\", renderHelper)\n\trouter.HandleFunc(subPath, renderHelper)\n\n\t// Sign up the deployer\n\tSignUp()\n}\n\nfunc SignUp() string {\n\t// Get transaction caller\n\tcaller := std.PreviousRealm().Addr().String()\n\theight := std.GetHeight()\n\n\t// Check if the user is already signed up\n\tif _, exists := tracker.Get(caller); exists {\n\t\tpanic(caller + \" is already signed up!\")\n\t}\n\n\t// Sign up the user\n\ttracker.Set(caller, struct{}{})\n\tsignup := Signup{\n\t\tcaller,\n\t\theight,\n\t}\n\n\tsignups = append(signups, signup)\n\tstd.Emit(signUpEvent, \"SignedUpAccount\", signup.account)\n\n\treturn ufmt.Sprintf(\"%s added to userbook up at block #%d!\", signup.account, signup.height)\n}\n\nfunc GetSignupsInRange(page, pageSize int) ([]Signup, int) {\n\tif page \u003c 1 {\n\t\tpanic(\"page number cannot be less than 1\")\n\t}\n\n\tif pageSize \u003c 1 || pageSize \u003e 50 {\n\t\tpanic(\"page size must be from 1 to 50\")\n\t}\n\n\t// Pagination\n\t// Calculate indexes\n\tstartIndex := (page - 1) * pageSize\n\tendIndex := startIndex + pageSize\n\n\t// If page does not contain any users\n\tif startIndex \u003e= len(signups) {\n\t\treturn nil, -1\n\t}\n\n\t// If page contains fewer users than the page size\n\tif endIndex \u003e len(signups) {\n\t\tendIndex = len(signups)\n\t}\n\n\treturn signups[startIndex:endIndex], endIndex\n}\n\nfunc renderHelper(res *mux.ResponseWriter, req *mux.Request) {\n\ttotalSignups := len(signups)\n\tres.Write(\"# Welcome to UserBook!\\n\\n\")\n\n\t// Get URL parameter\n\tpage, err := strconv.Atoi(req.GetVar(\"number\"))\n\tif err != nil {\n\t\tpage = 1 // render first page on bad input\n\t}\n\n\t// Fetch paginated signups\n\tfetchedSignups, endIndex := GetSignupsInRange(page, defaultPageSize)\n\t// Handle empty page case\n\tif len(fetchedSignups) == 0 {\n\t\tres.Write(\"No users on this page!\\n\\n\")\n\t\tres.Write(\"---\\n\\n\")\n\t\tres.Write(\"[Back to Page #1](/r/demo/userbook:page/1)\\n\\n\")\n\t\treturn\n\t}\n\n\t// Write page title\n\tres.Write(ufmt.Sprintf(\"## UserBook - Page #%d:\\n\\n\", page))\n\n\t// Write signups\n\tpageStartIndex := defaultPageSize * (page - 1)\n\tfor i, signup := range fetchedSignups {\n\t\tout := ufmt.Sprintf(\"#### User #%d - %s - signed up at Block #%d\\n\", pageStartIndex+i, signup.account, signup.height)\n\t\tres.Write(out)\n\t}\n\n\tres.Write(\"---\\n\\n\")\n\n\t// Write UserBook info\n\tlatestSignupIndex := totalSignups - 1\n\tres.Write(ufmt.Sprintf(\"#### Total users: %d\\n\", totalSignups))\n\tres.Write(ufmt.Sprintf(\"#### Latest signup: User #%d at Block #%d\\n\", latestSignupIndex, signups[latestSignupIndex].height))\n\n\tres.Write(\"---\\n\\n\")\n\n\t// Write page number\n\tres.Write(ufmt.Sprintf(\"You're viewing page #%d\", page))\n\n\t// Write navigation buttons\n\tvar prevPage string\n\tvar nextPage string\n\t// If we are on any page that is not the first page\n\tif page \u003e 1 {\n\t\tprevPage = ufmt.Sprintf(\" - [Previous page](/r/demo/userbook:page/%d)\", page-1)\n\t}\n\n\t// If there are more pages after the current one\n\tif endIndex \u003c totalSignups {\n\t\tnextPage = ufmt.Sprintf(\" - [Next page](/r/demo/userbook:page/%d)\\n\\n\", page+1)\n\t}\n\n\tres.Write(prevPage)\n\tres.Write(nextPage)\n}\n\nfunc Render(path string) string {\n\treturn router.Render(path)\n}\n"},{"name":"userbook_test.gno","body":"package userbook\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nfunc TestRender(t *testing.T) {\n\t// Sign up 20 users + deployer\n\tfor i := 0; i \u003c 20; i++ {\n\t\taddrName := ufmt.Sprintf(\"test%d\", i)\n\t\tcaller := testutils.TestAddress(addrName)\n\t\tstd.TestSetOriginCaller(caller)\n\t\tSignUp()\n\t}\n\n\ttestCases := []struct {\n\t\tname string\n\t\tnextPage bool\n\t\tprevPage bool\n\t\tpath string\n\t\texpectedNumberOfUsers int\n\t}{\n\t\t{\n\t\t\tname: \"1st page render\",\n\t\t\tnextPage: true,\n\t\t\tprevPage: false,\n\t\t\tpath: \"page/1\",\n\t\t\texpectedNumberOfUsers: 20,\n\t\t},\n\t\t{\n\t\t\tname: \"2nd page render\",\n\t\t\tnextPage: false,\n\t\t\tprevPage: true,\n\t\t\tpath: \"page/2\",\n\t\t\texpectedNumberOfUsers: 1,\n\t\t},\n\t\t{\n\t\t\tname: \"Invalid path render\",\n\t\t\tnextPage: true,\n\t\t\tprevPage: false,\n\t\t\tpath: \"page/invalidtext\",\n\t\t\texpectedNumberOfUsers: 20,\n\t\t},\n\t\t{\n\t\t\tname: \"Empty Page\",\n\t\t\tnextPage: false,\n\t\t\tprevPage: false,\n\t\t\tpath: \"page/1000\",\n\t\t\texpectedNumberOfUsers: 0,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := Render(tc.path)\n\t\t\tnumUsers := countUsers(got)\n\n\t\t\tif tc.prevPage \u0026\u0026 !strings.Contains(got, \"Previous page\") {\n\t\t\t\tt.Fatalf(\"expected to find Previous page, didn't find it\")\n\t\t\t}\n\t\t\tif tc.nextPage \u0026\u0026 !strings.Contains(got, \"Next page\") {\n\t\t\t\tt.Fatalf(\"expected to find Next page, didn't find it\")\n\t\t\t}\n\n\t\t\tif tc.expectedNumberOfUsers != numUsers {\n\t\t\t\tt.Fatalf(\"expected %d, got %d users\", tc.expectedNumberOfUsers, numUsers)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc countUsers(input string) int {\n\treturn strings.Count(input, \"#### User #\")\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":"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/users\"\n)\n\nvar (\n\tbanker *grc20.Banker = grc20.NewBanker(\"wrapped GNOT\", \"wugnot\", 0)\n\tToken = banker.Token()\n)\n\nconst (\n\tugnotMinDeposit uint64 = 1000\n\twugnotMinDeposit uint64 = 1\n)\n\nfunc Deposit() {\n\tcaller := std.PreviousRealm().Addr()\n\tsent := std.OriginSend()\n\tamount := sent.AmountOf(\"ugnot\")\n\n\trequire(uint64(amount) \u003e= ugnotMinDeposit, ufmt.Sprintf(\"Deposit below minimum: %d/%d ugnot.\", amount, ugnotMinDeposit))\n\tcheckErr(banker.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.PreviousRealm().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(banker.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 banker.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\tcheckErr(Token.Transfer(toAddr, amount))\n}\n\nfunc Approve(spender pusers.AddressOrName, amount uint64) {\n\tspenderAddr := users.Resolve(spender)\n\tcheckErr(Token.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(Token.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.TestSetOriginPkgAddr(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.TestSetOriginCaller(addr1)\n\tstd.TestSetOriginSend(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.TestSetOriginCaller(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=200000000 |\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=200000000 |\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=200004242 |\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":""}} -{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"gnoblog","path":"gno.land/r/gnoland/blog","files":[{"name":"admin.gno","body":"package gnoblog\n\nimport (\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/r/gov/dao/bridge\"\n)\n\nvar (\n\tadminAddr std.Address\n\tmoderatorList avl.Tree\n\tcommenterList avl.Tree\n\tinPause bool\n)\n\nfunc init() {\n\t// adminAddr = std.OriginCaller() // FIXME: find a way to use this from the main's genesis.\n\tadminAddr = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"\n}\n\nfunc AdminSetAdminAddr(addr std.Address) {\n\tassertIsAdmin()\n\tadminAddr = addr\n}\n\nfunc AdminSetInPause(state bool) {\n\tassertIsAdmin()\n\tinPause = state\n}\n\nfunc AdminAddModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), true)\n}\n\nfunc AdminRemoveModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), false) // FIXME: delete instead?\n}\n\nfunc NewPostExecutor(slug, title, body, publicationDate, authors, tags string) dao.Executor {\n\tcallback := func() error {\n\t\taddPost(std.PreviousRealm().Addr(), slug, title, body, publicationDate, authors, tags)\n\n\t\treturn nil\n\t}\n\n\treturn bridge.GovDAO().NewGovDAOExecutor(callback)\n}\n\nfunc ModAddPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\tcaller := std.OriginCaller()\n\taddPost(caller, slug, title, body, publicationDate, authors, tags)\n}\n\nfunc addPost(caller std.Address, slug, title, body, publicationDate, authors, tags string) {\n\tvar tagList []string\n\tif tags != \"\" {\n\t\ttagList = strings.Split(tags, \",\")\n\t}\n\tvar authorList []string\n\tif authors != \"\" {\n\t\tauthorList = strings.Split(authors, \",\")\n\t}\n\n\terr := b.NewPost(caller, slug, title, body, publicationDate, authorList, tagList)\n\n\tcheckErr(err)\n}\n\nfunc ModEditPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.GetPost(slug).Update(title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc ModRemovePost(slug string) {\n\tassertIsModerator()\n\n\tb.RemovePost(slug)\n}\n\nfunc ModAddCommenter(addr std.Address) {\n\tassertIsModerator()\n\tcommenterList.Set(addr.String(), true)\n}\n\nfunc ModDelCommenter(addr std.Address) {\n\tassertIsModerator()\n\tcommenterList.Set(addr.String(), false) // FIXME: delete instead?\n}\n\nfunc ModDelComment(slug string, index int) {\n\tassertIsModerator()\n\n\terr := b.GetPost(slug).DeleteComment(index)\n\tcheckErr(err)\n}\n\nfunc isAdmin(addr std.Address) bool {\n\treturn addr == adminAddr\n}\n\nfunc isModerator(addr std.Address) bool {\n\t_, found := moderatorList.Get(addr.String())\n\treturn found\n}\n\nfunc isCommenter(addr std.Address) bool {\n\t_, found := commenterList.Get(addr.String())\n\treturn found\n}\n\nfunc assertIsAdmin() {\n\tcaller := std.OriginCaller()\n\tif !isAdmin(caller) {\n\t\tpanic(\"access restricted.\")\n\t}\n}\n\nfunc assertIsModerator() {\n\tcaller := std.OriginCaller()\n\tif isAdmin(caller) || isModerator(caller) {\n\t\treturn\n\t}\n\tpanic(\"access restricted\")\n}\n\nfunc assertIsCommenter() {\n\tcaller := std.OriginCaller()\n\tif isAdmin(caller) || isModerator(caller) || isCommenter(caller) {\n\t\treturn\n\t}\n\tpanic(\"access restricted\")\n}\n\nfunc assertNotInPause() {\n\tif inPause {\n\t\tpanic(\"access restricted (pause)\")\n\t}\n}\n"},{"name":"gnoblog.gno","body":"package gnoblog\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/blog\"\n)\n\nvar b = \u0026blog.Blog{\n\tTitle: \"Gnoland's Blog\",\n\tPrefix: \"/r/gnoland/blog:\",\n}\n\nfunc AddComment(postSlug, comment string) {\n\tassertIsCommenter()\n\tassertNotInPause()\n\n\tcaller := std.OriginCaller()\n\terr := b.GetPost(postSlug).AddComment(caller, comment)\n\tcheckErr(err)\n}\n\nfunc Render(path string) string {\n\treturn b.Render(path)\n}\n\nfunc RenderLastPostsWidget(limit int) string {\n\treturn b.RenderLastPostsWidget(limit)\n}\n\nfunc PostExists(slug string) bool {\n\tif b.GetPost(slug) == nil {\n\t\treturn false\n\t}\n\treturn true\n}\n"},{"name":"gnoblog_test.gno","body":"package gnoblog\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestPackage(t *testing.T) {\n\tstd.TestSetOriginCaller(std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"))\n\n\tauthor := std.OriginCaller()\n\n\t// by default, no posts.\n\t{\n\t\tgot := Render(\"\")\n\t\texpected := `\n# Gnoland's Blog\n\nNo posts.\n`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// create two posts, list post.\n\t{\n\t\tModAddPost(\"slug1\", \"title1\", \"body1\", \"2022-05-20T13:17:22Z\", \"moul\", \"tag1,tag2\")\n\t\tModAddPost(\"slug2\", \"title2\", \"body2\", \"2022-05-20T13:17:23Z\", \"moul\", \"tag1,tag3\")\n\t\tgot := Render(\"\")\n\t\texpected := `\n\t# Gnoland's Blog\n\n\u003cdiv class='columns-3'\u003e\u003cdiv\u003e\n\n### [title2](/r/gnoland/blog:p/slug2)\n 20 May 2022\n\u003c/div\u003e\u003cdiv\u003e\n\n### [title1](/r/gnoland/blog:p/slug1)\n 20 May 2022\n\u003c/div\u003e\u003c/div\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// view post.\n\t{\n\t\tgot := Render(\"p/slug2\")\n\t\texpected := `\n\u003cmain class='gno-tmpl-page'\u003e\n\n# title2\n\nbody2\n\n---\n\nTags: [#tag1](/r/gnoland/blog:t/tag1) [#tag3](/r/gnoland/blog:t/tag3)\n\nWritten by moul on 20 May 2022\n\nPublished by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq to Gnoland's Blog\n\n---\n\u003cdetails\u003e\u003csummary\u003eComment section\u003c/summary\u003e\n\n\u003c/details\u003e\n\u003c/main\u003e\n\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// list by tags.\n\t{\n\t\tgot := Render(\"t/invalid\")\n\t\texpected := \"# [Gnoland's Blog](/r/gnoland/blog:) / t / invalid\\n\\nNo posts.\"\n\t\tassertMDEquals(t, got, expected)\n\n\t\tgot = Render(\"t/tag2\")\n\t\texpected = `\n# [Gnoland's Blog](/r/gnoland/blog:) / t / tag2\n\n\u003cdiv\u003e\n\n### [title1](/r/gnoland/blog:p/slug1)\n 20 May 2022\n\u003c/div\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// add comments.\n\t{\n\t\tAddComment(\"slug1\", \"comment1\")\n\t\tAddComment(\"slug2\", \"comment2\")\n\t\tAddComment(\"slug1\", \"comment3\")\n\t\tAddComment(\"slug2\", \"comment4\")\n\t\tAddComment(\"slug1\", \"comment5\")\n\t\tgot := Render(\"p/slug2\")\n\t\texpected := `\u003cmain class='gno-tmpl-page'\u003e\n\n# title2\n\nbody2\n\n---\n\nTags: [#tag1](/r/gnoland/blog:t/tag1) [#tag3](/r/gnoland/blog:t/tag3)\n\nWritten by moul on 20 May 2022\n\nPublished by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq to Gnoland's Blog\n\n---\n\u003cdetails\u003e\u003csummary\u003eComment section\u003c/summary\u003e\n\n\u003ch5\u003ecomment4\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003ch5\u003ecomment2\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003c/details\u003e\n\u003c/main\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// edit post.\n\t{\n\t\toldTitle := \"title2\"\n\t\toldDate := \"2022-05-20T13:17:23Z\"\n\n\t\tModEditPost(\"slug2\", oldTitle, \"body2++\", oldDate, \"manfred\", \"tag1,tag4\")\n\t\tgot := Render(\"p/slug2\")\n\t\texpected := `\u003cmain class='gno-tmpl-page'\u003e\n\n# title2\n\nbody2++\n\n---\n\nTags: [#tag1](/r/gnoland/blog:t/tag1) [#tag4](/r/gnoland/blog:t/tag4)\n\nWritten by manfred on 20 May 2022\n\nPublished by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq to Gnoland's Blog\n\n---\n\u003cdetails\u003e\u003csummary\u003eComment section\u003c/summary\u003e\n\n\u003ch5\u003ecomment4\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003ch5\u003ecomment2\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003c/details\u003e\n\u003c/main\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\n\t\thome := Render(\"\")\n\n\t\tif strings.Count(home, oldTitle) != 1 {\n\t\t\tt.Errorf(\"post not edited properly\")\n\t\t}\n\t\t// Edits work everything except title, slug, and publicationDate\n\t\t// Edits to the above will cause duplication on the blog home page\n\t}\n\n\t{ // Test remove functionality\n\t\ttitle := \"example title\"\n\t\tslug := \"testSlug1\"\n\t\tModAddPost(slug, title, \"body1\", \"2022-05-25T13:17:22Z\", \"moul\", \"tag1,tag2\")\n\n\t\tgot := Render(\"\")\n\n\t\tif !strings.Contains(got, title) {\n\t\t\tt.Errorf(\"post was not added properly\")\n\t\t}\n\n\t\tpostRender := Render(\"p/\" + slug)\n\n\t\tif !strings.Contains(postRender, title) {\n\t\t\tt.Errorf(\"post not rendered properly\")\n\t\t}\n\n\t\tModRemovePost(slug)\n\t\tgot = Render(\"\")\n\n\t\tif strings.Contains(got, title) {\n\t\t\tt.Errorf(\"post was not removed\")\n\t\t}\n\n\t\tpostRender = Render(\"p/\" + slug)\n\n\t\tassertMDEquals(t, postRender, \"404\")\n\t}\n\n\t// TODO: pagination.\n\t// TODO: ?format=...\n\n\t// all 404s\n\t{\n\t\tnotFoundPaths := []string{\n\t\t\t\"p/slug3\",\n\t\t\t\"p\",\n\t\t\t\"p/\",\n\t\t\t\"x/x\",\n\t\t\t\"t\",\n\t\t\t\"t/\",\n\t\t\t\"/\",\n\t\t\t\"p/slug1/\",\n\t\t}\n\t\tfor _, notFoundPath := range notFoundPaths {\n\t\t\tgot := Render(notFoundPath)\n\t\t\texpected := \"404\"\n\t\t\tif got != expected {\n\t\t\t\tt.Errorf(\"path %q: expected %q, got %q.\", notFoundPath, expected, got)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc assertMDEquals(t *testing.T, got, expected string) {\n\tt.Helper()\n\texpected = strings.TrimSpace(expected)\n\tgot = strings.TrimSpace(got)\n\tif expected != got {\n\t\tt.Errorf(\"invalid render output.\\nexpected %q.\\ngot %q.\", expected, got)\n\t}\n}\n"},{"name":"util.gno","body":"package gnoblog\n\nfunc checkErr(err error) {\n\tif err != nil {\n\t\tpanic(err)\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":"userbook","path":"gno.land/r/demo/userbook","files":[{"name":"userbook.gno","body":"// This realm demonstrates a small userbook system working with gnoweb\npackage userbook\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/mux\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype Signup struct {\n\taccount string\n\theight int64\n}\n\n// signups - keep a slice of signed up addresses efficient pagination\nvar signups []Signup\n\n// tracker - keep track of who signed up\nvar (\n\ttracker *avl.Tree\n\trouter *mux.Router\n)\n\nconst (\n\tdefaultPageSize = 20\n\tpathArgument = \"number\"\n\tsubPath = \"page/{\" + pathArgument + \"}\"\n\tsignUpEvent = \"SignUp\"\n)\n\nfunc init() {\n\t// Set up tracker tree\n\ttracker = avl.NewTree()\n\n\t// Set up route handling\n\trouter = mux.NewRouter()\n\trouter.HandleFunc(\"\", renderHelper)\n\trouter.HandleFunc(subPath, renderHelper)\n\n\t// Sign up the deployer\n\tSignUp()\n}\n\nfunc SignUp() string {\n\t// Get transaction caller\n\tcaller := std.PreviousRealm().Address().String()\n\theight := std.GetHeight()\n\n\t// Check if the user is already signed up\n\tif _, exists := tracker.Get(caller); exists {\n\t\tpanic(caller + \" is already signed up!\")\n\t}\n\n\t// Sign up the user\n\ttracker.Set(caller, struct{}{})\n\tsignup := Signup{\n\t\tcaller,\n\t\theight,\n\t}\n\n\tsignups = append(signups, signup)\n\tstd.Emit(signUpEvent, \"SignedUpAccount\", signup.account)\n\n\treturn ufmt.Sprintf(\"%s added to userbook up at block #%d!\", signup.account, signup.height)\n}\n\nfunc GetSignupsInRange(page, pageSize int) ([]Signup, int) {\n\tif page \u003c 1 {\n\t\tpanic(\"page number cannot be less than 1\")\n\t}\n\n\tif pageSize \u003c 1 || pageSize \u003e 50 {\n\t\tpanic(\"page size must be from 1 to 50\")\n\t}\n\n\t// Pagination\n\t// Calculate indexes\n\tstartIndex := (page - 1) * pageSize\n\tendIndex := startIndex + pageSize\n\n\t// If page does not contain any users\n\tif startIndex \u003e= len(signups) {\n\t\treturn nil, -1\n\t}\n\n\t// If page contains fewer users than the page size\n\tif endIndex \u003e len(signups) {\n\t\tendIndex = len(signups)\n\t}\n\n\treturn signups[startIndex:endIndex], endIndex\n}\n\nfunc renderHelper(res *mux.ResponseWriter, req *mux.Request) {\n\ttotalSignups := len(signups)\n\tres.Write(\"# Welcome to UserBook!\\n\\n\")\n\n\t// Get URL parameter\n\tpage, err := strconv.Atoi(req.GetVar(\"number\"))\n\tif err != nil {\n\t\tpage = 1 // render first page on bad input\n\t}\n\n\t// Fetch paginated signups\n\tfetchedSignups, endIndex := GetSignupsInRange(page, defaultPageSize)\n\t// Handle empty page case\n\tif len(fetchedSignups) == 0 {\n\t\tres.Write(\"No users on this page!\\n\\n\")\n\t\tres.Write(\"---\\n\\n\")\n\t\tres.Write(\"[Back to Page #1](/r/demo/userbook:page/1)\\n\\n\")\n\t\treturn\n\t}\n\n\t// Write page title\n\tres.Write(ufmt.Sprintf(\"## UserBook - Page #%d:\\n\\n\", page))\n\n\t// Write signups\n\tpageStartIndex := defaultPageSize * (page - 1)\n\tfor i, signup := range fetchedSignups {\n\t\tout := ufmt.Sprintf(\"#### User #%d - %s - signed up at Block #%d\\n\", pageStartIndex+i, signup.account, signup.height)\n\t\tres.Write(out)\n\t}\n\n\tres.Write(\"---\\n\\n\")\n\n\t// Write UserBook info\n\tlatestSignupIndex := totalSignups - 1\n\tres.Write(ufmt.Sprintf(\"#### Total users: %d\\n\", totalSignups))\n\tres.Write(ufmt.Sprintf(\"#### Latest signup: User #%d at Block #%d\\n\", latestSignupIndex, signups[latestSignupIndex].height))\n\n\tres.Write(\"---\\n\\n\")\n\n\t// Write page number\n\tres.Write(ufmt.Sprintf(\"You're viewing page #%d\", page))\n\n\t// Write navigation buttons\n\tvar prevPage string\n\tvar nextPage string\n\t// If we are on any page that is not the first page\n\tif page \u003e 1 {\n\t\tprevPage = ufmt.Sprintf(\" - [Previous page](/r/demo/userbook:page/%d)\", page-1)\n\t}\n\n\t// If there are more pages after the current one\n\tif endIndex \u003c totalSignups {\n\t\tnextPage = ufmt.Sprintf(\" - [Next page](/r/demo/userbook:page/%d)\\n\\n\", page+1)\n\t}\n\n\tres.Write(prevPage)\n\tres.Write(nextPage)\n}\n\nfunc Render(path string) string {\n\treturn router.Render(path)\n}\n"},{"name":"userbook_test.gno","body":"package userbook\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nfunc TestRender(t *testing.T) {\n\t// Sign up 20 users + deployer\n\tfor i := 0; i \u003c 20; i++ {\n\t\taddrName := ufmt.Sprintf(\"test%d\", i)\n\t\tcaller := testutils.TestAddress(addrName)\n\t\tstd.TestSetOriginCaller(caller)\n\t\tSignUp()\n\t}\n\n\ttestCases := []struct {\n\t\tname string\n\t\tnextPage bool\n\t\tprevPage bool\n\t\tpath string\n\t\texpectedNumberOfUsers int\n\t}{\n\t\t{\n\t\t\tname: \"1st page render\",\n\t\t\tnextPage: true,\n\t\t\tprevPage: false,\n\t\t\tpath: \"page/1\",\n\t\t\texpectedNumberOfUsers: 20,\n\t\t},\n\t\t{\n\t\t\tname: \"2nd page render\",\n\t\t\tnextPage: false,\n\t\t\tprevPage: true,\n\t\t\tpath: \"page/2\",\n\t\t\texpectedNumberOfUsers: 1,\n\t\t},\n\t\t{\n\t\t\tname: \"Invalid path render\",\n\t\t\tnextPage: true,\n\t\t\tprevPage: false,\n\t\t\tpath: \"page/invalidtext\",\n\t\t\texpectedNumberOfUsers: 20,\n\t\t},\n\t\t{\n\t\t\tname: \"Empty Page\",\n\t\t\tnextPage: false,\n\t\t\tprevPage: false,\n\t\t\tpath: \"page/1000\",\n\t\t\texpectedNumberOfUsers: 0,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := Render(tc.path)\n\t\t\tnumUsers := countUsers(got)\n\n\t\t\tif tc.prevPage \u0026\u0026 !strings.Contains(got, \"Previous page\") {\n\t\t\t\tt.Fatalf(\"expected to find Previous page, didn't find it\")\n\t\t\t}\n\t\t\tif tc.nextPage \u0026\u0026 !strings.Contains(got, \"Next page\") {\n\t\t\t\tt.Fatalf(\"expected to find Next page, didn't find it\")\n\t\t\t}\n\n\t\t\tif tc.expectedNumberOfUsers != numUsers {\n\t\t\t\tt.Fatalf(\"expected %d, got %d users\", tc.expectedNumberOfUsers, numUsers)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc countUsers(input string) int {\n\treturn strings.Count(input, \"#### User #\")\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":"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/users\"\n)\n\nvar (\n\tbanker *grc20.Banker = grc20.NewBanker(\"wrapped GNOT\", \"wugnot\", 0)\n\tToken = banker.Token()\n)\n\nconst (\n\tugnotMinDeposit uint64 = 1000\n\twugnotMinDeposit uint64 = 1\n)\n\nfunc Deposit() {\n\tcaller := std.PreviousRealm().Address()\n\tsent := std.OriginSend()\n\tamount := sent.AmountOf(\"ugnot\")\n\n\trequire(uint64(amount) \u003e= ugnotMinDeposit, ufmt.Sprintf(\"Deposit below minimum: %d/%d ugnot.\", amount, ugnotMinDeposit))\n\tcheckErr(banker.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.PreviousRealm().Address()\n\tpkgaddr := std.CurrentRealm().Address()\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(banker.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 banker.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\tcheckErr(Token.Transfer(toAddr, amount))\n}\n\nfunc Approve(spender pusers.AddressOrName, amount uint64) {\n\tspenderAddr := users.Resolve(spender)\n\tcheckErr(Token.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(Token.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.TestSetOriginPkgAddr(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.TestSetOriginCaller(addr1)\n\tstd.TestSetOriginSend(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.TestSetOriginCaller(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=200000000 |\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=200000000 |\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=200004242 |\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":""}} +{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"gnoblog","path":"gno.land/r/gnoland/blog","files":[{"name":"admin.gno","body":"package gnoblog\n\nimport (\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/r/gov/dao/bridge\"\n)\n\nvar (\n\tadminAddr std.Address\n\tmoderatorList avl.Tree\n\tcommenterList avl.Tree\n\tinPause bool\n)\n\nfunc init() {\n\t// adminAddr = std.OriginCaller() // FIXME: find a way to use this from the main's genesis.\n\tadminAddr = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"\n}\n\nfunc AdminSetAdminAddr(addr std.Address) {\n\tassertIsAdmin()\n\tadminAddr = addr\n}\n\nfunc AdminSetInPause(state bool) {\n\tassertIsAdmin()\n\tinPause = state\n}\n\nfunc AdminAddModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), true)\n}\n\nfunc AdminRemoveModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), false) // FIXME: delete instead?\n}\n\nfunc NewPostExecutor(slug, title, body, publicationDate, authors, tags string) dao.Executor {\n\tcallback := func() error {\n\t\taddPost(std.PreviousRealm().Address(), slug, title, body, publicationDate, authors, tags)\n\n\t\treturn nil\n\t}\n\n\treturn bridge.GovDAO().NewGovDAOExecutor(callback)\n}\n\nfunc ModAddPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\tcaller := std.OriginCaller()\n\taddPost(caller, slug, title, body, publicationDate, authors, tags)\n}\n\nfunc addPost(caller std.Address, slug, title, body, publicationDate, authors, tags string) {\n\tvar tagList []string\n\tif tags != \"\" {\n\t\ttagList = strings.Split(tags, \",\")\n\t}\n\tvar authorList []string\n\tif authors != \"\" {\n\t\tauthorList = strings.Split(authors, \",\")\n\t}\n\n\terr := b.NewPost(caller, slug, title, body, publicationDate, authorList, tagList)\n\n\tcheckErr(err)\n}\n\nfunc ModEditPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.GetPost(slug).Update(title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc ModRemovePost(slug string) {\n\tassertIsModerator()\n\n\tb.RemovePost(slug)\n}\n\nfunc ModAddCommenter(addr std.Address) {\n\tassertIsModerator()\n\tcommenterList.Set(addr.String(), true)\n}\n\nfunc ModDelCommenter(addr std.Address) {\n\tassertIsModerator()\n\tcommenterList.Set(addr.String(), false) // FIXME: delete instead?\n}\n\nfunc ModDelComment(slug string, index int) {\n\tassertIsModerator()\n\n\terr := b.GetPost(slug).DeleteComment(index)\n\tcheckErr(err)\n}\n\nfunc isAdmin(addr std.Address) bool {\n\treturn addr == adminAddr\n}\n\nfunc isModerator(addr std.Address) bool {\n\t_, found := moderatorList.Get(addr.String())\n\treturn found\n}\n\nfunc isCommenter(addr std.Address) bool {\n\t_, found := commenterList.Get(addr.String())\n\treturn found\n}\n\nfunc assertIsAdmin() {\n\tcaller := std.OriginCaller()\n\tif !isAdmin(caller) {\n\t\tpanic(\"access restricted.\")\n\t}\n}\n\nfunc assertIsModerator() {\n\tcaller := std.OriginCaller()\n\tif isAdmin(caller) || isModerator(caller) {\n\t\treturn\n\t}\n\tpanic(\"access restricted\")\n}\n\nfunc assertIsCommenter() {\n\tcaller := std.OriginCaller()\n\tif isAdmin(caller) || isModerator(caller) || isCommenter(caller) {\n\t\treturn\n\t}\n\tpanic(\"access restricted\")\n}\n\nfunc assertNotInPause() {\n\tif inPause {\n\t\tpanic(\"access restricted (pause)\")\n\t}\n}\n"},{"name":"gnoblog.gno","body":"package gnoblog\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/blog\"\n)\n\nvar b = \u0026blog.Blog{\n\tTitle: \"Gnoland's Blog\",\n\tPrefix: \"/r/gnoland/blog:\",\n}\n\nfunc AddComment(postSlug, comment string) {\n\tassertIsCommenter()\n\tassertNotInPause()\n\n\tcaller := std.OriginCaller()\n\terr := b.GetPost(postSlug).AddComment(caller, comment)\n\tcheckErr(err)\n}\n\nfunc Render(path string) string {\n\treturn b.Render(path)\n}\n\nfunc RenderLastPostsWidget(limit int) string {\n\treturn b.RenderLastPostsWidget(limit)\n}\n\nfunc PostExists(slug string) bool {\n\tif b.GetPost(slug) == nil {\n\t\treturn false\n\t}\n\treturn true\n}\n"},{"name":"gnoblog_test.gno","body":"package gnoblog\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestPackage(t *testing.T) {\n\tstd.TestSetOriginCaller(std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"))\n\n\tauthor := std.OriginCaller()\n\n\t// by default, no posts.\n\t{\n\t\tgot := Render(\"\")\n\t\texpected := `\n# Gnoland's Blog\n\nNo posts.\n`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// create two posts, list post.\n\t{\n\t\tModAddPost(\"slug1\", \"title1\", \"body1\", \"2022-05-20T13:17:22Z\", \"moul\", \"tag1,tag2\")\n\t\tModAddPost(\"slug2\", \"title2\", \"body2\", \"2022-05-20T13:17:23Z\", \"moul\", \"tag1,tag3\")\n\t\tgot := Render(\"\")\n\t\texpected := `\n\t# Gnoland's Blog\n\n\u003cdiv class='columns-3'\u003e\u003cdiv\u003e\n\n### [title2](/r/gnoland/blog:p/slug2)\n 20 May 2022\n\u003c/div\u003e\u003cdiv\u003e\n\n### [title1](/r/gnoland/blog:p/slug1)\n 20 May 2022\n\u003c/div\u003e\u003c/div\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// view post.\n\t{\n\t\tgot := Render(\"p/slug2\")\n\t\texpected := `\n\u003cmain class='gno-tmpl-page'\u003e\n\n# title2\n\nbody2\n\n---\n\nTags: [#tag1](/r/gnoland/blog:t/tag1) [#tag3](/r/gnoland/blog:t/tag3)\n\nWritten by moul on 20 May 2022\n\nPublished by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq to Gnoland's Blog\n\n---\n\u003cdetails\u003e\u003csummary\u003eComment section\u003c/summary\u003e\n\n\u003c/details\u003e\n\u003c/main\u003e\n\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// list by tags.\n\t{\n\t\tgot := Render(\"t/invalid\")\n\t\texpected := \"# [Gnoland's Blog](/r/gnoland/blog:) / t / invalid\\n\\nNo posts.\"\n\t\tassertMDEquals(t, got, expected)\n\n\t\tgot = Render(\"t/tag2\")\n\t\texpected = `\n# [Gnoland's Blog](/r/gnoland/blog:) / t / tag2\n\n\u003cdiv\u003e\n\n### [title1](/r/gnoland/blog:p/slug1)\n 20 May 2022\n\u003c/div\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// add comments.\n\t{\n\t\tAddComment(\"slug1\", \"comment1\")\n\t\tAddComment(\"slug2\", \"comment2\")\n\t\tAddComment(\"slug1\", \"comment3\")\n\t\tAddComment(\"slug2\", \"comment4\")\n\t\tAddComment(\"slug1\", \"comment5\")\n\t\tgot := Render(\"p/slug2\")\n\t\texpected := `\u003cmain class='gno-tmpl-page'\u003e\n\n# title2\n\nbody2\n\n---\n\nTags: [#tag1](/r/gnoland/blog:t/tag1) [#tag3](/r/gnoland/blog:t/tag3)\n\nWritten by moul on 20 May 2022\n\nPublished by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq to Gnoland's Blog\n\n---\n\u003cdetails\u003e\u003csummary\u003eComment section\u003c/summary\u003e\n\n\u003ch5\u003ecomment4\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003ch5\u003ecomment2\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003c/details\u003e\n\u003c/main\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// edit post.\n\t{\n\t\toldTitle := \"title2\"\n\t\toldDate := \"2022-05-20T13:17:23Z\"\n\n\t\tModEditPost(\"slug2\", oldTitle, \"body2++\", oldDate, \"manfred\", \"tag1,tag4\")\n\t\tgot := Render(\"p/slug2\")\n\t\texpected := `\u003cmain class='gno-tmpl-page'\u003e\n\n# title2\n\nbody2++\n\n---\n\nTags: [#tag1](/r/gnoland/blog:t/tag1) [#tag4](/r/gnoland/blog:t/tag4)\n\nWritten by manfred on 20 May 2022\n\nPublished by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq to Gnoland's Blog\n\n---\n\u003cdetails\u003e\u003csummary\u003eComment section\u003c/summary\u003e\n\n\u003ch5\u003ecomment4\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003ch5\u003ecomment2\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003c/details\u003e\n\u003c/main\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\n\t\thome := Render(\"\")\n\n\t\tif strings.Count(home, oldTitle) != 1 {\n\t\t\tt.Errorf(\"post not edited properly\")\n\t\t}\n\t\t// Edits work everything except title, slug, and publicationDate\n\t\t// Edits to the above will cause duplication on the blog home page\n\t}\n\n\t{ // Test remove functionality\n\t\ttitle := \"example title\"\n\t\tslug := \"testSlug1\"\n\t\tModAddPost(slug, title, \"body1\", \"2022-05-25T13:17:22Z\", \"moul\", \"tag1,tag2\")\n\n\t\tgot := Render(\"\")\n\n\t\tif !strings.Contains(got, title) {\n\t\t\tt.Errorf(\"post was not added properly\")\n\t\t}\n\n\t\tpostRender := Render(\"p/\" + slug)\n\n\t\tif !strings.Contains(postRender, title) {\n\t\t\tt.Errorf(\"post not rendered properly\")\n\t\t}\n\n\t\tModRemovePost(slug)\n\t\tgot = Render(\"\")\n\n\t\tif strings.Contains(got, title) {\n\t\t\tt.Errorf(\"post was not removed\")\n\t\t}\n\n\t\tpostRender = Render(\"p/\" + slug)\n\n\t\tassertMDEquals(t, postRender, \"404\")\n\t}\n\n\t// TODO: pagination.\n\t// TODO: ?format=...\n\n\t// all 404s\n\t{\n\t\tnotFoundPaths := []string{\n\t\t\t\"p/slug3\",\n\t\t\t\"p\",\n\t\t\t\"p/\",\n\t\t\t\"x/x\",\n\t\t\t\"t\",\n\t\t\t\"t/\",\n\t\t\t\"/\",\n\t\t\t\"p/slug1/\",\n\t\t}\n\t\tfor _, notFoundPath := range notFoundPaths {\n\t\t\tgot := Render(notFoundPath)\n\t\t\texpected := \"404\"\n\t\t\tif got != expected {\n\t\t\t\tt.Errorf(\"path %q: expected %q, got %q.\", notFoundPath, expected, got)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc assertMDEquals(t *testing.T, got, expected string) {\n\tt.Helper()\n\texpected = strings.TrimSpace(expected)\n\tgot = strings.TrimSpace(got)\n\tif expected != got {\n\t\tt.Errorf(\"invalid render output.\\nexpected %q.\\ngot %q.\", expected, got)\n\t}\n}\n"},{"name":"util.gno","body":"package gnoblog\n\nfunc checkErr(err error) {\n\tif err != nil {\n\t\tpanic(err)\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":"events","path":"gno.land/r/gnoland/events","files":[{"name":"administration.gno","body":"package events\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/ownable/exts/authorizable\"\n)\n\nvar (\n\tsu = std.Address(\"g125em6arxsnj49vx35f0n0z34putv5ty3376fg5\") // @leohhhn\n\tauth = authorizable.NewAuthorizableWithAddress(su)\n)\n\n// GetOwner gets the owner of the events realm\nfunc GetOwner() std.Address {\n\treturn auth.Owner()\n}\n\n// AddModerator adds a moderator to the events realm\nfunc AddModerator(mod std.Address) {\n\tauth.AssertCallerIsOwner()\n\n\tif err := auth.AddToAuthList(mod); err != nil {\n\t\tpanic(err)\n\t}\n}\n"},{"name":"errors.gno","body":"package events\n\nimport (\n\t\"errors\"\n\t\"strconv\"\n)\n\nvar (\n\tErrEmptyName = errors.New(\"event name cannot be empty\")\n\tErrNoSuchID = errors.New(\"event with specified ID does not exist\")\n\tErrMinWidgetSize = errors.New(\"you need to request at least 1 event to render\")\n\tErrMaxWidgetSize = errors.New(\"maximum number of events in widget is\" + strconv.Itoa(MaxWidgetSize))\n\tErrDescriptionTooLong = errors.New(\"event description is too long\")\n\tErrInvalidStartTime = errors.New(\"invalid start time format\")\n\tErrInvalidEndTime = errors.New(\"invalid end time format\")\n\tErrEndBeforeStart = errors.New(\"end time cannot be before start time\")\n\tErrStartEndTimezonemMismatch = errors.New(\"start and end timezones are not the same\")\n)\n"},{"name":"events.gno","body":"// Package events allows you to upload data about specific IRL/online events\n// It includes dynamic support for updating rendering events based on their\n// status, ie if they are upcoming, in progress, or in the past.\npackage events\n\nimport (\n\t\"sort\"\n\t\"std\"\n\t\"strings\"\n\t\"time\"\n\n\t\"gno.land/p/demo/seqid\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype (\n\tEvent struct {\n\t\tid string\n\t\tname string // name of event\n\t\tdescription string // short description of event\n\t\tlink string // link to auth corresponding web2 page, ie eventbrite/luma or conference page\n\t\tlocation string // location of the event\n\t\tstartTime time.Time // given in RFC3339\n\t\tendTime time.Time // end time of the event, given in RFC3339\n\t}\n\n\teventsSlice []*Event\n)\n\nvar (\n\tevents = make(eventsSlice, 0) // sorted\n\tidCounter seqid.ID\n)\n\nconst (\n\tmaxDescLength = 100\n\tEventAdded = \"EventAdded\"\n\tEventDeleted = \"EventDeleted\"\n\tEventEdited = \"EventEdited\"\n)\n\n// AddEvent adds auth new event\n// Start time \u0026 end time need to be specified in RFC3339, ie 2024-08-08T12:00:00+02:00\nfunc AddEvent(name, description, link, location, startTime, endTime string) (string, error) {\n\tauth.AssertOnAuthList()\n\n\tif strings.TrimSpace(name) == \"\" {\n\t\treturn \"\", ErrEmptyName\n\t}\n\n\tif len(description) \u003e maxDescLength {\n\t\treturn \"\", ufmt.Errorf(\"%s: provided length is %d, maximum is %d\", ErrDescriptionTooLong, len(description), maxDescLength)\n\t}\n\n\t// Parse times\n\tst, et, err := parseTimes(startTime, endTime)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tid := idCounter.Next().String()\n\te := \u0026Event{\n\t\tid: id,\n\t\tname: name,\n\t\tdescription: description,\n\t\tlink: link,\n\t\tlocation: location,\n\t\tstartTime: st,\n\t\tendTime: et,\n\t}\n\n\tevents = append(events, e)\n\tsort.Sort(events)\n\n\tstd.Emit(EventAdded,\n\t\t\"id\", e.id,\n\t)\n\n\treturn id, nil\n}\n\n// DeleteEvent deletes an event with auth given ID\nfunc DeleteEvent(id string) {\n\tauth.AssertOnAuthList()\n\n\te, idx, err := GetEventByID(id)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tevents = append(events[:idx], events[idx+1:]...)\n\n\tstd.Emit(EventDeleted,\n\t\t\"id\", e.id,\n\t)\n}\n\n// EditEvent edits an event with auth given ID\n// It only updates values corresponding to non-empty arguments sent with the call\n// Note: if you need to update the start time or end time, you need to provide both every time\nfunc EditEvent(id string, name, description, link, location, startTime, endTime string) {\n\tauth.AssertOnAuthList()\n\n\te, _, err := GetEventByID(id)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Set only valid values\n\tif strings.TrimSpace(name) != \"\" {\n\t\te.name = name\n\t}\n\n\tif strings.TrimSpace(description) != \"\" {\n\t\te.description = description\n\t}\n\n\tif strings.TrimSpace(link) != \"\" {\n\t\te.link = link\n\t}\n\n\tif strings.TrimSpace(location) != \"\" {\n\t\te.location = location\n\t}\n\n\tif strings.TrimSpace(startTime) != \"\" || strings.TrimSpace(endTime) != \"\" {\n\t\tst, et, err := parseTimes(startTime, endTime)\n\t\tif err != nil {\n\t\t\tpanic(err) // need to also revert other state changes\n\t\t}\n\n\t\toldStartTime := e.startTime\n\t\te.startTime = st\n\t\te.endTime = et\n\n\t\t// If sort order was disrupted, sort again\n\t\tif oldStartTime != e.startTime {\n\t\t\tsort.Sort(events)\n\t\t}\n\t}\n\n\tstd.Emit(EventEdited,\n\t\t\"id\", e.id,\n\t)\n}\n\nfunc GetEventByID(id string) (*Event, int, error) {\n\tfor i, event := range events {\n\t\tif event.id == id {\n\t\t\treturn event, i, nil\n\t\t}\n\t}\n\n\treturn nil, -1, ErrNoSuchID\n}\n\n// Len returns the length of the slice\nfunc (m eventsSlice) Len() int {\n\treturn len(m)\n}\n\n// Less compares the startTime fields of two elements\n// In this case, events will be sorted by largest startTime first (upcoming \u003e past)\nfunc (m eventsSlice) Less(i, j int) bool {\n\treturn m[i].startTime.After(m[j].startTime)\n}\n\n// Swap swaps two elements in the slice\nfunc (m eventsSlice) Swap(i, j int) {\n\tm[i], m[j] = m[j], m[i]\n}\n\n// parseTimes parses the start and end time for an event and checks for possible errors\nfunc parseTimes(startTime, endTime string) (time.Time, time.Time, error) {\n\tst, err := time.Parse(time.RFC3339, startTime)\n\tif err != nil {\n\t\treturn time.Time{}, time.Time{}, ufmt.Errorf(\"%s: %s\", ErrInvalidStartTime, err.Error())\n\t}\n\n\tet, err := time.Parse(time.RFC3339, endTime)\n\tif err != nil {\n\t\treturn time.Time{}, time.Time{}, ufmt.Errorf(\"%s: %s\", ErrInvalidEndTime, err.Error())\n\t}\n\n\tif et.Before(st) {\n\t\treturn time.Time{}, time.Time{}, ErrEndBeforeStart\n\t}\n\n\t_, stOffset := st.Zone()\n\t_, etOffset := et.Zone()\n\tif stOffset != etOffset {\n\t\treturn time.Time{}, time.Time{}, ErrStartEndTimezonemMismatch\n\t}\n\n\treturn st, et, nil\n}\n"},{"name":"events_test.gno","body":"package events\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gno.land/p/demo/uassert\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nvar (\n\tsuRealm = std.NewUserRealm(su)\n\n\tnow = \"2009-02-13T23:31:30Z\" // time.Now() is hardcoded to this value in the gno test machine currently\n\tparsedTimeNow, _ = time.Parse(time.RFC3339, now)\n)\n\nfunc TestAddEvent(t *testing.T) {\n\tstd.TestSetOriginCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\te1Start := parsedTimeNow.Add(time.Hour * 24 * 5)\n\te1End := e1Start.Add(time.Hour * 4)\n\n\tAddEvent(\"Event 1\", \"this event is upcoming\", \"gno.land\", \"gnome land\", e1Start.Format(time.RFC3339), e1End.Format(time.RFC3339))\n\n\tgot := renderHome(false)\n\n\tif !strings.Contains(got, \"Event 1\") {\n\t\tt.Fatalf(\"Expected to find Event 1 in render\")\n\t}\n\n\te2Start := parsedTimeNow.Add(-time.Hour * 24 * 5)\n\te2End := e2Start.Add(time.Hour * 4)\n\n\tAddEvent(\"Event 2\", \"this event is in the past\", \"gno.land\", \"gnome land\", e2Start.Format(time.RFC3339), e2End.Format(time.RFC3339))\n\n\tgot = renderHome(false)\n\n\tupcomingPos := strings.Index(got, \"## Upcoming events\")\n\tpastPos := strings.Index(got, \"## Past events\")\n\n\te1Pos := strings.Index(got, \"Event 1\")\n\te2Pos := strings.Index(got, \"Event 2\")\n\n\t// expected index ordering: upcoming \u003c e1 \u003c past \u003c e2\n\tif e1Pos \u003c upcomingPos || e1Pos \u003e pastPos {\n\t\tt.Fatalf(\"Expected to find Event 1 in Upcoming events\")\n\t}\n\n\tif e2Pos \u003c upcomingPos || e2Pos \u003c pastPos || e2Pos \u003c e1Pos {\n\t\tt.Fatalf(\"Expected to find Event 2 on auth different pos\")\n\t}\n\n\t// larger index =\u003e smaller startTime (future =\u003e past)\n\tif events[0].startTime.Unix() \u003c events[1].startTime.Unix() {\n\t\tt.Fatalf(\"expected ordering to be different\")\n\t}\n}\n\nfunc TestAddEventErrors(t *testing.T) {\n\tstd.TestSetOriginCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\t_, err := AddEvent(\"\", \"sample desc\", \"gno.land\", \"gnome land\", \"2009-02-13T23:31:31Z\", \"2009-02-13T23:33:31Z\")\n\tuassert.ErrorIs(t, err, ErrEmptyName)\n\n\t_, err = AddEvent(\"sample name\", \"sample desc\", \"gno.land\", \"gnome land\", \"\", \"2009-02-13T23:33:31Z\")\n\tuassert.ErrorContains(t, err, ErrInvalidStartTime.Error())\n\n\t_, err = AddEvent(\"sample name\", \"sample desc\", \"gno.land\", \"gnome land\", \"2009-02-13T23:31:31Z\", \"\")\n\tuassert.ErrorContains(t, err, ErrInvalidEndTime.Error())\n\n\t_, err = AddEvent(\"sample name\", \"sample desc\", \"gno.land\", \"gnome land\", \"2009-02-13T23:31:31Z\", \"2009-02-13T23:30:31Z\")\n\tuassert.ErrorIs(t, err, ErrEndBeforeStart)\n\n\t_, err = AddEvent(\"sample name\", \"sample desc\", \"gno.land\", \"gnome land\", \"2009-02-13T23:31:31+06:00\", \"2009-02-13T23:33:31+02:00\")\n\tuassert.ErrorIs(t, err, ErrStartEndTimezonemMismatch)\n\n\ttooLongDesc := `Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean ma`\n\t_, err = AddEvent(\"sample name\", tooLongDesc, \"gno.land\", \"gnome land\", \"2009-02-13T23:31:31Z\", \"2009-02-13T23:33:31Z\")\n\tuassert.ErrorContains(t, err, ErrDescriptionTooLong.Error())\n}\n\nfunc TestDeleteEvent(t *testing.T) {\n\tstd.TestSetOriginCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\te1Start := parsedTimeNow.Add(time.Hour * 24 * 5)\n\te1End := e1Start.Add(time.Hour * 4)\n\n\tid, _ := AddEvent(\"ToDelete\", \"description\", \"gno.land\", \"gnome land\", e1Start.Format(time.RFC3339), e1End.Format(time.RFC3339))\n\n\tgot := renderHome(false)\n\n\tif !strings.Contains(got, \"ToDelete\") {\n\t\tt.Fatalf(\"Expected to find ToDelete event in render\")\n\t}\n\n\tDeleteEvent(id)\n\tgot = renderHome(false)\n\n\tif strings.Contains(got, \"ToDelete\") {\n\t\tt.Fatalf(\"Did not expect to find ToDelete event in render\")\n\t}\n}\n\nfunc TestEditEvent(t *testing.T) {\n\tstd.TestSetOriginCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\te1Start := parsedTimeNow.Add(time.Hour * 24 * 5)\n\te1End := e1Start.Add(time.Hour * 4)\n\tloc := \"gnome land\"\n\n\tid, _ := AddEvent(\"ToDelete\", \"description\", \"gno.land\", loc, e1Start.Format(time.RFC3339), e1End.Format(time.RFC3339))\n\n\tnewName := \"New Name\"\n\tnewDesc := \"Normal description\"\n\tnewLink := \"new Link\"\n\tnewST := e1Start.Add(time.Hour)\n\tnewET := newST.Add(time.Hour)\n\n\tEditEvent(id, newName, newDesc, newLink, \"\", newST.Format(time.RFC3339), newET.Format(time.RFC3339))\n\tedited, _, _ := GetEventByID(id)\n\n\t// Check updated values\n\tuassert.Equal(t, edited.name, newName)\n\tuassert.Equal(t, edited.description, newDesc)\n\tuassert.Equal(t, edited.link, newLink)\n\tuassert.True(t, edited.startTime.Equal(newST))\n\tuassert.True(t, edited.endTime.Equal(newET))\n\n\t// Check if the old values are the same\n\tuassert.Equal(t, edited.location, loc)\n}\n\nfunc TestInvalidEdit(t *testing.T) {\n\tstd.TestSetOriginCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\tuassert.PanicsWithMessage(t, ErrNoSuchID.Error(), func() {\n\t\tEditEvent(\"123123\", \"\", \"\", \"\", \"\", \"\", \"\")\n\t})\n}\n\nfunc TestParseTimes(t *testing.T) {\n\t// times not provided\n\t// end time before start time\n\t// timezone Missmatch\n\n\t_, _, err := parseTimes(\"\", \"\")\n\tuassert.ErrorContains(t, err, ErrInvalidStartTime.Error())\n\n\t_, _, err = parseTimes(now, \"\")\n\tuassert.ErrorContains(t, err, ErrInvalidEndTime.Error())\n\n\t_, _, err = parseTimes(\"2009-02-13T23:30:30Z\", \"2009-02-13T21:30:30Z\")\n\tuassert.ErrorContains(t, err, ErrEndBeforeStart.Error())\n\n\t_, _, err = parseTimes(\"2009-02-10T23:30:30+02:00\", \"2009-02-13T21:30:33+05:00\")\n\tuassert.ErrorContains(t, err, ErrStartEndTimezonemMismatch.Error())\n}\n\nfunc TestRenderEventWidget(t *testing.T) {\n\tstd.TestSetOriginCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\t// No events yet\n\tevents = nil\n\tout, err := RenderEventWidget(1)\n\tuassert.NoError(t, err)\n\tuassert.Equal(t, out, \"No events.\")\n\n\t// Too many events\n\tout, err = RenderEventWidget(MaxWidgetSize + 1)\n\tuassert.ErrorIs(t, err, ErrMaxWidgetSize)\n\n\t// Too little events\n\tout, err = RenderEventWidget(0)\n\tuassert.ErrorIs(t, err, ErrMinWidgetSize)\n\n\t// Ordering \u0026 if requested amt is larger than the num of events that exist\n\te1Start := parsedTimeNow.Add(time.Hour * 24 * 5)\n\te1End := e1Start.Add(time.Hour * 4)\n\n\te2Start := parsedTimeNow.Add(time.Hour * 24 * 10) // event 2 is after event 1\n\te2End := e2Start.Add(time.Hour * 4)\n\n\t_, err = AddEvent(\"Event 1\", \"description\", \"gno.land\", \"loc\", e1Start.Format(time.RFC3339), e1End.Format(time.RFC3339))\n\turequire.NoError(t, err)\n\n\t_, err = AddEvent(\"Event 2\", \"description\", \"gno.land\", \"loc\", e2Start.Format(time.RFC3339), e2End.Format(time.RFC3339))\n\turequire.NoError(t, err)\n\n\tout, err = RenderEventWidget(MaxWidgetSize)\n\turequire.NoError(t, err)\n\n\tuniqueSequence := \"- [\" // sequence that is displayed once per each event as per the RenderEventWidget function\n\tuassert.Equal(t, 2, strings.Count(out, uniqueSequence))\n\n\tuassert.True(t, strings.Index(out, \"Event 1\") \u003e strings.Index(out, \"Event 2\"))\n}\n"},{"name":"rendering.gno","body":"package events\n\nimport (\n\t\"bytes\"\n\t\"time\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\nconst (\n\tMaxWidgetSize = 5\n)\n\n// RenderEventWidget shows up to eventsToRender of the latest events to a caller\nfunc RenderEventWidget(eventsToRender int) (string, error) {\n\tnumOfEvents := len(events)\n\tif numOfEvents == 0 {\n\t\treturn \"No events.\", nil\n\t}\n\n\tif eventsToRender \u003e MaxWidgetSize {\n\t\treturn \"\", ErrMaxWidgetSize\n\t}\n\n\tif eventsToRender \u003c 1 {\n\t\treturn \"\", ErrMinWidgetSize\n\t}\n\n\tif eventsToRender \u003e numOfEvents {\n\t\teventsToRender = numOfEvents\n\t}\n\n\toutput := \"\"\n\n\tfor _, event := range events[:eventsToRender] {\n\t\toutput += ufmt.Sprintf(\"- [%s](%s)\\n\", event.name, event.link)\n\t}\n\n\treturn output, nil\n}\n\n// renderHome renders the home page of the events realm\nfunc renderHome(admin bool) string {\n\toutput := \"# gno.land events\\n\\n\"\n\n\tif len(events) == 0 {\n\t\toutput += \"No upcoming or past events.\"\n\t\treturn output\n\t}\n\n\toutput += \"Below is a list of all gno.land events, including in progress, upcoming, and past ones.\\n\\n\"\n\toutput += \"---\\n\\n\"\n\n\tvar (\n\t\tinProgress = \"\"\n\t\tupcoming = \"\"\n\t\tpast = \"\"\n\t\tnow = time.Now()\n\t)\n\n\tfor _, e := range events {\n\t\tif now.Before(e.startTime) {\n\t\t\tupcoming += e.Render(admin)\n\t\t} else if now.After(e.endTime) {\n\t\t\tpast += e.Render(admin)\n\t\t} else {\n\t\t\tinProgress += e.Render(admin)\n\t\t}\n\t}\n\n\tif upcoming != \"\" {\n\t\t// Add upcoming events\n\t\toutput += \"## Upcoming events\\n\\n\"\n\t\toutput += \"\u003cdiv class='columns-3'\u003e\"\n\n\t\toutput += upcoming\n\n\t\toutput += \"\u003c/div\u003e\\n\\n\"\n\t\toutput += \"---\\n\\n\"\n\t}\n\n\tif inProgress != \"\" {\n\t\toutput += \"## Currently in progress\\n\\n\"\n\t\toutput += \"\u003cdiv class='columns-3'\u003e\"\n\n\t\toutput += inProgress\n\n\t\toutput += \"\u003c/div\u003e\\n\\n\"\n\t\toutput += \"---\\n\\n\"\n\t}\n\n\tif past != \"\" {\n\t\t// Add past events\n\t\toutput += \"## Past events\\n\\n\"\n\t\toutput += \"\u003cdiv class='columns-3'\u003e\"\n\n\t\toutput += past\n\n\t\toutput += \"\u003c/div\u003e\\n\\n\"\n\t}\n\n\treturn output\n}\n\n// Render returns the markdown representation of a single event instance\nfunc (e Event) Render(admin bool) string {\n\tvar buf bytes.Buffer\n\n\tbuf.WriteString(\"\u003cdiv\u003e\\n\\n\")\n\tbuf.WriteString(ufmt.Sprintf(\"### %s\\n\\n\", e.name))\n\tbuf.WriteString(ufmt.Sprintf(\"%s\\n\\n\", e.description))\n\tbuf.WriteString(ufmt.Sprintf(\"**Location:** %s\\n\\n\", e.location))\n\n\t_, offset := e.startTime.Zone() // offset is in seconds\n\thoursOffset := offset / (60 * 60)\n\tsign := \"\"\n\tif offset \u003e= 0 {\n\t\tsign = \"+\"\n\t}\n\n\tbuf.WriteString(ufmt.Sprintf(\"**Starts:** %s UTC%s%d\\n\\n\", e.startTime.Format(\"02 Jan 2006, 03:04 PM\"), sign, hoursOffset))\n\tbuf.WriteString(ufmt.Sprintf(\"**Ends:** %s UTC%s%d\\n\\n\", e.endTime.Format(\"02 Jan 2006, 03:04 PM\"), sign, hoursOffset))\n\n\tif admin {\n\t\tbuf.WriteString(ufmt.Sprintf(\"[EDIT](/r/gnoland/events$help\u0026func=EditEvent\u0026id=%s)\\n\\n\", e.id))\n\t\tbuf.WriteString(ufmt.Sprintf(\"[DELETE](/r/gnoland/events$help\u0026func=DeleteEvent\u0026id=%s)\\n\\n\", e.id))\n\t}\n\n\tif e.link != \"\" {\n\t\tbuf.WriteString(ufmt.Sprintf(\"[See more](%s)\\n\\n\", e.link))\n\t}\n\n\tbuf.WriteString(\"\u003c/div\u003e\")\n\n\treturn buf.String()\n}\n\n// Render is the main rendering entry point\nfunc Render(path string) string {\n\tif path == \"admin\" {\n\t\treturn renderHome(true)\n\t}\n\n\treturn renderHome(false)\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":"faucet","path":"gno.land/r/gnoland/faucet","files":[{"name":"admin.gno","body":"package faucet\n\nimport (\n\t\"errors\"\n\t\"std\"\n)\n\nfunc AdminSetInPause(inPause bool) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\tgInPause = inPause\n\treturn \"\"\n}\n\nfunc AdminSetMessage(message string) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\tgMessage = message\n\treturn \"\"\n}\n\nfunc AdminSetTransferLimit(amount int64) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\tgLimit = std.NewCoin(\"ugnot\", amount)\n\treturn \"\"\n}\n\nfunc AdminSetAdminAddr(addr std.Address) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\tgAdminAddr = addr\n\treturn \"\"\n}\n\nfunc AdminAddController(addr std.Address) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\n\tsize := gControllers.Size()\n\n\tif size \u003e= gControllersMaxSize {\n\t\treturn \"can not add more controllers than allowed\"\n\t}\n\n\tif gControllers.Has(addr.String()) {\n\t\treturn addr.String() + \" exists, no need to add.\"\n\t}\n\n\tgControllers.Set(addr.String(), addr)\n\n\treturn \"\"\n}\n\nfunc AdminRemoveController(addr std.Address) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\n\tif !gControllers.Has(addr.String()) {\n\t\treturn addr.String() + \" is not on the controller list\"\n\t}\n\n\t_, ok := gControllers.Remove(addr.String())\n\n\t// it not should happen.\n\t// we will check anyway to prevent issues in the underline implementation.\n\n\tif !ok {\n\t\treturn addr.String() + \" is not on the controller list\"\n\t}\n\n\treturn \"\"\n}\n\nfunc assertIsAdmin() error {\n\tcaller := std.OriginCaller()\n\tif caller != gAdminAddr {\n\t\treturn errors.New(\"restricted for admin\")\n\t}\n\treturn nil\n}\n"},{"name":"faucet.gno","body":"package faucet\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar (\n\t// configurable by admin.\n\tgAdminAddr std.Address = std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")\n\tgControllers = avl.NewTree()\n\tgControllersMaxSize = 10 // limit it to 10\n\tgInPause = false\n\tgMessage = \"# Community Faucet.\\n\\n\"\n\n\t// internal vars, for stats.\n\tgTotalTransferred std.Coins\n\tgTotalTransfers = uint(0)\n\n\t// per request limit, 350 gnot\n\tgLimit std.Coin = std.NewCoin(\"ugnot\", 350000000)\n)\n\nfunc Transfer(to std.Address, send int64) string {\n\tif err := assertIsController(); err != nil {\n\t\treturn err.Error()\n\t}\n\n\tif gInPause {\n\t\treturn errors.New(\"faucet in pause\").Error()\n\t}\n\n\t// limit the per request\n\tif send \u003e gLimit.Amount {\n\t\treturn errors.New(\"Per request limit \" + gLimit.String() + \" exceed\").Error()\n\t}\n\tsendCoins := std.Coins{std.NewCoin(\"ugnot\", send)}\n\n\tgTotalTransferred = gTotalTransferred.Add(sendCoins)\n\tgTotalTransfers++\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tpkgaddr := std.CurrentRealm().Addr()\n\tbanker.SendCoins(pkgaddr, to, sendCoins)\n\treturn \"\"\n}\n\nfunc GetPerTransferLimit() int64 {\n\treturn gLimit.Amount\n}\n\nfunc Render(_ string) string {\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tbalance := banker.GetCoins(std.CurrentRealm().Addr())\n\n\toutput := gMessage\n\tif gInPause {\n\t\toutput += \"Status: inactive.\\n\"\n\t} else {\n\t\toutput += \"Status: active.\\n\"\n\t}\n\toutput += ufmt.Sprintf(\"Balance: %s.\\n\", balance.String())\n\toutput += ufmt.Sprintf(\"Total transfers: %s (in %d times).\\n\\n\", gTotalTransferred.String(), gTotalTransfers)\n\n\toutput += \"Package address: \" + std.CurrentRealm().Addr().String() + \"\\n\\n\"\n\toutput += ufmt.Sprintf(\"Admin: %s\\n\\n \", gAdminAddr.String())\n\toutput += ufmt.Sprintf(\"Controllers:\\n\\n \")\n\n\tfor i := 0; i \u003c gControllers.Size(); i++ {\n\t\t_, v := gControllers.GetByIndex(i)\n\t\toutput += ufmt.Sprintf(\"%s \", v.(std.Address))\n\t}\n\n\toutput += \"\\n\\n\"\n\toutput += ufmt.Sprintf(\"Per request limit: %s\\n\\n\", gLimit.String())\n\n\treturn output\n}\n\nfunc assertIsController() error {\n\tcaller := std.OriginCaller()\n\n\tok := gControllers.Has(caller.String())\n\tif !ok {\n\t\treturn errors.New(caller.String() + \" is not on the controller list\")\n\t}\n\treturn nil\n}\n"},{"name":"faucet_test.gno","body":"package faucet\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/gnoland/faucet\"\n)\n\nfunc TestPackage(t *testing.T) {\n\tvar (\n\t\tadminaddr = std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")\n\t\tfaucetaddr = std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\t\tcontrolleraddr1 = testutils.TestAddress(\"controller1\")\n\t\tcontrolleraddr2 = testutils.TestAddress(\"controller2\")\n\t\tcontrolleraddr3 = testutils.TestAddress(\"controller3\")\n\t\tcontrolleraddr4 = testutils.TestAddress(\"controller4\")\n\t\tcontrolleraddr5 = testutils.TestAddress(\"controller5\")\n\t\tcontrolleraddr6 = testutils.TestAddress(\"controller6\")\n\t\tcontrolleraddr7 = testutils.TestAddress(\"controller7\")\n\t\tcontrolleraddr8 = testutils.TestAddress(\"controller8\")\n\t\tcontrolleraddr9 = testutils.TestAddress(\"controller9\")\n\t\tcontrolleraddr10 = testutils.TestAddress(\"controller10\")\n\t\tcontrolleraddr11 = testutils.TestAddress(\"controller11\")\n\n\t\ttest1addr = testutils.TestAddress(\"test1\")\n\t)\n\t// deposit 1000gnot to faucet contract\n\tstd.TestIssueCoins(faucetaddr, std.Coins{{\"ugnot\", 1000000000}})\n\tassertBalance(t, faucetaddr, 1200000000)\n\n\t// by default, balance is empty, and as a user I cannot call Transfer, or Admin commands.\n\n\tassertBalance(t, test1addr, 0)\n\tstd.TestSetOriginCaller(test1addr)\n\tassertErr(t, faucet.Transfer(test1addr, 1000000))\n\n\tassertErr(t, faucet.AdminAddController(controlleraddr1))\n\tstd.TestSetOriginCaller(controlleraddr1)\n\tassertErr(t, faucet.Transfer(test1addr, 1000000))\n\n\t// as an admin, add the controller to contract and deposit more 2000gnot to contract\n\tstd.TestSetOriginCaller(adminaddr)\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr1))\n\tassertBalance(t, faucetaddr, 1200000000)\n\n\t// now, send some tokens as controller.\n\tstd.TestSetOriginCaller(controlleraddr1)\n\tassertNoErr(t, faucet.Transfer(test1addr, 1000000))\n\tassertBalance(t, test1addr, 1000000)\n\tassertNoErr(t, faucet.Transfer(test1addr, 1000000))\n\tassertBalance(t, test1addr, 2000000)\n\tassertBalance(t, faucetaddr, 1198000000)\n\n\t// remove controller\n\t// as an admin, remove controller\n\tstd.TestSetOriginCaller(adminaddr)\n\tassertNoErr(t, faucet.AdminRemoveController(controlleraddr1))\n\tstd.TestSetOriginCaller(controlleraddr1)\n\tassertErr(t, faucet.Transfer(test1addr, 1000000))\n\n\t// duplicate controller\n\tstd.TestSetOriginCaller(adminaddr)\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr1))\n\tassertErr(t, faucet.AdminAddController(controlleraddr1))\n\t// add more than more than allowed controllers\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr2))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr3))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr4))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr5))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr6))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr7))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr8))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr9))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr10))\n\tassertErr(t, faucet.AdminAddController(controlleraddr11))\n\n\t// send more than per transfer limit\n\tstd.TestSetOriginCaller(adminaddr)\n\tfaucet.AdminSetTransferLimit(300000000)\n\tstd.TestSetOriginCaller(controlleraddr1)\n\tassertErr(t, faucet.Transfer(test1addr, 301000000))\n\n\t// block transefer from the address not on the controllers list.\n\tstd.TestSetOriginCaller(controlleraddr11)\n\tassertErr(t, faucet.Transfer(test1addr, 1000000))\n}\n\nfunc assertErr(t *testing.T, err string) {\n\tt.Helper()\n\n\tif err == \"\" {\n\t\tt.Logf(\"info: got err: %v\", err)\n\t\tt.Errorf(\"expected an error, got nil.\")\n\t}\n}\n\nfunc assertNoErr(t *testing.T, err string) {\n\tt.Helper()\n\tif err != \"\" {\n\t\tt.Errorf(\"got err: %v.\", err)\n\t}\n}\n\nfunc assertBalance(t *testing.T, addr std.Address, expectedBal int64) {\n\tt.Helper()\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tcoins := banker.GetCoins(addr)\n\tgot := coins.AmountOf(\"ugnot\")\n\n\tif expectedBal != got {\n\t\tt.Errorf(\"invalid balance: expected %d, got %d.\", expectedBal, got)\n\t}\n}\n"},{"name":"z0_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/gnoland/faucet\"\n)\n\n// mints ugnot to current realm\nfunc init() {\n\tfacuetaddr := std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\tstd.TestIssueCoins(facuetaddr, std.Coins{{\"ugnot\", 200000000}})\n}\n\n// assert render with empty path and no controllers\nfunc main() {\n\tprintln(faucet.Render(\"\"))\n}\n\n// Output:\n// # Community Faucet.\n//\n// Status: active.\n// Balance: 200000000ugnot.\n// Total transfers: (in 0 times).\n//\n// Package address: g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7\n//\n// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n//\n// Controllers:\n//\n//\n//\n// Per request limit: 350000000ugnot\n"},{"name":"z1_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/gnoland/faucet\"\n)\n\n// mints ugnot to current realm\nfunc init() {\n\tfacuetaddr := std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\tstd.TestIssueCoins(facuetaddr, std.Coins{{\"ugnot\", 200000000}})\n}\n\n// assert render with a path and no controllers\nfunc main() {\n\tprintln(faucet.Render(\"path\"))\n}\n\n// Output:\n// # Community Faucet.\n//\n// Status: active.\n// Balance: 200000000ugnot.\n// Total transfers: (in 0 times).\n//\n// Package address: g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7\n//\n// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n//\n// Controllers:\n//\n//\n//\n// Per request limit: 350000000ugnot\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/gnoland/faucet\"\n)\n\n// mints ugnot to current realm\nfunc init() {\n\tfacuetaddr := std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\tstd.TestIssueCoins(facuetaddr, std.Coins{{\"ugnot\", 200000000}})\n}\n\n// assert render with empty path and 2 controllers\nfunc main() {\n\tvar (\n\t\tadminaddr = std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")\n\t\tcontrolleraddr1 = testutils.TestAddress(\"controller1\")\n\t\tcontrolleraddr2 = testutils.TestAddress(\"controller2\")\n\t)\n\tstd.TestSetOriginCaller(adminaddr)\n\terr := faucet.AdminAddController(controlleraddr1)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\terr = faucet.AdminAddController(controlleraddr2)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\tprintln(faucet.Render(\"\"))\n}\n\n// Output:\n// # Community Faucet.\n//\n// Status: active.\n// Balance: 200000000ugnot.\n// Total transfers: (in 0 times).\n//\n// Package address: g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7\n//\n// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n//\n// Controllers:\n//\n// g1vdhkuarjdakxcetjx9047h6lta047h6lsdacav g1vdhkuarjdakxcetjxf047h6lta047h6lnrev3v\n//\n// Per request limit: 350000000ugnot\n"},{"name":"z3_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/gnoland/faucet\"\n)\n\n// mints coints to current realm\nfunc init() {\n\tfacuetaddr := std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\tstd.TestIssueCoins(facuetaddr, std.Coins{{\"ugnot\", 200000000}})\n}\n\n// assert render with 2 controllers and 2 transfers\nfunc main() {\n\tvar (\n\t\tadminaddr = std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")\n\t\tcontrolleraddr1 = testutils.TestAddress(\"controller1\")\n\t\tcontrolleraddr2 = testutils.TestAddress(\"controller2\")\n\t\ttestaddr1 = testutils.TestAddress(\"test1\")\n\t\ttestaddr2 = testutils.TestAddress(\"test2\")\n\t)\n\tstd.TestSetOriginCaller(adminaddr)\n\terr := faucet.AdminAddController(controlleraddr1)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\terr = faucet.AdminAddController(controlleraddr2)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\tstd.TestSetOriginCaller(controlleraddr1)\n\terr = faucet.Transfer(testaddr1, 1000000)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\tstd.TestSetOriginCaller(controlleraddr2)\n\terr = faucet.Transfer(testaddr1, 2000000)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\tprintln(faucet.Render(\"\"))\n}\n\n// Output:\n// # Community Faucet.\n//\n// Status: active.\n// Balance: 197000000ugnot.\n// Total transfers: 3000000ugnot (in 2 times).\n//\n// Package address: g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7\n//\n// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n//\n// Controllers:\n//\n// g1vdhkuarjdakxcetjx9047h6lta047h6lsdacav g1vdhkuarjdakxcetjxf047h6lta047h6lnrev3v\n//\n// Per request limit: 350000000ugnot\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":"faucet","path":"gno.land/r/gnoland/faucet","files":[{"name":"admin.gno","body":"package faucet\n\nimport (\n\t\"errors\"\n\t\"std\"\n)\n\nfunc AdminSetInPause(inPause bool) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\tgInPause = inPause\n\treturn \"\"\n}\n\nfunc AdminSetMessage(message string) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\tgMessage = message\n\treturn \"\"\n}\n\nfunc AdminSetTransferLimit(amount int64) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\tgLimit = std.NewCoin(\"ugnot\", amount)\n\treturn \"\"\n}\n\nfunc AdminSetAdminAddr(addr std.Address) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\tgAdminAddr = addr\n\treturn \"\"\n}\n\nfunc AdminAddController(addr std.Address) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\n\tsize := gControllers.Size()\n\n\tif size \u003e= gControllersMaxSize {\n\t\treturn \"can not add more controllers than allowed\"\n\t}\n\n\tif gControllers.Has(addr.String()) {\n\t\treturn addr.String() + \" exists, no need to add.\"\n\t}\n\n\tgControllers.Set(addr.String(), addr)\n\n\treturn \"\"\n}\n\nfunc AdminRemoveController(addr std.Address) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\n\tif !gControllers.Has(addr.String()) {\n\t\treturn addr.String() + \" is not on the controller list\"\n\t}\n\n\t_, ok := gControllers.Remove(addr.String())\n\n\t// it not should happen.\n\t// we will check anyway to prevent issues in the underline implementation.\n\n\tif !ok {\n\t\treturn addr.String() + \" is not on the controller list\"\n\t}\n\n\treturn \"\"\n}\n\nfunc assertIsAdmin() error {\n\tcaller := std.OriginCaller()\n\tif caller != gAdminAddr {\n\t\treturn errors.New(\"restricted for admin\")\n\t}\n\treturn nil\n}\n"},{"name":"faucet.gno","body":"package faucet\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar (\n\t// configurable by admin.\n\tgAdminAddr std.Address = std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")\n\tgControllers = avl.NewTree()\n\tgControllersMaxSize = 10 // limit it to 10\n\tgInPause = false\n\tgMessage = \"# Community Faucet.\\n\\n\"\n\n\t// internal vars, for stats.\n\tgTotalTransferred std.Coins\n\tgTotalTransfers = uint(0)\n\n\t// per request limit, 350 gnot\n\tgLimit std.Coin = std.NewCoin(\"ugnot\", 350000000)\n)\n\nfunc Transfer(to std.Address, send int64) string {\n\tif err := assertIsController(); err != nil {\n\t\treturn err.Error()\n\t}\n\n\tif gInPause {\n\t\treturn errors.New(\"faucet in pause\").Error()\n\t}\n\n\t// limit the per request\n\tif send \u003e gLimit.Amount {\n\t\treturn errors.New(\"Per request limit \" + gLimit.String() + \" exceed\").Error()\n\t}\n\tsendCoins := std.Coins{std.NewCoin(\"ugnot\", send)}\n\n\tgTotalTransferred = gTotalTransferred.Add(sendCoins)\n\tgTotalTransfers++\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tpkgaddr := std.CurrentRealm().Address()\n\tbanker.SendCoins(pkgaddr, to, sendCoins)\n\treturn \"\"\n}\n\nfunc GetPerTransferLimit() int64 {\n\treturn gLimit.Amount\n}\n\nfunc Render(_ string) string {\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tbalance := banker.GetCoins(std.CurrentRealm().Address())\n\n\toutput := gMessage\n\tif gInPause {\n\t\toutput += \"Status: inactive.\\n\"\n\t} else {\n\t\toutput += \"Status: active.\\n\"\n\t}\n\toutput += ufmt.Sprintf(\"Balance: %s.\\n\", balance.String())\n\toutput += ufmt.Sprintf(\"Total transfers: %s (in %d times).\\n\\n\", gTotalTransferred.String(), gTotalTransfers)\n\n\toutput += \"Package address: \" + std.CurrentRealm().Address().String() + \"\\n\\n\"\n\toutput += ufmt.Sprintf(\"Admin: %s\\n\\n \", gAdminAddr.String())\n\toutput += ufmt.Sprintf(\"Controllers:\\n\\n \")\n\n\tfor i := 0; i \u003c gControllers.Size(); i++ {\n\t\t_, v := gControllers.GetByIndex(i)\n\t\toutput += ufmt.Sprintf(\"%s \", v.(std.Address))\n\t}\n\n\toutput += \"\\n\\n\"\n\toutput += ufmt.Sprintf(\"Per request limit: %s\\n\\n\", gLimit.String())\n\n\treturn output\n}\n\nfunc assertIsController() error {\n\tcaller := std.OriginCaller()\n\n\tok := gControllers.Has(caller.String())\n\tif !ok {\n\t\treturn errors.New(caller.String() + \" is not on the controller list\")\n\t}\n\treturn nil\n}\n"},{"name":"faucet_test.gno","body":"package faucet\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/gnoland/faucet\"\n)\n\nfunc TestPackage(t *testing.T) {\n\tvar (\n\t\tadminaddr = std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")\n\t\tfaucetaddr = std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\t\tcontrolleraddr1 = testutils.TestAddress(\"controller1\")\n\t\tcontrolleraddr2 = testutils.TestAddress(\"controller2\")\n\t\tcontrolleraddr3 = testutils.TestAddress(\"controller3\")\n\t\tcontrolleraddr4 = testutils.TestAddress(\"controller4\")\n\t\tcontrolleraddr5 = testutils.TestAddress(\"controller5\")\n\t\tcontrolleraddr6 = testutils.TestAddress(\"controller6\")\n\t\tcontrolleraddr7 = testutils.TestAddress(\"controller7\")\n\t\tcontrolleraddr8 = testutils.TestAddress(\"controller8\")\n\t\tcontrolleraddr9 = testutils.TestAddress(\"controller9\")\n\t\tcontrolleraddr10 = testutils.TestAddress(\"controller10\")\n\t\tcontrolleraddr11 = testutils.TestAddress(\"controller11\")\n\n\t\ttest1addr = testutils.TestAddress(\"test1\")\n\t)\n\t// deposit 1000gnot to faucet contract\n\tstd.TestIssueCoins(faucetaddr, std.Coins{{\"ugnot\", 1000000000}})\n\tassertBalance(t, faucetaddr, 1200000000)\n\n\t// by default, balance is empty, and as a user I cannot call Transfer, or Admin commands.\n\n\tassertBalance(t, test1addr, 0)\n\tstd.TestSetOriginCaller(test1addr)\n\tassertErr(t, faucet.Transfer(test1addr, 1000000))\n\n\tassertErr(t, faucet.AdminAddController(controlleraddr1))\n\tstd.TestSetOriginCaller(controlleraddr1)\n\tassertErr(t, faucet.Transfer(test1addr, 1000000))\n\n\t// as an admin, add the controller to contract and deposit more 2000gnot to contract\n\tstd.TestSetOriginCaller(adminaddr)\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr1))\n\tassertBalance(t, faucetaddr, 1200000000)\n\n\t// now, send some tokens as controller.\n\tstd.TestSetOriginCaller(controlleraddr1)\n\tassertNoErr(t, faucet.Transfer(test1addr, 1000000))\n\tassertBalance(t, test1addr, 1000000)\n\tassertNoErr(t, faucet.Transfer(test1addr, 1000000))\n\tassertBalance(t, test1addr, 2000000)\n\tassertBalance(t, faucetaddr, 1198000000)\n\n\t// remove controller\n\t// as an admin, remove controller\n\tstd.TestSetOriginCaller(adminaddr)\n\tassertNoErr(t, faucet.AdminRemoveController(controlleraddr1))\n\tstd.TestSetOriginCaller(controlleraddr1)\n\tassertErr(t, faucet.Transfer(test1addr, 1000000))\n\n\t// duplicate controller\n\tstd.TestSetOriginCaller(adminaddr)\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr1))\n\tassertErr(t, faucet.AdminAddController(controlleraddr1))\n\t// add more than more than allowed controllers\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr2))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr3))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr4))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr5))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr6))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr7))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr8))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr9))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr10))\n\tassertErr(t, faucet.AdminAddController(controlleraddr11))\n\n\t// send more than per transfer limit\n\tstd.TestSetOriginCaller(adminaddr)\n\tfaucet.AdminSetTransferLimit(300000000)\n\tstd.TestSetOriginCaller(controlleraddr1)\n\tassertErr(t, faucet.Transfer(test1addr, 301000000))\n\n\t// block transefer from the address not on the controllers list.\n\tstd.TestSetOriginCaller(controlleraddr11)\n\tassertErr(t, faucet.Transfer(test1addr, 1000000))\n}\n\nfunc assertErr(t *testing.T, err string) {\n\tt.Helper()\n\n\tif err == \"\" {\n\t\tt.Logf(\"info: got err: %v\", err)\n\t\tt.Errorf(\"expected an error, got nil.\")\n\t}\n}\n\nfunc assertNoErr(t *testing.T, err string) {\n\tt.Helper()\n\tif err != \"\" {\n\t\tt.Errorf(\"got err: %v.\", err)\n\t}\n}\n\nfunc assertBalance(t *testing.T, addr std.Address, expectedBal int64) {\n\tt.Helper()\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tcoins := banker.GetCoins(addr)\n\tgot := coins.AmountOf(\"ugnot\")\n\n\tif expectedBal != got {\n\t\tt.Errorf(\"invalid balance: expected %d, got %d.\", expectedBal, got)\n\t}\n}\n"},{"name":"z0_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/gnoland/faucet\"\n)\n\n// mints ugnot to current realm\nfunc init() {\n\tfacuetaddr := std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\tstd.TestIssueCoins(facuetaddr, std.Coins{{\"ugnot\", 200000000}})\n}\n\n// assert render with empty path and no controllers\nfunc main() {\n\tprintln(faucet.Render(\"\"))\n}\n\n// Output:\n// # Community Faucet.\n//\n// Status: active.\n// Balance: 200000000ugnot.\n// Total transfers: (in 0 times).\n//\n// Package address: g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7\n//\n// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n//\n// Controllers:\n//\n//\n//\n// Per request limit: 350000000ugnot\n"},{"name":"z1_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/gnoland/faucet\"\n)\n\n// mints ugnot to current realm\nfunc init() {\n\tfacuetaddr := std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\tstd.TestIssueCoins(facuetaddr, std.Coins{{\"ugnot\", 200000000}})\n}\n\n// assert render with a path and no controllers\nfunc main() {\n\tprintln(faucet.Render(\"path\"))\n}\n\n// Output:\n// # Community Faucet.\n//\n// Status: active.\n// Balance: 200000000ugnot.\n// Total transfers: (in 0 times).\n//\n// Package address: g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7\n//\n// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n//\n// Controllers:\n//\n//\n//\n// Per request limit: 350000000ugnot\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/gnoland/faucet\"\n)\n\n// mints ugnot to current realm\nfunc init() {\n\tfacuetaddr := std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\tstd.TestIssueCoins(facuetaddr, std.Coins{{\"ugnot\", 200000000}})\n}\n\n// assert render with empty path and 2 controllers\nfunc main() {\n\tvar (\n\t\tadminaddr = std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")\n\t\tcontrolleraddr1 = testutils.TestAddress(\"controller1\")\n\t\tcontrolleraddr2 = testutils.TestAddress(\"controller2\")\n\t)\n\tstd.TestSetOriginCaller(adminaddr)\n\terr := faucet.AdminAddController(controlleraddr1)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\terr = faucet.AdminAddController(controlleraddr2)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\tprintln(faucet.Render(\"\"))\n}\n\n// Output:\n// # Community Faucet.\n//\n// Status: active.\n// Balance: 200000000ugnot.\n// Total transfers: (in 0 times).\n//\n// Package address: g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7\n//\n// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n//\n// Controllers:\n//\n// g1vdhkuarjdakxcetjx9047h6lta047h6lsdacav g1vdhkuarjdakxcetjxf047h6lta047h6lnrev3v\n//\n// Per request limit: 350000000ugnot\n"},{"name":"z3_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/gnoland/faucet\"\n)\n\n// mints coints to current realm\nfunc init() {\n\tfacuetaddr := std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\tstd.TestIssueCoins(facuetaddr, std.Coins{{\"ugnot\", 200000000}})\n}\n\n// assert render with 2 controllers and 2 transfers\nfunc main() {\n\tvar (\n\t\tadminaddr = std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")\n\t\tcontrolleraddr1 = testutils.TestAddress(\"controller1\")\n\t\tcontrolleraddr2 = testutils.TestAddress(\"controller2\")\n\t\ttestaddr1 = testutils.TestAddress(\"test1\")\n\t\ttestaddr2 = testutils.TestAddress(\"test2\")\n\t)\n\tstd.TestSetOriginCaller(adminaddr)\n\terr := faucet.AdminAddController(controlleraddr1)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\terr = faucet.AdminAddController(controlleraddr2)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\tstd.TestSetOriginCaller(controlleraddr1)\n\terr = faucet.Transfer(testaddr1, 1000000)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\tstd.TestSetOriginCaller(controlleraddr2)\n\terr = faucet.Transfer(testaddr1, 2000000)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\tprintln(faucet.Render(\"\"))\n}\n\n// Output:\n// # Community Faucet.\n//\n// Status: active.\n// Balance: 197000000ugnot.\n// Total transfers: 3000000ugnot (in 2 times).\n//\n// Package address: g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7\n//\n// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n//\n// Controllers:\n//\n// g1vdhkuarjdakxcetjx9047h6lta047h6lsdacav g1vdhkuarjdakxcetjxf047h6lta047h6lnrev3v\n//\n// Per request limit: 350000000ugnot\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":"ghverify","path":"gno.land/r/gnoland/ghverify","files":[{"name":"README.md","body":"# ghverify\n\nThis realm is intended to enable off chain gno address to github handle verification.\nThe steps are as follows:\n- A user calls `RequestVerification` and provides a github handle. This creates a new static oracle feed.\n- An off-chain agent controlled by the owner of this realm requests current feeds using the `GnorkleEntrypoint` function and provides a message of `\"request\"`\n- The agent receives the task information that includes the github handle and the gno address. It performs the verification step by checking whether this github user has the address in a github repository it controls.\n- The agent publishes the result of the verification by calling `GnorkleEntrypoint` with a message structured like: `\"ingest,\u003ctask id\u003e,\u003cverification status\u003e\"`. The verification status is `OK` if verification succeeded and any other value if it failed.\n- The oracle feed's ingester processes the verification and the handle to address mapping is written to the avl trees that exist as ghverify realm variables."},{"name":"contract.gno","body":"package ghverify\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/gnorkle/feeds/static\"\n\t\"gno.land/p/demo/gnorkle/gnorkle\"\n\t\"gno.land/p/demo/gnorkle/message\"\n)\n\nconst (\n\t// The agent should send this value if it has verified the github handle.\n\tverifiedResult = \"OK\"\n)\n\nvar (\n\townerAddress = std.OriginCaller()\n\toracle *gnorkle.Instance\n\tpostHandler postGnorkleMessageHandler\n\n\thandleToAddressMap = avl.NewTree()\n\taddressToHandleMap = avl.NewTree()\n)\n\nfunc init() {\n\toracle = gnorkle.NewInstance()\n\toracle.AddToWhitelist(\"\", []string{string(ownerAddress)})\n}\n\ntype postGnorkleMessageHandler struct{}\n\n// Handle does post processing after a message is ingested by the oracle feed. It extracts the value to realm\n// storage and removes the feed from the oracle.\nfunc (h postGnorkleMessageHandler) Handle(i *gnorkle.Instance, funcType message.FuncType, feed gnorkle.Feed) error {\n\tif funcType != message.FuncTypeIngest {\n\t\treturn nil\n\t}\n\n\tresult, _, consumable := feed.Value()\n\tif !consumable {\n\t\treturn nil\n\t}\n\n\t// The value is consumable, meaning the ingestion occurred, so we can remove the feed from the oracle\n\t// after saving it to realm storage.\n\tdefer oracle.RemoveFeed(feed.ID())\n\n\t// Couldn't verify; nothing to do.\n\tif result.String != verifiedResult {\n\t\treturn nil\n\t}\n\n\tfeedTasks := feed.Tasks()\n\tif len(feedTasks) != 1 {\n\t\treturn errors.New(\"expected feed to have exactly one task\")\n\t}\n\n\ttask, ok := feedTasks[0].(*verificationTask)\n\tif !ok {\n\t\treturn errors.New(\"expected ghverify task\")\n\t}\n\n\thandleToAddressMap.Set(task.githubHandle, task.gnoAddress)\n\taddressToHandleMap.Set(task.gnoAddress, task.githubHandle)\n\treturn nil\n}\n\n// RequestVerification creates a new static feed with a single task that will\n// instruct an agent to verify the github handle / gno address pair.\nfunc RequestVerification(githubHandle string) {\n\tgnoAddress := string(std.OriginCaller())\n\tif err := oracle.AddFeeds(\n\t\tstatic.NewSingleValueFeed(\n\t\t\tgnoAddress,\n\t\t\t\"string\",\n\t\t\t\u0026verificationTask{\n\t\t\t\tgnoAddress: gnoAddress,\n\t\t\t\tgithubHandle: githubHandle,\n\t\t\t},\n\t\t),\n\t); err != nil {\n\t\tpanic(err)\n\t}\n\tstd.Emit(\n\t\t\"verification_requested\",\n\t\t\"from\", gnoAddress,\n\t\t\"handle\", githubHandle,\n\t)\n}\n\n// GnorkleEntrypoint is the entrypoint to the gnorkle oracle handler.\nfunc GnorkleEntrypoint(message string) string {\n\tresult, err := oracle.HandleMessage(message, postHandler)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn result\n}\n\n// SetOwner transfers ownership of the contract to the given address.\nfunc SetOwner(owner std.Address) {\n\tif ownerAddress != std.OriginCaller() {\n\t\tpanic(\"only the owner can set a new owner\")\n\t}\n\n\townerAddress = owner\n\n\t// In the context of this contract, the owner is the only one that can\n\t// add new feeds to the oracle.\n\toracle.ClearWhitelist(\"\")\n\toracle.AddToWhitelist(\"\", []string{string(ownerAddress)})\n}\n\n// GetHandleByAddress returns the github handle associated with the given gno address.\nfunc GetHandleByAddress(address string) string {\n\tif value, ok := addressToHandleMap.Get(address); ok {\n\t\treturn value.(string)\n\t}\n\n\treturn \"\"\n}\n\n// GetAddressByHandle returns the gno address associated with the given github handle.\nfunc GetAddressByHandle(handle string) string {\n\tif value, ok := handleToAddressMap.Get(handle); ok {\n\t\treturn value.(string)\n\t}\n\n\treturn \"\"\n}\n\n// Render returns a json object string will all verified handle -\u003e address mappings.\nfunc Render(_ string) string {\n\tresult := \"{\"\n\tvar appendComma bool\n\thandleToAddressMap.Iterate(\"\", \"\", func(handle string, address interface{}) bool {\n\t\tif appendComma {\n\t\t\tresult += \",\"\n\t\t}\n\n\t\tresult += `\"` + handle + `\": \"` + address.(string) + `\"`\n\t\tappendComma = true\n\n\t\treturn false\n\t})\n\n\treturn result + \"}\"\n}\n"},{"name":"contract_test.gno","body":"package ghverify\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n)\n\nfunc TestVerificationLifecycle(t *testing.T) {\n\tdefaultAddress := std.OriginCaller()\n\tuser1Address := std.Address(testutils.TestAddress(\"user 1\"))\n\tuser2Address := std.Address(testutils.TestAddress(\"user 2\"))\n\n\t// Verify request returns no feeds.\n\tresult := GnorkleEntrypoint(\"request\")\n\tif result != \"[]\" {\n\t\tt.Fatalf(\"expected empty request result, got %s\", result)\n\t}\n\n\t// Make a verification request with the created user.\n\tstd.TestSetOriginCaller(user1Address)\n\tRequestVerification(\"deelawn\")\n\n\t// A subsequent request from the same address should panic because there is\n\t// already a feed with an ID of this user's address.\n\tvar errMsg string\n\tfunc() {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\terrMsg = r.(error).Error()\n\t\t\t}\n\t\t}()\n\t\tRequestVerification(\"deelawn\")\n\t}()\n\tif errMsg != \"feed already exists\" {\n\t\tt.Fatalf(\"expected feed already exists, got %s\", errMsg)\n\t}\n\n\t// Verify the request returns no feeds for this non-whitelisted user.\n\tresult = GnorkleEntrypoint(\"request\")\n\tif result != \"[]\" {\n\t\tt.Fatalf(\"expected empty request result, got %s\", result)\n\t}\n\n\t// Make a verification request with the created user.\n\tstd.TestSetOriginCaller(user2Address)\n\tRequestVerification(\"omarsy\")\n\n\t// Set the caller back to the whitelisted user and verify that the feed data\n\t// returned matches what should have been created by the `RequestVerification`\n\t// invocation.\n\tstd.TestSetOriginCaller(defaultAddress)\n\tresult = GnorkleEntrypoint(\"request\")\n\texpResult := `[{\"id\":\"` + string(user1Address) + `\",\"type\":\"0\",\"value_type\":\"string\",\"tasks\":[{\"gno_address\":\"` +\n\t\tstring(user1Address) + `\",\"github_handle\":\"deelawn\"}]},` +\n\t\t`{\"id\":\"` + string(user2Address) + `\",\"type\":\"0\",\"value_type\":\"string\",\"tasks\":[{\"gno_address\":\"` +\n\t\tstring(user2Address) + `\",\"github_handle\":\"omarsy\"}]}]`\n\tif result != expResult {\n\t\tt.Fatalf(\"expected request result %s, got %s\", expResult, result)\n\t}\n\n\t// Try to trigger feed ingestion from the non-authorized user.\n\tstd.TestSetOriginCaller(user1Address)\n\tfunc() {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\terrMsg = r.(error).Error()\n\t\t\t}\n\t\t}()\n\t\tGnorkleEntrypoint(\"ingest,\" + string(user1Address) + \",OK\")\n\t}()\n\tif errMsg != \"caller not whitelisted\" {\n\t\tt.Fatalf(\"expected caller not whitelisted, got %s\", errMsg)\n\t}\n\n\t// Set the caller back to the whitelisted user and transfer contract ownership.\n\tstd.TestSetOriginCaller(defaultAddress)\n\tSetOwner(defaultAddress)\n\n\t// Now trigger the feed ingestion from the user and new owner and only whitelisted address.\n\tGnorkleEntrypoint(\"ingest,\" + string(user1Address) + \",OK\")\n\tGnorkleEntrypoint(\"ingest,\" + string(user2Address) + \",OK\")\n\n\t// Verify the ingestion autocommitted the value and triggered the post handler.\n\tdata := Render(\"\")\n\texpResult = `{\"deelawn\": \"` + string(user1Address) + `\",\"omarsy\": \"` + string(user2Address) + `\"}`\n\tif data != expResult {\n\t\tt.Fatalf(\"expected render data %s, got %s\", expResult, data)\n\t}\n\n\t// Finally make sure the feed was cleaned up after the data was committed.\n\tresult = GnorkleEntrypoint(\"request\")\n\tif result != \"[]\" {\n\t\tt.Fatalf(\"expected empty request result, got %s\", result)\n\t}\n\n\t// Check that the accessor functions are working as expected.\n\tif handle := GetHandleByAddress(string(user1Address)); handle != \"deelawn\" {\n\t\tt.Fatalf(\"expected deelawn, got %s\", handle)\n\t}\n\tif address := GetAddressByHandle(\"deelawn\"); address != string(user1Address) {\n\t\tt.Fatalf(\"expected %s, got %s\", string(user1Address), address)\n\t}\n}\n"},{"name":"task.gno","body":"package ghverify\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n)\n\ntype verificationTask struct {\n\tgnoAddress string\n\tgithubHandle string\n}\n\n// MarshalJSON marshals the task contents to JSON.\nfunc (t *verificationTask) MarshalJSON() ([]byte, error) {\n\tbuf := new(bytes.Buffer)\n\tw := bufio.NewWriter(buf)\n\n\tw.Write(\n\t\t[]byte(`{\"gno_address\":\"` + t.gnoAddress + `\",\"github_handle\":\"` + t.githubHandle + `\"}`),\n\t)\n\n\tw.Flush()\n\treturn buf.Bytes(), nil\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":"home","path":"gno.land/r/gnoland/home","files":[{"name":"home.gno","body":"package home\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/p/demo/ui\"\n\tblog \"gno.land/r/gnoland/blog\"\n\tevents \"gno.land/r/gnoland/events\"\n)\n\n// XXX: p/demo/ui API is crappy, we need to make it more idiomatic\n// XXX: use an updatable block system to update content from a DAO\n// XXX: var blocks avl.Tree\n\nvar (\n\toverride string\n\tadmin = ownable.NewWithAddress(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\") // @manfred by default\n)\n\nfunc Render(_ string) string {\n\tif override != \"\" {\n\t\treturn override\n\t}\n\n\tdom := ui.DOM{Prefix: \"r/gnoland/home:\"}\n\tdom.Title = \"Welcome to gno.land\"\n\tdom.Classes = []string{\"gno-tmpl-section\"}\n\n\t// body\n\tdom.Body.Append(introSection()...)\n\n\tdom.Body.Append(ui.Jumbotron(discoverLinks()))\n\n\tdom.Body.Append(\n\t\tui.Columns{3, []ui.Element{\n\t\t\tlastBlogposts(4),\n\t\t\tupcomingEvents(),\n\t\t\tlastContributions(4),\n\t\t}},\n\t)\n\n\tdom.Body.Append(ui.HR{})\n\tdom.Body.Append(playgroundSection()...)\n\tdom.Body.Append(ui.HR{})\n\tdom.Body.Append(packageStaffPicks()...)\n\tdom.Body.Append(ui.HR{})\n\tdom.Body.Append(worxDAO()...)\n\tdom.Body.Append(ui.HR{})\n\t// footer\n\tdom.Footer.Append(\n\t\tui.Columns{2, []ui.Element{\n\t\t\tsocialLinks(),\n\t\t\tquoteOfTheBlock(),\n\t\t}},\n\t)\n\n\t// Testnet disclaimer\n\tdom.Footer.Append(\n\t\tui.HR{},\n\t\tui.Bold(\"This is a testnet.\"),\n\t\tui.Text(\"Package names are not guaranteed to be available for production.\"),\n\t)\n\n\treturn dom.String()\n}\n\nfunc lastBlogposts(limit int) ui.Element {\n\tposts := blog.RenderLastPostsWidget(limit)\n\treturn ui.Element{\n\t\tui.H3(\"[Latest Blogposts](/r/gnoland/blog)\"),\n\t\tui.Text(posts),\n\t}\n}\n\nfunc lastContributions(limit int) ui.Element {\n\treturn ui.Element{\n\t\tui.H3(\"Latest Contributions\"),\n\t\t// TODO: import r/gh to\n\t\tui.Link{Text: \"View latest contributions\", URL: \"https://github.com/gnolang/gno/pulls\"},\n\t}\n}\n\nfunc upcomingEvents() ui.Element {\n\tout, _ := events.RenderEventWidget(events.MaxWidgetSize)\n\treturn ui.Element{\n\t\tui.H3(\"[Latest Events](/r/gnoland/events)\"),\n\t\tui.Text(out),\n\t}\n}\n\nfunc introSection() ui.Element {\n\treturn ui.Element{\n\t\tui.H3(\"We’re building gno.land, set to become the leading open-source smart contract platform, using Gno, an interpreted and fully deterministic variation of the Go programming language for succinct and composable smart contracts.\"),\n\t\tui.Paragraph(\"With transparent and timeless code, gno.land is the next generation of smart contract platforms, serving as the “GitHub” of the ecosystem, with realms built using fully transparent, auditable code that anyone can inspect and reuse.\"),\n\t\tui.Paragraph(\"Intuitive and easy to use, gno.land lowers the barrier to web3 and makes censorship-resistant platforms accessible to everyone. If you want to help lay the foundations of a fairer and freer world, join us today.\"),\n\t}\n}\n\nfunc worxDAO() ui.Element {\n\t// WorxDAO\n\t// XXX(manfred): please, let me finish a v0, then we can iterate\n\t// highest level == highest responsibility\n\t// teams are responsible for components they don't owne\n\t// flag : realm maintainers VS facilitators\n\t// teams\n\t// committee of trustees to create the directory\n\t// each directory is a name, has a parent and have groups\n\t// homepage team - blocks aggregating events\n\t// XXX: TODO\n\t/*`\n\t# Directory\n\n\t* gno.land (owned by group)\n\t *\n\t* gnovm\n\t * gnolang (language)\n\t * gnovm\n\t - current challenges / concerns / issues\n\t* tm2\n\t * amino\n\t *\n\n\t## Contributors\n\t``*/\n\treturn ui.Element{\n\t\tui.H3(\"Contributions (WorxDAO \u0026 GoR)\"),\n\t\t// TODO: GoR dashboard + WorxDAO topics\n\t\tui.Text(`coming soon`),\n\t}\n}\n\nfunc quoteOfTheBlock() ui.Element {\n\tquotes := []string{\n\t\t\"Gno is for Truth.\",\n\t\t\"Gno is for Social Coordination.\",\n\t\t\"Gno is _not only_ for DeFi.\",\n\t\t\"Now, you Gno.\",\n\t\t\"Come for the Go, Stay for the Gno.\",\n\t}\n\theight := std.GetHeight()\n\tidx := int(height) % len(quotes)\n\tqotb := quotes[idx]\n\n\treturn ui.Element{\n\t\tui.H3(ufmt.Sprintf(\"Quote of the ~Day~ Block#%d\", height)),\n\t\tui.Quote(qotb),\n\t}\n}\n\nfunc socialLinks() ui.Element {\n\treturn ui.Element{\n\t\tui.H3(\"Socials\"),\n\t\tui.BulletList{\n\t\t\t// XXX: improve UI to support a nice GO api for such links\n\t\t\tui.Text(\"Check out our [community projects](https://github.com/gnolang/awesome-gno)\"),\n\t\t\tui.Text(\"![Discord](static/img/ico-discord.svg) [Discord](https://discord.gg/S8nKUqwkPn)\"),\n\t\t\tui.Text(\"![Twitter](static/img/ico-twitter.svg) [Twitter](https://twitter.com/_gnoland)\"),\n\t\t\tui.Text(\"![Youtube](static/img/ico-youtube.svg) [Youtube](https://www.youtube.com/@_gnoland)\"),\n\t\t\tui.Text(\"![Telegram](static/img/ico-telegram.svg) [Telegram](https://t.me/gnoland)\"),\n\t\t},\n\t}\n}\n\nfunc playgroundSection() ui.Element {\n\treturn ui.Element{\n\t\tui.H3(\"[Gno Playground](https://play.gno.land)\"),\n\t\tui.Paragraph(`Gno Playground is a web application designed for building, running, testing, and interacting\nwith your Gno code, enhancing your understanding of the Gno language. With Gno Playground, you can share your code,\nexecute tests, deploy your realms and packages to gno.land, and explore a multitude of other features.`),\n\t\tui.Paragraph(\"Experience the convenience of code sharing and rapid experimentation with [Gno Playground](https://play.gno.land).\"),\n\t}\n}\n\nfunc packageStaffPicks() ui.Element {\n\t// XXX: make it modifiable from a DAO\n\treturn ui.Element{\n\t\tui.H3(\"Explore New Packages and Realms\"),\n\t\tui.Columns{\n\t\t\t3,\n\t\t\t[]ui.Element{\n\t\t\t\t{\n\t\t\t\t\tui.H4(\"[r/gnoland](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/gnoland)\"),\n\t\t\t\t\tui.BulletList{\n\t\t\t\t\t\tui.Link{URL: \"r/gnoland/blog\"},\n\t\t\t\t\t\tui.Link{URL: \"r/gnoland/dao\"},\n\t\t\t\t\t\tui.Link{URL: \"r/gnoland/faucet\"},\n\t\t\t\t\t\tui.Link{URL: \"r/gnoland/home\"},\n\t\t\t\t\t\tui.Link{URL: \"r/gnoland/pages\"},\n\t\t\t\t\t},\n\t\t\t\t\tui.H4(\"[r/sys](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/sys)\"),\n\t\t\t\t\tui.BulletList{\n\t\t\t\t\t\tui.Link{URL: \"r/sys/names\"},\n\t\t\t\t\t\tui.Link{URL: \"r/sys/rewards\"},\n\t\t\t\t\t\tui.Link{URL: \"/r/sys/validators/v2\"},\n\t\t\t\t\t},\n\t\t\t\t}, {\n\t\t\t\t\tui.H4(\"[r/demo](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo)\"),\n\t\t\t\t\tui.BulletList{\n\t\t\t\t\t\tui.Link{URL: \"r/demo/boards\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/users\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/banktest\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/foo20\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/foo721\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/microblog\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/nft\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/types\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/art/gnoface\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/art/millipede\"},\n\t\t\t\t\t\tui.Link{URL: \"r/demo/groups\"},\n\t\t\t\t\t\tui.Text(\"...\"),\n\t\t\t\t\t},\n\t\t\t\t}, {\n\t\t\t\t\tui.H4(\"[p/demo](https://github.com/gnolang/gno/tree/master/examples/gno.land/p/demo)\"),\n\t\t\t\t\tui.BulletList{\n\t\t\t\t\t\tui.Link{URL: \"p/demo/avl\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/blog\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/ui\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/ufmt\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/merkle\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/bf\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/flow\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/gnode\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/grc/grc20\"},\n\t\t\t\t\t\tui.Link{URL: \"p/demo/grc/grc721\"},\n\t\t\t\t\t\tui.Text(\"...\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc discoverLinks() ui.Element {\n\treturn ui.Element{\n\t\tui.Text(`\u003cdiv class=\"columns-3\"\u003e\n\u003cdiv class=\"column\"\u003e\n\n### Learn about gno.land\n\n- [About](/about)\n- [GitHub](https://github.com/gnolang)\n- [Blog](/blog)\n- [Events](/events)\n- Tokenomics (soon)\n- [Partners, Fund, Grants](/partners)\n- [Explore the Ecosystem](/ecosystem)\n- [Careers](https://jobs.ashbyhq.com/allinbits)\n\n\u003c/div\u003e\u003c!-- end column--\u003e\n\n\u003cdiv class=\"column\"\u003e\n\n### Build with Gno\n\n- [Write Gno in the browser](https://play.gno.land)\n- [Read about the Gno Language](/gnolang)\n- [Visit the official documentation](https://docs.gno.land)\n- [Gno by Example](https://gno-by-example.com/)\n- [Efficient local development for Gno](https://docs.gno.land/gno-tooling/cli/gno-tooling-gnodev)\n- [Get testnet GNOTs](https://faucet.gno.land)\n\n\u003c/div\u003e\u003c!-- end column--\u003e\n\u003cdiv class=\"column\"\u003e\n\n### Explore the universe\n\n- [Discover demo packages](https://github.com/gnolang/gno/tree/master/examples)\n- [Gnoscan](https://gnoscan.io)\n- [Portal Loop](https://docs.gno.land/concepts/portal-loop)\n- [Testnet 4](https://test4.gno.land/)\n- Testnet Faucet Hub (soon)\n\n\u003c/div\u003e\u003c!-- end column--\u003e\n\u003c/div\u003e\u003c!-- end columns-3--\u003e`),\n\t}\n}\n\nfunc AdminSetOverride(content string) {\n\tadmin.AssertCallerIsOwner()\n\toverride = content\n}\n\nfunc AdminTransferOwnership(newAdmin std.Address) {\n\tadmin.AssertCallerIsOwner()\n\tadmin.TransferOwnership(newAdmin)\n}\n"},{"name":"home_filetest.gno","body":"package main\n\nimport \"gno.land/r/gnoland/home\"\n\nfunc main() {\n\tprintln(home.Render(\"\"))\n}\n\n// Output:\n// \u003cmain class='gno-tmpl-section'\u003e\n//\n// # Welcome to gno.land\n//\n// ### We’re building gno.land, set to become the leading open-source smart contract platform, using Gno, an interpreted and fully deterministic variation of the Go programming language for succinct and composable smart contracts.\n//\n//\n// With transparent and timeless code, gno.land is the next generation of smart contract platforms, serving as the “GitHub” of the ecosystem, with realms built using fully transparent, auditable code that anyone can inspect and reuse.\n//\n//\n// Intuitive and easy to use, gno.land lowers the barrier to web3 and makes censorship-resistant platforms accessible to everyone. If you want to help lay the foundations of a fairer and freer world, join us today.\n//\n// \u003cdiv class=\"jumbotron\"\u003e\n//\n// \u003cdiv class=\"columns-3\"\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Learn about gno.land\n//\n// - [About](/about)\n// - [GitHub](https://github.com/gnolang)\n// - [Blog](/blog)\n// - [Events](/events)\n// - Tokenomics (soon)\n// - [Partners, Fund, Grants](/partners)\n// - [Explore the Ecosystem](/ecosystem)\n// - [Careers](https://jobs.ashbyhq.com/allinbits)\n//\n// \u003c/div\u003e\u003c!-- end column--\u003e\n//\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Build with Gno\n//\n// - [Write Gno in the browser](https://play.gno.land)\n// - [Read about the Gno Language](/gnolang)\n// - [Visit the official documentation](https://docs.gno.land)\n// - [Gno by Example](https://gno-by-example.com/)\n// - [Efficient local development for Gno](https://docs.gno.land/gno-tooling/cli/gno-tooling-gnodev)\n// - [Get testnet GNOTs](https://faucet.gno.land)\n//\n// \u003c/div\u003e\u003c!-- end column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Explore the universe\n//\n// - [Discover demo packages](https://github.com/gnolang/gno/tree/master/examples)\n// - [Gnoscan](https://gnoscan.io)\n// - [Portal Loop](https://docs.gno.land/concepts/portal-loop)\n// - [Testnet 4](https://test4.gno.land/)\n// - Testnet Faucet Hub (soon)\n//\n// \u003c/div\u003e\u003c!-- end column--\u003e\n// \u003c/div\u003e\u003c!-- end columns-3--\u003e\n// \u003c/div\u003e\u003c!-- /jumbotron --\u003e\n//\n// \u003cdiv class=\"columns-3\"\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### [Latest Blogposts](/r/gnoland/blog)\n//\n// No posts.\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### [Latest Events](/r/gnoland/events)\n//\n// No events.\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Latest Contributions\n//\n// [View latest contributions](https://github.com/gnolang/gno/pulls)\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003c/div\u003e\u003c!-- /columns-3 --\u003e\n//\n//\n// ---\n//\n// ### [Gno Playground](https://play.gno.land)\n//\n//\n// Gno Playground is a web application designed for building, running, testing, and interacting\n// with your Gno code, enhancing your understanding of the Gno language. With Gno Playground, you can share your code,\n// execute tests, deploy your realms and packages to gno.land, and explore a multitude of other features.\n//\n//\n// Experience the convenience of code sharing and rapid experimentation with [Gno Playground](https://play.gno.land).\n//\n//\n// ---\n//\n// ### Explore New Packages and Realms\n//\n// \u003cdiv class=\"columns-3\"\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// #### [r/gnoland](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/gnoland)\n//\n// - [r/gnoland/blog](r/gnoland/blog)\n// - [r/gnoland/dao](r/gnoland/dao)\n// - [r/gnoland/faucet](r/gnoland/faucet)\n// - [r/gnoland/home](r/gnoland/home)\n// - [r/gnoland/pages](r/gnoland/pages)\n//\n// #### [r/sys](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/sys)\n//\n// - [r/sys/names](r/sys/names)\n// - [r/sys/rewards](r/sys/rewards)\n// - [/r/sys/validators/v2](/r/sys/validators/v2)\n//\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// #### [r/demo](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo)\n//\n// - [r/demo/boards](r/demo/boards)\n// - [r/demo/users](r/demo/users)\n// - [r/demo/banktest](r/demo/banktest)\n// - [r/demo/foo20](r/demo/foo20)\n// - [r/demo/foo721](r/demo/foo721)\n// - [r/demo/microblog](r/demo/microblog)\n// - [r/demo/nft](r/demo/nft)\n// - [r/demo/types](r/demo/types)\n// - [r/demo/art/gnoface](r/demo/art/gnoface)\n// - [r/demo/art/millipede](r/demo/art/millipede)\n// - [r/demo/groups](r/demo/groups)\n// - ...\n//\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// #### [p/demo](https://github.com/gnolang/gno/tree/master/examples/gno.land/p/demo)\n//\n// - [p/demo/avl](p/demo/avl)\n// - [p/demo/blog](p/demo/blog)\n// - [p/demo/ui](p/demo/ui)\n// - [p/demo/ufmt](p/demo/ufmt)\n// - [p/demo/merkle](p/demo/merkle)\n// - [p/demo/bf](p/demo/bf)\n// - [p/demo/flow](p/demo/flow)\n// - [p/demo/gnode](p/demo/gnode)\n// - [p/demo/grc/grc20](p/demo/grc/grc20)\n// - [p/demo/grc/grc721](p/demo/grc/grc721)\n// - ...\n//\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003c/div\u003e\u003c!-- /columns-3 --\u003e\n//\n//\n// ---\n//\n// ### Contributions (WorxDAO \u0026 GoR)\n//\n// coming soon\n//\n// ---\n//\n//\n// \u003cdiv class=\"columns-2\"\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Socials\n//\n// - Check out our [community projects](https://github.com/gnolang/awesome-gno)\n// - ![Discord](static/img/ico-discord.svg) [Discord](https://discord.gg/S8nKUqwkPn)\n// - ![Twitter](static/img/ico-twitter.svg) [Twitter](https://twitter.com/_gnoland)\n// - ![Youtube](static/img/ico-youtube.svg) [Youtube](https://www.youtube.com/@_gnoland)\n// - ![Telegram](static/img/ico-telegram.svg) [Telegram](https://t.me/gnoland)\n//\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003cdiv class=\"column\"\u003e\n//\n// ### Quote of the ~Day~ Block#123\n//\n// \u003e Now, you Gno.\n//\n// \u003c/div\u003e\u003c!-- /column--\u003e\n// \u003c/div\u003e\u003c!-- /columns-2 --\u003e\n//\n//\n// ---\n//\n// **This is a testnet.**\n// Package names are not guaranteed to be available for production.\n//\n// \u003c/main\u003e\n"},{"name":"overide_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/gnoland/home\"\n)\n\nfunc main() {\n\tstd.TestSetOriginCaller(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\thome.AdminSetOverride(\"Hello World!\")\n\tprintln(home.Render(\"\"))\n\thome.AdminTransferOwnership(testutils.TestAddress(\"newAdmin\"))\n\tdefer func() {\n\t\tr := recover()\n\t\tprintln(\"r: \", r)\n\t}()\n\thome.AdminSetOverride(\"Not admin anymore\")\n}\n\n// Output:\n// Hello World!\n// r: ownable: caller is not owner\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":"monit","path":"gno.land/r/gnoland/monit","files":[{"name":"monit.gno","body":"// Package monit links a monitoring system with the chain in both directions.\n//\n// The agent will periodically call Incr() and verify that the value is always\n// higher than the previously known one. The contract will store the last update\n// time and use it to detect whether or not the monitoring agent is functioning\n// correctly.\npackage monit\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/p/demo/watchdog\"\n)\n\nvar (\n\tcounter int\n\tlastUpdate time.Time\n\tlastCaller std.Address\n\twd = watchdog.Watchdog{Duration: 5 * time.Minute}\n\towner = ownable.New() // TODO: replace with -\u003e ownable.NewWithAddress...\n\twatchdogDuration = 5 * time.Minute\n)\n\n// Incr increments the counter and informs the watchdog that we're alive.\n// This function can be called by anyone.\nfunc Incr() int {\n\tcounter++\n\tlastUpdate = time.Now()\n\tlastCaller = std.PreviousRealm().Addr()\n\twd.Alive()\n\treturn counter\n}\n\n// Reset resets the realm state.\n// This function can only be called by the admin.\nfunc Reset() {\n\tif owner.CallerIsOwner() != nil { // TODO: replace with owner.AssertCallerIsOwner\n\t\tpanic(\"unauthorized\")\n\t}\n\tcounter = 0\n\tlastCaller = std.PreviousRealm().Addr()\n\tlastUpdate = time.Now()\n\twd = watchdog.Watchdog{Duration: 5 * time.Minute}\n}\n\nfunc Render(_ string) string {\n\tstatus := wd.Status()\n\treturn ufmt.Sprintf(\n\t\t\"counter=%d\\nlast update=%s\\nlast caller=%s\\nstatus=%s\",\n\t\tcounter, lastUpdate, lastCaller, status,\n\t)\n}\n\n// TransferOwnership transfers ownership to a new owner. This is a proxy to\n// ownable.Ownable.TransferOwnership.\nfunc TransferOwnership(newOwner std.Address) { owner.TransferOwnership(newOwner) }\n"},{"name":"monit_test.gno","body":"package monit\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestPackage(t *testing.T) {\n\t// initial state, watchdog is KO.\n\t{\n\t\texpected := `counter=0\nlast update=0001-01-01 00:00:00 +0000 UTC\nlast caller=\nstatus=KO`\n\t\tgot := Render(\"\")\n\t\tuassert.Equal(t, expected, got)\n\t}\n\n\t// call Incr(), watchdog is OK.\n\tIncr()\n\tIncr()\n\tIncr()\n\t{\n\t\texpected := `counter=3\nlast update=2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001\nlast caller=g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\nstatus=OK`\n\t\tgot := Render(\"\")\n\t\tuassert.Equal(t, expected, got)\n\t}\n\n\t/* XXX: improve tests once we've the missing std.TestSkipTime feature\n\t\t// wait 1h, watchdog is KO.\n\t\tuse std.TestSkipTime(time.Hour)\n\t\t{\n\t\t\texpected := `counter=3\n\tlast update=2009-02-13 22:31:30 +0000 UTC m=+1234564290.000000001\n\tlast caller=g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n\tstatus=KO`\n\t\t\tgot := Render(\"\")\n\t\t\tuassert.Equal(t, expected, got)\n\t\t}\n\n\t\t// call Incr(), watchdog is OK.\n\t\tIncr()\n\t\t{\n\t\t\texpected := `counter=4\n\tlast update=2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001\n\tlast caller=g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n\tstatus=OK`\n\t\t\tgot := Render(\"\")\n\t\t\tuassert.Equal(t, expected, got)\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":"monit","path":"gno.land/r/gnoland/monit","files":[{"name":"monit.gno","body":"// Package monit links a monitoring system with the chain in both directions.\n//\n// The agent will periodically call Incr() and verify that the value is always\n// higher than the previously known one. The contract will store the last update\n// time and use it to detect whether or not the monitoring agent is functioning\n// correctly.\npackage monit\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/p/demo/watchdog\"\n)\n\nvar (\n\tcounter int\n\tlastUpdate time.Time\n\tlastCaller std.Address\n\twd = watchdog.Watchdog{Duration: 5 * time.Minute}\n\towner = ownable.New() // TODO: replace with -\u003e ownable.NewWithAddress...\n\twatchdogDuration = 5 * time.Minute\n)\n\n// Incr increments the counter and informs the watchdog that we're alive.\n// This function can be called by anyone.\nfunc Incr() int {\n\tcounter++\n\tlastUpdate = time.Now()\n\tlastCaller = std.PreviousRealm().Address()\n\twd.Alive()\n\treturn counter\n}\n\n// Reset resets the realm state.\n// This function can only be called by the admin.\nfunc Reset() {\n\tif owner.CallerIsOwner() != nil { // TODO: replace with owner.AssertCallerIsOwner\n\t\tpanic(\"unauthorized\")\n\t}\n\tcounter = 0\n\tlastCaller = std.PreviousRealm().Address()\n\tlastUpdate = time.Now()\n\twd = watchdog.Watchdog{Duration: 5 * time.Minute}\n}\n\nfunc Render(_ string) string {\n\tstatus := wd.Status()\n\treturn ufmt.Sprintf(\n\t\t\"counter=%d\\nlast update=%s\\nlast caller=%s\\nstatus=%s\",\n\t\tcounter, lastUpdate, lastCaller, status,\n\t)\n}\n\n// TransferOwnership transfers ownership to a new owner. This is a proxy to\n// ownable.Ownable.TransferOwnership.\nfunc TransferOwnership(newOwner std.Address) { owner.TransferOwnership(newOwner) }\n"},{"name":"monit_test.gno","body":"package monit\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestPackage(t *testing.T) {\n\t// initial state, watchdog is KO.\n\t{\n\t\texpected := `counter=0\nlast update=0001-01-01 00:00:00 +0000 UTC\nlast caller=\nstatus=KO`\n\t\tgot := Render(\"\")\n\t\tuassert.Equal(t, expected, got)\n\t}\n\n\t// call Incr(), watchdog is OK.\n\tIncr()\n\tIncr()\n\tIncr()\n\t{\n\t\texpected := `counter=3\nlast update=2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001\nlast caller=g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\nstatus=OK`\n\t\tgot := Render(\"\")\n\t\tuassert.Equal(t, expected, got)\n\t}\n\n\t/* XXX: improve tests once we've the missing std.TestSkipTime feature\n\t\t// wait 1h, watchdog is KO.\n\t\tuse std.TestSkipTime(time.Hour)\n\t\t{\n\t\t\texpected := `counter=3\n\tlast update=2009-02-13 22:31:30 +0000 UTC m=+1234564290.000000001\n\tlast caller=g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n\tstatus=KO`\n\t\t\tgot := Render(\"\")\n\t\t\tuassert.Equal(t, expected, got)\n\t\t}\n\n\t\t// call Incr(), watchdog is OK.\n\t\tIncr()\n\t\t{\n\t\t\texpected := `counter=4\n\tlast update=2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001\n\tlast caller=g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n\tstatus=OK`\n\t\t\tgot := Render(\"\")\n\t\t\tuassert.Equal(t, expected, got)\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":"gnopages","path":"gno.land/r/gnoland/pages","files":[{"name":"admin.gno","body":"package gnopages\n\nimport (\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\nvar (\n\tadminAddr std.Address\n\tmoderatorList avl.Tree\n\tinPause bool\n)\n\nfunc init() {\n\t// adminAddr = std.OriginCaller() // FIXME: find a way to use this from the main's genesis.\n\tadminAddr = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"\n}\n\nfunc AdminSetAdminAddr(addr std.Address) {\n\tassertIsAdmin()\n\tadminAddr = addr\n}\n\nfunc AdminSetInPause(state bool) {\n\tassertIsAdmin()\n\tinPause = state\n}\n\nfunc AdminAddModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), true)\n}\n\nfunc AdminRemoveModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), false) // XXX: delete instead?\n}\n\nfunc ModAddPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\tcaller := std.OriginCaller()\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.NewPost(caller, slug, title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc ModEditPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.GetPost(slug).Update(title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc isAdmin(addr std.Address) bool {\n\treturn addr == adminAddr\n}\n\nfunc isModerator(addr std.Address) bool {\n\t_, found := moderatorList.Get(addr.String())\n\treturn found\n}\n\nfunc assertIsAdmin() {\n\tcaller := std.OriginCaller()\n\tif !isAdmin(caller) {\n\t\tpanic(\"access restricted.\")\n\t}\n}\n\nfunc assertIsModerator() {\n\tcaller := std.OriginCaller()\n\tif isAdmin(caller) || isModerator(caller) {\n\t\treturn\n\t}\n\tpanic(\"access restricted\")\n}\n\nfunc assertNotInPause() {\n\tif inPause {\n\t\tpanic(\"access restricted (pause)\")\n\t}\n}\n"},{"name":"page_about.gno","body":"package gnopages\n\nfunc init() {\n\tpath := \"about\"\n\ttitle := \"gno.land Is A Platform To Write Smart Contracts In Gno\"\n\t// XXX: description := \"On gno.land, developers write smart contracts and other blockchain apps using Gno without learning a language that’s exclusive to a single ecosystem.\"\n\tbody := `\ngno.land is a next-generation smart contract platform using Gno, an interpreted version of the general-purpose Go\nprogramming language. On gno.land, smart contracts can be uploaded on-chain only by publishing their full source code,\nmaking it trivial to verify the contract or fork it into an improved version. With a system to publish reusable code\nlibraries on-chain, gno.land serves as the “GitHub” of the ecosystem, with realms built using fully transparent,\nauditable code that anyone can inspect and reuse.\n\ngno.land addresses many pressing issues in the blockchain space, starting with the ease of use and intuitiveness of\nsmart contract platforms. Developers can write smart contracts without having to learn a new language that’s exclusive\nto a single ecosystem or limited by design. Go developers can easily port their existing web apps to gno.land or build\nnew ones from scratch, making web3 vastly more accessible.\n\nSecured by Proof of Contribution (PoC), a DAO-managed Proof-of-Authority consensus mechanism, gno.land prioritizes\nfairness and merit, rewarding the people most active on the platform. PoC restructures the financial incentives that\noften corrupt blockchain projects, opting instead to reward contributors for their work based on expertise, commitment, and\nalignment.\n\nOne of our inspirations for gno.land is the gospels, which built a system of moral code that lasted thousands of years.\nBy observing a minimal production implementation, gno.land’s design will endure over time and serve as a reference for\nfuture generations with censorship-resistant tools that improve their understanding of the world.\n`\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:22Z\", nil, nil)\n}\n"},{"name":"page_contribute.gno","body":"package gnopages\n\nfunc init() {\n\tpath := \"contribute\"\n\ttitle := \"Contributor Ecosystem: Call for Contributions\"\n\tbody := `\n\ngno.land puts at the center of its identity the contributors that help to create and shape the project into what it is; incentivizing those who contribute the most and help advance its vision. Eventually, contributions will be incentivized directly on-chain; in the meantime, this page serves to illustrate our current off-chain initiatives.\n\ngno.land is still in full-steam development. For now, we're looking for the earliest of adopters; curious to explore a new way to build smart contracts and eager to make an impact. Joining gno.land's development now means you can help to shape the base of its development ecosystem, which will pave the way for the next generation of blockchain programming.\n\nAs an open-source project, we welcome all contributions. On this page you can find some pointers on where to get started; as well as some incentives for the most valuable and important contributions.\n\n## Where to get started\n\nIf you are interested in contributing to gno.land, you can jump on in on our [GitHub monorepo](https://github.com/gnolang/gno/blob/master/CONTRIBUTING.md) - where most development happens.\n\nA good place where to start are the issues tagged [\"good first issue\"](https://github.com/gnolang/gno/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22). They should allow you to make some impact on the Gno repository while you're still exploring the details of how everything works.\n\n## Gno Bounties\n\nAdditionally, you can look out to help on specific issues labeled as bounties. All contributions will then concur to form your profile for Game of Realms.\n\nThe Gno bounty program is a good way to find interesting challenges in Gno, and get rewarded for helping us advance the project. We will maintain open and rewardable bounties in the gnolang/gno repository, and you can search all available bounties by using the [\"bounty\" label](https://github.com/gnolang/gno/labels/bounty).\n\nRecommendations on participating in the gno.land Bounty Program:\n\n- Identify the bounty you want to work on, and join in the discussion on the issue for anything that is unclear; or where you want to more clearly define the work to be done. At this stage, you can also start working on an initial implementation in your local enviornment.\n- Once you have spent time on the code related to the bounty, we recommend submitting a 'draft' PR as soon as possible.\n - The draft PR doesn't indicate that the bounty has been assigned to you, others are free to work on other draft PRs for the bounty.\n - Make sure to reference the bounty issue on the PR description you're writing.\n - After submitting the 'draft' PR, continue working until you are ready to mark the PR as \"ready for review\".\n - The core team will review the bounty PR submission after the work on the bounty has been completed, and determine if it qualifies for the bounty reward.\n- Ask for clarification early if an element on the requirements or implementation design is unclear.\n - Aside from publishing the PR early, keeping regular updates with the core team on the bounty issue is key to being on the right track.\n - As part of the requirements, you must adhere to the [contributing guidelines](https://github.com/gnolang/gno/blob/master/CONTRIBUTING.md); additionally, it is expected that any newly added code or functionality is properly documented, tested and covered, at least in 80% of added code.\n - You're welcome to propose additional features and work on an issue should you envision a plausible expansion or change in scope. The core team may assign a bounty to the additional work, or change the bounty with respect to the changed scope.\n\nYou may make your submission at any time; however we invite you to publish your draft PR very early in the development process. This will make your work public, so you can easily get help by the core team and other community members. Additionally, your work can be continued by other people should you get stuck or no longer be willing to work on the bounty. Likewise, you can continue the abandoned or stuck work that someone else worked on.\n\nDon't fear your work being \"stolen\": if a submission is the result of multiple people's efforts, we will look to split the bounty in a way that is fair and recognises each participant in creating the final outcome. Here are some examples of how that can happen:\n\n- If Alice does most of the work and abandons it; then Bob comes around and finishes the job, then Bob's PR will be merged. But the core team will propose a split like 70% for Alice and 30% for Bob (depending, of course, on the relative effort undertaken by both).\n- If Alice makes a PR that does only 50% of the work outlined in the requirements for the original issue, she will get 50%. Someone can still come up and finish the job; and claim the remaining part.\n\t- If you, for instance, cannot complete the entirety of the task or, as a non-developer, can only contribute a part of the specification/implementation, you may still be awarded a bounty for your input in the contribution.\n- If Alice makes a PR that aside from implementing what's required, also undertakes creating useful tools among the way, she may qualify for an \"outstanding contribution\"; and may be awarded up to 25% more of the original bounty's value. Or she may also ask if the team would be willing to offer a different bounty for the implementation of the tools.\n\nParticipants in the gno.land Bounty Program must meet the legal Terms and Conditions referenced [here](https://docs.google.com/document/d/e/2PACX-1vSUF-JwIXGscrNsc5QBD7Pa6i83mXUGogAEIf1wkeb_w42UgL3Lj6jFKMlNTdwEMUnhsLkjRlhe25K4/pub).\n\n### Bounty sizes\n\nEach bounty is associated with a size, to which corresponds the maximum compensation for the work involved on the bounty. A bounty size may under rare occasion be revisited to a bigger or smaller size; hence why it's important to talk about your proposed solution with the core team ahead of time.\n\nIn some cases, the work associated with a bounty may be outstanding. When that happens, the core team can decide to award up to 25% of the bounty's value to the recipient.\n\nThe value of the bounty, aside from the material completion of the task, considers the involved time in managing the created pull request and iterating on feedback.\n\n\nt-shirt size | expected compensation\n-------------|-----------------------\n[XS] | $ 500\n[S] | $ 1000\n[M] | $ 2000\n[L] | $ 4000\n[XL] | $ 8000\n_[XXL]_ \\* | $ 16000\n_[3XL]_ \\* | $ 32000\n\n[XS]: https://github.com/gnolang/gno/labels/bounty%2FXS\n[S]: https://github.com/gnolang/gno/labels/bounty%2FS\n[M]: https://github.com/gnolang/gno/labels/bounty%2FM\n[L]: https://github.com/gnolang/gno/labels/bounty%2FL\n[XL]: https://github.com/gnolang/gno/labels/bounty%2FXL\n[XXL]: https://github.com/gnolang/gno/labels/bounty%2FXXL\n[3XL]: https://github.com/gnolang/gno/labels/bounty%2F3XL\n\n\\*: XXL and 3XL bounties are exceptional. Almost no issues will have these sizes; most will be broken down into smaller bounties.\n\n## gno.land Grants\n\nThe gno.land grants program is to encourage and support the growth of the gno.land contributor community, and build out the usability of the platform and smart contract library. The program provides financial resources to contributors to explore the Gno tech stack, and build dApps, tooling, infrastructure, products, and smart contract libraries in gno.land.\n\nFor more details on gno.land grants, suggested topics, and how to apply, visit our grants [repository](https://github.com/gnolang/grants). \n\n## Join Game of Realms\n\nGame of Realms is the overarching contributor network of gnomes, currently running off-chain, and will eventually transition on-chain. At this stage, a Game of Realms contribution is comprised of high-impact contributions identified as ['notable contributions'](https://github.com/gnolang/game-of-realms/tree/main/contributors).\n\nThese contributions are not linked to immediate financial rewards, but are notable in nature, in the sense they are a challenge, make a significant addition to the project, and require persistence, with minimal feedback loops from the core team.\n\nThe selection of a notable contribution or the sum of contributions that equal 'notable' is based on the impact it has on the development of the project. For now, it is focused on code contributions, and will evolve over time. The Gno development teams will initially qualify and evaluate notable contributions, and vote off-chain on adding them to the 'notable contributions' folder on GitHub.\n\nYou can always contribute to the project, and all contributions will be noticed. Contributing now is a way to build your personal contributor profile in gno.land early on in the ecosystem, and signal your commitment to the project, the community, and its future.\n\nThere are a variety of ways to make your contributions count:\n\n- Core code contributions\n- Realm and pure package development\n- Validator tooling\n- Developer tooling\n- Tutorials and documentation\n\nTo start, we recommend you create a PR in the Game of Realms [repository](https://github.com/gnolang/game-of-realms) to create your profile page for all your contributions.`\n\n\t_ = b.NewPost(\"\", path, title, body, \"2024-09-05T00:00:00Z\", nil, nil)\n}\n"},{"name":"page_ecosystem.gno","body":"package gnopages\n\nfunc init() {\n\tvar (\n\t\tpath = \"ecosystem\"\n\t\ttitle = \"Discover gno.land Ecosystem Projects \u0026 Initiatives\"\n\t\t// XXX: description = \"Dive further into the gno.land ecosystem and discover the core infrastructure, projects, smart contracts, and tooling we’re building.\"\n\t\tbody = `\n### [Gno Playground](https://play.gno.land)\n\nGno Playground is a simple web interface that lets you write, test, and experiment with your Gno code to improve your\nunderstanding of the Gno language. You can share your code, run unit tests, deploy your realms and packages, and execute\nfunctions in your code using the repo.\n\nVisit the playground at [play.gno.land](https://play.gno.land)!\n\n### [Gno Studio Connect](https://gno.studio/connect)\n\nGno Studio Connect provides seamless access to realms, making it simple to explore, interact, and engage\nwith gno.land’s smart contracts through function calls. Connect focuses on function calls, enabling users to interact\nwith any realm’s exposed function(s) on gno.land.\n\nSee your realm interactions in [Gno Studio Connect](https://gno.studio/connect)\n\n### [Gnoscan](https://gnoscan.io)\n\nDeveloped by the Onbloc team, Gnoscan is gno.land’s blockchain explorer. Anyone can use Gnoscan to easily find\ninformation that resides on the gno.land blockchain, such as wallet addresses, TX hashes, blocks, and contracts.\nGnoscan makes our on-chain data easy to read and intuitive to discover.\n\nExplore the gno.land blockchain at [gnoscan.io](https://gnoscan.io)!\n\n### Adena\n\nAdena is a user-friendly non-custodial wallet for gno.land. Open-source and developed by Onbloc, Adena allows gnomes to\ninteract easily with the chain. With an emphasis on UX, Adena is built to handle millions of realms and tokens with a\nhigh-quality interface, support for NFTs and custom tokens, and seamless integration. Install Adena via the [official website](https://www.adena.app/)\n\n### Gnoswap\n\nGnoswap is currently under development and led by the Onbloc team. Gnoswap will be the first DEX on gno.land and is an\nautomated market maker (AMM) protocol written in Gno that allows for permissionless token exchanges on the platform.\n\n### Flippando\n\nFlippando is a simple on-chain memory game, ported from Solidity to Gno, which starts with an empty matrix to flip tiles\non to see what’s underneath. If the tiles match, they remain uncovered; if not, they are briefly shown, and the player\nmust memorize their colors until the entire matrix is uncovered. The end result can be minted as an NFT, which can later\nbe assembled into bigger, more complex NFTs, creating a digital “painting” with the uncovered tiles. Play the game at [Flippando](https://gno.flippando.xyz/flip)\n\n### Gno Native Kit\n\n[Gno Native Kit](https://github.com/gnolang/gnonative) is a framework that allows developers to build and port gno.land (d)apps written in the (d)app's native language.\n\n\n`\n\t)\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:23Z\", nil, nil)\n}\n"},{"name":"page_gnolang.gno","body":"package gnopages\n\nfunc init() {\n\tvar (\n\t\tpath = \"gnolang\"\n\t\ttitle = \"About the Gno, the Language for gno.land\"\n\t\t// TODO fix broken images\n\t\tbody = `\n\n[Gno](https://github.com/gnolang/gno) is an interpretation of the widely-used Go (Golang) programming language for blockchain created by Cosmos co-founder Jae Kwon in 2022 to mark a new era in smart contracting. Gno is ~99% identical to Go, so Go programmers can start coding in Gno right away, with a minimal learning curve. For example, Gno comes with blockchain-specific standard libraries, but any code that doesn’t use blockchain-specific logic can run in Go with minimal processing. Libraries that don’t make sense in the blockchain context, such as network or operating-system access, are not available in Gno. Otherwise, Gno loads and uses many standard libraries that power Go, so most of the parsing of the source code is the same.\n\nUnder the hood, the Gno code is parsed into an abstract syntax tree (AST) and the AST itself is used in the interpreter, rather than bytecode as in many virtual machines such as Java, Python, or Wasm. This makes even the GnoVM accessible to any Go programmer. The novel design of the intuitive GnoVM interpreter allows Gno to freeze and resume the program by persisting and loading the entire memory state. Gno is deterministic, auto-persisted, and auto-Merkle-ized, allowing (smart contract) programs to be succinct, as the programmer doesn’t have to serialize and deserialize objects to persist them into a database (unlike programming applications with the Cosmos SDK).\n\n## How Gno Differs from Go\n\n![Gno and Go differences](static/img/gno-language/go-and-gno.jpg)\n\nThe composable nature of Go/Gno allows for type-checked interactions between contracts, making gno.land safer and more powerful, as well as operationally cheaper and faster. Smart contracts on gno.land are light, simple, more focused, and easily interoperable—a network of interconnected contracts rather than siloed monoliths that limit interactions with other contracts.\n\n![Example of Gno code](static/img/gno-language/code-example.jpg)\n\n## Gno Inherits Go’s Built-in Security Features\n\nGo supports secure programming through exported/non-exported fields, enabling a “least-authority” design. It is easy to create objects and APIs that expose only what should be accessible to callers while hiding what should not be simply by the capitalization of letters, thus allowing a succinct representation of secure logic that can be called by multiple users.\n\nAnother major advantage of Go is that the language comes with an ecosystem of great tooling, like the compiler and third-party tools that statically analyze code. Gno inherits these advantages from Go directly to create a smart contract programming language that provides embedding, composability, type-check safety, and garbage collection, helping developers to write secure code relying on the compiler, parser, and interpreter to give warning alerts for common mistakes.\n\n## Gno vs Solidity\n\nThe most widely-adopted smart contract language today is Ethereum’s EVM-compatible Solidity. With bytecode built from the ground up and Turing complete, Solidity opened up a world of possibilities for decentralized applications (dApps) and there are currently more than 10 million contracts deployed on Ethereum. However, Solidity provides limited tooling and its EVM has a stack limit and computational inefficiencies.\n\nSolidity is designed for one purpose only (writing smart contracts) and is bound by the limitations of the EVM. In addition, developers have to learn several languages if they want to understand the whole stack or work across different ecosystems. Gno aspires to exceed Solidity on multiple fronts (and other smart contract languages like CosmWasm or Substrate) as every part of the stack is written in Gno. It’s easy for developers to understand the entire system just by studying a relatively small code base.\n\n## Gno Is Essential for the Wider Adoption of Web3\n\nGno makes imports as easy as they are in web2 with runtime-based imports for seamless dependency flow comprehension, and support for complex structs, beyond primitive types. Gno is ultimately cost-effective as dependencies are loaded once, enabling remote function calls as local, and providing automatic and independent per-realm state persistence.\n\nUsing Gno, developers can rapidly accelerate application development and adopt a modular structure by reusing and reassembling existing modules without building from scratch. They can embed one structure inside another in an intuitive way while preserving localism, and the language specification is simple, successfully balancing practicality and minimalism.\n\nThe Go language is so well designed that the Gno smart contract system will become the new gold standard for smart contract development and other blockchain applications. As a programming language that is universally adopted, secure, composable, and complete, Gno is essential for the broader adoption of web3 and its sustainable growth.`\n\t)\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:25Z\", nil, nil)\n}\n"},{"name":"page_license.gno","body":"package gnopages\n\nfunc init() {\n\tvar (\n\t\tpath = \"license\"\n\t\ttitle = \"Gno Network General Public License\"\n\t\tbody = `Copyright (C) 2024 NewTendermint, LLC\n\nThis program is free software: you can redistribute it and/or modify it under\nthe terms of the GNO Network General Public License as published by\nNewTendermint, LLC, either version 4 of the License, or (at your option) any\nlater version published by NewTendermint, LLC.\n\nThis program is distributed in the hope that it will be useful, but is provided\nas-is and WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNO Network\nGeneral Public License for more details.\n\nYou should have received a copy of the GNO Network General Public License along\nwith this program. If not, see \u003chttps://gno.land/license\u003e.\n\nAttached below are the terms of the GNO Network General Public License, Version\n4 (a fork of the GNU Affero General Public License 3).\n\n## Additional Terms\n\n### Strong Attribution\n\nIf any of your user interfaces, such as websites and mobile applications, serve\nas the primary point of entry to a platform or blockchain that 1) offers users\nthe ability to upload their own smart contracts to the platform or blockchain,\nand 2) leverages any Covered Work (including the GNO virtual machine) to run\nthose smart contracts on the platform or blockchain (\"Applicable Work\"), then\nthe Applicable Work must prominently link to (1) gno.land or (2) any other URL\ndesignated by NewTendermint, LLC that has not been rejected by the governance of\nthe first chain known as gno.land, provided that the identity of the first chain\nis not ambiguous. In the event the identity of the first chain is ambiguous,\nthen NewTendermint, LLC's designation shall control. Such link must appear\nconspicuously in the header or footer of the Applicable Work, such that all\nusers may learn of gno.land or the URL designated by NewTendermint, LLC.\n\nThis additional attribution requirement shall remain in effect for (1) 7\nyears from the date of publication of the Applicable Work, or (2) 7 years from\nthe date of publication of the Covered Work (including republication of new\nversions), whichever is later, but no later than 12 years after the application\nof this strong attribution requirement to the publication of the Applicable\nWork. For purposes of this Strong Attribution requirement, Covered Work shall\nmean any work that is licensed under the GNO Network General Public License,\nVersion 4 or later, by NewTendermint, LLC.\n\n\n# GNO NETWORK GENERAL PUBLIC LICENSE\n\nVersion 4, 7 May 2024\n\nModified from the GNU AFFERO GENERAL PUBLIC LICENSE.\nGNU is not affiliated with GNO or NewTendermint, LLC.\nCopyright (C) 2022 NewTendermint, LLC.\n\n## Preamble\n\nThe GNO Network General Public License is a free, copyleft license for\nsoftware and other kinds of works, specifically designed to ensure\ncooperation with the community in the case of network server software.\n\nThe licenses for most software and other practical works are designed\nto take away your freedom to share and change the works. By contrast,\nour General Public Licenses are intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.\n\nWhen we speak of free software, we are referring to freedom, not\nprice. Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\nDevelopers that use our General Public Licenses protect your rights\nwith two steps: (1) assert copyright on the software, and (2) offer\nyou this License which gives you legal permission to copy, distribute\nand/or modify the software.\n\nA secondary benefit of defending all users' freedom is that\nimprovements made in alternate versions of the program, if they\nreceive widespread use, become available for other developers to\nincorporate. Many developers of free software are heartened and\nencouraged by the resulting cooperation. However, in the case of\nsoftware used on network servers, this result may fail to come about.\nThe GNU General Public License permits making a modified version and\nletting the public access it on a server without ever releasing its\nsource code to the public.\n\nThe GNO Network General Public License is designed specifically to\nensure that, in such cases, the modified source code becomes available\nto the community. It requires the operator of a network server to\nprovide the source code of the modified version running there to the\nusers of that server. Therefore, public use of a modified version, on\na publicly accessible server, gives the public access to the source\ncode of the modified version.\n\nThe precise terms and conditions for copying, distribution and\nmodification follow.\n\n## TERMS AND CONDITIONS\n\n### 0. Definitions.\n\n\"This License\" refers to version 4 of the GNO Network General Public License.\n\n\"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n\"The Program\" refers to any copyrightable work licensed under this\nLicense. Each licensee is addressed as \"you\". \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\nTo \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy. The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\nA \"covered work\" means either the unmodified Program or a work based\non the Program.\n\nTo \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy. Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\nTo \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies. Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\nAn interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License. If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n### 1. Source Code.\n\nThe \"source code\" for a work means the preferred form of the work\nfor making modifications to it. \"Object code\" means any non-source\nform of a work.\n\nA \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\nThe \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form. A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\nThe \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities. However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work. For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\nThe Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\nThe Corresponding Source for a work in source code form is that\nsame work.\n\n### 2. Basic Permissions.\n\nAll rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met. This License explicitly affirms your unlimited\npermission to run the unmodified Program. The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work. This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\nYou may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force. You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright. Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\nConveying under any other circumstances is permitted solely under\nthe conditions stated below. Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n### 3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\nNo covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\nWhen you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n### 4. Conveying Verbatim Copies.\n\nYou may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\nYou may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n### 5. Conveying Modified Source Versions.\n\nYou may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n- a) The work must carry prominent notices stating that you modified\n it, and giving a relevant date.\n- b) The work must carry prominent notices stating that it is\n released under this License and any conditions added under section\n 7. This requirement modifies the requirement in section 4 to\n \"keep intact all notices\".\n- c) You must license the entire work, as a whole, under this\n License to anyone who comes into possession of a copy. This\n License will therefore apply, along with any applicable section 7\n additional terms, to the whole of the work, and all its parts,\n regardless of how they are packaged. This License gives no\n permission to license the work in any other way, but it does not\n invalidate such permission if you have separately received it.\n- d) If the work has interactive user interfaces, each must display\n Appropriate Legal Notices; however, if the Program has interactive\n interfaces that do not display Appropriate Legal Notices, your\n work need not make them do so.\n\nA compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit. Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n### 6. Conveying Non-Source Forms.\n\n You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n- a) Convey the object code in, or embodied in, a physical product\n (including a physical distribution medium), accompanied by the\n Corresponding Source fixed on a durable physical medium\n customarily used for software interchange.\n- b) Convey the object code in, or embodied in, a physical product\n (including a physical distribution medium), accompanied by a\n written offer, valid for at least three years and valid for as\n long as you offer spare parts or customer support for that product\n model, to give anyone who possesses the object code either (1) a\n copy of the Corresponding Source for all the software in the\n product that is covered by this License, on a durable physical\n medium customarily used for software interchange, for a price no\n more than your reasonable cost of physically performing this\n conveying of source, or (2) access to copy the\n Corresponding Source from a network server at no charge.\n- c) Convey individual copies of the object code with a copy of the\n written offer to provide the Corresponding Source. This\n alternative is allowed only occasionally and noncommercially, and\n only if you received the object code with such an offer, in accord\n with subsection 6b.\n- d) Convey the object code by offering access from a designated\n place (gratis or for a charge), and offer equivalent access to the\n Corresponding Source in the same way through the same place at no\n further charge. You need not require recipients to copy the\n Corresponding Source along with the object code. If the place to\n copy the object code is a network server, the Corresponding Source\n may be on a different server (operated by you or a third party)\n that supports equivalent copying facilities, provided you maintain\n clear directions next to the object code saying where to find the\n Corresponding Source. Regardless of what server hosts the\n Corresponding Source, you remain obligated to ensure that it is\n available for as long as needed to satisfy these requirements.\n- e) Convey the object code using peer-to-peer transmission, provided\n you inform other peers where the object code and Corresponding\n Source of the work are being offered to the general public at no\n charge under subsection 6d.\n\nA separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\nA \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling. In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage. For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product. A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n\"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source. The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\nIf you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information. But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\nThe requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed. Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\nCorresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n### 7. Additional Terms.\n\n\"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law. If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\nWhen you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit. (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.) You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\nNotwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n- a) Disclaiming warranty or limiting liability differently from the\n terms of sections 15 and 16 of this License; or\n- b) Requiring preservation of specified reasonable legal notices or\n author attributions in that material or in the Appropriate Legal\n Notices displayed by works containing it; or\n- c) Prohibiting misrepresentation of the origin of that material, or\n requiring that modified versions of such material be marked in\n reasonable ways as different from the original version; or\n- d) Limiting the use for publicity purposes of names of licensors or\n authors of the material; or\n- e) Declining to grant rights under trademark law for use of some\n trade names, trademarks, or service marks; or\n- f) Requiring indemnification of licensors and authors of that\n material by anyone who conveys the material (or modified versions of\n it) with contractual assumptions of liability to the recipient, for\n any liability that these contractual assumptions directly impose on\n those licensors and authors; or\n- g) Requiring strong attribution such as notices on any user interfaces\n that run or convey any covered work, such as a prominent link to a URL\n on the header of a website, such that all users of the covered work may\n become aware of the notice, for a period no longer than 20 years.\n\nAll other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10. If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term. If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\nIf you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\nAdditional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n### 8. Termination.\n\nYou may not propagate or modify a covered work except as expressly\nprovided under this License. Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\nHowever, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\nMoreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\nTermination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License. If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n### 9. Acceptance Not Required for Having Copies.\n\nYou are not required to accept this License in order to receive or\nrun a copy of the Program. Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance. However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work. These actions infringe copyright if you do\nnot accept this License. Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n### 10. Automatic Licensing of Downstream Recipients.\n\nEach time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License. You are not responsible\nfor enforcing compliance by third parties with this License.\n\nAn \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations. If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\nYou may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License. For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n### 11. Patents.\n\nA \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based. The\nwork thus licensed is called the contributor's \"contributor version\".\n\nA contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version. For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\nEach contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\nIn the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement). To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\nIf you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients. \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\nIf, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\nA patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License. You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\nNothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n### 12. No Surrender of Others' Freedom.\n\nIf conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License. If you cannot convey a\ncovered work so as to simultaneously satisfy your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all. For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n### 13. Remote Network Interaction; Use with the GNU General Public License.\n\nNotwithstanding any other provision of this License, if you modify the\nProgram, your modified version must prominently offer all users\ninteracting with it remotely through a computer network (if your version\nsupports such interaction) an opportunity to receive the Corresponding\nSource of your version by providing access to the Corresponding Source\nfrom a network server at no charge, through some standard or customary\nmeans of facilitating copying of software. This Corresponding Source\nshall include the Corresponding Source for any work covered by version 3\nof the GNU General Public License that is incorporated pursuant to the\nfollowing paragraph.\n\nNotwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU General Public License into a single\ncombined work, and to convey the resulting work. The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the work with which it is combined will remain governed by version\n3 of the GNU General Public License.\n\n### 14. Revised Versions of this License.\n\nNewTendermint LLC may publish revised and/or new versions of\nthe GNO Network General Public License from time to time. Such new versions\nwill be similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\nEach version is given a distinguishing version number. If the\nProgram specifies that a certain numbered version of the GNO Network General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Gno Software\nFoundation. If the Program does not specify a version number of the\nGNO Network General Public License, you may choose any version ever published\nby NewTendermint LLC.\n\nIf the Program specifies that a proxy can decide which future\nversions of the GNO Network General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\nLater license versions may give you additional or different\npermissions. However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n### 15. Disclaimer of Warranty.\n\nTHERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n### 16. Limitation of Liability.\n\nIN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n### 17. Interpretation of Sections 15 and 16.\n\nIf the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\nEND OF TERMS AND CONDITIONS\n\n## How to Apply These Terms to Your New Programs\n\nIf you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\nTo do so, attach the following notices to the program. It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n \u003cone line to give the program's name and a brief idea of what it does.\u003e\n Copyright (C) \u003cyear\u003e \u003cname of author\u003e\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNO Network General Public License as published by\n NewTendermint LLC, either version 4 of the License, or\n (at your option) any later version.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNO Network General Public License for more details.\n\n You should have received a copy of the GNO Network General Public License\n along with this program. If not, see \u003chttps://gno.land/license\u003e.\n\nAlso add information on how to contact you by electronic and paper mail.\n\nIf your software can interact with users remotely through a computer\nnetwork, you should also make sure that it provides a way for users to\nget its source. For example, if your program is a web application, its\ninterface could display a \"Source\" link that leads users to an archive\nof the code. There are many ways you could offer source, and different\nsolutions will be better for different programs; see section 13 for the\nspecific requirements.\n`\n\t)\n\t_ = b.NewPost(\"\", path, title, body, \"2024-04-22T00:00:00Z\", nil, nil)\n}\n"},{"name":"page_partners.gno","body":"package gnopages\n\nfunc init() {\n\tpath := \"partners\"\n\ttitle := \"Partnerships\"\n\tbody := `### Fund and Grants Program\n\nAre you a builder, tinkerer, or researcher? If you’re looking to create awesome dApps, tooling, infrastructure, \nor smart contract libraries on gno.land, you can apply for a grant. The gno.land Ecosystem Fund and Grants program \nprovides financial contributions for individuals and teams to innovate on the platform.\n\nRead more about our Funds and Grants program [here](https://github.com/gnolang/ecosystem-fund-grants).\n`\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:27Z\", nil, nil)\n}\n"},{"name":"page_start.gno","body":"package gnopages\n\nfunc init() {\n\tpath := \"start\"\n\ttitle := \"Getting Started with Gno\"\n\t// XXX: description := \"\"\n\n\t// TODO: codegen to use README files here\n\n\t/* TODO: port previous message: This is a demo of Gno smart contract programming. This document was\n\tconstructed by Gno onto a smart contract hosted on the data Realm\n\tname [\"gno.land/r/demo/boards\"](https://gno.land/r/demo/boards/)\n\t([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)).\n\t*/\n\tbody := `## Getting Started with Gno\n\n- [Install Gno Key](/r/demo/boards:testboard/5)\n- TODO: add more links\n`\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:28Z\", nil, nil)\n}\n"},{"name":"page_testnets.gno","body":"package gnopages\n\nfunc init() {\n\tpath := \"testnets\"\n\ttitle := \"gno.land Testnet List\"\n\tbody := `\n- [Portal Loop](https://docs.gno.land/concepts/portal-loop) - a rolling testnet\n- [staging.gno.land](https://staging.gno.land) - wiped every commit to monorepo master\n- _[test4.gno.land](https://test4.gno.land) (latest)_\n\nFor a list of RPC endpoints, see the [reference documentation](https://docs.gno.land/reference/rpc-endpoints).\n\n## Local development\n\nSee the \"Getting started\" section in the [official documentation](https://docs.gno.land/getting-started/local-setup).\n`\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:29Z\", nil, nil)\n}\n"},{"name":"page_tokenomics.gno","body":"package gnopages\n\nfunc init() {\n\tvar (\n\t\tpath = \"tokenomics\"\n\t\ttitle = \"gno.land Tokenomics\"\n\t\t// XXX: description = \"\"\"\n\t\tbody = `Lorem Ipsum`\n\t)\n\t_ = b.NewPost(\"\", path, title, body, \"2022-05-20T13:17:30Z\", nil, nil)\n}\n"},{"name":"pages.gno","body":"package gnopages\n\nimport (\n\t\"gno.land/p/demo/blog\"\n)\n\n// TODO: switch from p/blog to p/pages\n\nvar b = \u0026blog.Blog{\n\tTitle: \"Gnoland's Pages\",\n\tPrefix: \"/r/gnoland/pages:\",\n\tNoBreadcrumb: true,\n}\n\nfunc Render(path string) string {\n\treturn b.Render(path)\n}\n"},{"name":"pages_test.gno","body":"package gnopages\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestHome(t *testing.T) {\n\tprintedOnce := false\n\tgot := Render(\"\")\n\texpectedSubtrings := []string{\n\t\t\"/r/gnoland/pages:p/tokenomics\",\n\t\t\"/r/gnoland/pages:p/start\",\n\t\t\"/r/gnoland/pages:p/contribute\",\n\t\t\"/r/gnoland/pages:p/about\",\n\t\t\"/r/gnoland/pages:p/gnolang\",\n\t}\n\tfor _, substring := range expectedSubtrings {\n\t\tif !strings.Contains(got, substring) {\n\t\t\tif !printedOnce {\n\t\t\t\tprintln(got)\n\t\t\t\tprintedOnce = true\n\t\t\t}\n\t\t\tt.Errorf(\"expected %q, but not found.\", substring)\n\t\t}\n\t}\n}\n\nfunc TestAbout(t *testing.T) {\n\tprintedOnce := false\n\tgot := Render(\"p/about\")\n\texpectedSubtrings := []string{\n\t\t\"gno.land Is A Platform To Write Smart Contracts In Gno\",\n\t\t\"gno.land is a next-generation smart contract platform using Gno, an interpreted version of the general-purpose Go\\nprogramming language.\",\n\t}\n\tfor _, substring := range expectedSubtrings {\n\t\tif !strings.Contains(got, substring) {\n\t\t\tif !printedOnce {\n\t\t\t\tprintln(got)\n\t\t\t\tprintedOnce = true\n\t\t\t}\n\t\t\tt.Errorf(\"expected %q, but not found.\", substring)\n\t\t}\n\t}\n}\n"},{"name":"util.gno","body":"package gnopages\n\nfunc checkErr(err error) {\n\tif err != nil {\n\t\tpanic(err)\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":"validators","path":"gno.land/r/sys/validators/v2","files":[{"name":"doc.gno","body":"// Package validators implements the on-chain validator set management through Proof of Contribution.\n// The Realm exposes only a public executor for govdao proposals, that can suggest validator set changes.\npackage validators\n"},{"name":"gnosdk.gno","body":"package validators\n\nimport (\n\t\"gno.land/p/sys/validators\"\n)\n\n// GetChanges returns the validator changes stored on the realm, since the given block number.\n// This function is intended to be called by gno.land through the GnoSDK\nfunc GetChanges(from int64) []validators.Validator {\n\tvalsetChanges := make([]validators.Validator, 0)\n\n\t// Gather the changes from the specified block\n\tchanges.Iterate(getBlockID(from), \"\", func(_ string, value interface{}) bool {\n\t\tchs := value.([]change)\n\n\t\tfor _, ch := range chs {\n\t\t\tvalsetChanges = append(valsetChanges, ch.validator)\n\t\t}\n\n\t\treturn false\n\t})\n\n\treturn valsetChanges\n}\n"},{"name":"init.gno","body":"package validators\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/nt/poa\"\n\t\"gno.land/p/sys/validators\"\n)\n\nfunc init() {\n\t// Prepare the initial validator set\n\tset := []validators.Validator{\n\t\t// core-val-1\n\t\t{\n\t\t\tAddress: std.Address(\"g1qn3jwvdpva622j3fyudqy65zstnqx2wnqhrs3s\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zpndqtjh5dcsnd0gcez3frs3w6rsttmlekj4cyywegyh0n8uprwvj5n8688\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// core-val-2\n\t\t{\n\t\t\tAddress: std.Address(\"g1gtu9czw9qavrtdnf936usvwjwyjz0x0jk243au\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zq4y0ppxhxazdwxhnsxxzdmh9rxht888n4fl0mskwcpq7y2404dm2h0lamk\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// core-val-3\n\t\t{\n\t\t\tAddress: std.Address(\"g19emxxnzzfa0pkffvthrss5drgccjnwj8mdme4f\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zq288fe7pd2yy3h2h8qjh0elu3pxuamf3wpa9qt9s6jja0r3k64ue4mh636\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// core-val-4\n\t\t{\n\t\t\tAddress: std.Address(\"g1hyxtsgjr5zt06jcx4z0xenn3u442ad2xgzu7lp\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zpy4mst534500z7k6xk5u7c9ex8zs44rjjhmxaxtw9zzjv82qkfhkhx2rfs\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// core-val-5\n\t\t{\n\t\t\tAddress: std.Address(\"g1l072ma0vfhx7s4vpevfvuxd6wzkv5ztt7gh99w\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zqtvz3g6nvu3d6wdz97w7jdw2sjc65us5u8gj8pm4mhasw7zxakjhjn9qkm\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// core-val-6\n\t\t{\n\t\t\tAddress: std.Address(\"g1uwqd3284kuzm56auwyc9d87jf3953tp9pnt506\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zp8xm09ura7mwyntee78cl64hgzq0x75f05tv7fkxpqvc797j37hsr3vgjg\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// berty-val-1\n\t\t{\n\t\t\tAddress: std.Address(\"g1ut590acnamvhkrh4qz6dz9zt9e3hyu499u0gvl\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zq2gncppkfzmx7s22mn60mf0uxzzpl23yx97hwmwm8yc6lupepqqnlexfch\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// onbloc-val-1\n\t\t{\n\t\t\tAddress: std.Address(\"g1arkzjfrte9l97v9q2qye07v0lw07039gaa3hfy\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zpkvdy7n9744qay76fzekpu9l6g3mp4hzhqjmp6k2as72ghlzc546ju3a09\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// onbloc-val-2\n\t\t{\n\t\t\tAddress: std.Address(\"g1x0m33nyne064xdx7tvlfcjwd4xkajjar6h523z\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zp6s70v4wurhg699w6f9emkwxdlm2eyf2uv64annj47npq85tjeucedmky9\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// devx-val-1\n\t\t{\n\t\t\tAddress: std.Address(\"g1mxguhd5zacar64txhfm0v7hhtph5wur5hx86vs\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zqz6fwulsygvu9xypka3zqxhkxllm467e3adphmj6y44vn3yy8qq34vxnse\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// devx-val-2\n\t\t{\n\t\t\tAddress: std.Address(\"g1t9ctfa468hn6czff8kazw08crazehcxaqa2uaa\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zpsq650w975vqsf6ajj5x4wdzfnrh64kmw7sljqz7wts6k0p6l36d0huls3\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// devx-val-3\n\t\t{\n\t\t\tAddress: std.Address(\"g1sll4rtvrepdyzcvg5ml0kjtl7fnwgcsxgg9s5q\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zplr4zg2smgha4n9huwcywm6pnkuny2x2j44kk4srxcf0rrmpql3035k8s2\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// devx-val-4\n\t\t{\n\t\t\tAddress: std.Address(\"g1aa5pp94eaextkump38766hpdra74xtfh805msv\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zqe85el3ardhel5vruywsdjw0vj2zjyhqhsyhcnuh0dy8xhuj8mxjg5h7uw\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// tori-val-1\n\t\t{\n\t\t\tAddress: std.Address(\"g1r2lwzu0y0na4686a0lz4f2zqxlffqkfm7lqqqp\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zq2quztlp2pffjsun3jeqyesru8rx9yc6tfj9na3hnw9qgn4zlrpul5mhd0\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// aib-val-1\n\t\t{\n\t\t\tAddress: std.Address(\"g1ecdu2gwz9d46srrhpu7k60pnrquvle5z2a5nn0\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zqnrer4hlsq7q22egx9ur357hg8ftsntyh4z2g7x69u2s4ay25vdw4tredd\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// aib-val-2\n\t\t{\n\t\t\tAddress: std.Address(\"g169wsuqlrscnvxtsu6wrc0zuwn39tmctw7q9f0q\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zpv6k4a2r6x6gt7eqp70l5vxluk9zkdmlqvkxztnc8zp2llq73e6ukxvsf6\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t\t// aib-val-3\n\t\t{\n\t\t\tAddress: std.Address(\"g1hfwh3ufph3zczs5wu4qvpgtv79fzh30rgzdux8\"),\n\t\t\tPubKey: \"gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zqxx3qdzl9f6lee42vhtka5luujhxg22tesyww52af68f75zzp0snyhl8mw\",\n\t\t\tVotingPower: 1,\n\t\t},\n\t}\n\n\t// The default valset protocol is PoA\n\tvp = poa.NewPoA(poa.WithInitialSet(set))\n\n\t// No changes to apply initially\n\tchanges = avl.NewTree()\n}\n"},{"name":"poc.gno","body":"package validators\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/sys/validators\"\n\t\"gno.land/r/gov/dao/bridge\"\n)\n\nconst errNoChangesProposed = \"no set changes proposed\"\n\n// NewPropExecutor creates a new executor that wraps a changes closure\n// proposal. This wrapper is required to ensure the GovDAO Realm actually\n// executed the callback.\n//\n// Concept adapted from:\n// https://github.com/gnolang/gno/pull/1945\nfunc NewPropExecutor(changesFn func() []validators.Validator) dao.Executor {\n\tif changesFn == nil {\n\t\tpanic(errNoChangesProposed)\n\t}\n\n\tcallback := func() error {\n\t\tfor _, change := range changesFn() {\n\t\t\tif change.VotingPower == 0 {\n\t\t\t\t// This change request is to remove the validator\n\t\t\t\tremoveValidator(change.Address)\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// This change request is to add the validator\n\t\t\taddValidator(change)\n\t\t}\n\n\t\treturn nil\n\t}\n\n\treturn bridge.GovDAO().NewGovDAOExecutor(callback)\n}\n\n// IsValidator returns a flag indicating if the given bech32 address\n// is part of the validator set\nfunc IsValidator(addr std.Address) bool {\n\treturn vp.IsValidator(addr)\n}\n\n// GetValidators returns the typed validator set\nfunc GetValidators() []validators.Validator {\n\treturn vp.GetValidators()\n}\n"},{"name":"validators.gno","body":"package validators\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/seqid\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/p/sys/validators\"\n)\n\nvar (\n\tvp validators.ValsetProtocol // p is the underlying validator set protocol\n\tchanges *avl.Tree // changes holds any valset changes; seqid(block number) -\u003e []change\n)\n\n// change represents a single valset change, tied to a specific block number\ntype change struct {\n\tblockNum int64 // the block number associated with the valset change\n\tvalidator validators.Validator // the validator update\n}\n\n// addValidator adds a new validator to the validator set.\n// If the validator is already present, the method errors out\nfunc addValidator(validator validators.Validator) {\n\tval, err := vp.AddValidator(validator.Address, validator.PubKey, validator.VotingPower)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Validator added, note the change\n\tch := change{\n\t\tblockNum: std.GetHeight(),\n\t\tvalidator: val,\n\t}\n\n\tsaveChange(ch)\n\n\t// Emit the validator set change\n\tstd.Emit(validators.ValidatorAddedEvent)\n}\n\n// removeValidator removes the given validator from the set.\n// If the validator is not present in the set, the method errors out\nfunc removeValidator(address std.Address) {\n\tval, err := vp.RemoveValidator(address)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Validator removed, note the change\n\tch := change{\n\t\tblockNum: std.GetHeight(),\n\t\tvalidator: validators.Validator{\n\t\t\tAddress: val.Address,\n\t\t\tPubKey: val.PubKey,\n\t\t\tVotingPower: 0, // nullified the voting power indicates removal\n\t\t},\n\t}\n\n\tsaveChange(ch)\n\n\t// Emit the validator set change\n\tstd.Emit(validators.ValidatorRemovedEvent)\n}\n\n// saveChange saves the valset change\nfunc saveChange(ch change) {\n\tid := getBlockID(ch.blockNum)\n\n\tsetRaw, exists := changes.Get(id)\n\tif !exists {\n\t\tchanges.Set(id, []change{ch})\n\n\t\treturn\n\t}\n\n\t// Save the change\n\tset := setRaw.([]change)\n\tset = append(set, ch)\n\n\tchanges.Set(id, set)\n}\n\n// getBlockID converts the block number to a sequential ID\nfunc getBlockID(blockNum int64) string {\n\treturn seqid.ID(uint64(blockNum)).String()\n}\n\nfunc Render(_ string) string {\n\tvar (\n\t\tsize = changes.Size()\n\t\tmaxDisplay = 10\n\t)\n\n\tif size == 0 {\n\t\treturn \"No valset changes to apply.\"\n\t}\n\n\toutput := \"Valset changes:\\n\"\n\tchanges.ReverseIterateByOffset(size-maxDisplay, maxDisplay, func(_ string, value interface{}) bool {\n\t\tchs := value.([]change)\n\n\t\tfor _, ch := range chs {\n\t\t\toutput += ufmt.Sprintf(\n\t\t\t\t\"- #%d: %s (%d)\\n\",\n\t\t\t\tch.blockNum,\n\t\t\t\tch.validator.Address.String(),\n\t\t\t\tch.validator.VotingPower,\n\t\t\t)\n\t\t}\n\n\t\treturn false\n\t})\n\n\treturn output\n}\n"},{"name":"validators_test.gno","body":"package validators\n\nimport (\n\t\"testing\"\n\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\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/sys/validators\"\n)\n\n// generateTestValidators generates a dummy validator set\nfunc generateTestValidators(count int) []validators.Validator {\n\tvals := make([]validators.Validator, 0, count)\n\n\tfor i := 0; i \u003c count; i++ {\n\t\tval := validators.Validator{\n\t\t\tAddress: testutils.TestAddress(ufmt.Sprintf(\"%d\", i)),\n\t\t\tPubKey: \"public-key\",\n\t\t\tVotingPower: 10,\n\t\t}\n\n\t\tvals = append(vals, val)\n\t}\n\n\treturn vals\n}\n\nfunc TestValidators_AddRemove(t *testing.T) {\n\t// Clear any changes\n\tchanges = avl.NewTree()\n\n\tvar (\n\t\tvals = generateTestValidators(100)\n\t\tinitialHeight = int64(123)\n\t)\n\n\t// Add in the validators\n\tfor _, val := range vals {\n\t\taddValidator(val)\n\n\t\t// Make sure the validator is added\n\t\tuassert.True(t, vp.IsValidator(val.Address))\n\n\t\tstd.TestSkipHeights(1)\n\t}\n\n\tfor i := initialHeight; i \u003c initialHeight+int64(len(vals)); i++ {\n\t\t// Make sure the changes are saved\n\t\tchs := GetChanges(i)\n\n\t\t// We use the funky index calculation to make sure\n\t\t// changes are properly handled for each block span\n\t\tuassert.Equal(t, initialHeight+int64(len(vals))-i, int64(len(chs)))\n\n\t\tfor index, val := range vals[i-initialHeight:] {\n\t\t\t// Make sure the changes are equal to the additions\n\t\t\tch := chs[index]\n\n\t\t\tuassert.Equal(t, val.Address, ch.Address)\n\t\t\tuassert.Equal(t, val.PubKey, ch.PubKey)\n\t\t\tuassert.Equal(t, val.VotingPower, ch.VotingPower)\n\t\t}\n\t}\n\n\t// Save the beginning height for the removal\n\tinitialRemoveHeight := std.GetHeight()\n\n\t// Clear any changes\n\tchanges = avl.NewTree()\n\n\t// Remove the validators\n\tfor _, val := range vals {\n\t\tremoveValidator(val.Address)\n\n\t\t// Make sure the validator is removed\n\t\tuassert.False(t, vp.IsValidator(val.Address))\n\n\t\tstd.TestSkipHeights(1)\n\t}\n\n\tfor i := initialRemoveHeight; i \u003c initialRemoveHeight+int64(len(vals)); i++ {\n\t\t// Make sure the changes are saved\n\t\tchs := GetChanges(i)\n\n\t\t// We use the funky index calculation to make sure\n\t\t// changes are properly handled for each block span\n\t\tuassert.Equal(t, initialRemoveHeight+int64(len(vals))-i, int64(len(chs)))\n\n\t\tfor index, val := range vals[i-initialRemoveHeight:] {\n\t\t\t// Make sure the changes are equal to the additions\n\t\t\tch := chs[index]\n\n\t\t\tuassert.Equal(t, val.Address, ch.Address)\n\t\t\tuassert.Equal(t, val.PubKey, ch.PubKey)\n\t\t\tuassert.Equal(t, uint64(0), ch.VotingPower)\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":"valopers","path":"gno.land/r/gnoland/valopers/v2","files":[{"name":"init.gno","body":"package valopers\n\nimport \"gno.land/p/demo/avl\"\n\nfunc init() {\n\tvalopers = avl.NewTree()\n}\n"},{"name":"valopers.gno","body":"// Package valopers is designed around the permissionless lifecycle of valoper profiles.\n// It also includes parts designed for govdao to propose valset changes based on registered valopers.\npackage valopers\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/ufmt\"\n\tpVals \"gno.land/p/sys/validators\"\n\t\"gno.land/r/gov/dao/bridge\"\n\tvalidators \"gno.land/r/sys/validators/v2\"\n)\n\nconst (\n\terrValoperExists = \"valoper already exists\"\n\terrValoperMissing = \"valoper does not exist\"\n\terrInvalidAddressUpdate = \"valoper updated address exists\"\n\terrValoperNotCaller = \"valoper is not the caller\"\n)\n\n// valopers keeps track of all the active validator operators\nvar valopers *avl.Tree // Address -\u003e Valoper\n\n// Valoper represents a validator operator profile\ntype Valoper struct {\n\tName string // the display name of the valoper\n\tMoniker string // the moniker of the valoper\n\tDescription string // the description of the valoper\n\n\tAddress std.Address // The bech32 gno address of the validator\n\tPubKey string // the bech32 public key of the validator\n\tP2PAddresses []string // the publicly reachable P2P addresses of the validator\n\tActive bool // flag indicating if the valoper is active\n}\n\n// Register registers a new valoper\nfunc Register(v Valoper) {\n\t// Check if the valoper is already registered\n\tif isValoper(v.Address) {\n\t\tpanic(errValoperExists)\n\t}\n\n\t// TODO add address derivation from public key\n\t// (when the laws of gno make it possible)\n\n\t// Save the valoper to the set\n\tvalopers.Set(v.Address.String(), v)\n}\n\n// Update updates an existing valoper\nfunc Update(address std.Address, v Valoper) {\n\t// Check if the valoper is present\n\tif !isValoper(address) {\n\t\tpanic(errValoperMissing)\n\t}\n\n\t// Check that the valoper wouldn't be\n\t// overwriting an existing one\n\tisAddressUpdate := address != v.Address\n\tif isAddressUpdate \u0026\u0026 isValoper(v.Address) {\n\t\tpanic(errInvalidAddressUpdate)\n\t}\n\n\t// Remove the old valoper info\n\t// in case the address changed\n\tif address != v.Address {\n\t\tvalopers.Remove(address.String())\n\t}\n\n\t// Save the new valoper info\n\tvalopers.Set(v.Address.String(), v)\n}\n\n// GetByAddr fetches the valoper using the address, if present\nfunc GetByAddr(address std.Address) Valoper {\n\tvaloperRaw, exists := valopers.Get(address.String())\n\tif !exists {\n\t\tpanic(errValoperMissing)\n\t}\n\n\treturn valoperRaw.(Valoper)\n}\n\n// Render renders the current valoper set\nfunc Render(_ string) string {\n\tif valopers.Size() == 0 {\n\t\treturn \"No valopers to display.\"\n\t}\n\n\toutput := \"Valset changes to apply:\\n\"\n\tvalopers.Iterate(\"\", \"\", func(_ string, value interface{}) bool {\n\t\tvaloper := value.(Valoper)\n\n\t\toutput += valoper.Render()\n\n\t\treturn false\n\t})\n\n\treturn output\n}\n\n// Render renders a single valoper with their information\nfunc (v Valoper) Render() string {\n\toutput := ufmt.Sprintf(\"## %s (%s)\\n\", v.Name, v.Moniker)\n\toutput += ufmt.Sprintf(\"%s\\n\\n\", v.Description)\n\toutput += ufmt.Sprintf(\"- Address: %s\\n\", v.Address.String())\n\toutput += ufmt.Sprintf(\"- PubKey: %s\\n\", v.PubKey)\n\toutput += \"- P2P Addresses: [\\n\"\n\n\tif len(v.P2PAddresses) == 0 {\n\t\toutput += \"]\\n\"\n\n\t\treturn output\n\t}\n\n\tfor index, addr := range v.P2PAddresses {\n\t\toutput += addr\n\n\t\tif index == len(v.P2PAddresses)-1 {\n\t\t\toutput += \"]\\n\"\n\n\t\t\tcontinue\n\t\t}\n\n\t\toutput += \",\\n\"\n\t}\n\n\treturn output\n}\n\n// isValoper checks if the valoper exists\nfunc isValoper(address std.Address) bool {\n\t_, exists := valopers.Get(address.String())\n\n\treturn exists\n}\n\n// GovDAOProposal creates a proposal to the GovDAO\n// for adding the given valoper to the validator set.\n// This function is meant to serve as a helper\n// for generating the govdao proposal\nfunc GovDAOProposal(address std.Address) {\n\tvar (\n\t\tvaloper = GetByAddr(address)\n\t\tvotingPower = uint64(1)\n\t)\n\n\t// Make sure the valoper is the caller\n\tif std.OriginCaller() != address {\n\t\tpanic(errValoperNotCaller)\n\t}\n\n\t// Determine the voting power\n\tif !valoper.Active {\n\t\tvotingPower = uint64(0)\n\t}\n\n\tchangesFn := func() []pVals.Validator {\n\t\treturn []pVals.Validator{\n\t\t\t{\n\t\t\t\tAddress: valoper.Address,\n\t\t\t\tPubKey: valoper.PubKey,\n\t\t\t\tVotingPower: votingPower,\n\t\t\t},\n\t\t}\n\t}\n\n\t// Create the executor\n\texecutor := validators.NewPropExecutor(changesFn)\n\n\t// Craft the proposal description\n\tdescription := ufmt.Sprintf(\n\t\t\"Add valoper %s (Address: %s; PubKey: %s) to the valset\",\n\t\tvaloper.Name,\n\t\tvaloper.Address.String(),\n\t\tvaloper.PubKey,\n\t)\n\n\tprop := dao.ProposalRequest{\n\t\tDescription: description,\n\t\tExecutor: executor,\n\t}\n\n\t// Create the govdao proposal\n\tbridge.GovDAO().Propose(prop)\n}\n"},{"name":"valopers_test.gno","body":"package valopers\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestValopers_Register(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"already a valoper\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Clear the set for the test\n\t\tvalopers = avl.NewTree()\n\n\t\tv := Valoper{\n\t\t\tAddress: testutils.TestAddress(\"valoper\"),\n\t\t}\n\n\t\t// Add the valoper\n\t\tvalopers.Set(v.Address.String(), v)\n\n\t\tuassert.PanicsWithMessage(t, errValoperExists, func() {\n\t\t\tRegister(v)\n\t\t})\n\t})\n\n\tt.Run(\"successful registration\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Clear the set for the test\n\t\tvalopers = avl.NewTree()\n\n\t\tv := Valoper{\n\t\t\tAddress: testutils.TestAddress(\"valoper\"),\n\t\t\tName: \"new valoper\",\n\t\t\tMoniker: \"val-1\",\n\t\t\tPubKey: \"pub key\",\n\t\t}\n\n\t\tuassert.NotPanics(t, func() {\n\t\t\tRegister(v)\n\t\t})\n\n\t\tuassert.NotPanics(t, func() {\n\t\t\tvaloper := GetByAddr(v.Address)\n\n\t\t\tuassert.Equal(t, v.Address, valoper.Address)\n\t\t\tuassert.Equal(t, v.Name, valoper.Name)\n\t\t\tuassert.Equal(t, v.Moniker, valoper.Moniker)\n\t\t\tuassert.Equal(t, v.PubKey, valoper.PubKey)\n\t\t})\n\t})\n}\n\nfunc TestValopers_Update(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"non-existing valoper\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Clear the set for the test\n\t\tvalopers = avl.NewTree()\n\n\t\tv := Valoper{}\n\n\t\t// Update the valoper\n\t\tuassert.PanicsWithMessage(t, errValoperMissing, func() {\n\t\t\tUpdate(v.Address, v)\n\t\t})\n\t})\n\n\tt.Run(\"overwrite valoper\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Clear the set for the test\n\t\tvalopers = avl.NewTree()\n\n\t\tone := Valoper{\n\t\t\tAddress: testutils.TestAddress(\"valoper 1\"),\n\t\t}\n\n\t\t// Add the valoper\n\t\tuassert.NotPanics(t, func() {\n\t\t\tRegister(one)\n\t\t})\n\n\t\tinitialAddress := testutils.TestAddress(\"valoper 2\")\n\t\ttwo := Valoper{\n\t\t\tAddress: initialAddress,\n\t\t}\n\n\t\t// Add the valoper\n\t\tuassert.NotPanics(t, func() {\n\t\t\tRegister(two)\n\t\t})\n\n\t\t// Update the valoper address\n\t\t// so it overlaps\n\t\ttwo = Valoper{\n\t\t\tAddress: one.Address,\n\t\t}\n\n\t\t// Update the valoper\n\t\tuassert.PanicsWithMessage(t, errInvalidAddressUpdate, func() {\n\t\t\tUpdate(initialAddress, two)\n\t\t})\n\t})\n\n\tt.Run(\"successful update\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Clear the set for the test\n\t\tvalopers = avl.NewTree()\n\n\t\tvar (\n\t\t\tname = \"new valoper\"\n\t\t\tv = Valoper{\n\t\t\t\tAddress: testutils.TestAddress(\"valoper\"),\n\t\t\t\tName: name,\n\t\t\t\tPubKey: \"pub key\",\n\t\t\t}\n\t\t)\n\n\t\t// Add the valoper\n\t\tuassert.NotPanics(t, func() {\n\t\t\tRegister(v)\n\t\t})\n\n\t\t// Update the valoper name\n\t\tv.Name = \"new name\"\n\t\tv.Active = false\n\n\t\t// Update the valoper\n\t\tuassert.NotPanics(t, func() {\n\t\t\tUpdate(v.Address, v)\n\t\t})\n\n\t\t// Make sure the valoper is updated\n\t\tuassert.NotPanics(t, func() {\n\t\t\tvaloper := GetByAddr(v.Address)\n\n\t\t\tuassert.Equal(t, v.Name, valoper.Name)\n\t\t\tuassert.Equal(t, v.Active, valoper.Active)\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":"config","path":"gno.land/r/leon/config","files":[{"name":"config.gno","body":"package config\n\nimport (\n\t\"errors\"\n\t\"std\"\n)\n\nvar (\n\tmain std.Address // leon's main address\n\tbackup std.Address // backup address\n\n\tErrInvalidAddr = errors.New(\"leon's config: invalid address\")\n\tErrUnauthorized = errors.New(\"leon's config: unauthorized\")\n)\n\nfunc init() {\n\tmain = \"g125em6arxsnj49vx35f0n0z34putv5ty3376fg5\"\n}\n\nfunc Address() std.Address {\n\treturn main\n}\n\nfunc Backup() std.Address {\n\treturn backup\n}\n\nfunc SetAddress(a std.Address) error {\n\tif !a.IsValid() {\n\t\treturn ErrInvalidAddr\n\t}\n\n\tif err := checkAuthorized(); err != nil {\n\t\treturn err\n\t}\n\n\tmain = a\n\treturn nil\n}\n\nfunc SetBackup(a std.Address) error {\n\tif !a.IsValid() {\n\t\treturn ErrInvalidAddr\n\t}\n\n\tif err := checkAuthorized(); err != nil {\n\t\treturn err\n\t}\n\n\tbackup = a\n\treturn nil\n}\n\nfunc checkAuthorized() error {\n\tcaller := std.PreviousRealm().Addr()\n\tisAuthorized := caller == main || caller == backup\n\n\tif !isAuthorized {\n\t\treturn ErrUnauthorized\n\t}\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_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"home","path":"gno.land/r/leon/home","files":[{"name":"home.gno","body":"package home\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/ufmt\"\n\n\t\"gno.land/r/demo/art/gnoface\"\n\t\"gno.land/r/demo/art/millipede\"\n\t\"gno.land/r/leon/config\"\n)\n\nvar (\n\tpfp string // link to profile picture\n\tpfpCaption string // profile picture caption\n\tabtMe [2]string\n)\n\nfunc init() {\n\tpfp = \"https://i.imgflip.com/91vskx.jpg\"\n\tpfpCaption = \"[My favourite painting \u0026 pfp](https://en.wikipedia.org/wiki/Wanderer_above_the_Sea_of_Fog)\"\n\tabtMe = [2]string{\n\t\t`### About me\nHi, I'm Leon, a DevRel Engineer at gno.land. I am a tech enthusiast, \nlife-long learner, and sharer of knowledge.`,\n\t\t`### Contributions\nMy contributions to gno.land can mainly be found \n[here](https://github.com/gnolang/gno/issues?q=sort:updated-desc+author:leohhhn).\n\nTODO import r/gh\n`,\n\t}\n}\n\nfunc UpdatePFP(url, caption string) {\n\tif !isAuthorized(std.PreviousRealm().Addr()) {\n\t\tpanic(config.ErrUnauthorized)\n\t}\n\n\tpfp = url\n\tpfpCaption = caption\n}\n\nfunc UpdateAboutMe(col1, col2 string) {\n\tif !isAuthorized(std.PreviousRealm().Addr()) {\n\t\tpanic(config.ErrUnauthorized)\n\t}\n\n\tabtMe[0] = col1\n\tabtMe[1] = col2\n}\n\nfunc Render(path string) string {\n\tout := \"# Leon's Homepage\\n\\n\"\n\n\tout += renderAboutMe()\n\tout += renderBlogPosts()\n\tout += \"\\n\\n\"\n\tout += renderArt()\n\n\treturn out\n}\n\nfunc renderBlogPosts() string {\n\tout := \"\"\n\t//out += \"## Leon's Blog Posts\"\n\n\t// todo fetch blog posts authored by @leohhhn\n\t// and render them\n\treturn out\n}\n\nfunc renderAboutMe() string {\n\tout := \"\u003cdiv class='columns-3'\u003e\"\n\n\tout += \"\u003cdiv\u003e\\n\\n\"\n\tout += ufmt.Sprintf(\"![my profile pic](%s)\\n\\n%s\\n\\n\", pfp, pfpCaption)\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\tout += \"\u003cdiv\u003e\\n\\n\"\n\tout += abtMe[0] + \"\\n\\n\"\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\tout += \"\u003cdiv\u003e\\n\\n\"\n\tout += abtMe[1] + \"\\n\\n\"\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\tout += \"\u003c/div\u003e\u003c!-- /columns-3 --\u003e\\n\\n\"\n\n\treturn out\n}\n\nfunc renderArt() string {\n\tout := `\u003cdiv class=\"jumbotron\"\u003e` + \"\\n\\n\"\n\tout += \"# Gno Art\\n\\n\"\n\n\tout += \"\u003cdiv class='columns-3'\u003e\"\n\n\tout += renderGnoFace()\n\tout += renderMillipede()\n\tout += \"Empty spot :/\"\n\n\tout += \"\u003c/div\u003e\u003c!-- /columns-3 --\u003e\\n\\n\"\n\n\tout += \"This art is dynamic; it will change with every new block.\\n\\n\"\n\tout += `\u003c/div\u003e\u003c!-- /jumbotron --\u003e` + \"\\n\"\n\n\treturn out\n}\n\nfunc renderGnoFace() string {\n\tout := \"\u003cdiv\u003e\\n\\n\"\n\tout += gnoface.Render(strconv.Itoa(int(std.GetHeight())))\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\treturn out\n}\n\nfunc renderMillipede() string {\n\tout := \"\u003cdiv\u003e\\n\\n\"\n\tout += \"Millipede\\n\\n\"\n\tout += \"```\\n\" + millipede.Draw(int(std.GetHeight())%10+1) + \"```\\n\"\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\treturn out\n}\n\nfunc isAuthorized(addr std.Address) bool {\n\treturn addr == config.Address() || addr == config.Backup()\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":"config","path":"gno.land/r/leon/config","files":[{"name":"config.gno","body":"package config\n\nimport (\n\t\"errors\"\n\t\"std\"\n)\n\nvar (\n\tmain std.Address // leon's main address\n\tbackup std.Address // backup address\n\n\tErrInvalidAddr = errors.New(\"leon's config: invalid address\")\n\tErrUnauthorized = errors.New(\"leon's config: unauthorized\")\n)\n\nfunc init() {\n\tmain = \"g125em6arxsnj49vx35f0n0z34putv5ty3376fg5\"\n}\n\nfunc Address() std.Address {\n\treturn main\n}\n\nfunc Backup() std.Address {\n\treturn backup\n}\n\nfunc SetAddress(a std.Address) error {\n\tif !a.IsValid() {\n\t\treturn ErrInvalidAddr\n\t}\n\n\tif err := checkAuthorized(); err != nil {\n\t\treturn err\n\t}\n\n\tmain = a\n\treturn nil\n}\n\nfunc SetBackup(a std.Address) error {\n\tif !a.IsValid() {\n\t\treturn ErrInvalidAddr\n\t}\n\n\tif err := checkAuthorized(); err != nil {\n\t\treturn err\n\t}\n\n\tbackup = a\n\treturn nil\n}\n\nfunc checkAuthorized() error {\n\tcaller := std.PreviousRealm().Address()\n\tisAuthorized := caller == main || caller == backup\n\n\tif !isAuthorized {\n\t\treturn ErrUnauthorized\n\t}\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_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"home","path":"gno.land/r/leon/home","files":[{"name":"home.gno","body":"package home\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/ufmt\"\n\n\t\"gno.land/r/demo/art/gnoface\"\n\t\"gno.land/r/demo/art/millipede\"\n\t\"gno.land/r/leon/config\"\n)\n\nvar (\n\tpfp string // link to profile picture\n\tpfpCaption string // profile picture caption\n\tabtMe [2]string\n)\n\nfunc init() {\n\tpfp = \"https://i.imgflip.com/91vskx.jpg\"\n\tpfpCaption = \"[My favourite painting \u0026 pfp](https://en.wikipedia.org/wiki/Wanderer_above_the_Sea_of_Fog)\"\n\tabtMe = [2]string{\n\t\t`### About me\nHi, I'm Leon, a DevRel Engineer at gno.land. I am a tech enthusiast, \nlife-long learner, and sharer of knowledge.`,\n\t\t`### Contributions\nMy contributions to gno.land can mainly be found \n[here](https://github.com/gnolang/gno/issues?q=sort:updated-desc+author:leohhhn).\n\nTODO import r/gh\n`,\n\t}\n}\n\nfunc UpdatePFP(url, caption string) {\n\tif !isAuthorized(std.PreviousRealm().Address()) {\n\t\tpanic(config.ErrUnauthorized)\n\t}\n\n\tpfp = url\n\tpfpCaption = caption\n}\n\nfunc UpdateAboutMe(col1, col2 string) {\n\tif !isAuthorized(std.PreviousRealm().Address()) {\n\t\tpanic(config.ErrUnauthorized)\n\t}\n\n\tabtMe[0] = col1\n\tabtMe[1] = col2\n}\n\nfunc Render(path string) string {\n\tout := \"# Leon's Homepage\\n\\n\"\n\n\tout += renderAboutMe()\n\tout += renderBlogPosts()\n\tout += \"\\n\\n\"\n\tout += renderArt()\n\n\treturn out\n}\n\nfunc renderBlogPosts() string {\n\tout := \"\"\n\t//out += \"## Leon's Blog Posts\"\n\n\t// todo fetch blog posts authored by @leohhhn\n\t// and render them\n\treturn out\n}\n\nfunc renderAboutMe() string {\n\tout := \"\u003cdiv class='columns-3'\u003e\"\n\n\tout += \"\u003cdiv\u003e\\n\\n\"\n\tout += ufmt.Sprintf(\"![my profile pic](%s)\\n\\n%s\\n\\n\", pfp, pfpCaption)\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\tout += \"\u003cdiv\u003e\\n\\n\"\n\tout += abtMe[0] + \"\\n\\n\"\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\tout += \"\u003cdiv\u003e\\n\\n\"\n\tout += abtMe[1] + \"\\n\\n\"\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\tout += \"\u003c/div\u003e\u003c!-- /columns-3 --\u003e\\n\\n\"\n\n\treturn out\n}\n\nfunc renderArt() string {\n\tout := `\u003cdiv class=\"jumbotron\"\u003e` + \"\\n\\n\"\n\tout += \"# Gno Art\\n\\n\"\n\n\tout += \"\u003cdiv class='columns-3'\u003e\"\n\n\tout += renderGnoFace()\n\tout += renderMillipede()\n\tout += \"Empty spot :/\"\n\n\tout += \"\u003c/div\u003e\u003c!-- /columns-3 --\u003e\\n\\n\"\n\n\tout += \"This art is dynamic; it will change with every new block.\\n\\n\"\n\tout += `\u003c/div\u003e\u003c!-- /jumbotron --\u003e` + \"\\n\"\n\n\treturn out\n}\n\nfunc renderGnoFace() string {\n\tout := \"\u003cdiv\u003e\\n\\n\"\n\tout += gnoface.Render(strconv.Itoa(int(std.GetHeight())))\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\treturn out\n}\n\nfunc renderMillipede() string {\n\tout := \"\u003cdiv\u003e\\n\\n\"\n\tout += \"Millipede\\n\\n\"\n\tout += \"```\\n\" + millipede.Draw(int(std.GetHeight())%10+1) + \"```\\n\"\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\treturn out\n}\n\nfunc isAuthorized(addr std.Address) bool {\n\treturn addr == config.Address() || addr == config.Backup()\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":"config","path":"gno.land/r/manfred/config","files":[{"name":"config.gno","body":"package config\n\nimport \"std\"\n\nvar addr = std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\nfunc Addr() std.Address {\n\treturn addr\n}\n\nfunc UpdateAddr(newAddr std.Address) {\n\tAssertIsAdmin()\n\taddr = newAddr\n}\n\nfunc AssertIsAdmin() {\n\tif std.OriginCaller() != addr {\n\t\tpanic(\"restricted area\")\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":"home","path":"gno.land/r/manfred/home","files":[{"name":"home.gno","body":"package home\n\nimport \"gno.land/r/manfred/config\"\n\nvar (\n\ttodos []string\n\tstatus string\n\tmemeImgURL string\n)\n\nfunc init() {\n\ttodos = append(todos, \"fill this todo list...\")\n\tstatus = \"Online\" // Initial status set to \"Online\"\n\tmemeImgURL = \"https://i.imgflip.com/7ze8dc.jpg\"\n}\n\nfunc Render(path string) string {\n\tcontent := \"# Manfred's (gn)home Dashboard\\n\\n\"\n\n\tcontent += \"## Meme\\n\"\n\tcontent += \"![](\" + memeImgURL + \")\\n\\n\"\n\n\tcontent += \"## Status\\n\"\n\tcontent += status + \"\\n\\n\"\n\n\tcontent += \"## Personal ToDo List\\n\"\n\tfor _, todo := range todos {\n\t\tcontent += \"- [ ] \" + todo + \"\\n\"\n\t}\n\tcontent += \"\\n\"\n\n\t// TODO: Implement a feature to list replies on r/boards on my posts\n\t// TODO: Maybe integrate a calendar feature for upcoming events?\n\n\treturn content\n}\n\nfunc AddNewTodo(todo string) {\n\tconfig.AssertIsAdmin()\n\ttodos = append(todos, todo)\n}\n\nfunc DeleteTodo(todoIndex int) {\n\tconfig.AssertIsAdmin()\n\tif todoIndex \u003e= 0 \u0026\u0026 todoIndex \u003c len(todos) {\n\t\t// Remove the todo from the list by merging slices from before and after the todo\n\t\ttodos = append(todos[:todoIndex], todos[todoIndex+1:]...)\n\t} else {\n\t\tpanic(\"Invalid todo index\")\n\t}\n}\n\nfunc UpdateStatus(newStatus string) {\n\tconfig.AssertIsAdmin()\n\tstatus = newStatus\n}\n"},{"name":"z1_filetest.gno","body":"package main\n\nimport \"gno.land/r/manfred/home\"\n\nfunc main() {\n\tprintln(home.Render(\"\"))\n}\n\n// Output:\n// # Manfred's (gn)home Dashboard\n//\n// ## Meme\n// ![](https://i.imgflip.com/7ze8dc.jpg)\n//\n// ## Status\n// Online\n//\n// ## Personal ToDo List\n// - [ ] fill this todo list...\n"},{"name":"z2_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/manfred/home\"\n)\n\nfunc main() {\n\tstd.TestSetOriginCaller(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\thome.AddNewTodo(\"aaa\")\n\thome.AddNewTodo(\"bbb\")\n\thome.AddNewTodo(\"ccc\")\n\thome.AddNewTodo(\"ddd\")\n\thome.AddNewTodo(\"eee\")\n\thome.UpdateStatus(\"Lorem Ipsum\")\n\thome.DeleteTodo(3)\n\tprintln(home.Render(\"\"))\n}\n\n// Output:\n// # Manfred's (gn)home Dashboard\n//\n// ## Meme\n// ![](https://i.imgflip.com/7ze8dc.jpg)\n//\n// ## Status\n// Lorem Ipsum\n//\n// ## Personal ToDo List\n// - [ ] fill this todo list...\n// - [ ] aaa\n// - [ ] bbb\n// - [ ] ddd\n// - [ ] eee\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":"present","path":"gno.land/r/manfred/present","files":[{"name":"admin.gno","body":"package present\n\nimport (\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\nvar (\n\tadminAddr std.Address\n\tmoderatorList avl.Tree\n\tinPause bool\n)\n\nfunc init() {\n\t// adminAddr = std.OriginCaller() // FIXME: find a way to use this from the main's genesis.\n\tadminAddr = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"\n}\n\nfunc AdminSetAdminAddr(addr std.Address) {\n\tassertIsAdmin()\n\tadminAddr = addr\n}\n\nfunc AdminSetInPause(state bool) {\n\tassertIsAdmin()\n\tinPause = state\n}\n\nfunc AdminAddModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), true)\n}\n\nfunc AdminRemoveModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), false) // XXX: delete instead?\n}\n\nfunc ModAddPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\tcaller := std.OriginCaller()\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.NewPost(caller, slug, title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc ModEditPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.GetPost(slug).Update(title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc isAdmin(addr std.Address) bool {\n\treturn addr == adminAddr\n}\n\nfunc isModerator(addr std.Address) bool {\n\t_, found := moderatorList.Get(addr.String())\n\treturn found\n}\n\nfunc assertIsAdmin() {\n\tcaller := std.OriginCaller()\n\tif !isAdmin(caller) {\n\t\tpanic(\"access restricted.\")\n\t}\n}\n\nfunc assertIsModerator() {\n\tcaller := std.OriginCaller()\n\tif isAdmin(caller) || isModerator(caller) {\n\t\treturn\n\t}\n\tpanic(\"access restricted\")\n}\n\nfunc assertNotInPause() {\n\tif inPause {\n\t\tpanic(\"access restricted (pause)\")\n\t}\n}\n\nfunc checkErr(err error) {\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n"},{"name":"present_miami23.gno","body":"package present\n\nfunc init() {\n\tpath := \"miami23\"\n\ttitle := \"Portal Loop Demo (Miami 2023)\"\n\tbody := `\nRendered by Gno.\n\n[Source (WIP)](https://github.com/gnolang/gno/pull/1176)\n\n## Portal Loop\n\n- DONE: Dynamic homepage, key pages, aliases, and redirects.\n- TODO: Deploy with history, complete worxdao v0.\n- Will replace the static gno.land site.\n- Enhances local development.\n\n[GitHub Issue](https://github.com/gnolang/gno/issues/1108)\n\n## Roadmap\n\n- Crafting the roadmap this week, open to collaboration.\n- Combining onchain (portal loop) and offchain (GitHub).\n- Next week: Unveiling the official v0 roadmap.\n\n## Teams, DAOs, Projects\n\n- Developing worxDAO contracts for directories of projects and teams.\n- GitHub teams and projects align with this structure.\n- CODEOWNER file updates coming.\n- Initial teams announced next week.\n\n## Tech Team Retreat Plan\n\n- Continue Portal Loop.\n- Consider dApp development.\n- Explore new topics [here](https://github.com/orgs/gnolang/projects/15/).\n- Engage in workshops.\n- Connect and have fun with colleagues.\n`\n\t_ = b.NewPost(adminAddr, path, title, body, \"2023-10-15T13:17:24Z\", []string{\"moul\"}, []string{\"demo\", \"portal-loop\", \"miami\"})\n}\n"},{"name":"present_miami23_filetest.gno","body":"package main\n\nimport (\n\t\"gno.land/r/manfred/present\"\n)\n\nfunc main() {\n\tprintln(present.Render(\"\"))\n\tprintln(\"------------------------------------\")\n\tprintln(present.Render(\"p/miami23\"))\n}\n"},{"name":"presentations.gno","body":"package present\n\nimport (\n\t\"gno.land/p/demo/blog\"\n)\n\n// TODO: switch from p/blog to p/present\n\nvar b = \u0026blog.Blog{\n\tTitle: \"Manfred's Presentations\",\n\tPrefix: \"/r/manfred/present:\",\n\tNoBreadcrumb: true,\n}\n\nfunc Render(path string) string {\n\treturn b.Render(path)\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":"guestbook","path":"gno.land/r/morgan/guestbook","files":[{"name":"admin.gno","body":"package guestbook\n\nimport (\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/seqid\"\n)\n\nvar owner = ownable.New()\n\n// AdminDelete removes the guestbook message with the given ID.\n// The user will still be marked as having submitted a message, so they\n// won't be able to re-submit a new message.\nfunc AdminDelete(signatureID string) {\n\towner.AssertCallerIsOwner()\n\n\tid, err := seqid.FromString(signatureID)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tidb := id.Binary()\n\tif !guestbook.Has(idb) {\n\t\tpanic(\"signature does not exist\")\n\t}\n\tguestbook.Remove(idb)\n}\n"},{"name":"guestbook.gno","body":"// Realm guestbook contains an implementation of a simple guestbook.\n// Come and sign yourself up!\npackage guestbook\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\t\"unicode\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/seqid\"\n)\n\n// Signature is a single entry in the guestbook.\ntype Signature struct {\n\tMessage string\n\tAuthor std.Address\n\tTime time.Time\n}\n\nconst (\n\tmaxMessageLength = 140\n\tmaxPerPage = 25\n)\n\nvar (\n\tsignatureID seqid.ID\n\tguestbook avl.Tree // id -\u003e Signature\n\thasSigned avl.Tree // address -\u003e struct{}\n)\n\nfunc init() {\n\tSign(\"You reached the end of the guestbook!\")\n}\n\nconst (\n\terrNotAUser = \"this guestbook can only be signed by users\"\n\terrAlreadySigned = \"you already signed the guestbook!\"\n\terrInvalidCharacterInMessage = \"invalid character in message\"\n)\n\n// Sign signs the guestbook, with the specified message.\nfunc Sign(message string) {\n\tprev := std.PreviousRealm()\n\tswitch {\n\tcase !prev.IsUser():\n\t\tpanic(errNotAUser)\n\tcase hasSigned.Has(prev.Addr().String()):\n\t\tpanic(errAlreadySigned)\n\t}\n\tmessage = validateMessage(message)\n\n\tguestbook.Set(signatureID.Next().Binary(), Signature{\n\t\tMessage: message,\n\t\tAuthor: prev.Addr(),\n\t\t// NOTE: time.Now() will yield the \"block time\", which is deterministic.\n\t\tTime: time.Now(),\n\t})\n\thasSigned.Set(prev.Addr().String(), struct{}{})\n}\n\nfunc validateMessage(msg string) string {\n\tif len(msg) \u003e maxMessageLength {\n\t\tpanic(\"Keep it brief! (max \" + strconv.Itoa(maxMessageLength) + \" bytes!)\")\n\t}\n\tout := \"\"\n\tfor _, ch := range msg {\n\t\tswitch {\n\t\tcase unicode.IsLetter(ch),\n\t\t\tunicode.IsNumber(ch),\n\t\t\tunicode.IsSpace(ch),\n\t\t\tunicode.IsPunct(ch):\n\t\t\tout += string(ch)\n\t\tdefault:\n\t\t\tpanic(errInvalidCharacterInMessage)\n\t\t}\n\t}\n\treturn out\n}\n\nfunc Render(maxID string) string {\n\tvar bld strings.Builder\n\n\tbld.WriteString(\"# Guestbook 📝\\n\\n[Come sign the guestbook!](./guestbook$help\u0026func=Sign)\\n\\n---\\n\\n\")\n\n\tvar maxIDBinary string\n\tif maxID != \"\" {\n\t\tmid, err := seqid.FromString(maxID)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\t// AVL iteration is exclusive, so we need to decrease the ID value to get the \"true\" maximum.\n\t\tmid--\n\t\tmaxIDBinary = mid.Binary()\n\t}\n\n\tvar lastID seqid.ID\n\tvar printed int\n\tguestbook.ReverseIterate(\"\", maxIDBinary, func(key string, val interface{}) bool {\n\t\tsig := val.(Signature)\n\t\tmessage := strings.ReplaceAll(sig.Message, \"\\n\", \"\\n\u003e \")\n\t\tbld.WriteString(\"\u003e \" + message + \"\\n\u003e\\n\")\n\t\tidValue, ok := seqid.FromBinary(key)\n\t\tif !ok {\n\t\t\tpanic(\"invalid seqid id\")\n\t\t}\n\n\t\tbld.WriteString(\"\u003e _Written by \" + sig.Author.String() + \" at \" + sig.Time.Format(time.DateTime) + \"_ (#\" + idValue.String() + \")\\n\\n---\\n\\n\")\n\t\tlastID = idValue\n\n\t\tprinted++\n\t\t// stop after exceeding limit\n\t\treturn printed \u003e= maxPerPage\n\t})\n\n\tif printed == 0 {\n\t\tbld.WriteString(\"No messages!\")\n\t} else if printed \u003e= maxPerPage {\n\t\tbld.WriteString(\"\u003cp style='text-align:right'\u003e\u003ca href='./guestbook:\" + lastID.String() + \"'\u003eNext page\u003c/a\u003e\u003c/p\u003e\")\n\t}\n\n\treturn bld.String()\n}\n"},{"name":"guestbook_test.gno","body":"package guestbook\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n)\n\nfunc TestSign(t *testing.T) {\n\tguestbook = avl.Tree{}\n\thasSigned = avl.Tree{}\n\n\tstd.TestSetRealm(std.NewUserRealm(\"g1user\"))\n\tSign(\"Hello!\")\n\n\tstd.TestSetRealm(std.NewUserRealm(\"g1user2\"))\n\tSign(\"Hello2!\")\n\n\tres := Render(\"\")\n\tt.Log(res)\n\tif !strings.Contains(res, \"\u003e Hello!\\n\u003e\\n\u003e _Written by g1user \") {\n\t\tt.Error(\"does not contain first user's message\")\n\t}\n\tif !strings.Contains(res, \"\u003e Hello2!\\n\u003e\\n\u003e _Written by g1user2 \") {\n\t\tt.Error(\"does not contain second user's message\")\n\t}\n\tif guestbook.Size() != 2 {\n\t\tt.Error(\"invalid guestbook size\")\n\t}\n}\n\nfunc TestSign_FromRealm(t *testing.T) {\n\tstd.TestSetRealm(std.NewCodeRealm(\"gno.land/r/demo/users\"))\n\n\tdefer func() {\n\t\trec := recover()\n\t\tif rec == nil {\n\t\t\tt.Fatal(\"expected panic\")\n\t\t}\n\t\trecString, ok := rec.(string)\n\t\tif !ok {\n\t\t\tt.Fatal(\"not a string\", rec)\n\t\t} else if recString != errNotAUser {\n\t\t\tt.Fatal(\"invalid error\", recString)\n\t\t}\n\t}()\n\tSign(\"Hey!\")\n}\n\nfunc TestSign_Double(t *testing.T) {\n\t// Should not allow signing twice.\n\tguestbook = avl.Tree{}\n\thasSigned = avl.Tree{}\n\n\tstd.TestSetRealm(std.NewUserRealm(\"g1user\"))\n\tSign(\"Hello!\")\n\n\tdefer func() {\n\t\trec := recover()\n\t\tif rec == nil {\n\t\t\tt.Fatal(\"expected panic\")\n\t\t}\n\t\trecString, ok := rec.(string)\n\t\tif !ok {\n\t\t\tt.Error(\"type assertion failed\", rec)\n\t\t} else if recString != errAlreadySigned {\n\t\t\tt.Error(\"invalid error message\", recString)\n\t\t}\n\t}()\n\n\tSign(\"Hello again!\")\n}\n\nfunc TestSign_InvalidMessage(t *testing.T) {\n\t// Should not allow control characters in message.\n\tguestbook = avl.Tree{}\n\thasSigned = avl.Tree{}\n\n\tstd.TestSetRealm(std.NewUserRealm(\"g1user\"))\n\n\tdefer func() {\n\t\trec := recover()\n\t\tif rec == nil {\n\t\t\tt.Fatal(\"expected panic\")\n\t\t}\n\t\trecString, ok := rec.(string)\n\t\tif !ok {\n\t\t\tt.Error(\"type assertion failed\", rec)\n\t\t} else if recString != errInvalidCharacterInMessage {\n\t\t\tt.Error(\"invalid error message\", recString)\n\t\t}\n\t}()\n\tSign(\"\\x00Hello!\")\n}\n\nfunc TestAdminDelete(t *testing.T) {\n\tconst (\n\t\tuserAddr std.Address = \"g1user\"\n\t\tadminAddr std.Address = \"g1admin\"\n\t)\n\n\tguestbook = avl.Tree{}\n\thasSigned = avl.Tree{}\n\towner = ownable.NewWithAddress(adminAddr)\n\tsignatureID = 0\n\n\tstd.TestSetRealm(std.NewUserRealm(userAddr))\n\n\tconst bad = \"Very Bad Message! Nyeh heh heh!\"\n\tSign(bad)\n\n\tif rnd := Render(\"\"); !strings.Contains(rnd, bad) {\n\t\tt.Fatal(\"render does not contain bad message\", rnd)\n\t}\n\n\tstd.TestSetRealm(std.NewUserRealm(adminAddr))\n\tAdminDelete(signatureID.String())\n\n\tif rnd := Render(\"\"); strings.Contains(rnd, bad) {\n\t\tt.Error(\"render contains bad message\", rnd)\n\t}\n\tif guestbook.Size() != 0 {\n\t\tt.Error(\"invalid guestbook size\")\n\t}\n\tif hasSigned.Size() != 1 {\n\t\tt.Error(\"invalid hasSigned size\")\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":"guestbook","path":"gno.land/r/morgan/guestbook","files":[{"name":"admin.gno","body":"package guestbook\n\nimport (\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/seqid\"\n)\n\nvar owner = ownable.New()\n\n// AdminDelete removes the guestbook message with the given ID.\n// The user will still be marked as having submitted a message, so they\n// won't be able to re-submit a new message.\nfunc AdminDelete(signatureID string) {\n\towner.AssertCallerIsOwner()\n\n\tid, err := seqid.FromString(signatureID)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tidb := id.Binary()\n\tif !guestbook.Has(idb) {\n\t\tpanic(\"signature does not exist\")\n\t}\n\tguestbook.Remove(idb)\n}\n"},{"name":"guestbook.gno","body":"// Realm guestbook contains an implementation of a simple guestbook.\n// Come and sign yourself up!\npackage guestbook\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\t\"unicode\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/seqid\"\n)\n\n// Signature is a single entry in the guestbook.\ntype Signature struct {\n\tMessage string\n\tAuthor std.Address\n\tTime time.Time\n}\n\nconst (\n\tmaxMessageLength = 140\n\tmaxPerPage = 25\n)\n\nvar (\n\tsignatureID seqid.ID\n\tguestbook avl.Tree // id -\u003e Signature\n\thasSigned avl.Tree // address -\u003e struct{}\n)\n\nfunc init() {\n\tSign(\"You reached the end of the guestbook!\")\n}\n\nconst (\n\terrNotAUser = \"this guestbook can only be signed by users\"\n\terrAlreadySigned = \"you already signed the guestbook!\"\n\terrInvalidCharacterInMessage = \"invalid character in message\"\n)\n\n// Sign signs the guestbook, with the specified message.\nfunc Sign(message string) {\n\tprev := std.PreviousRealm()\n\tswitch {\n\tcase !prev.IsUser():\n\t\tpanic(errNotAUser)\n\tcase hasSigned.Has(prev.Address().String()):\n\t\tpanic(errAlreadySigned)\n\t}\n\tmessage = validateMessage(message)\n\n\tguestbook.Set(signatureID.Next().Binary(), Signature{\n\t\tMessage: message,\n\t\tAuthor: prev.Address(),\n\t\t// NOTE: time.Now() will yield the \"block time\", which is deterministic.\n\t\tTime: time.Now(),\n\t})\n\thasSigned.Set(prev.Address().String(), struct{}{})\n}\n\nfunc validateMessage(msg string) string {\n\tif len(msg) \u003e maxMessageLength {\n\t\tpanic(\"Keep it brief! (max \" + strconv.Itoa(maxMessageLength) + \" bytes!)\")\n\t}\n\tout := \"\"\n\tfor _, ch := range msg {\n\t\tswitch {\n\t\tcase unicode.IsLetter(ch),\n\t\t\tunicode.IsNumber(ch),\n\t\t\tunicode.IsSpace(ch),\n\t\t\tunicode.IsPunct(ch):\n\t\t\tout += string(ch)\n\t\tdefault:\n\t\t\tpanic(errInvalidCharacterInMessage)\n\t\t}\n\t}\n\treturn out\n}\n\nfunc Render(maxID string) string {\n\tvar bld strings.Builder\n\n\tbld.WriteString(\"# Guestbook 📝\\n\\n[Come sign the guestbook!](./guestbook$help\u0026func=Sign)\\n\\n---\\n\\n\")\n\n\tvar maxIDBinary string\n\tif maxID != \"\" {\n\t\tmid, err := seqid.FromString(maxID)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\t// AVL iteration is exclusive, so we need to decrease the ID value to get the \"true\" maximum.\n\t\tmid--\n\t\tmaxIDBinary = mid.Binary()\n\t}\n\n\tvar lastID seqid.ID\n\tvar printed int\n\tguestbook.ReverseIterate(\"\", maxIDBinary, func(key string, val interface{}) bool {\n\t\tsig := val.(Signature)\n\t\tmessage := strings.ReplaceAll(sig.Message, \"\\n\", \"\\n\u003e \")\n\t\tbld.WriteString(\"\u003e \" + message + \"\\n\u003e\\n\")\n\t\tidValue, ok := seqid.FromBinary(key)\n\t\tif !ok {\n\t\t\tpanic(\"invalid seqid id\")\n\t\t}\n\n\t\tbld.WriteString(\"\u003e _Written by \" + sig.Author.String() + \" at \" + sig.Time.Format(time.DateTime) + \"_ (#\" + idValue.String() + \")\\n\\n---\\n\\n\")\n\t\tlastID = idValue\n\n\t\tprinted++\n\t\t// stop after exceeding limit\n\t\treturn printed \u003e= maxPerPage\n\t})\n\n\tif printed == 0 {\n\t\tbld.WriteString(\"No messages!\")\n\t} else if printed \u003e= maxPerPage {\n\t\tbld.WriteString(\"\u003cp style='text-align:right'\u003e\u003ca href='./guestbook:\" + lastID.String() + \"'\u003eNext page\u003c/a\u003e\u003c/p\u003e\")\n\t}\n\n\treturn bld.String()\n}\n"},{"name":"guestbook_test.gno","body":"package guestbook\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n)\n\nfunc TestSign(t *testing.T) {\n\tguestbook = avl.Tree{}\n\thasSigned = avl.Tree{}\n\n\tstd.TestSetRealm(std.NewUserRealm(\"g1user\"))\n\tSign(\"Hello!\")\n\n\tstd.TestSetRealm(std.NewUserRealm(\"g1user2\"))\n\tSign(\"Hello2!\")\n\n\tres := Render(\"\")\n\tt.Log(res)\n\tif !strings.Contains(res, \"\u003e Hello!\\n\u003e\\n\u003e _Written by g1user \") {\n\t\tt.Error(\"does not contain first user's message\")\n\t}\n\tif !strings.Contains(res, \"\u003e Hello2!\\n\u003e\\n\u003e _Written by g1user2 \") {\n\t\tt.Error(\"does not contain second user's message\")\n\t}\n\tif guestbook.Size() != 2 {\n\t\tt.Error(\"invalid guestbook size\")\n\t}\n}\n\nfunc TestSign_FromRealm(t *testing.T) {\n\tstd.TestSetRealm(std.NewCodeRealm(\"gno.land/r/demo/users\"))\n\n\tdefer func() {\n\t\trec := recover()\n\t\tif rec == nil {\n\t\t\tt.Fatal(\"expected panic\")\n\t\t}\n\t\trecString, ok := rec.(string)\n\t\tif !ok {\n\t\t\tt.Fatal(\"not a string\", rec)\n\t\t} else if recString != errNotAUser {\n\t\t\tt.Fatal(\"invalid error\", recString)\n\t\t}\n\t}()\n\tSign(\"Hey!\")\n}\n\nfunc TestSign_Double(t *testing.T) {\n\t// Should not allow signing twice.\n\tguestbook = avl.Tree{}\n\thasSigned = avl.Tree{}\n\n\tstd.TestSetRealm(std.NewUserRealm(\"g1user\"))\n\tSign(\"Hello!\")\n\n\tdefer func() {\n\t\trec := recover()\n\t\tif rec == nil {\n\t\t\tt.Fatal(\"expected panic\")\n\t\t}\n\t\trecString, ok := rec.(string)\n\t\tif !ok {\n\t\t\tt.Error(\"type assertion failed\", rec)\n\t\t} else if recString != errAlreadySigned {\n\t\t\tt.Error(\"invalid error message\", recString)\n\t\t}\n\t}()\n\n\tSign(\"Hello again!\")\n}\n\nfunc TestSign_InvalidMessage(t *testing.T) {\n\t// Should not allow control characters in message.\n\tguestbook = avl.Tree{}\n\thasSigned = avl.Tree{}\n\n\tstd.TestSetRealm(std.NewUserRealm(\"g1user\"))\n\n\tdefer func() {\n\t\trec := recover()\n\t\tif rec == nil {\n\t\t\tt.Fatal(\"expected panic\")\n\t\t}\n\t\trecString, ok := rec.(string)\n\t\tif !ok {\n\t\t\tt.Error(\"type assertion failed\", rec)\n\t\t} else if recString != errInvalidCharacterInMessage {\n\t\t\tt.Error(\"invalid error message\", recString)\n\t\t}\n\t}()\n\tSign(\"\\x00Hello!\")\n}\n\nfunc TestAdminDelete(t *testing.T) {\n\tconst (\n\t\tuserAddr std.Address = \"g1user\"\n\t\tadminAddr std.Address = \"g1admin\"\n\t)\n\n\tguestbook = avl.Tree{}\n\thasSigned = avl.Tree{}\n\towner = ownable.NewWithAddress(adminAddr)\n\tsignatureID = 0\n\n\tstd.TestSetRealm(std.NewUserRealm(userAddr))\n\n\tconst bad = \"Very Bad Message! Nyeh heh heh!\"\n\tSign(bad)\n\n\tif rnd := Render(\"\"); !strings.Contains(rnd, bad) {\n\t\tt.Fatal(\"render does not contain bad message\", rnd)\n\t}\n\n\tstd.TestSetRealm(std.NewUserRealm(adminAddr))\n\tAdminDelete(signatureID.String())\n\n\tif rnd := Render(\"\"); strings.Contains(rnd, bad) {\n\t\tt.Error(\"render contains bad message\", rnd)\n\t}\n\tif guestbook.Size() != 0 {\n\t\tt.Error(\"invalid guestbook size\")\n\t}\n\tif hasSigned.Size() != 1 {\n\t\tt.Error(\"invalid hasSigned size\")\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":"home","path":"gno.land/r/morgan/home","files":[{"name":"home.gno","body":"package home\n\nconst staticHome = `# morgan's (gn)home\n\n- [📝 sign my guestbook](/r/morgan/guestbook)\n`\n\nfunc Render(path string) string {\n\treturn staticHome\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":"registry","path":"gno.land/r/stefann/registry","files":[{"name":"registry.gno","body":"package registry\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/ownable\"\n)\n\nvar (\n\tmainAddr std.Address\n\tbackupAddr std.Address\n\towner *ownable.Ownable\n)\n\nfunc init() {\n\tmainAddr = \"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\"\n\tbackupAddr = \"g13awn2575t8s2vf3svlprc4dg0e9z5wchejdxk8\"\n\n\towner = ownable.NewWithAddress(mainAddr)\n}\n\nfunc MainAddr() std.Address {\n\treturn mainAddr\n}\n\nfunc BackupAddr() std.Address {\n\treturn backupAddr\n}\n\nfunc SetMainAddr(addr std.Address) error {\n\tif !addr.IsValid() {\n\t\treturn errors.New(\"config: invalid address\")\n\t}\n\n\towner.AssertCallerIsOwner()\n\n\tmainAddr = addr\n\treturn nil\n}\n\nfunc SetBackupAddr(addr std.Address) error {\n\tif !addr.IsValid() {\n\t\treturn errors.New(\"config: invalid address\")\n\t}\n\n\towner.AssertCallerIsOwner()\n\n\tbackupAddr = addr\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_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"home","path":"gno.land/r/stefann/home","files":[{"name":"home.gno","body":"package home\n\nimport (\n\t\"sort\"\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/ufmt\"\n\n\t\"gno.land/r/stefann/registry\"\n)\n\ntype City struct {\n\tName string\n\tURL string\n}\n\ntype Sponsor struct {\n\tAddress std.Address\n\tAmount std.Coins\n}\n\ntype Profile struct {\n\tpfp string\n\taboutMe []string\n}\n\ntype Travel struct {\n\tcities []City\n\tcurrentCityIndex int\n\tjarLink string\n}\n\ntype Sponsorship struct {\n\tmaxSponsors int\n\tsponsors *avl.Tree\n\tDonationsCount int\n\tsponsorsCount int\n}\n\nvar (\n\tprofile Profile\n\ttravel Travel\n\tsponsorship Sponsorship\n\towner *ownable.Ownable\n)\n\nfunc init() {\n\towner = ownable.NewWithAddress(registry.MainAddr())\n\n\tprofile = Profile{\n\t\tpfp: \"https://i.ibb.co/Bc5YNCx/DSC-0095a.jpg\",\n\t\taboutMe: []string{\n\t\t\t`### About Me`,\n\t\t\t`Hey there! I’m Stefan, a student of Computer Science. I’m all about exploring and adventure — whether it’s diving into the latest tech or discovering a new city, I’m always up for the challenge!`,\n\n\t\t\t`### Contributions`,\n\t\t\t`I'm just getting started, but you can follow my journey through gno.land right [here](https://github.com/gnolang/hackerspace/issues/94) 🔗`,\n\t\t},\n\t}\n\n\ttravel = Travel{\n\t\tcities: []City{\n\t\t\t{Name: \"Venice\", URL: \"https://i.ibb.co/1mcZ7b1/venice.jpg\"},\n\t\t\t{Name: \"Tokyo\", URL: \"https://i.ibb.co/wNDJv3H/tokyo.jpg\"},\n\t\t\t{Name: \"São Paulo\", URL: \"https://i.ibb.co/yWMq2Sn/sao-paulo.jpg\"},\n\t\t\t{Name: \"Toronto\", URL: \"https://i.ibb.co/pb95HJB/toronto.jpg\"},\n\t\t\t{Name: \"Bangkok\", URL: \"https://i.ibb.co/pQy3w2g/bangkok.jpg\"},\n\t\t\t{Name: \"New York\", URL: \"https://i.ibb.co/6JWLm0h/new-york.jpg\"},\n\t\t\t{Name: \"Paris\", URL: \"https://i.ibb.co/q9vf6Hs/paris.jpg\"},\n\t\t\t{Name: \"Kandersteg\", URL: \"https://i.ibb.co/60DzywD/kandersteg.jpg\"},\n\t\t\t{Name: \"Rothenburg\", URL: \"https://i.ibb.co/cr8d2rQ/rothenburg.jpg\"},\n\t\t\t{Name: \"Capetown\", URL: \"https://i.ibb.co/bPGn0v3/capetown.jpg\"},\n\t\t\t{Name: \"Sydney\", URL: \"https://i.ibb.co/TBNzqfy/sydney.jpg\"},\n\t\t\t{Name: \"Oeschinen Lake\", URL: \"https://i.ibb.co/QJQwp2y/oeschinen-lake.jpg\"},\n\t\t\t{Name: \"Barra Grande\", URL: \"https://i.ibb.co/z4RXKc1/barra-grande.jpg\"},\n\t\t\t{Name: \"London\", URL: \"https://i.ibb.co/CPGtvgr/london.jpg\"},\n\t\t},\n\t\tcurrentCityIndex: 0,\n\t\tjarLink: \"https://TODO\", // This value should be injected through UpdateJarLink after deployment.\n\t}\n\n\tsponsorship = Sponsorship{\n\t\tmaxSponsors: 5,\n\t\tsponsors: avl.NewTree(),\n\t\tDonationsCount: 0,\n\t\tsponsorsCount: 0,\n\t}\n}\n\nfunc UpdateCities(newCities []City) {\n\towner.AssertCallerIsOwner()\n\ttravel.cities = newCities\n}\n\nfunc AddCities(newCities ...City) {\n\towner.AssertCallerIsOwner()\n\n\ttravel.cities = append(travel.cities, newCities...)\n}\n\nfunc UpdateJarLink(newLink string) {\n\towner.AssertCallerIsOwner()\n\ttravel.jarLink = newLink\n}\n\nfunc UpdatePFP(url string) {\n\towner.AssertCallerIsOwner()\n\tprofile.pfp = url\n}\n\nfunc UpdateAboutMe(aboutMeStr string) {\n\towner.AssertCallerIsOwner()\n\tprofile.aboutMe = strings.Split(aboutMeStr, \"|\")\n}\n\nfunc AddAboutMeRows(newRows ...string) {\n\towner.AssertCallerIsOwner()\n\n\tprofile.aboutMe = append(profile.aboutMe, newRows...)\n}\n\nfunc UpdateMaxSponsors(newMax int) {\n\towner.AssertCallerIsOwner()\n\tif newMax \u003c= 0 {\n\t\tpanic(\"maxSponsors must be greater than zero\")\n\t}\n\tsponsorship.maxSponsors = newMax\n}\n\nfunc Donate() {\n\taddress := std.OriginCaller()\n\tamount := std.OriginSend()\n\n\tif amount.AmountOf(\"ugnot\") == 0 {\n\t\tpanic(\"Donation must include GNOT\")\n\t}\n\n\texistingAmount, exists := sponsorship.sponsors.Get(address.String())\n\tif exists {\n\t\tupdatedAmount := existingAmount.(std.Coins).Add(amount)\n\t\tsponsorship.sponsors.Set(address.String(), updatedAmount)\n\t} else {\n\t\tsponsorship.sponsors.Set(address.String(), amount)\n\t\tsponsorship.sponsorsCount++\n\t}\n\n\ttravel.currentCityIndex++\n\tsponsorship.DonationsCount++\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\townerAddr := registry.MainAddr()\n\tbanker.SendCoins(std.CurrentRealm().Addr(), ownerAddr, banker.GetCoins(std.CurrentRealm().Addr()))\n}\n\ntype SponsorSlice []Sponsor\n\nfunc (s SponsorSlice) Len() int {\n\treturn len(s)\n}\n\nfunc (s SponsorSlice) Less(i, j int) bool {\n\treturn s[i].Amount.AmountOf(\"ugnot\") \u003e s[j].Amount.AmountOf(\"ugnot\")\n}\n\nfunc (s SponsorSlice) Swap(i, j int) {\n\ts[i], s[j] = s[j], s[i]\n}\n\nfunc GetTopSponsors() []Sponsor {\n\tvar sponsorSlice SponsorSlice\n\n\tsponsorship.sponsors.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\taddr := std.Address(key)\n\t\tamount := value.(std.Coins)\n\t\tsponsorSlice = append(sponsorSlice, Sponsor{Address: addr, Amount: amount})\n\t\treturn false\n\t})\n\n\tsort.Sort(sponsorSlice)\n\treturn sponsorSlice\n}\n\nfunc GetTotalDonations() int {\n\ttotal := 0\n\tsponsorship.sponsors.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\ttotal += int(value.(std.Coins).AmountOf(\"ugnot\"))\n\t\treturn false\n\t})\n\treturn total\n}\n\nfunc Render(path string) string {\n\tout := ufmt.Sprintf(\"# Exploring %s!\\n\\n\", travel.cities[travel.currentCityIndex].Name)\n\n\tout += renderAboutMe()\n\tout += \"\\n\\n\"\n\tout += renderTips()\n\n\treturn out\n}\n\nfunc renderAboutMe() string {\n\tout := \"\u003cdiv class='rows-3'\u003e\"\n\n\tout += \"\u003cdiv style='position: relative; text-align: center;'\u003e\\n\\n\"\n\n\tout += ufmt.Sprintf(\"\u003cdiv style='background-image: url(%s); background-size: cover; background-position: center; width: 100%%; height: 600px; position: relative; border-radius: 15px; overflow: hidden;'\u003e\\n\\n\", travel.cities[travel.currentCityIndex%len(travel.cities)].URL)\n\n\tout += ufmt.Sprintf(\"\u003cimg src='%s' alt='my profile pic' style='width: 250px; height: auto; aspect-ratio: 1 / 1; object-fit: cover; border-radius: 50%%; border: 3px solid #1e1e1e; position: absolute; top: 75%%; left: 50%%; transform: translate(-50%%, -50%%);'\u003e\\n\\n\", profile.pfp)\n\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\tfor _, rows := range profile.aboutMe {\n\t\tout += \"\u003cdiv\u003e\\n\\n\"\n\t\tout += rows + \"\\n\\n\"\n\t\tout += \"\u003c/div\u003e\\n\\n\"\n\t}\n\n\tout += \"\u003c/div\u003e\u003c!-- /rows-3 --\u003e\\n\\n\"\n\n\treturn out\n}\n\nfunc renderTips() string {\n\tout := `\u003cdiv class=\"jumbotron\" style=\"display: flex; flex-direction: column; justify-content: flex-start; align-items: center; padding-top: 40px; padding-bottom: 50px; text-align: center;\"\u003e` + \"\\n\\n\"\n\n\tout += `\u003cdiv class=\"rows-2\" style=\"max-width: 500px; width: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center;\"\u003e` + \"\\n\"\n\n\tout += `\u003ch1 style=\"margin-bottom: 50px;\"\u003eHelp Me Travel The World\u003c/h1\u003e` + \"\\n\\n\"\n\n\tout += renderTipsJar() + \"\\n\"\n\n\tout += ufmt.Sprintf(`\u003cstrong style=\"font-size: 1.2em;\"\u003eI am currently in %s, \u003cbr\u003e tip the jar to send me somewhere else!\u003c/strong\u003e`, travel.cities[travel.currentCityIndex].Name)\n\n\tout += `\u003cbr\u003e\u003cspan style=\"font-size: 1.2em; font-style: italic; margin-top: 10px; display: inline-block;\"\u003eClick the jar, tip in GNOT coins, and watch my background change as I head to a new adventure!\u003c/span\u003e\u003c/p\u003e` + \"\\n\\n\"\n\n\tout += renderSponsors()\n\n\tout += `\u003c/div\u003e\u003c!-- /rows-2 --\u003e` + \"\\n\\n\"\n\n\tout += `\u003c/div\u003e\u003c!-- /jumbotron --\u003e` + \"\\n\"\n\n\treturn out\n}\n\nfunc formatAddress(address string) string {\n\tif len(address) \u003c= 8 {\n\t\treturn address\n\t}\n\treturn address[:4] + \"...\" + address[len(address)-4:]\n}\n\nfunc renderSponsors() string {\n\tout := `\u003ch3 style=\"margin-top: 5px; margin-bottom: 20px\"\u003eSponsor Leaderboard\u003c/h3\u003e` + \"\\n\"\n\n\tif sponsorship.sponsorsCount == 0 {\n\t\treturn out + `\u003cp style=\"text-align: center;\"\u003eNo sponsors yet. Be the first to tip the jar!\u003c/p\u003e` + \"\\n\"\n\t}\n\n\ttopSponsors := GetTopSponsors()\n\tnumSponsors := len(topSponsors)\n\tif numSponsors \u003e sponsorship.maxSponsors {\n\t\tnumSponsors = sponsorship.maxSponsors\n\t}\n\n\tout += `\u003cul style=\"list-style-type: none; padding: 0; border: 1px solid #ddd; border-radius: 8px; width: 100%; max-width: 300px; margin: 0 auto;\"\u003e` + \"\\n\"\n\n\tfor i := 0; i \u003c numSponsors; i++ {\n\t\tsponsor := topSponsors[i]\n\t\tisLastItem := (i == numSponsors-1)\n\n\t\tpadding := \"10px 5px\"\n\t\tborder := \"border-bottom: 1px solid #ddd;\"\n\n\t\tif isLastItem {\n\t\t\tpadding = \"8px 5px\"\n\t\t\tborder = \"\"\n\t\t}\n\n\t\tout += ufmt.Sprintf(\n\t\t\t`\u003cli style=\"padding: %s; %s text-align: left;\"\u003e\n\t\t\t\t\u003cstrong style=\"padding-left: 5px;\"\u003e%d. %s\u003c/strong\u003e\n\t\t\t\t\u003cspan style=\"float: right; padding-right: 5px;\"\u003e%s\u003c/span\u003e\n\t\t\t\u003c/li\u003e`,\n\t\t\tpadding, border, i+1, formatAddress(sponsor.Address.String()), sponsor.Amount.String(),\n\t\t)\n\t}\n\n\treturn out\n}\n\nfunc renderTipsJar() string {\n\tout := ufmt.Sprintf(`\u003ca href=\"%s\" target=\"_blank\" style=\"display: block; text-decoration: none;\"\u003e`, travel.jarLink) + \"\\n\"\n\n\tout += `\u003cimg src=\"https://i.ibb.co/4TH9zbw/tips-jar.png\" alt=\"Tips Jar\" style=\"width: 300px; height: auto; display: block; margin: 0 auto;\"\u003e` + \"\\n\"\n\n\tout += `\u003c/a\u003e` + \"\\n\"\n\n\treturn out\n}\n"},{"name":"home_test.gno","body":"package home\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/testutils\"\n)\n\nfunc TestUpdatePFP(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOriginCaller(owner)\n\n\tprofile.pfp = \"\"\n\n\tUpdatePFP(\"https://example.com/pic.png\")\n\n\tif profile.pfp != \"https://example.com/pic.png\" {\n\t\tt.Fatalf(\"expected pfp to be https://example.com/pic.png, got %s\", profile.pfp)\n\t}\n}\n\nfunc TestUpdateAboutMe(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOriginCaller(owner)\n\n\tprofile.aboutMe = []string{}\n\n\tUpdateAboutMe(\"This is my new bio.|I love coding!\")\n\n\texpected := []string{\"This is my new bio.\", \"I love coding!\"}\n\n\tif len(profile.aboutMe) != len(expected) {\n\t\tt.Fatalf(\"expected aboutMe to have length %d, got %d\", len(expected), len(profile.aboutMe))\n\t}\n\n\tfor i := range profile.aboutMe {\n\t\tif profile.aboutMe[i] != expected[i] {\n\t\t\tt.Fatalf(\"expected aboutMe[%d] to be %s, got %s\", i, expected[i], profile.aboutMe[i])\n\t\t}\n\t}\n}\n\nfunc TestUpdateCities(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOriginCaller(owner)\n\n\ttravel.cities = []City{}\n\n\tnewCities := []City{\n\t\t{Name: \"Berlin\", URL: \"https://example.com/berlin.jpg\"},\n\t\t{Name: \"Vienna\", URL: \"https://example.com/vienna.jpg\"},\n\t}\n\n\tUpdateCities(newCities)\n\n\tif len(travel.cities) != 2 {\n\t\tt.Fatalf(\"expected 2 cities, got %d\", len(travel.cities))\n\t}\n\n\tif travel.cities[0].Name != \"Berlin\" || travel.cities[1].Name != \"Vienna\" {\n\t\tt.Fatalf(\"expected cities to be updated to Berlin and Vienna, got %+v\", travel.cities)\n\t}\n}\n\nfunc TestUpdateJarLink(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOriginCaller(owner)\n\n\ttravel.jarLink = \"\"\n\n\tUpdateJarLink(\"https://example.com/jar\")\n\n\tif travel.jarLink != \"https://example.com/jar\" {\n\t\tt.Fatalf(\"expected jarLink to be https://example.com/jar, got %s\", travel.jarLink)\n\t}\n}\n\nfunc TestUpdateMaxSponsors(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOriginCaller(owner)\n\n\tsponsorship.maxSponsors = 0\n\n\tUpdateMaxSponsors(10)\n\n\tif sponsorship.maxSponsors != 10 {\n\t\tt.Fatalf(\"expected maxSponsors to be 10, got %d\", sponsorship.maxSponsors)\n\t}\n\n\tdefer func() {\n\t\tif r := recover(); r == nil {\n\t\t\tt.Fatalf(\"expected panic for setting maxSponsors to 0\")\n\t\t}\n\t}()\n\tUpdateMaxSponsors(0)\n}\n\nfunc TestAddCities(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOriginCaller(owner)\n\n\ttravel.cities = []City{}\n\n\tAddCities(City{Name: \"Berlin\", URL: \"https://example.com/berlin.jpg\"})\n\n\tif len(travel.cities) != 1 {\n\t\tt.Fatalf(\"expected 1 city, got %d\", len(travel.cities))\n\t}\n\tif travel.cities[0].Name != \"Berlin\" || travel.cities[0].URL != \"https://example.com/berlin.jpg\" {\n\t\tt.Fatalf(\"expected city to be Berlin, got %+v\", travel.cities[0])\n\t}\n\n\tAddCities(\n\t\tCity{Name: \"Paris\", URL: \"https://example.com/paris.jpg\"},\n\t\tCity{Name: \"Tokyo\", URL: \"https://example.com/tokyo.jpg\"},\n\t)\n\n\tif len(travel.cities) != 3 {\n\t\tt.Fatalf(\"expected 3 cities, got %d\", len(travel.cities))\n\t}\n\tif travel.cities[1].Name != \"Paris\" || travel.cities[2].Name != \"Tokyo\" {\n\t\tt.Fatalf(\"expected cities to be Paris and Tokyo, got %+v\", travel.cities[1:])\n\t}\n}\n\nfunc TestAddAboutMeRows(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOriginCaller(owner)\n\n\tprofile.aboutMe = []string{}\n\n\tAddAboutMeRows(\"I love exploring new technologies!\")\n\n\tif len(profile.aboutMe) != 1 {\n\t\tt.Fatalf(\"expected 1 aboutMe row, got %d\", len(profile.aboutMe))\n\t}\n\tif profile.aboutMe[0] != \"I love exploring new technologies!\" {\n\t\tt.Fatalf(\"expected first aboutMe row to be 'I love exploring new technologies!', got %s\", profile.aboutMe[0])\n\t}\n\n\tAddAboutMeRows(\"Travel is my passion!\", \"Always learning.\")\n\n\tif len(profile.aboutMe) != 3 {\n\t\tt.Fatalf(\"expected 3 aboutMe rows, got %d\", len(profile.aboutMe))\n\t}\n\tif profile.aboutMe[1] != \"Travel is my passion!\" || profile.aboutMe[2] != \"Always learning.\" {\n\t\tt.Fatalf(\"expected aboutMe rows to be 'Travel is my passion!' and 'Always learning.', got %+v\", profile.aboutMe[1:])\n\t}\n}\n\nfunc TestDonate(t *testing.T) {\n\tvar user = testutils.TestAddress(\"user\")\n\tstd.TestSetOriginCaller(user)\n\n\tsponsorship.sponsors = avl.NewTree()\n\tsponsorship.DonationsCount = 0\n\tsponsorship.sponsorsCount = 0\n\ttravel.currentCityIndex = 0\n\n\tcoinsSent := std.NewCoins(std.NewCoin(\"ugnot\", 500))\n\tstd.TestSetOriginSend(coinsSent, std.NewCoins())\n\tDonate()\n\n\texistingAmount, exists := sponsorship.sponsors.Get(string(user))\n\tif !exists {\n\t\tt.Fatalf(\"expected sponsor to be added, but it was not found\")\n\t}\n\n\tif existingAmount.(std.Coins).AmountOf(\"ugnot\") != 500 {\n\t\tt.Fatalf(\"expected donation amount to be 500ugnot, got %d\", existingAmount.(std.Coins).AmountOf(\"ugnot\"))\n\t}\n\n\tif sponsorship.DonationsCount != 1 {\n\t\tt.Fatalf(\"expected DonationsCount to be 1, got %d\", sponsorship.DonationsCount)\n\t}\n\n\tif sponsorship.sponsorsCount != 1 {\n\t\tt.Fatalf(\"expected sponsorsCount to be 1, got %d\", sponsorship.sponsorsCount)\n\t}\n\n\tif travel.currentCityIndex != 1 {\n\t\tt.Fatalf(\"expected currentCityIndex to be 1, got %d\", travel.currentCityIndex)\n\t}\n\n\tcoinsSent = std.NewCoins(std.NewCoin(\"ugnot\", 300))\n\tstd.TestSetOriginSend(coinsSent, std.NewCoins())\n\tDonate()\n\n\texistingAmount, exists = sponsorship.sponsors.Get(string(user))\n\tif !exists {\n\t\tt.Fatalf(\"expected sponsor to exist after second donation, but it was not found\")\n\t}\n\n\tif existingAmount.(std.Coins).AmountOf(\"ugnot\") != 800 {\n\t\tt.Fatalf(\"expected total donation amount to be 800ugnot, got %d\", existingAmount.(std.Coins).AmountOf(\"ugnot\"))\n\t}\n\n\tif sponsorship.DonationsCount != 2 {\n\t\tt.Fatalf(\"expected DonationsCount to be 2 after second donation, got %d\", sponsorship.DonationsCount)\n\t}\n\n\tif travel.currentCityIndex != 2 {\n\t\tt.Fatalf(\"expected currentCityIndex to be 2 after second donation, got %d\", travel.currentCityIndex)\n\t}\n}\n\nfunc TestGetTopSponsors(t *testing.T) {\n\tvar user = testutils.TestAddress(\"user\")\n\tstd.TestSetOriginCaller(user)\n\n\tsponsorship.sponsors = avl.NewTree()\n\tsponsorship.sponsorsCount = 0\n\n\tsponsorship.sponsors.Set(\"g1address1\", std.NewCoins(std.NewCoin(\"ugnot\", 300)))\n\tsponsorship.sponsors.Set(\"g1address2\", std.NewCoins(std.NewCoin(\"ugnot\", 500)))\n\tsponsorship.sponsors.Set(\"g1address3\", std.NewCoins(std.NewCoin(\"ugnot\", 200)))\n\tsponsorship.sponsorsCount = 3\n\n\ttopSponsors := GetTopSponsors()\n\n\tif len(topSponsors) != 3 {\n\t\tt.Fatalf(\"expected 3 sponsors, got %d\", len(topSponsors))\n\t}\n\n\tif topSponsors[0].Address.String() != \"g1address2\" || topSponsors[0].Amount.AmountOf(\"ugnot\") != 500 {\n\t\tt.Fatalf(\"expected top sponsor to be g1address2 with 500ugnot, got %s with %dugnot\", topSponsors[0].Address.String(), topSponsors[0].Amount.AmountOf(\"ugnot\"))\n\t}\n\n\tif topSponsors[1].Address.String() != \"g1address1\" || topSponsors[1].Amount.AmountOf(\"ugnot\") != 300 {\n\t\tt.Fatalf(\"expected second sponsor to be g1address1 with 300ugnot, got %s with %dugnot\", topSponsors[1].Address.String(), topSponsors[1].Amount.AmountOf(\"ugnot\"))\n\t}\n\n\tif topSponsors[2].Address.String() != \"g1address3\" || topSponsors[2].Amount.AmountOf(\"ugnot\") != 200 {\n\t\tt.Fatalf(\"expected third sponsor to be g1address3 with 200ugnot, got %s with %dugnot\", topSponsors[2].Address.String(), topSponsors[2].Amount.AmountOf(\"ugnot\"))\n\t}\n}\n\nfunc TestGetTotalDonations(t *testing.T) {\n\tvar user = testutils.TestAddress(\"user\")\n\tstd.TestSetOriginCaller(user)\n\n\tsponsorship.sponsors = avl.NewTree()\n\tsponsorship.sponsorsCount = 0\n\n\tsponsorship.sponsors.Set(\"g1address1\", std.NewCoins(std.NewCoin(\"ugnot\", 300)))\n\tsponsorship.sponsors.Set(\"g1address2\", std.NewCoins(std.NewCoin(\"ugnot\", 500)))\n\tsponsorship.sponsors.Set(\"g1address3\", std.NewCoins(std.NewCoin(\"ugnot\", 200)))\n\tsponsorship.sponsorsCount = 3\n\n\ttotalDonations := GetTotalDonations()\n\n\tif totalDonations != 1000 {\n\t\tt.Fatalf(\"expected total donations to be 1000ugnot, got %dugnot\", totalDonations)\n\t}\n}\n\nfunc TestRender(t *testing.T) {\n\ttravel.currentCityIndex = 0\n\ttravel.cities = []City{\n\t\t{Name: \"Venice\", URL: \"https://example.com/venice.jpg\"},\n\t\t{Name: \"Paris\", URL: \"https://example.com/paris.jpg\"},\n\t}\n\n\toutput := Render(\"\")\n\n\texpectedCity := \"Venice\"\n\tif !strings.Contains(output, expectedCity) {\n\t\tt.Fatalf(\"expected output to contain city name '%s', got %s\", expectedCity, output)\n\t}\n\n\texpectedURL := \"https://example.com/venice.jpg\"\n\tif !strings.Contains(output, expectedURL) {\n\t\tt.Fatalf(\"expected output to contain city URL '%s', got %s\", expectedURL, output)\n\t}\n\n\ttravel.currentCityIndex = 1\n\toutput = Render(\"\")\n\n\texpectedCity = \"Paris\"\n\tif !strings.Contains(output, expectedCity) {\n\t\tt.Fatalf(\"expected output to contain city name '%s', got %s\", expectedCity, output)\n\t}\n\n\texpectedURL = \"https://example.com/paris.jpg\"\n\tif !strings.Contains(output, expectedURL) {\n\t\tt.Fatalf(\"expected output to contain city URL '%s', got %s\", expectedURL, output)\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":"home","path":"gno.land/r/stefann/home","files":[{"name":"home.gno","body":"package home\n\nimport (\n\t\"sort\"\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/ufmt\"\n\n\t\"gno.land/r/stefann/registry\"\n)\n\ntype City struct {\n\tName string\n\tURL string\n}\n\ntype Sponsor struct {\n\tAddress std.Address\n\tAmount std.Coins\n}\n\ntype Profile struct {\n\tpfp string\n\taboutMe []string\n}\n\ntype Travel struct {\n\tcities []City\n\tcurrentCityIndex int\n\tjarLink string\n}\n\ntype Sponsorship struct {\n\tmaxSponsors int\n\tsponsors *avl.Tree\n\tDonationsCount int\n\tsponsorsCount int\n}\n\nvar (\n\tprofile Profile\n\ttravel Travel\n\tsponsorship Sponsorship\n\towner *ownable.Ownable\n)\n\nfunc init() {\n\towner = ownable.NewWithAddress(registry.MainAddr())\n\n\tprofile = Profile{\n\t\tpfp: \"https://i.ibb.co/Bc5YNCx/DSC-0095a.jpg\",\n\t\taboutMe: []string{\n\t\t\t`### About Me`,\n\t\t\t`Hey there! I’m Stefan, a student of Computer Science. I’m all about exploring and adventure — whether it’s diving into the latest tech or discovering a new city, I’m always up for the challenge!`,\n\n\t\t\t`### Contributions`,\n\t\t\t`I'm just getting started, but you can follow my journey through gno.land right [here](https://github.com/gnolang/hackerspace/issues/94) 🔗`,\n\t\t},\n\t}\n\n\ttravel = Travel{\n\t\tcities: []City{\n\t\t\t{Name: \"Venice\", URL: \"https://i.ibb.co/1mcZ7b1/venice.jpg\"},\n\t\t\t{Name: \"Tokyo\", URL: \"https://i.ibb.co/wNDJv3H/tokyo.jpg\"},\n\t\t\t{Name: \"São Paulo\", URL: \"https://i.ibb.co/yWMq2Sn/sao-paulo.jpg\"},\n\t\t\t{Name: \"Toronto\", URL: \"https://i.ibb.co/pb95HJB/toronto.jpg\"},\n\t\t\t{Name: \"Bangkok\", URL: \"https://i.ibb.co/pQy3w2g/bangkok.jpg\"},\n\t\t\t{Name: \"New York\", URL: \"https://i.ibb.co/6JWLm0h/new-york.jpg\"},\n\t\t\t{Name: \"Paris\", URL: \"https://i.ibb.co/q9vf6Hs/paris.jpg\"},\n\t\t\t{Name: \"Kandersteg\", URL: \"https://i.ibb.co/60DzywD/kandersteg.jpg\"},\n\t\t\t{Name: \"Rothenburg\", URL: \"https://i.ibb.co/cr8d2rQ/rothenburg.jpg\"},\n\t\t\t{Name: \"Capetown\", URL: \"https://i.ibb.co/bPGn0v3/capetown.jpg\"},\n\t\t\t{Name: \"Sydney\", URL: \"https://i.ibb.co/TBNzqfy/sydney.jpg\"},\n\t\t\t{Name: \"Oeschinen Lake\", URL: \"https://i.ibb.co/QJQwp2y/oeschinen-lake.jpg\"},\n\t\t\t{Name: \"Barra Grande\", URL: \"https://i.ibb.co/z4RXKc1/barra-grande.jpg\"},\n\t\t\t{Name: \"London\", URL: \"https://i.ibb.co/CPGtvgr/london.jpg\"},\n\t\t},\n\t\tcurrentCityIndex: 0,\n\t\tjarLink: \"https://TODO\", // This value should be injected through UpdateJarLink after deployment.\n\t}\n\n\tsponsorship = Sponsorship{\n\t\tmaxSponsors: 5,\n\t\tsponsors: avl.NewTree(),\n\t\tDonationsCount: 0,\n\t\tsponsorsCount: 0,\n\t}\n}\n\nfunc UpdateCities(newCities []City) {\n\towner.AssertCallerIsOwner()\n\ttravel.cities = newCities\n}\n\nfunc AddCities(newCities ...City) {\n\towner.AssertCallerIsOwner()\n\n\ttravel.cities = append(travel.cities, newCities...)\n}\n\nfunc UpdateJarLink(newLink string) {\n\towner.AssertCallerIsOwner()\n\ttravel.jarLink = newLink\n}\n\nfunc UpdatePFP(url string) {\n\towner.AssertCallerIsOwner()\n\tprofile.pfp = url\n}\n\nfunc UpdateAboutMe(aboutMeStr string) {\n\towner.AssertCallerIsOwner()\n\tprofile.aboutMe = strings.Split(aboutMeStr, \"|\")\n}\n\nfunc AddAboutMeRows(newRows ...string) {\n\towner.AssertCallerIsOwner()\n\n\tprofile.aboutMe = append(profile.aboutMe, newRows...)\n}\n\nfunc UpdateMaxSponsors(newMax int) {\n\towner.AssertCallerIsOwner()\n\tif newMax \u003c= 0 {\n\t\tpanic(\"maxSponsors must be greater than zero\")\n\t}\n\tsponsorship.maxSponsors = newMax\n}\n\nfunc Donate() {\n\taddress := std.OriginCaller()\n\tamount := std.OriginSend()\n\n\tif amount.AmountOf(\"ugnot\") == 0 {\n\t\tpanic(\"Donation must include GNOT\")\n\t}\n\n\texistingAmount, exists := sponsorship.sponsors.Get(address.String())\n\tif exists {\n\t\tupdatedAmount := existingAmount.(std.Coins).Add(amount)\n\t\tsponsorship.sponsors.Set(address.String(), updatedAmount)\n\t} else {\n\t\tsponsorship.sponsors.Set(address.String(), amount)\n\t\tsponsorship.sponsorsCount++\n\t}\n\n\ttravel.currentCityIndex++\n\tsponsorship.DonationsCount++\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\townerAddr := registry.MainAddr()\n\tbanker.SendCoins(std.CurrentRealm().Address(), ownerAddr, banker.GetCoins(std.CurrentRealm().Address()))\n}\n\ntype SponsorSlice []Sponsor\n\nfunc (s SponsorSlice) Len() int {\n\treturn len(s)\n}\n\nfunc (s SponsorSlice) Less(i, j int) bool {\n\treturn s[i].Amount.AmountOf(\"ugnot\") \u003e s[j].Amount.AmountOf(\"ugnot\")\n}\n\nfunc (s SponsorSlice) Swap(i, j int) {\n\ts[i], s[j] = s[j], s[i]\n}\n\nfunc GetTopSponsors() []Sponsor {\n\tvar sponsorSlice SponsorSlice\n\n\tsponsorship.sponsors.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\taddr := std.Address(key)\n\t\tamount := value.(std.Coins)\n\t\tsponsorSlice = append(sponsorSlice, Sponsor{Address: addr, Amount: amount})\n\t\treturn false\n\t})\n\n\tsort.Sort(sponsorSlice)\n\treturn sponsorSlice\n}\n\nfunc GetTotalDonations() int {\n\ttotal := 0\n\tsponsorship.sponsors.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\ttotal += int(value.(std.Coins).AmountOf(\"ugnot\"))\n\t\treturn false\n\t})\n\treturn total\n}\n\nfunc Render(path string) string {\n\tout := ufmt.Sprintf(\"# Exploring %s!\\n\\n\", travel.cities[travel.currentCityIndex].Name)\n\n\tout += renderAboutMe()\n\tout += \"\\n\\n\"\n\tout += renderTips()\n\n\treturn out\n}\n\nfunc renderAboutMe() string {\n\tout := \"\u003cdiv class='rows-3'\u003e\"\n\n\tout += \"\u003cdiv style='position: relative; text-align: center;'\u003e\\n\\n\"\n\n\tout += ufmt.Sprintf(\"\u003cdiv style='background-image: url(%s); background-size: cover; background-position: center; width: 100%%; height: 600px; position: relative; border-radius: 15px; overflow: hidden;'\u003e\\n\\n\", travel.cities[travel.currentCityIndex%len(travel.cities)].URL)\n\n\tout += ufmt.Sprintf(\"\u003cimg src='%s' alt='my profile pic' style='width: 250px; height: auto; aspect-ratio: 1 / 1; object-fit: cover; border-radius: 50%%; border: 3px solid #1e1e1e; position: absolute; top: 75%%; left: 50%%; transform: translate(-50%%, -50%%);'\u003e\\n\\n\", profile.pfp)\n\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\tfor _, rows := range profile.aboutMe {\n\t\tout += \"\u003cdiv\u003e\\n\\n\"\n\t\tout += rows + \"\\n\\n\"\n\t\tout += \"\u003c/div\u003e\\n\\n\"\n\t}\n\n\tout += \"\u003c/div\u003e\u003c!-- /rows-3 --\u003e\\n\\n\"\n\n\treturn out\n}\n\nfunc renderTips() string {\n\tout := `\u003cdiv class=\"jumbotron\" style=\"display: flex; flex-direction: column; justify-content: flex-start; align-items: center; padding-top: 40px; padding-bottom: 50px; text-align: center;\"\u003e` + \"\\n\\n\"\n\n\tout += `\u003cdiv class=\"rows-2\" style=\"max-width: 500px; width: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center;\"\u003e` + \"\\n\"\n\n\tout += `\u003ch1 style=\"margin-bottom: 50px;\"\u003eHelp Me Travel The World\u003c/h1\u003e` + \"\\n\\n\"\n\n\tout += renderTipsJar() + \"\\n\"\n\n\tout += ufmt.Sprintf(`\u003cstrong style=\"font-size: 1.2em;\"\u003eI am currently in %s, \u003cbr\u003e tip the jar to send me somewhere else!\u003c/strong\u003e`, travel.cities[travel.currentCityIndex].Name)\n\n\tout += `\u003cbr\u003e\u003cspan style=\"font-size: 1.2em; font-style: italic; margin-top: 10px; display: inline-block;\"\u003eClick the jar, tip in GNOT coins, and watch my background change as I head to a new adventure!\u003c/span\u003e\u003c/p\u003e` + \"\\n\\n\"\n\n\tout += renderSponsors()\n\n\tout += `\u003c/div\u003e\u003c!-- /rows-2 --\u003e` + \"\\n\\n\"\n\n\tout += `\u003c/div\u003e\u003c!-- /jumbotron --\u003e` + \"\\n\"\n\n\treturn out\n}\n\nfunc formatAddress(address string) string {\n\tif len(address) \u003c= 8 {\n\t\treturn address\n\t}\n\treturn address[:4] + \"...\" + address[len(address)-4:]\n}\n\nfunc renderSponsors() string {\n\tout := `\u003ch3 style=\"margin-top: 5px; margin-bottom: 20px\"\u003eSponsor Leaderboard\u003c/h3\u003e` + \"\\n\"\n\n\tif sponsorship.sponsorsCount == 0 {\n\t\treturn out + `\u003cp style=\"text-align: center;\"\u003eNo sponsors yet. Be the first to tip the jar!\u003c/p\u003e` + \"\\n\"\n\t}\n\n\ttopSponsors := GetTopSponsors()\n\tnumSponsors := len(topSponsors)\n\tif numSponsors \u003e sponsorship.maxSponsors {\n\t\tnumSponsors = sponsorship.maxSponsors\n\t}\n\n\tout += `\u003cul style=\"list-style-type: none; padding: 0; border: 1px solid #ddd; border-radius: 8px; width: 100%; max-width: 300px; margin: 0 auto;\"\u003e` + \"\\n\"\n\n\tfor i := 0; i \u003c numSponsors; i++ {\n\t\tsponsor := topSponsors[i]\n\t\tisLastItem := (i == numSponsors-1)\n\n\t\tpadding := \"10px 5px\"\n\t\tborder := \"border-bottom: 1px solid #ddd;\"\n\n\t\tif isLastItem {\n\t\t\tpadding = \"8px 5px\"\n\t\t\tborder = \"\"\n\t\t}\n\n\t\tout += ufmt.Sprintf(\n\t\t\t`\u003cli style=\"padding: %s; %s text-align: left;\"\u003e\n\t\t\t\t\u003cstrong style=\"padding-left: 5px;\"\u003e%d. %s\u003c/strong\u003e\n\t\t\t\t\u003cspan style=\"float: right; padding-right: 5px;\"\u003e%s\u003c/span\u003e\n\t\t\t\u003c/li\u003e`,\n\t\t\tpadding, border, i+1, formatAddress(sponsor.Address.String()), sponsor.Amount.String(),\n\t\t)\n\t}\n\n\treturn out\n}\n\nfunc renderTipsJar() string {\n\tout := ufmt.Sprintf(`\u003ca href=\"%s\" target=\"_blank\" style=\"display: block; text-decoration: none;\"\u003e`, travel.jarLink) + \"\\n\"\n\n\tout += `\u003cimg src=\"https://i.ibb.co/4TH9zbw/tips-jar.png\" alt=\"Tips Jar\" style=\"width: 300px; height: auto; display: block; margin: 0 auto;\"\u003e` + \"\\n\"\n\n\tout += `\u003c/a\u003e` + \"\\n\"\n\n\treturn out\n}\n"},{"name":"home_test.gno","body":"package home\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/testutils\"\n)\n\nfunc TestUpdatePFP(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOriginCaller(owner)\n\n\tprofile.pfp = \"\"\n\n\tUpdatePFP(\"https://example.com/pic.png\")\n\n\tif profile.pfp != \"https://example.com/pic.png\" {\n\t\tt.Fatalf(\"expected pfp to be https://example.com/pic.png, got %s\", profile.pfp)\n\t}\n}\n\nfunc TestUpdateAboutMe(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOriginCaller(owner)\n\n\tprofile.aboutMe = []string{}\n\n\tUpdateAboutMe(\"This is my new bio.|I love coding!\")\n\n\texpected := []string{\"This is my new bio.\", \"I love coding!\"}\n\n\tif len(profile.aboutMe) != len(expected) {\n\t\tt.Fatalf(\"expected aboutMe to have length %d, got %d\", len(expected), len(profile.aboutMe))\n\t}\n\n\tfor i := range profile.aboutMe {\n\t\tif profile.aboutMe[i] != expected[i] {\n\t\t\tt.Fatalf(\"expected aboutMe[%d] to be %s, got %s\", i, expected[i], profile.aboutMe[i])\n\t\t}\n\t}\n}\n\nfunc TestUpdateCities(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOriginCaller(owner)\n\n\ttravel.cities = []City{}\n\n\tnewCities := []City{\n\t\t{Name: \"Berlin\", URL: \"https://example.com/berlin.jpg\"},\n\t\t{Name: \"Vienna\", URL: \"https://example.com/vienna.jpg\"},\n\t}\n\n\tUpdateCities(newCities)\n\n\tif len(travel.cities) != 2 {\n\t\tt.Fatalf(\"expected 2 cities, got %d\", len(travel.cities))\n\t}\n\n\tif travel.cities[0].Name != \"Berlin\" || travel.cities[1].Name != \"Vienna\" {\n\t\tt.Fatalf(\"expected cities to be updated to Berlin and Vienna, got %+v\", travel.cities)\n\t}\n}\n\nfunc TestUpdateJarLink(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOriginCaller(owner)\n\n\ttravel.jarLink = \"\"\n\n\tUpdateJarLink(\"https://example.com/jar\")\n\n\tif travel.jarLink != \"https://example.com/jar\" {\n\t\tt.Fatalf(\"expected jarLink to be https://example.com/jar, got %s\", travel.jarLink)\n\t}\n}\n\nfunc TestUpdateMaxSponsors(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOriginCaller(owner)\n\n\tsponsorship.maxSponsors = 0\n\n\tUpdateMaxSponsors(10)\n\n\tif sponsorship.maxSponsors != 10 {\n\t\tt.Fatalf(\"expected maxSponsors to be 10, got %d\", sponsorship.maxSponsors)\n\t}\n\n\tdefer func() {\n\t\tif r := recover(); r == nil {\n\t\t\tt.Fatalf(\"expected panic for setting maxSponsors to 0\")\n\t\t}\n\t}()\n\tUpdateMaxSponsors(0)\n}\n\nfunc TestAddCities(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOriginCaller(owner)\n\n\ttravel.cities = []City{}\n\n\tAddCities(City{Name: \"Berlin\", URL: \"https://example.com/berlin.jpg\"})\n\n\tif len(travel.cities) != 1 {\n\t\tt.Fatalf(\"expected 1 city, got %d\", len(travel.cities))\n\t}\n\tif travel.cities[0].Name != \"Berlin\" || travel.cities[0].URL != \"https://example.com/berlin.jpg\" {\n\t\tt.Fatalf(\"expected city to be Berlin, got %+v\", travel.cities[0])\n\t}\n\n\tAddCities(\n\t\tCity{Name: \"Paris\", URL: \"https://example.com/paris.jpg\"},\n\t\tCity{Name: \"Tokyo\", URL: \"https://example.com/tokyo.jpg\"},\n\t)\n\n\tif len(travel.cities) != 3 {\n\t\tt.Fatalf(\"expected 3 cities, got %d\", len(travel.cities))\n\t}\n\tif travel.cities[1].Name != \"Paris\" || travel.cities[2].Name != \"Tokyo\" {\n\t\tt.Fatalf(\"expected cities to be Paris and Tokyo, got %+v\", travel.cities[1:])\n\t}\n}\n\nfunc TestAddAboutMeRows(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOriginCaller(owner)\n\n\tprofile.aboutMe = []string{}\n\n\tAddAboutMeRows(\"I love exploring new technologies!\")\n\n\tif len(profile.aboutMe) != 1 {\n\t\tt.Fatalf(\"expected 1 aboutMe row, got %d\", len(profile.aboutMe))\n\t}\n\tif profile.aboutMe[0] != \"I love exploring new technologies!\" {\n\t\tt.Fatalf(\"expected first aboutMe row to be 'I love exploring new technologies!', got %s\", profile.aboutMe[0])\n\t}\n\n\tAddAboutMeRows(\"Travel is my passion!\", \"Always learning.\")\n\n\tif len(profile.aboutMe) != 3 {\n\t\tt.Fatalf(\"expected 3 aboutMe rows, got %d\", len(profile.aboutMe))\n\t}\n\tif profile.aboutMe[1] != \"Travel is my passion!\" || profile.aboutMe[2] != \"Always learning.\" {\n\t\tt.Fatalf(\"expected aboutMe rows to be 'Travel is my passion!' and 'Always learning.', got %+v\", profile.aboutMe[1:])\n\t}\n}\n\nfunc TestDonate(t *testing.T) {\n\tvar user = testutils.TestAddress(\"user\")\n\tstd.TestSetOriginCaller(user)\n\n\tsponsorship.sponsors = avl.NewTree()\n\tsponsorship.DonationsCount = 0\n\tsponsorship.sponsorsCount = 0\n\ttravel.currentCityIndex = 0\n\n\tcoinsSent := std.NewCoins(std.NewCoin(\"ugnot\", 500))\n\tstd.TestSetOriginSend(coinsSent, std.NewCoins())\n\tDonate()\n\n\texistingAmount, exists := sponsorship.sponsors.Get(string(user))\n\tif !exists {\n\t\tt.Fatalf(\"expected sponsor to be added, but it was not found\")\n\t}\n\n\tif existingAmount.(std.Coins).AmountOf(\"ugnot\") != 500 {\n\t\tt.Fatalf(\"expected donation amount to be 500ugnot, got %d\", existingAmount.(std.Coins).AmountOf(\"ugnot\"))\n\t}\n\n\tif sponsorship.DonationsCount != 1 {\n\t\tt.Fatalf(\"expected DonationsCount to be 1, got %d\", sponsorship.DonationsCount)\n\t}\n\n\tif sponsorship.sponsorsCount != 1 {\n\t\tt.Fatalf(\"expected sponsorsCount to be 1, got %d\", sponsorship.sponsorsCount)\n\t}\n\n\tif travel.currentCityIndex != 1 {\n\t\tt.Fatalf(\"expected currentCityIndex to be 1, got %d\", travel.currentCityIndex)\n\t}\n\n\tcoinsSent = std.NewCoins(std.NewCoin(\"ugnot\", 300))\n\tstd.TestSetOriginSend(coinsSent, std.NewCoins())\n\tDonate()\n\n\texistingAmount, exists = sponsorship.sponsors.Get(string(user))\n\tif !exists {\n\t\tt.Fatalf(\"expected sponsor to exist after second donation, but it was not found\")\n\t}\n\n\tif existingAmount.(std.Coins).AmountOf(\"ugnot\") != 800 {\n\t\tt.Fatalf(\"expected total donation amount to be 800ugnot, got %d\", existingAmount.(std.Coins).AmountOf(\"ugnot\"))\n\t}\n\n\tif sponsorship.DonationsCount != 2 {\n\t\tt.Fatalf(\"expected DonationsCount to be 2 after second donation, got %d\", sponsorship.DonationsCount)\n\t}\n\n\tif travel.currentCityIndex != 2 {\n\t\tt.Fatalf(\"expected currentCityIndex to be 2 after second donation, got %d\", travel.currentCityIndex)\n\t}\n}\n\nfunc TestGetTopSponsors(t *testing.T) {\n\tvar user = testutils.TestAddress(\"user\")\n\tstd.TestSetOriginCaller(user)\n\n\tsponsorship.sponsors = avl.NewTree()\n\tsponsorship.sponsorsCount = 0\n\n\tsponsorship.sponsors.Set(\"g1address1\", std.NewCoins(std.NewCoin(\"ugnot\", 300)))\n\tsponsorship.sponsors.Set(\"g1address2\", std.NewCoins(std.NewCoin(\"ugnot\", 500)))\n\tsponsorship.sponsors.Set(\"g1address3\", std.NewCoins(std.NewCoin(\"ugnot\", 200)))\n\tsponsorship.sponsorsCount = 3\n\n\ttopSponsors := GetTopSponsors()\n\n\tif len(topSponsors) != 3 {\n\t\tt.Fatalf(\"expected 3 sponsors, got %d\", len(topSponsors))\n\t}\n\n\tif topSponsors[0].Address.String() != \"g1address2\" || topSponsors[0].Amount.AmountOf(\"ugnot\") != 500 {\n\t\tt.Fatalf(\"expected top sponsor to be g1address2 with 500ugnot, got %s with %dugnot\", topSponsors[0].Address.String(), topSponsors[0].Amount.AmountOf(\"ugnot\"))\n\t}\n\n\tif topSponsors[1].Address.String() != \"g1address1\" || topSponsors[1].Amount.AmountOf(\"ugnot\") != 300 {\n\t\tt.Fatalf(\"expected second sponsor to be g1address1 with 300ugnot, got %s with %dugnot\", topSponsors[1].Address.String(), topSponsors[1].Amount.AmountOf(\"ugnot\"))\n\t}\n\n\tif topSponsors[2].Address.String() != \"g1address3\" || topSponsors[2].Amount.AmountOf(\"ugnot\") != 200 {\n\t\tt.Fatalf(\"expected third sponsor to be g1address3 with 200ugnot, got %s with %dugnot\", topSponsors[2].Address.String(), topSponsors[2].Amount.AmountOf(\"ugnot\"))\n\t}\n}\n\nfunc TestGetTotalDonations(t *testing.T) {\n\tvar user = testutils.TestAddress(\"user\")\n\tstd.TestSetOriginCaller(user)\n\n\tsponsorship.sponsors = avl.NewTree()\n\tsponsorship.sponsorsCount = 0\n\n\tsponsorship.sponsors.Set(\"g1address1\", std.NewCoins(std.NewCoin(\"ugnot\", 300)))\n\tsponsorship.sponsors.Set(\"g1address2\", std.NewCoins(std.NewCoin(\"ugnot\", 500)))\n\tsponsorship.sponsors.Set(\"g1address3\", std.NewCoins(std.NewCoin(\"ugnot\", 200)))\n\tsponsorship.sponsorsCount = 3\n\n\ttotalDonations := GetTotalDonations()\n\n\tif totalDonations != 1000 {\n\t\tt.Fatalf(\"expected total donations to be 1000ugnot, got %dugnot\", totalDonations)\n\t}\n}\n\nfunc TestRender(t *testing.T) {\n\ttravel.currentCityIndex = 0\n\ttravel.cities = []City{\n\t\t{Name: \"Venice\", URL: \"https://example.com/venice.jpg\"},\n\t\t{Name: \"Paris\", URL: \"https://example.com/paris.jpg\"},\n\t}\n\n\toutput := Render(\"\")\n\n\texpectedCity := \"Venice\"\n\tif !strings.Contains(output, expectedCity) {\n\t\tt.Fatalf(\"expected output to contain city name '%s', got %s\", expectedCity, output)\n\t}\n\n\texpectedURL := \"https://example.com/venice.jpg\"\n\tif !strings.Contains(output, expectedURL) {\n\t\tt.Fatalf(\"expected output to contain city URL '%s', got %s\", expectedURL, output)\n\t}\n\n\ttravel.currentCityIndex = 1\n\toutput = Render(\"\")\n\n\texpectedCity = \"Paris\"\n\tif !strings.Contains(output, expectedCity) {\n\t\tt.Fatalf(\"expected output to contain city name '%s', got %s\", expectedCity, output)\n\t}\n\n\texpectedURL = \"https://example.com/paris.jpg\"\n\tif !strings.Contains(output, expectedURL) {\n\t\tt.Fatalf(\"expected output to contain city URL '%s', got %s\", expectedURL, output)\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":"rewards","path":"gno.land/r/sys/rewards","files":[{"name":"rewards.gno","body":"// This package will be used to manage proof-of-contributions on the exposed smart-contract side.\npackage rewards\n\n// TODO: write specs.\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":"users","path":"gno.land/r/sys/users","files":[{"name":"verify.gno","body":"package users\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\" // @moul\n\ntype VerifyNameFunc func(enabled bool, address std.Address, name string) bool\n\nvar (\n\towner = ownable.NewWithAddress(admin) // Package owner\n\tcheckFunc = VerifyNameByUser // Checking namespace callback\n\tenabled = true // For now this package is disabled by default\n)\n\nfunc IsEnabled() bool { return enabled }\n\n// This method ensures that the given address has ownership of the given name.\nfunc IsAuthorizedAddressForName(address std.Address, name string) bool {\n\treturn checkFunc(enabled, address, name)\n}\n\n// VerifyNameByUser checks from the `users` package that the user has correctly\n// registered the given name.\n// This function considers as valid an `address` that matches the `name`.\nfunc VerifyNameByUser(enable bool, address std.Address, name string) bool {\n\tif !enable {\n\t\treturn true\n\t}\n\n\t// Allow user with their own address as name\n\tif address.String() == name {\n\t\treturn true\n\t}\n\n\tif user := users.GetUserByName(name); user != nil {\n\t\treturn user.Address == address\n\t}\n\n\treturn false\n}\n\n// Admin calls\n\n// Enable this package.\nfunc AdminEnable() {\n\tif err := owner.CallerIsOwner(); err != nil {\n\t\tpanic(err)\n\t}\n\n\tenabled = true\n}\n\n// Disable this package.\nfunc AdminDisable() {\n\tif err := owner.CallerIsOwner(); err != nil {\n\t\tpanic(err)\n\t}\n\n\tenabled = false\n}\n\n// AdminUpdateVerifyCall updates the method that verifies the namespace.\nfunc AdminUpdateVerifyCall(check VerifyNameFunc) {\n\tif err := owner.CallerIsOwner(); err != nil {\n\t\tpanic(err)\n\t}\n\n\tcheckFunc = check\n}\n\n// AdminTransferOwnership transfers the ownership to a new owner.\nfunc AdminTransferOwnership(newOwner std.Address) error {\n\tif err := owner.CallerIsOwner(); err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn owner.TransferOwnership(newOwner)\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}} From e2223ffa257c57671303123f2e278e379b6d0625 Mon Sep 17 00:00:00 2001 From: "hieu.ha" Date: Fri, 20 Dec 2024 13:33:44 +0700 Subject: [PATCH 08/14] refactor: refactor missing Addr => Address for Realm --- gnovm/stdlibs/std/frame.gno | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnovm/stdlibs/std/frame.gno b/gnovm/stdlibs/std/frame.gno index 1709f8cb8b5..bcffa458043 100644 --- a/gnovm/stdlibs/std/frame.gno +++ b/gnovm/stdlibs/std/frame.gno @@ -5,7 +5,7 @@ type Realm struct { pkgPath string } -func (r Realm) Addr() Address { +func (r Realm) Address() Address { return r.addr } From eb965f1cd10950ebd5a139cc78e0f4555e88b708 Mon Sep 17 00:00:00 2001 From: "hieu.ha" Date: Fri, 20 Dec 2024 15:26:35 +0700 Subject: [PATCH 09/14] fix: fix test crossrealm --- gnovm/tests/files/zrealm_crossrealm11.gno | 8 ++++---- gnovm/tests/files/zrealm_crossrealm13.gno | 12 ++++++------ gnovm/tests/files/zrealm_crossrealm13a.gno | 12 ++++++------ 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/gnovm/tests/files/zrealm_crossrealm11.gno b/gnovm/tests/files/zrealm_crossrealm11.gno index c565c1847cc..29295e99d10 100644 --- a/gnovm/tests/files/zrealm_crossrealm11.gno +++ b/gnovm/tests/files/zrealm_crossrealm11.gno @@ -117,10 +117,10 @@ func printColumns(left, right string) { // user1.gno -> r/crossrealm_test.main -> r/crossrealm_test.Exec = user1.gno // user1.gno -> r/crossrealm_test.main -> r/demo/tests.Exec = gno.land/r/demo/tests // user1.gno -> r/crossrealm_test.main -> p/demo/tests.Exec = user1.gno -// user1.gno -> r/crossrealm_test.main -> r/crossrealm_test.getPreviousRealm = user1.gno -// user1.gno -> r/crossrealm_test.main -> r/crossrealm_test.Exec -> r/crossrealm_test.getPreviousRealm = user1.gno -// user1.gno -> r/crossrealm_test.main -> r/demo/tests.Exec -> r/crossrealm_test.getPreviousRealm = gno.land/r/demo/tests -// user1.gno -> r/crossrealm_test.main -> p/demo/tests.Exec -> r/crossrealm_test.getPreviousRealm = user1.gno +// user1.gno -> r/crossrealm_test.main -> r/crossrealm_test.getPreviousRealm = user1.gno +// user1.gno -> r/crossrealm_test.main -> r/crossrealm_test.Exec -> r/crossrealm_test.getPreviousRealm = user1.gno +// user1.gno -> r/crossrealm_test.main -> r/demo/tests.Exec -> r/crossrealm_test.getPreviousRealm = gno.land/r/demo/tests +// user1.gno -> r/crossrealm_test.main -> p/demo/tests.Exec -> r/crossrealm_test.getPreviousRealm = user1.gno // user1.gno -> r/crossrealm_test.main -> p/demo/tests = user1.gno // user1.gno -> r/crossrealm_test.main -> r/crossrealm_test.Exec -> p/demo/tests = user1.gno // user1.gno -> r/crossrealm_test.main -> r/demo/tests.Exec -> p/demo/tests = gno.land/r/demo/tests diff --git a/gnovm/tests/files/zrealm_crossrealm13.gno b/gnovm/tests/files/zrealm_crossrealm13.gno index 63f8dec6357..53504d095ec 100644 --- a/gnovm/tests/files/zrealm_crossrealm13.gno +++ b/gnovm/tests/files/zrealm_crossrealm13.gno @@ -35,14 +35,14 @@ func PrintRealm() { // Output: // PrintRealm: CurrentRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) -// PrintRealm: PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PrintRealm: PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) // CurrentRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) -// PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) // PrintRealm: CurrentRealm: (struct{("g1user" std.Address),("" string)} std.Realm) -// PrintRealm: PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PrintRealm: PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) // CurrentRealm: (struct{("g1user" std.Address),("" string)} std.Realm) -// PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) // PrintRealm: CurrentRealm: (struct{("g17m4ga9t9dxn8uf06p3cahdavzfexe33ecg8v2s" std.Address),("gno.land/r/demo/users" string)} std.Realm) -// PrintRealm: PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PrintRealm: PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) // CurrentRealm: (struct{("g17m4ga9t9dxn8uf06p3cahdavzfexe33ecg8v2s" std.Address),("gno.land/r/demo/users" string)} std.Realm) -// PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) diff --git a/gnovm/tests/files/zrealm_crossrealm13a.gno b/gnovm/tests/files/zrealm_crossrealm13a.gno index c02711b7100..0434a0b6538 100644 --- a/gnovm/tests/files/zrealm_crossrealm13a.gno +++ b/gnovm/tests/files/zrealm_crossrealm13a.gno @@ -33,14 +33,14 @@ func PrintRealm() { // Output: // PrintRealm: CurrentRealm: (struct{("g1r0mlnkc05z0fv49km99z60qnp95tengyqfdr02" std.Address),("gno.land/r/demo/groups" string)} std.Realm) -// PrintRealm: PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PrintRealm: PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) // CurrentRealm: (struct{("g1r0mlnkc05z0fv49km99z60qnp95tengyqfdr02" std.Address),("gno.land/r/demo/groups" string)} std.Realm) -// PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) // PrintRealm: CurrentRealm: (struct{("g1r0mlnkc05z0fv49km99z60qnp95tengyqfdr02" std.Address),("gno.land/r/demo/groups" string)} std.Realm) -// PrintRealm: PreviousRealm: (struct{("g1user" std.Address),("" string)} std.Realm) +// PrintRealm: PreviousRealm: (struct{("g1user" std.Address),("" string)} std.Realm) // CurrentRealm: (struct{("g1user" std.Address),("" string)} std.Realm) -// PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) // PrintRealm: CurrentRealm: (struct{("g1r0mlnkc05z0fv49km99z60qnp95tengyqfdr02" std.Address),("gno.land/r/demo/groups" string)} std.Realm) -// PrintRealm: PreviousRealm: (struct{("g17m4ga9t9dxn8uf06p3cahdavzfexe33ecg8v2s" std.Address),("gno.land/r/demo/users" string)} std.Realm) +// PrintRealm: PreviousRealm: (struct{("g17m4ga9t9dxn8uf06p3cahdavzfexe33ecg8v2s" std.Address),("gno.land/r/demo/users" string)} std.Realm) // CurrentRealm: (struct{("g17m4ga9t9dxn8uf06p3cahdavzfexe33ecg8v2s" std.Address),("gno.land/r/demo/users" string)} std.Realm) -// PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) From ad57bfcc0127f0e6e3bb85e425a2c2656134fab7 Mon Sep 17 00:00:00 2001 From: hthieu1110 Date: Fri, 20 Dec 2024 20:00:50 +0700 Subject: [PATCH 10/14] refactor: address feedbacks --- docs/reference/stdlibs/std/chain.md | 2 +- docs/reference/stdlibs/std/realm.md | 4 ++-- docs/reference/stdlibs/std/testing.md | 8 ++++---- examples/gno.land/p/demo/grc/grc20/examples_test.gno | 2 +- examples/gno.land/r/demo/banktest/z_0_filetest.gno | 2 +- examples/gno.land/r/demo/banktest/z_1_filetest.gno | 2 +- examples/gno.land/r/demo/banktest/z_2_filetest.gno | 2 +- examples/gno.land/r/demo/disperse/z_0_filetest.gno | 2 +- examples/gno.land/r/demo/disperse/z_1_filetest.gno | 2 +- examples/gno.land/r/demo/disperse/z_2_filetest.gno | 2 +- examples/gno.land/r/demo/disperse/z_3_filetest.gno | 2 +- examples/gno.land/r/demo/disperse/z_4_filetest.gno | 2 +- .../gno.land/r/demo/grc20factory/grc20factory_test.gno | 2 +- examples/gno.land/r/demo/wugnot/z0_filetest.gno | 2 +- gnovm/tests/stdlibs/std/std.gno | 2 +- misc/deployments/test5.gno.land/genesis_txs.jsonl | 6 +++--- 16 files changed, 22 insertions(+), 22 deletions(-) diff --git a/docs/reference/stdlibs/std/chain.md b/docs/reference/stdlibs/std/chain.md index 69a1a77c8b6..0ef1c3457d6 100644 --- a/docs/reference/stdlibs/std/chain.md +++ b/docs/reference/stdlibs/std/chain.md @@ -101,7 +101,7 @@ caller := std.OriginCaller() ``` --- -## GetOriginPkgAddr +## GetOriginPkgAddress ```go func GetOriginPkgAddress() string ``` diff --git a/docs/reference/stdlibs/std/realm.md b/docs/reference/stdlibs/std/realm.md index 9c5aad86819..207285ca546 100644 --- a/docs/reference/stdlibs/std/realm.md +++ b/docs/reference/stdlibs/std/realm.md @@ -11,13 +11,13 @@ type Realm struct { pkgPath string } -func (r Realm) Addr() Address {...} +func (r Realm) Address() Address {...} func (r Realm) PkgPath() string {...} func (r Realm) IsUser() bool {...} func (r Realm) CoinDenom(coinName string) string {...} ``` -## Addr +## Address Returns the **Address** field of the realm it was called upon. #### Usage diff --git a/docs/reference/stdlibs/std/testing.md b/docs/reference/stdlibs/std/testing.md index ff625df745b..6a18d7381f2 100644 --- a/docs/reference/stdlibs/std/testing.md +++ b/docs/reference/stdlibs/std/testing.md @@ -7,7 +7,7 @@ id: testing ```go func TestSkipHeights(count int64) func TestSetOriginCaller(addr Address) -func TestSetOriginPkgAddr(addr Address) +func TestSetOriginPkgAddress(addr Address) func TestSetOriginSend(sent, spent Coins) func TestIssueCoins(addr Address, coins Coins) func TestSetRealm(realm Realm) @@ -45,16 +45,16 @@ std.TestSetOriginCaller(std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")) ``` --- -## TestSetOriginPkgAddr +## TestSetOriginPkgAddress ```go -func TestSetOriginPkgAddr(addr Address) +func TestSetOriginPkgAddress(addr Address) ``` Sets the call entry realm address to **addr**. #### Usage ```go -std.TestSetOriginPkgAddr(std.Address("g1ecely4gjy0yl6s9kt409ll330q9hk2lj9ls3ec")) +std.TestSetOriginPkgAddress(std.Address("g1ecely4gjy0yl6s9kt409ll330q9hk2lj9ls3ec")) ``` --- diff --git a/examples/gno.land/p/demo/grc/grc20/examples_test.gno b/examples/gno.land/p/demo/grc/grc20/examples_test.gno index 726ed4dc284..17ceb240668 100644 --- a/examples/gno.land/p/demo/grc/grc20/examples_test.gno +++ b/examples/gno.land/p/demo/grc/grc20/examples_test.gno @@ -7,7 +7,7 @@ func ExampleExposeBankForMaketxRunOrImports() {} func ExampleCustomTellerImpl() {} func ExampleAllowance() {} func ExampleRealmBanker() {} -func ExamplePreviousRealmBanker() {} +func ExamplePreviousRealmBanker() {} func ExampleAccountBanker() {} func ExampleTransfer() {} func ExampleApprove() {} diff --git a/examples/gno.land/r/demo/banktest/z_0_filetest.gno b/examples/gno.land/r/demo/banktest/z_0_filetest.gno index d41d1cbb851..f1e310b1d27 100644 --- a/examples/gno.land/r/demo/banktest/z_0_filetest.gno +++ b/examples/gno.land/r/demo/banktest/z_0_filetest.gno @@ -18,7 +18,7 @@ func main() { banktestAddr := std.DerivePkgAddr("gno.land/r/demo/banktest") mainaddr := std.DerivePkgAddr("gno.land/r/demo/bank1") std.TestSetOriginCaller(mainaddr) - std.TestSetOriginPkgAddr(banktestAddr) + std.TestSetOriginPkgAddress(banktestAddr) // get and print balance of mainaddr. // with the SEND, + 200 gnot given by the TestContext, main should have 300gnot. diff --git a/examples/gno.land/r/demo/banktest/z_1_filetest.gno b/examples/gno.land/r/demo/banktest/z_1_filetest.gno index 2f34d85e766..eebe70f251d 100644 --- a/examples/gno.land/r/demo/banktest/z_1_filetest.gno +++ b/examples/gno.land/r/demo/banktest/z_1_filetest.gno @@ -15,7 +15,7 @@ func main() { banktestAddr := std.DerivePkgAddr("gno.land/r/demo/banktest") // simulate a Deposit call. - std.TestSetOriginPkgAddr(banktestAddr) + std.TestSetOriginPkgAddress(banktestAddr) std.TestIssueCoins(banktestAddr, std.Coins{{"ugnot", 100000000}}) std.TestSetOriginSend(std.Coins{{"ugnot", 100000000}}, nil) res := banktest.Deposit("ugnot", 101000000) diff --git a/examples/gno.land/r/demo/banktest/z_2_filetest.gno b/examples/gno.land/r/demo/banktest/z_2_filetest.gno index 6d4d511c243..8510ce4de22 100644 --- a/examples/gno.land/r/demo/banktest/z_2_filetest.gno +++ b/examples/gno.land/r/demo/banktest/z_2_filetest.gno @@ -23,7 +23,7 @@ func main() { println("main before:", mainbal) // plus OriginSend equals 300. // simulate a Deposit call. - std.TestSetOriginPkgAddr(banktestAddr) + std.TestSetOriginPkgAddress(banktestAddr) std.TestIssueCoins(banktestAddr, std.Coins{{"ugnot", 100000000}}) std.TestSetOriginSend(std.Coins{{"ugnot", 100000000}}, nil) res := banktest.Deposit("ugnot", 55000000) diff --git a/examples/gno.land/r/demo/disperse/z_0_filetest.gno b/examples/gno.land/r/demo/disperse/z_0_filetest.gno index 5779eea71f4..8190ae203f2 100644 --- a/examples/gno.land/r/demo/disperse/z_0_filetest.gno +++ b/examples/gno.land/r/demo/disperse/z_0_filetest.gno @@ -14,7 +14,7 @@ func main() { disperseAddr := std.DerivePkgAddr("gno.land/r/demo/disperse") mainaddr := std.DerivePkgAddr("gno.land/r/demo/main") - std.TestSetOriginPkgAddr(disperseAddr) + std.TestSetOriginPkgAddress(disperseAddr) std.TestSetOriginCaller(mainaddr) banker := std.GetBanker(std.BankerTypeRealmSend) diff --git a/examples/gno.land/r/demo/disperse/z_1_filetest.gno b/examples/gno.land/r/demo/disperse/z_1_filetest.gno index 41388e22eb4..4e08aae156f 100644 --- a/examples/gno.land/r/demo/disperse/z_1_filetest.gno +++ b/examples/gno.land/r/demo/disperse/z_1_filetest.gno @@ -14,7 +14,7 @@ func main() { disperseAddr := std.DerivePkgAddr("gno.land/r/demo/disperse") mainaddr := std.DerivePkgAddr("gno.land/r/demo/main") - std.TestSetOriginPkgAddr(disperseAddr) + std.TestSetOriginPkgAddress(disperseAddr) std.TestSetOriginCaller(mainaddr) banker := std.GetBanker(std.BankerTypeRealmSend) diff --git a/examples/gno.land/r/demo/disperse/z_2_filetest.gno b/examples/gno.land/r/demo/disperse/z_2_filetest.gno index 018fdac827c..9c0c131ac34 100644 --- a/examples/gno.land/r/demo/disperse/z_2_filetest.gno +++ b/examples/gno.land/r/demo/disperse/z_2_filetest.gno @@ -14,7 +14,7 @@ func main() { disperseAddr := std.DerivePkgAddr("gno.land/r/demo/disperse") mainaddr := std.DerivePkgAddr("gno.land/r/demo/main") - std.TestSetOriginPkgAddr(disperseAddr) + std.TestSetOriginPkgAddress(disperseAddr) std.TestSetOriginCaller(mainaddr) banker := std.GetBanker(std.BankerTypeRealmSend) diff --git a/examples/gno.land/r/demo/disperse/z_3_filetest.gno b/examples/gno.land/r/demo/disperse/z_3_filetest.gno index 70128d47cf2..696a6f028ed 100644 --- a/examples/gno.land/r/demo/disperse/z_3_filetest.gno +++ b/examples/gno.land/r/demo/disperse/z_3_filetest.gno @@ -17,7 +17,7 @@ func main() { beneficiary1 := std.Address("g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0") beneficiary2 := std.Address("g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c") - std.TestSetOriginPkgAddr(disperseAddr) + std.TestSetOriginPkgAddress(disperseAddr) std.TestSetOriginCaller(mainaddr) banker := std.GetBanker(std.BankerTypeRealmSend) diff --git a/examples/gno.land/r/demo/disperse/z_4_filetest.gno b/examples/gno.land/r/demo/disperse/z_4_filetest.gno index 7ca890c5b8b..a021fe4aba3 100644 --- a/examples/gno.land/r/demo/disperse/z_4_filetest.gno +++ b/examples/gno.land/r/demo/disperse/z_4_filetest.gno @@ -17,7 +17,7 @@ func main() { beneficiary1 := std.Address("g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0") beneficiary2 := std.Address("g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c") - std.TestSetOriginPkgAddr(disperseAddr) + std.TestSetOriginPkgAddress(disperseAddr) std.TestSetOriginCaller(mainaddr) banker := std.GetBanker(std.BankerTypeRealmSend) diff --git a/examples/gno.land/r/demo/grc20factory/grc20factory_test.gno b/examples/gno.land/r/demo/grc20factory/grc20factory_test.gno index 5b53340b609..16584e63a1f 100644 --- a/examples/gno.land/r/demo/grc20factory/grc20factory_test.gno +++ b/examples/gno.land/r/demo/grc20factory/grc20factory_test.gno @@ -10,7 +10,7 @@ import ( ) func TestReadOnlyPublicMethods(t *testing.T) { - std.TestSetOriginPkgAddr("gno.land/r/demo/grc20factory") + std.TestSetOriginPkgAddress("gno.land/r/demo/grc20factory") admin := testutils.TestAddress("admin") bob := testutils.TestAddress("bob") carl := testutils.TestAddress("carl") diff --git a/examples/gno.land/r/demo/wugnot/z0_filetest.gno b/examples/gno.land/r/demo/wugnot/z0_filetest.gno index f7db3aea66d..b408038a251 100644 --- a/examples/gno.land/r/demo/wugnot/z0_filetest.gno +++ b/examples/gno.land/r/demo/wugnot/z0_filetest.gno @@ -18,7 +18,7 @@ var ( ) func main() { - std.TestSetOriginPkgAddr(addrc) + std.TestSetOriginPkgAddress(addrc) std.TestIssueCoins(addrc, std.Coins{{"ugnot", 100000001}}) // TODO: remove this // issue ugnots diff --git a/gnovm/tests/stdlibs/std/std.gno b/gnovm/tests/stdlibs/std/std.gno index 2fd5f38c0d9..46b73ef99ea 100644 --- a/gnovm/tests/stdlibs/std/std.gno +++ b/gnovm/tests/stdlibs/std/std.gno @@ -5,7 +5,7 @@ func IsOriginCall() bool // injected func TestSkipHeights(count int64) // injected func TestSetOriginCaller(addr Address) { testSetOriginCaller(string(addr)) } -func TestSetOriginPkgAddr(addr Address) { testSetOriginPkgAddr(string(addr)) } +func TestSetOriginPkgAddress(addr Address) { testSetOriginPkgAddr(string(addr)) } // TestSetRealm sets the realm for the current frame. // After calling TestSetRealm, calling CurrentRealm() in the test function will yield the value of diff --git a/misc/deployments/test5.gno.land/genesis_txs.jsonl b/misc/deployments/test5.gno.land/genesis_txs.jsonl index adbca225e5a..6a0d0e71c94 100755 --- a/misc/deployments/test5.gno.land/genesis_txs.jsonl +++ b/misc/deployments/test5.gno.land/genesis_txs.jsonl @@ -76,7 +76,7 @@ {"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"poa","path":"gno.land/p/nt/poa","files":[{"name":"option.gno","body":"package poa\n\nimport \"gno.land/p/sys/validators\"\n\ntype Option func(*PoA)\n\n// WithInitialSet sets the initial PoA validator set\nfunc WithInitialSet(validators []validators.Validator) Option {\n\treturn func(p *PoA) {\n\t\tfor _, validator := range validators {\n\t\t\tp.validators.Set(validator.Address.String(), validator)\n\t\t}\n\t}\n}\n"},{"name":"poa.gno","body":"package poa\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/sys/validators\"\n)\n\nvar ErrInvalidVotingPower = errors.New(\"invalid voting power\")\n\n// PoA specifies the Proof of Authority validator set, with simple add / remove constraints.\n//\n// To add:\n// - proposed validator must not be part of the set already\n// - proposed validator voting power must be \u003e 0\n//\n// To remove:\n// - proposed validator must be part of the set already\ntype PoA struct {\n\tvalidators *avl.Tree // std.Address -\u003e validators.Validator\n}\n\n// NewPoA creates a new empty Proof of Authority validator set\nfunc NewPoA(opts ...Option) *PoA {\n\t// Create the empty set\n\tp := \u0026PoA{\n\t\tvalidators: avl.NewTree(),\n\t}\n\n\t// Apply the options\n\tfor _, opt := range opts {\n\t\topt(p)\n\t}\n\n\treturn p\n}\n\nfunc (p *PoA) AddValidator(address std.Address, pubKey string, power uint64) (validators.Validator, error) {\n\t// Validate that the operation is a valid call.\n\t// Check if the validator is already in the set\n\tif p.IsValidator(address) {\n\t\treturn validators.Validator{}, validators.ErrValidatorExists\n\t}\n\n\t// Make sure the voting power \u003e 0\n\tif power == 0 {\n\t\treturn validators.Validator{}, ErrInvalidVotingPower\n\t}\n\n\tv := validators.Validator{\n\t\tAddress: address,\n\t\tPubKey: pubKey, // TODO: in the future, verify the public key\n\t\tVotingPower: power,\n\t}\n\n\t// Add the validator to the set\n\tp.validators.Set(address.String(), v)\n\n\treturn v, nil\n}\n\nfunc (p *PoA) RemoveValidator(address std.Address) (validators.Validator, error) {\n\t// Validate that the operation is a valid call\n\t// Fetch the validator\n\tvalidator, err := p.GetValidator(address)\n\tif err != nil {\n\t\treturn validators.Validator{}, err\n\t}\n\n\t// Remove the validator from the set\n\tp.validators.Remove(address.String())\n\n\treturn validator, nil\n}\n\nfunc (p *PoA) IsValidator(address std.Address) bool {\n\t_, exists := p.validators.Get(address.String())\n\n\treturn exists\n}\n\nfunc (p *PoA) GetValidator(address std.Address) (validators.Validator, error) {\n\tvalidatorRaw, exists := p.validators.Get(address.String())\n\tif !exists {\n\t\treturn validators.Validator{}, validators.ErrValidatorMissing\n\t}\n\n\tvalidator := validatorRaw.(validators.Validator)\n\n\treturn validator, nil\n}\n\nfunc (p *PoA) GetValidators() []validators.Validator {\n\tvals := make([]validators.Validator, 0, p.validators.Size())\n\n\tp.validators.Iterate(\"\", \"\", func(_ string, value interface{}) bool {\n\t\tvalidator := value.(validators.Validator)\n\t\tvals = append(vals, validator)\n\n\t\treturn false\n\t})\n\n\treturn vals\n}\n"},{"name":"poa_test.gno","body":"package poa\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/urequire\"\n\t\"gno.land/p/sys/validators\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// generateTestValidators generates a dummy validator set\nfunc generateTestValidators(count int) []validators.Validator {\n\tvals := make([]validators.Validator, 0, count)\n\n\tfor i := 0; i \u003c count; i++ {\n\t\tval := validators.Validator{\n\t\t\tAddress: testutils.TestAddress(ufmt.Sprintf(\"%d\", i)),\n\t\t\tPubKey: \"public-key\",\n\t\t\tVotingPower: 1,\n\t\t}\n\n\t\tvals = append(vals, val)\n\t}\n\n\treturn vals\n}\n\nfunc TestPoA_AddValidator_Invalid(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"validator already in set\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tproposalAddress = testutils.TestAddress(\"caller\")\n\t\t\tproposalKey = \"public-key\"\n\n\t\t\tinitialSet = generateTestValidators(1)\n\t\t)\n\n\t\tinitialSet[0].Address = proposalAddress\n\t\tinitialSet[0].PubKey = proposalKey\n\n\t\t// Create the protocol with an initial set\n\t\tp := NewPoA(WithInitialSet(initialSet))\n\n\t\t// Attempt to add the validator\n\t\t_, err := p.AddValidator(proposalAddress, proposalKey, 1)\n\t\tuassert.ErrorIs(t, err, validators.ErrValidatorExists)\n\t})\n\n\tt.Run(\"invalid voting power\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tproposalAddress = testutils.TestAddress(\"caller\")\n\t\t\tproposalKey = \"public-key\"\n\t\t)\n\n\t\t// Create the protocol with no initial set\n\t\tp := NewPoA()\n\n\t\t// Attempt to add the validator\n\t\t_, err := p.AddValidator(proposalAddress, proposalKey, 0)\n\t\tuassert.ErrorIs(t, err, ErrInvalidVotingPower)\n\t})\n}\n\nfunc TestPoA_AddValidator(t *testing.T) {\n\tt.Parallel()\n\n\tvar (\n\t\tproposalAddress = testutils.TestAddress(\"caller\")\n\t\tproposalKey = \"public-key\"\n\t)\n\n\t// Create the protocol with no initial set\n\tp := NewPoA()\n\n\t// Attempt to add the validator\n\t_, err := p.AddValidator(proposalAddress, proposalKey, 1)\n\tuassert.NoError(t, err)\n\n\t// Make sure the validator is added\n\tif !p.IsValidator(proposalAddress) || p.validators.Size() != 1 {\n\t\tt.Fatal(\"address is not validator\")\n\t}\n}\n\nfunc TestPoA_RemoveValidator_Invalid(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"proposed removal not in set\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\tproposalAddress = testutils.TestAddress(\"caller\")\n\t\t\tinitialSet = generateTestValidators(1)\n\t\t)\n\n\t\tinitialSet[0].Address = proposalAddress\n\n\t\t// Create the protocol with an initial set\n\t\tp := NewPoA(WithInitialSet(initialSet))\n\n\t\t// Attempt to remove the validator\n\t\t_, err := p.RemoveValidator(testutils.TestAddress(\"totally random\"))\n\t\tuassert.ErrorIs(t, err, validators.ErrValidatorMissing)\n\t})\n}\n\nfunc TestPoA_RemoveValidator(t *testing.T) {\n\tt.Parallel()\n\n\tvar (\n\t\tproposalAddress = testutils.TestAddress(\"caller\")\n\t\tinitialSet = generateTestValidators(1)\n\t)\n\n\tinitialSet[0].Address = proposalAddress\n\n\t// Create the protocol with an initial set\n\tp := NewPoA(WithInitialSet(initialSet))\n\n\t// Attempt to remove the validator\n\t_, err := p.RemoveValidator(proposalAddress)\n\turequire.NoError(t, err)\n\n\t// Make sure the validator is removed\n\tif p.IsValidator(proposalAddress) || p.validators.Size() != 0 {\n\t\tt.Fatal(\"address is validator\")\n\t}\n}\n\nfunc TestPoA_GetValidator(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"validator not in set\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Create the protocol with no initial set\n\t\tp := NewPoA()\n\n\t\t// Attempt to get the voting power\n\t\t_, err := p.GetValidator(testutils.TestAddress(\"caller\"))\n\t\tuassert.ErrorIs(t, err, validators.ErrValidatorMissing)\n\t})\n\n\tt.Run(\"validator fetched\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tvar (\n\t\t\taddress = testutils.TestAddress(\"caller\")\n\t\t\tpubKey = \"public-key\"\n\t\t\tvotingPower = uint64(10)\n\n\t\t\tinitialSet = generateTestValidators(1)\n\t\t)\n\n\t\tinitialSet[0].Address = address\n\t\tinitialSet[0].PubKey = pubKey\n\t\tinitialSet[0].VotingPower = votingPower\n\n\t\t// Create the protocol with an initial set\n\t\tp := NewPoA(WithInitialSet(initialSet))\n\n\t\t// Get the validator\n\t\tval, err := p.GetValidator(address)\n\t\turequire.NoError(t, err)\n\n\t\t// Validate the address\n\t\tif val.Address != address {\n\t\t\tt.Fatal(\"invalid address\")\n\t\t}\n\n\t\t// Validate the voting power\n\t\tif val.VotingPower != votingPower {\n\t\t\tt.Fatal(\"invalid voting power\")\n\t\t}\n\n\t\t// Validate the public key\n\t\tif val.PubKey != pubKey {\n\t\t\tt.Fatal(\"invalid public key\")\n\t\t}\n\t})\n}\n\nfunc TestPoA_GetValidators(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"empty set\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\t// Create the protocol with no initial set\n\t\tp := NewPoA()\n\n\t\t// Attempt to get the voting power\n\t\tvals := p.GetValidators()\n\n\t\tif len(vals) != 0 {\n\t\t\tt.Fatal(\"validator set is not empty\")\n\t\t}\n\t})\n\n\tt.Run(\"validator set fetched\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tinitialSet := generateTestValidators(10)\n\n\t\t// Create the protocol with an initial set\n\t\tp := NewPoA(WithInitialSet(initialSet))\n\n\t\t// Get the validator set\n\t\tvals := p.GetValidators()\n\n\t\tif len(vals) != len(initialSet) {\n\t\t\tt.Fatal(\"returned validator set mismatch\")\n\t\t}\n\n\t\tfor _, val := range vals {\n\t\t\tfor _, initialVal := range initialSet {\n\t\t\t\tif val.Address != initialVal.Address {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\t// Validate the voting power\n\t\t\t\tuassert.Equal(t, val.VotingPower, initialVal.VotingPower)\n\n\t\t\t\t// Validate the public key\n\t\t\t\tuassert.Equal(t, val.PubKey, initialVal.PubKey)\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":"gnoface","path":"gno.land/r/demo/art/gnoface","files":[{"name":"gnoface.gno","body":"package gnoface\n\nimport (\n\t\"math/rand\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/entropy\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nfunc Render(path string) string {\n\tseed := uint64(entropy.New().Value())\n\n\tpath = strings.TrimSpace(path)\n\tif path != \"\" {\n\t\ts, err := strconv.Atoi(path)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tseed = uint64(s)\n\t}\n\n\toutput := ufmt.Sprintf(\"Gnoface #%d\\n\", seed)\n\toutput += \"```\\n\" + Draw(seed) + \"```\\n\"\n\treturn output\n}\n\nfunc Draw(seed uint64) string {\n\tvar (\n\t\thairs = []string{\n\t\t\t\" s\",\n\t\t\t\" .......\",\n\t\t\t\" s s s\",\n\t\t\t\" /\\\\ /\\\\\",\n\t\t\t\" |||||||\",\n\t\t}\n\t\theadtop = []string{\n\t\t\t\" /-------\\\\\",\n\t\t\t\" /~~~~~~~\\\\\",\n\t\t\t\" /|||||||\\\\\",\n\t\t\t\" ////////\\\\\",\n\t\t\t\" |||||||||\",\n\t\t\t\" /\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\",\n\t\t}\n\t\theadspace = []string{\n\t\t\t\" | |\",\n\t\t}\n\t\teyebrow = []string{\n\t\t\t\"~\",\n\t\t\t\"*\",\n\t\t\t\"_\",\n\t\t\t\".\",\n\t\t}\n\t\tear = []string{\n\t\t\t\"o\",\n\t\t\t\" \",\n\t\t\t\"D\",\n\t\t\t\"O\",\n\t\t\t\"\u003c\",\n\t\t\t\"\u003e\",\n\t\t\t\".\",\n\t\t\t\"|\",\n\t\t\t\")\",\n\t\t\t\"(\",\n\t\t}\n\t\teyesmiddle = []string{\n\t\t\t\"| o o |\",\n\t\t\t\"| o _ |\",\n\t\t\t\"| _ o |\",\n\t\t\t\"| . . |\",\n\t\t\t\"| O O |\",\n\t\t\t\"| v v |\",\n\t\t\t\"| X X |\",\n\t\t\t\"| x X |\",\n\t\t\t\"| X D |\",\n\t\t\t\"| ~ ~ |\",\n\t\t}\n\t\tnose = []string{\n\t\t\t\" | o |\",\n\t\t\t\" | O |\",\n\t\t\t\" | V |\",\n\t\t\t\" | L |\",\n\t\t\t\" | C |\",\n\t\t\t\" | ~ |\",\n\t\t\t\" | . . |\",\n\t\t\t\" | . |\",\n\t\t}\n\t\tmouth = []string{\n\t\t\t\" | __/ |\",\n\t\t\t\" | \\\\_/ |\",\n\t\t\t\" | . |\",\n\t\t\t\" | ___ |\",\n\t\t\t\" | ~~~ |\",\n\t\t\t\" | === |\",\n\t\t\t\" | \u003c=\u003e |\",\n\t\t}\n\t\theadbottom = []string{\n\t\t\t\" \\\\-------/\",\n\t\t\t\" \\\\~~~~~~~/\",\n\t\t\t\" \\\\_______/\",\n\t\t}\n\t)\n\n\tr := rand.New(rand.NewPCG(seed, 0xdeadbeef))\n\n\treturn pick(r, hairs) + \"\\n\" +\n\t\tpick(r, headtop) + \"\\n\" +\n\t\tpick(r, headspace) + \"\\n\" +\n\t\t\" | \" + pick(r, eyebrow) + \" \" + pick(r, eyebrow) + \" |\\n\" +\n\t\tpick(r, ear) + pick(r, eyesmiddle) + pick(r, ear) + \"\\n\" +\n\t\tpick(r, headspace) + \"\\n\" +\n\t\tpick(r, nose) + \"\\n\" +\n\t\tpick(r, headspace) + \"\\n\" +\n\t\tpick(r, mouth) + \"\\n\" +\n\t\tpick(r, headspace) + \"\\n\" +\n\t\tpick(r, headbottom) + \"\\n\"\n}\n\nfunc pick(r *rand.Rand, slice []string) string {\n\treturn slice[r.IntN(len(slice))]\n}\n\n// based on https://github.com/moul/pipotron/blob/master/dict/ascii-face.yml\n"},{"name":"gnoface_test.gno","body":"package gnoface\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nfunc TestDraw(t *testing.T) {\n\tcases := []struct {\n\t\tseed uint64\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tseed: 42,\n\t\t\texpected: `\n |||||||\n |||||||||\n | |\n | . ~ |\n)| v v |O\n | |\n | L |\n | |\n | ___ |\n | |\n \\~~~~~~~/\n`[1:],\n\t\t},\n\t\t{\n\t\t\tseed: 1337,\n\t\t\texpected: `\n .......\n |||||||||\n | |\n | . _ |\nD| x X |O\n | |\n | ~ |\n | |\n | ~~~ |\n | |\n \\~~~~~~~/\n`[1:],\n\t\t},\n\t\t{\n\t\t\tseed: 123456789,\n\t\t\texpected: `\n .......\n ////////\\\n | |\n | ~ * |\n|| x X |o\n | |\n | V |\n | |\n | . |\n | |\n \\-------/\n`[1:],\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tname := ufmt.Sprintf(\"%d\", tc.seed)\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tgot := Draw(tc.seed)\n\t\t\tuassert.Equal(t, string(tc.expected), got)\n\t\t})\n\t}\n}\n\nfunc TestRender(t *testing.T) {\n\tcases := []struct {\n\t\tpath string\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tpath: \"42\",\n\t\t\texpected: \"Gnoface #42\\n```\" + `\n |||||||\n |||||||||\n | |\n | . ~ |\n)| v v |O\n | |\n | L |\n | |\n | ___ |\n | |\n \\~~~~~~~/\n` + \"```\\n\",\n\t\t},\n\t\t{\n\t\t\tpath: \"1337\",\n\t\t\texpected: \"Gnoface #1337\\n```\" + `\n .......\n |||||||||\n | |\n | . _ |\nD| x X |O\n | |\n | ~ |\n | |\n | ~~~ |\n | |\n \\~~~~~~~/\n` + \"```\\n\",\n\t\t},\n\t\t{\n\t\t\tpath: \"123456789\",\n\t\t\texpected: \"Gnoface #123456789\\n```\" + `\n .......\n ////////\\\n | |\n | ~ * |\n|| x X |o\n | |\n | V |\n | |\n | . |\n | |\n \\-------/\n` + \"```\\n\",\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tt.Run(tc.path, func(t *testing.T) {\n\t\t\tgot := Render(tc.path)\n\t\t\tuassert.Equal(t, tc.expected, got)\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":"millipede","path":"gno.land/r/demo/art/millipede","files":[{"name":"millipede.gno","body":"package millipede\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\nconst (\n\tminSize = 1\n\tdefaultSize = 20\n\tmaxSize = 100\n)\n\nfunc Draw(size int) string {\n\tif size \u003c minSize || size \u003e maxSize {\n\t\tpanic(\"invalid millipede size\")\n\t}\n\tpaddings := []string{\" \", \" \", \"\", \" \", \" \", \" \", \" \", \" \", \" \"}\n\tvar b strings.Builder\n\tb.WriteString(\" ╚⊙ ⊙╝\\n\")\n\tfor i := 0; i \u003c size; i++ {\n\t\tb.WriteString(paddings[i%9] + \"╚═(███)═╝\\n\")\n\t}\n\treturn b.String()\n}\n\nfunc Render(path string) string {\n\tsize := defaultSize\n\n\tpath = strings.TrimSpace(path)\n\tif path != \"\" {\n\t\tvar err error\n\t\tsize, err = strconv.Atoi(path)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}\n\n\toutput := \"```\\n\" + Draw(size) + \"```\\n\"\n\tif size \u003e minSize {\n\t\toutput += ufmt.Sprintf(\"[%d](/r/demo/art/millipede:%d)\u003c \", size-1, size-1)\n\t}\n\tif size \u003c maxSize {\n\t\toutput += ufmt.Sprintf(\" \u003e[%d](/r/demo/art/millipede:%d)\", size+1, size+1)\n\t}\n\treturn output\n}\n\n// based on https://github.com/getmillipede/millipede-go/blob/977f046c39c35a650eac0fd30245e96b22c7803c/main.go\n"},{"name":"millipede_test.gno","body":"package millipede\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestRender(t *testing.T) {\n\tcases := []struct {\n\t\tpath string\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tpath: \"\",\n\t\t\texpected: \"```\" + `\n ╚⊙ ⊙╝\n ╚═(███)═╝\n ╚═(███)═╝\n╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n ╚═(███)═╝\n` + \"```\\n[19](/r/demo/art/millipede:19)\u003c \u003e[21](/r/demo/art/millipede:21)\",\n\t\t},\n\t\t{\n\t\t\tpath: \"4\",\n\t\t\texpected: \"```\" + `\n ╚⊙ ⊙╝\n ╚═(███)═╝\n ╚═(███)═╝\n╚═(███)═╝\n ╚═(███)═╝\n` + \"```\\n[3](/r/demo/art/millipede:3)\u003c \u003e[5](/r/demo/art/millipede:5)\",\n\t\t},\n\t}\n\n\tfor _, tc := range cases {\n\t\tt.Run(tc.path, func(t *testing.T) {\n\t\t\tgot := Render(tc.path)\n\t\t\tuassert.Equal(t, tc.expected, got)\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":"banktest","path":"gno.land/r/demo/banktest","files":[{"name":"README.md","body":"This is a simple test realm contract that demonstrates how to use the banker.\n\nSee [gno.land/r/demo/banktest/banktest.go](/r/demo/banktest/banktest.go) to see the original contract code.\n\nThis article will go through each line to explain how it works.\n\n```go\npackage banktest\n```\n\nThis package is locally named \"banktest\" (could be anything).\n\n```go\nimport (\n \"std\"\n)\n```\n\nThe \"std\" package is defined by the gno code in stdlibs/std/. \u003c/br\u003e Self explanatory; and you'll see more usage from std later.\n\n```go\ntype activity struct {\n caller std.Address\n sent std.Coins\n returned std.Coins\n time time.Time\n}\n\nfunc (act *activity) String() string {\n return act.caller.String() + \" \" +\n act.sent.String() + \" sent, \" +\n act.returned.String() + \" returned, at \" +\n act.time.Format(\"2006-01-02 3:04pm MST\")\n}\n\nvar latest [10]*activity\n```\n\nThis is just maintaining a list of recent activity to this contract. Notice that the \"latest\" variable is defined \"globally\" within the context of the realm with path \"gno.land/r/demo/banktest\".\n\nThis means that calls to functions defined within this package are encapsulated within this \"data realm\", where the data is mutated based on transactions that can potentially cross many realm and non-realm package boundaries (in the call stack).\n\n```go\n// Deposit will take the coins (to the realm's pkgaddr) or return them to user.\nfunc Deposit(returnDenom string, returnAmount int64) string {\n std.AssertOriginCall()\n caller := std.OriginCaller()\n send := std.Coins{{returnDenom, returnAmount}}\n```\n\nThis is the beginning of the definition of the contract function named \"Deposit\". `std.AssertOriginCall() asserts that this function was called by a gno transactional Message. The caller is the user who signed off on this transactional message. Send is the amount of deposit sent along with this message.\n\n```go\n // record activity\n act := \u0026activity{\n caller: caller,\n sent: std.OriginSend(),\n returned: send,\n time: time.Now(),\n }\n for i := len(latest) - 2; i \u003e= 0; i-- {\n latest[i+1] = latest[i] // shift by +1.\n }\n latest[0] = act\n```\n\nUpdating the \"latest\" array for viewing at gno.land/r/demo/banktest: (w/ trailing colon).\n\n```go\n // return if any.\n if returnAmount \u003e 0 {\n```\n\nIf the user requested the return of coins...\n\n```go\n banker := std.GetBanker(std.BankerTypeOriginSend)\n```\n\nuse a std.Banker instance to return any deposited coins to the original sender.\n\n```go\n pkgaddr := std.GetOriginPkgAddress()\n // TODO: use std.Coins constructors, this isn't generally safe.\n banker.SendCoins(pkgaddr, caller, send)\n return \"returned!\"\n```\n\nNotice that each realm package has an associated Cosmos address.\n\nFinally, the results are rendered via an ABCI query call when you visit [/r/demo/banktest:](/r/demo/banktest:).\n\n```go\nfunc Render(path string) string {\n // get realm coins.\n banker := std.GetBanker(std.BankerTypeReadonly)\n coins := banker.GetCoins(std.GetOriginPkgAddress())\n\n // render\n res := \"\"\n res += \"## recent activity\\n\"\n res += \"\\n\"\n for _, act := range latest {\n if act == nil {\n break\n }\n res += \" * \" + act.String() + \"\\n\"\n }\n res += \"\\n\"\n res += \"## total deposits\\n\"\n res += coins.String()\n return res\n}\n```\n\nYou can call this contract yourself, by vistiing [/r/demo/banktest](/r/demo/banktest) and the [quickstart guide](/r/demo/boards:gnolang/4).\n"},{"name":"banktest.gno","body":"package banktest\n\nimport (\n\t\"std\"\n\t\"time\"\n)\n\ntype activity struct {\n\tcaller std.Address\n\tsent std.Coins\n\treturned std.Coins\n\ttime time.Time\n}\n\nfunc (act *activity) String() string {\n\treturn act.caller.String() + \" \" +\n\t\tact.sent.String() + \" sent, \" +\n\t\tact.returned.String() + \" returned, at \" +\n\t\tact.time.Format(\"2006-01-02 3:04pm MST\")\n}\n\nvar latest [10]*activity\n\n// Deposit will take the coins (to the realm's pkgaddr) or return them to user.\nfunc Deposit(returnDenom string, returnAmount int64) string {\n\tstd.AssertOriginCall()\n\tcaller := std.OriginCaller()\n\tsend := std.Coins{{returnDenom, returnAmount}}\n\t// record activity\n\tact := \u0026activity{\n\t\tcaller: caller,\n\t\tsent: std.OriginSend(),\n\t\treturned: send,\n\t\ttime: time.Now(),\n\t}\n\tfor i := len(latest) - 2; i \u003e= 0; i-- {\n\t\tlatest[i+1] = latest[i] // shift by +1.\n\t}\n\tlatest[0] = act\n\t// return if any.\n\tif returnAmount \u003e 0 {\n\t\tbanker := std.GetBanker(std.BankerTypeOriginSend)\n\t\tpkgaddr := std.GetOriginPkgAddress()\n\t\t// TODO: use std.Coins constructors, this isn't generally safe.\n\t\tbanker.SendCoins(pkgaddr, caller, send)\n\t\treturn \"returned!\"\n\t} else {\n\t\treturn \"thank you!\"\n\t}\n}\n\nfunc Render(path string) string {\n\t// get realm coins.\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tcoins := banker.GetCoins(std.GetOriginPkgAddress())\n\n\t// render\n\tres := \"\"\n\tres += \"## recent activity\\n\"\n\tres += \"\\n\"\n\tfor _, act := range latest {\n\t\tif act == nil {\n\t\t\tbreak\n\t\t}\n\t\tres += \" * \" + act.String() + \"\\n\"\n\t}\n\tres += \"\\n\"\n\tres += \"## total deposits\\n\"\n\tres += coins.String()\n\treturn res\n}\n"},{"name":"z_0_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\n// SEND: 100000000ugnot\n\npackage bank1\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/banktest\"\n)\n\nfunc main() {\n\t// set up main address and banktest addr.\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/bank1\")\n\tstd.TestSetOriginCaller(mainaddr)\n\tstd.TestSetOriginPkgAddr(banktestAddr)\n\n\t// get and print balance of mainaddr.\n\t// with the SEND, + 200 gnot given by the TestContext, main should have 300gnot.\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\t// simulate a Deposit call. use Send + OriginSend to simulate -send.\n\tbanker.SendCoins(mainaddr, banktestAddr, std.Coins{{\"ugnot\", 100_000_000}})\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 100_000_000}}, nil)\n\tres := banktest.Deposit(\"ugnot\", 50_000_000)\n\tprintln(\"Deposit():\", res)\n\n\t// print main balance after.\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal)\n\n\t// simulate a Render(). banker should have given back all coins.\n\tres = banktest.Render(\"\")\n\tprintln(res)\n}\n\n// Output:\n// main before: 300000000ugnot\n// Deposit(): returned!\n// main after: 250000000ugnot\n// ## recent activity\n//\n// * g1tnpdmvrmtgql8fmxgsq9rwtst5hsxahk3f05dk 100000000ugnot sent, 50000000ugnot returned, at 2009-02-13 11:31pm UTC\n//\n// ## total deposits\n// 50000000ugnot\n"},{"name":"z_1_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\npackage bank1\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/banktest\"\n)\n\nfunc main() {\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\n\t// simulate a Deposit call.\n\tstd.TestSetOriginPkgAddr(banktestAddr)\n\tstd.TestIssueCoins(banktestAddr, std.Coins{{\"ugnot\", 100000000}})\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 100000000}}, nil)\n\tres := banktest.Deposit(\"ugnot\", 101000000)\n\tprintln(res)\n}\n\n// Error:\n// cannot send \"101000000ugnot\", limit \"100000000ugnot\" exceeded with \"\" already spent\n"},{"name":"z_2_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\npackage bank1\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/banktest\"\n)\n\nfunc main() {\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\n\t// print main balance before.\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/bank1\")\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal) // plus OriginSend equals 300.\n\n\t// simulate a Deposit call.\n\tstd.TestSetOriginPkgAddr(banktestAddr)\n\tstd.TestIssueCoins(banktestAddr, std.Coins{{\"ugnot\", 100000000}})\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 100000000}}, nil)\n\tres := banktest.Deposit(\"ugnot\", 55000000)\n\tprintln(\"Deposit():\", res)\n\n\t// print main balance after.\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal) // now 255.\n\n\t// simulate a Render().\n\tres = banktest.Render(\"\")\n\tprintln(res)\n}\n\n// Output:\n// main before: 200000000ugnot\n// Deposit(): returned!\n// main after: 255000000ugnot\n// ## recent activity\n//\n// * g1tnpdmvrmtgql8fmxgsq9rwtst5hsxahk3f05dk 100000000ugnot sent, 55000000ugnot returned, at 2009-02-13 11:31pm UTC\n//\n// ## total deposits\n// 45000000ugnot\n"},{"name":"z_3_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\npackage bank1\n\nimport (\n\t\"std\"\n)\n\nfunc main() {\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/bank1\")\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tsend := std.Coins{{\"ugnot\", 123}}\n\tbanker.SendCoins(banktestAddr, mainaddr, send)\n\n}\n\n// Error:\n// can only send coins from realm that created banker \"g1tnpdmvrmtgql8fmxgsq9rwtst5hsxahk3f05dk\", not \"g1dv3435088tlrgggf745kaud0ptrkc9v42k8llz\"\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":"banktest","path":"gno.land/r/demo/banktest","files":[{"name":"README.md","body":"This is a simple test realm contract that demonstrates how to use the banker.\n\nSee [gno.land/r/demo/banktest/banktest.go](/r/demo/banktest/banktest.go) to see the original contract code.\n\nThis article will go through each line to explain how it works.\n\n```go\npackage banktest\n```\n\nThis package is locally named \"banktest\" (could be anything).\n\n```go\nimport (\n \"std\"\n)\n```\n\nThe \"std\" package is defined by the gno code in stdlibs/std/. \u003c/br\u003e Self explanatory; and you'll see more usage from std later.\n\n```go\ntype activity struct {\n caller std.Address\n sent std.Coins\n returned std.Coins\n time time.Time\n}\n\nfunc (act *activity) String() string {\n return act.caller.String() + \" \" +\n act.sent.String() + \" sent, \" +\n act.returned.String() + \" returned, at \" +\n act.time.Format(\"2006-01-02 3:04pm MST\")\n}\n\nvar latest [10]*activity\n```\n\nThis is just maintaining a list of recent activity to this contract. Notice that the \"latest\" variable is defined \"globally\" within the context of the realm with path \"gno.land/r/demo/banktest\".\n\nThis means that calls to functions defined within this package are encapsulated within this \"data realm\", where the data is mutated based on transactions that can potentially cross many realm and non-realm package boundaries (in the call stack).\n\n```go\n// Deposit will take the coins (to the realm's pkgaddr) or return them to user.\nfunc Deposit(returnDenom string, returnAmount int64) string {\n std.AssertOriginCall()\n caller := std.OriginCaller()\n send := std.Coins{{returnDenom, returnAmount}}\n```\n\nThis is the beginning of the definition of the contract function named \"Deposit\". `std.AssertOriginCall() asserts that this function was called by a gno transactional Message. The caller is the user who signed off on this transactional message. Send is the amount of deposit sent along with this message.\n\n```go\n // record activity\n act := \u0026activity{\n caller: caller,\n sent: std.OriginSend(),\n returned: send,\n time: time.Now(),\n }\n for i := len(latest) - 2; i \u003e= 0; i-- {\n latest[i+1] = latest[i] // shift by +1.\n }\n latest[0] = act\n```\n\nUpdating the \"latest\" array for viewing at gno.land/r/demo/banktest: (w/ trailing colon).\n\n```go\n // return if any.\n if returnAmount \u003e 0 {\n```\n\nIf the user requested the return of coins...\n\n```go\n banker := std.GetBanker(std.BankerTypeOriginSend)\n```\n\nuse a std.Banker instance to return any deposited coins to the original sender.\n\n```go\n pkgaddr := std.GetOriginPkgAddress()\n // TODO: use std.Coins constructors, this isn't generally safe.\n banker.SendCoins(pkgaddr, caller, send)\n return \"returned!\"\n```\n\nNotice that each realm package has an associated Cosmos address.\n\nFinally, the results are rendered via an ABCI query call when you visit [/r/demo/banktest:](/r/demo/banktest:).\n\n```go\nfunc Render(path string) string {\n // get realm coins.\n banker := std.GetBanker(std.BankerTypeReadonly)\n coins := banker.GetCoins(std.GetOriginPkgAddress())\n\n // render\n res := \"\"\n res += \"## recent activity\\n\"\n res += \"\\n\"\n for _, act := range latest {\n if act == nil {\n break\n }\n res += \" * \" + act.String() + \"\\n\"\n }\n res += \"\\n\"\n res += \"## total deposits\\n\"\n res += coins.String()\n return res\n}\n```\n\nYou can call this contract yourself, by vistiing [/r/demo/banktest](/r/demo/banktest) and the [quickstart guide](/r/demo/boards:gnolang/4).\n"},{"name":"banktest.gno","body":"package banktest\n\nimport (\n\t\"std\"\n\t\"time\"\n)\n\ntype activity struct {\n\tcaller std.Address\n\tsent std.Coins\n\treturned std.Coins\n\ttime time.Time\n}\n\nfunc (act *activity) String() string {\n\treturn act.caller.String() + \" \" +\n\t\tact.sent.String() + \" sent, \" +\n\t\tact.returned.String() + \" returned, at \" +\n\t\tact.time.Format(\"2006-01-02 3:04pm MST\")\n}\n\nvar latest [10]*activity\n\n// Deposit will take the coins (to the realm's pkgaddr) or return them to user.\nfunc Deposit(returnDenom string, returnAmount int64) string {\n\tstd.AssertOriginCall()\n\tcaller := std.OriginCaller()\n\tsend := std.Coins{{returnDenom, returnAmount}}\n\t// record activity\n\tact := \u0026activity{\n\t\tcaller: caller,\n\t\tsent: std.OriginSend(),\n\t\treturned: send,\n\t\ttime: time.Now(),\n\t}\n\tfor i := len(latest) - 2; i \u003e= 0; i-- {\n\t\tlatest[i+1] = latest[i] // shift by +1.\n\t}\n\tlatest[0] = act\n\t// return if any.\n\tif returnAmount \u003e 0 {\n\t\tbanker := std.GetBanker(std.BankerTypeOriginSend)\n\t\tpkgaddr := std.GetOriginPkgAddress()\n\t\t// TODO: use std.Coins constructors, this isn't generally safe.\n\t\tbanker.SendCoins(pkgaddr, caller, send)\n\t\treturn \"returned!\"\n\t} else {\n\t\treturn \"thank you!\"\n\t}\n}\n\nfunc Render(path string) string {\n\t// get realm coins.\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tcoins := banker.GetCoins(std.GetOriginPkgAddress())\n\n\t// render\n\tres := \"\"\n\tres += \"## recent activity\\n\"\n\tres += \"\\n\"\n\tfor _, act := range latest {\n\t\tif act == nil {\n\t\t\tbreak\n\t\t}\n\t\tres += \" * \" + act.String() + \"\\n\"\n\t}\n\tres += \"\\n\"\n\tres += \"## total deposits\\n\"\n\tres += coins.String()\n\treturn res\n}\n"},{"name":"z_0_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\n// SEND: 100000000ugnot\n\npackage bank1\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/banktest\"\n)\n\nfunc main() {\n\t// set up main address and banktest addr.\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/bank1\")\n\tstd.TestSetOriginCaller(mainaddr)\n\tstd.TestSetOriginPkgAddress(banktestAddr)\n\n\t// get and print balance of mainaddr.\n\t// with the SEND, + 200 gnot given by the TestContext, main should have 300gnot.\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\t// simulate a Deposit call. use Send + OriginSend to simulate -send.\n\tbanker.SendCoins(mainaddr, banktestAddr, std.Coins{{\"ugnot\", 100_000_000}})\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 100_000_000}}, nil)\n\tres := banktest.Deposit(\"ugnot\", 50_000_000)\n\tprintln(\"Deposit():\", res)\n\n\t// print main balance after.\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal)\n\n\t// simulate a Render(). banker should have given back all coins.\n\tres = banktest.Render(\"\")\n\tprintln(res)\n}\n\n// Output:\n// main before: 300000000ugnot\n// Deposit(): returned!\n// main after: 250000000ugnot\n// ## recent activity\n//\n// * g1tnpdmvrmtgql8fmxgsq9rwtst5hsxahk3f05dk 100000000ugnot sent, 50000000ugnot returned, at 2009-02-13 11:31pm UTC\n//\n// ## total deposits\n// 50000000ugnot\n"},{"name":"z_1_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\npackage bank1\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/banktest\"\n)\n\nfunc main() {\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\n\t// simulate a Deposit call.\n\tstd.TestSetOriginPkgAddress(banktestAddr)\n\tstd.TestIssueCoins(banktestAddr, std.Coins{{\"ugnot\", 100000000}})\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 100000000}}, nil)\n\tres := banktest.Deposit(\"ugnot\", 101000000)\n\tprintln(res)\n}\n\n// Error:\n// cannot send \"101000000ugnot\", limit \"100000000ugnot\" exceeded with \"\" already spent\n"},{"name":"z_2_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\npackage bank1\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/banktest\"\n)\n\nfunc main() {\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\n\t// print main balance before.\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/bank1\")\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal) // plus OriginSend equals 300.\n\n\t// simulate a Deposit call.\n\tstd.TestSetOriginPkgAddress(banktestAddr)\n\tstd.TestIssueCoins(banktestAddr, std.Coins{{\"ugnot\", 100000000}})\n\tstd.TestSetOriginSend(std.Coins{{\"ugnot\", 100000000}}, nil)\n\tres := banktest.Deposit(\"ugnot\", 55000000)\n\tprintln(\"Deposit():\", res)\n\n\t// print main balance after.\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal) // now 255.\n\n\t// simulate a Render().\n\tres = banktest.Render(\"\")\n\tprintln(res)\n}\n\n// Output:\n// main before: 200000000ugnot\n// Deposit(): returned!\n// main after: 255000000ugnot\n// ## recent activity\n//\n// * g1tnpdmvrmtgql8fmxgsq9rwtst5hsxahk3f05dk 100000000ugnot sent, 55000000ugnot returned, at 2009-02-13 11:31pm UTC\n//\n// ## total deposits\n// 45000000ugnot\n"},{"name":"z_3_filetest.gno","body":"// Empty line between the directives is important for them to be parsed\n// independently. :facepalm:\n\n// PKGPATH: gno.land/r/demo/bank1\n\npackage bank1\n\nimport (\n\t\"std\"\n)\n\nfunc main() {\n\tbanktestAddr := std.DerivePkgAddr(\"gno.land/r/demo/banktest\")\n\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/bank1\")\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tsend := std.Coins{{\"ugnot\", 123}}\n\tbanker.SendCoins(banktestAddr, mainaddr, send)\n\n}\n\n// Error:\n// can only send coins from realm that created banker \"g1tnpdmvrmtgql8fmxgsq9rwtst5hsxahk3f05dk\", not \"g1dv3435088tlrgggf745kaud0ptrkc9v42k8llz\"\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":"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)\n\nvar (\n\tbanker *grc20.Banker // private banker.\n\tToken grc20.Token // public safe-object.\n)\n\nfunc init() {\n\tbanker = grc20.NewBanker(\"Bar\", \"BAR\", 4)\n\tToken = banker.Token()\n}\n\nfunc Faucet() string {\n\tcaller := std.PreviousRealm().Address()\n\tif err := banker.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 banker.RenderHome() // XXX: should be 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.TestSetOriginCaller(alice) // XXX: should not need this\n\n\turequire.Equal(t, Token.BalanceOf(alice), uint64(0))\n\turequire.Equal(t, Faucet(), \"OK\")\n\turequire.Equal(t, Token.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":"counter","path":"gno.land/r/demo/counter","files":[{"name":"counter.gno","body":"package counter\n\nimport \"strconv\"\n\nvar counter int\n\nfunc Increment() int {\n\tcounter++\n\treturn counter\n}\n\nfunc Render(_ string) string {\n\treturn strconv.Itoa(counter)\n}\n"},{"name":"counter_test.gno","body":"package counter\n\nimport \"testing\"\n\nfunc TestIncrement(t *testing.T) {\n\tcounter = 0\n\tval := Increment()\n\tif val != 1 {\n\t\tt.Fatalf(\"result from Increment(): %d != 1\", val)\n\t}\n\tif counter != val {\n\t\tt.Fatalf(\"counter (%d) != val (%d)\", counter, val)\n\t}\n}\n\nfunc TestRender(t *testing.T) {\n\tcounter = 1337\n\tres := Render(\"\")\n\tif res != \"1337\" {\n\t\tt.Fatalf(\"render result %q != %q\", res, \"1337\")\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":"govdao","path":"gno.land/r/gov/dao/v2","files":[{"name":"dao.gno","body":"package govdao\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/membstore\"\n\t\"gno.land/p/demo/simpledao\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar (\n\td *simpledao.SimpleDAO // the current active DAO implementation\n\tmembers membstore.MemberStore // the member store\n)\n\nfunc init() {\n\tvar (\n\t\tset = []membstore.Member{\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\"), // Jae\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"), // Manfred\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1e6gxg5tvc55mwsn7t7dymmlasratv7mkv0rap2\"), // Milos\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1jazghxvvgz3egnr2fc8uf72z4g0l03596y9ls7\"), // Nemanja\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1qhskthp2uycmg4zsdc9squ2jds7yv3t0qyrlnp\"), // Petar\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g18amm3fc00t43dcxsys6udug0czyvqt9e7p23rd\"), // Marc\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1dfr24yhk5ztwtqn2a36m8f6ud8cx5hww4dkjfl\"), // Antonio\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g19p3yzr3cuhzqa02j0ce6kzvyjqfzwemw3vam0x\"), // Guilhem\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1mx4pum9976th863jgry4sdjzfwu03qan5w2v9j\"), // Ray\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g127l4gkhk0emwsx5tmxe96sp86c05h8vg5tufzq\"), // Maxwell\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1cpx59z5r8vzeww2fm4ezpz7yvjs7kptywkm864\"), // Morgan\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1ker4vvggvsyatexxn3hkthp2hu80pkhrwmuczr\"), // Sergio\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g18x425qmujg99cfz3q97y4uep5pxjq3z8lmpt25\"), // Antoine\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t// GNO DEVX\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g16tfrrul20g4jzt3z303raqw8vs8s2pqqh5clwu\"), // Ilker\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1hy6zry03hg5d8le9s2w4fxme6236hkgd928dun\"), // Jerónimo\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g15ruzptpql4dpuyzej0wkt5rq6r26kw4nxu9fwd\"), // Denis\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1dnllrdzwfhxv3evyk09y48mgn5phfjvtyrlzm7\"), // Danny\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1778y2yphxs2wpuaflsy5y9qwcd4gttn4g5yjx5\"), // Michelle\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1mq7g0jszdmn4qdpc9tq94w0gyex37su892n80m\"), // Alan\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g197q5e9v00vuz256ly7fq7v3ekaun5cr7wmjgfh\"), // Salvo\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1mpkp5lm8lwpm0pym4388836d009zfe4maxlqsq\"), // Alexis\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g125em6arxsnj49vx35f0n0z34putv5ty3376fg5\"), // Leon\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1whzkakk4hzjkvy60d5pwfk484xu67ar2cl62h2\"), // Kirk\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t// AiB\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1sw5xklxjjuv0yvuxy5f5s3l3mnj0nqq626a9wr\"), // Albert\n\t\t\t\tVotingPower: 20,\n\t\t\t},\n\t\t\t// ONBLOC\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g12vx7dn3dqq89mz550zwunvg4qw6epq73d9csay\"), // Dongwon\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1r04aw56fgvzy859fachr8hzzhqkulkaemltr76\"), // Blake\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g17n4y745s08awwq4e0a38lagsgtntna0749tnxe\"), // Jinwoo\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1ckae7tc5sez8ul3ssne75sk4muwgttp6ks2ky9\"), // ByeongJun\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t\t// TERITORI\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g14u5eaheavy0ux4dmpykg2gvxpvqvexm9cyg58a\"), // Norman\n\t\t\t\tVotingPower: 5,\n\t\t\t},\n\t\t\t// BERTY\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g1qynsu9dwj9lq0m5fkje7jh6qy3md80ztqnshhm\"), // Rémi\n\t\t\t\tVotingPower: 5,\n\t\t\t},\n\t\t\t// FLIPPANDO / ZENTASKTIC\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g17ernafy6ctpcz6uepfsq2js8x2vz0wladh5yc3\"), // Dragos\n\t\t\t\tVotingPower: 5,\n\t\t\t},\n\t\t}\n\t)\n\n\t// Set the member store\n\tmembers = membstore.NewMembStore(membstore.WithInitialMembers(set))\n\n\t// Set the DAO implementation\n\td = simpledao.New(members)\n}\n\n// Propose is designed to be called by another contract or with\n// `maketx run`, not by a `maketx call`.\nfunc Propose(request dao.ProposalRequest) uint64 {\n\tidx, err := d.Propose(request)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn idx\n}\n\n// VoteOnProposal casts a vote for the given proposal\nfunc VoteOnProposal(id uint64, option dao.VoteOption) {\n\tif err := d.VoteOnProposal(id, option); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// ExecuteProposal executes the proposal\nfunc ExecuteProposal(id uint64) {\n\tif err := d.ExecuteProposal(id); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// GetPropStore returns the active proposal store\nfunc GetPropStore() dao.PropStore {\n\treturn d\n}\n\n// GetMembStore returns the active member store\nfunc GetMembStore() membstore.MemberStore {\n\treturn members\n}\n\nfunc Render(path string) string {\n\tif path == \"\" {\n\t\tnumProposals := d.Size()\n\n\t\tif numProposals == 0 {\n\t\t\treturn \"No proposals found :(\" // corner case\n\t\t}\n\n\t\toutput := \"\"\n\n\t\toffset := uint64(0)\n\t\tif numProposals \u003e= 10 {\n\t\t\toffset = uint64(numProposals) - 10\n\t\t}\n\n\t\t// Fetch the last 10 proposals\n\t\tfor idx, prop := range d.Proposals(offset, uint64(10)) {\n\t\t\toutput += ufmt.Sprintf(\n\t\t\t\t\"- [Proposal #%d](%s:%d) - (**%s**)(by %s)\\n\",\n\t\t\t\tidx,\n\t\t\t\t\"/r/gov/dao/v2\",\n\t\t\t\tidx,\n\t\t\t\tprop.Status().String(),\n\t\t\t\tprop.Author().String(),\n\t\t\t)\n\t\t}\n\n\t\treturn output\n\t}\n\n\t// Display the detailed proposal\n\tidx, err := strconv.Atoi(path)\n\tif err != nil {\n\t\treturn \"404: Invalid proposal ID\"\n\t}\n\n\t// Fetch the proposal\n\tprop, err := d.ProposalByID(uint64(idx))\n\tif err != nil {\n\t\treturn ufmt.Sprintf(\"unable to fetch proposal, %s\", err.Error())\n\t}\n\n\t// Render the proposal\n\toutput := \"\"\n\toutput += ufmt.Sprintf(\"# Prop #%d\", idx)\n\toutput += \"\\n\\n\"\n\toutput += prop.Render()\n\toutput += \"\\n\\n\"\n\n\treturn output\n}\n"},{"name":"poc.gno","body":"package govdao\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/combinederr\"\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/membstore\"\n\t\"gno.land/p/gov/executor\"\n)\n\nvar errNoChangesProposed = errors.New(\"no set changes proposed\")\n\n// NewGovDAOExecutor creates the govdao wrapped callback executor\nfunc NewGovDAOExecutor(cb func() error) dao.Executor {\n\tif cb == nil {\n\t\tpanic(errNoChangesProposed)\n\t}\n\n\treturn executor.NewCallbackExecutor(\n\t\tcb,\n\t\tstd.CurrentRealm().PkgPath(),\n\t)\n}\n\n// NewMemberPropExecutor returns the GOVDAO member change executor\nfunc NewMemberPropExecutor(changesFn func() []membstore.Member) dao.Executor {\n\tif changesFn == nil {\n\t\tpanic(errNoChangesProposed)\n\t}\n\n\tcallback := func() error {\n\t\terrs := \u0026combinederr.CombinedError{}\n\t\tcbMembers := changesFn()\n\n\t\tfor _, member := range cbMembers {\n\t\t\tswitch {\n\t\t\tcase !members.IsMember(member.Address):\n\t\t\t\t// Addition request\n\t\t\t\terr := members.AddMember(member)\n\n\t\t\t\terrs.Add(err)\n\t\t\tcase member.VotingPower == 0:\n\t\t\t\t// Remove request\n\t\t\t\terr := members.UpdateMember(member.Address, membstore.Member{\n\t\t\t\t\tAddress: member.Address,\n\t\t\t\t\tVotingPower: 0, // 0 indicated removal\n\t\t\t\t})\n\n\t\t\t\terrs.Add(err)\n\t\t\tdefault:\n\t\t\t\t// Update request\n\t\t\t\terr := members.UpdateMember(member.Address, member)\n\n\t\t\t\terrs.Add(err)\n\t\t\t}\n\t\t}\n\n\t\t// Check if there were any execution errors\n\t\tif errs.Size() == 0 {\n\t\t\treturn nil\n\t\t}\n\n\t\treturn errs\n\t}\n\n\treturn NewGovDAOExecutor(callback)\n}\n\nfunc NewMembStoreImplExecutor(changeFn func() membstore.MemberStore) dao.Executor {\n\tif changeFn == nil {\n\t\tpanic(errNoChangesProposed)\n\t}\n\n\tcallback := func() error {\n\t\tsetMembStoreImpl(changeFn())\n\n\t\treturn nil\n\t}\n\n\treturn NewGovDAOExecutor(callback)\n}\n\n// setMembStoreImpl sets a new dao.MembStore implementation\nfunc setMembStoreImpl(impl membstore.MemberStore) {\n\tif impl == nil {\n\t\tpanic(\"invalid member store\")\n\t}\n\n\tmembers = impl\n}\n"},{"name":"prop1_filetest.gno","body":"// Please note that this package is intended for demonstration purposes only.\n// You could execute this code (the init part) by running a `maketx run` command\n// or by uploading a similar package to a personal namespace.\n//\n// For the specific case of validators, a `r/gnoland/valopers` will be used to\n// organize the lifecycle of validators (register, etc), and this more complex\n// contract will be responsible to generate proposals.\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/dao\"\n\tpVals \"gno.land/p/sys/validators\"\n\tgovdao \"gno.land/r/gov/dao/v2\"\n\tvalidators \"gno.land/r/sys/validators/v2\"\n)\n\nfunc init() {\n\tchangesFn := func() []pVals.Validator {\n\t\treturn []pVals.Validator{\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g12345678\"),\n\t\t\t\tPubKey: \"pubkey\",\n\t\t\t\tVotingPower: 10, // add a new validator\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g000000000\"),\n\t\t\t\tPubKey: \"pubkey\",\n\t\t\t\tVotingPower: 10, // add a new validator\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g000000000\"),\n\t\t\t\tPubKey: \"pubkey\",\n\t\t\t\tVotingPower: 0, // remove an existing validator\n\t\t\t},\n\t\t}\n\t}\n\n\t// Wraps changesFn to emit a certified event only if executed from a\n\t// complete governance proposal process.\n\texecutor := validators.NewPropExecutor(changesFn)\n\n\t// Create a proposal\n\tdescription := \"manual valset changes proposal example\"\n\n\tprop := dao.ProposalRequest{\n\t\tDescription: description,\n\t\tExecutor: executor,\n\t}\n\n\tgovdao.Propose(prop)\n}\n\nfunc main() {\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"\"))\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tgovdao.VoteOnProposal(0, dao.YesVote)\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tprintln(validators.Render(\"\"))\n\tprintln(\"--\")\n\tgovdao.ExecuteProposal(0)\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tprintln(validators.Render(\"\"))\n}\n\n// Output:\n// --\n// - [Proposal #0](/r/gov/dao/v2:0) - (**active**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm)\n//\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// manual valset changes proposal example\n//\n// Status: active\n//\n// Voting stats: YES 0 (0%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 10 (100%)\n//\n// Threshold met: false\n//\n//\n// --\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// manual valset changes proposal example\n//\n// Status: accepted\n//\n// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%)\n//\n// Threshold met: true\n//\n//\n// --\n// No valset changes to apply.\n// --\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// manual valset changes proposal example\n//\n// Status: execution successful\n//\n// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%)\n//\n// Threshold met: true\n//\n//\n// --\n// Valset changes:\n// - #123: g12345678 (10)\n// - #123: g000000000 (10)\n// - #123: g000000000 (0)\n"},{"name":"prop2_filetest.gno","body":"package main\n\nimport (\n\t\"time\"\n\n\t\"gno.land/p/demo/dao\"\n\tgnoblog \"gno.land/r/gnoland/blog\"\n\tgovdao \"gno.land/r/gov/dao/v2\"\n)\n\nfunc init() {\n\tex := gnoblog.NewPostExecutor(\n\t\t\"hello-from-govdao\", // slug\n\t\t\"Hello from GovDAO!\", // title\n\t\t\"This post was published by a GovDAO proposal.\", // body\n\t\ttime.Now().Format(time.RFC3339), // publication date\n\t\t\"moul\", // authors\n\t\t\"govdao,example\", // tags\n\t)\n\n\t// Create a proposal\n\tdescription := \"post a new blogpost about govdao\"\n\n\tprop := dao.ProposalRequest{\n\t\tDescription: description,\n\t\tExecutor: ex,\n\t}\n\n\tgovdao.Propose(prop)\n}\n\nfunc main() {\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"\"))\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tgovdao.VoteOnProposal(0, \"YES\")\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tprintln(gnoblog.Render(\"\"))\n\tprintln(\"--\")\n\tgovdao.ExecuteProposal(0)\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tprintln(gnoblog.Render(\"\"))\n}\n\n// Output:\n// --\n// - [Proposal #0](/r/gov/dao/v2:0) - (**active**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm)\n//\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// post a new blogpost about govdao\n//\n// Status: active\n//\n// Voting stats: YES 0 (0%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 10 (100%)\n//\n// Threshold met: false\n//\n//\n// --\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// post a new blogpost about govdao\n//\n// Status: accepted\n//\n// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%)\n//\n// Threshold met: true\n//\n//\n// --\n// # Gnoland's Blog\n//\n// No posts.\n// --\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// post a new blogpost about govdao\n//\n// Status: execution successful\n//\n// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%)\n//\n// Threshold met: true\n//\n//\n// --\n// # Gnoland's Blog\n//\n// \u003cdiv class='columns-3'\u003e\u003cdiv\u003e\n//\n// ### [Hello from GovDAO!](/r/gnoland/blog:p/hello-from-govdao)\n// 13 Feb 2009\n// \u003c/div\u003e\u003c/div\u003e\n"},{"name":"prop3_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/membstore\"\n\tgovdao \"gno.land/r/gov/dao/v2\"\n)\n\nfunc init() {\n\tmemberFn := func() []membstore.Member {\n\t\treturn []membstore.Member{\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g123\"),\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g456\"),\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAddress: std.Address(\"g789\"),\n\t\t\t\tVotingPower: 10,\n\t\t\t},\n\t\t}\n\t}\n\n\t// Create a proposal\n\tdescription := \"add new members to the govdao\"\n\n\tprop := dao.ProposalRequest{\n\t\tDescription: description,\n\t\tExecutor: govdao.NewMemberPropExecutor(memberFn),\n\t}\n\n\tgovdao.Propose(prop)\n}\n\nfunc main() {\n\tprintln(\"--\")\n\tprintln(govdao.GetMembStore().Size())\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"\"))\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tgovdao.VoteOnProposal(0, \"YES\")\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"\"))\n\tprintln(\"--\")\n\tgovdao.ExecuteProposal(0)\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"0\"))\n\tprintln(\"--\")\n\tprintln(govdao.Render(\"\"))\n\tprintln(\"--\")\n\tprintln(govdao.GetMembStore().Size())\n}\n\n// Output:\n// --\n// 1\n// --\n// - [Proposal #0](/r/gov/dao/v2:0) - (**active**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm)\n//\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// add new members to the govdao\n//\n// Status: active\n//\n// Voting stats: YES 0 (0%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 10 (100%)\n//\n// Threshold met: false\n//\n//\n// --\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// add new members to the govdao\n//\n// Status: accepted\n//\n// Voting stats: YES 10 (100%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 0 (0%)\n//\n// Threshold met: true\n//\n//\n// --\n// - [Proposal #0](/r/gov/dao/v2:0) - (**accepted**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm)\n//\n// --\n// --\n// # Prop #0\n//\n// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm\n//\n// add new members to the govdao\n//\n// Status: execution successful\n//\n// Voting stats: YES 10 (25%), NO 0 (0%), ABSTAIN 0 (0%), MISSING VOTE 30 (75%)\n//\n// Threshold met: false\n//\n//\n// --\n// - [Proposal #0](/r/gov/dao/v2:0) - (**execution successful**)(by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm)\n//\n// --\n// 4\n"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}} @@ -84,7 +84,7 @@ {"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"daoweb","path":"gno.land/r/demo/daoweb","files":[{"name":"daoweb.gno","body":"package daoweb\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/dao\"\n\t\"gno.land/p/demo/json\"\n\t\"gno.land/r/gov/dao/bridge\"\n)\n\n// Proposals returns the paginated GovDAO proposals\nfunc Proposals(offset, count uint64) string {\n\tvar (\n\t\tpropStore = bridge.GovDAO().GetPropStore()\n\t\tsize = propStore.Size()\n\t)\n\n\t// Get the props\n\tprops := propStore.Proposals(offset, count)\n\n\tresp := ProposalsResponse{\n\t\tProposals: make([]Proposal, 0, count),\n\t\tTotal: uint64(size),\n\t}\n\n\tfor _, p := range props {\n\t\tprop := Proposal{\n\t\t\tAuthor: p.Author(),\n\t\t\tDescription: p.Description(),\n\t\t\tStatus: p.Status(),\n\t\t\tStats: p.Stats(),\n\t\t\tIsExpired: p.IsExpired(),\n\t\t}\n\n\t\tresp.Proposals = append(resp.Proposals, prop)\n\t}\n\n\t// Encode the response into JSON\n\tencodedProps, err := json.Marshal(encodeProposalsResponse(resp))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn string(encodedProps)\n}\n\n// ProposalByID fetches the proposal using the given ID\nfunc ProposalByID(id uint64) string {\n\tpropStore := bridge.GovDAO().GetPropStore()\n\n\tp, err := propStore.ProposalByID(id)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Encode the response into JSON\n\tprop := Proposal{\n\t\tAuthor: p.Author(),\n\t\tDescription: p.Description(),\n\t\tStatus: p.Status(),\n\t\tStats: p.Stats(),\n\t\tIsExpired: p.IsExpired(),\n\t}\n\n\tencodedProp, err := json.Marshal(encodeProposal(prop))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn string(encodedProp)\n}\n\n// encodeProposal encodes a proposal into a json node\nfunc encodeProposal(p Proposal) *json.Node {\n\treturn json.ObjectNode(\"\", map[string]*json.Node{\n\t\t\"author\": json.StringNode(\"author\", p.Author.String()),\n\t\t\"description\": json.StringNode(\"description\", p.Description),\n\t\t\"status\": json.StringNode(\"status\", p.Status.String()),\n\t\t\"stats\": json.ObjectNode(\"stats\", map[string]*json.Node{\n\t\t\t\"yay_votes\": json.NumberNode(\"yay_votes\", float64(p.Stats.YayVotes)),\n\t\t\t\"nay_votes\": json.NumberNode(\"nay_votes\", float64(p.Stats.NayVotes)),\n\t\t\t\"abstain_votes\": json.NumberNode(\"abstain_votes\", float64(p.Stats.AbstainVotes)),\n\t\t\t\"total_voting_power\": json.NumberNode(\"total_voting_power\", float64(p.Stats.TotalVotingPower)),\n\t\t}),\n\t\t\"is_expired\": json.BoolNode(\"is_expired\", p.IsExpired),\n\t})\n}\n\n// encodeProposalsResponse encodes a proposal response into a JSON node\nfunc encodeProposalsResponse(props ProposalsResponse) *json.Node {\n\tproposals := make([]*json.Node, 0, len(props.Proposals))\n\n\tfor _, p := range props.Proposals {\n\t\tproposals = append(proposals, encodeProposal(p))\n\t}\n\n\treturn json.ObjectNode(\"\", map[string]*json.Node{\n\t\t\"proposals\": json.ArrayNode(\"proposals\", proposals),\n\t\t\"total\": json.NumberNode(\"total\", float64(props.Total)),\n\t})\n}\n\n// ProposalsResponse is a paginated proposal response\ntype ProposalsResponse struct {\n\tProposals []Proposal `json:\"proposals\"`\n\tTotal uint64 `json:\"total\"`\n}\n\n// Proposal is a single GovDAO proposal\ntype Proposal struct {\n\tAuthor std.Address `json:\"author\"`\n\tDescription string `json:\"description\"`\n\tStatus dao.ProposalStatus `json:\"status\"`\n\tStats dao.Stats `json:\"stats\"`\n\tIsExpired bool `json:\"is_expired\"`\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":"deep","path":"gno.land/r/demo/deep/very/deep","files":[{"name":"render.gno","body":"package deep\n\nfunc Render(path string) string {\n\tif path == \"\" {\n\t\treturn \"it works!\"\n\t} else {\n\t\treturn \"hi \" + path\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)\n\nvar instances avl.Tree // symbol -\u003e instance\n\nfunc New(name, symbol string, decimals uint, initialMint, faucet uint64) {\n\tcaller := std.PreviousRealm().Address()\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\tbanker := grc20.NewBanker(name, symbol, decimals)\n\tif initialMint \u003e 0 {\n\t\tbanker.Mint(admin, initialMint)\n\t}\n\n\tinst := instance{\n\t\tbanker: banker,\n\t\tadmin: ownable.NewWithAddress(admin),\n\t\tfaucet: faucet,\n\t}\n\n\tinstances.Set(symbol, \u0026inst)\n}\n\ntype instance struct {\n\tbanker *grc20.Banker\n\tadmin *ownable.Ownable\n\tfaucet uint64 // per-request amount. disabled if 0.\n}\n\nfunc (inst instance) Token() grc20.Token { return inst.banker.Token() }\n\nfunc TotalSupply(symbol string) uint64 {\n\tinst := mustGetInstance(symbol)\n\treturn inst.Token().TotalSupply()\n}\n\nfunc BalanceOf(symbol string, owner std.Address) uint64 {\n\tinst := mustGetInstance(symbol)\n\treturn inst.Token().BalanceOf(owner)\n}\n\nfunc Allowance(symbol string, owner, spender std.Address) uint64 {\n\tinst := mustGetInstance(symbol)\n\treturn inst.Token().Allowance(owner, spender)\n}\n\nfunc Transfer(symbol string, to std.Address, amount uint64) {\n\tinst := mustGetInstance(symbol)\n\tcheckErr(inst.Token().Transfer(to, amount))\n}\n\nfunc Approve(symbol string, spender std.Address, amount uint64) {\n\tinst := mustGetInstance(symbol)\n\tcheckErr(inst.Token().Approve(spender, amount))\n}\n\nfunc TransferFrom(symbol string, from, to std.Address, amount uint64) {\n\tinst := mustGetInstance(symbol)\n\tcheckErr(inst.Token().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.PreviousRealm().Address()\n\tcheckErr(inst.banker.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.banker.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.banker.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.banker.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().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)\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/uassert\"\n)\n\nfunc TestReadOnlyPublicMethods(t *testing.T) {\n\tadmin := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\tmanfred := std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\")\n\tunknown := std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\") // valid but never used.\n\tNewWithAdmin(\"Foo\", \"FOO\", 4, 10_000*1_000_000, 0, admin)\n\tNewWithAdmin(\"Bar\", \"BAR\", 4, 10_000*1_000, 0, admin)\n\tmustGetInstance(\"FOO\").banker.Mint(manfred, 100_000_000)\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_100_000_000, func() uint64 { return TotalSupply(\"FOO\") }},\n\t\t\t{\"BalanceOf(admin)\", 10_000_000_000, func() uint64 { return BalanceOf(\"FOO\", admin) }},\n\t\t\t{\"BalanceOf(manfred)\", 100_000_000, func() uint64 { return BalanceOf(\"FOO\", manfred) }},\n\t\t\t{\"Allowance(admin, manfred)\", 0, func() uint64 { return Allowance(\"FOO\", admin, manfred) }},\n\t\t\t{\"BalanceOf(unknown)\", 0, func() uint64 { return BalanceOf(\"FOO\", unknown) }},\n\t\t}\n\t\tfor _, tc := range tests {\n\t\t\tuassert.Equal(t, tc.balance, tc.fn(), \"balance does not match\")\n\t\t}\n\t}\n\treturn\n\n\t// unknown uses the faucet.\n\tstd.TestSetOriginCaller(unknown)\n\tFaucet(\"FOO\")\n\n\t// check balances #2.\n\t{\n\t\ttests := []test{\n\t\t\t{\"TotalSupply\", 10_110_000_000, func() uint64 { return TotalSupply(\"FOO\") }},\n\t\t\t{\"BalanceOf(admin)\", 10_000_000_000, func() uint64 { return BalanceOf(\"FOO\", admin) }},\n\t\t\t{\"BalanceOf(manfred)\", 100_000_000, func() uint64 { return BalanceOf(\"FOO\", manfred) }},\n\t\t\t{\"Allowance(admin, manfred)\", 0, func() uint64 { return Allowance(\"FOO\", admin, manfred) }},\n\t\t\t{\"BalanceOf(unknown)\", 10_000_000, func() uint64 { return BalanceOf(\"FOO\", unknown) }},\n\t\t}\n\t\tfor _, tc := range tests {\n\t\t\tuassert.Equal(t, tc.balance, tc.fn(), \"balance does not match\")\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":"disperse","path":"gno.land/r/demo/disperse","files":[{"name":"disperse.gno","body":"package disperse\n\nimport (\n\t\"std\"\n\n\ttokens \"gno.land/r/demo/grc20factory\"\n)\n\n// Get address of Disperse realm\nvar realmAddr = std.CurrentRealm().Address()\n\n// DisperseUgnot parses receivers and amounts and sends out ugnot\n// The function will send out the coins to the addresses and return the leftover coins to the caller\n// if there are any to return\nfunc DisperseUgnot(addresses []std.Address, coins std.Coins) {\n\tcoinSent := std.OriginSend()\n\tcaller := std.PreviousRealm().Address()\n\tbanker := std.GetBanker(std.BankerTypeOriginSend)\n\n\tif len(addresses) != len(coins) {\n\t\tpanic(ErrNumAddrValMismatch)\n\t}\n\n\tfor _, coin := range coins {\n\t\tif coin.Amount \u003c= 0 {\n\t\t\tpanic(ErrNegativeCoinAmount)\n\t\t}\n\n\t\tif banker.GetCoins(realmAddr).AmountOf(coin.Denom) \u003c coin.Amount {\n\t\t\tpanic(ErrMismatchBetweenSentAndParams)\n\t\t}\n\t}\n\n\t// Send coins\n\tfor i, _ := range addresses {\n\t\tbanker.SendCoins(realmAddr, addresses[i], std.NewCoins(coins[i]))\n\t}\n\n\t// Return possible leftover coins\n\tfor _, coin := range coinSent {\n\t\tleftoverAmt := banker.GetCoins(realmAddr).AmountOf(coin.Denom)\n\t\tif leftoverAmt \u003e 0 {\n\t\t\tsend := std.Coins{std.NewCoin(coin.Denom, leftoverAmt)}\n\t\t\tbanker.SendCoins(realmAddr, caller, send)\n\t\t}\n\t}\n}\n\n// DisperseGRC20 disperses tokens to multiple addresses\n// Note that it is necessary to approve the realm to spend the tokens before calling this function\n// see the corresponding filetests for examples\nfunc DisperseGRC20(addresses []std.Address, amounts []uint64, symbols []string) {\n\tcaller := std.PreviousRealm().Address()\n\n\tif (len(addresses) != len(amounts)) || (len(amounts) != len(symbols)) {\n\t\tpanic(ErrArgLenAndSentLenMismatch)\n\t}\n\n\tfor i := 0; i \u003c len(addresses); i++ {\n\t\ttokens.TransferFrom(symbols[i], caller, addresses[i], amounts[i])\n\t}\n}\n\n// DisperseGRC20String receives a string of addresses and a string of tokens\n// and parses them to be used in DisperseGRC20\nfunc DisperseGRC20String(addresses string, tokens string) {\n\tparsedAddresses, err := parseAddresses(addresses)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tparsedAmounts, parsedSymbols, err := parseTokens(tokens)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tDisperseGRC20(parsedAddresses, parsedAmounts, parsedSymbols)\n}\n\n// DisperseUgnotString receives a string of addresses and a string of amounts\n// and parses them to be used in DisperseUgnot\nfunc DisperseUgnotString(addresses string, amounts string) {\n\tparsedAddresses, err := parseAddresses(addresses)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tparsedAmounts, err := parseAmounts(amounts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tcoins := make(std.Coins, len(parsedAmounts))\n\tfor i, amount := range parsedAmounts {\n\t\tcoins[i] = std.NewCoin(\"ugnot\", amount)\n\t}\n\n\tDisperseUgnot(parsedAddresses, coins)\n}\n"},{"name":"doc.gno","body":"// Package disperse provides methods to disperse coins or GRC20 tokens among multiple addresses.\n//\n// The disperse package is an implementation of an existing service that allows users to send coins or GRC20 tokens to multiple addresses\n// on the Ethereum blockchain.\n//\n// Usage:\n// To use disperse, you can either use `DisperseUgnot` to send coins or `DisperseGRC20` to send GRC20 tokens to multiple addresses.\n//\n// Example:\n// Dispersing 200 coins to two addresses:\n// - DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n// Dispersing 200 worth of a GRC20 token \"TEST\" to two addresses:\n// - DisperseGRC20String(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150TEST,50TEST\")\n//\n// Reference:\n// - [the original dispere app](https://disperse.app/)\n// - [the original disperse app on etherscan](https://etherscan.io/address/0xd152f549545093347a162dce210e7293f1452150#code)\n// - [the gno disperse web app](https://gno-disperse.netlify.app/)\npackage disperse // import \"gno.land/r/demo/disperse\"\n"},{"name":"errors.gno","body":"package disperse\n\nimport \"errors\"\n\nvar (\n\tErrNotEnoughCoin = errors.New(\"disperse: not enough coin sent in\")\n\tErrNumAddrValMismatch = errors.New(\"disperse: number of addresses and values to send doesn't match\")\n\tErrInvalidAddress = errors.New(\"disperse: invalid address\")\n\tErrNegativeCoinAmount = errors.New(\"disperse: coin amount cannot be negative\")\n\tErrMismatchBetweenSentAndParams = errors.New(\"disperse: mismatch between coins sent and params called\")\n\tErrArgLenAndSentLenMismatch = errors.New(\"disperse: mismatch between coins sent and args called\")\n)\n"},{"name":"util.gno","body":"package disperse\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"unicode\"\n)\n\nfunc parseAddresses(addresses string) ([]std.Address, error) {\n\tvar ret []std.Address\n\n\tfor _, str := range strings.Split(addresses, \",\") {\n\t\taddr := std.Address(str)\n\t\tif !addr.IsValid() {\n\t\t\treturn nil, ErrInvalidAddress\n\t\t}\n\n\t\tret = append(ret, addr)\n\t}\n\n\treturn ret, nil\n}\n\nfunc splitString(input string) (string, string) {\n\tvar pos int\n\tfor i, char := range input {\n\t\tif !unicode.IsDigit(char) {\n\t\t\tpos = i\n\t\t\tbreak\n\t\t}\n\t}\n\treturn input[:pos], input[pos:]\n}\n\nfunc parseTokens(tokens string) ([]uint64, []string, error) {\n\tvar amounts []uint64\n\tvar symbols []string\n\n\tfor _, token := range strings.Split(tokens, \",\") {\n\t\tamountStr, symbol := splitString(token)\n\t\tamount, _ := strconv.Atoi(amountStr)\n\t\tif amount \u003c 0 {\n\t\t\treturn nil, nil, ErrNegativeCoinAmount\n\t\t}\n\n\t\tamounts = append(amounts, uint64(amount))\n\t\tsymbols = append(symbols, symbol)\n\t}\n\n\treturn amounts, symbols, nil\n}\n\nfunc parseAmounts(amounts string) ([]int64, error) {\n\tvar ret []int64\n\n\tfor _, amt := range strings.Split(amounts, \",\") {\n\t\tamount, _ := strconv.Atoi(amt)\n\t\tif amount \u003c 0 {\n\t\t\treturn nil, ErrNegativeCoinAmount\n\t\t}\n\n\t\tret = append(ret, int64(amount))\n\t}\n\n\treturn ret, nil\n}\n"},{"name":"z_0_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 200ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\n\tstd.TestSetOriginPkgAddr(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\tbanker.SendCoins(mainaddr, disperseAddr, std.Coins{{\"ugnot\", 200}})\n\tdisperse.DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal)\n}\n\n// Output:\n// main before: 200000200ugnot\n// main after: 200000000ugnot\n"},{"name":"z_1_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\n\tstd.TestSetOriginPkgAddr(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\tbanker.SendCoins(mainaddr, disperseAddr, std.Coins{{\"ugnot\", 300}})\n\tdisperse.DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal)\n}\n\n// Output:\n// main before: 200000300ugnot\n// main after: 200000100ugnot\n"},{"name":"z_2_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\n\tstd.TestSetOriginPkgAddr(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\tbanker.SendCoins(mainaddr, disperseAddr, std.Coins{{\"ugnot\", 100}})\n\tdisperse.DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n}\n\n// Error:\n// disperse: mismatch between coins sent and params called\n"},{"name":"z_3_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n\ttokens \"gno.land/r/demo/grc20factory\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\tbeneficiary1 := std.Address(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0\")\n\tbeneficiary2 := std.Address(\"g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\")\n\n\tstd.TestSetOriginPkgAddr(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\ttokens.New(\"test\", \"TEST\", 4, 0, 0)\n\ttokens.Mint(\"TEST\", mainaddr, 200)\n\n\tmainbal := tokens.BalanceOf(\"TEST\", mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\ttokens.Approve(\"TEST\", disperseAddr, 200)\n\n\tdisperse.DisperseGRC20String(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150TEST,50TEST\")\n\n\tmainbal = tokens.BalanceOf(\"TEST\", mainaddr)\n\tprintln(\"main after:\", mainbal)\n\tben1bal := tokens.BalanceOf(\"TEST\", beneficiary1)\n\tprintln(\"beneficiary1:\", ben1bal)\n\tben2bal := tokens.BalanceOf(\"TEST\", beneficiary2)\n\tprintln(\"beneficiary2:\", ben2bal)\n}\n\n// Output:\n// main before: 200\n// main after: 0\n// beneficiary1: 150\n// beneficiary2: 50\n"},{"name":"z_4_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n\ttokens \"gno.land/r/demo/grc20factory\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\tbeneficiary1 := std.Address(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0\")\n\tbeneficiary2 := std.Address(\"g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\")\n\n\tstd.TestSetOriginPkgAddr(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\ttokens.New(\"test1\", \"TEST1\", 4, 0, 0)\n\ttokens.Mint(\"TEST1\", mainaddr, 200)\n\ttokens.New(\"test2\", \"TEST2\", 4, 0, 0)\n\ttokens.Mint(\"TEST2\", mainaddr, 200)\n\n\tmainbal := tokens.BalanceOf(\"TEST1\", mainaddr) + tokens.BalanceOf(\"TEST2\", mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\ttokens.Approve(\"TEST1\", disperseAddr, 200)\n\ttokens.Approve(\"TEST2\", disperseAddr, 200)\n\n\tdisperse.DisperseGRC20String(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"200TEST1,200TEST2\")\n\n\tmainbal = tokens.BalanceOf(\"TEST1\", mainaddr) + tokens.BalanceOf(\"TEST2\", mainaddr)\n\tprintln(\"main after:\", mainbal)\n\tben1bal := tokens.BalanceOf(\"TEST1\", beneficiary1) + tokens.BalanceOf(\"TEST2\", beneficiary1)\n\tprintln(\"beneficiary1:\", ben1bal)\n\tben2bal := tokens.BalanceOf(\"TEST1\", beneficiary2) + tokens.BalanceOf(\"TEST2\", beneficiary2)\n\tprintln(\"beneficiary2:\", ben2bal)\n}\n\n// Output:\n// main before: 400\n// main after: 0\n// beneficiary1: 200\n// beneficiary2: 200\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":"disperse","path":"gno.land/r/demo/disperse","files":[{"name":"disperse.gno","body":"package disperse\n\nimport (\n\t\"std\"\n\n\ttokens \"gno.land/r/demo/grc20factory\"\n)\n\n// Get address of Disperse realm\nvar realmAddr = std.CurrentRealm().Address()\n\n// DisperseUgnot parses receivers and amounts and sends out ugnot\n// The function will send out the coins to the addresses and return the leftover coins to the caller\n// if there are any to return\nfunc DisperseUgnot(addresses []std.Address, coins std.Coins) {\n\tcoinSent := std.OriginSend()\n\tcaller := std.PreviousRealm().Address()\n\tbanker := std.GetBanker(std.BankerTypeOriginSend)\n\n\tif len(addresses) != len(coins) {\n\t\tpanic(ErrNumAddrValMismatch)\n\t}\n\n\tfor _, coin := range coins {\n\t\tif coin.Amount \u003c= 0 {\n\t\t\tpanic(ErrNegativeCoinAmount)\n\t\t}\n\n\t\tif banker.GetCoins(realmAddr).AmountOf(coin.Denom) \u003c coin.Amount {\n\t\t\tpanic(ErrMismatchBetweenSentAndParams)\n\t\t}\n\t}\n\n\t// Send coins\n\tfor i, _ := range addresses {\n\t\tbanker.SendCoins(realmAddr, addresses[i], std.NewCoins(coins[i]))\n\t}\n\n\t// Return possible leftover coins\n\tfor _, coin := range coinSent {\n\t\tleftoverAmt := banker.GetCoins(realmAddr).AmountOf(coin.Denom)\n\t\tif leftoverAmt \u003e 0 {\n\t\t\tsend := std.Coins{std.NewCoin(coin.Denom, leftoverAmt)}\n\t\t\tbanker.SendCoins(realmAddr, caller, send)\n\t\t}\n\t}\n}\n\n// DisperseGRC20 disperses tokens to multiple addresses\n// Note that it is necessary to approve the realm to spend the tokens before calling this function\n// see the corresponding filetests for examples\nfunc DisperseGRC20(addresses []std.Address, amounts []uint64, symbols []string) {\n\tcaller := std.PreviousRealm().Address()\n\n\tif (len(addresses) != len(amounts)) || (len(amounts) != len(symbols)) {\n\t\tpanic(ErrArgLenAndSentLenMismatch)\n\t}\n\n\tfor i := 0; i \u003c len(addresses); i++ {\n\t\ttokens.TransferFrom(symbols[i], caller, addresses[i], amounts[i])\n\t}\n}\n\n// DisperseGRC20String receives a string of addresses and a string of tokens\n// and parses them to be used in DisperseGRC20\nfunc DisperseGRC20String(addresses string, tokens string) {\n\tparsedAddresses, err := parseAddresses(addresses)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tparsedAmounts, parsedSymbols, err := parseTokens(tokens)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tDisperseGRC20(parsedAddresses, parsedAmounts, parsedSymbols)\n}\n\n// DisperseUgnotString receives a string of addresses and a string of amounts\n// and parses them to be used in DisperseUgnot\nfunc DisperseUgnotString(addresses string, amounts string) {\n\tparsedAddresses, err := parseAddresses(addresses)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tparsedAmounts, err := parseAmounts(amounts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tcoins := make(std.Coins, len(parsedAmounts))\n\tfor i, amount := range parsedAmounts {\n\t\tcoins[i] = std.NewCoin(\"ugnot\", amount)\n\t}\n\n\tDisperseUgnot(parsedAddresses, coins)\n}\n"},{"name":"doc.gno","body":"// Package disperse provides methods to disperse coins or GRC20 tokens among multiple addresses.\n//\n// The disperse package is an implementation of an existing service that allows users to send coins or GRC20 tokens to multiple addresses\n// on the Ethereum blockchain.\n//\n// Usage:\n// To use disperse, you can either use `DisperseUgnot` to send coins or `DisperseGRC20` to send GRC20 tokens to multiple addresses.\n//\n// Example:\n// Dispersing 200 coins to two addresses:\n// - DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n// Dispersing 200 worth of a GRC20 token \"TEST\" to two addresses:\n// - DisperseGRC20String(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150TEST,50TEST\")\n//\n// Reference:\n// - [the original dispere app](https://disperse.app/)\n// - [the original disperse app on etherscan](https://etherscan.io/address/0xd152f549545093347a162dce210e7293f1452150#code)\n// - [the gno disperse web app](https://gno-disperse.netlify.app/)\npackage disperse // import \"gno.land/r/demo/disperse\"\n"},{"name":"errors.gno","body":"package disperse\n\nimport \"errors\"\n\nvar (\n\tErrNotEnoughCoin = errors.New(\"disperse: not enough coin sent in\")\n\tErrNumAddrValMismatch = errors.New(\"disperse: number of addresses and values to send doesn't match\")\n\tErrInvalidAddress = errors.New(\"disperse: invalid address\")\n\tErrNegativeCoinAmount = errors.New(\"disperse: coin amount cannot be negative\")\n\tErrMismatchBetweenSentAndParams = errors.New(\"disperse: mismatch between coins sent and params called\")\n\tErrArgLenAndSentLenMismatch = errors.New(\"disperse: mismatch between coins sent and args called\")\n)\n"},{"name":"util.gno","body":"package disperse\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"unicode\"\n)\n\nfunc parseAddresses(addresses string) ([]std.Address, error) {\n\tvar ret []std.Address\n\n\tfor _, str := range strings.Split(addresses, \",\") {\n\t\taddr := std.Address(str)\n\t\tif !addr.IsValid() {\n\t\t\treturn nil, ErrInvalidAddress\n\t\t}\n\n\t\tret = append(ret, addr)\n\t}\n\n\treturn ret, nil\n}\n\nfunc splitString(input string) (string, string) {\n\tvar pos int\n\tfor i, char := range input {\n\t\tif !unicode.IsDigit(char) {\n\t\t\tpos = i\n\t\t\tbreak\n\t\t}\n\t}\n\treturn input[:pos], input[pos:]\n}\n\nfunc parseTokens(tokens string) ([]uint64, []string, error) {\n\tvar amounts []uint64\n\tvar symbols []string\n\n\tfor _, token := range strings.Split(tokens, \",\") {\n\t\tamountStr, symbol := splitString(token)\n\t\tamount, _ := strconv.Atoi(amountStr)\n\t\tif amount \u003c 0 {\n\t\t\treturn nil, nil, ErrNegativeCoinAmount\n\t\t}\n\n\t\tamounts = append(amounts, uint64(amount))\n\t\tsymbols = append(symbols, symbol)\n\t}\n\n\treturn amounts, symbols, nil\n}\n\nfunc parseAmounts(amounts string) ([]int64, error) {\n\tvar ret []int64\n\n\tfor _, amt := range strings.Split(amounts, \",\") {\n\t\tamount, _ := strconv.Atoi(amt)\n\t\tif amount \u003c 0 {\n\t\t\treturn nil, ErrNegativeCoinAmount\n\t\t}\n\n\t\tret = append(ret, int64(amount))\n\t}\n\n\treturn ret, nil\n}\n"},{"name":"z_0_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 200ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\n\tstd.TestSetOriginPkgAddress(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\tbanker.SendCoins(mainaddr, disperseAddr, std.Coins{{\"ugnot\", 200}})\n\tdisperse.DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal)\n}\n\n// Output:\n// main before: 200000200ugnot\n// main after: 200000000ugnot\n"},{"name":"z_1_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\n\tstd.TestSetOriginPkgAddress(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\tmainbal := banker.GetCoins(mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\tbanker.SendCoins(mainaddr, disperseAddr, std.Coins{{\"ugnot\", 300}})\n\tdisperse.DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n\n\tmainbal = banker.GetCoins(mainaddr)\n\tprintln(\"main after:\", mainbal)\n}\n\n// Output:\n// main before: 200000300ugnot\n// main after: 200000100ugnot\n"},{"name":"z_2_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\n\tstd.TestSetOriginPkgAddress(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\tbanker.SendCoins(mainaddr, disperseAddr, std.Coins{{\"ugnot\", 100}})\n\tdisperse.DisperseUgnotString(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150,50\")\n}\n\n// Error:\n// disperse: mismatch between coins sent and params called\n"},{"name":"z_3_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n\ttokens \"gno.land/r/demo/grc20factory\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\tbeneficiary1 := std.Address(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0\")\n\tbeneficiary2 := std.Address(\"g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\")\n\n\tstd.TestSetOriginPkgAddress(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\ttokens.New(\"test\", \"TEST\", 4, 0, 0)\n\ttokens.Mint(\"TEST\", mainaddr, 200)\n\n\tmainbal := tokens.BalanceOf(\"TEST\", mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\ttokens.Approve(\"TEST\", disperseAddr, 200)\n\n\tdisperse.DisperseGRC20String(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"150TEST,50TEST\")\n\n\tmainbal = tokens.BalanceOf(\"TEST\", mainaddr)\n\tprintln(\"main after:\", mainbal)\n\tben1bal := tokens.BalanceOf(\"TEST\", beneficiary1)\n\tprintln(\"beneficiary1:\", ben1bal)\n\tben2bal := tokens.BalanceOf(\"TEST\", beneficiary2)\n\tprintln(\"beneficiary2:\", ben2bal)\n}\n\n// Output:\n// main before: 200\n// main after: 0\n// beneficiary1: 150\n// beneficiary2: 50\n"},{"name":"z_4_filetest.gno","body":"// PKGPATH: gno.land/r/demo/main\n\n// SEND: 300ugnot\n\npackage main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/demo/disperse\"\n\ttokens \"gno.land/r/demo/grc20factory\"\n)\n\nfunc main() {\n\tdisperseAddr := std.DerivePkgAddr(\"gno.land/r/demo/disperse\")\n\tmainaddr := std.DerivePkgAddr(\"gno.land/r/demo/main\")\n\tbeneficiary1 := std.Address(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0\")\n\tbeneficiary2 := std.Address(\"g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\")\n\n\tstd.TestSetOriginPkgAddress(disperseAddr)\n\tstd.TestSetOriginCaller(mainaddr)\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\ttokens.New(\"test1\", \"TEST1\", 4, 0, 0)\n\ttokens.Mint(\"TEST1\", mainaddr, 200)\n\ttokens.New(\"test2\", \"TEST2\", 4, 0, 0)\n\ttokens.Mint(\"TEST2\", mainaddr, 200)\n\n\tmainbal := tokens.BalanceOf(\"TEST1\", mainaddr) + tokens.BalanceOf(\"TEST2\", mainaddr)\n\tprintln(\"main before:\", mainbal)\n\n\ttokens.Approve(\"TEST1\", disperseAddr, 200)\n\ttokens.Approve(\"TEST2\", disperseAddr, 200)\n\n\tdisperse.DisperseGRC20String(\"g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c\", \"200TEST1,200TEST2\")\n\n\tmainbal = tokens.BalanceOf(\"TEST1\", mainaddr) + tokens.BalanceOf(\"TEST2\", mainaddr)\n\tprintln(\"main after:\", mainbal)\n\tben1bal := tokens.BalanceOf(\"TEST1\", beneficiary1) + tokens.BalanceOf(\"TEST2\", beneficiary1)\n\tprintln(\"beneficiary1:\", ben1bal)\n\tben2bal := tokens.BalanceOf(\"TEST1\", beneficiary2) + tokens.BalanceOf(\"TEST2\", beneficiary2)\n\tprintln(\"beneficiary2:\", ben2bal)\n}\n\n// Output:\n// main before: 400\n// main after: 0\n// beneficiary1: 200\n// beneficiary2: 200\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":"echo","path":"gno.land/r/demo/echo","files":[{"name":"echo.gno","body":"package echo\n\n/*\n * This realm echoes the `path` argument it received.\n * Can be used by developers as a simple endpoint to test\n * forbidden characters, for pentesting or simply to\n * test it works.\n *\n * See also r/demo/print (to print various thing like user address)\n */\nfunc Render(path string) string {\n\treturn path\n}\n"},{"name":"echo_test.gno","body":"package echo\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/urequire\"\n)\n\nfunc Test(t *testing.T) {\n\turequire.Equal(t, \"aa\", Render(\"aa\"))\n\turequire.Equal(t, \"\", Render(\"\"))\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":"event","path":"gno.land/r/demo/event","files":[{"name":"event.gno","body":"package event\n\nimport (\n\t\"std\"\n)\n\nfunc Emit(value string) {\n\tstd.Emit(\"TAG\", \"key\", value)\n}\n"},{"name":"z1_filetest.gno","body":"package main\n\nimport \"gno.land/r/demo/event\"\n\nfunc main() {\n\tevent.Emit(\"foo\")\n\tevent.Emit(\"bar\")\n}\n\n// Events:\n// [\n// {\n// \"type\": \"TAG\",\n// \"attrs\": [\n// {\n// \"key\": \"key\",\n// \"value\": \"foo\"\n// }\n// ],\n// \"pkg_path\": \"gno.land/r/demo/event\",\n// \"func\": \"Emit\"\n// },\n// {\n// \"type\": \"TAG\",\n// \"attrs\": [\n// {\n// \"key\": \"key\",\n// \"value\": \"bar\"\n// }\n// ],\n// \"pkg_path\": \"gno.land/r/demo/event\",\n// \"func\": \"Emit\"\n// }\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":"foo1155","path":"gno.land/r/demo/foo1155","files":[{"name":"foo1155.gno","body":"package foo1155\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/grc/grc1155\"\n\t\"gno.land/p/demo/ufmt\"\n\t\"gno.land/r/demo/users\"\n\n\tpusers \"gno.land/p/demo/users\"\n)\n\nvar (\n\tdummyURI = \"ipfs://xyz\"\n\tadmin std.Address = \"g10x5phu0k6p64cwrhfpsc8tk43st9kug6wft530\"\n\tfoo = grc1155.NewBasicGRC1155Token(dummyURI)\n)\n\nfunc init() {\n\tmintGRC1155Token(admin) // @administrator (10)\n}\n\nfunc mintGRC1155Token(owner std.Address) {\n\tfor i := 1; i \u003c= 10; i++ {\n\t\ttid := grc1155.TokenID(ufmt.Sprintf(\"%d\", i))\n\t\tfoo.SafeMint(owner, tid, 100)\n\t}\n}\n\n// Getters\n\nfunc BalanceOf(user pusers.AddressOrName, tid grc1155.TokenID) uint64 {\n\tbalance, err := foo.BalanceOf(users.Resolve(user), tid)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn balance\n}\n\nfunc BalanceOfBatch(ul []pusers.AddressOrName, batch []grc1155.TokenID) []uint64 {\n\tvar usersResolved []std.Address\n\n\tfor i := 0; i \u003c len(ul); i++ {\n\t\tusersResolved[i] = users.Resolve(ul[i])\n\t}\n\tbalanceBatch, err := foo.BalanceOfBatch(usersResolved, batch)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn balanceBatch\n}\n\nfunc IsApprovedForAll(owner, user pusers.AddressOrName) bool {\n\treturn foo.IsApprovedForAll(users.Resolve(owner), users.Resolve(user))\n}\n\n// Setters\n\nfunc SetApprovalForAll(user pusers.AddressOrName, approved bool) {\n\terr := foo.SetApprovalForAll(users.Resolve(user), approved)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc TransferFrom(from, to pusers.AddressOrName, tid grc1155.TokenID, amount uint64) {\n\terr := foo.SafeTransferFrom(users.Resolve(from), users.Resolve(to), tid, amount)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc BatchTransferFrom(from, to pusers.AddressOrName, batch []grc1155.TokenID, amounts []uint64) {\n\terr := foo.SafeBatchTransferFrom(users.Resolve(from), users.Resolve(to), batch, amounts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// Admin\n\nfunc Mint(to pusers.AddressOrName, tid grc1155.TokenID, amount uint64) {\n\tcaller := std.OriginCaller()\n\tassertIsAdmin(caller)\n\terr := foo.SafeMint(users.Resolve(to), tid, amount)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc MintBatch(to pusers.AddressOrName, batch []grc1155.TokenID, amounts []uint64) {\n\tcaller := std.OriginCaller()\n\tassertIsAdmin(caller)\n\terr := foo.SafeBatchMint(users.Resolve(to), batch, amounts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc Burn(from pusers.AddressOrName, tid grc1155.TokenID, amount uint64) {\n\tcaller := std.OriginCaller()\n\tassertIsAdmin(caller)\n\terr := foo.Burn(users.Resolve(from), tid, amount)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc BurnBatch(from pusers.AddressOrName, batch []grc1155.TokenID, amounts []uint64) {\n\tcaller := std.OriginCaller()\n\tassertIsAdmin(caller)\n\terr := foo.BatchBurn(users.Resolve(from), batch, amounts)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// Render\n\nfunc Render(path string) string {\n\tswitch {\n\tcase path == \"\":\n\t\treturn foo.RenderHome()\n\tdefault:\n\t\treturn \"404\\n\"\n\t}\n}\n\n// Util\n\nfunc assertIsAdmin(address std.Address) {\n\tif address != admin {\n\t\tpanic(\"restricted access\")\n\t}\n}\n"},{"name":"foo1155_test.gno","body":"package foo1155\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/grc/grc1155\"\n\t\"gno.land/p/demo/users\"\n)\n\nfunc TestFoo721(t *testing.T) {\n\tadmin := users.AddressOrName(\"g10x5phu0k6p64cwrhfpsc8tk43st9kug6wft530\")\n\tbob := users.AddressOrName(\"g1ze6et22ces5atv79y4xh38s4kuraey4y2fr6tw\")\n\ttid1 := grc1155.TokenID(\"1\")\n\ttid2 := grc1155.TokenID(\"2\")\n\n\tfor i, tc := range []struct {\n\t\tname string\n\t\texpected interface{}\n\t\tfn func() interface{}\n\t}{\n\t\t{\"BalanceOf(admin, tid1)\", uint64(100), func() interface{} { return BalanceOf(admin, tid1) }},\n\t\t{\"BalanceOf(bob, tid1)\", uint64(0), func() interface{} { return BalanceOf(bob, tid1) }},\n\t\t{\"IsApprovedForAll(admin, bob)\", false, func() interface{} { return IsApprovedForAll(admin, bob) }},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := tc.fn()\n\t\t\tif tc.expected != got {\n\t\t\t\tt.Errorf(\"expected: %v got: %v\", tc.expected, got)\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":""}} @@ -108,7 +108,7 @@ {"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"types","path":"gno.land/r/demo/types","files":[{"name":"types.gno","body":"// package to test types behavior in various conditions (TXs, imports).\npackage types\n\nimport (\n\t\"errors\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\nvar (\n\tgInt int = -42\n\tgUint uint = 42\n\tgString string = \"a string\"\n\tgStringSlice []string = []string{\"a\", \"string\", \"slice\"}\n\tgError error = errors.New(\"an error\")\n\tgIntSlice []int = []int{-42, 0, 42}\n\tgUintSlice []uint = []uint{0, 42, 84}\n\tgTree avl.Tree\n\t// gInterface = interface{}{-42, \"a string\", uint(42)}\n)\n\nfunc init() {\n\tgTree.Set(\"a\", \"content of A\")\n\tgTree.Set(\"b\", \"content of B\")\n}\n\nfunc Noop() {}\nfunc RetTimeNow() time.Time { return time.Now() }\nfunc RetString() string { return gString }\nfunc RetStringPointer() *string { return \u0026gString }\nfunc RetUint() uint { return gUint }\nfunc RetInt() int { return gInt }\nfunc RetUintPointer() *uint { return \u0026gUint }\nfunc RetIntPointer() *int { return \u0026gInt }\nfunc RetTree() avl.Tree { return gTree }\nfunc RetIntSlice() []int { return gIntSlice }\nfunc RetUintSlice() []uint { return gUintSlice }\nfunc RetStringSlice() []string { return gStringSlice }\nfunc RetError() error { return gError }\nfunc Panic() { panic(\"PANIC!\") }\n\n// TODO: floats\n// TODO: typed errors\n// TODO: ret interface\n// TODO: recover\n// TODO: take types as input\n\nfunc Render(path string) string {\n\treturn \"package to test data types.\"\n}\n"},{"name":"types_test.gno","body":"package types\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":"ui","path":"gno.land/r/demo/ui","files":[{"name":"ui.gno","body":"package ui\n\nimport \"gno.land/p/demo/ui\"\n\nfunc Render(path string) string {\n\t// TODO: build this realm as a demo one with one page per feature.\n\n\t// TODO: pagination\n\t// TODO: non-standard markdown\n\t// TODO: error, warn\n\t// TODO: header\n\t// TODO: HTML\n\t// TODO: toc\n\t// TODO: forms\n\t// TODO: comments\n\n\tdom := ui.DOM{\n\t\tPrefix: \"r/demo/ui:\",\n\t}\n\n\tdom.Title = \"UI Demo\"\n\n\tdom.Header.Append(ui.Breadcrumb{\n\t\tui.Link{Text: \"foo\", Path: \"foo\"},\n\t\tui.Link{Text: \"bar\", Path: \"foo/bar\"},\n\t})\n\n\tdom.Body.Append(\n\t\tui.Paragraph(\"Simple UI demonstration.\"),\n\t\tui.BulletList{\n\t\t\tui.Text(\"a text\"),\n\t\t\tui.Link{Text: \"a relative link\", Path: \"foobar\"},\n\t\t\tui.Text(\"another text\"),\n\t\t\t// ui.H1(\"a H1 text\"),\n\t\t\tui.Bold(\"a bold text\"),\n\t\t\tui.Italic(\"italic text\"),\n\t\t\tui.Text(\"raw markdown with **bold** text in the middle.\"),\n\t\t\tui.Code(\"some inline code\"),\n\t\t\tui.Link{Text: \"a remote link\", URL: \"https://gno.land\"},\n\t\t},\n\t)\n\n\tdom.Footer.Append(ui.Text(\"I'm the footer.\"))\n\tdom.Body.Append(ui.Text(\"another string.\"))\n\tdom.Body.Append(ui.Paragraph(\"a paragraph.\"), ui.HR{})\n\n\treturn dom.String()\n}\n"},{"name":"ui_test.gno","body":"package ui\n\nimport (\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestRender(t *testing.T) {\n\tgot := Render(\"\")\n\texpected := \"# UI Demo\\n\\n[foo](r/demo/ui:foo) / [bar](r/demo/ui:foo/bar)\\n\\n\\nSimple UI demonstration.\\n\\n- a text\\n- [a relative link](r/demo/ui:foobar)\\n- another text\\n- **a bold text**\\n- _italic text_\\n- raw markdown with **bold** text in the middle.\\n- `some inline code`\\n- [a remote link](https://gno.land)\\n\\nanother string.\\n\\na paragraph.\\n\\n\\n---\\n\\n\\nI'm the footer.\\n\\n\"\n\tuassert.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":"userbook","path":"gno.land/r/demo/userbook","files":[{"name":"userbook.gno","body":"// This realm demonstrates a small userbook system working with gnoweb\npackage userbook\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/mux\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype Signup struct {\n\taccount string\n\theight int64\n}\n\n// signups - keep a slice of signed up addresses efficient pagination\nvar signups []Signup\n\n// tracker - keep track of who signed up\nvar (\n\ttracker *avl.Tree\n\trouter *mux.Router\n)\n\nconst (\n\tdefaultPageSize = 20\n\tpathArgument = \"number\"\n\tsubPath = \"page/{\" + pathArgument + \"}\"\n\tsignUpEvent = \"SignUp\"\n)\n\nfunc init() {\n\t// Set up tracker tree\n\ttracker = avl.NewTree()\n\n\t// Set up route handling\n\trouter = mux.NewRouter()\n\trouter.HandleFunc(\"\", renderHelper)\n\trouter.HandleFunc(subPath, renderHelper)\n\n\t// Sign up the deployer\n\tSignUp()\n}\n\nfunc SignUp() string {\n\t// Get transaction caller\n\tcaller := std.PreviousRealm().Address().String()\n\theight := std.GetHeight()\n\n\t// Check if the user is already signed up\n\tif _, exists := tracker.Get(caller); exists {\n\t\tpanic(caller + \" is already signed up!\")\n\t}\n\n\t// Sign up the user\n\ttracker.Set(caller, struct{}{})\n\tsignup := Signup{\n\t\tcaller,\n\t\theight,\n\t}\n\n\tsignups = append(signups, signup)\n\tstd.Emit(signUpEvent, \"SignedUpAccount\", signup.account)\n\n\treturn ufmt.Sprintf(\"%s added to userbook up at block #%d!\", signup.account, signup.height)\n}\n\nfunc GetSignupsInRange(page, pageSize int) ([]Signup, int) {\n\tif page \u003c 1 {\n\t\tpanic(\"page number cannot be less than 1\")\n\t}\n\n\tif pageSize \u003c 1 || pageSize \u003e 50 {\n\t\tpanic(\"page size must be from 1 to 50\")\n\t}\n\n\t// Pagination\n\t// Calculate indexes\n\tstartIndex := (page - 1) * pageSize\n\tendIndex := startIndex + pageSize\n\n\t// If page does not contain any users\n\tif startIndex \u003e= len(signups) {\n\t\treturn nil, -1\n\t}\n\n\t// If page contains fewer users than the page size\n\tif endIndex \u003e len(signups) {\n\t\tendIndex = len(signups)\n\t}\n\n\treturn signups[startIndex:endIndex], endIndex\n}\n\nfunc renderHelper(res *mux.ResponseWriter, req *mux.Request) {\n\ttotalSignups := len(signups)\n\tres.Write(\"# Welcome to UserBook!\\n\\n\")\n\n\t// Get URL parameter\n\tpage, err := strconv.Atoi(req.GetVar(\"number\"))\n\tif err != nil {\n\t\tpage = 1 // render first page on bad input\n\t}\n\n\t// Fetch paginated signups\n\tfetchedSignups, endIndex := GetSignupsInRange(page, defaultPageSize)\n\t// Handle empty page case\n\tif len(fetchedSignups) == 0 {\n\t\tres.Write(\"No users on this page!\\n\\n\")\n\t\tres.Write(\"---\\n\\n\")\n\t\tres.Write(\"[Back to Page #1](/r/demo/userbook:page/1)\\n\\n\")\n\t\treturn\n\t}\n\n\t// Write page title\n\tres.Write(ufmt.Sprintf(\"## UserBook - Page #%d:\\n\\n\", page))\n\n\t// Write signups\n\tpageStartIndex := defaultPageSize * (page - 1)\n\tfor i, signup := range fetchedSignups {\n\t\tout := ufmt.Sprintf(\"#### User #%d - %s - signed up at Block #%d\\n\", pageStartIndex+i, signup.account, signup.height)\n\t\tres.Write(out)\n\t}\n\n\tres.Write(\"---\\n\\n\")\n\n\t// Write UserBook info\n\tlatestSignupIndex := totalSignups - 1\n\tres.Write(ufmt.Sprintf(\"#### Total users: %d\\n\", totalSignups))\n\tres.Write(ufmt.Sprintf(\"#### Latest signup: User #%d at Block #%d\\n\", latestSignupIndex, signups[latestSignupIndex].height))\n\n\tres.Write(\"---\\n\\n\")\n\n\t// Write page number\n\tres.Write(ufmt.Sprintf(\"You're viewing page #%d\", page))\n\n\t// Write navigation buttons\n\tvar prevPage string\n\tvar nextPage string\n\t// If we are on any page that is not the first page\n\tif page \u003e 1 {\n\t\tprevPage = ufmt.Sprintf(\" - [Previous page](/r/demo/userbook:page/%d)\", page-1)\n\t}\n\n\t// If there are more pages after the current one\n\tif endIndex \u003c totalSignups {\n\t\tnextPage = ufmt.Sprintf(\" - [Next page](/r/demo/userbook:page/%d)\\n\\n\", page+1)\n\t}\n\n\tres.Write(prevPage)\n\tres.Write(nextPage)\n}\n\nfunc Render(path string) string {\n\treturn router.Render(path)\n}\n"},{"name":"userbook_test.gno","body":"package userbook\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nfunc TestRender(t *testing.T) {\n\t// Sign up 20 users + deployer\n\tfor i := 0; i \u003c 20; i++ {\n\t\taddrName := ufmt.Sprintf(\"test%d\", i)\n\t\tcaller := testutils.TestAddress(addrName)\n\t\tstd.TestSetOriginCaller(caller)\n\t\tSignUp()\n\t}\n\n\ttestCases := []struct {\n\t\tname string\n\t\tnextPage bool\n\t\tprevPage bool\n\t\tpath string\n\t\texpectedNumberOfUsers int\n\t}{\n\t\t{\n\t\t\tname: \"1st page render\",\n\t\t\tnextPage: true,\n\t\t\tprevPage: false,\n\t\t\tpath: \"page/1\",\n\t\t\texpectedNumberOfUsers: 20,\n\t\t},\n\t\t{\n\t\t\tname: \"2nd page render\",\n\t\t\tnextPage: false,\n\t\t\tprevPage: true,\n\t\t\tpath: \"page/2\",\n\t\t\texpectedNumberOfUsers: 1,\n\t\t},\n\t\t{\n\t\t\tname: \"Invalid path render\",\n\t\t\tnextPage: true,\n\t\t\tprevPage: false,\n\t\t\tpath: \"page/invalidtext\",\n\t\t\texpectedNumberOfUsers: 20,\n\t\t},\n\t\t{\n\t\t\tname: \"Empty Page\",\n\t\t\tnextPage: false,\n\t\t\tprevPage: false,\n\t\t\tpath: \"page/1000\",\n\t\t\texpectedNumberOfUsers: 0,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := Render(tc.path)\n\t\t\tnumUsers := countUsers(got)\n\n\t\t\tif tc.prevPage \u0026\u0026 !strings.Contains(got, \"Previous page\") {\n\t\t\t\tt.Fatalf(\"expected to find Previous page, didn't find it\")\n\t\t\t}\n\t\t\tif tc.nextPage \u0026\u0026 !strings.Contains(got, \"Next page\") {\n\t\t\t\tt.Fatalf(\"expected to find Next page, didn't find it\")\n\t\t\t}\n\n\t\t\tif tc.expectedNumberOfUsers != numUsers {\n\t\t\t\tt.Fatalf(\"expected %d, got %d users\", tc.expectedNumberOfUsers, numUsers)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc countUsers(input string) int {\n\treturn strings.Count(input, \"#### User #\")\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":"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/users\"\n)\n\nvar (\n\tbanker *grc20.Banker = grc20.NewBanker(\"wrapped GNOT\", \"wugnot\", 0)\n\tToken = banker.Token()\n)\n\nconst (\n\tugnotMinDeposit uint64 = 1000\n\twugnotMinDeposit uint64 = 1\n)\n\nfunc Deposit() {\n\tcaller := std.PreviousRealm().Address()\n\tsent := std.OriginSend()\n\tamount := sent.AmountOf(\"ugnot\")\n\n\trequire(uint64(amount) \u003e= ugnotMinDeposit, ufmt.Sprintf(\"Deposit below minimum: %d/%d ugnot.\", amount, ugnotMinDeposit))\n\tcheckErr(banker.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.PreviousRealm().Address()\n\tpkgaddr := std.CurrentRealm().Address()\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(banker.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 banker.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\tcheckErr(Token.Transfer(toAddr, amount))\n}\n\nfunc Approve(spender pusers.AddressOrName, amount uint64) {\n\tspenderAddr := users.Resolve(spender)\n\tcheckErr(Token.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(Token.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.TestSetOriginPkgAddr(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.TestSetOriginCaller(addr1)\n\tstd.TestSetOriginSend(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.TestSetOriginCaller(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=200000000 |\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=200000000 |\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=200004242 |\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":""}} +{"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/users\"\n)\n\nvar (\n\tbanker *grc20.Banker = grc20.NewBanker(\"wrapped GNOT\", \"wugnot\", 0)\n\tToken = banker.Token()\n)\n\nconst (\n\tugnotMinDeposit uint64 = 1000\n\twugnotMinDeposit uint64 = 1\n)\n\nfunc Deposit() {\n\tcaller := std.PreviousRealm().Address()\n\tsent := std.OriginSend()\n\tamount := sent.AmountOf(\"ugnot\")\n\n\trequire(uint64(amount) \u003e= ugnotMinDeposit, ufmt.Sprintf(\"Deposit below minimum: %d/%d ugnot.\", amount, ugnotMinDeposit))\n\tcheckErr(banker.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.PreviousRealm().Address()\n\tpkgaddr := std.CurrentRealm().Address()\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(banker.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 banker.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\tcheckErr(Token.Transfer(toAddr, amount))\n}\n\nfunc Approve(spender pusers.AddressOrName, amount uint64) {\n\tspenderAddr := users.Resolve(spender)\n\tcheckErr(Token.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(Token.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.TestSetOriginPkgAddress(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.TestSetOriginCaller(addr1)\n\tstd.TestSetOriginSend(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.TestSetOriginCaller(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=200000000 |\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=200000000 |\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=200004242 |\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":""}} {"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"gnoblog","path":"gno.land/r/gnoland/blog","files":[{"name":"admin.gno","body":"package gnoblog\n\nimport (\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/r/gov/dao/bridge\"\n)\n\nvar (\n\tadminAddr std.Address\n\tmoderatorList avl.Tree\n\tcommenterList avl.Tree\n\tinPause bool\n)\n\nfunc init() {\n\t// adminAddr = std.OriginCaller() // FIXME: find a way to use this from the main's genesis.\n\tadminAddr = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"\n}\n\nfunc AdminSetAdminAddr(addr std.Address) {\n\tassertIsAdmin()\n\tadminAddr = addr\n}\n\nfunc AdminSetInPause(state bool) {\n\tassertIsAdmin()\n\tinPause = state\n}\n\nfunc AdminAddModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), true)\n}\n\nfunc AdminRemoveModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), false) // FIXME: delete instead?\n}\n\nfunc NewPostExecutor(slug, title, body, publicationDate, authors, tags string) dao.Executor {\n\tcallback := func() error {\n\t\taddPost(std.PreviousRealm().Address(), slug, title, body, publicationDate, authors, tags)\n\n\t\treturn nil\n\t}\n\n\treturn bridge.GovDAO().NewGovDAOExecutor(callback)\n}\n\nfunc ModAddPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\tcaller := std.OriginCaller()\n\taddPost(caller, slug, title, body, publicationDate, authors, tags)\n}\n\nfunc addPost(caller std.Address, slug, title, body, publicationDate, authors, tags string) {\n\tvar tagList []string\n\tif tags != \"\" {\n\t\ttagList = strings.Split(tags, \",\")\n\t}\n\tvar authorList []string\n\tif authors != \"\" {\n\t\tauthorList = strings.Split(authors, \",\")\n\t}\n\n\terr := b.NewPost(caller, slug, title, body, publicationDate, authorList, tagList)\n\n\tcheckErr(err)\n}\n\nfunc ModEditPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.GetPost(slug).Update(title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc ModRemovePost(slug string) {\n\tassertIsModerator()\n\n\tb.RemovePost(slug)\n}\n\nfunc ModAddCommenter(addr std.Address) {\n\tassertIsModerator()\n\tcommenterList.Set(addr.String(), true)\n}\n\nfunc ModDelCommenter(addr std.Address) {\n\tassertIsModerator()\n\tcommenterList.Set(addr.String(), false) // FIXME: delete instead?\n}\n\nfunc ModDelComment(slug string, index int) {\n\tassertIsModerator()\n\n\terr := b.GetPost(slug).DeleteComment(index)\n\tcheckErr(err)\n}\n\nfunc isAdmin(addr std.Address) bool {\n\treturn addr == adminAddr\n}\n\nfunc isModerator(addr std.Address) bool {\n\t_, found := moderatorList.Get(addr.String())\n\treturn found\n}\n\nfunc isCommenter(addr std.Address) bool {\n\t_, found := commenterList.Get(addr.String())\n\treturn found\n}\n\nfunc assertIsAdmin() {\n\tcaller := std.OriginCaller()\n\tif !isAdmin(caller) {\n\t\tpanic(\"access restricted.\")\n\t}\n}\n\nfunc assertIsModerator() {\n\tcaller := std.OriginCaller()\n\tif isAdmin(caller) || isModerator(caller) {\n\t\treturn\n\t}\n\tpanic(\"access restricted\")\n}\n\nfunc assertIsCommenter() {\n\tcaller := std.OriginCaller()\n\tif isAdmin(caller) || isModerator(caller) || isCommenter(caller) {\n\t\treturn\n\t}\n\tpanic(\"access restricted\")\n}\n\nfunc assertNotInPause() {\n\tif inPause {\n\t\tpanic(\"access restricted (pause)\")\n\t}\n}\n"},{"name":"gnoblog.gno","body":"package gnoblog\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/blog\"\n)\n\nvar b = \u0026blog.Blog{\n\tTitle: \"Gnoland's Blog\",\n\tPrefix: \"/r/gnoland/blog:\",\n}\n\nfunc AddComment(postSlug, comment string) {\n\tassertIsCommenter()\n\tassertNotInPause()\n\n\tcaller := std.OriginCaller()\n\terr := b.GetPost(postSlug).AddComment(caller, comment)\n\tcheckErr(err)\n}\n\nfunc Render(path string) string {\n\treturn b.Render(path)\n}\n\nfunc RenderLastPostsWidget(limit int) string {\n\treturn b.RenderLastPostsWidget(limit)\n}\n\nfunc PostExists(slug string) bool {\n\tif b.GetPost(slug) == nil {\n\t\treturn false\n\t}\n\treturn true\n}\n"},{"name":"gnoblog_test.gno","body":"package gnoblog\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestPackage(t *testing.T) {\n\tstd.TestSetOriginCaller(std.Address(\"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"))\n\n\tauthor := std.OriginCaller()\n\n\t// by default, no posts.\n\t{\n\t\tgot := Render(\"\")\n\t\texpected := `\n# Gnoland's Blog\n\nNo posts.\n`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// create two posts, list post.\n\t{\n\t\tModAddPost(\"slug1\", \"title1\", \"body1\", \"2022-05-20T13:17:22Z\", \"moul\", \"tag1,tag2\")\n\t\tModAddPost(\"slug2\", \"title2\", \"body2\", \"2022-05-20T13:17:23Z\", \"moul\", \"tag1,tag3\")\n\t\tgot := Render(\"\")\n\t\texpected := `\n\t# Gnoland's Blog\n\n\u003cdiv class='columns-3'\u003e\u003cdiv\u003e\n\n### [title2](/r/gnoland/blog:p/slug2)\n 20 May 2022\n\u003c/div\u003e\u003cdiv\u003e\n\n### [title1](/r/gnoland/blog:p/slug1)\n 20 May 2022\n\u003c/div\u003e\u003c/div\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// view post.\n\t{\n\t\tgot := Render(\"p/slug2\")\n\t\texpected := `\n\u003cmain class='gno-tmpl-page'\u003e\n\n# title2\n\nbody2\n\n---\n\nTags: [#tag1](/r/gnoland/blog:t/tag1) [#tag3](/r/gnoland/blog:t/tag3)\n\nWritten by moul on 20 May 2022\n\nPublished by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq to Gnoland's Blog\n\n---\n\u003cdetails\u003e\u003csummary\u003eComment section\u003c/summary\u003e\n\n\u003c/details\u003e\n\u003c/main\u003e\n\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// list by tags.\n\t{\n\t\tgot := Render(\"t/invalid\")\n\t\texpected := \"# [Gnoland's Blog](/r/gnoland/blog:) / t / invalid\\n\\nNo posts.\"\n\t\tassertMDEquals(t, got, expected)\n\n\t\tgot = Render(\"t/tag2\")\n\t\texpected = `\n# [Gnoland's Blog](/r/gnoland/blog:) / t / tag2\n\n\u003cdiv\u003e\n\n### [title1](/r/gnoland/blog:p/slug1)\n 20 May 2022\n\u003c/div\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// add comments.\n\t{\n\t\tAddComment(\"slug1\", \"comment1\")\n\t\tAddComment(\"slug2\", \"comment2\")\n\t\tAddComment(\"slug1\", \"comment3\")\n\t\tAddComment(\"slug2\", \"comment4\")\n\t\tAddComment(\"slug1\", \"comment5\")\n\t\tgot := Render(\"p/slug2\")\n\t\texpected := `\u003cmain class='gno-tmpl-page'\u003e\n\n# title2\n\nbody2\n\n---\n\nTags: [#tag1](/r/gnoland/blog:t/tag1) [#tag3](/r/gnoland/blog:t/tag3)\n\nWritten by moul on 20 May 2022\n\nPublished by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq to Gnoland's Blog\n\n---\n\u003cdetails\u003e\u003csummary\u003eComment section\u003c/summary\u003e\n\n\u003ch5\u003ecomment4\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003ch5\u003ecomment2\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003c/details\u003e\n\u003c/main\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\t}\n\n\t// edit post.\n\t{\n\t\toldTitle := \"title2\"\n\t\toldDate := \"2022-05-20T13:17:23Z\"\n\n\t\tModEditPost(\"slug2\", oldTitle, \"body2++\", oldDate, \"manfred\", \"tag1,tag4\")\n\t\tgot := Render(\"p/slug2\")\n\t\texpected := `\u003cmain class='gno-tmpl-page'\u003e\n\n# title2\n\nbody2++\n\n---\n\nTags: [#tag1](/r/gnoland/blog:t/tag1) [#tag4](/r/gnoland/blog:t/tag4)\n\nWritten by manfred on 20 May 2022\n\nPublished by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq to Gnoland's Blog\n\n---\n\u003cdetails\u003e\u003csummary\u003eComment section\u003c/summary\u003e\n\n\u003ch5\u003ecomment4\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003ch5\u003ecomment2\n\n\u003c/h5\u003e\u003ch6\u003eby g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 13 Feb 09 23:31 UTC\u003c/h6\u003e\n\n---\n\n\u003c/details\u003e\n\u003c/main\u003e\n\t`\n\t\tassertMDEquals(t, got, expected)\n\n\t\thome := Render(\"\")\n\n\t\tif strings.Count(home, oldTitle) != 1 {\n\t\t\tt.Errorf(\"post not edited properly\")\n\t\t}\n\t\t// Edits work everything except title, slug, and publicationDate\n\t\t// Edits to the above will cause duplication on the blog home page\n\t}\n\n\t{ // Test remove functionality\n\t\ttitle := \"example title\"\n\t\tslug := \"testSlug1\"\n\t\tModAddPost(slug, title, \"body1\", \"2022-05-25T13:17:22Z\", \"moul\", \"tag1,tag2\")\n\n\t\tgot := Render(\"\")\n\n\t\tif !strings.Contains(got, title) {\n\t\t\tt.Errorf(\"post was not added properly\")\n\t\t}\n\n\t\tpostRender := Render(\"p/\" + slug)\n\n\t\tif !strings.Contains(postRender, title) {\n\t\t\tt.Errorf(\"post not rendered properly\")\n\t\t}\n\n\t\tModRemovePost(slug)\n\t\tgot = Render(\"\")\n\n\t\tif strings.Contains(got, title) {\n\t\t\tt.Errorf(\"post was not removed\")\n\t\t}\n\n\t\tpostRender = Render(\"p/\" + slug)\n\n\t\tassertMDEquals(t, postRender, \"404\")\n\t}\n\n\t// TODO: pagination.\n\t// TODO: ?format=...\n\n\t// all 404s\n\t{\n\t\tnotFoundPaths := []string{\n\t\t\t\"p/slug3\",\n\t\t\t\"p\",\n\t\t\t\"p/\",\n\t\t\t\"x/x\",\n\t\t\t\"t\",\n\t\t\t\"t/\",\n\t\t\t\"/\",\n\t\t\t\"p/slug1/\",\n\t\t}\n\t\tfor _, notFoundPath := range notFoundPaths {\n\t\t\tgot := Render(notFoundPath)\n\t\t\texpected := \"404\"\n\t\t\tif got != expected {\n\t\t\t\tt.Errorf(\"path %q: expected %q, got %q.\", notFoundPath, expected, got)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc assertMDEquals(t *testing.T, got, expected string) {\n\tt.Helper()\n\texpected = strings.TrimSpace(expected)\n\tgot = strings.TrimSpace(got)\n\tif expected != got {\n\t\tt.Errorf(\"invalid render output.\\nexpected %q.\\ngot %q.\", expected, got)\n\t}\n}\n"},{"name":"util.gno","body":"package gnoblog\n\nfunc checkErr(err error) {\n\tif err != nil {\n\t\tpanic(err)\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":"events","path":"gno.land/r/gnoland/events","files":[{"name":"administration.gno","body":"package events\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/ownable/exts/authorizable\"\n)\n\nvar (\n\tsu = std.Address(\"g125em6arxsnj49vx35f0n0z34putv5ty3376fg5\") // @leohhhn\n\tauth = authorizable.NewAuthorizableWithAddress(su)\n)\n\n// GetOwner gets the owner of the events realm\nfunc GetOwner() std.Address {\n\treturn auth.Owner()\n}\n\n// AddModerator adds a moderator to the events realm\nfunc AddModerator(mod std.Address) {\n\tauth.AssertCallerIsOwner()\n\n\tif err := auth.AddToAuthList(mod); err != nil {\n\t\tpanic(err)\n\t}\n}\n"},{"name":"errors.gno","body":"package events\n\nimport (\n\t\"errors\"\n\t\"strconv\"\n)\n\nvar (\n\tErrEmptyName = errors.New(\"event name cannot be empty\")\n\tErrNoSuchID = errors.New(\"event with specified ID does not exist\")\n\tErrMinWidgetSize = errors.New(\"you need to request at least 1 event to render\")\n\tErrMaxWidgetSize = errors.New(\"maximum number of events in widget is\" + strconv.Itoa(MaxWidgetSize))\n\tErrDescriptionTooLong = errors.New(\"event description is too long\")\n\tErrInvalidStartTime = errors.New(\"invalid start time format\")\n\tErrInvalidEndTime = errors.New(\"invalid end time format\")\n\tErrEndBeforeStart = errors.New(\"end time cannot be before start time\")\n\tErrStartEndTimezonemMismatch = errors.New(\"start and end timezones are not the same\")\n)\n"},{"name":"events.gno","body":"// Package events allows you to upload data about specific IRL/online events\n// It includes dynamic support for updating rendering events based on their\n// status, ie if they are upcoming, in progress, or in the past.\npackage events\n\nimport (\n\t\"sort\"\n\t\"std\"\n\t\"strings\"\n\t\"time\"\n\n\t\"gno.land/p/demo/seqid\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype (\n\tEvent struct {\n\t\tid string\n\t\tname string // name of event\n\t\tdescription string // short description of event\n\t\tlink string // link to auth corresponding web2 page, ie eventbrite/luma or conference page\n\t\tlocation string // location of the event\n\t\tstartTime time.Time // given in RFC3339\n\t\tendTime time.Time // end time of the event, given in RFC3339\n\t}\n\n\teventsSlice []*Event\n)\n\nvar (\n\tevents = make(eventsSlice, 0) // sorted\n\tidCounter seqid.ID\n)\n\nconst (\n\tmaxDescLength = 100\n\tEventAdded = \"EventAdded\"\n\tEventDeleted = \"EventDeleted\"\n\tEventEdited = \"EventEdited\"\n)\n\n// AddEvent adds auth new event\n// Start time \u0026 end time need to be specified in RFC3339, ie 2024-08-08T12:00:00+02:00\nfunc AddEvent(name, description, link, location, startTime, endTime string) (string, error) {\n\tauth.AssertOnAuthList()\n\n\tif strings.TrimSpace(name) == \"\" {\n\t\treturn \"\", ErrEmptyName\n\t}\n\n\tif len(description) \u003e maxDescLength {\n\t\treturn \"\", ufmt.Errorf(\"%s: provided length is %d, maximum is %d\", ErrDescriptionTooLong, len(description), maxDescLength)\n\t}\n\n\t// Parse times\n\tst, et, err := parseTimes(startTime, endTime)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tid := idCounter.Next().String()\n\te := \u0026Event{\n\t\tid: id,\n\t\tname: name,\n\t\tdescription: description,\n\t\tlink: link,\n\t\tlocation: location,\n\t\tstartTime: st,\n\t\tendTime: et,\n\t}\n\n\tevents = append(events, e)\n\tsort.Sort(events)\n\n\tstd.Emit(EventAdded,\n\t\t\"id\", e.id,\n\t)\n\n\treturn id, nil\n}\n\n// DeleteEvent deletes an event with auth given ID\nfunc DeleteEvent(id string) {\n\tauth.AssertOnAuthList()\n\n\te, idx, err := GetEventByID(id)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tevents = append(events[:idx], events[idx+1:]...)\n\n\tstd.Emit(EventDeleted,\n\t\t\"id\", e.id,\n\t)\n}\n\n// EditEvent edits an event with auth given ID\n// It only updates values corresponding to non-empty arguments sent with the call\n// Note: if you need to update the start time or end time, you need to provide both every time\nfunc EditEvent(id string, name, description, link, location, startTime, endTime string) {\n\tauth.AssertOnAuthList()\n\n\te, _, err := GetEventByID(id)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Set only valid values\n\tif strings.TrimSpace(name) != \"\" {\n\t\te.name = name\n\t}\n\n\tif strings.TrimSpace(description) != \"\" {\n\t\te.description = description\n\t}\n\n\tif strings.TrimSpace(link) != \"\" {\n\t\te.link = link\n\t}\n\n\tif strings.TrimSpace(location) != \"\" {\n\t\te.location = location\n\t}\n\n\tif strings.TrimSpace(startTime) != \"\" || strings.TrimSpace(endTime) != \"\" {\n\t\tst, et, err := parseTimes(startTime, endTime)\n\t\tif err != nil {\n\t\t\tpanic(err) // need to also revert other state changes\n\t\t}\n\n\t\toldStartTime := e.startTime\n\t\te.startTime = st\n\t\te.endTime = et\n\n\t\t// If sort order was disrupted, sort again\n\t\tif oldStartTime != e.startTime {\n\t\t\tsort.Sort(events)\n\t\t}\n\t}\n\n\tstd.Emit(EventEdited,\n\t\t\"id\", e.id,\n\t)\n}\n\nfunc GetEventByID(id string) (*Event, int, error) {\n\tfor i, event := range events {\n\t\tif event.id == id {\n\t\t\treturn event, i, nil\n\t\t}\n\t}\n\n\treturn nil, -1, ErrNoSuchID\n}\n\n// Len returns the length of the slice\nfunc (m eventsSlice) Len() int {\n\treturn len(m)\n}\n\n// Less compares the startTime fields of two elements\n// In this case, events will be sorted by largest startTime first (upcoming \u003e past)\nfunc (m eventsSlice) Less(i, j int) bool {\n\treturn m[i].startTime.After(m[j].startTime)\n}\n\n// Swap swaps two elements in the slice\nfunc (m eventsSlice) Swap(i, j int) {\n\tm[i], m[j] = m[j], m[i]\n}\n\n// parseTimes parses the start and end time for an event and checks for possible errors\nfunc parseTimes(startTime, endTime string) (time.Time, time.Time, error) {\n\tst, err := time.Parse(time.RFC3339, startTime)\n\tif err != nil {\n\t\treturn time.Time{}, time.Time{}, ufmt.Errorf(\"%s: %s\", ErrInvalidStartTime, err.Error())\n\t}\n\n\tet, err := time.Parse(time.RFC3339, endTime)\n\tif err != nil {\n\t\treturn time.Time{}, time.Time{}, ufmt.Errorf(\"%s: %s\", ErrInvalidEndTime, err.Error())\n\t}\n\n\tif et.Before(st) {\n\t\treturn time.Time{}, time.Time{}, ErrEndBeforeStart\n\t}\n\n\t_, stOffset := st.Zone()\n\t_, etOffset := et.Zone()\n\tif stOffset != etOffset {\n\t\treturn time.Time{}, time.Time{}, ErrStartEndTimezonemMismatch\n\t}\n\n\treturn st, et, nil\n}\n"},{"name":"events_test.gno","body":"package events\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gno.land/p/demo/uassert\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nvar (\n\tsuRealm = std.NewUserRealm(su)\n\n\tnow = \"2009-02-13T23:31:30Z\" // time.Now() is hardcoded to this value in the gno test machine currently\n\tparsedTimeNow, _ = time.Parse(time.RFC3339, now)\n)\n\nfunc TestAddEvent(t *testing.T) {\n\tstd.TestSetOriginCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\te1Start := parsedTimeNow.Add(time.Hour * 24 * 5)\n\te1End := e1Start.Add(time.Hour * 4)\n\n\tAddEvent(\"Event 1\", \"this event is upcoming\", \"gno.land\", \"gnome land\", e1Start.Format(time.RFC3339), e1End.Format(time.RFC3339))\n\n\tgot := renderHome(false)\n\n\tif !strings.Contains(got, \"Event 1\") {\n\t\tt.Fatalf(\"Expected to find Event 1 in render\")\n\t}\n\n\te2Start := parsedTimeNow.Add(-time.Hour * 24 * 5)\n\te2End := e2Start.Add(time.Hour * 4)\n\n\tAddEvent(\"Event 2\", \"this event is in the past\", \"gno.land\", \"gnome land\", e2Start.Format(time.RFC3339), e2End.Format(time.RFC3339))\n\n\tgot = renderHome(false)\n\n\tupcomingPos := strings.Index(got, \"## Upcoming events\")\n\tpastPos := strings.Index(got, \"## Past events\")\n\n\te1Pos := strings.Index(got, \"Event 1\")\n\te2Pos := strings.Index(got, \"Event 2\")\n\n\t// expected index ordering: upcoming \u003c e1 \u003c past \u003c e2\n\tif e1Pos \u003c upcomingPos || e1Pos \u003e pastPos {\n\t\tt.Fatalf(\"Expected to find Event 1 in Upcoming events\")\n\t}\n\n\tif e2Pos \u003c upcomingPos || e2Pos \u003c pastPos || e2Pos \u003c e1Pos {\n\t\tt.Fatalf(\"Expected to find Event 2 on auth different pos\")\n\t}\n\n\t// larger index =\u003e smaller startTime (future =\u003e past)\n\tif events[0].startTime.Unix() \u003c events[1].startTime.Unix() {\n\t\tt.Fatalf(\"expected ordering to be different\")\n\t}\n}\n\nfunc TestAddEventErrors(t *testing.T) {\n\tstd.TestSetOriginCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\t_, err := AddEvent(\"\", \"sample desc\", \"gno.land\", \"gnome land\", \"2009-02-13T23:31:31Z\", \"2009-02-13T23:33:31Z\")\n\tuassert.ErrorIs(t, err, ErrEmptyName)\n\n\t_, err = AddEvent(\"sample name\", \"sample desc\", \"gno.land\", \"gnome land\", \"\", \"2009-02-13T23:33:31Z\")\n\tuassert.ErrorContains(t, err, ErrInvalidStartTime.Error())\n\n\t_, err = AddEvent(\"sample name\", \"sample desc\", \"gno.land\", \"gnome land\", \"2009-02-13T23:31:31Z\", \"\")\n\tuassert.ErrorContains(t, err, ErrInvalidEndTime.Error())\n\n\t_, err = AddEvent(\"sample name\", \"sample desc\", \"gno.land\", \"gnome land\", \"2009-02-13T23:31:31Z\", \"2009-02-13T23:30:31Z\")\n\tuassert.ErrorIs(t, err, ErrEndBeforeStart)\n\n\t_, err = AddEvent(\"sample name\", \"sample desc\", \"gno.land\", \"gnome land\", \"2009-02-13T23:31:31+06:00\", \"2009-02-13T23:33:31+02:00\")\n\tuassert.ErrorIs(t, err, ErrStartEndTimezonemMismatch)\n\n\ttooLongDesc := `Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean ma`\n\t_, err = AddEvent(\"sample name\", tooLongDesc, \"gno.land\", \"gnome land\", \"2009-02-13T23:31:31Z\", \"2009-02-13T23:33:31Z\")\n\tuassert.ErrorContains(t, err, ErrDescriptionTooLong.Error())\n}\n\nfunc TestDeleteEvent(t *testing.T) {\n\tstd.TestSetOriginCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\te1Start := parsedTimeNow.Add(time.Hour * 24 * 5)\n\te1End := e1Start.Add(time.Hour * 4)\n\n\tid, _ := AddEvent(\"ToDelete\", \"description\", \"gno.land\", \"gnome land\", e1Start.Format(time.RFC3339), e1End.Format(time.RFC3339))\n\n\tgot := renderHome(false)\n\n\tif !strings.Contains(got, \"ToDelete\") {\n\t\tt.Fatalf(\"Expected to find ToDelete event in render\")\n\t}\n\n\tDeleteEvent(id)\n\tgot = renderHome(false)\n\n\tif strings.Contains(got, \"ToDelete\") {\n\t\tt.Fatalf(\"Did not expect to find ToDelete event in render\")\n\t}\n}\n\nfunc TestEditEvent(t *testing.T) {\n\tstd.TestSetOriginCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\te1Start := parsedTimeNow.Add(time.Hour * 24 * 5)\n\te1End := e1Start.Add(time.Hour * 4)\n\tloc := \"gnome land\"\n\n\tid, _ := AddEvent(\"ToDelete\", \"description\", \"gno.land\", loc, e1Start.Format(time.RFC3339), e1End.Format(time.RFC3339))\n\n\tnewName := \"New Name\"\n\tnewDesc := \"Normal description\"\n\tnewLink := \"new Link\"\n\tnewST := e1Start.Add(time.Hour)\n\tnewET := newST.Add(time.Hour)\n\n\tEditEvent(id, newName, newDesc, newLink, \"\", newST.Format(time.RFC3339), newET.Format(time.RFC3339))\n\tedited, _, _ := GetEventByID(id)\n\n\t// Check updated values\n\tuassert.Equal(t, edited.name, newName)\n\tuassert.Equal(t, edited.description, newDesc)\n\tuassert.Equal(t, edited.link, newLink)\n\tuassert.True(t, edited.startTime.Equal(newST))\n\tuassert.True(t, edited.endTime.Equal(newET))\n\n\t// Check if the old values are the same\n\tuassert.Equal(t, edited.location, loc)\n}\n\nfunc TestInvalidEdit(t *testing.T) {\n\tstd.TestSetOriginCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\tuassert.PanicsWithMessage(t, ErrNoSuchID.Error(), func() {\n\t\tEditEvent(\"123123\", \"\", \"\", \"\", \"\", \"\", \"\")\n\t})\n}\n\nfunc TestParseTimes(t *testing.T) {\n\t// times not provided\n\t// end time before start time\n\t// timezone Missmatch\n\n\t_, _, err := parseTimes(\"\", \"\")\n\tuassert.ErrorContains(t, err, ErrInvalidStartTime.Error())\n\n\t_, _, err = parseTimes(now, \"\")\n\tuassert.ErrorContains(t, err, ErrInvalidEndTime.Error())\n\n\t_, _, err = parseTimes(\"2009-02-13T23:30:30Z\", \"2009-02-13T21:30:30Z\")\n\tuassert.ErrorContains(t, err, ErrEndBeforeStart.Error())\n\n\t_, _, err = parseTimes(\"2009-02-10T23:30:30+02:00\", \"2009-02-13T21:30:33+05:00\")\n\tuassert.ErrorContains(t, err, ErrStartEndTimezonemMismatch.Error())\n}\n\nfunc TestRenderEventWidget(t *testing.T) {\n\tstd.TestSetOriginCaller(su)\n\tstd.TestSetRealm(suRealm)\n\n\t// No events yet\n\tevents = nil\n\tout, err := RenderEventWidget(1)\n\tuassert.NoError(t, err)\n\tuassert.Equal(t, out, \"No events.\")\n\n\t// Too many events\n\tout, err = RenderEventWidget(MaxWidgetSize + 1)\n\tuassert.ErrorIs(t, err, ErrMaxWidgetSize)\n\n\t// Too little events\n\tout, err = RenderEventWidget(0)\n\tuassert.ErrorIs(t, err, ErrMinWidgetSize)\n\n\t// Ordering \u0026 if requested amt is larger than the num of events that exist\n\te1Start := parsedTimeNow.Add(time.Hour * 24 * 5)\n\te1End := e1Start.Add(time.Hour * 4)\n\n\te2Start := parsedTimeNow.Add(time.Hour * 24 * 10) // event 2 is after event 1\n\te2End := e2Start.Add(time.Hour * 4)\n\n\t_, err = AddEvent(\"Event 1\", \"description\", \"gno.land\", \"loc\", e1Start.Format(time.RFC3339), e1End.Format(time.RFC3339))\n\turequire.NoError(t, err)\n\n\t_, err = AddEvent(\"Event 2\", \"description\", \"gno.land\", \"loc\", e2Start.Format(time.RFC3339), e2End.Format(time.RFC3339))\n\turequire.NoError(t, err)\n\n\tout, err = RenderEventWidget(MaxWidgetSize)\n\turequire.NoError(t, err)\n\n\tuniqueSequence := \"- [\" // sequence that is displayed once per each event as per the RenderEventWidget function\n\tuassert.Equal(t, 2, strings.Count(out, uniqueSequence))\n\n\tuassert.True(t, strings.Index(out, \"Event 1\") \u003e strings.Index(out, \"Event 2\"))\n}\n"},{"name":"rendering.gno","body":"package events\n\nimport (\n\t\"bytes\"\n\t\"time\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\nconst (\n\tMaxWidgetSize = 5\n)\n\n// RenderEventWidget shows up to eventsToRender of the latest events to a caller\nfunc RenderEventWidget(eventsToRender int) (string, error) {\n\tnumOfEvents := len(events)\n\tif numOfEvents == 0 {\n\t\treturn \"No events.\", nil\n\t}\n\n\tif eventsToRender \u003e MaxWidgetSize {\n\t\treturn \"\", ErrMaxWidgetSize\n\t}\n\n\tif eventsToRender \u003c 1 {\n\t\treturn \"\", ErrMinWidgetSize\n\t}\n\n\tif eventsToRender \u003e numOfEvents {\n\t\teventsToRender = numOfEvents\n\t}\n\n\toutput := \"\"\n\n\tfor _, event := range events[:eventsToRender] {\n\t\toutput += ufmt.Sprintf(\"- [%s](%s)\\n\", event.name, event.link)\n\t}\n\n\treturn output, nil\n}\n\n// renderHome renders the home page of the events realm\nfunc renderHome(admin bool) string {\n\toutput := \"# gno.land events\\n\\n\"\n\n\tif len(events) == 0 {\n\t\toutput += \"No upcoming or past events.\"\n\t\treturn output\n\t}\n\n\toutput += \"Below is a list of all gno.land events, including in progress, upcoming, and past ones.\\n\\n\"\n\toutput += \"---\\n\\n\"\n\n\tvar (\n\t\tinProgress = \"\"\n\t\tupcoming = \"\"\n\t\tpast = \"\"\n\t\tnow = time.Now()\n\t)\n\n\tfor _, e := range events {\n\t\tif now.Before(e.startTime) {\n\t\t\tupcoming += e.Render(admin)\n\t\t} else if now.After(e.endTime) {\n\t\t\tpast += e.Render(admin)\n\t\t} else {\n\t\t\tinProgress += e.Render(admin)\n\t\t}\n\t}\n\n\tif upcoming != \"\" {\n\t\t// Add upcoming events\n\t\toutput += \"## Upcoming events\\n\\n\"\n\t\toutput += \"\u003cdiv class='columns-3'\u003e\"\n\n\t\toutput += upcoming\n\n\t\toutput += \"\u003c/div\u003e\\n\\n\"\n\t\toutput += \"---\\n\\n\"\n\t}\n\n\tif inProgress != \"\" {\n\t\toutput += \"## Currently in progress\\n\\n\"\n\t\toutput += \"\u003cdiv class='columns-3'\u003e\"\n\n\t\toutput += inProgress\n\n\t\toutput += \"\u003c/div\u003e\\n\\n\"\n\t\toutput += \"---\\n\\n\"\n\t}\n\n\tif past != \"\" {\n\t\t// Add past events\n\t\toutput += \"## Past events\\n\\n\"\n\t\toutput += \"\u003cdiv class='columns-3'\u003e\"\n\n\t\toutput += past\n\n\t\toutput += \"\u003c/div\u003e\\n\\n\"\n\t}\n\n\treturn output\n}\n\n// Render returns the markdown representation of a single event instance\nfunc (e Event) Render(admin bool) string {\n\tvar buf bytes.Buffer\n\n\tbuf.WriteString(\"\u003cdiv\u003e\\n\\n\")\n\tbuf.WriteString(ufmt.Sprintf(\"### %s\\n\\n\", e.name))\n\tbuf.WriteString(ufmt.Sprintf(\"%s\\n\\n\", e.description))\n\tbuf.WriteString(ufmt.Sprintf(\"**Location:** %s\\n\\n\", e.location))\n\n\t_, offset := e.startTime.Zone() // offset is in seconds\n\thoursOffset := offset / (60 * 60)\n\tsign := \"\"\n\tif offset \u003e= 0 {\n\t\tsign = \"+\"\n\t}\n\n\tbuf.WriteString(ufmt.Sprintf(\"**Starts:** %s UTC%s%d\\n\\n\", e.startTime.Format(\"02 Jan 2006, 03:04 PM\"), sign, hoursOffset))\n\tbuf.WriteString(ufmt.Sprintf(\"**Ends:** %s UTC%s%d\\n\\n\", e.endTime.Format(\"02 Jan 2006, 03:04 PM\"), sign, hoursOffset))\n\n\tif admin {\n\t\tbuf.WriteString(ufmt.Sprintf(\"[EDIT](/r/gnoland/events$help\u0026func=EditEvent\u0026id=%s)\\n\\n\", e.id))\n\t\tbuf.WriteString(ufmt.Sprintf(\"[DELETE](/r/gnoland/events$help\u0026func=DeleteEvent\u0026id=%s)\\n\\n\", e.id))\n\t}\n\n\tif e.link != \"\" {\n\t\tbuf.WriteString(ufmt.Sprintf(\"[See more](%s)\\n\\n\", e.link))\n\t}\n\n\tbuf.WriteString(\"\u003c/div\u003e\")\n\n\treturn buf.String()\n}\n\n// Render is the main rendering entry point\nfunc Render(path string) string {\n\tif path == \"admin\" {\n\t\treturn renderHome(true)\n\t}\n\n\treturn renderHome(false)\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":"faucet","path":"gno.land/r/gnoland/faucet","files":[{"name":"admin.gno","body":"package faucet\n\nimport (\n\t\"errors\"\n\t\"std\"\n)\n\nfunc AdminSetInPause(inPause bool) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\tgInPause = inPause\n\treturn \"\"\n}\n\nfunc AdminSetMessage(message string) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\tgMessage = message\n\treturn \"\"\n}\n\nfunc AdminSetTransferLimit(amount int64) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\tgLimit = std.NewCoin(\"ugnot\", amount)\n\treturn \"\"\n}\n\nfunc AdminSetAdminAddr(addr std.Address) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\tgAdminAddr = addr\n\treturn \"\"\n}\n\nfunc AdminAddController(addr std.Address) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\n\tsize := gControllers.Size()\n\n\tif size \u003e= gControllersMaxSize {\n\t\treturn \"can not add more controllers than allowed\"\n\t}\n\n\tif gControllers.Has(addr.String()) {\n\t\treturn addr.String() + \" exists, no need to add.\"\n\t}\n\n\tgControllers.Set(addr.String(), addr)\n\n\treturn \"\"\n}\n\nfunc AdminRemoveController(addr std.Address) string {\n\tif err := assertIsAdmin(); err != nil {\n\t\treturn err.Error()\n\t}\n\n\tif !gControllers.Has(addr.String()) {\n\t\treturn addr.String() + \" is not on the controller list\"\n\t}\n\n\t_, ok := gControllers.Remove(addr.String())\n\n\t// it not should happen.\n\t// we will check anyway to prevent issues in the underline implementation.\n\n\tif !ok {\n\t\treturn addr.String() + \" is not on the controller list\"\n\t}\n\n\treturn \"\"\n}\n\nfunc assertIsAdmin() error {\n\tcaller := std.OriginCaller()\n\tif caller != gAdminAddr {\n\t\treturn errors.New(\"restricted for admin\")\n\t}\n\treturn nil\n}\n"},{"name":"faucet.gno","body":"package faucet\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\nvar (\n\t// configurable by admin.\n\tgAdminAddr std.Address = std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")\n\tgControllers = avl.NewTree()\n\tgControllersMaxSize = 10 // limit it to 10\n\tgInPause = false\n\tgMessage = \"# Community Faucet.\\n\\n\"\n\n\t// internal vars, for stats.\n\tgTotalTransferred std.Coins\n\tgTotalTransfers = uint(0)\n\n\t// per request limit, 350 gnot\n\tgLimit std.Coin = std.NewCoin(\"ugnot\", 350000000)\n)\n\nfunc Transfer(to std.Address, send int64) string {\n\tif err := assertIsController(); err != nil {\n\t\treturn err.Error()\n\t}\n\n\tif gInPause {\n\t\treturn errors.New(\"faucet in pause\").Error()\n\t}\n\n\t// limit the per request\n\tif send \u003e gLimit.Amount {\n\t\treturn errors.New(\"Per request limit \" + gLimit.String() + \" exceed\").Error()\n\t}\n\tsendCoins := std.Coins{std.NewCoin(\"ugnot\", send)}\n\n\tgTotalTransferred = gTotalTransferred.Add(sendCoins)\n\tgTotalTransfers++\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tpkgaddr := std.CurrentRealm().Address()\n\tbanker.SendCoins(pkgaddr, to, sendCoins)\n\treturn \"\"\n}\n\nfunc GetPerTransferLimit() int64 {\n\treturn gLimit.Amount\n}\n\nfunc Render(_ string) string {\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\tbalance := banker.GetCoins(std.CurrentRealm().Address())\n\n\toutput := gMessage\n\tif gInPause {\n\t\toutput += \"Status: inactive.\\n\"\n\t} else {\n\t\toutput += \"Status: active.\\n\"\n\t}\n\toutput += ufmt.Sprintf(\"Balance: %s.\\n\", balance.String())\n\toutput += ufmt.Sprintf(\"Total transfers: %s (in %d times).\\n\\n\", gTotalTransferred.String(), gTotalTransfers)\n\n\toutput += \"Package address: \" + std.CurrentRealm().Address().String() + \"\\n\\n\"\n\toutput += ufmt.Sprintf(\"Admin: %s\\n\\n \", gAdminAddr.String())\n\toutput += ufmt.Sprintf(\"Controllers:\\n\\n \")\n\n\tfor i := 0; i \u003c gControllers.Size(); i++ {\n\t\t_, v := gControllers.GetByIndex(i)\n\t\toutput += ufmt.Sprintf(\"%s \", v.(std.Address))\n\t}\n\n\toutput += \"\\n\\n\"\n\toutput += ufmt.Sprintf(\"Per request limit: %s\\n\\n\", gLimit.String())\n\n\treturn output\n}\n\nfunc assertIsController() error {\n\tcaller := std.OriginCaller()\n\n\tok := gControllers.Has(caller.String())\n\tif !ok {\n\t\treturn errors.New(caller.String() + \" is not on the controller list\")\n\t}\n\treturn nil\n}\n"},{"name":"faucet_test.gno","body":"package faucet\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/gnoland/faucet\"\n)\n\nfunc TestPackage(t *testing.T) {\n\tvar (\n\t\tadminaddr = std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")\n\t\tfaucetaddr = std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\t\tcontrolleraddr1 = testutils.TestAddress(\"controller1\")\n\t\tcontrolleraddr2 = testutils.TestAddress(\"controller2\")\n\t\tcontrolleraddr3 = testutils.TestAddress(\"controller3\")\n\t\tcontrolleraddr4 = testutils.TestAddress(\"controller4\")\n\t\tcontrolleraddr5 = testutils.TestAddress(\"controller5\")\n\t\tcontrolleraddr6 = testutils.TestAddress(\"controller6\")\n\t\tcontrolleraddr7 = testutils.TestAddress(\"controller7\")\n\t\tcontrolleraddr8 = testutils.TestAddress(\"controller8\")\n\t\tcontrolleraddr9 = testutils.TestAddress(\"controller9\")\n\t\tcontrolleraddr10 = testutils.TestAddress(\"controller10\")\n\t\tcontrolleraddr11 = testutils.TestAddress(\"controller11\")\n\n\t\ttest1addr = testutils.TestAddress(\"test1\")\n\t)\n\t// deposit 1000gnot to faucet contract\n\tstd.TestIssueCoins(faucetaddr, std.Coins{{\"ugnot\", 1000000000}})\n\tassertBalance(t, faucetaddr, 1200000000)\n\n\t// by default, balance is empty, and as a user I cannot call Transfer, or Admin commands.\n\n\tassertBalance(t, test1addr, 0)\n\tstd.TestSetOriginCaller(test1addr)\n\tassertErr(t, faucet.Transfer(test1addr, 1000000))\n\n\tassertErr(t, faucet.AdminAddController(controlleraddr1))\n\tstd.TestSetOriginCaller(controlleraddr1)\n\tassertErr(t, faucet.Transfer(test1addr, 1000000))\n\n\t// as an admin, add the controller to contract and deposit more 2000gnot to contract\n\tstd.TestSetOriginCaller(adminaddr)\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr1))\n\tassertBalance(t, faucetaddr, 1200000000)\n\n\t// now, send some tokens as controller.\n\tstd.TestSetOriginCaller(controlleraddr1)\n\tassertNoErr(t, faucet.Transfer(test1addr, 1000000))\n\tassertBalance(t, test1addr, 1000000)\n\tassertNoErr(t, faucet.Transfer(test1addr, 1000000))\n\tassertBalance(t, test1addr, 2000000)\n\tassertBalance(t, faucetaddr, 1198000000)\n\n\t// remove controller\n\t// as an admin, remove controller\n\tstd.TestSetOriginCaller(adminaddr)\n\tassertNoErr(t, faucet.AdminRemoveController(controlleraddr1))\n\tstd.TestSetOriginCaller(controlleraddr1)\n\tassertErr(t, faucet.Transfer(test1addr, 1000000))\n\n\t// duplicate controller\n\tstd.TestSetOriginCaller(adminaddr)\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr1))\n\tassertErr(t, faucet.AdminAddController(controlleraddr1))\n\t// add more than more than allowed controllers\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr2))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr3))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr4))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr5))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr6))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr7))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr8))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr9))\n\tassertNoErr(t, faucet.AdminAddController(controlleraddr10))\n\tassertErr(t, faucet.AdminAddController(controlleraddr11))\n\n\t// send more than per transfer limit\n\tstd.TestSetOriginCaller(adminaddr)\n\tfaucet.AdminSetTransferLimit(300000000)\n\tstd.TestSetOriginCaller(controlleraddr1)\n\tassertErr(t, faucet.Transfer(test1addr, 301000000))\n\n\t// block transefer from the address not on the controllers list.\n\tstd.TestSetOriginCaller(controlleraddr11)\n\tassertErr(t, faucet.Transfer(test1addr, 1000000))\n}\n\nfunc assertErr(t *testing.T, err string) {\n\tt.Helper()\n\n\tif err == \"\" {\n\t\tt.Logf(\"info: got err: %v\", err)\n\t\tt.Errorf(\"expected an error, got nil.\")\n\t}\n}\n\nfunc assertNoErr(t *testing.T, err string) {\n\tt.Helper()\n\tif err != \"\" {\n\t\tt.Errorf(\"got err: %v.\", err)\n\t}\n}\n\nfunc assertBalance(t *testing.T, addr std.Address, expectedBal int64) {\n\tt.Helper()\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tcoins := banker.GetCoins(addr)\n\tgot := coins.AmountOf(\"ugnot\")\n\n\tif expectedBal != got {\n\t\tt.Errorf(\"invalid balance: expected %d, got %d.\", expectedBal, got)\n\t}\n}\n"},{"name":"z0_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/gnoland/faucet\"\n)\n\n// mints ugnot to current realm\nfunc init() {\n\tfacuetaddr := std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\tstd.TestIssueCoins(facuetaddr, std.Coins{{\"ugnot\", 200000000}})\n}\n\n// assert render with empty path and no controllers\nfunc main() {\n\tprintln(faucet.Render(\"\"))\n}\n\n// Output:\n// # Community Faucet.\n//\n// Status: active.\n// Balance: 200000000ugnot.\n// Total transfers: (in 0 times).\n//\n// Package address: g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7\n//\n// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n//\n// Controllers:\n//\n//\n//\n// Per request limit: 350000000ugnot\n"},{"name":"z1_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/gnoland/faucet\"\n)\n\n// mints ugnot to current realm\nfunc init() {\n\tfacuetaddr := std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\tstd.TestIssueCoins(facuetaddr, std.Coins{{\"ugnot\", 200000000}})\n}\n\n// assert render with a path and no controllers\nfunc main() {\n\tprintln(faucet.Render(\"path\"))\n}\n\n// Output:\n// # Community Faucet.\n//\n// Status: active.\n// Balance: 200000000ugnot.\n// Total transfers: (in 0 times).\n//\n// Package address: g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7\n//\n// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n//\n// Controllers:\n//\n//\n//\n// Per request limit: 350000000ugnot\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/gnoland/faucet\"\n)\n\n// mints ugnot to current realm\nfunc init() {\n\tfacuetaddr := std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\tstd.TestIssueCoins(facuetaddr, std.Coins{{\"ugnot\", 200000000}})\n}\n\n// assert render with empty path and 2 controllers\nfunc main() {\n\tvar (\n\t\tadminaddr = std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")\n\t\tcontrolleraddr1 = testutils.TestAddress(\"controller1\")\n\t\tcontrolleraddr2 = testutils.TestAddress(\"controller2\")\n\t)\n\tstd.TestSetOriginCaller(adminaddr)\n\terr := faucet.AdminAddController(controlleraddr1)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\terr = faucet.AdminAddController(controlleraddr2)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\tprintln(faucet.Render(\"\"))\n}\n\n// Output:\n// # Community Faucet.\n//\n// Status: active.\n// Balance: 200000000ugnot.\n// Total transfers: (in 0 times).\n//\n// Package address: g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7\n//\n// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n//\n// Controllers:\n//\n// g1vdhkuarjdakxcetjx9047h6lta047h6lsdacav g1vdhkuarjdakxcetjxf047h6lta047h6lnrev3v\n//\n// Per request limit: 350000000ugnot\n"},{"name":"z3_filetest.gno","body":"package main\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/gnoland/faucet\"\n)\n\n// mints coints to current realm\nfunc init() {\n\tfacuetaddr := std.DerivePkgAddr(\"gno.land/r/gnoland/faucet\")\n\tstd.TestIssueCoins(facuetaddr, std.Coins{{\"ugnot\", 200000000}})\n}\n\n// assert render with 2 controllers and 2 transfers\nfunc main() {\n\tvar (\n\t\tadminaddr = std.Address(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")\n\t\tcontrolleraddr1 = testutils.TestAddress(\"controller1\")\n\t\tcontrolleraddr2 = testutils.TestAddress(\"controller2\")\n\t\ttestaddr1 = testutils.TestAddress(\"test1\")\n\t\ttestaddr2 = testutils.TestAddress(\"test2\")\n\t)\n\tstd.TestSetOriginCaller(adminaddr)\n\terr := faucet.AdminAddController(controlleraddr1)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\terr = faucet.AdminAddController(controlleraddr2)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\tstd.TestSetOriginCaller(controlleraddr1)\n\terr = faucet.Transfer(testaddr1, 1000000)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\tstd.TestSetOriginCaller(controlleraddr2)\n\terr = faucet.Transfer(testaddr1, 2000000)\n\tif err != \"\" {\n\t\tpanic(err)\n\t}\n\tprintln(faucet.Render(\"\"))\n}\n\n// Output:\n// # Community Faucet.\n//\n// Status: active.\n// Balance: 197000000ugnot.\n// Total transfers: 3000000ugnot (in 2 times).\n//\n// Package address: g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7\n//\n// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\n//\n// Controllers:\n//\n// g1vdhkuarjdakxcetjx9047h6lta047h6lsdacav g1vdhkuarjdakxcetjxf047h6lta047h6lnrev3v\n//\n// Per request limit: 350000000ugnot\n"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}} From 2a4eb5449b6f297d198451a7c2aa39d506d76f59 Mon Sep 17 00:00:00 2001 From: hthieu1110 Date: Sat, 21 Dec 2024 09:58:50 +0700 Subject: [PATCH 11/14] refactor: refactor new merged code --- examples/gno.land/r/x/jeronimo_render_proxy/home/home.gno | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/gno.land/r/x/jeronimo_render_proxy/home/home.gno b/examples/gno.land/r/x/jeronimo_render_proxy/home/home.gno index c73e99cc583..92b277d1b9c 100644 --- a/examples/gno.land/r/x/jeronimo_render_proxy/home/home.gno +++ b/examples/gno.land/r/x/jeronimo_render_proxy/home/home.gno @@ -25,7 +25,7 @@ func Register(fn RenderFn) { } proxyPath := std.CurrentRealm().PkgPath() - callerPath := std.PrevRealm().PkgPath() + callerPath := std.PreviousRealm().PkgPath() if !strings.HasPrefix(callerPath, proxyPath+"/") { panic("caller realm path must start with " + proxyPath) } From 93eec426d51b56c8ae29f1d928beecba93cd19bb Mon Sep 17 00:00:00 2001 From: "hieu.ha" Date: Mon, 30 Dec 2024 15:05:21 +0700 Subject: [PATCH 12/14] refactor: refactor new added code --- examples/gno.land/r/nemanya/config/config.gno | 2 +- examples/gno.land/r/nemanya/home/home.gno | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/gno.land/r/nemanya/config/config.gno b/examples/gno.land/r/nemanya/config/config.gno index 795e48c94c1..78fe329d5fe 100644 --- a/examples/gno.land/r/nemanya/config/config.gno +++ b/examples/gno.land/r/nemanya/config/config.gno @@ -52,7 +52,7 @@ func SetBackup(a std.Address) error { } func checkAuthorized() error { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() isAuthorized := caller == main || caller == backup if !isAuthorized { diff --git a/examples/gno.land/r/nemanya/home/home.gno b/examples/gno.land/r/nemanya/home/home.gno index 08e24baecfd..1c16d7e60d2 100644 --- a/examples/gno.land/r/nemanya/home/home.gno +++ b/examples/gno.land/r/nemanya/home/home.gno @@ -137,7 +137,7 @@ func renderProjects(projectsMap map[string]Project, title string) string { } func UpdateLink(name, newURL string) { - if !isAuthorized(std.PrevRealm().Addr()) { + if !isAuthorized(std.PreviousRealm().Address()) { panic(config.ErrUnauthorized) } @@ -152,7 +152,7 @@ func UpdateLink(name, newURL string) { } func UpdateAboutMe(text string) { - if !isAuthorized(std.PrevRealm().Addr()) { + if !isAuthorized(std.PreviousRealm().Address()) { panic(config.ErrUnauthorized) } @@ -160,7 +160,7 @@ func UpdateAboutMe(text string) { } func AddGnoProject(name, description, url, imageURL string) { - if !isAuthorized(std.PrevRealm().Addr()) { + if !isAuthorized(std.PreviousRealm().Address()) { panic(config.ErrUnauthorized) } project := Project{ @@ -174,7 +174,7 @@ func AddGnoProject(name, description, url, imageURL string) { } func DeleteGnoProject(projectName string) { - if !isAuthorized(std.PrevRealm().Addr()) { + if !isAuthorized(std.PreviousRealm().Address()) { panic(config.ErrUnauthorized) } @@ -186,7 +186,7 @@ func DeleteGnoProject(projectName string) { } func AddOtherProject(name, description, url, imageURL string) { - if !isAuthorized(std.PrevRealm().Addr()) { + if !isAuthorized(std.PreviousRealm().Address()) { panic(config.ErrUnauthorized) } project := Project{ @@ -200,7 +200,7 @@ func AddOtherProject(name, description, url, imageURL string) { } func RemoveOtherProject(projectName string) { - if !isAuthorized(std.PrevRealm().Addr()) { + if !isAuthorized(std.PreviousRealm().Address()) { panic(config.ErrUnauthorized) } @@ -216,8 +216,8 @@ func isAuthorized(addr std.Address) bool { } func SponsorGnoProject(projectName string) { - address := std.GetOrigCaller() - amount := std.GetOrigSend() + address := std.GetOriginCaller() + amount := std.GetOriginSend() if amount.AmountOf("ugnot") == 0 { panic("Donation must include GNOT") @@ -262,7 +262,7 @@ func SponsorOtherProject(projectName string) { } func Withdraw() string { - if !isAuthorized(std.PrevRealm().Addr()) { + if !isAuthorized(std.PreviousRealm().Address()) { panic(config.ErrUnauthorized) } From daf15745ec5c4590f74c656efa3417582355f08b Mon Sep 17 00:00:00 2001 From: "hieu.ha" Date: Mon, 30 Dec 2024 15:07:26 +0700 Subject: [PATCH 13/14] refactor: refactor new added code --- examples/gno.land/r/nemanya/home/home.gno | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/gno.land/r/nemanya/home/home.gno b/examples/gno.land/r/nemanya/home/home.gno index 1c16d7e60d2..dfa55b1152e 100644 --- a/examples/gno.land/r/nemanya/home/home.gno +++ b/examples/gno.land/r/nemanya/home/home.gno @@ -239,8 +239,8 @@ func SponsorGnoProject(projectName string) { } func SponsorOtherProject(projectName string) { - address := std.GetOrigCaller() - amount := std.GetOrigSend() + address := std.GetOriginCaller() + amount := std.GetOriginSend() if amount.AmountOf("ugnot") == 0 { panic("Donation must include GNOT") @@ -267,7 +267,7 @@ func Withdraw() string { } banker := std.GetBanker(std.BankerTypeRealmSend) - realmAddress := std.GetOrigPkgAddr() + realmAddress := std.GetOriginPkgAddress() coins := banker.GetCoins(realmAddress) if len(coins) == 0 { From ec5c4f1f59874da57318658f8751c942942ad33b Mon Sep 17 00:00:00 2001 From: "hieu.ha" Date: Mon, 30 Dec 2024 17:09:46 +0700 Subject: [PATCH 14/14] refactor: refactor new added code --- examples/gno.land/r/nemanya/home/home.gno | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/gno.land/r/nemanya/home/home.gno b/examples/gno.land/r/nemanya/home/home.gno index dfa55b1152e..589772691c4 100644 --- a/examples/gno.land/r/nemanya/home/home.gno +++ b/examples/gno.land/r/nemanya/home/home.gno @@ -216,8 +216,8 @@ func isAuthorized(addr std.Address) bool { } func SponsorGnoProject(projectName string) { - address := std.GetOriginCaller() - amount := std.GetOriginSend() + address := std.OriginCaller() + amount := std.OriginSend() if amount.AmountOf("ugnot") == 0 { panic("Donation must include GNOT") @@ -239,8 +239,8 @@ func SponsorGnoProject(projectName string) { } func SponsorOtherProject(projectName string) { - address := std.GetOriginCaller() - amount := std.GetOriginSend() + address := std.OriginCaller() + amount := std.OriginSend() if amount.AmountOf("ugnot") == 0 { panic("Donation must include GNOT")