diff --git a/examples/gno.land/p/demo/ownable/errors.gno b/examples/gno.land/p/demo/ownable/errors.gno new file mode 100644 index 00000000000..ffbf6ab3f6f --- /dev/null +++ b/examples/gno.land/p/demo/ownable/errors.gno @@ -0,0 +1,8 @@ +package ownable + +import "errors" + +var ( + ErrUnauthorized = errors.New("unauthorized; caller is not owner") + ErrInvalidAddress = errors.New("new owner address is invalid") +) diff --git a/examples/gno.land/p/demo/ownable/gno.mod b/examples/gno.land/p/demo/ownable/gno.mod new file mode 100644 index 00000000000..9a9abb1e661 --- /dev/null +++ b/examples/gno.land/p/demo/ownable/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/ownable diff --git a/examples/gno.land/p/demo/ownable/ownable.gno b/examples/gno.land/p/demo/ownable/ownable.gno new file mode 100644 index 00000000000..7f2eac008e1 --- /dev/null +++ b/examples/gno.land/p/demo/ownable/ownable.gno @@ -0,0 +1,57 @@ +package ownable + +import ( + "std" +) + +// Ownable is meant to be used as a top-level object to make your contract ownable OR +// being embedded in a Gno object to manage per-object ownership. +type Ownable struct { + owner std.Address +} + +func New() *Ownable { + return &Ownable{ + owner: std.GetOrigCaller(), + } +} + +// TransferOwnership transfers ownership of the Ownable struct to a new address +func (o *Ownable) TransferOwnership(newOwner std.Address) error { + err := o.CallerIsOwner() + if err != nil { + return err + } + + if !newOwner.IsValid() { + return ErrInvalidAddress + } + + o.owner = newOwner + return nil +} + +// DropOwnership removes the owner, effectively disabling any owner-related actions +// Top-level usage: disables all only-owner actions/functions, +// Embedded usage: behaves like a burn functionality, removing the owner from the struct +func (o *Ownable) DropOwnership() error { + err := o.CallerIsOwner() + if err != nil { + return err + } + + o.owner = "" + return nil +} + +// CallerIsOwner checks if the caller of the function is the Realm's owner +func (o *Ownable) CallerIsOwner() error { + if std.GetOrigCaller() == o.owner { + return nil + } + return ErrUnauthorized +} + +func (o *Ownable) Owner() std.Address { + return o.owner +} diff --git a/examples/gno.land/p/demo/ownable/ownable_test.gno b/examples/gno.land/p/demo/ownable/ownable_test.gno new file mode 100644 index 00000000000..f725795fd47 --- /dev/null +++ b/examples/gno.land/p/demo/ownable/ownable_test.gno @@ -0,0 +1,113 @@ +package ownable + +import ( + "std" + "testing" +) + +var ( + firstCaller = std.Address("g1l9aypkr8xfvs82zeux486ddzec88ty69lue9de") + secondCaller = std.Address("g127jydsh6cms3lrtdenydxsckh23a8d6emqcvfa") +) + +func TestNew(t *testing.T) { + std.TestSetOrigCaller(firstCaller) + + result := New() + if firstCaller != result.owner { + t.Fatalf("Expected %s, got: %s\n", firstCaller, result.owner) + } +} + +func TestOwner(t *testing.T) { + std.TestSetOrigCaller(firstCaller) + + result := New() + resultOwner := result.Owner() + + expected := firstCaller + if resultOwner != expected { + t.Fatalf("Expected %s, got: %s\n", expected, result) + } +} + +func TestTransferOwnership(t *testing.T) { + std.TestSetOrigCaller(firstCaller) + o := New() + + err := o.TransferOwnership(secondCaller) + if err != nil { + t.Fatalf("TransferOwnership failed, %v", err) + } + + result := o.Owner() + if secondCaller != result { + t.Fatalf("Expected: %s, got: %s\n", secondCaller, result) + } +} + +func TestCallerIsOwner(t *testing.T) { + std.TestSetOrigCaller(firstCaller) + + o := New() + unauthorizedCaller := secondCaller + + std.TestSetOrigCaller(unauthorizedCaller) + + err := o.CallerIsOwner() + if err == nil { + t.Fatalf("Expected %s to not be owner\n", unauthorizedCaller) + } +} + +func TestDropOwnership(t *testing.T) { + std.TestSetOrigCaller(firstCaller) + + o := New() + + err := o.DropOwnership() + if err != nil { + t.Fatalf("DropOwnership failed, %v", err) + } + + owner := o.Owner() + if owner != "" { + t.Fatalf("Expected owner to be empty, not %s\n", owner) + } +} + +// Errors + +func TestErrUnauthorized(t *testing.T) { + std.TestSetOrigCaller(firstCaller) + + o := New() + + std.TestSetOrigCaller(secondCaller) + + err := o.TransferOwnership(firstCaller) + if err != ErrUnauthorized { + t.Fatalf("Should've been ErrUnauthorized, was %v", err) + } + + err = o.DropOwnership() + if err != ErrUnauthorized { + t.Fatalf("Should've been ErrUnauthorized, was %v", err) + } +} + +func TestErrInvalidAddress(t *testing.T) { + std.TestSetOrigCaller(firstCaller) + + o := New() + + err := o.TransferOwnership("") + if err != ErrInvalidAddress { + t.Fatalf("Should've been ErrInvalidAddress, was %v", err) + } + + err = o.TransferOwnership("10000000001000000000100000000010000000001000000000") + if err != ErrInvalidAddress { + t.Fatalf("Should've been ErrInvalidAddress, was %v", err) + } +}