競投這回事,記蕭雲「我們的獅子山」NFT 一事
文:薯伯伯
最近在區塊鏈的世界上,有兩個題目較多人討論,一是儲存資料及影片平台,這些平台在極權下的發展特別蓬勃。另一個話題是「非同質化代幣」,此詞的中文及英文全名都翹口難讀,直接叫 NFT(non-fungible token)更為方便。
所謂「同質化」,即是交易的時候,不理會物品之個性。例如我用匯豐銀行的五百元,換你渣打銀行的五百元,兩者沒有分別,就叫做「同質化」。但如果五百元鈔票的號碼含有 888888, 那這張鈔票跟其他五百元鈔票的價值就不同,這樣就叫「非同質化」。
如果有人仍然不明白,認為區塊鏈上的 NFT 圖像,與任意下載的相片沒有任何分別,那就再說一個例子。電影《十年》導演團隊之一的郭臻所製作的《夜更》,當中有一幕,女學生坐的士,把身上僅有摺疊成心形的五百元紙幣支付車資,女孩看著五百元鈔票依依不捨,正是因為這個摺疊成心形的五百元,看來是承載著一段故事,記述著一個人的故事。對她來說,這張心形鈔票的價值,與一張普通的五百元鈔票就不是「同質化」。
因為比特幣的吸引力,發覺身邊不少人誤以為區塊鏈上只能存放金錢相關的資產,其實同樣的鏈,可以存放影像,記錄照片,反正都是數據。把相片放到區塊鏈,跟普通下載的 jpg,看起來都是一樣,但在區塊鏈的 NFT,能看到其過去及現在的擁有者,資料不可消毀,不可竄改,這就是其價值所在。競投 NFT 這回事,大概總帶著愛,既是對創作的支持,也是對其理念的支持。
最近我自己也參與了人生第一個競投 NFT 的活動,LikeCoin 團隊在 2021 年 5 月,協助公民寫作人蕭雲(或朝雲),把他拍攝的〈Our Lion Rock 我們的獅子山〉鑄成 NFT 拍賣。回想當初開始接觸區塊鏈,正是 LikeCoin 團隊為我點了盞燈,照亮了路。而蕭雲則是我最敬佩的社運寫作人,其文字之細膩,其為人之真誠,總是打動我。這次拍賣由蕭雲發起,收益捐給支援香港人的組織。蕭雲自己則說:「希望能夠幫助到香港與同道。」
拍賣的平台為 Opensea.io,先要連結密碼貨幣錢包,這次我使用的是 Metamask Crypto Wallet,但在電腦的 Chrome 操作時略有問題,似乎用手機版較好。
Metamask 錢包裡沒有放錢,所以要先用另一個交易平台(這裡省去平台的名字),把美元穩定幣轉成以太幣,把以太幣傳送到 Metamask,再把以太幣轉換成 wETH(wrapped ETH,即「包裝過的以太幣」),符合 ERC-20 標準,之後才能參與拍賣。
我首次競投的價錢為 0.1 以太幣(約 340 元美金),及後與不同的人競價,最後投至 0.4 個以太幣(約 1360 元美金)。後來有朋友私底下聯絡我,想與我合力競投,所以我把原來的競投名字,由 pazukong 改為 pazukong_etal,et al 即 et alia(拉丁文:及他人的意思)。
在競價結束前半小時,我和朋友的出價去到 1 個以太幣,約是 3400 元美金,以為勝券在握,便去睡覺,發了一個奇怪的夢,夢見有人出了更高的價錢。醒來之後,才發現原來在拍賣結束前一刻,真的有人出了更高的價錢,以 1.05 個以太幣(即 3570 元美金,27668 元港幣),投得了蕭雲的作品。
競投雖然失敗,但我沒有失落。這次拍賣的收益,本來就是用來支援香港人的組織,任何人拍得,也是值得高興之事。這位有心人名叫 Thomas Lee 湯瑪斯,是一位香港、台灣兩邊走的創作人,在此恭喜他!我本來打算用來競投的費用,則會用不同方式捐給其他機構。
無法確定這次是不是香港第一個 NFT 競投,不敢把話說得太盡,但這個肯定香港其中一個最早期公開競投的 NFT 之一,留下幾點使用的筆記:
一,對於從來沒有接觸過密碼貨幣世界的人,從零開始去參與競投,較為困難。這就收到不少朋友向我查詢如何參與競投,但我自己也只是見步行步,難寫逐步教學。
二,從交易所把以太幣轉至非託管錢包(例如 Metamask),這個過程很直接。
三,但是後來要先把以太幣轉換成 wETH(包裝的以太幣),以乎合 ERC-20 的要求,這個過程雖然花些礦工費(gas fee)。
四,首次競投,亦要支付礦工費。
五,如果稍後取消競投,同樣要支付礦工費。
六,如果只是增加競投金額,第二次開始就不用再支付礦工費。
七,但競投的金額,要超過上一次金額的 5%。如果不是太確定需要增加多少競投金額,最好是在錢包裡預多一點加密幣並一次過做好包裝(wrapped),以免把幾個過程拆散來做,要消耗額外的礦工費。
八,其他創作人如果想把作品鑄成 NFT 拍賣,應把礦工費考慮其中。如果礦工費高過拍賣品所得的價值,那就要自己衡量是否值得(因為 NFT 有一些難以用金錢來衡量的價值,所以即使 gas fee 高過競投金額,也有可能是值得,要自己衡量了)。
至於 NFT 競投能否為香港其他藝術人帶出一條新血路,我無意作任何預測。只能說,慶幸自己能參與這次競投,雖然最終沒有投得,但總算是感受一下這個自己未知的領域。
相關連結:
蕭雲(朝雲)的 NFT
https://opensea.io/assets/0x495f947276749ce646f68ac8c248420045cb7b5e/1167538013598567592854727837872916410629077686003238157943995667380086767617
LF2 念念不忘 NFT 必有迴響(文:高重建)
https://ckxpress.com/lf2-nft/
競得蕭雲作品的 ThomasLee,其 Instagram
https://www.instagram.com/thomaslylee/
* * *
🔑 【資訊保安文章整理,超務實長清單】 https://www.patreon.com/posts/46192115
▶️ 請訂閱 Patreon 頻道,支持不受干預的獨立創作及評論 | www.patreon.com/pazu
同時也有3部Youtube影片,追蹤數超過1萬的網紅DanOnTheMove,也在其Youtube影片中提到,#粵語 #F1 #DanOnTheMove 角田反 Pierre 檯❓ ??/??? Patrons 獨家《DANFLIX》睇 Netflix:F1 極速求生 第三季導賞團 ? 第九及十集《火裡逃生》➕《三哥之戰》?https://www.patreon.com/posts/48991910 ?...
「gas費查詢」的推薦目錄:
- 關於gas費查詢 在 Pazu 薯伯伯 Facebook 的最佳解答
- 關於gas費查詢 在 Taipei Ethereum Meetup Facebook 的最佳貼文
- 關於gas費查詢 在 Taipei Ethereum Meetup Facebook 的最佳解答
- 關於gas費查詢 在 DanOnTheMove Youtube 的最佳解答
- 關於gas費查詢 在 DanOnTheMove Youtube 的最讚貼文
- 關於gas費查詢 在 EVITAvsEVITA Youtube 的最佳貼文
- 關於gas費查詢 在 4个工具教你实时查看GAS Fee!|以太坊转账太贵?如何在小 ... 的評價
- 關於gas費查詢 在 eth手續費查詢-在PTT/MOBILE01/Dcard上的毛小孩推薦資訊整理 的評價
- 關於gas費查詢 在 eth手續費查詢-在PTT/MOBILE01/Dcard上的毛小孩推薦資訊整理 的評價
gas費查詢 在 Taipei Ethereum Meetup Facebook 的最佳貼文
📜 [專欄新文章] 可升級合約介紹 - 鑽石合約(EIP-2535 Diamond standard)
✍️ Kimi Wu
📥 歡迎投稿: https://medium.com/taipei-ethereum-meetup #徵技術分享文 #使用心得 #教學文 #medium
Photo by Evie S. on Unsplash
前言
可升級合約簡單來說是透過 proxy contract(代理合約)來達成,藉由代理合約去呼叫欲執行的合約,若要升級,則把代理合約中的指向的地址換為新的合約地址即可。而執行的方式則是透過 delegateCall,但 delegateCall 不會更動目標合約的狀態。所以要怎麼處理變數,就是一門學問了。
舉例來說,contract B 有個變數 uint256 x,初始值為 0, 而 function setX(uint256),可以改變 x 的值。proxy contract A 使用 delegatecall 呼叫 contract B 的 setX(10),交易結束後,contract B中的 x 依然還是 0。
OpenZeppelin 提出了三種實作方式,可以做到可升級合約,細節可參考 Proxy Patterns,而最終的實作選用了 Unstructured Storage的這個方式,這種方式對於開發較友善,開發時不需特別處理 state variables(不過升級時就需要特別注意了)。而這篇主要是介紹 Diamond standard,OpenZeppelin 的可升級合約就不多做介紹。
USDC V2 : Upgrading a multi-billion dollar ERC-20 token 詳細地介紹代理合約跟變數儲存之間的關係,不了解升級合約的原理,建議先看看。
鑽石合約
名詞介紹
diamond:合約本體,是一個代理合約,無商業邏輯
facet:延伸的合約(實際商業邏輯實作的合約)
loupe:也是一個 facet,負責查詢的功能。可查詢此 diamond所提供的 facet與facet所提供的函式
diamondCut:一組函式,用來管理(增加/取代/減少)此 diamond合約所支援的功能
Loupe
直接來看 loupe的介面,從宣告就能很清楚暸解 diamond合約的實作方式,loupe宣告了一個結構 Facet,Facet結構包含一個地址及 function selector 陣列,所以我們只需要記錄一個 Facet陣列就可以得知這個 diamond 合約有多少個延伸合約及所支援的功能(loupe只定義結構,而實際變數是存在diamon合約中的)。也就是 diamond合約中只記錄延伸合約的地址及其支援的 function selectors,及少數 diamond合約的管理邏輯,並無商業邏輯,因此可以外掛非常非常多的合約上去(就像一個Hub),也就可以突破一個合約只有24K的限制。
// A loupe is a small magnifying glass used to look at diamonds.interface IDiamondLoupe { struct Facet { address facetAddress; bytes4[] functionSelectors; } function facets() external view returns (Facet[] memory facets_); function facetFunctionSelectors(address _facet) external view returns (bytes4[] memory facetFunctionSelectors_); function facetAddresses() external view returns (address[] memory facetAddresses_); function facetAddress(bytes4 _functionSelector) external view returns (address facetAddress_);}
DiamondCut
至於 facet在 diamond合約上的註冊或是修改,就由 diamondCut負責,從以下程式碼可以清楚瞭解其功能(EIP中有規範,每次改變都需要發送DiamondCut事件)
interface IDiamondCut { enum FacetCutAction {Add, Replace, Remove} // Add=0, Replace=1, Remove=2 struct FacetCut { address facetAddress; FacetCutAction action; bytes4[] functionSelectors; } function diamondCut( FacetCut[] calldata _diamondCut, address _init, bytes calldata _calldata ) external; event DiamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata);}
Diamond合約
接下來就是最核心的部分 — diamond本體合約。以下是官方的範例,方法上跟 OpenZeppelin 一樣使用 fallback 函式跟 delegateCall 。
呼叫合約所不支援的函式,就會去執行 fallback 函式,fallback 函式中再透過 delegateCall 呼叫 facet 合約相對應的函式
fallback() external payable { address facet = selectorTofacet[msg.sig]; require(facet != address(0)); // Execute external function from facet using delegatecall and return any value. assembly { calldatacopy(0, 0, calldatasize()) let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0) returndatacopy(0, 0, returndatasize()) switch result case 0 {revert(0, returndatasize())} default {return (0, returndatasize())} }}
主要的差異在於變數的處理,OpenZepplin 是針對單一合約設計的代理合約(也就是每個合約都有自己的代理合約),所以無法處理單一代理合約儲存多個合約的變數(state variables)的狀況(後有圖例)。先由官方的範例程式來了解是怎麼處理變數的
在官方的範例中,都是以更改合約 owner 為例子
首先看到 DimaondStorage這個結構,結構中的前面三個變數都是在維持 diamond合約的運作(同上面loupe的範例),最後一個變數 contractOwner就是我們商業邏輯中所需的變數。
接著看到 function diamondStorage(),取變數的方式就跟OpenZeppelin 儲存特定變數方式一樣(EIP-1967),是把變數存到一個遠方不會跟其他變數碰撞到的位置,在這裡就是從 DIMOND_STORAGE_POSITION 這個 storage slot 讀取。
在實作上就可以有 LibDiamond1 ,宣告DIMOND_STORAGE_POSITION1=keccak256("diamond.standard.diamond.storage1") ,負責處理另一組的變數。藉由這種方式讓每個 facet合約有屬於自己合約的變數, facet合約間就不會互相影響。而最下方的 setContractOwner 是實際使用的範例。
library LibDiamond {
bytes32 constant DIAMOND_STORAGE_POSITION = keccak256("diamond.standard.diamond.storage");
struct FacetAddressAndSelectorPosition { address facetAddress; uint16 selectorPosition; }
struct DiamondStorage { mapping(bytes4 => FacetAddressAndSelectorPosition) facetAddressAndSelectorPosition; bytes4[] selectors; mapping(bytes4 => bool) supportedInterfaces; // owner of the contract address contractOwner; }
function diamondStorage() internal pure returns (DiamondStorage storage ds) { bytes32 position = DIAMOND_STORAGE_POSITION; assembly { ds.slot := position } }
function setContractOwner(address _newOwner) internal { DiamondStorage storage ds = diamondStorage(); address previousOwner = ds.contractOwner; ds.contractOwner = _newOwner; emit OwnershipTransferred(previousOwner, _newOwner); }
每個 library 處理了一組或多組變數的存取, facet 合約透過 library 對變數做操作。也就是把變數存在diamond主體合約,延伸的 facet合約只處理邏輯,是透過 library 去操作變數。
下面圖中清楚地解釋了 facet合約,function selectors 與變數之間的關係,從最左上這邊有個 facets 的 map,紀錄了哪個 selector 在哪個合約中,例如func1, func2是 FacetA的函式。左下角宣告了變數,每組變數的存取如同上述 library 的方式處理。
https://eips.ethereum.org/EIPS/eip-2535#diagrams
在 diamond的設計中,每個 facet合約都是獨立的,因此可以重複使用(跟library 的概念一樣)
https://eips.ethereum.org/EIPS/eip-2535#diagrams
小結
diamond合約使用不同的設計來達成合約的可升級性,藉由這種Hub方式可隨時擴充/移除功能,讓合約不再受限於24KB的限制,此外充分的模組化,讓每次升級的範圍可以很小。最後,因為跟library一樣只處理邏輯,並無狀態儲存,所以可以重複被不同的diamond合約所使用。
雖然又不少好處,也是有些缺點。首先,術語名詞太多,facet, diamondCut, loupe等等(其實還有好幾個,不過沒有介紹到那些部分,所以沒有寫出來)。開發上不直覺,把變數跟邏輯拆開,若要再加上合約之間的繼承關係,容易搞混,不易維護。最後,gas的花費,在函式的讀取、呼叫,變數的存取、傳遞都會有不少的額外支出。Trail of Bits 專欄中有點出更多的缺陷 Good idea, bad design: How the Diamond standard falls short,不過作者也有反擊 Addressing Josselin Feist’s Concern’s of EIP-2535 Diamond Standard,有興趣的讀者可以自行看看、比較。
為了模組化及彈性,diamond合約在設計上有點太複雜(over engineering),會造成可讀性越差(這點也是Vyper誕生的原因之一),而可讀性越差就越容易產生bug、也越不容易抓到bug,而在defi專案中,一個小小的bug通常代表著大筆金額的損失 😱😱😱。
雖然如此,筆者還是覺得很酷,有些設計的思維仍然可以使用在自己的專案
ref:
EIP 2535
Diamond 實作
Addressing Josselin Feist’s Concern’s of EIP-2535 Diamond Standard
OpenZeppelin upgradeable contract
可升級合約介紹 - 鑽石合約(EIP-2535 Diamond standard) was originally published in Taipei Ethereum Meetup on Medium, where people are continuing the conversation by highlighting and responding to this story.
👏 歡迎轉載分享鼓掌
gas費查詢 在 Taipei Ethereum Meetup Facebook 的最佳解答
📜 [專欄新文章] Uniswap v2 實作 : 從創建交易對到Ether 換 Dai 投入 Compound
✍️ 田少谷 Shao
📥 歡迎投稿: https://medium.com/taipei-ethereum-meetup #徵技術分享文 #使用心得 #教學文 #medium
Uniswap v2 實作 : 從創建交易對到Ether 換 Dai 投入 Compound
V̶y̶p̶e̶r̶ Solidity coding time!
Image source: https://uniswap.org/
Outline
一. 前言二. 程式碼結構 1. Wrapped Ether(WETH)? 2. Impermanent Loss三. 創建交易對 - 準備 Interfaces四. 注入資金 - 注意事項五. 兌換虛擬貨幣六. 取得報價作為預言機七. 自行兌換 WETH八. 結語
一. 前言
暨上一篇解釋了 Uniswap 的演算法後,由於個人有使用 Uniswap v2 的需求,因此整理後寫成本篇,希望能幫助到其他也需要用 Uniswap 的讀者!(不熟 Uniswap 的人也可以參考區塊勢 許明恩 Astro Hsu 寫的介紹文:點我)
Uniswap 解析:恆定乘積做市商模型 Constant Product Market Maker Model 的 Vyper 實作
本文希望透過實際操作 Uniswap 我個人會用到、我認為大家比較常會用到的功能,來讓不熟悉的讀者快速了解其程式架構(v2 相較 v1 繁複)、熟悉實作方法,無法顧及全部還請見諒。
以下實作的測試環境為 Rinkeby 測試網,由於只是要講解使用方法,因此選擇在 Remix 上操作。
而 Uniswap v2 跟 v1 的差異,個人沒有很認真研究,大致列舉以下幾點,本文只會就其中幾點在後方進行較詳細的解釋:
使用 Wrapped Ether, WETH (於 二. 程式碼結構 解釋),讓 ERC20 交易對不再需要透過 Ether ,降低 gas 的消耗,但還是可以用 Ether 支付
加入時間權重的預言機,降低被操縱價格的風險,於 六. 取得報價作為預言機 中舉例說明,有興趣者可以看看 白皮書 有詳細介紹
閃電貸
使用 Solidity 而非 Vyper,因為 Solidity 功能上較齊全,於 五. 兌換虛擬貨幣 中舉例說明原因
在開始之前,本文超長,播個背景音樂吧:
二. 程式碼結構
本段落簡單描述 Uniswap 程式碼各部分的功能。若讀者要自己實作,可以參考此處得知該去哪一份程式碼找相應的功能:
Factory / UniswapV2Factory.sol : 創建交易對、查詢交易對的地址與總數;查詢、指定交易對手續費的收款地址
Pair(ERC-20) / UniswapV2ERC20.sol : Uniswap 流動性代幣 ERC20 的部分
Pair / UniswapV2Pair.sol : Uniswap 流動性代幣的其他部分;查詢交易對資訊
Router / UniswapV2Router01.sol : 注入、取出流動性/資金;兌換虛擬貨幣。此合約看似最複雜,其實只是因為收付款的單位可以是 Ether 或是 ERC20,所以有很多重複的函式
ExampleOracleSimple.sol, ExampleSlidingWindowOracle.sol : 預言機的範例程式碼
Library / UniswapV2OracleLibrary.sol : 供預言機調用的函式
Library / UniswapV2Library.sol : 供內部調用的函式
除了描述程式碼結構,為了以下的實作我們還需要知道 Wrapped Ether 是什麼,順便了解其使用原因:
1. Wrapped Ether (WETH) ?
從字面上來解釋,Wrapped Ether 是被包起來的 Ether。那為什麼好好的 Ether 不用還要創造出另一版本,嫌這小小世界的術語不夠多嗎 (ETH, WETH, Dai, aDai, cDai, sDai…)?xD
wETH | ERC20 tradable version of ETH
主因有兩個:廣泛地說,Ether 是以太坊上的原生虛擬貨幣,但它與廣為使用的 ERC20 標準並不相容( ERC20 有 approve(), transfer() 等等功能);而針對 Uniswap 的場景來說,v1 的交易對都一定有 Ether,而使用 Ether 可能會造成 Impermanent Loss,於下方解釋。
因此,就以上兩點的解決方法個別是:
部署一 ERC20 <-> Ether 的兌換合約:使用者將 Ether 付給 Wrapped Ether (ERC20) 的智能合約,合約就會給使用者同等數目的 WETH;拿回 Ether 則有點不太一樣,方法是告訴 WETH 的合約使用者要 withdraw(),WETH 的合約就會把使用者 WETH 擁有的額度設回 0 (或減少) 並返還 Ether,於 五. 兌換虛擬貨幣 中舉例說明
v2 交易對的建立不再只能是 (Ether, ERC20),可以是 (ERC20, ERC20)
2. Impermanent Loss
Impermanent loss 在 DeFi 指的是像 Uniswap 這類用演算法的去中心化交易所,如果交易對是兩幣價不相干的虛擬貨幣,例如:穩定幣 (Dai, USDC, etc) 和 Ether,流動性提供者 liquidity provider 會因為幣價的相對波動而比起直接持有兩幣還損失了一筆。
容我舉個例解釋清楚點,可以搭配我上一篇所寫的 Uniswap 的演算法 來理解:假設一開始 1 Ether 幣價為 100 Dai,只有一流動性提供者 LP 投入了 1 Ether 及 100 Dai (1 * 100 = 100 = k,k值要維持不變),總價值為 200 Dai。當 Ether 的幣價來到 200 Dai,眼尖者會發現資金池中的 Ether 價格低、有利可圖,因此會進行套利,例如:拿 33 Dai 約可以換到 0.25 Ether (0.75 * 133 ≒ 100),比起市場上要用 50 Dai 才能換到 0.25 Ether,套利者賺到了。此時,流動性提供者若將自己的資金提出,0.75 Ether 和 133 Dai 此時的總價值是 283 Dai,看似比當初的 200 Dai 還多,但其實將兩幣放著不動 1 Ether + 100 Dai 在此時就已經是 300 Dai 的價值了。於是,impermanent loss 就變成了 permanent loss :(
三. 創建交易對
- 準備 Interfaces
在開始之前,由於使用到的合約不少,所以我將全部所需整理在此:點我。其中,UniswapImplementation.sol 是本文實作的檔案。
若讀者在自己調用 Interface 時遇到版本問題,就依照 compiler 提供的指示稍作修改即可。我所整理的合約都修正過版本的差異、以下的實作也測試了可行,因此可以安心使用。
進入正題
通常大家使用的 Uniswap 資金池都是已經存在的,而如果想要上架自己的虛擬貨幣就要自己創建一組新的交易對,有兩種方式:在 Uniswap 官網上執行或是透過呼叫 Uniswap 的合約來建立,本文使用合約的方式。
首先,我們需要決定資金池為哪兩種虛擬貨幣,那就很普通地選 ETH 和 Dai 吧。雖然選了 ETH,但如同上方所述實際上必須使用 WETH,於是記下其在 Rinkeby 上的位置 。Dai 就使用 Compound 部署在 Rinkeby 上的版本,位置在 0x5592EC0cfb4dbc12D3aB100b257153436a1f0FEa。
接著,打開 IUniswapVFactory.sol,依照官方文件的指示將此合約部署在 Rinkeby 上的 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f。以下會有許多由 Uniswap 文件得知的合約地址及其他資訊等等,就不再一一附上來源或畫面截圖!
如上圖黑框所示,輸入了地址後按下藍色按鍵就完成部署了。接著,如下圖將 Dai 的地址 及 WETH 的地址輸入 createPair():
由於這個交易對我已經部署過了,因此讀者若想嘗試就麻煩去找別的 Rinkeby 上的測試幣了、或自己發一個! 完成後可以用 getPair() 輸入兩幣地址來確認交易對被建立成功:
如果想要進一步核對,可以先呼叫下圖紅框中的 allPairsLength(),得知當前總共有幾組交易對,再將 (交易對數 - 1) 輸入 allPairs(),就能得到和上圖一樣的地址。需要減 1 是因為陣列的 index 從 0 開始。
allPairsLength 的值會因為其他人的使用而增加,故日後可能會和本圖產出的 9 不同
四. 注入資金
看到標題的讀者可能會想:為何要把注入資金/流動性和上方的創建交易對分開呢?因為注入流動性這個功能被放在了 UniswapV2Router01.sol 中,所以就分開解釋。
雖然上一節只需要複製貼上按按鍵,但接下來要探討的注入資金 addLiquidity() 及虛擬貨幣的交換 swap()比起創建交易對 createPair() 是較有可能被融入到其他 DeFi 服務的功能(應該不太會有人會一直需要創建新的交易對),因此打開 UniswapImplementation.sol、要開始 Solidity coding!
如果不熟悉為何別的 DeFi 會需要在自己的服務中使用 Uniswap,可以搜尋 DeFi Money Lego/ DeFi 樂高,顧名思義就是在一個 DeFi 服務上使用另一個 DeFi 服務。實際的例子有:Aave 的新功能是可以拿往 Uniswap 注入流動性後得到的流動性代幣再投入 Aave 來賺取利息,以及下一節兌換虛擬貨幣的功能可以在 Pelith 的輕鬆貸 EasyDai、一站式 DeFi 組合工具 Furucombo 等場景中看到他們如何將 Uniswap 銜接上其他的 DeFi 服務。
回歸正題
在開始之前,先簡單提及一下有哪些變數、instance 需要初始化:
我們要呼叫 Uniswap 合約的 instance 來進行互動: IUniswapV2Router01
不只需要 Dai 的 instance,也需要 Dai 和 WETH 的地址;DaiAmount 和 ETHAmount 是為了稍後注入流動性所設置的,本文假設第一筆流動性我們使用 200 Dai + 1 Ether
immutable 是版本 0.6.5 加入的,用途是讓變數只能被讀取 read-only,但不同於 constant 的是能夠在 constructor() 中賦值。之所以各 instance 的地址不一起加上 immutable ,是因為若加上了,這些地址就不能在 constructor() 中被使用
p.s. 由於 gist 沒有 Solidity 的 syntax highlighting,所以隨便用了 .js 請忽略
看完了初始設定後,由於我們目前鎖定的資金池是 Dai 及 ETH,因此使用如下的 addLiquidityETH() 此函式,可以直接匯入 ETH 及 Dai。
本處我使用長度為 3 的陣列 addLiquidityResult 來記錄注入資金後的返回值:注入 Ether 的數量、Dai 的數量及返回的 Uniswap 流動性代幣的數量。實際上應該要依照不同使用者記錄的他們執行 addLiquidity() 後各自的返還值
第五行中的 approve() 是為了讓 UniswapV2Router01 的合約能夠從我們部署的合約 UniswapImplementation 取得 200 Dai 的使用權
於第六行 addLiquidityETH() 後方的大括號 {value: ETHAmount},這是 Solidity 0.6.0 後版本如果要在呼叫 function 的同時送入 Ether 的標準寫法,以前的寫法 .value() 目前也還能用但 compiler 會給提示
addLiquidityETH() 的第三、四個參數為最少要成功注入的數量。使用者能夠成功注入的數量取決於資金池中兩虛擬貨幣當下的數量,而本處直接給 0 比較方便
這邊非常重要的是上述程式碼還欠缺了一個無法被寫在合約內的步驟:使用者要同意這個被部署的合約 UniswapImplementation 可以從自己的帳戶中轉出 200 Dai。因此,將 ERC20 (Dai) 部署在 Rinkeby 上的位置,也就是0x5592EC0cfb4dbc12D3aB100b257153436a1f0FEa,接著輸入被部署合約的地址以及 200 Dai = 200000000000000000000、按下 approve() 後準備作業完成,如下圖。
終於可以呼叫 addLiquidity() 了! 如下圖,在紅框中以 1 Ether 呼叫黑框中的 addLiquidity() 後,就能成功將資金注入到 liquidity pool 了! 由於本文寫在測試後,因此沒有留下第一次 addLiquidity() 的結果 :(
此處被部署的合約位置跟上方截圖不同,因為其中測試了一些東西所以重新部署qq
接著,可以拿出 IUniswapV2Pair.sol,將其部署在 三. 創建交易對 中成功部署的位置 0x03E6c12eF405AC3F642B9184eDed8E1322de1a9e,使用黑框中的 getReserves() 就可以看到資金池中確實有匯入的資金! (本處依然沒有第一次使用後的截圖,因此截圖只是為了讓讀者看到 getReserves() 的結果)
此圖黑框中的值代表:在資金池中,Dai的數量、Ether的數量及上一次匯入資金的時間戳記
- 注意事項
使用 addLiquidity() 時需要小心的地方是:除了第一筆注入的資金可以自行決定兩虛擬貨幣的數量,第二筆開始就會依照其演算法算出兩幣可以投入的各自數量,因此使用者放入的兩幣中可能會有部分的其中一幣被 Uniswap 返回。
上方的程式碼只是為了第一筆流動性所寫,如果不是第一筆的情況就需要用成功注入流動性後的返回值(例如筆者的作法是用一陣列 addLiquidityResult 來存結果)來把沒有成功注入的資金返回給使用者。
五. 兌換虛擬貨幣
本節使用的兌換功能依舊是來自 IUniswapV2Router01.sol。
由於兌換虛擬貨幣實際上只有五行不到的程式碼,那麼就來把兌換 Ether 而得到的 Dai… 投到 Compound 來賺取放款利息吧! (雖然只是在測試網) 如果覺得這個場景似曾相識,沒錯,這就是上面提到的 輕鬆貸 EasyDai 的不專業版本!
首先將 Ether 和 Dai 互換的邏輯完成:
Ether 換 Dai : 使用 swapExactETHForTokens(),給某數量的 Ether 能換多少 Dai 是多少
Dai 換 Ether : 使用 swapExactTokensForETH(),作法只差在要把 Dai 轉到當前合約,再同意 UniswapV2Router01 可以從當前合約把 Dai 轉走
兩個做法的第二個參數都是可以自行指定兌換的路徑,此處就直接給 WETH 和 Dai 的地址即可(順序有差)。需要注意的是這個路徑要是動態陣列 dynamic array,而這就是 Vyper 所不支援的功能! 動態陣列跟靜態陣列宣告方式的差別我有註解在程式碼中
此處就先來試試 Dai 換 Ether 吧!和上方一樣,在使用時也要先 approve() 當前合約,當前合約才能轉走使用者的 Dai。
由上方的截圖可以很清楚的看到 Dai 換 Ether 這個動作牽涉到的資金轉移路徑:
Dai: 我的帳戶→當前合約→交易對所在合約
WETH: 交易對所在合約→UniswapV2Router01
Ether: WETH 合約→UniswapV2Router01→我的帳戶
以上的路徑有些人稍微思考後可能會納悶:為什麼上方沒有一筆 WETH 從 UniswapV2Router01 再轉到 WETH 合約的動作呢? 這就是在 Wrapped Ether (WETH) ? 中提到的案例。原因是:把 WETH 還回 WETH 的合約時實際上使用的函示是 withdraw() 而非 transfer(),而在 WETH 合約中發生的只是把使用者 WETH 擁有的額度歸零或減少而已。
接下來就是把 Dai 轉到 Compound 的部分。由於 Compound 不是本文重點,此處只求功能正常,因此比起真正的實作方法當然是簡化許多。
一如往常初始化 Compound 合約的 instance
ETH 換 Dai 後放入 Compound : 將用 ETH 換得的 Dai 的數量,也就是 swapExactETHForTokens() 返回的第二個值,approve() Compound 的合約後就可以用 mint() 匯入了! 要注意的是,ETH 換成 Dai 後的收款地址(第四個參數)是當前合約,才能從此合約轉 Dai 到 Compound
還款給使用者: 用 redeem() 取出 Dai,一如往常同意 UniswapV2Router01 使用 Dai 的權力
之所以說這個程式碼不能真的拿來用是因為:cDai 轉給使用者、讓使用者自己持有是比較安全的作法;即使選擇把 cDai 留在當前合約,以上程式碼檢查 cDai 數量是用當前合約 address(this) 去檢查,實際上應該要去記錄每個使用者所擁有的 cDai 數量
最後附上截圖,可以看一下資金的轉移路徑:
ETH -> WETH -> Dai -> cDai (Compound)
cDai -> Dai -> WETH -> ETH
六. 取得報價作為預言機
若使用 Uniswap v1的報價作為預言機,攻擊者可以利用其演算法造成的滑點來操控價格。為此,Uniswap v2 提供了兩個加入時間權重的合約範例:
ExampleOracleSimple.sol : 簡單版
ExampleSlidingWindowOracle.sol : 複雜版;Sliding Window 在此場景是指透過改變擷取資料(歷史價格)的片段,用該特定期間的價格來做成時間權重,讓使用上更靈活!
本處以簡單版為例。打開 ExampleOracleSimple.sol,由於一些匯入檔案的問題我將 UniswapV2OracleLibrary 也放在這份檔案中。
做法非常簡單:將 UniswapV2Factory、Dai 及 WETH 所在的地址作為部署合約 ExampleOracleSimple 時的輸入值就完成了。部署成功後會有個 24 小時的鎖 Time lock,因為這個預言機是有時間權重的,所以並不是一部署完就能立刻使用。若要體驗更新價格此功能可以使用我部署的兩個,其位置我寫在註解中。
將 WETH 或是 Dai 的地址和要查詢的數量輸入 consult() 就能查到兩虛擬貨幣的價格:
1 ETH 價格約為 97 Dai
1 Dai 價格約為 0.01 ETH
然而,在測試網上我們沒辦法拿著預言機查到的價格套入演算法來核對,因為測試網上的 Uniswap 沒有啟用收費機制,而 k 值要在收費機制啟動時才能被計算,欲知詳情者就麻煩去看官方文件了!
七. 自行兌換 WETH
上方雖然有提到 WETH 在 Uniswap 中的使用原因及場合,但或許有人想試著自己動手將 Ether 換成 WETH、把 WETH 換回 Ether。方法非常簡單,將 WETH.sol 部署到 0xc778417E063141139Fce010982780140Aa0cD5Ab 就能使用,如下圖:
按下綠框中的 At Address 後,使用下方黑框中的 deposit 搭配在中間的黑框輸入所要兌換 Ether 的量,就能成功換到 WETH。同理,圖中未顯示的 withdraw 功能就是讓人輸入 WETH 來換回等量的 Ether。
稍微提一下,如果是第一次兌換,將 WETH 所在的地址輸入 Metamask 就能在錢包中看到自己擁有的 WETH 的數量,如下兩圖:
Voila!
八. 結語
呼,雖然上述操作及程式碼的撰寫其實還蠻簡單的,但畢竟 Uniswap 的功能不少、我個人也希望能將小細節解釋清楚些,因此長度遠超過預期...有看到結尾處的讀者,辛苦了xD 希望大家現在對於 Uniswap v2 的內容跟實作方法都很清楚了!
最後,如果本文有任何錯誤,請不吝提出,我會盡快做修正;而如果我的文章有幫助到你,可以看看我的其他文章,歡迎一起交流 :)
田少谷 Shao - Medium
Uniswap v2 實作 : 從創建交易對到Ether 換 Dai 投入 Compound was originally published in Taipei Ethereum Meetup on Medium, where people are continuing the conversation by highlighting and responding to this story.
👏 歡迎轉載分享鼓掌
gas費查詢 在 DanOnTheMove Youtube 的最佳解答
#粵語 #F1 #DanOnTheMove
角田反 Pierre 檯❓
??/??? Patrons 獨家《DANFLIX》睇 Netflix:F1 極速求生
第三季導賞團
? 第九及十集《火裡逃生》➕《三哥之戰》?https://www.patreon.com/posts/48991910
? 第六至八集《PIERRE 興奮打科興》➕《Mick I$ King》➕《No More Bromance》?https://www.patreon.com/posts/48956766
? 第三至五集《保子心戰室》➕《四屆反擊戰》➕《威龍男人の浪漫?》?https://www.patreon.com/posts/48912032
? 第一及二集《武肺來了》➕《太陽繼續升起》?https://www.patreon.com/posts/48851326
第二季導賞團
? 第十集《最終局》?https://www.patreon.com/posts/48563023
? 第九集《威記唔威の牌越爛越要俾心機打》?https://www.patreon.com/posts/48272887
? 第八集《威龍解 HULK》?https://www.patreon.com/posts/47710102
? 第七集《法拉利見紅》?https://www.patreon.com/posts/47366101
? 第六集《咪又係一九樣のALB》?https://www.patreon.com/posts/46917456
? 第五集《燉冬菇のGAS》?https://www.patreon.com/posts/46770949
? 第四集《黑平》?https://www.patreon.com/posts/46471176
? 第三集《本是同根生》?https://www.patreon.com/posts/46125034
? 第二集《哈屎要爆喇》?https://www.patreon.com/posts/45876252
? 第一集《五盞紅燈一熄》?https://www.patreon.com/posts/45590040
=================================
丹尼爾 VS 陳恩能 Social Media
? Patreon 年費 92 折: http://www.patreon.com/danonthemove
? MeWe: https://mewe.com/p/danonthemove
? MeWe: https://mewe.com/join/F1正體中文台
? MeWe: https://mewe.com/join/我愛黨足球台
? Facebook: https://www.facebook.com/danonthemove
? Instagram: https://www.instagram.com/mrdan91
? 其他合作及查詢可以電郵至 info@passionmarketing.com.hk
=================================
gas費查詢 在 DanOnTheMove Youtube 的最讚貼文
#粵語 #F1 #DanOnTheMove
??/??? Patrons 獨家《DANFLIX》睇 Netflix:F1 極速求生第三季導賞團
? 19/3/2021 晚上十點第一至二集?https://www.patreon.com/posts/48851326
? 20/3/2021 晚上十點第三至五集?https://www.patreon.com/posts/48912032
? 21/3/2021 晚上十點第六至八集?
? 22/3/2021 晚上十點第九至十集?
第二季導賞團
? 第十集《最終局》?https://www.patreon.com/posts/48563023
? 第九集《威記唔威の牌越爛越要俾心機打》?https://www.patreon.com/posts/48272887
? 第八集《威龍解 HULK》?https://www.patreon.com/posts/47710102
? 第七集《法拉利見紅》?https://www.patreon.com/posts/47366101
? 第六集《咪又係一九樣のALB》?https://www.patreon.com/posts/46917456
? 第五集《燉冬菇のGAS》?https://www.patreon.com/posts/46770949
? 第四集《黑平》?https://www.patreon.com/posts/46471176
? 第三集《本是同根生》?https://www.patreon.com/posts/46125034
? 第二集《哈屎要爆喇》?https://www.patreon.com/posts/45876252
? 第一集《五盞紅燈一熄》?https://www.patreon.com/posts/45590040
=================================
丹尼爾 VS 陳恩能 Social Media
? Patreon 年費 92 折: http://www.patreon.com/danonthemove
? MeWe: https://mewe.com/p/danonthemove
? MeWe: https://mewe.com/join/F1正體中文台
? MeWe: https://mewe.com/join/我愛黨足球台
? Facebook: https://www.facebook.com/danonthemove
? Instagram: https://www.instagram.com/mrdan91
? 其他合作及查詢可以電郵至 info@passionmarketing.com.hk
=================================
gas費查詢 在 EVITAvsEVITA Youtube 的最佳貼文
全片都是我從網上看到的資料
相信與否請自行做更多research再判斷
醫學上的事,食入口的東西,
請先問問你的家庭醫生
祝各位健康。
Let's CONNECT!! 成為真正嘅甜甜星子民
https://www.instagram.com/evitavsevita/
https://www.patreon.com/EVITAvsEVITA
https://www.facebook.com/ekee.w 好多貓貓直播!
https://evitawong.com 有間格文章同每月星座!
SUBSCRIBE 呢個channel,逢星期二同Facebook同步直播!
鋪頭IG:https://www.instagram.com/badblood.magick/
塔羅占卜/開阿卡西紀錄/學用天使牌等等
收費及詳情查詢請 whatsapp 9768-5388
tarot/ akashic records reading booking (hk and /or overseas):
whatsapp 9768-5388
watch my other videos:
50 FACTS about EKEE!!
https://www.youtube.com/watch?v=miWVxLHMT8Q
BADASS official MV
https://www.youtube.com/watch?v=SCs0lpCMVFE&index=2&list=PLIsDIt5LKshINfRZFA8H6KdefMrtNV_d0
Witchy Talk - Heavy Shxt
https://www.youtube.com/watch?v=WScIj7JOJvE
阿卡西紀錄 open your Akashic Record
https://www.youtube.com/watch?v=-uhrbXloadI&t=2s
捐獻字幕吧!WELCOME to submit subtitles to this video!! thank you very much if you are willing to do so!! i will put your name in this info box to THANK you!!
gas費查詢 在 eth手續費查詢-在PTT/MOBILE01/Dcard上的毛小孩推薦資訊整理 的必吃
2022eth手續費查詢討論資訊,在PTT/MOBILE01/Dcard上的毛小孩推薦資訊整理,找gwei換算,ETH gas fee,gas fee換算在Instagram影片與照片(Facebook/Youtube)熱門討論內容 ... ... <看更多>
gas費查詢 在 eth手續費查詢-在PTT/MOBILE01/Dcard上的毛小孩推薦資訊整理 的必吃
2022eth手續費查詢討論資訊,在PTT/MOBILE01/Dcard上的毛小孩推薦資訊整理,找gwei換算,ETH gas fee,gas fee換算在Instagram影片與照片(Facebook/Youtube)熱門討論內容 ... ... <看更多>
gas費查詢 在 4个工具教你实时查看GAS Fee!|以太坊转账太贵?如何在小 ... 的必吃
这4个工具教你实时查看以太坊 GAS Fee,省下一大笔手续费|如何在小狐狸Metamask中设置 GAS Fee,更省手续费——————————————————————章节:0:00 GAS费 ... ... <看更多>