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.

847 lines (643 loc) 23.4 kB
# Blockchain API Reference The `Blockchain` singleton provides access to the runtime environment, storage, and blockchain operations. ## Import ```typescript import { Blockchain } from '@btc-vision/btc-runtime/runtime'; ``` ## Properties ### Block Context | Property | Type | Description | |----------|------|-------------| | `Blockchain.block` | `Block` | Current block information | | `Blockchain.block.number` | `u64` | Block height | | `Blockchain.block.numberU256` | `u256` | Block height as u256 | | `Blockchain.block.hash` | `Uint8Array` | 32-byte block hash | | `Blockchain.block.medianTimestamp` | `u64` | Median timestamp (seconds) | ```typescript const blockNum = Blockchain.block.number; const timestamp = Blockchain.block.medianTimestamp; ``` **Solidity Comparison:** | Solidity | OP_NET | |----------|-------| | `block.number` | `Blockchain.block.number` | | `block.timestamp` | `Blockchain.block.medianTimestamp` | ### Transaction Context | Property | Type | Description | |----------|------|-------------| | `Blockchain.tx` | `Transaction` | Current transaction info | | `Blockchain.tx.sender` | `Address` | Immediate caller | | `Blockchain.tx.origin` | `ExtendedAddress` | Original signer | | `Blockchain.tx.txId` | `Uint8Array` | Transaction ID | | `Blockchain.tx.hash` | `Uint8Array` | Transaction hash | | `Blockchain.tx.inputs` | `TransactionInput[]` | Transaction inputs (UTXOs) | | `Blockchain.tx.outputs` | `TransactionOutput[]` | Transaction outputs (UTXOs) | | `Blockchain.tx.consensus` | `ConsensusRules` | Consensus rules | ```typescript const caller = Blockchain.tx.sender; // Immediate caller const signer = Blockchain.tx.origin; // Original signer const unsafeAllowed = Blockchain.tx.consensus.unsafeSignaturesAllowed(); ``` **Solidity Comparison:** | Solidity | OP_NET | |----------|-------| | `msg.sender` | `Blockchain.tx.sender` | | `tx.origin` | `Blockchain.tx.origin` | ### Contract Context | Property | Type | Description | |----------|------|-------------| | `Blockchain.contract` | `OP_NET` | Current contract instance | | `Blockchain.contractAddress` | `Address` | This contract's address | | `Blockchain.contractDeployer` | `Address` | Deployer's address | ```typescript const self = Blockchain.contractAddress; const deployer = Blockchain.contractDeployer; ``` **Solidity Comparison:** | Solidity | OP_NET | |----------|-------| | `address(this)` | `Blockchain.contractAddress` | ### Network Context | Property | Type | Description | |----------|------|-------------| | `Blockchain.network` | `Networks` | Network identifier | | `Blockchain.chainId` | `Uint8Array` | 32-byte chain ID | | `Blockchain.protocolId` | `Uint8Array` | 32-byte protocol ID | | `Blockchain.DEAD_ADDRESS` | `ExtendedAddress` | Burn address | ```typescript if (Blockchain.network === Networks.Mainnet) { // Mainnet-specific logic } ``` **Solidity Comparison:** | Solidity | OP_NET | |----------|-------| | `block.chainid` | `Blockchain.chainId` | ## Storage Architecture ### Storage Pointers | Property | Type | Description | |----------|------|-------------| | `Blockchain.nextPointer` | `u16` | Get next storage pointer | ```typescript private myPointer: u16 = Blockchain.nextPointer; ``` The storage system uses a pointer-based architecture where each storage slot is identified by a unique hash: ```mermaid graph LR subgraph "Pointer Allocation" A[Contract] -->|Allocate| B[Blockchain.nextPointer] B -->|Returns u16<br/>0-65535| C[Pointer ID] end subgraph "Key Generation" C -->|Pointer u16| F[encodePointer] D[SubPointer<br/>30 bytes] -->|Address/Key Data| F F -->|SHA-256 Hash| G[32-byte Storage Key] end subgraph "Storage Operations" G -->|Read| H[getStorageAt<br/>Returns 32 bytes] G -->|Write| I[setStorageAt<br/>Stores value] G -->|Check| J[hasStorageAt<br/>Returns boolean] end H --> K[Persistent Storage] I --> K J --> K ``` ## Storage Methods ### encodePointer Generates a storage key hash from a pointer and subPointer. ```typescript encodePointer(pointer: u16, subPointer: Uint8Array): Uint8Array ``` | Parameter | Type | Description | |-----------|------|-------------| | `pointer` | `u16` | Storage slot identifier (0-65535) | | `subPointer` | `Uint8Array` | Sub-index bytes (typically 30 bytes) | | **Returns** | `Uint8Array` | 32-byte storage key hash | ```typescript import { encodePointer } from '@btc-vision/btc-runtime/runtime'; const pointer: u16 = Blockchain.nextPointer; const subPointer = address.toBytes(); // or u256.toUint8Array(true) // Generate storage key const pointerHash = encodePointer(pointer, subPointer); ``` ### getStorageAt Reads from persistent storage. ```typescript getStorageAt(pointerHash: Uint8Array): Uint8Array ``` | Parameter | Type | Description | |-----------|------|-------------| | `pointerHash` | `Uint8Array` | 32-byte storage key (from encodePointer) | | **Returns** | `Uint8Array` | 32-byte value (zeros if unset) | ```typescript import { encodePointer } from '@btc-vision/btc-runtime/runtime'; // Create storage key const pointerHash = encodePointer(pointer, subPointer); // Read from storage const stored = Blockchain.getStorageAt(pointerHash); const value = u256.fromUint8ArrayBE(stored); ``` ### setStorageAt Writes to persistent storage. ```typescript setStorageAt(pointerHash: Uint8Array, value: Uint8Array): void ``` | Parameter | Type | Description | |-----------|------|-------------| | `pointerHash` | `Uint8Array` | 32-byte storage key (from encodePointer) | | `value` | `Uint8Array` | Value to store (auto-padded to 32 bytes) | ```typescript import { encodePointer } from '@btc-vision/btc-runtime/runtime'; // Create storage key const pointerHash = encodePointer(pointer, subPointer); // Write to storage Blockchain.setStorageAt(pointerHash, value.toUint8Array(true)); ``` ### hasStorageAt Checks if storage slot has non-zero value. ```typescript hasStorageAt(pointerHash: Uint8Array): bool ``` ```typescript const pointerHash = encodePointer(pointer, subPointer); if (Blockchain.hasStorageAt(pointerHash)) { // Slot has a value } ``` ### Storage Flow The following sequence diagram illustrates the complete flow of storage operations: ```mermaid sequenceDiagram participant C as Contract participant B as Blockchain participant S as Storage Layer Note over C,S: Storage Pointer Allocation C->>B: Blockchain.nextPointer B->>C: Returns u16 (e.g., 5) Note over C,S: Write Operation C->>C: address.toBytes() -> subPointer C->>B: encodePointer(5, subPointer) B->>B: SHA-256(pointer + subPointer) B->>C: Returns 32-byte hash C->>B: setStorageAt(hash, value) B->>S: Persist to storage Note over C,S: Read Operation C->>B: encodePointer(5, subPointer) B->>C: Returns 32-byte hash C->>B: getStorageAt(hash) S->>B: Retrieve value B->>C: Returns 32-byte value ``` ### Storage Pattern Example Complete example showing the correct pattern: ```typescript import { u256 } from '@btc-vision/as-bignum/assembly'; import { Blockchain, encodePointer, Address } from '@btc-vision/btc-runtime/runtime'; // Allocate pointer private balancesPointer: u16 = Blockchain.nextPointer; // Write balance public setBalance(address: Address, amount: u256): void { const pointerHash = encodePointer(this.balancesPointer, address.toBytes()); Blockchain.setStorageAt(pointerHash, amount.toUint8Array(true)); } // Read balance public getBalance(address: Address): u256 { const pointerHash = encodePointer(this.balancesPointer, address.toBytes()); const stored = Blockchain.getStorageAt(pointerHash); return u256.fromUint8ArrayBE(stored); } ``` **Solidity Comparison:** | Solidity | OP_NET | |----------|-------| | `mapping(address => uint256) balances` | `AddressMemoryMap` with pointer | | `balances[addr] = value` | `Blockchain.setStorageAt(pointerHash, value)` | | `balances[addr]` | `Blockchain.getStorageAt(pointerHash)` | ### Transient Storage (Experimental) > **Warning:** Transient storage is NOT enabled in production. Only available in testing. ```typescript getTransientStorageAt(pointerHash: Uint8Array): Uint8Array setTransientStorageAt(pointerHash: Uint8Array, value: Uint8Array): void hasTransientStorageAt(pointerHash: Uint8Array): bool ``` ## Cross-Contract Calls ### call Calls another contract. ```typescript call( destinationContract: Address, calldata: BytesWriter, stopExecutionOnFailure: boolean = true ): CallResult ``` | Parameter | Type | Description | |-----------|------|-------------| | `destinationContract` | `Address` | Target contract | | `calldata` | `BytesWriter` | Encoded call data | | `stopExecutionOnFailure` | `boolean` | Revert on failure (default: true) | | **Returns** | `CallResult` | Success flag and response data | The following diagram shows the two call patterns - standard calls that revert on failure, and try-catch style calls that handle failures gracefully: ```mermaid flowchart LR subgraph StdPattern["stopOnFailure = true"] A1["Call"] --> B1{"Success?"} B1 -->|"Yes"| C1["Continue"] B1 -->|"No"| D1["REVERT TX"] end subgraph TryPattern["stopOnFailure = false"] A2["Call"] --> B2{"Success?"} B2 -->|"Yes"| C2["Continue"] B2 -->|"No"| D2["Handle error"] end ``` ```typescript // Standard call - reverts on failure const result = Blockchain.call(tokenAddress, calldata); const balance = result.data.readU256(); // Try-catch pattern - handles failure gracefully const result = Blockchain.call(tokenAddress, calldata, false); if (result.success) { // Process response } else { // Handle failure without reverting } ``` The complete call flow with both success and failure scenarios: ```mermaid sequenceDiagram participant Caller as Calling Contract participant BC as Blockchain participant Target as Target Contract Note over Caller,Target: Standard Call (stopExecutionOnFailure=true) Caller->>Caller: Encode calldata (BytesWriter) Caller->>BC: call(targetAddress, calldata, true) BC->>Target: Execute method alt Success Case Target->>Target: Process request Target->>BC: Return BytesWriter response BC->>Caller: CallResult{success: true, data} Caller->>Caller: Process response.data else Failure Case Target->>BC: Throw Revert BC->>Caller: Transaction reverts Note over Caller: Execution stops end Note over Caller,Target: Try-Catch Pattern (stopExecutionOnFailure=false) Caller->>BC: call(targetAddress, calldata, false) BC->>Target: Execute method alt Success Case Target->>BC: Return response BC->>Caller: CallResult{success: true, data} Caller->>Caller: Handle success else Failure Case Target->>BC: Throw Revert BC->>Caller: CallResult{success: false, data} Caller->>Caller: Handle failure gracefully Note over Caller: Execution continues end ``` **Solidity Comparison:** | Solidity | OP_NET | |----------|-------| | `target.call(data)` | `Blockchain.call(target, calldata, false)` | | `target.functionCall(args)` | `Blockchain.call(target, calldata, true)` | | `try target.call() { } catch { }` | `Blockchain.call(target, calldata, false)` + check `result.success` | ### CallResult ```typescript class CallResult { readonly success: boolean; readonly data: BytesReader; } ``` ### deployContractFromExisting Deploys a new contract from a template. ```typescript deployContractFromExisting( existingAddress: Address, salt: u256, calldata: BytesWriter ): Address ``` | Parameter | Type | Description | |-----------|------|-------------| | `existingAddress` | `Address` | Template contract | | `salt` | `u256` | Unique salt for address | | `calldata` | `BytesWriter` | Constructor parameters | | **Returns** | `Address` | New contract address | ```typescript const salt = u256.fromBytes(Blockchain.sha256(uniqueData)); const newContract = Blockchain.deployContractFromExisting( templateAddress, salt, constructorData ); ``` ### updateContractFromExisting Updates the calling contract's bytecode from another deployed contract. The new bytecode takes effect at the next block. ```typescript updateContractFromExisting( sourceAddress: Address, calldata?: BytesWriter | null ): void ``` | Parameter | Type | Description | |-----------|------|-------------| | `sourceAddress` | `Address` | Contract containing new bytecode | | `calldata` | `BytesWriter \| null` | Optional calldata passed to VM (default: empty) | ```typescript // Basic update (not recommended without access control) Blockchain.updateContractFromExisting(newBytecodeAddress); // With calldata const updateData = new BytesWriter(32); updateData.writeU256(migrationVersion); Blockchain.updateContractFromExisting(newBytecodeAddress, updateData); ``` > **Warning:** This is a privileged operation. Always implement access control (e.g., `onlyDeployer`) and consider using the `Updatable` base class or `UpdatablePlugin` for timelock protection. See [Contract Updates](../advanced/updatable) for details. ## Cryptographic Operations ### sha256 Computes SHA-256 hash. ```typescript sha256(buffer: Uint8Array): Uint8Array ``` ```typescript const hash = Blockchain.sha256(data); // 32 bytes ``` ### hash256 Computes double SHA-256 (Bitcoin standard). ```typescript hash256(buffer: Uint8Array): Uint8Array ``` ```typescript const txHash = Blockchain.hash256(txData); // 32 bytes ``` ## Signature Verification ### verifySignature Verifies signature based on current consensus rules. ```typescript import { SignaturesMethods } from '@btc-vision/btc-runtime/runtime'; verifySignature( address: ExtendedAddress, signature: Uint8Array, hash: Uint8Array, signatureType: SignaturesMethods = SignaturesMethods.Schnorr ): boolean ``` | Parameter | Type | Description | |-----------|------|-------------| | `address` | `ExtendedAddress` | Signer's address | | `signature` | `Uint8Array` | Signature bytes (64 for Schnorr, 2420+ for ML-DSA) | | `hash` | `Uint8Array` | 32-byte message hash | | `signatureType` | `SignaturesMethods` | Signature type: `Schnorr` (default), `MLDSA`, or `ECDSA` | | **Returns** | `boolean` | True if valid | The signature verification selects the appropriate algorithm based on the `signatureType` parameter and consensus rules: ```mermaid flowchart LR subgraph "verifySignature Flow" Start([Verify Signature]) --> GetInput[Receive address,<br/>signature, hash] GetInput --> CheckType{signatureType?} CheckType -->|Schnorr| CheckConsensus{unsafeSignatures<br/>Allowed?} CheckConsensus -->|Yes| Schnorr[Schnorr Path] CheckConsensus -->|No| MLDSA CheckType -->|MLDSA| MLDSA[ML-DSA Path] CheckType -->|ECDSA| ECDSAErr[Error: Use dedicated<br/>ECDSA methods] end subgraph "Schnorr Verification" Schnorr --> SchnorrVerify[Verify Schnorr] SchnorrVerify --> SchnorrResult{Valid?} end subgraph "ML-DSA Verification" MLDSA --> MLDSAVerify[Verify ML-DSA-44<br/>Level2] MLDSAVerify --> MLDSAResult{Valid?} end MLDSAResult -->|Yes| Success([Return true]) MLDSAResult -->|No| Fail([Return false]) SchnorrResult -->|Yes| Success SchnorrResult -->|No| Fail ``` ```typescript const isValid = Blockchain.verifySignature( signerAddress, signatureBytes, messageHash ); if (!isValid) { throw new Revert('Invalid signature'); } ``` The complete EIP-712 style signature verification flow: ```mermaid sequenceDiagram participant C as Contract participant BC as Blockchain participant Cons as Consensus Layer participant Crypto as Crypto Module Note over C,Crypto: EIP-712 Style Signature Verification C->>C: Build domain separator C->>C: Hash struct data C->>C: Combine: 0x1901 + domain + structHash C->>C: messageHash = SHA-256(combined) C->>BC: verifySignature(address, signature, messageHash, signatureType) BC->>Cons: Check consensus flags Cons->>BC: unsafeSignaturesAllowed() alt signatureType == Schnorr AND unsafeSignaturesAllowed() BC->>Crypto: verifySchnorr(tweakedPublicKey, sig, hash) Crypto->>BC: Valid/Invalid else signatureType == MLDSA OR !unsafeSignaturesAllowed() BC->>BC: Use ML-DSA Level2 (ML-DSA-44) BC->>Crypto: verifyMLDSA(Level2, mldsaPublicKey, sig, hash) Crypto->>BC: Valid/Invalid end BC->>C: Return boolean ``` ### verifyECDSASignature (Deprecated) Verifies an ECDSA signature using the Ethereum ecrecover model (secp256k1). ```typescript verifyECDSASignature( publicKey: Uint8Array, signature: Uint8Array, hash: Uint8Array ): boolean ``` | Parameter | Type | Description | |-----------|------|-------------| | `publicKey` | `Uint8Array` | secp256k1 public key (33, 64, or 65 bytes) | | `signature` | `Uint8Array` | 65-byte signature: r(32) \|\| s(32) \|\| v(1) | | `hash` | `Uint8Array` | 32-byte message hash (typically keccak256) | | **Returns** | `boolean` | True if ecrecover produces matching key | > **Warning:** Only available when `UNSAFE_QUANTUM_SIGNATURES_ALLOWED` consensus flag is set. Throws `Revert` otherwise. ### verifyBitcoinECDSASignature (Deprecated) Verifies an ECDSA signature using the Bitcoin direct verification model (secp256k1). ```typescript verifyBitcoinECDSASignature( publicKey: Uint8Array, signature: Uint8Array, hash: Uint8Array ): boolean ``` | Parameter | Type | Description | |-----------|------|-------------| | `publicKey` | `Uint8Array` | secp256k1 public key (33, 64, or 65 bytes) | | `signature` | `Uint8Array` | 64-byte compact signature: r(32) \|\| s(32) | | `hash` | `Uint8Array` | 32-byte message hash (typically SHA-256 double hash) | | **Returns** | `boolean` | True if signature is valid | > **Warning:** Only available when `UNSAFE_QUANTUM_SIGNATURES_ALLOWED` consensus flag is set. Throws `Revert` otherwise. Enforces BIP-0062 low-S normalization. ### verifyMLDSASignature Verifies ML-DSA (quantum-resistant) signature. ```typescript verifyMLDSASignature( level: MLDSASecurityLevel, publicKey: Uint8Array, signature: Uint8Array, hash: Uint8Array ): boolean ``` | Parameter | Type | Description | |-----------|------|-------------| | `level` | `MLDSASecurityLevel` | Security level (Level2, Level3, Level5) | | `publicKey` | `Uint8Array` | ML-DSA public key | | `signature` | `Uint8Array` | ML-DSA signature | | `hash` | `Uint8Array` | 32-byte message hash | | **Returns** | `boolean` | True if valid | ```typescript import { MLDSASecurityLevel } from '@btc-vision/btc-runtime/runtime'; const isValid = Blockchain.verifyMLDSASignature( MLDSASecurityLevel.Level2, // ML-DSA-44 address.mldsaPublicKey, // 1312 bytes signature, // 2420 bytes messageHash // 32 bytes ); ``` ### verifySchnorrSignature (Deprecated) ```typescript verifySchnorrSignature( publicKey: ExtendedAddress, signature: Uint8Array, hash: Uint8Array ): boolean ``` > **Note:** Use `verifySignature()` instead for automatic consensus migration. ## Keccak-256 Hashing Ethereum-compatible Keccak-256 hash functions (original Keccak, not NIST SHA-3-256). ### keccak256 ```typescript import { keccak256 } from '@btc-vision/btc-runtime/runtime'; keccak256(data: Uint8Array): Uint8Array // 32-byte digest ``` ### keccak256Concat Hash two concatenated byte arrays. Common for `abi.encodePacked` patterns. ```typescript import { keccak256Concat } from '@btc-vision/btc-runtime/runtime'; keccak256Concat(a: Uint8Array, b: Uint8Array): Uint8Array // 32-byte digest ``` ### functionSelector Compute Ethereum-style 4-byte function selector. ```typescript import { functionSelector } from '@btc-vision/btc-runtime/runtime'; functionSelector(signature: string): Uint8Array // 4 bytes // e.g. functionSelector('transfer(address,uint256)') => 0xa9059cbb ``` ### ethAddressFromPubKey Derive Ethereum address from 64-byte uncompressed public key. ```typescript import { ethAddressFromPubKey } from '@btc-vision/btc-runtime/runtime'; ethAddressFromPubKey(publicKey: Uint8Array): Uint8Array // 20-byte Ethereum address ``` ## Utility Methods ### validateBitcoinAddress Validates a Bitcoin address string. ```typescript validateBitcoinAddress(address: string): bool ``` ```typescript if (!Blockchain.validateBitcoinAddress(userAddress)) { throw new Revert('Invalid Bitcoin address'); } ``` ### isContract Checks if an address is a contract. ```typescript isContract(address: Address): boolean ``` ```typescript if (Blockchain.isContract(targetAddress)) { // Is a contract, not EOA } ``` **Solidity Comparison:** | Solidity | OP_NET | |----------|-------| | `address.code.length > 0` | `Blockchain.isContract(address)` | ### getAccountType Gets account type code. ```typescript getAccountType(address: Address): u32 ``` | Return Value | Meaning | |--------------|---------| | `0` | EOA or uninitialized | | `>0` | Contract (type code) | ### getBlockHash Retrieves historical block hash. ```typescript getBlockHash(blockNumber: u64): Uint8Array ``` ```typescript const hash = Blockchain.getBlockHash(Blockchain.block.number - 10); ``` > **Warning:** Only ~256 recent blocks available. Older blocks return zeros. **Solidity Comparison:** | Solidity | OP_NET | |----------|-------| | `blockhash(blockNumber)` | `Blockchain.getBlockHash(blockNumber)` | ## Event Methods ### emit Emits an event. ```typescript emit(event: NetEvent): void ``` ```typescript Blockchain.emit(new TransferEvent(from, to, amount)); ``` **Solidity Comparison:** | Solidity | OP_NET | |----------|-------| | `emit Transfer(from, to, amount)` | `Blockchain.emit(new TransferEvent(from, to, amount))` | ### log Logs debug message (testing only). ```typescript log(data: string): void ``` > **Warning:** Only available in unit testing framework. ```typescript Blockchain.log('Debug: operation started'); ``` ## Lifecycle Hooks These are called by the runtime: | Method | When Called | |--------|-------------| | `onDeployment(calldata)` | Contract deployment | | `onUpdate(calldata)` | Contract bytecode update (via `updateContractFromExisting`) | | `onExecutionStarted(selector, calldata)` | Before method execution | | `onExecutionCompleted(selector, calldata)` | After successful execution | ### onUpdate Called when the contract's bytecode is updated via `updateContractFromExisting`. Use this hook to perform storage migrations or initialization logic when upgrading. ```typescript public override onUpdate(calldata: Calldata): void { super.onUpdate(calldata); // Call plugins first // Perform migration logic const migrationVersion = calldata.readU64(); if (migrationVersion === 2) { // Migrate from v1 to v2 this.migrateToV2(); } } ``` > **Note:** The calldata is the same data passed to `Blockchain.updateContractFromExisting(sourceAddress, calldata)`. If no calldata was provided, an empty reader is passed. --- **Navigation:** - Previous: [Oracle Integration](../examples/oracle-integration.md) - Next: [OP20 API](./op20.md)