From a46e39f33822e7d68b3550247e61c0bb93985f63 Mon Sep 17 00:00:00 2001 From: Jeff Smale <6363749+jeffsmale90@users.noreply.github.com> Date: Wed, 22 Apr 2026 14:43:41 +1200 Subject: [PATCH 1/9] Add ERC20AllowanceRevocationEnforcer --- .../ERC20AllowanceRevocationEnforcer.sol | 71 +++++++ .../ERC20AllowanceRevocationEnforcer.t.sol | 197 ++++++++++++++++++ 2 files changed, 268 insertions(+) create mode 100644 src/enforcers/ERC20AllowanceRevocationEnforcer.sol create mode 100644 test/enforcers/ERC20AllowanceRevocationEnforcer.t.sol diff --git a/src/enforcers/ERC20AllowanceRevocationEnforcer.sol b/src/enforcers/ERC20AllowanceRevocationEnforcer.sol new file mode 100644 index 00000000..d0857769 --- /dev/null +++ b/src/enforcers/ERC20AllowanceRevocationEnforcer.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: MIT AND Apache-2.0 +pragma solidity 0.8.23; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { ExecutionLib } from "@erc7579/lib/ExecutionLib.sol"; + +import { CaveatEnforcer } from "./CaveatEnforcer.sol"; +import { ModeCode } from "../utils/Types.sol"; + +/** + * @title ERC20AllowanceRevocationEnforcer + * @notice Allows the delegate to revoke an existing ERC-20 allowance on behalf of the delegator. + * + * @dev The execution must: + * - transfer zero native value, + * - call `approve(address spender, uint256 amount)` on the execution target, + * - set `amount` to zero, and + * - target a contract on which `allowance(delegator, spender)` currently returns a non-zero value. + * + * The allowance precondition guarantees the call is strictly a revocation of an existing allowance rather than a + * new grant or a no-op against an already-zero allowance. + * + * @dev This enforcer does not consume any terms. + * @dev Operates only in single call type and default execution mode. + */ +contract ERC20AllowanceRevocationEnforcer is CaveatEnforcer { + using ExecutionLib for bytes; + + /// @dev Calldata length of `approve(address,uint256)`: 4-byte selector + two 32-byte words. + uint256 private constant _APPROVE_CALLDATA_LENGTH = 68; + + ////////////////////////////// Public Methods ////////////////////////////// + + /** + * @notice Requires the execution to revoke an existing ERC-20 allowance owned by `_delegator`. + * @param _mode Must be single call type and default execution mode. + * @param _executionCallData Single execution targeting the ERC-20 token contract. + * @param _delegator The delegator, treated as the allowance `owner`. + */ + function beforeHook( + bytes calldata, + bytes calldata, + ModeCode _mode, + bytes calldata _executionCallData, + bytes32, + address _delegator, + address + ) + public + view + override + onlySingleCallTypeMode(_mode) + onlyDefaultExecutionMode(_mode) + { + (address target_, uint256 value_, bytes calldata callData_) = _executionCallData.decodeSingle(); + + require(value_ == 0, "ERC20AllowanceRevocationEnforcer:invalid-value"); + require(callData_.length == _APPROVE_CALLDATA_LENGTH, "ERC20AllowanceRevocationEnforcer:invalid-execution-length"); + require(bytes4(callData_[0:4]) == IERC20.approve.selector, "ERC20AllowanceRevocationEnforcer:invalid-method"); + require(uint256(bytes32(callData_[36:68])) == 0, "ERC20AllowanceRevocationEnforcer:non-zero-amount"); + + address spender_ = address(uint160(uint256(bytes32(callData_[4:36])))); + + (bool success_, bytes memory returnData_) = + target_.staticcall(abi.encodeWithSelector(IERC20.allowance.selector, _delegator, spender_)); + require( + success_ && returnData_.length >= 32, "ERC20AllowanceRevocationEnforcer:allowance-call-failed" + ); + require(abi.decode(returnData_, (uint256)) != 0, "ERC20AllowanceRevocationEnforcer:no-allowance-to-revoke"); + } +} diff --git a/test/enforcers/ERC20AllowanceRevocationEnforcer.t.sol b/test/enforcers/ERC20AllowanceRevocationEnforcer.t.sol new file mode 100644 index 00000000..c8901cb7 --- /dev/null +++ b/test/enforcers/ERC20AllowanceRevocationEnforcer.t.sol @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: MIT AND Apache-2.0 +pragma solidity 0.8.23; + +import "forge-std/Test.sol"; +import { ExecutionLib } from "@erc7579/lib/ExecutionLib.sol"; + +import { Execution, Caveat, Delegation, ModeCode } from "../../src/utils/Types.sol"; +import { CaveatEnforcerBaseTest } from "./CaveatEnforcerBaseTest.t.sol"; +import { BasicERC20, IERC20 } from "../utils/BasicERC20.t.sol"; +import { EncoderLib } from "../../src/libraries/EncoderLib.sol"; +import { ICaveatEnforcer } from "../../src/interfaces/ICaveatEnforcer.sol"; +import { ERC20AllowanceRevocationEnforcer } from "../../src/enforcers/ERC20AllowanceRevocationEnforcer.sol"; + +/** + * @title ERC20AllowanceRevocationEnforcer Test + */ +contract ERC20AllowanceRevocationEnforcerTest is CaveatEnforcerBaseTest { + ////////////////////////////// State ////////////////////////////// + + ERC20AllowanceRevocationEnforcer public enforcer; + BasicERC20 public token; + + address public delegator; + address public spender; + + ////////////////////////////// Set up ////////////////////////////// + + function setUp() public override { + super.setUp(); + enforcer = new ERC20AllowanceRevocationEnforcer(); + vm.label(address(enforcer), "ERC20AllowanceRevocationEnforcer"); + + delegator = address(users.alice.deleGator); + spender = address(users.bob.deleGator); + + token = new BasicERC20(delegator, "TestToken", "TT", 100 ether); + vm.label(address(token), "BasicERC20"); + + vm.prank(delegator); + token.approve(spender, 42 ether); + } + + ////////////////////////////// Helpers ////////////////////////////// + + function _approveCallData(address _spender, uint256 _amount) internal pure returns (bytes memory) { + return abi.encodeWithSelector(IERC20.approve.selector, _spender, _amount); + } + + function _encodeSingle(address _target, uint256 _value, bytes memory _callData) internal pure returns (bytes memory) { + return ExecutionLib.encodeSingle(_target, _value, _callData); + } + + function _callBeforeHook(bytes memory _executionCallData) internal { + vm.prank(address(delegationManager)); + enforcer.beforeHook(hex"", hex"", singleDefaultMode, _executionCallData, bytes32(0), delegator, address(0)); + } + + ////////////////////////////// Valid cases ////////////////////////////// + + // should SUCCEED when revoking an existing allowance + function test_revokeSucceedsForExistingAllowance() public { + bytes memory executionCallData_ = _encodeSingle(address(token), 0, _approveCallData(spender, 0)); + _callBeforeHook(executionCallData_); + } + + // should SUCCEED for any non-zero allowance amount (1 wei) + function test_revokeSucceedsForMinimalAllowance() public { + address otherSpender_ = address(users.carol.deleGator); + vm.prank(delegator); + token.approve(otherSpender_, 1); + + bytes memory executionCallData_ = _encodeSingle(address(token), 0, _approveCallData(otherSpender_, 0)); + _callBeforeHook(executionCallData_); + } + + ////////////////////////////// Invalid cases ////////////////////////////// + + // should FAIL when the execution transfers native value + function test_revertOnNonZeroValue() public { + bytes memory executionCallData_ = _encodeSingle(address(token), 1, _approveCallData(spender, 0)); + vm.prank(address(delegationManager)); + vm.expectRevert("ERC20AllowanceRevocationEnforcer:invalid-value"); + enforcer.beforeHook(hex"", hex"", singleDefaultMode, executionCallData_, bytes32(0), delegator, address(0)); + } + + // should FAIL when the calldata length differs from approve(address,uint256) + function test_revertOnInvalidExecutionLength() public { + bytes memory shortCallData_ = abi.encodePacked(IERC20.approve.selector, bytes32(uint256(uint160(spender)))); + bytes memory executionCallData_ = _encodeSingle(address(token), 0, shortCallData_); + vm.prank(address(delegationManager)); + vm.expectRevert("ERC20AllowanceRevocationEnforcer:invalid-execution-length"); + enforcer.beforeHook(hex"", hex"", singleDefaultMode, executionCallData_, bytes32(0), delegator, address(0)); + } + + // should FAIL when the calldata length is longer than expected (extra trailing bytes) + function test_revertOnInvalidExecutionLengthTooLong() public { + bytes memory longCallData_ = abi.encodePacked(_approveCallData(spender, 0), bytes1(0x00)); + bytes memory executionCallData_ = _encodeSingle(address(token), 0, longCallData_); + vm.prank(address(delegationManager)); + vm.expectRevert("ERC20AllowanceRevocationEnforcer:invalid-execution-length"); + enforcer.beforeHook(hex"", hex"", singleDefaultMode, executionCallData_, bytes32(0), delegator, address(0)); + } + + // should FAIL when the selector is not approve(address,uint256) + function test_revertOnInvalidMethod() public { + bytes memory wrongMethodCallData_ = abi.encodeWithSelector(IERC20.transfer.selector, spender, uint256(0)); + bytes memory executionCallData_ = _encodeSingle(address(token), 0, wrongMethodCallData_); + vm.prank(address(delegationManager)); + vm.expectRevert("ERC20AllowanceRevocationEnforcer:invalid-method"); + enforcer.beforeHook(hex"", hex"", singleDefaultMode, executionCallData_, bytes32(0), delegator, address(0)); + } + + // should FAIL when the approved amount is non-zero (i.e. not a revocation) + function test_revertOnNonZeroAmount() public { + bytes memory executionCallData_ = _encodeSingle(address(token), 0, _approveCallData(spender, 1)); + vm.prank(address(delegationManager)); + vm.expectRevert("ERC20AllowanceRevocationEnforcer:non-zero-amount"); + enforcer.beforeHook(hex"", hex"", singleDefaultMode, executionCallData_, bytes32(0), delegator, address(0)); + } + + // should FAIL when the current allowance is already zero (nothing to revoke) + function test_revertWhenNoAllowanceToRevoke() public { + address otherSpender_ = address(users.dave.deleGator); + assertEq(token.allowance(delegator, otherSpender_), 0); + + bytes memory executionCallData_ = _encodeSingle(address(token), 0, _approveCallData(otherSpender_, 0)); + vm.prank(address(delegationManager)); + vm.expectRevert("ERC20AllowanceRevocationEnforcer:no-allowance-to-revoke"); + enforcer.beforeHook(hex"", hex"", singleDefaultMode, executionCallData_, bytes32(0), delegator, address(0)); + } + + // should FAIL when the execution target does not implement allowance(address,address) + function test_revertWhenAllowanceCallFails() public { + // A contract address with code but no allowance() implementation — the enforcer contract itself. + address noAllowanceTarget_ = address(enforcer); + bytes memory executionCallData_ = _encodeSingle(noAllowanceTarget_, 0, _approveCallData(spender, 0)); + vm.prank(address(delegationManager)); + vm.expectRevert("ERC20AllowanceRevocationEnforcer:allowance-call-failed"); + enforcer.beforeHook(hex"", hex"", singleDefaultMode, executionCallData_, bytes32(0), delegator, address(0)); + } + + // should FAIL when the execution target is an EOA (no code) + function test_revertWhenTargetHasNoCode() public { + address eoa_ = address(0xBEEF); + require(eoa_.code.length == 0); + bytes memory executionCallData_ = _encodeSingle(eoa_, 0, _approveCallData(spender, 0)); + vm.prank(address(delegationManager)); + vm.expectRevert("ERC20AllowanceRevocationEnforcer:allowance-call-failed"); + enforcer.beforeHook(hex"", hex"", singleDefaultMode, executionCallData_, bytes32(0), delegator, address(0)); + } + + // should FAIL with invalid call type mode (batch instead of single) + function test_revertWithInvalidCallTypeMode() public { + bytes memory executionCallData_ = ExecutionLib.encodeBatch(new Execution[](2)); + vm.expectRevert("CaveatEnforcer:invalid-call-type"); + enforcer.beforeHook(hex"", hex"", batchDefaultMode, executionCallData_, bytes32(0), delegator, address(0)); + } + + // should FAIL with invalid execution mode (try instead of default) + function test_revertWithInvalidExecutionMode() public { + vm.expectRevert("CaveatEnforcer:invalid-execution-type"); + enforcer.beforeHook(hex"", hex"", singleTryMode, hex"", bytes32(0), delegator, address(0)); + } + + ////////////////////////////// Integration ////////////////////////////// + + // should revoke an existing allowance through the full delegation redemption path + function test_revokeIntegration() public { + assertEq(token.allowance(delegator, spender), 42 ether); + + Execution memory execution_ = + Execution({ target: address(token), value: 0, callData: _approveCallData(spender, 0) }); + + Caveat[] memory caveats_ = new Caveat[](1); + caveats_[0] = Caveat({ args: hex"", enforcer: address(enforcer), terms: hex"" }); + Delegation memory delegation_ = Delegation({ + delegate: address(users.bob.deleGator), + delegator: address(users.alice.deleGator), + authority: ROOT_AUTHORITY, + caveats: caveats_, + salt: 0, + signature: hex"" + }); + delegation_ = signDelegation(users.alice, delegation_); + + Delegation[] memory delegations_ = new Delegation[](1); + delegations_[0] = delegation_; + + invokeDelegation_UserOp(users.bob, delegations_, execution_); + + assertEq(token.allowance(delegator, spender), 0); + } + + function _getEnforcer() internal view override returns (ICaveatEnforcer) { + return ICaveatEnforcer(address(enforcer)); + } +} From e8b202f21487a585afbfd3aebaa0e980476df65c Mon Sep 17 00:00:00 2001 From: Jeff Smale <6363749+jeffsmale90@users.noreply.github.com> Date: Wed, 22 Apr 2026 14:44:26 +1200 Subject: [PATCH 2/9] Pivot to move flexible AllowanceRevocationEnforcer --- src/enforcers/AllowanceRevocationEnforcer.sol | 136 ++++++++ .../ERC20AllowanceRevocationEnforcer.sol | 71 ---- .../AllowanceRevocationEnforcer.t.sol | 305 ++++++++++++++++++ .../ERC20AllowanceRevocationEnforcer.t.sol | 197 ----------- 4 files changed, 441 insertions(+), 268 deletions(-) create mode 100644 src/enforcers/AllowanceRevocationEnforcer.sol delete mode 100644 src/enforcers/ERC20AllowanceRevocationEnforcer.sol create mode 100644 test/enforcers/AllowanceRevocationEnforcer.t.sol delete mode 100644 test/enforcers/ERC20AllowanceRevocationEnforcer.t.sol diff --git a/src/enforcers/AllowanceRevocationEnforcer.sol b/src/enforcers/AllowanceRevocationEnforcer.sol new file mode 100644 index 00000000..6476a9a1 --- /dev/null +++ b/src/enforcers/AllowanceRevocationEnforcer.sol @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: MIT AND Apache-2.0 +pragma solidity 0.8.23; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import { ExecutionLib } from "@erc7579/lib/ExecutionLib.sol"; + +import { CaveatEnforcer } from "./CaveatEnforcer.sol"; +import { ModeCode } from "../utils/Types.sol"; + +/** + * @title AllowanceRevocationEnforcer + * @notice Allows the delegate to revoke an existing token approval on behalf of the delegator. Supports ERC-20 + * `approve`, ERC-721 per-token `approve`, and ERC-721/ERC-1155 `setApprovalForAll`. + * + * @dev The execution must transfer zero native value and carry one of the supported approval calldatas (length 68 + * bytes: 4-byte selector + two 32-byte words). Branching is determined as follows: + * - selector `setApprovalForAll(address operator, bool approved)`: + * - `approved` MUST be false, and + * - `isApprovedForAll(delegator, operator)` MUST currently be true. + * - selector `approve(address, uint256)` (shared by ERC-20 and ERC-721): + * - if the first parameter is `address(0)` the call is treated as an ERC-721 per-token revocation: + * - `getApproved(tokenId)` on the target MUST currently return a non-zero address. + * - otherwise the call is treated as an ERC-20 allowance revocation: + * - the second parameter (amount) MUST be zero, and + * - `allowance(delegator, spender)` on the target MUST currently return non-zero. + * + * The pre-existing approval check guarantees the call is strictly a revocation of an existing approval rather than + * a new grant or a no-op. + * + * @dev This enforcer does not consume any terms. + * @dev Operates only in single call type and default execution mode. + */ +contract AllowanceRevocationEnforcer is CaveatEnforcer { + using ExecutionLib for bytes; + + /// @dev Calldata length of `approve(address,uint256)` and `setApprovalForAll(address,bool)`: + /// 4-byte selector + two 32-byte words. + uint256 private constant _APPROVAL_CALLDATA_LENGTH = 68; + + ////////////////////////////// Public Methods ////////////////////////////// + + /** + * @notice Requires the execution to revoke an existing token approval owned by `_delegator`. + * @param _mode Must be single call type and default execution mode. + * @param _executionCallData Single execution targeting the token contract. + * @param _delegator The delegator, treated as the approval `owner`. + */ + function beforeHook( + bytes calldata, + bytes calldata, + ModeCode _mode, + bytes calldata _executionCallData, + bytes32, + address _delegator, + address + ) + public + view + override + onlySingleCallTypeMode(_mode) + onlyDefaultExecutionMode(_mode) + { + (address target_, uint256 value_, bytes calldata callData_) = _executionCallData.decodeSingle(); + + require(value_ == 0, "AllowanceRevocationEnforcer:invalid-value"); + require(callData_.length == _APPROVAL_CALLDATA_LENGTH, "AllowanceRevocationEnforcer:invalid-execution-length"); + + bytes4 selector_ = bytes4(callData_[0:4]); + if (selector_ == IERC721.setApprovalForAll.selector) { + _enforceSetApprovalForAllRevocation(target_, callData_, _delegator); + return; + } + if (selector_ == IERC20.approve.selector) { + address firstParam_ = address(uint160(uint256(bytes32(callData_[4:36])))); + if (firstParam_ == address(0)) { + _enforceErc721ApproveRevocation(target_, callData_); + } else { + _enforceErc20ApproveRevocation(target_, callData_, _delegator, firstParam_); + } + return; + } + revert("AllowanceRevocationEnforcer:invalid-method"); + } + + ////////////////////////////// Internal Methods ////////////////////////////// + + /** + * @dev Validates an ERC-20 `approve(spender, 0)` revocation. Requires `allowance(delegator, spender) > 0` on the + * target. + */ + function _enforceErc20ApproveRevocation( + address _target, + bytes calldata _callData, + address _delegator, + address _spender + ) + private + view + { + require(uint256(bytes32(_callData[36:68])) == 0, "AllowanceRevocationEnforcer:non-zero-amount"); + + (bool success_, bytes memory returnData_) = + _target.staticcall(abi.encodeWithSelector(IERC20.allowance.selector, _delegator, _spender)); + require(success_ && returnData_.length >= 32, "AllowanceRevocationEnforcer:allowance-call-failed"); + require(abi.decode(returnData_, (uint256)) != 0, "AllowanceRevocationEnforcer:no-allowance-to-revoke"); + } + + /** + * @dev Validates an ERC-721 `approve(address(0), tokenId)` revocation. Requires `getApproved(tokenId)` on the + * target to be non-zero (i.e. an approval is currently set). + */ + function _enforceErc721ApproveRevocation(address _target, bytes calldata _callData) private view { + uint256 tokenId_ = uint256(bytes32(_callData[36:68])); + + (bool success_, bytes memory returnData_) = + _target.staticcall(abi.encodeWithSelector(IERC721.getApproved.selector, tokenId_)); + require(success_ && returnData_.length >= 32, "AllowanceRevocationEnforcer:getApproved-call-failed"); + require(abi.decode(returnData_, (address)) != address(0), "AllowanceRevocationEnforcer:no-approval-to-revoke"); + } + + /** + * @dev Validates a `setApprovalForAll(operator, false)` revocation. Requires `isApprovedForAll(delegator, + * operator)` on the target to currently be true. + */ + function _enforceSetApprovalForAllRevocation(address _target, bytes calldata _callData, address _delegator) private view { + address operator_ = address(uint160(uint256(bytes32(_callData[4:36])))); + bool approved_ = uint256(bytes32(_callData[36:68])) != 0; + require(!approved_, "AllowanceRevocationEnforcer:not-a-revocation"); + + (bool success_, bytes memory returnData_) = + _target.staticcall(abi.encodeWithSelector(IERC721.isApprovedForAll.selector, _delegator, operator_)); + require(success_ && returnData_.length >= 32, "AllowanceRevocationEnforcer:isApprovedForAll-call-failed"); + require(abi.decode(returnData_, (bool)), "AllowanceRevocationEnforcer:no-approval-to-revoke"); + } +} diff --git a/src/enforcers/ERC20AllowanceRevocationEnforcer.sol b/src/enforcers/ERC20AllowanceRevocationEnforcer.sol deleted file mode 100644 index d0857769..00000000 --- a/src/enforcers/ERC20AllowanceRevocationEnforcer.sol +++ /dev/null @@ -1,71 +0,0 @@ -// SPDX-License-Identifier: MIT AND Apache-2.0 -pragma solidity 0.8.23; - -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { ExecutionLib } from "@erc7579/lib/ExecutionLib.sol"; - -import { CaveatEnforcer } from "./CaveatEnforcer.sol"; -import { ModeCode } from "../utils/Types.sol"; - -/** - * @title ERC20AllowanceRevocationEnforcer - * @notice Allows the delegate to revoke an existing ERC-20 allowance on behalf of the delegator. - * - * @dev The execution must: - * - transfer zero native value, - * - call `approve(address spender, uint256 amount)` on the execution target, - * - set `amount` to zero, and - * - target a contract on which `allowance(delegator, spender)` currently returns a non-zero value. - * - * The allowance precondition guarantees the call is strictly a revocation of an existing allowance rather than a - * new grant or a no-op against an already-zero allowance. - * - * @dev This enforcer does not consume any terms. - * @dev Operates only in single call type and default execution mode. - */ -contract ERC20AllowanceRevocationEnforcer is CaveatEnforcer { - using ExecutionLib for bytes; - - /// @dev Calldata length of `approve(address,uint256)`: 4-byte selector + two 32-byte words. - uint256 private constant _APPROVE_CALLDATA_LENGTH = 68; - - ////////////////////////////// Public Methods ////////////////////////////// - - /** - * @notice Requires the execution to revoke an existing ERC-20 allowance owned by `_delegator`. - * @param _mode Must be single call type and default execution mode. - * @param _executionCallData Single execution targeting the ERC-20 token contract. - * @param _delegator The delegator, treated as the allowance `owner`. - */ - function beforeHook( - bytes calldata, - bytes calldata, - ModeCode _mode, - bytes calldata _executionCallData, - bytes32, - address _delegator, - address - ) - public - view - override - onlySingleCallTypeMode(_mode) - onlyDefaultExecutionMode(_mode) - { - (address target_, uint256 value_, bytes calldata callData_) = _executionCallData.decodeSingle(); - - require(value_ == 0, "ERC20AllowanceRevocationEnforcer:invalid-value"); - require(callData_.length == _APPROVE_CALLDATA_LENGTH, "ERC20AllowanceRevocationEnforcer:invalid-execution-length"); - require(bytes4(callData_[0:4]) == IERC20.approve.selector, "ERC20AllowanceRevocationEnforcer:invalid-method"); - require(uint256(bytes32(callData_[36:68])) == 0, "ERC20AllowanceRevocationEnforcer:non-zero-amount"); - - address spender_ = address(uint160(uint256(bytes32(callData_[4:36])))); - - (bool success_, bytes memory returnData_) = - target_.staticcall(abi.encodeWithSelector(IERC20.allowance.selector, _delegator, spender_)); - require( - success_ && returnData_.length >= 32, "ERC20AllowanceRevocationEnforcer:allowance-call-failed" - ); - require(abi.decode(returnData_, (uint256)) != 0, "ERC20AllowanceRevocationEnforcer:no-allowance-to-revoke"); - } -} diff --git a/test/enforcers/AllowanceRevocationEnforcer.t.sol b/test/enforcers/AllowanceRevocationEnforcer.t.sol new file mode 100644 index 00000000..999db261 --- /dev/null +++ b/test/enforcers/AllowanceRevocationEnforcer.t.sol @@ -0,0 +1,305 @@ +// SPDX-License-Identifier: MIT AND Apache-2.0 +pragma solidity 0.8.23; + +import "forge-std/Test.sol"; +import { ExecutionLib } from "@erc7579/lib/ExecutionLib.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import { IERC1155 } from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; + +import { Execution, Caveat, Delegation, ModeCode } from "../../src/utils/Types.sol"; +import { CaveatEnforcerBaseTest } from "./CaveatEnforcerBaseTest.t.sol"; +import { BasicERC20 } from "../utils/BasicERC20.t.sol"; +import { BasicCF721 } from "../utils/BasicCF721.t.sol"; +import { BasicERC1155 } from "../utils/BasicERC1155.t.sol"; +import { ICaveatEnforcer } from "../../src/interfaces/ICaveatEnforcer.sol"; +import { AllowanceRevocationEnforcer } from "../../src/enforcers/AllowanceRevocationEnforcer.sol"; + +/** + * @title AllowanceRevocationEnforcer Test + */ +contract AllowanceRevocationEnforcerTest is CaveatEnforcerBaseTest { + ////////////////////////////// State ////////////////////////////// + + AllowanceRevocationEnforcer public enforcer; + BasicERC20 public erc20; + BasicCF721 public erc721; + BasicERC1155 public erc1155; + + address public delegator; + address public spender; + address public operator; + + uint256 public mintedTokenId; + + ////////////////////////////// Set up ////////////////////////////// + + function setUp() public override { + super.setUp(); + enforcer = new AllowanceRevocationEnforcer(); + vm.label(address(enforcer), "AllowanceRevocationEnforcer"); + + delegator = address(users.alice.deleGator); + spender = address(users.bob.deleGator); + operator = address(users.carol.deleGator); + + erc20 = new BasicERC20(delegator, "TestToken", "TT", 100 ether); + erc721 = new BasicCF721(delegator, "TestNFT", "TNFT", ""); + erc1155 = new BasicERC1155(delegator, "Test1155", "T1155", ""); + + vm.label(address(erc20), "BasicERC20"); + vm.label(address(erc721), "BasicCF721"); + vm.label(address(erc1155), "BasicERC1155"); + + // Mint an ERC-721 token to the delegator and approve it. + vm.startPrank(delegator); + erc721.mint(delegator); + mintedTokenId = 0; + erc721.approve(spender, mintedTokenId); + + // ERC-20 allowance. + erc20.approve(spender, 42 ether); + + // setApprovalForAll on both ERC-721 and ERC-1155. + erc721.setApprovalForAll(operator, true); + erc1155.setApprovalForAll(operator, true); + vm.stopPrank(); + } + + ////////////////////////////// Helpers ////////////////////////////// + + function _approveCallData(address _spender, uint256 _amount) internal pure returns (bytes memory) { + return abi.encodeWithSelector(IERC20.approve.selector, _spender, _amount); + } + + function _setApprovalForAllCallData(address _operator, bool _approved) internal pure returns (bytes memory) { + return abi.encodeWithSelector(IERC721.setApprovalForAll.selector, _operator, _approved); + } + + function _encodeSingle(address _target, uint256 _value, bytes memory _callData) internal pure returns (bytes memory) { + return ExecutionLib.encodeSingle(_target, _value, _callData); + } + + function _callBeforeHook(bytes memory _executionCallData) internal { + vm.prank(address(delegationManager)); + enforcer.beforeHook(hex"", hex"", singleDefaultMode, _executionCallData, bytes32(0), delegator, address(0)); + } + + function _expectRevertBeforeHook(bytes memory _executionCallData, bytes memory _revertReason) internal { + vm.prank(address(delegationManager)); + vm.expectRevert(_revertReason); + enforcer.beforeHook(hex"", hex"", singleDefaultMode, _executionCallData, bytes32(0), delegator, address(0)); + } + + ////////////////////////////// Valid cases (ERC-20 approve) ////////////////////////////// + + function test_erc20_revokeSucceedsForExistingAllowance() public { + bytes memory executionCallData_ = _encodeSingle(address(erc20), 0, _approveCallData(spender, 0)); + _callBeforeHook(executionCallData_); + } + + function test_erc20_revokeSucceedsForOneWeiAllowance() public { + address other_ = address(users.dave.deleGator); + vm.prank(delegator); + erc20.approve(other_, 1); + bytes memory executionCallData_ = _encodeSingle(address(erc20), 0, _approveCallData(other_, 0)); + _callBeforeHook(executionCallData_); + } + + ////////////////////////////// Invalid cases (ERC-20 approve) ////////////////////////////// + + function test_erc20_revertOnNonZeroAmount() public { + bytes memory executionCallData_ = _encodeSingle(address(erc20), 0, _approveCallData(spender, 1)); + _expectRevertBeforeHook(executionCallData_, "AllowanceRevocationEnforcer:non-zero-amount"); + } + + function test_erc20_revertWhenNoAllowance() public { + address other_ = address(users.dave.deleGator); + assertEq(erc20.allowance(delegator, other_), 0); + bytes memory executionCallData_ = _encodeSingle(address(erc20), 0, _approveCallData(other_, 0)); + _expectRevertBeforeHook(executionCallData_, "AllowanceRevocationEnforcer:no-allowance-to-revoke"); + } + + function test_erc20_revertWhenAllowanceCallFails() public { + // Target is a contract with no `allowance(address,address)` function. + bytes memory executionCallData_ = _encodeSingle(address(enforcer), 0, _approveCallData(spender, 0)); + _expectRevertBeforeHook(executionCallData_, "AllowanceRevocationEnforcer:allowance-call-failed"); + } + + ////////////////////////////// Valid cases (ERC-721 approve) ////////////////////////////// + + function test_erc721_revokeSucceedsForExistingApproval() public { + bytes memory executionCallData_ = _encodeSingle(address(erc721), 0, _approveCallData(address(0), mintedTokenId)); + _callBeforeHook(executionCallData_); + } + + ////////////////////////////// Invalid cases (ERC-721 approve) ////////////////////////////// + + function test_erc721_revertWhenNoApproval() public { + // Mint a fresh token without approving it. + vm.prank(delegator); + erc721.mint(delegator); + uint256 freshTokenId_ = 1; + assertEq(erc721.getApproved(freshTokenId_), address(0)); + + bytes memory executionCallData_ = _encodeSingle(address(erc721), 0, _approveCallData(address(0), freshTokenId_)); + _expectRevertBeforeHook(executionCallData_, "AllowanceRevocationEnforcer:no-approval-to-revoke"); + } + + function test_erc721_revertWhenGetApprovedCallFails() public { + // Non-existent token id reverts in OpenZeppelin's getApproved. + bytes memory executionCallData_ = _encodeSingle(address(erc721), 0, _approveCallData(address(0), 9999)); + _expectRevertBeforeHook(executionCallData_, "AllowanceRevocationEnforcer:getApproved-call-failed"); + } + + ////////////////////////////// Valid cases (setApprovalForAll) ////////////////////////////// + + function test_setApprovalForAll_erc721_revokeSucceeds() public { + assertTrue(erc721.isApprovedForAll(delegator, operator)); + bytes memory executionCallData_ = _encodeSingle(address(erc721), 0, _setApprovalForAllCallData(operator, false)); + _callBeforeHook(executionCallData_); + } + + function test_setApprovalForAll_erc1155_revokeSucceeds() public { + assertTrue(erc1155.isApprovedForAll(delegator, operator)); + bytes memory executionCallData_ = _encodeSingle(address(erc1155), 0, _setApprovalForAllCallData(operator, false)); + _callBeforeHook(executionCallData_); + } + + ////////////////////////////// Invalid cases (setApprovalForAll) ////////////////////////////// + + function test_setApprovalForAll_revertWhenSettingTrue() public { + bytes memory executionCallData_ = _encodeSingle(address(erc721), 0, _setApprovalForAllCallData(operator, true)); + _expectRevertBeforeHook(executionCallData_, "AllowanceRevocationEnforcer:not-a-revocation"); + } + + function test_setApprovalForAll_revertWhenNotApproved() public { + address other_ = address(users.dave.deleGator); + assertFalse(erc721.isApprovedForAll(delegator, other_)); + bytes memory executionCallData_ = _encodeSingle(address(erc721), 0, _setApprovalForAllCallData(other_, false)); + _expectRevertBeforeHook(executionCallData_, "AllowanceRevocationEnforcer:no-approval-to-revoke"); + } + + function test_setApprovalForAll_revertWhenIsApprovedForAllCallFails() public { + // Target is a contract with no `isApprovedForAll(address,address)` function. + bytes memory executionCallData_ = _encodeSingle(address(enforcer), 0, _setApprovalForAllCallData(operator, false)); + _expectRevertBeforeHook(executionCallData_, "AllowanceRevocationEnforcer:isApprovedForAll-call-failed"); + } + + ////////////////////////////// Generic invalid cases ////////////////////////////// + + function test_revertOnNonZeroValue() public { + bytes memory executionCallData_ = _encodeSingle(address(erc20), 1, _approveCallData(spender, 0)); + _expectRevertBeforeHook(executionCallData_, "AllowanceRevocationEnforcer:invalid-value"); + } + + function test_revertOnInvalidExecutionLengthShort() public { + bytes memory shortCallData_ = abi.encodePacked(IERC20.approve.selector, bytes32(uint256(uint160(spender)))); + bytes memory executionCallData_ = _encodeSingle(address(erc20), 0, shortCallData_); + _expectRevertBeforeHook(executionCallData_, "AllowanceRevocationEnforcer:invalid-execution-length"); + } + + function test_revertOnInvalidExecutionLengthLong() public { + bytes memory longCallData_ = abi.encodePacked(_approveCallData(spender, 0), bytes1(0x00)); + bytes memory executionCallData_ = _encodeSingle(address(erc20), 0, longCallData_); + _expectRevertBeforeHook(executionCallData_, "AllowanceRevocationEnforcer:invalid-execution-length"); + } + + function test_revertOnInvalidMethod() public { + bytes memory wrongMethodCallData_ = abi.encodeWithSelector(IERC20.transfer.selector, spender, uint256(0)); + bytes memory executionCallData_ = _encodeSingle(address(erc20), 0, wrongMethodCallData_); + _expectRevertBeforeHook(executionCallData_, "AllowanceRevocationEnforcer:invalid-method"); + } + + function test_revertWithInvalidCallTypeMode() public { + bytes memory executionCallData_ = ExecutionLib.encodeBatch(new Execution[](2)); + vm.expectRevert("CaveatEnforcer:invalid-call-type"); + enforcer.beforeHook(hex"", hex"", batchDefaultMode, executionCallData_, bytes32(0), delegator, address(0)); + } + + function test_revertWithInvalidExecutionMode() public { + vm.expectRevert("CaveatEnforcer:invalid-execution-type"); + enforcer.beforeHook(hex"", hex"", singleTryMode, hex"", bytes32(0), delegator, address(0)); + } + + ////////////////////////////// Integration ////////////////////////////// + + function test_integration_revokesErc20Allowance() public { + assertEq(erc20.allowance(delegator, spender), 42 ether); + + Execution memory execution_ = + Execution({ target: address(erc20), value: 0, callData: _approveCallData(spender, 0) }); + + Caveat[] memory caveats_ = new Caveat[](1); + caveats_[0] = Caveat({ args: hex"", enforcer: address(enforcer), terms: hex"" }); + Delegation memory delegation_ = Delegation({ + delegate: address(users.bob.deleGator), + delegator: delegator, + authority: ROOT_AUTHORITY, + caveats: caveats_, + salt: 0, + signature: hex"" + }); + delegation_ = signDelegation(users.alice, delegation_); + + Delegation[] memory delegations_ = new Delegation[](1); + delegations_[0] = delegation_; + + invokeDelegation_UserOp(users.bob, delegations_, execution_); + assertEq(erc20.allowance(delegator, spender), 0); + } + + function test_integration_revokesErc721Approval() public { + assertEq(erc721.getApproved(mintedTokenId), spender); + + Execution memory execution_ = + Execution({ target: address(erc721), value: 0, callData: _approveCallData(address(0), mintedTokenId) }); + + Caveat[] memory caveats_ = new Caveat[](1); + caveats_[0] = Caveat({ args: hex"", enforcer: address(enforcer), terms: hex"" }); + Delegation memory delegation_ = Delegation({ + delegate: address(users.bob.deleGator), + delegator: delegator, + authority: ROOT_AUTHORITY, + caveats: caveats_, + salt: 0, + signature: hex"" + }); + delegation_ = signDelegation(users.alice, delegation_); + + Delegation[] memory delegations_ = new Delegation[](1); + delegations_[0] = delegation_; + + invokeDelegation_UserOp(users.bob, delegations_, execution_); + assertEq(erc721.getApproved(mintedTokenId), address(0)); + } + + function test_integration_revokesSetApprovalForAll() public { + assertTrue(erc1155.isApprovedForAll(delegator, operator)); + + Execution memory execution_ = + Execution({ target: address(erc1155), value: 0, callData: _setApprovalForAllCallData(operator, false) }); + + Caveat[] memory caveats_ = new Caveat[](1); + caveats_[0] = Caveat({ args: hex"", enforcer: address(enforcer), terms: hex"" }); + Delegation memory delegation_ = Delegation({ + delegate: address(users.bob.deleGator), + delegator: delegator, + authority: ROOT_AUTHORITY, + caveats: caveats_, + salt: 0, + signature: hex"" + }); + delegation_ = signDelegation(users.alice, delegation_); + + Delegation[] memory delegations_ = new Delegation[](1); + delegations_[0] = delegation_; + + invokeDelegation_UserOp(users.bob, delegations_, execution_); + assertFalse(erc1155.isApprovedForAll(delegator, operator)); + } + + function _getEnforcer() internal view override returns (ICaveatEnforcer) { + return ICaveatEnforcer(address(enforcer)); + } +} diff --git a/test/enforcers/ERC20AllowanceRevocationEnforcer.t.sol b/test/enforcers/ERC20AllowanceRevocationEnforcer.t.sol deleted file mode 100644 index c8901cb7..00000000 --- a/test/enforcers/ERC20AllowanceRevocationEnforcer.t.sol +++ /dev/null @@ -1,197 +0,0 @@ -// SPDX-License-Identifier: MIT AND Apache-2.0 -pragma solidity 0.8.23; - -import "forge-std/Test.sol"; -import { ExecutionLib } from "@erc7579/lib/ExecutionLib.sol"; - -import { Execution, Caveat, Delegation, ModeCode } from "../../src/utils/Types.sol"; -import { CaveatEnforcerBaseTest } from "./CaveatEnforcerBaseTest.t.sol"; -import { BasicERC20, IERC20 } from "../utils/BasicERC20.t.sol"; -import { EncoderLib } from "../../src/libraries/EncoderLib.sol"; -import { ICaveatEnforcer } from "../../src/interfaces/ICaveatEnforcer.sol"; -import { ERC20AllowanceRevocationEnforcer } from "../../src/enforcers/ERC20AllowanceRevocationEnforcer.sol"; - -/** - * @title ERC20AllowanceRevocationEnforcer Test - */ -contract ERC20AllowanceRevocationEnforcerTest is CaveatEnforcerBaseTest { - ////////////////////////////// State ////////////////////////////// - - ERC20AllowanceRevocationEnforcer public enforcer; - BasicERC20 public token; - - address public delegator; - address public spender; - - ////////////////////////////// Set up ////////////////////////////// - - function setUp() public override { - super.setUp(); - enforcer = new ERC20AllowanceRevocationEnforcer(); - vm.label(address(enforcer), "ERC20AllowanceRevocationEnforcer"); - - delegator = address(users.alice.deleGator); - spender = address(users.bob.deleGator); - - token = new BasicERC20(delegator, "TestToken", "TT", 100 ether); - vm.label(address(token), "BasicERC20"); - - vm.prank(delegator); - token.approve(spender, 42 ether); - } - - ////////////////////////////// Helpers ////////////////////////////// - - function _approveCallData(address _spender, uint256 _amount) internal pure returns (bytes memory) { - return abi.encodeWithSelector(IERC20.approve.selector, _spender, _amount); - } - - function _encodeSingle(address _target, uint256 _value, bytes memory _callData) internal pure returns (bytes memory) { - return ExecutionLib.encodeSingle(_target, _value, _callData); - } - - function _callBeforeHook(bytes memory _executionCallData) internal { - vm.prank(address(delegationManager)); - enforcer.beforeHook(hex"", hex"", singleDefaultMode, _executionCallData, bytes32(0), delegator, address(0)); - } - - ////////////////////////////// Valid cases ////////////////////////////// - - // should SUCCEED when revoking an existing allowance - function test_revokeSucceedsForExistingAllowance() public { - bytes memory executionCallData_ = _encodeSingle(address(token), 0, _approveCallData(spender, 0)); - _callBeforeHook(executionCallData_); - } - - // should SUCCEED for any non-zero allowance amount (1 wei) - function test_revokeSucceedsForMinimalAllowance() public { - address otherSpender_ = address(users.carol.deleGator); - vm.prank(delegator); - token.approve(otherSpender_, 1); - - bytes memory executionCallData_ = _encodeSingle(address(token), 0, _approveCallData(otherSpender_, 0)); - _callBeforeHook(executionCallData_); - } - - ////////////////////////////// Invalid cases ////////////////////////////// - - // should FAIL when the execution transfers native value - function test_revertOnNonZeroValue() public { - bytes memory executionCallData_ = _encodeSingle(address(token), 1, _approveCallData(spender, 0)); - vm.prank(address(delegationManager)); - vm.expectRevert("ERC20AllowanceRevocationEnforcer:invalid-value"); - enforcer.beforeHook(hex"", hex"", singleDefaultMode, executionCallData_, bytes32(0), delegator, address(0)); - } - - // should FAIL when the calldata length differs from approve(address,uint256) - function test_revertOnInvalidExecutionLength() public { - bytes memory shortCallData_ = abi.encodePacked(IERC20.approve.selector, bytes32(uint256(uint160(spender)))); - bytes memory executionCallData_ = _encodeSingle(address(token), 0, shortCallData_); - vm.prank(address(delegationManager)); - vm.expectRevert("ERC20AllowanceRevocationEnforcer:invalid-execution-length"); - enforcer.beforeHook(hex"", hex"", singleDefaultMode, executionCallData_, bytes32(0), delegator, address(0)); - } - - // should FAIL when the calldata length is longer than expected (extra trailing bytes) - function test_revertOnInvalidExecutionLengthTooLong() public { - bytes memory longCallData_ = abi.encodePacked(_approveCallData(spender, 0), bytes1(0x00)); - bytes memory executionCallData_ = _encodeSingle(address(token), 0, longCallData_); - vm.prank(address(delegationManager)); - vm.expectRevert("ERC20AllowanceRevocationEnforcer:invalid-execution-length"); - enforcer.beforeHook(hex"", hex"", singleDefaultMode, executionCallData_, bytes32(0), delegator, address(0)); - } - - // should FAIL when the selector is not approve(address,uint256) - function test_revertOnInvalidMethod() public { - bytes memory wrongMethodCallData_ = abi.encodeWithSelector(IERC20.transfer.selector, spender, uint256(0)); - bytes memory executionCallData_ = _encodeSingle(address(token), 0, wrongMethodCallData_); - vm.prank(address(delegationManager)); - vm.expectRevert("ERC20AllowanceRevocationEnforcer:invalid-method"); - enforcer.beforeHook(hex"", hex"", singleDefaultMode, executionCallData_, bytes32(0), delegator, address(0)); - } - - // should FAIL when the approved amount is non-zero (i.e. not a revocation) - function test_revertOnNonZeroAmount() public { - bytes memory executionCallData_ = _encodeSingle(address(token), 0, _approveCallData(spender, 1)); - vm.prank(address(delegationManager)); - vm.expectRevert("ERC20AllowanceRevocationEnforcer:non-zero-amount"); - enforcer.beforeHook(hex"", hex"", singleDefaultMode, executionCallData_, bytes32(0), delegator, address(0)); - } - - // should FAIL when the current allowance is already zero (nothing to revoke) - function test_revertWhenNoAllowanceToRevoke() public { - address otherSpender_ = address(users.dave.deleGator); - assertEq(token.allowance(delegator, otherSpender_), 0); - - bytes memory executionCallData_ = _encodeSingle(address(token), 0, _approveCallData(otherSpender_, 0)); - vm.prank(address(delegationManager)); - vm.expectRevert("ERC20AllowanceRevocationEnforcer:no-allowance-to-revoke"); - enforcer.beforeHook(hex"", hex"", singleDefaultMode, executionCallData_, bytes32(0), delegator, address(0)); - } - - // should FAIL when the execution target does not implement allowance(address,address) - function test_revertWhenAllowanceCallFails() public { - // A contract address with code but no allowance() implementation — the enforcer contract itself. - address noAllowanceTarget_ = address(enforcer); - bytes memory executionCallData_ = _encodeSingle(noAllowanceTarget_, 0, _approveCallData(spender, 0)); - vm.prank(address(delegationManager)); - vm.expectRevert("ERC20AllowanceRevocationEnforcer:allowance-call-failed"); - enforcer.beforeHook(hex"", hex"", singleDefaultMode, executionCallData_, bytes32(0), delegator, address(0)); - } - - // should FAIL when the execution target is an EOA (no code) - function test_revertWhenTargetHasNoCode() public { - address eoa_ = address(0xBEEF); - require(eoa_.code.length == 0); - bytes memory executionCallData_ = _encodeSingle(eoa_, 0, _approveCallData(spender, 0)); - vm.prank(address(delegationManager)); - vm.expectRevert("ERC20AllowanceRevocationEnforcer:allowance-call-failed"); - enforcer.beforeHook(hex"", hex"", singleDefaultMode, executionCallData_, bytes32(0), delegator, address(0)); - } - - // should FAIL with invalid call type mode (batch instead of single) - function test_revertWithInvalidCallTypeMode() public { - bytes memory executionCallData_ = ExecutionLib.encodeBatch(new Execution[](2)); - vm.expectRevert("CaveatEnforcer:invalid-call-type"); - enforcer.beforeHook(hex"", hex"", batchDefaultMode, executionCallData_, bytes32(0), delegator, address(0)); - } - - // should FAIL with invalid execution mode (try instead of default) - function test_revertWithInvalidExecutionMode() public { - vm.expectRevert("CaveatEnforcer:invalid-execution-type"); - enforcer.beforeHook(hex"", hex"", singleTryMode, hex"", bytes32(0), delegator, address(0)); - } - - ////////////////////////////// Integration ////////////////////////////// - - // should revoke an existing allowance through the full delegation redemption path - function test_revokeIntegration() public { - assertEq(token.allowance(delegator, spender), 42 ether); - - Execution memory execution_ = - Execution({ target: address(token), value: 0, callData: _approveCallData(spender, 0) }); - - Caveat[] memory caveats_ = new Caveat[](1); - caveats_[0] = Caveat({ args: hex"", enforcer: address(enforcer), terms: hex"" }); - Delegation memory delegation_ = Delegation({ - delegate: address(users.bob.deleGator), - delegator: address(users.alice.deleGator), - authority: ROOT_AUTHORITY, - caveats: caveats_, - salt: 0, - signature: hex"" - }); - delegation_ = signDelegation(users.alice, delegation_); - - Delegation[] memory delegations_ = new Delegation[](1); - delegations_[0] = delegation_; - - invokeDelegation_UserOp(users.bob, delegations_, execution_); - - assertEq(token.allowance(delegator, spender), 0); - } - - function _getEnforcer() internal view override returns (ICaveatEnforcer) { - return ICaveatEnforcer(address(enforcer)); - } -} From 90b53423ec4b3afbfe51608f02bcab40b6f73c5d Mon Sep 17 00:00:00 2001 From: MoMannn Date: Wed, 22 Apr 2026 10:24:12 +0200 Subject: [PATCH 3/9] Refactor allowance revocation enforcer --- src/enforcers/AllowanceRevocationEnforcer.sol | 136 ------------ src/enforcers/ApprovalRevocationEnforcer.sol | 180 ++++++++++++++++ ...t.sol => ApprovalRevocationEnforcer.t.sol} | 195 ++++++++++++++++-- 3 files changed, 353 insertions(+), 158 deletions(-) delete mode 100644 src/enforcers/AllowanceRevocationEnforcer.sol create mode 100644 src/enforcers/ApprovalRevocationEnforcer.sol rename test/enforcers/{AllowanceRevocationEnforcer.t.sol => ApprovalRevocationEnforcer.t.sol} (57%) diff --git a/src/enforcers/AllowanceRevocationEnforcer.sol b/src/enforcers/AllowanceRevocationEnforcer.sol deleted file mode 100644 index 6476a9a1..00000000 --- a/src/enforcers/AllowanceRevocationEnforcer.sol +++ /dev/null @@ -1,136 +0,0 @@ -// SPDX-License-Identifier: MIT AND Apache-2.0 -pragma solidity 0.8.23; - -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import { ExecutionLib } from "@erc7579/lib/ExecutionLib.sol"; - -import { CaveatEnforcer } from "./CaveatEnforcer.sol"; -import { ModeCode } from "../utils/Types.sol"; - -/** - * @title AllowanceRevocationEnforcer - * @notice Allows the delegate to revoke an existing token approval on behalf of the delegator. Supports ERC-20 - * `approve`, ERC-721 per-token `approve`, and ERC-721/ERC-1155 `setApprovalForAll`. - * - * @dev The execution must transfer zero native value and carry one of the supported approval calldatas (length 68 - * bytes: 4-byte selector + two 32-byte words). Branching is determined as follows: - * - selector `setApprovalForAll(address operator, bool approved)`: - * - `approved` MUST be false, and - * - `isApprovedForAll(delegator, operator)` MUST currently be true. - * - selector `approve(address, uint256)` (shared by ERC-20 and ERC-721): - * - if the first parameter is `address(0)` the call is treated as an ERC-721 per-token revocation: - * - `getApproved(tokenId)` on the target MUST currently return a non-zero address. - * - otherwise the call is treated as an ERC-20 allowance revocation: - * - the second parameter (amount) MUST be zero, and - * - `allowance(delegator, spender)` on the target MUST currently return non-zero. - * - * The pre-existing approval check guarantees the call is strictly a revocation of an existing approval rather than - * a new grant or a no-op. - * - * @dev This enforcer does not consume any terms. - * @dev Operates only in single call type and default execution mode. - */ -contract AllowanceRevocationEnforcer is CaveatEnforcer { - using ExecutionLib for bytes; - - /// @dev Calldata length of `approve(address,uint256)` and `setApprovalForAll(address,bool)`: - /// 4-byte selector + two 32-byte words. - uint256 private constant _APPROVAL_CALLDATA_LENGTH = 68; - - ////////////////////////////// Public Methods ////////////////////////////// - - /** - * @notice Requires the execution to revoke an existing token approval owned by `_delegator`. - * @param _mode Must be single call type and default execution mode. - * @param _executionCallData Single execution targeting the token contract. - * @param _delegator The delegator, treated as the approval `owner`. - */ - function beforeHook( - bytes calldata, - bytes calldata, - ModeCode _mode, - bytes calldata _executionCallData, - bytes32, - address _delegator, - address - ) - public - view - override - onlySingleCallTypeMode(_mode) - onlyDefaultExecutionMode(_mode) - { - (address target_, uint256 value_, bytes calldata callData_) = _executionCallData.decodeSingle(); - - require(value_ == 0, "AllowanceRevocationEnforcer:invalid-value"); - require(callData_.length == _APPROVAL_CALLDATA_LENGTH, "AllowanceRevocationEnforcer:invalid-execution-length"); - - bytes4 selector_ = bytes4(callData_[0:4]); - if (selector_ == IERC721.setApprovalForAll.selector) { - _enforceSetApprovalForAllRevocation(target_, callData_, _delegator); - return; - } - if (selector_ == IERC20.approve.selector) { - address firstParam_ = address(uint160(uint256(bytes32(callData_[4:36])))); - if (firstParam_ == address(0)) { - _enforceErc721ApproveRevocation(target_, callData_); - } else { - _enforceErc20ApproveRevocation(target_, callData_, _delegator, firstParam_); - } - return; - } - revert("AllowanceRevocationEnforcer:invalid-method"); - } - - ////////////////////////////// Internal Methods ////////////////////////////// - - /** - * @dev Validates an ERC-20 `approve(spender, 0)` revocation. Requires `allowance(delegator, spender) > 0` on the - * target. - */ - function _enforceErc20ApproveRevocation( - address _target, - bytes calldata _callData, - address _delegator, - address _spender - ) - private - view - { - require(uint256(bytes32(_callData[36:68])) == 0, "AllowanceRevocationEnforcer:non-zero-amount"); - - (bool success_, bytes memory returnData_) = - _target.staticcall(abi.encodeWithSelector(IERC20.allowance.selector, _delegator, _spender)); - require(success_ && returnData_.length >= 32, "AllowanceRevocationEnforcer:allowance-call-failed"); - require(abi.decode(returnData_, (uint256)) != 0, "AllowanceRevocationEnforcer:no-allowance-to-revoke"); - } - - /** - * @dev Validates an ERC-721 `approve(address(0), tokenId)` revocation. Requires `getApproved(tokenId)` on the - * target to be non-zero (i.e. an approval is currently set). - */ - function _enforceErc721ApproveRevocation(address _target, bytes calldata _callData) private view { - uint256 tokenId_ = uint256(bytes32(_callData[36:68])); - - (bool success_, bytes memory returnData_) = - _target.staticcall(abi.encodeWithSelector(IERC721.getApproved.selector, tokenId_)); - require(success_ && returnData_.length >= 32, "AllowanceRevocationEnforcer:getApproved-call-failed"); - require(abi.decode(returnData_, (address)) != address(0), "AllowanceRevocationEnforcer:no-approval-to-revoke"); - } - - /** - * @dev Validates a `setApprovalForAll(operator, false)` revocation. Requires `isApprovedForAll(delegator, - * operator)` on the target to currently be true. - */ - function _enforceSetApprovalForAllRevocation(address _target, bytes calldata _callData, address _delegator) private view { - address operator_ = address(uint160(uint256(bytes32(_callData[4:36])))); - bool approved_ = uint256(bytes32(_callData[36:68])) != 0; - require(!approved_, "AllowanceRevocationEnforcer:not-a-revocation"); - - (bool success_, bytes memory returnData_) = - _target.staticcall(abi.encodeWithSelector(IERC721.isApprovedForAll.selector, _delegator, operator_)); - require(success_ && returnData_.length >= 32, "AllowanceRevocationEnforcer:isApprovedForAll-call-failed"); - require(abi.decode(returnData_, (bool)), "AllowanceRevocationEnforcer:no-approval-to-revoke"); - } -} diff --git a/src/enforcers/ApprovalRevocationEnforcer.sol b/src/enforcers/ApprovalRevocationEnforcer.sol new file mode 100644 index 00000000..45773aaa --- /dev/null +++ b/src/enforcers/ApprovalRevocationEnforcer.sol @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: MIT AND Apache-2.0 +pragma solidity 0.8.23; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import { ExecutionLib } from "@erc7579/lib/ExecutionLib.sol"; + +import { CaveatEnforcer } from "./CaveatEnforcer.sol"; +import { ModeCode } from "../utils/Types.sol"; + +/** + * @title ApprovalRevocationEnforcer + * @notice Allows a delegate to clear an existing token approval. Covers the three standard approval primitives: + * - ERC-20 `approve(spender, 0)` + * - ERC-721 per-token `approve(address(0), tokenId)` + * - ERC-721 / ERC-1155 `setApprovalForAll(operator, false)` + * + * @dev ERC-721 and ERC-1155 intentionally share the `setApprovalForAll(address,bool)` selector; this enforcer + * handles both via the `IERC721` interface (the selector and ABI are identical, so a typed `IERC1155` import is + * unnecessary for the external call). ERC-20 and ERC-721 likewise share the `approve(address,uint256)` selector, + * and are disambiguated by inspecting the first parameter (see branching rules below). + * + * @dev The execution must transfer zero native value and carry one of the supported approval calldatas (length 68 + * bytes: 4-byte selector + two 32-byte words). Branching is determined as follows: + * - selector `setApprovalForAll(address operator, bool approved)`: + * - `approved` MUST be false, and + * - `isApprovedForAll(delegator, operator)` MUST currently be true on the target. + * - selector `approve(address, uint256)` (shared by ERC-20 and ERC-721): + * - if the first parameter is `address(0)` the call is treated as an ERC-721 per-token revocation: + * - `getApproved(tokenId)` on the target MUST currently return a non-zero address. + * - otherwise the call is treated as an ERC-20 revocation: + * - the second parameter (amount) MUST be zero, and + * - `allowance(delegator, spender)` on the target MUST currently return non-zero. + * + * @dev All three accepted calldatas structurally result in a net reduction of permissions on the target (amount + * `0`, spender `address(0)`, or `approved` `false`). A delegate using this enforcer can therefore never be granted + * new authority over the delegator's assets — only existing approvals can be cleared. + * + * @dev REDELEGATION WARNING — link-local pre-check vs. root-level execution. + * + * The `_delegator` argument passed to `beforeHook` is the delegator of the specific delegation that carries the + * caveat, not the root of a redelegation chain. The DelegationManager always executes the downstream + * `approve` / `setApprovalForAll` call against the *root* delegator's account (the account at the end of the + * leaf-to-root chain). On a root-level delegation (chain length 1) the two are the same and the pre-check + * queries the account whose storage will actually be mutated — this is the intended usage. + * + * On an intermediate (redelegation) link the two differ: the pre-check queries the *intermediate* delegator's + * approval state, while the execution mutates the *root* delegator's storage. A redelegator adding this caveat + * to constrain their delegate is very likely expecting the pre-check to run against the root (the account whose + * approval will be cleared). That expectation is wrong — the check is link-local. + * + * Concrete example. Alice -> Bob -> Carol. Alice's link has no caveat (Bob has full authority over Alice). + * Bob places this enforcer on his delegation to Carol, intending "Carol can only revoke an existing approval on + * Alice's account". When Carol redeems, the hook fires with `_delegator = Bob`, not Alice, so: + * - if Bob has no allowance to the same spender on the target, the hook reverts even when Alice does have + * one (Carol cannot use the chain, even though the revocation would have been valid for Alice); + * - if Bob happens to have some allowance, the hook passes and the execution clears Alice's allowance — + * independently of whether Alice actually had an allowance to clear. + * + * This is never an authority escalation (the structural constraints above still apply — the call can only + * reduce permissions), but the sanity guard is misaligned with the executed effect and will behave + * unintuitively for anyone reading "the delegator's approval must exist" as a check on the root. + * + * If a redelegator needs a root-scoped guarantee (e.g. "Carol may only revoke one of Alice's specific + * approvals") they should rely on structural caveats that compose cleanly across links, such as + * `AllowedTargetsEnforcer` (restrict which token contract), `AllowedCalldataEnforcer` (pin the exact spender + * or tokenId), or `ExactCalldataEnforcer`. Placing `ApprovalRevocationEnforcer` on an intermediate link in the + * hope of validating the root's approval state does not achieve that. + * + * @dev The "pre-existing approval" check is a liveness/sanity guard ensuring the call is not a no-op at the time + * the hook runs. It is not a race-free invariant: the delegator could independently clear the approval between + * the hook and the execution. In that case the execution is still safe — it simply becomes a no-op. + * + * @dev This enforcer does not consume any terms and is not scoped to a specific target contract. Delegators who + * want to restrict revocation to specific tokens should compose this enforcer with `AllowedTargetsEnforcer`. + * + * @dev This enforcer operates only in single call type and default execution mode. + */ +contract ApprovalRevocationEnforcer is CaveatEnforcer { + using ExecutionLib for bytes; + + ////////////////////////////// Public Methods ////////////////////////////// + + /** + * @notice Requires the execution to revoke an existing token approval owned by `_delegator`. + * @param _mode Must be single call type and default execution mode. + * @param _executionCallData Single execution targeting the token contract. + * @param _delegator The delegator of the delegation carrying this caveat (link-local, not the chain root). + * See the contract-level NatSpec for the implications in redelegation chains. + */ + function beforeHook( + bytes calldata, + bytes calldata, + ModeCode _mode, + bytes calldata _executionCallData, + bytes32, + address _delegator, + address + ) + public + view + override + onlySingleCallTypeMode(_mode) + onlyDefaultExecutionMode(_mode) + { + (address target_, uint256 value_, bytes calldata callData_) = _executionCallData.decodeSingle(); + + require(value_ == 0, "ApprovalRevocationEnforcer:invalid-value"); + // 68 = 4-byte selector + two 32-byte words. Shared by `approve(address,uint256)` and + // `setApprovalForAll(address,bool)`. + require(callData_.length == 68, "ApprovalRevocationEnforcer:invalid-execution-length"); + + bytes4 selector_ = bytes4(callData_[0:4]); + if (selector_ == IERC721.setApprovalForAll.selector) { + _validateOperatorRevocation(target_, callData_, _delegator); + return; + } + if (selector_ == IERC20.approve.selector) { + // ERC-20 and ERC-721 share `approve(address,uint256)`. Disambiguate by the first parameter: ERC-721 + // revokes via `approve(address(0), tokenId)`, while ERC-20 revokes via `approve(spender, 0)` with a + // non-zero spender. + address firstParam_ = address(uint160(uint256(bytes32(callData_[4:36])))); + if (firstParam_ == address(0)) { + _validateErc721Revocation(target_, callData_); + } else { + _validateErc20Revocation(target_, callData_, _delegator, firstParam_); + } + return; + } + revert("ApprovalRevocationEnforcer:invalid-method"); + } + + ////////////////////////////// Internal Methods ////////////////////////////// + + /** + * @dev Validates an ERC-20 `approve(spender, 0)` revocation. Requires `allowance(delegator, spender) > 0` on + * the target. + */ + function _validateErc20Revocation( + address _target, + bytes calldata _callData, + address _delegator, + address _spender + ) + private + view + { + require(uint256(bytes32(_callData[36:68])) == 0, "ApprovalRevocationEnforcer:non-zero-amount"); + + require( + IERC20(_target).allowance(_delegator, _spender) != 0, "ApprovalRevocationEnforcer:no-approval-to-revoke" + ); + } + + /** + * @dev Validates an ERC-721 `approve(address(0), tokenId)` revocation. Requires `getApproved(tokenId)` on the + * target to be non-zero (i.e. an approval is currently set). + */ + function _validateErc721Revocation(address _target, bytes calldata _callData) private view { + uint256 tokenId_ = uint256(bytes32(_callData[36:68])); + + require( + IERC721(_target).getApproved(tokenId_) != address(0), "ApprovalRevocationEnforcer:no-approval-to-revoke" + ); + } + + /** + * @dev Validates a `setApprovalForAll(operator, false)` revocation (ERC-721 and ERC-1155 share this selector). + * Requires `isApprovedForAll(delegator, operator)` on the target to currently be true. + */ + function _validateOperatorRevocation(address _target, bytes calldata _callData, address _delegator) private view { + require(uint256(bytes32(_callData[36:68])) == 0, "ApprovalRevocationEnforcer:not-a-revocation"); + + address operator_ = address(uint160(uint256(bytes32(_callData[4:36])))); + require( + IERC721(_target).isApprovedForAll(_delegator, operator_), + "ApprovalRevocationEnforcer:no-approval-to-revoke" + ); + } +} diff --git a/test/enforcers/AllowanceRevocationEnforcer.t.sol b/test/enforcers/ApprovalRevocationEnforcer.t.sol similarity index 57% rename from test/enforcers/AllowanceRevocationEnforcer.t.sol rename to test/enforcers/ApprovalRevocationEnforcer.t.sol index 999db261..f5f833b9 100644 --- a/test/enforcers/AllowanceRevocationEnforcer.t.sol +++ b/test/enforcers/ApprovalRevocationEnforcer.t.sol @@ -13,15 +13,16 @@ import { BasicERC20 } from "../utils/BasicERC20.t.sol"; import { BasicCF721 } from "../utils/BasicCF721.t.sol"; import { BasicERC1155 } from "../utils/BasicERC1155.t.sol"; import { ICaveatEnforcer } from "../../src/interfaces/ICaveatEnforcer.sol"; -import { AllowanceRevocationEnforcer } from "../../src/enforcers/AllowanceRevocationEnforcer.sol"; +import { ApprovalRevocationEnforcer } from "../../src/enforcers/ApprovalRevocationEnforcer.sol"; +import { EncoderLib } from "../../src/libraries/EncoderLib.sol"; /** - * @title AllowanceRevocationEnforcer Test + * @title ApprovalRevocationEnforcer Test */ -contract AllowanceRevocationEnforcerTest is CaveatEnforcerBaseTest { +contract ApprovalRevocationEnforcerTest is CaveatEnforcerBaseTest { ////////////////////////////// State ////////////////////////////// - AllowanceRevocationEnforcer public enforcer; + ApprovalRevocationEnforcer public enforcer; BasicERC20 public erc20; BasicCF721 public erc721; BasicERC1155 public erc1155; @@ -36,8 +37,8 @@ contract AllowanceRevocationEnforcerTest is CaveatEnforcerBaseTest { function setUp() public override { super.setUp(); - enforcer = new AllowanceRevocationEnforcer(); - vm.label(address(enforcer), "AllowanceRevocationEnforcer"); + enforcer = new ApprovalRevocationEnforcer(); + vm.label(address(enforcer), "ApprovalRevocationEnforcer"); delegator = address(users.alice.deleGator); spender = address(users.bob.deleGator); @@ -110,20 +111,23 @@ contract AllowanceRevocationEnforcerTest is CaveatEnforcerBaseTest { function test_erc20_revertOnNonZeroAmount() public { bytes memory executionCallData_ = _encodeSingle(address(erc20), 0, _approveCallData(spender, 1)); - _expectRevertBeforeHook(executionCallData_, "AllowanceRevocationEnforcer:non-zero-amount"); + _expectRevertBeforeHook(executionCallData_, "ApprovalRevocationEnforcer:non-zero-amount"); } - function test_erc20_revertWhenNoAllowance() public { + function test_erc20_revertWhenNoApproval() public { address other_ = address(users.dave.deleGator); assertEq(erc20.allowance(delegator, other_), 0); bytes memory executionCallData_ = _encodeSingle(address(erc20), 0, _approveCallData(other_, 0)); - _expectRevertBeforeHook(executionCallData_, "AllowanceRevocationEnforcer:no-allowance-to-revoke"); + _expectRevertBeforeHook(executionCallData_, "ApprovalRevocationEnforcer:no-approval-to-revoke"); } function test_erc20_revertWhenAllowanceCallFails() public { - // Target is a contract with no `allowance(address,address)` function. + // Target is a contract with no `allowance(address,address)` function; the high-level call reverts with + // empty returndata when ABI-decoding the (empty) response fails. bytes memory executionCallData_ = _encodeSingle(address(enforcer), 0, _approveCallData(spender, 0)); - _expectRevertBeforeHook(executionCallData_, "AllowanceRevocationEnforcer:allowance-call-failed"); + vm.prank(address(delegationManager)); + vm.expectRevert(); + enforcer.beforeHook(hex"", hex"", singleDefaultMode, executionCallData_, bytes32(0), delegator, address(0)); } ////////////////////////////// Valid cases (ERC-721 approve) ////////////////////////////// @@ -143,13 +147,16 @@ contract AllowanceRevocationEnforcerTest is CaveatEnforcerBaseTest { assertEq(erc721.getApproved(freshTokenId_), address(0)); bytes memory executionCallData_ = _encodeSingle(address(erc721), 0, _approveCallData(address(0), freshTokenId_)); - _expectRevertBeforeHook(executionCallData_, "AllowanceRevocationEnforcer:no-approval-to-revoke"); + _expectRevertBeforeHook(executionCallData_, "ApprovalRevocationEnforcer:no-approval-to-revoke"); } function test_erc721_revertWhenGetApprovedCallFails() public { - // Non-existent token id reverts in OpenZeppelin's getApproved. + // Non-existent token id reverts in OpenZeppelin's getApproved; the custom error bubbles up through the + // high-level call. bytes memory executionCallData_ = _encodeSingle(address(erc721), 0, _approveCallData(address(0), 9999)); - _expectRevertBeforeHook(executionCallData_, "AllowanceRevocationEnforcer:getApproved-call-failed"); + vm.prank(address(delegationManager)); + vm.expectRevert(abi.encodeWithSignature("ERC721NonexistentToken(uint256)", 9999)); + enforcer.beforeHook(hex"", hex"", singleDefaultMode, executionCallData_, bytes32(0), delegator, address(0)); } ////////////////////////////// Valid cases (setApprovalForAll) ////////////////////////////// @@ -170,45 +177,48 @@ contract AllowanceRevocationEnforcerTest is CaveatEnforcerBaseTest { function test_setApprovalForAll_revertWhenSettingTrue() public { bytes memory executionCallData_ = _encodeSingle(address(erc721), 0, _setApprovalForAllCallData(operator, true)); - _expectRevertBeforeHook(executionCallData_, "AllowanceRevocationEnforcer:not-a-revocation"); + _expectRevertBeforeHook(executionCallData_, "ApprovalRevocationEnforcer:not-a-revocation"); } function test_setApprovalForAll_revertWhenNotApproved() public { address other_ = address(users.dave.deleGator); assertFalse(erc721.isApprovedForAll(delegator, other_)); bytes memory executionCallData_ = _encodeSingle(address(erc721), 0, _setApprovalForAllCallData(other_, false)); - _expectRevertBeforeHook(executionCallData_, "AllowanceRevocationEnforcer:no-approval-to-revoke"); + _expectRevertBeforeHook(executionCallData_, "ApprovalRevocationEnforcer:no-approval-to-revoke"); } function test_setApprovalForAll_revertWhenIsApprovedForAllCallFails() public { - // Target is a contract with no `isApprovedForAll(address,address)` function. + // Target is a contract with no `isApprovedForAll(address,address)` function; the high-level call reverts + // with empty returndata when ABI-decoding the (empty) response fails. bytes memory executionCallData_ = _encodeSingle(address(enforcer), 0, _setApprovalForAllCallData(operator, false)); - _expectRevertBeforeHook(executionCallData_, "AllowanceRevocationEnforcer:isApprovedForAll-call-failed"); + vm.prank(address(delegationManager)); + vm.expectRevert(); + enforcer.beforeHook(hex"", hex"", singleDefaultMode, executionCallData_, bytes32(0), delegator, address(0)); } ////////////////////////////// Generic invalid cases ////////////////////////////// function test_revertOnNonZeroValue() public { bytes memory executionCallData_ = _encodeSingle(address(erc20), 1, _approveCallData(spender, 0)); - _expectRevertBeforeHook(executionCallData_, "AllowanceRevocationEnforcer:invalid-value"); + _expectRevertBeforeHook(executionCallData_, "ApprovalRevocationEnforcer:invalid-value"); } function test_revertOnInvalidExecutionLengthShort() public { bytes memory shortCallData_ = abi.encodePacked(IERC20.approve.selector, bytes32(uint256(uint160(spender)))); bytes memory executionCallData_ = _encodeSingle(address(erc20), 0, shortCallData_); - _expectRevertBeforeHook(executionCallData_, "AllowanceRevocationEnforcer:invalid-execution-length"); + _expectRevertBeforeHook(executionCallData_, "ApprovalRevocationEnforcer:invalid-execution-length"); } function test_revertOnInvalidExecutionLengthLong() public { bytes memory longCallData_ = abi.encodePacked(_approveCallData(spender, 0), bytes1(0x00)); bytes memory executionCallData_ = _encodeSingle(address(erc20), 0, longCallData_); - _expectRevertBeforeHook(executionCallData_, "AllowanceRevocationEnforcer:invalid-execution-length"); + _expectRevertBeforeHook(executionCallData_, "ApprovalRevocationEnforcer:invalid-execution-length"); } function test_revertOnInvalidMethod() public { bytes memory wrongMethodCallData_ = abi.encodeWithSelector(IERC20.transfer.selector, spender, uint256(0)); bytes memory executionCallData_ = _encodeSingle(address(erc20), 0, wrongMethodCallData_); - _expectRevertBeforeHook(executionCallData_, "AllowanceRevocationEnforcer:invalid-method"); + _expectRevertBeforeHook(executionCallData_, "ApprovalRevocationEnforcer:invalid-method"); } function test_revertWithInvalidCallTypeMode() public { @@ -299,6 +309,147 @@ contract AllowanceRevocationEnforcerTest is CaveatEnforcerBaseTest { assertFalse(erc1155.isApprovedForAll(delegator, operator)); } + ////////////////////////////// Redelegation ////////////////////////////// + + /** + * @notice Alice -> Bob -> Carol, with the `ApprovalRevocationEnforcer` caveat on Alice's (root) link. Carol + * redeems. The caveat's `beforeHook` receives `_delegator = Alice`, matching the account whose approval is + * actually cleared at execution time. Works end-to-end. + */ + function test_integration_redelegation_caveatOnRootLink_revokesRootAllowance() public { + assertEq(erc20.allowance(delegator, spender), 42 ether); + + Caveat[] memory caveats_ = new Caveat[](1); + caveats_[0] = Caveat({ args: hex"", enforcer: address(enforcer), terms: hex"" }); + Delegation memory aliceDelegation_ = Delegation({ + delegate: address(users.bob.deleGator), + delegator: delegator, + authority: ROOT_AUTHORITY, + caveats: caveats_, + salt: 0, + signature: hex"" + }); + aliceDelegation_ = signDelegation(users.alice, aliceDelegation_); + bytes32 aliceDelegationHash_ = EncoderLib._getDelegationHash(aliceDelegation_); + + Delegation memory bobDelegation_ = Delegation({ + delegate: address(users.carol.deleGator), + delegator: address(users.bob.deleGator), + authority: aliceDelegationHash_, + caveats: new Caveat[](0), + salt: 0, + signature: hex"" + }); + bobDelegation_ = signDelegation(users.bob, bobDelegation_); + + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = bobDelegation_; + delegations_[1] = aliceDelegation_; + + Execution memory execution_ = + Execution({ target: address(erc20), value: 0, callData: _approveCallData(spender, 0) }); + + invokeDelegation_UserOp(users.carol, delegations_, execution_); + assertEq(erc20.allowance(delegator, spender), 0); + } + + /** + * @notice Alice -> Bob -> Carol, with the caveat on Bob's (intermediate) link. The `beforeHook` runs with + * `_delegator = Bob`, so the pre-check queries `allowance(Bob, spender)`. Bob has no such allowance, so the + * hook reverts even though Alice (the root, whose account actually runs `approve`) does have one. + * + * @dev This test pins down a subtlety of redelegation semantics: caveats are evaluated against the delegator + * of their own link, not the root of the chain. For this enforcer it means an intermediate-link caveat + * checks the *intermediate* delegator's approval state, which is almost never what the delegator intends. + */ + function test_integration_redelegation_caveatOnIntermediateLink_revertsWhenIntermediateHasNoApproval() public { + assertEq(erc20.allowance(delegator, spender), 42 ether); + assertEq(erc20.allowance(address(users.bob.deleGator), spender), 0); + + Delegation memory aliceDelegation_ = Delegation({ + delegate: address(users.bob.deleGator), + delegator: delegator, + authority: ROOT_AUTHORITY, + caveats: new Caveat[](0), + salt: 0, + signature: hex"" + }); + aliceDelegation_ = signDelegation(users.alice, aliceDelegation_); + bytes32 aliceDelegationHash_ = EncoderLib._getDelegationHash(aliceDelegation_); + + Caveat[] memory caveats_ = new Caveat[](1); + caveats_[0] = Caveat({ args: hex"", enforcer: address(enforcer), terms: hex"" }); + Delegation memory bobDelegation_ = Delegation({ + delegate: address(users.carol.deleGator), + delegator: address(users.bob.deleGator), + authority: aliceDelegationHash_, + caveats: caveats_, + salt: 0, + signature: hex"" + }); + bobDelegation_ = signDelegation(users.bob, bobDelegation_); + + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = bobDelegation_; + delegations_[1] = aliceDelegation_; + + Execution memory execution_ = + Execution({ target: address(erc20), value: 0, callData: _approveCallData(spender, 0) }); + + // UserOp swallows the enforcer revert; the effect is that the approval is NOT cleared. + invokeDelegation_UserOp(users.carol, delegations_, execution_); + assertEq(erc20.allowance(delegator, spender), 42 ether); + } + + /** + * @notice Unit-level check on the link-local `_delegator` semantics. The hook queries the external token + * using whatever address is passed as `_delegator`; it does not reach back into the chain to find the root. + */ + function test_unit_beforeHook_usesPassedDelegatorNotRoot() public { + address intermediate_ = address(users.bob.deleGator); + assertEq(erc20.allowance(intermediate_, spender), 0); + + bytes memory executionCallData_ = _encodeSingle(address(erc20), 0, _approveCallData(spender, 0)); + + vm.prank(address(delegationManager)); + vm.expectRevert("ApprovalRevocationEnforcer:no-approval-to-revoke"); + enforcer.beforeHook(hex"", hex"", singleDefaultMode, executionCallData_, bytes32(0), intermediate_, address(0)); + + // And once Bob has an allowance of his own, the pre-check passes against Bob's state (regardless of who + // would actually execute the call). + vm.prank(intermediate_); + erc20.approve(spender, 1); + vm.prank(address(delegationManager)); + enforcer.beforeHook(hex"", hex"", singleDefaultMode, executionCallData_, bytes32(0), intermediate_, address(0)); + } + + ////////////////////////////// Additional coverage ////////////////////////////// + + /** + * @notice `approve(non-zero, 0)` targeting an ERC-721 contract routes to the ERC-20 branch (because the + * first parameter is non-zero). The pre-check calls `allowance(delegator, spender)` on the ERC-721, which + * does not implement it and therefore reverts (empty returndata after ABI-decode). + */ + function test_crossStandard_erc721TargetWithErc20Style_reverts() public { + bytes memory executionCallData_ = _encodeSingle(address(erc721), 0, _approveCallData(spender, 0)); + vm.prank(address(delegationManager)); + vm.expectRevert(); + enforcer.beforeHook(hex"", hex"", singleDefaultMode, executionCallData_, bytes32(0), delegator, address(0)); + } + + /** + * @notice `approve(address(0), 0)` on an ERC-20 is routed to the ERC-721 branch by the `firstParam == 0` + * heuristic. The branch then calls `getApproved(0)` on the target. Standard ERC-20s do not implement + * `getApproved`, so the pre-check reverts. Pins the behavior of this edge case so future refactors don't + * silently change routing. + */ + function test_edgeCase_approveAddressZeroAmountZeroOnErc20_reverts() public { + bytes memory executionCallData_ = _encodeSingle(address(erc20), 0, _approveCallData(address(0), 0)); + vm.prank(address(delegationManager)); + vm.expectRevert(); + enforcer.beforeHook(hex"", hex"", singleDefaultMode, executionCallData_, bytes32(0), delegator, address(0)); + } + function _getEnforcer() internal view override returns (ICaveatEnforcer) { return ICaveatEnforcer(address(enforcer)); } From 84896cd4112c4aca16084b7009d249236c907024 Mon Sep 17 00:00:00 2001 From: MoMannn Date: Wed, 22 Apr 2026 10:29:45 +0200 Subject: [PATCH 4/9] add docs and deployment script --- documents/CaveatEnforcers.md | 50 +++++++++++++++++++ script/DeployCaveatEnforcers.s.sol | 9 +++- .../verification/verify-enforcer-contracts.sh | 2 + 3 files changed, 59 insertions(+), 2 deletions(-) diff --git a/documents/CaveatEnforcers.md b/documents/CaveatEnforcers.md index 43c3b51c..339412e5 100644 --- a/documents/CaveatEnforcers.md +++ b/documents/CaveatEnforcers.md @@ -174,6 +174,56 @@ Note that in this scenario we have the same end recipient (treasury) and the sam If you are delegating to an EOA in a delegation chain, the EOA cannot execute directly since it cannot redeem inner delegations. The EOA can become a deleGator by using EIP7702 or it can use an adapter contract to execute the delegation. An example for that is available in `./src/helpers/DelegationMetaSwapAdapter.sol`. +### ApprovalRevocationEnforcer + +The `ApprovalRevocationEnforcer` lets a delegator grant a delegate the narrow authority to **clear an existing token approval** on the delegator's behalf, without granting any other power over the delegator's assets. It covers the three standard approval primitives: + +- ERC-20 `approve(spender, 0)` +- ERC-721 per-token `approve(address(0), tokenId)` +- ERC-721 / ERC-1155 `setApprovalForAll(operator, false)` (both standards share the selector) + +#### How It Works + +The enforcer runs only in single call type and default execution mode, consumes no terms, and makes no assumption about the target contract. In `beforeHook` it: + +1. Requires the execution to transfer zero native value and to carry calldata of exactly 68 bytes (4-byte selector + two 32-byte words). +2. Branches on the selector: + - `setApprovalForAll(address operator, bool approved)` — requires `approved == false` and `isApprovedForAll(delegator, operator) == true` on the target. + - `approve(address, uint256)` — shared by ERC-20 and ERC-721, disambiguated by the first parameter: + - First parameter is `address(0)` → treated as an ERC-721 per-token revocation; requires `getApproved(tokenId)` on the target to return a non-zero address. + - First parameter is non-zero → treated as an ERC-20 revocation; requires the second parameter (amount) to be zero and `allowance(delegator, spender) > 0` on the target. +3. Reverts on any other selector. + +All three accepted calldatas structurally reduce permissions (amount `0`, spender `address(0)`, or `approved` `false`). A delegate using this enforcer can therefore **never be granted new authority** over the delegator's assets — only existing approvals can be cleared. + +#### Use Cases + +- **Revocation bots / keepers**: Delegate to a third party that can proactively clean up stale or compromised approvals. +- **Post-incident remediation**: Issue a short-lived delegation to revoke a specific approval after a spender contract is found to be malicious. +- **User-facing "revoke all" flows**: Let a UI batch revocations on the user's behalf without asking for a new signature per clear. + +#### Composition + +The enforcer is not scoped to any particular token contract or spender. To restrict it further, compose it with existing enforcers: + +- `AllowedTargetsEnforcer` — restrict revocation to specific token contracts. +- `AllowedCalldataEnforcer` / `ExactCalldataEnforcer` — pin the exact spender, operator, or tokenId. + +#### Redelegation Caveat (Link-Local Semantics) + +The `_delegator` argument passed to `beforeHook` is the delegator of the specific delegation that carries the caveat, **not** the root of a redelegation chain. The `DelegationManager` always executes the downstream `approve` / `setApprovalForAll` call against the root delegator's account. On a root-level delegation (chain length 1) the two are the same and the pre-check queries the account whose storage will actually be mutated — this is the intended usage. + +On an intermediate (redelegation) link the two differ: the pre-check queries the intermediate delegator's approval state while the execution mutates the root delegator's storage. This is **never an authority escalation** (the structural constraints above still hold — the call can only reduce permissions), but the sanity guard becomes misaligned with the executed effect: + +- If the intermediate delegator has no matching approval, the hook reverts even when the root does (the chain cannot be used, even though the revocation would have been valid for the root). +- If the intermediate delegator happens to have some approval, the hook passes and the execution clears the root's approval regardless of whether the root actually had one to clear. + +If a redelegator needs a root-scoped guarantee (e.g. "Carol may only revoke one of Alice's specific approvals"), they should rely on structural caveats that compose cleanly across links, such as `AllowedTargetsEnforcer`, `AllowedCalldataEnforcer`, or `ExactCalldataEnforcer`. Placing `ApprovalRevocationEnforcer` on an intermediate link in the hope of validating the root's approval state does not achieve that. + +#### Liveness vs. Race-Freedom + +The "pre-existing approval" check is a liveness / sanity guard ensuring the call is not a no-op at the time the hook runs. It is not a race-free invariant: the delegator could independently clear the approval between the hook and the execution. In that case the execution is still safe — it simply becomes a no-op on the token contract. + ## LogicalOrWrapperEnforcer Context Switching The `LogicalOrWrapperEnforcer` enables logical OR functionality between groups of enforcers, allowing flexibility in delegation constraints. This enforcer is designed for a narrow set of use cases, and careful attention must be given when constructing caveats. The enforcer introduces an important architectural consideration: **context switching**. diff --git a/script/DeployCaveatEnforcers.s.sol b/script/DeployCaveatEnforcers.s.sol index cff28621..629da134 100644 --- a/script/DeployCaveatEnforcers.s.sol +++ b/script/DeployCaveatEnforcers.s.sol @@ -41,8 +41,10 @@ import { ValueLteEnforcer } from "../src/enforcers/ValueLteEnforcer.sol"; import { ERC20MultiOperationIncreaseBalanceEnforcer } from "../src/enforcers/ERC20MultiOperationIncreaseBalanceEnforcer.sol"; import { ERC721MultiOperationIncreaseBalanceEnforcer } from "../src/enforcers/ERC721MultiOperationIncreaseBalanceEnforcer.sol"; import { ERC1155MultiOperationIncreaseBalanceEnforcer } from "../src/enforcers/ERC1155MultiOperationIncreaseBalanceEnforcer.sol"; -import { NativeTokenMultiOperationIncreaseBalanceEnforcer } from - "../src/enforcers/NativeTokenMultiOperationIncreaseBalanceEnforcer.sol"; +import { + NativeTokenMultiOperationIncreaseBalanceEnforcer +} from "../src/enforcers/NativeTokenMultiOperationIncreaseBalanceEnforcer.sol"; +import { ApprovalRevocationEnforcer } from "../src/enforcers/ApprovalRevocationEnforcer.sol"; /** * @title DeployCaveatEnforcers @@ -183,6 +185,9 @@ contract DeployCaveatEnforcers is Script { deployedAddress = address(new NativeTokenMultiOperationIncreaseBalanceEnforcer{ salt: salt }()); console2.log("NativeTokenMultiOperationIncreaseBalanceEnforcer: %s", deployedAddress); + deployedAddress = address(new ApprovalRevocationEnforcer{ salt: salt }()); + console2.log("ApprovalRevocationEnforcer: %s", deployedAddress); + vm.stopBroadcast(); } } diff --git a/script/verification/verify-enforcer-contracts.sh b/script/verification/verify-enforcer-contracts.sh index b9cbc162..ef77e9a1 100755 --- a/script/verification/verify-enforcer-contracts.sh +++ b/script/verification/verify-enforcer-contracts.sh @@ -57,6 +57,7 @@ ENFORCERS=( "ERC721MultiOperationIncreaseBalanceEnforcer" "ERC1155MultiOperationIncreaseBalanceEnforcer" "NativeTokenMultiOperationIncreaseBalanceEnforcer" + "ApprovalRevocationEnforcer" ) ADDRESSES=( @@ -94,6 +95,7 @@ ADDRESSES=( "0x44877cDAFC0d529ab144bb6B0e202eE377C90229" "0x9eB86bbdaA71D4D8d5Fb1B8A9457F04D3344797b" "0xaD551E9b971C1b0c02c577bFfCFAA20b81777276" + "0x0000000000000000000000000000000000000000" ) ############################################################################### From 9583d15886bb99f9ce5a00b254dae5db0a648118 Mon Sep 17 00:00:00 2001 From: MoMannn Date: Thu, 23 Apr 2026 16:45:17 +0200 Subject: [PATCH 5/9] add terms for setting which function types we can use to revoke --- documents/CaveatEnforcers.md | 33 ++- src/enforcers/ApprovalRevocationEnforcer.sol | 63 ++++-- .../ApprovalRevocationEnforcer.t.sol | 203 +++++++++++++++--- 3 files changed, 249 insertions(+), 50 deletions(-) diff --git a/documents/CaveatEnforcers.md b/documents/CaveatEnforcers.md index 339412e5..e618ad87 100644 --- a/documents/CaveatEnforcers.md +++ b/documents/CaveatEnforcers.md @@ -182,17 +182,42 @@ The `ApprovalRevocationEnforcer` lets a delegator grant a delegate the narrow au - ERC-721 per-token `approve(address(0), tokenId)` - ERC-721 / ERC-1155 `setApprovalForAll(operator, false)` (both standards share the selector) +#### Terms + +The enforcer reads a **1-byte bitmask** from `terms` to control which revocation primitives the delegate may use: + +| Bit | Hex mask | Allowed primitive | +|-----|----------|-------------------| +| 0 | `0x01` | ERC-20 `approve(spender, 0)` | +| 1 | `0x02` | ERC-721 `approve(address(0), tokenId)` | +| 2 | `0x04` | `setApprovalForAll(operator, false)` (ERC-721 & ERC-1155) | +| 3–7 | — | Reserved; MUST be zero | + +- Terms MUST be exactly 1 byte. +- A zero mask (`0x00`) is rejected — at least one primitive must be permitted. +- Any reserved bit (3–7) set is rejected. +- `0x07` enables all three primitives. + +**Common examples:** + +``` +terms = 0x01 → ERC-20 revocations only +terms = 0x04 → operator (setApprovalForAll) revocations only +terms = 0x07 → all three primitives allowed +``` + #### How It Works -The enforcer runs only in single call type and default execution mode, consumes no terms, and makes no assumption about the target contract. In `beforeHook` it: +The enforcer runs only in single call type and default execution mode and makes no assumption about the target contract. In `beforeHook` it: -1. Requires the execution to transfer zero native value and to carry calldata of exactly 68 bytes (4-byte selector + two 32-byte words). -2. Branches on the selector: +1. Decodes and validates the 1-byte terms bitmask (rejects empty, zero, or reserved-bit-set terms). +2. Requires the execution to transfer zero native value and to carry calldata of exactly 68 bytes (4-byte selector + two 32-byte words). +3. Checks that the selector matches a permitted primitive (per the bitmask), then branches: - `setApprovalForAll(address operator, bool approved)` — requires `approved == false` and `isApprovedForAll(delegator, operator) == true` on the target. - `approve(address, uint256)` — shared by ERC-20 and ERC-721, disambiguated by the first parameter: - First parameter is `address(0)` → treated as an ERC-721 per-token revocation; requires `getApproved(tokenId)` on the target to return a non-zero address. - First parameter is non-zero → treated as an ERC-20 revocation; requires the second parameter (amount) to be zero and `allowance(delegator, spender) > 0` on the target. -3. Reverts on any other selector. +4. Reverts on any other selector. All three accepted calldatas structurally reduce permissions (amount `0`, spender `address(0)`, or `approved` `false`). A delegate using this enforcer can therefore **never be granted new authority** over the delegator's assets — only existing approvals can be cleared. diff --git a/src/enforcers/ApprovalRevocationEnforcer.sol b/src/enforcers/ApprovalRevocationEnforcer.sol index 45773aaa..599096f9 100644 --- a/src/enforcers/ApprovalRevocationEnforcer.sol +++ b/src/enforcers/ApprovalRevocationEnforcer.sol @@ -10,10 +10,20 @@ import { ModeCode } from "../utils/Types.sol"; /** * @title ApprovalRevocationEnforcer - * @notice Allows a delegate to clear an existing token approval. Covers the three standard approval primitives: - * - ERC-20 `approve(spender, 0)` - * - ERC-721 per-token `approve(address(0), tokenId)` - * - ERC-721 / ERC-1155 `setApprovalForAll(operator, false)` + * @notice Allows a delegate to clear existing token approvals. The delegator controls which of the three + * standard revocation primitives the delegate may perform via a 1-byte bitmask in `terms`: + * + * Bit 0 (`0x01`) — ERC-20 `approve(spender, 0)` (spender non-zero, amount zero) + * Bit 1 (`0x02`) — ERC-721 per-token `approve(address(0), tokenId)` + * Bit 2 (`0x04`) — ERC-721 / ERC-1155 `setApprovalForAll(operator, false)` + * Bits 3-7 — Reserved; MUST be zero. + * + * Examples: + * `0x01` — delegate may only clear ERC-20 allowances. + * `0x04` — delegate may only revoke operator approvals. + * `0x07` — delegate may use all three revocation primitives. + * + * Terms MUST be exactly 1 byte, MUST not be zero, and MUST NOT set any reserved bit. * * @dev ERC-721 and ERC-1155 intentionally share the `setApprovalForAll(address,bool)` selector; this enforcer * handles both via the `IERC721` interface (the selector and ABI are identical, so a typed `IERC1155` import is @@ -71,25 +81,36 @@ import { ModeCode } from "../utils/Types.sol"; * the hook runs. It is not a race-free invariant: the delegator could independently clear the approval between * the hook and the execution. In that case the execution is still safe — it simply becomes a no-op. * - * @dev This enforcer does not consume any terms and is not scoped to a specific target contract. Delegators who - * want to restrict revocation to specific tokens should compose this enforcer with `AllowedTargetsEnforcer`. + * @dev Delegators who want to restrict revocation to specific tokens should compose this enforcer with + * `AllowedTargetsEnforcer`. * * @dev This enforcer operates only in single call type and default execution mode. */ contract ApprovalRevocationEnforcer is CaveatEnforcer { using ExecutionLib for bytes; + ////////////////////////////// Constants ////////////////////////////// + + /// @dev Permission flags packed into the single-byte terms bitmask. + uint8 internal constant _PERMISSION_ERC20_APPROVE = 0x01; + uint8 internal constant _PERMISSION_ERC721_APPROVE = 0x02; + uint8 internal constant _PERMISSION_SET_APPROVAL_FOR_ALL = 0x04; + uint8 internal constant _PERMISSION_MASK = + _PERMISSION_ERC20_APPROVE | _PERMISSION_ERC721_APPROVE | _PERMISSION_SET_APPROVAL_FOR_ALL; + ////////////////////////////// Public Methods ////////////////////////////// /** - * @notice Requires the execution to revoke an existing token approval owned by `_delegator`. + * @notice Requires the execution to revoke an existing token approval owned by `_delegator`, and that the + * revocation primitive used is permitted by `_terms`. + * @param _terms 1-byte bitmask selecting which revocation primitives are allowed. See `getTermsInfo`. * @param _mode Must be single call type and default execution mode. * @param _executionCallData Single execution targeting the token contract. * @param _delegator The delegator of the delegation carrying this caveat (link-local, not the chain root). * See the contract-level NatSpec for the implications in redelegation chains. */ function beforeHook( - bytes calldata, + bytes calldata _terms, bytes calldata, ModeCode _mode, bytes calldata _executionCallData, @@ -103,6 +124,9 @@ contract ApprovalRevocationEnforcer is CaveatEnforcer { onlySingleCallTypeMode(_mode) onlyDefaultExecutionMode(_mode) { + // Validate terms and capture the raw flags byte (1 stack slot vs. 3 bools). + uint8 flags_ = _parseFlags(_terms); + (address target_, uint256 value_, bytes calldata callData_) = _executionCallData.decodeSingle(); require(value_ == 0, "ApprovalRevocationEnforcer:invalid-value"); @@ -110,20 +134,21 @@ contract ApprovalRevocationEnforcer is CaveatEnforcer { // `setApprovalForAll(address,bool)`. require(callData_.length == 68, "ApprovalRevocationEnforcer:invalid-execution-length"); - bytes4 selector_ = bytes4(callData_[0:4]); - if (selector_ == IERC721.setApprovalForAll.selector) { + if (bytes4(callData_[0:4]) == IERC721.setApprovalForAll.selector) { + require(flags_ & _PERMISSION_SET_APPROVAL_FOR_ALL != 0, "ApprovalRevocationEnforcer:permission-not-granted"); _validateOperatorRevocation(target_, callData_, _delegator); return; } - if (selector_ == IERC20.approve.selector) { + if (bytes4(callData_[0:4]) == IERC20.approve.selector) { // ERC-20 and ERC-721 share `approve(address,uint256)`. Disambiguate by the first parameter: ERC-721 // revokes via `approve(address(0), tokenId)`, while ERC-20 revokes via `approve(spender, 0)` with a // non-zero spender. - address firstParam_ = address(uint160(uint256(bytes32(callData_[4:36])))); - if (firstParam_ == address(0)) { + if (address(uint160(uint256(bytes32(callData_[4:36])))) == address(0)) { + require(flags_ & _PERMISSION_ERC721_APPROVE != 0, "ApprovalRevocationEnforcer:permission-not-granted"); _validateErc721Revocation(target_, callData_); } else { - _validateErc20Revocation(target_, callData_, _delegator, firstParam_); + require(flags_ & _PERMISSION_ERC20_APPROVE != 0, "ApprovalRevocationEnforcer:permission-not-granted"); + _validateErc20Revocation(target_, callData_, _delegator, address(uint160(uint256(bytes32(callData_[4:36]))))); } return; } @@ -132,6 +157,16 @@ contract ApprovalRevocationEnforcer is CaveatEnforcer { ////////////////////////////// Internal Methods ////////////////////////////// + /** + * @dev Validates and returns the raw permission flags byte. Reverts on invalid terms. + */ + function _parseFlags(bytes calldata _terms) private pure returns (uint8 flags_) { + require(_terms.length == 1, "ApprovalRevocationEnforcer:invalid-terms-length"); + flags_ = uint8(_terms[0]); + require(flags_ != 0, "ApprovalRevocationEnforcer:no-permissions"); + require(flags_ & ~_PERMISSION_MASK == 0, "ApprovalRevocationEnforcer:invalid-terms"); + } + /** * @dev Validates an ERC-20 `approve(spender, 0)` revocation. Requires `allowance(delegator, spender) > 0` on * the target. diff --git a/test/enforcers/ApprovalRevocationEnforcer.t.sol b/test/enforcers/ApprovalRevocationEnforcer.t.sol index f5f833b9..b7b02497 100644 --- a/test/enforcers/ApprovalRevocationEnforcer.t.sol +++ b/test/enforcers/ApprovalRevocationEnforcer.t.sol @@ -33,6 +33,12 @@ contract ApprovalRevocationEnforcerTest is CaveatEnforcerBaseTest { uint256 public mintedTokenId; + /// @dev Permission flag constants mirroring the contract. + uint8 internal constant PERMISSION_ERC20_APPROVE = 0x01; + uint8 internal constant PERMISSION_ERC721_APPROVE = 0x02; + uint8 internal constant PERMISSION_SET_APPROVAL_FOR_ALL = 0x04; + uint8 internal constant PERMISSION_ALL = 0x07; + ////////////////////////////// Set up ////////////////////////////// function setUp() public override { @@ -69,6 +75,10 @@ contract ApprovalRevocationEnforcerTest is CaveatEnforcerBaseTest { ////////////////////////////// Helpers ////////////////////////////// + function _terms(uint8 _flags) internal pure returns (bytes memory) { + return abi.encodePacked(_flags); + } + function _approveCallData(address _spender, uint256 _amount) internal pure returns (bytes memory) { return abi.encodeWithSelector(IERC20.approve.selector, _spender, _amount); } @@ -81,22 +91,122 @@ contract ApprovalRevocationEnforcerTest is CaveatEnforcerBaseTest { return ExecutionLib.encodeSingle(_target, _value, _callData); } - function _callBeforeHook(bytes memory _executionCallData) internal { + function _callBeforeHook(bytes memory _termsBytes, bytes memory _executionCallData) internal { vm.prank(address(delegationManager)); - enforcer.beforeHook(hex"", hex"", singleDefaultMode, _executionCallData, bytes32(0), delegator, address(0)); + enforcer.beforeHook(_termsBytes, hex"", singleDefaultMode, _executionCallData, bytes32(0), delegator, address(0)); } - function _expectRevertBeforeHook(bytes memory _executionCallData, bytes memory _revertReason) internal { + function _expectRevertBeforeHook(bytes memory _termsBytes, bytes memory _executionCallData, bytes memory _revertReason) internal { vm.prank(address(delegationManager)); vm.expectRevert(_revertReason); - enforcer.beforeHook(hex"", hex"", singleDefaultMode, _executionCallData, bytes32(0), delegator, address(0)); + enforcer.beforeHook(_termsBytes, hex"", singleDefaultMode, _executionCallData, bytes32(0), delegator, address(0)); + } + + ////////////////////////////// Terms decoding ////////////////////////////// + + function test_terms_revertOnEmptyTerms() public { + bytes memory executionCallData_ = _encodeSingle(address(erc20), 0, _approveCallData(spender, 0)); + _expectRevertBeforeHook(hex"", executionCallData_, "ApprovalRevocationEnforcer:invalid-terms-length"); + } + + function test_terms_revertOnWrongLength() public { + bytes memory executionCallData_ = _encodeSingle(address(erc20), 0, _approveCallData(spender, 0)); + _expectRevertBeforeHook(abi.encodePacked(uint16(0x0007)), executionCallData_, "ApprovalRevocationEnforcer:invalid-terms-length"); + } + + function test_terms_revertOnZeroMask() public { + bytes memory executionCallData_ = _encodeSingle(address(erc20), 0, _approveCallData(spender, 0)); + _expectRevertBeforeHook(_terms(0x00), executionCallData_, "ApprovalRevocationEnforcer:no-permissions"); + } + + function test_terms_revertOnReservedBitSet_bit3() public { + bytes memory executionCallData_ = _encodeSingle(address(erc20), 0, _approveCallData(spender, 0)); + _expectRevertBeforeHook(_terms(0x08), executionCallData_, "ApprovalRevocationEnforcer:invalid-terms"); + } + + function test_terms_revertOnReservedBitSet_highBit() public { + bytes memory executionCallData_ = _encodeSingle(address(erc20), 0, _approveCallData(spender, 0)); + _expectRevertBeforeHook(_terms(0x80), executionCallData_, "ApprovalRevocationEnforcer:invalid-terms"); + } + + function test_terms_revertOnReservedBitSet_allBits() public { + bytes memory executionCallData_ = _encodeSingle(address(erc20), 0, _approveCallData(spender, 0)); + _expectRevertBeforeHook(_terms(0xFF), executionCallData_, "ApprovalRevocationEnforcer:invalid-terms"); + } + + ////////////////////////////// Per-flag gating ////////////////////////////// + + function test_terms_onlyErc20_allowsErc20() public { + bytes memory executionCallData_ = _encodeSingle(address(erc20), 0, _approveCallData(spender, 0)); + _callBeforeHook(_terms(PERMISSION_ERC20_APPROVE), executionCallData_); + } + + function test_terms_onlyErc20_blocksErc721Approve() public { + bytes memory executionCallData_ = _encodeSingle(address(erc721), 0, _approveCallData(address(0), mintedTokenId)); + _expectRevertBeforeHook(_terms(PERMISSION_ERC20_APPROVE), executionCallData_, "ApprovalRevocationEnforcer:permission-not-granted"); + } + + function test_terms_onlyErc20_blocksSetApprovalForAll() public { + bytes memory executionCallData_ = _encodeSingle(address(erc721), 0, _setApprovalForAllCallData(operator, false)); + _expectRevertBeforeHook(_terms(PERMISSION_ERC20_APPROVE), executionCallData_, "ApprovalRevocationEnforcer:permission-not-granted"); + } + + function test_terms_onlyErc721Approve_allowsErc721() public { + bytes memory executionCallData_ = _encodeSingle(address(erc721), 0, _approveCallData(address(0), mintedTokenId)); + _callBeforeHook(_terms(PERMISSION_ERC721_APPROVE), executionCallData_); + } + + function test_terms_onlyErc721Approve_blocksErc20() public { + bytes memory executionCallData_ = _encodeSingle(address(erc20), 0, _approveCallData(spender, 0)); + _expectRevertBeforeHook(_terms(PERMISSION_ERC721_APPROVE), executionCallData_, "ApprovalRevocationEnforcer:permission-not-granted"); + } + + function test_terms_onlyErc721Approve_blocksSetApprovalForAll() public { + bytes memory executionCallData_ = _encodeSingle(address(erc721), 0, _setApprovalForAllCallData(operator, false)); + _expectRevertBeforeHook(_terms(PERMISSION_ERC721_APPROVE), executionCallData_, "ApprovalRevocationEnforcer:permission-not-granted"); + } + + function test_terms_onlySetApprovalForAll_allowsErc721() public { + bytes memory executionCallData_ = _encodeSingle(address(erc721), 0, _setApprovalForAllCallData(operator, false)); + _callBeforeHook(_terms(PERMISSION_SET_APPROVAL_FOR_ALL), executionCallData_); + } + + function test_terms_onlySetApprovalForAll_allowsErc1155() public { + bytes memory executionCallData_ = _encodeSingle(address(erc1155), 0, _setApprovalForAllCallData(operator, false)); + _callBeforeHook(_terms(PERMISSION_SET_APPROVAL_FOR_ALL), executionCallData_); + } + + function test_terms_onlySetApprovalForAll_blocksErc20Approve() public { + bytes memory executionCallData_ = _encodeSingle(address(erc20), 0, _approveCallData(spender, 0)); + _expectRevertBeforeHook(_terms(PERMISSION_SET_APPROVAL_FOR_ALL), executionCallData_, "ApprovalRevocationEnforcer:permission-not-granted"); + } + + function test_terms_onlySetApprovalForAll_blocksErc721Approve() public { + bytes memory executionCallData_ = _encodeSingle(address(erc721), 0, _approveCallData(address(0), mintedTokenId)); + _expectRevertBeforeHook(_terms(PERMISSION_SET_APPROVAL_FOR_ALL), executionCallData_, "ApprovalRevocationEnforcer:permission-not-granted"); + } + + function test_terms_pair_erc20AndErc721Approve_blocksSetApprovalForAll() public { + uint8 flags_ = PERMISSION_ERC20_APPROVE | PERMISSION_ERC721_APPROVE; + // Both approve variants allowed. + _callBeforeHook(_terms(flags_), _encodeSingle(address(erc20), 0, _approveCallData(spender, 0))); + _callBeforeHook(_terms(flags_), _encodeSingle(address(erc721), 0, _approveCallData(address(0), mintedTokenId))); + // setApprovalForAll blocked. + _expectRevertBeforeHook(_terms(flags_), _encodeSingle(address(erc721), 0, _setApprovalForAllCallData(operator, false)), "ApprovalRevocationEnforcer:permission-not-granted"); + } + + function test_terms_pair_erc20AndSetApprovalForAll_blocksErc721Approve() public { + uint8 flags_ = PERMISSION_ERC20_APPROVE | PERMISSION_SET_APPROVAL_FOR_ALL; + _callBeforeHook(_terms(flags_), _encodeSingle(address(erc20), 0, _approveCallData(spender, 0))); + _callBeforeHook(_terms(flags_), _encodeSingle(address(erc721), 0, _setApprovalForAllCallData(operator, false))); + _expectRevertBeforeHook(_terms(flags_), _encodeSingle(address(erc721), 0, _approveCallData(address(0), mintedTokenId)), "ApprovalRevocationEnforcer:permission-not-granted"); } ////////////////////////////// Valid cases (ERC-20 approve) ////////////////////////////// function test_erc20_revokeSucceedsForExistingAllowance() public { bytes memory executionCallData_ = _encodeSingle(address(erc20), 0, _approveCallData(spender, 0)); - _callBeforeHook(executionCallData_); + _callBeforeHook(_terms(PERMISSION_ALL), executionCallData_); } function test_erc20_revokeSucceedsForOneWeiAllowance() public { @@ -104,21 +214,21 @@ contract ApprovalRevocationEnforcerTest is CaveatEnforcerBaseTest { vm.prank(delegator); erc20.approve(other_, 1); bytes memory executionCallData_ = _encodeSingle(address(erc20), 0, _approveCallData(other_, 0)); - _callBeforeHook(executionCallData_); + _callBeforeHook(_terms(PERMISSION_ALL), executionCallData_); } ////////////////////////////// Invalid cases (ERC-20 approve) ////////////////////////////// function test_erc20_revertOnNonZeroAmount() public { bytes memory executionCallData_ = _encodeSingle(address(erc20), 0, _approveCallData(spender, 1)); - _expectRevertBeforeHook(executionCallData_, "ApprovalRevocationEnforcer:non-zero-amount"); + _expectRevertBeforeHook(_terms(PERMISSION_ALL), executionCallData_, "ApprovalRevocationEnforcer:non-zero-amount"); } function test_erc20_revertWhenNoApproval() public { address other_ = address(users.dave.deleGator); assertEq(erc20.allowance(delegator, other_), 0); bytes memory executionCallData_ = _encodeSingle(address(erc20), 0, _approveCallData(other_, 0)); - _expectRevertBeforeHook(executionCallData_, "ApprovalRevocationEnforcer:no-approval-to-revoke"); + _expectRevertBeforeHook(_terms(PERMISSION_ALL), executionCallData_, "ApprovalRevocationEnforcer:no-approval-to-revoke"); } function test_erc20_revertWhenAllowanceCallFails() public { @@ -127,14 +237,14 @@ contract ApprovalRevocationEnforcerTest is CaveatEnforcerBaseTest { bytes memory executionCallData_ = _encodeSingle(address(enforcer), 0, _approveCallData(spender, 0)); vm.prank(address(delegationManager)); vm.expectRevert(); - enforcer.beforeHook(hex"", hex"", singleDefaultMode, executionCallData_, bytes32(0), delegator, address(0)); + enforcer.beforeHook(_terms(PERMISSION_ALL), hex"", singleDefaultMode, executionCallData_, bytes32(0), delegator, address(0)); } ////////////////////////////// Valid cases (ERC-721 approve) ////////////////////////////// function test_erc721_revokeSucceedsForExistingApproval() public { bytes memory executionCallData_ = _encodeSingle(address(erc721), 0, _approveCallData(address(0), mintedTokenId)); - _callBeforeHook(executionCallData_); + _callBeforeHook(_terms(PERMISSION_ALL), executionCallData_); } ////////////////////////////// Invalid cases (ERC-721 approve) ////////////////////////////// @@ -147,7 +257,7 @@ contract ApprovalRevocationEnforcerTest is CaveatEnforcerBaseTest { assertEq(erc721.getApproved(freshTokenId_), address(0)); bytes memory executionCallData_ = _encodeSingle(address(erc721), 0, _approveCallData(address(0), freshTokenId_)); - _expectRevertBeforeHook(executionCallData_, "ApprovalRevocationEnforcer:no-approval-to-revoke"); + _expectRevertBeforeHook(_terms(PERMISSION_ALL), executionCallData_, "ApprovalRevocationEnforcer:no-approval-to-revoke"); } function test_erc721_revertWhenGetApprovedCallFails() public { @@ -156,7 +266,7 @@ contract ApprovalRevocationEnforcerTest is CaveatEnforcerBaseTest { bytes memory executionCallData_ = _encodeSingle(address(erc721), 0, _approveCallData(address(0), 9999)); vm.prank(address(delegationManager)); vm.expectRevert(abi.encodeWithSignature("ERC721NonexistentToken(uint256)", 9999)); - enforcer.beforeHook(hex"", hex"", singleDefaultMode, executionCallData_, bytes32(0), delegator, address(0)); + enforcer.beforeHook(_terms(PERMISSION_ALL), hex"", singleDefaultMode, executionCallData_, bytes32(0), delegator, address(0)); } ////////////////////////////// Valid cases (setApprovalForAll) ////////////////////////////// @@ -164,27 +274,27 @@ contract ApprovalRevocationEnforcerTest is CaveatEnforcerBaseTest { function test_setApprovalForAll_erc721_revokeSucceeds() public { assertTrue(erc721.isApprovedForAll(delegator, operator)); bytes memory executionCallData_ = _encodeSingle(address(erc721), 0, _setApprovalForAllCallData(operator, false)); - _callBeforeHook(executionCallData_); + _callBeforeHook(_terms(PERMISSION_ALL), executionCallData_); } function test_setApprovalForAll_erc1155_revokeSucceeds() public { assertTrue(erc1155.isApprovedForAll(delegator, operator)); bytes memory executionCallData_ = _encodeSingle(address(erc1155), 0, _setApprovalForAllCallData(operator, false)); - _callBeforeHook(executionCallData_); + _callBeforeHook(_terms(PERMISSION_ALL), executionCallData_); } ////////////////////////////// Invalid cases (setApprovalForAll) ////////////////////////////// function test_setApprovalForAll_revertWhenSettingTrue() public { bytes memory executionCallData_ = _encodeSingle(address(erc721), 0, _setApprovalForAllCallData(operator, true)); - _expectRevertBeforeHook(executionCallData_, "ApprovalRevocationEnforcer:not-a-revocation"); + _expectRevertBeforeHook(_terms(PERMISSION_ALL), executionCallData_, "ApprovalRevocationEnforcer:not-a-revocation"); } function test_setApprovalForAll_revertWhenNotApproved() public { address other_ = address(users.dave.deleGator); assertFalse(erc721.isApprovedForAll(delegator, other_)); bytes memory executionCallData_ = _encodeSingle(address(erc721), 0, _setApprovalForAllCallData(other_, false)); - _expectRevertBeforeHook(executionCallData_, "ApprovalRevocationEnforcer:no-approval-to-revoke"); + _expectRevertBeforeHook(_terms(PERMISSION_ALL), executionCallData_, "ApprovalRevocationEnforcer:no-approval-to-revoke"); } function test_setApprovalForAll_revertWhenIsApprovedForAllCallFails() public { @@ -193,43 +303,43 @@ contract ApprovalRevocationEnforcerTest is CaveatEnforcerBaseTest { bytes memory executionCallData_ = _encodeSingle(address(enforcer), 0, _setApprovalForAllCallData(operator, false)); vm.prank(address(delegationManager)); vm.expectRevert(); - enforcer.beforeHook(hex"", hex"", singleDefaultMode, executionCallData_, bytes32(0), delegator, address(0)); + enforcer.beforeHook(_terms(PERMISSION_ALL), hex"", singleDefaultMode, executionCallData_, bytes32(0), delegator, address(0)); } ////////////////////////////// Generic invalid cases ////////////////////////////// function test_revertOnNonZeroValue() public { bytes memory executionCallData_ = _encodeSingle(address(erc20), 1, _approveCallData(spender, 0)); - _expectRevertBeforeHook(executionCallData_, "ApprovalRevocationEnforcer:invalid-value"); + _expectRevertBeforeHook(_terms(PERMISSION_ALL), executionCallData_, "ApprovalRevocationEnforcer:invalid-value"); } function test_revertOnInvalidExecutionLengthShort() public { bytes memory shortCallData_ = abi.encodePacked(IERC20.approve.selector, bytes32(uint256(uint160(spender)))); bytes memory executionCallData_ = _encodeSingle(address(erc20), 0, shortCallData_); - _expectRevertBeforeHook(executionCallData_, "ApprovalRevocationEnforcer:invalid-execution-length"); + _expectRevertBeforeHook(_terms(PERMISSION_ALL), executionCallData_, "ApprovalRevocationEnforcer:invalid-execution-length"); } function test_revertOnInvalidExecutionLengthLong() public { bytes memory longCallData_ = abi.encodePacked(_approveCallData(spender, 0), bytes1(0x00)); bytes memory executionCallData_ = _encodeSingle(address(erc20), 0, longCallData_); - _expectRevertBeforeHook(executionCallData_, "ApprovalRevocationEnforcer:invalid-execution-length"); + _expectRevertBeforeHook(_terms(PERMISSION_ALL), executionCallData_, "ApprovalRevocationEnforcer:invalid-execution-length"); } function test_revertOnInvalidMethod() public { bytes memory wrongMethodCallData_ = abi.encodeWithSelector(IERC20.transfer.selector, spender, uint256(0)); bytes memory executionCallData_ = _encodeSingle(address(erc20), 0, wrongMethodCallData_); - _expectRevertBeforeHook(executionCallData_, "ApprovalRevocationEnforcer:invalid-method"); + _expectRevertBeforeHook(_terms(PERMISSION_ALL), executionCallData_, "ApprovalRevocationEnforcer:invalid-method"); } function test_revertWithInvalidCallTypeMode() public { bytes memory executionCallData_ = ExecutionLib.encodeBatch(new Execution[](2)); vm.expectRevert("CaveatEnforcer:invalid-call-type"); - enforcer.beforeHook(hex"", hex"", batchDefaultMode, executionCallData_, bytes32(0), delegator, address(0)); + enforcer.beforeHook(_terms(PERMISSION_ALL), hex"", batchDefaultMode, executionCallData_, bytes32(0), delegator, address(0)); } function test_revertWithInvalidExecutionMode() public { vm.expectRevert("CaveatEnforcer:invalid-execution-type"); - enforcer.beforeHook(hex"", hex"", singleTryMode, hex"", bytes32(0), delegator, address(0)); + enforcer.beforeHook(_terms(PERMISSION_ALL), hex"", singleTryMode, hex"", bytes32(0), delegator, address(0)); } ////////////////////////////// Integration ////////////////////////////// @@ -241,7 +351,7 @@ contract ApprovalRevocationEnforcerTest is CaveatEnforcerBaseTest { Execution({ target: address(erc20), value: 0, callData: _approveCallData(spender, 0) }); Caveat[] memory caveats_ = new Caveat[](1); - caveats_[0] = Caveat({ args: hex"", enforcer: address(enforcer), terms: hex"" }); + caveats_[0] = Caveat({ args: hex"", enforcer: address(enforcer), terms: _terms(PERMISSION_ALL) }); Delegation memory delegation_ = Delegation({ delegate: address(users.bob.deleGator), delegator: delegator, @@ -266,7 +376,7 @@ contract ApprovalRevocationEnforcerTest is CaveatEnforcerBaseTest { Execution({ target: address(erc721), value: 0, callData: _approveCallData(address(0), mintedTokenId) }); Caveat[] memory caveats_ = new Caveat[](1); - caveats_[0] = Caveat({ args: hex"", enforcer: address(enforcer), terms: hex"" }); + caveats_[0] = Caveat({ args: hex"", enforcer: address(enforcer), terms: _terms(PERMISSION_ALL) }); Delegation memory delegation_ = Delegation({ delegate: address(users.bob.deleGator), delegator: delegator, @@ -291,7 +401,7 @@ contract ApprovalRevocationEnforcerTest is CaveatEnforcerBaseTest { Execution({ target: address(erc1155), value: 0, callData: _setApprovalForAllCallData(operator, false) }); Caveat[] memory caveats_ = new Caveat[](1); - caveats_[0] = Caveat({ args: hex"", enforcer: address(enforcer), terms: hex"" }); + caveats_[0] = Caveat({ args: hex"", enforcer: address(enforcer), terms: _terms(PERMISSION_ALL) }); Delegation memory delegation_ = Delegation({ delegate: address(users.bob.deleGator), delegator: delegator, @@ -309,6 +419,35 @@ contract ApprovalRevocationEnforcerTest is CaveatEnforcerBaseTest { assertFalse(erc1155.isApprovedForAll(delegator, operator)); } + function test_integration_onlyErc20_revokesErc20AllowanceAndBlocksOtherPrimitives() public { + assertEq(erc20.allowance(delegator, spender), 42 ether); + + Caveat[] memory caveats_ = new Caveat[](1); + caveats_[0] = Caveat({ args: hex"", enforcer: address(enforcer), terms: _terms(PERMISSION_ERC20_APPROVE) }); + Delegation memory delegation_ = Delegation({ + delegate: address(users.bob.deleGator), + delegator: delegator, + authority: ROOT_AUTHORITY, + caveats: caveats_, + salt: 0, + signature: hex"" + }); + delegation_ = signDelegation(users.alice, delegation_); + + Delegation[] memory delegations_ = new Delegation[](1); + delegations_[0] = delegation_; + + // ERC-20 revocation succeeds. + Execution memory erc20Execution_ = Execution({ target: address(erc20), value: 0, callData: _approveCallData(spender, 0) }); + invokeDelegation_UserOp(users.bob, delegations_, erc20Execution_); + assertEq(erc20.allowance(delegator, spender), 0); + + // ERC-721 approve revocation is blocked (UserOp swallows revert; approval unchanged). + Execution memory erc721Execution_ = Execution({ target: address(erc721), value: 0, callData: _approveCallData(address(0), mintedTokenId) }); + invokeDelegation_UserOp(users.bob, delegations_, erc721Execution_); + assertEq(erc721.getApproved(mintedTokenId), spender); + } + ////////////////////////////// Redelegation ////////////////////////////// /** @@ -320,7 +459,7 @@ contract ApprovalRevocationEnforcerTest is CaveatEnforcerBaseTest { assertEq(erc20.allowance(delegator, spender), 42 ether); Caveat[] memory caveats_ = new Caveat[](1); - caveats_[0] = Caveat({ args: hex"", enforcer: address(enforcer), terms: hex"" }); + caveats_[0] = Caveat({ args: hex"", enforcer: address(enforcer), terms: _terms(PERMISSION_ALL) }); Delegation memory aliceDelegation_ = Delegation({ delegate: address(users.bob.deleGator), delegator: delegator, @@ -378,7 +517,7 @@ contract ApprovalRevocationEnforcerTest is CaveatEnforcerBaseTest { bytes32 aliceDelegationHash_ = EncoderLib._getDelegationHash(aliceDelegation_); Caveat[] memory caveats_ = new Caveat[](1); - caveats_[0] = Caveat({ args: hex"", enforcer: address(enforcer), terms: hex"" }); + caveats_[0] = Caveat({ args: hex"", enforcer: address(enforcer), terms: _terms(PERMISSION_ALL) }); Delegation memory bobDelegation_ = Delegation({ delegate: address(users.carol.deleGator), delegator: address(users.bob.deleGator), @@ -413,14 +552,14 @@ contract ApprovalRevocationEnforcerTest is CaveatEnforcerBaseTest { vm.prank(address(delegationManager)); vm.expectRevert("ApprovalRevocationEnforcer:no-approval-to-revoke"); - enforcer.beforeHook(hex"", hex"", singleDefaultMode, executionCallData_, bytes32(0), intermediate_, address(0)); + enforcer.beforeHook(_terms(PERMISSION_ALL), hex"", singleDefaultMode, executionCallData_, bytes32(0), intermediate_, address(0)); // And once Bob has an allowance of his own, the pre-check passes against Bob's state (regardless of who // would actually execute the call). vm.prank(intermediate_); erc20.approve(spender, 1); vm.prank(address(delegationManager)); - enforcer.beforeHook(hex"", hex"", singleDefaultMode, executionCallData_, bytes32(0), intermediate_, address(0)); + enforcer.beforeHook(_terms(PERMISSION_ALL), hex"", singleDefaultMode, executionCallData_, bytes32(0), intermediate_, address(0)); } ////////////////////////////// Additional coverage ////////////////////////////// @@ -434,7 +573,7 @@ contract ApprovalRevocationEnforcerTest is CaveatEnforcerBaseTest { bytes memory executionCallData_ = _encodeSingle(address(erc721), 0, _approveCallData(spender, 0)); vm.prank(address(delegationManager)); vm.expectRevert(); - enforcer.beforeHook(hex"", hex"", singleDefaultMode, executionCallData_, bytes32(0), delegator, address(0)); + enforcer.beforeHook(_terms(PERMISSION_ALL), hex"", singleDefaultMode, executionCallData_, bytes32(0), delegator, address(0)); } /** @@ -447,7 +586,7 @@ contract ApprovalRevocationEnforcerTest is CaveatEnforcerBaseTest { bytes memory executionCallData_ = _encodeSingle(address(erc20), 0, _approveCallData(address(0), 0)); vm.prank(address(delegationManager)); vm.expectRevert(); - enforcer.beforeHook(hex"", hex"", singleDefaultMode, executionCallData_, bytes32(0), delegator, address(0)); + enforcer.beforeHook(_terms(PERMISSION_ALL), hex"", singleDefaultMode, executionCallData_, bytes32(0), delegator, address(0)); } function _getEnforcer() internal view override returns (ICaveatEnforcer) { From 3f7906f99e9d0d5d964a55902d0eca828910f25f Mon Sep 17 00:00:00 2001 From: MoMannn Date: Tue, 28 Apr 2026 08:59:35 +0200 Subject: [PATCH 6/9] update error messages --- src/enforcers/ApprovalRevocationEnforcer.sol | 8 ++++---- .../enforcers/ApprovalRevocationEnforcer.t.sol | 18 +++++++++--------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/enforcers/ApprovalRevocationEnforcer.sol b/src/enforcers/ApprovalRevocationEnforcer.sol index 599096f9..eb3062c0 100644 --- a/src/enforcers/ApprovalRevocationEnforcer.sol +++ b/src/enforcers/ApprovalRevocationEnforcer.sol @@ -135,7 +135,7 @@ contract ApprovalRevocationEnforcer is CaveatEnforcer { require(callData_.length == 68, "ApprovalRevocationEnforcer:invalid-execution-length"); if (bytes4(callData_[0:4]) == IERC721.setApprovalForAll.selector) { - require(flags_ & _PERMISSION_SET_APPROVAL_FOR_ALL != 0, "ApprovalRevocationEnforcer:permission-not-granted"); + require(flags_ & _PERMISSION_SET_APPROVAL_FOR_ALL != 0, "ApprovalRevocationEnforcer:setApprovalForAll-not-allowed"); _validateOperatorRevocation(target_, callData_, _delegator); return; } @@ -144,10 +144,10 @@ contract ApprovalRevocationEnforcer is CaveatEnforcer { // revokes via `approve(address(0), tokenId)`, while ERC-20 revokes via `approve(spender, 0)` with a // non-zero spender. if (address(uint160(uint256(bytes32(callData_[4:36])))) == address(0)) { - require(flags_ & _PERMISSION_ERC721_APPROVE != 0, "ApprovalRevocationEnforcer:permission-not-granted"); + require(flags_ & _PERMISSION_ERC721_APPROVE != 0, "ApprovalRevocationEnforcer:erc721-approve-not-allowed"); _validateErc721Revocation(target_, callData_); } else { - require(flags_ & _PERMISSION_ERC20_APPROVE != 0, "ApprovalRevocationEnforcer:permission-not-granted"); + require(flags_ & _PERMISSION_ERC20_APPROVE != 0, "ApprovalRevocationEnforcer:erc20-approve-not-allowed"); _validateErc20Revocation(target_, callData_, _delegator, address(uint160(uint256(bytes32(callData_[4:36]))))); } return; @@ -163,7 +163,7 @@ contract ApprovalRevocationEnforcer is CaveatEnforcer { function _parseFlags(bytes calldata _terms) private pure returns (uint8 flags_) { require(_terms.length == 1, "ApprovalRevocationEnforcer:invalid-terms-length"); flags_ = uint8(_terms[0]); - require(flags_ != 0, "ApprovalRevocationEnforcer:no-permissions"); + require(flags_ != 0, "ApprovalRevocationEnforcer:no-methods-allowed"); require(flags_ & ~_PERMISSION_MASK == 0, "ApprovalRevocationEnforcer:invalid-terms"); } diff --git a/test/enforcers/ApprovalRevocationEnforcer.t.sol b/test/enforcers/ApprovalRevocationEnforcer.t.sol index b7b02497..e1483b90 100644 --- a/test/enforcers/ApprovalRevocationEnforcer.t.sol +++ b/test/enforcers/ApprovalRevocationEnforcer.t.sol @@ -116,7 +116,7 @@ contract ApprovalRevocationEnforcerTest is CaveatEnforcerBaseTest { function test_terms_revertOnZeroMask() public { bytes memory executionCallData_ = _encodeSingle(address(erc20), 0, _approveCallData(spender, 0)); - _expectRevertBeforeHook(_terms(0x00), executionCallData_, "ApprovalRevocationEnforcer:no-permissions"); + _expectRevertBeforeHook(_terms(0x00), executionCallData_, "ApprovalRevocationEnforcer:no-methods-allowed"); } function test_terms_revertOnReservedBitSet_bit3() public { @@ -143,12 +143,12 @@ contract ApprovalRevocationEnforcerTest is CaveatEnforcerBaseTest { function test_terms_onlyErc20_blocksErc721Approve() public { bytes memory executionCallData_ = _encodeSingle(address(erc721), 0, _approveCallData(address(0), mintedTokenId)); - _expectRevertBeforeHook(_terms(PERMISSION_ERC20_APPROVE), executionCallData_, "ApprovalRevocationEnforcer:permission-not-granted"); + _expectRevertBeforeHook(_terms(PERMISSION_ERC20_APPROVE), executionCallData_, "ApprovalRevocationEnforcer:erc721-approve-not-allowed"); } function test_terms_onlyErc20_blocksSetApprovalForAll() public { bytes memory executionCallData_ = _encodeSingle(address(erc721), 0, _setApprovalForAllCallData(operator, false)); - _expectRevertBeforeHook(_terms(PERMISSION_ERC20_APPROVE), executionCallData_, "ApprovalRevocationEnforcer:permission-not-granted"); + _expectRevertBeforeHook(_terms(PERMISSION_ERC20_APPROVE), executionCallData_, "ApprovalRevocationEnforcer:setApprovalForAll-not-allowed"); } function test_terms_onlyErc721Approve_allowsErc721() public { @@ -158,12 +158,12 @@ contract ApprovalRevocationEnforcerTest is CaveatEnforcerBaseTest { function test_terms_onlyErc721Approve_blocksErc20() public { bytes memory executionCallData_ = _encodeSingle(address(erc20), 0, _approveCallData(spender, 0)); - _expectRevertBeforeHook(_terms(PERMISSION_ERC721_APPROVE), executionCallData_, "ApprovalRevocationEnforcer:permission-not-granted"); + _expectRevertBeforeHook(_terms(PERMISSION_ERC721_APPROVE), executionCallData_, "ApprovalRevocationEnforcer:erc20-approve-not-allowed"); } function test_terms_onlyErc721Approve_blocksSetApprovalForAll() public { bytes memory executionCallData_ = _encodeSingle(address(erc721), 0, _setApprovalForAllCallData(operator, false)); - _expectRevertBeforeHook(_terms(PERMISSION_ERC721_APPROVE), executionCallData_, "ApprovalRevocationEnforcer:permission-not-granted"); + _expectRevertBeforeHook(_terms(PERMISSION_ERC721_APPROVE), executionCallData_, "ApprovalRevocationEnforcer:setApprovalForAll-not-allowed"); } function test_terms_onlySetApprovalForAll_allowsErc721() public { @@ -178,12 +178,12 @@ contract ApprovalRevocationEnforcerTest is CaveatEnforcerBaseTest { function test_terms_onlySetApprovalForAll_blocksErc20Approve() public { bytes memory executionCallData_ = _encodeSingle(address(erc20), 0, _approveCallData(spender, 0)); - _expectRevertBeforeHook(_terms(PERMISSION_SET_APPROVAL_FOR_ALL), executionCallData_, "ApprovalRevocationEnforcer:permission-not-granted"); + _expectRevertBeforeHook(_terms(PERMISSION_SET_APPROVAL_FOR_ALL), executionCallData_, "ApprovalRevocationEnforcer:erc20-approve-not-allowed"); } function test_terms_onlySetApprovalForAll_blocksErc721Approve() public { bytes memory executionCallData_ = _encodeSingle(address(erc721), 0, _approveCallData(address(0), mintedTokenId)); - _expectRevertBeforeHook(_terms(PERMISSION_SET_APPROVAL_FOR_ALL), executionCallData_, "ApprovalRevocationEnforcer:permission-not-granted"); + _expectRevertBeforeHook(_terms(PERMISSION_SET_APPROVAL_FOR_ALL), executionCallData_, "ApprovalRevocationEnforcer:erc721-approve-not-allowed"); } function test_terms_pair_erc20AndErc721Approve_blocksSetApprovalForAll() public { @@ -192,14 +192,14 @@ contract ApprovalRevocationEnforcerTest is CaveatEnforcerBaseTest { _callBeforeHook(_terms(flags_), _encodeSingle(address(erc20), 0, _approveCallData(spender, 0))); _callBeforeHook(_terms(flags_), _encodeSingle(address(erc721), 0, _approveCallData(address(0), mintedTokenId))); // setApprovalForAll blocked. - _expectRevertBeforeHook(_terms(flags_), _encodeSingle(address(erc721), 0, _setApprovalForAllCallData(operator, false)), "ApprovalRevocationEnforcer:permission-not-granted"); + _expectRevertBeforeHook(_terms(flags_), _encodeSingle(address(erc721), 0, _setApprovalForAllCallData(operator, false)), "ApprovalRevocationEnforcer:setApprovalForAll-not-allowed"); } function test_terms_pair_erc20AndSetApprovalForAll_blocksErc721Approve() public { uint8 flags_ = PERMISSION_ERC20_APPROVE | PERMISSION_SET_APPROVAL_FOR_ALL; _callBeforeHook(_terms(flags_), _encodeSingle(address(erc20), 0, _approveCallData(spender, 0))); _callBeforeHook(_terms(flags_), _encodeSingle(address(erc721), 0, _setApprovalForAllCallData(operator, false))); - _expectRevertBeforeHook(_terms(flags_), _encodeSingle(address(erc721), 0, _approveCallData(address(0), mintedTokenId)), "ApprovalRevocationEnforcer:permission-not-granted"); + _expectRevertBeforeHook(_terms(flags_), _encodeSingle(address(erc721), 0, _approveCallData(address(0), mintedTokenId)), "ApprovalRevocationEnforcer:erc721-approve-not-allowed"); } ////////////////////////////// Valid cases (ERC-20 approve) ////////////////////////////// From be5c72fb3eea8f04026d5e43dddda8243120c0c2 Mon Sep 17 00:00:00 2001 From: MoMannn Date: Mon, 4 May 2026 11:37:51 +0200 Subject: [PATCH 7/9] add permit2 approval revocation --- documents/CaveatEnforcers.md | 92 ++++-- src/enforcers/ApprovalRevocationEnforcer.sol | 253 +++++++++++--- .../ApprovalRevocationEnforcer.t.sol | 310 +++++++++++++++++- 3 files changed, 581 insertions(+), 74 deletions(-) diff --git a/documents/CaveatEnforcers.md b/documents/CaveatEnforcers.md index e618ad87..125dc68c 100644 --- a/documents/CaveatEnforcers.md +++ b/documents/CaveatEnforcers.md @@ -176,11 +176,16 @@ If you are delegating to an EOA in a delegation chain, the EOA cannot execute di ### ApprovalRevocationEnforcer -The `ApprovalRevocationEnforcer` lets a delegator grant a delegate the narrow authority to **clear an existing token approval** on the delegator's behalf, without granting any other power over the delegator's assets. It covers the three standard approval primitives: +The `ApprovalRevocationEnforcer` lets a delegator grant a delegate the narrow authority to **clear an existing token approval** on the delegator's behalf, without granting any other power over the delegator's assets. It covers six revocation primitives — three standard token-contract primitives and three against the canonical Permit2 deployment: - ERC-20 `approve(spender, 0)` - ERC-721 per-token `approve(address(0), tokenId)` - ERC-721 / ERC-1155 `setApprovalForAll(operator, false)` (both standards share the selector) +- Permit2 `approve(token, spender, 0, 0)` — single-pair on-chain allowance revocation +- Permit2 `lockdown((address,address)[])` — batched on-chain allowance revocation +- Permit2 `invalidateNonces(token, spender, newNonce)` — invalidate signed-but-unredeemed `permit` payloads + +The Permit2 branches are restricted to the canonical deployment at `0x000000000022D473030F116dDEE9F6B43aC78BA3` (deterministic across mainnet, Base, Arbitrum, Optimism, etc.). On chains where canonical Permit2 is not deployed, do not enable the Permit2 bits — see [Trust Assumptions](#trust-assumptions) below. #### Terms @@ -191,63 +196,104 @@ The enforcer reads a **1-byte bitmask** from `terms` to control which revocation | 0 | `0x01` | ERC-20 `approve(spender, 0)` | | 1 | `0x02` | ERC-721 `approve(address(0), tokenId)` | | 2 | `0x04` | `setApprovalForAll(operator, false)` (ERC-721 & ERC-1155) | -| 3–7 | — | Reserved; MUST be zero | +| 3 | `0x08` | Permit2 `approve(token, spender, 0, 0)` | +| 4 | `0x10` | Permit2 `lockdown((address,address)[])` | +| 5 | `0x20` | Permit2 `invalidateNonces(token, spender, newNonce)` | +| 6–7 | — | Reserved; MUST be zero | - Terms MUST be exactly 1 byte. - A zero mask (`0x00`) is rejected — at least one primitive must be permitted. -- Any reserved bit (3–7) set is rejected. -- `0x07` enables all three primitives. +- Any reserved bit (6–7) set is rejected. +- `0x3F` enables all six primitives. **Common examples:** ``` terms = 0x01 → ERC-20 revocations only terms = 0x04 → operator (setApprovalForAll) revocations only -terms = 0x07 → all three primitives allowed +terms = 0x08 → single-pair Permit2 revocations only +terms = 0x10 → batched Permit2 revocations only +terms = 0x20 → Permit2 nonce invalidation only +terms = 0x18 → both Permit2 on-chain revocation primitives +terms = 0x38 → all three Permit2 primitives (full Permit2 sever: on-chain allowance + pending signed permits) +terms = 0x3F → all six primitives allowed ``` +#### Permit2 Revocation Surface + +The three Permit2 primitives target different parts of Permit2's state, and **none of them subsumes the others**: + +| Primitive | Zeros `amount`? | Resets `expiration`? | Bumps `nonce`? | Invalidates pending signed permits? | +|-----------------------|-----------------|---------------------------------|----------------|-------------------------------------| +| `approve(_,_,0,0)` | yes | yes (set to `block.timestamp`) | no | no | +| `lockdown(pairs)` | yes | no | no | no | +| `invalidateNonces(…)` | no | no | yes | yes | + +To **fully sever** a delegator's Permit2 exposure to a `(token, spender)` pair, both an on-chain allowance revocation (bit 3 or 4) **and** a nonce invalidation (bit 5) are typically required. Enabling only on-chain revocation leaves any signed-but-unredeemed `permit` payloads live; enabling only nonce invalidation leaves the existing on-chain allowance intact. Bit-mask `0x38` enables all three. + +> **Note (DoS surface on bit 5).** A delegate granted `invalidateNonces` (bit `0x20`) can advance the stored nonce for any `(token, spender)` pair the caveat does not pin (Permit2 caps the per-call delta at `type(uint16).max`, but a determined delegate can repeat until `nonce == type(uint48).max`, after which the root delegator can no longer sign new permits for that pair). This is never an authority escalation — it can only invalidate, never create — but it is a denial-of-service vector for the delegator's future signed-permit flow. When granting bit 5, pin the `(token, spender)` pair via `AllowedCalldataEnforcer` / `ExactCalldataEnforcer` and/or rate-limit the delegation with `LimitedCallsEnforcer`. + #### How It Works -The enforcer runs only in single call type and default execution mode and makes no assumption about the target contract. In `beforeHook` it: +The enforcer runs only in single call type and default execution mode and makes no assumption about the target contract (other than the Permit2 branches, which require the canonical Permit2 address). In `beforeHook` it: 1. Decodes and validates the 1-byte terms bitmask (rejects empty, zero, or reserved-bit-set terms). -2. Requires the execution to transfer zero native value and to carry calldata of exactly 68 bytes (4-byte selector + two 32-byte words). -3. Checks that the selector matches a permitted primitive (per the bitmask), then branches: - - `setApprovalForAll(address operator, bool approved)` — requires `approved == false` and `isApprovedForAll(delegator, operator) == true` on the target. - - `approve(address, uint256)` — shared by ERC-20 and ERC-721, disambiguated by the first parameter: +2. Requires the execution to transfer zero native value and to carry at least 4 bytes of calldata. +3. Dispatches by selector and applies the permitted-primitive check (per the bitmask), then branches: + - **Permit2 `approve(address,address,uint160,uint48)`** — requires `target == _PERMIT2`, calldata length `== 132`, `amount == 0`, and `expiration == 0`. No on-chain liveness check is performed. + - **Permit2 `lockdown((address,address)[])`** — requires `target == _PERMIT2`. The calldata is otherwise unconstrained: every entry of the array structurally forces `amount = 0` for the corresponding `(token, spender)` pair (`expiration` and `nonce` are left untouched), so no parameter the delegate could supply can grant new authority. A malformed array reverts inside Permit2 itself. + - **Permit2 `invalidateNonces(address,address,uint48)`** — requires `target == _PERMIT2`. The calldata is otherwise unconstrained: Permit2 enforces strict nonce monotonicity (and a per-call delta capped at `type(uint16).max`), so the call can only invalidate signed-but-unredeemed `permit` payloads, never create or extend an allowance. + - **`setApprovalForAll(address operator, bool approved)`** (calldata length 68) — requires `approved == false` and `isApprovedForAll(delegator, operator) == true` on the target. + - **`approve(address, uint256)`** (calldata length 68, shared by ERC-20 and ERC-721) — disambiguated by the first parameter: - First parameter is `address(0)` → treated as an ERC-721 per-token revocation; requires `getApproved(tokenId)` on the target to return a non-zero address. - First parameter is non-zero → treated as an ERC-20 revocation; requires the second parameter (amount) to be zero and `allowance(delegator, spender) > 0` on the target. 4. Reverts on any other selector. -All three accepted calldatas structurally reduce permissions (amount `0`, spender `address(0)`, or `approved` `false`). A delegate using this enforcer can therefore **never be granted new authority** over the delegator's assets — only existing approvals can be cleared. +All six accepted calldatas structurally reduce permissions (amount `0`, spender `address(0)`, `approved` `false`, per-pair Permit2 amount zeroing, or strictly monotonic Permit2 nonce bump). A delegate using this enforcer can therefore **never be granted new authority** over the delegator's assets — only existing approvals can be cleared and pending Permit2 signatures invalidated. + +#### Liveness vs. Race-Freedom + +The ERC-20, ERC-721, and `setApprovalForAll` branches each include a "pre-existing approval" check on the target token contract. This is a liveness / sanity guard ensuring the call is not a no-op at the time the hook runs. It is **not** a race-free invariant: the delegator could independently clear the approval between the hook and the execution. In that case the execution is still safe — it simply becomes a no-op on the token contract. + +The three Permit2 branches intentionally **omit** this on-chain liveness pre-check. Permit2 silently overwrites any existing allowance, so a call against a `(token, spender)` pair with no live allowance is a harmless no-op (or, for `invalidateNonces` against a triple whose stored nonce already meets the new value, reverts inside Permit2 itself). The structural constraints (canonical Permit2 target, fixed selector, and — for `approve` — zero amount and zero expiration) already guarantee the call can only reduce permissions. + +#### Trust Assumptions + +The Permit2 branches assume the canonical Uniswap-deployed Permit2 contract is at `_PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3` on the target chain. On chains where Uniswap has deployed Permit2 this is a safe deterministic address. On chains where canonical Permit2 is **not** deployed: + +- if the address is empty, the executor's call returns successfully with no effect (harmless no-op); +- if a *different* contract happens to live at that address, the selector dispatches into whatever that contract does. The `approve(0, 0)` branch is partially self-protected by its structural calldata checks (any contract under that selector would have to interpret the layout identically to grant authority), but `lockdown` and `invalidateNonces` have no such structural moat. + +Delegators on chains without canonical Permit2 should NOT enable bits 3, 4, or 5. #### Use Cases - **Revocation bots / keepers**: Delegate to a third party that can proactively clean up stale or compromised approvals. -- **Post-incident remediation**: Issue a short-lived delegation to revoke a specific approval after a spender contract is found to be malicious. -- **User-facing "revoke all" flows**: Let a UI batch revocations on the user's behalf without asking for a new signature per clear. +- **Post-incident remediation**: Issue a short-lived delegation to revoke a specific approval after a spender contract is found to be malicious. For Permit2, combine bit 3/4 (on-chain) with bit 5 (signature invalidation) to fully sever the spender. +- **User-facing "revoke all" flows**: Let a UI batch revocations on the user's behalf without asking for a new signature per clear. `lockdown` is particularly useful here for clearing many Permit2 allowances in a single transaction; pair it with `invalidateNonces` if the user also wants to kill any outstanding signed permits. #### Composition -The enforcer is not scoped to any particular token contract or spender. To restrict it further, compose it with existing enforcers: +The enforcer is intentionally not scoped to any particular spender, operator, or `(token, spender)` pair. To restrict it further, compose it with existing enforcers: -- `AllowedTargetsEnforcer` — restrict revocation to specific token contracts. -- `AllowedCalldataEnforcer` / `ExactCalldataEnforcer` — pin the exact spender, operator, or tokenId. +- `AllowedTargetsEnforcer` — restrict revocation to specific token contracts. Note that for the Permit2 branches the target is already pinned to the canonical Permit2 address by the enforcer itself. +- `AllowedCalldataEnforcer` / `ExactCalldataEnforcer` — pin the exact spender, operator, tokenId, or `(token, spender, newNonce)` triple. For the static branches (`approve`, `setApprovalForAll`, Permit2 `approve`, Permit2 `invalidateNonces`) these compare cleanly against fixed offsets. For Permit2 `lockdown` the calldata is dynamic (offset + array length + entries), so `ExactCalldataEnforcer` is usually the cleaner option for pinning a specific list of pairs. #### Redelegation Caveat (Link-Local Semantics) -The `_delegator` argument passed to `beforeHook` is the delegator of the specific delegation that carries the caveat, **not** the root of a redelegation chain. The `DelegationManager` always executes the downstream `approve` / `setApprovalForAll` call against the root delegator's account. On a root-level delegation (chain length 1) the two are the same and the pre-check queries the account whose storage will actually be mutated — this is the intended usage. +The `_delegator` argument passed to `beforeHook` is the delegator of the specific delegation that carries the caveat, **not** the root of a redelegation chain. The `DelegationManager` always executes the downstream call against the root delegator's account. On a root-level delegation (chain length 1) the two are the same and the pre-check queries the account whose storage will actually be mutated — this is the intended usage. -On an intermediate (redelegation) link the two differ: the pre-check queries the intermediate delegator's approval state while the execution mutates the root delegator's storage. This is **never an authority escalation** (the structural constraints above still hold — the call can only reduce permissions), but the sanity guard becomes misaligned with the executed effect: +On an intermediate (redelegation) link the two differ. The implications are different per primitive group: -- If the intermediate delegator has no matching approval, the hook reverts even when the root does (the chain cannot be used, even though the revocation would have been valid for the root). -- If the intermediate delegator happens to have some approval, the hook passes and the execution clears the root's approval regardless of whether the root actually had one to clear. +- **ERC-20 / ERC-721 / `setApprovalForAll` branches** — the pre-check queries the intermediate delegator's approval state while the execution mutates the root delegator's storage. Concretely: + - If the intermediate delegator has no matching approval, the hook reverts even when the root does (the chain cannot be used, even though the revocation would have been valid for the root). + - If the intermediate delegator happens to have some approval, the hook passes and the execution clears the root's approval regardless of whether the root actually had one to clear. -If a redelegator needs a root-scoped guarantee (e.g. "Carol may only revoke one of Alice's specific approvals"), they should rely on structural caveats that compose cleanly across links, such as `AllowedTargetsEnforcer`, `AllowedCalldataEnforcer`, or `ExactCalldataEnforcer`. Placing `ApprovalRevocationEnforcer` on an intermediate link in the hope of validating the root's approval state does not achieve that. +- **Permit2 branches** — no per-delegator pre-check is performed. On an intermediate link the link-local sanity guard is simply absent: the hook always passes (subject only to the per-flag and target checks), and the executed call zeros / bumps the root delegator's Permit2 state for whatever `(token, spender)` pair the delegate supplies. Composition with `AllowedCalldataEnforcer` / `ExactCalldataEnforcer` to pin the pair is therefore **load-bearing** — not belt-and-suspenders — for any redelegated Permit2 caveat. -#### Liveness vs. Race-Freedom +Neither case is an authority escalation (the structural constraints above still hold — the call can only reduce permissions), but the sanity guard is misaligned with the executed effect for the standard branches and absent entirely for the Permit2 branches. -The "pre-existing approval" check is a liveness / sanity guard ensuring the call is not a no-op at the time the hook runs. It is not a race-free invariant: the delegator could independently clear the approval between the hook and the execution. In that case the execution is still safe — it simply becomes a no-op on the token contract. +If a redelegator needs a root-scoped guarantee (e.g. "Carol may only revoke one of Alice's specific approvals"), they should rely on structural caveats that compose cleanly across links, such as `AllowedTargetsEnforcer`, `AllowedCalldataEnforcer`, or `ExactCalldataEnforcer`. Placing `ApprovalRevocationEnforcer` on an intermediate link in the hope of validating the root's approval state does not achieve that. ## LogicalOrWrapperEnforcer Context Switching diff --git a/src/enforcers/ApprovalRevocationEnforcer.sol b/src/enforcers/ApprovalRevocationEnforcer.sol index eb3062c0..4104c8d1 100644 --- a/src/enforcers/ApprovalRevocationEnforcer.sol +++ b/src/enforcers/ApprovalRevocationEnforcer.sol @@ -10,18 +10,27 @@ import { ModeCode } from "../utils/Types.sol"; /** * @title ApprovalRevocationEnforcer - * @notice Allows a delegate to clear existing token approvals. The delegator controls which of the three - * standard revocation primitives the delegate may perform via a 1-byte bitmask in `terms`: + * @notice Allows a delegate to clear existing token approvals. The delegator controls which revocation + * primitives the delegate may perform via a 1-byte bitmask in `terms`: * * Bit 0 (`0x01`) — ERC-20 `approve(spender, 0)` (spender non-zero, amount zero) * Bit 1 (`0x02`) — ERC-721 per-token `approve(address(0), tokenId)` * Bit 2 (`0x04`) — ERC-721 / ERC-1155 `setApprovalForAll(operator, false)` - * Bits 3-7 — Reserved; MUST be zero. + * Bit 3 (`0x08`) — Permit2 `approve(token, spender, 0, 0)` against the canonical Permit2 deployment + * Bit 4 (`0x10`) — Permit2 `lockdown((address,address)[])` against the canonical Permit2 deployment + * Bit 5 (`0x20`) — Permit2 `invalidateNonces(token, spender, newNonce)` against the canonical Permit2 deployment + * Bits 6-7 — Reserved; MUST be zero. * * Examples: * `0x01` — delegate may only clear ERC-20 allowances. * `0x04` — delegate may only revoke operator approvals. - * `0x07` — delegate may use all three revocation primitives. + * `0x08` — delegate may only revoke a single Permit2 allowance per call. + * `0x10` — delegate may only batch-revoke Permit2 allowances via `lockdown`. + * `0x20` — delegate may only invalidate Permit2 nonces (kill pending signed permits). + * `0x38` — delegate may use all three Permit2 primitives (`approve` + `lockdown` + `invalidateNonces`), + * which together fully sever a Permit2 (token, spender) exposure (on-chain allowance + pending + * signed permits). + * `0x3F` — delegate may use all six revocation primitives. * * Terms MUST be exactly 1 byte, MUST not be zero, and MUST NOT set any reserved bit. * @@ -30,34 +39,96 @@ import { ModeCode } from "../utils/Types.sol"; * unnecessary for the external call). ERC-20 and ERC-721 likewise share the `approve(address,uint256)` selector, * and are disambiguated by inspecting the first parameter (see branching rules below). * - * @dev The execution must transfer zero native value and carry one of the supported approval calldatas (length 68 - * bytes: 4-byte selector + two 32-byte words). Branching is determined as follows: - * - selector `setApprovalForAll(address operator, bool approved)`: - * - `approved` MUST be false, and - * - `isApprovedForAll(delegator, operator)` MUST currently be true on the target. - * - selector `approve(address, uint256)` (shared by ERC-20 and ERC-721): - * - if the first parameter is `address(0)` the call is treated as an ERC-721 per-token revocation: - * - `getApproved(tokenId)` on the target MUST currently return a non-zero address. - * - otherwise the call is treated as an ERC-20 revocation: - * - the second parameter (amount) MUST be zero, and - * - `allowance(delegator, spender)` on the target MUST currently return non-zero. - * - * @dev All three accepted calldatas structurally result in a net reduction of permissions on the target (amount - * `0`, spender `address(0)`, or `approved` `false`). A delegate using this enforcer can therefore never be granted - * new authority over the delegator's assets — only existing approvals can be cleared. + * @dev The execution must transfer zero native value and carry one of the supported approval calldatas. Branching + * is determined as follows: + * - selector `approve(address token, address spender, uint160 amount, uint48 expiration)` (Permit2): + * - `target` MUST equal the canonical Permit2 deployment (`_PERMIT2`), and + * - calldata length MUST be 132 bytes (4-byte selector + four 32-byte words), and + * - the third parameter (amount) MUST be zero, and + * - the fourth parameter (expiration) MUST be zero. + * - selector `lockdown((address,address)[])` (Permit2): + * - `target` MUST equal the canonical Permit2 deployment (`_PERMIT2`). + * - The calldata is otherwise unconstrained: every entry of the array structurally forces the corresponding + * `(token, spender)` Permit2 allowance `amount` to zero (`expiration` and `nonce` are left untouched). There + * is no parameter the delegate could supply to grant new authority, so no further calldata validation is + * performed. + * - selector `invalidateNonces(address,address,uint48)` (Permit2): + * - `target` MUST equal the canonical Permit2 deployment (`_PERMIT2`). + * - The calldata is otherwise unconstrained: Permit2's `invalidateNonces` strictly monotonically increases the + * stored nonce for the `(caller, token, spender)` triple (it reverts if `newNonce <= oldNonce` and caps the + * per-call delta at `type(uint16).max`). It cannot create or extend an allowance, so no further calldata + * validation is performed. + * - calldata length 68 bytes (4-byte selector + two 32-byte words), shared by `approve(address,uint256)` and + * `setApprovalForAll(address,bool)`: + * - selector `setApprovalForAll(address operator, bool approved)`: + * - `approved` MUST be false, and + * - `isApprovedForAll(delegator, operator)` MUST currently be true on the target. + * - selector `approve(address, uint256)` (shared by ERC-20 and ERC-721): + * - if the first parameter is `address(0)` the call is treated as an ERC-721 per-token revocation: + * - `getApproved(tokenId)` on the target MUST currently return a non-zero address. + * - otherwise the call is treated as an ERC-20 revocation: + * - the second parameter (amount) MUST be zero, and + * - `allowance(delegator, spender)` on the target MUST currently return non-zero. + * + * @dev All six accepted calldatas structurally result in a net reduction of permissions on the target (amount + * `0`, spender `address(0)`, `approved` `false`, per-pair Permit2 amount zeroing, or strictly monotonic Permit2 + * nonce bump). A delegate using this enforcer can therefore never be granted new authority over the delegator's + * assets — only existing approvals can be cleared and pending Permit2 signatures invalidated. + * + * @dev Unlike the ERC-20 / ERC-721 / `setApprovalForAll` primitives, the three Permit2 branches perform no + * on-chain liveness pre-check. The structural constraints (canonical Permit2 target, fixed selector, and — for + * `approve` — zero amount and zero expiration) already guarantee the call can only reduce permissions; if no + * Permit2 state exists for the targeted `(token, spender)` pair(s) the execution is either a harmless no-op or + * (for `invalidateNonces`) reverts inside Permit2. Restrict which pairs the delegate may target by composing + * this enforcer with `AllowedCalldataEnforcer` or `ExactCalldataEnforcer`. Note that for `lockdown` such pinning + * also has to fix the array length and ABI head words, since the calldata is dynamic; `ExactCalldataEnforcer` + * is usually the cleaner option there. + * + * @dev Permit2 revocation surface — what each primitive does and does not cover: + * - `approve(token, spender, 0, 0)` zeros `amount` and sets `expiration` to `block.timestamp` for the caller's + * `(token, spender)` allowance. Pending signed permits are NOT invalidated (their `nonce` is unaffected). + * - `lockdown` zeros `amount` only. `expiration` and `nonce` are unchanged. Pending signed permits are NOT + * invalidated. + * - `invalidateNonces` strictly monotonically increases the stored `nonce`, rendering all signed-but-unredeemed + * `permit` payloads with a now-stale nonce uncollectable. It does NOT zero on-chain `amount` or `expiration`. + * To fully sever Permit2 exposure to a spender, both an on-chain allowance revocation (bit 3 or 4) AND a + * nonce invalidation (bit 5) are typically required. Enabling only one leaves the other vector live. + * + * @dev DoS surface on bit 5 (`invalidateNonces`). A delegate granted bit 5 can advance the stored nonce for any + * `(token, spender)` pair the caveat does not pin (Permit2 caps the per-call delta at `type(uint16).max`, but a + * determined delegate can repeat until `nonce == type(uint48).max`, after which the root delegator can no longer + * sign new permits for that pair). This is never an authority escalation — it can only invalidate, never create — + * but it is a denial-of-service vector for the delegator's future signed-permit flow. When granting bit 5, pin the + * `(token, spender)` pair via `AllowedCalldataEnforcer` / `ExactCalldataEnforcer` and/or rate-limit the delegation + * with `LimitedCallsEnforcer`. + * + * @dev Trust assumption — canonical Permit2 deployment. + * + * The Permit2 branches assume the canonical Uniswap-deployed Permit2 contract is at `_PERMIT2` on the target + * chain. On chains where Uniswap has deployed Permit2 (mainnet, Base, Arbitrum, Optimism, Polygon, BNB, Avalanche, + * etc.) this is a safe deterministic address. On chains where canonical Permit2 is NOT deployed: + * - if the address is empty, the executor's call returns successfully with no effect (harmless no-op); + * - if a *different* contract happens to live at that address, the selector dispatches into whatever that + * contract does. The `approve(0, 0)` branch is partially self-protected by its structural calldata checks + * (any contract under that selector would have to interpret the layout identically to grant authority), but + * `lockdown` and `invalidateNonces` have no such structural moat. + * Delegators on chains without canonical Permit2 should NOT enable bits 3, 4, or 5. * * @dev REDELEGATION WARNING — link-local pre-check vs. root-level execution. * * The `_delegator` argument passed to `beforeHook` is the delegator of the specific delegation that carries the - * caveat, not the root of a redelegation chain. The DelegationManager always executes the downstream - * `approve` / `setApprovalForAll` call against the *root* delegator's account (the account at the end of the - * leaf-to-root chain). On a root-level delegation (chain length 1) the two are the same and the pre-check - * queries the account whose storage will actually be mutated — this is the intended usage. + * caveat, not the root of a redelegation chain. The DelegationManager always executes the downstream call against + * the *root* delegator's account (the account at the end of the leaf-to-root chain). On a root-level delegation + * (chain length 1) the two are the same and the pre-check queries the account whose storage will actually be + * mutated — this is the intended usage. + * + * On an intermediate (redelegation) link the two differ. The implications are different per primitive group: + * + * (a) ERC-20 / ERC-721 / `setApprovalForAll` branches — pre-check is link-local. * - * On an intermediate (redelegation) link the two differ: the pre-check queries the *intermediate* delegator's - * approval state, while the execution mutates the *root* delegator's storage. A redelegator adding this caveat - * to constrain their delegate is very likely expecting the pre-check to run against the root (the account whose - * approval will be cleared). That expectation is wrong — the check is link-local. + * The pre-check queries the *intermediate* delegator's approval state, while the execution mutates the *root* + * delegator's storage. A redelegator adding this caveat to constrain their delegate is very likely expecting the + * pre-check to run against the root. That expectation is wrong — the check is link-local. * * Concrete example. Alice -> Bob -> Carol. Alice's link has no caveat (Bob has full authority over Alice). * Bob places this enforcer on his delegation to Carol, intending "Carol can only revoke an existing approval on @@ -67,9 +138,17 @@ import { ModeCode } from "../utils/Types.sol"; * - if Bob happens to have some allowance, the hook passes and the execution clears Alice's allowance — * independently of whether Alice actually had an allowance to clear. * - * This is never an authority escalation (the structural constraints above still apply — the call can only - * reduce permissions), but the sanity guard is misaligned with the executed effect and will behave - * unintuitively for anyone reading "the delegator's approval must exist" as a check on the root. + * (b) Permit2 branches — no pre-check at all. + * + * The Permit2 branches do not consult `_delegator` (no on-chain liveness check is performed). On an intermediate + * link this means the link-local sanity guard that exists for the ERC-20/721/operator branches is simply absent: + * the hook always passes (subject only to the per-flag and target checks), and the executed call zeros / bumps + * the *root* delegator's Permit2 state for whatever `(token, spender)` pair the delegate supplies. + * + * Neither (a) nor (b) is an authority escalation (the structural constraints above still apply — the call can + * only reduce permissions). But the sanity guard is misaligned with the executed effect, and for the Permit2 + * branches it is absent entirely. Composition with `AllowedCalldataEnforcer` / `ExactCalldataEnforcer` to pin the + * `(token, spender)` pair is therefore load-bearing for any redelegated Permit2 caveat. * * If a redelegator needs a root-scoped guarantee (e.g. "Carol may only revoke one of Alice's specific * approvals") they should rely on structural caveats that compose cleanly across links, such as @@ -91,19 +170,52 @@ contract ApprovalRevocationEnforcer is CaveatEnforcer { ////////////////////////////// Constants ////////////////////////////// - /// @dev Permission flags packed into the single-byte terms bitmask. + /** + * @dev Permission flags packed into the single-byte terms bitmask. + */ uint8 internal constant _PERMISSION_ERC20_APPROVE = 0x01; uint8 internal constant _PERMISSION_ERC721_APPROVE = 0x02; uint8 internal constant _PERMISSION_SET_APPROVAL_FOR_ALL = 0x04; - uint8 internal constant _PERMISSION_MASK = - _PERMISSION_ERC20_APPROVE | _PERMISSION_ERC721_APPROVE | _PERMISSION_SET_APPROVAL_FOR_ALL; + uint8 internal constant _PERMISSION_PERMIT2_APPROVE = 0x08; + uint8 internal constant _PERMISSION_PERMIT2_LOCKDOWN = 0x10; + uint8 internal constant _PERMISSION_PERMIT2_INVALIDATE_NONCES = 0x20; + uint8 internal constant _PERMISSION_MASK = _PERMISSION_ERC20_APPROVE | _PERMISSION_ERC721_APPROVE + | _PERMISSION_SET_APPROVAL_FOR_ALL | _PERMISSION_PERMIT2_APPROVE | _PERMISSION_PERMIT2_LOCKDOWN + | _PERMISSION_PERMIT2_INVALIDATE_NONCES; + + /** + * @dev Canonical Permit2 deployment address (deterministic across EVM chains where Uniswap has deployed it, + * e.g. mainnet, Base, Arbitrum, Optimism, etc.). See the contract-level "Trust assumption" NatSpec for the + * implications on chains where canonical Permit2 is not deployed. + */ + address internal constant _PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3; + + /** + * @dev `bytes4(keccak256("approve(address,address,uint160,uint48)"))` — Permit2's `approve` selector. + */ + bytes4 internal constant _PERMIT2_APPROVE_SELECTOR = 0x87517c45; + + /** + * @dev `bytes4(keccak256("lockdown((address,address)[])"))` — Permit2's batch revocation selector. Every entry + * of the array unconditionally zeros `amount` for the corresponding `(token, spender)` pair on the caller; + * `expiration` and `nonce` are left untouched. No parameter can be used to grant authority. + */ + bytes4 internal constant _PERMIT2_LOCKDOWN_SELECTOR = 0xcc53287f; + + /** + * @dev `bytes4(keccak256("invalidateNonces(address,address,uint48)"))` — Permit2's nonce-invalidation + * selector. The new nonce is required by Permit2 to be strictly greater than the current nonce (with a + * per-call delta capped at `type(uint16).max`); it can therefore only invalidate signed-but-unredeemed + * `permit` payloads, never create or extend an allowance. + */ + bytes4 internal constant _PERMIT2_INVALIDATE_NONCES_SELECTOR = 0x65d9723c; ////////////////////////////// Public Methods ////////////////////////////// /** * @notice Requires the execution to revoke an existing token approval owned by `_delegator`, and that the * revocation primitive used is permitted by `_terms`. - * @param _terms 1-byte bitmask selecting which revocation primitives are allowed. See `getTermsInfo`. + * @param _terms 1-byte bitmask selecting which revocation primitives are allowed. See the contract NatSpec. * @param _mode Must be single call type and default execution mode. * @param _executionCallData Single execution targeting the token contract. * @param _delegator The delegator of the delegation carrying this caveat (link-local, not the chain root). @@ -130,25 +242,59 @@ contract ApprovalRevocationEnforcer is CaveatEnforcer { (address target_, uint256 value_, bytes calldata callData_) = _executionCallData.decodeSingle(); require(value_ == 0, "ApprovalRevocationEnforcer:invalid-value"); + require(callData_.length >= 4, "ApprovalRevocationEnforcer:invalid-execution-length"); + + bytes4 selector_ = bytes4(callData_[0:4]); + + // Permit2 `approve(address,address,uint160,uint48)`: 4 + 4*32 = 132 bytes. Dispatched first because it has + // its own length and selector and never overlaps with the other primitives. + if (selector_ == _PERMIT2_APPROVE_SELECTOR) { + require(flags_ & _PERMISSION_PERMIT2_APPROVE != 0, "ApprovalRevocationEnforcer:permit2-approve-not-allowed"); + _validatePermit2Approve(target_, callData_); + return; + } + + // Permit2 `lockdown((address,address)[])`: dynamic calldata. Only the canonical Permit2 target is + // enforced — every entry of the array structurally zeros a Permit2 allowance amount, so no + // calldata-shape validation is needed (a malformed payload simply reverts inside Permit2 itself). + if (selector_ == _PERMIT2_LOCKDOWN_SELECTOR) { + require(flags_ & _PERMISSION_PERMIT2_LOCKDOWN != 0, "ApprovalRevocationEnforcer:permit2-lockdown-not-allowed"); + require(target_ == _PERMIT2, "ApprovalRevocationEnforcer:invalid-permit2-target"); + return; + } + + // Permit2 `invalidateNonces(address,address,uint48)`: only the canonical Permit2 target is enforced. + // Permit2 itself enforces strict nonce monotonicity (and a per-call uint16-bounded delta), so the call + // can only invalidate signed-but-unredeemed permits, never create authority. + if (selector_ == _PERMIT2_INVALIDATE_NONCES_SELECTOR) { + require( + flags_ & _PERMISSION_PERMIT2_INVALIDATE_NONCES != 0, + "ApprovalRevocationEnforcer:permit2-invalidate-nonces-not-allowed" + ); + require(target_ == _PERMIT2, "ApprovalRevocationEnforcer:invalid-permit2-target"); + return; + } + // 68 = 4-byte selector + two 32-byte words. Shared by `approve(address,uint256)` and // `setApprovalForAll(address,bool)`. require(callData_.length == 68, "ApprovalRevocationEnforcer:invalid-execution-length"); - if (bytes4(callData_[0:4]) == IERC721.setApprovalForAll.selector) { + if (selector_ == IERC721.setApprovalForAll.selector) { require(flags_ & _PERMISSION_SET_APPROVAL_FOR_ALL != 0, "ApprovalRevocationEnforcer:setApprovalForAll-not-allowed"); _validateOperatorRevocation(target_, callData_, _delegator); return; } - if (bytes4(callData_[0:4]) == IERC20.approve.selector) { + if (selector_ == IERC20.approve.selector) { // ERC-20 and ERC-721 share `approve(address,uint256)`. Disambiguate by the first parameter: ERC-721 // revokes via `approve(address(0), tokenId)`, while ERC-20 revokes via `approve(spender, 0)` with a // non-zero spender. - if (address(uint160(uint256(bytes32(callData_[4:36])))) == address(0)) { + address firstParam_ = address(uint160(uint256(bytes32(callData_[4:36])))); + if (firstParam_ == address(0)) { require(flags_ & _PERMISSION_ERC721_APPROVE != 0, "ApprovalRevocationEnforcer:erc721-approve-not-allowed"); _validateErc721Revocation(target_, callData_); } else { require(flags_ & _PERMISSION_ERC20_APPROVE != 0, "ApprovalRevocationEnforcer:erc20-approve-not-allowed"); - _validateErc20Revocation(target_, callData_, _delegator, address(uint160(uint256(bytes32(callData_[4:36]))))); + _validateErc20Revocation(target_, callData_, _delegator, firstParam_); } return; } @@ -182,9 +328,7 @@ contract ApprovalRevocationEnforcer is CaveatEnforcer { { require(uint256(bytes32(_callData[36:68])) == 0, "ApprovalRevocationEnforcer:non-zero-amount"); - require( - IERC20(_target).allowance(_delegator, _spender) != 0, "ApprovalRevocationEnforcer:no-approval-to-revoke" - ); + require(IERC20(_target).allowance(_delegator, _spender) != 0, "ApprovalRevocationEnforcer:no-approval-to-revoke"); } /** @@ -194,9 +338,7 @@ contract ApprovalRevocationEnforcer is CaveatEnforcer { function _validateErc721Revocation(address _target, bytes calldata _callData) private view { uint256 tokenId_ = uint256(bytes32(_callData[36:68])); - require( - IERC721(_target).getApproved(tokenId_) != address(0), "ApprovalRevocationEnforcer:no-approval-to-revoke" - ); + require(IERC721(_target).getApproved(tokenId_) != address(0), "ApprovalRevocationEnforcer:no-approval-to-revoke"); } /** @@ -207,9 +349,24 @@ contract ApprovalRevocationEnforcer is CaveatEnforcer { require(uint256(bytes32(_callData[36:68])) == 0, "ApprovalRevocationEnforcer:not-a-revocation"); address operator_ = address(uint160(uint256(bytes32(_callData[4:36])))); - require( - IERC721(_target).isApprovedForAll(_delegator, operator_), - "ApprovalRevocationEnforcer:no-approval-to-revoke" - ); + require(IERC721(_target).isApprovedForAll(_delegator, operator_), "ApprovalRevocationEnforcer:no-approval-to-revoke"); + } + + /** + * @dev Validates a Permit2 `approve(token, spender, 0, 0)` revocation. Requires the target to be the canonical + * Permit2 deployment, the calldata to be exactly 132 bytes, and both `amount` (uint160) and `expiration` + * (uint48) parameters to be zero. No on-chain liveness check is performed: Permit2 silently overwrites any + * existing allowance, so calling against a (token, spender) pair with no live allowance is a harmless no-op. + * The first two parameters (token, spender) are intentionally unconstrained here — compose with + * `AllowedCalldataEnforcer` if a particular pair must be enforced. + */ + function _validatePermit2Approve(address _target, bytes calldata _callData) private pure { + require(_target == _PERMIT2, "ApprovalRevocationEnforcer:invalid-permit2-target"); + require(_callData.length == 132, "ApprovalRevocationEnforcer:permit2-invalid-execution-length"); + // amount (uint160) sits in the 3rd word; it is ABI-encoded with 12 bytes of left padding (right-aligned + // in the word), so checking the full 32-byte word for zero is equivalent and cheaper. + require(uint256(bytes32(_callData[68:100])) == 0, "ApprovalRevocationEnforcer:non-zero-amount"); + // expiration (uint48, in the 4th word) MUST be zero. + require(uint256(bytes32(_callData[100:132])) == 0, "ApprovalRevocationEnforcer:non-zero-expiration"); } } diff --git a/test/enforcers/ApprovalRevocationEnforcer.t.sol b/test/enforcers/ApprovalRevocationEnforcer.t.sol index e1483b90..94889378 100644 --- a/test/enforcers/ApprovalRevocationEnforcer.t.sol +++ b/test/enforcers/ApprovalRevocationEnforcer.t.sol @@ -37,7 +37,16 @@ contract ApprovalRevocationEnforcerTest is CaveatEnforcerBaseTest { uint8 internal constant PERMISSION_ERC20_APPROVE = 0x01; uint8 internal constant PERMISSION_ERC721_APPROVE = 0x02; uint8 internal constant PERMISSION_SET_APPROVAL_FOR_ALL = 0x04; - uint8 internal constant PERMISSION_ALL = 0x07; + uint8 internal constant PERMISSION_PERMIT2_APPROVE = 0x08; + uint8 internal constant PERMISSION_PERMIT2_LOCKDOWN = 0x10; + uint8 internal constant PERMISSION_PERMIT2_INVALIDATE_NONCES = 0x20; + uint8 internal constant PERMISSION_ALL = 0x3F; + + /// @dev Mirrors the contract constants. + address internal constant PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3; + bytes4 internal constant PERMIT2_APPROVE_SELECTOR = 0x87517c45; + bytes4 internal constant PERMIT2_LOCKDOWN_SELECTOR = 0xcc53287f; + bytes4 internal constant PERMIT2_INVALIDATE_NONCES_SELECTOR = 0x65d9723c; ////////////////////////////// Set up ////////////////////////////// @@ -87,6 +96,38 @@ contract ApprovalRevocationEnforcerTest is CaveatEnforcerBaseTest { return abi.encodeWithSelector(IERC721.setApprovalForAll.selector, _operator, _approved); } + function _permit2ApproveCallData(address _token, address _spender, uint160 _amount, uint48 _expiration) + internal + pure + returns (bytes memory) + { + return abi.encodeWithSelector(PERMIT2_APPROVE_SELECTOR, _token, _spender, _amount, _expiration); + } + + /// @dev Mirrors Permit2's `TokenSpenderPair { address token; address spender; }`. Defined locally to avoid a + /// direct Permit2 dependency. + struct TokenSpenderPair { + address token; + address spender; + } + + function _permit2LockdownCallData(TokenSpenderPair[] memory _pairs) internal pure returns (bytes memory) { + return abi.encodeWithSelector(PERMIT2_LOCKDOWN_SELECTOR, _pairs); + } + + function _singlePair(address _token, address _spender) internal pure returns (TokenSpenderPair[] memory pairs_) { + pairs_ = new TokenSpenderPair[](1); + pairs_[0] = TokenSpenderPair({ token: _token, spender: _spender }); + } + + function _permit2InvalidateNoncesCallData(address _token, address _spender, uint48 _newNonce) + internal + pure + returns (bytes memory) + { + return abi.encodeWithSelector(PERMIT2_INVALIDATE_NONCES_SELECTOR, _token, _spender, _newNonce); + } + function _encodeSingle(address _target, uint256 _value, bytes memory _callData) internal pure returns (bytes memory) { return ExecutionLib.encodeSingle(_target, _value, _callData); } @@ -119,9 +160,9 @@ contract ApprovalRevocationEnforcerTest is CaveatEnforcerBaseTest { _expectRevertBeforeHook(_terms(0x00), executionCallData_, "ApprovalRevocationEnforcer:no-methods-allowed"); } - function test_terms_revertOnReservedBitSet_bit3() public { + function test_terms_revertOnReservedBitSet_bit6() public { bytes memory executionCallData_ = _encodeSingle(address(erc20), 0, _approveCallData(spender, 0)); - _expectRevertBeforeHook(_terms(0x08), executionCallData_, "ApprovalRevocationEnforcer:invalid-terms"); + _expectRevertBeforeHook(_terms(0x40), executionCallData_, "ApprovalRevocationEnforcer:invalid-terms"); } function test_terms_revertOnReservedBitSet_highBit() public { @@ -306,6 +347,269 @@ contract ApprovalRevocationEnforcerTest is CaveatEnforcerBaseTest { enforcer.beforeHook(_terms(PERMISSION_ALL), hex"", singleDefaultMode, executionCallData_, bytes32(0), delegator, address(0)); } + ////////////////////////////// Per-flag gating (Permit2 approve) ////////////////////////////// + + function test_terms_onlyPermit2Approve_allowsPermit2Approve() public { + bytes memory executionCallData_ = _encodeSingle(PERMIT2, 0, _permit2ApproveCallData(address(erc20), spender, 0, 0)); + _callBeforeHook(_terms(PERMISSION_PERMIT2_APPROVE), executionCallData_); + } + + function test_terms_onlyPermit2Approve_blocksErc20() public { + bytes memory executionCallData_ = _encodeSingle(address(erc20), 0, _approveCallData(spender, 0)); + _expectRevertBeforeHook(_terms(PERMISSION_PERMIT2_APPROVE), executionCallData_, "ApprovalRevocationEnforcer:erc20-approve-not-allowed"); + } + + function test_terms_onlyPermit2Approve_blocksErc721Approve() public { + bytes memory executionCallData_ = _encodeSingle(address(erc721), 0, _approveCallData(address(0), mintedTokenId)); + _expectRevertBeforeHook(_terms(PERMISSION_PERMIT2_APPROVE), executionCallData_, "ApprovalRevocationEnforcer:erc721-approve-not-allowed"); + } + + function test_terms_onlyPermit2Approve_blocksSetApprovalForAll() public { + bytes memory executionCallData_ = _encodeSingle(address(erc721), 0, _setApprovalForAllCallData(operator, false)); + _expectRevertBeforeHook(_terms(PERMISSION_PERMIT2_APPROVE), executionCallData_, "ApprovalRevocationEnforcer:setApprovalForAll-not-allowed"); + } + + function test_terms_withoutPermit2Approve_blocksPermit2Approve() public { + uint8 flags_ = PERMISSION_ERC20_APPROVE | PERMISSION_ERC721_APPROVE | PERMISSION_SET_APPROVAL_FOR_ALL; + bytes memory executionCallData_ = _encodeSingle(PERMIT2, 0, _permit2ApproveCallData(address(erc20), spender, 0, 0)); + _expectRevertBeforeHook(_terms(flags_), executionCallData_, "ApprovalRevocationEnforcer:permit2-approve-not-allowed"); + } + + ////////////////////////////// Valid cases (Permit2 approve) ////////////////////////////// + + function test_permit2_revokeSucceeds() public { + bytes memory executionCallData_ = _encodeSingle(PERMIT2, 0, _permit2ApproveCallData(address(erc20), spender, 0, 0)); + _callBeforeHook(_terms(PERMISSION_ALL), executionCallData_); + } + + function test_permit2_revokeSucceedsWithArbitraryTokenAndSpender() public { + // The enforcer does not constrain the (token, spender) pair on its own — those should be pinned via + // composition (e.g. AllowedCalldataEnforcer). Here we just verify the hook accepts arbitrary values. + bytes memory executionCallData_ = + _encodeSingle(PERMIT2, 0, _permit2ApproveCallData(address(0xdead), address(0xbeef), 0, 0)); + _callBeforeHook(_terms(PERMISSION_ALL), executionCallData_); + } + + ////////////////////////////// Invalid cases (Permit2 approve) ////////////////////////////// + + function test_permit2_revertOnNonPermit2Target() public { + bytes memory executionCallData_ = _encodeSingle(address(erc20), 0, _permit2ApproveCallData(address(erc20), spender, 0, 0)); + _expectRevertBeforeHook(_terms(PERMISSION_ALL), executionCallData_, "ApprovalRevocationEnforcer:invalid-permit2-target"); + } + + function test_permit2_revertOnNonZeroAmount() public { + bytes memory executionCallData_ = _encodeSingle(PERMIT2, 0, _permit2ApproveCallData(address(erc20), spender, 1, 0)); + _expectRevertBeforeHook(_terms(PERMISSION_ALL), executionCallData_, "ApprovalRevocationEnforcer:non-zero-amount"); + } + + function test_permit2_revertOnNonZeroExpiration() public { + bytes memory executionCallData_ = _encodeSingle(PERMIT2, 0, _permit2ApproveCallData(address(erc20), spender, 0, 1)); + _expectRevertBeforeHook(_terms(PERMISSION_ALL), executionCallData_, "ApprovalRevocationEnforcer:non-zero-expiration"); + } + + function test_permit2_revertOnMaxNonZeroAmount() public { + bytes memory executionCallData_ = _encodeSingle(PERMIT2, 0, _permit2ApproveCallData(address(erc20), spender, type(uint160).max, 0)); + _expectRevertBeforeHook(_terms(PERMISSION_ALL), executionCallData_, "ApprovalRevocationEnforcer:non-zero-amount"); + } + + function test_permit2_revertOnMaxNonZeroExpiration() public { + bytes memory executionCallData_ = _encodeSingle(PERMIT2, 0, _permit2ApproveCallData(address(erc20), spender, 0, type(uint48).max)); + _expectRevertBeforeHook(_terms(PERMISSION_ALL), executionCallData_, "ApprovalRevocationEnforcer:non-zero-expiration"); + } + + function test_permit2_revertOnTruncatedCallData() public { + // 4 selector + 3 words = 100 bytes; matches Permit2 selector dispatch but fails the length gate. + bytes memory truncated_ = abi.encodePacked( + PERMIT2_APPROVE_SELECTOR, bytes32(uint256(uint160(address(erc20)))), bytes32(uint256(uint160(spender))), bytes32(uint256(0)) + ); + bytes memory executionCallData_ = _encodeSingle(PERMIT2, 0, truncated_); + _expectRevertBeforeHook(_terms(PERMISSION_ALL), executionCallData_, "ApprovalRevocationEnforcer:permit2-invalid-execution-length"); + } + + function test_permit2_revertOnExtraTrailingByte() public { + bytes memory longCallData_ = abi.encodePacked(_permit2ApproveCallData(address(erc20), spender, 0, 0), bytes1(0x00)); + bytes memory executionCallData_ = _encodeSingle(PERMIT2, 0, longCallData_); + _expectRevertBeforeHook(_terms(PERMISSION_ALL), executionCallData_, "ApprovalRevocationEnforcer:permit2-invalid-execution-length"); + } + + function test_permit2_revertOnNonZeroValue() public { + bytes memory executionCallData_ = _encodeSingle(PERMIT2, 1, _permit2ApproveCallData(address(erc20), spender, 0, 0)); + _expectRevertBeforeHook(_terms(PERMISSION_ALL), executionCallData_, "ApprovalRevocationEnforcer:invalid-value"); + } + + ////////////////////////////// Per-flag gating (Permit2 lockdown) ////////////////////////////// + + function test_terms_onlyPermit2Lockdown_allowsLockdown() public { + bytes memory executionCallData_ = + _encodeSingle(PERMIT2, 0, _permit2LockdownCallData(_singlePair(address(erc20), spender))); + _callBeforeHook(_terms(PERMISSION_PERMIT2_LOCKDOWN), executionCallData_); + } + + function test_terms_onlyPermit2Lockdown_blocksErc20() public { + bytes memory executionCallData_ = _encodeSingle(address(erc20), 0, _approveCallData(spender, 0)); + _expectRevertBeforeHook(_terms(PERMISSION_PERMIT2_LOCKDOWN), executionCallData_, "ApprovalRevocationEnforcer:erc20-approve-not-allowed"); + } + + function test_terms_onlyPermit2Lockdown_blocksErc721Approve() public { + bytes memory executionCallData_ = _encodeSingle(address(erc721), 0, _approveCallData(address(0), mintedTokenId)); + _expectRevertBeforeHook(_terms(PERMISSION_PERMIT2_LOCKDOWN), executionCallData_, "ApprovalRevocationEnforcer:erc721-approve-not-allowed"); + } + + function test_terms_onlyPermit2Lockdown_blocksSetApprovalForAll() public { + bytes memory executionCallData_ = _encodeSingle(address(erc721), 0, _setApprovalForAllCallData(operator, false)); + _expectRevertBeforeHook(_terms(PERMISSION_PERMIT2_LOCKDOWN), executionCallData_, "ApprovalRevocationEnforcer:setApprovalForAll-not-allowed"); + } + + function test_terms_onlyPermit2Lockdown_blocksPermit2Approve() public { + bytes memory executionCallData_ = _encodeSingle(PERMIT2, 0, _permit2ApproveCallData(address(erc20), spender, 0, 0)); + _expectRevertBeforeHook(_terms(PERMISSION_PERMIT2_LOCKDOWN), executionCallData_, "ApprovalRevocationEnforcer:permit2-approve-not-allowed"); + } + + function test_terms_onlyPermit2Approve_blocksLockdown() public { + bytes memory executionCallData_ = + _encodeSingle(PERMIT2, 0, _permit2LockdownCallData(_singlePair(address(erc20), spender))); + _expectRevertBeforeHook(_terms(PERMISSION_PERMIT2_APPROVE), executionCallData_, "ApprovalRevocationEnforcer:permit2-lockdown-not-allowed"); + } + + function test_terms_withoutPermit2Lockdown_blocksLockdown() public { + uint8 flags_ = PERMISSION_ERC20_APPROVE | PERMISSION_ERC721_APPROVE | PERMISSION_SET_APPROVAL_FOR_ALL | PERMISSION_PERMIT2_APPROVE; + bytes memory executionCallData_ = + _encodeSingle(PERMIT2, 0, _permit2LockdownCallData(_singlePair(address(erc20), spender))); + _expectRevertBeforeHook(_terms(flags_), executionCallData_, "ApprovalRevocationEnforcer:permit2-lockdown-not-allowed"); + } + + ////////////////////////////// Valid cases (Permit2 lockdown) ////////////////////////////// + + function test_permit2Lockdown_revokeSucceedsForSinglePair() public { + bytes memory executionCallData_ = + _encodeSingle(PERMIT2, 0, _permit2LockdownCallData(_singlePair(address(erc20), spender))); + _callBeforeHook(_terms(PERMISSION_ALL), executionCallData_); + } + + function test_permit2Lockdown_revokeSucceedsForMultiplePairs() public { + TokenSpenderPair[] memory pairs_ = new TokenSpenderPair[](3); + pairs_[0] = TokenSpenderPair({ token: address(erc20), spender: spender }); + pairs_[1] = TokenSpenderPair({ token: address(0xdead), spender: address(0xbeef) }); + pairs_[2] = TokenSpenderPair({ token: address(erc721), spender: operator }); + bytes memory executionCallData_ = _encodeSingle(PERMIT2, 0, _permit2LockdownCallData(pairs_)); + _callBeforeHook(_terms(PERMISSION_ALL), executionCallData_); + } + + function test_permit2Lockdown_revokeSucceedsForEmptyArray() public { + // Empty lockdown is structurally a no-op — Permit2 accepts it. The enforcer accepts it too: there is + // nothing here that could ever grant authority. Pinned as documented behavior so future refactors don't + // silently change it. + bytes memory executionCallData_ = _encodeSingle(PERMIT2, 0, _permit2LockdownCallData(new TokenSpenderPair[](0))); + _callBeforeHook(_terms(PERMISSION_ALL), executionCallData_); + } + + ////////////////////////////// Invalid cases (Permit2 lockdown) ////////////////////////////// + + function test_permit2Lockdown_revertOnNonPermit2Target() public { + bytes memory executionCallData_ = + _encodeSingle(address(erc20), 0, _permit2LockdownCallData(_singlePair(address(erc20), spender))); + _expectRevertBeforeHook(_terms(PERMISSION_ALL), executionCallData_, "ApprovalRevocationEnforcer:invalid-permit2-target"); + } + + function test_permit2Lockdown_revertOnNonZeroValue() public { + bytes memory executionCallData_ = + _encodeSingle(PERMIT2, 1, _permit2LockdownCallData(_singlePair(address(erc20), spender))); + _expectRevertBeforeHook(_terms(PERMISSION_ALL), executionCallData_, "ApprovalRevocationEnforcer:invalid-value"); + } + + function test_permit2Lockdown_acceptsMalformedPayload_safetyRestsOnPermit2() public { + // The lockdown branch performs no calldata-shape validation: the structural argument is that any contract + // at the canonical Permit2 address can only zero allowance amounts under this selector. Pin the + // enforcer-level behavior here so future refactors don't silently introduce a length check that breaks + // composition with `ExactCalldataEnforcer` for non-standard pinning shapes. + bytes memory malformed_ = abi.encodePacked(PERMIT2_LOCKDOWN_SELECTOR, hex"deadbeef"); + bytes memory executionCallData_ = _encodeSingle(PERMIT2, 0, malformed_); + _callBeforeHook(_terms(PERMISSION_ALL), executionCallData_); + } + + ////////////////////////////// Per-flag gating (Permit2 invalidateNonces) ////////////////////////////// + + function test_terms_onlyPermit2InvalidateNonces_allowsInvalidateNonces() public { + bytes memory executionCallData_ = _encodeSingle(PERMIT2, 0, _permit2InvalidateNoncesCallData(address(erc20), spender, 1)); + _callBeforeHook(_terms(PERMISSION_PERMIT2_INVALIDATE_NONCES), executionCallData_); + } + + function test_terms_onlyPermit2InvalidateNonces_blocksErc20() public { + bytes memory executionCallData_ = _encodeSingle(address(erc20), 0, _approveCallData(spender, 0)); + _expectRevertBeforeHook(_terms(PERMISSION_PERMIT2_INVALIDATE_NONCES), executionCallData_, "ApprovalRevocationEnforcer:erc20-approve-not-allowed"); + } + + function test_terms_onlyPermit2InvalidateNonces_blocksErc721Approve() public { + bytes memory executionCallData_ = _encodeSingle(address(erc721), 0, _approveCallData(address(0), mintedTokenId)); + _expectRevertBeforeHook(_terms(PERMISSION_PERMIT2_INVALIDATE_NONCES), executionCallData_, "ApprovalRevocationEnforcer:erc721-approve-not-allowed"); + } + + function test_terms_onlyPermit2InvalidateNonces_blocksSetApprovalForAll() public { + bytes memory executionCallData_ = _encodeSingle(address(erc721), 0, _setApprovalForAllCallData(operator, false)); + _expectRevertBeforeHook(_terms(PERMISSION_PERMIT2_INVALIDATE_NONCES), executionCallData_, "ApprovalRevocationEnforcer:setApprovalForAll-not-allowed"); + } + + function test_terms_onlyPermit2InvalidateNonces_blocksPermit2Approve() public { + bytes memory executionCallData_ = _encodeSingle(PERMIT2, 0, _permit2ApproveCallData(address(erc20), spender, 0, 0)); + _expectRevertBeforeHook(_terms(PERMISSION_PERMIT2_INVALIDATE_NONCES), executionCallData_, "ApprovalRevocationEnforcer:permit2-approve-not-allowed"); + } + + function test_terms_onlyPermit2InvalidateNonces_blocksLockdown() public { + bytes memory executionCallData_ = + _encodeSingle(PERMIT2, 0, _permit2LockdownCallData(_singlePair(address(erc20), spender))); + _expectRevertBeforeHook(_terms(PERMISSION_PERMIT2_INVALIDATE_NONCES), executionCallData_, "ApprovalRevocationEnforcer:permit2-lockdown-not-allowed"); + } + + function test_terms_withoutPermit2InvalidateNonces_blocksInvalidateNonces() public { + uint8 flags_ = PERMISSION_ERC20_APPROVE | PERMISSION_ERC721_APPROVE | PERMISSION_SET_APPROVAL_FOR_ALL + | PERMISSION_PERMIT2_APPROVE | PERMISSION_PERMIT2_LOCKDOWN; + bytes memory executionCallData_ = _encodeSingle(PERMIT2, 0, _permit2InvalidateNoncesCallData(address(erc20), spender, 1)); + _expectRevertBeforeHook(_terms(flags_), executionCallData_, "ApprovalRevocationEnforcer:permit2-invalidate-nonces-not-allowed"); + } + + ////////////////////////////// Valid cases (Permit2 invalidateNonces) ////////////////////////////// + + function test_permit2InvalidateNonces_succeedsWithArbitraryNonce() public { + // The enforcer does not validate the nonce value — Permit2 itself enforces strict monotonicity and the + // per-call uint16-bounded delta. Pin the enforcer-level acceptance with a representative nonce. + bytes memory executionCallData_ = _encodeSingle(PERMIT2, 0, _permit2InvalidateNoncesCallData(address(erc20), spender, 1)); + _callBeforeHook(_terms(PERMISSION_ALL), executionCallData_); + } + + function test_permit2InvalidateNonces_succeedsWithMaxNonce() public { + bytes memory executionCallData_ = + _encodeSingle(PERMIT2, 0, _permit2InvalidateNoncesCallData(address(erc20), spender, type(uint48).max)); + _callBeforeHook(_terms(PERMISSION_ALL), executionCallData_); + } + + function test_permit2InvalidateNonces_succeedsWithArbitraryTokenAndSpender() public { + bytes memory executionCallData_ = + _encodeSingle(PERMIT2, 0, _permit2InvalidateNoncesCallData(address(0xdead), address(0xbeef), 7)); + _callBeforeHook(_terms(PERMISSION_ALL), executionCallData_); + } + + function test_permit2InvalidateNonces_acceptsMalformedPayload_safetyRestsOnPermit2() public { + // Same rationale as the lockdown counterpart: nonce monotonicity is enforced inside Permit2, so the + // enforcer does not validate calldata shape beyond the selector + target. + bytes memory malformed_ = abi.encodePacked(PERMIT2_INVALIDATE_NONCES_SELECTOR, hex"deadbeef"); + bytes memory executionCallData_ = _encodeSingle(PERMIT2, 0, malformed_); + _callBeforeHook(_terms(PERMISSION_ALL), executionCallData_); + } + + ////////////////////////////// Invalid cases (Permit2 invalidateNonces) ////////////////////////////// + + function test_permit2InvalidateNonces_revertOnNonPermit2Target() public { + bytes memory executionCallData_ = + _encodeSingle(address(erc20), 0, _permit2InvalidateNoncesCallData(address(erc20), spender, 1)); + _expectRevertBeforeHook(_terms(PERMISSION_ALL), executionCallData_, "ApprovalRevocationEnforcer:invalid-permit2-target"); + } + + function test_permit2InvalidateNonces_revertOnNonZeroValue() public { + bytes memory executionCallData_ = + _encodeSingle(PERMIT2, 1, _permit2InvalidateNoncesCallData(address(erc20), spender, 1)); + _expectRevertBeforeHook(_terms(PERMISSION_ALL), executionCallData_, "ApprovalRevocationEnforcer:invalid-value"); + } + ////////////////////////////// Generic invalid cases ////////////////////////////// function test_revertOnNonZeroValue() public { From 0213fe2dfce6184ff7eeda40b4ae4a48d70932b2 Mon Sep 17 00:00:00 2001 From: MoMannn Date: Wed, 13 May 2026 09:58:15 +0200 Subject: [PATCH 8/9] add docs for incompatibility --- documents/CaveatEnforcers.md | 12 ++++++++++++ src/enforcers/ApprovalRevocationEnforcer.sol | 9 +++++++++ 2 files changed, 21 insertions(+) diff --git a/documents/CaveatEnforcers.md b/documents/CaveatEnforcers.md index 125dc68c..f7a57fb9 100644 --- a/documents/CaveatEnforcers.md +++ b/documents/CaveatEnforcers.md @@ -266,6 +266,18 @@ The Permit2 branches assume the canonical Uniswap-deployed Permit2 contract is a Delegators on chains without canonical Permit2 should NOT enable bits 3, 4, or 5. +#### Incompatibility: ERC-20 Tokens That Revert on Zero-Value `approve` + +A small number of non-standard ERC-20 tokens — most notably **BNB on Ethereum mainnet**, and a handful of older tokens — revert when `approve(spender, 0)` is called. Because the ERC-20 branch of this enforcer strictly requires `amount == 0` and provides no alternative revocation primitive (e.g. `decreaseAllowance`), **allowances previously granted on such tokens cannot be revoked through this enforcer**: the executed `approve(spender, 0)` reverts inside the token contract. + +Implications for delegators: + +- Do not rely on a delegation carrying `ApprovalRevocationEnforcer` (bit `0x01`) to clear an outstanding allowance for one of these tokens. +- Be aware before signing a **batch** that includes this enforcer for such a token — the entire batch will revert at the token call. +- Revoke these allowances directly from the owning account (or via a different revocation path) instead. + +The ERC-721, `setApprovalForAll`, and Permit2 branches are unaffected. + #### Use Cases - **Revocation bots / keepers**: Delegate to a third party that can proactively clean up stale or compromised approvals. diff --git a/src/enforcers/ApprovalRevocationEnforcer.sol b/src/enforcers/ApprovalRevocationEnforcer.sol index 4104c8d1..2555479c 100644 --- a/src/enforcers/ApprovalRevocationEnforcer.sol +++ b/src/enforcers/ApprovalRevocationEnforcer.sol @@ -163,6 +163,15 @@ import { ModeCode } from "../utils/Types.sol"; * @dev Delegators who want to restrict revocation to specific tokens should compose this enforcer with * `AllowedTargetsEnforcer`. * + * @dev INCOMPATIBILITY — ERC-20 tokens that revert on zero-value `approve`. A small number of non-standard + * ERC-20 tokens (notably BNB on Ethereum mainnet, and a handful of older tokens) revert when `approve(spender, 0)` + * is called. Because the ERC-20 branch of this enforcer strictly requires `amount == 0` and provides no alternative + * revocation primitive (e.g. `decreaseAllowance`), allowances previously granted on such tokens CANNOT be revoked + * through this enforcer — the executed `approve(spender, 0)` will revert inside the token contract. Delegators + * holding these tokens should revoke their allowances directly from the owning account (or via a different + * revocation path), and should be aware before signing a delegation or batch that includes this enforcer for + * such a token. The Permit2, ERC-721, and `setApprovalForAll` branches are unaffected. + * * @dev This enforcer operates only in single call type and default execution mode. */ contract ApprovalRevocationEnforcer is CaveatEnforcer { From cfa5bcb77de6001d70ab96c45588666216653d3d Mon Sep 17 00:00:00 2001 From: MoMannn Date: Wed, 13 May 2026 09:58:23 +0200 Subject: [PATCH 9/9] add audit --- audits/cyfrin/cyfrin-5-26.pdf | Bin 0 -> 187349 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 audits/cyfrin/cyfrin-5-26.pdf diff --git a/audits/cyfrin/cyfrin-5-26.pdf b/audits/cyfrin/cyfrin-5-26.pdf new file mode 100644 index 0000000000000000000000000000000000000000..006668caf2d5c574ae8ea8473c5b9600471d11d8 GIT binary patch literal 187349 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

nZuSArLy3c+33J%rxygK`v&+3(p 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�f>0=(fN=Nv1x;5d<0!J5 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 Tav55fnHyPfsj9mAyKw;kE;Tuz literal 0 HcmV?d00001