// src/vm.ts
import "ses";

// src/gas.ts
var BASE_CALL_GAS = 21000n;
var BASE_DEPLOY_GAS = 32000n;
var GAS_STORAGE_READ = 800n;
var GAS_STORAGE_WRITE_NEW = 20000n;
var GAS_STORAGE_WRITE_UPDATE = 2900n;
var GAS_STORAGE_DELETE = 5000n;
var GAS_EVENT_BASE = 375n;
var GAS_EVENT_PER_BYTE = 8n;
var GAS_DEPLOY_PER_BYTE = 200n;
var GasLimitExceededError = class extends Error {
  constructor(used, limit) {
    super(`gas limit exceeded: used ${used}, limit ${limit}`);
    this.used = used;
    this.limit = limit;
    this.name = "GasLimitExceededError";
  }
  used;
  limit;
};
var GasMeter = class {
  constructor(limit) {
    this.limit = limit;
    if (limit <= 0n) {
      throw new Error(`gas limit must be positive, got ${limit}`);
    }
  }
  limit;
  used_ = 0n;
  /** Charge a fixed amount of gas. Throws if the limit is exceeded. */
  charge(amount) {
    if (amount < 0n) throw new Error("gas charge cannot be negative");
    this.used_ += amount;
    if (this.used_ > this.limit) {
      throw new GasLimitExceededError(this.used_, this.limit);
    }
  }
  /** Current gas used. */
  used() {
    return this.used_;
  }
  /** Gas remaining before the limit is hit. */
  remaining() {
    return this.limit - this.used_;
  }
};
var GAS_TRANSFER = 9000n;
var GAS_CROSS_CALL = 15000n;

// src/errors.ts
var ContractLoadError = class extends Error {
  constructor(message, options) {
    super(message, options);
    this.name = "ContractLoadError";
  }
};
var ContractInterfaceError = class extends Error {
  constructor(message) {
    super(message);
    this.name = "ContractInterfaceError";
  }
};
var ContractExecutionError = class extends Error {
  constructor(message, options) {
    super(message, options);
    this.name = "ContractExecutionError";
  }
};

