Skip to content

Commit e2c57be

Browse files
committed
fix: add safe transfer
1 parent babc50c commit e2c57be

2 files changed

Lines changed: 26 additions & 1 deletion

File tree

src/types/ComposableCowPoller.sol

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22
pragma solidity >=0.8.0 <0.9.0;
33

44
import {IERC20, GPv2Order} from "cowprotocol/contracts/libraries/GPv2Order.sol";
5+
import {GPv2SafeERC20} from "cowprotocol/contracts/libraries/GPv2SafeERC20.sol";
56

67
import {ComposableCoW} from "src/ComposableCoW.sol";
78
import {IConditionalOrder, IConditionalOrderGenerator} from "src/interfaces/IConditionalOrder.sol";
89

910
/// @title ComposableCowPoller - Just-in-time funding for composable conditional orders.
1011
contract ComposableCowPoller {
12+
using GPv2SafeERC20 for IERC20;
13+
1114
ComposableCoW public immutable composableCow;
1215

1316
/// @notice Parameters for a JIT funding schedule.
@@ -101,7 +104,7 @@ contract ComposableCowPoller {
101104
if (digest == lastFunded[id]) return;
102105
lastFunded[id] = digest;
103106

104-
order.sellToken.transferFrom(schedule.funder, schedule.owner, order.sellAmount);
107+
order.sellToken.safeTransferFrom(schedule.funder, schedule.owner, order.sellAmount);
105108
emit Pulled(id, digest, order.sellAmount);
106109
}
107110
}

test/ComposableCowPoller.t.sol

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,28 @@ contract ComposableCowPollerTest is BaseComposableCoWTest {
230230
assertEq(token0.balanceOf(funder), PART * N - PART, "no extra pull");
231231
}
232232

233+
/// @dev A failed ERC-20 transfer must not mark this part as funded.
234+
function test_pollFunds_RevertWhen_transferFromReturnsFalse() public {
235+
(, bytes32 ctx, bytes32 id) = _setupSchedule();
236+
vm.warp(_t0(ctx));
237+
238+
vm.mockCall(
239+
address(token0),
240+
abi.encodeWithSelector(
241+
token0.transferFrom.selector,
242+
funder,
243+
address(safe1),
244+
PART
245+
),
246+
abi.encode(false)
247+
);
248+
249+
vm.expectRevert(bytes("GPv2: failed transferFrom"));
250+
poller.pollFunds(id);
251+
252+
assertEq(poller.lastFunded(id), bytes32(0), "failed pull is not recorded");
253+
}
254+
233255
/// @dev The headline flow: each part is funded JIT and the owner holds nothing in between.
234256
function test_pollFunds_fundsEachPartAcrossSchedule() public {
235257
(, bytes32 ctx, bytes32 id) = _setupSchedule();

0 commit comments

Comments
 (0)