diff --git a/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/Hub.java b/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/Hub.java index 9f10da9011..961167fa7d 100644 --- a/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/Hub.java +++ b/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/Hub.java @@ -99,7 +99,7 @@ import net.consensys.linea.zktracer.module.shf.Shf; import net.consensys.linea.zktracer.module.stp.Stp; import net.consensys.linea.zktracer.module.tables.bin.BinRt; -import net.consensys.linea.zktracer.module.tables.instructionDecoder.InstructionDecoder; +import net.consensys.linea.zktracer.module.tables.instructionDecoder.*; import net.consensys.linea.zktracer.module.tables.shf.ShfRt; import net.consensys.linea.zktracer.module.trm.Trm; import net.consensys.linea.zktracer.module.txndata.TxnData; @@ -107,6 +107,7 @@ import net.consensys.linea.zktracer.opcode.OpCode; import net.consensys.linea.zktracer.opcode.OpCodeData; import net.consensys.linea.zktracer.opcode.gas.projector.GasProjector; +import net.consensys.linea.zktracer.runtime.callstack.CallDataInfo; import net.consensys.linea.zktracer.runtime.callstack.CallFrame; import net.consensys.linea.zktracer.runtime.callstack.CallFrameType; import net.consensys.linea.zktracer.runtime.callstack.CallStack; @@ -121,7 +122,6 @@ import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.evm.account.AccountState; import org.hyperledger.besu.evm.frame.MessageFrame; -import org.hyperledger.besu.evm.internal.Words; import org.hyperledger.besu.evm.log.Log; import org.hyperledger.besu.evm.log.LogTopic; import org.hyperledger.besu.evm.operation.Operation; @@ -606,41 +606,29 @@ public void traceContextEnter(MessageFrame frame) { // internal transaction (CALL) or internal deployment (CREATE) if (frame.getDepth() > 0) { final OpCode currentOpCode = callStack.currentCallFrame().opCode(); + final boolean isDeployment = frame.getType() == CONTRACT_CREATION; + checkState(currentOpCode.isCall() || currentOpCode.isCreate()); checkState( currentTraceSection() instanceof CallSection || currentTraceSection() instanceof CreateSection); - final boolean isDeployment = frame.getType() == CONTRACT_CREATION; + checkState(currentTraceSection() instanceof CreateSection == isDeployment); + final CallFrameType frameType = frame.isStatic() ? CallFrameType.STATIC : CallFrameType.STANDARD; - final long callDataOffset = - isDeployment - ? 0 - : Words.clampedToLong( - callStack - .currentCallFrame() - .frame() - .getStackItem(currentOpCode.callMayNotTransferValue() ? 2 : 3)); - - final long callDataSize = + final CallDataInfo callDataInfo = isDeployment - ? 0 - : Words.clampedToLong( - callStack - .currentCallFrame() - .frame() - .getStackItem(currentOpCode.callMayNotTransferValue() ? 3 : 4)); - - final long callDataContextNumber = callStack.currentCallFrame().contextNumber(); + ? CallDataInfo.empty() + : ((CallSection) currentTraceSection()).getCallDataInfo(); currentFrame().rememberGasNextBeforePausing(this); currentFrame().pauseCurrentFrame(); MemorySpan returnDataTargetInCaller = - (currentTraceSection() instanceof CallSection) - ? ((CallSection) currentTraceSection()).getCallProvidedReturnDataTargetSpan() - : MemorySpan.empty(); + isDeployment + ? MemorySpan.empty() + : ((CallSection) currentTraceSection()).getReturnAtMemorySpan(); callStack.enter( frameType, @@ -654,10 +642,7 @@ public void traceContextEnter(MessageFrame frame) { this.deploymentNumberOf(frame.getContractAddress()), new Bytecode(frame.getCode().getBytes()), frame.getSenderAddress(), - frame.getInputData(), - callDataOffset, - callDataSize, - callDataContextNumber, + callDataInfo, returnDataTargetInCaller); this.currentFrame().initializeFrame(frame); @@ -724,6 +709,16 @@ public void tracePreExecution(final MessageFrame frame) { this.processStateExec(frame); } + /** + * A comment on {@link #unlatchStack(MessageFrame, TraceSection)}: Any instruction that writes + * onto the stack gets immediately unlatched if it raises an exception. If unexceptional it also + * gets immediately unlatched, except CALL's and CREATE's. The value written on the stack + * (successBit or successBit ∙ [child address] respectively) is only written after + * the child context has been executed. + * + *
Question: Does this work well with CALL's to EOA's ? to PRC's ? trivial deployments
+ * (i.e. empty initialization code) ?
+ */
public void tracePostExecution(MessageFrame frame, Operation.OperationResult operationResult) {
checkArgument(
this.state().processingPhase == TX_EXEC,
@@ -748,7 +743,7 @@ public void tracePostExecution(MessageFrame frame, Operation.OperationResult ope
defers.resolvePostExecution(this, frame, operationResult);
- if (!this.currentFrame().opCode().isCall() && !this.currentFrame().opCode().isCreate()) {
+ if (isExceptional() || !opCode().isCallOrCreate()) {
this.unlatchStack(frame, currentSection);
}
@@ -1062,7 +1057,7 @@ void traceOpcode(MessageFrame frame) {
case RETURN -> new ReturnSection(this);
case REVERT -> new RevertSection(this);
case STOP -> new StopSection(this);
- case SELFDESTRUCT -> new SelfdestructSection(this);
+ case SELFDESTRUCT -> new SelfdestructSection(this, frame);
}
}
@@ -1101,7 +1096,7 @@ void traceOpcode(MessageFrame frame) {
case JUMP -> new JumpSection(this);
- case CREATE -> new CreateSection(this);
+ case CREATE -> new CreateSection(this, frame);
case CALL -> new CallSection(this, frame);
diff --git a/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/defer/ImmediateContextEntryDefer.java b/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/defer/ContextEntryDefer.java
similarity index 94%
rename from arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/defer/ImmediateContextEntryDefer.java
rename to arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/defer/ContextEntryDefer.java
index 46374cccd1..cc0d43c95c 100644
--- a/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/defer/ImmediateContextEntryDefer.java
+++ b/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/defer/ContextEntryDefer.java
@@ -17,6 +17,6 @@
import net.consensys.linea.zktracer.module.hub.Hub;
-public interface ImmediateContextEntryDefer {
+public interface ContextEntryDefer {
void resolveUponContextEntry(Hub hub);
}
diff --git a/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/defer/DeferRegistry.java b/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/defer/DeferRegistry.java
index ec7379eea3..9f6cd23dfc 100644
--- a/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/defer/DeferRegistry.java
+++ b/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/defer/DeferRegistry.java
@@ -31,7 +31,7 @@
*/
public class DeferRegistry
implements PostOpcodeDefer,
- ImmediateContextEntryDefer,
+ ContextEntryDefer,
ContextExitDefer,
PostRollbackDefer,
PostTransactionDefer,
@@ -41,7 +41,7 @@ public class DeferRegistry
private final List Unexceptional CALL-type instructions, including aborted ones, always require some
+ * degree of post-processing. For one, they are all rollback sensitive as it pertains to
+ * value transfers and warmth. As such everything gets scheduled for post rollback.
+ *
+ * We also need to schedule unexceptional {@link CallSection}'s for post-transaction resolution.
+ * Indeed, the following must always be performed, in that order, at transaction end:
+ *
+ * - append the precompile subsection (if applicable)
+ *
+ * - append the final context fragment
+ */
public class CallSection extends TraceSection
implements PostOpcodeDefer,
- ImmediateContextEntryDefer,
+ ContextEntryDefer,
ContextExitDefer,
ContextReEntryDefer,
PostRollbackDefer,
@@ -95,7 +113,10 @@ public boolean isAbortingScenario() {
// last row
@Setter private ContextFragment finalContextFragment;
+ private Address callerAddress;
+ private Address calleeAddress;
private Bytes rawCalleeAddress;
+ final ImcFragment firstImcFragment;
// Just before the CALL Opcode
private AccountSnapshot preOpcodeCallerSnapshot;
@@ -113,8 +134,7 @@ public boolean isAbortingScenario() {
private AccountSnapshot reEntryCallerSnapshot;
private AccountSnapshot reEntryCalleeSnapshot;
- private boolean selfCallWithNonzeroValueTransfer;
-
+ private final OpCode opCode;
private Wei value;
private AccountSnapshot postRollbackCalleeSnapshot;
@@ -123,17 +143,20 @@ public boolean isAbortingScenario() {
public StpCall stpCall;
private PrecompileSubsection precompileSubsection;
- @Getter private MemorySpan callProvidedReturnDataTargetSpan;
+ @Getter private MemorySpan returnAtMemorySpan;
+ @Getter private CallDataInfo callDataInfo;
public CallSection(Hub hub, MessageFrame frame) {
super(hub, maxNumberOfLines(hub));
+ opCode = hub.opCode();
+
final short exceptions = hub.pch().exceptions();
// row i + 1
final ContextFragment currentContextFragment = ContextFragment.readCurrentContextData(hub);
// row i + 2
- final ImcFragment firstImcFragment = ImcFragment.empty(hub);
+ firstImcFragment = ImcFragment.empty(hub);
this.addStackAndFragments(hub, scenarioFragment, currentContextFragment, firstImcFragment);
@@ -166,11 +189,11 @@ public CallSection(Hub hub, MessageFrame frame) {
hubStamp()));
final CallFrame currentFrame = hub.currentFrame();
- final Address callerAddress = currentFrame.frame().getRecipientAddress();
- preOpcodeCallerSnapshot = canonical(hub, callerAddress);
+ callerAddress = frame.getRecipientAddress();
+ rawCalleeAddress = frame.getStackItem(1);
+ calleeAddress = Address.extract(EWord.of(rawCalleeAddress));
- rawCalleeAddress = currentFrame.frame().getStackItem(1);
- final Address calleeAddress = Address.extract(EWord.of(rawCalleeAddress)); // TODO check this
+ preOpcodeCallerSnapshot = canonical(hub, callerAddress);
preOpcodeCalleeSnapshot = canonical(hub, calleeAddress);
// OOGX case
@@ -183,12 +206,16 @@ public CallSection(Hub hub, MessageFrame frame) {
checkArgument(Exceptions.none(exceptions));
currentFrame.childSpanningSection(this);
- final boolean callCanTransferValue = currentFrame.opCode().callCanTransferValue();
- callProvidedReturnDataTargetSpan =
- returnDataMemorySpan(currentFrame.frame(), callCanTransferValue);
+ final boolean callHasValueArgument = currentFrame.opCode().callHasValueArgument();
+
+ // the call data span and ``return at'' spans are only required once the CALL is unexceptional
+ returnAtMemorySpan = returnAtMemorySpan(frame, callHasValueArgument);
+ callDataInfo =
+ new CallDataInfo(
+ frame, callDataSpan(frame, callHasValueArgument), currentFrame.contextNumber());
value =
- callCanTransferValue
+ callHasValueArgument
? Wei.of(currentFrame.frame().getStackItem(2).toUnsignedBigInteger())
: Wei.ZERO;
@@ -201,52 +228,15 @@ public CallSection(Hub hub, MessageFrame frame) {
hub.defers().scheduleForPostRollback(this, currentFrame);
hub.defers().scheduleForPostTransaction(this);
- if (aborts) {
- this.abortingCall(hub);
- return;
- }
-
// The CALL is now unexceptional and un-aborted
- hub.defers().scheduleForImmediateContextEntry(this);
- hub.defers().scheduleForContextReEntry(this, currentFrame);
- final WorldUpdater world = currentFrame.frame().getWorldUpdater();
-
- if (isPrecompile(calleeAddress)) {
- precompileAddress = Optional.of(calleeAddress);
- scenarioFragment.setScenario(CALL_PRC_UNDEFINED);
- // Account rows for precompile are traced at contextReEntry
-
- precompileSubsection =
- ADDRESS_TO_PRECOMPILE.get(preOpcodeCalleeSnapshot.address()).apply(hub, this);
- } else {
- Optional.ofNullable(world.get(calleeAddress))
- .ifPresentOrElse(
- account -> {
- scenarioFragment.setScenario(
- account.hasCode() ? CALL_SMC_UNDEFINED : CALL_EOA_SUCCESS_WONT_REVERT);
- },
- () -> {
- scenarioFragment.setScenario(CALL_EOA_SUCCESS_WONT_REVERT);
- });
-
- // TODO is world == worldUpdater & what happen if get doesn't work ?
- // doesn't work ?
- // TODO: write a test where the recipient of the call does not exist in the state
- }
-
- if (scenarioFragment.getScenario() == CALL_SMC_UNDEFINED) {
- this.commonValues.payGasPaidOutOfPocket(hub);
- hub.defers().scheduleForContextReEntry(firstImcFragment, currentFrame);
- finalContextFragment = ContextFragment.initializeNewExecutionContext(hub);
- final boolean isSelfCall = callerAddress.equals(calleeAddress);
- selfCallWithNonzeroValueTransfer = isSelfCall && !value.isZero();
- hub.romLex().callRomLex(currentFrame.frame());
- hub.defers().scheduleForContextExit(this, hub.callStack().futureId());
- }
-
- if (scenarioFragment.getScenario() == CALL_EOA_SUCCESS_WONT_REVERT) {
- this.commonValues.collectChildStipend(hub);
- finalContextFragment = ContextFragment.nonExecutionProvidesEmptyReturnData(hub);
+ refineUndefinedScenario(hub);
+ CallScenarioFragment.CallScenario scenario = scenarioFragment.getScenario();
+ switch (scenario) {
+ case CALL_ABORT_WONT_REVERT -> abortingCall(hub);
+ case CALL_EOA_UNDEFINED -> eoaProcessing(hub);
+ case CALL_PRC_UNDEFINED -> prcProcessing(hub);
+ case CALL_SMC_UNDEFINED -> smcProcessing(hub, frame);
+ default -> throw new RuntimeException("Illegal CALL scenario");
}
}
@@ -285,7 +275,7 @@ private void oogXCall(Hub hub) {
}
private void abortingCall(Hub hub) {
- scenarioFragment.setScenario(CALL_ABORT_WONT_REVERT);
+
postOpcodeCallerSnapshot = preOpcodeCallerSnapshot.deepCopy();
postOpcodeCalleeSnapshot = preOpcodeCalleeSnapshot.deepCopy().turnOnWarmth();
final Factories factories = hub.factories();
@@ -312,6 +302,74 @@ private void abortingCall(Hub hub) {
commonValues.collectChildStipend(hub);
}
+ /**
+ * Sets the scenario to the relevant undefined variant, i.e. either
+ *
+ * - {@link
+ * net.consensys.linea.zktracer.module.hub.fragment.scenario.CallScenarioFragment.CallScenario#CALL_PRC_UNDEFINED}
+ *
+ * - {@link
+ * net.consensys.linea.zktracer.module.hub.fragment.scenario.CallScenarioFragment.CallScenario#CALL_SMC_UNDEFINED}
+ *
+ * - {@link
+ * net.consensys.linea.zktracer.module.hub.fragment.scenario.CallScenarioFragment.CallScenario#CALL_EOA_UNDEFINED}
+ *
+ * depending on the address.
+ *
+ * @param hub
+ */
+ private void refineUndefinedScenario(Hub hub) {
+
+ final boolean aborts = hub.pch().abortingConditions().any();
+ if (aborts) {
+ scenarioFragment.setScenario(CALL_ABORT_WONT_REVERT);
+ return;
+ }
+
+ final WorldUpdater world = hub.currentFrame().frame().getWorldUpdater();
+ if (isPrecompile(calleeAddress)) {
+ precompileAddress = Optional.of(calleeAddress);
+ scenarioFragment.setScenario(CALL_PRC_UNDEFINED);
+
+ precompileSubsection =
+ ADDRESS_TO_PRECOMPILE.get(preOpcodeCalleeSnapshot.address()).apply(hub, this);
+ } else {
+ Optional.ofNullable(world.get(calleeAddress))
+ .ifPresentOrElse(
+ account -> {
+ scenarioFragment.setScenario(
+ account.hasCode() ? CALL_SMC_UNDEFINED : CALL_EOA_UNDEFINED);
+ },
+ () -> {
+ scenarioFragment.setScenario(CALL_EOA_UNDEFINED);
+ });
+ }
+ }
+
+ private void eoaProcessing(Hub hub) {
+ hub.defers().scheduleForContextReEntry(this, hub.currentFrame());
+ commonValues.collectChildStipend(hub);
+ finalContextFragment = ContextFragment.nonExecutionProvidesEmptyReturnData(hub);
+ }
+
+ private void smcProcessing(Hub hub, MessageFrame frame) {
+ final CallFrame currentFrame = hub.currentFrame();
+ hub.defers().scheduleForContextEntry(this);
+ hub.defers().scheduleForContextExit(this, hub.callStack().futureId());
+ hub.defers().scheduleForContextReEntry(this, currentFrame);
+
+ hub.defers().scheduleForContextReEntry(firstImcFragment, currentFrame);
+
+ this.commonValues.payGasPaidOutOfPocket(hub);
+ finalContextFragment = ContextFragment.initializeNewExecutionContext(hub);
+ hub.romLex().callRomLex(frame);
+ }
+
+ private void prcProcessing(Hub hub) {
+ hub.defers().scheduleForContextEntry(this);
+ hub.defers().scheduleForContextReEntry(this, hub.currentFrame());
+ }
+
@Override
public void resolvePostExecution(
Hub hub, MessageFrame frame, Operation.OperationResult operationResult) {
@@ -321,53 +379,50 @@ public void resolvePostExecution(
@Override
public void resolveUponContextEntry(Hub hub) {
- postOpcodeCallerSnapshot = preOpcodeCallerSnapshot.deepCopy().decrementBalanceBy(value);
- postOpcodeCalleeSnapshot =
- preOpcodeCalleeSnapshot.deepCopy().incrementBalanceBy(value).turnOnWarmth();
- switch (scenarioFragment.getScenario()) {
- case CALL_SMC_UNDEFINED -> {
- if (selfCallWithNonzeroValueTransfer) {
- // In case of a self-call that transfers value, the balance of the caller
- // is decremented by the value transferred. This becomes the initial state
- // of the callee, which is then credited by that value. This can happen
- // only for the SMC case.
- preOpcodeCalleeSnapshot = postOpcodeCallerSnapshot;
- postOpcodeCalleeSnapshot = preOpcodeCallerSnapshot;
- }
+ CallScenarioFragment.CallScenario scenario = scenarioFragment.getScenario();
+ checkState(scenario == CALL_SMC_UNDEFINED | scenario == CALL_PRC_UNDEFINED);
- final Factories factories = hub.factories();
- final AccountFragment firstCallerAccountFragment =
- factories
- .accountFragment()
- .make(
- preOpcodeCallerSnapshot,
- postOpcodeCallerSnapshot,
- DomSubStampsSubFragment.standardDomSubStamps(this.hubStamp(), 0));
+ postOpcodeCallerSnapshot = preOpcodeCallerSnapshot.deepCopy();
+ postOpcodeCalleeSnapshot = preOpcodeCalleeSnapshot.deepCopy().turnOnWarmth();
- final AccountFragment firstCalleeAccountFragment =
- factories
- .accountFragment()
- .makeWithTrm(
- preOpcodeCalleeSnapshot,
- postOpcodeCalleeSnapshot,
- rawCalleeAddress,
- DomSubStampsSubFragment.standardDomSubStamps(this.hubStamp(), 1));
+ if (opCode == CALL) {
+ postOpcodeCallerSnapshot.decrementBalanceBy(value);
+ postOpcodeCalleeSnapshot.incrementBalanceBy(value);
+ }
- firstCalleeAccountFragment.requiresRomlex(true);
+ // we may be doing more stuff here later
+ if (scenarioFragment.getScenario() == CALL_PRC_UNDEFINED) {
+ return;
+ }
- this.addFragments(firstCallerAccountFragment, firstCalleeAccountFragment);
- }
+ if (isNonzeroValueSelfCall()) {
+ checkState(scenarioFragment.getScenario() == CALL_SMC_UNDEFINED);
+ preOpcodeCalleeSnapshot = postOpcodeCallerSnapshot;
+ postOpcodeCalleeSnapshot = preOpcodeCallerSnapshot;
+ }
+
+ final Factories factories = hub.factories();
+ final AccountFragment firstCallerAccountFragment =
+ factories
+ .accountFragment()
+ .make(
+ preOpcodeCallerSnapshot,
+ postOpcodeCallerSnapshot,
+ DomSubStampsSubFragment.standardDomSubStamps(this.hubStamp(), 0));
- case CALL_PRC_UNDEFINED -> {}
+ final AccountFragment firstCalleeAccountFragment =
+ factories
+ .accountFragment()
+ .makeWithTrm(
+ preOpcodeCalleeSnapshot,
+ postOpcodeCalleeSnapshot,
+ rawCalleeAddress,
+ DomSubStampsSubFragment.standardDomSubStamps(this.hubStamp(), 1));
- case CALL_EOA_SUCCESS_WONT_REVERT -> {
- // Account rows for EOA calls are traced at contextReEntry
- return;
- }
+ firstCalleeAccountFragment.requiresRomlex(true);
- default -> throw new IllegalArgumentException("Should be in one of the three scenario above");
- }
+ this.addFragments(firstCallerAccountFragment, firstCalleeAccountFragment);
}
/** Resolution happens as the child context is about to terminate. */
@@ -388,11 +443,13 @@ public void resolveAtContextReEntry(Hub hub, CallFrame frame) {
// if the call is acted upon i.e. if the call is un-exceptional and un-aborted
final boolean successBit = bytesToBoolean(hub.messageFrame().getStackItem(0));
- reEntryCallerSnapshot = canonical(hub, preOpcodeCallerSnapshot.address());
- reEntryCalleeSnapshot = canonical(hub, preOpcodeCalleeSnapshot.address());
+ reEntryCallerSnapshot = canonical(hub, callerAddress);
+ reEntryCalleeSnapshot = canonical(hub, calleeAddress);
switch (scenarioFragment.getScenario()) {
- case CALL_EOA_SUCCESS_WONT_REVERT -> {
+ case CALL_EOA_UNDEFINED -> {
+ checkState(successBit);
+ scenarioFragment.setScenario(CALL_EOA_SUCCESS_WONT_REVERT);
emptyCodeFirstCoupleOfAccountFragments(hub);
}
@@ -403,6 +460,11 @@ public void resolveAtContextReEntry(Hub hub, CallFrame frame) {
scenarioFragment.setScenario(CALL_PRC_FAILURE);
}
emptyCodeFirstCoupleOfAccountFragments(hub);
+
+ CallFrame prcFrame = hub.callStack().getById(frame.childFramesId().getLast());
+ finalContextFragment =
+ ContextFragment.updateReturnData(
+ hub, prcFrame.contextNumber(), prcFrame.outputDataSpan());
}
case CALL_SMC_UNDEFINED -> {
@@ -413,10 +475,19 @@ public void resolveAtContextReEntry(Hub hub, CallFrame frame) {
return;
}
+ AccountSnapshot beforeFailureCallerSnapshot =
+ postOpcodeCallerSnapshot.deepCopy().setDeploymentInfo(hub);
+ AccountSnapshot afterFailureCallerSnapshot =
+ preOpcodeCallerSnapshot.deepCopy().setDeploymentInfo(hub);
+ AccountSnapshot beforeFailureCalleeSnapshot =
+ postOpcodeCalleeSnapshot.deepCopy().setDeploymentInfo(hub);
+ AccountSnapshot afterFailureCalleeSnapshot =
+ preOpcodeCalleeSnapshot.deepCopy().setDeploymentInfo(hub).turnOnWarmth();
+
// CALL_SMC_FAILURE_XXX case
scenarioFragment.setScenario(CALL_SMC_FAILURE_WONT_REVERT);
- if (selfCallWithNonzeroValueTransfer) {
+ if (isNonzeroValueSelfCall()) {
childContextExitCallerSnapshot.decrementBalanceBy(value);
reEntryCalleeSnapshot.decrementBalanceBy(value);
}
@@ -429,8 +500,8 @@ public void resolveAtContextReEntry(Hub hub, CallFrame frame) {
hub.factories()
.accountFragment()
.make(
- childContextExitCallerSnapshot,
- reEntryCallerSnapshot,
+ beforeFailureCallerSnapshot,
+ afterFailureCallerSnapshot,
DomSubStampsSubFragment.revertsWithChildDomSubStamps(
this.hubStamp(), childContextRevertStamp, 2));
@@ -438,8 +509,8 @@ public void resolveAtContextReEntry(Hub hub, CallFrame frame) {
hub.factories()
.accountFragment()
.make(
- childContextExitCalleeSnapshot,
- reEntryCalleeSnapshot,
+ beforeFailureCalleeSnapshot,
+ afterFailureCalleeSnapshot,
DomSubStampsSubFragment.revertsWithChildDomSubStamps(
this.hubStamp(), childContextRevertStamp, 3));
@@ -453,11 +524,8 @@ public void resolveAtContextReEntry(Hub hub, CallFrame frame) {
@Override
public void resolveUponRollback(Hub hub, MessageFrame messageFrame, CallFrame callFrame) {
final Factories factory = hub.factories();
- postRollbackCalleeSnapshot = canonical(hub, preOpcodeCalleeSnapshot.address());
- postRollbackCallerSnapshot = canonical(hub, preOpcodeCallerSnapshot.address());
-
- final boolean selfCall =
- postOpcodeCalleeSnapshot.address().equals(postOpcodeCallerSnapshot.address());
+ postRollbackCalleeSnapshot = canonical(hub, calleeAddress);
+ postRollbackCallerSnapshot = canonical(hub, callerAddress);
final CallScenarioFragment.CallScenario callScenario = scenarioFragment.getScenario();
switch (callScenario) {
@@ -488,7 +556,7 @@ public void resolvePostTransaction(
"Call scenario = %s, HUB_STAMP = %s, successBit = %s",
scenarioFragment.getScenario(), this.hubStamp(), isSuccessful));
- if (scenario.isPrecompileScenario()) {
+ if (scenario.isPrcCallScenario()) {
this.addFragments(precompileSubsection.fragments());
}
@@ -516,12 +584,12 @@ private void completeEoaSuccessWillRevert(Factories factory) {
scenarioFragment.setScenario(CALL_EOA_SUCCESS_WILL_REVERT);
final AccountSnapshot callerRightBeforeRollBack =
- postOpcodeCallerSnapshot.deepCopy().copyDeploymentInfoFrom(postRollbackCallerSnapshot);
+ reEntryCallerSnapshot.deepCopy().copyDeploymentInfoFrom(postRollbackCallerSnapshot);
final AccountSnapshot callerRightAfterRollBack =
preOpcodeCallerSnapshot.deepCopy().copyDeploymentInfoFrom(postRollbackCallerSnapshot);
final AccountSnapshot calleeRightBeforeRollBack =
- postOpcodeCalleeSnapshot.deepCopy().copyDeploymentInfoFrom(postRollbackCalleeSnapshot);
+ reEntryCalleeSnapshot.deepCopy().copyDeploymentInfoFrom(postRollbackCalleeSnapshot);
final AccountSnapshot calleeRightAfterRollBack =
preOpcodeCalleeSnapshot.deepCopy().copyDeploymentInfoFrom(postRollbackCalleeSnapshot);
@@ -623,15 +691,56 @@ private void emptyCodeFirstCoupleOfAccountFragments(final Hub hub) {
this.addFragments(firstCallerAccountFragment, firstCalleeAccountFragment);
}
- private MemorySpan returnDataMemorySpan(MessageFrame currentFrame, boolean callCanTransferValue) {
- final int returnDataOffset =
- callCanTransferValue
- ? bytesToInt(currentFrame.getStackItem(5))
- : bytesToInt(currentFrame.getStackItem(4));
- final int returnDataLength =
- callCanTransferValue
- ? bytesToInt(currentFrame.getStackItem(6))
- : bytesToInt(currentFrame.getStackItem(5));
- return MemorySpan.fromStartLength(returnDataOffset, returnDataLength);
+ private MemorySpan callDataSpan(MessageFrame frame, boolean callHasValueArgument) {
+ final long callDataSize =
+ callHasValueArgument
+ ? Words.clampedToLong(frame.getStackItem(4))
+ : Words.clampedToLong(frame.getStackItem(3));
+
+ if (callDataSize == 0) {
+ return MemorySpan.empty();
+ }
+
+ final long returnAtOffset =
+ callHasValueArgument
+ ? Words.clampedToLong(frame.getStackItem(3))
+ : Words.clampedToLong(frame.getStackItem(2));
+ return MemorySpan.fromStartLength(returnAtOffset, callDataSize);
+ }
+
+ /**
+ * The {@link #returnAtMemorySpan(MessageFrame, boolean)} method implements the spec logic for
+ * defining the ``returnAtMemorySpan`` of a CALL. The main point being: if its capacity is zero we
+ * require that {@link MemorySpan} to be {@link MemorySpan#empty()}.
+ *
+ * @param frame
+ * @param callHasValueArgument
+ * @return
+ */
+ private MemorySpan returnAtMemorySpan(MessageFrame frame, boolean callHasValueArgument) {
+ final long returnAtCapacity =
+ callHasValueArgument
+ ? Words.clampedToLong(frame.getStackItem(6))
+ : Words.clampedToLong(frame.getStackItem(5));
+
+ if (returnAtCapacity == 0) {
+ return MemorySpan.empty();
+ }
+
+ final long returnAtOffset =
+ callHasValueArgument
+ ? Words.clampedToLong(frame.getStackItem(5))
+ : Words.clampedToLong(frame.getStackItem(4));
+ return MemorySpan.fromStartLength(returnAtOffset, returnAtCapacity);
+ }
+
+ private boolean isSelfCall() {
+ checkState(scenarioFragment.getScenario().isIndefiniteSmcCallScenario());
+ return calleeAddress.equals(callerAddress);
+ }
+
+ private boolean isNonzeroValueSelfCall() {
+ checkState(scenarioFragment.getScenario().isIndefiniteSmcCallScenario());
+ return isSelfCall() && !value.isZero();
}
}
diff --git a/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/section/call/precompileSubsection/PrecompileSubsection.java b/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/section/call/precompileSubsection/PrecompileSubsection.java
index ce04a893d8..e827dacab9 100644
--- a/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/section/call/precompileSubsection/PrecompileSubsection.java
+++ b/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/section/call/precompileSubsection/PrecompileSubsection.java
@@ -48,10 +48,7 @@
@Getter
@Accessors(fluent = true)
public class PrecompileSubsection
- implements ImmediateContextEntryDefer,
- ContextExitDefer,
- ContextReEntryDefer,
- PostRollbackDefer {
+ implements ContextEntryDefer, ContextExitDefer, ContextReEntryDefer, PostRollbackDefer {
public final CallSection callSection;
@@ -98,7 +95,7 @@ public PrecompileSubsection(final Hub hub, final CallSection callSection) {
final MessageFrame messageFrame = hub.messageFrame();
- hub.defers().scheduleForImmediateContextEntry(this); // gas & input data, ...
+ hub.defers().scheduleForContextEntry(this); // gas & input data, ...
hub.defers().scheduleForContextExit(this, hub.callStack().futureId());
hub.defers().scheduleForContextReEntry(this, hub.currentFrame()); // success bit & return data
@@ -115,12 +112,12 @@ public PrecompileSubsection(final Hub hub, final CallSection callSection) {
final OpCode opCode = hub.opCode();
final long offset =
Words.clampedToLong(
- opCode.callCanTransferValue()
+ opCode.callHasValueArgument()
? messageFrame.getStackItem(3)
: messageFrame.getStackItem(2));
final long length =
Words.clampedToLong(
- opCode.callCanTransferValue()
+ opCode.callHasValueArgument()
? messageFrame.getStackItem(4)
: messageFrame.getStackItem(3));
callDataMemorySpan = new MemorySpan(offset, length);
diff --git a/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/section/halt/SelfdestructSection.java b/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/section/halt/SelfdestructSection.java
index 6b3ac62a8b..f548c81777 100644
--- a/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/section/halt/SelfdestructSection.java
+++ b/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/section/halt/SelfdestructSection.java
@@ -62,10 +62,11 @@ public class SelfdestructSection extends TraceSection
AccountSnapshot recipientAccountBefore;
AccountSnapshot recipientAccountAfter;
- final boolean selfdestructTargetsItself;
@Getter boolean selfDestructWasReverted = false;
- public SelfdestructSection(Hub hub) {
+ ContextFragment finalUnexceptionalContextFragment;
+
+ public SelfdestructSection(Hub hub, MessageFrame frame) {
// up to 8 = 1 + 7 rows
super(hub, (short) 8);
@@ -75,29 +76,28 @@ public SelfdestructSection(Hub hub) {
hubStamp = hub.stamp();
exceptions = hub.pch().exceptions();
- final MessageFrame frame = hub.messageFrame();
-
// Account
addressWhichMaySelfDestruct = frame.getRecipientAddress();
- selfdestructorAccountBefore = AccountSnapshot.canonical(hub, addressWhichMaySelfDestruct);
+ selfdestructorAccountBefore =
+ AccountSnapshot.canonical(hub, frame.getWorldUpdater(), addressWhichMaySelfDestruct);
// Recipient
recipientAddressUntrimmed = frame.getStackItem(0);
recipientAddress = Address.extract(Bytes32.leftPad(recipientAddressUntrimmed));
- selfdestructTargetsItself = addressWhichMaySelfDestruct.equals(recipientAddress);
-
- selfdestructScenarioFragment = new SelfdestructScenarioFragment();
// SCN fragment
- this.addFragment(selfdestructScenarioFragment);
+ selfdestructScenarioFragment = new SelfdestructScenarioFragment();
if (Exceptions.any(exceptions)) {
selfdestructScenarioFragment.setScenario(
SelfdestructScenarioFragment.SelfdestructScenario.SELFDESTRUCT_EXCEPTION);
}
// CON fragment (1)
- final ContextFragment contextFragment = ContextFragment.readCurrentContextData(hub);
- this.addFragment(contextFragment);
+ final ContextFragment readCurrentContext = ContextFragment.readCurrentContextData(hub);
+
+ this.addStack(hub); // stack fragments
+ this.addFragment(selfdestructScenarioFragment); // scenario fragment
+ this.addFragment(readCurrentContext);
// STATICX case
if (Exceptions.staticFault(exceptions)) {
@@ -109,9 +109,9 @@ public SelfdestructSection(Hub hub) {
checkArgument(exceptions == OUT_OF_GAS_EXCEPTION);
recipientAccountBefore =
- selfdestructTargetsItself
+ selfdestructTargetsItself()
? selfdestructorAccountBefore
- : AccountSnapshot.canonical(hub, recipientAddress);
+ : AccountSnapshot.canonical(hub, frame.getWorldUpdater(), recipientAddress);
selfdestructorFirstAccountFragment =
hub.factories()
@@ -136,6 +136,10 @@ public SelfdestructSection(Hub hub) {
}
// Unexceptional case
+ finalUnexceptionalContextFragment =
+ ContextFragment.executionProvidesEmptyReturnData(
+ hub, hub.callStack().currentCallFrame().contextNumber());
+
final Map - the transaction targets a smart contract that immediately RETURN's /
+ * REVERT's with zero size, huge offset
+ *
+ * @param opCode either {@link OpCode#RETURN} or {@link OpCode#REVERT}
+ */
+ @ParameterizedTest
+ @EnumSource(
+ value = OpCode.class,
+ names = {"RETURN", "REVERT"})
+ void rootContextMessageCall(OpCode opCode) {
+ BytecodeCompiler program = BytecodeCompiler.newProgram();
+ zeroSizeReturnOrRevert(program, opCode);
+ BytecodeRunner.of(program.compile()).run();
+ }
+
+ /**
+ * The following test does the following:
+ *
+ * - a deployment transaction that runs init code that immediately RETURN's /
+ * REVERT's with zero size, huge offset
+ *
+ * @param opCode either {@link OpCode#RETURN} or {@link OpCode#REVERT}
+ */
+ @ParameterizedTest
+ @EnumSource(
+ value = OpCode.class,
+ names = {"RETURN", "REVERT"})
+ void rootContextDeploymentTransaction(OpCode opCode) {
+ checkArgument(opCode.isAnyOf(RETURN, REVERT));
+
+ BytecodeCompiler initCode = BytecodeCompiler.newProgram();
+ zeroSizeReturnOrRevert(initCode, opCode);
+
+ Transaction deploymentTransaction =
+ ToyTransaction.builder()
+ .sender(userAccount)
+ .keyPair(keyPair)
+ .gasPrice(Wei.of(8L))
+ .gasLimit(1_000_000L)
+ .value(Wei.of(1L))
+ .payload(initCode.compile())
+ .build();
+
+ ToyExecutionEnvironmentV2.builder()
+ .accounts(List.of(userAccount))
+ .transaction(deploymentTransaction)
+ .build()
+ .run();
+ }
+
+ /**
+ * The following test does the following:
+ *
+ * - CALL a smart contract that immediately RETURN's / REVERT's with zero
+ * size, huge offset
+ *
+ * - back in the root context, check the BALANCE of both accounts for good measure
+ *
+ * @param opCode either {@link OpCode#RETURN} or {@link OpCode#REVERT}
+ */
+ @ParameterizedTest
+ @EnumSource(
+ value = OpCode.class,
+ names = {"RETURN", "REVERT"})
+ void nonRootContextMessageCall(OpCode opCode) {
+ checkArgument(opCode.isAnyOf(RETURN, REVERT));
+
+ Address calleeAccountAddress = Address.fromHexString("ca11eec0def3fd");
+ BytecodeCompiler calleeAccountCode = BytecodeCompiler.newProgram();
+ zeroSizeReturnOrRevert(calleeAccountCode, opCode);
+
+ ToyAccount calleeAccount =
+ ToyAccount.builder()
+ .address(calleeAccountAddress)
+ .nonce(71)
+ .balance(Wei.of(512L))
+ .code(calleeAccountCode.compile())
+ .build();
+
+ Address callerAccountAddress = Address.fromHexString("ca11e7c0de");
+ BytecodeCompiler callerAccountCode = BytecodeCompiler.newProgram();
+ callerAccountCode
+ .push(0) // r@c
+ .push(0) // r@o
+ .op(CALLDATASIZE) // cds
+ .push(0) // cdo
+ .push(255) // 0xff value
+ .push(calleeAccountAddress)
+ .push(100_000) // gas
+ .op(CALL)
+ // then we check balances for good measure
+ .push(calleeAccountAddress)
+ .op(BALANCE)
+ .op(SELFBALANCE);
+
+ ToyAccount callerAccount =
+ ToyAccount.builder()
+ .address(callerAccountAddress)
+ .nonce(127)
+ .balance(Wei.of(1_000_000L))
+ .code(callerAccountCode.compile())
+ .build();
+
+ Transaction transaction =
+ ToyTransaction.builder()
+ .sender(userAccount)
+ .keyPair(keyPair)
+ .to(callerAccount)
+ .gasPrice(Wei.of(8L))
+ .gasLimit(1_000_000L)
+ .value(Wei.of(1L))
+ .build();
+
+ ToyExecutionEnvironmentV2.builder()
+ .accounts(List.of(userAccount, callerAccount, calleeAccount))
+ .transaction(transaction)
+ .build()
+ .run();
+ }
+
+ /**
+ * The following test does the following:
+ *
+ * - create a transaction with payload that will later be interpreted as init code
+ *
+ * - perform a full copy of the call data to RAM
+ *
+ * - perform a CREATE operation, using the call data as init code
+ *
+ * - the init code does an immediate RETURN / REVERT with zero size, huge offset
+ *
+ * - back in the root context, check the BALANCE of the createe account (or 0) for good
+ * measure
+ *
+ * @param opCode either {@link OpCode#RETURN} or {@link OpCode#REVERT}
+ */
+ @ParameterizedTest
+ @EnumSource(
+ value = OpCode.class,
+ names = {"RETURN", "REVERT"})
+ void nonRootContextDeployment(OpCode opCode) {
+ checkArgument(opCode.isAnyOf(RETURN, REVERT));
+
+ BytecodeCompiler creatorAccountCode = BytecodeCompiler.newProgram();
+ loadTheFullCallDataToRam(creatorAccountCode, 0);
+ creatorAccountCode
+ .op(CALLDATASIZE) // init code size
+ .push(0) // init code offset
+ .push(255) // 0xff value
+ .op(CREATE)
+ .op(BALANCE) // should return 255 in the RETURN case, 0 in the REVERT case
+ ;
+
+ ToyAccount creatorAccount =
+ ToyAccount.builder()
+ .balance(Wei.of(1_000_000L))
+ .code(creatorAccountCode.compile())
+ .nonce(891)
+ .address(Address.fromHexString("c0dec0ffeef3fd"))
+ .build();
+
+ BytecodeCompiler payload = BytecodeCompiler.newProgram();
+ zeroSizeReturnOrRevert(payload, opCode);
+ Transaction transaction =
+ ToyTransaction.builder()
+ .sender(userAccount)
+ .to(creatorAccount)
+ .keyPair(keyPair)
+ .gasPrice(Wei.of(8L))
+ .gasLimit(1_000_000L)
+ .value(Wei.of(1L))
+ .payload(payload.compile()) // here: call data, later: init code
+ .build();
+
+ ToyExecutionEnvironmentV2.builder()
+ .accounts(List.of(userAccount, creatorAccount))
+ .transaction(transaction)
+ .build()
+ .run();
+ }
+
+ /**
+ * @param opCode
+ * @return
+ */
+ private void zeroSizeReturnOrRevert(BytecodeCompiler program, OpCode opCode) {
+ checkArgument(opCode.isAnyOf(RETURN, REVERT));
+ program
+ .push(0) // zero size
+ .push(hugeOffset) // huge offset
+ .op(opCode);
+ }
+
+ public void loadTheFullCallDataToRam(BytecodeCompiler program, int targetOffset) {
+ program.op(CALLDATASIZE).push(0).push(targetOffset).op(CALLDATACOPY);
+ }
+}
diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/DoubleCall.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/DoubleCall.java
index 9c78596adc..f74fa5a3ed 100644
--- a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/DoubleCall.java
+++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/DoubleCall.java
@@ -14,60 +14,68 @@
*/
package net.consensys.linea.zktracer.instructionprocessing.callTests;
-import static net.consensys.linea.zktracer.instructionprocessing.callTests.Utilities.*;
+import static net.consensys.linea.zktracer.instructionprocessing.utilities.Calls.*;
import static net.consensys.linea.zktracer.opcode.OpCode.*;
import net.consensys.linea.testing.BytecodeCompiler;
import net.consensys.linea.testing.BytecodeRunner;
+import net.consensys.linea.zktracer.opcode.OpCode;
import org.hyperledger.besu.datatypes.Address;
-import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
public class DoubleCall {
- /** Same address */
- @Test
- void doubleCallToSameAddressWontRevert() {
+ /** Same selfDestructorAddress */
+ @ParameterizedTest
+ @EnumSource(
+ value = OpCode.class,
+ names = {"CALL", "CALLCODE", "DELEGATECALL", "STATICCALL"})
+ void doubleCallToSameAddressWontRevert(OpCode callOpCode) {
BytecodeCompiler program = BytecodeCompiler.newProgram();
- simpleCall(program, CALL, 0, Address.fromHexString(eoaAddress), 1, 0, 0, 0, 0);
+ appendCall(program, callOpCode, 0, Address.fromHexString(eoaAddress), 1, 0, 0, 0, 0);
+ appendCall(program, callOpCode, 0, Address.fromHexString(eoaAddress), 2, 0, 0, 0, 0);
- simpleCall(program, CALL, 0, Address.fromHexString(eoaAddress), 2, 0, 0, 0, 0);
-
- BytecodeRunner.of(program.compile()).run();
+ BytecodeRunner.of(program).run();
}
- @Test
- void doubleCallToSameAddressWillRevert() {
+ @ParameterizedTest
+ @EnumSource(
+ value = OpCode.class,
+ names = {"CALL", "CALLCODE", "DELEGATECALL", "STATICCALL"})
+ void doubleCallToSameAddressWillRevert(OpCode callOpCode) {
BytecodeCompiler program = BytecodeCompiler.newProgram();
- simpleCall(program, CALL, 0, Address.fromHexString(eoaAddress), 1, 0, 0, 0, 0);
-
- simpleCall(program, CALL, 0, Address.fromHexString(eoaAddress), 2, 0, 0, 0, 0);
-
- program.op(REVERT);
+ appendCall(program, callOpCode, 0, Address.fromHexString(eoaAddress), 1, 0, 0, 0, 0);
+ appendCall(program, callOpCode, 0, Address.fromHexString(eoaAddress), 2, 0, 0, 0, 0);
+ program.op(REVERT); // N.B. The stack contains the two success bits
- BytecodeRunner.of(program.compile()).run();
+ BytecodeRunner.of(program).run();
}
- /** Different address */
- @Test
- void doubleCallTodifferentAddressesWontRevert() {
+ /** Different selfDestructorAddress */
+ @ParameterizedTest
+ @EnumSource(
+ value = OpCode.class,
+ names = {"CALL", "CALLCODE", "DELEGATECALL", "STATICCALL"})
+ void doubleCallTodifferentAddressesWontRevert(OpCode callOpCode) {
BytecodeCompiler program = BytecodeCompiler.newProgram();
- simpleCall(program, CALL, 0, Address.fromHexString(eoaAddress), 1, 0, 0, 0, 0);
+ appendCall(program, callOpCode, 0, Address.fromHexString(eoaAddress), 1, 0, 0, 0, 0);
+ appendCall(program, callOpCode, 0, Address.fromHexString(eoaAddress2), 2, 0, 0, 0, 0);
- simpleCall(program, CALL, 0, Address.fromHexString(eoaAddress2), 2, 0, 0, 0, 0);
-
- BytecodeRunner.of(program.compile()).run();
+ BytecodeRunner.of(program).run();
}
- @Test
- void doubleCallTodifferentAddressesWillRevert() {
+ @ParameterizedTest
+ @EnumSource(
+ value = OpCode.class,
+ names = {"CALL", "CALLCODE", "DELEGATECALL", "STATICCALL"})
+ void doubleCallTodifferentAddressesWillRevert(OpCode callOpCode) {
BytecodeCompiler program = BytecodeCompiler.newProgram();
- simpleCall(program, CALL, 0, Address.fromHexString(eoaAddress), 1, 0, 0, 0, 0);
-
- simpleCall(program, CALL, 0, Address.fromHexString(eoaAddress2), 2, 0, 0, 0, 0);
-
+ appendCall(program, callOpCode, 0, Address.fromHexString(eoaAddress), 1, 0, 0, 0, 0);
+ appendCall(program, callOpCode, 0, Address.fromHexString(eoaAddress2), 2, 0, 0, 0, 0);
program.push(13).push(71); // the stack already contains two items but why not ...
program.op(REVERT);
- BytecodeRunner.of(program.compile()).run();
+ BytecodeRunner.of(program).run();
}
}
diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/TrimmingTests.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/TrimmingTests.java
index f33cfb72c7..106637ad5e 100644
--- a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/TrimmingTests.java
+++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/TrimmingTests.java
@@ -14,13 +14,12 @@
*/
package net.consensys.linea.zktracer.instructionprocessing.callTests;
-import static net.consensys.linea.zktracer.instructionprocessing.callTests.Utilities.eoaAddress;
-import static net.consensys.linea.zktracer.instructionprocessing.callTests.Utilities.untrimmedEoaAddress;
+import static net.consensys.linea.zktracer.instructionprocessing.utilities.Calls.eoaAddress;
+import static net.consensys.linea.zktracer.instructionprocessing.utilities.Calls.untrimmedEoaAddress;
import static net.consensys.linea.zktracer.opcode.OpCode.*;
import net.consensys.linea.testing.BytecodeCompiler;
import net.consensys.linea.testing.BytecodeRunner;
-import org.apache.tuweni.bytes.Bytes;
import org.junit.jupiter.api.Test;
public class TrimmingTests {
@@ -28,7 +27,7 @@ public class TrimmingTests {
@Test
void targetTrimming() {
- Bytes bytecode =
+ BytecodeCompiler bytecode =
BytecodeCompiler.newProgram()
.push(1)
.push(2)
@@ -55,8 +54,7 @@ void targetTrimming() {
.push(untrimmedEoaAddress) // address
.op(BALANCE)
.push(eoaAddress) // address
- .op(EXTCODEHASH)
- .compile();
+ .op(EXTCODEHASH);
BytecodeRunner.of(bytecode).run();
}
diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/Utilities.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/Utilities.java
index e14877e80c..12cb49a107 100644
--- a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/Utilities.java
+++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/Utilities.java
@@ -39,7 +39,7 @@ public static void fullGasCall(
int rao,
int rac) {
program.push(rac).push(rao).push(cds).push(cdo);
- if (callOpcode.callCanTransferValue()) {
+ if (callOpcode.callHasValueArgument()) {
program.push(value);
}
program.push(to).op(GAS).op(callOpcode).op(POP);
@@ -56,7 +56,7 @@ public static void simpleCall(
int rao,
int rac) {
program.push(rac).push(rao).push(cds).push(cdo);
- if (callOpcode.callCanTransferValue()) {
+ if (callOpcode.callHasValueArgument()) {
program.push(value);
}
program.push(to).push(gas).op(callOpcode);
@@ -71,7 +71,7 @@ public static void appendInsufficientBalanceCall(
int cds,
int rao,
int rac) {
- checkArgument(callOpcode.callCanTransferValue());
+ checkArgument(callOpcode.callHasValueArgument());
program
.push(rac)
.push(rao)
diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/abort/BalanceAbortTests.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/abort/BalanceAbortTests.java
index a602be5a64..32f072ae87 100644
--- a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/abort/BalanceAbortTests.java
+++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/abort/BalanceAbortTests.java
@@ -14,15 +14,15 @@
*/
package net.consensys.linea.zktracer.instructionprocessing.callTests.abort;
-import static net.consensys.linea.zktracer.instructionprocessing.callTests.Utilities.appendInsufficientBalanceCall;
+import static net.consensys.linea.zktracer.instructionprocessing.utilities.Calls.*;
import static net.consensys.linea.zktracer.opcode.OpCode.*;
-import static net.consensys.linea.zktracer.opcode.OpCode.CALL;
import net.consensys.linea.testing.BytecodeCompiler;
import net.consensys.linea.testing.BytecodeRunner;
-import org.apache.tuweni.bytes.Bytes;
+import net.consensys.linea.zktracer.opcode.OpCode;
import org.hyperledger.besu.datatypes.Address;
-import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
/**
* The arithmetization has a two aborting scenarios for CALL's
@@ -36,49 +36,52 @@ public class BalanceAbortTests {
final String eoaAddress = "abcdef0123456789";
- @Test
- void insufficientBalanceAbortWarmsUpTarget() {
-
- Bytes bytecode =
- BytecodeCompiler.newProgram()
- .push(1)
- .push(2)
- .push(3)
- .push(4)
- .op(SELFBALANCE)
- .push(1)
- .op(ADD) // our balance + 1
- .push(eoaAddress) // address
- .push(0) // gas
- .op(CALL) // CALL_ABORT_WONT_REVERT
- .op(POP)
- .push(eoaAddress) // address
- .op(EXTCODESIZE) // discounted pricing
- .compile();
-
- BytecodeRunner.of(bytecode).run();
- }
-
- /** scenario/CALL_ABORT_WILL_REVERT; reverts the warmth; */
- @Test
- void insufficientBalanceAbortWillRevert() {
-
+ /**
+ * This test has different behaviour for CALL and CALLCODE vs the other CALL-type
+ * instructions. The point being: these two can transfer value, the others can't. As such they are
+ * the only instructions that can trigger the desired INSUFFICIENT_BALANCE_ABORT.
+ *
+ * This test should trigger scenario/CALL_ABORT_WONT_REVERT for both CALL and
+ * CALLCODE.
+ *
+ * @param callOpCode
+ */
+ @ParameterizedTest
+ @EnumSource(
+ value = OpCode.class,
+ names = {"CALL", "CALLCODE"})
+ void insufficientBalanceAbortWarmsUpTarget(OpCode callOpCode) {
BytecodeCompiler program = BytecodeCompiler.newProgram();
appendInsufficientBalanceCall(
- program, CALL, 1000, Address.fromHexString(eoaAddress), 0, 0, 0, 0);
- program.push(6).push(7).op(REVERT);
- Bytes bytecode = program.compile();
- BytecodeRunner.of(bytecode).run();
+ program, callOpCode, 1000, Address.fromHexString(eoaAddress), 4, 3, 2, 1);
+ program
+ .op(POP)
+ .push(eoaAddress) // address
+ .op(EXTCODESIZE) // discounted pricing since warm
+ .compile();
+
+ BytecodeRunner.of(program).run();
}
- /** scenario/CALL_ABORT_WONT_REVERT */
- @Test
- void insufficientBalanceAbortWontRevert() {
+ /**
+ * The same comments apply as for {@link #insufficientBalanceAbortWarmsUpTarget(OpCode)}. In this
+ * test we further impose a REVERT which will affect the warmth of the target address.
+ *
+ * This test should trigger scenario/CALL_ABORT_WILL_REVERT for both CALL and
+ * CALLCODE.
+ *
+ * @param callOpCode
+ */
+ @ParameterizedTest
+ @EnumSource(
+ value = OpCode.class,
+ names = {"CALL", "CALLCODE"})
+ void insufficientBalanceAbortWillRevert(OpCode callOpCode) {
BytecodeCompiler program = BytecodeCompiler.newProgram();
appendInsufficientBalanceCall(
- program, CALL, 1000, Address.fromHexString(eoaAddress), 0, 0, 0, 0);
- Bytes bytecode = program.compile();
- BytecodeRunner.of(bytecode).run();
+ program, callOpCode, 1000, Address.fromHexString(eoaAddress), 0, 0, 0, 0);
+ program.push(6).push(7).op(REVERT);
+ BytecodeRunner.of(program).run();
}
}
diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/abort/CallStackDepthAbortTests.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/abort/CallStackDepthAbortTests.java
index ad369914c0..fe7bc53d6d 100644
--- a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/abort/CallStackDepthAbortTests.java
+++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/abort/CallStackDepthAbortTests.java
@@ -14,53 +14,43 @@
*/
package net.consensys.linea.zktracer.instructionprocessing.callTests.abort;
+import static net.consensys.linea.zktracer.instructionprocessing.utilities.Calls.appendRecursiveSelfCall;
+import static net.consensys.linea.zktracer.instructionprocessing.utilities.Calls.appendRevert;
import static net.consensys.linea.zktracer.opcode.OpCode.*;
import net.consensys.linea.testing.BytecodeCompiler;
import net.consensys.linea.testing.BytecodeRunner;
-import org.apache.tuweni.bytes.Bytes;
-import org.junit.jupiter.api.Test;
+import net.consensys.linea.zktracer.opcode.OpCode;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
/**
* Attempt to trigger the maximum call stack depth abort. We put everything to 0 to avoid memory
* expansion costs. We will want to revert so we transfer value to see the effect of reverting.
*/
public class CallStackDepthAbortTests {
- @Test
- void attemptAtCallStackDepthAbortWillRevert() {
+ @ParameterizedTest
+ @EnumSource(
+ value = OpCode.class,
+ names = {"CALL", "CALLCODE", "DELEGATECALL", "STATICCALL"})
+ void attemptAtCallStackDepthAbortWillRevert(OpCode callOpCode) {
- Bytes bytecode =
- BytecodeCompiler.newProgram()
- .push(0)
- .push(0)
- .push(0)
- .push(0) //
- .push(5) // value
- .op(ADDRESS) // current address
- .op(GAS) // providing all available gas
- .op(CALL) // self-call
- .push(6)
- .push(7)
- .op(REVERT)
- .compile();
+ BytecodeCompiler program = BytecodeCompiler.newProgram();
+ appendRecursiveSelfCall(program, callOpCode);
+ appendRevert(program, 6, 7);
- BytecodeRunner.of(bytecode).run();
+ BytecodeRunner.of(program).run();
}
- @Test
- void attemptAtCallStackDepthAbortWontRevert() {
+ @ParameterizedTest
+ @EnumSource(
+ value = OpCode.class,
+ names = {"CALL", "CALLCODE", "DELEGATECALL", "STATICCALL"})
+ void attemptAtCallStackDepthAbort(OpCode callOpCode) {
- Bytes bytecode =
- BytecodeCompiler.newProgram()
- .push(0)
- .push(0)
- .push(0)
- .push(0)
- .op(ADDRESS)
- .op(GAS) // providing as much gas as possible
- .op(STATICCALL) // self-call
- .compile();
+ BytecodeCompiler program = BytecodeCompiler.newProgram();
+ appendRecursiveSelfCall(program, callOpCode);
- BytecodeRunner.of(bytecode).run();
+ BytecodeRunner.of(program).run();
}
}
diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/abort/MultiCallAbortTests.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/abort/MultiCallAbortTests.java
index 351c3aaf0a..7739fc3901 100644
--- a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/abort/MultiCallAbortTests.java
+++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/abort/MultiCallAbortTests.java
@@ -14,7 +14,7 @@
*/
package net.consensys.linea.zktracer.instructionprocessing.callTests.abort;
-import static net.consensys.linea.zktracer.instructionprocessing.callTests.Utilities.*;
+import static net.consensys.linea.zktracer.instructionprocessing.utilities.Calls.*;
import static net.consensys.linea.zktracer.opcode.OpCode.*;
import net.consensys.linea.testing.BytecodeCompiler;
@@ -29,7 +29,7 @@ public class MultiCallAbortTests {
@Test
void normalCallThenAbortedCallToEoaThenRevert() {
BytecodeCompiler program = BytecodeCompiler.newProgram();
- simpleCall(program, CALL, 0, Address.fromHexString(eoaAddress), 1, 0, 0, 0, 0);
+ appendCall(program, CALL, 0, Address.fromHexString(eoaAddress), 1, 0, 0, 0, 0);
appendInsufficientBalanceCall(
program, CALL, 1000, Address.fromHexString(eoaAddress), 0, 0, 0, 0);
program.push(6).push(7).op(REVERT);
@@ -42,7 +42,7 @@ void abortedCallNormalCallToEoaThenRevert() {
BytecodeCompiler program = BytecodeCompiler.newProgram();
appendInsufficientBalanceCall(
program, CALL, 1000, Address.fromHexString(eoaAddress), 0, 0, 0, 0);
- simpleCall(program, CALL, 0, Address.fromHexString(eoaAddress), 1, 0, 0, 0, 0);
+ appendCall(program, CALL, 0, Address.fromHexString(eoaAddress), 1, 0, 0, 0, 0);
program.push(6).push(7).op(REVERT);
Bytes bytecode = program.compile();
BytecodeRunner.of(bytecode).run();
@@ -54,7 +54,7 @@ void balanceThenAbortedCallToEoaThenRevert() {
program.push(eoaAddress).op(BALANCE).op(POP);
appendInsufficientBalanceCall(
program, CALL, 1000, Address.fromHexString(eoaAddress), 0, 0, 0, 0);
- simpleCall(program, CALL, 0, Address.fromHexString(eoaAddress), 0, 0, 0, 0, 0);
+ appendCall(program, CALL, 0, Address.fromHexString(eoaAddress), 0, 0, 0, 0, 0);
program.push(6).push(7).op(REVERT);
Bytes bytecode = program.compile();
BytecodeRunner.of(bytecode).run();
@@ -65,7 +65,7 @@ void abortedCallThenBalanceToEoaThenRevert() {
BytecodeCompiler program = BytecodeCompiler.newProgram();
appendInsufficientBalanceCall(
program, CALL, 1000, Address.fromHexString(eoaAddress), 0, 0, 0, 0);
- simpleCall(program, CALL, 0, Address.fromHexString(eoaAddress), 0, 0, 0, 0, 0);
+ appendCall(program, CALL, 0, Address.fromHexString(eoaAddress), 0, 0, 0, 0, 0);
program.push(6).push(7).op(REVERT);
Bytes bytecode = program.compile();
BytecodeRunner.of(bytecode).run();
diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/eoa/EoaTests.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/eoa/EoaTests.java
deleted file mode 100644
index 4d438d7d2b..0000000000
--- a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/eoa/EoaTests.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Copyright Consensys Software Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
- * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations under the License.
- *
- * SPDX-License-Identifier: Apache-2.0
- */
-package net.consensys.linea.zktracer.instructionprocessing.callTests.eoa;
-
-import static net.consensys.linea.zktracer.opcode.OpCode.*;
-
-import net.consensys.linea.testing.BytecodeCompiler;
-import net.consensys.linea.testing.BytecodeRunner;
-import org.apache.tuweni.bytes.Bytes;
-import org.junit.jupiter.api.Test;
-
-/**
- * In the arithmetization there are the following EOA specific scenarios:
- *
- * - scn/CALL_EOA_SUCCESS_WILL_REVERT
- *
- * - scn/CALL_EOA_SUCCESS_WONT_REVERT
- */
-public class EoaTests {
-
- final String eoaAddress = "abcdef0123456789";
-
- @Test
- void transfersValueWillRevertTest() {
-
- Bytes bytecode =
- BytecodeCompiler.newProgram()
- .push(1)
- .push(2)
- .push(3)
- .push(4)
- .push(5) // value
- .push(eoaAddress) // address
- .push(0) // gas
- .op(CALL)
- .op(POP)
- .push(6)
- .push(7)
- .op(REVERT)
- .compile();
-
- BytecodeRunner.of(bytecode).run();
- }
-
- @Test
- void transfersValueWontRevertTest() {
-
- Bytes bytecode =
- BytecodeCompiler.newProgram()
- .push(1)
- .push(2)
- .push(3)
- .push(4)
- .push(5) // value
- .push(eoaAddress) // address
- .push(0) // gas
- .op(CALL)
- .op(POP)
- .compile();
-
- BytecodeRunner.of(bytecode).run();
- }
-
- @Test
- void transfersAllValueWillRevertTest() {
-
- Bytes program =
- BytecodeCompiler.newProgram()
- .push(1)
- .push(2)
- .push(3)
- .push(4)
- .op(SELFBALANCE) // all our balance
- .push(eoaAddress) // address
- .push(0) // gas
- .op(CALL)
- .op(POP)
- .push(6)
- .push(7)
- .op(REVERT)
- .compile();
-
- BytecodeRunner.of(program).run();
- }
-
- @Test
- void transfersAllValueWontRevertTest() {
-
- Bytes bytecode =
- BytecodeCompiler.newProgram()
- .push(1)
- .push(2)
- .push(3)
- .push(4)
- .op(SELFBALANCE) // all our balance
- .push(eoaAddress) // address
- .push(0) // gas
- .op(CALL)
- .compile();
-
- BytecodeRunner.of(bytecode).run();
- }
-
- @Test
- void transfersNoValueWillRevertTest() {
-
- Bytes bytecode =
- BytecodeCompiler.newProgram()
- .push(1)
- .push(2)
- .push(3)
- .push(4)
- .push(0) // value
- .push(eoaAddress) // address
- .push(0) // gas
- .op(CALL)
- .op(POP)
- .push(6)
- .push(7)
- .op(REVERT)
- .compile();
-
- BytecodeRunner.of(bytecode).run();
- }
-
- @Test
- void transfersNoValueWontRevertTest() {
-
- Bytes bytecode =
- BytecodeCompiler.newProgram()
- .push(1)
- .push(2)
- .push(3)
- .push(4)
- .push(0) // value
- .push(eoaAddress) // address
- .push(0) // gas
- .op(CALL)
- .op(POP)
- .compile();
-
- BytecodeRunner.of(bytecode).run();
- }
-}
diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/eoa/SmcCallsEoaInRoot.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/eoa/SmcCallsEoaInRoot.java
new file mode 100644
index 0000000000..5112c4d125
--- /dev/null
+++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/eoa/SmcCallsEoaInRoot.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright Consensys Software Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package net.consensys.linea.zktracer.instructionprocessing.callTests.eoa;
+
+import static net.consensys.linea.zktracer.instructionprocessing.utilities.Calls.appendCall;
+import static net.consensys.linea.zktracer.instructionprocessing.utilities.Calls.fullBalanceCall;
+import static net.consensys.linea.zktracer.opcode.OpCode.*;
+
+import net.consensys.linea.testing.BytecodeCompiler;
+import net.consensys.linea.testing.BytecodeRunner;
+import org.hyperledger.besu.datatypes.Address;
+import org.junit.jupiter.api.Test;
+
+/**
+ * In the arithmetization there are the following EOA specific scenarios:
+ *
+ * - scn/CALL_EOA_SUCCESS_WILL_REVERT
+ *
+ * - scn/CALL_EOA_SUCCESS_WONT_REVERT
+ */
+public class SmcCallsEoaInRoot {
+
+ final String eoaAddress = "abcdef0123456789";
+
+ @Test
+ void transfersSomeValueWillRevertTest() {
+
+ BytecodeCompiler bytecode = BytecodeCompiler.newProgram();
+ appendCall(bytecode, CALL, 0, Address.fromHexString(eoaAddress), 13, 2, 3, 4, 5);
+ bytecode.op(POP).push(6).push(7).op(REVERT).compile();
+
+ BytecodeRunner.of(bytecode.compile()).run();
+ }
+
+ @Test
+ void transfersSomeValueWontRevertTest() {
+
+ BytecodeCompiler bytecode = BytecodeCompiler.newProgram();
+ appendCall(bytecode, CALL, 0, Address.fromHexString(eoaAddress), 13, 2, 3, 4, 5);
+
+ BytecodeRunner.of(bytecode.compile()).run();
+ }
+
+ @Test
+ void transfersAllValueWillRevertTest() {
+
+ BytecodeCompiler program = BytecodeCompiler.newProgram();
+ fullBalanceCall(program, CALL, Address.fromHexString(eoaAddress), 1, 2, 3, 4);
+ program.push(6).push(7).op(REVERT);
+
+ BytecodeRunner.of(program.compile()).run();
+ }
+
+ @Test
+ void transfersAllValueWontRevertTest() {
+
+ BytecodeCompiler program = BytecodeCompiler.newProgram();
+ fullBalanceCall(program, CALL, Address.fromHexString(eoaAddress), 1, 2, 3, 4);
+
+ BytecodeRunner.of(program.compile()).run();
+ }
+
+ @Test
+ void transfersNoValueWillRevertTest() {
+
+ BytecodeCompiler program = BytecodeCompiler.newProgram();
+ appendCall(program, CALL, 0, Address.fromHexString(eoaAddress), 0, 1, 2, 3, 4);
+ program.op(POP).push(6).push(7).op(REVERT).compile();
+
+ BytecodeRunner.of(program.compile()).run();
+ }
+
+ @Test
+ void transfersNoValueWontRevertTest() {
+
+ BytecodeCompiler program = BytecodeCompiler.newProgram();
+ appendCall(program, CALL, 0, Address.fromHexString(eoaAddress), 0, 1, 2, 3, 4);
+
+ BytecodeRunner.of(program.compile()).run();
+ }
+}
diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/eoa/gasStipendTests.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/eoa/gasStipendTests.java
index e693d765dc..88974958ec 100644
--- a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/eoa/gasStipendTests.java
+++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/eoa/gasStipendTests.java
@@ -14,7 +14,7 @@
*/
package net.consensys.linea.zktracer.instructionprocessing.callTests.eoa;
-import static net.consensys.linea.zktracer.instructionprocessing.callTests.Utilities.*;
+import static net.consensys.linea.zktracer.instructionprocessing.utilities.Calls.*;
import static net.consensys.linea.zktracer.opcode.OpCode.*;
import net.consensys.linea.testing.BytecodeCompiler;
@@ -31,7 +31,7 @@ public class gasStipendTests {
@Test
void zeroValueEoaCallTest() {
BytecodeCompiler program = BytecodeCompiler.newProgram();
- simpleCall(program, CALL, 0, Address.fromHexString(eoaAddress), 0, 0, 0, 0, 0);
+ appendCall(program, CALL, 0, Address.fromHexString(eoaAddress), 0, 0, 0, 0, 0);
BytecodeRunner.of(program.compile()).run();
}
@@ -40,7 +40,7 @@ void zeroValueEoaCallTest() {
@Test
void nonzeroValueEoaCallTest() {
BytecodeCompiler program = BytecodeCompiler.newProgram();
- simpleCall(program, CALL, 0, Address.fromHexString(eoaAddress), 1, 0, 0, 0, 0);
+ appendCall(program, CALL, 0, Address.fromHexString(eoaAddress), 1, 0, 0, 0, 0);
BytecodeRunner.of(program.compile()).run();
}
diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/prc/ecrecover/GasStipendTests.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/prc/ecrecover/GasStipendTests.java
new file mode 100644
index 0000000000..4d6262f8ca
--- /dev/null
+++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/prc/ecrecover/GasStipendTests.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright Consensys Software Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package net.consensys.linea.zktracer.instructionprocessing.callTests.prc.ecrecover;
+
+import static net.consensys.linea.zktracer.instructionprocessing.utilities.Calls.*;
+
+import net.consensys.linea.testing.BytecodeCompiler;
+import net.consensys.linea.testing.BytecodeRunner;
+import net.consensys.linea.zktracer.opcode.OpCode;
+import org.hyperledger.besu.datatypes.Address;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+
+/**
+ * For these tests to work as expected, the transaction should start out with sufficient gas. At
+ * least 21k plus gas to cover the PUSH's and the (warm) CALL with potentially value transfer and
+ * potentially account creation costs. Also there should be enough gas in the end for the 63/64
+ * business not diminish the gas we provide the callee.
+ *
+ * Something like 60k gas should cover all costs (21k transaction costs + 9k for potential value
+ * transfer + 25k if value transfer leads to a precompile starting to exist in the state etc ... +
+ * 3k for the callee + opcode costs on the order of 130 or so)
+ */
+public class GasStipendTests {
+
+ // sufficient gas for PRC execution
+ @ParameterizedTest
+ @EnumSource(
+ value = OpCode.class,
+ names = {"CALL", "CALLCODE", "DELEGATECALL", "STATICCALL"})
+ void zeroValueEcRecoverCallTest(OpCode callOpCode) {
+ BytecodeCompiler program = BytecodeCompiler.newProgram();
+ validEcrecoverData(program);
+ appendCall(
+ program,
+ callOpCode,
+ 3000,
+ Address.fromHexString(Address.ECREC.toHexString()),
+ 0,
+ 0,
+ 0,
+ 0,
+ 0);
+
+ BytecodeRunner.of(program.compile()).run();
+ }
+
+ @ParameterizedTest
+ @EnumSource(
+ value = OpCode.class,
+ names = {"CALL", "CALLCODE", "DELEGATECALL", "STATICCALL"})
+ void nonzeroValueEcRecoverCallTest(OpCode callOpCode) {
+ BytecodeCompiler program = BytecodeCompiler.newProgram();
+ validEcrecoverData(program);
+ appendCall(
+ program,
+ callOpCode,
+ 3000,
+ Address.fromHexString(Address.ECREC.toHexString()),
+ 1,
+ 0,
+ 0,
+ 0,
+ 0);
+
+ BytecodeRunner.of(program.compile()).run();
+ }
+
+ @ParameterizedTest
+ @EnumSource(
+ value = OpCode.class,
+ names = {"CALL", "CALLCODE", "DELEGATECALL", "STATICCALL"})
+ void nonzeroValueEcRecoverCallWillRevertTest(OpCode callOpCode) {
+ BytecodeCompiler program = BytecodeCompiler.newProgram();
+ validEcrecoverData(program);
+ appendCall(
+ program,
+ callOpCode,
+ 3000,
+ Address.fromHexString(Address.ECREC.toHexString()),
+ 1,
+ 0,
+ 0,
+ 0,
+ 32);
+ appendRevert(program, 0, 32);
+
+ BytecodeRunner.of(program.compile()).run();
+ }
+
+ @ParameterizedTest
+ @EnumSource(
+ value = OpCode.class,
+ names = {"CALL", "CALLCODE"})
+ void stipendCompletesGasEcRecoverCallTest(OpCode callOpCode) {
+ BytecodeCompiler program = BytecodeCompiler.newProgram();
+ validEcrecoverData(program);
+ appendCall(
+ program,
+ callOpCode,
+ 700,
+ Address.fromHexString(Address.ECREC.toHexString()),
+ 1,
+ 0,
+ 0,
+ 0,
+ 0);
+
+ BytecodeRunner.of(program.compile()).run();
+ }
+
+ // insufficient gas for PRC execution
+ @ParameterizedTest
+ @EnumSource(
+ value = OpCode.class,
+ names = {"CALL", "CALLCODE", "DELEGATECALL", "STATICCALL"})
+ void gasFallsShortForEcRecoverTest(OpCode callOpCode) {
+ BytecodeCompiler program = BytecodeCompiler.newProgram();
+ validEcrecoverData(program);
+ appendCall(
+ program,
+ callOpCode,
+ 2999,
+ Address.fromHexString(Address.ECREC.toHexString()),
+ 0,
+ 0,
+ 0,
+ 0,
+ 0);
+
+ BytecodeRunner.of(program.compile()).run();
+ }
+
+ @ParameterizedTest
+ @EnumSource(
+ value = OpCode.class,
+ names = {"CALL", "CALLCODE"})
+ void stipendFromValueFallsShortOfCompletingGasEcrecoverCallTest(OpCode callOpCode) {
+ BytecodeCompiler program = BytecodeCompiler.newProgram();
+ validEcrecoverData(program);
+ appendCall(
+ program,
+ callOpCode,
+ 699, // value transfer adds G_stipend = 2_300 to this, falling short of the 3_000 required
+ // gas
+ Address.fromHexString(Address.ECREC.toHexString()),
+ 1,
+ 0,
+ 0,
+ 0,
+ 0);
+
+ BytecodeRunner.of(program.compile()).run();
+ }
+}
diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/prc/ecrecover/gasStipendTests.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/prc/ecrecover/gasStipendTests.java
deleted file mode 100644
index 0acfcef5ef..0000000000
--- a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/prc/ecrecover/gasStipendTests.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright Consensys Software Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
- * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations under the License.
- *
- * SPDX-License-Identifier: Apache-2.0
- */
-package net.consensys.linea.zktracer.instructionprocessing.callTests.prc.ecrecover;
-
-import static net.consensys.linea.zktracer.instructionprocessing.callTests.Utilities.*;
-import static net.consensys.linea.zktracer.opcode.OpCode.CALL;
-
-import net.consensys.linea.testing.BytecodeCompiler;
-import net.consensys.linea.testing.BytecodeRunner;
-import org.hyperledger.besu.datatypes.Address;
-import org.junit.jupiter.api.Test;
-
-/**
- * For these tests to work as expected, the transaction should start out with sufficient gas. At
- * least 21k plus gas to cover the PUSH's and the (warm) CALL with potentially value transfer and
- * potentially account creation costs. Also there should be enough gas in the end for the 63/64
- * business not diminish the gas we provide the callee.
- *
- * Something like 60k gas should cover all costs (21k transaction costs + 9k for potential value
- * transfer + 25k if value transfer leads to a precompile starting to exist in the state etc ... +
- * 3k for the callee + opcode costs on the order of 130 or so)
- */
-public class gasStipendTests {
-
- // sufficient gas for PRC execution
- @Test
- void zeroValueEcrecoverCallTest() {
- BytecodeCompiler program = BytecodeCompiler.newProgram();
- validEcrecoverData(program);
- simpleCall(
- program, CALL, 3000, Address.fromHexString(Address.ECREC.toHexString()), 0, 0, 0, 0, 0);
-
- BytecodeRunner.of(program.compile()).run();
- }
-
- @Test
- void nonzeroValueEcrecoverCallTest() {
- BytecodeCompiler program = BytecodeCompiler.newProgram();
- validEcrecoverData(program);
- simpleCall(
- program, CALL, 3000, Address.fromHexString(Address.ECREC.toHexString()), 1, 0, 0, 0, 0);
-
- BytecodeRunner.of(program.compile()).run();
- }
-
- @Test
- void nonzeroValueStipendCompletesGasEcrecoverCallTest() {
- BytecodeCompiler program = BytecodeCompiler.newProgram();
- validEcrecoverData(program);
- simpleCall(
- program, CALL, 700, Address.fromHexString(Address.ECREC.toHexString()), 1, 0, 0, 0, 0);
-
- BytecodeRunner.of(program.compile()).run();
- }
-
- // insufficient gas for PRC execution
- @Test
- void nonzeroValueShortOnGasEcrecoverCallTest() {
- BytecodeCompiler program = BytecodeCompiler.newProgram();
- validEcrecoverData(program);
- simpleCall(
- program, CALL, 2999, Address.fromHexString(Address.ECREC.toHexString()), 1, 0, 0, 0, 0);
-
- BytecodeRunner.of(program.compile()).run();
- }
-
- @Test
- void nonzeroValueStipendFallsShortOfCompletingGasEcrecoverCallTest() {
- BytecodeCompiler program = BytecodeCompiler.newProgram();
- validEcrecoverData(program);
- simpleCall(
- program, CALL, 699, Address.fromHexString(Address.ECREC.toHexString()), 1, 0, 0, 0, 0);
-
- BytecodeRunner.of(program.compile()).run();
- }
-}
diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/prc/identity/Tests.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/prc/identity/Tests.java
new file mode 100644
index 0000000000..264b254ce6
--- /dev/null
+++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/prc/identity/Tests.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright Consensys Software Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package net.consensys.linea.zktracer.instructionprocessing.callTests.prc.identity;
+
+import static net.consensys.linea.zktracer.instructionprocessing.utilities.Calls.appendCall;
+import static net.consensys.linea.zktracer.instructionprocessing.utilities.MonoOpCodeSmcs.keyPair;
+import static net.consensys.linea.zktracer.instructionprocessing.utilities.MonoOpCodeSmcs.userAccount;
+import static net.consensys.linea.zktracer.opcode.OpCode.*;
+
+import java.util.List;
+
+import net.consensys.linea.testing.BytecodeCompiler;
+import net.consensys.linea.testing.ToyAccount;
+import net.consensys.linea.testing.ToyExecutionEnvironmentV2;
+import net.consensys.linea.testing.ToyTransaction;
+import net.consensys.linea.zktracer.opcode.OpCode;
+import org.apache.tuweni.bytes.Bytes;
+import org.hyperledger.besu.datatypes.Address;
+import org.hyperledger.besu.datatypes.Wei;
+import org.hyperledger.besu.ethereum.core.Transaction;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+
+public class Tests {
+
+ // 512 random bytes
+ final Bytes randBytes =
+ Bytes.fromHexString(
+ "fc82e3eacb81bb23c620386ea0427556d93af36b9dec2bcf8642728901f578a9f29cfead7f44372286ac20032e3d83939c4f58a93efee5ddc4a48990dc2cb188694a240b2f6b00386a442ed5368cf030bd2e73e09a44848e9130522b2d7a6a78bbb4554c8923c5be03a49d62c430e7e28d92b18f6db900ea8be85fa0f82fa913e02f106bc1322919f59223465c6accff2d89360f5e38cd4a38872696751af5191ad19123702e68d910b2a0898700a08272e2326b43a6dca195c71db782ab560d31ed7a2d698c4bdcd7fceb252e042160ba5c2929144373ccc3ec089317908b31cfaed98a0a6f49afdf69f23d2c06c308478af4c671385393b0413fe4b93a67b8acac06aafb9d1a4c50732d3bb9bc45a9d799b767158474efa59a3ecba29b7f815589f8f5b6363341ee5c39a492ffc74b3ce61909367570100f8ede71e0ace31780d9eebf8a4fe33fcab4c6efb3e66910574fd8693f1960be4a107be95268493e318aeac36caaae7b0275e372425b765e4c55512aaa4237365d614b48bc0815e095637f14a9bd3cd7eac38130864286c08d25cb94cc953112af1f902c3a5f387ba2ce3a4fc6393ef4c360d22418e127f0d6f6a455b9386aa2c95984cfea90834dcd5de0934c81b4b555d7ae6e02d467fbb2335abdefa741430d8845f73dbb07c71b07cf2a536a14ea80160277153ea6ee552dbe6432fc415df89af463260b3881");
+
+ final ToyAccount byteSource =
+ ToyAccount.builder()
+ .address(Address.fromHexString("acc0fc0de"))
+ .nonce(59)
+ .balance(Wei.of(1_000_000L))
+ .code(randBytes)
+ .build();
+
+ final ToyAccount identityCaller =
+ ToyAccount.builder()
+ .address(Address.fromHexString("1dca11e7")) // identity caller
+ .nonce(103)
+ .balance(Wei.of(1_000_000L))
+ .code(randBytes)
+ .build();
+
+ @ParameterizedTest
+ @EnumSource(
+ value = OpCode.class,
+ names = {"CALL", "CALLCODE", "DELEGATECALL", "STATICCALL"})
+ void nontrivialCallDataIdentityTest(OpCode callOpCode) {
+
+ BytecodeCompiler program = BytecodeCompiler.newProgram();
+ fullCodeCopyOf(program, byteSource);
+ appendCall(
+ program,
+ callOpCode,
+ 1_000_000,
+ Address.ID,
+ 1_000_000,
+ 0,
+ byteSource.getCode().size(),
+ 7,
+ 51);
+ program.op(RETURNDATASIZE); // should return 512
+
+ identityCaller.setCode(program.compile());
+
+ Transaction transaction =
+ ToyTransaction.builder()
+ .sender(userAccount)
+ .keyPair(keyPair)
+ .to(identityCaller)
+ .value(Wei.of(7_000_000_000L))
+ .build();
+ ToyExecutionEnvironmentV2.builder()
+ .accounts(List.of(byteSource, userAccount, identityCaller))
+ .transaction(transaction)
+ .build()
+ .run();
+ }
+
+ /**
+ * The following copies the entirety of the account code to RAM.
+ *
+ * @param program
+ * @param account
+ */
+ public void fullCodeCopyOf(BytecodeCompiler program, ToyAccount account) {
+ final Address address = account.getAddress();
+ program
+ .push(address)
+ .op(EXTCODESIZE) // puts the code size on the stack
+ .push(0)
+ .push(0)
+ .push(address)
+ .op(EXTCODECOPY); // copies the entire code to RAM
+ }
+}
diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/prc/sha2/GasTests.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/prc/sha2/GasTests.java
new file mode 100644
index 0000000000..ff14b8a8c8
--- /dev/null
+++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/prc/sha2/GasTests.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright Consensys Software Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package net.consensys.linea.zktracer.instructionprocessing.callTests.prc.sha2;
+
+import static net.consensys.linea.zktracer.instructionprocessing.utilities.Calls.appendCall;
+import static net.consensys.linea.zktracer.instructionprocessing.utilities.Calls.appendRevert;
+import static net.consensys.linea.zktracer.opcode.OpCode.RETURNDATASIZE;
+
+import net.consensys.linea.testing.BytecodeCompiler;
+import net.consensys.linea.testing.BytecodeRunner;
+import net.consensys.linea.zktracer.opcode.OpCode;
+import org.hyperledger.besu.datatypes.Address;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+
+/**
+ * All of the tests below have in common that they attempt to hash 0x00...00 ∈ B_321 with SHA256.
+ * The test vectors vary accoring to
+ *
+ * - the kind of CALL instruction used
+ *
+ * - whether the operation is reverted or not (they all transfer value)
+ */
+public class GasTests {
+
+ @ParameterizedTest
+ @EnumSource(
+ value = OpCode.class,
+ names = {"CALL", "CALLCODE", "DELEGATECALL", "STATICCALL"})
+ void sha2ProvidedWithLittleToNoneGasTest(OpCode callOpCode) {
+
+ BytecodeCompiler program = BytecodeCompiler.newProgram();
+ appendCall(program, callOpCode, 0, Address.SHA256, 1_000_000, 0, 32 * 10 + 1, 7, 32);
+ program.op(RETURNDATASIZE); // should return 0
+
+ BytecodeRunner.of(program).run();
+ }
+
+ @ParameterizedTest
+ @EnumSource(
+ value = OpCode.class,
+ names = {"CALL", "CALLCODE", "DELEGATECALL", "STATICCALL"})
+ void sha2ProvidedWithLittleToNoneGasWillRevertTest(OpCode callOpCode) {
+
+ BytecodeCompiler program = BytecodeCompiler.newProgram();
+ appendCall(program, callOpCode, 0, Address.SHA256, 1_000_000, 0, 32 * 10, 7, 32);
+ program.op(RETURNDATASIZE); // should return 0
+ appendRevert(program, 1, 34);
+
+ BytecodeRunner.of(program).run();
+ }
+
+ @ParameterizedTest
+ @EnumSource(
+ value = OpCode.class,
+ names = {"CALL", "CALLCODE", "DELEGATECALL", "STATICCALL"})
+ void sha2ProvidedWithPlentifulGasTest(OpCode callOpCode) {
+
+ BytecodeCompiler program = BytecodeCompiler.newProgram();
+ appendCall(program, callOpCode, 1_000_000, Address.SHA256, 1_000_000, 0, 32 * 10, 7, 32);
+ program.op(RETURNDATASIZE); // should return 32
+ BytecodeRunner.of(program).run();
+ }
+
+ @ParameterizedTest
+ @EnumSource(
+ value = OpCode.class,
+ names = {"CALL", "CALLCODE", "DELEGATECALL", "STATICCALL"})
+ void sha2ProvidedWithPlentifulGasWillRevertTest(OpCode callOpCode) {
+
+ BytecodeCompiler program = BytecodeCompiler.newProgram();
+ appendCall(program, callOpCode, 1_000_000, Address.SHA256, 1_000_000, 0, 32 * 10, 7, 32);
+ program.op(RETURNDATASIZE); // should return 32
+ appendRevert(program, 1, 34);
+
+ BytecodeRunner.of(program).run();
+ }
+}
diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/smc/monoOpCodeTargets/ImmediateInvalid.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/smc/monoOpCodeTargets/ImmediateInvalid.java
index 47c04d3dba..f6abd47516 100644
--- a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/smc/monoOpCodeTargets/ImmediateInvalid.java
+++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/smc/monoOpCodeTargets/ImmediateInvalid.java
@@ -14,8 +14,8 @@
*/
package net.consensys.linea.zktracer.instructionprocessing.callTests.smc.monoOpCodeTargets;
-import static net.consensys.linea.zktracer.instructionprocessing.callTests.Utilities.simpleCall;
-import static net.consensys.linea.zktracer.instructionprocessing.callTests.smc.Utilities.*;
+import static net.consensys.linea.zktracer.instructionprocessing.utilities.Calls.appendCall;
+import static net.consensys.linea.zktracer.instructionprocessing.utilities.MonoOpCodeSmcs.*;
import static net.consensys.linea.zktracer.opcode.OpCode.CALL;
import static net.consensys.linea.zktracer.opcode.OpCode.REVERT;
@@ -29,7 +29,7 @@ public class ImmediateInvalid {
void zeroValueTransferToInvalid() {
BytecodeCompiler program = BytecodeCompiler.newProgram();
- simpleCall(program, CALL, 0, accountWhoseByteCodeIsASingleInvalid.getAddress(), 0, 0, 0, 0, 0);
+ appendCall(program, CALL, 0, accountWhoseByteCodeIsASingleInvalid.getAddress(), 0, 0, 0, 0, 0);
BytecodeRunner.of(program.compile()).run(accounts);
}
@@ -38,7 +38,7 @@ void zeroValueTransferToInvalid() {
void nonZeroValueTransferToInvalidContract() {
BytecodeCompiler program = BytecodeCompiler.newProgram();
- simpleCall(program, CALL, 0, accountWhoseByteCodeIsASingleInvalid.getAddress(), 1, 0, 0, 0, 0);
+ appendCall(program, CALL, 0, accountWhoseByteCodeIsASingleInvalid.getAddress(), 1, 0, 0, 0, 0);
BytecodeRunner.of(program.compile()).run(accounts);
}
@@ -47,7 +47,7 @@ void nonZeroValueTransferToInvalidContract() {
void nonZeroValueTransferToInvalidContractRevertingTransaction() {
BytecodeCompiler program = BytecodeCompiler.newProgram();
- simpleCall(program, CALL, 0, accountWhoseByteCodeIsASingleInvalid.getAddress(), 1, 0, 0, 0, 0);
+ appendCall(program, CALL, 0, accountWhoseByteCodeIsASingleInvalid.getAddress(), 1, 0, 0, 0, 0);
// we use the 1 on the stack after this successful CALL as the revert message size
program.push(0).op(REVERT);
diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/smc/monoOpCodeTargets/singleJumpDest.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/smc/monoOpCodeTargets/SingleJumpDest.java
similarity index 91%
rename from arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/smc/monoOpCodeTargets/singleJumpDest.java
rename to arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/smc/monoOpCodeTargets/SingleJumpDest.java
index 7190a9f6d4..13163251e4 100644
--- a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/smc/monoOpCodeTargets/singleJumpDest.java
+++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/smc/monoOpCodeTargets/SingleJumpDest.java
@@ -14,8 +14,8 @@
*/
package net.consensys.linea.zktracer.instructionprocessing.callTests.smc.monoOpCodeTargets;
-import static net.consensys.linea.zktracer.instructionprocessing.callTests.Utilities.simpleCall;
-import static net.consensys.linea.zktracer.instructionprocessing.callTests.smc.Utilities.*;
+import static net.consensys.linea.zktracer.instructionprocessing.utilities.Calls.appendCall;
+import static net.consensys.linea.zktracer.instructionprocessing.utilities.MonoOpCodeSmcs.*;
import static net.consensys.linea.zktracer.opcode.OpCode.CALL;
import static net.consensys.linea.zktracer.opcode.OpCode.REVERT;
@@ -27,14 +27,14 @@
* Second-simplest case where we enter a smart contract. The called smart contract executes a single
* JUMPDEST opcode (which is costs gas).
*/
-public class singleJumpDest {
+public class SingleJumpDest {
/** This test should trigger the scenario/CALL_TO_SMC_SUCCESS_WONT_REVERT scenario. */
@Test
void zeroValueTransferToJumpDestContract() {
BytecodeCompiler program = BytecodeCompiler.newProgram();
- simpleCall(
+ appendCall(
program, CALL, 10, accountWhoseByteCodeIsASingleJumpDest.getAddress(), 0, 0, 0, 0, 0);
BytecodeRunner.of(program.compile()).run(accounts);
@@ -45,7 +45,7 @@ void zeroValueTransferToJumpDestContract() {
void nonZeroValueTransferToJumpDestContract() {
BytecodeCompiler program = BytecodeCompiler.newProgram();
- simpleCall(
+ appendCall(
program, CALL, 10, accountWhoseByteCodeIsASingleJumpDest.getAddress(), 1, 0, 0, 0, 0);
BytecodeRunner.of(program.compile()).run(accounts);
@@ -56,7 +56,7 @@ void nonZeroValueTransferToJumpDestContract() {
void nonZeroValueTransferToJumpDestContractRevertingTransaction() {
BytecodeCompiler program = BytecodeCompiler.newProgram();
- simpleCall(
+ appendCall(
program, CALL, 10, accountWhoseByteCodeIsASingleJumpDest.getAddress(), 1, 0, 0, 0, 0);
// we use the 1 on the stack after this successful CALL as the revert message size
@@ -73,7 +73,7 @@ void nonZeroValueTransferToJumpDestContractRevertingTransaction() {
void zeroValueTransferToJumpDestContractOogx() {
BytecodeCompiler program = BytecodeCompiler.newProgram();
- simpleCall(program, CALL, 0, accountWhoseByteCodeIsASingleJumpDest.getAddress(), 0, 0, 0, 0, 0);
+ appendCall(program, CALL, 0, accountWhoseByteCodeIsASingleJumpDest.getAddress(), 0, 0, 0, 0, 0);
BytecodeRunner.of(program.compile()).run(accounts);
}
@@ -83,7 +83,7 @@ void zeroValueTransferToJumpDestContractOogx() {
void nonZeroValueTransferToJumpDestContractOogx() {
BytecodeCompiler program = BytecodeCompiler.newProgram();
- simpleCall(program, CALL, 0, accountWhoseByteCodeIsASingleJumpDest.getAddress(), 1, 0, 0, 0, 0);
+ appendCall(program, CALL, 0, accountWhoseByteCodeIsASingleJumpDest.getAddress(), 1, 0, 0, 0, 0);
BytecodeRunner.of(program.compile()).run(accounts);
}
@@ -93,7 +93,7 @@ void nonZeroValueTransferToJumpDestContractOogx() {
void nonZeroValueTransferToJumpDestContractOogxAndRevertingTransaction() {
BytecodeCompiler program = BytecodeCompiler.newProgram();
- simpleCall(program, CALL, 0, accountWhoseByteCodeIsASingleJumpDest.getAddress(), 1, 0, 0, 0, 0);
+ appendCall(program, CALL, 0, accountWhoseByteCodeIsASingleJumpDest.getAddress(), 1, 0, 0, 0, 0);
// we use the 1 on the stack after this successful CALL as the revert message size
program.push(0).op(REVERT);
diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/smc/monoOpCodeTargets/singleStop.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/smc/monoOpCodeTargets/singleStop.java
index cdf44ac4f8..035c056ca1 100644
--- a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/smc/monoOpCodeTargets/singleStop.java
+++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/smc/monoOpCodeTargets/singleStop.java
@@ -14,8 +14,8 @@
*/
package net.consensys.linea.zktracer.instructionprocessing.callTests.smc.monoOpCodeTargets;
-import static net.consensys.linea.zktracer.instructionprocessing.callTests.Utilities.simpleCall;
-import static net.consensys.linea.zktracer.instructionprocessing.callTests.smc.Utilities.*;
+import static net.consensys.linea.zktracer.instructionprocessing.utilities.Calls.appendCall;
+import static net.consensys.linea.zktracer.instructionprocessing.utilities.MonoOpCodeSmcs.*;
import static net.consensys.linea.zktracer.opcode.OpCode.*;
import net.consensys.linea.testing.*;
@@ -32,7 +32,7 @@ public class singleStop {
void zeroValueTransferToContractThatStops() {
BytecodeCompiler program = BytecodeCompiler.newProgram();
- simpleCall(program, CALL, 0, accountWhoseByteCodeIsASingleStop.getAddress(), 0, 0, 0, 0, 0);
+ appendCall(program, CALL, 0, accountWhoseByteCodeIsASingleStop.getAddress(), 0, 0, 0, 0, 0);
BytecodeRunner.of(program.compile()).run(accounts);
}
@@ -42,7 +42,7 @@ void zeroValueTransferToContractThatStops() {
void nonZeroValueTransferToContractThatStops() {
BytecodeCompiler program = BytecodeCompiler.newProgram();
- simpleCall(program, CALL, 0, accountWhoseByteCodeIsASingleStop.getAddress(), 1, 0, 0, 0, 0);
+ appendCall(program, CALL, 0, accountWhoseByteCodeIsASingleStop.getAddress(), 1, 0, 0, 0, 0);
BytecodeRunner.of(program.compile()).run(accounts);
}
@@ -52,7 +52,7 @@ void nonZeroValueTransferToContractThatStops() {
void nonZeroValueTransferToContractThatStopsRevertingTransaction() {
BytecodeCompiler program = BytecodeCompiler.newProgram();
- simpleCall(program, CALL, 0, accountWhoseByteCodeIsASingleStop.getAddress(), 1, 0, 0, 0, 0);
+ appendCall(program, CALL, 0, accountWhoseByteCodeIsASingleStop.getAddress(), 1, 0, 0, 0, 0);
// we use the 1 on the stack after this successful CALL as the revert message size
program.push(0).op(REVERT);
diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/selfdestructTests/Heir.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/selfdestructTests/Heir.java
new file mode 100644
index 0000000000..33ca9ab414
--- /dev/null
+++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/selfdestructTests/Heir.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright Consensys Software Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package net.consensys.linea.zktracer.instructionprocessing.selfdestructTests;
+
+import static net.consensys.linea.zktracer.instructionprocessing.utilities.Calls.eoaAddress;
+import static net.consensys.linea.zktracer.instructionprocessing.utilities.SelfDestructs.createValueFromContextParameters;
+import static net.consensys.linea.zktracer.opcode.OpCode.*;
+import static net.consensys.linea.zktracer.opcode.OpCode.SELFDESTRUCT;
+
+import net.consensys.linea.testing.BytecodeCompiler;
+import net.consensys.linea.testing.ToyAccount;
+import org.hyperledger.besu.datatypes.Address;
+import org.hyperledger.besu.datatypes.Wei;
+
+public enum Heir {
+ HEIR_IS_ZERO,
+ HEIR_IS_ORIGIN,
+ HEIR_IS_CALLER,
+ HEIR_IS_SELF,
+ HEIR_IS_EOA,
+ HEIR_IS_ECREC,
+ HEIR_IS_COMPUTED;
+
+ public static Address selfDestructorAddress = Address.fromHexString("0xFFc0deadd7");
+
+ public static ToyAccount basicSelfDestructor(Heir heir) {
+
+ BytecodeCompiler program = BytecodeCompiler.newProgram();
+ switch (heir) {
+ case HEIR_IS_ZERO:
+ program.push(0);
+ case HEIR_IS_ORIGIN:
+ program.op(ORIGIN);
+ case HEIR_IS_CALLER:
+ program.op(CALLER);
+ case HEIR_IS_SELF:
+ program.op(ADDRESS);
+ case HEIR_IS_EOA:
+ program.push(eoaAddress);
+ case HEIR_IS_ECREC:
+ program.push(Address.ECREC);
+ case HEIR_IS_COMPUTED:
+ createValueFromContextParameters(program);
+ }
+ program.op(SELFDESTRUCT);
+
+ return ToyAccount.builder()
+ .address(selfDestructorAddress)
+ .code(program.compile())
+ .balance(Wei.fromEth(1))
+ .nonce(1776)
+ .build();
+ }
+}
diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/selfdestructTests/RepeatedSelfDestructsOfSameAccountTests.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/selfdestructTests/RepeatedSelfDestructsOfSameAccountTests.java
new file mode 100644
index 0000000000..5c1e79260a
--- /dev/null
+++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/selfdestructTests/RepeatedSelfDestructsOfSameAccountTests.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright Consensys Software Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package net.consensys.linea.zktracer.instructionprocessing.selfdestructTests;
+
+import static net.consensys.linea.zktracer.instructionprocessing.selfdestructTests.Heir.*;
+import static net.consensys.linea.zktracer.instructionprocessing.utilities.Calls.*;
+import static net.consensys.linea.zktracer.instructionprocessing.utilities.MonoOpCodeSmcs.keyPair;
+import static net.consensys.linea.zktracer.instructionprocessing.utilities.MonoOpCodeSmcs.userAccount;
+
+import java.util.List;
+
+import net.consensys.linea.testing.*;
+import net.consensys.linea.zktracer.opcode.OpCode;
+import org.hyperledger.besu.datatypes.Address;
+import org.hyperledger.besu.datatypes.TransactionType;
+import org.hyperledger.besu.datatypes.Wei;
+import org.hyperledger.besu.ethereum.core.Transaction;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+
+/**
+ * Basic tests for SELFDESTRUCT opcode. They test interactions with REVERT opcode. We consider the
+ * case of 3 successful SELFDESTRUCT's in a row at the same selfDestructorAddress. The
+ * selfDestructorAddress parameter decides whether the SELFDESTRUCT targets the same
+ * selfDestructorAddress or not. We consider the reverted vs unreverted cases.
+ */
+public class RepeatedSelfDestructsOfSameAccountTests {
+
+ private ToyAccount toAccount;
+ BytecodeCompiler toAccountCode = BytecodeCompiler.newProgram();
+ private ToyAccount selfDestructorAccount;
+
+ private void buildToAccount() {
+ toAccount =
+ ToyAccount.builder()
+ .address(Address.fromHexString("0x1234567890"))
+ .balance(Wei.fromEth(2))
+ .code(toAccountCode.compile())
+ .nonce(23)
+ .build();
+ }
+
+ private Transaction transaction() {
+ return ToyTransaction.builder()
+ .keyPair(keyPair)
+ .sender(userAccount)
+ .to(toAccount)
+ .transactionType(TransactionType.FRONTIER)
+ .gasLimit(500_000L)
+ .value(Wei.ONE)
+ .build();
+ }
+
+ private void run(Heir heir) {
+ selfDestructorAccount = basicSelfDestructor(heir);
+ buildToAccount();
+ ToyExecutionEnvironmentV2.builder()
+ .accounts(List.of(userAccount, toAccount, selfDestructorAccount))
+ .transaction(transaction())
+ .build()
+ .run();
+ }
+
+ /**
+ * The root contract CALL's the selfdestructor thrice, each time providing him with new balance.
+ */
+ @ParameterizedTest
+ @EnumSource(Heir.class)
+ public void sameAccountSelfDestructsThrice(Heir heir) {
+
+ appendCall(toAccountCode, OpCode.CALL, 100_000, Heir.selfDestructorAddress, 12, 0, 4, 0, 0);
+ appendCall(toAccountCode, OpCode.CALL, 100_000, Heir.selfDestructorAddress, 19, 0, 3, 0, 0);
+ appendCall(toAccountCode, OpCode.CALL, 100_000, Heir.selfDestructorAddress, 26, 0, 2, 0, 0);
+
+ run(heir);
+ }
+
+ @ParameterizedTest
+ @EnumSource(Heir.class)
+ public void sameAccountSelfDestructsThriceReverted(Heir heir) {
+
+ appendCall(toAccountCode, OpCode.CALL, 100_000, Heir.selfDestructorAddress, 12, 0, 4, 0, 0);
+ appendCall(toAccountCode, OpCode.CALL, 100_000, Heir.selfDestructorAddress, 19, 0, 3, 0, 0);
+ appendCall(toAccountCode, OpCode.CALL, 100_000, Heir.selfDestructorAddress, 26, 0, 2, 0, 0);
+ appendRevert(toAccountCode, 0, 0);
+
+ run(heir);
+ }
+
+ /**
+ * DELEGATECALL induces SELFDESTRUCT in the caller. A second DELEGATECALL does it again.
+ *
+ * The second SELFDESTRUCT should go through due to DELEGATECALL not transferring value.
+ */
+ @ParameterizedTest
+ @EnumSource(Heir.class)
+ public void calleeInducesSelfDestructInCallerViaDelegateCall(Heir heir) {
+
+ appendCall(
+ toAccountCode, OpCode.DELEGATECALL, 100_000, Heir.selfDestructorAddress, 12, 0, 4, 0, 0);
+ appendCall(
+ toAccountCode, OpCode.DELEGATECALL, 100_000, Heir.selfDestructorAddress, 19, 0, 3, 0, 0);
+
+ run(heir);
+ }
+
+ @ParameterizedTest
+ @EnumSource(Heir.class)
+ public void calleeInducesSelfDestructInCallerViaDelegateCallReverted(Heir heir) {
+
+ appendCall(
+ toAccountCode, OpCode.DELEGATECALL, 100_000, Heir.selfDestructorAddress, 12, 0, 4, 0, 0);
+ appendCall(
+ toAccountCode, OpCode.DELEGATECALL, 100_000, Heir.selfDestructorAddress, 19, 0, 3, 0, 0);
+ appendRevert(toAccountCode, 0, 0);
+
+ run(heir);
+ }
+
+ /** The second call should abort due to not having any funds left. */
+ @ParameterizedTest
+ @EnumSource(Heir.class)
+ public void calleeInducesSelfDestructInCallerViaCallCode(Heir heir) {
+ appendCall(toAccountCode, OpCode.CALLCODE, 100_000, Heir.selfDestructorAddress, 12, 0, 4, 0, 0);
+ appendCall(toAccountCode, OpCode.CALLCODE, 100_000, Heir.selfDestructorAddress, 19, 0, 3, 0, 0);
+
+ run(heir);
+ }
+
+ @ParameterizedTest
+ @EnumSource(Heir.class)
+ public void calleeInducesSelfDestructInCallerViaCallCodeReverted(Heir heir) {
+ appendCall(toAccountCode, OpCode.CALLCODE, 100_000, Heir.selfDestructorAddress, 12, 0, 4, 0, 0);
+ appendCall(toAccountCode, OpCode.CALLCODE, 100_000, Heir.selfDestructorAddress, 19, 0, 3, 0, 0);
+ appendRevert(toAccountCode, 0, 0);
+
+ run(heir);
+ }
+
+ /**
+ * DELEGATECALL into account that induces SELFDESTRUCT in caller followed by the caller CALL'ing
+ * the callee again inducing SELFDESTRUCT in callee this time.
+ *
+ * Both reverted and unreverted versions
+ */
+ @ParameterizedTest
+ @EnumSource(Heir.class)
+ public void callerThenCalleeSelfDestruct(Heir heir) {
+ appendCall(
+ toAccountCode, OpCode.DELEGATECALL, 100_000, Heir.selfDestructorAddress, 12, 0, 4, 0, 0);
+ appendCall(toAccountCode, OpCode.CALL, 100_000, Heir.selfDestructorAddress, 0, 0, 3, 0, 0);
+
+ run(heir);
+ }
+
+ @ParameterizedTest
+ @EnumSource(Heir.class)
+ public void callerThenCalleeSelfDestructReverted(Heir heir) {
+ appendCall(
+ toAccountCode, OpCode.DELEGATECALL, 100_000, Heir.selfDestructorAddress, 12, 0, 4, 0, 0);
+ appendCall(toAccountCode, OpCode.CALL, 100_000, Heir.selfDestructorAddress, 0, 0, 3, 0, 0);
+
+ run(heir);
+ }
+
+ /**
+ * CALL into account which SELFDESTRUCT's followed by the caller DELEGATECALL'ing the callee again
+ * inducing SELFDESTRUCT in caller this time.
+ *
+ * Both reverted and unreverted versions
+ */
+ @ParameterizedTest
+ @EnumSource(Heir.class)
+ public void calleeThenCallerSelfDestruct(Heir heir) {
+ appendCall(toAccountCode, OpCode.CALL, 100_000, Heir.selfDestructorAddress, 25, 0, 3, 0, 0);
+ appendCall(
+ toAccountCode, OpCode.DELEGATECALL, 100_000, Heir.selfDestructorAddress, 12, 0, 4, 0, 0);
+
+ run(heir);
+ }
+
+ @ParameterizedTest
+ @EnumSource(Heir.class)
+ public void calleeThenCallerSelfDestructReverted(Heir heir) {
+ appendCall(toAccountCode, OpCode.CALL, 100_000, Heir.selfDestructorAddress, 25, 0, 3, 0, 0);
+ appendCall(
+ toAccountCode, OpCode.DELEGATECALL, 100_000, Heir.selfDestructorAddress, 12, 0, 4, 0, 0);
+ appendRevert(toAccountCode, 0, 0);
+
+ run(heir);
+ }
+}
diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/selfdestructTests/SeveralSelfDestructsInARowModifyingStorageTests.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/selfdestructTests/SeveralSelfDestructsInARowModifyingStorageTests.java
new file mode 100644
index 0000000000..563415b02c
--- /dev/null
+++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/selfdestructTests/SeveralSelfDestructsInARowModifyingStorageTests.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright Consensys Software Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package net.consensys.linea.zktracer.instructionprocessing.selfdestructTests;
+
+import net.consensys.linea.testing.ToyAccount;
+import net.consensys.linea.zktracer.instructionprocessing.utilities.*;
+import org.hyperledger.besu.crypto.KeyPair;
+import org.hyperledger.besu.crypto.SECP256K1;
+import org.hyperledger.besu.datatypes.Address;
+import org.hyperledger.besu.datatypes.Hash;
+import org.hyperledger.besu.datatypes.Wei;
+
+public class SeveralSelfDestructsInARowModifyingStorageTests {
+ Address modifyStorageThenSelfDestructAddress = Address.fromHexString("ffc0de");
+ Hash hash = Hash.fromHexString("modifyStorageThenSelfDestruct");
+ Address multipleCallsAddress = Address.fromHexString("ca11e7");
+
+ public static KeyPair keyPair = new SECP256K1().generateKeyPair();
+ public static Address userAddress =
+ Address.extract(Hash.hash(keyPair.getPublicKey().getEncodedBytes()));
+ public static ToyAccount userAccount =
+ ToyAccount.builder().balance(Wei.fromEth(10)).nonce(99).address(userAddress).build();
+
+ private ToyAccount modifyStorageThenSelfDestruct =
+ ToyAccount.builder()
+ .balance(Wei.fromEth(1))
+ .nonce(13)
+ .address(modifyStorageThenSelfDestructAddress)
+ .code(SelfDestructs.storageTouchingSelfDestructorRewardsZeroAddress().compile())
+ .build();
+}
diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/selfdestructTests/todo.md b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/selfdestructTests/todo.md
new file mode 100644
index 0000000000..494c265bdf
--- /dev/null
+++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/selfdestructTests/todo.md
@@ -0,0 +1,43 @@
+# SELFDESTRUCT related testing
+
+## The complexity
+
+SELFDESTRUCT's are complex. They interact with
+- **balance**
+- **storage**
+- **code**
+- they are REVERT-sensitive
+- they behave differently in a CALL/CALLCODE/DELEGATECALL/STATICCALL execution context
+- they lead to the (temporary) deployment of empty bytecode if performed in a deployment context
+
+What further complicates their analysis is that they only take effect at transaction end.
+Thus inside of the transaction an account MARKED_FOR_SELFDESTRUCT will
+- remain callable with the same code
+- its storage can still be interacted with
+- it can regain balance (which will be wiped in the end)
+- it can do more deployments ... which will be unaffected by the SD
+Thus multi block tests will be required.
+
+They also allow one to (attempt) to re-CREATE(2) at the same address.
+
+There is the classical example of how to produce a CREATE address collision:
+- accountA CREATE2's accountB with init code ic
+- accountB CREATE's accountC when its nonce is n
+- accountB SELFDESTRUCT's
+- accountA re-CREATE2's accountB with the same init code ic (and same deployed code)
+- accountB re-CREATE's accountC, using the same nonce n ...
+- ... which leads to a collision of addresses with CREATE :D
+
+## The tests
+
+- [ ] repeated SELFDESTRUCT's
+ - [ ] same SMC sd's several times in same transaction
+- [ ] sd'ing + getting a value transfer before deletion
+- [ ] sd'ing + storage all the time repeatedly afterwards
+- [ ] sd'ing + reverting the sd
+- [ ] sd'ing + [y/n] reverting the sd + sd'ing again + [y/n] reverting the sd
+ - [ ] while deploying new accounts
+- [ ] sd'ing in the root of a message call transaction
+- [ ] sd'ing in the root of a deployment transaction
+ - **Note.** This leads to a temporary deployment of empty bytecode that immediately gets wiped.
+- [ ] sd'ing and redeploying
\ No newline at end of file
diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/utilities/Calls.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/utilities/Calls.java
new file mode 100644
index 0000000000..aad30723b1
--- /dev/null
+++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/utilities/Calls.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright Consensys Software Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package net.consensys.linea.zktracer.instructionprocessing.utilities;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static net.consensys.linea.zktracer.opcode.OpCode.*;
+
+import net.consensys.linea.testing.BytecodeCompiler;
+import net.consensys.linea.testing.ToyAccount;
+import net.consensys.linea.zktracer.opcode.OpCode;
+import org.apache.tuweni.bytes.Bytes32;
+import org.hyperledger.besu.datatypes.Address;
+
+public class Calls {
+
+ public static final String fullEoaAddress = "000000000000000000000000abcdef0123456789";
+ public static final String toTrim12 = "aaaaaaaaaaaaaaaaaaaaaaaa";
+ public static final String untrimmedEoaAddress = toTrim12 + fullEoaAddress;
+ public static final String eoaAddress = "c0ffeef00d";
+ public static final String eoaAddress2 = "badbeef";
+
+ public static Bytes32 randRao =
+ Bytes32.fromHexString("0b03478988fb194f3ddd922bbc4e9fb415fbdb99818f88186ccfa206337b023d");
+ public static Bytes32 randCdo =
+ Bytes32.fromHexString("1a3b88fc78471a5d0ce2df8a5799299b7eefd8e6bfd6d6afb0e437e0a6311878");
+
+ public static void appendFullGasCall(
+ BytecodeCompiler program,
+ OpCode callOpcode,
+ Address to,
+ int value,
+ int cdo,
+ int cds,
+ int rao,
+ int rac) {
+ program.push(rac).push(rao).push(cds).push(cdo);
+ if (callOpcode.callHasValueArgument()) {
+ program.push(value);
+ }
+ program.push(to).op(GAS).op(callOpcode);
+ }
+
+ public static void fullBalanceCall(
+ BytecodeCompiler program, OpCode callOpcode, Address to, int cdo, int cds, int rao, int rac) {
+ program.push(rac).push(rao).push(cds).push(cdo);
+ if (callOpcode.callHasValueArgument()) {
+ program.op(BALANCE);
+ }
+ program.push(to).op(GAS).op(callOpcode);
+ }
+
+ public static void appendRevert(BytecodeCompiler program, int rdo, int rds) {
+ program.push(rds).push(rdo).op(REVERT);
+ }
+
+ public static void appendCall(
+ BytecodeCompiler program,
+ OpCode callOpcode,
+ int gas,
+ Address to,
+ int value,
+ int cdo,
+ int cds,
+ int rao,
+ int rac) {
+ program.push(rac).push(rao).push(cds).push(cdo);
+ if (callOpcode.callHasValueArgument()) {
+ program.push(value);
+ }
+ program.push(to).push(gas).op(callOpcode);
+ }
+
+ public static void appendExtremalCall(
+ BytecodeCompiler program,
+ OpCode callOpcode,
+ int gas,
+ ToyAccount toAccount,
+ int value,
+ boolean emptyCallData,
+ boolean emptyReturnAt) {
+
+ // return at parameters
+ if (emptyReturnAt) {
+ program.push(0).push(randRao);
+ } else {
+ program.push(256).push(257);
+ }
+
+ // call data parameters
+ if (emptyCallData) {
+ program.push(0).push(randCdo);
+ } else {
+ program.push(258).push(259);
+ }
+
+ if (callOpcode.callHasValueArgument()) {
+ program.push(value);
+ }
+ program.push(toAccount.getAddress()).push(gas).op(callOpcode);
+ }
+
+ public static void appendInsufficientBalanceCall(
+ BytecodeCompiler program,
+ OpCode callOpcode,
+ int gas,
+ Address to,
+ int cdo,
+ int cds,
+ int rao,
+ int rac) {
+ checkArgument(callOpcode.callHasValueArgument());
+ program
+ .push(rac)
+ .push(rao)
+ .push(cds)
+ .push(cdo)
+ .op(BALANCE)
+ .push(1)
+ .op(ADD) // puts balance + 1 on the stack
+ .push(to)
+ .push(gas)
+ .op(callOpcode);
+ }
+
+ public static void appendRecursiveSelfCall(BytecodeCompiler program, OpCode callOpCode) {
+ checkArgument(callOpCode.isCall());
+ program.push(0).push(0).push(0).push(0);
+ if (callOpCode.callHasValueArgument()) {
+ program.push("1000"); // value
+ }
+ program
+ .op(ADDRESS) // current address
+ .op(GAS) // providing all available gas
+ .op(callOpCode); // self-call
+ }
+
+ /**
+ * Pushing, in order: h, v, r, s; values produce a valid signature.
+ *
+ * @param program
+ */
+ public static void validEcrecoverData(BytecodeCompiler program) {
+ program
+ .push("279d94621558f755796898fc4bd36b6d407cae77537865afe523b79c74cc680b")
+ .push(0)
+ .op(MSTORE)
+ .push("1b")
+ .push(32)
+ .op(MSTORE)
+ .push("c2ff96feed8749a5ad1c0714f950b5ac939d8acedbedcbc2949614ab8af06312")
+ .push(64)
+ .op(MSTORE)
+ .push("1feecd50adc6273fdd5d11c6da18c8cfe14e2787f5a90af7c7c1328e7d0a2c42")
+ .push(96)
+ .op(MSTORE);
+ }
+
+ public static void appendGibberishReturn(BytecodeCompiler program) {
+ program.op(CALLER).op(EXTCODEHASH).op(DUP1);
+ program.push(1).push(0).op(SUB); // writes 0xffff...ff onto the stack
+ program.op(XOR);
+ program.push(11).op(MSTORE);
+ program.push(50).op(MSTORE);
+ program.push(77).push(3).op(RETURN); // returning some of that with zeros at the start
+ }
+
+ public static class ProgramIncrement {
+
+ public final BytecodeCompiler program;
+ public final int initialSize;
+
+ public ProgramIncrement(BytecodeCompiler program) {
+ this.program = program;
+ this.initialSize = program.compile().size();
+ }
+
+ public int sizeDelta() {
+ return program.compile().size() - initialSize;
+ }
+ }
+}
diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/smc/Utilities.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/utilities/MonoOpCodeSmcs.java
similarity index 95%
rename from arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/smc/Utilities.java
rename to arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/utilities/MonoOpCodeSmcs.java
index e0ef573629..d81289648c 100644
--- a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/smc/Utilities.java
+++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/utilities/MonoOpCodeSmcs.java
@@ -12,7 +12,7 @@
*
* SPDX-License-Identifier: Apache-2.0
*/
-package net.consensys.linea.zktracer.instructionprocessing.callTests.smc;
+package net.consensys.linea.zktracer.instructionprocessing.utilities;
import java.util.List;
@@ -24,7 +24,7 @@
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.Wei;
-public class Utilities {
+public class MonoOpCodeSmcs {
public static KeyPair keyPair = new SECP256K1().generateKeyPair();
public static Address userAddress =
diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/utilities/MultiOpCodeSmcs.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/utilities/MultiOpCodeSmcs.java
new file mode 100644
index 0000000000..a0145ad234
--- /dev/null
+++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/utilities/MultiOpCodeSmcs.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright Consensys Software Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package net.consensys.linea.zktracer.instructionprocessing.utilities;
+
+import static net.consensys.linea.zktracer.instructionprocessing.utilities.Calls.*;
+import static net.consensys.linea.zktracer.opcode.OpCode.*;
+
+import net.consensys.linea.testing.BytecodeCompiler;
+import net.consensys.linea.testing.ToyAccount;
+import org.hyperledger.besu.datatypes.Address;
+import org.hyperledger.besu.datatypes.Wei;
+
+public class MultiOpCodeSmcs {
+
+ /**
+ * Produces a program that triggers all opcodes from the CONTEXT instruction family.
+ *
+ * @return
+ */
+ public static BytecodeCompiler allContextOpCodes() {
+
+ BytecodeCompiler program = BytecodeCompiler.newProgram();
+ program
+ .op(ADDRESS)
+ .op(CALLDATASIZE)
+ .op(RETURNDATASIZE) // will return 0, but will be tested in the caller
+ .op(CALLER)
+ .op(CALLVALUE);
+
+ // producing some gibberish return data
+ appendGibberishReturn(program);
+
+ return program;
+ }
+
+ public static ToyAccount allContextOpCodesSmc =
+ ToyAccount.builder()
+ .balance(Wei.fromEth(9))
+ .nonce(13)
+ .address(Address.fromHexString("c0de"))
+ .code(allContextOpCodes().compile())
+ .build();
+}
diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/utilities/SelfDestructs.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/utilities/SelfDestructs.java
new file mode 100644
index 0000000000..3fb393d9c0
--- /dev/null
+++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/utilities/SelfDestructs.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright Consensys Software Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package net.consensys.linea.zktracer.instructionprocessing.utilities;
+
+import static net.consensys.linea.zktracer.opcode.OpCode.*;
+import static org.hyperledger.besu.datatypes.Address.ECREC;
+
+import net.consensys.linea.testing.BytecodeCompiler;
+import net.consensys.linea.zktracer.opcode.OpCode;
+
+public class SelfDestructs {
+
+ /**
+ * The zero selfDestructorAddress will likely be cold.
+ *
+ * @param program
+ */
+ public static void zeroRecipientSelfDestruct(BytecodeCompiler program) {
+ program.push(0).op(OpCode.SELFDESTRUCT);
+ }
+
+ public static BytecodeCompiler seldestructWithRecipientLoadedFromStorage(
+ BytecodeCompiler program) {
+ return program
+ .push(0)
+ .op(OpCode.SLOAD) // value will be interpreted as recipient; recipient will be cold;
+ .op(OpCode.SELFDESTRUCT);
+ }
+
+ // will have to be tested in conjunction with DELEGATECALL and CALLCODE
+ public static BytecodeCompiler selfReferentialSelfDestruct(BytecodeCompiler program) {
+ return program
+ .op(ADDRESS) // one self, thus already warm
+ .op(OpCode.SELFDESTRUCT);
+ }
+
+ // will have to be tested in conjunction with DELEGATECALL and CALLCODE
+ public static BytecodeCompiler recipientIsCallerSelfDestruct(BytecodeCompiler program) {
+ return program
+ .op(CALLER) // warm caller;
+ .op(OpCode.SELFDESTRUCT);
+ }
+
+ // will have to be tested in conjunction with DELEGATECALL and CALLCODE
+ public static BytecodeCompiler recipientIsOriginSelfDestruct(BytecodeCompiler program) {
+ return program
+ .op(ORIGIN) // warm origin;
+ .op(OpCode.SELFDESTRUCT);
+ }
+
+ // will have to be tested in conjunction with DELEGATECALL and CALLCODE
+ public static int recipientIsPrecompileSelfDestruct(BytecodeCompiler program) {
+ Calls.ProgramIncrement increment = new Calls.ProgramIncrement(program);
+ program
+ .push(ECREC) // precompiles are warm by default
+ .op(OpCode.SELFDESTRUCT);
+ return increment.sizeDelta();
+ }
+
+ public static int createValueFromContextParameters(BytecodeCompiler program) {
+ Calls.ProgramIncrement increment = new Calls.ProgramIncrement(program);
+
+ program
+ .push(256)
+ .op(CALLDATASIZE)
+ .push(5003)
+ .op(ADD)
+ .op(CALLVALUE)
+ .push(1789)
+ .op(ADD)
+ .op(MUL)
+ .op(MOD);
+
+ return increment.sizeDelta();
+ }
+
+ public static BytecodeCompiler storageTouchingSelfDestructorRewardsZeroAddress() {
+
+ BytecodeCompiler selfDestructor = BytecodeCompiler.newProgram();
+ loadAndStoreValues(selfDestructor);
+ // selfDestructWithZeroRecipient(selfDestructor);
+
+ return selfDestructor;
+ }
+
+ public static BytecodeCompiler variableRecipientStorageTouchingSelfDestructor() {
+
+ BytecodeCompiler selfDestructor = BytecodeCompiler.newProgram();
+ loadAndStoreValues(selfDestructor);
+ seldestructWithRecipientLoadedFromStorage(selfDestructor);
+
+ return selfDestructor;
+ }
+
+ /**
+ * The following code snippet
+ *
+ * - computes a value from context parameters and duplicates it
+ *
+ * - loads a values from storage slot 0, adds the previous value to it and stores the result in
+ * storage slot 0
+ *
+ * - does the same for storage slot 1 (but adds 1 to the result) uses that value to over write
+ * these values in storage.
+ *
+ * @return code
+ */
+ public static void loadAndStoreValues(BytecodeCompiler loadAndOverWriteValuesInStorage) {
+
+ SelfDestructs.createValueFromContextParameters(loadAndOverWriteValuesInStorage);
+ loadAndOverWriteValuesInStorage.op(DUP1);
+ loadAndOverWriteValuesInStorage.push(0);
+ loadAndOverWriteValuesInStorage.op(SLOAD); // load σ[acc]_s[0]
+ loadAndOverWriteValuesInStorage.op(ADD); // adding computed value to current storage value
+ loadAndOverWriteValuesInStorage.push(0).op(SSTORE); // overwriting storage valueσ σ[acc]_s[0]
+ loadAndOverWriteValuesInStorage.push(1);
+ loadAndOverWriteValuesInStorage.op(SLOAD); // load σ[acc]_s[1]
+ loadAndOverWriteValuesInStorage.op(ADD); // adding computed value to current storage value
+ loadAndOverWriteValuesInStorage.push(1).op(ADD);
+ loadAndOverWriteValuesInStorage.push(1).op(SSTORE); // overwriting storage valueσ σ[acc]_s[1]
+ }
+}
diff --git a/gradle.properties b/gradle.properties
index cd76cc1dd0..2bd6de8875 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,5 +1,5 @@
releaseVersion=0.8.0-rc5
-besuVersion=24.11-develop-96e9ed8
+besuVersion=24.11-develop-139a0f1
besuArtifactGroup=io.consensys.linea-besu
distributionIdentifier=linea-tracer
distributionBaseUrl=https://artifacts.consensys.net/public/linea-besu/raw/names/linea-besu.tar.gz/versions/
diff --git a/linea-constraints b/linea-constraints
index ec03fa8464..909628d415 160000
--- a/linea-constraints
+++ b/linea-constraints
@@ -1 +1 @@
-Subproject commit ec03fa84647e7cfe0e290601a9a6b20d164a8dbc
+Subproject commit 909628d4159e412ff78ee2803ce9b1579aca1a55
diff --git a/testing/src/main/java/net/consensys/linea/testing/BytecodeRunner.java b/testing/src/main/java/net/consensys/linea/testing/BytecodeRunner.java
index 1ebe9ff86a..f38dbf78c6 100644
--- a/testing/src/main/java/net/consensys/linea/testing/BytecodeRunner.java
+++ b/testing/src/main/java/net/consensys/linea/testing/BytecodeRunner.java
@@ -52,6 +52,10 @@ public BytecodeRunner(Bytes byteCode) {
this.byteCode = byteCode;
}
+ public static BytecodeRunner of(BytecodeCompiler program) {
+ return new BytecodeRunner(program.compile());
+ }
+
public static BytecodeRunner of(Bytes byteCode) {
return new BytecodeRunner(byteCode);
}