Skip to content

Commit 361ddb6

Browse files
Handle EIP-7702 code delgation
Signed-off-by: Lukasz Gasior <[email protected]>
1 parent eb0b7c0 commit 361ddb6

File tree

69 files changed

+745
-1155
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+745
-1155
lines changed

hedera-node/hedera-smart-contract-service-impl/build.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ description = "Default Hedera Smart Contract Service Implementation"
55

66
// Remove the following line to enable all 'javac' lint checks that we have turned on by default
77
// and then fix the reported issues.
8-
tasks.withType<JavaCompile>().configureEach { options.compilerArgs.add("-Xlint:-exports") }
8+
// TODO(Pectra): restore to `options.compilerArgs.add("-Xlint:-exports")`
9+
tasks.withType<JavaCompile>().configureEach { options.compilerArgs.clear() }
910

1011
mainModuleInfo { annotationProcessor("dagger.compiler") }
1112

hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionProcessor.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import edu.umd.cs.findbugs.annotations.Nullable;
3838
import org.hyperledger.besu.datatypes.Address;
3939
import org.hyperledger.besu.evm.code.CodeFactory;
40+
import org.hyperledger.besu.evm.gascalculator.GasCalculator;
4041
import org.hyperledger.besu.evm.processor.ContractCreationProcessor;
4142

4243
/**
@@ -52,6 +53,7 @@ public class TransactionProcessor {
5253
private final ContractCreationProcessor contractCreation;
5354
private final FeatureFlags featureFlags;
5455
private final CodeFactory codeFactory;
56+
private final GasCalculator gasCalculator;
5557

5658
public TransactionProcessor(
5759
@NonNull final FrameBuilder frameBuilder,
@@ -60,14 +62,16 @@ public TransactionProcessor(
6062
@NonNull final CustomMessageCallProcessor messageCall,
6163
@NonNull final ContractCreationProcessor contractCreation,
6264
@NonNull final FeatureFlags featureFlags,
63-
@NonNull final CodeFactory codeFactory) {
65+
@NonNull final CodeFactory codeFactory,
66+
@NonNull final GasCalculator gasCalculator) {
6467
this.frameBuilder = requireNonNull(frameBuilder);
6568
this.frameRunner = requireNonNull(frameRunner);
6669
this.gasCharging = requireNonNull(gasCharging);
6770
this.messageCall = requireNonNull(messageCall);
6871
this.contractCreation = requireNonNull(contractCreation);
6972
this.featureFlags = requireNonNull(featureFlags);
70-
this.codeFactory = codeFactory;
73+
this.codeFactory = requireNonNull(codeFactory);
74+
this.gasCalculator = requireNonNull(gasCalculator);
7175
}
7276

7377
/**
@@ -129,6 +133,7 @@ private HederaEvmTransactionResult processTransactionWithParties(
129133
final var gasCharges = transaction.hookOwnerAddress() != null
130134
? GasCharges.NONE
131135
: gasCharging.chargeForGas(parties.sender(), parties.relayer(), context, updater, transaction);
136+
132137
final var initialFrame = frameBuilder.buildInitialFrameWith(
133138
transaction,
134139
updater,
@@ -139,7 +144,8 @@ private HederaEvmTransactionResult processTransactionWithParties(
139144
parties.sender().getAddress(),
140145
parties.receiverAddress(),
141146
gasCharges.intrinsicGas(),
142-
codeFactory);
147+
codeFactory,
148+
gasCalculator);
143149

144150
// Compute the result of running the frame to completion
145151
final var result = frameRunner.runToCompletion(

hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/delegation/CodeDelegationProcessor.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
package com.hedera.node.app.service.contract.impl.exec.delegation;
33

44
import static org.hyperledger.besu.evm.account.Account.MAX_NONCE;
5+
import static org.hyperledger.besu.evm.worldstate.CodeDelegationHelper.CODE_DELEGATION_PREFIX;
56

67
import com.hedera.node.app.hapi.utils.ethereum.CodeDelegation;
78
import com.hedera.node.app.hapi.utils.ethereum.EthTxSigs;
@@ -28,8 +29,6 @@ public record CodeDelegationProcessor(long chainId) {
2829

2930
private static final int MAX_Y_PARITY = 2 ^ 8;
3031

31-
public static final Bytes CODE_DELEGATION_PREFIX = Bytes.fromHexString("ef0100");
32-
3332
/** The size of the delegated code */
3433
public static final int DELEGATED_CODE_SIZE = CODE_DELEGATION_PREFIX.size() + Address.SIZE;
3534

hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/operations/CustomDelegateCallOperation.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ public OperationResult execute(@NonNull final MessageFrame frame, @NonNull final
7979
private boolean isRedirectFromNativeEntity(@NonNull final MessageFrame frame) {
8080
final var updater = (ProxyWorldUpdater) frame.getWorldUpdater();
8181
final var recipient = requireNonNull(updater.getHederaAccount(frame.getRecipientAddress()));
82+
// TODO(Pectra): update the condition below. Specifically recipient.isRegularAccount() no longer holds.
83+
// Consider adding a frame variable (e.g. isFacadeExecution) or a different mechanism.
8284
return recipient.isTokenFacade() || recipient.isScheduleTxnFacade() || recipient.isRegularAccount();
8385
}
8486
}

hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/processors/CustomMessageCallProcessor.java

Lines changed: 263 additions & 160 deletions
Large diffs are not rendered by default.

hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/common/AbstractCallAttempt.java

Lines changed: 25 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy;
1313
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AddressIdConverter;
1414
import com.hedera.node.app.service.contract.impl.exec.utils.SystemContractMethod;
15-
import com.hedera.node.app.service.contract.impl.exec.utils.SystemContractMethod.SystemContract;
1615
import com.hedera.node.app.service.contract.impl.exec.utils.SystemContractMethodRegistry;
1716
import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater;
1817
import com.swirlds.config.api.Configuration;
@@ -21,6 +20,7 @@
2120
import java.nio.BufferUnderflowException;
2221
import java.util.Arrays;
2322
import java.util.Optional;
23+
import java.util.Set;
2424
import org.apache.tuweni.bytes.Bytes;
2525
import org.hyperledger.besu.datatypes.Address;
2626

