Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(examples): add disperse (v2) #2613

Merged
merged 58 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from 52 commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
b427d46
wip disperse
leohhhn Nov 28, 2023
0c80d55
add disperse PoC
leohhhn Dec 5, 2023
f4d352e
Merge branch 'gnolang:master' into feat/add-disperse
leohhhn Dec 5, 2023
405f690
GNOT disperse PoC complete
leohhhn Dec 5, 2023
b0889c7
comments, formatting
leohhhn Dec 5, 2023
70354ef
save
leohhhn Dec 10, 2023
a5a5324
wip add tests
leohhhn Dec 12, 2023
f14a6ed
Merge branch 'master' into feat/add-disperse
leohhhn Dec 13, 2023
fe220f3
wip tests
leohhhn Dec 14, 2023
70d91b1
Merge branch 'master' into feat/add-disperse
leohhhn Dec 14, 2023
3637e81
remove blocked tests
leohhhn Dec 14, 2023
813b331
Merge branch 'gnolang:master' into feat/add-disperse
leohhhn Dec 23, 2023
6cb1b0e
generalize to sendcoin
leohhhn Dec 24, 2023
39aaf1b
txtar testing basics
leohhhn Dec 25, 2023
66db7cb
Merge branch 'master' into feat/add-disperse
leohhhn Jan 16, 2024
cc14f6d
Merge branch 'master' into feat/add-disperse
leohhhn Jan 23, 2024
eac2b10
remove keys db
leohhhn Feb 5, 2024
40ba918
add tests
leohhhn Feb 5, 2024
80389b8
Merge branch 'master' into feat/add-disperse
leohhhn Feb 5, 2024
9079f54
wip
leohhhn Feb 7, 2024
724656a
refactor
leohhhn Feb 22, 2024
c9f5ed5
Merge branch 'master' into feat/add-disperse
agherasie Jul 16, 2024
86c1d37
feat: add disperse gnot and token
agherasie Jul 16, 2024
ebf397d
Update:
lennyvong Jul 22, 2024
d1106bc
chore(examples): disperse fmt and tidy
agherasie Jul 22, 2024
a542d4d
chore(examples): disperse godoc
agherasie Jul 22, 2024
1a73a65
Merge branch 'master' into feat/add-disperse
agherasie Jul 22, 2024
5157b66
Update examples/gno.land/r/demo/disperse/disperse.gno
agherasie Jul 30, 2024
7437f60
fix(examples): disperse checker function
agherasie Jul 30, 2024
6418040
feat(examples): disperse file tests
agherasie Jul 30, 2024
c30e1f6
chore(examples): fmt disperse
agherasie Jul 30, 2024
f427841
Merge branch 'master' into feat/add-disperse
agherasie Jul 30, 2024
885d791
Update examples/gno.land/r/demo/disperse/disperse.gno
agherasie Aug 7, 2024
86b0671
Update examples/gno.land/r/demo/disperse/disperse.gno
agherasie Aug 7, 2024
78ee151
Update examples/gno.land/r/demo/disperse/util.gno
agherasie Aug 7, 2024
de01899
Update examples/gno.land/r/demo/disperse/disperse.gno
agherasie Aug 7, 2024
28e029a
fix(examples): disperse helper functions at bottom
agherasie Aug 7, 2024
d41e44c
fix(examples): disperse ugnot instead of gnot
agherasie Aug 7, 2024
7f8aa84
fix(examples): disperse global state realmAddr
agherasie Aug 7, 2024
cfc2ee9
fix(examples): disperse common helper function
agherasie Aug 7, 2024
d91b227
feat(examples): disperse doc.gno
agherasie Aug 7, 2024
5fd031e
fix(examples): disperse err format
agherasie Aug 7, 2024
042ec41
fix(examples): disperse grc20 function name
agherasie Aug 7, 2024
20a428c
fix(examples): disperse comments
agherasie Aug 7, 2024
7928cb1
fix(examples): mistake in comment
agherasie Aug 7, 2024
f2fa497
fix(examples): disperse document leftover functionality
agherasie Aug 7, 2024
b70a621
fix(examples): disperse unnecessary error check
agherasie Aug 7, 2024
56d0716
fix(examples): disperse not using std.Coins for tokens
agherasie Aug 8, 2024
73f42ff
chore(examples): fmt
agherasie Aug 8, 2024
0a87b66
chore(examples): disperse new line after block
agherasie Aug 8, 2024
dcd81fe
feat(examples): disperse comment on approval
agherasie Aug 8, 2024
63df55e
Merge branch 'master' into feat/add-disperse
agherasie Aug 8, 2024
d05632a
fix(examples): redundant disperse logic
agherasie Aug 16, 2024
286da83
fix(examples): disperse using NewCoin
agherasie Aug 16, 2024
128a8e8
feat(examples): disperse check denoms per sent and called
agherasie Aug 16, 2024
5b0ef03
fix(examples): remove unused logic
agherasie Aug 16, 2024
a6e970d
chore(examples): fmt disperse
agherasie Aug 16, 2024
eb1dc54
Merge branch 'master' into feat/add-disperse
leohhhn Aug 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions examples/gno.land/r/demo/disperse/disperse.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package disperse
leohhhn marked this conversation as resolved.
Show resolved Hide resolved

