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:
vsecoder
2026-04-17 14:16:44 +03:00
commit 7e7393e4f8
196 changed files with 55947 additions and 0 deletions

Binary file not shown.

View File

@@ -0,0 +1,45 @@
{
"contract": "auction",
"version": "1.0.0",
"description": "English auction with on-chain token escrow. Bids are locked in the contract treasury. When an auction settles, the seller receives the top bid; outbid funds are automatically refunded.",
"methods": [
{
"name": "create",
"description": "Create a new auction. Logs 'created: <auction_id>'. auction_id = block_height:seq (seq auto-incremented).",
"args": [
{"name": "title", "type": "string", "description": "Human-readable item title (max 128 chars)"},
{"name": "min_bid", "type": "uint64", "description": "Minimum opening bid in µT"},
{"name": "duration", "type": "uint64", "description": "Duration in blocks"}
]
},
{
"name": "bid",
"description": "Place a bid on an open auction. Caller transfers bid amount to the contract treasury; previous top bidder is refunded. Logs 'bid: <auction_id>'.",
"args": [
{"name": "auction_id", "type": "string", "description": "Auction ID returned by create"},
{"name": "amount", "type": "uint64", "description": "Bid amount in µT (must exceed current top bid)"}
]
},
{
"name": "settle",
"description": "Settle an ended auction. Transfers winning bid to seller. Anyone may call after the end block. Logs 'settled: <auction_id>'.",
"args": [
{"name": "auction_id", "type": "string", "description": "Auction ID to settle"}
]
},
{
"name": "cancel",
"description": "Cancel an auction before any bids. Only the seller may cancel. Logs 'cancelled: <auction_id>'.",
"args": [
{"name": "auction_id", "type": "string", "description": "Auction ID to cancel"}
]
},
{
"name": "info",
"description": "Query auction info. Logs 'seller: ...', 'title: ...', 'top_bid: ...', 'end_block: ...', 'status: open/settled/cancelled'.",
"args": [
{"name": "auction_id", "type": "string", "description": "Auction ID"}
]
}
]
}

View File

@@ -0,0 +1,784 @@
// gen generates contracts/auction/auction.wasm
// Run from repo root: go run ./contracts/auction/gen/
//
// Methods: create, bid, settle, cancel, info
//
// State keys (per auction_id, single-char suffix):
// "a:<id>:s" → seller address
// "a:<id>:t" → title
// "a:<id>:m" → min_bid (8-byte big-endian u64 via put_u64/get_u64)
// "a:<id>:e" → end_block (8-byte big-endian u64)
// "a:<id>:b" → top_bidder address (empty string = no bid)
// "a:<id>:v" → top_bid amount (8-byte big-endian u64)
// "a:<id>:x" → status byte ('o'=open, 's'=settled, 'c'=cancelled)
//
// The contract treasury holds in-flight bid escrow.
package main
import (
"fmt"
"os"
)
// ── LEB128 & builders ────────────────────────────────────────────────────────
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)
}
}
func cat(slices ...[]byte) []byte {
var out []byte
for _, sl := range slices {
out = append(out, sl...)
}
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)
}
func vec(items ...[]byte) []byte {
out := u(uint64(len(items)))
for _, it := range items {
out = append(out, it...)
}
return out
}
func functype(params, results []byte) []byte {
return cat([]byte{0x60}, u(uint64(len(params))), params, u(uint64(len(results))), results)
}
func importFunc(mod, name string, typeIdx uint32) []byte {
return cat(wstr(mod), wstr(name), []byte{0x00}, u(uint64(typeIdx)))
}
func exportEntry(name string, kind byte, idx uint32) []byte {
return cat(wstr(name), []byte{kind}, u(uint64(idx)))
}
func dataSegment(offset int32, data []byte) []byte {
return cat(
[]byte{0x00},
[]byte{0x41}, s(int64(offset)), []byte{0x0B},
u(uint64(len(data))), data,
)
}
func funcBody(localDecls []byte, instrs ...[]byte) []byte {
inner := cat(localDecls)
for _, ins := range instrs {
inner = append(inner, ins...)
}
inner = append(inner, 0x0B)
return cat(u(uint64(len(inner))), inner)
}
var noLocals = u(0)
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...))
}
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 else_() []byte { return []byte{0x05} }
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 drop() []byte { return []byte{0x1A} }
func i32Eqz() []byte { return []byte{0x45} }
func i32Ne() []byte { return []byte{0x47} }
func i32GtU() []byte { return []byte{0x4B} }
func i32GeU() []byte { return []byte{0x4F} }
func i32Add() []byte { return []byte{0x6A} }
func i64Eqz() []byte { return []byte{0x50} }
func i64Eq() []byte { return []byte{0x51} }
func i64GtU() []byte { return []byte{0x56} }
func i64LeU() []byte { return []byte{0x57} }
func i64Add() []byte { return []byte{0x7C} }
func i32Load8U() []byte { return []byte{0x2D, 0x00, 0x00} }
func i32Store8() []byte { return []byte{0x3A, 0x00, 0x00} }
// ── Memory layout ─────────────────────────────────────────────────────────────
//
// 0x000 64 arg[0]: auction_id
// 0x040 128 arg[1]: title
// 0x140 128 caller buffer
// 0x1C0 64 treasury buffer
// 0x200 128 state-read: seller or top_bidder
// 0x280 128 secondary state-read: top_bidder (during bid)
// 0x300 2 state-read: status byte
// 0x310 256 scratch (buildField writes here)
//
// Constant strings at 0x500+:
// 0x500 10 "created: "
// 0x50A 5 "bid: "
// 0x510 10 "settled: "
// 0x51A 11 "cancelled: "
// 0x526 14 "unauthorized: "
// 0x535 9 "not found: "
// 0x540 12 "not open: " (auction not in open state)
// 0x54C 14 "bidding open: " (cannot cancel with bids)
// 0x55B 12 "still open: " (auction has not ended yet)
// 0x568 11 "low bid: " (bid too low)
// 0x574 8 "seller: "
// 0x57D 7 "title: "
// 0x585 10 "top bid: "
// 0x590 11 "end block: "
// 0x59C 8 "status: "
// 0x5A5 5 "open"
// 0x5AA 8 "settled"
// 0x5B3 10 "cancelled"
const (
offArg0 int32 = 0x000
offArg1 int32 = 0x040
offCaller int32 = 0x140
offTreasury int32 = 0x1C0
offRead1 int32 = 0x200 // seller / first state read
offRead2 int32 = 0x280 // top_bidder / second state read
offReadStat int32 = 0x300 // status byte
offScratch int32 = 0x310 // key scratch
offCreatedPfx int32 = 0x500
offBidPfx int32 = 0x50A
offSettledPfx int32 = 0x510
offCancelledPfx int32 = 0x51A
offUnauthPfx int32 = 0x526
offNotFoundPfx int32 = 0x535
offNotOpenPfx int32 = 0x540
offHasBidsPfx int32 = 0x54C
offStillOpenPfx int32 = 0x55B
offLowBidPfx int32 = 0x568
offSellerPfx int32 = 0x574
offTitlePfx int32 = 0x57D
offTopBidPfx int32 = 0x585
offEndBlockPfx int32 = 0x590
offStatusPfx int32 = 0x59C
offStrOpen int32 = 0x5A5
offStrSettled int32 = 0x5AA
offStrCancelled int32 = 0x5B3
)
// ── Import / function indices ─────────────────────────────────────────────────
const (
fnGetArgStr = 0
fnGetArgU64 = 1
fnGetCaller = 2
fnGetState = 3
fnSetState = 4
fnLog = 5
fnTransfer = 6
fnGetBalance = 7
fnGetContractTreasury = 8
fnGetBlockHeight = 9
fnPutU64 = 10
fnGetU64 = 11
fnBytesEqual = 12
fnMemcpy = 13
fnLogPrefix = 14
fnBuildField = 15 // buildField(idOff, idLen, fieldChar i32) → keyLen i32
fnCreate = 16
fnBid = 17
fnSettle = 18
fnCancel = 19
fnInfo = 20
)
// ── Helper bodies ─────────────────────────────────────────────────────────────
func bytesEqualBody() []byte {
return funcBody(
withLocals(localDecl(2, tI32)),
ic32(1), lset(4), ic32(0), lset(3),
block_(), loop_(),
lget(3), lget(2), i32GeU(), brIf_(1),
lget(0), lget(3), i32Add(), i32Load8U(),
lget(1), lget(3), i32Add(), i32Load8U(),
i32Ne(), if_(), ic32(0), lset(4), br_(2), end_(),
lget(3), ic32(1), i32Add(), lset(3),
br_(0), end_(), end_(),
lget(4),
)
}
func memcpyBody() []byte {
return funcBody(
withLocals(localDecl(1, tI32)),
ic32(0), lset(3),
block_(), loop_(),
lget(3), lget(2), i32GeU(), brIf_(1),
lget(0), lget(3), i32Add(),
lget(1), lget(3), i32Add(), i32Load8U(),
i32Store8(),
lget(3), ic32(1), i32Add(), lset(3),
br_(0), end_(), end_(),
)
}
func logPrefixBody() []byte {
return funcBody(
noLocals,
ic32(offScratch), lget(0), lget(1), call(fnMemcpy),
ic32(offScratch), lget(1), i32Add(), lget(2), lget(3), call(fnMemcpy),
ic32(offScratch), lget(1), lget(3), i32Add(), call(fnLog),
)
}
// buildField(idOff, idLen, fieldChar i32) → keyLen i32
// Writes "a:<id>:<fieldChar>" into offScratch. Returns idLen+4.
func buildFieldBody() []byte {
return funcBody(
noLocals,
// scratch[0] = 'a'
ic32(offScratch), ic32('a'), i32Store8(),
// scratch[1] = ':'
ic32(offScratch+1), ic32(':'), i32Store8(),
// memcpy(scratch+2, idOff, idLen)
ic32(offScratch+2), lget(0), lget(1), call(fnMemcpy),
// scratch[2+idLen] = ':'
ic32(offScratch+2), lget(1), i32Add(), ic32(':'), i32Store8(),
// scratch[3+idLen] = fieldChar
ic32(offScratch+3), lget(1), i32Add(), lget(2), i32Store8(),
// return idLen + 4
lget(1), ic32(4), i32Add(),
)
}
// ── Contract methods ──────────────────────────────────────────────────────────
// create(id string, title string, min_bid u64, duration u64)
// Locals: idLen(0), titleLen(1), treasuryLen(2), keyLen(3)
// i64 locals: minBid(4), duration(5)
func createBody() []byte {
return funcBody(
withLocals(localDecl(4, tI32), localDecl(2, tI64)),
ic32(0), ic32(offArg0), ic32(64), call(fnGetArgStr), lset(0),
lget(0), i32Eqz(), if_(), return_(), end_(),
ic32(1), ic32(offArg1), ic32(128), call(fnGetArgStr), lset(1),
lget(1), i32Eqz(), if_(), return_(), end_(),
// minBid = get_arg_u64(2)
ic32(2), call(fnGetArgU64), lset(4),
// duration = get_arg_u64(3)
ic32(3), call(fnGetArgU64), lset(5),
// Check not already registered: get_state("a:<id>:x", ...)
ic32(offArg0), lget(0), ic32('x'), call(fnBuildField), lset(3),
ic32(offScratch), lget(3), ic32(offReadStat), ic32(2), call(fnGetState),
ic32(0), i32GtU(), if_(), // status exists → already created
ic32(offNotFoundPfx), ic32(11), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
// Get treasury address
ic32(offTreasury), ic32(64), call(fnGetContractTreasury), lset(2),
// Get caller (seller)
ic32(offCaller), ic32(128), call(fnGetCaller), lset(2), // reuse local 2 for callerLen
// Write seller: a:<id>:s → caller
ic32(offArg0), lget(0), ic32('s'), call(fnBuildField), lset(3),
ic32(offScratch), lget(3), ic32(offCaller), lget(2), call(fnSetState),
// Write title: a:<id>:t → title
ic32(offArg0), lget(0), ic32('t'), call(fnBuildField), lset(3),
ic32(offScratch), lget(3), ic32(offArg1), lget(1), call(fnSetState),
// Write min_bid: a:<id>:m → put_u64
ic32(offArg0), lget(0), ic32('m'), call(fnBuildField), lset(3),
ic32(offScratch), lget(3), lget(4), call(fnPutU64),
// Write end_block: a:<id>:e → get_block_height() + duration
call(fnGetBlockHeight), lget(5), i64Add(),
ic32(offArg0), lget(0), ic32('e'), call(fnBuildField), lset(3),
ic32(offScratch), lget(3),
// stack: keyPtr, keyLen, [endBlock i64 still on stack? No — need to rearrange]
// Actually put_u64 takes (keyPtr, keyLen, val i64)
// We already have endBlock on stack but called buildField after. Let me restructure.
drop(), // drop keyLen from buildField - oops this won't work as coded above
// Actually above I wrote lset(3) to save keyLen, so stack should be clean.
// The i64 (endBlock) is on the operand stack before the buildField call... this is wrong.
// Let me restructure: compute endBlock first into an i64 local.
// I need another i64 local. Let me add local 6 (i64).
// This requires restructuring... see revised body below
)
}
// Revised create body with proper local management
// Locals: idLen(0), titleLen(1), callerLen(2), keyLen(3) [i32]
// minBid(4), duration(5), endBlock(6) [i64]
func createBodyV2() []byte {
return funcBody(
withLocals(localDecl(4, tI32), localDecl(3, tI64)),
// Read args
ic32(0), ic32(offArg0), ic32(64), call(fnGetArgStr), lset(0),
lget(0), i32Eqz(), if_(), return_(), end_(),
ic32(1), ic32(offArg1), ic32(128), call(fnGetArgStr), lset(1),
lget(1), i32Eqz(), if_(), return_(), end_(),
ic32(2), call(fnGetArgU64), lset(4), // minBid
ic32(3), call(fnGetArgU64), lset(5), // duration
// Check id not already used: read status key
ic32(offArg0), lget(0), ic32('x'), call(fnBuildField), lset(3),
ic32(offScratch), lget(3), ic32(offReadStat), ic32(2), call(fnGetState),
ic32(0), i32GtU(), if_(),
// ID already exists — log conflict
ic32(offNotFoundPfx), ic32(11), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
// Get caller (seller)
ic32(offCaller), ic32(128), call(fnGetCaller), lset(2),
// Compute endBlock = get_block_height() + duration
call(fnGetBlockHeight), lget(5), i64Add(), lset(6),
// Write seller: a:<id>:s → caller
ic32(offArg0), lget(0), ic32('s'), call(fnBuildField), lset(3),
ic32(offScratch), lget(3), ic32(offCaller), lget(2), call(fnSetState),
// Write title: a:<id>:t → arg1
ic32(offArg0), lget(0), ic32('t'), call(fnBuildField), lset(3),
ic32(offScratch), lget(3), ic32(offArg1), lget(1), call(fnSetState),
// Write min_bid: a:<id>:m
ic32(offArg0), lget(0), ic32('m'), call(fnBuildField), lset(3),
ic32(offScratch), lget(3), lget(4), call(fnPutU64),
// Write end_block: a:<id>:e
ic32(offArg0), lget(0), ic32('e'), call(fnBuildField), lset(3),
ic32(offScratch), lget(3), lget(6), call(fnPutU64),
// Write top_bid = 0: a:<id>:v
ic32(offArg0), lget(0), ic32('v'), call(fnBuildField), lset(3),
ic32(offScratch), lget(3), ic64(0), call(fnPutU64),
// Write status = 'o': a:<id>:x
ic32(offArg0), lget(0), ic32('x'), call(fnBuildField), lset(3),
ic32(offReadStat), ic32('o'), i32Store8(), // write 'o' to offReadStat
ic32(offScratch), lget(3), ic32(offReadStat), ic32(1), call(fnSetState),
ic32(offCreatedPfx), ic32(9), ic32(offArg0), lget(0), call(fnLogPrefix),
)
}
// bid(id string, amount u64)
// Locals: idLen(0), callerLen(1), sellerLen(2), topBidderLen(3), keyLen(4) [i32]
// amount(5), topBid(6), endBlock(7) [i64]
func bidBody() []byte {
return funcBody(
withLocals(localDecl(5, tI32), localDecl(3, tI64)),
ic32(0), ic32(offArg0), ic32(64), call(fnGetArgStr), lset(0),
lget(0), i32Eqz(), if_(), return_(), end_(),
ic32(1), call(fnGetArgU64), lset(5), // amount
// Check status == 'o'
ic32(offArg0), lget(0), ic32('x'), call(fnBuildField), lset(4),
ic32(offScratch), lget(4), ic32(offReadStat), ic32(2), call(fnGetState),
ic32(0), i32Ne(), if_(), // status byte exists
ic32(offReadStat), i32Load8U(), ic32('o'), i32Ne(), if_(),
ic32(offNotOpenPfx), ic32(10), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
else_(),
// no status byte = not found
ic32(offNotFoundPfx), ic32(11), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
// Check end_block > current block
ic32(offArg0), lget(0), ic32('e'), call(fnBuildField), lset(4),
ic32(offScratch), lget(4), call(fnGetU64), lset(7),
call(fnGetBlockHeight), lget(7), i64LeU(), if_(), // blockHeight >= endBlock → ended
ic32(offNotOpenPfx), ic32(10), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
// Get current top_bid
ic32(offArg0), lget(0), ic32('v'), call(fnBuildField), lset(4),
ic32(offScratch), lget(4), call(fnGetU64), lset(6),
// amount must be > max(topBid, minBid-1) → amount > topBid AND amount >= minBid
// Check amount > topBid
lget(5), lget(6), i64GtU(), i32Eqz(), if_(),
ic32(offLowBidPfx), ic32(9), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
// Get caller
ic32(offCaller), ic32(128), call(fnGetCaller), lset(1),
// Get treasury
ic32(offTreasury), ic32(64), call(fnGetContractTreasury), lset(2), // reuse local 2
// Transfer amount from caller to treasury
ic32(offCaller), lget(1), ic32(offTreasury), lget(2), lget(5), call(fnTransfer), drop(),
// Refund previous top bidder if topBid > 0
lget(6), i64Eqz(), i32Eqz(), if_(), // topBid > 0
// Read top_bidder address into offRead2
ic32(offArg0), lget(0), ic32('b'), call(fnBuildField), lset(4),
ic32(offScratch), lget(4), ic32(offRead2), ic32(128), call(fnGetState), lset(3),
lget(3), ic32(0), i32GtU(), if_(), // topBidder address exists
// transfer(treasury, topBidder, topBid)
ic32(offTreasury), lget(2), ic32(offRead2), lget(3), lget(6), call(fnTransfer), drop(),
end_(),
end_(),
// Update top_bidder: a:<id>:b → caller
ic32(offArg0), lget(0), ic32('b'), call(fnBuildField), lset(4),
ic32(offScratch), lget(4), ic32(offCaller), lget(1), call(fnSetState),
// Update top_bid: a:<id>:v → amount
ic32(offArg0), lget(0), ic32('v'), call(fnBuildField), lset(4),
ic32(offScratch), lget(4), lget(5), call(fnPutU64),
ic32(offBidPfx), ic32(5), ic32(offArg0), lget(0), call(fnLogPrefix),
)
}
// settle(id string)
// Locals: idLen(0), sellerLen(1), topBidderLen(2), treasuryLen(3), keyLen(4) [i32]
// topBid(5), endBlock(6) [i64]
func settleBody() []byte {
return funcBody(
withLocals(localDecl(5, tI32), localDecl(2, tI64)),
ic32(0), ic32(offArg0), ic32(64), call(fnGetArgStr), lset(0),
lget(0), i32Eqz(), if_(), return_(), end_(),
// Check status == 'o'
ic32(offArg0), lget(0), ic32('x'), call(fnBuildField), lset(4),
ic32(offScratch), lget(4), ic32(offReadStat), ic32(2), call(fnGetState),
ic32(0), i32Ne(), if_(),
ic32(offReadStat), i32Load8U(), ic32('o'), i32Ne(), if_(),
ic32(offNotOpenPfx), ic32(10), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
else_(),
ic32(offNotFoundPfx), ic32(11), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
// Check auction has ended: current_block >= end_block
ic32(offArg0), lget(0), ic32('e'), call(fnBuildField), lset(4),
ic32(offScratch), lget(4), call(fnGetU64), lset(6),
call(fnGetBlockHeight), lget(6), i64LeU(), if_(), // height < endBlock → still open
ic32(offStillOpenPfx), ic32(12), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
// Get top_bid
ic32(offArg0), lget(0), ic32('v'), call(fnBuildField), lset(4),
ic32(offScratch), lget(4), call(fnGetU64), lset(5),
// If top_bid > 0: transfer topBid from treasury to seller
lget(5), i64Eqz(), i32Eqz(), if_(),
// Read seller into offRead1
ic32(offArg0), lget(0), ic32('s'), call(fnBuildField), lset(4),
ic32(offScratch), lget(4), ic32(offRead1), ic32(128), call(fnGetState), lset(1),
// Get treasury
ic32(offTreasury), ic32(64), call(fnGetContractTreasury), lset(3),
ic32(offTreasury), lget(3), ic32(offRead1), lget(1), lget(5), call(fnTransfer), drop(),
end_(),
// Mark status = 's'
ic32(offReadStat), ic32('s'), i32Store8(),
ic32(offArg0), lget(0), ic32('x'), call(fnBuildField), lset(4),
ic32(offScratch), lget(4), ic32(offReadStat), ic32(1), call(fnSetState),
ic32(offSettledPfx), ic32(9), ic32(offArg0), lget(0), call(fnLogPrefix),
)
}
// cancel(id string) — seller cancels (no bids yet)
// Locals: idLen(0), callerLen(1), sellerLen(2), keyLen(3) [i32]
// topBid(4) [i64]
func cancelBody() []byte {
return funcBody(
withLocals(localDecl(4, tI32), localDecl(1, tI64)),
ic32(0), ic32(offArg0), ic32(64), call(fnGetArgStr), lset(0),
lget(0), i32Eqz(), if_(), return_(), end_(),
// Check status == 'o'
ic32(offArg0), lget(0), ic32('x'), call(fnBuildField), lset(3),
ic32(offScratch), lget(3), ic32(offReadStat), ic32(2), call(fnGetState),
ic32(0), i32Ne(), if_(),
ic32(offReadStat), i32Load8U(), ic32('o'), i32Ne(), if_(),
ic32(offNotOpenPfx), ic32(10), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
else_(),
ic32(offNotFoundPfx), ic32(11), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
// Check no bids: topBid == 0
ic32(offArg0), lget(0), ic32('v'), call(fnBuildField), lset(3),
ic32(offScratch), lget(3), call(fnGetU64), lset(4),
lget(4), i64Eqz(), i32Eqz(), if_(), // topBid > 0 → has bids
ic32(offHasBidsPfx), ic32(14), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
// Verify caller is seller
ic32(offArg0), lget(0), ic32('s'), call(fnBuildField), lset(3),
ic32(offScratch), lget(3), ic32(offRead1), ic32(128), call(fnGetState), lset(2),
ic32(offCaller), ic32(128), call(fnGetCaller), lset(1),
// isOwner: callerLen == sellerLen && bytes_equal
lget(1), lget(2), i32Ne(), if_(),
ic32(offUnauthPfx), ic32(14), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
ic32(offCaller), ic32(offRead1), lget(1), call(fnBytesEqual),
i32Eqz(), if_(),
ic32(offUnauthPfx), ic32(14), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
// Mark status = 'c'
ic32(offReadStat), ic32('c'), i32Store8(),
ic32(offArg0), lget(0), ic32('x'), call(fnBuildField), lset(3),
ic32(offScratch), lget(3), ic32(offReadStat), ic32(1), call(fnSetState),
ic32(offCancelledPfx), ic32(11), ic32(offArg0), lget(0), call(fnLogPrefix),
)
}
// info(id string) — log auction details
// Locals: idLen(0), strLen(1), keyLen(2) [i32]
// topBid(3) [i64]
func infoBody() []byte {
return funcBody(
withLocals(localDecl(3, tI32), localDecl(1, tI64)),
ic32(0), ic32(offArg0), ic32(64), call(fnGetArgStr), lset(0),
lget(0), i32Eqz(), if_(), return_(), end_(),
// Check exists
ic32(offArg0), lget(0), ic32('x'), call(fnBuildField), lset(2),
ic32(offScratch), lget(2), ic32(offReadStat), ic32(2), call(fnGetState),
ic32(0), i32Ne(), i32Eqz(), if_(),
ic32(offNotFoundPfx), ic32(11), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
// Log seller: a:<id>:s
ic32(offArg0), lget(0), ic32('s'), call(fnBuildField), lset(2),
ic32(offScratch), lget(2), ic32(offRead1), ic32(128), call(fnGetState), lset(1),
ic32(offSellerPfx), ic32(8), ic32(offRead1), lget(1), call(fnLogPrefix),
// Log title: a:<id>:t
ic32(offArg0), lget(0), ic32('t'), call(fnBuildField), lset(2),
ic32(offScratch), lget(2), ic32(offRead1), ic32(128), call(fnGetState), lset(1),
ic32(offTitlePfx), ic32(7), ic32(offRead1), lget(1), call(fnLogPrefix),
// Log status
ic32(offReadStat), i32Load8U(), ic32('s'), i32Ne(), if_(),
ic32(offReadStat), i32Load8U(), ic32('c'), i32Ne(), if_(),
// status == 'o'
ic32(offStatusPfx), ic32(8), ic32(offStrOpen), ic32(4), call(fnLogPrefix),
else_(),
ic32(offStatusPfx), ic32(8), ic32(offStrCancelled), ic32(9), call(fnLogPrefix),
end_(),
else_(),
ic32(offStatusPfx), ic32(8), ic32(offStrSettled), ic32(7), call(fnLogPrefix),
end_(),
)
}
// ── main ──────────────────────────────────────────────────────────────────────
func main() {
// Types:
// 0: (i32,i32,i32)→(i32) get_arg_str, bytes_equal
// 1: (i32)→(i64) get_arg_u64, get_block_height (no, diff sig)
// 2: (i32,i32,i32,i32)→(i32) get_state
// 3: (i32,i32,i32,i32)→() set_state, log_prefix
// 4: (i32,i32)→() log
// 5: (i32,i32,i32,i32,i64)→(i32) transfer
// 6: (i32,i32)→(i64) get_balance, get_u64
// 7: (i32,i32)→(i32) get_caller, get_contract_treasury
// 8: ()→(i64) get_block_height
// 9: (i32,i32,i64)→() put_u64
// 10: ()→() exported methods
// 11: (i32,i32,i32)→() memcpy
// 12: (i32,i32,i32)→(i32) buildField
typeSection := section(0x01, vec(
functype([]byte{tI32, tI32, tI32}, []byte{tI32}), // 0
functype([]byte{tI32}, []byte{tI64}), // 1
functype([]byte{tI32, tI32, tI32, tI32}, []byte{tI32}), // 2
functype([]byte{tI32, tI32, tI32, tI32}, []byte{}), // 3
functype([]byte{tI32, tI32}, []byte{}), // 4
functype([]byte{tI32, tI32, tI32, tI32, tI64}, []byte{tI32}), // 5
functype([]byte{tI32, tI32}, []byte{tI64}), // 6
functype([]byte{tI32, tI32}, []byte{tI32}), // 7
functype([]byte{}, []byte{tI64}), // 8
functype([]byte{tI32, tI32, tI64}, []byte{}), // 9
functype([]byte{}, []byte{}), // 10
functype([]byte{tI32, tI32, tI32}, []byte{}), // 11
))
importSection := section(0x02, vec(
importFunc("env", "get_arg_str", 0), // 0
importFunc("env", "get_arg_u64", 1), // 1
importFunc("env", "get_caller", 7), // 2
importFunc("env", "get_state", 2), // 3
importFunc("env", "set_state", 3), // 4
importFunc("env", "log", 4), // 5
importFunc("env", "transfer", 5), // 6
importFunc("env", "get_balance", 6), // 7
importFunc("env", "get_contract_treasury", 7), // 8
importFunc("env", "get_block_height", 8), // 9
importFunc("env", "put_u64", 9), // 10
importFunc("env", "get_u64", 6), // 11
))
// 9 local functions
functionSection := section(0x03, vec(
u(0), // bytes_equal type 0
u(11), // memcpy type 11
u(3), // log_prefix type 3
u(0), // buildField type 0 (i32,i32,i32)→(i32)
u(10), // create type 10
u(10), // bid type 10
u(10), // settle type 10
u(10), // cancel type 10
u(10), // info type 10
))
memorySection := section(0x05, vec(cat([]byte{0x00}, u(2)))) // 2 pages
exportSection := section(0x07, vec(
exportEntry("memory", 0x02, 0),
exportEntry("create", 0x00, fnCreate),
exportEntry("bid", 0x00, fnBid),
exportEntry("settle", 0x00, fnSettle),
exportEntry("cancel", 0x00, fnCancel),
exportEntry("info", 0x00, fnInfo),
))
dataSection := section(0x0B, cat(
u(17),
dataSegment(offCreatedPfx, []byte("created: ")),
dataSegment(offBidPfx, []byte("bid: ")),
dataSegment(offSettledPfx, []byte("settled: ")),
dataSegment(offCancelledPfx, []byte("cancelled: ")),
dataSegment(offUnauthPfx, []byte("unauthorized: ")),
dataSegment(offNotFoundPfx, []byte("not found: ")),
dataSegment(offNotOpenPfx, []byte("not open: ")),
dataSegment(offHasBidsPfx, []byte("has bids: ")),
dataSegment(offStillOpenPfx, []byte("still open: ")),
dataSegment(offLowBidPfx, []byte("low bid: ")),
dataSegment(offSellerPfx, []byte("seller: ")),
dataSegment(offTitlePfx, []byte("title: ")),
dataSegment(offTopBidPfx, []byte("top bid: ")),
dataSegment(offEndBlockPfx, []byte("end block: ")),
dataSegment(offStatusPfx, []byte("status: ")),
dataSegment(offStrOpen, []byte("open")),
dataSegment(offStrSettled, []byte("settled")),
// Note: offStrCancelled is at 0x5B3 but we declared 17 segments
// Add cancelled string too
))
// Fix: 18 data segments including "cancelled"
dataSection = section(0x0B, cat(
u(18),
dataSegment(offCreatedPfx, []byte("created: ")),
dataSegment(offBidPfx, []byte("bid: ")),
dataSegment(offSettledPfx, []byte("settled: ")),
dataSegment(offCancelledPfx, []byte("cancelled: ")),
dataSegment(offUnauthPfx, []byte("unauthorized: ")),
dataSegment(offNotFoundPfx, []byte("not found: ")),
dataSegment(offNotOpenPfx, []byte("not open: ")),
dataSegment(offHasBidsPfx, []byte("has bids: ")),
dataSegment(offStillOpenPfx, []byte("still open: ")),
dataSegment(offLowBidPfx, []byte("low bid: ")),
dataSegment(offSellerPfx, []byte("seller: ")),
dataSegment(offTitlePfx, []byte("title: ")),
dataSegment(offTopBidPfx, []byte("top bid: ")),
dataSegment(offEndBlockPfx, []byte("end block: ")),
dataSegment(offStatusPfx, []byte("status: ")),
dataSegment(offStrOpen, []byte("open")),
dataSegment(offStrSettled, []byte("settled")),
dataSegment(offStrCancelled, []byte("cancelled")),
))
codeSection := section(0x0A, cat(
u(9),
bytesEqualBody(),
memcpyBody(),
logPrefixBody(),
buildFieldBody(),
createBodyV2(),
bidBody(),
settleBody(),
cancelBody(),
infoBody(),
))
module := cat(
[]byte{0x00, 0x61, 0x73, 0x6d},
[]byte{0x01, 0x00, 0x00, 0x00},
typeSection,
importSection,
functionSection,
memorySection,
exportSection,
dataSection,
codeSection,
)
out := "contracts/auction/auction.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))
}

