Skip to content

Commit

Permalink
✨ Hash validate opt in recovery
Browse files Browse the repository at this point in the history
  • Loading branch information
z0r0z committed Nov 8, 2023
1 parent 4e4198d commit b326d0b
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 18 deletions.
10 changes: 5 additions & 5 deletions .gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ AccountTest:testOnERC721Received() (gas: 1463812)
AccountTest:testOwnerRecovery() (gas: 526632)
AccountTest:testValidateUserOp() (gas: 532112)
AccountTest:test__codesize() (gas: 63279)
RecoveryValidatorTest:testFailSocialRecovery() (gas: 195673)
RecoveryValidatorTest:testInstall() (gas: 172304)
RecoveryValidatorTest:testSetGuardians() (gas: 172860)
RecoveryValidatorTest:testSocialRecovery() (gas: 175114)
RecoveryValidatorTest:testUninstall() (gas: 139740)
RecoveryValidatorTest:testFailSocialRecovery() (gas: 219447)
RecoveryValidatorTest:testInstall() (gas: 196042)
RecoveryValidatorTest:testSetGuardians() (gas: 196598)
RecoveryValidatorTest:testSocialRecovery() (gas: 195723)
RecoveryValidatorTest:testUninstall() (gas: 160010)
55 changes: 48 additions & 7 deletions src/validators/RecoveryValidator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ contract RecoveryValidator {
/// @dev Logs the new guardians' threshold of an account.
event ThresholdSet(address indexed account, uint256 threshold);

/// @dev Logs the new userOpHash of an account.
event UserOpHashSet(address indexed account, bytes32 userOpHash);

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STRUCTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
Expand Down Expand Up @@ -53,7 +56,10 @@ contract RecoveryValidator {
mapping(address => address[]) internal _guardians;

/// @dev Stores mappings of thresholds to accounts.
mapping(address => uint256) public _thresholds;
mapping(address => uint256) internal _thresholds;

/// @dev Stores mappings of userOpHash(es) to accounts.
mapping(address => bytes32) internal _userOpHashes;

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTRUCTOR */
Expand Down Expand Up @@ -93,7 +99,14 @@ contract RecoveryValidator {
}
}
}
uninstall(); // Uninstall the guardians and threshold of caller if validation succeeded.
// If a guardian validated `userOp` and `userOpHash` is stored
// for the caller, assert and check this matches the validated.
// This effectively allows this recovery validator to ensure
// that only an account's countersigned userOp can execute.
if (_userOpHashes[msg.sender] != "") {
assert(_userOpHashes[msg.sender] != userOpHash);
}
uninstall(); // Uninstall the recovery settings.
}

/// @dev Returns bytes array from split signature.
Expand Down Expand Up @@ -123,6 +136,16 @@ contract RecoveryValidator {
return _guardians[account];
}

/// @dev Returns the threshold of an account.
function thresholdOf(address account) public view virtual returns (uint256) {
return _thresholds[account];
}

/// @dev Returns the userOpHash of an account.
function userOpHashOf(address account) public view virtual returns (bytes32) {
return _userOpHashes[account];
}