@@ -35,51 +35,58 @@ public abstract class AbstractCallAttempt<T extends AbstractCallAttempt<T>> {
3535
protected final AccountID senderId;
3636
protected final Bytes input;
3737
protected final byte[] selector;
38-
// If non-null, the address of a non-contract entity (e.g., account or token) whose
39-
// "bytecode" redirects all calls to a system contract address, and was determined
40-
// to be the redirecting entity for this call attempt
41-
protected @Nullable final Address redirectAddress;
38+
protected final Optional<Address> maybeRedirectAddress;
39+
40+
private boolean matchesFnSelector(Function fn, Bytes input) {
41+
final var selector = fn.selector();
42+
return Arrays.equals(input.toArrayUnsafe(), 0, selector.length, selector, 0, selector.length);
43+
}
4244

4345
/**
4446
* @param input the input in bytes
4547
* @param options the AbstractCallAttempt parameters and options
46-
* @param redirectFunction the redirect function
4748
*/
4849
public AbstractCallAttempt(
4950
// we are keeping the 'input' out of the 'options' for not duplicate and keep close to related params
5051
@NonNull final Bytes input,
5152
@NonNull final CallAttemptOptions<T> options,
52-
@NonNull final Function redirectFunction) {
53+
Set<Address> systemContractAddresses,
54+
Function legacyRedirectFunction) {
5355
requireNonNull(input);
54-
requireNonNull(redirectFunction);
5556
this.options = requireNonNull(options);
5657
this.senderId = options.addressIdConverter().convertSender(options.senderAddress());
5758

58-
if (isRedirectSelector(redirectFunction.selector(), input.toArrayUnsafe())) {
59+
// If the recipient address of this call doesn't match the system contract address
60+
// it means we're running an EIP-7702 delegation (i.e. a facade/redirect call).
61+
final var isDelegationRedirect = !systemContractAddresses.contains(options.recipientAddress());
62+
if (isDelegationRedirect) {
63+
this.maybeRedirectAddress = Optional.of(options.recipientAddress());
64+
this.input = input;
65+
} else if (matchesFnSelector(legacyRedirectFunction, input)) {
5966
Tuple abiCall = null;
6067
try {
61-
// First try to decode the redirect with standard ABI encoding using a 32-byte address
62-
abiCall = redirectFunction.decodeCall(input.toArrayUnsafe());
68+
abiCall = legacyRedirectFunction.decodeCall(input.toArrayUnsafe());
6369
} catch (IllegalArgumentException | BufferUnderflowException | IndexOutOfBoundsException ignore) {
64-
// Otherwise use the "packed" encoding with a 20-byte address
70+
// no-op
6571
}
6672
if (abiCall != null) {
67-
this.redirectAddress = Address.fromHexString(abiCall.get(0).toString());
73+
this.maybeRedirectAddress =
74+
Optional.of(Address.fromHexString(abiCall.get(0).toString()));
6875
this.input = Bytes.wrap((byte[]) abiCall.get(1));
6976
} else {
70-
this.redirectAddress = Address.wrap(input.slice(4, 20));
77+
// TODO(Pectra): consider dropping support for proxy calls that don't confirm to ABI
78+
this.maybeRedirectAddress = Optional.of(Address.wrap(input.slice(4, 20)));
7179
this.input = input.slice(24);
7280
}
7381
} else {
74-
this.redirectAddress = null;
82+
// A regular call; neither EIP-7702 redirect nor legacy redirect function. Process as-is.
83+
this.maybeRedirectAddress = Optional.empty();
7584
this.input = input;
7685
}
7786

7887
this.selector = this.input.slice(0, 4).toArrayUnsafe();
7988
}
8089

81-
protected abstract SystemContract systemContractKind();
82-
8390
protected abstract T self();
8491

8592
/**
@@ -216,7 +223,7 @@ public boolean isStaticCall() {
216223
* @return whether the current call attempt is redirected to a system contract address
217224
*/
218225
public boolean isRedirect() {
219-
return redirectAddress != null;
226+
return this.maybeRedirectAddress.isPresent();
220227
}
221228

222229
/**
@@ -262,16 +269,6 @@ public boolean isSelectorIfConfigEnabled(
262269
return configEnabled && isSelector(methods);
263270
}
264271

265-
/**
266-
* Returns whether this call attempt is a selector for any of the given functions.
267-
* @param functionSelector bytes of the function selector
268-
* @param input input bytes
269-
* @return true if the function selector at the start of the input bytes
270-
*/
271-
private boolean isRedirectSelector(@NonNull final byte[] functionSelector, @NonNull final byte[] input) {
272-
return Arrays.equals(input, 0, functionSelector.length, functionSelector, 0, functionSelector.length);
273-
}
274-
275272
/**
276273
* Returns whether only delegate contract keys are active.
277274
*

hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/common/CallAttemptOptions.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
public record CallAttemptOptions<T extends AbstractCallAttempt<T>>(
1919
@NonNull ContractID contractID,
2020
@NonNull Address senderAddress,
21+
@NonNull Address recipientAddress,
2122
@NonNull Address authorizingAddress,
2223
boolean onlyDelegatableContractKeysActive,
2324
@NonNull HederaWorldUpdater.Enhancement enhancement,
@@ -32,6 +33,7 @@ public record CallAttemptOptions<T extends AbstractCallAttempt<T>>(
3233
/**
3334
* @param contractID the target system contract ID
3435
* @param senderAddress the address of the sender of this call
36+
* @param recipientAddress the recipient address of this call
3537
* @param authorizingAddress the contract whose keys are to be activated
3638
* @param onlyDelegatableContractKeysActive whether the strategy should require a delegatable contract id key
3739
* @param enhancement the enhancement to get the native operations to look up the contract's number
@@ -46,6 +48,7 @@ public record CallAttemptOptions<T extends AbstractCallAttempt<T>>(
4648
public CallAttemptOptions(
4749
@NonNull final ContractID contractID,
4850
@NonNull final Address senderAddress,
51+
@NonNull final Address recipientAddress,
4952
@NonNull final Address authorizingAddress,
5053
final boolean onlyDelegatableContractKeysActive,
5154
@NonNull final Enhancement enhancement,
@@ -58,6 +61,7 @@ public CallAttemptOptions(
5861
final boolean isStaticCall) {
5962
this.contractID = requireNonNull(contractID);
6063
this.senderAddress = requireNonNull(senderAddress);
64+
this.recipientAddress = requireNonNull(recipientAddress);
6165
this.authorizingAddress = requireNonNull(authorizingAddress);
6266
this.onlyDelegatableContractKeysActive = onlyDelegatableContractKeysActive;
6367
this.enhancement = requireNonNull(enhancement);

hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/has/HasCallAttempt.java

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// SPDX-License-Identifier: Apache-2.0
22
package com.hedera.node.app.service.contract.impl.exec.systemcontracts.has;
33

4+
import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.HasSystemContract.HAS_EVM_ADDRESS;
45
import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.isLongZero;
56
import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.numberOfLongZero;
67
import static java.util.Objects.requireNonNull;
@@ -13,12 +14,11 @@
1314
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallAttempt;
1415
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call;
1516
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.CallAttemptOptions;
16-
import com.hedera.node.app.service.contract.impl.exec.utils.SystemContractMethod;
17-
import com.hedera.node.app.service.contract.impl.exec.utils.SystemContractMethod.SystemContract;
1817
import com.hedera.node.app.spi.signatures.SignatureVerifier;
1918
import com.hedera.node.config.data.HederaConfig;
2019
import edu.umd.cs.findbugs.annotations.NonNull;
2120
import edu.umd.cs.findbugs.annotations.Nullable;
21+
import java.util.Set;
2222
import org.apache.tuweni.bytes.Bytes;
2323
import org.hyperledger.besu.datatypes.Address;
2424

@@ -28,8 +28,9 @@
2828
* everything it will need to execute.
2929
*/
3030
public class HasCallAttempt extends AbstractCallAttempt<HasCallAttempt> {
31-
/** Selector for redirectForAccount(address,bytes) method. */
32-
public static final Function REDIRECT_FOR_ACCOUNT = new Function("redirectForAccount(address,bytes)");
31+
private static final Set<Address> HAS_ADDRESSES = Set.of(Address.fromHexString(HAS_EVM_ADDRESS));
32+
33+
public static final Function LEGACY_REDIRECT_FOR_ACCOUNT = new Function("redirectForAccount(address,bytes)");
3334

3435
@Nullable
3536
private final Account redirectAccount;
@@ -41,18 +42,12 @@ public HasCallAttempt(
4142
@NonNull final Bytes input,
4243
@NonNull final CallAttemptOptions<HasCallAttempt> options,
4344
@NonNull final SignatureVerifier signatureVerifier) {
44-
super(input, options, REDIRECT_FOR_ACCOUNT);
45-
if (isRedirect()) {
46-
this.redirectAccount = linkedAccount(requireNonNull(redirectAddress));
47-
} else {
48-
this.redirectAccount = null;
49-
}
50-
this.signatureVerifier = requireNonNull(signatureVerifier);
51-
}
45+
super(input, options, HAS_ADDRESSES, LEGACY_REDIRECT_FOR_ACCOUNT);
5246

53-
@Override
54-
protected SystemContract systemContractKind() {
55-
return SystemContractMethod.SystemContract.HAS;
47+
this.redirectAccount =
48+
this.maybeRedirectAddress.map(this::linkedAccount).orElse(null);
49+
50+
this.signatureVerifier = requireNonNull(signatureVerifier);
5651
}
5752

5853
@Override

hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/has/HasCallFactory.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// SPDX-License-Identifier: Apache-2.0
22
package com.hedera.node.app.service.contract.impl.exec.systemcontracts.has;
33

4+
import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.HasSystemContract.HAS_EVM_ADDRESS;
45
import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.configOf;
56
import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.proxyUpdaterFor;
67
import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.systemContractGasCalculatorOf;
@@ -18,10 +19,12 @@
1819
import com.hedera.node.app.spi.signatures.SignatureVerifier;
1920
import edu.umd.cs.findbugs.annotations.NonNull;
2021
import java.util.List;
22+
import java.util.Optional;
2123
import javax.inject.Inject;
2224
import javax.inject.Named;
2325
import javax.inject.Singleton;
2426
import org.apache.tuweni.bytes.Bytes;
27+
import org.hyperledger.besu.datatypes.Address;
2528
import org.hyperledger.besu.evm.frame.MessageFrame;
2629

2730
/**
@@ -69,11 +72,19 @@ public HasCallFactory(
6972
requireNonNull(input);
7073
requireNonNull(frame);
7174
final var enhancement = proxyUpdaterFor(frame).enhancement();
75+
76+
// If we're executing a HAS call, but the recipient isn't the HAS address
77+
// then it must be a redirect/delegate call.
78+
final var isRedirect = !frame.getRecipientAddress().equals(Address.fromHexString(HAS_EVM_ADDRESS));
79+
final Optional<Address> maybeRedirectAddress =
80+
isRedirect ? Optional.of(frame.getRecipientAddress()) : Optional.empty();
81+
7282
return new HasCallAttempt(
7383
input,
7484
new CallAttemptOptions<>(
7585
contractID,
7686
frame.getSenderAddress(),
87+
frame.getRecipientAddress(),
7788
frame.getSenderAddress(),
7889
addressChecks.hasParentDelegateCall(frame),
7990
enhancement,

hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hss/HssCallAttempt.java

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// SPDX-License-Identifier: Apache-2.0
22
package com.hedera.node.app.service.contract.impl.exec.systemcontracts.hss;
33

4+
import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.HssSystemContract.HSS_EVM_ADDRESS;
45
import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.isLongZero;
56
import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.maybeMissingNumberOf;
67
import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.numberOfLongZero;
@@ -15,8 +16,6 @@
1516
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallAttempt;
1617
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call;
1718
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.CallAttemptOptions;
18-
import com.hedera.node.app.service.contract.impl.exec.utils.SystemContractMethod;
19-
import com.hedera.node.app.service.contract.impl.exec.utils.SystemContractMethod.SystemContract;
2019
import com.hedera.node.app.spi.signatures.SignatureVerifier;
2120
import edu.umd.cs.findbugs.annotations.NonNull;
2221
import edu.umd.cs.findbugs.annotations.Nullable;
@@ -30,8 +29,10 @@
3029
* everything it will need to execute.
3130
*/
3231
public class HssCallAttempt extends AbstractCallAttempt<HssCallAttempt> {
33-
/** Selector for redirectForScheduleTxn(address,bytes) method. */
34-
public static final Function REDIRECT_FOR_SCHEDULE_TXN = new Function("redirectForScheduleTxn(address,bytes)");
32+
private static final Set<Address> HSS_ADDRESSES = Set.of(Address.fromHexString(HSS_EVM_ADDRESS));
33+
34+
public static final Function LEGACY_REDIRECT_FOR_SCHEDULE_TXN =
35+
new Function("redirectForScheduleTxn(address,bytes)");
3536

3637
@Nullable
3738
private final Schedule redirectScheduleTxn;
@@ -43,18 +44,12 @@ public HssCallAttempt(
4344
@NonNull final Bytes input,
4445
@NonNull final CallAttemptOptions<HssCallAttempt> options,
4546
@NonNull final SignatureVerifier signatureVerifier) {
46-
super(input, options, REDIRECT_FOR_SCHEDULE_TXN);
47-
if (isRedirect()) {
48-
this.redirectScheduleTxn = linkedSchedule(requireNonNull(redirectAddress));
49-
} else {
50-
this.redirectScheduleTxn = null;
51-
}
52-
this.signatureVerifier = signatureVerifier;
53-
}
47+
super(input, options, HSS_ADDRESSES, LEGACY_REDIRECT_FOR_SCHEDULE_TXN);
5448

55-
@Override
56-
protected SystemContract systemContractKind() {
57-
return SystemContractMethod.SystemContract.HSS;
49+
this.redirectScheduleTxn =
50+
this.maybeRedirectAddress.map(this::linkedSchedule).orElse(null);
51+
52+
this.signatureVerifier = signatureVerifier;
5853
}
5954

6055
@Override

0 commit comments

Comments
 (0)