From 7a2fd206981ded63da7f80d4237178dc0f106cad Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 20 Sep 2024 11:39:03 -0700 Subject: [PATCH 1/4] Add cursor rules file This is my current best draft of a preprompt to help development of caveat enforcers. It will need to be kept up to date when we change interfaces. I believe I have it up to date with main now. --- .cursorrules | 329 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 329 insertions(+) create mode 100644 .cursorrules diff --git a/.cursorrules b/.cursorrules new file mode 100644 index 0000000..2b9daf1 --- /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. From 47a51b4a66e84e4ff22dd88734d4bc3a19220178 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 25 Sep 2024 09:41:23 -0700 Subject: [PATCH 2/4] Add Caveats helper library --- src/libraries/Caveats.sol | 289 ++++++++++++++++++ test/enforcers/AllowedCalldataEnforcer.t.sol | 11 +- test/enforcers/AllowedMethodsEnforcer.t.sol | 16 +- test/enforcers/AllowedTargetsEnforcer.t.sol | 15 +- .../enforcers/ArgsEqualityCheckEnforcer.t.sol | 18 +- test/enforcers/BlockNumberEnforcer.t.sol | 33 +- test/enforcers/CaveatEnforcerBaseTest.t.sol | 1 + test/enforcers/DeployedEnforcer.t.sol | 14 +- test/enforcers/ERC20BalanceGteEnforcer.t.sol | 43 ++- .../ERC20TransferAmountEnforcer.t.sol | 15 +- test/enforcers/IdEnforcer.t.sol | 5 +- test/enforcers/LimitedCallsEnforcer.t.sol | 24 +- test/enforcers/NativeAllowanceEnforcer.t.sol | 3 +- test/enforcers/NativeBalanceGteEnforcer.t.sol | 39 +-- .../NativeTokenPaymentEnforcer.t.sol | 65 ++-- test/enforcers/NonceEnforcer.t.sol | 30 +- test/enforcers/RedeemerEnforcer.t.sol | 35 ++- test/enforcers/TimestampEnforcer.t.sol | 33 +- test/enforcers/ValueLteEnforcer.t.sol | 27 +- 19 files changed, 505 insertions(+), 211 deletions(-) create mode 100644 src/libraries/Caveats.sol diff --git a/src/libraries/Caveats.sol b/src/libraries/Caveats.sol new file mode 100644 index 0000000..ac43fcc --- /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 b845486..6ca7620 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 5e179cb..68ded84 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 585993b..094c24c 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 5588682..b2f7673 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 c5eb7bc..936e1cb 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 8e28a94..d44501d 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 870b179..9083ade 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 48362e8..827107c 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 dc4ca88..1a5dd80 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/IdEnforcer.t.sol b/test/enforcers/IdEnforcer.t.sol index 06c19b4..2dd8ea9 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 c07edd5..b63bd24 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 8cc5e47..6329706 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 afaaf3a..0f85305 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 755bbea..4e1208a 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 3252eb1..ec28693 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 6ac8030..26a56aa 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 1accd25..bfd321f 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 737b61b..380111b 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 From d34f0dbfbb5a0af820dc56a399b071bcaee28f2b Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 30 Sep 2024 11:25:28 -0700 Subject: [PATCH 3/4] first pass at swap enforcer, getting stack too deep error --- src/enforcers/SwapOfferEnforcer.sol | 196 +++++++++++++++++ src/libraries/Caveats.sol | 19 ++ test/enforcers/SwapOfferEnforcer.t.sol | 294 +++++++++++++++++++++++++ 3 files changed, 509 insertions(+) create mode 100644 src/enforcers/SwapOfferEnforcer.sol create mode 100644 test/enforcers/SwapOfferEnforcer.t.sol diff --git a/src/enforcers/SwapOfferEnforcer.sol b/src/enforcers/SwapOfferEnforcer.sol new file mode 100644 index 0000000..c70dae4 --- /dev/null +++ b/src/enforcers/SwapOfferEnforcer.sol @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: MIT AND Apache-2.0 +pragma solidity 0.8.23; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { ExecutionLib } from "@erc7579/lib/ExecutionLib.sol"; +import { ModeLib } from "@erc7579/lib/ModeLib.sol"; + +import { CaveatEnforcer } from "./CaveatEnforcer.sol"; +import { ModeCode, Delegation } from "../utils/Types.sol"; +import { IDelegationManager } from "../interfaces/IDelegationManager.sol"; + +/** + * @title SwapOfferEnforcer + * @dev This contract enforces a swap offer, allowing partial transfers if the order is not filled in a single transaction. + * @dev This caveat enforcer only works when the execution is in single mode. + * @dev The redeemer must include an allowance delegation when executing the swap to ensure payment. + */ +contract SwapOfferEnforcer is CaveatEnforcer { + using ExecutionLib for bytes; + using ModeLib for ModeCode; + + struct SwapOffer { + address tokenIn; + address tokenOut; + uint256 amountIn; + uint256 amountOut; + uint256 amountInFilled; + uint256 amountOutFilled; + address recipient; + } + + ////////////////////////////// State ////////////////////////////// + + mapping(address delegationManager => mapping(bytes32 delegationHash => SwapOffer)) public swapOffers; + + ////////////////////////////// Events ////////////////////////////// + event SwapOfferUpdated( + address indexed sender, + address indexed redeemer, + bytes32 indexed delegationHash, + uint256 amountInFilled, + uint256 amountOutFilled + ); + + ////////////////////////////// Public Methods ////////////////////////////// + + /** + * @notice Enforces the swap offer before the transaction is performed. + * @param _terms The encoded swap offer terms. + * @param _args The encoded arguments containing the claimed amount and payment delegation. + * @param _mode The mode of the execution. + * @param _executionCallData The transaction the delegate might try to perform. + * @param _delegationHash The hash of the delegation being operated on. + */ + function beforeHook( + bytes calldata _terms, + bytes calldata _args, + ModeCode _mode, + bytes calldata _executionCallData, + bytes32 _delegationHash, + address, + address _redeemer + ) + public + override + onlySingleExecutionMode(_mode) + { + (uint256 claimedAmount, IDelegationManager delegationManager,) = abi.decode(_args, (uint256, IDelegationManager, bytes)); + + (uint256 amountInFilled_, uint256 amountOutFilled_) = _validateAndUpdate(_terms, _executionCallData, _delegationHash, claimedAmount); + + // Store the payment info for the afterHook + SwapOffer storage offer = swapOffers[address(delegationManager)][_delegationHash]; + offer.amountInFilled = amountInFilled_; + offer.amountOutFilled = amountOutFilled_; + + emit SwapOfferUpdated(msg.sender, _redeemer, _delegationHash, amountInFilled_, amountOutFilled_); + } + + /** + * @notice Enforces the conditions that should hold after a transaction is performed. + * @param _terms The encoded swap offer terms. + * @param _args The encoded arguments containing the claimed amount and payment delegation. + * @param _delegationHash The hash of the delegation. + */ + function afterHook( + bytes calldata _terms, + bytes calldata _args, + ModeCode, + bytes calldata, + bytes32 _delegationHash, + address, + address _redeemer + ) + public + override + { + (uint256 claimedAmount, IDelegationManager delegationManager, bytes memory permissionContext) = abi.decode(_args, (uint256, IDelegationManager, bytes)); + + (address tokenIn,,,,address recipient) = getTermsInfo(_terms); + + bytes[] memory permissionContexts = new bytes[](1); + permissionContexts[0] = permissionContext; + + bytes[] memory executionCallDatas = new bytes[](1); + executionCallDatas[0] = ExecutionLib.encodeSingle(tokenIn, claimedAmount, abi.encodeWithSelector(IERC20.transfer.selector, address(this), claimedAmount)); + + ModeCode[] memory encodedModes = new ModeCode[](1); + encodedModes[0] = ModeLib.encodeSimpleSingle(); + + uint256 balanceBefore = IERC20(tokenIn).balanceOf(address(this)); + + // Attempt to redeem the delegation and make the payment + delegationManager.redeemDelegations(permissionContexts, encodedModes, executionCallDatas); + + // Ensure the contract received the payment + uint256 balanceAfter = IERC20(tokenIn).balanceOf(address(this)); + require(balanceAfter >= balanceBefore + claimedAmount, "SwapOfferEnforcer:payment-not-received"); + + // Transfer the received tokens to the recipient + require(IERC20(tokenIn).transfer(recipient, claimedAmount), "SwapOfferEnforcer:transfer-to-recipient-failed"); + } + + /** + * @notice Decodes the terms used in this CaveatEnforcer. + * @param _terms encoded data that is used during the execution hooks. + * @return tokenIn_ The address of the token being sold. + * @return tokenOut_ The address of the token being bought. + * @return amountIn_ The total amount of tokens to be sold. + * @return amountOut_ The total amount of tokens to be bought. + * @return recipient_ The address to receive the input tokens. + */ + function getTermsInfo(bytes calldata _terms) public pure returns (address tokenIn_, address tokenOut_, uint256 amountIn_, uint256 amountOut_, address recipient_) { + require(_terms.length == 148, "SwapOfferEnforcer:invalid-terms-length"); + + tokenIn_ = address(bytes20(_terms[:20])); + tokenOut_ = address(bytes20(_terms[20:40])); + amountIn_ = uint256(bytes32(_terms[40:72])); + amountOut_ = uint256(bytes32(_terms[72:104])); + recipient_ = address(bytes20(_terms[104:124])); + } + + /** + * @notice Validates and updates the swap offer. + * @param _terms The encoded swap offer terms. + * @param _executionCallData The transaction the delegate might try to perform. + * @param _delegationHash The hash of the delegation being operated on. + * @param _claimedAmount The amount claimed to be transferred in. + * @return amountInFilled_ The updated amount of input tokens filled. + * @return amountOutFilled_ The updated amount of output tokens filled. + */ + function _validateAndUpdate( + bytes calldata _terms, + bytes calldata _executionCallData, + bytes32 _delegationHash, + uint256 _claimedAmount + ) + internal + returns (uint256 amountInFilled_, uint256 amountOutFilled_) + { + (address target_,, bytes calldata callData_) = _executionCallData.decodeSingle(); + + require(callData_.length == 68, "SwapOfferEnforcer:invalid-execution-length"); + + (address tokenIn_, address tokenOut_, uint256 amountIn_, uint256 amountOut_, address recipient_) = getTermsInfo(_terms); + + SwapOffer storage offer = swapOffers[msg.sender][_delegationHash]; + if (offer.tokenIn == address(0)) { + // Initialize the offer if it doesn't exist + offer.tokenIn = tokenIn_; + offer.tokenOut = tokenOut_; + offer.amountIn = amountIn_; + offer.amountOut = amountOut_; + offer.recipient = recipient_; + } else { + require(offer.tokenIn == tokenIn_ && offer.tokenOut == tokenOut_ && + offer.amountIn == amountIn_ && offer.amountOut == amountOut_ && + offer.recipient == recipient_, + "SwapOfferEnforcer:terms-mismatch"); + } + + require(target_ == tokenOut_, "SwapOfferEnforcer:invalid-token"); + + bytes4 selector = bytes4(callData_[0:4]); + require(selector == IERC20.transfer.selector || selector == IERC20.transferFrom.selector, "SwapOfferEnforcer:invalid-method"); + + uint256 amount = uint256(bytes32(callData_[36:68])); + + require(offer.amountOutFilled + amount <= offer.amountOut, "SwapOfferEnforcer:exceeds-output-amount"); + + amountInFilled_ = offer.amountInFilled + _claimedAmount; + require(amountInFilled_ <= offer.amountIn, "SwapOfferEnforcer:exceeds-input-amount"); + + amountOutFilled_ = offer.amountOutFilled + amount; + } +} diff --git a/src/libraries/Caveats.sol b/src/libraries/Caveats.sol index ac43fcc..b62d8ce 100644 --- a/src/libraries/Caveats.sol +++ b/src/libraries/Caveats.sol @@ -20,6 +20,8 @@ import { NonceEnforcer } from "../enforcers/NonceEnforcer.sol"; import { RedeemerEnforcer } from "../enforcers/RedeemerEnforcer.sol"; import { TimestampEnforcer } from "../enforcers/TimestampEnforcer.sol"; import { ValueLteEnforcer } from "../enforcers/ValueLteEnforcer.sol"; +import { SwapOfferEnforcer } from "../enforcers/SwapOfferEnforcer.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. @@ -286,4 +288,21 @@ library Caveats { args: "" }); } + + function createSwapOfferCaveat( + address enforcerAddress, + address tokenIn, + address tokenOut, + uint256 amountIn, + uint256 amountOut, + address recipient + ) internal pure returns (Caveat memory) { + bytes memory terms = abi.encodePacked(tokenIn, tokenOut, amountIn, amountOut, recipient); + + return Caveat({ + enforcer: enforcerAddress, + terms: terms, + args: "" + }); + } } diff --git a/test/enforcers/SwapOfferEnforcer.t.sol b/test/enforcers/SwapOfferEnforcer.t.sol new file mode 100644 index 0000000..aa89ba7 --- /dev/null +++ b/test/enforcers/SwapOfferEnforcer.t.sol @@ -0,0 +1,294 @@ +// 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 { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.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 { BasicERC20 } from "../utils/BasicERC20.t.sol"; +import { CaveatEnforcerBaseTest } from "./CaveatEnforcerBaseTest.t.sol"; +import { SwapOfferEnforcer } from "../../src/enforcers/SwapOfferEnforcer.sol"; +import { ERC20TransferAmountEnforcer } from "../../src/enforcers/ERC20TransferAmountEnforcer.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"; +import { ArgsEqualityCheckEnforcer } from "../../src/enforcers/ArgsEqualityCheckEnforcer.sol"; + +contract SwapOfferEnforcerTest is CaveatEnforcerBaseTest { + using ModeLib for ModeCode; + + SwapOfferEnforcer public swapOfferEnforcer; + ERC20TransferAmountEnforcer public erc20TransferAmountEnforcer; + ArgsEqualityCheckEnforcer public argsEqualityCheckEnforcer; + BasicERC20 public tokenIn; + BasicERC20 public tokenOut; + ModeCode public mode = ModeLib.encodeSimpleSingle(); + + uint256 constant AMOUNT_IN = 1000 ether; + uint256 constant AMOUNT_OUT = 500 ether; + + function setUp() public override { + super.setUp(); + swapOfferEnforcer = new SwapOfferEnforcer(); + erc20TransferAmountEnforcer = new ERC20TransferAmountEnforcer(); + argsEqualityCheckEnforcer = new ArgsEqualityCheckEnforcer(); + tokenIn = new BasicERC20(address(users.alice.deleGator), "Token In", "TIN", AMOUNT_IN); + tokenOut = new BasicERC20(address(users.bob.deleGator), "Token Out", "TOUT", AMOUNT_OUT); + + vm.label(address(swapOfferEnforcer), "Swap Offer Enforcer"); + vm.label(address(erc20TransferAmountEnforcer), "ERC20 Transfer Amount Enforcer"); + vm.label(address(argsEqualityCheckEnforcer), "Args Equality Check Enforcer"); + vm.label(address(tokenIn), "Token In"); + vm.label(address(tokenOut), "Token Out"); + } + + function test_swapOfferEnforcer() public { + uint256 initialAliceBalanceIn = tokenIn.balanceOf(address(users.alice.deleGator)); + uint256 initialBobBalanceOut = tokenOut.balanceOf(address(users.bob.deleGator)); + + // Create swap offer caveat + Caveat memory swapOfferCaveat = Caveats.createSwapOfferCaveat( + address(swapOfferEnforcer), + address(tokenIn), + address(tokenOut), + AMOUNT_IN, + AMOUNT_OUT, + address(users.alice.deleGator) + ); + + // Create ERC20 transfer amount caveat for payment + Caveat memory erc20TransferCaveat = Caveats.createERC20TransferAmountCaveat( + address(erc20TransferAmountEnforcer), + address(tokenIn), + AMOUNT_IN + ); + + Caveat[] memory caveats = new Caveat[](2); + caveats[0] = swapOfferCaveat; + caveats[1] = erc20TransferCaveat; + + 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); + + // Create the execution for token transfer + Execution memory execution = Execution({ + target: address(tokenOut), + value: 0, + callData: abi.encodeWithSelector(IERC20.transfer.selector, address(users.alice.deleGator), AMOUNT_OUT) + }); + + // Create the allowance delegation for payment + Caveat[] memory allowanceCaveats = new Caveat[](1); + allowanceCaveats[0] = Caveat({ + enforcer: address(argsEqualityCheckEnforcer), + terms: hex"", + args: abi.encodePacked(keccak256(abi.encode(delegation)), address(users.bob.deleGator)) + }); + + Delegation memory allowanceDelegation = Delegation({ + delegate: address(delegationManager), + delegator: address(users.bob.deleGator), + authority: ROOT_AUTHORITY, + caveats: allowanceCaveats, + salt: 0, + signature: hex"" + }); + + allowanceDelegation = signDelegation(users.bob, allowanceDelegation); + + // Prepare the arguments for the swap + bytes memory args = abi.encode(AMOUNT_IN, delegationManager, abi.encode(new Delegation[](1))); + + // Execute Bob's UserOp + Delegation[] memory delegations = new Delegation[](1); + delegations[0] = delegation; + + vm.prank(address(users.bob.deleGator)); + invokeDelegation_UserOp(users.bob, delegations, execution, args); + + // Check balances after swap + uint256 finalAliceBalanceIn = tokenIn.balanceOf(address(users.alice.deleGator)); + uint256 finalBobBalanceOut = tokenOut.balanceOf(address(users.bob.deleGator)); + + assertEq(finalAliceBalanceIn, initialAliceBalanceIn + AMOUNT_IN, "Alice should receive the correct amount of tokenIn"); + assertEq(finalBobBalanceOut, initialBobBalanceOut - AMOUNT_OUT, "Bob should send the correct amount of tokenOut"); + } + + function test_swapOfferEnforcer_partialFill() public { + uint256 partialAmountIn = AMOUNT_IN / 2; + uint256 partialAmountOut = AMOUNT_OUT / 2; + + // Create swap offer caveat + Caveat memory swapOfferCaveat = Caveats.createSwapOfferCaveat( + address(swapOfferEnforcer), + address(tokenIn), + address(tokenOut), + AMOUNT_IN, + AMOUNT_OUT, + address(users.alice.deleGator) + ); + + // Create ERC20 transfer amount caveat for payment + Caveat memory erc20TransferCaveat = Caveats.createERC20TransferAmountCaveat( + address(erc20TransferAmountEnforcer), + address(tokenIn), + AMOUNT_IN + ); + + Caveat[] memory caveats = new Caveat[](2); + caveats[0] = swapOfferCaveat; + caveats[1] = erc20TransferCaveat; + + 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); + + // Create the execution for token transfer (partial amount) + Execution memory execution = Execution({ + target: address(tokenOut), + value: 0, + callData: abi.encodeWithSelector(IERC20.transfer.selector, address(users.alice.deleGator), partialAmountOut) + }); + + // Create the allowance delegation for payment + Caveat[] memory allowanceCaveats = new Caveat[](1); + allowanceCaveats[0] = Caveat({ + enforcer: address(argsEqualityCheckEnforcer), + terms: hex"", + args: abi.encodePacked(keccak256(abi.encode(delegation)), address(users.bob.deleGator)) + }); + + Delegation memory allowanceDelegation = Delegation({ + delegate: address(delegationManager), + delegator: address(users.bob.deleGator), + authority: ROOT_AUTHORITY, + caveats: allowanceCaveats, + salt: 0, + signature: hex"" + }); + + allowanceDelegation = signDelegation(users.bob, allowanceDelegation); + + // Prepare the arguments for the swap (partial amount) + bytes memory args = abi.encode(partialAmountIn, delegationManager, abi.encode(new Delegation[](1))); + + // Execute Bob's UserOp + Delegation[] memory delegations = new Delegation[](1); + delegations[0] = delegation; + + vm.prank(address(users.bob.deleGator)); + invokeDelegation_UserOp(users.bob, delegations, execution, args); + + // Check balances after partial swap + uint256 aliceBalanceIn = tokenIn.balanceOf(address(users.alice.deleGator)); + uint256 bobBalanceOut = tokenOut.balanceOf(address(users.bob.deleGator)); + + assertEq(aliceBalanceIn, AMOUNT_IN + partialAmountIn, "Alice should receive the correct partial amount of tokenIn"); + assertEq(bobBalanceOut, AMOUNT_OUT - partialAmountOut, "Bob should send the correct partial amount of tokenOut"); + + // Execute the remaining part of the swap + vm.prank(address(users.bob.deleGator)); + invokeDelegation_UserOp(users.bob, delegations, execution, args); + + // Check final balances + aliceBalanceIn = tokenIn.balanceOf(address(users.alice.deleGator)); + bobBalanceOut = tokenOut.balanceOf(address(users.bob.deleGator)); + + assertEq(aliceBalanceIn, AMOUNT_IN * 2, "Alice should receive the full amount of tokenIn"); + assertEq(bobBalanceOut, 0, "Bob should send the full amount of tokenOut"); + } + + function test_swapOfferEnforcer_invalidAmount() public { + // Create swap offer caveat + Caveat memory swapOfferCaveat = Caveats.createSwapOfferCaveat( + address(swapOfferEnforcer), + address(tokenIn), + address(tokenOut), + AMOUNT_IN, + AMOUNT_OUT, + address(users.alice.deleGator) + ); + + // Create ERC20 transfer amount caveat for payment + Caveat memory erc20TransferCaveat = Caveats.createERC20TransferAmountCaveat( + address(erc20TransferAmountEnforcer), + address(tokenIn), + AMOUNT_IN + ); + + Caveat[] memory caveats = new Caveat[](2); + caveats[0] = swapOfferCaveat; + caveats[1] = erc20TransferCaveat; + + 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); + + // Create the execution for token transfer with invalid amount + Execution memory execution = Execution({ + target: address(tokenOut), + value: 0, + callData: abi.encodeWithSelector(IERC20.transfer.selector, address(users.alice.deleGator), AMOUNT_OUT + 1 ether) + }); + + // Create the allowance delegation for payment + Caveat[] memory allowanceCaveats = new Caveat[](1); + allowanceCaveats[0] = Caveat({ + enforcer: address(argsEqualityCheckEnforcer), + terms: hex"", + args: abi.encodePacked(keccak256(abi.encode(delegation)), address(users.bob.deleGator)) + }); + + Delegation memory allowanceDelegation = Delegation({ + delegate: address(delegationManager), + delegator: address(users.bob.deleGator), + authority: ROOT_AUTHORITY, + caveats: allowanceCaveats, + salt: 0, + signature: hex"" + }); + + allowanceDelegation = signDelegation(users.bob, allowanceDelegation); + + // Prepare the arguments for the swap + bytes memory args = abi.encode(AMOUNT_IN, delegationManager, abi.encode(new Delegation[](1))); + + // Execute Bob's UserOp + Delegation[] memory delegations = new Delegation[](1); + delegations[0] = delegation; + + vm.prank(address(users.bob.deleGator)); + vm.expectRevert("SwapOfferEnforcer:exceeds-output-amount"); + invokeDelegation_UserOp(users.bob, delegations, execution, args); + } + + function _getEnforcer() internal view override returns (ICaveatEnforcer) { + return ICaveatEnforcer(address(swapOfferEnforcer)); + } +} From dd94310eb6158004ae9dcf07806a93b37dea311b Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 2 Oct 2024 13:14:48 -0400 Subject: [PATCH 4/4] Tests passing --- src/enforcers/SwapOfferEnforcer.sol | 84 +++--- src/libraries/Caveats.sol | 9 +- test/enforcers/SwapOfferEnforcer.t.sol | 377 +++++++++++-------------- 3 files changed, 220 insertions(+), 250 deletions(-) diff --git a/src/enforcers/SwapOfferEnforcer.sol b/src/enforcers/SwapOfferEnforcer.sol index c70dae4..8ef00f0 100644 --- a/src/enforcers/SwapOfferEnforcer.sol +++ b/src/enforcers/SwapOfferEnforcer.sol @@ -29,6 +29,20 @@ contract SwapOfferEnforcer is CaveatEnforcer { address recipient; } + struct SwapOfferTerms { + address tokenIn; + address tokenOut; + uint256 amountIn; + uint256 amountOut; + address recipient; + } + + struct SwapOfferArgs { + uint256 claimedAmount; + IDelegationManager delegationManager; + bytes permissionContext; + } + ////////////////////////////// State ////////////////////////////// mapping(address delegationManager => mapping(bytes32 delegationHash => SwapOffer)) public swapOffers; @@ -65,18 +79,17 @@ contract SwapOfferEnforcer is CaveatEnforcer { override onlySingleExecutionMode(_mode) { - (uint256 claimedAmount, IDelegationManager delegationManager,) = abi.decode(_args, (uint256, IDelegationManager, bytes)); + SwapOfferArgs memory args = abi.decode(_args, (SwapOfferArgs)); - (uint256 amountInFilled_, uint256 amountOutFilled_) = _validateAndUpdate(_terms, _executionCallData, _delegationHash, claimedAmount); + (uint256 amountInFilled_, uint256 amountOutFilled_) = _validateAndUpdate(_terms, _executionCallData, _delegationHash, args.claimedAmount); // Store the payment info for the afterHook - SwapOffer storage offer = swapOffers[address(delegationManager)][_delegationHash]; + SwapOffer storage offer = swapOffers[address(args.delegationManager)][_delegationHash]; offer.amountInFilled = amountInFilled_; offer.amountOutFilled = amountOutFilled_; emit SwapOfferUpdated(msg.sender, _redeemer, _delegationHash, amountInFilled_, amountOutFilled_); } - /** * @notice Enforces the conditions that should hold after a transaction is performed. * @param _terms The encoded swap offer terms. @@ -95,30 +108,33 @@ contract SwapOfferEnforcer is CaveatEnforcer { public override { - (uint256 claimedAmount, IDelegationManager delegationManager, bytes memory permissionContext) = abi.decode(_args, (uint256, IDelegationManager, bytes)); - - (address tokenIn,,,,address recipient) = getTermsInfo(_terms); - + SwapOfferArgs memory args = abi.decode(_args, (SwapOfferArgs)); + SwapOfferTerms memory terms = abi.decode(_terms, (SwapOfferTerms)); + address tokenIn = terms.tokenIn; + address recipient = terms.recipient; bytes[] memory permissionContexts = new bytes[](1); - permissionContexts[0] = permissionContext; + permissionContexts[0] = args.permissionContext; + + uint256 balanceBefore = IERC20(tokenIn).balanceOf(recipient); + + SwapOffer storage offer = swapOffers[address(args.delegationManager)][_delegationHash]; + uint256 amountToTransfer = offer.amountInFilled; bytes[] memory executionCallDatas = new bytes[](1); - executionCallDatas[0] = ExecutionLib.encodeSingle(tokenIn, claimedAmount, abi.encodeWithSelector(IERC20.transfer.selector, address(this), claimedAmount)); + executionCallDatas[0] = ExecutionLib.encodeSingle(tokenIn, 0, abi.encodeWithSelector(IERC20.transfer.selector, recipient, amountToTransfer)); ModeCode[] memory encodedModes = new ModeCode[](1); encodedModes[0] = ModeLib.encodeSimpleSingle(); - uint256 balanceBefore = IERC20(tokenIn).balanceOf(address(this)); - - // Attempt to redeem the delegation and make the payment - delegationManager.redeemDelegations(permissionContexts, encodedModes, executionCallDatas); + // Attempt to redeem the delegation and make the payment directly to the recipient + args.delegationManager.redeemDelegations(permissionContexts, encodedModes, executionCallDatas); - // Ensure the contract received the payment - uint256 balanceAfter = IERC20(tokenIn).balanceOf(address(this)); - require(balanceAfter >= balanceBefore + claimedAmount, "SwapOfferEnforcer:payment-not-received"); + // Ensure the recipient received the payment + uint256 balanceAfter = IERC20(tokenIn).balanceOf(recipient); + require(balanceAfter >= balanceBefore + amountToTransfer, "SwapOfferEnforcer:payment-not-received"); - // Transfer the received tokens to the recipient - require(IERC20(tokenIn).transfer(recipient, claimedAmount), "SwapOfferEnforcer:transfer-to-recipient-failed"); + // Reset the swap offer + delete swapOffers[address(args.delegationManager)][_delegationHash]; } /** @@ -131,13 +147,8 @@ contract SwapOfferEnforcer is CaveatEnforcer { * @return recipient_ The address to receive the input tokens. */ function getTermsInfo(bytes calldata _terms) public pure returns (address tokenIn_, address tokenOut_, uint256 amountIn_, uint256 amountOut_, address recipient_) { - require(_terms.length == 148, "SwapOfferEnforcer:invalid-terms-length"); - - tokenIn_ = address(bytes20(_terms[:20])); - tokenOut_ = address(bytes20(_terms[20:40])); - amountIn_ = uint256(bytes32(_terms[40:72])); - amountOut_ = uint256(bytes32(_terms[72:104])); - recipient_ = address(bytes20(_terms[104:124])); + SwapOfferTerms memory terms = abi.decode(_terms, (SwapOfferTerms)); + return (terms.tokenIn, terms.tokenOut, terms.amountIn, terms.amountOut, terms.recipient); } /** @@ -162,24 +173,23 @@ contract SwapOfferEnforcer is CaveatEnforcer { require(callData_.length == 68, "SwapOfferEnforcer:invalid-execution-length"); - (address tokenIn_, address tokenOut_, uint256 amountIn_, uint256 amountOut_, address recipient_) = getTermsInfo(_terms); - + SwapOfferTerms memory terms = abi.decode(_terms, (SwapOfferTerms)); SwapOffer storage offer = swapOffers[msg.sender][_delegationHash]; if (offer.tokenIn == address(0)) { // Initialize the offer if it doesn't exist - offer.tokenIn = tokenIn_; - offer.tokenOut = tokenOut_; - offer.amountIn = amountIn_; - offer.amountOut = amountOut_; - offer.recipient = recipient_; + offer.tokenIn = terms.tokenIn; + offer.tokenOut = terms.tokenOut; + offer.amountIn = terms.amountIn; + offer.amountOut = terms.amountOut; + offer.recipient = terms.recipient; } else { - require(offer.tokenIn == tokenIn_ && offer.tokenOut == tokenOut_ && - offer.amountIn == amountIn_ && offer.amountOut == amountOut_ && - offer.recipient == recipient_, + require(offer.tokenIn == terms.tokenIn && offer.tokenOut == terms.tokenOut && + offer.amountIn == terms.amountIn && offer.amountOut == terms.amountOut && + offer.recipient == terms.recipient, "SwapOfferEnforcer:terms-mismatch"); } - require(target_ == tokenOut_, "SwapOfferEnforcer:invalid-token"); + require(target_ == terms.tokenOut, "SwapOfferEnforcer:invalid-token"); bytes4 selector = bytes4(callData_[0:4]); require(selector == IERC20.transfer.selector || selector == IERC20.transferFrom.selector, "SwapOfferEnforcer:invalid-method"); diff --git a/src/libraries/Caveats.sol b/src/libraries/Caveats.sol index b62d8ce..1209fa1 100644 --- a/src/libraries/Caveats.sol +++ b/src/libraries/Caveats.sol @@ -288,16 +288,11 @@ library Caveats { args: "" }); } - function createSwapOfferCaveat( address enforcerAddress, - address tokenIn, - address tokenOut, - uint256 amountIn, - uint256 amountOut, - address recipient + SwapOfferEnforcer.SwapOfferTerms memory swapOfferTerms ) internal pure returns (Caveat memory) { - bytes memory terms = abi.encodePacked(tokenIn, tokenOut, amountIn, amountOut, recipient); + bytes memory terms = abi.encode(swapOfferTerms); return Caveat({ enforcer: enforcerAddress, diff --git a/test/enforcers/SwapOfferEnforcer.t.sol b/test/enforcers/SwapOfferEnforcer.t.sol index aa89ba7..128a78b 100644 --- a/test/enforcers/SwapOfferEnforcer.t.sol +++ b/test/enforcers/SwapOfferEnforcer.t.sol @@ -1,294 +1,259 @@ // 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 { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { Test } from "forge-std/Test.sol"; import { ModeLib } from "@erc7579/lib/ModeLib.sol"; import { ExecutionLib } from "@erc7579/lib/ExecutionLib.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { Execution, Caveat, Delegation, ModeCode } from "../../src/utils/Types.sol"; -import { BasicERC20 } from "../utils/BasicERC20.t.sol"; import { CaveatEnforcerBaseTest } from "./CaveatEnforcerBaseTest.t.sol"; import { SwapOfferEnforcer } from "../../src/enforcers/SwapOfferEnforcer.sol"; -import { ERC20TransferAmountEnforcer } from "../../src/enforcers/ERC20TransferAmountEnforcer.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"; -import { ArgsEqualityCheckEnforcer } from "../../src/enforcers/ArgsEqualityCheckEnforcer.sol"; +import { BasicERC20 } from "../utils/BasicERC20.t.sol"; +import { ERC20TransferAmountEnforcer } from "../../src/enforcers/ERC20TransferAmountEnforcer.sol"; contract SwapOfferEnforcerTest is CaveatEnforcerBaseTest { using ModeLib for ModeCode; SwapOfferEnforcer public swapOfferEnforcer; ERC20TransferAmountEnforcer public erc20TransferAmountEnforcer; - ArgsEqualityCheckEnforcer public argsEqualityCheckEnforcer; - BasicERC20 public tokenIn; - BasicERC20 public tokenOut; - ModeCode public mode = ModeLib.encodeSimpleSingle(); - - uint256 constant AMOUNT_IN = 1000 ether; - uint256 constant AMOUNT_OUT = 500 ether; + ModeCode public modeSimpleSingle = ModeLib.encodeSimpleSingle(); + address public constant TOKEN_IN = address(0x1); + address public constant TOKEN_OUT = address(0x2); + uint256 public constant AMOUNT_IN = 100; + uint256 public constant AMOUNT_OUT = 200; + address public constant RECIPIENT = address(0x3); function setUp() public override { super.setUp(); swapOfferEnforcer = new SwapOfferEnforcer(); - erc20TransferAmountEnforcer = new ERC20TransferAmountEnforcer(); - argsEqualityCheckEnforcer = new ArgsEqualityCheckEnforcer(); - tokenIn = new BasicERC20(address(users.alice.deleGator), "Token In", "TIN", AMOUNT_IN); - tokenOut = new BasicERC20(address(users.bob.deleGator), "Token Out", "TOUT", AMOUNT_OUT); - vm.label(address(swapOfferEnforcer), "Swap Offer Enforcer"); + erc20TransferAmountEnforcer = new ERC20TransferAmountEnforcer(); vm.label(address(erc20TransferAmountEnforcer), "ERC20 Transfer Amount Enforcer"); - vm.label(address(argsEqualityCheckEnforcer), "Args Equality Check Enforcer"); - vm.label(address(tokenIn), "Token In"); - vm.label(address(tokenOut), "Token Out"); } - function test_swapOfferEnforcer() public { - uint256 initialAliceBalanceIn = tokenIn.balanceOf(address(users.alice.deleGator)); - uint256 initialBobBalanceOut = tokenOut.balanceOf(address(users.bob.deleGator)); - - // Create swap offer caveat + function test_validSwapOffer() public { Caveat memory swapOfferCaveat = Caveats.createSwapOfferCaveat( address(swapOfferEnforcer), - address(tokenIn), - address(tokenOut), - AMOUNT_IN, - AMOUNT_OUT, - address(users.alice.deleGator) - ); - - // Create ERC20 transfer amount caveat for payment - Caveat memory erc20TransferCaveat = Caveats.createERC20TransferAmountCaveat( - address(erc20TransferAmountEnforcer), - address(tokenIn), - AMOUNT_IN + SwapOfferEnforcer.SwapOfferTerms({ + tokenIn: TOKEN_IN, + tokenOut: TOKEN_OUT, + amountIn: AMOUNT_IN, + amountOut: AMOUNT_OUT, + recipient: RECIPIENT + }) ); - Caveat[] memory caveats = new Caveat[](2); - caveats[0] = swapOfferCaveat; - caveats[1] = erc20TransferCaveat; - Delegation memory delegation = Delegation({ delegate: address(users.bob.deleGator), delegator: address(users.alice.deleGator), authority: ROOT_AUTHORITY, - caveats: caveats, + caveats: new Caveat[](1), salt: 0, signature: hex"" }); - + delegation.caveats[0] = swapOfferCaveat; delegation = signDelegation(users.alice, delegation); - // Create the execution for token transfer - Execution memory execution = Execution({ - target: address(tokenOut), - value: 0, - callData: abi.encodeWithSelector(IERC20.transfer.selector, address(users.alice.deleGator), AMOUNT_OUT) - }); - - // Create the allowance delegation for payment - Caveat[] memory allowanceCaveats = new Caveat[](1); - allowanceCaveats[0] = Caveat({ - enforcer: address(argsEqualityCheckEnforcer), - terms: hex"", - args: abi.encodePacked(keccak256(abi.encode(delegation)), address(users.bob.deleGator)) - }); - - Delegation memory allowanceDelegation = Delegation({ - delegate: address(delegationManager), - delegator: address(users.bob.deleGator), - authority: ROOT_AUTHORITY, - caveats: allowanceCaveats, - salt: 0, - signature: hex"" - }); - - allowanceDelegation = signDelegation(users.bob, allowanceDelegation); - - // Prepare the arguments for the swap - bytes memory args = abi.encode(AMOUNT_IN, delegationManager, abi.encode(new Delegation[](1))); + bytes memory executionCallData = ExecutionLib.encodeSingle( + TOKEN_OUT, + 0, + abi.encodeWithSelector(IERC20.transfer.selector, users.bob.addr, AMOUNT_OUT) + ); - // Execute Bob's UserOp - Delegation[] memory delegations = new Delegation[](1); - delegations[0] = delegation; + vm.prank(address(delegationManager)); + swapOfferEnforcer.beforeHook( + swapOfferCaveat.terms, + abi.encode(SwapOfferEnforcer.SwapOfferArgs({ + claimedAmount: AMOUNT_IN, + delegationManager: IDelegationManager(address(delegationManager)), + permissionContext: abi.encode(delegation) + })), + modeSimpleSingle, + executionCallData, + keccak256(abi.encode(delegation)), + address(0), + address(users.bob.deleGator) + ); + } - vm.prank(address(users.bob.deleGator)); - invokeDelegation_UserOp(users.bob, delegations, execution, args); + function test_invalidToken() public { + Caveat memory swapOfferCaveat = Caveats.createSwapOfferCaveat( + address(swapOfferEnforcer), + SwapOfferEnforcer.SwapOfferTerms({ + tokenIn: TOKEN_IN, + tokenOut: TOKEN_OUT, + amountIn: AMOUNT_IN, + amountOut: AMOUNT_OUT, + recipient: RECIPIENT + }) + ); - // Check balances after swap - uint256 finalAliceBalanceIn = tokenIn.balanceOf(address(users.alice.deleGator)); - uint256 finalBobBalanceOut = tokenOut.balanceOf(address(users.bob.deleGator)); + bytes memory executionCallData = ExecutionLib.encodeSingle( + address(0x4), // Invalid token + 0, + abi.encodeWithSelector(IERC20.transfer.selector, users.bob.addr, AMOUNT_OUT) + ); - assertEq(finalAliceBalanceIn, initialAliceBalanceIn + AMOUNT_IN, "Alice should receive the correct amount of tokenIn"); - assertEq(finalBobBalanceOut, initialBobBalanceOut - AMOUNT_OUT, "Bob should send the correct amount of tokenOut"); + vm.prank(address(delegationManager)); + vm.expectRevert("SwapOfferEnforcer:invalid-token"); + swapOfferEnforcer.beforeHook( + swapOfferCaveat.terms, + abi.encode(SwapOfferEnforcer.SwapOfferArgs({ + claimedAmount: AMOUNT_IN, + delegationManager: IDelegationManager(address(delegationManager)), + permissionContext: abi.encode(Delegation({ + delegate: address(users.bob.deleGator), + delegator: address(users.alice.deleGator), + authority: ROOT_AUTHORITY, + caveats: new Caveat[](1), + salt: 0, + signature: hex"" + })) + })), + modeSimpleSingle, + executionCallData, + keccak256(""), + address(0), + address(users.bob.deleGator) + ); } - function test_swapOfferEnforcer_partialFill() public { - uint256 partialAmountIn = AMOUNT_IN / 2; - uint256 partialAmountOut = AMOUNT_OUT / 2; - - // Create swap offer caveat + function test_invalidMethod() public { Caveat memory swapOfferCaveat = Caveats.createSwapOfferCaveat( address(swapOfferEnforcer), - address(tokenIn), - address(tokenOut), - AMOUNT_IN, - AMOUNT_OUT, - address(users.alice.deleGator) + SwapOfferEnforcer.SwapOfferTerms({ + tokenIn: TOKEN_IN, + tokenOut: TOKEN_OUT, + amountIn: AMOUNT_IN, + amountOut: AMOUNT_OUT, + recipient: RECIPIENT + }) ); - // Create ERC20 transfer amount caveat for payment - Caveat memory erc20TransferCaveat = Caveats.createERC20TransferAmountCaveat( - address(erc20TransferAmountEnforcer), - address(tokenIn), - AMOUNT_IN + bytes memory executionCallData = ExecutionLib.encodeSingle( + TOKEN_OUT, + 0, + abi.encodeWithSelector(IERC20.approve.selector, users.bob.addr, AMOUNT_OUT) // Invalid method + ); + + vm.prank(address(delegationManager)); + vm.expectRevert("SwapOfferEnforcer:invalid-method"); + swapOfferEnforcer.beforeHook( + swapOfferCaveat.terms, + abi.encode(SwapOfferEnforcer.SwapOfferArgs({ + claimedAmount: AMOUNT_IN, + delegationManager: IDelegationManager(address(delegationManager)), + permissionContext: abi.encode(Delegation({ + delegate: address(users.bob.deleGator), + delegator: address(users.alice.deleGator), + authority: ROOT_AUTHORITY, + caveats: new Caveat[](1), + salt: 0, + signature: hex"" + })) + })), + modeSimpleSingle, + executionCallData, + keccak256(""), + address(0), + address(users.bob.deleGator) ); + } - Caveat[] memory caveats = new Caveat[](2); - caveats[0] = swapOfferCaveat; - caveats[1] = erc20TransferCaveat; + function test_integrationSwapOfferFulfillment() public { + // Deploy two ERC20 tokens for the swap + BasicERC20 tokenIn = new BasicERC20(address(this), "Token In", "TIN", 1000 ether); + BasicERC20 tokenOut = new BasicERC20(address(this), "Token Out", "TOUT", 1000 ether); + + // Transfer tokens to Alice and Bob + tokenOut.transfer(address(users.alice.deleGator), 100 ether); + tokenIn.transfer(address(users.bob.deleGator), 10 ether); // Bob needs tokenIn, not tokenOut + + // Create the caveat for the swap offer + Caveat memory swapOfferCaveat = Caveat({ + enforcer: address(swapOfferEnforcer), + terms: abi.encode(SwapOfferEnforcer.SwapOfferTerms({ + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), + amountIn: 10 ether, + amountOut: 5 ether, + recipient: address(users.alice.deleGator) + })), + args: "" + }); + // Create and sign the delegation from Alice to Bob Delegation memory delegation = Delegation({ delegate: address(users.bob.deleGator), delegator: address(users.alice.deleGator), authority: ROOT_AUTHORITY, - caveats: caveats, + caveats: new Caveat[](1), salt: 0, signature: hex"" }); - + delegation.caveats[0] = swapOfferCaveat; delegation = signDelegation(users.alice, delegation); - // Create the execution for token transfer (partial amount) - Execution memory execution = Execution({ - target: address(tokenOut), - value: 0, - callData: abi.encodeWithSelector(IERC20.transfer.selector, address(users.alice.deleGator), partialAmountOut) - }); - - // Create the allowance delegation for payment - Caveat[] memory allowanceCaveats = new Caveat[](1); - allowanceCaveats[0] = Caveat({ - enforcer: address(argsEqualityCheckEnforcer), - terms: hex"", - args: abi.encodePacked(keccak256(abi.encode(delegation)), address(users.bob.deleGator)) - }); - - Delegation memory allowanceDelegation = Delegation({ - delegate: address(delegationManager), - delegator: address(users.bob.deleGator), - authority: ROOT_AUTHORITY, - caveats: allowanceCaveats, - salt: 0, - signature: hex"" - }); - - allowanceDelegation = signDelegation(users.bob, allowanceDelegation); - - // Prepare the arguments for the swap (partial amount) - bytes memory args = abi.encode(partialAmountIn, delegationManager, abi.encode(new Delegation[](1))); - - // Execute Bob's UserOp - Delegation[] memory delegations = new Delegation[](1); - delegations[0] = delegation; - - vm.prank(address(users.bob.deleGator)); - invokeDelegation_UserOp(users.bob, delegations, execution, args); - - // Check balances after partial swap - uint256 aliceBalanceIn = tokenIn.balanceOf(address(users.alice.deleGator)); - uint256 bobBalanceOut = tokenOut.balanceOf(address(users.bob.deleGator)); - - assertEq(aliceBalanceIn, AMOUNT_IN + partialAmountIn, "Alice should receive the correct partial amount of tokenIn"); - assertEq(bobBalanceOut, AMOUNT_OUT - partialAmountOut, "Bob should send the correct partial amount of tokenOut"); - - // Execute the remaining part of the swap - vm.prank(address(users.bob.deleGator)); - invokeDelegation_UserOp(users.bob, delegations, execution, args); - - // Check final balances - aliceBalanceIn = tokenIn.balanceOf(address(users.alice.deleGator)); - bobBalanceOut = tokenOut.balanceOf(address(users.bob.deleGator)); - - assertEq(aliceBalanceIn, AMOUNT_IN * 2, "Alice should receive the full amount of tokenIn"); - assertEq(bobBalanceOut, 0, "Bob should send the full amount of tokenOut"); - } - - function test_swapOfferEnforcer_invalidAmount() public { - // Create swap offer caveat - Caveat memory swapOfferCaveat = Caveats.createSwapOfferCaveat( - address(swapOfferEnforcer), - address(tokenIn), - address(tokenOut), - AMOUNT_IN, - AMOUNT_OUT, - address(users.alice.deleGator) - ); - - // Create ERC20 transfer amount caveat for payment + // Create a delegation from Bob to allow the SwapOfferEnforcer to transfer tokenOut Caveat memory erc20TransferCaveat = Caveats.createERC20TransferAmountCaveat( address(erc20TransferAmountEnforcer), address(tokenIn), - AMOUNT_IN + 10 ether // The amount Bob is willing to transfer ); - Caveat[] memory caveats = new Caveat[](2); - caveats[0] = swapOfferCaveat; - caveats[1] = erc20TransferCaveat; - - Delegation memory delegation = Delegation({ - delegate: address(users.bob.deleGator), - delegator: address(users.alice.deleGator), + Delegation memory bobDelegation = Delegation({ + delegate: address(swapOfferEnforcer), + delegator: address(users.bob.deleGator), authority: ROOT_AUTHORITY, - caveats: caveats, + caveats: new Caveat[](1), salt: 0, signature: hex"" }); + bobDelegation.caveats[0] = erc20TransferCaveat; + bobDelegation = signDelegation(users.bob, bobDelegation); - delegation = signDelegation(users.alice, delegation); - - // Create the execution for token transfer with invalid amount + // Prepare the execution for the swap Execution memory execution = Execution({ target: address(tokenOut), value: 0, - callData: abi.encodeWithSelector(IERC20.transfer.selector, address(users.alice.deleGator), AMOUNT_OUT + 1 ether) + callData: abi.encodeWithSelector(IERC20.transfer.selector, address(users.bob.deleGator), 5 ether) }); - // Create the allowance delegation for payment - Caveat[] memory allowanceCaveats = new Caveat[](1); - allowanceCaveats[0] = Caveat({ - enforcer: address(argsEqualityCheckEnforcer), - terms: hex"", - args: abi.encodePacked(keccak256(abi.encode(delegation)), address(users.bob.deleGator)) - }); - - Delegation memory allowanceDelegation = Delegation({ - delegate: address(delegationManager), - delegator: address(users.bob.deleGator), - authority: ROOT_AUTHORITY, - caveats: allowanceCaveats, - salt: 0, - signature: hex"" + // Prepare the args for the SwapOfferEnforcer + Delegation[] memory bobDelegations = new Delegation[](1); + bobDelegations[0] = bobDelegation; + SwapOfferEnforcer.SwapOfferArgs memory args = SwapOfferEnforcer.SwapOfferArgs({ + claimedAmount: 10 ether, + delegationManager: IDelegationManager(address(delegationManager)), + permissionContext: abi.encode(bobDelegations) }); + delegation.caveats[0].args = abi.encode(args); - allowanceDelegation = signDelegation(users.bob, allowanceDelegation); + // Record initial balances + uint256 aliceTokenInBalanceBefore = tokenIn.balanceOf(address(users.alice.deleGator)); + uint256 aliceTokenOutBalanceBefore = tokenOut.balanceOf(address(users.alice.deleGator)); + uint256 bobTokenInBalanceBefore = tokenIn.balanceOf(address(users.bob.deleGator)); + uint256 bobTokenOutBalanceBefore = tokenOut.balanceOf(address(users.bob.deleGator)); - // Prepare the arguments for the swap - bytes memory args = abi.encode(AMOUNT_IN, delegationManager, abi.encode(new Delegation[](1))); - - // Execute Bob's UserOp + // Execute the swap + vm.prank(address(users.bob.deleGator)); Delegation[] memory delegations = new Delegation[](1); delegations[0] = delegation; + invokeDelegation_UserOp(users.bob, delegations, execution); - vm.prank(address(users.bob.deleGator)); - vm.expectRevert("SwapOfferEnforcer:exceeds-output-amount"); - invokeDelegation_UserOp(users.bob, delegations, execution, args); + // Verify the swap results + assertEq(tokenIn.balanceOf(address(users.alice.deleGator)), aliceTokenInBalanceBefore + 10 ether, "Alice's tokenIn balance incorrect"); + assertEq(tokenOut.balanceOf(address(users.alice.deleGator)), aliceTokenOutBalanceBefore - 5 ether, "Alice's tokenOut balance incorrect"); + assertEq(tokenIn.balanceOf(address(users.bob.deleGator)), bobTokenInBalanceBefore - 10 ether, "Bob's tokenIn balance incorrect"); + assertEq(tokenOut.balanceOf(address(users.bob.deleGator)), bobTokenOutBalanceBefore + 5 ether, "Bob's tokenOut balance incorrect"); } function _getEnforcer() internal view override returns (ICaveatEnforcer) { return ICaveatEnforcer(address(swapOfferEnforcer)); } -} +} \ No newline at end of file