Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
mmv08 committed Jun 15, 2022
1 parent ccdd94b commit c010e16
Show file tree
Hide file tree
Showing 11 changed files with 302 additions and 135 deletions.
69 changes: 51 additions & 18 deletions contracts/SafeEIP4337Diatomic.sol
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.7.0 <0.9.0;
pragma solidity >=0.8.0 <0.9.0;

import "@gnosis.pm/safe-contracts/contracts/GnosisSafe.sol";
import "@gnosis.pm/safe-contracts/contracts/handler/HandlerContext.sol";
import "./UserOperation.sol";
import "./interfaces/Safe.sol";
import "hardhat/console.sol";

/// ERRORS ///

/// @notice Thrown when `validateUserOp` wasn't called through the Safe
error InvalidCaller();

/// @notice Thrown when userOp suggests a mismatching nonce
error InvalidNonce(uint256 actual, uint256 expected);
error InvalidNonce(uint256 proposed, uint256 expected);

/// @notice Thrown when the prefund quoted by the entrypoint is larger than one defined in the userOp
error InvalidPrefund();
Expand All @@ -22,16 +23,17 @@ error InvalidTransaction();
/// @notice Thrown when the transaction from the operation reverts
error ExecutionFailure();

/// @notice Thrown when a method is called with an invalid opcode. For example, calling a method
/// that should be called via DELEGATECALL with CALL
error InvalidCallOpcode();

