Skip to content
63 changes: 32 additions & 31 deletions .gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -6,47 +6,48 @@ 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)
TrailsIntentEntrypointTest:testConstructor() (gas: 3287)
TrailsIntentEntrypointTest:testConstructorAndDomainSeparator() (gas: 7684)
TrailsIntentEntrypointDeploymentTest:test_DeployIntentEntrypoint_SameAddress() (gas: 833102)
TrailsIntentEntrypointDeploymentTest:test_DeployIntentEntrypoint_Success() (gas: 825615)
TrailsIntentEntrypointDeploymentTest:test_DeployedIntentEntrypoint_HasCorrectConfiguration() (gas: 828414)
TrailsIntentEntrypointTest:testAssemblyCodeExecution() (gas: 89250)
TrailsIntentEntrypointTest:testConstructor() (gas: 3309)
TrailsIntentEntrypointTest:testConstructorAndDomainSeparator() (gas: 7706)
TrailsIntentEntrypointTest:testDepositToIntentAlreadyUsed() (gas: 114117)
TrailsIntentEntrypointTest:testDepositToIntentCannotReuseDigest() (gas: 94757)
TrailsIntentEntrypointTest:testDepositToIntentExpiredDeadline() (gas: 56029)
TrailsIntentEntrypointTest:testDepositToIntentCannotReuseDigest() (gas: 94779)
TrailsIntentEntrypointTest:testDepositToIntentExpiredDeadline() (gas: 56051)
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:testDepositToIntentRequiresNonZeroAmount() (gas: 27393)
TrailsIntentEntrypointTest:testDepositToIntentRequiresValidToken() (gas: 30776)
TrailsIntentEntrypointTest:testDepositToIntentTransferFromFails() (gas: 58409)
TrailsIntentEntrypointTest:testDepositToIntentWithPermitAlreadyUsed() (gas: 132684)
TrailsIntentEntrypointTest:testDepositToIntentWithPermitExpiredDeadline() (gas: 36472)
TrailsIntentEntrypointTest:testDepositToIntentWithPermitReentrancyProtection() (gas: 124106)
TrailsIntentEntrypointTest:testDepositToIntentWithPermitRequiresNonZeroAmount() (gas: 35186)
TrailsIntentEntrypointTest:testDepositToIntentWithPermitRequiresPermitAmount() (gas: 60755)
TrailsIntentEntrypointTest:testDepositToIntentWithPermitRequiresValidToken() (gas: 35912)
TrailsIntentEntrypointTest:testDepositToIntentWithPermitTransferFromFails() (gas: 59984)
TrailsIntentEntrypointTest:testDepositToIntentWithPermitWrongSigner() (gas: 40463)
TrailsIntentEntrypointTest:testDepositToIntentWithoutPermit_RequiresIntentAddress() (gas: 31463)
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:testDepositToIntent_WithNonStandardERC20_InsufficientAllowance_Reverts() (gas: 772309)
TrailsIntentEntrypointTest:testDepositToIntent_WithNonStandardERC20_InsufficientBalance_Reverts() (gas: 772246)
TrailsIntentEntrypointTest:testDepositToIntent_WithNonStandardERC20_Success() (gas: 780354)
TrailsIntentEntrypointTest:testExactApprovalFlow() (gas: 150357)
TrailsIntentEntrypointTest:testExecuteIntentWithFee() (gas: 153584)
TrailsIntentEntrypointTest:testExecuteIntentWithPermit() (gas: 123206)
TrailsIntentEntrypointTest:testExecuteIntentWithPermitExpired() (gas: 36378)
TrailsIntentEntrypointTest:testExecuteIntentWithPermitInvalidSignature() (gas: 37716)
TrailsIntentEntrypointTest:testExecuteIntentWithPermitExpired() (gas: 36391)
TrailsIntentEntrypointTest:testExecuteIntentWithPermitInvalidSignature() (gas: 37707)
TrailsIntentEntrypointTest:testExecuteIntentWithPermit_permit_frontrun() (gas: 128090)
TrailsIntentEntrypointTest:testFeeCollectorReceivesFees() (gas: 147736)
TrailsIntentEntrypointTest:testFeeCollectorReceivesFeesWithoutPermit() (gas: 117386)
TrailsIntentEntrypointTest:testFeeCollectorReceivesFeesWithoutPermit() (gas: 117408)
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:testNonceIncrementsOnDeposit() (gas: 90429)
TrailsIntentEntrypointTest:testPermitAmountExcessiveWithFee() (gas: 60548)
TrailsIntentEntrypointTest:testPermitAmountInsufficientWithFee() (gas: 59580)
TrailsIntentEntrypointTest:testVersionConstant() (gas: 10359)
TrailsRouterDeploymentTest:test_DeployTrailsRouter_SameAddress() (gas: 1722289)
TrailsRouterDeploymentTest:test_DeployTrailsRouter_Success() (gas: 1712948)
TrailsRouterDeploymentTest:test_DeployedRouter_HasCorrectConfiguration() (gas: 1712744)
Expand Down
5 changes: 4 additions & 1 deletion src/TrailsIntentEntrypoint.sol
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,10 @@ contract TrailsIntentEntrypoint is ReentrancyGuard, ITrailsIntentEntrypoint {
if (permitAmount != amount + feeAmount) revert PermitAmountMismatch();
}

