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 getFlattenedKeys = (depth: number, value: any): string[] => { const getStorageLayout = async (name: string): Promise<any> => {
if (depth === 0) { const artifact: any = await readArtifact(bre.config.paths.artifacts, name)
return [] return artifact.storageLayout
} }
let keys = Object.keys(value) export const getModifiableStorageFactory = async (
if (depth > 1) { name: string
keys = keys.concat(getFlattenedKeys(depth - 1, Object.values(value)[0])) ): Promise<ContractFactory> => {
} const contractFactory = await ethers.getContractFactory(name)
const proxyFactory = await ethers.getContractFactory(
'Helper_ModifiableStorage'
)
return keys const originalDeployFn = contractFactory.deploy.bind(contractFactory)
} contractFactory.deploy = async (...args: any[]): Promise<Contract> => {
const originalDefinePropertyFn = Object.defineProperty
Object.defineProperty = (obj: any, pname: string, prop: any): void => {
if (prop.writable === false) {
prop.writable = true
}
const toHexString32 = ( originalDefinePropertyFn(obj, pname, prop)
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[] => { const contract: any = await originalDeployFn(...args)
if (depth > 0) { const proxy = await proxyFactory.deploy(contract.address)
return getFlattenedValues(depth - 1, Object.values(value)[0])
}
if (typeof value === 'object' && value !== null) { Object.defineProperty = originalDefinePropertyFn
return Object.keys(value).map((key) => {
return {
label: key,
value: toHexString32(value[key]),
}
})
} else {
return [
{
label: 'default',
value: toHexString32(value),
},
]
}
}
const getStorageSlotHash = ( contract.address = proxy.address
slot: number, contract.resolvedAddress = proxy.address
depth: number,
value: any contract.__setStorageSlot = proxy.__setStorageSlot.bind(proxy)
): string => { contract.__getStorageSlot = proxy.__getStorageSlot.bind(proxy)
let keys = []
if (typeof value === 'object' && value !== null) { contract.__setContractStorage = async (obj: any) => {
keys = getFlattenedKeys(depth, value) await setContractStorage(contract, await getStorageLayout(name), obj)
}
contract.__checkContractStorage = async (obj: any) => {
await checkContractStorage(contract, await getStorageLayout(name), obj)
}
return contract
} }
if (keys.length === 0) { return contractFactory
return defaultAbiCoder.encode(['uint256'], [slot]) }
} else {
let slotHash = toHexString32(slot) const flattenObject = (
for (const key of keys) { obj: Object,
slotHash = keccak256(toHexString32(key) + remove0x(slotHash)) 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 slotHash 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 {
throw new Error('Cannot flatten unsupported object type.')
} }
} }
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) const toHexString32 = (
contractFactory.deploy = async (...args: any[]): Promise<Contract> => { value: string | number | BigNumber | boolean
const originalDefinePropertyFn = Object.defineProperty ): string => {
Object.defineProperty = ( if (typeof value === 'string') {
object: any, return '0x' + remove0x(value).padStart(64, '0').toLowerCase()
propName: string, } else if (typeof value === 'boolean') {
props: any return toHexString32(value ? 1 : 0)
): void => { } else {
if (props.writable === false) { return toHexString32(BigNumber.from(value).toHexString())
props.writable = true }
} }
originalDefinePropertyFn(object, propName, props) const getStorageSlots = (storageLayout: any, obj: any): StorageSlot[] => {
} const slots: StorageSlot[] = []
const flat = flattenObject(obj)
const contract = await originalDeploy(...args) for (const key of Object.keys(flat)) {
const proxy = await proxyFactory.deploy(contract.address) const path = key.split('.')
;(contract as any).address = proxy.address const variableLabel = path[0]
;(contract as any).resolvedAddress = proxy.address
;(contract as any).__setStorageSlot = proxy.__setStorageSlot.bind(proxy) const variableDef = storageLayout.storage.find((vDef: any) => {
;(contract as any).__getStorageSlot = proxy.__getStorageSlot.bind(proxy) return vDef.label === variableLabel
;(contract as any).__setContractStorage = async (value: any) => { })
await setContractStorage(
contract, if (!variableDef) {
((await readArtifact(bre.config.paths.artifacts, name)) as any) throw new Error(
.storageLayout, `Could not find a matching variable definition for ${variableLabel}`
value
) )
} }
;(contract as any).__checkContractStorage = async (value: any) => {
await checkContractStorage( const baseSlot = parseInt(variableDef.slot, 10)
contract, const baseDepth = (variableDef.type.match(/t_mapping/g) || []).length
((await readArtifact(bre.config.paths.artifacts, name)) as any) const slotLabel =
.storageLayout, path.length > 1 + baseDepth ? path[path.length - 1] : 'default'
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}`
) )
} }
Object.defineProperty = originalDefinePropertyFn let slotHash = toHexString32(baseSlot)
return contract 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 contractFactory return slots
} }
export const setContractStorage = 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)) {
const layoutMap = layout.storage.find((lmap: any) => {
return lmap.label === key
})
const inputSlots = parseInputSlots(layout, layoutMap.type)
const slot = parseInt(layoutMap.slot, 10) if (!obj) {
let depth = (layoutMap.type.match(/t_mapping/g) || []).length return
}
if (typeof value !== 'object') { const slots = getStorageSlots(layout, obj)
const slotHash = getStorageSlotHash(slot, depth, value) for (const slot of slots) {
await contract.__setStorageSlot(slotHash, toHexString32(value as string)) contract.__setStorageSlot(slot.hash, slot.value)
} 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 slotIndex = inputSlots.find((inputSlot) => {
return inputSlot.label === slotValue.label
}).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 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 ( const checkContractStorage = 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 slots = getStorageSlots(layout, obj)
const slotHash = getStorageSlotHash(slot, depth, value) for (const slot of slots) {
const retSlotValue = await contract.__getStorageSlot(slotHash) const value = contract.__getStorageSlot(slot.hash)
if (retSlotValue !== toHexString32(value as string)) { if (value !== slot.value) {
throw new Error( throw new Error(
`Resulting state of ${key} (${retSlotValue}) did not match expected state (${toHexString32( `Resulting state of ${slot.label} (${value}) did not match expected state (${slot.value}).`
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 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}).`
)
}
}
}
}
} 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)
if (retSlotValue !== slotValue.value) {
throw new Error(
`Resulting state of ${slotValue.label} (${retSlotValue}) did not match expected state (${slotValue.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