Source Code


	;; title: vault
	;; version: 0.0.1
	;; summary: Vault for storing collateral and constructing bond(s) to lend to protocol(s)
	;; description: Vault for storing collateral and constructing bond(s) to lend to protocol(s)
	
	;; traits
	;; mainnet - SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE.sip-010-trait-ft-standard
	;; testnet - ST2XX28V6YR45HZJ0D5990MRCBHMGC843GQQ12N1Q
	;; devnet - ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM
	(use-trait ft-trait 'SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE.sip-010-trait-ft-standard.sip-010-trait)
	;;
	
	;; constants
	(define-constant contract-owner tx-sender)
	;; mainnet - SP1X5C20QNS2RDPRWMXMHJVTGEH1KKSREH2Q6RBWN.sbtc
	;; testnet - ST2XX28V6YR45HZJ0D5990MRCBHMGC843GQQ12N1Q.sbtc-2
	;; devnet - ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.sbtc
	(define-constant collateral-asset-contract 'SP1X5C20QNS2RDPRWMXMHJVTGEH1KKSREH2Q6RBWN.sbtc)
	;; mainnet - SP1X5C20QNS2RDPRWMXMHJVTGEH1KKSREH2Q6RBWN
	;; testnet - ST2XX28V6YR45HZJ0D5990MRCBHMGC843GQQ12N1Q
	;; devnet - ST2JHG361ZXG51QTKY2NQCVBPPRRE2KZB1HR05NNC
	(define-constant tetris-principal 'SP1X5C20QNS2RDPRWMXMHJVTGEH1KKSREH2Q6RBWN)
	(define-constant dummy-principal 'ST1HTBVD3JG9C05J7HBJTHGR0GGW7KXW28M5JS8QE) ;;todo - how to have a zero address or something
	(define-constant dummy-position {
		id: u0,
		borrower: dummy-principal,
		collateralBalance: u0,
		state: u0,
		lender: dummy-principal,
		liquidator: dummy-principal,
		proofTxId: "",
		tetris: dummy-principal
	})
	(define-constant dummy-proof-tx-id "1D10T")
	
	;; errors
	(define-constant err-owner-only (err u100))
	(define-constant err-amount-not-gt-zero (err u102))
	(define-constant err-principal-network-mismatch (err u103))
	(define-constant err-invalid-position-id (err u104))
	(define-constant err-not-a-number (err u105))
	(define-constant err-not-enough-collateral (err u106))
	(define-constant err-vault-amount-must-gte-than-locked (err u107))
	(define-constant err-invalid-vault-state (err u108))
	(define-constant err-existing-vault-state (err u109))
	(define-constant err-invalid-collateral-state (err u110))
	(define-constant err-unauthorized-principal (err u111))
	(define-constant err-no-liquidator-defined (err u112))
	(define-constant err-no-proof-tx-defined (err u113))
	
	
	;; max positions for a vault
	(define-constant position-ids (list u1 u2 u3 u4 u5 u6 u7 u8 u9 u10))
	
	;; collateral states
	(define-constant collateral-state-open u200)
	(define-constant collateral-state-locked u201)
	(define-constant collateral-state-liquidated u202)
	(define-constant collateral-state-archived u203)
	
	;; vault states
	(define-constant vault-state-open u300)
	(define-constant vault-state-archived u301)
	
	;; data vars
	(define-data-var last-position-id uint u0)
	(define-data-var current-vault-state uint vault-state-open)
	(define-data-var tetris-proof (string-ascii 66) "") ;; todo - this is a replay-proof signature from tetris that the lender needs to validate
	
	
	;; data maps
	(define-map positions
		uint
		{
			id: uint,
			borrower: principal,
			collateralBalance: uint,
			state: uint,
			lender: principal,
			liquidator: principal,
			proofTxId: (string-ascii 66),
			tetris: principal
		}
	)
	;;
	
	;; public functions
	(define-public (deposit (amount uint))
		(let (
			(available-collateral-balance (unwrap! (get-available-collateral-balance) err-not-a-number))
			(event {type: "deposit_to_vault", amount: amount, sender: tx-sender, asset: collateral-asset-contract})
		)
	
			;; ownership check
			(asserts! (is-eq tx-sender contract-owner) err-owner-only)
	
			;; vault state
			(asserts! (is-eq (var-get current-vault-state) vault-state-open) err-invalid-vault-state)
	
			;; context state
			(asserts! (is-eq true (> amount u0)) err-amount-not-gt-zero)
			;; note - we may implement a max and or min deposit amount
	
			(try! (transfer-ft collateral-asset-contract amount tx-sender (as-contract tx-sender)))
			(print event)
			(ok true)
		)
	)
	
	(define-public (withdraw (amount uint))
		(let (
			(available-collateral-balance (unwrap! (get-available-collateral-balance) err-not-a-number))
			(event {type: "withdraw_from_vault", amount: amount, sender: tx-sender, asset: collateral-asset-contract})
		)
			;; ownership check
			(asserts! (is-eq tx-sender contract-owner) err-owner-only)
	
			;; vault check
			;; note - maybe we will allow withdrawal from a closed vault
			(asserts! (is-eq (var-get current-vault-state) vault-state-open) err-invalid-vault-state)
	
			;; context check
			(asserts! (is-eq true (> amount u0)) err-amount-not-gt-zero)
			(asserts! (is-eq true (>= available-collateral-balance amount)) err-not-enough-collateral)
	
			(try! (transfer-ft collateral-asset-contract amount (as-contract tx-sender) tx-sender))
			(print event)
			(ok true)
		)
	)
	
	(define-public (create-position (collateral-amount uint) (lender principal))
		(let (
			(position-id (+ (var-get last-position-id) u1))
			(available-collateral-balance (unwrap! (get-available-collateral-balance) err-not-a-number))
			(event {type: "create_position", sender: tx-sender, asset: collateral-asset-contract})
		)
	
			(asserts! (is-standard lender) err-principal-network-mismatch)
	
			;; ownership checks
			(asserts! (is-eq tx-sender contract-owner) err-owner-only)
	
			;; vault checks
			(asserts! (is-eq (var-get current-vault-state) vault-state-open) err-invalid-vault-state)
	
			;; context checks
			(asserts! (is-eq true (> collateral-amount u0)) err-amount-not-gt-zero)
			(asserts! (is-eq true (> position-id (var-get last-position-id))) err-invalid-position-id)
			(asserts! (is-eq true (<= position-id (len position-ids))) err-invalid-position-id)
			(asserts! (is-eq true (>= available-collateral-balance collateral-amount)) err-not-enough-collateral)
	
			(map-set positions position-id {
				id: position-id,
				borrower: tx-sender,
				collateralBalance: collateral-amount,
				state: collateral-state-open,
				lender: lender,
				liquidator: dummy-principal,
				proofTxId: dummy-proof-tx-id,
				tetris: tetris-principal
			})
			(var-set last-position-id position-id)
			(print event)
			(ok true)
		)
	)
	
	(define-public (add-to-position (position-id uint) (collateral-amount uint))
		(let (
			(position (unwrap! (map-get? positions position-id) err-invalid-position-id))
			(available-collateral-balance (unwrap! (get-available-collateral-balance) err-not-a-number))
			(event {type: "add_to_position", positionId: position-id, amount: collateral-amount, sender: tx-sender, asset: collateral-asset-contract})
		)
			;; ownership checks
			(asserts! (is-eq tx-sender contract-owner) err-owner-only) ;; only owner can call
	
			;; vault checks
			(asserts! (is-eq (var-get current-vault-state) vault-state-open) err-invalid-vault-state) ;; vault state must be open
	
			;; common position checks
			(asserts! (is-eq (get state position) collateral-state-open) err-invalid-collateral-state)
			(asserts! (is-eq tx-sender (get borrower position)) err-owner-only) ;; borrower must match position principal
			(asserts! (is-some (some position)) err-invalid-position-id) ;; position must exist
			(asserts! (is-eq true (<= position-id (var-get last-position-id))) err-invalid-position-id) ;; position must be within valid range
	
			;; specific context checks
			(asserts! (is-eq true (> collateral-amount u0)) err-amount-not-gt-zero)
			(asserts! (is-eq true (>= available-collateral-balance collateral-amount)) err-not-enough-collateral)
	
			(map-set positions position-id (merge position {collateralBalance: (+ (get collateralBalance position) collateral-amount)}))
			(print event)
			(ok true)
		)
	)
	
	(define-public (remove-from-position (position-id uint) (collateral-amount uint))
		(let (
			(position (unwrap! (map-get? positions position-id) err-invalid-position-id))
			(event {type: "remove_from_position", positionId: position-id, amount: collateral-amount, sender: tx-sender, asset: collateral-asset-contract})
		)
	
			;; ownership checks
			(asserts! (is-eq tx-sender contract-owner) err-owner-only) ;; only owner can call
	
			;; vault checks
			(asserts! (is-eq (var-get current-vault-state) vault-state-open) err-invalid-vault-state) ;; vault state must be open
	
			;; common position checks
			(asserts! (is-eq (get state position) collateral-state-open) err-invalid-collateral-state)
			(asserts! (is-eq tx-sender (get borrower position)) err-owner-only) ;; borrower must match position principal
			(asserts! (is-some (some position)) err-invalid-position-id) ;; position must exist
			(asserts! (is-eq true (<= position-id (var-get last-position-id))) err-invalid-position-id) ;; position must be within valid range
	
			;; specific context checks
			(asserts! (is-eq true (> collateral-amount u0)) err-amount-not-gt-zero)
			(asserts! (is-eq true (>= (get collateralBalance position) collateral-amount)) err-vault-amount-must-gte-than-locked)
			
			(map-set positions position-id (merge position {collateralBalance: (- (get collateralBalance position) collateral-amount)}))
			(print event)
			(ok true)
		)
	)
	
	(define-public (change-vault-state (new-state uint)) 
		(let (
			(event {type: "change_vault_state", sender: tx-sender, oldState: (var-get current-vault-state), newState: new-state })
		) 
	
			;; ownership check
			(asserts! (is-eq tx-sender contract-owner) err-owner-only)
			
			;; context check
			(asserts! (is-eq true (or (is-eq new-state vault-state-open) (is-eq new-state vault-state-archived))) err-invalid-vault-state)
			(asserts! (is-eq false (is-eq new-state (var-get current-vault-state))) err-existing-vault-state)
			
			(var-set current-vault-state new-state)
			(print event)
			(ok new-state)	
		)
	)
	
	(define-public (lock-position (position-id uint))
		(let (
			(position (unwrap! (map-get? positions position-id) err-invalid-position-id))
			(event {type: "lock_position", positionId: position-id, sender: tx-sender})
		)
			;; access checks
			(asserts! (is-eq true (is-eq tx-sender (get lender position))) err-unauthorized-principal)
	
			;; vault checks
			(asserts! (is-eq (var-get current-vault-state) vault-state-open) err-invalid-vault-state) ;; vault state must be open
	
			;; common position checks
			(asserts! (is-some (some position)) err-invalid-position-id) ;; position must exist
			(asserts! (is-eq true (<= position-id (var-get last-position-id))) err-invalid-position-id) ;; position must be within valid range
	
			;; specific context checks
			(asserts! (is-eq (get state position) collateral-state-open) err-invalid-collateral-state) ;; collateral state must be open
	
			(map-set positions position-id (merge position {state: collateral-state-locked}))
			(print event)
			(ok true)
		)
	)
	
	(define-public (liquidate-position (position-id uint) (liquidator principal) (proofTxId (string-ascii 66)))
		(let (
			(position (unwrap! (map-get? positions position-id) err-invalid-position-id))
			(event {type: "liquidate_position", positionId: position-id, sender: tx-sender, liquidator: liquidator, proofTxId: proofTxId})
		)
			;; access checks
			(asserts! (is-eq true (is-eq tx-sender (get lender position))) err-unauthorized-principal)
	
			;; vault checks
			(asserts! (is-eq (var-get current-vault-state) vault-state-open) err-invalid-vault-state) ;; vault state must be open
	
			;; common position checks
			(asserts! (is-some (some position)) err-invalid-position-id) ;; position must exist
			(asserts! (is-eq true (<= position-id (var-get last-position-id))) err-invalid-position-id) ;; position must be within valid range
	
			;; specific context checks
			(asserts! (is-eq (get state position) collateral-state-locked) err-invalid-collateral-state) ;; collateral state must be locked
	
			(map-set positions position-id (merge position {state: collateral-state-liquidated, liquidator: liquidator, proofTxId: proofTxId}))
			(print event)
			(ok true)
		)
	)
	
	(define-public (unlock-position (position-id uint) (proofTxId (string-ascii 66)))
		(let (
			(position (unwrap! (map-get? positions position-id) err-invalid-position-id))
			(event {type: "unlock_position", positionId: position-id, sender: tx-sender, proofTxId: proofTxId})
		)
			;; access checks
			(asserts! (is-eq true (is-eq tx-sender (get lender position))) err-unauthorized-principal)
	
			;; vault checks
			(asserts! (is-eq (var-get current-vault-state) vault-state-open) err-invalid-vault-state) ;; vault state must be open
	
			;; common position checks
			(asserts! (is-some (some position)) err-invalid-position-id) ;; position must exist
			(asserts! (is-eq true (<= position-id (var-get last-position-id))) err-invalid-position-id) ;; position must be within valid range
	
			;; specific context checks
			(asserts! (is-eq (get state position) collateral-state-locked) err-invalid-collateral-state) ;; collateral state must be locked
	
			(map-set positions position-id (merge position {state: collateral-state-open, proofTxId: proofTxId}))
			(print event)
			(ok true)
		)
	)
	
	(define-public (pay-liquidator (position-id uint)) 
		(let (
			(position (unwrap! (map-get? positions position-id) err-invalid-position-id))
			(event {type: "pay_liquidator", positionId: position-id, sender: tx-sender})
		)
			;; access checks
			(asserts! (is-eq true (is-eq tx-sender (get tetris position))) err-unauthorized-principal)
			(asserts! (is-eq true (is-eq tx-sender tetris-principal)) err-unauthorized-principal)
	
			;; vault checks
			(asserts! (is-eq (var-get current-vault-state) vault-state-open) err-invalid-vault-state)
	
			;; common position checks
			(asserts! (is-some (some position)) err-invalid-position-id) ;; position must exist
			(asserts! (is-eq true (<= position-id (var-get last-position-id))) err-invalid-position-id) ;; position must be within valid range
	
			;; specific context checks
			(asserts! (is-eq (get state position) collateral-state-liquidated) err-invalid-collateral-state)
			(asserts! (is-eq false (is-eq (get liquidator position) dummy-principal)) err-no-liquidator-defined)
			(asserts! (is-eq false (is-eq (get proofTxId position) dummy-proof-tx-id)) err-no-proof-tx-defined)
	
			(try! (transfer-ft collateral-asset-contract (get collateralBalance position) (as-contract tx-sender) (get liquidator position)))
			(map-set positions position-id (merge position {state: collateral-state-archived, collateralBalance: u0}))
			(print event)
			(ok true)
		)
	)
	
	(define-public (get-available-collateral-balance)
		(let (
			(vault-balance (unwrap! (balance-ft collateral-asset-contract (as-contract tx-sender)) err-not-a-number)) 
			(locked-balance (unwrap! (get-locked-collateral-balance) err-not-a-number))
		)
			(ok (if (>= vault-balance locked-balance) (- vault-balance locked-balance) u0))
		)
	)
	
	(define-public (get-borrower-position  (position-id uint))
	(let (
			(position (unwrap! (map-get? positions position-id) err-invalid-position-id))
		)
			(ok position)
		)
	)
	
	;; read only functions
	(define-read-only (get-position (position-id uint))
		(map-get? positions position-id)
	)
	
	(define-read-only (get-locked-collateral-balance)
		(let (
			(total (fold sum-position-locked-balances position-ids u0))
		) 
		(ok total)
		)
	)
	
	(define-read-only (get-vault-state)
		(var-get current-vault-state)
	)
	
	;; get position state
	(define-read-only (get-position-state (position-id uint))
		(let (
			(position (unwrap! (map-get? positions position-id) err-invalid-position-id))
		)
			(ok (get state position))
		)
	)
	;;
	
	;; private functions
	(define-private (transfer-ft (token-contract <ft-trait>) (amount uint) (sender principal) (recipient principal))
		(contract-call? token-contract transfer amount sender recipient none)
	)
	
	(define-private (balance-ft (token-contract <ft-trait>) (who principal))
		(contract-call? token-contract get-balance who)
	)
	
	(define-private (sum-position-locked-balances (addend uint) (sum uint))
		(let (
			(position (default-to dummy-position (map-get? positions addend)))
		)
			(+ (get collateralBalance position) sum)
		)
	)
	
	(define-private (common-add-remove-position-checks (position (tuple (borrower principal) (collateralBalance uint) (state uint) (id uint))))
		(begin 
			;; ownership checks
			(asserts! (is-eq tx-sender contract-owner) err-owner-only) ;; only owner can call
	
			;; vault checks
			(asserts! (is-eq (var-get current-vault-state) vault-state-open) err-invalid-vault-state) ;; vault state must be open
	
			;; common position checks
			(asserts! (is-eq (get state position) collateral-state-open) err-invalid-collateral-state)
			(asserts! (is-eq tx-sender (get borrower position)) err-owner-only) ;; borrower must match position principal
			(asserts! (is-some (some position)) err-invalid-position-id) ;; position must exist
			(asserts! (is-eq true (<= (get id position) (var-get last-position-id))) err-invalid-position-id) ;; position must be within valid range
			(ok true)
		)
	)	

Functions (20)

FunctionAccessArgs
depositpublicamount: uint
withdrawpublicamount: uint
create-positionpubliccollateral-amount: uint, lender: principal
add-to-positionpublicposition-id: uint, collateral-amount: uint
remove-from-positionpublicposition-id: uint, collateral-amount: uint
change-vault-statepublicnew-state: uint
lock-positionpublicposition-id: uint
liquidate-positionpublicposition-id: uint, liquidator: principal, proofTxId: (string-ascii 66
unlock-positionpublicposition-id: uint, proofTxId: (string-ascii 66
pay-liquidatorpublicposition-id: uint
get-available-collateral-balancepublic
get-borrower-positionpublicposition-id: uint
get-positionread-onlyposition-id: uint
get-locked-collateral-balanceread-only
get-vault-stateread-only
get-position-stateread-onlyposition-id: uint
transfer-ftprivatetoken-contract: <ft-trait>, amount: uint, sender: principal, recipient: principal
balance-ftprivatetoken-contract: <ft-trait>, who: principal
sum-position-locked-balancesprivateaddend: uint, sum: uint
common-add-remove-position-checksprivateposition: (tuple (borrower principal, collateralBalance: uint, state: uint, id: uint