溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Solidity語法的合約/抽象合約/接口/庫(kù)的定義是什么

發(fā)布時(shí)間:2021-12-07 15:28:46 來源:億速云 閱讀:179 作者:iii 欄目:互聯(lián)網(wǎng)科技

這篇文章主要介紹“Solidity語法的合約/抽象合約/接口/庫(kù)的定義是什么”,在日常操作中,相信很多人在Solidity語法的合約/抽象合約/接口/庫(kù)的定義是什么問題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”Solidity語法的合約/抽象合約/接口/庫(kù)的定義是什么”的疑惑有所幫助!接下來,請(qǐng)跟著小編一起來學(xué)習(xí)吧!

1

合約定義(Contract)

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);

    }

}

2

抽象合約(Abstract Contract)

合約函數(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ù),那么它本身就是抽象的。

3

接口(Interface)

接口類似于抽象合約,但是它們不能實(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;
}

4

庫(kù)(Libary)

庫(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í)用的文章!

向AI問一下細(xì)節(jié)

免責(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)容。

AI