🎁Staking and Rewards [USDA-sUSDT LP]

An overview of the contract facilitating staking and reward distributions for the USDA-sUSDT liquidity pool within the BitFlow StableSwap.

This contract is designed to facilitate staking and reward distributions for the USDA-sUSDT liquidity pool within the BitFlow StableSwap. It allows users to perform actions, including staking liquidity pool tokens and claiming rewards.

Data Storage

These structures are used to enable the contract to maintain its state, monitor information, and ensure data integrity.

reward-cycle-index

(define-constant reward-cycle-index (list u1 u2 u3 u4 u5 u6 u7 u8 u9 u10 u11 u12 u13 u14 u15 u16 u17 u18 u19 u20 u21 u22 u23 u24 u25 u26 u27 u28 u29 u30 u31 u32 u33 u34 u35 u36 u37 u38 u39 u40 u41 u42 u43 u44 u45 u46 u47 u48 u49 u50 u51 u52 u53 u54 u55 u56 u57 u58 u59 u60 u61 u62 u63 u64 u65 u66 u67 u68 u69 u70 u71 u72 u73 u74 u75 u76 u77 u78 u79 u80 u81 u82 u83 u84 u85 u86 u87 u88 u89 u90 u91 u92 u93 u94 u95 u96 u97 u98 u99 u100 u101 u102 u103 u104 u105 u106 u107 u108 u109 u110 u111 u112 u113 u114 u115 u116 u117 u118 u119 u120))

A constant list that serves as an index for iterating through reward cycles.

helper-uint

(define-data-var helper-uint uint u0)

A data variable that is used for operations related to filtering null values and mapping an index to the succeeding cycle.

helper-uint-list

(define-data-var helper-uint-list (list 12000 uint) (list ))

A data variable that is used to filter out existing cycles in the cycles-staked list.

StakerDataMap

(define-map StakerDataMap {x-token: principal, y-token: principal, lp-token: principal, user: principal} {
    cycles-staked: (list 12000 uint),
    cycles-to-unstake: (list 12000 uint),
    total-currently-staked: uint
})

A map that records comprehensive staking data associated with a particular principal.

StakerDataPerCycleMap

(define-map StakerDataPerCycleMap {x-token: principal, y-token: principal, lp-token: principal, user: principal, cycle: uint} {
    lp-token-staked: uint,
    reward-claimed: bool,
    lp-token-to-unstake: uint
})

A map that records staking data for each cycle linked to a principal.

DataPerCycleMap

(define-map DataPerCycleMap {x-token: principal, y-token: principal, lp-token: principal, cycle: uint} uint)

A map that records staking data across all stakers for a specific cycle.

TotalStakedPerPairMap

(define-map TotalStakedPerPairMap {x-token: principal, y-token: principal, lp-token: principal} {total-staked: uint})

A map that tracks the total amount of LP tokens staked across all participants for a specific token pair.


Error Codes

These error codes are displayed when there's a problem executing any of the functions defined in this smart contract.

err-no-pair-data

This error code indicates that the contract could not retrieve data for the requested token pair.

err-pair-not-approved

This error code indicates that the specified token pair has not been granted the necessary approval or permissions to proceed with the action.

err-cycles-too-high

This error code indicates that the user is attempting to stake for more than 121 cycles.

err-cycles-too-low

This error code indicates that the user is attempting to stake for less than 1 cycle.

err-amount-too-low

This error code indicates that the specified amount is too low.

err-lp-token-transfer-failed

This error code indicates that an issue occurred while attempting to transfer LP tokens.

err-cycles-staked-overflow

This error code indicates that the combined list of current and new staking cycles has exceeded the maximum allowed limit of 12,000 entries.

err-cycles-to-unstake-overflow

This error code indicates that the resulting list, after appending new unstaking cycles to the current cycles, has gone beyond the permissible limit of 12,000 entries.

err-no-cycle-data

This error code indicates that an issue occurred while trying to find data for a specific cycle.

err-no-rewards-to-claim

This error code indicates that the user has no available rewards to claim.

err-rewards-already-claimed

This error code indicates that the user has already claimed their rewards.

err-cycle-too-high

This error code occurs when the user tries to claim staking rewards from the current cycle.

err-y-token-transfer-failed

This error code indicates that an issue occurred while attempting to transfer token Y.

err-x-token-transfer-failed

This error code indicates that an issue occurred while attempting to transfer token X.

err-no-staker-data

This error code indicates that the user has no available staking data.

err-no-total-staked-per-pair

This error code indicates that an issue occurred while attempting to calculate the total amount staked for a specified token pair.

