Files
dchain/contracts/name_registry/name_registry.wat
vsecoder 7e7393e4f8 chore: initial commit for v0.0.1
DChain single-node blockchain + React Native messenger client.

Core:
- PBFT consensus with multi-sig validator admission + equivocation slashing
- BadgerDB + schema migration scaffold (CurrentSchemaVersion=0)
- libp2p gossipsub (tx/v1, blocks/v1, relay/v1, version/v1)
- Native Go contracts (username_registry) alongside WASM (wazero)
- WebSocket gateway with topic-based fanout + Ed25519-nonce auth
- Relay mailbox with NaCl envelope encryption (X25519 + Ed25519)
- Prometheus /metrics, per-IP rate limit, body-size cap

Deployment:
- Single-node compose (deploy/single/) with Caddy TLS + optional Prometheus
- 3-node dev compose (docker-compose.yml) with mocked internet topology
- 3-validator prod compose (deploy/prod/) for federation
- Auto-update from Gitea via /api/update-check + systemd timer
- Build-time version injection (ldflags → node --version)
- UI / Swagger toggle flags (DCHAIN_DISABLE_UI, DCHAIN_DISABLE_SWAGGER)

Client (client-app/):
- Expo / React Native / NativeWind
- E2E NaCl encryption, typing indicator, contact requests
- Auto-discovery of canonical contracts, chain_id aware, WS reconnect on node switch

