From 71e7da9ecaa96aca0b19713f9b956bc912ff6a75 Mon Sep 17 00:00:00 2001 From: Amobi Ndubuisi Date: Mon, 28 Oct 2024 07:23:57 -0700 Subject: [PATCH 1/9] implemented: Asset registration, ownership management --- tokenizer/.gitignore | 4 + tokenizer/.vscode/settings.json | 4 + tokenizer/.vscode/tasks.json | 18 +++++ tokenizer/Clarinet.toml | 22 ++++++ tokenizer/README.md | 0 tokenizer/contracts/rwa.clar | 118 +++++++++++++++++++++++++++++ tokenizer/settings/Devnet.toml | 127 ++++++++++++++++++++++++++++++++ tokenizer/tests/rwa_test.ts | 26 +++++++ 8 files changed, 319 insertions(+) create mode 100644 tokenizer/.gitignore create mode 100644 tokenizer/.vscode/settings.json create mode 100644 tokenizer/.vscode/tasks.json create mode 100644 tokenizer/Clarinet.toml create mode 100644 tokenizer/README.md create mode 100644 tokenizer/contracts/rwa.clar create mode 100644 tokenizer/settings/Devnet.toml create mode 100644 tokenizer/tests/rwa_test.ts diff --git a/tokenizer/.gitignore b/tokenizer/.gitignore new file mode 100644 index 0000000..f18b582 --- /dev/null +++ b/tokenizer/.gitignore @@ -0,0 +1,4 @@ + +settings/Mainnet.toml +settings/Testnet.toml +history.txt diff --git a/tokenizer/.vscode/settings.json b/tokenizer/.vscode/settings.json new file mode 100644 index 0000000..02e21eb --- /dev/null +++ b/tokenizer/.vscode/settings.json @@ -0,0 +1,4 @@ + +{ + "deno.enable": true, +} diff --git a/tokenizer/.vscode/tasks.json b/tokenizer/.vscode/tasks.json new file mode 100644 index 0000000..22af91c --- /dev/null +++ b/tokenizer/.vscode/tasks.json @@ -0,0 +1,18 @@ + +{ + "version": "2.0.0", + "tasks": [ + { + "label": "check contracts", + "group": "test", + "type": "shell", + "command": "clarinet check" + }, + { + "label": "test contracts", + "group": "test", + "type": "shell", + "command": "clarinet test" + } + ] +} diff --git a/tokenizer/Clarinet.toml b/tokenizer/Clarinet.toml new file mode 100644 index 0000000..59ba713 --- /dev/null +++ b/tokenizer/Clarinet.toml @@ -0,0 +1,22 @@ +[project] +name = "tokenizer" +authors = [] +description = "" +telemetry = true +requirements = [] +[contracts.rwa] +path = "contracts/rwa.clar" +depends_on = [] + +[repl] +costs_version = 2 +parser_version = 2 + +[repl.analysis] +passes = ["check_checker"] + +[repl.analysis.check_checker] +strict = false +trusted_sender = false +trusted_caller = false +callee_filter = false diff --git a/tokenizer/README.md b/tokenizer/README.md new file mode 100644 index 0000000..e69de29 diff --git a/tokenizer/contracts/rwa.clar b/tokenizer/contracts/rwa.clar new file mode 100644 index 0000000..8864c25 --- /dev/null +++ b/tokenizer/contracts/rwa.clar @@ -0,0 +1,118 @@ +;; Real World Asset Token Contract +;; Implements a secure tokenization system for real-world assets + +(define-constant contract-owner tx-sender) +(define-constant err-owner-only (err u100)) +(define-constant err-not-found (err u101)) +(define-constant err-already-listed (err u102)) +(define-constant err-invalid-amount (err u103)) + +;; Data Maps +(define-map assets + { asset-id: uint } + { + owner: principal, + metadata-uri: (string-ascii 256), + asset-value: uint, + is-locked: bool, + creation-height: uint + } +) + +(define-map token-balances + { owner: principal, asset-id: uint } + { balance: uint } +) + +;; SFTs per asset +(define-constant tokens-per-asset u100000) + +;; Asset Registration +(define-public (register-asset (metadata-uri (string-ascii 256)) (asset-value uint)) + (let + ( + (asset-id (get-next-asset-id)) + ) + (asserts! (is-eq tx-sender contract-owner) err-owner-only) + (map-set assets + { asset-id: asset-id } + { + owner: contract-owner, + metadata-uri: metadata-uri, + asset-value: asset-value, + is-locked: false, + creation-height: block-height + } + ) + (map-set token-balances + { owner: contract-owner, asset-id: asset-id } + { balance: tokens-per-asset } + ) + (ok asset-id) + ) +) + +;; Token Transfer +(define-public (transfer (asset-id uint) (amount uint) (recipient principal)) + (let + ( + (sender-balance (get-balance tx-sender asset-id)) + (asset (unwrap! (get-asset-info asset-id) err-not-found)) + ) + (asserts! (>= sender-balance amount) err-invalid-amount) + (asserts! (not (get is-locked asset)) err-owner-only) + + (map-set token-balances + { owner: tx-sender, asset-id: asset-id } + { balance: (- sender-balance amount) } + ) + (map-set token-balances + { owner: recipient, asset-id: asset-id } + { balance: (+ (get-balance recipient asset-id) amount) } + ) + (ok true) + ) +) + +;; Asset Locking +(define-public (lock-asset (asset-id uint)) + (let + ( + (asset (unwrap! (get-asset-info asset-id) err-not-found)) + ) + (asserts! (is-eq tx-sender contract-owner) err-owner-only) + (map-set assets + { asset-id: asset-id } + (merge asset { is-locked: true }) + ) + (ok true) + ) +) + +;; Read Functions +(define-read-only (get-asset-info (asset-id uint)) + (map-get? assets { asset-id: asset-id }) +) + +(define-read-only (get-balance (owner principal) (asset-id uint)) + (default-to u0 + (get balance + (map-get? token-balances + { owner: owner, asset-id: asset-id } + ) + ) + ) +) + +(define-read-only (get-next-asset-id) + (default-to u1 + (get-last-asset-id) + ) +) + +;; Private Functions +(define-private (get-last-asset-id) + ;; Implementation would track the last asset ID + ;; For simplicity, we're returning none here + none +) diff --git a/tokenizer/settings/Devnet.toml b/tokenizer/settings/Devnet.toml new file mode 100644 index 0000000..8a5ff75 --- /dev/null +++ b/tokenizer/settings/Devnet.toml @@ -0,0 +1,127 @@ +[network] +name = "devnet" +deployment_fee_rate = 10 + +[accounts.deployer] +mnemonic = "twice kind fence tip hidden tilt action fragile skin nothing glory cousin green tomorrow spring wrist shed math olympic multiply hip blue scout claw" +balance = 100_000_000_000_000 +# secret_key: 753b7cc01a1a2e86221266a154af739463fce51219d97e4f856cd7200c3bd2a601 +# stx_address: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM +# btc_address: mqVnk6NPRdhntvfm4hh9vvjiRkFDUuSYsH + +[accounts.wallet_1] +mnemonic = "sell invite acquire kitten bamboo drastic jelly vivid peace spawn twice guilt pave pen trash pretty park cube fragile unaware remain midnight betray rebuild" +balance = 100_000_000_000_000 +# secret_key: 7287ba251d44a4d3fd9276c88ce34c5c52a038955511cccaf77e61068649c17801 +# stx_address: ST1SJ3DTE5DN7X54YDH5D64R3BCB6A2AG2ZQ8YPD5 +# btc_address: mr1iPkD9N3RJZZxXRk7xF9d36gffa6exNC + +[accounts.wallet_2] +mnemonic = "hold excess usual excess ring elephant install account glad dry fragile donkey gaze humble truck breeze nation gasp vacuum limb head keep delay hospital" +balance = 100_000_000_000_000 +# secret_key: 530d9f61984c888536871c6573073bdfc0058896dc1adfe9a6a10dfacadc209101 +# stx_address: ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG +# btc_address: muYdXKmX9bByAueDe6KFfHd5Ff1gdN9ErG + +[accounts.wallet_3] +mnemonic = "cycle puppy glare enroll cost improve round trend wrist mushroom scorpion tower claim oppose clever elephant dinosaur eight problem before frozen dune wagon high" +balance = 100_000_000_000_000 +# secret_key: d655b2523bcd65e34889725c73064feb17ceb796831c0e111ba1a552b0f31b3901 +# stx_address: ST2JHG361ZXG51QTKY2NQCVBPPRRE2KZB1HR05NNC +# btc_address: mvZtbibDAAA3WLpY7zXXFqRa3T4XSknBX7 + +[accounts.wallet_4] +mnemonic = "board list obtain sugar hour worth raven scout denial thunder horse logic fury scorpion fold genuine phrase wealth news aim below celery when cabin" +balance = 100_000_000_000_000 +# secret_key: f9d7206a47f14d2870c163ebab4bf3e70d18f5d14ce1031f3902fbbc894fe4c701 +# stx_address: ST2NEB84ASENDXKYGJPQW86YXQCEFEX2ZQPG87ND +# btc_address: mg1C76bNTutiCDV3t9nWhZs3Dc8LzUufj8 + +[accounts.wallet_5] +mnemonic = "hurry aunt blame peanut heavy update captain human rice crime juice adult scale device promote vast project quiz unit note reform update climb purchase" +balance = 100_000_000_000_000 +# secret_key: 3eccc5dac8056590432db6a35d52b9896876a3d5cbdea53b72400bc9c2099fe801 +# stx_address: ST2REHHS5J3CERCRBEPMGH7921Q6PYKAADT7JP2VB +# btc_address: mweN5WVqadScHdA81aATSdcVr4B6dNokqx + +[accounts.wallet_6] +mnemonic = "area desk dutch sign gold cricket dawn toward giggle vibrant indoor bench warfare wagon number tiny universe sand talk dilemma pottery bone trap buddy" +balance = 100_000_000_000_000 +# secret_key: 7036b29cb5e235e5fd9b09ae3e8eec4404e44906814d5d01cbca968a60ed4bfb01 +# stx_address: ST3AM1A56AK2C1XAFJ4115ZSV26EB49BVQ10MGCS0 +# btc_address: mzxXgV6e4BZSsz8zVHm3TmqbECt7mbuErt + +[accounts.wallet_7] +mnemonic = "prevent gallery kind limb income control noise together echo rival record wedding sense uncover school version force bleak nuclear include danger skirt enact arrow" +balance = 100_000_000_000_000 +# secret_key: b463f0df6c05d2f156393eee73f8016c5372caa0e9e29a901bb7171d90dc4f1401 +# stx_address: ST3PF13W7Z0RRM42A8VZRVFQ75SV1K26RXEP8YGKJ +# btc_address: n37mwmru2oaVosgfuvzBwgV2ysCQRrLko7 + +[accounts.wallet_8] +mnemonic = "female adjust gallery certain visit token during great side clown fitness like hurt clip knife warm bench start reunion globe detail dream depend fortune" +balance = 100_000_000_000_000 +# secret_key: 6a1a754ba863d7bab14adbbc3f8ebb090af9e871ace621d3e5ab634e1422885e01 +# stx_address: ST3NBRSFKX28FQ2ZJ1MAKX58HKHSDGNV5N7R21XCP +# btc_address: n2v875jbJ4RjBnTjgbfikDfnwsDV5iUByw + +[accounts.wallet_9] +mnemonic = "shadow private easily thought say logic fault paddle word top book during ignore notable orange flight clock image wealth health outside kitten belt reform" +balance = 100_000_000_000_000 +# secret_key: de433bdfa14ec43aa1098d5be594c8ffb20a31485ff9de2923b2689471c401b801 +# stx_address: STNHKEPYEPJ8ET55ZZ0M5A34J0R3N5FM2CMMMAZ6 +# btc_address: mjSrB3wS4xab3kYqFktwBzfTdPg367ZJ2d + +[devnet] +disable_bitcoin_explorer = true +# disable_stacks_explorer = true +# disable_stacks_api = true +# working_dir = "tmp/devnet" +# stacks_node_events_observers = ["host.docker.internal:8002"] +# miner_mnemonic = "twice kind fence tip hidden tilt action fragile skin nothing glory cousin green tomorrow spring wrist shed math olympic multiply hip blue scout claw" +# miner_derivation_path = "m/44'/5757'/0'/0/0" +# orchestrator_port = 20445 +# bitcoin_node_p2p_port = 18444 +# bitcoin_node_rpc_port = 18443 +# bitcoin_node_username = "devnet" +# bitcoin_node_password = "devnet" +# bitcoin_controller_port = 18442 +# bitcoin_controller_block_time = 30_000 +# stacks_node_rpc_port = 20443 +# stacks_node_p2p_port = 20444 +# stacks_api_port = 3999 +# stacks_api_events_port = 3700 +# bitcoin_explorer_port = 8001 +# stacks_explorer_port = 8000 +# postgres_port = 5432 +# postgres_username = "postgres" +# postgres_password = "postgres" +# postgres_database = "postgres" +# bitcoin_node_image_url = "quay.io/hirosystems/bitcoind:devnet" +# stacks_node_image_url = "localhost:5000/stacks-node:devnet" +# stacks_api_image_url = "blockstack/stacks-blockchain-api:latest" +# stacks_explorer_image_url = "blockstack/explorer:latest" +# bitcoin_explorer_image_url = "quay.io/hirosystems/bitcoin-explorer:devnet" +# postgres_image_url = "postgres:alpine" + +# Send some stacking orders +[[devnet.pox_stacking_orders]] +start_at_cycle = 3 +duration = 12 +wallet = "wallet_1" +slots = 2 +btc_address = "mr1iPkD9N3RJZZxXRk7xF9d36gffa6exNC" + +[[devnet.pox_stacking_orders]] +start_at_cycle = 3 +duration = 12 +wallet = "wallet_2" +slots = 1 +btc_address = "muYdXKmX9bByAueDe6KFfHd5Ff1gdN9ErG" + +[[devnet.pox_stacking_orders]] +start_at_cycle = 3 +duration = 12 +wallet = "wallet_3" +slots = 1 +btc_address = "mvZtbibDAAA3WLpY7zXXFqRa3T4XSknBX7" diff --git a/tokenizer/tests/rwa_test.ts b/tokenizer/tests/rwa_test.ts new file mode 100644 index 0000000..9a18ae0 --- /dev/null +++ b/tokenizer/tests/rwa_test.ts @@ -0,0 +1,26 @@ + +import { Clarinet, Tx, Chain, Account, types } from 'https://deno.land/x/clarinet@v0.14.0/index.ts'; +import { assertEquals } from 'https://deno.land/std@0.90.0/testing/asserts.ts'; + +Clarinet.test({ + name: "Ensure that <...>", + async fn(chain: Chain, accounts: Map) { + let block = chain.mineBlock([ + /* + * Add transactions with: + * Tx.contractCall(...) + */ + ]); + assertEquals(block.receipts.length, 0); + assertEquals(block.height, 2); + + block = chain.mineBlock([ + /* + * Add transactions with: + * Tx.contractCall(...) + */ + ]); + assertEquals(block.receipts.length, 0); + assertEquals(block.height, 3); + }, +}); From 276e4bff1cb233e1fdcc4b7bef5918fbe54f5888 Mon Sep 17 00:00:00 2001 From: Amobi Ndubuisi Date: Mon, 28 Oct 2024 07:37:46 -0700 Subject: [PATCH 2/9] enhanced governance and kyc --- tokenizer/contracts/rwa.clar | 200 ++++++++++++++++++++++++++++++----- 1 file changed, 171 insertions(+), 29 deletions(-) diff --git a/tokenizer/contracts/rwa.clar b/tokenizer/contracts/rwa.clar index 8864c25..f2490a6 100644 --- a/tokenizer/contracts/rwa.clar +++ b/tokenizer/contracts/rwa.clar @@ -1,11 +1,17 @@ -;; Real World Asset Token Contract -;; Implements a secure tokenization system for real-world assets +;; Enhanced Real World Asset Token Contract +;; Implements advanced features for real-world asset tokenization +;; Constants (define-constant contract-owner tx-sender) (define-constant err-owner-only (err u100)) (define-constant err-not-found (err u101)) (define-constant err-already-listed (err u102)) (define-constant err-invalid-amount (err u103)) +(define-constant err-not-authorized (err u104)) +(define-constant err-kyc-required (err u105)) +(define-constant err-vote-exists (err u106)) +(define-constant err-vote-ended (err u107)) +(define-constant err-price-expired (err u108)) ;; Data Maps (define-map assets @@ -15,7 +21,9 @@ metadata-uri: (string-ascii 256), asset-value: uint, is-locked: bool, - creation-height: uint + creation-height: uint, + last-price-update: uint, + total-dividends: uint } ) @@ -24,11 +32,58 @@ { balance: uint } ) +(define-map kyc-status + { address: principal } + { + is-approved: bool, + level: uint, + expiry: uint + } +) + +(define-map proposals + { proposal-id: uint } + { + title: (string-ascii 256), + asset-id: uint, + start-height: uint, + end-height: uint, + executed: bool, + votes-for: uint, + votes-against: uint, + minimum-votes: uint + } +) + +(define-map votes + { proposal-id: uint, voter: principal } + { vote-amount: uint } +) + +(define-map dividend-claims + { asset-id: uint, claimer: principal } + { last-claimed-amount: uint } +) + +;; Price Oracle Integration +(define-map price-feeds + { asset-id: uint } + { + price: uint, + decimals: uint, + last-updated: uint, + oracle: principal + } +) + ;; SFTs per asset (define-constant tokens-per-asset u100000) -;; Asset Registration -(define-public (register-asset (metadata-uri (string-ascii 256)) (asset-value uint)) +;; Asset Registration with Enhanced Metadata +(define-public (register-asset + (metadata-uri (string-ascii 256)) + (asset-value uint) + (minimum-votes uint)) (let ( (asset-id (get-next-asset-id)) @@ -41,7 +96,9 @@ metadata-uri: metadata-uri, asset-value: asset-value, is-locked: false, - creation-height: block-height + creation-height: block-height, + last-price-update: block-height, + total-dividends: u0 } ) (map-set token-balances @@ -52,15 +109,42 @@ ) ) -;; Token Transfer +;; KYC/AML Functions +(define-public (set-kyc-status (address principal) (is-approved bool) (level uint) (expiry uint)) + (begin + (asserts! (is-eq tx-sender contract-owner) err-owner-only) + (map-set kyc-status + { address: address } + { + is-approved: is-approved, + level: level, + expiry: expiry + } + ) + (ok true) + ) +) + +(define-read-only (get-kyc-status (address principal)) + (default-to + { is-approved: false, level: u0, expiry: u0 } + (map-get? kyc-status { address: address }) + ) +) + +;; Enhanced Transfer with KYC Check (define-public (transfer (asset-id uint) (amount uint) (recipient principal)) (let ( (sender-balance (get-balance tx-sender asset-id)) (asset (unwrap! (get-asset-info asset-id) err-not-found)) + (sender-kyc (get-kyc-status tx-sender)) + (recipient-kyc (get-kyc-status recipient)) ) (asserts! (>= sender-balance amount) err-invalid-amount) (asserts! (not (get is-locked asset)) err-owner-only) + (asserts! (get is-approved sender-kyc) err-kyc-required) + (asserts! (get is-approved recipient-kyc) err-kyc-required) (map-set token-balances { owner: tx-sender, asset-id: asset-id } @@ -74,8 +158,8 @@ ) ) -;; Asset Locking -(define-public (lock-asset (asset-id uint)) +;; Dividend Distribution System +(define-public (distribute-dividends (asset-id uint) (total-amount uint)) (let ( (asset (unwrap! (get-asset-info asset-id) err-not-found)) @@ -83,36 +167,94 @@ (asserts! (is-eq tx-sender contract-owner) err-owner-only) (map-set assets { asset-id: asset-id } - (merge asset { is-locked: true }) + (merge asset + { total-dividends: (+ (get total-dividends asset) total-amount) } + ) ) (ok true) ) ) -;; Read Functions -(define-read-only (get-asset-info (asset-id uint)) - (map-get? assets { asset-id: asset-id }) -) - -(define-read-only (get-balance (owner principal) (asset-id uint)) - (default-to u0 - (get balance - (map-get? token-balances - { owner: owner, asset-id: asset-id } - ) +(define-public (claim-dividends (asset-id uint)) + (let + ( + (asset (unwrap! (get-asset-info asset-id) err-not-found)) + (balance (get-balance tx-sender asset-id)) + (last-claim (get-last-claim asset-id tx-sender)) + (total-dividends (get total-dividends asset)) + (claimable-amount (/ (* balance (- total-dividends last-claim)) tokens-per-asset)) + ) + (asserts! (> claimable-amount u0) err-invalid-amount) + (map-set dividend-claims + { asset-id: asset-id, claimer: tx-sender } + { last-claimed-amount: total-dividends } ) + ;; Transfer STX implementation here + (ok claimable-amount) ) ) -(define-read-only (get-next-asset-id) - (default-to u1 - (get-last-asset-id) +;; Governance System +(define-public (create-proposal + (asset-id uint) + (title (string-ascii 256)) + (duration uint) + (minimum-votes uint)) + (let + ( + (proposal-id (get-next-proposal-id)) + ) + (asserts! (>= (get-balance tx-sender asset-id) (/ tokens-per-asset u10)) err-not-authorized) + (map-set proposals + { proposal-id: proposal-id } + { + title: title, + asset-id: asset-id, + start-height: block-height, + end-height: (+ block-height duration), + executed: false, + votes-for: u0, + votes-against: u0, + minimum-votes: minimum-votes + } + ) + (ok proposal-id) ) ) -;; Private Functions -(define-private (get-last-asset-id) - ;; Implementation would track the last asset ID - ;; For simplicity, we're returning none here - none +(define-public (vote + (proposal-id uint) + (vote-for bool) + (amount uint)) + (let + ( + (proposal (unwrap! (get-proposal proposal-id) err-not-found)) + (asset-id (get asset-id proposal)) + (balance (get-balance tx-sender asset-id)) + ) + (asserts! (>= balance amount) err-invalid-amount) + (asserts! (< block-height (get end-height proposal)) err-vote-ended) + (asserts! (is-none (get-vote proposal-id tx-sender)) err-vote-exists) + + (map-set votes + { proposal-id: proposal-id, voter: tx-sender } + { vote-amount: amount } + ) + (map-set proposals + { proposal-id: proposal-id } + (merge proposal + { + votes-for: (if vote-for + (+ (get votes-for proposal) amount) + (get votes-for proposal) + ), + votes-against: (if vote-for + (get votes-against proposal) + (+ (get votes-against proposal) amount) + ) + } + ) + ) + (ok true) + ) ) From b4ebcec8f420a274d9b0b3770a70950d5fa05338 Mon Sep 17 00:00:00 2001 From: Amobi Ndubuisi Date: Mon, 28 Oct 2024 07:51:39 -0700 Subject: [PATCH 3/9] integrated oracle --- tokenizer/contracts/rwa.clar | 43 ++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tokenizer/contracts/rwa.clar b/tokenizer/contracts/rwa.clar index f2490a6..61d240c 100644 --- a/tokenizer/contracts/rwa.clar +++ b/tokenizer/contracts/rwa.clar @@ -258,3 +258,46 @@ (ok true) ) ) + +;; Price Oracle Integration +(define-public (update-price (asset-id uint) (new-price uint)) + (let + ( + (price-feed (unwrap! (get-price-feed asset-id) err-not-found)) + ) + (asserts! (is-eq tx-sender (get oracle price-feed)) err-not-authorized) + (map-set price-feeds + { asset-id: asset-id } + (merge price-feed + { + price: new-price, + last-updated: block-height + } + ) + ) + (ok true) + ) +) + +;; Read Functions +(define-read-only (get-asset-info (asset-id uint)) + (map-get? assets { asset-id: asset-id }) +) + +(define-read-only (get-balance (owner principal) (asset-id uint)) + (default-to u0 + (get balance + (map-get? token-balances + { owner: owner, asset-id: asset-id } + ) + ) + ) +) + +(define-read-only (get-proposal (proposal-id uint)) + (map-get? proposals { proposal-id: proposal-id }) +) + +(define-read-only (get-vote (proposal-id uint) (voter principal)) + (map-get? votes { proposal-id: proposal-id, voter: voter }) +) From 4de28dd96c15a4e8296809aeadf08feaf198fda7 Mon Sep 17 00:00:00 2001 From: Amobi Ndubuisi Date: Mon, 28 Oct 2024 07:56:06 -0700 Subject: [PATCH 4/9] fixed all error --- tokenizer/contracts/rwa.clar | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tokenizer/contracts/rwa.clar b/tokenizer/contracts/rwa.clar index 61d240c..75e81c3 100644 --- a/tokenizer/contracts/rwa.clar +++ b/tokenizer/contracts/rwa.clar @@ -301,3 +301,38 @@ (define-read-only (get-vote (proposal-id uint) (voter principal)) (map-get? votes { proposal-id: proposal-id, voter: voter }) ) + +(define-read-only (get-price-feed (asset-id uint)) + (map-get? price-feeds { asset-id: asset-id }) +) + +(define-read-only (get-last-claim (asset-id uint) (claimer principal)) + (default-to u0 + (get last-claimed-amount + (map-get? dividend-claims + { asset-id: asset-id, claimer: claimer } + ) + ) + ) +) + +;; Private Functions +(define-private (get-next-asset-id) + (default-to u1 + (get-last-asset-id) + ) +) + +(define-private (get-next-proposal-id) + (default-to u1 + (get-last-proposal-id) + ) +) + +(define-private (get-last-asset-id) + none +) + +(define-private (get-last-proposal-id) + none +) From 077b6fe42adc445f8aad201d1bdec245db1621db Mon Sep 17 00:00:00 2001 From: Amobi Ndubuisi Date: Mon, 28 Oct 2024 08:02:50 -0700 Subject: [PATCH 5/9] added input validation constants, and helper function --- tokenizer/contracts/rwa.clar | 258 ++++++++++++++++------------------- 1 file changed, 116 insertions(+), 142 deletions(-) diff --git a/tokenizer/contracts/rwa.clar b/tokenizer/contracts/rwa.clar index 75e81c3..89093a6 100644 --- a/tokenizer/contracts/rwa.clar +++ b/tokenizer/contracts/rwa.clar @@ -13,6 +13,23 @@ (define-constant err-vote-ended (err u107)) (define-constant err-price-expired (err u108)) +(define-constant MAX-ASSET-VALUE u1000000000000) ;; 1 trillion +(define-constant MIN-ASSET-VALUE u1000) ;; 1 thousand +(define-constant MAX-DURATION u144) ;; ~1 day in blocks +(define-constant MIN-DURATION u12) ;; ~1 hour in blocks +(define-constant MAX-KYC-LEVEL u5) +(define-constant MAX-EXPIRY u52560) ;; ~1 year in blocks + +;; Add new error codes +(define-constant err-invalid-uri (err u110)) +(define-constant err-invalid-value (err u111)) +(define-constant err-invalid-duration (err u112)) +(define-constant err-invalid-kyc-level (err u113)) +(define-constant err-invalid-expiry (err u114)) +(define-constant err-invalid-votes (err u115)) +(define-constant err-invalid-address (err u116)) +(define-constant err-invalid-title (err u117)) + ;; Data Maps (define-map assets { asset-id: uint } @@ -65,7 +82,6 @@ { last-claimed-amount: uint } ) -;; Price Oracle Integration (define-map price-feeds { asset-id: uint } { @@ -79,102 +95,79 @@ ;; SFTs per asset (define-constant tokens-per-asset u100000) -;; Asset Registration with Enhanced Metadata -(define-public (register-asset - (metadata-uri (string-ascii 256)) - (asset-value uint) - (minimum-votes uint)) - (let - ( - (asset-id (get-next-asset-id)) - ) - (asserts! (is-eq tx-sender contract-owner) err-owner-only) - (map-set assets - { asset-id: asset-id } - { - owner: contract-owner, - metadata-uri: metadata-uri, - asset-value: asset-value, - is-locked: false, - creation-height: block-height, - last-price-update: block-height, - total-dividends: u0 - } - ) - (map-set token-balances - { owner: contract-owner, asset-id: asset-id } - { balance: tokens-per-asset } - ) - (ok asset-id) +;; Helper functions for input validation +(define-private (validate-asset-value (value uint)) + (and + (>= value MIN-ASSET-VALUE) + (<= value MAX-ASSET-VALUE) ) ) -;; KYC/AML Functions -(define-public (set-kyc-status (address principal) (is-approved bool) (level uint) (expiry uint)) - (begin - (asserts! (is-eq tx-sender contract-owner) err-owner-only) - (map-set kyc-status - { address: address } - { - is-approved: is-approved, - level: level, - expiry: expiry - } - ) - (ok true) +(define-private (validate-duration (duration uint)) + (and + (>= duration MIN-DURATION) + (<= duration MAX-DURATION) + ) +) + +(define-private (validate-kyc-level (level uint)) + (<= level MAX-KYC-LEVEL) +) + +(define-private (validate-expiry (expiry uint)) + (and + (> expiry block-height) + (<= (- expiry block-height) MAX-EXPIRY) ) ) -(define-read-only (get-kyc-status (address principal)) - (default-to - { is-approved: false, level: u0, expiry: u0 } - (map-get? kyc-status { address: address }) +(define-private (validate-minimum-votes (vote-count uint)) + (and + (> vote-count u0) + (<= vote-count tokens-per-asset) ) ) -;; Enhanced Transfer with KYC Check -(define-public (transfer (asset-id uint) (amount uint) (recipient principal)) - (let - ( - (sender-balance (get-balance tx-sender asset-id)) - (asset (unwrap! (get-asset-info asset-id) err-not-found)) - (sender-kyc (get-kyc-status tx-sender)) - (recipient-kyc (get-kyc-status recipient)) - ) - (asserts! (>= sender-balance amount) err-invalid-amount) - (asserts! (not (get is-locked asset)) err-owner-only) - (asserts! (get is-approved sender-kyc) err-kyc-required) - (asserts! (get is-approved recipient-kyc) err-kyc-required) - - (map-set token-balances - { owner: tx-sender, asset-id: asset-id } - { balance: (- sender-balance amount) } - ) - (map-set token-balances - { owner: recipient, asset-id: asset-id } - { balance: (+ (get-balance recipient asset-id) amount) } - ) - (ok true) +(define-private (validate-metadata-uri (uri (string-ascii 256))) + (and + (> (len uri) u0) + (<= (len uri) u256) ) ) -;; Dividend Distribution System -(define-public (distribute-dividends (asset-id uint) (total-amount uint)) - (let - ( - (asset (unwrap! (get-asset-info asset-id) err-not-found)) - ) +(define-public (register-asset + (metadata-uri (string-ascii 256)) + (asset-value uint)) + (begin (asserts! (is-eq tx-sender contract-owner) err-owner-only) - (map-set assets - { asset-id: asset-id } - (merge asset - { total-dividends: (+ (get total-dividends asset) total-amount) } + (asserts! (validate-metadata-uri metadata-uri) err-invalid-uri) + (asserts! (validate-asset-value asset-value) err-invalid-value) + + (let + ((asset-id (get-next-asset-id))) + (map-set assets + { asset-id: asset-id } + { + owner: contract-owner, + metadata-uri: metadata-uri, + asset-value: asset-value, + is-locked: false, + creation-height: block-height, + last-price-update: block-height, + total-dividends: u0 + } + ) + (map-set token-balances + { owner: contract-owner, asset-id: asset-id } + { balance: tokens-per-asset } ) + (ok asset-id) ) - (ok true) ) ) + + (define-public (claim-dividends (asset-id uint)) (let ( @@ -185,40 +178,40 @@ (claimable-amount (/ (* balance (- total-dividends last-claim)) tokens-per-asset)) ) (asserts! (> claimable-amount u0) err-invalid-amount) - (map-set dividend-claims + (ok (map-set dividend-claims { asset-id: asset-id, claimer: tx-sender } { last-claimed-amount: total-dividends } - ) - ;; Transfer STX implementation here - (ok claimable-amount) + )) ) ) -;; Governance System (define-public (create-proposal (asset-id uint) (title (string-ascii 256)) (duration uint) (minimum-votes uint)) - (let - ( - (proposal-id (get-next-proposal-id)) - ) + (begin + (asserts! (validate-duration duration) err-invalid-duration) + (asserts! (validate-minimum-votes minimum-votes) err-invalid-votes) + (asserts! (validate-metadata-uri title) err-invalid-title) (asserts! (>= (get-balance tx-sender asset-id) (/ tokens-per-asset u10)) err-not-authorized) - (map-set proposals - { proposal-id: proposal-id } - { - title: title, - asset-id: asset-id, - start-height: block-height, - end-height: (+ block-height duration), - executed: false, - votes-for: u0, - votes-against: u0, - minimum-votes: minimum-votes - } + + (let + ((proposal-id (get-next-proposal-id))) + (ok (map-set proposals + { proposal-id: proposal-id } + { + title: title, + asset-id: asset-id, + start-height: block-height, + end-height: (+ block-height duration), + executed: false, + votes-for: u0, + votes-against: u0, + minimum-votes: minimum-votes + } + )) ) - (ok proposal-id) ) ) @@ -232,50 +225,31 @@ (asset-id (get asset-id proposal)) (balance (get-balance tx-sender asset-id)) ) - (asserts! (>= balance amount) err-invalid-amount) - (asserts! (< block-height (get end-height proposal)) err-vote-ended) - (asserts! (is-none (get-vote proposal-id tx-sender)) err-vote-exists) - - (map-set votes - { proposal-id: proposal-id, voter: tx-sender } - { vote-amount: amount } - ) - (map-set proposals - { proposal-id: proposal-id } - (merge proposal - { - votes-for: (if vote-for - (+ (get votes-for proposal) amount) - (get votes-for proposal) - ), - votes-against: (if vote-for - (get votes-against proposal) - (+ (get votes-against proposal) amount) - ) - } + (begin + (asserts! (>= balance amount) err-invalid-amount) + (asserts! (< block-height (get end-height proposal)) err-vote-ended) + (asserts! (is-none (get-vote proposal-id tx-sender)) err-vote-exists) + + (map-set votes + { proposal-id: proposal-id, voter: tx-sender } + { vote-amount: amount } ) - ) - (ok true) - ) -) - -;; Price Oracle Integration -(define-public (update-price (asset-id uint) (new-price uint)) - (let - ( - (price-feed (unwrap! (get-price-feed asset-id) err-not-found)) - ) - (asserts! (is-eq tx-sender (get oracle price-feed)) err-not-authorized) - (map-set price-feeds - { asset-id: asset-id } - (merge price-feed - { - price: new-price, - last-updated: block-height - } + (ok (map-set proposals + { proposal-id: proposal-id } + (merge proposal + { + votes-for: (if vote-for + (+ (get votes-for proposal) amount) + (get votes-for proposal) + ), + votes-against: (if vote-for + (get votes-against proposal) + (+ (get votes-against proposal) amount) + ) + } + )) ) ) - (ok true) ) ) From c48a95c677f3fdba911afc671e7101b0f84ea73b Mon Sep 17 00:00:00 2001 From: Amobi Ndubuisi Date: Mon, 28 Oct 2024 08:51:20 -0700 Subject: [PATCH 6/9] Added a comprehensive readme --- tokenizer/README.md | 77 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/tokenizer/README.md b/tokenizer/README.md index e69de29..a553a38 100644 --- a/tokenizer/README.md +++ b/tokenizer/README.md @@ -0,0 +1,77 @@ +## Overview + +The Real World Asset Tokenization Smart Contract enables the representation of physical assets (e.g., real estate, art, precious metals) as digital tokens. This contract introduces features like asset registration, dividend distribution, compliance via KYC checks, and voting-based governance. Tokenization allows fractional ownership, enabling smaller investors to invest in traditionally illiquid assets. + +## Features + +- **Asset Registration**: Register assets with metadata, value, and ownership details. +- **Fractional Ownership**: Assets are divided into smaller, tradeable tokens. +- **Dividend Distribution**: Distribute dividends to token holders based on ownership. +- **KYC Compliance**: Enforce KYC levels to ensure only verified addresses can hold assets. +- **Governance**: Implement on-chain voting for asset-related decisions. +- **Price Feed Integration**: Track asset prices and update them periodically. + +## Contract Components + +### Constants +- Define essential values like the maximum asset value, durations for proposals, and KYC levels. +- Include error codes for ease of debugging and user-friendly error handling. + +### Data Maps +- `assets`: Stores asset details, including ownership, metadata, and valuation. +- `token-balances`: Manages token balances for each asset holder. +- `kyc-status`: Keeps track of KYC approval levels for participants. +- `proposals` and `votes`: Implements governance features, allowing token holders to create and vote on proposals. +- `dividend-claims`: Records dividend claims for individual asset holders. +- `price-feeds`: Maintains price data for assets. + +### Helper Functions +- **Input Validators**: Ensure input values (e.g., asset value, duration) conform to specified criteria. +- **Utility Functions**: Retrieve asset and proposal IDs and verify KYC compliance. + +## Functions + +### Public Functions + +- **Asset Management** + - `register-asset(metadata-uri, asset-value)`: Registers a new asset, specifying metadata and valuation. Only the contract owner can register assets. + - `claim-dividends(asset-id)`: Allows token holders to claim dividends based on their share of ownership. + +- **Governance and Voting** + - `create-proposal(asset-id, title, duration, minimum-votes)`: Creates a proposal for asset-related decisions. A minimum amount of tokens is required to participate. + - `vote(proposal-id, vote-for, amount)`: Enables token holders to vote on proposals by committing a specific amount of tokens. + +### Read-Only Functions + +- `get-asset-info(asset-id)`: Retrieves detailed information about a specific asset. +- `get-balance(owner, asset-id)`: Checks the token balance of a particular owner for an asset. +- `get-proposal(proposal-id)`: Fetches details of a specific proposal. +- `get-price-feed(asset-id)`: Returns the current price information for an asset. + +### Private Functions + +- `get-next-asset-id()`, `get-next-proposal-id()`: Retrieve unique IDs for new assets or proposals. +- `validate-asset-value(value)`, `validate-duration(duration)`, etc.: Helper functions to validate input values, improving contract security and integrity. + +## Setup and Deployment + +1. **Prerequisites**: Ensure you have Clarity tools and a testnet wallet setup. +2. **Compilation**: Compile the contract using a Clarity-compatible development environment. +3. **Deployment**: Deploy the contract to the Stacks testnet or mainnet with a wallet holding enough STX for deployment fees. +4. **Post-deployment Configuration**: Configure price feeds and KYC information for users as needed. + + +## Error Codes + +| Code | Meaning | +|-----------------------|-----------------------------------| +| `err-owner-only` | Only the contract owner can execute this action | +| `err-not-found` | Asset or proposal not found | +| `err-already-listed` | Asset is already registered | +| `err-invalid-amount` | Invalid amount specified | +| `err-not-authorized` | Action requires additional permissions | +| `err-kyc-required` | KYC verification required | +| `err-invalid-duration`| Duration exceeds limits | +| `err-invalid-votes` | Minimum votes not met | + +-- Author: Amobi Ndubuisi From b3870eb7804146ece44695001061eafec59549b1 Mon Sep 17 00:00:00 2001 From: Amobi Ndubuisi Date: Sat, 2 Nov 2024 17:35:34 +0100 Subject: [PATCH 7/9] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f5e63ba..f519eac 100644 --- a/README.md +++ b/README.md @@ -1 +1 @@ -Comprehensive Asset Tokenization Guideline +# xxx From e3b14e8f84b2c4b27980c3c92862a97cced4f889 Mon Sep 17 00:00:00 2001 From: Amobi Ndubuisi Date: Sat, 2 Nov 2024 17:38:22 +0100 Subject: [PATCH 8/9] Update rwa.clar --- tokenizer/contracts/rwa.clar | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tokenizer/contracts/rwa.clar b/tokenizer/contracts/rwa.clar index 89093a6..3e708ff 100644 --- a/tokenizer/contracts/rwa.clar +++ b/tokenizer/contracts/rwa.clar @@ -1,4 +1,4 @@ -;; Enhanced Real World Asset Token Contract +;;Real World Asset Token Contract ;; Implements advanced features for real-world asset tokenization ;; Constants From 8e315d1e198bfab03e32fd7fc79d484ebfbf2152 Mon Sep 17 00:00:00 2001 From: Amobi Ndubuisi Date: Sat, 2 Nov 2024 17:43:56 +0100 Subject: [PATCH 9/9] Update README.md --- README.md | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f519eac..cecfbeb 100644 --- a/README.md +++ b/README.md @@ -1 +1,92 @@ -# xxx +# Real World Asset Token Smart Contract + +## Overview + +This Clarity smart contract implements a comprehensive system for tokenizing real-world assets on the Stacks blockchain. It provides advanced features for asset management, token distribution, governance, and price feed integration. + +## Key Features + +1. Asset Registration and Management +2. Semi-Fungible Token (SFT) Implementation +3. Dividend Distribution System +4. Governance Proposal and Voting Mechanism +5. KYC (Know Your Customer) Integration +6. Price Feed Oracle Integration +7. Robust Input Validation and Error Handling + +## Core Functions + +### Asset Management + +- `register-asset`: Register a new real-world asset +- `claim-dividends`: Claim dividends for a specific asset + +### Governance + +- `create-proposal`: Create a new governance proposal +- `vote`: Vote on an existing proposal + +### Read-Only Functions + +- `get-asset-info`: Retrieve information about a specific asset +- `get-balance`: Get the token balance of an address for a specific asset +- `get-proposal`: Retrieve details of a specific proposal +- `get-vote`: Get voting information for a specific proposal and voter +- `get-price-feed`: Retrieve price feed information for an asset +- `get-last-claim`: Get the last dividend claim amount for an address + +## Data Structures + +- `assets`: Stores metadata and state for each registered asset +- `token-balances`: Tracks token ownership for each asset +- `kyc-status`: Manages KYC approval status and levels for addresses +- `proposals`: Stores governance proposal details +- `votes`: Records votes cast on proposals +- `dividend-claims`: Tracks dividend claims for each asset and address +- `price-feeds`: Manages price feed data for assets + +## Constants + +- `MAX-ASSET-VALUE`: 1 trillion (1,000,000,000,000) +- `MIN-ASSET-VALUE`: 1 thousand (1,000) +- `MAX-DURATION`: ~1 day in blocks (144) +- `MIN-DURATION`: ~1 hour in blocks (12) +- `MAX-KYC-LEVEL`: 5 +- `MAX-EXPIRY`: ~1 year in blocks (52,560) +- `tokens-per-asset`: 100,000 (number of SFTs per asset) + +## Error Handling + +The contract uses a comprehensive set of error codes for various scenarios, ensuring proper validation and error reporting. Some key error codes include: + +- `err-owner-only`: Operation restricted to contract owner +- `err-not-found`: Requested item not found +- `err-invalid-amount`: Invalid token or value amount +- `err-not-authorized`: User not authorized for the operation +- `err-kyc-required`: KYC approval required for the operation + +## Input Validation + +The contract implements several helper functions for input validation: + +- `validate-asset-value`: Ensures asset value is within allowed range +- `validate-duration`: Checks if proposal duration is valid +- `validate-kyc-level`: Verifies KYC level is within allowed range +- `validate-expiry`: Ensures expiry is set to a valid future block height +- `validate-minimum-votes`: Checks if minimum vote count is valid +- `validate-metadata-uri`: Verifies metadata URI length and format + +## Usage + +To use this contract, deploy it to the Stacks blockchain and interact with it using the provided public functions. Ensure that users have the necessary permissions and meet the required conditions (e.g., KYC approval) for their intended actions. + +## Security Considerations + +- Only the contract owner can register new assets +- KYC checks are implemented for certain operations +- Input validation is performed to prevent invalid data entry +- Proposal voting has minimum token holding requirements + +## Note on Completeness + +This README is based on the provided contract snippet. Some functionalities, such as KYC management and price feed updates, may not be fully represented here if they were not included in the provided code.