Skip to content

Commit efde495

Browse files
Handle EIP-7702 code delgation
Signed-off-by: Lukasz Gasior <[email protected]>
1 parent 8fa1b77 commit efde495

File tree

46 files changed

+187
-383
lines changed

Some content is hidden

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

46 files changed

+187
-383
lines changed

hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ContractsConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ public record ContractsConfig(
101101
@ConfigProperty(value = "evm.nativeLibVerification.halt.enabled", defaultValue = "false") @NetworkProperty
102102
boolean nativeLibVerificationHaltEnabled,
103103
@ConfigProperty(value = "evm.pectra.enabled", defaultValue = "true") @NetworkProperty boolean evmPectraEnabled,
104-
@ConfigProperty(value = "metrics.smartContract.primary.enabled", defaultValue = "false") @NetworkProperty
104+
@ConfigProperty(value = "metrics.smartContract.primary.enabled", defaultValue = "true") @NetworkProperty
105105
boolean metricsSmartContractPrimaryEnabled,
106106
@ConfigProperty(value = "metrics.smartContract.secondary.enabled", defaultValue = "true") @NetworkProperty
107107
boolean metricsSmartContractSecondaryEnabled) {}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ 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-
// TODO: bring back .add("-Xlint:-exports")
8+
// TODO(Pectra): restore to `options.compilerArgs.add("-Xlint:-exports")`
99
tasks.withType<JavaCompile>().configureEach { options.compilerArgs.clear() }
1010

1111
mainModuleInfo { annotationProcessor("dagger.compiler") }

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: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ && isPayloadEligibleForAccountServiceRedirect(frame.getInputData())) {
157157
// disable precompile if so configured.
158158
evmPrecompile = null;
159159
}
160-
// TODO(Pectra): revisit this?
160+
// TODO(Pectra): double check all corner cases for the below condition
161161
// Check to see if the code address is a system account and possibly halt
162162
// Note that we allow calls to the allowance hook address(0x16d) if the call is part of
163163
// a hook dispatch; in that case, the allowance hook is being treated as a normal
@@ -398,9 +398,6 @@ private void doHalt(
398398
}
399399

400400
private static boolean isPayloadEligibleForAccountServiceRedirect(Bytes payload) {
401-
// The compiled binary uses an ABI-compatible function dispatch, so we expect
402-
// the first 4 bytes of the payload to be the selector of the function.
403-
// We check that against a fixed list of selectors.
404401
final int prefix = payload.size() >= FUNCTION_SELECTOR_LENGTH ? payload.getInt(0) : 0;
405402
return ACCOUNT_PROXY_ELIGIBLE_CALL_DATA_PREFIXES.contains(prefix);
406403
}

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

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@
1717
import com.swirlds.config.api.Configuration;
1818
import edu.umd.cs.findbugs.annotations.NonNull;
1919
import edu.umd.cs.findbugs.annotations.Nullable;
20-
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,8 +35,12 @@ public abstract class AbstractCallAttempt<T extends AbstractCallAttempt<T>> {
3535
protected final AccountID senderId;
3636
protected final Bytes input;
3737
protected final byte[] selector;
38+
protected final Optional<Address> maybeRedirectAddress;
3839

39-
protected final Optional<Address> legacyRedirectAddress;
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+
}
4044

4145
/**
4246
* @param input the input in bytes
@@ -46,29 +50,37 @@ public AbstractCallAttempt(
4650
// we are keeping the 'input' out of the 'options' for not duplicate and keep close to related params
4751
@NonNull final Bytes input,
4852
@NonNull final CallAttemptOptions<T> options,
49-
Function redirectFunction) {
53+
Set<Address> systemContractAddresses,
54+
Function legacyRedirectFunction) {
5055
requireNonNull(input);
5156
this.options = requireNonNull(options);
5257
this.senderId = options.addressIdConverter().convertSender(options.senderAddress());
5358

54-
final var redirectFnSelector = redirectFunction.selector();
55-
final var matchesRedirectSelector = Arrays.equals(input.toArrayUnsafe(), 0, redirectFnSelector.length, redirectFnSelector, 0, redirectFnSelector.length);
56-
if (matchesRedirectSelector) {
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)) {
5766
Tuple abiCall = null;
5867
try {
59-
abiCall = redirectFunction.decodeCall(input.toArrayUnsafe());
68+
abiCall = legacyRedirectFunction.decodeCall(input.toArrayUnsafe());
6069
} catch (IllegalArgumentException | BufferUnderflowException | IndexOutOfBoundsException ignore) {
6170
// no-op
6271
}
6372
if (abiCall != null) {
64-
this.legacyRedirectAddress = Optional.of(Address.fromHexString(abiCall.get(0).toString()));
73+
this.maybeRedirectAddress =
74+
Optional.of(Address.fromHexString(abiCall.get(0).toString()));
6575
this.input = Bytes.wrap((byte[]) abiCall.get(1));
6676
} else {
67-
this.legacyRedirectAddress = Optional.empty();
68-
this.input = input;
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)));
79+
this.input = input.slice(24);
6980
}
7081
} else {
71-
this.legacyRedirectAddress = Optional.empty();
82+
// A regular call; neither EIP-7702 redirect nor legacy redirect function. Process as-is.
83+
this.maybeRedirectAddress = Optional.empty();
7284
this.input = input;
7385
}
7486

@@ -211,7 +223,7 @@ public boolean isStaticCall() {
211223
* @return whether the current call attempt is redirected to a system contract address
212224
*/
213225
public boolean isRedirect() {
214-
return this.legacyRedirectAddress.isPresent() || options.maybeRedirectAddress().isPresent();
226+
return this.maybeRedirectAddress.isPresent();
215227
}
216228

