@btc-vision/btc-runtime
Version:
Bitcoin L1 Smart Contract Runtime for OP_NET. Build decentralized applications on Bitcoin using AssemblyScript and WebAssembly. Fully audited.
136 lines (118 loc) • 4.5 kB
text/typescript
import { BytesWriter } from '../buffer/BytesWriter';
import { Blockchain } from '../env';
import { MAX_EVENT_DATA_SIZE, NetEvent } from '../events/NetEvent';
import { IBTC } from '../interfaces/IBTC';
import { encodeSelector, Selector } from '../math/abi';
import { Calldata } from '../types';
import { Address } from '../types/Address';
import { Revert } from '../types/Revert';
import { ADDRESS_BYTE_LENGTH } from '../utils';
import { Plugin } from '../plugins/Plugin';
export class OP_NET implements IBTC {
/**
* Registered plugins that can handle method selectors.
* Plugins are checked in registration order when execute() is called.
*/
private readonly _plugins: Plugin[] = [];
public get address(): Address {
return Blockchain.contractAddress;
}
public get contractDeployer(): Address {
return Blockchain.contractDeployer;
}
public onDeployment(calldata: Calldata): void {
// Call onDeployment for all registered plugins
for (let i = 0; i < this._plugins.length; i++) {
this._plugins[i].onDeployment(calldata);
}
}
/**
* Called when the contract's bytecode is updated via updateContractFromExisting.
* Override this method to perform migration logic when the contract is updated.
*
* @param calldata - The calldata passed to updateContractFromExisting
*
* @example
* ```typescript
* public override onUpdate(calldata: Calldata): void {
* super.onUpdate(calldata); // Call plugins
* const version = calldata.readU64();
* // Perform migration based on version
* }
* ```
*/
public onUpdate(calldata: Calldata): void {
// Call onUpdate for all registered plugins
for (let i = 0; i < this._plugins.length; i++) {
this._plugins[i].onUpdate(calldata);
}
}
public onExecutionStarted(selector: Selector, calldata: Calldata): void {
// Call onExecutionStarted for all registered plugins
for (let i = 0; i < this._plugins.length; i++) {
this._plugins[i].onExecutionStarted(selector, calldata);
}
}
public onExecutionCompleted(selector: Selector, calldata: Calldata): void {
// Call onExecutionCompleted for all registered plugins
for (let i = 0; i < this._plugins.length; i++) {
this._plugins[i].onExecutionCompleted(selector, calldata);
}
}
public execute(method: Selector, calldata: Calldata): BytesWriter {
// Check built-in methods first
switch (method) {
case encodeSelector('deployer()'): {
const response = new BytesWriter(ADDRESS_BYTE_LENGTH);
response.writeAddress(this.contractDeployer);
return response;
}
}
// Try registered plugins
const startOffset = calldata.getOffset();
for (let i = 0; i < this._plugins.length; i++) {
const result = this._plugins[i].execute(method, calldata);
if (result !== null) {
return result;
}
calldata.setOffset(startOffset);
}
// No handler found
throw new Revert(`Method not found: ${method}`);
}
/**
* Registers a plugin to handle method selectors automatically.
* Plugins are checked in registration order when the contract's execute() falls through to super.
*
* @example
* ```typescript
* public constructor() {
* super();
* this.registerPlugin(new UpdatablePlugin(144));
* }
* ```
*
* @param plugin - The plugin to register
*/
protected registerPlugin(plugin: Plugin): void {
this._plugins.push(plugin);
}
protected emitEvent(event: NetEvent): void {
if (event.length > MAX_EVENT_DATA_SIZE) {
throw new Revert('Event data length exceeds maximum length.');
}
Blockchain.emit(event);
}
protected isSelf(address: Address): boolean {
return this.address === address;
}
protected _buildDomainSeparator(): Uint8Array {
// This method should be overridden in derived classes to provide the domain separator
throw new Error('Method not implemented.');
}
protected onlyDeployer(caller: Address): void {
if (this.contractDeployer !== caller) {
throw new Revert('Only deployer can call this method');
}
}
}