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: mocked edge case #33

Merged
merged 2 commits into from
Aug 20, 2024
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
321 changes: 170 additions & 151 deletions test/coprocessorUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,20 +66,6 @@ export const getClearText = async (handle: bigint): Promise<string> => {
executeQuery();
});
};
/*export const getClearText = async (handle: bigint): Promise<string> => {
const handleStr = '0x' + handle.toString(16).padStart(64, '0');
return new Promise((resolve, reject) => {
db.get('SELECT clearText FROM ciphertexts WHERE handle = ?', [handleStr], (err, row) => {
if (err) {
reject(new Error(`Error querying database: ${err.message}`));
} else if (row) {
resolve(row.clearText);
} else {
reject(new Error('No record found'));
}
});
});
};*/

db.serialize(() => db.run("CREATE TABLE IF NOT EXISTS ciphertexts (handle BINARY PRIMARY KEY,clearText TEXT)"));

Expand Down Expand Up @@ -195,12 +181,12 @@ function getRandomBigInt(numBits: number): bigint {
return randomBigInt;
}

async function insertHandle(obj: EvmState) {
async function insertHandle(obj2: EvmState, validIdxes: [number]) {
const obj = obj2.value;
if (isCoprocAdd(obj!.stack.at(-2))) {
const argsOffset = Number(`0x${obj!.stack.at(-4)}`);
const argsSize = Number(`0x${obj!.stack.at(-5)}`);
const calldata = extractCalldata(obj.memory, argsOffset, argsSize);
//console.log('calldata : ', calldata);
const currentSelector = "0x" + calldata.slice(0, 8);
const decodedData = iface.decodeFunctionData(currentSelector, "0x" + calldata);

Expand Down Expand Up @@ -669,68 +655,75 @@ async function insertHandle(obj: EvmState) {
insertSQL(handle, clearText);
break;

case "verifyCiphertext(bytes32,address,bytes,bytes1)": {
handle = decodedData[0];
const type = parseInt(handle.slice(-4, -2), 16);
if (type !== 11) {
//not an ebytes256
const typeSize = TypesBytesSize[type];
const idx = parseInt(handle.slice(-6, -4), 16);
const inputProof = decodedData[2].replace(/^0x/, "");
clearText = BigInt("0x" + inputProof.slice(2 + 2 * 53 * idx, 2 + 2 * typeSize + 2 * 53 * idx));
insertSQL(handle, clearText);
} else {
const inputProof = decodedData[2].replace(/^0x/, "");
clearText = BigInt("0x" + inputProof.slice(2, 2 + 2 * 256));
insertSQL(handle, clearText);
case "verifyCiphertext(bytes32,address,bytes,bytes1)":
{
handle = decodedData[0];
const type = parseInt(handle.slice(-4, -2), 16);
if (type !== 11) {
//not an ebytes256
const typeSize = TypesBytesSize[type];
const idx = parseInt(handle.slice(-6, -4), 16);
const inputProof = decodedData[2].replace(/^0x/, "");
clearText = BigInt("0x" + inputProof.slice(2 + 2 * 53 * idx, 2 + 2 * typeSize + 2 * 53 * idx));
insertSQL(handle, clearText);
} else {
const inputProof = decodedData[2].replace(/^0x/, "");
clearText = BigInt("0x" + inputProof.slice(2, 2 + 2 * 256));
insertSQL(handle, clearText);
}
}
break;
}
case "fheIfThenElse(uint256,uint256,uint256)": {
resultType = parseInt(decodedData[1].toString(16).slice(-4, -2), 16);
handle = ethers.keccak256(
ethers.solidityPacked(
["uint8", "uint256", "uint256", "uint256"],
[Operators.fheIfThenElse, decodedData[0], decodedData[1], decodedData[2]],
),
);
handle = appendType(handle, resultType);
const clearControl = BigInt(await getClearText(decodedData[0]));
const clearIfTrue = BigInt(await getClearText(decodedData[1]));
const clearIfFalse = BigInt(await getClearText(decodedData[2]));
if (clearControl === 1n) {
clearText = clearIfTrue;
} else {
clearText = clearIfFalse;

case "fheIfThenElse(uint256,uint256,uint256)":
{
resultType = parseInt(decodedData[1].toString(16).slice(-4, -2), 16);
handle = ethers.keccak256(
ethers.solidityPacked(
["uint8", "uint256", "uint256", "uint256"],
[Operators.fheIfThenElse, decodedData[0], decodedData[1], decodedData[2]],
),
);
handle = appendType(handle, resultType);
const clearControl = BigInt(await getClearText(decodedData[0]));
const clearIfTrue = BigInt(await getClearText(decodedData[1]));
const clearIfFalse = BigInt(await getClearText(decodedData[2]));
if (clearControl === 1n) {
clearText = clearIfTrue;
} else {
clearText = clearIfFalse;
}
insertSQL(handle, clearText);
}
insertSQL(handle, clearText);
break;
}
case "fheRand(bytes1)": {
resultType = parseInt(decodedData[0], 16);
handle = ethers.keccak256(
ethers.solidityPacked(["uint8", "bytes1", "uint256"], [Operators.fheRand, decodedData[0], counterRand]),
);
handle = appendType(handle, resultType);
clearText = getRandomBigInt(Number(NumBits[resultType]));
insertSQL(handle, clearText, true);
counterRand++;

case "fheRand(bytes1)":
if (validIdxes.includes(obj2.index)) {
resultType = parseInt(decodedData[0], 16);
handle = ethers.keccak256(
ethers.solidityPacked(["uint8", "bytes1", "uint256"], [Operators.fheRand, decodedData[0], counterRand]),
);
handle = appendType(handle, resultType);
clearText = getRandomBigInt(Number(NumBits[resultType]));
insertSQL(handle, clearText, true);
counterRand++;
}
break;
}
case "fheRandBounded(uint256,bytes1)": {
resultType = parseInt(decodedData[1], 16);
handle = ethers.keccak256(
ethers.solidityPacked(
["uint8", "uint256", "bytes1", "uint256"],
[Operators.fheRandBounded, decodedData[0], decodedData[1], counterRand],
),
);
handle = appendType(handle, resultType);
clearText = getRandomBigInt(Number(log2(BigInt(decodedData[0]))));
insertSQL(handle, clearText, true);
counterRand++;

case "fheRandBounded(uint256,bytes1)":
if (validIdxes.includes(obj2.index)) {
resultType = parseInt(decodedData[1], 16);
handle = ethers.keccak256(
ethers.solidityPacked(
["uint8", "uint256", "bytes1", "uint256"],
[Operators.fheRandBounded, decodedData[0], decodedData[1], counterRand],
),
);
handle = appendType(handle, resultType);
clearText = getRandomBigInt(Number(log2(BigInt(decodedData[0]))));
insertSQL(handle, clearText, true);
counterRand++;
}
break;
}
}
}
}
Expand All @@ -755,9 +748,11 @@ function isCoprocAdd(longString: string): boolean {
return normalizedLongString === coprocAdd;
}

async function processLogs(trace, blockNo) {
for (const obj of trace.structLogs.filter((obj) => obj.op === "CALL")) {
await insertHandle(obj, blockNo);
async function processLogs(trace, validSubcallsIndexes) {
for (const obj of trace.structLogs
.map((value, index) => ({ value, index }))
.filter((obj) => obj.value.op === "CALL")) {
await insertHandle(obj, validSubcallsIndexes);
}
}

Expand All @@ -767,15 +762,17 @@ export const awaitCoprocessor = async (): Promise<void> => {
const trace = await ethers.provider.send("debug_traceTransaction", [txHash[0]]);

if (!trace.failed) {
await processLogs(trace, txHash[1]);
const callTree = await buildCallTree(trace, txHash[1]);
const validSubcallsIndexes = getValidSubcallsIds(callTree)[1];
await processLogs(trace, validSubcallsIndexes);
}
}
};

async function getAllPastTransactionHashes() {
const provider = ethers.provider;
const latestBlockNumber = await provider.getBlockNumber();
const txHashes: [string, number][] = [];
const txHashes = [];

if (hre.__SOLIDITY_COVERAGE_RUNNING !== true) {
// evm_snapshot is not supported in coverage mode
Expand All @@ -788,9 +785,10 @@ async function getAllPastTransactionHashes() {

// Iterate through all blocks and collect transaction hashes
for (let i = firstBlockListening; i <= latestBlockNumber; i++) {
const block = await provider.getBlock(i);
block!.transactions.forEach((tx) => {
txHashes.push([tx, i]);
const block = await provider.getBlock(i, true);
block!.transactions.forEach((tx, index) => {
const rcpt = block?.prefetchedTransactions[index];
txHashes.push([tx, { to: rcpt.to, status: rcpt.status }]);
});
}
firstBlockListening = latestBlockNumber + 1;
Expand All @@ -801,74 +799,95 @@ async function getAllPastTransactionHashes() {
return txHashes;
}

// async function buildCallTree(receipt: TransactionReceipt) {
// const txHash = receipt.hash;
// const trace = await ethers.provider.send("debug_traceTransaction", [txHash, {}]);
// const structLogs = trace.structLogs;

// const callStack = [];
// const callTree = {
// id: 0,
// type: !!receipt.to ? "TOPCALL" : "TOPCREATE",
// revert: receipt.status === 1 ? false : true,
// to: !!receipt.to ? receipt.to : null,
// calls: [],
// };
// let currentNode = callTree;
// const lenStructLogs = structLogs.length;
// let index = 1;
// for (const [i, log] of structLogs.entries()) {
// if (i < lenStructLogs - 1) {
// if (structLogs[i].depth - structLogs[i + 1].depth === 1) {
// if (!["RETURN", "SELFDESTRUCT", "STOP", "REVERT", "INVALID"].includes(structLogs[i].op)) {
// currentNode.outofgasOrOther = true;
// currentNode = callStack.pop();
// }
// }
// }

// switch (log.op) {
// case "CALL":
// case "DELEGATECALL":
// case "CALLCODE":
// case "STATICCALL":
// case "CREATE":
// case "CREATE2":
// if (i < lenStructLogs - 1) {
// if (structLogs[i + 1].depth - structLogs[i].depth === 1) {
// const newNode = {
// id: index,
// type: log.op,
// to: log.stack[log.stack.length - 2],
// calls: [],
// revert: true,
// outofgasOrOther: false,
// };
// currentNode.calls.push(newNode);
// callStack.push(currentNode);
// currentNode = newNode;
// index += 1;
// }
// }
// break;
// case "RETURN": // some edge case probably not handled well : if memory expansion cost on RETURN exceeds the remaining gas in current subcall, but it's OK for a mocked mode
// case "SELFDESTRUCT": // some edge case probably not handled well : if there is not enough gas remaining on SELFDESTRUCT, but it's OK for a mocked mode
// case "STOP":
// currentNode.revert = false;
// currentNode = callStack.pop();
// break;
// case "REVERT":
// case "INVALID":
// currentNode = callStack.pop();
// break;
// }

// switch (log.op) {
// case "CREATE":
// case "CREATE2":
// currentNode.to = null;
// break;
// }
// }
// return callTree;
// }
async function buildCallTree(trace, receipt) {
const structLogs = trace.structLogs;

const callStack = [];
const callTree = {
id: 0,
type: receipt.to ? "TOPCALL" : "TOPCREATE",
revert: receipt.status === 1 ? false : true,
to: receipt.to ? receipt.to : null,
calls: [],
indexTrace: 0,
};
let currentNode = callTree;
const lenStructLogs = structLogs.length;
let index = 1;
for (const [i, log] of structLogs.entries()) {
if (i < lenStructLogs - 1) {
if (structLogs[i].depth - structLogs[i + 1].depth === 1) {
if (!["RETURN", "SELFDESTRUCT", "STOP", "REVERT", "INVALID"].includes(structLogs[i].op)) {
currentNode.outofgasOrOther = true;
currentNode = callStack.pop();
}
}
}

switch (log.op) {
case "CALL":
case "DELEGATECALL":
case "CALLCODE":
case "STATICCALL":
case "CREATE":
case "CREATE2":
if (i < lenStructLogs - 1) {
if (structLogs[i + 1].depth - structLogs[i].depth === 1) {
const newNode = {
id: index,
type: log.op,
to: log.stack[log.stack.length - 2],
calls: [],
revert: true,
outofgasOrOther: false,
indexTrace: i,
};
currentNode.calls.push(newNode);
callStack.push(currentNode);
currentNode = newNode;
index += 1;
}
}
break;
case "RETURN": // some edge case probably not handled well : if memory expansion cost on RETURN exceeds the remaining gas in current subcall, but it's OK for a mocked mode
case "SELFDESTRUCT": // some edge case probably not handled well : if there is not enough gas remaining on SELFDESTRUCT, but it's OK for a mocked mode
case "STOP":
currentNode.revert = false;
currentNode = callStack.pop();
break;
case "REVERT":
case "INVALID":
currentNode = callStack.pop();
break;
}

switch (log.op) {
case "CREATE":
case "CREATE2":
currentNode.to = null;
break;
}
}
return callTree;
}

function getValidSubcallsIds(tree) {
const result = [];
const resultIndexes = [];

function traverse(node, ancestorReverted) {
if (ancestorReverted || node.revert) {
ancestorReverted = true;
} else {
result.push(node.id);
resultIndexes.push(node.indexTrace);
}
for (const child of node.calls) {
traverse(child, ancestorReverted);
}
}

traverse(tree, false);

return [result, resultIndexes];
}
Loading
Loading