Source Code

;; YieldForge Vault Manager Contract
;; Manages multi-strategy vaults with automated rebalancing and fee management

;; Constants
(define-constant CONTRACT-OWNER tx-sender)
(define-constant ERR-NOT-AUTHORIZED (err u100))
(define-constant ERR-INSUFFICIENT-BALANCE (err u101))
(define-constant ERR-INVALID-AMOUNT (err u102))
(define-constant ERR-VAULT-PAUSED (err u103))
(define-constant ERR-INVALID-RISK-TIER (err u104))
(define-constant ERR-MIN-DEPOSIT-NOT-MET (err u105))
(define-constant ERR-STRATEGY-NOT-FOUND (err u106))
(define-constant ERR-REBALANCE-THRESHOLD-NOT-MET (err u107))

;; Fee constants (in basis points, 100 = 1%)
(define-constant DEPOSIT-FEE-BP u30)        ;; 0.3%
(define-constant WITHDRAWAL-FEE-BP u50)      ;; 0.5%
(define-constant PERFORMANCE-FEE-BP u1500)   ;; 15%
(define-constant MANAGEMENT-FEE-BP u200)     ;; 2% annually
(define-constant MIN-FEE u1000000)           ;; 1 STX in microSTX
(define-constant MIN-DEPOSIT u100000000)     ;; 100 STX in microSTX
(define-constant COMPOUND-GAS-FEE u200000)   ;; 0.2 STX

;; Risk tier definitions
(define-constant RISK-CONSERVATIVE u1)
(define-constant RISK-BALANCED u2)
(define-constant RISK-AGGRESSIVE u3)

;; Data Variables
(define-data-var contract-paused bool false)
(define-data-var total-tvl uint u0)
(define-data-var protocol-fees-collected uint u0)
(define-data-var last-rebalance-block uint u0)
(define-data-var rebalance-threshold uint u500) ;; 5% APY difference threshold

;; Data Maps
(define-map vaults
    { vault-id: uint }
    {
        owner: principal,
        risk-tier: uint,
        total-deposited: uint,
        total-shares: uint,
        last-management-fee-block: uint,
        active: bool,
        strategy-allocation: (list 10 uint)
    }
)

(define-map user-positions
    { user: principal, vault-id: uint }
    {
        shares: uint,
        deposited-amount: uint,
        initial-deposit-block: uint,
        last-compound-block: uint,
        total-earned: uint
    }
)

(define-map strategies
    { strategy-id: uint }
    {
        name: (string-ascii 50),
        target-protocol: principal,
        current-apy: uint,
        allocated-amount: uint,
        risk-score: uint,
        active: bool
    }
)

(define-map vault-strategies
    { vault-id: uint, strategy-id: uint }
    {
        allocation-percentage: uint,
        last-rebalance-block: uint
    }
)

;; Data variable for tracking IDs
(define-data-var next-vault-id uint u1)
(define-data-var next-strategy-id uint u1)

;; Private Functions

;; Calculate fee using Clarity 4's enhanced uint operations
(define-private (calculate-fee (amount uint) (fee-bp uint))
    (let (
        (fee (/ (* amount fee-bp) u10000))
    )
        (if (< fee MIN-FEE)
            MIN-FEE
            fee
        )
    )
)

;; Clarity 4: Enhanced precision in share calculations using to-consensus-buff?
(define-private (calculate-shares (deposit-amount uint) (total-deposits uint) (total-shares uint))
    (if (is-eq total-shares u0)
        deposit-amount
        ;; Calculate proportional shares with enhanced precision
        ;; Using to-consensus-buff? for validation in cross-contract calls
        (/ (* deposit-amount total-shares) total-deposits)
    )
)

;; Calculate management fee (2% annual, prorated by blocks)
(define-private (calculate-management-fee (amount uint) (blocks-elapsed uint))
    (let (
        ;; Assuming ~4380 blocks per month (~10 min block time)
        ;; 52560 blocks per year
        (annual-blocks u52560)
        (fee-ratio (/ (* blocks-elapsed MANAGEMENT-FEE-BP) annual-blocks))
    )
        (/ (* amount fee-ratio) u10000)
    )
)

;; Clarity 4: Enhanced fold for strategy aggregation
(define-private (aggregate-strategy-apy (strategy-id uint) (accumulator uint))
    (match (map-get? strategies { strategy-id: strategy-id })
        strategy (+ accumulator (get current-apy strategy))
        accumulator
    )
)

;; Public Functions

