Merge pull request 'fix: revm evaluator — UUPS bypass, deployedBytecode, graceful attack ops' (#629) from fix/revm-evaluator into master
Reviewed-on: https://codeberg.org/johba/harb/pulls/629 Reviewed-by: review_bot <review_bot@noreply.codeberg.org>
This commit is contained in:
commit
c3c262a719
3 changed files with 97 additions and 30 deletions
|
|
@ -2,7 +2,7 @@
|
|||
src = "src"
|
||||
out = "out"
|
||||
libs = ["lib"]
|
||||
fs_permissions = [{ access = "read-write", path = "./"}]
|
||||
fs_permissions = [{ access = "read-write", path = "./"}, { access = "read", path = "/tmp"}]
|
||||
gas_limit = 1_000_000_000
|
||||
gas_price = 0
|
||||
optimizer = true
|
||||
|
|
|
|||
|
|
@ -221,24 +221,26 @@ contract FitnessEvaluator is Test {
|
|||
vm.revertTo(baseSnap);
|
||||
baseSnap = vm.snapshot();
|
||||
|
||||
// Etch candidate optimizer bytecode and upgrade proxy.
|
||||
// Wrapped in try/catch: a malformed candidate (compiler bug, bad transpiler output)
|
||||
// would otherwise abort the entire batch. On failure, emit fitness=0 and continue;
|
||||
// vm.revertTo(baseSnap) at the top of the next iteration cleans up state.
|
||||
// Etch candidate optimizer bytecode onto the implementation address
|
||||
// and update the ERC1967 implementation slot directly.
|
||||
// Skips UUPS upgradeTo() check — candidates are standalone contracts
|
||||
// (OptimizerV3Push3) without UUPSUpgradeable inheritance.
|
||||
// This is safe because we only care about calculateParams() output.
|
||||
bytes memory candidateBytecode = vm.parseBytes(bytecodeHex);
|
||||
vm.etch(IMPL_SLOT, candidateBytecode);
|
||||
bool upgradeOk = true;
|
||||
try UUPSUpgradeable(optProxy).upgradeTo(IMPL_SLOT) { }
|
||||
catch {
|
||||
upgradeOk = false;
|
||||
}
|
||||
if (!upgradeOk) {
|
||||
console.log(string.concat('{"candidate_id":"', candidateId, '","fitness":0,"error":"upgrade_failed"}'));
|
||||
if (candidateBytecode.length == 0) {
|
||||
console.log(string.concat('{"candidate_id":"', candidateId, '","fitness":0,"error":"empty_bytecode"}'));
|
||||
continue;
|
||||
}
|
||||
vm.etch(IMPL_SLOT, candidateBytecode);
|
||||
// ERC1967 implementation slot = keccak256("eip1967.proxy.implementation") - 1
|
||||
bytes32 ERC1967_IMPL = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
|
||||
vm.store(optProxy, ERC1967_IMPL, bytes32(uint256(uint160(IMPL_SLOT))));
|
||||
|
||||
// Bootstrap: fund LM, set recenterAccess, initial recenter.
|
||||
_bootstrap();
|
||||
if (!_bootstrap()) {
|
||||
console.log(string.concat('{"candidate_id":"', candidateId, '","fitness":0,"error":"bootstrap_failed"}'));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Score: sum lm_eth_total across all attack sequences.
|
||||
uint256 totalFitness = 0;
|
||||
|
|
@ -321,7 +323,7 @@ contract FitnessEvaluator is Test {
|
|||
* d. Wrap 9000 WETH for adversary trades + set approvals.
|
||||
* e. Initial recenter (succeeds immediately: recenterAccess set, no ANCHOR liquidity yet).
|
||||
*/
|
||||
function _bootstrap() internal {
|
||||
function _bootstrap() internal returns (bool) {
|
||||
// a. Grant recenterAccess (feeDestination call, no ETH needed with gas_price=0).
|
||||
vm.prank(FEE_DEST);
|
||||
LiquidityManager(payable(lmAddr)).setRecenterAccess(recenterAddr);
|
||||
|
|
@ -356,9 +358,15 @@ contract FitnessEvaluator is Test {
|
|||
try ILM(lmAddr).recenter() returns (bool) {
|
||||
recentered = true;
|
||||
break;
|
||||
} catch { }
|
||||
} catch (bytes memory reason) {
|
||||
console.log(string.concat("recenter attempt ", vm.toString(_attempt), " failed"));
|
||||
console.logBytes(reason);
|
||||
}
|
||||
}
|
||||
require(recentered, "FitnessEvaluator: bootstrap recenter failed after 5 attempts");
|
||||
if (!recentered) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ─── Attack execution ─────────────────────────────────────────────────────
|
||||
|
|
@ -394,7 +402,7 @@ contract FitnessEvaluator is Test {
|
|||
if (_eq(op, "buy")) {
|
||||
uint256 amount = vm.parseUint(vm.parseJsonString(line, ".amount"));
|
||||
vm.prank(advAddr);
|
||||
ISwapRouter02(SWAP_ROUTER).exactInputSingle(
|
||||
try ISwapRouter02(SWAP_ROUTER).exactInputSingle(
|
||||
ISwapRouter02.ExactInputSingleParams({
|
||||
tokenIn: WETH_ADDR,
|
||||
tokenOut: krkAddr,
|
||||
|
|
@ -404,13 +412,13 @@ contract FitnessEvaluator is Test {
|
|||
amountOutMinimum: 0,
|
||||
sqrtPriceLimitX96: 0
|
||||
})
|
||||
);
|
||||
) { } catch { }
|
||||
} else if (_eq(op, "sell")) {
|
||||
string memory amtStr = vm.parseJsonString(line, ".amount");
|
||||
uint256 amount = _eq(amtStr, "all") ? IERC20(krkAddr).balanceOf(advAddr) : vm.parseUint(amtStr);
|
||||
if (amount == 0) return;
|
||||
vm.prank(advAddr);
|
||||
ISwapRouter02(SWAP_ROUTER).exactInputSingle(
|
||||
try ISwapRouter02(SWAP_ROUTER).exactInputSingle(
|
||||
ISwapRouter02.ExactInputSingleParams({
|
||||
tokenIn: krkAddr,
|
||||
tokenOut: WETH_ADDR,
|
||||
|
|
@ -420,7 +428,7 @@ contract FitnessEvaluator is Test {
|
|||
amountOutMinimum: 0,
|
||||
sqrtPriceLimitX96: 0
|
||||
})
|
||||
);
|
||||
) { } catch { }
|
||||
} else if (_eq(op, "recenter")) {
|
||||
vm.prank(recenterAddr);
|
||||
try ILM(lmAddr).recenter() { } catch { }
|
||||
|
|
@ -428,16 +436,14 @@ contract FitnessEvaluator is Test {
|
|||
uint256 amount = vm.parseUint(vm.parseJsonString(line, ".amount"));
|
||||
uint32 taxRate = uint32(vm.parseJsonUint(line, ".taxRateIndex"));
|
||||
vm.prank(advAddr);
|
||||
uint256 posId = IStake(stakeAddr).snatch(amount, advAddr, taxRate, new uint256[](0));
|
||||
_stakedPositionIds.push(posId);
|
||||
try IStake(stakeAddr).snatch(amount, advAddr, taxRate, new uint256[](0)) returns (uint256 posId) {
|
||||
_stakedPositionIds.push(posId);
|
||||
} catch { }
|
||||
} else if (_eq(op, "unstake")) {
|
||||
uint256 posIndex = vm.parseJsonUint(line, ".positionId");
|
||||
require(
|
||||
posIndex >= 1 && posIndex <= _stakedPositionIds.length,
|
||||
"FitnessEvaluator: unstake positionId out of range"
|
||||
);
|
||||
if (posIndex < 1 || posIndex > _stakedPositionIds.length) return;
|
||||
vm.prank(advAddr);
|
||||
IStake(stakeAddr).exitPosition(_stakedPositionIds[posIndex - 1]);
|
||||
try IStake(stakeAddr).exitPosition(_stakedPositionIds[posIndex - 1]) { } catch { }
|
||||
} else if (_eq(op, "mine")) {
|
||||
uint256 blocks = vm.parseJsonUint(line, ".blocks");
|
||||
vm.roll(block.number + blocks);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue