UNPKG

vue-blocklink

Version:

Vue support for the Blockchain Link browser extension

325 lines (324 loc) 14 kB
import { Web3Wrapper } from './0xw3w'; import { EventEmitter } from "eventemitter3"; import { assert } from './0xassert'; import { schemas } from './validations'; import { AbiEncoder, abiUtils, B, decodeBytesAsRevertError, decodeThrownErrorAsRevertError, providerUtils, StringRevertError, } from './utils'; import * as util from 'ethereumjs-util'; import VM from '@ethereumjs/vm'; import * as _ from "lodash"; import { DefaultStateManager } from "@ethereumjs/vm/dist/state"; import { AbiType, } from "./types"; export { SubscriptionManager } from './subscription_manager'; const ARBITRARY_PRIVATE_KEY = 'e331b6d69882b4cb4ea581d88e0b604039a3de5967688d3dcffdd2270c0fd109'; export function methodAbiToFunctionSignature(methodAbi) { const method = AbiEncoder.createMethod(methodAbi.name, methodAbi.inputs); return method.getSignature(); } export function linkLibrariesInBytecode(artifact, libraryAddresses) { const bytecodeArtifact = artifact.compilerOutput.evm.bytecode; let bytecode = bytecodeArtifact.object.substr(2); for (const link of Object.values(bytecodeArtifact.linkReferences)) { for (const [libraryName, libraryRefs] of Object.entries(link)) { const libraryAddress = libraryAddresses[libraryName]; if (!libraryAddress) { throw new Error(`${artifact.contractName} has an unlinked reference library ${libraryName} but no addresses was provided'.`); } for (const ref of libraryRefs) { bytecode = [ bytecode.substring(0, ref.start * 2), libraryAddress.toLowerCase().substr(2), bytecode.substring((ref.start + ref.length) * 2), ].join(''); } } } return `0x${bytecode}`; } function formatABIDataItem(type, components, value) { const trailingArrayRegex = /\[\d*\]$/; if (type.match(trailingArrayRegex)) { const arrayItemType = type.replace(trailingArrayRegex, ''); return _.map(value, val => (formatABIDataItem(arrayItemType, components, val))); } else if (type === 'tuple') { const formattedTuple = {}; _.forEach(components, componentABI => { formattedTuple[componentABI.name] = formatABIDataItem(componentABI.type, componentABI.components, value[componentABI.name]); }); return formattedTuple; } else { return type.match(/^u?int\d*$/) ? value.toString() : value; } } export class PromiseWithTransactionHash { constructor(txHashPromise, promise) { this.txHashPromise = txHashPromise; this._promise = promise; } then(onFulfilled, onRejected) { return this._promise.then(onFulfilled, onRejected); } catch(onRejected) { return this._promise.catch(onRejected); } finally(onFinally) { return this._promise.finally(onFinally); } get [Symbol.toStringTag]() { return this._promise[Symbol.toStringTag]; } } export class BaseContract extends EventEmitter { constructor(contractName, abi, address, supportedProvider, callAndTxnDefaults, logDecodeDependencies, deployedBytecode, encoderOverrides) { super(); this.__debug = false; this.constructorArgs = []; assert.isString('contractName', contractName); assert.isETHAddressHex('address', address); if (deployedBytecode !== undefined && deployedBytecode !== '') { try { assert.isHexString('deployedBytecode', deployedBytecode); this._deployedBytecodeIfExists = Buffer.from(deployedBytecode.substr(2), 'hex'); } catch (err) { } } const provider = providerUtils.standardizeOrThrow(supportedProvider); if (callAndTxnDefaults !== undefined) { assert.doesConformToSchema('callAndTxnDefaults', callAndTxnDefaults, schemas.callDataSchema); } this.contractName = contractName; this._web3Wrapper = new Web3Wrapper(provider, callAndTxnDefaults); this._encoderOverrides = encoderOverrides || {}; this.abi = abi; this.address = address; const methodAbis = this.abi.filter((abiDefinition) => abiDefinition.type === AbiType.Function); this._abiEncoderByFunctionSignature = {}; methodAbis.forEach(methodAbi => { const abiEncoder = new AbiEncoder.Method(methodAbi); const functionSignature = abiEncoder.getSignature(); this._abiEncoderByFunctionSignature[functionSignature] = abiEncoder; this._web3Wrapper.abiDecoder.addABI(abi, contractName); }); if (logDecodeDependencies) { Object.entries(logDecodeDependencies).forEach(([dependencyName, dependencyAbi]) => this._web3Wrapper.abiDecoder.addABI(dependencyAbi, dependencyName)); } } decodeValues(params) { let results = []; if (w3.utils.isArray(params)) { const l = params.length; for (let h = 0; h < l; h++) { if (w3.utils.isBigNumber(params[h])) { results.push(params[h].toString()); } else { console.log("parse outside :: ", params[h]); } } } else { results = []; } return results; } setDebug(bool) { this.__debug = bool; } static _formatABIDataItemList(abis, values, formatter) { return values.map((value, i) => formatABIDataItem(abis[i].type, value, formatter)); } static _lowercaseAddress(type, value) { return type === 'address' ? value.toLowerCase() : value; } static _bigNumberToString(_type, value) { return B.BigNumber.isBigNumber(value) ? value.toString() : value; } static _lookupConstructorAbi(abi) { const constructorAbiIfExists = abi.find((abiDefinition) => abiDefinition.type === AbiType.Constructor); if (constructorAbiIfExists !== undefined) { return constructorAbiIfExists; } else { const defaultConstructorAbi = { type: AbiType.Constructor, stateMutability: 'nonpayable', payable: false, inputs: [], }; return defaultConstructorAbi; } } static _throwIfCallResultIsRevertError(rawCallResult) { let revert; try { revert = decodeBytesAsRevertError(rawCallResult); } catch (err) { return; } throw revert; } static _throwIfThrownErrorIsRevertError(error) { let revertError; try { revertError = decodeThrownErrorAsRevertError(error); } catch (err) { return; } if (revertError instanceof StringRevertError) { throw new Error(revertError.values.message); } throw revertError; } static _throwIfUnexpectedEmptyCallResult(rawCallResult, methodAbi) { if (!rawCallResult || rawCallResult === '0x') { const returnValueDataItem = methodAbi.getReturnValueDataItem(); if (returnValueDataItem.components === undefined || returnValueDataItem.components.length === 0) { return; } throw new Error(`Function "${methodAbi.getSignature()}" reverted with no data`); } } static strictArgumentEncodingCheck(inputAbi, args) { const abiEncoder = AbiEncoder.create(inputAbi); const params = abiUtils.parseEthersParams(inputAbi); const rawEncoded = abiEncoder.encode(args); const rawDecoded = abiEncoder.decodeAsArray(rawEncoded); for (let i = 0; i < rawDecoded.length; i++) { const original = args[i]; const decoded = rawDecoded[i]; if (!abiUtils.isAbiDataEqual(params.names[i], params.types[i], original, decoded)) { throw new Error(`Cannot safely encode argument: ${params.names[i]} (${original}) of type ${params.types[i]}. (Possible type overflow or other encoding error)`); } } return rawEncoded; } static async _applyDefaultsToContractTxDataAsync(txData, estimateGasAsync) { const txDataWithDefaults = BaseContract._removeUndefinedProperties(txData); if (txDataWithDefaults.gas === undefined && estimateGasAsync !== undefined) { txDataWithDefaults.gas = await estimateGasAsync(txDataWithDefaults); } if (txDataWithDefaults.from !== undefined) { txDataWithDefaults.from = txDataWithDefaults.from.toLowerCase(); } return txDataWithDefaults; } static _assertCallParams(callData, defaultBlock) { assert.doesConformToSchema('callData', callData, schemas.callDataSchema); if (defaultBlock !== undefined) { assert.isBlockParam('defaultBlock', defaultBlock); } } static _removeUndefinedProperties(props) { const clonedProps = { ...props }; Object.keys(clonedProps).forEach(key => clonedProps[key] === undefined && delete clonedProps[key]); return clonedProps; } _promiseWithTransactionHash(txHashPromise, opts) { return new PromiseWithTransactionHash(txHashPromise, (async () => { return this._web3Wrapper.awaitTransactionSuccessAsync(await txHashPromise, opts.pollingIntervalMs, opts.timeoutMs); })()); } async _applyDefaultsToTxDataAsync(txData, estimateGasAsync) { const txDataWithDefaults = { to: this.address, ...this._web3Wrapper.getContractDefaults(), ...BaseContract._removeUndefinedProperties(txData), }; if (txDataWithDefaults.gas === undefined && estimateGasAsync !== undefined) { txDataWithDefaults.gas = await estimateGasAsync(txDataWithDefaults); } if (txDataWithDefaults.from !== undefined) { txDataWithDefaults.from = txDataWithDefaults.from.toLowerCase(); } return txDataWithDefaults; } async _evmExecAsync(encodedData) { const encodedDataBytes = Buffer.from(encodedData.substr(2), 'hex'); const addressBuf = Buffer.from(this.address.substr(2), 'hex'); const addressFromBuf = new util.Address(addressBuf); if (this._evmIfExists === undefined) { const vm = new VM({}); const psm = new DefaultStateManager(vm.stateManager); const accountPk = Buffer.from(ARBITRARY_PRIVATE_KEY, 'hex'); const accountAddressStr = util.privateToAddress(accountPk); const accountAddress = new util.Address(accountAddressStr); const account = new util.Account(); await psm.putAccount(accountAddress, account); if (this._deployedBytecodeIfExists === undefined) { const contractCode = await this._web3Wrapper.getContractCodeAsync(this.address); this._deployedBytecodeIfExists = Buffer.from(contractCode.substr(2), 'hex'); } await psm.putContractCode(addressFromBuf, this._deployedBytecodeIfExists); this._evmIfExists = vm; this._evmAccountIfExists = accountAddressStr; } let rawCallResult; try { if (this._evmAccountIfExists) { const addressExist = new util.Address(this._evmAccountIfExists); const result = await this._evmIfExists.runCall({ to: addressFromBuf, caller: addressExist, origin: addressExist, data: encodedDataBytes, }); rawCallResult = `0x${result.execResult.returnValue.toString('hex')}`; } } catch (err) { BaseContract._throwIfThrownErrorIsRevertError(err); throw err; } BaseContract._throwIfCallResultIsRevertError(rawCallResult); return rawCallResult; } async _performCallAsync(callData, defaultBlock) { const callDataWithDefaults = await this._applyDefaultsToTxDataAsync(callData); let rawCallResult; try { rawCallResult = await this._web3Wrapper.callAsync(callDataWithDefaults, defaultBlock); } catch (err) { BaseContract._throwIfThrownErrorIsRevertError(err); throw err; } BaseContract._throwIfCallResultIsRevertError(rawCallResult); return rawCallResult; } _lookupAbiEncoder(functionSignature) { const abiEncoder = this._abiEncoderByFunctionSignature[functionSignature]; if (abiEncoder === undefined) { throw new Error(`Failed to lookup method with function signature '${functionSignature}'`); } return abiEncoder; } _lookupAbi(functionSignature) { const methodAbi = this.abi.find((abiDefinition) => { if (abiDefinition.type !== AbiType.Function) { return false; } const abiFunctionSignature = new AbiEncoder.Method(abiDefinition).getSignature(); if (abiFunctionSignature === functionSignature) { return true; } return false; }); return methodAbi; } _strictEncodeArguments(functionSignature, functionArguments) { if (this._encoderOverrides.encodeInput) { return this._encoderOverrides.encodeInput(functionSignature.split('(')[0], functionArguments); } const abiEncoder = this._lookupAbiEncoder(functionSignature); const inputAbi = abiEncoder.getDataItem().components; if (inputAbi === undefined) { throw new Error(`Undefined Method Input ABI`); } const abiEncodedArguments = abiEncoder.encode(functionArguments); return abiEncodedArguments; } }