Skip to content
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

Closed
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Copy link
Member Author

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

"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",
Expand Down Expand Up @@ -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",
Copy link
Member Author

Choose a reason for hiding this comment

The 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",
Expand All @@ -83,7 +84,6 @@
"@types/node": "15.12.2",
"@types/semver": "^7.3.8",
"chai": "4.3.4",
"chai-as-promised": "7.1.1",
Copy link
Member Author

Choose a reason for hiding this comment

The 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",
Expand Down
51 changes: 12 additions & 39 deletions src/factories/ethers-interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i believe this should not be here, please make sure to pull dev up

}

let foundInterface: any = spec;
Expand All @@ -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) == '}';
}
18 changes: 16 additions & 2 deletions src/logic/editable-storage-logic.ts
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 {
Expand All @@ -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));
// }
Copy link
Member Author

Choose a reason for hiding this comment

The 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'))) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm interesting code here, is there any way of generalizing this?
i don't think it will cover all use cases, let's say you have an address, bool, bool pack, would it work?
did you perform tests on multiple scenarios?

Copy link

@wang-jundong wang-jundong Jul 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only address and bool has same slot value
so if they have same slot, previous storage value will be overwritten
i have tested in all case
so i have done this action

// console.log('_______________________________', stringValue, slot.val);
let lastResult = slot.val.slice(0, slot.val.length - stringValue.length).concat(stringValue);
Copy link
Member

Choose a reason for hiding this comment

The 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);
Copy link
Member Author

Choose a reason for hiding this comment

The 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);
}
}
}

Expand Down
21 changes: 21 additions & 0 deletions src/utils/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export interface SolidityStorageLayout {
interface StorageSlotPair {
key: string;
val: string;
type: string;
}

/**
Expand Down Expand Up @@ -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;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rm

return (output as any).storageLayout;
}

Expand All @@ -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);
Copy link
Member Author

Choose a reason for hiding this comment

The 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;
});
Expand All @@ -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);
Copy link
Member Author

Choose a reason for hiding this comment

The 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
Expand Down Expand Up @@ -139,6 +152,7 @@ export function computeStorageSlots(storageLayout: SolidityStorageLayout, variab
prevSlots.push({
key: slot.key,
val: mergedVal,
type: slot.type,
});
}

Expand Down Expand Up @@ -228,6 +242,7 @@ function encodeVariable(
{
key: slotKey,
val: padNumHexSlotValue(variable, storageObj.offset),
type: variableType.label,
},
];
} else if (variableType.label === 'bool') {
Expand All @@ -249,6 +264,7 @@ function encodeVariable(
{
key: slotKey,
val: padNumHexSlotValue(variable ? '1' : '0', storageObj.offset),
type: variableType.label,
},
];
} else if (variableType.label.startsWith('bytes')) {
Expand All @@ -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')) {
Expand All @@ -271,6 +288,7 @@ function encodeVariable(
{
key: slotKey,
val: padNumHexSlotValue(variable, storageObj.offset),
type: variableType.label,
},
];
} else if (variableType.label.startsWith('struct')) {
Expand Down Expand Up @@ -314,6 +332,7 @@ function encodeVariable(
ethers.BigNumber.from(bytes.length * 2).toHexString(),
])
),
type: variableType.label,
},
];
} else {
Expand All @@ -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
Expand All @@ -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,
});
}

Expand Down
7 changes: 7 additions & 0 deletions test/contracts/mock/Bug.sol
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;
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please generalize to different cases

26 changes: 0 additions & 26 deletions test/contracts/watchable-function-logic/Storage.sol

This file was deleted.

26 changes: 0 additions & 26 deletions test/contracts/watchable-function-logic/StorageDuplicate.sol

This file was deleted.

20 changes: 1 addition & 19 deletions test/unit/fake/initialization.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down Expand Up @@ -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);
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why deleting this??

});
3 changes: 1 addition & 2 deletions test/unit/mock/editable-storage-logic.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ describe('Mock: Editable storage logic', () => {
});

beforeEach(async () => {
mock = await storageGetterFactory.deploy(1);
mock = await storageGetterFactory.deploy(4);
});

describe('setVariable', () => {
Expand Down Expand Up @@ -64,7 +64,6 @@ describe('Mock: Editable storage logic', () => {

it('should be able to set an address in a packed storage slot', async () => {
await mock.setVariable('_packedB', ADDRESS_EXAMPLE);

expect(await mock.getPackedAddress()).to.equal(ADDRESS_EXAMPLE);
});

Expand Down
26 changes: 26 additions & 0 deletions test/unit/mock/mytest.spec.ts
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);
});
});
});
25 changes: 25 additions & 0 deletions test/unit/mock/mytest.spec2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { MockContract, MockContractFactory, smock } from '@src';
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this file named mytest.spec2.ts??

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 () => {
Copy link
Member Author

Choose a reason for hiding this comment

The 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);
});
});
});
Loading