Allow users to specify the contracts backend in abi-gen

This commit is contained in:
Leonid Logvinov 2018-02-23 11:35:44 -08:00
parent f5275d3ad7
commit 2d561bc8a0
No known key found for this signature in database
GPG Key ID: 0DD294BFDE8C95D4
19 changed files with 199 additions and 71 deletions

View File

@ -6,6 +6,9 @@
// tslint:disable-next-line:no-unused-variable
import { TxData, TxDataPayable } from '@0xproject/types';
import { BigNumber, classUtils, promisify } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import * as ethersContracts from 'ethers-contracts';
import * as _ from 'lodash';
import * as Web3 from 'web3';
import {BaseContract} from './base_contract';
@ -28,6 +31,7 @@ export enum {{contractName}}Events {
{{/each}}
{{/if}}
// tslint:disable:no-parameter-reassignment
export class {{contractName}}Contract extends BaseContract {
{{#each methods}}
{{#this.constant}}
@ -37,8 +41,8 @@ export class {{contractName}}Contract extends BaseContract {
{{> tx contractName=../contractName}}
{{/this.constant}}
{{/each}}
constructor(web3ContractInstance: Web3.ContractInstance, defaults: Partial<TxData>) {
super(web3ContractInstance, defaults);
classUtils.bindAll(this, ['_web3ContractInstance', '_defaults']);
constructor(web3Wrapper: Web3Wrapper, abi: Web3.ContractAbi, address: string) {
super(web3Wrapper, abi, address);
classUtils.bindAll(this, ['_ethersInterface', '_address', '_abi', '_web3Wrapper']);
}
} // tslint:disable:max-file-line-count

View File

@ -4,12 +4,20 @@ public {{this.name}} = {
defaultBlock?: Web3.BlockParam,
): Promise<{{> return_type outputs=outputs}}> {
const self = this as {{contractName}}Contract;
const result = await promisify<{{> return_type outputs=outputs}}>(
self._web3ContractInstance.{{this.name}}.call,
self._web3ContractInstance,
)(
const inputAbi = _.find(this._abi, {name: '{{this.name}}'}).inputs;
[{{> params inputs=inputs}}] = BaseContract._transformABIData(inputAbi, [{{> params inputs=inputs}}], BaseContract._bigNumberToString.bind(this));
const callDescription = self._ethersInterface.functions.{{this.name}}(
{{> params inputs=inputs}}
);
return result;
) as ethersContracts.CallDescription;
const callData = await self._applyDefaultsToTxDataAsync(
{
data: callDescription.data,
}
)
const rawCallResult = await self._web3Wrapper.callAsync(callData);
let resultArray = callDescription.parse(rawCallResult);
const outputAbi = _.find(this._abi, {name: '{{this.name}}'}).outputs;
resultArray = BaseContract._transformABIData(outputAbi, resultArray, BaseContract._lowercaseAddress.bind(this));
return resultArray{{#singleReturnValue}}[0]{{/singleReturnValue}};
},
};

View File

@ -9,19 +9,22 @@ public {{this.name}} = {
{{/this.payable}}
): Promise<string> {
const self = this as {{contractName}}Contract;
const inputAbi = _.find(this._abi, {name: '{{this.name}}'}).inputs;
[{{> params inputs=inputs}}] = BaseContract._transformABIData(inputAbi, [{{> params inputs=inputs}}], BaseContract._bigNumberToString.bind(this));
const data = this._ethersInterface.functions.{{this.name}}(
{{> params inputs=inputs}}
).data
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync(
txData,
{
...txData,
data,
},
self.{{this.name}}.estimateGasAsync.bind(
self,
{{> params inputs=inputs}}
),
);
const txHash = await promisify<string>(
self._web3ContractInstance.{{this.name}}, self._web3ContractInstance,
)(
{{> params inputs=inputs}}
txDataWithDefaults,
);
const txHash = await this._web3Wrapper.sendTransactionAsync(txDataWithDefaults);
return txHash;
},
async estimateGasAsync(
@ -29,15 +32,16 @@ public {{this.name}} = {
txData: TxData = {},
): Promise<number> {
const self = this as {{contractName}}Contract;
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync(
txData,
);
const gas = await promisify<number>(
self._web3ContractInstance.{{this.name}}.estimateGas, self._web3ContractInstance,
)(
const data = this._ethersInterface.functions.{{this.name}}(
{{> params inputs=inputs}}
txDataWithDefaults,
).data
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync(
{
...txData,
data,
}
);
const gas = await this._web3Wrapper.estimateGasAsync(txDataWithDefaults);
return gas;
},
getABIEncodedTransactionData(
@ -45,7 +49,9 @@ public {{this.name}} = {
txData: TxData = {},
): string {
const self = this as {{contractName}}Contract;
const abiEncodedTransactionData = self._web3ContractInstance.{{this.name}}.getData();
const abiEncodedTransactionData = this._ethersInterface.functions.{{this.name}}(
{{> params inputs=inputs}}
).data
return abiEncodedTransactionData;
},
};

View File

@ -17,7 +17,7 @@
"build": "run-p build:umd:prod build:commonjs; exit 0;",
"docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json $JSON_FILE_PATH $PROJECT_DIR",
"upload_docs_json": "aws s3 cp generated_docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type application/json",
"generate_contract_wrappers": "node ../abi-gen/lib/index.js --abis 'src/artifacts/@(Exchange|Token|TokenTransferProxy|EtherToken|TokenRegistry|DummyToken).json' --template contract_templates/contract.handlebars --partials 'contract_templates/partials/**/*.handlebars' --output src/contract_wrappers/generated",
"generate_contract_wrappers": "node ../abi-gen/lib/index.js --abis 'src/artifacts/@(Exchange|Token|TokenTransferProxy|EtherToken|TokenRegistry|DummyToken).json' --template contract_templates/contract.handlebars --partials 'contract_templates/partials/**/*.handlebars' --output src/contract_wrappers/generated && prettier --write 'src/contract_wrappers/generated/**.ts'",
"lint": "tslint --project . 'src/**/*.ts' 'test/**/*.ts'",
"test:circleci": "run-s test:coverage report_test_coverage",
"test": "run-s clean test:commonjs",
@ -91,6 +91,7 @@
"ethereumjs-abi": "^0.6.4",
"ethereumjs-blockstream": "^2.0.6",
"ethereumjs-util": "^5.1.1",
"ethers-contracts": "^2.2.1",
"js-sha3": "^0.7.0",
"lodash": "^4.17.4",
"uuid": "^3.1.0",

View File

@ -108,10 +108,10 @@ export class ContractWrapper {
const logWithDecodedArgs = this._abiDecoder.tryToDecodeLogOrNoop(log);
return logWithDecodedArgs;
}
protected async _instantiateContractIfExistsAsync(
protected async _getContractAbiAndAddressFromArtifactsAsync(
artifact: Artifact,
addressIfExists?: string,
): Promise<Web3.ContractInstance> {
): Promise<[Web3.ContractAbi, string]> {
let contractAddress: string;
if (_.isUndefined(addressIfExists)) {
if (_.isUndefined(artifact.networks[this._networkId])) {
@ -125,8 +125,8 @@ export class ContractWrapper {
if (!doesContractExist) {
throw new Error(CONTRACT_NAME_TO_NOT_FOUND_ERROR[artifact.contract_name]);
}
const contractInstance = this._web3Wrapper.getContractInstance(artifact.abi, contractAddress);
return contractInstance;
const abiAndAddress: [Web3.ContractAbi, string] = [artifact.abi, contractAddress];
return abiAndAddress;
}
protected _getContractAddress(artifact: Artifact, addressIfExists?: string): string {
if (_.isUndefined(addressIfExists)) {

View File

@ -187,11 +187,11 @@ export class EtherTokenWrapper extends ContractWrapper {
if (!_.isUndefined(etherTokenContract)) {
return etherTokenContract;
}
const web3ContractInstance = await this._instantiateContractIfExistsAsync(
const [abi, address] = await this._getContractAbiAndAddressFromArtifactsAsync(
artifacts.EtherTokenArtifact,
etherTokenAddress,
);
const contractInstance = new EtherTokenContract(web3ContractInstance, this._web3Wrapper.getContractDefaults());
const contractInstance = new EtherTokenContract(this._web3Wrapper, abi, address);
etherTokenContract = contractInstance;
this._etherTokenContractsByAddress[etherTokenAddress] = etherTokenContract;
return etherTokenContract;

View File

@ -858,7 +858,7 @@ export class ExchangeWrapper extends ContractWrapper {
});
if (!_.isUndefined(errLog)) {
const logArgs = (errLog as LogWithDecodedArgs<LogErrorContractEventArgs>).args;
const errCode = logArgs.errorId.toNumber();
const errCode = logArgs.errorId;
const errMessage = this._exchangeContractErrCodesToMsg[errCode];
throw new Error(errMessage);
}
@ -906,11 +906,11 @@ export class ExchangeWrapper extends ContractWrapper {
if (!_.isUndefined(this._exchangeContractIfExists)) {
return this._exchangeContractIfExists;
}
const web3ContractInstance = await this._instantiateContractIfExistsAsync(
const [abi, address] = await this._getContractAbiAndAddressFromArtifactsAsync(
artifacts.ExchangeArtifact,
this._contractAddressIfExists,
);
const contractInstance = new ExchangeContract(web3ContractInstance, this._web3Wrapper.getContractDefaults());
const contractInstance = new ExchangeContract(this._web3Wrapper, abi, address);
this._exchangeContractIfExists = contractInstance;
return this._exchangeContractIfExists;
}

View File

@ -1,11 +1,45 @@
import { TxData, TxDataPayable } from '@0xproject/types';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import * as ethersContracts from 'ethers-contracts';
import * as _ from 'lodash';
import * as Web3 from 'web3';
export class BaseContract {
protected _web3ContractInstance: Web3.ContractInstance;
protected _defaults: Partial<TxData>;
protected async _applyDefaultsToTxDataAsync<T extends TxData|TxDataPayable>(
protected _ethersInterface: ethersContracts.Interface;
protected _web3Wrapper: Web3Wrapper;
protected _abi: Web3.ContractAbi;
protected _address: string;
protected static _transformABIData(
abis: Web3.DataItem[],
values: any[],
transformation: (type: string, value: any) => any,
): any {
return _.map(values, (value: any, i: number) =>
BaseContract._transformTypedData(abis[i].type, value, transformation),
);
}
protected static _lowercaseAddress(type: string, value: string): string {
return type === 'address' ? value.toLowerCase() : value;
}
protected static _bigNumberToString(type: string, value: string): string {
return _.isObject(value) && (value as any).isBigNumber ? value.toString() : value;
}
private static _transformTypedData(
type: string,
values: any,
transformation: (type: string, value: any) => any,
): any {
const trailingArrayRegex = /\[\d*\]$/;
if (type.match(trailingArrayRegex)) {
const arrayItemType = type.replace(trailingArrayRegex, '');
return _.map(values, (value: any, i: number) =>
this._transformTypedData(arrayItemType, value, transformation),
);
} else {
return transformation(type, values);
}
}
protected async _applyDefaultsToTxDataAsync<T extends Partial<TxData | TxDataPayable>>(
txData: T,
estimateGasAsync?: (txData: T) => Promise<number>,
): Promise<TxData> {
@ -15,7 +49,8 @@ export class BaseContract {
// 3. Gas estimate calculation + safety margin
const removeUndefinedProperties = _.pickBy;
const txDataWithDefaults = {
...removeUndefinedProperties(this._defaults),
to: this._address,
...removeUndefinedProperties(this._web3Wrapper.getContractDefaults()),
...removeUndefinedProperties(txData as any),
// HACK: TS can't prove that T is spreadable.
// Awaiting https://github.com/Microsoft/TypeScript/pull/13288 to be merged
@ -26,8 +61,10 @@ export class BaseContract {
}
return txDataWithDefaults;
}
constructor(web3ContractInstance: Web3.ContractInstance, defaults: Partial<TxData>) {
this._web3ContractInstance = web3ContractInstance;
this._defaults = defaults;
constructor(web3Wrapper: Web3Wrapper, abi: Web3.ContractAbi, address: string) {
this._web3Wrapper = web3Wrapper;
this._abi = abi;
this._address = address;
this._ethersInterface = new ethersContracts.Interface(abi);
}
}

View File

@ -23,7 +23,7 @@ export class TokenRegistryWrapper extends ContractWrapper {
address: metadata[0],
name: metadata[1],
symbol: metadata[2],
decimals: metadata[3].toNumber(),
decimals: metadata[3],
};
return token;
}
@ -50,7 +50,8 @@ export class TokenRegistryWrapper extends ContractWrapper {
public async getTokenAddressesAsync(): Promise<string[]> {
const tokenRegistryContract = await this._getTokenRegistryContractAsync();
const addresses = await tokenRegistryContract.getTokenAddresses.callAsync();
return addresses;
const lowerCaseAddresses = _.map(addresses, address => address.toLowerCase());
return lowerCaseAddresses;
}
/**
* Retrieves a token by address currently listed in the Token Registry smart contract
@ -116,14 +117,11 @@ export class TokenRegistryWrapper extends ContractWrapper {
if (!_.isUndefined(this._tokenRegistryContractIfExists)) {
return this._tokenRegistryContractIfExists;
}
const web3ContractInstance = await this._instantiateContractIfExistsAsync(
const [abi, address] = await this._getContractAbiAndAddressFromArtifactsAsync(
artifacts.TokenRegistryArtifact,
this._contractAddressIfExists,
);
const contractInstance = new TokenRegistryContract(
web3ContractInstance,
this._web3Wrapper.getContractDefaults(),
);
const contractInstance = new TokenRegistryContract(this._web3Wrapper, abi, address);
this._tokenRegistryContractIfExists = contractInstance;
return this._tokenRegistryContractIfExists;
}

View File

@ -59,14 +59,11 @@ export class TokenTransferProxyWrapper extends ContractWrapper {
if (!_.isUndefined(this._tokenTransferProxyContractIfExists)) {
return this._tokenTransferProxyContractIfExists;
}
const web3ContractInstance = await this._instantiateContractIfExistsAsync(
const [abi, address] = await this._getContractAbiAndAddressFromArtifactsAsync(
artifacts.TokenTransferProxyArtifact,
this._contractAddressIfExists,
);
const contractInstance = new TokenTransferProxyContract(
web3ContractInstance,
this._web3Wrapper.getContractDefaults(),
);
const contractInstance = new TokenTransferProxyContract(this._web3Wrapper, abi, address);
this._tokenTransferProxyContractIfExists = contractInstance;
return this._tokenTransferProxyContractIfExists;
}

View File

@ -419,11 +419,11 @@ export class TokenWrapper extends ContractWrapper {
if (!_.isUndefined(tokenContract)) {
return tokenContract;
}
const web3ContractInstance = await this._instantiateContractIfExistsAsync(
const [abi, address] = await this._getContractAbiAndAddressFromArtifactsAsync(
artifacts.TokenArtifact,
normalizedTokenAddress,
);
const contractInstance = new TokenContract(web3ContractInstance, this._web3Wrapper.getContractDefaults());
const contractInstance = new TokenContract(this._web3Wrapper, abi, address);
tokenContract = contractInstance;
this._tokenContractsByAddress[normalizedTokenAddress] = tokenContract;
return tokenContract;

View File

@ -41,3 +41,31 @@ declare module 'truffle-hdwallet-provider' {
}
export = HDWalletProvider;
}
declare module 'ethers-contracts' {
export interface TransactionDescription {
name: string;
signature: string;
sighash: string;
data: string;
}
export interface CallDescription extends TransactionDescription {
parse: (...args: any[]) => any;
}
export interface FunctionDescription {
(...params: any[]): TransactionDescription | CallDescription;
inputs: { names: string[]; types: string[] };
outputs: { names: string[]; types: string[] };
}
export interface EventDescription {
inputs: { names: string[]; types: string[] };
signature: string;
topic: string;
}
// tslint:disable-next-line:max-classes-per-file
export class Interface {
public functions: { [functionName: string]: FunctionDescription };
public events: { [eventName: string]: EventDescription };
constructor(abi: any);
}
}

View File

@ -127,7 +127,7 @@ export interface SignedOrder extends Order {
}
// [address, name, symbol, decimals, ipfsHash, swarmHash]
export type TokenMetadata = [string, string, string, BigNumber, string, string];
export type TokenMetadata = [string, string, string, number, string, string];
export interface Token {
name: string;

View File

@ -35,12 +35,8 @@ export class FillScenarios {
const web3Wrapper = (this._zeroEx as any)._web3Wrapper as Web3Wrapper;
for (const token of this._tokens) {
if (token.symbol !== 'ZRX' && token.symbol !== 'WETH') {
const contractInstance = web3Wrapper.getContractInstance(
artifacts.DummyTokenArtifact.abi,
token.address,
);
const defaults = {};
const dummyToken = new DummyTokenContract(contractInstance, defaults);
const dummyToken = new DummyTokenContract(web3Wrapper, artifacts.DummyTokenArtifact.abi, token.address);
const tokenSupply = ZeroEx.toBaseUnitAmount(INITIAL_COINBASE_TOKEN_SUPPLY_IN_UNITS, token.decimals);
const txHash = await dummyToken.setBalance.sendTransactionAsync(this._coinbase, tokenSupply, {
from: this._coinbase,

View File

@ -1,5 +1,9 @@
# CHANGELOG
## v0.2.3 - _TBD, 2018_
* Add a `backend` parameter that allows you to specify your backend (web3 or ethers). Ethers auto-converts small ints to numbers (#TBD)
## v0.2.1 - _February 9, 2018_
* Fix publishing issue where .npmignore was not properly excluding undesired content (#389)

View File

@ -11,13 +11,14 @@ import * as yargs from 'yargs';
import toSnakeCase = require('to-snake-case');
import * as Web3 from 'web3';
import { ContextData, ParamKind } from './types';
import { ContextData, ContractsBackend, ParamKind } from './types';
import { utils } from './utils';
const ABI_TYPE_CONSTRUCTOR = 'constructor';
const ABI_TYPE_METHOD = 'function';
const ABI_TYPE_EVENT = 'event';
const DEFAULT_NETWORK_ID = 50;
const DEFAULT_BACKEND = 'web3';
const args = yargs
.option('abis', {
@ -43,6 +44,12 @@ const args = yargs
demandOption: true,
normalize: true,
})
.option('backend', {
describe: 'Which backend do you plan to use. Either web3 or ethers',
type: 'string',
choices: [ContractsBackend.Web3, ContractsBackend.Ethers],
default: DEFAULT_BACKEND,
})
.option('network-id', {
describe: 'ID of the network where contract ABIs are nested in artifacts',
type: 'number',
@ -73,8 +80,8 @@ function writeOutputFile(name: string, renderedTsCode: string): void {
utils.log(`Created: ${chalk.bold(filePath)}`);
}
Handlebars.registerHelper('parameterType', utils.solTypeToTsType.bind(utils, ParamKind.Input));
Handlebars.registerHelper('returnType', utils.solTypeToTsType.bind(utils, ParamKind.Output));
Handlebars.registerHelper('parameterType', utils.solTypeToTsType.bind(utils, ParamKind.Input, args.backend));
Handlebars.registerHelper('returnType', utils.solTypeToTsType.bind(utils, ParamKind.Output, args.backend));
if (args.partials) {
registerPartials(args.partials);

View File

@ -12,6 +12,11 @@ export enum AbiType {
Fallback = 'fallback',
}
export enum ContractsBackend {
Web3 = 'web3',
Ethers = 'ethers',
}
export interface Method extends Web3.MethodAbi {
singleReturnValue: boolean;
}

View File

@ -3,14 +3,14 @@ import * as _ from 'lodash';
import * as path from 'path';
import * as Web3 from 'web3';
import { AbiType, ParamKind } from './types';
import { AbiType, ContractsBackend, ParamKind } from './types';
export const utils = {
solTypeToTsType(paramKind: ParamKind, solType: string): string {
solTypeToTsType(paramKind: ParamKind, backend: ContractsBackend, solType: string): string {
const trailingArrayRegex = /\[\d*\]$/;
if (solType.match(trailingArrayRegex)) {
const arrayItemSolType = solType.replace(trailingArrayRegex, '');
const arrayItemTsType = utils.solTypeToTsType(paramKind, arrayItemSolType);
const arrayItemTsType = utils.solTypeToTsType(paramKind, backend, arrayItemSolType);
const arrayTsType = utils.isUnionType(arrayItemTsType)
? `Array<${arrayItemTsType}>`
: `${arrayItemTsType}[]`;
@ -24,13 +24,21 @@ export const utils = {
{ regex: '^bytes\\d*$', tsType: 'string' },
];
if (paramKind === ParamKind.Input) {
// web3 allows to pass those an non-bignumbers and that's nice
// but it always returns stuff as BigNumbers
// web3 and ethers allow to pass those as numbers instead of bignumbers
solTypeRegexToTsType.unshift({
regex: '^u?int(8|16|32)?$',
tsType: 'number|BigNumber',
});
}
if (backend === ContractsBackend.Ethers) {
if (paramKind === ParamKind.Output) {
// ethers-contracts automatically converts small BigNumbers to numbers
solTypeRegexToTsType.unshift({
regex: '^u?int(8|16|32|48)?$',
tsType: 'number',
});
}
}
for (const regexAndTxType of solTypeRegexToTsType) {
const { regex, tsType } = regexAndTxType;
if (solType.match(regex)) {

View File

@ -10,6 +10,12 @@
version "4.0.3"
resolved "https://registry.yarnpkg.com/@types/bignumber.js/-/bignumber.js-4.0.3.tgz#e8ce5f28c3025a01c6af7fc6d944494903a9e348"
"@types/bignumber.js@^5.0.0":
version "5.0.0"
resolved "https://registry.yarnpkg.com/@types/bignumber.js/-/bignumber.js-5.0.0.tgz#d9f1a378509f3010a3255e9cc822ad0eeb4ab969"
dependencies:
bignumber.js "*"
"@types/bintrees@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@types/bintrees/-/bintrees-1.0.2.tgz#0dfdce4eeebdf90427bd35b0e79dc248b3d157a6"
@ -1283,6 +1289,10 @@ big.js@^3.1.3:
version "3.2.0"
resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e"
bignumber.js@*, bignumber.js@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-6.0.0.tgz#bbfa047644609a5af093e9cbd83b0461fa3f6002"
"bignumber.js@git+https://github.com/debris/bignumber.js#master":
version "2.0.7"
resolved "git+https://github.com/debris/bignumber.js#c7a38de919ed75e6fb6ba38051986e294b328df9"
@ -3227,6 +3237,21 @@ ethereumjs-wallet@^0.6.0:
utf8 "^2.1.1"
uuid "^2.0.1"
ethers-contracts@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/ethers-contracts/-/ethers-contracts-2.2.1.tgz#e2bf5dd5e157313ba454b50c646c8472fcd0a8b3"
dependencies:
ethers-utils "^2.1.0"
ethers-utils@^2.1.0:
version "2.1.11"
resolved "https://registry.yarnpkg.com/ethers-utils/-/ethers-utils-2.1.11.tgz#b27535ca3226118be300211c39c896b1e5e21641"
dependencies:
bn.js "^4.4.0"
hash.js "^1.0.0"
js-sha3 "0.5.7"
xmlhttprequest "1.8.0"
ethjs-abi@0.1.8:
version "0.1.8"
resolved "https://registry.yarnpkg.com/ethjs-abi/-/ethjs-abi-0.1.8.tgz#cd288583ed628cdfadaf8adefa3ba1dbcbca6c18"
@ -4970,6 +4995,10 @@ js-sha3@0.5.5:
version "0.5.5"
resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.5.5.tgz#baf0c0e8c54ad5903447df96ade7a4a1bca79a4a"
js-sha3@0.5.7:
version "0.5.7"
resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.5.7.tgz#0d4ffd8002d5333aabaf4a23eed2f6374c9f28e7"
js-sha3@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.3.1.tgz#86122802142f0828502a0d1dee1d95e253bb0243"
@ -9811,7 +9840,7 @@ xml-js@^1.3.2:
dependencies:
sax "^1.2.4"
xmlhttprequest@*:
xmlhttprequest@*, xmlhttprequest@1.8.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc"