# Ryan 第五週作業
## 基本題
### 1. 部署自己的 AMM 合約 開源並提交合約地址 實作 swap, addLiquidity, removeLiquidity 提交截圖並說明各操作
#### 合約地址 : https://goerli.etherscan.io/address/0x988735d2e12af2c4b6a85d9d1a5b895b2e3fd092#code

#### 程式碼
```solidity=
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
contract AMM {
IERC20 public immutable token0; //流動池token0
IERC20 public immutable token1; //流動池token1
uint256 public reserve0; //token0數量
uint256 public reserve1; //token1數量
uint256 public totalSupply; //目前總數
mapping(address => uint256) public balanceOf; //用戶的流動性token map
constructor(address _token0, address _token1) {
token0 = IERC20(_token0);
token1 = IERC20(_token1);
}
//用戶提供token0、token1時,增加該用戶流動性token及增加總數
function _mint(address _to, uint256 _amount) private {
balanceOf[_to] += _amount;
totalSupply += _amount;
}
//用戶撤回token0、token1時,減少該用戶流動性token
function _burn(address _from, uint256 _amount) private {
balanceOf[_from] -= _amount;
totalSupply -= _amount;
}
//更新token0、token1數量
function _update(uint256 _reserve0, uint256 _reserve1) private {
reserve0 = _reserve0;
reserve1 = _reserve1;
}
//幣幣交換
function swap(address _tokenIn, uint256 _amountIn) external returns (uint256 amountOut) {
//判斷是否為token0 or token1
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轉給給合約
tokenIn.transferFrom(msg.sender, address(this), _amountIn);
//計算 0.3% 手續費
uint256 amountInWithFee = (_amountIn * 997) / 1000;
// 透過 x * y = k 計算該換多少
amountOut = (reserveOut * amountInWithFee) / (reserveIn + amountInWithFee);
// 把tokenOut轉給該用戶
tokenOut.transfer(msg.sender, amountOut);
// 更新token0、token1數量
_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);
}
```
#### token0為之前上課創建自己的ERC20代幣 RyanToken (RT)
https://goerli.etherscan.io/token/0xD0FA61a4C4fa1057B9104b390fA2221b9EEBB561
#### token1為之前上課同組同學創建的ERC20代幣 Garrick (GHC)
https://goerli.etherscan.io/token/0x66081df82eb1fcc32831b902fa733f92fd67770f?a=0x7a4d6c296b28460cda81fb584234a15fc105e182
#### 先查看目前資產

#### 向token0 RyanToken (RT) 取得授權approve


#### 向token1 Garrick (GHC) 取得授權approve


#### 批准成功

#### addLiquidity 添加進資金池
第一次添加 token0 20個,token1 10個

成功添加進資金池

第二次添加 token0 20000個,token1 10000個

#### 查看資金池目前狀態

#### swap 幣幣交換
使用token0 100個 換 token1


#### 查看資金池目前狀態

#### removeLiquidity 從資金池撤回
先查看目前我擁有的流動性token數量

輸入數量並撤回