Binary file not shown.

View 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))
)
)

View File

@@ -0,0 +1 @@
{"methods":[{"name":"increment","args":[]},{"name":"get","args":[]},{"name":"reset","args":[]}]}

View 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
View 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() {}

Binary file not shown.

View File

@@ -0,0 +1,57 @@
{
"contract": "escrow",
"version": "1.0.0",
"description": "Two-party trustless escrow. Buyer deposits funds into the contract treasury. Seller delivers; buyer releases. If disputed, the contract admin resolves.",
"methods": [
{
"name": "init",
"description": "Set the caller as the escrow admin. Call once after deployment.",
"args": []
},
{
"name": "create",
"description": "Buyer creates an escrow. Transfers amount from buyer to treasury. Logs 'created: <id>'.",
"args": [
{"name": "id", "type": "string", "description": "Unique escrow ID (user-supplied)"},
{"name": "seller", "type": "string", "description": "Seller address (hex pubkey)"},
{"name": "amount", "type": "uint64", "description": "Amount in µT to lock in escrow"}
]
},
{
"name": "release",
"description": "Buyer releases funds to seller. Transfers treasury → seller. Logs 'released: <id>'.",
"args": [
{"name": "id", "type": "string", "description": "Escrow ID"}
]
},
{
"name": "refund",
"description": "Seller refunds the buyer (voluntary). Transfers treasury → buyer. Logs 'refunded: <id>'.",
"args": [
{"name": "id", "type": "string", "description": "Escrow ID"}
]
},
{
"name": "dispute",
"description": "Buyer or seller raises a dispute. Logs 'disputed: <id>'. Admin must then call resolve.",
"args": [
{"name": "id", "type": "string", "description": "Escrow ID"}
]
},
{
"name": "resolve",
"description": "Admin resolves a disputed escrow. winner must be 'buyer' or 'seller'. Logs 'resolved: <id>'.",
"args": [
{"name": "id", "type": "string", "description": "Escrow ID"},
{"name": "winner", "type": "string", "description": "'buyer' or 'seller'"}
]
},
{
"name": "info",
"description": "Log escrow details: buyer, seller, amount, status.",
"args": [
{"name": "id", "type": "string", "description": "Escrow ID"}
]
}
]
}

View File

@@ -0,0 +1,818 @@
// gen generates contracts/escrow/escrow.wasm
// Run from repo root: go run ./contracts/escrow/gen/
//
// Methods: init, create, release, refund, dispute, resolve, info
//
// State keys (per escrow id):
// "admin" → admin address
// "e:<id>:b" → buyer address
// "e:<id>:s" → seller address
// "e:<id>:v" → amount (8-byte big-endian u64)
// "e:<id>:x" → status byte:
// 'a' = active (funds locked)
// 'd' = disputed
// 'r' = released to seller
// 'f' = refunded to buyer
//
// The contract treasury holds the locked funds during active/disputed state.
package main
import (
"fmt"
"os"
)
// ── LEB128 & builders ────────────────────────────────────────────────────────
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)
}
}
func cat(slices ...[]byte) []byte {
var out []byte
for _, sl := range slices {
out = append(out, sl...)
}
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)
}
func vec(items ...[]byte) []byte {
out := u(uint64(len(items)))
for _, it := range items {
out = append(out, it...)
}
return out
}
func functype(params, results []byte) []byte {
return cat([]byte{0x60}, u(uint64(len(params))), params, u(uint64(len(results))), results)
}
func importFunc(mod, name string, typeIdx uint32) []byte {
return cat(wstr(mod), wstr(name), []byte{0x00}, u(uint64(typeIdx)))
}
func exportEntry(name string, kind byte, idx uint32) []byte {
return cat(wstr(name), []byte{kind}, u(uint64(idx)))
}
func dataSegment(offset int32, data []byte) []byte {
return cat(
[]byte{0x00},
[]byte{0x41}, s(int64(offset)), []byte{0x0B},
u(uint64(len(data))), data,
)
}
func funcBody(localDecls []byte, instrs ...[]byte) []byte {
inner := cat(localDecls)
for _, ins := range instrs {
inner = append(inner, ins...)
}
inner = append(inner, 0x0B)
return cat(u(uint64(len(inner))), inner)
}
var noLocals = u(0)
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...))
}
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 ifI32() []byte { return []byte{0x04, tI32} }
func else_() []byte { return []byte{0x05} }
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 drop() []byte { return []byte{0x1A} }
func i32Eqz() []byte { return []byte{0x45} }
func i32Ne() []byte { return []byte{0x47} }
func i32GtU() []byte { return []byte{0x4B} }
func i32GeU() []byte { return []byte{0x4F} }
func i32Add() []byte { return []byte{0x6A} }
func i64Eqz() []byte { return []byte{0x50} }
func i32Load8U() []byte { return []byte{0x2D, 0x00, 0x00} }
func i32Store8() []byte { return []byte{0x3A, 0x00, 0x00} }
// ── Memory layout ─────────────────────────────────────────────────────────────
//
// 0x000 64 arg[0]: escrow id
// 0x040 128 arg[1]: seller address or winner string
// 0x140 128 caller buffer
// 0x1C0 64 treasury buffer
// 0x200 128 state-read buffer 1 (buyer or seller)
// 0x280 128 state-read buffer 2 (seller or admin)
// 0x300 2 status byte buffer
// 0x310 256 scratch (buildField key workspace)
//
// Constants at 0x500+:
// 0x500 5 "admin"
// 0x506 13 "initialized: "
// 0x514 10 "created: "
// 0x51E 10 "released: "
// 0x529 10 "refunded: "
// 0x534 10 "disputed: "
// 0x53F 10 "resolved: "
// 0x54A 14 "unauthorized: "
// 0x559 11 "not found: "
// 0x565 14 "already init: "
// 0x574 12 "not active: "
// 0x581 14 "not disputed: "
// 0x590 7 "buyer: "
// 0x598 8 "seller: "
// 0x5A1 8 "status: "
// 0x5AA 7 "active"
// 0x5B1 9 "disputed"
// 0x5BB 9 "released"
// 0x5C5 8 "refunded"
// 0x5CE 6 "buyer" (for resolve winner comparison)
// 0x5D4 6 "seller"
const (
offArg0 int32 = 0x000
offArg1 int32 = 0x040
offCaller int32 = 0x140
offTreasury int32 = 0x1C0
offRead1 int32 = 0x200
offRead2 int32 = 0x280
offStatBuf int32 = 0x300
offScratch int32 = 0x310
offKeyAdmin int32 = 0x500
offInitedPfx int32 = 0x506
offCreatedPfx int32 = 0x514
offReleasedPfx int32 = 0x51E
offRefundedPfx int32 = 0x529
offDisputedPfx int32 = 0x534
offResolvedPfx int32 = 0x53F
offUnauthPfx int32 = 0x54A
offNotFoundPfx int32 = 0x559
offAlreadyPfx int32 = 0x565
offNotActivePfx int32 = 0x574
offNotDispPfx int32 = 0x581
offBuyerPfx int32 = 0x590
offSellerPfx int32 = 0x598
offStatusPfx int32 = 0x5A1
offStrActive int32 = 0x5AA
offStrDisputed int32 = 0x5B1
offStrReleased int32 = 0x5BB
offStrRefunded int32 = 0x5C5
offStrBuyer int32 = 0x5CE
offStrSeller int32 = 0x5D4
)
// ── Import / function indices ─────────────────────────────────────────────────
const (
fnGetArgStr = 0
fnGetArgU64 = 1
fnGetCaller = 2
fnGetState = 3
fnSetState = 4
fnLog = 5
fnTransfer = 6
fnGetContractTreasury = 7
fnPutU64 = 8
fnGetU64 = 9
fnBytesEqual = 10
fnMemcpy = 11
fnLogPrefix = 12
fnBuildField = 13 // (idOff, idLen, fieldChar i32) → keyLen i32
fnInit = 14
fnCreate = 15
fnRelease = 16
fnRefund = 17
fnDispute = 18
fnResolve = 19
fnInfo = 20
)
// ── Helper bodies ─────────────────────────────────────────────────────────────
func bytesEqualBody() []byte {
return funcBody(
withLocals(localDecl(2, tI32)),
ic32(1), lset(4), ic32(0), lset(3),
block_(), loop_(),
lget(3), lget(2), i32GeU(), brIf_(1),
lget(0), lget(3), i32Add(), i32Load8U(),
lget(1), lget(3), i32Add(), i32Load8U(),
i32Ne(), if_(), ic32(0), lset(4), br_(2), end_(),
lget(3), ic32(1), i32Add(), lset(3),
br_(0), end_(), end_(),
lget(4),
)
}
func memcpyBody() []byte {
return funcBody(
withLocals(localDecl(1, tI32)),
ic32(0), lset(3),
block_(), loop_(),
lget(3), lget(2), i32GeU(), brIf_(1),
lget(0), lget(3), i32Add(),
lget(1), lget(3), i32Add(), i32Load8U(),
i32Store8(),
lget(3), ic32(1), i32Add(), lset(3),
br_(0), end_(), end_(),
)
}
func logPrefixBody() []byte {
// (prefixPtr, prefixLen, suffixPtr, suffixLen i32)
return funcBody(
noLocals,
ic32(offScratch), lget(0), lget(1), call(fnMemcpy),
ic32(offScratch), lget(1), i32Add(), lget(2), lget(3), call(fnMemcpy),
ic32(offScratch), lget(1), lget(3), i32Add(), call(fnLog),
)
}
// buildField(idOff, idLen, fieldChar i32) → keyLen i32
// Writes "e:<id>:<fieldChar>" into offScratch.
func buildFieldBody() []byte {
return funcBody(
noLocals,
ic32(offScratch), ic32('e'), i32Store8(),
ic32(offScratch+1), ic32(':'), i32Store8(),
ic32(offScratch+2), lget(0), lget(1), call(fnMemcpy),
ic32(offScratch+2), lget(1), i32Add(), ic32(':'), i32Store8(),
ic32(offScratch+3), lget(1), i32Add(), lget(2), i32Store8(),
lget(1), ic32(4), i32Add(),
)
}
// isCallerAdminCode: reads admin into offRead2, checks caller == admin.
// Params: callerLen local index, adminLen local index.
// Returns inline code that leaves i32 (1=is admin) on stack.
func isCallerAdminCode(callerLenLocal, adminLenLocal uint32) []byte {
return cat(
ic32(offKeyAdmin), ic32(5), ic32(offRead2), ic32(128), call(fnGetState), lset(adminLenLocal),
lget(adminLenLocal), i32Eqz(),
ifI32(), ic32(0), else_(),
lget(callerLenLocal), lget(adminLenLocal), i32Ne(),
ifI32(), ic32(0), else_(),
ic32(offCaller), ic32(offRead2), lget(callerLenLocal), call(fnBytesEqual),
end_(), end_(),
)
}
// ── Contract method bodies ────────────────────────────────────────────────────
// init() — set caller as admin
// Locals: callerLen(0), existingLen(1)
func initBody() []byte {
return funcBody(
withLocals(localDecl(2, tI32)),
ic32(offCaller), ic32(128), call(fnGetCaller), lset(0),
ic32(offKeyAdmin), ic32(5), ic32(offRead2), ic32(128), call(fnGetState), lset(1),
lget(1), ic32(0), i32GtU(), if_(),
ic32(offAlreadyPfx), ic32(14), ic32(offCaller), lget(0), call(fnLogPrefix),
return_(),
end_(),
ic32(offKeyAdmin), ic32(5), ic32(offCaller), lget(0), call(fnSetState),
ic32(offInitedPfx), ic32(13), ic32(offCaller), lget(0), call(fnLogPrefix),
)
}
// create(id, seller, amount u64)
// Locals: idLen(0), sellerLen(1), callerLen(2), treasuryLen(3), keyLen(4) [i32]
// amount(5) [i64]
func createBody() []byte {
return funcBody(
withLocals(localDecl(5, tI32), localDecl(1, tI64)),
ic32(0), ic32(offArg0), ic32(64), call(fnGetArgStr), lset(0),
lget(0), i32Eqz(), if_(), return_(), end_(),
ic32(1), ic32(offArg1), ic32(128), call(fnGetArgStr), lset(1),
lget(1), i32Eqz(), if_(), return_(), end_(),
ic32(2), call(fnGetArgU64), lset(5),
// Check id not already used
ic32(offArg0), lget(0), ic32('x'), call(fnBuildField), lset(4),
ic32(offScratch), lget(4), ic32(offStatBuf), ic32(2), call(fnGetState),
ic32(0), i32GtU(), if_(),
ic32(offNotFoundPfx), ic32(11), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
// Get caller (buyer) and treasury
ic32(offCaller), ic32(128), call(fnGetCaller), lset(2),
ic32(offTreasury), ic32(64), call(fnGetContractTreasury), lset(3),
// Transfer amount: buyer → treasury
ic32(offCaller), lget(2), ic32(offTreasury), lget(3), lget(5), call(fnTransfer), drop(),
// Write buyer: e:<id>:b → caller
ic32(offArg0), lget(0), ic32('b'), call(fnBuildField), lset(4),
ic32(offScratch), lget(4), ic32(offCaller), lget(2), call(fnSetState),
// Write seller: e:<id>:s → arg1
ic32(offArg0), lget(0), ic32('s'), call(fnBuildField), lset(4),
ic32(offScratch), lget(4), ic32(offArg1), lget(1), call(fnSetState),
// Write amount: e:<id>:v
ic32(offArg0), lget(0), ic32('v'), call(fnBuildField), lset(4),
ic32(offScratch), lget(4), lget(5), call(fnPutU64),
// Write status = 'a': e:<id>:x
ic32(offStatBuf), ic32('a'), i32Store8(),
ic32(offArg0), lget(0), ic32('x'), call(fnBuildField), lset(4),
ic32(offScratch), lget(4), ic32(offStatBuf), ic32(1), call(fnSetState),
ic32(offCreatedPfx), ic32(9), ic32(offArg0), lget(0), call(fnLogPrefix),
)
}
// checkActiveStatus: reads status byte into offStatBuf; returns inline code
// that returns with an error log if status != expectedChar.
func checkStatus(idLenLocal uint32, expectedChar int32, errPfxOff int32, errPfxLen int32) []byte {
return cat(
ic32(offArg0), lget(idLenLocal), ic32('x'), call(fnBuildField),
// keyLen on stack — but we need it in a local. Use the call result directly:
// Actually we need to save it. This is getting complex. Let me inline differently.
// Instead, we'll read status without saving keyLen — just check immediately.
// Actually the fn returns keyLen on stack. We need (keyPtr, keyLen, dstPtr, dstLen) for get_state.
// So: ic32(offScratch), [keyLen], ic32(offStatBuf), ic32(2), call(fnGetState)
// But keyLen is already on the stack after buildField!
// We can do: ic32(offScratch), <stack: keyLen>, ...
// No wait, stack ordering: we push ic32(offScratch) BEFORE the keyLen.
// For get_state(keyPtr, keyLen, dstPtr, dstLen):
// We need: keyPtr first, then keyLen.
// buildField leaves keyLen on stack, but we need keyPtr first.
// This is the fundamental issue: the keyPtr (offScratch) needs to be pushed before keyLen.
//
// Fix: save keyLen to a local first, then push offScratch, keyLen.
// But we don't have a spare local in this helper...
//
// For now, let me just not use this helper function and inline the check in each method.
ic32(0), // placeholder - don't use this helper
)
}
// release(id) — buyer releases funds to seller
// Locals: idLen(0), buyerLen(1), sellerLen(2), callerLen(3), treasuryLen(4), keyLen(5) [i32]
// amount(6) [i64]
func releaseBody() []byte {
return funcBody(
withLocals(localDecl(6, tI32), localDecl(1, tI64)),
ic32(0), ic32(offArg0), ic32(64), call(fnGetArgStr), lset(0),
lget(0), i32Eqz(), if_(), return_(), end_(),
// Check status == 'a'
ic32(offArg0), lget(0), ic32('x'), call(fnBuildField), lset(5),
ic32(offScratch), lget(5), ic32(offStatBuf), ic32(2), call(fnGetState),
ic32(0), i32Ne(), if_(),
ic32(offStatBuf), i32Load8U(), ic32('a'), i32Ne(), if_(),
ic32(offNotActivePfx), ic32(12), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
else_(),
ic32(offNotFoundPfx), ic32(11), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
// Check caller == buyer
ic32(offCaller), ic32(128), call(fnGetCaller), lset(3),
ic32(offArg0), lget(0), ic32('b'), call(fnBuildField), lset(5),
ic32(offScratch), lget(5), ic32(offRead1), ic32(128), call(fnGetState), lset(1),
lget(3), lget(1), i32Ne(), if_(),
ic32(offUnauthPfx), ic32(14), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
ic32(offCaller), ic32(offRead1), lget(3), call(fnBytesEqual),
i32Eqz(), if_(),
ic32(offUnauthPfx), ic32(14), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
// Get seller, treasury, amount
ic32(offArg0), lget(0), ic32('s'), call(fnBuildField), lset(5),
ic32(offScratch), lget(5), ic32(offRead2), ic32(128), call(fnGetState), lset(2),
ic32(offTreasury), ic32(64), call(fnGetContractTreasury), lset(4),
ic32(offArg0), lget(0), ic32('v'), call(fnBuildField), lset(5),
ic32(offScratch), lget(5), call(fnGetU64), lset(6),
// Transfer: treasury → seller
ic32(offTreasury), lget(4), ic32(offRead2), lget(2), lget(6), call(fnTransfer), drop(),
// Mark status = 'r'
ic32(offStatBuf), ic32('r'), i32Store8(),
ic32(offArg0), lget(0), ic32('x'), call(fnBuildField), lset(5),
ic32(offScratch), lget(5), ic32(offStatBuf), ic32(1), call(fnSetState),
ic32(offReleasedPfx), ic32(10), ic32(offArg0), lget(0), call(fnLogPrefix),
)
}
// refund(id) — seller voluntarily refunds buyer
// Locals: idLen(0), sellerLen(1), buyerLen(2), callerLen(3), treasuryLen(4), keyLen(5) [i32]
// amount(6) [i64]
func refundBody() []byte {
return funcBody(
withLocals(localDecl(6, tI32), localDecl(1, tI64)),
ic32(0), ic32(offArg0), ic32(64), call(fnGetArgStr), lset(0),
lget(0), i32Eqz(), if_(), return_(), end_(),
// Check status == 'a'
ic32(offArg0), lget(0), ic32('x'), call(fnBuildField), lset(5),
ic32(offScratch), lget(5), ic32(offStatBuf), ic32(2), call(fnGetState),
ic32(0), i32Ne(), if_(),
ic32(offStatBuf), i32Load8U(), ic32('a'), i32Ne(), if_(),
ic32(offNotActivePfx), ic32(12), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
else_(),
ic32(offNotFoundPfx), ic32(11), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
// Check caller == seller
ic32(offCaller), ic32(128), call(fnGetCaller), lset(3),
ic32(offArg0), lget(0), ic32('s'), call(fnBuildField), lset(5),
ic32(offScratch), lget(5), ic32(offRead1), ic32(128), call(fnGetState), lset(1),
lget(3), lget(1), i32Ne(), if_(),
ic32(offUnauthPfx), ic32(14), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
ic32(offCaller), ic32(offRead1), lget(3), call(fnBytesEqual),
i32Eqz(), if_(),
ic32(offUnauthPfx), ic32(14), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
// Get buyer, treasury, amount
ic32(offArg0), lget(0), ic32('b'), call(fnBuildField), lset(5),
ic32(offScratch), lget(5), ic32(offRead2), ic32(128), call(fnGetState), lset(2),
ic32(offTreasury), ic32(64), call(fnGetContractTreasury), lset(4),
ic32(offArg0), lget(0), ic32('v'), call(fnBuildField), lset(5),
ic32(offScratch), lget(5), call(fnGetU64), lset(6),
// Transfer: treasury → buyer
ic32(offTreasury), lget(4), ic32(offRead2), lget(2), lget(6), call(fnTransfer), drop(),
// Mark status = 'f'
ic32(offStatBuf), ic32('f'), i32Store8(),
ic32(offArg0), lget(0), ic32('x'), call(fnBuildField), lset(5),
ic32(offScratch), lget(5), ic32(offStatBuf), ic32(1), call(fnSetState),
ic32(offRefundedPfx), ic32(10), ic32(offArg0), lget(0), call(fnLogPrefix),
)
}
// dispute(id) — buyer or seller raises a dispute
// Locals: idLen(0), callerLen(1), buyerLen(2), sellerLen(3), keyLen(4) [i32]
func disputeBody() []byte {
return funcBody(
withLocals(localDecl(5, tI32)),
ic32(0), ic32(offArg0), ic32(64), call(fnGetArgStr), lset(0),
lget(0), i32Eqz(), if_(), return_(), end_(),
// Check status == 'a'
ic32(offArg0), lget(0), ic32('x'), call(fnBuildField), lset(4),
ic32(offScratch), lget(4), ic32(offStatBuf), ic32(2), call(fnGetState),
ic32(0), i32Ne(), if_(),
ic32(offStatBuf), i32Load8U(), ic32('a'), i32Ne(), if_(),
ic32(offNotActivePfx), ic32(12), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
else_(),
ic32(offNotFoundPfx), ic32(11), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
// Caller must be buyer or seller
ic32(offCaller), ic32(128), call(fnGetCaller), lset(1),
ic32(offArg0), lget(0), ic32('b'), call(fnBuildField), lset(4),
ic32(offScratch), lget(4), ic32(offRead1), ic32(128), call(fnGetState), lset(2),
ic32(offArg0), lget(0), ic32('s'), call(fnBuildField), lset(4),
ic32(offScratch), lget(4), ic32(offRead2), ic32(128), call(fnGetState), lset(3),
// isBuyer = callerLen==buyerLen && bytes_equal(caller, buyer, callerLen)
// isSeller = callerLen==sellerLen && bytes_equal(caller, seller, callerLen)
// if !isBuyer && !isSeller: unauthorized
lget(1), lget(2), i32Ne(),
if_(),
// callerLen != buyerLen → check seller
lget(1), lget(3), i32Ne(), if_(),
ic32(offUnauthPfx), ic32(14), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
ic32(offCaller), ic32(offRead2), lget(1), call(fnBytesEqual),
i32Eqz(), if_(),
ic32(offUnauthPfx), ic32(14), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
else_(),
// callerLen == buyerLen → check bytes
ic32(offCaller), ic32(offRead1), lget(1), call(fnBytesEqual),
i32Eqz(), if_(),
// not buyer — check seller
lget(1), lget(3), i32Ne(), if_(),
ic32(offUnauthPfx), ic32(14), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
ic32(offCaller), ic32(offRead2), lget(1), call(fnBytesEqual),
i32Eqz(), if_(),
ic32(offUnauthPfx), ic32(14), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
end_(),
end_(),
// Mark status = 'd'
ic32(offStatBuf), ic32('d'), i32Store8(),
ic32(offArg0), lget(0), ic32('x'), call(fnBuildField), lset(4),
ic32(offScratch), lget(4), ic32(offStatBuf), ic32(1), call(fnSetState),
ic32(offDisputedPfx), ic32(10), ic32(offArg0), lget(0), call(fnLogPrefix),
)
}
// resolve(id, winner) — admin resolves disputed escrow
// winner arg must be "buyer" or "seller"
// Locals: idLen(0), winnerLen(1), callerLen(2), adminLen(3),
// recipientLen(4), treasuryLen(5), keyLen(6) [i32]
// amount(7) [i64]
func resolveBody() []byte {
return funcBody(
withLocals(localDecl(7, tI32), localDecl(1, tI64)),
ic32(0), ic32(offArg0), ic32(64), call(fnGetArgStr), lset(0),
lget(0), i32Eqz(), if_(), return_(), end_(),
ic32(1), ic32(offArg1), ic32(128), call(fnGetArgStr), lset(1),
lget(1), i32Eqz(), if_(), return_(), end_(),
// Check status == 'd'
ic32(offArg0), lget(0), ic32('x'), call(fnBuildField), lset(6),
ic32(offScratch), lget(6), ic32(offStatBuf), ic32(2), call(fnGetState),
ic32(0), i32Ne(), if_(),
ic32(offStatBuf), i32Load8U(), ic32('d'), i32Ne(), if_(),
ic32(offNotDispPfx), ic32(14), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
else_(),
ic32(offNotFoundPfx), ic32(11), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
// Check caller is admin
ic32(offCaller), ic32(128), call(fnGetCaller), lset(2),
isCallerAdminCode(2, 3),
i32Eqz(), if_(),
ic32(offUnauthPfx), ic32(14), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
// Determine recipient based on winner arg
// winner == "buyer" (5 bytes) → recipient = buyer address
// winner == "seller" (6 bytes) → recipient = seller address
ic32(offTreasury), ic32(64), call(fnGetContractTreasury), lset(5),
ic32(offArg0), lget(0), ic32('v'), call(fnBuildField), lset(6),
ic32(offScratch), lget(6), call(fnGetU64), lset(7),
// Compare winner to "buyer"
lget(1), ic32(5), i32Ne(), if_(),
// not "buyer" — assume "seller"
ic32(offArg0), lget(0), ic32('s'), call(fnBuildField), lset(6),
ic32(offScratch), lget(6), ic32(offRead1), ic32(128), call(fnGetState), lset(4),
else_(),
// might be "buyer" — verify bytes
ic32(offArg1), ic32(offStrBuyer), ic32(5), call(fnBytesEqual),
i32Eqz(), if_(),
// not "buyer" bytes — default to seller
ic32(offArg0), lget(0), ic32('s'), call(fnBuildField), lset(6),
ic32(offScratch), lget(6), ic32(offRead1), ic32(128), call(fnGetState), lset(4),
else_(),
ic32(offArg0), lget(0), ic32('b'), call(fnBuildField), lset(6),
ic32(offScratch), lget(6), ic32(offRead1), ic32(128), call(fnGetState), lset(4),
end_(),
end_(),
// Transfer: treasury → recipient
ic32(offTreasury), lget(5), ic32(offRead1), lget(4), lget(7), call(fnTransfer), drop(),
// Mark status = 'r' (released, settled)
ic32(offStatBuf), ic32('r'), i32Store8(),
ic32(offArg0), lget(0), ic32('x'), call(fnBuildField), lset(6),
ic32(offScratch), lget(6), ic32(offStatBuf), ic32(1), call(fnSetState),
ic32(offResolvedPfx), ic32(10), ic32(offArg0), lget(0), call(fnLogPrefix),
)
}
// info(id) — log escrow details
// Locals: idLen(0), buyerLen(1), sellerLen(2), keyLen(3) [i32]
func infoBody() []byte {
return funcBody(
withLocals(localDecl(4, tI32)),
ic32(0), ic32(offArg0), ic32(64), call(fnGetArgStr), lset(0),
lget(0), i32Eqz(), if_(), return_(), end_(),
// Check exists
ic32(offArg0), lget(0), ic32('x'), call(fnBuildField), lset(3),
ic32(offScratch), lget(3), ic32(offStatBuf), ic32(2), call(fnGetState),
ic32(0), i32Ne(), i32Eqz(), if_(),
ic32(offNotFoundPfx), ic32(11), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
// Log buyer
ic32(offArg0), lget(0), ic32('b'), call(fnBuildField), lset(3),
ic32(offScratch), lget(3), ic32(offRead1), ic32(128), call(fnGetState), lset(1),
ic32(offBuyerPfx), ic32(7), ic32(offRead1), lget(1), call(fnLogPrefix),
// Log seller
ic32(offArg0), lget(0), ic32('s'), call(fnBuildField), lset(3),
ic32(offScratch), lget(3), ic32(offRead2), ic32(128), call(fnGetState), lset(2),
ic32(offSellerPfx), ic32(8), ic32(offRead2), lget(2), call(fnLogPrefix),
// Log status
ic32(offStatBuf), i32Load8U(), ic32('a'), i32Ne(), if_(),
ic32(offStatBuf), i32Load8U(), ic32('d'), i32Ne(), if_(),
ic32(offStatBuf), i32Load8U(), ic32('r'), i32Ne(), if_(),
ic32(offStatusPfx), ic32(8), ic32(offStrRefunded), ic32(8), call(fnLogPrefix),
else_(),
ic32(offStatusPfx), ic32(8), ic32(offStrReleased), ic32(8), call(fnLogPrefix),
end_(),
else_(),
ic32(offStatusPfx), ic32(8), ic32(offStrDisputed), ic32(8), call(fnLogPrefix),
end_(),
else_(),
ic32(offStatusPfx), ic32(8), ic32(offStrActive), ic32(6), call(fnLogPrefix),
end_(),
)
}
// ── main ──────────────────────────────────────────────────────────────────────
func main() {
// Types:
// 0: (i32,i32,i32)→(i32) get_arg_str, bytes_equal, buildField
// 1: (i32)→(i64) get_arg_u64
// 2: (i32,i32,i32,i32)→(i32) get_state
// 3: (i32,i32,i32,i32)→() set_state, log_prefix
// 4: (i32,i32)→() log
// 5: (i32,i32,i32,i32,i64)→(i32) transfer
// 6: (i32,i32)→(i32) get_caller, get_contract_treasury
// 7: (i32,i32,i64)→() put_u64
// 8: (i32,i32)→(i64) get_u64
// 9: ()→() exported methods
// 10: (i32,i32,i32)→() memcpy
typeSection := section(0x01, vec(
functype([]byte{tI32, tI32, tI32}, []byte{tI32}), // 0
functype([]byte{tI32}, []byte{tI64}), // 1
functype([]byte{tI32, tI32, tI32, tI32}, []byte{tI32}), // 2
functype([]byte{tI32, tI32, tI32, tI32}, []byte{}), // 3
functype([]byte{tI32, tI32}, []byte{}), // 4
functype([]byte{tI32, tI32, tI32, tI32, tI64}, []byte{tI32}), // 5
functype([]byte{tI32, tI32}, []byte{tI32}), // 6
functype([]byte{tI32, tI32, tI64}, []byte{}), // 7
functype([]byte{tI32, tI32}, []byte{tI64}), // 8
functype([]byte{}, []byte{}), // 9
functype([]byte{tI32, tI32, tI32}, []byte{}), // 10
))
importSection := section(0x02, vec(
importFunc("env", "get_arg_str", 0), // 0
importFunc("env", "get_arg_u64", 1), // 1
importFunc("env", "get_caller", 6), // 2
importFunc("env", "get_state", 2), // 3
importFunc("env", "set_state", 3), // 4
importFunc("env", "log", 4), // 5
importFunc("env", "transfer", 5), // 6
importFunc("env", "get_contract_treasury", 6), // 7
importFunc("env", "put_u64", 7), // 8
importFunc("env", "get_u64", 8), // 9
))
// 11 local functions
functionSection := section(0x03, vec(
u(0), // bytes_equal type 0
u(10), // memcpy type 10
u(3), // log_prefix type 3
u(0), // build_field type 0
u(9), // init type 9
u(9), // create type 9
u(9), // release type 9
u(9), // refund type 9
u(9), // dispute type 9
u(9), // resolve type 9
u(9), // info type 9
))
memorySection := section(0x05, vec(cat([]byte{0x00}, u(2)))) // 2 pages
exportSection := section(0x07, vec(
exportEntry("memory", 0x02, 0),
exportEntry("init", 0x00, fnInit),
exportEntry("create", 0x00, fnCreate),
exportEntry("release", 0x00, fnRelease),
exportEntry("refund", 0x00, fnRefund),
exportEntry("dispute", 0x00, fnDispute),
exportEntry("resolve", 0x00, fnResolve),
exportEntry("info", 0x00, fnInfo),
))
dataSection := section(0x0B, cat(
u(21),
dataSegment(offKeyAdmin, []byte("admin")),
dataSegment(offInitedPfx, []byte("initialized: ")),
dataSegment(offCreatedPfx, []byte("created: ")),
dataSegment(offReleasedPfx, []byte("released: ")),
dataSegment(offRefundedPfx, []byte("refunded: ")),
dataSegment(offDisputedPfx, []byte("disputed: ")),
dataSegment(offResolvedPfx, []byte("resolved: ")),
dataSegment(offUnauthPfx, []byte("unauthorized: ")),
dataSegment(offNotFoundPfx, []byte("not found: ")),
dataSegment(offAlreadyPfx, []byte("already init: ")),
dataSegment(offNotActivePfx,[]byte("not active: ")),
dataSegment(offNotDispPfx, []byte("not disputed: ")),
dataSegment(offBuyerPfx, []byte("buyer: ")),
dataSegment(offSellerPfx, []byte("seller: ")),
dataSegment(offStatusPfx, []byte("status: ")),
dataSegment(offStrActive, []byte("active")),
dataSegment(offStrDisputed, []byte("disputed")),
dataSegment(offStrReleased, []byte("released")),
dataSegment(offStrRefunded, []byte("refunded")),
dataSegment(offStrBuyer, []byte("buyer")),
dataSegment(offStrSeller, []byte("seller")),
))
codeSection := section(0x0A, cat(
u(11),
bytesEqualBody(),
memcpyBody(),
logPrefixBody(),
buildFieldBody(),
initBody(),
createBody(),
releaseBody(),
refundBody(),
disputeBody(),
resolveBody(),
infoBody(),
))
module := cat(
[]byte{0x00, 0x61, 0x73, 0x6d},
[]byte{0x01, 0x00, 0x00, 0x00},
typeSection,
importSection,
functionSection,
memorySection,
exportSection,
dataSection,
codeSection,
)
out := "contracts/escrow/escrow.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))
}

