PathrockNetwork Gno Explorer
HomeBlocksTransactionsRealmsPackagesValidatorsAnalytics

PathrockNetwork Gno Explorer — an independent explorer for Gno.land Mainnet (gnoland-1), operated by PathrockNetwork. Not an official Gno.land service.

gnowebarchive RPC

gno.land/r/gnoswap/protocol_fee/v1

Realm
Open in gnoweb ↗

Overview

Kind
Realm (renderable)
Name
v1
Namespace
gnoswap / protocol_fee
Files
12 (README)(gnomod.toml)
Exported functions
2
Module
gno.land/r/gnoswap/protocol_fee/v1
gno
0.9

Files (12)

  • README.mdmarkdown
  • gnomod.tomltoml
  • assert.gnogno
  • doc.gnogno
  • errors.gnogno
  • getter.gnogno
  • init.gnogno
  • instance.gnogno
  • protocol_fee_state.gnogno
  • protocol_fee.gnogno
  • render.gnogno
  • utils.gnogno
  • protocol_fee.gnogno
    1package protocol_fee23import (4	"chain"5	"errors"6	"strconv"78

    Functions

    • NewProtocolFeeStateBy(protocolFeeStore interface {AddAccrualBucket func(int, .uverse.realm, string, int64, int64) .uverse.error; AddAccrualPendingToken func(int, .uverse.realm, string) .uverse.error; AddReservedToken func(int, .uverse.realm, string) .uverse.error; GetAccrualBuckets func(string, int) ([]int64, []int64); GetAccrualEpoch func() int64; GetAccrualPendingTokens func() []string; GetAccuToDevOps func() *gno.land/p/nt/bptree/v0.BPTree; GetAccuToDevOpsItem func(string) (int64, bool); GetAccuToGovStaker func() *gno.land/p/nt/bptree/v0.BPTree; GetAccuToGovStakerItem func(string) (int64, bool); GetDevOpsPct func() int64; GetDistributedToDevOpsHistory func() *gno.land/p/nt/bptree/v0.BPTree; GetDistributedToDevOpsHistoryItem func(string) (int64, bool); GetDistributedToGovStakerHistory func() *gno.land/p/nt/bptree/v0.BPTree; GetDistributedToGovStakerHistoryItem func(string) (int64, bool); GetReservedTokens func() []string; HasAccrualBucketsStoreKey func() bool; HasAccrualEpochStoreKey func() bool; HasAccrualPendingTokensStoreKey func() bool; HasAccuToDevOpsStoreKey func() bool; HasAccuToGovStakerStoreKey func() bool; HasDevOpsPctStoreKey func() bool; HasDistributedToDevOpsHistoryStoreKey func() bool; HasDistributedToGovStakerHistoryStoreKey func() bool; HasReservedToken func(string) bool; HasReservedTokensStoreKey func() bool; InitializeAccrualBuckets func(int, .uverse.realm) .uverse.error; InitializeAccrualEpoch func(int, .uverse.realm) .uverse.error; InitializeAccrualPendingTokens func(int, .uverse.realm) .uverse.error; InitializeAccuToDevOps func(int, .uverse.realm) .uverse.error; InitializeAccuToGovStaker func(int, .uverse.realm) .uverse.error; InitializeDevOpsPct func(int, .uverse.realm) .uverse.error; InitializeDistributedToDevOpsHistory func(int, .uverse.realm) .uverse.error; InitializeDistributedToGovStakerHistory func(int, .uverse.realm) .uverse.error; InitializeReservedTokens func(int, .uverse.realm) .uverse.error; RemoveAccrualBuckets func(int, .uverse.realm, string, []int64) .uverse.error; RemoveAccrualPendingToken func(int, .uverse.realm, string) .uverse.error; RemoveReservedToken func(int, .uverse.realm, string) .uverse.error; SetAccrualEpoch func(int, .uverse.realm, int64) .uverse.error; SetAccuToDevOpsItem func(int, .uverse.realm, string, int64) .uverse.error; SetAccuToGovStakerItem func(int, .uverse.realm, string, int64) .uverse.error; SetDevOpsPct func(int, .uverse.realm, int64) .uverse.error; SetDistributedToDevOpsHistoryItem func(int, .uverse.realm, string, int64) .uverse.error; SetDistributedToGovStakerHistoryItem func(int, .uverse.realm, string, int64) .uverse.error}) *gno.land/r/gnoswap/protocol_fee/v1.protocolFeeState

    • NewProtocolFeeV1(protocolFeeStore interface {AddAccrualBucket func(int, .uverse.realm, string, int64, int64) .uverse.error; AddAccrualPendingToken func(int, .uverse.realm, string) .uverse.error; AddReservedToken func(int, .uverse.realm, string) .uverse.error; GetAccrualBuckets func(string, int) ([]int64, []int64); GetAccrualEpoch func() int64; GetAccrualPendingTokens func() []string; GetAccuToDevOps func() *gno.land/p/nt/bptree/v0.BPTree; GetAccuToDevOpsItem func(string) (int64, bool); GetAccuToGovStaker func() *gno.land/p/nt/bptree/v0.BPTree; GetAccuToGovStakerItem func(string) (int64, bool); GetDevOpsPct func() int64; GetDistributedToDevOpsHistory func() *gno.land/p/nt/bptree/v0.BPTree; GetDistributedToDevOpsHistoryItem func(string) (int64, bool); GetDistributedToGovStakerHistory func() *gno.land/p/nt/bptree/v0.BPTree; GetDistributedToGovStakerHistoryItem func(string) (int64, bool); GetReservedTokens func() []string; HasAccrualBucketsStoreKey func() bool; HasAccrualEpochStoreKey func() bool; HasAccrualPendingTokensStoreKey func() bool; HasAccuToDevOpsStoreKey func() bool; HasAccuToGovStakerStoreKey func() bool; HasDevOpsPctStoreKey func() bool; HasDistributedToDevOpsHistoryStoreKey func() bool; HasDistributedToGovStakerHistoryStoreKey func() bool; HasReservedToken func(string) bool; HasReservedTokensStoreKey func() bool; InitializeAccrualBuckets func(int, .uverse.realm) .uverse.error; InitializeAccrualEpoch func(int, .uverse.realm) .uverse.error; InitializeAccrualPendingTokens func(int, .uverse.realm) .uverse.error; InitializeAccuToDevOps func(int, .uverse.realm) .uverse.error; InitializeAccuToGovStaker func(int, .uverse.realm) .uverse.error; InitializeDevOpsPct func(int, .uverse.realm) .uverse.error; InitializeDistributedToDevOpsHistory func(int, .uverse.realm) .uverse.error; InitializeDistributedToGovStakerHistory func(int, .uverse.realm) .uverse.error; InitializeReservedTokens func(int, .uverse.realm) .uverse.error; RemoveAccrualBuckets func(int, .uverse.realm, string, []int64) .uverse.error; RemoveAccrualPendingToken func(int, .uverse.realm, string) .uverse.error; RemoveReservedToken func(int, .uverse.realm, string) .uverse.error; SetAccrualEpoch func(int, .uverse.realm, int64) .uverse.error; SetAccuToDevOpsItem func(int, .uverse.realm, string, int64) .uverse.error; SetAccuToGovStakerItem func(int, .uverse.realm, string, int64) .uverse.error; SetDevOpsPct func(int, .uverse.realm, int64) .uverse.error; SetDistributedToDevOpsHistoryItem func(int, .uverse.realm, string, int64) .uverse.error; SetDistributedToGovStakerHistoryItem func(int, .uverse.realm, string, int64) .uverse.error}) interface {AddToProtocolFee func(int, .uverse.realm, string, int64) .uverse.error; AdvanceAccrualEpoch func(int, .uverse.realm) int64; ConsumeAccrualBuckets func(int, .uverse.realm, string, int) ([]int64, []int64); DistributeProtocolFee func(int, .uverse.realm); DistributeProtocolFeeByTokenPath func(int, .uverse.realm, string); GetAccrualBuckets func(string, int) ([]int64, []int64); GetAccrualEpoch func() int64; GetAccrualPendingTokens func() []string; GetAccuTransferToDevOpsByTokenPath func(string) int64; GetAccuTransferToGovStakerByTokenPath func(string) int64; GetAccuTransfersToDevOps func() map[string]int64; GetAccuTransfersToGovStaker func() map[string]int64; GetActualDistributedToDevOps func() map[string]int64; GetActualDistributedToDevOpsByTokenPath func(string) int64; GetActualDistributedToGovStaker func() map[string]int64; GetActualDistributedToGovStakerByTokenPath func(string) int64; GetDevOpsPct func() int64; GetGovStakerPct func() int64; GetReservedTokens func() []string; Render func(string) string; SetDevOpsPct func(int, .uverse.realm, int64); SetGovStakerPct func(int, .uverse.realm, int64)}

    Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.

    Rendered

    RenderedRawgnoweb ↗
    This realm does not provide renderable output (vm/qrender → NoRenderDeclError). View on gnoweb ↗

    vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.

    ufmt "gno.land/p/nt/ufmt/v0"
    9
    10 gnsmath "gno.land/p/gnoswap/gnsmath/v1"
    11 prabc "gno.land/p/gnoswap/rbac/v1"
    12 "gno.land/r/gnoswap/access/v1"
    13 "gno.land/r/gnoswap/common"
    14 "gno.land/r/gnoswap/halt/v1"
    15)
    16
    17// DistributeProtocolFee distributes collected protocol fees.
    18//
    19// Splits fees between devOps and gov/staker based on configured percentages.
    20// This function processes all accumulated fees since last distribution.
    21//
    22// Only callable by admin or gov/staker contract.
    23// Note: Default split is 0% devOps, 100% gov/staker.
    24//
    25// Parameters:
    26// - _: cross-call discriminator; callers pass 0
    27// - rlm: propagated protocol_fee realm context; it must match the current crossing frame
    28func (pf *protocolFeeV1) DistributeProtocolFee(_ int, rlm realm) {
    29 access.AssertIsRlmCurrent(0, rlm)
    30
    31 prev := rlm.Previous()
    32 assertIsAdminOrGovStaker(prev.Address())
    33
    34 if halt.IsHaltedWithdraw() {
    35 return
    36 }
    37
    38 protocolFeeAddr := access.MustGetAddress(prabc.ROLE_PROTOCOL_FEE.String())
    39 pfs := pf.getProtocolFeeState()
    40
    41 reservedTokens := pfs.ReservedTokens()
    42
    43 for _, token := range reservedTokens {
    44 pf.distributeProtocolFeeForToken(
    45 0,
    46 rlm,
    47 pfs,
    48 protocolFeeAddr,
    49 token,
    50 )
    51 }
    52}
    53
    54// DistributeProtocolFeeByTokenPath distributes collected protocol fees for one token path.
    55//
    56// Parameters:
    57// - _: cross-call discriminator; callers pass 0
    58// - rlm: propagated protocol_fee realm context; it must match the current crossing frame
    59// - tokenPath: token contract path whose reserved fee is eligible for distribution
    60func (pf *protocolFeeV1) DistributeProtocolFeeByTokenPath(_ int, rlm realm, tokenPath string) {
    61 access.AssertIsRlmCurrent(0, rlm)
    62
    63 prev := rlm.Previous()
    64 assertIsAdminOrGovStaker(prev.Address())
    65
    66 if halt.IsHaltedWithdraw() {
    67 return
    68 }
    69
    70 protocolFeeAddr := access.MustGetAddress(prabc.ROLE_PROTOCOL_FEE.String())
    71 pfs := pf.getProtocolFeeState()
    72
    73 if !pfs.store.HasReservedToken(tokenPath) {
    74 return
    75 }
    76
    77 pf.distributeProtocolFeeForToken(
    78 0,
    79 rlm,
    80 pfs,
    81 protocolFeeAddr,
    82 tokenPath,
    83 )
    84}
    85
    86// distributeProtocolFeeForToken validates and distributes one reserved token's pending fees.
    87func (pf *protocolFeeV1) distributeProtocolFeeForToken(_ int, rlm realm, pfs *protocolFeeState, protocolFeeAddr address, tokenPath string) {
    88 toDevOpsAmount := gnsmath.SafeSubInt64(
    89 pfs.GetAccuTransferToDevOpsByTokenPath(tokenPath),
    90 pfs.GetActualDistributedToDevOpsByTokenPath(tokenPath),
    91 )
    92 toGovStakerAmount := gnsmath.SafeSubInt64(
    93 pfs.GetAccuTransferToGovStakerByTokenPath(tokenPath),
    94 pfs.GetActualDistributedToGovStakerByTokenPath(tokenPath),
    95 )
    96
    97 amount := gnsmath.SafeAddInt64(toDevOpsAmount, toGovStakerAmount)
    98 balance := common.BalanceOf(tokenPath, protocolFeeAddr)
    99
    100 // amount should be less than or equal to balance
    101 if amount > balance {
    102 panic(makeErrorWithDetail(
    103 errInvalidAmount,
    104 ufmt.Sprintf("amount: %d should be less than or equal to balance: %d", amount, balance),
    105 ))
    106 }
    107
    108 if err := pfs.removeReservedToken(0, rlm, tokenPath); err != nil {
    109 panic(err)
    110 }
    111
    112 if amount <= 0 {
    113 return
    114 }
    115
    116 // distributeToDevOps and distributeToGovStaker record history before transferring.
    117 if err := pfs.distributeToDevOps(0, rlm, tokenPath, toDevOpsAmount); err != nil {
    118 panic(err)
    119 }
    120 if err := pfs.distributeToGovStaker(0, rlm, tokenPath, toGovStakerAmount); err != nil {
    121 panic(err)
    122 }
    123
    124 prev := rlm.Previous()
    125
    126 chain.Emit(
    127 "TransferProtocolFee",
    128 "prevAddr", prev.Address().String(),
    129 "prevRealm", prev.PkgPath(),
    130 "tokenPath", tokenPath,
    131 "toDevOpsAmount", strconv.FormatInt(toDevOpsAmount, 10),
    132 "toGovStakerAmount", strconv.FormatInt(toGovStakerAmount, 10),
    133 "amount", strconv.FormatInt(amount, 10),
    134 )
    135}
    136
    137// SetDevOpsPct sets the devOpsPct.
    138//
    139// Parameters:
    140// - _: cross-call discriminator; callers pass 0
    141// - rlm: propagated protocol_fee realm context; it must match the current crossing frame
    142// - pct: percentage for devOps (0-10000, where 10000 = 100%)
    143//
    144// Only callable by admin or governance.
    145// Note: GovStaker percentage is automatically adjusted to (10000 - devOpsPct).
    146func (pf *protocolFeeV1) SetDevOpsPct(_ int, rlm realm, pct int64) {
    147 access.AssertIsRlmCurrent(0, rlm)
    148
    149 halt.AssertIsNotHaltedProtocolFee()
    150
    151 prev := rlm.Previous()
    152 access.AssertIsAdminOrGovernance(prev.Address())
    153
    154 assertIsValidPercent(pct)
    155
    156 prevDevOpsPct := pf.getProtocolFeeState().DevOpsPct()
    157 prevGovStakerPct := pf.getProtocolFeeState().GovStakerPct()
    158
    159 newDevOpsPct, err := pf.getProtocolFeeState().setDevOpsPct(0, rlm, pct)
    160 if err != nil {
    161 panic(err)
    162 }
    163 newGovStakerPct := pf.getProtocolFeeState().GovStakerPct()
    164
    165 chain.Emit(
    166 "SetDevOpsPct",
    167 "prevAddr", prev.Address().String(),
    168 "prevRealm", prev.PkgPath(),
    169 "newDevOpsPct", strconv.FormatInt(newDevOpsPct, 10),
    170 "prevDevOpsPct", strconv.FormatInt(prevDevOpsPct, 10),
    171 "newGovStakerPct", strconv.FormatInt(newGovStakerPct, 10),
    172 "prevGovStakerPct", strconv.FormatInt(prevGovStakerPct, 10),
    173 )
    174}
    175
    176// SetGovStakerPct sets the stakerPct.
    177//
    178// Parameters:
    179// - _: cross-call discriminator; callers pass 0
    180// - rlm: propagated protocol_fee realm context; it must match the current crossing frame
    181// - pct: percentage for gov/staker (0-10000, where 10000 = 100%)
    182//
    183// Only callable by admin or governance.
    184// Note: DevOps percentage is automatically adjusted to (10000 - govStakerPct).
    185func (pf *protocolFeeV1) SetGovStakerPct(_ int, rlm realm, pct int64) {
    186 access.AssertIsRlmCurrent(0, rlm)
    187
    188 halt.AssertIsNotHaltedProtocolFee()
    189
    190 prev := rlm.Previous()
    191 access.AssertIsAdminOrGovernance(prev.Address())
    192
    193 assertIsValidPercent(pct)
    194
    195 prevDevOpsPct := pf.getProtocolFeeState().DevOpsPct()
    196 prevGovStakerPct := pf.getProtocolFeeState().GovStakerPct()
    197
    198 newGovStakerPct, err := pf.getProtocolFeeState().setGovStakerPct(0, rlm, pct)
    199 if err != nil {
    200 panic(err)
    201 }
    202 newDevOpsPct := pf.getProtocolFeeState().DevOpsPct()
    203
    204 chain.Emit(
    205 "SetGovStakerPct",
    206 "prevAddr", prev.Address().String(),
    207 "prevRealm", prev.PkgPath(),
    208 "newDevOpsPct", strconv.FormatInt(newDevOpsPct, 10),
    209 "prevDevOpsPct", strconv.FormatInt(prevDevOpsPct, 10),
    210 "newGovStakerPct", strconv.FormatInt(newGovStakerPct, 10),
    211 "prevGovStakerPct", strconv.FormatInt(prevGovStakerPct, 10),
    212 )
    213}
    214
    215// AddToProtocolFee pulls the approved amount into protocol fee accounting.
    216//
    217// Parameters:
    218// - _: cross-call discriminator; callers pass 0
    219// - rlm: propagated protocol_fee realm context; a non-current value returns errSpoofedRealm
    220// - tokenPath: token contract path from which the approved fee is pulled
    221// - amount: non-negative fee amount in token base units; zero returns nil without transferring
    222//
    223// Returns:
    224// - err: nil on success; errSpoofedRealm for a spoofed-realm call; errProtocolFeeHalted when protocol-fee collection is halted
    225//
    226// Only callable by pool, position, router or staker contracts.
    227// Caller must approve the protocol fee realm for at least amount before calling.
    228// Note: Accumulated fees are distributed when DistributeProtocolFee is called.
    229func (pf *protocolFeeV1) AddToProtocolFee(_ int, rlm realm, tokenPath string, amount int64) error {
    230 if !rlm.IsCurrent() {
    231 return errors.New(errSpoofedRealm)
    232 }
    233
    234 if halt.IsHaltedProtocolFee() {
    235 return errors.New(errProtocolFeeHalted)
    236 }
    237
    238 prev := rlm.Previous()
    239 caller := prev.Address()
    240 assertIsPoolOrPositionOrRouterOrStaker(caller)
    241
    242 if amount < 0 {
    243 panic(makeErrorWithDetail(
    244 errInvalidAmount,
    245 ufmt.Sprintf("amount(%d) should not be negative", amount),
    246 ))
    247 }
    248
    249 if amount == 0 {
    250 return nil
    251 }
    252
    253 pf.reserveCollectedProtocolFee(0, rlm, tokenPath, amount)
    254 protocolFeeAddr := access.MustGetAddress(prabc.ROLE_PROTOCOL_FEE.String())
    255 common.SafeGRC20TransferFrom(0, rlm, tokenPath, caller, protocolFeeAddr, amount)
    256
    257 chain.Emit(
    258 "AddToProtocolFee",
    259 "prevAddr", caller.String(),
    260 "prevRealm", prev.PkgPath(),
    261 "tokenPath", tokenPath,
    262 "amount", strconv.FormatInt(amount, 10),
    263 )
    264
    265 return nil
    266}
    267
    268func (pf *protocolFeeV1) reserveCollectedProtocolFee(_ int, rlm realm, tokenPath string, amount int64) {
    269 pfs := pf.getProtocolFeeState()
    270
    271 toDevOpsAmount := gnsmath.SafeMulDivInt64(amount, pfs.DevOpsPct(), 10000)
    272 toGovStakerAmount := gnsmath.SafeSubInt64(amount, toDevOpsAmount)
    273
    274 if err := pfs.addAccuToDevOps(0, rlm, tokenPath, toDevOpsAmount); err != nil {
    275 panic(err)
    276 }
    277 if err := pfs.addAccuToGovStaker(0, rlm, tokenPath, toGovStakerAmount); err != nil {
    278 panic(err)
    279 }
    280
    281 if err := pfs.store.AddReservedToken(0, rlm, tokenPath); err != nil {
    282 panic(err)
    283 }
    284
    285 // Bucket the gov/staker share under the accrual epoch in force so that gov/staker
    286 // can later fold it against the stake distribution that was live when it arrived.
    287 // A split that leaves gov/staker nothing moves no accumulator, so it is not bucketed.
    288 if toGovStakerAmount > 0 {
    289 epoch := pfs.store.GetAccrualEpoch()
    290 if err := pfs.store.AddAccrualBucket(0, rlm, tokenPath, epoch, toGovStakerAmount); err != nil {
    291 panic(err)
    292 }
    293 if err := pfs.store.AddAccrualPendingToken(0, rlm, tokenPath); err != nil {
    294 panic(err)
    295 }
    296 }
    297}
    298
    299// AdvanceAccrualEpoch closes the accrual epoch in force and returns the new one.
    300//
    301// gov/staker calls it on every stake change, before it records the new stake
    302// distribution, so every fee bucketed from now on is attributed to that distribution.
    303// Restricted to gov/staker: an epoch advanced by anyone else would fragment the buckets
    304// without a matching stake record on the gov/staker side.
    305//
    306// Parameters:
    307// - _: cross-call discriminator; callers pass 0
    308// - rlm: propagated protocol_fee realm context; it must be current and be called by GovStaker
    309//
    310// Returns:
    311// - epoch: newly stored non-negative accrual epoch for fees collected after the stake change
    312func (pf *protocolFeeV1) AdvanceAccrualEpoch(_ int, rlm realm) int64 {
    313 if !rlm.IsCurrent() {
    314 panic(errors.New(errSpoofedRealm))
    315 }
    316
    317 prev := rlm.Previous()
    318 access.AssertIsGovStaker(prev.Address())
    319
    320 pfs := pf.getProtocolFeeState()
    321 epoch := gnsmath.SafeAddInt64(pfs.store.GetAccrualEpoch(), 1)
    322 if err := pfs.store.SetAccrualEpoch(0, rlm, epoch); err != nil {
    323 panic(err)
    324 }
    325
    326 return epoch
    327}
    328
    329// ConsumeAccrualBuckets returns and clears up to limit of the oldest pending buckets of
    330// tokenPath, as parallel epoch and amount slices in epoch order. A limit of zero or
    331// less consumes every pending bucket. Once no bucket remains the token leaves the
    332// pending set.
    333//
    334// Restricted to gov/staker: clearing a bucket without folding it into an accumulator
    335// would strand that fee share.
    336//
    337// Parameters:
    338// - _: cross-call discriminator; callers pass 0
    339// - rlm: propagated protocol_fee realm context; it must be current and be called by GovStaker
    340// - tokenPath: token contract path whose pending accrual buckets are consumed
    341// - limit: maximum number of oldest buckets to consume; zero or negative consumes all pending buckets
    342//
    343// Returns:
    344// - epochs: consumed accrual epoch numbers in ascending epoch order
    345// - amounts: fee amounts corresponding positionally to epochs, in token base units
    346func (pf *protocolFeeV1) ConsumeAccrualBuckets(_ int, rlm realm, tokenPath string, limit int) ([]int64, []int64) {
    347 if !rlm.IsCurrent() {
    348 panic(errors.New(errSpoofedRealm))
    349 }
    350
    351 prev := rlm.Previous()
    352 access.AssertIsGovStaker(prev.Address())
    353
    354 pfs := pf.getProtocolFeeState()
    355 epochs, amounts := pfs.store.GetAccrualBuckets(tokenPath, limit)
    356 if len(epochs) == 0 {
    357 return epochs, amounts
    358 }
    359
    360 if err := pfs.store.RemoveAccrualBuckets(0, rlm, tokenPath, epochs); err != nil {
    361 panic(err)
    362 }
    363
    364 remainingEpochs, _ := pfs.store.GetAccrualBuckets(tokenPath, 1)
    365 if len(remainingEpochs) == 0 {
    366 if err := pfs.store.RemoveAccrualPendingToken(0, rlm, tokenPath); err != nil {
    367 panic(err)
    368 }
    369 }
    370
    371 return epochs, amounts
    372}
    373