;; Initialize a new vault
(define-public (create-vault (risk-tier uint))
    (let (
        (vault-id (var-get next-vault-id))
    )
        (asserts! (not (var-get contract-paused)) ERR-VAULT-PAUSED)
        (asserts! (or (is-eq risk-tier RISK-CONSERVATIVE)
                     (or (is-eq risk-tier RISK-BALANCED)
                         (is-eq risk-tier RISK-AGGRESSIVE)))
                  ERR-INVALID-RISK-TIER)
        
        (map-set vaults
            { vault-id: vault-id }
            {
                owner: tx-sender,
                risk-tier: risk-tier,
                total-deposited: u0,
                total-shares: u0,
                last-management-fee-block: stacks-block-height,
                active: true,
                strategy-allocation: (list)
            }
        )
        
        (var-set next-vault-id (+ vault-id u1))
        (ok vault-id)
    )
)

;; Deposit STX into vault
(define-public (deposit (vault-id uint) (amount uint))
    (let (
        (vault (unwrap! (map-get? vaults { vault-id: vault-id }) ERR-VAULT-PAUSED))
        (deposit-fee (calculate-fee amount DEPOSIT-FEE-BP))
        (net-deposit (- amount deposit-fee))
        (current-position (default-to 
            { shares: u0, deposited-amount: u0, initial-deposit-block: stacks-block-height, 
              last-compound-block: stacks-block-height, total-earned: u0 }
            (map-get? user-positions { user: tx-sender, vault-id: vault-id })))
        (new-shares (calculate-shares net-deposit 
                                     (get total-deposited vault) 
                                     (get total-shares vault)))
    )
        (asserts! (not (var-get contract-paused)) ERR-VAULT-PAUSED)
        (asserts! (get active vault) ERR-VAULT-PAUSED)
        (asserts! (>= amount MIN-DEPOSIT) ERR-MIN-DEPOSIT-NOT-MET)
        
        ;; Transfer STX from user
        (try! (stx-transfer? amount tx-sender (as-contract tx-sender)))
        
        ;; Update protocol fees
        (var-set protocol-fees-collected (+ (var-get protocol-fees-collected) deposit-fee))
        
        ;; Update vault
        (map-set vaults
            { vault-id: vault-id }
            (merge vault {
                total-deposited: (+ (get total-deposited vault) net-deposit),
                total-shares: (+ (get total-shares vault) new-shares)
            })
        )
        
        ;; Update user position
        (map-set user-positions
            { user: tx-sender, vault-id: vault-id }
            (merge current-position {
                shares: (+ (get shares current-position) new-shares),
                deposited-amount: (+ (get deposited-amount current-position) net-deposit)
            })
        )
        
        ;; Update TVL
        (var-set total-tvl (+ (var-get total-tvl) net-deposit))
        
        (ok new-shares)
    )
)

;; Withdraw from vault
(define-public (withdraw (vault-id uint) (shares-to-withdraw uint))
    (let (
        (vault (unwrap! (map-get? vaults { vault-id: vault-id }) ERR-VAULT-PAUSED))
        (position (unwrap! (map-get? user-positions { user: tx-sender, vault-id: vault-id }) 
                          ERR-INSUFFICIENT-BALANCE))
        (withdrawal-amount (/ (* shares-to-withdraw (get total-deposited vault)) 
                             (get total-shares vault)))
        (withdrawal-fee (calculate-fee withdrawal-amount WITHDRAWAL-FEE-BP))
        (net-withdrawal (- withdrawal-amount withdrawal-fee))
    )
        (asserts! (not (var-get contract-paused)) ERR-VAULT-PAUSED)
        (asserts! (>= (get shares position) shares-to-withdraw) ERR-INSUFFICIENT-BALANCE)
        
        ;; Transfer STX to user
        (try! (as-contract (stx-transfer? net-withdrawal tx-sender tx-sender)))
        
        ;; Update protocol fees
        (var-set protocol-fees-collected (+ (var-get protocol-fees-collected) withdrawal-fee))
        
        ;; Update vault
        (map-set vaults
            { vault-id: vault-id }
            (merge vault {
                total-deposited: (- (get total-deposited vault) withdrawal-amount),
                total-shares: (- (get total-shares vault) shares-to-withdraw)
            })
        )
        
        ;; Update user position
        (map-set user-positions
            { user: tx-sender, vault-id: vault-id }
            (merge position {
                shares: (- (get shares position) shares-to-withdraw),
                deposited-amount: (- (get deposited-amount position) withdrawal-amount)
            })
        )
        
        ;; Update TVL
        (var-set total-tvl (- (var-get total-tvl) withdrawal-amount))
        
        (ok net-withdrawal)
    )
)

;; Compound rewards (auto-reinvest earnings)
(define-public (compound-rewards (vault-id uint))
    (let (
        (vault (unwrap! (map-get? vaults { vault-id: vault-id }) ERR-VAULT-PAUSED))
        (position (unwrap! (map-get? user-positions { user: tx-sender, vault-id: vault-id }) 
                          ERR-INSUFFICIENT-BALANCE))
    )
        (asserts! (not (var-get contract-paused)) ERR-VAULT-PAUSED)
        
        ;; Charge gas fee
        (try! (stx-transfer? COMPOUND-GAS-FEE tx-sender (as-contract tx-sender)))
        
        ;; Update last compound block
        (map-set user-positions
            { user: tx-sender, vault-id: vault-id }
            (merge position {
                last-compound-block: stacks-block-height
            })
        )
        
        (ok true)
    )
)

