From faa3ed38df8732421dc1e73127cb87cdae8570e8 Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 4 May 2026 17:39:12 +0200 Subject: [PATCH 1/5] Fix transfer for USDT on Tron with Tron specific contracts --- snapshots/inputSettler.json | 39 ++-- src/input/escrow/InputSettlerEscrowLIFI.sol | 2 +- .../escrow/InputSettlerEscrowLIFI.tron.sol | 50 +++++ src/libs/SafeTransferLib.tron.sol | 38 ++++ .../escrow/InputSettlerEscrowLIFI.tron.t.sol | 190 ++++++++++++++++++ test/libs/SafeTransferLib.tron.t.sol | 137 +++++++++++++ test/mocks/MockUSDT.tron.sol | 22 ++ 7 files changed, 458 insertions(+), 20 deletions(-) create mode 100644 src/input/escrow/InputSettlerEscrowLIFI.tron.sol create mode 100644 src/libs/SafeTransferLib.tron.sol create mode 100644 test/input/escrow/InputSettlerEscrowLIFI.tron.t.sol create mode 100644 test/libs/SafeTransferLib.tron.t.sol create mode 100644 test/mocks/MockUSDT.tron.sol diff --git a/snapshots/inputSettler.json b/snapshots/inputSettler.json index 2b3673f..e908e31 100644 --- a/snapshots/inputSettler.json +++ b/snapshots/inputSettler.json @@ -1,21 +1,22 @@ { - "CompactFinaliseFor": "101978", - "CompactFinaliseSelf": "94549", - "CompactFinaliseTo": "94549", - "EscrowFinalise": "44316", - "IntegrationCoinFill": "64965", - "IntegrationCompactFinaliseSelf": "85359", - "IntegrationWormholeReceiveMessage": "45665", - "IntegrationWormholeSubmit": "13837", - "broadcast": "14904", - "compactFinaliseSelfWithFee": "164087", - "depositAndRegisterFor": "129887", - "escrowFinaliseSelfWithFee": "106519", - "escrowFinaliseWithSignature": "51505", - "escrowOpen": "56999", - "escrowOpenFor3009Single": "85640", - "escrowOpenFor3009SingleArray": "90796", - "escrowOpenFor3009Two": "140807", - "escrowOpenForMsgSender": "57219", - "escrowOpenForPermit2": "94897" + "CompactFinaliseFor": "121475", + "CompactFinaliseSelf": "113064", + "CompactFinaliseTo": "113064", + "EscrowFinalise": "82167", + "IntegrationCoinFill": "72125", + "IntegrationCompactFinaliseSelf": "104441", + "IntegrationWormholeReceiveMessage": "50882", + "IntegrationWormholeSubmit": "19569", + "broadcast": "24664", + "compactFinaliseSelfWithFee": "183219", + "depositAndRegisterFor": "136929", + "escrowFinaliseSelfWithFee": "117556", + "escrowFinaliseWithSignature": "90557", + "escrowOpen": "65150", + "escrowOpenFor3009Single": "97550", + "escrowOpenFor3009SingleArray": "105900", + "escrowOpenFor3009Two": "156939", + "escrowOpenForMsgSender": "65951", + "escrowOpenForPermit2": "105531", + "tronEscrowFinaliseSelfWithFee": "147414" } \ No newline at end of file diff --git a/src/input/escrow/InputSettlerEscrowLIFI.sol b/src/input/escrow/InputSettlerEscrowLIFI.sol index ff34a06..47c3250 100644 --- a/src/input/escrow/InputSettlerEscrowLIFI.sol +++ b/src/input/escrow/InputSettlerEscrowLIFI.sol @@ -44,7 +44,7 @@ contract InputSettlerEscrowLIFI is InputSettlerEscrow, GovernanceFee { * by the EIP712 base contract. * @return name The domain name. */ - function _domainName() internal pure override returns (string memory) { + function _domainName() internal pure virtual override returns (string memory) { return "OIFEscrowLIFI"; } diff --git a/src/input/escrow/InputSettlerEscrowLIFI.tron.sol b/src/input/escrow/InputSettlerEscrowLIFI.tron.sol new file mode 100644 index 0000000..571a2b6 --- /dev/null +++ b/src/input/escrow/InputSettlerEscrowLIFI.tron.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.26; + +import { SafeTransferLibTron } from "../../libs/SafeTransferLib.tron.sol"; +import { InputSettlerEscrowLIFI } from "./InputSettlerEscrowLIFI.sol"; +import { LibAddress } from "OIF/src/libs/LibAddress.sol"; + +/// @title LIFI Input Settler Escrow for Tron +/// @dev Overrides _resolveLock to use SafeTransferLibTron, which replaces broken transfer() +/// calls with an approve + transferFrom pattern for Tron USDT compatibility. +contract InputSettlerEscrowLIFITron is InputSettlerEscrowLIFI { + using LibAddress for uint256; + + constructor( + address initialOwner + ) InputSettlerEscrowLIFI(initialOwner) { } + + function _domainName() internal pure override returns (string memory) { + return "OIFEscrowLIFITron"; + } + + function _resolveLock( + bytes32 orderId, + uint256[2][] calldata inputs, + address destination, + OrderStatus newStatus + ) internal virtual override { + if (orderStatus[orderId] != OrderStatus.Deposited) revert InvalidOrderStatus(); + orderStatus[orderId] = newStatus; + + address _owner = owner(); + uint64 fee = _owner != address(0) ? governanceFee : 0; + uint256 numInputs = inputs.length; + for (uint256 i; i < numInputs; ++i) { + uint256[2] memory input = inputs[i]; + address token = input[0].fromIdentifier(); + uint256 amount = input[1]; + + uint256 calculatedFee = _calcFee(amount, fee); + if (calculatedFee > 0) { + SafeTransferLibTron.safeTransfer(token, _owner, calculatedFee); + unchecked { + amount = amount - calculatedFee; + } + } + + SafeTransferLibTron.safeTransfer(token, destination, amount); + } + } +} diff --git a/src/libs/SafeTransferLib.tron.sol b/src/libs/SafeTransferLib.tron.sol new file mode 100644 index 0000000..83a8cd4 --- /dev/null +++ b/src/libs/SafeTransferLib.tron.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.26; + +import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; + +/// @notice Wrapper around Solady's SafeTransferLib for Tron-deployed contracts. +/// @dev Tron USDT's transfer() returns false on success, causing Solady's safeTransfer to revert. +/// This library replaces transfer() with an approve + transferFrom pattern since those functions +/// return true correctly on Tron USDT. +library SafeTransferLibTron { + function safeTransfer( + address token, + address to, + uint256 amount + ) internal { + if (_selfAllowance(token) < amount) { + SafeTransferLib.safeApproveWithRetry(token, address(this), type(uint256).max); + } + SafeTransferLib.safeTransferFrom(token, address(this), to, amount); + } + + function _selfAllowance( + address token + ) private view returns (uint256 result) { + assembly ("memory-safe") { + let m := mload(0x40) + let self := address() + mstore(0x34, self) + mstore(0x14, self) + mstore(0x00, 0xdd62ed3e000000000000000000000000) // allowance(address,address) + result := mul( + mload(0x00), + and(gt(returndatasize(), 0x1f), staticcall(gas(), token, 0x10, 0x44, 0x00, 0x20)) + ) + mstore(0x40, m) + } + } +} diff --git a/test/input/escrow/InputSettlerEscrowLIFI.tron.t.sol b/test/input/escrow/InputSettlerEscrowLIFI.tron.t.sol new file mode 100644 index 0000000..17c009c --- /dev/null +++ b/test/input/escrow/InputSettlerEscrowLIFI.tron.t.sol @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.22; + +import { InputSettlerEscrowLIFI } from "../../../src/input/escrow/InputSettlerEscrowLIFI.sol"; +import { InputSettlerEscrowLIFITron } from "../../../src/input/escrow/InputSettlerEscrowLIFI.tron.sol"; +import { InputSettlerBase } from "OIF/src/input/InputSettlerBase.sol"; + +import { StandardOrder } from "OIF/src/input/types/StandardOrderType.sol"; +import { MandateOutput, MandateOutputEncodingLib } from "OIF/src/libs/MandateOutputEncodingLib.sol"; + +import { InputSettlerEscrowTest } from "OIF/test/input/escrow/InputSettlerEscrow.t.sol"; + +import { MockTronUSDT } from "../../mocks/MockUSDT.tron.sol"; + +contract InputSettlerEscrowLIFITronHarness is InputSettlerEscrowLIFITron { + constructor( + address initialOwner + ) InputSettlerEscrowLIFITron(initialOwner) { } + + function validateFillsNow( + address inputOracle, + MandateOutput[] calldata outputs, + bytes32 orderId + ) external view { + _validateFillsNow(inputOracle, outputs, orderId); + } +} + +contract InputSettlerEscrowLIFITronTest is InputSettlerEscrowTest { + function setUp() public virtual override { + super.setUp(); + + owner = makeAddr("owner"); + inputSettlerEscrow = address(new InputSettlerEscrowLIFITronHarness(owner)); + + // Replace tokens with MockTronUSDT to simulate Tron USDT behavior + token = new MockTronUSDT("Tron USDT", "USDT", 6); + anotherToken = new MockTronUSDT("Tron USDT2", "USDT2", 6); + + token.mint(swapper, 1e18); + anotherToken.mint(solver, 1e18); + + vm.prank(swapper); + token.approve(address(permit2), type(uint256).max); + vm.prank(solver); + anotherToken.approve(address(outputSettlerCoin), type(uint256).max); + } + + /// forge-config: default.isolate = true + function test_finalise_self_with_fee_gas() public { + test_finalise_self_with_fee(MAX_GOVERNANCE_FEE / 3); + } + + function test_finalise_self_with_fee( + uint64 fee + ) public { + vm.assume(fee <= MAX_GOVERNANCE_FEE); + vm.prank(owner); + InputSettlerEscrowLIFI(inputSettlerEscrow).setGovernanceFee(fee); + vm.warp(uint32(block.timestamp) + GOVERNANCE_FEE_CHANGE_DELAY + 1); + InputSettlerEscrowLIFI(inputSettlerEscrow).applyGovernanceFee(); + + uint256 amount = 1e18 / 10; + address inputOracle = address(alwaysYesOracle); + + MandateOutput[] memory outputs = new MandateOutput[](1); + outputs[0] = MandateOutput({ + settler: bytes32(uint256(uint160(address(outputSettlerCoin)))), + oracle: bytes32(uint256(uint160(inputOracle))), + chainId: block.chainid, + token: bytes32(uint256(uint160(address(anotherToken)))), + amount: amount, + recipient: bytes32(uint256(uint160(swapper))), + callbackData: hex"", + context: hex"" + }); + uint256[2][] memory inputs = new uint256[2][](1); + inputs[0] = [uint256(uint160(address(token))), amount]; + + StandardOrder memory order = StandardOrder({ + user: swapper, + nonce: 0, + originChainId: block.chainid, + expires: type(uint32).max, + fillDeadline: type(uint32).max, + inputOracle: inputOracle, + inputs: inputs, + outputs: outputs + }); + + vm.prank(swapper); + token.approve(inputSettlerEscrow, amount); + vm.prank(swapper); + InputSettlerEscrowLIFI(inputSettlerEscrow).open(order); + + bytes32 orderId = InputSettlerEscrowLIFI(inputSettlerEscrow).orderIdentifier(order); + bytes memory payload = MandateOutputEncodingLib.encodeFillDescriptionMemory( + bytes32(uint256(uint160((solver)))), orderId, uint32(block.timestamp), outputs[0] + ); + bytes32 payloadHash = keccak256(payload); + + vm.expectCall( + address(alwaysYesOracle), + abi.encodeWithSignature( + "efficientRequireProven(bytes)", + abi.encodePacked( + order.outputs[0].chainId, order.outputs[0].oracle, order.outputs[0].settler, payloadHash + ) + ) + ); + + InputSettlerBase.SolveParams[] memory solveParams = new InputSettlerBase.SolveParams[](1); + solveParams[0] = InputSettlerBase.SolveParams({ + timestamp: uint32(block.timestamp), solver: bytes32(uint256(uint160((solver)))) + }); + + vm.prank(solver); + InputSettlerEscrowLIFI(inputSettlerEscrow) + .finalise(order, solveParams, bytes32(uint256(uint160((solver)))), hex""); + vm.snapshotGasLastCall("inputSettler", "tronEscrowFinaliseSelfWithFee"); + + uint256 govFeeAmount = (amount * fee) / 10 ** 18; + uint256 amountPostFee = amount - govFeeAmount; + + assertEq(token.balanceOf(solver), amountPostFee); + assertEq(token.balanceOf(InputSettlerEscrowLIFI(inputSettlerEscrow).owner()), govFeeAmount); + } + + function test_finalise_self_no_fee() public { + uint256 amount = 1e18 / 10; + address inputOracle = address(alwaysYesOracle); + + MandateOutput[] memory outputs = new MandateOutput[](1); + outputs[0] = MandateOutput({ + settler: bytes32(uint256(uint160(address(outputSettlerCoin)))), + oracle: bytes32(uint256(uint160(inputOracle))), + chainId: block.chainid, + token: bytes32(uint256(uint160(address(anotherToken)))), + amount: amount, + recipient: bytes32(uint256(uint160(swapper))), + callbackData: hex"", + context: hex"" + }); + uint256[2][] memory inputs = new uint256[2][](1); + inputs[0] = [uint256(uint160(address(token))), amount]; + + StandardOrder memory order = StandardOrder({ + user: swapper, + nonce: 0, + originChainId: block.chainid, + expires: type(uint32).max, + fillDeadline: type(uint32).max, + inputOracle: inputOracle, + inputs: inputs, + outputs: outputs + }); + + vm.prank(swapper); + token.approve(inputSettlerEscrow, amount); + vm.prank(swapper); + InputSettlerEscrowLIFI(inputSettlerEscrow).open(order); + + bytes32 orderId = InputSettlerEscrowLIFI(inputSettlerEscrow).orderIdentifier(order); + bytes memory payload = MandateOutputEncodingLib.encodeFillDescriptionMemory( + bytes32(uint256(uint160((solver)))), orderId, uint32(block.timestamp), outputs[0] + ); + bytes32 payloadHash = keccak256(payload); + + vm.expectCall( + address(alwaysYesOracle), + abi.encodeWithSignature( + "efficientRequireProven(bytes)", + abi.encodePacked( + order.outputs[0].chainId, order.outputs[0].oracle, order.outputs[0].settler, payloadHash + ) + ) + ); + + InputSettlerBase.SolveParams[] memory solveParams = new InputSettlerBase.SolveParams[](1); + solveParams[0] = InputSettlerBase.SolveParams({ + timestamp: uint32(block.timestamp), solver: bytes32(uint256(uint160((solver)))) + }); + + vm.prank(solver); + InputSettlerEscrowLIFI(inputSettlerEscrow) + .finalise(order, solveParams, bytes32(uint256(uint160((solver)))), hex""); + + assertEq(token.balanceOf(solver), amount); + } +} diff --git a/test/libs/SafeTransferLib.tron.t.sol b/test/libs/SafeTransferLib.tron.t.sol new file mode 100644 index 0000000..b38dd3d --- /dev/null +++ b/test/libs/SafeTransferLib.tron.t.sol @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.22; + +import { Test } from "forge-std/Test.sol"; + +import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; + +import { SafeTransferLibTron } from "../../src/libs/SafeTransferLib.tron.sol"; +import { MockTronUSDT } from "../mocks/MockUSDT.tron.sol"; +import { MockERC20 } from "OIF/test/mocks/MockERC20.sol"; + +contract SafeTransferLibTronHarness { + function safeTransfer( + address token, + address to, + uint256 amount + ) external { + SafeTransferLibTron.safeTransfer(token, to, amount); + } +} + +contract SoladySafeTransferLibHarness { + function safeTransfer( + address token, + address to, + uint256 amount + ) external { + SafeTransferLib.safeTransfer(token, to, amount); + } +} + +contract SafeTransferLibTronTest is Test { + SafeTransferLibTronHarness harness; + SoladySafeTransferLibHarness soladyHarness; + MockTronUSDT tronToken; + MockERC20 normalToken; + address recipient; + + function setUp() public { + harness = new SafeTransferLibTronHarness(); + soladyHarness = new SoladySafeTransferLibHarness(); + tronToken = new MockTronUSDT("Tron USDT", "USDT", 6); + normalToken = new MockERC20("Normal Token", "NORM", 18); + recipient = makeAddr("recipient"); + } + + function test_safeTransfer_normalToken() public { + uint256 amount = 1e18; + normalToken.mint(address(harness), amount); + + harness.safeTransfer(address(normalToken), recipient, amount); + + assertEq(normalToken.balanceOf(recipient), amount); + assertEq(normalToken.balanceOf(address(harness)), 0); + } + + function test_safeTransfer_tronToken() public { + uint256 amount = 1e6; + tronToken.mint(address(harness), amount); + + harness.safeTransfer(address(tronToken), recipient, amount); + + assertEq(tronToken.balanceOf(recipient), amount); + assertEq(tronToken.balanceOf(address(harness)), 0); + } + + function test_safeTransfer_tronToken_fuzz( + uint256 amount + ) public { + vm.assume(amount > 0 && amount < type(uint128).max); + tronToken.mint(address(harness), amount); + + harness.safeTransfer(address(tronToken), recipient, amount); + + assertEq(tronToken.balanceOf(recipient), amount); + assertEq(tronToken.balanceOf(address(harness)), 0); + } + + function test_safeTransfer_zeroAmount() public { + tronToken.mint(address(harness), 1e6); + + harness.safeTransfer(address(tronToken), recipient, 0); + + assertEq(tronToken.balanceOf(recipient), 0); + assertEq(tronToken.balanceOf(address(harness)), 1e6); + } + + function test_safeTransfer_reverts_insufficientBalance() public { + vm.expectRevert(); + harness.safeTransfer(address(tronToken), recipient, 1e6); + } + + function test_safeTransfer_maxApprovalReused() public { + uint256 amount = 1e6; + tronToken.mint(address(harness), amount * 3); + + harness.safeTransfer(address(tronToken), recipient, amount); + // OZ ERC20 doesn't decrease infinite approval + uint256 allowanceAfterFirst = tronToken.allowance(address(harness), address(harness)); + assertEq(allowanceAfterFirst, type(uint256).max); + + harness.safeTransfer(address(tronToken), recipient, amount); + harness.safeTransfer(address(tronToken), recipient, amount); + + assertEq(tronToken.balanceOf(recipient), amount * 3); + assertEq(tronToken.balanceOf(address(harness)), 0); + } + + function test_safeTransfer_normalToken_directTransferUsedFirst() public { + uint256 amount = 1e18; + normalToken.mint(address(harness), amount); + + harness.safeTransfer(address(normalToken), recipient, amount); + + assertEq(normalToken.balanceOf(recipient), amount); + } + + function test_soladySafeTransfer_reverts_tronToken() public { + uint256 amount = 1e6; + tronToken.mint(address(soladyHarness), amount); + + vm.expectRevert(SafeTransferLib.TransferFailed.selector); + soladyHarness.safeTransfer(address(tronToken), recipient, amount); + } + + function test_safeTransfer_tronToken_multipleRecipients() public { + address recipient2 = makeAddr("recipient2"); + uint256 amount = 1e6; + tronToken.mint(address(harness), amount * 2); + + harness.safeTransfer(address(tronToken), recipient, amount); + harness.safeTransfer(address(tronToken), recipient2, amount); + + assertEq(tronToken.balanceOf(recipient), amount); + assertEq(tronToken.balanceOf(recipient2), amount); + } +} diff --git a/test/mocks/MockUSDT.tron.sol b/test/mocks/MockUSDT.tron.sol new file mode 100644 index 0000000..f5b17ac --- /dev/null +++ b/test/mocks/MockUSDT.tron.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.22; + +import { MockERC20 } from "OIF/test/mocks/MockERC20.sol"; + +/// @notice Mock ERC20 mimicking Tron USDT behavior: transfer() returns false on success. +/// @dev approve() and transferFrom() work normally and return true. +contract MockTronUSDT is MockERC20 { + constructor( + string memory name_, + string memory symbol_, + uint8 decimals_ + ) MockERC20(name_, symbol_, decimals_) { } + + function transfer( + address to, + uint256 amount + ) public virtual override returns (bool) { + super.transfer(to, amount); + return false; + } +} From 3baa0f421f9f97608a9c998681e174a5d7a38860 Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 4 May 2026 17:47:03 +0200 Subject: [PATCH 2/5] Update snapshot --- snapshots/inputSettler.json | 40 ++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/snapshots/inputSettler.json b/snapshots/inputSettler.json index e908e31..a4998e6 100644 --- a/snapshots/inputSettler.json +++ b/snapshots/inputSettler.json @@ -1,22 +1,22 @@ { - "CompactFinaliseFor": "121475", - "CompactFinaliseSelf": "113064", - "CompactFinaliseTo": "113064", - "EscrowFinalise": "82167", - "IntegrationCoinFill": "72125", - "IntegrationCompactFinaliseSelf": "104441", - "IntegrationWormholeReceiveMessage": "50882", - "IntegrationWormholeSubmit": "19569", - "broadcast": "24664", - "compactFinaliseSelfWithFee": "183219", - "depositAndRegisterFor": "136929", - "escrowFinaliseSelfWithFee": "117556", - "escrowFinaliseWithSignature": "90557", - "escrowOpen": "65150", - "escrowOpenFor3009Single": "97550", - "escrowOpenFor3009SingleArray": "105900", - "escrowOpenFor3009Two": "156939", - "escrowOpenForMsgSender": "65951", - "escrowOpenForPermit2": "105531", - "tronEscrowFinaliseSelfWithFee": "147414" + "CompactFinaliseFor": "101978", + "CompactFinaliseSelf": "94549", + "CompactFinaliseTo": "94549", + "EscrowFinalise": "44316", + "IntegrationCoinFill": "64965", + "IntegrationCompactFinaliseSelf": "85359", + "IntegrationWormholeReceiveMessage": "45665", + "IntegrationWormholeSubmit": "13837", + "broadcast": "14904", + "compactFinaliseSelfWithFee": "164087", + "depositAndRegisterFor": "129887", + "escrowFinaliseSelfWithFee": "106519", + "escrowFinaliseWithSignature": "51505", + "escrowOpen": "56999", + "escrowOpenFor3009Single": "85640", + "escrowOpenFor3009SingleArray": "90796", + "escrowOpenFor3009Two": "140807", + "escrowOpenForMsgSender": "57219", + "escrowOpenForPermit2": "94897", + "tronEscrowFinaliseSelfWithFee": "133336" } \ No newline at end of file From c1104d7f55e8c960b424319d5b49f765ff192f45 Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 5 May 2026 12:20:30 +0200 Subject: [PATCH 3/5] Use an overwrite function for transfers --- snapshots/inputSettler.json | 18 +++------- src/input/escrow/InputSettlerEscrowLIFI.sol | 8 +++-- .../escrow/InputSettlerEscrowLIFI.tron.sol | 34 ++----------------- 3 files changed, 13 insertions(+), 47 deletions(-) diff --git a/snapshots/inputSettler.json b/snapshots/inputSettler.json index a4998e6..b4f1a7f 100644 --- a/snapshots/inputSettler.json +++ b/snapshots/inputSettler.json @@ -1,22 +1,12 @@ { - "CompactFinaliseFor": "101978", - "CompactFinaliseSelf": "94549", - "CompactFinaliseTo": "94549", - "EscrowFinalise": "44316", - "IntegrationCoinFill": "64965", - "IntegrationCompactFinaliseSelf": "85359", - "IntegrationWormholeReceiveMessage": "45665", - "IntegrationWormholeSubmit": "13837", - "broadcast": "14904", - "compactFinaliseSelfWithFee": "164087", - "depositAndRegisterFor": "129887", - "escrowFinaliseSelfWithFee": "106519", - "escrowFinaliseWithSignature": "51505", + "EscrowFinalise": "44305", + "escrowFinaliseSelfWithFee": "106455", + "escrowFinaliseWithSignature": "51494", "escrowOpen": "56999", "escrowOpenFor3009Single": "85640", "escrowOpenFor3009SingleArray": "90796", "escrowOpenFor3009Two": "140807", "escrowOpenForMsgSender": "57219", "escrowOpenForPermit2": "94897", - "tronEscrowFinaliseSelfWithFee": "133336" + "tronEscrowFinaliseSelfWithFee": "133263" } \ No newline at end of file diff --git a/src/input/escrow/InputSettlerEscrowLIFI.sol b/src/input/escrow/InputSettlerEscrowLIFI.sol index 47c3250..34ec0e5 100644 --- a/src/input/escrow/InputSettlerEscrowLIFI.sol +++ b/src/input/escrow/InputSettlerEscrowLIFI.sol @@ -228,13 +228,17 @@ contract InputSettlerEscrowLIFI is InputSettlerEscrow, GovernanceFee { uint256 calculatedFee = _calcFee(amount, fee); if (calculatedFee > 0) { - SafeTransferLib.safeTransfer(token, _owner, calculatedFee); + _transfer(token, _owner, calculatedFee); unchecked { amount = amount - calculatedFee; } } - SafeTransferLib.safeTransfer(token, destination, amount); + _transfer(token, destination, amount); } } + + function _transfer(address token, address to, uint256 amount) internal virtual { + SafeTransferLib.safeTransfer(token, to, amount); + } } diff --git a/src/input/escrow/InputSettlerEscrowLIFI.tron.sol b/src/input/escrow/InputSettlerEscrowLIFI.tron.sol index 571a2b6..6a54bc1 100644 --- a/src/input/escrow/InputSettlerEscrowLIFI.tron.sol +++ b/src/input/escrow/InputSettlerEscrowLIFI.tron.sol @@ -3,14 +3,11 @@ pragma solidity ^0.8.26; import { SafeTransferLibTron } from "../../libs/SafeTransferLib.tron.sol"; import { InputSettlerEscrowLIFI } from "./InputSettlerEscrowLIFI.sol"; -import { LibAddress } from "OIF/src/libs/LibAddress.sol"; /// @title LIFI Input Settler Escrow for Tron -/// @dev Overrides _resolveLock to use SafeTransferLibTron, which replaces broken transfer() +/// @dev Overrides _transfer to use SafeTransferLibTron, which replaces broken transfer() /// calls with an approve + transferFrom pattern for Tron USDT compatibility. contract InputSettlerEscrowLIFITron is InputSettlerEscrowLIFI { - using LibAddress for uint256; - constructor( address initialOwner ) InputSettlerEscrowLIFI(initialOwner) { } @@ -19,32 +16,7 @@ contract InputSettlerEscrowLIFITron is InputSettlerEscrowLIFI { return "OIFEscrowLIFITron"; } - function _resolveLock( - bytes32 orderId, - uint256[2][] calldata inputs, - address destination, - OrderStatus newStatus - ) internal virtual override { - if (orderStatus[orderId] != OrderStatus.Deposited) revert InvalidOrderStatus(); - orderStatus[orderId] = newStatus; - - address _owner = owner(); - uint64 fee = _owner != address(0) ? governanceFee : 0; - uint256 numInputs = inputs.length; - for (uint256 i; i < numInputs; ++i) { - uint256[2] memory input = inputs[i]; - address token = input[0].fromIdentifier(); - uint256 amount = input[1]; - - uint256 calculatedFee = _calcFee(amount, fee); - if (calculatedFee > 0) { - SafeTransferLibTron.safeTransfer(token, _owner, calculatedFee); - unchecked { - amount = amount - calculatedFee; - } - } - - SafeTransferLibTron.safeTransfer(token, destination, amount); - } + function _transfer(address token, address to, uint256 amount) internal override { + SafeTransferLibTron.safeTransfer(token, to, amount); } } From c90e764e5edeb24e0a435a8f08ac9541be32186f Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 5 May 2026 12:33:10 +0200 Subject: [PATCH 4/5] Set Tron pragma to 0.8.25 --- foundry.toml | 11 +++++++++++ snapshots/inputSettler.json | 14 ++++++++++++-- src/input/escrow/InputSettlerEscrowLIFI.tron.sol | 2 +- src/libs/SafeTransferLib.tron.sol | 2 +- .../input/escrow/InputSettlerEscrowLIFI.tron.t.sol | 2 +- test/libs/SafeTransferLib.tron.t.sol | 2 +- test/mocks/MockUSDT.tron.sol | 2 +- 7 files changed, 28 insertions(+), 7 deletions(-) diff --git a/foundry.toml b/foundry.toml index 6c2d3d5..c58035f 100644 --- a/foundry.toml +++ b/foundry.toml @@ -14,6 +14,17 @@ fs_permissions = [{ access = "read-write", path = "./script/wormhole.json" }, { # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options +[profile.tron] +src = "src" +out = "out" +libs = ["lib"] +via_ir = true +solc_version = "0.8.25" +evm_version = "london" +optimizer = true +optimizer_runs = 100_000_000 +bytecode_hash = 'none' + [fmt] sort_imports = true bracket_spacing = true diff --git a/snapshots/inputSettler.json b/snapshots/inputSettler.json index b4f1a7f..076afde 100644 --- a/snapshots/inputSettler.json +++ b/snapshots/inputSettler.json @@ -1,7 +1,17 @@ { - "EscrowFinalise": "44305", + "CompactFinaliseFor": "101978", + "CompactFinaliseSelf": "94549", + "CompactFinaliseTo": "94549", + "EscrowFinalise": "69922", + "IntegrationCoinFill": "64965", + "IntegrationCompactFinaliseSelf": "85359", + "IntegrationWormholeReceiveMessage": "45665", + "IntegrationWormholeSubmit": "13837", + "broadcast": "14904", + "compactFinaliseSelfWithFee": "164087", + "depositAndRegisterFor": "129887", "escrowFinaliseSelfWithFee": "106455", - "escrowFinaliseWithSignature": "51494", + "escrowFinaliseWithSignature": "77111", "escrowOpen": "56999", "escrowOpenFor3009Single": "85640", "escrowOpenFor3009SingleArray": "90796", diff --git a/src/input/escrow/InputSettlerEscrowLIFI.tron.sol b/src/input/escrow/InputSettlerEscrowLIFI.tron.sol index 6a54bc1..2d99f30 100644 --- a/src/input/escrow/InputSettlerEscrowLIFI.tron.sol +++ b/src/input/escrow/InputSettlerEscrowLIFI.tron.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: LGPL-3.0-only -pragma solidity ^0.8.26; +pragma solidity ^0.8.25; import { SafeTransferLibTron } from "../../libs/SafeTransferLib.tron.sol"; import { InputSettlerEscrowLIFI } from "./InputSettlerEscrowLIFI.sol"; diff --git a/src/libs/SafeTransferLib.tron.sol b/src/libs/SafeTransferLib.tron.sol index 83a8cd4..ee836ad 100644 --- a/src/libs/SafeTransferLib.tron.sol +++ b/src/libs/SafeTransferLib.tron.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: LGPL-3.0-only -pragma solidity ^0.8.26; +pragma solidity ^0.8.25; import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; diff --git a/test/input/escrow/InputSettlerEscrowLIFI.tron.t.sol b/test/input/escrow/InputSettlerEscrowLIFI.tron.t.sol index 17c009c..8dd9b92 100644 --- a/test/input/escrow/InputSettlerEscrowLIFI.tron.t.sol +++ b/test/input/escrow/InputSettlerEscrowLIFI.tron.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: LGPL-3.0-only -pragma solidity ^0.8.22; +pragma solidity ^0.8.25; import { InputSettlerEscrowLIFI } from "../../../src/input/escrow/InputSettlerEscrowLIFI.sol"; import { InputSettlerEscrowLIFITron } from "../../../src/input/escrow/InputSettlerEscrowLIFI.tron.sol"; diff --git a/test/libs/SafeTransferLib.tron.t.sol b/test/libs/SafeTransferLib.tron.t.sol index b38dd3d..3e8e3ff 100644 --- a/test/libs/SafeTransferLib.tron.t.sol +++ b/test/libs/SafeTransferLib.tron.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.22; +pragma solidity ^0.8.25; import { Test } from "forge-std/Test.sol"; diff --git a/test/mocks/MockUSDT.tron.sol b/test/mocks/MockUSDT.tron.sol index f5b17ac..cd548bc 100644 --- a/test/mocks/MockUSDT.tron.sol +++ b/test/mocks/MockUSDT.tron.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.22; +pragma solidity ^0.8.25; import { MockERC20 } from "OIF/test/mocks/MockERC20.sol"; From b1a7f6da78cf08807b37d4e6cb906182c5ade1da Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 5 May 2026 12:48:47 +0200 Subject: [PATCH 5/5] Remove snapshot check from ci --- .github/workflows/test.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 522fa76..188b95f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -36,11 +36,6 @@ jobs: run: | forge test -vvv id: test - - - name: Run Forge Snapshots - run: | - FOUNDRY_FUZZ_RUNS=10 forge test --gas-snapshot-check true - id: snapshot - name: Run Forge Coverage run: |