Skip to content

Instantly share code, notes, and snippets.

@wong2
Last active August 26, 2022 04:00
Embed
What would you like to do?
Minimum social recovery wallet implementation on zkSync https://vitalik.ca/general/2021/01/11/recovery.html
// 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