View File

@@ -0,0 +1,538 @@
// gen generates contracts/governance/governance.wasm
// Run from repo root: go run ./contracts/governance/gen/
//
// Methods: init, propose, approve, reject, get, get_pending, set_admin
//
// State layout (all keys are raw UTF-8 strings):
// "admin" → admin address bytes
// "param:<key>" → current live value bytes
// "prop:<key>" → pending proposed value bytes
//
// Access control:
// approve / reject / set_admin → admin only
// propose / get / get_pending → anyone
// init → anyone (but only writes if admin not set)
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)
}
}
func cat(slices ...[]byte) []byte {
var out []byte
for _, sl := range slices {
out = append(out, sl...)
}
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)
}
func vec(items ...[]byte) []byte {
out := u(uint64(len(items)))
for _, it := range items {
out = append(out, it...)
}
return out
}
func functype(params, results []byte) []byte {
return cat([]byte{0x60}, u(uint64(len(params))), params, u(uint64(len(results))), results)
}
func importFunc(mod, name string, typeIdx uint32) []byte {
return cat(wstr(mod), wstr(name), []byte{0x00}, u(uint64(typeIdx)))
}
func exportEntry(name string, kind byte, idx uint32) []byte {
return cat(wstr(name), []byte{kind}, u(uint64(idx)))
}
func dataSegment(offset int32, data []byte) []byte {
return cat(
[]byte{0x00},
[]byte{0x41}, s(int64(offset)), []byte{0x0B},
u(uint64(len(data))), data,
)
}
func funcBody(localDecls []byte, instrs ...[]byte) []byte {
inner := cat(localDecls)
for _, ins := range instrs {
inner = append(inner, ins...)
}
inner = append(inner, 0x0B)
return cat(u(uint64(len(inner))), inner)
}
var noLocals = u(0)
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...))
}
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 block_() []byte { return []byte{0x02, 0x40} }
func loop_() []byte { return []byte{0x03, 0x40} }
func if_() []byte { return []byte{0x04, 0x40} }
func ifI32() []byte { return []byte{0x04, tI32} }
func else_() []byte { return []byte{0x05} }
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 i32GtU() []byte { return []byte{0x4B} }
func i32GeU() []byte { return []byte{0x4F} }
func i32Add() []byte { return []byte{0x6A} }
func i32Load8U() []byte { return []byte{0x2D, 0x00, 0x00} }
func i32Store8() []byte { return []byte{0x3A, 0x00, 0x00} }
// ── Memory layout ─────────────────────────────────────────────────────────────
//
// 0x000 64 arg[0] key buffer
// 0x040 256 arg[1] value buffer
// 0x140 128 caller buffer
// 0x1C0 128 admin state-read buffer
// 0x240 256 value state-read buffer
// 0x340 256 scratch (build_key writes here, log_prefix_name writes here)
//
// Constant strings:
// 0x500 6 "admin" (state key)
// 0x506 6 "param:" (state key prefix)
// 0x50D 5 "prop:" (state key prefix)
// 0x513 13 "initialized: "
// 0x521 9 "proposed: "
// 0x52B 10 "approved: "
// 0x536 10 "rejected: "
// 0x541 8 "value: "
// 0x549 11 "not set: "
// 0x555 9 "pending: "
// 0x55F 12 "no pending: "
// 0x56C 7 "admin: "
// 0x574 14 "unauthorized: "
// 0x583 14 "already init: "
const (
offArg0 int32 = 0x000
offArg1 int32 = 0x040
offCaller int32 = 0x140
offAdminRead int32 = 0x1C0
offValRead int32 = 0x240
offScratch int32 = 0x340
offKeyAdmin int32 = 0x500 // "admin" 5 bytes
offPfxParam int32 = 0x506 // "param:" 6 bytes
offPfxProp int32 = 0x50D // "prop:" 5 bytes
offInitedPfx int32 = 0x513 // "initialized: " 14
offProposedPfx int32 = 0x521 // "proposed: " 9 (key=val shown as arg0=arg1)
offApprovedPfx int32 = 0x52B // "approved: " 10
offRejectedPfx int32 = 0x536 // "rejected: " 10
offValuePfx int32 = 0x541 // "value: " 7
offNotSetPfx int32 = 0x549 // "not set: " 9
offPendingPfx int32 = 0x555 // "pending: " 9
offNoPendingPfx int32 = 0x55F // "no pending: " 12
offAdminPfx int32 = 0x56C // "admin: " 7
offUnauthPfx int32 = 0x574 // "unauthorized: " 14
offAlreadyPfx int32 = 0x583 // "already init" 12
)
const (
fnGetArgStr = 0
fnGetCaller = 1
fnGetState = 2
fnSetState = 3
fnLog = 4
fnBytesEqual = 5
fnMemcpy = 6
fnLogPrefixName = 7
fnBuildKey = 8
fnInit = 9
fnPropose = 10
fnApprove = 11
fnReject = 12
fnGet = 13
fnGetPending = 14
fnSetAdmin = 15
)
// ── Helper bodies ─────────────────────────────────────────────────────────────
func bytesEqualBody() []byte {
return funcBody(
withLocals(localDecl(2, tI32)),
ic32(1), lset(4), ic32(0), lset(3),
block_(), loop_(),
lget(3), lget(2), i32GeU(), brIf_(1),
lget(0), lget(3), i32Add(), i32Load8U(),
lget(1), lget(3), i32Add(), i32Load8U(),
i32Ne(), if_(), ic32(0), lset(4), br_(2), end_(),
lget(3), ic32(1), i32Add(), lset(3),
br_(0), end_(), end_(),
lget(4),
)
}
func memcpyBody() []byte {
return funcBody(
withLocals(localDecl(1, tI32)),
ic32(0), lset(3),
block_(), loop_(),
lget(3), lget(2), i32GeU(), brIf_(1),
lget(0), lget(3), i32Add(),
lget(1), lget(3), i32Add(), i32Load8U(),
i32Store8(),
lget(3), ic32(1), i32Add(), lset(3),
br_(0), end_(), end_(),
)
}
func logPrefixNameBody() []byte {
return funcBody(
noLocals,
ic32(offScratch), lget(0), lget(1), call(fnMemcpy),
ic32(offScratch), lget(1), i32Add(), lget(2), lget(3), call(fnMemcpy),
ic32(offScratch), lget(1), lget(3), i32Add(), call(fnLog),
)
}
// $build_key(pfxOff, pfxLen, dataOff, dataLen i32) → keyLen i32
func buildKeyBody() []byte {
return funcBody(
noLocals,
ic32(offScratch), lget(0), lget(1), call(fnMemcpy),
ic32(offScratch), lget(1), i32Add(), lget(2), lget(3), call(fnMemcpy),
lget(1), lget(3), i32Add(),
)
}
// isCallerAdmin: reads admin from state into offAdminRead, returns 1 if caller==admin.
// (callerLenLocal i32) → i32
// Leaves adminLen on stack as side effect (stored in local adminLenLocal).
// Emits code that: reads admin, compares with caller.
// Uses offAdminRead for the state read buffer.
func isCallerAdminCode(callerLenLocal, adminLenLocal uint32) []byte {
return cat(
// adminLen = get_state("admin", 5, offAdminRead, 128)
ic32(offKeyAdmin), ic32(5), ic32(offAdminRead), ic32(128), call(fnGetState), lset(adminLenLocal),
// if adminLen == 0: not initialized → return 0
lget(adminLenLocal), i32Eqz(),
ifI32(),
ic32(0),
else_(),
// bytes_equal(offCaller, offAdminRead, callerLen) if callerLen==adminLen else 0
lget(callerLenLocal), lget(adminLenLocal), i32Ne(),
ifI32(), ic32(0), else_(),
ic32(offCaller), ic32(offAdminRead), lget(callerLenLocal), call(fnBytesEqual),
end_(),
end_(),
)
}
// ── Contract method bodies ────────────────────────────────────────────────────
// init() — set caller as admin (only if not already initialized)
func initBody() []byte {
return funcBody(
withLocals(localDecl(2, tI32)),
// callerLen = get_caller(offCaller, 128)
ic32(offCaller), ic32(128), call(fnGetCaller), lset(0),
// Check if admin already set
ic32(offKeyAdmin), ic32(5), ic32(offAdminRead), ic32(128), call(fnGetState), lset(1),
lget(1), ic32(0), i32GtU(), if_(),
ic32(offAlreadyPfx), ic32(12), ic32(offCaller), lget(0), call(fnLogPrefixName),
return_(),
end_(),
// set state["admin"] = caller
ic32(offKeyAdmin), ic32(5), ic32(offCaller), lget(0), call(fnSetState),
ic32(offInitedPfx), ic32(14), ic32(offCaller), lget(0), call(fnLogPrefixName),
)
}
// propose(key, value) — store pending proposal
// Locals: keyLen(0), valLen(1), keyBufLen(2)
func proposeBody() []byte {
return funcBody(
withLocals(localDecl(3, tI32)),
ic32(0), ic32(offArg0), ic32(64), call(fnGetArgStr), lset(0),
lget(0), i32Eqz(), if_(), return_(), end_(),
ic32(1), ic32(offArg1), ic32(256), call(fnGetArgStr), lset(1),
lget(1), i32Eqz(), if_(), return_(), end_(),
// set state["prop:<key>"] = value
ic32(offPfxProp), ic32(5), ic32(offArg0), lget(0), call(fnBuildKey), lset(2),
ic32(offScratch), lget(2), ic32(offArg1), lget(1), call(fnSetState),
// log "proposed: <key>"
ic32(offProposedPfx), ic32(9), ic32(offArg0), lget(0), call(fnLogPrefixName),
)
}
// approve(key) — admin: move prop:<key> → param:<key>
// Locals: keyLen(0), callerLen(1), adminLen(2), propLen(3), keyBufLen(4)
func approveBody() []byte {
return funcBody(
withLocals(localDecl(5, tI32)),
ic32(0), ic32(offArg0), ic32(64), call(fnGetArgStr), lset(0),
lget(0), i32Eqz(), if_(), return_(), end_(),
ic32(offCaller), ic32(128), call(fnGetCaller), lset(1),
isCallerAdminCode(1, 2),
i32Eqz(), if_(),
ic32(offUnauthPfx), ic32(14), ic32(offArg0), lget(0), call(fnLogPrefixName),
return_(),
end_(),
// Read pending value: get_state("prop:<key>", offValRead, 256)
ic32(offPfxProp), ic32(5), ic32(offArg0), lget(0), call(fnBuildKey), lset(4),
ic32(offScratch), lget(4), ic32(offValRead), ic32(256), call(fnGetState), lset(3),
lget(3), i32Eqz(), if_(),
ic32(offNoPendingPfx), ic32(12), ic32(offArg0), lget(0), call(fnLogPrefixName),
return_(),
end_(),
// set state["param:<key>"] = offValRead[0..propLen)
ic32(offPfxParam), ic32(6), ic32(offArg0), lget(0), call(fnBuildKey), lset(4),
ic32(offScratch), lget(4), ic32(offValRead), lget(3), call(fnSetState),
// delete pending: set state["prop:<key>"] = ""
ic32(offPfxProp), ic32(5), ic32(offArg0), lget(0), call(fnBuildKey), lset(4),
ic32(offScratch), lget(4), ic32(offScratch), ic32(0), call(fnSetState),
ic32(offApprovedPfx), ic32(10), ic32(offArg0), lget(0), call(fnLogPrefixName),
)
}
// reject(key) — admin: delete prop:<key>
// Locals: keyLen(0), callerLen(1), adminLen(2), keyBufLen(3)
func rejectBody() []byte {
return funcBody(
withLocals(localDecl(4, tI32)),
ic32(0), ic32(offArg0), ic32(64), call(fnGetArgStr), lset(0),
lget(0), i32Eqz(), if_(), return_(), end_(),
ic32(offCaller), ic32(128), call(fnGetCaller), lset(1),
isCallerAdminCode(1, 2),
i32Eqz(), if_(),
ic32(offUnauthPfx), ic32(14), ic32(offArg0), lget(0), call(fnLogPrefixName),
return_(),
end_(),
// delete pending
ic32(offPfxProp), ic32(5), ic32(offArg0), lget(0), call(fnBuildKey), lset(3),
ic32(offScratch), lget(3), ic32(offScratch), ic32(0), call(fnSetState),
ic32(offRejectedPfx), ic32(10), ic32(offArg0), lget(0), call(fnLogPrefixName),
)
}
// get(key) — read live parameter value
// Locals: keyLen(0), valLen(1), keyBufLen(2)
func getBody() []byte {
return funcBody(
withLocals(localDecl(3, tI32)),
ic32(0), ic32(offArg0), ic32(64), call(fnGetArgStr), lset(0),
lget(0), i32Eqz(), if_(), return_(), end_(),
ic32(offPfxParam), ic32(6), ic32(offArg0), lget(0), call(fnBuildKey), lset(2),
ic32(offScratch), lget(2), ic32(offValRead), ic32(256), call(fnGetState), lset(1),
lget(1), i32Eqz(), if_(),
ic32(offNotSetPfx), ic32(9), ic32(offArg0), lget(0), call(fnLogPrefixName),
return_(),
end_(),
ic32(offValuePfx), ic32(7), ic32(offValRead), lget(1), call(fnLogPrefixName),
)
}
// get_pending(key) — read pending proposal
// Locals: keyLen(0), valLen(1), keyBufLen(2)
func getPendingBody() []byte {
return funcBody(
withLocals(localDecl(3, tI32)),
ic32(0), ic32(offArg0), ic32(64), call(fnGetArgStr), lset(0),
lget(0), i32Eqz(), if_(), return_(), end_(),
ic32(offPfxProp), ic32(5), ic32(offArg0), lget(0), call(fnBuildKey), lset(2),
ic32(offScratch), lget(2), ic32(offValRead), ic32(256), call(fnGetState), lset(1),
lget(1), i32Eqz(), if_(),
ic32(offNoPendingPfx), ic32(12), ic32(offArg0), lget(0), call(fnLogPrefixName),
return_(),
end_(),
ic32(offPendingPfx), ic32(9), ic32(offValRead), lget(1), call(fnLogPrefixName),
)
}
// set_admin(new_admin) — transfer admin role
// Locals: newAdminLen(0), callerLen(1), adminLen(2)
func setAdminBody() []byte {
return funcBody(
withLocals(localDecl(3, tI32)),
ic32(0), ic32(offArg0), ic32(128), call(fnGetArgStr), lset(0),
lget(0), i32Eqz(), if_(), return_(), end_(),
ic32(offCaller), ic32(128), call(fnGetCaller), lset(1),
isCallerAdminCode(1, 2),
i32Eqz(), if_(),
ic32(offUnauthPfx), ic32(14), ic32(offArg0), lget(0), call(fnLogPrefixName),
return_(),
end_(),
ic32(offKeyAdmin), ic32(5), ic32(offArg0), lget(0), call(fnSetState),
ic32(offAdminPfx), ic32(7), ic32(offArg0), lget(0), call(fnLogPrefixName),
)
}
// ── main ──────────────────────────────────────────────────────────────────────
func main() {
// Types:
// 0: (i32,i32,i32)→(i32) get_arg_str, bytes_equal
// 1: (i32,i32)→(i32) get_caller
// 2: (i32,i32,i32,i32)→(i32) get_state, build_key
// 3: (i32,i32,i32,i32)→() set_state, log_prefix_name
// 4: (i32,i32)→() log
// 5: ()→() exported methods
// 6: (i32,i32,i32)→() memcpy
typeSection := section(0x01, vec(
functype([]byte{tI32, tI32, tI32}, []byte{tI32}), // 0
functype([]byte{tI32, tI32}, []byte{tI32}), // 1
functype([]byte{tI32, tI32, tI32, tI32}, []byte{tI32}), // 2
functype([]byte{tI32, tI32, tI32, tI32}, []byte{}), // 3
functype([]byte{tI32, tI32}, []byte{}), // 4
functype([]byte{}, []byte{}), // 5
functype([]byte{tI32, tI32, tI32}, []byte{}), // 6
))
importSection := section(0x02, vec(
importFunc("env", "get_arg_str", 0), // 0 type 0
importFunc("env", "get_caller", 1), // 1 type 1
importFunc("env", "get_state", 2), // 2 type 2
importFunc("env", "set_state", 3), // 3 type 3
importFunc("env", "log", 4), // 4 type 4
))
// 11 local functions
functionSection := section(0x03, vec(
u(0), // bytes_equal type 0
u(6), // memcpy type 6
u(3), // log_prefix_name type 3
u(2), // build_key type 2
u(5), // init type 5
u(5), // propose type 5
u(5), // approve type 5
u(5), // reject type 5
u(5), // get type 5
u(5), // get_pending type 5
u(5), // set_admin type 5
))
memorySection := section(0x05, vec(cat([]byte{0x00}, u(2)))) // 2 pages = 128KB
exportSection := section(0x07, vec(
exportEntry("memory", 0x02, 0),
exportEntry("init", 0x00, fnInit),
exportEntry("propose", 0x00, fnPropose),
exportEntry("approve", 0x00, fnApprove),
exportEntry("reject", 0x00, fnReject),
exportEntry("get", 0x00, fnGet),
exportEntry("get_pending", 0x00, fnGetPending),
exportEntry("set_admin", 0x00, fnSetAdmin),
))
dataSection := section(0x0B, cat(
u(14),
dataSegment(offKeyAdmin, []byte("admin")),
dataSegment(offPfxParam, []byte("param:")),
dataSegment(offPfxProp, []byte("prop:")),
dataSegment(offInitedPfx, []byte("initialized: ")),
dataSegment(offProposedPfx, []byte("proposed: ")),
dataSegment(offApprovedPfx, []byte("approved: ")),
dataSegment(offRejectedPfx, []byte("rejected: ")),
dataSegment(offValuePfx, []byte("value: ")),
dataSegment(offNotSetPfx, []byte("not set: ")),
dataSegment(offPendingPfx, []byte("pending: ")),
dataSegment(offNoPendingPfx, []byte("no pending: ")),
dataSegment(offAdminPfx, []byte("admin: ")),
dataSegment(offUnauthPfx, []byte("unauthorized: ")),
dataSegment(offAlreadyPfx, []byte("already init")),
))
codeSection := section(0x0A, cat(
u(11),
bytesEqualBody(),
memcpyBody(),
logPrefixNameBody(),
buildKeyBody(),
initBody(),
proposeBody(),
approveBody(),
rejectBody(),
getBody(),
getPendingBody(),
setAdminBody(),
))
module := cat(
[]byte{0x00, 0x61, 0x73, 0x6d},
[]byte{0x01, 0x00, 0x00, 0x00},
typeSection,
importSection,
functionSection,
memorySection,
exportSection,
dataSection,
codeSection,
)
out := "contracts/governance/governance.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))
}

