Commit 82390b3a authored by Kelvin Fichter's avatar Kelvin Fichter

Linting and storage mod cleanup

parent a7760482
node_modules/ node_modules/
artifacts/ artifacts/
cache/ cache/
yarn-error.log
\ No newline at end of file
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
"build": "yarn run build:contracts", "build": "yarn run build:contracts",
"build:contracts": "buidler compile", "build:contracts": "buidler compile",
"test": "yarn run test:contracts", "test": "yarn run test:contracts",
"test:contracts": "buidler test \"test/contracts/OVM/execution/OVM_ExecutionManager/context-opcodes.spec.ts\"", "test:contracts": "buidler test",
"lint": "tslint --format stylish --project .", "lint": "tslint --format stylish --project .",
"fix": "prettier --config prettier-config.json --write \"buidler.config.ts\" \"{src,test}/**/*.ts\"" "fix": "prettier --config prettier-config.json --write \"buidler.config.ts\" \"{src,test}/**/*.ts\""
}, },
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
"@nomiclabs/buidler-ethers": "^2.0.0", "@nomiclabs/buidler-ethers": "^2.0.0",
"@nomiclabs/buidler-waffle": "^2.0.0", "@nomiclabs/buidler-waffle": "^2.0.0",
"@types/chai": "^4.2.12", "@types/chai": "^4.2.12",
"@types/lodash": "^4.14.161",
"@types/mocha": "^8.0.3", "@types/mocha": "^8.0.3",
"@types/node": "^14.6.0", "@types/node": "^14.6.0",
"assert": "^2.0.0", "assert": "^2.0.0",
......
...@@ -4,13 +4,10 @@ import { ...@@ -4,13 +4,10 @@ import {
TestDefinition, TestDefinition,
OVM_TX_GAS_LIMIT, OVM_TX_GAS_LIMIT,
NON_NULL_BYTES32, NON_NULL_BYTES32,
REVERT_FLAGS,
ZERO_ADDRESS, ZERO_ADDRESS,
VERIFIED_EMPTY_CONTRACT_HASH, VERIFIED_EMPTY_CONTRACT_HASH,
} from '../../../../helpers' } from '../../../../helpers'
const DUMMY_REVERT_DATA =
'0xdeadbeef1e5420deadbeef1e5420deadbeef1e5420deadbeef1e5420deadbeef1e5420'
const GAS_METADATA_ADDRESS = '0x06a506a506a506a506a506a506a506a506a506a5' const GAS_METADATA_ADDRESS = '0x06a506a506a506a506a506a506a506a506a506a5'
enum GasMetadataKey { enum GasMetadataKey {
......
...@@ -5,7 +5,7 @@ import { ethers } from '@nomiclabs/buidler' ...@@ -5,7 +5,7 @@ import { ethers } from '@nomiclabs/buidler'
import { Contract } from 'ethers' import { Contract } from 'ethers'
/* Internal Imports */ /* Internal Imports */
import { SAFETY_CHECKER_TEST_JSON } from '../../../helpers' import { SAFETY_CHECKER_TEST_JSON } from '../../../data'
describe('OVM_SafetyChecker', () => { describe('OVM_SafetyChecker', () => {
let OVM_SafetyChecker: Contract let OVM_SafetyChecker: Contract
......
/* External Imports */
import { ethers } from '@nomiclabs/buidler' import { ethers } from '@nomiclabs/buidler'
import { hexZeroPad } from 'ethers/lib/utils'
export const encodeRevertData = ( export const encodeRevertData = (
flag: number, flag: number,
...@@ -15,8 +15,6 @@ export const encodeRevertData = ( ...@@ -15,8 +15,6 @@ export const encodeRevertData = (
} }
export const decodeRevertData = (revertData: string): any => { export const decodeRevertData = (revertData: string): any => {
// const length: number = revertData.length/2 - 1
// const reencodedRevertData = '0x' + hexZeroPad('0x' + length.toString(16), 32) + revertData.slice(2)
const decoded = ethers.utils.defaultAbiCoder.decode( const decoded = ethers.utils.defaultAbiCoder.decode(
['uint256', 'uint256', 'uint256', 'bytes'], ['uint256', 'uint256', 'uint256', 'bytes'],
revertData revertData
......
...@@ -3,7 +3,7 @@ import { ethers } from 'ethers' ...@@ -3,7 +3,7 @@ import { ethers } from 'ethers'
import { defaultAccounts } from 'ethereum-waffle' import { defaultAccounts } from 'ethereum-waffle'
/* Internal Imports */ /* Internal Imports */
import { makeHexString, makeAddress } from './byte-utils' import { makeHexString, makeAddress } from './utils'
export const DEFAULT_ACCOUNTS = defaultAccounts export const DEFAULT_ACCOUNTS = defaultAccounts
export const DEFAULT_ACCOUNTS_BUIDLER = defaultAccounts.map((account) => { export const DEFAULT_ACCOUNTS_BUIDLER = defaultAccounts.map((account) => {
......
/* Internal Imports */ /* Internal Imports */
import { DUMMY_BYTES32 } from './bytes32' import { DUMMY_BYTES32 } from './bytes32'
import { ZERO_ADDRESS, NON_ZERO_ADDRESS } from '../constants' import { ZERO_ADDRESS, NON_ZERO_ADDRESS } from '../constants'
import { makeAddress } from '../byte-utils' import { makeAddress } from '../utils'
import { OVMAccount } from '../types/ovm-types' import { OVMAccount } from '../types/ovm-types'
export const DUMMY_ACCOUNTS: Array<{ export const DUMMY_ACCOUNTS: Array<{
......
...@@ -3,8 +3,8 @@ export * from './types' ...@@ -3,8 +3,8 @@ export * from './types'
export * from './constants' export * from './constants'
export * from './proxy' export * from './proxy'
export * from './mocks' export * from './mocks'
export * from './buffer-utils' export * from './utils'
export * from './byte-utils'
export * from './codec' export * from './codec'
export * from './data' export * from './test-runner'
export * from './test-utils' export * from './storage'
export * from './solidity'
...@@ -3,7 +3,7 @@ import bre from '@nomiclabs/buidler' ...@@ -3,7 +3,7 @@ import bre from '@nomiclabs/buidler'
import { ethers, Contract, ContractFactory } from 'ethers' import { ethers, Contract, ContractFactory } from 'ethers'
/* Internal Imports */ /* Internal Imports */
import { toHexString, fromHexString } from '../buffer-utils' import { toHexString, fromHexString } from '../utils'
import { MockContract, MockContractFunction } from './mock-contract.types' import { MockContract, MockContractFunction } from './mock-contract.types'
/** /**
......
...@@ -6,7 +6,7 @@ import { FunctionFragment, ParamType } from 'ethers/lib/utils' ...@@ -6,7 +6,7 @@ import { FunctionFragment, ParamType } from 'ethers/lib/utils'
/* Internal Imports */ /* Internal Imports */
import { MockContract, MockContractFunction } from './mock-contract.types' import { MockContract, MockContractFunction } from './mock-contract.types'
import { bindMockContractToVM, bindMockWatcherToVM } from './mock-binding' import { bindMockContractToVM, bindMockWatcherToVM } from './mock-binding'
import { SolidityCompiler, getDefaultCompiler, compile } from '../compilation' import { SolidityCompiler, getDefaultCompiler, compile } from '../solidity'
/** /**
* Generates contract code for a mock contract. * Generates contract code for a mock contract.
......
/* External Imports */
import bre, { ethers } from '@nomiclabs/buidler' import bre, { ethers } from '@nomiclabs/buidler'
import { readArtifact } from '@nomiclabs/buidler/internal/artifacts'
import { Contract, BigNumber, ContractFactory } from 'ethers' import { Contract, BigNumber, ContractFactory } from 'ethers'
import { keccak256, defaultAbiCoder } from 'ethers/lib/utils' import { keccak256 } from 'ethers/lib/utils'
import _ from 'lodash'
import { remove0x } from '../byte-utils' /* Internal Imports */
import { readArtifact } from '@nomiclabs/buidler/internal/artifacts' import { remove0x } from '../utils'
const getStorageLayout = async (name: string): Promise<any> => {
const artifact: any = await readArtifact(bre.config.paths.artifacts, name)
return artifact.storageLayout
}
export const getModifiableStorageFactory = async (
name: string
): Promise<ContractFactory> => {
const contractFactory = await ethers.getContractFactory(name)
const proxyFactory = await ethers.getContractFactory(
'Helper_ModifiableStorage'
)
const getFlattenedKeys = (depth: number, value: any): string[] => { const originalDeployFn = contractFactory.deploy.bind(contractFactory)
if (depth === 0) { contractFactory.deploy = async (...args: any[]): Promise<Contract> => {
return [] const originalDefinePropertyFn = Object.defineProperty
Object.defineProperty = (obj: any, pname: string, prop: any): void => {
if (prop.writable === false) {
prop.writable = true
} }
let keys = Object.keys(value) originalDefinePropertyFn(obj, pname, prop)
if (depth > 1) {
keys = keys.concat(getFlattenedKeys(depth - 1, Object.values(value)[0]))
} }
return keys const contract: any = await originalDeployFn(...args)
} const proxy = await proxyFactory.deploy(contract.address)
const toHexString32 = ( Object.defineProperty = originalDefinePropertyFn
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[] => { contract.address = proxy.address
if (depth > 0) { contract.resolvedAddress = proxy.address
return getFlattenedValues(depth - 1, Object.values(value)[0])
}
if (typeof value === 'object' && value !== null) { contract.__setStorageSlot = proxy.__setStorageSlot.bind(proxy)
return Object.keys(value).map((key) => { contract.__getStorageSlot = proxy.__getStorageSlot.bind(proxy)
return {
label: key, contract.__setContractStorage = async (obj: any) => {
value: toHexString32(value[key]), await setContractStorage(contract, await getStorageLayout(name), obj)
} }
})
} else { contract.__checkContractStorage = async (obj: any) => {
return [ await checkContractStorage(contract, await getStorageLayout(name), obj)
{
label: 'default',
value: toHexString32(value),
},
]
} }
}
const getStorageSlotHash = ( return contract
slot: number,
depth: number,
value: any
): string => {
let keys = []
if (typeof value === 'object' && value !== null) {
keys = getFlattenedKeys(depth, value)
} }
if (keys.length === 0) { return contractFactory
return defaultAbiCoder.encode(['uint256'], [slot]) }
const flattenObject = (
obj: Object,
prefix: string = '',
res: Object = {}
): Object => {
if (_.isString(obj) || _.isNumber(obj) || _.isBoolean(obj)) {
res[prefix] = obj
return res
} else if (_.isArray(obj)) {
for (let i = 0; i < obj.length; i++) {
const pre = _.isEmpty(prefix) ? `${i}` : `${prefix}.${i}`
flattenObject(obj[i], pre, res)
}
return res
} else if (_.isPlainObject(obj)) {
for (const key of Object.keys(obj)) {
const pre = _.isEmpty(prefix) ? key : `${prefix}.${key}`
flattenObject(obj[key], pre, res)
}
return res
} else { } else {
let slotHash = toHexString32(slot) throw new Error('Cannot flatten unsupported object type.')
for (const key of keys) {
slotHash = keccak256(toHexString32(key) + remove0x(slotHash))
}
return slotHash
} }
} }
const parseInputSlots = (layout: any, inputTypeName: string): any[] => { interface InputSlot {
const inputType = layout.types[inputTypeName] label: string
slot: number
}
const getInputSlots = (
storageLayout: any,
inputTypeName: string
): InputSlot[] => {
const inputType = storageLayout.types[inputTypeName]
if (inputType.encoding === 'mapping') { if (inputType.encoding === 'mapping') {
return parseInputSlots(layout, inputType.value) return getInputSlots(storageLayout, inputType.value)
} else if (inputType.encoding === 'inplace') { } else if (inputType.encoding === 'inplace') {
if (inputType.members) { if (inputType.members) {
return inputType.members.map((member: any) => { return inputType.members.map((member: any) => {
...@@ -99,217 +116,112 @@ const parseInputSlots = (layout: any, inputTypeName: string): any[] => { ...@@ -99,217 +116,112 @@ const parseInputSlots = (layout: any, inputTypeName: string): any[] => {
} }
} }
export const getModifiableStorageFactory = async ( interface StorageSlot {
name: string label: string
): Promise<ContractFactory> => { hash: string
const contractFactory = await ethers.getContractFactory(name) value: string
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,
propName: string,
props: any
): void => {
if (props.writable === false) {
props.writable = true
}
originalDefinePropertyFn(object, propName, 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 const toHexString32 = (
return contract 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())
} }
return contractFactory
} }
export const setContractStorage = async ( const getStorageSlots = (storageLayout: any, obj: any): StorageSlot[] => {
contract: Contract, const slots: StorageSlot[] = []
layout: any, const flat = flattenObject(obj)
storage: any
): Promise<void> => {
storage = storage || {}
for (const [key, value] of Object.entries(storage)) { for (const key of Object.keys(flat)) {
const layoutMap = layout.storage.find((lmap: any) => { const path = key.split('.')
return lmap.label === key const variableLabel = path[0]
})
const inputSlots = parseInputSlots(layout, layoutMap.type)
const slot = parseInt(layoutMap.slot, 10)
let depth = (layoutMap.type.match(/t_mapping/g) || []).length
if (typeof value !== 'object') { const variableDef = storageLayout.storage.find((vDef: any) => {
const slotHash = getStorageSlotHash(slot, depth, value) return vDef.label === variableLabel
await contract.__setStorageSlot(slotHash, toHexString32(value as string))
} else {
if (key === 'contractStorage' || key === 'verifiedContractStorage') {
for (const [subKey1, subValue1] of Object.entries(value)) {
for (const [subKey, subValue] of Object.entries(subValue1)) {
const baseSlotHash = getStorageSlotHash(slot, depth, {
[subKey1]: {
[subKey]: subValue,
},
})
const slotValues = getFlattenedValues(depth, {
[subKey1]: {
[subKey]: subValue,
},
}) })
for (const slotValue of slotValues) { if (!variableDef) {
const slotIndex = inputSlots.find((inputSlot) => { throw new Error(
return inputSlot.label === slotValue.label `Could not find a matching variable definition for ${variableLabel}`
}).slot
const slotHash = toHexString32(
BigNumber.from(baseSlotHash).add(slotIndex)
) )
await contract.__setStorageSlot(slotHash, slotValue.value)
}
} }
}
} else {
for (const [subKey, subValue] of Object.entries(value)) {
const baseSlotHash = getStorageSlotHash(slot, depth, {
[subKey]: subValue,
})
const slotValues = getFlattenedValues(depth, {
[subKey]: subValue,
})
for (const slotValue of slotValues) { const baseSlot = parseInt(variableDef.slot, 10)
const slotIndex = inputSlots.find((inputSlot) => { const baseDepth = (variableDef.type.match(/t_mapping/g) || []).length
return inputSlot.label === slotValue.label const slotLabel =
}).slot path.length > 1 + baseDepth ? path[path.length - 1] : 'default'
const slotHash = toHexString32(
BigNumber.from(baseSlotHash).add(slotIndex)
)
await contract.__setStorageSlot(slotHash, slotValue.value) const inputSlot = getInputSlots(storageLayout, variableDef.type).find(
} (iSlot) => {
return iSlot.label === slotLabel
} }
)
if (!inputSlot) {
throw new Error(
`Could not find a matching slot definition for ${slotLabel}`
)
} }
let slotHash = toHexString32(baseSlot)
for (let i = 0; i < baseDepth; i++) {
slotHash = keccak256(toHexString32(path[i + 1]) + remove0x(slotHash))
} }
slotHash = toHexString32(BigNumber.from(slotHash).add(inputSlot.slot))
slots.push({
label: key,
hash: slotHash,
value: toHexString32(flat[key]),
})
} }
return slots
} }
export const checkContractStorage = async ( const setContractStorage = async (
contract: Contract, contract: Contract,
layout: any, layout: any,
storage: any obj: any
): Promise<void> => { ): Promise<void> => {
storage = storage || {} obj = obj || {}
for (const [key, value] of Object.entries(storage)) { if (!obj) {
const layoutMap = layout.storage.find((lmap: any) => { return
return lmap.label === key
})
const inputSlots = parseInputSlots(layout, layoutMap.type)
const slot = parseInt(layoutMap.slot, 10)
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 {
if (key === 'contractStorage' || key === 'verifiedContractStorage') {
for (const [subKey1, subValue1] of Object.entries(value)) {
for (const [subKey, subValue] of Object.entries(subValue1)) {
const baseSlotHash = getStorageSlotHash(slot, depth, {
[subKey1]: {
[subKey]: subValue,
},
})
const slotValues = getFlattenedValues(depth, {
[subKey1]: {
[subKey]: subValue,
},
})
for (const slotValue of slotValues) { const slots = getStorageSlots(layout, obj)
const slotIndex = inputSlots.find((inputSlot) => { for (const slot of slots) {
return inputSlot.label === slotValue.label contract.__setStorageSlot(slot.hash, slot.value)
}).slot }
const slotHash = toHexString32( }
BigNumber.from(baseSlotHash).add(slotIndex)
)
const retSlotValue = await contract.__getStorageSlot(slotHash) const checkContractStorage = async (
contract: Contract,
layout: any,
obj: any
): Promise<void> => {
obj = obj || {}
if (retSlotValue !== slotValue.value) { if (!obj) {
throw new Error( return
`Resulting state of ${slotValue.label} (${retSlotValue}) did not match expected state (${slotValue.value}).`
)
} }
}
}
}
} else {
for (const [subKey, subValue] of Object.entries(value)) {
const baseSlotHash = getStorageSlotHash(slot, depth, {
[subKey]: subValue,
})
const slotValues = getFlattenedValues(depth, {
[subKey]: subValue,
})
for (const slotValue of slotValues) {
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) const slots = getStorageSlots(layout, obj)
for (const slot of slots) {
const value = contract.__getStorageSlot(slot.hash)
if (retSlotValue !== slotValue.value) { if (value !== slot.value) {
throw new Error( throw new Error(
`Resulting state of ${slotValue.label} (${retSlotValue}) did not match expected state (${slotValue.value}).` `Resulting state of ${slot.label} (${value}) did not match expected state (${slot.value}).`
) )
} }
} }
}
}
}
}
} }
export * from './contract-storage'
...@@ -28,7 +28,7 @@ import { ...@@ -28,7 +28,7 @@ import {
isTestStep_REVERT, isTestStep_REVERT,
} from './test.types' } from './test.types'
import { encodeRevertData } from '../codec' import { encodeRevertData } from '../codec'
import { getModifiableStorageFactory } from '../storage/contract-storage' import { getModifiableStorageFactory } from '../storage'
import { import {
OVM_TX_GAS_LIMIT, OVM_TX_GAS_LIMIT,
RUN_OVM_TEST_GAS, RUN_OVM_TEST_GAS,
...@@ -226,10 +226,10 @@ export class ExecutionManagerTestRunner { ...@@ -226,10 +226,10 @@ export class ExecutionManagerTestRunner {
await this.contracts.OVM_ExecutionManager.run( await this.contracts.OVM_ExecutionManager.run(
{ {
timestamp: step.functionParams.timestamp, timestamp: step.functionParams.timestamp,
queueOrigin: step.functionParams.queueOrigin, number: 0,
l1QueueOrigin: step.functionParams.queueOrigin,
l1Txorigin: step.functionParams.origin,
entrypoint: step.functionParams.entrypoint, entrypoint: step.functionParams.entrypoint,
origin: step.functionParams.origin,
msgSender: step.functionParams.msgSender,
gasLimit: step.functionParams.gasLimit, gasLimit: step.functionParams.gasLimit,
data: calldata, data: calldata,
}, },
......
...@@ -6,6 +6,11 @@ export interface OVMAccount { ...@@ -6,6 +6,11 @@ export interface OVMAccount {
ethAddress: string ethAddress: string
} }
/**
* Converts a raw ethers result to an OVM account.
* @param result Raw ethers transaction result.
* @returns Converted OVM account.
*/
export const toOVMAccount = (result: any[]): OVMAccount => { export const toOVMAccount = (result: any[]): OVMAccount => {
return { return {
nonce: result[0].toNumber(), nonce: result[0].toNumber(),
......
/**
* Converts a string or buffer to a '0x'-prefixed hex string.
* @param buf String or buffer to convert.
* @returns '0x'-prefixed string.
*/
export const toHexString = (buf: Buffer | string): string => { export const toHexString = (buf: Buffer | string): string => {
return '0x' + fromHexString(buf).toString('hex') return '0x' + fromHexString(buf).toString('hex')
} }
/**
* Converts a '0x'-prefixed string to a buffer.
* @param str '0x'-prefixed string to convert.
* @returns Hex buffer.
*/
export const fromHexString = (str: string | Buffer): Buffer => { export const fromHexString = (str: string | Buffer): Buffer => {
if (typeof str === 'string' && str.startsWith('0x')) { if (typeof str === 'string' && str.startsWith('0x')) {
return Buffer.from(str.slice(2), 'hex') return Buffer.from(str.slice(2), 'hex')
......
/**
* Generates a hex string of repeated bytes.
* @param byte Byte to repeat.
* @param len Number of times to repeat the byte.
* @return '0x'-prefixed hex string filled with the provided byte.
*/
export const makeHexString = (byte: string, len: number): string => { export const makeHexString = (byte: string, len: number): string => {
return '0x' + byte.repeat(len) return '0x' + byte.repeat(len)
} }
/**
* Genereates an address with a repeated byte.
* @param byte Byte to repeat in the address.
* @return Address filled with the repeated byte.
*/
export const makeAddress = (byte: string): string => { export const makeAddress = (byte: string): string => {
return makeHexString(byte, 20) return makeHexString(byte, 20)
} }
/**
* Removes '0x' from a hex string.
* @param str Hex string to remove '0x' from.
* @returns String without the '0x' prefix.
*/
export const remove0x = (str: string): string => { export const remove0x = (str: string): string => {
if (str.startsWith('0x')) { if (str.startsWith('0x')) {
return str.slice(2) return str.slice(2)
......
export * from './buffer-utils'
export * from './byte-utils'
...@@ -655,6 +655,11 @@ ...@@ -655,6 +655,11 @@
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==
"@types/lodash@^4.14.161":
version "4.14.161"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.161.tgz#a21ca0777dabc6e4f44f3d07f37b765f54188b18"
integrity sha512-EP6O3Jkr7bXvZZSZYlsgt5DIjiGr0dXP1/jVEwVLTFgg0d+3lWVQkRavYVQszV7dYUwvg0B8R0MBDpcmXg7XIA==
"@types/lru-cache@^5.1.0": "@types/lru-cache@^5.1.0":
version "5.1.0" version "5.1.0"
resolved "https://registry.yarnpkg.com/@types/lru-cache/-/lru-cache-5.1.0.tgz#57f228f2b80c046b4a1bd5cac031f81f207f4f03" resolved "https://registry.yarnpkg.com/@types/lru-cache/-/lru-cache-5.1.0.tgz#57f228f2b80c046b4a1bd5cac031f81f207f4f03"
......
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