googlier-stake-pool-gglr-v1-2

SP3HSPWMFYQS6S7BZ7KBNWMA059DV9S61X8EFSD6Y

Source Code

;; @contract gglr Stake Pool - Stake gglr to get stgglr
;; A fixed amount of rewards per block will be distributed across all stakers, according to their size in the pool
;; Rewards will be automatically staked before staking or unstaking. 
;; The cumm reward per stake represents the rewards over time, taking into account total staking volume over time
;; When total stake changes, the cumm reward per stake is increased accordingly.
;; The cooldown mechanism makes sure there is a period of 10 days before the user can unstake. 
;; Unstaking must happen within 2 days after the 10 days cooldown period.
;; @version 1.2

(impl-trait .googlier-stake-pool-trait-v1.stake-pool-trait)
(impl-trait .googlier-stake-pool-gglr-trait-v1.stake-pool-gglr-trait)
(use-trait ft-trait .sip-010-trait-ft-standard.sip-010-trait)
(use-trait stake-registry-trait .googlier-stake-registry-trait-v1.stake-registry-trait)

;; Errors
(define-constant ERR-NOT-AUTHORIZED (err u18401))
(define-constant ERR-REWARDS-CALC (err u18001))
(define-constant ERR-WRONG-TOKEN (err u18002))
(define-constant ERR-COOLDOWN-NOT-ENDED (err u18003))
(define-constant ERR-WRONG-REGISTRY (err u18004))

;; Constants
(define-constant POOL-TOKEN .googlier-token)

;; Variables
(define-data-var last-reward-add-block uint u0)

;; ---------------------------------------------------------
;; Migration
;; ---------------------------------------------------------

;; Set last rewards block
(define-public (set-last-reward-add-block (new-value uint))
  (begin
    (asserts! (is-eq tx-sender (contract-call? .googlier-dao get-dao-owner)) (err ERR-NOT-AUTHORIZED))
    (var-set last-reward-add-block new-value)
    (ok true)
  )
)

;; Migrate gglr from old to new contract
(define-public (migrate-gglr)
  (let (
    (gglr-supply-v1 (unwrap-panic (contract-call? .googlier-token get-balance .googlier-stake-pool-gglr-v1-1)))
  )
    (asserts! (is-eq tx-sender (contract-call? .googlier-dao get-dao-owner)) ERR-NOT-AUTHORIZED)
    
    ;; Burn gglr in pool V1, mint in V2
    (try! (as-contract (contract-call? .googlier-dao burn-token .googlier-token gglr-supply-v1 .googlier-stake-pool-gglr-v1-1)))
    (try! (as-contract (contract-call? .googlier-dao mint-token .googlier-token gglr-supply-v1 (as-contract tx-sender))))

    (ok gglr-supply-v1)
  )
)

;; ---------------------------------------------------------
;; Cooldown
;; ---------------------------------------------------------

;; Cooldown map
(define-map wallet-cooldown 
   { wallet: principal } 
   {
      redeem-period-start-block: uint,
      redeem-period-end-block: uint
   }
)

;; Get cooldown info for wallet
(define-read-only (get-cooldown-info-of (wallet principal))
  (default-to
    { redeem-period-start-block: u0, redeem-period-end-block: u0 }
    (map-get? wallet-cooldown { wallet: wallet })
  )
)

;; @desc start cooldown period of 10 days
;; @post uint; returns block number when redeem period of 2 days starts
(define-public (start-cooldown)
  (let (
    (redeem-period-start-block (+ block-height u1440)) ;; 1440 blocks = ~10 days
    (redeem-period-end-block (+ redeem-period-start-block u288)) ;; 288 blocks = ~2 days
  )
    (map-set wallet-cooldown { wallet: tx-sender } { 
      redeem-period-start-block: redeem-period-start-block,
      redeem-period-end-block: redeem-period-end-block 
    })
    (ok redeem-period-start-block)
  )
)


;; Check if cooldown ended
(define-read-only (wallet-can-redeem (wallet principal))
  (let (
    (wallet-cooldown-info (get-cooldown-info-of wallet))
    (redeem-period-start (get redeem-period-start-block wallet-cooldown-info))
    (redeem-period-end (get redeem-period-end-block wallet-cooldown-info))
  )
    (if (and (> block-height redeem-period-start) (< block-height redeem-period-end) (not (is-eq redeem-period-start u0)))
      true
      false
    )
  )
)


;; ---------------------------------------------------------
;; Stake Functions
;; ---------------------------------------------------------

;; Get variable last-reward-add-block
(define-read-only (get-last-reward-add-block)
  (var-get last-reward-add-block)
)

;; gglr (staked & rewards) over total supply of stgglr - Result with 6 decimals
(define-read-only (gglr-stgglr-ratio)
  (let (
    ;; Total stgglr supply
    (stgglr-supply (unwrap-panic (contract-call? .stgglr-token get-total-supply)))

    ;; Total gglr (staked + rewards)
    (gglr-supply (unwrap-panic (contract-call? .googlier-token get-balance (as-contract tx-sender))))
  )
    (if (is-eq stgglr-supply u0)
      (ok u1000000)
      (ok (/ (* gglr-supply u1000000) stgglr-supply))
    )
  )
)

