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,