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 new file mode 100644 index 00000000000..abc3615d79c --- /dev/null +++ b/gno.land/cmd/gnoland/testdata/realm-banker-issued-coin-denom.txtar @@ -0,0 +1,88 @@ +# test for https://github.com/gnolang/gno/pull/875 + +## another test user, test2 +adduser test2 + +## start a new node +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 + +## 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 + +## test2 spend all balance +gnokey maketx send -send "9999999ugnot" -to g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 -gas-fee 1ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test2 + +## check test2 balance +gnokey query bank/balances/${USER_ADDR_test2} +stdout '' + +## mint coin from banker +gnokey maketx call -pkgpath gno.land/r/test/realm_banker -func Mint -args ${USER_ADDR_test2} -args "ugnot" -args "31337" -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 + +## check balance after minting, without patching banker will return '31337ugnot' +gnokey query bank/balances/${USER_ADDR_test2} +stdout '"31337/gno.land/r/test/realm_banker:ugnot"' + +## burn coin +gnokey maketx call -pkgpath gno.land/r/test/realm_banker -func Burn -args ${USER_ADDR_test2} -args "ugnot" -args "7" -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 + +## check balance after burning +gnokey query bank/balances/${USER_ADDR_test2} +stdout '"31330/gno.land/r/test/realm_banker:ugnot"' + +## transfer 1ugnot to test2 for gas-fee of below tx +gnokey maketx send -send "1ugnot" -to ${USER_ADDR_test2} -gas-fee 1ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 + +## transfer coin +gnokey maketx send -send "1330/gno.land/r/test/realm_banker:ugnot" -to g1yr0dpfgthph7y6mepdx8afuec4q3ga2lg8tjt0 -gas-fee 1ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test2 + +## check sender balance +gnokey query bank/balances/${USER_ADDR_test2} +stdout '"30000/gno.land/r/test/realm_banker:ugnot"' + +## check receiver balance +gnokey query bank/balances/g1yr0dpfgthph7y6mepdx8afuec4q3ga2lg8tjt0 +stdout '"1330/gno.land/r/test/realm_banker:ugnot"' + +## mint coin from long named package with banker +gnokey maketx call -pkgpath gno.land/r/test/package89_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_1234567890 -func Mint -args "g1cq2ecdq3eyn5qa0fzznpurg87zq3k77g63q6u7" -args "ugnot" -args "100" -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 +gnokey query bank/balances/g1cq2ecdq3eyn5qa0fzznpurg87zq3k77g63q6u7 +stdout '"100/gno.land/r/test/package89_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_1234567890:ugnot"' + +-- short/realm_banker.gno -- +package realm_banker + +import ( + "std" +) + +func Mint(addr std.Address, denom string, amount int64) { + banker := std.GetBanker(std.BankerTypeRealmIssue) + banker.IssueCoin(addr, denom, amount) +} + +func Burn(addr std.Address, denom string, amount int64) { + banker := std.GetBanker(std.BankerTypeRealmIssue) + banker.RemoveCoin(addr, denom, amount) +} + +-- long/realm_banker.gno -- +// package name is 130 characters long +package package89_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_1234567890 + +import ( + "std" +) + +func Mint(addr std.Address, denom string, amount int64) { + banker := std.GetBanker(std.BankerTypeRealmIssue) + banker.IssueCoin(addr, denom, amount) +} + +func Burn(addr std.Address, denom string, amount int64) { + banker := std.GetBanker(std.BankerTypeRealmIssue) + banker.RemoveCoin(addr, denom, amount) +} \ No newline at end of file diff --git a/gnovm/stdlibs/std/banker.go b/gnovm/stdlibs/std/banker.go index 7d0b339552a..ef66e1ae7fb 100644 --- a/gnovm/stdlibs/std/banker.go +++ b/gnovm/stdlibs/std/banker.go @@ -2,6 +2,7 @@ package std import ( "fmt" + "regexp" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/tm2/pkg/crypto" @@ -32,6 +33,11 @@ const ( btRealmIssue ) +// regexp for denom format +const denomRegex = "[a-z][a-z0-9]{2,15}" + +var reg = regexp.MustCompile(denomRegex) + func X_bankerGetCoins(m *gno.Machine, bt uint8, addr string) (denoms []string, amounts []int64) { coins := m.Context.(ExecContext).Banker.GetCoins(crypto.Bech32Address(addr)) return ExpandCoins(coins) @@ -88,10 +94,30 @@ func X_bankerTotalCoin(m *gno.Machine, bt uint8, denom string) int64 { func X_bankerIssueCoin(m *gno.Machine, bt uint8, addr string, denom string, amount int64) { // gno checks for bt == RealmIssue - m.Context.(ExecContext).Banker.IssueCoin(crypto.Bech32Address(addr), denom, amount) + + // check origin denom format + matched := reg.MatchString(denom) + if !matched { + m.Panic(typedString("invalid denom format to issue coin, must be " + denomRegex)) + return + } + + // Similar to ibc spec + // ibc_denom := 'ibc/' + hash('path' + 'base_denom') + // gno_realm_denom := '/' + 'pkg_path' + ':' + 'base_denom' + newDenom := "/" + m.Realm.Path + ":" + denom + m.Context.(ExecContext).Banker.IssueCoin(crypto.Bech32Address(addr), newDenom, amount) } func X_bankerRemoveCoin(m *gno.Machine, bt uint8, addr string, denom string, amount int64) { // gno checks for bt == RealmIssue - m.Context.(ExecContext).Banker.IssueCoin(crypto.Bech32Address(addr), denom, amount) + + matched := reg.MatchString(denom) + if !matched { + m.Panic(typedString("invalid denom format to remove coin, must be " + denomRegex)) + return + } + + newDenom := "/" + m.Realm.Path + ":" + denom + m.Context.(ExecContext).Banker.RemoveCoin(crypto.Bech32Address(addr), newDenom, amount) } diff --git a/tm2/pkg/std/coin.go b/tm2/pkg/std/coin.go index 75063320ad3..4f36757efc0 100644 --- a/tm2/pkg/std/coin.go +++ b/tm2/pkg/std/coin.go @@ -616,8 +616,7 @@ func (coins Coins) Sort() Coins { // Parsing var ( - // Denominations can be 3 ~ 16 characters long. - reDnmString = `[a-z][a-z0-9]{2,15}` + reDnmString = `[a-z\/][a-z0-9_.:\/]{2,}` reAmt = `[[:digit:]]+` reSpc = `[[:space:]]*` reDnm = regexp.MustCompile(fmt.Sprintf(`^%s$`, reDnmString)) diff --git a/tm2/pkg/std/coin_test.go b/tm2/pkg/std/coin_test.go index 8d9a9359b61..33ee7425a0c 100644 --- a/tm2/pkg/std/coin_test.go +++ b/tm2/pkg/std/coin_test.go @@ -429,11 +429,11 @@ func TestParse(t *testing.T) { {"98 bar , 1 foo ", true, Coins{{"bar", int64(98)}, {"foo", one}}}, {" 55\t \t bling\n", true, Coins{{"bling", int64(55)}}}, {"2foo, 97 bar", true, Coins{{"bar", int64(97)}, {"foo", int64(2)}}}, + {"5foo-bar", false, nil}, {"5 mycoin,", false, nil}, // no empty coins in a list {"2 3foo, 97 bar", false, nil}, // 3foo is invalid coin name {"11me coin, 12you coin", false, nil}, // no spaces in coin names {"1.2btc", false, nil}, // amount must be integer - {"5foo-bar", false, nil}, // once more, only letters in coin name } for tcIndex, tc := range cases {