LCOV - code coverage report
Current view: top level - contracts/extensions - bme030-0-reputation-token.clar (source / functions) Coverage Total Hit
Test: lcov.info Lines: 81.2 % 197 160
Test Date: 2025-11-05 10:54:00 Functions: 68.8 % 32 22
Branches: 57.1 % 42 24

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

Generated by: LCOV version 2.3.2-1