Skip to content

Commit

Permalink
Merge pull request #23 from MetaMask/v1.2.0
Browse files Browse the repository at this point in the history
V1.2.0
  • Loading branch information
McOso authored Sep 30, 2024
2 parents 4bae93c + 07acb01 commit 486c0d2
Show file tree
Hide file tree
Showing 42 changed files with 1,356 additions and 350 deletions.
9 changes: 6 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@
[submodule "lib/solidity-stringutils"]
path = lib/solidity-stringutils
url = https://github.com/Arachnid/solidity-stringutils
[submodule "lib/FreshCryptoLib"]
path = lib/FreshCryptoLib
url = https://github.com/rdubois-crypto/FreshCryptoLib
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
Expand All @@ -19,3 +16,9 @@
[submodule "lib/erc7579-implementation"]
path = lib/erc7579-implementation
url = https://github.com/erc7579/erc7579-implementation
[submodule "lib/SCL"]
path = lib/SCL
url = https://github.com/get-smooth/crypto-lib
[submodule "lib/FCL"]
path = lib/FCL
url = https://github.com/rdubois-crypto/FreshCryptoLib
10 changes: 9 additions & 1 deletion documents/CaveatEnforcers.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,15 @@

> NOTE: Each `CaveatEnforcer` is called by the `DelegationManager` contract. This is important when storing data in the `CaveatEnforcer`, as `msg.sender` will always be the address of the `DelegationManager`.
> NOTE: There is no guarantee that the action will be executed. Keep this in mind when designing Caveat Enforcers. If your logic depends on the action being performed, ensure you use the afterHook method to validate any expected state changes.
> NOTE: There is no guarantee that the action will be executed. Keep this in mind when designing Caveat Enforcers. If your logic depends on the action being performed, ensure you use the afterHook and afterAllHook methods to validate any expected state changes.
The execution order of the caveat hooks may vary depending on the delegation manager implementation, but they are designed to be used in the following sequence:

1. `beforeAllHook`: Called for all delegations before any executions begin, proceeding from the leaf delegation to the root delegation.
2. `beforeHook`: Called before each individual execution tied to a delegation, also proceeding from the leaf delegation to the root delegation.
3. Execution: The specified execution is performed.
4. `afterHook`: Called after each individual execution tied to a delegation, proceeding from the root delegation back to the leaf delegation.
5. `afterAllHook`: Called for all delegations after all executions have been processed, proceeding from the root delegation back to the leaf delegation.

## Enforcer Details

Expand Down
4 changes: 4 additions & 0 deletions documents/DeleGatorCore.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ There are two methods available to upgrade your account to a different implement
- `upgradeToAndCall` - This method is exposed by the UUPSUpgradable contract and upgrades the account to a different implementation defaulting to clearing the storage associated to the account.
- `upgradeToAndCallAndRetainStorage` - This method upgrades the account to a different implementation and retains the storage associated to the account.

# Signing a UserOperation

Contracts that extend the DeleGatorCore contract MUST use [EIP712](https://eips.ethereum.org/EIPS/eip-712) typed data signatures for User Operations to provide legibility when signing. The typed data to be signed is a [PackedUserOperation](https://github.com/eth-infinitism/account-abstraction/blob/releases/v0.7/contracts/interfaces/PackedUserOperation.sol).

## Rules

- DeleGator Implementations MUST use namespaced storage.
Expand Down
8 changes: 5 additions & 3 deletions documents/DelegationManager.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,11 @@ Our `DelegationManager` implementation:
3. Validates the signatures of offchain delegations.
4. Checks if any of the delegations being redeemed are disabled
5. Ensures each delegation has sufficient authority to execute, given by the previous delegation or by being a root delegation
6. Calls `beforeHook` for all delegations (from leaf to root delegation)
7. Executes the `Execution` provided
8. Calls `afterHook` for all delegations (from root to leaf delegation)
6. Calls `beforeAllHook` for all delegations before processing any of the executions (from leaf to root delegation)
7. Calls `beforeHook` before each individual execution tied to a delegation (from leaf to root delegation)
8. Performs the `Execution` provided
9. Calls `afterHook` after each individual execution tied to a delegation (from root to leaf delegation)
10. Calls `afterAllHook` for all delegations before processing all the executions (from root to leaf delegation)

> NOTE: Ensure to double check that the delegation is valid before submitting a UserOp. A delegation can be revoked or a signature can be invalidated at any time.
> Validate a delegation redemption by either simulating the transaction or by reading the storage on our implementation `disabledDelegations(delegationHash)`.
Expand Down
1 change: 1 addition & 0 deletions lib/FCL
Submodule FCL added at 8179e0
1 change: 0 additions & 1 deletion lib/FreshCryptoLib
Submodule FreshCryptoLib deleted from d9bb3b
1 change: 1 addition & 0 deletions lib/SCL
Submodule SCL added at 6c3d76
5 changes: 3 additions & 2 deletions remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ ds-test/=lib/forge-std/lib/ds-test/src/
forge-std/=lib/forge-std/src/
@solidity-stringutils/=lib/solidity-stringutils/src/
@bytes-utils/=lib/solidity-bytes-utils/contracts/
@freshCryptoLib/=lib/FreshCryptoLib/solidity/src/
@erc7579/=lib/erc7579-implementation/src/
@FCL/=lib/FCL/solidity/src/
@erc7579/=lib/erc7579-implementation/src/
@SCL=lib/SCL/src
19 changes: 17 additions & 2 deletions script/DeployEnvironmentSetUp.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import { IDelegationManager } from "../src/interfaces/IDelegationManager.sol";

import { AllowedCalldataEnforcer } from "../src/enforcers/AllowedCalldataEnforcer.sol";
import { AllowedMethodsEnforcer } from "../src/enforcers/AllowedMethodsEnforcer.sol";
import { NativeTokenTransferAmountEnforcer } from "../src/enforcers/NativeTokenTransferAmountEnforcer.sol";
import { AllowedTargetsEnforcer } from "../src/enforcers/AllowedTargetsEnforcer.sol";
import { NativeTokenTransferAmountEnforcer } from "../src/enforcers/NativeTokenTransferAmountEnforcer.sol";
import { BlockNumberEnforcer } from "../src/enforcers/BlockNumberEnforcer.sol";
import { DeployedEnforcer } from "../src/enforcers/DeployedEnforcer.sol";
import { ERC20BalanceGteEnforcer } from "../src/enforcers/ERC20BalanceGteEnforcer.sol";
Expand All @@ -23,9 +23,13 @@ import { LimitedCallsEnforcer } from "../src/enforcers/LimitedCallsEnforcer.sol"
import { NonceEnforcer } from "../src/enforcers/NonceEnforcer.sol";
import { TimestampEnforcer } from "../src/enforcers/TimestampEnforcer.sol";
import { ValueLteEnforcer } from "../src/enforcers/ValueLteEnforcer.sol";
import { RedeemerEnforcer } from "../src/enforcers/RedeemerEnforcer.sol";
import { NativeBalanceGteEnforcer } from "../src/enforcers/NativeBalanceGteEnforcer.sol";
import { NativeTokenPaymentEnforcer } from "../src/enforcers/NativeTokenPaymentEnforcer.sol";
import { ArgsEqualityCheckEnforcer } from "../src/enforcers/ArgsEqualityCheckEnforcer.sol";
import { RedeemerEnforcer } from "../src/enforcers/RedeemerEnforcer.sol";
import { ERC721BalanceGteEnforcer } from "../src/enforcers/ERC721BalanceGteEnforcer.sol";
import { ERC1155BalanceGteEnforcer } from "../src/enforcers/ERC1155BalanceGteEnforcer.sol";

/**
* @title DeployEnvironmentSetUp
Expand Down Expand Up @@ -60,11 +64,13 @@ contract DeployEnvironmentSetUp is Script {
// Deploy Delegation Environment Contracts
address delegationManager = address(new DelegationManager{ salt: salt }(deployer));
console2.log("DelegationManager: %s", address(delegationManager));

deployedAddress = address(new MultiSigDeleGator{ salt: salt }(IDelegationManager(delegationManager), entryPoint));
console2.log("MultiSigDeleGatorImpl: %s", deployedAddress);

deployedAddress = address(new HybridDeleGator{ salt: salt }(IDelegationManager(delegationManager), entryPoint));
console2.log("HybridDeleGatorImpl: %s", address(deployedAddress));
console2.log("HybridDeleGatorImpl: %s", deployedAddress);

console2.log("~~~");

// Caveat Enforcers
Expand Down Expand Up @@ -104,6 +110,9 @@ contract DeployEnvironmentSetUp is Script {
deployedAddress = address(new ValueLteEnforcer{ salt: salt }());
console2.log("ValueLteEnforcer: %s", deployedAddress);

deployedAddress = address(new RedeemerEnforcer{ salt: salt }());
console2.log("RedeemerEnforcer: %s", deployedAddress);

deployedAddress = address(new NativeTokenTransferAmountEnforcer{ salt: salt }());
console2.log("NativeTokenTransferAmountEnforcer: %s", deployedAddress);

Expand All @@ -117,6 +126,12 @@ contract DeployEnvironmentSetUp is Script {
address(new NativeTokenPaymentEnforcer{ salt: salt }(IDelegationManager(delegationManager), deployedAddress));
console2.log("NativeTokenPaymentEnforcer: %s", deployedAddress);

deployedAddress = address(new ERC721BalanceGteEnforcer{ salt: salt }());
console2.log("ERC721BalanceGteEnforcer: %s", deployedAddress);

deployedAddress = address(new ERC1155BalanceGteEnforcer{ salt: salt }());
console2.log("ERC1155BalanceGteEnforcer: %s", deployedAddress);

vm.stopBroadcast();
}
}
70 changes: 61 additions & 9 deletions src/DeleGatorCore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ pragma solidity 0.8.23;
import { IEntryPoint } from "@account-abstraction/interfaces/IEntryPoint.sol";
import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import { IERC1271 } from "@openzeppelin/contracts/interfaces/IERC1271.sol";
import { MessageHashUtils } from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
import { UUPSUpgradeable } from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
import { IERC1155Receiver } from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol";
import { IERC721Receiver } from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
Expand All @@ -13,6 +12,8 @@ import { ERC1967Utils } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils
import { ModeLib } from "@erc7579/lib/ModeLib.sol";
import { ExecutionLib } from "@erc7579/lib/ExecutionLib.sol";
import { ExecutionHelper } from "@erc7579/core/ExecutionHelper.sol";
import { EIP712 } from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import { MessageHashUtils } from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";

import { ERC1271Lib } from "./libraries/ERC1271Lib.sol";
import { IDeleGatorCore } from "./interfaces/IDeleGatorCore.sol";
Expand All @@ -34,9 +35,9 @@ abstract contract DeleGatorCore is
IERC165,
IDeleGatorCore,
IERC721Receiver,
IERC1155Receiver
IERC1155Receiver,
EIP712
{
using MessageHashUtils for bytes32;
using ModeLib for ModeCode;
using ExecutionLib for bytes;

Expand All @@ -48,6 +49,11 @@ abstract contract DeleGatorCore is
/// @dev The EntryPoint contract that has root access to this contract
IEntryPoint public immutable entryPoint;

/// @dev The typehash for the PackedUserOperation struct
bytes32 public constant PACKED_USER_OP_TYPEHASH = keccak256(
"PackedUserOperation(address sender,uint256 nonce,bytes initCode,bytes callData,bytes32 accountGasLimits,uint256 preVerificationGas,bytes32 gasFees,bytes paymasterAndData)"
);

////////////////////////////// Events //////////////////////////////

/// @dev Emitted when the Delegation manager is set
Expand Down Expand Up @@ -117,8 +123,18 @@ abstract contract DeleGatorCore is
* @notice Initializes the DeleGatorCore contract
* @custom:oz-upgrades-unsafe-allow constructor
* @param _delegationManager the address of the trusted DelegationManager contract that will have root access to this contract
* @param _entryPoint the address of entry point
* @param _name Name of the contract
* @param _domainVersion Domain version of the contract
*/
constructor(IDelegationManager _delegationManager, IEntryPoint _entryPoint) {
constructor(
IDelegationManager _delegationManager,
IEntryPoint _entryPoint,
string memory _name,
string memory _domainVersion
)
EIP712(_name, _domainVersion)
{
_disableInitializers();
delegationManager = _delegationManager;
entryPoint = _entryPoint;
Expand Down Expand Up @@ -245,21 +261,20 @@ abstract contract DeleGatorCore is
* @notice Validates a UserOp signature and sends any necessary funds to the EntryPoint
* @dev Related: ERC4337
* @param _userOp The UserOp struct to validate
* @param _userOpHash The hash of the UserOp struct
* @param _missingAccountFunds The missing funds from the account
* @return validationData_ The validation data
*/
function validateUserOp(
PackedUserOperation calldata _userOp,
bytes32 _userOpHash,
bytes32, // Ignore UserOpHash from the Entry Point
uint256 _missingAccountFunds
)
external
onlyEntryPoint
onlyProxy
returns (uint256 validationData_)
{
validationData_ = _validateUserOpSignature(_userOp, _userOpHash);
validationData_ = _validateUserOpSignature(_userOp, getPackedUserOperationTypedDataHash(_userOp));
_payPrefund(_missingAccountFunds);
}

Expand Down Expand Up @@ -435,6 +450,23 @@ abstract contract DeleGatorCore is
return entryPoint.getNonce(address(this), _key);
}

/**
* @notice This method returns the domain hash used for signing typed data
* @return bytes32 The domain hash
*/
function getDomainHash() external view returns (bytes32) {
return _domainSeparatorV4();
}

/**
* @notice Returns the formatted hash to sign for an EIP712 typed data signature
* @param _userOp the UserOp to hash
* @notice Returns an EIP712 typed data hash for a given UserOp
*/
function getPackedUserOperationTypedDataHash(PackedUserOperation calldata _userOp) public view returns (bytes32) {
return MessageHashUtils.toTypedDataHash(_domainSeparatorV4(), getPackedUserOperationHash(_userOp));
}

/**
* @inheritdoc IERC165
* @dev Supports the following interfaces: IDeleGatorCore, IERC721Receiver, IERC1155Receiver, IERC165, IERC1271
Expand All @@ -445,6 +477,26 @@ abstract contract DeleGatorCore is
|| _interfaceId == type(IERC1271).interfaceId;
}

/**
* Provides the typed data hash for a PackedUserOperation
* @param _userOp the PackedUserOperation to hash
*/
function getPackedUserOperationHash(PackedUserOperation calldata _userOp) public pure returns (bytes32) {
return keccak256(
abi.encode(
PACKED_USER_OP_TYPEHASH,
_userOp.sender,
_userOp.nonce,
keccak256(_userOp.initCode),
keccak256(_userOp.callData),
_userOp.accountGasLimits,
_userOp.preVerificationGas,
_userOp.gasFees,
keccak256(_userOp.paymasterAndData)
)
);
}

////////////////////////////// Internal Methods //////////////////////////////

/**
Expand Down Expand Up @@ -479,7 +531,7 @@ abstract contract DeleGatorCore is
* @dev Returns 0 if the signature is valid, 1 if the signature is invalid.
* @dev Related: ERC4337
* @param _userOp The UserOp
* @param _userOpHash The hash of the UserOp
* @param _userOpHash UserOp hash produced with typed data
* @return validationData_ A code indicating if the signature is valid or not
*/
function _validateUserOpSignature(
Expand All @@ -490,7 +542,7 @@ abstract contract DeleGatorCore is
view
returns (uint256 validationData_)
{
bytes4 result_ = _isValidSignature(_userOpHash.toEthSignedMessageHash(), _userOp.signature);
bytes4 result_ = _isValidSignature(_userOpHash, _userOp.signature);
if (result_ == ERC1271Lib.EIP1271_MAGIC_VALUE) {
return 0;
} else {
Expand Down
Loading

0 comments on commit 486c0d2

Please sign in to comment.