Binary file not shown.

View File

@@ -0,0 +1,55 @@
{
"contract": "governance",
"version": "1.0.0",
"description": "On-chain parameter governance. The deployer becomes the admin. Anyone can propose a parameter change; the admin approves or rejects it. Used to manage gas_price, messenger_entry_fee, relay_fee, etc.",
"methods": [
{
"name": "init",
"description": "Initialize the contract, setting the caller as the admin. Must be called once after deployment.",
"args": []
},
{
"name": "propose",
"description": "Submit a proposal to change a named parameter. Anyone can propose. Logs 'proposed: <key>=<value>'.",
"args": [
{"name": "key", "type": "string", "description": "Parameter name (max 64 chars)"},
{"name": "value", "type": "string", "description": "Proposed new value (max 256 chars)"}
]
},
{
"name": "approve",
"description": "Admin approves the pending proposal for a key, committing it as the live value. Logs 'approved: <key>'.",
"args": [
{"name": "key", "type": "string", "description": "Parameter key to approve"}
]
},
{
"name": "reject",
"description": "Admin rejects and removes the pending proposal for a key. Logs 'rejected: <key>'.",
"args": [
{"name": "key", "type": "string", "description": "Parameter key to reject"}
]
},
{
"name": "get",
"description": "Read the current live value of a parameter. Logs 'value: <value>' or 'not set: <key>'.",
"args": [
{"name": "key", "type": "string", "description": "Parameter key"}
]
},
{
"name": "get_pending",
"description": "Read the pending proposed value. Logs 'pending: <value>' or 'no pending: <key>'.",
"args": [
{"name": "key", "type": "string", "description": "Parameter key"}
]
},
{
"name": "set_admin",
"description": "Transfer the admin role to a new address. Only the current admin may call this. Logs 'admin: <new_admin>'.",
"args": [
{"name": "new_admin", "type": "string", "description": "New admin address (hex pubkey)"}
]
}
]
}

View File

@@ -0,0 +1,38 @@
{
"contract": "hello_go",
"version": "1.0.0",
"description": "Example DChain contract written in Go (TinyGo SDK). Demonstrates counter, owner-gated reset, string args, and inter-contract calls.",
"build": "tinygo build -o hello_go.wasm -target wasip1 -no-debug .",
"methods": [
{
"name": "increment",
"description": "Add 1 to the counter. Logs 'counter: N'.",
"args": []
},
{
"name": "get",
"description": "Log the current counter value without changing state.",
"args": []
},
{
"name": "reset",
"description": "Reset counter to 0. First caller becomes the owner; only owner can reset.",
"args": []
},
{
"name": "greet",
"description": "Log a greeting. Logs 'hello, <name>! block=N'.",
"args": [
{"name": "name", "type": "string", "description": "Name to greet (defaults to 'world')"}
]
},
{
"name": "ping",
"description": "Call a method on another contract (inter-contract call demo).",
"args": [
{"name": "contract_id", "type": "string", "description": "Target contract ID"},
{"name": "method", "type": "string", "description": "Method to call on target"}
]
}
]
}

104
contracts/hello_go/main.go Normal file
View File

