-
Notifications
You must be signed in to change notification settings - Fork 49
/
Copy pathVault.sol
225 lines (188 loc) · 8.55 KB
/
Vault.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (C) 2024 PancakeSwap
pragma solidity 0.8.26;
import {Ownable, Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {IVault, IVaultToken} from "./interfaces/IVault.sol";
import {SettlementGuard} from "./libraries/SettlementGuard.sol";
import {Currency, CurrencyLibrary} from "./types/Currency.sol";
import {BalanceDelta} from "./types/BalanceDelta.sol";
import {ILockCallback} from "./interfaces/ILockCallback.sol";
import {SafeCast} from "./libraries/SafeCast.sol";
import {VaultReserve} from "./libraries/VaultReserve.sol";
import {VaultToken} from "./VaultToken.sol";
contract Vault is IVault, VaultToken, Ownable2Step {
using SafeCast for *;
using CurrencyLibrary for Currency;
constructor() Ownable(msg.sender) {}
mapping(address app => bool isRegistered) public override isAppRegistered;
/// @dev keep track of each app's reserves
mapping(address app => mapping(Currency currency => uint256 reserve)) public reservesOfApp;
/// @notice only registered app is allowed to perform accounting
modifier onlyRegisteredApp() {
if (!isAppRegistered[msg.sender]) revert AppUnregistered();
_;
}
/// @notice revert if no locker is set
modifier isLocked() {
if (SettlementGuard.getLocker() == address(0)) revert NoLocker();
_;
}
/// @inheritdoc IVault
function registerApp(address app) external override onlyOwner {
isAppRegistered[app] = true;
emit AppRegistered(app);
}
/// @inheritdoc IVault
function getLocker() external view override returns (address) {
return SettlementGuard.getLocker();
}
/// @inheritdoc IVault
function getUnsettledDeltasCount() external view override returns (uint256) {
return SettlementGuard.getUnsettledDeltasCount();
}
/// @inheritdoc IVault
function currencyDelta(address settler, Currency currency) external view override returns (int256) {
return SettlementGuard.getCurrencyDelta(settler, currency);
}
/// @dev interaction must start from lock
/// @inheritdoc IVault
function lock(bytes calldata data) external override returns (bytes memory result) {
/// @dev only one locker at a time
SettlementGuard.setLocker(msg.sender);
result = ILockCallback(msg.sender).lockAcquired(data);
/// @notice the caller can do anything in this callback as long as all deltas are offset after this
if (SettlementGuard.getUnsettledDeltasCount() != 0) revert CurrencyNotSettled();
/// @dev release the lock
SettlementGuard.setLocker(address(0));
}
/// @inheritdoc IVault
function accountAppBalanceDelta(
Currency currency0,
Currency currency1,
BalanceDelta delta,
address settler,
BalanceDelta hookDelta,
address hook
) external override isLocked onlyRegisteredApp {
(int128 delta0, int128 delta1) = (delta.amount0(), delta.amount1());
(int128 hookDelta0, int128 hookDelta1) = (hookDelta.amount0(), hookDelta.amount1());
/// @dev call _accountDeltaForApp once with both delta/hookDelta to save gas and prevent
/// reservesOfApp from underflow when it deduct before addition
_accountDeltaForApp(currency0, delta0 + hookDelta0);
_accountDeltaForApp(currency1, delta1 + hookDelta1);
// keep track of the balance on vault level
SettlementGuard.accountDelta(settler, currency0, delta0);
SettlementGuard.accountDelta(settler, currency1, delta1);
SettlementGuard.accountDelta(hook, currency0, hookDelta0);
SettlementGuard.accountDelta(hook, currency1, hookDelta1);
}
/// @inheritdoc IVault
function accountAppBalanceDelta(Currency currency0, Currency currency1, BalanceDelta delta, address settler)
external
override
isLocked
onlyRegisteredApp
{
int128 delta0 = delta.amount0();
int128 delta1 = delta.amount1();
// keep track of the balance on app level
_accountDeltaForApp(currency0, delta0);
_accountDeltaForApp(currency1, delta1);
// keep track of the balance on vault level
SettlementGuard.accountDelta(settler, currency0, delta0);
SettlementGuard.accountDelta(settler, currency1, delta1);
}
/// @inheritdoc IVault
function accountAppBalanceDelta(Currency currency, int128 delta, address settler)
external
override
isLocked
onlyRegisteredApp
{
_accountDeltaForApp(currency, delta);
SettlementGuard.accountDelta(settler, currency, delta);
}
/// @inheritdoc IVault
function take(Currency currency, address to, uint256 amount) external override isLocked {
unchecked {
SettlementGuard.accountDelta(msg.sender, currency, -(amount.toInt128()));
currency.transfer(to, amount);
}
}
/// @inheritdoc IVault
function mint(address to, Currency currency, uint256 amount) external override isLocked {
unchecked {
SettlementGuard.accountDelta(msg.sender, currency, -(amount.toInt128()));
_mint(to, currency, amount);
}
}
function sync(Currency currency) public override {
if (currency.isNative()) {
VaultReserve.setVaultReserve(CurrencyLibrary.NATIVE, 0);
} else {
uint256 balance = currency.balanceOfSelf();
VaultReserve.setVaultReserve(currency, balance);
}
}
/// @inheritdoc IVault
function settle() external payable override isLocked returns (uint256) {
return _settle(msg.sender);
}
/// @inheritdoc IVault
function settleFor(address recipient) external payable override isLocked returns (uint256) {
return _settle(recipient);
}
/// @inheritdoc IVault
function clear(Currency currency, uint256 amount) external isLocked {
int256 existingDelta = SettlementGuard.getCurrencyDelta(msg.sender, currency);
int128 amountDelta = amount.toInt128();
/// @dev since amount is uint256, existingDelta must be positive otherwise revert
if (amountDelta != existingDelta) revert MustClearExactPositiveDelta();
unchecked {
SettlementGuard.accountDelta(msg.sender, currency, -amountDelta);
}
}
/// @inheritdoc IVault
function burn(address from, Currency currency, uint256 amount) external override isLocked {
SettlementGuard.accountDelta(msg.sender, currency, amount.toInt128());
_burnFrom(from, currency, amount);
}
/// @inheritdoc IVault
function collectFee(Currency currency, uint256 amount, address recipient) external onlyRegisteredApp {
// prevent transfer between the sync and settle balanceOfs (native settle uses msg.value)
(Currency syncedCurrency,) = VaultReserve.getVaultReserve();
if (!currency.isNative() && syncedCurrency == currency) revert FeeCurrencySynced();
reservesOfApp[msg.sender][currency] -= amount;
currency.transfer(recipient, amount);
}
/// @inheritdoc IVault
function getVaultReserve() external view returns (Currency, uint256) {
return VaultReserve.getVaultReserve();
}
function _accountDeltaForApp(Currency currency, int128 delta) internal {
if (delta == 0) return;
/// @dev optimization: msg.sender will always be app address, verification should be done on caller address
if (delta >= 0) {
/// @dev arithmetic underflow make sure trader can't withdraw too much from app
reservesOfApp[msg.sender][currency] -= uint128(delta);
} else {
/// @dev arithmetic overflow make sure trader won't deposit too much into app
reservesOfApp[msg.sender][currency] += uint128(-delta);
}
}
// if settling native, integrators should still call `sync` first to avoid DoS attack vectors
function _settle(address recipient) internal returns (uint256 paid) {
(Currency currency, uint256 reservesBefore) = VaultReserve.getVaultReserve();
if (!currency.isNative()) {
if (msg.value > 0) revert SettleNonNativeCurrencyWithValue();
uint256 reservesNow = currency.balanceOfSelf();
paid = reservesNow - reservesBefore;
/// @dev reset the reserve after settled
VaultReserve.setVaultReserve(CurrencyLibrary.NATIVE, 0);
} else {
// NATIVE token does not require sync call before settle
paid = msg.value;
}
SettlementGuard.accountDelta(recipient, currency, paid.toInt128());
}
}