Skip to content

Commit

Permalink
feat/manage protocol fee list (#464)
Browse files Browse the repository at this point in the history
* feat: manage accumulated protocol_fee list
* feat: `pool` adds token(+amount) to protocol_fee
* feat: `router` adds token(+amount) to protocol_fee
* feat: `staker` adds token(+amount) to protocol_fee
* fix: amount and balance checking

---------

Co-authored-by: Dongwon <74406335+dongwon8247@users.noreply.github.com>
Co-authored-by: Lee ByeongJun <lbj199874@gmail.com>
  • Loading branch information
3 people authored and moul committed Jan 20, 2025
1 parent 12c46a8 commit 1429e2b
Show file tree
Hide file tree
Showing 12 changed files with 406 additions and 17 deletions.
8 changes: 8 additions & 0 deletions _deploy/r/gnoswap/common/access.gno
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ func RouterOnly(caller std.Address) error {
return nil
}

// PoolOnly checks if the caller is the pool contract.
func PoolOnly(caller std.Address) error {
if caller != consts.POOL_ADDR {
return ufmt.Errorf(ErrNoPermission, caller.String())
}
return nil
}

// PositionOnly checks if the caller is the position contract.
func PositionOnly(caller std.Address) error {
if caller != consts.POSITION_ADDR {
Expand Down
4 changes: 4 additions & 0 deletions pool/pool_manager.gno
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ import (

"gno.land/r/gnoswap/v1/common"
"gno.land/r/gnoswap/v1/consts"

en "gno.land/r/gnoswap/v1/emission"
pf "gno.land/r/gnoswap/v1/protocol_fee"

"gno.land/r/gnoswap/v1/gns"
)

Expand Down Expand Up @@ -189,6 +192,7 @@ func CreatePool(

if poolCreationFee > 0 {
gns.TransferFrom(a2u(std.PrevRealm().Addr()), a2u(consts.PROTOCOL_FEE_ADDR), poolCreationFee)
pf.AddToProtocolFee(consts.GNS_PATH, poolCreationFee)

std.Emit(
"PoolCreationFee",
Expand Down
4 changes: 4 additions & 0 deletions pool/protocol_fee_withdrawal.gno
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import (

"gno.land/p/demo/ufmt"
u256 "gno.land/p/gnoswap/uint256"

"gno.land/r/gnoswap/v1/common"
"gno.land/r/gnoswap/v1/consts"
pf "gno.land/r/gnoswap/v1/protocol_fee"
)

// withdrawalFeeBPS is the fee that is charged when a user withdraws their collected fees
Expand Down Expand Up @@ -68,9 +70,11 @@ func HandleWithdrawalFee(

token0Teller := common.GetTokenTeller(token0Path)
checkTransferError(token0Teller.TransferFrom(positionCaller, consts.PROTOCOL_FEE_ADDR, feeAmount0.Uint64()))
pf.AddToProtocolFee(token0Path, feeAmount0.Uint64())

token1Teller := common.GetTokenTeller(token1Path)
checkTransferError(token1Teller.TransferFrom(positionCaller, consts.PROTOCOL_FEE_ADDR, feeAmount1.Uint64()))
pf.AddToProtocolFee(token1Path, feeAmount1.Uint64())

prevAddr, prevPkgPath := getPrevAsString()
std.Emit(
Expand Down
2 changes: 1 addition & 1 deletion pool/protocol_fee_withdrawal_test.gno
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func TestHandleWithdrawalFee(t *testing.T) {
HandleWithdrawalFee(0, "pkgPath", "1000", "pkgPath", "1000", "poolPath", users.Resolve(admin))
},
verify: nil,
expected: "[GNOSWAP-COMMON-004] token is not registered || token(pkgPath) is not registered",
expected: "[GNOSWAP-COMMON-004] token is not registered || token(pkgPath)",
shouldPanic: true,
},
{
Expand Down
9 changes: 3 additions & 6 deletions protocol_fee/errors.gno
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,9 @@ import (
)

var (
errNoPermission = errors.New("[GNOSWAP-PROTOCOL_FEE-001] caller has no permission")
errNotRegistered = errors.New("[GNOSWAP-PROTOCOL_FEE-002] not registered token")
errAlreadyRegistered = errors.New("[GNOSWAP-PROTOCOL_FEE-003] already registered token")
errLocked = errors.New("[GNOSWAP-PROTOCOL_FEE-004] can't transfer token while locked")
errInvalidInput = errors.New("[GNOSWAP-PROTOCOL_FEE-005] invalid input data")
errInvalidPct = errors.New("[GNOSWAP-PROTOCOL_FEE-006] invalid percentage")
errNoPermission = errors.New("[GNOSWAP-PROTOCOL_FEE-001] caller has no permission")
errInvalidPct = errors.New("[GNOSWAP-PROTOCOL_FEE-002] invalid percentage")
errInvalidAmount = errors.New("[GNOSWAP-PROTOCOL_FEE-003] invalid amount")
)

func addDetailToError(err error, detail string) string {
Expand Down
5 changes: 0 additions & 5 deletions protocol_fee/protocol_fee.gno
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,6 @@ func ClearAccuTransferToGovStaker() {
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)
Expand Down
2 changes: 1 addition & 1 deletion protocol_fee/protocol_fee_test.gno
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func TestSetDevOpsPctByAdminInvalidFee(t *testing.T) {

uassert.PanicsWithMessage(
t,
`[GNOSWAP-PROTOCOL_FEE-006] invalid percentage || pct(100001) should not be bigger than 10000`,
`[GNOSWAP-PROTOCOL_FEE-002] invalid percentage || pct(100001) should not be bigger than 10000`,
func() {
SetDevOpsPctByAdmin(100001)
},
Expand Down
137 changes: 137 additions & 0 deletions protocol_fee/token_list_with_amount.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package protocol_fee

import (
"std"
"strings"

"gno.land/p/demo/ufmt"

"gno.land/r/gnoswap/v1/common"
"gno.land/r/gnoswap/v1/consts"
)

var (
tokenListWithAmount = make(map[string]uint64) // tokenPath -> amount
)

// TokenList returns only the list of token path.
// If positive is true, it returns only the token path with amount > 0.
// If positive is false, it returns all the token path.
func TokenList(positive bool) []string {
tokens := []string{}

for tokenPath, amount := range tokenListWithAmount {
if positive && amount == 0 {
continue
}

tokens = append(tokens, tokenPath)
}

return tokens
}

// TokenListWithAmount returns the token path and amount.
func TokenListWithAmount() map[string]uint64 {
return tokenListWithAmount
}

// AddToProtocolFee adds the amount to the tokenListWithAmount
// Only `pool + router + staker` can execute this function.
func AddToProtocolFee(tokenPath string, amount uint64) {
assertOnlyPoolRouterStaker()
tokenListWithAmount[tokenPath] += amount
}

// ClearTokenListWithAmount clears the tokenListWithAmount.
// only `gov/staker` can execute this function.
func ClearTokenListWithAmount() {
assertOnlyGovStaker()
clearTokenListWithAmount()
}

// TransferProtocolFee transfers the protocol fee to devOps and gov/staker.
// only `gov/staker` can execute this function.
// It returns list of token with amount has been sent to gov/staker.
func TransferProtocolFee() map[string]uint64 {
assertOnlyGovStaker()

sentToDevOps := []string{}
sentToGovStaker := []string{}
toReturn := map[string]uint64{}

for token, amount := range tokenListWithAmount {
balance := common.BalanceOf(token, consts.PROTOCOL_FEE_ADDR)

// anyone can just send certain grc20 token to `protocol_fee` contract
// therefore, we don't need any guard logic to check whether protocol_fee's xxx token balance is equal to `amount`
// however, amount always should be less than or equal to balance
if amount > balance {
panic(addDetailToError(
errInvalidAmount,
ufmt.Sprintf("amount: %d should be less than or equal to balance: %d", amount, balance),
))
}

if amount > 0 {
toDevOps := balance * devOpsPct / 10000 // default 0%
toGovStaker := balance - toDevOps // default 100%

tokenTeller := common.GetTokenTeller(token)
if toDevOps > 0 {
tokenTeller.Transfer(consts.DEV_OPS, toDevOps)
sentToDevOps = append(sentToDevOps, makeEventString(token, toDevOps))
}

if toGovStaker > 0 {
tokenTeller.Transfer(consts.GOV_STAKER_ADDR, toGovStaker)
sentToGovStaker = append(sentToGovStaker, makeEventString(token, toGovStaker))

toReturn[token] = toGovStaker
}
}
}

clearTokenListWithAmount()

prevAddr, prevRealm := getPrev()
std.Emit(
"TransferProtocolFee",
"prevAddr", prevAddr,
"prevRealm", prevRealm,
"toDevOps", strings.Join(sentToDevOps, ","),
"toGovStaker", strings.Join(sentToGovStaker, ","),
)

return toReturn
}

// clearTokenListWithAmount clears the tokenListWithAmount.
func clearTokenListWithAmount() {
tokenListWithAmount = map[string]uint64{}
}

// assertOnlyPoolRouterStaker panics if the caller is not the pool, router, or staker contract.
func assertOnlyPoolRouterStaker() {
caller := std.PrevRealm().Addr()

poolOnlyErr := common.PoolOnly(caller)
routerOnlyErr := common.RouterOnly(caller)
stakerOnlyErr := common.StakerOnly(caller)

if poolOnlyErr != nil && routerOnlyErr != nil && stakerOnlyErr != nil {
panic(errNoPermission)
}
}

// assertOnlyGovStaker panics if the caller is not the gov/staker contract.
func assertOnlyGovStaker() {
caller := std.PrevRealm().Addr()
if err := common.GovStakerOnly(caller); err != nil {
panic(err.Error())
}
}

func makeEventString(tokenPath string, amount uint64) string {
return tokenPath + "*FEE*" + ufmt.Sprintf("%d", amount)
}
Loading

0 comments on commit 1429e2b

Please sign in to comment.