import (
"std"

tokens "gno.land/r/demo/grc20factory"
)

// Get address of Disperse realm
var realmAddr = std.CurrentRealm().Addr()

// 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.GetOrigSend()
caller := std.PrevRealm().Addr()
banker := std.GetBanker(std.BankerTypeOrigSend)

// Check if realm balance is equal to amount sent
if banker.GetCoins(realmAddr).AmountOf("ugnot") != coinSent.AmountOf("ugnot") {
leohhhn marked this conversation as resolved.
Show resolved Hide resolved
panic(ErrBalanceNotEqToAmountSent)
}

if len(addresses) != len(coins) {
panic(ErrNumAddrValMismatch)
}

for _, coin := range coins {
if coin.Amount <= 0 {
panic(ErrNegativeCoinAmount)
}
}

var totalAmount int64
for _, coin := range coins {
totalAmount += coin.Amount
}
leohhhn marked this conversation as resolved.
Show resolved Hide resolved

// Send coins
for i, _ := range addresses {
banker.SendCoins(realmAddr, addresses[i], std.NewCoins(coins[i]))
}

// Return possible leftover coins
for _, coin := range coinSent {
leftoverAmt := banker.GetCoins(realmAddr).AmountOf(coin.Denom)
leohhhn marked this conversation as resolved.
Show resolved Hide resolved
if leftoverAmt > 0 {
send := std.Coins{{coin.Denom, leftoverAmt}}
leohhhn marked this conversation as resolved.
Show resolved Hide resolved
banker.SendCoins(realmAddr, caller, send)
}
}
}

// DisperseGRC20 disperses tokens to multiple addresses
// 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()

if (len(addresses) != len(amounts)) || (len(amounts) != len(symbols)) {
panic(ErrArgLenAndSentLenMismatch)
}

for i := 0; i < len(addresses); i++ {
// Transfer tokens into the realm
tokens.TransferFrom(symbols[i], caller, std.CurrentRealm().Addr(), amounts[i])
leohhhn marked this conversation as resolved.
Show resolved Hide resolved
// Transfer tokens to the addresses
tokens.Transfer(symbols[i], addresses[i], uint64(amounts[i]))
}
}

// DisperseGRC20String receives a string of addresses and a string of tokens
// and parses them to be used in DisperseGRC20
func DisperseGRC20String(addresses string, tokens string) {
parsedAddresses, err := parseAddresses(addresses)
if err != nil {
panic(err)
}

parsedAmounts, parsedSymbols, err := parseTokens(tokens)
if err != nil {
panic(err)
}

DisperseGRC20(parsedAddresses, parsedAmounts, parsedSymbols)
}

// DisperseUgnotString receives a string of addresses and a string of amounts
// and parses them to be used in DisperseUgnot
func DisperseUgnotString(addresses string, amounts string) {
parsedAddresses, err := parseAddresses(addresses)
if err != nil {
panic(err)
}

parsedAmounts, err := parseAmounts(amounts)
if err != nil {
panic(err)
}

coins := make(std.Coins, len(parsedAmounts))
for i, amount := range parsedAmounts {
coins[i] = std.Coin{"ugnot", amount}
leohhhn marked this conversation as resolved.
Show resolved Hide resolved
}

DisperseUgnot(parsedAddresses, coins)
}
19 changes: 19 additions & 0 deletions examples/gno.land/r/demo/disperse/doc.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Package disperse provides methods to disperse coins or GRC20 tokens among multiple addresses.
//
// The disperse package is an implementation of an existing service that allows users to send coins or GRC20 tokens to multiple addresses
// on the Ethereum blockchain.
//
// Usage:
// To use disperse, you can either use `DisperseUgnot` to send coins or `DisperseGRC20` to send GRC20 tokens to multiple addresses.
//
// Example:
// Dispersing 200 coins to two addresses:
// - DisperseUgnotString("g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c", "150,50")
// Dispersing 200 worth of a GRC20 token "TEST" to two addresses:
// - DisperseGRC20String("g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c", "150TEST,50TEST")
//
// Reference:
// - [the original dispere app](https://disperse.app/)
// - [the original disperse app on etherscan](https://etherscan.io/address/0xd152f549545093347a162dce210e7293f1452150#code)
// - [the gno disperse web app](https://gno-disperse.netlify.app/)
package disperse // import "gno.land/r/demo/disperse"
12 changes: 12 additions & 0 deletions examples/gno.land/r/demo/disperse/errors.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package disperse

import "errors"

var (
ErrNotEnoughCoin = errors.New("disperse: not enough coin sent in")
ErrNumAddrValMismatch = errors.New("disperse: number of addresses and values to send doesn't match")
ErrInvalidAddress = errors.New("disperse: invalid address")
ErrNegativeCoinAmount = errors.New("disperse: coin amount cannot be negative")
ErrBalanceNotEqToAmountSent = errors.New("disperse: balance needs to be equal to amount sent")
ErrArgLenAndSentLenMismatch = errors.New("disperse: mismatch between coins sent and args called")
)
3 changes: 3 additions & 0 deletions examples/gno.land/r/demo/disperse/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module gno.land/r/demo/disperse

require gno.land/r/demo/grc20factory v0.0.0-latest
67 changes: 67 additions & 0 deletions examples/gno.land/r/demo/disperse/util.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package disperse

import (
"std"
"strconv"
"strings"
"unicode"
)

func parseAddresses(addresses string) ([]std.Address, error) {
var ret []std.Address

for _, str := range strings.Split(addresses, ",") {
addr := std.Address(str)
if !addr.IsValid() {
return nil, ErrInvalidAddress
}
agherasie marked this conversation as resolved.
Show resolved Hide resolved

ret = append(ret, addr)
}

return ret, nil
}

func splitString(input string) (string, string) {
var pos int
for i, char := range input {
if !unicode.IsDigit(char) {
pos = i
break
}
}
return input[:pos], input[pos:]
}

func parseTokens(tokens string) ([]uint64, []string, error) {
var amounts []uint64
var symbols []string

for _, token := range strings.Split(tokens, ",") {
amountStr, symbol := splitString(token)
amount, _ := strconv.Atoi(amountStr)
if amount < 0 {
return nil, nil, ErrNegativeCoinAmount
}

amounts = append(amounts, uint64(amount))
symbols = append(symbols, symbol)
}

return amounts, symbols, nil
}

func parseAmounts(amounts string) ([]int64, error) {
var ret []int64

for _, amt := range strings.Split(amounts, ",") {
amount, _ := strconv.Atoi(amt)
if amount < 0 {
return nil, ErrNegativeCoinAmount
}

ret = append(ret, int64(amount))
}

return ret, nil
}
32 changes: 32 additions & 0 deletions examples/gno.land/r/demo/disperse/z_0_filetest.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SEND: 200ugnot

package main

import (
"std"

"gno.land/r/demo/disperse"
)

func main() {
disperseAddr := std.DerivePkgAddr("gno.land/r/demo/disperse")
mainaddr := std.DerivePkgAddr("main")

std.TestSetOrigPkgAddr(disperseAddr)
std.TestSetOrigCaller(mainaddr)

banker := std.GetBanker(std.BankerTypeRealmSend)

mainbal := banker.GetCoins(mainaddr)
println("main before:", mainbal)

banker.SendCoins(mainaddr, disperseAddr, std.Coins{{"ugnot", 200}})
disperse.DisperseUgnotString("g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c", "150,50")

mainbal = banker.GetCoins(mainaddr)
println("main after:", mainbal)
}

// Output:
// main before: 200000200ugnot
// main after: 200000000ugnot
32 changes: 32 additions & 0 deletions examples/gno.land/r/demo/disperse/z_1_filetest.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SEND: 300ugnot

package main

import (
"std"

"gno.land/r/demo/disperse"
)

func main() {
disperseAddr := std.DerivePkgAddr("gno.land/r/demo/disperse")
mainaddr := std.DerivePkgAddr("main")

std.TestSetOrigPkgAddr(disperseAddr)
std.TestSetOrigCaller(mainaddr)

banker := std.GetBanker(std.BankerTypeRealmSend)

mainbal := banker.GetCoins(mainaddr)
println("main before:", mainbal)

banker.SendCoins(mainaddr, disperseAddr, std.Coins{{"ugnot", 300}})
disperse.DisperseUgnotString("g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c", "150,50")

mainbal = banker.GetCoins(mainaddr)
println("main after:", mainbal)
}

// Output:
// main before: 200000300ugnot
// main after: 200000100ugnot
25 changes: 25 additions & 0 deletions examples/gno.land/r/demo/disperse/z_2_filetest.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SEND: 300ugnot

package main

import (
"std"

"gno.land/r/demo/disperse"
)

func main() {
disperseAddr := std.DerivePkgAddr("gno.land/r/demo/disperse")
mainaddr := std.DerivePkgAddr("main")

std.TestSetOrigPkgAddr(disperseAddr)
std.TestSetOrigCaller(mainaddr)

banker := std.GetBanker(std.BankerTypeRealmSend)

banker.SendCoins(mainaddr, disperseAddr, std.Coins{{"ugnot", 100}})
disperse.DisperseUgnotString("g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c", "150,50")
}

// Error:
// disperse: balance needs to be equal to amount sent
45 changes: 45 additions & 0 deletions examples/gno.land/r/demo/disperse/z_3_filetest.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SEND: 300ugnot

package main

import (
"std"

"gno.land/r/demo/disperse"
tokens "gno.land/r/demo/grc20factory"
)

func main() {
disperseAddr := std.DerivePkgAddr("gno.land/r/demo/disperse")
mainaddr := std.DerivePkgAddr("main")
beneficiary1 := std.Address("g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0")
beneficiary2 := std.Address("g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c")

std.TestSetOrigPkgAddr(disperseAddr)
std.TestSetOrigCaller(mainaddr)

banker := std.GetBanker(std.BankerTypeRealmSend)

tokens.New("test", "TEST", 4, 0, 0)
tokens.Mint("TEST", mainaddr, 200)

mainbal := tokens.BalanceOf("TEST", mainaddr)
println("main before:", mainbal)

tokens.Approve("TEST", disperseAddr, 200)

disperse.DisperseGRC20String("g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c", "150TEST,50TEST")

mainbal = tokens.BalanceOf("TEST", mainaddr)
println("main after:", mainbal)
ben1bal := tokens.BalanceOf("TEST", beneficiary1)
println("beneficiary1:", ben1bal)
ben2bal := tokens.BalanceOf("TEST", beneficiary2)
println("beneficiary2:", ben2bal)
}

// Output:
// main before: 200
// main after: 0
// beneficiary1: 150
// beneficiary2: 50
Loading
Loading