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:
784
contracts/auction/gen/main.go
Normal file
784
contracts/auction/gen/main.go
Normal 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))
|
||||
}
|
||||
Reference in New Issue
Block a user