err-no-lp-tokens-to-unstake

This error code indicates that there are no LP tokens to unstake.

err-failed-to-transfer-lp-tokens

This error code indicates that an issue occurred while attempting to transfer LP tokens.


Public Functions

These functions can be accessed by external contracts or users, allowing for interaction with the contract and modification of its state.

stake-lp-tokens

(define-public (stake-lp-tokens (x-token <og-sip-010-trait>) (y-token <susdt-sip-010-trait>) (lp-token <og-sip-010-trait>) (cycles uint) (amount uint))
    (let 
        (
            (current-staker-data (map-get? StakerDataMap {x-token: (contract-of x-token), y-token: (contract-of y-token), lp-token: (contract-of lp-token), user: tx-sender}))
            (current-cycles-staked (default-to (list ) (get cycles-staked current-staker-data)))
            (current-cycles-to-unstake (default-to (list ) (get cycles-to-unstake current-staker-data)))
            (updated-helper-uint-to-filter (var-set helper-uint cycles))
            (filtered-null-list (filter filter-null-value reward-cycle-index))
            (current-cycle (contract-call? .stableswap-usda-susdt get-current-cycle))
            (updated-helper-uint-to-map (var-set helper-uint current-cycle))
            (next-cycles (map map-filtered-null-list filtered-null-list))
            (updated-helper-uint-list-current-cycles (var-set helper-uint-list current-cycles-staked))
            (next-cycles-not-in-current-cycles (filter filter-list next-cycles))
            (unstake-cycle (+ u1 (+ current-cycle cycles)))
            (is-unstakeable-block-in-unstakeable-cycles (is-some (index-of current-cycles-to-unstake unstake-cycle)))
            (current-all-staker-data (map-get? DataPerCycleMap {x-token: (contract-of x-token), y-token: (contract-of y-token), lp-token: (contract-of lp-token), cycle: current-cycle}))
            (pair-data (unwrap! (contract-call? .stableswap-usda-susdt get-pair-data x-token y-token lp-token) (err "err-no-pair-data")))
            (total-currently-staked-data (default-to {total-staked: u0} (map-get? TotalStakedPerPairMap {x-token: (contract-of x-token), y-token: (contract-of y-token), lp-token: (contract-of lp-token)})))
            (total-currently-staked-in-contract (get total-staked total-currently-staked-data))
            (approved-pair (get approval pair-data))
        )

        ;; Assert that pair is approved
        (asserts! approved-pair (err "err-pair-not-approved"))

        ;; Assert that cycles is less than 121
        (asserts! (< cycles u121) (err "err-cycles-too-high"))

        ;; Assert that cycles is greater than 0
        (asserts! (> cycles u0) (err "err-cycles-too-low"))

        ;; Assert that amount is greater than 0
        (asserts! (> amount u0) (err "err-amount-too-low"))

        ;; Transfer LP tokens from user to contract
        (unwrap! (contract-call? lp-token transfer amount tx-sender (as-contract tx-sender) none) (err "err-lp-token-transfer-failed"))

        ;; Update lp-tokens-staked in the appropriate cycles
        (fold update-staker-data-per-cycle-fold next-cycles {x-token: (contract-of x-token), y-token: (contract-of y-token), lp-token: (contract-of lp-token), cycles-staked: current-cycles-staked, amount: amount})

        ;; Updating the total balance of LP tokens staked in this contract
        (map-set TotalStakedPerPairMap {x-token: (contract-of x-token), y-token: (contract-of y-token), lp-token: (contract-of lp-token)} {total-staked: (+ total-currently-staked-in-contract amount)})

        ;; Update StakerDataMap
        (if (is-some current-staker-data)
            ;; Staker already exists, update cycles-staked list
            (map-set StakerDataMap {x-token: (contract-of x-token), y-token: (contract-of y-token), lp-token: (contract-of lp-token), user: tx-sender} {
                cycles-staked: (unwrap! (as-max-len? (concat current-cycles-staked next-cycles-not-in-current-cycles) u12000) (err "err-cycles-staked-overflow")),
                cycles-to-unstake: (if is-unstakeable-block-in-unstakeable-cycles 
                    ;; Unstakeable cycle already exists, don't update cycles-to-unstake list
                    current-cycles-to-unstake
                    ;; Unstakeable cycle doesn't exist, update cycles-to-unstake list
                    (unwrap! (as-max-len? (concat current-cycles-to-unstake (list unstake-cycle)) u12000) (err "err-cycles-to-unstake-overflow"))
                ),
                total-currently-staked: (+ amount (default-to u0 (get total-currently-staked current-staker-data)))
            })
            ;; Staker doesn't exist, create new staker
            (map-set StakerDataMap {x-token: (contract-of x-token), y-token: (contract-of y-token), lp-token: (contract-of lp-token), user: tx-sender} {
                cycles-staked: next-cycles,
                cycles-to-unstake: (list unstake-cycle),
                total-currently-staked: amount
            })
        )

        ;; Update unstakeable lp-token StakerDataMap
        (ok (if (is-some (map-get? StakerDataPerCycleMap {x-token: (contract-of x-token), y-token: (contract-of y-token), lp-token: (contract-of lp-token), user: tx-sender, cycle: unstake-cycle}))
            ;; Staker already exists, only update lp-token-to-unstake
            (map-set StakerDataPerCycleMap {x-token: (contract-of x-token), y-token: (contract-of y-token), lp-token: (contract-of lp-token), user: tx-sender, cycle: unstake-cycle} (merge 
                (default-to { lp-token-staked: u0, reward-claimed: false, lp-token-to-unstake: u0} (map-get? StakerDataPerCycleMap {x-token: (contract-of x-token), y-token: (contract-of y-token), lp-token: (contract-of lp-token), user: tx-sender, cycle: unstake-cycle}))
                {lp-token-to-unstake: (+ amount (default-to u0 (get lp-token-to-unstake (map-get? StakerDataPerCycleMap {x-token: (contract-of x-token), y-token: (contract-of y-token), lp-token: (contract-of lp-token), user: tx-sender, cycle: unstake-cycle}))))}
            ))
            ;; Staker doesn't exist, create new entry
            (map-set StakerDataPerCycleMap {x-token: (contract-of x-token), y-token: (contract-of y-token), lp-token: (contract-of lp-token), user: tx-sender, cycle: unstake-cycle} { 
                lp-token-staked: u0, 
                reward-claimed: false, 
                lp-token-to-unstake: amount
            })
        ))

    )
)

A public function that allows a user to stake liquidity provider (LP) tokens for a given number of cycles. The process begins by retrieving the current data of the staker, such as the cycles they've staked and those they wish to unstake in. After a series of checks (ensuring the staking pair is approved, validating the number of cycles, and verifying the staking amount), the function transfers the LP tokens from the user to the contract. The LP token amount is then updated for the appropriate cycles. Finally, the staking data is updated for the user, detailing the cycles they've staked in, the amount, and the time at which they can unstake.

claim-cycle-staking-rewards

(define-public (claim-cycle-staking-rewards (x-token principal) (y-token principal) (lp-token principal) (x-token-trait <og-sip-010-trait>) (y-token-trait <susdt-sip-010-trait>) (lp-token-trait <og-sip-010-trait>) (cycle uint))
    (let 
        (
            (current-cycle (contract-call? .stableswap-usda-susdt get-current-cycle))
            (param-cycle-user-data (unwrap! (map-get? StakerDataPerCycleMap {x-token: x-token, y-token: y-token, lp-token: lp-token, user: tx-sender, cycle: cycle}) (err "err-no-cycle-data")))
            (param-cycle-reward-claimed (get reward-claimed param-cycle-user-data))
            (param-cycle-user-lp-staked (get lp-token-staked param-cycle-user-data))
            (param-cycle-total-lp-staked (unwrap! (map-get? DataPerCycleMap {x-token: x-token, y-token: y-token, lp-token: lp-token, cycle: cycle}) (err "err-no-cycle-data")))
            (param-cycle-fees (unwrap! (contract-call? .stableswap-usda-susdt get-cycle-data x-token y-token lp-token cycle) (err "err-no-cycle-data")))
            (param-cycle-balance-x-fee (get cycle-fee-balance-x param-cycle-fees))
            (param-cycle-balance-y-fee (get cycle-fee-balance-y param-cycle-fees))
            (param-cycle-x-rewards (/ (* param-cycle-balance-x-fee param-cycle-user-lp-staked) param-cycle-total-lp-staked))
            (param-cycle-y-rewards (/ (* param-cycle-balance-y-fee param-cycle-user-lp-staked) param-cycle-total-lp-staked))
            (claimer tx-sender)
        )

        ;; Assert that param-cycle-x or param-cycle-y rewards are greater than 0
        (asserts! (or (> param-cycle-x-rewards u0) (> param-cycle-y-rewards u0)) (err "err-no-rewards-to-claim"))

        ;; Assert that param-cycle-reward-claimed is false
        (asserts! (not param-cycle-reward-claimed) (err "err-rewards-already-claimed"))

        ;; Assert that claiming from a previous cycle
        (asserts! (< cycle current-cycle) (err "err-cycle-too-high"))

        ;; Check if one of the param-cycle-x or param-cycle-y rewards is equal to 0
        (if (or (is-eq param-cycle-balance-x-fee u0) (is-eq param-cycle-balance-y-fee u0))
            ;; One of them is equal to 0, only transfer the other
            (if (is-eq param-cycle-balance-x-fee u0)
                ;; param-cycle-x-rewards is equal to 0, transfer param-cycle-y-rewards from contract to user
                (unwrap! (as-contract (contract-call? y-token-trait transfer param-cycle-y-rewards tx-sender claimer none)) (err "err-y-token-transfer-failed"))
                ;; param-cycle-y-rewards is equal to 0, transfer param-cycle-x-rewards from contract to user
                (unwrap! (as-contract (contract-call? x-token-trait transfer param-cycle-x-rewards tx-sender claimer none)) (err "err-x-token-transfer-failed"))
            )
            ;; Neither of them are equal to 0, transfer both
            (begin 
                
                ;; Transfer param-cycle-x-rewards from contract to user
                (unwrap! (as-contract (contract-call? x-token-trait transfer param-cycle-x-rewards tx-sender claimer none)) (err "err-x-token-transfer-failed"))

                ;; Transfer param-cycle-y-rewards from contract to user
                (unwrap! (as-contract (contract-call? y-token-trait transfer param-cycle-y-rewards tx-sender claimer none)) (err "err-y-token-transfer-failed"))
            )
        )

        ;; Update StakerDataPerCycleMap with reward-claimed = true
        (map-set StakerDataPerCycleMap {x-token: x-token, y-token: y-token, lp-token: lp-token, user: claimer, cycle: cycle} (merge 
            param-cycle-user-data
            {reward-claimed: true}
        ))

        (ok {x-token-reward: param-cycle-x-rewards, y-token-reward: param-cycle-y-rewards})
    )
)

A public function that allows a user to claim staking rewards for a specific cycle. Initially, it gathers necessary data about the current cycle, rewards, and fees. Various assertions are made to ensure rewards exist for the specified cycle and that the rewards haven't been claimed previously. After these checks, the function determines whether the user is eligible for either x-token or y-token rewards (or both). The eligible rewards are transferred from the contract to the user, and the user's claim status for the cycle is updated to reflect that the rewards have been claimed.

claim-all-staking-rewards

(define-public (claim-all-staking-rewards (x-token <og-sip-010-trait>) (y-token <susdt-sip-010-trait>) (lp-token <og-sip-010-trait>))
    (let 
        (
            (current-cycle (contract-call? .stableswap-usda-susdt get-current-cycle))
            (current-cycle-helper (var-set helper-uint current-cycle))
            (current-staker-data (unwrap! (map-get? StakerDataMap {x-token: (contract-of x-token), y-token: (contract-of y-token), lp-token: (contract-of lp-token), user: tx-sender}) (err "err-no-staker-data")))
            (current-cycles-staked (get cycles-staked current-staker-data))
            (rewards-to-claim (fold fold-from-all-cycles-to-cycles-unclaimed current-cycles-staked {x-token: (contract-of x-token), y-token: (contract-of y-token), lp-token: (contract-of lp-token), total-rewards-x: u0, total-rewards-y: u0, current-cycle: current-cycle}))
            (rewards-to-claim-x (get total-rewards-x rewards-to-claim))
            (rewards-to-claim-y (get total-rewards-y rewards-to-claim))
            (claimer tx-sender)
        )

        ;; Check if one of the param-cycle-x or param-cycle-y rewards is equal to 0
        (ok (if (or (is-eq rewards-to-claim-x u0) (is-eq rewards-to-claim-y u0))
                ;; One of them is equal to 0, only transfer the other
                (if (is-eq rewards-to-claim-x u0)
                    ;; param-cycle-x-rewards is equal to 0, transfer param-cycle-y-rewards from contract to user
                    (unwrap! (as-contract (contract-call? y-token transfer rewards-to-claim-y tx-sender claimer none)) (err "err-y-token-transfer-failed"))
                    ;; param-cycle-y-rewards is equal to 0, transfer param-cycle-x-rewards from contract to user
                    (unwrap! (as-contract (contract-call? x-token transfer rewards-to-claim-x tx-sender claimer none)) (err "err-x-token-transfer-failed"))
                )
                ;; Neither of them are equal to 0, transfer both
                (begin 
                    
                    ;; Transfer param-cycle-x-rewards from contract to user
                    (unwrap! (as-contract (contract-call? x-token transfer rewards-to-claim-x tx-sender claimer none)) (err "err-x-token-transfer-failed"))

                    ;; Transfer param-cycle-y-rewards from contract to user
                    (unwrap! (as-contract (contract-call? y-token transfer rewards-to-claim-y tx-sender claimer none)) (err "err-y-token-transfer-failed"))
                )
            )
        )
    )
)

A public function that facilitates the claiming of all staking rewards by the user for all the cycles up to the current one. The function starts by fetching the current cycle and staker data, including the cycles the user has staked in. It then calculates the total rewards the user is eligible to claim for both x-token and y-token. If the rewards for either x-token or y-token are zero, the function will transfer only the non-zero rewards to the user. Otherwise, both rewards are transferred to the user.

unstake-all-lp-tokens

(define-public (unstake-all-lp-tokens (x-token <og-sip-010-trait>) (y-token <susdt-sip-010-trait>) (lp-token <og-sip-010-trait>))
    (let 
        (
            (liquidity-provider tx-sender)
            (current-cycle (contract-call? .stableswap-usda-susdt get-current-cycle))
            (current-cycle-helper (var-set helper-uint current-cycle))
            (current-staker-data (unwrap! (map-get? StakerDataMap {x-token: (contract-of x-token), y-token: (contract-of y-token), lp-token: (contract-of lp-token), user: tx-sender}) (err "err-no-staker-data")))
            (current-cycles-to-unstake (get cycles-to-unstake current-staker-data))
            (current-staked-by-unstaker (get total-currently-staked current-staker-data))
            (total-currently-staked-data (unwrap! (map-get? TotalStakedPerPairMap {x-token: (contract-of x-token), y-token: (contract-of y-token), lp-token: (contract-of lp-token)}) (err "err-no-total-staked-per-pair")))
            (total-currently-staked-in-contract (get total-staked total-currently-staked-data))
            (unstake-data (fold fold-from-all-cycles-to-unstakeable-cycles current-cycles-to-unstake {x-token: (contract-of x-token), y-token: (contract-of y-token), lp-token: (contract-of lp-token), total-lps-to-unstake: u0, current-cycles-to-unstake: current-cycles-to-unstake}))
            (lp-tokens-to-unstake (get total-lps-to-unstake unstake-data))
            (updated-total-currently-staked (- total-currently-staked-in-contract lp-tokens-to-unstake))
            (updated-total-currently-staked-by-unstaker (- current-staked-by-unstaker lp-tokens-to-unstake))
            (updated-current-cycles-to-unstake (get current-cycles-to-unstake unstake-data))
        )

        (asserts! (> lp-tokens-to-unstake u0) (err "err-no-lp-tokens-to-unstake"))
        
        ;; Transfer LP tokens to unstake from the contract to the user
        (unwrap! (as-contract (contract-call? lp-token transfer lp-tokens-to-unstake tx-sender liquidity-provider none)) (err "err-failed-to-transfer-lp-tokens"))
        
        (map-set StakerDataMap {x-token: (contract-of x-token), y-token: (contract-of y-token), lp-token: (contract-of lp-token), user: tx-sender} (merge 
            current-staker-data
            {total-currently-staked: updated-total-currently-staked-by-unstaker, cycles-to-unstake: updated-current-cycles-to-unstake}
        ))
        ;; Updating the total balance of LP tokens staked in this contract
        (map-set TotalStakedPerPairMap {x-token: (contract-of x-token), y-token: (contract-of y-token), lp-token: (contract-of lp-token)} {total-staked: updated-total-currently-staked})
        (ok lp-tokens-to-unstake)

    )
)

A public function that enables a liquidity provider to fully unstake their liquidity pool (LP) tokens from a specified token pair. By taking the x-token, y-token, and lp-token as parameters, it first ascertains the current staking cycle and extracts the relevant staker data. It calculates the total LP tokens available for unstaking, making sure there's a non-zero amount, and then transfers these tokens from the contract back to the user. Post withdrawal, the function updates both the user's staking data and the global staked LP token count.


Private Functions

These functions are confined to the contract in which they are defined, ensuring encapsulation and restricting external access to certain functionalities.

filter-null-value

(define-private (filter-null-value (value uint)) 
    (if (<= value (var-get helper-uint)) 
        true
        false
    )
)

A private function that checks if the given value is less than or equal to the value of the global variable helper-uint. It returns true if the condition is met, otherwise it returns false.

map-filtered-null-list

(define-private (map-filtered-null-list (index uint)) 
    (+ (var-get helper-uint) index)
)

