Skip to content

Commit

Permalink
Address merge conflicts
Browse files Browse the repository at this point in the history
  • Loading branch information
dylandesrosier committed Aug 19, 2024
2 parents 7092408 + dc9bc0f commit 32c9b90
Show file tree
Hide file tree
Showing 70 changed files with 3,823 additions and 2,709 deletions.
395 changes: 0 additions & 395 deletions .gas-snapshot

This file was deleted.

3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@
[submodule "lib/account-abstraction"]
path = lib/account-abstraction
url = https://github.com/eth-infinitism/account-abstraction
[submodule "lib/erc7579-implementation"]
path = lib/erc7579-implementation
url = https://github.com/erc7579/erc7579-implementation
21 changes: 7 additions & 14 deletions documents/DelegationManager.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,21 @@ A Delegation Manager is responsible for validating delegations and triggering th

## Rules

- A Delegation Manager MUST implement `redeemDelegation` interface as specified `function redeemDelegation(bytes calldata _data, Action calldata _action) external;`.
- A Delegation Manager MUST implement `redeemDelegation` interface as specified `function redeemDelegation(bytes[] calldata _permissionContexts, Action[] calldata _actions) external;`.

## Delegations

Users can allow other contracts or EOAs to invoke an Action directly from their DeleGator Smart Account through a Delegation. There are 2 flows for delegating: onchain and offchain. Both flows require creating a Delegation and sharing some additional data offchain to the delegate for them to be able to redeem the Delegation.
Users can allow other contracts or EOAs to invoke an action directly from their DeleGator Smart Account through a delegation. Creating a delegation requires specifying a delegate and some optional data such caveats (more detail below) and an authority. Delegations are stored offchain and helper utilities that assist with the delegation lifecycle are provided in the SDK.

> NOTE: onchain Delegations are validated at the time of creation, not at the time of execution. This means if anything regarding a DeleGator’s control scheme is changed, the onchain Delegation is not impacted. Contrast this with an offchain Delegation, which is validated at the time of execution.
> NOTE: Delegations are validated at execution time, so the signature may become invalid if the conditions of a valid signature change.
>
> Example: Alice delegates to Bob the ability to spend her USDC with a 1 of 1 MultiSig DeleGator Account. She then updates her DeleGator Account to a 2 of 3 MultiSig.
>
> - If she delegated onchain, Bob is still able to spend her USDC.
> - If she delegated offchain, the signature will no longer be valid and Bob is not able to spend her USDC.
> Example: Alice delegates to Bob the ability to spend her USDC with a 1 of 1 MultiSig DeleGator Account. She then updates her DeleGator Account to a 2 of 3 MultiSig. When Bob redeems the delegation from Alice, it will fail since the signed delegation is no longer valid.
# MetaMask's Delegation Manager

## Creating a Delegation

Users can create a `Delegation` and provide it to a delegate in the form of an onchain delegation or an offchain delegation.

### Onchain Delegations

Onchain Delegations are done through calling the `delegate` method on a DeleGator or DelegationManager. This validates the delegation at this time and the redeemer only needs the `Delegation` to redeem it (no signature needed).
Users can create a `Delegation` and provide it to a delegate in the form of an offchain delegation.

### Offchain Delegations

Expand All @@ -41,10 +34,10 @@ Open delegations are delegations that don't have a strict `delegate`. By setting

Our `DelegationManager` implementation:

1. `redeemDelegation` consumes a list of delegations (`Delegation[]`) and an `Action` to be executed
1. `redeemDelegation` consumes an array of bytes with the encoded delegation chains (`Delegation[]`) for executing each of the `Action`.
> NOTE: Delegations are ordered from leaf to root. The last delegation in the array must have the root authority.
2. Validates the `msg.sender` calling `redeemDelegation` is allowed to do so
3. Validates the signatures of offchain delegations and that onchain delegations have already been verified
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)
Expand Down
1 change: 1 addition & 0 deletions lib/erc7579-implementation
Submodule erc7579-implementation added at 42aa53
3 changes: 2 additions & 1 deletion remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ 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
@freshCryptoLib/=lib/FreshCryptoLib/solidity/src/
@erc7579/=lib/erc7579-implementation/src/
18 changes: 18 additions & 0 deletions script/DeployEnvironmentSetUp.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ 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 { BlockNumberEnforcer } from "../src/enforcers/BlockNumberEnforcer.sol";
import { DeployedEnforcer } from "../src/enforcers/DeployedEnforcer.sol";
Expand All @@ -22,6 +23,9 @@ 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 { NativeBalanceGteEnforcer } from "../src/enforcers/NativeBalanceGteEnforcer.sol";
import { NativeTokenPaymentEnforcer } from "../src/enforcers/NativeTokenPaymentEnforcer.sol";
import { ArgsEqualityCheckEnforcer } from "../src/enforcers/ArgsEqualityCheckEnforcer.sol";

/**
* @title DeployEnvironmentSetUp
Expand Down Expand Up @@ -101,6 +105,20 @@ contract DeployEnvironmentSetUp is Script {
address valueLteEnfocer = address(new ValueLteEnforcer{ salt: salt }());
console2.log("ValueLteEnforcer: %s", address(valueLteEnfocer));

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

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

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

address nativeTokenPaymentEnforcer = address(
new NativeTokenPaymentEnforcer{ salt: salt }(IDelegationManager(delegationManager), address(argsEqualityCheckEnforcer))
);
console2.log("NativeTokenPaymentEnforcer: %s", address(nativeTokenPaymentEnforcer));

vm.stopBroadcast();
}
}
177 changes: 133 additions & 44 deletions src/DeleGatorCore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@ import { IERC1155Receiver } from "@openzeppelin/contracts/token/ERC1155/IERC1155
import { IERC721Receiver } from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import { ERC1967Utils } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";
import { ModeLib } from "@erc7579/lib/ModeLib.sol";
import { ExecutionLib } from "@erc7579/lib/ExecutionLib.sol";
import { ExecutionHelper } from "@erc7579/core/ExecutionHelper.sol";

import { ERC1271Lib } from "./libraries/ERC1271Lib.sol";
import { IDeleGatorCore } from "./interfaces/IDeleGatorCore.sol";
import { IDelegationManager } from "./interfaces/IDelegationManager.sol";
import { Action, Delegation, PackedUserOperation } from "./utils/Types.sol";
import { ExecutionLib } from "./libraries/ExecutionLib.sol";
import { CallType, ExecType, Execution, Delegation, PackedUserOperation, ModeCode } from "./utils/Types.sol";
import { CALLTYPE_SINGLE, CALLTYPE_BATCH, EXECTYPE_DEFAULT, EXECTYPE_TRY } from "./utils/Constants.sol";

/**
* @title DeleGatorCore
Expand All @@ -24,8 +27,18 @@ import { ExecutionLib } from "./libraries/ExecutionLib.sol";
* @dev DeleGator implementations can inherit this to enable Delegation, ERC4337 and UUPS.
* @dev DeleGator implementations MUST use Namespaced Storage to ensure subsequent UUPS implementation updates are safe.
*/
abstract contract DeleGatorCore is Initializable, UUPSUpgradeable, IERC165, IDeleGatorCore, IERC721Receiver, IERC1155Receiver {
abstract contract DeleGatorCore is
Initializable,
ExecutionHelper,
UUPSUpgradeable,
IERC165,
IDeleGatorCore,
IERC721Receiver,
IERC1155Receiver
{
using MessageHashUtils for bytes32;
using ModeLib for ModeCode;
using ExecutionLib for bytes;

////////////////////////////// State //////////////////////////////

Expand All @@ -46,6 +59,9 @@ abstract contract DeleGatorCore is Initializable, UUPSUpgradeable, IERC165, IDel
/// @dev Emitted when the storage is cleared
event ClearedStorage();

/// @dev Event emitted when prefunding is sent.
event SentPrefund(address indexed sender, uint256 amount, bool success);

////////////////////////////// Errors //////////////////////////////

/// @dev Error thrown when the caller is not this contract.
Expand All @@ -60,6 +76,12 @@ abstract contract DeleGatorCore is Initializable, UUPSUpgradeable, IERC165, IDel
/// @dev Error thrown when the caller is not the delegation manager.
error NotDelegationManager();

/// @dev Error thrown when an execution with an unsupported CallType was made
error UnsupportedCallType(CallType callType);

/// @dev Error thrown when an execution with an unsupported ExecType was made
error UnsupportedExecType(ExecType execType);

////////////////////////////// Modifiers //////////////////////////////

/**
Expand Down Expand Up @@ -112,40 +134,111 @@ abstract contract DeleGatorCore is Initializable, UUPSUpgradeable, IERC165, IDel
receive() external payable { }

/**
* @notice Redeems a delegation on the DelegationManager and executes the action as the root delegator
* @dev `_data` is made up of an array of `Delegation` structs that are used to validate the authority given to execute the
* action
* on the root delegator ordered from leaf to root.
* @param _data the data used to validate the authority given to execute the action
* @param _action The action to be executed
* @notice Redeems a delegation on the DelegationManager and executes the specified executions on behalf of the root delegator.
* @param _permissionContexts An array of bytes where each element is made up of an array
* of `Delegation` structs that are used to validate the authority given to execute the corresponding execution on the
* root delegator, ordered from leaf to root.
* @param _modes An array of `ModeCode` structs representing the mode of execiton for each execution callData.
* @param _executionCallDatas An array of `Execution` structs representing the executions to be executed.
*/
function redeemDelegation(bytes calldata _data, Action calldata _action) external onlyEntryPointOrSelf {
delegationManager.redeemDelegation(_data, _action);
function redeemDelegations(
bytes[] calldata _permissionContexts,
ModeCode[] calldata _modes,
bytes[] calldata _executionCallDatas
)
external
onlyEntryPointOrSelf
{
delegationManager.redeemDelegations(_permissionContexts, _modes, _executionCallDatas);
}

/// @inheritdoc IDeleGatorCore
function executeDelegatedAction(Action calldata _action) external onlyDelegationManager {
ExecutionLib._execute(_action);
/**
* @notice Executes an Execution from this contract
* @dev This method is intended to be called through a UserOp which ensures the invoker has sufficient permissions
* @dev This convenience method defeaults to reverting on failure and a single execution.
* @param _execution The Execution to be executed
*/
function execute(Execution calldata _execution) external payable onlyEntryPoint {
_execute(_execution.target, _execution.value, _execution.callData);
}

/**
* @notice Executes an Action from this contract
* @notice Executes an Execution from this contract
* @dev Related: @erc7579/MSAAdvanced.sol
* @dev This method is intended to be called through a UserOp which ensures the invoker has sufficient permissions
* @dev This method reverts if the action fails.
* @param _action the action to execute
* @param _mode The ModeCode for the execution
* @param _executionCalldata The calldata for the execution
*/
function execute(Action calldata _action) external onlyEntryPoint {
ExecutionLib._execute(_action);
function execute(ModeCode _mode, bytes calldata _executionCalldata) external payable onlyEntryPoint {
(CallType callType_, ExecType execType_,,) = _mode.decode();

// Check if calltype is batch or single
if (callType_ == CALLTYPE_BATCH) {
// destructure executionCallData according to batched exec
Execution[] calldata executions_ = _executionCalldata.decodeBatch();
// Check if execType is revert or try
if (execType_ == EXECTYPE_DEFAULT) _execute(executions_);
else if (execType_ == EXECTYPE_TRY) _tryExecute(executions_);
else revert UnsupportedExecType(execType_);
} else if (callType_ == CALLTYPE_SINGLE) {
// Destructure executionCallData according to single exec
(address target_, uint256 value_, bytes calldata callData_) = _executionCalldata.decodeSingle();
// Check if execType is revert or try
if (execType_ == EXECTYPE_DEFAULT) {
_execute(target_, value_, callData_);
} else if (execType_ == EXECTYPE_TRY) {
bytes[] memory returnData_ = new bytes[](1);
bool success_;
(success_, returnData_[0]) = _tryExecute(target_, value_, callData_);
if (!success_) emit TryExecuteUnsuccessful(0, returnData_[0]);
} else {
revert UnsupportedExecType(execType_);
}
} else {
revert UnsupportedCallType(callType_);
}
}

/**
* @notice This method executes several Actions in order.
* @dev This method is intended to be called through a UserOp which ensures the invoker has sufficient permissions.
* @dev This method reverts if any of the actions fail.
* @param _actions the ordered actions to execute
* @inheritdoc IDeleGatorCore
* @dev Related: @erc7579/MSAAdvanced.sol
*/
function executeBatch(Action[] calldata _actions) external onlyEntryPointOrSelf {
ExecutionLib._executeBatch(_actions);
function executeFromExecutor(
ModeCode _mode,
bytes calldata _executionCalldata
)
external
payable
onlyDelegationManager
returns (bytes[] memory returnData_)
{
(CallType callType_, ExecType execType_,,) = _mode.decode();

// Check if calltype is batch or single
if (callType_ == CALLTYPE_BATCH) {
// Destructure executionCallData according to batched exec
Execution[] calldata executions_ = _executionCalldata.decodeBatch();
// check if execType is revert or try
if (execType_ == EXECTYPE_DEFAULT) returnData_ = _execute(executions_);
else if (execType_ == EXECTYPE_TRY) returnData_ = _tryExecute(executions_);
else revert UnsupportedExecType(execType_);
} else if (callType_ == CALLTYPE_SINGLE) {
// Destructure executionCallData according to single exec
(address target_, uint256 value_, bytes calldata callData_) = _executionCalldata.decodeSingle();
returnData_ = new bytes[](1);
bool success_;
// check if execType is revert or try
if (execType_ == EXECTYPE_DEFAULT) {
returnData_[0] = _execute(target_, value_, callData_);
} else if (execType_ == EXECTYPE_TRY) {
(success_, returnData_[0]) = _tryExecute(target_, value_, callData_);
if (!success_) emit TryExecuteUnsuccessful(0, returnData_[0]);
} else {
revert UnsupportedExecType(execType_);
}
} else {
revert UnsupportedCallType(callType_);
}
}

/**
Expand All @@ -167,7 +260,7 @@ abstract contract DeleGatorCore is Initializable, UUPSUpgradeable, IERC165, IDel
returns (uint256 validationData_)
{
validationData_ = _validateUserOpSignature(_userOp, _userOpHash);
ExecutionLib._payPrefund(_missingAccountFunds);
_payPrefund(_missingAccountFunds);
}

/**
Expand Down Expand Up @@ -249,15 +342,6 @@ abstract contract DeleGatorCore is Initializable, UUPSUpgradeable, IERC165, IDel
entryPoint.withdrawTo(_withdrawAddress, _withdrawAmount);
}

/**
* @notice Delegates authority to an address and caches the delegation hash onchain
* @dev Forwards a call to the DelegationManager to delegate
* @param _delegation The delegation to be stored
*/
function delegate(Delegation calldata _delegation) external onlyEntryPointOrSelf {
delegationManager.delegate(_delegation);
}

/**
* @notice Disables a delegation from being used
* @param _delegation The delegation to be disabled
Expand Down Expand Up @@ -305,15 +389,6 @@ abstract contract DeleGatorCore is Initializable, UUPSUpgradeable, IERC165, IDel
return delegationManager.disabledDelegations(_delegationHash);
}

/**
* @notice Checks if the delegation hash has been cached onchain
* @param _delegationHash the hash of the delegation to check for
* @return bool is the delegation stored onchain
*/
function isDelegationOnchain(bytes32 _delegationHash) external view returns (bool) {
return delegationManager.onchainDelegations(_delegationHash);
}

/**
* @notice Gets the current account's deposit in the entry point
* @dev Related: ERC4337
Expand Down Expand Up @@ -422,4 +497,18 @@ abstract contract DeleGatorCore is Initializable, UUPSUpgradeable, IERC165, IDel
return 1;
}
}

/**
* @notice Sends the entrypoint (msg.sender) any needed funds for the transaction.
* @param _missingAccountFunds the minimum value this method should send the entrypoint.
* this value MAY be zero, in case there is enough deposit, or the userOp has a paymaster.
*/
function _payPrefund(uint256 _missingAccountFunds) internal {
if (_missingAccountFunds != 0) {
(bool success_,) = payable(msg.sender).call{ value: _missingAccountFunds, gas: type(uint256).max }("");
(success_);
// Ignore failure (it's EntryPoint's job to verify, not account.)
emit SentPrefund(msg.sender, _missingAccountFunds, success_);
}
}
}
Loading

0 comments on commit 32c9b90

Please sign in to comment.