/// @title SafeEIP4337Diatomic
/// @author Mikhail Mikheev - <[email protected]>
/// @author Mikhail Mikheev - @mikhailxyz
/// @notice Diatomic implementation of EIP-4337 for the Gnosis Safe, consisting of a module and a fallback handler
contract SafeEIP4337Diatomic is HandlerContext {
using UserOperationLib for UserOperation;

enum Operation {
Call,
DelegateCall
}
address private immutable diatomicAddress;

bytes32 private constant DOMAIN_SEPARATOR_TYPEHASH = keccak256("EIP712Domain(uint256 chainId,address verifyingContract)");

Expand All @@ -40,10 +42,13 @@ contract SafeEIP4337Diatomic is HandlerContext {
"SafeOp(address safe,bytes callData,uint256 nonce,uint256 verificationGas,uint256 preVerificationGas,uint256 maxFeePerGas,uint256 maxPriorityFeePerGas,uint256 callGas,address entryPoint)"
);

bytes32 private constant TRANSACTION_TO_EXECUTE_SLOT = keccak256("eip4337diatomic.transaction_hash_to_execute");

mapping(address => uint256) public safeNonces;
// A mapping to keep track of transactions hashes ready to be executed, a transaction is marked as such
// if `validateUserOp` passes.
mapping(address => bytes32) public transactionsReadyToExecute;

constructor() {
diatomicAddress = address(this);
}

/// @dev Validates user operation provided by the entry point
/// @param userOp User operation struct
Expand All @@ -70,10 +75,16 @@ contract SafeEIP4337Diatomic is HandlerContext {
_validateSignatures(entryPoint, userOp);

bytes32 intermediateTxHash = getIntermediateTransactionHash(userOp.callData, safeNonce, entryPoint, block.chainid);
transactionsReadyToExecute[safeAddress] = intermediateTxHash;

Safe(safeAddress).execTransactionFromModule(
address(this),
0,
abi.encodeWithSelector(this.setTransactionToExecute.selector, intermediateTxHash),
1
);

if (requiredPrefund != 0) {
GnosisSafe(safeAddress).execTransactionFromModule(entryPoint, requiredPrefund, "", Enum.Operation.Call);
Safe(safeAddress).execTransactionFromModule(entryPoint, requiredPrefund, "", 0);
}
}

Expand All @@ -87,7 +98,7 @@ contract SafeEIP4337Diatomic is HandlerContext {
address to,
uint256 value,
bytes calldata data,
Enum.Operation operation
uint8 operation
) external payable returns (bool success) {
// we need to strip out msg.sender address appended by HandlerContext contract from the calldata
bytes memory callData;
Expand All @@ -110,13 +121,23 @@ contract SafeEIP4337Diatomic is HandlerContext {
address entryPoint = _msgSender();
// `validateUserOp` increased the nonce, so we need to use nonce - 1 for hash calculation
uint256 safeNonce = safeNonces[safeAddress] - 1;
if (transactionsReadyToExecute[safeAddress] != getIntermediateTransactionHash(callData, safeNonce, entryPoint, block.chainid)) {

Safe safe = Safe(safeAddress);
if (
bytes32(safe.getStorageAt(uint256(TRANSACTION_TO_EXECUTE_SLOT), 32)) !=
getIntermediateTransactionHash(callData, safeNonce, entryPoint, block.chainid)
) {
revert InvalidTransaction();
}

transactionsReadyToExecute[safeAddress] = 0;
safe.execTransactionFromModule(
address(this),
0,
abi.encodeWithSelector(this.setTransactionToExecute.selector, TRANSACTION_TO_EXECUTE_SLOT, bytes32(0)),
1
);

success = GnosisSafe(safeAddress).execTransactionFromModule(to, value, data, operation);
success = safe.execTransactionFromModule(to, value, data, operation);
if (!success) revert ExecutionFailure();
}

Expand Down Expand Up @@ -234,6 +255,18 @@ contract SafeEIP4337Diatomic is HandlerContext {
);
bytes32 operationHash = keccak256(operationData);

GnosisSafe(payable(userOp.sender)).checkSignatures(operationHash, operationData, userOp.signature);
Safe(payable(userOp.sender)).checkSignatures(operationHash, operationData, userOp.signature);
}

function setTransactionToExecute(bytes32 txHash) public {
if (address(this) == diatomicAddress) {
revert InvalidCallOpcode();
}

bytes32 slot = TRANSACTION_TO_EXECUTE_SLOT;
// solhint-disable-next-line no-inline-assembly
assembly {
sstore(slot, txHash)
}
}
}
38 changes: 38 additions & 0 deletions contracts/interfaces/Safe.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.7.0 <0.9.0;

interface Safe {
/**
* @dev Reads `length` bytes of storage in the currents contract
* @param offset - the offset in the current contract's storage in words to start reading from
* @param length - the number of words (32 bytes) of data to read
* @return the bytes that were read.
*/
function getStorageAt(uint256 offset, uint256 length) external view returns (bytes memory);

/**
* @dev Allows a Module to execute a Safe transaction without any further confirmations.
* @param to Destination address of module transaction.
* @param value Ether value of module transaction.
* @param data Data payload of module transaction.
* @param operation Operation type of module transaction.
*/
function execTransactionFromModule(
address to,
uint256 value,
bytes memory data,
uint8 operation
) external returns (bool success);

/**
* @dev Checks whether the signature provided is valid for the provided data, hash. Will revert otherwise.
* @param dataHash Hash of the data (could be either a message hash or transaction hash)
* @param data That should be signed (this is passed to an external validator contract)
* @param signatures Signature data that should be verified. Can be ECDSA signature, contract signature (EIP-1271) or approved hash.
*/
function checkSignatures(
bytes32 dataHash,
bytes memory data,
bytes memory signatures
) external view;
}
15 changes: 15 additions & 0 deletions contracts/test/DiatomicExposed.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.8.0 <0.9.0;

import "../SafeEIP4337Diatomic.sol";

contract DiatomicExposed is SafeEIP4337Diatomic {
function exposedGetIntermediateTransactionHash(
bytes memory callData,
uint256 nonce,
address entryPoint,
uint256 chainId
) public pure returns (bytes32) {
return getIntermediateTransactionHash(callData, nonce, entryPoint, chainId);
}
}
12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,14 @@
},
"devDependencies": {
"@nomiclabs/hardhat-ethers": "^2.0.6",
"@nomiclabs/hardhat-etherscan": "^3.0.4",
"@nomiclabs/hardhat-etherscan": "^3.1.0",
"@nomiclabs/hardhat-waffle": "^2.0.3",
"@types/chai": "^4.3.1",
"@types/mocha": "^9.1.1",
"@types/node": "^17.0.41",
"@types/node": "^17.0.42",
"@types/yargs": "^17.0.10",
"@typescript-eslint/eslint-plugin": "^5.27.1",
"@typescript-eslint/parser": "^5.27.1",
"@typescript-eslint/eslint-plugin": "^5.28.0",
"@typescript-eslint/parser": "^5.28.0",
"argv": "^0.0.2",
"chai": "^4.2.0",
"debug": "^4.3.4",
Expand All @@ -63,10 +63,10 @@
"eslint-plugin-prettier": "^4.0.0",
"ethereum-waffle": "^3.4.4",
"ethers": "^5.6.8",
"hardhat": "2.9.7",
"hardhat": "2.9.9",
"hardhat-deploy": "0.11.10",
"husky": "^8.0.1",
"prettier": "^2.6.2",
"prettier": "^2.7.0",
"prettier-plugin-solidity": "^1.0.0-beta.19",
"solc": "0.8.14-fixed",
"solhint": "^3.3.7",
Expand Down
8 changes: 4 additions & 4 deletions test/scripts/runOpGoerli.ts → scripts/runOpGoerli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import {
getSafeAtAddress,
getStorageSetterAtAddress,
getTestStorageSetter,
} from '../utils/setup'
import { buildSignatureBytes, signHash } from '../../src/utils/execution'
} from '../test/utils/setup'
import { buildSignatureBytes, signHash } from '../src/utils/execution'
import {
buildSafeUserOp,
buildSafeUserOpContractCall,
Expand All @@ -20,9 +20,9 @@ import {
calculateIntermediateTxHash,
buildSafeUserOpTransaction,
getSupportedEntryPoints,
} from '../../src/utils/userOp'
} from '../src/utils/userOp'
import { parseEther } from '@ethersproject/units'
import { chainId } from '../utils/encoding'
import { chainId } from '../test/utils/encoding'

