@usdu-finance/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.
- package/.claude/settings.local.json +12 -0
- package/.env.example +6 -0
- package/.prettierrc.json +7 -0
- package/LICENSE +674 -0
- package/README.md +244 -0
- package/contracts/curve/CurveAdapterV1.sol +310 -0
- package/contracts/curve/CurveAdapterV1_1.sol +198 -0
- package/contracts/curve/helpers/ICurveStableSwapNG.json +566 -0
- package/contracts/curve/helpers/ICurveStableSwapNG.sol +177 -0
- package/contracts/deploy/VaultDeployer.sol +110 -0
- package/contracts/morpho/MorphoAdapterV1.sol +255 -0
- package/contracts/morpho/MorphoAdapterV1_1.sol +137 -0
- package/contracts/morpho/MorphoAdapterV1_2.sol +126 -0
- package/contracts/morpho/helpers/AggregatorV3Interface.sol +24 -0
- package/contracts/morpho/helpers/ChainlinkDataFeedLib.sol +36 -0
- package/contracts/morpho/helpers/ConstantsLib.sol +20 -0
- package/contracts/morpho/helpers/ErrorsLib.sol +17 -0
- package/contracts/morpho/helpers/IERC4626.sol +6 -0
- package/contracts/morpho/helpers/IMetaMorphoV1_1.sol +229 -0
- package/contracts/morpho/helpers/IMetaMorphoV1_1Factory.sol +32 -0
- package/contracts/morpho/helpers/IMorpho.sol +361 -0
- package/contracts/morpho/helpers/IMorphoCallbacks.sol +52 -0
- package/contracts/morpho/helpers/IMorphoChainlinkOracleV2.sol +39 -0
- package/contracts/morpho/helpers/IMorphoChainlinkOracleV2Factory.sol +56 -0
- package/contracts/morpho/helpers/IOracle.sol +15 -0
- package/contracts/morpho/helpers/MarketParamsLib.sol +21 -0
- package/contracts/morpho/helpers/MathLib.sol +45 -0
- package/contracts/morpho/helpers/Morpho.sol.bak +517 -0
- package/contracts/morpho/helpers/MorphoChainlinkOracleV2.sol +157 -0
- package/contracts/morpho/helpers/PendingLib.sol +47 -0
- package/contracts/morpho/helpers/SharesMathLib.sol +45 -0
- package/contracts/morpho/helpers/VaultLib.sol +18 -0
- package/contracts/reward/RewardDistributionV1.sol +110 -0
- package/contracts/reward/RewardRouterV0.sol +63 -0
- package/contracts/reward/Rewards.example.json +72 -0
- package/contracts/stablecoin/IStablecoin.sol +211 -0
- package/contracts/stablecoin/IStablecoinMetadata.sol +27 -0
- package/contracts/stablecoin/IStablecoinModifier.sol +52 -0
- package/contracts/stablecoin/Stablecoin.sol +376 -0
- package/contracts/stablecoin/libraries/ConstantsLib.sol +13 -0
- package/contracts/stablecoin/libraries/ErrorsLib.sol +45 -0
- package/contracts/stablecoin/libraries/EventsLib.sol +74 -0
- package/contracts/stablecoin/libraries/PendingLib.sol +38 -0
- package/contracts/vault/VaultAdapterRecoverV1.sol +29 -0
- package/contracts/vault/VaultAdapterV1.sol +126 -0
- package/dist/index.d.mts +16154 -0
- package/dist/index.d.ts +16154 -0
- package/dist/index.js +21134 -0
- package/dist/index.mjs +21061 -0
- package/docs/CoreVault: Integration of new Markets.md +197 -0
- package/docs/CoreVault: SupplyQueue.md +11 -0
- package/docs/Markets USDC Vault.md +35 -0
- package/docs/Markets USDU Vault.md +89 -0
- package/docs/Markets WETH Vault.md +35 -0
- package/docs/Overview.drawio +117 -0
- package/exports/abis/curve/CurveAdapterV1.ts +599 -0
- package/exports/abis/curve/CurveAdapterV1_1.ts +609 -0
- package/exports/abis/curve/CurveAdapterV1_2.ts +721 -0
- package/exports/abis/curve/helper/ICurveStableSwapNG.ts +1589 -0
- package/exports/abis/morpho/MorphoAdapterV1.ts +516 -0
- package/exports/abis/morpho/MorphoAdapterV1_1.ts +489 -0
- package/exports/abis/morpho/MorphoAdapterV1_2.ts +459 -0
- package/exports/abis/morpho/helper/AggregatorV3Interface.ts +113 -0
- package/exports/abis/morpho/helper/IMetaMorphoV1_1.ts +1483 -0
- package/exports/abis/morpho/helper/IMetaMorphoV1_1Base.ts +607 -0
- package/exports/abis/morpho/helper/IMetaMorphoV1_1StaticTyping.ts +696 -0
- package/exports/abis/morpho/helper/IMorpho.ts +1024 -0
- package/exports/abis/morpho/helper/IMorphoBase.ts +886 -0
- package/exports/abis/morpho/helper/IMorphoChainlinkOracleV2.ts +132 -0
- package/exports/abis/morpho/helper/IMorphoChainlinkOracleV2Factory.ts +109 -0
- package/exports/abis/morpho/helper/IMorphoFlashLoanCallback.ts +20 -0
- package/exports/abis/morpho/helper/IMorphoLiquidateCallback.ts +20 -0
- package/exports/abis/morpho/helper/IMorphoRepayCallback.ts +20 -0
- package/exports/abis/morpho/helper/IMorphoStaticTyping.ts +1003 -0
- package/exports/abis/morpho/helper/IMorphoSupplyCallback.ts +20 -0
- package/exports/abis/morpho/helper/IMorphoSupplyCollateralCallback.ts +20 -0
- package/exports/abis/morpho/helper/IMulticall.ts +21 -0
- package/exports/abis/morpho/helper/IOracle.ts +15 -0
- package/exports/abis/morpho/helper/IOwnable.ts +55 -0
- package/exports/abis/morpho/helper/MorphoChainlinkOracleV2.ts +188 -0
- package/exports/abis/openzeppelin/ERC20.ts +310 -0
- package/exports/abis/openzeppelin/ERC20Permit.ts +520 -0
- package/exports/abis/openzeppelin/IERC20.ts +185 -0
- package/exports/abis/openzeppelin/IERC20Metadata.ts +224 -0
- package/exports/abis/openzeppelin/IERC20Permit.ts +77 -0
- package/exports/abis/openzeppelin/IERC4626.ts +614 -0
- package/exports/abis/reward/RewardDistributionV1.ts +246 -0
- package/exports/abis/stablecoin/ErrorsLib.ts +114 -0
- package/exports/abis/stablecoin/EventsLib.ts +372 -0
- package/exports/abis/stablecoin/IStablecoin.ts +642 -0
- package/exports/abis/stablecoin/IStablecoinMetadata.ts +856 -0
- package/exports/abis/stablecoin/IStablecoinModifier.ts +15 -0
- package/exports/abis/stablecoin/Stablecoin.ts +1922 -0
- package/exports/abis/termmax/ITermMaxVault.ts +2335 -0
- package/exports/abis/vault/VaultAdapterRecoverV1.ts +490 -0
- package/exports/abis/vault/VaultAdapterV1.ts +459 -0
- package/exports/address.config.ts +113 -0
- package/exports/address.types.ts +130 -0
- package/exports/index.ts +61 -0
- package/hardhat.config.ts +231 -0
- package/helper/store.args.ts +17 -0
- package/helper/wallet.info.ts +3 -0
- package/helper/wallet.ts +41 -0
- package/install-macos.sh +46 -0
- package/package.json +73 -0
- package/tsconfig.json +15 -0
- 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
|
+
}
|