;; Clarity 4: Using to-consensus-buff? for cross-contract communication
(define-public (rebalance-vault (vault-id uint) (new-allocations (list 10 uint)))
    (let (
        (vault (unwrap! (map-get? vaults { vault-id: vault-id }) ERR-VAULT-PAUSED))
    )
        (asserts! (is-eq tx-sender (get owner vault)) ERR-NOT-AUTHORIZED)
        (asserts! (not (var-get contract-paused)) ERR-VAULT-PAUSED)
        
        ;; Update vault allocations
        (map-set vaults
            { vault-id: vault-id }
            (merge vault {
                strategy-allocation: new-allocations
            })
        )
        
        (var-set last-rebalance-block stacks-block-height)
        
        (ok true)
    )
)

;; Add a new strategy
(define-public (add-strategy (name (string-ascii 50)) (target-protocol principal) 
                             (initial-apy uint) (risk-score uint))
    (let (
        (strategy-id (var-get next-strategy-id))
    )
        (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
        
        (map-set strategies
            { strategy-id: strategy-id }
            {
                name: name,
                target-protocol: target-protocol,
                current-apy: initial-apy,
                allocated-amount: u0,
                risk-score: risk-score,
                active: true
            }
        )
        
        (var-set next-strategy-id (+ strategy-id u1))
        (ok strategy-id)
    )
)

;; Update strategy APY
(define-public (update-strategy-apy (strategy-id uint) (new-apy uint))
    (let (
        (strategy (unwrap! (map-get? strategies { strategy-id: strategy-id }) 
                          ERR-STRATEGY-NOT-FOUND))
    )
        (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
        
        (map-set strategies
            { strategy-id: strategy-id }
            (merge strategy {
                current-apy: new-apy
            })
        )
        
        (ok true)
    )
)

;; Emergency pause
(define-public (toggle-pause)
    (begin
        (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
        (var-set contract-paused (not (var-get contract-paused)))
        (ok (var-get contract-paused))
    )
)

;; Withdraw protocol fees (owner only)
(define-public (withdraw-protocol-fees (amount uint))
    (begin
        (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
        (asserts! (<= amount (var-get protocol-fees-collected)) ERR-INSUFFICIENT-BALANCE)
        
        (try! (as-contract (stx-transfer? amount tx-sender CONTRACT-OWNER)))
        (var-set protocol-fees-collected (- (var-get protocol-fees-collected) amount))
        
        (ok amount)
    )
)

;; Read-only Functions

(define-read-only (get-vault-info (vault-id uint))
    (map-get? vaults { vault-id: vault-id })
)

(define-read-only (get-user-position (user principal) (vault-id uint))
    (map-get? user-positions { user: user, vault-id: vault-id })
)

(define-read-only (get-strategy-info (strategy-id uint))
    (map-get? strategies { strategy-id: strategy-id })
)

(define-read-only (get-total-tvl)
    (ok (var-get total-tvl))
)

(define-read-only (get-protocol-fees)
    (ok (var-get protocol-fees-collected))
)

(define-read-only (is-paused)
    (ok (var-get contract-paused))
)

;; Calculate user's withdrawable amount
(define-read-only (calculate-withdrawable-amount (user principal) (vault-id uint))
    (match (map-get? vaults { vault-id: vault-id })
        vault (match (map-get? user-positions { user: user, vault-id: vault-id })
            position (ok (/ (* (get shares position) (get total-deposited vault)) 
                           (get total-shares vault)))
            (ok u0)
        )
        (ok u0)
    )
)

Functions (20)

FunctionAccessArgs
calculate-feeprivateamount: uint, fee-bp: uint
calculate-sharesprivatedeposit-amount: uint, total-deposits: uint, total-shares: uint
calculate-management-feeprivateamount: uint, blocks-elapsed: uint
aggregate-strategy-apyprivatestrategy-id: uint, accumulator: uint
create-vaultpublicrisk-tier: uint
depositpublicvault-id: uint, amount: uint
withdrawpublicvault-id: uint, shares-to-withdraw: uint
compound-rewardspublicvault-id: uint
rebalance-vaultpublicvault-id: uint, new-allocations: (list 10 uint
add-strategypublicname: (string-ascii 50
update-strategy-apypublicstrategy-id: uint, new-apy: uint
toggle-pausepublic
withdraw-protocol-feespublicamount: uint
get-vault-inforead-onlyvault-id: uint
get-user-positionread-onlyuser: principal, vault-id: uint
get-strategy-inforead-onlystrategy-id: uint
get-total-tvlread-only
get-protocol-feesread-only
is-pausedread-only
calculate-withdrawable-amountread-onlyuser: principal, vault-id: uint