Documentation:
- README.md, CHANGELOG.md, CONTEXT.md
- deploy/single/README.md with 6 operator scenarios
- deploy/UPDATE_STRATEGY.md with 4-layer forward-compat design
- docs/contracts/*.md per contract
2026-04-17 14:16:44 +03:00

302 lines
12 KiB
Plaintext

(module
;; Name Registry smart contract
;;
;; Maps human-readable names → owner public keys on-chain.
;; Each name can only be registered once; only the current owner can
;; transfer or release it.
;;
;; Methods (all void, no WASM return values):
;; register(name string) — claim a name for the caller
;; resolve(name string) — log the current owner pubkey
;; transfer(name string, new_owner string) — give name to another pubkey
;; release(name string) — delete name registration
;;
;; State keys: the raw name bytes → owner pubkey bytes.
;; All args come in via env.get_arg_str / env.get_arg_u64.
;; ── imports ──────────────────────────────────────────────────────────────
(import "env" "get_arg_str"
(func $get_arg_str (param i32 i32 i32) (result i32)))
(import "env" "get_caller"
(func $get_caller (param i32 i32) (result i32)))
(import "env" "get_state"
(func $get_state (param i32 i32 i32 i32) (result i32)))
(import "env" "set_state"
(func $set_state (param i32 i32 i32 i32)))
(import "env" "log"
(func $log (param i32 i32)))
;; ── memory ───────────────────────────────────────────────────────────────
;; Offset Size Purpose
;; 0x000 64 arg[0] buffer — name (max 64 bytes)
;; 0x040 128 arg[1] buffer — new_owner pubkey (max 128 bytes)
;; 0x0C0 128 caller pubkey buffer
;; 0x140 128 state-read buffer (existing owner)
;; 0x200 ~128 verbose log prefix strings
;; 0x300 256 scratch buffer — build "prefix: name" log messages
(memory (export "memory") 1)
;; ── verbose log prefix strings ───────────────────────────────────────────
;; Each entry is a human-readable prefix ending with ": " so that the
;; log message becomes "prefix: <argument>" — readable in the explorer.
;;
;; "registered: " 12 bytes @ 0x200
;; "name taken: " 12 bytes @ 0x20C
;; "not found: " 11 bytes @ 0x218
;; "owner: " 7 bytes @ 0x224
;; "transferred: " 13 bytes @ 0x22C
;; "unauthorized: " 14 bytes @ 0x23A
;; "released: " 10 bytes @ 0x249
(data (i32.const 0x200) "registered: ")
(data (i32.const 0x20C) "name taken: ")
(data (i32.const 0x218) "not found: ")
(data (i32.const 0x224) "owner: ")
(data (i32.const 0x22C) "transferred: ")
(data (i32.const 0x23A) "unauthorized: ")
(data (i32.const 0x249) "released: ")
;; ── helpers ───────────────────────────────────────────────────────────────
;; $memcpy: copy len bytes from src to dst
(func $memcpy (param $dst i32) (param $src i32) (param $len i32)
(local $i i32)
(local.set $i (i32.const 0))
(block $break
(loop $loop
(br_if $break (i32.ge_u (local.get $i) (local.get $len)))
(i32.store8
(i32.add (local.get $dst) (local.get $i))
(i32.load8_u (i32.add (local.get $src) (local.get $i))))
(local.set $i (i32.add (local.get $i) (i32.const 1)))
(br $loop)
)
)
)
;; $log_prefix_name: build "<prefix><suffix>" in scratch buf 0x300 and log it.
;; prefixPtr / prefixLen — the prefix string (e.g. "registered: ", 12 bytes)
;; suffixPtr / suffixLen — the name / pubkey to append
(func $log_prefix_name
(param $prefixPtr i32) (param $prefixLen i32)
(param $suffixPtr i32) (param $suffixLen i32)
;; copy prefix → scratch[0]
(call $memcpy (i32.const 0x300) (local.get $prefixPtr) (local.get $prefixLen))
;; copy suffix → scratch[prefixLen]
(call $memcpy
(i32.add (i32.const 0x300) (local.get $prefixLen))
(local.get $suffixPtr)
(local.get $suffixLen))
;; log scratch[0 .. prefixLen+suffixLen)
(call $log
(i32.const 0x300)
(i32.add (local.get $prefixLen) (local.get $suffixLen)))
)
;; $bytes_equal: compare mem[aPtr..aPtr+len) with mem[bPtr..bPtr+len)
;; Params: aPtr(0) bPtr(1) len(2)
;; Result: i32 1 = equal, 0 = not equal
(func $bytes_equal (param i32 i32 i32) (result i32)
(local $i i32)
(local $same i32)
(local.set $same (i32.const 1))
(local.set $i (i32.const 0))
(block $break
(loop $loop
(br_if $break (i32.ge_u (local.get $i) (local.get 2)))
(if (i32.ne
(i32.load8_u (i32.add (local.get 0) (local.get $i)))
(i32.load8_u (i32.add (local.get 1) (local.get $i))))
(then (local.set $same (i32.const 0)) (br $break))
)
(local.set $i (i32.add (local.get $i) (i32.const 1)))
(br $loop)
)
)
(local.get $same)
)
;; ── register(name) ────────────────────────────────────────────────────────
;; Claims `name` for the caller.
;; Logs "registered: <name>" on success, "name taken: <name>" on conflict.
(func (export "register")
(local $nameLen i32)
(local $callerLen i32)
(local $existingLen i32)
;; Read name into 0x000, max 64 bytes
(local.set $nameLen
(call $get_arg_str (i32.const 0) (i32.const 0x000) (i32.const 64)))
(if (i32.eqz (local.get $nameLen)) (then return))
;; Check if name is already taken
(local.set $existingLen
(call $get_state
(i32.const 0x000) (local.get $nameLen)
(i32.const 0x140) (i32.const 128)))
(if (i32.gt_u (local.get $existingLen) (i32.const 0))
(then
(call $log_prefix_name
(i32.const 0x20C) (i32.const 12) ;; "name taken: "
(i32.const 0x000) (local.get $nameLen))
return
)
)
;; Store: state[name] = caller_pubkey
(local.set $callerLen
(call $get_caller (i32.const 0x0C0) (i32.const 128)))
(call $set_state
(i32.const 0x000) (local.get $nameLen)
(i32.const 0x0C0) (local.get $callerLen))
(call $log_prefix_name
(i32.const 0x200) (i32.const 12) ;; "registered: "
(i32.const 0x000) (local.get $nameLen))
)
;; ── resolve(name) ─────────────────────────────────────────────────────────
;; Logs "owner: <pubkey>" for the registered name, or "not found: <name>".
(func (export "resolve")
(local $nameLen i32)
(local $ownerLen i32)
(local.set $nameLen
(call $get_arg_str (i32.const 0) (i32.const 0x000) (i32.const 64)))
(if (i32.eqz (local.get $nameLen)) (then return))
(local.set $ownerLen
(call $get_state
(i32.const 0x000) (local.get $nameLen)
(i32.const 0x140) (i32.const 128)))
(if (i32.eqz (local.get $ownerLen))
(then
(call $log_prefix_name
(i32.const 0x218) (i32.const 11) ;; "not found: "
(i32.const 0x000) (local.get $nameLen))
return
)
)
;; Log "owner: <pubkey bytes>"
;; The pubkey stored in state is the raw caller pubkey bytes that the
;; host wrote via get_caller — these are the hex-encoded public key
;; string bytes, so the log will show the readable hex address.
(call $log_prefix_name
(i32.const 0x224) (i32.const 7) ;; "owner: "
(i32.const 0x140) (local.get $ownerLen))
)
;; ── transfer(name, new_owner) ─────────────────────────────────────────────
;; Transfers ownership of `name` to `new_owner`.
;; Only the current owner may call this (or anyone if name is unregistered).
;; Logs "transferred: <name>" on success, "unauthorized: <name>" otherwise.
(func (export "transfer")
(local $nameLen i32)
(local $newOwnerLen i32)
(local $callerLen i32)
(local $existingLen i32)
(local.set $nameLen
(call $get_arg_str (i32.const 0) (i32.const 0x000) (i32.const 64)))
(if (i32.eqz (local.get $nameLen)) (then return))
(local.set $newOwnerLen
(call $get_arg_str (i32.const 1) (i32.const 0x040) (i32.const 128)))
(if (i32.eqz (local.get $newOwnerLen)) (then return))
;; Read existing owner into 0x140
(local.set $existingLen
(call $get_state
(i32.const 0x000) (local.get $nameLen)
(i32.const 0x140) (i32.const 128)))
;; If not registered, anyone can claim → register directly for new_owner
(if (i32.eqz (local.get $existingLen))
(then
(call $set_state
(i32.const 0x000) (local.get $nameLen)
(i32.const 0x040) (local.get $newOwnerLen))
(call $log_prefix_name
(i32.const 0x22C) (i32.const 13) ;; "transferred: "
(i32.const 0x000) (local.get $nameLen))
return
)
)
;; Verify caller == existing owner
(local.set $callerLen
(call $get_caller (i32.const 0x0C0) (i32.const 128)))
(if (i32.eqz
(call $bytes_equal
(i32.const 0x0C0) (i32.const 0x140)
(if (result i32) (i32.ne (local.get $callerLen) (local.get $existingLen))
(then (i32.const 0)) ;; length mismatch → not equal
(else (local.get $callerLen)))))
(then
(call $log_prefix_name
(i32.const 0x23A) (i32.const 14) ;; "unauthorized: "
(i32.const 0x000) (local.get $nameLen))
return
)
)
;; Authorized — update owner
(call $set_state
(i32.const 0x000) (local.get $nameLen)
(i32.const 0x040) (local.get $newOwnerLen))
(call $log_prefix_name
(i32.const 0x22C) (i32.const 13) ;; "transferred: "
(i32.const 0x000) (local.get $nameLen))
)
;; ── release(name) ─────────────────────────────────────────────────────────
;; Removes a name registration. Only the current owner may call this.
;; Logs "released: <name>" on success.
(func (export "release")
(local $nameLen i32)
(local $callerLen i32)
(local $existingLen i32)
(local.set $nameLen
(call $get_arg_str (i32.const 0) (i32.const 0x000) (i32.const 64)))
(if (i32.eqz (local.get $nameLen)) (then return))
(local.set $existingLen
(call $get_state
(i32.const 0x000) (local.get $nameLen)
(i32.const 0x140) (i32.const 128)))
(if (i32.eqz (local.get $existingLen))
(then
(call $log_prefix_name
(i32.const 0x218) (i32.const 11) ;; "not found: "
(i32.const 0x000) (local.get $nameLen))
return
)
)
;; Verify caller == owner
(local.set $callerLen
(call $get_caller (i32.const 0x0C0) (i32.const 128)))
(if (i32.eqz
(call $bytes_equal
(i32.const 0x0C0) (i32.const 0x140)
(if (result i32) (i32.ne (local.get $callerLen) (local.get $existingLen))
(then (i32.const 0))
(else (local.get $callerLen)))))
(then
(call $log_prefix_name
(i32.const 0x23A) (i32.const 14) ;; "unauthorized: "
(i32.const 0x000) (local.get $nameLen))
return
)
)
;; Store empty bytes → effectively deletes the record
(call $set_state
(i32.const 0x000) (local.get $nameLen)
(i32.const 0x000) (i32.const 0))
(call $log_prefix_name
(i32.const 0x249) (i32.const 10) ;; "released: "
(i32.const 0x000) (local.get $nameLen))
)
)