Skip to content

Commit fd8da64

Browse files
committed
add swapAndStartBridgeTokensViaPolymerCCTP
1 parent 2b2c37c commit fd8da64

File tree

2 files changed

+161
-12
lines changed

2 files changed

+161
-12
lines changed

src/Facets/PolymerCCTPFacet.sol

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {ITokenMessenger} from "../Interfaces/ITokenMessenger.sol";
88
import {IPolymerCCTPFacet, PolymerCCTPData} from "../Interfaces/IPolymerCCTP.sol";
99
import {LiFiData} from "../Helpers/LiFiData.sol";
1010
import {LibAsset} from "../Libraries/LibAsset.sol";
11+
import {LibUtil} from "../Libraries/LibUtil.sol";
1112
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
1213
import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
1314
import {ILiFi} from "../Interfaces/ILiFi.sol";
@@ -28,6 +29,17 @@ contract PolymerCCTPFacet is IPolymerCCTPFacet, ILiFi, ReentrancyGuard, SwapperV
2829
address public immutable USDC;
2930
address payable public immutable POLYMER_FEE_RECEIVER;
3031

32+
modifier validatePolymerData(ILiFi.BridgeData memory _bridgeData, PolymerCCTPData calldata _polymerData) {
33+
if (_bridgeData.sendingAssetId != USDC) {
34+
revert InvalidSendingAsset(_bridgeData.sendingAssetId, USDC);
35+
}
36+
37+
if (_polymerData.polymerTokenFee >= _bridgeData.minAmount) {
38+
revert FeeCannotBeLessThanAmount();
39+
}
40+
_;
41+
}
42+
3143
constructor(address _tokenMessenger, address _usdc, address _polymerFeeReceiver) {
3244
// TODO: Do we want to have fee collector here?
3345

@@ -50,21 +62,53 @@ contract PolymerCCTPFacet is IPolymerCCTPFacet, ILiFi, ReentrancyGuard, SwapperV
5062
nonReentrant
5163
refundExcessNative(payable(msg.sender))
5264
validateBridgeData(_bridgeData)
65+
validatePolymerData(_bridgeData, _polymerData)
5366
onlyAllowSourceToken(_bridgeData, USDC)
5467
doesNotContainSourceSwaps(_bridgeData)
5568
doesNotContainDestinationCalls(_bridgeData)
5669
{
57-
// TODO - is it worth validating the integrator and bridge from the bridgeData here?
58-
if (_bridgeData.sendingAssetId != USDC) {
59-
revert InvalidSendingAsset(_bridgeData.sendingAssetId, USDC);
60-
}
61-
62-
if (_polymerData.polymerTokenFee >= _bridgeData.minAmount) {
63-
revert FeeCannotBeLessThanAmount();
64-
}
6570

6671
// TODO: Do we need this check if it's always going to be usdc?
6772
LibAsset.depositAsset(_bridgeData.sendingAssetId, _bridgeData.minAmount);
73+
74+
_startBridge(_bridgeData, _polymerData);
75+
}
76+
77+
/// @notice Performs a swap before bridging via PolymerCCTP
78+
/// @param _bridgeData The core information needed for bridging
79+
/// @param _swapData An array of swap related data for performing swaps before bridging
80+
/// @param _polymerData Data specific to PolymerCCTP
81+
function swapAndStartBridgeTokensViaPolymerCCTP(
82+
ILiFi.BridgeData memory _bridgeData,
83+
LibSwap.SwapData[] calldata _swapData,
84+
PolymerCCTPData calldata _polymerData
85+
)
86+
external
87+
payable
88+
nonReentrant
89+
refundExcessNative(payable(msg.sender))
90+
containsSourceSwaps(_bridgeData)
91+
doesNotContainDestinationCalls(_bridgeData)
92+
validateBridgeData(_bridgeData)
93+
validatePolymerData(_bridgeData, _polymerData)
94+
{
95+
_bridgeData.minAmount = _depositAndSwap(
96+
_bridgeData.transactionId,
97+
_bridgeData.minAmount,
98+
_swapData,
99+
payable(msg.sender)
100+
);
101+
102+
_startBridge(_bridgeData, _polymerData);
103+
}
104+
105+
/// @dev Contains the business logic for the bridge via PolymerCCTP
106+
/// @param _bridgeData The core information needed for bridging
107+
/// @param _polymerData Data specific to PolymerCCTP
108+
function _startBridge(
109+
ILiFi.BridgeData memory _bridgeData,
110+
PolymerCCTPData calldata _polymerData
111+
) internal {
68112
LibAsset.transferERC20(USDC, POLYMER_FEE_RECEIVER, _polymerData.polymerTokenFee);
69113

70114
// TODO we don't need to use safe approve here?

test/solidity/Facets/PolymerCCTPFacet.t.sol

Lines changed: 109 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,25 @@ import {PolymerCCTPFacet} from "lifi/Facets/PolymerCCTPFacet.sol";
66
import {IPolymerCCTPFacet} from "lifi/Interfaces/IPolymerCCTP.sol";
77
import {PolymerCCTPData} from "lifi/Interfaces/IPolymerCCTP.sol";
88
import {ILiFi} from "lifi/Interfaces/ILiFi.sol";
9+
import {LibSwap} from "lifi/Libraries/LibSwap.sol";
10+
import {LibAllowList} from "lifi/Libraries/LibAllowList.sol";
911
import {ERC20} from "solmate/tokens/ERC20.sol";
10-
import {InvalidReceiver, InvalidAmount, InvalidSendingToken} from "src/Errors/GenericErrors.sol";
12+
import {InvalidReceiver, InvalidAmount} from "src/Errors/GenericErrors.sol";
13+
14+
// Test version with allowlist management
15+
contract TestPolymerCCTPFacet is PolymerCCTPFacet {
16+
constructor(address _tokenMessenger, address _usdc, address _polymerFeeReceiver)
17+
PolymerCCTPFacet(_tokenMessenger, _usdc, _polymerFeeReceiver)
18+
{}
19+
20+
function addDex(address _dex) external {
21+
LibAllowList.addAllowedContract(_dex);
22+
}
23+
24+
function setFunctionApprovalBySignature(bytes4 _signature) external {
25+
LibAllowList.addAllowedSelector(_signature);
26+
}
27+
}
1128

1229
// Mock TokenMessenger
1330
contract MockTokenMessenger {
@@ -37,8 +54,25 @@ contract MockUSDC is ERC20 {
3754
}
3855
}
3956

57+
// Mock token for swap testing
58+
contract MockToken is ERC20 {
59+
constructor(string memory name, string memory symbol, uint8 decimals) ERC20(name, symbol, decimals) {}
60+
61+
function mint(address to, uint256 amount) external {
62+
_mint(to, amount);
63+
}
64+
}
65+
66+
// Mock DEX for swapping
67+
contract MockDEX {
68+
function swap(address tokenIn, address tokenOut, uint256 amountIn, uint256 amountOut) external {
69+
ERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn);
70+
ERC20(tokenOut).transfer(msg.sender, amountOut);
71+
}
72+
}
73+
4074
contract PolymerCCTPFacetTest is Test {
41-
PolymerCCTPFacet public facet;
75+
TestPolymerCCTPFacet public facet;
4276
MockTokenMessenger public tokenMessenger;
4377
MockUSDC public usdc;
4478

@@ -52,7 +86,7 @@ contract PolymerCCTPFacetTest is Test {
5286
tokenMessenger = new MockTokenMessenger();
5387

5488
// Deploy facet
55-
facet = new PolymerCCTPFacet(address(tokenMessenger), address(usdc), feeReceiver);
89+
facet = new TestPolymerCCTPFacet(address(tokenMessenger), address(usdc), feeReceiver);
5690

5791
// Mint USDC to user
5892
usdc.mint(user, 1_000_000e6); // 1M USDC
@@ -207,9 +241,80 @@ contract PolymerCCTPFacetTest is Test {
207241
vm.startPrank(user);
208242
wrongToken.approve(address(facet), bridgeData.minAmount);
209243

210-
vm.expectRevert(InvalidSendingToken.selector);
244+
vm.expectRevert( abi.encodeWithSelector(IPolymerCCTPFacet.InvalidSendingAsset.selector, address(wrongToken), address(usdc)));
211245
facet.startBridgeTokensViaPolymerCCTP(bridgeData, polymerData);
212246

213247
vm.stopPrank();
214248
}
249+
250+
function test_CanSwapAndBridgeViaPolymerCCTP() public {
251+
// Deploy mock token and DEX
252+
MockToken dai = new MockToken("DAI", "DAI", 18);
253+
MockDEX dex = new MockDEX();
254+
255+
// Add DEX to allowlist
256+
facet.addDex(address(dex));
257+
facet.setFunctionApprovalBySignature(MockDEX.swap.selector);
258+
259+
// Setup: User has DAI, wants to swap to USDC and bridge
260+
uint256 daiAmount = 100_000e18; // 100k DAI
261+
uint256 usdcAmount = 100_000e6; // 100k USDC (after swap)
262+
uint256 polymerFee = 100e6; // 100 USDC fee
263+
uint256 amountAfterFee = usdcAmount - polymerFee;
264+
265+
// Mint DAI to user and USDC to DEX
266+
dai.mint(user, daiAmount);
267+
usdc.mint(address(dex), usdcAmount);
268+
269+
ILiFi.BridgeData memory bridgeData = ILiFi.BridgeData({
270+
transactionId: bytes32(uint256(1)),
271+
bridge: "polymercctp",
272+
integrator: "test",
273+
referrer: address(0),
274+
sendingAssetId: address(usdc),
275+
receiver: receiver,
276+
minAmount: usdcAmount,
277+
destinationChainId: 81457, // Blast
278+
hasSourceSwaps: true,
279+
hasDestinationCall: false
280+
});
281+
282+
// Create swap data
283+
LibSwap.SwapData[] memory swapData = new LibSwap.SwapData[](1);
284+
swapData[0] = LibSwap.SwapData({
285+
callTo: address(dex),
286+
approveTo: address(dex),
287+
sendingAssetId: address(dai),
288+
receivingAssetId: address(usdc),
289+
fromAmount: daiAmount,
290+
callData: abi.encodeWithSelector(MockDEX.swap.selector, address(dai), address(usdc), daiAmount, usdcAmount),
291+
requiresDeposit: true
292+
});
293+
294+
PolymerCCTPData memory polymerData = PolymerCCTPData({
295+
polymerTokenFee: polymerFee, maxCCTPFee: 0, nonEvmAddress: bytes32(0), minFinalityThreshold: 0
296+
});
297+
298+
vm.startPrank(user);
299+
300+
// Approve facet to spend DAI
301+
dai.approve(address(facet), daiAmount);
302+
303+
// Execute swap and bridge
304+
facet.swapAndStartBridgeTokensViaPolymerCCTP(bridgeData, swapData, polymerData);
305+
306+
vm.stopPrank();
307+
308+
// Verify fee receiver got the fee
309+
assertEq(usdc.balanceOf(feeReceiver), polymerFee);
310+
311+
// Verify token messenger got the bridge amount minus fee
312+
assertEq(usdc.balanceOf(address(tokenMessenger)), amountAfterFee);
313+
314+
// Verify user's DAI balance decreased
315+
assertEq(dai.balanceOf(user), 0);
316+
317+
// Verify DEX has the DAI
318+
assertEq(dai.balanceOf(address(dex)), daiAmount);
319+
}
215320
}

0 commit comments

Comments
 (0)