Commit 5c97a885 authored by Kelvin Fichter's avatar Kelvin Fichter

Added test parsing

parent 954e87cd
......@@ -8,6 +8,8 @@ import {
usePlugin('@nomiclabs/buidler-ethers')
usePlugin('@nomiclabs/buidler-waffle')
import './test/helpers/buidler/modify-compiler'
const config: BuidlerConfig = {
networks: {
buidlerevm: {
......
......@@ -693,12 +693,16 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
// existence.
(bool success, bytes memory returndata) = _target.call{gas: _gasLimit}(_data);
// Assuming there were no reverts, the message record should be accurate here. We'll update
// this value in the case of a revert.
uint256 nuisanceGasLeft = messageRecord.nuisanceGasLeft;
// Reverts at this point are completely OK, but we need to make a few updates based on the
// information passed through the revert.
if (success == false) {
(
RevertFlag flag,
uint256 nuisanceGasLeft,
uint256 nuisanceGasLeftPostRevert,
uint256 ovmGasRefund,
) = _decodeRevertData(returndata);
......@@ -718,15 +722,18 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
) {
transactionRecord.ovmGasRefund = ovmGasRefund;
}
// Reverts mean we need to use up whatever "nuisance gas" was used by the call.
// EXCEEDS_NUISANCE_GAS explicitly reduces the remaining nuisance gas for this message
// to zero. OUT_OF_GAS is a "pseudo" flag given that messages return no data when they
// run out of gas, so we have to treat this like EXCEEDS_NUISANCE_GAS. All other flags
// will simply pass up the remaining nuisance gas.
messageRecord.nuisanceGasLeft = prevNuisanceGasLeft - (nuisanceGasLimit - nuisanceGasLeft);
nuisanceGasLeft = nuisanceGasLeftPostRevert;
}
// We need to reset the nuisance gas back to its original value minus the amount used here.
messageRecord.nuisanceGasLeft = prevNuisanceGasLeft - (nuisanceGasLimit - nuisanceGasLeft);
// Switch back to the original message context now that we're out of the call.
_switchMessageContext(_nextMessageContext, prevMessageContext);
......@@ -1110,6 +1117,11 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
messageContext.ovmCALLER = _nextMessageContext.ovmCALLER;
}
// Avoid unnecessary the SSTORE.
if (_prevMessageContext.ovmADDRESS != _nextMessageContext.ovmADDRESS) {
messageContext.ovmADDRESS = _nextMessageContext.ovmADDRESS;
}
// Avoid unnecessary the SSTORE.
if (_prevMessageContext.isStatic != _nextMessageContext.isStatic) {
messageContext.isStatic = _nextMessageContext.isStatic;
......
pragma solidity >=0.7.0;
pragma experimental ABIEncoderV2;
struct MessageSteps {
bytes[] callsToEM;
bool shouldRevert;
}
struct EMResponse {
bool success;
bytes data;
}
contract Helper_CodeContractForCalls {
function runSteps(
MessageSteps calldata _stepsToRun
) external returns(EMResponse[] memory) {
uint numSteps = _stepsToRun.callsToEM.length;
EMResponse[] memory EMResponses = new EMResponse[](numSteps);
for (uint i = 0; i < numSteps; i++) {
bytes memory dataToSend = _stepsToRun.callsToEM[i];
(bool success, bytes memory responseData) = address(msg.sender).call(dataToSend);
EMResponses[i].success = success;
EMResponses[i].data = responseData;
}
return EMResponses; // TODO: revert with this data in case of !shouldRevert
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
contract Helper_ModifiableStorage {
address private target;
constructor(
address _target
) {
target = _target;
}
fallback()
external
{
(bool success, bytes memory returndata) = target.delegatecall(msg.data);
if (success) {
assembly {
return(add(returndata, 0x20), mload(returndata))
}
} else {
assembly {
revert(add(returndata, 0x20), mload(returndata))
}
}
}
function __setStorageSlot(
bytes32 _key,
bytes32 _value
)
public
{
assembly {
sstore(_key, _value)
}
}
function __getStorageSlot(
bytes32 _key
)
public
view
returns (
bytes32 _value
)
{
bytes32 value;
assembly {
value := sload(_key)
}
return value;
}
}
......@@ -7,7 +7,7 @@
"build": "yarn run build:contracts",
"build:contracts": "buidler compile",
"test": "yarn run test:contracts",
"test:contracts": "buidler test \"test/contracts/OVM/execution/OVM_ExecutionManager/opcodes/OVM_ExecutionManager.opcodes.calling.spec.ts\""
"test:contracts": "buidler test \"test/contracts/OVM/execution/OVM_test.spec.ts\""
},
"devDependencies": {
"@nomiclabs/buidler": "^1.4.4",
......@@ -20,6 +20,7 @@
"chai": "^4.2.0",
"ethereum-waffle": "3.0.0",
"ethers": "5.0.0",
"fs-extra": "^9.0.1",
"mocha": "^8.1.1",
"ts-node": "^9.0.0",
"typescript": "^4.0.2"
......
import { expect } from '../../../setup'
/* External Imports */
import bre, { ethers } from '@nomiclabs/buidler'
import { Contract } from 'ethers'
/* Internal Imports */
import { getModifiableStorageFactory } from '../../../helpers/storage/contract-storage'
import { runExecutionManagerTest } from '../../../helpers/test-parsing/parse-tests'
import { NON_NULL_BYTES32, GAS_LIMIT } from '../../../helpers'
const getCodeSize = async (address: string): Promise<number> => {
const code = await ethers.provider.getCode(address)
return (code.length - 2) / 2
}
describe('OVM_StateManager', () => {
let OVM_ExecutionManager: Contract
let OVM_StateManager: Contract
let Helper_CodeContractForCalls: Contract
before(async () => {
const Factory__OVM_ExecutionManager = await getModifiableStorageFactory(
'OVM_ExecutionManager'
)
const Factory__OVM_StateManager = await getModifiableStorageFactory(
'OVM_StateManager'
)
const Factory__Helper_CodeContractForCalls = await getModifiableStorageFactory(
'Helper_CodeContractForCalls'
)
OVM_ExecutionManager = await Factory__OVM_ExecutionManager.deploy(
'0x5a0b54d5dc17e0aadc383d2db43b0a0d3e029c4c'
)
OVM_StateManager = await Factory__OVM_StateManager.deploy()
Helper_CodeContractForCalls = await Factory__Helper_CodeContractForCalls.deploy()
})
const DUMMY_OVM_ADDRESS_1 = '0x' + '12'.repeat(20)
const DUMMY_OVM_ADDRESS_2 = '0x' + '21'.repeat(20)
describe('the test suite', () => {
it('does the test suite', async () => {
runExecutionManagerTest(
{
name: 'Top level test',
preState: {
ExecutionManager: {
ovmStateManager: OVM_StateManager.address,
messageRecord: {
nuisanceGasLeft: GAS_LIMIT / 2
}
},
StateManager: {
accounts: {
[DUMMY_OVM_ADDRESS_1]: {
codeHash: NON_NULL_BYTES32,
ethAddress: Helper_CodeContractForCalls.address
},
[DUMMY_OVM_ADDRESS_2]: {
codeHash: NON_NULL_BYTES32,
ethAddress: Helper_CodeContractForCalls.address
}
}
}
},
postState: {
ExecutionManager: {
messageRecord: {
nuisanceGasLeft: GAS_LIMIT / 2 - (await getCodeSize(Helper_CodeContractForCalls.address)) * 100 * 2
}
}
},
parameters: [
{
name: 'Do an ovmCALL',
parameters: [
{
steps: [
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT / 2,
DUMMY_OVM_ADDRESS_1,
[
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT / 2,
DUMMY_OVM_ADDRESS_2,
[
{
functionName: 'ovmADDRESS',
functionParams: [],
returnStatus: true,
returnValues: [DUMMY_OVM_ADDRESS_2]
},
{
functionName: 'ovmCALLER',
functionParams: [],
returnStatus: true,
returnValues: [DUMMY_OVM_ADDRESS_1]
}
]
],
returnStatus: true,
returnValues: []
},
]
],
returnStatus: true,
returnValues: []
}
]
}
]
}
]
},
OVM_ExecutionManager,
OVM_StateManager
)
})
})
})
/* External Imports */
import fsExtra from 'fs-extra'
import { internalTask } from '@nomiclabs/buidler/config'
import { pluralize } from '@nomiclabs/buidler/internal/util/strings'
import { getArtifactFromContractOutput, saveArtifact } from '@nomiclabs/buidler/internal/artifacts'
import {
TASK_COMPILE_GET_COMPILER_INPUT,
TASK_BUILD_ARTIFACTS,
TASK_COMPILE_GET_SOURCE_PATHS,
TASK_COMPILE_CHECK_CACHE,
TASK_COMPILE_COMPILE
} from '@nomiclabs/buidler/builtin-tasks/task-names'
internalTask(
TASK_COMPILE_GET_COMPILER_INPUT,
async (_, { config, run }, runSuper) => {
const input = await runSuper();
// Insert the "storageLayout" input option.
input.settings.outputSelection['*']['*'].push('storageLayout')
return input
}
)
internalTask(TASK_BUILD_ARTIFACTS).setAction(
async ({ force }, { config, run }) => {
const sources = await run(TASK_COMPILE_GET_SOURCE_PATHS);
if (sources.length === 0) {
console.log("No Solidity source file available.");
return;
}
const isCached: boolean = await run(TASK_COMPILE_CHECK_CACHE, { force });
if (isCached) {
console.log(
"All contracts have already been compiled, skipping compilation."
);
return;
}
const compilationOutput = await run(TASK_COMPILE_COMPILE);
if (compilationOutput === undefined) {
return;
}
await fsExtra.ensureDir(config.paths.artifacts);
let numberOfContracts = 0;
for (const file of Object.values<any>(compilationOutput.contracts)) {
for (const [contractName, contractOutput] of Object.entries(file)) {
const artifact: any = getArtifactFromContractOutput(
contractName,
contractOutput
);
numberOfContracts += 1;
// Only difference here, set the "storageLayout" field of the artifact.
artifact.storageLayout = (contractOutput as any).storageLayout
await saveArtifact(config.paths.artifacts, artifact);
}
}
console.log(
"Compiled",
numberOfContracts,
pluralize(numberOfContracts, "contract"),
"successfully"
);
}
)
......@@ -5,3 +5,11 @@ export const makeHexString = (byte: string, len: number): string => {
export const makeAddress = (byte: string): string => {
return makeHexString(byte, 20)
}
export const remove0x = (str: string): string => {
if (str.startsWith('0x')) {
return str.slice(2)
} else {
return str
}
}
import bre, { ethers } from '@nomiclabs/buidler'
import { Contract, BigNumber, ContractFactory } from "ethers";
import { keccak256, defaultAbiCoder } from "ethers/lib/utils";
import { remove0x } from '../byte-utils'
import { readArtifact } from '@nomiclabs/buidler/internal/artifacts';
const getFlattenedKeys = (
depth: number,
value: any,
): string[] => {
if (depth === 0) {
return []
}
let keys = Object.keys(value)
if (depth > 1) {
keys = keys.concat(
getFlattenedKeys(
depth - 1,
Object.values(value)[0]
)
)
}
return keys
}
const toHexString32 = (
value: string | number | BigNumber | boolean
): string => {
if (typeof value === 'string') {
return '0x' + remove0x(value).padStart(64, '0').toLowerCase()
} else if (typeof value === 'boolean') {
return toHexString32(value ? 1 : 0)
} else {
return toHexString32(BigNumber.from(value).toHexString())
}
}
const getFlattenedValues = (
depth: number,
value: any
): any[] => {
if (depth > 0) {
return getFlattenedValues(
depth - 1,
Object.values(value)[0]
)
}
if (typeof value === 'object' && value !== null) {
return Object.keys(value).map((key) => {
return {
label: key,
value: toHexString32(value[key])
}
})
} else {
return [{
label: "default",
value: toHexString32(value)
}]
}
}
const getStorageSlotHash = (
slot: number,
depth: number,
value: any
): string =>{
let keys = []
if (typeof value === 'object' && value !== null) {
keys = getFlattenedKeys(depth, value)
}
if (keys.length === 0) {
return defaultAbiCoder.encode(
['uint256'],
[slot]
)
} else {
let slotHash = toHexString32(slot)
for (const key of keys) {
slotHash = keccak256(
toHexString32(key) +
remove0x(slotHash)
)
}
return slotHash
}
}
const parseInputSlots = (
layout: any,
inputTypeName: string
): any[] => {
const inputType = layout.types[inputTypeName]
if (inputType.encoding === 'mapping') {
return parseInputSlots(layout, inputType.value)
} else if (inputType.encoding === 'inplace') {
if (inputType.members) {
return inputType.members.map((member: any) => {
return {
label: member.label,
slot: member.slot
}
})
} else {
return [{
label: "default",
slot: 0
}]
}
} else {
throw new Error('Encoding type not supported.')
}
}
export const getModifiableStorageFactory = async (
name: string
): Promise<ContractFactory> => {
const contractFactory = await ethers.getContractFactory(name)
const proxyFactory = await ethers.getContractFactory('Helper_ModifiableStorage')
const originalDeploy = contractFactory.deploy.bind(contractFactory)
contractFactory.deploy = async (...args: any[]): Promise<Contract> => {
const originalDefinePropertyFn = Object.defineProperty
Object.defineProperty = (object: any, name: string, props: any): void => {
if (props.writable === false) {
props.writable = true
}
originalDefinePropertyFn(object, name, props)
}
const contract = await originalDeploy(...args)
const proxy = await proxyFactory.deploy(contract.address)
;(contract as any).address = proxy.address
;(contract as any).resolvedAddress = proxy.address
;(contract as any).__setStorageSlot = proxy.__setStorageSlot.bind(proxy)
;(contract as any).__getStorageSlot = proxy.__getStorageSlot.bind(proxy)
;(contract as any).__setContractStorage = async (value: any) => {
await setContractStorage(
contract,
(await readArtifact(bre.config.paths.artifacts, name) as any).storageLayout,
value
)
}
;(contract as any).__checkContractStorage = async (value: any) => {
await checkContractStorage(
contract,
(await readArtifact(bre.config.paths.artifacts, name) as any).storageLayout,
value
)
}
Object.defineProperty = originalDefinePropertyFn
return contract
}
return contractFactory
}
export const setContractStorage = async (
contract: Contract,
layout: any,
storage: any
): Promise<void> => {
storage = storage || {}
for (const [key, value] of Object.entries(storage)) {
const layoutMap = layout.storage.find((layoutMap: any) => {
return layoutMap.label === key
})
const inputSlots = parseInputSlots(layout, layoutMap.type)
const slot = parseInt(layoutMap.slot)
const depth = (layoutMap.type.match(/t_mapping/g) || []).length
if (typeof value !== 'object') {
const slotHash = getStorageSlotHash(slot, depth, value)
await contract.__setStorageSlot(
slotHash,
toHexString32(value as string)
)
} else {
for (const [subKey, subValue] of Object.entries(value)) {
const baseSlotHash = getStorageSlotHash(slot, depth, {
[subKey]: subValue
})
const slotValues = getFlattenedValues(depth, {
[subKey]: subValue
})
for (let i = 0; i < slotValues.length; i++) {
const slotValue = slotValues[i]
const slotIndex = inputSlots.find((inputSlot) => {
return inputSlot.label === slotValue.label
}).slot
const slotHash = toHexString32(BigNumber.from(baseSlotHash).add(slotIndex))
await contract.__setStorageSlot(
slotHash,
slotValue.value
)
}
}
}
}
}
export const checkContractStorage = async (
contract: Contract,
layout: any,
storage: any
): Promise<void> => {
storage = storage || {}
for (const [key, value] of Object.entries(storage)) {
const layoutMap = layout.storage.find((layoutMap: any) => {
return layoutMap.label === key
})
const inputSlots = parseInputSlots(layout, layoutMap.type)
const slot = parseInt(layoutMap.slot)
const depth = (layoutMap.type.match(/t_mapping/g) || []).length
if (typeof value !== 'object') {
const slotHash = getStorageSlotHash(slot, depth, value)
const retSlotValue = await contract.__getStorageSlot(
slotHash
)
if (retSlotValue !== toHexString32(value as string)) {
throw new Error(`Resulting state of ${key} (${retSlotValue}) did not match expected state (${toHexString32(value as string)})`)
}
} else {
for (const [subKey, subValue] of Object.entries(value)) {
const baseSlotHash = getStorageSlotHash(slot, depth, {
[subKey]: subValue
})
const slotValues = getFlattenedValues(depth, {
[subKey]: subValue
})
for (let i = 0; i < slotValues.length; i++) {
const slotValue = slotValues[i]
const slotIndex = inputSlots.find((inputSlot) => {
return inputSlot.label === slotValue.label
}).slot
const slotHash = toHexString32(BigNumber.from(baseSlotHash).add(slotIndex))
const retSlotValue = await contract.__getStorageSlot(
slotHash
)
if (retSlotValue !== slotValue.value) {
throw new Error(`Resulting state of ${slotValue.label} (${retSlotValue}) did not match expected state (${slotValue.value}).`)
}
}
}
}
}
}
import * as path from 'path'
import { ethers } from "@nomiclabs/buidler"
import { ContractFactory } from 'ethers'
import { Interface } from 'ethers/lib/utils'
const getContractDefinition = (name: string): any => {
return require(path.join(__dirname, '../../../artifacts', `${name}.json`))
}
export const getContractInterface = (name: string): Interface => {
const definition = getContractDefinition(name)
return new ethers.utils.Interface(definition.abi)
}
// const ExecutionManager: ContractFactory = await ethers.getContractFactory('iOVM_ExecutionManager')
export const iEM = getContractInterface('iOVM_ExecutionManager')
// const CodeContract: ContractFactory = await ethers.getContractFactory('Helper_CodeContractForCalls')
const iCC = getContractInterface('Helper_CodeContractForCalls')
export interface TestCallGenerator {
generateCalldata(): string
generateExpectedReturnData(): string
parseActualReturnData(returned: string): any
}
export abstract class baseOVMCallTest implements TestCallGenerator {
executionManagerMethodName: string = 'EM METHOD NAME NOT SET'
arguments: any[] = []
expectedReturnValues: any[] = []
generateCalldata(): string {
return iEM.encodeFunctionData(
this.executionManagerMethodName,
this.arguments
)
}
generateExpectedReturnData(): string {
return iEM.encodeFunctionResult(
this.executionManagerMethodName,
this.expectedReturnValues
)
}
parseActualReturnData(returned: string) {
const decodedResult = iEM.decodeFunctionResult(
this.executionManagerMethodName,
returned
)
return 'call to ExeMgr.' + this.executionManagerMethodName + ' returned: \n' + decodedResult
}
}
export class ovmADDRESSTest extends baseOVMCallTest {
constructor(
expectedAddress: string
) {
super()
this.executionManagerMethodName = 'ovmADDRESS'
this.expectedReturnValues = [expectedAddress]
}
}
export class ovmCALLERTest extends baseOVMCallTest {
constructor(
expectedMsgSender: string
) {
super()
this.executionManagerMethodName = 'ovmCALLER'
this.expectedReturnValues = [expectedMsgSender]
}
}
const DEFAULT_GAS_LIMIT = 1_000_000
export class ovmCALLTest extends baseOVMCallTest {
constructor(
callee: string,
calleeTests: Array<TestCallGenerator>,
shouldCalleeSucceed: boolean = true,
gasLimit: number = DEFAULT_GAS_LIMIT
) {
super()
this.executionManagerMethodName = 'ovmCALL'
this.arguments = [
gasLimit,
callee,
iCC.encodeFunctionData(
'runSteps',
[{
callsToEM: calleeTests.map((testGenerator) => {
return testGenerator.generateCalldata()
}),
shouldRevert: !shouldCalleeSucceed
}]
)
]
this.expectedReturnValues = [
shouldCalleeSucceed,
iCC.encodeFunctionResult(
'runSteps',
[calleeTests.map((testGenerator) => {
return {
success: true, //TODO: figure out if we need this not to happen
data: testGenerator.generateExpectedReturnData()
}
})]
)
]
}
}
import { expect } from '../../setup'
import { Contract, BigNumber } from "ethers"
import { TestCallGenerator, ovmADDRESSTest, ovmCALLERTest, ovmCALLTest } from "./call-generator"
import { GAS_LIMIT } from '../constants'
type SolidityFunctionParameter = string | number | BigNumber
interface TestStep {
functionName: string
functionParams: Array<SolidityFunctionParameter | TestStep[]>
returnStatus: boolean
returnValues: any[]
}
interface TestParameters {
steps: TestStep[]
}
interface TestDefinition {
name: string
preState?: {
ExecutionManager?: any,
StateManager?: any
},
parameters: Array<TestParameters | TestDefinition>,
postState?: {
ExecutionManager?: any,
StateManager?: any
},
}
const isTestDefinition = (parameters: TestParameters | TestDefinition): parameters is TestDefinition => {
return (parameters as TestDefinition).name !== undefined
}
/*
const encodeTestStep = (
step: TestStep,
ovmExecutionManager: Contract,
helperCodeContract: Contract
): string => {
const params = step.functionParams.map((functionParam) => {
if (Array.isArray(functionParam)) {
return
}
})
return ovmExecutionManager.interface.encodeFunctionData(
step.functionName,
params
)
}
*/
const getCorrectGenerator = (step: TestStep): TestCallGenerator => {
switch (step.functionName) {
case 'ovmADDRESS':
return new ovmADDRESSTest(step.returnValues[0])
case 'ovmCALLER':
return new ovmCALLERTest(step.returnValues[0])
case 'ovmCALL':
return new ovmCALLTest(
step.functionParams[1] as string,
(step.functionParams[2] as TestStep[]).map((param) => {
return getCorrectGenerator(param)
}),
step.returnStatus,
step.functionParams[0] as number
)
default:
throw new Error('Input type not implemented.')
}
}
const getTestGenerator = (
step: TestStep
): TestCallGenerator => {
return getCorrectGenerator(step)
}
export const runExecutionManagerTest = (
test: TestDefinition,
ovmExecutionManager: Contract,
ovmStateManager: Contract
): void => {
test.preState = test.preState || {}
test.postState = test.postState || {}
describe(`Standard test: ${test.name}`, () => {
test.parameters.map((parameters) => {
if (isTestDefinition(parameters)) {
runExecutionManagerTest(
{
...parameters,
preState: {
...test.preState,
...parameters.preState
},
postState: {
...test.postState,
...parameters.postState
}
},
ovmExecutionManager,
ovmStateManager,
)
} else {
beforeEach(async () => {
await ovmExecutionManager.__setContractStorage(test.preState.ExecutionManager)
await ovmStateManager.__setContractStorage(test.preState.StateManager)
})
afterEach(async () => {
await ovmExecutionManager.__checkContractStorage({
...test.preState.ExecutionManager,
...test.postState.ExecutionManager
})
await ovmStateManager.__checkContractStorage({
...test.preState.StateManager,
...test.postState.StateManager
})
})
parameters.steps.map((step, idx) => {
it(`should run test: ${test.name} ${idx}`, async () => {
const testGenerator = getTestGenerator(step)
const callResult = await ovmExecutionManager.provider.call({
to: ovmExecutionManager.address,
data: testGenerator.generateCalldata(),
gasLimit: GAS_LIMIT
})
await ovmExecutionManager.signer.sendTransaction({
to: ovmExecutionManager.address,
data: testGenerator.generateCalldata(),
gasLimit: GAS_LIMIT
})
expect(callResult).to.equal(testGenerator.generateExpectedReturnData())
})
})
}
})
})
}
......@@ -1126,6 +1126,11 @@ asynckit@^0.4.0:
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
at-least-node@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2"
integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==
atob@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
......@@ -4065,6 +4070,16 @@ fs-extra@^7.0.1:
jsonfile "^4.0.0"
universalify "^0.1.0"
fs-extra@^9.0.1:
version "9.0.1"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc"
integrity sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==
dependencies:
at-least-node "^1.0.0"
graceful-fs "^4.2.0"
jsonfile "^6.0.1"
universalify "^1.0.0"
fs-minipass@^1.2.5:
version "1.2.7"
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7"
......@@ -4379,7 +4394,7 @@ got@^8.3.1:
url-parse-lax "^3.0.0"
url-to-options "^1.0.1"
graceful-fs@^4.0.0, graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9:
graceful-fs@^4.0.0, graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0:
version "4.2.4"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
......@@ -5261,6 +5276,15 @@ jsonfile@^4.0.0:
optionalDependencies:
graceful-fs "^4.1.6"
jsonfile@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.0.1.tgz#98966cba214378c8c84b82e085907b40bf614179"
integrity sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==
dependencies:
universalify "^1.0.0"
optionalDependencies:
graceful-fs "^4.1.6"
jsonify@~0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
......@@ -8211,6 +8235,11 @@ universalify@^0.1.0:
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
universalify@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d"
integrity sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==
unorm@^1.3.3:
version "1.6.0"
resolved "https://registry.yarnpkg.com/unorm/-/unorm-1.6.0.tgz#029b289661fba714f1a9af439eb51d9b16c205af"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment