From 3ec2f722138a146d38630770529ce752064dc703 Mon Sep 17 00:00:00 2001 From: Leon Hudak <33522493+leohhhn@users.noreply.github.com> Date: Mon, 27 May 2024 15:04:00 +0200 Subject: [PATCH] feat(gnovm): add `Coin` constructor and more functionality (#2104) ## Description This PR ports more functionality from [coin.go](https://github.com/gnolang/gno/blob/master/tm2/pkg/std/coin.go?rgh-link-date=2024-02-27T11%3A01%3A45Z), into Gno (std & stdshim). It will also update the concept & reference docs for Coins. Superseding https://github.com/gnolang/gno/pull/1696
Contributors' checklist... - [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).
--------- Co-authored-by: Morgan --- docs/reference/stdlibs/std/coin.md | 148 +++++++++++++++++- docs/reference/stdlibs/std/coins.md | 17 ++ examples/gno.land/r/demo/boards/public.gno | 2 +- examples/gno.land/r/demo/users/users.gno | 2 +- .../gno.land/r/demo/users/z_0_filetest.gno | 4 +- examples/gno.land/r/gnoland/faucet/admin.gno | 2 +- examples/gno.land/r/gnoland/faucet/faucet.gno | 4 +- .../cmd/gnoland/testdata/issue_1786.txtar | 2 +- .../realm_banker_issued_coin_denom.txtar | 4 +- .../integration/testdata/improved-coins.txtar | 86 ++++++++++ gnovm/cmd/gno/transpile.go | 4 +- gnovm/stdlibs/std/coins.gno | 137 ++++++++++++++-- gnovm/stdlibs/stdshim/coins.gno | 125 ++++++++++++++- gnovm/tests/files/zrealm_natbind0.gno | 2 +- 14 files changed, 502 insertions(+), 37 deletions(-) create mode 100644 gno.land/pkg/integration/testdata/improved-coins.txtar diff --git a/docs/reference/stdlibs/std/coin.md b/docs/reference/stdlibs/std/coin.md index f2e1cf694b4..3675e1365f6 100644 --- a/docs/reference/stdlibs/std/coin.md +++ b/docs/reference/stdlibs/std/coin.md @@ -10,31 +10,167 @@ type Coin struct { Denom string `json:"denom"` Amount int64 `json:"amount"` } + +func NewCoin(denom string, amount int64) Coin {...} func (c Coin) String() string {...} func (c Coin) IsGTE(other Coin) bool {...} +func (c Coin) IsLT(other Coin) bool {...} +func (c Coin) IsEqual(other Coin) bool {...} +func (c Coin) Add(other Coin) Coin {...} +func (c Coin) Sub(other Coin) Coin {...} +func (c Coin) IsPositive() bool {...} +func (c Coin) IsNegative() bool {...} +func (c Coin) IsZero() bool {...} ``` +## NewCoin +Returns a new Coin with a specific denomination and amount. + +#### Usage +```go +coin := std.NewCoin("ugnot", 100) +``` +--- + ## String Returns a string representation of the `Coin` it was called upon. #### Usage ```go -coin := std.Coin{"ugnot", 100} +coin := std.NewCoin("ugnot", 100) coin.String() // 100ugnot ``` --- + ## IsGTE -Checks if the amount of `other` Coin is greater or equal than amount of Coin `c` it was called upon. -If coins compared are not of the same denomination, `IsGTE` will panic. +Checks if the amount of `other` Coin is greater than or equal than amount of +Coin `c` it was called upon. If coins compared are not of the same denomination, +`IsGTE` will panic. #### Parameters - `other` **Coin** to compare with #### Usage ```go -coin1 := std.Coin{"ugnot", 150} -coin2 := std.Coin{"ugnot", 100} +coin1 := std.NewCoin("ugnot", 150) +coin2 := std.NewCoin("ugnot", 100) coin1.IsGTE(coin2) // true coin2.IsGTE(coin1) // false -``` \ No newline at end of file +``` +--- + +## IsLT +Checks if the amount of `other` Coin is less than the amount of Coin `c` it was +called upon. If coins compared are not of the same denomination, `IsLT` will +panic. + +#### Parameters +- `other` **Coin** to compare with + +#### Usage +```go +coin := std.NewCoin("ugnot", 150) +coin := std.NewCoin("ugnot", 100) + +coin1.IsLT(coin2) // false +coin2.IsLT(coin1) // true +``` +--- + +## IsEqual +Checks if the amount of `other` Coin is equal to the amount of Coin `c` it was +called upon. If coins compared are not of the same denomination, `IsEqual` will +panic. + +#### Parameters +- `other` **Coin** to compare with + +#### Usage +```go +coin1 := std.NewCoin("ugnot", 150) +coin2 := std.NewCoin("ugnot", 100) +coin3 := std.NewCoin("ugnot", 100) + +coin1.IsEqual(coin2) // false +coin2.IsEqual(coin1) // false +coin2.IsEqual(coin3) // true +``` +--- + +## Add +Adds two coins of the same denomination. If coins are not of the same +denomination, `Add` will panic. If final amount is larger than the maximum size +of `int64`, `Add` will panic with an overflow error. Adding a negative amount +will result in subtraction. + +#### Parameters +- `other` **Coin** to add + +#### Usage +```go +coin1 := std.NewCoin("ugnot", 150) +coin2 := std.NewCoin("ugnot", 100) + +coin3 := coin1.Add(coin2) +coin3.String() // 250ugnot +``` +--- + +## Sub +Subtracts two coins of the same denomination. If coins are not of the same +denomination, `Sub` will panic. If final amount is smaller than the minimum size +of `int64`, `Sub` will panic with an underflow error. Subtracting a negative amount +will result in addition. + +#### Parameters +- `other` **Coin** to subtract + +#### Usage +```go +coin1 := std.NewCoin("ugnot", 150) +coin2 := std.NewCoin("ugnot", 100) + +coin3 := coin1.Sub(coin2) +coin3.String() // 50ugnot +``` +--- + +## IsPositive +Checks if a coin amount is positive. + +#### Usage +```go +coin1 := std.NewCoin("ugnot", 150) +coin2 := std.NewCoin("ugnot", -150) + +coin1.IsPositive() // true +coin2.IsPositive() // false +``` +--- + +## IsNegative +Checks if a coin amount is negative. + +#### Usage +```go +coin1 := std.NewCoin("ugnot", 150) +coin2 := std.NewCoin("ugnot", -150) + +coin1.IsNegative() // false +coin2.IsNegative() // true +``` +--- + +## IsZero +Checks if a coin amount is zero. + +#### Usage +```go +coin1 := std.NewCoin("ugnot", 150) +coin2 := std.NewCoin("ugnot", 0) + +coin1.IsZero() // false +coin2.IsZero() // true +``` + diff --git a/docs/reference/stdlibs/std/coins.md b/docs/reference/stdlibs/std/coins.md index 0f813178330..ea38ce51580 100644 --- a/docs/reference/stdlibs/std/coins.md +++ b/docs/reference/stdlibs/std/coins.md @@ -8,11 +8,28 @@ id: coins ```go type Coins []Coin + +func NewCoins(coins ...Coin) Coins {...} func (c Coins) String() string {...} func (c Coins) AmountOf(denom string) int64 {...} func (c Coins) Add(other Coins) Coins {...} ``` +### NewCoins +Returns a new set of `Coins` given one or more `Coin`. Consolidates any denom +duplicates into one, keeping the properties of a mathematical set. + +#### Usage +```go +coin1 := std.NewCoin("ugnot", 150) +coin2 := std.NewCoin("example", 100) +coin3 := std.NewCoin("ugnot", 100) + +coins := std.NewCoins(coin1, coin2, coin3) +coins.String() // 250ugnot, 100example +``` +--- + ### String Returns a string representation of the `Coins` set it was called upon. diff --git a/examples/gno.land/r/demo/boards/public.gno b/examples/gno.land/r/demo/boards/public.gno index 6643c863cec..1ef2e72f4c2 100644 --- a/examples/gno.land/r/demo/boards/public.gno +++ b/examples/gno.land/r/demo/boards/public.gno @@ -33,7 +33,7 @@ func CreateBoard(name string) BoardID { func checkAnonFee() bool { sent := std.GetOrigSend() - anonFeeCoin := std.Coin{"ugnot", int64(gDefaultAnonFee)} + anonFeeCoin := std.NewCoin("ugnot", int64(gDefaultAnonFee)) if len(sent) == 1 && sent[0].IsGTE(anonFeeCoin) { return true } diff --git a/examples/gno.land/r/demo/users/users.gno b/examples/gno.land/r/demo/users/users.gno index 4fe486a71d0..5b4b8ec2c14 100644 --- a/examples/gno.land/r/demo/users/users.gno +++ b/examples/gno.land/r/demo/users/users.gno @@ -35,7 +35,7 @@ func Register(inviter std.Address, name string, profile string) { panic("should not happen") // because std.AssertOrigCall(). } sentCoins := std.GetOrigSend() - minCoin := std.Coin{"ugnot", minFee} + minCoin := std.NewCoin("ugnot", minFee) if inviter == "" { // banker := std.GetBanker(std.BankerTypeOrigSend) if len(sentCoins) == 1 && sentCoins[0].IsGTE(minCoin) { 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 45d789b1a3e..cbb2e9209f4 100644 --- a/examples/gno.land/r/demo/users/z_0_filetest.gno +++ b/examples/gno.land/r/demo/users/z_0_filetest.gno @@ -7,10 +7,10 @@ import ( ) func main() { - std.TestSetOrigSend(std.Coins{{"dontcare", 1}}, nil) + std.TestSetOrigSend(std.Coins{std.NewCoin("dontcare", 1)}, nil) users.Register("", "gnouser", "my profile") println("done") } // Error: -// invalid coin denominations: dontcare +// incompatible coin denominations: dontcare, ugnot diff --git a/examples/gno.land/r/gnoland/faucet/admin.gno b/examples/gno.land/r/gnoland/faucet/admin.gno index a4916c5af02..37108059b74 100644 --- a/examples/gno.land/r/gnoland/faucet/admin.gno +++ b/examples/gno.land/r/gnoland/faucet/admin.gno @@ -25,7 +25,7 @@ func AdminSetTransferLimit(amount int64) string { if err := assertIsAdmin(); err != nil { return err.Error() } - gLimit = std.Coin{Denom: "ugnot", Amount: amount} + gLimit = std.NewCoin("ugnot", amount) return "" } diff --git a/examples/gno.land/r/gnoland/faucet/faucet.gno b/examples/gno.land/r/gnoland/faucet/faucet.gno index 038c7283fa1..908b86d4aaf 100644 --- a/examples/gno.land/r/gnoland/faucet/faucet.gno +++ b/examples/gno.land/r/gnoland/faucet/faucet.gno @@ -21,7 +21,7 @@ var ( gTotalTransfers = uint(0) // per request limit, 350 gnot - gLimit std.Coin = std.Coin{"ugnot", 350000000} + gLimit std.Coin = std.NewCoin("ugnot", 350000000) ) func Transfer(to std.Address, send int64) string { @@ -37,7 +37,7 @@ func Transfer(to std.Address, send int64) string { if send > gLimit.Amount { return errors.New("Per request limit " + gLimit.String() + " exceed").Error() } - sendCoins := std.Coins{std.Coin{Denom: "ugnot", Amount: send}} + sendCoins := std.Coins{std.NewCoin("ugnot", send)} gTotalTransferred = gTotalTransferred.Add(sendCoins) gTotalTransfers++ diff --git a/gno.land/cmd/gnoland/testdata/issue_1786.txtar b/gno.land/cmd/gnoland/testdata/issue_1786.txtar index 44ea17674c9..7c92e81dfb6 100644 --- a/gno.land/cmd/gnoland/testdata/issue_1786.txtar +++ b/gno.land/cmd/gnoland/testdata/issue_1786.txtar @@ -5,7 +5,7 @@ loadpkg gno.land/r/demo/wugnot gnoland start # add contract -gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/demo/proxywugnot -gas-fee 1000000ugnot -gas-wanted 4000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/demo/proxywugnot -gas-fee 1000000ugnot -gas-wanted 6000000 -broadcast -chainid=tendermint_test test1 stdout OK! # approve wugnot to `proxywugnot ≈ g1fndyg0we60rdfchyy5dwxzkfmhl5u34j932rg3` diff --git a/gno.land/cmd/gnoland/testdata/realm_banker_issued_coin_denom.txtar b/gno.land/cmd/gnoland/testdata/realm_banker_issued_coin_denom.txtar index abc3615d79c..71ef6400471 100644 --- a/gno.land/cmd/gnoland/testdata/realm_banker_issued_coin_denom.txtar +++ b/gno.land/cmd/gnoland/testdata/realm_banker_issued_coin_denom.txtar @@ -7,10 +7,10 @@ adduser test2 gnoland start ## add realm_banker -gnokey maketx addpkg -pkgdir $WORK/short -pkgpath gno.land/r/test/realm_banker -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx addpkg -pkgdir $WORK/short -pkgpath gno.land/r/test/realm_banker -gas-fee 1000000ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 ## add realm_banker with long package_name -gnokey maketx addpkg -pkgdir $WORK/long -pkgpath gno.land/r/test/package89_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_1234567890 -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx addpkg -pkgdir $WORK/long -pkgpath gno.land/r/test/package89_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_1234567890 -gas-fee 1000000ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 ## test2 spend all balance gnokey maketx send -send "9999999ugnot" -to g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 -gas-fee 1ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test2 diff --git a/gno.land/pkg/integration/testdata/improved-coins.txtar b/gno.land/pkg/integration/testdata/improved-coins.txtar new file mode 100644 index 00000000000..8950371afb3 --- /dev/null +++ b/gno.land/pkg/integration/testdata/improved-coins.txtar @@ -0,0 +1,86 @@ +loadpkg gno.land/r/demo/coins $WORK + +gnoland start + +gnokey maketx call -pkgpath gno.land/r/demo/coins -func "MakeNewCoins" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout '(300 int64)' +stdout '(321 int64)' +stdout '("ugnot" string)' +stdout '("example" string)' + +gnokey maketx call -pkgpath gno.land/r/demo/coins -func "AddCoin" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout '(300 int64)' + +gnokey maketx call -pkgpath gno.land/r/demo/coins -func "SubCoin" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout '(123 int64)' + +gnokey maketx call -pkgpath gno.land/r/demo/coins -func "StringZeroCoin" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout '("0ugnot" string)' + +gnokey maketx call -pkgpath gno.land/r/demo/coins -func "IsZero" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout '(true bool)' +stdout '(false bool)' +stdout '(false bool)' + +gnokey maketx call -pkgpath gno.land/r/demo/coins -func "IsPositive" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout '(false bool)' +stdout '(false bool)' +stdout '(true bool)' + +gnokey maketx call -pkgpath gno.land/r/demo/coins -func "IsNegative" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout '(true bool)' +stdout '(false bool)' +stdout '(false bool)' + +-- coins.gno -- +package coins + +import "std" + +func MakeNewCoins() std.Coins { + coin1 := std.NewCoin("ugnot", 123) + coin2 := std.NewCoin("example", 321) + coin3 := std.NewCoin("ugnot", 177) + + + return std.NewCoins(coin1, coin2, coin3) +} + +func AddCoin() std.Coin { + coin1 := std.NewCoin("ugnot", 123) + coin2 := std.NewCoin("ugnot", 177) + return coin1.Add(coin2) +} + +func SubCoin() std.Coin { + coin1 := std.NewCoin("ugnot", 300) + coin2 := std.NewCoin("ugnot", 177) + return coin1.Sub(coin2) +} + +func StringZeroCoin() string { + coin1 := std.NewCoin("ugnot", 0) + return coin1.String() +} + +func IsZero() (bool, bool, bool) { + coin1 := std.NewCoin("ugnot", 0) + coin2 := std.NewCoin("ugnot", 123) + coin3 := std.NewCoin("ugnot", -123) + return coin1.IsZero(), coin2.IsZero(), coin3.IsZero() +} + +func IsPositive() (bool, bool, bool) { + coin1 := std.NewCoin("ugnot", -123) + coin2 := std.NewCoin("ugnot", 0) + coin3 := std.NewCoin("ugnot", 123) + return coin1.IsPositive(), coin2.IsPositive(), coin3.IsPositive() +} + +func IsNegative() (bool, bool, bool) { + coin1 := std.NewCoin("ugnot", -123) + coin2 := std.NewCoin("ugnot", 0) + coin3 := std.NewCoin("ugnot", 123) + return coin1.IsNegative(), coin2.IsNegative(), coin3.IsNegative() +} + diff --git a/gnovm/cmd/gno/transpile.go b/gnovm/cmd/gno/transpile.go index 3469304bea2..7d01c603555 100644 --- a/gnovm/cmd/gno/transpile.go +++ b/gnovm/cmd/gno/transpile.go @@ -250,7 +250,9 @@ func transpileFile(srcPath string, opts *transpileOptions) error { if !flags.skipImports { importPaths := getPathsFromImportSpec(transpileRes.Imports) for _, path := range importPaths { - transpilePkg(path, opts) + if err := transpilePkg(path, opts); err != nil { + return err + } } } diff --git a/gnovm/stdlibs/std/coins.gno b/gnovm/stdlibs/std/coins.gno index dd138d30822..47e88e238d2 100644 --- a/gnovm/stdlibs/std/coins.gno +++ b/gnovm/stdlibs/std/coins.gno @@ -1,33 +1,141 @@ package std -import "strconv" +import ( + "math/overflow" + "strconv" +) -// NOTE: this is selectly copied over from pkgs/std/coin.go -// TODO: import all functionality(?). -// TODO: implement Coin/Coins constructors. +// NOTE: this is selectively copied over from tm2/pkgs/std/coin.go -// Coin hold some amount of one currency. +// Coin holds some amount of one currency. // A negative amount is invalid. type Coin struct { Denom string `json:"denom"` Amount int64 `json:"amount"` } +// NewCoin returns a new coin with a denomination and amount +func NewCoin(denom string, amount int64) Coin { + return Coin{ + Denom: denom, + Amount: amount, + } +} + +// String provides a human-readable representation of a coin func (c Coin) String() string { return strconv.Itoa(int(c.Amount)) + c.Denom } +// IsGTE returns true if they are the same type and the receiver is +// an equal or greater value func (c Coin) IsGTE(other Coin) bool { - if c.Denom != other.Denom { - panic("invalid coin denominations: " + c.Denom) - } + mustMatchDenominations(c.Denom, other.Denom) + return c.Amount >= other.Amount } +// IsLT returns true if they are the same type and the receiver is +// a smaller value +func (c Coin) IsLT(other Coin) bool { + mustMatchDenominations(c.Denom, other.Denom) + + return c.Amount < other.Amount +} + +// IsEqual returns true if the two sets of Coins have the same value +func (c Coin) IsEqual(other Coin) bool { + mustMatchDenominations(c.Denom, other.Denom) + + return c.Amount == other.Amount +} + +// Add adds amounts of two coins with same denom. +// If the coins differ in denom then it panics. +// An overflow or underflow panics. +// An invalid result panics. +func (c Coin) Add(other Coin) Coin { + mustMatchDenominations(c.Denom, other.Denom) + + sum, ok := overflow.Add64(c.Amount, other.Amount) + if !ok { + panic("coin add overflow/underflow: " + strconv.Itoa(int(c.Amount)) + " +/- " + strconv.Itoa(int(other.Amount))) + } + + c.Amount = sum + return c +} + +// Sub subtracts amounts of two coins with same denom. +// If the coins differ in denom then it panics. +// An overflow or underflow panics. +// An invalid result panics. +func (c Coin) Sub(other Coin) Coin { + mustMatchDenominations(c.Denom, other.Denom) + + dff, ok := overflow.Sub64(c.Amount, other.Amount) + if !ok { + panic("coin sub overflow/underflow: " + strconv.Itoa(int(c.Amount)) + " +/- " + strconv.Itoa(int(other.Amount))) + } + c.Amount = dff + + return c +} + +// IsPositive returns true if coin amount is positive. +func (c Coin) IsPositive() bool { + return c.Amount > 0 +} + +// IsNegative returns true if the coin amount is negative and false otherwise. +func (c Coin) IsNegative() bool { + return c.Amount < 0 +} + +// IsZero returns true if the amount of given coin is zero +func (c Coin) IsZero() bool { + return c.Amount == 0 +} + +func mustMatchDenominations(denomA, denomB string) { + if denomA != denomB { + panic("incompatible coin denominations: " + denomA + ", " + denomB) + } +} + // Coins is a set of Coin, one per currency type Coins []Coin +// NewCoins returns a new set of Coins given one or more Coins +// Consolidates any denom duplicates into one, keeping the properties of a mathematical set +func NewCoins(coins ...Coin) Coins { + coinMap := make(map[string]int64) + + for _, coin := range coins { + if currentAmount, exists := coinMap[coin.Denom]; exists { + var ok bool + if coinMap[coin.Denom], ok = overflow.Add64(currentAmount, coin.Amount); !ok { + panic("coin sub overflow/underflow: " + strconv.Itoa(int(currentAmount)) + " +/- " + strconv.Itoa(int(coin.Amount))) + } + } else { + coinMap[coin.Denom] = coin.Amount + } + } + + var setCoins Coins + for denom, amount := range coinMap { + setCoins = append(setCoins, NewCoin(denom, amount)) + } + + return setCoins +} + +// String returns the string representation of Coins func (cz Coins) String() string { + if len(cz) == 0 { + return "" + } + res := "" for i, c := range cz { if i > 0 { @@ -35,35 +143,41 @@ func (cz Coins) String() string { } res += c.String() } + return res } +// AmountOf returns the amount of a specific coin from the Coins set func (cz Coins) AmountOf(denom string) int64 { for _, c := range cz { if c.Denom == denom { return c.Amount } } + return 0 } -func (a Coins) Add(b Coins) Coins { +// Add adds a Coin to the Coins set +func (cz Coins) Add(b Coins) Coins { c := Coins{} - for _, ac := range a { + for _, ac := range cz { bc := b.AmountOf(ac.Denom) ac.Amount += bc c = append(c, ac) } + for _, bc := range b { cc := c.AmountOf(bc.Denom) if cc == 0 { c = append(c, bc) } } + return c } -// expand for usage within natively bound functions. +// expandNative expands for usage within natively bound functions. func (cz Coins) expandNative() (denoms []string, amounts []int64) { denoms = make([]string, len(cz)) amounts = make([]int64, len(cz)) @@ -71,5 +185,6 @@ func (cz Coins) expandNative() (denoms []string, amounts []int64) { denoms[i] = coin.Denom amounts[i] = coin.Amount } + return denoms, amounts } diff --git a/gnovm/stdlibs/stdshim/coins.gno b/gnovm/stdlibs/stdshim/coins.gno index aaede81309d..4589113bff4 100644 --- a/gnovm/stdlibs/stdshim/coins.gno +++ b/gnovm/stdlibs/stdshim/coins.gno @@ -2,8 +2,7 @@ package std import "strconv" -// NOTE: this is selectly copied over from pkgs/std/coin.go -// TODO: import all functionality(?). +// NOTE: this is selectively copied over from tm2/pkgs/std/coin.go // Coin hold some amount of one currency. // A negative amount is invalid. @@ -12,21 +11,115 @@ type Coin struct { Amount int64 `json:"amount"` } +// NewCoin returns a new coin with a denomination and amount +func NewCoin(denom string, amount int64) Coin { + return Coin{ + Denom: denom, + Amount: amount, + } +} + +// String provides a human-readable representation of a coin func (c Coin) String() string { return strconv.Itoa(int(c.Amount)) + c.Denom } +// IsGTE returns true if they are the same type and the receiver is +// an equal or greater value func (c Coin) IsGTE(other Coin) bool { - if c.Denom != other.Denom { - panic("invalid coin denominations: " + c.Denom) - } + mustMatchDenominations(c.Denom, other.Denom) + return c.Amount >= other.Amount } +// IsLT returns true if they are the same type and the receiver is +// a smaller value +func (c Coin) IsLT(other Coin) bool { + mustMatchDenominations(c.Denom, other.Denom) + + return c.Amount < other.Amount +} + +// IsEqual returns true if the two sets of Coins have the same value +func (c Coin) IsEqual(other Coin) bool { + mustMatchDenominations(c.Denom, other.Denom) + + return c.Amount == other.Amount +} + +// Add adds amounts of two coins with same denom. +// If the coins differ in denom then it panics. +// An overflow or underflow panics. +// An invalid result panics. +func (c Coin) Add(coinB Coin) Coin { + mustMatchDenominations(c.Denom, coinB.Denom) + + sum := c.Amount + coinB.Amount + + c.Amount = sum + return c +} + +// Sub subtracts amounts of two coins with same denom. +// If the coins differ in denom then it panics. +// An overflow or underflow panics. +// An invalid result panics. +func (c Coin) Sub(coinB Coin) Coin { + mustMatchDenominations(c.Denom, coinB.Denom) + + dff := c.Amount - coinB.Amount + c.Amount = dff + + return c +} + +// IsPositive returns true if coin amount is positive. +func (c Coin) IsPositive() bool { + return c.Amount > 0 +} + +// IsNegative returns true if the coin amount is negative and false otherwise. +func (c Coin) IsNegative() bool { + return c.Amount < 0 +} + +// IsZero returns if this represents no money +func (c Coin) IsZero() bool { + return c.Amount == 0 +} + +func mustMatchDenominations(denomA, denomB string) { + if denomA != denomB { + panic("incompatible coin denominations: " + denomA + ", " + denomB) + } +} + // Coins is a set of Coin, one per currency type Coins []Coin +// NewCoins returns a new set of Coins given one or more Coins +// Consolidates any denom duplicates into one, keeping the properties of a mathematical set +func NewCoins(coins ...Coin) Coins { + coinMap := make(map[string]int64) + + for _, coin := range coins { + coinMap[coin.Denom] = coin.Amount + } + + var setCoins Coins + for denom, amount := range coinMap { + setCoins = append(setCoins, NewCoin(denom, amount)) + } + + return setCoins +} + +// String returns the string representation of Coins func (cz Coins) String() string { + if len(cz) == 0 { + return "" + } + res := "" for i, c := range cz { if i > 0 { @@ -34,32 +127,48 @@ func (cz Coins) String() string { } res += c.String() } + return res } +// AmountOf returns the amount of a specific coin from the Coins set func (cz Coins) AmountOf(denom string) int64 { for _, c := range cz { if c.Denom == denom { return c.Amount } } + return 0 } -func (a Coins) Add(b Coins) Coins { +// Add adds a Coin to the Coins set +func (cz Coins) Add(b Coins) Coins { c := Coins{} - for _, ac := range a { + for _, ac := range cz { bc := b.AmountOf(ac.Denom) ac.Amount += bc c = append(c, ac) } + for _, bc := range b { cc := c.AmountOf(bc.Denom) if cc == 0 { c = append(c, bc) } } + return c } -// TODO implement Coin/Coins constructors. +// expandNative expands for usage within natively bound functions. +func (cz Coins) expandNative() (denoms []string, amounts []int64) { + denoms = make([]string, len(cz)) + amounts = make([]int64, len(cz)) + for i, coin := range cz { + denoms[i] = coin.Denom + amounts[i] = coin.Amount + } + + return denoms, amounts +} diff --git a/gnovm/tests/files/zrealm_natbind0.gno b/gnovm/tests/files/zrealm_natbind0.gno index 9cf0e809ece..3808905928b 100644 --- a/gnovm/tests/files/zrealm_natbind0.gno +++ b/gnovm/tests/files/zrealm_natbind0.gno @@ -141,7 +141,7 @@ func main() { // "Closure": { // "@type": "/gno.RefValue", // "Escaped": true, -// "ObjectID": "a7f5397443359ea76c50be82c77f1f893a060925:7" +// "ObjectID": "a7f5397443359ea76c50be82c77f1f893a060925:8" // }, // "FileName": "native.gno", // "IsMethod": false,