From 1967ffd35f3558687aeaae35d28d933f8bfafa19 Mon Sep 17 00:00:00 2001 From: moul <94029+moul@users.noreply.github.com> Date: Sun, 7 Jul 2024 23:49:04 -0500 Subject: [PATCH 1/4] feat: r/demo/vault Signed-off-by: moul <94029+moul@users.noreply.github.com> --- examples/gno.land/r/demo/vault/gno.mod | 6 + examples/gno.land/r/demo/vault/vault.gno | 125 ++++++++++++++++++ examples/gno.land/r/demo/vault/vault_test.gno | 3 + .../gno.land/r/demo/vault/z1_filetest.gno | 78 +++++++++++ 4 files changed, 212 insertions(+) create mode 100644 examples/gno.land/r/demo/vault/gno.mod create mode 100644 examples/gno.land/r/demo/vault/vault.gno create mode 100644 examples/gno.land/r/demo/vault/vault_test.gno create mode 100644 examples/gno.land/r/demo/vault/z1_filetest.gno diff --git a/examples/gno.land/r/demo/vault/gno.mod b/examples/gno.land/r/demo/vault/gno.mod new file mode 100644 index 00000000000..a6a5907e35f --- /dev/null +++ b/examples/gno.land/r/demo/vault/gno.mod @@ -0,0 +1,6 @@ +module gno.land/r/demo/vault + +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/vault/vault.gno b/examples/gno.land/r/demo/vault/vault.gno new file mode 100644 index 00000000000..47aa70f483e --- /dev/null +++ b/examples/gno.land/r/demo/vault/vault.gno @@ -0,0 +1,125 @@ +package vault + +import ( + "std" + + "gno.land/p/demo/avl" + "gno.land/p/demo/grc/grc20" +) + +// Vault is a GRC20 compatible token with vault features. +type Vault interface { + Deposit(amount uint, recovery std.Address, lockDuration uint) error + Unvault(amount uint) error + Recover(dest std.Address) error + Redeem() error +} + +func New(adminToken *grc20.AdminToken) Vault { + return &impl{ + adminToken: adminToken, + users: avl.Tree{}, + } +} + +type impl struct { + adminToken *grc20.AdminToken + users avl.Tree // std.Address -> userVault +} + +type userVault struct { + // constructor parameters. + recover std.Address + lockDuration uint + + // internal parameters. + owner std.Address + redeemMinHeight int64 + unvaultedAmount uint +} + +func (v *impl) Deposit(amount uint, recover std.Address, lockDuration uint) error { + caller := std.GetOrigCaller() + pkgAddr := std.GetOrigPkgAddr() + + uv := userVault{ + lockDuration: lockDuration, + redeemMinHeight: 0, // will be set in Unvault. + unvaultedAmount: 0, // will be increased in Unvault, zeroed in Redeem. + owner: caller, + } + + // deposit. + err := v.adminToken.Transfer(caller, pkgAddr, uint64(amount)) + if err != nil { + return err + } + v.users.Set(caller.String(), &uv) + + return nil +} + +func (v *impl) Unvault(amount uint) error { + caller := std.GetOrigCaller() + uv, err := v.getUserVault(caller) + if err != nil { + return err + } + + balance, err := v.adminToken.BalanceOf(caller) + if err != nil { + return err + } + if balance < uint64(amount) { + return grc20.ErrInsufficientBalance + } + + println("AAA1", std.GetHeight(), uv.redeemMinHeight, uv.lockDuration) + uv.redeemMinHeight = std.GetHeight() + int64(uv.lockDuration) + uv.unvaultedAmount += amount + v.users.Set(caller.String(), uv) + println("AAA2", std.GetHeight(), uv.redeemMinHeight, uv.lockDuration) + return nil +} + +func (v *impl) Redeem() error { + pkgAddr := std.GetOrigPkgAddr() + caller := std.GetOrigCaller() + uv, err := v.getUserVault(caller) + if err != nil { + return err + } + + println("AAA3", std.GetHeight(), uv.redeemMinHeight, uv.lockDuration) + if std.GetHeight() < uv.redeemMinHeight { + return ErrTooEarlyToRedeem + } + // TODO: check balance. (should be optional, but let's be sure). + // TODO: check height. + + // transfer token. + err = v.adminToken.Transfer(pkgAddr, caller, uint64(uv.unvaultedAmount)) + if err != nil { + return err + } + + uv.unvaultedAmount = 0 + // TODO: if balance == 0 -> destroy? + return nil +} + +func (v *impl) Recover(dest std.Address) error { + // TODO: assert caller (recovery). + // TODO: trasfertToken. + // TODO: destroy? + return nil +} + +func (v *impl) getUserVault(address std.Address) (*userVault, error) { + uvI, exists := v.users.Get(address.String()) + if !exists { + return nil, ErrNoSuchVault + } + uv := uvI.(*userVault) + return uv, nil +} diff --git a/examples/gno.land/r/demo/vault/vault_test.gno b/examples/gno.land/r/demo/vault/vault_test.gno new file mode 100644 index 00000000000..c0dc6499300 --- /dev/null +++ b/examples/gno.land/r/demo/vault/vault_test.gno @@ -0,0 +1,3 @@ +package vault + +// TODO: unit tests, edge cases. diff --git a/examples/gno.land/r/demo/vault/z1_filetest.gno b/examples/gno.land/r/demo/vault/z1_filetest.gno new file mode 100644 index 00000000000..34d38afef1f --- /dev/null +++ b/examples/gno.land/r/demo/vault/z1_filetest.gno @@ -0,0 +1,78 @@ +package main + +import ( + "std" + + "gno.land/p/demo/grc/exts/vault" + "gno.land/p/demo/grc/grc20" + "gno.land/p/demo/testutils" + "gno.land/p/demo/ufmt" +) + +func main() { + alice := testutils.TestAddress("alice") + bob := testutils.TestAddress("bob") // recovery request address (cold wallet). + charly := testutils.TestAddress("charly") // recovery dest. + pkgaddr := std.GetOrigPkgAddr() + + // create a fooAdminToken + fooToken (GRC20) pair. + fooAdminToken := grc20.NewAdminToken("Foo", "FOO", 4) + fooAdminToken.Mint(alice, 1000) + fooToken := fooAdminToken.GRC20() + + printBalances := func() { + aliceBalance, _ := fooToken.BalanceOf(alice) + bobBalance, _ := fooToken.BalanceOf(bob) + charlyBalance, _ := fooToken.BalanceOf(charly) + pkgBalance, _ := fooToken.BalanceOf(pkgaddr) + println(ufmt.Sprintf( + "balances: alice=%d, bob=%d, charly=%d, pkg=%d, height=%d", + aliceBalance, bobBalance, charlyBalance, pkgBalance, std.GetHeight(), + )) + } + + // create a vault for fooAdminToken. + v := vault.New(fooAdminToken) + printBalances() + + // alice deposits 300 with an unlock duration of 5 blocks. + std.TestSetOrigCaller(alice) + lockAmount := uint(300) + lockDuration := uint(5) + checkErr(v.Deposit(lockAmount, bob, lockDuration)) + printBalances() + + // alice calls unvault for 200 tokens. + checkErr(v.Unvault(200)) + printBalances() + + // alice waits for few blocks. + std.TestSkipHeights(int64(lockDuration) + 1) + printBalances() + + // alice redeems 200 tokens. + checkErr(v.Redeem()) + printBalances() + + // bob instantly recover everything in the wallet. + std.TestSetOrigCaller(bob) + checkErr(v.Recover(charly)) + printBalances() +} + +func checkErr(err error) { + if err != nil { + panic(err) + } +} + +// Output: +// balances: alice=1000, bob=0, charly=0, pkg=0, height=123 +// balances: alice=700, bob=0, charly=0, pkg=300, height=123 +// AAA1 123 0 5 +// AAA2 123 128 5 +// balances: alice=700, bob=0, charly=0, pkg=300, height=123 +// balances: alice=700, bob=0, charly=0, pkg=300, height=129 +// AAA3 129 128 5 +// balances: alice=900, bob=0, charly=0, pkg=100, height=129 +// balances: alice=900, bob=0, charly=0, pkg=100, height=129 From 1e7d43d170b7037165f13cb7550923949b6e28c1 Mon Sep 17 00:00:00 2001 From: moul <94029+moul@users.noreply.github.com> Date: Mon, 8 Jul 2024 15:13:36 -0500 Subject: [PATCH 2/4] chore: fixup Signed-off-by: moul <94029+moul@users.noreply.github.com> --- examples/gno.land/r/demo/vault/types.gno | 8 +++++ examples/gno.land/r/demo/vault/vault.gno | 9 ++---- .../gno.land/r/demo/vault/z1_filetest.gno | 32 +++++++++---------- 3 files changed, 26 insertions(+), 23 deletions(-) create mode 100644 examples/gno.land/r/demo/vault/types.gno diff --git a/examples/gno.land/r/demo/vault/types.gno b/examples/gno.land/r/demo/vault/types.gno new file mode 100644 index 00000000000..82cc35e1f58 --- /dev/null +++ b/examples/gno.land/r/demo/vault/types.gno @@ -0,0 +1,8 @@ +package vault + +import "errors" + +var ( + ErrTooEarlyToRedeem = errors.New("too early to redeem") + ErrNoSuchVault = errors.New("no such vault") +) diff --git a/examples/gno.land/r/demo/vault/vault.gno b/examples/gno.land/r/demo/vault/vault.gno index 47aa70f483e..331dd327c33 100644 --- a/examples/gno.land/r/demo/vault/vault.gno +++ b/examples/gno.land/r/demo/vault/vault.gno @@ -15,7 +15,7 @@ type Vault interface { Redeem() error } -func New(adminToken *grc20.AdminToken) Vault { +func New(adminToken *grc20.Banker) Vault { return &impl{ adminToken: adminToken, users: avl.Tree{}, @@ -23,7 +23,7 @@ func New(adminToken *grc20.AdminToken) Vault { } type impl struct { - adminToken *grc20.AdminToken + adminToken *grc20.Banker users avl.Tree // std.Address -> userVault } @@ -66,10 +66,7 @@ func (v *impl) Unvault(amount uint) error { return err } - balance, err := v.adminToken.BalanceOf(caller) - if err != nil { - return err - } + balance := v.adminToken.BalanceOf(caller) if balance < uint64(amount) { return grc20.ErrInsufficientBalance } diff --git a/examples/gno.land/r/demo/vault/z1_filetest.gno b/examples/gno.land/r/demo/vault/z1_filetest.gno index 34d38afef1f..6c7f76d30db 100644 --- a/examples/gno.land/r/demo/vault/z1_filetest.gno +++ b/examples/gno.land/r/demo/vault/z1_filetest.gno @@ -3,32 +3,30 @@ package main import ( "std" - "gno.land/p/demo/grc/exts/vault" - "gno.land/p/demo/grc/grc20" "gno.land/p/demo/testutils" "gno.land/p/demo/ufmt" + "gno.land/r/demo/tests/test20" + "gno.land/r/demo/vault" ) func main() { - alice := testutils.TestAddress("alice") - bob := testutils.TestAddress("bob") // recovery request address (cold wallet). - charly := testutils.TestAddress("charly") // recovery dest. - pkgaddr := std.GetOrigPkgAddr() - - // create a fooAdminToken + fooToken (GRC20) pair. - fooAdminToken := grc20.NewAdminToken("Foo", "FOO", 4) - fooAdminToken.Mint(alice, 1000) - fooToken := fooAdminToken.GRC20() + var ( + alice = testutils.TestAddress("alice") + bob = testutils.TestAddress("bob") // recovery request address (cold wallet). + charly = testutils.TestAddress("charly") // recovery dest. + pkgaddr = std.GetOrigPkgAddr() + token = test20.Token + ) printBalances := func() { - aliceBalance, _ := fooToken.BalanceOf(alice) - bobBalance, _ := fooToken.BalanceOf(bob) - charlyBalance, _ := fooToken.BalanceOf(charly) - pkgBalance, _ := fooToken.BalanceOf(pkgaddr) + aliceBalance := fooToken.BalanceOf(alice) + bobBalance := fooToken.BalanceOf(bob) + charlyBalance := fooToken.BalanceOf(charly) + pkgBalance := fooToken.BalanceOf(pkgaddr) + height := std.GetHeight() println(ufmt.Sprintf( "balances: alice=%d, bob=%d, charly=%d, pkg=%d, height=%d", - aliceBalance, bobBalance, charlyBalance, pkgBalance, std.GetHeight(), - )) + aliceBalance, bobBalance, charlyBalance, pkgBalance, height)) } // create a vault for fooAdminToken. From 7a7c65e80c4cb2c7541eda79bbde3ba863ed12ab Mon Sep 17 00:00:00 2001 From: moul <94029+moul@users.noreply.github.com> Date: Mon, 8 Jul 2024 15:16:33 -0500 Subject: [PATCH 3/4] feat: add r/demo/tests/test20 Signed-off-by: moul <94029+moul@users.noreply.github.com> --- examples/gno.land/r/demo/tests/test20/gno.mod | 3 +++ .../gno.land/r/demo/tests/test20/test20.gno | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 examples/gno.land/r/demo/tests/test20/gno.mod create mode 100644 examples/gno.land/r/demo/tests/test20/test20.gno diff --git a/examples/gno.land/r/demo/tests/test20/gno.mod b/examples/gno.land/r/demo/tests/test20/gno.mod new file mode 100644 index 00000000000..5be8c8d409f --- /dev/null +++ b/examples/gno.land/r/demo/tests/test20/gno.mod @@ -0,0 +1,3 @@ +module gno.land/r/demo/tests/test20 + +require gno.land/p/demo/grc/grc20 v0.0.0-latest diff --git a/examples/gno.land/r/demo/tests/test20/test20.gno b/examples/gno.land/r/demo/tests/test20/test20.gno new file mode 100644 index 00000000000..6d1aaf108bf --- /dev/null +++ b/examples/gno.land/r/demo/tests/test20/test20.gno @@ -0,0 +1,18 @@ +// Package test20 implements a deliberately insecure ERC20 token for testing purposes. +// The Test20 token allows anyone to mint any amount of tokens to any address, making +// it unsuitable for production use. The primary goal of this package is to facilitate +// testing and experimentation without any security measures or restrictions. +// +// WARNING: This token is highly insecure and should not be used in any +// production environment. It is intended solely for testing and +// educational purposes. +package test20 + +import "gno.land/p/demo/grc/grc20" + +var ( + Banker = grc20.NewBanker("Test20", "TST", 4) + Token = Banker.Token() +) + +// XXX func init() { grc20reg.Register(Pub, "") } // Depends on #2516 From 246af4f623730cb67362a155cde86e61b066cca8 Mon Sep 17 00:00:00 2001 From: moul <94029+moul@users.noreply.github.com> Date: Mon, 8 Jul 2024 19:12:09 -0500 Subject: [PATCH 4/4] chore: fixup Signed-off-by: moul <94029+moul@users.noreply.github.com> --- examples/gno.land/r/demo/vault/vault.gno | 1 - .../gno.land/r/demo/vault/z1_filetest.gno | 28 ++++++++++--------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/examples/gno.land/r/demo/vault/vault.gno b/examples/gno.land/r/demo/vault/vault.gno index 331dd327c33..ea24a12a393 100644 --- a/examples/gno.land/r/demo/vault/vault.gno +++ b/examples/gno.land/r/demo/vault/vault.gno @@ -7,7 +7,6 @@ import ( "gno.land/p/demo/grc/grc20" ) -// Vault is a GRC20 compatible token with vault features. type Vault interface { Deposit(amount uint, recovery std.Address, lockDuration uint) error Unvault(amount uint) error diff --git a/examples/gno.land/r/demo/vault/z1_filetest.gno b/examples/gno.land/r/demo/vault/z1_filetest.gno index 6c7f76d30db..4f2c3b1e597 100644 --- a/examples/gno.land/r/demo/vault/z1_filetest.gno +++ b/examples/gno.land/r/demo/vault/z1_filetest.gno @@ -11,28 +11,30 @@ import ( func main() { var ( - alice = testutils.TestAddress("alice") - bob = testutils.TestAddress("bob") // recovery request address (cold wallet). - charly = testutils.TestAddress("charly") // recovery dest. - pkgaddr = std.GetOrigPkgAddr() - token = test20.Token + alice = testutils.TestAddress("alice") + bob = testutils.TestAddress("bob") // recovery request address (cold wallet). + charly = testutils.TestAddress("charly") // recovery dest. + pkgaddr = std.GetOrigPkgAddr() + t20token = test20.Token ) printBalances := func() { - aliceBalance := fooToken.BalanceOf(alice) - bobBalance := fooToken.BalanceOf(bob) - charlyBalance := fooToken.BalanceOf(charly) - pkgBalance := fooToken.BalanceOf(pkgaddr) + aliceBalance := t20token.BalanceOf(alice) + bobBalance := t20token.BalanceOf(bob) + charlyBalance := t20token.BalanceOf(charly) + pkgBalance := t20token.BalanceOf(pkgaddr) height := std.GetHeight() println(ufmt.Sprintf( "balances: alice=%d, bob=%d, charly=%d, pkg=%d, height=%d", aliceBalance, bobBalance, charlyBalance, pkgBalance, height)) } - // create a vault for fooAdminToken. - v := vault.New(fooAdminToken) + // create a vault for t20token. + v := vault.New(t20token) printBalances() + return // XXX + // alice deposits 300 with an unlock duration of 5 blocks. std.TestSetOrigCaller(alice) lockAmount := uint(300) @@ -40,7 +42,7 @@ func main() { checkErr(v.Deposit(lockAmount, bob, lockDuration)) printBalances() - // alice calls unvault for 200 tokens. + // alice calls unvault for 200 t20tokens. checkErr(v.Unvault(200)) printBalances() @@ -48,7 +50,7 @@ func main() { std.TestSkipHeights(int64(lockDuration) + 1) printBalances() - // alice redeems 200 tokens. + // alice redeems 200 t20tokens. checkErr(v.Redeem()) printBalances()