Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Variant 2, only module #2

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 14 additions & 23 deletions .gas-snapshot
Original file line number Diff line number Diff line change
@@ -1,23 +1,14 @@
RecoveryModuleTest:testAddRecovery() (gas: 86490)
RecoveryModuleTest:testAddRecoveryModule() (gas: 80360)
RecoveryModuleTest:testAddRecoveryShouldRevert() (gas: 20492)
RecoveryModuleTest:testAddRecoveryShouldRevertOnInvalidAddress() (gas: 23035)
RecoveryModuleTest:testAddRecoveryWithoutSubscription() (gas: 44064)
RecoveryModuleTest:testClearRecovery() (gas: 38353)
RecoveryModuleTest:testRemoveRecoveryModule() (gas: 63596)
RecoveryModuleTest:testSubscriptionAmount(uint256) (runs: 256, μ: 15631, ~: 15894)
RecoveryModuleTest:testUpdateLastActivity() (gas: 106312)
RecoveryModuleTest:testUpdateLastActivityShouldFail() (gas: 16050)
RecoveryModuleTest:testCancelTransferOwnership() (gas: 161908)
RecoveryModuleTest:testDuplicateAddRecoveryShouldWork() (gas: 146115)
RecoveryModuleTest:testFailFinalizeOwnership() (gas: 14282)
RecoveryModuleTest:testFinalizeTransferOwnership() (gas: 204028)
RecoveryModuleTest:testFinalizeTransferOwnershipShouldRevert() (gas: 17285)
RecoveryModuleTest:testInactiveForShouldWork() (gas: 189607)
RecoveryModuleTest:testInactiveForTooEarly() (gas: 123639)
RecoveryModuleTest:testInitiateFinalizeTooEarlyShouldRevert() (gas: 147919)
RecoveryModuleTest:testInitiateTransferOwnership() (gas: 151898)
RecoveryModuleTest:testInitiateTransferOwnershipShouldRevertWithAlreadyInitiated() (gas: 153909)
RecoveryModuleTest:testInitiateTransferOwnershipShouldRevertWithInvalidAddress() (gas: 19488)
RecoveryModuleTest:testInitiateTransferOwnershipTooEarlyShouldRevert() (gas: 122070)
RecoveryModuleTest:testSetUp() (gas: 15617)
RecoveryModuleTest:testAddRecoveryShouldRevertForBadRecoveryAddress() (gas: 70027)
RecoveryModuleTest:testCancelTransferOwnership() (gas: 170364)
RecoveryModuleTest:testDuplicateAddRecoveryShouldWork() (gas: 158583)
RecoveryModuleTest:testFailFinalizeOwnership() (gas: 10336)
RecoveryModuleTest:testFinalizeTransferOwnership() (gas: 211308)
RecoveryModuleTest:testFinalizeTransferOwnershipShouldRevert() (gas: 13361)
RecoveryModuleTest:testInactiveForShouldWork(address,uint40,uint256) (runs: 256, μ: 200220, ~: 201587)
RecoveryModuleTest:testInactiveForTooEarlyShouldRevert(uint40) (runs: 256, μ: 129346, ~: 129346)
RecoveryModuleTest:testInitiateFinalizeTooEarlyShouldRevert() (gas: 152601)
RecoveryModuleTest:testInitiateTransferOwnership() (gas: 158115)
RecoveryModuleTest:testInitiateTransferOwnershipShouldRevertWithAlreadyInitiated() (gas: 160105)
RecoveryModuleTest:testInitiateTransferOwnershipShouldRevertWithInvalidAddress() (gas: 15565)
RecoveryModuleTest:testInitiateTransferOwnershipTooEarlyShouldRevert() (gas: 128243)
RecoveryModuleTest:testSetUp() (gas: 15595)
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@
path = lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
branch = v4.8.0
[submodule "lib/solady"]
path = lib/solady
url = https://github.com/Vectorized/solady
branch = v0.0.73
34 changes: 33 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,40 @@
# Safe module template repository with Forge
# Safe recovery module

More info:
https://forum.gnosis-safe.io/t/social-recovery-module/2117/5

## How to activate recovery:

1. Go to safe web app
2. Go to `apps`
3. Click Add custom app
4.
<img src="https://europe1.discourse-cdn.com/standard20/uploads/gnosis_safe/optimized/2X/f/fde5e325c2e22bfb7f16e684a68d54f3f28fd027_2_523x500.png">

Now the module is activated.

5. From safe, create a transaction to that module and call `addRecovery`
`addRecovery` takes 3 parameters (recoveryAddress, recoveryDate, recoveryType)
RecoveryAddress - Address to where we transfer the ownership of the safe
RecoveryType - If value is 0 the selected mode is 'Trigger transfer ownership if safe did not have any new transactions for x seconds'
RecoveryType - If value is 1 the selected mode is 'Trigger after x timestamp'

RecoveryDate - Based on `RecoveryType` it is either seconds, or timestamp (in seconds)
For timestamp value check https://www.epochconverter.com/

6. After executing this transaction safe is now in 'recovery mode'
7. If the transfer ownership condition is met, safe ownership is not transferred right away. It has a timelock period of 10 days.
8. If safe owner doesn't cancel ownership transfer after it has been initiated, ownership is finally transferred after timelock passes.

## Q&A:
1. Can this module steal tokens/nft's/eth from my safe?
<br>
No, Module does not have permissions to take money from safe.
2. Can this module steal safe ownership
<br>
No, safe ownership is only transferred to a predefined recovery address.


