Source Code

;; @contract Stacking Delegates
;; @version 1
;;
;; Delegate contracts in the protocol are difficult to replace as they are activily delegating/stacking. 
;; So we want to keep the delegate contract itself as simple as possible.
;; This contract adds some extra logic for the delegates.
;;
;; There are 4 important functions: delegate, revoke, handle-excess and handle-rewards.

(use-trait reserve-trait .reserve-trait-v1.reserve-trait)
(use-trait stacking-delegate-trait .stacking-delegate-trait-v1.stacking-delegate-trait)
(use-trait rewards-trait .rewards-trait-v1.rewards-trait)

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

(define-constant ERR_DELEGATE_AMOUNT_LOCKED u201001)

;;-------------------------------------
;; Maps
;;-------------------------------------

;; Delegate to last selected pool
(define-map last-selected-pool principal principal)

;; Delegate to target locked amount
(define-map target-locked-amount principal uint)

;; Delegate to last updated locked amount
(define-map last-locked-amount principal uint)

;; Delegate to last updated unlocked amount
(define-map last-unlocked-amount principal uint)

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


(define-read-only (get-last-selected-pool (delegate principal))
  (default-to
    .stacking-pool-v1
    (map-get? last-selected-pool delegate)
  )
)

(define-read-only (get-target-locked-amount (delegate principal))
  (default-to
    u0
    (map-get? target-locked-amount delegate)
  )
)

(define-read-only (get-last-locked-amount (delegate principal))
  (default-to
    u0
    (map-get? last-locked-amount delegate)
  )
)

(define-read-only (get-last-unlocked-amount (delegate principal))
  (default-to
    u0
    (map-get? last-unlocked-amount delegate)
  )
)

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

(define-read-only (get-stx-account (account principal))
  (stx-account account)
)

;;-------------------------------------
;; Reserve Wrappers
;;-------------------------------------

(define-private (request-stx-to-stack (delegate <stacking-delegate-trait>) (reserve <reserve-trait>) (amount uint))
  (begin
    (try! (as-contract (contract-call? delegate request-stx-to-stack reserve amount)))

    (map-set last-locked-amount (contract-of delegate) (get locked (get-stx-account (contract-of delegate))))
    ;; Need to add and not set to current amount, as rewards must still be calculated correctly
    (map-set last-unlocked-amount (contract-of delegate) (+ (get-last-unlocked-amount (contract-of delegate)) amount))

    (print { action: "request-stx-to-stack", data: { delegate: (contract-of delegate), amount: amount, block-height: block-height } })
    (ok true)
  )
)

(define-private (return-stx-from-stacking (delegate  <stacking-delegate-trait>) (reserve <reserve-trait>) (amount uint))
  (begin
    (try! (as-contract (contract-call? delegate return-stx-from-stacking reserve amount)))

    (map-set last-locked-amount (contract-of delegate) (get locked (get-stx-account (contract-of delegate))))
    ;; Need to subtract and not set to current amount, as rewards must still be calculated correctly
    (map-set last-unlocked-amount (contract-of delegate) (- (get-last-unlocked-amount (contract-of delegate)) amount))

    (print { action: "return-stx-from-stacking", data: { delegate: (contract-of delegate), amount: amount, block-height: block-height } })
    (ok true)
  )
)


;;-------------------------------------
;; Handle rewards
;;-------------------------------------

(define-read-only (calculate-rewards (delegate principal)) 
  (let (
    (last-locked (get-last-locked-amount delegate))
    (last-unlocked (get-last-unlocked-amount delegate))

    (locked-amount (get locked (get-stx-account delegate)))
    (unlocked-amount (get unlocked (get-stx-account delegate)))

    ;; Extra STX must be rewards
    (rewards (if (> (+ locked-amount unlocked-amount) (+ last-locked last-unlocked))
      (- (+ locked-amount unlocked-amount) (+ last-locked last-unlocked))
      u0
    ))
  )
    rewards
  )
)

;; If extra STX in (contract + locked) it means rewards were added
(define-public (handle-rewards (delegate <stacking-delegate-trait>) (reserve <reserve-trait>) (rewards-contract <rewards-trait>))
  (let (
    (rewards (calculate-rewards (contract-of delegate)))
  )
    (try! (contract-call? .dao check-is-protocol (contract-of delegate)))
    (try! (contract-call? .dao check-is-protocol (contract-of reserve)))
    (try! (contract-call? .dao check-is-protocol (contract-of rewards-contract)))

    (try! (as-contract (contract-call? delegate handle-rewards (get-last-selected-pool (contract-of delegate)) rewards rewards-contract)))

    (print { action: "handle-excess", data: { delegate: (contract-of delegate), rewards: rewards, block-height: block-height } })
    (ok rewards)
  )
)

;;-------------------------------------
;; Handle excess amount
;;-------------------------------------

(define-read-only (calculate-excess (delegate principal)) 
  (let (
    (locked-amount (get locked (get-stx-account delegate)))
    (unlocked-amount (get unlocked (get-stx-account delegate)))
    (rewards-amount (calculate-rewards delegate))

    (target-amount (get-target-locked-amount delegate))
    (total-amount (if (> (+ locked-amount unlocked-amount) rewards-amount)
      (- (+ locked-amount unlocked-amount) rewards-amount)
      u0
    ))
    (excess-amount (if (> total-amount target-amount)
      (- total-amount target-amount)
      u0
    ))
  )
    (if (> excess-amount u0)
      (if (> unlocked-amount excess-amount)
        excess-amount
        unlocked-amount
      )
      u0
    )
  )
)

