/**
* DELEGATECALL opcode (0xf4)
* Execute code preserving msg.sender and msg.value
*/
export function delegatecall(frame: FrameType): EvmError | null {
// Pop 6 arguments (no value)
const gas = popStack(frame);
const address = popStack(frame);
const inOffset = popStack(frame);
const inLength = popStack(frame);
const outOffset = popStack(frame);
const outLength = popStack(frame);
// Calculate gas cost
let gasCost = 700n; // Base (Tangerine Whistle+)
// No value transfer cost
// Cold access cost (Berlin+)
const accessCost = getAccessCost(address);
gasCost += accessCost;
// Memory expansion
const inEnd = inLength > 0 ? inOffset + inLength : 0;
const outEnd = outLength > 0 ? outOffset + outLength : 0;
const maxEnd = Math.max(inEnd, outEnd);
if (maxEnd > 0) {
gasCost += memoryExpansionCost(frame, maxEnd);
updateMemorySize(frame, maxEnd);
}
// Calculate forwarded gas (63/64 rule)
const afterCharge = frame.gasRemaining - gasCost;
const maxForward = afterCharge - afterCharge / 64n;
const forwardedGas = min(gas, maxForward);
// Charge total cost
consumeGas(frame, gasCost + forwardedGas);
// Read calldata
const calldata = readMemory(frame, inOffset, inLength);
// Execute delegatecall (preserve context!)
const result = executeDelegateCall({
codeAddress: address, // Code to execute
storageAddress: frame.address, // Storage to modify
sender: frame.caller, // msg.sender PRESERVED
value: frame.value, // msg.value PRESERVED
data: calldata,
gas: forwardedGas
});
// Refund unused gas
frame.gasRemaining += result.gasLeft;
// Copy returndata
const copySize = min(outLength, result.returnData.length);
writeMemory(frame, outOffset, result.returnData.slice(0, copySize));
// Set return_data buffer
frame.returnData = result.returnData;
// Push success flag
pushStack(frame, result.success ? 1n : 0n);
frame.pc += 1;
return null;
}