diff --git a/.gitignore b/.gitignore index 4cb0394..53cc03f 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,6 @@ docs/ lcov.info lcov.* .lcov* -*lcov* \ No newline at end of file +*lcov* + +settings.local.json \ No newline at end of file diff --git a/solidity/src/helpers/CATValidator.sol b/solidity/src/helpers/CATValidator.sol deleted file mode 100644 index 53cdb1f..0000000 --- a/solidity/src/helpers/CATValidator.sol +++ /dev/null @@ -1,177 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity ^0.8.30; - -import { EIP712 } from "solady/src/utils/EIP712.sol"; -import { SafeTransferLib } from "solady/src/utils/SafeTransferLib.sol"; -import { SignatureCheckerLib } from "solady/src/utils/SignatureCheckerLib.sol"; - -import { CallProxy } from "./CallProxy.sol"; -import { InputTarget, LibExecutionConstraint, Output } from "./libs/LibExecutionConstraint.sol"; - -/** - * @title Constrained Asset Transaction Validator – C.A.T Validator - * @author Alexander @ LIFI (https://li.fi) - * @custom:version 0.1.0 - * @notice Validation of a pre-approved asset allowance to execute a transaction that should result in a specific asset - * outcome. - * The intended usecase is in combination with Catapultar with an embedded action. A Catapultar account with an batch - * transaction of setSignature and approve, allows the configured executor to find calldata to complete the provided - * description: inputs for outputs. - * - * This contract should never hold assets: - * - Inputs are collected from the signer and delivered to an executor specified destination. - * - Outputs are expected to be delivered directly to the destination specified in the output. Initial balances are - * recorded before input transfers and after the external call. - * - * This contract uses a call proxy for arbitrary call execution. This makes it safe to set approvals to the contract. - * This contract does not have a fixed callback function. The call proxy address is ::CALL_PROXY(). - * - * Each approval can only be accessed once, invalidating other approvals by nonce. Except nonce 0 which can be used for - * long lived approvals like DCAs. - */ -contract CATValidator is EIP712 { - error InvalidTokenAmount(uint256 expected, uint256 received); - error AllocationTooSmall(uint256 allocated, uint256 spend); - error NonceAlreadySpent(); - error BadSignature(); - - address public immutable CALL_PROXY; - - mapping(address => mapping(uint256 => bool)) public spentNonces; - - constructor() { - CALL_PROXY = address(new CallProxy()); - } - - function _domainNameAndVersion() internal pure override returns (string memory name, string memory version) { - name = "CAT Validator"; - version = "1"; - } - - function DOMAIN_SEPARATOR() external view returns (bytes32) { - return _domainSeparator(); - } - - /** - * @notice Execute a transaction for an account given a signed execution constraint. - * - */ - function entry( - address execTarget, - bytes calldata execPayload, - address account, - uint256 nonce, - InputTarget[] calldata inputs, - Output[] calldata outputs, - bytes calldata signature - ) external { - if (nonce != 0) _checkNonce(account, nonce); - - _validateApproval(account, nonce, inputs, outputs, signature); - - uint256[] memory recordedBalances = _recordOutputs(account, outputs); - - _handleInputs(execTarget, account, inputs); - - _call(execTarget, execPayload); - - _compareOutputs(account, outputs, recordedBalances); - } - - function _checkNonce( - address account, - uint256 nonce - ) internal { - bool spent = spentNonces[account][nonce]; - if (spent) revert NonceAlreadySpent(); - spentNonces[account][nonce] = !spent; - } - - /** - * @dev Validate an approval. Requires that the caller is the executor associated with the constraint. - */ - function _validateApproval( - address account, - uint256 nonce, - InputTarget[] calldata inputs, - Output[] calldata outputs, - bytes calldata signature - ) internal view { - bytes32 typehash = LibExecutionConstraint.typehash(inputs, outputs, msg.sender, nonce); - bytes32 digest = _hashTypedData(typehash); - - if (!SignatureCheckerLib.isValidSignatureNowCalldata(account, digest, signature)) revert BadSignature(); - } - - function _balanceOf( - address account, - Output calldata output - ) internal view returns (uint256 bal) { - address destination = output.destination == address(0) ? account : output.destination; - bal = output.token == address(0) ? destination.balance : SafeTransferLib.balanceOf(output.token, destination); - } - - function _recordOutputs( - address account, - Output[] calldata outputs - ) internal view returns (uint256[] memory balances) { - balances = new uint256[](outputs.length); - for (uint256 i; i < outputs.length; ++i) { - Output calldata output = outputs[i]; - balances[i] = _balanceOf(account, output); - } - } - - function _compareOutputs( - address account, - Output[] calldata outputs, - uint256[] memory recordedBalances - ) internal view { - for (uint256 i; i < outputs.length; ++i) { - Output calldata output = outputs[i]; - uint256 newBalance = _balanceOf(account, output); - uint256 diff = newBalance - recordedBalances[i]; - if (diff < output.amount) revert InvalidTokenAmount(output.amount, diff); - } - } - - function _handleInputs( - address destination, - address source, - InputTarget[] calldata inputs - ) internal { - for (uint256 i = 0; i < inputs.length; ++i) { - InputTarget calldata input = inputs[i]; - - uint256 spend = input.spend == 0 ? SafeTransferLib.balanceOf(input.token, source) : input.spend; - if (input.allocated != 0 && input.allocated < spend) revert AllocationTooSmall(input.allocated, spend); - - SafeTransferLib.safeTransferFrom(input.token, source, destination, spend); - } - } - - /** - * @notice Makes an arbitrary external call through the call proxy. - */ - function _call( - address execTarget, - bytes calldata execPayload - ) internal { - address callProxy = CALL_PROXY; - assembly ("memory-safe") { - // get the free memory pointer. - let m := mload(0x40) - - // Copy call into memory - mstore(m, execTarget) - calldatacopy(add(m, 32), execPayload.offset, execPayload.length) - - let success := call(gas(), callProxy, selfbalance(), m, add(execPayload.length, 32), codesize(), 0x00) - - if iszero(success) { - returndatacopy(0x00, 0x00, returndatasize()) - if iszero(success) { revert(0x00, returndatasize()) } - } - } - } -} diff --git a/solidity/src/helpers/CallProxy.sol b/solidity/src/helpers/CallProxy.sol deleted file mode 100644 index b691644..0000000 --- a/solidity/src/helpers/CallProxy.sol +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity ^0.8.30; - -/** - * @title Proxies call such that arbitrary calls are safe to execute. - * @author Alexander @ LIFI (https://li.fi) - */ -contract CallProxy { - /** - * @dev This contract expects to receive calldata of type: abi.encodePacked(bytes32(target), bytes) - * This data looks like: - * 000000000000000000000000f79Db8d4E9baF5266B2578790363F027AE550B7a - * 2b096926.... // Payload - */ - fallback() external { - assembly ("memory-safe") { - // get the free memory pointer. - let m := mload(0x40) - - // Copy call into memory - calldatacopy(m, 32, sub(calldatasize(), 32)) - - let target := calldataload(0) - let success := call(gas(), target, callvalue(), m, sub(calldatasize(), 32), codesize(), 0x00) - - // Handle return data - returndatacopy(0x00, 0x00, returndatasize()) - if iszero(success) { revert(0x00, returndatasize()) } - return(0x00, returndatasize()) - } - } -} diff --git a/solidity/src/helpers/libs/LibExecutionConstraint.sol b/solidity/src/helpers/libs/LibExecutionConstraint.sol deleted file mode 100644 index 850bfcd..0000000 --- a/solidity/src/helpers/libs/LibExecutionConstraint.sol +++ /dev/null @@ -1,87 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity ^0.8.30; - -import { EfficientHashLib } from "solady/src/utils/EfficientHashLib.sol"; - -struct Input { - address token; - uint256 amount; -} - -struct InputTarget { - address token; - uint256 allocated; - uint256 spend; // 0 means balanceOf. Note that if allocated != 0, then this may fail since balanceOf may be larger - // than allocated. -} - -struct Output { - address token; - uint256 amount; - address destination; -} - -struct ExecutionConstraint { - Input[] inputs; - Output[] outputs; - address executor; - uint256 nonce; -} - -library LibExecutionConstraint { - using EfficientHashLib for uint256; - using EfficientHashLib for bytes; - using EfficientHashLib for bytes32; - using EfficientHashLib for bytes32[]; - - bytes32 constant EXECUTION_CONSTRAINT_TYPE_HASH = keccak256( - bytes( - "ExecutionConstraint(Input[] inputs,Output[] outputs,address executor,uint256 nonce)Input(address token,uint256 amount)Output(address token,uint256 amount,address destination)" - ) - ); - - bytes32 constant INPUT_TYPE_HASH = keccak256(bytes("Input(address token,uint256 amount)")); - - bytes32 constant OUTPUT_TYPE_HASH = keccak256(bytes("Output(address token,uint256 amount,address destination)")); - - function inputsHash( - InputTarget[] calldata inputs - ) internal pure returns (bytes32 h) { - uint256 numInputs = inputs.length; - bytes32[] memory buffer = numInputs.malloc(); - for (uint256 i; i < numInputs; ++i) { - InputTarget calldata input = inputs[i]; - buffer[i] = INPUT_TYPE_HASH.hash(bytes32(uint256(uint160(input.token))), bytes32(input.allocated)); - } - h = buffer.hash(); - buffer.free(); - } - - function outputsHash( - Output[] calldata outputs - ) internal pure returns (bytes32 h) { - uint256 numOutputs = outputs.length; - bytes32[] memory buffer = numOutputs.malloc(); - for (uint256 i; i < numOutputs; ++i) { - Output calldata output = outputs[i]; - buffer[i] = OUTPUT_TYPE_HASH.hash( - bytes32(uint256(uint160(output.token))), - bytes32(output.amount), - bytes32(uint256(uint160(output.destination))) - ); - } - h = buffer.hash(); - buffer.free(); - } - - function typehash( - InputTarget[] calldata inputs, - Output[] calldata outputs, - address executor, - uint256 nonce - ) internal pure returns (bytes32 messageHash) { - messageHash = EXECUTION_CONSTRAINT_TYPE_HASH.hash( - inputsHash(inputs), outputsHash(outputs), bytes32(uint256(uint160(executor))), bytes32(nonce) - ); - } -} diff --git a/solidity/test/CallProxy.t.sol b/solidity/test/CallProxy.t.sol index af197e4..753388c 100644 --- a/solidity/test/CallProxy.t.sol +++ b/solidity/test/CallProxy.t.sol @@ -64,4 +64,12 @@ contract CallProxyTest is Test { assertEq(rt, abi.encode(returnData)); assertEq(success, true); } + + function test_emptyEtherTransfer_fails() external { + vm.deal(address(this), 1 ether); + // Empty calldata causes sub(calldatasize(), 32) to underflow to 2^256-32, + // triggering an impossibly large memory expansion that exhausts all gas. + (bool success,) = address(c).call{ value: 1 ether }(""); + assertFalse(success); + } } diff --git a/solidity/test/helpers/CATValidator.t.sol b/solidity/test/helpers/CATValidator.t.sol index 5cd55bf..63066d6 100644 --- a/solidity/test/helpers/CATValidator.t.sol +++ b/solidity/test/helpers/CATValidator.t.sol @@ -5,13 +5,16 @@ import { MockERC20 } from "solady/test/utils/mocks/MockERC20.sol"; import { LibExecutionConstraintTest } from "./libs/LibExecutionConstraint.t.sol"; -import { CATValidator, InputTarget, Output } from "../../src/helpers/CATValidator.sol"; +import { CATValidator } from "../../src/CATValidator.sol"; +import { AllowanceSpend, Outcome } from "../../src/libs/LibExecutionConstraint.sol"; interface EIP712 { function DOMAIN_SEPARATOR() external view returns (bytes32); } contract CATValidatorMock is CATValidator { + uint256 public constant MAGIC_BALANCE = SPEND_BALANCE_OF_MAGIC; + function checkNonce( address account, uint256 nonce @@ -22,19 +25,19 @@ contract CATValidatorMock is CATValidator { function validateApproval( address account, uint256 nonce, - InputTarget[] calldata inputs, - Output[] calldata outputs, + AllowanceSpend[] calldata allowances, + Outcome[] calldata outcomes, bytes calldata signature ) external view { - return _validateApproval(account, nonce, inputs, outputs, signature); + return _validateApproval(account, nonce, allowances, outcomes, signature); } - function handleInputs( + function handleAllowances( address destination, address source, - InputTarget[] calldata inputs + AllowanceSpend[] calldata allowances ) external { - return _handleInputs(destination, source, inputs); + return _handleAllowances(destination, source, allowances); } function call( @@ -44,19 +47,19 @@ contract CATValidatorMock is CATValidator { return _call(target, payload); } - function recordOutputs( + function recordBalances( address account, - Output[] calldata outputs + Outcome[] calldata outcomes ) external view returns (uint256[] memory balances) { - return _recordOutputs(account, outputs); + return _recordBalances(account, outcomes); } - function compareOutputs( + function compareOutcomes( address account, - Output[] calldata outputs, + Outcome[] calldata outcomes, uint256[] calldata balances ) external view { - return _compareOutputs(account, outputs, balances); + return _compareOutcomes(account, outcomes, balances); } } @@ -84,8 +87,8 @@ contract CATValidatorTest is LibExecutionConstraintTest { } function test_validateApproval( - InputTarget[] memory inputs, - Output[] memory outputs, + AllowanceSpend[] memory allowances, + Outcome[] memory outcomes, address executor, uint256 nonce ) external { @@ -95,7 +98,7 @@ contract CATValidatorTest is LibExecutionConstraintTest { bytes32 s; address signer; { - bytes32 th = typehashReference(inputTargetToInput(inputs), outputs, executor, nonce); + bytes32 th = typehashReference(allowances, outcomes, executor, nonce); bytes32 domainSeparator = EIP712(address(validator)).DOMAIN_SEPARATOR(); bytes32 digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, th)); @@ -109,16 +112,16 @@ contract CATValidatorTest is LibExecutionConstraintTest { // Sets msg.sender for the test vm.startPrank(executor); - validator.validateApproval(signer, nonce, inputs, outputs, abi.encodePacked(r, s, v)); + validator.validateApproval(signer, nonce, allowances, outcomes, abi.encodePacked(r, s, v)); vm.expectRevert(abi.encodeWithSelector(CATValidator.BadSignature.selector)); - validator.validateApproval(signer, nonce, inputs, outputs, abi.encodePacked(bytes32(uint256(r) + 1), s, v)); + validator.validateApproval(signer, nonce, allowances, outcomes, abi.encodePacked(bytes32(uint256(r) + 1), s, v)); vm.expectRevert(abi.encodeWithSelector(CATValidator.BadSignature.selector)); - validator.validateApproval(signer, nonce, inputs, outputs, hex""); + validator.validateApproval(signer, nonce, allowances, outcomes, hex""); } - function test_fuzz_handleInputs( + function test_fuzz_handleAllowances( uint256[] calldata amounts ) external { address destination = makeAddr("destination"); @@ -133,26 +136,28 @@ contract CATValidatorTest is LibExecutionConstraintTest { MockERC20(tokens[i]).approve(address(validator), amounts[i]); } - InputTarget[] memory targets = new InputTarget[](amounts.length); - for (uint256 i; i < targets.length; ++i) { - targets[i] = InputTarget({ token: tokens[i], allocated: amounts[i], spend: amounts[i] }); + AllowanceSpend[] memory allowances = new AllowanceSpend[](amounts.length); + for (uint256 i; i < allowances.length; ++i) { + allowances[i] = AllowanceSpend({ token: tokens[i], allocated: amounts[i], spend: amounts[i] }); } - for (uint256 i; i < targets.length; ++i) { - assertEq(amounts[i], MockERC20(targets[i].token).balanceOf(account)); + for (uint256 i; i < allowances.length; ++i) { + assertEq(amounts[i], MockERC20(allowances[i].token).balanceOf(account)); } - for (uint256 i; i < targets.length; ++i) { - vm.expectCall(targets[i].token, abi.encodeCall(MockERC20.transferFrom, (account, destination, amounts[i]))); + for (uint256 i; i < allowances.length; ++i) { + vm.expectCall( + allowances[i].token, abi.encodeCall(MockERC20.transferFrom, (account, destination, amounts[i])) + ); } - validator.handleInputs(destination, account, targets); + validator.handleAllowances(destination, account, allowances); - for (uint256 i; i < targets.length; ++i) { - assertEq(0, MockERC20(targets[i].token).balanceOf(account)); - assertEq(amounts[i], MockERC20(targets[i].token).balanceOf(destination)); + for (uint256 i; i < allowances.length; ++i) { + assertEq(0, MockERC20(allowances[i].token).balanceOf(account)); + assertEq(amounts[i], MockERC20(allowances[i].token).balanceOf(destination)); } } - function test_handleInputs_half_spend( + function test_handleAllowances_half_spend( uint256[] calldata amounts ) external { address destination = makeAddr("destination"); @@ -168,29 +173,30 @@ contract CATValidatorTest is LibExecutionConstraintTest { MockERC20(tokens[i]).approve(address(validator), amounts[i]); } - InputTarget[] memory targets = new InputTarget[](amounts.length); - for (uint256 i; i < targets.length; ++i) { - targets[i] = InputTarget({ token: tokens[i], allocated: amounts[i], spend: amounts[i] / 2 }); + AllowanceSpend[] memory allowances = new AllowanceSpend[](amounts.length); + for (uint256 i; i < allowances.length; ++i) { + allowances[i] = AllowanceSpend({ token: tokens[i], allocated: amounts[i], spend: amounts[i] / 2 }); } - for (uint256 i; i < targets.length; ++i) { - assertEq(amounts[i], MockERC20(targets[i].token).balanceOf(account)); + for (uint256 i; i < allowances.length; ++i) { + assertEq(amounts[i], MockERC20(allowances[i].token).balanceOf(account)); } - for (uint256 i; i < targets.length; ++i) { + for (uint256 i; i < allowances.length; ++i) { vm.expectCall( - targets[i].token, abi.encodeCall(MockERC20.transferFrom, (account, destination, amounts[i] / 2)) + allowances[i].token, + abi.encodeCall(MockERC20.transferFrom, (account, destination, amounts[i] / 2)) ); } - validator.handleInputs(destination, account, targets); + validator.handleAllowances(destination, account, allowances); - for (uint256 i; i < targets.length; ++i) { + for (uint256 i; i < allowances.length; ++i) { uint256 spend = amounts[i] / 2; - assertEq(amounts[i] - spend, MockERC20(targets[i].token).balanceOf(account)); - assertEq(spend, MockERC20(targets[i].token).balanceOf(destination)); + assertEq(amounts[i] - spend, MockERC20(allowances[i].token).balanceOf(account)); + assertEq(spend, MockERC20(allowances[i].token).balanceOf(destination)); } } - function test_revert_handleInputs_exceed_allowance( + function test_revert_handleAllowances_exceed_allocation( uint256 amount ) external { address destination = makeAddr("destination"); @@ -204,14 +210,14 @@ contract CATValidatorTest is LibExecutionConstraintTest { vm.prank(account); MockERC20(token).approve(address(validator), amount); - InputTarget[] memory targets = new InputTarget[](1); - targets[0] = InputTarget({ token: token, allocated: amount, spend: amount + 1 }); + AllowanceSpend[] memory allowances = new AllowanceSpend[](1); + allowances[0] = AllowanceSpend({ token: token, allocated: amount, spend: amount + 1 }); vm.expectRevert(abi.encodeWithSelector(CATValidator.AllocationTooSmall.selector, amount, amount + 1)); - validator.handleInputs(destination, account, targets); + validator.handleAllowances(destination, account, allowances); } - function test_handleInputs_0_allowance_any_spend( + function test_handleAllowances_magic_spend_uses_balanceOf( uint256 amount ) external { address destination = makeAddr("destination"); @@ -222,49 +228,11 @@ contract CATValidatorTest is LibExecutionConstraintTest { vm.prank(account); MockERC20(token).approve(address(validator), amount); - InputTarget[] memory targets = new InputTarget[](1); - targets[0] = InputTarget({ token: token, allocated: 0, spend: amount }); + AllowanceSpend[] memory allowances = new AllowanceSpend[](1); + allowances[0] = AllowanceSpend({ token: token, allocated: type(uint256).max, spend: validator.MAGIC_BALANCE() }); vm.expectCall(token, abi.encodeCall(MockERC20.transferFrom, (account, destination, amount))); - validator.handleInputs(destination, account, targets); - } - - function test_handleInputs_0_spend_balanceOf( - uint256 amount - ) external { - address destination = makeAddr("destination"); - address account = makeAddr("account"); - - address token = address(new MockERC20("Test Token", "TT", 18)); - MockERC20(token).mint(account, amount); - vm.prank(account); - MockERC20(token).approve(address(validator), amount); - - InputTarget[] memory targets = new InputTarget[](1); - targets[0] = InputTarget({ token: token, allocated: 0, spend: 0 }); - - vm.expectCall(token, abi.encodeCall(MockERC20.transferFrom, (account, destination, amount))); - validator.handleInputs(destination, account, targets); - } - - function test_revert_handleInputs_0_spend_fix_allowance( - uint256 amount - ) external { - address destination = makeAddr("destination"); - address account = makeAddr("account"); - vm.assume(amount != 0); - vm.assume(amount != type(uint256).max); - - address token = address(new MockERC20("Test Token", "TT", 18)); - MockERC20(token).mint(account, amount + 1); - vm.prank(account); - MockERC20(token).approve(address(validator), amount + 1); - - InputTarget[] memory targets = new InputTarget[](1); - targets[0] = InputTarget({ token: token, allocated: amount, spend: 0 }); - - vm.expectRevert(abi.encodeWithSelector(CATValidator.AllocationTooSmall.selector, amount, amount + 1)); - validator.handleInputs(destination, account, targets); + validator.handleAllowances(destination, account, allowances); } function test_revert_call_transfer() external { @@ -311,77 +279,77 @@ contract CATValidatorTest is LibExecutionConstraintTest { uint128 amount; } - function test_fuzz_recordOutputs( + function test_fuzz_recordBalances( AmountAndDestination[] calldata ad ) external { address account = makeAddr("account"); - Output[] memory outputs = new Output[](ad.length); - for (uint256 i; i < outputs.length; ++i) { + Outcome[] memory outcomes = new Outcome[](ad.length); + for (uint256 i; i < outcomes.length; ++i) { address dest = ad[i].destination == address(0) ? account : ad[i].destination; if (i == 2) { vm.deal(dest, ad[i].amount); - outputs[i] = Output({ amount: ad[i].amount, destination: ad[i].destination, token: address(0) }); + outcomes[i] = Outcome({ amount: ad[i].amount, destination: ad[i].destination, token: address(0) }); continue; } string memory vv = string(abi.encode(keccak256(abi.encode(i)))); address token = address(new MockERC20(vv, vv, 18)); MockERC20(token).mint(dest, ad[i].amount); - outputs[i] = Output({ amount: ad[i].amount, destination: ad[i].destination, token: token }); + outcomes[i] = Outcome({ amount: ad[i].amount, destination: ad[i].destination, token: token }); } - uint256[] memory balances = validator.recordOutputs(account, outputs); + uint256[] memory balances = validator.recordBalances(account, outcomes); - assertEq(balances.length, outputs.length); - for (uint256 i; i < outputs.length; ++i) { - assertEq(balances[i], outputs[i].amount); + assertEq(balances.length, outcomes.length); + for (uint256 i; i < outcomes.length; ++i) { + assertEq(balances[i], outcomes[i].amount); } } - function test_fuzz_compareOutputs( + function test_fuzz_compareOutcomes( AmountAndDestination[] calldata ad ) external { address account = makeAddr("account"); vm.assume(ad.length > 0); vm.assume(ad[0].amount != 0); - Output[] memory outputs = new Output[](ad.length); - for (uint256 i; i < outputs.length; ++i) { + Outcome[] memory outcomes = new Outcome[](ad.length); + for (uint256 i; i < outcomes.length; ++i) { address dest = ad[i].destination == address(0) ? account : ad[i].destination; if (i == 2) { vm.deal(dest, ad[i].amount); - outputs[i] = Output({ amount: ad[i].amount, destination: ad[i].destination, token: address(0) }); + outcomes[i] = Outcome({ amount: ad[i].amount, destination: ad[i].destination, token: address(0) }); continue; } string memory vv = string(abi.encode(keccak256(abi.encode(i)))); address token = address(new MockERC20(vv, vv, 18)); MockERC20(token).mint(dest, ad[i].amount); - outputs[i] = Output({ amount: ad[i].amount, destination: ad[i].destination, token: token }); + outcomes[i] = Outcome({ amount: ad[i].amount, destination: ad[i].destination, token: token }); } - uint256[] memory balances = validator.recordOutputs(account, outputs); + uint256[] memory balances = validator.recordBalances(account, outcomes); - // If we run compare outputs it should fail immediately on the first entry. + // If we run compareOutcomes it should fail immediately on the first entry. vm.expectRevert(abi.encodeWithSelector(CATValidator.InvalidTokenAmount.selector, ad[0].amount, 0)); - validator.compareOutputs(account, outputs, balances); + validator.compareOutcomes(account, outcomes, balances); // However, if we give a 0 array, then it should pass. - uint256[] memory zeroBalances = new uint256[](outputs.length); - validator.compareOutputs(account, outputs, zeroBalances); + uint256[] memory zeroBalances = new uint256[](outcomes.length); + validator.compareOutcomes(account, outcomes, zeroBalances); // Lets send another batch so the amounts will be 2x ad[i].amount. - for (uint256 i; i < outputs.length; ++i) { + for (uint256 i; i < outcomes.length; ++i) { address dest = ad[i].destination == address(0) ? account : ad[i].destination; if (i == 2) { vm.deal(dest, uint256(ad[i].amount) * 2); continue; } - MockERC20(outputs[i].token).mint(dest, ad[i].amount); + MockERC20(outcomes[i].token).mint(dest, ad[i].amount); } // Neither reverts. - validator.compareOutputs(account, outputs, balances); - validator.compareOutputs(account, outputs, zeroBalances); + validator.compareOutcomes(account, outcomes, balances); + validator.compareOutcomes(account, outcomes, zeroBalances); } } diff --git a/solidity/test/helpers/CallProxy.t.sol b/solidity/test/helpers/CallProxy.t.sol index 7891eed..176d40a 100644 --- a/solidity/test/helpers/CallProxy.t.sol +++ b/solidity/test/helpers/CallProxy.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.30; -import { CallProxy } from "../../src/helpers/CallProxy.sol"; +import { CallProxy } from "../../src/CallProxy.sol"; import { Test } from "forge-std/src/Test.sol"; contract MockMocker { diff --git a/solidity/test/helpers/libs/LibExecutionConstraint.t.sol b/solidity/test/helpers/libs/LibExecutionConstraint.t.sol index 681a2c4..a9ae937 100644 --- a/solidity/test/helpers/libs/LibExecutionConstraint.t.sol +++ b/solidity/test/helpers/libs/LibExecutionConstraint.t.sol @@ -4,43 +4,37 @@ pragma solidity ^0.8.30; import { Test } from "forge-std/src/Test.sol"; import { - Input, - InputTarget, + AllowanceSpend, LibExecutionConstraint, - Output -} from "../../../src/helpers/libs/LibExecutionConstraint.sol"; + Outcome +} from "../../../src/libs/LibExecutionConstraint.sol"; contract LibExecutionConstraintTest is Test { - function inputTargetToInput( - InputTarget[] memory inputTargets - ) internal pure returns (Input[] memory inputs) { - inputs = new Input[](inputTargets.length); - for (uint256 i; i < inputTargets.length; ++i) { - inputs[i] = Input({ token: inputTargets[i].token, amount: inputTargets[i].allocated }); - } - } - function typehashReference( - Input[] memory inputs, - Output[] memory outputs, + AllowanceSpend[] memory allowances, + Outcome[] memory outcomes, address executor, uint256 nonce ) internal pure returns (bytes32) { - bytes32[] memory inputHashes = new bytes32[](inputs.length); - for (uint256 i; i < inputs.length; ++i) { - inputHashes[i] = keccak256( - abi.encode(keccak256(bytes("Input(address token,uint256 amount)")), inputs[i].token, inputs[i].amount) + bytes32[] memory allowanceHashes = new bytes32[](allowances.length); + for (uint256 i; i < allowances.length; ++i) { + allowanceHashes[i] = keccak256( + abi.encode( + keccak256(bytes("Allowance(address token,uint256 amount)")), + allowances[i].token, + allowances[i].allocated + ) ); } - bytes32[] memory outputHashes = new bytes32[](outputs.length); - for (uint256 i; i < outputs.length; ++i) { - outputHashes[i] = keccak256( + bytes32[] memory outcomeHashes = new bytes32[](outcomes.length); + for (uint256 i; i < outcomes.length; ++i) { + outcomeHashes[i] = keccak256( abi.encode( - keccak256(bytes("Output(address token,uint256 amount,address destination)")), - outputs[i].token, - outputs[i].amount, - outputs[i].destination + keccak256(bytes("Outcome(address token,uint256 amount,address destination)")), + outcomes[i].token, + outcomes[i].amount, + outcomes[i].destination ) ); } @@ -49,11 +43,11 @@ contract LibExecutionConstraintTest is Test { abi.encode( keccak256( bytes( - "ExecutionConstraint(Input[] inputs,Output[] outputs,address executor,uint256 nonce)Input(address token,uint256 amount)Output(address token,uint256 amount,address destination)" + "ExecutionConstraint(Allowance[] allowances,Outcome[] outcomes,address executor,uint256 nonce)Allowance(address token,uint256 amount)Outcome(address token,uint256 amount,address destination)" ) ), - keccak256(abi.encodePacked(inputHashes)), - keccak256(abi.encodePacked(outputHashes)), + keccak256(abi.encodePacked(allowanceHashes)), + keccak256(abi.encodePacked(outcomeHashes)), executor, nonce ) @@ -61,13 +55,13 @@ contract LibExecutionConstraintTest is Test { } function test_typehash( - InputTarget[] calldata inputs, - Output[] calldata outputs, + AllowanceSpend[] calldata allowances, + Outcome[] calldata outcomes, address executor, uint256 nonce ) external pure { - bytes32 libraryTypeHash = LibExecutionConstraint.typehash(inputs, outputs, executor, nonce); - bytes32 expectedTypeHash = typehashReference(inputTargetToInput(inputs), outputs, executor, nonce); + bytes32 libraryTypeHash = LibExecutionConstraint.typehash(allowances, outcomes, executor, nonce); + bytes32 expectedTypeHash = typehashReference(allowances, outcomes, executor, nonce); assertEq(libraryTypeHash, expectedTypeHash); }