Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add hooks to AbstractCreateOperation for library users #5656

Merged
merged 3 commits into from
Jul 4, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,14 @@
import org.hyperledger.besu.evm.EVM;
import org.hyperledger.besu.evm.account.MutableAccount;
import org.hyperledger.besu.evm.code.CodeFactory;
import org.hyperledger.besu.evm.code.CodeInvalid;
import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.gascalculator.GasCalculator;
import org.hyperledger.besu.evm.internal.Words;

import java.util.Optional;

import org.apache.tuweni.bytes.Bytes;

/** The Abstract create operation. */
Expand Down Expand Up @@ -185,18 +188,57 @@ private void complete(final MessageFrame frame, final MessageFrame childFrame, f

if (childFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS) {
frame.mergeWarmedUpFields(childFrame);
frame.pushStackItem(Words.fromAddress(childFrame.getContractAddress()));
Address createdAddress = childFrame.getContractAddress();
frame.pushStackItem(Words.fromAddress(createdAddress));
onSuccess(frame, createdAddress);
} else {
frame.setReturnData(childFrame.getOutputData());
frame.pushStackItem(FAILURE_STACK_ITEM);
onFailure(frame, childFrame.getExceptionalHaltReason());
}
} else {
frame.getWorldUpdater().deleteAccount(childFrame.getRecipientAddress());
frame.setReturnData(childFrame.getOutputData());
frame.pushStackItem(FAILURE_STACK_ITEM);
onInvalid(frame, (CodeInvalid) outputCode);
}

final int currentPC = frame.getPC();
frame.setPC(currentPC + 1);
}

/**
* Called when the child {@code CONTRACT_CREATION} message has completed successfully, used to
* give library users a chance to do implementation specific logic.
*
* @param frame the frame running the successful operation
* @param createdAddress the address of the newly created contract
*/
protected void onSuccess(final MessageFrame frame, final Address createdAddress) {

Check notice

Code scanning / CodeQL

Useless parameter

The parameter 'frame' is never used.

Check notice

Code scanning / CodeQL

Useless parameter

The parameter 'createdAddress' is never used.
// no-op by default
}

/**
* Called when the child {@code CONTRACT_CREATION} message has failed to execute, used to give
* library users a chance to do implementation specific logic.
*
* @param frame the frame running the successful operation
* @param haltReason the exceptional halt reason of the child frame
*/
protected void onFailure(
final MessageFrame frame, final Optional<ExceptionalHaltReason> haltReason) {

Check notice

Code scanning / CodeQL

Useless parameter

The parameter 'frame' is never used.

Check notice

Code scanning / CodeQL

Useless parameter

The parameter 'haltReason' is never used.
// no-op by default
}

/**
* Called when the child {@code CONTRACT_CREATION} message has completed successfully but the
* returned contract is invalid per chain rules, used to give library users a chance to do
* implementation specific logic.
*
* @param frame the frame running the successful operation
* @param invalidCode the code object containing the invalid code
*/
protected void onInvalid(final MessageFrame frame, final CodeInvalid invalidCode) {

Check notice

Code scanning / CodeQL

Useless parameter

The parameter 'frame' is never used.

Check notice

Code scanning / CodeQL

Useless parameter

The parameter 'invalidCode' is never used.
// no-op by default
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
/*
* Copyright contributors to Hyperledger Besu
*
* 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 org.hyperledger.besu.evm.operation;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.evm.EVM;
import org.hyperledger.besu.evm.MainnetEVMs;
import org.hyperledger.besu.evm.account.Account;
import org.hyperledger.besu.evm.account.MutableAccount;
import org.hyperledger.besu.evm.code.CodeFactory;
import org.hyperledger.besu.evm.code.CodeInvalid;
import org.hyperledger.besu.evm.frame.BlockValues;
import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.gascalculator.ConstantinopleGasCalculator;
import org.hyperledger.besu.evm.gascalculator.GasCalculator;
import org.hyperledger.besu.evm.internal.EvmConfiguration;
import org.hyperledger.besu.evm.internal.Words;
import org.hyperledger.besu.evm.processor.ContractCreationProcessor;
import org.hyperledger.besu.evm.tracing.OperationTracer;
import org.hyperledger.besu.evm.worldstate.WorldUpdater;
import org.hyperledger.besu.evm.worldstate.WrappedEvmAccount;

import java.util.ArrayDeque;
import java.util.List;
import java.util.Optional;

import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.units.bigints.UInt256;
import org.junit.jupiter.api.Test;

class AbstractCreateOperationTest {

private final WorldUpdater worldUpdater = mock(WorldUpdater.class);
private final WrappedEvmAccount account = mock(WrappedEvmAccount.class);
private final WrappedEvmAccount newAccount = mock(WrappedEvmAccount.class);
private final MutableAccount mutableAccount = mock(MutableAccount.class);
private final MutableAccount newMutableAccount = mock(MutableAccount.class);
private final FakeCreateOperation operation =
new FakeCreateOperation(new ConstantinopleGasCalculator(), Integer.MAX_VALUE);

private static final Bytes SIMPLE_CREATE =
Bytes.fromHexString(
"0x"
+ "6000" // PUSH1 0x00
+ "6000" // PUSH1 0x00
+ "F3" // RETURN
);
private static final Bytes POP_UNDERFLOW_CREATE =
Bytes.fromHexString(
"0x"
+ "50" // POP (but empty stack)
+ "6000" // PUSH1 0x00
+ "6000" // PUSH1 0x00
+ "F3" // RETURN
);
public static final Bytes INVALID_EOF =
Bytes.fromHexString(
"0x"
+ "73EF99010100040200010001030000000000000000" // PUSH20 contract
+ "6000" // PUSH1 0x00
+ "52" // MSTORE
+ "6014" // PUSH1 20
+ "600c" // PUSH1 12
+ "F3" // RETURN
);
public static final String SENDER = "0xdeadc0de00000000000000000000000000000000";

/** The Create operation. */
public static class FakeCreateOperation extends AbstractCreateOperation {

private MessageFrame successFrame;
private Address successCreatedAddress;
private MessageFrame failureFrame;
private Optional<ExceptionalHaltReason> failureHaltReason;
private MessageFrame invalidFrame;
private CodeInvalid invalidInvalidCode;

/**
* Instantiates a new Create operation.
*
* @param gasCalculator the gas calculator
* @param maxInitcodeSize Maximum init code size
*/
public FakeCreateOperation(final GasCalculator gasCalculator, final int maxInitcodeSize) {
super(0xEF, "FAKECREATE", 3, 1, gasCalculator, maxInitcodeSize);
}

@Override
public long cost(final MessageFrame frame) {
return gasCalculator().createOperationGasCost(frame);
}

@Override
protected Address targetContractAddress(final MessageFrame frame) {
final Account sender = frame.getWorldUpdater().get(frame.getRecipientAddress());
// Decrement nonce by 1 to normalize the effect of transaction execution
final Address address =
Address.contractAddress(frame.getRecipientAddress(), sender.getNonce() - 1L);
frame.warmUpAddress(address);
return address;
}

@Override
protected void onSuccess(final MessageFrame frame, final Address createdAddress) {
successFrame = frame;
successCreatedAddress = createdAddress;
}

@Override
protected void onFailure(
final MessageFrame frame, final Optional<ExceptionalHaltReason> haltReason) {
failureFrame = frame;
failureHaltReason = haltReason;
}

@Override
protected void onInvalid(final MessageFrame frame, final CodeInvalid invalidCode) {
invalidFrame = frame;
invalidInvalidCode = invalidCode;
}
}

private void executeOperation(final Bytes contract, final EVM evm) {
final UInt256 memoryOffset = UInt256.fromHexString("0xFF");
final ArrayDeque<MessageFrame> messageFrameStack = new ArrayDeque<>();
final MessageFrame messageFrame =
MessageFrame.builder()
.type(MessageFrame.Type.CONTRACT_CREATION)
.contract(Address.ZERO)
.inputData(Bytes.EMPTY)
.sender(Address.fromHexString(SENDER))
.value(Wei.ZERO)
.apparentValue(Wei.ZERO)
.code(CodeFactory.createCode(SIMPLE_CREATE, 0, true))
.depth(1)
.completer(__ -> {})
.address(Address.fromHexString(SENDER))
.blockHashLookup(n -> Hash.hash(Words.longBytes(n)))
.blockValues(mock(BlockValues.class))
.gasPrice(Wei.ZERO)
.messageFrameStack(messageFrameStack)
.miningBeneficiary(Address.ZERO)
.originator(Address.ZERO)
.initialGas(100000L)
.worldUpdater(worldUpdater)
.build();
messageFrame.pushStackItem(Bytes.ofUnsignedLong(contract.size()));
messageFrame.pushStackItem(memoryOffset);
messageFrame.pushStackItem(Bytes.EMPTY);
messageFrame.expandMemory(0, 500);
messageFrame.writeMemory(memoryOffset.trimLeadingZeros().toInt(), contract.size(), contract);

when(account.getMutable()).thenReturn(mutableAccount);
when(account.getNonce()).thenReturn(55L);
when(mutableAccount.getBalance()).thenReturn(Wei.ZERO);
when(worldUpdater.getAccount(any())).thenReturn(account);
when(worldUpdater.get(any())).thenReturn(account);
when(worldUpdater.getSenderAccount(any())).thenReturn(account);
when(worldUpdater.getOrCreate(any())).thenReturn(newAccount);
when(newAccount.getMutable()).thenReturn(newMutableAccount);
when(newMutableAccount.getCode()).thenReturn(Bytes.EMPTY);
when(worldUpdater.updater()).thenReturn(worldUpdater);

operation.execute(messageFrame, evm);
final MessageFrame createFrame = messageFrameStack.peek();
final ContractCreationProcessor ccp =
new ContractCreationProcessor(evm.getGasCalculator(), evm, false, List.of(), 0, List.of());
ccp.process(createFrame, OperationTracer.NO_TRACING);
}

@Test
void onSuccess() {
final EVM evm = MainnetEVMs.london(EvmConfiguration.DEFAULT);

executeOperation(SIMPLE_CREATE, evm);

assertThat(operation.successFrame).isNotNull();
assertThat(operation.successCreatedAddress)
.isEqualTo(Address.fromHexString("0xecccb0113190dfd26a044a7f26f45152a4270a64"));
assertThat(operation.failureFrame).isNull();
assertThat(operation.failureHaltReason).isNull();
assertThat(operation.invalidFrame).isNull();
assertThat(operation.invalidInvalidCode).isNull();
}

@Test
void onFailure() {
final EVM evm = MainnetEVMs.london(EvmConfiguration.DEFAULT);

executeOperation(POP_UNDERFLOW_CREATE, evm);

assertThat(operation.successFrame).isNull();
assertThat(operation.successCreatedAddress).isNull();
assertThat(operation.failureFrame).isNotNull();
assertThat(operation.failureHaltReason)
.contains(ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS);
assertThat(operation.invalidFrame).isNull();
assertThat(operation.invalidInvalidCode).isNull();
}

@Test
void onInvalid() {
final EVM evm = MainnetEVMs.futureEips(EvmConfiguration.DEFAULT);

executeOperation(INVALID_EOF, evm);

assertThat(operation.successFrame).isNull();
assertThat(operation.successCreatedAddress).isNull();
assertThat(operation.failureFrame).isNull();
assertThat(operation.failureHaltReason).isNull();
assertThat(operation.invalidFrame).isNotNull();
assertThat(operation.invalidInvalidCode).isNotNull();
}
}