Consider checkpoint relaying costs in interchain gas calculator, only require `handle` gas amount (#389)

* Update interchain gas calculator to consider checkpoint relaying costs

* Add sinon to sdk

* Tests passing

* Include tests in prettier

* add test for checkpointRelayGas

* better comment

* Self-nits

* Include intrinsic gas in inboxProcessOverheadGas

* PR comments

* builds

* prettier

* rm unnecessary nevers

* nit
pull/409/head
Trevor Porter 3 years ago committed by GitHub
parent 6f74c6bb0d
commit e7cf13b923
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      typescript/sdk/package.json
  2. 149
      typescript/sdk/src/gas/calculator.ts
  3. 182
      typescript/sdk/test/gas/calculator.test.ts
  4. 3
      typescript/sdk/test/utils.ts
  5. 93
      yarn.lock

@ -7,7 +7,7 @@
"scripts": {
"build": "tsc",
"check": "tsc --noEmit",
"prettier": "prettier --write ./src",
"prettier": "prettier --write ./src ./test",
"prepublishOnly": "npm run build",
"test": "mocha --config .mocharc.json './test/**/*.test.ts'"
},
@ -23,6 +23,7 @@
"fs": "0.0.1-security",
"mocha": "^9.2.2",
"prettier": "^2.4.1",
"sinon": "^13.0.2",
"typescript": "^4.4.3"
},
"dependencies": {

@ -28,6 +28,34 @@ import { convertDecimalValue, mulBigAndFixed } from './utils';
// If a domain doesn't specify how many decimals their native token has, 18 is used.
const DEFAULT_TOKEN_DECIMALS = 18;
// A generous estimation of the overhead gas amount when processing a message. This
// includes intrinsic gas, the merkle proof, making the external call to the recipient
// handle function, but does not account for any gas consumed by the handle function.
// This number was arrived at by estimating the proving and processing of a message
// whose body was small and whose recipient contract included only an empty fallback
// function. The estimated gas cost was 86777, which included the intrinsic cost.
// 130,000 is chosen as a generous buffer for safety. The large buffer is mostly to do
// with flexibility in message sizes, where large messages can cost more due to tx calldata,
// hashing, and calling to the recipient handle function.
const INBOX_PROCESS_OVERHEAD_GAS = 130_000;
// Intrinsic gas for a transaction. Does not consider calldata costs or differences in
// intrinsic gas or different networks.
const BASE_INTRINSIC_GAS = 21_000;
// The gas used if the quorum threshold of a signed checkpoint is zero.
// Includes intrinsic gas and all other gas that does not scale with the
// number of signatures. Note this does not consider differences in intrinsic gas for
// different chains.
// Derived by observing the amount of gas consumed for a quorum of 1 (~86800 gas),
// subtracting the gas used per signature, and rounding up for safety.
const BASE_CHECKPOINT_RELAY_GAS = 80_000;
// The amount of gas used for each signature when a signed checkpoint
// is submitted for verification.
// Really observed to be about 8350, but rounding up for safety.
const CHECKPOINT_RELAY_GAS_PER_SIGNATURE = 9_000;
export interface InterchainGasCalculatorConfig {
/**
* A multiplier applied to the estimated origin token payment amount.
@ -81,24 +109,36 @@ export class InterchainGasCalculator {
}
/**
* Calculates the estimated payment for an amount of gas on the destination chain,
* denominated in the native token of the origin chain. Considers the exchange
* rate between the native tokens of the origin and destination chains, and the
* suggested gas price on the destination chain. Applies the multiplier
* `paymentEstimateMultiplier`.
* Given an amount of gas the message's recipient `handle` function is expected
* to use, calculates the estimated payment denominated in the native
* token of the origin chain. Considers the exchange rate between the native
* tokens of the origin and destination chains, the suggested gas price on
* the destination chain, gas costs incurred by a relayer when submitting a signed
* checkpoint to the destination chain, and the overhead gas cost in Inbox of processing
* a message. Applies the multiplier `paymentEstimateMultiplier`.
* @param originDomain The domain of the origin chain.
* @param destinationDomain The domain of the destination chain.
* @param destinationGas The amount of gas to pay for on the destination chain.
* @param destinationHandleGas The amount of gas the recipient `handle` function
* is estimated to use.
* @returns An estimated amount of origin chain tokens to cover gas costs of the
* message on the destination chain.
*/
async estimatePaymentForGasAmount(
async estimatePaymentForHandleGasAmount(
originDomain: number,
destinationDomain: number,
destinationGas: BigNumber,
destinationHandleGas: BigNumber,
): Promise<BigNumber> {
const destinationGasPrice = await this.suggestedGasPrice(destinationDomain);
const destinationCostWei = destinationGas.mul(destinationGasPrice);
const checkpointRelayGas = await this.checkpointRelayGas(
originDomain,
destinationDomain,
);
const inboxProcessOverheadGas = await this.inboxProcessOverheadGas();
const totalDestinationGas = checkpointRelayGas
.add(inboxProcessOverheadGas)
.add(destinationHandleGas);
const destinationCostWei = totalDestinationGas.mul(destinationGasPrice);
// Convert from destination domain native tokens to origin domain native tokens.
const originCostWei = await this.convertBetweenNativeTokens(
@ -117,16 +157,17 @@ export class InterchainGasCalculator {
/**
* Calculates the estimated payment to process the message on its destination chain,
* denominated in the native token of the origin chain. The destination gas is
* determined by estimating the gas to process the provided message, which is then used
* to calculate the payment using {@link estimatePaymentForGasAmount}.
* denominated in the native token of the origin chain. The gas used by the message's
* recipient handler function is estimated in an eth_estimateGas call to the
* destination chain, and is then used to calculate the payment using
* {@link estimatePaymentForHandleGasAmount}.
* @param message The parsed message to estimate payment for.
* @returns An estimated amount of origin chain tokens to cover gas costs of the
* message on the destination chain.
*/
async estimatePaymentForMessage(message: ParsedMessage) {
const destinationGas = await this.estimateGasForMessage(message);
return this.estimatePaymentForGasAmount(
const destinationGas = await this.estimateHandleGasForMessage(message);
return this.estimatePaymentForHandleGasAmount(
message.origin,
message.destination,
destinationGas,
@ -222,19 +263,22 @@ export class InterchainGasCalculator {
}
/**
* Estimates the amount of gas required to process a message on its destination chain.
* This does not assume the Inbox of the destination domain has a checkpoint that
* the message is included in. Therefore, we estimate the gas by summing:
* 1. The intrinsic gas cost of a transaction on the destination chain.
* 2. Any gas costs imposed by operations in the Inbox, including proving
* the message and logic surrounding the processing of a message.
* 3. The estimated gas consumption of a direct call to the `handle`
* Estimates the amount of gas used by message's recipient `handle` function
* on its destination chain. This does not assume the Inbox of the destination
* domain has a checkpoint that the message is included in, and does not
* consider intrinsic gas or any "overhead" gas incurred by Inbox.process.
* The estimated gas returned is the sum of:
* 1. The estimated gas consumption of a direct call to the `handle`
* function of the recipient address using the correct parameters and
* setting the `from` address of the transaction to the address of the inbox.
* 4. A buffer to account for inaccuracies in the above estimations.
* @returns The estimated gas required to process the message on the destination chain.
* 2. A buffer to account for inaccuracies in the above estimation.
* @param message The message to estimate recipient `handle` gas usage for.
* @returns The estimated gas required by the message's recipient handle function
* on the destination chain.
*/
async estimateGasForMessage(message: ParsedMessage): Promise<BigNumber> {
async estimateHandleGasForMessage(
message: ParsedMessage,
): Promise<BigNumber> {
const provider = this.multiProvider.getDomainConnection(
resolveDomain(message.destination),
).provider!;
@ -250,7 +294,7 @@ export class InterchainGasCalculator {
]);
// Estimates a direct call to the `handle` function of the recipient
// with the `from` address set to the inbox.
// This includes intrinsic gas, so no need to add it
// This includes intrinsic gas.
const directHandleCallGas = await provider.estimateGas({
to: utils.bytes32ToAddress(message.recipient),
from: inbox.address,
@ -261,27 +305,56 @@ export class InterchainGasCalculator {
]),
});
// directHandleCallGas includes the intrinsic gas
// Subtract intrinsic gas, which is included in directHandleCallGas.
// Note the "real" intrinsic gas will always be higher than this.intrinsicGas
// due to calldata costs, but this is desired because subtracting the lower bound
// this.intrinsicGas will result in a more generous final estimate.
return directHandleCallGas
.add(this.inboxProvingAndProcessingGas)
.add(this.messageGasEstimateBuffer);
.add(this.messageGasEstimateBuffer)
.sub(this.intrinsicGas);
}
/**
* @param originDomain The domain of the origin chain.
* @param destinationDomain The domain of the destination chain.
* @returns An estimated gas amount a relayer will spend when submitting a signed
* checkpoint to the destination domain.
*/
async checkpointRelayGas(
originDomain: number,
destinationDomain: number,
): Promise<BigNumber> {
const inboxValidatorManager = this.core.getContracts(
resolveDomain(destinationDomain) as never,
).inboxes[resolveDomain(originDomain)].validatorManager;
const threshold = await inboxValidatorManager.threshold();
return threshold
.mul(CHECKPOINT_RELAY_GAS_PER_SIGNATURE)
.add(BASE_CHECKPOINT_RELAY_GAS);
}
/**
* @returns A generous estimation of the gas consumption of all prove and process
* operations in Inbox.sol, excluding:
* 1. Intrinsic gas.
* 2. Any gas consumed within a `handle` function when processing a message once called.
* operations within Inbox.sol, including intrinsic gas. Does not include any gas
* consumed within a message's recipient `handle` function.
* Returns a Promise because we expect this to eventually include async logic to
* estimate sovereign consensus costs, and we'd like to keep the interface consistent.
*/
get inboxProvingAndProcessingGas() {
inboxProcessOverheadGas(): Promise<BigNumber> {
// This does not consider that different domains can possibly have different gas costs.
// Consider this being configurable for each domain, or investigate ways to estimate
// this over RPC.
//
// This number was arrived at by estimating the proving and processing of a message
// whose recipient contract included only an empty fallback function. The estimated
// gas cost was ~100,000 which includes the intrinsic cost, but 150,000 is chosen as
// a generous buffer.
return 150_000;
// Also does not consider gas usage that may scale with message size, e.g. calldata
// costs.
return Promise.resolve(BigNumber.from(INBOX_PROCESS_OVERHEAD_GAS));
}
/**
* @returns The intrinsic gas of a basic transaction. Note this does not consider calldata
* costs or potentially different intrinsic gas costs for different chains.
*/
get intrinsicGas(): BigNumber {
return BigNumber.from(BASE_INTRINSIC_GAS);
}
}

@ -1,21 +1,35 @@
import { utils } from '@abacus-network/utils';
import { expect } from 'chai';
import { BigNumber, ethers, FixedNumber } from 'ethers';
import { BigNumber, ethers } from 'ethers';
import sinon from 'sinon';
import { utils } from '@abacus-network/utils';
import {
AbacusCore,
InterchainGasCalculator,
MultiProvider,
ParsedMessage
ParsedMessage,
domains,
resolveDomain,
} from '../..';
import { domains } from '../../src/domains';
import { MockProvider, MockTokenPriceGetter } from '../utils';
const HANDLE_GAS = 100_000;
const SUGGESTED_GAS_PRICE = 10;
const CHECKPOINT_RELAY_GAS = 100_000;
const INBOX_PROCESS_OVERHEAD_GAS = 100_000;
describe('InterchainGasCalculator', () => {
const provider = new MockProvider();
const multiProvider = new MultiProvider({
// TODO: fix types to not require the <any> here.
// This is because InterchainGasCalculator isn't very strongly typed,
// which is because ParsedMessage isn't very strongly typed. This results
// in InterchainGasCalculator expecting a multiprovider with providers for
// every network.
const multiProvider = new MultiProvider<any>({
test1: { provider },
test2: { provider },
test3: { provider }
test3: { provider },
});
const core = AbacusCore.fromEnvironment('test', multiProvider);
const originDomain = domains.test1.id;
@ -30,50 +44,66 @@ describe('InterchainGasCalculator', () => {
tokenPriceGetter.setTokenPrice(originDomain, 10);
// Destination domain token
tokenPriceGetter.setTokenPrice(destinationDomain, 5);
calculator = new InterchainGasCalculator(
multiProvider as any, // TODO: fix types
core as any,
{
tokenPriceGetter,
},
);
calculator = new InterchainGasCalculator(multiProvider, core, {
tokenPriceGetter,
// A multiplier of 1 makes testing easier to reason about
paymentEstimateMultiplier: '1',
});
});
afterEach(() => {
sinon.restore();
provider.clearMethodResolveValues();
});
describe('estimatePaymentForGasAmount', () => {
describe('estimatePaymentForHandleGasAmount', () => {
it('estimates origin token payment from a specified destination gas amount', async () => {
const destinationGas = BigNumber.from(100_000);
// Set destination gas price to 10 wei
provider.setMethodResolveValue('getGasPrice', BigNumber.from(10));
// Set paymentEstimateMultiplier to 1 just to test easily
calculator.paymentEstimateMultiplier = FixedNumber.from(1);
const estimatedPayment = await calculator.estimatePaymentForGasAmount(
originDomain,
destinationDomain,
destinationGas,
provider.setMethodResolveValue(
'getGasPrice',
BigNumber.from(SUGGESTED_GAS_PRICE),
);
// 100_000 dest gas * 10 gas price * ($5 per origin token / $10 per origin token)
expect(estimatedPayment.toNumber()).to.equal(500_000);
// Stub the checkpoint relay gas cost
sinon
.stub(calculator, 'checkpointRelayGas')
.returns(Promise.resolve(BigNumber.from(CHECKPOINT_RELAY_GAS)));
// Stub the inbox process overhead gas
sinon
.stub(calculator, 'inboxProcessOverheadGas')
.returns(Promise.resolve(BigNumber.from(INBOX_PROCESS_OVERHEAD_GAS)));
const estimatedPayment =
await calculator.estimatePaymentForHandleGasAmount(
originDomain,
destinationDomain,
BigNumber.from(HANDLE_GAS),
);
// (100_000 dest handler gas + 100_000 checkpoint relay gas + 100_000 process overhead gas)
// * 10 gas price * ($5 per origin token / $10 per origin token)
expect(estimatedPayment.toNumber()).to.equal(1_500_000);
});
});
describe('estimatePaymentForMessage', () => {
it('estimates origin token payment from a specified message', async () => {
// Set the estimated destination gas
const estimatedDestinationGas = 100_000;
calculator.estimateGasForMessage = () =>
Promise.resolve(BigNumber.from(estimatedDestinationGas));
// Set the estimated handle gas
sinon
.stub(calculator, 'estimateHandleGasForMessage')
.returns(Promise.resolve(BigNumber.from(HANDLE_GAS)));
// Set destination gas price to 10 wei
calculator.suggestedGasPrice = (_) => Promise.resolve(BigNumber.from(10));
// Set paymentEstimateMultiplier to 1 just to test easily
calculator.paymentEstimateMultiplier = FixedNumber.from(1);
sinon
.stub(calculator, 'suggestedGasPrice')
.returns(Promise.resolve(BigNumber.from(SUGGESTED_GAS_PRICE)));
// Stub the checkpoint relay gas cost
sinon
.stub(calculator, 'checkpointRelayGas')
.returns(Promise.resolve(BigNumber.from(CHECKPOINT_RELAY_GAS)));
// Stub the inbox process overhead gas
sinon
.stub(calculator, 'inboxProcessOverheadGas')
.returns(Promise.resolve(BigNumber.from(INBOX_PROCESS_OVERHEAD_GAS)));
const zeroAddressBytes32 = utils.addressToBytes32(
ethers.constants.AddressZero,
@ -90,14 +120,16 @@ describe('InterchainGasCalculator', () => {
message,
);
// 100_000 dest gas * 10 gas price * ($5 per origin token / $10 per origin token)
expect(estimatedPayment.toNumber()).to.equal(500_000);
// (100_000 dest handler gas + 100_000 checkpoint relay gas + 100_000 process overhead gas)
// * 10 gas price * ($5 per origin token / $10 per origin token)
expect(estimatedPayment.toNumber()).to.equal(1_500_000);
});
});
describe('convertBetweenNativeTokens', () => {
const destinationWei = BigNumber.from('1000');
it('converts using the USD value of origin and destination native tokens', async () => {
const destinationWei = BigNumber.from('1000');
const originWei = await calculator.convertBetweenNativeTokens(
destinationDomain,
originDomain,
@ -115,7 +147,6 @@ describe('InterchainGasCalculator', () => {
return 18;
};
const destinationWei = BigNumber.from('1000');
const originWei = await calculator.convertBetweenNativeTokens(
destinationDomain,
originDomain,
@ -126,14 +157,15 @@ describe('InterchainGasCalculator', () => {
});
it('considers when the origin token decimals < the destination token decimals', async () => {
calculator.nativeTokenDecimals = (domain: number) => {
if (domain === originDomain) {
return 16;
}
return 18;
};
sinon
.stub(calculator, 'nativeTokenDecimals')
.callsFake((domain: number) => {
if (domain === originDomain) {
return 16;
}
return 18;
});
const destinationWei = BigNumber.from('1000');
const originWei = await calculator.convertBetweenNativeTokens(
destinationDomain,
originDomain,
@ -146,12 +178,66 @@ describe('InterchainGasCalculator', () => {
describe('suggestedGasPrice', () => {
it('gets the gas price from the provider', async () => {
const gasPrice = 1000;
provider.setMethodResolveValue('getGasPrice', BigNumber.from(gasPrice));
provider.setMethodResolveValue(
'getGasPrice',
BigNumber.from(SUGGESTED_GAS_PRICE),
);
expect(
(await calculator.suggestedGasPrice(destinationDomain)).toNumber(),
).to.equal(gasPrice);
).to.equal(SUGGESTED_GAS_PRICE);
});
});
describe('checkpointRelayGas', () => {
let threshold: number;
// Mock the return value of InboxValidatorManager.threshold
// to return `threshold`. Because the mocking involves a closure,
// changing `threshold` will change the return value of InboxValidatorManager.threshold.
before(() => {
const getContractsStub = sinon.stub(core, 'getContracts');
let thresholdStub: sinon.SinonStub | undefined;
getContractsStub.callsFake((domain) => {
// Get the "real" return value of getContracts.
const contracts = getContractsStub.wrappedMethod.bind(core)(domain);
// Ethers contracts are frozen using Object.freeze, so we make a copy
// of the object so we can stub `threshold`.
const validatorManager = Object.assign(
{},
// @ts-ignore - TODO more strongly type InterchainGasCalculator
contracts.inboxes[resolveDomain(originDomain)].validatorManager,
);
// Because we are stubbing vaidatorManager.threshold when core.getContracts gets called,
// we must ensure we don't try to stub more than once or sinon will complain.
if (!thresholdStub) {
thresholdStub = sinon
.stub(validatorManager, 'threshold')
.callsFake(() => Promise.resolve(BigNumber.from(threshold)));
// @ts-ignore - TODO more strongly type InterchainGasCalculator
contracts.inboxes[resolveDomain(originDomain)].validatorManager =
validatorManager;
}
return contracts;
});
});
it('scales the gas cost with the quorum threshold', async () => {
threshold = 2;
const gasWithThresholdLow = await calculator.checkpointRelayGas(
originDomain,
destinationDomain,
);
threshold = 3;
const gasWithThresholdHigh = await calculator.checkpointRelayGas(
originDomain,
destinationDomain,
);
expect(gasWithThresholdHigh.gt(gasWithThresholdLow)).to.be.true;
});
});
});

@ -1,4 +1,5 @@
import { ethers, FixedNumber } from 'ethers';
import { FixedNumber, ethers } from 'ethers';
import { NameOrDomain } from '../src';
import { resolveId } from '../src/core/message';

@ -201,6 +201,7 @@ __metadata:
fs: 0.0.1-security
mocha: ^9.2.2
prettier: ^2.4.1
sinon: ^13.0.2
typescript: ^4.4.3
languageName: unknown
linkType: soft
@ -3249,6 +3250,42 @@ __metadata:
languageName: node
linkType: hard
"@sinonjs/commons@npm:^1.6.0, @sinonjs/commons@npm:^1.7.0, @sinonjs/commons@npm:^1.8.3":
version: 1.8.3
resolution: "@sinonjs/commons@npm:1.8.3"
dependencies:
type-detect: 4.0.8
checksum: 6159726db5ce6bf9f2297f8427f7ca5b3dff45b31e5cee23496f1fa6ef0bb4eab878b23fb2c5e6446381f6a66aba4968ef2fc255c1180d753d4b8c271636a2e5
languageName: node
linkType: hard
"@sinonjs/fake-timers@npm:>=5, @sinonjs/fake-timers@npm:^9.1.2":
version: 9.1.2
resolution: "@sinonjs/fake-timers@npm:9.1.2"
dependencies:
"@sinonjs/commons": ^1.7.0
checksum: 7d3aef54e17c1073101cb64d953157c19d62a40e261a30923fa1ee337b049c5f29cc47b1f0c477880f42b5659848ba9ab897607ac8ea4acd5c30ddcfac57fca6
languageName: node
linkType: hard
"@sinonjs/samsam@npm:^6.1.1":
version: 6.1.1
resolution: "@sinonjs/samsam@npm:6.1.1"
dependencies:
"@sinonjs/commons": ^1.6.0
lodash.get: ^4.4.2
type-detect: ^4.0.8
checksum: a09b0914bf573f0da82bd03c64ba413df81a7c173818dc3f0a90c2652240ac835ef583f4d52f0b215e626633c91a4095c255e0669f6ead97241319f34f05e7fc
languageName: node
linkType: hard
"@sinonjs/text-encoding@npm:^0.7.1":
version: 0.7.1
resolution: "@sinonjs/text-encoding@npm:0.7.1"
checksum: 130de0bb568c5f8a611ec21d1a4e3f80ab0c5ec333010f49cfc1adc5cba6d8808699c8a587a46b0f0b016a1f4c1389bc96141e773e8460fcbb441875b2e91ba7
languageName: node
linkType: hard
"@solidity-parser/parser@npm:^0.14.0, @solidity-parser/parser@npm:^0.14.1":
version: 0.14.1
resolution: "@solidity-parser/parser@npm:0.14.1"
@ -6525,7 +6562,7 @@ __metadata:
languageName: node
linkType: hard
"diff@npm:5.0.0":
"diff@npm:5.0.0, diff@npm:^5.0.0":
version: 5.0.0
resolution: "diff@npm:5.0.0"
checksum: f19fe29284b633afdb2725c2a8bb7d25761ea54d321d8e67987ac851c5294be4afeab532bd84531e02583a3fe7f4014aa314a3eda84f5590e7a9e6b371ef3b46
@ -10174,6 +10211,13 @@ __metadata:
languageName: node
linkType: hard
"just-extend@npm:^4.0.2":
version: 4.2.1
resolution: "just-extend@npm:4.2.1"
checksum: ff9fdede240fad313efeeeb68a660b942e5586d99c0058064c78884894a2690dc09bba44c994ad4e077e45d913fef01a9240c14a72c657b53687ac58de53b39c
languageName: node
linkType: hard
"keccak@npm:3.0.1":
version: 3.0.1
resolution: "keccak@npm:3.0.1"
@ -10580,6 +10624,13 @@ __metadata:
languageName: node
linkType: hard
"lodash.get@npm:^4.4.2":
version: 4.4.2
resolution: "lodash.get@npm:4.4.2"
checksum: e403047ddb03181c9d0e92df9556570e2b67e0f0a930fcbbbd779370972368f5568e914f913e93f3b08f6d492abc71e14d4e9b7a18916c31fa04bd2306efe545
languageName: node
linkType: hard
"lodash.merge@npm:^4.6.2":
version: 4.6.2
resolution: "lodash.merge@npm:4.6.2"
@ -11452,6 +11503,19 @@ __metadata:
languageName: node
linkType: hard
"nise@npm:^5.1.1":
version: 5.1.1
resolution: "nise@npm:5.1.1"
dependencies:
"@sinonjs/commons": ^1.8.3
"@sinonjs/fake-timers": ">=5"
"@sinonjs/text-encoding": ^0.7.1
just-extend: ^4.0.2
path-to-regexp: ^1.7.0
checksum: d8be29e84a014743c9a10f428fac86f294ac5f92bed1f606fe9b551e935f494d8e0ce1af8a12673c6014010ec7f771f2d48aa5c8e116f223eb4f40c5e1ab44b3
languageName: node
linkType: hard
"node-addon-api@npm:^2.0.0":
version: 2.0.2
resolution: "node-addon-api@npm:2.0.2"
@ -12141,6 +12205,15 @@ __metadata:
languageName: node
linkType: hard
"path-to-regexp@npm:^1.7.0":
version: 1.8.0
resolution: "path-to-regexp@npm:1.8.0"
dependencies:
isarray: 0.0.1
checksum: 709f6f083c0552514ef4780cb2e7e4cf49b0cc89a97439f2b7cc69a608982b7690fb5d1720a7473a59806508fc2dae0be751ba49f495ecf89fd8fbc62abccbcd
languageName: node
linkType: hard
"path-type@npm:^1.0.0":
version: 1.1.0
resolution: "path-type@npm:1.1.0"
@ -13503,6 +13576,20 @@ __metadata:
languageName: node
linkType: hard
"sinon@npm:^13.0.2":
version: 13.0.2
resolution: "sinon@npm:13.0.2"
dependencies:
"@sinonjs/commons": ^1.8.3
"@sinonjs/fake-timers": ^9.1.2
"@sinonjs/samsam": ^6.1.1
diff: ^5.0.0
nise: ^5.1.1
supports-color: ^7.2.0
checksum: 237f21c8c4a8b31574c71b1b9f4c0f74a63dde5c0e86bd116effa4ce63c52467bd45fb4034a8fa32656a7919d9b19fc7b108ca9e1e6e3144f3735da96dad2877
languageName: node
linkType: hard
"slash@npm:^1.0.0":
version: 1.0.0
resolution: "slash@npm:1.0.0"
@ -14149,7 +14236,7 @@ __metadata:
languageName: node
linkType: hard
"supports-color@npm:^7.1.0":
"supports-color@npm:^7.1.0, supports-color@npm:^7.2.0":
version: 7.2.0
resolution: "supports-color@npm:7.2.0"
dependencies:
@ -14629,7 +14716,7 @@ __metadata:
languageName: node
linkType: hard
"type-detect@npm:^4.0.0, type-detect@npm:^4.0.5":
"type-detect@npm:4.0.8, type-detect@npm:^4.0.0, type-detect@npm:^4.0.5, type-detect@npm:^4.0.8":
version: 4.0.8
resolution: "type-detect@npm:4.0.8"
checksum: 62b5628bff67c0eb0b66afa371bd73e230399a8d2ad30d852716efcc4656a7516904570cd8631a49a3ce57c10225adf5d0cbdcb47f6b0255fe6557c453925a15

Loading…
Cancel
Save