From 1ef48034a40b040b23b9b77690e9e8ede0e30238 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 4 Mar 2025 21:29:48 -0800 Subject: [PATCH 1/3] Add simple readable terms system --- src/SafeDelegationManager.sol | 78 +++++++++++++++++++++++++++++++++++ src/utils/Types.sol | 35 ++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 src/SafeDelegationManager.sol diff --git a/src/SafeDelegationManager.sol b/src/SafeDelegationManager.sol new file mode 100644 index 00000000..6470681d --- /dev/null +++ b/src/SafeDelegationManager.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: MIT AND Apache-2.0 +pragma solidity 0.8.23; + +import { DelegationManager } from "./DelegationManager.sol"; +import { Parameter, ReadableTerm } from "./utils/Types.sol"; + +contract SafeDelegationManager is DelegationManager { + constructor(address _owner) DelegationManager(_owner) {} + + mapping(string => address) public termsToEnforcer; + + /** + * @dev Decodes readable terms into standard delegation format + */ + modifier _decodeReadableTerms(bytes[] calldata _permissionContexts) { + uint256 batchSize_ = _permissionContexts.length; + bytes[] memory rewrittenContexts_ = new bytes[](batchSize_); + + for (uint256 batchIndex_; batchIndex_ < batchSize_; ++batchIndex_) { + // Try to decode as readable format + try abi.decode(_permissionContexts[batchIndex_], (ReadableDelegation[])) returns (ReadableDelegation[] memory readableDelegations_) { + if (readableDelegations_.length == 0) { + rewrittenContexts_[batchIndex_] = _permissionContexts[batchIndex_]; + continue; + } + + // Convert to standard delegations + Delegation[] memory standardDelegations_ = new Delegation[](readableDelegations_.length); + + for (uint256 delegationIndex_; delegationIndex_ < readableDelegations_.length; ++delegationIndex_) { + ReadableDelegation memory readableDelegation_ = readableDelegations_[delegationIndex_]; + + // Convert readable terms to caveats + Caveat[] memory caveats_ = new Caveat[](readableDelegation_.readableTerms.length); + + for (uint256 termIndex_; termIndex_ < readableDelegation_.readableTerms.length; ++termIndex_) { + ReadableTerm memory term_ = readableDelegation_.readableTerms[termIndex_]; + address enforcer_ = termsToEnforcer[term_.permissionName]; + if (enforcer_ == address(0)) revert("Unknown permission type"); + + caveats_[termIndex_] = Caveat({ + enforcer: enforcer_, + terms: term_.terms, + args: term_.args + }); + } + + standardDelegations_[delegationIndex_] = Delegation({ + delegate: readableDelegation_.delegate, + delegator: readableDelegation_.delegator, + authority: readableDelegation_.authority, + caveats: caveats_, + salt: readableDelegation_.salt, + signature: readableDelegation_.signature + }); + } + + rewrittenContexts_[batchIndex_] = abi.encode(standardDelegations_); + } catch { + // If decoding as ReadableDelegation fails, assume it's already a standard Delegation + rewrittenContexts_[batchIndex_] = _permissionContexts[batchIndex_]; + } + } + + _; + } + + /** + * @inheritdoc DelegationManager + */ + function redeemDelegations( + bytes[] calldata _permissionContexts, + ModeCode[] calldata _modes, + bytes[] calldata _executionCallDatas + ) external override whenNotPaused _decodeReadableTerms { + super.redeemDelegations(_permissionContexts, _modes, _executionCallDatas); + } +} diff --git a/src/utils/Types.sol b/src/utils/Types.sol index 50bff062..999d9457 100644 --- a/src/utils/Types.sol +++ b/src/utils/Types.sol @@ -20,7 +20,42 @@ struct EIP712Domain { * @title Delegation * @notice Struct representing a delegation to give a delegate authority to act on behalf of a delegator. * @dev `signature` is ignored during delegation hashing so it can be manipulated post signing. + * @dev Can be represented in a human-readable format using ReadableDelegation and ReadableTerm. */ + +/** + * @title ReadableDelegation + * @notice Human-readable version of a Delegation that uses string permission names and parameters. + */ +struct ReadableDelegation { + address delegate; + address delegator; + bytes32 authority; + ReadableTerm[] readableTerms; + uint256 salt; + bytes signature; +} + +/** + * @title ReadableTerm + * @notice Human-readable version of a Caveat that uses string permission names and parameters. + */ +struct ReadableTerm { + string permissionName; + Parameter[] parameters; + bytes args; +} + +/** + * @title Parameter + * @notice Name-value pair for readable delegation parameters. + */ +struct Parameter { + string name; + string value; +} + +/** struct Delegation { address delegate; address delegator; From 170e759a0ee4d8a13cc337e07daa8bff5b21d164 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 4 Mar 2025 21:35:35 -0800 Subject: [PATCH 2/3] Add term decoding as an optional method defined on each caveat enforcer --- src/SafeDelegationManager.sol | 13 +++++++------ src/enforcers/CaveatEnforcer.sol | 3 +++ src/interfaces/ICaveatEnforcer.sol | 9 +++++++++ 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/SafeDelegationManager.sol b/src/SafeDelegationManager.sol index 6470681d..88cfe209 100644 --- a/src/SafeDelegationManager.sol +++ b/src/SafeDelegationManager.sol @@ -32,17 +32,18 @@ contract SafeDelegationManager is DelegationManager { // Convert readable terms to caveats Caveat[] memory caveats_ = new Caveat[](readableDelegation_.readableTerms.length); - for (uint256 termIndex_; termIndex_ < readableDelegation_.readableTerms.length; ++termIndex_) { ReadableTerm memory term_ = readableDelegation_.readableTerms[termIndex_]; address enforcer_ = termsToEnforcer[term_.permissionName]; if (enforcer_ == address(0)) revert("Unknown permission type"); - caveats_[termIndex_] = Caveat({ - enforcer: enforcer_, - terms: term_.terms, - args: term_.args - }); + // Convert readable terms to caveats using enforcer's conversion method + ReadableTerm[] memory singleTerm_ = new ReadableTerm[](1); + singleTerm_[0] = term_; + Caveat[] memory convertedCaveats_ = ICaveatEnforcer(enforcer_)._convertReadableTermsToCaveats(singleTerm_); + + // Take first caveat since we only passed one term + caveats_[termIndex_] = convertedCaveats_[0]; } standardDelegations_[delegationIndex_] = Delegation({ diff --git a/src/enforcers/CaveatEnforcer.sol b/src/enforcers/CaveatEnforcer.sol index 66bdace5..8372f92f 100644 --- a/src/enforcers/CaveatEnforcer.sol +++ b/src/enforcers/CaveatEnforcer.sol @@ -26,6 +26,9 @@ abstract contract CaveatEnforcer is ICaveatEnforcer { /// @inheritdoc ICaveatEnforcer function afterAllHook(bytes calldata, bytes calldata, ModeCode, bytes calldata, bytes32, address, address) public virtual { } + /// @inheritdoc ICaveatEnforcer + function _convertReadableTermsToCaveats(ReadableTerm[] memory _readableTerms) internal view virtual returns (Caveat[] memory) { } + /** * @dev Require the function call to be in single execution mode */ diff --git a/src/interfaces/ICaveatEnforcer.sol b/src/interfaces/ICaveatEnforcer.sol index 326f9f5c..c4c3eafe 100644 --- a/src/interfaces/ICaveatEnforcer.sol +++ b/src/interfaces/ICaveatEnforcer.sol @@ -1,4 +1,6 @@ // SPDX-License-Identifier: MIT AND Apache-2.0 + +import { ReadableTerm, Caveat } from "../utils/Types.sol"; pragma solidity 0.8.23; import { ModeCode } from "../utils/Types.sol"; @@ -100,4 +102,11 @@ interface ICaveatEnforcer { address _redeemer ) external; + + /** + * @notice Converts readable terms into caveats + * @param _readableTerms Array of readable terms to convert + * @return Array of converted caveats + */ + function _convertReadableTermsToCaveats(ReadableTerm[] memory _readableTerms) internal view virtual returns (Caveat[] memory); } From 6484982c2ad0ff2a20d4d65790ec09aac045e7c6 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 4 Mar 2025 21:40:47 -0800 Subject: [PATCH 3/3] Disallow empty terms --- src/SafeDelegationManager.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/SafeDelegationManager.sol b/src/SafeDelegationManager.sol index 88cfe209..4c2e3a68 100644 --- a/src/SafeDelegationManager.sol +++ b/src/SafeDelegationManager.sol @@ -30,6 +30,10 @@ contract SafeDelegationManager is DelegationManager { for (uint256 delegationIndex_; delegationIndex_ < readableDelegations_.length; ++delegationIndex_) { ReadableDelegation memory readableDelegation_ = readableDelegations_[delegationIndex_]; + if (readableDelegation_.readableTerms.length == 0) { + revert("This safe delegation manager does not currently permit unconditional delegations"); + } + // Convert readable terms to caveats Caveat[] memory caveats_ = new Caveat[](readableDelegation_.readableTerms.length); for (uint256 termIndex_; termIndex_ < readableDelegation_.readableTerms.length; ++termIndex_) {