-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #24 from zkemail/feat/native-safe-recovery-module
Feat/native safe recovery module
- Loading branch information
Showing
13 changed files
with
503 additions
and
35 deletions.
There are no files selected for viewing
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.25; | ||
|
||
import { Script } from "forge-std/Script.sol"; | ||
import { console } from "forge-std/console.sol"; | ||
|
||
contract ComputeSafeRecoveryCalldataScript is Script { | ||
function run() public { | ||
address oldOwner = vm.envAddress("OLD_OWNER"); | ||
address newOwner = vm.envAddress("NEW_OWNER"); | ||
address previousOwnerInLinkedList = address(1); | ||
|
||
bytes memory recoveryCalldata = abi.encodeWithSignature( | ||
"swapOwner(address,address,address)", previousOwnerInLinkedList, oldOwner, newOwner | ||
); | ||
|
||
console.log("recoveryCalldata", vm.toString(recoveryCalldata)); | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.25; | ||
|
||
import { Script } from "forge-std/Script.sol"; | ||
import { console } from "forge-std/console.sol"; | ||
import { Verifier } from "ether-email-auth/packages/contracts/src/utils/Verifier.sol"; | ||
import { ECDSAOwnedDKIMRegistry } from | ||
"ether-email-auth/packages/contracts/src/utils/ECDSAOwnedDKIMRegistry.sol"; | ||
import { EmailAuth } from "ether-email-auth/packages/contracts/src/EmailAuth.sol"; | ||
import { SafeRecoverySubjectHandler } from "src/handlers/SafeRecoverySubjectHandler.sol"; | ||
import { SafeEmailRecoveryModule } from "src/modules/SafeEmailRecoveryModule.sol"; | ||
|
||
contract DeploySafeNativeRecovery_Script is Script { | ||
function run() public { | ||
vm.startBroadcast(vm.envUint("PRIVATE_KEY")); | ||
address verifier = vm.envOr("VERIFIER", address(0)); | ||
address dkimRegistry = vm.envOr("DKIM_REGISTRY", address(0)); | ||
address dkimRegistrySigner = vm.envOr("SIGNER", address(0)); | ||
address emailAuthImpl = vm.envOr("EMAIL_AUTH_IMPL", address(0)); | ||
address subjectHandler = vm.envOr("SUBJECT_HANDLER", address(0)); | ||
|
||
if (verifier == address(0)) { | ||
verifier = address(new Verifier()); | ||
console.log("Deployed Verifier at", verifier); | ||
} | ||
|
||
if (dkimRegistry == address(0)) { | ||
require(dkimRegistrySigner != address(0), "DKIM_REGISTRY_SIGNER is required"); | ||
dkimRegistry = address(new ECDSAOwnedDKIMRegistry(dkimRegistrySigner)); | ||
console.log("Deployed DKIM Registry at", dkimRegistry); | ||
} | ||
|
||
if (emailAuthImpl == address(0)) { | ||
emailAuthImpl = address(new EmailAuth()); | ||
console.log("Deployed Email Auth at", emailAuthImpl); | ||
} | ||
|
||
if (subjectHandler == address(0)) { | ||
subjectHandler = address(new SafeRecoverySubjectHandler()); | ||
console.log("Deployed Subject Handler at", subjectHandler); | ||
} | ||
|
||
address module = address( | ||
new SafeEmailRecoveryModule(verifier, dkimRegistry, emailAuthImpl, subjectHandler) | ||
); | ||
|
||
console.log("Deployed Email Recovery Module at ", vm.toString(module)); | ||
|
||
vm.stopBroadcast(); | ||
} | ||
} |
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
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.25; | ||
|
||
import { ISafe } from "../interfaces/ISafe.sol"; | ||
import { Enum } from "@safe-global/safe-contracts/contracts/common/Enum.sol"; | ||
import { EmailRecoveryManager } from "../EmailRecoveryManager.sol"; | ||
|
||
/** | ||
* A safe module that recovers a safe owner via ZK Email | ||
*/ | ||
contract SafeEmailRecoveryModule is EmailRecoveryManager { | ||
bytes4 public constant selector = bytes4(keccak256(bytes("swapOwner(address,address,address)"))); | ||
|
||
event RecoveryExecuted(address indexed account); | ||
|
||
error InvalidAccount(address account); | ||
error InvalidSelector(bytes4 selector); | ||
error RecoveryFailed(address account); | ||
|
||
constructor( | ||
address verifier, | ||
address dkimRegistry, | ||
address emailAuthImpl, | ||
address subjectHandler | ||
) | ||
EmailRecoveryManager(verifier, dkimRegistry, emailAuthImpl, subjectHandler) | ||
{ } | ||
|
||
/** | ||
* Check if a recovery request can be initiated based on guardian acceptance | ||
* @param account The smart account to check | ||
* @return true if the recovery request can be started, false otherwise | ||
*/ | ||
function canStartRecoveryRequest(address account) external view returns (bool) { | ||
GuardianConfig memory guardianConfig = getGuardianConfig(account); | ||
|
||
return guardianConfig.acceptedWeight >= guardianConfig.threshold; | ||
} | ||
|
||
/** | ||
* @notice Executes recovery on a Safe account. Called from the recovery manager | ||
* @param account The account to execute recovery for | ||
* @param recoveryData The recovery data that should be executed on the Safe | ||
* being recovered. recoveryData = abi.encode(safeAccount, recoveryFunctionCalldata) | ||
*/ | ||
function recover(address account, bytes calldata recoveryData) internal override { | ||
(address encodedAccount, bytes memory recoveryCalldata) = | ||
abi.decode(recoveryData, (address, bytes)); | ||
|
||
if (encodedAccount == address(0) || encodedAccount != account) { | ||
revert InvalidAccount(encodedAccount); | ||
} | ||
|
||
bytes4 calldataSelector; | ||
assembly { | ||
calldataSelector := mload(add(recoveryCalldata, 32)) | ||
} | ||
if (calldataSelector != selector) { | ||
revert InvalidSelector(calldataSelector); | ||
} | ||
|
||
bool success = ISafe(account).execTransactionFromModule({ | ||
to: account, | ||
value: 0, | ||
data: recoveryCalldata, | ||
operation: uint8(Enum.Operation.Call) | ||
}); | ||
if (!success) { | ||
revert RecoveryFailed(account); | ||
} | ||
|
||
emit RecoveryExecuted(account); | ||
} | ||
} |
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
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
Oops, something went wrong.