-
Notifications
You must be signed in to change notification settings - Fork 441
feat(examples): add subscriptions package #4931
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
base: master
Are you sure you want to change the base?
Changes from all commits
9fb7747
579f023
75bdaa4
ad067c6
9981378
6fad397
de69fac
7fffcbc
14a3917
db5944e
b2d70ef
6c5f7da
e9c13bd
68013c9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,145 @@ | ||||||
| package subscriptions | ||||||
|
|
||||||
| import ( | ||||||
| "chain" | ||||||
| "chain/banker" | ||||||
| "chain/runtime" | ||||||
| "errors" | ||||||
| "strings" | ||||||
|
|
||||||
| "gno.land/r/demo/defi/grc20reg" | ||||||
| ) | ||||||
|
|
||||||
| // XXX: this part is 100% from payrolls realm from N0izN0iz <3 | ||||||
| // Payrolls realm: https://github.com/gnolang/gno/pull/3432 | ||||||
|
|
||||||
| // Coins transforms coins into subscriptions coins, prefixing their denom them with their type. Examples: "ugnot" -> "/native/ugnot", "gno.land/r/demo/foo20" -> "/grc20/gno.land/r/demo/foo20" | ||||||
| func Coins(cur realm, native chain.Coins, grc20 chain.Coins) (chain.Coins, error) { | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Or |
||||||
| out := make(chain.Coins, len(native)+len(grc20)) | ||||||
| for i, coin := range native { | ||||||
| out[i].Amount = coin.Amount | ||||||
| out[i].Denom = "/native/" + coin.Denom | ||||||
| } | ||||||
| offset := len(native) | ||||||
| for i, coin := range grc20 { | ||||||
| j := offset + i | ||||||
| out[j].Amount = coin.Amount | ||||||
| out[j].Denom = "/grc20/" + coin.Denom | ||||||
| } | ||||||
| return out, nil | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. error is always == to nil |
||||||
| } | ||||||
|
|
||||||
| func coinsHasPositive(coins chain.Coins) bool { | ||||||
| for _, coin := range coins { | ||||||
| if coin.Amount > 0 { | ||||||
| return true | ||||||
| } | ||||||
| } | ||||||
| return false | ||||||
| } | ||||||
|
|
||||||
| func addCoins(a chain.Coins, b chain.Coins) chain.Coins { | ||||||
| out := make(chain.Coins, len(a)) | ||||||
| copy(out, a) | ||||||
| for _, coin := range b { | ||||||
| out = addCoinAmount(out, coin) | ||||||
| } | ||||||
| return out | ||||||
| } | ||||||
|
|
||||||
| func subCoins(a chain.Coins, b chain.Coins) chain.Coins { | ||||||
| out := make(chain.Coins, len(a)) | ||||||
| copy(out, a) | ||||||
| for _, coin := range b { | ||||||
| out = addCoinAmount(out, chain.NewCoin(coin.Denom, -coin.Amount)) | ||||||
| } | ||||||
| return out | ||||||
| } | ||||||
|
|
||||||
| func multiplyCoins(coins chain.Coins, factor int64) chain.Coins { | ||||||
| out := make(chain.Coins, len(coins)) | ||||||
| for i, coin := range coins { | ||||||
| out[i] = chain.Coin{ | ||||||
| Denom: coin.Denom, | ||||||
| Amount: coin.Amount * factor, | ||||||
| } | ||||||
| } | ||||||
| return out | ||||||
| } | ||||||
|
|
||||||
| func lessCoinsThan(a chain.Coins, b chain.Coins) bool { | ||||||
| for _, coinA := range a { | ||||||
| found := false | ||||||
| for _, coinB := range b { | ||||||
| if coinA.Denom == coinB.Denom { | ||||||
| if coinA.Amount < coinB.Amount { | ||||||
| return true | ||||||
| } | ||||||
| found = true | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can remove the found variable and do the next condition in that line |
||||||
| break | ||||||
| } | ||||||
| } | ||||||
| if !found && coinA.Amount > 0 { | ||||||
| return false | ||||||
| } | ||||||
| } | ||||||
| return false | ||||||
| } | ||||||
|
|
||||||
| func addCoinAmount(coins chain.Coins, value chain.Coin) chain.Coins { | ||||||
| for i, coin := range coins { | ||||||
| if coin.Denom != value.Denom { | ||||||
| continue | ||||||
| } | ||||||
|
|
||||||
| out := make(chain.Coins, len(coins)) | ||||||
| copy(out, coins) | ||||||
| out[i].Amount += value.Amount | ||||||
| return out | ||||||
| } | ||||||
| return append(coins, value) | ||||||
| } | ||||||
|
|
||||||
| func sendCoins(dst address, coins chain.Coins) { | ||||||
| if len(coins) == 0 { | ||||||
| return | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of returning silently, we should return an error, a status or a panic |
||||||
| } | ||||||
|
|
||||||
| natives := chain.Coins{} | ||||||
| grc20s := chain.Coins{} | ||||||
|
|
||||||
| for _, coin := range coins { | ||||||
| if coin.Amount == 0 { | ||||||
| continue | ||||||
| } | ||||||
| if coin.Amount < 0 { | ||||||
| panic(errors.New("negative send amount")) | ||||||
| } | ||||||
|
|
||||||
| var ( | ||||||
| target *chain.Coins | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The value is always de-referenced, so the pointer is not necessary |
||||||
| denom string | ||||||
| ) | ||||||
|
|
||||||
| if strings.HasPrefix(coin.Denom, "/native/") { | ||||||
| target = &natives | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why extracting target + denom, if it's merged in the |
||||||
| denom = strings.TrimPrefix(coin.Denom, "/native/") | ||||||
| } else if strings.HasPrefix(coin.Denom, "/grc20/") { | ||||||
| target = &grc20s | ||||||
| denom = strings.TrimPrefix(coin.Denom, "/grc20/") | ||||||
| } else { | ||||||
| panic(errors.New("invalid coin denom prefix: " + coin.Denom)) | ||||||
| } | ||||||
| *target = addCoinAmount(*target, chain.NewCoin(denom, coin.Amount)) | ||||||
| } | ||||||
|
|
||||||
| banker_ := banker.NewBanker(banker.BankerTypeRealmIssue) | ||||||
| from := runtime.CurrentRealm().Address() | ||||||
| if len(natives) != 0 { | ||||||
| banker_.SendCoins(from, dst, natives) | ||||||
| } | ||||||
|
|
||||||
| for _, coin := range grc20s { | ||||||
| grc20reg.MustGet(coin.Denom).RealmTeller().Transfer(dst, coin.Amount) | ||||||
| } | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,243 @@ | ||
| package subscriptions | ||
|
|
||
| import ( | ||
| "chain" | ||
| "chain/banker" | ||
| "testing" | ||
|
|
||
| "gno.land/p/nt/testutils" | ||
| ) | ||
|
|
||
| func TestCoins(t *testing.T) { | ||
| native := chain.NewCoins( | ||
| chain.NewCoin("ugnot", 1000), | ||
| ) | ||
| grc20 := chain.NewCoins( | ||
| chain.NewCoin("gno.land/r/demo/foo20", 500), | ||
| ) | ||
| expected := chain.NewCoins( | ||
| chain.NewCoin("/native/ugnot", 1000), | ||
| chain.NewCoin("/grc20/gno.land/r/demo/foo20", 500), | ||
| ) | ||
|
|
||
| result, err := Coins(cross, native, grc20) | ||
| if err != nil { | ||
| t.Fatalf("unexpected error: %v", err) | ||
| } | ||
| for i, coin := range expected { | ||
| if result[i].Amount != coin.Amount { | ||
| t.Fatalf("invalid amount of %s got %d, want %d", coin.Denom, result[i].Amount, coin.Amount) | ||
| } | ||
| if result[i].Denom != coin.Denom { | ||
| t.Fatalf("invalid denom: got %s, want %s", result[i].Denom, coin.Denom) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func TestCoinsHasPositive(t *testing.T) { | ||
| tests := []struct { | ||
| coins chain.Coins | ||
| expected bool | ||
| }{ | ||
| {chain.NewCoins(chain.NewCoin("coinA", 0)), false}, | ||
| {chain.NewCoins(chain.NewCoin("coinA", -100)), false}, | ||
| {chain.NewCoins(chain.NewCoin("coinA", 50)), true}, | ||
| {chain.NewCoins(chain.NewCoin("coinA", 0), chain.NewCoin("coinB", 0)), false}, | ||
| {chain.NewCoins(chain.NewCoin("coinA", -10), chain.NewCoin("coinB", 20)), true}, | ||
| } | ||
|
|
||
| for _, test := range tests { | ||
| result := coinsHasPositive(test.coins) | ||
| if result != test.expected { | ||
| t.Fatalf("coinsHasPositive(%v) = %v; want %v", test.coins, result, test.expected) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func TestAddCoins(t *testing.T) { | ||
| a := chain.NewCoins( | ||
| chain.NewCoin("coinA", 100), | ||
| chain.NewCoin("coinB", 200), | ||
| ) | ||
| b := chain.NewCoins( | ||
| chain.NewCoin("coinB", 150), | ||
| chain.NewCoin("coinC", 300), | ||
| ) | ||
| expected := chain.NewCoins( | ||
| chain.NewCoin("coinA", 100), | ||
| chain.NewCoin("coinB", 350), | ||
| chain.NewCoin("coinC", 300), | ||
| ) | ||
|
|
||
| result := addCoins(a, b) | ||
| if len(result) != len(expected) { | ||
| t.Fatalf("invalid result length: got %d, want %d", len(result), len(expected)) | ||
| } | ||
| for _, coin := range expected { | ||
| found := false | ||
| for _, resCoin := range result { | ||
| if resCoin.Denom == coin.Denom { | ||
| found = true | ||
| if resCoin.Amount != coin.Amount { | ||
| t.Fatalf("invalid amount for %s: got %d, want %d", coin.Denom, resCoin.Amount, coin.Amount) | ||
| } | ||
| break | ||
| } | ||
| } | ||
| if !found { | ||
| t.Fatalf("missing coin in result: %s", coin.Denom) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func TestSubCoins(t *testing.T) { | ||
| a := chain.NewCoins( | ||
| chain.NewCoin("coinA", 300), | ||
| chain.NewCoin("coinB", 400), | ||
| ) | ||
| b := chain.NewCoins( | ||
| chain.NewCoin("coinB", 150), | ||
| chain.NewCoin("coinC", 100), | ||
| ) | ||
| expected := chain.NewCoins( | ||
| chain.NewCoin("coinA", 300), | ||
| chain.NewCoin("coinB", 250), | ||
| chain.NewCoin("coinC", -100), | ||
| ) | ||
|
|
||
| result := subCoins(a, b) | ||
| if len(result) != len(expected) { | ||
| t.Fatalf("invalid result length: got %d, want %d", len(result), len(expected)) | ||
| } | ||
| for _, coin := range expected { | ||
| found := false | ||
| for _, resCoin := range result { | ||
| if resCoin.Denom == coin.Denom { | ||
| found = true | ||
| if resCoin.Amount != coin.Amount { | ||
| t.Fatalf("invalid amount for %s: got %d, want %d", coin.Denom, resCoin.Amount, coin.Amount) | ||
| } | ||
| break | ||
| } | ||
| } | ||
| if !found { | ||
| t.Fatalf("missing coin in result: %s", coin.Denom) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func TestMultiplyCoins(t *testing.T) { | ||
| coins := chain.NewCoins( | ||
| chain.NewCoin("coinA", 100), | ||
| chain.NewCoin("coinB", 200), | ||
| ) | ||
| factor := int64(3) | ||
| expected := chain.NewCoins( | ||
| chain.NewCoin("coinA", 300), | ||
| chain.NewCoin("coinB", 600), | ||
| ) | ||
|
|
||
| result := multiplyCoins(coins, factor) | ||
| if len(result) != len(expected) { | ||
| t.Fatalf("invalid result length: got %d, want %d", len(result), len(expected)) | ||
| } | ||
| for _, coin := range expected { | ||
| found := false | ||
| for _, resCoin := range result { | ||
| if resCoin.Denom == coin.Denom { | ||
| found = true | ||
| if resCoin.Amount != coin.Amount { | ||
| t.Fatalf("invalid amount for %s: got %d, want %d", coin.Denom, resCoin.Amount, coin.Amount) | ||
| } | ||
| break | ||
| } | ||
| } | ||
| if !found { | ||
| t.Fatalf("missing coin in result: %s", coin.Denom) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func TestAddCoinAmount(t *testing.T) { | ||
| coins := chain.NewCoins( | ||
| chain.NewCoin("coinA", 100), | ||
| chain.NewCoin("coinB", 200), | ||
| ) | ||
| value := chain.NewCoin("coinB", 150) | ||
| expected := chain.NewCoins( | ||
| chain.NewCoin("coinA", 100), | ||
| chain.NewCoin("coinB", 350), | ||
| ) | ||
|
|
||
| result := addCoinAmount(coins, value) | ||
| if len(result) != len(expected) { | ||
| t.Fatalf("invalid result length: got %d, want %d", len(result), len(expected)) | ||
| } | ||
| for _, coin := range expected { | ||
| found := false | ||
| for _, resCoin := range result { | ||
| if resCoin.Denom == coin.Denom { | ||
| found = true | ||
| if resCoin.Amount != coin.Amount { | ||
| t.Fatalf("invalid amount for %s: got %d, want %d", coin.Denom, resCoin.Amount, coin.Amount) | ||
| } | ||
| break | ||
| } | ||
| } | ||
| if !found { | ||
| t.Fatalf("missing coin in result: %s", coin.Denom) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func TestSendCoins(t *testing.T) { | ||
| alice := testutils.TestAddress("alice") | ||
| testing.SetOriginCaller(alice) | ||
| testing.SetRealm(testing.NewUserRealm(alice)) | ||
| coinsToSend := chain.NewCoins( | ||
| chain.NewCoin("/native/gno.land/r/samcrew/subscriptions:a", 100), | ||
| chain.NewCoin("/native/gno.land/r/samcrew/subscriptions:b", 200), | ||
| ) | ||
|
|
||
| // without native prefix | ||
| coinsToIssue := chain.NewCoins( | ||
| chain.NewCoin("gno.land/r/samcrew/subscriptions:a", 100), | ||
| chain.NewCoin("gno.land/r/samcrew/subscriptions:b", 200), | ||
| ) | ||
|
|
||
| testing.IssueCoins(alice, coinsToIssue) | ||
|
|
||
| bob := testutils.TestAddress("bob") | ||
| testing.SetOriginSend(coinsToSend) | ||
|
|
||
| sendCoins(bob, coinsToSend) | ||
|
|
||
| banker := banker.NewBanker(banker.BankerTypeRealmIssue) | ||
| aliceCoins := banker.GetCoins(alice) | ||
| bobCoins := banker.GetCoins(bob) | ||
| for _, coin := range coinsToIssue { | ||
| aliceAmount := int64(0) | ||
| bobAmount := int64(0) | ||
| for _, aCoin := range aliceCoins { | ||
| if aCoin.Denom == coin.Denom { | ||
| aliceAmount = aCoin.Amount | ||
| break | ||
| } | ||
| } | ||
| for _, bCoin := range bobCoins { | ||
| if bCoin.Denom == coin.Denom { | ||
| bobAmount = bCoin.Amount | ||
| break | ||
| } | ||
| } | ||
|
|
||
| expectedAliceAmount := int64(0) | ||
| expectedBobAmount := coin.Amount | ||
| if aliceAmount != expectedAliceAmount { | ||
| t.Fatalf("invalid alice amount for %s: got %d, want %d", coin.Denom, aliceAmount, expectedAliceAmount) | ||
| } | ||
| if bobAmount != expectedBobAmount { | ||
| t.Fatalf("invalid bob amount for %s: got %d, want %d", coin.Denom, bobAmount, expectedBobAmount) | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| module = "gno.land/r/samcrew/subscriptions" | ||
| gno = "0.9" | ||
| private = true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be move to its own standalone package
/p/samcrew/coins/normalizedCoins.gnoOr something like that