const MNEMONIC = process.env.GOERLI_SCRIPT_MNEMONIC
const SAFE_ADDRESS = process.env.GOERLI_SCRIPT_SAFE_ADDRESS
Expand Down
2 changes: 2 additions & 0 deletions src/utils/execution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { Contract, Wallet, utils, BigNumber, BigNumberish, Signer, PopulatedTran
import { TypedDataSigner } from '@ethersproject/abstract-signer'
import { AddressZero } from '@ethersproject/constants'

export const TRANSACTION_TO_EXECUTE_SLOT = utils.keccak256(utils.toUtf8Bytes('eip4337diatomic.transaction_hash_to_execute'))

export const EIP_DOMAIN = {
EIP712Domain: [
{ type: 'uint256', name: 'chainId' },
Expand Down
4 changes: 2 additions & 2 deletions src/utils/userOp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,8 @@ export const getRequiredPrefund = (userOp: UserOperation): string => {
return BigNumber.from(getRequiredGas(userOp)).mul(BigNumber.from(userOp.maxFeePerGas)).toString()
}

export const calculateIntermediateTxHash = (userOp: UserOperation, entryPoint: string, chainId: BigNumberish): string => {
return ethersUtils.solidityKeccak256(['bytes', 'uint256', 'address', 'uint256'], [userOp.callData, userOp.nonce, entryPoint, chainId])
export const calculateIntermediateTxHash = (callData: string, nonce: BigNumberish, entryPoint: string, chainId: BigNumberish): string => {
return ethersUtils.solidityKeccak256(['bytes', 'uint256', 'address', 'uint256'], [callData, nonce, entryPoint, chainId])
}

export const getSupportedEntryPoints = async (provider: ethers.providers.JsonRpcProvider): Promise<string[]> => {
Expand Down
Loading

0 comments on commit c010e16

Please sign in to comment.