import { ArweaveWrapper, Benchmark, ContractDefinition, ContractType, DefinitionLoader, getTag, LoggerFactory, SmartWeaveTags, SwCache } from '@smartweave'; import Arweave from 'arweave'; import Transaction from 'arweave/web/lib/transaction'; const supportedSrcContentTypes = ['application/javascript', 'application/wasm']; export class ContractDefinitionLoader implements DefinitionLoader { private readonly logger = LoggerFactory.INST.create('ContractDefinitionLoader'); private 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?: SwCache> ) { this.arweaveWrapper = new ArweaveWrapper(arweave); } async load(contractTxId: string, evolvedSrcTxId?: string): Promise> { if (!evolvedSrcTxId && this.cache?.contains(contractTxId)) { this.logger.debug('ContractDefinitionLoader: Hit from cache!'); return Promise.resolve(this.cache?.get(contractTxId) as ContractDefinition); } const benchmark = Benchmark.measure(); const contract = await this.doLoad(contractTxId, evolvedSrcTxId); this.logger.info(`Contract definition loaded in: ${benchmark.elapsed()}`); this.cache?.put(contractTxId, contract); return contract; } async doLoad(contractTxId: string, forcedSrcTxId?: string): Promise> { 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, SmartWeaveTags.CONTRACT_SRC_TX_ID); const minFee = getTag(contractTx, SmartWeaveTags.MIN_FEE); this.logger.debug('Tags decoding', benchmark.elapsed()); benchmark.reset(); const contractSrcTx = await this.arweaveWrapper.tx(contractSrcTxId); const srcContentType = getTag(contractSrcTx, SmartWeaveTags.CONTENT_TYPE); if (supportedSrcContentTypes.indexOf(srcContentType) == -1) { 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; if (contractType == 'wasm') { srcWasmLang = getTag(contractSrcTx, SmartWeaveTags.WASM_LANG); if (!srcWasmLang) { this.logger.warn('Wasm lang not set for wasm contract src', contractSrcTxId); } } this.logger.debug('Contract src tx load', 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()); return { txId: contractTxId, srcTxId: contractSrcTxId, src, srcWasmLang, initState, minFee, owner, contractType }; } private async evalInitialState(contractTx: Transaction): Promise { if (getTag(contractTx, SmartWeaveTags.INIT_STATE)) { return getTag(contractTx, SmartWeaveTags.INIT_STATE); } else if (getTag(contractTx, SmartWeaveTags.INIT_STATE_TX)) { const stateTX = getTag(contractTx, SmartWeaveTags.INIT_STATE_TX); return this.arweaveWrapper.txDataString(stateTX); } else { return this.arweaveWrapper.txDataString(contractTx.id); } } }