Skip to content
This repository has been archived by the owner on Feb 26, 2024. It is now read-only.

test(eth_getCode): add tests for contract factories #667

Merged
merged 1 commit into from
Nov 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
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 src/chains/ethereum/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1039,7 +1039,7 @@ export default class EthereumApi implements types.Api {
const trie = blockchain.trie.copy();
const getFromTrie = (address: Buffer): Promise<Buffer> =>
new Promise((resolve, reject) => {
trie.get(address, (err, data) => {
trie.get(address, (err: Error, data: Buffer) => {
if (err) return void reject(err);
resolve(data);
});
Expand Down Expand Up @@ -1071,7 +1071,7 @@ export default class EthereumApi implements types.Api {
return Data.from("0x");
}
return new Promise((resolve, reject) => {
trie.getRaw(codeHash, (err, data) => {
trie.getRaw(codeHash, (err: Error, data: Buffer) => {
if (err) return void reject(err);
resolve(Data.from(data));
});
Expand Down
9 changes: 8 additions & 1 deletion src/chains/ethereum/tests/api/eth/contracts/GetCode.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.4;

contract GetCode {}
import "./NoOp.sol";

contract GetCode {
address public noop;
constructor() {
noop = address(new NoOp());
}
}
7 changes: 7 additions & 0 deletions src/chains/ethereum/tests/api/eth/contracts/NoOp.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.4;

/**
* This contract does nothing and should do nothing.
*/
contract NoOp {}
132 changes: 113 additions & 19 deletions src/chains/ethereum/tests/api/eth/getCode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,22 @@ describe("api", () => {

describe("code checks", () => {
let provider: EthereumProvider;
let accounts: string[];
let from: string;
let contractAddress: string;
let blockNumber: Quantity;
let contract: ReturnType<typeof compile>;

before(async () => {
contract = compile(join(__dirname, "./contracts/GetCode.sol"));
contract = compile(join(__dirname, "./contracts/GetCode.sol"), {
contractName: "GetCode",
imports: [join(__dirname, "./contracts/NoOp.sol")]
});
provider = await getProvider();
accounts = await provider.send("eth_accounts");
[from] = await provider.send("eth_accounts");
await provider.send("eth_subscribe", ["newHeads"]);
const transactionHash = await provider.send("eth_sendTransaction", [
{
from: accounts[0],
from,
data: contract.code,
gas: 3141592
}
Expand All @@ -73,24 +76,115 @@ describe("api", () => {
blockNumber = Quantity.from(transactionReceipt.blockNumber);
});

it("should return the code at the deployed block number", async () => {
const code = await provider.send("eth_getCode", [
contractAddress,
blockNumber.toString()
]);
assert.strictEqual(
code,
`0x${contract.contract.evm.deployedBytecode.object}`
);
describe("factory contract", () => {
const context: {
contractAddress?: string;
expectedCode?: string;
} = {};
before(() => {
context.contractAddress = contractAddress;
context.expectedCode = `0x${contract.contract.evm.deployedBytecode.object}`;
});
testContractCode(context);
});

it("should return the no code at the previous block number", async () => {
const code = await provider.send("eth_getCode", [
contractAddress,
Quantity.from(blockNumber.toBigInt() - 1n).toString()
]);
assert.strictEqual(code, "0x");
describe("factory-deployed contract", () => {
const context: {
contractAddress?: string;
expectedCode?: string;
} = {};
before(async () => {
const methods = contract.contract.evm.methodIdentifiers;
const value = await provider.send("eth_call", [
{ from, to: contractAddress, data: "0x" + methods["noop()"] }
]);
context.contractAddress = `0x${value.slice(2 + 64 - 40)}`; // 0x...000...{20-byte address}
context.expectedCode = `0x${contract.imports["NoOp.sol"]["NoOp"].evm.deployedBytecode.object}`;
});
testContractCode(context);
});

function testContractCode(context: {
contractAddress?: string;
expectedCode?: string;
}) {
let contractAddress: string;
let expectedCode: string;
before(() => ({ contractAddress, expectedCode } = context));

it("should return the code at the deployed block number", async () => {
const code = await provider.send("eth_getCode", [
contractAddress,
blockNumber.toString()
]);
assert.strictEqual(code, expectedCode);
});

it("should return the no code at the previous block number", async () => {
const code = await provider.send("eth_getCode", [
contractAddress,
Quantity.from(blockNumber.toBigInt() - 1n).toString()
]);
assert.strictEqual(code, "0x");
});

it("should return the code at the 'latest' block when `latest` and the deployed block number are the same", async () => {
const code = await provider.send("eth_getCode", [
contractAddress,
"latest"
]);
assert.strictEqual(code, expectedCode);

const code2 = await provider.send("eth_getCode", [
contractAddress
// testing "latest" as default
]);
assert.strictEqual(code2, expectedCode);
});

it("should return the code at the 'latest' block when the chain has progressed to new blocks", async () => {
await provider.send("evm_mine");

const latestBlockNumber = await provider.send("eth_blockNumber");

assert.notStrictEqual(blockNumber.toString(), latestBlockNumber);

const code = await provider.send("eth_getCode", [
contractAddress,
blockNumber.toString()
]);
assert.strictEqual(code, expectedCode);

const code2 = await provider.send("eth_getCode", [
contractAddress,
"latest"
]);
assert.strictEqual(code2, expectedCode);

const code3 = await provider.send("eth_getCode", [
contractAddress
// testing "latest" as default
]);
assert.strictEqual(code3, expectedCode);
});

it("should return a `header not found` error for requests to non-existent blocks", async () => {
const nextBlockNumber =
Quantity.from(await provider.send("eth_blockNumber")).toBigInt() +
1n;

await assert.rejects(
provider.send("eth_getCode", [
contractAddress,
Quantity.from(nextBlockNumber).toString()
]),
{
message: "header not found"
},
`eth_getCode should return an error when code at a non-existent block is requested (block #: ${nextBlockNumber})`
);
});
}
});
});
});
Expand Down
4 changes: 2 additions & 2 deletions src/chains/ethereum/tests/contracts/Debug.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ contract Debug {

event ValueSet(uint);

constructor() public payable {
constructor() payable {
value = 5;
}

Expand All @@ -21,7 +21,7 @@ contract Debug {
}
}

function doARevert() public {
function doARevert() public pure {
revert("all your base");
}
}
42 changes: 35 additions & 7 deletions src/chains/ethereum/tests/helpers/compile.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import solc from "solc";
import solc, { CompilerInputSourceCode, CompilerInputSourceFile } from "solc";

{
// Clean up after solc. Looks like this never really got fixed:
Expand All @@ -20,25 +20,43 @@ import { parse } from "path";
export type CompileOutput = {
code: string;
contract: solc.CompilerOutputContracts[string][string];
imports: solc.CompilerOutputContracts;
};

type ContractPath = string;
type Imports = ContractPath[];
type Sources = {
[globalName: string]: CompilerInputSourceFile | CompilerInputSourceCode;
};

export default function compile(
contractPath: string,
contractName?: string
contractPath: ContractPath,
{
contractName = null,
imports = []
}: {
contractName?: string;
imports?: Imports;
} = {}
): CompileOutput {
const parsedPath = parse(contractPath);
const content = readFileSync(contractPath, { encoding: "utf8" });
const content = readFileSync(contractPath, "utf8");
const globalName = parsedPath.base;
contractName ||= parsedPath.name;
const sources = imports.reduce<Sources>(
(o, p) => ((o[parse(p).base] = { content: readFileSync(p, "utf8") }), o),
{}
);

let result = JSON.parse(
const result = JSON.parse(
solc.compile(
JSON.stringify({
language: "Solidity",
sources: {
[globalName]: {
content
}
},
...sources
},
settings: {
outputSelection: {
Expand All @@ -51,9 +69,19 @@ export default function compile(
)
) as solc.CompilerOutput;

if (
result.errors &&
result.errors.some(error => error.severity === "error")
) {
throw new Error(result.errors.map(e => e.formattedMessage).join("\n\n"));
}

const contract = result.contracts[globalName][contractName];
const importSources = result.contracts;
delete importSources[globalName];
return {
code: "0x" + contract.evm.bytecode.object,
contract
contract,
imports: importSources
};
}