-
Notifications
You must be signed in to change notification settings - Fork 41
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: slot-overwrite bug #133
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,7 +36,9 @@ | |
"build": "rm -rf dist && tsc -p tsconfig.build.json", | ||
"compile": "hardhat compile", | ||
"test:nocompile": "cross-env mocha 'test/unit/**/*.spec.ts'", | ||
"mytest:nocompile": "cross-env mocha 'test/unit/mock/mytest.spec.ts'", | ||
"test": "yarn compile && yarn test:nocompile", | ||
"mytest": "yarn compile && yarn mytest:nocompile", | ||
"lint:check": "cross-env solhint 'contracts/**/*.sol' 'interfaces/**/*.sol' && cross-env prettier --check './**'", | ||
"lint:fix": "sort-package-json && cross-env prettier --write './**' && cross-env solhint --fix 'contracts/**/*.sol' 'interfaces/**/*.sol'", | ||
"docs:md": "pandoc README.rst -f rst -t markdown -o README.md", | ||
|
@@ -74,7 +76,6 @@ | |
"@typechain/ethers-v5": "7.0.1", | ||
"@typechain/hardhat": "2.0.2", | ||
"@types/chai": "4.2.18", | ||
"@types/chai-as-promised": "7.1.5", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why are we removing this import? |
||
"@types/diff": "^5.0.1", | ||
"@types/lodash": "4.14.170", | ||
"@types/lodash.isequal": "^4.5.5", | ||
|
@@ -83,7 +84,6 @@ | |
"@types/node": "15.12.2", | ||
"@types/semver": "^7.3.8", | ||
"chai": "4.3.4", | ||
"chai-as-promised": "7.1.1", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why? |
||
"cross-env": "7.0.3", | ||
"ethereum-waffle": "3.4.0", | ||
"ethers": "5.4.1", | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,14 +5,18 @@ import { FakeContractSpec } from '../types'; | |
export async function ethersInterfaceFromSpec(spec: FakeContractSpec): Promise<ethers.utils.Interface> { | ||
if (typeof spec === 'string') { | ||
try { | ||
if (isMaybeJsonObject(spec)) { | ||
return await ethersInterfaceFromAbi(spec); | ||
} else { | ||
return await ethersInterfaceFromContractName(spec); | ||
} | ||
} catch (err) { | ||
throw err; | ||
} | ||
return new ethers.utils.Interface(spec); | ||
} catch {} | ||
|
||
try { | ||
return (await (hre as any).ethers.getContractFactory(spec)).interface; | ||
} catch {} | ||
|
||
try { | ||
return (await (hre as any).ethers.getContractAt(spec, ethers.constants.AddressZero)).interface; | ||
} catch {} | ||
|
||
throw new Error(`unable to generate smock spec from string`); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i believe this should not be here, please make sure to pull |
||
} | ||
|
||
let foundInterface: any = spec; | ||
|
@@ -28,34 +32,3 @@ export async function ethersInterfaceFromSpec(spec: FakeContractSpec): Promise<e | |
return new ethers.utils.Interface(foundInterface); | ||
} | ||
} | ||
|
||
async function ethersInterfaceFromAbi(abi: string): Promise<ethers.utils.Interface> { | ||
try { | ||
return new ethers.utils.Interface(abi); | ||
} catch (err) { | ||
const error: Error = err as Error; | ||
throw new Error(`unable to generate smock spec from abi string.\n${error.message}`); | ||
} | ||
} | ||
|
||
async function ethersInterfaceFromContractName(contractNameOrFullyQualifiedName: string): Promise<ethers.utils.Interface> { | ||
let error: Error | null = null; | ||
try { | ||
return (await (hre as any).ethers.getContractFactory(contractNameOrFullyQualifiedName)).interface; | ||
} catch (err) { | ||
error = err as Error; | ||
} | ||
|
||
try { | ||
return (await (hre as any).ethers.getContractAt(contractNameOrFullyQualifiedName, ethers.constants.AddressZero)).interface; | ||
} catch (err) { | ||
error = err as Error; | ||
} | ||
|
||
throw new Error(`unable to generate smock spec from contract name.\n${error.message}`); | ||
} | ||
|
||
function isMaybeJsonObject(str: string): boolean { | ||
let strJson = str.trim(); | ||
return strJson.charAt(0) == '{' && strJson.charAt(strJson.length - 1) == '}'; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
import { SetVariablesType, SmockVMManager } from '../types'; | ||
import { fromHexString, toFancyAddress } from '../utils'; | ||
import { fromHexString, remove0x, toFancyAddress, toHexString } from '../utils'; | ||
import { computeStorageSlots, SolidityStorageLayout } from '../utils/storage'; | ||
|
||
export class EditableStorageLogic { | ||
|
@@ -18,8 +18,22 @@ export class EditableStorageLogic { | |
|
||
const slots = computeStorageSlots(this.storageLayout, { [variableName]: value }); | ||
|
||
// for (const slot of slots) { | ||
// await this.vmManager.putContractStorage(toFancyAddress(this.contractAddress), fromHexString(slot.key), fromHexString(slot.val)); | ||
// } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ??? |
||
|
||
for (const slot of slots) { | ||
await this.vmManager.putContractStorage(toFancyAddress(this.contractAddress), fromHexString(slot.key), fromHexString(slot.val)); | ||
let prevStorageValue = await this.vmManager.getContractStorage(toFancyAddress(this.contractAddress), fromHexString(slot.key)); | ||
let stringValue = remove0x(toHexString(prevStorageValue)); | ||
if (stringValue && (slot.type === 'address' || slot.type === 'bool' || slot.type.startsWith('contract'))) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hmmm interesting code here, is there any way of generalizing this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. only address and bool has same slot value |
||
// console.log('_______________________________', stringValue, slot.val); | ||
let lastResult = slot.val.slice(0, slot.val.length - stringValue.length).concat(stringValue); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how come this works if you are always slicing starting in index 0? What if the variable packed slot starts from the middle? |
||
await this.vmManager.putContractStorage(toFancyAddress(this.contractAddress), fromHexString(slot.key), fromHexString(lastResult)); | ||
// console.log('>>>>>>>>>>>>>>>>>>>>>>>', slot.key, lastResult); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove comments |
||
} else { | ||
await this.vmManager.putContractStorage(toFancyAddress(this.contractAddress), fromHexString(slot.key), fromHexString(slot.val)); | ||
// console.log('>>>>>>>>>>>>>>>>>>>>>>>', slot.key, slot.val); | ||
} | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,6 +40,7 @@ export interface SolidityStorageLayout { | |
interface StorageSlotPair { | ||
key: string; | ||
val: string; | ||
type: string; | ||
} | ||
|
||
/** | ||
|
@@ -67,6 +68,13 @@ export async function getStorageLayout(name: string): Promise<SolidityStorageLay | |
); | ||
} | ||
|
||
// let storageLayout = (output as any).storageLayout; | ||
// console.log('___ storage ___', (output as any).storageLayout); | ||
// for (let i = 0; i < storageLayout.storage.length; i++) { | ||
// storageLayout.storage[i].slot = i.toString(); | ||
// } | ||
|
||
// return storageLayout; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. rm |
||
return (output as any).storageLayout; | ||
} | ||
|
||
|
@@ -82,6 +90,7 @@ export function computeStorageSlots(storageLayout: SolidityStorageLayout, variab | |
let slots: StorageSlotPair[] = []; | ||
for (const [variableName, variableValue] of Object.entries(variables)) { | ||
// Find the entry in the storage layout that corresponds to this variable name. | ||
// console.log('___ variableName, variableValue ___', variableName, variableValue); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. rm rm rm |
||
const storageObj = storageLayout.storage.find((entry) => { | ||
return entry.label === variableName; | ||
}); | ||
|
@@ -91,10 +100,14 @@ export function computeStorageSlots(storageLayout: SolidityStorageLayout, variab | |
throw new Error(`Variable name not found in storage layout: ${variableName}`); | ||
} | ||
|
||
// console.log('___ storageObj ___', storageObj); | ||
// console.log('___ slots before ___', slots); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. rrrrrrrrrrrrmmmmmmmmm |
||
// Encode this variable as series of storage slot key/value pairs and save it. | ||
slots = slots.concat(encodeVariable(variableValue, storageObj, storageLayout.types)); | ||
// console.log('___ slots after ___', slots); | ||
} | ||
|
||
// console.log('___ 1 ___', slots); | ||
// Dealing with packed storage slots now. We know that a storage slot is packed when two storage | ||
// slots produced by the above encoding have the same key. In this case, we want to merge the two | ||
// values into a single bytes32 value. We'll throw an error if the two values overlap (have some | ||
|
@@ -139,6 +152,7 @@ export function computeStorageSlots(storageLayout: SolidityStorageLayout, variab | |
prevSlots.push({ | ||
key: slot.key, | ||
val: mergedVal, | ||
type: slot.type, | ||
}); | ||
} | ||
|
||
|
@@ -228,6 +242,7 @@ function encodeVariable( | |
{ | ||
key: slotKey, | ||
val: padNumHexSlotValue(variable, storageObj.offset), | ||
type: variableType.label, | ||
}, | ||
]; | ||
} else if (variableType.label === 'bool') { | ||
|
@@ -249,6 +264,7 @@ function encodeVariable( | |
{ | ||
key: slotKey, | ||
val: padNumHexSlotValue(variable ? '1' : '0', storageObj.offset), | ||
type: variableType.label, | ||
}, | ||
]; | ||
} else if (variableType.label.startsWith('bytes')) { | ||
|
@@ -260,6 +276,7 @@ function encodeVariable( | |
{ | ||
key: slotKey, | ||
val: padBytesHexSlotValue(remove0x(variable).padEnd(parseInt(variableType.numberOfBytes, 10) * 2, '0'), storageObj.offset), | ||
type: variableType.label, | ||
}, | ||
]; | ||
} else if (variableType.label.startsWith('uint') || variableType.label.startsWith('int')) { | ||
|
@@ -271,6 +288,7 @@ function encodeVariable( | |
{ | ||
key: slotKey, | ||
val: padNumHexSlotValue(variable, storageObj.offset), | ||
type: variableType.label, | ||
}, | ||
]; | ||
} else if (variableType.label.startsWith('struct')) { | ||
|
@@ -314,6 +332,7 @@ function encodeVariable( | |
ethers.BigNumber.from(bytes.length * 2).toHexString(), | ||
]) | ||
), | ||
type: variableType.label, | ||
}, | ||
]; | ||
} else { | ||
|
@@ -324,6 +343,7 @@ function encodeVariable( | |
slots = slots.concat({ | ||
key: slotKey, | ||
val: padNumHexSlotValue(bytes.length * 2 + 1, 0), | ||
type: variableType.label, | ||
}); | ||
|
||
// Each storage slot has 32 bytes so we make sure to slice the large bytes into 32bytes chunks | ||
|
@@ -335,6 +355,7 @@ function encodeVariable( | |
slots = slots.concat({ | ||
key: key, | ||
val: ethers.utils.hexlify(ethers.utils.concat([bytes.slice(i * 32, i * 32 + 32), ethers.constants.HashZero]).slice(0, 32)), | ||
type: variableType.label, | ||
}); | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.4; | ||
|
||
contract Bug { | ||
address public myAddress; | ||
bool public myBool; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please generalize to different cases |
This file was deleted.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,11 +3,10 @@ import { makeRandomAddress } from '@src/utils'; | |
import { Receiver, Receiver__factory, Returner } from '@typechained'; | ||
import receiverArtifact from 'artifacts/test/contracts/watchable-function-logic/Receiver.sol/Receiver.json'; | ||
import chai, { expect } from 'chai'; | ||
import chaiAsPromised from 'chai-as-promised'; | ||
import { ethers, network } from 'hardhat'; | ||
import storageArtifact from 'test/unit/fake/testdata/Storage.json'; | ||
|
||
chai.use(chaiAsPromised); | ||
chai.use(smock.matchers); | ||
|
||
describe('Fake: Initialization', () => { | ||
it('should work with the contract name', async () => { | ||
|
@@ -91,21 +90,4 @@ describe('Fake: Initialization', () => { | |
const fake = await smock.fake(storageArtifact); | ||
expect(fake.store._watchable).not.to.be.undefined; | ||
}); | ||
|
||
it('should throw error for invalid json string abi', async () => { | ||
await expect(smock.fake(`{invalid}`)).to.be.rejectedWith( | ||
Error, | ||
`unable to generate smock spec from abi string.\nUnexpected token i in JSON at position 1` | ||
); | ||
}); | ||
|
||
it('should throw error for non existent contract', async () => { | ||
let regex: RegExp = /unable to generate smock spec from contract name*/; | ||
await expect(smock.fake('NonExistentContract')).to.be.rejectedWith(Error, regex); | ||
}); | ||
|
||
it('should throw error if contract name is ambiguous', async () => { | ||
let regex: RegExp = /unable to generate smock spec from contract name*/; | ||
await expect(smock.fake('Storage')).to.be.rejectedWith(Error, regex); | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why deleting this?? |
||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { MockContract, MockContractFactory, smock } from '@src'; | ||
import { ADDRESS_EXAMPLE } from '@test-utils'; | ||
import { Bug, Bug__factory } from '@typechained'; | ||
import { expect } from 'chai'; | ||
|
||
describe('Bug Fix', () => { | ||
let bugFactory: MockContractFactory<Bug__factory>; | ||
let mock: MockContract<Bug>; | ||
|
||
before(async () => { | ||
bugFactory = await smock.mock('Bug'); | ||
}); | ||
|
||
beforeEach(async () => { | ||
mock = await bugFactory.deploy(); | ||
}); | ||
|
||
describe('setVariable', () => { | ||
it('should be able to fix slot overwrite on packed variables', async () => { | ||
await mock.setVariable('myAddress', ADDRESS_EXAMPLE); | ||
await mock.setVariable('myBool', true); | ||
console.log(await mock.myAddress()); | ||
expect(await mock.myAddress()).to.equal(ADDRESS_EXAMPLE); | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { MockContract, MockContractFactory, smock } from '@src'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why is this file named |
||
import { ADDRESS_EXAMPLE } from '@test-utils'; | ||
import { StorageGetter, StorageGetter__factory } from '@typechained'; | ||
import { expect } from 'chai'; | ||
|
||
describe('Mock: Editable storage logic', () => { | ||
let storageGetterFactory: MockContractFactory<StorageGetter__factory>; | ||
let mock: MockContract<StorageGetter>; | ||
|
||
before(async () => { | ||
storageGetterFactory = await smock.mock('StorageGetter'); | ||
}); | ||
|
||
beforeEach(async () => { | ||
mock = await storageGetterFactory.deploy(1); | ||
}); | ||
|
||
describe('setVariable', () => { | ||
it('should be able to fix bug', async () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. don't reference the bug, this will stay in documentation for the future |
||
await mock.setVariable('_address', ADDRESS_EXAMPLE); | ||
await mock.setVariable('_bool', true); | ||
expect(await mock.getAddress()).to.equal(ADDRESS_EXAMPLE); | ||
}); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please remove extra scripts from package.json
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done