-
Notifications
You must be signed in to change notification settings - Fork 375
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(examples): add disperse (v2) (#2613)
This PR adds a gno version of the [disperse ethereum app](https://disperse.app/) to gno.land ! Another attempt was made in #1414 but we have decided to pick up from @leohhhn 's work with @lennyvong and made this PR, tested using txtar tests to avoid the situation described in #2595 There is also an older (but functional for the most part) version [deployed in test4](https://test4.gno.land/r/g1w62226g8hykfmtuasvz80rdf0jl6phgxsphh5v/testing/disperse2?help) with a linked [webapp](https://gno-disperse.netlify.app/) <img width="649" alt="image" src="https://github.com/user-attachments/assets/ad26a1a9-0447-4333-858a-253441c457ba"> <details><summary>Contributors' checklist...</summary> - [X] Added new tests, or not needed, or not feasible - [X] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory - [X] Updated the official documentation or not needed - [X] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description - [X] Added references to related issues and PRs - [X] Provided any useful hints for running manual tests - [ ] Added new benchmarks to [generated graphs](https://gnoland.github.io/benchmarks), if any. More info [here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md). </details> --------- Co-authored-by: leohhhn <hudakleon@gmail.com> Co-authored-by: Leon Hudak <33522493+leohhhn@users.noreply.github.com> Co-authored-by: lennyvongphouthone <lenny.vongphouthone@epitech.eu> Co-authored-by: Guilhem Fanton <8671905+gfanton@users.noreply.github.com>
- Loading branch information
1 parent
04d5239
commit aae5d49
Showing
10 changed files
with
382 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package disperse | ||
|
||
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) | ||
|
||
if len(addresses) != len(coins) { | ||
panic(ErrNumAddrValMismatch) | ||
} | ||
|
||
for _, coin := range coins { | ||
if coin.Amount <= 0 { | ||
panic(ErrNegativeCoinAmount) | ||
} | ||
|
||
if banker.GetCoins(realmAddr).AmountOf(coin.Denom) < coin.Amount { | ||
panic(ErrMismatchBetweenSentAndParams) | ||
} | ||
} | ||
|
||
// 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) | ||
if leftoverAmt > 0 { | ||
send := std.Coins{std.NewCoin(coin.Denom, leftoverAmt)} | ||
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++ { | ||
tokens.TransferFrom(symbols[i], caller, addresses[i], 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.NewCoin("ugnot", amount) | ||
} | ||
|
||
DisperseUgnot(parsedAddresses, coins) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
ErrMismatchBetweenSentAndParams = errors.New("disperse: mismatch between coins sent and params called") | ||
ErrArgLenAndSentLenMismatch = errors.New("disperse: mismatch between coins sent and args called") | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
|
||
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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: mismatch between coins sent and params called |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
// 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("test1", "TEST1", 4, 0, 0) | ||
tokens.Mint("TEST1", mainaddr, 200) | ||
tokens.New("test2", "TEST2", 4, 0, 0) | ||
tokens.Mint("TEST2", mainaddr, 200) | ||
|
||
mainbal := tokens.BalanceOf("TEST1", mainaddr) + tokens.BalanceOf("TEST2", mainaddr) | ||
println("main before:", mainbal) | ||
|
||
tokens.Approve("TEST1", disperseAddr, 200) | ||
tokens.Approve("TEST2", disperseAddr, 200) | ||
|
||
disperse.DisperseGRC20String("g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c", "200TEST1,200TEST2") | ||
|
||
mainbal = tokens.BalanceOf("TEST1", mainaddr) + tokens.BalanceOf("TEST2", mainaddr) | ||
println("main after:", mainbal) | ||
ben1bal := tokens.BalanceOf("TEST1", beneficiary1) + tokens.BalanceOf("TEST2", beneficiary1) | ||
println("beneficiary1:", ben1bal) | ||
ben2bal := tokens.BalanceOf("TEST1", beneficiary2) + tokens.BalanceOf("TEST2", beneficiary2) | ||
println("beneficiary2:", ben2bal) | ||
} | ||
|
||
// Output: | ||
// main before: 400 | ||
// main after: 0 | ||
// beneficiary1: 200 | ||
// beneficiary2: 200 |