Skip to content

Commit

Permalink
Merge pull request #86 from rhinestonewtf/examples/social-recovery
Browse files Browse the repository at this point in the history
Add social recovery example module
  • Loading branch information
kopy-kat authored Apr 8, 2024
2 parents de15428 + 127f639 commit f2da345
Show file tree
Hide file tree
Showing 14 changed files with 537 additions and 121 deletions.
2 changes: 1 addition & 1 deletion .solhint.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"extends": "solhint:recommended",
"rules": {
"avoid-low-level-calls": "off",
"code-complexity": ["error", 9],
"code-complexity": ["error", 10],
"compiler-version": ["error", ">=0.8.0"],
"contract-name-camelcase": "off",
"const-name-snakecase": "off",
Expand Down
15 changes: 8 additions & 7 deletions examples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,25 @@
"url": "https://github.com/rhinestonewtf/modulekit/issues"
},
"devDependencies": {
"@ERC4337/account-abstraction": "github:kopy-kat/account-abstraction#develop",
"@ERC4337/account-abstraction-v0.6": "github:eth-infinitism/account-abstraction#v0.6.0",
"@openzeppelin/contracts": "5.0.1",
"@prb/math": "^4.0.2",
"@rhinestone/modulekit": "workspace:*",
"@rhinestone/safe7579": "workspace:*",
"@rhinestone/sessionkeymanager": "workspace:*",
"@prb/math": "^4.0.2",
"erc4337-validation": "github:rhinestonewtf/erc4337-validation",
"@ERC4337/account-abstraction": "github:kopy-kat/account-abstraction#develop",
"@safe-global/safe-contracts": "^1.4.1",
"@ERC4337/account-abstraction-v0.6": "github:eth-infinitism/account-abstraction#v0.6.0",
"forge-std": "github:foundry-rs/forge-std",
"checknsignatures": "github:kopy-kat/checknsignatures",
"ds-test": "github:dapphub/ds-test",
"erc4337-validation": "github:rhinestonewtf/erc4337-validation",
"erc7579": "github:erc7579/erc7579-implementation",
"forge-std": "github:foundry-rs/forge-std",
"prettier": "^2.8.8",
"sentinellist": "github:zeroknots/sentinellist",
"solady": "github:vectorized/solady",
"solarray": "github:sablier-labs/solarray",
"solmate": "github:transmissions11/solmate",
"solhint": "^4.1.1",
"prettier": "^2.8.8"
"solmate": "github:transmissions11/solmate"
},
"files": [
"artifacts",
Expand Down
1 change: 1 addition & 0 deletions examples/remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ modulekit/=node_modules/@rhinestone/modulekit/packages/modulekit/
erc4337-validation/=node_modules/erc4337-validation/src/
@ERC4337/=node_modules/@ERC4337/
@prb/math/=node_modules/@prb/math/
checknsignatures/=node_modules/checknsignatures/src/
22 changes: 11 additions & 11 deletions examples/script/Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,27 +27,27 @@ contract DeployScript is Script {
vm.startBroadcast(vm.envUint("PK"));

// Deploy Modules
// AutoSavingToVault autoSavings = new AutoSavingToVault{ salt: salt }();
AutoSavingToVault autoSavings = new AutoSavingToVault{ salt: salt }();

// AutoSendSessionKey autoSend = new AutoSendSessionKey{ salt: salt }();
AutoSendSessionKey autoSend = new AutoSendSessionKey{ salt: salt }();

FlashloanCallback flashloanCallback = new FlashloanCallback{ salt: salt }();
FlashloanLender flashloanLender = new FlashloanLender{ salt: salt }();
// ColdStorageHook coldStorageHook = new ColdStorageHook{ salt: salt }();
// ColdStorageExecutor coldStorageExecutor = new ColdStorageExecutor{ salt: salt }();
ColdStorageHook coldStorageHook = new ColdStorageHook{ salt: salt }();
ColdStorageExecutor coldStorageExecutor = new ColdStorageExecutor{ salt: salt }();

// DeadmanSwitch deadmanSwitch = new DeadmanSwitch{ salt: salt }();
DeadmanSwitch deadmanSwitch = new DeadmanSwitch{ salt: salt }();

// DollarCostAverage dollarCostAverage = new DollarCostAverage{ salt: salt }();
DollarCostAverage dollarCostAverage = new DollarCostAverage{ salt: salt }();

// MultiFactor multiFactor = new MultiFactor{ salt: salt }();
MultiFactor multiFactor = new MultiFactor{ salt: salt }();

// OwnableValidator ownableValidator = new OwnableValidator{ salt: salt }();
OwnableValidator ownableValidator = new OwnableValidator{ salt: salt }();

// ScheduledOrders scheduledOrders = new ScheduledOrders{ salt: salt }();
// ScheduledTransfers scheduledTransfers = new ScheduledTransfers{ salt: salt }();
ScheduledOrders scheduledOrders = new ScheduledOrders{ salt: salt }();
ScheduledTransfers scheduledTransfers = new ScheduledTransfers{ salt: salt }();

// WebAuthnValidator webAuthnValidator = new WebAuthnValidator{ salt: salt }();
WebAuthnValidator webAuthnValidator = new WebAuthnValidator{ salt: salt }();

vm.stopBroadcast();
}
Expand Down
178 changes: 178 additions & 0 deletions examples/src/SocialRecovery/SocialRecovery.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.23;

import { ERC7579ValidatorBase } from "modulekit/src/Modules.sol";
import { PackedUserOperation } from "modulekit/src/external/ERC4337.sol";
import { SentinelListLib } from "sentinellist/SentinelList.sol";
import { CheckSignatures } from "checknsignatures/CheckNSignatures.sol";
import { IERC7579Account } from "modulekit/src/Accounts.sol";
import { ModeLib, CallType, ModeCode, CALLTYPE_SINGLE } from "erc7579/lib/ModeLib.sol";
import { ExecutionLib } from "erc7579/lib/ExecutionLib.sol";
import { LibSort } from "solady/src/utils/LibSort.sol";

contract SocialRecovery is ERC7579ValidatorBase {
using LibSort for *;
using SentinelListLib for SentinelListLib.SentinelList;

/*//////////////////////////////////////////////////////////////////////////
CONSTANTS & STORAGE
//////////////////////////////////////////////////////////////////////////*/

error UnsopportedOperation();
error InvalidGuardian(address guardian);
error ThresholdNotSet();
error InvalidThreshold();

SentinelListLib.SentinelList guardians;
mapping(address account => uint256) thresholds;

/*//////////////////////////////////////////////////////////////////////////
CONFIG
//////////////////////////////////////////////////////////////////////////*/

function onInstall(bytes calldata data) external override {
// Get the threshold and guardians from the data
(uint256 threshold, address[] memory _guardians) = abi.decode(data, (uint256, address[]));

// Sort and uniquify the guardians to make sure a guardian is not reused
_guardians.sort();
_guardians.uniquifySorted();

// Make sure the threshold is set
if (threshold == 0) {
revert ThresholdNotSet();
}

uint256 guardiansLength = _guardians.length;
if (guardiansLength < threshold) {
revert InvalidThreshold();
}

// Set threshold
thresholds[msg.sender] = threshold;

// Initialize the guardian list
guardians.init();

// Add guardians to the list
for (uint256 i = 0; i < guardiansLength; i++) {
address _guardian = _guardians[i];
if (_guardian == address(0)) {
revert InvalidGuardian(_guardian);
}
guardians.push(_guardian);
}
}

function onUninstall(bytes calldata) external override {
// todo
}

function isInitialized(address smartAccount) external view returns (bool) {
return thresholds[smartAccount] != 0;
}

/*//////////////////////////////////////////////////////////////////////////
MODULE LOGIC
//////////////////////////////////////////////////////////////////////////*/

function validateUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash
)
external
override
returns (ValidationData)
{
// Get the threshold and check that its set
uint256 threshold = thresholds[msg.sender];
if (threshold == 0) {
return VALIDATION_FAILED;
}

// Recover the signers from the signatures
address[] memory signers =
CheckSignatures.recoverNSignatures(userOpHash, userOp.signature, threshold);

// Sort and uniquify the signers to make sure a signer is not reused
signers.sort();
signers.uniquifySorted();

// Check if the signers are guardians
SentinelListLib.SentinelList storage _guardians = guardians;
uint256 validSigners;
for (uint256 i = 0; i < signers.length; i++) {
if (_guardians.contains(signers[i])) {
validSigners++;
}
}

// Check if the execution is allowed
bool isAllowedExecution;
bytes4 selector = bytes4(userOp.callData[0:4]);
if (selector == IERC7579Account.execute.selector) {
// Decode and check the execution
// Only single executions to installed validators are allowed
isAllowedExecution = _decodeAndCheckExecution(userOp.callData);
}

// Check if the threshold is met and the execution is allowed and return the result
if (validSigners >= threshold && isAllowedExecution) {
return VALIDATION_SUCCESS;
}
return VALIDATION_FAILED;
}

function isValidSignatureWithSender(
address,
bytes32 hash,
bytes calldata data
)
external
view
override
returns (bytes4)
{
// ERC-1271 not supported for recovery
revert UnsopportedOperation();
}

/*//////////////////////////////////////////////////////////////////////////
INTERNAL
//////////////////////////////////////////////////////////////////////////*/

function _decodeAndCheckExecution(bytes calldata callData)
internal
returns (bool isAllowedExecution)
{
// Get the mode and call type
ModeCode mode = ModeCode.wrap(bytes32(callData[4:36]));
CallType calltype = ModeLib.getCallType(mode);

if (calltype == CALLTYPE_SINGLE) {
// Decode the calldata
(address to,,) = ExecutionLib.decodeSingle(callData[100:]);

// Check if the module is installed as a validator
return IERC7579Account(msg.sender).isModuleInstalled(TYPE_VALIDATOR, to, "");
} else {
return false;
}
}

/*//////////////////////////////////////////////////////////////////////////
METADATA
//////////////////////////////////////////////////////////////////////////*/

function name() external pure returns (string memory) {
return "SocialRecoveryValidator";
}

function version() external pure returns (string memory) {
return "0.0.1";
}

function isModuleType(uint256 typeID) external pure override returns (bool) {
return typeID == TYPE_VALIDATOR;
}
}
File renamed without changes.
Loading

0 comments on commit f2da345

Please sign in to comment.