### 2. 選擇兩個前 20 大的 DeFi 協議操作,並詳細說明該協議的功能及特色 提交交易的 Tx hash 以及協議地址寫下詳細的操作過程,例如:使用 Uniswap 將 0.1 ETH 換成 … DAI 說明該操作使用協議中智能合約的哪些 function 並說明其中程式碼共做了哪些鏈上狀態改變
#### 1. 使用uniswap交易所進行幣幣交換
Tx hash : [0x108e8d995c23969f27bd01aa640dcffa9a95cef64657e9205d906824bbdef298](https://goerli.etherscan.io/tx/0x108e8d995c23969f27bd01aa640dcffa9a95cef64657e9205d906824bbdef298)



查看Deposit function

Deposit function改變了鏈上的該用戶的WETH餘額
## 進階題
### 1. 實作 Staking 功能的合約,質押 A token ,可獲得 B token 作為獎勵設定 B token 獎勵發放期間 設定 B token 發放數量
```solidity=
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
contract StakingRewards {
IERC20 public immutable stakingToken; //質押的token
IERC20 public immutable rewardsToken; //獎勵的token
address public owner;
// 支付獎勵的持續時間(以秒為單位)
uint public duration;
// 獎勵結束時間戳
uint public finishAt;
// 最後更新時間和獎勵完成時間的最小值
uint public updatedAt;
// 每秒支付的獎勵
uint public rewardRate;
// (獎勵率 * dt * 1e18 / 總供應量)的總和
uint public rewardPerTokenStored;
// 用戶地址 => rewardPerTokenStored
mapping(address => uint) public userRewardPerTokenPaid;
// 用戶地址 => 待領取獎勵
mapping(address => uint) public rewards;
// 總質押
uint public totalSupply;
// 用戶地址 => 質押金額
mapping(address => uint) public balanceOf;
constructor(address _stakingToken, address _rewardToken) {
owner = msg.sender;
stakingToken = IERC20(_stakingToken);
rewardsToken = IERC20(_rewardToken);
}
modifier onlyOwner() {
require(msg.sender == owner, "not authorized");
_;
}
modifier updateReward(address _account) {
rewardPerTokenStored = rewardPerToken();
updatedAt = lastTimeRewardApplicable();
if (_account != address(0)) {
rewards[_account] = earned(_account);
userRewardPerTokenPaid[_account] = rewardPerTokenStored;
}
_;
}
function lastTimeRewardApplicable() public view returns (uint) {
return _min(finishAt, block.timestamp);
}
function rewardPerToken() public view returns (uint) {
if (totalSupply == 0) {
return rewardPerTokenStored;
}
return
rewardPerTokenStored +
(rewardRate * (lastTimeRewardApplicable() - updatedAt) * 1e18) /
totalSupply;
}
//質押
function stake(uint _amount) external updateReward(msg.sender) {
require(_amount > 0, "amount = 0");
stakingToken.transferFrom(msg.sender, address(this), _amount);
balanceOf[msg.sender] += _amount;
totalSupply += _amount;
}
//領回
function withdraw(uint _amount) external updateReward(msg.sender) {
require(_amount > 0, "amount = 0");
balanceOf[msg.sender] -= _amount;
totalSupply -= _amount;
stakingToken.transfer(msg.sender, _amount);
}
//計算質押獎勵
function earned(address _account) public view returns (uint) {
return
((balanceOf[_account] *
(rewardPerToken() - userRewardPerTokenPaid[_account])) / 1e18) +
rewards[_account];
}
//領取質押獎勵
function getReward() external updateReward(msg.sender) {
uint reward = rewards[msg.sender];
if (reward > 0) {
rewards[msg.sender] = 0;
rewardsToken.transfer(msg.sender, reward);
}
}
//設定獎勵結束時間戳
function setRewardsDuration(uint _duration) external onlyOwner {
require(finishAt < block.timestamp, "reward duration not finished");
duration = _duration;
}
//通知獎勵金額
function notifyRewardAmount(
uint _amount
) external onlyOwner updateReward(address(0)) {
if (block.timestamp >= finishAt) {
rewardRate = _amount / duration;
} else {
uint remainingRewards = (finishAt - block.timestamp) * rewardRate;
rewardRate = (_amount + remainingRewards) / duration;
}
require(rewardRate > 0, "reward rate = 0");
require(
rewardRate * duration <= rewardsToken.balanceOf(address(this)),
"reward amount > balance"
);
finishAt = block.timestamp + duration;
updatedAt = block.timestamp;
}
//取最小數
function _min(uint x, uint y) private pure returns (uint) {
return x <= y ? x : y;
}
}
interface IERC20 {
function totalSupply() external view returns (uint);
function balanceOf(address account) external view returns (uint);
function transfer(address recipient, uint amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint);
function approve(address spender, uint amount) external returns (bool);
function transferFrom(
address sender,
address recipient,
uint amount
) external returns (bool);
event Transfer(address indexed from, address indexed to, uint value);
event Approval(address indexed owner, address indexed spender, uint value);
}
```
建立質押Token

建立獎勵Token

Mint 1000個質押Token給自己

Mint 100000個獎勵Token給合約

設定發放時間為1天 = 86400秒

設定獎勵Token發放數量

approve給合約

質押100個質押Token

查看質押獎勵Token

領取質押獎勵Token,查看帳戶裡面有多少個獎勵Token

領回質押Token

### 2. 研究 DeFi 合約審計報告,找一個 DeFi 協議並查看其審計報告後寫下心得
UniSwap Audit



###### tags: `Solidity 工程師實戰營第 5 期`