;; @desc get amount of gglr to receive for given stgglr
;; @param registry-trait; current stake registry
;; @param amount; amount of stgglr tokens
;; @param stgglr-supply; total stgglr supply
;; @post uint; returns amount of gglr tokens
(define-public (gglr-for-stgglr (registry-trait <stake-registry-trait>) (amount uint) (stgglr-supply uint))
  (let (
    ;; gglr already in pool
    (gglr-supply (unwrap-panic (contract-call? .googlier-token get-balance (as-contract tx-sender))))

    ;; gglr still to be added to pool
    (rewards-to-add (calculate-pending-rewards-for-pool registry-trait))

    ;; Total gglr
    (total-gglr-supply (+ gglr-supply rewards-to-add))

    ;; User stgglr percentage
    (stgglr-percentage (/ (* amount u1000000000000) stgglr-supply))

    ;; Amount of gglr the user will receive
    (gglr-to-receive (/ (* stgglr-percentage total-gglr-supply) u1000000000000))
  )
    (ok gglr-to-receive)
  )
)

;; @desc get total amount of gglr in pool for staker, based on stgglr in wallet
;; @param registry-trait; current stake registry
;; @param staker; user for which we want to get total stake
;; @param stgglr-supply; total stgglr supply
;; @post uint; returns amount of gglr tokens the user would get when unstaking
(define-public (get-stake-of (registry-trait <stake-registry-trait>) (staker principal) (stgglr-supply uint))
  (let (
    ;; Sender stgglr balance
    (stgglr-balance (unwrap-panic (contract-call? .stgglr-token get-balance tx-sender)))
  )
    (if (> stgglr-balance u0)
      ;; Amount of gglr the user would receive when unstaking
      (ok (unwrap-panic (gglr-for-stgglr registry-trait stgglr-balance stgglr-supply)))
      (ok u0)
    )
  )
)

;; Get total amount of gglr in pool
(define-read-only (get-total-staked)
  (unwrap-panic (contract-call? .googlier-token get-balance (as-contract tx-sender)))
)

;; @desc stake tokens in the pool, used by stake-registry
;; @param registry-trait; current stake registry
;; @param token; token to stake
;; @param staker; user who wants to stake
;; @param amount; amount of tokens to stake
;; @post uint; returns amount of tokens staked
(define-public (stake (registry-trait <stake-registry-trait>) (token <ft-trait>) (staker principal) (amount uint))
  (begin
    (asserts! (is-eq contract-caller (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "stake-registry"))) ERR-NOT-AUTHORIZED)
    (asserts! (is-eq POOL-TOKEN (contract-of token)) ERR-WRONG-TOKEN)

    ;; Add pending rewards to pool
    (try! (add-rewards-to-pool registry-trait))

    (let (
      ;; gglr/stgglr 
      (gglr-stgglr (unwrap-panic (gglr-stgglr-ratio)))

      ;; Calculate amount of stgglr to receive
      (stgglr-to-receive (/ (* amount u1000000) gglr-stgglr))
    )
      ;; Mint stgglr
      (try! (contract-call? .googlier-dao mint-token .stgglr-token stgglr-to-receive staker))

      ;; Transfer gglr to this contract
      (try! (contract-call? .googlier-token transfer amount staker (as-contract tx-sender) none))

      (ok stgglr-to-receive)
    )
  )
)

;; @desc unstake tokens in the pool, used by stake-registry
;; @param registry-trait; current stake registry
;; @param token; token to unstake
;; @param staker; user who wants to unstake
;; @param amount; amount of tokens to unstake
;; @post uint; returns amount of tokens unstaked
(define-public (unstake (registry-trait <stake-registry-trait>) (token <ft-trait>) (staker principal) (amount uint))
  (begin
    (asserts! (is-eq contract-caller (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "stake-registry"))) ERR-NOT-AUTHORIZED)
    (asserts! (is-eq POOL-TOKEN (contract-of token)) ERR-WRONG-TOKEN)
    (asserts! (is-eq (wallet-can-redeem staker) true) ERR-COOLDOWN-NOT-ENDED)

    ;; Add pending rewards to pool
    (try! (add-rewards-to-pool registry-trait))

    (let (
      ;; Amount of gglr the user will receive
      (gglr-to-receive (unwrap-panic (gglr-for-stgglr registry-trait amount (unwrap-panic (contract-call? .stgglr-token get-total-supply)))))
    )
      ;; Burn stgglr 
      (try! (contract-call? .googlier-dao burn-token .stgglr-token amount staker))

      ;; Transfer gglr back from this contract to the user
      (try! (as-contract (contract-call? .googlier-token transfer gglr-to-receive tx-sender staker none)))

      (ok gglr-to-receive)
    )
  )
)

