From 2a198457fc535ba2d3747bfdd6db37046dd7a09b Mon Sep 17 00:00:00 2001 From: n3wbie Date: Thu, 26 Dec 2024 16:59:51 +0900 Subject: [PATCH 1/7] feat: ListRegisteredTokens in common realm --- _deploy/r/gnoswap/common/grc20reg_helper.gno | 23 +++++ .../r/gnoswap/common/grc20reg_helper_test.gno | 87 +++++++++++++++++++ 2 files changed, 110 insertions(+) diff --git a/_deploy/r/gnoswap/common/grc20reg_helper.gno b/_deploy/r/gnoswap/common/grc20reg_helper.gno index b3f23147d..a959e94d2 100644 --- a/_deploy/r/gnoswap/common/grc20reg_helper.gno +++ b/_deploy/r/gnoswap/common/grc20reg_helper.gno @@ -1,7 +1,9 @@ package common import ( + "regexp" "std" + "strings" "gno.land/p/demo/grc/grc20" "gno.land/p/demo/ufmt" @@ -58,6 +60,27 @@ func MustRegistered(path string) { } } +// ListRegisteredTokens returns the list of registered tokens +// NOTE: +// - Unfortunate, grc20reg doesn't support this. +// - We need to parse the rendered grc20reg page to get the list of registered tokens. +func ListRegisteredTokens() []string { + render := grc20reg.Render("") + return extractTokenPathsFromRender(render) +} + +func extractTokenPathsFromRender(render string) []string { + re := regexp.MustCompile(`\[gno\.land/r/[^\]]+\]`) + matches := re.FindAllString(render, -1) + + tokenPaths := []string{} + for _, match := range matches { + tokenPath := strings.Trim(match, "[]") // Remove the brackets + tokenPaths = append(tokenPaths, tokenPath) + } + return tokenPaths +} + // TotalSupply returns the total supply of the token func TotalSupply(path string) uint64 { return GetToken(path).TotalSupply() diff --git a/_deploy/r/gnoswap/common/grc20reg_helper_test.gno b/_deploy/r/gnoswap/common/grc20reg_helper_test.gno index 0ae8b6379..cbb0707f2 100644 --- a/_deploy/r/gnoswap/common/grc20reg_helper_test.gno +++ b/_deploy/r/gnoswap/common/grc20reg_helper_test.gno @@ -153,6 +153,74 @@ func TestMustRegistered(t *testing.T) { }) } +func TestListRegisteredTokens(t *testing.T) { + t.Skip("skipping tests -> can not mock grc20reg.Render() || testing extractTokenPathsFromRender() does cover this") +} + +func TestExtractTokenPathsFromRender(t *testing.T) { + var ( + wugnotPath = "gno.land/r/demo/wugnot" + gnsPath = "gno.land/r/gnoswap/v1/gns" + fooPath = "gno.land/r/onbloc/foo" + quxPath = "gno.land/r/onbloc/qux" + ) + + // NOTE: following strings are return from grc20reg.Render() + renderList := []string{ + // no registered token + `No registered token.`, + + // 1 token + `- **wrapped GNOT** - [gno.land/r/demo/wugnot](/r/demo/wugnot) - [info](/r/demo/grc20reg:gno.land/r/demo/wugnot)`, + + // 2 tokens + `- **wrapped GNOT** - [gno.land/r/demo/wugnot](/r/demo/wugnot) - [info](/r/demo/grc20reg:gno.land/r/demo/wugnot) +- **Gnoswap** - [gno.land/r/gnoswap/v1/gns](/r/gnoswap/v1/gns) - [info](/r/demo/grc20reg:gno.land/r/gnoswap/v1/gns) +`, + + // 4 tokens + `- **wrapped GNOT** - [gno.land/r/demo/wugnot](/r/demo/wugnot) - [info](/r/demo/grc20reg:gno.land/r/demo/wugnot) +- **Gnoswap** - [gno.land/r/gnoswap/v1/gns](/r/gnoswap/v1/gns) - [info](/r/demo/grc20reg:gno.land/r/gnoswap/v1/gns) +- **Baz** - [gno.land/r/onbloc/foo](/r/onbloc/foo) - [info](/r/demo/grc20reg:gno.land/r/onbloc/foo) +- **Qux** - [gno.land/r/onbloc/qux](/r/onbloc/qux) - [info](/r/demo/grc20reg:gno.land/r/onbloc/qux) +`, + } + + tests := []struct { + name string + render string + expected []string + }{ + { + name: "no registered token", + render: renderList[0], + expected: []string{}, + }, + { + name: "1 registered token", + render: renderList[1], + expected: []string{wugnotPath}, + }, + { + name: "2 registered tokens", + render: renderList[2], + expected: []string{wugnotPath, gnsPath}, + }, + { + name: "4 registered tokens", + render: renderList[3], + expected: []string{wugnotPath, gnsPath, fooPath, quxPath}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + extracted := extractTokenPathsFromRender(tt.render) + uassert.True(t, areSlicesEqual(t, tt.expected, extracted)) + }) + } +} + func TestTotalSupply(t *testing.T) { // result from grc2reg and (direct import/call) should be the same uassert.Equal(t, foo20.TotalSupply(), TotalSupply(tokenPath)) @@ -172,3 +240,22 @@ func TestAllowance(t *testing.T) { // result from grc2reg and (direct import/call) should be the same uassert.Equal(t, foo20.Allowance(AddrToUser(owner), AddrToUser(spender)), Allowance(tokenPath, owner, spender)) } + +// areSlicesEqual compares two slices of strings +func areSlicesEqual(t *testing.T, a, b []string) bool { + t.Helper() + + // Check if lengths are different + if len(a) != len(b) { + return false + } + + // Compare each element + for i := range a { + if a[i] != b[i] { + return false + } + } + + return true +} From 1f2d0c4be0ed4611b467292becef3ef94b219745 Mon Sep 17 00:00:00 2001 From: n3wbie Date: Thu, 26 Dec 2024 17:30:35 +0900 Subject: [PATCH 2/7] feat: use grc20reg in `protocol_fee` --- protocol_fee/gno.mod | 7 - protocol_fee/protocol_fee.gno | 84 +++++---- protocol_fee/protocol_fee_test.gno | 79 ++++++++ .../tests/__TEST_protocol_fee_test.gnoA | 81 -------- protocol_fee/token_register.gno | 175 ------------------ 5 files changed, 132 insertions(+), 294 deletions(-) create mode 100644 protocol_fee/protocol_fee_test.gno delete mode 100644 protocol_fee/tests/__TEST_protocol_fee_test.gnoA delete mode 100644 protocol_fee/token_register.gno diff --git a/protocol_fee/gno.mod b/protocol_fee/gno.mod index d254f292e..4bac85f15 100644 --- a/protocol_fee/gno.mod +++ b/protocol_fee/gno.mod @@ -1,8 +1 @@ module gno.land/r/gnoswap/v1/protocol_fee - -require ( - gno.land/p/demo/ufmt v0.0.0-latest - gno.land/p/demo/users v0.0.0-latest - gno.land/r/gnoswap/v1/common v0.0.0-latest - gno.land/r/gnoswap/v1/consts v0.0.0-latest -) diff --git a/protocol_fee/protocol_fee.gno b/protocol_fee/protocol_fee.gno index 7dbef73aa..13b862ab4 100644 --- a/protocol_fee/protocol_fee.gno +++ b/protocol_fee/protocol_fee.gno @@ -3,6 +3,7 @@ package protocol_fee import ( "std" + "gno.land/p/demo/avl" "gno.land/p/demo/ufmt" "gno.land/r/gnoswap/v1/common" @@ -14,19 +15,24 @@ var ( gnsToDevOps uint64 gnsToGovStaker uint64 - accuToGovStaker = make(map[string]uint64) // tokenPath -> amount + accuToGovStaker = avl.NewTree() ) +// DistributeProtocolFee distributes the protocol fee to devOps and gov/staker. func DistributeProtocolFee() { - common.IsHalted() + assertOnlyNotHalted() + + tokens := common.ListRegisteredTokens() + if len(tokens) == 0 { + return + } - tokens := GetRegisteredTokens() for _, token := range tokens { // default distribute protocol fee percent // govStaker 100% // ... - balance := balanceOfByRegisterCall(token, consts.PROTOCOL_FEE_ADDR) + balance := common.BalanceOf(token, consts.PROTOCOL_FEE_ADDR) if balance > 0 { toDevOps := balance * devOpsPct / 10000 // default 0% toGovStaker := balance - toDevOps // default 100% @@ -36,14 +42,15 @@ func DistributeProtocolFee() { gnsToGovStaker = toGovStaker } - accuToGovStaker[token] += toGovStaker + addAccuToGovStaker(token, toGovStaker) + tokenTeller := common.GetTokenTeller(token) if toDevOps > 0 { - transferByRegisterCall(token, consts.DEV_OPS, toDevOps) + tokenTeller.Transfer(consts.DEV_OPS, toDevOps) } if toGovStaker > 0 { - transferByRegisterCall(token, consts.GOV_STAKER_ADDR, toGovStaker) + tokenTeller.Transfer(consts.GOV_STAKER_ADDR, toGovStaker) } } } @@ -61,15 +68,6 @@ func SetDevOpsPctByAdmin(pct uint64) { } setDevOpsPct(pct) - - prevAddr, prevRealm := getPrev() - - std.Emit( - "SetDevOpsPctByAdmin", - "prevAddr", prevAddr, - "prevRealm", prevRealm, - "pct", ufmt.Sprintf("%d", pct), - ) } // SetDevOpsPct sets the devOpsPct. @@ -81,45 +79,69 @@ func SetDevOpsPct(pct uint64) { } setDevOpsPct(pct) - - prevAddr, prevRealm := getPrev() - - std.Emit( - "SetDevOpsPct", - "prevAddr", prevAddr, - "prevRealm", prevRealm, - "pct", ufmt.Sprintf("%d", pct), - ) } +// setDevOpsPct sets the devOpsPct. func setDevOpsPct(pct uint64) { - common.IsHalted() - if pct > 10000 { panic(addDetailToError( errInvalidPct, - ufmt.Sprintf("protocol_fee.gno__setDevOpsPct() || pct(%d) should not be bigger than 10000", pct), + ufmt.Sprintf("pct(%d) should not be bigger than 10000", pct), )) } devOpsPct = pct + + prevAddr, prevRealm := getPrev() + std.Emit( + "SetDevOpsPct", + "prevAddr", prevAddr, + "prevRealm", prevRealm, + "pct", ufmt.Sprintf("%d", pct), + ) } +// GetLastTransferToDevOps returns the last transfer to devOps. func GetLastTransferToDevOps() uint64 { return gnsToDevOps } -func GetAccuTransferToGovStaker() map[string]uint64 { +// GetAccuTransferToGovStaker returns the accuToGovStaker. +func GetAccuTransferToGovStaker() *avl.Tree { return accuToGovStaker } +// GetAccuTransferToGovStakerByTokenPath returns the accumulated transfer to gov/staker by token path. +func GetAccuTransferToGovStakerByTokenPath(path string) uint64 { + amountI, exists := accuToGovStaker.Get(path) + if !exists { + return 0 + } + + return amountI.(uint64) +} + +// ClearAccuTransferToGovStaker clears the accuToGovStaker. +// Only gov/staker can execute this function. func ClearAccuTransferToGovStaker() { - common.IsHalted() + assertOnlyNotHalted() caller := std.PrevRealm().Addr() if err := common.GovStakerOnly(caller); err != nil { panic(err) } - accuToGovStaker = make(map[string]uint64) + accuToGovStaker = avl.NewTree() +} + +// assertOnlyNotHalted panics if the contract is halted. +func assertOnlyNotHalted() { + common.IsHalted() +} + +// addAccuToGovStaker adds the amount to the accuToGovStaker by token path. +func addAccuToGovStaker(path string, amount uint64) { + before := GetAccuTransferToGovStakerByTokenPath(path) + after := before + amount + accuToGovStaker.Set(path, after) } diff --git a/protocol_fee/protocol_fee_test.gno b/protocol_fee/protocol_fee_test.gno new file mode 100644 index 000000000..4feb35538 --- /dev/null +++ b/protocol_fee/protocol_fee_test.gno @@ -0,0 +1,79 @@ +package protocol_fee + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" + + "gno.land/r/gnoswap/v1/common" + "gno.land/r/gnoswap/v1/consts" + + "gno.land/r/onbloc/bar" + "gno.land/r/onbloc/qux" +) + +var ( + adminAddr = consts.ADMIN + adminUser = common.AddrToUser(adminAddr) + adminRealm = std.NewUserRealm(adminAddr) +) + +func TestDistributeProtocolFee(t *testing.T) { + // admin > protocol_fee + // send qux, bar for testing + std.TestSetRealm(adminRealm) + bar.Transfer(common.AddrToUser(consts.PROTOCOL_FEE_ADDR), 1000) + qux.Transfer(common.AddrToUser(consts.PROTOCOL_FEE_ADDR), 1000) + + uassert.Equal(t, bar.BalanceOf(common.AddrToUser(consts.PROTOCOL_FEE_ADDR)), uint64(1000)) + uassert.Equal(t, bar.BalanceOf(common.AddrToUser(consts.DEV_OPS)), uint64(0)) + uassert.Equal(t, bar.BalanceOf(common.AddrToUser(consts.GOV_STAKER_ADDR)), uint64(0)) + + uassert.Equal(t, qux.BalanceOf(common.AddrToUser(consts.PROTOCOL_FEE_ADDR)), uint64(1000)) + uassert.Equal(t, qux.BalanceOf(common.AddrToUser(consts.DEV_OPS)), uint64(0)) + uassert.Equal(t, qux.BalanceOf(common.AddrToUser(consts.GOV_STAKER_ADDR)), uint64(0)) + + DistributeProtocolFee() + + uassert.Equal(t, bar.BalanceOf(common.AddrToUser(consts.PROTOCOL_FEE_ADDR)), uint64(0)) + uassert.Equal(t, bar.BalanceOf(common.AddrToUser(consts.DEV_OPS)), uint64(0)) + uassert.Equal(t, bar.BalanceOf(common.AddrToUser(consts.GOV_STAKER_ADDR)), uint64(1000)) + + uassert.Equal(t, qux.BalanceOf(common.AddrToUser(consts.PROTOCOL_FEE_ADDR)), uint64(0)) + uassert.Equal(t, qux.BalanceOf(common.AddrToUser(consts.DEV_OPS)), uint64(0)) + uassert.Equal(t, qux.BalanceOf(common.AddrToUser(consts.GOV_STAKER_ADDR)), uint64(1000)) +} + +func TestSetDevOpsPctByAdminNoPermission(t *testing.T) { + dummy := testutils.TestAddress("dummy") + dummyRealm := std.NewUserRealm(dummy) + std.TestSetRealm(dummyRealm) + + uassert.PanicsWithMessage( + t, `caller(g1v36k6mteta047h6lta047h6lta047h6lz7gmv8) has no permission`, func() { SetDevOpsPctByAdmin(123) }, + ) +} + +func TestSetDevOpsPctByAdminInvalidFee(t *testing.T) { + std.TestSetRealm(adminRealm) + + uassert.PanicsWithMessage( + t, + `[GNOSWAP-PROTOCOL_FEE-006] invalid percentage || pct(100001) should not be bigger than 10000`, + func() { + SetDevOpsPctByAdmin(100001) + }, + ) +} + +func TestSetDevOpsPctByAdmin(t *testing.T) { + std.TestSetRealm(adminRealm) + + uassert.Equal(t, GetDevOpsPct(), uint64(0)) + + SetDevOpsPctByAdmin(123) + + uassert.Equal(t, GetDevOpsPct(), uint64(123)) +} diff --git a/protocol_fee/tests/__TEST_protocol_fee_test.gnoA b/protocol_fee/tests/__TEST_protocol_fee_test.gnoA deleted file mode 100644 index 71a5fbbcd..000000000 --- a/protocol_fee/tests/__TEST_protocol_fee_test.gnoA +++ /dev/null @@ -1,81 +0,0 @@ -package protocol_fee - -import ( - "std" - "testing" - - "gno.land/p/demo/testutils" - "gno.land/p/demo/uassert" - pusers "gno.land/p/demo/users" - - "gno.land/r/onbloc/bar" - "gno.land/r/onbloc/qux" - - "gno.land/r/gnoswap/v1/consts" -) - -func TestDistributeProtocolFee(t *testing.T) { - // admin > protocol_fee - // send qux, bar for testing - std.TestSetRealm(adminRealm) - bar.Transfer(a2u(consts.PROTOCOL_FEE_ADDR), 1000) - qux.Transfer(a2u(consts.PROTOCOL_FEE_ADDR), 1000) - - uassert.Equal(t, bar.BalanceOf(a2u(consts.PROTOCOL_FEE_ADDR)), uint64(1000)) - uassert.Equal(t, bar.BalanceOf(a2u(consts.DEV_OPS)), uint64(0)) - uassert.Equal(t, bar.BalanceOf(a2u(consts.GOV_STAKER_ADDR)), uint64(0)) - - uassert.Equal(t, qux.BalanceOf(a2u(consts.PROTOCOL_FEE_ADDR)), uint64(1000)) - uassert.Equal(t, qux.BalanceOf(a2u(consts.DEV_OPS)), uint64(0)) - uassert.Equal(t, qux.BalanceOf(a2u(consts.GOV_STAKER_ADDR)), uint64(0)) - - DistributeProtocolFee() - - uassert.Equal(t, bar.BalanceOf(a2u(consts.PROTOCOL_FEE_ADDR)), uint64(0)) - uassert.Equal(t, bar.BalanceOf(a2u(consts.DEV_OPS)), uint64(0)) - uassert.Equal(t, bar.BalanceOf(a2u(consts.GOV_STAKER_ADDR)), uint64(1000)) - - uassert.Equal(t, qux.BalanceOf(a2u(consts.PROTOCOL_FEE_ADDR)), uint64(0)) - uassert.Equal(t, qux.BalanceOf(a2u(consts.DEV_OPS)), uint64(0)) - uassert.Equal(t, qux.BalanceOf(a2u(consts.GOV_STAKER_ADDR)), uint64(1000)) -} - -func TestSetDevOpsPctByAdminNoPermission(t *testing.T) { - dummy := testutils.TestAddress("dummy") - dummyRealm := std.NewUserRealm(dummy) - std.TestSetRealm(dummyRealm) - - uassert.PanicsWithMessage( - t, - `[GNOSWAP-PROTOCOL_FEE-001] caller has no permission || protocol_fee.gno__SetDevOpsPctByAdmin() || only admin(g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d) can set devOpsPct, called from g1v36k6mteta047h6lta047h6lta047h6lz7gmv8`, - func() { - SetDevOpsPctByAdmin(123) - }, - ) -} - -func TestSetDevOpsPctByAdminInvalidFee(t *testing.T) { - std.TestSetRealm(adminRealm) - - uassert.PanicsWithMessage( - t, - `[GNOSWAP-PROTOCOL_FEE-006] invalid percentage || protocol_fee.gno__setDevOpsPct() || pct(100001) should not be bigger than 10000`, - func() { - SetDevOpsPctByAdmin(100001) - }, - ) -} - -func TestSetDevOpsPctByAdmin(t *testing.T) { - std.TestSetRealm(adminRealm) - - uassert.Equal(t, GetDevOpsPct(), uint64(0)) - - SetDevOpsPctByAdmin(123) - - uassert.Equal(t, GetDevOpsPct(), uint64(123)) -} - -func a2u(addr std.Address) pusers.AddressOrName { - return pusers.AddressOrName(addr) -} diff --git a/protocol_fee/token_register.gno b/protocol_fee/token_register.gno deleted file mode 100644 index ca0cf66a9..000000000 --- a/protocol_fee/token_register.gno +++ /dev/null @@ -1,175 +0,0 @@ -package protocol_fee - -import ( - "std" - "strings" - - "gno.land/p/demo/ufmt" - pusers "gno.land/p/demo/users" - - "gno.land/r/gnoswap/v1/common" - "gno.land/r/gnoswap/v1/consts" -) - -type GRC20Interface interface { - Transfer() func(to pusers.AddressOrName, amount uint64) - TransferFrom() func(from, to pusers.AddressOrName, amount uint64) - BalanceOf() func(owner pusers.AddressOrName) uint64 - Approve() func(spender pusers.AddressOrName, amount uint64) -} - -var ( - registered = make(map[string]GRC20Interface) - locked = false // mutex -) - -func GetRegisteredTokens() []string { - tokens := make([]string, 0, len(registered)) - for k := range registered { - tokens = append(tokens, k) - } - return tokens -} - -// RegisterGRC20Interface registers a GRC20 token interface -// -// Panics: -// - caller is not the admin -// - token already registered -func RegisterGRC20Interface(pkgPath string, igrc20 GRC20Interface) { - prevAddr := std.PrevRealm().Addr() - prevPath := std.PrevRealm().PkgPath() - if !(prevAddr == consts.TOKEN_REGISTER || prevPath == consts.INIT_REGISTER_PATH || strings.HasPrefix(prevPath, "gno.land/r/g1er355fkjksqpdtwmhf5penwa82p0rhqxkkyhk5")) { - panic(addDetailToError( - errNoPermission, - ufmt.Sprintf("token_register.gno__RegisterGRC20Interface() || only register(%s) can register token, called from %s", consts.TOKEN_REGISTER, prevAddr), - )) - } - - pkgPath = handleNative(pkgPath) - - _, found := registered[pkgPath] - if found { - panic(addDetailToError( - errAlreadyRegistered, - ufmt.Sprintf("token_register.gno__RegisterGRC20Interface() || token(%s) already registered", pkgPath), - )) - } - - registered[pkgPath] = igrc20 -} - -// UnregisterGRC20Interface unregisters a GRC20 token interface -// -// Panics: -// - caller is not the admin -func UnregisterGRC20Interface(pkgPath string) { - if err := common.SatisfyCond(isUserCall()); err != nil { - panic(err) - } - - caller := std.PrevRealm().Addr() - if err := common.TokenRegisterOnly(caller); err != nil { - panic(err) - } - - pkgPath = handleNative(pkgPath) - - _, found := registered[pkgPath] - if found { - delete(registered, pkgPath) - } -} - -func transferByRegisterCall(pkgPath string, to std.Address, amount uint64) bool { - pkgPath = handleNative(pkgPath) - - _, found := registered[pkgPath] - if !found { - panic(addDetailToError( - errNotRegistered, - ufmt.Sprintf("token_register.gno__transferByRegisterCall() || token(%s) not registered", pkgPath), - )) - } - - if !locked { - locked = true - registered[pkgPath].Transfer()(pusers.AddressOrName(to), amount) - - defer func() { - locked = false - }() - } else { - panic(addDetailToError( - errLocked, - ufmt.Sprintf("token_register.gno__transferByRegisterCall() || expected locked(%t) to be false", locked), - )) - } - return true -} - -func transferFromByRegisterCall(pkgPath string, from, to std.Address, amount uint64) bool { - pkgPath = handleNative(pkgPath) - - _, found := registered[pkgPath] - if !found { - panic(addDetailToError( - errNotRegistered, - ufmt.Sprintf("token_register.gno__transferFromByRegisterCall() || token(%s) not registered", pkgPath), - )) - } - - if !locked { - locked = true - registered[pkgPath].TransferFrom()(pusers.AddressOrName(from), pusers.AddressOrName(to), amount) - - defer func() { - locked = false - }() - } else { - panic(addDetailToError( - errLocked, - ufmt.Sprintf("token_register.gno__transferFromByRegisterCall() || expected locked(%t) to be false", locked), - )) - } - return true -} - -func balanceOfByRegisterCall(pkgPath string, owner std.Address) uint64 { - pkgPath = handleNative(pkgPath) - - _, found := registered[pkgPath] - if !found { - panic(addDetailToError( - errNotRegistered, - ufmt.Sprintf("token_register.gno__balanceOfByRegisterCall() || token(%s) not registered", pkgPath), - )) - } - - balance := registered[pkgPath].BalanceOf()(pusers.AddressOrName(owner)) - return balance -} - -func approveByRegisterCall(pkgPath string, spender std.Address, amount uint64) bool { - pkgPath = handleNative(pkgPath) - - _, found := registered[pkgPath] - if !found { - panic(addDetailToError( - errNotRegistered, - ufmt.Sprintf("token_register.gno__approveByRegisterCall() || token(%s) not registered", pkgPath), - )) - } - - registered[pkgPath].Approve()(pusers.AddressOrName(spender), amount) - - return true -} - -func handleNative(pkgPath string) string { - if pkgPath == consts.GNOT { - return consts.WRAPPED_WUGNOT - } - - return pkgPath -} From 6cfd486dae7e2dbd10205d1d02ee5dda14080e95 Mon Sep 17 00:00:00 2001 From: n3wbie Date: Thu, 26 Dec 2024 21:02:09 +0900 Subject: [PATCH 3/7] feat: use grc20reg in `community_pool` --- community_pool/community_pool.gno | 82 ++++--- community_pool/community_pool_test.gno | 219 +++++++++++++++++++ community_pool/gno.mod | 7 - community_pool/tests/community_pool_test.gno | 70 ------ community_pool/token_register.gno | 178 --------------- community_pool/util.gno | 20 -- 6 files changed, 268 insertions(+), 308 deletions(-) create mode 100644 community_pool/community_pool_test.gno delete mode 100644 community_pool/tests/community_pool_test.gno delete mode 100644 community_pool/token_register.gno delete mode 100644 community_pool/util.gno diff --git a/community_pool/community_pool.gno b/community_pool/community_pool.gno index 20436188a..182a6dad9 100644 --- a/community_pool/community_pool.gno +++ b/community_pool/community_pool.gno @@ -6,60 +6,76 @@ import ( "gno.land/p/demo/ufmt" "gno.land/r/gnoswap/v1/common" - "gno.land/r/gnoswap/v1/consts" ) // TransferTokenByAdmin transfers token to the given address. -func TransferTokenByAdmin(pkgPath string, to std.Address, amount uint64) { - caller := std.PrevRealm().Addr() - if err := common.AdminOnly(caller); err != nil { - panic(err) - } - - transferToken(pkgPath, to, amount) +func TransferTokenByAdmin(tokenPath string, to std.Address, amount uint64) { + assertOnlyNotHalted() + assertOnlyAdmin() - prevAddr, prevRealm := getPrev() - std.Emit( - "TransferTokenByAdmin", - "prevAddr", prevAddr, - "prevRealm", prevRealm, - "pkgPath", pkgPath, - "to", to.String(), - "amount", ufmt.Sprintf("%d", amount), - ) + transferToken(tokenPath, to, amount) } // TransferToken transfers token to the given address. // Only governance contract can execute this function via proposal -func TransferToken(pkgPath string, to std.Address, amount uint64) { - caller := std.PrevRealm().Addr() - if err := common.GovernanceOnly(caller); err != nil { - panic(err) - } +func TransferToken(tokenPath string, to std.Address, amount uint64) { + assertOnlyNotHalted() + assertOnlyGovernance() + + transferToken(tokenPath, to, amount) +} - transferToken(pkgPath, to, amount) +// transferToken transfers token to the given address. +func transferToken(tokenPath string, to std.Address, amount uint64) { + teller := common.GetTokenTeller(tokenPath) + checkErr(teller.Transfer(to, amount)) - prevAddr, prevRealm := getPrev() + prevAddr, prevRealm := getPrevAsString() std.Emit( "TransferToken", "prevAddr", prevAddr, "prevRealm", prevRealm, - "pkgPath", pkgPath, + "tokenPath", tokenPath, "to", to.String(), "amount", ufmt.Sprintf("%d", amount), ) } -func transferToken(pkgPath string, to std.Address, amount uint64) { +// checkErr panics if the error is not nil. +func checkErr(err error) { + if err != nil { + panic(err.Error()) + } +} + +// assertOnlyNotHalted panics if the contract is halted. +func assertOnlyNotHalted() { common.IsHalted() +} + +// assertOnlyAdmin panics if the caller is not the admin. +func assertOnlyAdmin() { + caller := getPrevAddr() + if err := common.AdminOnly(caller); err != nil { + panic(err) + } +} - _, found := registered[pkgPath] - if !found { - panic(addDetailToError( - errNotRegistered, - ufmt.Sprintf("community_pool.gno__transferToken() || token(%s) not registered", pkgPath), - )) +// assertOnlyGovernance panics if the caller is not the governance. +func assertOnlyGovernance() { + caller := getPrevAddr() + if err := common.GovernanceOnly(caller); err != nil { + panic(err) } +} + +// getPrevAddr returns the address of the caller. +func getPrevAddr() std.Address { + return std.PrevRealm().Addr() +} - registered[pkgPath].Transfer()(a2u(to), amount) +// getPrevAsString returns the address and realm of the caller as a string. +func getPrevAsString() (string, string) { + prev := std.PrevRealm() + return prev.Addr().String(), prev.PkgPath() } diff --git a/community_pool/community_pool_test.gno b/community_pool/community_pool_test.gno new file mode 100644 index 000000000..e077036ec --- /dev/null +++ b/community_pool/community_pool_test.gno @@ -0,0 +1,219 @@ +package community_pool + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" + + "gno.land/r/gnoswap/v1/common" + "gno.land/r/gnoswap/v1/consts" + + "gno.land/r/gnoswap/v1/gns" +) + +var ( + adminAddr = consts.ADMIN + adminRealm = std.NewUserRealm(adminAddr) + + govRealm = std.NewCodeRealm(consts.GOV_GOVERNANCE_PATH) + + dummyCaller = std.NewUserRealm(testutils.TestAddress("dummyCaller")) + dummyReceiver = testutils.TestAddress("dummyReceiver") +) + +func TestTransferTokenByAdmin(t *testing.T) { + tests := []struct { + name string + setup func() + caller std.Realm + tokenPath string + to std.Address + amount uint64 + shouldPanic bool + panicMsg string + }{ + { + name: "panic if halted", + setup: func() { + std.TestSetRealm(adminRealm) + common.SetHaltByAdmin(true) + }, + tokenPath: consts.GNS_PATH, + to: dummyReceiver, + amount: 1000, + shouldPanic: true, + panicMsg: "[GNOSWAP-COMMON-002] halted || GnoSwap is halted", + }, + { + name: "panic if not admin", + setup: func() { + std.TestSetRealm(adminRealm) + common.SetHaltByAdmin(false) + }, + tokenPath: consts.GNS_PATH, + to: dummyReceiver, + amount: 1000, + shouldPanic: true, + panicMsg: "caller(g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm) has no permission", + }, + { + name: "panic if not enough balance", + setup: func() { + std.TestSetRealm(adminRealm) + gns.Transfer(common.AddrToUser(consts.COMMUNITY_POOL_ADDR), 10_000) + }, + caller: adminRealm, + tokenPath: consts.GNS_PATH, + to: dummyReceiver, + amount: 10_001, + shouldPanic: true, + panicMsg: "insufficient balance", + }, + { + name: "success if enough balance", + caller: adminRealm, + tokenPath: consts.GNS_PATH, + to: dummyReceiver, + amount: 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.setup != nil { + tt.setup() + } + + if tt.caller != (std.Realm{}) { + std.TestSetRealm(tt.caller) + } + + if tt.shouldPanic { + uassert.PanicsWithMessage(t, tt.panicMsg, func() { + TransferTokenByAdmin(tt.tokenPath, tt.to, tt.amount) + }) + } else { + receiverOldBalance := gns.BalanceOf(common.AddrToUser(tt.to)) + TransferTokenByAdmin(tt.tokenPath, tt.to, tt.amount) + receiverNewBalance := gns.BalanceOf(common.AddrToUser(tt.to)) + uassert.Equal(t, receiverNewBalance-receiverOldBalance, tt.amount) + } + }) + } +} + +func TestTransferToken(t *testing.T) { + tests := []struct { + name string + setup func() + caller std.Realm + tokenPath string + to std.Address + amount uint64 + shouldPanic bool + panicMsg string + }{ + { + name: "panic if halted", + setup: func() { + std.TestSetRealm(adminRealm) + common.SetHaltByAdmin(true) + }, + tokenPath: consts.GNS_PATH, + to: dummyReceiver, + amount: 1000, + shouldPanic: true, + panicMsg: "[GNOSWAP-COMMON-002] halted || GnoSwap is halted", + }, + { + name: "panic if not governance", + setup: func() { + std.TestSetRealm(adminRealm) + common.SetHaltByAdmin(false) + }, + tokenPath: consts.GNS_PATH, + to: dummyReceiver, + amount: 1000, + shouldPanic: true, + panicMsg: "caller(g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm) has no permission", + }, + { + name: "governance can't transfer community pool token", + setup: func() { + std.TestSetRealm(adminRealm) + }, + caller: govRealm, + tokenPath: consts.GNS_PATH, + to: dummyReceiver, + amount: 10_001, + shouldPanic: true, + panicMsg: "insufficient balance", + }, + { + name: "governance can transfer community pool token", + caller: govRealm, + tokenPath: consts.GNS_PATH, + to: dummyReceiver, + amount: 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.setup != nil { + tt.setup() + } + + if tt.caller != (std.Realm{}) { + std.TestSetRealm(tt.caller) + } + + if tt.shouldPanic { + uassert.PanicsWithMessage(t, tt.panicMsg, func() { + TransferToken(tt.tokenPath, tt.to, tt.amount) + }) + } else { + receiverOldBalance := gns.BalanceOf(common.AddrToUser(tt.to)) + TransferToken(tt.tokenPath, tt.to, tt.amount) + receiverNewBalance := gns.BalanceOf(common.AddrToUser(tt.to)) + uassert.Equal(t, receiverNewBalance-receiverOldBalance, tt.amount) + } + }) + } +} + +func TestPrivateTransferToken(t *testing.T) { + tests := []struct { + tokenPath string + to std.Address + amount uint64 + shouldPanic bool + panicMsg string + }{ + { + tokenPath: "not_registered_token", + to: dummyReceiver, + amount: 1, + shouldPanic: true, + panicMsg: "unknown token: not_registered_token", + }, + { + tokenPath: consts.GNS_PATH, + to: dummyReceiver, + amount: 1, + shouldPanic: false, + }, + } + + for _, tt := range tests { + if tt.shouldPanic { + uassert.PanicsWithMessage(t, tt.panicMsg, func() { + transferToken(tt.tokenPath, tt.to, tt.amount) + }) + } else { + transferToken(tt.tokenPath, tt.to, tt.amount) + } + } +} diff --git a/community_pool/gno.mod b/community_pool/gno.mod index 02684f86e..72d91102b 100644 --- a/community_pool/gno.mod +++ b/community_pool/gno.mod @@ -1,8 +1 @@ module gno.land/r/gnoswap/v1/community_pool - -require ( - gno.land/p/demo/ufmt v0.0.0-latest - gno.land/p/demo/users v0.0.0-latest - gno.land/r/gnoswap/v1/common v0.0.0-latest - gno.land/r/gnoswap/v1/consts v0.0.0-latest -) diff --git a/community_pool/tests/community_pool_test.gno b/community_pool/tests/community_pool_test.gno deleted file mode 100644 index 033f242c0..000000000 --- a/community_pool/tests/community_pool_test.gno +++ /dev/null @@ -1,70 +0,0 @@ -package community_pool - -import ( - "std" - "testing" - - "gno.land/p/demo/testutils" - "gno.land/p/demo/uassert" - - "gno.land/r/gnoswap/v1/consts" - - "gno.land/r/gnoswap/v1/gns" -) - -var ( - dummyAddr = testutils.TestAddress("dummyAddr") -) - -func TestTransferTokenByAdmin(t *testing.T) { - t.Run("panic if not admin", func(t *testing.T) { - uassert.PanicsWithMessage( - t, - "[GNOSWAP-COMMUNITY_POOL-001] caller has no permission || community_pool.gno__TransferTokenByAdmin() || only admin(g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d) can transfer token, called from g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm", - func() { - TransferTokenByAdmin(consts.GNS_PATH, dummyAddr, 1000) - }, - ) - }) - - t.Run("panic if not registered", func(t *testing.T) { - uassert.PanicsWithMessage( - t, - "[GNOSWAP-COMMUNITY_POOL-002] not registered || community_pool.gno__transferToken() || token(gno.land/r/demo/nope) not registered", - func() { - std.TestSetRealm(adminRealm) - TransferTokenByAdmin("gno.land/r/demo/nope", dummyAddr, 1000) - }, - ) - }) - - t.Run("success if admin", func(t *testing.T) { - adminRealm := std.NewUserRealm(consts.ADMIN) - std.TestSetRealm(adminRealm) - gns.Transfer(a2u(consts.COMMUNITY_POOL_ADDR), 1) - - TransferTokenByAdmin(consts.GNS_PATH, dummyAddr, 1) - }) -} - -func TestTransferToken(t *testing.T) { - t.Run("panic if not governance", func(t *testing.T) { - uassert.PanicsWithMessage( - t, - "[GNOSWAP-COMMUNITY_POOL-001] caller has no permission || community_pool.gno__TransferToken() || only governance(g17s8w2ve7k85fwfnrk59lmlhthkjdted8whvqxd) can transfer token, called from g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm", - func() { - TransferToken(consts.GNS_PATH, dummyAddr, 1) - }, - ) - }) - - t.Run("success if governance", func(t *testing.T) { - adminRealm := std.NewUserRealm(consts.ADMIN) - std.TestSetRealm(adminRealm) - gns.Transfer(a2u(consts.COMMUNITY_POOL_ADDR), 1) - - govRealm := std.NewUserRealm(consts.GOV_GOVERNANCE_ADDR) - std.TestSetRealm(govRealm) - TransferToken(consts.GNS_PATH, dummyAddr, 1) - }) -} diff --git a/community_pool/token_register.gno b/community_pool/token_register.gno deleted file mode 100644 index 54e426af0..000000000 --- a/community_pool/token_register.gno +++ /dev/null @@ -1,178 +0,0 @@ -package community_pool - -import ( - "std" - "strings" - - "gno.land/p/demo/ufmt" - pusers "gno.land/p/demo/users" - - "gno.land/r/gnoswap/v1/common" - "gno.land/r/gnoswap/v1/consts" -) - -type GRC20Interface interface { - Transfer() func(to pusers.AddressOrName, amount uint64) - TransferFrom() func(from, to pusers.AddressOrName, amount uint64) - BalanceOf() func(owner pusers.AddressOrName) uint64 - Approve() func(spender pusers.AddressOrName, amount uint64) -} - -var ( - registered = make(map[string]GRC20Interface) - locked = false // mutex -) - -func GetRegisteredTokens() []string { - tokens := make([]string, 0, len(registered)) - for k := range registered { - tokens = append(tokens, k) - } - return tokens -} - -// RegisterGRC20Interface registers a GRC20 token interface -// -// Panics: -// - caller is not the admin -// - token already registered -func RegisterGRC20Interface(pkgPath string, igrc20 GRC20Interface) { - prevAddr := std.PrevRealm().Addr() - prevPath := std.PrevRealm().PkgPath() - if err := common.SatisfyCond(isValidRegisterCall(prevAddr, prevPath)); err != nil { - panic(err) - } - - pkgPath = handleNative(pkgPath) - - _, found := registered[pkgPath] - if found { - panic(addDetailToError( - errAlreadyRegistered, - ufmt.Sprintf("token_register.gno__RegisterGRC20Interface() || token(%s) already registered", pkgPath), - )) - } - - registered[pkgPath] = igrc20 -} - -func isValidRegisterCall(prevAddr std.Address, prevPath string) bool { - return prevAddr == consts.TOKEN_REGISTER || - prevPath == consts.INIT_REGISTER_PATH || - strings.HasPrefix(prevPath, consts.TOKEN_REGISTER_NAMESPACE) -} - -// UnregisterGRC20Interface unregisters a GRC20 token interface -// -// Panics: -// - caller is not the admin -func UnregisterGRC20Interface(pkgPath string) { - if err := common.SatisfyCond(isUserCall()); err != nil { - panic(err) - } - - caller := std.PrevRealm().Addr() - if err := common.TokenRegisterOnly(caller); err != nil { - panic(err) - } - - pkgPath = handleNative(pkgPath) - - _, found := registered[pkgPath] - if found { - delete(registered, pkgPath) - } -} - -func transferByRegisterCall(pkgPath string, to std.Address, amount uint64) bool { - pkgPath = handleNative(pkgPath) - - _, found := registered[pkgPath] - if !found { - panic(addDetailToError( - errNotRegistered, - ufmt.Sprintf("token_register.gno__transferByRegisterCall() || token(%s) not registered", pkgPath), - )) - } - - if !locked { - locked = true - registered[pkgPath].Transfer()(pusers.AddressOrName(to), amount) - - defer func() { - locked = false - }() - } else { - panic(addDetailToError( - errLocked, - ufmt.Sprintf("token_register.gno__transferByRegisterCall() || expected locked(%t) to be false", locked), - )) - } - return true -} - -func transferFromByRegisterCall(pkgPath string, from, to std.Address, amount uint64) bool { - pkgPath = handleNative(pkgPath) - - _, found := registered[pkgPath] - if !found { - panic(addDetailToError( - errNotRegistered, - ufmt.Sprintf("token_register.gno__transferFromByRegisterCall() || token(%s) not registered", pkgPath), - )) - } - - if !locked { - locked = true - registered[pkgPath].TransferFrom()(pusers.AddressOrName(from), pusers.AddressOrName(to), amount) - - defer func() { - locked = false - }() - } else { - panic(addDetailToError( - errLocked, - ufmt.Sprintf("token_register.gno__transferFromByRegisterCall() || expected locked(%t) to be false", locked), - )) - } - - return true -} - -func balanceOfByRegisterCall(pkgPath string, owner std.Address) uint64 { - pkgPath = handleNative(pkgPath) - - _, found := registered[pkgPath] - if !found { - panic(addDetailToError( - errNotRegistered, - ufmt.Sprintf("token_register.gno__balanceOfByRegisterCall() || token(%s) not registered", pkgPath), - )) - } - - balance := registered[pkgPath].BalanceOf()(pusers.AddressOrName(owner)) - return balance -} - -func approveByRegisterCall(pkgPath string, spender std.Address, amount uint64) bool { - pkgPath = handleNative(pkgPath) - - _, found := registered[pkgPath] - if !found { - panic(addDetailToError( - errNotRegistered, - ufmt.Sprintf("token_register.gno__approveByRegisterCall() || token(%s) not registered", pkgPath), - )) - } - - registered[pkgPath].Approve()(pusers.AddressOrName(spender), amount) - return true -} - -func handleNative(pkgPath string) string { - if pkgPath == consts.GNOT { - return consts.WRAPPED_WUGNOT - } - - return pkgPath -} diff --git a/community_pool/util.gno b/community_pool/util.gno deleted file mode 100644 index 6a8da54b0..000000000 --- a/community_pool/util.gno +++ /dev/null @@ -1,20 +0,0 @@ -package community_pool - -import ( - "std" - - pusers "gno.land/p/demo/users" -) - -func isUserCall() bool { - return std.PrevRealm().IsUser() -} - -func getPrev() (string, string) { - prev := std.PrevRealm() - return prev.Addr().String(), prev.PkgPath() -} - -func a2u(addr std.Address) pusers.AddressOrName { - return pusers.AddressOrName(addr) -} From e39738d65522492ef17191062d1b97bcea0f0ac7 Mon Sep 17 00:00:00 2001 From: n3wbie Date: Thu, 26 Dec 2024 21:09:11 +0900 Subject: [PATCH 4/7] feat: use grc20reg in `launchpad` --- launchpad/launchpad_deposit.gno | 6 +- launchpad/launchpad_init.gno | 8 +- launchpad/launchpad_reward.gno | 12 +- ...unchpad_reward_and_gov_reward_03_test.gnoA | 2 +- launchpad/token_register.gno | 180 ------------------ 5 files changed, 18 insertions(+), 190 deletions(-) delete mode 100644 launchpad/token_register.gno diff --git a/launchpad/launchpad_deposit.gno b/launchpad/launchpad_deposit.gno index df49dceac..8bb68eebd 100644 --- a/launchpad/launchpad_deposit.gno +++ b/launchpad/launchpad_deposit.gno @@ -2,7 +2,6 @@ package launchpad import ( "std" - "strings" "time" "gno.land/p/demo/ufmt" @@ -479,6 +478,7 @@ func checkDepositConditions(project Project) { return } + caller := std.PrevRealm().Addr() for _, condition := range project.conditions { if condition.minAmount == 0 { continue @@ -486,9 +486,9 @@ func checkDepositConditions(project Project) { // check balance var balance uint64 if condition.tokenPath == consts.GOV_XGNS_PATH { - balance = xgns.BalanceOf(a2u(std.PrevRealm().Addr())) + balance = xgns.BalanceOf(a2u(caller)) } else { - balance = balanceOfByRegisterCall(condition.tokenPath, std.PrevRealm().Addr()) + balance = common.BalanceOf(condition.tokenPath, caller) } if balance < condition.minAmount { panic(addDetailToError( diff --git a/launchpad/launchpad_init.gno b/launchpad/launchpad_init.gno index 6cff17af6..976a4c498 100644 --- a/launchpad/launchpad_init.gno +++ b/launchpad/launchpad_init.gno @@ -122,8 +122,8 @@ func CreateProject( common.IsHalted() en.MintAndDistributeGns() - transferFromByRegisterCall( - tokenPath, + tokenTeller := common.GetTokenTeller(tokenPath) + tokenTeller.TransferFrom( std.PrevRealm().Addr(), std.Address(consts.LAUNCHPAD_ADDR), depositAmount, @@ -294,8 +294,8 @@ func TransferLeftFromProjectByAdmin(projectId string, recipient std.Address) uin leftReward := left30 + left90 + left180 if leftReward > 0 { - transferByRegisterCall( - project.tokenPath, + tokenTeller := common.GetTokenTeller(tokenPath) + tokenTeller.Transfer( recipient, leftReward, ) diff --git a/launchpad/launchpad_reward.gno b/launchpad/launchpad_reward.gno index 81102148c..ad1d881f2 100644 --- a/launchpad/launchpad_reward.gno +++ b/launchpad/launchpad_reward.gno @@ -119,7 +119,11 @@ func CollectRewardByProjectId(projectId string) uint64 { } // transfer reward to user - transferByRegisterCall(project.tokenPath, std.PrevRealm().Addr(), toUser) + tokenTeller := common.GetTokenTeller(project.tokenPath) + tokenTeller.Transfer( + std.PrevRealm().Addr(), + toUser, + ) return toUser } @@ -198,7 +202,11 @@ func CollectRewardByDepositId(depositId string) uint64 { } // transfer reward to user - transferByRegisterCall(project.tokenPath, std.PrevRealm().Addr(), toUser) + tokenTeller := common.GetTokenTeller(project.tokenPath) + tokenTeller.Transfer( + std.PrevRealm().Addr(), + toUser, + ) return toUser } diff --git a/launchpad/tests/__TEST_launchpad_reward_and_gov_reward_03_test.gnoA b/launchpad/tests/__TEST_launchpad_reward_and_gov_reward_03_test.gnoA index c813af8e5..b4f54622a 100644 --- a/launchpad/tests/__TEST_launchpad_reward_and_gov_reward_03_test.gnoA +++ b/launchpad/tests/__TEST_launchpad_reward_and_gov_reward_03_test.gnoA @@ -416,7 +416,7 @@ func checkProtocolFeeBalance() { println("[START] checkProtocolFeeBalance") pfRegistered := pf.GetRegisteredTokens() for _, token := range pfRegistered { - balance := balanceOfByRegisterCall(token, consts.PROTOCOL_FEE_ADDR) + balance := common.BalanceOf(token, consts.PROTOCOL_FEE_ADDR) if balance != 0 { println("token", token) println("balance", balance) diff --git a/launchpad/token_register.gno b/launchpad/token_register.gno deleted file mode 100644 index 6e75b2405..000000000 --- a/launchpad/token_register.gno +++ /dev/null @@ -1,180 +0,0 @@ -package launchpad - -import ( - "std" - "strings" - - "gno.land/p/demo/ufmt" - - pusers "gno.land/p/demo/users" - - "gno.land/r/gnoswap/v1/common" - "gno.land/r/gnoswap/v1/consts" -) - -// GRC20Interface is the interface for GRC20 tokens -// It is used to interact with the GRC20 tokens without importing but by registering each tokens function -type GRC20Interface interface { - Transfer() func(to pusers.AddressOrName, amount uint64) - TransferFrom() func(from, to pusers.AddressOrName, amount uint64) - BalanceOf() func(owner pusers.AddressOrName) uint64 - Approve() func(spender pusers.AddressOrName, amount uint64) -} - -var ( - registered = make(map[string]GRC20Interface) - locked = false // mutex -) - -// GetRegisteredTokens returns a list of all registered tokens -func GetRegisteredTokens() []string { - tokens := make([]string, 0, len(registered)) - for k := range registered { - tokens = append(tokens, k) - } - return tokens -} - -// RegisterGRC20Interface registers a GRC20 token interface -// -// Panics: -// - caller is not the admin -// - token already registered -func RegisterGRC20Interface(pkgPath string, igrc20 GRC20Interface) { - prevAddr := std.PrevRealm().Addr() - prevPath := std.PrevRealm().PkgPath() - if !(prevAddr == consts.TOKEN_REGISTER || prevPath == consts.INIT_REGISTER_PATH || strings.HasPrefix(prevPath, "gno.land/r/g1er355fkjksqpdtwmhf5penwa82p0rhqxkkyhk5")) { - panic(addDetailToError( - errNoPermission, - ufmt.Sprintf("token_register.gno__RegisterGRC20Interface() || only register(%s) can register token, called from %s", consts.TOKEN_REGISTER, prevAddr), - )) - } - - pkgPath = handleNative(pkgPath) - - _, found := registered[pkgPath] - if found { - panic(addDetailToError( - errAlreadyRegistered, - ufmt.Sprintf("token_register.gno__RegisterGRC20Interface() || token(%s) already registered", pkgPath), - )) - } - - registered[pkgPath] = igrc20 -} - -// UnregisterGRC20Interface unregisters a GRC20 token interface -// -// Panics: -// - caller is not the admin -func UnregisterGRC20Interface(pkgPath string) { - if err := common.SatisfyCond(isUserCall()); err != nil { - panic(err) - } - - caller := std.PrevRealm().Addr() - if err := common.TokenRegisterOnly(caller); err != nil { - panic(err) - } - - pkgPath = handleNative(pkgPath) - - _, found := registered[pkgPath] - if found { - delete(registered, pkgPath) - } -} - -func transferByRegisterCall(pkgPath string, to std.Address, amount uint64) bool { - pkgPath = handleNative(pkgPath) - - _, found := registered[pkgPath] - if !found { - panic(addDetailToError( - errNotRegistered, - ufmt.Sprintf("token_register.gno__transferByRegisterCall() || token(%s) not registered", pkgPath), - )) - } - - if !locked { - locked = true - registered[pkgPath].Transfer()(pusers.AddressOrName(to), amount) - - defer func() { - locked = false - }() - } else { - panic(addDetailToError( - errLocked, - ufmt.Sprintf("token_register.gno__transferByRegisterCall() || expected locked(%t) to be false", locked), - )) - } - - return true -} - -func transferFromByRegisterCall(pkgPath string, from, to std.Address, amount uint64) bool { - pkgPath = handleNative(pkgPath) - - _, found := registered[pkgPath] - if !found { - panic(addDetailToError( - errNotRegistered, - ufmt.Sprintf("token_register.gno__transferFromByRegisterCall() || token(%s) not registered", pkgPath), - )) - } - - if !locked { - locked = true - registered[pkgPath].TransferFrom()(pusers.AddressOrName(from), pusers.AddressOrName(to), amount) - - defer func() { - locked = false - }() - } else { - panic(addDetailToError( - errLocked, - ufmt.Sprintf("token_register.gno__transferFromByRegisterCall() || expected locked(%t) to be false", locked), - )) - } - return true -} - -func balanceOfByRegisterCall(pkgPath string, owner std.Address) uint64 { - pkgPath = handleNative(pkgPath) - - _, found := registered[pkgPath] - if !found { - panic(addDetailToError( - errNotRegistered, - ufmt.Sprintf("token_register.gno__balanceOfByRegisterCall() || token(%s) not registered", pkgPath), - )) - } - - balance := registered[pkgPath].BalanceOf()(pusers.AddressOrName(owner)) - return balance -} - -func approveByRegisterCall(pkgPath string, spender std.Address, amount uint64) bool { - pkgPath = handleNative(pkgPath) - - _, found := registered[pkgPath] - if !found { - panic(addDetailToError( - errNotRegistered, - ufmt.Sprintf("token_register.gno__approveByRegisterCall() || token(%s) not registered", pkgPath), - )) - } - - registered[pkgPath].Approve()(pusers.AddressOrName(spender), amount) - - return true -} - -func handleNative(pkgPath string) string { - if pkgPath == consts.GNOT { - return consts.WRAPPED_WUGNOT - } - - return pkgPath -} From 379ec90c35963947d88012f4bb54f402e93a155a Mon Sep 17 00:00:00 2001 From: n3wbie Date: Thu, 26 Dec 2024 21:11:37 +0900 Subject: [PATCH 5/7] feat: use grc20reg in `gov/staker` --- gov/staker/staker.gno | 8 +- gov/staker/token_register.gno | 176 ---------------------------------- 2 files changed, 4 insertions(+), 180 deletions(-) delete mode 100644 gov/staker/token_register.gno diff --git a/gov/staker/staker.gno b/gov/staker/staker.gno index 4c2e8c1d5..eb4044a2a 100644 --- a/gov/staker/staker.gno +++ b/gov/staker/staker.gno @@ -285,7 +285,8 @@ func CollectReward() { banker.SendCoins(consts.GOV_STAKER_ADDR, caller, std.Coins{{"ugnot", int64(amount)}}) } } else { - transferByRegisterCall(tokenPath, caller, amount) + tokenTeller := common.GetTokenTeller(tokenPath) + tokenTeller.Transfer(caller, amount) } userProtocolFeeReward[caller][tokenPath] = 0 @@ -353,9 +354,8 @@ func CollectRewardFromLaunchPad(to std.Address) { continue } - // transfer token to to - // token.Transfer(a2u(to), amount) - transferByRegisterCall(tokenPath, to, amount) + tokenTeller := common.GetTokenTeller(tokenPath) + tokenTeller.Transfer(to, amount) userProtocolFeeReward[to][tokenPath] = 0 prevAddr, prevRealm := getPrev() diff --git a/gov/staker/token_register.gno b/gov/staker/token_register.gno deleted file mode 100644 index 8f57bef17..000000000 --- a/gov/staker/token_register.gno +++ /dev/null @@ -1,176 +0,0 @@ -package staker - -import ( - "std" - "strings" - - "gno.land/p/demo/ufmt" - pusers "gno.land/p/demo/users" - - "gno.land/r/gnoswap/v1/consts" - "gno.land/r/gnoswap/v1/common" -) - -type GRC20Interface interface { - Transfer() func(to pusers.AddressOrName, amount uint64) - TransferFrom() func(from, to pusers.AddressOrName, amount uint64) - BalanceOf() func(owner pusers.AddressOrName) uint64 - Approve() func(spender pusers.AddressOrName, amount uint64) -} - -var ( - // registered is a map of registered GRC20 interfaces keyed by package path - registered = make(map[string]GRC20Interface) - locked = false // mutex -) - -// GetRegisteredTokens returns a slice of all registered tokjen package paths -func GetRegisteredTokens() []string { - tokens := make([]string, 0, len(registered)) - for k := range registered { - tokens = append(tokens, k) - } - return tokens -} - -// RegisterGRC20Interface registers a GRC20 token interface -// -// Panics: -// - caller is not the admin -// - token already registered -func RegisterGRC20Interface(pkgPath string, igrc20 GRC20Interface) { - prevAddr := std.PrevRealm().Addr() - prevPath := std.PrevRealm().PkgPath() - if !(prevAddr == consts.TOKEN_REGISTER || prevPath == consts.INIT_REGISTER_PATH || strings.HasPrefix(prevPath, consts.TOKEN_REGISTER_NAMESPACE)) { - panic(addDetailToError( - errNoPermission, - ufmt.Sprintf("token_register.gno__RegisterGRC20Interface() || only register(%s) can register token, called from %s", consts.TOKEN_REGISTER, prevAddr), - )) - } - - pkgPath = handleNative(pkgPath) - - _, found := registered[pkgPath] - if found { - panic(addDetailToError( - errAlreadyRegistered, - ufmt.Sprintf("token_register.gno__RegisterGRC20Interface() || token(%s) already registered", pkgPath), - )) - } - - registered[pkgPath] = igrc20 -} - -// UnregisterGRC20Interface unregisters a GRC20 token interface -// -// Panics: -// - caller is not the admin -func UnregisterGRC20Interface(pkgPath string) { - if err := common.SatisfyCond(isUserCall()); err != nil { - panic(err) - } - - caller := std.PrevRealm().Addr() - if err := common.TokenRegisterOnly(caller); err != nil { - panic(err) - } - - pkgPath = handleNative(pkgPath) - - _, found := registered[pkgPath] - if found { - delete(registered, pkgPath) - } -} - -func transferByRegisterCall(pkgPath string, to std.Address, amount uint64) bool { - pkgPath = handleNative(pkgPath) - - _, found := registered[pkgPath] - if !found { - panic(addDetailToError( - errNotRegistered, - ufmt.Sprintf("token_register.gno__transferByRegisterCall() || token(%s) not registered", pkgPath), - )) - } - - if !locked { - locked = true - registered[pkgPath].Transfer()(pusers.AddressOrName(to), amount) - - defer func() { - locked = false - }() - } else { - panic(addDetailToError( - errLocked, - ufmt.Sprintf("token_register.gno__transferByRegisterCall() || expected locked(%t) to be false", locked), - )) - } - return true -} - -func transferFromByRegisterCall(pkgPath string, from, to std.Address, amount uint64) bool { - pkgPath = handleNative(pkgPath) - - _, found := registered[pkgPath] - if !found { - panic(addDetailToError( - errNotRegistered, - ufmt.Sprintf("token_register.gno__transferFromByRegisterCall() || token(%s) not registered", pkgPath), - )) - } - - if !locked { - locked = true - registered[pkgPath].TransferFrom()(pusers.AddressOrName(from), pusers.AddressOrName(to), amount) - - defer func() { - locked = false - }() - } else { - panic(addDetailToError( - errLocked, - ufmt.Sprintf("token_register.gno__transferFromByRegisterCall() || expected locked(%t) to be false", locked), - )) - } - return true -} - -func balanceOfByRegisterCall(pkgPath string, owner std.Address) uint64 { - pkgPath = handleNative(pkgPath) - - _, found := registered[pkgPath] - if !found { - panic(addDetailToError( - errNotRegistered, - ufmt.Sprintf("token_register.gno__balanceOfByRegisterCall() || token(%s) not registered", pkgPath), - )) - } - - balance := registered[pkgPath].BalanceOf()(pusers.AddressOrName(owner)) - return balance -} - -func approveByRegisterCall(pkgPath string, spender std.Address, amount uint64) bool { - pkgPath = handleNative(pkgPath) - - _, found := registered[pkgPath] - if !found { - panic(addDetailToError( - errNotRegistered, - ufmt.Sprintf("token_register.gno__approveByRegisterCall() || token(%s) not registered", pkgPath), - )) - } - - registered[pkgPath].Approve()(pusers.AddressOrName(spender), amount) - return true -} - -func handleNative(pkgPath string) string { - if pkgPath == consts.GNOT { - return consts.WRAPPED_WUGNOT - } - - return pkgPath -} From 82df9368959308e6d0540cef009ded364cafb764 Mon Sep 17 00:00:00 2001 From: n3wbie Date: Thu, 26 Dec 2024 21:16:51 +0900 Subject: [PATCH 6/7] chore: apply tlin suggestion --- launchpad/launchpad_deposit.gno | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/launchpad/launchpad_deposit.gno b/launchpad/launchpad_deposit.gno index 8bb68eebd..5886e5374 100644 --- a/launchpad/launchpad_deposit.gno +++ b/launchpad/launchpad_deposit.gno @@ -482,22 +482,23 @@ func checkDepositConditions(project Project) { for _, condition := range project.conditions { if condition.minAmount == 0 { continue + } + + // check balance + var balance uint64 + if condition.tokenPath == consts.GOV_XGNS_PATH { + balance = xgns.BalanceOf(a2u(caller)) } else { - // check balance - var balance uint64 - if condition.tokenPath == consts.GOV_XGNS_PATH { - balance = xgns.BalanceOf(a2u(caller)) - } else { - balance = common.BalanceOf(condition.tokenPath, caller) - } - if balance < condition.minAmount { - panic(addDetailToError( - errNotEnoughBalance, - ufmt.Sprintf("launchpad_deposit.gno__checkDepositConditions() || insufficient balance(%d) for token(%s)", balance, condition.tokenPath), - )) - } + balance = common.BalanceOf(condition.tokenPath, caller) + } + if balance < condition.minAmount { + panic(addDetailToError( + errNotEnoughBalance, + ufmt.Sprintf("launchpad_deposit.gno__checkDepositConditions() || insufficient balance(%d) for token(%s)", balance, condition.tokenPath), + )) } } + } func checkProjectActive(project Project) bool { From b9647501027d359c9882311d7da67917ae9cad19 Mon Sep 17 00:00:00 2001 From: n3wbie Date: Thu, 26 Dec 2024 21:21:55 +0900 Subject: [PATCH 7/7] fix: --- _deploy/r/gnoswap/common/grc20reg_helper.gno | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/_deploy/r/gnoswap/common/grc20reg_helper.gno b/_deploy/r/gnoswap/common/grc20reg_helper.gno index a959e94d2..5f521a857 100644 --- a/_deploy/r/gnoswap/common/grc20reg_helper.gno +++ b/_deploy/r/gnoswap/common/grc20reg_helper.gno @@ -10,6 +10,10 @@ import ( "gno.land/r/demo/grc20reg" ) +var ( + re = regexp.MustCompile(`\[gno\.land/r/[^\]]+\]`) +) + // GetToken returns a grc20.Token instance // if token is not registered, it will panic // token instance supports following methods: @@ -70,10 +74,9 @@ func ListRegisteredTokens() []string { } func extractTokenPathsFromRender(render string) []string { - re := regexp.MustCompile(`\[gno\.land/r/[^\]]+\]`) matches := re.FindAllString(render, -1) - tokenPaths := []string{} + tokenPaths := make([]string, 0, len(matches)) for _, match := range matches { tokenPath := strings.Trim(match, "[]") // Remove the brackets tokenPaths = append(tokenPaths, tokenPath)