UNPKG

@btc-vision/btc-runtime

Version:

Bitcoin L1 Smart Contract Runtime for OP_NET. Build decentralized applications on Bitcoin using AssemblyScript and WebAssembly. Fully audited.

1,015 lines (818 loc) 27.1 kB
# BytesWriter and BytesReader `BytesWriter` and `BytesReader` handle binary serialization and deserialization. They're used for encoding return values, event data, and cross-contract communication. ## BytesWriter ### Overview ```typescript import { BytesWriter, Address } from '@btc-vision/btc-runtime/runtime'; import { u256 } from '@btc-vision/as-bignum/assembly'; // Create writer with initial capacity const writer = new BytesWriter(64); // Write data writer.writeAddress(recipient); writer.writeU256(amount); writer.writeBoolean(true); // Get encoded bytes const data: Uint8Array = writer.getBuffer(); ``` ### BytesWriter/Reader Architecture ```mermaid classDiagram class BytesWriter { -DataView buffer -Uint8Array typedArray -u32 currentOffset +BytesWriter(length) +write~T~(value) void +writeU8(value) +writeU16(value, be) +writeU32(value, be) +writeU64(value, be) +writeU128(value, be) +writeU256(value, be) +writeAddress(addr) +writeExtendedAddress(extAddr) +writeSchnorrSignature(addr, sig) +writeString(str) +writeBytes(data) +writeBoolean(bool) +writeExtendedAddressArray(arr) +writeExtendedAddressMapU256(map) +getBuffer() Uint8Array } class BytesReader { -DataView buffer -i32 currentOffset +BytesReader(bytes) +read~T~() T +readU8() u8 +readU16(be) u16 +readU32(be) u32 +readU64(be) u64 +readU128(be) u128 +readU256(be) u256 +readAddress() Address +readExtendedAddress() ExtendedAddress +readSchnorrSignature() SchnorrSignature +readString() string +readBytes(length) Uint8Array +readBoolean() bool +readExtendedAddressArray() ExtendedAddress[] +readExtendedAddressMapU256() ExtendedAddressMap +hasMoreData() bool } class DataView { <<built-in>> +getUint8() +setUint8() +getUint32() +setUint32() } BytesWriter --> DataView : uses BytesReader --> DataView : uses note for BytesWriter "Sequential write\nfixed-size buffer" note for BytesReader "Sequential read\nwith position tracking" ``` ### Creating a BytesWriter ```typescript // With initial capacity (recommended - buffer does NOT auto-grow) const writer = new BytesWriter(128); // Note: Buffer does NOT grow dynamically - will throw Revert if exceeded // Always pre-calculate required size or use generous initial capacity ``` ### Writing Primitives ```typescript // Boolean writer.writeBoolean(true); // 1 byte // Unsigned integers writer.writeU8(255); // 1 byte writer.writeU16(65535); // 2 bytes writer.writeU32(4294967295); // 4 bytes writer.writeU64(value); // 8 bytes writer.writeU128(value); // 16 bytes writer.writeU256(value); // 32 bytes // Signed integers writer.writeI8(-128); writer.writeI16(-32768); writer.writeI32(-2147483648); writer.writeI64(value); ``` ### Writing Complex Types ```typescript // Address (32 bytes) writer.writeAddress(address); // ExtendedAddress (64 bytes: 32 tweaked key + 32 ML-DSA key hash) writer.writeExtendedAddress(extendedAddress); // Schnorr signature with signer address (128 bytes total) writer.writeSchnorrSignature(signerAddress, signature); // String without length prefix writer.writeString('Hello, World!'); // String with u32 length prefix writer.writeStringWithLength('Hello, World!'); // Bytes without length prefix writer.writeBytes(data); // Bytes with u32 length prefix writer.writeBytesWithLength(data); // Selector (4 bytes, big-endian) writer.writeSelector(selector); ``` ### Generic Write Method The `write<T>()` method automatically selects the correct write method based on the type: ```typescript // Automatically dispatches to the correct write method writer.write<u64>(12345); // writeU64 writer.write<u256>(amount); // writeU256 writer.write<bool>(true); // writeBoolean writer.write<Address>(addr); // writeAddress writer.write<ExtendedAddress>(extAddr); // writeExtendedAddress writer.write<string>('hello'); // writeStringWithLength // Supported types: // - Primitives: bool, u8, u16, u32, u64, i8, i16, i32, i64 // - Big numbers: u128, u256, i128 // - Complex: Address, ExtendedAddress, Selector, string, Uint8Array ``` ### Writing Arrays All array methods use a u16 length prefix (max 65535 elements): ```typescript // Address array (u16 length prefix + addresses) writer.writeAddressArray(addresses); // ExtendedAddress array (u16 length prefix + 64-byte addresses) writer.writeExtendedAddressArray(extendedAddresses); // Numeric arrays (u16 length prefix + values) writer.writeU8Array(u8Values); writer.writeU16Array(u16Values); writer.writeU32Array(u32Values); writer.writeU64Array(u64Values); writer.writeU128Array(u128Values); writer.writeU256Array(u256Values); // Array of variable-length buffers (u16 count, then u32 length + data for each) writer.writeArrayOfBuffer(buffers); // AddressMap<u256> (u16 count, then address + u256 pairs) writer.writeAddressMapU256(addressMap); // ExtendedAddressMap<u256> (u16 count, then 64-byte address + u256 pairs) writer.writeExtendedAddressMapU256(extendedAddressMap); ``` ### Getting Results ```typescript // Get the full buffer const buffer: Uint8Array = writer.getBuffer(); // Get current write offset (bytes written so far) const offset: u32 = writer.getOffset(); // Get total buffer capacity const capacity: u32 = writer.bufferLength(); // Convert to BytesReader for reading const reader: BytesReader = writer.toBytesReader(); ``` ## BytesReader ### Overview ```typescript import { BytesReader, Address } from '@btc-vision/btc-runtime/runtime'; import { u256 } from '@btc-vision/as-bignum/assembly'; // Create reader from buffer const reader = new BytesReader(data); // Read data in same order it was written const recipient: Address = reader.readAddress(); const amount: u256 = reader.readU256(); const flag: bool = reader.readBoolean(); ``` ### Creating a BytesReader ```typescript // From Uint8Array const reader = new BytesReader(buffer); // From BytesWriter const reader = writer.toBytesReader(); ``` ### Reading Primitives ```typescript // Boolean const flag: bool = reader.readBoolean(); // Unsigned integers const u8val: u8 = reader.readU8(); const u16val: u16 = reader.readU16(); const u32val: u32 = reader.readU32(); const u64val: u64 = reader.readU64(); const u128val: u128 = reader.readU128(); const u256val: u256 = reader.readU256(); // Signed integers const i8val: i8 = reader.readI8(); const i16val: i16 = reader.readI16(); const i32val: i32 = reader.readI32(); const i64val: i64 = reader.readI64(); ``` ### Reading Complex Types ```typescript // Address (32 bytes) const addr: Address = reader.readAddress(); // ExtendedAddress (64 bytes: 32 tweaked key + 32 ML-DSA key hash) const extAddr: ExtendedAddress = reader.readExtendedAddress(); // Schnorr signature with signer address (128 bytes) const schnorrSig: SchnorrSignature = reader.readSchnorrSignature(); const signer: ExtendedAddress = schnorrSig.address; const signature: Uint8Array = schnorrSig.signature; // String with known length (bytes read, zeroStop = true) const name: string = reader.readString(32); // reads up to 32 bytes, stops at null // String with u32 length prefix const name2: string = reader.readStringWithLength(); // Bytes with known length const data: Uint8Array = reader.readBytes(64); // Bytes with u32 length prefix const data2: Uint8Array = reader.readBytesWithLength(); // Selector (4 bytes, big-endian) const selector: Selector = reader.readSelector(); ``` ### Generic Read Method The `read<T>()` method automatically selects the correct read method based on the type: ```typescript // Automatically dispatches to the correct read method const num: u64 = reader.read<u64>(); // readU64 const amount: u256 = reader.read<u256>(); // readU256 const flag: bool = reader.read<bool>(); // readBoolean const addr: Address = reader.read<Address>(); // readAddress const extAddr: ExtendedAddress = reader.read<ExtendedAddress>(); // readExtendedAddress const text: string = reader.read<string>(); // readStringWithLength // Supported types: // - Primitives: bool, u8, u16, u32, u64, i8, i16, i32, i64 // - Big numbers: u128, u256, i128 // - Complex: Address, ExtendedAddress, Selector, string ``` ### Reading Arrays All array methods expect a u16 length prefix: ```typescript // Address array const addresses: Address[] = reader.readAddressArray(); // ExtendedAddress array (64 bytes each) const extAddresses: ExtendedAddress[] = reader.readExtendedAddressArray(); // Numeric arrays const u8Values: u8[] = reader.readU8Array(); const u16Values: u16[] = reader.readU16Array(); const u32Values: u32[] = reader.readU32Array(); const u64Values: u64[] = reader.readU64Array(); const u128Values: u128[] = reader.readU128Array(); const u256Values: u256[] = reader.readU256Array(); // Array of variable-length buffers const buffers: Uint8Array[] = reader.readArrayOfBuffer(); // AddressMap<u256> const addressMap: AddressMap<u256> = reader.readAddressMapU256(); // ExtendedAddressMap<u256> (64-byte addresses as keys) const extAddressMap: ExtendedAddressMap<u256> = reader.readExtendedAddressMapU256(); ``` ### Position Management ```typescript // Get current read offset const offset: i32 = reader.getOffset(); // Set read position (must be within buffer bounds) reader.setOffset(32); // Get total buffer length const total: i32 = reader.byteLength; // Verify enough bytes remain for next read reader.verifyEnd(offset + 32); // Throws Revert if not enough bytes ``` ## Data Format ### Encoding Summary | Type | Size | Format | |------|------|--------| | `bool` | 1 | 0 or 1 | | `u8`/`i8` | 1 | Raw byte | | `u16`/`i16` | 2 | Big-endian (default) | | `u32`/`i32` | 4 | Big-endian (default) | | `u64`/`i64` | 8 | Big-endian (default) | | `u128`/`i128` | 16 | Big-endian (default) | | `u256` | 32 | Big-endian (default) | | `Address` | 32 | Raw bytes | | `ExtendedAddress` | 64 | 32 bytes tweaked key + 32 bytes ML-DSA key hash | | `SchnorrSignature` | 128 | 64 bytes ExtendedAddress + 64 bytes signature | | `Selector` | 4 | Big-endian (u32) | | `string` | 4 + n | Length prefix (u32 BE) + UTF-8 | | `bytes` | 4 + n | Length prefix (u32 BE) + raw | | `arrays` | 2 + n | Length prefix (u16 BE) + elements | **Note:** All multi-byte integers default to big-endian. Pass `be = false` for little-endian: ```typescript writer.writeU32(value, false); // Little-endian reader.readU32(false); // Little-endian ``` ### Data Encoding Flow ```mermaid --- config: theme: dark --- flowchart LR A["Data"] --> B{"Type?"} B -->|"Primitive"| C["Fixed Size"] B -->|"String/Array"| D["Length Prefix + Data"] C --> E["Write to Buffer"] D --> E E --> F["Increment Offset"] F --> G["Final Buffer"] ``` ### String Encoding Example ``` "Hello" encoded: | 05 00 00 00 | 48 65 6c 6c 6f | | length=5 | "Hello" UTF-8 | ``` ### Array Encoding Example ``` [1, 2, 3] as u256 encoded: | 03 00 00 00 | 01 00...00 | 02 00...00 | 03 00...00 | | length=3 | 1 (32 bytes)| 2 (32 bytes)| 3 (32 bytes)| ``` ## Common Patterns ### Return Values ```typescript // Simple return public getValue(_calldata: Calldata): BytesWriter { const writer = new BytesWriter(32); writer.writeU256(this._value.value); return writer; } // Multiple return values public getState(_calldata: Calldata): BytesWriter { const writer = new BytesWriter(96); writer.writeU256(this._value1.value); writer.writeU256(this._value2.value); writer.writeAddress(this._owner.value); return writer; } // No return public setState(calldata: Calldata): BytesWriter { // ... do work ... return new BytesWriter(0); // Empty response } ``` ### Event Encoding ```typescript export class SwapEvent extends NetEvent { constructor( public readonly user: Address, public readonly tokenIn: Address, public readonly tokenOut: Address, public readonly amountIn: u256, public readonly amountOut: u256 ) { super('Swap'); } protected override encodeData(writer: BytesWriter): void { writer.writeAddress(this.user); writer.writeAddress(this.tokenIn); writer.writeAddress(this.tokenOut); writer.writeU256(this.amountIn); writer.writeU256(this.amountOut); } } ``` ### Cross-Contract Call Data ```typescript // Define method selectors (sha256 first 4 bytes of method signature) const TRANSFER_SELECTOR: u32 = 0xa9059cbb; // transfer(address,uint256) // Encode call to another contract private encodeTransfer(to: Address, amount: u256): Uint8Array { const writer = new BytesWriter(68); writer.writeSelector(TRANSFER_SELECTOR); writer.writeAddress(to); writer.writeU256(amount); return writer.getBuffer(); } // Make the call const calldata = this.encodeTransfer(recipient, tokenAmount); const result = Blockchain.call(tokenContract, calldata, true); // Decode result if (result.success) { const reader = new BytesReader(result.data); const success: bool = reader.readBoolean(); } ``` ### Cross-Contract Call Sequence ```mermaid sequenceDiagram participant C1 as Calling Contract participant W as BytesWriter participant BC as Blockchain participant C2 as Target Contract participant R as BytesReader C1->>W: new BytesWriter(68) C1->>W: writeSelector(0xa9059cbb) Note over W: [4 bytes] Selector C1->>W: writeAddress(recipient) Note over W: [4 + 32 bytes] Selector + Address C1->>W: writeU256(amount) Note over W: [4 + 32 + 32 bytes] Complete calldata W-->>C1: getBuffer() C1->>BC: call(tokenContract, calldata, true) BC->>C2: Execute with calldata C2->>R: new BytesReader(calldata) R->>C2: readSelector() 0xa9059cbb R->>C2: readAddress() recipient R->>C2: readU256() amount C2->>C2: Execute transfer logic C2->>W: new BytesWriter(1) C2->>W: writeBoolean(true) W-->>C2: getBuffer() C2-->>BC: Return encoded result BC-->>C1: result.data C1->>R: new BytesReader(result.data) R-->>C1: readBoolean() true Note over C1,C2: Total calldata: 68 bytes<br/>Selector(4) + Address(32) + u256(32) ``` ### Struct-like Encoding ```typescript // Define a struct-like type class Order { maker: Address; taker: Address; makerAmount: u256; takerAmount: u256; expiry: u64; } // Encode a "struct" private encodeOrder( maker: Address, taker: Address, makerAmount: u256, takerAmount: u256, expiry: u64 ): Uint8Array { const writer = new BytesWriter(104); writer.writeAddress(maker); writer.writeAddress(taker); writer.writeU256(makerAmount); writer.writeU256(takerAmount); writer.writeU64(expiry); return writer.getBuffer(); } // Decode a "struct" private decodeOrder(data: Uint8Array): Order { const reader = new BytesReader(data); const order = new Order(); order.maker = reader.readAddress(); order.taker = reader.readAddress(); order.makerAmount = reader.readU256(); order.takerAmount = reader.readU256(); order.expiry = reader.readU64(); return order; } ``` ### Struct Encoding Memory Layout ```mermaid graph LR subgraph Order["Order Struct Encoding (104 bytes)"] direction LR A["Offset 0-31<br/>maker Address<br/>32 bytes"] B["Offset 32-63<br/>taker Address<br/>32 bytes"] C["Offset 64-95<br/>makerAmount u256<br/>32 bytes"] D["Offset 96-127<br/>takerAmount u256<br/>32 bytes"] E["Offset 128-135<br/>expiry u64<br/>8 bytes"] end A --> B --> C --> D --> E ``` ## Size Limits ### Array Limits Maximum array length: **65,535 elements** ```typescript // Writing large arrays if (items.length > 65535) { throw new Revert('Array too large'); } writer.writeU256Array(items); ``` ### Event Size Limit Maximum event data: **352 bytes** ```typescript protected override encodeData(writer: BytesWriter): void { // Calculate total size // 32 + 32 + 32 + 32 + 8 = 136 bytes - OK writer.writeAddress(this.addr1); // 32 writer.writeAddress(this.addr2); // 32 writer.writeU256(this.amount1); // 32 writer.writeU256(this.amount2); // 32 writer.writeU64(this.timestamp); // 8 } ``` ## Solidity vs OP_NET Comparison ### Encoding/Decoding Comparison Table | Feature | Solidity | OP_NET | |---------|----------|-------| | **Encode function** | `abi.encode(...)` | `BytesWriter` methods | | **Decode function** | `abi.decode(data, (types))` | `BytesReader` methods | | **Packed encoding** | `abi.encodePacked(...)` | Default behavior (no padding) | | **Encode with selector** | `abi.encodeWithSelector(sel, ...)` | `writer.writeSelector(sel); writer.write...()` | | **Encode with signature** | `abi.encodeWithSignature(sig, ...)` | Manual selector + params | | **Byte order** | Big-endian | Big-endian (default) | | **Padding** | 32-byte aligned | No padding (native sizes) | | **Return encoding** | Automatic | Manual via BytesWriter | ### Type Encoding Comparison | Solidity Encoding | OP_NET Encoding | |-------------------|----------------| | `abi.encode(uint256)` | `writer.writeU256(value)` | | `abi.encode(uint128)` | `writer.writeU128(value)` | | `abi.encode(uint64)` | `writer.writeU64(value)` | | `abi.encode(uint32)` | `writer.writeU32(value)` | | `abi.encode(uint16)` | `writer.writeU16(value)` | | `abi.encode(uint8)` | `writer.writeU8(value)` | | `abi.encode(bool)` | `writer.writeBoolean(value)` | | `abi.encode(address)` | `writer.writeAddress(addr)` | | `abi.encode(bytes32)` | `writer.writeBytes(data)` | | `abi.encode(string)` | `writer.writeString(str)` | | `abi.encode(bytes)` | `writer.writeBytes(data)` | | `abi.encode(address[])` | `writer.writeAddressArray(addrs)` | | `abi.encode(uint256[])` | `writer.writeU256Array(values)` | ### Side-by-Side Code Examples #### Basic Encoding ```solidity // Solidity bytes memory data = abi.encode(recipient, amount); bytes memory packed = abi.encodePacked(recipient, amount); ``` ```typescript // OP_NET const writer = new BytesWriter(64); // 32 + 32 bytes writer.writeAddress(recipient); writer.writeU256(amount); const data: Uint8Array = writer.getBuffer(); // OP_NET encoding is similar to encodePacked (no padding) ``` #### Basic Decoding ```solidity // Solidity (address to, uint256 amount) = abi.decode(data, (address, uint256)); ``` ```typescript // OP_NET const reader = new BytesReader(data); const to: Address = reader.readAddress(); const amount: u256 = reader.readU256(); ``` #### Encoding Multiple Values ```solidity // Solidity function encodeTransferData( address from, address to, uint256 amount, string memory memo ) public pure returns (bytes memory) { return abi.encode(from, to, amount, memo); } ``` ```typescript // OP_NET function encodeTransferData( from: Address, to: Address, amount: u256, memo: string ): Uint8Array { // Calculate size: 32 + 32 + 32 + (4 + memo.length) const writer = new BytesWriter(100 + memo.length); writer.writeAddress(from); writer.writeAddress(to); writer.writeU256(amount); writer.writeString(memo); return writer.getBuffer(); } ``` #### Encoding with Function Selector ```solidity // Solidity bytes4 selector = bytes4(keccak256("transfer(address,uint256)")); bytes memory callData = abi.encodeWithSelector(selector, to, amount); // Or bytes memory callData = abi.encodeWithSignature("transfer(address,uint256)", to, amount); ``` ```typescript // OP_NET const TRANSFER_SELECTOR: Selector = Selector.from("transfer(address,uint256)"); const writer = new BytesWriter(68); // 4 + 32 + 32 writer.writeSelector(TRANSFER_SELECTOR); writer.writeAddress(to); writer.writeU256(amount); const callData: Uint8Array = writer.getBuffer(); ``` #### Return Value Encoding ```solidity // Solidity - Automatic return encoding function getInfo() public view returns (address, uint256, bool) { return (owner, balance, isActive); } // ABI automatically encodes the return tuple ``` ```typescript // OP_NET - Manual return encoding public getInfo(_calldata: Calldata): BytesWriter { const writer = new BytesWriter(65); // 32 + 32 + 1 writer.writeAddress(this._owner.value); writer.writeU256(this._balance.value); writer.writeBoolean(this._isActive.value); return writer; } ``` #### Decoding Return Values ```solidity // Solidity (bool success, bytes memory returnData) = target.call(callData); if (success) { (address addr, uint256 amount) = abi.decode(returnData, (address, uint256)); } ``` ```typescript // OP_NET const result = Blockchain.call(target, callData, true); if (result.success) { const reader = new BytesReader(result.data); const addr: Address = reader.readAddress(); const amount: u256 = reader.readU256(); } ``` #### Encoding Structs ```solidity // Solidity struct Order { address maker; address taker; uint256 makerAmount; uint256 takerAmount; uint64 expiry; } function encodeOrder(Order memory order) public pure returns (bytes memory) { return abi.encode(order.maker, order.taker, order.makerAmount, order.takerAmount, order.expiry); } ``` ```typescript // OP_NET class Order { maker: Address; taker: Address; makerAmount: u256; takerAmount: u256; expiry: u64; } function encodeOrder(order: Order): Uint8Array { const writer = new BytesWriter(136); // 32+32+32+32+8 writer.writeAddress(order.maker); writer.writeAddress(order.taker); writer.writeU256(order.makerAmount); writer.writeU256(order.takerAmount); writer.writeU64(order.expiry); return writer.getBuffer(); } ``` #### Event Data Encoding ```solidity // Solidity - Events are indexed/non-indexed event Transfer(address indexed from, address indexed to, uint256 amount); function _transfer(address from, address to, uint256 amount) internal { // ... transfer logic emit Transfer(from, to, amount); // Automatic encoding } ``` ```typescript // OP_NET - Custom event class with manual encoding export class TransferEvent extends NetEvent { constructor( public readonly from: Address, public readonly to: Address, public readonly amount: u256 ) { super('Transfer'); } protected override encodeData(writer: BytesWriter): void { writer.writeAddress(this.from); writer.writeAddress(this.to); writer.writeU256(this.amount); } } // Usage this.emitEvent(new TransferEvent(from, to, amount)); ``` #### Low-Level Bytes Manipulation ```solidity // Solidity function extractSelector(bytes calldata data) public pure returns (bytes4) { return bytes4(data[:4]); } function extractAddress(bytes calldata data, uint offset) public pure returns (address) { return abi.decode(data[offset:offset+32], (address)); } ``` ```typescript // OP_NET function extractSelector(data: Uint8Array): Selector { const reader = new BytesReader(data); return reader.readSelector(); } function extractAddress(data: Uint8Array, offset: u32): Address { const reader = new BytesReader(data); // Skip to offset by reading and discarding bytes for (let i: u32 = 0; i < offset; i++) { reader.readU8(); } return reader.readAddress(); } ``` ### Size Comparison (Encoding Overhead) | Data | Solidity abi.encode | Solidity encodePacked | OP_NET | |------|---------------------|----------------------|-------| | `bool` | 32 bytes | 1 byte | 1 byte | | `uint8` | 32 bytes | 1 byte | 1 byte | | `uint16` | 32 bytes | 2 bytes | 2 bytes | | `uint32` | 32 bytes | 4 bytes | 4 bytes | | `uint64` | 32 bytes | 8 bytes | 8 bytes | | `uint128` | 32 bytes | 16 bytes | 16 bytes | | `uint256` | 32 bytes | 32 bytes | 32 bytes | | `address` | 32 bytes | 20 bytes | 32 bytes | | `(addr, u256, bool)` | 96 bytes | 53 bytes | 65 bytes | ### Key Differences Summary | Aspect | Solidity | OP_NET | |--------|----------|-------| | **Encoding approach** | `abi.encode()` function | BytesWriter object methods | | **Decoding approach** | `abi.decode()` with type tuple | BytesReader sequential reads | | **Packed encoding** | `abi.encodePacked()` (separate) | Default (always packed) | | **Selector encoding** | `abi.encodeWithSelector()` | `writeSelector()` + params | | **Dynamic sizing** | Automatic | Pre-calculate or auto-grow | | **Return values** | Automatic encoding | Manual BytesWriter construction | | **Error on overflow** | Reverts | Reverts | | **Endianness** | Big-endian | Big-endian (default) | ### Migration Patterns #### From Solidity `abi.encode` ```solidity // Solidity bytes memory encoded = abi.encode(addr, amount, flag); ``` ```typescript // OP_NET equivalent const writer = new BytesWriter(65); // 32 + 32 + 1 writer.writeAddress(addr); writer.writeU256(amount); writer.writeBoolean(flag); const encoded = writer.getBuffer(); ``` #### From Solidity `abi.decode` ```solidity // Solidity (address addr, uint256 amount, bool flag) = abi.decode(data, (address, uint256, bool)); ``` ```typescript // OP_NET equivalent const reader = new BytesReader(data); const addr = reader.readAddress(); const amount = reader.readU256(); const flag = reader.readBoolean(); ``` #### From Solidity `abi.encodeWithSelector` ```solidity // Solidity bytes memory data = abi.encodeWithSelector( IERC20.transfer.selector, recipient, amount ); ``` ```typescript // OP_NET equivalent const TRANSFER_SELECTOR: Selector = Selector.from("transfer(address,uint256)"); const writer = new BytesWriter(68); writer.writeSelector(TRANSFER_SELECTOR); writer.writeAddress(recipient); writer.writeU256(amount); const data = writer.getBuffer(); ``` ## Best Practices ### 1. Pre-calculate Capacity ```typescript // Calculate required size const size = 32 + 32 + 4; // address + u256 + u32 const writer = new BytesWriter(size); // Better than growing the buffer ``` ### 2. Consistent Order ```typescript // Always read in the same order as written // Document the order clearly /** * Encoded format: * - to: Address (32 bytes) * - amount: u256 (32 bytes) * - data: bytes (4 + n bytes) */ ``` ### 3. Validate Before Reading ```typescript // Check if enough data remains if (reader.remaining < 32) { throw new Revert('Insufficient data'); } const value = reader.readU256(); ``` ### 4. Handle Variable-Length Data Last ```typescript // Fixed-size fields first writer.writeAddress(addr); writer.writeU256(amount); writer.writeU64(timestamp); // Variable-size fields last writer.writeString(message); writer.writeBytes(data); ``` --- **Navigation:** - Previous: [Calldata](./calldata.md) - Next: [Stored Primitives](../storage/stored-primitives.md)