;; If target amount is lower than (contract + locked)
;; we can return the STX held by the contract
(define-public (handle-excess (delegate <stacking-delegate-trait>) (reserve <reserve-trait>))
  (let (
    (excess (calculate-excess (contract-of delegate)))
  )
    (try! (contract-call? .dao check-is-protocol (contract-of delegate)))
    (try! (contract-call? .dao check-is-protocol (contract-of reserve)))

    ;; Not needed STX to reserve
    (if (> excess u0)
      (try! (as-contract (return-stx-from-stacking delegate reserve excess)))
      true
    )

    (print { action: "handle-excess", data: { delegate: (contract-of delegate), excess: excess, block-height: block-height } })
    (ok excess)
  )
)


;;-------------------------------------
;; Delegation 
;;-------------------------------------

(define-public (revoke (delegate <stacking-delegate-trait>) (reserve <reserve-trait>) (rewards-contract <rewards-trait>))
  (begin 
    ;; Need to be done first
    (try! (handle-rewards delegate reserve rewards-contract))

    (try! (contract-call? .dao check-is-protocol contract-caller))
    (try! (contract-call? .dao check-is-protocol (contract-of delegate)))
    (try! (contract-call? .dao check-is-protocol (contract-of reserve)))
    (try! (contract-call? .dao check-is-protocol (contract-of rewards-contract)))

    (let (
      (unlocked-amount (get unlocked (get-stx-account (contract-of delegate))))
    )
      ;; Revoke
      (try! (contract-call? delegate revoke-delegate-stx))

      ;; Return STX
      (if (> unlocked-amount u0)
        (try! (as-contract (return-stx-from-stacking delegate reserve unlocked-amount)))
        true
      )

      ;; Set target
      (map-set target-locked-amount (contract-of delegate) u0)

      (print { action: "revoke", data: { delegate: (contract-of delegate), block-height: block-height } })
      (ok true)
    )
  )
)

(define-public (revoke-and-delegate (delegate <stacking-delegate-trait>) (reserve <reserve-trait>) (rewards-contract <rewards-trait>) (amount-ustx uint) (delegate-to principal) (until-burn-ht uint))
  (begin
    ;; Need to be done first
    (try! (handle-rewards delegate reserve rewards-contract))

    ;; Revoke
    (try! (contract-call? delegate revoke-delegate-stx))

    (try! (contract-call? .dao check-is-protocol contract-caller))
    (try! (contract-call? .dao check-is-protocol (contract-of delegate)))
    (try! (contract-call? .dao check-is-protocol (contract-of reserve)))
    (try! (contract-call? .dao check-is-protocol (contract-of rewards-contract)))

    (let (
      (locked-amount (get locked (get-stx-account (contract-of delegate))))
      (unlocked-amount (get unlocked (get-stx-account (contract-of delegate))))
    )
      (asserts! (>= amount-ustx locked-amount) (err ERR_DELEGATE_AMOUNT_LOCKED))

      ;; Request STX from reserve if needed
      (if (> amount-ustx (+ unlocked-amount locked-amount))
        (try! (as-contract (request-stx-to-stack delegate reserve (- amount-ustx (+ unlocked-amount locked-amount)))))
        true
      )

      ;; Delegate STX
      (try! (contract-call? delegate delegate-stx amount-ustx delegate-to (some until-burn-ht)))

      ;; Set target
      (map-set target-locked-amount (contract-of delegate) amount-ustx)
      (map-set last-selected-pool (contract-of delegate) delegate-to)

      ;; Handle excess
      (try! (handle-excess delegate reserve))

      (print { action: "revoke-and-delegate", data: { delegate: (contract-of delegate), amount: amount-ustx, delegate-to: delegate-to, until-burn-ht: until-burn-ht, block-height: block-height } })
      (ok true)
    )
  )
)

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

;; In case something goes wrong
(define-public (update-amounts (delegate principal) (target-locked uint) (last-locked uint) (last-unlocked uint))
  (begin
    (try! (contract-call? .dao check-is-protocol contract-caller))

    (map-set target-locked-amount delegate target-locked)
    (map-set last-locked-amount delegate last-locked)
    (map-set last-unlocked-amount delegate last-unlocked)
    (ok true)
  )
)

Functions (14)

FunctionAccessArgs
get-last-selected-poolread-onlydelegate: principal
get-target-locked-amountread-onlydelegate: principal
get-last-locked-amountread-onlydelegate: principal
get-last-unlocked-amountread-onlydelegate: principal
get-stx-accountread-onlyaccount: principal
request-stx-to-stackprivatedelegate: <stacking-delegate-trait>, reserve: <reserve-trait>, amount: uint
return-stx-from-stackingprivatedelegate: <stacking-delegate-trait>, reserve: <reserve-trait>, amount: uint
calculate-rewardsread-onlydelegate: principal
handle-rewardspublicdelegate: <stacking-delegate-trait>, reserve: <reserve-trait>, rewards-contract: <rewards-trait>
calculate-excessread-onlydelegate: principal
handle-excesspublicdelegate: <stacking-delegate-trait>, reserve: <reserve-trait>
revokepublicdelegate: <stacking-delegate-trait>, reserve: <reserve-trait>, rewards-contract: <rewards-trait>
revoke-and-delegatepublicdelegate: <stacking-delegate-trait>, reserve: <reserve-trait>, rewards-contract: <rewards-trait>, amount-ustx: uint, delegate-to: principal, until-burn-ht: uint
update-amountspublicdelegate: principal, target-locked: uint, last-locked: uint, last-unlocked: uint