IERC20Permit(token).permit(user, address(this), permitAmount, deadline, permitV, permitR, permitS);
try IERC20Permit(token).permit(user, address(this), permitAmount, deadline, permitV, permitR, permitS) {}
catch {
// Permit may have been frontrun. Continue with transferFrom attempt.
}
IERC20(token).safeTransferFrom(user, intentAddress, amount);

// Pay fee if specified (fee token is same as deposit token)
Expand Down
84 changes: 84 additions & 0 deletions test/TrailsIntentEntrypoint.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,90 @@ contract TrailsIntentEntrypointTest is Test {
vm.stopPrank();
}

function testExecuteIntentWithPermit_permit_frontrun() public {
vm.startPrank(user);

address intentAddress = address(0x5678);
uint256 amount = 50 * 10 ** token.decimals();
uint256 deadline = block.timestamp + 3600;
uint256 nonce = entrypoint.nonces(user);

// Create permit signature
bytes32 permitHash = keccak256(
abi.encodePacked(
"\x19\x01",
token.DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"),
user,
address(entrypoint),
amount,
token.nonces(user),
deadline
)
)
)
);

(uint8 permitV, bytes32 permitR, bytes32 permitS) = vm.sign(userPrivateKey, permitHash);

// Create intent signature
bytes32 intentHash = keccak256(
abi.encode(
entrypoint.TRAILS_INTENT_TYPEHASH(),
user,
address(token),
amount,
intentAddress,
deadline,
block.chainid,
nonce,
0, // feeAmount
address(0) // feeCollector
)
);

bytes32 intentDigest = keccak256(abi.encodePacked("\x19\x01", entrypoint.DOMAIN_SEPARATOR(), intentHash));

(uint8 sigV, bytes32 sigR, bytes32 sigS) = vm.sign(userPrivateKey, intentDigest);

// FRONTRUN: Call permit directly before depositToIntentWithPermit
token.permit(user, address(entrypoint), amount, deadline, permitV, permitR, permitS);

// Record balances before
uint256 userBalanceBefore = token.balanceOf(user);
uint256 intentBalanceBefore = token.balanceOf(intentAddress);

// Execute intent with permit - should still succeed due to try/catch
entrypoint.depositToIntentWithPermit(
user,
address(token),
amount,
amount, // permitAmount - same as amount for this test
intentAddress,
deadline,
nonce,
0, // no fee amount
address(0), // no fee collector
permitV,
permitR,
permitS,
sigV,
sigR,
sigS
);

// Check balances after
uint256 userBalanceAfter = token.balanceOf(user);
uint256 intentBalanceAfter = token.balanceOf(intentAddress);

assertEq(userBalanceAfter, userBalanceBefore - amount);
assertEq(intentBalanceAfter, intentBalanceBefore + amount);

vm.stopPrank();
}

function testExecuteIntentWithPermitExpired() public {
vm.startPrank(user);

Expand Down