diff --git a/examples/gno.land/r/demo/dao_realm/dao_realm.gno b/examples/gno.land/r/demo/dao_realm/dao_realm.gno index 16cf96ca808..4a8655d9090 100644 --- a/examples/gno.land/r/demo/dao_realm/dao_realm.gno +++ b/examples/gno.land/r/demo/dao_realm/dao_realm.gno @@ -12,6 +12,7 @@ import ( "gno.land/p/demo/daodao/voting_group" "gno.land/r/demo/groups" modboards "gno.land/r/demo/modboards" + tori "gno.land/r/demo/tori" ) var ( @@ -49,6 +50,10 @@ func init() { registry.Register(modboards.NewCreateBoardHandler()) registry.Register(modboards.NewDeletePostHandler()) modboards.CreateBoard(mainBoardName) + + // TODO: replace with grc20_admin_registry + registry.Register(tori.NewMintToriHandler()) + registry.Register(tori.NewBurnToriHandler()) } func Render(path string) string { diff --git a/examples/gno.land/r/demo/dao_realm/gno.mod b/examples/gno.land/r/demo/dao_realm/gno.mod index dae2df2a65f..5d4e48c01a6 100644 --- a/examples/gno.land/r/demo/dao_realm/gno.mod +++ b/examples/gno.land/r/demo/dao_realm/gno.mod @@ -7,4 +7,5 @@ require ( "gno.land/p/demo/daodao/voting_group" v0.0.0-latest "gno.land/r/demo/groups" v0.0.0-latest "gno.land/r/demo/modboards" v0.0.0-latest + "gno.land/r/demo/tori" v0.0.0-latest ) \ No newline at end of file diff --git a/examples/gno.land/r/demo/grc20_registry/gno.mod b/examples/gno.land/r/demo/grc20_registry/gno.mod new file mode 100644 index 00000000000..ea560f63150 --- /dev/null +++ b/examples/gno.land/r/demo/grc20_registry/gno.mod @@ -0,0 +1,6 @@ +module gno.land/r/demo/grc20_registry + +require ( + "gno.land/p/demo/avl" v0.0.0-latest + "gno.land/p/demo/grc/grc20" v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/grc20_registry/grc20_registry.gno b/examples/gno.land/r/demo/grc20_registry/grc20_registry.gno new file mode 100644 index 00000000000..9bcd60ff2eb --- /dev/null +++ b/examples/gno.land/r/demo/grc20_registry/grc20_registry.gno @@ -0,0 +1,31 @@ +package grc20_registry + +import ( + "std" + + "gno.land/p/demo/avl" + "gno.land/p/demo/grc/grc20" +) + +var ( + registry = avl.NewTree() // addr -> IGRC20 + lol grc20.IGRC20 +) + +func Register(token grc20.IGRC20) { + caller := std.PrevRealm().Addr() + // registry.Set(caller.String(), token) + lol = token +} + +func Get(addr std.Address) (grc20.IGRC20, bool) { + coinI, ok := registry.Get(addr.String()) + if !ok { + return nil, false + } + coin, ok := coinI.(grc20.IGRC20) + if !ok { + panic("should not happen") + } + return coin, true +} diff --git a/examples/gno.land/r/demo/tori/gno.mod b/examples/gno.land/r/demo/tori/gno.mod new file mode 100644 index 00000000000..8be64e8b4ab --- /dev/null +++ b/examples/gno.land/r/demo/tori/gno.mod @@ -0,0 +1,8 @@ +module gno.land/r/demo/tori + +require ( + "gno.land/p/demo/ufmt" v0.0.0-latest + "gno.land/p/demo/grc/grc20" v0.0.0-latest + "gno.land/r/demo/users" v0.0.0-latest + "gno.land/r/demo/grc20_registry" v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/tori/messages.gno b/examples/gno.land/r/demo/tori/messages.gno new file mode 100644 index 00000000000..c9e2171ee05 --- /dev/null +++ b/examples/gno.land/r/demo/tori/messages.gno @@ -0,0 +1,136 @@ +package tori + +import ( + "encoding/binary" + "std" + "strconv" + "strings" + + "gno.land/p/demo/binutils" + "gno.land/p/demo/daodao/interfaces" + "gno.land/r/demo/users" +) + +type ExecutableMessageMintTori struct { + dao_interfaces.ExecutableMessage + + Address users.AddressOrName + Amount uint64 +} + +func (msg *ExecutableMessageMintTori) Type() string { + return "MintTori" +} + +func (msg *ExecutableMessageMintTori) String() string { + var ss []string + ss = append(ss, msg.Type()) + s := "Address: " + string(msg.Address) + "\n" + s += "Amount: " + strconv.FormatUint(msg.Amount, 10) + ss = append(ss, s) + return strings.Join(ss, "\n---\n") +} + +func (msg *ExecutableMessageMintTori) Binary() []byte { + b := []byte{} + b = append(b, binutils.EncodeLengthPrefixedStringUint16BE(msg.Type())...) + b = append(b, binutils.EncodeLengthPrefixedStringUint16BE(string(msg.Address))...) + b = binary.BigEndian.AppendUint64(b, msg.Amount) + return b +} + +func ExecutableMessageMintToriFromBinary(b []byte) *ExecutableMessageMintTori { + msg := &ExecutableMessageMintTori{} + t, b := binutils.MustDecodeLengthPrefixedStringUint16BE(b) + if t != msg.Type() { + panic("invalid type") + } + var addr string + addr, b = binutils.MustDecodeLengthPrefixedStringUint16BE(b) + msg.Address = users.AddressOrName(addr) + msg.Amount, b = binary.BigEndian.Uint64(b), b[8:] + return msg +} + +type MintToriHandler struct { + dao_interfaces.MessageHandler +} + +func NewMintToriHandler() *MintToriHandler { + return &MintToriHandler{} +} + +func (h *MintToriHandler) Execute(imsg dao_interfaces.ExecutableMessage) { + msg := imsg.(*ExecutableMessageMintTori) + Mint(msg.Address, msg.Amount) +} + +func (h *MintToriHandler) Type() string { + return ExecutableMessageMintTori{}.Type() +} + +func (h *MintToriHandler) FromBinary(b []byte) dao_interfaces.ExecutableMessage { + return ExecutableMessageMintToriFromBinary(b) +} + +type ExecutableMessageBurnTori struct { + dao_interfaces.ExecutableMessage + + Address users.AddressOrName + Amount uint64 +} + +func (msg *ExecutableMessageBurnTori) Type() string { + return "BurnTori" +} + +func (msg *ExecutableMessageBurnTori) String() string { + var ss []string + ss = append(ss, msg.Type()) + s := "Address: " + string(msg.Address) + "\n" + s += "Amount: " + strconv.FormatUint(msg.Amount, 10) + ss = append(ss, s) + return strings.Join(ss, "\n---\n") +} + +func (msg *ExecutableMessageBurnTori) Binary() []byte { + b := []byte{} + b = append(b, binutils.EncodeLengthPrefixedStringUint16BE(msg.Type())...) + b = append(b, binutils.EncodeLengthPrefixedStringUint16BE(string(msg.Address))...) + b = binary.BigEndian.AppendUint64(b, msg.Amount) + return b +} + +func ExecutableMessageBurnToriFromBinary(b []byte) *ExecutableMessageBurnTori { + msg := &ExecutableMessageBurnTori{} + t, b := binutils.MustDecodeLengthPrefixedStringUint16BE(b) + if t != msg.Type() { + panic("invalid type") + } + var addr string + addr, b = binutils.MustDecodeLengthPrefixedStringUint16BE(b) + msg.Address = users.AddressOrName(addr) + msg.Amount, b = binary.BigEndian.Uint64(b), b[8:] + return msg +} + +type BurnToriHandler struct { + dao_interfaces.MessageHandler +} + +func NewBurnToriHandler() *BurnToriHandler { + return &BurnToriHandler{} +} + +func (h *BurnToriHandler) Execute(imsg dao_interfaces.ExecutableMessage) { + msg := imsg.(*ExecutableMessageBurnTori) + Burn(msg.Address, msg.Amount) +} + +func (h *BurnToriHandler) Type() string { + return ExecutableMessageBurnTori{}.Type() +} + +func (h *BurnToriHandler) FromBinary(b []byte) dao_interfaces.ExecutableMessage { + return ExecutableMessageBurnToriFromBinary(b) +} diff --git a/examples/gno.land/r/demo/tori/tori.gno b/examples/gno.land/r/demo/tori/tori.gno new file mode 100644 index 00000000000..12aa95e670e --- /dev/null +++ b/examples/gno.land/r/demo/tori/tori.gno @@ -0,0 +1,113 @@ +package tori + +import ( + "std" + "strings" + + "gno.land/p/demo/grc/grc20" + "gno.land/p/demo/ufmt" + "gno.land/r/demo/grc20_registry" + "gno.land/r/demo/users" +) + +var ( + tori *grc20.AdminToken + userTori grc20.IGRC20 + admin std.Address = std.DerivePkgAddr("gno.land/r/demo/dao_realm") // TODO: helper to change admin +) + +func init() { + tori = grc20.NewAdminToken("Tori", "TORI", 6) + userTori = tori.GRC20() + grc20_registry.Register(userTori) // found another bug, calling this sets some grc20 internal object's pkgid to the registry realm id +} + +// method proxies as public functions. +// + +// getters. + +func TotalSupply() uint64 { + return tori.TotalSupply() +} + +func BalanceOf(owner users.AddressOrName) uint64 { + balance, err := tori.BalanceOf(owner.Resolve()) + if err != nil { + panic(err) + } + return balance +} + +func Allowance(owner, spender users.AddressOrName) uint64 { + allowance, err := tori.Allowance(owner.Resolve(), spender.Resolve()) + if err != nil { + panic(err) + } + return allowance +} + +// setters. + +func Transfer(to users.AddressOrName, amount uint64) { + caller := std.PrevRealm().Addr() + tori.Transfer(caller, to.Resolve(), amount) +} + +func Approve(spender users.AddressOrName, amount uint64) { + caller := std.PrevRealm().Addr() + tori.Approve(caller, spender.Resolve(), amount) +} + +func TransferFrom(from, to users.AddressOrName, amount uint64) { + caller := std.PrevRealm().Addr() + tori.TransferFrom(caller, from.Resolve(), to.Resolve(), amount) +} + +// faucet. + +func Faucet() { + // FIXME: add limits? + // FIXME: add payment in gnot? + caller := std.PrevRealm().Addr() + tori.Mint(caller, 1000*10000) // 1k +} + +// administration. + +func Mint(address users.AddressOrName, amount uint64) { + caller := std.PrevRealm().Addr() + assertIsAdmin(caller) + tori.Mint(address.Resolve(), amount) +} + +func Burn(address users.AddressOrName, amount uint64) { + caller := std.PrevRealm().Addr() + assertIsAdmin(caller) + tori.Burn(address.Resolve(), amount) +} + +// render. +// + +func Render(path string) string { + parts := strings.Split(path, "/") + c := len(parts) + + switch { + case path == "": + return tori.RenderHome() + case c == 2 && parts[0] == "balance": + owner := users.AddressOrName(parts[1]) + balance, _ := tori.BalanceOf(owner.Resolve()) + return ufmt.Sprintf("%d\n", balance) + default: + return "404\n" + } +} + +func assertIsAdmin(address std.Address) { + if address != admin { + panic("restricted access") + } +} diff --git a/gnovm/pkg/gnolang/realm.go b/gnovm/pkg/gnolang/realm.go index 70e67cb9ec9..5c584080fcc 100644 --- a/gnovm/pkg/gnolang/realm.go +++ b/gnovm/pkg/gnolang/realm.go @@ -69,8 +69,16 @@ func (pid PkgID) Bytes() []byte { return pid.Hashlet[:] } +var pathsFromIds = make(map[string]string) + func PkgIDFromPkgPath(path string) PkgID { - return PkgID{HashBytes([]byte(path))} + id := PkgID{HashBytes([]byte(path))} + pathsFromIds[id.String()] = path + return id +} + +func PkgPathFromPkgID(id PkgID) string { + return pathsFromIds[id.String()] } func ObjectIDFromPkgPath(path string) ObjectID { @@ -157,7 +165,12 @@ func (rlm *Realm) DidUpdate(po, xo, co Object) { return // do nothing. } if po.GetObjectID().PkgID != rlm.ID { - panic("cannot modify external-realm or non-realm object") + opid := po.GetObjectID().PkgID + prettyName := PkgPathFromPkgID(opid) + if prettyName == "" { + prettyName = opid.String() + } + panic(fmt.Sprintf("cannot modify external-realm or non-realm object: object pkg path: %s, realm: %s", prettyName, rlm.Path)) } // From here on, po is real (not new-real). // Updates to .newCreated/.newEscaped /.newDeleted made here. (first gen) @@ -179,8 +192,7 @@ func (rlm *Realm) DidUpdate(po, xo, co Object) { fmt.Println("DidUpdate - mark escaped") rlm.MarkNewEscaped(co) } - } - if co.GetIsReal() { + } else if co.GetIsReal() { if shouldDebug { fmt.Println("MarkDirty co", co) }