// src/vm.ts
var lockdownCalled = false;
function initializeVm() {
  if (lockdownCalled) return;
  lockdown({
    errorTaming: "unsafe",
    consoleTaming: "unsafe"
  });
  lockdownCalled = true;
}
var Vm = class {
  constructor() {
    initializeVm();
  }
  /**
   * Deploy a contract: evaluate its source and register the exported
   * interface. If `initArgs` is undefined, `init` is NOT auto-invoked —
   * the caller should invoke it via a separate `call()` if they want
   * explicit initialization. If `initArgs` is an array (even empty), the
   * contract's `init` method (if present) is called with those args.
   *
   * This lets higher-level code choose between two deploy styles:
   *   - "bare deploy, init later" — pass `undefined`
   *   - "deploy with constructor" — pass `[constructorArgs]`
   */
  deploy(options, initArgs) {
    const gas = new GasMeter(options.gasLimit);
    const sourceLength = BigInt(options.source.length);
    gas.charge(BASE_DEPLOY_GAS + sourceLength * GAS_DEPLOY_PER_BYTE);
    const events = [];
    const outboundTransfers = [];
    const exported = this.evaluateContract(options, gas, events, outboundTransfers);
    let initReturnValue;
    if (initArgs !== void 0 && exported !== null && typeof exported === "object" && typeof exported.init === "function") {
      const initFn = exported.init;
      try {
        initReturnValue = initFn(...initArgs);
      } catch (err) {
        if (err instanceof GasLimitExceededError) throw err;
        throw new ContractExecutionError(
          `contract init failed: ${err instanceof Error ? err.message : String(err)}`,
          { cause: err }
        );
      }
    }
    return {
      events,
      gasUsed: gas.used(),
      initReturnValue,
      outboundTransfers
    };
  }
  /**
   * Call a method on an already-deployed contract. Re-evaluates the
   * source in a fresh Compartment, finds the exported method, invokes it
   * with the given arguments, and returns the result.
   */
  call(options, method, args) {
    const gas = new GasMeter(options.gasLimit);
    gas.charge(BASE_CALL_GAS);
    const events = [];
    const outboundTransfers = [];
    const exported = this.evaluateContract(options, gas, events, outboundTransfers);
    if (exported === null || typeof exported !== "object") {
      throw new ContractInterfaceError(
        "contract did not export an object interface"
      );
    }
    const methodFn = exported[method];
    if (typeof methodFn !== "function") {
      throw new ContractInterfaceError(`contract method not found: ${method}`);
    }
    let returnValue;
    try {
      returnValue = methodFn(...args);
    } catch (err) {
      if (err instanceof GasLimitExceededError) throw err;
      throw new ContractExecutionError(
        `contract method '${method}' failed: ${err instanceof Error ? err.message : String(err)}`,
        { cause: err }
      );
    }
    return {
      returnValue,
      events,
      gasUsed: gas.used(),
      outboundTransfers
    };
  }
  /**
   * Shared compartment setup: builds a metered storage wrapper, an event
   * sink, a safe `chain` / `msg` view, and the `assert` function, then
   * evaluates the contract source and returns whatever the last
   * expression evaluated to.
   */
  evaluateContract(options, gas, events, outboundTransfers) {
    const { source, context, storage } = options;
    const meteredStorage = {
      get(key) {
        gas.charge(GAS_STORAGE_READ);
        return storage.get(String(key));
      },
      set(key, value) {
        const k = String(key);
        const isNew = !storage.has(k);
        gas.charge(isNew ? GAS_STORAGE_WRITE_NEW : GAS_STORAGE_WRITE_UPDATE);
        storage.set(k, String(value));
      },
      delete(key) {
        gas.charge(GAS_STORAGE_DELETE);
        return storage.delete(String(key));
      },
      has(key) {
        gas.charge(GAS_STORAGE_READ);
        return storage.has(String(key));
      },
      // @dev keys(prefix) — eagerly materialises the filtered key list
      // so we can charge gas before the contract starts walking. Charging
      // per key prevents a contract from scanning a huge prefix for free
      // (DoS vector). Returns [] if the underlying adapter doesn't
      // implement keys (back-compat for embedders).
      keys(prefix) {
        gas.charge(GAS_STORAGE_READ);
        if (typeof storage.keys !== "function") return [];
        const out = [];
        for (const k of storage.keys(prefix === void 0 ? void 0 : String(prefix))) {
          gas.charge(GAS_STORAGE_READ);
          out.push(k);
        }
        return out;
      }
    };
    const emit = (name, data) => {
      const nameStr = String(name);
      let payloadSize = BigInt(nameStr.length);
      try {
        const serialized = JSON.stringify(data, bigintReplacer);
        if (serialized) payloadSize += BigInt(serialized.length);
      } catch {
      }
      gas.charge(GAS_EVENT_BASE + payloadSize * GAS_EVENT_PER_BYTE);
      events.push({ name: nameStr, data });
    };
    const assertFn = (condition, message) => {
      if (!condition) {
        throw new Error(message !== void 0 ? String(message) : "assertion failed");
      }
    };
    const chain = harden({
      blockNumber: context.blockNumber,
      blockTimestamp: context.blockTimestamp,
      chainId: context.chainId
    });
    const msg = harden({
      sender: context.sender,
      value: context.value,
      contractAddress: context.contractAddress
    });
    const crossCallFn = options.crossCall;
    const contractAddr = context.contractAddress;
    const E = (targetAddress) => {
      const target = String(targetAddress).toLowerCase();
      if (!/^0x[0-9a-f]{40}$/.test(target)) {
        throw new Error(`E(): invalid contract address '${targetAddress}'`);
      }
      if (!crossCallFn) {
        throw new Error("E(): cross-contract calls not available in this context");
      }
      return new Proxy({}, {
        get(_, method) {
          return (...args) => {
            gas.charge(GAS_CROSS_CALL);
            return crossCallFn(contractAddr, target, method, args);
          };
        }
      });
    };
    const transfer = (to, amount) => {
      gas.charge(GAS_TRANSFER);
      const toStr = String(to).toLowerCase();
      if (!/^0x[0-9a-f]{40}$/.test(toStr)) {
        throw new Error(`transfer: invalid address '${to}'`);
      }
      let amt;
      try {
        amt = typeof amount === "bigint" ? amount : BigInt(amount);
      } catch {
        throw new Error(`transfer: amount must be a bigint-compatible value, got ${typeof amount}`);
      }
      if (amt <= 0n) {
        throw new Error("transfer: amount must be positive");
      }
      outboundTransfers.push({ to: toStr, amount: amt });
    };
    const endowments = {
      chain,
      msg,
      storage: meteredStorage,
      emit,
      assert: assertFn,
      transfer,
      E
    };
    if (options.approvedLibraries) {
      for (const lib of options.approvedLibraries) {
        try {
          const libCompartment = new Compartment({});
          const libExport = libCompartment.evaluate(lib.source);
          endowments[lib.name] = harden(
            typeof libExport === "object" && libExport !== null ? libExport : { default: libExport }
          );
        } catch (err) {
          console.error(`[vm] failed to load approved library '${lib.name}':`, err);
        }
      }
    }
    let compartment;
    try {
      compartment = new Compartment(endowments);
    } catch (err) {
      throw new ContractLoadError(
        `failed to create compartment: ${err instanceof Error ? err.message : String(err)}`,
        { cause: err }
      );
    }
    try {
      return compartment.evaluate(source);
    } catch (err) {
      if (err instanceof Error && err.name === "GasLimitExceededError") throw err;
      throw new ContractLoadError(
        `failed to evaluate contract source: ${err instanceof Error ? err.message : String(err)}`,
        { cause: err }
      );
    }
  }
};
function bigintReplacer(_key, value) {
  if (typeof value === "bigint") return value.toString();
  return value;
}
export {
  BASE_CALL_GAS,
  BASE_DEPLOY_GAS,
  ContractExecutionError,
  ContractInterfaceError,
  ContractLoadError,
  GAS_CROSS_CALL,
  GAS_DEPLOY_PER_BYTE,
  GAS_EVENT_BASE,
  GAS_EVENT_PER_BYTE,
  GAS_STORAGE_DELETE,
  GAS_STORAGE_READ,
  GAS_STORAGE_WRITE_NEW,
  GAS_STORAGE_WRITE_UPDATE,
  GAS_TRANSFER,
  GasLimitExceededError,
  GasMeter,
  Vm,
  initializeVm
};
//# sourceMappingURL=index.js.map