agentic-team-templates 0.3.0
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/README.md +280 -0
- package/bin/cli.js +5 -0
- package/package.json +47 -0
- package/src/index.js +521 -0
- package/templates/_shared/code-quality.md +162 -0
- package/templates/_shared/communication.md +114 -0
- package/templates/_shared/core-principles.md +62 -0
- package/templates/_shared/git-workflow.md +165 -0
- package/templates/_shared/security-fundamentals.md +173 -0
- package/templates/blockchain/.cursorrules/defi-patterns.md +520 -0
- package/templates/blockchain/.cursorrules/gas-optimization.md +339 -0
- package/templates/blockchain/.cursorrules/overview.md +130 -0
- package/templates/blockchain/.cursorrules/security.md +318 -0
- package/templates/blockchain/.cursorrules/smart-contracts.md +364 -0
- package/templates/blockchain/.cursorrules/testing.md +415 -0
- package/templates/blockchain/.cursorrules/web3-integration.md +538 -0
- package/templates/blockchain/CLAUDE.md +389 -0
- package/templates/cli-tools/.cursorrules/architecture.md +412 -0
- package/templates/cli-tools/.cursorrules/arguments.md +406 -0
- package/templates/cli-tools/.cursorrules/distribution.md +546 -0
- package/templates/cli-tools/.cursorrules/error-handling.md +455 -0
- package/templates/cli-tools/.cursorrules/overview.md +136 -0
- package/templates/cli-tools/.cursorrules/testing.md +537 -0
- package/templates/cli-tools/.cursorrules/user-experience.md +545 -0
- package/templates/cli-tools/CLAUDE.md +356 -0
- package/templates/data-engineering/.cursorrules/data-modeling.md +367 -0
- package/templates/data-engineering/.cursorrules/data-quality.md +455 -0
- package/templates/data-engineering/.cursorrules/overview.md +85 -0
- package/templates/data-engineering/.cursorrules/performance.md +339 -0
- package/templates/data-engineering/.cursorrules/pipeline-design.md +280 -0
- package/templates/data-engineering/.cursorrules/security.md +460 -0
- package/templates/data-engineering/.cursorrules/testing.md +452 -0
- package/templates/data-engineering/CLAUDE.md +974 -0
- package/templates/devops-sre/.cursorrules/capacity-planning.md +653 -0
- package/templates/devops-sre/.cursorrules/change-management.md +584 -0
- package/templates/devops-sre/.cursorrules/chaos-engineering.md +651 -0
- package/templates/devops-sre/.cursorrules/disaster-recovery.md +641 -0
- package/templates/devops-sre/.cursorrules/incident-management.md +565 -0
- package/templates/devops-sre/.cursorrules/observability.md +714 -0
- package/templates/devops-sre/.cursorrules/overview.md +230 -0
- package/templates/devops-sre/.cursorrules/postmortems.md +588 -0
- package/templates/devops-sre/.cursorrules/runbooks.md +760 -0
- package/templates/devops-sre/.cursorrules/slo-sli.md +617 -0
- package/templates/devops-sre/.cursorrules/toil-reduction.md +567 -0
- package/templates/devops-sre/CLAUDE.md +1007 -0
- package/templates/documentation/.cursorrules/adr.md +277 -0
- package/templates/documentation/.cursorrules/api-documentation.md +411 -0
- package/templates/documentation/.cursorrules/code-comments.md +253 -0
- package/templates/documentation/.cursorrules/maintenance.md +260 -0
- package/templates/documentation/.cursorrules/overview.md +82 -0
- package/templates/documentation/.cursorrules/readme-standards.md +306 -0
- package/templates/documentation/CLAUDE.md +120 -0
- package/templates/fullstack/.cursorrules/api-contracts.md +331 -0
- package/templates/fullstack/.cursorrules/architecture.md +298 -0
- package/templates/fullstack/.cursorrules/overview.md +109 -0
- package/templates/fullstack/.cursorrules/shared-types.md +348 -0
- package/templates/fullstack/.cursorrules/testing.md +386 -0
- package/templates/fullstack/CLAUDE.md +349 -0
- package/templates/ml-ai/.cursorrules/data-engineering.md +483 -0
- package/templates/ml-ai/.cursorrules/deployment.md +601 -0
- package/templates/ml-ai/.cursorrules/model-development.md +538 -0
- package/templates/ml-ai/.cursorrules/monitoring.md +658 -0
- package/templates/ml-ai/.cursorrules/overview.md +131 -0
- package/templates/ml-ai/.cursorrules/security.md +637 -0
- package/templates/ml-ai/.cursorrules/testing.md +678 -0
- package/templates/ml-ai/CLAUDE.md +1136 -0
- package/templates/mobile/.cursorrules/navigation.md +246 -0
- package/templates/mobile/.cursorrules/offline-first.md +302 -0
- package/templates/mobile/.cursorrules/overview.md +71 -0
- package/templates/mobile/.cursorrules/performance.md +345 -0
- package/templates/mobile/.cursorrules/testing.md +339 -0
- package/templates/mobile/CLAUDE.md +233 -0
- package/templates/platform-engineering/.cursorrules/ci-cd.md +778 -0
- package/templates/platform-engineering/.cursorrules/developer-experience.md +632 -0
- package/templates/platform-engineering/.cursorrules/infrastructure-as-code.md +600 -0
- package/templates/platform-engineering/.cursorrules/kubernetes.md +710 -0
- package/templates/platform-engineering/.cursorrules/observability.md +747 -0
- package/templates/platform-engineering/.cursorrules/overview.md +215 -0
- package/templates/platform-engineering/.cursorrules/security.md +855 -0
- package/templates/platform-engineering/.cursorrules/testing.md +878 -0
- package/templates/platform-engineering/CLAUDE.md +850 -0
- package/templates/utility-agent/.cursorrules/action-control.md +284 -0
- package/templates/utility-agent/.cursorrules/context-management.md +186 -0
- package/templates/utility-agent/.cursorrules/hallucination-prevention.md +253 -0
- package/templates/utility-agent/.cursorrules/overview.md +78 -0
- package/templates/utility-agent/.cursorrules/token-optimization.md +369 -0
- package/templates/utility-agent/CLAUDE.md +513 -0
- package/templates/web-backend/.cursorrules/api-design.md +255 -0
- package/templates/web-backend/.cursorrules/authentication.md +309 -0
- package/templates/web-backend/.cursorrules/database-patterns.md +298 -0
- package/templates/web-backend/.cursorrules/error-handling.md +366 -0
- package/templates/web-backend/.cursorrules/overview.md +69 -0
- package/templates/web-backend/.cursorrules/security.md +358 -0
- package/templates/web-backend/.cursorrules/testing.md +395 -0
- package/templates/web-backend/CLAUDE.md +366 -0
- package/templates/web-frontend/.cursorrules/accessibility.md +296 -0
- package/templates/web-frontend/.cursorrules/component-patterns.md +204 -0
- package/templates/web-frontend/.cursorrules/overview.md +72 -0
- package/templates/web-frontend/.cursorrules/performance.md +325 -0
- package/templates/web-frontend/.cursorrules/state-management.md +227 -0
- package/templates/web-frontend/.cursorrules/styling.md +271 -0
- package/templates/web-frontend/.cursorrules/testing.md +311 -0
- package/templates/web-frontend/CLAUDE.md +399 -0
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
# DeFi Protocol Patterns
|
|
2
|
+
|
|
3
|
+
Advanced patterns for building DeFi protocols: AMMs, lending, yield strategies, and MEV protection.
|
|
4
|
+
|
|
5
|
+
## Token Standards
|
|
6
|
+
|
|
7
|
+
### ERC-20 (Fungible Tokens)
|
|
8
|
+
|
|
9
|
+
```solidity
|
|
10
|
+
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
|
11
|
+
|
|
12
|
+
contract MyToken is ERC20 {
|
|
13
|
+
constructor() ERC20("My Token", "MTK") {
|
|
14
|
+
_mint(msg.sender, 1_000_000e18);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// With extensions
|
|
19
|
+
import {ERC20Burnable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
|
|
20
|
+
import {ERC20Pausable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol";
|
|
21
|
+
import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
|
|
22
|
+
|
|
23
|
+
contract AdvancedToken is ERC20, ERC20Burnable, ERC20Pausable, ERC20Permit {
|
|
24
|
+
// Permit allows gasless approvals via signatures
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### ERC-721 (NFTs)
|
|
29
|
+
|
|
30
|
+
```solidity
|
|
31
|
+
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
|
|
32
|
+
import {ERC721URIStorage} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
|
|
33
|
+
|
|
34
|
+
contract MyNFT is ERC721, ERC721URIStorage {
|
|
35
|
+
uint256 private s_tokenIdCounter;
|
|
36
|
+
|
|
37
|
+
constructor() ERC721("My NFT", "MNFT") {}
|
|
38
|
+
|
|
39
|
+
function mint(address to, string memory uri) external {
|
|
40
|
+
uint256 tokenId = s_tokenIdCounter++;
|
|
41
|
+
_safeMint(to, tokenId);
|
|
42
|
+
_setTokenURI(tokenId, uri);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### ERC-1155 (Multi-Token)
|
|
48
|
+
|
|
49
|
+
```solidity
|
|
50
|
+
import {ERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
|
|
51
|
+
|
|
52
|
+
contract GameItems is ERC1155 {
|
|
53
|
+
uint256 public constant GOLD = 0;
|
|
54
|
+
uint256 public constant SILVER = 1;
|
|
55
|
+
uint256 public constant SWORD = 2;
|
|
56
|
+
uint256 public constant SHIELD = 3;
|
|
57
|
+
|
|
58
|
+
constructor() ERC1155("https://game.example/api/item/{id}.json") {
|
|
59
|
+
_mint(msg.sender, GOLD, 10**18, "");
|
|
60
|
+
_mint(msg.sender, SILVER, 10**27, "");
|
|
61
|
+
_mint(msg.sender, SWORD, 100, "");
|
|
62
|
+
_mint(msg.sender, SHIELD, 50, "");
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### ERC-4626 (Tokenized Vaults)
|
|
68
|
+
|
|
69
|
+
The standard for yield-bearing tokens:
|
|
70
|
+
|
|
71
|
+
```solidity
|
|
72
|
+
import {ERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol";
|
|
73
|
+
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
|
74
|
+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
75
|
+
|
|
76
|
+
contract YieldVault is ERC4626 {
|
|
77
|
+
constructor(IERC20 asset_)
|
|
78
|
+
ERC4626(asset_)
|
|
79
|
+
ERC20("Yield Vault Token", "yVLT")
|
|
80
|
+
{}
|
|
81
|
+
|
|
82
|
+
// Override to implement yield strategy
|
|
83
|
+
function totalAssets() public view override returns (uint256) {
|
|
84
|
+
return IERC20(asset()).balanceOf(address(this)) + _accruedYield();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function _accruedYield() internal view returns (uint256) {
|
|
88
|
+
// Calculate yield from strategy
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## AMM Patterns
|
|
94
|
+
|
|
95
|
+
### Constant Product Market Maker (x * y = k)
|
|
96
|
+
|
|
97
|
+
```solidity
|
|
98
|
+
contract SimpleAMM {
|
|
99
|
+
IERC20 public immutable token0;
|
|
100
|
+
IERC20 public immutable token1;
|
|
101
|
+
|
|
102
|
+
uint256 public reserve0;
|
|
103
|
+
uint256 public reserve1;
|
|
104
|
+
|
|
105
|
+
uint256 private constant FEE_BPS = 30; // 0.3%
|
|
106
|
+
uint256 private constant BPS = 10_000;
|
|
107
|
+
|
|
108
|
+
event Swap(
|
|
109
|
+
address indexed sender,
|
|
110
|
+
uint256 amountIn,
|
|
111
|
+
uint256 amountOut,
|
|
112
|
+
bool zeroForOne
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
function swap(uint256 amountIn, bool zeroForOne, uint256 minAmountOut)
|
|
116
|
+
external
|
|
117
|
+
returns (uint256 amountOut)
|
|
118
|
+
{
|
|
119
|
+
(IERC20 tokenIn, IERC20 tokenOut, uint256 reserveIn, uint256 reserveOut) =
|
|
120
|
+
zeroForOne
|
|
121
|
+
? (token0, token1, reserve0, reserve1)
|
|
122
|
+
: (token1, token0, reserve1, reserve0);
|
|
123
|
+
|
|
124
|
+
// Transfer in
|
|
125
|
+
tokenIn.transferFrom(msg.sender, address(this), amountIn);
|
|
126
|
+
|
|
127
|
+
// Calculate output: dy = (dx * y * (1 - fee)) / (x + dx * (1 - fee))
|
|
128
|
+
uint256 amountInWithFee = amountIn * (BPS - FEE_BPS);
|
|
129
|
+
amountOut = (amountInWithFee * reserveOut) / (reserveIn * BPS + amountInWithFee);
|
|
130
|
+
|
|
131
|
+
// Slippage protection
|
|
132
|
+
if (amountOut < minAmountOut) revert SlippageExceeded();
|
|
133
|
+
|
|
134
|
+
// Update reserves
|
|
135
|
+
if (zeroForOne) {
|
|
136
|
+
reserve0 += amountIn;
|
|
137
|
+
reserve1 -= amountOut;
|
|
138
|
+
} else {
|
|
139
|
+
reserve1 += amountIn;
|
|
140
|
+
reserve0 -= amountOut;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Transfer out
|
|
144
|
+
tokenOut.transfer(msg.sender, amountOut);
|
|
145
|
+
|
|
146
|
+
emit Swap(msg.sender, amountIn, amountOut, zeroForOne);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function addLiquidity(uint256 amount0, uint256 amount1) external returns (uint256 liquidity) {
|
|
150
|
+
// Implementation...
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Price Calculation
|
|
156
|
+
|
|
157
|
+
```solidity
|
|
158
|
+
// Get current spot price
|
|
159
|
+
function getSpotPrice() public view returns (uint256) {
|
|
160
|
+
return (reserve1 * 1e18) / reserve0;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Get output amount for input
|
|
164
|
+
function getAmountOut(uint256 amountIn, bool zeroForOne) public view returns (uint256) {
|
|
165
|
+
(uint256 reserveIn, uint256 reserveOut) = zeroForOne
|
|
166
|
+
? (reserve0, reserve1)
|
|
167
|
+
: (reserve1, reserve0);
|
|
168
|
+
|
|
169
|
+
uint256 amountInWithFee = amountIn * (BPS - FEE_BPS);
|
|
170
|
+
return (amountInWithFee * reserveOut) / (reserveIn * BPS + amountInWithFee);
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Flash Loans (ERC-3156)
|
|
175
|
+
|
|
176
|
+
### Flash Loan Lender
|
|
177
|
+
|
|
178
|
+
```solidity
|
|
179
|
+
import {IERC3156FlashLender} from "@openzeppelin/contracts/interfaces/IERC3156FlashLender.sol";
|
|
180
|
+
import {IERC3156FlashBorrower} from "@openzeppelin/contracts/interfaces/IERC3156FlashBorrower.sol";
|
|
181
|
+
|
|
182
|
+
contract FlashLender is IERC3156FlashLender {
|
|
183
|
+
bytes32 private constant CALLBACK_SUCCESS = keccak256("ERC3156FlashBorrower.onFlashLoan");
|
|
184
|
+
|
|
185
|
+
uint256 public constant FEE_BPS = 9; // 0.09%
|
|
186
|
+
|
|
187
|
+
mapping(address => bool) public supportedTokens;
|
|
188
|
+
|
|
189
|
+
function flashLoan(
|
|
190
|
+
IERC3156FlashBorrower receiver,
|
|
191
|
+
address token,
|
|
192
|
+
uint256 amount,
|
|
193
|
+
bytes calldata data
|
|
194
|
+
) external returns (bool) {
|
|
195
|
+
if (!supportedTokens[token]) revert UnsupportedToken();
|
|
196
|
+
|
|
197
|
+
uint256 fee = flashFee(token, amount);
|
|
198
|
+
uint256 balanceBefore = IERC20(token).balanceOf(address(this));
|
|
199
|
+
|
|
200
|
+
// Lend
|
|
201
|
+
IERC20(token).transfer(address(receiver), amount);
|
|
202
|
+
|
|
203
|
+
// Callback
|
|
204
|
+
if (receiver.onFlashLoan(msg.sender, token, amount, fee, data) != CALLBACK_SUCCESS) {
|
|
205
|
+
revert CallbackFailed();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Verify repayment
|
|
209
|
+
uint256 balanceAfter = IERC20(token).balanceOf(address(this));
|
|
210
|
+
if (balanceAfter < balanceBefore + fee) {
|
|
211
|
+
revert RepaymentFailed();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function flashFee(address token, uint256 amount) public view returns (uint256) {
|
|
218
|
+
return (amount * FEE_BPS) / 10_000;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function maxFlashLoan(address token) public view returns (uint256) {
|
|
222
|
+
return supportedTokens[token] ? IERC20(token).balanceOf(address(this)) : 0;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Flash Loan Borrower
|
|
228
|
+
|
|
229
|
+
```solidity
|
|
230
|
+
contract FlashBorrower is IERC3156FlashBorrower {
|
|
231
|
+
IERC3156FlashLender public lender;
|
|
232
|
+
|
|
233
|
+
function onFlashLoan(
|
|
234
|
+
address initiator,
|
|
235
|
+
address token,
|
|
236
|
+
uint256 amount,
|
|
237
|
+
uint256 fee,
|
|
238
|
+
bytes calldata data
|
|
239
|
+
) external returns (bytes32) {
|
|
240
|
+
require(msg.sender == address(lender), "Untrusted lender");
|
|
241
|
+
require(initiator == address(this), "Untrusted initiator");
|
|
242
|
+
|
|
243
|
+
// Do something with the flash loan
|
|
244
|
+
// e.g., arbitrage, liquidation, collateral swap
|
|
245
|
+
|
|
246
|
+
// Repay
|
|
247
|
+
IERC20(token).approve(address(lender), amount + fee);
|
|
248
|
+
|
|
249
|
+
return keccak256("ERC3156FlashBorrower.onFlashLoan");
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function executeFlashLoan(address token, uint256 amount) external {
|
|
253
|
+
lender.flashLoan(this, token, amount, "");
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## MEV Protection
|
|
259
|
+
|
|
260
|
+
### Commit-Reveal Pattern
|
|
261
|
+
|
|
262
|
+
```solidity
|
|
263
|
+
contract CommitRevealAuction {
|
|
264
|
+
struct Bid {
|
|
265
|
+
bytes32 commitment;
|
|
266
|
+
uint256 revealedAmount;
|
|
267
|
+
bool revealed;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
mapping(address => Bid) public bids;
|
|
271
|
+
uint256 public commitDeadline;
|
|
272
|
+
uint256 public revealDeadline;
|
|
273
|
+
|
|
274
|
+
// Phase 1: Commit (hidden bid)
|
|
275
|
+
function commit(bytes32 commitment) external {
|
|
276
|
+
require(block.timestamp < commitDeadline, "Commit phase ended");
|
|
277
|
+
bids[msg.sender].commitment = commitment;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Phase 2: Reveal
|
|
281
|
+
function reveal(uint256 amount, bytes32 salt) external {
|
|
282
|
+
require(block.timestamp >= commitDeadline, "Commit phase not ended");
|
|
283
|
+
require(block.timestamp < revealDeadline, "Reveal phase ended");
|
|
284
|
+
|
|
285
|
+
Bid storage bid = bids[msg.sender];
|
|
286
|
+
bytes32 expectedCommitment = keccak256(abi.encode(msg.sender, amount, salt));
|
|
287
|
+
|
|
288
|
+
require(bid.commitment == expectedCommitment, "Invalid reveal");
|
|
289
|
+
|
|
290
|
+
bid.revealedAmount = amount;
|
|
291
|
+
bid.revealed = true;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Slippage Protection
|
|
297
|
+
|
|
298
|
+
```solidity
|
|
299
|
+
function swap(
|
|
300
|
+
address tokenIn,
|
|
301
|
+
address tokenOut,
|
|
302
|
+
uint256 amountIn,
|
|
303
|
+
uint256 minAmountOut, // User-specified minimum
|
|
304
|
+
uint256 deadline // Transaction expiry
|
|
305
|
+
) external {
|
|
306
|
+
// Deadline check - prevents stale transactions
|
|
307
|
+
if (block.timestamp > deadline) revert TransactionExpired();
|
|
308
|
+
|
|
309
|
+
uint256 amountOut = _calculateAmountOut(tokenIn, tokenOut, amountIn);
|
|
310
|
+
|
|
311
|
+
// Slippage check - prevents sandwich attacks
|
|
312
|
+
if (amountOut < minAmountOut) revert SlippageExceeded(amountOut, minAmountOut);
|
|
313
|
+
|
|
314
|
+
_executeSwap(tokenIn, tokenOut, amountIn, amountOut);
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### Private Mempools
|
|
319
|
+
|
|
320
|
+
For sensitive transactions, use private transaction services:
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
// Frontend: Submit to Flashbots Protect
|
|
324
|
+
const flashbotsProvider = new ethers.providers.JsonRpcProvider(
|
|
325
|
+
'https://rpc.flashbots.net'
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
// Transaction won't be visible in public mempool
|
|
329
|
+
const tx = await flashbotsProvider.sendTransaction(signedTx);
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
## Oracle Integration
|
|
333
|
+
|
|
334
|
+
### Chainlink Price Feeds
|
|
335
|
+
|
|
336
|
+
```solidity
|
|
337
|
+
import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
|
|
338
|
+
|
|
339
|
+
contract PriceConsumer {
|
|
340
|
+
AggregatorV3Interface internal priceFeed;
|
|
341
|
+
|
|
342
|
+
constructor(address feedAddress) {
|
|
343
|
+
priceFeed = AggregatorV3Interface(feedAddress);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function getLatestPrice() public view returns (int256 price, uint256 timestamp) {
|
|
347
|
+
(
|
|
348
|
+
/* uint80 roundID */,
|
|
349
|
+
price,
|
|
350
|
+
/* uint256 startedAt */,
|
|
351
|
+
timestamp,
|
|
352
|
+
/* uint80 answeredInRound */
|
|
353
|
+
) = priceFeed.latestRoundData();
|
|
354
|
+
|
|
355
|
+
// Staleness check
|
|
356
|
+
if (block.timestamp - timestamp > 1 hours) {
|
|
357
|
+
revert StalePrice();
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Sanity check
|
|
361
|
+
if (price <= 0) {
|
|
362
|
+
revert InvalidPrice();
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return (price, timestamp);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### TWAP (Time-Weighted Average Price)
|
|
371
|
+
|
|
372
|
+
```solidity
|
|
373
|
+
// Uniswap V3 TWAP
|
|
374
|
+
import {OracleLibrary} from "@uniswap/v3-periphery/contracts/libraries/OracleLibrary.sol";
|
|
375
|
+
|
|
376
|
+
function getTwapPrice(address pool, uint32 twapInterval) public view returns (uint256) {
|
|
377
|
+
(int24 arithmeticMeanTick,) = OracleLibrary.consult(pool, twapInterval);
|
|
378
|
+
|
|
379
|
+
uint160 sqrtPriceX96 = TickMath.getSqrtRatioAtTick(arithmeticMeanTick);
|
|
380
|
+
|
|
381
|
+
// Convert to price
|
|
382
|
+
return uint256(sqrtPriceX96) ** 2 / (2 ** 192);
|
|
383
|
+
}
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
## Yield Strategies
|
|
387
|
+
|
|
388
|
+
### Basic Yield Aggregator
|
|
389
|
+
|
|
390
|
+
```solidity
|
|
391
|
+
contract YieldAggregator is ERC4626 {
|
|
392
|
+
struct Strategy {
|
|
393
|
+
address target;
|
|
394
|
+
uint256 allocation; // In BPS
|
|
395
|
+
bool active;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
Strategy[] public strategies;
|
|
399
|
+
uint256 public constant TOTAL_BPS = 10_000;
|
|
400
|
+
|
|
401
|
+
function harvest() external {
|
|
402
|
+
uint256 totalYield;
|
|
403
|
+
|
|
404
|
+
for (uint256 i = 0; i < strategies.length; i++) {
|
|
405
|
+
if (strategies[i].active) {
|
|
406
|
+
uint256 yield = IStrategy(strategies[i].target).harvest();
|
|
407
|
+
totalYield += yield;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Compound or distribute
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function rebalance() external {
|
|
415
|
+
uint256 totalAssets_ = totalAssets();
|
|
416
|
+
|
|
417
|
+
for (uint256 i = 0; i < strategies.length; i++) {
|
|
418
|
+
Strategy memory strat = strategies[i];
|
|
419
|
+
if (!strat.active) continue;
|
|
420
|
+
|
|
421
|
+
uint256 targetAllocation = (totalAssets_ * strat.allocation) / TOTAL_BPS;
|
|
422
|
+
uint256 currentAllocation = IStrategy(strat.target).balance();
|
|
423
|
+
|
|
424
|
+
if (currentAllocation < targetAllocation) {
|
|
425
|
+
// Deposit more
|
|
426
|
+
uint256 delta = targetAllocation - currentAllocation;
|
|
427
|
+
IStrategy(strat.target).deposit(delta);
|
|
428
|
+
} else if (currentAllocation > targetAllocation) {
|
|
429
|
+
// Withdraw excess
|
|
430
|
+
uint256 delta = currentAllocation - targetAllocation;
|
|
431
|
+
IStrategy(strat.target).withdraw(delta);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
## Liquidation Patterns
|
|
439
|
+
|
|
440
|
+
```solidity
|
|
441
|
+
contract LendingProtocol {
|
|
442
|
+
struct Position {
|
|
443
|
+
uint256 collateral;
|
|
444
|
+
uint256 debt;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
uint256 public constant LIQUIDATION_THRESHOLD = 8000; // 80%
|
|
448
|
+
uint256 public constant LIQUIDATION_BONUS = 500; // 5%
|
|
449
|
+
|
|
450
|
+
function isLiquidatable(address user) public view returns (bool) {
|
|
451
|
+
Position memory pos = positions[user];
|
|
452
|
+
uint256 collateralValue = getCollateralValue(pos.collateral);
|
|
453
|
+
uint256 debtValue = getDebtValue(pos.debt);
|
|
454
|
+
|
|
455
|
+
// Health factor = collateral * threshold / debt
|
|
456
|
+
return (collateralValue * LIQUIDATION_THRESHOLD / 10_000) < debtValue;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function liquidate(address user, uint256 debtToCover) external {
|
|
460
|
+
require(isLiquidatable(user), "Not liquidatable");
|
|
461
|
+
|
|
462
|
+
Position storage pos = positions[user];
|
|
463
|
+
|
|
464
|
+
// Calculate collateral to seize (debt + bonus)
|
|
465
|
+
uint256 collateralToSeize = (debtToCover * (10_000 + LIQUIDATION_BONUS)) / 10_000;
|
|
466
|
+
collateralToSeize = collateralToSeize * debtPrice / collateralPrice;
|
|
467
|
+
|
|
468
|
+
// Cap at user's collateral
|
|
469
|
+
if (collateralToSeize > pos.collateral) {
|
|
470
|
+
collateralToSeize = pos.collateral;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Update position
|
|
474
|
+
pos.debt -= debtToCover;
|
|
475
|
+
pos.collateral -= collateralToSeize;
|
|
476
|
+
|
|
477
|
+
// Transfer
|
|
478
|
+
debtToken.transferFrom(msg.sender, address(this), debtToCover);
|
|
479
|
+
collateralToken.transfer(msg.sender, collateralToSeize);
|
|
480
|
+
|
|
481
|
+
emit Liquidation(user, msg.sender, debtToCover, collateralToSeize);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
## Anti-Patterns to Avoid
|
|
487
|
+
|
|
488
|
+
### 1. On-Chain Price Manipulation
|
|
489
|
+
|
|
490
|
+
```solidity
|
|
491
|
+
// Bad: Using spot price from AMM
|
|
492
|
+
uint256 price = amm.getSpotPrice(); // Can be manipulated in same tx
|
|
493
|
+
|
|
494
|
+
// Good: Use TWAP or Chainlink
|
|
495
|
+
uint256 price = oracle.getTwapPrice(1 hours);
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
### 2. Unprotected External Calls
|
|
499
|
+
|
|
500
|
+
```solidity
|
|
501
|
+
// Bad: No validation of external protocol
|
|
502
|
+
IExternalProtocol(untrustedAddress).deposit(amount);
|
|
503
|
+
|
|
504
|
+
// Good: Whitelist and validate
|
|
505
|
+
require(trustedProtocols[protocol], "Untrusted protocol");
|
|
506
|
+
IExternalProtocol(protocol).deposit(amount);
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### 3. Lack of Emergency Controls
|
|
510
|
+
|
|
511
|
+
```solidity
|
|
512
|
+
// Good: Emergency withdraw capability
|
|
513
|
+
function emergencyWithdraw() external onlyOwner {
|
|
514
|
+
_pause();
|
|
515
|
+
// Withdraw all funds from strategies
|
|
516
|
+
for (uint256 i = 0; i < strategies.length; i++) {
|
|
517
|
+
IStrategy(strategies[i]).withdrawAll();
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
```
|