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.

133 lines (110 loc) 4.25 kB
import { Blockchain } from '../env'; import { OP_NET } from './OP_NET'; import { StoredBoolean } from '../storage/StoredBoolean'; import { StoredU256 } from '../storage/StoredU256'; import { Selector } from '../math/abi'; import { Calldata } from '../types'; import { Revert } from '../types/Revert'; import { u256 } from '@btc-vision/as-bignum/assembly'; import { SafeMath } from '../types/SafeMath'; import { ON_OP20_RECEIVED_SELECTOR, ON_OP721_RECEIVED_SELECTOR, } from '../constants/Exports'; import { EMPTY_POINTER } from '../math/bytes'; const statusPointer: u16 = Blockchain.nextPointer; const depthPointer: u16 = Blockchain.nextPointer; /** * @enum ReentrancyLevel * @description * Defines the level of reentrancy protection. * * STANDARD: Strict single entry, no reentrancy allowed. * CALLBACK: Allows one level of reentrancy for callbacks (e.g., token transfers). */ export enum ReentrancyLevel { STANDARD = 0, CALLBACK = 1, } export class ReentrancyGuard extends OP_NET { protected readonly _locked: StoredBoolean; protected readonly _reentrancyDepth: StoredU256; // Override this in derived contracts to set protection level protected readonly reentrancyLevel: ReentrancyLevel = ReentrancyLevel.STANDARD; protected constructor() { super(); this._locked = new StoredBoolean(statusPointer, false); this._reentrancyDepth = new StoredU256(depthPointer, EMPTY_POINTER); } public override onExecutionCompleted(selector: Selector, calldata: Calldata): void { super.onExecutionCompleted(selector, calldata); if (this.isSelectorExcluded(selector)) { return; } this.nonReentrantAfter(); } public override onExecutionStarted(selector: Selector, calldata: Calldata): void { super.onExecutionStarted(selector, calldata); if (this.isSelectorExcluded(selector)) { return; } this.nonReentrantBefore(); } public nonReentrantBefore(): void { if (this.reentrancyLevel === ReentrancyLevel.STANDARD) { // Standard behavior - strict single entry if (this._locked.value) { this.reentrancyGuardReentrantCall(); } this._locked.value = true; } else if (this.reentrancyLevel === ReentrancyLevel.CALLBACK) { // Allow one level of reentrancy for callbacks const currentDepth = this._reentrancyDepth.value; // Maximum depth of 2 (original call + one callback reentry) if (currentDepth >= u256.One) { throw new Revert('ReentrancyGuard: Max depth exceeded'); } this._reentrancyDepth.value = SafeMath.add(currentDepth, u256.One); // Use locked flag for first entry if (currentDepth.isZero()) { this._locked.value = true; } } } public nonReentrantAfter(): void { if (this.reentrancyLevel === ReentrancyLevel.STANDARD) { // Standard behavior this._locked.value = false; } else if (this.reentrancyLevel === ReentrancyLevel.CALLBACK) { // Decrement depth const currentDepth = this._reentrancyDepth.value; if (currentDepth.isZero()) { throw new Revert('ReentrancyGuard: Depth underflow'); } const newDepth = SafeMath.sub(currentDepth, u256.One); this._reentrancyDepth.value = newDepth; // Clear locked flag when fully exited if (newDepth.isZero()) { this._locked.value = false; } } } public reentrancyGuardEntered(): boolean { return this._locked.value === true; } public getCurrentDepth(): u256 { return this._reentrancyDepth.value; } /** * @dev Unauthorized reentrant call. */ protected reentrancyGuardReentrantCall(): void { throw new Revert('ReentrancyGuard: LOCKED'); } protected isSelectorExcluded(selector: Selector): boolean { return ( selector === ON_OP20_RECEIVED_SELECTOR || selector === ON_OP721_RECEIVED_SELECTOR ); } }