@@ -0,0 +1,104 @@
// Package main is an example DChain smart contract written in Go.
//
// # Build
//
// tinygo build -o hello_go.wasm -target wasip1 -no-debug .
//
// # Deploy
//
// client deploy-contract --key /keys/node1.json \
// --wasm hello_go.wasm --abi hello_go_abi.json \
// --node http://localhost:8081
//
// # Use
//
// client call-contract --key /keys/node1.json --contract $ID \
// --method increment --gas 5000 --node http://localhost:8081
//
// The contract implements a simple counter with owner-gated reset.
// It demonstrates every SDK primitive: arguments, state, caller, logging,
// and inter-contract calls.
package main
import (
"strconv"
dc "go-blockchain/contracts/sdk"
)
// increment adds 1 to the counter and logs the new value.
//
//export increment
func increment() {
v := dc.GetU64("counter")
v++
dc.PutU64("counter", v)
dc.Log("counter: " + strconv.FormatUint(v, 10))
}
// get logs the current counter value without changing state.
//
//export get
func get() {
v := dc.GetU64("counter")
dc.Log("counter: " + strconv.FormatUint(v, 10))
}
// reset sets the counter to 0. On first call the caller becomes the owner.
// Subsequent calls are restricted to the owner.
//
//export reset
func reset() {
owner := dc.GetStateStr("owner")
caller := dc.Caller()
if owner == "" {
dc.SetStateStr("owner", caller)
dc.PutU64("counter", 0)
dc.Log("initialized — owner: " + caller[:min8(caller)])
return
}
if caller != owner {
dc.Log("unauthorized: " + caller[:min8(caller)])
return
}
dc.PutU64("counter", 0)
dc.Log("reset by owner")
}
// greet logs a personalised greeting using the first call argument.
//
//export greet
func greet() {
name := dc.ArgStr(0, 64)
if name == "" {
name = "world"
}
dc.Log("hello, " + name + "! block=" + strconv.FormatUint(dc.BlockHeight(), 10))
}
// ping calls another contract's method (demonstrates inter-contract calls).
// Args: contract_id (string), method (string)
//
//export ping
func ping() {
target := dc.ArgStr(0, 64)
method := dc.ArgStr(1, 64)
if target == "" || method == "" {
dc.Log("ping: target and method required")
return
}
if dc.CallContract(target, method, "[]") {
dc.Log("ping: " + method + " ok")
} else {
dc.Log("ping: " + method + " failed")
}
}
func min8(s string) int {
if len(s) < 8 {
return len(s)
}
return 8
}
func main() {}

View File

@@ -0,0 +1,458 @@
// gen generates contracts/name_registry/name_registry.wasm
// Run from the repo root: go run ./contracts/name_registry/gen/
//
// Contract methods: register, resolve, transfer, release
// Host imports from "env": get_arg_str, get_caller, get_state, set_state, log
//
// Every contract action emits a human-readable log entry visible in the
// block explorer, e.g.:
// "registered: alice"
// "name taken: alice"
// "not found: alice"
// "owner: <pubkey>"
// "transferred: alice"
// "unauthorized: alice"
// "released: alice"
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 _, sl := range slices {
out = append(out, sl...)
}
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)
}
func vec(items ...[]byte) []byte {
out := u(uint64(len(items)))
for _, it := range items {
out = append(out, it...)
}
return out
}
func functype(params, results []byte) []byte {
return cat([]byte{0x60}, u(uint64(len(params))), params, u(uint64(len(results))), results)
}
func importFunc(mod, name string, typeIdx uint32) []byte {
return cat(wstr(mod), wstr(name), []byte{0x00}, u(uint64(typeIdx)))
}
func exportEntry(name string, kind byte, idx uint32) []byte {
return cat(wstr(name), []byte{kind}, u(uint64(idx)))
}
func dataSegment(offset int32, data []byte) []byte {
return cat(
[]byte{0x00},
[]byte{0x41}, s(int64(offset)), []byte{0x0B},
u(uint64(len(data))), data,
)
}
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)
}
var noLocals = u(0)
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 block_() []byte { return []byte{0x02, 0x40} }
func loop_() []byte { return []byte{0x03, 0x40} }
func if_() []byte { return []byte{0x04, 0x40} }
func ifI32() []byte { return []byte{0x04, tI32} } // if that returns i32
func else_() []byte { return []byte{0x05} }
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 i32GtU() []byte { return []byte{0x4B} }
func i32GeU() []byte { return []byte{0x4F} }
func i32Add() []byte { return []byte{0x6A} }
func i32Load8U() []byte { return []byte{0x2D, 0x00, 0x00} }
func i32Store8() []byte { return []byte{0x3A, 0x00, 0x00} }
// ── Memory layout ─────────────────────────────────────────────────────────────
const (
offArg0 = 0x000 // arg[0] name buffer (64 bytes)
offArg1 = 0x040 // arg[1] new_owner buffer (128 bytes)
offCaller = 0x0C0 // caller pubkey buffer (128 bytes)
offStateRead = 0x140 // existing owner buffer (128 bytes)
// Verbose prefix strings — each ends with ": " for readable log messages.
offRegisteredPfx = 0x200 // "registered: " 12 bytes
offNameTakenPfx = 0x20C // "name taken: " 12 bytes
offNotFoundPfx = 0x218 // "not found: " 11 bytes
offOwnerPfx = 0x224 // "owner: " 7 bytes
offTransferredPfx = 0x22C // "transferred: " 13 bytes
offUnauthPfx = 0x23A // "unauthorized: " 14 bytes
offReleasedPfx = 0x249 // "released: " 10 bytes
// Scratch buffer for building concatenated log messages.
offScratch = 0x300 // 256 bytes
)
// Import function indices (order must match importSection below)
const (
fnGetArgStr = 0 // get_arg_str(idx, dstPtr, dstLen i32) → i32
fnGetCaller = 1 // get_caller(bufPtr, bufLen i32) → i32
fnGetState = 2 // get_state(kP,kL,dP,dL i32) → i32
fnSetState = 3 // set_state(kP,kL,vP,vL i32)
fnLog = 4 // log(msgPtr, msgLen i32)
)
// Local function indices (imports first, then locals in declaration order)
const (
fnBytesEqual = 5 // $bytes_equal(aPtr,bPtr,len i32) → i32
fnMemcpy = 6 // $memcpy(dst,src,len i32)
fnLogPrefixName = 7 // $log_prefix_name(pfxPtr,pfxLen,sfxPtr,sfxLen i32)
fnRegister = 8
fnResolve = 9
fnTransfer = 10
fnRelease = 11
)
// ── $bytes_equal helper ───────────────────────────────────────────────────────
// (aPtr i32, bPtr i32, len i32) → i32
// locals: i(3), same(4)
func bytesEqualBody() []byte {
return funcBody(
withLocals(localDecl(2, tI32)), // locals 3=i, 4=same
// same = 1; i = 0
ic32(1), lset(4),
ic32(0), lset(3),
block_(),
loop_(),
lget(3), lget(2), i32GeU(), brIf_(1), // i >= len → exit block
// load mem[aPtr+i]
lget(0), lget(3), i32Add(), i32Load8U(),
// load mem[bPtr+i]
lget(1), lget(3), i32Add(), i32Load8U(),
i32Ne(), if_(),
ic32(0), lset(4),
br_(2), // exit block
end_(),
lget(3), ic32(1), i32Add(), lset(3),
br_(0), // next iteration
end_(),
end_(),
lget(4), // return same
)
}
// ── $memcpy helper ────────────────────────────────────────────────────────────
// (dst i32, src i32, len i32) — copies len bytes from src to dst.
// locals: i(3)
func memcpyBody() []byte {
return funcBody(
withLocals(localDecl(1, tI32)), // local 3 = i
ic32(0), lset(3), // i = 0
block_(),
loop_(),
lget(3), lget(2), i32GeU(), brIf_(1), // if i >= len: exit
// mem[dst+i] = mem[src+i]
lget(0), lget(3), i32Add(), // dst+i (store address)
lget(1), lget(3), i32Add(), i32Load8U(), // load mem[src+i]
i32Store8(),
lget(3), ic32(1), i32Add(), lset(3), // i++
br_(0),
end_(),
end_(),
)
}
// ── $log_prefix_name helper ───────────────────────────────────────────────────
// (prefixPtr i32, prefixLen i32, suffixPtr i32, suffixLen i32)
// Builds "prefix<suffix>" in scratch buffer and logs it.
func logPrefixNameBody() []byte {
return funcBody(
noLocals,
// memcpy(offScratch, prefixPtr, prefixLen)
ic32(offScratch), lget(0), lget(1), call(fnMemcpy),
// memcpy(offScratch + prefixLen, suffixPtr, suffixLen)
ic32(offScratch), lget(1), i32Add(), lget(2), lget(3), call(fnMemcpy),
// log(offScratch, prefixLen + suffixLen)
ic32(offScratch), lget(1), lget(3), i32Add(), call(fnLog),
)
}
// ── isOwner: shared caller-vs-existing check ──────────────────────────────────
// Assumes: caller is at offCaller with len callerLenLocal,
// existing is at offStateRead with len existingLenLocal.
// Returns instructions that leave i32 (1=same, 0=not) on stack.
func isOwnerCheck(callerLenLocal, existingLenLocal uint32) []byte {
return cat(
// if callerLen != existingLen → push 0
ifI32(),
ic32(0),
else_(),
// else call bytes_equal(offCaller, offStateRead, callerLen)
ic32(offCaller), ic32(offStateRead), lget(callerLenLocal),
call(fnBytesEqual),
end_(),
)
}
func main() {
// ── Type section ──────────────────────────────────────────────────────────
// Type 0: (i32,i32,i32)→(i32) get_arg_str, bytes_equal
// Type 1: (i32,i32)→(i32) get_caller
// Type 2: (i32,i32,i32,i32)→(i32) get_state
// Type 3: (i32,i32,i32,i32)→() set_state, log_prefix_name
// Type 4: (i32,i32)→() log
// Type 5: ()→() register, resolve, transfer, release
// Type 6: (i32,i32,i32)→() memcpy
typeSection := section(0x01, vec(
functype([]byte{tI32, tI32, tI32}, []byte{tI32}), // 0
functype([]byte{tI32, tI32}, []byte{tI32}), // 1
functype([]byte{tI32, tI32, tI32, tI32}, []byte{tI32}), // 2
functype([]byte{tI32, tI32, tI32, tI32}, []byte{}), // 3
functype([]byte{tI32, tI32}, []byte{}), // 4
functype([]byte{}, []byte{}), // 5
functype([]byte{tI32, tI32, tI32}, []byte{}), // 6
))
// ── Import section ────────────────────────────────────────────────────────
importSection := section(0x02, vec(
importFunc("env", "get_arg_str", 0), // fnGetArgStr=0 type 0
importFunc("env", "get_caller", 1), // fnGetCaller=1 type 1
importFunc("env", "get_state", 2), // fnGetState=2 type 2
importFunc("env", "set_state", 3), // fnSetState=3 type 3
importFunc("env", "log", 4), // fnLog=4 type 4
))
// ── Function section: 7 local functions ──────────────────────────────────
functionSection := section(0x03, vec(
u(0), // bytes_equal → type 0
u(6), // memcpy → type 6
u(3), // log_prefix_name → type 3
u(5), // register → type 5
u(5), // resolve → type 5
u(5), // transfer → type 5
u(5), // release → type 5
))
// ── Memory section ────────────────────────────────────────────────────────
memorySection := section(0x05, vec(cat([]byte{0x00}, u(1))))
// ── Export section ────────────────────────────────────────────────────────
exportSection := section(0x07, vec(
exportEntry("memory", 0x02, 0),
exportEntry("register", 0x00, fnRegister),
exportEntry("resolve", 0x00, fnResolve),
exportEntry("transfer", 0x00, fnTransfer),
exportEntry("release", 0x00, fnRelease),
))
// ── Data section ──────────────────────────────────────────────────────────
dataSection := section(0x0B, cat(
u(7),
dataSegment(offRegisteredPfx, []byte("registered: ")),
dataSegment(offNameTakenPfx, []byte("name taken: ")),
dataSegment(offNotFoundPfx, []byte("not found: ")),
dataSegment(offOwnerPfx, []byte("owner: ")),
dataSegment(offTransferredPfx, []byte("transferred: ")),
dataSegment(offUnauthPfx, []byte("unauthorized: ")),
dataSegment(offReleasedPfx, []byte("released: ")),
))
// ── Code section ─────────────────────────────────────────────────────────
// register(): locals nameLen(0), callerLen(1), existingLen(2)
registerBody := funcBody(
withLocals(localDecl(3, tI32)),
// nameLen = get_arg_str(0, offArg0, 64)
ic32(0), ic32(offArg0), ic32(64), call(fnGetArgStr), lset(0),
// if nameLen == 0: return
lget(0), i32Eqz(), if_(), return_(), end_(),
// existingLen = get_state(offArg0, nameLen, offStateRead, 128)
ic32(offArg0), lget(0), ic32(offStateRead), ic32(128), call(fnGetState), lset(2),
// if existingLen > 0: log("name taken: <name>"); return
lget(2), ic32(0), i32GtU(), if_(),
ic32(offNameTakenPfx), ic32(12), ic32(offArg0), lget(0), call(fnLogPrefixName),
return_(),
end_(),
// callerLen = get_caller(offCaller, 128)
ic32(offCaller), ic32(128), call(fnGetCaller), lset(1),
// set_state(offArg0, nameLen, offCaller, callerLen)
ic32(offArg0), lget(0), ic32(offCaller), lget(1), call(fnSetState),
// log("registered: <name>")
ic32(offRegisteredPfx), ic32(12), ic32(offArg0), lget(0), call(fnLogPrefixName),
)
// resolve(): locals nameLen(0), ownerLen(1)
resolveBody := funcBody(
withLocals(localDecl(2, tI32)),
// nameLen = get_arg_str(0, offArg0, 64)
ic32(0), ic32(offArg0), ic32(64), call(fnGetArgStr), lset(0),
lget(0), i32Eqz(), if_(), return_(), end_(),
// ownerLen = get_state(offArg0, nameLen, offStateRead, 128)
ic32(offArg0), lget(0), ic32(offStateRead), ic32(128), call(fnGetState), lset(1),
// if ownerLen == 0: log("not found: <name>"); return
lget(1), i32Eqz(), if_(),
ic32(offNotFoundPfx), ic32(11), ic32(offArg0), lget(0), call(fnLogPrefixName),
return_(),
end_(),
// log("owner: <pubkey>")
// The pubkey stored in state is the raw caller string (hex-encoded),
// so the log will display the human-readable address.
ic32(offOwnerPfx), ic32(7), ic32(offStateRead), lget(1), call(fnLogPrefixName),
)
// transfer(): locals nameLen(0), newOwnerLen(1), callerLen(2), existingLen(3)
transferBody := funcBody(
withLocals(localDecl(4, tI32)),
// nameLen = get_arg_str(0, offArg0, 64)
ic32(0), ic32(offArg0), ic32(64), call(fnGetArgStr), lset(0),
lget(0), i32Eqz(), if_(), return_(), end_(),
// newOwnerLen = get_arg_str(1, offArg1, 128)
ic32(1), ic32(offArg1), ic32(128), call(fnGetArgStr), lset(1),
lget(1), i32Eqz(), if_(), return_(), end_(),
// existingLen = get_state(offArg0, nameLen, offStateRead, 128)
ic32(offArg0), lget(0), ic32(offStateRead), ic32(128), call(fnGetState), lset(3),
// if existingLen == 0: not registered → anyone can claim
lget(3), i32Eqz(), if_(),
ic32(offArg0), lget(0), ic32(offArg1), lget(1), call(fnSetState),
ic32(offTransferredPfx), ic32(13), ic32(offArg0), lget(0), call(fnLogPrefixName),
return_(),
end_(),
// callerLen = get_caller(offCaller, 128)
ic32(offCaller), ic32(128), call(fnGetCaller), lset(2),
// isOwner = (callerLen == existingLen) ? bytes_equal(...) : 0
lget(2), lget(3), i32Ne(),
isOwnerCheck(2, 3),
// if !isOwner: log("unauthorized: <name>"); return
i32Eqz(), if_(),
ic32(offUnauthPfx), ic32(14), ic32(offArg0), lget(0), call(fnLogPrefixName),
return_(),
end_(),
// Authorized
ic32(offArg0), lget(0), ic32(offArg1), lget(1), call(fnSetState),
ic32(offTransferredPfx), ic32(13), ic32(offArg0), lget(0), call(fnLogPrefixName),
)
// release(): locals nameLen(0), callerLen(1), existingLen(2)
releaseBody := funcBody(
withLocals(localDecl(3, tI32)),
// nameLen = get_arg_str(0, offArg0, 64)
ic32(0), ic32(offArg0), ic32(64), call(fnGetArgStr), lset(0),
lget(0), i32Eqz(), if_(), return_(), end_(),
// existingLen = get_state(offArg0, nameLen, offStateRead, 128)
ic32(offArg0), lget(0), ic32(offStateRead), ic32(128), call(fnGetState), lset(2),
// if existingLen == 0: log("not found: <name>"); return
lget(2), i32Eqz(), if_(),
ic32(offNotFoundPfx), ic32(11), ic32(offArg0), lget(0), call(fnLogPrefixName),
return_(),
end_(),
// callerLen = get_caller(offCaller, 128)
ic32(offCaller), ic32(128), call(fnGetCaller), lset(1),
// isOwner check
lget(1), lget(2), i32Ne(),
isOwnerCheck(1, 2),
i32Eqz(), if_(),
ic32(offUnauthPfx), ic32(14), ic32(offArg0), lget(0), call(fnLogPrefixName),
return_(),
end_(),
// Store empty value → effectively releases the name
ic32(offArg0), lget(0), ic32(offArg0), ic32(0), call(fnSetState),
ic32(offReleasedPfx), ic32(10), ic32(offArg0), lget(0), call(fnLogPrefixName),
)
codeSection := section(0x0A, cat(
u(7),
bytesEqualBody(),
memcpyBody(),
logPrefixNameBody(),
registerBody,
resolveBody,
transferBody,
releaseBody,
))
// ── Assemble module ───────────────────────────────────────────────────────
module := cat(
[]byte{0x00, 0x61, 0x73, 0x6d}, // \0asm
[]byte{0x01, 0x00, 0x00, 0x00}, // version 1
typeSection,
importSection,
functionSection,
memorySection,
exportSection,
dataSection,
codeSection,
)
out := "contracts/name_registry/name_registry.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))
}

Binary file not shown.

View File

@@ -0,0 +1,301 @@
(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))
)
)

View File

@@ -0,0 +1,6 @@
{"methods":[
{"name":"register", "args":[{"name":"name","type":"string"}]},
{"name":"resolve", "args":[{"name":"name","type":"string"}]},
{"name":"transfer", "args":[{"name":"name","type":"string"},{"name":"new_owner","type":"string"}]},
{"name":"release", "args":[{"name":"name","type":"string"}]}
]}

210
contracts/sdk/dchain.go Normal file
View File

@@ -0,0 +1,210 @@
//go:build tinygo
// Package dchain is the DChain smart contract SDK for TinyGo.
//
// # Build a contract
//
// tinygo build -o mycontract.wasm -target wasip1 -no-debug ./mycontract
//
// # Deploy
//
// client deploy-contract --key key.json \
// --wasm mycontract.wasm --abi mycontract_abi.json \
// --node http://localhost:8081
//
// Each exported Go function becomes a callable contract method.
// All inputs come through Arg*/GetState; all outputs go through Log/SetState.
package dchain
import "unsafe"
// ── Argument accessors ────────────────────────────────────────────────────────
//go:wasmimport env get_arg_str
func hostGetArgStr(idx uint32, ptr uintptr, maxLen uint32) uint32
//go:wasmimport env get_arg_u64
func hostGetArgU64(idx uint32) uint64
// ArgStr returns the idx-th call argument as a string (max maxLen bytes).
// Returns "" if the index is out of range.
func ArgStr(idx int, maxLen int) string {
buf := make([]byte, maxLen)
n := hostGetArgStr(uint32(idx), uintptr(unsafe.Pointer(&buf[0])), uint32(maxLen))
if n == 0 {
return ""
}
return string(buf[:n])
}
// ArgU64 returns the idx-th call argument as a uint64. Returns 0 if out of range.
func ArgU64(idx int) uint64 {
return hostGetArgU64(uint32(idx))
}
// ── State ─────────────────────────────────────────────────────────────────────
//go:wasmimport env get_state
func hostGetState(kPtr uintptr, kLen uint32, dstPtr uintptr, dstLen uint32) uint32
//go:wasmimport env set_state
func hostSetState(kPtr uintptr, kLen uint32, vPtr uintptr, vLen uint32)
//go:wasmimport env get_u64
func hostGetU64(kPtr uintptr, kLen uint32) uint64
//go:wasmimport env put_u64
func hostPutU64(kPtr uintptr, kLen uint32, val uint64)
// GetState reads a value from contract state by key.
// Returns nil if the key does not exist.
func GetState(key string) []byte {
k := []byte(key)
buf := make([]byte, 1024)
n := hostGetState(
uintptr(unsafe.Pointer(&k[0])), uint32(len(k)),
uintptr(unsafe.Pointer(&buf[0])), uint32(len(buf)),
)
if n == 0 {
return nil
}
return buf[:n]
}
// GetStateStr reads a contract state value as a string.
func GetStateStr(key string) string {
v := GetState(key)
if v == nil {
return ""
}
return string(v)
}
// SetState writes a value to contract state.
// Passing an empty slice clears the key.
func SetState(key string, value []byte) {
k := []byte(key)
if len(value) == 0 {
// vPtr=0, vLen=0 → clear key
hostSetState(uintptr(unsafe.Pointer(&k[0])), uint32(len(k)), 0, 0)
return
}
hostSetState(
uintptr(unsafe.Pointer(&k[0])), uint32(len(k)),
uintptr(unsafe.Pointer(&value[0])), uint32(len(value)),
)
}
// SetStateStr writes a string value to contract state.
func SetStateStr(key, value string) {
SetState(key, []byte(value))
}
// GetU64 reads a uint64 stored by PutU64 from contract state.
func GetU64(key string) uint64 {
k := []byte(key)
return hostGetU64(uintptr(unsafe.Pointer(&k[0])), uint32(len(k)))
}
// PutU64 stores a uint64 in contract state as 8-byte big-endian.
func PutU64(key string, val uint64) {
k := []byte(key)
hostPutU64(uintptr(unsafe.Pointer(&k[0])), uint32(len(k)), val)
}
// ── Caller & chain ────────────────────────────────────────────────────────────
//go:wasmimport env get_caller
func hostGetCaller(bufPtr uintptr, bufLen uint32) uint32
//go:wasmimport env get_block_height
func hostGetBlockHeight() uint64
//go:wasmimport env get_contract_treasury
func hostGetContractTreasury(bufPtr uintptr, bufLen uint32) uint32
// Caller returns the hex pubkey of the transaction sender (or parent contract ID).
func Caller() string {
buf := make([]byte, 128)
n := hostGetCaller(uintptr(unsafe.Pointer(&buf[0])), uint32(len(buf)))
return string(buf[:n])
}
// BlockHeight returns the height of the block currently being processed.
func BlockHeight() uint64 {
return hostGetBlockHeight()
}
// Treasury returns the contract's ownerless escrow address.
// Derived as hex(sha256(contractID+":treasury")); no private key exists.
// Only this contract can spend from it via Transfer.
func Treasury() string {
buf := make([]byte, 64)
n := hostGetContractTreasury(uintptr(unsafe.Pointer(&buf[0])), uint32(len(buf)))
return string(buf[:n])
}
// ── Token operations ──────────────────────────────────────────────────────────
//go:wasmimport env get_balance
func hostGetBalance(pubPtr uintptr, pubLen uint32) int64
//go:wasmimport env transfer
func hostTransfer(fromPtr uintptr, fromLen uint32, toPtr uintptr, toLen uint32, amount uint64) uint32
// Balance returns the token balance of a hex pubkey address in µT.
func Balance(pubKey string) uint64 {
p := []byte(pubKey)
return uint64(hostGetBalance(uintptr(unsafe.Pointer(&p[0])), uint32(len(p))))
}
// Transfer sends amount µT from one address to another.
// Returns true on success, false if from has insufficient balance.
func Transfer(from, to string, amount uint64) bool {
f := []byte(from)
t := []byte(to)
return hostTransfer(
uintptr(unsafe.Pointer(&f[0])), uint32(len(f)),
uintptr(unsafe.Pointer(&t[0])), uint32(len(t)),
amount,
) == 0
}
// ── Inter-contract calls ──────────────────────────────────────────────────────
//go:wasmimport env call_contract
func hostCallContract(cidPtr uintptr, cidLen uint32, mthPtr uintptr, mthLen uint32, argPtr uintptr, argLen uint32) uint32
// CallContract executes a method on another deployed contract.
// argsJSON must be a JSON array, e.g. `["alice", "100"]`.
// Caller of the sub-contract is set to this contract's ID.
// Gas is shared — sub-call consumes from the parent's gas budget.
// Returns true on success.
func CallContract(contractID, method, argsJSON string) bool {
cid := []byte(contractID)
mth := []byte(method)
if argsJSON == "" {
argsJSON = "[]"
}
arg := []byte(argsJSON)
return hostCallContract(
uintptr(unsafe.Pointer(&cid[0])), uint32(len(cid)),
uintptr(unsafe.Pointer(&mth[0])), uint32(len(mth)),
uintptr(unsafe.Pointer(&arg[0])), uint32(len(arg)),
) == 0
}
// ── Logging ───────────────────────────────────────────────────────────────────
//go:wasmimport env log
func hostLog(msgPtr uintptr, msgLen uint32)
// Log writes a message to the contract log.
// Logs are visible in the block explorer at /contract?id=<id> → Logs tab.
func Log(msg string) {
b := []byte(msg)
if len(b) == 0 {
return
}
hostLog(uintptr(unsafe.Pointer(&b[0])), uint32(len(b)))
}

View File

@@ -0,0 +1,53 @@
//go:build !tinygo
// Package dchain provides stub implementations for non-TinyGo builds.
// These allow go build / IDEs to compile contract code without TinyGo.
// The stubs panic at runtime — they are never executed in production.
package dchain
// ArgStr returns the idx-th call argument as a string.
func ArgStr(idx int, maxLen int) string { panic("dchain: ArgStr requires TinyGo (tinygo build -target wasip1)") }
// ArgU64 returns the idx-th call argument as a uint64.
func ArgU64(idx int) uint64 { panic("dchain: ArgU64 requires TinyGo") }
// GetState reads a value from contract state.
func GetState(key string) []byte { panic("dchain: GetState requires TinyGo") }
// GetStateStr reads a contract state value as a string.
func GetStateStr(key string) string { panic("dchain: GetStateStr requires TinyGo") }
// SetState writes a value to contract state.
func SetState(key string, value []byte) { panic("dchain: SetState requires TinyGo") }
// SetStateStr writes a string value to contract state.
func SetStateStr(key, value string) { panic("dchain: SetStateStr requires TinyGo") }
// GetU64 reads a uint64 from contract state.
func GetU64(key string) uint64 { panic("dchain: GetU64 requires TinyGo") }
// PutU64 stores a uint64 in contract state.
func PutU64(key string, val uint64) { panic("dchain: PutU64 requires TinyGo") }
// Caller returns the hex pubkey of the transaction sender.
func Caller() string { panic("dchain: Caller requires TinyGo") }
// BlockHeight returns the current block height.
func BlockHeight() uint64 { panic("dchain: BlockHeight requires TinyGo") }
// Treasury returns the contract's ownerless treasury address.
func Treasury() string { panic("dchain: Treasury requires TinyGo") }
// Balance returns the token balance of a hex pubkey in µT.
func Balance(pubKey string) uint64 { panic("dchain: Balance requires TinyGo") }
// Transfer sends amount µT from one address to another.
func Transfer(from, to string, amount uint64) bool { panic("dchain: Transfer requires TinyGo") }
// CallContract executes a method on another deployed contract.
func CallContract(contractID, method, argsJSON string) bool {
panic("dchain: CallContract requires TinyGo")
}
// Log writes a message to the contract log.
func Log(msg string) { panic("dchain: Log requires TinyGo") }

View File

