133 lines
4.6 KiB
TypeScript
133 lines
4.6 KiB
TypeScript
import {
|
|
ArweaveWrapper,
|
|
Benchmark,
|
|
ContractDefinition,
|
|
ContractSource,
|
|
ContractType,
|
|
DefinitionLoader,
|
|
getTag,
|
|
LoggerFactory,
|
|
WarpTags,
|
|
WarpCache
|
|
} from '@warp';
|
|
import Arweave from 'arweave';
|
|
import Transaction from 'arweave/web/lib/transaction';
|
|
import { WasmSrc } from './wasm/WasmSrc';
|
|
|
|
const supportedSrcContentTypes = ['application/javascript', 'application/wasm'];
|
|
|
|
export class ContractDefinitionLoader implements DefinitionLoader {
|
|
private readonly logger = LoggerFactory.INST.create('ContractDefinitionLoader');
|
|
|
|
protected arweaveWrapper: ArweaveWrapper;
|
|
|
|
constructor(
|
|
private readonly arweave: Arweave,
|
|
// TODO: cache should be removed from the core layer and implemented in a wrapper of the core implementation
|
|
protected readonly cache?: WarpCache<string, ContractDefinition<unknown>>
|
|
) {
|
|
this.arweaveWrapper = new ArweaveWrapper(arweave);
|
|
}
|
|
|
|
async load<State>(contractTxId: string, evolvedSrcTxId?: string): Promise<ContractDefinition<State>> {
|
|
if (!evolvedSrcTxId && this.cache?.contains(contractTxId)) {
|
|
this.logger.debug('ContractDefinitionLoader: Hit from cache!');
|
|
return Promise.resolve(this.cache?.get(contractTxId) as ContractDefinition<State>);
|
|
}
|
|
const benchmark = Benchmark.measure();
|
|
const contract = await this.doLoad<State>(contractTxId, evolvedSrcTxId);
|
|
this.logger.info(`Contract definition loaded in: ${benchmark.elapsed()}`);
|
|
this.cache?.put(contractTxId, contract);
|
|
|
|
return contract;
|
|
}
|
|
|
|
async doLoad<State>(contractTxId: string, forcedSrcTxId?: string): Promise<ContractDefinition<State>> {
|
|
const benchmark = Benchmark.measure();
|
|
|
|
const contractTx = await this.arweaveWrapper.tx(contractTxId);
|
|
const owner = await this.arweave.wallets.ownerToAddress(contractTx.owner);
|
|
this.logger.debug('Contract tx and owner', benchmark.elapsed());
|
|
benchmark.reset();
|
|
|
|
const contractSrcTxId = forcedSrcTxId ? forcedSrcTxId : getTag(contractTx, WarpTags.CONTRACT_SRC_TX_ID);
|
|
const minFee = getTag(contractTx, WarpTags.MIN_FEE);
|
|
this.logger.debug('Tags decoding', benchmark.elapsed());
|
|
benchmark.reset();
|
|
const s = await this.evalInitialState(contractTx);
|
|
this.logger.debug('init state', s);
|
|
const initState = JSON.parse(await this.evalInitialState(contractTx));
|
|
this.logger.debug('Parsing src and init state', benchmark.elapsed());
|
|
|
|
const { src, srcBinary, srcWasmLang, contractType, metadata, srcTx } = await this.loadContractSource(
|
|
contractSrcTxId
|
|
);
|
|
|
|
return {
|
|
txId: contractTxId,
|
|
srcTxId: contractSrcTxId,
|
|
src,
|
|
srcBinary,
|
|
srcWasmLang,
|
|
initState,
|
|
minFee,
|
|
owner,
|
|
contractType,
|
|
metadata,
|
|
contractTx: contractTx.toJSON(),
|
|
srcTx
|
|
};
|
|
}
|
|
|
|
async loadContractSource(contractSrcTxId: string): Promise<ContractSource> {
|
|
const benchmark = Benchmark.measure();
|
|
|
|
const contractSrcTx = await this.arweaveWrapper.tx(contractSrcTxId);
|
|
const srcContentType = getTag(contractSrcTx, WarpTags.CONTENT_TYPE);
|
|
if (!supportedSrcContentTypes.includes(srcContentType)) {
|
|
throw new Error(`Contract source content type ${srcContentType} not supported`);
|
|
}
|
|
const contractType: ContractType = srcContentType == 'application/javascript' ? 'js' : 'wasm';
|
|
|
|
const src =
|
|
contractType == 'js'
|
|
? await this.arweaveWrapper.txDataString(contractSrcTxId)
|
|
: await this.arweaveWrapper.txData(contractSrcTxId);
|
|
|
|
let srcWasmLang;
|
|
let wasmSrc: WasmSrc;
|
|
let srcMetaData;
|
|
if (contractType == 'wasm') {
|
|
wasmSrc = new WasmSrc(src as Buffer);
|
|
srcWasmLang = getTag(contractSrcTx, WarpTags.WASM_LANG);
|
|
if (!srcWasmLang) {
|
|
throw new Error(`Wasm lang not set for wasm contract src ${contractSrcTxId}`);
|
|
}
|
|
srcMetaData = JSON.parse(getTag(contractSrcTx, WarpTags.WASM_META));
|
|
}
|
|
|
|
this.logger.debug('Contract src tx load', benchmark.elapsed());
|
|
benchmark.reset();
|
|
|
|
return {
|
|
src: contractType == 'js' ? (src as string) : null,
|
|
srcBinary: contractType == 'wasm' ? wasmSrc.wasmBinary() : null,
|
|
srcWasmLang,
|
|
contractType,
|
|
metadata: srcMetaData,
|
|
srcTx: contractSrcTx.toJSON()
|
|
};
|
|
}
|
|
|
|
private async evalInitialState(contractTx: Transaction): Promise<string> {
|
|
if (getTag(contractTx, WarpTags.INIT_STATE)) {
|
|
return getTag(contractTx, WarpTags.INIT_STATE);
|
|
} else if (getTag(contractTx, WarpTags.INIT_STATE_TX)) {
|
|
const stateTX = getTag(contractTx, WarpTags.INIT_STATE_TX);
|
|
return this.arweaveWrapper.txDataString(stateTX);
|
|
} else {
|
|
return this.arweaveWrapper.txDataString(contractTx.id);
|
|
}
|
|
}
|
|
}
|