Instantly share code, notes, and snippets.
Last active
August 26, 2022 04:00
-
Star
(0)
0
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save wong2/02e7d833444851587019bfd38f56221f to your computer and use it in GitHub Desktop.
Minimum social recovery wallet implementation on zkSync https://vitalik.ca/general/2021/01/11/recovery.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.0; | |
import "@matterlabs/zksync-contracts/l2/system-contracts/interfaces/IAccountAbstraction.sol"; | |
import "@matterlabs/zksync-contracts/l2/system-contracts/TransactionHelper.sol"; | |
import "@matterlabs/zksync-contracts/l2/system-contracts/Constants.sol"; | |
import "@openzeppelin/contracts/interfaces/IERC1271.sol"; | |
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; | |
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; | |
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; | |
contract SocialRecoveryAccount is IAccountAbstraction, IERC1271 { | |
using TransactionHelper for Transaction; | |
using EnumerableSet for EnumerableSet.AddressSet; | |
address public signer; | |
EnumerableSet.AddressSet private _guardiansSet; | |
uint256 public replaceSignerNonce; | |
constructor(address _signer, address[] memory _guardians) { | |
signer = _signer; | |
for (uint256 i = 0; i < _guardians.length; i++) { | |
_guardiansSet.add(_guardians[i]); | |
} | |
require( | |
_guardiansSet.length() >= 3, | |
"At least 3 guardians are required" | |
); | |
} | |
function guardians() public view returns (address[] memory) { | |
return _guardiansSet.values(); | |
} | |
function replaceSigner( | |
address _newSigner, | |
uint256 _nonce, | |
bytes[] calldata _signatures | |
) external { | |
require(_nonce == replaceSignerNonce, "Invalid nonce"); | |
bytes32 hash = ECDSA.toEthSignedMessageHash( | |
abi.encodePacked(_newSigner, _nonce) | |
); | |
uint256 validSignatureCount = 0; | |
for (uint256 i = 0; i < _signatures.length; i++) { | |
address addr = ECDSA.recover(hash, _signatures[i]); | |
if (_guardiansSet.contains(addr)) { | |
validSignatureCount += 1; | |
} | |
} | |
require(validSignatureCount > _guardiansSet.length() / 2); | |
signer = _newSigner; | |
replaceSignerNonce += 1; | |
} | |
modifier onlyBootloader() { | |
require( | |
msg.sender == BOOTLOADER_FORMAL_ADDRESS, | |
"Only bootloader can call this method" | |
); | |
_; | |
} | |
function validateTransaction(Transaction calldata _transaction) | |
external | |
payable | |
override | |
onlyBootloader | |
{ | |
_validateTransaction(_transaction); | |
} | |
function _validateTransaction(Transaction calldata _transaction) internal { | |
NONCE_HOLDER_SYSTEM_CONTRACT.incrementNonceIfEquals( | |
_transaction.reserved[0] | |
); | |
bytes32 txHash = _transaction.encodeHash(); | |
require( | |
isValidSignature(txHash, _transaction.signature) == | |
EIP1271_SUCCESS_RETURN_VALUE | |
); | |
} | |
function executeTransaction(Transaction calldata _transaction) | |
external | |
payable | |
override | |
onlyBootloader | |
{ | |
_executeTransaction(_transaction); | |
} | |
function _executeTransaction(Transaction calldata _transaction) internal { | |
uint256 to = _transaction.to; | |
uint256 value = _transaction.reserved[1]; | |
bytes memory data = _transaction.data; | |
bool success; | |
assembly { | |
success := call( | |
gas(), | |
to, | |
value, | |
add(data, 0x20), | |
mload(data), | |
0, | |
0 | |
) | |
} | |
require(success); | |
} | |
function executeTransactionFromOutside(Transaction calldata _transaction) | |
external | |
payable | |
{ | |
_validateTransaction(_transaction); | |
_executeTransaction(_transaction); | |
} | |
bytes4 constant EIP1271_SUCCESS_RETURN_VALUE = 0x1626ba7e; | |
function isValidSignature(bytes32 _hash, bytes calldata _signature) | |
public | |
view | |
override | |
returns (bytes4) | |
{ | |
address recoveredAddr = ECDSA.recover(_hash, _signature); | |
require(recoveredAddr == signer); | |
return EIP1271_SUCCESS_RETURN_VALUE; | |
} | |
function onERC721Received( | |
address, | |
address, | |
uint256, | |
bytes calldata | |
) external pure returns (bytes4) { | |
return IERC721Receiver.onERC721Received.selector; | |
} | |
receive() external payable { | |
// If the bootloader called the `receive` function, it likely means | |
// that something went wrong and the transaction should be aborted. The bootloader should | |
// only interact through the `validateTransaction`/`executeTransaction` methods. | |
assert(msg.sender != BOOTLOADER_FORMAL_ADDRESS); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment