Skip to content

Instantly share code, notes, and snippets.

@Alexintosh
Last active April 24, 2023 17:55
Show Gist options
  • Save Alexintosh/1ad0bdd9658f992d05bd45ee35ad482e to your computer and use it in GitHub Desktop.
Save Alexintosh/1ad0bdd9658f992d05bd45ee35ad482e to your computer and use it in GitHub Desktop.
pragma solidity ^0.8.0;
import "@gnosis.pm/safe-contracts/contracts/base/Module.sol";
contract PasswordRecoveryModule is Module {
bytes32 private passwordHash;
uint256 private constant BLOCKS_VALID = 200;
uint256 private constant RECOVERY_DELAY = 50;
uint256 private recoveryInitiatedBlock;
address private recoveryInitiator;
mapping(address => bool) private recoveryCancelers;
event PasswordHashUpdated(bytes32 newPasswordHash);
event RecoveryInitiated(address indexed initiator, uint256 validUntilBlock);
event RecoveryCancelled(address indexed canceler);
event CancelerAdded(address indexed canceler);
event CancelerRemoved(address indexed canceler);
constructor(
address _safe,
bytes32 _passwordHash,
address[] memory _recoveryCancelers
) {
require(_safe != address(0), "Invalid Safe address");
require(_passwordHash != bytes32(0), "Invalid password hash");
safe = Safe(_safe);
passwordHash = _passwordHash;
for (uint256 i = 0; i < _recoveryCancelers.length; i++) {
require(_recoveryCancelers[i] != address(0), "Invalid recovery canceler address");
recoveryCancelers[_recoveryCancelers[i]] = true;
}
}
function setup(bytes memory initParams) public override {
// No setup required for this module
}
function updatePasswordHash(bytes32 newPasswordHash) public {
require(safe.isOwner(msg.sender), "Not authorized");
require(newPasswordHash != bytes32(0), "Invalid password hash");
passwordHash = newPasswordHash;
emit PasswordHashUpdated(newPasswordHash);
}
function initiateRecovery() public {
recoveryInitiatedBlock = block.number;
recoveryInitiator = msg.sender;
emit RecoveryInitiated(msg.sender, recoveryInitiatedBlock + BLOCKS_VALID);
}
function cancelRecovery() public {
require(
safe.isOwner(msg.sender) || recoveryCancelers[msg.sender],
"Not authorized"
);
require(recoveryInitiator != address(0), "No recovery initiated");
emit RecoveryCancelled(msg.sender);
// Reset recovery state
recoveryInitiatedBlock = 0;
recoveryInitiator = address(0);
}
function recoverAccess(address newOwner, string memory password) public {
require(msg.sender == recoveryInitiator, "Not authorized");
require(recoveryInitiatedBlock + RECOVERY_DELAY <= block.number, "Recovery delay not met");
require(recoveryInitiatedBlock + BLOCKS_VALID >= block.number, "Recovery reservation expired");
require(passwordHash == keccak256(abi.encodePacked(password)), "Invalid password");
address oldOwner = safe.getOwner(msg.sender);
require(oldOwner != address(0), "Not an owner");
safe.swapOwner(msg.sender, oldOwner, newOwner);
// Reset recovery state and password hash
recoveryInitiatedBlock = 0;
recoveryInitiator = address(0);
passwordHash = bytes32(0);
}
function addCanceler(address canceler) public {
require(safe.isOwner(msg.sender), "Not authorized");
require(canceler != address(0), "Invalid canceler address");
require(!recoveryCancelers[canceler], "Address is already a canceler");
recoveryCancelers[canceler] = true;
emit CancelerAdded(canceler);
}
function removeCanceler(address canceler) public {
require(safe.isOwner(msg.sender), "Not authorized");
require(canceler != address(0), "Invalid canceler address");
require(recoveryCancelers[canceler], "Address is not a canceler");
delete recoveryCancelers[canceler];
emit CancelerRemoved(canceler);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment