Skip to content

Commit 941fad9

Browse files
committed
Set code hash for delegated account
1 parent 55b1891 commit 941fad9

File tree

4 files changed

+97
-8
lines changed

4 files changed

+97
-8
lines changed

system-contracts/bootloader/bootloader.yul

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,50 @@ object "Bootloader" {
694694
ret := mload(0)
695695
}
696696

697+
698+
699+
/// @notice Overrides the "raw" code hash of the address. "Raw" means that it must use exactly the value
700+
/// that is stored in the AccountCodeStorage system contract for that address, without applying any
701+
/// additional transformations.
702+
/// This method is very unsafe and it shouldn't be used to do long-term modifications.
703+
/// Right now it's only used to override the bytecode hash of delegated accounts to perform
704+
/// transaction validation & payment.
705+
/// @param addr The address of the account to set the code hash of.
706+
/// @param codeHash The code hash to be set.
707+
/// @param assertSuccess Whether to revert the bootloader if the call to the AccountCodeStorage fails. If `false`, only
708+
/// `nearCallPanic` will be issued in case of failure, which is helpful for cases, when the reason for failure is user providing not
709+
/// enough gas.
710+
function setRawCodeHash(addr, codeHash, assertSuccess) -> ret {
711+
mstore(0, {{RIGHT_PADDED_SET_RAW_CODE_HASH_SELECTOR}})
712+
mstore(4, addr)
713+
mstore(36, codeHash)
714+
let success := staticcall(
715+
gas(),
716+
ACCOUNT_CODE_STORAGE_ADDR(),
717+
0,
718+
36,
719+
0,
720+
32
721+
)
722+
723+
// In case the call to the account code storage fails,
724+
// it most likely means that the caller did not provide enough gas for
725+
// the call.
726+
// In case the caller is certain that the amount of gas provided is enough, i.e.
727+
// (`assertSuccess` = true), then we should panic.
728+
if iszero(success) {
729+
if assertSuccess {
730+
// The call must've succeeded, but it didn't. So we revert the bootloader.
731+
assertionError("getRawCodeHash failed")
732+
}
733+
734+
// Most likely not enough gas provided, revert the current frame.
735+
nearCallPanic()
736+
}
737+
738+
ret := mload(0)
739+
}
740+
697741
/// @notice Returns the address of EIP-7702 delegation for the account (or zero, if account
698742
/// is not delegated).
699743
/// @param addr The address of the account to check.
@@ -2239,28 +2283,48 @@ object "Bootloader" {
22392283
}
22402284
}
22412285

2242-
/// @dev Checks whether an address is an EOA (i.e. has not code deployed on it)
2286+
/// @dev Checks whether an address is an EOA (i.e. has not code deployed on it or it's a 7702-delegated account)
22432287
/// @param addr The address to check
22442288
function isEOA(addr) -> ret {
22452289
ret := 0
2290+
let delegation := getDelegationAddress(addr)
22462291

2292+
// TODO: This logic is duplicated in several places, we should create a dedicated method.
22472293
if gt(addr, MAX_SYSTEM_CONTRACT_ADDR()) {
2248-
ret := iszero(getRawCodeHash(addr, false))
2294+
ret := or(
2295+
iszero(getRawCodeHash(addr, false)),
2296+
gt(delegation, 0)
2297+
)
22492298
}
22502299
}
22512300

22522301
/// @dev Calls the `payForTransaction` method of an account
22532302
function accountPayForTx(account, txDataOffset) -> success {
2303+
let delegation := getDelegationAddress(account)
2304+
let rawCodeHash := 0
2305+
if gt(delegation, 0) {
2306+
rawCodeHash := getRawCodeHash(delegation, true)
2307+
setRawCodeHash(account, 0, true)
2308+
}
22542309
success := callAccountMethod({{PAY_FOR_TX_SELECTOR}}, account, txDataOffset)
2310+
if gt(delegation, 0) {
2311+
setRawCodeHash(account, rawCodeHash, true)
2312+
}
22552313
}
22562314

22572315
/// @dev Calls the `prepareForPaymaster` method of an account
22582316
function accountPrePaymaster(account, txDataOffset) -> success {
2317+
// TODO: should we allow delegated accounts to use native paymasters?
2318+
// TODO: Gut feeling is that the answer is "NO" as we're deprecating EIP-712 txs
2319+
// TOOD: and native accounts have their own entrypoint.
22592320
success := callAccountMethod({{PRE_PAYMASTER_SELECTOR}}, account, txDataOffset)
22602321
}
22612322

22622323
/// @dev Calls the `validateAndPayForPaymasterTransaction` method of a paymaster
22632324
function validateAndPayForPaymasterTransaction(paymaster, txDataOffset) -> success {
2325+
// TODO: should we allow delegated accounts to use native paymasters?
2326+
// TODO: Gut feeling is that the answer is "NO" as we're deprecating EIP-712 txs
2327+
// TOOD: and native accounts have their own entrypoint.
22642328
success := callAccountMethod({{VALIDATE_AND_PAY_PAYMASTER}}, paymaster, txDataOffset)
22652329
}
22662330

@@ -2501,7 +2565,21 @@ object "Bootloader" {
25012565
setHook(VM_HOOK_ACCOUNT_VALIDATION_ENTERED())
25022566
debugLog("pre-validate",0)
25032567
debugLog("pre-validate",from)
2568+
2569+
// Override bytecode hash for validation if required.
2570+
// TODO: It should be safe, since delegation is only allowed for EOAs in the first place.
2571+
let delegation := getDelegationAddress(from)
2572+
let rawCodeHash := 0
2573+
if gt(delegation, 0) {
2574+
rawCodeHash := getRawCodeHash(delegation, true)
2575+
setRawCodeHash(from, 0, true)
2576+
}
2577+
25042578
let success := callAccountMethod({{VALIDATE_TX_SELECTOR}}, from, txDataOffset)
2579+
2580+
if gt(delegation, 0) {
2581+
setRawCodeHash(from, rawCodeHash, true)
2582+
}
25052583
setHook(VM_HOOK_NO_VALIDATION_ENTERED())
25062584

25072585
if iszero(success) {
@@ -2645,7 +2723,7 @@ object "Bootloader" {
26452723
let innerTxDataOffset := add(txDataOffset, 32)
26462724
let calldataPtr := getDataPtr(innerTxDataOffset)
26472725
let value := getValue(innerTxDataOffset)
2648-
ret := msgValueSimulatorMimicCall(delegation, from, value, calldataPtr)
2726+
ret := msgValueSimulatorMimicCall(from, from, value, calldataPtr)
26492727
}
26502728

26512729
if iszero(ret) {

system-contracts/contracts/AccountCodeStorage.sol

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,12 @@ contract AccountCodeStorage is IAccountCodeStorage, SystemContractBase {
173173
return delegatedEOAs[_addr];
174174
}
175175

176+
/// @notice Allows the bootloader to override bytecode hash of account.
177+
/// TODO: can we avoid it and do it in bootloader? Having it as a public interface feels very unsafe.
178+
function setRawCodeHash(address addr, bytes32 rawBytecodeHash) external onlyCallFromBootloader {
179+
_storeCodeHash(addr, rawBytecodeHash);
180+
}
181+
176182
function processDelegations(AuthorizationListItem[] calldata authorizationList) external onlyCallFromBootloader {
177183
for (uint256 i = 0; i < authorizationList.length; i++) {
178184
// Per EIP7702 rules, if any check for the tuple item fails,
@@ -217,7 +223,7 @@ contract AccountCodeStorage is IAccountCodeStorage, SystemContractBase {
217223
address authority = ecrecover(message, uint8(item.yParity + 27), bytes32(item.r), bytes32(item.s));
218224

219225
// ZKsync has native account abstraction, so we only allow delegation for EOAs.
220-
if (this.getRawCodeHash(authority) != 0x00) {
226+
if (this.getRawCodeHash(authority) != 0x00 && this.getAccountDelegation(authority) == address(0)) {
221227
continue;
222228
}
223229

@@ -234,11 +240,15 @@ contract AccountCodeStorage is IAccountCodeStorage, SystemContractBase {
234240
if (item.addr == address(0)) {
235241
// If the delegation address is 0, we need to remove the delegation.
236242
delete delegatedEOAs[authority];
243+
_storeCodeHash(authority, 0x00);
237244
emit AccountDelegationRemoved(authority);
238245
} else {
239246
// Otherwise, store the delegation.
240247
// TODO: Do we need any security checks here, e.g. non-default code hash or non-system contract?
241248
delegatedEOAs[authority] = item.addr;
249+
250+
bytes32 codeHash = getRawCodeHash(item.addr);
251+
_storeCodeHash(authority, codeHash); // TODO: Do we need additional checks here?
242252
emit AccountDelegated(authority, item.addr);
243253
}
244254
}

system-contracts/contracts/ContractDeployer.sol

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,10 @@ contract ContractDeployer is IContractDeployer, SystemContractBase {
5656
}
5757

5858
// It is an EOA, it is still an account.
59-
if (
60-
_address > address(MAX_SYSTEM_CONTRACT_ADDRESS) &&
61-
ACCOUNT_CODE_STORAGE_SYSTEM_CONTRACT.getRawCodeHash(_address) == 0
62-
) {
59+
bool notSystem = _address > address(MAX_SYSTEM_CONTRACT_ADDRESS);
60+
bool noCodeHash = ACCOUNT_CODE_STORAGE_SYSTEM_CONTRACT.getRawCodeHash(_address) == 0;
61+
bool delegated = ACCOUNT_CODE_STORAGE_SYSTEM_CONTRACT.getAccountDelegation(_address) != address(0);
62+
if (notSystem && (noCodeHash || delegated)) {
6363
return AccountAbstractionVersion.Version1;
6464
}
6565

system-contracts/scripts/preprocess-bootloader.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ const params = {
6969
RIGHT_PADDED_GET_ACCOUNT_VERSION_SELECTOR: getPaddedSelector("ContractDeployer", "extendedAccountVersion"),
7070
RIGHT_PADDED_GET_RAW_CODE_HASH_SELECTOR: getPaddedSelector("AccountCodeStorage", "getRawCodeHash"),
7171
RIGHT_PADDED_GET_ACCOUNT_DELEGATION_SELECTOR: getPaddedSelector("AccountCodeStorage", "getAccountDelegation"),
72+
RIGHT_PADDED_SET_RAW_CODE_HASH_SELECTOR: getPaddedSelector("AccountCodeStorage", "setRawCodeHash"),
7273
PROCESS_DELEGATIONS_SELECTOR: getSelector("AccountCodeStorage", "processDelegations"),
7374
PAY_FOR_TX_SELECTOR: getSelector("DefaultAccount", "payForTransaction"),
7475
PRE_PAYMASTER_SELECTOR: getSelector("DefaultAccount", "prepareForPaymaster"),

0 commit comments

Comments
 (0)