owned this note
owned this note
Published
Linked with GitHub
# AMM
# dex AMM module
假设valut中原本有10,0 eth, eth/usdc= 100, 则x=100,k=100 * 10000,
eth/vusd
1. Alice用 1eth 以2倍开多仓后变化如下:
Alice将 1 eth存入valut中, Protocol 将 Alice 的 2eth(1 的 2 倍杠杆)记入AMM,作为回报,它根据常数函数 (x*y = k) 计算 Alice 收到的 vusd 数量。 Protocol 记录 Alice 现在有 2 ETH,并且这个AMM内部的状态变成了 98ETH 和 10204.08 vusd。 Alice持仓为2eth, -204.08vusd。
**开多时: 传入eth金额,池子减少eth金额, 计算出对应vusd数量返回,margin将vusd数量标记为负;**
| 动作 | ETH | USDC | 计算 |
| --- | --- | --- | --- |
| 初始状态 | 100 | 10000 | - |
| Alice开了2eth多仓 | 98 | 10204.08 | 100*10000/98 |
1. 同样,如果此时Bob继续注入1eth以2倍杠杆开多仓后,AMM公式将自动算出其持有多仓为 2 ETH;bob仓位为2eth, -212.59 vusd;
| 动作 | ETH | USDC | 计算 |
| --- | --- | --- | --- |
| Bob开了2eth多仓 | 96 | 10416.67 | 10000* 100/96 |
| Alice平了多仓 | 97.92 | 10212.59 | 1000000/10212.59 |
| Bob平了1.89多仓 | 100 | 10000 | 1000000/100 |
此时 alice平仓时候, 池子vusd储备增加204.08, 兑换出1.92eth, alice 持仓2eth- 1.92eth = 0.08eth;
**平多时:传入vusd数量,池子减少vusd金额,计算出eth金额,margin需要当负数处理。**
而bob平仓 2eth - 2.08eth= -0.08eth;
3. 如果 David 以1eth以2倍杠杆开空单。David 1eth存入valut,Protocol 将 David 的 2eth 记入vAMM,作为回报,它根据常数函数 (x*y = k) 计算 Bob 收到的负 vusd 的数量。Protocol 记录 David 现在已经做空了 -2 vETH,并且这个vAMM内部的状态现在变成了 102 vETH 和 9803.92 vDAI。David持仓为 -2eth, 196.08 vusd;
| 动作 | ETH | USDC | 计算 |
| --- | --- | --- | --- |
| David开了2eth空仓 | 102 | 9803.92 | 100*10000/102 |
David平掉2eth空仓, 则将vusd兑换成eth即可。
假设初始值:
eth =100 usdc = 10000; A 100% 流动性
1 B来填充流动性:
100eth,10000usdc, 则A 50% B50%
池子 200eth, 20000usdc,池子价格100
2 市场大量空单:
池子 250eth,16000usdc (真实流动性200eth,池子价格64)
相当于空单造成50eth偏差
3 C来填充流动性
100eth, 6400usdc 则C 33% B 33% A 33%
池子350eth, 22400usdc (池子价格64)
4 C提取流动性
- 池子剩余: 350*2/3= 233.3eth, 22400*2/3 = 14933.3usdc
真实流动性: 200eth,
此时空单33eth偏差
**开空时:**
**传入eth金额,池子增加eth金额,计算出对应的vusd返回, margin记vusd金额为正;**
**平空时:**
**传入vusd金额,池子增加vusd金额,计算出对应eth返回,margin将eth金额记为正;**
总结:
**开多时: 传入eth金额,池子减少eth金额,计算出对应vusd数量返回,margin将vusd数量标记为负;**
**开空时: 传入eth金额,池子增加eth金额,计算出对应的vusd返回, margin记vusd金额为正;**
**平多时:传入vusd数量,池子减少vusd金额,计算出eth金额, margin需要当负数处理。**
**平空时:传入vusd金额,池子增加vusd金额,计算出对应eth返回,margin将eth金额记为正;**
- 初始化函数
```
function initialize(
uint256 _quoteAssetReserve, // 初始usdc储备
uint256 _baseAssetReserve, // 初始eth储备
uint256 _tradeLimitRatio, // 最大交易比率
uint256 _fundingPeriod, // funding fee周期
IPriceFeed _priceFeed, // 预言机
address _quoteAsset, // eth地址
address _baseAsset, // usdt地址
uint256 _fluctuationLimitRatio, // 价格波动率
uint256 _tollRatio, //非必要
uint256 _spreadRatio // 非必要
)
```
初始化池子的必要参数。
EVENT :
ammInitialize(uint256 _quoteAssetReserve, uint256 _baseAssetReserve, uint256 _tradeLimitRatio, uint256 _fundingPeriod, IPriceFeed _priceFeed,address _baseAsset, address _quoteAsset, uint256 _fluctuationLimitRatio)
- 交易接口:
- [ ] swap (address inputAddress, address outputAddress, uint inputAmout, uint outputAmount)
功能: 交易接口;开平(多空)时候调用;inputAddress,输入地址; outputAddress输出地址;
inputAmout输入数量, outputAmount输出数量;
示例:
开多: swap( 0 , weth , 0 , 2 ) ; 1000000/ 100-2
开空:swap(weth, 0 , 2, 0 ) ; 1000000/100+2
平多: swap(0, usdt, 0 ,204.08) 1000000/ X-204.08
平空: swap(usdt, 0 , 196.08, 0) 1000000/X+196.08
EVENT: Swap(address index inputAddress, address index outputAddress, uint inputAmout, uint outputAmount)
EVENT: Sync(reserveBaseToken, reserveQuoteToken)
- [ ] updateReserve(uint reserveBaseToken, uint reserveQuoteToken);
功能:穿仓时候使用,只能margin使用。
// only margin, 数值校验,todo。影响价格波动5%, reserveA数量变化范围。 替代方案;
EVENT: updateReserve(uint reserveBaseTokenbefore, uint reserveBaseTokenbefore, uint reserveQuoteTokenAfter, uint reserveQuoteTokenAfter)
- [ ] rebase();
功能:调整vusd的数量,使价格回到当前。
//没有奖励, 设置价差5%或(时间间隔) ;
EVENT: rebase(uint priceBefore, uint priceAfter, uint modifyAmount)
- 查询接口:
- [ ] function getReserve() external view returns (uint reserveBaseToken, uint reserveQuoteToken,uint32 _blockTimestampLast);
功能: 获取池子当前储备;
- [ ] function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) internal pure returns (uint amountIn)
功能: 根据out数量获取in的数量;
- [ ] function getAmountsOut(uint amountIn, uint reserveIn, uint reserveOut)
功能: 根据in数量获取out数量;
- [ ] function quote(uint amountA, uint reserveA, uint reserveB)
功能:添加流动性使用
LP接口:
- [ ] function mint(address to) external returns (uint quoteAmount, uint liquidity);
EVENT: Mint(address indexed to , uint liquidity, uint baseTokenDeposit )
- [ ] function burn(address to) external returns (uint baseAmount, uint quoteAmount)
EVENT: Burn(address indexed to , uint liquidty, uint baseTokendraw)
stake接口:
EVENT: Staked(address indexed user, uint256 amount);
EVENT: Withdrawn(address indexed user, uint256 amount);
EVENT: RewardPaid(address indexed user, uint256 reward);
## 一. 添加流动性
1. LP 调用 Router 合约的 addLiquidity 接口
2. 通过 Factory 合约查询交易对 AMM 是否存在
3. 若不存在,通过 Factory 合约创建该交易对的 Vault、Margin、AMM 合约
4. 若不存在,需根据 Uniswap 的流动性计算 quoteAmount
1. 从 Uniswap 查询交易对是否存在,不存在则报错
2. 存在则查询出两个 token 的 reserve
3. 根据 reserves 计算出 baseToken 的价格
4. 再根据 baseAmount 计算出 quoteAmount
5. 若存在,根据 AMM 的 reserve,计算出 quoteAmount
6. 将 baseAmount 和 quoteAmount 注入到 AMM 中并返回 LPToken 数量
7. 从 LP 用户地址转入 baseAmount 到 Vault 合约
8. 如果 autoStake 为 true,则将 LPToken 质押到 Staking 合约
9. 如果 autoStake 为 false,则将 LPToken 转到 LP 用户地址
## 二、移除流动性
1. LP 调用 Router 合约的 removeLiquidity 接口
2. 通过 Factory 合约查询交易对 AMM 是否存在,不存在则报错
3. 从 LP 用户地址将 liquidity 数量的 LPToken 转入 AMM
4. 调用 AMM 合约的 burn 接口执行底层的移除流动性操作
5. AMM 返回 baseAmount 和 quoteAmount
6. 校验 baseAmount 是否大于 baseAmountMin,否则报错
7. 从 Vault 合约提取 baseAmount 转到 LP 用户地址
-
-
# Caculate problem
**风控答疑(jerry):**
[https://www.notion.so/5874219ca74e46939a5f9479ae102b5a](https://www.notion.so/5874219ca74e46939a5f9479ae102b5a)
**代码实现公式(arwen):**
[公式大全](https://www.notion.so/9c5720293e704300bdb724111bfe9a1f)
## **前提:**
假设当前eth价格为 2000 usdt,池子有5000eth, 10,000,000vusd
我们采用非精确(杠杆)方案:
协议可以当贷款方,可以借出baseToken或quoteToken。但只能跟AMM交易。
假设eth/usdt 2000 ,保证金率为10%,即最大开10倍杠杆;假设beta=1;
## 1 **开多**
用户存2eth,
### 前端根据**最大开仓量公式:**
$$
vUSD_{acc.max} = -({\dfrac{MarginRate_{open}}{Margin_{acc}*MarkPrice}+\dfrac{2\beta}{vUSD_{pool}}})^{-1}
$$
得出vUSDacc.max = - 1/(0.1/2*2000+ 2/10000000)= -39684.54;
用户打算开5倍附近杠杆,即向协议借20000 vusd,然后跟AMM交易(swapinput),卖出20000vusd, 换出9.98 eth,
所以实际**杠杆倍数**9.98/2 ~ 4.99(按eth算).
用户仓位**(11.98eth, -20000vusd ),** 用户实际购买平均价格20000/9.98 = **2004**;
池子储备**(4990.02, 10020000)**,池子当前价格**2008.007**。
### **用户的强平价格(保证精度!):**
$$
LiqPrice_{buffer}=(\sqrt{\dfrac{vUSD^2}{4xy}-\dfrac{vUSD}{vETH + FundingAccrual}}-\beta\dfrac{vUSD}{\sqrt{xy}})^2
$$
LiqPricebuffer = [ sqrt(0.0019+1669.45)+**20000/**sqrt(10020000*4990.02)]**2 =
(40.86+0.089)**2 =1676.77;
比中心化交易所: entryPrice*[lv/(lv+1)] = 2000* 5/6 =1666.67 大,符合预期;
### 此时用户的标记价格Markeprice.acc:
$$
AccountSpecificMarkPrice=(\dfrac{{y}+\beta*{vUSD}}{\sqrt{xy}})^2
$$
此时用户的标记价格:
[10020000+1*(**-20000**)]**2/(4990.02*10020000) = 2000 ;
**注:**用户标记价格低于池子当前价格,符合预期;
调整池子仓位,使得池子当前价格为**1676.77(达到强平价)** ,保持恒定乘积,
X*Y= K,Y/X=P;
则当前储备为**(5460.70,9156336.60)**
### 触发强平条件:
(资产 - 负债 > 0 或 负债 / 资产>1);
然后触发强平:此时用户标记价格: **1659.375**
**注:做多,标记价格低于当前价格,低估资产,有利于池子。**
用标记价格计算用户净资产: 1659.375*11.98 - 20000 = -120.69 < 0; 可以强平,符合预期;
**验证:** 用户平掉-20000vusd,需要支付eth金额:
x = 50000000000/ (9156336.60-20000) - 5460.70=5472.65- 5460.70 = 11.953<11.98
平均交易价格: 20000/11.953 =**1673.22**;
强平价格**1676.77**,
标记价格 **1659.375**;
**重要检查点:**强平价格>平均交易价格>标记价格
符合预期;
**用户有盈余,归池子所有,符合预期;**
(11.98-11.95)/11.98 = 0.25%, 用户损失在合理范围内;
## 2 **开空**
**回到假设,用户A开仓后未平仓,此时eth/usdt为 2008,池子储备(4990.02, 10020000)**
用户B存1eth, 打算开2.5倍附近杠杆, 即持有5000vusd仓位, 然后AMM计算出(swapoutput)对应eth量。即需要向协议借2.4913eth。
### 最大可开仓量
$$
vUSD_{acc.max} = ({\dfrac{MarginRate_{open}}{Margin_{acc}*MarkPrice}+\dfrac{2\beta}{vUSD_{pool}}})^{-1}
$$
得出vUSDacc.max = 1 /(0.1/(1*2008)+ 2/10020000)= 19999.8409;
所以实际杠杆倍数2.49/1 ~ 2.49(按eth算). 用户仓位**(-1.4913eth, 5000vusd)**
用户平均购买价格 5000/2.4913=2006.98;
池子储备(4992.51, 10015000),池子价格降低到价格 2006.004;
**用户的强平价格(保证精度!):**
$$
LiqPrice_{buffer}=(\sqrt{\dfrac{vUSD^2}{4xy}-\dfrac{vUSD}{vETH + FundingAccrual}}-\beta\dfrac{vUSD}{\sqrt{xy}})^2
$$
LiqPricebuffer = [ sqrt(0.000125 + 3352.779) - 5000**/**sqrt(10015000*4992.51)]**2 =
(sqrt(3352.78) - 0.0224)**2 =3350.19**;**
中心化交易所强平价格: 5000/(2.49-1) = 3355.70,提前强平, 符合预期
调整池子仓位,使得池子当前价格为 3350.19**(达到强平价)** ,保持恒定乘积,
则当前储备为**(3863.23,12942539.29)**
## 触发强平条件:
(资产 - 负债 > 0 或 负债 / 资产>1);
$$
AccountSpecificMarkPrice=(\dfrac{{y}+\beta*{vUSD}}{\sqrt{xy}})^2
$$
然后触发强平:此时用户**标记价格**: ****(**12942539.29**+5000)^2/(4990.02*10020000) = **3352.78**
用标记价格计算用户净资产: **3352.78***(-1.4913) + 5000 = -0.001 < 0; 可以强平,符合预期;
**注:做多,标记价格低于当前价格,低估资产,有利于池子。**
验证: 用户卖出 5000 vusd,需要支付eth金额:
x = **3863.23 - 50000000400.00001** / (**12942539.29+ 5000**) = 1.4923 > 1.4913, 足够覆盖负债。
平均交易价格: 5000/1.4923 = 3350.53; 小于强平价格,高于标记价格;
强平价格:3350.19
平均交易价格:3350.53
标记价格:3352.78
**重要检查点:**强平价格<平均交易价格<标记价格
符合预期;
**用户有盈余,归池子所有,符合预期;**
(1.4925-1.4913)/2.49 = 0.05%, 用户损失在合理范围内;
**注:做空时,标记价格高于当前价格,保证金率偏小,有利于池子。**

## **多次开仓:**
### 1. 正向:
**回到假设,用户A开仓后未平仓,此时eth/usdt为 2008,池子储备(4990.02, 10020000)**
用户A 仓位**(11.98eth, -20000vusd );**
用户A 追加2个eth, 再开5倍杠杆;
Margin.acc = 4;
### 2. 反向
**回到假设,用户A开仓后未平仓,此时eth/usdt为 2008,池子储备(4990.02, 10020000)**
用户A 仓位**(11.98eth, -20000vusd );**
**2.1 用户A 追加1个eth, 再开2倍杠杆空单;**
Margin.acc = 3;
**2.2 用户A 追加3个eth, 再开8倍杠空单;**
Margin.acc = 6;
手续费:
开多收到的手续费是U,开空手续费是eth;
平多手续费收到是eth,平空手续费是U;
正常一笔开多,平多, 收到的是U,eth
如果一笔开多,被强平,则收到的只有U;
如果一笔开空,被强平,则收到的只有eth,
-
# Margin 视角分析
margin资金变化:

0 Margin资金池的weth的数量和value
1 LP充提
2 trader开仓保证金或只添加保证金
3 trader 平仓盈亏或只移除保证金
4 清算bouns
主网graph:
[https://api.apex.exchange/g2/subgraphs/name/apex/exchange/graphql](https://api.apex.exchange/g2/subgraphs/name/apex/exchange/graphql)
0 Margin资金池的weth的数量和value 高优先级
[https://arbiscan.io/address/0xb2E780110C0B333203Da7F1fD7dcE2F90A608f3C#readContract](https://arbiscan.io/address/0xb2E780110C0B333203Da7F1fD7dcE2F90A608f3C#readContract)
1 统计LP充提记录:
todo, removeliquidity 需要识别to地址。 transfer log处理
```
{
liquidityLogs(first:1000, orderBy: timestamp, orderDirection: desc,
where :{pair: "0xf466a566f6b7145d0e79b3961422b40111623fd0",
timestamp_gt: 1653321600, timestamp_lt: 1653408000}){
{
timestamp
baseAmount
price
account
type
}
}
```
2 开仓保证金
```jsx
{
addMarginLogs(first:1000, orderBy: timestamp, orderDirection: desc,
where :{pair: "0xf466a566f6b7145d0e79b3961422b40111623fd0", trader: "0x9dcccea681933a8d3848e3eb61fa076bd23ecaaa"}
) {
depositAmount
quoteSize
baseSize
tradeSize
timestamp
}
```
3 trader 平仓移除保证金和盈亏
保证金移除
```
{
removeMarginLogs(first:1000 , orderBy: timestamp, orderDirection: desc, where :{pair: "0xf466a566f6b7145d0e79b3961422b40111623fd0",timestamp_gt: 1653321600, timestamp_lt: 1653408000
}){
trader
withdrawAmountFromMargin
fundingFee
}
}
```
待验证: addmargin + removemargin == 盈亏???
4 清算bouns
# AMM 视角分析
AMM

问题1 : 有无rebase时的amm盈亏
LP的亏损体现在穿仓.
问题2: 验证仓的总盈利和总亏损是否相等。
### fundingfee
1 LP视角的池子大小
2 fundingfee(在多空大于10倍的情况,amm才会参与fundingfee)
1.平仓时的fundingfee
```jsx
{
closeUserPositions(first:1000 , orderBy: lastTime, orderDirection: desc where:{exitType: "close", pair: "0xf466a566f6b7145d0e79b3961422b40111623fd0"}) {
id
user
margin {
id
}
pair {
id
}
closedProfitAndLoss
closeSide
exitPrice
entryPrice
exitType
contractValue
fundingFee
initMarginAmount
}
}
```
2.移除保证金时候的fundingfee
```jsx
{
removeMarginLogs(first:1000 , orderBy: timestamp, orderDirection: desc where:{ pair: "0xf466a566f6b7145d0e79b3961422b40111623fd0"}) {
id
pair
fundingFee
}
}
```
3.开仓/逐仓时候的fundingfee
```jsx
{
positionLogs(first:1000 , orderBy: timestamp, orderDirection: desc where:{type:"OpenPosition", pair: "0xf466a566f6b7145d0e79b3961422b40111623fd0"}) {
id
pair
side
baseSize
quoteSize
quoteAmount
markPrice
entryPrice
fundingFee
}
}
```
1. LP的AMM池的盈亏
getRealBaseReserve - amm的lp金额(某时间段的添加流动性-移除流动性的amt)
1. 协议盈余 = margin余额 - 用户保证金 - amm的lp金额
即基本手续费收入