Skip to content

Commit f0ede37

Browse files
shemnondaniellehrner
authored andcommitted
Add hooks to AbstractCreateOperation for library users (hyperledger#5656)
Add hooks for a successful contract create, a failed contract create, and an invalid contact create. Users of the library will be able to customize create responses without having to replace core logic. Signed-off-by: Danno Ferrin <[email protected]> Signed-off-by: Daniel Lehrner <[email protected]>
1 parent 64e3d25 commit f0ede37

File tree

3 files changed

+280
-1
lines changed

3 files changed

+280
-1
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
12
# Changelog
23

34
## 23.4.5
@@ -7,6 +8,7 @@
78
### Additions and Improvements
89
- EvmTool now executes the `execution-spec-tests` via the `t8n` and `b11r`. See the [README](ethereum/evmtool/README.md) in EvmTool for more instructions.
910
- Improve lifecycle management of the transaction pool [#5634](https://github.com/hyperledger/besu/pull/5634)
11+
- Add extension points in AbstractCreateOperation for EVM libraries to react to contract creations [#5656](https://github.com/hyperledger/besu/pull/5656)
1012

1113
### Bug Fixes
1214
- Use the node's configuration to determine if DNS enode URLs are allowed in calls to `admin_addPeer` and `admin_removePeer` [#5584](https://github.com/hyperledger/besu/pull/5584)

evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java

+43-1
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,14 @@
2222
import org.hyperledger.besu.evm.EVM;
2323
import org.hyperledger.besu.evm.account.MutableAccount;
2424
import org.hyperledger.besu.evm.code.CodeFactory;
25+
import org.hyperledger.besu.evm.code.CodeInvalid;
2526
import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
2627
import org.hyperledger.besu.evm.frame.MessageFrame;
2728
import org.hyperledger.besu.evm.gascalculator.GasCalculator;
2829
import org.hyperledger.besu.evm.internal.Words;
2930

31+
import java.util.Optional;
32+
3033
import org.apache.tuweni.bytes.Bytes;
3134

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

186189
if (childFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS) {
187190
frame.mergeWarmedUpFields(childFrame);
188-
frame.pushStackItem(Words.fromAddress(childFrame.getContractAddress()));
191+
Address createdAddress = childFrame.getContractAddress();
192+
frame.pushStackItem(Words.fromAddress(createdAddress));
193+
onSuccess(frame, createdAddress);
189194
} else {
190195
frame.setReturnData(childFrame.getOutputData());
191196
frame.pushStackItem(FAILURE_STACK_ITEM);
197+
onFailure(frame, childFrame.getExceptionalHaltReason());
192198
}
193199
} else {
194200
frame.getWorldUpdater().deleteAccount(childFrame.getRecipientAddress());
195201
frame.setReturnData(childFrame.getOutputData());
196202
frame.pushStackItem(FAILURE_STACK_ITEM);
203+
onInvalid(frame, (CodeInvalid) outputCode);
197204
}
198205

199206
final int currentPC = frame.getPC();
200207
frame.setPC(currentPC + 1);
201208
}
209+
210+
/**
211+
* Called when the child {@code CONTRACT_CREATION} message has completed successfully, used to
212+
* give library users a chance to do implementation specific logic.
213+
*
214+
* @param frame the frame running the successful operation
215+
* @param createdAddress the address of the newly created contract
216+
*/
217+
protected void onSuccess(final MessageFrame frame, final Address createdAddress) {
218+
// no-op by default
219+
}
220+
221+
/**
222+
* Called when the child {@code CONTRACT_CREATION} message has failed to execute, used to give
223+
* library users a chance to do implementation specific logic.
224+
*
225+
* @param frame the frame running the successful operation
226+
* @param haltReason the exceptional halt reason of the child frame
227+
*/
228+
protected void onFailure(
229+
final MessageFrame frame, final Optional<ExceptionalHaltReason> haltReason) {
230+
// no-op by default
231+
}
232+
233+
/**
234+
* Called when the child {@code CONTRACT_CREATION} message has completed successfully but the
235+
* returned contract is invalid per chain rules, used to give library users a chance to do
236+
* implementation specific logic.
237+
*
238+
* @param frame the frame running the successful operation
239+
* @param invalidCode the code object containing the invalid code
240+
*/
241+
protected void onInvalid(final MessageFrame frame, final CodeInvalid invalidCode) {
242+
// no-op by default
243+
}
202244
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
/*
2+
* Copyright contributors to Hyperledger Besu
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*
13+
* SPDX-License-Identifier: Apache-2.0
14+
*
15+
*/
16+
package org.hyperledger.besu.evm.operation;
17+
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
import static org.mockito.ArgumentMatchers.any;
20+
import static org.mockito.Mockito.mock;
21+
import static org.mockito.Mockito.when;
22+
23+
import org.hyperledger.besu.datatypes.Address;
24+
import org.hyperledger.besu.datatypes.Hash;
25+
import org.hyperledger.besu.datatypes.Wei;
26+
import org.hyperledger.besu.evm.EVM;
27+
import org.hyperledger.besu.evm.MainnetEVMs;
28+
import org.hyperledger.besu.evm.account.Account;
29+
import org.hyperledger.besu.evm.account.MutableAccount;
30+
import org.hyperledger.besu.evm.code.CodeFactory;
31+
import org.hyperledger.besu.evm.code.CodeInvalid;
32+
import org.hyperledger.besu.evm.frame.BlockValues;
33+
import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
34+
import org.hyperledger.besu.evm.frame.MessageFrame;
35+
import org.hyperledger.besu.evm.gascalculator.ConstantinopleGasCalculator;
36+
import org.hyperledger.besu.evm.gascalculator.GasCalculator;
37+
import org.hyperledger.besu.evm.internal.EvmConfiguration;
38+
import org.hyperledger.besu.evm.internal.Words;
39+
import org.hyperledger.besu.evm.processor.ContractCreationProcessor;
40+
import org.hyperledger.besu.evm.tracing.OperationTracer;
41+
import org.hyperledger.besu.evm.worldstate.WorldUpdater;
42+
import org.hyperledger.besu.evm.worldstate.WrappedEvmAccount;
43+
44+
import java.util.ArrayDeque;
45+
import java.util.List;
46+
import java.util.Optional;
47+
48+
import org.apache.tuweni.bytes.Bytes;
49+
import org.apache.tuweni.units.bigints.UInt256;
50+
import org.junit.jupiter.api.Test;
51+
52+
class AbstractCreateOperationTest {
53+
54+
private final WorldUpdater worldUpdater = mock(WorldUpdater.class);
55+
private final WrappedEvmAccount account = mock(WrappedEvmAccount.class);
56+
private final WrappedEvmAccount newAccount = mock(WrappedEvmAccount.class);
57+
private final MutableAccount mutableAccount = mock(MutableAccount.class);
58+
private final MutableAccount newMutableAccount = mock(MutableAccount.class);
59+
private final FakeCreateOperation operation =
60+
new FakeCreateOperation(new ConstantinopleGasCalculator(), Integer.MAX_VALUE);
61+
62+
private static final Bytes SIMPLE_CREATE =
63+
Bytes.fromHexString(
64+
"0x"
65+
+ "6000" // PUSH1 0x00
66+
+ "6000" // PUSH1 0x00
67+
+ "F3" // RETURN
68+
);
69+
private static final Bytes POP_UNDERFLOW_CREATE =
70+
Bytes.fromHexString(
71+
"0x"
72+
+ "50" // POP (but empty stack)
73+
+ "6000" // PUSH1 0x00
74+
+ "6000" // PUSH1 0x00
75+
+ "F3" // RETURN
76+
);
77+
public static final Bytes INVALID_EOF =
78+
Bytes.fromHexString(
79+
"0x"
80+
+ "73EF99010100040200010001030000000000000000" // PUSH20 contract
81+
+ "6000" // PUSH1 0x00
82+
+ "52" // MSTORE
83+
+ "6014" // PUSH1 20
84+
+ "600c" // PUSH1 12
85+
+ "F3" // RETURN
86+
);
87+
public static final String SENDER = "0xdeadc0de00000000000000000000000000000000";
88+
89+
/** The Create operation. */
90+
public static class FakeCreateOperation extends AbstractCreateOperation {
91+
92+
private MessageFrame successFrame;
93+
private Address successCreatedAddress;
94+
private MessageFrame failureFrame;
95+
private Optional<ExceptionalHaltReason> failureHaltReason;
96+
private MessageFrame invalidFrame;
97+
private CodeInvalid invalidInvalidCode;
98+
99+
/**
100+
* Instantiates a new Create operation.
101+
*
102+
* @param gasCalculator the gas calculator
103+
* @param maxInitcodeSize Maximum init code size
104+
*/
105+
public FakeCreateOperation(final GasCalculator gasCalculator, final int maxInitcodeSize) {
106+
super(0xEF, "FAKECREATE", 3, 1, gasCalculator, maxInitcodeSize);
107+
}
108+
109+
@Override
110+
public long cost(final MessageFrame frame) {
111+
return gasCalculator().createOperationGasCost(frame);
112+
}
113+
114+
@Override
115+
protected Address targetContractAddress(final MessageFrame frame) {
116+
final Account sender = frame.getWorldUpdater().get(frame.getRecipientAddress());
117+
// Decrement nonce by 1 to normalize the effect of transaction execution
118+
final Address address =
119+
Address.contractAddress(frame.getRecipientAddress(), sender.getNonce() - 1L);
120+
frame.warmUpAddress(address);
121+
return address;
122+
}
123+
124+
@Override
125+
protected void onSuccess(final MessageFrame frame, final Address createdAddress) {
126+
successFrame = frame;
127+
successCreatedAddress = createdAddress;
128+
}
129+
130+
@Override
131+
protected void onFailure(
132+
final MessageFrame frame, final Optional<ExceptionalHaltReason> haltReason) {
133+
failureFrame = frame;
134+
failureHaltReason = haltReason;
135+
}
136+
137+
@Override
138+
protected void onInvalid(final MessageFrame frame, final CodeInvalid invalidCode) {
139+
invalidFrame = frame;
140+
invalidInvalidCode = invalidCode;
141+
}
142+
}
143+
144+
private void executeOperation(final Bytes contract, final EVM evm) {
145+
final UInt256 memoryOffset = UInt256.fromHexString("0xFF");
146+
final ArrayDeque<MessageFrame> messageFrameStack = new ArrayDeque<>();
147+
final MessageFrame messageFrame =
148+
MessageFrame.builder()
149+
.type(MessageFrame.Type.CONTRACT_CREATION)
150+
.contract(Address.ZERO)
151+
.inputData(Bytes.EMPTY)
152+
.sender(Address.fromHexString(SENDER))
153+
.value(Wei.ZERO)
154+
.apparentValue(Wei.ZERO)
155+
.code(CodeFactory.createCode(SIMPLE_CREATE, 0, true))
156+
.depth(1)
157+
.completer(__ -> {})
158+
.address(Address.fromHexString(SENDER))
159+
.blockHashLookup(n -> Hash.hash(Words.longBytes(n)))
160+
.blockValues(mock(BlockValues.class))
161+
.gasPrice(Wei.ZERO)
162+
.messageFrameStack(messageFrameStack)
163+
.miningBeneficiary(Address.ZERO)
164+
.originator(Address.ZERO)
165+
.initialGas(100000L)
166+
.worldUpdater(worldUpdater)
167+
.build();
168+
messageFrame.pushStackItem(Bytes.ofUnsignedLong(contract.size()));
169+
messageFrame.pushStackItem(memoryOffset);
170+
messageFrame.pushStackItem(Bytes.EMPTY);
171+
messageFrame.expandMemory(0, 500);
172+
messageFrame.writeMemory(memoryOffset.trimLeadingZeros().toInt(), contract.size(), contract);
173+
174+
when(account.getMutable()).thenReturn(mutableAccount);
175+
when(account.getNonce()).thenReturn(55L);
176+
when(mutableAccount.getBalance()).thenReturn(Wei.ZERO);
177+
when(worldUpdater.getAccount(any())).thenReturn(account);
178+
when(worldUpdater.get(any())).thenReturn(account);
179+
when(worldUpdater.getSenderAccount(any())).thenReturn(account);
180+
when(worldUpdater.getOrCreate(any())).thenReturn(newAccount);
181+
when(newAccount.getMutable()).thenReturn(newMutableAccount);
182+
when(newMutableAccount.getCode()).thenReturn(Bytes.EMPTY);
183+
when(worldUpdater.updater()).thenReturn(worldUpdater);
184+
185+
operation.execute(messageFrame, evm);
186+
final MessageFrame createFrame = messageFrameStack.peek();
187+
final ContractCreationProcessor ccp =
188+
new ContractCreationProcessor(evm.getGasCalculator(), evm, false, List.of(), 0, List.of());
189+
ccp.process(createFrame, OperationTracer.NO_TRACING);
190+
}
191+
192+
@Test
193+
void onSuccess() {
194+
final EVM evm = MainnetEVMs.london(EvmConfiguration.DEFAULT);
195+
196+
executeOperation(SIMPLE_CREATE, evm);
197+
198+
assertThat(operation.successFrame).isNotNull();
199+
assertThat(operation.successCreatedAddress)
200+
.isEqualTo(Address.fromHexString("0xecccb0113190dfd26a044a7f26f45152a4270a64"));
201+
assertThat(operation.failureFrame).isNull();
202+
assertThat(operation.failureHaltReason).isNull();
203+
assertThat(operation.invalidFrame).isNull();
204+
assertThat(operation.invalidInvalidCode).isNull();
205+
}
206+
207+
@Test
208+
void onFailure() {
209+
final EVM evm = MainnetEVMs.london(EvmConfiguration.DEFAULT);
210+
211+
executeOperation(POP_UNDERFLOW_CREATE, evm);
212+
213+
assertThat(operation.successFrame).isNull();
214+
assertThat(operation.successCreatedAddress).isNull();
215+
assertThat(operation.failureFrame).isNotNull();
216+
assertThat(operation.failureHaltReason)
217+
.contains(ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS);
218+
assertThat(operation.invalidFrame).isNull();
219+
assertThat(operation.invalidInvalidCode).isNull();
220+
}
221+
222+
@Test
223+
void onInvalid() {
224+
final EVM evm = MainnetEVMs.futureEips(EvmConfiguration.DEFAULT);
225+
226+
executeOperation(INVALID_EOF, evm);
227+
228+
assertThat(operation.successFrame).isNull();
229+
assertThat(operation.successCreatedAddress).isNull();
230+
assertThat(operation.failureFrame).isNull();
231+
assertThat(operation.failureHaltReason).isNull();
232+
assertThat(operation.invalidFrame).isNotNull();
233+
assertThat(operation.invalidInvalidCode).isNotNull();
234+
}
235+
}

0 commit comments

Comments
 (0)