Skip to content

Commit 4b98cae

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

File tree

24 files changed

+104
-160
lines changed

24 files changed

+104
-160
lines changed

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 & 1 deletion
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

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

Lines changed: 23 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,36 @@ 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) {
61-
// no-op
70+
// ignore
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();
77+
this.maybeRedirectAddress = Optional.empty();
6878
this.input = input;
6979
}
7080
} else {
71-
this.legacyRedirectAddress = Optional.empty();
81+
// A regular call; neither EIP-7702 redirect nor legacy redirect function. Process as-is.
82+
this.maybeRedirectAddress = Optional.empty();
7283
this.input = input;
7384
}
7485

@@ -211,7 +222,7 @@ public boolean isStaticCall() {
211222
* @return whether the current call attempt is redirected to a system contract address
212223
*/
213224
public boolean isRedirect() {
214-
return this.legacyRedirectAddress.isPresent() || options.maybeRedirectAddress().isPresent();
225+
return this.maybeRedirectAddress.isPresent();
215226
}
216227

217228
/**

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
}

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

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
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;
54
import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.configOf;
65
import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.proxyUpdaterFor;
76
import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.systemContractGasCalculatorOf;
@@ -19,12 +18,10 @@
1918
import com.hedera.node.app.spi.signatures.SignatureVerifier;
2019
import edu.umd.cs.findbugs.annotations.NonNull;
2120
import java.util.List;
22-
import java.util.Optional;
2321
import javax.inject.Inject;
2422
import javax.inject.Named;
2523
import javax.inject.Singleton;
2624
import org.apache.tuweni.bytes.Bytes;
27-
import org.hyperledger.besu.datatypes.Address;
2825
import org.hyperledger.besu.evm.frame.MessageFrame;
2926

3027
/**
@@ -72,19 +69,12 @@ public HssCallFactory(
7269
requireNonNull(input);
7370
requireNonNull(frame);
7471
final var enhancement = proxyUpdaterFor(frame).enhancement();
75-
76-
// If we're executing a HSS call, but the recipient isn't the HSS address
77-
// then it must be a redirect/delegate call.
78-
final var isRedirect = !frame.getRecipientAddress().equals(Address.fromHexString(HSS_EVM_ADDRESS));
79-
final Optional<Address> maybeRedirectAddress =
80-
isRedirect ? Optional.of(frame.getRecipientAddress()) : Optional.empty();
81-
8272
return new HssCallAttempt(
8373
input,
8474
new CallAttemptOptions<>(
8575
contractID,
8676
frame.getSenderAddress(),
87-
maybeRedirectAddress,
77+
frame.getRecipientAddress(),
8878
frame.getSenderAddress(),
8979
addressChecks.hasParentDelegateCall(frame),
9080
enhancement,

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

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

4+
import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.HtsSystemContract.HTS_167_EVM_ADDRESS;
5+
import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.HtsSystemContract.HTS_16C_EVM_ADDRESS;
46
import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.isLongZeroAddress;
57
import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.numberOfLongZero;
68
import static java.util.Objects.requireNonNull;
79

10+
import com.esaulpaugh.headlong.abi.Function;
811
import com.hedera.hapi.node.base.AccountID;
912
import com.hedera.hapi.node.base.TokenID;
1013
import com.hedera.hapi.node.base.TokenType;
@@ -15,17 +18,19 @@
1518
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.CallAttemptOptions;
1619
import edu.umd.cs.findbugs.annotations.NonNull;
1720
import edu.umd.cs.findbugs.annotations.Nullable;
21+
import java.util.Set;
1822
import org.apache.tuweni.bytes.Bytes;
1923
import org.hyperledger.besu.datatypes.Address;
20-
import com.esaulpaugh.headlong.abi.Function;
2124

2225
/**
2326
* Manages the call attempted by a {@link Bytes} payload received by the {@link HtsSystemContract}. Translates a valid
2427
* attempt into an appropriate {@link Call} subclass, giving the {@link Call} everything it will need to execute.
2528
*/
2629
public class HtsCallAttempt extends AbstractCallAttempt<HtsCallAttempt> {
27-
public static final Function LEGACY_REDIRECT_FOR_TOKEN =
28-
new Function("redirectForToken(address,bytes)");
30+
private static final Set<Address> HTS_ADDRESSES =
31+
Set.of(Address.fromHexString(HTS_167_EVM_ADDRESS), Address.fromHexString(HTS_16C_EVM_ADDRESS));
32+
33+
public static final Function LEGACY_REDIRECT_FOR_TOKEN = new Function("redirectForToken(address,bytes)");
2934

3035
// The id address of the account authorizing the call, in the sense
3136
// that (1) a dispatch should omit the key of this account from the
@@ -40,13 +45,9 @@ public class HtsCallAttempt extends AbstractCallAttempt<HtsCallAttempt> {
4045
private final Token redirectToken;
4146

4247
public HtsCallAttempt(@NonNull final Bytes input, @NonNull final CallAttemptOptions<HtsCallAttempt> options) {
43-
super(input, options, LEGACY_REDIRECT_FOR_TOKEN);
48+
super(input, options, HTS_ADDRESSES, LEGACY_REDIRECT_FOR_TOKEN);
4449

45-
this.redirectToken =
46-
this.legacyRedirectAddress
47-
.or(options::maybeRedirectAddress)
48-
.map(this::linkedToken)
49-
.orElse(null);
50+
this.redirectToken = this.maybeRedirectAddress.map(this::linkedToken).orElse(null);
5051

5152
this.authorizingId = (options.authorizingAddress() != senderAddress())
5253
? addressIdConverter().convertSender(options.authorizingAddress())

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

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

4-
import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.HtsSystemContract.HTS_167_EVM_ADDRESS;
5-
import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.HtsSystemContract.HTS_16C_EVM_ADDRESS;
64
import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.CallType.QUALIFIED_DELEGATE;
75
import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.configOf;
86
import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.proxyUpdaterFor;
@@ -19,12 +17,10 @@
1917
import com.hedera.node.app.service.contract.impl.exec.utils.SystemContractMethodRegistry;
2018
import edu.umd.cs.findbugs.annotations.NonNull;
2119
import java.util.List;
22-
import java.util.Optional;
2320
import javax.inject.Inject;
2421
import javax.inject.Named;
2522
import javax.inject.Singleton;
2623
import org.apache.tuweni.bytes.Bytes;
27-
import org.hyperledger.besu.datatypes.Address;
2824
import org.hyperledger.besu.evm.frame.MessageFrame;
2925

3026
/**
@@ -70,20 +66,12 @@ public HtsCallFactory(
7066
requireNonNull(input);
7167
requireNonNull(frame);
7268
final var enhancement = proxyUpdaterFor(frame).enhancement();
73-
74-
// If we're executing a HTS call, but the recipient isn't the HTS address
75-
// then it must be a redirect/delegate call.
76-
final var isRedirect = !frame.getRecipientAddress().equals(Address.fromHexString(HTS_167_EVM_ADDRESS))
77-
&& !frame.getRecipientAddress().equals(Address.fromHexString(HTS_16C_EVM_ADDRESS));
78-
final Optional<Address> maybeRedirectAddress =
79-
isRedirect ? Optional.of(frame.getRecipientAddress()) : Optional.empty();
80-
8169
return new HtsCallAttempt(
8270
input,
8371
new CallAttemptOptions<>(
8472
contractID,
8573
frame.getSenderAddress(),
86-
maybeRedirectAddress,
74+
frame.getRecipientAddress(),
8775
// We only need to distinguish between the EVM sender id and the
8876
// "authorizing id" for qualified delegate calls; and even then, only
8977
// for classic transfers. In that specific case, the qualified delegate

0 commit comments

Comments
 (0)