您好,登錄后才能下訂單哦!
這篇文章主要介紹“Solidity語法的合約/抽象合約/接口/庫(kù)的定義是什么”,在日常操作中,相信很多人在Solidity語法的合約/抽象合約/接口/庫(kù)的定義是什么問題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”Solidity語法的合約/抽象合約/接口/庫(kù)的定義是什么”的疑惑有所幫助!接下來,請(qǐng)跟著小編一起來學(xué)習(xí)吧!
Solidity 合約類似于面向?qū)ο笳Z言中的類。合約中有用于數(shù)據(jù)持久化的狀態(tài)變量,和可以修改狀態(tài)變量的函數(shù)。 調(diào)用另一個(gè)合約實(shí)例的函數(shù)時(shí),會(huì)執(zhí)行一個(gè) EVM 函數(shù)調(diào)用,這個(gè)操作會(huì)切換執(zhí)行時(shí)的上下文,這樣,前一個(gè)合約的狀態(tài)變量就不能訪問了。
1.1 創(chuàng)建合約
可以通過以太坊交易“從外部”或從 Solidity 合約內(nèi)部創(chuàng)建合約。
一些集成開發(fā)環(huán)境,例如 Remix, 通過使用一些用戶界面元素使創(chuàng)建過程更加流暢。 在以太坊上編程創(chuàng)建合約最好使用 JavaScript API web3.js。 現(xiàn)在,我們已經(jīng)有了一個(gè)叫做 web3.eth.Contract 的方法能夠更容易的創(chuàng)建合約。
創(chuàng)建合約時(shí),會(huì)執(zhí)行一次構(gòu)造函數(shù)(與合約同名的函數(shù))。構(gòu)造函數(shù)是可選的。只允許有一個(gè)構(gòu)造函數(shù),這意味著不支持重載。
在內(nèi)部,構(gòu)造函數(shù)參數(shù)在合約代碼之后通過 ABI 編碼 傳遞,但是如果你使用 web3.js 則不必關(guān)心這個(gè)問題。
如果一個(gè)合約想要?jiǎng)?chuàng)建另一個(gè)合約,那么創(chuàng)建者必須知曉被創(chuàng)建合約的源代碼(和二進(jìn)制代碼)。 這意味著不可能循環(huán)創(chuàng)建依賴項(xiàng)。
pragma solidity ^0.4.16; contract OwnedToken { // TokenCreator 是如下定義的合約類型. // 不創(chuàng)建新合約的話,也可以引用它。 TokenCreator creator; address owner; bytes32 name; // 這是注冊(cè) creator 和設(shè)置名稱的構(gòu)造函數(shù)。 function OwnedToken(bytes32 _name) public { // 狀態(tài)變量通過其名稱訪問,而不是通過例如 this.owner 的方式訪問。 // 這也適用于函數(shù),特別是在構(gòu)造函數(shù)中,你只能像這樣(“內(nèi)部地”)調(diào)用它們, // 因?yàn)楹霞s本身還不存在。 owner = msg.sender; // 從 `address` 到 `TokenCreator` ,是做顯式的類型轉(zhuǎn)換 // 并且假定調(diào)用合約的類型是 TokenCreator,沒有真正的方法來檢查這一點(diǎn)。 creator = TokenCreator(msg.sender); name = _name; } function changeName(bytes32 newName) public { // 只有 creator (即創(chuàng)建當(dāng)前合約的合約)能夠更改名稱 —— 因?yàn)楹霞s是隱式轉(zhuǎn)換為地址的, // 所以這里的比較是可行的。 if (msg.sender == address(creator)) name = newName; } function transfer(address newOwner) public { // 只有當(dāng)前所有者才能發(fā)送 token。 if (msg.sender != owner) return; // 我們也想詢問 creator 是否可以發(fā)送。 // 請(qǐng)注意,這里調(diào)用了一個(gè)下面定義的合約中的函數(shù)。 // 如果調(diào)用失敗(比如,由于 gas 不足),會(huì)立即停止執(zhí)行。 if (creator.isTokenTransferOK(owner, newOwner)) owner = newOwner; } } contract TokenCreator { function createToken(bytes32 name) public returns (OwnedToken tokenAddress) { // 創(chuàng)建一個(gè)新的 Token 合約并且返回它的地址。 // 從 JavaScript 方面來說,返回類型是簡(jiǎn)單的 `address` 類型,因?yàn)? // 這是在 ABI 中可用的最接近的類型。 return new OwnedToken(name); } function changeName(OwnedToken tokenAddress, bytes32 name) public { // 同樣,`tokenAddress` 的外部類型也是 `address` 。 tokenAddress.changeName(name); } function isTokenTransferOK(address currentOwner, address newOwner) public view returns (bool ok) { // 檢查一些任意的情況。 address tokenAddress = msg.sender; return (keccak256(newOwner) & 0xff) == (bytes20(tokenAddress) & 0xff); } }
合約函數(shù)可以缺少實(shí)現(xiàn),如下例所示(請(qǐng)注意函數(shù)聲明頭由 ; 結(jié)尾):
pragma solidity ^0.4.0; contract Feline { function utterance() public returns (bytes32); }
這些合約無法成功編譯(即使它們除了未實(shí)現(xiàn)的函數(shù)還包含其他已經(jīng)實(shí)現(xiàn)了的函數(shù)),但他們可以用作基類合約:
pragma solidity ^0.4.0; contract Feline { function utterance() public returns (bytes32); } contract Cat is Feline { function utterance() public returns (bytes32) { return "miaow"; } }
如果合約繼承自抽象合約,并且沒有通過重寫來實(shí)現(xiàn)所有未實(shí)現(xiàn)的函數(shù),那么它本身就是抽象的。
接口類似于抽象合約,但是它們不能實(shí)現(xiàn)任何函數(shù)。還有進(jìn)一步的限制:
無法繼承其他合約或接口。
無法定義構(gòu)造函數(shù)。
無法定義變量。
無法定義結(jié)構(gòu)體
無法定義枚舉。 將來可能會(huì)解除這里的某些限制。
接口基本上僅限于合約 ABI 可以表示的內(nèi)容,并且 ABI 和接口之間的轉(zhuǎn)換應(yīng)該不會(huì)丟失任何信息。
接口由它們自己的關(guān)鍵字表示:
pragma solidity ^0.4.11; interface Token { function transfer(address recipient, uint amount) public; }
庫(kù)與合約類似,它們只需要在特定的地址部署一次,并且它們的代碼可以通過 EVM 的 DELEGATECALL (Homestead 之前使用 CALLCODE 關(guān)鍵字)特性進(jìn)行重用。 這意味著如果庫(kù)函數(shù)被調(diào)用,它的代碼在調(diào)用合約的上下文中執(zhí)行,即 this 指向調(diào)用合約,特別是可以訪問調(diào)用合約的存儲(chǔ)。 因?yàn)槊總€(gè)庫(kù)都是一段獨(dú)立的代碼,所以它僅能訪問調(diào)用合約明確提供的狀態(tài)變量(否則它就無法通過名字訪問這些變量)。 因?yàn)槲覀兗俣◣?kù)是無狀態(tài)的,所以如果它們不修改狀態(tài)(也就是說,如果它們是 view 或者 pure 函數(shù)), 庫(kù)函數(shù)僅可以通過直接調(diào)用來使用(即不使用 DELEGATECALL 關(guān)鍵字), 特別是,除非能規(guī)避 Solidity 的類型系統(tǒng),否則是不可能銷毀任何庫(kù)的。
庫(kù)可以看作是使用他們的合約的隱式的基類合約。雖然它們?cè)诶^承關(guān)系中不會(huì)顯式可見,但調(diào)用庫(kù)函數(shù)與調(diào)用顯式的基類合約十分類似 (如果 L 是庫(kù)的話,可以使用 L.f() 調(diào)用庫(kù)函數(shù))。此外,就像庫(kù)是基類合約一樣,對(duì)所有使用庫(kù)的合約,庫(kù)的 internal 函數(shù)都是可見的。 當(dāng)然,需要使用內(nèi)部調(diào)用約定來調(diào)用內(nèi)部函數(shù),這意味著所有內(nèi)部類型,內(nèi)存類型都是通過引用而不是復(fù)制來傳遞。 為了在 EVM 中實(shí)現(xiàn)這些,內(nèi)部庫(kù)函數(shù)的代碼和從其中調(diào)用的所有函數(shù)都在編譯階段被拉取到調(diào)用合約中,然后使用一個(gè) JUMP 調(diào)用來代替 DELEGATECALL。
下面的示例說明如何使用庫(kù)(但也請(qǐng)務(wù)必看看 using for-https://solidity-cn.readthedocs.io/zh/develop/contracts.html?highlight=view#using-for 有一個(gè)實(shí)現(xiàn) set 更好的例子)。
library Set { // 我們定義了一個(gè)新的結(jié)構(gòu)體數(shù)據(jù)類型,用于在調(diào)用合約中保存數(shù)據(jù)。 struct Data { mapping(uint => bool) flags; } // 注意第一個(gè)參數(shù)是“storage reference”類型,因此在調(diào)用中參數(shù)傳遞的只是它的存儲(chǔ)地址而不是內(nèi)容。 // 這是庫(kù)函數(shù)的一個(gè)特性。如果該函數(shù)可以被視為對(duì)象的方法,則習(xí)慣稱第一個(gè)參數(shù)為 `self` 。 function insert(Data storage self, uint value) public returns (bool) { if (self.flags[value]) return false; // 已經(jīng)存在 self.flags[value] = true; return true; } function remove(Data storage self, uint value) public returns (bool) { if (!self.flags[value]) return false; // 不存在 self.flags[value] = false; return true; } function contains(Data storage self, uint value) public view returns (bool) { return self.flags[value]; } } contract C { Set.Data knownValues; function register(uint value) public { // 不需要庫(kù)的特定實(shí)例就可以調(diào)用庫(kù)函數(shù), // 因?yàn)楫?dāng)前合約就是“instance”。 require(Set.insert(knownValues, value)); } // 如果我們?cè)敢?,我們也可以在這個(gè)合約中直接訪問 knownValues.flags。 }
當(dāng)然,你不必按照這種方式去使用庫(kù):它們也可以在不定義結(jié)構(gòu)數(shù)據(jù)類型的情況下使用。 函數(shù)也不需要任何存儲(chǔ)引用參數(shù),庫(kù)可以出現(xiàn)在任何位置并且可以有多個(gè)存儲(chǔ)引用參數(shù)。
調(diào)用 Set.contains,Set.insert 和 Set.remove 都被編譯為外部調(diào)用( DELEGATECALL )。 如果使用庫(kù),請(qǐng)注意實(shí)際執(zhí)行的是外部函數(shù)調(diào)用。 msg.sender, msg.value 和 this 在調(diào)用中將保留它們的值, (在 Homestead 之前,因?yàn)槭褂昧?CALLCODE,改變了 msg.sender 和 msg.value)。
以下示例展示了如何在庫(kù)中使用內(nèi)存類型和內(nèi)部函數(shù)來實(shí)現(xiàn)自定義類型,而無需支付外部函數(shù)調(diào)用的開銷:
library BigInt { struct bigint { uint[] limbs; } function fromUint(uint x) internal pure returns (bigint r) { r.limbs = new uint[](1); r.limbs[0] = x; } function add(bigint _a, bigint _b) internal pure returns (bigint 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 a = limb(_a, i); uint b = limb(_b, i); r.limbs[i] = a + b + carry; if (a + b < a || (a + b == uint(-1) && carry > 0)) carry = 1; else carry = 0; } if (carry > 0) { // 太差了,我們需要增加一個(gè) limb uint[] memory newLimbs = new uint[](r.limbs.length + 1); for (i = 0; i < r.limbs.length; ++i) newLimbs[i] = r.limbs[i]; newLimbs[i] = carry; r.limbs = newLimbs; } } function limb(bigint _a, uint _limb) internal pure returns (uint) { return _limb < _a.limbs.length ? _a.limbs[_limb] : 0; } function max(uint a, uint b) private pure returns (uint) { return a > b ? a : b; } } contract C { using BigInt for BigInt.bigint; function f() public pure { var x = BigInt.fromUint(7); var y = BigInt.fromUint(uint(-1)); var z = x.add(y); } }
由于編譯器無法知道庫(kù)的部署位置,我們需要通過鏈接器將這些地址填入最終的字節(jié)碼中 (請(qǐng)參閱 使用命令行編譯器-https://solidity-cn.readthedocs.io/zh/develop/using-the-compiler.html#commandline-compiler 以了解如何使用命令行編譯器來鏈接字節(jié)碼)。 如果這些地址沒有作為參數(shù)傳遞給編譯器,編譯后的十六進(jìn)制代碼將包含 Set____ 形式的占位符(其中 Set 是庫(kù)的名稱)。 可以手動(dòng)填寫地址來將那 40 個(gè)字符替換為庫(kù)合約地址的十六進(jìn)制編碼。
與合約相比,庫(kù)的限制:
沒有狀態(tài)變量
不能夠繼承或被繼承
不能接收以太幣
(將來有可能會(huì)解除這些限制)
4.1 庫(kù)的調(diào)用保護(hù)
如果庫(kù)的代碼是通過 CALL 來執(zhí)行,而不是 DELEGATECALL 或者 CALLCODE 那么執(zhí)行的結(jié)果會(huì)被回退, 除非是對(duì) view 或者 pure 函數(shù)的調(diào)用。
EVM 沒有為合約提供檢測(cè)是否使用 CALL 的直接方式,但是合約可以使用 ADDRESS 操作碼找出正在運(yùn)行的“位置”。 生成的代碼通過比較這個(gè)地址和構(gòu)造時(shí)的地址來確定調(diào)用模式。
更具體地說,庫(kù)的運(yùn)行時(shí)代碼總是從一個(gè) push 指令開始,它在編譯時(shí)是 20 字節(jié)的零。當(dāng)部署代碼運(yùn)行時(shí),這個(gè)常數(shù) 被內(nèi)存中的當(dāng)前地址替換,修改后的代碼存儲(chǔ)在合約中。在運(yùn)行時(shí),這導(dǎo)致部署時(shí)地址是第一個(gè)被 push 到堆棧上的常數(shù), 對(duì)于任何 non-view 和 non-pure 函數(shù),調(diào)度器代碼都將對(duì)比當(dāng)前地址與這個(gè)常數(shù)是否一致。
4.2 Using For
指令 using A for B; 可用于附加庫(kù)函數(shù)(從庫(kù) A)到任何類型(B)。 這些函數(shù)將接收到調(diào)用它們的對(duì)象作為它們的第一個(gè)參數(shù)(像 Python 的 self 變量)。
using A for *; 的效果是,庫(kù) A 中的函數(shù)被附加在任意的類型上。
在這兩種情況下,所有函數(shù)都會(huì)被附加一個(gè)參數(shù),即使它們的第一個(gè)參數(shù)類型與對(duì)象的類型不匹配。 函數(shù)調(diào)用和重載解析時(shí)才會(huì)做類型檢查。
using A for B; 指令僅在當(dāng)前作用域有效,目前僅限于在當(dāng)前合約中,后續(xù)可能提升到全局范圍。 通過引入一個(gè)模塊,不需要再添加代碼就可以使用包括庫(kù)函數(shù)在內(nèi)的數(shù)據(jù)類型。
讓我們用這種方式將 庫(kù) 中的 set 例子重寫:
// 這是和之前一樣的代碼,只是沒有注釋。 library Set { struct Data { mapping(uint => bool) flags; } function insert(Data storage self, uint value) public returns (bool) { if (self.flags[value]) return false; // 已經(jīng)存在 self.flags[value] = true; return true; } function remove(Data storage self, uint value) public returns (bool) { if (!self.flags[value]) return false; // 不存在 self.flags[value] = false; return true; } function contains(Data storage self, uint value) public view returns (bool) { return self.flags[value]; } } contract C { using Set for Set.Data; // 這里是關(guān)鍵的修改 Set.Data knownValues; function register(uint value) public { // Here, all variables of type Set.Data have // corresponding member functions. // The following function call is identical to // `Set.insert(knownValues, value)` // 這里, Set.Data 類型的所有變量都有與之相對(duì)應(yīng)的成員函數(shù)。 // 下面的函數(shù)調(diào)用和 `Set.insert(knownValues, value)` 的效果完全相同。 require(knownValues.insert(value)); } } 也可以像這樣擴(kuò)展基本類型: library Search { function indexOf(uint[] storage self, uint value) public view returns (uint) { for (uint i = 0; i < self.length; i++) if (self[i] == value) return i; return uint(-1); } } contract C { using Search for uint[]; uint[] data; function append(uint value) public { data.push(value); } function replace(uint _old, uint _new) public { // 執(zhí)行庫(kù)函數(shù)調(diào)用 uint index = data.indexOf(_old); if (index == uint(-1)) data.push(_new); else data[index] = _new; } }
注意,所有庫(kù)調(diào)用都是實(shí)際的 EVM 函數(shù)調(diào)用。這意味著如果傳遞內(nèi)存或值類型,都將產(chǎn)生一個(gè)副本,即使是 self 變量。 使用存儲(chǔ)引用變量是唯一不會(huì)發(fā)生拷貝的情況。
到此,關(guān)于“Solidity語法的合約/抽象合約/接口/庫(kù)的定義是什么”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。