@juicedollar/jusd 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/contracts/MintingHubV2/MintingHub.sol +47 -17
- package/contracts/MintingHubV2/Position.sol +94 -24
- package/contracts/MintingHubV2/PositionFactory.sol +2 -2
- package/contracts/MintingHubV2/PositionRoller.sol +2 -1
- package/contracts/MintingHubV2/interface/IMintingHub.sol +1 -1
- package/contracts/MintingHubV2/interface/IPosition.sol +5 -3
- package/contracts/gateway/MintingHubGateway.sol +13 -18
- package/contracts/gateway/interface/IMintingHubGateway.sol +2 -2
- package/contracts/interface/IWrappedNative.sol +10 -0
- package/contracts/test/PositionExpirationTest.sol +3 -3
- package/contracts/test/ReentrantAttacker.sol +74 -0
- package/contracts/test/RejectNative.sol +17 -0
- package/dist/index.d.mts +151 -480
- package/dist/index.d.ts +151 -480
- package/dist/index.js +218 -645
- package/dist/index.mjs +218 -644
- package/exports/abis/MintingHubV2/PositionV2.ts +68 -26
- package/exports/abis/core/MintingHubGateway.ts +40 -75
- package/exports/abis/utils/MintingHubV2.ts +35 -36
- package/exports/address.config.ts +10 -13
- package/exports/index.ts +0 -1
- package/package.json +1 -1
- package/contracts/gateway/CoinLendingGateway.sol +0 -223
- package/contracts/gateway/interface/ICoinLendingGateway.sol +0 -73
- package/exports/abis/core/CoinLendingGateway.ts +0 -427
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
pragma solidity ^0.8.0;
|
|
3
3
|
|
|
4
4
|
import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
|
|
5
|
+
import {IWrappedNative} from "../interface/IWrappedNative.sol";
|
|
5
6
|
import {IJuiceDollar} from "../interface/IJuiceDollar.sol";
|
|
6
7
|
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
|
|
7
8
|
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
@@ -36,6 +37,7 @@ contract MintingHub is IMintingHub, ERC165 {
|
|
|
36
37
|
IJuiceDollar public immutable JUSD; // currency
|
|
37
38
|
PositionRoller public immutable ROLLER; // helper to roll positions
|
|
38
39
|
ILeadrate public immutable RATE; // to determine the interest rate
|
|
40
|
+
address public immutable WCBTC; // wrapped native token (cBTC) address
|
|
39
41
|
|
|
40
42
|
Challenge[] public challenges; // list of open challenges
|
|
41
43
|
|
|
@@ -75,17 +77,20 @@ contract MintingHub is IMintingHub, ERC165 {
|
|
|
75
77
|
error InvalidCollateralDecimals();
|
|
76
78
|
error ChallengeTimeTooShort();
|
|
77
79
|
error InitPeriodTooShort();
|
|
80
|
+
error NativeOnlyForWCBTC();
|
|
81
|
+
error ValueMismatch();
|
|
78
82
|
|
|
79
83
|
modifier validPos(address position) {
|
|
80
84
|
if (JUSD.getPositionParent(position) != address(this)) revert InvalidPos();
|
|
81
85
|
_;
|
|
82
86
|
}
|
|
83
87
|
|
|
84
|
-
constructor(address _jusd, address _leadrate, address _roller, address _factory) {
|
|
88
|
+
constructor(address _jusd, address _leadrate, address _roller, address _factory, address _wcbtc) {
|
|
85
89
|
JUSD = IJuiceDollar(_jusd);
|
|
86
90
|
RATE = ILeadrate(_leadrate);
|
|
87
91
|
POSITION_FACTORY = IPositionFactory(_factory);
|
|
88
92
|
ROLLER = PositionRoller(_roller);
|
|
93
|
+
WCBTC = _wcbtc;
|
|
89
94
|
}
|
|
90
95
|
|
|
91
96
|
/**
|
|
@@ -118,15 +123,15 @@ contract MintingHub is IMintingHub, ERC165 {
|
|
|
118
123
|
uint24 _riskPremium,
|
|
119
124
|
uint256 _liqPrice,
|
|
120
125
|
uint24 _reservePPM
|
|
121
|
-
) public returns (address) {
|
|
126
|
+
) public payable returns (address) {
|
|
122
127
|
{
|
|
123
128
|
if (_riskPremium > 1_000_000) revert InvalidRiskPremium();
|
|
124
129
|
if (CHALLENGER_REWARD > _reservePPM || _reservePPM > 1_000_000) revert InvalidReservePPM();
|
|
125
130
|
if (IERC20Metadata(_collateralAddress).decimals() > 24) revert InvalidCollateralDecimals(); // leaves 12 digits for price
|
|
126
131
|
if (_challengeSeconds < 1 days) revert ChallengeTimeTooShort();
|
|
127
|
-
if (_initPeriodSeconds <
|
|
132
|
+
if (_initPeriodSeconds < 3 days) revert InitPeriodTooShort();
|
|
128
133
|
uint256 invalidAmount = IERC20(_collateralAddress).totalSupply() + 1;
|
|
129
|
-
// TODO: Improve for older tokens that revert with assert,
|
|
134
|
+
// TODO: Improve for older tokens that revert with assert,
|
|
130
135
|
// which consumes all gas and makes the entire tx fail (uncatchable)
|
|
131
136
|
try IERC20(_collateralAddress).transfer(address(0x123), invalidAmount) {
|
|
132
137
|
revert IncompatibleCollateral(); // we need a collateral that reverts on failed transfers
|
|
@@ -137,6 +142,7 @@ contract MintingHub is IMintingHub, ERC165 {
|
|
|
137
142
|
// must start with at least 100 JUSD worth of collateral
|
|
138
143
|
if (_minCollateral * _liqPrice < 100 ether * 10 ** 18) revert InsufficientCollateral();
|
|
139
144
|
}
|
|
145
|
+
|
|
140
146
|
IPosition pos = IPosition(
|
|
141
147
|
POSITION_FACTORY.createNewPosition(
|
|
142
148
|
msg.sender,
|
|
@@ -154,41 +160,65 @@ contract MintingHub is IMintingHub, ERC165 {
|
|
|
154
160
|
);
|
|
155
161
|
JUSD.registerPosition(address(pos));
|
|
156
162
|
JUSD.collectProfits(msg.sender, OPENING_FEE);
|
|
157
|
-
|
|
163
|
+
|
|
164
|
+
// Transfer collateral (handles native coin positions)
|
|
165
|
+
if (msg.value > 0) {
|
|
166
|
+
if (_collateralAddress != WCBTC) revert NativeOnlyForWCBTC();
|
|
167
|
+
if (msg.value != _initialCollateral) revert ValueMismatch();
|
|
168
|
+
IWrappedNative(WCBTC).deposit{value: msg.value}();
|
|
169
|
+
IERC20(WCBTC).transfer(address(pos), _initialCollateral);
|
|
170
|
+
} else {
|
|
171
|
+
IERC20(_collateralAddress).transferFrom(msg.sender, address(pos), _initialCollateral); // TODO: Use SafeERC20
|
|
172
|
+
}
|
|
158
173
|
|
|
159
174
|
emit PositionOpened(msg.sender, address(pos), address(pos), _collateralAddress);
|
|
160
175
|
return address(pos);
|
|
161
176
|
}
|
|
162
177
|
|
|
163
|
-
function clone(
|
|
164
|
-
address parent,
|
|
165
|
-
uint256 _initialCollateral,
|
|
166
|
-
uint256 _initialMint,
|
|
167
|
-
uint40 expiration
|
|
168
|
-
) public returns (address) {
|
|
169
|
-
return clone(msg.sender, parent, _initialCollateral, _initialMint, expiration);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
178
|
/**
|
|
173
179
|
* @notice Clones an existing position and immediately tries to mint the specified amount using the given collateral.
|
|
174
180
|
* @dev This needs an allowance to be set on the collateral contract such that the minting hub can get the collateral.
|
|
181
|
+
* For native coin positions (WcBTC), send msg.value equal to _initialCollateral.
|
|
182
|
+
* @param owner The owner of the cloned position
|
|
183
|
+
* @param parent The parent position to clone from
|
|
184
|
+
* @param _initialCollateral Amount of collateral to deposit
|
|
185
|
+
* @param _initialMint Amount of JUSD to mint
|
|
186
|
+
* @param expiration Expiration timestamp for the clone
|
|
187
|
+
* @param _liqPrice The liquidation price. If 0, inherits from parent.
|
|
175
188
|
*/
|
|
176
189
|
function clone(
|
|
177
190
|
address owner,
|
|
178
191
|
address parent,
|
|
179
192
|
uint256 _initialCollateral,
|
|
180
193
|
uint256 _initialMint,
|
|
181
|
-
uint40 expiration
|
|
182
|
-
|
|
194
|
+
uint40 expiration,
|
|
195
|
+
uint256 _liqPrice
|
|
196
|
+
) public payable validPos(parent) returns (address) {
|
|
183
197
|
address pos = POSITION_FACTORY.clonePosition(parent);
|
|
184
198
|
IPosition child = IPosition(pos);
|
|
185
199
|
child.initialize(parent, expiration);
|
|
186
200
|
JUSD.registerPosition(pos);
|
|
187
201
|
IERC20 collateral = child.collateral();
|
|
188
202
|
if (_initialCollateral < child.minimumCollateral()) revert InsufficientCollateral();
|
|
189
|
-
|
|
203
|
+
|
|
204
|
+
// Transfer collateral (handles native coin positions)
|
|
205
|
+
if (msg.value > 0) {
|
|
206
|
+
if (address(collateral) != WCBTC) revert NativeOnlyForWCBTC();
|
|
207
|
+
if (msg.value != _initialCollateral) revert ValueMismatch();
|
|
208
|
+
IWrappedNative(WCBTC).deposit{value: msg.value}();
|
|
209
|
+
collateral.transfer(pos, _initialCollateral);
|
|
210
|
+
} else {
|
|
211
|
+
collateral.transferFrom(msg.sender, pos, _initialCollateral); // collateral must still come from sender for security
|
|
212
|
+
}
|
|
213
|
+
|
|
190
214
|
emit PositionOpened(owner, address(pos), parent, address(collateral));
|
|
191
215
|
child.mint(owner, _initialMint);
|
|
216
|
+
|
|
217
|
+
// Adjust price if requested, incurs cooldown on price increase
|
|
218
|
+
if (_liqPrice > 0 && _liqPrice != child.price()) {
|
|
219
|
+
child.adjustPrice(_liqPrice);
|
|
220
|
+
}
|
|
221
|
+
|
|
192
222
|
Ownable(address(child)).transferOwnership(owner);
|
|
193
223
|
return address(pos);
|
|
194
224
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
pragma solidity ^0.8.0;
|
|
3
3
|
|
|
4
4
|
import {IMintingHubGateway} from "../gateway/interface/IMintingHubGateway.sol";
|
|
5
|
+
import {IWrappedNative} from "../interface/IWrappedNative.sol";
|
|
5
6
|
import {IJuiceDollar} from "../interface/IJuiceDollar.sol";
|
|
6
7
|
import {IReserve} from "../interface/IReserve.sol";
|
|
7
8
|
import {MathUtil} from "../utils/MathUtil.sol";
|
|
@@ -141,6 +142,8 @@ contract Position is Ownable, IPosition, MathUtil {
|
|
|
141
142
|
error AlreadyInitialized();
|
|
142
143
|
error PriceTooHigh(uint256 newPrice, uint256 maxPrice);
|
|
143
144
|
error InvalidPriceReference();
|
|
145
|
+
error NativeTransferFailed();
|
|
146
|
+
error CannotRescueCollateral();
|
|
144
147
|
|
|
145
148
|
modifier alive() {
|
|
146
149
|
if (block.timestamp >= expiration) revert Expired(uint40(block.timestamp), expiration);
|
|
@@ -205,7 +208,7 @@ contract Position is Ownable, IPosition, MathUtil {
|
|
|
205
208
|
reserveContribution = _reservePPM;
|
|
206
209
|
minimumCollateral = _minCollateral;
|
|
207
210
|
challengePeriod = _challengePeriod;
|
|
208
|
-
start = uint40(block.timestamp) + _initPeriod; // at least
|
|
211
|
+
start = uint40(block.timestamp) + _initPeriod; // at least three days time to deny the position
|
|
209
212
|
cooldown = start;
|
|
210
213
|
expiration = start + _duration;
|
|
211
214
|
limit = _initialLimit;
|
|
@@ -219,10 +222,10 @@ contract Position is Ownable, IPosition, MathUtil {
|
|
|
219
222
|
*/
|
|
220
223
|
function initialize(address parent, uint40 _expiration) external onlyHub {
|
|
221
224
|
if (expiration != 0) revert AlreadyInitialized();
|
|
222
|
-
if (_expiration < block.timestamp || _expiration > Position(original).expiration()) revert InvalidExpiration(); // expiration must not be later than original
|
|
225
|
+
if (_expiration < block.timestamp || _expiration > Position(payable(original)).expiration()) revert InvalidExpiration(); // expiration must not be later than original
|
|
223
226
|
expiration = _expiration;
|
|
224
|
-
price = Position(parent).price();
|
|
225
|
-
_fixRateToLeadrate(Position(parent).riskPremiumPPM());
|
|
227
|
+
price = Position(payable(parent)).price();
|
|
228
|
+
_fixRateToLeadrate(Position(payable(parent)).riskPremiumPPM());
|
|
226
229
|
_transferOwnership(hub);
|
|
227
230
|
}
|
|
228
231
|
|
|
@@ -268,7 +271,7 @@ contract Position is Ownable, IPosition, MathUtil {
|
|
|
268
271
|
if (address(this) == original) {
|
|
269
272
|
return limit - totalMinted;
|
|
270
273
|
} else {
|
|
271
|
-
return Position(original).availableForClones();
|
|
274
|
+
return Position(payable(original)).availableForClones();
|
|
272
275
|
}
|
|
273
276
|
}
|
|
274
277
|
|
|
@@ -312,27 +315,46 @@ contract Position is Ownable, IPosition, MathUtil {
|
|
|
312
315
|
/**
|
|
313
316
|
* @notice "All in one" function to adjust the principal, the collateral amount,
|
|
314
317
|
* and the price in one transaction.
|
|
318
|
+
* @dev For native coin positions (WCBTC), msg.value will be wrapped and added as collateral.
|
|
319
|
+
* If withdrawAsNative is true, collateral withdrawals will be unwrapped to native coin.
|
|
320
|
+
* @param newPrincipal The new principal amount
|
|
321
|
+
* @param newCollateral The new collateral amount
|
|
322
|
+
* @param newPrice The new liquidation price
|
|
323
|
+
* @param withdrawAsNative If true, withdraw collateral as native coin instead of wrapped token
|
|
315
324
|
*/
|
|
316
|
-
function adjust(uint256 newPrincipal, uint256 newCollateral, uint256 newPrice) external onlyOwner {
|
|
317
|
-
_adjust(newPrincipal, newCollateral, newPrice, address(0));
|
|
325
|
+
function adjust(uint256 newPrincipal, uint256 newCollateral, uint256 newPrice, bool withdrawAsNative) external payable onlyOwner {
|
|
326
|
+
_adjust(newPrincipal, newCollateral, newPrice, address(0), withdrawAsNative);
|
|
318
327
|
}
|
|
319
328
|
|
|
320
329
|
/**
|
|
321
330
|
* @notice "All in one" function to adjust the principal, the collateral amount,
|
|
322
331
|
* and the price in one transaction, with optional reference position for cooldown-free price increase.
|
|
332
|
+
* @dev For native coin positions (WCBTC), msg.value will be wrapped and added as collateral.
|
|
333
|
+
* If withdrawAsNative is true, collateral withdrawals will be unwrapped to native coin.
|
|
323
334
|
* @param newPrincipal The new principal amount
|
|
324
335
|
* @param newCollateral The new collateral amount
|
|
325
336
|
* @param newPrice The new liquidation price
|
|
326
337
|
* @param referencePosition Reference position for cooldown-free price increase (address(0) for normal logic with cooldown)
|
|
338
|
+
* @param withdrawAsNative If true, withdraw collateral as native coin instead of wrapped token
|
|
327
339
|
*/
|
|
328
|
-
function
|
|
329
|
-
_adjust(newPrincipal, newCollateral, newPrice, referencePosition);
|
|
340
|
+
function adjustWithReference(uint256 newPrincipal, uint256 newCollateral, uint256 newPrice, address referencePosition, bool withdrawAsNative) external payable onlyOwner {
|
|
341
|
+
_adjust(newPrincipal, newCollateral, newPrice, referencePosition, withdrawAsNative);
|
|
330
342
|
}
|
|
331
343
|
|
|
332
344
|
/**
|
|
333
345
|
* @dev Internal implementation of adjust() - handles collateral, principal, and price adjustments.
|
|
346
|
+
* @param newPrincipal The new principal amount
|
|
347
|
+
* @param newCollateral The new collateral amount
|
|
348
|
+
* @param newPrice The new liquidation price
|
|
349
|
+
* @param referencePosition Reference position for cooldown-free price increase (address(0) for normal logic)
|
|
350
|
+
* @param withdrawAsNative If true and withdrawing collateral, unwrap to native coin
|
|
334
351
|
*/
|
|
335
|
-
function _adjust(uint256 newPrincipal, uint256 newCollateral, uint256 newPrice, address referencePosition) internal {
|
|
352
|
+
function _adjust(uint256 newPrincipal, uint256 newCollateral, uint256 newPrice, address referencePosition, bool withdrawAsNative) internal {
|
|
353
|
+
// Handle native coin deposit first (wraps to WCBTC)
|
|
354
|
+
if (msg.value > 0) {
|
|
355
|
+
IWrappedNative(address(collateral)).deposit{value: msg.value}();
|
|
356
|
+
}
|
|
357
|
+
|
|
336
358
|
uint256 colbal = _collateralBalance();
|
|
337
359
|
if (newCollateral > colbal) {
|
|
338
360
|
collateral.transferFrom(msg.sender, address(this), newCollateral - colbal);
|
|
@@ -343,7 +365,11 @@ contract Position is Ownable, IPosition, MathUtil {
|
|
|
343
365
|
_payDownDebt(debt - newPrincipal);
|
|
344
366
|
}
|
|
345
367
|
if (newCollateral < colbal) {
|
|
346
|
-
|
|
368
|
+
if (withdrawAsNative) {
|
|
369
|
+
_withdrawCollateralAsNative(msg.sender, colbal - newCollateral);
|
|
370
|
+
} else {
|
|
371
|
+
_withdrawCollateral(msg.sender, colbal - newCollateral);
|
|
372
|
+
}
|
|
347
373
|
}
|
|
348
374
|
// Must be called after collateral withdrawal
|
|
349
375
|
if (newPrincipal > principal) {
|
|
@@ -359,6 +385,7 @@ contract Position is Ownable, IPosition, MathUtil {
|
|
|
359
385
|
* @notice Allows the position owner to adjust the liquidation price as long as there is no pending challenge.
|
|
360
386
|
* Lowering the liquidation price can be done with immediate effect, given that there is enough collateral.
|
|
361
387
|
* Increasing the liquidation price triggers a cooldown period of 3 days, during which minting is suspended.
|
|
388
|
+
* @param newPrice The new liquidation price
|
|
362
389
|
*/
|
|
363
390
|
function adjustPrice(uint256 newPrice) public onlyOwner {
|
|
364
391
|
_adjustPrice(newPrice, address(0));
|
|
@@ -589,7 +616,7 @@ contract Position is Ownable, IPosition, MathUtil {
|
|
|
589
616
|
_accrueInterest(); // accrue interest
|
|
590
617
|
_fixRateToLeadrate(riskPremiumPPM); // sync interest rate with leadrate
|
|
591
618
|
|
|
592
|
-
Position(original).notifyMint(amount);
|
|
619
|
+
Position(payable(original)).notifyMint(amount);
|
|
593
620
|
jusd.mintWithReserve(target, amount, reserveContribution);
|
|
594
621
|
|
|
595
622
|
principal += amount;
|
|
@@ -643,7 +670,7 @@ contract Position is Ownable, IPosition, MathUtil {
|
|
|
643
670
|
*/
|
|
644
671
|
function _notifyRepaid(uint256 amount) internal {
|
|
645
672
|
if (amount > principal) revert RepaidTooMuch(amount - principal);
|
|
646
|
-
Position(original).notifyRepaid(amount);
|
|
673
|
+
Position(payable(original)).notifyRepaid(amount);
|
|
647
674
|
principal -= amount;
|
|
648
675
|
}
|
|
649
676
|
|
|
@@ -713,36 +740,68 @@ contract Position is Ownable, IPosition, MathUtil {
|
|
|
713
740
|
}
|
|
714
741
|
|
|
715
742
|
/**
|
|
716
|
-
* @notice
|
|
717
|
-
*
|
|
743
|
+
* @notice Rescue ERC20 tokens that were accidentally sent to this address.
|
|
744
|
+
* @dev Cannot be used for collateral - use withdrawCollateral() instead.
|
|
745
|
+
* @param token The ERC20 token to rescue
|
|
746
|
+
* @param target The address to send rescued tokens to
|
|
747
|
+
* @param amount The amount of tokens to rescue
|
|
718
748
|
*/
|
|
719
|
-
function
|
|
720
|
-
if (token == address(collateral))
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
IERC20(token).transfer(target, amount);
|
|
725
|
-
require(balance == _collateralBalance()); // guard against double-entry-point tokens
|
|
726
|
-
}
|
|
749
|
+
function rescueToken(address token, address target, uint256 amount) external onlyOwner {
|
|
750
|
+
if (token == address(collateral)) revert CannotRescueCollateral();
|
|
751
|
+
uint256 balance = _collateralBalance();
|
|
752
|
+
IERC20(token).transfer(target, amount);
|
|
753
|
+
require(balance == _collateralBalance()); // guard against double-entry-point tokens
|
|
727
754
|
}
|
|
728
755
|
|
|
729
756
|
/**
|
|
730
757
|
* @notice Withdraw collateral from the position up to the extent that it is still well collateralized afterwards.
|
|
731
758
|
* Not possible as long as there is an open challenge or the contract is subject to a cooldown.
|
|
732
|
-
*
|
|
733
759
|
* Withdrawing collateral below the minimum collateral amount formally closes the position.
|
|
760
|
+
* @param target Address to receive the collateral
|
|
761
|
+
* @param amount Amount of collateral to withdraw
|
|
734
762
|
*/
|
|
735
763
|
function withdrawCollateral(address target, uint256 amount) public ownerOrRoller {
|
|
736
764
|
uint256 balance = _withdrawCollateral(target, amount);
|
|
737
765
|
emit MintingUpdate(balance, price, principal);
|
|
738
766
|
}
|
|
739
767
|
|
|
768
|
+
/**
|
|
769
|
+
* @notice Withdraw collateral as native coin (unwrapped).
|
|
770
|
+
* @dev Only works for wrapped native collateral tokens.
|
|
771
|
+
* @param target Address to receive the native coin
|
|
772
|
+
* @param amount Amount of collateral to withdraw and unwrap
|
|
773
|
+
*/
|
|
774
|
+
function withdrawCollateralAsNative(address target, uint256 amount) public onlyOwner {
|
|
775
|
+
uint256 balance = _withdrawCollateralAsNative(target, amount);
|
|
776
|
+
emit MintingUpdate(balance, price, principal);
|
|
777
|
+
}
|
|
778
|
+
|
|
740
779
|
function _withdrawCollateral(address target, uint256 amount) internal noCooldown noChallenge returns (uint256) {
|
|
741
780
|
uint256 balance = _sendCollateral(target, amount);
|
|
742
781
|
_checkCollateral(balance, price);
|
|
743
782
|
return balance;
|
|
744
783
|
}
|
|
745
784
|
|
|
785
|
+
/**
|
|
786
|
+
* @dev Internal helper for native coin withdrawal. Used by withdrawCollateralAsNative() and _adjust().
|
|
787
|
+
* Does NOT emit MintingUpdate - callers are responsible for emitting.
|
|
788
|
+
*/
|
|
789
|
+
function _withdrawCollateralAsNative(address target, uint256 amount) internal noCooldown noChallenge returns (uint256) {
|
|
790
|
+
if (amount > 0) {
|
|
791
|
+
IWrappedNative(address(collateral)).withdraw(amount);
|
|
792
|
+
(bool success, ) = target.call{value: amount}("");
|
|
793
|
+
if (!success) revert NativeTransferFailed();
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
uint256 balance = _collateralBalance();
|
|
797
|
+
if (balance < minimumCollateral) {
|
|
798
|
+
_close();
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
_checkCollateral(balance, price);
|
|
802
|
+
return balance;
|
|
803
|
+
}
|
|
804
|
+
|
|
746
805
|
/**
|
|
747
806
|
* @notice Transfer the challenged collateral to the bidder. Only callable by minting hub.
|
|
748
807
|
*/
|
|
@@ -903,4 +962,15 @@ contract Position is Ownable, IPosition, MathUtil {
|
|
|
903
962
|
|
|
904
963
|
return (owner(), _size, principalToPay, interestToPay, reserveContribution);
|
|
905
964
|
}
|
|
965
|
+
|
|
966
|
+
/**
|
|
967
|
+
* @notice Receive native coin and auto-wrap to collateral.
|
|
968
|
+
* @dev Reverts for non-native positions to prevent stuck funds.
|
|
969
|
+
* Checks if sender is collateral to prevent unwrap loops.
|
|
970
|
+
*/
|
|
971
|
+
receive() external payable {
|
|
972
|
+
if (msg.sender != address(collateral)) {
|
|
973
|
+
IWrappedNative(address(collateral)).deposit{value: msg.value}();
|
|
974
|
+
}
|
|
975
|
+
}
|
|
906
976
|
}
|
|
@@ -48,9 +48,9 @@ contract PositionFactory {
|
|
|
48
48
|
* @return address of the newly created clone position
|
|
49
49
|
*/
|
|
50
50
|
function clonePosition(address _parent) external returns (address) {
|
|
51
|
-
Position parent = Position(_parent);
|
|
51
|
+
Position parent = Position(payable(_parent));
|
|
52
52
|
parent.assertCloneable();
|
|
53
|
-
Position clone = Position(_createClone(parent.original()));
|
|
53
|
+
Position clone = Position(payable(_createClone(parent.original())));
|
|
54
54
|
return address(clone);
|
|
55
55
|
}
|
|
56
56
|
|
|
@@ -137,12 +137,13 @@ contract PositionRoller {
|
|
|
137
137
|
collDeposit,
|
|
138
138
|
mint,
|
|
139
139
|
expiration,
|
|
140
|
+
0, // inherit price from parent
|
|
140
141
|
frontendCode // use the same frontend code
|
|
141
142
|
)
|
|
142
143
|
);
|
|
143
144
|
} else {
|
|
144
145
|
return IPosition(
|
|
145
|
-
IMintingHub(target.hub()).clone(msg.sender, address(target), collDeposit, mint, expiration)
|
|
146
|
+
IMintingHub(target.hub()).clone(msg.sender, address(target), collDeposit, mint, expiration, 0)
|
|
146
147
|
);
|
|
147
148
|
}
|
|
148
149
|
}
|
|
@@ -22,5 +22,5 @@ interface IMintingHub {
|
|
|
22
22
|
|
|
23
23
|
function buyExpiredCollateral(IPosition pos, uint256 upToAmount) external returns (uint256);
|
|
24
24
|
|
|
25
|
-
function clone(address owner, address parent, uint256 _initialCollateral, uint256 _initialMint, uint40 expiration) external returns (address);
|
|
25
|
+
function clone(address owner, address parent, uint256 _initialCollateral, uint256 _initialMint, uint40 expiration, uint256 _liqPrice) external payable returns (address);
|
|
26
26
|
}
|
|
@@ -56,13 +56,13 @@ interface IPosition {
|
|
|
56
56
|
|
|
57
57
|
function getMintAmount(uint256 usableMint) external view returns (uint256);
|
|
58
58
|
|
|
59
|
-
function adjust(uint256 newMinted, uint256 newCollateral, uint256 newPrice) external;
|
|
59
|
+
function adjust(uint256 newMinted, uint256 newCollateral, uint256 newPrice, bool withdrawAsNative) external payable;
|
|
60
60
|
|
|
61
61
|
function adjustPrice(uint256 newPrice) external;
|
|
62
62
|
|
|
63
63
|
function adjustPriceWithReference(uint256 newPrice, address referencePosition) external;
|
|
64
64
|
|
|
65
|
-
function
|
|
65
|
+
function adjustWithReference(uint256 newMinted, uint256 newCollateral, uint256 newPrice, address referencePosition, bool withdrawAsNative) external payable;
|
|
66
66
|
|
|
67
67
|
function isValidPriceReference(address referencePosition, uint256 newPrice) external view returns (bool);
|
|
68
68
|
|
|
@@ -80,10 +80,12 @@ interface IPosition {
|
|
|
80
80
|
|
|
81
81
|
function forceSale(address buyer, uint256 colAmount, uint256 proceeds) external;
|
|
82
82
|
|
|
83
|
-
function
|
|
83
|
+
function rescueToken(address token, address target, uint256 amount) external;
|
|
84
84
|
|
|
85
85
|
function withdrawCollateral(address target, uint256 amount) external;
|
|
86
86
|
|
|
87
|
+
function withdrawCollateralAsNative(address target, uint256 amount) external;
|
|
88
|
+
|
|
87
89
|
function transferChallengedCollateral(address target, uint256 amount) external;
|
|
88
90
|
|
|
89
91
|
function challengeData() external view returns (uint256 liqPrice, uint40 phase);
|
|
@@ -13,8 +13,9 @@ contract MintingHubGateway is MintingHub, IMintingHubGateway {
|
|
|
13
13
|
address _leadrate,
|
|
14
14
|
address _roller,
|
|
15
15
|
address _factory,
|
|
16
|
-
address _gateway
|
|
17
|
-
|
|
16
|
+
address _gateway,
|
|
17
|
+
address _wcbtc
|
|
18
|
+
) MintingHub(_jusd, _leadrate, _roller, _factory, _wcbtc) {
|
|
18
19
|
GATEWAY = IFrontendGateway(_gateway);
|
|
19
20
|
}
|
|
20
21
|
|
|
@@ -30,8 +31,8 @@ contract MintingHubGateway is MintingHub, IMintingHubGateway {
|
|
|
30
31
|
uint256 _liqPrice,
|
|
31
32
|
uint24 _reservePPM,
|
|
32
33
|
bytes32 _frontendCode
|
|
33
|
-
) public returns (address) {
|
|
34
|
-
address position = openPosition(
|
|
34
|
+
) public payable returns (address) {
|
|
35
|
+
address position = MintingHub.openPosition(
|
|
35
36
|
_collateralAddress,
|
|
36
37
|
_minCollateral,
|
|
37
38
|
_initialCollateral,
|
|
@@ -47,19 +48,12 @@ contract MintingHubGateway is MintingHub, IMintingHubGateway {
|
|
|
47
48
|
return position;
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
function clone(
|
|
51
|
-
address parent,
|
|
52
|
-
uint256 _initialCollateral,
|
|
53
|
-
uint256 _initialMint,
|
|
54
|
-
uint40 expiration,
|
|
55
|
-
bytes32 frontendCode
|
|
56
|
-
) public returns (address) {
|
|
57
|
-
return clone(msg.sender, parent, _initialCollateral, _initialMint, expiration, frontendCode);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
51
|
/**
|
|
61
|
-
* @notice Clones an existing position and immediately tries to mint the specified amount using the given collateral.
|
|
62
|
-
* @dev
|
|
52
|
+
* @notice Clones an existing position and immediately tries to mint the specified amount using the given collateral.
|
|
53
|
+
* @dev For native coin positions (WcBTC), send msg.value equal to _initialCollateral.
|
|
54
|
+
* For ERC20 collateral, ensure prior approval for the minting hub to transfer _initialCollateral.
|
|
55
|
+
* @param _liqPrice Optionally adjust price of new position after minting. Set to 0 to inherit parent's price.
|
|
56
|
+
* @param frontendCode Optionally register the position with a frontend code.
|
|
63
57
|
*/
|
|
64
58
|
function clone(
|
|
65
59
|
address owner,
|
|
@@ -67,9 +61,10 @@ contract MintingHubGateway is MintingHub, IMintingHubGateway {
|
|
|
67
61
|
uint256 _initialCollateral,
|
|
68
62
|
uint256 _initialMint,
|
|
69
63
|
uint40 expiration,
|
|
64
|
+
uint256 _liqPrice,
|
|
70
65
|
bytes32 frontendCode
|
|
71
|
-
) public returns (address) {
|
|
72
|
-
address position = clone(owner, parent, _initialCollateral, _initialMint, expiration);
|
|
66
|
+
) public payable returns (address) {
|
|
67
|
+
address position = MintingHub.clone(owner, parent, _initialCollateral, _initialMint, expiration, _liqPrice);
|
|
73
68
|
GATEWAY.registerPosition(position, frontendCode);
|
|
74
69
|
return position;
|
|
75
70
|
}
|
|
@@ -7,6 +7,6 @@ import {IFrontendGateway} from "./IFrontendGateway.sol";
|
|
|
7
7
|
interface IMintingHubGateway {
|
|
8
8
|
function GATEWAY() external view returns (IFrontendGateway);
|
|
9
9
|
function notifyInterestPaid(uint256 amount) external;
|
|
10
|
-
function openPosition(address _collateralAddress, uint256 _minCollateral, uint256 _initialCollateral, uint256 _mintingMaximum, uint40 _initPeriodSeconds, uint40 _expirationSeconds, uint40 _challengeSeconds, uint24 _riskPremium, uint256 _liqPrice, uint24 _reservePPM, bytes32 _frontendCode) external returns (address);
|
|
11
|
-
function clone(address owner, address parent, uint256 _initialCollateral, uint256 _initialMint, uint40 expiration, bytes32 frontendCode) external returns (address);
|
|
10
|
+
function openPosition(address _collateralAddress, uint256 _minCollateral, uint256 _initialCollateral, uint256 _mintingMaximum, uint40 _initPeriodSeconds, uint40 _expirationSeconds, uint40 _challengeSeconds, uint24 _riskPremium, uint256 _liqPrice, uint24 _reservePPM, bytes32 _frontendCode) external payable returns (address);
|
|
11
|
+
function clone(address owner, address parent, uint256 _initialCollateral, uint256 _initialMint, uint40 expiration, uint256 _liqPrice, bytes32 frontendCode) external payable returns (address);
|
|
12
12
|
}
|
|
@@ -54,7 +54,7 @@ contract PositionExpirationTest {
|
|
|
54
54
|
200000
|
|
55
55
|
);
|
|
56
56
|
}
|
|
57
|
-
Position(pos).transferOwnership(owner);
|
|
57
|
+
Position(payable(pos)).transferOwnership(owner);
|
|
58
58
|
return pos;
|
|
59
59
|
}
|
|
60
60
|
|
|
@@ -63,10 +63,10 @@ contract PositionExpirationTest {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
function forceBuy(address pos, uint256 amount) public {
|
|
66
|
-
uint256 price = hub.expiredPurchasePrice(Position(pos));
|
|
66
|
+
uint256 price = hub.expiredPurchasePrice(Position(payable(pos)));
|
|
67
67
|
uint256 balanceBefore = jusd.balanceOf(address(this));
|
|
68
68
|
uint256 colBalBefore = col.balanceOf(address(this));
|
|
69
|
-
amount = hub.buyExpiredCollateral(Position(pos), amount);
|
|
69
|
+
amount = hub.buyExpiredCollateral(Position(payable(pos)), amount);
|
|
70
70
|
uint256 balanceAfter = jusd.balanceOf(address(this));
|
|
71
71
|
uint256 colBalAfter = col.balanceOf(address(this));
|
|
72
72
|
require(colBalAfter - colBalBefore == amount, "collateral amount");
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.0;
|
|
3
|
+
|
|
4
|
+
import {IPosition} from "../MintingHubV2/interface/IPosition.sol";
|
|
5
|
+
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @title ReentrantAttacker
|
|
9
|
+
* @notice Test contract that attempts reentrancy attack on Position.withdrawCollateralAsNative()
|
|
10
|
+
* @dev Used to verify Position is safe against reentrancy
|
|
11
|
+
*/
|
|
12
|
+
contract ReentrantAttacker is Ownable {
|
|
13
|
+
IPosition public targetPosition;
|
|
14
|
+
uint256 public attackCount;
|
|
15
|
+
uint256 public withdrawAmount;
|
|
16
|
+
bool public attackSucceeded;
|
|
17
|
+
string public lastRevertReason;
|
|
18
|
+
|
|
19
|
+
event AttackAttempted(uint256 count, bool success, string reason);
|
|
20
|
+
|
|
21
|
+
constructor() Ownable(msg.sender) {}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @notice Sets the target position for the attack
|
|
25
|
+
* @param _position The position contract to attack
|
|
26
|
+
*/
|
|
27
|
+
function setTarget(address _position) external onlyOwner {
|
|
28
|
+
targetPosition = IPosition(_position);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @notice Initiates the reentrancy attack
|
|
33
|
+
* @param _amount Amount to withdraw in each attempt
|
|
34
|
+
*/
|
|
35
|
+
function attack(uint256 _amount) external onlyOwner {
|
|
36
|
+
require(address(targetPosition) != address(0), "Target not set");
|
|
37
|
+
withdrawAmount = _amount;
|
|
38
|
+
attackCount = 0;
|
|
39
|
+
attackSucceeded = false;
|
|
40
|
+
lastRevertReason = "";
|
|
41
|
+
|
|
42
|
+
targetPosition.withdrawCollateralAsNative(address(this), _amount);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @notice Called when receiving native coin - attempts reentrancy
|
|
47
|
+
*/
|
|
48
|
+
receive() external payable {
|
|
49
|
+
attackCount++;
|
|
50
|
+
|
|
51
|
+
if (attackCount < 2) {
|
|
52
|
+
// Attempt reentrancy on second receive
|
|
53
|
+
try targetPosition.withdrawCollateralAsNative(address(this), withdrawAmount) {
|
|
54
|
+
// If this succeeds, reentrancy attack worked
|
|
55
|
+
attackSucceeded = true;
|
|
56
|
+
emit AttackAttempted(attackCount, true, "Attack succeeded - VULNERABILITY!");
|
|
57
|
+
} catch Error(string memory reason) {
|
|
58
|
+
lastRevertReason = reason;
|
|
59
|
+
emit AttackAttempted(attackCount, false, reason);
|
|
60
|
+
} catch (bytes memory) {
|
|
61
|
+
lastRevertReason = "Unknown revert";
|
|
62
|
+
emit AttackAttempted(attackCount, false, "Unknown revert");
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @notice Allows owner to withdraw any native coin from this contract
|
|
69
|
+
*/
|
|
70
|
+
function withdrawAll() external onlyOwner {
|
|
71
|
+
(bool success, ) = owner().call{value: address(this).balance}("");
|
|
72
|
+
require(success, "Withdraw failed");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.0;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @title RejectNative
|
|
6
|
+
* @notice Test helper contract that rejects all native coin transfers
|
|
7
|
+
* @dev Used to test NativeTransferFailed error in Position.withdrawCollateralAsNative()
|
|
8
|
+
*/
|
|
9
|
+
contract RejectNative {
|
|
10
|
+
receive() external payable {
|
|
11
|
+
revert("I reject native coin");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
fallback() external payable {
|
|
15
|
+
revert("I reject native coin");
|
|
16
|
+
}
|
|
17
|
+
}
|