The structure of MondrianWallet wallet is not compliant with zkSync chain, zkSync use native AA and differ in some points compared to EIP4337.
Let's focus on transaction processing, EIP 4337 introduces a separate transaction flow for smart contract accounts, which relies on a separate mempool for user operations, and Bundlers - nodes that bundle user operations and sends them to be processed by the EntryPoint contract, resulting in two separate transaction flows. In contrast, on zkSync Era there is a unified mempool for transactions from both Externally Owned Accounts (EOAs) and smart contract accounts. On zkSync Era, the Operator takes on the role of bundling transactions, irrespective of the account type, and sends them to the Bootloader (similar to the EntryPoint contract), which results in a single mempool and transaction flow.
Full description ==> https://docs.zksync.io/build/developer-reference/differences-with-ethereum.html#native-aa-vs-eip-4337
You can't use this code base to implement MondrianWallet on zkSync.
Refactor completely your code base and project to be compliant with zkSync chain, you can check DefaultAccount.sol. Keep in mind that you must provide to different code base to address both ERC4337 (Ethereum) and zkSync native AA.
pragma solidity 0.8.20;
import "./interfaces/IAccount.sol";
import "./libraries/TransactionHelper.sol";
import "./libraries/SystemContractHelper.sol";
import "./libraries/EfficientCall.sol";
import {BOOTLOADER_FORMAL_ADDRESS, NONCE_HOLDER_SYSTEM_CONTRACT, DEPLOYER_SYSTEM_CONTRACT, INonceHolder} from "./Constants.sol";
* @author Matter Labs
* @custom:security-contact security@matterlabs.dev
* @notice The default implementation of account.
* @dev The bytecode of the contract is set by default for all addresses for which no other bytecodes are deployed.
* @notice If the caller is not a bootloader always returns empty data on call, just like EOA does.
* @notice If it is delegate called always returns empty data, just like EOA does.
*/
contract DefaultAccount is IAccount {
using TransactionHelper for *;
* @dev Simulate the behavior of the EOA if the caller is not the bootloader.
* Essentially, for all non-bootloader callers halt the execution with empty return data.
* If all functions will use this modifier AND the contract will implement an empty payable fallback()
* then the contract will be indistinguishable from the EOA when called.
*/
modifier ignoreNonBootloader() {
if (msg.sender != BOOTLOADER_FORMAL_ADDRESS) {
assembly {
return(0, 0)
}
}
_;
}
* @dev Simulate the behavior of the EOA if it is called via `delegatecall`.
* Thus, the default account on a delegate call behaves the same as EOA on Ethereum.
* If all functions will use this modifier AND the contract will implement an empty payable fallback()
* then the contract will be indistinguishable from the EOA when called.
*/
modifier ignoreInDelegateCall() {
address codeAddress = SystemContractHelper.getCodeAddress();
if (codeAddress != address(this)) {
assembly {
return(0, 0)
}
}
_;
}
function validateTransaction(
bytes32,
bytes32 _suggestedSignedHash,
Transaction calldata _transaction
) external payable override ignoreNonBootloader ignoreInDelegateCall returns (bytes4 magic) {
magic = _validateTransaction(_suggestedSignedHash, _transaction);
}
function _validateTransaction(
bytes32 _suggestedSignedHash,
Transaction calldata _transaction
) internal returns (bytes4 magic) {
SystemContractsCaller.systemCallWithPropagatedRevert(
uint32(gasleft()),
address(NONCE_HOLDER_SYSTEM_CONTRACT),
0,
abi.encodeCall(INonceHolder.incrementMinNonceIfEquals, (_transaction.nonce))
);
bytes32 txHash = _suggestedSignedHash != bytes32(0) ? _suggestedSignedHash : _transaction.encodeHash();
uint256 totalRequiredBalance = _transaction.totalRequiredBalance();
require(totalRequiredBalance <= address(this).balance, "Not enough balance for fee + value");
if (_isValidSignature(txHash, _transaction.signature)) {
magic = ACCOUNT_VALIDATION_SUCCESS_MAGIC;
}
}
function executeTransaction(
bytes32,
bytes32,
Transaction calldata _transaction
) external payable override ignoreNonBootloader ignoreInDelegateCall {
_execute(_transaction);
}
function executeTransactionFromOutside(Transaction calldata _transaction) external payable override {
}
function _execute(Transaction calldata _transaction) internal {
address to = address(uint160(_transaction.to));
uint128 value = Utils.safeCastToU128(_transaction.value);
bytes calldata data = _transaction.data;
uint32 gas = Utils.safeCastToU32(gasleft());
bool isSystemCall;
if (to == address(DEPLOYER_SYSTEM_CONTRACT) && data.length >= 4) {
bytes4 selector = bytes4(data[:4]);
isSystemCall =
selector == DEPLOYER_SYSTEM_CONTRACT.create.selector ||
selector == DEPLOYER_SYSTEM_CONTRACT.create2.selector ||
selector == DEPLOYER_SYSTEM_CONTRACT.createAccount.selector ||
selector == DEPLOYER_SYSTEM_CONTRACT.create2Account.selector;
}
bool success = EfficientCall.rawCall(gas, to, value, data, isSystemCall);
if (!success) {
EfficientCall.propagateRevert();
}
}
function _isValidSignature(bytes32 _hash, bytes memory _signature) internal view returns (bool) {
require(_signature.length == 65, "Signature length is incorrect");
uint8 v;
bytes32 r;
bytes32 s;
assembly {
r := mload(add(_signature, 0x20))
s := mload(add(_signature, 0x40))
v := and(mload(add(_signature, 0x41)), 0xff)
}
require(v == 27 || v == 28, "v is neither 27 nor 28");
require(uint256(s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0, "Invalid s");
address recoveredAddress = ecrecover(_hash, v, r, s);
return recoveredAddress == address(this) && recoveredAddress != address(0);
}
function payForTransaction(
bytes32,
bytes32,
Transaction calldata _transaction
) external payable ignoreNonBootloader ignoreInDelegateCall {
bool success = _transaction.payToTheBootloader();
require(success, "Failed to pay the fee to the operator");
}
function prepareForPaymaster(
bytes32,
bytes32,
Transaction calldata _transaction
) external payable ignoreNonBootloader ignoreInDelegateCall {
_transaction.processPaymasterInput();
}
fallback() external payable ignoreInDelegateCall {
assert(msg.sender != BOOTLOADER_FORMAL_ADDRESS);
}
receive() external payable {
}
}