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

Updates to support body-parsing of email account recovery. #12

Draft
wants to merge 12 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion contracts/AccountFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ contract AccountFactory is Ownable {
address _registry,
bytes32 _proxyBytecodeHash,
address deployer
) Ownable() {
) Ownable(msg.sender) {
implementation = _implementation;
registry = _registry;
proxyBytecodeHash = _proxyBytecodeHash;
Expand Down
2 changes: 1 addition & 1 deletion contracts/ClaveRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ contract ClaveRegistry is Ownable, IClaveRegistry {
event FactoryUnset(address indexed factory);

// Constructor function of the contracts
constructor() Ownable() {}
constructor() Ownable(msg.sender) {}

/**
* @notice Registers an account as a Clave account
Expand Down
4 changes: 3 additions & 1 deletion contracts/cns/ClaveNameService.sol
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,8 @@ contract ClaveNameService is IClaveNameService, ERC721, ERC721Burnable, AccessCo

/// @inheritdoc ERC721
function tokenURI(uint256 tokenId) public view override returns (string memory) {
if (!_exists(tokenId)) return '';
// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/cae60c595b37b1e7ed7dd50ad0257387ec07c0cf/CHANGELOG.md?plain=1#L208
if (_ownerOf(tokenId) == address(0)) return '';

return
bytes(baseTokenURI).length > 0 ? string.concat(baseTokenURI, tokenId.toString()) : '';
Expand Down Expand Up @@ -234,6 +235,7 @@ contract ClaveNameService is IClaveNameService, ERC721, ERC721Burnable, AccessCo
* @inheritdoc ERC721
* @dev Transfers to addresses already have assets are restricted
*/
// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/cae60c595b37b1e7ed7dd50ad0257387ec07c0cf/CHANGELOG.md?plain=1#L142
function _beforeTokenTransfer(address from, address to, uint256) internal view override {
require(
from == address(0) || to == address(0),
Expand Down
5 changes: 4 additions & 1 deletion contracts/earn/KoiEarnRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity ^0.8.17;

import {SafeERC20, IERC20} from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
import {SafeApprove} from '../libraries/SafeApprove.sol';

interface IKoiRouter {
function swapExactTokensForTokens(
Expand Down Expand Up @@ -104,7 +105,9 @@ interface IKoiPair is IERC20 {
*/
contract KoiEarnRouter is IKoiEarnRouter {
using SafeERC20 for IERC20;
using SafeApprove for IERC20;
using SafeERC20 for IKoiPair;
using SafeApprove for IKoiPair;

IKoiRouter private koiRouter;

Expand Down Expand Up @@ -308,7 +311,7 @@ contract KoiEarnRouter is IKoiEarnRouter {
}

tokenA.safeTransfer(msg.sender, desiredA - amountA);
tokenA.safeApprove(address(koiRouter), 0);
tokenA.approve(address(koiRouter), 0);

emit Deposit(
msg.sender,
Expand Down
4 changes: 3 additions & 1 deletion contracts/earn/SyncEarnRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity ^0.8.17;

import {SafeERC20, IERC20} from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
import {SafeApprove} from '../libraries/SafeApprove.sol';

struct ExactInputSingleParams {
address tokenIn;
Expand Down Expand Up @@ -88,7 +89,8 @@ interface ISyncPair is IERC20 {

contract SyncEarnRouter is ISyncEarnRouter {
using SafeERC20 for IERC20;

using SafeApprove for IERC20;

uint256 private constant PRECISION = 1e24;

ISyncRouter private syncRouter;
Expand Down
4 changes: 3 additions & 1 deletion contracts/earn/SyncEarnRouterV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity ^0.8.17;

import {SafeERC20, IERC20} from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
import {SafeApprove} from '../libraries/SafeApprove.sol';

interface IWETH {
function withdraw(uint256 amount) external;
Expand Down Expand Up @@ -72,7 +73,8 @@ interface ISyncPair is IERC20 {
/// @dev Deployed with SyncEarnRouter name
contract SyncEarnRouterV2 is ISyncEarnRouter {
using SafeERC20 for IERC20;

using SafeApprove for IERC20;

uint256 private constant PRECISION = 1e24;

ISyncRouter private syncRouter;
Expand Down
2 changes: 1 addition & 1 deletion contracts/earn/ZtaKeV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ contract ZtaKeV2 is Ownable {
address _stakeToken,
address _rewardToken,
address _registry
) Ownable() {
) Ownable(msg.sender) {
limitPerUser = _limitPerUser;
totalLimit = _totalLimit;

Expand Down
4 changes: 2 additions & 2 deletions contracts/handlers/ERC1271Handler.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.17;

import {IERC1271Upgradeable} from '@openzeppelin/contracts-upgradeable/interfaces/IERC1271Upgradeable.sol';
import {IERC1271} from '@openzeppelin/contracts/interfaces/IERC1271.sol';

import {SignatureDecoder} from '../libraries/SignatureDecoder.sol';
import {ValidationHandler} from './ValidationHandler.sol';
Expand All @@ -13,7 +13,7 @@ import {EIP712} from '../helpers/EIP712.sol';
* @author https://getclave.io
*/
abstract contract ERC1271Handler is
IERC1271Upgradeable,
IERC1271,
EIP712('Clave1271', '1.0.0'),
ValidationHandler
{
Expand Down
6 changes: 3 additions & 3 deletions contracts/interfaces/IClave.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ pragma solidity ^0.8.17;

import {IAccount} from '@matterlabs/zksync-contracts/l2/system-contracts/interfaces/IAccount.sol';

import {IERC1271Upgradeable} from '@openzeppelin/contracts-upgradeable/interfaces/IERC1271Upgradeable.sol';
import {IERC777Recipient} from '@openzeppelin/contracts/token/ERC777/IERC777Recipient.sol';
import {IERC1271} from '@openzeppelin/contracts/interfaces/IERC1271.sol';
import {IERC777Recipient} from '@openzeppelin/contracts/interfaces/IERC777Recipient.sol';
import {IERC721Receiver} from '@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol';
import {IERC1155Receiver} from '@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol';

Expand All @@ -20,7 +20,7 @@ import {IValidatorManager} from './IValidatorManager.sol';
* @dev Implementations of this interface are contract that can be used as a Clave
*/
interface IClaveAccount is
IERC1271Upgradeable,
IERC1271,
IERC721Receiver,
IERC1155Receiver,
IHookManager,
Expand Down
6 changes: 0 additions & 6 deletions contracts/interfaces/IEmailRecoveryModule.sol

This file was deleted.

29 changes: 0 additions & 29 deletions contracts/interfaces/IEmailRecoverySubjectHandler.sol

This file was deleted.

19 changes: 19 additions & 0 deletions contracts/libraries/SafeApprove.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.17;

import {SafeERC20, IERC20} from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';

library SafeApprove {
using SafeERC20 for IERC20;

function safeApprove(IERC20 token, address spender, uint256 amount) internal {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance > amount) {
uint256 decreaseAmount = currentAllowance - amount;
token.safeDecreaseAllowance(spender, decreaseAmount);
} else if (currentAllowance < amount) {
uint256 increaseAmount = amount - currentAllowance;
token.safeIncreaseAllowance(spender, increaseAmount);
}
}
}
161 changes: 161 additions & 0 deletions contracts/modules/recovery/EmailRecoveryCommandHandler.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;

import {IEmailRecoveryCommandHandler} from "@zk-email/email-recovery/src/interfaces/IEmailRecoveryCommandHandler.sol";
import {IEmailRecoveryManager} from "@zk-email/email-recovery/src/interfaces/IEmailRecoveryManager.sol";
import {StringUtils} from "@zk-email/email-recovery/src/libraries/StringUtils.sol";

/**
* Handler contract that defines command templates and how to validate them
* This is the default command handler that will work with any validator.
*/
contract EmailRecoveryCommandHandler is IEmailRecoveryCommandHandler {
error InvalidCommandParams();
error InvalidAccount();
error InvalidRecoveryModule();

/**
* @notice Returns a hard-coded two-dimensional array of strings representing the command
* templates for an acceptance by a new guardian.
* @return string[][] A two-dimensional array of strings, where each inner array represents a
* set of fixed strings and matchers for a command template.
*/
function acceptanceCommandTemplates()
public
pure
returns (string[][] memory)
{
string[][] memory templates = new string[][](1);
templates[0] = new string[](5);
templates[0][0] = "Accept";
templates[0][1] = "guardian";
templates[0][2] = "request";
templates[0][3] = "for";
templates[0][4] = "{ethAddr}";
return templates;
}

/**
* @notice Returns a hard-coded two-dimensional array of strings representing the command
* templates for email recovery.
* @return string[][] A two-dimensional array of strings, where each inner array represents a
* set of fixed strings and matchers for a command template.
*/
function recoveryCommandTemplates()
public
pure
returns (string[][] memory)
{
string[][] memory templates = new string[][](1);
templates[0] = new string[](10);
templates[0][0] = "Recover";
templates[0][1] = "account";
templates[0][2] = "{ethAddr}";
templates[0][3] = "via";
templates[0][4] = "recovery";
templates[0][5] = "module";
templates[0][6] = "{ethAddr}";
templates[0][7] = "to";
templates[0][8] = "owner";
templates[0][9] = "{string}";
return templates;
}

/**
* @notice Extracts the account address to be recovered from the command parameters of an
* acceptance email.
* @param commandParams The command parameters of the acceptance email.
*/
function extractRecoveredAccountFromAcceptanceCommand(
bytes[] memory commandParams,
uint256 /* templateIdx */
) public pure returns (address) {
return abi.decode(commandParams[0], (address));
}

/**
* @notice Extracts the account address to be recovered from the command parameters of a
* recovery email.
* @param commandParams The command parameters of the recovery email.
*/
function extractRecoveredAccountFromRecoveryCommand(
bytes[] memory commandParams,
uint256 /* templateIdx */
) public pure returns (address) {
return abi.decode(commandParams[0], (address));
}

/**
* @notice Validates the command params for an acceptance email
* @param templateIdx The index of the template used for the acceptance email
* @param commandParams The command parameters of the recovery email.
* @return accountInEmail The account address in the acceptance email
*/
function validateAcceptanceCommand(
uint256 templateIdx,
bytes[] calldata commandParams
) external pure returns (address) {
if (templateIdx != 0 || commandParams.length != 1)
revert InvalidCommandParams();

// The GuardianStatus check in acceptGuardian implicitly
// validates the account, so no need to re-validate here
address accountInEmail = abi.decode(commandParams[0], (address));

return accountInEmail;
}

/**
* @notice Validates the command params for an acceptance email
* @param templateIdx The index of the template used for the recovery email
* @param commandParams The command parameters of the recovery email.
* @return accountInEmail The account address in the acceptance email
*/
function validateRecoveryCommand(
uint256 templateIdx,
bytes[] calldata commandParams
) public view returns (address) {
if (templateIdx != 0 || commandParams.length != 3) {
revert InvalidCommandParams();
}

address accountInEmail = abi.decode(commandParams[0], (address));
address recoveryModuleInEmail = abi.decode(commandParams[1], (address));

if (accountInEmail == address(0)) {
revert InvalidAccount();
}

address expectedRecoveryModule = address(this);
if (recoveryModuleInEmail != expectedRecoveryModule) {
revert InvalidRecoveryModule();
}

return accountInEmail;
}

/**
* @notice parses the recovery data hash from the command params. The data hash is
* verified against later when recovery is executed
* @dev recoveryDataHash = abi.encode(validator, recoveryFunctionCalldata)
* @param templateIdx The index of the template used for the recovery request
* @param commandParams The command parameters of the recovery email
* @return recoveryDataHash The keccak256 hash of the recovery data
*/
function parseRecoveryDataHash(
uint256 templateIdx,
bytes[] memory commandParams
) external view returns (bytes32) {
if (templateIdx != 0 || commandParams.length != 3) {
revert InvalidCommandParams();
}
address accountInEmail = abi.decode(commandParams[0], (address));
address moduleInEmail = abi.decode(commandParams[1], (address));
address newOwnerInEmail = abi.decode(commandParams[2], (address));
bytes memory recoveryCalldata = abi.encode(
moduleInEmail,
newOwnerInEmail
);
return keccak256(abi.encode(accountInEmail, recoveryCalldata));
}
}
Loading