Commit 8d8e72dd authored by Kelvin Fichter's avatar Kelvin Fichter

Force invalid state access check on SSTORE

parent 4a177ba1
...@@ -1216,6 +1216,12 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager { ...@@ -1216,6 +1216,12 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager {
) )
internal internal
{ {
// We need to make sure that the transaction isn't trying to access storage that hasn't
// been provided to the OVM_StateManager. We'll immediately abort if this is the case.
_checkInvalidStateAccess(
ovmStateManager.hasContractStorage(_contract, _key)
);
// Check whether the slot has been changed before and mark it as changed if not. We need // Check whether the slot has been changed before and mark it as changed if not. We need
// this because "nuisance gas" only applies to the first time that a slot is changed. // this because "nuisance gas" only applies to the first time that a slot is changed.
( (
......
...@@ -366,7 +366,14 @@ contract OVM_StateManager is iOVM_StateManager { ...@@ -366,7 +366,14 @@ contract OVM_StateManager is iOVM_StateManager {
authenticated authenticated
{ {
contractStorage[_contract][_key] = _value; contractStorage[_contract][_key] = _value;
verifiedContractStorage[_contract][_key] = true;
// Only used when initially populating the contract storage. OVM_ExecutionManager will
// perform a `hasContractStorage` INVALID_STATE_ACCESS check before putting any contract
// storage because writing to zero when the actual value is nonzero causes a gas
// discrepancy.
if (verifiedContractStorage[_contract][_key] == false) {
verifiedContractStorage[_contract][_key] = true;
}
} }
/** /**
......
...@@ -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/ovmDELEGATECALL.spec.ts\"", "test:contracts": "buidler test \"test/contracts/OVM/execution/OVM_ExecutionManager/run.spec.ts\"",
"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\""
}, },
......
/* Internal Imports */
import {
ExecutionManagerTestRunner,
TestDefinition,
GAS_LIMIT,
NON_NULL_BYTES32,
REVERT_FLAGS,
ZERO_ADDRESS,
VERIFIED_EMPTY_CONTRACT_HASH,
} from '../../../../helpers'
const DUMMY_REVERT_DATA =
'0xdeadbeef1e5420deadbeef1e5420deadbeef1e5420deadbeef1e5420deadbeef1e5420'
const GAS_METADATA_ADDRESS = '0x06a506a506a506a506a506a506a506a506a506a5'
enum GasMetadataKey {
CURRENT_EPOCH_START_TIMESTAMP,
CUMULATIVE_SEQUENCER_QUEUE_GAS,
CUMULATIVE_L1TOL2_QUEUE_GAS,
PREV_EPOCH_SEQUENCER_QUEUE_GAS,
PREV_EPOCH_L1TOL2_QUEUE_GAS
}
const keyToBytes32 = (key: GasMetadataKey): string => {
return '0x' + `0${key}`.padStart(64, '0')
}
const test_run: TestDefinition = {
name: 'Basic tests for ovmCALL',
preState: {
ExecutionManager: {
ovmStateManager: '$OVM_STATE_MANAGER',
ovmSafetyChecker: '$OVM_SAFETY_CHECKER',
messageRecord: {
nuisanceGasLeft: GAS_LIMIT,
},
},
StateManager: {
owner: '$OVM_EXECUTION_MANAGER',
accounts: {
$DUMMY_OVM_ADDRESS_1: {
codeHash: NON_NULL_BYTES32,
ethAddress: '$OVM_CALL_HELPER',
},
$DUMMY_OVM_ADDRESS_2: {
codeHash: NON_NULL_BYTES32,
ethAddress: '$OVM_CALL_HELPER',
},
$DUMMY_OVM_ADDRESS_3: {
codeHash: VERIFIED_EMPTY_CONTRACT_HASH,
ethAddress: '0x' + '00'.repeat(20),
},
},
contractStorage: {
[GAS_METADATA_ADDRESS]: {
[keyToBytes32(GasMetadataKey.CURRENT_EPOCH_START_TIMESTAMP)]: 1,
[keyToBytes32(GasMetadataKey.CUMULATIVE_SEQUENCER_QUEUE_GAS)]: 0,
[keyToBytes32(GasMetadataKey.CUMULATIVE_L1TOL2_QUEUE_GAS)]: 0,
[keyToBytes32(GasMetadataKey.PREV_EPOCH_SEQUENCER_QUEUE_GAS)]: 0,
[keyToBytes32(GasMetadataKey.PREV_EPOCH_L1TOL2_QUEUE_GAS)]: 0
}
},
verifiedContractStorage: {
[GAS_METADATA_ADDRESS]: {
[keyToBytes32(GasMetadataKey.CURRENT_EPOCH_START_TIMESTAMP)]: true,
[keyToBytes32(GasMetadataKey.CUMULATIVE_SEQUENCER_QUEUE_GAS)]: true,
[keyToBytes32(GasMetadataKey.CUMULATIVE_L1TOL2_QUEUE_GAS)]: true,
[keyToBytes32(GasMetadataKey.PREV_EPOCH_SEQUENCER_QUEUE_GAS)]: true,
[keyToBytes32(GasMetadataKey.PREV_EPOCH_L1TOL2_QUEUE_GAS)]: true,
}
}
},
},
parameters: [
{
name: 'run => ovmCALL(ADDRESS_1) => ovmADDRESS',
focus: true,
steps: [
{
functionName: 'run',
functionParams: {
timestamp: 0,
queueOrigin: 0,
entrypoint: '$OVM_CALL_HELPER',
origin: ZERO_ADDRESS,
msgSender: ZERO_ADDRESS,
gasLimit: GAS_LIMIT,
subSteps: [
{
functionName: 'ovmCALL',
functionParams: {
gasLimit: GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_1',
subSteps: [
{
functionName: 'ovmADDRESS',
expectedReturnValue: '$DUMMY_OVM_ADDRESS_1',
},
],
},
expectedReturnStatus: true,
},
]
}
}
],
},
]
}
const runner = new ExecutionManagerTestRunner()
runner.run(test_run)
...@@ -166,29 +166,57 @@ export const setContractStorage = async ( ...@@ -166,29 +166,57 @@ export const setContractStorage = async (
const inputSlots = parseInputSlots(layout, layoutMap.type) const inputSlots = parseInputSlots(layout, layoutMap.type)
const slot = parseInt(layoutMap.slot, 10) const slot = parseInt(layoutMap.slot, 10)
const depth = (layoutMap.type.match(/t_mapping/g) || []).length let depth = (layoutMap.type.match(/t_mapping/g) || []).length
if (typeof value !== 'object') { if (typeof value !== 'object') {
const slotHash = getStorageSlotHash(slot, depth, value) const slotHash = getStorageSlotHash(slot, depth, value)
await contract.__setStorageSlot(slotHash, toHexString32(value as string)) await contract.__setStorageSlot(slotHash, toHexString32(value as string))
} else { } else {
for (const [subKey, subValue] of Object.entries(value)) { if (key === 'contractStorage' || key === 'verifiedContractStorage') {
const baseSlotHash = getStorageSlotHash(slot, depth, { for (const [subKey1, subValue1] of Object.entries(value)) {
[subKey]: subValue, for (const [subKey, subValue] of Object.entries(subValue1)) {
}) const baseSlotHash = getStorageSlotHash(slot, depth, {
const slotValues = getFlattenedValues(depth, { [subKey1]: {
[subKey]: subValue, [subKey]: subValue,
}) }
})
for (const slotValue of slotValues) { const slotValues = getFlattenedValues(depth, {
const slotIndex = inputSlots.find((inputSlot) => { [subKey1]: {
return inputSlot.label === slotValue.label [subKey]: subValue,
}).slot }
const slotHash = toHexString32( })
BigNumber.from(baseSlotHash).add(slotIndex)
) for (const slotValue of slotValues) {
const slotIndex = inputSlots.find((inputSlot) => {
await contract.__setStorageSlot(slotHash, slotValue.value) 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)
}
} }
} }
} }
...@@ -223,28 +251,62 @@ export const checkContractStorage = async ( ...@@ -223,28 +251,62 @@ export const checkContractStorage = async (
) )
} }
} else { } else {
for (const [subKey, subValue] of Object.entries(value)) { if (key === 'contractStorage' || key === 'verifiedContractStorage') {
const baseSlotHash = getStorageSlotHash(slot, depth, { for (const [subKey1, subValue1] of Object.entries(value)) {
[subKey]: subValue, for (const [subKey, subValue] of Object.entries(subValue1)) {
}) const baseSlotHash = getStorageSlotHash(slot, depth, {
const slotValues = getFlattenedValues(depth, { [subKey1]: {
[subKey]: subValue, [subKey]: subValue,
}) }
})
for (const slotValue of slotValues) { const slotValues = getFlattenedValues(depth, {
const slotIndex = inputSlots.find((inputSlot) => { [subKey1]: {
return inputSlot.label === slotValue.label [subKey]: subValue,
}).slot }
const slotHash = toHexString32( })
BigNumber.from(baseSlotHash).add(slotIndex)
) for (const slotValue of slotValues) {
const slotIndex = inputSlots.find((inputSlot) => {
const retSlotValue = await contract.__getStorageSlot(slotHash) return inputSlot.label === slotValue.label
}).slot
if (retSlotValue !== slotValue.value) { const slotHash = toHexString32(
throw new Error( BigNumber.from(baseSlotHash).add(slotIndex)
`Resulting state of ${slotValue.label} (${retSlotValue}) did not match expected state (${slotValue.value}).` )
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}).`
)
}
} }
} }
} }
......
...@@ -8,17 +8,20 @@ import { cloneDeep } from 'lodash' ...@@ -8,17 +8,20 @@ import { cloneDeep } from 'lodash'
/* Internal Imports */ /* Internal Imports */
import { import {
TestDefinition, TestDefinition,
ParsedTestStep,
TestParameter,
TestStep, TestStep,
TestStep_CALL,
TestStep_Run,
isRevertFlagError,
isTestStep_SSTORE, isTestStep_SSTORE,
isTestStep_SLOAD, isTestStep_SLOAD,
isTestStep_CALL, isTestStep_CALL,
isTestStep_CREATE, isTestStep_CREATE,
isTestStep_CREATE2, isTestStep_CREATE2,
isTestStep_Context, isTestStep_Context,
ParsedTestStep,
isRevertFlagError,
TestParameter,
isTestStep_evm, isTestStep_evm,
isTestStep_Run,
isTestStep_EXTCODESIZE, isTestStep_EXTCODESIZE,
isTestStep_EXTCODEHASH, isTestStep_EXTCODEHASH,
isTestStep_EXTCODECOPY, isTestStep_EXTCODECOPY,
...@@ -190,15 +193,47 @@ export class ExecutionManagerTestRunner { ...@@ -190,15 +193,47 @@ export class ExecutionManagerTestRunner {
return ret return ret
} }
private async runTestStep(step: TestStep) { private async runTestStep(step: TestStep | TestStep_Run) {
await this.contracts.OVM_ExecutionManager.ovmCALL( if (isTestStep_Run(step)) {
GAS_LIMIT / 2, let calldata: string
this.contracts.Helper_TestRunner.address, if (step.functionParams.data) {
this.contracts.Helper_TestRunner.interface.encodeFunctionData( calldata = step.functionParams.data
'runSingleTestStep', } else {
[this.parseTestStep(step)] const runStep: TestStep_CALL = {
functionName: 'ovmCALL',
functionParams: {
gasLimit: GAS_LIMIT,
target: this.contracts.Helper_TestRunner.address,
subSteps: step.functionParams.subSteps
},
expectedReturnStatus: true
}
calldata = this.encodeFunctionData(runStep)
}
await this.contracts.OVM_ExecutionManager.run(
{
timestamp: step.functionParams.timestamp,
queueOrigin: step.functionParams.queueOrigin,
entrypoint: step.functionParams.entrypoint,
origin: step.functionParams.origin,
msgSender: step.functionParams.msgSender,
gasLimit: step.functionParams.gasLimit,
data: calldata
},
this.contracts.OVM_StateManager.address
) )
) } else {
await this.contracts.OVM_ExecutionManager.ovmCALL(
GAS_LIMIT / 2,
this.contracts.Helper_TestRunner.address,
this.contracts.Helper_TestRunner.interface.encodeFunctionData(
'runSingleTestStep',
[this.parseTestStep(step)]
)
)
}
} }
private parseTestStep(step: TestStep): ParsedTestStep { private parseTestStep(step: TestStep): ParsedTestStep {
......
...@@ -83,7 +83,7 @@ interface TestStep_SLOAD { ...@@ -83,7 +83,7 @@ interface TestStep_SLOAD {
expectedReturnValue: string | RevertFlagError expectedReturnValue: string | RevertFlagError
} }
interface TestStep_CALL { export interface TestStep_CALL {
functionName: CallOpcode functionName: CallOpcode
functionParams: { functionParams: {
gasLimit: number | BigNumber gasLimit: number | BigNumber
...@@ -116,6 +116,20 @@ interface TestStep_CREATE2 { ...@@ -116,6 +116,20 @@ interface TestStep_CREATE2 {
expectedReturnValue: string | RevertFlagError expectedReturnValue: string | RevertFlagError
} }
export interface TestStep_Run {
functionName: 'run',
functionParams: {
timestamp: number
queueOrigin: number
entrypoint: string
origin: string
msgSender: string
gasLimit: number
data?: string
subSteps?: TestStep[]
}
}
export type TestStep = export type TestStep =
| TestStep_Context | TestStep_Context
| TestStep_SSTORE | TestStep_SSTORE
...@@ -164,33 +178,33 @@ export const isTestStep_Context = ( ...@@ -164,33 +178,33 @@ export const isTestStep_Context = (
} }
export const isTestStep_SSTORE = (step: TestStep): step is TestStep_SSTORE => { export const isTestStep_SSTORE = (step: TestStep): step is TestStep_SSTORE => {
return step.functionName == 'ovmSSTORE' return step.functionName === 'ovmSSTORE'
} }
export const isTestStep_SLOAD = (step: TestStep): step is TestStep_SLOAD => { export const isTestStep_SLOAD = (step: TestStep): step is TestStep_SLOAD => {
return step.functionName == 'ovmSLOAD' return step.functionName === 'ovmSLOAD'
} }
export const isTestStep_EXTCODESIZE = ( export const isTestStep_EXTCODESIZE = (
step: TestStep step: TestStep
): step is TestStep_EXTCODESIZE => { ): step is TestStep_EXTCODESIZE => {
return step.functionName == 'ovmEXTCODESIZE' return step.functionName === 'ovmEXTCODESIZE'
} }
export const isTestStep_EXTCODEHASH = ( export const isTestStep_EXTCODEHASH = (
step: TestStep step: TestStep
): step is TestStep_EXTCODEHASH => { ): step is TestStep_EXTCODEHASH => {
return step.functionName == 'ovmEXTCODEHASH' return step.functionName === 'ovmEXTCODEHASH'
} }
export const isTestStep_EXTCODECOPY = ( export const isTestStep_EXTCODECOPY = (
step: TestStep step: TestStep
): step is TestStep_EXTCODECOPY => { ): step is TestStep_EXTCODECOPY => {
return step.functionName == 'ovmEXTCODECOPY' return step.functionName === 'ovmEXTCODECOPY'
} }
export const isTestStep_REVERT = (step: TestStep): step is TestStep_REVERT => { export const isTestStep_REVERT = (step: TestStep): step is TestStep_REVERT => {
return step.functionName == 'ovmREVERT' return step.functionName === 'ovmREVERT'
} }
export const isTestStep_CALL = (step: TestStep): step is TestStep_CALL => { export const isTestStep_CALL = (step: TestStep): step is TestStep_CALL => {
...@@ -200,13 +214,19 @@ export const isTestStep_CALL = (step: TestStep): step is TestStep_CALL => { ...@@ -200,13 +214,19 @@ export const isTestStep_CALL = (step: TestStep): step is TestStep_CALL => {
} }
export const isTestStep_CREATE = (step: TestStep): step is TestStep_CREATE => { export const isTestStep_CREATE = (step: TestStep): step is TestStep_CREATE => {
return step.functionName == 'ovmCREATE' return step.functionName === 'ovmCREATE'
} }
export const isTestStep_CREATE2 = ( export const isTestStep_CREATE2 = (
step: TestStep step: TestStep
): step is TestStep_CREATE2 => { ): step is TestStep_CREATE2 => {
return step.functionName == 'ovmCREATE2' return step.functionName === 'ovmCREATE2'
}
export const isTestStep_Run = (
step: TestStep | TestStep_Run
): step is TestStep_Run => {
return step.functionName === 'run'
} }
interface TestState { interface TestState {
...@@ -216,7 +236,7 @@ interface TestState { ...@@ -216,7 +236,7 @@ interface TestState {
export interface TestParameter { export interface TestParameter {
name: string name: string
steps: TestStep[] steps: Array<TestStep | TestStep_Run>
expectInvalidStateAccess?: boolean expectInvalidStateAccess?: boolean
focus?: boolean focus?: boolean
} }
......
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