;; @desc add pending gglr rewards to the pool
;; @param registry-trait; current stake registry
;; @post uint; returns amount of rewards added
(define-public (add-rewards-to-pool (registry-trait <stake-registry-trait>))
  (let (
    (rewards-to-add (calculate-pending-rewards-for-pool registry-trait))
    (deactivated-block (unwrap-panic (contract-call? registry-trait get-pool-deactivated-block .googlier-stake-pool-gglr-v1-2)))
  )
    (asserts! (is-eq (contract-of registry-trait) (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "stake-registry"))) ERR-WRONG-REGISTRY)
    (asserts! (> block-height (var-get last-reward-add-block)) (ok u0))

    ;; Rewards to add can be 0 if called multiple times in same block
    ;; Do not mint if pool deactivated
    (if (or (is-eq rewards-to-add u0) (not (is-eq deactivated-block u0)))
      false
      (try! (contract-call? .googlier-dao mint-token .googlier-token rewards-to-add (as-contract tx-sender)))
    )

    ;; Update block number
    (var-set last-reward-add-block (get height (get-last-block-height registry-trait)))

    (ok rewards-to-add)
  )
)

;; Amount of rewards still to be added to the pool
;; This is an approximation as the rewards per block change every block
(define-private (calculate-pending-rewards-for-pool (registry-trait <stake-registry-trait>))
  (let (
    (rewards-per-block (unwrap-panic (contract-call? registry-trait get-rewards-per-block-for-pool .googlier-stake-pool-gglr-v1-2)))
    (last-block-info (get-last-block-height registry-trait))
    (block-diff (if (> (get height last-block-info) (var-get last-reward-add-block))
      (- (get height last-block-info) (var-get last-reward-add-block))
      u0
    ))
    (rewards-to-add (* rewards-per-block block-diff))
  )
    ;; Rewards to add can be 0 if called multiple times in same block
    ;; Do not mint if pool deactivated
    (if (or (is-eq rewards-to-add u0) (is-eq false (get pool-active last-block-info)))
      u0
      rewards-to-add
    )
  )
)

;; Return current block height, or block height when pool was deactivated
(define-private (get-last-block-height (registry-trait <stake-registry-trait>))
  (let (
    (deactivated-block (unwrap-panic (contract-call? registry-trait get-pool-deactivated-block .googlier-stake-pool-gglr-v1-2)))
    (pool-active (is-eq deactivated-block u0))
  )
    (if (is-eq pool-active true)
      { height: block-height, pool-active: true }
      { height: deactivated-block, pool-active: false }
    )
  )
)

;; @desc execute slash with given percentage
;; @param percentage; percentage to slash
;; @post uint; returns total tokens removed from pool
(define-public (execute-slash (percentage uint))
  (let (
    (gglr-supply (unwrap-panic (contract-call? .googlier-token get-balance (as-contract tx-sender))))
    (slash-total (/ (* gglr-supply percentage) u100))
    (dao-owner (contract-call? .googlier-dao get-dao-owner))
  )
    (asserts! (is-eq contract-caller (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "gglr-slash"))) ERR-NOT-AUTHORIZED)
    (try! (as-contract (contract-call? .googlier-token transfer slash-total tx-sender dao-owner none)))
    (ok slash-total)
  )
)

;; Needed because of pool trait
(define-public (claim-pending-rewards (registry-trait <stake-registry-trait>) (staker principal))
  (ok u0)
)

;; Needed because of pool trait
(define-public (get-pending-rewards (registry-trait <stake-registry-trait>) (staker principal))
  (ok u0)
)

Functions (18)

FunctionAccessArgs
set-last-reward-add-blockpublicnew-value: uint
migrate-gglrpublic
get-cooldown-info-ofread-onlywallet: principal
start-cooldownpublic
wallet-can-redeemread-onlywallet: principal
get-last-reward-add-blockread-only
gglr-stgglr-ratioread-only
gglr-for-stgglrpublicregistry-trait: <stake-registry-trait>, amount: uint, stgglr-supply: uint
get-stake-ofpublicregistry-trait: <stake-registry-trait>, staker: principal, stgglr-supply: uint
get-total-stakedread-only
stakepublicregistry-trait: <stake-registry-trait>, token: <ft-trait>, staker: principal, amount: uint
unstakepublicregistry-trait: <stake-registry-trait>, token: <ft-trait>, staker: principal, amount: uint
add-rewards-to-poolpublicregistry-trait: <stake-registry-trait>
calculate-pending-rewards-for-poolprivateregistry-trait: <stake-registry-trait>
get-last-block-heightprivateregistry-trait: <stake-registry-trait>
execute-slashpublicpercentage: uint
claim-pending-rewardspublicregistry-trait: <stake-registry-trait>, staker: principal
get-pending-rewardspublicregistry-trait: <stake-registry-trait>, staker: principal