@usdu-core/usdu-core 0.0.1

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.
Files changed (107) hide show
  1. package/.claude/settings.local.json +12 -0
  2. package/.env.example +6 -0
  3. package/.prettierrc.json +7 -0
  4. package/LICENSE +674 -0
  5. package/README.md +244 -0
  6. package/contracts/curve/CurveAdapterV1.sol +310 -0
  7. package/contracts/curve/CurveAdapterV1_1.sol +198 -0
  8. package/contracts/curve/helpers/ICurveStableSwapNG.json +566 -0
  9. package/contracts/curve/helpers/ICurveStableSwapNG.sol +177 -0
  10. package/contracts/deploy/VaultDeployer.sol +110 -0
  11. package/contracts/morpho/MorphoAdapterV1.sol +255 -0
  12. package/contracts/morpho/MorphoAdapterV1_1.sol +137 -0
  13. package/contracts/morpho/MorphoAdapterV1_2.sol +126 -0
  14. package/contracts/morpho/helpers/AggregatorV3Interface.sol +24 -0
  15. package/contracts/morpho/helpers/ChainlinkDataFeedLib.sol +36 -0
  16. package/contracts/morpho/helpers/ConstantsLib.sol +20 -0
  17. package/contracts/morpho/helpers/ErrorsLib.sol +17 -0
  18. package/contracts/morpho/helpers/IERC4626.sol +6 -0
  19. package/contracts/morpho/helpers/IMetaMorphoV1_1.sol +229 -0
  20. package/contracts/morpho/helpers/IMetaMorphoV1_1Factory.sol +32 -0
  21. package/contracts/morpho/helpers/IMorpho.sol +361 -0
  22. package/contracts/morpho/helpers/IMorphoCallbacks.sol +52 -0
  23. package/contracts/morpho/helpers/IMorphoChainlinkOracleV2.sol +39 -0
  24. package/contracts/morpho/helpers/IMorphoChainlinkOracleV2Factory.sol +56 -0
  25. package/contracts/morpho/helpers/IOracle.sol +15 -0
  26. package/contracts/morpho/helpers/MarketParamsLib.sol +21 -0
  27. package/contracts/morpho/helpers/MathLib.sol +45 -0
  28. package/contracts/morpho/helpers/Morpho.sol.bak +517 -0
  29. package/contracts/morpho/helpers/MorphoChainlinkOracleV2.sol +157 -0
  30. package/contracts/morpho/helpers/PendingLib.sol +47 -0
  31. package/contracts/morpho/helpers/SharesMathLib.sol +45 -0
  32. package/contracts/morpho/helpers/VaultLib.sol +18 -0
  33. package/contracts/reward/RewardDistributionV1.sol +110 -0
  34. package/contracts/reward/RewardRouterV0.sol +63 -0
  35. package/contracts/reward/Rewards.example.json +72 -0
  36. package/contracts/stablecoin/IStablecoin.sol +211 -0
  37. package/contracts/stablecoin/IStablecoinMetadata.sol +27 -0
  38. package/contracts/stablecoin/IStablecoinModifier.sol +52 -0
  39. package/contracts/stablecoin/Stablecoin.sol +376 -0
  40. package/contracts/stablecoin/libraries/ConstantsLib.sol +13 -0
  41. package/contracts/stablecoin/libraries/ErrorsLib.sol +45 -0
  42. package/contracts/stablecoin/libraries/EventsLib.sol +74 -0
  43. package/contracts/stablecoin/libraries/PendingLib.sol +38 -0
  44. package/contracts/vault/VaultAdapterRecoverV1.sol +29 -0
  45. package/contracts/vault/VaultAdapterV1.sol +126 -0
  46. package/dist/index.d.mts +16154 -0
  47. package/dist/index.d.ts +16154 -0
  48. package/dist/index.js +21134 -0
  49. package/dist/index.mjs +21061 -0
  50. package/docs/CoreVault: Integration of new Markets.md +197 -0
  51. package/docs/CoreVault: SupplyQueue.md +11 -0
  52. package/docs/Markets USDC Vault.md +35 -0
  53. package/docs/Markets USDU Vault.md +89 -0
  54. package/docs/Markets WETH Vault.md +35 -0
  55. package/docs/Overview.drawio +117 -0
  56. package/exports/abis/curve/CurveAdapterV1.ts +599 -0
  57. package/exports/abis/curve/CurveAdapterV1_1.ts +609 -0
  58. package/exports/abis/curve/CurveAdapterV1_2.ts +721 -0
  59. package/exports/abis/curve/helper/ICurveStableSwapNG.ts +1589 -0
  60. package/exports/abis/morpho/MorphoAdapterV1.ts +516 -0
  61. package/exports/abis/morpho/MorphoAdapterV1_1.ts +489 -0
  62. package/exports/abis/morpho/MorphoAdapterV1_2.ts +459 -0
  63. package/exports/abis/morpho/helper/AggregatorV3Interface.ts +113 -0
  64. package/exports/abis/morpho/helper/IMetaMorphoV1_1.ts +1483 -0
  65. package/exports/abis/morpho/helper/IMetaMorphoV1_1Base.ts +607 -0
  66. package/exports/abis/morpho/helper/IMetaMorphoV1_1StaticTyping.ts +696 -0
  67. package/exports/abis/morpho/helper/IMorpho.ts +1024 -0
  68. package/exports/abis/morpho/helper/IMorphoBase.ts +886 -0
  69. package/exports/abis/morpho/helper/IMorphoChainlinkOracleV2.ts +132 -0
  70. package/exports/abis/morpho/helper/IMorphoChainlinkOracleV2Factory.ts +109 -0
  71. package/exports/abis/morpho/helper/IMorphoFlashLoanCallback.ts +20 -0
  72. package/exports/abis/morpho/helper/IMorphoLiquidateCallback.ts +20 -0
  73. package/exports/abis/morpho/helper/IMorphoRepayCallback.ts +20 -0
  74. package/exports/abis/morpho/helper/IMorphoStaticTyping.ts +1003 -0
  75. package/exports/abis/morpho/helper/IMorphoSupplyCallback.ts +20 -0
  76. package/exports/abis/morpho/helper/IMorphoSupplyCollateralCallback.ts +20 -0
  77. package/exports/abis/morpho/helper/IMulticall.ts +21 -0
  78. package/exports/abis/morpho/helper/IOracle.ts +15 -0
  79. package/exports/abis/morpho/helper/IOwnable.ts +55 -0
  80. package/exports/abis/morpho/helper/MorphoChainlinkOracleV2.ts +188 -0
  81. package/exports/abis/openzeppelin/ERC20.ts +310 -0
  82. package/exports/abis/openzeppelin/ERC20Permit.ts +520 -0
  83. package/exports/abis/openzeppelin/IERC20.ts +185 -0
  84. package/exports/abis/openzeppelin/IERC20Metadata.ts +224 -0
  85. package/exports/abis/openzeppelin/IERC20Permit.ts +77 -0
  86. package/exports/abis/openzeppelin/IERC4626.ts +614 -0
  87. package/exports/abis/reward/RewardDistributionV1.ts +246 -0
  88. package/exports/abis/stablecoin/ErrorsLib.ts +114 -0
  89. package/exports/abis/stablecoin/EventsLib.ts +372 -0
  90. package/exports/abis/stablecoin/IStablecoin.ts +642 -0
  91. package/exports/abis/stablecoin/IStablecoinMetadata.ts +856 -0
  92. package/exports/abis/stablecoin/IStablecoinModifier.ts +15 -0
  93. package/exports/abis/stablecoin/Stablecoin.ts +1922 -0
  94. package/exports/abis/termmax/ITermMaxVault.ts +2335 -0
  95. package/exports/abis/vault/VaultAdapterRecoverV1.ts +490 -0
  96. package/exports/abis/vault/VaultAdapterV1.ts +459 -0
  97. package/exports/address.config.ts +113 -0
  98. package/exports/address.types.ts +130 -0
  99. package/exports/index.ts +61 -0
  100. package/hardhat.config.ts +231 -0
  101. package/helper/store.args.ts +17 -0
  102. package/helper/wallet.info.ts +3 -0
  103. package/helper/wallet.ts +41 -0
  104. package/install-macos.sh +46 -0
  105. package/package.json +73 -0
  106. package/tsconfig.json +15 -0
  107. package/tsup.config.ts +10 -0
package/README.md ADDED
@@ -0,0 +1,244 @@
1
+ # Core
2
+
3
+ ### Yarn Package Scripts
4
+
5
+ ```json
6
+ // yarn run <command> args...
7
+
8
+ "wallet": "npx ts-node helper/wallet.info.ts",
9
+ "wallet:info": "npx ts-node helper/wallet.info.ts",
10
+
11
+ "compile": "npx hardhat compile",
12
+ "test": "npx hardhat test",
13
+ "coverage": "npx hardhat coverage",
14
+
15
+ "deploy": "npx hardhat ignition deploy",
16
+ "verify": "npx hardhat verify",
17
+
18
+ "npm:build": "tsup",
19
+ "npm:publish": "npm publish --access public"
20
+ ```
21
+
22
+ ### 1. Install dependencies
23
+
24
+ `yarn install`
25
+
26
+ ### 2. Set Environment
27
+
28
+ > See .env.example
29
+
30
+ ```JSON
31
+ file: .env
32
+
33
+ DEPLOYER_SEED="test test test test test test test test test test test junk"
34
+ DEPLOYER_SEED_INDEX=1
35
+ ALCHEMY_RPC_KEY=...
36
+ ETHERSCAN_API=...
37
+ ```
38
+
39
+ > Create new session or re-navigate to the current directory, to make sure environment is loaded from `.env`
40
+
41
+ ### 3. Develop Smart Contracts
42
+
43
+ > Develop your contracts in the `/contracts` directory and compile with:
44
+
45
+ ```Bash
46
+ yarn run compile # Compiles all contracts
47
+ ```
48
+
49
+ ### 4. Testing
50
+
51
+ > All test files are located in /test directory. Run tests using:
52
+
53
+ ```Bash
54
+ yarn run test # Run all tests
55
+ yarn run test test/Membership.ts # Run specific test file
56
+ yarn run coverage # Generate test coverage report
57
+ ```
58
+
59
+ ### 5. Write Deployment Scripts (via ignition deploy and verify)
60
+
61
+ - Create new `/ignition/params/[file].ts` for type conform deployment params
62
+ - Create new `/ignition/module/[file].ts` for workflow of deployment
63
+
64
+ > Deployment modules are located in `/ignition/modules`. Deploy your contracts:
65
+
66
+ ```Bash
67
+ yarn run deploy ignition/modules/MembershipFactory.ts --network polygon --verify --deployment-id Membership01
68
+
69
+ --> increase: deployment-id
70
+ ```
71
+
72
+ This will:
73
+
74
+ - Compile and deploy contracts
75
+ - Verify on Etherscan and Sourcify
76
+ - Generate deployment artifacts in `/ignition/deployments`
77
+
78
+ Verify:
79
+
80
+ - verifies contract on etherscan
81
+ - verifies contract on sourcify
82
+
83
+ Key deployment files:
84
+
85
+ - deployed_addresses.json: Contains contract addresses
86
+ - journal.json: Detailed deployment logs
87
+
88
+ - creates deployment artifacts in /ignition`/deployments` directory
89
+ - creates ./ignition/deployments/[deployment]/`deployed_addresses.json`
90
+ - creates ./ignition/deployments/[deployment]/`journal.jsonl`
91
+ - creates constructor-args in /ignition`/constructor-args` directory, as JS module export
92
+
93
+ ### 5.1 Example
94
+
95
+ ```Bash
96
+ ✔ Confirm deploy to network polygon (137)? … yes
97
+ {
98
+ message: 'Config Info: Deploying Module with accounts',
99
+ admin: '0xb687FE7E47774B22F10Ca5E747496d81827167E3',
100
+ executor: '0xBdae8D35EDe5bc5174E805DcBe3F7714d142DAAb',
101
+ member: '0x2ACf17C04F1d8BE7E9D5529894DCee86bf2fcdC3'
102
+ }
103
+ Constructor Args
104
+ [
105
+ '0xb687FE7E47774B22F10Ca5E747496d81827167E3',
106
+ '0xBdae8D35EDe5bc5174E805DcBe3F7714d142DAAb',
107
+ '0x2ACf17C04F1d8BE7E9D5529894DCee86bf2fcdC3'
108
+ ]
109
+ Hardhat Ignition 🚀
110
+
111
+ Deploying [ MembershipModule ]
112
+
113
+ Batch #1
114
+ Executed MembershipModule#Membership
115
+
116
+ Batch #2
117
+ Executed MembershipModule#Storage
118
+
119
+ [ MembershipModule ] successfully deployed 🚀
120
+
121
+ Deployed Addresses
122
+
123
+ MembershipModule#Membership - 0x72950A0A9689fCA941Ddc9E1a58dcD3fb792E3D2
124
+ MembershipModule#Storage - 0x8A7e8091e71cCB7D1EbDd773C26AD82AAd323328
125
+
126
+ Verifying deployed contracts
127
+
128
+ Verifying contract "contracts/Membership.sol:Membership" for network polygon...
129
+ Contract contracts/Membership.sol:Membership already verified on network polygon:
130
+ - https://polygonscan.com/address/0x72950A0A9689fCA941Ddc9E1a58dcD3fb792E3D2#code
131
+
132
+ Verifying contract "contracts/Storage.sol:Storage" for network polygon...
133
+ Contract contracts/Storage.sol:Storage already verified on network polygon:
134
+ - https://polygonscan.com/address/0x8A7e8091e71cCB7D1EbDd773C26AD82AAd323328#code
135
+
136
+ ✨ Done in 69.96s.
137
+ ```
138
+
139
+ ### 5.2 Manual Verify
140
+
141
+ `npx hardhat verify --network polygon --constructor-args ./ignition/constructor-args/$FILE.js $ADDRESS`
142
+
143
+ or manually include unrelated contracts by creating or using `/ignition/constructor-args/[file].js`
144
+
145
+ `npx hardhat ignition verify $DEPLOYMENT --include-unrelated-contracts`
146
+
147
+ ### 6 Prepare NPM Package Support
148
+
149
+ - [x] Export ready to use TypeScript ABIs
150
+ - [x] Export ready to use TypeScript deployed address config
151
+ - [ ] ...
152
+
153
+ ### 6.1 TypeScript ABIs
154
+
155
+ Export contract ABIs for npm package usage by copying the JSON into dedicated TypeScript files:
156
+
157
+ ```TS
158
+ file: exports/abis/...
159
+
160
+ export const StorageABI = [
161
+ ...
162
+ JSON
163
+ ...
164
+ ] as const;
165
+ ```
166
+
167
+ ### 6.2 TypeScript Address Config
168
+
169
+ Provides a mapping of contract addresses for the Membership and Storage contracts deployed on different blockchain networks.
170
+
171
+ The `ADDRESS` object contains the contract addresses for the `mainnet` and `polygon` networks, with the network ID as the key.
172
+ The `zeroAddress` is used as a placeholder for the `mainnet` network, as the contracts have not been deployed there yet.
173
+
174
+ ```TS
175
+ file: exports/address.config.ts
176
+
177
+ import { mainnet, polygon } from 'viem/chains';
178
+ import { Address, zeroAddress } from 'viem';
179
+
180
+ export interface ChainAddress {
181
+ membership: Address;
182
+ storage: Address;
183
+ }
184
+
185
+ export const ADDRESS: Record<number, ChainAddress> = {
186
+ [mainnet.id]: {
187
+ membership: zeroAddress,
188
+ storage: zeroAddress,
189
+ },
190
+ [polygon.id]: {
191
+ membership: '0x72950A0A9689fCA941Ddc9E1a58dcD3fb792E3D2',
192
+ storage: '0x8A7e8091e71cCB7D1EbDd773C26AD82AAd323328',
193
+ },
194
+ };
195
+ ```
196
+
197
+ # 7. TSUP and npm package
198
+
199
+ ### 7.1 TSUP
200
+
201
+ > Config: /tsup.config.ts
202
+
203
+ TSUP bundles TypeScript code into optimized JavaScript packages. This package uses TSUP to create production-ready builds.
204
+
205
+ `yarn run build`
206
+
207
+ ### 7.2 NPM Package
208
+
209
+ > **Increase Version:** Update version number in package.json using semantic versioning (e.g. 0.0.1 -> 0.0.2) before publishing new changes.
210
+
211
+ ```
212
+ file: /package.json
213
+
214
+ "name": "@wrytlabs/core-template",
215
+ "version": "0.0.1", <-- HERE
216
+ ```
217
+
218
+ Login to your NPM account
219
+
220
+ `npm login`
221
+
222
+ This will publish your package to NPM with public access, making it **available for anyone to install and use**.
223
+
224
+ `yarn run publish`
225
+
226
+ To publish new version. `publish: "npm publish --access public"`
227
+
228
+ > **Note**: During npm package publishing, the command may execute twice. The second execution will fail with a version conflict since the package is already published. This is expected behavior and the first publish will have succeeded.
229
+
230
+ ### 7.3 How to transpile package into bundled apps
231
+
232
+ (not needed, since its already a true JS bundled module)
233
+
234
+ E.g. for `NextJs` using the `next.config.js` in root of project.
235
+
236
+ ```js
237
+ /** @type {import('next').NextConfig} */
238
+ const nextConfig = {
239
+ reactStrictMode: true,
240
+ transpilePackages: ['@wrtylabs/core', '@wrytelabs/api'],
241
+ };
242
+
243
+ module.exports = nextConfig;
244
+ ```
@@ -0,0 +1,310 @@
1
+ // SPDX-License-Identifier: GPL-2.0-or-later
2
+ pragma solidity ^0.8.20;
3
+
4
+ import {Math} from '@openzeppelin/contracts/utils/math/Math.sol';
5
+
6
+ import {Context} from '@openzeppelin/contracts/utils/Context.sol';
7
+ import {IERC20Metadata} from '@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol';
8
+ import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
9
+
10
+ import {Stablecoin} from '../stablecoin/Stablecoin.sol';
11
+ import {ErrorsLib} from '../stablecoin/libraries/ErrorsLib.sol';
12
+ import {ICurveStableSwapNG} from './helpers/ICurveStableSwapNG.sol';
13
+
14
+ /**
15
+ * @title CurveAdapterV1
16
+ * @author @samclassix <samclassix@proton.me>, @wrytlabs <wrytlabs@proton.me>
17
+ * @notice This is an adapter for interacting with ICurveStableSwapNG to mint liquidity straight into the pool under certain conditions.
18
+ */
19
+ contract CurveAdapterV1 is Context {
20
+ using Math for uint256;
21
+ using SafeERC20 for IERC20Metadata;
22
+ using SafeERC20 for Stablecoin;
23
+
24
+ ICurveStableSwapNG public immutable pool;
25
+ Stablecoin public immutable stable;
26
+ IERC20Metadata public immutable coin;
27
+
28
+ uint256 public immutable idxS;
29
+ uint256 public immutable idxC;
30
+
31
+ uint256 public totalMinted;
32
+ uint256 public totalRevenue;
33
+
34
+ address[5] public receivers;
35
+ uint32[5] public weights;
36
+ uint256 public totalWeights;
37
+
38
+ address[5] public pendingReceivers;
39
+ uint32[5] public pendingWeights;
40
+ uint256 public pendingValidAt;
41
+
42
+ // ---------------------------------------------------------------------------------------
43
+
44
+ event SubmitDistribution(address indexed caller, address[5] receivers, uint32[5] weights, uint256 timelock);
45
+ event RevokeDistribution(address indexed caller);
46
+ event SetDistribution(address indexed caller);
47
+
48
+ event AddLiquidity(address indexed sender, uint256 minted, uint256 totalMinted, uint256 sharesMinted, uint256 totalShares);
49
+ event RemoveLiquidity(address indexed sender, uint256 burned, uint256 totalMinted, uint256 sharesBurned, uint256 totalShares);
50
+ event Revenue(uint256 amount, uint256 totalRevenue, uint256 totalMinted);
51
+ event Distribution(address indexed receiver, uint256 amount, uint256 ratio);
52
+
53
+ // ---------------------------------------------------------------------------------------
54
+
55
+ error ImbalancedVariant(uint256[] balances);
56
+ error NotProfitable();
57
+
58
+ // ---------------------------------------------------------------------------------------
59
+
60
+ modifier onlyCurator() {
61
+ stable.verifyCurator(_msgSender());
62
+ _;
63
+ }
64
+
65
+ modifier onlyCuratorOrGuardian() {
66
+ stable.verifyCuratorOrGuardian(_msgSender());
67
+ _;
68
+ }
69
+
70
+ modifier afterTimelock(uint256 validAt) {
71
+ if (validAt == 0) revert ErrorsLib.NoPendingValue();
72
+ if (block.timestamp < validAt) revert ErrorsLib.TimelockNotElapsed();
73
+ _;
74
+ }
75
+
76
+ // ---------------------------------------------------------------------------------------
77
+
78
+ constructor(
79
+ ICurveStableSwapNG _pool,
80
+ uint256 _idxS, // IStablecoin
81
+ uint256 _idxC // IERC20 coin
82
+ ) {
83
+ pool = _pool;
84
+
85
+ require(_idxS < 8, 'idxS out of bounds for max 8 tokens');
86
+ require(_idxC < 8, 'idxC out of bounds for max 8 tokens');
87
+
88
+ idxS = _idxS;
89
+ idxC = _idxC;
90
+
91
+ stable = Stablecoin(_pool.coins(_idxS));
92
+ coin = IERC20Metadata(_pool.coins(_idxC));
93
+ }
94
+
95
+ // ---------------------------------------------------------------------------------------
96
+
97
+ function setDistribution(address[5] calldata _receivers, uint32[5] calldata _weights) external onlyCurator {
98
+ if (pendingValidAt != 0) revert ErrorsLib.AlreadyPending();
99
+
100
+ if (receivers[0] == address(0)) {
101
+ _setDistribution(_receivers, _weights);
102
+ } else {
103
+ pendingReceivers = _receivers;
104
+ pendingWeights = _weights;
105
+ pendingValidAt = block.timestamp + stable.timelock();
106
+ emit SubmitDistribution(_msgSender(), _receivers, _weights, pendingValidAt);
107
+ }
108
+ }
109
+
110
+ function revokePendingDistribution() external onlyCuratorOrGuardian {
111
+ if (pendingValidAt == 0) revert ErrorsLib.NoPendingValue();
112
+ emit RevokeDistribution(_msgSender());
113
+ _cleanUpPending();
114
+ }
115
+
116
+ function applyDistribution() external afterTimelock(pendingValidAt) {
117
+ _setDistribution(pendingReceivers, pendingWeights);
118
+ }
119
+
120
+ function _setDistribution(address[5] memory _receivers, uint32[5] memory _weights) internal {
121
+ // reset totalWeights
122
+ totalWeights = 0;
123
+
124
+ // update total weight
125
+ for (uint32 i = 0; i < _receivers.length; i++) {
126
+ totalWeights += _weights[i];
127
+ }
128
+
129
+ // update distribution
130
+ receivers = _receivers;
131
+ weights = _weights;
132
+
133
+ // emit event
134
+ emit SetDistribution(_msgSender());
135
+
136
+ _cleanUpPending();
137
+ }
138
+
139
+ function _cleanUpPending() internal {
140
+ delete pendingReceivers;
141
+ delete pendingWeights;
142
+ delete pendingValidAt;
143
+ }
144
+
145
+ // ---------------------------------------------------------------------------------------
146
+
147
+ function checkImbalance() public view returns (bool) {
148
+ uint256 correctedAmount = (pool.balances(idxC) * 1 ether) / 10 ** coin.decimals();
149
+ if (pool.balances(idxS) <= correctedAmount) {
150
+ return true;
151
+ } else {
152
+ return false;
153
+ }
154
+ }
155
+
156
+ function verifyImbalance(bool state) public view {
157
+ if (checkImbalance() != state) revert ImbalancedVariant(pool.get_balances());
158
+ }
159
+
160
+ // ---------------------------------------------------------------------------------------
161
+
162
+ function addLiquidity(uint256 amount, uint256 minShares) external returns (uint256) {
163
+ uint256 amountStable = (amount * 1 ether) / 10 ** coin.decimals();
164
+
165
+ // transfer coin token, needs approval
166
+ coin.safeTransferFrom(_msgSender(), address(this), amount);
167
+
168
+ // mint the same amountStable in stables
169
+ stable.mintModule(address(this), amountStable);
170
+ totalMinted += amountStable;
171
+
172
+ // approve tokens
173
+ stable.forceApprove(address(pool), amountStable);
174
+ coin.forceApprove(address(pool), amount);
175
+
176
+ // prepare amounts
177
+ uint256[] memory amounts = new uint256[](2);
178
+ amounts[idxS] = amountStable;
179
+ amounts[idxC] = amount;
180
+
181
+ // provide liquidity
182
+ uint256 shares = pool.add_liquidity(amounts, minShares * 2);
183
+
184
+ // verify imbalance for stable
185
+ verifyImbalance(true);
186
+
187
+ // return sender's split of shares
188
+ uint256 split = shares / 2;
189
+ pool.transfer(_msgSender(), split);
190
+
191
+ // emit event and return share split
192
+ emit AddLiquidity(_msgSender(), amountStable, totalMinted, split, pool.balanceOf(address(this)));
193
+ return split;
194
+ }
195
+
196
+ // ---------------------------------------------------------------------------------------
197
+
198
+ function calcProfitability(uint256 beforeLP, uint256 afterLP, uint256 split) public view returns (uint256) {
199
+ // @dev: if all debt is already paid, `calcBeforeSplit` will be 0 and therefore profit is the whole `split`
200
+ uint256 calcBeforeSplit = ((1 ether - ((afterLP * 1 ether) / beforeLP)) * totalMinted) / 1 ether;
201
+ if (split > calcBeforeSplit) {
202
+ return split - calcBeforeSplit;
203
+ } else {
204
+ return 0;
205
+ }
206
+ }
207
+
208
+ // ---------------------------------------------------------------------------------------
209
+
210
+ function removeLiquidity(uint256 shares, uint256 minAmount) external returns (uint256) {
211
+ // store LP balance
212
+ uint256 beforeLP = pool.balanceOf(address(this));
213
+
214
+ // transfer LP shares from sender, needs approval
215
+ pool.transferFrom(_msgSender(), address(this), shares);
216
+
217
+ // remove both shares and get split
218
+ uint256 split = pool.remove_liquidity_one_coin(shares * 2, int128(int256(idxS)), minAmount * 2) / 2;
219
+
220
+ // verify imbalance for coin
221
+ verifyImbalance(false);
222
+
223
+ // verify if in profit
224
+ uint256 afterLP = pool.balanceOf(address(this));
225
+ uint256 profit = calcProfitability(beforeLP, afterLP, split);
226
+ if (profit == 0) revert NotProfitable();
227
+ totalRevenue += profit;
228
+
229
+ // transfer split to sender
230
+ stable.transfer(_msgSender(), split);
231
+
232
+ // reconcile
233
+ uint256 burned = _reconcile();
234
+
235
+ // emit revenue with reduced totalMinted
236
+ emit Revenue(profit, totalRevenue, totalMinted);
237
+
238
+ // emit event and return share portion
239
+ emit RemoveLiquidity(_msgSender(), burned, totalMinted, shares, afterLP);
240
+ return split;
241
+ }
242
+
243
+ // ---------------------------------------------------------------------------------------
244
+ // redeem, onlyCurator
245
+
246
+ function redeem(uint256 shares, uint256 minAmount) external onlyCurator {
247
+ pool.remove_liquidity_one_coin(shares, int128(int256(idxS)), minAmount);
248
+ _reconcile();
249
+ }
250
+
251
+ // ---------------------------------------------------------------------------------------
252
+
253
+ function payOffDebt() external onlyCuratorOrGuardian {
254
+ _reconcile();
255
+ }
256
+
257
+ // ---------------------------------------------------------------------------------------
258
+
259
+ function _reconcile() internal returns (uint256) {
260
+ uint256 amount = stable.balanceOf(address(this));
261
+ if (totalMinted <= amount) {
262
+ // in profit or neutral
263
+ uint256 dist = amount - totalMinted;
264
+
265
+ if (totalMinted != 0) {
266
+ stable.burn(totalMinted);
267
+ totalMinted = 0;
268
+ }
269
+
270
+ // distribute
271
+ if (dist != 0) {
272
+ _distribute(dist);
273
+ }
274
+
275
+ return totalMinted;
276
+ } else {
277
+ // fallback, burn existing totalMinted if available,
278
+ // will leave with dust debt
279
+ stable.burn(amount);
280
+ totalMinted -= amount;
281
+ return amount;
282
+ }
283
+ }
284
+
285
+ function _distribute(uint256 amount) internal {
286
+ for (uint256 i = 0; i < 5; i++) {
287
+ address receiver = receivers[i];
288
+ uint256 weight = weights[i];
289
+ uint256 split;
290
+
291
+ // end distribution
292
+ if (receiver == address(0)) return;
293
+
294
+ // last item reached (index: 5 - 1 = 4) OR next receiver is zeroAddress
295
+ if (i == 4 || (i < 4 && receivers[i + 1] == address(0))) {
296
+ // distribute remainings, eliminating rounding or deposit issues
297
+ split = stable.balanceOf(address(this));
298
+ } else {
299
+ // distribute weighted split
300
+ split = (weight * amount) / totalWeights;
301
+ }
302
+
303
+ // distribute revenue split
304
+ stable.transfer(receiver, split);
305
+
306
+ // last weighted ratio might be inconsistant, due to remaining assets distribution
307
+ emit Distribution(receiver, split, (weight * 1 ether) / totalWeights);
308
+ }
309
+ }
310
+ }