217229
/**

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 & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,12 @@
1313
import com.swirlds.config.api.Configuration;
1414
import edu.umd.cs.findbugs.annotations.NonNull;
1515
import java.util.List;
16-
import java.util.Optional;
1716
import org.hyperledger.besu.datatypes.Address;
1817

1918
public record CallAttemptOptions<T extends AbstractCallAttempt<T>>(
2019
@NonNull ContractID contractID,
2120
@NonNull Address senderAddress,
22-
@NonNull Optional<Address> maybeRedirectAddress,
21+
@NonNull Address recipientAddress,
2322
@NonNull Address authorizingAddress,
2423
boolean onlyDelegatableContractKeysActive,
2524
@NonNull HederaWorldUpdater.Enhancement enhancement,
@@ -34,6 +33,7 @@ public record CallAttemptOptions<T extends AbstractCallAttempt<T>>(
3433
/**
3534
* @param contractID the target system contract ID
3635
* @param senderAddress the address of the sender of this call
36+
* @param recipientAddress the recipient address of this call
3737
* @param authorizingAddress the contract whose keys are to be activated
3838
* @param onlyDelegatableContractKeysActive whether the strategy should require a delegatable contract id key
3939
* @param enhancement the enhancement to get the native operations to look up the contract's number
@@ -48,7 +48,7 @@ public record CallAttemptOptions<T extends AbstractCallAttempt<T>>(
4848
public CallAttemptOptions(
4949
@NonNull final ContractID contractID,
5050
@NonNull final Address senderAddress,
51-
@NonNull final Optional<Address> maybeRedirectAddress,
51+
@NonNull final Address recipientAddress,
5252
@NonNull final Address authorizingAddress,
5353
final boolean onlyDelegatableContractKeysActive,
5454
@NonNull final Enhancement enhancement,
@@ -61,7 +61,7 @@ public CallAttemptOptions(
6161
final boolean isStaticCall) {
6262
this.contractID = requireNonNull(contractID);
6363
this.senderAddress = requireNonNull(senderAddress);
64-
this.maybeRedirectAddress = requireNonNull(maybeRedirectAddress);
64+
this.recipientAddress = requireNonNull(recipientAddress);
6565
this.authorizingAddress = requireNonNull(authorizingAddress);
6666
this.onlyDelegatableContractKeysActive = onlyDelegatableContractKeysActive;
6767
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: 7 additions & 7 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;
@@ -17,6 +18,7 @@
1718
import com.hedera.node.config.data.HederaConfig;
1819
import edu.umd.cs.findbugs.annotations.NonNull;
1920
import edu.umd.cs.findbugs.annotations.Nullable;
21+
import java.util.Set;
2022
import org.apache.tuweni.bytes.Bytes;
2123
import org.hyperledger.besu.datatypes.Address;
2224

@@ -26,8 +28,9 @@
2628
* everything it will need to execute.
2729
*/
2830
public class HasCallAttempt extends AbstractCallAttempt<HasCallAttempt> {
29-
public static final Function LEGACY_REDIRECT_FOR_ACCOUNT =
30-
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)");
3134

3235
@Nullable
3336
private final Account redirectAccount;
@@ -39,13 +42,10 @@ public HasCallAttempt(
3942
@NonNull final Bytes input,
4043
@NonNull final CallAttemptOptions<HasCallAttempt> options,
4144
@NonNull final SignatureVerifier signatureVerifier) {
42-
super(input, options, LEGACY_REDIRECT_FOR_ACCOUNT);
45+
super(input, options, HAS_ADDRESSES, LEGACY_REDIRECT_FOR_ACCOUNT);
4346

4447
this.redirectAccount =
45-
this.legacyRedirectAddress
46-
.or(options::maybeRedirectAddress)
47-
.map(this::linkedAccount)
48-
.orElse(null);
48+
this.maybeRedirectAddress.map(this::linkedAccount).orElse(null);
4949

5050
this.signatureVerifier = requireNonNull(signatureVerifier);
5151
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public HasCallFactory(
8484
new CallAttemptOptions<>(
8585
contractID,
8686
frame.getSenderAddress(),
87-
maybeRedirectAddress,
87+
frame.getRecipientAddress(),
8888
frame.getSenderAddress(),
8989
addressChecks.hasParentDelegateCall(frame),
9090
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: 7 additions & 7 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;
@@ -28,8 +29,10 @@
2829
* everything it will need to execute.
2930
*/
3031
public class HssCallAttempt extends AbstractCallAttempt<HssCallAttempt> {
31-
public static final Function LEGACY_REDIRECT_FOR_SCHEDULE =
32-
new Function("redirectForSchedule(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)");
3336

3437
@Nullable
3538
private final Schedule redirectScheduleTxn;
@@ -41,13 +44,10 @@ public HssCallAttempt(
4144
@NonNull final Bytes input,
4245
@NonNull final CallAttemptOptions<HssCallAttempt> options,
4346
@NonNull final SignatureVerifier signatureVerifier) {
44-
super(input, options, LEGACY_REDIRECT_FOR_SCHEDULE);
47+
super(input, options, HSS_ADDRESSES, LEGACY_REDIRECT_FOR_SCHEDULE_TXN);
4548

4649
this.redirectScheduleTxn =
47-
this.legacyRedirectAddress
48-
.or(options::maybeRedirectAddress)
49-
.map(this::linkedSchedule)
50-
.orElse(null);
50+
this.maybeRedirectAddress.map(this::linkedSchedule).orElse(null);
5151

5252
this.signatureVerifier = signatureVerifier;
5353
}

0 commit comments

Comments
 (0)