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:
678
contracts/username_registry/gen/main.go
Normal file
678
contracts/username_registry/gen/main.go
Normal 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))
|
||||
}
|
||||
BIN
contracts/username_registry/username_registry.wasm
Normal file
BIN
contracts/username_registry/username_registry.wasm
Normal file
Binary file not shown.
50
contracts/username_registry/username_registry_abi.json
Normal file
50
contracts/username_registry/username_registry_abi.json
Normal 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"}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user