LCOV - code coverage report
Current view: top level - contracts/extensions - bme030-0-reputation-token.clar (source / functions) Coverage Total Hit
Test: lcov.info Lines: 91.5 % 199 182
Test Date: 2025-11-10 13:03:30 Functions: 87.9 % 33 29
Branches: 59.5 % 42 25

             Branch data     Line data    Source code
       1                 :             : ;; Title: BME030 Reputation Token
       2                 :             : ;; Synopsis:
       3                 :             : ;; Wraps reputation scheme within a non-transferable soulbound semi fungible token (see sip-013).
       4                 :             : ;; Description:
       5                 :             : ;; The reputation token is a SIP-013 compliant token that is controlled by active DAO extensions.
       6                 :             : ;; It facilitates hierarchical reputation and rewards based on engagements across a number of
       7                 :             : ;; BigMarket DAO features and use cases. 
       8                 :             : 
       9                 :             : (impl-trait 'SPDBEG5X8XD50SPM1JJH0E5CTXGDV5NJTKAKKR5V.sip013-semi-fungible-token-trait.sip013-semi-fungible-token-trait)
      10                 :             : (impl-trait 'SPDBEG5X8XD50SPM1JJH0E5CTXGDV5NJTKAKKR5V.sip013-transfer-many-trait.sip013-transfer-many-trait)
      11                 :             : (impl-trait 'SP3JP0N1ZXGASRJ0F7QAHWFPGTVK9T2XNXDB908Z.extension-trait.extension-trait)
      12                 :             : 
      13                 :             : (define-constant err-unauthorised (err u30001))
      14                 :             : (define-constant err-already-minted (err u30002))
      15                 :             : (define-constant err-soulbound (err u30003))
      16                 :             : (define-constant err-insufficient-balance (err u30004))
      17                 :             : (define-constant err-zero-amount (err u30005))
      18                 :             : (define-constant err-claims-old-epoch (err u30006))
      19                 :             : (define-constant err-claims-zero-rep (err u30007))
      20                 :             : (define-constant err-claims-zero-total (err u30008))
      21                 :             : (define-constant err-invalid-tier (err u30009))
      22                 :             : (define-constant err-too-high (err u30010))
      23                 :             : 
      24                 :             : (define-constant max-tier u20)
      25                 :             : (define-constant epoch-duration u1000)
      26                 :             : (define-constant SCALE u1000000)
      27                 :             : 
      28                 :             : (define-fungible-token bigr-token)
      29                 :             : (define-non-fungible-token bigr-id { token-id: uint, owner: principal })
      30                 :             : 
      31                 :             : (define-map balances { token-id: uint, owner: principal } uint)
      32                 :             : (define-map supplies uint uint)
      33                 :             : (define-map last-claimed-epoch { who: principal } uint)
      34                 :             : (define-map tier-weights uint uint)
      35                 :             : (define-map join-epoch { who: principal } uint)
      36                 :             : (define-map minted-in-epoch { epoch: uint } uint)
      37                 :             : (define-map minted-in-epoch-by { epoch: uint, who: principal } uint)
      38                 :             : 
      39                 :             : (define-data-var reward-per-epoch uint u10000000000) ;; 10,000 BIG per epoch (in micro units)
      40                 :             : (define-data-var overall-supply uint u0)
      41                 :             : (define-data-var token-name (string-ascii 32) "BigMarket Reputation Token")
      42                 :             : (define-data-var token-symbol (string-ascii 10) "BIGR")
      43                 :             : (define-data-var launch-height uint u0)
      44                 :             : 
      45                 :             : ;; ------------------------
      46                 :             : ;; DAO Control Check
      47                 :             : ;; ------------------------
      48                 :         421 : (define-public (is-dao-or-extension)
      49    [ + ][ +  + ]:        9586 :         (ok (asserts! (or (is-eq tx-sender .bigmarket-dao) (contract-call? .bigmarket-dao is-extension contract-caller)) err-unauthorised))
      50                 :             : )
      51                 :             : 
      52                 :          10 : (define-read-only (get-epoch)
      53                 :          68 :         (/ burn-block-height epoch-duration)
      54                 :             : )
      55                 :             : 
      56                 :           4 : (define-read-only (get-last-claimed-epoch (user principal))
      57                 :          34 :   (default-to u0 (map-get? last-claimed-epoch { who: user }))
      58                 :             : )
      59                 :             : 
      60                 :          10 : (define-read-only (get-latest-claimable-epoch)
      61                 :          52 :   (let ((cur (get-epoch)))
      62         [ +  + ]:          52 :     (if (> cur u0) (- cur u1) u0) ;; floor and subtract 1, but never negative
      63                 :             :   )
      64                 :             : )
      65                 :             : 
      66                 :             : ;; ------------------------
      67                 :             : ;; Trait Implementations
      68                 :             : ;; ------------------------
      69                 :           4 : (define-read-only (get-balance (token-id uint) (who principal))
      70                 :          17 :   (ok (default-to u0 (map-get? balances { token-id: token-id, owner: who })))
      71                 :             : )
      72                 :             : 
      73                 :         421 : (define-public (set-launch-height)
      74                 :         422 :   (begin
      75                 :         422 :     (try! (is-dao-or-extension))
      76            [ - ]:         421 :     (asserts! (is-eq (var-get launch-height) u0) err-unauthorised)
      77                 :         421 :     (var-set launch-height burn-block-height)
      78                 :         421 :     (ok (var-get launch-height))
      79                 :             :   )
      80                 :             : )
      81                 :             : 
      82                 :           0 : (define-read-only (get-symbol)
      83                 :           0 :         (ok (var-get token-symbol))
      84                 :             : )
      85                 :             : 
      86                 :           0 : (define-read-only (get-name)
      87                 :           0 :         (ok (var-get token-name))
      88                 :             : )
      89                 :             : 
      90                 :           7 : (define-read-only (get-overall-balance (who principal))
      91                 :          16 :   (ok (ft-get-balance bigr-token who))
      92                 :             : )
      93                 :             : 
      94                 :           4 : (define-read-only (get-total-supply (token-id uint))
      95                 :           6 :   (ok (default-to u0 (map-get? supplies token-id)))
      96                 :             : )
      97                 :             : 
      98                 :           1 : (define-read-only (get-overall-supply)
      99                 :           1 :   (ok (var-get overall-supply))
     100                 :             : )
     101                 :             : 
     102                 :           2 : (define-read-only (get-decimals (token-id uint)) (ok u0))
     103                 :             : 
     104                 :           1 : (define-read-only (get-token-uri (token-id uint))
     105                 :           1 :   (ok none)
     106                 :             : )
     107                 :             : 
     108                 :           2 : (define-public (set-reward-per-epoch (new-reward uint))
     109                 :           2 :   (begin
     110                 :           2 :     (try! (is-dao-or-extension))
     111            [ - ]:           1 :     (asserts! (<= new-reward u100000000000) err-too-high) ;; cap at 100k BIG
     112                 :           1 :     (var-set reward-per-epoch new-reward)
     113                 :           1 :     (print { event: "set-reward-per-epoch", new-reward: new-reward })
     114                 :           1 :     (ok true)
     115                 :             :   )
     116                 :             : )
     117                 :         421 : (define-public (set-tier-weight (token-id uint) (weight uint))
     118                 :        8443 :   (begin
     119                 :        8443 :     (try! (is-dao-or-extension))
     120                 :        8442 :     (map-set tier-weights token-id weight)
     121                 :        8442 :     (print { event: "set-tier-weight", token-id: token-id, weight: weight })
     122                 :        8442 :     (ok true)
     123                 :             :   )
     124                 :             : )
     125                 :             : 
     126                 :             : ;; ------------------------
     127                 :             : ;; Mint / Burn
     128                 :             : ;; ------------------------
     129                 :         356 : (define-public (mint (recipient principal) (token-id uint) (amount uint))
     130                 :         700 :   (let (
     131                 :         700 :     (current-epoch (/ burn-block-height epoch-duration))
     132                 :           0 :     (base-amount
     133                 :         700 :       (if (not is-in-mainnet)
     134            [ + ]:         700 :           (* amount u2) ;; testnet: 2x
     135            [ - ]:           0 :           (if (< burn-block-height (+ (var-get launch-height) u12000))
     136            [ - ]:           0 :               (/ (* amount u3) u2) ;; 1.5x on early mainnet
     137            [ - ]:           0 :               amount))) ;; no early adopter bonus
     138                 :         700 :     (weight (default-to u1 (map-get? tier-weights token-id)))
     139                 :         700 :     (weighted-amount (* base-amount weight))
     140                 :             :   )
     141                 :         700 :     (begin
     142                 :         700 :       (try! (is-dao-or-extension))
     143            [ - ]:         698 :       (asserts! (> base-amount u0) err-zero-amount)
     144    [ - ][ +  + ]:         698 :       (asserts! (and (> token-id u0) (<= token-id max-tier)) err-invalid-tier)
     145                 :             : 
     146                 :         698 :       (try! (ft-mint? bigr-token base-amount recipient))
     147                 :         698 :       (try! (tag-nft { token-id: token-id, owner: recipient }))
     148                 :         698 :       (map-set balances { token-id: token-id, owner: recipient }
     149                 :         698 :         (+ base-amount (default-to u0 (map-get? balances { token-id: token-id, owner: recipient }))))
     150                 :         698 :       (map-set supplies token-id (+ base-amount (default-to u0 (map-get? supplies token-id))))
     151                 :         698 :       (var-set overall-supply (+ (var-get overall-supply) base-amount))
     152                 :             : 
     153                 :             :       ;; track epoch joined to avoid overpaying rewards
     154                 :         698 :       (if (is-none (map-get? join-epoch { who: recipient }))
     155            [ + ]:         505 :             (map-set join-epoch { who: recipient } current-epoch)
     156            [ + ]:         193 :             true)
     157                 :             :       
     158                 :             :       ;; update minted for this epoch
     159                 :         698 :       (map-set minted-in-epoch { epoch: current-epoch }
     160                 :         698 :         (+ weighted-amount (default-to u0 (map-get? minted-in-epoch { epoch: current-epoch }))))
     161                 :             :       
     162                 :         698 :       (map-set minted-in-epoch-by { epoch: current-epoch, who: recipient }
     163                 :         698 :         (+ weighted-amount (default-to u0 (map-get? minted-in-epoch-by { epoch: current-epoch, who: recipient }))))
     164                 :             :       
     165                 :         698 :       (print { event: "sft_mint", token-id: token-id, amount: base-amount, recipient: recipient })
     166                 :         698 :       (ok true)
     167                 :             :     )
     168                 :             :   )
     169                 :             : )
     170                 :             : 
     171                 :           4 : (define-public (burn (owner principal) (token-id uint) (amount uint))
     172                 :           5 :   (let (
     173                 :           5 :     (current-epoch (/ burn-block-height epoch-duration))
     174                 :           5 :     (weight (default-to u1 (map-get? tier-weights token-id)))
     175                 :           5 :     (weighted-amount (* amount weight))
     176                 :           5 :     (current (default-to u0 (map-get? balances { token-id: token-id, owner: owner })))
     177                 :             :   )
     178                 :           5 :     (begin
     179                 :           5 :       (try! (is-dao-or-extension))
     180            [ - ]:           3 :       (asserts! (>= current amount) err-insufficient-balance)
     181                 :           3 :       (try! (ft-burn? bigr-token amount owner))
     182                 :           3 :       (map-set balances { token-id: token-id, owner: owner } (- current amount))
     183                 :           3 :       (map-set supplies token-id (- (unwrap-panic (get-total-supply token-id)) amount))
     184                 :           3 :       (var-set overall-supply (- (var-get overall-supply) amount))
     185                 :             : 
     186                 :             :       ;; Decrement epoch-level mint counters
     187                 :           3 :       (let (
     188                 :           3 :         (prev-total (default-to u0 (map-get? minted-in-epoch { epoch: current-epoch })))
     189                 :           3 :         (prev-user (default-to u0 (map-get? minted-in-epoch-by { epoch: current-epoch, who: owner })))
     190                 :             :       )
     191                 :           3 :         (map-set minted-in-epoch { epoch: current-epoch }
     192         [ +  - ]:           3 :           (if (> prev-total weighted-amount) (- prev-total weighted-amount) u0))
     193                 :           3 :         (map-set minted-in-epoch-by { epoch: current-epoch, who: owner }
     194         [ +  - ]:           3 :           (if (> prev-user weighted-amount) (- prev-user weighted-amount) u0))
     195                 :             :       )
     196                 :             : 
     197                 :           3 :       (try! (nft-burn? bigr-id { token-id: token-id, owner: owner } owner))
     198                 :           3 :       (print { event: "sft_burn", token-id: token-id, amount: amount, sender: owner })
     199                 :           3 :       (ok true)
     200                 :             :     )
     201                 :             :   )
     202                 :             : )
     203                 :             : 
     204                 :             : ;; ------------------------
     205                 :             : ;; Transfer (DAO-only)
     206                 :             : ;; ------------------------
     207                 :           6 : (define-public (transfer (token-id uint) (amount uint) (sender principal) (recipient principal))
     208                 :          14 :   (begin
     209                 :          14 :     (try! (is-dao-or-extension))
     210            [ - ]:          12 :     (asserts! (> amount u0) err-zero-amount)
     211                 :          12 :     (let (
     212                 :          12 :         (sender-balance (default-to u0 (map-get? balances { token-id: token-id, owner: sender })))
     213                 :          12 :         (weight (default-to u1 (map-get? tier-weights token-id)))
     214                 :          12 :         (weighted-amount (* amount weight))
     215                 :          12 :         (epoch (/ burn-block-height epoch-duration))
     216                 :             :       )
     217            [ - ]:          12 :       (asserts! (>= sender-balance amount) err-insufficient-balance)
     218                 :          12 :       (try! (ft-transfer? bigr-token amount sender recipient))
     219                 :          12 :       (try! (tag-nft { token-id: token-id, owner: sender }))
     220                 :          12 :       (try! (tag-nft { token-id: token-id, owner: recipient }))
     221                 :          12 :       (map-set balances { token-id: token-id, owner: sender } (- sender-balance amount))
     222                 :          12 :       (map-set balances { token-id: token-id, owner: recipient }
     223                 :          12 :         (+ amount (default-to u0 (map-get? balances { token-id: token-id, owner: recipient }))))
     224                 :             : 
     225                 :             :       ;; adjust minted-in-epoch-by so transferred tokens stay new for this epoch
     226                 :          12 :       (let (
     227                 :          12 :         (sender-prev (default-to u0 (map-get? minted-in-epoch-by {epoch: epoch, who: sender})))
     228                 :          12 :         (recipient-prev (default-to u0 (map-get? minted-in-epoch-by {epoch: epoch, who: recipient})))
     229                 :             :       )
     230                 :          12 :         (map-set minted-in-epoch-by {epoch: epoch, who: sender}
     231         [ +  - ]:          12 :           (if (> sender-prev weighted-amount) (- sender-prev weighted-amount) u0))
     232                 :          12 :         (map-set minted-in-epoch-by {epoch: epoch, who: recipient}
     233                 :          12 :           (+ recipient-prev weighted-amount))
     234                 :             :       )
     235                 :             : 
     236                 :          12 :       (print { event: "sft_transfer", token-id: token-id, amount: amount, sender: sender, recipient: recipient })
     237                 :          12 :       (ok true)
     238                 :             :     )
     239                 :             :   )
     240                 :             : )
     241                 :             : 
     242                 :           1 : (define-public (transfer-memo (token-id uint) (amount uint) (sender principal) (recipient principal) (memo (buff 34)))
     243                 :           2 :   (begin
     244                 :           2 :     (try! (transfer token-id amount sender recipient))
     245                 :           2 :     (print memo)
     246                 :           2 :     (ok true)
     247                 :             :   )
     248                 :             : )
     249                 :             : 
     250                 :           2 : (define-public (transfer-many (transfers (list 200 { token-id: uint, amount: uint, sender: principal, recipient: principal })))
     251                 :           2 :   (fold transfer-many-iter transfers (ok true))
     252                 :             : )
     253                 :             : 
     254                 :           1 : (define-public (transfer-many-memo (transfers (list 200 { token-id: uint, amount: uint, sender: principal, recipient: principal, memo: (buff 34) })))
     255                 :           1 :   (fold transfer-many-memo-iter transfers (ok true))
     256                 :             : )
     257                 :             : 
     258                 :             : ;; -------------------------
     259                 :             : ;; Claims for big from bigr
     260                 :             : ;; individual and batch supported..
     261                 :             : ;; -------------------------
     262                 :             : 
     263                 :           9 : (define-public (claim-big-reward)
     264                 :          51 :   (claim-big-reward-for-user tx-sender)
     265                 :             : )
     266                 :             : 
     267                 :           0 : (define-public (claim-big-reward-batch (users (list 100 principal)))
     268                 :           0 :   (fold claim-big-reward-batch-iter users (ok u0))
     269                 :             : )
     270                 :             : 
     271                 :           0 : (define-private (claim-big-reward-batch-iter (user principal) (acc (response uint uint)))
     272                 :           0 :   (let (
     273                 :           0 :         (a (unwrap! acc err-claims-zero-total))
     274                 :           0 :         (b (unwrap! (claim-big-reward-for-user user) err-claims-zero-total))
     275                 :             :       )
     276                 :           0 :     (ok (+ a b))
     277                 :             :   )
     278                 :             : )
     279                 :             : 
     280                 :           9 : (define-private (claim-big-reward-for-user (user principal)) ;; returns share or u0
     281                 :          51 :   (let (
     282                 :          51 :         (epoch (/ burn-block-height epoch-duration))
     283                 :          51 :         (claim-epoch (get-latest-claimable-epoch))
     284                 :          51 :         (last (default-to u0 (map-get? last-claimed-epoch { who: user })))
     285                 :          51 :         (joined (default-to epoch (map-get? join-epoch { who: user }))) ;; epoch they joined
     286                 :          51 :         (total-live (unwrap! (get-weighted-supply) err-claims-zero-total))
     287                 :          51 :         (minted-this-epoch (default-to u0 (map-get? minted-in-epoch { epoch: epoch })))
     288                 :          51 :         (total (- total-live minted-this-epoch))
     289                 :             :     )
     290         [ +  + ]:          51 :     (if (and (< last claim-epoch) (> claim-epoch joined))
     291            [ + ]:          32 :       (let (
     292                 :          32 :             (user-weighted-rep (unwrap! (get-weighted-rep user) err-claims-zero-rep))
     293                 :          32 :             (user-minted-this-epoch (default-to u0 (map-get? minted-in-epoch-by { epoch: epoch, who: user })))
     294                 :          32 :             (rep (if (> user-weighted-rep user-minted-this-epoch)
     295            [ + ]:          32 :                     (- user-weighted-rep user-minted-this-epoch)
     296            [ - ]:           0 :                     u0))
     297                 :             :           )
     298         [ +  + ]:          32 :         (if (and (> rep u0) (> total u0))
     299            [ + ]:          32 :           (let (
     300                 :          32 :               (share-scaled (/ (* (* rep (var-get reward-per-epoch)) SCALE) total))
     301                 :          32 :               (share (/ share-scaled SCALE))
     302                 :             :             )
     303                 :          32 :             (map-set last-claimed-epoch { who: user } claim-epoch)
     304                 :          32 :             (try! (contract-call? .bme006-0-treasury sip010-transfer share user none .bme000-0-governance-token))
     305                 :          31 :             (print { event: "big-claim", user: user, epoch: epoch, amount: share, reward-per-epoch: (var-get reward-per-epoch) })
     306                 :          31 :             (ok share)
     307                 :             :           )
     308            [ - ]:           0 :           (ok u0)
     309                 :             :         )
     310                 :             :       )
     311            [ + ]:          19 :       (ok u0)
     312                 :             :     )
     313                 :             :   )
     314                 :             : )
     315                 :             : 
     316                 :             : ;; ------------------------
     317                 :             : ;; Helpers
     318                 :             : ;; ------------------------
     319                 :         354 : (define-private (tag-nft (nft-token-id { token-id: uint, owner: principal }))
     320                 :         722 :   (begin
     321                 :         722 :     (if (is-some (nft-get-owner? bigr-id nft-token-id))
     322            [ + ]:         186 :       (try! (nft-burn? bigr-id nft-token-id (get owner nft-token-id)))
     323            [ + ]:         536 :       true)
     324                 :         722 :     (nft-mint? bigr-id nft-token-id (get owner nft-token-id))
     325                 :             :   )
     326                 :             : )
     327                 :             : 
     328                 :           2 : (define-private (transfer-many-iter (item { token-id: uint, amount: uint, sender: principal, recipient: principal }) (prev (response bool uint)))
     329         [ +  - ]:           6 :   (match prev ok-prev (transfer (get token-id item) (get amount item) (get sender item) (get recipient item)) err-prev prev)
     330                 :             : )
     331                 :             : 
     332                 :           1 : (define-private (transfer-many-memo-iter (item { token-id: uint, amount: uint, sender: principal, recipient: principal, memo: (buff 34) }) (prev (response bool uint)))
     333         [ +  - ]:           2 :   (match prev ok-prev (transfer-memo (get token-id item) (get amount item) (get sender item) (get recipient item) (get memo item)) err-prev prev)
     334                 :             : )
     335                 :             : 
     336                 :             : ;; dynamic weighted totals for user
     337                 :           7 : (define-read-only (get-weighted-rep (user principal))
     338                 :          32 :   (let (
     339                 :          32 :     (tiers (list u1 u2 u3 u4 u5 u6 u7 u8 u9 u10 u11 u12 u13 u14 u15 u16 u17 u18 u19 u20))
     340                 :          32 :         (result (fold add-weighted-rep-for-user tiers (tuple (acc u0) (user user))))
     341                 :             :   )
     342                 :          32 :     (ok (get acc result))
     343                 :             :   )
     344                 :             : )
     345                 :             : 
     346                 :           7 : (define-private (add-weighted-rep-for-user (token-id uint) (state (tuple (acc uint) (user principal))))
     347                 :         640 :   (let (
     348                 :         640 :     (acc (get acc state))
     349                 :         640 :     (user (get user state))
     350                 :         640 :     (bal-at-tier (default-to u0 (map-get? balances {token-id: token-id, owner: user})))
     351                 :         640 :     (weight-at-tier (default-to u1 (map-get? tier-weights token-id)))
     352                 :             :   )
     353                 :         640 :     (tuple (acc (+ acc (* bal-at-tier weight-at-tier))) (user user))
     354                 :             :   )
     355                 :             : )
     356                 :             : 
     357                 :             : ;; dynamic weighted totals for overall supply pool
     358                 :          10 : (define-read-only (get-weighted-supply)
     359                 :          52 :   (let (
     360                 :          52 :     (tiers (list u1 u2 u3 u4 u5 u6 u7 u8 u9 u10 u11 u12 u13 u14 u15 u16 u17 u18 u19 u20))
     361                 :          52 :     (result (fold add-weighted-supply-for-tier tiers u0))
     362                 :             :   )
     363                 :          52 :     (ok result)
     364                 :             :   )
     365                 :             : )
     366                 :             : 
     367                 :          10 : (define-private (add-weighted-supply-for-tier (token-id uint) (acc uint))
     368                 :        1040 :   (let (
     369                 :        1040 :     (tier-supply (default-to u0 (map-get? supplies token-id)))
     370                 :        1040 :     (weight (default-to u1 (map-get? tier-weights token-id)))
     371                 :             :   )
     372                 :        1040 :     (+ acc (* tier-supply weight))
     373                 :             :   )
     374                 :             : )
     375                 :             : 
     376                 :             : ;; --- Extension callback
     377                 :             : 
     378                 :           1 : (define-public (callback (sender principal) (memo (buff 34)))
     379                 :           1 :         (ok true)
     380                 :             : )
        

Generated by: LCOV version 2.3.2-1