@@ -0,0 +1,678 @@
// gen generates contracts/username_registry/username_registry.wasm
// Run from repo root: go run ./contracts/username_registry/gen/
//
// Methods: register, resolve, lookup, transfer, release, fee
//
// State layout:
// "name:<name>" → owner address bytes
// "addr:<address>" → name bytes
//
// Fee schedule (µT):
// len=1 → 10_000_000 len=2 → 1_000_000 len=3 → 100_000
// len=4 → 10_000 len=5 → 1_000 len≥6 → 100
//
// Fees are transferred from caller to the contract treasury address
// (sha256(contractID+":treasury") — ownerless, only the contract can spend).
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 _, sl := range slices {
out = append(out, sl...)
}
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)
}
func vec(items ...[]byte) []byte {
out := u(uint64(len(items)))
for _, it := range items {
out = append(out, it...)
}
return out
}
func functype(params, results []byte) []byte {
return cat([]byte{0x60}, u(uint64(len(params))), params, u(uint64(len(results))), results)
}
func importFunc(mod, name string, typeIdx uint32) []byte {
return cat(wstr(mod), wstr(name), []byte{0x00}, u(uint64(typeIdx)))
}
func exportEntry(name string, kind byte, idx uint32) []byte {
return cat(wstr(name), []byte{kind}, u(uint64(idx)))
}
func dataSegment(offset int32, data []byte) []byte {
return cat(
[]byte{0x00},
[]byte{0x41}, s(int64(offset)), []byte{0x0B},
u(uint64(len(data))), data,
)
}
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)
}
var noLocals = u(0)
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 ifI32() []byte { return []byte{0x04, tI32} }
func else_() []byte { return []byte{0x05} }
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 drop() []byte { return []byte{0x1A} }
func i32Eqz() []byte { return []byte{0x45} }
func i32Ne() []byte { return []byte{0x47} }
func i32GtU() []byte { return []byte{0x4B} }
func i32GeU() []byte { return []byte{0x4F} }
func i32LeU() []byte { return []byte{0x4D} }
func i32Add() []byte { return []byte{0x6A} }
func i64LtU() []byte { return []byte{0x54} }
func i64Eqz() []byte { return []byte{0x50} }
func i32Load8U() []byte { return []byte{0x2D, 0x00, 0x00} }
func i32Store8() []byte { return []byte{0x3A, 0x00, 0x00} }
// ── Memory layout ─────────────────────────────────────────────────────────────
//
// 0x000 64 arg[0] name buffer
// 0x040 128 arg[1] new_owner buffer
// 0x0C0 128 caller buffer
// 0x140 64 contract treasury buffer
// 0x180 128 state-read result buffer
//
// Constant string data (read-only, written by data segments):
// 0x200 5 "name:"
// 0x206 5 "addr:"
// 0x20C 12 "registered: "
// 0x218 12 "name taken: "
// 0x224 11 "not found: "
// 0x230 7 "owner: "
// 0x238 13 "transferred: "
// 0x246 14 "unauthorized: "
// 0x255 10 "released: "
// 0x260 6 "name: "
// 0x267 9 "no name: "
// 0x271 14 "insufficient: "
// 0x280 13 "fee: 10000000"
// 0x28D 12 "fee: 1000000"
// 0x299 11 "fee: 100000"
// 0x2A4 10 "fee: 10000"
// 0x2AE 9 "fee: 1000"
// 0x2B7 8 "fee: 100"
//
// 0x300 256 scratch buffer (used by buildKey + logPrefixName)
const (
offArg0 int32 = 0x000
offArg1 int32 = 0x040
offCaller int32 = 0x0C0
offTreasury int32 = 0x140
offStateRead int32 = 0x180
offPfxName int32 = 0x200 // "name:" 5 bytes
offPfxAddr int32 = 0x206 // "addr:" 5 bytes
offRegisteredPfx int32 = 0x20C // "registered: " 12
offNameTakenPfx int32 = 0x218 // "name taken: " 12
offNotFoundPfx int32 = 0x224 // "not found: " 11
offOwnerPfx int32 = 0x230 // "owner: " 7
offTransferredPfx int32 = 0x238 // "transferred: " 13
offUnauthPfx int32 = 0x246 // "unauthorized: " 14
offReleasedPfx int32 = 0x255 // "released: " 10
offNamePfx int32 = 0x260 // "name: " 6
offNoNamePfx int32 = 0x267 // "no name: " 9
offInsuffPfx int32 = 0x271 // "insufficient: " 14
offFee1 int32 = 0x280 // "fee: 10000000" 13
offFee2 int32 = 0x28D // "fee: 1000000" 12
offFee3 int32 = 0x299 // "fee: 100000" 11
offFee4 int32 = 0x2A4 // "fee: 10000" 10
offFee5 int32 = 0x2AE // "fee: 1000" 9
offFee6 int32 = 0x2B7 // "fee: 100" 8
offScratch int32 = 0x300
)
// ── Import / function indices ─────────────────────────────────────────────────
const (
// imports (0-based)
fnGetArgStr = 0
fnGetCaller = 1
fnGetState = 2
fnSetState = 3
fnLog = 4
fnTransfer = 5
fnGetBalance = 6
fnGetContractTreasury = 7
// local functions (after 8 imports)
fnBytesEqual = 8
fnMemcpy = 9
fnLogPrefixName = 10
fnCalcFee = 11
fnBuildKey = 12
fnRegister = 13
fnResolve = 14
fnLookup = 15
fnTransferFn = 16
fnRelease = 17
fnFee = 18
)
// ── Helper function bodies ────────────────────────────────────────────────────
// $bytes_equal(aPtr, bPtr, len i32) → i32 (1=equal, 0=not)
// extra locals: i(3), same(4)
func bytesEqualBody() []byte {
return funcBody(
withLocals(localDecl(2, tI32)),
ic32(1), lset(4),
ic32(0), lset(3),
block_(),
loop_(),
lget(3), lget(2), i32GeU(), brIf_(1),
lget(0), lget(3), i32Add(), i32Load8U(),
lget(1), lget(3), i32Add(), i32Load8U(),
i32Ne(), if_(),
ic32(0), lset(4), br_(2),
end_(),
lget(3), ic32(1), i32Add(), lset(3),
br_(0),
end_(),
end_(),
lget(4),
)
}
// $memcpy(dst, src, len i32)
// extra local: i(3)
func memcpyBody() []byte {
return funcBody(
withLocals(localDecl(1, tI32)),
ic32(0), lset(3),
block_(),
loop_(),
lget(3), lget(2), i32GeU(), brIf_(1),
lget(0), lget(3), i32Add(),
lget(1), lget(3), i32Add(), i32Load8U(),
i32Store8(),
lget(3), ic32(1), i32Add(), lset(3),
br_(0),
end_(),
end_(),
)
}
// $log_prefix_name(prefixPtr, prefixLen, suffixPtr, suffixLen i32)
// Concatenates prefix+suffix into scratch and logs the result.
func logPrefixNameBody() []byte {
return funcBody(
noLocals,
ic32(offScratch), lget(0), lget(1), call(fnMemcpy),
ic32(offScratch), lget(1), i32Add(), lget(2), lget(3), call(fnMemcpy),
ic32(offScratch), lget(1), lget(3), i32Add(), call(fnLog),
)
}
// $calc_fee(nameLen i32) → i64
// Fee tiers: 1→10M, 2→1M, 3→100K, 4→10K, 5→1K, ≥6→100 µT
func calcFeeBody() []byte {
return funcBody(
noLocals,
lget(0), ic32(1), i32LeU(), if_(),
ic64(10_000_000), return_(),
end_(),
lget(0), ic32(2), i32Ne(), if_(), // len>=3
lget(0), ic32(3), i32Ne(), if_(), // len>=4
lget(0), ic32(4), i32Ne(), if_(), // len>=5
lget(0), ic32(5), i32Ne(), if_(), // len>=6
ic64(100), return_(),
end_(),
ic64(1_000), return_(),
end_(),
ic64(10_000), return_(),
end_(),
ic64(100_000), return_(),
end_(),
ic64(1_000_000),
)
}
// $build_key(pfxOff, pfxLen, dataOff, dataLen i32) → keyLen i32
// Writes pfx+data into scratch (offScratch) and returns total length.
func buildKeyBody() []byte {
return funcBody(
noLocals,
ic32(offScratch), lget(0), lget(1), call(fnMemcpy),
ic32(offScratch), lget(1), i32Add(), lget(2), lget(3), call(fnMemcpy),
lget(1), lget(3), i32Add(),
)
}
// isOwnerCheck emits code that, given two length locals, leaves i32(1=owner,0=not) on stack.
// Assumes caller bytes are at offCaller and existing owner bytes at offStateRead.
func isOwnerCheck(callerLenLocal, existingLenLocal uint32) []byte {
return cat(
lget(callerLenLocal), lget(existingLenLocal), i32Ne(),
ifI32(),
ic32(0),
else_(),
ic32(offCaller), ic32(offStateRead), lget(callerLenLocal), call(fnBytesEqual),
end_(),
)
}
// ── Contract method bodies ────────────────────────────────────────────────────
// register(name string)
// Locals (i32): nameLen(0), callerLen(1), existingLen(2), treasuryLen(3), keyLen(4)
// Locals (i64): fee(5)
func registerBody() []byte {
return funcBody(
withLocals(localDecl(5, tI32), localDecl(1, tI64)),
// Read name arg
ic32(0), ic32(offArg0), ic32(64), call(fnGetArgStr), lset(0),
lget(0), i32Eqz(), if_(), return_(), end_(),
// Check name not already taken
ic32(offPfxName), ic32(5), ic32(offArg0), lget(0), call(fnBuildKey), lset(4),
ic32(offScratch), lget(4), ic32(offStateRead), ic32(128), call(fnGetState), lset(2),
lget(2), ic32(0), i32GtU(), if_(),
ic32(offNameTakenPfx), ic32(12), ic32(offArg0), lget(0), call(fnLogPrefixName),
return_(),
end_(),
// Get caller
ic32(offCaller), ic32(128), call(fnGetCaller), lset(1),
// Compute fee
lget(0), call(fnCalcFee), lset(5),
// Check balance >= fee
ic32(offCaller), lget(1), call(fnGetBalance),
lget(5), i64LtU(), if_(),
ic32(offInsuffPfx), ic32(14), ic32(offArg0), lget(0), call(fnLogPrefixName),
return_(),
end_(),
// Get treasury address
ic32(offTreasury), ic32(64), call(fnGetContractTreasury), lset(3),
// Transfer fee: caller → treasury
ic32(offCaller), lget(1), ic32(offTreasury), lget(3), lget(5), call(fnTransfer), drop(),
// Write name→owner
ic32(offPfxName), ic32(5), ic32(offArg0), lget(0), call(fnBuildKey), lset(4),
ic32(offScratch), lget(4), ic32(offCaller), lget(1), call(fnSetState),
// Write addr→name (reverse index)
ic32(offPfxAddr), ic32(5), ic32(offCaller), lget(1), call(fnBuildKey), lset(4),
ic32(offScratch), lget(4), ic32(offArg0), lget(0), call(fnSetState),
// Log success
ic32(offRegisteredPfx), ic32(12), ic32(offArg0), lget(0), call(fnLogPrefixName),
)
}
// resolve(name string)
// Locals: nameLen(0), ownerLen(1), keyLen(2)
func resolveBody() []byte {
return funcBody(
withLocals(localDecl(3, tI32)),
ic32(0), ic32(offArg0), ic32(64), call(fnGetArgStr), lset(0),
lget(0), i32Eqz(), if_(), return_(), end_(),
ic32(offPfxName), ic32(5), ic32(offArg0), lget(0), call(fnBuildKey), lset(2),
ic32(offScratch), lget(2), ic32(offStateRead), ic32(128), call(fnGetState), lset(1),
lget(1), i32Eqz(), if_(),
ic32(offNotFoundPfx), ic32(11), ic32(offArg0), lget(0), call(fnLogPrefixName),
return_(),
end_(),
ic32(offOwnerPfx), ic32(7), ic32(offStateRead), lget(1), call(fnLogPrefixName),
)
}
// lookup(address string) — reverse lookup address → name
// Locals: addrLen(0), nameLen(1), keyLen(2)
func lookupBody() []byte {
return funcBody(
withLocals(localDecl(3, tI32)),
ic32(0), ic32(offArg0), ic32(128), call(fnGetArgStr), lset(0),
lget(0), i32Eqz(), if_(), return_(), end_(),
ic32(offPfxAddr), ic32(5), ic32(offArg0), lget(0), call(fnBuildKey), lset(2),
ic32(offScratch), lget(2), ic32(offStateRead), ic32(128), call(fnGetState), lset(1),
lget(1), i32Eqz(), if_(),
ic32(offNoNamePfx), ic32(9), ic32(offArg0), lget(0), call(fnLogPrefixName),
return_(),
end_(),
ic32(offNamePfx), ic32(6), ic32(offStateRead), lget(1), call(fnLogPrefixName),
)
}
// transfer(name, new_owner) — transfer username ownership
// Locals: nameLen(0), newOwnerLen(1), callerLen(2), existingLen(3), keyLen(4)
func transferBody() []byte {
return funcBody(
withLocals(localDecl(5, tI32)),
ic32(0), ic32(offArg0), ic32(64), call(fnGetArgStr), lset(0),
lget(0), i32Eqz(), if_(), return_(), end_(),
ic32(1), ic32(offArg1), ic32(128), call(fnGetArgStr), lset(1),
lget(1), i32Eqz(), if_(), return_(), end_(),
// Look up existing owner
ic32(offPfxName), ic32(5), ic32(offArg0), lget(0), call(fnBuildKey), lset(4),
ic32(offScratch), lget(4), ic32(offStateRead), ic32(128), call(fnGetState), lset(3),
lget(3), i32Eqz(), if_(),
ic32(offNotFoundPfx), ic32(11), ic32(offArg0), lget(0), call(fnLogPrefixName),
return_(),
end_(),
// Verify caller is the current owner
ic32(offCaller), ic32(128), call(fnGetCaller), lset(2),
isOwnerCheck(2, 3),
i32Eqz(), if_(),
ic32(offUnauthPfx), ic32(14), ic32(offArg0), lget(0), call(fnLogPrefixName),
return_(),
end_(),
// Delete old reverse index: addr:<oldOwner> → ""
ic32(offPfxAddr), ic32(5), ic32(offStateRead), lget(3), call(fnBuildKey), lset(4),
ic32(offScratch), lget(4), ic32(offScratch), ic32(0), call(fnSetState),
// Update forward index: name:<name> → newOwner
ic32(offPfxName), ic32(5), ic32(offArg0), lget(0), call(fnBuildKey), lset(4),
ic32(offScratch), lget(4), ic32(offArg1), lget(1), call(fnSetState),
// Add new reverse index: addr:<newOwner> → name
ic32(offPfxAddr), ic32(5), ic32(offArg1), lget(1), call(fnBuildKey), lset(4),
ic32(offScratch), lget(4), ic32(offArg0), lget(0), call(fnSetState),
ic32(offTransferredPfx), ic32(13), ic32(offArg0), lget(0), call(fnLogPrefixName),
)
}
// release(name) — release a username registration
// Locals: nameLen(0), callerLen(1), existingLen(2), keyLen(3)
func releaseBody() []byte {
return funcBody(
withLocals(localDecl(4, tI32)),
ic32(0), ic32(offArg0), ic32(64), call(fnGetArgStr), lset(0),
lget(0), i32Eqz(), if_(), return_(), end_(),
ic32(offPfxName), ic32(5), ic32(offArg0), lget(0), call(fnBuildKey), lset(3),
ic32(offScratch), lget(3), ic32(offStateRead), ic32(128), call(fnGetState), lset(2),
lget(2), i32Eqz(), if_(),
ic32(offNotFoundPfx), ic32(11), ic32(offArg0), lget(0), call(fnLogPrefixName),
return_(),
end_(),
ic32(offCaller), ic32(128), call(fnGetCaller), lset(1),
isOwnerCheck(1, 2),
i32Eqz(), if_(),
ic32(offUnauthPfx), ic32(14), ic32(offArg0), lget(0), call(fnLogPrefixName),
return_(),
end_(),
// Delete reverse index: addr:<owner> → ""
ic32(offPfxAddr), ic32(5), ic32(offStateRead), lget(2), call(fnBuildKey), lset(3),
ic32(offScratch), lget(3), ic32(offScratch), ic32(0), call(fnSetState),
// Delete forward index: name:<name> → ""
ic32(offPfxName), ic32(5), ic32(offArg0), lget(0), call(fnBuildKey), lset(3),
ic32(offScratch), lget(3), ic32(offScratch), ic32(0), call(fnSetState),
ic32(offReleasedPfx), ic32(10), ic32(offArg0), lget(0), call(fnLogPrefixName),
)
}
// fee(name) — log the registration fee for a given name
// Uses pre-baked static strings; no arithmetic needed.
// Locals: nameLen(0)
func feeBody() []byte {
return funcBody(
withLocals(localDecl(1, tI32)),
ic32(0), ic32(offArg0), ic32(64), call(fnGetArgStr), lset(0),
lget(0), i32Eqz(), if_(), return_(), end_(),
// Branch on name length and log the matching static fee string.
lget(0), ic32(1), i32LeU(), if_(),
ic32(offFee1), ic32(13), call(fnLog), return_(),
end_(),
lget(0), ic32(2), i32Ne(), if_(), // len >= 3
lget(0), ic32(3), i32Ne(), if_(), // len >= 4
lget(0), ic32(4), i32Ne(), if_(), // len >= 5
lget(0), ic32(5), i32Ne(), if_(), // len >= 6
ic32(offFee6), ic32(8), call(fnLog), return_(),
end_(),
ic32(offFee5), ic32(9), call(fnLog), return_(),
end_(),
ic32(offFee4), ic32(10), call(fnLog), return_(),
end_(),
ic32(offFee3), ic32(11), call(fnLog), return_(),
end_(),
// len == 2
ic32(offFee2), ic32(12), call(fnLog),
)
}
// ── main ──────────────────────────────────────────────────────────────────────
func main() {
// ── Type table ────────────────────────────────────────────────────────────
// 0: (i32,i32,i32)→(i32) get_arg_str, bytes_equal
// 1: (i32,i32)→(i32) get_caller, get_contract_treasury
// 2: (i32,i32,i32,i32)→(i32) get_state, build_key
// 3: (i32,i32,i32,i32)→() set_state, log_prefix_name
// 4: (i32,i32)→() log
// 5: (i32,i32,i32,i32,i64)→(i32) transfer
// 6: (i32,i32)→(i64) get_balance
// 7: ()→() exported methods
// 8: (i32,i32,i32)→() memcpy
// 9: (i32)→(i64) calc_fee
typeSection := section(0x01, vec(
functype([]byte{tI32, tI32, tI32}, []byte{tI32}), // 0
functype([]byte{tI32, tI32}, []byte{tI32}), // 1
functype([]byte{tI32, tI32, tI32, tI32}, []byte{tI32}), // 2
functype([]byte{tI32, tI32, tI32, tI32}, []byte{}), // 3
functype([]byte{tI32, tI32}, []byte{}), // 4
functype([]byte{tI32, tI32, tI32, tI32, tI64}, []byte{tI32}), // 5
functype([]byte{tI32, tI32}, []byte{tI64}), // 6
functype([]byte{}, []byte{}), // 7
functype([]byte{tI32, tI32, tI32}, []byte{}), // 8
functype([]byte{tI32}, []byte{tI64}), // 9
))
importSection := section(0x02, vec(
importFunc("env", "get_arg_str", 0), // 0 type 0
importFunc("env", "get_caller", 1), // 1 type 1
importFunc("env", "get_state", 2), // 2 type 2
importFunc("env", "set_state", 3), // 3 type 3
importFunc("env", "log", 4), // 4 type 4
importFunc("env", "transfer", 5), // 5 type 5
importFunc("env", "get_balance", 6), // 6 type 6
importFunc("env", "get_contract_treasury", 1), // 7 type 1
))
// 11 local functions
functionSection := section(0x03, vec(
u(0), // bytes_equal type 0
u(8), // memcpy type 8
u(3), // log_prefix_name type 3
u(9), // calc_fee type 9
u(2), // build_key type 2
u(7), // register type 7
u(7), // resolve type 7
u(7), // lookup type 7
u(7), // transfer_fn type 7
u(7), // release type 7
u(7), // fee type 7
))
memorySection := section(0x05, vec(cat([]byte{0x00}, u(1))))
exportSection := section(0x07, vec(
exportEntry("memory", 0x02, 0),
exportEntry("register", 0x00, fnRegister),
exportEntry("resolve", 0x00, fnResolve),
exportEntry("lookup", 0x00, fnLookup),
exportEntry("transfer", 0x00, fnTransferFn),
exportEntry("release", 0x00, fnRelease),
exportEntry("fee", 0x00, fnFee),
))
dataSection := section(0x0B, cat(
u(19),
dataSegment(offPfxName, []byte("name:")),
dataSegment(offPfxAddr, []byte("addr:")),
dataSegment(offRegisteredPfx, []byte("registered: ")),
dataSegment(offNameTakenPfx, []byte("name taken: ")),
dataSegment(offNotFoundPfx, []byte("not found: ")),
dataSegment(offOwnerPfx, []byte("owner: ")),
dataSegment(offTransferredPfx, []byte("transferred: ")),
dataSegment(offUnauthPfx, []byte("unauthorized: ")),
dataSegment(offReleasedPfx, []byte("released: ")),
dataSegment(offNamePfx, []byte("name: ")),
dataSegment(offNoNamePfx, []byte("no name: ")),
dataSegment(offInsuffPfx, []byte("insufficient: ")),
dataSegment(offFee1, []byte("fee: 10000000")),
dataSegment(offFee2, []byte("fee: 1000000")),
dataSegment(offFee3, []byte("fee: 100000")),
dataSegment(offFee4, []byte("fee: 10000")),
dataSegment(offFee5, []byte("fee: 1000")),
dataSegment(offFee6, []byte("fee: 100")),
// pad entry to make count = 19 (u(19) above) — actually 18 strings above
// Let me count: pfxName, pfxAddr, registered, nameTaken, notFound, owner,
// transferred, unauth, released, name, noName, insuff, fee1..fee6 = 18
// Fix: change u(19) → u(18) — corrected below in final assembly.
))
// Recount: 18 data segments total — rebuild without the u() wrapper mismatch.
dataSection = section(0x0B, cat(
u(18),
dataSegment(offPfxName, []byte("name:")),
dataSegment(offPfxAddr, []byte("addr:")),
dataSegment(offRegisteredPfx, []byte("registered: ")),
dataSegment(offNameTakenPfx, []byte("name taken: ")),
dataSegment(offNotFoundPfx, []byte("not found: ")),
dataSegment(offOwnerPfx, []byte("owner: ")),
dataSegment(offTransferredPfx, []byte("transferred: ")),
dataSegment(offUnauthPfx, []byte("unauthorized: ")),
dataSegment(offReleasedPfx, []byte("released: ")),
dataSegment(offNamePfx, []byte("name: ")),
dataSegment(offNoNamePfx, []byte("no name: ")),
dataSegment(offInsuffPfx, []byte("insufficient: ")),
dataSegment(offFee1, []byte("fee: 10000000")),
dataSegment(offFee2, []byte("fee: 1000000")),
dataSegment(offFee3, []byte("fee: 100000")),
dataSegment(offFee4, []byte("fee: 10000")),
dataSegment(offFee5, []byte("fee: 1000")),
dataSegment(offFee6, []byte("fee: 100")),
))
codeSection := section(0x0A, cat(
u(11),
bytesEqualBody(),
memcpyBody(),
logPrefixNameBody(),
calcFeeBody(),
buildKeyBody(),
registerBody(),
resolveBody(),
lookupBody(),
transferBody(),
releaseBody(),
feeBody(),
))
module := cat(
[]byte{0x00, 0x61, 0x73, 0x6d},
[]byte{0x01, 0x00, 0x00, 0x00},
typeSection,
importSection,
functionSection,
memorySection,
exportSection,
dataSection,
codeSection,
)
out := "contracts/username_registry/username_registry.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))
}

Binary file not shown.

View File

@@ -0,0 +1,50 @@
{
"contract": "username_registry",
"version": "1.0.0",
"description": "Maps human-readable usernames to wallet addresses. Shorter names cost more to register. Fees go to the contract treasury.",
"methods": [
{
"name": "register",
"description": "Register a username for the caller. Fee = 10^(7 - min(len,6)) µT (1-char=10M, 2=1M, 3=100K, 4=10K, 5=1K, 6+=100). Caller must have sufficient balance.",
"args": [
{"name": "name", "type": "string", "description": "Username to register (max 64 chars, lowercase)"}
]
},
{
"name": "resolve",
"description": "Look up the wallet address that owns a username. Logs 'owner: <address>' or 'not found: <name>'.",
"args": [
{"name": "name", "type": "string", "description": "Username to look up"}
]
},
{
"name": "lookup",
"description": "Reverse lookup: find the username registered to a given address. Logs 'name: <username>' or 'no name: <address>'.",
"args": [
{"name": "address", "type": "string", "description": "Wallet address (hex pubkey)"}
]
},
{
"name": "transfer",
"description": "Transfer ownership of a username to another address. Only the current owner may call this.",
"args": [
{"name": "name", "type": "string", "description": "Username to transfer"},
{"name": "new_owner", "type": "string", "description": "New owner address (hex pubkey)"}
]
},
{
"name": "release",
"description": "Release a username registration. Only the current owner may call this.",
"args": [
{"name": "name", "type": "string", "description": "Username to release"}
]
},
{
"name": "fee",
"description": "Query the registration fee for a given name length. Logs 'fee: <amount>' in µT.",
"args": [
{"name": "name", "type": "string", "description": "Name whose fee you want to check"}
]
}
]
}