-
Notifications
You must be signed in to change notification settings - Fork 1
/
IndexLogic.sol
148 lines (124 loc) · 5.92 KB
/
IndexLogic.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
// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.7;
import "@openzeppelin/contracts/access/IAccessControl.sol";
import "./libraries/BP.sol";
import "./libraries/IndexLibrary.sol";
import "./interfaces/IvToken.sol";
import "./interfaces/IOrderer.sol";
import "./interfaces/IIndexLogic.sol";
import "./interfaces/IvTokenFactory.sol";
import "./interfaces/IPhuturePriceOracle.sol";
import "./PhutureIndex.sol";
/// @title Index logic
/// @notice Contains common logic for index minting and burning
contract IndexLogic is PhutureIndex, IIndexLogic {
using FullMath for uint;
using EnumerableSet for EnumerableSet.AddressSet;
/// @notice Asset role
bytes32 internal constant ASSET_ROLE = keccak256("ASSET_ROLE");
/// @notice Role granted for asset which should be skipped during burning
bytes32 internal constant SKIPPED_ASSET_ROLE = keccak256("SKIPPED_ASSET_ROLE");
/// @notice Mints index to `_recipient` address
/// @param _recipient Recipient address
function mint(address _recipient) external override {
address feePool = IIndexRegistry(registry).feePool();
_chargeAUMFee(feePool);
IPhuturePriceOracle oracle = IPhuturePriceOracle(IIndexRegistry(registry).priceOracle());
uint lastAssetBalanceInBase;
uint minAmountInBase = type(uint).max;
for (uint i; i < assets.length(); ++i) {
require(IAccessControl(registry).hasRole(ASSET_ROLE, assets.at(i)), "Index: INVALID_ASSET");
if (weightOf[assets.at(i)] == 0) {
continue;
}
uint assetPerBaseInUQ = oracle.refreshedAssetPerBaseInUQ(assets.at(i));
// Q_b * w_i * p_i = Q_i
// Q_b = Q_i / (w_i * p_i)
IvToken vToken = IvToken(IvTokenFactory(vTokenFactory).createOrReturnVTokenOf(assets.at(i)));
uint amountInAsset = IERC20(assets.at(i)).balanceOf(address(vToken)) - vToken.lastBalance();
uint weightedPrice = assetPerBaseInUQ * weightOf[assets.at(i)];
uint _minAmountInBase = amountInAsset.mulDiv(FixedPoint112.Q112 * IndexLibrary.MAX_WEIGHT, weightedPrice);
if (_minAmountInBase < minAmountInBase) {
minAmountInBase = _minAmountInBase;
}
uint lastBalanceInAsset = vToken.lastAssetBalanceOf(address(this));
vToken.mint();
uint balanceInBase = lastBalanceInAsset.mulDiv(FixedPoint112.Q112, assetPerBaseInUQ);
lastAssetBalanceInBase += balanceInBase;
}
for (uint i; i < inactiveAssets.length(); ++i) {
if (!IAccessControl(registry).hasRole(SKIPPED_ASSET_ROLE, inactiveAssets.at(i))) {
uint lastBalanceInAsset = IvToken(
IvTokenFactory(vTokenFactory).createOrReturnVTokenOf(inactiveAssets.at(i))
).lastAssetBalanceOf(address(this));
lastAssetBalanceInBase += lastBalanceInAsset.mulDiv(
FixedPoint112.Q112,
oracle.refreshedAssetPerBaseInUQ(inactiveAssets.at(i))
);
}
}
assert(minAmountInBase != type(uint).max);
uint value;
if (totalSupply() != 0) {
require(lastAssetBalanceInBase > 0, "Index: INSUFFICIENT_AMOUNT");
value =
(oracle.convertToIndex(minAmountInBase, decimals()) * totalSupply()) /
oracle.convertToIndex(lastAssetBalanceInBase, decimals());
} else {
value = oracle.convertToIndex(minAmountInBase, decimals()) - IndexLibrary.INITIAL_QUANTITY;
_mint(address(0xdead), IndexLibrary.INITIAL_QUANTITY);
}
uint fee = (value * IFeePool(feePool).mintingFeeInBPOf(address(this))) / BP.DECIMAL_FACTOR;
if (fee > 0) {
_mint(feePool, fee);
value -= fee;
}
_mint(_recipient, value);
}
/// @notice Burns index and transfers assets to `_recipient` address
/// @param _recipient Recipient address
function burn(address _recipient) external override {
uint value = balanceOf(address(this));
require(value > 0, "Index: INSUFFICIENT_AMOUNT");
uint length = assets.length();
bool containsBlacklistedAssets;
for (uint i; i < length; ++i) {
if (!IAccessControl(registry).hasRole(ASSET_ROLE, assets.at(i))) {
containsBlacklistedAssets = true;
break;
}
}
if (!containsBlacklistedAssets) {
address feePool = IIndexRegistry(registry).feePool();
uint fee = (value * IFeePool(feePool).burningFeeInBPOf(address(this))) / BP.DECIMAL_FACTOR;
if (fee > 0) {
// AUM charged in _transfer method
_transfer(address(this), feePool, fee);
value -= fee;
} else {
_chargeAUMFee(feePool);
}
}
address orderer = IIndexRegistry(registry).orderer();
uint lastOrderId = IOrderer(orderer).lastOrderIdOf(address(this));
for (uint i; i < length + inactiveAssets.length(); ++i) {
address asset = i < length ? assets.at(i) : inactiveAssets.at(i - length);
if (containsBlacklistedAssets && IAccessControl(registry).hasRole(SKIPPED_ASSET_ROLE, asset)) {
continue;
}
IvToken vToken = IvToken(IvTokenFactory(vTokenFactory).vTokenOf(asset));
uint indexAssetBalance = vToken.balanceOf(address(this));
uint accountBalance = (value * indexAssetBalance) / totalSupply();
if (accountBalance == 0) {
continue;
}
// calculate index value in vault to be burned
vToken.transfer(address(vToken), accountBalance);
vToken.burn(_recipient);
if (lastOrderId > 0) {
IOrderer(orderer).reduceOrderAsset(asset, totalSupply() - value, totalSupply());
}
}
_burn(address(this), value);
}
}