diff --git a/.cursorrules b/.cursorrules new file mode 100644 index 00000000..2b9daf1d --- /dev/null +++ b/.cursorrules @@ -0,0 +1,329 @@ +# Developing Smart Contracts for Delegation Systems + +This guide focuses on creating smart contracts that work seamlessly with the MetaMask Delegation Toolkit. The key principle is to keep your contracts simple, focused on core functionality, and completely unaware of the delegation system itself. + +## Core Principles + +1. **Simplicity**: Contracts should focus solely on their core business logic. +2. **Owner-centric**: Use `onlyOwner` modifiers for privileged functions. +3. **Delegation-agnostic**: Contracts should not reference Delegation, DelegationManager, or mode encoding. +4. **Extensibility**: Design core functions to be easily extended through the delegation framework. + +## Contract Structure + +Here's an example of a basic contract structure: + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +contract MyContract is ERC721, Ownable { + constructor(string memory name, string memory symbol) ERC721(name, symbol) Ownable(msg.sender) {} + + function mint(address to, uint256 tokenId) public onlyOwner { + _mint(to, tokenId); + } +} +``` + +## Core Functions + +### Minting + +The `mint` function is a simple example of a core function that can be easily extended through the delegation framework. + +## Using Caveat Enforcers + +Caveat enforcers allow you to add specific conditions or restrictions to delegations. The MetaMask Delegation Toolkit provides several out-of-the-box caveat enforcers: + +- `AllowedCalldataEnforcer.sol` +- `AllowedMethodsEnforcer.sol` +- `AllowedTargetsEnforcer.sol` +- `BlockNumberEnforcer.sol` +- `DeployedEnforcer.sol` +- `ERC20TransferAmountEnforcer.sol` +- `ERC20BalanceGteEnforcer.sol` +- `NonceEnforcer.sol` +- `LimitedCallsEnforcer.sol` +- `IdEnforcer.sol` +- `TimestampEnforcer.sol` +- `ValueLteEnforcer.sol` + +So any policy that is composed of those can be assumed provided already. + +In the case that you need to create a custom enforcer, you can use the `CaveatEnforcer.sol` base class and write your own like this: + +```solidity +// SPDX-License-Identifier: MIT AND Apache-2.0 +pragma solidity 0.8.23; + +import "forge-std/Test.sol"; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { ModeLib } from "@erc7579/lib/ModeLib.sol"; +import { ExecutionLib } from "@erc7579/lib/ExecutionLib.sol"; + +import { Execution, Caveat, Delegation, ModeCode } from "../../src/utils/Types.sol"; +import { Counter } from "../utils/Counter.t.sol"; +import { CaveatEnforcerBaseTest } from "./CaveatEnforcerBaseTest.t.sol"; +import { AllowedMethodsEnforcer } from "../../src/enforcers/AllowedMethodsEnforcer.sol"; +import { IDelegationManager } from "../../src/interfaces/IDelegationManager.sol"; +import { EncoderLib } from "../../src/libraries/EncoderLib.sol"; +import { ICaveatEnforcer } from "../../src/interfaces/ICaveatEnforcer.sol"; + +contract AllowedMethodsEnforcerTest is CaveatEnforcerBaseTest { + using ModeLib for ModeCode; + + ////////////////////// State ////////////////////// + + AllowedMethodsEnforcer public allowedMethodsEnforcer; + ModeCode public mode = ModeLib.encodeSimpleSingle(); + + ////////////////////// Set up ////////////////////// + + function setUp() public override { + super.setUp(); + allowedMethodsEnforcer = new AllowedMethodsEnforcer(); + vm.label(address(allowedMethodsEnforcer), "Allowed Methods Enforcer"); + } + + ////////////////////// Valid cases ////////////////////// + + // should allow a method to be called when a single method is allowed + function test_singleMethodCanBeCalled() public { + // Create the execution that would be executed + Execution memory execution_ = Execution({ + target: address(aliceDeleGatorCounter), + value: 0, + callData: abi.encodeWithSelector(Counter.increment.selector) + }); + bytes memory executionCallData_ = ExecutionLib.encodeSingle(execution_.target, execution_.value, execution_.callData); + + // beforeHook, mimicking the behavior of Alice's DeleGator + vm.prank(address(delegationManager)); + allowedMethodsEnforcer.beforeHook( + abi.encodePacked(Counter.increment.selector), hex"", mode, executionCallData_, keccak256(""), address(0), address(0) + ); + } + + // should allow a method to be called when a multiple methods are allowed + function test_multiMethodCanBeCalled() public { + // Create the execution that would be executed + Execution memory execution_ = Execution({ + target: address(aliceDeleGatorCounter), + value: 0, + callData: abi.encodeWithSelector(Counter.increment.selector) + }); + bytes memory executionCallData_ = ExecutionLib.encodeSingle(execution_.target, execution_.value, execution_.callData); + + // beforeHook, mimicking the behavior of Alice's DeleGator + vm.prank(address(delegationManager)); + allowedMethodsEnforcer.beforeHook( + abi.encodePacked(Counter.setCount.selector, Ownable.renounceOwnership.selector, Counter.increment.selector), + hex"", + mode, + executionCallData_, + keccak256(""), + address(0), + address(0) + ); + } + + ////////////////////// Invalid cases ////////////////////// + + // should FAIL to get terms info when passing an invalid terms length + function test_getTermsInfoFailsForInvalidLength() public { + vm.expectRevert("AllowedMethodsEnforcer:invalid-terms-length"); + allowedMethodsEnforcer.getTermsInfo(bytes("1")); + } + + // should FAIL if execution.callData length < 4 + function test_notAllow_invalidExecutionLength() public { + // Create the execution that would be executed + Execution memory execution_ = + Execution({ target: address(aliceDeleGatorCounter), value: 0, callData: abi.encodePacked(true) }); + bytes memory executionCallData_ = ExecutionLib.encodeSingle(execution_.target, execution_.value, execution_.callData); + + // beforeHook, mimicking the behavior of Alice's DeleGator + vm.prank(address(delegationManager)); + vm.expectRevert("AllowedMethodsEnforcer:invalid-execution-data-length"); + allowedMethodsEnforcer.beforeHook( + abi.encodePacked(Counter.setCount.selector, Ownable.renounceOwnership.selector, Ownable.owner.selector), + hex"", + mode, + executionCallData_, + keccak256(""), + address(0), + address(0) + ); + } + + // should NOT allow a method to be called when the method is not allowed + function test_onlyApprovedMethodsCanBeCalled() public { + // Create the execution that would be executed + Execution memory execution_ = Execution({ + target: address(aliceDeleGatorCounter), + value: 0, + callData: abi.encodeWithSelector(Counter.increment.selector) + }); + bytes memory executionCallData_ = ExecutionLib.encodeSingle(execution_.target, execution_.value, execution_.callData); + + // beforeHook, mimicking the behavior of Alice's DeleGator + vm.prank(address(delegationManager)); + vm.expectRevert("AllowedMethodsEnforcer:method-not-allowed"); + allowedMethodsEnforcer.beforeHook( + abi.encodePacked(Counter.setCount.selector, Ownable.renounceOwnership.selector, Ownable.owner.selector), + hex"", + mode, + executionCallData_, + keccak256(""), + address(0), + address(0) + ); + } + + ////////////////////// Integration ////////////////////// + + // should allow a method to be called when a single method is allowed Integration + function test_methodCanBeSingleMethodIntegration() public { + uint256 initialValue_ = aliceDeleGatorCounter.count(); + + // Create the execution that would be executed + Execution memory execution_ = Execution({ + target: address(aliceDeleGatorCounter), + value: 0, + callData: abi.encodeWithSelector(Counter.increment.selector) + }); + + Caveat[] memory caveats_ = new Caveat[](1); + caveats_[0] = + Caveat({ args: hex"", enforcer: address(allowedMethodsEnforcer), terms: abi.encodePacked(Counter.increment.selector) }); + 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_); + + // Execute Bob's UserOp + Delegation[] memory delegations_ = new Delegation[](1); + delegations_[0] = delegation_; + + // Enforcer allows the delegation + invokeDelegation_UserOp(users.bob, delegations_, execution_); + // Get count + uint256 valueAfter_ = aliceDeleGatorCounter.count(); + // Validate that the count has increased by 1 + assertEq(valueAfter_, initialValue_ + 1); + + // Enforcer allows to reuse the delegation + invokeDelegation_UserOp(users.bob, delegations_, execution_); + // Get final count + uint256 finalValue_ = aliceDeleGatorCounter.count(); + // Validate that the count has increased again + assertEq(finalValue_, initialValue_ + 2); + } + + // should NOT allow a method to be called when the method is not allowed Integration + function test_onlyApprovedMethodsCanBeCalledIntegration() public { + uint256 initialValue_ = aliceDeleGatorCounter.count(); + + // Create the execution that would be executed + Execution memory execution_ = Execution({ + target: address(aliceDeleGatorCounter), + value: 0, + callData: abi.encodeWithSelector(Counter.increment.selector) + }); + + Caveat[] memory caveats_ = new Caveat[](1); + caveats_[0] = Caveat({ + args: hex"", + enforcer: address(allowedMethodsEnforcer), + terms: abi.encodePacked(Counter.setCount.selector, Ownable.renounceOwnership.selector, Ownable.owner.selector) + }); + 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_); + + // Execute Bob's UserOp + Delegation[] memory delegations_ = new Delegation[](1); + delegations_[0] = delegation_; + + // Enforcer allows the delegation + invokeDelegation_UserOp(users.bob, delegations_, execution_); + // Get final count + uint256 valueAfter_ = aliceDeleGatorCounter.count(); + // Validate that the count has not changed + assertEq(valueAfter_, initialValue_); + } + + function _getEnforcer() internal view override returns (ICaveatEnforcer) { + return ICaveatEnforcer(address(allowedMethodsEnforcer)); + } +} + +``` + +This example demonstrates a custom caveat enforcer that only permits approved methods to be called on. Here are some additional tips for working with the MetaMask Delegation Toolkit: + +1. Simplify contract logic: The delegation framework allows you to move complex permission logic out of your main contracts. For example, instead of implementing complex access control in your main contract, you can use `onlyOwner` modifiers and handle permissions through delegations. + +2. Leverage existing enforcers: Before creating a custom enforcer, check if any of the existing caveat enforcers (like AllowedCalldataEnforcer, BlockNumberEnforcer, ERC20TransferAmountEnforcer, etc.) can meet your needs. + +3. Intent-based applications: When building intent-based apps: + a. Identify the resource being offered (e.g., NFT minting rights) + b. Determine the terms for offering the resource (e.g., payment, time limits) + c. Use or create appropriate caveat enforcers + +4. Handle payments via delegations: NEVER use ERC20 allowances, `payable` functions, or permit() signatures. ONLY use delegations for receiving payments. + +Assume that payments are usually handled by delegation managers (enforced outside of primary application logic), and that the primary application logic is focused on the intent to share a resource, without needing to consider the payment mechanics. As a result, it should be rare that a payment is required directly in a non-CaveatEnforcer contract. If payment is required by a contract (usually a CaveatEnforcer contract), never use ERC20 allowances, or token permit() signatures. The delegation framework is meant to be a general purpose alternative to these more constrained mechanisms. If a payment is required in exchange for something, have the payment be paid as a `Delegation` + a `delegationManager address`, which can then be redeemed using the ERC-7710 `redeemDelegations()` function on that `delegationManager`: + +```solidity +function redeemDelegations( + bytes[] calldata _permissionContexts, + ModeCode[] calldata _modes, + bytes[] calldata _executionCallDatas + ) +``` + +Where `_modes` is an array of `ModeCode` values, which are used to determine the behavior of the `redeemDelegations()` function (and mode 1 is a normal internal message call). + +Calling a function using a permission context and delegation manager to performa simple send looks like this: + +```solidity +import { ModeLib } from "@erc7579/lib/ModeLib.sol"; +using ModeLib for ModeCode; + +// Redeem ERC-7710 delegation +bytes[] memory permissionContexts_ = new bytes[](1); +permissionContexts_[0] = abi.encode(delegations_); + +bytes[] memory executionCallDatas_ = new bytes[](1); +executionCallDatas_[0] = ExecutionLib.encodeSingle(recipient_, amount_, hex""); + +ModeCode[] memory encodedModes_ = new ModeCode[](1); +encodedModes_[0] = ModeLib.encodeSimpleSingle(); + +// Attempt to redeem the delegation and make the payment +delegationManager.redeemDelegations(permissionContexts_, encodedModes_, executionCallDatas_); +``` + +5. Always use the delegation framework for invitations. Assume the owner can always let others use their account on their behalf with arbitrary terms. + +6. Always ensure you use the `onlyOwner` modifier on any sensitive functions that should only be available to the owner. It's ok to leave this modifier off the transfer functions of a token or NFT contract. It's also ok to leave it off any function that is meant to be exposed as a public service. + +7. After auto-generating a super simple contract, provide comments above each `onlyOwner` function that describes some expected/recommended delegation types for that function. diff --git a/src/enforcers/ERC721TransferEnforcer.sol b/src/enforcers/ERC721TransferEnforcer.sol new file mode 100644 index 00000000..74eaa2fd --- /dev/null +++ b/src/enforcers/ERC721TransferEnforcer.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +import { CaveatEnforcer } from "./CaveatEnforcer.sol"; +import { ModeCode } from "../utils/Types.sol"; +import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import { ExecutionLib } from "@erc7579/lib/ExecutionLib.sol"; + +contract ERC721TransferEnforcer is CaveatEnforcer { + error UnauthorizedTransfer(); + + function beforeHook( + bytes calldata _terms, + bytes calldata, + ModeCode, + bytes calldata executionCallData, + bytes32, + address, + address + ) public virtual override { + (address permittedContract, uint256 permittedTokenId) = getTermsInfo(_terms); + (address target, uint256 _value, bytes calldata callData_) = ExecutionLib.decodeSingle(executionCallData); + bytes4 selector = bytes4(callData_[0:4]); + // Decode the remaining callData into NFT transfer parameters + if (callData_.length < 100) { + revert("ERC721TransferEnforcer:invalid-calldata-length"); + } + + address from; + address to; + uint256 transferTokenId; + + if (callData_.length < 100) { + revert("ERC721TransferEnforcer:invalid-calldata-length"); + } + + (from, to, transferTokenId) = abi.decode(callData_[4:], (address, address, uint256)); + + if (from == address(0) || to == address(0)) { + revert("ERC721TransferEnforcer:invalid-address"); + } + + if (from == address(0) || to == address(0)) { + revert("ERC721TransferEnforcer:invalid-address"); + } + if (target != permittedContract) { + revert("ERC721TransferEnforcer:unauthorized-contract-target"); + } else if (selector != IERC721.transferFrom.selector) { + revert("ERC721TransferEnforcer:unauthorized-selector"); + } else if (transferTokenId != permittedTokenId) { + revert("ERC721TransferEnforcer:unauthorized-token-id"); + } + } + + function afterHook( + bytes calldata, + bytes calldata, + ModeCode, + bytes calldata, + bytes32, + address, + address + ) public virtual override {} + + function getTermsInfo(bytes calldata _terms) public pure returns (address permittedContract, uint256 permittedTokenId) { + if (_terms.length != 52) revert("ERC721TransferEnforcer:invalid-terms-length"); + permittedContract = address(bytes20(_terms[:20])); + permittedTokenId = uint256(bytes32(_terms[20:])); + } +} diff --git a/src/libraries/Caveats.sol b/src/libraries/Caveats.sol new file mode 100644 index 00000000..ac43fcc1 --- /dev/null +++ b/src/libraries/Caveats.sol @@ -0,0 +1,289 @@ +// SPDX-License-Identifier: MIT AND Apache-2.0 +pragma solidity 0.8.23; + +import { Caveat } from "../utils/Types.sol"; +import { AllowedCalldataEnforcer } from "../enforcers/AllowedCalldataEnforcer.sol"; +import { AllowedMethodsEnforcer } from "../enforcers/AllowedMethodsEnforcer.sol"; +import { AllowedTargetsEnforcer } from "../enforcers/AllowedTargetsEnforcer.sol"; +import { ArgsEqualityCheckEnforcer } from "../enforcers/ArgsEqualityCheckEnforcer.sol"; +import { BlockNumberEnforcer } from "../enforcers/BlockNumberEnforcer.sol"; +import { DeployedEnforcer } from "../enforcers/DeployedEnforcer.sol"; +import { ERC20BalanceGteEnforcer } from "../enforcers/ERC20BalanceGteEnforcer.sol"; +import { ERC20TransferAmountEnforcer } from "../enforcers/ERC20TransferAmountEnforcer.sol"; +import { ERC721TransferEnforcer } from "../enforcers/ERC721TransferEnforcer.sol"; +import { IdEnforcer } from "../enforcers/IdEnforcer.sol"; +import { LimitedCallsEnforcer } from "../enforcers/LimitedCallsEnforcer.sol"; +import { NativeTokenTransferAmountEnforcer } from "../enforcers/NativeTokenTransferAmountEnforcer.sol"; +import { NativeBalanceGteEnforcer } from "../enforcers/NativeBalanceGteEnforcer.sol"; +import { NativeTokenPaymentEnforcer } from "../enforcers/NativeTokenPaymentEnforcer.sol"; +import { NonceEnforcer } from "../enforcers/NonceEnforcer.sol"; +import { RedeemerEnforcer } from "../enforcers/RedeemerEnforcer.sol"; +import { TimestampEnforcer } from "../enforcers/TimestampEnforcer.sol"; +import { ValueLteEnforcer } from "../enforcers/ValueLteEnforcer.sol"; +/** + @title Caveats + @notice This library aims to export the easier way to create caveats for tests. Its parameters should always be provided in the easiest creator-readable way, even at the cost of gas. + */ +library Caveats { + function createAllowedCalldataCaveat( + address enforcerAddress, + uint256 dataStart, + bytes memory expectedValue + ) internal pure returns (Caveat memory) { + bytes memory terms = abi.encodePacked(dataStart, expectedValue); + + return Caveat({ + enforcer: enforcerAddress, + terms: terms, + args: "" + }); + } + + function createERC721TransferCaveat( + address enforcerAddress, + address permittedContract, + uint256 permittedTokenId + ) internal pure returns (Caveat memory) { + bytes memory terms = abi.encodePacked(permittedContract, permittedTokenId); + + return Caveat({ + enforcer: enforcerAddress, + terms: terms, + args: "" + }); + } + + function createRedeemerCaveat( + address enforcerAddress, + address[] memory allowedRedeemers + ) internal pure returns (Caveat memory) { + bytes memory terms = new bytes(allowedRedeemers.length * 20); + for (uint256 i = 0; i < allowedRedeemers.length; i++) { + bytes20 redeemer = bytes20(allowedRedeemers[i]); + for (uint256 j = 0; j < 20; j++) { + terms[i * 20 + j] = redeemer[j]; + } + } + + return Caveat({ + enforcer: enforcerAddress, + terms: terms, + args: "" + }); + } + + function createValueLteCaveat( + address enforcerAddress, + uint256 maxValue + ) internal pure returns (Caveat memory) { + bytes memory terms = abi.encode(maxValue); + + return Caveat({ + enforcer: enforcerAddress, + terms: terms, + args: "" + }); + } + + function createNativeAllowanceCaveat( + address enforcerAddress, + uint256 allowance + ) internal pure returns (Caveat memory) { + bytes memory terms = abi.encode(allowance); + + return Caveat({ + enforcer: enforcerAddress, + terms: terms, + args: "" + }); + } + + function createTimestampCaveat( + address enforcerAddress, + uint128 timestampAfterThreshold, + uint128 timestampBeforeThreshold + ) internal pure returns (Caveat memory) { + bytes memory terms = abi.encodePacked(timestampAfterThreshold, timestampBeforeThreshold); + + return Caveat({ + enforcer: enforcerAddress, + terms: terms, + args: "" + }); + } + + function createNonceCaveat( + address enforcerAddress, + uint256 nonce + ) internal pure returns (Caveat memory) { + bytes memory terms = abi.encode(nonce); + + return Caveat({ + enforcer: enforcerAddress, + terms: terms, + args: "" + }); + } + + function createIdCaveat( + address enforcerAddress, + uint256 id + ) internal pure returns (Caveat memory) { + bytes memory terms = abi.encode(id); + + return Caveat({ + enforcer: enforcerAddress, + terms: terms, + args: "" + }); + } + + function createNativeBalanceGteCaveat( + address enforcerAddress, + address recipient, + uint256 minBalanceIncrease + ) internal pure returns (Caveat memory) { + bytes memory terms = abi.encodePacked(recipient, minBalanceIncrease); + + return Caveat({ + enforcer: enforcerAddress, + terms: terms, + args: "" + }); + } + + function createNativeTokenPaymentCaveat( + address enforcerAddress, + address recipient, + uint256 amount + ) internal pure returns (Caveat memory) { + bytes memory terms = abi.encodePacked(recipient, amount); + + return Caveat({ + enforcer: enforcerAddress, + terms: terms, + args: "" + }); + } + + function createLimitedCallsCaveat( + address enforcerAddress, + uint256 limit + ) internal pure returns (Caveat memory) { + bytes memory terms = abi.encode(limit); + + return Caveat({ + enforcer: enforcerAddress, + terms: terms, + args: "" + }); + } + + function createAllowedMethodsCaveat( + address enforcerAddress, + string[] memory approvedMethods + ) internal pure returns (Caveat memory) { + bytes memory terms = new bytes(approvedMethods.length * 4); + uint256 offset = 0; + + for (uint256 i = 0; i < approvedMethods.length; i++) { + bytes4 methodId = bytes4(keccak256(bytes(approvedMethods[i]))); + assembly { + mstore(add(add(terms, 32), offset), methodId) + } + offset += 4; + } + + return Caveat({ + enforcer: enforcerAddress, + terms: terms, + args: "" + }); + } + function createAllowedTargetsCaveat( + address enforcerAddress, + address[] memory allowedTargets + ) internal pure returns (Caveat memory) { + bytes memory terms = new bytes(allowedTargets.length * 20); + + for (uint256 i = 0; i < allowedTargets.length; i++) { + bytes20 target = bytes20(allowedTargets[i]); + for (uint256 j = 0; j < 20; j++) { + terms[i * 20 + j] = target[j]; + } + } + + return Caveat({ + enforcer: enforcerAddress, + terms: terms, + args: "" + }); + } + function createArgsEqualityCheckCaveat( + address enforcerAddress, + bytes memory expectedArgs + ) internal pure returns (Caveat memory) { + return Caveat({ + enforcer: enforcerAddress, + terms: expectedArgs, + args: "" + }); + } + + function createBlockNumberCaveat( + address enforcerAddress, + uint128 blockAfterThreshold, + uint128 blockBeforeThreshold + ) internal pure returns (Caveat memory) { + bytes memory terms = abi.encodePacked(blockAfterThreshold, blockBeforeThreshold); + + return Caveat({ + enforcer: enforcerAddress, + terms: terms, + args: "" + }); + } + + function createDeployedEnforcerCaveat( + address enforcerAddress, + address expectedAddress, + bytes32 salt, + bytes memory bytecode + ) internal pure returns (Caveat memory) { + bytes memory terms = abi.encodePacked(expectedAddress, salt, bytecode); + + return Caveat({ + enforcer: enforcerAddress, + terms: terms, + args: "" + }); + } + + function createERC20BalanceGteCaveat( + address enforcerAddress, + address token, + uint256 amount + ) internal pure returns (Caveat memory) { + bytes memory terms = abi.encodePacked(token, amount); + + return Caveat({ + enforcer: enforcerAddress, + terms: terms, + args: "" + }); + } + + function createERC20TransferAmountCaveat( + address enforcerAddress, + address token, + uint256 maxAmount + ) internal pure returns (Caveat memory) { + bytes memory terms = abi.encodePacked(token, maxAmount); + + return Caveat({ + enforcer: enforcerAddress, + terms: terms, + args: "" + }); + } +} diff --git a/test/enforcers/AllowedCalldataEnforcer.t.sol b/test/enforcers/AllowedCalldataEnforcer.t.sol index b845486b..6ca76206 100644 --- a/test/enforcers/AllowedCalldataEnforcer.t.sol +++ b/test/enforcers/AllowedCalldataEnforcer.t.sol @@ -16,6 +16,7 @@ import { EncoderLib } from "../../src/libraries/EncoderLib.sol"; import { BasicERC20, IERC20 } from "../utils/BasicERC20.t.sol"; import { BasicCF721 } from "../utils/BasicCF721.t.sol"; import { ICaveatEnforcer } from "../../src/interfaces/ICaveatEnforcer.sol"; +import { Caveats } from "../../src/libraries/Caveats.sol"; contract DummyContract { function stringFn(uint256[] calldata _str) public { } @@ -209,7 +210,7 @@ contract AllowedCalldataEnforcerTest is CaveatEnforcerBaseTest { bytes memory inputTerms_ = abi.encodePacked(paramStart_, paramValue_); Caveat[] memory caveats_ = new Caveat[](1); - caveats_[0] = Caveat({ args: hex"", enforcer: address(allowedCalldataEnforcer), terms: inputTerms_ }); + caveats_[0] = Caveats.createAllowedCalldataCaveat(address(allowedCalldataEnforcer), paramStart_, abi.encode(paramValue_)); Delegation memory delegation_ = Delegation({ delegate: address(users.bob.deleGator), delegator: address(users.alice.deleGator), @@ -248,14 +249,12 @@ contract AllowedCalldataEnforcerTest is CaveatEnforcerBaseTest { value: 0, callData: abi.encodeWithSelector(IERC20.transfer.selector, address(users.bob.deleGator), uint256(2)) }); - // create terms for the enforcer - uint256 paramStart_ = abi.encodeWithSelector(IERC20.transfer.selector, address(0)).length; - uint256 paramValue_ = 1; - bytes memory inputTerms_ = abi.encodePacked(paramStart_, paramValue_); + uint256 dataStart = abi.encodeWithSelector(IERC20.transfer.selector, address(0)).length; + bytes memory expectedValue = abi.encode(uint256(1)); Caveat[] memory caveats_ = new Caveat[](1); - caveats_[0] = Caveat({ args: hex"", enforcer: address(allowedCalldataEnforcer), terms: inputTerms_ }); + caveats_[0] = Caveats.createAllowedCalldataCaveat(address(allowedCalldataEnforcer), dataStart, expectedValue); Delegation memory delegation_ = Delegation({ delegate: address(users.bob.deleGator), delegator: address(users.alice.deleGator), diff --git a/test/enforcers/AllowedMethodsEnforcer.t.sol b/test/enforcers/AllowedMethodsEnforcer.t.sol index 5e179cb7..68ded845 100644 --- a/test/enforcers/AllowedMethodsEnforcer.t.sol +++ b/test/enforcers/AllowedMethodsEnforcer.t.sol @@ -13,6 +13,7 @@ import { AllowedMethodsEnforcer } from "../../src/enforcers/AllowedMethodsEnforc import { IDelegationManager } from "../../src/interfaces/IDelegationManager.sol"; import { EncoderLib } from "../../src/libraries/EncoderLib.sol"; import { ICaveatEnforcer } from "../../src/interfaces/ICaveatEnforcer.sol"; +import { Caveats } from "../../src/libraries/Caveats.sol"; contract AllowedMethodsEnforcerTest is CaveatEnforcerBaseTest { using ModeLib for ModeCode; @@ -139,8 +140,9 @@ contract AllowedMethodsEnforcerTest is CaveatEnforcerBaseTest { }); Caveat[] memory caveats_ = new Caveat[](1); - caveats_[0] = - Caveat({ args: hex"", enforcer: address(allowedMethodsEnforcer), terms: abi.encodePacked(Counter.increment.selector) }); + string[] memory approvedMethods = new string[](1); + approvedMethods[0] = "increment()"; + caveats_[0] = Caveats.createAllowedMethodsCaveat(address(allowedMethodsEnforcer), approvedMethods); Delegation memory delegation_ = Delegation({ delegate: address(users.bob.deleGator), delegator: address(users.alice.deleGator), @@ -183,11 +185,11 @@ contract AllowedMethodsEnforcerTest is CaveatEnforcerBaseTest { }); Caveat[] memory caveats_ = new Caveat[](1); - caveats_[0] = Caveat({ - args: hex"", - enforcer: address(allowedMethodsEnforcer), - terms: abi.encodePacked(Counter.setCount.selector, Ownable.renounceOwnership.selector, Ownable.owner.selector) - }); + string[] memory approvedMethods = new string[](3); + approvedMethods[0] = "setCount(uint256)"; + approvedMethods[1] = "renounceOwnership()"; + approvedMethods[2] = "owner()"; + caveats_[0] = Caveats.createAllowedMethodsCaveat(address(allowedMethodsEnforcer), approvedMethods); Delegation memory delegation_ = Delegation({ delegate: address(users.bob.deleGator), delegator: address(users.alice.deleGator), diff --git a/test/enforcers/AllowedTargetsEnforcer.t.sol b/test/enforcers/AllowedTargetsEnforcer.t.sol index 585993b4..094c24c8 100644 --- a/test/enforcers/AllowedTargetsEnforcer.t.sol +++ b/test/enforcers/AllowedTargetsEnforcer.t.sol @@ -13,6 +13,7 @@ import { IDelegationManager } from "../../src/interfaces/IDelegationManager.sol" import { EncoderLib } from "../../src/libraries/EncoderLib.sol"; import { ICaveatEnforcer } from "../../src/interfaces/ICaveatEnforcer.sol"; import { BasicERC20, IERC20 } from "../utils/BasicERC20.t.sol"; +import { Caveats } from "../../src/libraries/Caveats.sol"; contract AllowedTargetsEnforcerTest is CaveatEnforcerBaseTest { using ModeLib for ModeCode; @@ -129,11 +130,10 @@ contract AllowedTargetsEnforcerTest is CaveatEnforcerBaseTest { }); Caveat[] memory caveats_ = new Caveat[](1); - caveats_[0] = Caveat({ - args: hex"", - enforcer: address(allowedTargetsEnforcer), - terms: abi.encodePacked(address(aliceDeleGatorCounter), address(testFToken1)) - }); + address[] memory allowedTargets = new address[](2); + allowedTargets[0] = address(aliceDeleGatorCounter); + allowedTargets[1] = address(testFToken1); + caveats_[0] = Caveats.createAllowedTargetsCaveat(address(allowedTargetsEnforcer), allowedTargets); Delegation memory delegation_ = Delegation({ delegate: address(users.bob.deleGator), delegator: address(users.alice.deleGator), @@ -180,8 +180,9 @@ contract AllowedTargetsEnforcerTest is CaveatEnforcerBaseTest { // Approving the user to use the FToken1 Caveat[] memory caveats_ = new Caveat[](1); - caveats_[0] = - Caveat({ args: hex"", enforcer: address(allowedTargetsEnforcer), terms: abi.encodePacked(address(testFToken1)) }); + address[] memory allowedTargets = new address[](1); + allowedTargets[0] = address(testFToken1); + caveats_[0] = Caveats.createAllowedTargetsCaveat(address(allowedTargetsEnforcer), allowedTargets); Delegation memory delegation_ = Delegation({ delegate: address(users.bob.deleGator), delegator: address(users.alice.deleGator), diff --git a/test/enforcers/ArgsEqualityCheckEnforcer.t.sol b/test/enforcers/ArgsEqualityCheckEnforcer.t.sol index 5588682c..b2f7673c 100644 --- a/test/enforcers/ArgsEqualityCheckEnforcer.t.sol +++ b/test/enforcers/ArgsEqualityCheckEnforcer.t.sol @@ -7,6 +7,7 @@ import "../../src/utils/Types.sol"; import { CaveatEnforcerBaseTest } from "./CaveatEnforcerBaseTest.t.sol"; import { ArgsEqualityCheckEnforcer } from "../../src/enforcers/ArgsEqualityCheckEnforcer.sol"; import { ICaveatEnforcer } from "../../src/interfaces/ICaveatEnforcer.sol"; +import { Caveats } from "../../src/libraries/Caveats.sol"; contract ArgsEqualityCheckEnforcerTest is CaveatEnforcerBaseTest { using ModeLib for ModeCode; @@ -28,26 +29,27 @@ contract ArgsEqualityCheckEnforcerTest is CaveatEnforcerBaseTest { // should SUCCEED to pass enforcer if terms equals args function test_passEnforcerWhenTermsEqualsArgs() public { - bytes memory terms_ = bytes("This is an example"); - bytes memory args_ = bytes("This is an example"); + bytes memory expectedArgs = bytes("This is an example"); + Caveat memory caveat = Caveats.createArgsEqualityCheckCaveat(address(argsEqualityCheckEnforcer), expectedArgs); argsEqualityCheckEnforcer.beforeHook( - terms_, args_, mode, abi.encode(new Execution[](1)[0]), bytes32(0), address(0), address(0) + caveat.terms, expectedArgs, mode, abi.encode(new Execution[](1)[0]), bytes32(0), address(0), address(0) ); } ////////////////////// Invalid cases ////////////////////// - // should FAIL to pass enforcer if terms and args are differnt + // should FAIL to pass enforcer if terms and args are different function test_failToPasEnforcerWhenTermsAndArgsAreDifferent() public { - bytes memory terms_ = bytes("This is an example1"); - bytes memory args_ = bytes("This is an example2"); + bytes memory expectedArgs = bytes("This is an example1"); + bytes memory actualArgs = bytes("This is an example2"); + Caveat memory caveat = Caveats.createArgsEqualityCheckCaveat(address(argsEqualityCheckEnforcer), expectedArgs); address redeemer_ = address(99999); vm.startPrank(address(delegationManager)); vm.expectRevert("ArgsEqualityCheckEnforcer:different-args-and-terms"); vm.expectEmit(true, true, true, true, address(argsEqualityCheckEnforcer)); - emit ArgsEqualityCheckEnforcer.DifferentArgsAndTerms(address(delegationManager), redeemer_, bytes32(0), terms_, args_); + emit ArgsEqualityCheckEnforcer.DifferentArgsAndTerms(address(delegationManager), redeemer_, bytes32(0), caveat.terms, actualArgs); argsEqualityCheckEnforcer.beforeHook( - terms_, args_, mode, abi.encode(new Execution[](1)[0]), bytes32(0), address(0), redeemer_ + caveat.terms, actualArgs, mode, abi.encode(new Execution[](1)[0]), bytes32(0), address(0), redeemer_ ); } diff --git a/test/enforcers/BlockNumberEnforcer.t.sol b/test/enforcers/BlockNumberEnforcer.t.sol index c5eb7bc4..936e1cbc 100644 --- a/test/enforcers/BlockNumberEnforcer.t.sol +++ b/test/enforcers/BlockNumberEnforcer.t.sol @@ -13,6 +13,7 @@ import { BlockNumberEnforcer } from "../../src/enforcers/BlockNumberEnforcer.sol import { EncoderLib } from "../../src/libraries/EncoderLib.sol"; import { IDelegationManager } from "../../src/interfaces/IDelegationManager.sol"; import { ICaveatEnforcer } from "../../src/interfaces/ICaveatEnforcer.sol"; +import { Caveats } from "../../src/libraries/Caveats.sol"; contract BlockNumberEnforcerTest is CaveatEnforcerBaseTest { ////////////////////// State ////////////////////// @@ -43,9 +44,9 @@ contract BlockNumberEnforcerTest is CaveatEnforcerBaseTest { vm.roll(10000); uint128 blockAfterThreshold_ = 1; uint128 blockBeforeThreshold_ = 0; // Not using before threshold - bytes memory inputTerms_ = abi.encodePacked(blockAfterThreshold_, blockBeforeThreshold_); + Caveat memory caveat = Caveats.createBlockNumberCaveat(address(blockNumberEnforcer), blockAfterThreshold_, blockBeforeThreshold_); vm.prank(address(delegationManager)); - blockNumberEnforcer.beforeHook(inputTerms_, hex"", mode, executionCallData_, keccak256(""), address(0), address(0)); + blockNumberEnforcer.beforeHook(caveat.terms, hex"", mode, executionCallData_, keccak256(""), address(0), address(0)); } //should SUCCEED to INVOKE method BEFORE blockNumber reached @@ -60,9 +61,9 @@ contract BlockNumberEnforcerTest is CaveatEnforcerBaseTest { uint128 blockAfterThreshold_ = 0; // Not using after threshold uint128 blockBeforeThreshold_ = uint128(block.number + 10000); - bytes memory inputTerms_ = abi.encodePacked(blockAfterThreshold_, blockBeforeThreshold_); + Caveat memory caveat = Caveats.createBlockNumberCaveat(address(blockNumberEnforcer), blockAfterThreshold_, blockBeforeThreshold_); vm.prank(address(delegationManager)); - blockNumberEnforcer.beforeHook(inputTerms_, hex"", mode, executionCallData_, keccak256(""), address(0), address(0)); + blockNumberEnforcer.beforeHook(caveat.terms, hex"", mode, executionCallData_, keccak256(""), address(0), address(0)); } // should SUCCEED to INVOKE method inside blockNumber RANGE @@ -78,9 +79,9 @@ contract BlockNumberEnforcerTest is CaveatEnforcerBaseTest { uint128 blockAfterThreshold_ = 1; uint128 blockBeforeThreshold_ = uint128(block.number + 10000); vm.roll(1000); // making block number between 1 and 10001 - bytes memory inputTerms_ = abi.encodePacked(blockAfterThreshold_, blockBeforeThreshold_); + Caveat memory caveat = Caveats.createBlockNumberCaveat(address(blockNumberEnforcer), blockAfterThreshold_, blockBeforeThreshold_); vm.prank(address(delegationManager)); - blockNumberEnforcer.beforeHook(inputTerms_, hex"", mode, executionCallData_, keccak256(""), address(0), address(0)); + blockNumberEnforcer.beforeHook(caveat.terms, hex"", mode, executionCallData_, keccak256(""), address(0), address(0)); } ////////////////////// Invalid cases ////////////////////// @@ -103,11 +104,11 @@ contract BlockNumberEnforcerTest is CaveatEnforcerBaseTest { uint128 blockAfterThreshold_ = uint128(block.number + 10000); uint128 blockBeforeThreshold_ = 0; // Not using before threshold - bytes memory inputTerms_ = abi.encodePacked(blockAfterThreshold_, blockBeforeThreshold_); + Caveat memory caveat = Caveats.createBlockNumberCaveat(address(blockNumberEnforcer), blockAfterThreshold_, blockBeforeThreshold_); vm.prank(address(delegationManager)); vm.expectRevert("BlockNumberEnforcer:early-delegation"); - blockNumberEnforcer.beforeHook(inputTerms_, hex"", mode, executionCallData_, keccak256(""), address(0), address(0)); + blockNumberEnforcer.beforeHook(caveat.terms, hex"", mode, executionCallData_, keccak256(""), address(0), address(0)); } // should FAIL to INVOKE method AFTER blockNumber reached @@ -123,11 +124,11 @@ contract BlockNumberEnforcerTest is CaveatEnforcerBaseTest { uint128 blockAfterThreshold_ = 0; // Not using after threshold uint128 blockBeforeThreshold_ = uint128(block.number); vm.roll(10000); - bytes memory inputTerms_ = abi.encodePacked(blockAfterThreshold_, blockBeforeThreshold_); + Caveat memory caveat = Caveats.createBlockNumberCaveat(address(blockNumberEnforcer), blockAfterThreshold_, blockBeforeThreshold_); vm.prank(address(delegationManager)); vm.expectRevert("BlockNumberEnforcer:expired-delegation"); - blockNumberEnforcer.beforeHook(inputTerms_, hex"", mode, executionCallData_, keccak256(""), address(0), address(0)); + blockNumberEnforcer.beforeHook(caveat.terms, hex"", mode, executionCallData_, keccak256(""), address(0), address(0)); } // should FAIL to INVOKE method BEFORE blocknumber RANGE @@ -142,11 +143,11 @@ contract BlockNumberEnforcerTest is CaveatEnforcerBaseTest { uint128 blockAfterThreshold_ = uint128(block.number + 10000); uint128 blockBeforeThreshold_ = uint128(block.number + 20000); - bytes memory inputTerms_ = abi.encodePacked(blockAfterThreshold_, blockBeforeThreshold_); + Caveat memory caveat = Caveats.createBlockNumberCaveat(address(blockNumberEnforcer), blockAfterThreshold_, blockBeforeThreshold_); vm.prank(address(delegationManager)); vm.expectRevert("BlockNumberEnforcer:early-delegation"); - blockNumberEnforcer.beforeHook(inputTerms_, hex"", mode, executionCallData_, keccak256(""), address(0), address(0)); + blockNumberEnforcer.beforeHook(caveat.terms, hex"", mode, executionCallData_, keccak256(""), address(0), address(0)); } // should FAIL to INVOKE method AFTER blocknumber RANGE" @@ -162,11 +163,11 @@ contract BlockNumberEnforcerTest is CaveatEnforcerBaseTest { uint128 blockAfterThreshold_ = uint128(block.number + 10000); uint128 blockBeforeThreshold_ = uint128(block.number + 20000); vm.roll(30000); - bytes memory inputTerms_ = abi.encodePacked(blockAfterThreshold_, blockBeforeThreshold_); + Caveat memory caveat = Caveats.createBlockNumberCaveat(address(blockNumberEnforcer), blockAfterThreshold_, blockBeforeThreshold_); vm.prank(address(delegationManager)); vm.expectRevert("BlockNumberEnforcer:expired-delegation"); - blockNumberEnforcer.beforeHook(inputTerms_, hex"", mode, executionCallData_, keccak256(""), address(0), address(0)); + blockNumberEnforcer.beforeHook(caveat.terms, hex"", mode, executionCallData_, keccak256(""), address(0), address(0)); } ////////////////////// Integration ////////////////////// @@ -182,10 +183,10 @@ contract BlockNumberEnforcerTest is CaveatEnforcerBaseTest { }); vm.roll(10); // Not using before threshold (blockAfterThreshold_ = 1, blockBeforeThreshold_ = 100) - bytes memory inputTerms_ = abi.encodePacked(uint128(1), uint128(100)); + Caveat memory caveat = Caveats.createBlockNumberCaveat(address(blockNumberEnforcer), 1, 100); Caveat[] memory caveats_ = new Caveat[](1); - caveats_[0] = Caveat({ args: hex"", enforcer: address(blockNumberEnforcer), terms: inputTerms_ }); + caveats_[0] = caveat; Delegation memory delegation = Delegation({ delegate: address(users.bob.deleGator), delegator: address(users.alice.deleGator), diff --git a/test/enforcers/CaveatEnforcerBaseTest.t.sol b/test/enforcers/CaveatEnforcerBaseTest.t.sol index 8e28a94a..d44501df 100644 --- a/test/enforcers/CaveatEnforcerBaseTest.t.sol +++ b/test/enforcers/CaveatEnforcerBaseTest.t.sol @@ -5,6 +5,7 @@ import { BaseTest } from "../utils/BaseTest.t.sol"; import { Implementation, SignatureType } from "../utils/Types.t.sol"; import { Counter } from "../utils/Counter.t.sol"; import { ICaveatEnforcer } from "../../src/interfaces/ICaveatEnforcer.sol"; +import { Caveats } from "../../src/libraries/Caveats.sol"; abstract contract CaveatEnforcerBaseTest is BaseTest { constructor() { diff --git a/test/enforcers/DeployedEnforcer.t.sol b/test/enforcers/DeployedEnforcer.t.sol index 870b1797..9083ade8 100644 --- a/test/enforcers/DeployedEnforcer.t.sol +++ b/test/enforcers/DeployedEnforcer.t.sol @@ -11,6 +11,7 @@ import { DeployedEnforcer } from "../../src/enforcers/DeployedEnforcer.sol"; import { IDelegationManager } from "../../src/interfaces/IDelegationManager.sol"; import { EncoderLib } from "../../src/libraries/EncoderLib.sol"; import { ICaveatEnforcer } from "../../src/interfaces/ICaveatEnforcer.sol"; +import { Caveats } from "../../src/libraries/Caveats.sol"; contract DeployedEnforcerTest is CaveatEnforcerBaseTest { using ModeLib for ModeCode; @@ -269,13 +270,13 @@ contract DeployedEnforcerTest is CaveatEnforcerBaseTest { // Check that the contract hasn't been deployed yet bytes memory initialCode_ = predictedAddr_.code; assertEq(initialCode_, bytes("")); - Caveat[] memory caveats_ = new Caveat[](1); - caveats_[0] = Caveat({ - args: hex"", - enforcer: address(deployedEnforcer), - terms: abi.encodePacked(predictedAddr_, salt, abi.encodePacked(type(Counter).creationCode)) - }); + caveats_[0] = Caveats.createDeployedEnforcerCaveat( + address(deployedEnforcer), + predictedAddr_, + salt, + type(Counter).creationCode + ); Delegation memory delegation = Delegation({ delegate: address(users.bob.deleGator), delegator: address(users.alice.deleGator), @@ -284,7 +285,6 @@ contract DeployedEnforcerTest is CaveatEnforcerBaseTest { salt: 0, signature: hex"" }); - delegation = signDelegation(users.alice, delegation); // Execute Bob's UserOp diff --git a/test/enforcers/ERC20BalanceGteEnforcer.t.sol b/test/enforcers/ERC20BalanceGteEnforcer.t.sol index 48362e8e..827107ca 100644 --- a/test/enforcers/ERC20BalanceGteEnforcer.t.sol +++ b/test/enforcers/ERC20BalanceGteEnforcer.t.sol @@ -10,6 +10,7 @@ import { Execution } from "../../src/utils/Types.sol"; import { CaveatEnforcerBaseTest } from "./CaveatEnforcerBaseTest.t.sol"; import { ERC20BalanceGteEnforcer } from "../../src/enforcers/ERC20BalanceGteEnforcer.sol"; import { ICaveatEnforcer } from "../../src/interfaces/ICaveatEnforcer.sol"; +import { Caveats } from "../../src/libraries/Caveats.sol"; contract ERC20BalanceGteEnforcerTest is CaveatEnforcerBaseTest { using ModeLib for ModeCode; @@ -44,10 +45,10 @@ contract ERC20BalanceGteEnforcerTest is CaveatEnforcerBaseTest { // Validates the terms get decoded correctly function test_decodedTheTerms() public { - bytes memory terms_ = abi.encodePacked(address(token), uint256(100)); + Caveat memory caveat = Caveats.createERC20BalanceGteCaveat(address(enforcer), address(token), 100); uint256 amount_; address token_; - (token_, amount_) = enforcer.getTermsInfo(terms_); + (token_, amount_) = enforcer.getTermsInfo(caveat.terms); assertEq(amount_, 100); assertEq(token_, address(token)); } @@ -55,23 +56,23 @@ contract ERC20BalanceGteEnforcerTest is CaveatEnforcerBaseTest { // Validates that a balance has increased at least the expected amount function test_allow_ifBalanceIncreases() public { // Expect it to increase by at least 100 - bytes memory terms_ = abi.encodePacked(address(token), uint256(100)); + Caveat memory caveat = Caveats.createERC20BalanceGteCaveat(address(enforcer), address(token), 100); // Increase by 100 vm.prank(dm); - enforcer.beforeHook(terms_, hex"", mode, mintExecutionCallData, bytes32(0), delegator, delegate); + enforcer.beforeHook(caveat.terms, hex"", mode, mintExecutionCallData, bytes32(0), delegator, delegate); vm.prank(delegator); token.mint(delegator, 100); vm.prank(dm); - enforcer.afterHook(terms_, hex"", mode, mintExecutionCallData, bytes32(0), delegator, delegate); + enforcer.afterHook(caveat.terms, hex"", mode, mintExecutionCallData, bytes32(0), delegator, delegate); // Increase by 1000 vm.prank(dm); - enforcer.beforeHook(terms_, hex"", mode, mintExecutionCallData, bytes32(0), delegator, delegate); + enforcer.beforeHook(caveat.terms, hex"", mode, mintExecutionCallData, bytes32(0), delegator, delegate); vm.prank(delegator); token.mint(delegator, 1000); vm.prank(dm); - enforcer.afterHook(terms_, hex"", mode, mintExecutionCallData, bytes32(0), delegator, delegate); + enforcer.afterHook(caveat.terms, hex"", mode, mintExecutionCallData, bytes32(0), delegator, delegate); } // ////////////////////// Errors ////////////////////// @@ -79,41 +80,41 @@ contract ERC20BalanceGteEnforcerTest is CaveatEnforcerBaseTest { // Reverts if a balance hasn't increased by the set amount function test_notAllow_insufficientIncrease() public { // Expect it to increase by at least 100 - bytes memory terms_ = abi.encodePacked(address(token), uint256(100)); + Caveat memory caveat = Caveats.createERC20BalanceGteCaveat(address(enforcer), address(token), 100); // Increase by 10, expect revert vm.prank(dm); - enforcer.beforeHook(terms_, hex"", mode, mintExecutionCallData, bytes32(0), delegator, delegate); + enforcer.beforeHook(caveat.terms, hex"", mode, mintExecutionCallData, bytes32(0), delegator, delegate); vm.prank(delegator); token.mint(delegator, 10); vm.prank(dm); vm.expectRevert(bytes("ERC20BalanceGteEnforcer:balance-not-gt")); - enforcer.afterHook(terms_, hex"", mode, mintExecutionCallData, bytes32(0), delegator, delegate); + enforcer.afterHook(caveat.terms, hex"", mode, mintExecutionCallData, bytes32(0), delegator, delegate); } // Reverts if a enforcer is locked function test_notAllow_reenterALockedEnforcer() public { // Expect it to increase by at least 100 - bytes memory terms_ = abi.encodePacked(address(token), uint256(100)); + Caveat memory caveat = Caveats.createERC20BalanceGteCaveat(address(enforcer), address(token), 100); bytes32 delegationHash_ = bytes32(uint256(99999999)); // Increase by 100 vm.startPrank(dm); // Locks the enforcer - enforcer.beforeHook(terms_, hex"", mode, mintExecutionCallData, delegationHash_, delegator, delegate); + enforcer.beforeHook(caveat.terms, hex"", mode, mintExecutionCallData, delegationHash_, delegator, delegate); bytes32 hashKey_ = enforcer.getHashKey(address(delegationManager), address(token), delegationHash_); assertTrue(enforcer.isLocked(hashKey_)); vm.expectRevert(bytes("ERC20BalanceGteEnforcer:enforcer-is-locked")); - enforcer.beforeHook(terms_, hex"", mode, mintExecutionCallData, delegationHash_, delegator, delegate); + enforcer.beforeHook(caveat.terms, hex"", mode, mintExecutionCallData, delegationHash_, delegator, delegate); vm.startPrank(delegator); token.mint(delegator, 1000); vm.startPrank(dm); // Unlocks the enforcer - enforcer.afterHook(terms_, hex"", mode, mintExecutionCallData, delegationHash_, delegator, delegate); + enforcer.afterHook(caveat.terms, hex"", mode, mintExecutionCallData, delegationHash_, delegator, delegate); assertFalse(enforcer.isLocked(hashKey_)); // Can be used again, and locks it again - enforcer.beforeHook(terms_, hex"", mode, mintExecutionCallData, delegationHash_, delegator, delegate); + enforcer.beforeHook(caveat.terms, hex"", mode, mintExecutionCallData, delegationHash_, delegator, delegate); assertTrue(enforcer.isLocked(hashKey_)); } @@ -134,24 +135,22 @@ contract ERC20BalanceGteEnforcerTest is CaveatEnforcerBaseTest { // Validates the token address is a token function test_invalid_tokenAddress() public { - bytes memory terms_; + Caveat memory caveat = Caveats.createERC20BalanceGteCaveat(address(enforcer), address(0), 100); - // Invalid token - terms_ = abi.encodePacked(address(0), uint256(100)); vm.expectRevert(); - enforcer.beforeHook(terms_, hex"", mode, mintExecutionCallData, bytes32(0), delegator, delegate); + enforcer.beforeHook(caveat.terms, hex"", mode, mintExecutionCallData, bytes32(0), delegator, delegate); } // Validates that an invalid ID reverts function test_notAllow_expectingOverflow() public { // Expect balance to increase so much that the balance overflows - bytes memory terms_ = abi.encodePacked(address(token), type(uint256).max); + Caveat memory caveat = Caveats.createERC20BalanceGteCaveat(address(enforcer), address(token), type(uint256).max); // Increase vm.prank(dm); - enforcer.beforeHook(terms_, hex"", mode, mintExecutionCallData, bytes32(0), delegator, delegate); + enforcer.beforeHook(caveat.terms, hex"", mode, mintExecutionCallData, bytes32(0), delegator, delegate); vm.expectRevert(); - enforcer.afterHook(terms_, hex"", mode, mintExecutionCallData, bytes32(0), delegator, delegate); + enforcer.afterHook(caveat.terms, hex"", mode, mintExecutionCallData, bytes32(0), delegator, delegate); } ////////////////////// Integration ////////////////////// diff --git a/test/enforcers/ERC20TransferAmountEnforcer.t.sol b/test/enforcers/ERC20TransferAmountEnforcer.t.sol index dc4ca88e..1a5dd806 100644 --- a/test/enforcers/ERC20TransferAmountEnforcer.t.sol +++ b/test/enforcers/ERC20TransferAmountEnforcer.t.sol @@ -13,6 +13,7 @@ import { BasicERC20, IERC20 } from "../utils/BasicERC20.t.sol"; import { EncoderLib } from "../../src/libraries/EncoderLib.sol"; import { IDelegationManager } from "../../src/interfaces/IDelegationManager.sol"; import { ICaveatEnforcer } from "../../src/interfaces/ICaveatEnforcer.sol"; +import { Caveats } from "../../src/libraries/Caveats.sol"; contract ERC20TransferAmountEnforcerTest is CaveatEnforcerBaseTest { using ModeLib for ModeCode; @@ -53,7 +54,7 @@ contract ERC20TransferAmountEnforcerTest is CaveatEnforcerBaseTest { bytes memory inputTerms_ = abi.encodePacked(address(basicERC20), spendingLimit_); Caveat[] memory caveats_ = new Caveat[](1); - caveats_[0] = Caveat({ args: hex"", enforcer: address(erc20TransferAmountEnforcer), terms: inputTerms_ }); + caveats_[0] = Caveats.createERC20TransferAmountCaveat(address(erc20TransferAmountEnforcer), address(basicERC20), spendingLimit_); Delegation memory delegation_ = Delegation({ delegate: address(users.bob.deleGator), delegator: address(users.alice.deleGator), @@ -91,7 +92,7 @@ contract ERC20TransferAmountEnforcerTest is CaveatEnforcerBaseTest { bytes memory inputTerms_ = abi.encodePacked(address(basicERC20), spendingLimit_); Caveat[] memory caveats_ = new Caveat[](1); - caveats_[0] = Caveat({ args: hex"", enforcer: address(erc20TransferAmountEnforcer), terms: inputTerms_ }); + caveats_[0] = Caveats.createERC20TransferAmountCaveat(address(erc20TransferAmountEnforcer), address(basicERC20), spendingLimit_); Delegation memory delegation_ = Delegation({ delegate: address(users.bob.deleGator), delegator: address(users.alice.deleGator), @@ -129,7 +130,7 @@ contract ERC20TransferAmountEnforcerTest is CaveatEnforcerBaseTest { bytes memory inputTerms_ = abi.encodePacked(address(invalidERC20), spendingLimit_); Caveat[] memory caveats_ = new Caveat[](1); - caveats_[0] = Caveat({ args: hex"", enforcer: address(erc20TransferAmountEnforcer), terms: inputTerms_ }); + caveats_[0] = Caveats.createERC20TransferAmountCaveat(address(erc20TransferAmountEnforcer), address(invalidERC20), spendingLimit_); Delegation memory delegation_ = Delegation({ delegate: address(users.bob.deleGator), @@ -167,7 +168,7 @@ contract ERC20TransferAmountEnforcerTest is CaveatEnforcerBaseTest { bytes memory inputTerms_ = abi.encodePacked(address(basicERC20), spendingLimit_); Caveat[] memory caveats_ = new Caveat[](1); - caveats_[0] = Caveat({ args: hex"", enforcer: address(erc20TransferAmountEnforcer), terms: inputTerms_ }); + caveats_[0] = Caveats.createERC20TransferAmountCaveat(address(erc20TransferAmountEnforcer), address(basicERC20), spendingLimit_); Delegation memory delegation_ = Delegation({ delegate: address(users.bob.deleGator), @@ -203,7 +204,7 @@ contract ERC20TransferAmountEnforcerTest is CaveatEnforcerBaseTest { bytes memory inputTerms_ = abi.encodePacked(address(basicERC20), spendingLimit_); Caveat[] memory caveats_ = new Caveat[](1); - caveats_[0] = Caveat({ args: hex"", enforcer: address(erc20TransferAmountEnforcer), terms: inputTerms_ }); + caveats_[0] = Caveats.createERC20TransferAmountCaveat(address(erc20TransferAmountEnforcer), address(basicERC20), spendingLimit_); Delegation memory delegation_ = Delegation({ delegate: address(users.bob.deleGator), delegator: address(users.alice.deleGator), @@ -239,7 +240,7 @@ contract ERC20TransferAmountEnforcerTest is CaveatEnforcerBaseTest { bytes memory inputTerms_ = abi.encodePacked(address(basicERC20)); Caveat[] memory caveats_ = new Caveat[](1); - caveats_[0] = Caveat({ args: hex"", enforcer: address(erc20TransferAmountEnforcer), terms: inputTerms_ }); + caveats_[0] = Caveats.createERC20TransferAmountCaveat(address(erc20TransferAmountEnforcer), address(basicERC20), spendingLimit_); Delegation memory delegation_ = Delegation({ delegate: address(users.bob.deleGator), delegator: address(users.alice.deleGator), @@ -276,7 +277,7 @@ contract ERC20TransferAmountEnforcerTest is CaveatEnforcerBaseTest { bytes memory inputTerms_ = abi.encodePacked(address(basicERC20), spendingLimit_); Caveat[] memory caveats_ = new Caveat[](1); - caveats_[0] = Caveat({ args: hex"", enforcer: address(erc20TransferAmountEnforcer), terms: inputTerms_ }); + caveats_[0] = Caveats.createERC20TransferAmountCaveat(address(erc20TransferAmountEnforcer), address(basicERC20), spendingLimit_); Delegation memory delegation_ = Delegation({ delegate: address(users.bob.deleGator), delegator: address(users.alice.deleGator), diff --git a/test/enforcers/ERC721TransferEnforcer.t.sol b/test/enforcers/ERC721TransferEnforcer.t.sol new file mode 100644 index 00000000..cabdb07c --- /dev/null +++ b/test/enforcers/ERC721TransferEnforcer.t.sol @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: MIT AND Apache-2.0 +pragma solidity 0.8.23; + +import "forge-std/Test.sol"; +import { ModeLib } from "@erc7579/lib/ModeLib.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 { ERC721TransferEnforcer } from "../../src/enforcers/ERC721TransferEnforcer.sol"; +import { IDelegationManager } from "../../src/interfaces/IDelegationManager.sol"; +import { EncoderLib } from "../../src/libraries/EncoderLib.sol"; +import { ICaveatEnforcer } from "../../src/interfaces/ICaveatEnforcer.sol"; +import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; + +contract ERC721TransferEnforcerTest is CaveatEnforcerBaseTest { + using ModeLib for ModeCode; + + ////////////////////// State ////////////////////// + + ERC721TransferEnforcer public erc721TransferEnforcer; + ModeCode public mode = ModeLib.encodeSimpleSingle(); + IERC721 public mockNFT; + address public constant NFT_CONTRACT = address(0x1234567890123456789012345678901234567890); + uint256 public constant TOKEN_ID = 42; + + ////////////////////// Set up ////////////////////// + + function setUp() public override { + super.setUp(); + erc721TransferEnforcer = new ERC721TransferEnforcer(); + vm.label(address(erc721TransferEnforcer), "ERC721 Transfer Enforcer"); + mockNFT = IERC721(NFT_CONTRACT); + } + + ////////////////////// Valid cases ////////////////////// + function test_validTransfer() public { + Execution memory execution_ = Execution({ + target: NFT_CONTRACT, + value: 0, + callData: abi.encodeWithSelector(IERC721.transferFrom.selector, address(this), address(0xBEEF), TOKEN_ID) + }); + bytes memory executionCallData_ = ExecutionLib.encodeSingle(execution_.target, execution_.value, execution_.callData); + + vm.prank(address(delegationManager)); + erc721TransferEnforcer.beforeHook( + abi.encodePacked(NFT_CONTRACT, TOKEN_ID), + hex"", + mode, + executionCallData_, + keccak256(""), + address(0), + address(0) + ); + } + + ////////////////////// Invalid cases ////////////////////// + + function test_invalidTermsLength() public { + vm.expectRevert("ERC721TransferEnforcer:invalid-terms-length"); + erc721TransferEnforcer.getTermsInfo(abi.encodePacked(NFT_CONTRACT)); + } + + function test_unauthorizedTransfer_wrongContract() public { + Execution memory execution_ = Execution({ + target: address(0xDEAD), + value: 0, + callData: abi.encodeWithSelector(IERC721.transferFrom.selector, address(this), address(0xBEEF), TOKEN_ID) + }); + bytes memory executionCallData_ = ExecutionLib.encodeSingle(execution_.target, execution_.value, execution_.callData); + + vm.prank(address(delegationManager)); + vm.expectRevert("ERC721TransferEnforcer:unauthorized-contract-target"); + erc721TransferEnforcer.beforeHook( + abi.encodePacked(NFT_CONTRACT, TOKEN_ID), + hex"", + mode, + executionCallData_, + keccak256(""), + address(0), + address(0) + ); + } + + function test_unauthorizedTransfer_wrongTokenId() public { + Execution memory execution_ = Execution({ + target: NFT_CONTRACT, + value: 0, + callData: abi.encodeWithSelector(IERC721.transferFrom.selector, address(this), address(0xBEEF), TOKEN_ID + 1) + }); + bytes memory executionCallData_ = ExecutionLib.encodeSingle(execution_.target, execution_.value, execution_.callData); + + vm.prank(address(delegationManager)); + vm.expectRevert("ERC721TransferEnforcer:unauthorized-token-id"); + erc721TransferEnforcer.beforeHook( + abi.encodePacked(NFT_CONTRACT, TOKEN_ID), + hex"", + mode, + executionCallData_, + keccak256(""), + address(0), + address(0) + ); + } + + function test_unauthorizedTransfer_wrongSelector() public { + Execution memory execution_ = Execution({ + target: NFT_CONTRACT, + value: 0, + callData: abi.encodeWithSelector(IERC721.approve.selector, address(0xBEEF), TOKEN_ID) + }); + bytes memory executionCallData_ = ExecutionLib.encodeSingle(execution_.target, execution_.value, execution_.callData); + + vm.prank(address(delegationManager)); + vm.expectRevert("ERC721TransferEnforcer:invalid-calldata-length"); + erc721TransferEnforcer.beforeHook( + abi.encodePacked(NFT_CONTRACT, TOKEN_ID), + hex"", + mode, + executionCallData_, + keccak256(""), + address(0), + address(0) + ); + } + + ////////////////////// Integration ////////////////////// + + function test_validTransferIntegration() public { + Execution memory execution_ = Execution({ + target: NFT_CONTRACT, + value: 0, + callData: abi.encodeWithSelector(IERC721.transferFrom.selector, address(users.alice.deleGator), address(users.bob.deleGator), TOKEN_ID) + }); + + Caveat[] memory caveats_ = new Caveat[](1); + caveats_[0] = Caveat({ + args: hex"", + enforcer: address(erc721TransferEnforcer), + terms: abi.encodePacked(NFT_CONTRACT, TOKEN_ID) + }); + 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_; + + // Execute Bob's UserOp + invokeDelegation_UserOp(users.bob, delegations_, execution_); + } + + function _getEnforcer() internal view override returns (ICaveatEnforcer) { + return ICaveatEnforcer(address(erc721TransferEnforcer)); + } +} diff --git a/test/enforcers/IdEnforcer.t.sol b/test/enforcers/IdEnforcer.t.sol index 06c19b4a..2dd8ea98 100644 --- a/test/enforcers/IdEnforcer.t.sol +++ b/test/enforcers/IdEnforcer.t.sol @@ -14,6 +14,7 @@ import { EncoderLib } from "../../src/libraries/EncoderLib.sol"; import { IDelegationManager } from "../../src/interfaces/IDelegationManager.sol"; import { ICaveatEnforcer } from "../../src/interfaces/ICaveatEnforcer.sol"; import { BasicERC20, IERC20 } from "../utils/BasicERC20.t.sol"; +import { Caveats } from "../../src/libraries/Caveats.sol"; contract IdEnforcerEnforcerTest is CaveatEnforcerBaseTest { using ModeLib for ModeCode; @@ -95,10 +96,10 @@ contract IdEnforcerEnforcerTest is CaveatEnforcerBaseTest { callData: abi.encodeWithSelector(Counter.increment.selector) }); - bytes memory inputTerms_ = abi.encode(uint256(12345)); + uint256 id_ = 12345; Caveat[] memory caveats_ = new Caveat[](1); - caveats_[0] = Caveat({ args: hex"", enforcer: address(idEnforcer), terms: inputTerms_ }); + caveats_[0] = Caveats.createIdCaveat(address(idEnforcer), id_); Delegation memory delegation = Delegation({ delegate: address(users.bob.deleGator), delegator: address(users.alice.deleGator), diff --git a/test/enforcers/LimitedCallsEnforcer.t.sol b/test/enforcers/LimitedCallsEnforcer.t.sol index c07edd5b..b63bd24c 100644 --- a/test/enforcers/LimitedCallsEnforcer.t.sol +++ b/test/enforcers/LimitedCallsEnforcer.t.sol @@ -13,6 +13,7 @@ import { LimitedCallsEnforcer } from "../../src/enforcers/LimitedCallsEnforcer.s import { EncoderLib } from "../../src/libraries/EncoderLib.sol"; import { IDelegationManager } from "../../src/interfaces/IDelegationManager.sol"; import { ICaveatEnforcer } from "../../src/interfaces/ICaveatEnforcer.sol"; +import { Caveats } from "../../src/libraries/Caveats.sol"; contract LimitedCallsEnforcerTest is CaveatEnforcerBaseTest { using ModeLib for ModeCode; @@ -48,9 +49,9 @@ contract LimitedCallsEnforcerTest is CaveatEnforcerBaseTest { bytes memory executionCallData_ = ExecutionLib.encodeSingle(execution_.target, execution_.value, execution_.callData); uint256 transactionsLimit_ = 1; - bytes memory inputTerms_ = abi.encodePacked(transactionsLimit_); + Caveat memory caveat_ = Caveats.createLimitedCallsCaveat(address(limitedCallsEnforcer), transactionsLimit_); Caveat[] memory caveats_ = new Caveat[](1); - caveats_[0] = Caveat({ args: hex"", enforcer: address(limitedCallsEnforcer), terms: inputTerms_ }); + caveats_[0] = caveat_; Delegation memory delegation_ = Delegation({ delegate: address(users.bob.deleGator), delegator: address(users.alice.deleGator), @@ -65,12 +66,12 @@ contract LimitedCallsEnforcerTest is CaveatEnforcerBaseTest { vm.prank(address(delegationManager)); vm.expectEmit(true, true, true, true, address(limitedCallsEnforcer)); emit IncreasedCount(address(delegationManager), address(0), delegationHash_, 1, 1); - limitedCallsEnforcer.beforeHook(inputTerms_, hex"", mode, executionCallData_, delegationHash_, address(0), address(0)); + limitedCallsEnforcer.beforeHook(caveat_.terms, hex"", mode, executionCallData_, delegationHash_, address(0), address(0)); assertEq(limitedCallsEnforcer.callCounts(address(delegationManager), delegationHash_), transactionsLimit_); } - ////////////////////// Invalid cases ////////////////////// + ////////////////////// Invalid cases ////////////////////////////// // should FAIL to INVOKE method ABOVE limit number function test_methodFailsIfCalledAboveLimitNumber() public { @@ -83,9 +84,9 @@ contract LimitedCallsEnforcerTest is CaveatEnforcerBaseTest { bytes memory executionCallData_ = ExecutionLib.encodeSingle(execution_.target, execution_.value, execution_.callData); uint256 transactionsLimit_ = 1; - bytes memory inputTerms_ = abi.encodePacked(transactionsLimit_); + Caveat memory caveat_ = Caveats.createLimitedCallsCaveat(address(limitedCallsEnforcer), transactionsLimit_); Caveat[] memory caveats_ = new Caveat[](1); - caveats_[0] = Caveat({ args: hex"", enforcer: address(limitedCallsEnforcer), terms: inputTerms_ }); + caveats_[0] = caveat_; Delegation memory delegation_ = Delegation({ delegate: address(users.bob.deleGator), delegator: address(users.alice.deleGator), @@ -98,9 +99,9 @@ contract LimitedCallsEnforcerTest is CaveatEnforcerBaseTest { bytes32 delegationHash_ = EncoderLib._getDelegationHash(delegation_); assertEq(limitedCallsEnforcer.callCounts(address(delegationManager), delegationHash_), 0); vm.startPrank(address(delegationManager)); - limitedCallsEnforcer.beforeHook(inputTerms_, hex"", mode, executionCallData_, delegationHash_, address(0), address(0)); + limitedCallsEnforcer.beforeHook(caveat_.terms, hex"", mode, executionCallData_, delegationHash_, address(0), address(0)); vm.expectRevert("LimitedCallsEnforcer:limit-exceeded"); - limitedCallsEnforcer.beforeHook(inputTerms_, hex"", mode, executionCallData_, delegationHash_, address(0), address(0)); + limitedCallsEnforcer.beforeHook(caveat_.terms, hex"", mode, executionCallData_, delegationHash_, address(0), address(0)); assertEq(limitedCallsEnforcer.callCounts(address(delegationManager), delegationHash_), transactionsLimit_); } @@ -118,7 +119,7 @@ contract LimitedCallsEnforcerTest is CaveatEnforcerBaseTest { limitedCallsEnforcer.beforeHook(terms_, hex"", mode, executionCallData_, bytes32(0), address(0), address(0)); } - ////////////////////// Integration ////////////////////// + ////////////////////// Integration ////////////////////////////// // should FAIL to increment counter ABOVE limit number Integration function test_methodFailsAboveLimitIntegration() public { @@ -130,9 +131,10 @@ contract LimitedCallsEnforcerTest is CaveatEnforcerBaseTest { value: 0, callData: abi.encodeWithSelector(Counter.increment.selector) }); - bytes memory inputTerms_ = abi.encodePacked(uint256(1)); + uint256 transactionsLimit_ = 1; + Caveat memory caveat_ = Caveats.createLimitedCallsCaveat(address(limitedCallsEnforcer), transactionsLimit_); Caveat[] memory caveats_ = new Caveat[](1); - caveats_[0] = Caveat({ args: hex"", enforcer: address(limitedCallsEnforcer), terms: inputTerms_ }); + caveats_[0] = caveat_; Delegation memory delegation_ = Delegation({ delegate: address(users.bob.deleGator), delegator: address(users.alice.deleGator), diff --git a/test/enforcers/NativeAllowanceEnforcer.t.sol b/test/enforcers/NativeAllowanceEnforcer.t.sol index 8cc5e470..6329706b 100644 --- a/test/enforcers/NativeAllowanceEnforcer.t.sol +++ b/test/enforcers/NativeAllowanceEnforcer.t.sol @@ -9,6 +9,7 @@ import { CaveatEnforcerBaseTest } from "./CaveatEnforcerBaseTest.t.sol"; import { NativeTokenTransferAmountEnforcer } from "../../src/enforcers/NativeTokenTransferAmountEnforcer.sol"; import { EncoderLib } from "../../src/libraries/EncoderLib.sol"; import { ICaveatEnforcer } from "../../src/interfaces/ICaveatEnforcer.sol"; +import { Caveats } from "../../src/libraries/Caveats.sol"; contract NativeAllowanceEnforcerTest is CaveatEnforcerBaseTest { using ModeLib for ModeCode; @@ -131,7 +132,7 @@ contract NativeAllowanceEnforcerTest is CaveatEnforcerBaseTest { function _getExampleDelegation(bytes memory inputTerms_) internal view returns (bytes32 delegationHash_) { Caveat[] memory caveats_ = new Caveat[](1); - caveats_[0] = Caveat({ args: hex"", enforcer: address(nativeTokenTransferAmountEnforcer), terms: inputTerms_ }); + caveats_[0] = Caveats.createNativeAllowanceCaveat(address(nativeTokenTransferAmountEnforcer), abi.decode(inputTerms_, (uint256))); Delegation memory delegation_ = Delegation({ delegate: address(users.bob.deleGator), diff --git a/test/enforcers/NativeBalanceGteEnforcer.t.sol b/test/enforcers/NativeBalanceGteEnforcer.t.sol index afaaf3a9..0f85305c 100644 --- a/test/enforcers/NativeBalanceGteEnforcer.t.sol +++ b/test/enforcers/NativeBalanceGteEnforcer.t.sol @@ -9,6 +9,7 @@ import { CaveatEnforcerBaseTest } from "./CaveatEnforcerBaseTest.t.sol"; import { NativeBalanceGteEnforcer } from "../../src/enforcers/NativeBalanceGteEnforcer.sol"; import { ICaveatEnforcer } from "../../src/interfaces/ICaveatEnforcer.sol"; import { Counter } from "../utils/Counter.t.sol"; +import { Caveats } from "../../src/libraries/Caveats.sol"; contract NativeBalanceGteEnforcerTest is CaveatEnforcerBaseTest { using ModeLib for ModeCode; @@ -38,11 +39,11 @@ contract NativeBalanceGteEnforcerTest is CaveatEnforcerBaseTest { // Validates the terms get decoded correctly function test_decodedTheTerms() public { - bytes memory terms_ = abi.encodePacked(address(users.carol.deleGator), uint256(100)); + Caveat memory caveat = Caveats.createNativeBalanceGteCaveat(address(enforcer), address(users.carol.deleGator), 100); uint256 amount_; address recipient_; - (recipient_, amount_) = enforcer.getTermsInfo(terms_); - assertEq(recipient_, address(address(users.carol.deleGator))); + (recipient_, amount_) = enforcer.getTermsInfo(caveat.terms); + assertEq(recipient_, address(users.carol.deleGator)); assertEq(amount_, 100); } @@ -50,18 +51,18 @@ contract NativeBalanceGteEnforcerTest is CaveatEnforcerBaseTest { function test_allow_ifBalanceIncreases() public { address recipient_ = delegator; // Expect it to increase by at least 100 - bytes memory terms_ = abi.encodePacked(recipient_, uint256(100)); + Caveat memory caveat = Caveats.createNativeBalanceGteCaveat(address(enforcer), recipient_, 100); // Increase by 100 vm.startPrank(dm); - enforcer.beforeHook(terms_, hex"", mode, executionCallData, bytes32(0), delegator, delegate); + enforcer.beforeHook(caveat.terms, hex"", mode, executionCallData, bytes32(0), delegator, delegate); _increaseBalance(delegator, 100); - enforcer.afterHook(terms_, hex"", mode, executionCallData, bytes32(0), delegator, delegate); + enforcer.afterHook(caveat.terms, hex"", mode, executionCallData, bytes32(0), delegator, delegate); // Increase by 1000 - enforcer.beforeHook(terms_, hex"", mode, executionCallData, bytes32(0), delegator, delegate); + enforcer.beforeHook(caveat.terms, hex"", mode, executionCallData, bytes32(0), delegator, delegate); _increaseBalance(delegator, 1000); - enforcer.afterHook(terms_, hex"", mode, executionCallData, bytes32(0), delegator, delegate); + enforcer.afterHook(caveat.terms, hex"", mode, executionCallData, bytes32(0), delegator, delegate); } // ////////////////////// Errors ////////////////////// @@ -70,39 +71,39 @@ contract NativeBalanceGteEnforcerTest is CaveatEnforcerBaseTest { function test_notAllow_insufficientIncrease() public { address recipient_ = delegator; // Expect it to increase by at least 100 - bytes memory terms_ = abi.encodePacked(recipient_, uint256(100)); + Caveat memory caveat = Caveats.createNativeBalanceGteCaveat(address(enforcer), recipient_, 100); // Increase by 10, expect revert vm.startPrank(dm); - enforcer.beforeHook(terms_, hex"", mode, executionCallData, bytes32(0), delegator, delegate); + enforcer.beforeHook(caveat.terms, hex"", mode, executionCallData, bytes32(0), delegator, delegate); _increaseBalance(delegator, 10); vm.expectRevert(bytes("NativeBalanceGteEnforcer:balance-not-gt")); - enforcer.afterHook(terms_, hex"", mode, executionCallData, bytes32(0), delegator, delegate); + enforcer.afterHook(caveat.terms, hex"", mode, executionCallData, bytes32(0), delegator, delegate); } // Reverts if a enforcer is locked function test_notAllow_reenterALockedEnforcer() public { address recipient_ = delegator; // Expect it to increase by at least 100 - bytes memory terms_ = abi.encodePacked(recipient_, uint256(100)); + Caveat memory caveat = Caveats.createNativeBalanceGteCaveat(address(enforcer), recipient_, 100); bytes32 delegationHash_ = bytes32(uint256(99999999)); // Increase by 100 vm.startPrank(dm); // Locks the enforcer - enforcer.beforeHook(terms_, hex"", mode, executionCallData, delegationHash_, delegator, delegate); + enforcer.beforeHook(caveat.terms, hex"", mode, executionCallData, delegationHash_, delegator, delegate); bytes32 hashKey_ = enforcer.getHashKey(address(delegationManager), delegationHash_); assertTrue(enforcer.isLocked(hashKey_)); vm.expectRevert(bytes("NativeBalanceGteEnforcer:enforcer-is-locked")); - enforcer.beforeHook(terms_, hex"", mode, executionCallData, delegationHash_, delegator, delegate); + enforcer.beforeHook(caveat.terms, hex"", mode, executionCallData, delegationHash_, delegator, delegate); _increaseBalance(delegator, 1000); vm.startPrank(dm); // Unlocks the enforcer - enforcer.afterHook(terms_, hex"", mode, executionCallData, delegationHash_, delegator, delegate); + enforcer.afterHook(caveat.terms, hex"", mode, executionCallData, delegationHash_, delegator, delegate); assertFalse(enforcer.isLocked(hashKey_)); // Can be used again, and locks it again - enforcer.beforeHook(terms_, hex"", mode, executionCallData, delegationHash_, delegator, delegate); + enforcer.beforeHook(caveat.terms, hex"", mode, executionCallData, delegationHash_, delegator, delegate); assertTrue(enforcer.isLocked(hashKey_)); } @@ -127,12 +128,12 @@ contract NativeBalanceGteEnforcerTest is CaveatEnforcerBaseTest { address recipient_ = delegator; // Expect balance to increase so much that the validation overflows - bytes memory terms_ = abi.encodePacked(recipient_, type(uint256).max); + Caveat memory caveat = Caveats.createNativeBalanceGteCaveat(address(enforcer), recipient_, type(uint256).max); vm.deal(recipient_, type(uint256).max); vm.startPrank(dm); - enforcer.beforeHook(terms_, hex"", mode, executionCallData, bytes32(0), delegator, delegate); + enforcer.beforeHook(caveat.terms, hex"", mode, executionCallData, bytes32(0), delegator, delegate); vm.expectRevert(); - enforcer.afterHook(terms_, hex"", mode, executionCallData, bytes32(0), delegator, delegate); + enforcer.afterHook(caveat.terms, hex"", mode, executionCallData, bytes32(0), delegator, delegate); } function _increaseBalance(address _recipient, uint256 _amount) internal { diff --git a/test/enforcers/NativeTokenPaymentEnforcer.t.sol b/test/enforcers/NativeTokenPaymentEnforcer.t.sol index 755bbea1..4e1208a8 100644 --- a/test/enforcers/NativeTokenPaymentEnforcer.t.sol +++ b/test/enforcers/NativeTokenPaymentEnforcer.t.sol @@ -15,6 +15,7 @@ import { EncoderLib } from "../../src/libraries/EncoderLib.sol"; import { IDelegationManager } from "../../src/interfaces/IDelegationManager.sol"; import { ICaveatEnforcer } from "../../src/interfaces/ICaveatEnforcer.sol"; import { Counter } from "../utils/Counter.t.sol"; +import { Caveats } from "../../src/libraries/Caveats.sol"; contract NativeTokenPaymentEnforcerTest is CaveatEnforcerBaseTest { using ModeLib for ModeCode; @@ -133,7 +134,7 @@ contract NativeTokenPaymentEnforcerTest is CaveatEnforcerBaseTest { argsEnforcerTerms = abi.encodePacked(delegationHash_, address(users.bob.deleGator)); Caveat[] memory caveats_ = new Caveat[](1); - caveats_[0] = Caveat({ args: hex"", enforcer: address(argsEqualityCheckEnforcer), terms: argsEnforcerTerms }); + caveats_[0] = Caveats.createArgsEqualityCheckCaveat(address(argsEqualityCheckEnforcer), argsEnforcerTerms); allowanceDelegations_[0] = Delegation({ delegate: address(nativeTokenPaymentEnforcer), @@ -181,15 +182,12 @@ contract NativeTokenPaymentEnforcerTest is CaveatEnforcerBaseTest { // The args of the nativeTokenTransferAmountEnforcer will be overwritten // The limitedCallsEnforcer and allowedTargetsEnforcer should stay the same Caveat[] memory allowanceCaveats_ = new Caveat[](4); - allowanceCaveats_[0] = Caveat({ args: hex"", enforcer: address(argsEqualityCheckEnforcer), terms: argsEnforcerTerms }); - allowanceCaveats_[1] = Caveat({ args: hex"", enforcer: address(nativeTokenTransferAmountEnforcer), terms: allowanceTerms }); - allowanceCaveats_[2] = - Caveat({ args: hex"", enforcer: address(limitedCallsEnforcer), terms: abi.encodePacked(uint256(10)) }); - allowanceCaveats_[3] = Caveat({ - args: hex"", - enforcer: address(allowedTargetsEnforcer), - terms: abi.encodePacked(address(users.alice.deleGator)) - }); + allowanceCaveats_[0] = Caveats.createArgsEqualityCheckCaveat(address(argsEqualityCheckEnforcer), argsEnforcerTerms); + allowanceCaveats_[1] = Caveats.createNativeAllowanceCaveat(address(nativeTokenTransferAmountEnforcer), paymentAmount); + allowanceCaveats_[2] = Caveats.createLimitedCallsCaveat(address(limitedCallsEnforcer), 10); + address[] memory allowedTargets = new address[](1); + allowedTargets[0] = address(users.alice.deleGator); + allowanceCaveats_[3] = Caveats.createAllowedTargetsCaveat(address(allowedTargetsEnforcer), allowedTargets); // Create allowance delegation from Bob to NativeTokenPaymentEnforcer Delegation[] memory allowanceDelegations_ = new Delegation[](1); @@ -237,14 +235,11 @@ contract NativeTokenPaymentEnforcerTest is CaveatEnforcerBaseTest { // Even with other enforcers it should revert if it does not include the args enforcer Caveat[] memory allowanceCaveats_ = new Caveat[](3); - allowanceCaveats_[0] = Caveat({ args: hex"", enforcer: address(nativeTokenTransferAmountEnforcer), terms: allowanceTerms }); - allowanceCaveats_[1] = - Caveat({ args: hex"", enforcer: address(limitedCallsEnforcer), terms: abi.encodePacked(uint256(10)) }); - allowanceCaveats_[2] = Caveat({ - args: hex"", - enforcer: address(allowedTargetsEnforcer), - terms: abi.encodePacked(address(users.alice.deleGator)) - }); + allowanceCaveats_[0] = Caveats.createNativeAllowanceCaveat(address(nativeTokenTransferAmountEnforcer), paymentAmount); + allowanceCaveats_[1] = Caveats.createLimitedCallsCaveat(address(limitedCallsEnforcer), 10); + address[] memory allowedTargets = new address[](1); + allowedTargets[0] = address(users.alice.deleGator); + allowanceCaveats_[2] = Caveats.createAllowedTargetsCaveat(address(allowedTargetsEnforcer), allowedTargets); // Create allowance delegation from Bob to NativeTokenPaymentEnforcer Delegation[] memory allowanceDelegations_ = new Delegation[](1); @@ -337,8 +332,8 @@ contract NativeTokenPaymentEnforcerTest is CaveatEnforcerBaseTest { Caveat[] memory caveats_ = new Caveat[](2); allowanceTerms = abi.encode(paymentAmount); argsEnforcerTerms = abi.encodePacked(delegationHash_, address(users.bob.deleGator)); - caveats_[0] = Caveat({ args: hex"", enforcer: address(nativeTokenTransferAmountEnforcer), terms: allowanceTerms }); - caveats_[1] = Caveat({ args: hex"", enforcer: address(argsEqualityCheckEnforcer), terms: argsEnforcerTerms }); + caveats_[0] = Caveats.createNativeAllowanceCaveat(address(nativeTokenTransferAmountEnforcer), paymentAmount); + caveats_[1] = Caveats.createArgsEqualityCheckCaveat(address(argsEqualityCheckEnforcer), argsEnforcerTerms); Delegation[] memory allowanceDelegations_ = new Delegation[](1); allowanceDelegations_[0] = Delegation({ @@ -420,8 +415,8 @@ contract NativeTokenPaymentEnforcerTest is CaveatEnforcerBaseTest { argsEnforcerTerms = abi.encodePacked(delegationHash_, address(users.bob.deleGator)); Caveat[] memory caveats_ = new Caveat[](2); - caveats_[0] = Caveat({ args: hex"", enforcer: address(argsEqualityCheckEnforcer), terms: argsEnforcerTerms }); - caveats_[1] = Caveat({ args: hex"", enforcer: address(nativeTokenTransferAmountEnforcer), terms: allowanceTerms }); + caveats_[0] = Caveats.createArgsEqualityCheckCaveat(address(argsEqualityCheckEnforcer), argsEnforcerTerms); + caveats_[1] = Caveats.createNativeAllowanceCaveat(address(nativeTokenTransferAmountEnforcer), paymentAmount); // Create allowance delegation from Bob to NativeTokenPaymentEnforcer Delegation[] memory allowanceDelegations_ = new Delegation[](1); @@ -457,13 +452,9 @@ contract NativeTokenPaymentEnforcerTest is CaveatEnforcerBaseTest { function test_allowsRedelegationAddingExtraCosts() public { // Creating paid delegation Caveat[] memory caveatsAlice_ = new Caveat[](1); - caveatsAlice_[0] = Caveat({ args: hex"", enforcer: address(nativeTokenPaymentEnforcer), terms: paymentTerms }); + caveatsAlice_[0] = Caveats.createNativeTokenPaymentCaveat(address(nativeTokenPaymentEnforcer), paymentRecipient, paymentAmount); Caveat[] memory caveatsBob_ = new Caveat[](1); - caveatsBob_[0] = Caveat({ - args: hex"", - enforcer: address(nativeTokenPaymentEnforcer), - terms: abi.encodePacked(address(users.bob.deleGator), paymentAmount / 2) - }); + caveatsBob_[0] = Caveats.createNativeTokenPaymentCaveat(address(nativeTokenPaymentEnforcer), address(users.bob.deleGator), paymentAmount / 2); Delegation[] memory paidDelegations_ = new Delegation[](2); paidDelegations_[1] = Delegation({ @@ -490,22 +481,12 @@ contract NativeTokenPaymentEnforcerTest is CaveatEnforcerBaseTest { // Creating allowance delegation Caveat[] memory caveatsToAlice_ = new Caveat[](2); - caveatsToAlice_[0] = Caveat({ - args: hex"", - enforcer: address(argsEqualityCheckEnforcer), - terms: abi.encodePacked(delegationHashAlice_, address(users.carol.deleGator)) - }); - caveatsToAlice_[1] = - Caveat({ args: hex"", enforcer: address(nativeTokenTransferAmountEnforcer), terms: abi.encode(paymentAmount) }); + caveatsToAlice_[0] = Caveats.createArgsEqualityCheckCaveat(address(argsEqualityCheckEnforcer), abi.encodePacked(delegationHashAlice_, address(users.carol.deleGator))); + caveatsToAlice_[1] = Caveats.createNativeAllowanceCaveat(address(nativeTokenTransferAmountEnforcer), paymentAmount); Caveat[] memory caveatsToBob_ = new Caveat[](2); - caveatsToBob_[0] = Caveat({ - args: hex"", - enforcer: address(argsEqualityCheckEnforcer), - terms: abi.encodePacked(delegationHashBob_, address(users.carol.deleGator)) - }); - caveatsToBob_[1] = - Caveat({ args: hex"", enforcer: address(nativeTokenTransferAmountEnforcer), terms: abi.encode(paymentAmount / 2) }); + caveatsToBob_[0] = Caveats.createArgsEqualityCheckCaveat(address(argsEqualityCheckEnforcer), abi.encodePacked(delegationHashBob_, address(users.carol.deleGator))); + caveatsToBob_[1] = Caveats.createNativeAllowanceCaveat(address(nativeTokenTransferAmountEnforcer), paymentAmount / 2); // Create allowance delegation from Bob to NativeTokenPaymentEnforcer Delegation[] memory allowanceDelegationsToAlice_ = new Delegation[](1); diff --git a/test/enforcers/NonceEnforcer.t.sol b/test/enforcers/NonceEnforcer.t.sol index 3252eb17..ec286937 100644 --- a/test/enforcers/NonceEnforcer.t.sol +++ b/test/enforcers/NonceEnforcer.t.sol @@ -10,6 +10,7 @@ import { Execution } from "../../src/utils/Types.sol"; import { CaveatEnforcerBaseTest } from "./CaveatEnforcerBaseTest.t.sol"; import { NonceEnforcer } from "../../src/enforcers/NonceEnforcer.sol"; import { ICaveatEnforcer } from "../../src/interfaces/ICaveatEnforcer.sol"; +import { Caveats } from "../../src/libraries/Caveats.sol"; contract NonceEnforcerTest is CaveatEnforcerBaseTest { using ModeLib for ModeCode; @@ -39,19 +40,19 @@ contract NonceEnforcerTest is CaveatEnforcerBaseTest { // Validates the terms get decoded correctly function test_decodedTheTerms() public { // 0 - uint256 nonce_; - bytes memory terms_ = abi.encode(nonce_); - assertEq(enforcer.getTermsInfo(terms_), nonce_); + uint256 nonce_ = 0; + Caveat memory caveat = Caveats.createNonceCaveat(address(enforcer), nonce_); + assertEq(enforcer.getTermsInfo(caveat.terms), nonce_); // boring integer nonce_ = 100; - terms_ = abi.encode(nonce_); - assertEq(enforcer.getTermsInfo(terms_), nonce_); + caveat = Caveats.createNonceCaveat(address(enforcer), nonce_); + assertEq(enforcer.getTermsInfo(caveat.terms), nonce_); // uint256 max nonce_ = type(uint256).max; - terms_ = abi.encode(nonce_); - assertEq(enforcer.getTermsInfo(terms_), nonce_); + caveat = Caveats.createNonceCaveat(address(enforcer), nonce_); + assertEq(enforcer.getTermsInfo(caveat.terms), nonce_); } // Validates that the delegator can increment the ID @@ -67,19 +68,18 @@ contract NonceEnforcerTest is CaveatEnforcerBaseTest { // Validates that a valid ID doesn't revert function test_allow_validId() public { uint256 nonce_ = enforcer.currentNonce(dm, delegator); - bytes memory terms_ = abi.encode(nonce_); + Caveat memory caveat = Caveats.createNonceCaveat(address(enforcer), nonce_); vm.startPrank(dm); // Should not revert - enforcer.beforeHook(terms_, hex"", mode, executionCallData, bytes32(0), delegator, address(0)); + enforcer.beforeHook(caveat.terms, hex"", mode, executionCallData, bytes32(0), delegator, address(0)); } ////////////////////// Errors ////////////////////// // Validates the terms are enforced function test_invalid_decodedTheTerms() public { - uint256 nonce_; bytes memory terms_ = hex""; // Too small @@ -87,7 +87,7 @@ contract NonceEnforcerTest is CaveatEnforcerBaseTest { enforcer.getTermsInfo(terms_); // Too large - nonce_ = 100; + uint256 nonce_ = 100; terms_ = abi.encode(nonce_, nonce_); vm.expectRevert(bytes("NonceEnforcer:invalid-terms-length")); enforcer.getTermsInfo(terms_); @@ -97,10 +97,10 @@ contract NonceEnforcerTest is CaveatEnforcerBaseTest { function test_notAllow_invalidId() public { // Higher ID should revert uint256 nonce_ = enforcer.currentNonce(dm, delegator); - bytes memory terms_ = abi.encode(nonce_ + 1); + Caveat memory caveat = Caveats.createNonceCaveat(address(enforcer), nonce_ + 1); vm.startPrank(dm); vm.expectRevert(bytes("NonceEnforcer:invalid-nonce")); - enforcer.beforeHook(terms_, hex"", mode, executionCallData, bytes32(0), delegator, address(0)); + enforcer.beforeHook(caveat.terms, hex"", mode, executionCallData, bytes32(0), delegator, address(0)); // Increment ID so the current ID is high enough to check a lower ID vm.startPrank(dm); @@ -108,10 +108,10 @@ contract NonceEnforcerTest is CaveatEnforcerBaseTest { nonce_ = enforcer.currentNonce(dm, delegator); // Lower ID should also revert - terms_ = abi.encode(nonce_ - 1); + caveat = Caveats.createNonceCaveat(address(enforcer), nonce_ - 1); vm.startPrank(dm); vm.expectRevert(bytes("NonceEnforcer:invalid-nonce")); - enforcer.beforeHook(terms_, hex"", mode, executionCallData, bytes32(0), delegator, address(0)); + enforcer.beforeHook(caveat.terms, hex"", mode, executionCallData, bytes32(0), delegator, address(0)); } ////////////////////// Integration ////////////////////// diff --git a/test/enforcers/RedeemerEnforcer.t.sol b/test/enforcers/RedeemerEnforcer.t.sol index 6ac80302..26a56aa6 100644 --- a/test/enforcers/RedeemerEnforcer.t.sol +++ b/test/enforcers/RedeemerEnforcer.t.sol @@ -4,11 +4,12 @@ pragma solidity 0.8.23; import { ModeLib } from "@erc7579/lib/ModeLib.sol"; import { ExecutionLib } from "@erc7579/lib/ExecutionLib.sol"; -import { Execution, ModeCode } from "../../src/utils/Types.sol"; +import { Execution, ModeCode, Caveat } from "../../src/utils/Types.sol"; import { Counter } from "../utils/Counter.t.sol"; import { CaveatEnforcerBaseTest } from "./CaveatEnforcerBaseTest.t.sol"; import { RedeemerEnforcer } from "../../src/enforcers/RedeemerEnforcer.sol"; import { ICaveatEnforcer } from "../../src/interfaces/ICaveatEnforcer.sol"; +import { Caveats } from "../../src/libraries/Caveats.sol"; contract RedeemerEnforcerTest is CaveatEnforcerBaseTest { using ModeLib for ModeCode; @@ -29,10 +30,13 @@ contract RedeemerEnforcerTest is CaveatEnforcerBaseTest { // should SUCCEED to get terms info when passing valid terms function test_decodeTermsInfo() public { - bytes memory terms_ = abi.encodePacked(address(users.alice.deleGator), address(users.bob.deleGator)); - address[] memory allowedRedeemers_ = redeemerEnforcer.getTermsInfo(terms_); - assertEq(allowedRedeemers_[0], address(users.alice.deleGator)); - assertEq(allowedRedeemers_[1], address(users.bob.deleGator)); + address[] memory allowedRedeemers = new address[](2); + allowedRedeemers[0] = address(users.alice.deleGator); + allowedRedeemers[1] = address(users.bob.deleGator); + Caveat memory caveat = Caveats.createRedeemerCaveat(address(redeemerEnforcer), allowedRedeemers); + address[] memory decodedRedeemers = redeemerEnforcer.getTermsInfo(caveat.terms); + assertEq(decodedRedeemers[0], address(users.alice.deleGator)); + assertEq(decodedRedeemers[1], address(users.bob.deleGator)); } // should pass if called from a single valid redeemer @@ -44,10 +48,12 @@ contract RedeemerEnforcerTest is CaveatEnforcerBaseTest { }); bytes memory executionCallData_ = ExecutionLib.encodeSingle(execution_.target, execution_.value, execution_.callData); - bytes memory terms_ = abi.encodePacked(address(users.bob.deleGator)); + address[] memory allowedRedeemers = new address[](1); + allowedRedeemers[0] = address(users.bob.deleGator); + Caveat memory caveat = Caveats.createRedeemerCaveat(address(redeemerEnforcer), allowedRedeemers); vm.prank(address(delegationManager)); redeemerEnforcer.beforeHook( - terms_, hex"", mode, executionCallData_, keccak256(""), address(0), address(users.bob.deleGator) + caveat.terms, hex"", mode, executionCallData_, keccak256(""), address(0), address(users.bob.deleGator) ); } @@ -60,13 +66,16 @@ contract RedeemerEnforcerTest is CaveatEnforcerBaseTest { }); bytes memory executionCallData_ = ExecutionLib.encodeSingle(execution_.target, execution_.value, execution_.callData); - bytes memory terms_ = abi.encodePacked(address(users.alice.deleGator), address(users.bob.deleGator)); + address[] memory allowedRedeemers = new address[](2); + allowedRedeemers[0] = address(users.alice.deleGator); + allowedRedeemers[1] = address(users.bob.deleGator); + Caveat memory caveat = Caveats.createRedeemerCaveat(address(redeemerEnforcer), allowedRedeemers); vm.startPrank(address(delegationManager)); redeemerEnforcer.beforeHook( - terms_, hex"", mode, executionCallData_, keccak256(""), address(0), address(users.alice.deleGator) + caveat.terms, hex"", mode, executionCallData_, keccak256(""), address(0), address(users.alice.deleGator) ); redeemerEnforcer.beforeHook( - terms_, hex"", mode, executionCallData_, keccak256(""), address(0), address(users.bob.deleGator) + caveat.terms, hex"", mode, executionCallData_, keccak256(""), address(0), address(users.bob.deleGator) ); } @@ -87,12 +96,14 @@ contract RedeemerEnforcerTest is CaveatEnforcerBaseTest { }); bytes memory executionCallData_ = ExecutionLib.encodeSingle(execution_.target, execution_.value, execution_.callData); - bytes memory terms_ = abi.encodePacked(address(users.bob.deleGator)); + address[] memory allowedRedeemers = new address[](1); + allowedRedeemers[0] = address(users.bob.deleGator); + Caveat memory caveat = Caveats.createRedeemerCaveat(address(redeemerEnforcer), allowedRedeemers); vm.prank(address(delegationManager)); // Dave is not a valid redeemer vm.expectRevert("RedeemerEnforcer:unauthorized-redeemer"); redeemerEnforcer.beforeHook( - terms_, hex"", mode, executionCallData_, keccak256(""), address(0), address(users.dave.deleGator) + caveat.terms, hex"", mode, executionCallData_, keccak256(""), address(0), address(users.dave.deleGator) ); } diff --git a/test/enforcers/TimestampEnforcer.t.sol b/test/enforcers/TimestampEnforcer.t.sol index 1accd25d..bfd321fa 100644 --- a/test/enforcers/TimestampEnforcer.t.sol +++ b/test/enforcers/TimestampEnforcer.t.sol @@ -13,6 +13,7 @@ import { IDelegationManager } from "../../src/interfaces/IDelegationManager.sol" import { DelegationManager } from "../../src/DelegationManager.sol"; import { EncoderLib } from "../../src/libraries/EncoderLib.sol"; import { ICaveatEnforcer } from "../../src/interfaces/ICaveatEnforcer.sol"; +import { Caveats } from "../../src/libraries/Caveats.sol"; contract TimestampEnforcerTest is CaveatEnforcerBaseTest { using ModeLib for ModeCode; @@ -45,9 +46,9 @@ contract TimestampEnforcerTest is CaveatEnforcerBaseTest { skip(1 hours); // Increase time 1 hour uint128 timestampAfterThreshold_ = 1; // Minimum timestamp uint128 timestampBeforeThreshold_ = 0; // Not using before threshold - bytes memory inputTerms_ = abi.encodePacked(timestampAfterThreshold_, timestampBeforeThreshold_); + Caveat memory caveat = Caveats.createTimestampCaveat(address(timestampEnforcer), timestampAfterThreshold_, timestampBeforeThreshold_); vm.prank(address(delegationManager)); - timestampEnforcer.beforeHook(inputTerms_, hex"", mode, executionCallData_, keccak256(""), address(0), address(0)); + timestampEnforcer.beforeHook(caveat.terms, hex"", mode, executionCallData_, keccak256(""), address(0), address(0)); } // should SUCCEED to INVOKE method BEFORE timestamp reached @@ -62,9 +63,9 @@ contract TimestampEnforcerTest is CaveatEnforcerBaseTest { uint128 timestampAfterThreshold_ = 0; // Not using after threshold uint128 timestampBeforeThreshold_ = uint128(block.timestamp + 1 hours); - bytes memory inputTerms_ = abi.encodePacked(timestampAfterThreshold_, timestampBeforeThreshold_); + Caveat memory caveat = Caveats.createTimestampCaveat(address(timestampEnforcer), timestampAfterThreshold_, timestampBeforeThreshold_); vm.prank(address(delegationManager)); - timestampEnforcer.beforeHook(inputTerms_, hex"", mode, executionCallData_, keccak256(""), address(0), address(0)); + timestampEnforcer.beforeHook(caveat.terms, hex"", mode, executionCallData_, keccak256(""), address(0), address(0)); } // should SUCCEED to INVOKE method inside of timestamp RANGE @@ -80,9 +81,9 @@ contract TimestampEnforcerTest is CaveatEnforcerBaseTest { uint128 timestampAfterThreshold_ = 1; // Minimum timestamp uint128 timestampBeforeThreshold_ = uint128(block.timestamp + 1 hours); skip(1 minutes); // Increase time 1 minute - bytes memory inputTerms_ = abi.encodePacked(timestampAfterThreshold_, timestampBeforeThreshold_); + Caveat memory caveat = Caveats.createTimestampCaveat(address(timestampEnforcer), timestampAfterThreshold_, timestampBeforeThreshold_); vm.prank(address(delegationManager)); - timestampEnforcer.beforeHook(inputTerms_, hex"", mode, executionCallData_, keccak256(""), address(0), address(0)); + timestampEnforcer.beforeHook(caveat.terms, hex"", mode, executionCallData_, keccak256(""), address(0), address(0)); } ////////////////////// Invalid cases ////////////////////// @@ -99,11 +100,11 @@ contract TimestampEnforcerTest is CaveatEnforcerBaseTest { uint128 timestampAfterThreshold_ = uint128(block.timestamp + 1 hours); uint128 timestampBeforeThreshold_ = 0; // Not using before threshold - bytes memory inputTerms_ = abi.encodePacked(timestampAfterThreshold_, timestampBeforeThreshold_); + Caveat memory caveat = Caveats.createTimestampCaveat(address(timestampEnforcer), timestampAfterThreshold_, timestampBeforeThreshold_); vm.prank(address(delegationManager)); vm.expectRevert("TimestampEnforcer:early-delegation"); - timestampEnforcer.beforeHook(inputTerms_, hex"", mode, executionCallData_, keccak256(""), address(0), address(0)); + timestampEnforcer.beforeHook(caveat.terms, hex"", mode, executionCallData_, keccak256(""), address(0), address(0)); } // should FAIL to INVOKE method AFTER timestamp reached @@ -119,10 +120,10 @@ contract TimestampEnforcerTest is CaveatEnforcerBaseTest { uint128 timestampAfterThreshold_ = 0; // Not using after threshold uint128 timestampBeforeThreshold_ = uint128(block.timestamp); skip(1 hours); // Increase time 1 hour - bytes memory inputTerms_ = abi.encodePacked(timestampAfterThreshold_, timestampBeforeThreshold_); + Caveat memory caveat = Caveats.createTimestampCaveat(address(timestampEnforcer), timestampAfterThreshold_, timestampBeforeThreshold_); vm.prank(address(delegationManager)); vm.expectRevert("TimestampEnforcer:expired-delegation"); - timestampEnforcer.beforeHook(inputTerms_, hex"", mode, executionCallData_, keccak256(""), address(0), address(0)); + timestampEnforcer.beforeHook(caveat.terms, hex"", mode, executionCallData_, keccak256(""), address(0), address(0)); } // should FAIL to INVOKE method BEFORE timestamp RANGE @@ -137,10 +138,10 @@ contract TimestampEnforcerTest is CaveatEnforcerBaseTest { uint128 timestampAfterThreshold_ = uint128(block.timestamp + 1 hours); uint128 timestampBeforeThreshold_ = uint128(block.timestamp + 2 hours); - bytes memory inputTerms_ = abi.encodePacked(timestampAfterThreshold_, timestampBeforeThreshold_); + Caveat memory caveat = Caveats.createTimestampCaveat(address(timestampEnforcer), timestampAfterThreshold_, timestampBeforeThreshold_); vm.prank(address(delegationManager)); vm.expectRevert("TimestampEnforcer:early-delegation"); - timestampEnforcer.beforeHook(inputTerms_, hex"", mode, executionCallData_, keccak256(""), address(0), address(0)); + timestampEnforcer.beforeHook(caveat.terms, hex"", mode, executionCallData_, keccak256(""), address(0), address(0)); } // should FAIL to INVOKE method AFTER timestamp RANGE @@ -156,10 +157,10 @@ contract TimestampEnforcerTest is CaveatEnforcerBaseTest { uint128 timestampAfterThreshold_ = uint128(block.timestamp + 1 hours); uint128 timestampBeforeThreshold_ = uint128(block.timestamp + 2 hours); skip(3 hours); // Increase time 3 hours - bytes memory inputTerms_ = abi.encodePacked(timestampAfterThreshold_, timestampBeforeThreshold_); + Caveat memory caveat = Caveats.createTimestampCaveat(address(timestampEnforcer), timestampAfterThreshold_, timestampBeforeThreshold_); vm.prank(address(delegationManager)); vm.expectRevert("TimestampEnforcer:expired-delegation"); - timestampEnforcer.beforeHook(inputTerms_, hex"", mode, executionCallData_, keccak256(""), address(0), address(0)); + timestampEnforcer.beforeHook(caveat.terms, hex"", mode, executionCallData_, keccak256(""), address(0), address(0)); } // should FAIL to INVOKE with invalid input terms @@ -188,10 +189,10 @@ contract TimestampEnforcerTest is CaveatEnforcerBaseTest { }); skip(10); // Increase time 10 seconds // Not using before threshold (timestampAfterThreshold_ = 1, timestampBeforeThreshold_ = 100) - bytes memory inputTerms_ = abi.encodePacked(uint128(1), uint128(100)); + Caveat memory caveat = Caveats.createTimestampCaveat(address(timestampEnforcer), 1, 100); Caveat[] memory caveats_ = new Caveat[](1); - caveats_[0] = Caveat({ args: hex"", enforcer: address(timestampEnforcer), terms: inputTerms_ }); + caveats_[0] = caveat; Delegation memory delegation = Delegation({ delegate: address(users.bob.deleGator), delegator: address(users.alice.deleGator), diff --git a/test/enforcers/ValueLteEnforcer.t.sol b/test/enforcers/ValueLteEnforcer.t.sol index 737b61bb..380111bf 100644 --- a/test/enforcers/ValueLteEnforcer.t.sol +++ b/test/enforcers/ValueLteEnforcer.t.sol @@ -7,10 +7,11 @@ import { ModeLib } from "@erc7579/lib/ModeLib.sol"; import { ExecutionLib } from "@erc7579/lib/ExecutionLib.sol"; import "../../src/utils/Types.sol"; -import { Execution, ModeCode } from "../../src/utils/Types.sol"; +import { Execution, ModeCode, Caveat } from "../../src/utils/Types.sol"; import { CaveatEnforcerBaseTest } from "./CaveatEnforcerBaseTest.t.sol"; import { ValueLteEnforcer } from "../../src/enforcers/ValueLteEnforcer.sol"; import { ICaveatEnforcer } from "../../src/interfaces/ICaveatEnforcer.sol"; +import { Caveats } from "../../src/libraries/Caveats.sol"; contract ValueLteEnforcerTest is CaveatEnforcerBaseTest { using ModeLib for ModeCode; @@ -36,29 +37,29 @@ contract ValueLteEnforcerTest is CaveatEnforcerBaseTest { // Validates the terms get decoded correctly function test_allow_decodeTerms() public { - bytes memory terms_; + Caveat memory caveat; uint256 amount_; // 0 - terms_ = abi.encodePacked(uint256(0)); - amount_ = enforcer.getTermsInfo(terms_); + caveat = Caveats.createValueLteCaveat(address(enforcer), 0); + amount_ = enforcer.getTermsInfo(caveat.terms); assertEq(amount_, 0); // 1 ether - terms_ = abi.encodePacked(uint256(1 ether)); - amount_ = enforcer.getTermsInfo(terms_); + caveat = Caveats.createValueLteCaveat(address(enforcer), 1 ether); + amount_ = enforcer.getTermsInfo(caveat.terms); assertEq(amount_, uint256(1 ether)); // Max - terms_ = abi.encodePacked(type(uint256).max); - amount_ = enforcer.getTermsInfo(terms_); + caveat = Caveats.createValueLteCaveat(address(enforcer), type(uint256).max); + amount_ = enforcer.getTermsInfo(caveat.terms); assertEq(amount_, type(uint256).max); } // Validates that valid values don't revert function test_allow_valueLte() public view { // Equal - bytes memory terms_ = abi.encode(uint256(1 ether)); + Caveat memory caveat = Caveats.createValueLteCaveat(address(enforcer), 1 ether); Execution memory execution_ = Execution({ target: address(users.alice.deleGator), value: 1 ether, @@ -67,7 +68,7 @@ contract ValueLteEnforcerTest is CaveatEnforcerBaseTest { bytes memory executionCallData_ = ExecutionLib.encodeSingle(execution_.target, execution_.value, execution_.callData); // Should not revert - enforcer.beforeHook(terms_, "", mode, executionCallData_, bytes32(0), address(0), address(0)); + enforcer.beforeHook(caveat.terms, "", mode, executionCallData_, bytes32(0), address(0), address(0)); // Less than execution_ = Execution({ @@ -78,7 +79,7 @@ contract ValueLteEnforcerTest is CaveatEnforcerBaseTest { executionCallData_ = ExecutionLib.encodeSingle(execution_.target, execution_.value, execution_.callData); // Should not revert - enforcer.beforeHook(terms_, "", mode, executionCallData_, bytes32(0), address(0), address(0)); + enforcer.beforeHook(caveat.terms, "", mode, executionCallData_, bytes32(0), address(0), address(0)); } //////////////////////// Errors //////////////////////// @@ -86,7 +87,7 @@ contract ValueLteEnforcerTest is CaveatEnforcerBaseTest { // Validates that invalid values revert function test_notAllow_valueGt() public { // Gt - bytes memory terms_ = abi.encodePacked(uint256(1 ether)); + Caveat memory caveat = Caveats.createValueLteCaveat(address(enforcer), 1 ether); Execution memory execution_ = Execution({ target: address(users.alice.deleGator), value: 2 ether, @@ -96,7 +97,7 @@ contract ValueLteEnforcerTest is CaveatEnforcerBaseTest { // Should not revert vm.expectRevert(bytes("ValueLteEnforcer:value-too-high")); - enforcer.beforeHook(terms_, "", mode, executionCallData_, bytes32(0), address(0), address(0)); + enforcer.beforeHook(caveat.terms, "", mode, executionCallData_, bytes32(0), address(0), address(0)); } // Validates the terms are well formed