Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
5 changes: 0 additions & 5 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: |
Expand Down
11 changes: 11 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 5 additions & 4 deletions snapshots/inputSettler.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,21 @@
"CompactFinaliseFor": "101978",
"CompactFinaliseSelf": "94549",
"CompactFinaliseTo": "94549",
"EscrowFinalise": "44316",
"EscrowFinalise": "69922",
"IntegrationCoinFill": "64965",
"IntegrationCompactFinaliseSelf": "85359",
"IntegrationWormholeReceiveMessage": "45665",
"IntegrationWormholeSubmit": "13837",
"broadcast": "14904",
"compactFinaliseSelfWithFee": "164087",
"depositAndRegisterFor": "129887",
"escrowFinaliseSelfWithFee": "106519",
"escrowFinaliseWithSignature": "51505",
"escrowFinaliseSelfWithFee": "106455",
"escrowFinaliseWithSignature": "77111",
"escrowOpen": "56999",
"escrowOpenFor3009Single": "85640",
"escrowOpenFor3009SingleArray": "90796",
"escrowOpenFor3009Two": "140807",
"escrowOpenForMsgSender": "57219",
"escrowOpenForPermit2": "94897"
"escrowOpenForPermit2": "94897",
"tronEscrowFinaliseSelfWithFee": "133263"
}
10 changes: 7 additions & 3 deletions src/input/escrow/InputSettlerEscrowLIFI.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}

Expand Down Expand Up @@ -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);
}
}
22 changes: 22 additions & 0 deletions src/input/escrow/InputSettlerEscrowLIFI.tron.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.25;

import { SafeTransferLibTron } from "../../libs/SafeTransferLib.tron.sol";
import { InputSettlerEscrowLIFI } from "./InputSettlerEscrowLIFI.sol";

/// @title LIFI Input Settler Escrow for Tron
/// @dev Overrides _transfer to use SafeTransferLibTron, which replaces broken transfer()
/// calls with an approve + transferFrom pattern for Tron USDT compatibility.
contract InputSettlerEscrowLIFITron is InputSettlerEscrowLIFI {
constructor(
address initialOwner
) InputSettlerEscrowLIFI(initialOwner) { }

function _domainName() internal pure override returns (string memory) {
return "OIFEscrowLIFITron";
}

function _transfer(address token, address to, uint256 amount) internal override {
SafeTransferLibTron.safeTransfer(token, to, amount);
}
}
38 changes: 38 additions & 0 deletions src/libs/SafeTransferLib.tron.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.25;

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)
}
}
}
190 changes: 190 additions & 0 deletions test/input/escrow/InputSettlerEscrowLIFI.tron.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.25;

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);
}
}
Loading
Loading