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
This commit is contained in:
BIN
contracts/counter/counter.wasm
Normal file
BIN
contracts/counter/counter.wasm
Normal file
Binary file not shown.
102
contracts/counter/counter.wat
Normal file
102
contracts/counter/counter.wat
Normal file
@@ -0,0 +1,102 @@
|
||||
(module
|
||||
;; Counter smart contract — methods: increment, get, reset
|
||||
;; State key "counter" stores uint64 as 8-byte big-endian.
|
||||
;; State key "owner" stores the deployer pub key set on first reset() call.
|
||||
|
||||
;; ── imports ──────────────────────────────────────────────────────────────
|
||||
(import "env" "put_u64"
|
||||
(func $put_u64 (param i32 i32 i64)))
|
||||
(import "env" "get_u64"
|
||||
(func $get_u64 (param i32 i32) (result i64)))
|
||||
(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 ───────────────────────────────────────────────────────────────
|
||||
;; Memory layout:
|
||||
;; offset 0 : "counter" (7 bytes) — state key for the counter value
|
||||
;; offset 16 : "owner" (5 bytes) — state key for the owner pub key
|
||||
;; offset 32 : caller buffer (128 bytes)
|
||||
;; offset 160 : owner buffer (128 bytes)
|
||||
;; offset 288 : log messages
|
||||
(memory (export "memory") 1)
|
||||
|
||||
(data (i32.const 0) "counter")
|
||||
(data (i32.const 16) "owner")
|
||||
(data (i32.const 288) "incremented")
|
||||
(data (i32.const 300) "get called")
|
||||
(data (i32.const 310) "reset")
|
||||
(data (i32.const 316) "unauthorized")
|
||||
(data (i32.const 329) "reset ok")
|
||||
|
||||
;; ── increment() ──────────────────────────────────────────────────────────
|
||||
(func (export "increment")
|
||||
(local $val i64)
|
||||
(local.set $val (call $get_u64 (i32.const 0) (i32.const 7)))
|
||||
(local.set $val (i64.add (local.get $val) (i64.const 1)))
|
||||
(call $put_u64 (i32.const 0) (i32.const 7) (local.get $val))
|
||||
(call $log (i32.const 288) (i32.const 11))
|
||||
)
|
||||
|
||||
;; ── get() ─────────────────────────────────────────────────────────────────
|
||||
(func (export "get")
|
||||
(call $log (i32.const 300) (i32.const 10))
|
||||
)
|
||||
|
||||
;; ── reset() ───────────────────────────────────────────────────────────────
|
||||
;; Resets counter to 0. Only callable by the deployer (first caller sets ownership).
|
||||
(func (export "reset")
|
||||
(local $callerLen i32)
|
||||
(local $ownerLen i32)
|
||||
(local $i i32)
|
||||
(local $same i32)
|
||||
|
||||
(local.set $callerLen (call $get_caller (i32.const 32) (i32.const 128)))
|
||||
(local.set $ownerLen
|
||||
(call $get_state (i32.const 16) (i32.const 5) (i32.const 160) (i32.const 128)))
|
||||
|
||||
;; No owner yet — first caller becomes owner and resets to 0
|
||||
(if (i32.eqz (local.get $ownerLen))
|
||||
(then
|
||||
(call $set_state (i32.const 16) (i32.const 5) (i32.const 32) (local.get $callerLen))
|
||||
(call $put_u64 (i32.const 0) (i32.const 7) (i64.const 0))
|
||||
(call $log (i32.const 329) (i32.const 8))
|
||||
return
|
||||
)
|
||||
)
|
||||
|
||||
;; Length mismatch → unauthorized
|
||||
(if (i32.ne (local.get $callerLen) (local.get $ownerLen))
|
||||
(then (call $log (i32.const 316) (i32.const 12)) return)
|
||||
)
|
||||
|
||||
;; Byte-by-byte comparison
|
||||
(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 $callerLen)))
|
||||
(if (i32.ne
|
||||
(i32.load8_u (i32.add (i32.const 32) (local.get $i)))
|
||||
(i32.load8_u (i32.add (i32.const 160) (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)
|
||||
)
|
||||
)
|
||||
|
||||
(if (i32.eqz (local.get $same))
|
||||
(then (call $log (i32.const 316) (i32.const 12)) return)
|
||||
)
|
||||
|
||||
;; Authorized — reset
|
||||
(call $put_u64 (i32.const 0) (i32.const 7) (i64.const 0))
|
||||
(call $log (i32.const 329) (i32.const 8))
|
||||
)
|
||||
)
|
||||
1
contracts/counter/counter_abi.json
Normal file
1
contracts/counter/counter_abi.json
Normal file
@@ -0,0 +1 @@
|
||||
{"methods":[{"name":"increment","args":[]},{"name":"get","args":[]},{"name":"reset","args":[]}]}
|
||||
331
contracts/counter/gen/main.go
Normal file
331
contracts/counter/gen/main.go
Normal file
@@ -0,0 +1,331 @@
|
||||
// gen generates contracts/counter/counter.wasm — binary WASM for the counter contract.
|
||||
// Run from the repo root: go run ./contracts/counter/gen/
|
||||
//
|
||||
// Contract methods exported: increment, get, reset
|
||||
// Host imports from "env": put_u64, get_u64, log, get_caller, get_state, set_state
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// ── LEB128 ───────────────────────────────────────────────────────────────────
|
||||
|
||||
func u(v uint64) []byte {
|
||||
var b []byte
|
||||
for {
|
||||
bt := byte(v & 0x7f)
|
||||
v >>= 7
|
||||
if v != 0 {
|
||||
bt |= 0x80
|
||||
}
|
||||
b = append(b, bt)
|
||||
if v == 0 {
|
||||
return b
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func s(v int64) []byte {
|
||||
var b []byte
|
||||
for {
|
||||
bt := byte(v & 0x7f)
|
||||
v >>= 7
|
||||
sign := (bt & 0x40) != 0
|
||||
if (v == 0 && !sign) || (v == -1 && sign) {
|
||||
return append(b, bt)
|
||||
}
|
||||
b = append(b, bt|0x80)
|
||||
}
|
||||
}
|
||||
|
||||
// ── Builders ──────────────────────────────────────────────────────────────────
|
||||
|
||||
func cat(slices ...[]byte) []byte {
|
||||
var out []byte
|
||||
for _, s := range slices {
|
||||
out = append(out, s...)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func wstr(str string) []byte { return cat(u(uint64(len(str))), []byte(str)) }
|
||||
|
||||
func section(id byte, content []byte) []byte {
|
||||
return cat([]byte{id}, u(uint64(len(content))), content)
|
||||
}
|
||||
|
||||
// vec encodes a vector: count followed by concatenated items.
|
||||
func vec(items ...[]byte) []byte {
|
||||
out := u(uint64(len(items)))
|
||||
for _, it := range items {
|
||||
out = append(out, it...)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// functype encodes a WASM function type (0x60 prefix).
|
||||
func functype(params, results []byte) []byte {
|
||||
return cat([]byte{0x60}, u(uint64(len(params))), params, u(uint64(len(results))), results)
|
||||
}
|
||||
|
||||
// importFunc encodes a function import entry.
|
||||
func importFunc(mod, name string, typeIdx uint32) []byte {
|
||||
return cat(wstr(mod), wstr(name), []byte{0x00}, u(uint64(typeIdx)))
|
||||
}
|
||||
|
||||
// exportEntry encodes an export entry.
|
||||
func exportEntry(name string, kind byte, idx uint32) []byte {
|
||||
return cat(wstr(name), []byte{kind}, u(uint64(idx)))
|
||||
}
|
||||
|
||||
// dataSegment encodes an active data segment for memory 0.
|
||||
func dataSegment(offset int32, data []byte) []byte {
|
||||
return cat(
|
||||
[]byte{0x00}, // active segment, implicit mem 0
|
||||
[]byte{0x41}, s(int64(offset)), []byte{0x0B}, // i32.const offset; end
|
||||
u(uint64(len(data))), data,
|
||||
)
|
||||
}
|
||||
|
||||
// funcBody encodes one function body: localDecls + instructions + end.
|
||||
func funcBody(localDecls []byte, instrs ...[]byte) []byte {
|
||||
inner := cat(localDecls)
|
||||
for _, ins := range instrs {
|
||||
inner = append(inner, ins...)
|
||||
}
|
||||
inner = append(inner, 0x0B) // end
|
||||
return cat(u(uint64(len(inner))), inner)
|
||||
}
|
||||
|
||||
// noLocals is an empty local decl list.
|
||||
var noLocals = u(0)
|
||||
|
||||
// localDecl encodes n locals of a given type.
|
||||
func localDecl(n uint32, typ byte) []byte { return cat(u(uint64(n)), []byte{typ}) }
|
||||
func withLocals(decls ...[]byte) []byte {
|
||||
return cat(u(uint64(len(decls))), cat(decls...))
|
||||
}
|
||||
|
||||
// ── Instructions ──────────────────────────────────────────────────────────────
|
||||
|
||||
const (
|
||||
tI32 byte = 0x7F
|
||||
tI64 byte = 0x7E
|
||||
)
|
||||
|
||||
func call(fn uint32) []byte { return cat([]byte{0x10}, u(uint64(fn))) }
|
||||
func lget(i uint32) []byte { return cat([]byte{0x20}, u(uint64(i))) }
|
||||
func lset(i uint32) []byte { return cat([]byte{0x21}, u(uint64(i))) }
|
||||
func ic32(v int32) []byte { return cat([]byte{0x41}, s(int64(v))) }
|
||||
func ic64(v int64) []byte { return cat([]byte{0x42}, s(v)) }
|
||||
func block_() []byte { return []byte{0x02, 0x40} }
|
||||
func loop_() []byte { return []byte{0x03, 0x40} }
|
||||
func if_() []byte { return []byte{0x04, 0x40} }
|
||||
func end_() []byte { return []byte{0x0B} }
|
||||
func br_(lbl uint32) []byte { return cat([]byte{0x0C}, u(uint64(lbl))) }
|
||||
func brIf_(lbl uint32) []byte { return cat([]byte{0x0D}, u(uint64(lbl))) }
|
||||
func return_() []byte { return []byte{0x0F} }
|
||||
func i32Eqz() []byte { return []byte{0x45} }
|
||||
func i32Ne() []byte { return []byte{0x47} }
|
||||
func i32GeU() []byte { return []byte{0x4F} }
|
||||
func i32Add() []byte { return []byte{0x6A} }
|
||||
func i64Add() []byte { return []byte{0x7C} }
|
||||
func i32Load8U() []byte { return []byte{0x2D, 0x00, 0x00} } // align=0, offset=0
|
||||
|
||||
// ── Memory layout constants ───────────────────────────────────────────────────
|
||||
|
||||
const (
|
||||
offCounter = 0x00 // "counter" (7 bytes)
|
||||
offOwner = 0x10 // "owner" (5 bytes)
|
||||
offIncMsg = 0x20 // "incremented" (11 bytes)
|
||||
offGetMsg = 0x30 // "get called" (10 bytes)
|
||||
offResetOk = 0x40 // "reset ok" (8 bytes)
|
||||
offUnauth = 0x50 // "unauthorized" (12 bytes)
|
||||
offCallerBuf = 0x60 // caller buf (128 bytes)
|
||||
offOwnerBuf = 0xE0 // owner buf (128 bytes)
|
||||
)
|
||||
|
||||
// Import function indices
|
||||
const (
|
||||
fnPutU64 = 0 // put_u64(keyPtr, keyLen i32, val i64)
|
||||
fnGetU64 = 1 // get_u64(keyPtr, keyLen i32) → i64
|
||||
fnLog = 2 // log(msgPtr, msgLen i32)
|
||||
fnGetCaller = 3 // get_caller(bufPtr, bufLen i32) → i32
|
||||
fnGetState = 4 // get_state(kPtr,kLen,dPtr,dLen i32) → i32
|
||||
fnSetState = 5 // set_state(kPtr,kLen,vPtr,vLen i32)
|
||||
)
|
||||
|
||||
// Local function indices (imports are 0-5, locals start at 6)
|
||||
const (
|
||||
fnIncrement = 6
|
||||
fnGet = 7
|
||||
fnReset = 8
|
||||
)
|
||||
|
||||
func main() {
|
||||
// ── Type section ─────────────────────────────────────────────────────────
|
||||
// Type 0: (i32,i32,i64)→() put_u64
|
||||
// Type 1: (i32,i32)→(i64) get_u64
|
||||
// Type 2: (i32,i32)→() log
|
||||
// Type 3: (i32,i32)→(i32) get_caller
|
||||
// Type 4: (i32,i32,i32,i32)→(i32) get_state
|
||||
// Type 5: (i32,i32,i32,i32)→() set_state
|
||||
// Type 6: ()→() increment, get, reset
|
||||
typeSection := section(0x01, vec(
|
||||
functype([]byte{tI32, tI32, tI64}, []byte{}), // 0
|
||||
functype([]byte{tI32, tI32}, []byte{tI64}), // 1
|
||||
functype([]byte{tI32, tI32}, []byte{}), // 2
|
||||
functype([]byte{tI32, tI32}, []byte{tI32}), // 3
|
||||
functype([]byte{tI32, tI32, tI32, tI32}, []byte{tI32}), // 4
|
||||
functype([]byte{tI32, tI32, tI32, tI32}, []byte{}), // 5
|
||||
functype([]byte{}, []byte{}), // 6
|
||||
))
|
||||
|
||||
// ── Import section ────────────────────────────────────────────────────────
|
||||
importSection := section(0x02, vec(
|
||||
importFunc("env", "put_u64", fnPutU64),
|
||||
importFunc("env", "get_u64", fnGetU64),
|
||||
importFunc("env", "log", fnLog),
|
||||
importFunc("env", "get_caller", fnGetCaller),
|
||||
importFunc("env", "get_state", fnGetState),
|
||||
importFunc("env", "set_state", fnSetState),
|
||||
))
|
||||
|
||||
// ── Function section: 3 local functions, all type 6 ──────────────────────
|
||||
functionSection := section(0x03, vec(u(6), u(6), u(6)))
|
||||
|
||||
// ── Memory section: 1 page (64 KiB) ──────────────────────────────────────
|
||||
// limits type 0x00 = min only; type 0x01 = min+max
|
||||
memorySection := section(0x05, vec(cat([]byte{0x00}, u(1)))) // min=1, no max
|
||||
|
||||
// ── Export section ────────────────────────────────────────────────────────
|
||||
exportSection := section(0x07, vec(
|
||||
exportEntry("memory", 0x02, 0),
|
||||
exportEntry("increment", 0x00, fnIncrement),
|
||||
exportEntry("get", 0x00, fnGet),
|
||||
exportEntry("reset", 0x00, fnReset),
|
||||
))
|
||||
|
||||
// ── Data section ──────────────────────────────────────────────────────────
|
||||
dataSection := section(0x0B, cat(
|
||||
u(6), // 6 segments
|
||||
dataSegment(offCounter, []byte("counter")),
|
||||
dataSegment(offOwner, []byte("owner")),
|
||||
dataSegment(offIncMsg, []byte("incremented")),
|
||||
dataSegment(offGetMsg, []byte("get called")),
|
||||
dataSegment(offResetOk, []byte("reset ok")),
|
||||
dataSegment(offUnauth, []byte("unauthorized")),
|
||||
))
|
||||
|
||||
// ── Code section ─────────────────────────────────────────────────────────
|
||||
|
||||
// increment():
|
||||
// local $val i64
|
||||
// $val = get_u64("counter")
|
||||
// $val++
|
||||
// put_u64("counter", $val)
|
||||
// log("incremented")
|
||||
incrementBody := funcBody(
|
||||
withLocals(localDecl(1, tI64)),
|
||||
ic32(offCounter), ic32(7), call(fnGetU64), lset(0),
|
||||
lget(0), ic64(1), i64Add(), lset(0),
|
||||
ic32(offCounter), ic32(7), lget(0), call(fnPutU64),
|
||||
ic32(offIncMsg), ic32(11), call(fnLog),
|
||||
)
|
||||
|
||||
// get():
|
||||
// log("get called")
|
||||
getBody := funcBody(
|
||||
noLocals,
|
||||
ic32(offGetMsg), ic32(10), call(fnLog),
|
||||
)
|
||||
|
||||
// reset():
|
||||
// locals: callerLen(0), ownerLen(1), i(2), same(3) — all i32
|
||||
// callerLen = get_caller(callerBuf, 128)
|
||||
// ownerLen = get_state("owner", ownerBuf, 128)
|
||||
// if ownerLen == 0:
|
||||
// set_state("owner", callerBuf[:callerLen])
|
||||
// put_u64("counter", 0)
|
||||
// log("reset ok")
|
||||
// return
|
||||
// if callerLen != ownerLen: log unauthorized; return
|
||||
// same = 1; i = 0
|
||||
// block:
|
||||
// loop:
|
||||
// if i >= callerLen: br 1 (exit block)
|
||||
// if callerBuf[i] != ownerBuf[i]: same=0; br 1
|
||||
// i++; continue loop
|
||||
// if !same: log unauthorized; return
|
||||
// put_u64("counter", 0); log("reset ok")
|
||||
resetBody := funcBody(
|
||||
withLocals(localDecl(4, tI32)),
|
||||
// callerLen = get_caller(callerBuf, 128)
|
||||
ic32(offCallerBuf), ic32(128), call(fnGetCaller), lset(0),
|
||||
// ownerLen = get_state("owner", 5, ownerBuf, 128)
|
||||
ic32(offOwner), ic32(5), ic32(offOwnerBuf), ic32(128), call(fnGetState), lset(1),
|
||||
// if ownerLen == 0:
|
||||
lget(1), i32Eqz(), if_(),
|
||||
ic32(offOwner), ic32(5), ic32(offCallerBuf), lget(0), call(fnSetState),
|
||||
ic32(offCounter), ic32(7), ic64(0), call(fnPutU64),
|
||||
ic32(offResetOk), ic32(8), call(fnLog),
|
||||
return_(),
|
||||
end_(),
|
||||
// if callerLen != ownerLen: unauthorized
|
||||
lget(0), lget(1), i32Ne(), if_(),
|
||||
ic32(offUnauth), ic32(12), call(fnLog),
|
||||
return_(),
|
||||
end_(),
|
||||
// same = 1; i = 0
|
||||
ic32(1), lset(3),
|
||||
ic32(0), lset(2),
|
||||
// block $break
|
||||
block_(),
|
||||
loop_(),
|
||||
lget(2), lget(0), i32GeU(), brIf_(1), // i >= callerLen → break
|
||||
// load callerBuf[i]
|
||||
ic32(offCallerBuf), lget(2), i32Add(), i32Load8U(),
|
||||
// load ownerBuf[i]
|
||||
ic32(offOwnerBuf), lget(2), i32Add(), i32Load8U(),
|
||||
i32Ne(), if_(),
|
||||
ic32(0), lset(3),
|
||||
br_(2), // break out of block
|
||||
end_(),
|
||||
lget(2), ic32(1), i32Add(), lset(2),
|
||||
br_(0), // continue loop
|
||||
end_(),
|
||||
end_(),
|
||||
// if !same: unauthorized
|
||||
lget(3), i32Eqz(), if_(),
|
||||
ic32(offUnauth), ic32(12), call(fnLog),
|
||||
return_(),
|
||||
end_(),
|
||||
// authorized
|
||||
ic32(offCounter), ic32(7), ic64(0), call(fnPutU64),
|
||||
ic32(offResetOk), ic32(8), call(fnLog),
|
||||
)
|
||||
|
||||
codeSection := section(0x0A, cat(u(3), incrementBody, getBody, resetBody))
|
||||
|
||||
// ── Assemble module ───────────────────────────────────────────────────────
|
||||
module := cat(
|
||||
[]byte{0x00, 0x61, 0x73, 0x6d}, // magic \0asm
|
||||
[]byte{0x01, 0x00, 0x00, 0x00}, // version 1
|
||||
typeSection,
|
||||
importSection,
|
||||
functionSection,
|
||||
memorySection,
|
||||
exportSection,
|
||||
dataSection,
|
||||
codeSection,
|
||||
)
|
||||
|
||||
out := "contracts/counter/counter.wasm"
|
||||
if err := os.WriteFile(out, module, 0644); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "write:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Written %s (%d bytes)\n", out, len(module))
|
||||
}
|
||||
137
contracts/counter/main.go
Normal file
137
contracts/counter/main.go
Normal file
@@ -0,0 +1,137 @@
|
||||
// Counter smart contract — compiles to WASM with GOOS=wasip1 GOARCH=wasm.
|
||||
//
|
||||
// Methods (exported via //go:export):
|
||||
// - increment — adds 1 to the stored counter
|
||||
// - get — logs the current value (readable via /api/contracts/{id}/state/counter)
|
||||
// - reset — resets counter to 0; only the first caller (owner) is allowed
|
||||
//
|
||||
// Host imports from the "env" module (see vm/host.go):
|
||||
// - put_u64(keyPtr, keyLen, val) — stores uint64 as 8-byte big-endian
|
||||
// - get_u64(keyPtr, keyLen) uint64 — reads 8-byte big-endian uint64
|
||||
// - get_caller(buf, bufLen) int32 — writes caller pub key hex into buf
|
||||
// - get_state(kPtr,kLen,dPtr,dLen) int32 — reads raw state bytes
|
||||
// - set_state(kPtr,kLen,vPtr,vLen) — writes raw state bytes
|
||||
// - log(msgPtr, msgLen) — emits message to node log
|
||||
//
|
||||
//go:build wasip1
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// ── host function imports ─────────────────────────────────────────────────────
|
||||
|
||||
//go:wasmimport env put_u64
|
||||
func hostPutU64(keyPtr unsafe.Pointer, keyLen int32, val uint64)
|
||||
|
||||
//go:wasmimport env get_u64
|
||||
func hostGetU64(keyPtr unsafe.Pointer, keyLen int32) uint64
|
||||
|
||||
//go:wasmimport env get_caller
|
||||
func hostGetCaller(buf unsafe.Pointer, bufLen int32) int32
|
||||
|
||||
//go:wasmimport env get_state
|
||||
func hostGetState(keyPtr unsafe.Pointer, keyLen int32, dstPtr unsafe.Pointer, dstLen int32) int32
|
||||
|
||||
//go:wasmimport env set_state
|
||||
func hostSetState(keyPtr unsafe.Pointer, keyLen int32, valPtr unsafe.Pointer, valLen int32)
|
||||
|
||||
//go:wasmimport env log
|
||||
func hostLog(msgPtr unsafe.Pointer, msgLen int32)
|
||||
|
||||
// ── helpers ───────────────────────────────────────────────────────────────────
|
||||
|
||||
func logMsg(s string) {
|
||||
if len(s) == 0 {
|
||||
return
|
||||
}
|
||||
b := []byte(s)
|
||||
hostLog(unsafe.Pointer(&b[0]), int32(len(b)))
|
||||
}
|
||||
|
||||
func putU64(key string, val uint64) {
|
||||
b := []byte(key)
|
||||
hostPutU64(unsafe.Pointer(&b[0]), int32(len(b)), val)
|
||||
}
|
||||
|
||||
func getU64(key string) uint64 {
|
||||
b := []byte(key)
|
||||
return hostGetU64(unsafe.Pointer(&b[0]), int32(len(b)))
|
||||
}
|
||||
|
||||
func getState(key string, dst []byte) int32 {
|
||||
kb := []byte(key)
|
||||
return hostGetState(unsafe.Pointer(&kb[0]), int32(len(kb)),
|
||||
unsafe.Pointer(&dst[0]), int32(len(dst)))
|
||||
}
|
||||
|
||||
func setState(key string, val []byte) {
|
||||
kb := []byte(key)
|
||||
hostSetState(unsafe.Pointer(&kb[0]), int32(len(kb)),
|
||||
unsafe.Pointer(&val[0]), int32(len(val)))
|
||||
}
|
||||
|
||||
func getCaller() string {
|
||||
buf := make([]byte, 128)
|
||||
n := hostGetCaller(unsafe.Pointer(&buf[0]), int32(len(buf)))
|
||||
if n <= 0 {
|
||||
return ""
|
||||
}
|
||||
return string(buf[:n])
|
||||
}
|
||||
|
||||
// ── contract state keys ───────────────────────────────────────────────────────
|
||||
|
||||
const (
|
||||
keyCounter = "counter"
|
||||
keyOwner = "owner"
|
||||
)
|
||||
|
||||
// ── exported contract methods ─────────────────────────────────────────────────
|
||||
|
||||
//go:export increment
|
||||
func increment() {
|
||||
val := getU64(keyCounter)
|
||||
val++
|
||||
putU64(keyCounter, val)
|
||||
logMsg("incremented")
|
||||
}
|
||||
|
||||
//go:export get
|
||||
func get() {
|
||||
logMsg("get called")
|
||||
}
|
||||
|
||||
//go:export reset
|
||||
func reset() {
|
||||
caller := getCaller()
|
||||
if caller == "" {
|
||||
logMsg("reset: no caller")
|
||||
return
|
||||
}
|
||||
|
||||
ownerBuf := make([]byte, 128)
|
||||
ownerLen := getState(keyOwner, ownerBuf)
|
||||
|
||||
if ownerLen == 0 {
|
||||
// No owner set yet — first caller becomes the owner.
|
||||
setState(keyOwner, []byte(caller))
|
||||
putU64(keyCounter, 0)
|
||||
logMsg("reset ok (owner set)")
|
||||
return
|
||||
}
|
||||
|
||||
owner := string(ownerBuf[:ownerLen])
|
||||
if caller != owner {
|
||||
logMsg("reset: unauthorized")
|
||||
return
|
||||
}
|
||||
|
||||
putU64(keyCounter, 0)
|
||||
logMsg("reset ok")
|
||||
}
|
||||
|
||||
// main is required by the Go runtime for wasip1 programs.
|
||||
func main() {}
|
||||
Reference in New Issue
Block a user