;; 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
}
)