## Usage
```
forge install
Expand Down
5 changes: 4 additions & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,7 @@ extra_output = ["abi"]
# goerli = "${GOERLI_RPC_URL}"

[etherscan]
goerli = { key = "${ETHERSCAN_API_KEY}" }
goerli = { key = "${ETHERSCAN_API_KEY}" }

[fuzz]
runs = 50000
1 change: 1 addition & 0 deletions lib/solady
Submodule solady added at 459f66
3 changes: 2 additions & 1 deletion remappings.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
ds-test/=lib/forge-std/lib/ds-test/src/
forge-std/=lib/forge-std/src/
safe-contracts/=lib/safe-contracts/contracts/
@openzeppelin/=lib/openzeppelin-contracts/
@openzeppelin/=lib/openzeppelin-contracts/
@solady/=lib/solady/
5 changes: 1 addition & 4 deletions script/DeployRecovery.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,15 @@
pragma solidity ^0.8.13;

import "forge-std/Script.sol";
import "../src/Recovery.sol";
import "../src/RecoveryModule.sol";

contract DeployRecovery is Script {
function run() external {
uint256 deployerPrivateKey = vm.envUint("PK");
vm.startBroadcast(deployerPrivateKey);

Recovery recovery = new Recovery();
RecoveryModule module = new RecoveryModule(address(recovery), 10 minutes);
RecoveryModule module = new RecoveryModule(10 minutes);

console2.log("Recovery deployed:", address(recovery));
console2.log("Safe module deployed:", address(module));

vm.stopBroadcast();
Expand Down
114 changes: 0 additions & 114 deletions src/IRecovery.sol

This file was deleted.

72 changes: 66 additions & 6 deletions src/IRecoveryModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ import {GnosisSafe} from "safe-contracts/GnosisSafe.sol";
/// @title IRecoveryModule interface
/// @author Benjamin H - <[email protected]>
interface IRecoveryModule {
enum RecoveryType {
After,
InactiveFor
}

/// @notice Thrown when amount is not enough for a desired subscription
/// @param amountYouShouldPay amount you should pay for this subscription
/// Selector 0x0e2e0926
error InvalidPayment(uint256 amountYouShouldPay);

/// @notice Thrown when the current time is lower than `timeLockExpiration`
/// Selector 0x085de625
error TooEarly();
Expand All @@ -22,6 +32,29 @@ interface IRecoveryModule {
/// Selector 0x77fcbe52
error TransferOwnershipAlreadyInitiated();

/// @notice Thrown when the recovery address iz address(0)
/// Or the recovery address is the first owner of safe
/// That is not possible with current implementation
/// Selector 0xa61421a2
error InvalidRecoveryAddress();

/// @notice Emitted when the safe owner adds recovery data
/// @param safe is the address of a safe
/// @param recoveryAddress is the address to which safe ownership will eventually be transfered
/// @param recoveryDate is the recovery date timestamp (in seconds) that marks the start transfer ownership
/// @param recoveryType is the recovery type
event RecoveryAddressAdded(
address indexed safe, address indexed recoveryAddress, uint64 recoveryDate, RecoveryType recoveryType
);

/// @notice Emitted when the safe owner clears his recovery data
/// @param safe is the address of a safe
event RecoveryDataCleared(address indexed safe);

/// @notice Emitted ether is transfered to an address
/// @param recipient is the address of a recipient
event EtherTransferred(address indexed recipient);

/// @notice Emitted when the transfer ownership is initiated
/// @param safe is the safe address
/// @param timeLockExpiration is the timestamp (seconds) when the timelock expires
Expand All @@ -31,12 +64,8 @@ interface IRecoveryModule {
/// @param safe is the safe address
event TransferOwnershipFinalized(address indexed safe);

/// @notice Emitted when the Safe cancels ownership transfer
/// @param safe is the safe address
event TransferOwnershipCanceled(address indexed safe);

/// @notice Cancels the ownership transfer when called by Safe
function cancelTransferOwnership() external;
/// @notice Cancels/clears the ownership transfer when called by Safe
function clearRecovery() external;

/// @notice Initiates ownership transfer
/// @param safe is the safe address
Expand All @@ -50,4 +79,35 @@ interface IRecoveryModule {
/// @param safe is the safe address
/// @return timestamp in seconds
function getTimelockExpiration(address safe) external view returns (uint256);

/// @notice Gets the last activity of a safe
/// @param safe is the address of the safe
/// @return last activity timestamp (in seconds)
function getLastActivity(address safe) external view returns (uint256);

/// @notice Gets the recovery type
/// @param safe is the address of the safe
/// @return recovery type
function getRecoveryType(address safe) external view returns (RecoveryType);

/// @notice Returns recovery address for a `safe`
/// @param safe is the address of the safe
/// @return recovery address
function getRecoveryAddress(address safe) external view returns (address);

/// @notice Returns recovery date for a `safe`
/// @param safe is the address of the safe
/// @return recovery date timestamp (in seconds)
function getRecoveryDate(address safe) external view returns (uint64);

/// @notice Returns recovery value for a `safe`
/// @param safe is the address of the safe
/// @return recovery value
function getRecoveryValue(address safe) external view returns (uint256);

/// @notice Adds recovery address and a recovery date
/// Safe is expected to be a caller
/// @param recoveryAddress is an address to which safe ownership will be transfered
/// @param recoveryDate is a timestamp (in seconds) in the future when the recovery process will start
function addRecovery(address recoveryAddress, uint40 recoveryDate, RecoveryType recoveryType) external payable;
}
Loading