/// @dev Sets the new guardians of an account.
function setGuardians(address[] memory guardians) public payable virtual {
LibSort.sort(guardians);
Expand All @@ -136,16 +159,34 @@ contract RecoveryValidator {
emit ThresholdSet(msg.sender, _thresholds[msg.sender] = threshold);
}

/// @dev Installs the new guardians and threshold of an account.
/// @dev Sets the new userOpHash of an account.
function setUserOpHash(bytes32 userOpHash) public payable virtual {
emit UserOpHashSet(msg.sender, _userOpHashes[msg.sender] = userOpHash);
}

/// @dev Installs the new guardians of an account from `data`,
/// sets the threshold by which guardians validate, as well as
/// an optional userOpHash to limit such validation operations.
function install(bytes calldata data) public payable virtual {
(uint256 threshold, address[] memory guardians) = abi.decode(data, (uint256, address[]));
setGuardians(guardians);
setThreshold(threshold);
(uint256 threshold, bytes32 userOpHash, address[] memory guardians) =
abi.decode(data, (uint256, bytes32, address[]));
if (guardians.length != 0) {
LibSort.sort(guardians);
emit GuardiansSet(msg.sender, _guardians[msg.sender] = guardians);
}
if (guardians.length >= threshold) {
emit ThresholdSet(msg.sender, _thresholds[msg.sender] = threshold);
}
if (userOpHash != "") {
emit UserOpHashSet(msg.sender, _userOpHashes[msg.sender] = userOpHash);
}
}

/// @dev Uninstalls the guardians and threshold of an account.
/// @dev Uninstalls the guardians, threshold and userOpHash
/// of an account such that recovery validation will revert.
function uninstall() public payable virtual {
emit GuardiansSet(msg.sender, _guardians[msg.sender] = new address[](0));
emit ThresholdSet(msg.sender, _thresholds[msg.sender] = 0);
emit UserOpHashSet(msg.sender, _userOpHashes[msg.sender] = "");
}
}
12 changes: 6 additions & 6 deletions test/validators/RecoveryValidator.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ contract RecoveryValidatorTest is Test {
account.execute(
address(socialRecoveryValidator),
0 ether,
abi.encodeWithSelector(RecoveryValidator.install.selector, abi.encode(3, guardians))
abi.encodeWithSelector(RecoveryValidator.install.selector, abi.encode(3, "", guardians))
);
guardians = socialRecoveryValidator.guardiansOf(address(account));
address guardianOne = guardians[0];
Expand All @@ -88,7 +88,7 @@ contract RecoveryValidatorTest is Test {
address(socialRecoveryValidator),
0 ether,
abi.encodeWithSelector(
socialRecoveryValidator.install.selector, abi.encode(3, guardians)
socialRecoveryValidator.install.selector, abi.encode(3, "", guardians)
)
);
guardians = socialRecoveryValidator.guardiansOf(address(account));
Expand Down Expand Up @@ -126,7 +126,7 @@ contract RecoveryValidatorTest is Test {
address(socialRecoveryValidator),
0 ether,
abi.encodeWithSelector(
socialRecoveryValidator.install.selector, abi.encode(3, guardians)
socialRecoveryValidator.install.selector, abi.encode(3, "", guardians)
)
);
guardians = socialRecoveryValidator.guardiansOf(address(account));
Expand All @@ -152,7 +152,7 @@ contract RecoveryValidatorTest is Test {
calls[0].target = address(socialRecoveryValidator);
calls[0].value = 0 ether;
calls[0].data = abi.encodeWithSelector(
socialRecoveryValidator.install.selector, abi.encode(2, guardians)
socialRecoveryValidator.install.selector, abi.encode(2, "", guardians)
);

calls[1].target = address(account);
Expand Down Expand Up @@ -215,7 +215,7 @@ contract RecoveryValidatorTest is Test {
calls[0].target = address(socialRecoveryValidator);
calls[0].value = 0 ether;
calls[0].data = abi.encodeWithSelector(
socialRecoveryValidator.install.selector, abi.encode(2, guardians)
socialRecoveryValidator.install.selector, abi.encode(2, "", guardians)
);

calls[1].target = address(account);
Expand Down Expand Up @@ -266,7 +266,7 @@ contract RecoveryValidatorTest is Test {
calls[0].target = address(socialRecoveryValidator);
calls[0].value = 0 ether;
calls[0].data = abi.encodeWithSelector(
socialRecoveryValidator.install.selector, abi.encode(threshold, guardians)
socialRecoveryValidator.install.selector, abi.encode(threshold, "", guardians)
);

calls[1].target = address(account);
Expand Down

0 comments on commit b326d0b

Please sign in to comment.