第二週作業 --- 基礎: 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 完成測試確認成功串接合約 --- ## 基礎​1 **AMM Address :** [0xD74d0139123cC4795Fb7c0Fa8c051a68aFD34B7C](https://sepolia.etherscan.io/address/0xd74d0139123cc4795fb7c0fa8c051a68afd34b7c) 引用 [DeFi 課程 (Week2)](https://hackmd.io/@KryptoCampDev/Web3-DeFi#%E4%BD%9C%E6%A5%AD) contract AMM,並佈署在Sepolia Testnet --- ***token0 address (fake DAI):*** [0xFF34B3d4Aee8ddCd6F9AFFFB6Fe49bD371b8a357](https://sepolia.etherscan.io/address/0xFF34B3d4Aee8ddCd6F9AFFFB6Fe49bD371b8a357) ***token1 address (fake USDC):*** [0x94a9D9AC8a22534E3FaCa9F4e7F2E2cf85d5E4C8](https://sepolia.etherscan.io/address/0x94a9D9AC8a22534E3FaCa9F4e7F2E2cf85d5E4C8) ***approve token0 for AMM Address to add liquidity tx :*** [0xc96b553d95f938714058dc493f685a6faede8a94c573391931986871b516085b](https://sepolia.etherscan.io/tx/0xc96b553d95f938714058dc493f685a6faede8a94c573391931986871b516085b) ***approve token1 for AMM Address to add liquidity tx :*** [0x17bde7ee0eddcbc6a7b3acdd5b14852c502eac50bbbf91c193911ae423d58b71](https://sepolia.etherscan.io/tx/0x17bde7ee0eddcbc6a7b3acdd5b14852c502eac50bbbf91c193911ae423d58b71) --- ***addLiquidity*** 1. 將token0,1 傳給AMM合約 2. 完成1. 前先檢查傳入數量 3. 判斷此次為首次添加之流動性與否,用以計算此次添加之流動性份額能佔該流動性池之比例(shares) 4. 檢查份額(shares)數量是否大於0,並更新該呼叫者之shares數值 5. 更新此流動性池隻數量 ***addLiquidity tx :*** [0x3f8e0f0fb8daa188035448a4443cb6c7f697b56a592adeb41523324bf12ddfd5 ](https://sepolia.etherscan.io/tx/0x3f8e0f0fb8daa188035448a4443cb6c7f697b56a592adeb41523324bf12ddfd5) ```solidity 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) { shares = _sqrt(_amount0 * _amount1); } else { 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))); } ``` ***swap*** 1. 判斷此次交換之token address及數量是否正確且大於0 2. 查看最近一次紀錄之token0,1數量並用於接下來之計算 3. 計算並扣除手續費 4. 發送此次要換取之token 5. 更新此流動性池之數量 ***swap tx :*** [0xaa5c7a2d31b8dbcf77579485198e7f249faaeb75ec0b0f12284868d7ec96c8a4 ](https://sepolia.etherscan.io/tx/0xaa5c7a2d31b8dbcf77579485198e7f249faaeb75ec0b0f12284868d7ec96c8a4) ```solidity 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"); 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); uint256 amountInWithFee = (_amountIn * 997) / 1000; amountOut = (reserveOut * amountInWithFee) / (reserveIn + amountInWithFee); tokenOut.transfer(msg.sender, amountOut); _update(token0.balanceOf(address(this)), token1.balanceOf(address(this))); } ``` ***removeLiquidity*** 1. 依照輸入之份額計算能取回之token0,1數量(此處可以加入檢查) 2. 更新份額並傳送計算之token0,1數量 ***removeLiquidity tx :*** [0xcdd1a47f7eee9b053ac40c987324aa6ce40c13a0315f2b0486212f93a29372da ](https://sepolia.etherscan.io/tx/0xcdd1a47f7eee9b053ac40c987324aa6ce40c13a0315f2b0486212f93a29372da) ```solidity 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; } } ``` --- ## 基礎​2 Uniswap v3 * Uniswap用戶使用的是自動做市商協議 (AMM) * 其為恆定乘積做市商(Constant Product Market Maker) * 允許用戶在不需要傳統訂單簿或中心化中介的情況下交易加密貨幣 * 享有高度去中心化及免審查的特性 * 並鼓勵流動性提供者投入加密貨幣和建立流動性池來賺取交易手續費 UniversalRouter : [0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD](https://sepolia.etherscan.io/address/0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD#code) UniswapV3Pool : [0xA470a353577901AA8cDCb828BB616ef41d58B88a](https://sepolia.etherscan.io/address/0xa470a353577901aa8cdcb828bb616ef41d58b88a#code) Permit2 : [0x000000000022D473030F116dDEE9F6B43aC78BA3](https://sepolia.etherscan.io/address/0x000000000022D473030F116dDEE9F6B43aC78BA3#code) **Uniswap v3 Swap Link to ETH tx :** *Approve tx :* [0xd344e1f2a304f4a8e7d0cf08ea01432b177a082a8ec87cba3eb244d1ecc64323](https://sepolia.etherscan.io/tx/0xd344e1f2a304f4a8e7d0cf08ea01432b177a082a8ec87cba3eb244d1ecc64323) *Execute tx :* [0xab82babd4a24911b24b1cc0047a11f01b292a1f308dc1f8c55ce5552af2d9fd6](https://sepolia.etherscan.io/tx/0xab82babd4a24911b24b1cc0047a11f01b292a1f308dc1f8c55ce5552af2d9fd6) 1. Execute tx中合約UniswapV3Pool首先Transfer WETH 給合約UniversalRouter 2. Approve tx中首先更改了Link中的allowance用以授權給合約Permit2 3. 轉移了發起Swap之地址之Link token 4. (Swap)更新紀錄交易池中的資訊的struct Slot0(看不懂QQ) 5. Withdrawal後到UniversalRouter後給發起Swap之地址(ETH餘額更新) 總結: 1. 變動了發起Swap地址之ETH餘額 2. 各更新兩次Link,WETH中的相關allowance 3. UniswapV3Pool struct Slot0(新價格及包含手旭費收入後之新數量) --- Aave v3 * Aave是一個去中心化加密貨幣借貸系統 * 容許用戶在系統中存入或借出加密貨幣(超額抵押) * 存款人透過為市場提供流動性,來賺取被動收入 * 借貸人則能夠以抵押方式借款。 * 其憑證aTokens亦可倚賴其他協議,從而實現更高的資金利用率 *實在太多了,只貼tx上的* *Aave : WrappedTokenGatewayV3 :* [0xD322A49006FC828F9B5B37Ab215F99B4E5caB19C](https://etherscan.io/address/0xd322a49006fc828f9b5b37ab215f99b4e5cab19c#code) **Aave v3 Deposit ETH tx :** *Deposit tx :* [0x1b9888a0d9dc714f7e856008db9c144ac1b9a96b5faa0de7a8dbfb15f21fd42c](https://sepolia.etherscan.io/tx/0x1b9888a0d9dc714f7e856008db9c144ac1b9a96b5faa0de7a8dbfb15f21fd42c) LendingPool.sol ``` function deposit( address asset, uint256 amount, address onBehalfOf, uint16 referralCode ) external override whenNotPaused { DataTypes.ReserveData storage reserve = _reserves[asset]; ValidationLogic.validateDeposit(reserve, amount); address aToken = reserve.aTokenAddress; reserve.updateState(); reserve.updateInterestRates(asset, aToken, amount, 0); IERC20(asset).safeTransferFrom(msg.sender, aToken, amount); bool isFirstDeposit = IAToken(aToken).mint(onBehalfOf, amount, reserve.liquidityIndex); if (isFirstDeposit) { _usersConfig[onBehalfOf].setUsingAsCollateral(reserve.id, true); emit ReserveUsedAsCollateralEnabled(asset, onBehalfOf); } emit Deposit(asset, msg.sender, onBehalfOf, amount, referralCode); } ``` 總結: 1. 變動了發起Swap地址之ETH餘額 2. 各更新WETH及aEthWETH(包含mint增加supply但沒有更動allowance) 3. 初始化aave交易資料(ReserveData _reserves, UserConfigurationMap _usersConfig) --- ## 進階​1 現在突然發現好像沒看懂題目... 寫成用ethers.js了... 我之後再修正它 程式本身也還有很多要修正 先以可以運作為主 以下為以此送出之交易 交互的合約為在Polygon的Mumbai Testnet的**AAVE V2合約** 1. WMATIC deposit() tx : [0x85a475b2b77dfb2af6f00376b8792d200d79e88b032c3dad03cd1c3ac9787e49](https://mumbai.polygonscan.com/tx/0x85a475b2b77dfb2af6f00376b8792d200d79e88b032c3dad03cd1c3ac9787e49) 2. Approve WMATIC to AAVE approve(address guy, uint256 wad) : [0x4f3ebe012ad5de1b7e85df3fc8438464814743337886e9449c460bd9c8528ce9](https://mumbai.polygonscan.com/tx/0x4f3ebe012ad5de1b7e85df3fc8438464814743337886e9449c460bd9c8528ce9) 3. AAVE deposit(address asset, uint256 amount, address onBehalfOf, uint16 referralCode) : [0xf46d1a7e38db7dfaed401423f1ac5a671e171a1510f973ad76bbd7c97e37a98e](https://mumbai.polygonscan.com/tx/0xf46d1a7e38db7dfaed401423f1ac5a671e171a1510f973ad76bbd7c97e37a98e) ```javascript //getWmatic.js const fs = require("fs"); const path = require("path"); const { ethers, getNamedAccounts, network } = require("hardhat"); const { networkConfig } = require("../helper-hardhat-config"); const AMOUNT = ethers.parseEther("0.01"); const getTheIWmaticAbi = () => { try { const dir = path.resolve( __dirname, "../artifacts/contracts/interfaces/WMATIC.sol/WMATIC.json" ); const file = fs.readFileSync(dir, "utf8"); const json = JSON.parse(file); return json.abi; } catch (e) { console.log(`e`, e); } }; async function getWmatic() { const { deployer } = await getNamedAccounts(); const abi = getTheIWmaticAbi(); const signer = await ethers.provider.getSigner(); const iWmatic = new ethers.Contract( "0x9c3C9283D3e44854697Cd22D3Faa240Cfb032889", abi, signer ); const wethOriBalance = await iWmatic.balanceOf(deployer); console.log(`Ori ${wethOriBalance.toString()} WMATIC`); const txResponse = await iWmatic.deposit({ value: AMOUNT, }); await txResponse.wait(1); const wmaticBalance = await iWmatic.balanceOf(deployer); console.log(`Got ${wmaticBalance.toString()} WMATIC`); } module.exports = { getWmatic, AMOUNT }; ``` ```javascript //aaveBorrow.js const fs = require("fs"); const path = require("path"); const { ethers, getNamedAccounts, network } = require("hardhat"); const { getWmatic, AMOUNT } = require("./getWmatic.js"); const { networkConfig } = require("../helper-hardhat-config"); const getTheLendingPoolAddressesProviderAbi = () => { try { const dir = path.resolve( __dirname, "../artifacts/contracts/interfaces/ILendingPoolAddressesProvider.sol/ILendingPoolAddressesProvider.json" ); const file = fs.readFileSync(dir, "utf8"); const json = JSON.parse(file); return json.abi; } catch (e) { console.log(`e`, e); } }; const getTheLendingPoolAbi = () => { try { const dir = path.resolve( __dirname, "../artifacts/contracts/interfaces/ILendingPool.sol/ILendingPool.json" ); const file = fs.readFileSync(dir, "utf8"); const json = JSON.parse(file); return json.abi; } catch (e) { console.log(`e`, e); } }; const getTheERC20Abi = () => { try { const dir = path.resolve( __dirname, "../artifacts/contracts/interfaces/IERC20.sol/IERC20.json" ); const file = fs.readFileSync(dir, "utf8"); const json = JSON.parse(file); return json.abi; } catch (e) { console.log(`e`, e); } }; async function main() { await getWmatic(); const { deployer } = await getNamedAccounts(); const lendingPool = await getLendingPool(deployer); await approveErc20( "0x9c3C9283D3e44854697Cd22D3Faa240Cfb032889", "0x9198F13B08E299d85E096929fA9781A1E3d5d827", AMOUNT, deployer ); console.log("Depositing WMATIC..."); await lendingPool.deposit( "0x9c3C9283D3e44854697Cd22D3Faa240Cfb032889", AMOUNT, deployer, 0 ); console.log("Desposited!"); } async function approveErc20(erc20Address, spenderAddress, amount, signer) { const abi = getTheERC20Abi(); const account = await ethers.provider.getSigner(); const erc20Token = new ethers.Contract( "0x9c3C9283D3e44854697Cd22D3Faa240Cfb032889", abi, account ); txResponse = await erc20Token.approve(spenderAddress, amount); await txResponse.wait(1); console.log("Approved!"); } async function getLendingPool(account) { const abi = getTheLendingPoolAddressesProviderAbi(); const signer = await ethers.provider.getSigner(); const lendingPoolAddressesProvider = new ethers.Contract( "0x178113104fEcbcD7fF8669a0150721e231F0FD4B", abi, signer ); const lendingPoolAddress = await lendingPoolAddressesProvider.getLendingPool(); console.log(`lendingPoolAddress : ${lendingPoolAddress}`); const _abi = getTheLendingPoolAbi(); const _signer = await ethers.provider.getSigner(); const lendingPool = new ethers.Contract(lendingPoolAddress, _abi, _signer); return lendingPool; } main() .then(() => process.exit(0)) .catch((error) => { console.error(error); process.exit(1); }); ``` --- ## ***# Refactor*** ```solidity ```