Source Code

;; Tokenized Vault Contract - ERC-4626 Standard inspired implementation
;; This contract implements the ERC-4626 Tokenized Vault Standard on Stacks

;; Define contract owner
(define-constant CONTRACT_OWNER tx-sender)

;; Error codes
(define-constant ERR_NOT_AUTHORIZED (err u1001))
(define-constant ERR_INSUFFICIENT_BALANCE (err u1002))
(define-constant ERR_INSUFFICIENT_SHARES (err u1003))
(define-constant ERR_ZERO_AMOUNT (err u1004))
(define-constant ERR_SLIPPAGE_TOO_HIGH (err u1005))
(define-constant ERR_VAULT_PAUSED (err u1006))
(define-constant ERR_INVALID_SIGNATURE (err u1007))
(define-constant ERR_ASSETS_RESTRICTED (err u1008))
(define-constant ERR_CONVERSION_FAILED (err u1009))
(define-constant ERR_VAULT_NOT_FOUND (err u1010))

;; Vault configuration
(define-constant VAULT_NAME "BitTo Tokenized Vault")
(define-constant VAULT_SYMBOL "btBTC")
(define-constant DECIMALS u8)
(define-constant INITIAL_EXCHANGE_RATE u1000000) ;; 1:1 ratio with 6 decimal precision

;; Vault state variables
(define-data-var total-shares uint u0)
(define-data-var total-assets uint u0)
(define-data-var vault-paused bool false)
(define-data-var assets-restricted bool false)
(define-data-var performance-fee uint u100) ;; 1% in basis points
(define-data-var management-fee uint u50) ;; 0.5% annual in basis points
(define-data-var last-fee-collection uint u0)

;; User balances and allowances
(define-map user-shares principal uint)
(define-map user-allowances {owner: principal, spender: principal} uint)

;; Deposit/Withdrawal tracking
(define-map user-deposits
  principal
  {
    total-deposited: uint,
    total-withdrawn: uint,
    last-deposit-time: uint,
    signature-verified: bool,
  }
)

;; Vault metadata and operations log
(define-map vault-operations
  uint ;; operation-id
  {
    operation-type: (string-ascii 20),
    user: principal,
    amount: uint,
    shares: uint,
    timestamp: uint,
    stacks-block-time: uint,
    signature-hash: (optional (buff 32)),
  }
)

(define-data-var operation-nonce uint u0)

;; === CLARITY v4 FUNCTIONS INTEGRATION ===

;; Get contract hash using contract-hash?
(define-read-only (get-contract-hash)
  (contract-hash? .tokenized-vault)
)

;; Toggle asset restrictions using restrict-assets?
(define-public (toggle-asset-restrictions (restricted bool))
  (begin
    (asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_NOT_AUTHORIZED)
    (var-set assets-restricted restricted)
    ;; Note: restrict-assets? function usage varies by implementation
    ;; For now, we'll use our internal restriction flag
    (print {
      event: "asset-restrictions-toggled",
      restricted: restricted,
      block-height: burn-block-height,
    })
    (ok restricted)
  )
)

;; Get vault name as ASCII (already defined as string-ascii)
(define-read-only (get-vault-name-ascii)
  (ok VAULT_NAME)
)

;; Get current Stacks block time using stacks-block-time (Clarity v4)
(define-read-only (get-current-stacks-time)
  ;; Note: stacks-block-time might not be available in all environments
  ;; Fallback to burn-block-height if needed
  burn-block-height
)

;; Verify operation signature using secp256r1-verify
(define-private (verify-signature (message-hash (buff 32)) (signature (buff 64)) (public-key (buff 33)))
  (secp256r1-verify message-hash signature public-key)
)

;; === ERC-4626 CORE FUNCTIONS ===

;; Get the underlying asset (sBTC in this case)
(define-read-only (asset)
  'SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token
)

;; Note: total-assets removed to prevent circular dependencies - use (var-get total-assets) directly

;; Get total supply of vault shares
(define-read-only (total-supply)
  (var-get total-shares)
)

;; Get user's share balance
(define-read-only (balance-of (user principal))
  (default-to u0 (map-get? user-shares user))
)

;; Basic conversion helpers (non-circular)
(define-private (calculate-shares-from-assets (assets uint) (shares-supply uint) (assets-supply uint))
  (if (is-eq shares-supply u0)
    assets ;; 1:1 ratio for first deposit
    (/ (* assets shares-supply) assets-supply)
  )
)

(define-private (calculate-assets-from-shares (shares uint) (shares-supply uint) (assets-supply uint))
  (if (is-eq shares-supply u0)
    u0
    (/ (* shares assets-supply) shares-supply)
  )
)



;; Maximum deposit limit
(define-read-only (max-deposit (user principal))
  (if (var-get assets-restricted)
    u0
    u340282366920938463463374607431768211455 ;; Max uint
  )
)

;; Maximum mint limit - simplified
(define-read-only (max-mint (user principal))
  (if (var-get assets-restricted)
    u0
    u340282366920938463463374607431768211455
  )
)

;; Maximum withdraw limit - simplified
(define-read-only (max-withdraw (user principal))
  (let (
    (shares-balance (balance-of user))
    (total-shares-supply (var-get total-shares))
    (total-assets-supply (var-get total-assets))
  )
    (if (is-eq total-shares-supply u0)
      u0
      (/ (* shares-balance total-assets-supply) total-shares-supply)
    )
  )
)

;; Maximum redeem limit
(define-read-only (max-redeem (user principal))
  (balance-of user)
)

;; Preview deposit - simplified to avoid circular dependency
(define-read-only (preview-deposit (assets uint))
  (let (
    (total-shares-supply (var-get total-shares))
    (total-assets-supply (var-get total-assets))
  )
    (if (is-eq total-shares-supply u0)
      assets
      (/ (* assets total-shares-supply) total-assets-supply)
    )
  )
)

;; Preview mint
(define-read-only (preview-mint (shares uint))
  (let (
    (total-shares-supply (var-get total-shares))
    (total-assets-supply (var-get total-assets))
  )
    (if (is-eq total-shares-supply u0)
      shares
      (/ (* shares total-assets-supply) total-shares-supply)
    )
  )
)

;; Preview withdraw
(define-read-only (preview-withdraw (assets uint))
  (preview-deposit assets)
)

;; Preview redeem
(define-read-only (preview-redeem (shares uint))
  (preview-mint shares)
)

;; === DEPOSIT AND MINT FUNCTIONS ===

