From be08eab8ec7fda866e6357c52a3c634ae6749538 Mon Sep 17 00:00:00 2001 From: MarvinJanssen Date: Mon, 19 Feb 2024 13:37:36 +0100 Subject: [PATCH 1/2] Add treasury, LISA token, operators extension --- Clarinet.toml | 28 ++++ contracts/boot.clar | 23 +++- contracts/extensions/operators.clar | 96 ++++++++++++++ contracts/proxies/lisa-transfer-proxy.clar | 9 ++ contracts/proxies/lqstx-transfer-proxy.clar | 9 ++ contracts/token-lisa.clar | 122 ++++++++++++++++++ contracts/token-lqstx.clar | 2 +- contracts/traits/sip-010-trait.clar | 24 ++++ .../traits/sip-010-transferable-trait.clar | 5 + contracts/treasury.clar | 32 +++++ 10 files changed, 348 insertions(+), 2 deletions(-) create mode 100644 contracts/extensions/operators.clar create mode 100644 contracts/proxies/lisa-transfer-proxy.clar create mode 100644 contracts/proxies/lqstx-transfer-proxy.clar create mode 100644 contracts/token-lisa.clar create mode 100644 contracts/traits/sip-010-trait.clar create mode 100644 contracts/traits/sip-010-transferable-trait.clar create mode 100644 contracts/treasury.clar diff --git a/Clarinet.toml b/Clarinet.toml index 3a962ea..d28ce42 100644 --- a/Clarinet.toml +++ b/Clarinet.toml @@ -24,6 +24,10 @@ epoch = 2.4 path = "contracts/vault.clar" epoch = 2.4 +[contracts.treasury] +path = "contracts/treasury.clar" +epoch = 2.4 + [contracts.strategy-trait] path = "contracts/traits/strategy-trait.clar" epoch = 2.4 @@ -48,6 +52,14 @@ epoch = 2.4 path = "contracts/proxies/stx-transfer-many-proxy.clar" epoch = 2.4 +[contracts.lqstx-transfer-proxy] +path = "contracts/proxies/lqstx-transfer-proxy.clar" +epoch = 2.4 + +[contracts.lisa-transfer-proxy] +path = "contracts/proxies/lisa-transfer-proxy.clar" +epoch = 2.4 + [contracts.fastpool-strategy] path = "contracts/strategies/fastpool/fastpool-strategy.clar" epoch = 2.4 @@ -132,6 +144,14 @@ epoch = 2.4 path = "contracts/strategies/fastpool/fastpool-member.clar" epoch = 2.4 +[contracts.sip-010-trait] +path = "contracts/traits/sip-010-trait.clar" +epoch = 2.4 + +[contracts.sip-010-transferable-trait] +path = "contracts/traits/sip-010-transferable-trait.clar" +epoch = 2.4 + [contracts.sip-010-extensions-trait] path = "contracts/traits/sip-010-extensions-trait.clar" epoch = 2.4 @@ -156,6 +176,10 @@ epoch = 2.4 path = "contracts/token-wlqstx.clar" epoch = 2.4 +[contracts.token-lisa] +path = "contracts/token-lisa.clar" +epoch = 2.4 + [contracts.lisa-rebase] path = "contracts/extensions/lisa-rebase.clar" epoch = 2.4 @@ -164,6 +188,10 @@ epoch = 2.4 path = "contracts/rules/rebase-1.clar" epoch = 2.4 +[contracts.operators] +path = "contracts/extensions/operators.clar" +epoch = 2.4 + # [repl.analysis] # passes = ["check_checker"] # check_checker = { trusted_sender = false, trusted_caller = false, callee_filter = false } diff --git a/contracts/boot.clar b/contracts/boot.clar index f770add..81d21c9 100644 --- a/contracts/boot.clar +++ b/contracts/boot.clar @@ -2,12 +2,33 @@ (define-public (execute (sender principal)) (begin + ;; Enable core contracts (try! (contract-call? .lisa-dao set-extensions (list - {extension: .deposit-stx, enabled: true} + {extension: .lqstx-mint-endpoint, enabled: true} + {extension: .lqstx-mint-registry, enabled: true} {extension: .vault, enabled: true} + {extension: .treasury, enabled: true} {extension: .fastpool-strategy-manager, enabled: true} {extension: .lisa-rebase, enabled: true} {extension: .rebase-1, enabled: true} + {extension: .operators-1, enabled: true} + ))) + + ;; Set initial operators + (try! (contract-call? .operators set-operators (list + {operator: tx-sender, enabled: true} + {operator: 'ST1SJ3DTE5DN7X54YDH5D64R3BCB6A2AG2ZQ8YPD5, enabled: true} + {operator: 'ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG, enabled: true} + ))) + ;; Set operator signal threshold + (try! (contract-call? .operators set-proposal-threshold 2)) + + ;; Set initial Fastpool strategy managers + (try! (contract-call? .fastpool-strategy-manager set-authorised-manager tx-sender true)) + + ;; Mint initial LISA token supply + (try! (contract-call? .token-lisa dao-mint-many (list + {recipient: .treasury, amount: u100000000000000} ))) (ok true) ) diff --git a/contracts/extensions/operators.clar b/contracts/extensions/operators.clar new file mode 100644 index 0000000..55fa859 --- /dev/null +++ b/contracts/extensions/operators.clar @@ -0,0 +1,96 @@ +(use-trait proposal-trait .proposal-trait.proposal-trait) + +(define-constant err-unauthorised (err u1000)) +(define-constant err-not-operator (err u1001)) +(define-constant err-already-signalled (err u1002)) +(define-constant err-proposal-expired (err u1003)) +(define-constant err-unknown-proposal (err u1004)) +(define-constant err-reused-proposal (err u1005)) + +(define-constant proposal-validity-period u144) + +(define-data-var proposal-threshold int 1) +(define-data-var operators-update-height uint block-height) + +(define-map operators principal bool) +(define-map proposals principal { proposed-at: uint, signals: int, executed: bool }) +(define-map proposal-signals { proposal: principal, operator: principal } uint) + +(define-read-only (is-dao-or-extension) + (ok (asserts! (or (is-eq tx-sender .lisa-dao) (contract-call? .lisa-dao is-extension contract-caller)) err-unauthorised)) +) + +(define-read-only (is-operator) + (ok (asserts! (default-to false (map-get? operators tx-sender)) err-not-operator)) +) + +(define-private (set-operator (entry {operator: principal, enabled: bool})) + (map-set operators (get operator entry) (get enabled entry)) +) + +;; operators + +(define-private (check-validity (proposed-at uint)) + (and + (< block-height (+ proposed-at proposal-validity-period)) + (< (var-get operators-update-height) proposed-at) + ) +) + +(define-public (signal (proposal ) (for bool)) + (let ( + (proposal-principal (contract-of proposal)) + (proposal-data (unwrap! (map-get? proposals proposal-principal) err-unknown-proposal)) + (proposal-height (get proposed-at proposal-data)) + (signals (+ (get signals proposal-data) (if for 1 -1))) + (threshold-met (>= signals (var-get proposal-threshold))) + ) + (try! (is-operator)) + (asserts! (check-validity proposal-height) err-proposal-expired) + (asserts! (< + ;; operator can signal again on a proposal that was resubmitted + (default-to u0 (map-get? proposal-signals { proposal: proposal-principal, operator: tx-sender })) + proposal-height) + err-already-signalled + ) + (map-set proposal-signals { proposal: proposal-principal, operator: tx-sender } block-height) + (map-set proposals proposal-principal (merge proposal-data {signals: signals, executed: threshold-met})) + (if threshold-met + (contract-call? .lisa-dao execute proposal tx-sender) + (ok false) + ) + ) +) + +(define-public (propose (proposal )) + (let ((proposal-principal (contract-of proposal))) + (try! (is-operator)) + (asserts! (match (map-get? proposals proposal-principal) + ;; proposal can be resubmitted if it was not executed and it expired + data (not (or (get executed data) (check-validity (get proposed-at data)))) + true) + err-reused-proposal + ) + (map-set proposals proposal-principal { proposed-at: block-height, signals: 0, executed: false }) + (signal proposal true) + ) +) + +;; DAO + +(define-public (set-operators (entries (list 20 {operator: principal, enabled: bool}))) + (begin + (try! (is-dao-or-extension)) + (var-set operators-update-height block-height) + (ok (map set-operator entries)) + ) +) + +(define-public (set-proposal-threshold (threshold int)) + (begin + (try! (is-dao-or-extension)) + (asserts! (> threshold 0) err-unauthorised) + (var-set operators-update-height block-height) + (ok (var-set proposal-threshold threshold)) + ) +) \ No newline at end of file diff --git a/contracts/proxies/lisa-transfer-proxy.clar b/contracts/proxies/lisa-transfer-proxy.clar new file mode 100644 index 0000000..53b1574 --- /dev/null +++ b/contracts/proxies/lisa-transfer-proxy.clar @@ -0,0 +1,9 @@ +(impl-trait .proxy-trait.proxy-trait) + +(define-constant err-invalid-payload (err u4000)) + +(define-public (proxy-call (payload (buff 2048))) + (let ((decoded (unwrap! (from-consensus-buff? { amount: uint, recipient: principal, memo: (optional (buff 34)) } payload) err-invalid-payload))) + (contract-call? .token-lisa transfer (get amount decoded) tx-sender (get recipient decoded) (get memo decoded)) + ) +) diff --git a/contracts/proxies/lqstx-transfer-proxy.clar b/contracts/proxies/lqstx-transfer-proxy.clar new file mode 100644 index 0000000..fef18dd --- /dev/null +++ b/contracts/proxies/lqstx-transfer-proxy.clar @@ -0,0 +1,9 @@ +(impl-trait .proxy-trait.proxy-trait) + +(define-constant err-invalid-payload (err u4000)) + +(define-public (proxy-call (payload (buff 2048))) + (let ((decoded (unwrap! (from-consensus-buff? { amount: uint, recipient: principal, memo: (optional (buff 34)) } payload) err-invalid-payload))) + (contract-call? .token-lqstx transfer (get amount decoded) tx-sender (get recipient decoded) (get memo decoded)) + ) +) diff --git a/contracts/token-lisa.clar b/contracts/token-lisa.clar new file mode 100644 index 0000000..4492db2 --- /dev/null +++ b/contracts/token-lisa.clar @@ -0,0 +1,122 @@ +(impl-trait .sip-010-trait.sip-010-trait) + +(define-constant err-unauthorised (err u3000)) +(define-constant err-not-token-owner (err u4)) + +(define-fungible-token lisa) + +(define-data-var token-name (string-ascii 32) "LISA") +(define-data-var token-symbol (string-ascii 10) "LISA") +(define-data-var token-uri (optional (string-utf8 256)) none) +(define-data-var token-decimals uint u6) + +(define-public (is-dao-or-extension) + (ok (asserts! (or (is-eq tx-sender .lisa-dao) (contract-call? .lisa-dao is-extension contract-caller)) err-unauthorised)) +) + +(define-public (dao-transfer (amount uint) (sender principal) (recipient principal)) + (begin + (try! (is-dao-or-extension)) + (ft-transfer? lisa amount sender recipient) + ) +) + +(define-public (dao-mint (amount uint) (recipient principal)) + (begin + (try! (is-dao-or-extension)) + (ft-mint? lisa amount recipient) + ) +) + +(define-public (dao-burn (amount uint) (owner principal)) + (begin + (try! (is-dao-or-extension)) + (ft-burn? lisa amount owner) + ) +) + +;; Other + +(define-public (set-name (new-name (string-ascii 32))) + (begin + (try! (is-dao-or-extension)) + (ok (var-set token-name new-name)) + ) +) + +(define-public (set-symbol (new-symbol (string-ascii 10))) + (begin + (try! (is-dao-or-extension)) + (ok (var-set token-symbol new-symbol)) + ) +) + +(define-public (set-decimals (new-decimals uint)) + (begin + (try! (is-dao-or-extension)) + (ok (var-set token-decimals new-decimals)) + ) +) + +(define-public (set-token-uri (new-uri (optional (string-utf8 256)))) + (begin + (try! (is-dao-or-extension)) + (ok (var-set token-uri new-uri)) + ) +) + +(define-private (dao-mint-many-iter (item {amount: uint, recipient: principal})) + (ft-mint? lisa (get amount item) (get recipient item)) +) + +(define-public (dao-mint-many (recipients (list 200 {amount: uint, recipient: principal}))) + (begin + (try! (is-dao-or-extension)) + (ok (map dao-mint-many-iter recipients)) + ) +) + +;; --- Public functions + +;; sip010-ft-trait + +(define-public (transfer (amount uint) (sender principal) (recipient principal) (memo (optional (buff 34)))) + (begin + (asserts! (or (is-eq tx-sender sender) (is-eq contract-caller sender)) err-not-token-owner) + (ft-transfer? lisa amount sender recipient) + ) +) + +(define-read-only (get-name) + (ok (var-get token-name)) +) + +(define-read-only (get-symbol) + (ok (var-get token-symbol)) +) + +(define-read-only (get-decimals) + (ok (var-get token-decimals)) +) + +(define-read-only (get-balance (who principal)) + (ok (ft-get-balance lisa who)) +) + +(define-read-only (get-total-supply) + (ok (ft-get-supply lisa)) +) + +(define-read-only (get-token-uri) + (ok (var-get token-uri)) +) + +;; governance-token-trait + +(define-read-only (dao-get-balance (who principal)) + (get-balance who) +) + +(define-read-only (dao-has-percentage-balance (who principal) (factor uint)) + (ok (>= (* (unwrap-panic (get-balance who)) factor) (* (unwrap-panic (get-total-supply)) u1000))) +) diff --git a/contracts/token-lqstx.clar b/contracts/token-lqstx.clar index 52444ef..07bc0f6 100644 --- a/contracts/token-lqstx.clar +++ b/contracts/token-lqstx.clar @@ -134,7 +134,7 @@ (define-public (transfer-fixed (amount uint) (sender principal) (recipient principal) (memo (optional (buff 34)))) (begin - (asserts! (is-eq sender tx-sender) err-unauthorised) + (asserts! (or (is-eq sender contract-caller) (is-eq sender tx-sender)) err-unauthorised) (try! (ft-transfer? lqstx (fixed-to-decimals (unwrap-panic (get-tokens-to-shares amount))) sender recipient)) (match memo to-print (print to-print) 0x) (ok true))) diff --git a/contracts/traits/sip-010-trait.clar b/contracts/traits/sip-010-trait.clar new file mode 100644 index 0000000..1045c8a --- /dev/null +++ b/contracts/traits/sip-010-trait.clar @@ -0,0 +1,24 @@ +(define-trait sip-010-trait + ( + ;; Transfer from the caller to a new principal + (transfer (uint principal principal (optional (buff 34))) (response bool uint)) + + ;; the human readable name of the token + (get-name () (response (string-ascii 32) uint)) + + ;; the ticker symbol, or empty if none + (get-symbol () (response (string-ascii 32) uint)) + + ;; the number of decimals used, e.g. 6 would mean 1_000_000 represents 1 token + (get-decimals () (response uint uint)) + + ;; the balance of the passed principal + (get-balance (principal) (response uint uint)) + + ;; the current total supply (which does not need to be a constant) + (get-total-supply () (response uint uint)) + + ;; an optional URI that represents metadata of this token + (get-token-uri () (response (optional (string-utf8 256)) uint)) + ) +) \ No newline at end of file diff --git a/contracts/traits/sip-010-transferable-trait.clar b/contracts/traits/sip-010-transferable-trait.clar new file mode 100644 index 0000000..2c47625 --- /dev/null +++ b/contracts/traits/sip-010-transferable-trait.clar @@ -0,0 +1,5 @@ +(define-trait sip-010-transferable-trait + ( + (transfer (uint principal principal (optional (buff 34))) (response bool uint)) + ) +) diff --git a/contracts/treasury.clar b/contracts/treasury.clar new file mode 100644 index 0000000..716e0e4 --- /dev/null +++ b/contracts/treasury.clar @@ -0,0 +1,32 @@ +(use-trait proxy-trait .proxy-trait.proxy-trait) +(use-trait sip-010-transferable-trait .sip-010-transferable-trait.sip-010-transferable-trait) + +(define-constant err-unauthorised (err u1000)) + +(define-read-only (is-dao-or-extension) + (ok (asserts! (or (is-eq tx-sender .lisa-dao) (contract-call? .lisa-dao is-extension contract-caller)) err-unauthorised)) +) + +(define-public (stx-transfer (amount uint) (recipient principal) (memo (optional (buff 34)))) + (begin + (try! (is-dao-or-extension)) + (as-contract (match memo + to-print (stx-transfer-memo? amount tx-sender recipient to-print) + (stx-transfer? amount tx-sender recipient) + )) + ) +) + +(define-public (sip010-transfer (amount uint) (recipient principal) (memo (optional (buff 34))) (sip010 )) + (begin + (try! (is-dao-or-extension)) + (contract-call? sip010 transfer amount (as-contract tx-sender) recipient memo) + ) +) + +(define-public (proxy-call (proxy ) (payload (buff 2048))) + (begin + (try! (is-dao-or-extension)) + (as-contract (contract-call? proxy proxy-call payload)) + ) +) From 17b9ea69543db5d7e03d46b99873004c4a1d8c6b Mon Sep 17 00:00:00 2001 From: MarvinJanssen Date: Mon, 19 Feb 2024 13:41:35 +0100 Subject: [PATCH 2/2] fix contract principal --- contracts/boot.clar | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/boot.clar b/contracts/boot.clar index 81d21c9..bae38a7 100644 --- a/contracts/boot.clar +++ b/contracts/boot.clar @@ -11,7 +11,7 @@ {extension: .fastpool-strategy-manager, enabled: true} {extension: .lisa-rebase, enabled: true} {extension: .rebase-1, enabled: true} - {extension: .operators-1, enabled: true} + {extension: .operators, enabled: true} ))) ;; Set initial operators