Skip to content

Commit d26f97c

Browse files
committed
feat(multi-atm): perfectly linear accrual
1 parent 91bb5d1 commit d26f97c

3 files changed

Lines changed: 2799 additions & 165 deletions

File tree

contracts/token/MultiATM.sol

Lines changed: 100 additions & 165 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,23 @@
22

33
pragma solidity ^0.8.27;
44

5-
import { IAuthority } from "@openzeppelin/contracts/access/manager/IAuthority.sol";
6-
import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol";
7-
import { IERC20Metadata } from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol";
8-
import { ERC2771Context } from "@openzeppelin/contracts/metatx/ERC2771Context.sol";
9-
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
10-
import { Hashes } from "@openzeppelin/contracts/utils/cryptography/Hashes.sol";
11-
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
12-
import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
13-
import { SignedMath } from "@openzeppelin/contracts/utils/math/SignedMath.sol";
14-
import { Context } from "@openzeppelin/contracts/utils/Context.sol";
15-
import { Multicall } from "@openzeppelin/contracts/utils/Multicall.sol";
16-
import { Oracle } from "../oracle/Oracle.sol";
5+
import { IAuthority } from "@openzeppelin/contracts/access/manager/IAuthority.sol";
6+
import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol";
7+
import { IERC20Metadata } from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol";
8+
import { ERC2771Context } from "@openzeppelin/contracts/metatx/ERC2771Context.sol";
9+
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
10+
import { Hashes } from "@openzeppelin/contracts/utils/cryptography/Hashes.sol";
11+
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
12+
import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
13+
import { SignedMath } from "@openzeppelin/contracts/utils/math/SignedMath.sol";
14+
import { Context } from "@openzeppelin/contracts/utils/Context.sol";
15+
import { Multicall } from "@openzeppelin/contracts/utils/Multicall.sol";
16+
import { Oracle } from "../oracle/Oracle.sol";
1717
import { PermissionManaged } from "../permissions/PermissionManaged.sol";
1818

