Source Code

;; @contract Rewards
;; @version 1
;;
;; PoX rewards are added here via `add-rewards`. The pool owner can add the rewards
;; here, or transfer the rewards to the delegates. The delegates will make sure these rewards
;; are added here.
;;
;; Added rewards are kept in this contract until the end of the cycle. At the end of the
;; cycle the method `process-rewards` can be called to actually add the STX rewards to the reserve.
;;
;; We can not add rewards during the cycle, as a user could stack right before the rewards are added,
;; and unstack right after. His stSTX will become more valuable while his STX was never stacked.
;; To prevent this, rewards can only be added in the last blocks (withdraw offset) of the cycle.

(impl-trait .rewards-trait-v1.rewards-trait)
(use-trait commission-trait .commission-trait-v1.commission-trait)
(use-trait staking-trait .staking-trait-v1.staking-trait)
(use-trait reserve-trait .reserve-trait-v1.reserve-trait)

;;-------------------------------------
;; Constants 
;;-------------------------------------

(define-constant ERR_CAN_NOT_PROCESS_YET u203001)

(define-constant DENOMINATOR_BPS u10000)

;;-------------------------------------
;; Variables 
;;-------------------------------------

(define-data-var total-commission uint u0)
(define-data-var total-rewards-left uint u0)
(define-data-var rewards-unlock uint u0)

;;-------------------------------------
;; Getters
;;-------------------------------------

(define-read-only (get-total-commission)
  (var-get total-commission)
)

(define-read-only (get-total-rewards-left)
  (var-get total-rewards-left)
)

(define-read-only (get-rewards-unlock)
  (var-get rewards-unlock)
)

(define-read-only (next-rewards-unlock)
  (let (
    (start-block-next-cycle (reward-cycle-to-burn-height (+ (get-pox-cycle) u1)))
    (withdrawal-offset (contract-call? .data-core-v1 get-cycle-withdraw-offset))
  )
    (- start-block-next-cycle withdrawal-offset)
  )
)

;;-------------------------------------
;; Add rewards
;;-------------------------------------

;; Rewards might be transferred to the delegates. 
;; When executing a strategy, rewards in delegates are handled and added via `add-rewards`
;; Or they must be added manually by the pool owner via `add-rewards`
;; The `process-rewards` method can be called at the end of each cycle.

(define-public (add-rewards 
  (pool principal)
  (stx-amount uint) 
) 
  (let (
    (commission (contract-call? .data-pools-v1 get-pool-commission pool))
    (commission-amount (/ (* stx-amount commission) DENOMINATOR_BPS))
    (rewards-left (- stx-amount commission-amount))

    (pool-owner-commission (contract-call? .data-pools-v1 get-pool-owner-commission pool))
    (pool-owner-amount (/ (* commission-amount (get share pool-owner-commission)) DENOMINATOR_BPS))
    (protocol-amount (- commission-amount pool-owner-amount))
  )
    (var-set total-commission (+ (var-get total-commission) protocol-amount))
    (var-set total-rewards-left (+ (var-get total-rewards-left) rewards-left))

    (var-set rewards-unlock (next-rewards-unlock))

    (print { action: "add-rewards", data: { cycle: (get-pox-cycle), pool: pool, stx-amount: stx-amount, rewards-unlock: (var-get rewards-unlock), commission-amount: commission-amount, rewards-left: rewards-left, block-height: block-height } })
    (try! (stx-transfer? stx-amount tx-sender (as-contract tx-sender)))

    (if (> pool-owner-amount u0)
      (as-contract (stx-transfer? pool-owner-amount tx-sender (get receiver pool-owner-commission)))
      (ok true)
    )
  )
)

(define-public (process-rewards 
  (commission-contract <commission-trait>) 
  (staking-contract <staking-trait>) 
  (reserve <reserve-trait>) 
)
  (begin
    (try! (contract-call? .dao check-is-enabled))
    (try! (contract-call? .dao check-is-protocol (contract-of reserve)))
    (try! (contract-call? .dao check-is-protocol (contract-of commission-contract)))
    (try! (contract-call? .dao check-is-protocol (contract-of staking-contract)))
    (asserts! (> burn-block-height (var-get rewards-unlock)) (err ERR_CAN_NOT_PROCESS_YET))

    (if (> (var-get total-commission) u0)
      (try! (as-contract (contract-call? commission-contract add-commission staking-contract (var-get total-commission))))
      u0
    )

    (if (> (var-get total-rewards-left) u0)
      (try! (as-contract (stx-transfer? (var-get total-rewards-left) tx-sender (contract-of reserve))))
      false
    )

    (var-set total-commission u0)
    (var-set total-rewards-left u0)

    (print { action: "process-rewards", data: { cycle: (get-pox-cycle), commission-amount: (var-get total-commission), rewards-left: (var-get total-rewards-left), block-height: block-height } })
    (ok true)
  )
)

;;-------------------------------------
;; Admin
;;-------------------------------------

(define-public (get-stx (requested-stx uint) (receiver principal))
  (begin
    (try! (contract-call? .dao check-is-protocol contract-caller))

    (try! (as-contract (stx-transfer? requested-stx tx-sender receiver)))
    (ok requested-stx)
  )
)

;;-------------------------------------
;; PoX Helpers
;;-------------------------------------

(define-read-only (get-pox-cycle)
  (contract-call? 'SP000000000000000000002Q6VF78.pox-4 current-pox-reward-cycle)
)

(define-read-only (reward-cycle-to-burn-height (cycle-id uint)) 
  (contract-call? 'SP000000000000000000002Q6VF78.pox-4 reward-cycle-to-burn-height cycle-id)
)

Functions (7)

FunctionAccessArgs
get-total-commissionread-only
get-total-rewards-leftread-only
get-rewards-unlockread-only
next-rewards-unlockread-only
get-stxpublicrequested-stx: uint, receiver: principal
get-pox-cycleread-only
reward-cycle-to-burn-heightread-onlycycle-id: uint