Transaction
04C0210124D639…4E0D70AC8D16
Block 77,264 · index 1 · indexed
Summary
- Hash
- 04C0210124D6399502621C4267DD737325F417877C9C823FF04C4E0D70AC8D16
- Block
- 77,264
- Size
- 37764 bytes
- Gas used
- 49,941,077 / 59,929,257
- Fee
- 59930 ugnot
- Status
- success
Messages
Arguments · 24
- #1protocol_fee
- #2README.md
- #3# Protocol Fee Fee collection and distribution for protocol operations. ## Overview The protocol-fee contract collects authorized fees from protocol operations and distributes them to GovStaker and DevOps according to configured percentages. ## Configuration - **Router Fee (initial/default)**: 0.15% of the swap amount; configured by the router, with an admin-or-governance range of 0–10% - **Pool Creation Fee (initial/default)**: 100 GNS; configured by the pool and modifiable through governance - **Withdrawal Fee (initial/default)**: 1% of LP fees claimed; configured by the pool and modifiable through governance - **Unstaking Fee (initial/default)**: 1% of staking rewards; configured by the staker and modifiable by admin or governance - **Distribution (default)**: 100% to GovStaker and 0% to DevOps The operation-specific fees above are configured in their owning modules; they are not protocol-fee distribution percentages. ## Fee Sources 1. **Swaps**: The router applies its configured swap fee (0.15% initially). 2. **Pool Creation**: The pool charges its configured creation fee (100 GNS initially). 3. **LP Withdrawals**: The pool charges its configured withdrawal fee (1% initially). 4. **Staking Claims**: The staker charges its configured unstaking fee (1% initially). ## Key Functions ### `DistributeProtocolFee` Distributes accumulated fees to recipients. ### `SetDevOpsPct` Sets the DevOps funding percentage. ### `SetGovStakerPct` Sets the GovStaker funding percentage. ### `AddToProtocolFee` Adds an approved fee amount to the distribution queue. ### `AdvanceAccrualEpoch` Closes the accrual epoch in force and returns the new one. Called by gov/staker on every stake change, so fees are attributed to the stake distribution live when they arrived. ### `ConsumeAccrualBuckets` Returns and clears up to `limit` of the oldest pending buckets of one token, as parallel epoch and amount slices. Called by gov/staker when that token is collected. ## Usage ```go // Distribute accumulated fees DistributeProtocolFee(cross(cur)) // Configure distribution SetDevOpsPct(cross(cur), 2000) // 20% to DevOps SetGovStakerPct(cross(cur), 8000) // 80% to GovStaker // View tokens reserved for the next distribution GetReservedTokens() // View what gov/staker has not folded yet GetAccrualEpoch() GetAccrualPendingTokens() GetAccrualBuckets(tokenPath, 0) ``` ## Security - Configuration changes are restricted to admin or governance; distribution is restricted to admin or gov/staker - Automatic fee accumulation - Multi-token support - Transparent distribution tracking
- #4assert.gno
- #5package protocol_fee import ( prbac "gno.land/p/gnoswap/rbac/v1" ufmt "gno.land/p/nt/ufmt/v0" "gno.land/r/gnoswap/access/v1" ) // assertIsPoolOrPositionOrRouterOrStaker panics if the caller is not the pool, router, or staker contract. func assertIsPoolOrPositionOrRouterOrStaker(caller address) { if access.IsAuthorized(prbac.ROLE_POOL.String(), caller) || access.IsAuthorized(prbac.ROLE_POSITION.String(), caller) || access.IsAuthorized(prbac.ROLE_ROUTER.String(), caller) || access.IsAuthorized(prbac.ROLE_STAKER.String(), caller) { return } panic(ufmt.Errorf("unauthorized: caller %s is not pool or position or router or staker", caller)) } // assertIsAdminOrGovStaker panics if the caller is not admin or gov/staker. func assertIsAdminOrGovStaker(caller address) { if access.IsAuthorized(prbac.ROLE_ADMIN.String(), caller) { return } if access.IsAuthorized(prbac.ROLE_GOV_STAKER.String(), caller) { return } panic(ufmt.Errorf("unauthorized: caller %s is not admin or gov/staker", caller)) } // assertIsValidPercent panics if the percentage is invalid (not between 0-10000). func assertIsValidPercent(pct int64) { if pct > 10000 { panic(makeErrorWithDetail( errInvalidPct, ufmt.Sprintf("pct(%d) should not be bigger than 10000", pct), )) } if pct < 0 { panic(makeErrorWithDetail( errInvalidPct, ufmt.Sprintf("pct(%d) should not be smaller than 0", pct), )) } } func assertNotImplementYet() { panic("NotImplementYet") }
- #6doc.gno
- #7// package v1 collects and distributes protocol fees from swaps. // // This contract accumulates fees generated from trading activity and // distributes them to devOps and governance stakers according to // configurable percentages set through governance. package protocol_fee
- #8errors.gno
- #9package protocol_fee import ( ufmt "gno.land/p/nt/ufmt/v0" ) const ( errInvalidPct = "[GNOSWAP-PROTOCOL_FEE-001] invalid percentage" errInvalidAmount = "[GNOSWAP-PROTOCOL_FEE-002] invalid amount" errProtocolFeeHalted = "[GNOSWAP-PROTOCOL_FEE-003] protocol fee halted" errSpoofedRealm = "[GNOSWAP-PROTOCOL_FEE-004] rlm does not match the current crossing frame" ) // makeErrorWithDetail creates an error with additional context. func makeErrorWithDetail(message string, detail string) error { return ufmt.Errorf("%s || %s", message, detail) }
- #10getter.gno
- #11package protocol_fee // GetDevOpsPct returns the percentage allocated to devOps. // // Returns: // - pct: DevOps' protocol-fee allocation percentage in basis points (0 through 10000). func (pf *protocolFeeV1) GetDevOpsPct() int64 { return pf.getProtocolFeeState().DevOpsPct() } // GetGovStakerPct returns the percentage allocated to gov/staker. // // Returns: // - pct: Gov/Staker's protocol-fee allocation percentage in basis points, computed as 10000 minus the stored DevOps percentage. func (pf *protocolFeeV1) GetGovStakerPct() int64 { return pf.getProtocolFeeState().GovStakerPct() } // GetReservedTokens returns token paths reserved for distribution. // // Returns: // - tokenPaths: Token paths currently reserved for protocol-fee distribution. func (pf *protocolFeeV1) GetReservedTokens() []string { return pf.getProtocolFeeState().ReservedTokens() } // GetAccrualEpoch returns the accrual epoch fees are currently bucketed under. // // Returns: // - epoch: Current accrual epoch under which newly collected Gov/Staker amounts are bucketed. func (pf *protocolFeeV1) GetAccrualEpoch() int64 { return pf.getProtocolFeeState().store.GetAccrualEpoch() } // GetAccrualPendingTokens returns the token paths that still own pending accrual buckets. // // Returns: // - tokenPaths: Token paths that still have one or more pending Gov/Staker accrual buckets. func (pf *protocolFeeV1) GetAccrualPendingTokens() []string { return pf.getProtocolFeeState().store.GetAccrualPendingTokens() } // GetAccrualBuckets returns up to limit of the oldest pending buckets of tokenPath // without clearing them. A limit of zero or less returns every pending bucket. // // Parameters: // - tokenPath: Token path whose pending Gov/Staker accrual buckets should be read. // - limit: Maximum number of buckets to return; zero or a negative value returns all pending buckets. // // Returns: // - epochs: Epoch numbers for the returned buckets in ascending epoch order. // - amounts: Gov/Staker amounts for the returned epochs; amounts[i] corresponds to epochs[i]. func (pf *protocolFeeV1) GetAccrualBuckets(tokenPath string, limit int) ([]int64, []int64) { return pf.getProtocolFeeState().store.GetAccrualBuckets(tokenPath, limit) } // GetAccuTransfersToGovStaker returns all accumulated transfers to gov/staker. // // Returns: // - transfers: Snapshot map from token path to cumulative amount allocated to Gov/Staker, including amounts still pending distribution. func (pf *protocolFeeV1) GetAccuTransfersToGovStaker() map[string]int64 { accuTransfers := make(map[string]int64) pf.getProtocolFeeState().AccuToGovStaker().Iterate("", "", func(key string, value any) bool { amount, ok := value.(int64) if !ok { return false } accuTransfers[key] = amount return false }) return accuTransfers } // GetAccuTransfersToDevOps returns all accumulated transfers to devOps. // // Returns: // - transfers: Snapshot map from token path to cumulative amount allocated to DevOps, including amounts still pending distribution. func (pf *protocolFeeV1) GetAccuTransfersToDevOps() map[string]int64 { accuTransfers := make(map[string]int64) pf.getProtocolFeeState().AccuToDevOps().Iterate("", "", func(key string, value any) bool { amount, ok := value.(int64) if !ok { return false } accuTransfers[key] = amount return false }) return accuTransfers } // GetAccuTransferToGovStakerByTokenPath returns the accumulated transfer to gov/staker by token path. // // Parameters: // - path: Token path whose cumulative Gov/Staker allocation should be read. // // Returns: // - amount: Cumulative Gov/Staker amount allocated for path, or zero when no allocation is recorded. func (pf *protocolFeeV1) GetAccuTransferToGovStakerByTokenPath(path string) int64 { return pf.getProtocolFeeState().GetAccuTransferToGovStakerByTokenPath(path) } // GetAccuTransferToDevOpsByTokenPath returns the accumulated transfer to devOps by token path. // // Parameters: // - path: Token path whose cumulative DevOps allocation should be read. // // Returns: // - amount: Cumulative DevOps amount allocated for path, or zero when no allocation is recorded. func (pf *protocolFeeV1) GetAccuTransferToDevOpsByTokenPath(path string) int64 { return pf.getProtocolFeeState().GetAccuTransferToDevOpsByTokenPath(path) } // GetActualDistributedToGovStaker returns all actual transfers to gov/staker. // // Returns: // - transfers: Snapshot map from token path to cumulative amount actually distributed to Gov/Staker. func (pf *protocolFeeV1) GetActualDistributedToGovStaker() map[string]int64 { actualDistributed := make(map[string]int64) pf.getProtocolFeeState().ActualDistributedToGovStaker().Iterate("", "", func(key string, value any) bool { amount, ok := value.(int64) if !ok { return false } actualDistributed[key] = amount return false }) return actualDistributed } // GetActualDistributedToDevOps returns all actual transfers to devOps. // // Returns: // - transfers: Snapshot map from token path to cumulative amount actually distributed to DevOps. func (pf *protocolFeeV1) GetActualDistributedToDevOps() map[string]int64 { actualDistributed := make(map[string]int64) pf.getProtocolFeeState().ActualDistributedToDevOps().Iterate("", "", func(key string, value any) bool { amount, ok := value.(int64) if !ok { return false } actualDistributed[key] = amount return false }) return actualDistributed } // GetActualDistributedToGovStakerByTokenPath returns the actual transfer to gov/staker by token path. // // Parameters: // - path: Token path whose cumulative actual Gov/Staker distribution should be read. // // Returns: // - amount: Cumulative amount actually distributed to Gov/Staker for path, or zero when no history is recorded. func (pf *protocolFeeV1) GetActualDistributedToGovStakerByTokenPath(path string) int64 { return pf.getProtocolFeeState().GetActualDistributedToGovStakerByTokenPath(path) } // GetActualDistributedToDevOpsByTokenPath returns the actual transfer to devOps by token path. // // Parameters: // - path: Token path whose cumulative actual DevOps distribution should be read. // // Returns: // - amount: Cumulative amount actually distributed to DevOps for path, or zero when no history is recorded. func (pf *protocolFeeV1) GetActualDistributedToDevOpsByTokenPath(path string) int64 { return pf.getProtocolFeeState().GetActualDistributedToDevOpsByTokenPath(path) }
- #12gnomod.toml
- #13module = "gno.land/r/gnoswap/protocol_fee/v1" gno = "0.9"
- #14init.gno
- #15package protocol_fee import ( "gno.land/r/gnoswap/protocol_fee" ) func init(cur realm) { registerProtocolFeeV1(cur) } func registerProtocolFeeV1(cur realm) { protocol_fee.RegisterInitializer(cross(cur), func(_ int, rlm realm, protocolFeeStore protocol_fee.IProtocolFeeStore) protocol_fee.IProtocolFee { // `rlm` here is the protocol_fee proxy realm threaded in by // protocol_fee.RegisterInitializer. Per-version store bootstrap // writes therefore pass the proxy's KV-store ACL. err := initStoreData(0, rlm, protocolFeeStore) if err != nil { panic(err) } return NewProtocolFeeV1(protocolFeeStore) }) } func initStoreData(_ int, rlm realm, protocolFeeStore protocol_fee.IProtocolFeeStore) error { if !protocolFeeStore.HasDevOpsPctStoreKey() { err := protocolFeeStore.InitializeDevOpsPct(0, rlm) if err != nil { return err } } if !protocolFeeStore.HasAccuToGovStakerStoreKey() { err := protocolFeeStore.InitializeAccuToGovStaker(0, rlm) if err != nil { return err } } if !protocolFeeStore.HasAccuToDevOpsStoreKey() { err := protocolFeeStore.InitializeAccuToDevOps(0, rlm) if err != nil { return err } } if !protocolFeeStore.HasDistributedToGovStakerHistoryStoreKey() { err := protocolFeeStore.InitializeDistributedToGovStakerHistory(0, rlm) if err != nil { return err } } if !protocolFeeStore.HasDistributedToDevOpsHistoryStoreKey() { err := protocolFeeStore.InitializeDistributedToDevOpsHistory(0, rlm) if err != nil { return err } } if !protocolFeeStore.HasReservedTokensStoreKey() { err := protocolFeeStore.InitializeReservedTokens(0, rlm) if err != nil { return err } } if !protocolFeeStore.HasAccrualEpochStoreKey() { err := protocolFeeStore.InitializeAccrualEpoch(0, rlm) if err != nil { return err } } if !protocolFeeStore.HasAccrualBucketsStoreKey() { err := protocolFeeStore.InitializeAccrualBuckets(0, rlm) if err != nil { return err } } if !protocolFeeStore.HasAccrualPendingTokensStoreKey() { err := protocolFeeStore.InitializeAccrualPendingTokens(0, rlm) if err != nil { return err } } return nil }
- #16instance.gno
- #17package protocol_fee import ( "gno.land/r/gnoswap/protocol_fee" ) type protocolFeeV1 struct { store protocol_fee.IProtocolFeeStore } func (pf *protocolFeeV1) getProtocolFeeState() *protocolFeeState { return NewProtocolFeeStateBy(pf.store) } // NewProtocolFeeV1 creates the protocol-fee v1 implementation backed by protocolFeeStore. // // Parameters: // - protocolFeeStore: Store implementation containing the protocol-fee state for this version. // // Returns: // - implementation: An IProtocolFee implementation that exposes v1 behavior through protocolFeeStore. func NewProtocolFeeV1(protocolFeeStore protocol_fee.IProtocolFeeStore) protocol_fee.IProtocolFee { return &protocolFeeV1{ store: protocolFeeStore, } }
- #18protocol_fee.gno
- #19package protocol_fee import ( "chain" "errors" "strconv" ufmt "gno.land/p/nt/ufmt/v0" gnsmath "gno.land/p/gnoswap/gnsmath/v1" prabc "gno.land/p/gnoswap/rbac/v1" "gno.land/r/gnoswap/access/v1" "gno.land/r/gnoswap/common" "gno.land/r/gnoswap/halt/v1" ) // DistributeProtocolFee distributes collected protocol fees. // // Splits fees between devOps and gov/staker based on configured percentages. // This function processes all accumulated fees since last distribution. // // Only callable by admin or gov/staker contract. // Note: Default split is 0% devOps, 100% gov/staker. // // Parameters: // - _: cross-call discriminator; callers pass 0 // - rlm: propagated protocol_fee realm context; it must match the current crossing frame func (pf *protocolFeeV1) DistributeProtocolFee(_ int, rlm realm) { access.AssertIsRlmCurrent(0, rlm) prev := rlm.Previous() assertIsAdminOrGovStaker(prev.Address()) if halt.IsHaltedWithdraw() { return } protocolFeeAddr := access.MustGetAddress(prabc.ROLE_PROTOCOL_FEE.String()) pfs := pf.getProtocolFeeState() reservedTokens := pfs.ReservedTokens() for _, token := range reservedTokens { pf.distributeProtocolFeeForToken( 0, rlm, pfs, protocolFeeAddr, token, ) } } // DistributeProtocolFeeByTokenPath distributes collected protocol fees for one token path. // // Parameters: // - _: cross-call discriminator; callers pass 0 // - rlm: propagated protocol_fee realm context; it must match the current crossing frame // - tokenPath: token contract path whose reserved fee is eligible for distribution func (pf *protocolFeeV1) DistributeProtocolFeeByTokenPath(_ int, rlm realm, tokenPath string) { access.AssertIsRlmCurrent(0, rlm) prev := rlm.Previous() assertIsAdminOrGovStaker(prev.Address()) if halt.IsHaltedWithdraw() { return } protocolFeeAddr := access.MustGetAddress(prabc.ROLE_PROTOCOL_FEE.String()) pfs := pf.getProtocolFeeState() if !pfs.store.HasReservedToken(tokenPath) { return } pf.distributeProtocolFeeForToken( 0, rlm, pfs, protocolFeeAddr, tokenPath, ) } // distributeProtocolFeeForToken validates and distributes one reserved token's pending fees. func (pf *protocolFeeV1) distributeProtocolFeeForToken(_ int, rlm realm, pfs *protocolFeeState, protocolFeeAddr address, tokenPath string) { toDevOpsAmount := gnsmath.SafeSubInt64( pfs.GetAccuTransferToDevOpsByTokenPath(tokenPath), pfs.GetActualDistributedToDevOpsByTokenPath(tokenPath), ) toGovStakerAmount := gnsmath.SafeSubInt64( pfs.GetAccuTransferToGovStakerByTokenPath(tokenPath), pfs.GetActualDistributedToGovStakerByTokenPath(tokenPath), ) amount := gnsmath.SafeAddInt64(toDevOpsAmount, toGovStakerAmount) balance := common.BalanceOf(tokenPath, protocolFeeAddr) // amount should be less than or equal to balance if amount > balance { panic(makeErrorWithDetail( errInvalidAmount, ufmt.Sprintf("amount: %d should be less than or equal to balance: %d", amount, balance), )) } if err := pfs.removeReservedToken(0, rlm, tokenPath); err != nil { panic(err) } if amount <= 0 { return } // distributeToDevOps and distributeToGovStaker record history before transferring. if err := pfs.distributeToDevOps(0, rlm, tokenPath, toDevOpsAmount); err != nil { panic(err) } if err := pfs.distributeToGovStaker(0, rlm, tokenPath, toGovStakerAmount); err != nil { panic(err) } prev := rlm.Previous() chain.Emit( "TransferProtocolFee", "prevAddr", prev.Address().String(), "prevRealm", prev.PkgPath(), "tokenPath", tokenPath, "toDevOpsAmount", strconv.FormatInt(toDevOpsAmount, 10), "toGovStakerAmount", strconv.FormatInt(toGovStakerAmount, 10), "amount", strconv.FormatInt(amount, 10), ) } // SetDevOpsPct sets the devOpsPct. // // Parameters: // - _: cross-call discriminator; callers pass 0 // - rlm: propagated protocol_fee realm context; it must match the current crossing frame // - pct: percentage for devOps (0-10000, where 10000 = 100%) // // Only callable by admin or governance. // Note: GovStaker percentage is automatically adjusted to (10000 - devOpsPct). func (pf *protocolFeeV1) SetDevOpsPct(_ int, rlm realm, pct int64) { access.AssertIsRlmCurrent(0, rlm) halt.AssertIsNotHaltedProtocolFee() prev := rlm.Previous() access.AssertIsAdminOrGovernance(prev.Address()) assertIsValidPercent(pct) prevDevOpsPct := pf.getProtocolFeeState().DevOpsPct() prevGovStakerPct := pf.getProtocolFeeState().GovStakerPct() newDevOpsPct, err := pf.getProtocolFeeState().setDevOpsPct(0, rlm, pct) if err != nil { panic(err) } newGovStakerPct := pf.getProtocolFeeState().GovStakerPct() chain.Emit( "SetDevOpsPct", "prevAddr", prev.Address().String(), "prevRealm", prev.PkgPath(), "newDevOpsPct", strconv.FormatInt(newDevOpsPct, 10), "prevDevOpsPct", strconv.FormatInt(prevDevOpsPct, 10), "newGovStakerPct", strconv.FormatInt(newGovStakerPct, 10), "prevGovStakerPct", strconv.FormatInt(prevGovStakerPct, 10), ) } // SetGovStakerPct sets the stakerPct. // // Parameters: // - _: cross-call discriminator; callers pass 0 // - rlm: propagated protocol_fee realm context; it must match the current crossing frame // - pct: percentage for gov/staker (0-10000, where 10000 = 100%) // // Only callable by admin or governance. // Note: DevOps percentage is automatically adjusted to (10000 - govStakerPct). func (pf *protocolFeeV1) SetGovStakerPct(_ int, rlm realm, pct int64) { access.AssertIsRlmCurrent(0, rlm) halt.AssertIsNotHaltedProtocolFee() prev := rlm.Previous() access.AssertIsAdminOrGovernance(prev.Address()) assertIsValidPercent(pct) prevDevOpsPct := pf.getProtocolFeeState().DevOpsPct() prevGovStakerPct := pf.getProtocolFeeState().GovStakerPct() newGovStakerPct, err := pf.getProtocolFeeState().setGovStakerPct(0, rlm, pct) if err != nil { panic(err) } newDevOpsPct := pf.getProtocolFeeState().DevOpsPct() chain.Emit( "SetGovStakerPct", "prevAddr", prev.Address().String(), "prevRealm", prev.PkgPath(), "newDevOpsPct", strconv.FormatInt(newDevOpsPct, 10), "prevDevOpsPct", strconv.FormatInt(prevDevOpsPct, 10), "newGovStakerPct", strconv.FormatInt(newGovStakerPct, 10), "prevGovStakerPct", strconv.FormatInt(prevGovStakerPct, 10), ) } // AddToProtocolFee pulls the approved amount into protocol fee accounting. // // Parameters: // - _: cross-call discriminator; callers pass 0 // - rlm: propagated protocol_fee realm context; a non-current value returns errSpoofedRealm // - tokenPath: token contract path from which the approved fee is pulled // - amount: non-negative fee amount in token base units; zero returns nil without transferring // // Returns: // - err: nil on success; errSpoofedRealm for a spoofed-realm call; errProtocolFeeHalted when protocol-fee collection is halted // // Only callable by pool, position, router or staker contracts. // Caller must approve the protocol fee realm for at least amount before calling. // Note: Accumulated fees are distributed when DistributeProtocolFee is called. func (pf *protocolFeeV1) AddToProtocolFee(_ int, rlm realm, tokenPath string, amount int64) error { if !rlm.IsCurrent() { return errors.New(errSpoofedRealm) } if halt.IsHaltedProtocolFee() { return errors.New(errProtocolFeeHalted) } prev := rlm.Previous() caller := prev.Address() assertIsPoolOrPositionOrRouterOrStaker(caller) if amount < 0 { panic(makeErrorWithDetail( errInvalidAmount, ufmt.Sprintf("amount(%d) should not be negative", amount), )) } if amount == 0 { return nil } pf.reserveCollectedProtocolFee(0, rlm, tokenPath, amount) protocolFeeAddr := access.MustGetAddress(prabc.ROLE_PROTOCOL_FEE.String()) common.SafeGRC20TransferFrom(0, rlm, tokenPath, caller, protocolFeeAddr, amount) chain.Emit( "AddToProtocolFee", "prevAddr", caller.String(), "prevRealm", prev.PkgPath(), "tokenPath", tokenPath, "amount", strconv.FormatInt(amount, 10), ) return nil } func (pf *protocolFeeV1) reserveCollectedProtocolFee(_ int, rlm realm, tokenPath string, amount int64) { pfs := pf.getProtocolFeeState() toDevOpsAmount := gnsmath.SafeMulDivInt64(amount, pfs.DevOpsPct(), 10000) toGovStakerAmount := gnsmath.SafeSubInt64(amount, toDevOpsAmount) if err := pfs.addAccuToDevOps(0, rlm, tokenPath, toDevOpsAmount); err != nil { panic(err) } if err := pfs.addAccuToGovStaker(0, rlm, tokenPath, toGovStakerAmount); err != nil { panic(err) } if err := pfs.store.AddReservedToken(0, rlm, tokenPath); err != nil { panic(err) } // Bucket the gov/staker share under the accrual epoch in force so that gov/staker // can later fold it against the stake distribution that was live when it arrived. // A split that leaves gov/staker nothing moves no accumulator, so it is not bucketed. if toGovStakerAmount > 0 { epoch := pfs.store.GetAccrualEpoch() if err := pfs.store.AddAccrualBucket(0, rlm, tokenPath, epoch, toGovStakerAmount); err != nil { panic(err) } if err := pfs.store.AddAccrualPendingToken(0, rlm, tokenPath); err != nil { panic(err) } } } // AdvanceAccrualEpoch closes the accrual epoch in force and returns the new one. // // gov/staker calls it on every stake change, before it records the new stake // distribution, so every fee bucketed from now on is attributed to that distribution. // Restricted to gov/staker: an epoch advanced by anyone else would fragment the buckets // without a matching stake record on the gov/staker side. // // Parameters: // - _: cross-call discriminator; callers pass 0 // - rlm: propagated protocol_fee realm context; it must be current and be called by GovStaker // // Returns: // - epoch: newly stored non-negative accrual epoch for fees collected after the stake change func (pf *protocolFeeV1) AdvanceAccrualEpoch(_ int, rlm realm) int64 { if !rlm.IsCurrent() { panic(errors.New(errSpoofedRealm)) } prev := rlm.Previous() access.AssertIsGovStaker(prev.Address()) pfs := pf.getProtocolFeeState() epoch := gnsmath.SafeAddInt64(pfs.store.GetAccrualEpoch(), 1) if err := pfs.store.SetAccrualEpoch(0, rlm, epoch); err != nil { panic(err) } return epoch } // ConsumeAccrualBuckets returns and clears up to limit of the oldest pending buckets of // tokenPath, as parallel epoch and amount slices in epoch order. A limit of zero or // less consumes every pending bucket. Once no bucket remains the token leaves the // pending set. // // Restricted to gov/staker: clearing a bucket without folding it into an accumulator // would strand that fee share. // // Parameters: // - _: cross-call discriminator; callers pass 0 // - rlm: propagated protocol_fee realm context; it must be current and be called by GovStaker // - tokenPath: token contract path whose pending accrual buckets are consumed // - limit: maximum number of oldest buckets to consume; zero or negative consumes all pending buckets // // Returns: // - epochs: consumed accrual epoch numbers in ascending epoch order // - amounts: fee amounts corresponding positionally to epochs, in token base units func (pf *protocolFeeV1) ConsumeAccrualBuckets(_ int, rlm realm, tokenPath string, limit int) ([]int64, []int64) { if !rlm.IsCurrent() { panic(errors.New(errSpoofedRealm)) } prev := rlm.Previous() access.AssertIsGovStaker(prev.Address()) pfs := pf.getProtocolFeeState() epochs, amounts := pfs.store.GetAccrualBuckets(tokenPath, limit) if len(epochs) == 0 { return epochs, amounts } if err := pfs.store.RemoveAccrualBuckets(0, rlm, tokenPath, epochs); err != nil { panic(err) } remainingEpochs, _ := pfs.store.GetAccrualBuckets(tokenPath, 1) if len(remainingEpochs) == 0 { if err := pfs.store.RemoveAccrualPendingToken(0, rlm, tokenPath); err != nil { panic(err) } } return epochs, amounts }
- #20protocol_fee_state.gno
- #21package protocol_fee import ( bptree "gno.land/p/nt/bptree/v0" ufmt "gno.land/p/nt/ufmt/v0" gnsmath "gno.land/p/gnoswap/gnsmath/v1" prabc "gno.land/p/gnoswap/rbac/v1" "gno.land/r/gnoswap/access/v1" "gno.land/r/gnoswap/common" "gno.land/r/gnoswap/protocol_fee" ) // protocolFeeState holds all the state variables for protocol fee management type protocolFeeState struct { store protocol_fee.IProtocolFeeStore } // DevOpsPct returns the percentage of protocol fees allocated to DevOps. // // Returns: // - pct: Stored DevOps protocol-fee allocation percentage in basis points. func (pfs *protocolFeeState) DevOpsPct() int64 { return pfs.store.GetDevOpsPct() } // GovStakerPct returns the percentage of protocol fees allocated to Gov/Staker. // // Returns: // - pct: Gov/Staker protocol-fee allocation percentage in basis points, computed as 10000 minus the stored DevOps percentage. func (pfs *protocolFeeState) GovStakerPct() int64 { return 10000 - pfs.store.GetDevOpsPct() } // AccuToGovStaker returns the cumulative amount allocated to GovStaker. // It includes allocations that may still be pending distribution; completed // transfers are tracked separately in the distribution history. // // Returns: // - tree: BP-tree mapping token paths to cumulative amounts allocated to Gov/Staker, including allocations pending distribution. func (pfs *protocolFeeState) AccuToGovStaker() *bptree.BPTree { return pfs.store.GetAccuToGovStaker() } // AccuToDevOps returns the cumulative amount allocated to DevOps. // It includes allocations that may still be pending distribution; completed // transfers are tracked separately in the distribution history. // // Returns: // - tree: BP-tree mapping token paths to cumulative amounts allocated to DevOps, including allocations pending distribution. func (pfs *protocolFeeState) AccuToDevOps() *bptree.BPTree { return pfs.store.GetAccuToDevOps() } // ActualDistributedToGovStaker returns the cumulative amounts actually distributed to Gov/Staker. // // Returns: // - tree: BP-tree mapping token paths to cumulative amounts actually distributed to Gov/Staker. func (pfs *protocolFeeState) ActualDistributedToGovStaker() *bptree.BPTree { return pfs.store.GetDistributedToGovStakerHistory() } // ActualDistributedToDevOps returns the cumulative amounts actually distributed to DevOps. // // Returns: // - tree: BP-tree mapping token paths to cumulative amounts actually distributed to DevOps. func (pfs *protocolFeeState) ActualDistributedToDevOps() *bptree.BPTree { return pfs.store.GetDistributedToDevOpsHistory() } // ReservedTokens returns token paths reserved for distribution. // // Returns: // - tokenPaths: Token paths currently reserved for protocol-fee distribution. func (pfs *protocolFeeState) ReservedTokens() []string { return pfs.store.GetReservedTokens() } // distributeToDevOps distributes tokens to DevOps and updates related state. // Amount should be greater than 0 (already checked in DistributeProtocolFee). func (pfs *protocolFeeState) distributeToDevOps(_ int, rlm realm, token string, amount int64) error { devOpsAddr := access.MustGetAddress(prabc.ROLE_DEVOPS.String()) if err := pfs.addActualDistributedToDevOps(0, rlm, token, amount); err != nil { return err } common.SafeGRC20Transfer(0, rlm, token, devOpsAddr, amount) return nil } // distributeToGovStaker distributes tokens to Gov/Staker and updates related state. // Amount should be greater than 0 (already checked in DistributeProtocolFee). func (pfs *protocolFeeState) distributeToGovStaker(_ int, rlm realm, token string, amount int64) error { govStakerAddr := access.MustGetAddress(prabc.ROLE_GOV_STAKER.String()) if err := pfs.addActualDistributedToGovStaker(0, rlm, token, amount); err != nil { return err } common.SafeGRC20Transfer(0, rlm, token, govStakerAddr, amount) return nil } // setDevOpsPct sets the devOpsPct. func (pfs *protocolFeeState) setDevOpsPct(_ int, rlm realm, pct int64) (int64, error) { if pct < 0 { return 0, makeErrorWithDetail( errInvalidPct, ufmt.Sprintf("pct(%d) should not be negative", pct), ) } if pct > 10000 { return 0, makeErrorWithDetail( errInvalidPct, ufmt.Sprintf("pct(%d) should not be bigger than 10000", pct), ) } if err := pfs.store.SetDevOpsPct(0, rlm, pct); err != nil { return 0, err } return pct, nil } // setGovStakerPct sets the govStakerPct by calculating devOpsPct. func (pfs *protocolFeeState) setGovStakerPct(_ int, rlm realm, pct int64) (int64, error) { if pct < 0 { return 0, makeErrorWithDetail( errInvalidPct, ufmt.Sprintf("pct(%d) should not be negative", pct), ) } devOpsPct := 10000 - pct if _, err := pfs.setDevOpsPct(0, rlm, devOpsPct); err != nil { return 0, err } return pct, nil } // addAccuToGovStaker adds the amount to the accuToGovStaker by token path. func (pfs *protocolFeeState) addAccuToGovStaker(_ int, rlm realm, tokenPath string, amount int64) error { before := pfs.GetAccuTransferToGovStakerByTokenPath(tokenPath) // Check for overflow after := gnsmath.SafeAddInt64(before, amount) if err := pfs.store.SetAccuToGovStakerItem(0, rlm, tokenPath, after); err != nil { return err } return nil } // addAccuToDevOps adds the amount to the accuToDevOps by token path. func (pfs *protocolFeeState) addAccuToDevOps(_ int, rlm realm, tokenPath string, amount int64) error { before := pfs.GetAccuTransferToDevOpsByTokenPath(tokenPath) // Check for overflow after := gnsmath.SafeAddInt64(before, amount) if err := pfs.store.SetAccuToDevOpsItem(0, rlm, tokenPath, after); err != nil { return err } return nil } // addActualDistributedToGovStaker adds the amount to the actual distributed amount to gov/staker by token path. func (pfs *protocolFeeState) addActualDistributedToGovStaker(_ int, rlm realm, tokenPath string, amount int64) error { before := pfs.GetActualDistributedToGovStakerByTokenPath(tokenPath) after := gnsmath.SafeAddInt64(before, amount) if err := pfs.store.SetDistributedToGovStakerHistoryItem(0, rlm, tokenPath, after); err != nil { return err } return nil } // addActualDistributedToDevOps adds the amount to the actual distributed amount to devOps by token path. func (pfs *protocolFeeState) addActualDistributedToDevOps(_ int, rlm realm, tokenPath string, amount int64) error { before := pfs.GetActualDistributedToDevOpsByTokenPath(tokenPath) after := gnsmath.SafeAddInt64(before, amount) if err := pfs.store.SetDistributedToDevOpsHistoryItem(0, rlm, tokenPath, after); err != nil { return err } return nil } // GetAccuTransferToGovStakerByTokenPath gets the accumulated amount to gov/staker by token path. // // Parameters: // - tokenPath: Token path whose cumulative Gov/Staker allocation should be read. // // Returns: // - amount: Cumulative Gov/Staker allocation for tokenPath, or zero when no entry is recorded. func (pfs *protocolFeeState) GetAccuTransferToGovStakerByTokenPath(tokenPath string) int64 { return retrieveAmount(pfs.store.GetAccuToGovStaker(), tokenPath) } // GetAccuTransferToDevOpsByTokenPath gets the accumulated amount to devOps by token path. // // Parameters: // - tokenPath: Token path whose cumulative DevOps allocation should be read. // // Returns: // - amount: Cumulative DevOps allocation for tokenPath, or zero when no entry is recorded. func (pfs *protocolFeeState) GetAccuTransferToDevOpsByTokenPath(tokenPath string) int64 { return retrieveAmount(pfs.store.GetAccuToDevOps(), tokenPath) } // GetActualDistributedToGovStakerByTokenPath gets the actual distributed amount to gov/staker by token path. // // Parameters: // - tokenPath: Token path whose cumulative actual Gov/Staker distribution should be read. // // Returns: // - amount: Cumulative amount actually distributed to Gov/Staker for tokenPath, or zero when no history is recorded. func (pfs *protocolFeeState) GetActualDistributedToGovStakerByTokenPath(tokenPath string) int64 { return retrieveAmount(pfs.store.GetDistributedToGovStakerHistory(), tokenPath) } // GetActualDistributedToDevOpsByTokenPath gets the actual distributed amount to devOps by token path. // // Parameters: // - tokenPath: Token path whose cumulative actual DevOps distribution should be read. // // Returns: // - amount: Cumulative amount actually distributed to DevOps for tokenPath, or zero when no history is recorded. func (pfs *protocolFeeState) GetActualDistributedToDevOpsByTokenPath(tokenPath string) int64 { return retrieveAmount(pfs.store.GetDistributedToDevOpsHistory(), tokenPath) } // removeReservedToken removes one distributed token from the reserved token index. func (pfs *protocolFeeState) removeReservedToken(_ int, rlm realm, tokenPath string) error { return pfs.store.RemoveReservedToken(0, rlm, tokenPath) } // NewProtocolFeeState creates a new instance of protocolFeeState with initialized values // // Parameters: // - protocolFeeStore: Store implementation backing the new protocol-fee state view. // // Returns: // - state: New protocolFeeState wrapper backed by protocolFeeStore. func NewProtocolFeeStateBy(protocolFeeStore protocol_fee.IProtocolFeeStore) *protocolFeeState { return &protocolFeeState{ store: protocolFeeStore, } } func retrieveAmount(tree *bptree.BPTree, key string) int64 { amountI := tree.Get(key) if amountI == nil { return 0 } res, ok := amountI.(int64) if !ok { panic(ufmt.Sprintf("failed to cast amount to int64: %T", amountI)) } return res }
- #22render.gno
- #23package protocol_fee import ( "chain" "strings" prbac "gno.land/p/gnoswap/rbac/v1" "gno.land/p/moul/md/v0" "gno.land/p/moul/mdtable/v0" ufmt "gno.land/p/nt/ufmt/v0" "gno.land/r/gnoswap/access/v1" "gno.land/r/gnoswap/halt/v1" "gno.land/r/gnoswap/protocol_fee" ) func (pf *protocolFeeV1) Render(path string) string { if path != "" { return "404\n" } devOpsPct := pf.GetDevOpsPct() govStakerPct := pf.GetGovStakerPct() implementation := protocol_fee.GetImplementationPackagePath() table := mdtable.Table{ Headers: []string{"Field", "Value"}, Rows: [][]string{ {"Realm address", md.InlineCode(chain.PackageAddress("gno.land/r/gnoswap/protocol_fee").String())}, {"Active implementation", md.Link(implementation, strings.TrimPrefix(implementation, "gno.land"))}, {"DevOps allocation", ufmt.Sprintf("%d bps", devOpsPct)}, {"Governance-staker allocation", ufmt.Sprintf("%d bps", govStakerPct)}, {"Distribution-rate unit", "1 bps = 0.01%; 10,000 bps = 100%"}, {"DevOps recipient", md.InlineCode(access.MustGetAddress(prbac.ROLE_DEVOPS.String()).String())}, {"Governance-staker recipient", md.InlineCode(access.MustGetAddress(prbac.ROLE_GOV_STAKER.String()).String())}, {"Current accrual epoch", ufmt.Sprintf("%d", pf.GetAccrualEpoch())}, {"Protocol-fee halted", ufmt.Sprintf("%t", halt.IsHaltedProtocolFee())}, {"Withdrawals halted", ufmt.Sprintf("%t", halt.IsHaltedWithdraw())}, }, } out := md.H1("Gnoswap Protocol Fee") + "\n" out += md.H2("Configuration and distribution") + "\n" out += table.String() return out }
- #24utils.gno
Result log
msg:0,success:true,log:,events:[]