# DeFi 課程 (Week2)
## Agenda
1. DeFi 簡介
2. Orderbook & AMM
3. Uniswap 介紹,解析 Uniswap 合約
4. 實作 AMM
5. Aave (Lending) 簡介
6. 補充
- 其他 AMM 公式
- Uniswap X
- Solidty Example
7. 作業說明
## DeFi 簡介
Lido, MakerDAO, Uniswap, Curve, Aave, Compound, Balancer...
[DefiLlama](https://defillama.com/)
## Orderbook & AMM
Orderbook 訂單簿交易
優點:效率高、無滑點
缺點:需造市商、操作較複雜
AMM 自動做市商
優點:操作容易、馬上成交
缺點:流動性低、高滑點
### Uniswap (X * Y = K)
```
example:
x: ETH
y: USDT
pool 有 10 個 ETH and 20,000 個 USDT, k = 200,000
-> 1 ETH = 2000 USDT
swap 1 ETH 換 USDT (without fee)
-> total ETH -> 1 + 10 = 11
-> 200,000 / 11 = 18,181.818...
-> 可獲得 20,000 - 18,181.818... ~= 1818 USDT
swap 1 ETH 換 USDT (with fee)
-> total ETH -> 1 * 0.997 + 10 = 10.997
-> 200,000 / 10.997 = 18,186.778...
-> 可獲得 20,000 - 18,186.778... ~= 1813 USDT
```
[Uniswap](https://app.uniswap.org/#/swap)
[Uniswap v2 core github](https://github.com/Uniswap/v2-core)
[Uniswap v2 periphery github](https://github.com/Uniswap/v2-periphery)
Core
* UniswapV2Factory
* UniswapV2Pair
* swap
* mint
* burn
Periphery
* UniswapV2Router
* addLiquidity
* removeLiquidity
* swap...For...
### 實作AMM
```solidity=
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract AMM {
IERC20 public immutable token0;
IERC20 public immutable token1;
uint256 public reserve0;
uint256 public reserve1;
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
constructor(address _token0, address _token1) {
token0 = IERC20(_token0);
token1 = IERC20(_token1);
}
function _mint(address _to, uint256 _amount) private {
balanceOf[_to] += _amount;
totalSupply += _amount;
}
function _burn(address _from, uint256 _amount) private {
balanceOf[_from] -= _amount;
totalSupply -= _amount;
}
function _update(uint256 _reserve0, uint256 _reserve1) private {
reserve0 = _reserve0;
reserve1 = _reserve1;
}
function swap(address _tokenIn, uint256 _amountIn) external returns (uint256 amountOut) {
require(
_tokenIn == address(token0) || _tokenIn == address(token1),
"invalid token"
);
require(_amountIn > 0, "amount in = 0");
// 判斷 token0, token1 順序
bool isToken0 = _tokenIn == address(token0);
(IERC20 tokenIn, IERC20 tokenOut, uint256 reserveIn, uint256 reserveOut) = isToken0
? (token0, token1, reserve0, reserve1)
: (token1, token0, reserve1, reserve0);
tokenIn.transferFrom(msg.sender, address(this), _amountIn);
// 0.3% fee
uint256 amountInWithFee = (_amountIn * 997) / 1000;
// 透過 x * y = k 計算該換多少
amountOut = (reserveOut * amountInWithFee) / (reserveIn + amountInWithFee);
tokenOut.transfer(msg.sender, amountOut);
_update(token0.balanceOf(address(this)), token1.balanceOf(address(this)));
}
function addLiquidity(uint256 _amount0, uint256 _amount1) external returns (uint256 shares) {
token0.transferFrom(msg.sender, address(this), _amount0);
token1.transferFrom(msg.sender, address(this), _amount1);
// 需添加等值流動性
if (reserve0 > 0 || reserve1 > 0) {
require(reserve0 * _amount1 == reserve1 * _amount0, "x / y != dx / dy");
}
// 第一次添加
if (totalSupply == 0) {
// 流動性 token 數量計算 = √ (amount0 * amount1)
shares = _sqrt(_amount0 * _amount1);
} else {
// 計算流動性 token 比例,精度問題取小的
shares = _min(
(_amount0 * totalSupply) / reserve0,
(_amount1 * totalSupply) / reserve1
);
}
require(shares > 0, "shares = 0");
_mint(msg.sender, shares);
_update(token0.balanceOf(address(this)), token1.balanceOf(address(this)));
}
function removeLiquidity(
uint256 _shares
) external returns (uint256 amount0, uint256 amount1) {
uint256 bal0 = token0.balanceOf(address(this));
uint256 bal1 = token1.balanceOf(address(this));
// 計算該取回的 token0 及 token1 數量
amount0 = (_shares * bal0) / totalSupply;
amount1 = (_shares * bal1) / totalSupply;
require(amount0 > 0 && amount1 > 0, "amount0 or amount1 = 0");
_burn(msg.sender, _shares);
_update(bal0 - amount0, bal1 - amount1);
token0.transfer(msg.sender, amount0);
token1.transfer(msg.sender, amount1);
}
function _sqrt(uint256 y) private pure returns (uint256 z) {
if (y > 3) {
z = y;
uint256 x = y / 2 + 1;
while (x < z) {
z = x;
x = (y / x + x) / 2;
}
} else if (y != 0) {
z = 1;
}
}
function _min(uint256 x, uint256 y) private pure returns (uint256) {
return x <= y ? x : y;
}
}
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
}
```
## Lending
### AAVE
https://app.aave.com/
- Over Collateral
- Loan to Value (LTV)
- Liquidation Threshold

- Utilization Rate

- Health Factor

Risk Parameters: https://docs.aave.com/risk/v/aave-v2/asset-risk/risk-parameters
## 補充資料
### 不同的 AMM 公式
#### Curve: stableswap


#### Balancer
V: 常數、t:token種數、W:權重

ex: t = 3

假設 x = ETH, y = USDT, z = BTC
用 ETH 換 BTC

Link:https://miguelmota.com/blog/understanding-stableswap-curve/
### AAVE V3 & Layer2
https://github.com/aave/aave-v3-core/blob/master/contracts/protocol/pool/L2Pool.sol
### Permit
- ERC20 permit
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/ERC20Permit.sol
### Uniswap X

### Examples
https://solidity-by-example.org/
## 作業
### 基本題
>用 HackMD 繳交作業。繳交作業時,請提供 HackMD 網址並貼到 [學員社群](https://courses.kryptocamp.dev/communities/groups/membership-program/) 的作業討論區交流
1. 部署自己的 AMM 合約
* 開源並提交合約地址
* 實作 swap, addLiquidity, removeLiquidity 提交截圖並說明各操作
2. 親身體驗 DeFi,選擇兩個前 20 大的 DeFi 協議操作,並詳細說明該協議的功能及特色
* 提交交易的 Tx hash 以及協議地址
* 寫下詳細的操作過程,例如:使用 Uniswap 將 0.1 ETH 換成 ... DAI
* 說明該操作使用協議中智能合約的哪些 function 並說明其中程式碼共做了哪些鏈上狀態改變
### 進階題
1. 實作一個整合 DeFi 協議的智能合約。例如串接 Uniswap 的 Router 將 tokenA 換成 tokenB 或串接 Aave 抵押 tokenA 借出 tokenB ...等等
* 描述該合約的功能以及串接哪個 DeFi 協議
* 使用 foundry or hardhat 完成測試確認成功串接合約