# 去中心化交易所 開發紀錄 開發來源 [Kryptocamp Defi 講義](https://hackmd.io/@hazelwu/HkIVkLACo/https%3A%2F%2Fhackmd.io%2F%40RayHuangTW%2Fryo8KTGxh) 使用chatGPT4改成使用ETH與tGD的交易對。 ![](https://i.imgur.com/uwtRVhU.png) # V7 完全體 最終版以Gitgub最新版本為準 # [AMM V7 github](https://github.com/penguin72487/KryptoCampSolidity/blob/main/W6/simpleAMM/src/simpleAMMV7.sol) ## [etherscan上查看](https://sepolia.etherscan.io/address/0x69911ed54c0393afd31b278b93d908fb4387a94f#code) constructor 有測試token互動介面,以保證運行順暢 //check ERC20 interface token.name(); token.symbol(); token.decimals(); function swap ERC20/ETH, function swapTokenForETH ETH/ERC20 function swap_WithSlipLock ERC20/ETH,輸入要換的數量以外,輸入預期得到的輸出還有滑點 function swapTokenForETH_WithSlipLock ETH/ERC20 輸入要換的數量以外,輸入預期得到的輸出還有滑點 開發者拿交易手續的0.3%中的50%,直接算在開發者的share裡面。 function addLiquidity 提供流動性,一定要成比例才能加,比例可以浮動在+-fee%。 function removeALLLiquidity 根據使用者移除自己所有的的流動性,在前端可以在pool加入移除使用者所有流動性的按鈕,既然都加了,也可以在前端加上移除25%、50%、75%的按鈕,數量由前端計算。 function removeLiquidity 根據使用者移除流動性 function _removeLiquidity V4從原本V3的external變成internal,要經過removeLiquidity檢查才能叫。 function getETHPrice() ETH/ERC 無滑點手續費的價格*$10^{18}$為了小數點精度 function getERCPrice() ERC/ETH 無滑點手續費的價格\*10\*\*t0.decimals$為了小數點精度 function getInETHPredictOutputERC ERC20/ETH算上滑點+手續費的輸出,輸入ETH得交換後ERC20的數量,顯示在客戶端記得除以decimals function getInERCPredictOutputETH ETH/ERC20 算上滑點+手續費的輸出,輸入ERC20得交換後ETH的數量,顯示在客戶端記得除以decimals function getInERCOutputETHLiquidityAmount 輸入想要加入的ERC數量,生成對應所需ETH,顯示在客戶端記得除以decimals function getInETHOutputERCLiquidityAmount 輸入想要加入的ETH數量,生成對應所需ERC,顯示在客戶端記得除以decimals function myShares() 使用者可以直接呼叫取得自己的Share function sharesOf(address _user) 任何人都可以看別人有沒有提供流動性,提供了多少。 # [AMM V7 ERC github](https://github.com/penguin72487/KryptoCampSolidity/blob/main/W6/simpleAMM/src/simpleAMMV7ERC.sol) ## [etherscan上查看](https://sepolia.etherscan.io/address/0x4cD54dB1026CAcC623ac50bE02Ce21C9B28E4f0E#code) constructor(address _token0, address _token1, uint256 _fee) { require(_fee>0 &&_fee <= 100, "fee > 10%"); developer = msg.sender; token0 = ERC20(_token0); token1 = ERC20(_token1); fee = _fee; //check ERC20 interface token0.name(); token0.symbol(); token0.decimals(); token1.name(); token1.symbol(); token1.decimals(); } constructor 有測試token互動介面,以保證運行順暢 先填入的token是token0,後填的是token1 function swapToken1ForToken0 ERCt0/ERCt1 在前端Send Token : token1,輸入框框的數量要輸入這個函式 function swapToken0ForToken1 ERCt1/ERCt0 在前端Send Token : token0,輸入框框的數量要輸入這個函式 function swapToken1ForToken0_WithSlipLock ERCt0/ERCt1,輸入要換的數量以外,輸入預期得到的輸出還有滑點 function swapToken0ForToken1_WithSlipLock ERCt1/ERCt0 輸入要換的數量以外,輸入預期得到的輸出還有滑點 開發者拿交易手續的0.3%中的50%,直接算在開發者的share裡面。 function addLiquidity 提供流動性,一定要成比例才能加,比例可以浮動在+-fee%。在前端輸入Add Amount可以配合下面的getInToken1OutputToken0LiquidityAmount還有getInToken0OutputToken1LiquidityAmount來取得對應數量,可以不用在前端算。 function removeALLLiquidity 根據使用者移除自己所有的的流動性,在前端可以在pool加入移除使用者所有流動性的按鈕,既然都加了,也可以在前端加上移除25%、50%、75%的按鈕,數量由前端計算。 function removeLiquidity 根據使用者移除自己的流動性 function _removeLiquidity V4從原本V3的external變成internal,要經過removeLiquidity檢查才能叫。 function getToken0Price ERCt0/ERCt1 無滑點手續費的價格\*10\*t0.decimals為了小數點精度 function getToken1Price ERCt1/ERCt0 無滑點手續費的價格\*10\*\*t1.decimals為了小數點精度 function getInToken0PredictOutputToken1 ERCt0/ERCt1算上滑點+手續費的輸出,輸入ETH得交換後ERC20的數量,輸出記得除以decimals function getInToken0PredictOutputToken1 ERCt1/ERCt0 算上滑點+手續費的輸出,輸入ERC20得交換後ETH的數量,輸出記得除以decimals function getInToken1OutputToken0LiquidityAmount 輸入想要加入的token1數量,生成對應所需Token0 function getInToken0OutputToken1LiquidityAmount 輸入想要加入的token0數量,生成對應所需Token1 function myShares() 使用者可以直接呼叫取得自己的Share function sharesOf(address _user) 任何人都可以看別人有沒有提供流動性,提供了多少。 # [AMM V6 github](https://github.com/penguin72487/KryptoCampSolidity/blob/main/W6/simpleAMM/src/simpleAMMV6.sol) ## [etherscan上查看NOT](https://goerli.etherscan.io/address/) function swap ERC20/ETH function swapTokenForETH ETH/ERC20 function swap_WithSlipLock ERC20/ETH,輸入要換的數量以外,輸入預期得到的輸出還有滑點 function swapTokenForETH_WithSlipLock ETH/ERC20 輸入要換的數量以外,輸入預期得到的輸出還有滑點 開發者拿交易手續的0.3%中的50%,直接算在開發者的share裡面。 function addLiquidity 提供流動性,一定要成比例才能加。 function removeLiquidity 根據使用者移除流動性 function _removeLiquidity V4從原本V3的external變成internal,要經過removeLiquidity檢查才能叫。 function getETHPrice() ETH/ERC 無滑點手續費的價格*$10^{18}$為了小數點精度 function getERCPrice() ERC/ETH 無滑點手續費的價格*$10^{18}$為了小數點精度 function getInETHPredictOutputERC ERC20/ETH算上滑點+手續費的輸出,輸入ETH得交換後ERC20的數量,輸出記得除以decimals function getInERCPredictOutputETH ETH/ERC20 算上滑點+手續費的輸出,輸入ERC20得交換後ETH的數量,輸出記得除以decimals # [AMM V6 ERC github](https://github.com/penguin72487/KryptoCampSolidity/blob/main/W6/simpleAMM/src/simpleAMMV6.sol) function swapToken1ForToken0 ERCt0/ERCt1 function swapToken0ForToken1 ERCt1/ERCt0 function swapToken1ForToken0_WithSlipLock ERCt0/ERCt1,輸入要換的數量以外,輸入預期得到的輸出還有滑點 function swapToken0ForToken1_WithSlipLock ERCt1/ERCt0 輸入要換的數量以外,輸入預期得到的輸出還有滑點 開發者拿交易手續的0.3%中的50%,直接算在開發者的share裡面。 function addLiquidity 提供流動性,一定要成比例才能加。 function removeLiquidity 根據使用者移除流動性 function _removeLiquidity V4從原本V3的external變成internal,要經過removeLiquidity檢查才能叫。 function getToken0Price ERCt0/ERCt1 無滑點手續費的價格*$10^{tO.decimals}$為了小數點精度 function getToken1Price ERCt1/ERCt0 無滑點手續費的價格*$10^{t1.decimals}$為了小數點精度 function getInToken0PredictOutputToken1 ERCt0/ERCt1算上滑點+手續費的輸出,輸入ETH得交換後ERC20的數量,輸出記得除以decimals function getInToken0PredictOutputToken1 ERCt1/ERCt0 算上滑點+手續費的輸出,輸入ERC20得交換後ETH的數量,輸出記得除以decimals ## 功能請求 1. removeLiquidity 後的reserve數量不對導致addLiquidity怪怪的 2. 合約工廠 4. gas優化 6. 安全審計 # [AMM V5 github](https://github.com/penguin72487/KryptoCampSolidity/blob/main/W6/simpleAMM/src/simpleAMMV5.sol) ## [etherscan上查看NOT](https://goerli.etherscan.io/address/) goerli NOT ## 非正式部屬 https://goerli.etherscan.io/address/0x2Ab03c02654d9BA990Bb44c62a17D0765719B9D7 function swap tGD/ETH function swapTokenForETH ETH/tGD function addLiquidity 提供流動性,如果不成比例就swap成比例後再加,swap成比例也是要手續費的。 function removeLiquidity 根據使用者移除流動性 function _removeLiquidity V4從原本V3的external變成internal,要經過removeLiquidity檢查才能叫。 function getETHPrice() ETH/ERC 無滑點手續費的價格*$10^{18}$為了小數點精度 function getERCPrice() ERC/ETH 無滑點手續費的價格*$10^{18}$為了小數點精度 function getPredictOutputERC ERC20/ETH算上滑點+手續費的輸出,輸入ETH得交換後ERC20的數量,輸出記得除以decimals function getPredictOutputETH ETH/ERC20 算上滑點+手續費的輸出,輸入ERC20得交換後ETH的數量,輸出記得除以decimals function calculateSlippage ERC20價格滑點預計算 ## 功能請求 1. function 名稱優化 2. 合約工廠 3. 流動性提供限制、最小提供時間限制。 4. gas優化 5. ETH檔價格滑點 ,Slippage lock 6. 安全審計 addLiquidity如果沒成比例要swap的部分還是有些問題。 7. 手續費分配 ```solidity= // SPDX-License-Identifier: MIT pragma solidity ^0.8.18; //goerli 0xae6B0f75b55fa4c90b2768e3157b7000241A41c5 merge 0x4a9C121080f6D9250Fc0143f41B595fD172E31bf // V1 0xf60440f93a677AB6968E1Fd10cf8a6cE61941131 // V2 0x8b175c421E9307F0365dd37bc32Dda5df95C4946 // V3 0x3ea585565c490232b0379C7D3C3A9fC3fA5C9c0C // V4 0x6D81EE8B003422Ee9d1255aceA42386eCBD20a60 merge 0x540d7E428D5207B30EE03F2551Cbb5751D3c7569 /** * @title ContractName * @dev ContractDescription * @custom:dev-run-script ../script/tAMM.js */ import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/utils/math/Math.sol"; contract AMM { IERC20 public immutable token; address public constant ETH_ADDRESS = address(0); uint256 public reserve0; uint256 public reserve1; uint256 public totalSupply; mapping(address => uint256) public balanceOf; constructor(address _token) { token = IERC20(_token); } 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(uint256 _amountIn) public payable returns (uint256 amountOut) { require(msg.value == _amountIn, "ETH amount mismatch"); uint256 amountInWithFee = (_amountIn * 997) / 1000; amountOut = (reserve1 * amountInWithFee) / (reserve0 + amountInWithFee); token.transfer(msg.sender, amountOut); _update(address(this).balance, token.balanceOf(address(this))); } function swapTokenForETH(uint256 _amountIn) public returns (uint256 amountOut) { uint256 amountInWithFee = (_amountIn * 997) / 1000; amountOut = (reserve0 * amountInWithFee) / (reserve1 + amountInWithFee); require(amountOut > 0, "Insufficient output amount"); token.transferFrom(msg.sender, address(this), _amountIn); payable(msg.sender).transfer(amountOut); _update(address(this).balance - amountOut, token.balanceOf(address(this))); } function addLiquidity(uint256 _amount1) external payable returns (uint256 shares) { //token.approve(address(this), _amount1); token.transferFrom(msg.sender, address(this), _amount1); uint256 _amount0 = msg.value; if (reserve0 > 0 || reserve1 > 0) { if (reserve0 * _amount1 != reserve1 * _amount0) { // Calculate the required amount of tokens to make the ratio equal uint256 requiredAmount1 = (reserve1 * _amount0) / (reserve0*997/1000); //requiredAmount1*=(1000/997); // Swap the excess tokens for ETH without fee if (_amount1 > requiredAmount1) { uint256 excessAmount1 = _amount1 - requiredAmount1; _amount0 += swapTokenForETH(excessAmount1); // Swap the excess ETH for tokens without fee } else { uint256 excessAmount0 = _amount0 - requiredAmount1 * reserve0 / reserve1; _amount1 += swap(excessAmount0); } // Update the _amount1 to the required amount _amount1 = requiredAmount1; } } if (totalSupply == 0) { shares = _sqrt(_amount0 * _amount1); } else { shares = Math.min( (_amount0 * totalSupply) / reserve0, (_amount1 * totalSupply) / reserve1 ); } require(shares > 0, "shares = 0"); _mint(msg.sender, shares); _update(address(this).balance, token.balanceOf(address(this))); } function removeAllLiquidity(address _user) external returns (uint256 amount0, uint256 amount1) { return _removeLiquidity(_user,balanceOf[_user]); } function removeLiquidity(uint256 _shares) external returns (uint256 amount0, uint256 amount1) { require(_shares <= balanceOf[msg.sender], "Insufficient balance"); return _removeLiquidity(msg.sender, _shares); } function removeLiquidity(address _user, uint256 _shares) external returns (uint256 amount0, uint256 amount1) { require(_shares <= balanceOf[_user], "Insufficient balance"); return _removeLiquidity(_user, _shares); } function _removeLiquidity(address _user, uint256 _shares) internal returns (uint256 amount0, uint256 amount1) { amount0 = (_shares * address(this).balance) / totalSupply; amount1 = (_shares * token.balanceOf(address(this))) / totalSupply; require(amount0 > 0 && amount1 > 0, "amount0 or amount1 = 0"); _burn(_user, _shares); _update(address(this).balance - amount0, token.balanceOf(address(this)) - amount1); payable(_user).transfer(amount0); token.transfer(_user, amount1); } function my_shars() public view returns(uint256){ return balanceOf[msg.sender]; } function sharesOf(address _user) public view returns(uint256){ return balanceOf[_user]; } function _sqrt(uint y) internal pure returns (uint z) { if (y > 3) { z = y; uint 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; } function getETHPrice() public view returns (uint256) { require(reserve0 > 0 && reserve1 > 0, "Invalid ETH reserves"); return (reserve1 * 10**18) / reserve0; } function getERCPrice() public view returns (uint256) { require(reserve0 > 0 && reserve1 > 0, "Invalid ERC20 reserves"); return (reserve0 * 10**18) / reserve1; } function getPredictOutputERC(uint256 _amount) public view returns (uint256) { require(reserve0 > 0 && reserve1 > 0 && _amount>0, "Invalid ERC20 reserves"); uint256 amountInWithFee=(_amount * 997) / 1000; return (reserve1 * amountInWithFee) / (reserve0 + amountInWithFee); } function getPredictOutputETH(uint256 _amount) public view returns (uint256) { require(reserve0 > 0 && reserve1 > 0 && _amount>0, "Invalid ETH reserves"); uint256 amountInWithFee=(_amount * 997) / 1000; return (reserve0 * amountInWithFee) / (reserve1 + amountInWithFee); } function calculateSlippage(uint256 amountIn) public view returns (uint256) { require(amountIn > 0, "Invalid input amount"); uint256 amountInWithFee = (amountIn * 997) / 1000; uint256 amountOut = (reserve1 * amountInWithFee) / (reserve0 + amountInWithFee); require(amountOut > 0, "Invalid output amount"); // Calculate the price without any slippage uint256 noSlippagePrice = getERCPrice(); // Calculate the expected output amount without any slippage uint256 expectedAmountOut = (amountIn * noSlippagePrice) / 10**18; // Calculate the slippage percentage uint256 slippage; if (amountOut > expectedAmountOut) { slippage = ((amountOut - expectedAmountOut) * 1000) / expectedAmountOut; } else { slippage = ((expectedAmountOut - amountOut) * 1000) / expectedAmountOut; } // Clamp the slippage value between 1 and 1000 (0.1% and 100%) slippage = _min(slippage, 1000); return slippage; } } ``` # [AMM V4 github](https://github.com/penguin72487/KryptoCampSolidity/blob/main/W6/simpleAMM/src/simpleAMMV4.sol) ## [etherscan上查看](https://goerli.etherscan.io/address/0x6D81EE8B003422Ee9d1255aceA42386eCBD20a60#code) goerli 0x6D81EE8B003422Ee9d1255aceA42386eCBD20a60 function swap tGD/ETH function swapTokenForETH ETH/tGD function addLiquidity 提供流動性,如果不成比例就swap成比例後再加 function removeLiquidity 根據使用者移除流動性 function _removeLiquidity V4從原本V3的external變成internal,要經過removeLiquidity檢查才能叫。 function getETHPrice() ETH/tGD 無滑點手續費的價格 function getERCPrice() tGD/ETH 無滑點手續費的價格 function getPricePredicttGD tGD/ETH算上滑點+手續費的輸出 function getPricePredictETH ETH/tGD 算上滑點+手續費的輸出 function calculateSlippage tGD價格滑點預計算 ## 功能請求 1. function 名稱優化 2. 合約工廠 3. 流動性提供限制、最小提供時間限制。 4. gas優化 5. ETH檔價格滑點 ``` solidity= // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; //goerli 0xae6B0f75b55fa4c90b2768e3157b7000241A41c5 merge 0x4a9C121080f6D9250Fc0143f41B595fD172E31bf // V1 0xf60440f93a677AB6968E1Fd10cf8a6cE61941131 // V2 0x8b175c421E9307F0365dd37bc32Dda5df95C4946 // V3 0x3ea585565c490232b0379C7D3C3A9fC3fA5C9c0C // V4 0x6D81EE8B003422Ee9d1255aceA42386eCBD20a60 merge 0x540d7E428D5207B30EE03F2551Cbb5751D3c7569 /** * @title ContractName * @dev ContractDescription * @custom:dev-run-script ../script/tAMM.js */ import "./erc20.sol"; import "@openzeppelin/contracts/utils/math/SafeMath.sol"; import "@openzeppelin/contracts/utils/math/Math.sol"; contract AMM { //using safeMath for uint256; IERC20 public immutable token; address public constant ETH_ADDRESS = address(0); uint256 public reserve0; uint256 public reserve1; uint256 public totalSupply; mapping(address => uint256) public balanceOf; constructor(address _token) { token = IERC20(_token); } 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(uint256 _amountIn) public payable returns (uint256 amountOut) { require(msg.value == _amountIn, "ETH amount mismatch"); uint256 amountInWithFee = (_amountIn * 997) / 1000; amountOut = (reserve1 * amountInWithFee) / (reserve0 + amountInWithFee); token.transfer(msg.sender, amountOut); _update(address(this).balance, token.balanceOf(address(this))); } // function _swapWithOutFee(uint256 _amountIn) internal payable returns (uint256 amountOut) { // require(msg.value == _amountIn, "ETH amount mismatch"); // amountOut = reserve1 / reserve0 ; // token.transfer(msg.sender, amountOut); // _update(address(this).balance, token.balanceOf(address(this))); // } function swapTokenForETH(uint256 _amountIn) public returns (uint256 amountOut) { uint256 amountInWithFee = (_amountIn * 997) / 1000; amountOut = (reserve0 * amountInWithFee) / (reserve1 + amountInWithFee); require(amountOut > 0, "Insufficient output amount"); token.transferFrom(msg.sender, address(this), _amountIn); payable(msg.sender).transfer(amountOut); _update(address(this).balance - amountOut, token.balanceOf(address(this))); } // function _swapTokenForETHWithOutFee(uint256 _amountIn) internal returns (uint256 amountOut) { // amountOut = reserve0 / reserve1; // require(amountOut > 0, "Insufficient output amount"); // token.transferFrom(msg.sender, address(this), _amountIn); // payable(msg.sender).transfer(amountOut); // _update(address(this).balance - amountOut, token.balanceOf(address(this))); // } function addLiquidity(uint256 _amount1) external payable returns (uint256 shares) { //token.approve(address(this), _amount1); token.transferFrom(msg.sender, address(this), _amount1); uint256 _amount0 = msg.value; if (reserve0 > 0 || reserve1 > 0) { if (reserve0 * _amount1 != reserve1 * _amount0) { // Calculate the required amount of tokens to make the ratio equal uint256 requiredAmount1 = (reserve1 * _amount0) / reserve0; // Swap the excess tokens for ETH without fee if (_amount1 > requiredAmount1) { uint256 excessAmount1 = _amount1 - requiredAmount1; _amount0 += swapTokenForETH(excessAmount1); // Swap the excess ETH for tokens without fee } else { uint256 excessAmount0 = _amount0 - requiredAmount1 * reserve0 / reserve1; _amount1 += swap(excessAmount0); } // Update the _amount1 to the required amount _amount1 = requiredAmount1; } } if (totalSupply == 0) { shares = _sqrt(_amount0 * _amount1); } else { shares = Math.min( (_amount0 * totalSupply) / reserve0, (_amount1 * totalSupply) / reserve1 ); } require(shares > 0, "shares = 0"); _mint(msg.sender, shares); _update(address(this).balance, token.balanceOf(address(this))); } function removeAllLiquidity(address _user) external returns (uint256 amount0, uint256 amount1) { return _removeLiquidity(_user,balanceOf[_user]); } function removeLiquidity(uint256 _shares) external returns (uint256 amount0, uint256 amount1) { require(_shares <= balanceOf[msg.sender], "Insufficient balance"); return _removeLiquidity(msg.sender, _shares); } function removeLiquidity(address _user, uint256 _shares) external returns (uint256 amount0, uint256 amount1) { require(_shares <= balanceOf[_user], "Insufficient balance"); return _removeLiquidity(_user, _shares); } function _removeLiquidity(address _user, uint256 _shares) internal returns (uint256 amount0, uint256 amount1) { amount0 = (_shares * address(this).balance) / totalSupply; amount1 = (_shares * token.balanceOf(address(this))) / totalSupply; require(amount0 > 0 && amount1 > 0, "amount0 or amount1 = 0"); _burn(_user, _shares); _update(address(this).balance - amount0, token.balanceOf(address(this)) - amount1); payable(_user).transfer(amount0); token.transfer(_user, amount1); } function my_shars() public view returns(uint256){ return balanceOf[msg.sender]; } function sharesOf(address _user) public view returns(uint256){ return balanceOf[_user]; } function _sqrt(uint y) internal pure returns (uint z) { if (y > 3) { z = y; uint 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; } function getETHPrice() public view returns (uint256) { require(reserve0 + reserve1 > 0, "Invalid reserves"); return (reserve1 * 10**18) / reserve0; } function getERCPrice() public view returns (uint256) { require(reserve0 + reserve1 > 0, "Invalid reserves"); return (reserve0 * 10**18) / reserve1; } function getPricePredicttGD(uint256 _amount) public view returns (uint256) { require(reserve0 + reserve1 > 0, "Invalid reserves"); uint256 amountInWithFee=(_amount * 997) / 1000; return (reserve1 * amountInWithFee) / (reserve0 + amountInWithFee); } function getPricePredictETH(uint256 _amount) public view returns (uint256) { require(reserve0 + reserve1 > 0, "Invalid reserves"); uint256 amountInWithFee=(_amount * 997) / 1000; return (reserve0 * amountInWithFee) / (reserve1 + amountInWithFee); } function calculateSlippage(uint256 amountIn) public view returns (uint256) { require(amountIn > 0, "Invalid input amount"); uint256 amountInWithFee = (amountIn * 997) / 1000; uint256 amountOut = (reserve1 * amountInWithFee) / (reserve0 + amountInWithFee); require(amountOut > 0, "Invalid output amount"); // Calculate the price without any slippage uint256 noSlippagePrice = getERCPrice(); // Calculate the expected output amount without any slippage uint256 expectedAmountOut = (amountIn * noSlippagePrice) / 10**18; // Calculate the slippage percentage uint256 slippage; if (amountOut > expectedAmountOut) { slippage = ((amountOut - expectedAmountOut) * 1000) / expectedAmountOut; } else { slippage = ((expectedAmountOut - amountOut) * 1000) / expectedAmountOut; } // Clamp the slippage value between 1 and 1000 (0.1% and 100%) slippage = _min(slippage, 1000); return slippage; } } ``` # 詢問chatGPT 可是這不會有時間上的問題嗎?後來的人搶前面的人的手續費? Uniswap 的設計確實可能導致所謂的「搶手續費」問題。換句話說,由於流動性提供者在任何時間都可以增加或移除流動性,因此可能出現後來的流動性提供者在短時間內獲得部分前期手續費收益的情況。 然而,實際上,這個問題的影響有限。原因有以下幾點: 競爭性:Uniswap 的競爭性將使得搶手續費行為的收益降低。一旦有人嘗試搶手續費,市場上其他流動性提供者可能也會跟隨進行類似操作,從而降低搶手續費的收益。 预测性:由於 Uniswap 是去中心化的,因此無法確保在短時間內會有大量的交易。因此,試圖搶手續費的流動性提供者需要承擔風險,因為在他們增加流動性的期間可能沒有足夠的交易產生。 成本:增加和移除流動性涉及到執行智能合約操作,這需要支付以太坊網絡的手續費(gas fee)。當網絡擁堵時,gas 費用可能會變得很高,這將抵消試圖搶手續費的收益。 綜合以上因素,試圖搶手續費的行為在實際應用中並不常見,並且收益有限。 # AMM V# 原本為了解決時間上講手續費問題但是不用解決了。 [AMM V# github](https://github.com/penguin72487/KryptoCampSolidity/blob/main/W6/simpleAMM/src/simpleAMM.sol) function swap tGD/ETH function swapTokenForETH ETH/tGD function removeLiquidity 根據使用者移除流動性 V4 還未部屬 function getETHPrice() ETH/tGD的價格 function getERCPrice() tGD/ETH ### 以下是V5更新的部分 主要圍繞在分配0.3%的手續費100%給流動性提提供者。 mapping(address => uint256) public balanceOf; 就是原本的balanceOf,紀錄初始流動性提供的share int256[] public diff_LP是一個差分的陣列, 拿使用者地址在au_LPindex找到index,從diff_LP[0]+...+diff_LP[index]就是流動性的一個加權 diff_LP[0]+...+diff_LP.length-2[=diff_LP.length-2]=sumLP totalSupply =所有使用者的balanceOf加起來+sumLP 每次交易所有人base都會加一,只要差分第一個加一,全部人都加一,可是合約內要算的話複雜度是n(流動性提供者的數量) 只看不用gas ```solidity= // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; //goerli 0xae6B0f75b55fa4c90b2768e3157b7000241A41c5 // V1 0xf60440f93a677AB6968E1Fd10cf8a6cE61941131 // V2 0x8b175c421E9307F0365dd37bc32Dda5df95C4946 // V3 0x3ea585565c490232b0379C7D3C3A9fC3fA5C9c0C // v4 import "./erc20.sol"; contract AMM { IERC20 public immutable token; address public constant ETH_ADDRESS = address(0); uint256 public reserve0; uint256 public reserve1; uint256 public totalSupply; // total LP mapping(address => uint256) public balanceOf; mapping (address=>uint256) public au_LPindex; mapping (uint256=>address) public ua_LPaddress; int256[] public diff_LP;// differences uint256 public sumLP;// sigima diff_LP constructor(address _token) { token = IERC20(_token); diff_LP.push(0); } function _mint(address _to, uint256 _amount) private { balanceOf[_to] += _amount; totalSupply += _amount; diff_LP.push(0); } function _burn(address _from, uint256 _amount) private { balanceOf[_from] -= _amount/_shareBaseOf(_from); totalSupply -= _amount; _removeLiquidityUser(_from); } function _shareBaseOf(address _user) internal view returns (uint256) { uint256 base = 0; for (uint256 i = 0; i < au_LPindex[_user]; i++) { base += uint256(diff_LP[i]); } return base; } function _removeLiquidityUser(address _user) internal { require(balanceOf[_user]==0,"balance not empty!"); uint256 index = au_LPindex[_user]; uint256 last = diff_LP.length-2; uint256 user_shareBase=_shareBaseOf(_user); sumLP-=user_shareBase; (index,au_LPindex[ua_LPaddress[last]]) = swap(index,au_LPindex[ua_LPaddress[last]]); (ua_LPaddress[index],ua_LPaddress[au_LPindex[ua_LPaddress[last]]]) = swap(ua_LPaddress[index],ua_LPaddress[au_LPindex[ua_LPaddress[last]]]); swap(diff_LP[diff_LP.length-1],diff_LP[last]); diff_LP.pop(); diff_LP[index+1]-=int256(user_shareBase); diff_LP[index]=int256(sumLP-user_shareBase); } function swap(uint256 a,uint256 b) public pure returns (uint256,uint256) { uint256 temp = a; a = b; b = temp; return (a,b); } function swap(int a,int b) public pure returns (int,int) { int temp = a; a = b; b = temp; return (a,b); } function swap(address a,address b) public pure returns (address,address) { address temp = a; a = b; b = temp; return (a,b); } function _update(uint256 _reserve0, uint256 _reserve1) private { reserve0 = _reserve0; reserve1 = _reserve1; } function my_Share() public view returns (uint256) { uint256 base = _shareBaseOf(msg.sender); return base * balanceOf[msg.sender]; } function all_Share() public view returns (uint256) { return totalSupply; } function share_Of(address _user) public view returns (uint256) { uint256 base = _shareBaseOf(_user); return base * balanceOf[_user]; } function all_addShare() internal { // totalSupply+=diff_LP.length-1; sumLP+=diff_LP.length-1; diff_LP[0]+=1; diff_LP[diff_LP.length-1]-=1; } function swap(uint256 _amountIn) public payable returns (uint256 amountOut) { require(msg.value == _amountIn, "ETH amount mismatch"); uint256 amountInWithFee = (_amountIn * 997) / 1000; amountOut = (reserve1 * amountInWithFee) / (reserve0 + amountInWithFee); all_addShare(); token.transfer(msg.sender, amountOut); _update(address(this).balance, token.balanceOf(address(this))); } function swapTokenForETH(uint256 _amountIn) public returns (uint256 amountOut) { uint256 amountInWithFee = (_amountIn * 997) / 1000; amountOut = (reserve0 * amountInWithFee) / (reserve1 + amountInWithFee); require(amountOut > 0, "Insufficient output amount"); token.transferFrom(msg.sender, address(this), _amountIn); payable(msg.sender).transfer(amountOut); _update(address(this).balance - amountOut, token.balanceOf(address(this))); } function addLiquidity(uint256 _amount1) external payable returns (uint256 shares) { token.transferFrom(msg.sender, address(this), _amount1); uint256 _amount0 = msg.value; if (reserve0 + reserve1 > 0) { if (reserve0 * _amount1 != reserve1 * _amount0) { uint256 targetAmount0 = (reserve1 * _amount1) / reserve1; uint256 targetAmount1 = (reserve0 * _amount0) / reserve0; if (_amount0 < targetAmount0) { uint256 requiredTokenAmount = targetAmount1 - _amount1; uint256 amountOut = swapTokenForETH(requiredTokenAmount); _amount0 += amountOut; } else { uint256 requiredEthAmount = targetAmount0 - _amount0; uint256 amountOut = swap(requiredEthAmount); _amount1 += amountOut; } } } if (totalSupply == 0) { shares = _sqrt(_amount0 * _amount1); } else { shares = _min( (_amount0 * totalSupply) / reserve0, (_amount1 * totalSupply) / reserve1 ); } require(shares > 0, "shares = 0"); _mint(msg.sender, shares); _update(address(this).balance, token.balanceOf(address(this))); } function removeLiquidity(uint256 _share) external { uint256 share = share_Of(msg.sender); require(share>=_share,"shares not enough"); _removeLiquidity(_share); } function _removeLiquidity(uint256 _shares) internal returns (uint256 amount0, uint256 amount1) { amount0 = (_shares * address(this).balance) / totalSupply; amount1 = (_shares * token.balanceOf(address(this))) / totalSupply; require(amount0 > 0 && amount1 > 0, "amount0 or amount1 = 0"); _burn(msg.sender, _shares); _update(address(this).balance - amount0, token.balanceOf(address(this)) - amount1); payable(msg.sender).transfer(amount0); token.transfer(msg.sender, amount1); } function _sqrt(uint256 x) public pure returns (uint256) { if (x == 0) return 0; uint256 z = (x + 1) / 2; uint256 y = x; while (z < y) { y = z; z = (x / z + z) / 2; } return y; } function _min(uint256 x, uint256 y) private pure returns (uint256) { return x <= y ? x : y; } function getETHPrice() public view returns (uint256) { require(reserve0 + reserve1 > 0, "Invalid reserves"); return (reserve1 * 1 ether) / reserve0; } function getERCPrice() public view returns (uint256) { require(reserve0 + reserve1 > 0, "Invalid reserves"); return (reserve0 * 1 ether) / reserve1; } } ``` # 致謝 感謝chatGPT4 不眠不休幫我debug