19-
contract MultiATM is ERC2771Context, PermissionManaged, Multicall {
20-
using Math for *;
19+
contract MultiATM is ERC2771Context, PermissionManaged, Multicall
20+
{
21+
using Math for *;
2122
using SafeCast for *;
2223

2324
uint256 private constant _BASIS_POINT_SCALE = 1e4;
@@ -53,21 +54,8 @@ contract MultiATM is ERC2771Context, PermissionManaged, Multicall {
5354
mapping(bytes32 id => Pair) private _pairs;
5455
uint256 public feeBasisPoints;
5556

56-
event SwapExact(
57-
IERC20 indexed input,
58-
IERC20 indexed output,
59-
uint256 inputAmount,
60-
uint256 outputAmount,
61-
address from,
62-
address to
63-
);
64-
event PairUpdated(
65-
bytes32 indexed id,
66-
IERC20 indexed token1,
67-
IERC20 indexed token2,
68-
Oracle oracle,
69-
uint256 oracleTTL
70-
);
57+
event SwapExact(IERC20 indexed input, IERC20 indexed output, uint256 inputAmount, uint256 outputAmount, address from, address to);
58+
event PairUpdated(bytes32 indexed id, IERC20 indexed token1, IERC20 indexed token2, Oracle oracle, uint256 oracleTTL);
7159
event PairRemoved(bytes32 indexed id);
7260
event FeeUpdated(uint256 newFeeBasisPoints);
7361
error OutputAmountTooLow(uint256 outputAmount, uint256 minOutputAmount);
@@ -77,207 +65,153 @@ contract MultiATM is ERC2771Context, PermissionManaged, Multicall {
7765
error InvalidFee(uint256 feeBasisPoints);
7866

7967
/// @custom:oz-upgrades-unsafe-allow constructor
80-
constructor(
81-
IAuthority _authority,
82-
address _trustedForwarder
83-
) PermissionManaged(_authority) ERC2771Context(_trustedForwarder) {}
68+
constructor(IAuthority _authority, address _trustedForwarder)
69+
PermissionManaged(_authority)
70+
ERC2771Context(_trustedForwarder)
71+
{}
8472

8573
/****************************************************************************************************************
8674
* Getters *
8775
****************************************************************************************************************/
88-
function viewPairDetails(
89-
IERC20 input,
90-
IERC20 output
91-
)
92-
public
93-
view
94-
virtual
95-
returns (
96-
bytes32 id,
97-
IERC20 token1,
98-
IERC20 token2,
99-
Oracle oracle,
100-
uint256 oracleTTL,
101-
uint256 numerator,
102-
uint256 denominator
103-
)
104-
{
76+
function viewPairDetails(IERC20 input, IERC20 output) public view virtual returns (
77+
bytes32 id,
78+
IERC20 token1,
79+
IERC20 token2,
80+
Oracle oracle,
81+
uint256 oracleTTL,
82+
uint256 numerator,
83+
uint256 denominator
84+
) {
10585
id = hashPair(input, output);
10686
Pair storage pair = _pairs[id];
10787

108-
return (id, pair.token1, pair.token2, pair.oracle, pair.oracleTTL, pair.numerator, pair.denominator);
88+
return (
89+
id,
90+
pair.token1,
91+
pair.token2,
92+
pair.oracle,
93+
pair.oracleTTL,
94+
pair.numerator,
95+
pair.denominator
96+
);
10997
}
11098

11199
function hashPair(IERC20 input, IERC20 output) public view virtual returns (bytes32) {
112-
return
113-
Hashes.commutativeKeccak256(
114-
bytes32(uint256(uint160(address(input)))),
115-
bytes32(uint256(uint160(address(output))))
116-
);
100+
return Hashes.commutativeKeccak256(
101+
bytes32(uint256(uint160(address(input)))),
102+
bytes32(uint256(uint160(address(output))))
103+
);
117104
}
118105

119106
/****************************************************************************************************************
120107
* Core - preview swaps *
121108
****************************************************************************************************************/
122-
function previewExactInput(
123-
IERC20[] memory path,
124-
uint256 inputAmount
125-
) public view virtual returns (uint256 /*outputAmount*/) {
109+
function previewExactInput(IERC20[] memory path, uint256 inputAmount) public view virtual returns (uint256 /*outputAmount*/) {
126110
uint256 outputAmount = inputAmount;
127111
for (uint256 i = 0; i < path.length - 1; ++i) {
128-
outputAmount = _exactInput(path[i], path[i + 1], outputAmount);
112+
outputAmount = _exactInput(path[i], path[i+1], outputAmount);
129113
}
130114
return outputAmount.mulDiv(_BASIS_POINT_SCALE - feeBasisPoints, _BASIS_POINT_SCALE, Math.Rounding.Floor);
131115
}
132116

133-
function previewExactOutput(
134-
IERC20[] memory path,
135-
uint256 outputAmount
136-
) public view virtual returns (uint256 /*inputAmount*/) {
117+
function previewExactOutput(IERC20[] memory path, uint256 outputAmount) public view virtual returns (uint256 /*inputAmount*/) {
137118
uint256 inputAmount = outputAmount;
138119
for (uint256 i = path.length - 1; i > 0; --i) {
139-
inputAmount = _exactOutput(path[i - 1], path[i], inputAmount);
120+
inputAmount = _exactOutput(path[i-1], path[i], inputAmount);
140121
}
141122
return inputAmount.mulDiv(_BASIS_POINT_SCALE, _BASIS_POINT_SCALE - feeBasisPoints, Math.Rounding.Ceil);
142123
}
143124

144-
function previewExactInputSingle(
145-
IERC20 input,
146-
IERC20 output,
147-
uint256 inputAmount
148-
) public view virtual returns (uint256 /*outputAmount*/) {
149-
return
150-
_exactInput(input, output, inputAmount).mulDiv(
151-
_BASIS_POINT_SCALE - feeBasisPoints,
152-
_BASIS_POINT_SCALE,
153-
Math.Rounding.Floor
154-
);
125+
function previewExactInputSingle(IERC20 input, IERC20 output, uint256 inputAmount) public view virtual returns (uint256 /*outputAmount*/) {
126+
return _exactInput(input, output, inputAmount).mulDiv(_BASIS_POINT_SCALE - feeBasisPoints, _BASIS_POINT_SCALE, Math.Rounding.Floor);
155127
}
156128

157-
function previewExactOutputSingle(
158-
IERC20 input,
159-
IERC20 output,
160-
uint256 outputAmount
161-
) public view virtual returns (uint256 /*inputAmount*/) {
162-
return
163-
_exactOutput(input, output, outputAmount).mulDiv(
164-
_BASIS_POINT_SCALE,
165-
_BASIS_POINT_SCALE - feeBasisPoints,
166-
Math.Rounding.Ceil
167-
);
129+
function previewExactOutputSingle(IERC20 input, IERC20 output, uint256 outputAmount) public view virtual returns (uint256 /*inputAmount*/) {
130+
return _exactOutput(input, output, outputAmount).mulDiv(_BASIS_POINT_SCALE, _BASIS_POINT_SCALE - feeBasisPoints, Math.Rounding.Ceil);
168131
}
169132

170-
function _exactInput(
171-
IERC20 input,
172-
IERC20 output,
173-
uint256 inputAmount
174-
) internal view virtual returns (uint256 /*outputAmount*/) {
175-
(, IERC20 token1, , Oracle oracle, uint256 oracleTTL, uint256 numerator, uint256 denominator) = viewPairDetails(
176-
input,
177-
output
178-
);
133+
function _exactInput(IERC20 input, IERC20 output, uint256 inputAmount) internal view virtual returns (uint256 /*outputAmount*/) {
134+
(
135+
,
136+
IERC20 token1,
137+
,
138+
Oracle oracle,
139+
uint256 oracleTTL,
140+
uint256 numerator,
141+
uint256 denominator
142+
) = viewPairDetails(input, output);
179143

180144
require(address(oracle) != address(0), UnknownPair(input, output));
181145

182146
(int256 minPrice, int256 maxPrice) = _getPrices(oracle, oracleTTL);
183-
return
184-
inputAmount.mulDiv(
185-
Math.ternary(input == token1, numerator * minPrice.toUint256(), denominator),
186-
Math.ternary(input == token1, denominator, numerator * maxPrice.toUint256()),
187-
Math.Rounding.Floor
188-
);
147+
return inputAmount.mulDiv(
148+
Math.ternary(input == token1, numerator * minPrice.toUint256(), denominator),
149+
Math.ternary(input == token1, denominator, numerator * maxPrice.toUint256()),
150+
Math.Rounding.Floor
151+
);
189152
}
190153

191-
function _exactOutput(
192-
IERC20 input,
193-
IERC20 output,
194-
uint256 outputAmount
195-
) internal view virtual returns (uint256 /*inputAmount*/) {
196-
(, IERC20 token1, , Oracle oracle, uint256 oracleTTL, uint256 numerator, uint256 denominator) = viewPairDetails(
197-
input,
198-
output
199-
);
154+
function _exactOutput(IERC20 input, IERC20 output, uint256 outputAmount) internal view virtual returns (uint256 /*inputAmount*/) {
155+
(
156+
,
157+
IERC20 token1,
158+
,
159+
Oracle oracle,
160+
uint256 oracleTTL,
161+
uint256 numerator,
162+
uint256 denominator
163+
) = viewPairDetails(input, output);
200164

201165
require(address(oracle) != address(0), UnknownPair(input, output));
202166

203167
(int256 minPrice, int256 maxPrice) = _getPrices(oracle, oracleTTL);
204-
return
205-
outputAmount.mulDiv(
206-
Math.ternary(input == token1, denominator, numerator * maxPrice.toUint256()),
207-
Math.ternary(input == token1, numerator * minPrice.toUint256(), denominator),
208-
Math.Rounding.Ceil
209-
);
168+
return outputAmount.mulDiv(
169+
Math.ternary(input == token1, denominator, numerator * maxPrice.toUint256()),
170+
Math.ternary(input == token1, numerator * minPrice.toUint256(), denominator),
171+
Math.Rounding.Ceil
172+
);
210173
}
211174

212175
/****************************************************************************************************************
213176
* Core - execute swaps *
214177
****************************************************************************************************************/
215-
function swapExactInput(
216-
IERC20[] memory path,
217-
uint256 inputAmount,
218-
address recipient,
219-
uint256 minOutputAmount
220-
) public virtual restricted returns (uint256 /*outputAmount*/) {
178+
function swapExactInput(IERC20[] memory path, uint256 inputAmount, address recipient, uint256 minOutputAmount) public virtual restricted() returns (uint256 /*outputAmount*/) {
221179
uint256 outputAmount = previewExactInput(path, inputAmount);
222180
require(outputAmount >= minOutputAmount, OutputAmountTooLow(outputAmount, minOutputAmount));
223181
_swapExact(path[0], path[path.length - 1], inputAmount, outputAmount, _msgSender(), recipient);
224182
return outputAmount;
225183
}
226184

227-
function swapExactInputSingle(
228-
IERC20 input,
229-
IERC20 output,
230-
uint256 inputAmount,
231-
address recipient,
232-
uint256 minOutputAmount
233-
) public virtual restricted returns (uint256 /*outputAmount*/) {
185+
function swapExactInputSingle(IERC20 input, IERC20 output, uint256 inputAmount, address recipient, uint256 minOutputAmount) public virtual restricted() returns (uint256 /*outputAmount*/) {
234186
uint256 outputAmount = previewExactInputSingle(input, output, inputAmount);
235187
require(outputAmount >= minOutputAmount, OutputAmountTooLow(outputAmount, minOutputAmount));
236188
_swapExact(input, output, inputAmount, outputAmount, _msgSender(), recipient);
237189
return outputAmount;
238190
}
239191

240-
function swapExactOutput(
241-
IERC20[] memory path,
242-
uint256 outputAmount,
243-
address recipient,
244-
uint256 maxInputAmount
245-
) public virtual restricted returns (uint256 /*inputAmount*/) {
192+
function swapExactOutput(IERC20[] memory path, uint256 outputAmount, address recipient, uint256 maxInputAmount) public virtual restricted() returns (uint256 /*inputAmount*/) {
246193
uint256 inputAmount = previewExactOutput(path, outputAmount);
247194
require(inputAmount <= maxInputAmount, InputAmountTooHigh(inputAmount, maxInputAmount));
248195
_swapExact(path[0], path[path.length - 1], inputAmount, outputAmount, _msgSender(), recipient);
249196
return inputAmount;
250197
}
251198

252-
function swapExactOutputSingle(
253-
IERC20 input,
254-
IERC20 output,
255-
uint256 outputAmount,
256-
address recipient,
257-
uint256 maxInputAmount
258-
) public virtual restricted returns (uint256 /*inputAmount*/) {
199+
function swapExactOutputSingle(IERC20 input, IERC20 output, uint256 outputAmount, address recipient, uint256 maxInputAmount) public virtual restricted() returns (uint256 /*inputAmount*/) {
259200
uint256 inputAmount = previewExactOutputSingle(input, output, outputAmount);
260201
require(inputAmount <= maxInputAmount, InputAmountTooHigh(inputAmount, maxInputAmount));
261202
_swapExact(input, output, inputAmount, outputAmount, _msgSender(), recipient);
262203
return inputAmount;
263204
}
264205

265-
function _swapExact(
266-
IERC20 input,
267-
IERC20 output,
268-
uint256 inputAmount,
269-
uint256 outputAmount,
270-
address from,
271-
address to
272-
) private {
206+
function _swapExact(IERC20 input, IERC20 output, uint256 inputAmount, uint256 outputAmount, address from, address to) private {
273207
SafeERC20.safeTransferFrom(input, from, address(this), inputAmount);
274208
SafeERC20.safeTransfer(output, to, outputAmount);
275209
emit SwapExact(input, output, inputAmount, outputAmount, from, to);
276210
}
277211

278212
function _getPrices(Oracle oracle, uint256 oracleTTL) internal view virtual returns (int256 min, int256 max) {
279-
(uint80 roundId, int256 latest, , , ) = oracle.latestRoundData();
280-
(, int256 previous, , uint256 updatedAt, ) = oracle.getRoundData(roundId - 1);
213+
(uint80 roundId, int256 latest,,,) = oracle.latestRoundData();
214+
(, int256 previous,,uint256 updatedAt,) = oracle.getRoundData(roundId - 1);
281215
require(block.timestamp < updatedAt + oracleTTL, OracleValueTooOld(oracle));
282216
min = SignedMath.min(latest, previous);
283217
max = SignedMath.max(latest, previous);
@@ -286,12 +220,7 @@ contract MultiATM is ERC2771Context, PermissionManaged, Multicall {
286220
/****************************************************************************************************************
287221
* Admin actions *
288222
****************************************************************************************************************/
289-
function setPair(
290-
IERC20Metadata token1,
291-
IERC20Metadata token2,
292-
Oracle oracle,
293-
uint256 oracleTTL
294-
) public virtual restricted {
223+
function setPair(IERC20Metadata token1, IERC20Metadata token2, Oracle oracle, uint256 oracleTTL) public virtual restricted() {
295224
bytes32 id = hashPair(token1, token2);
296225
_pairs[id] = Pair({
297226
token1: token1,
@@ -305,21 +234,27 @@ contract MultiATM is ERC2771Context, PermissionManaged, Multicall {
305234
emit PairUpdated(id, token1, token2, oracle, oracleTTL);
306235
}
307236

308-
function removePair(IERC20 token1, IERC20 token2) public virtual restricted {
237+
function removePair(IERC20 token1, IERC20 token2) public virtual restricted() {
309238
bytes32 id = hashPair(token1, token2);
310239
delete _pairs[id];
311240

312241
emit PairRemoved(id);
313242
}
314243

315-
function setFee(uint256 newFeeBasisPoints) public virtual restricted {
244+
function setFee(uint256 newFeeBasisPoints) public virtual restricted() {
316245
require(newFeeBasisPoints <= 50, InvalidFee(newFeeBasisPoints)); // Max 0.5%
317246
feeBasisPoints = newFeeBasisPoints;
318247
emit FeeUpdated(newFeeBasisPoints);
319248
}
320249

321-
function withdraw(IERC20 _token, address _to, uint256 _amount) public virtual restricted {
322-
SafeERC20.safeTransfer(_token, _to, _amount == type(uint256).max ? _token.balanceOf(address(this)) : _amount);
250+
function withdraw(IERC20 _token, address _to, uint256 _amount) public virtual restricted() {
251+
SafeERC20.safeTransfer(
252+
_token,
253+
_to,
254+
_amount == type(uint256).max
255+
? _token.balanceOf(address(this))
256+
: _amount
257+
);
323258
}
324259

325260
/****************************************************************************************************************
@@ -336,4 +271,4 @@ contract MultiATM is ERC2771Context, PermissionManaged, Multicall {
336271
function _contextSuffixLength() internal view override(Context, ERC2771Context) returns (uint256) {
337272
return super._contextSuffixLength();
338273
}
339-
}
274+
}

0 commit comments

Comments
 (0)