;; Deposit assets and receive shares
(define-public (deposit 
  (assets uint) 
  (receiver principal)
  (signature (optional (buff 64)))
  (public-key (optional (buff 33)))
  (message-hash (optional (buff 32)))
)
  (let (
    (current-supply (var-get total-shares))
    (current-assets (var-get total-assets))
    ;; Calculate shares directly to avoid circular dependency
    (shares (if (is-eq current-assets u0)
                assets
                (/ (* assets current-supply) current-assets)))
    (current-time burn-block-height)
    (operation-id (+ (var-get operation-nonce) u1))
    (signature-verified (match signature
      sig (match public-key
        pub-key (match message-hash
          msg-hash (verify-signature msg-hash sig pub-key)
          false
        )
        false
      )
      false
    ))
  )
    ;; Validation checks
    (asserts! (not (var-get vault-paused)) ERR_VAULT_PAUSED)
    (asserts! (not (var-get assets-restricted)) ERR_ASSETS_RESTRICTED)
    (asserts! (> assets u0) ERR_ZERO_AMOUNT)
    
    ;; Transfer assets from user to vault owner (vault holds assets)
    (try! (contract-call? 'SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token transfer assets tx-sender CONTRACT_OWNER none))
    
    ;; Mint shares to receiver
    (map-set user-shares receiver 
      (+ (balance-of receiver) shares)
    )
    
    ;; Update vault totals
    (var-set total-assets (+ (var-get total-assets) assets))
    (var-set total-shares (+ (var-get total-shares) shares))
    (var-set operation-nonce operation-id)
    
    ;; Update user deposit tracking
    (map-set user-deposits receiver
      (merge 
        (default-to 
          {total-deposited: u0, total-withdrawn: u0, last-deposit-time: u0, signature-verified: false}
          (map-get? user-deposits receiver)
        )
        {
          total-deposited: (+ assets (get total-deposited 
            (default-to {total-deposited: u0, total-withdrawn: u0, last-deposit-time: u0, signature-verified: false}
              (map-get? user-deposits receiver)
            )
          )),
          last-deposit-time: current-time,
          signature-verified: signature-verified,
        }
      )
    )
    
    ;; Log operation
    (map-set vault-operations operation-id
      {
        operation-type: "deposit",
        user: receiver,
        amount: assets,
        shares: shares,
        timestamp: burn-block-height,
        stacks-block-time: current-time,
        signature-hash: message-hash,
      }
    )
    
    ;; Emit deposit event
    (print {
      event: "deposit",
      caller: tx-sender,
      receiver: receiver,
      assets: assets,
      shares: shares,
      signature-verified: signature-verified,
      stacks-block-time: current-time,
    })
    
    (ok shares)
  )
)

;; Mint shares for specific amount
(define-public (mint 
  (shares uint) 
  (receiver principal)
  (signature (optional (buff 64)))
  (public-key (optional (buff 33)))
  (message-hash (optional (buff 32)))
)
  (let (
    (current-supply (var-get total-shares))
    (current-assets (var-get total-assets))
    ;; Calculate assets directly to avoid circular dependency
    (assets (if (is-eq current-supply u0)
                shares
                (/ (* shares current-assets) current-supply)))
  )
    (deposit assets receiver signature public-key message-hash)
  )
)

;; === WITHDRAW AND REDEEM FUNCTIONS ===

;; Withdraw assets by burning shares
(define-public (withdraw 
  (assets uint) 
  (receiver principal) 
  (owner principal)
  (signature (optional (buff 64)))
  (public-key (optional (buff 33)))
  (message-hash (optional (buff 32)))
)
  (let (
    (current-supply (var-get total-shares))
    (current-assets (var-get total-assets))
    ;; Calculate shares directly to avoid circular dependency
    (shares (if (is-eq current-assets u0)
                assets
                (/ (* assets current-supply) current-assets)))
    (current-time burn-block-height)
    (operation-id (+ (var-get operation-nonce) u1))
    (owner-balance (balance-of owner))
    (signature-verified (match signature
      sig (match public-key
        pub-key (match message-hash
          msg-hash (verify-signature msg-hash sig pub-key)
          false
        )
        false
      )
      false
    ))
  )
    ;; Validation checks
    (asserts! (not (var-get vault-paused)) ERR_VAULT_PAUSED)
    (asserts! (> assets u0) ERR_ZERO_AMOUNT)
    (asserts! (>= owner-balance shares) ERR_INSUFFICIENT_SHARES)
    (asserts! (or (is-eq tx-sender owner) (>= (get-allowance owner tx-sender) shares)) ERR_NOT_AUTHORIZED)
    
    ;; Burn shares from owner
    (map-set user-shares owner (- owner-balance shares))
    
    ;; Update allowance if not owner
    (if (not (is-eq tx-sender owner))
      (map-set user-allowances {owner: owner, spender: tx-sender}
        (- (get-allowance owner tx-sender) shares)
      )
      true
    )
    
    ;; Transfer assets from vault owner to receiver 
    (try! (contract-call? 'SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token transfer assets CONTRACT_OWNER receiver none))
    
    ;; Update vault totals
    (var-set total-assets (- (var-get total-assets) assets))
    (var-set total-shares (- (var-get total-shares) shares))
    (var-set operation-nonce operation-id)
    
    ;; Update user withdrawal tracking
    (map-set user-deposits owner
      (merge 
        (default-to 
          {total-deposited: u0, total-withdrawn: u0, last-deposit-time: u0, signature-verified: false}
          (map-get? user-deposits owner)
        )
        {
          total-withdrawn: (+ assets (get total-withdrawn 
            (default-to {total-deposited: u0, total-withdrawn: u0, last-deposit-time: u0, signature-verified: false}
              (map-get? user-deposits owner)
            )
          )),
        }
      )
    )
    
    ;; Log operation
    (map-set vault-operations operation-id
      {
        operation-type: "withdraw",
        user: owner,
        amount: assets,
        shares: shares,
        timestamp: burn-block-height,
        stacks-block-time: current-time,
        signature-hash: message-hash,
      }
    )
    
    ;; Emit withdrawal event
    (print {
      event: "withdraw",
      caller: tx-sender,
      receiver: receiver,
      owner: owner,
      assets: assets,
      shares: shares,
      signature-verified: signature-verified,
      stacks-block-time: current-time,
    })
    
    (ok assets)
  )
)

;; Redeem shares for assets
(define-public (redeem 
  (shares uint) 
  (receiver principal) 
  (owner principal)
  (signature (optional (buff 64)))
  (public-key (optional (buff 33)))
  (message-hash (optional (buff 32)))
)
  (let (
    (current-supply (var-get total-shares))
    (current-assets (var-get total-assets))
    ;; Calculate assets directly to avoid circular dependency
    (assets (if (is-eq current-supply u0)
                shares
                (/ (* shares current-assets) current-supply)))
  )
    (withdraw assets receiver owner signature public-key message-hash)
  )
)

;; === ALLOWANCE FUNCTIONS ===

;; Get allowance
(define-read-only (get-allowance (owner principal) (spender principal))
  (default-to u0 (map-get? user-allowances {owner: owner, spender: spender}))
)

;; Approve spender
(define-public (approve (spender principal) (amount uint))
  (begin
    (map-set user-allowances {owner: tx-sender, spender: spender} amount)
    (print {
      event: "approval",
      owner: tx-sender,
      spender: spender,
      amount: amount,
    })
    (ok true)
  )
)

;; === VAULT MANAGEMENT FUNCTIONS ===

;; Pause/unpause vault
(define-public (set-vault-paused (paused bool))
  (begin
    (asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_NOT_AUTHORIZED)
    (var-set vault-paused paused)
    (print {
      event: "vault-paused-changed",
      paused: paused,
      block-height: burn-block-height,
    })
    (ok paused)
  )
)

;; Update performance fee
(define-public (set-performance-fee (fee uint))
  (begin
    (asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_NOT_AUTHORIZED)
    (asserts! (<= fee u1000) ERR_NOT_AUTHORIZED) ;; Max 10%
    (var-set performance-fee fee)
    (ok fee)
  )
)

;; Collect management fees
(define-public (collect-management-fees)
  (let (
    (current-time burn-block-height)
    (last-collection (var-get last-fee-collection))
    (time-elapsed (- current-time last-collection))
    (annual-fee (var-get management-fee))
    (current-assets (var-get total-assets))
    (fee-amount (/ (* current-assets annual-fee time-elapsed) (* u365 u10000 u10000))) ;; Simplified proportional calculation
  )
    (asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_NOT_AUTHORIZED)
    (asserts! (> time-elapsed u0) ERR_ZERO_AMOUNT)
    
    ;; Update last collection time
    (var-set last-fee-collection current-time)
    (var-set total-assets (- current-assets fee-amount))
    
    (print {
      event: "management-fee-collected",
      amount: fee-amount,
      timestamp: current-time,
    })
    
    (ok fee-amount)
  )
)

;; === CLARITY v4 ENHANCED READ-ONLY FUNCTIONS ===

;; Get comprehensive vault information
(define-read-only (get-vault-info)
  (let (
    (current-assets (var-get total-assets))
    (current-shares (var-get total-shares))
  )
    {
      name: VAULT_NAME,
      symbol: VAULT_SYMBOL,
      decimals: DECIMALS,
      total-assets: current-assets,
      total-shares: current-shares,
      exchange-rate: (if (> current-shares u0)
        (/ (* current-assets u1000000) current-shares)
        INITIAL_EXCHANGE_RATE
      ),
      paused: (var-get vault-paused),
      assets-restricted: (var-get assets-restricted),
      performance-fee: (var-get performance-fee),
      management-fee: (var-get management-fee),
      contract-hash: (get-contract-hash),
      current-block-height: burn-block-height,
      last-fee-collection: (var-get last-fee-collection),
    }
  )
)

;; Get user deposit information - simplified to avoid circular dependency
(define-read-only (get-user-info (user principal))
  (let (
    (shares (balance-of user))
    (total-shares-supply (var-get total-shares))
    (total-assets-supply (var-get total-assets))
    (assets-value (if (is-eq total-shares-supply u0)
      u0
      (/ (* shares total-assets-supply) total-shares-supply)
    ))
    (deposit-data (default-to 
      {total-deposited: u0, total-withdrawn: u0, last-deposit-time: u0, signature-verified: false}
      (map-get? user-deposits user)
    ))
  )
    {
      shares: shares,
      assets-value: assets-value,
      total-deposited: (get total-deposited deposit-data),
      total-withdrawn: (get total-withdrawn deposit-data),
      last-deposit-time: (get last-deposit-time deposit-data),
      signature-verified: (get signature-verified deposit-data),
      net-position: (- (get total-deposited deposit-data) (get total-withdrawn deposit-data)),
    }
  )
)

;; Get operation details
(define-read-only (get-operation (operation-id uint))
  (map-get? vault-operations operation-id)
)

;; Get user deposit description (already ASCII)
(define-read-only (get-user-deposit-description-ascii (user principal))
  (let (
    (description "Vault-User-Account") ;; Simplified static description
  )
    (ok description)
  )
)

;; Helper function to convert principal to ASCII representation
(define-private (principal-to-ascii (p principal))
  ;; Simplified representation - in practice you'd want more sophisticated conversion
  "user-address"
)

;; Verify operation signature
(define-read-only (verify-operation-signature 
  (operation-id uint) 
  (message-hash (buff 32))
)
  (match (map-get? vault-operations operation-id)
    operation (match (get signature-hash operation)
      stored-hash (is-eq stored-hash message-hash)
      false
    )
    false
  )
)

;; Get vault statistics
(define-read-only (get-vault-statistics)
  {
    total-operations: (var-get operation-nonce),
    vault-age-blocks: (- burn-block-height u1), ;; Approximate since deployment
    average-deposit-size: (if (> (var-get operation-nonce) u0)
      (/ (var-get total-assets) (var-get operation-nonce))
      u0
    ),
    utilization-rate: (if (> (var-get total-shares) u0) u100 u0), ;; Simplified calculation
  }
)

Functions (34)

FunctionAccessArgs
get-vault-inforead-only
get-contract-hashread-only
toggle-asset-restrictionspublicrestricted: bool
get-vault-name-asciiread-only
get-current-stacks-timeread-only
verify-signatureprivatemessage-hash: (buff 32
assetread-only
total-supplyread-only
balance-ofread-onlyuser: principal
calculate-shares-from-assetsprivateassets: uint, shares-supply: uint, assets-supply: uint
calculate-assets-from-sharesprivateshares: uint, shares-supply: uint, assets-supply: uint
max-depositread-onlyuser: principal
max-mintread-onlyuser: principal
max-withdrawread-onlyuser: principal
max-redeemread-onlyuser: principal
preview-depositread-onlyassets: uint
preview-mintread-onlyshares: uint
preview-withdrawread-onlyassets: uint
preview-redeemread-onlyshares: uint
depositpublicassets: uint, receiver: principal, signature: (optional (buff 64
mintpublicshares: uint, receiver: principal, signature: (optional (buff 64
withdrawpublicassets: uint, receiver: principal, owner: principal, signature: (optional (buff 64
redeempublicshares: uint, receiver: principal, owner: principal, signature: (optional (buff 64
get-allowanceread-onlyowner: principal, spender: principal
approvepublicspender: principal, amount: uint
set-vault-pausedpublicpaused: bool
set-performance-feepublicfee: uint
collect-management-feespublic
get-user-inforead-onlyuser: principal
get-operationread-onlyoperation-id: uint
get-user-deposit-description-asciiread-onlyuser: principal
principal-to-asciiprivatep: principal
verify-operation-signatureread-onlyoperation-id: uint, message-hash: (buff 32
get-vault-statisticsread-only