From 8b68b09c0200601c920b6e9b345c6cbfe75c089a Mon Sep 17 00:00:00 2001 From: hanzel98 Date: Wed, 26 Mar 2025 13:22:52 -0600 Subject: [PATCH 1/6] Added MultiTokenPeriodEnforcer --- src/enforcers/MultiTokenPeriodEnforcer.sol | 302 ++++++++ test/enforcers/MultiTokenPeriodEnforcer.t.sol | 699 ++++++++++++++++++ 2 files changed, 1001 insertions(+) create mode 100644 src/enforcers/MultiTokenPeriodEnforcer.sol create mode 100644 test/enforcers/MultiTokenPeriodEnforcer.t.sol diff --git a/src/enforcers/MultiTokenPeriodEnforcer.sol b/src/enforcers/MultiTokenPeriodEnforcer.sol new file mode 100644 index 00000000..24fb9745 --- /dev/null +++ b/src/enforcers/MultiTokenPeriodEnforcer.sol @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: MIT AND Apache-2.0 +pragma solidity 0.8.23; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { ExecutionLib } from "@erc7579/lib/ExecutionLib.sol"; + +import { CaveatEnforcer } from "./CaveatEnforcer.sol"; +import { ModeCode } from "../utils/Types.sol"; + +/** + * @title MultiTokenPeriodEnforcer + * @notice Enforces periodic transfer limits for multiple tokens per delegation. + * @dev The enforcer expects the _terms to be a concatenation of one or more 116-byte configurations. + * Each 116-byte segment encodes: + * - 20 bytes: token address (address(0) indicates a native transfer) + * - 32 bytes: periodAmount. + * - 32 bytes: periodDuration (in seconds). + * - 32 bytes: startDate for the first period. + * The _executionCallData always contains instructions for one token transfer. + */ +contract MultiTokenPeriodEnforcer is CaveatEnforcer { + using ExecutionLib for bytes; + + ////////////////////////////// State ////////////////////////////// + + struct PeriodicAllowance { + uint256 periodAmount; // Maximum transferable amount per period. + uint256 periodDuration; // Duration of each period in seconds. + uint256 startDate; // Timestamp when the first period begins. + uint256 lastTransferPeriod; // The period index in which the last transfer was made. + uint256 transferredInCurrentPeriod; // Cumulative amount transferred in the current period. + } + + // Mapping from delegation manager => delegation hash => token address => PeriodicAllowance. + mapping(address => mapping(bytes32 => mapping(address => PeriodicAllowance))) public periodicAllowances; + + ////////////////////////////// Events ////////////////////////////// + + /** + * @notice Emitted when a transfer is made and the allowance is updated. + * @param sender The address initiating the transfer. + * @param redeemer The address receiving the tokens/ETH. + * @param delegationHash The hash identifying the delegation. + * @param token The token contract address; for native transfers this is address(0). + * @param periodAmount The maximum transferable amount per period. + * @param periodDuration The duration of each period in seconds. + * @param startDate The timestamp when the first period begins. + * @param transferredInCurrentPeriod The total transferred in the current period after this transfer. + * @param transferTimestamp The block timestamp when the transfer occurred. + */ + event TransferredInPeriod( + address indexed sender, + address indexed redeemer, + bytes32 indexed delegationHash, + address token, + uint256 periodAmount, + uint256 periodDuration, + uint256 startDate, + uint256 transferredInCurrentPeriod, + uint256 transferTimestamp + ); + + ////////////////////////////// Public Methods ////////////////////////////// + + /** + * @notice Retrieves the available amount along with period details for a specific token. + * @param _delegationHash The delegation hash. + * @param _delegationManager The delegation manager's address. + * @param _terms A concatenation of one or more 116-byte configurations. + * @param _token The token for which the available amount is requested (address(0) for native). + * @return availableAmount_ The remaining transferable amount in the current period. + * @return isNewPeriod_ True if a new period has begun. + * @return currentPeriod_ The current period index. + */ + function getAvailableAmount( + bytes32 _delegationHash, + address _delegationManager, + bytes calldata _terms, + address _token + ) + external + view + returns (uint256 availableAmount_, bool isNewPeriod_, uint256 currentPeriod_) + { + PeriodicAllowance memory storedAllowance_ = periodicAllowances[_delegationManager][_delegationHash][_token]; + if (storedAllowance_.startDate != 0) { + return _getAvailableAmount(storedAllowance_); + } + + // Not yet initialized; simulate using provided terms. + (uint256 periodAmount_, uint256 periodDuration_, uint256 startDate_) = getTermsInfo(_terms, _token); + PeriodicAllowance memory allowance_ = PeriodicAllowance({ + periodAmount: periodAmount_, + periodDuration: periodDuration_, + startDate: startDate_, + lastTransferPeriod: 0, + transferredInCurrentPeriod: 0 + }); + return _getAvailableAmount(allowance_); + } + + /** + * @notice Hook called before a transfer to enforce the periodic limit. + * @dev For ERC20 transfers, expects _executionCallData to decode to (target, , callData) + * with callData length of 68 and beginning with IERC20.transfer.selector. + * For native transfers, expects _executionCallData to decode to (target, value, callData) + * with an empty callData. + * @param _terms A concatenation of one or more 116-byte configurations. + * @param _mode The execution mode (must be single callType, default execType). + * @param _executionCallData The encoded execution data. + * @param _delegationHash The delegation hash. + * @param _redeemer The address intended to receive the tokens/ETH. + */ + function beforeHook( + bytes calldata _terms, + bytes calldata, + ModeCode _mode, + bytes calldata _executionCallData, + bytes32 _delegationHash, + address, + address _redeemer + ) + public + override + onlySingleCallTypeMode(_mode) + onlyDefaultExecutionMode(_mode) + { + _validateAndConsumeTransfer(_terms, _executionCallData, _delegationHash, _redeemer); + } + + /** + * @notice Searches the provided _terms for a configuration matching _token. + * @dev Expects _terms length to be a multiple of 116. + * @param _terms A concatenation of 116-byte configurations. + * @param _token The token address to search for (address(0) for native transfers). + * @return periodAmount_ The maximum transferable amount for this token. + * @return periodDuration_ The period duration (in seconds) for this token. + * @return startDate_ The start date for the first period. + */ + function getTermsInfo( + bytes calldata _terms, + address _token + ) + public + pure + returns (uint256 periodAmount_, uint256 periodDuration_, uint256 startDate_) + { + uint256 termsLength_ = _terms.length; + require(termsLength_ % 116 == 0 && termsLength_ != 0, "MultiTokenPeriodEnforcer:invalid-terms-length"); + uint256 numConfigs_ = termsLength_ / 116; + for (uint256 i = 0; i < numConfigs_; i++) { + uint256 offset_ = i * 116; + address token_ = address(bytes20(_terms[offset_:offset_ + 20])); + if (token_ == _token) { + periodAmount_ = uint256(bytes32(_terms[offset_ + 20:offset_ + 52])); + periodDuration_ = uint256(bytes32(_terms[offset_ + 52:offset_ + 84])); + startDate_ = uint256(bytes32(_terms[offset_ + 84:offset_ + 116])); + return (periodAmount_, periodDuration_, startDate_); + } + } + revert("MultiTokenPeriodEnforcer:token-config-not-found"); + } + + /** + * @notice Decodes all configurations contained in _terms. + * @dev Expects _terms length to be a multiple of 116. + * @param _terms A concatenation of 116-byte configurations. + * @return tokens_ An array of token addresses. + * @return periodAmounts_ An array of period amounts. + * @return periodDurations_ An array of period durations (in seconds). + * @return startDates_ An array of start dates for the first period. + */ + function getAllTermsInfo(bytes calldata _terms) + public + pure + returns ( + address[] memory tokens_, + uint256[] memory periodAmounts_, + uint256[] memory periodDurations_, + uint256[] memory startDates_ + ) + { + uint256 termsLength_ = _terms.length; + require(termsLength_ % 116 == 0 && termsLength_ != 0, "MultiTokenPeriodEnforcer:invalid-terms-length"); + uint256 numConfigs_ = termsLength_ / 116; + tokens_ = new address[](numConfigs_); + periodAmounts_ = new uint256[](numConfigs_); + periodDurations_ = new uint256[](numConfigs_); + startDates_ = new uint256[](numConfigs_); + + for (uint256 i = 0; i < numConfigs_; i++) { + uint256 offset_ = i * 116; + tokens_[i] = address(bytes20(_terms[offset_:offset_ + 20])); + periodAmounts_[i] = uint256(bytes32(_terms[offset_ + 20:offset_ + 52])); + periodDurations_[i] = uint256(bytes32(_terms[offset_ + 52:offset_ + 84])); + startDates_[i] = uint256(bytes32(_terms[offset_ + 84:offset_ + 116])); + } + } + + ////////////////////////////// Internal Methods ////////////////////////////// + + /** + * @notice Validates and consumes a transfer (native or ERC20) by ensuring the amount does not exceed the available limit. + * @dev Decodes the execution data based on token type: + * - For native transfers (_token == address(0)): decodes (target, value, callData) and requires callData to be empty. + * - For ERC20 transfers (_token != address(0)): decodes (target, , callData) and requires callData length to be 68 with a + * valid IERC20.transfer selector. + * @param _terms The concatenated configurations. + * @param _executionCallData The encoded execution data. + * @param _delegationHash The delegation hash. + * @param _redeemer The address intended to receive the tokens/ETH. + */ + function _validateAndConsumeTransfer( + bytes calldata _terms, + bytes calldata _executionCallData, + bytes32 _delegationHash, + address _redeemer + ) + private + { + uint256 transferAmount_; + address token_; + + // Decode _executionCallData using decodeSingle. + (address target_, uint256 value_, bytes calldata callData_) = _executionCallData.decodeSingle(); + if (value_ > 0) { + // Native transfer. + token_ = address(0); + transferAmount_ = value_; + } else { + // ERC20 transfer. + require(callData_.length == 68, "MultiTokenPeriodEnforcer:invalid-execution-length"); + require(bytes4(callData_[0:4]) == IERC20.transfer.selector, "MultiTokenPeriodEnforcer:invalid-method"); + token_ = target_; + transferAmount_ = uint256(bytes32(callData_[36:68])); + } + + // Retrieve the configuration for the token from _terms. + (uint256 periodAmount_, uint256 periodDuration_, uint256 startDate_) = getTermsInfo(_terms, token_); + + // Use the multi-token mapping. + PeriodicAllowance storage allowance_ = periodicAllowances[msg.sender][_delegationHash][token_]; + + // Initialize the allowance if not already set. + if (allowance_.startDate == 0) { + require(startDate_ > 0, "MultiTokenPeriodEnforcer:invalid-zero-start-date"); + require(periodAmount_ > 0, "MultiTokenPeriodEnforcer:invalid-zero-period-amount"); + require(periodDuration_ > 0, "MultiTokenPeriodEnforcer:invalid-zero-period-duration"); + require(block.timestamp >= startDate_, "MultiTokenPeriodEnforcer:transfer-not-started"); + + allowance_.periodAmount = periodAmount_; + allowance_.periodDuration = periodDuration_; + allowance_.startDate = startDate_; + } + + // Determine the available amount. + (uint256 availableAmount_, bool isNewPeriod_, uint256 currentPeriod_) = _getAvailableAmount(allowance_); + require(transferAmount_ <= availableAmount_, "MultiTokenPeriodEnforcer:transfer-amount-exceeded"); + + // Reset transferred amount if a new period has begun. + if (isNewPeriod_) { + allowance_.lastTransferPeriod = currentPeriod_; + allowance_.transferredInCurrentPeriod = 0; + } + allowance_.transferredInCurrentPeriod += transferAmount_; + + emit TransferredInPeriod( + msg.sender, + _redeemer, + _delegationHash, + token_, + periodAmount_, + periodDuration_, + startDate_, + allowance_.transferredInCurrentPeriod, + block.timestamp + ); + } + + /** + * @notice Computes the available amount for the current period. + * @dev If block.timestamp is before startDate, available amount is 0. + * @param _allowance The PeriodicAllowance struct. + * @return availableAmount_ The remaining transferable amount in the current period. + * @return isNewPeriod_ True if the last transfer period is not equal to the current period. + * @return currentPeriod_ The current period index. + */ + function _getAvailableAmount(PeriodicAllowance memory _allowance) + internal + view + returns (uint256 availableAmount_, bool isNewPeriod_, uint256 currentPeriod_) + { + if (block.timestamp < _allowance.startDate) { + return (0, false, 0); + } + + currentPeriod_ = (block.timestamp - _allowance.startDate) / _allowance.periodDuration + 1; + isNewPeriod_ = (_allowance.lastTransferPeriod != currentPeriod_); + uint256 alreadyTransferred_ = isNewPeriod_ ? 0 : _allowance.transferredInCurrentPeriod; + availableAmount_ = _allowance.periodAmount > alreadyTransferred_ ? _allowance.periodAmount - alreadyTransferred_ : 0; + } +} diff --git a/test/enforcers/MultiTokenPeriodEnforcer.t.sol b/test/enforcers/MultiTokenPeriodEnforcer.t.sol new file mode 100644 index 00000000..2a0e0e67 --- /dev/null +++ b/test/enforcers/MultiTokenPeriodEnforcer.t.sol @@ -0,0 +1,699 @@ +// SPDX-License-Identifier: MIT AND Apache-2.0 +pragma solidity 0.8.23; + +import "forge-std/Test.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { ExecutionLib } from "@erc7579/lib/ExecutionLib.sol"; + +import { Caveat, Delegation, Execution } from "../../src/utils/Types.sol"; +import { CaveatEnforcerBaseTest } from "./CaveatEnforcerBaseTest.t.sol"; +import { MultiTokenPeriodEnforcer } from "../../src/enforcers/MultiTokenPeriodEnforcer.sol"; +import { BasicERC20, IERC20 as BasicIERC20 } from "../utils/BasicERC20.t.sol"; +import { ICaveatEnforcer } from "../../src/interfaces/ICaveatEnforcer.sol"; +import { EncoderLib } from "../../src/libraries/EncoderLib.sol"; + +contract MultiTokenPeriodEnforcerTest is CaveatEnforcerBaseTest { + ////////////////////////////// State ////////////////////////////// + MultiTokenPeriodEnforcer public multiTokenEnforcer; + BasicERC20 public basicERC20; // Used for ERC20 tests + address public alice; + address public bob; + + // A dummy delegation hash for simulation. + bytes32 dummyDelegationHash = keccak256("test-delegation"); + address redeemer = address(0x123); + + // Parameters for the ERC20 configuration. + uint256 public erc20PeriodAmount = 1000; + uint256 public erc20PeriodDuration = 1 days; // 86400 seconds + uint256 public erc20StartDate; + + // Parameters for the native token configuration. + uint256 public nativePeriodAmount = 1 ether; + uint256 public nativePeriodDuration = 1 days; + uint256 public nativeStartDate; + + ////////////////////// Set up ////////////////////// + function setUp() public override { + super.setUp(); + multiTokenEnforcer = new MultiTokenPeriodEnforcer(); + vm.label(address(multiTokenEnforcer), "MultiToken Period Transfer Enforcer"); + + alice = address(users.alice.deleGator); + bob = address(users.bob.deleGator); + basicERC20 = new BasicERC20(alice, "TestToken", "TT", 100 ether); + + erc20StartDate = block.timestamp; + nativeStartDate = block.timestamp; + // For native tests, ensure the sender has ETH. + vm.deal(alice, 100 ether); + } + + ////////////////////// Error / Revert Tests ////////////////////// + + /// @notice Ensures it reverts if _terms length is not a multiple of 116 bytes. + function test_InvalidTermsLength() public { + bytes memory invalidTerms_ = new bytes(115); // not a multiple of 116 + vm.expectRevert("MultiTokenPeriodEnforcer:invalid-terms-length"); + multiTokenEnforcer.getTermsInfo(invalidTerms_, address(basicERC20)); + } + + /// @notice Reverts if a token configuration is not found in _terms. + function test_TokenConfigNotFound() public { + // Encode only an ERC20 configuration. + bytes memory terms_ = abi.encodePacked(address(basicERC20), erc20PeriodAmount, erc20PeriodDuration, erc20StartDate); + // Request native config (address(0)) which is not present. + vm.expectRevert("MultiTokenPeriodEnforcer:token-config-not-found"); + multiTokenEnforcer.getTermsInfo(terms_, address(0)); + } + + /// @notice Reverts if the ERC20 config has a zero start date. + function test_InvalidZeroStartDateErc20() public { + bytes memory terms_ = abi.encodePacked(address(basicERC20), erc20PeriodAmount, erc20PeriodDuration, uint256(0)); + bytes memory callData_ = _encodeERC20Transfer(bob, 100); + bytes memory execData_ = _encodeSingleExecution(address(basicERC20), 0, callData_); + vm.expectRevert("MultiTokenPeriodEnforcer:invalid-zero-start-date"); + multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, execData_, dummyDelegationHash, address(0), redeemer); + } + + /// @notice Reverts if the ERC20 config has a zero period duration. + function test_InvalidZeroPeriodDurationErc20() public { + bytes memory terms_ = abi.encodePacked(address(basicERC20), erc20PeriodAmount, uint256(0), erc20StartDate); + bytes memory callData_ = _encodeERC20Transfer(bob, 100); + bytes memory execData_ = _encodeSingleExecution(address(basicERC20), 0, callData_); + vm.expectRevert("MultiTokenPeriodEnforcer:invalid-zero-period-duration"); + multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, execData_, dummyDelegationHash, address(0), redeemer); + } + + /// @notice Reverts if the ERC20 config has a zero period amount. + function test_InvalidZeroPeriodAmountErc20() public { + bytes memory terms_ = abi.encodePacked(address(basicERC20), uint256(0), erc20PeriodDuration, erc20StartDate); + bytes memory callData_ = _encodeERC20Transfer(bob, 100); + bytes memory execData_ = _encodeSingleExecution(address(basicERC20), 0, callData_); + vm.expectRevert("MultiTokenPeriodEnforcer:invalid-zero-period-amount"); + multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, execData_, dummyDelegationHash, address(0), redeemer); + } + + /// @notice Reverts if the ERC20 transfer is attempted before the start date. + function test_TransferNotStartedErc20() public { + uint256 futureStart_ = block.timestamp + 100; + bytes memory terms_ = abi.encodePacked(address(basicERC20), erc20PeriodAmount, erc20PeriodDuration, futureStart_); + bytes memory callData_ = _encodeERC20Transfer(bob, 10 ether); + bytes memory execData_ = _encodeSingleExecution(address(basicERC20), 0, callData_); + vm.expectRevert("MultiTokenPeriodEnforcer:transfer-not-started"); + multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, execData_, dummyDelegationHash, address(0), redeemer); + } + + /// @notice Reverts if the ERC20 execution call data length is invalid. + function test_InvalidExecutionLengthErc20() public { + bytes memory terms_ = abi.encodePacked(address(basicERC20), erc20PeriodAmount, erc20PeriodDuration, erc20StartDate); + bytes memory invalidExecData_ = new bytes(67); + vm.expectRevert("MultiTokenPeriodEnforcer:invalid-execution-length"); + multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, invalidExecData_, dummyDelegationHash, address(0), redeemer); + } + + /// @notice Reverts if the ERC20 target does not match the token in _terms. + function test_InvalidContractErc20() public { + bytes memory terms_ = abi.encodePacked(address(basicERC20), erc20PeriodAmount, erc20PeriodDuration, erc20StartDate); + bytes memory callData_ = _encodeERC20Transfer(bob, 10 ether); + bytes memory invalidExecData_ = _encodeSingleExecution(address(0xdead), 0, callData_); + vm.expectRevert("MultiTokenPeriodEnforcer:token-config-not-found"); + multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, invalidExecData_, dummyDelegationHash, address(0), redeemer); + } + + /// @notice Reverts if the ERC20 call data selector is invalid. + function test_InvalidMethodErc20() public { + bytes memory terms_ = abi.encodePacked(address(basicERC20), erc20PeriodAmount, erc20PeriodDuration, erc20StartDate); + bytes memory invalidCallData_ = abi.encodeWithSelector(IERC20.transferFrom.selector, redeemer, 500); + bytes memory invalidExecData_ = _encodeSingleExecution(address(basicERC20), 0, invalidCallData_); + vm.expectRevert("MultiTokenPeriodEnforcer:invalid-method"); + multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, invalidExecData_, dummyDelegationHash, address(0), redeemer); + } + + /// @notice Reverts if an ERC20 transfer exceeds the available allowance. + function test_TransferAmountExceededErc20() public { + bytes memory terms_ = abi.encodePacked(address(basicERC20), erc20PeriodAmount, erc20PeriodDuration, erc20StartDate); + // First transfer: 800 tokens. + bytes memory callData1_ = _encodeERC20Transfer(bob, 800); + bytes memory execData1_ = _encodeSingleExecution(address(basicERC20), 0, callData1_); + multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, execData1_, dummyDelegationHash, address(0), redeemer); + + // Second transfer: attempt 300 tokens (exceeds remaining allowance). + bytes memory callData2_ = _encodeERC20Transfer(bob, 300); + bytes memory execData2_ = _encodeSingleExecution(address(basicERC20), 0, callData2_); + vm.expectRevert("MultiTokenPeriodEnforcer:transfer-amount-exceeded"); + multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, execData2_, dummyDelegationHash, address(0), redeemer); + } + + /// @notice Tests a successful ERC20 transfer and verifies that the TransferredInPeriod event is emitted. + function test_SuccessfulTransferAndEventErc20() public { + uint256 transferAmount_ = 500; + bytes memory terms_ = abi.encodePacked(address(basicERC20), erc20PeriodAmount, erc20PeriodDuration, erc20StartDate); + bytes memory callData_ = _encodeERC20Transfer(bob, transferAmount_); + bytes memory execData_ = _encodeSingleExecution(address(basicERC20), 0, callData_); + + vm.expectEmit(true, true, true, true); + emit MultiTokenPeriodEnforcer.TransferredInPeriod( + address(this), + redeemer, + dummyDelegationHash, + address(basicERC20), + erc20PeriodAmount, + erc20PeriodDuration, + erc20StartDate, + transferAmount_, + block.timestamp + ); + multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, execData_, dummyDelegationHash, address(0), redeemer); + (uint256 available_,,) = + multiTokenEnforcer.getAvailableAmount(dummyDelegationHash, address(this), terms_, address(basicERC20)); + assertEq(available_, erc20PeriodAmount - transferAmount_); + } + + /// @notice Tests multiple ERC20 transfers within the same period. + function test_MultipleTransfersInSamePeriodErc20() public { + bytes memory terms_ = abi.encodePacked(address(basicERC20), erc20PeriodAmount, erc20PeriodDuration, erc20StartDate); + // First transfer: 400 tokens. + bytes memory callData1_ = _encodeERC20Transfer(bob, 400); + bytes memory execData1_ = _encodeSingleExecution(address(basicERC20), 0, callData1_); + multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, execData1_, dummyDelegationHash, address(0), bob); + + // Second transfer: 300 tokens. + bytes memory callData2_ = _encodeERC20Transfer(bob, 300); + bytes memory execData2_ = _encodeSingleExecution(address(basicERC20), 0, callData2_); + multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, execData2_, dummyDelegationHash, address(0), bob); + + (uint256 available_,,) = + multiTokenEnforcer.getAvailableAmount(dummyDelegationHash, address(this), terms_, address(basicERC20)); + assertEq(available_, erc20PeriodAmount - 700); + + // Third transfer: attempt 400 tokens (should exceed allowance). + bytes memory callData3_ = _encodeERC20Transfer(bob, 400); + bytes memory execData3_ = _encodeSingleExecution(address(basicERC20), 0, callData3_); + vm.expectRevert("MultiTokenPeriodEnforcer:transfer-amount-exceeded"); + multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, execData3_, dummyDelegationHash, address(0), bob); + } + + /// @notice Tests that the ERC20 allowance resets when a new period begins. + function test_NewPeriodResetsAllowanceErc20() public { + bytes memory terms_ = abi.encodePacked(address(basicERC20), erc20PeriodAmount, erc20PeriodDuration, erc20StartDate); + // First transfer: 800 tokens. + bytes memory callData1_ = _encodeERC20Transfer(bob, 800); + bytes memory execData1_ = _encodeSingleExecution(address(basicERC20), 0, callData1_); + multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, execData1_, dummyDelegationHash, address(0), redeemer); + + (uint256 availableBefore_,, uint256 periodBefore_) = + multiTokenEnforcer.getAvailableAmount(dummyDelegationHash, address(this), terms_, address(basicERC20)); + assertEq(availableBefore_, erc20PeriodAmount - 800); + + vm.warp(block.timestamp + erc20PeriodDuration + 1); + + (uint256 availableAfter_, bool isNewPeriod_, uint256 periodAfter_) = + multiTokenEnforcer.getAvailableAmount(dummyDelegationHash, address(this), terms_, address(basicERC20)); + assertEq(availableAfter_, erc20PeriodAmount); + assertTrue(isNewPeriod_); + assertGt(periodAfter_, periodBefore_); + + // New transfer: 600 tokens. + bytes memory callData2_ = _encodeERC20Transfer(bob, 600); + bytes memory execData2_ = _encodeSingleExecution(address(basicERC20), 0, callData2_); + multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, execData2_, dummyDelegationHash, address(0), redeemer); + + (uint256 availableAfterTransfer_,,) = + multiTokenEnforcer.getAvailableAmount(dummyDelegationHash, address(this), terms_, address(basicERC20)); + assertEq(availableAfterTransfer_, erc20PeriodAmount - 600); + } + + /// @notice Reverts if an invalid call type mode is used. + function test_RevertWithInvalidCallTypeMode() public { + bytes memory executionCallData_ = ExecutionLib.encodeBatch(new Execution[](2)); + vm.expectRevert("CaveatEnforcer:invalid-call-type"); + multiTokenEnforcer.beforeHook(hex"", hex"", batchDefaultMode, executionCallData_, bytes32(0), address(0), address(0)); + } + + /// @notice Reverts if an invalid execution mode is used. + function test_RevertWithInvalidExecutionMode() public { + vm.prank(address(delegationManager)); + vm.expectRevert("CaveatEnforcer:invalid-execution-type"); + multiTokenEnforcer.beforeHook(hex"", hex"", singleTryMode, hex"", bytes32(0), address(0), address(0)); + } + + ////////////////////// Native Token Tests ////////////////////// + + /// @notice Reverts if the native token config has a zero start date. + function test_InvalidZeroStartDateNative() public { + bytes memory terms_ = abi.encodePacked(address(0), nativePeriodAmount, nativePeriodDuration, uint256(0)); + bytes memory execData_ = _encodeNativeTransfer(bob, 0.5 ether); + vm.expectRevert("MultiTokenPeriodEnforcer:invalid-zero-start-date"); + multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, execData_, dummyDelegationHash, address(0), redeemer); + } + + /// @notice Reverts if the native token config has a zero period duration. + function test_InvalidZeroPeriodDurationNative() public { + bytes memory terms_ = abi.encodePacked(address(0), nativePeriodAmount, uint256(0), nativeStartDate); + bytes memory execData_ = _encodeNativeTransfer(bob, 0.5 ether); + vm.expectRevert("MultiTokenPeriodEnforcer:invalid-zero-period-duration"); + multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, execData_, dummyDelegationHash, address(0), redeemer); + } + + /// @notice Reverts if the native token config has a zero period amount. + function test_InvalidZeroPeriodAmountNative() public { + bytes memory terms_ = abi.encodePacked(address(0), uint256(0), nativePeriodDuration, nativeStartDate); + bytes memory execData_ = _encodeNativeTransfer(bob, 0.5 ether); + vm.expectRevert("MultiTokenPeriodEnforcer:invalid-zero-period-amount"); + multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, execData_, dummyDelegationHash, address(0), redeemer); + } + + /// @notice Reverts if a native transfer is attempted before the start date. + function test_TransferNotStartedNative() public { + uint256 futureStart_ = block.timestamp + 100; + bytes memory terms_ = abi.encodePacked(address(0), nativePeriodAmount, nativePeriodDuration, futureStart_); + bytes memory execData_ = _encodeNativeTransfer(bob, 0.5 ether); + vm.expectRevert("MultiTokenPeriodEnforcer:transfer-not-started"); + multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, execData_, dummyDelegationHash, address(0), redeemer); + } + + /// @notice Reverts if a native transfer exceeds the available allowance. + function test_TransferAmountExceededNative() public { + bytes memory terms_ = abi.encodePacked(address(0), nativePeriodAmount, nativePeriodDuration, nativeStartDate); + // First transfer: 0.8 ether. + bytes memory execData1_ = _encodeNativeTransfer(bob, 0.8 ether); + multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, execData1_, dummyDelegationHash, address(0), redeemer); + + // Second transfer: attempt 0.3 ether (exceeds remaining allowance). + bytes memory execData2_ = _encodeNativeTransfer(bob, 0.3 ether); + vm.expectRevert("MultiTokenPeriodEnforcer:transfer-amount-exceeded"); + multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, execData2_, dummyDelegationHash, address(0), redeemer); + } + + /// @notice Tests a successful native transfer and verifies that the TransferredInPeriod event is emitted. + function test_SuccessfulTransferAndEventNative() public { + uint256 transferAmount_ = 0.5 ether; + bytes memory terms_ = abi.encodePacked(address(0), nativePeriodAmount, nativePeriodDuration, nativeStartDate); + bytes memory execData_ = _encodeNativeTransfer(bob, transferAmount_); + + vm.expectEmit(true, true, true, true); + emit MultiTokenPeriodEnforcer.TransferredInPeriod( + address(this), + redeemer, + dummyDelegationHash, + address(0), + nativePeriodAmount, + nativePeriodDuration, + nativeStartDate, + transferAmount_, + block.timestamp + ); + multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, execData_, dummyDelegationHash, address(0), redeemer); + (uint256 available_,,) = multiTokenEnforcer.getAvailableAmount(dummyDelegationHash, address(this), terms_, address(0)); + assertEq(available_, nativePeriodAmount - transferAmount_); + } + + /// @notice Tests multiple native transfers within the same period. + function test_MultipleTransfersInSamePeriodNative() public { + bytes memory terms_ = abi.encodePacked(address(0), nativePeriodAmount, nativePeriodDuration, nativeStartDate); + // First transfer: 0.4 ether. + bytes memory execData1_ = _encodeNativeTransfer(bob, 0.4 ether); + multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, execData1_, dummyDelegationHash, address(0), redeemer); + + // Second transfer: 0.3 ether. + bytes memory execData2_ = _encodeNativeTransfer(bob, 0.3 ether); + multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, execData2_, dummyDelegationHash, address(0), redeemer); + + (uint256 available_,,) = multiTokenEnforcer.getAvailableAmount(dummyDelegationHash, address(this), terms_, address(0)); + assertEq(available_, nativePeriodAmount - 0.7 ether); + + // Third transfer: attempt 0.4 ether (should revert). + bytes memory execData3_ = _encodeNativeTransfer(bob, 0.4 ether); + vm.expectRevert("MultiTokenPeriodEnforcer:transfer-amount-exceeded"); + multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, execData3_, dummyDelegationHash, address(0), redeemer); + } + + /// @notice Tests that the native allowance resets when a new period begins. + function test_NewPeriodResetsAllowanceNative() public { + bytes memory terms_ = abi.encodePacked(address(0), nativePeriodAmount, nativePeriodDuration, nativeStartDate); + // First transfer: 0.8 ether. + bytes memory execData1_ = _encodeNativeTransfer(bob, 0.8 ether); + multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, execData1_, dummyDelegationHash, address(0), redeemer); + + (uint256 availableBefore_,, uint256 periodBefore_) = + multiTokenEnforcer.getAvailableAmount(dummyDelegationHash, address(this), terms_, address(0)); + assertEq(availableBefore_, nativePeriodAmount - 0.8 ether); + + vm.warp(block.timestamp + nativePeriodDuration + 1); + + (uint256 availableAfter_, bool isNewPeriod_, uint256 periodAfter_) = + multiTokenEnforcer.getAvailableAmount(dummyDelegationHash, address(this), terms_, address(0)); + assertEq(availableAfter_, nativePeriodAmount); + assertTrue(isNewPeriod_); + assertGt(periodAfter_, periodBefore_); + + // New transfer: 0.3 ether. + bytes memory execData2_ = _encodeNativeTransfer(bob, 0.3 ether); + multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, execData2_, dummyDelegationHash, address(0), redeemer); + + (uint256 availableAfterTransfer_,,) = + multiTokenEnforcer.getAvailableAmount(dummyDelegationHash, address(this), terms_, address(0)); + assertEq(availableAfterTransfer_, nativePeriodAmount - 0.3 ether); + } + + ////////////////////// Additional Tests ////////////////////// + + /// @notice Ensures that once an allowance is initialized, subsequent calls with different _terms do not override the stored + /// state. + function test_TermsMismatchAfterInitialization() public { + // Initialize allowance using the initial ERC20 configuration. + bytes memory initialTerms_ = abi.encodePacked(address(basicERC20), erc20PeriodAmount, erc20PeriodDuration, erc20StartDate); + uint256 initialTransfer_ = 200; + bytes memory callData1_ = _encodeERC20Transfer(bob, initialTransfer_); + bytes memory execData1_ = _encodeSingleExecution(address(basicERC20), 0, callData1_); + multiTokenEnforcer.beforeHook(initialTerms_, "", singleDefaultMode, execData1_, dummyDelegationHash, address(0), redeemer); + + // Prepare new terms with different parameters. + uint256 newPeriodAmount_ = erc20PeriodAmount + 500; + uint256 newPeriodDuration_ = erc20PeriodDuration + 100; + uint256 newStartDate_ = erc20StartDate + 50; + bytes memory newTerms_ = abi.encodePacked(address(basicERC20), newPeriodAmount_, newPeriodDuration_, newStartDate_); + uint256 secondTransfer_ = 300; + bytes memory callData2_ = _encodeERC20Transfer(bob, secondTransfer_); + bytes memory execData2_ = _encodeSingleExecution(address(basicERC20), 0, callData2_); + multiTokenEnforcer.beforeHook(newTerms_, "", singleDefaultMode, execData2_, dummyDelegationHash, address(0), redeemer); + + // The stored allowance should still reflect the initial terms. + (uint256 available_,,) = + multiTokenEnforcer.getAvailableAmount(dummyDelegationHash, address(this), initialTerms_, address(basicERC20)); + assertEq(available_, erc20PeriodAmount - (initialTransfer_ + secondTransfer_), "Stored state overridden by new terms"); + } + + /// @notice Tests that allowances are isolated per delegation manager (msg.sender). + function test_AllowanceIsolationByDelegationManager() public { + bytes memory terms_ = abi.encodePacked(address(basicERC20), erc20PeriodAmount, erc20PeriodDuration, erc20StartDate); + uint256 transferAmount_ = 400; + bytes memory callData_ = _encodeERC20Transfer(bob, transferAmount_); + bytes memory execData_ = _encodeSingleExecution(address(basicERC20), 0, callData_); + // Call as the default delegation manager. + multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, execData_, dummyDelegationHash, address(0), redeemer); + // Now simulate a different delegation manager. + address otherManager_ = address(0x456); + vm.prank(otherManager_); + (uint256 availableOther_,,) = + multiTokenEnforcer.getAvailableAmount(dummyDelegationHash, otherManager_, terms_, address(basicERC20)); + assertEq(availableOther_, erc20PeriodAmount, "Allowance not isolated by delegation manager"); + } + + /// @notice Tests boundary conditions in period calculation at exactly the start date and at a period boundary. + function test_BoundaryPeriodCalculation() public { + bytes memory terms_ = abi.encodePacked(address(basicERC20), erc20PeriodAmount, erc20PeriodDuration, erc20StartDate); + + // Warp to exactly the start date. + vm.warp(erc20StartDate); + (uint256 availableAtStart_, bool isNewAtStart_, uint256 currentPeriodAtStart_) = + multiTokenEnforcer.getAvailableAmount(dummyDelegationHash, address(this), terms_, address(basicERC20)); + assertEq(availableAtStart_, erc20PeriodAmount, "Available not full at startDate"); + assertTrue(isNewAtStart_, "isNewPeriod not true at startDate"); + assertEq(currentPeriodAtStart_, 1, "Current period should be 1 at startDate"); + + // Perform a transfer to initialize the allowance. + uint256 transferAmount_ = 100; + bytes memory callData_ = _encodeERC20Transfer(bob, transferAmount_); + bytes memory execData_ = _encodeSingleExecution(address(basicERC20), 0, callData_); + multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, execData_, dummyDelegationHash, address(0), redeemer); + + // Warp to exactly the end of the period. + uint256 boundaryTime_ = erc20StartDate + erc20PeriodDuration; + vm.warp(boundaryTime_); + (uint256 availableAtBoundary_, bool isNewAtBoundary_, uint256 currentPeriodAtBoundary_) = + multiTokenEnforcer.getAvailableAmount(dummyDelegationHash, address(this), terms_, address(basicERC20)); + // Expect a reset: full allowance available and period index incremented. + assertEq(availableAtBoundary_, erc20PeriodAmount, "Available should reset at period boundary"); + assertTrue(isNewAtBoundary_, "isNewPeriod should be true at boundary"); + assertEq(currentPeriodAtBoundary_, 2, "Current period should be 2 at boundary"); + } + + /// @notice Tests native transfer behavior when non-empty callData is provided. + /// @dev Although the spec expects empty callData for native transfers, the current implementation does not enforce it. + function test_NativeTransferWithNonEmptyCallData() public { + bytes memory terms_ = abi.encodePacked(address(0), nativePeriodAmount, nativePeriodDuration, nativeStartDate); + // Prepare execution data for a native transfer with non-empty callData. + bytes memory nonEmptyCallData_ = "non-empty"; + bytes memory execData_ = abi.encodePacked(bob, nativePeriodAmount / 2, nonEmptyCallData_); + // This should pass and reduce the available allowance. + multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, execData_, dummyDelegationHash, address(0), redeemer); + (uint256 available_,,) = multiTokenEnforcer.getAvailableAmount(dummyDelegationHash, address(this), terms_, address(0)); + assertEq( + available_, nativePeriodAmount - (nativePeriodAmount / 2), "Available not reduced correctly with non-empty callData" + ); + } + + /// @notice Tests that multiple beforeHook calls within the same period correctly accumulate the transferred amount. + function test_MultipleBeforeHookCallsMaintainsState() public { + bytes memory terms_ = abi.encodePacked(address(basicERC20), erc20PeriodAmount, erc20PeriodDuration, erc20StartDate); + // First call: transfer 300 tokens. + uint256 firstTransfer_ = 300; + bytes memory callData1_ = _encodeERC20Transfer(bob, firstTransfer_); + bytes memory execData1_ = _encodeSingleExecution(address(basicERC20), 0, callData1_); + multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, execData1_, dummyDelegationHash, address(0), redeemer); + + // Second call: transfer 200 tokens. + uint256 secondTransfer_ = 200; + bytes memory callData2_ = _encodeERC20Transfer(bob, secondTransfer_); + bytes memory execData2_ = _encodeSingleExecution(address(basicERC20), 0, callData2_); + multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, execData2_, dummyDelegationHash, address(0), redeemer); + + // Total transferred should be 500 tokens. + (uint256 available_,,) = + multiTokenEnforcer.getAvailableAmount(dummyDelegationHash, address(this), terms_, address(basicERC20)); + assertEq( + available_, erc20PeriodAmount - (firstTransfer_ + secondTransfer_), "Multiple calls did not accumulate state properly" + ); + } + + ////////////////////// Integration Tests ////////////////////// + + /// @notice Integration: Successfully transfers both ERC20 and native tokens under the same delegation. + function test_IntegrationCombinedTokens() public { + // Encode terms for two token configurations (ERC20 then native). + bytes memory terms_ = abi.encodePacked( + address(basicERC20), + erc20PeriodAmount, + erc20PeriodDuration, + erc20StartDate, + address(0), + nativePeriodAmount, + nativePeriodDuration, + nativeStartDate + ); + // Build execution data for an ERC20 transfer. + uint256 erc20TransferAmount_ = 400; + bytes memory erc20CallData_ = _encodeERC20Transfer(bob, erc20TransferAmount_); + bytes memory erc20ExecData_ = _encodeSingleExecution(address(basicERC20), 0, erc20CallData_); + // Build execution data for a native transfer. + uint256 nativeTransferAmount_ = 0.5 ether; + bytes memory nativeExecData_ = _encodeNativeTransfer(bob, nativeTransferAmount_); + + // Perform ERC20 transfer. + multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, erc20ExecData_, dummyDelegationHash, address(0), redeemer); + // Perform native transfer. + multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, nativeExecData_, dummyDelegationHash, address(0), redeemer); + + // Verify available amounts for each token. + (uint256 availableERC20_,,) = + multiTokenEnforcer.getAvailableAmount(dummyDelegationHash, address(this), terms_, address(basicERC20)); + (uint256 availableNative_,,) = multiTokenEnforcer.getAvailableAmount(dummyDelegationHash, address(this), terms_, address(0)); + assertEq(availableERC20_, erc20PeriodAmount - erc20TransferAmount_, "ERC20 allowance updated correctly"); + assertEq(availableNative_, nativePeriodAmount - nativeTransferAmount_, "Native allowance updated correctly"); + } + + /// @notice Integration: Confirms that different delegation hashes are tracked independently. + function test_IntegrationMultipleDelegations() public { + bytes memory terms_ = abi.encodePacked(address(basicERC20), erc20PeriodAmount, erc20PeriodDuration, erc20StartDate); + // Build two delegations with different salts. + Caveat[] memory caveats_ = new Caveat[](1); + caveats_[0] = Caveat({ args: hex"", enforcer: address(multiTokenEnforcer), terms: terms_ }); + Delegation memory delegation1_ = + Delegation({ delegate: bob, delegator: alice, authority: ROOT_AUTHORITY, caveats: caveats_, salt: 0, signature: hex"" }); + Delegation memory delegation2_ = + Delegation({ delegate: bob, delegator: alice, authority: ROOT_AUTHORITY, caveats: caveats_, salt: 1, signature: hex"" }); + delegation1_ = signDelegation(users.alice, delegation1_); + delegation2_ = signDelegation(users.alice, delegation2_); + bytes32 delHash1_ = EncoderLib._getDelegationHash(delegation1_); + bytes32 delHash2_ = EncoderLib._getDelegationHash(delegation2_); + + // For delegation1_, transfer 600 tokens. + bytes memory callData1_ = _encodeERC20Transfer(bob, 600); + + // Simulate a native transfer user operation. + invokeDelegation_UserOp( + users.bob, toDelegationArray(delegation1_), Execution({ target: address(basicERC20), value: 0, callData: callData1_ }) + ); + + // For delegation2_, transfer 900 tokens. + bytes memory callData2_ = _encodeERC20Transfer(bob, 900); + invokeDelegation_UserOp( + users.bob, toDelegationArray(delegation2_), Execution({ target: address(basicERC20), value: 0, callData: callData2_ }) + ); + + (uint256 available1_,,) = + multiTokenEnforcer.getAvailableAmount(delHash1_, address(delegationManager), terms_, address(basicERC20)); + (uint256 available2_,,) = + multiTokenEnforcer.getAvailableAmount(delHash2_, address(delegationManager), terms_, address(basicERC20)); + assertEq(available1_, erc20PeriodAmount - 600, "Delegation1 ERC20 allowance not updated correctly"); + assertEq(available2_, erc20PeriodAmount - 900, "Delegation2 ERC20 allowance not updated correctly"); + } + + /// @notice Simulation: Tests getAvailableAmount for ERC20 before and after initialization. + function test_GetAvailableAmountSimulationBeforeInitializationErc20() public { + uint256 futureStart_ = block.timestamp + 100; + bytes memory terms_ = abi.encodePacked(address(basicERC20), erc20PeriodAmount, erc20PeriodDuration, futureStart_); + (uint256 availableBefore_, bool isNewPeriodBefore_, uint256 currentPeriodBefore_) = + multiTokenEnforcer.getAvailableAmount(dummyDelegationHash, address(this), terms_, address(basicERC20)); + assertEq(availableBefore_, 0, "Available should be 0 before start date"); + assertEq(isNewPeriodBefore_, false, "isNewPeriod false before start"); + assertEq(currentPeriodBefore_, 0, "Current period 0 before start"); + + vm.warp(futureStart_ + 1); + + (uint256 availableAfter_, bool isNewPeriodAfter_, uint256 currentPeriodAfter_) = + multiTokenEnforcer.getAvailableAmount(dummyDelegationHash, address(this), terms_, address(basicERC20)); + assertEq(availableAfter_, erc20PeriodAmount, "Available equals periodAmount after start"); + assertTrue(isNewPeriodAfter_, "isNewPeriod true after start"); + uint256 expectedPeriod_ = (block.timestamp - futureStart_) / erc20PeriodDuration + 1; + assertEq(currentPeriodAfter_, expectedPeriod_, "Current period computed correctly after start"); + } + + /// @notice Simulation: Tests getAvailableAmount for native tokens before and after initialization. + function test_GetAvailableAmountSimulationBeforeInitializationNative() public { + uint256 futureStart_ = block.timestamp + 100; + bytes memory terms_ = abi.encodePacked(address(0), nativePeriodAmount, nativePeriodDuration, futureStart_); + (uint256 availableBefore_, bool isNewPeriodBefore_, uint256 currentPeriodBefore_) = + multiTokenEnforcer.getAvailableAmount(dummyDelegationHash, address(this), terms_, address(0)); + assertEq(availableBefore_, 0, "Available should be 0 before start"); + assertEq(isNewPeriodBefore_, false, "isNewPeriod false before start"); + assertEq(currentPeriodBefore_, 0, "Current period 0 before start"); + + vm.warp(futureStart_ + 1); + + (uint256 availableAfter_, bool isNewPeriodAfter_, uint256 currentPeriodAfter_) = + multiTokenEnforcer.getAvailableAmount(dummyDelegationHash, address(this), terms_, address(0)); + assertEq(availableAfter_, nativePeriodAmount, "Available equals periodAmount after start"); + assertTrue(isNewPeriodAfter_, "isNewPeriod true after start"); + uint256 expectedPeriod_ = (block.timestamp - futureStart_) / nativePeriodDuration + 1; + assertEq(currentPeriodAfter_, expectedPeriod_, "Current period computed correctly after start"); + } + + /// @notice Ensures getAllTermsInfo reverts if _terms length is not a multiple of 116 bytes. + function test_GetAllTermsInfoInvalidTermsLength() public { + bytes memory invalidTerms_ = new bytes(115); // Not a multiple of 116 + vm.expectRevert("MultiTokenPeriodEnforcer:invalid-terms-length"); + multiTokenEnforcer.getAllTermsInfo(invalidTerms_); + } + + /// @notice Checks that getAllTermsInfo correctly decodes a single configuration. + function test_GetAllTermsInfoSingleConfiguration() public { + bytes memory terms_ = abi.encodePacked(address(basicERC20), erc20PeriodAmount, erc20PeriodDuration, erc20StartDate); + (address[] memory tokens_, uint256[] memory periodAmounts_, uint256[] memory periodDurations_, uint256[] memory startDates_) + = multiTokenEnforcer.getAllTermsInfo(terms_); + assertEq(tokens_.length, 1, "Expected one configuration"); + assertEq(tokens_[0], address(basicERC20), "Token address mismatch"); + assertEq(periodAmounts_[0], erc20PeriodAmount, "Period amount mismatch"); + assertEq(periodDurations_[0], erc20PeriodDuration, "Period duration mismatch"); + assertEq(startDates_[0], erc20StartDate, "Start date mismatch"); + } + + /// @notice Ensures getAllTermsInfo correctly decodes multiple configurations. + function test_GetAllTermsInfoMultipleConfigurations() public { + // Encode two configurations: one for basicERC20 and one for native token (address(0)). + bytes memory terms_ = abi.encodePacked( + address(basicERC20), + erc20PeriodAmount, + erc20PeriodDuration, + erc20StartDate, + address(0), + nativePeriodAmount, + nativePeriodDuration, + nativeStartDate + ); + (address[] memory tokens_, uint256[] memory periodAmounts_, uint256[] memory periodDurations_, uint256[] memory startDates_) + = multiTokenEnforcer.getAllTermsInfo(terms_); + assertEq(tokens_.length, 2, "Expected two configurations"); + + // First configuration: ERC20 token. + assertEq(tokens_[0], address(basicERC20), "First config: token address mismatch"); + assertEq(periodAmounts_[0], erc20PeriodAmount, "First config: period amount mismatch"); + assertEq(periodDurations_[0], erc20PeriodDuration, "First config: period duration mismatch"); + assertEq(startDates_[0], erc20StartDate, "First config: start date mismatch"); + + // Second configuration: Native token. + assertEq(tokens_[1], address(0), "Second config: token address mismatch"); + assertEq(periodAmounts_[1], nativePeriodAmount, "Second config: period amount mismatch"); + assertEq(periodDurations_[1], nativePeriodDuration, "Second config: period duration mismatch"); + assertEq(startDates_[1], nativeStartDate, "Second config: start date mismatch"); + } + + /// @notice Validates that getAllTermsInfo correctly decodes a mixed set of configurations. + function test_GetAllTermsInfoMixedTokens() public { + // Create three configurations: ERC20, Native, and a second ERC20 with different parameters. + uint256 secondERC20Amount_ = erc20PeriodAmount + 100; + uint256 secondERC20Duration_ = erc20PeriodDuration + 100; + uint256 secondERC20Start_ = erc20StartDate + 50; + bytes memory terms_ = abi.encodePacked( + address(basicERC20), + erc20PeriodAmount, + erc20PeriodDuration, + erc20StartDate, + address(0), + nativePeriodAmount, + nativePeriodDuration, + nativeStartDate, + address(basicERC20), + secondERC20Amount_, + secondERC20Duration_, + secondERC20Start_ + ); + (address[] memory tokens_, uint256[] memory periodAmounts_, uint256[] memory periodDurations_, uint256[] memory startDates_) + = multiTokenEnforcer.getAllTermsInfo(terms_); + assertEq(tokens_.length, 3, "Expected three configurations"); + + // First configuration: Original ERC20. + assertEq(tokens_[0], address(basicERC20), "Config0: token mismatch"); + assertEq(periodAmounts_[0], erc20PeriodAmount, "Config0: period amount mismatch"); + assertEq(periodDurations_[0], erc20PeriodDuration, "Config0: period duration mismatch"); + assertEq(startDates_[0], erc20StartDate, "Config0: start date mismatch"); + + // Second configuration: Native token. + assertEq(tokens_[1], address(0), "Config1: token mismatch"); + assertEq(periodAmounts_[1], nativePeriodAmount, "Config1: period amount mismatch"); + assertEq(periodDurations_[1], nativePeriodDuration, "Config1: period duration mismatch"); + assertEq(startDates_[1], nativeStartDate, "Config1: start date mismatch"); + + // Third configuration: Second ERC20 config. + assertEq(tokens_[2], address(basicERC20), "Config2: token mismatch"); + assertEq(periodAmounts_[2], secondERC20Amount_, "Config2: period amount mismatch"); + assertEq(periodDurations_[2], secondERC20Duration_, "Config2: period duration mismatch"); + assertEq(startDates_[2], secondERC20Start_, "Config2: start date mismatch"); + } + + ////////////////////// Helper Functions ////////////////////// + function _encodeERC20Transfer(address _to, uint256 _amount) internal pure returns (bytes memory) { + return abi.encodeWithSelector(IERC20.transfer.selector, _to, _amount); + } + + function _encodeSingleExecution(address _target, uint256 _value, bytes memory _callData) internal pure returns (bytes memory) { + return abi.encodePacked(_target, _value, _callData); + } + + function _encodeNativeTransfer(address _target, uint256 _value) internal pure returns (bytes memory) { + // target (20 bytes) + value (32 bytes) + empty callData. + return abi.encodePacked(_target, _value, ""); + } + + function toDelegationArray(Delegation memory _delegation) internal pure returns (Delegation[] memory) { + Delegation[] memory arr_ = new Delegation[](1); + arr_[0] = _delegation; + return arr_; + } + + function _getEnforcer() internal view override returns (ICaveatEnforcer) { + return ICaveatEnforcer(address(multiTokenEnforcer)); + } +} From 091b711b09544a56446f639613c78c2cf4b0f9e4 Mon Sep 17 00:00:00 2001 From: hanzel98 Date: Thu, 27 Mar 2025 18:37:51 -0600 Subject: [PATCH 2/6] Added script and tests --- script/DeployCaveatEnforcers.s.sol | 8 +- test/enforcers/MultiTokenPeriodEnforcer.t.sol | 97 +++++++++++++++++++ 2 files changed, 101 insertions(+), 4 deletions(-) diff --git a/script/DeployCaveatEnforcers.s.sol b/script/DeployCaveatEnforcers.s.sol index 791db54d..2dd6a7ef 100644 --- a/script/DeployCaveatEnforcers.s.sol +++ b/script/DeployCaveatEnforcers.s.sol @@ -22,10 +22,10 @@ import { ERC1155BalanceGteEnforcer } from "../src/enforcers/ERC1155BalanceGteEnf import { ExactCalldataBatchEnforcer } from "../src/enforcers/ExactCalldataBatchEnforcer.sol"; import { ExactCalldataEnforcer } from "../src/enforcers/ExactCalldataEnforcer.sol"; import { ExactExecutionBatchEnforcer } from "../src/enforcers/ExactExecutionBatchEnforcer.sol"; -import { ExactCalldataBatchEnforcer } from "../src/enforcers/ExactCalldataBatchEnforcer.sol"; import { ExactExecutionEnforcer } from "../src/enforcers/ExactExecutionEnforcer.sol"; import { IdEnforcer } from "../src/enforcers/IdEnforcer.sol"; import { LimitedCallsEnforcer } from "../src/enforcers/LimitedCallsEnforcer.sol"; +import { MultiTokenPeriodEnforcer } from "../src/enforcers/MultiTokenPeriodEnforcer.sol"; import { NativeBalanceGteEnforcer } from "../src/enforcers/NativeBalanceGteEnforcer.sol"; import { NativeTokenPaymentEnforcer } from "../src/enforcers/NativeTokenPaymentEnforcer.sol"; import { NativeTokenPeriodTransferEnforcer } from "../src/enforcers/NativeTokenPeriodTransferEnforcer.sol"; @@ -110,9 +110,6 @@ contract DeployCaveatEnforcers is Script { deployedAddress = address(new ExactCalldataEnforcer{ salt: salt }()); console2.log("ExactCalldataEnforcer: %s", deployedAddress); - deployedAddress = address(new ExactCalldataBatchEnforcer{ salt: salt }()); - console2.log("ExactCalldataBatchEnforcer: %s", deployedAddress); - deployedAddress = address(new ExactExecutionBatchEnforcer{ salt: salt }()); console2.log("ExactExecutionBatchEnforcer: %s", deployedAddress); @@ -125,6 +122,9 @@ contract DeployCaveatEnforcers is Script { deployedAddress = address(new LimitedCallsEnforcer{ salt: salt }()); console2.log("LimitedCallsEnforcer: %s", deployedAddress); + deployedAddress = address(new MultiTokenPeriodEnforcer{ salt: salt }()); + console2.log("MultiTokenPeriodEnforcer: %s", deployedAddress); + deployedAddress = address(new NativeBalanceGteEnforcer{ salt: salt }()); console2.log("NativeBalanceGteEnforcer: %s", deployedAddress); diff --git a/test/enforcers/MultiTokenPeriodEnforcer.t.sol b/test/enforcers/MultiTokenPeriodEnforcer.t.sol index 2a0e0e67..31c846b0 100644 --- a/test/enforcers/MultiTokenPeriodEnforcer.t.sol +++ b/test/enforcers/MultiTokenPeriodEnforcer.t.sol @@ -16,6 +16,8 @@ contract MultiTokenPeriodEnforcerTest is CaveatEnforcerBaseTest { ////////////////////////////// State ////////////////////////////// MultiTokenPeriodEnforcer public multiTokenEnforcer; BasicERC20 public basicERC20; // Used for ERC20 tests + BasicERC20 public basicERC20B; + address public alice; address public bob; @@ -42,6 +44,7 @@ contract MultiTokenPeriodEnforcerTest is CaveatEnforcerBaseTest { alice = address(users.alice.deleGator); bob = address(users.bob.deleGator); basicERC20 = new BasicERC20(alice, "TestToken", "TT", 100 ether); + basicERC20B = new BasicERC20(alice, "TestTokenB", "TTB", 50 ether); erc20StartDate = block.timestamp; nativeStartDate = block.timestamp; @@ -673,6 +676,100 @@ contract MultiTokenPeriodEnforcerTest is CaveatEnforcerBaseTest { assertEq(startDates_[2], secondERC20Start_, "Config2: start date mismatch"); } + /// @notice Tests multiple tokens (three configurations) with different settings. + /// It deploys a second ERC20 token (Token B) and sets up: + /// - Token A: an ERC20 token (basicERC20) with its configuration. + /// - Token B: an ERC20 token (basicERC20B) with a different configuration. + /// - Token C: a native token (address(0)) with its configuration. + /// All start dates are set in the past so that the beforeHook calls succeed. + /// Then, it verifies that getAvailableAmount returns the expected available amounts. + function test_MultipleTokensBeforeHook() public { + vm.warp(10000); + + // Define configuration for Token A (ERC20 - basicERC20) + uint256 periodAmountA_ = 1000; + uint256 periodDurationA_ = 100; // 100 seconds + + // Deploy a second ERC20 token for Token B. + uint256 periodAmountB_ = 500; + uint256 periodDurationB_ = 50; // 50 seconds + + // Define configuration for Token C (Native: address(0)) + uint256 periodAmountC_ = 1 ether; + uint256 periodDurationC_ = 100; // 100 seconds + + // Build the _terms blob: concatenation of configurations for Token A, Token B, and Token C. + bytes memory terms_ = abi.encodePacked( + address(basicERC20), + periodAmountA_, + periodDurationA_, + block.timestamp - 100, // Token A start date, + address(basicERC20B), + periodAmountB_, + periodDurationB_, + block.timestamp - 50, // Token B start date + address(0), + periodAmountC_, + periodDurationC_, + block.timestamp - 10 // Token C (native) start date + ); + + { + // Call beforeHook for Token A (ERC20). + bytes memory callDataA_ = _encodeERC20Transfer(bob, 300); + bytes memory execDataA_ = _encodeSingleExecution(address(basicERC20), 0, callDataA_); + multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, execDataA_, dummyDelegationHash, address(0), redeemer); + } + + { + // Call beforeHook for Token B (ERC20). + bytes memory callDataB_ = _encodeERC20Transfer(bob, 200); + bytes memory execDataB_ = _encodeSingleExecution(address(basicERC20B), 0, callDataB_); + multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, execDataB_, dummyDelegationHash, address(0), redeemer); + } + { + // Call beforeHook for Token C (Native). + bytes memory execDataC_ = _encodeNativeTransfer(bob, 0.2 ether); + multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, execDataC_, dummyDelegationHash, address(0), redeemer); + } + { + // Verify available amounts for each token. + (uint256 availableA_,,) = + multiTokenEnforcer.getAvailableAmount(dummyDelegationHash, address(this), terms_, address(basicERC20)); + (uint256 availableB_,,) = + multiTokenEnforcer.getAvailableAmount(dummyDelegationHash, address(this), terms_, address(basicERC20B)); + (uint256 availableC_,,) = multiTokenEnforcer.getAvailableAmount(dummyDelegationHash, address(this), terms_, address(0)); + + assertEq(availableA_, periodAmountA_ - 300, "Token A available amount incorrect"); + assertEq(availableB_, periodAmountB_ - 200, "Token B available amount incorrect"); + assertEq(availableC_, periodAmountC_ - 0.2 ether, "Token C available amount incorrect"); + } + } + + /// @notice Tests getAvailableAmount for an ERC20 token when no beforeHook has been called, + /// so that the allowance is simulated from _terms. + function test_GetAvailableAmountWithoutBeforeHookErc20() public { + // Build an ERC20 configuration _terms blob. + bytes memory terms_ = abi.encodePacked(address(basicERC20), erc20PeriodAmount, erc20PeriodDuration, erc20StartDate); + + // Call getAvailableAmount without any prior beforeHook call. + (uint256 available_, bool isNewPeriod_, uint256 currentPeriod_) = + multiTokenEnforcer.getAvailableAmount(dummyDelegationHash, address(this), terms_, address(basicERC20)); + + // Since no transfer has occurred, available amount should equal the periodAmount. + assertEq(available_, erc20PeriodAmount, "Available amount should equal periodAmount when uninitialized"); + // If the block.timestamp is >= erc20StartDate, isNewPeriod_ should be true and currentPeriod_ computed properly. + if (block.timestamp >= erc20StartDate) { + assertTrue(isNewPeriod_, "isNewPeriod should be true after startDate"); + assertGt(currentPeriod_, 0, "Current period should be > 0 after startDate"); + } else { + // If before the start date, available should be 0. + assertEq(available_, 0, "Available should be 0 before start date"); + assertFalse(isNewPeriod_, "isNewPeriod should be false before start date"); + assertEq(currentPeriod_, 0, "Current period should be 0 before start date"); + } + } + ////////////////////// Helper Functions ////////////////////// function _encodeERC20Transfer(address _to, uint256 _amount) internal pure returns (bytes memory) { return abi.encodeWithSelector(IERC20.transfer.selector, _to, _amount); From f3e63a2b77e2317d177a68fc9143657d2273b6fe Mon Sep 17 00:00:00 2001 From: hanzel98 Date: Tue, 8 Apr 2025 09:42:17 -0600 Subject: [PATCH 3/6] Improved native token vs erc20 token flow --- src/enforcers/MultiTokenPeriodEnforcer.sol | 42 +++++++++++++------ test/enforcers/MultiTokenPeriodEnforcer.t.sol | 10 +++-- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/src/enforcers/MultiTokenPeriodEnforcer.sol b/src/enforcers/MultiTokenPeriodEnforcer.sol index 24fb9745..ffaf0f8d 100644 --- a/src/enforcers/MultiTokenPeriodEnforcer.sol +++ b/src/enforcers/MultiTokenPeriodEnforcer.sol @@ -6,6 +6,7 @@ import { ExecutionLib } from "@erc7579/lib/ExecutionLib.sol"; import { CaveatEnforcer } from "./CaveatEnforcer.sol"; import { ModeCode } from "../utils/Types.sol"; +import "forge-std/Test.sol"; /** * @title MultiTokenPeriodEnforcer @@ -89,6 +90,11 @@ contract MultiTokenPeriodEnforcer is CaveatEnforcer { // Not yet initialized; simulate using provided terms. (uint256 periodAmount_, uint256 periodDuration_, uint256 startDate_) = getTermsInfo(_terms, _token); + + uint256 gasleft_ = gasleft(); + getAllTermsInfo(_terms); + console2.log("gasleft():", uint256(gasleft_ - gasleft())); + PeriodicAllowance memory allowance_ = PeriodicAllowance({ periodAmount: periodAmount_, periodDuration: periodDuration_, @@ -146,14 +152,18 @@ contract MultiTokenPeriodEnforcer is CaveatEnforcer { returns (uint256 periodAmount_, uint256 periodDuration_, uint256 startDate_) { uint256 termsLength_ = _terms.length; - require(termsLength_ % 116 == 0 && termsLength_ != 0, "MultiTokenPeriodEnforcer:invalid-terms-length"); - uint256 numConfigs_ = termsLength_ / 116; - for (uint256 i = 0; i < numConfigs_; i++) { - uint256 offset_ = i * 116; + require(termsLength_ != 0 && termsLength_ % 116 == 0, "MultiTokenPeriodEnforcer:invalid-terms-length"); + + // Iterate over the byte offset directly in increments of 116 bytes. + for (uint256 offset_ = 0; offset_ < termsLength_; offset_ += 116) { + // Extract token address from the first 20 bytes. address token_ = address(bytes20(_terms[offset_:offset_ + 20])); if (token_ == _token) { + // Get periodAmount from the next 32 bytes. periodAmount_ = uint256(bytes32(_terms[offset_ + 20:offset_ + 52])); + // Get periodDuration from the subsequent 32 bytes. periodDuration_ = uint256(bytes32(_terms[offset_ + 52:offset_ + 84])); + // Get startDate from the final 32 bytes. startDate_ = uint256(bytes32(_terms[offset_ + 84:offset_ + 116])); return (periodAmount_, periodDuration_, startDate_); } @@ -188,11 +198,17 @@ contract MultiTokenPeriodEnforcer is CaveatEnforcer { periodDurations_ = new uint256[](numConfigs_); startDates_ = new uint256[](numConfigs_); - for (uint256 i = 0; i < numConfigs_; i++) { + // Loop over each configuration using its index. + for (uint256 i = 0; i < numConfigs_; ++i) { + // Calculate the starting offset for this configuration. uint256 offset_ = i * 116; + // Get the token address from the first 20 bytes. tokens_[i] = address(bytes20(_terms[offset_:offset_ + 20])); + // Get the periodAmount from the next 32 bytes. periodAmounts_[i] = uint256(bytes32(_terms[offset_ + 20:offset_ + 52])); + // Get the periodDuration from the following 32 bytes. periodDurations_[i] = uint256(bytes32(_terms[offset_ + 52:offset_ + 84])); + // Get the startDate from the final 32 bytes. startDates_[i] = uint256(bytes32(_terms[offset_ + 84:offset_ + 116])); } } @@ -202,7 +218,7 @@ contract MultiTokenPeriodEnforcer is CaveatEnforcer { /** * @notice Validates and consumes a transfer (native or ERC20) by ensuring the amount does not exceed the available limit. * @dev Decodes the execution data based on token type: - * - For native transfers (_token == address(0)): decodes (target, value, callData) and requires callData to be empty. + * - For native transfers (_token == address(0)): decodes (target, value, callData). * - For ERC20 transfers (_token != address(0)): decodes (target, , callData) and requires callData length to be 68 with a * valid IERC20.transfer selector. * @param _terms The concatenated configurations. @@ -223,16 +239,18 @@ contract MultiTokenPeriodEnforcer is CaveatEnforcer { // Decode _executionCallData using decodeSingle. (address target_, uint256 value_, bytes calldata callData_) = _executionCallData.decodeSingle(); - if (value_ > 0) { - // Native transfer. - token_ = address(0); - transferAmount_ = value_; - } else { + + if (callData_.length == 68) { // ERC20 transfer. - require(callData_.length == 68, "MultiTokenPeriodEnforcer:invalid-execution-length"); + require(value_ == 0, "MultiTokenPeriodEnforcer:invalid-value-in-erc20-transfer"); require(bytes4(callData_[0:4]) == IERC20.transfer.selector, "MultiTokenPeriodEnforcer:invalid-method"); token_ = target_; transferAmount_ = uint256(bytes32(callData_[36:68])); + } else { + // Native transfer. + require(value_ > 0, "MultiTokenPeriodEnforcer:invalid-zero-value-in-native-transfer"); + token_ = address(0); + transferAmount_ = value_; } // Retrieve the configuration for the token from _terms. diff --git a/test/enforcers/MultiTokenPeriodEnforcer.t.sol b/test/enforcers/MultiTokenPeriodEnforcer.t.sol index 31c846b0..17ae02ac 100644 --- a/test/enforcers/MultiTokenPeriodEnforcer.t.sol +++ b/test/enforcers/MultiTokenPeriodEnforcer.t.sol @@ -107,11 +107,15 @@ contract MultiTokenPeriodEnforcerTest is CaveatEnforcerBaseTest { multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, execData_, dummyDelegationHash, address(0), redeemer); } - /// @notice Reverts if the ERC20 execution call data length is invalid. + /// @notice Reverts if the ERC20 execution call data contains value function test_InvalidExecutionLengthErc20() public { bytes memory terms_ = abi.encodePacked(address(basicERC20), erc20PeriodAmount, erc20PeriodDuration, erc20StartDate); - bytes memory invalidExecData_ = new bytes(67); - vm.expectRevert("MultiTokenPeriodEnforcer:invalid-execution-length"); + + bytes memory callData_ = _encodeERC20Transfer(bob, 10 ether); + // Value greater than 0 + bytes memory invalidExecData_ = _encodeSingleExecution(address(basicERC20), 1, callData_); + + vm.expectRevert("MultiTokenPeriodEnforcer:invalid-value-in-erc20-transfer"); multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, invalidExecData_, dummyDelegationHash, address(0), redeemer); } From 6cdc3bc67f1c925e2130971a7f5ab6138883322c Mon Sep 17 00:00:00 2001 From: hanzel98 Date: Wed, 9 Apr 2025 12:54:24 -0600 Subject: [PATCH 4/6] chore: validate empty calldata for value greater than zero --- src/enforcers/MultiTokenPeriodEnforcer.sol | 5 +++- test/enforcers/MultiTokenPeriodEnforcer.t.sol | 26 +++++++++++++------ 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/enforcers/MultiTokenPeriodEnforcer.sol b/src/enforcers/MultiTokenPeriodEnforcer.sol index ffaf0f8d..912968dd 100644 --- a/src/enforcers/MultiTokenPeriodEnforcer.sol +++ b/src/enforcers/MultiTokenPeriodEnforcer.sol @@ -246,11 +246,14 @@ contract MultiTokenPeriodEnforcer is CaveatEnforcer { require(bytes4(callData_[0:4]) == IERC20.transfer.selector, "MultiTokenPeriodEnforcer:invalid-method"); token_ = target_; transferAmount_ = uint256(bytes32(callData_[36:68])); - } else { + } else if (callData_.length == 0) { // Native transfer. require(value_ > 0, "MultiTokenPeriodEnforcer:invalid-zero-value-in-native-transfer"); token_ = address(0); transferAmount_ = value_; + } else { + // If callData length is neither 68 nor 0, revert. + revert("MultiTokenPeriodEnforcer:invalid-call-data-length"); } // Retrieve the configuration for the token from _terms. diff --git a/test/enforcers/MultiTokenPeriodEnforcer.t.sol b/test/enforcers/MultiTokenPeriodEnforcer.t.sol index 17ae02ac..1fd59e18 100644 --- a/test/enforcers/MultiTokenPeriodEnforcer.t.sol +++ b/test/enforcers/MultiTokenPeriodEnforcer.t.sol @@ -437,19 +437,29 @@ contract MultiTokenPeriodEnforcerTest is CaveatEnforcerBaseTest { assertEq(currentPeriodAtBoundary_, 2, "Current period should be 2 at boundary"); } - /// @notice Tests native transfer behavior when non-empty callData is provided. - /// @dev Although the spec expects empty callData for native transfers, the current implementation does not enforce it. + /// @notice Reverts if the execution call data (decoded) is neither 68 bytes (ERC20) nor 0 bytes (native). + function test_InvalidCallDataLengthForERC20() public { + bytes memory terms_ = abi.encodePacked(address(basicERC20), erc20PeriodAmount, erc20PeriodDuration, erc20StartDate); + // Build a valid ERC20 callData (normally 68 bytes). + bytes memory validCallData_ = _encodeERC20Transfer(bob, 100); + // Append an extra byte to force an invalid length (e.g. 69 bytes). + bytes memory invalidCallData_ = abi.encodePacked(validCallData_, bytes1(0x00)); + // Build execution data with the invalid callData. + bytes memory execData_ = _encodeSingleExecution(address(basicERC20), 0, invalidCallData_); + vm.expectRevert("MultiTokenPeriodEnforcer:invalid-call-data-length"); + multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, execData_, dummyDelegationHash, address(0), redeemer); + } + + /// @notice Reverts if a native transfer is provided with non-empty callData. + /// @dev Updated to expect a revert since the new requirement is to allow only an empty callData for native transfers. function test_NativeTransferWithNonEmptyCallData() public { bytes memory terms_ = abi.encodePacked(address(0), nativePeriodAmount, nativePeriodDuration, nativeStartDate); - // Prepare execution data for a native transfer with non-empty callData. + // Prepare non-empty callData (which is not allowed for native transfers). bytes memory nonEmptyCallData_ = "non-empty"; + // Build execution data that contains the non-empty callData. bytes memory execData_ = abi.encodePacked(bob, nativePeriodAmount / 2, nonEmptyCallData_); - // This should pass and reduce the available allowance. + vm.expectRevert("MultiTokenPeriodEnforcer:invalid-call-data-length"); multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, execData_, dummyDelegationHash, address(0), redeemer); - (uint256 available_,,) = multiTokenEnforcer.getAvailableAmount(dummyDelegationHash, address(this), terms_, address(0)); - assertEq( - available_, nativePeriodAmount - (nativePeriodAmount / 2), "Available not reduced correctly with non-empty callData" - ); } /// @notice Tests that multiple beforeHook calls within the same period correctly accumulate the transferred amount. From 740e983119ba2b27ae2c93c3ffc2b4bdb2700196 Mon Sep 17 00:00:00 2001 From: hanzel98 Date: Thu, 10 Apr 2025 10:48:33 -0600 Subject: [PATCH 5/6] chore: improved docs, and gas --- script/verification/.gas-snapshot | 793 ++++++++++++++++++ src/enforcers/MultiTokenPeriodEnforcer.sol | 45 +- test/enforcers/MultiTokenPeriodEnforcer.t.sol | 63 ++ 3 files changed, 888 insertions(+), 13 deletions(-) create mode 100644 script/verification/.gas-snapshot diff --git a/script/verification/.gas-snapshot b/script/verification/.gas-snapshot new file mode 100644 index 00000000..2e81ade0 --- /dev/null +++ b/script/verification/.gas-snapshot @@ -0,0 +1,793 @@ +AllowedCalldataEnforcerTest:test_failsWithInvalidTermsLength() (gas: 22590) +AllowedCalldataEnforcerTest:test_methodCanBeCalledWithSpecificMetadata() (gas: 25470) +AllowedCalldataEnforcerTest:test_revertWithInvalidCallTypeMode() (gas: 14244) +AllowedCalldataEnforcerTest:test_revertWithInvalidExecutionMode() (gas: 14472) +AllowedCalldataEnforcerTest:test_singleMethodCanBeCalledWithEqualDynamicArrayParam() (gas: 29703) +AllowedCalldataEnforcerTest:test_singleMethodCanBeCalledWithEqualDynamicStringParam() (gas: 29052) +AllowedCalldataEnforcerTest:test_singleMethodCanBeCalledWithEqualParam() (gas: 22152) +AllowedCalldataEnforcerTest:test_singleMethodCanBeCalledWithEqualParamIntegration() (gas: 448602) +AllowedCalldataEnforcerTest:test_singleMethodCanNotBeCalledWithNonEqualParamIntegration() (gas: 271264) +AllowedCalldataEnforcerTest:test_singleMethodCanNotCalledWithInvalidTermsSize() (gas: 21401) +AllowedCalldataEnforcerTest:test_singleMethodCanNotCalledWithNonEqualParam() (gas: 22586) +AllowedMethodsEnforcerTest:test_getTermsInfoFailsForInvalidLength() (gas: 15335) +AllowedMethodsEnforcerTest:test_methodCanBeSingleMethodIntegration() (gas: 435585) +AllowedMethodsEnforcerTest:test_multiMethodCanBeCalled() (gas: 21183) +AllowedMethodsEnforcerTest:test_notAllow_invalidExecutionLength() (gas: 18679) +AllowedMethodsEnforcerTest:test_onlyApprovedMethodsCanBeCalled() (gas: 21723) +AllowedMethodsEnforcerTest:test_onlyApprovedMethodsCanBeCalledIntegration() (gas: 270576) +AllowedMethodsEnforcerTest:test_revertWithInvalidCallTypeMode() (gas: 14288) +AllowedMethodsEnforcerTest:test_revertWithInvalidExecutionMode() (gas: 14494) +AllowedMethodsEnforcerTest:test_singleMethodCanBeCalled() (gas: 19508) +AllowedTargetsEnforcerTest:testFToken1() (gas: 2428) +AllowedTargetsEnforcerTest:testFToken2() (gas: 2470) +AllowedTargetsEnforcerTest:test_getTermsInfoFailsForInvalidLength() (gas: 15377) +AllowedTargetsEnforcerTest:test_multiTargetCanBeCalled() (gas: 25156) +AllowedTargetsEnforcerTest:test_multiTargetCanBeCalledIntegration() (gas: 735529) +AllowedTargetsEnforcerTest:test_onlyApprovedMethodsCanBeCalledIntegration() (gas: 279584) +AllowedTargetsEnforcerTest:test_onlyApprovedTargetsCanBeCalled() (gas: 27657) +AllowedTargetsEnforcerTest:test_revertWithInvalidCallTypeMode() (gas: 14288) +AllowedTargetsEnforcerTest:test_revertWithInvalidExecutionMode() (gas: 14494) +AllowedTargetsEnforcerTest:test_singleTargetCanBeCalled() (gas: 19311) +ArgsEqualityCheckEnforcerTest:test_failToPasEnforcerWhenTermsAndArgsAreDifferent() (gas: 25197) +ArgsEqualityCheckEnforcerTest:test_passEnforcerWhenTermsEqualsArgs() (gas: 10971) +ArgsEqualityCheckEnforcerTest:test_revertWithInvalidExecutionMode() (gas: 14247) +BlockNumberEnforcerTest:test_getTermsInfoFailsForInvalidLength() (gas: 8942) +BlockNumberEnforcerTest:test_methodCanBeCalledAfterBlockNumber() (gas: 18390) +BlockNumberEnforcerTest:test_methodCanBeCalledAfterBlockNumberIntegration() (gas: 415592) +BlockNumberEnforcerTest:test_methodCanBeCalledBeforeBlockNumber() (gas: 18039) +BlockNumberEnforcerTest:test_methodCanBeCalledInsideBlockNumberRange() (gas: 18477) +BlockNumberEnforcerTest:test_methodFailsIfCalledAfterBlockNumber() (gas: 18852) +BlockNumberEnforcerTest:test_methodFailsIfCalledAfterBlockNumberRange() (gas: 19078) +BlockNumberEnforcerTest:test_methodFailsIfCalledBeforeBlockNumber() (gas: 18542) +BlockNumberEnforcerTest:test_methodFailsIfCalledBeforeBlockNumberRange() (gas: 18600) +BlockNumberEnforcerTest:test_revertWithInvalidExecutionMode() (gas: 14244) +CounterfactualAssetsTest:test_allow_createNftAndDelegateWithCaveatsAndRedelegate_offchain() (gas: 2904561) +CounterfactualAssetsTest:test_allow_createNftAndDelegateWithCaveats_offchain() (gas: 2845667) +CounterfactualAssetsTest:test_allow_createNftAndDelegate_offchain() (gas: 2790262) +DelegationChainMaxDepthTest:test_delegationChainDepthLimitedByGas() (gas: 3745558) +DelegationChainWithCaveatsTest:test_threeLevel_delegationChain_Fail_withCaveats() (gas: 384496) +DelegationChainWithCaveatsTest:test_threeLevel_delegationChain_withCaveats() (gas: 514899) +DelegationManagerTest:test_allow_anyDelegateReads() (gas: 5605) +DelegationManagerTest:test_allow_contractNameReads() (gas: 7296) +DelegationManagerTest:test_allow_contractVersionReads() (gas: 7280) +DelegationManagerTest:test_allow_domainHashReads() (gas: 9454) +DelegationManagerTest:test_allow_domainHashReadsWhenChainIdChanges() (gas: 12704) +DelegationManagerTest:test_allow_domainVersionReads() (gas: 7095) +DelegationManagerTest:test_allow_getDelegationHash() (gas: 15884) +DelegationManagerTest:test_allow_redeemBatchDelegation() (gas: 791298) +DelegationManagerTest:test_allow_redeemBatchDelegationWithPassthrough() (gas: 565613) +DelegationManagerTest:test_allow_redeemBatchWithEoaInSecondBatchDelegation() (gas: 856509) +DelegationManagerTest:test_allow_rootAuthorityReads() (gas: 5563) +DelegationManagerTest:test_notAllow_crossDelegationManagerReplays() (gas: 2641060) +DelegationManagerTest:test_notAllow_invalidSignatureReturns() (gas: 136520) +DelegationManagerTest:test_notAllow_invalidSignatureReverts() (gas: 146676) +DelegationManagerTest:test_notAllow_redeemLengthMismatching() (gas: 25720) +DelegationManagerTest:test_ownership_transferAndAcceptOwnership() (gas: 36096) +DelegationManagerTest:test_pausability_allowsOwnerToPause() (gas: 39584) +DelegationManagerTest:test_pausability_allowsOwnerToPauseRedemptions() (gas: 43652) +DelegationManagerTest:test_pausability_allowsOwnerToUnpause() (gas: 29358) +DelegationManagerTest:test_pausability_failsToPauseIfNotOwner() (gas: 17608) +DelegationManagerTest:test_pausability_failsToPauseWhenActivePause() (gas: 37313) +DelegationManagerTest:test_pausability_failsToPauseWhenActiveUnpause() (gas: 15097) +DelegationManagerTest:test_pausability_failsToUnpauseIfNotOwner() (gas: 41226) +DelegationManagerTest:test_pausability_validateInitialPauseState() (gas: 2373109) +DelegationManagerTest:test_revert_whenAnyCaveatHookFails() (gas: 943364) +DelegationMetaSwapAdapterForkTest:test_canSwapByDelegationsInForkErc20ToErc20() (gas: 10701636) +DelegationMetaSwapAdapterForkTest:test_canSwapByDelegationsInForkErc20ToNativeToken() (gas: 9891895) +DelegationMetaSwapAdapterForkTest:test_canSwapByDelegationsInForkNativeTokenToErc20() (gas: 10217015) +DelegationMetaSwapAdapterMockTest:test_canSwapByDelegationsMockErc20TokenFrom() (gas: 5186553) +DelegationMetaSwapAdapterMockTest:test_canSwapByDelegationsMockNativeTo() (gas: 4412390) +DelegationMetaSwapAdapterMockTest:test_canSwapByDelegationsMockNativeTokenFrom() (gas: 4327559) +DelegationMetaSwapAdapterMockTest:test_canTransferAndAcceptOwnership() (gas: 4439669) +DelegationMetaSwapAdapterMockTest:test_canUpdateAllowedAggregatorIds() (gas: 4445158) +DelegationMetaSwapAdapterMockTest:test_canUpdateAllowedTokens() (gas: 5059562) +DelegationMetaSwapAdapterMockTest:test_event_ChangedAggregatorIdStatus() (gas: 4460399) +DelegationMetaSwapAdapterMockTest:test_event_ChangedTokenStatus() (gas: 5060385) +DelegationMetaSwapAdapterMockTest:test_event_SentTokens_in_swapTokens() (gas: 4549729) +DelegationMetaSwapAdapterMockTest:test_event_constructor_SetDelegationManager_SetMetaSwap() (gas: 2001192) +DelegationMetaSwapAdapterMockTest:test_executeFromExecutor() (gas: 4457791) +DelegationMetaSwapAdapterMockTest:test_revert_acceptOwnership_ifNotPendingOwner() (gas: 4457886) +DelegationMetaSwapAdapterMockTest:test_revert_executeFromExecutor_ifNotDelegationManager() (gas: 4434040) +DelegationMetaSwapAdapterMockTest:test_revert_swapByDelegation_aggregatorIdNotAllowed() (gas: 4475867) +DelegationMetaSwapAdapterMockTest:test_revert_swapByDelegation_amountFromMismatch() (gas: 4480951) +DelegationMetaSwapAdapterMockTest:test_revert_swapByDelegation_emptyDelegations() (gas: 4443960) +DelegationMetaSwapAdapterMockTest:test_revert_swapByDelegation_identicalTokens() (gas: 4486446) +DelegationMetaSwapAdapterMockTest:test_revert_swapByDelegation_invalidSwapFunctionSelector() (gas: 4474566) +DelegationMetaSwapAdapterMockTest:test_revert_swapByDelegation_nonLeafDelegator() (gas: 4465454) +DelegationMetaSwapAdapterMockTest:test_revert_swapByDelegation_tokenFromMismatch() (gas: 4485126) +DelegationMetaSwapAdapterMockTest:test_revert_swapByDelegation_tokenFromNotAllowed() (gas: 4471712) +DelegationMetaSwapAdapterMockTest:test_revert_swapByDelegation_tokenToNotAllowed() (gas: 4471887) +DelegationMetaSwapAdapterMockTest:test_revert_swapTokens_ifNotSelf() (gas: 4431788) +DelegationMetaSwapAdapterMockTest:test_revert_swapTokens_insufficientTokens() (gas: 4439116) +DelegationMetaSwapAdapterMockTest:test_revert_transferOwnership_ifNotOwner() (gas: 4432828) +DelegationMetaSwapAdapterMockTest:test_revert_updateAllowedAggregatorIds_arrayLengthMismatch() (gas: 4433122) +DelegationMetaSwapAdapterMockTest:test_revert_updateAllowedAggregatorIds_arrayLengthMismatch_New() (gas: 4432760) +DelegationMetaSwapAdapterMockTest:test_revert_updateAllowedAggregatorIds_ifNotOwner() (gas: 4432849) +DelegationMetaSwapAdapterMockTest:test_revert_updateAllowedTokens_arrayLengthMismatch() (gas: 4432586) +DelegationMetaSwapAdapterMockTest:test_revert_updateAllowedTokens_arrayLengthMismatch_New() (gas: 4432222) +DelegationMetaSwapAdapterMockTest:test_revert_updateAllowedTokens_ifNotOwner() (gas: 4432565) +DelegationMetaSwapAdapterMockTest:test_revert_withdraw_failedNativeTokenTransfer() (gas: 4438076) +DelegationMetaSwapAdapterMockTest:test_revert_withdraw_ifNotOwner() (gas: 4431237) +DelegationMetaSwapAdapterMockTest:test_swapTokens_extraTokenFromSent() (gas: 4549709) +DelegationMetaSwapAdapterMockTest:test_withdraw() (gas: 4468521) +DelegationMetaSwapAdapterMockTest:test_withdraw_native() (gas: 4469517) +DeployedEnforcerTest:test_computesAPredictedAddress() (gas: 12714) +DeployedEnforcerTest:test_deploysIfNonExistent() (gas: 167853) +DeployedEnforcerTest:test_deploysIfNonExistentAndAllowsToUseItIntegration() (gas: 455185) +DeployedEnforcerTest:test_doesNotDeployIfExistent() (gas: 121192) +DeployedEnforcerTest:test_revertIfContractIsEmpty() (gas: 57924) +DeployedEnforcerTest:test_revertIfPredictedAddressDoesNotMatch() (gas: 114116) +DeployedEnforcerTest:test_revertIfTermsLengthIsInvalid() (gas: 26677) +DeployedEnforcerTest:test_revertWithInvalidExecutionMode() (gas: 14315) +DeployedEnforcerTest:test_revertsIfBytecodeDoesntExist() (gas: 113728) +EIP7702Staless_TestSuite_Test:test_allow_chainOfOffchainDelegationToDeleGators() (gas: 305603) +EIP7702Staless_TestSuite_Test:test_allow_chainOfOffchainDelegationToEoa() (gas: 152656) +EIP7702Staless_TestSuite_Test:test_allow_deleGatorInvokeOffchainDelegation() (gas: 260206) +EIP7702Staless_TestSuite_Test:test_allow_disableOffchainDelegation() (gas: 526207) +EIP7702Staless_TestSuite_Test:test_allow_eoaInvokeOffchainDelegation() (gas: 109673) +EIP7702Staless_TestSuite_Test:test_allow_eoaRedelegateOffchainDelegation() (gas: 142582) +EIP7702Staless_TestSuite_Test:test_allow_executeFromExecutor() (gas: 50076) +EIP7702Staless_TestSuite_Test:test_allow_executeFromExecutorUnsupportedExecType() (gas: 21101) +EIP7702Staless_TestSuite_Test:test_allow_executeFromExecutor_batch() (gas: 55616) +EIP7702Staless_TestSuite_Test:test_allow_getDelegationManager() (gas: 10354) +EIP7702Staless_TestSuite_Test:test_allow_getDeposit() (gas: 13633) +EIP7702Staless_TestSuite_Test:test_allow_getDomainValues() (gas: 12690) +EIP7702Staless_TestSuite_Test:test_allow_getEntryPoint() (gas: 10442) +EIP7702Staless_TestSuite_Test:test_allow_getNonce() (gas: 13918) +EIP7702Staless_TestSuite_Test:test_allow_getNonceWithKey() (gas: 14084) +EIP7702Staless_TestSuite_Test:test_allow_invokeOffchainDelegationWithCaveats() (gas: 316442) +EIP7702Staless_TestSuite_Test:test_allow_multiExecutionCombination_UserOp() (gas: 309718) +EIP7702Staless_TestSuite_Test:test_allow_multiExecutionDelegationClaim_Offchain_UserOp() (gas: 316942) +EIP7702Staless_TestSuite_Test:test_allow_multiExecution_UserOp() (gas: 203011) +EIP7702Staless_TestSuite_Test:test_allow_offchainOpenDelegation() (gas: 347238) +EIP7702Staless_TestSuite_Test:test_allow_offchainOpenDelegationRedelegation() (gas: 181917) +EIP7702Staless_TestSuite_Test:test_allow_receiveNativeToken() (gas: 14984) +EIP7702Staless_TestSuite_Test:test_allow_resetDisabledDelegation() (gas: 270431) +EIP7702Staless_TestSuite_Test:test_allow_singleExecution_UserOp() (gas: 196031) +EIP7702Staless_TestSuite_Test:test_allow_tryExecuteFromExecutor() (gas: 50176) +EIP7702Staless_TestSuite_Test:test_allow_tryExecuteFromExecutor_batch() (gas: 55773) +EIP7702Staless_TestSuite_Test:test_allow_tryMultiExecution_UserOp() (gas: 203190) +EIP7702Staless_TestSuite_Test:test_allow_trySingleExecution_UserOp() (gas: 196336) +EIP7702Staless_TestSuite_Test:test_allow_updatingOffchainDelegationDisabledStateWithStruct() (gas: 271835) +EIP7702Staless_TestSuite_Test:test_allow_withdrawDeposit() (gas: 244712) +EIP7702Staless_TestSuite_Test:test_emit_sentPrefund() (gas: 127824) +EIP7702Staless_TestSuite_Test:test_erc165_supportsInterface() (gas: 17732) +EIP7702Staless_TestSuite_Test:test_error_InvalidAuthority() (gas: 273078) +EIP7702Staless_TestSuite_Test:test_error_InvalidDelegate() (gas: 225976) +EIP7702Staless_TestSuite_Test:test_error_InvalidDelegator() (gas: 185094) +EIP7702Staless_TestSuite_Test:test_error_InvalidRootAuthority() (gas: 72488) +EIP7702Staless_TestSuite_Test:test_error_InvalidSignature() (gas: 235853) +EIP7702Staless_TestSuite_Test:test_error_InvalidSigner() (gas: 235649) +EIP7702Staless_TestSuite_Test:test_event_Deposited() (gas: 181469) +EIP7702Staless_TestSuite_Test:test_executesFirstRootAuthorityFound() (gas: 262741) +EIP7702Staless_TestSuite_Test:test_executionRevertsWithoutReason() (gas: 31500) +EIP7702Staless_TestSuite_Test:test_notAllow_callFromNonProxyAddress_IsValidSignature() (gas: 80858) +EIP7702Staless_TestSuite_Test:test_notAllow_callFromNonProxyAddress_ValidateUserOp() (gas: 82190) +EIP7702Staless_TestSuite_Test:test_notAllow_chainOfOffchainDelegationToDeleGators() (gas: 281063) +EIP7702Staless_TestSuite_Test:test_notAllow_delegatingForAnotherDelegator() (gas: 185126) +EIP7702Staless_TestSuite_Test:test_notAllow_delegationWithoutContactSignature() (gas: 44650) +EIP7702Staless_TestSuite_Test:test_notAllow_delegationWithoutEOASignature() (gas: 40688) +EIP7702Staless_TestSuite_Test:test_notAllow_disableAlreadyDisabledDelegation() (gas: 300669) +EIP7702Staless_TestSuite_Test:test_notAllow_disablingInvalidDelegation() (gas: 185238) +EIP7702Staless_TestSuite_Test:test_notAllow_enableAlreadyEnabledDelegation() (gas: 197088) +EIP7702Staless_TestSuite_Test:test_notAllow_eoaRedelegateOffchainDelegation() (gas: 85674) +EIP7702Staless_TestSuite_Test:test_notAllow_executeFromExecutorUnsupportedCallType() (gas: 20162) +EIP7702Staless_TestSuite_Test:test_notAllow_executeFromExecutorUnsupportedExecType_batch() (gas: 23451) +EIP7702Staless_TestSuite_Test:test_notAllow_invalidEntryPoint() (gas: 3168482) +EIP7702Staless_TestSuite_Test:test_notAllow_invalidRedeemDelegationData() (gas: 55656) +EIP7702Staless_TestSuite_Test:test_notAllow_invalidUserOpSignature() (gas: 130799) +EIP7702Staless_TestSuite_Test:test_notAllow_invokeOffchainDelegationToAnotherUser() (gas: 217768) +EIP7702Staless_TestSuite_Test:test_notAllow_multiExecutionUserOp() (gas: 190884) +EIP7702Staless_TestSuite_Test:test_notAllow_multiUnsupportedExecType_UserOp() (gas: 177758) +EIP7702Staless_TestSuite_Test:test_notAllow_nonceReuse() (gas: 206208) +EIP7702Staless_TestSuite_Test:test_notAllow_notDelegationManager() (gas: 19011) +EIP7702Staless_TestSuite_Test:test_notAllow_notEntryPoint() (gas: 14303) +EIP7702Staless_TestSuite_Test:test_notAllow_singleUnsupportedExecType_UserOp() (gas: 173214) +EIP7702Staless_TestSuite_Test:test_notAllow_unsupportedCallType_UserOp() (gas: 172365) +EIP7702Staless_TestSuite_Test:test_replayAttackAcrossEntryPoints() (gas: 5569165) +EIP7702StatelessDeleGatorTest:test_allow_getName() (gas: 9786) +EIP7702StatelessDeleGatorTest:test_allow_getVersion() (gas: 9822) +EIP7702StatelessDeleGatorTest:test_allow_invokeOffchainDelegation() (gas: 250108) +EIP7702StatelessDeleGatorTest:test_allow_signatureFromEOA() (gas: 22705) +EIP7702StatelessDeleGatorTest:test_emitEvent_DelegationManagerSet() (gas: 2287398) +EIP7702StatelessDeleGatorTest:test_emitEvent_EntryPointSet() (gas: 2287398) +EIP7702StatelessDeleGatorTest:test_execute_Execution_accessControl() (gas: 52970) +EIP7702StatelessDeleGatorTest:test_execute_ModeCode_accessControl() (gas: 56860) +EIP7702StatelessDeleGatorTest:test_notAllow_invalidSignatureFromInvalidEOA() (gas: 22724) +EIP7702StatelessDeleGatorTest:test_notAllow_invalidSignatureLength() (gas: 12874) +EIP7702StatelessDeleGatorTest:test_notAllow_toSetLongName() (gas: 85050) +EIP7702StatelessDeleGatorTest:test_notAllow_toSetLongVersion() (gas: 85047) +EIP7702StatelessDeleGatorTest:test_supportsExecutionMode() (gas: 28302) +ERC1155BalanceGteEnforcerTest:test_allow_ifBalanceIncreases() (gas: 153112) +ERC1155BalanceGteEnforcerTest:test_allow_withDifferentRecipients() (gas: 159877) +ERC1155BalanceGteEnforcerTest:test_decodedTheTerms() (gas: 14742) +ERC1155BalanceGteEnforcerTest:test_differentiateDelegationHashWithRecipient() (gas: 162584) +ERC1155BalanceGteEnforcerTest:test_invalid_decodedTheTerms() (gas: 17826) +ERC1155BalanceGteEnforcerTest:test_invalid_tokenAddress() (gas: 71821) +ERC1155BalanceGteEnforcerTest:test_notAllow_expectingOverflow() (gas: 93343) +ERC1155BalanceGteEnforcerTest:test_notAllow_ifBalanceDecreases() (gas: 137130) +ERC1155BalanceGteEnforcerTest:test_notAllow_insufficientIncrease() (gas: 108208) +ERC1155BalanceGteEnforcerTest:test_notAllow_reenterALockedEnforcer() (gas: 170467) +ERC1155BalanceGteEnforcerTest:test_notAllow_withPreExistingBalance() (gas: 137058) +ERC1155BalanceGteEnforcerTest:test_revertWithInvalidExecutionMode() (gas: 14435) +ERC20BalanceGteEnforcerTest:test_allow_ifBalanceIncreases() (gas: 149411) +ERC20BalanceGteEnforcerTest:test_allow_reuseDelegationWithDifferentRecipients() (gas: 151842) +ERC20BalanceGteEnforcerTest:test_decodedTheTerms() (gas: 12133) +ERC20BalanceGteEnforcerTest:test_invalid_decodedTheTerms() (gas: 18044) +ERC20BalanceGteEnforcerTest:test_invalid_tokenAddress() (gas: 64875) +ERC20BalanceGteEnforcerTest:test_notAllow_expectingOverflow() (gas: 85510) +ERC20BalanceGteEnforcerTest:test_notAllow_insufficientIncrease() (gas: 110737) +ERC20BalanceGteEnforcerTest:test_notAllow_noIncreaseToRecipient() (gas: 62566) +ERC20BalanceGteEnforcerTest:test_notAllow_reenterALockedEnforcer() (gas: 170372) +ERC20BalanceGteEnforcerTest:test_revertWithInvalidExecutionMode() (gas: 14457) +ERC20PeriodTransferEnforcerTest:testInvalidContract() (gas: 30433) +ERC20PeriodTransferEnforcerTest:testInvalidExecutionLength() (gas: 26549) +ERC20PeriodTransferEnforcerTest:testInvalidMethod() (gas: 28747) +ERC20PeriodTransferEnforcerTest:testInvalidTermsLength() (gas: 9604) +ERC20PeriodTransferEnforcerTest:testInvalidZeroPeriodAmount() (gas: 31109) +ERC20PeriodTransferEnforcerTest:testInvalidZeroPeriodDuration() (gas: 31085) +ERC20PeriodTransferEnforcerTest:testInvalidZeroStartDate() (gas: 31039) +ERC20PeriodTransferEnforcerTest:testMultipleTransfersInSamePeriod() (gas: 168393) +ERC20PeriodTransferEnforcerTest:testNewPeriodResetsAllowance() (gas: 170022) +ERC20PeriodTransferEnforcerTest:testSuccessfulTransferAndEvent() (gas: 154559) +ERC20PeriodTransferEnforcerTest:testTransferAmountExceeded() (gas: 154953) +ERC20PeriodTransferEnforcerTest:testTransferNotStarted() (gas: 31123) +ERC20PeriodTransferEnforcerTest:test_getAvailableAmountSimulationBeforeInitialization() (gas: 35877) +ERC20PeriodTransferEnforcerTest:test_integration_MultipleDelegations() (gas: 713734) +ERC20PeriodTransferEnforcerTest:test_integration_NewPeriodReset() (gas: 595883) +ERC20PeriodTransferEnforcerTest:test_integration_OverTransferFails() (gas: 457722) +ERC20PeriodTransferEnforcerTest:test_integration_SuccessfulTransfer() (gas: 448365) +ERC20PeriodTransferEnforcerTest:test_revertWithInvalidCallTypeMode() (gas: 14247) +ERC20PeriodTransferEnforcerTest:test_revertWithInvalidExecutionMode() (gas: 14454) +ERC20StreamingEnforcerTest:test_allowanceExceeded() (gas: 115279) +ERC20StreamingEnforcerTest:test_availableAtExactStartTime() (gas: 144624) +ERC20StreamingEnforcerTest:test_fullySpentCannotTransferMore() (gas: 148230) +ERC20StreamingEnforcerTest:test_getAvailableAmountBeforeStartTime() (gas: 127194) +ERC20StreamingEnforcerTest:test_getTermsInfoHappyPath() (gas: 10837) +ERC20StreamingEnforcerTest:test_getTermsInfoInvalidLength() (gas: 10038) +ERC20StreamingEnforcerTest:test_increasedSpentMapEvent() (gas: 146036) +ERC20StreamingEnforcerTest:test_invalidContract() (gas: 688469) +ERC20StreamingEnforcerTest:test_invalidExecutionLength() (gas: 19614) +ERC20StreamingEnforcerTest:test_invalidMaxAmount() (gas: 23262) +ERC20StreamingEnforcerTest:test_invalidMethodSelector() (gas: 23376) +ERC20StreamingEnforcerTest:test_invalidTermsLength() (gas: 21633) +ERC20StreamingEnforcerTest:test_invalidZeroStartTime() (gas: 23134) +ERC20StreamingEnforcerTest:test_linearStreamingWithInitialNonzero() (gas: 159458) +ERC20StreamingEnforcerTest:test_linearStreamingWithInitialZero() (gas: 126725) +ERC20StreamingEnforcerTest:test_nativeTokenStreamingIntegration_ExceedsAllowance() (gas: 583686) +ERC20StreamingEnforcerTest:test_nativeTokenStreamingIntegration_Success() (gas: 618768) +ERC20StreamingEnforcerTest:test_revertWithInvalidCallTypeMode() (gas: 14336) +ERC20StreamingEnforcerTest:test_revertWithInvalidExecutionMode() (gas: 14443) +ERC20StreamingEnforcerTest:test_streamingAllowanceDrainWithFailedTransfers() (gas: 686286) +ERC20TransferAmountEnforcerTest:test_methodFailsIfInvokesInvalidContract() (gas: 35257) +ERC20TransferAmountEnforcerTest:test_methodFailsIfInvokesInvalidMethod() (gas: 33457) +ERC20TransferAmountEnforcerTest:test_methodFailsIfInvokesInvalidTermsLength() (gas: 32585) +ERC20TransferAmountEnforcerTest:test_notAllow_invalidExecutionLength() (gas: 32847) +ERC20TransferAmountEnforcerTest:test_revertWithInvalidCallTypeMode() (gas: 14335) +ERC20TransferAmountEnforcerTest:test_revertWithInvalidExecutionMode() (gas: 14442) +ERC20TransferAmountEnforcerTest:test_transferFailsAboveAllowance() (gas: 1824133) +ERC20TransferAmountEnforcerTest:test_transferFailsButSpentLimitIncreases() (gas: 1402646) +ERC20TransferAmountEnforcerTest:test_transferFailsIfCalledAboveAllowance() (gas: 54035) +ERC20TransferAmountEnforcerTest:test_transferSucceedsIfCalledBelowAllowance() (gas: 59241) +ERC721BalanceGteEnforcerTest:test_allow_ifBalanceIncreases() (gas: 189682) +ERC721BalanceGteEnforcerTest:test_allow_withDifferentRecipients() (gas: 196632) +ERC721BalanceGteEnforcerTest:test_decodedTheTerms() (gas: 12161) +ERC721BalanceGteEnforcerTest:test_differentiateDelegationHashWithRecipient() (gas: 191064) +ERC721BalanceGteEnforcerTest:test_invalid_decodedTheTerms() (gas: 17819) +ERC721BalanceGteEnforcerTest:test_invalid_tokenAddress() (gas: 60669) +ERC721BalanceGteEnforcerTest:test_notAllow_expectingOverflow() (gas: 81112) +ERC721BalanceGteEnforcerTest:test_notAllow_ifBalanceDecreases() (gas: 159441) +ERC721BalanceGteEnforcerTest:test_notAllow_insufficientIncrease() (gas: 58214) +ERC721BalanceGteEnforcerTest:test_notAllow_reenterALockedEnforcer() (gas: 188962) +ERC721BalanceGteEnforcerTest:test_notAllow_withPreExistingBalance() (gas: 148820) +ERC721BalanceGteEnforcerTest:test_revertWithInvalidExecutionMode() (gas: 14457) +ERC721TransferEnforcerTest:test_invalidTermsLength() (gas: 11392) +ERC721TransferEnforcerTest:test_revertWithInvalidCallTypeMode() (gas: 14240) +ERC721TransferEnforcerTest:test_revertWithInvalidExecutionMode() (gas: 14447) +ERC721TransferEnforcerTest:test_unauthorizedSelector_wrongMethod() (gas: 20987) +ERC721TransferEnforcerTest:test_unauthorizedTransfer_wrongContract() (gas: 20648) +ERC721TransferEnforcerTest:test_unauthorizedTransfer_wrongSelectorLength() (gas: 19685) +ERC721TransferEnforcerTest:test_unauthorizedTransfer_wrongTokenId() (gas: 20957) +ERC721TransferEnforcerTest:test_validSafeTransferFrom3argsIntegration() (gas: 327033) +ERC721TransferEnforcerTest:test_validSafeTransferFrom4argsIntegration() (gas: 328172) +ERC721TransferEnforcerTest:test_validSafeTransferFrom_3args() (gas: 20405) +ERC721TransferEnforcerTest:test_validSafeTransferFrom_4args() (gas: 20682) +ERC721TransferEnforcerTest:test_validTransfer() (gas: 20455) +ERC721TransferEnforcerTest:test_validTransferIntegration() (gas: 324246) +EncoderLibTest:test_ShouldEncodeAnArrayOfCaveats() (gas: 10875) +EncoderLibTest:test_ShouldEncodeOneCaveat() (gas: 5517) +EncoderLibTest:test_shouldEncodeOneDelegation() (gas: 8797) +ExactCalldataBatchEnforcerTest:test_batchSizeMismatch() (gas: 29243) +ExactCalldataBatchEnforcerTest:test_exactCalldataFailsWhenMismatch() (gas: 33450) +ExactCalldataBatchEnforcerTest:test_exactCalldataMatches() (gas: 31813) +ExactCalldataBatchEnforcerTest:test_integration_AllowsBatchTokenTransfers() (gas: 203112) +ExactCalldataBatchEnforcerTest:test_integration_BlocksBatchWhenCalldataDiffers() (gas: 121084) +ExactCalldataBatchEnforcerTest:test_revertWithInvalidCallTypeMode() (gas: 18946) +ExactCalldataBatchEnforcerTest:test_revertWithInvalidExecutionMode() (gas: 19118) +ExactCalldataBatchEnforcerTest:test_singleModeReverts() (gas: 27890) +ExactCalldataEnforcerTest:test_batchEncodedExecutionReverts() (gas: 18233) +ExactCalldataEnforcerTest:test_emptyCalldataFailsWhenMismatch() (gas: 16658) +ExactCalldataEnforcerTest:test_emptyCalldataMatches() (gas: 16082) +ExactCalldataEnforcerTest:test_equalDynamicArrayParam() (gas: 17887) +ExactCalldataEnforcerTest:test_equalDynamicStringParam() (gas: 17370) +ExactCalldataEnforcerTest:test_exactCalldataFailsWhenMismatch() (gas: 21903) +ExactCalldataEnforcerTest:test_exactCalldataMatches() (gas: 21173) +ExactCalldataEnforcerTest:test_integration_AllowsETHTransferWhenEmptyCalldataMatches() (gas: 290094) +ExactCalldataEnforcerTest:test_integration_AllowsTokenTransferWhenCalldataMatches() (gas: 448769) +ExactCalldataEnforcerTest:test_integration_BlocksETHTransferWhenEmptyCalldataDiffers() (gas: 264094) +ExactCalldataEnforcerTest:test_integration_BlocksTokenTransferWhenCalldataDiffers() (gas: 271241) +ExactCalldataEnforcerTest:test_revertWithInvalidCallTypeMode() (gas: 14310) +ExactCalldataEnforcerTest:test_revertWithInvalidExecutionMode() (gas: 14405) +ExactExecutionBatchEnforcerTest:test_batchSizeMismatch() (gas: 29241) +ExactExecutionBatchEnforcerTest:test_exactExecutionFailsWhenCalldataDiffers() (gas: 35289) +ExactExecutionBatchEnforcerTest:test_exactExecutionFailsWhenTargetDiffers() (gas: 34924) +ExactExecutionBatchEnforcerTest:test_exactExecutionFailsWhenValueDiffers() (gas: 34890) +ExactExecutionBatchEnforcerTest:test_exactExecutionMatches() (gas: 33520) +ExactExecutionBatchEnforcerTest:test_integration_AllowsBatchTokenTransfers() (gas: 204798) +ExactExecutionBatchEnforcerTest:test_integration_BlocksBatchWhenExecutionDiffers() (gas: 122547) +ExactExecutionBatchEnforcerTest:test_revertWithInvalidCallTypeMode() (gas: 18946) +ExactExecutionBatchEnforcerTest:test_revertWithInvalidExecutionMode() (gas: 19118) +ExactExecutionBatchEnforcerTest:test_singleDefaultModeReverts() (gas: 27889) +ExactExecutionEnforcerTest:test_batchModeReverts() (gas: 21015) +ExactExecutionEnforcerTest:test_exactExecutionFailsWhenCalldataDiffers() (gas: 23136) +ExactExecutionEnforcerTest:test_exactExecutionFailsWhenTargetDiffers() (gas: 22284) +ExactExecutionEnforcerTest:test_exactExecutionFailsWhenValueDiffers() (gas: 22352) +ExactExecutionEnforcerTest:test_exactExecutionMatches() (gas: 22333) +ExactExecutionEnforcerTest:test_integration_AllowsETHTransferWhenExecutionMatches() (gas: 292919) +ExactExecutionEnforcerTest:test_integration_AllowsTokenTransferWhenExecutionMatches() (gas: 319216) +ExactExecutionEnforcerTest:test_integration_BlocksTokenTransferWhenExecutionDiffers() (gas: 271976) +ExactExecutionEnforcerTest:test_revertWithInvalidCallTypeMode() (gas: 14310) +ExactExecutionEnforcerTest:test_revertWithInvalidExecutionMode() (gas: 14405) +HybridDeleGator_Test:test_allow_addKey() (gas: 471196) +HybridDeleGator_Test:test_allow_erc173InterfaceId() (gas: 13070) +HybridDeleGator_Test:test_allow_removeKey() (gas: 685748) +HybridDeleGator_Test:test_allow_replaceEOAWithEOA() (gas: 176350) +HybridDeleGator_Test:test_allow_replaceEOAWithEOAAndP256() (gas: 279284) +HybridDeleGator_Test:test_allow_replaceEOAWithEOAAndP256WithOffchainDelegation() (gas: 743121) +HybridDeleGator_Test:test_allow_replaceEOAWithP256() (gas: 270845) +HybridDeleGator_Test:test_allow_signatureWithEOA() (gas: 38427) +HybridDeleGator_Test:test_allow_signatureWithP256() (gas: 249779) +HybridDeleGator_Test:test_allow_upgradingHybridDeleGator() (gas: 492794) +HybridDeleGator_Test:test_error_replacedSignersInputsMismatch() (gas: 38910) +HybridDeleGator_Test:test_error_replacedSignersToEmpty() (gas: 16163) +HybridDeleGator_Test:test_events_replacedSigners() (gas: 109615) +HybridDeleGator_Test:test_fails_signingWithWebAuthnWithInvalidType() (gas: 218084) +HybridDeleGator_Test:test_initialize_multipleP256OneEOA() (gas: 1032190) +HybridDeleGator_Test:test_initialize_multipleP256ZeroEOA() (gas: 1010044) +HybridDeleGator_Test:test_initialize_zeroP256OneEOA() (gas: 153865) +HybridDeleGator_Test:test_keyAdded_addKey() (gas: 471408) +HybridDeleGator_Test:test_keyAdded_initialize() (gas: 260088) +HybridDeleGator_Test:test_keyRemoved_removeKey() (gas: 685178) +HybridDeleGator_Test:test_notAllow_addKeyNotOnCurve() (gas: 17570) +HybridDeleGator_Test:test_notAllow_addingAnEmptyKey() (gas: 15286) +HybridDeleGator_Test:test_notAllow_addingExistingKey() (gas: 24822) +HybridDeleGator_Test:test_notAllow_invalidKeyOnDeploy() (gas: 99686) +HybridDeleGator_Test:test_notAllow_invalidSignatureLength() (gas: 22709) +HybridDeleGator_Test:test_notAllow_removingLastKeyViaEOA() (gas: 30431) +HybridDeleGator_Test:test_notAllow_removingLastKeyViaP256() (gas: 27142) +HybridDeleGator_Test:test_notAllow_removingNonExistantKey() (gas: 22302) +HybridDeleGator_Test:test_notAllow_renounceOwnership_Direct() (gas: 19397) +HybridDeleGator_Test:test_notAllow_transferOwnership_directNonOwner() (gas: 16550) +HybridDeleGator_Test:test_notAllow_transferOwnership_directOwner() (gas: 18560) +HybridDeleGator_Test:test_reinitialize_clearsAndSetSigners() (gas: 185870) +HybridDeleGator_Test:test_reinitialize_keepAndSetSigners() (gas: 204571) +HybridDeleGator_Test:test_removeP256KeyAndRearrangeStoredKeyIdHashes() (gas: 173310) +HybridDeleGator_Test:test_return_KeyIdHashes() (gas: 472478) +HybridDeleGator_Test:test_upgradeMultipleTimes() (gas: 533976) +HybridDeleGator_Test:test_upgradeWithoutStorageCleanup() (gas: 439532) +HybridDeleGator_TestSuite_EOA_Test:test_allow_chainOfOffchainDelegationToDeleGators() (gas: 321888) +HybridDeleGator_TestSuite_EOA_Test:test_allow_chainOfOffchainDelegationToEoa() (gas: 162787) +HybridDeleGator_TestSuite_EOA_Test:test_allow_deleGatorInvokeOffchainDelegation() (gas: 271414) +HybridDeleGator_TestSuite_EOA_Test:test_allow_disableOffchainDelegation() (gas: 542233) +HybridDeleGator_TestSuite_EOA_Test:test_allow_eoaInvokeOffchainDelegation() (gas: 114950) +HybridDeleGator_TestSuite_EOA_Test:test_allow_eoaRedelegateOffchainDelegation() (gas: 145931) +HybridDeleGator_TestSuite_EOA_Test:test_allow_executeFromExecutor() (gas: 52499) +HybridDeleGator_TestSuite_EOA_Test:test_allow_executeFromExecutorUnsupportedExecType() (gas: 23485) +HybridDeleGator_TestSuite_EOA_Test:test_allow_executeFromExecutor_batch() (gas: 58151) +HybridDeleGator_TestSuite_EOA_Test:test_allow_getDelegationManager() (gas: 12666) +HybridDeleGator_TestSuite_EOA_Test:test_allow_getDeposit() (gas: 15989) +HybridDeleGator_TestSuite_EOA_Test:test_allow_getDomainValues() (gas: 15193) +HybridDeleGator_TestSuite_EOA_Test:test_allow_getEntryPoint() (gas: 12776) +HybridDeleGator_TestSuite_EOA_Test:test_allow_getNonce() (gas: 16230) +HybridDeleGator_TestSuite_EOA_Test:test_allow_getNonceWithKey() (gas: 16399) +HybridDeleGator_TestSuite_EOA_Test:test_allow_invokeOffchainDelegationWithCaveats() (gas: 327910) +HybridDeleGator_TestSuite_EOA_Test:test_allow_multiExecutionCombination_UserOp() (gas: 321593) +HybridDeleGator_TestSuite_EOA_Test:test_allow_multiExecutionDelegationClaim_Offchain_UserOp() (gas: 331002) +HybridDeleGator_TestSuite_EOA_Test:test_allow_multiExecution_UserOp() (gas: 208709) +HybridDeleGator_TestSuite_EOA_Test:test_allow_offchainOpenDelegation() (gas: 359852) +HybridDeleGator_TestSuite_EOA_Test:test_allow_offchainOpenDelegationRedelegation() (gas: 192048) +HybridDeleGator_TestSuite_EOA_Test:test_allow_receiveNativeToken() (gas: 17268) +HybridDeleGator_TestSuite_EOA_Test:test_allow_resetDisabledDelegation() (gas: 279375) +HybridDeleGator_TestSuite_EOA_Test:test_allow_singleExecution_UserOp() (gas: 201510) +HybridDeleGator_TestSuite_EOA_Test:test_allow_tryExecuteFromExecutor() (gas: 52599) +HybridDeleGator_TestSuite_EOA_Test:test_allow_tryExecuteFromExecutor_batch() (gas: 58308) +HybridDeleGator_TestSuite_EOA_Test:test_allow_tryMultiExecution_UserOp() (gas: 208888) +HybridDeleGator_TestSuite_EOA_Test:test_allow_trySingleExecution_UserOp() (gas: 201815) +HybridDeleGator_TestSuite_EOA_Test:test_allow_updatingOffchainDelegationDisabledStateWithStruct() (gas: 280113) +HybridDeleGator_TestSuite_EOA_Test:test_allow_withdrawDeposit() (gas: 255086) +HybridDeleGator_TestSuite_EOA_Test:test_emit_sentPrefund() (gas: 133832) +HybridDeleGator_TestSuite_EOA_Test:test_erc165_supportsInterface() (gas: 21225) +HybridDeleGator_TestSuite_EOA_Test:test_error_InvalidAuthority() (gas: 288944) +HybridDeleGator_TestSuite_EOA_Test:test_error_InvalidDelegate() (gas: 231911) +HybridDeleGator_TestSuite_EOA_Test:test_error_InvalidDelegator() (gas: 190853) +HybridDeleGator_TestSuite_EOA_Test:test_error_InvalidRootAuthority() (gas: 77342) +HybridDeleGator_TestSuite_EOA_Test:test_error_InvalidSignature() (gas: 246654) +HybridDeleGator_TestSuite_EOA_Test:test_error_InvalidSigner() (gas: 246450) +HybridDeleGator_TestSuite_EOA_Test:test_event_Deposited() (gas: 187603) +HybridDeleGator_TestSuite_EOA_Test:test_executesFirstRootAuthorityFound() (gas: 278607) +HybridDeleGator_TestSuite_EOA_Test:test_executionRevertsWithoutReason() (gas: 33878) +HybridDeleGator_TestSuite_EOA_Test:test_notAllow_callFromNonProxyAddress_IsValidSignature() (gas: 80787) +HybridDeleGator_TestSuite_EOA_Test:test_notAllow_callFromNonProxyAddress_ValidateUserOp() (gas: 82119) +HybridDeleGator_TestSuite_EOA_Test:test_notAllow_chainOfOffchainDelegationToDeleGators() (gas: 296929) +HybridDeleGator_TestSuite_EOA_Test:test_notAllow_delegatingForAnotherDelegator() (gas: 190885) +HybridDeleGator_TestSuite_EOA_Test:test_notAllow_delegationWithoutContactSignature() (gas: 49482) +HybridDeleGator_TestSuite_EOA_Test:test_notAllow_delegationWithoutEOASignature() (gas: 43077) +HybridDeleGator_TestSuite_EOA_Test:test_notAllow_disableAlreadyDisabledDelegation() (gas: 308980) +HybridDeleGator_TestSuite_EOA_Test:test_notAllow_disablingInvalidDelegation() (gas: 190865) +HybridDeleGator_TestSuite_EOA_Test:test_notAllow_enableAlreadyEnabledDelegation() (gas: 202902) +HybridDeleGator_TestSuite_EOA_Test:test_notAllow_eoaRedelegateOffchainDelegation() (gas: 81232) +HybridDeleGator_TestSuite_EOA_Test:test_notAllow_executeFromExecutorUnsupportedCallType() (gas: 22546) +HybridDeleGator_TestSuite_EOA_Test:test_notAllow_executeFromExecutorUnsupportedExecType_batch() (gas: 25918) +HybridDeleGator_TestSuite_EOA_Test:test_notAllow_invalidEntryPoint() (gas: 3172216) +HybridDeleGator_TestSuite_EOA_Test:test_notAllow_invalidRedeemDelegationData() (gas: 55656) +HybridDeleGator_TestSuite_EOA_Test:test_notAllow_invalidUserOpSignature() (gas: 136084) +HybridDeleGator_TestSuite_EOA_Test:test_notAllow_invokeOffchainDelegationToAnotherUser() (gas: 223703) +HybridDeleGator_TestSuite_EOA_Test:test_notAllow_multiExecutionUserOp() (gas: 196589) +HybridDeleGator_TestSuite_EOA_Test:test_notAllow_multiUnsupportedExecType_UserOp() (gas: 183474) +HybridDeleGator_TestSuite_EOA_Test:test_notAllow_nonceReuse() (gas: 212613) +HybridDeleGator_TestSuite_EOA_Test:test_notAllow_notDelegationManager() (gas: 21392) +HybridDeleGator_TestSuite_EOA_Test:test_notAllow_notEntryPoint() (gas: 16564) +HybridDeleGator_TestSuite_EOA_Test:test_notAllow_singleUnsupportedExecType_UserOp() (gas: 178700) +HybridDeleGator_TestSuite_EOA_Test:test_notAllow_unsupportedCallType_UserOp() (gas: 177851) +HybridDeleGator_TestSuite_EOA_Test:test_replayAttackAcrossEntryPoints() (gas: 7421912) +HybridDeleGator_TestSuite_P256_Test:test_allow_chainOfOffchainDelegationToDeleGators() (gas: 950857) +HybridDeleGator_TestSuite_P256_Test:test_allow_chainOfOffchainDelegationToEoa() (gas: 583603) +HybridDeleGator_TestSuite_P256_Test:test_allow_deleGatorInvokeOffchainDelegation() (gas: 693334) +HybridDeleGator_TestSuite_P256_Test:test_allow_disableOffchainDelegation() (gas: 1579914) +HybridDeleGator_TestSuite_P256_Test:test_allow_eoaInvokeOffchainDelegation() (gas: 324228) +HybridDeleGator_TestSuite_P256_Test:test_allow_eoaRedelegateOffchainDelegation() (gas: 355209) +HybridDeleGator_TestSuite_P256_Test:test_allow_executeFromExecutor() (gas: 52499) +HybridDeleGator_TestSuite_P256_Test:test_allow_executeFromExecutorUnsupportedExecType() (gas: 23485) +HybridDeleGator_TestSuite_P256_Test:test_allow_executeFromExecutor_batch() (gas: 58151) +HybridDeleGator_TestSuite_P256_Test:test_allow_getDelegationManager() (gas: 12666) +HybridDeleGator_TestSuite_P256_Test:test_allow_getDeposit() (gas: 15989) +HybridDeleGator_TestSuite_P256_Test:test_allow_getDomainValues() (gas: 15193) +HybridDeleGator_TestSuite_P256_Test:test_allow_getEntryPoint() (gas: 12776) +HybridDeleGator_TestSuite_P256_Test:test_allow_getNonce() (gas: 16230) +HybridDeleGator_TestSuite_P256_Test:test_allow_getNonceWithKey() (gas: 16399) +HybridDeleGator_TestSuite_P256_Test:test_allow_invokeOffchainDelegationWithCaveats() (gas: 746878) +HybridDeleGator_TestSuite_P256_Test:test_allow_multiExecutionCombination_UserOp() (gas: 744109) +HybridDeleGator_TestSuite_P256_Test:test_allow_multiExecutionDelegationClaim_Offchain_UserOp() (gas: 955831) +HybridDeleGator_TestSuite_P256_Test:test_allow_multiExecution_UserOp() (gas: 418513) +HybridDeleGator_TestSuite_P256_Test:test_allow_offchainOpenDelegation() (gas: 989351) +HybridDeleGator_TestSuite_P256_Test:test_allow_offchainOpenDelegationRedelegation() (gas: 615201) +HybridDeleGator_TestSuite_P256_Test:test_allow_receiveNativeToken() (gas: 17268) +HybridDeleGator_TestSuite_P256_Test:test_allow_resetDisabledDelegation() (gas: 701139) +HybridDeleGator_TestSuite_P256_Test:test_allow_singleExecution_UserOp() (gas: 414340) +HybridDeleGator_TestSuite_P256_Test:test_allow_tryExecuteFromExecutor() (gas: 52599) +HybridDeleGator_TestSuite_P256_Test:test_allow_tryExecuteFromExecutor_batch() (gas: 58308) +HybridDeleGator_TestSuite_P256_Test:test_allow_tryMultiExecution_UserOp() (gas: 421112) +HybridDeleGator_TestSuite_P256_Test:test_allow_trySingleExecution_UserOp() (gas: 416460) +HybridDeleGator_TestSuite_P256_Test:test_allow_updatingOffchainDelegationDisabledStateWithStruct() (gas: 698164) +HybridDeleGator_TestSuite_P256_Test:test_allow_withdrawDeposit() (gas: 672698) +HybridDeleGator_TestSuite_P256_Test:test_emit_sentPrefund() (gas: 555549) +HybridDeleGator_TestSuite_P256_Test:test_erc165_supportsInterface() (gas: 21225) +HybridDeleGator_TestSuite_P256_Test:test_error_InvalidAuthority() (gas: 913238) +HybridDeleGator_TestSuite_P256_Test:test_error_InvalidDelegate() (gas: 446575) +HybridDeleGator_TestSuite_P256_Test:test_error_InvalidDelegator() (gas: 404371) +HybridDeleGator_TestSuite_P256_Test:test_error_InvalidRootAuthority() (gas: 289649) +HybridDeleGator_TestSuite_P256_Test:test_error_InvalidSignature() (gas: 462189) +HybridDeleGator_TestSuite_P256_Test:test_error_InvalidSigner() (gas: 461985) +HybridDeleGator_TestSuite_P256_Test:test_event_Deposited() (gas: 399911) +HybridDeleGator_TestSuite_P256_Test:test_executesFirstRootAuthorityFound() (gas: 907823) +HybridDeleGator_TestSuite_P256_Test:test_executionRevertsWithoutReason() (gas: 33878) +HybridDeleGator_TestSuite_P256_Test:test_notAllow_callFromNonProxyAddress_IsValidSignature() (gas: 82652) +HybridDeleGator_TestSuite_P256_Test:test_notAllow_callFromNonProxyAddress_ValidateUserOp() (gas: 83985) +HybridDeleGator_TestSuite_P256_Test:test_notAllow_chainOfOffchainDelegationToDeleGators() (gas: 925457) +HybridDeleGator_TestSuite_P256_Test:test_notAllow_delegatingForAnotherDelegator() (gas: 404320) +HybridDeleGator_TestSuite_P256_Test:test_notAllow_delegationWithoutContactSignature() (gas: 49482) +HybridDeleGator_TestSuite_P256_Test:test_notAllow_delegationWithoutEOASignature() (gas: 43077) +HybridDeleGator_TestSuite_P256_Test:test_notAllow_disableAlreadyDisabledDelegation() (gas: 730192) +HybridDeleGator_TestSuite_P256_Test:test_notAllow_disablingInvalidDelegation() (gas: 403778) +HybridDeleGator_TestSuite_P256_Test:test_notAllow_enableAlreadyEnabledDelegation() (gas: 413967) +HybridDeleGator_TestSuite_P256_Test:test_notAllow_eoaRedelegateOffchainDelegation() (gas: 83094) +HybridDeleGator_TestSuite_P256_Test:test_notAllow_executeFromExecutorUnsupportedCallType() (gas: 22546) +HybridDeleGator_TestSuite_P256_Test:test_notAllow_executeFromExecutorUnsupportedExecType_batch() (gas: 25918) +HybridDeleGator_TestSuite_P256_Test:test_notAllow_invalidEntryPoint() (gas: 3175946) +HybridDeleGator_TestSuite_P256_Test:test_notAllow_invalidRedeemDelegationData() (gas: 57518) +HybridDeleGator_TestSuite_P256_Test:test_notAllow_invalidUserOpSignature() (gas: 136402) +HybridDeleGator_TestSuite_P256_Test:test_notAllow_invokeOffchainDelegationToAnotherUser() (gas: 438367) +HybridDeleGator_TestSuite_P256_Test:test_notAllow_multiExecutionUserOp() (gas: 410711) +HybridDeleGator_TestSuite_P256_Test:test_notAllow_multiUnsupportedExecType_UserOp() (gas: 396303) +HybridDeleGator_TestSuite_P256_Test:test_notAllow_nonceReuse() (gas: 627073) +HybridDeleGator_TestSuite_P256_Test:test_notAllow_notDelegationManager() (gas: 21392) +HybridDeleGator_TestSuite_P256_Test:test_notAllow_notEntryPoint() (gas: 16564) +HybridDeleGator_TestSuite_P256_Test:test_notAllow_singleUnsupportedExecType_UserOp() (gas: 392739) +HybridDeleGator_TestSuite_P256_Test:test_notAllow_unsupportedCallType_UserOp() (gas: 393788) +HybridDeleGator_TestSuite_P256_Test:test_replayAttackAcrossEntryPoints() (gas: 7839340) +IdEnforcerEnforcerTest:testFToken1() (gas: 2405) +IdEnforcerEnforcerTest:test_blocksDelegationWithRepeatedNonce() (gas: 54575) +IdEnforcerEnforcerTest:test_decodedTheTerms() (gas: 6322) +IdEnforcerEnforcerTest:test_methodFailsIfCalledWithInvalidInputTerms() (gas: 18534) +IdEnforcerEnforcerTest:test_methodFailsIfNonceAlreadyUsed() (gas: 438760) +IdEnforcerEnforcerTest:test_revertWithInvalidExecutionMode() (gas: 14270) +InviteTest:test_createADeleGatorForBobAndDelegate() (gas: 596475) +InviteTest:test_createADeleGatorForBobAndSend() (gas: 439112) +LimitedCallsEnforcerTest:test_methodCanBeCalledBelowLimitNumber() (gas: 56429) +LimitedCallsEnforcerTest:test_methodFailsAboveLimitIntegration() (gas: 442856) +LimitedCallsEnforcerTest:test_methodFailsIfCalledAboveLimitNumber() (gas: 57086) +LimitedCallsEnforcerTest:test_methodFailsIfCalledWithInvalidInputTerms() (gas: 16607) +LimitedCallsEnforcerTest:test_revertWithInvalidExecutionMode() (gas: 14334) +MixedAuthorityDelegationTest:test_SingleDelegation_MixedAuthorityDelegationTest() (gas: 482308) +MixedAuthorityDelegationTest:test_delegationChain_MixedAuthorityDelegationTest() (gas: 1159604) +MultiSigDeleGatorTest:test_DelegationManagerSetEvent() (gas: 3489871) +MultiSigDeleGatorTest:test_ImplementationUsesMaxThreshold() (gas: 7695) +MultiSigDeleGatorTest:test_InitializedImplementationEvent() (gas: 3489721) +MultiSigDeleGatorTest:test_InitializedSignersEvents() (gas: 334418) +MultiSigDeleGatorTest:test_ReinitializedSignersEvents() (gas: 140800) +MultiSigDeleGatorTest:test_allow_addSigner() (gas: 81754) +MultiSigDeleGatorTest:test_allow_deploySCAWithInitCode() (gas: 425706) +MultiSigDeleGatorTest:test_allow_getMaxSigners() (gas: 10562) +MultiSigDeleGatorTest:test_allow_getSignersCount() (gas: 12635) +MultiSigDeleGatorTest:test_allow_invokeOffchainDelegationWithMultipleSigners() (gas: 297389) +MultiSigDeleGatorTest:test_allow_removeSigner() (gas: 220292) +MultiSigDeleGatorTest:test_allow_replaceSigner() (gas: 221947) +MultiSigDeleGatorTest:test_allow_updateMultiSigParameters_base() (gas: 550681) +MultiSigDeleGatorTest:test_allow_updatingThreshold() (gas: 81381) +MultiSigDeleGatorTest:test_error_Init_AlreadyASigner() (gas: 164432) +MultiSigDeleGatorTest:test_error_Init_InvalidSignerAddress() (gas: 95471) +MultiSigDeleGatorTest:test_error_Init_InvalidSignersLength() (gas: 264902) +MultiSigDeleGatorTest:test_error_Init_InvalidThreshold() (gas: 182364) +MultiSigDeleGatorTest:test_error_Init_thresholdGreaterThanSigners() (gas: 119605) +MultiSigDeleGatorTest:test_error_updateSigParamatersAlreadyASigner() (gas: 71738) +MultiSigDeleGatorTest:test_error_updateSigParamatersContractNewSigner() (gas: 24856) +MultiSigDeleGatorTest:test_error_updateSigParamatersZeroNewSigner() (gas: 20042) +MultiSigDeleGatorTest:test_error_updateSigParametersInvalidThreshold() (gas: 17312) +MultiSigDeleGatorTest:test_notAllow_addSigner() (gas: 1579266) +MultiSigDeleGatorTest:test_notAllow_invalidSignatureLength() (gas: 163444) +MultiSigDeleGatorTest:test_notAllow_invalidSigners() (gas: 177160) +MultiSigDeleGatorTest:test_notAllow_removeSigner() (gas: 111132) +MultiSigDeleGatorTest:test_notAllow_replaceSigner() (gas: 53514) +MultiSigDeleGatorTest:test_notAllow_signerReuse() (gas: 166518) +MultiSigDeleGatorTest:test_notAllow_updateMultiSigParameters_access() (gas: 37220) +MultiSigDeleGatorTest:test_notAllow_updateMultiSigParameters_maxNumberOfSigners() (gas: 31047) +MultiSigDeleGatorTest:test_notAllow_updateMultiSigParameters_threshold() (gas: 32095) +MultiSigDeleGatorTest:test_notAllow_updateMultiSigParameters_thresholdKeepingSigners() (gas: 26648) +MultiSigDeleGatorTest:test_notAllow_updatingThreshold() (gas: 86193) +MultiSigDeleGatorTest:test_reinitialize_clearsAndSetSigners() (gas: 128410) +MultiSigDeleGatorTest:test_reinitialize_keepAndSetSigners() (gas: 145921) +MultiSigDeleGatorTest:test_return_ifAnAddressIsAValidSigner() (gas: 20744) +MultiSigDeleGatorTest:test_unauthorizedReinitializeCall() (gas: 24170) +MultiSig_TestSuite_Test:test_allow_chainOfOffchainDelegationToDeleGators() (gas: 335693) +MultiSig_TestSuite_Test:test_allow_chainOfOffchainDelegationToEoa() (gas: 171981) +MultiSig_TestSuite_Test:test_allow_deleGatorInvokeOffchainDelegation() (gas: 280587) +MultiSig_TestSuite_Test:test_allow_disableOffchainDelegation() (gas: 557690) +MultiSig_TestSuite_Test:test_allow_eoaInvokeOffchainDelegation() (gas: 119513) +MultiSig_TestSuite_Test:test_allow_eoaRedelegateOffchainDelegation() (gas: 150495) +MultiSig_TestSuite_Test:test_allow_executeFromExecutor() (gas: 52433) +MultiSig_TestSuite_Test:test_allow_executeFromExecutorUnsupportedExecType() (gas: 23419) +MultiSig_TestSuite_Test:test_allow_executeFromExecutor_batch() (gas: 58085) +MultiSig_TestSuite_Test:test_allow_getDelegationManager() (gas: 12666) +MultiSig_TestSuite_Test:test_allow_getDeposit() (gas: 15989) +MultiSig_TestSuite_Test:test_allow_getDomainValues() (gas: 15193) +MultiSig_TestSuite_Test:test_allow_getEntryPoint() (gas: 12776) +MultiSig_TestSuite_Test:test_allow_getNonce() (gas: 16208) +MultiSig_TestSuite_Test:test_allow_getNonceWithKey() (gas: 16355) +MultiSig_TestSuite_Test:test_allow_invokeOffchainDelegationWithCaveats() (gas: 337084) +MultiSig_TestSuite_Test:test_allow_multiExecutionCombination_UserOp() (gas: 330768) +MultiSig_TestSuite_Test:test_allow_multiExecutionDelegationClaim_Offchain_UserOp() (gas: 341147) +MultiSig_TestSuite_Test:test_allow_multiExecution_UserOp() (gas: 213339) +MultiSig_TestSuite_Test:test_allow_offchainOpenDelegation() (gas: 370017) +MultiSig_TestSuite_Test:test_allow_offchainOpenDelegationRedelegation() (gas: 201242) +MultiSig_TestSuite_Test:test_allow_receiveNativeToken() (gas: 17268) +MultiSig_TestSuite_Test:test_allow_resetDisabledDelegation() (gas: 286835) +MultiSig_TestSuite_Test:test_allow_singleExecution_UserOp() (gas: 206140) +MultiSig_TestSuite_Test:test_allow_tryExecuteFromExecutor() (gas: 52533) +MultiSig_TestSuite_Test:test_allow_tryExecuteFromExecutor_batch() (gas: 58242) +MultiSig_TestSuite_Test:test_allow_tryMultiExecution_UserOp() (gas: 213518) +MultiSig_TestSuite_Test:test_allow_trySingleExecution_UserOp() (gas: 206445) +MultiSig_TestSuite_Test:test_allow_updatingOffchainDelegationDisabledStateWithStruct() (gas: 287573) +MultiSig_TestSuite_Test:test_allow_withdrawDeposit() (gas: 262503) +MultiSig_TestSuite_Test:test_emit_sentPrefund() (gas: 139520) +MultiSig_TestSuite_Test:test_erc165_supportsInterface() (gas: 20940) +MultiSig_TestSuite_Test:test_error_InvalidAuthority() (gas: 302816) +MultiSig_TestSuite_Test:test_error_InvalidDelegate() (gas: 238093) +MultiSig_TestSuite_Test:test_error_InvalidDelegator() (gas: 195440) +MultiSig_TestSuite_Test:test_error_InvalidRootAuthority() (gas: 81971) +MultiSig_TestSuite_Test:test_error_InvalidSignature() (gas: 255795) +MultiSig_TestSuite_Test:test_error_InvalidSigner() (gas: 255591) +MultiSig_TestSuite_Test:test_event_Deposited() (gas: 192300) +MultiSig_TestSuite_Test:test_executesFirstRootAuthorityFound() (gas: 292478) +MultiSig_TestSuite_Test:test_executionRevertsWithoutReason() (gas: 33812) +MultiSig_TestSuite_Test:test_notAllow_callFromNonProxyAddress_IsValidSignature() (gas: 84859) +MultiSig_TestSuite_Test:test_notAllow_callFromNonProxyAddress_ValidateUserOp() (gas: 86192) +MultiSig_TestSuite_Test:test_notAllow_chainOfOffchainDelegationToDeleGators() (gas: 310801) +MultiSig_TestSuite_Test:test_notAllow_delegatingForAnotherDelegator() (gas: 195472) +MultiSig_TestSuite_Test:test_notAllow_delegationWithoutContactSignature() (gas: 51664) +MultiSig_TestSuite_Test:test_notAllow_delegationWithoutEOASignature() (gas: 43055) +MultiSig_TestSuite_Test:test_notAllow_disableAlreadyDisabledDelegation() (gas: 317949) +MultiSig_TestSuite_Test:test_notAllow_disablingInvalidDelegation() (gas: 195474) +MultiSig_TestSuite_Test:test_notAllow_enableAlreadyEnabledDelegation() (gas: 209061) +MultiSig_TestSuite_Test:test_notAllow_eoaRedelegateOffchainDelegation() (gas: 82805) +MultiSig_TestSuite_Test:test_notAllow_executeFromExecutorUnsupportedCallType() (gas: 22480) +MultiSig_TestSuite_Test:test_notAllow_executeFromExecutorUnsupportedExecType_batch() (gas: 25841) +MultiSig_TestSuite_Test:test_notAllow_invalidEntryPoint() (gas: 3175346) +MultiSig_TestSuite_Test:test_notAllow_invalidRedeemDelegationData() (gas: 57228) +MultiSig_TestSuite_Test:test_notAllow_invalidUserOpSignature() (gas: 140616) +MultiSig_TestSuite_Test:test_notAllow_invokeOffchainDelegationToAnotherUser() (gas: 229885) +MultiSig_TestSuite_Test:test_notAllow_multiExecutionUserOp() (gas: 201219) +MultiSig_TestSuite_Test:test_notAllow_multiUnsupportedExecType_UserOp() (gas: 188093) +MultiSig_TestSuite_Test:test_notAllow_nonceReuse() (gas: 218391) +MultiSig_TestSuite_Test:test_notAllow_notDelegationManager() (gas: 21326) +MultiSig_TestSuite_Test:test_notAllow_notEntryPoint() (gas: 16653) +MultiSig_TestSuite_Test:test_notAllow_singleUnsupportedExecType_UserOp() (gas: 183330) +MultiSig_TestSuite_Test:test_notAllow_unsupportedCallType_UserOp() (gas: 182481) +MultiSig_TestSuite_Test:test_replayAttackAcrossEntryPoints() (gas: 6793793) +MultiTokenPeriodEnforcerTest:test_AllowanceIsolationByDelegationManager() (gas: 169528) +MultiTokenPeriodEnforcerTest:test_BoundaryPeriodCalculation() (gas: 164494) +MultiTokenPeriodEnforcerTest:test_GetAllTermsInfoInvalidTermsLength() (gas: 13149) +MultiTokenPeriodEnforcerTest:test_GetAllTermsInfoMixedTokens() (gas: 36711) +MultiTokenPeriodEnforcerTest:test_GetAllTermsInfoMultipleConfigurations() (gas: 32454) +MultiTokenPeriodEnforcerTest:test_GetAllTermsInfoSingleConfiguration() (gas: 22090) +MultiTokenPeriodEnforcerTest:test_GetAvailableAmountSimulationBeforeInitializationErc20() (gas: 47538) +MultiTokenPeriodEnforcerTest:test_GetAvailableAmountSimulationBeforeInitializationNative() (gas: 45198) +MultiTokenPeriodEnforcerTest:test_GetAvailableAmountWithoutBeforeHookErc20() (gas: 37466) +MultiTokenPeriodEnforcerTest:test_IntegrationCombinedTokens() (gas: 282941) +MultiTokenPeriodEnforcerTest:test_IntegrationMultipleDelegations() (gas: 716674) +MultiTokenPeriodEnforcerTest:test_InvalidCallDataLengthForERC20() (gas: 30211) +MultiTokenPeriodEnforcerTest:test_InvalidContractErc20() (gas: 30714) +MultiTokenPeriodEnforcerTest:test_InvalidExecutionLengthErc20() (gas: 29733) +MultiTokenPeriodEnforcerTest:test_InvalidMethodErc20() (gas: 27954) +MultiTokenPeriodEnforcerTest:test_InvalidTermsLength() (gas: 11965) +MultiTokenPeriodEnforcerTest:test_InvalidZeroPeriodAmountErc20() (gas: 32305) +MultiTokenPeriodEnforcerTest:test_InvalidZeroPeriodAmountNative() (gas: 28900) +MultiTokenPeriodEnforcerTest:test_InvalidZeroPeriodDurationErc20() (gas: 32379) +MultiTokenPeriodEnforcerTest:test_InvalidZeroPeriodDurationNative() (gas: 28882) +MultiTokenPeriodEnforcerTest:test_InvalidZeroStartDateErc20() (gas: 32265) +MultiTokenPeriodEnforcerTest:test_InvalidZeroStartDateNative() (gas: 28834) +MultiTokenPeriodEnforcerTest:test_MultipleBeforeHookCallsMaintainsState() (gas: 162137) +MultiTokenPeriodEnforcerTest:test_MultipleTokensBeforeHook() (gas: 405532) +MultiTokenPeriodEnforcerTest:test_MultipleTransfersInSamePeriodErc20() (gas: 172056) +MultiTokenPeriodEnforcerTest:test_MultipleTransfersInSamePeriodNative() (gas: 167926) +MultiTokenPeriodEnforcerTest:test_NativeTransferWithNonEmptyCallData() (gas: 27152) +MultiTokenPeriodEnforcerTest:test_NewPeriodResetsAllowanceErc20() (gas: 173362) +MultiTokenPeriodEnforcerTest:test_NewPeriodResetsAllowanceNative() (gas: 168122) +MultiTokenPeriodEnforcerTest:test_RevertWithInvalidCallTypeMode() (gas: 14349) +MultiTokenPeriodEnforcerTest:test_RevertWithInvalidExecutionMode() (gas: 14532) +MultiTokenPeriodEnforcerTest:test_SuccessfulTransferAndEventErc20() (gas: 155967) +MultiTokenPeriodEnforcerTest:test_SuccessfulTransferAndEventNative() (gas: 152370) +MultiTokenPeriodEnforcerTest:test_TermsMismatchAfterInitialization() (gas: 163116) +MultiTokenPeriodEnforcerTest:test_TokenConfigNotFound() (gas: 18780) +MultiTokenPeriodEnforcerTest:test_TransferAmountExceededErc20() (gas: 157009) +MultiTokenPeriodEnforcerTest:test_TransferAmountExceededNative() (gas: 152283) +MultiTokenPeriodEnforcerTest:test_TransferNotStartedErc20() (gas: 32351) +MultiTokenPeriodEnforcerTest:test_TransferNotStartedNative() (gas: 28924) +NativeBalanceGteEnforcerTest:test_allow_ifBalanceIncreases() (gas: 89780) +NativeBalanceGteEnforcerTest:test_decodedTheTerms() (gas: 9363) +NativeBalanceGteEnforcerTest:test_invalid_decodedTheTerms() (gas: 15187) +NativeBalanceGteEnforcerTest:test_notAllow_expectingOverflow() (gas: 63428) +NativeBalanceGteEnforcerTest:test_notAllow_insufficientIncrease() (gas: 67070) +NativeBalanceGteEnforcerTest:test_notAllow_reenterALockedEnforcer() (gas: 104252) +NativeBalanceGteEnforcerTest:test_revertWithInvalidExecutionMode() (gas: 14293) +NativeTokenPaymentEnforcerTest:test_allowsRedelegationAddingExtraCosts() (gas: 732792) +NativeTokenPaymentEnforcerTest:test_chargePaymentFromAllowance() (gas: 1217483) +NativeTokenPaymentEnforcerTest:test_decodesTheTerms() (gas: 9248) +NativeTokenPaymentEnforcerTest:test_delegationPreventFrontRunningWithArgs() (gas: 1541651) +NativeTokenPaymentEnforcerTest:test_delegationPreventFrontRunningWithArgsCatchError() (gas: 1049900) +NativeTokenPaymentEnforcerTest:test_getTermsInfoFailsForInvalidLength() (gas: 9249) +NativeTokenPaymentEnforcerTest:test_onlyOverwriteAllowanceEnforcerArgs() (gas: 1328192) +NativeTokenPaymentEnforcerTest:test_paymentFailsIfAllowanceDelegationIsEmpty() (gas: 112758) +NativeTokenPaymentEnforcerTest:test_paymentFailsIfArgsEnforcerIsMissing() (gas: 875329) +NativeTokenPaymentEnforcerTest:test_readInitializationValues() (gas: 906664) +NativeTokenPaymentEnforcerTest:test_revertWithInvalidExecutionMode() (gas: 14500) +NativeTokenPaymentEnforcerTest:test_validationFailIfArgsEnforcerNotInFirstPlace() (gas: 824082) +NativeTokenPaymentEnforcerTest:test_validationFailWhenInvalidSender() (gas: 14699) +NativeTokenPaymentEnforcerTest:test_validationFailWithEmptyCaveatsInAllowanceDelegation() (gas: 382475) +NativeTokenPaymentEnforcerTest:test_validationFailWithInsufficientPayment() (gas: 1839369) +NativeTokenPaymentEnforcerTest:test_validationPassWithValidPayment() (gas: 964850) +NativeTokenPaymentEnforcerTest:test_validationPassWithValidRedelegationAllowance() (gas: 1009722) +NativeTokenPeriodTransferEnforcerTest:testInvalidTermsLength() (gas: 9435) +NativeTokenPeriodTransferEnforcerTest:testInvalidZeroPeriodAmount() (gas: 27321) +NativeTokenPeriodTransferEnforcerTest:testInvalidZeroPeriodDuration() (gas: 27322) +NativeTokenPeriodTransferEnforcerTest:testInvalidZeroStartDate() (gas: 27327) +NativeTokenPeriodTransferEnforcerTest:testMultipleTransfersInSamePeriod() (gas: 162731) +NativeTokenPeriodTransferEnforcerTest:testNewPeriodResetsAllowance() (gas: 164230) +NativeTokenPeriodTransferEnforcerTest:testSuccessfulTransferAndEvent() (gas: 150048) +NativeTokenPeriodTransferEnforcerTest:testTransferAmount_Exceeded() (gas: 149102) +NativeTokenPeriodTransferEnforcerTest:testTransferNotStarted() (gas: 27429) +NativeTokenPeriodTransferEnforcerTest:test_getAvailableAmountSimulationBeforeInitialization() (gas: 33038) +NativeTokenPeriodTransferEnforcerTest:test_integration_MultipleDelegations() (gas: 683447) +NativeTokenPeriodTransferEnforcerTest:test_integration_NewPeriodReset() (gas: 565582) +NativeTokenPeriodTransferEnforcerTest:test_integration_OverTransferFails() (gas: 425233) +NativeTokenPeriodTransferEnforcerTest:test_integration_SuccessfulTransfer() (gas: 417526) +NativeTokenPeriodTransferEnforcerTest:test_revertWithInvalidCallTypeMode() (gas: 14247) +NativeTokenPeriodTransferEnforcerTest:test_revertWithInvalidExecutionMode() (gas: 14465) +NativeTokenStreamingEnforcerTest:test_allowanceExceeded() (gas: 109094) +NativeTokenStreamingEnforcerTest:test_availableAtExactStartTime() (gas: 138173) +NativeTokenStreamingEnforcerTest:test_fullySpentCannotTransferMore() (gas: 139871) +NativeTokenStreamingEnforcerTest:test_getAvailableAmountBeforeStartTime() (gas: 121009) +NativeTokenStreamingEnforcerTest:test_getTermsInfoHappyPath() (gas: 8116) +NativeTokenStreamingEnforcerTest:test_getTermsInfoInvalidLength() (gas: 9918) +NativeTokenStreamingEnforcerTest:test_increasedSpentMapEvent() (gas: 139183) +NativeTokenStreamingEnforcerTest:test_invalidMaxAmount() (gas: 17595) +NativeTokenStreamingEnforcerTest:test_invalidTermsLength() (gas: 16563) +NativeTokenStreamingEnforcerTest:test_invalidZeroStartTime() (gas: 17575) +NativeTokenStreamingEnforcerTest:test_linearStreamingWithInitialNonzero() (gas: 150694) +NativeTokenStreamingEnforcerTest:test_linearStreamingWithInitialZero() (gas: 120353) +NativeTokenStreamingEnforcerTest:test_nativeTokenStreamingIntegration_ExceedsAllowance() (gas: 529506) +NativeTokenStreamingEnforcerTest:test_nativeTokenStreamingIntegration_Success() (gas: 567401) +NativeTokenStreamingEnforcerTest:test_revertWithInvalidCallTypeMode() (gas: 14314) +NativeTokenStreamingEnforcerTest:test_revertWithInvalidExecutionMode() (gas: 14432) +NativeTokenTransferAmountEnforcerTest:test_decodesTheTerms() (gas: 6341) +NativeTokenTransferAmountEnforcerTest:test_revertWithInvalidCallTypeMode() (gas: 14313) +NativeTokenTransferAmountEnforcerTest:test_revertWithInvalidExecutionMode() (gas: 14442) +NativeTokenTransferAmountEnforcerTest:test_transferFailsIfAllowanceExceeded() (gas: 50159) +NativeTokenTransferAmountEnforcerTest:test_transferSucceedsIfCalledBelowAllowance() (gas: 55352) +NativeTokenTransferAmountEnforcerTest:test_transferSucceedsIfCalledBelowAllowanceMultipleCalls() (gas: 77525) +NonceEnforcerTest:test_allow_incrementingId() (gas: 42268) +NonceEnforcerTest:test_allow_validId() (gas: 27093) +NonceEnforcerTest:test_decodedTheTerms() (gas: 9309) +NonceEnforcerTest:test_invalid_decodedTheTerms() (gas: 12688) +NonceEnforcerTest:test_notAllow_invalidId() (gas: 58175) +NonceEnforcerTest:test_revertWithInvalidExecutionMode() (gas: 14342) +OwnershipTransferEnforcerTest:test_allow_validOwnershipTransfer() (gas: 210793) +OwnershipTransferEnforcerTest:test_decodedTheTerms() (gas: 8752) +OwnershipTransferEnforcerTest:test_invalid_termsLength() (gas: 11559) +OwnershipTransferEnforcerTest:test_notAllow_invalidExecutionLength() (gas: 138172) +OwnershipTransferEnforcerTest:test_notAllow_invalidMethod() (gas: 165750) +OwnershipTransferEnforcerTest:test_notAllow_invalidTargetContract() (gas: 205735) +OwnershipTransferEnforcerTest:test_revertWithInvalidCallTypeMode() (gas: 14313) +OwnershipTransferEnforcerTest:test_revertWithInvalidExecutionMode() (gas: 14420) +PasswordEnforcerTest:test_revertWithInvalidExecutionMode() (gas: 14299) +PasswordEnforcerTest:test_userInputCorrectArgsWorks() (gas: 17742) +PasswordEnforcerTest:test_userInputCorrectArgsWorksWithOffchainDelegation() (gas: 297771) +PasswordEnforcerTest:test_userInputIncorrectArgs() (gas: 18241) +PasswordEnforcerTest:test_userInputIncorrectArgsWithOffchainDelegation() (gas: 267229) +PasswordEnforcerTest:test_userInputIncorrectArgsWorksWithOffchainDelegation() (gas: 257028) +RedeemerEnforcerTest:test_decodeTermsInfo() (gas: 13683) +RedeemerEnforcerTest:test_getTermsInfoFailsForInvalidLength() (gas: 11298) +RedeemerEnforcerTest:test_revertWithInvalidExecutionMode() (gas: 14364) +RedeemerEnforcerTest:test_revertWithInvalidRedeemer() (gas: 23185) +RedeemerEnforcerTest:test_revertWithInvalidTerms() (gas: 20048) +RedeemerEnforcerTest:test_validMultipleRedeemersCanExecute() (gas: 28185) +RedeemerEnforcerTest:test_validSingleRedeemerCanExecute() (gas: 20668) +RedemptionBatchSizeLimitsTest:test_batchSizeLimits() (gas: 22237066) +RedemptionBatchSizeLimitsTest:test_batchSizeWithCaveats() (gas: 5524303) +RevokeDelegationMidChainTest:test_delegationChain_RevocationMidChain() (gas: 1700525) +SpecificActionERC20TransferBatchEnforcerTest:test_allow_specificActionERC20TransferBatch() (gas: 217391) +SpecificActionERC20TransferBatchEnforcerTest:test_multipleDelegationsAllowed() (gas: 90081) +SpecificActionERC20TransferBatchEnforcerTest:test_revertOnDelegationReuse() (gas: 60667) +SpecificActionERC20TransferBatchEnforcerTest:test_revertWithInvalidBatchSize() (gas: 48125) +SpecificActionERC20TransferBatchEnforcerTest:test_revertWithInvalidCallTypeMode() (gas: 25579) +SpecificActionERC20TransferBatchEnforcerTest:test_revertWithInvalidExecutionMode() (gas: 14515) +SpecificActionERC20TransferBatchEnforcerTest:test_revertWithInvalidFirstTransactionCalldata() (gas: 51963) +SpecificActionERC20TransferBatchEnforcerTest:test_revertWithInvalidFirstTransactionTarget() (gas: 51043) +SpecificActionERC20TransferBatchEnforcerTest:test_revertWithInvalidFirstTransactionValue() (gas: 51059) +SpecificActionERC20TransferBatchEnforcerTest:test_revertWithInvalidSecondTransactionAmount() (gas: 54843) +SpecificActionERC20TransferBatchEnforcerTest:test_revertWithInvalidSecondTransactionCalldataLength() (gas: 52727) +SpecificActionERC20TransferBatchEnforcerTest:test_revertWithInvalidSecondTransactionRecipient() (gas: 54030) +SpecificActionERC20TransferBatchEnforcerTest:test_revertWithInvalidSecondTransactionSelector() (gas: 53485) +SpecificActionERC20TransferBatchEnforcerTest:test_revertWithInvalidSecondTransactionTarget() (gas: 52198) +SpecificActionERC20TransferBatchEnforcerTest:test_revertWithInvalidSecondTransactionValue() (gas: 52233) +SpecificActionERC20TransferBatchEnforcerTest:test_revertWithInvalidTermsLength() (gas: 12261) +SpecificActionERC20TransferBatchEnforcerTest:test_validBatchExecution() (gas: 60307) +StorageUtilsLibTest:testToBool() (gas: 11185) +TimestampEnforcerTest:test_methodCanBeCalledAfterTimestamp() (gas: 18416) +TimestampEnforcerTest:test_methodCanBeCalledAfterTimestampIntegration() (gas: 415874) +TimestampEnforcerTest:test_methodCanBeCalledBeforeTimestamp() (gas: 17995) +TimestampEnforcerTest:test_methodCanBeCalledInsideTimestampRange() (gas: 18527) +TimestampEnforcerTest:test_methodFailsIfCalledAfterTimestamp() (gas: 18950) +TimestampEnforcerTest:test_methodFailsIfCalledAfterTimestampRange() (gas: 19235) +TimestampEnforcerTest:test_methodFailsIfCalledBeforeTimestampRange() (gas: 18621) +TimestampEnforcerTest:test_methodFailsIfCalledTimestamp() (gas: 18587) +TimestampEnforcerTest:test_methodFailsIfCalledWithInvalidInputTerms() (gas: 16441) +TimestampEnforcerTest:test_revertWithInvalidExecutionMode() (gas: 14244) +TypehashTest:test_CaveatTypehash() (gas: 7615) +TypehashTest:test_DelegationTypehash() (gas: 18191) +TypehashTest:test_EIP712DomainTypehash() (gas: 9844) +ValueLteEnforcerTest:test_allow_decodeTerms() (gas: 9331) +ValueLteEnforcerTest:test_allow_valueLte() (gas: 17400) +ValueLteEnforcerTest:test_notAllow_decodeTerms() (gas: 14961) +ValueLteEnforcerTest:test_notAllow_valueGt() (gas: 16468) +ValueLteEnforcerTest:test_revertWithInvalidCallTypeMode() (gas: 14266) +ValueLteEnforcerTest:test_revertWithInvalidExecutionMode() (gas: 14472) \ No newline at end of file diff --git a/src/enforcers/MultiTokenPeriodEnforcer.sol b/src/enforcers/MultiTokenPeriodEnforcer.sol index 912968dd..dca7268b 100644 --- a/src/enforcers/MultiTokenPeriodEnforcer.sol +++ b/src/enforcers/MultiTokenPeriodEnforcer.sol @@ -6,7 +6,6 @@ import { ExecutionLib } from "@erc7579/lib/ExecutionLib.sol"; import { CaveatEnforcer } from "./CaveatEnforcer.sol"; import { ModeCode } from "../utils/Types.sol"; -import "forge-std/Test.sol"; /** * @title MultiTokenPeriodEnforcer @@ -17,6 +16,21 @@ import "forge-std/Test.sol"; * - 32 bytes: periodAmount. * - 32 bytes: periodDuration (in seconds). * - 32 bytes: startDate for the first period. + * + * For optimal gas usage, it is recommended that the configurations in _terms are sorted + * from the most frequently used token to the least frequently used token. Tokens placed + * earlier in the sequence will be processed first, reducing gas consumption. + * + * The logic does not support duplicated token entries in the _terms. In the event that + * duplicate tokens are provided, the enforcer will only consider the configuration for the + * first occurrence and ignore subsequent configurations. This design choice is made to + * optimize gas efficiency and will not result in a revert. + * + * Additionally, the enforcer does not support restrictions on the recipient address or + * arbitrary calldata. For ERC20 transfers, the execution data is strictly required to + * match the IERC20.transfer function selector with a zero ETH value, and for native transfers, + * only an empty calldata is permitted. + * * The _executionCallData always contains instructions for one token transfer. */ contract MultiTokenPeriodEnforcer is CaveatEnforcer { @@ -33,7 +47,8 @@ contract MultiTokenPeriodEnforcer is CaveatEnforcer { } // Mapping from delegation manager => delegation hash => token address => PeriodicAllowance. - mapping(address => mapping(bytes32 => mapping(address => PeriodicAllowance))) public periodicAllowances; + mapping(address delegationManager => mapping(bytes32 delegationHash => mapping(address token => PeriodicAllowance))) public + periodicAllowances; ////////////////////////////// Events ////////////////////////////// @@ -91,10 +106,6 @@ contract MultiTokenPeriodEnforcer is CaveatEnforcer { // Not yet initialized; simulate using provided terms. (uint256 periodAmount_, uint256 periodDuration_, uint256 startDate_) = getTermsInfo(_terms, _token); - uint256 gasleft_ = gasleft(); - getAllTermsInfo(_terms); - console2.log("gasleft():", uint256(gasleft_ - gasleft())); - PeriodicAllowance memory allowance_ = PeriodicAllowance({ periodAmount: periodAmount_, periodDuration: periodDuration_, @@ -107,8 +118,8 @@ contract MultiTokenPeriodEnforcer is CaveatEnforcer { /** * @notice Hook called before a transfer to enforce the periodic limit. - * @dev For ERC20 transfers, expects _executionCallData to decode to (target, , callData) - * with callData length of 68 and beginning with IERC20.transfer.selector. + * @dev For ERC20 transfers, expects _executionCallData to decode to (target,, callData) + * with callData length of 68, beginning with IERC20.transfer.selector and zero value. * For native transfers, expects _executionCallData to decode to (target, value, callData) * with an empty callData. * @param _terms A concatenation of one or more 116-byte configurations. @@ -155,7 +166,7 @@ contract MultiTokenPeriodEnforcer is CaveatEnforcer { require(termsLength_ != 0 && termsLength_ % 116 == 0, "MultiTokenPeriodEnforcer:invalid-terms-length"); // Iterate over the byte offset directly in increments of 116 bytes. - for (uint256 offset_ = 0; offset_ < termsLength_; offset_ += 116) { + for (uint256 offset_ = 0; offset_ < termsLength_;) { // Extract token address from the first 20 bytes. address token_ = address(bytes20(_terms[offset_:offset_ + 20])); if (token_ == _token) { @@ -167,6 +178,10 @@ contract MultiTokenPeriodEnforcer is CaveatEnforcer { startDate_ = uint256(bytes32(_terms[offset_ + 84:offset_ + 116])); return (periodAmount_, periodDuration_, startDate_); } + + unchecked { + offset_ += 116; + } } revert("MultiTokenPeriodEnforcer:token-config-not-found"); } @@ -199,7 +214,7 @@ contract MultiTokenPeriodEnforcer is CaveatEnforcer { startDates_ = new uint256[](numConfigs_); // Loop over each configuration using its index. - for (uint256 i = 0; i < numConfigs_; ++i) { + for (uint256 i = 0; i < numConfigs_;) { // Calculate the starting offset for this configuration. uint256 offset_ = i * 116; // Get the token address from the first 20 bytes. @@ -210,6 +225,10 @@ contract MultiTokenPeriodEnforcer is CaveatEnforcer { periodDurations_[i] = uint256(bytes32(_terms[offset_ + 52:offset_ + 84])); // Get the startDate from the final 32 bytes. startDates_[i] = uint256(bytes32(_terms[offset_ + 84:offset_ + 116])); + + unchecked { + i++; + } } } @@ -218,9 +237,9 @@ contract MultiTokenPeriodEnforcer is CaveatEnforcer { /** * @notice Validates and consumes a transfer (native or ERC20) by ensuring the amount does not exceed the available limit. * @dev Decodes the execution data based on token type: - * - For native transfers (_token == address(0)): decodes (target, value, callData). - * - For ERC20 transfers (_token != address(0)): decodes (target, , callData) and requires callData length to be 68 with a - * valid IERC20.transfer selector. + * - For native transfers (_token == address(0)): expect no calldata, value greater than zero. + * - For ERC20 transfers (_token != address(0)): requires callData length to be 68 with a + * valid IERC20.transfer selector, and zero value. * @param _terms The concatenated configurations. * @param _executionCallData The encoded execution data. * @param _delegationHash The delegation hash. diff --git a/test/enforcers/MultiTokenPeriodEnforcer.t.sol b/test/enforcers/MultiTokenPeriodEnforcer.t.sol index 1fd59e18..4e18bf55 100644 --- a/test/enforcers/MultiTokenPeriodEnforcer.t.sol +++ b/test/enforcers/MultiTokenPeriodEnforcer.t.sol @@ -462,6 +462,17 @@ contract MultiTokenPeriodEnforcerTest is CaveatEnforcerBaseTest { multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, execData_, dummyDelegationHash, address(0), redeemer); } + /// @notice Reverts if a native transfer is attempted with a zero value. + function test_InvalidZeroValueInNativeTransfer() public { + // Build a native token configuration _terms blob. + bytes memory terms_ = abi.encodePacked(address(0), nativePeriodAmount, nativePeriodDuration, nativeStartDate); + // Build execution call data for a native transfer using zero value. + bytes memory execData_ = _encodeNativeTransfer(bob, 0); + // Expect the revert with the specific error message. + vm.expectRevert("MultiTokenPeriodEnforcer:invalid-zero-value-in-native-transfer"); + multiTokenEnforcer.beforeHook(terms_, "", singleDefaultMode, execData_, dummyDelegationHash, address(0), redeemer); + } + /// @notice Tests that multiple beforeHook calls within the same period correctly accumulate the transferred amount. function test_MultipleBeforeHookCallsMaintainsState() public { bytes memory terms_ = abi.encodePacked(address(basicERC20), erc20PeriodAmount, erc20PeriodDuration, erc20StartDate); @@ -760,6 +771,58 @@ contract MultiTokenPeriodEnforcerTest is CaveatEnforcerBaseTest { } } + // / @notice Helper to generate a _terms blob with a configurable number of token configurations. + // / @param _amountcount The number of token configurations to include in the blob. + // / @param _basicERC20 The address of the erc20 token. + // / @param _periodAmount The period amount (uint256) to include in each configuration. + // / @param _periodDuration The period duration (uint256) to include in each configuration. + // / @param _startDate The start date (uint256) to include in each configuration. + function _generateTerms( + uint256 _amountcount, + address _basicERC20, + uint256 _periodAmount, + uint256 _periodDuration, + uint256 _startDate + ) + internal + returns (bytes memory terms) + { + bytes memory blob_; + // Adding the basic token to the first place. + // blob_ = abi.encodePacked(blob_, _basicERC20, _periodAmount, _periodDuration, _startDate); + + for (uint256 i = 0; i < _amountcount; i++) { + (address tokenAddress_) = makeAddr(string(abi.encodePacked("token", i))); + blob_ = abi.encodePacked(blob_, tokenAddress_, _periodAmount, _periodDuration, _startDate); + } + // Adding the basic token to the last place. + blob_ = abi.encodePacked(blob_, _basicERC20, _periodAmount, _periodDuration, _startDate); + return blob_; + } + + /// @notice Measures the gas cost for beforeHook when provided with a _terms blob containing a configurable number of token + /// configurations. + /// Initially set for 10 token configurations. Adjust `numTokens` to test with a different quantity. + function test_GasCostBeforeHookForMultipleTokens() public { + // Set the number of token configurations (adjustable later). + uint256 numTokens = 1; + // Generate a _terms blob with numTokens configurations. + // The matching configuration for basicERC20 is positioned at the end. + bytes memory terms = _generateTerms(numTokens, address(basicERC20), erc20PeriodAmount, erc20PeriodDuration, erc20StartDate); + + // Prepare a valid ERC20 execution call data (68 bytes) that corresponds to basicERC20. + uint256 transferAmount = 100; // Must be less than erc20PeriodAmount. + bytes memory callData = _encodeERC20Transfer(bob, transferAmount); + bytes memory execData = _encodeSingleExecution(address(basicERC20), 0, callData); + + // uint256 gasBefore = gasleft(); + for (uint256 i; i < 10; ++i) { + multiTokenEnforcer.beforeHook(terms, "", singleDefaultMode, execData, dummyDelegationHash, address(0), redeemer); + } + // uint256 gasUsed = gasBefore - gasleft(); + // console2.log("Gas used for beforeHook with", numTokens + 1, "token configurations:", gasUsed); + } + /// @notice Tests getAvailableAmount for an ERC20 token when no beforeHook has been called, /// so that the allowance is simulated from _terms. function test_GetAvailableAmountWithoutBeforeHookErc20() public { From 49818d43c380088a8e69f2dc098aa5d036d026dc Mon Sep 17 00:00:00 2001 From: hanzel98 Date: Thu, 10 Apr 2025 11:52:12 -0600 Subject: [PATCH 6/6] chore: incrementing loop iterator with unchecked by default --- src/enforcers/MultiTokenPeriodEnforcer.sol | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/enforcers/MultiTokenPeriodEnforcer.sol b/src/enforcers/MultiTokenPeriodEnforcer.sol index dca7268b..bd015382 100644 --- a/src/enforcers/MultiTokenPeriodEnforcer.sol +++ b/src/enforcers/MultiTokenPeriodEnforcer.sol @@ -214,7 +214,7 @@ contract MultiTokenPeriodEnforcer is CaveatEnforcer { startDates_ = new uint256[](numConfigs_); // Loop over each configuration using its index. - for (uint256 i = 0; i < numConfigs_;) { + for (uint256 i = 0; i < numConfigs_; ++i) { // Calculate the starting offset for this configuration. uint256 offset_ = i * 116; // Get the token address from the first 20 bytes. @@ -225,10 +225,6 @@ contract MultiTokenPeriodEnforcer is CaveatEnforcer { periodDurations_[i] = uint256(bytes32(_terms[offset_ + 52:offset_ + 84])); // Get the startDate from the final 32 bytes. startDates_[i] = uint256(bytes32(_terms[offset_ + 84:offset_ + 116])); - - unchecked { - i++; - } } }