日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

uniswap ERC20代码学习

發布時間:2023/12/20 50 豆豆
生活随笔 收集整理的這篇文章主要介紹了 uniswap ERC20代码学习 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

目錄

  • 1、合約源碼
  • 2、代碼逐行解讀
  • 3、知識拓展
    • 3.1 鏈下簽名消息
    • 3.2 EIP-712
    • 3.3 為什么存在permit函數
    • 3.4 代幣元數據

UniswapV2ERC20.sol是交易對合約的父合約,主要實現了ERC20代幣功能并增加了對線下簽名消息進行授權的支持。它除了標準的ERC20接口外還有自己的接口,因此取名為UniswapV2ERC20。

1、合約源碼

pragma solidity =0.5.16;import './interfaces/IUniswapV2ERC20.sol'; import './libraries/SafeMath.sol';contract UniswapV2ERC20 is IUniswapV2ERC20 {using SafeMath for uint;string public constant name = 'Uniswap V2';string public constant symbol = 'UNI-V2';uint8 public constant decimals = 18;uint public totalSupply;mapping(address => uint) public balanceOf;mapping(address => mapping(address => uint)) public allowance;bytes32 public DOMAIN_SEPARATOR;// keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;mapping(address => uint) public nonces;event Approval(address indexed owner, address indexed spender, uint value);event Transfer(address indexed from, address indexed to, uint value);constructor() public {uint chainId;assembly {chainId := chainid}DOMAIN_SEPARATOR = keccak256(abi.encode(keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),keccak256(bytes(name)),keccak256(bytes('1')),chainId,address(this)));}function _mint(address to, uint value) internal {totalSupply = totalSupply.add(value);balanceOf[to] = balanceOf[to].add(value);emit Transfer(address(0), to, value);}function _burn(address from, uint value) internal {balanceOf[from] = balanceOf[from].sub(value);totalSupply = totalSupply.sub(value);emit Transfer(from, address(0), value);}function _approve(address owner, address spender, uint value) private {allowance[owner][spender] = value;emit Approval(owner, spender, value);}function _transfer(address from, address to, uint value) private {balanceOf[from] = balanceOf[from].sub(value);balanceOf[to] = balanceOf[to].add(value);emit Transfer(from, to, value);}function approve(address spender, uint value) external returns (bool) {_approve(msg.sender, spender, value);return true;}function transfer(address to, uint value) external returns (bool) {_transfer(msg.sender, to, value);return true;}function transferFrom(address from, address to, uint value) external returns (bool) {if (allowance[from][msg.sender] != uint(-1)) {allowance[from][msg.sender] = allowance[from][msg.sender].sub(value);}_transfer(from, to, value);return true;}function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external {require(deadline >= block.timestamp, 'UniswapV2: EXPIRED');bytes32 digest = keccak256(abi.encodePacked('\x19\x01',DOMAIN_SEPARATOR,keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))));address recoveredAddress = ecrecover(digest, v, r, s);require(recoveredAddress != address(0) && recoveredAddress == owner, 'UniswapV2: INVALID_SIGNATURE');_approve(owner, spender, value);} }

2、代碼逐行解讀

  • 確定的使用的Solidity版本
  • pragma solidity =0.5.16;
  • 這兩行導入了該合約必須實現的接口IUniswapV2ERC20.sol和一個防溢出的數學工具庫SafeMath。一個合約實現的接口代表了它的基本功能;防溢出數學工具庫應用很常見,主要是因為數值是可以無限大的,但是存儲位數是有限的。例如最大256位,因此最大的無符號整數就是是2**256-1。再大就會溢出,這時就會得到預期外的結果。另外,因為在Solidity中,應用最多的是無符號整數,如果減法得到了負數,根據二進制的表示法,結果會被認為成另一個無符號整數。在早期的智能合約中,存在溢出漏洞或者得到負值而遭受損失的情況。當前編寫的智能合約一般都會防范這種問題的發生,使用SafeMath工具庫是最常見的預防手段。注意,該庫里只有加、減和乘三種計算,沒有除法。因為除法不會有溢出;如果被零除,Solidity語言本身會報錯重置整個交易,不需要額外處理。
  • import "./interfaces/IUniswapV2ERC20.sol"; import "./libraries/SafeMath.sol";
  • 定義了該合約必須實現導入的IUniswapV2ERC20接口。該接口是由標準ERC20接口加上自定義的線下簽名消息支持接口組成,所以UniswapV2ERC20也是一個ERC20代幣合約。最后一個花括號是作用域開始。
  • contract UniswapV2ERC20 is IUniswapV2ERC20 {
  • 代表在uint256(uint是它的同名)類型上使用SafeMath庫。Solidity中庫函數在指定調用實例時(例如本例中的.sub等)和Rust語言中的結構體的方法類似,實例自動作為庫函數中的第一個參數。
  • using SafeMath for uint256;
  • 這三行代碼定義了ERC20代幣的三個對外狀態變量(代幣元數據):名稱,符號和精度。這里的精度就是小數點位數。注意,由于該合約為交易對合約的父合約,而交易對合約是可以創建無數個的,所以這無數個交易對合約中的ERC20代幣的名稱、符號和精度都一樣。我們平常在交易所中看到的只是ERC20代幣的符號,從這里可以看出,符號是可以重復的,并不是唯一確定的。代幣之間根本區別是合約地址,這個是唯一的,不同的地址就是不同的代幣,哪怕合約代碼完全一樣。
  • //token名稱 string public constant name = "Uniswap V2"; //token縮寫 string public constant symbol = "UNI-V2"; //token精度 uint8 public constant decimals = 18;
  • 記錄代幣發行總量的狀態變量。為什么是訪問權限是public的呢?主要是利用編譯器的自動構造同名函數功能來實現相應接口。
  • uint256 public totalSupply;
  • 用一個map記錄每個地址的代幣余額
  • mapping(address => uint256) public balanceOf;
  • 用來記錄每個地址的授權分布,用于非直接轉移代幣(例如調用第三方合約來轉移)。這個概念初學者不好理解,為什么要授權后才能轉移代幣呢?這里打個比方,代幣合約就相當于銀行,你直接去銀行轉賬(代幣)是不需要授權的。但是如果你使用微信充值,將銀行卡里的錢充值到微信錢包,微信必須得到你的授權(包括額度),這樣微信才能在你的授權額度范圍內轉移你銀行卡內的錢。如果沒有授權機制而可以直接轉錢的話,微信就可能把你的銀行卡悄無聲息的掏空了。同樣,如果你訪問第三方合約(非代幣合約),第三方合約沒有得到你的授權就無法轉移你的代幣。否則,遇到個惡意合約,一下就把你所有的代幣都偷走了。
  • mapping(address => mapping(address => uint256)) public allowance;
  • 用來在不同Dapp之間區分相同結構和內容的簽名消息,該值也有助于用戶辨識哪些為信任的Dapp,具體可見eip-712提案。
  • bytes32 public DOMAIN_SEPARATOR;
  • 這一行代碼根據事先約定使用permit函數的部分定義計算哈希值,重建消息簽名時使用。
  • bytes32public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
  • 記錄合約中每個地址使用鏈下簽名消息交易的數量,用來防止重放攻擊。
  • mapping(address => uint256) public nonces;
  • 兩個event是ERC20標準中的兩個事件定義,方便客戶端進行一些追蹤。
  • //批準事件 event Approval(address indexed owner,address indexed spender,uint256 value ); //發送事件 event Transfer(address indexed from, address indexed to, uint256 value);
  • constructor構造器。該構造器只做了一件事,計算DOMAIN_SEPARATOR的值。根據EIP-712的介紹,該值通過domainSeparator = hashStruct(eip712Domain)計算。這其中eip712Domain是一個名為EIP712Domain的 結構,它可以有以下一個或者多個字段
  • onstructor() public {uint256 chainId;// solium-disable-next-lineassembly {chainId := chainid}//EIP712DomainDOMAIN_SEPARATOR = keccak256(abi.encode(keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),keccak256(bytes(name)),keccak256(bytes("1")),chainId,address(this)));}
    • string name 可讀的簽名域的名稱,例如Dapp的名稱,在本例中為代幣名稱。
    • string version當前簽名域的版本,本例中為"1"。
    • uint256 chainId。當前鏈的ID,注意因為Solidity不支持直接獲取該值,所以使用了內嵌匯編來獲取。
    • address verifyingContract驗證合約的地址,在本例中就是本合約地址了。
    • bytes32 salt用來消除歧義的salt,它可以用來作為DOMAIN_SEPARATOR的最后措施。在本例中對’EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'進行keccak256運算后得到的哈希值。
      注意:結構體本身無法直接進行hash運算,所以構造器中先進行了轉換,hashStruct就是指將結構體轉換并計算最終hash的過程。
  • _mint函數,進行代幣增發,注意它是internal函數,所以外部是無法調用的
  • function _mint(address to, uint256 value) internal {totalSupply = totalSupply.add(value);balanceOf[to] = balanceOf[to].add(value);emit Transfer(address(0), to, value); }
  • _burn函數,進行代幣燃燒,同樣它也是internal函數
  • function _burn(address from, uint256 value) internal {balanceOf[from] = balanceOf[from].sub(value);totalSupply = totalSupply.sub(value);emit Transfer(from, address(0), value);}
  • _approve函數,進行授權操作,注意它是private函數,意味著只能在本合約內直接調用。不過,在子合約中可以通過一個內部或者公共的函數進行間接調用。
  • function _approve(address owner,address spender,uint256 value) private {allowance[owner][spender] = value;emit Approval(owner, spender, value);}
  • _transfer函數,轉移代幣操作,注意也是一個private函數
  • function _transfer(address from,address to,uint256 value) private {balanceOf[from] = balanceOf[from].sub(value);balanceOf[to] = balanceOf[to].add(value);emit Transfer(from, to, value);}
  • approve函數,注意它是external(外部)函數,用戶通常進行授權操作的外部調用接口。
  • function approve(address spender, uint256 value) external returns (bool) {_approve(msg.sender, spender, value);return true;}
  • transfer函數,同上,用戶轉移代幣操作的外部調用接口。
  • function transfer(address to, uint256 value) external returns (bool) {_transfer(msg.sender, to, value);return true;}
  • transferFrom代幣授權轉移函數,它是一個外部函數,主要是由第三方合約來調用。注意它的實現中(UniswapV2的實現)作了一個假定,如果你的授權額度為最大值(幾乎用不完,相當于永久授權),為了減小操作步數和gas,調用時授權余額是不扣除相應的轉移代幣數量的。這里如果沒有授權(授權額度為0),那么會怎樣呢?庫函數.sub(value)調用時無法通過SafeMath的require檢查,會導致整個交易會被重置。所以如果沒有授權,第三方合約是無法轉移你的代幣的,你不用擔心你的資產被別的合約隨便偷走。
  • function transferFrom(address from,address to,uint256 value) external returns (bool) {if (allowance[from][msg.sender] != uint256(-1)) {allowance[from][msg.sender] = allowance[from][msg.sender].sub(value);}_transfer(from, to, value);return true;}
  • permit使用線下簽名消息進行授權操作。為什么會有使用線下簽名然后再線上驗證操作這種方式呢?首先線下簽名不需要花費任何gas,然后任何其它賬號或者智能合約可以驗證這個簽名后的消息,然后再進行相應的操作(這一步可能是需要花費gas的,簽名本身是不花費gas的)。線下簽名還有一個好處是減少以太坊上交易的數量,UniswapV2中使用線下簽名消息主要是為了消除代幣授權轉移時對授權交易的需求。
  • function permit(address owner,address spender,uint256 value,uint256 deadline,uint8 v,bytes32 r,bytes32 s) external {// solium-disable-next-line security/no-block-membersrequire(deadline >= block.timestamp, "UniswapV2: EXPIRED");bytes32 digest = keccak256(abi.encodePacked("\x19\x01",DOMAIN_SEPARATOR,keccak256(abi.encode(PERMIT_TYPEHASH,owner,spender,value,nonces[owner]++,deadline))));address recoveredAddress = ecrecover(digest, v, r, s);require(recoveredAddress != address(0) && recoveredAddress == owner,"UniswapV2: INVALID_SIGNATURE");_approve(owner, spender, value);}

    3、知識拓展

    3.1 鏈下簽名消息

    鏈下簽名消息相關知識可以參考Solidity官方文檔中的Solidity by Example下的Micropayment Channel示例。根據應用場景的不同,簽名的消息包含不同的內容,但一般都要包含一個防重放攻擊的元素。通常使用和以太坊交易本身相同的技巧,即使用一個nonce記錄賬號進行交易的數量,智能合約檢查該nonce以確保簽名消息不被多次使用。本例中簽名消息的內容包括:[PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline]。從代碼nonces[owner]++中 可以看到,每調用一次permit,相應地址的nonce就會加1,這樣再使用原來的簽名消息就無法再通過驗證了(重建的簽名消息不正確了),也就防止了重放攻擊。

    在以太坊中,在ECDSA簽名原有的r和s的基礎上加了一個v,使用它們可以驗證簽名消息的賬號。Solidity中有一個內置的函數ecrecover來獲取消息的簽名地址,它使用簽名消息和r,s,v作為參數。

    使用鏈下簽名消息的常用流程是在首先鏈上根據輸入參數重建整個簽名消息,然后將重建的簽名消息和輸入的簽名消息進行處理及比較對照,來進行相關判定和驗證輸入信息未受到篡改。

    鏈下簽名計算實質上是模擬的是Solidity中的keccak256及abi.encodePacked函數,因此本合約中消息簽名的計算方式為bytes32 digest = keccak256(這行及接下來的代碼。計算后得到一個hash值digest,利用這個值和函數參數中的,r,s,v,使用ecrecover函數就可以得到消息簽名者的地址。將這個對址和owner相對比,就可以驗證該消息是否由owner簽名的(顯而易見每個賬號只能對本地址進行授權操作)。注意:簽名內容包含了spender和value,如果簽名內容的任意值做了更改,使用原來的r,s,v是無法通過驗證的。

    查看了一下UniswapV2的前端,它使用了web3-react中的eth_signTypedData_v4方法來計算簽名消息中的r,s,v的,最終傳遞給了permit函數作為參數。這里V1版本前端直接使用的是Javascript + React,V2版本前端使用的是TypeScript + React。

    3.2 EIP-712

    該提案是用來增強鏈下簽名消息在鏈上的可用性的。具體內容參見github上的EIP地址:https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md,它同時提供了一個測試示例Example.sol,本合約中DOMAIN_SEPARATOR的計算方法和示例中是一致的。因為原生的簽名消息對用戶不太友好,用戶無法從中獲取更多信息,使用EIP-712第一可以讓用戶了解消息簽名的大致描述,第二可以讓用戶辨識哪些是信任的Dapp,哪些是高風險的Dapp,從而不隨便簽名消息讓自己遭受損失(比如一個惡意Dapp進行偽裝等)。

    3.3 為什么存在permit函數

    現在我們來弄明白為什么存豐permit函數。UniswapV2的核心合約雖然功能完整,但對用戶不友好,用戶需要借助它的周邊合約才能和核心合約交互。但是在涉及到流動性供給時,比如用戶減少流動性,此時用戶需要將自己的流動性代幣(一種ERC20代幣)燃燒掉。由于用戶調用的是周邊合約,周邊合約未經授權是無法進行燃燒操作的( 上面提到過)。此時,如果按照常規操作,用戶需要首先調用交易對合約對周邊合約進行授權,再調用周邊合約進行燃燒,這個過程實質上是調用兩個不同合約的兩個交易(無法合并到一個交易中),它分成了兩步,用戶需要交易兩次才能完成。

    使用線下消息簽名后,可以減少其中一個交易,將所有操作放在一個交易里執行,確保了交易的原子性。在周邊合約里,減小流動性來提取資產時,周邊合約在一個函數內先調用交易對的permit函數進行授權,接著再進行轉移流動性代幣到交易對合約,提取代幣等操作。所有操作都在周邊合約的同一個函數中進行,達成了交易的原子性和對用戶的友好性。

    因此permit函數存在并且執行了授權操作的原因:

    第三方合約在進行ERC20代幣轉移時(代幣交易),用戶首先需要調用代幣合約進行授權(授權交易),然后才能調用第三方合約進行轉移。這樣整個過程將構成分階段的兩個交易,用戶必須交易兩次,失去了交易的原子性。使用線下消息簽名線上驗證的方式可以消除對授權交易的需求,permit就是進行線上驗證并同時執行授權的函數。

    當然如果用戶會操作的話,也可以手動授權,不使用permit函數相關的周邊合約接口進行交易。

    3.4 代幣元數據

    什么叫代幣元數據,指的是代幣名稱,符號(簡寫)和精度。這三種元數據雖然存在于標準的ERC20協議中,必須得到實現,但是對于代幣轉移本身來講卻是沒有任何作用或者意義的(代幣轉移函數transfer和transferFrom并未使用到它們)。它們屬于對外展示的屬性,所以在ERC1155協議中,不管是同質代幣還是非同質代幣(例如ERC721藏品)已經取消了這三種元數據,設法將它們放到了鏈下(不過放到鏈下就意味著需要一個額外的存儲媒介)。然而當前錢包對ERC1155的支持并不太友好,并且ERC1155代幣統一處理各種資產,無法同時滿足多種場景需求。ERC1155提案雖然已變成Final狀態兩年了,始終未得到大規模應用。

    總結

    以上是生活随笔為你收集整理的uniswap ERC20代码学习的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。