A private function that takes an uint as its input and returns the sum of the value of the global variable helper-uint and the given index uint.

filter-list

(define-private (filter-list (value uint)) 
    (if (is-some (index-of (var-get helper-uint-list) value))
        false
        true
    )
)

A private function that checks if the given value exists in the list stored in the global variable helper-uint-list. It returns false if the value exists in the list, otherwise, it returns true.

filter-unstaked-cycle

(define-private (filter-unstaked-cycle (value uint)) 
    (if (is-eq value (var-get helper-uint)) 
        false
        true
    )
)

A private function that checks if the provided value is equal to the value of the global variable helper-uint. If they are equal, it returns false; otherwise, it returns true.

update-staker-data-per-cycle-fold

(define-private (update-staker-data-per-cycle-fold (next-cycle uint) (static-user-and-cycle-data {x-token: principal, y-token: principal, lp-token: principal, cycles-staked: (list 12000 uint), amount: uint}))
    (let 
        (
            (x-token-static (get x-token static-user-and-cycle-data))
            (y-token-static (get y-token static-user-and-cycle-data))
            (lp-token-static (get lp-token static-user-and-cycle-data))
            (cycles-staked-static (get cycles-staked static-user-and-cycle-data))
            (amount-static (get amount static-user-and-cycle-data))
            (current-cycle-user-data (default-to {lp-token-staked: u0, reward-claimed: false, lp-token-to-unstake: u0} (map-get? StakerDataPerCycleMap {x-token: x-token-static, y-token: y-token-static, lp-token: lp-token-static, user: tx-sender, cycle: next-cycle})))
            (current-cycle-lp-token-staked (get lp-token-staked current-cycle-user-data))
            (current-cycle-lp-token-to-unstake (get lp-token-to-unstake current-cycle-user-data))
            (current-cycle-reward-claimed (get reward-claimed current-cycle-user-data))
            (current-all-staker-data (map-get? DataPerCycleMap {x-token: x-token-static, y-token: y-token-static, lp-token: lp-token-static, cycle: next-cycle}))
        )
        ;; Check if staker is already staked in this cycle
        (if (is-some (index-of cycles-staked-static next-cycle))
            ;; Staker is already staked in this cycle, update StakerDataPerCycleMap
            (map-set StakerDataPerCycleMap {x-token: x-token-static, y-token: y-token-static, lp-token: lp-token-static, user: tx-sender, cycle: next-cycle} (merge 
                    current-cycle-user-data
                    {lp-token-staked: (+ amount-static current-cycle-lp-token-staked)}
            ))
            ;; Staker is not already staked in this cycle, create new StakerDataPerCycleMap
            (map-set StakerDataPerCycleMap {x-token: x-token-static, y-token: y-token-static, lp-token: lp-token-static, user: tx-sender, cycle: next-cycle} (merge 
                    current-cycle-user-data
            {
                lp-token-staked: amount-static,
                reward-claimed: false,
            }))
        )
        ;; Update DataPerCycleMap
        (if (is-some current-all-staker-data)
            ;; Cycle data already exists, update total-lp-token-staked
            (map-set DataPerCycleMap {x-token: x-token-static, y-token: y-token-static, lp-token: lp-token-static, cycle: next-cycle} (+ amount-static (default-to u0 current-all-staker-data)))
            ;; Staker doesn't exist, create new entry
            (map-set DataPerCycleMap {x-token: x-token-static, y-token: y-token-static, lp-token: lp-token-static, cycle: next-cycle} amount-static)
        )

        static-user-and-cycle-data
    )
)

A private function that updates a staker's data for a particular staking cycle. It checks whether the staker has already staked in the given cycle and accordingly updates or creates a new entry in the StakerDataPerCycleMap.

fold-from-all-cycles-to-cycles-unclaimed

