Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 40 additions & 40 deletions .gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -6,47 +6,47 @@ DelegatecallGuardTest:test_direct_call_reverts_NotDelegateCall() (gas: 8420)
DelegatecallGuardTest:test_multiple_delegatecall_guards() (gas: 298825)
DelegatecallGuardTest:test_onlyDelegatecall_modifier_usage() (gas: 98867)
DelegatecallGuardTest:test_self_address_immutable() (gas: 2877)
TrailsIntentEntrypointDeploymentTest:test_DeployIntentEntrypoint_SameAddress() (gas: 839567)
TrailsIntentEntrypointDeploymentTest:test_DeployIntentEntrypoint_Success() (gas: 832067)
TrailsIntentEntrypointDeploymentTest:test_DeployedIntentEntrypoint_HasCorrectConfiguration() (gas: 834866)
TrailsIntentEntrypointTest:testAssemblyCodeExecution() (gas: 89228)
TrailsIntentEntrypointDeploymentTest:test_DeployIntentEntrypoint_SameAddress() (gas: 925036)
TrailsIntentEntrypointDeploymentTest:test_DeployIntentEntrypoint_Success() (gas: 917312)
TrailsIntentEntrypointDeploymentTest:test_DeployedIntentEntrypoint_HasCorrectConfiguration() (gas: 920161)
TrailsIntentEntrypointTest:testAssemblyCodeExecution() (gas: 90256)
TrailsIntentEntrypointTest:testConstructor() (gas: 3287)
TrailsIntentEntrypointTest:testConstructorAndDomainSeparator() (gas: 7684)
TrailsIntentEntrypointTest:testDepositToIntentAlreadyUsed() (gas: 114117)
TrailsIntentEntrypointTest:testDepositToIntentCannotReuseDigest() (gas: 94757)
TrailsIntentEntrypointTest:testDepositToIntentExpiredDeadline() (gas: 56029)
TrailsIntentEntrypointTest:testDepositToIntentReentrancyProtection() (gas: 114095)
TrailsIntentEntrypointTest:testDepositToIntentRequiresNonZeroAmount() (gas: 27371)
TrailsIntentEntrypointTest:testDepositToIntentRequiresValidToken() (gas: 30754)
TrailsIntentEntrypointTest:testDepositToIntentTransferFromFails() (gas: 58387)
TrailsIntentEntrypointTest:testDepositToIntentWithPermitAlreadyUsed() (gas: 132693)
TrailsIntentEntrypointTest:testDepositToIntentWithPermitExpiredDeadline() (gas: 36459)
TrailsIntentEntrypointTest:testDepositToIntentWithPermitReentrancyProtection() (gas: 124115)
TrailsIntentEntrypointTest:testDepositToIntentWithPermitRequiresNonZeroAmount() (gas: 35173)
TrailsIntentEntrypointTest:testDepositToIntentWithPermitRequiresPermitAmount() (gas: 60742)
TrailsIntentEntrypointTest:testDepositToIntentWithPermitRequiresValidToken() (gas: 35899)
TrailsIntentEntrypointTest:testDepositToIntentWithPermitTransferFromFails() (gas: 59993)
TrailsIntentEntrypointTest:testDepositToIntentWithPermitWrongSigner() (gas: 40450)
TrailsIntentEntrypointTest:testDepositToIntentWithoutPermit_RequiresIntentAddress() (gas: 31441)
TrailsIntentEntrypointTest:testDepositToIntentWrongSigner() (gas: 57297)
TrailsIntentEntrypointTest:testDepositToIntent_WithNonStandardERC20AndFee_Success() (gas: 809490)
TrailsIntentEntrypointTest:testDepositToIntent_WithNonStandardERC20_InsufficientAllowance_Reverts() (gas: 772287)
TrailsIntentEntrypointTest:testDepositToIntent_WithNonStandardERC20_InsufficientBalance_Reverts() (gas: 772224)
TrailsIntentEntrypointTest:testDepositToIntent_WithNonStandardERC20_Success() (gas: 780332)
TrailsIntentEntrypointTest:testExactApprovalFlow() (gas: 150375)
TrailsIntentEntrypointTest:testExecuteIntentWithFee() (gas: 153584)
TrailsIntentEntrypointTest:testExecuteIntentWithPermit() (gas: 123206)
TrailsIntentEntrypointTest:testExecuteIntentWithPermitExpired() (gas: 36378)
TrailsIntentEntrypointTest:testExecuteIntentWithPermitInvalidSignature() (gas: 37716)
TrailsIntentEntrypointTest:testFeeCollectorReceivesFees() (gas: 147736)
TrailsIntentEntrypointTest:testFeeCollectorReceivesFeesWithoutPermit() (gas: 117386)
TrailsIntentEntrypointTest:testInfiniteApprovalFlow() (gas: 144644)
TrailsIntentEntrypointTest:testIntentTypehashConstant() (gas: 5685)
TrailsIntentEntrypointTest:testInvalidNonceReverts() (gas: 54678)
TrailsIntentEntrypointTest:testNonceIncrementsOnDeposit() (gas: 90407)
TrailsIntentEntrypointTest:testPermitAmountExcessiveWithFee() (gas: 60535)
TrailsIntentEntrypointTest:testPermitAmountInsufficientWithFee() (gas: 59589)
TrailsIntentEntrypointTest:testVersionConstant() (gas: 10337)
TrailsIntentEntrypointTest:testConstructorAndDomainSeparator() (gas: 7697)
TrailsIntentEntrypointTest:testDepositToIntentAlreadyUsed() (gas: 115721)
TrailsIntentEntrypointTest:testDepositToIntentCannotReuseDigest() (gas: 96346)
TrailsIntentEntrypointTest:testDepositToIntentExpiredDeadline() (gas: 56714)
TrailsIntentEntrypointTest:testDepositToIntentReentrancyProtection() (gas: 115699)
TrailsIntentEntrypointTest:testDepositToIntentRequiresNonZeroAmount() (gas: 28057)
TrailsIntentEntrypointTest:testDepositToIntentRequiresValidToken() (gas: 31436)
TrailsIntentEntrypointTest:testDepositToIntentTransferFromFails() (gas: 59381)
TrailsIntentEntrypointTest:testDepositToIntentWithPermitAlreadyUsed() (gas: 162629)
TrailsIntentEntrypointTest:testDepositToIntentWithPermitExpiredDeadline() (gas: 38153)
TrailsIntentEntrypointTest:testDepositToIntentWithPermitReentrancyProtection() (gas: 131146)
TrailsIntentEntrypointTest:testDepositToIntentWithPermitRequiresNonZeroAmount() (gas: 65705)
TrailsIntentEntrypointTest:testDepositToIntentWithPermitRequiresPermitAmount() (gas: 37053)
TrailsIntentEntrypointTest:testDepositToIntentWithPermitRequiresValidToken() (gas: 36735)
TrailsIntentEntrypointTest:testDepositToIntentWithPermitTransferFromFails() (gas: 36276)
TrailsIntentEntrypointTest:testDepositToIntentWithPermitWrongSigner() (gas: 91231)
TrailsIntentEntrypointTest:testDepositToIntentWithoutPermit_RequiresIntentAddress() (gas: 32144)
TrailsIntentEntrypointTest:testDepositToIntentWrongSigner() (gas: 58325)
TrailsIntentEntrypointTest:testDepositToIntent_WithNonStandardERC20AndFee_Success() (gas: 810481)
TrailsIntentEntrypointTest:testDepositToIntent_WithNonStandardERC20_InsufficientAllowance_Reverts() (gas: 773277)
TrailsIntentEntrypointTest:testDepositToIntent_WithNonStandardERC20_InsufficientBalance_Reverts() (gas: 773220)
TrailsIntentEntrypointTest:testDepositToIntent_WithNonStandardERC20_Success() (gas: 781319)
TrailsIntentEntrypointTest:testExactApprovalFlow() (gas: 152572)
TrailsIntentEntrypointTest:testExecuteIntentWithFee() (gas: 154935)
TrailsIntentEntrypointTest:testExecuteIntentWithPermit() (gas: 124552)
TrailsIntentEntrypointTest:testExecuteIntentWithPermitExpired() (gas: 38087)
TrailsIntentEntrypointTest:testExecuteIntentWithPermitInvalidSignature() (gas: 59633)
TrailsIntentEntrypointTest:testFeeCollectorReceivesFees() (gas: 151366)
TrailsIntentEntrypointTest:testFeeCollectorReceivesFeesWithoutPermit() (gas: 120671)
TrailsIntentEntrypointTest:testInfiniteApprovalFlow() (gas: 146540)
TrailsIntentEntrypointTest:testIntentTypehashConstant() (gas: 5679)
TrailsIntentEntrypointTest:testInvalidNonceReverts() (gas: 55401)
TrailsIntentEntrypointTest:testNonceIncrementsOnDeposit() (gas: 91391)
TrailsIntentEntrypointTest:testPermitAmountExcessiveWithFee() (gas: 37875)
TrailsIntentEntrypointTest:testPermitAmountInsufficientWithFee() (gas: 36929)
TrailsIntentEntrypointTest:testVersionConstant() (gas: 10409)
TrailsRouterDeploymentTest:test_DeployTrailsRouter_SameAddress() (gas: 1722289)
TrailsRouterDeploymentTest:test_DeployTrailsRouter_Success() (gas: 1712948)
TrailsRouterDeploymentTest:test_DeployedRouter_HasCorrectConfiguration() (gas: 1712744)
Expand Down
80 changes: 53 additions & 27 deletions src/TrailsIntentEntrypoint.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ contract TrailsIntentEntrypoint is ReentrancyGuard, ITrailsIntentEntrypoint {
// -------------------------------------------------------------------------

bytes32 public constant TRAILS_INTENT_TYPEHASH = keccak256(
"TrailsIntent(address user,address token,uint256 amount,address intentAddress,uint256 deadline,uint256 chainId,uint256 nonce,uint256 feeAmount,address feeCollector)"
"TrailsIntent(string description,address user,address token,uint256 amount,address intentAddress,uint256 deadline,uint256 chainId,uint256 nonce,uint256 feeAmount,address feeCollector)"
);
string public constant VERSION = "1";

Expand Down Expand Up @@ -84,24 +84,37 @@ contract TrailsIntentEntrypoint is ReentrancyGuard, ITrailsIntentEntrypoint {
uint256 nonce,
uint256 feeAmount,
address feeCollector,
uint8 permitV,
bytes32 permitR,
bytes32 permitS,
uint8 sigV,
bytes32 sigR,
bytes32 sigS
string calldata description,
ITrailsIntentEntrypoint.Signature calldata permitSig,
ITrailsIntentEntrypoint.Signature calldata intentSig
) external nonReentrant {
_verifyAndMarkIntent(
user, token, amount, intentAddress, deadline, nonce, feeAmount, feeCollector, sigV, sigR, sigS
);

// Validate permitAmount exactly matches the total required amount (deposit + fee)
// This prevents permit/approval mismatches that could cause DoS or unexpected behavior
unchecked {
if (permitAmount != amount + feeAmount) revert PermitAmountMismatch();
}

IERC20Permit(token).permit(user, address(this), permitAmount, deadline, permitV, permitR, permitS);
// Execute permit and scope variables to avoid stack too deep
{
IERC20Permit(token)
.permit(user, address(this), permitAmount, deadline, permitSig.v, permitSig.r, permitSig.s);
}

_verifyAndMarkIntent(
user,
token,
amount,
intentAddress,
deadline,
nonce,
feeAmount,
feeCollector,
description,
intentSig.v,
intentSig.r,
intentSig.s
);

IERC20(token).safeTransferFrom(user, intentAddress, amount);

// Pay fee if specified (fee token is same as deposit token)
Expand All @@ -123,12 +136,22 @@ contract TrailsIntentEntrypoint is ReentrancyGuard, ITrailsIntentEntrypoint {
uint256 nonce,
uint256 feeAmount,
address feeCollector,
uint8 sigV,
bytes32 sigR,
bytes32 sigS
string calldata description,
ITrailsIntentEntrypoint.Signature calldata intentSig
) external nonReentrant {
_verifyAndMarkIntent(
user, token, amount, intentAddress, deadline, nonce, feeAmount, feeCollector, sigV, sigR, sigS
user,
token,
amount,
intentAddress,
deadline,
nonce,
feeAmount,
feeCollector,
description,
intentSig.v,
intentSig.r,
intentSig.s
);

IERC20(token).safeTransferFrom(user, intentAddress, amount);
Expand Down Expand Up @@ -156,6 +179,7 @@ contract TrailsIntentEntrypoint is ReentrancyGuard, ITrailsIntentEntrypoint {
uint256 nonce,
uint256 feeAmount,
address feeCollector,
string calldata description,
uint8 sigV,
bytes32 sigR,
bytes32 sigS
Expand All @@ -169,21 +193,23 @@ contract TrailsIntentEntrypoint is ReentrancyGuard, ITrailsIntentEntrypoint {
if (nonce != nonces[user]) revert InvalidNonce();

bytes32 _typehash = TRAILS_INTENT_TYPEHASH;
bytes32 descriptionHash = keccak256(bytes(description));
bytes32 intentHash;
// keccak256(abi.encode(TRAILS_INTENT_TYPEHASH, user, token, amount, intentAddress, deadline, chainId, nonce, feeAmount, feeCollector));
// keccak256(abi.encode(TRAILS_INTENT_TYPEHASH, keccak256(bytes(description)), user, token, amount, intentAddress, deadline, chainId, nonce, feeAmount, feeCollector));
assembly {
let ptr := mload(0x40)
mstore(ptr, _typehash)
mstore(add(ptr, 0x20), user)
mstore(add(ptr, 0x40), token)
mstore(add(ptr, 0x60), amount)
mstore(add(ptr, 0x80), intentAddress)
mstore(add(ptr, 0xa0), deadline)
mstore(add(ptr, 0xc0), chainid())
mstore(add(ptr, 0xe0), nonce)
mstore(add(ptr, 0x100), feeAmount)
mstore(add(ptr, 0x120), feeCollector)
intentHash := keccak256(ptr, 0x140)
mstore(add(ptr, 0x20), descriptionHash)
mstore(add(ptr, 0x40), user)
mstore(add(ptr, 0x60), token)
mstore(add(ptr, 0x80), amount)
mstore(add(ptr, 0xa0), intentAddress)
mstore(add(ptr, 0xc0), deadline)
mstore(add(ptr, 0xe0), chainid())
mstore(add(ptr, 0x100), nonce)
mstore(add(ptr, 0x120), feeAmount)
mstore(add(ptr, 0x140), feeCollector)
intentHash := keccak256(ptr, 0x160)
}

bytes32 _domainSeparator = DOMAIN_SEPARATOR;
Expand Down
40 changes: 21 additions & 19 deletions src/interfaces/ITrailsIntentEntrypoint.sol
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
pragma solidity ^0.8.30;

/// @title ITrailsIntentEntrypoint
/// @notice Interface for the TrailsIntentEntrypoint contract
interface ITrailsIntentEntrypoint {
// -------------------------------------------------------------------------
// Types
// -------------------------------------------------------------------------

struct Signature {
uint8 v;
bytes32 r;
bytes32 s;
}

// -------------------------------------------------------------------------
// Events
// -------------------------------------------------------------------------
Expand Down Expand Up @@ -55,12 +65,9 @@ interface ITrailsIntentEntrypoint {
/// @param nonce The nonce for this user
/// @param feeAmount The amount of fee to pay (0 for no fee, paid in same token)
/// @param feeCollector The address to receive the fee (address(0) for no fee)
/// @param permitV The permit signature v component
/// @param permitR The permit signature r component
/// @param permitS The permit signature s component
/// @param sigV The intent signature v component
/// @param sigR The intent signature r component
/// @param sigS The intent signature s component
/// @param description A description string for the intent
/// @param permitSig The permit signature (v, r, s)
/// @param intentSig The intent signature (v, r, s)
function depositToIntentWithPermit(
address user,
address token,
Expand All @@ -71,12 +78,9 @@ interface ITrailsIntentEntrypoint {
uint256 nonce,
uint256 feeAmount,
address feeCollector,
uint8 permitV,
bytes32 permitR,
bytes32 permitS,
uint8 sigV,
bytes32 sigR,
bytes32 sigS
string calldata description,
Signature calldata permitSig,
Signature calldata intentSig
) external;

/// @notice Deposit tokens to an intent address (requires prior approval)
Expand All @@ -88,9 +92,8 @@ interface ITrailsIntentEntrypoint {
/// @param nonce The nonce for this user
/// @param feeAmount The amount of fee to pay (0 for no fee, paid in same token)
/// @param feeCollector The address to receive the fee (address(0) for no fee)
/// @param sigV The intent signature v component
/// @param sigR The intent signature r component
/// @param sigS The intent signature s component
/// @param description A description string for the intent
/// @param intentSig The intent signature (v, r, s)
function depositToIntent(
address user,
address token,
Expand All @@ -100,8 +103,7 @@ interface ITrailsIntentEntrypoint {
uint256 nonce,
uint256 feeAmount,
address feeCollector,
uint8 sigV,
bytes32 sigR,
bytes32 sigS
string calldata description,
Signature calldata intentSig
) external;
}
Loading