庫類似于合約,但它們的目的是它們只在特定地址部署一次,并且使用 EVM 的DELEGATECALL (CALLCODE直到 Homestead)功能重用它們的代碼。這意味著如果調(diào)用庫函數(shù),它們的代碼將在調(diào)用合約的上下文中執(zhí)行,即this指向調(diào)用合約,尤其是可以訪問調(diào)用合約的存儲(chǔ)。由于庫是一段孤立的源代碼,它只能訪問調(diào)用合約的狀態(tài)變量,如果它們被顯式提供(否則它無法命名它們)。如果庫函數(shù)DELEGATECALL不修改狀態(tài)(即如果它們是view或pure函數(shù)),因?yàn)閹毂患俣闊o狀態(tài)的。特別是,不可能銷毀庫。
筆記
在 0.4.20 版本之前,可以通過繞過 Solidity 的類型系統(tǒng)來銷毀庫。從那個(gè)版本開始,庫包含一種不允許直接調(diào)用狀態(tài)修改函數(shù)的機(jī)制DELEGATECALL(即沒有)。
庫可以被視為使用它們的合約的隱含基礎(chǔ)合約。它們?cè)诶^承層次結(jié)構(gòu)中不會(huì)顯式可見,但對(duì)庫函數(shù)的調(diào)用看起來就像對(duì)顯式基礎(chǔ)合約的函數(shù)的調(diào)用(使用限定訪問,如L.f())。當(dāng)然,對(duì)內(nèi)部函數(shù)的調(diào)用使用內(nèi)部調(diào)用約定,這意味著所有內(nèi)部類型都可以傳遞,并且存儲(chǔ)在內(nèi)存中的類型將通過引用傳遞而不是復(fù)制。為了在 EVM 中實(shí)現(xiàn)這一點(diǎn),從合約調(diào)用的內(nèi)部庫函數(shù)的代碼以及從其中調(diào)用的所有函數(shù)將在編譯時(shí)包含在調(diào)用合約中,并且JUMP將使用常規(guī)調(diào)用而不是DELEGATECALL.
筆記
當(dāng)涉及到公共函數(shù)時(shí),繼承類比就失效了。調(diào)用公共庫函數(shù)L.f()會(huì)導(dǎo)致外部調(diào)用(DELEGATECALL 準(zhǔn)確地說)。相反,當(dāng)是當(dāng)前合約的基礎(chǔ)合約時(shí),A.f()是內(nèi)部調(diào)用。A
以下示例說明了如何使用庫(但使用手動(dòng)方法,請(qǐng)務(wù)必查看using for以獲得更高級(jí)的示例來實(shí)現(xiàn)集合)。
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.6.0 <0.9.0; // We define a new struct datatype that will be used to // hold its data in the calling contract. struct Data { mapping(uint => bool) flags; } library Set { // Note that the first parameter is of type "storage // reference" and thus only its storage address and not // its contents is passed as part of the call. This is a // special feature of library functions. It is idiomatic // to call the first parameter `self`, if the function can // be seen as a method of that object. function insert(Data storage self, uint value) public returns (bool) { if (self.flags[value]) return false; // already there self.flags[value] = true; return true; } function remove(Data storage self, uint value) public returns (bool) { if (!self.flags[value]) return false; // not there self.flags[value] = false; return true; } function contains(Data storage self, uint value) public view returns (bool) { return self.flags[value]; } } contract C { Data knownValues; function register(uint value) public { // The library functions can be called without a // specific instance of the library, since the // "instance" will be the current contract. require(Set.insert(knownValues, value)); } // In this contract, we can also directly access knownValues.flags, if we want. }
當(dāng)然,您不必按照這種方式使用庫:也可以在不定義結(jié)構(gòu)數(shù)據(jù)類型的情況下使用它們。函數(shù)也可以在沒有任何存儲(chǔ)引用參數(shù)的情況下工作,并且它們可以有多個(gè)存儲(chǔ)引用參數(shù)并且可以在任何位置。
對(duì)和的調(diào)用Set.contains都 編譯為對(duì)外部合約/庫的調(diào)用 ( )。如果您使用庫,請(qǐng)注意執(zhí)行了實(shí)際的外部函數(shù)調(diào)用。 ,并且將在此調(diào)用中保留它們的值(在 Homestead 之前,因?yàn)槭褂?并且 更改了)。Set.insertSet.removeDELEGATECALLmsg.sendermsg.valuethisCALLCODEmsg.sendermsg.value
以下示例顯示了如何使用存儲(chǔ)在內(nèi)存中的類型和庫中的內(nèi)部函數(shù)來實(shí)現(xiàn)自定義類型,而無需外部函數(shù)調(diào)用的開銷:
// SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.0; struct bigint { uint[] limbs; } library BigInt { function fromUint(uint x) internal pure returns (bigint memory r) { r.limbs = new uint[](1); r.limbs[0] = x; } function add(bigint memory a, bigint memory b) internal pure returns (bigint memory r) { r.limbs = new uint[](max(a.limbs.length, b.limbs.length)); uint carry = 0; for (uint i = 0; i < r.limbs.length; ++i) { uint limbA = limb(a, i); uint limbB = limb(b, i); unchecked { r.limbs[i] = limbA + limbB + carry; if (limbA + limbB < limbA || (limbA + limbB == type(uint).max && carry > 0)) carry = 1; else carry = 0; } } if (carry > 0) { // too bad, we have to add a limb uint[] memory newLimbs = new uint[](r.limbs.length + 1); uint i; for (i = 0; i < r.limbs.length; ++i) newLimbs[i] = r.limbs[i]; newLimbs[i] = carry; r.limbs = newLimbs; } } function limb(bigint memory a, uint index) internal pure returns (uint) { return index < a.limbs.length ? a.limbs[index] : 0; } function max(uint a, uint b) private pure returns (uint) { return a > b ? a : b; } } contract C { using BigInt for bigint; function f() public pure { bigint memory x = BigInt.fromUint(7); bigint memory y = BigInt.fromUint(type(uint).max); bigint memory z = x.add(y); assert(z.limb(1) > 0); } }
可以通過將庫類型轉(zhuǎn)換為類型來獲取庫的地址address,即使用address(LibraryName).
由于編譯器不知道庫將部署到的地址,因此編譯后的十六進(jìn)制代碼將包含表單的占位符__$30bbc0abd4d6364515865950d3e0d10953$__。占位符是完全限定庫名稱的 keccak256 哈希的十六進(jìn)制編碼的 34 個(gè)字符前綴,例如libraries/bigint.sol:BigInt,如果庫存儲(chǔ)在目錄中調(diào)用的文件bigint.sol中l(wèi)ibraries/。此類字節(jié)碼不完整,不應(yīng)部署。占位符需要替換為實(shí)際地址。您可以通過在編譯庫時(shí)將它們傳遞給編譯器或使用鏈接器更新已編譯的二進(jìn)制文件來做到這一點(diǎn)。有關(guān)如何使用命令行編譯器進(jìn)行鏈接的信息,請(qǐng)參閱 庫鏈接。
與合約相比,庫在以下方面受到限制:
(這些可能會(huì)在稍后解除。)
雖然可以對(duì)公共或外部庫函數(shù)進(jìn)行外部調(diào)用,但此類調(diào)用的調(diào)用約定被認(rèn)為是 Solidity 內(nèi)部的,與為常規(guī)合約 ABI指定的不同。外部庫函數(shù)支持比外部合約函數(shù)更多的參數(shù)類型,例如遞歸結(jié)構(gòu)和存儲(chǔ)指針。出于這個(gè)原因,用于計(jì)算 4 字節(jié)選擇器的函數(shù)簽名是按照內(nèi)部命名模式計(jì)算的,并且合約 ABI 中不支持的類型的參數(shù)使用內(nèi)部編碼。
以下標(biāo)識(shí)符用于簽名中的類型:
值類型、非存儲(chǔ)string
和非存儲(chǔ)bytes
使用與合約 ABI 中相同的標(biāo)識(shí)符。
非存儲(chǔ)數(shù)組類型遵循與合同 ABI 中相同的約定,即<type>[]
用于動(dòng)態(tài)數(shù)組和 <type>[M]
固定大小的M
元素?cái)?shù)組。
非存儲(chǔ)結(jié)構(gòu)由它們的完全限定名稱引用,即C.S
for 。contract C { struct S { ... } }
存儲(chǔ)指針映射分別使用where和是映射的鍵和值類型的標(biāo)識(shí)符。mapping(<keyType> => <valueType>) storage
<keyType>
<valueType>
其他存儲(chǔ)指針類型使用其對(duì)應(yīng)的非存儲(chǔ)類型的類型標(biāo)識(shí)符,但在其后附加一個(gè)空格storage
。
參數(shù)編碼與常規(guī)合約 ABI 相同,除了存儲(chǔ)指針,它們被編碼為 uint256
引用它們指向的存儲(chǔ)槽的值。
與合約 ABI 類似,選擇器由簽名的 Keccak256-hash 的前四個(gè)字節(jié)組成。它的值可以使用.selector
如下成員從 Solidity 獲得:
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.5.14 <0.9.0; library L { function f(uint256) external {} } contract C { function g() public pure returns (bytes4) { return L.f.selector; } }
正如介紹中提到的,如果使用 aCALL
而不是DELEGATECALL
or來執(zhí)行庫的代碼,除非調(diào)用or函數(shù)CALLCODE
,否則它將恢復(fù)原狀。view
pure
EVM 沒有為合約提供直接的方法來檢測(cè)它是否被調(diào)用CALL
,但是合約可以使用ADDRESS
操作碼來找出它當(dāng)前運(yùn)行的“位置”。生成的代碼將此地址與構(gòu)建時(shí)使用的地址進(jìn)行比較,以確定調(diào)用方式。
更具體地說,庫的運(yùn)行時(shí)代碼總是以 push 指令開始,它在編譯時(shí)是 20 字節(jié)的零。當(dāng)部署代碼運(yùn)行時(shí),這個(gè)常量在內(nèi)存中被當(dāng)前地址替換,修改后的代碼存儲(chǔ)在合約中。在運(yùn)行時(shí),這會(huì)導(dǎo)致部署時(shí)間地址成為第一個(gè)被壓入堆棧的常量,并且調(diào)度程序代碼將當(dāng)前地址與任何非視圖和非純函數(shù)的該常量進(jìn)行比較。
這意味著存儲(chǔ)在鏈上的庫的實(shí)際代碼與編譯器報(bào)告的代碼不同 deployedBytecode
。
更多建議: