# Бинарный WASM Альтернативный способ написания контрактов — генерация WASM-байткода вручную. Используется для системных/минимальных контрактов где критичен размер (~500–2000 байт вместо 30–100 KB TinyGo). > **Рекомендуется TinyGo** для новых контрактов. Бинарный WASM используется для встроенных контрактов в этом проекте. ## Структура генератора ``` contracts/ mycontract/ gen/ main.go # Go-программа, печатает WASM байты mycontract.wasm # Результат: go run gen/main.go > mycontract.wasm mycontract_abi.json ``` Генератор запускается стандартным Go (`go run gen/main.go`) и выводит бинарный WASM в stdout. ## Анатомия WASM модуля ``` Magic + Version : \0asm\x01\x00\x00\x00 Секции (по порядку): 1. Type — сигнатуры функций 2. Import — импортируемые host-функции ("env" модуль) 3. Function — индексы типов для локальных функций 4. Export — экспортируемые функции (методы контракта) 5. Code — тела функций 6. Data — статические строки в памяти 7. Memory — объявление памяти (мин. 1 страница = 64 KB) ``` ## LEB128 кодирование Целые числа в WASM кодируются в LEB128 (variable-length encoding). ```go // Unsigned LEB128 func u(v uint64) []byte { var out []byte for { b := byte(v & 0x7f) v >>= 7 if v != 0 { b |= 0x80 } out = append(out, b) if v == 0 { break } } return out } // Signed LEB128 (для i32/i64 константы) func s(v int64) []byte { var out []byte for { b := byte(v & 0x7f) v >>= 7 if (v == 0 && b&0x40 == 0) || (v == -1 && b&0x40 != 0) { out = append(out, b) break } out = append(out, b|0x80) } return out } ``` ## Вспомогательные функции ```go // Секция с длиной-префиксом func section(id byte, content []byte) []byte { return append(append([]byte{id}, u(uint64(len(content)))...), content...) } // Вектор (count + элементы) func vec(items ...[]byte) []byte { out := u(uint64(len(items))) for _, item := range items { out = append(out, item...) } return out } // Строка с длиной-префиксом func str(s string) []byte { return append(u(uint64(len(s))), []byte(s)...) } ``` ## WASM инструкции Наиболее используемые опкоды: | Инструкция | Байт | Описание | |-----------|------|---------| | `local.get` | `0x20 + leb(idx)` | Прочитать локальную переменную | | `local.set` | `0x21 + leb(idx)` | Записать локальную переменную | | `local.tee` | `0x22 + leb(idx)` | Записать и оставить на стеке | | `i32.const` | `0x41 + sleb(val)` | Константа i32 | | `i64.const` | `0x42 + sleb(val)` | Константа i64 | | `i32.load` | `0x28 0x02 leb(offset)` | Загрузить 4 байта | | `i32.store` | `0x36 0x02 leb(offset)` | Сохранить 4 байта | | `i64.load` | `0x29 0x03 leb(offset)` | Загрузить 8 байт | | `i64.store` | `0x37 0x03 leb(offset)` | Сохранить 8 байт | | `i32.add` | `0x6A` | Сложение i32 | | `i32.sub` | `0x6B` | Вычитание i32 | | `i32.eq` | `0x46` | Равенство i32 | | `i64.eq` | `0x51` | Равенство i64 | | `if` | `0x04 0x40` | Ветвление (void) | | `else` | `0x05` | — | | `end` | `0x0B` | Конец блока/функции | | `return` | `0x0F` | Возврат из функции | | `call` | `0x10 + leb(funcIdx)` | Вызов функции | | `drop` | `0x1A` | Убрать верхний элемент стека | ## Шаблон генератора ```go package main import ( "os" ) // Импорты host-функций const ( fnGetArgStr = 0 // индекс в таблице импортов fnSetState = 1 fnGetState = 2 fnGetStateLen = 3 fnGetCaller = 4 fnLog = 5 ) // Смещения в памяти const ( pKey = 0 // буфер для ключей state pVal = 128 // буфер для значений pCaller = 256 // буфер для caller pArg0 = 320 // буфер для аргумента 0 pStatic = 400 // статические строки (из Data секции) ) func main() { // 1. Magic + version wasm := []byte{0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00} // 2. Type секция — сигнатуры функций // ... // 3. Import секция — host функции // ... // 4. Function + Memory + Export + Code + Data // ... os.Stdout.Write(wasm) } ``` ## Пример: функция increment Аналог Go-кода: ```go func increment() { count := getU64("counter") putU64("counter", count+1) log("incremented") } ``` В бинарном WASM: ```go // В Data секции: "counter\x00" @ offset 0, "incremented\x00" @ offset 8 // Функция increment: func buildIncrement() []byte { // locals: none body := []byte{0x00} // local decl count = 0 // load key "counter" (ptr=0, len=7) → call get_u64 → result on stack body = append(body, 0x41) // i32.const body = append(body, s(0)...) // ptr = 0 (offset of "counter" in data) body = append(body, 0x41) body = append(body, s(7)...) // len = 7 body = append(body, 0x10) // call body = append(body, u(fnGetU64)...) // add 1 body = append(body, 0x42, 0x01) // i64.const 1 body = append(body, 0x7C) // i64.add // put_u64("counter", result) body = append(body, 0x21, 0x00) // local.set 0 (tmp) body = append(body, 0x41) body = append(body, s(0)...) body = append(body, 0x41) body = append(body, s(7)...) body = append(body, 0x20, 0x00) // local.get 0 body = append(body, 0x10) body = append(body, u(fnPutU64)...) // log("incremented") body = append(body, 0x41) body = append(body, s(8)...) // ptr = 8 (offset of "incremented") body = append(body, 0x41) body = append(body, s(11)...) // len = 11 body = append(body, 0x10) body = append(body, u(fnLog)...) body = append(body, 0x0B) // end return append(u(uint64(len(body))), body...) } ``` ## Сборка и деплой ```bash # Генерация go run contracts/mycontract/gen/main.go > contracts/mycontract/mycontract.wasm # Проверка (wasm-objdump из wabt) wasm-objdump -d contracts/mycontract/mycontract.wasm # Деплой client deploy-contract \ --key key.json \ --wasm contracts/mycontract/mycontract.wasm \ --abi contracts/mycontract/mycontract_abi.json \ --node http://localhost:8081 ``` ## Встроенные контракты в проекте | Контракт | Генератор | Размер .wasm | |---------|----------|-------------| | counter | contracts/counter/gen/main.go | ~500 байт | | governance | contracts/governance/gen/main.go | ~800 байт | | name_registry | contracts/name_registry/gen/main.go | ~1.2 KB | | username_registry | contracts/username_registry/gen/main.go | ~1.5 KB | | escrow | contracts/escrow/gen/main.go | ~2 KB | | auction | contracts/auction/gen/main.go | ~2.5 KB | Все генераторы используют одни и те же LEB128-утилиты и паттерн `vec/section/str`.