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 postOpcodeDefers = new ArrayList<>(); /** A list of actions deferred to the immediate entry into a child or parent context */ - private final List immediateContextEntryDefers = new ArrayList<>(); + private final List contextEntryDefers = new ArrayList<>(); /** A list of actions deferred to the end of a given context */ private final Map> contextExitDefers = new HashMap<>(); @@ -63,8 +63,8 @@ public class DeferRegistry private final Map> rollbackDefers = new HashMap<>(); /** Schedule an action to be executed after the completion of the current opcode. */ - public void scheduleForImmediateContextEntry(ImmediateContextEntryDefer defer) { - immediateContextEntryDefers.add(defer); + public void scheduleForContextEntry(ContextEntryDefer defer) { + contextEntryDefers.add(defer); } /** Schedule an action to be executed after the completion of the current opcode. */ @@ -189,10 +189,10 @@ public void resolveUponRollback( @Override public void resolveUponContextEntry(Hub hub) { - for (ImmediateContextEntryDefer defer : immediateContextEntryDefers) { + for (ContextEntryDefer defer : contextEntryDefers) { defer.resolveUponContextEntry(hub); } - immediateContextEntryDefers.clear(); + contextEntryDefers.clear(); } @Override diff --git a/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/fragment/imc/StpCall.java b/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/fragment/imc/StpCall.java index 0a49b63afe..a6ceb1fe63 100644 --- a/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/fragment/imc/StpCall.java +++ b/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/fragment/imc/StpCall.java @@ -77,24 +77,15 @@ public StpCall(Hub hub, long memoryExpansionGas) { private void stpCallForCalls(Hub hub) { final MessageFrame frame = hub.messageFrame(); - final boolean callCanTransferValue = opCode.callCanTransferValue(); + final boolean callHasValueArgument = opCode.callHasValueArgument(); final Address to = Words.toAddress(frame.getStackItem(1)); final Account toAccount = frame.getWorldUpdater().get(to); this.gas = EWord.of(frame.getStackItem(0)); - this.value = (callCanTransferValue) ? EWord.of(frame.getStackItem(2)) : ZERO; + this.value = (callHasValueArgument) ? EWord.of(frame.getStackItem(2)) : ZERO; this.exists = switch (hub.opCode()) { - case CALL, STATICCALL -> toAccount != null - ? !toAccount.isEmpty() - : false; // the address that matters here is that of the callee - case CALLCODE, - DELEGATECALL -> true; // the address that matters here is that of the caller --- who - // always exists - // TODO: @Olivier or @François: - // replace this with the same logic above with the - // current account (frame.getRecipientAddress() ?) - // also add arg check verifying existence == true - // in that case. + case CALL -> toAccount != null ? !toAccount.isEmpty() : false; + case CALLCODE, DELEGATECALL, STATICCALL -> false; default -> throw new IllegalArgumentException( "STP module triggered for a non CALL-type instruction"); }; diff --git a/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/fragment/scenario/CallScenarioFragment.java b/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/fragment/scenario/CallScenarioFragment.java index 8400819dff..6543bdcac6 100644 --- a/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/fragment/scenario/CallScenarioFragment.java +++ b/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/fragment/scenario/CallScenarioFragment.java @@ -36,6 +36,7 @@ public enum CallScenario { CALL_ABORT_WILL_REVERT, CALL_ABORT_WONT_REVERT, // Externally owned account call scenarios + CALL_EOA_UNDEFINED, CALL_EOA_SUCCESS_WILL_REVERT, CALL_EOA_SUCCESS_WONT_REVERT, // Smart contract call scenarios: @@ -50,18 +51,44 @@ public enum CallScenario { CALL_PRC_SUCCESS_WILL_REVERT, CALL_PRC_SUCCESS_WONT_REVERT; - public boolean isPrecompileScenario() { + public boolean isIndefinitePrcCallScenario() { + return this == CALL_PRC_UNDEFINED || isPrcCallScenario(); + } + + public boolean isPrcCallScenario() { return this == CALL_PRC_FAILURE || this == CALL_PRC_SUCCESS_WILL_REVERT || this == CALL_PRC_SUCCESS_WONT_REVERT; } + public boolean isIndefiniteSmcCallScenario() { + return this == CALL_SMC_UNDEFINED || isSmcCallScenario(); + } + + public boolean isSmcCallScenario() { + return this == CALL_SMC_FAILURE_WILL_REVERT + || this == CALL_SMC_FAILURE_WONT_REVERT + || this == CALL_SMC_SUCCESS_WILL_REVERT + || this == CALL_SMC_SUCCESS_WONT_REVERT; + } + + public boolean isIndefiniteEoaCallScenario() { + return this == CALL_EOA_UNDEFINED || isEoaCallScenario(); + } + + public boolean isEoaCallScenario() { + return this == CALL_EOA_SUCCESS_WILL_REVERT || this == CALL_EOA_SUCCESS_WONT_REVERT; + } + public boolean isAbortingScenario() { return this == CALL_ABORT_WILL_REVERT || this == CALL_ABORT_WONT_REVERT; } public boolean noLongerUndefined() { - return this != UNDEFINED && this != CALL_PRC_UNDEFINED && this != CALL_SMC_UNDEFINED; + return this != UNDEFINED + && this != CALL_PRC_UNDEFINED + && this != CALL_SMC_UNDEFINED + && this != CALL_EOA_UNDEFINED; } } diff --git a/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/section/CreateSection.java b/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/section/CreateSection.java index e2af4e48b0..ee0eac5d16 100644 --- a/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/section/CreateSection.java +++ b/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/section/CreateSection.java @@ -62,11 +62,15 @@ public class CreateSection extends TraceSection implements PostOpcodeDefer, - ImmediateContextEntryDefer, + ContextEntryDefer, PostRollbackDefer, ContextReEntryDefer, PostTransactionDefer { + private Address creatorAddress; + private Address createeAddress; + final ImcFragment imcFragment; + // Just before create private AccountSnapshot preOpcodeCreatorSnapshot; private AccountSnapshot preOpcodeCreateeSnapshot; @@ -91,7 +95,7 @@ public class CreateSection extends TraceSection // TODO: according to our preliminary conclusion in issue #866 // CREATE's that raise a failure condition _do spawn a child context_. - public CreateSection(Hub hub) { + public CreateSection(Hub hub, MessageFrame frame) { super(hub, maxNumberOfLines(hub.pch().exceptions(), hub.pch().abortingConditions())); final short exceptions = hub.pch().exceptions(); @@ -106,7 +110,7 @@ public CreateSection(Hub hub) { this.addFragment(currentContextFragment); // row: i + 2 - final ImcFragment imcFragment = ImcFragment.empty(hub); + imcFragment = ImcFragment.empty(hub); this.addFragment(imcFragment); // STATICX case @@ -148,12 +152,11 @@ public CreateSection(Hub hub) { checkArgument(oobCall.isAbortingCondition() == aborts.any()); final CallFrame callFrame = hub.currentFrame(); - final MessageFrame messageFrame = hub.messageFrame(); - final Address creatorAddress = callFrame.accountAddress(); + creatorAddress = frame.getRecipientAddress(); preOpcodeCreatorSnapshot = AccountSnapshot.canonical(hub, creatorAddress); - final Address createeAddress = getDeploymentAddress(messageFrame); + createeAddress = getDeploymentAddress(frame); preOpcodeCreateeSnapshot = AccountSnapshot.canonical(hub, createeAddress); if (aborts.any()) { @@ -165,14 +168,14 @@ public CreateSection(Hub hub) { // The CREATE(2) is now unexceptional and unaborted checkArgument(aborts.none()); - hub.defers().scheduleForImmediateContextEntry(this); // when we add the two account fragments + hub.defers().scheduleForContextEntry(this); // when we add the two account fragments hub.defers().scheduleForPostRollback(this, hub.currentFrame()); // in case of Rollback hub.defers().scheduleForPostTransaction(this); // when we add the last context row rlpAddrSubFragment = RlpAddrSubFragment.makeFragment(hub, createeAddress); final Optional deploymentAccount = - Optional.ofNullable(messageFrame.getWorldUpdater().get(createeAddress)); + Optional.ofNullable(frame.getWorldUpdater().get(createeAddress)); final boolean createdAddressHasNonZeroNonce = deploymentAccount.map(a -> a.getNonce() != 0).orElse(false); final boolean createdAddressHasNonEmptyCode = @@ -181,12 +184,12 @@ public CreateSection(Hub hub) { final boolean failedCreate = createdAddressHasNonZeroNonce || createdAddressHasNonEmptyCode; final boolean emptyInitCode = hub.transients().op().initCodeSegment().isEmpty(); - final long offset = Words.clampedToLong(messageFrame.getStackItem(1)); - final long size = Words.clampedToLong(messageFrame.getStackItem(2)); + final long offset = Words.clampedToLong(frame.getStackItem(1)); + final long size = Words.clampedToLong(frame.getStackItem(2)); // Trigger MMU & SHAKIRA to hash the (non-empty) InitCode of CREATE2 - even for failed CREATE2 if (hub.opCode() == CREATE2 && !emptyInitCode) { - final Bytes create2InitCode = messageFrame.shadowReadMemory(offset, size); + final Bytes create2InitCode = frame.shadowReadMemory(offset, size); final MmuCall mmuCall = MmuCall.create2(hub, create2InitCode, failedCreate); imcFragment.callMmu(mmuCall); @@ -198,7 +201,7 @@ public CreateSection(Hub hub) { writeHashInfoResult(shakiraDataOperation.result()); } - value = failedCreate ? Wei.ZERO : Wei.of(UInt256.fromBytes(messageFrame.getStackItem(0))); + value = failedCreate ? Wei.ZERO : Wei.of(UInt256.fromBytes(frame.getStackItem(0))); if (failedCreate) { finalContextFragment = ContextFragment.nonExecutionProvidesEmptyReturnData(hub); @@ -226,11 +229,11 @@ public CreateSection(Hub hub) { .scheduleForContextReEntry(this, callFrame); // To get the success bit of the CREATE(2) requiresRomLex = true; - hub.romLex().callRomLex(messageFrame); + hub.romLex().callRomLex(frame); hub.transients() .conflation() .deploymentInfo() - .newDeploymentWithExecutionAt(createeAddress, messageFrame.shadowReadMemory(offset, size)); + .newDeploymentWithExecutionAt(createeAddress, frame.shadowReadMemory(offset, size)); // Note: the case CREATE2 has been set before, we need to do it even in the failure case if (hub.opCode() == CREATE) { diff --git a/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/section/call/CallSection.java b/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/section/call/CallSection.java index d34af30743..fc0abf2779 100644 --- a/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/section/call/CallSection.java +++ b/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/section/call/CallSection.java @@ -18,9 +18,9 @@ import static com.google.common.base.Preconditions.*; import static net.consensys.linea.zktracer.module.hub.AccountSnapshot.canonical; import static net.consensys.linea.zktracer.module.hub.fragment.scenario.CallScenarioFragment.CallScenario.*; +import static net.consensys.linea.zktracer.opcode.OpCode.CALL; import static net.consensys.linea.zktracer.types.AddressUtils.isPrecompile; import static net.consensys.linea.zktracer.types.Conversions.bytesToBoolean; -import static net.consensys.linea.zktracer.types.Conversions.bytesToInt; import static org.hyperledger.besu.datatypes.Address.*; import java.util.Map; @@ -32,9 +32,9 @@ import net.consensys.linea.zktracer.module.hub.AccountSnapshot; import net.consensys.linea.zktracer.module.hub.Factories; import net.consensys.linea.zktracer.module.hub.Hub; +import net.consensys.linea.zktracer.module.hub.defer.ContextEntryDefer; import net.consensys.linea.zktracer.module.hub.defer.ContextExitDefer; import net.consensys.linea.zktracer.module.hub.defer.ContextReEntryDefer; -import net.consensys.linea.zktracer.module.hub.defer.ImmediateContextEntryDefer; import net.consensys.linea.zktracer.module.hub.defer.PostOpcodeDefer; import net.consensys.linea.zktracer.module.hub.defer.PostRollbackDefer; import net.consensys.linea.zktracer.module.hub.defer.PostTransactionDefer; @@ -50,6 +50,8 @@ import net.consensys.linea.zktracer.module.hub.section.TraceSection; import net.consensys.linea.zktracer.module.hub.section.call.precompileSubsection.*; import net.consensys.linea.zktracer.module.hub.signals.Exceptions; +import net.consensys.linea.zktracer.opcode.OpCode; +import net.consensys.linea.zktracer.runtime.callstack.CallDataInfo; import net.consensys.linea.zktracer.runtime.callstack.CallFrame; import net.consensys.linea.zktracer.types.EWord; import net.consensys.linea.zktracer.types.MemorySpan; @@ -58,13 +60,29 @@ import org.hyperledger.besu.datatypes.Transaction; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.internal.Words; import org.hyperledger.besu.evm.operation.Operation; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import org.hyperledger.besu.evm.worldstate.WorldView; +/** + * A {@link CallSection} first detects exceptional CALL-type instructions. Exceptional CALL's are + * easily dealt with and require no post-processing. + * + *

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> unexceptionalSelfDestructMap = hub.txStack().current().getUnexceptionalSelfDestructMap(); @@ -168,11 +172,12 @@ public SelfdestructSection(Hub hub) { selfdestructorAccountAfter = selfdestructorAccountBefore.deepCopy().setBalanceToZero(); - if (selfdestructTargetsItself) { + if (selfdestructTargetsItself()) { recipientAccountBefore = selfdestructorAccountAfter.deepCopy(); recipientAccountAfter = recipientAccountBefore.deepCopy(); } else { - recipientAccountBefore = AccountSnapshot.canonical(hub, recipientAddress); + recipientAccountBefore = + AccountSnapshot.canonical(hub, frame.getWorldUpdater(), recipientAddress); recipientAccountAfter = recipientAccountBefore .deepCopy() @@ -187,7 +192,7 @@ public SelfdestructSection(Hub hub) { .make( selfdestructorAccountBefore, selfdestructorAccountAfter, - DomSubStampsSubFragment.selfdestructDomSubStamps(hub)); + DomSubStampsSubFragment.standardDomSubStamps(hub.stamp(), 0)); recipientFirstAccountFragment = hub.factories() .accountFragment() @@ -195,7 +200,7 @@ public SelfdestructSection(Hub hub) { recipientAccountBefore, recipientAccountAfter, recipientAddressUntrimmed, - DomSubStampsSubFragment.selfdestructDomSubStamps(hub)); + DomSubStampsSubFragment.standardDomSubStamps(hub.stamp(), 1)); this.addFragment(selfdestructorFirstAccountFragment); this.addFragment(recipientFirstAccountFragment); @@ -239,10 +244,13 @@ public void resolveUponRollback(Hub hub, MessageFrame messageFrame, CallFrame ca @Override public void resolvePostTransaction( Hub hub, WorldView state, Transaction tx, boolean isSuccessful) { + if (selfDestructWasReverted) { + this.addFragment(finalUnexceptionalContextFragment); return; } + // beyond this point the self destruct was not reverted final Map effectiveSelfDestructMap = transactionProcessingMetadata.getEffectiveSelfDestructMap(); final EphemeralAccount ephemeralAccount = @@ -252,10 +260,10 @@ public void resolvePostTransaction( checkArgument(effectiveSelfDestructMap.containsKey(ephemeralAccount)); // We modify the account fragment to reflect the self-destruct time + final int hubStampOfTheSelfDestructThatSealedTheDeal = + effectiveSelfDestructMap.get(ephemeralAccount); - final int selfDestructTime = effectiveSelfDestructMap.get(ephemeralAccount); - - checkArgument(hubStamp >= selfDestructTime); + checkArgument(hubStamp >= hubStampOfTheSelfDestructThatSealedTheDeal); final AccountSnapshot accountBeforeSelfDestruct = transactionProcessingMetadata.getDestructedAccountsSnapshot().stream() @@ -264,7 +272,7 @@ public void resolvePostTransaction( .findFirst() .orElseThrow(() -> new IllegalStateException("Account not found")); - if (hubStamp == selfDestructTime) { + if (hubStamp == hubStampOfTheSelfDestructThatSealedTheDeal) { selfdestructScenarioFragment.setScenario( SelfdestructScenarioFragment.SelfdestructScenario .SELFDESTRUCT_WONT_REVERT_NOT_YET_MARKED); @@ -287,5 +295,11 @@ public void resolvePostTransaction( SelfdestructScenarioFragment.SelfdestructScenario .SELFDESTRUCT_WONT_REVERT_ALREADY_MARKED); } + + this.addFragment(finalUnexceptionalContextFragment); + } + + private boolean selfdestructTargetsItself() { + return addressWhichMaySelfDestruct.equals(recipientAddress); } } diff --git a/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/transients/OperationAncillaries.java b/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/transients/OperationAncillaries.java index 22de7df1ba..34ba51b2eb 100644 --- a/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/transients/OperationAncillaries.java +++ b/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/transients/OperationAncillaries.java @@ -209,9 +209,14 @@ public static MemorySpan outputDataSpan(final MessageFrame frame) { switch (opCode) { case RETURN, REVERT -> { + long size = Words.clampedToLong(frame.getStackItem(1)); + + if (size == 0) { + return MemorySpan.empty(); + } + long offset = Words.clampedToLong(frame.getStackItem(0)); - long length = Words.clampedToLong(frame.getStackItem(1)); - return MemorySpan.fromStartLength(offset, length); + return MemorySpan.fromStartLength(offset, size); } case STOP, SELFDESTRUCT -> { return MemorySpan.empty(); diff --git a/arithmetization/src/main/java/net/consensys/linea/zktracer/module/oob/OobOperation.java b/arithmetization/src/main/java/net/consensys/linea/zktracer/module/oob/OobOperation.java index 47c650ed39..737fe1ebe4 100644 --- a/arithmetization/src/main/java/net/consensys/linea/zktracer/module/oob/OobOperation.java +++ b/arithmetization/src/main/java/net/consensys/linea/zktracer/module/oob/OobOperation.java @@ -256,7 +256,7 @@ private void populateColumnsForEvmInstruction(MessageFrame frame) { // DELEGATECALL, STATICCALL can't trasfer value, // CALL, CALLCODE may transfer value - EWord value = opCode.callCanTransferValue() ? EWord.of(frame.getStackItem(2)) : EWord.ZERO; + EWord value = opCode.callHasValueArgument() ? EWord.of(frame.getStackItem(2)) : EWord.ZERO; CallOobCall callOobCall = (CallOobCall) oobCall; callOobCall.setValue(value); callOobCall.setBalance(callerAccount.getBalance().toUnsignedBigInteger()); @@ -302,11 +302,11 @@ public void populateColumnsForPrecompile(MessageFrame frame) { final OpCode opCode = getOpCode(frame); final long argsOffset = Words.clampedToLong( - opCode.callCanTransferValue() + opCode.callHasValueArgument() ? hub.messageFrame().getStackItem(3) : hub.messageFrame().getStackItem(2)); - final int cdsIndex = opCode.callCanTransferValue() ? 4 : 3; - final int returnAtCapacityIndex = opCode.callCanTransferValue() ? 6 : 5; + final int cdsIndex = opCode.callHasValueArgument() ? 4 : 3; + final int returnAtCapacityIndex = opCode.callHasValueArgument() ? 6 : 5; BigInteger calleeGas = BigInteger.ZERO; if (oobCall instanceof PrecompileCommonOobCall) { diff --git a/arithmetization/src/main/java/net/consensys/linea/zktracer/module/romlex/RomLex.java b/arithmetization/src/main/java/net/consensys/linea/zktracer/module/romlex/RomLex.java index d7207e4a71..fafcd5a562 100644 --- a/arithmetization/src/main/java/net/consensys/linea/zktracer/module/romlex/RomLex.java +++ b/arithmetization/src/main/java/net/consensys/linea/zktracer/module/romlex/RomLex.java @@ -34,8 +34,8 @@ import net.consensys.linea.zktracer.container.module.OperationSetModule; import net.consensys.linea.zktracer.container.stacked.ModuleOperationStackedSet; import net.consensys.linea.zktracer.module.hub.Hub; +import net.consensys.linea.zktracer.module.hub.defer.ContextEntryDefer; import net.consensys.linea.zktracer.module.hub.defer.ContextExitDefer; -import net.consensys.linea.zktracer.module.hub.defer.ImmediateContextEntryDefer; import net.consensys.linea.zktracer.runtime.callstack.CallFrame; import net.consensys.linea.zktracer.types.TransactionProcessingMetadata; import org.apache.tuweni.bytes.Bytes; @@ -50,7 +50,7 @@ @Accessors(fluent = true) @RequiredArgsConstructor public class RomLex - implements OperationSetModule, ImmediateContextEntryDefer, ContextExitDefer { + implements OperationSetModule, ContextEntryDefer, ContextExitDefer { private final Hub hub; @@ -161,7 +161,7 @@ public void callRomLex(final MessageFrame frame) { checkArgument(length > 0, "callRomLex expects positive size for CREATE(2)"); - hub.defers().scheduleForImmediateContextEntry(this); + hub.defers().scheduleForContextEntry(this); byteCode = frame.shadowReadMemory(offset, length); address = getDeploymentAddress(frame); } diff --git a/arithmetization/src/main/java/net/consensys/linea/zktracer/module/stp/Stp.java b/arithmetization/src/main/java/net/consensys/linea/zktracer/module/stp/Stp.java index 671a9b9834..4e37d7e3ca 100644 --- a/arithmetization/src/main/java/net/consensys/linea/zktracer/module/stp/Stp.java +++ b/arithmetization/src/main/java/net/consensys/linea/zktracer/module/stp/Stp.java @@ -61,7 +61,7 @@ public void call(StpCall stpCall) { if (stpCall.opCode().isCall()) { wcp.callLT(longToBytes32(stpCall.gasActual()), Bytes32.ZERO); - if (stpCall.opCode().callCanTransferValue()) { + if (stpCall.opCode().callHasValueArgument()) { wcp.callISZERO(Bytes32.leftPad(stpCall.value())); } wcp.callLT(longToBytes32(stpCall.gasActual()), longToBytes32(stpCall.upfrontGasCost())); diff --git a/arithmetization/src/main/java/net/consensys/linea/zktracer/module/stp/StpOperation.java b/arithmetization/src/main/java/net/consensys/linea/zktracer/module/stp/StpOperation.java index 883350a52b..d5046bd9b7 100644 --- a/arithmetization/src/main/java/net/consensys/linea/zktracer/module/stp/StpOperation.java +++ b/arithmetization/src/main/java/net/consensys/linea/zktracer/module/stp/StpOperation.java @@ -180,7 +180,7 @@ private void traceCall(Trace trace, int stamp) { .arg2Lo(Bytes.EMPTY) .exogenousModuleInstruction(UnsignedByte.of(OpCode.ISZERO.byteValue())) .resLo(Bytes.of(stpCall.value().isZero() ? 1 : 0)) - .wcpFlag(stpCall.opCode().callCanTransferValue()) + .wcpFlag(stpCall.opCode().callHasValueArgument()) .modFlag(false) .fillAndValidateRow(); case 2 -> trace diff --git a/arithmetization/src/main/java/net/consensys/linea/zktracer/opcode/OpCode.java b/arithmetization/src/main/java/net/consensys/linea/zktracer/opcode/OpCode.java index 67ca70ee9c..f13cc31e67 100644 --- a/arithmetization/src/main/java/net/consensys/linea/zktracer/opcode/OpCode.java +++ b/arithmetization/src/main/java/net/consensys/linea/zktracer/opcode/OpCode.java @@ -237,12 +237,16 @@ public boolean isCall() { return getData().isCall(); } - public boolean callMayNotTransferValue() { + public boolean isCallOrCreate() { + return isCall() || isCreate(); + } + + public boolean callHasNoValueArgument() { checkArgument(isCall()); return this == OpCode.DELEGATECALL || this == OpCode.STATICCALL; } - public boolean callCanTransferValue() { + public boolean callHasValueArgument() { checkArgument(isCall()); return this == OpCode.CALL || this == OpCode.CALLCODE; } diff --git a/arithmetization/src/main/java/net/consensys/linea/zktracer/runtime/callstack/CallDataInfo.java b/arithmetization/src/main/java/net/consensys/linea/zktracer/runtime/callstack/CallDataInfo.java index eefe8dcbda..f27777b605 100644 --- a/arithmetization/src/main/java/net/consensys/linea/zktracer/runtime/callstack/CallDataInfo.java +++ b/arithmetization/src/main/java/net/consensys/linea/zktracer/runtime/callstack/CallDataInfo.java @@ -15,14 +15,23 @@ package net.consensys.linea.zktracer.runtime.callstack; +import static com.google.common.base.Preconditions.checkArgument; + import lombok.Getter; import lombok.experimental.Accessors; import net.consensys.linea.zktracer.types.MemorySpan; import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.frame.MessageFrame; @Accessors(fluent = true) @Getter public class CallDataInfo { + private static final CallDataInfo EMPTY = new CallDataInfo(Bytes.EMPTY, 0, 0, 0); + + public static CallDataInfo empty() { + return EMPTY; + } + private final Bytes data; private final MemorySpan memorySpan; private final long callDataContextNumber; @@ -32,8 +41,18 @@ public CallDataInfo( final long callDataOffset, final long callDataSize, final long callDataContextNumber) { + + checkArgument(data.size() == callDataSize); this.data = data; this.memorySpan = new MemorySpan(callDataOffset, callDataSize); this.callDataContextNumber = callDataContextNumber; } + + public CallDataInfo( + final MessageFrame frame, final MemorySpan span, final int callDataContextNumber) { + this.callDataContextNumber = callDataContextNumber; + this.memorySpan = span; + this.data = + (span.isEmpty()) ? Bytes.EMPTY : frame.shadowReadMemory(span.offset(), span.length()); + } } diff --git a/arithmetization/src/main/java/net/consensys/linea/zktracer/runtime/callstack/CallFrame.java b/arithmetization/src/main/java/net/consensys/linea/zktracer/runtime/callstack/CallFrame.java index 0172959463..b115006749 100644 --- a/arithmetization/src/main/java/net/consensys/linea/zktracer/runtime/callstack/CallFrame.java +++ b/arithmetization/src/main/java/net/consensys/linea/zktracer/runtime/callstack/CallFrame.java @@ -216,11 +216,8 @@ public static void updateParentContextReturnData( * @param byteCodeDeploymentNumber DN of this call frame in the {@link Hub} * @param byteCode byteCode that executes in the present context * @param callerAddress either account address of the caller/creator context - * @param callDataContextNumber CN of the RAM segment wherein the call data lives * @param parentId ID of the caller frame in the {@link CallStack} - * @param callData {@link Bytes} containing this frame's call data - * @param callDataOffset offset of call data in the caller's RAM (if applicable) - * @param callDataSize size (in bytes) of the call data + * @param callDataInfo call data of the current frame */ CallFrame( CallFrameType type, @@ -236,11 +233,8 @@ public static void updateParentContextReturnData( int byteCodeDeploymentNumber, Bytecode byteCode, Address callerAddress, - long callDataContextNumber, int parentId, - Bytes callData, - long callDataOffset, - long callDataSize, + CallDataInfo callDataInfo, MemorySpan returnDataTargetInCaller) { this.type = type; this.id = id; @@ -256,8 +250,7 @@ public static void updateParentContextReturnData( this.code = byteCode; this.callerAddress = callerAddress; this.parentId = parentId; - this.callDataInfo = - new CallDataInfo(callData, callDataOffset, callDataSize, callDataContextNumber); + this.callDataInfo = callDataInfo; this.outputDataSpan = MemorySpan.empty(); this.returnDataSpan = MemorySpan.empty(); this.returnDataTargetInCaller = returnDataTargetInCaller; diff --git a/arithmetization/src/main/java/net/consensys/linea/zktracer/runtime/callstack/CallStack.java b/arithmetization/src/main/java/net/consensys/linea/zktracer/runtime/callstack/CallStack.java index 9b0dcede16..39db6cc01e 100644 --- a/arithmetization/src/main/java/net/consensys/linea/zktracer/runtime/callstack/CallStack.java +++ b/arithmetization/src/main/java/net/consensys/linea/zktracer/runtime/callstack/CallStack.java @@ -84,10 +84,7 @@ public void newRootContext( codeDeploymentNumber, toCode == null ? Bytecode.EMPTY : toCode, from, - callData, - 0, - callData.size(), - callDataContextNumber, + new CallDataInfo(callData, 0, callData.size(), callDataContextNumber), MemorySpan.empty()); this.currentId = this.callFrames.size() - 1; } @@ -113,16 +110,7 @@ public void newTransactionCallDataContext(int transactionCallDataContextNumber, 0, Bytecode.EMPTY, Address.ZERO, // useless - // useless - // useless - // useless - callData, - 0, - callData.size(), - transactionCallDataContextNumber, - // useless - // useless - // useless + CallDataInfo.empty(), MemorySpan.empty()); this.currentId = this.callFrames.size() - 1; } @@ -169,7 +157,6 @@ public Optional maybeCurrent() { * @param accountDeploymentNumber * @param byteCodeDeploymentNumber * @param byteCode the {@link Code} being executed - * @param inputData the call data sent to this call frame */ public void enter( CallFrameType type, @@ -183,19 +170,12 @@ public void enter( int byteCodeDeploymentNumber, Bytecode byteCode, Address callerAddress, - Bytes inputData, - long callDataOffset, - long callDataSize, - long callDataContextNumber, + CallDataInfo callDataInfo, MemorySpan returnDataTargetInCaller) { final int callerId = this.depth == -1 ? -1 : this.currentId; final int newCallFrameId = this.callFrames.size(); this.depth += 1; - Bytes callData = Bytes.EMPTY; - if (type != CallFrameType.INIT_CODE) { - callData = inputData; - } final CallFrame newFrame = new CallFrame( type, @@ -211,11 +191,8 @@ public void enter( byteCodeDeploymentNumber, byteCode, callerAddress, - callDataContextNumber, callerId, - callData, - callDataOffset, - callDataSize, + callDataInfo, returnDataTargetInCaller); this.callFrames.add(newFrame); diff --git a/arithmetization/src/main/java/net/consensys/linea/zktracer/runtime/stack/Stack.java b/arithmetization/src/main/java/net/consensys/linea/zktracer/runtime/stack/Stack.java index cdfb618c2d..ab7965943c 100644 --- a/arithmetization/src/main/java/net/consensys/linea/zktracer/runtime/stack/Stack.java +++ b/arithmetization/src/main/java/net/consensys/linea/zktracer/runtime/stack/Stack.java @@ -278,7 +278,7 @@ private void call(MessageFrame frame, StackContext pending) { Bytes val5 = getStack(frame, 4); Bytes val6 = getStack(frame, 5); - boolean callCanTransferValue = currentOpcodeData.mnemonic().callCanTransferValue(); + boolean callCanTransferValue = currentOpcodeData.mnemonic().callHasValueArgument(); if (callCanTransferValue) { Bytes val7 = getStack(frame, 6); diff --git a/arithmetization/src/main/java/net/consensys/linea/zktracer/types/MemorySpan.java b/arithmetization/src/main/java/net/consensys/linea/zktracer/types/MemorySpan.java index 454a61441d..09809c348e 100644 --- a/arithmetization/src/main/java/net/consensys/linea/zktracer/types/MemorySpan.java +++ b/arithmetization/src/main/java/net/consensys/linea/zktracer/types/MemorySpan.java @@ -22,8 +22,11 @@ * @param length the region length */ public record MemorySpan(long offset, long length) { + + private static final MemorySpan EMPTY = new MemorySpan(0, 0); + public static MemorySpan empty() { - return new MemorySpan(0, 0); + return EMPTY; } /** diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/exceptions/StaticExceptionTest.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/exceptions/StaticExceptionTest.java index 0fc2fcea8b..791e31e783 100644 --- a/arithmetization/src/test/java/net/consensys/linea/zktracer/exceptions/StaticExceptionTest.java +++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/exceptions/StaticExceptionTest.java @@ -16,6 +16,7 @@ package net.consensys.linea.zktracer.exceptions; import static net.consensys.linea.zktracer.module.hub.signals.TracedException.STATIC_FAULT; +import static net.consensys.linea.zktracer.opcode.OpCode.GAS; import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.List; @@ -42,7 +43,7 @@ void staticExceptionDueToCallWithNonZeroValueTest(int value) { .push(0) // call data size .push(0) // call data offset .push("ca11ee") // address - .push(1000) // gas + .op(GAS) .op(OpCode.STATICCALL); BytecodeCompiler calleeProgram = BytecodeCompiler.newProgram(); @@ -52,8 +53,8 @@ void staticExceptionDueToCallWithNonZeroValueTest(int value) { .push(0) // call data size .push(0) // call data offset .push(value) // value - .push("ca11ee") // address - .push(1000) // gas + .push(Address.ZERO) // address + .op(GAS) .op(OpCode.CALL); final ToyAccount calleeAccount = @@ -64,7 +65,7 @@ void staticExceptionDueToCallWithNonZeroValueTest(int value) { .code(calleeProgram.compile()) .build(); - BytecodeRunner bytecodeRunner = BytecodeRunner.of(program.compile()); + BytecodeRunner bytecodeRunner = BytecodeRunner.of(program); bytecodeRunner.run(List.of(calleeAccount)); diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/ContextFamilyTests/MessageCallTests.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/ContextFamilyTests/MessageCallTests.java new file mode 100644 index 0000000000..5408d06942 --- /dev/null +++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/ContextFamilyTests/MessageCallTests.java @@ -0,0 +1,109 @@ +/* + * 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.ContextFamilyTests; + +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.instructionprocessing.utilities.MultiOpCodeSmcs.allContextOpCodesSmc; +import static net.consensys.linea.zktracer.opcode.OpCode.*; + +import java.util.ArrayList; +import java.util.List; + +import net.consensys.linea.testing.*; +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 MessageCallTests { + + /** + * This test serves to verify that context data is correctly initialized after a CALL-type + * instruction. + */ + @ParameterizedTest + @EnumSource( + value = OpCode.class, + names = {"CALL", "CALLCODE", "DELEGATECALL", "STATICCALL"}) + public void testWithCall(OpCode opCode) { + + ToyAccount recipientAccount = buildRecipient(opCode); + + List accounts = new ArrayList<>(); + accounts.add(userAccount); + accounts.add(allContextOpCodesSmc); + accounts.add(recipientAccount); + + ToyExecutionEnvironmentV2.builder() + .transaction(buildTransaction(recipientAccount)) + .accounts(accounts) + .transactionProcessingResultValidator(TransactionProcessingResultValidator.EMPTY_VALIDATOR) + .build() + .run(); + } + + /** + * The recipient will see only its return data change. We take the opportunity to test context + * data, too. + * + * @return + */ + private ToyAccount buildRecipient(OpCode callOpCode) { + + BytecodeCompiler recipientCode = BytecodeCompiler.newProgram(); + recipientCode.op(CALLDATASIZE); + recipientCode.op(RETURNDATASIZE); + recipientCode.op(CALLER); + recipientCode.op(ADDRESS); + recipientCode.op(CALLVALUE); + appendCall( + recipientCode, + callOpCode, + 100_000, + allContextOpCodesSmc.getAddress(), + 1664, + 13, + 91, + 51, + 77); + recipientCode.op(CALLDATASIZE); + recipientCode.op(RETURNDATASIZE); + + return ToyAccount.builder() + .balance(Wei.of(500_000L)) + .code(recipientCode.compile()) + .nonce(891) + .address(Address.fromHexString("c0dec0ffee31")) + .build(); + } + + private Transaction buildTransaction(ToyAccount recipientAccount) { + return ToyTransaction.builder() + .sender(userAccount) + .to(recipientAccount) + .value(Wei.of(3_000_000L)) + .payload(Bytes.fromHexString("0xaabb3311ee88")) + .gasLimit(1_000_000L) + .keyPair(keyPair) + .nonce(userAccount.getNonce()) + .gasPrice(Wei.of(8L)) + .build(); + } +} diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/ContextFamilyTests/RootOfMessageCallTests.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/ContextFamilyTests/RootOfMessageCallTests.java new file mode 100644 index 0000000000..6d9f6e5c91 --- /dev/null +++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/ContextFamilyTests/RootOfMessageCallTests.java @@ -0,0 +1,30 @@ +/* + * 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.ContextFamilyTests; + +import static net.consensys.linea.zktracer.instructionprocessing.utilities.MultiOpCodeSmcs.allContextOpCodes; + +import net.consensys.linea.testing.BytecodeCompiler; +import net.consensys.linea.testing.BytecodeRunner; +import org.junit.jupiter.api.Test; + +public class RootOfMessageCallTests { + + @Test + public void messageCallTest() { + BytecodeCompiler program = allContextOpCodes(); + BytecodeRunner.of(program).run(); + } +} diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/ContextFamilyTests/todo.md b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/ContextFamilyTests/todo.md new file mode 100644 index 0000000000..f49d2a8bdf --- /dev/null +++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/ContextFamilyTests/todo.md @@ -0,0 +1,14 @@ +# Testing the CONTEXT family + +- [ ] in the root context + - [x] of a message call transaction + - [ ] of a deployment transaction +- [ ] in the child context + - [x] after a CALL-type instruction + - [x] CALL + - [x] CALLCODE + - [ ] DELEGATECALL + - [x] STATICCALL + - [ ] of a CREATE-type instruction + - [ ] CREATE + - [ ] CREATE2 diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/ZeroSizeTests/CallArguments.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/ZeroSizeTests/CallArguments.java new file mode 100644 index 0000000000..aaabe150d4 --- /dev/null +++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/ZeroSizeTests/CallArguments.java @@ -0,0 +1,75 @@ +/* + * 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.ZeroSizeTests; + +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.testing.ToyAccount; +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; + +/** + * In the {@link CallArguments} tests, we test the extremal cases of CALLs with respect to "call + * data" and "return at ..." parameters. This follows up on the recent change in constraints where, + * focusing on CALLs only, we set CDO to zero whenever CDS ≡ 0, and similarly for R@0 and R@C. + */ +public class CallArguments { + + @ParameterizedTest + @EnumSource( + value = OpCode.class, + names = {"CALL", "CALLCODE", "DELEGATECALL", "STATICCALL"}) + void emptyCallDataAndReturnAtCall(OpCode callOpCode) { + BytecodeCompiler program = BytecodeCompiler.newProgram(); + extremalCallContract(program, callOpCode, true, true); + BytecodeRunner.of(program.compile()).run(); + } + + @ParameterizedTest + @EnumSource( + value = OpCode.class, + names = {"CALL", "CALLCODE", "DELEGATECALL", "STATICCALL"}) + void emptyReturnAtCall(OpCode callOpCode) { + BytecodeCompiler program = BytecodeCompiler.newProgram(); + extremalCallContract(program, callOpCode, false, true); + BytecodeRunner.of(program.compile()).run(); + } + + @ParameterizedTest + @EnumSource( + value = OpCode.class, + names = {"CALL", "CALLCODE", "DELEGATECALL", "STATICCALL"}) + void emptyCallDataCall(OpCode callOpCode) { + BytecodeCompiler program = BytecodeCompiler.newProgram(); + extremalCallContract(program, callOpCode, true, false); + BytecodeRunner.of(program.compile()).run(); + } + + private void extremalCallContract( + BytecodeCompiler program, OpCode callOpCode, boolean emptyCallData, boolean emptyReturnAt) { + appendExtremalCall( + program, + callOpCode, + 300_000, + ToyAccount.builder().address(Address.fromHexString(eoaAddress)).build(), + 2048, + emptyCallData, + emptyReturnAt); + } +} diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/ZeroSizeTests/CallArgumentsMaybeRedundant.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/ZeroSizeTests/CallArgumentsMaybeRedundant.java new file mode 100644 index 0000000000..05829feda7 --- /dev/null +++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/ZeroSizeTests/CallArgumentsMaybeRedundant.java @@ -0,0 +1,121 @@ +/* + * 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.ZeroSizeTests; + +import java.util.List; + +import net.consensys.linea.testing.BytecodeCompiler; +import net.consensys.linea.testing.BytecodeRunner; +import net.consensys.linea.testing.ToyAccount; +import net.consensys.linea.zktracer.opcode.OpCode; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.junit.jupiter.api.Test; + +public class CallArgumentsMaybeRedundant { + /* + * Copyright ConsenSys 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 + */ + + @Test + void zeroReturnAtCapacityTest() { + BytecodeCompiler program = BytecodeCompiler.newProgram(); + program + .push(0) // return at capacity + .push("ff".repeat(32)) // return at offset + .push(0) // call data size + .push(0) // call data offset + .push("ca11ee") // address + .push(1000) // gas + .op(OpCode.STATICCALL); + + BytecodeCompiler calleeProgram = BytecodeCompiler.newProgram(); + calleeProgram.op(OpCode.CALLDATASIZE); + + final ToyAccount calleeAccount = + ToyAccount.builder() + .balance(Wei.fromEth(1)) + .nonce(10) + .address(Address.fromHexString("ca11ee")) + .code(calleeProgram.compile()) + .build(); + + BytecodeRunner.of(program.compile()).run(Wei.fromEth(1), 30000L, List.of(calleeAccount)); + // TODO: this test is supposed to fail as the ones below, but it does not. Understand why + } + + @Test + void zeroCallDataSizeTest() { + BytecodeCompiler program = BytecodeCompiler.newProgram(); + program + .push(0) // return at capacity + .push(0) // return at offset + .push(0) // call data size + .push("ff".repeat(32)) // call data offset + .push("ca11ee") // address + .push(1000) // gas + .op(OpCode.STATICCALL); + + BytecodeCompiler calleeProgram = BytecodeCompiler.newProgram(); + calleeProgram.op(OpCode.CALLDATASIZE); + + final ToyAccount calleeAccount = + ToyAccount.builder() + .balance(Wei.fromEth(1)) + .nonce(10) + .address(Address.fromHexString("ca11ee")) + .code(calleeProgram.compile()) + .build(); + + BytecodeRunner.of(program.compile()).run(Wei.fromEth(1), 30000L, List.of(calleeAccount)); + } + + @Test + void zeroCallDataSizeAndReturnAtCapacityTest() { + BytecodeCompiler program = BytecodeCompiler.newProgram(); + program + .push(0) // return at capacity + .push("ff".repeat(32)) // return at offset + .push(0) // call data size + .push("ff".repeat(32)) // call data offset + .push("ca11ee") // address + .push(1000) // gas + .op(OpCode.STATICCALL); + + BytecodeCompiler calleeProgram = BytecodeCompiler.newProgram(); + calleeProgram.push(0).push("ff".repeat(32)).op(OpCode.RETURN); + + final ToyAccount calleeAccount = + ToyAccount.builder() + .balance(Wei.fromEth(1)) + .nonce(10) + .address(Address.fromHexString("ca11ee")) + .code(calleeProgram.compile()) + .build(); + + BytecodeRunner.of(program.compile()).run(Wei.fromEth(1), 30000L, List.of(calleeAccount)); + } +} diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/ZeroSizeTests/ReturnRevertArguments.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/ZeroSizeTests/ReturnRevertArguments.java new file mode 100644 index 0000000000..b0075ce8f7 --- /dev/null +++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/ZeroSizeTests/ReturnRevertArguments.java @@ -0,0 +1,235 @@ +/* + * 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.ZeroSizeTests; + +import static com.google.common.base.Preconditions.checkArgument; +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.*; +import net.consensys.linea.zktracer.opcode.OpCode; +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 ReturnRevertArguments { + + public String hugeOffset = "ff".repeat(32); + + /** + * The following test does the following: + * + *

- 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); }