(define-private (fold-from-all-cycles-to-cycles-unclaimed (cycle uint) (fold-data {x-token: principal, y-token: principal, lp-token: principal, total-rewards-x: uint, total-rewards-y: uint, current-cycle: uint})) 
    (let 
        (
            (static-current-cycle (get current-cycle fold-data))
            (static-x-token (get x-token fold-data))
            (static-y-token (get y-token fold-data))
            (static-lp-token (get lp-token fold-data))
            (current-total-rewards-x (get total-rewards-x fold-data))
            (current-total-rewards-y (get total-rewards-y fold-data))
            (param-cycle-staking-rewards (get-staking-rewards-at-cycle static-x-token static-y-token static-lp-token cycle))
            (param-cycle-rewards-x (match param-cycle-staking-rewards 
                ok-branch
                    (get x-token-reward ok-branch)
                err-branch
                    u0
            ))
            (param-cycle-rewards-y (match param-cycle-staking-rewards 
                ok-branch
                    (get y-token-reward ok-branch)
                err-branch
                    u0
            ))
            ;; If the param-cycle is not in the past, then the rewards have to be zero.
            (param-cycle-x-rewards (if (>= cycle static-current-cycle) u0 param-cycle-rewards-x))
            (param-cycle-y-rewards (if (>= cycle static-current-cycle) u0 param-cycle-rewards-y))

            (param-cycle-user-data (default-to {lp-token-staked: u0,reward-claimed: false, lp-token-to-unstake: u0} (map-get? StakerDataPerCycleMap {x-token: static-x-token, y-token: static-y-token, lp-token: static-lp-token, user: tx-sender, cycle: cycle})))
        )

        (if (or (> param-cycle-x-rewards u0) (> param-cycle-y-rewards u0))
            ;; There are rewards to claim
            (begin 
                ;; Update StakerDataPerCycleMap with reward-claimed = true
                (map-set StakerDataPerCycleMap {x-token: static-x-token, y-token: static-y-token, lp-token: static-lp-token, user: tx-sender, cycle: cycle} (merge 
                    param-cycle-user-data
                    {reward-claimed: true}
                ))
                {x-token: static-x-token, y-token: static-y-token, lp-token: static-lp-token, total-rewards-x: (+ current-total-rewards-x param-cycle-x-rewards), total-rewards-y: (+ current-total-rewards-y param-cycle-y-rewards), current-cycle: static-current-cycle}
            )
            ;; There are no rewards to claim
            fold-data
        )

    )
)

A private function that iterates through all staking cycles and calculates the unclaimed rewards for the staker. For each cycle, it checks the staking rewards available for the staker. If rewards are found, the function updates the StakerDataPerCycleMap to mark the rewards as claimed. The cumulative rewards across all cycles are returned as the output.

fold-from-all-cycles-to-unstakeable-cycles

(define-private (fold-from-all-cycles-to-unstakeable-cycles (cycle uint) (fold-data {x-token: principal, y-token: principal, lp-token: principal, total-lps-to-unstake: uint, current-cycles-to-unstake: (list 12000 uint)})) 
    (let 
        (
            (current-cycle (contract-call? .stableswap-usda-susdt get-current-cycle))
            (current-total-lp-tokens-to-unstake (get total-lps-to-unstake fold-data))
            (static-x-token (get x-token fold-data))
            (static-y-token (get y-token fold-data))
            (static-lp-token (get lp-token fold-data))
            (current-cycles-to-unstake (get current-cycles-to-unstake fold-data))
            (param-cycle-user-data (match (map-get? StakerDataPerCycleMap {x-token: static-x-token, y-token: static-y-token, lp-token: static-lp-token, user: tx-sender, cycle: cycle}) 
                ;; StakerDataPerCycleMap entry exists, save it to param-cycle-user-data
                unwrapped-value
                    unwrapped-value
                ;; StakerDataPerCycleMap entry doesn't exist (this should never happen)
                {lp-token-staked: u0,
                reward-claimed: false,
                lp-token-to-unstake: u0}
            ))
            
            (param-cycle-user-lp-tokens-to-unstake (get lp-token-to-unstake param-cycle-user-data))
            (updated-helper-uint-to-filter (var-set helper-uint cycle))
            (updated-cycles-to-unstake (filter filter-unstaked-cycle current-cycles-to-unstake))

        )

        (if (and (> param-cycle-user-lp-tokens-to-unstake u0) (<= cycle current-cycle))
            ;; There are lp-tokens to unstake
            (begin 
                ;; Update StakerDataPerCycleMap with lp-token-to-unstake = u0
                (map-set StakerDataPerCycleMap {x-token: static-x-token, y-token: static-y-token, lp-token: static-lp-token, user: tx-sender, cycle: cycle} (merge 
                    param-cycle-user-data
                    {lp-token-to-unstake: u0}
                ))
                {x-token: static-x-token, y-token: static-y-token, lp-token: static-lp-token, total-lps-to-unstake: (+ param-cycle-user-lp-tokens-to-unstake current-total-lp-tokens-to-unstake), current-cycles-to-unstake: updated-cycles-to-unstake}
            )
            ;; There are no rewards to claim
            fold-data
        )

    )
)

A private function that iterates through each staking cycle to determine the amount of LP tokens that can be unstaked. For every cycle that contains unstaked LP tokens and has not crossed the current cycle, the function updates the StakerDataPerCycleMap to set the lp-token-to-unstake field to zero. The overall tokens that can be unstaked are accumulated and returned along with the cycles that were checked.


