Beginner FriendlyFoundry
100 EXP
View results
Submission Details
Severity: low
Invalid

ZkSync Era does not have native support for transferring Ether

Summary

MondiranWallet has multiple functions with low-level calls that are supposed to transfer Ether. However, ZkSync Era does not have native support for transferring Ether, so deployment to ZkSync requires additional considerations.

Vulnerability Details

MondrianWallet utilizes low-level calls (indicated with "@> below) to directly transfer Ether in the following three functions:

function execute(address dest, uint256 value, bytes calldata func) external requireFromEntryPointOrOwner {
@> (bool success, bytes memory result) = dest.call{value: value}(func);
if (!success) {
assembly {
revert(add(result, 32), mload(result))
}
}
}
function _payPrefund(uint256 missingAccountFunds) internal virtual {
if (missingAccountFunds != 0) {
@> (bool success,) = payable(msg.sender).call{value: missingAccountFunds, gas: type(uint256).max}("");
(success);
//ignore failure (its EntryPoint's job to verify, not account.)
}
}
function addDeposit() public payable {
@> i_entryPoint.depositTo{value: msg.value}(address(this));
}

However, ZkSync does not have support for such direct Ether transfers, see the documentation at:

  • https://docs.zksync.io/build/developer-reference/differences-with-ethereum.html#call-staticcall-delegatecall

  • https://docs.zksync.io/zk-stack/components/smart-contracts/system-contracts.html#l2ethtoken-msgvaluesimulator

  • https://docs.zksync.io/zk-stack/components/compiler/specification/system-contracts.html#ether-value-simulator

Instead, Ether transfers on ZkSync are handled by a special system contract called MsgValueSimulator.

Further details on how this work is available here: https://www.rollup.codes/zksync-era

"CALL Creates a new sub-context and executes the code of the given account.
The OPCODE does not support sending ether natively
If not compiled with zk-solc or zk-vyper, Ether must be send using a system contract MsgValueSimulator prior to executing the CALL opcode.
zk-solc and zk-vyper compilers inject the call to the system contract under the hood during compilation.
...
"

Impact

The impact depends on the compilation:

  • if the contract is compiled with zk-solc or zk-vyper, there is no impact, as Ether transfer through CALL is handled automatically by the compiler, which injects calls to the MsgValueSimulator system contract.

  • if the contract is not compiled with zk-solc or zk-vyper, MondrianWallet::execute, MondrianWallet::_payPrefund, and MondrianWallet::addDeposit will not work on ZkSync and the contract will be esentially disfunctional, provided that the code is not adjusted.

Tools Used

Manual review, Foundry.

Recommendations

Either

  • compile the contract using zk-solc for zkSync (so that you can use the CALL opcode directly without manually invoking the MsgValueSimulator, or

  • tod ensure compatibility across all environments, check the chainID and explicitly use the MsgValueSimulator for zkSync.

Updates

Lead Judging Commences

inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.