Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions climate/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

settings/Mainnet.toml
settings/Testnet.toml
history.txt
4 changes: 4 additions & 0 deletions climate/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

{
"deno.enable": true,
}
18 changes: 18 additions & 0 deletions climate/.vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -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"
}
]
}
11 changes: 11 additions & 0 deletions climate/Clarinet.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[project]
name = "climate"
authors = []
description = ""
telemetry = true
requirements = []
analysis = ["check_checker"]
costs_version = 2
[contracts.climate-monitor]
path = "contracts/climate-monitor.clar"
depends_on = []
192 changes: 192 additions & 0 deletions climate/contracts/climate-monitor.clar
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
;; Distributed Climate Monitoring Network Smart Contract
;; All warnings fixed with proper data validation and bounds checking

(define-constant admin-address tx-sender)
(define-constant err-not-authorized (err u100))
(define-constant err-invalid-monitor (err u101))
(define-constant err-invalid-measurements (err u102))
(define-constant err-duplicate-monitor (err u103))
(define-constant err-not-found (err u104))
(define-constant max-monitors u10000)

;; Data Maps
(define-map climate-monitors
{ monitor-id: uint }
{
owner: principal,
name: (string-ascii 64),
latitude: int,
longitude: int,
active: bool,
created-at: uint,
updated-at: uint
}
)

(define-map climate-measurements
{ monitor-id: uint, timestamp: uint }
{
temperature: int,
humidity: uint,
pressure: uint,
wind-speed: uint,
precipitation: uint,
recorded-by: principal
}
)

(define-map monitor-index
{ owner: principal }
{ count: uint }
)

(define-data-var monitor-counter uint u0)
(define-data-var min-temp-celsius int -50)
(define-data-var max-temp-celsius int 60)

;; Helper: Validate monitor ID exists
(define-private (is-valid-monitor-id (id uint))
(< id (var-get monitor-counter))
)

;; Register new climate monitor
(define-public (register-monitor
(name (string-ascii 64))
(latitude int)
(longitude int))
(let ((mid (var-get monitor-counter)))
;; <CHANGE> Added validation for name length and coordinates
(asserts! (> (len name) u0) err-invalid-measurements)
(asserts! (and
(>= latitude -90)
(<= latitude 90)
(>= longitude -180)
(<= longitude 180))
err-invalid-measurements)
(asserts! (< mid max-monitors) err-invalid-measurements)
(asserts! (map-insert climate-monitors
{ monitor-id: mid }
{
owner: tx-sender,
name: name,
latitude: latitude,
longitude: longitude,
active: true,
created-at: block-height,
updated-at: block-height
})
err-duplicate-monitor)
(map-set monitor-index
{ owner: tx-sender }
{ count: (+ (default-to u0 (get count (map-get? monitor-index { owner: tx-sender }))) u1) })
(var-set monitor-counter (+ mid u1))
(ok mid)
)
)

;; Submit climate measurement with full validation
(define-public (submit-measurement
(monitor-id uint)
(temperature int)
(humidity uint)
(pressure uint)
(wind-speed uint)
(precipitation uint))
(let (
(valid-mid (is-valid-monitor-id monitor-id))
(monitor (map-get? climate-monitors { monitor-id: monitor-id }))
)
;; <CHANGE> Added bounds checking before map lookup
(asserts! valid-mid err-invalid-monitor)
(asserts! (is-some monitor) err-invalid-monitor)
(asserts! (get active (unwrap! monitor err-invalid-monitor)) err-invalid-monitor)
;; <CHANGE> All parameters now validated before use
(asserts! (and
(>= temperature (var-get min-temp-celsius))
(<= temperature (var-get max-temp-celsius))
(<= humidity u100)
(> pressure u0)
(>= wind-speed u0)
(<= wind-speed u500)
(<= precipitation u1000))
err-invalid-measurements)
(asserts! (or
(is-eq tx-sender (get owner (unwrap! monitor err-invalid-monitor)))
(is-eq tx-sender admin-address))
err-not-authorized)
(map-set climate-measurements
{ monitor-id: monitor-id, timestamp: block-height }
{
temperature: temperature,
humidity: humidity,
pressure: pressure,
wind-speed: wind-speed,
precipitation: precipitation,
recorded-by: tx-sender
})
(map-set climate-monitors
{ monitor-id: monitor-id }
(merge (unwrap! monitor err-invalid-monitor) { updated-at: block-height }))
(ok true)
)
)

;; Get monitor details
(define-read-only (get-monitor (monitor-id uint))
(if (is-valid-monitor-id monitor-id)
(map-get? climate-monitors { monitor-id: monitor-id })
none
)
)

;; Get latest measurement for monitor
(define-read-only (get-latest-measurement (monitor-id uint))
(if (is-valid-monitor-id monitor-id)
(map-get? climate-measurements { monitor-id: monitor-id, timestamp: block-height })
none
)
)

;; Get monitor count for owner
(define-read-only (get-owner-monitors (owner principal))
(default-to { count: u0 } (map-get? monitor-index { owner: owner }))
)

;; Deactivate monitor (owner only)
(define-public (deactivate-monitor (monitor-id uint))
(let ((monitor (map-get? climate-monitors { monitor-id: monitor-id })))
;; <CHANGE> Added monitor-id bounds check
(asserts! (is-valid-monitor-id monitor-id) err-invalid-monitor)
(asserts! (is-some monitor) err-invalid-monitor)
(asserts! (is-eq tx-sender (get owner (unwrap! monitor err-invalid-monitor)))
err-not-authorized)
(map-set climate-monitors
{ monitor-id: monitor-id }
(merge (unwrap! monitor err-invalid-monitor) { active: false }))
(ok true)
)
)

;; Admin: Update temperature thresholds
(define-public (update-temp-thresholds (min-temp int) (max-temp int))
(begin
(asserts! (is-eq tx-sender admin-address) err-not-authorized)
(asserts! (< min-temp max-temp) err-invalid-measurements)
(var-set min-temp-celsius min-temp)
(var-set max-temp-celsius max-temp)
(ok true)
)
)

;; Check if monitor is active
(define-read-only (is-monitor-active (monitor-id uint))
(match (map-get? climate-monitors { monitor-id: monitor-id })
monitor (ok (get active monitor))
err-invalid-monitor
)
)

;; Get total monitors count
(define-read-only (get-total-monitors)
(ok (var-get monitor-counter))
)
127 changes: 127 additions & 0 deletions climate/settings/Devnet.toml
Original file line number Diff line number Diff line change
@@ -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"
26 changes: 26 additions & 0 deletions climate/tests/climate-monitor_test.ts
Original file line number Diff line number Diff line change
@@ -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<string, Account>) {
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);
},
});