Read-only Functions

These functions are read-only and are utilized for querying or retrieving data. While they can be invoked by external contracts or users, they do not alter the underlying state of the contract.

get-user-data

(define-read-only (get-user-data (x-token <og-sip-010-trait>) (y-token <susdt-sip-010-trait>) (lp-token <lp-trait>) (user principal)) 
    (map-get? StakerDataMap {x-token: (contract-of x-token), y-token: (contract-of y-token), lp-token: (contract-of lp-token), user: user})
)

A read-only function that retrieves the staking data for a specified principal given the x-token, y-token, and lp-token parameters. It maps and fetches the data from the StakerDataMap and returns it.

get-user-data-at-cycle

(define-read-only (get-user-data-at-cycle (x-token <og-sip-010-trait>) (y-token <susdt-sip-010-trait>) (lp-token <lp-trait>) (user principal) (cycle uint)) 
    (map-get? StakerDataPerCycleMap {x-token: (contract-of x-token), y-token: (contract-of y-token), lp-token: (contract-of lp-token), user: user, cycle: cycle})
)

A read-only function that obtains the staking data for a specific principal at a given cycle, considering the x-token, y-token, and lp-token parameters. It sources the information from the StakerDataPerCycleMap based on the provided parameters.

get-data-at-cycle

(define-read-only (get-data-at-cycle (x-token <og-sip-010-trait>) (y-token <susdt-sip-010-trait>) (lp-token <lp-trait>) (cycle uint)) 
    (map-get? DataPerCycleMap {x-token: (contract-of x-token), y-token: (contract-of y-token), lp-token: (contract-of lp-token), cycle: cycle})
)

A read-only function that fetches the overall staking data at a specified cycle for the given x-token, y-token, and lp-token parameters. The data is extracted from the DataPerCycleMap for the given cycle.

get-total-staked

(define-read-only (get-total-staked (x-token <og-sip-010-trait>) (y-token <susdt-sip-010-trait>) (lp-token <lp-trait>)) 
    (map-get? TotalStakedPerPairMap {x-token: (contract-of x-token), y-token: (contract-of y-token), lp-token: (contract-of lp-token)})
)

A read-only function that retrieves the total amount of x-token, y-token, and lp-token staked. The data is sourced from the TotalStakedPerPairMap.

get-staking-rewards-at-cycle

(define-read-only (get-staking-rewards-at-cycle (x-token principal) (y-token principal) (lp-token principal) (cycle uint))
    (let 
        (
            (param-cycle-user-data (unwrap! (map-get? StakerDataPerCycleMap {x-token: x-token, y-token: y-token, lp-token: lp-token, user: tx-sender, cycle: cycle}) (err u4)))
            (param-cycle-reward-claimed (get reward-claimed param-cycle-user-data))
            (param-cycle-user-lp-staked (get lp-token-staked param-cycle-user-data))
            (param-cycle-total-lp-staked (unwrap! (map-get? DataPerCycleMap {x-token: x-token, y-token: y-token, lp-token: lp-token, cycle: cycle}) (err u5)))
            (param-cycle-fees (unwrap! (contract-call? .stableswap-usda-susdt get-cycle-data x-token y-token lp-token cycle) (err u0)))
            (param-cycle-balance-x-fee (get cycle-fee-balance-x param-cycle-fees))
            (param-cycle-balance-y-fee (get cycle-fee-balance-y param-cycle-fees))
            (param-cycle-x-rewards (/ (* param-cycle-balance-x-fee param-cycle-user-lp-staked) param-cycle-total-lp-staked))
            (param-cycle-y-rewards (/ (* param-cycle-balance-y-fee param-cycle-user-lp-staked) param-cycle-total-lp-staked))
            (claimer tx-sender)
        )

        ;; Assert that param-cycle-x or param-cycle-y rewards are greater than 0
        (asserts! (or (> param-cycle-x-rewards u0) (> param-cycle-y-rewards u0)) (err u1))

        ;; Assert that param-cycle-reward-claimed is false
        (asserts! (not param-cycle-reward-claimed) (err u2))

        (ok {x-token-reward: param-cycle-x-rewards, y-token-reward: param-cycle-y-rewards})
    )
)

A read-only function that calculates and returns the staking rewards for the invoking user (tx-sender) at a given cycle. It considers x-token and y-token and computes the rewards based on the fees, the user's lp-token staked amount, and the total LP tokens staked during the cycle.

Last updated