@btc-vision/btc-runtime
Version:
Bitcoin L1 Smart Contract Runtime for OP_NET. Build decentralized applications on Bitcoin using AssemblyScript and WebAssembly. Fully audited.
262 lines (214 loc) • 8.99 kB
text/typescript
import { u256 } from '@btc-vision/as-bignum/assembly';
import { BytesWriter } from '../buffer/BytesWriter';
import { Blockchain } from '../env';
import { Calldata } from '../types';
import { Address } from '../types/Address';
import { Revert } from '../types/Revert';
import { StoredU256 } from '../storage/StoredU256';
import { EMPTY_POINTER } from '../math/bytes';
import { OP20 } from './OP20';
import { Selector } from '../math/abi';
import { AddressMemoryMap } from '../memory/AddressMemoryMap';
import {
MaxStalenessUpdatedEvent,
PegAuthorityRenouncedEvent,
PegAuthorityTransferredEvent,
PegAuthorityTransferStartedEvent,
PegRateUpdatedEvent,
} from '../events/op20s/OP20SEvents';
// Selectors: sha256 first 4 bytes
export const PEG_RATE_SELECTOR: u32 = 0x4d1f6caf;
export const PEG_AUTHORITY_SELECTOR: u32 = 0xd767a583;
export const PEG_UPDATED_AT_SELECTOR: u32 = 0x1e99d1a1;
export const MAX_STALENESS_SELECTOR: u32 = 0x0b17a602;
export const IS_STALE_SELECTOR: u32 = 0x147c08ef;
const pegRatePointer: u16 = Blockchain.nextPointer;
const pegAuthorityPointer: u16 = Blockchain.nextPointer;
const pegUpdatedAtPointer: u16 = Blockchain.nextPointer;
const maxStalenessPointer: u16 = Blockchain.nextPointer;
const pendingAuthorityPointer: u16 = Blockchain.nextPointer;
export abstract class OP20S extends OP20 {
protected readonly _pegRate: StoredU256;
protected readonly _pegAuthorityMap: AddressMemoryMap;
protected readonly _pegUpdatedAt: StoredU256;
protected readonly _maxStaleness: StoredU256;
protected readonly _pendingAuthorityMap: AddressMemoryMap;
public constructor() {
super();
this._pegRate = new StoredU256(pegRatePointer, EMPTY_POINTER);
this._pegAuthorityMap = new AddressMemoryMap(pegAuthorityPointer);
this._pegUpdatedAt = new StoredU256(pegUpdatedAtPointer, EMPTY_POINTER);
this._maxStaleness = new StoredU256(maxStalenessPointer, EMPTY_POINTER);
this._pendingAuthorityMap = new AddressMemoryMap(pendingAuthorityPointer);
}
public pegRate(_: Calldata): BytesWriter {
const w = new BytesWriter(32);
w.writeU256(this._pegRate.value);
return w;
}
public pegAuthority(_: Calldata): BytesWriter {
const w = new BytesWriter(32);
w.writeAddress(this._getPegAuthority());
return w;
}
public pegUpdatedAt(_: Calldata): BytesWriter {
const w = new BytesWriter(8);
w.writeU64(this._pegUpdatedAt.value.toU64());
return w;
}
public maxStaleness(_: Calldata): BytesWriter {
const w = new BytesWriter(8);
w.writeU64(this._maxStaleness.value.toU64());
return w;
}
public isStale(_: Calldata): BytesWriter {
const w = new BytesWriter(1);
w.writeBoolean(this._isStale());
return w;
}
public updatePegRate(calldata: Calldata): BytesWriter {
this._onlyPegAuthority();
const newRate = calldata.readU256();
if (newRate.isZero()) {
throw new Revert('Invalid peg rate');
}
const oldRate = this._pegRate.value;
const blockNumber = Blockchain.block.number;
this._pegRate.value = newRate;
this._pegUpdatedAt.value = u256.fromU64(blockNumber);
this.emitEvent(new PegRateUpdatedEvent(oldRate, newRate, blockNumber));
return new BytesWriter(0);
}
public updateMaxStaleness(calldata: Calldata): BytesWriter {
this._onlyPegAuthority();
const newStaleness = calldata.readU64();
if (newStaleness == 0) {
throw new Revert('Invalid max staleness');
}
const oldStaleness = this._maxStaleness.value.toU64();
this._maxStaleness.value = u256.fromU64(newStaleness);
this.emitEvent(new MaxStalenessUpdatedEvent(oldStaleness, newStaleness));
return new BytesWriter(0);
}
public transferPegAuthority(calldata: Calldata): BytesWriter {
this._onlyPegAuthority();
const newAuthority = calldata.readAddress();
if (newAuthority.equals(Address.zero())) {
throw new Revert('Invalid new authority');
}
const currentAuthority = this._getPegAuthority();
this._setPendingAuthority(newAuthority);
this.emitEvent(new PegAuthorityTransferStartedEvent(currentAuthority, newAuthority));
return new BytesWriter(0);
}
public acceptPegAuthority(_: Calldata): BytesWriter {
const pending = this._getPendingAuthority();
if (pending.equals(Address.zero())) {
throw new Revert('No pending authority');
}
if (!Blockchain.tx.sender.equals(pending)) {
throw new Revert('Not pending authority');
}
const previousAuthority = this._getPegAuthority();
this._setPegAuthority(pending);
this._setPendingAuthority(Address.zero());
this.emitEvent(new PegAuthorityTransferredEvent(previousAuthority, pending));
return new BytesWriter(0);
}
public renouncePegAuthority(_: Calldata): BytesWriter {
this._onlyPegAuthority();
const previousAuthority = this._getPegAuthority();
this._setPegAuthority(Address.zero());
this._setPendingAuthority(Address.zero());
this.emitEvent(new PegAuthorityRenouncedEvent(previousAuthority));
return new BytesWriter(0);
}
protected initializePeg(pegAuthority: Address, initialPegRate: u256, maxStaleness: u64): void {
if (pegAuthority.equals(Address.zero())) {
throw new Revert('Invalid peg authority');
}
if (initialPegRate.isZero()) {
throw new Revert('Invalid initial peg rate');
}
if (maxStaleness == 0) {
throw new Revert('Invalid max staleness');
}
this._setPegAuthority(pegAuthority);
this._pegRate.value = initialPegRate;
this._pegUpdatedAt.value = u256.fromU64(Blockchain.block.number);
this._maxStaleness.value = u256.fromU64(maxStaleness);
this.emitEvent(new PegRateUpdatedEvent(u256.Zero, initialPegRate, Blockchain.block.number));
}
protected _isStale(): boolean {
const currentBlock = Blockchain.block.number;
const updatedAt = this._pegUpdatedAt.value.toU64();
const maxStale = this._maxStaleness.value.toU64();
return currentBlock > updatedAt + maxStale;
}
protected _requireFreshPeg(): void {
if (this._isStale()) {
throw new Revert('Peg rate stale');
}
}
protected _onlyPegAuthority(): void {
if (!Blockchain.tx.sender.equals(this._getPegAuthority())) {
throw new Revert('Not peg authority');
}
}
protected _getPegAuthority(): Address {
const stored = this._pegAuthorityMap.get(Address.zero());
if (stored.isZero()) return Address.zero();
return this._u256ToAddress(stored);
}
protected _setPegAuthority(addr: Address): void {
this._pegAuthorityMap.set(Address.zero(), this._addressToU256(addr));
}
protected _getPendingAuthority(): Address {
const stored = this._pendingAuthorityMap.get(Address.zero());
if (stored.isZero()) return Address.zero();
return this._u256ToAddress(stored);
}
protected _setPendingAuthority(addr: Address): void {
this._pendingAuthorityMap.set(Address.zero(), this._addressToU256(addr));
}
protected _addressToU256(addr: Address): u256 {
return u256.fromUint8ArrayBE(addr);
}
protected _u256ToAddress(val: u256): Address {
if (val.isZero()) return Address.zero();
const bytes = val.toUint8Array(true);
return Address.fromUint8Array(bytes);
}
protected override isSelectorExcluded(selector: Selector): boolean {
if (
selector == PEG_RATE_SELECTOR ||
selector == PEG_AUTHORITY_SELECTOR ||
selector == PEG_UPDATED_AT_SELECTOR ||
selector == MAX_STALENESS_SELECTOR ||
selector == IS_STALE_SELECTOR
) {
return true;
}
return super.isSelectorExcluded(selector);
}
}