Skip to content

Commit a2721b1

Browse files
committed
contract: DFS: no approvals
1 parent 6636779 commit a2721b1

File tree

6 files changed

+32
-30
lines changed

6 files changed

+32
-30
lines changed

packages/contract/src/DaimoAccountV2.sol

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -447,11 +447,10 @@ contract DaimoAccountV2 is IAccount, Initializable, IERC1271, ReentrancyGuard {
447447
if (address(tokenIn) == address(0)) {
448448
value = amountIn; // native token
449449
} else {
450-
tokenIn.forceApprove(address(swapper), amountIn);
450+
tokenIn.safeTransfer(address(swapper), amountIn);
451451
}
452452
amountOut = swapper.swapToCoin{value: value}({
453453
tokenIn: tokenIn,
454-
amountIn: amountIn,
455454
tokenOut: tokenOut,
456455
extraData: extraData
457456
});

packages/contract/src/DaimoFlexSwapper.sol

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ import "./interfaces/IDaimoSwapper.sol";
1717
/// @author The Daimo team
1818
/// @custom:security-contact security@daimo.com
1919
///
20+
/// For security, this contract never holds any tokens (except during a swap)
21+
/// and does not require any token approvals.
22+
///
2023
/// Starts by quoting an accurate reference price from any input (token, amount)
2124
/// to a list of supported output stablecoins using Uniswap V3 TWAP/TWALs. See
2225
/// https://uniswap.org/whitepaper-v3.pdf for more on TWAP and TWAL.
@@ -166,17 +169,28 @@ contract DaimoFlexSwapper is
166169
// ----- PUBLIC FUNCTIONS -----
167170

168171
/// Swap input to output token at a fair price. Input token 0x0 refers to
169-
/// the native token, eg ETH. Output token cannot be 0x0.
172+
/// the native token, eg ETH. Output token cannot be 0x0. To call this, you
173+
/// must first send the input amount to the contract. This must be done
174+
/// within a single transaction. (Much like the Uniswap UniversalRouter.)
170175
function swapToCoin(
171176
IERC20 tokenIn,
172-
uint256 amountIn,
173177
IERC20 tokenOut,
174178
bytes calldata extraData
175179
) public payable returns (uint256 swapAmountOut) {
176180
// Input checks. Input token 0x0 = native token, output must be ERC-20.
177181
require(tokenIn != tokenOut, "DFS: input token = output token");
178182
require(address(tokenOut) != address(0), "DFS: output token = 0x0");
179183
require(isOutputToken[tokenOut], "DFS: unsupported output token");
184+
185+
// Get input amount
186+
uint256 amountIn;
187+
if (address(tokenIn) == address(0)) {
188+
require(msg.value > 0, "DFS: missing msg.value");
189+
amountIn = msg.value;
190+
} else {
191+
require(msg.value == 0, "DFS: unexpected msg.value");
192+
amountIn = tokenIn.balanceOf(address(this));
193+
}
180194
require(amountIn < _MAX_UINT128, "DFS: amountIn too large");
181195
DaimoFlexSwapperExtraData memory extra;
182196
extra = abi.decode(extraData, (DaimoFlexSwapperExtraData));
@@ -193,11 +207,9 @@ contract DaimoFlexSwapper is
193207
bytes memory callData = extra.callData;
194208
uint256 callValue = 0;
195209
if (address(tokenIn) == address(0)) {
196-
require(msg.value == amountIn, "DFS: incorrect msg.value");
197210
callValue = amountIn;
198211
} else {
199-
require(msg.value == 0, "DFS: unexpected msg.value");
200-
tokenIn.safeTransferFrom(msg.sender, callDest, amountIn);
212+
tokenIn.safeTransfer(callDest, amountIn);
201213
}
202214

203215
// Execute swap

packages/contract/src/interfaces/IDaimoSwapper.sol

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,14 @@ import "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
55

66
/// Swaps assets automatically. More precisely, it lets any market maker swap
77
/// swap tokens for a destination token, ensuring a fair price. The input comes
8-
/// from msg.sender (which must have already approved) and output goes to same.
8+
/// from and output goes to msg.sender.
99
interface IDaimoSwapper {
1010
/// Called to swap tokenIn to tokenOut. Ensures fair price or reverts.
1111
/// @param tokenIn input ERC-20 token, 0x0 for native token
12-
/// @param amountIn amount to swap. For native token, must match msg.value
1312
/// @param tokenOut output ERC-20 token, cannot be 0x0
1413
/// @param extraData swap route or similar, depending on implementation
1514
function swapToCoin(
1615
IERC20 tokenIn,
17-
uint256 amountIn,
1816
IERC20 tokenOut,
1917
bytes calldata extraData
2018
) external payable returns (uint256 amountOut);

packages/contract/test/dummy/DaimoDummySwapper.sol

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ contract DummySwapper is IDaimoSwapper {
3030

3131
function swapToCoin(
3232
IERC20 tokenIn,
33-
uint256 amountIn,
3433
IERC20 tokenOut,
3534
bytes calldata extraData
3635
) external payable returns (uint256 amountOut) {
@@ -48,7 +47,7 @@ contract DummySwapper is IDaimoSwapper {
4847

4948
require(tokenIn == expectedTokenIn, "wrong tokenIn");
5049
require(tokenOut == expectedTokenOut, "wrong tokenOut");
51-
tokenIn.transferFrom(msg.sender, address(this), amountIn);
50+
uint256 amountIn = tokenIn.balanceOf(address(this));
5251

5352
if (extraData.length > 0) {
5453
// Call the reentrant swapAndTip() function

packages/contract/test/uniswap/DaimoFlexSwapper.t.sol

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,6 @@ contract SwapperTest is Test {
8282
deal(address(weth), alice, 1e18);
8383
deal(address(degen), alice, amountIn);
8484

85-
degen.approve(address(swapper), amountIn);
86-
8785
bytes memory swapCallData = abi.encodeWithSignature(
8886
"exactInput((bytes,address,uint256,uint256))",
8987
ExactInputParams({
@@ -94,9 +92,9 @@ contract SwapperTest is Test {
9492
})
9593
);
9694

95+
degen.transfer(address(swapper), amountIn);
9796
uint256 amountOut = swapper.swapToCoin({
9897
tokenIn: degen,
99-
amountIn: amountIn,
10098
tokenOut: usdc,
10199
extraData: abi.encode(
102100
DaimoFlexSwapper.DaimoFlexSwapperExtraData({
@@ -153,7 +151,6 @@ contract SwapperTest is Test {
153151
vm.expectRevert(bytes("DFS: insufficient output"));
154152
swapper.swapToCoin{value: 1 ether}({
155153
tokenIn: IERC20(address(0)),
156-
amountIn: 1 ether,
157154
tokenOut: weth,
158155
extraData: abi.encode(
159156
DaimoFlexSwapper.DaimoFlexSwapperExtraData({
@@ -166,7 +163,6 @@ contract SwapperTest is Test {
166163
// 1:1 ETH to WETH = allowed
167164
uint256 amountOut = swapper.swapToCoin{value: 1 ether}({
168165
tokenIn: IERC20(address(0)),
169-
amountIn: 1 ether,
170166
tokenOut: weth,
171167
extraData: abi.encode(
172168
DaimoFlexSwapper.DaimoFlexSwapperExtraData({
@@ -185,7 +181,6 @@ contract SwapperTest is Test {
185181
vm.expectRevert(bytes("DFS: input token = output token"));
186182
swapper.swapToCoin({
187183
tokenIn: weth,
188-
amountIn: 1 ether,
189184
tokenOut: weth,
190185
extraData: abi.encode(
191186
DaimoFlexSwapper.DaimoFlexSwapperExtraData({
@@ -221,7 +216,6 @@ contract SwapperTest is Test {
221216

222217
amountOut = swapper.swapToCoin{value: amountIn}({
223218
tokenIn: IERC20(address(0)), // ETH
224-
amountIn: amountIn,
225219
tokenOut: usdc,
226220
extraData: abi.encode(
227221
DaimoFlexSwapper.DaimoFlexSwapperExtraData({

packages/contract/test/uniswap/Quoter.t.sol

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -97,42 +97,42 @@ contract QuoterTest is Test {
9797
// $3000.00 = 1 ETH, wrong price = block swap
9898
fakeFeedETHUSD.setPrice(300000, 2);
9999
vm.expectRevert(bytes("DFS: quote sanity check failed"));
100-
swapper.swapToCoin(eth, 1 ether, usdc, emptySwapData());
100+
swapper.swapToCoin{value: 1 ether}(eth, usdc, emptySwapData());
101101

102102
// $3450.00 = 1 ETH, ~correct price = OK, attempt swap
103103
fakeFeedETHUSD.setPrice(345000, 2);
104104
vm.expectRevert(bytes("DFS: swap produced no output"));
105-
swapper.swapToCoin{value: 1 ether}(eth, 1 ether, usdc, emptySwapData());
105+
swapper.swapToCoin{value: 1 ether}(eth, usdc, emptySwapData());
106106

107107
// Feed returning stale or missing price = block swap
108108
fakeFeedETHUSD.setPrice(0, 0);
109109
vm.expectRevert(bytes("DFS: CL price <= 0"));
110-
swapper.swapToCoin{value: 1 ether}(eth, 1 ether, usdc, emptySwapData());
110+
swapper.swapToCoin{value: 1 ether}(eth, usdc, emptySwapData());
111111

112112
// No price feed = OK, attempt swap
113113
swapper.setKnownToken(weth, zeroToken);
114114
vm.expectRevert(bytes("DFS: swap produced no output"));
115-
swapper.swapToCoin{value: 1 ether}(eth, 1 ether, usdc, emptySwapData());
115+
swapper.swapToCoin{value: 1 ether}(eth, usdc, emptySwapData());
116116
}
117117

118118
function testChainlinkSanityCheckERC20() public {
119119
deal(address(degen), address(this), 10e18);
120-
degen.approve(address(swapper), 10e18);
120+
degen.transfer(address(swapper), 1e18);
121121

122122
// $0.50 = 1 DEGEN, wrong price = block swap
123123
fakeFeedDEGENUSD.setPrice(500, 3);
124124
vm.expectRevert(bytes("DFS: quote sanity check failed"));
125-
swapper.swapToCoin(degen, 1e18, usdc, emptySwapData());
125+
swapper.swapToCoin(degen, usdc, emptySwapData());
126126

127127
// $0.0086 = 1 DEGEN, ~correct price = OK, attempt swap
128128
fakeFeedDEGENUSD.setPrice(8600, 6);
129129
vm.expectRevert(bytes("DFS: swap produced no output"));
130-
swapper.swapToCoin(degen, 1 ether, usdc, emptySwapData());
130+
swapper.swapToCoin(degen, usdc, emptySwapData());
131131

132132
// No price = OK, attempt swap
133133
swapper.setKnownToken(degen, zeroToken);
134134
vm.expectRevert(bytes("DFS: swap produced no output"));
135-
swapper.swapToCoin(degen, 1e18, usdc, emptySwapData());
135+
swapper.swapToCoin(degen, usdc, emptySwapData());
136136
}
137137

138138
function testFlexSwapperQuote() public view {
@@ -154,14 +154,14 @@ contract QuoterTest is Test {
154154
function testRebasingToken() public {
155155
IERC20 usdm = IERC20(0x28eD8909de1b3881400413Ea970ccE377a004ccA);
156156
deal(address(usdm), address(this), 123e18);
157-
usdm.approve(address(swapper), 123e18);
157+
usdm.transfer(address(swapper), 123e18);
158158

159159
// Protocol lets you unwrap 123 USDM for 122 USDC = within 1% of 1:1
160160
bytes memory callData = fakeSwapData(usdm, usdc, 123e18, 122e6);
161161

162162
// Initially, swap fails because USDM is a rebasing token, no Uni price.
163163
vm.expectRevert(bytes("DFS: no path found, amountOut 0"));
164-
swapper.swapToCoin(usdm, 123e18, usdc, callData);
164+
swapper.swapToCoin(usdm, usdc, callData);
165165

166166
// Give USDM a price feed + skip Uniswap. Swap should succeed.
167167
FakeAggregator fakeFeedUSDM = new FakeAggregator();
@@ -174,7 +174,7 @@ contract QuoterTest is Test {
174174
skipUniswap: true
175175
})
176176
);
177-
swapper.swapToCoin(usdm, 123e18, usdc, callData);
177+
swapper.swapToCoin(usdm, usdc, callData);
178178

179179
assertEq(usdm.balanceOf(address(this)), 0);
180180
assertEq(usdc.balanceOf(address(this)), 122e6);

0 commit comments

Comments
 (0)