亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

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

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

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

發布時間:2021-12-07 15:28:46 來源:億速云 閱讀:185 作者:iii 欄目:互聯網科技

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

1

合約定義(Contract)

Solidity 合約類似于面向對象語言中的類。合約中有用于數據持久化的狀態變量,和可以修改狀態變量的函數。 調用另一個合約實例的函數時,會執行一個 EVM 函數調用,這個操作會切換執行時的上下文,這樣,前一個合約的狀態變量就不能訪問了。

1.1 創建合約

可以通過以太坊交易“從外部”或從 Solidity 合約內部創建合約。

一些集成開發環境,例如 Remix, 通過使用一些用戶界面元素使創建過程更加流暢。 在以太坊上編程創建合約最好使用 JavaScript API web3.js。 現在,我們已經有了一個叫做 web3.eth.Contract 的方法能夠更容易的創建合約。

創建合約時,會執行一次構造函數(與合約同名的函數)。構造函數是可選的。只允許有一個構造函數,這意味著不支持重載。

在內部,構造函數參數在合約代碼之后通過 ABI 編碼 傳遞,但是如果你使用 web3.js 則不必關心這個問題。

如果一個合約想要創建另一個合約,那么創建者必須知曉被創建合約的源代碼(和二進制代碼)。 這意味著不可能循環創建依賴項。

pragma solidity ^0.4.16;

contract OwnedToken {

    // TokenCreator 是如下定義的合約類型.

    // 不創建新合約的話,也可以引用它。

    TokenCreator creator;

    address owner;

    bytes32 name;

    // 這是注冊 creator 和設置名稱的構造函數。

    function OwnedToken(bytes32 _name) public {

        // 狀態變量通過其名稱訪問,而不是通過例如 this.owner 的方式訪問。

        // 這也適用于函數,特別是在構造函數中,你只能像這樣(“內部地”)調用它們,

        // 因為合約本身還不存在。

        owner = msg.sender; 

       // 從 `address` 到 `TokenCreator` ,是做顯式的類型轉換

        // 并且假定調用合約的類型是 TokenCreator,沒有真正的方法來檢查這一點。

        creator = TokenCreator(msg.sender);

        name = _name;

    }

    function changeName(bytes32 newName) public {

        // 只有 creator (即創建當前合約的合約)能夠更改名稱 —— 因為合約是隱式轉換為地址的,

        // 所以這里的比較是可行的。

        if (msg.sender == address(creator))

            name = newName;

    }

    function transfer(address newOwner) public {

        // 只有當前所有者才能發送 token。

        if (msg.sender != owner) return;

        // 我們也想詢問 creator 是否可以發送。

        // 請注意,這里調用了一個下面定義的合約中的函數。

        // 如果調用失敗(比如,由于 gas 不足),會立即停止執行。

        if (creator.isTokenTransferOK(owner, newOwner))

            owner = newOwner;

    }

}

contract TokenCreator {

    function createToken(bytes32 name)

       public

       returns (OwnedToken tokenAddress)

    {

        // 創建一個新的 Token 合約并且返回它的地址。

        // 從 JavaScript 方面來說,返回類型是簡單的 `address` 類型,因為

        // 這是在 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)

合約函數可以缺少實現,如下例所示(請注意函數聲明頭由 ; 結尾):

pragma solidity ^0.4.0;

contract Feline {

    function utterance() public returns (bytes32);
}

這些合約無法成功編譯(即使它們除了未實現的函數還包含其他已經實現了的函數),但他們可以用作基類合約:

pragma solidity ^0.4.0;

contract Feline {

    function utterance() public returns (bytes32);

}

contract Cat is Feline {

    function utterance() public returns (bytes32) { return "miaow"; }

}

如果合約繼承自抽象合約,并且沒有通過重寫來實現所有未實現的函數,那么它本身就是抽象的。

3

接口(Interface)

接口類似于抽象合約,但是它們不能實現任何函數。還有進一步的限制:

  • 無法繼承其他合約或接口。

  • 無法定義構造函數。

  • 無法定義變量。

  • 無法定義結構體

  • 無法定義枚舉。 將來可能會解除這里的某些限制。

接口基本上僅限于合約 ABI 可以表示的內容,并且 ABI 和接口之間的轉換應該不會丟失任何信息。

接口由它們自己的關鍵字表示:

pragma solidity ^0.4.11;

interface Token {

    function transfer(address recipient, uint amount) public;
}

4

庫(Libary)

庫與合約類似,它們只需要在特定的地址部署一次,并且它們的代碼可以通過 EVM 的 DELEGATECALL (Homestead 之前使用 CALLCODE 關鍵字)特性進行重用。 這意味著如果庫函數被調用,它的代碼在調用合約的上下文中執行,即 this 指向調用合約,特別是可以訪問調用合約的存儲。 因為每個庫都是一段獨立的代碼,所以它僅能訪問調用合約明確提供的狀態變量(否則它就無法通過名字訪問這些變量)。 因為我們假定庫是無狀態的,所以如果它們不修改狀態(也就是說,如果它們是 view 或者 pure 函數), 庫函數僅可以通過直接調用來使用(即不使用 DELEGATECALL 關鍵字), 特別是,除非能規避 Solidity 的類型系統,否則是不可能銷毀任何庫的。

庫可以看作是使用他們的合約的隱式的基類合約。雖然它們在繼承關系中不會顯式可見,但調用庫函數與調用顯式的基類合約十分類似 (如果 L 是庫的話,可以使用 L.f() 調用庫函數)。此外,就像庫是基類合約一樣,對所有使用庫的合約,庫的 internal 函數都是可見的。 當然,需要使用內部調用約定來調用內部函數,這意味著所有內部類型,內存類型都是通過引用而不是復制來傳遞。 為了在 EVM 中實現這些,內部庫函數的代碼和從其中調用的所有函數都在編譯階段被拉取到調用合約中,然后使用一個 JUMP 調用來代替 DELEGATECALL。

下面的示例說明如何使用庫(但也請務必看看 using for-https://solidity-cn.readthedocs.io/zh/develop/contracts.html?highlight=view#using-for 有一個實現 set 更好的例子)。

 library Set {

  // 我們定義了一個新的結構體數據類型,用于在調用合約中保存數據。

  struct Data { mapping(uint => bool) flags; }

  // 注意第一個參數是“storage reference”類型,因此在調用中參數傳遞的只是它的存儲地址而不是內容。

  // 這是庫函數的一個特性。如果該函數可以被視為對象的方法,則習慣稱第一個參數為 `self` 。

  function insert(Data storage self, uint value)

      public

      returns (bool)

  {

      if (self.flags[value])

          return false; // 已經存在

      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 {

        // 不需要庫的特定實例就可以調用庫函數,

        // 因為當前合約就是“instance”。

        require(Set.insert(knownValues, value));

    }

    // 如果我們愿意,我們也可以在這個合約中直接訪問 knownValues.flags。

}

當然,你不必按照這種方式去使用庫:它們也可以在不定義結構數據類型的情況下使用。 函數也不需要任何存儲引用參數,庫可以出現在任何位置并且可以有多個存儲引用參數。

調用 Set.contains,Set.insert 和 Set.remove 都被編譯為外部調用( DELEGATECALL )。 如果使用庫,請注意實際執行的是外部函數調用。 msg.sender, msg.value 和 this 在調用中將保留它們的值, (在 Homestead 之前,因為使用了 CALLCODE,改變了 msg.sender 和 msg.value)。

以下示例展示了如何在庫中使用內存類型和內部函數來實現自定義類型,而無需支付外部函數調用的開銷:

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

            // 太差了,我們需要增加一個 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);

    }

}

由于編譯器無法知道庫的部署位置,我們需要通過鏈接器將這些地址填入最終的字節碼中 (請參閱 使用命令行編譯器-https://solidity-cn.readthedocs.io/zh/develop/using-the-compiler.html#commandline-compiler 以了解如何使用命令行編譯器來鏈接字節碼)。 如果這些地址沒有作為參數傳遞給編譯器,編譯后的十六進制代碼將包含 Set____ 形式的占位符(其中 Set 是庫的名稱)。 可以手動填寫地址來將那 40 個字符替換為庫合約地址的十六進制編碼。

與合約相比,庫的限制:

  • 沒有狀態變量

  • 不能夠繼承或被繼承

  • 不能接收以太幣

(將來有可能會解除這些限制)

4.1 庫的調用保護

如果庫的代碼是通過 CALL 來執行,而不是 DELEGATECALL 或者 CALLCODE 那么執行的結果會被回退, 除非是對 view 或者 pure 函數的調用。

EVM 沒有為合約提供檢測是否使用 CALL 的直接方式,但是合約可以使用 ADDRESS 操作碼找出正在運行的“位置”。 生成的代碼通過比較這個地址和構造時的地址來確定調用模式。

更具體地說,庫的運行時代碼總是從一個 push 指令開始,它在編譯時是 20 字節的零。當部署代碼運行時,這個常數 被內存中的當前地址替換,修改后的代碼存儲在合約中。在運行時,這導致部署時地址是第一個被 push 到堆棧上的常數, 對于任何 non-view 和 non-pure 函數,調度器代碼都將對比當前地址與這個常數是否一致。

4.2 Using For

指令 using A for B; 可用于附加庫函數(從庫 A)到任何類型(B)。 這些函數將接收到調用它們的對象作為它們的第一個參數(像 Python 的 self 變量)。

using A for *; 的效果是,庫 A 中的函數被附加在任意的類型上。

在這兩種情況下,所有函數都會被附加一個參數,即使它們的第一個參數類型與對象的類型不匹配。 函數調用和重載解析時才會做類型檢查。

using A for B; 指令僅在當前作用域有效,目前僅限于在當前合約中,后續可能提升到全局范圍。 通過引入一個模塊,不需要再添加代碼就可以使用包括庫函數在內的數據類型。

讓我們用這種方式將 庫 中的 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; // 已經存在

      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; // 這里是關鍵的修改

    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 類型的所有變量都有與之相對應的成員函數。

        // 下面的函數調用和 `Set.insert(knownValues, value)` 的效果完全相同。

        require(knownValues.insert(value));

    }
}

也可以像這樣擴展基本類型:

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 {

        // 執行庫函數調用

        uint index = data.indexOf(_old);

        if (index == uint(-1)) 

           data.push(_new);

        else

            data[index] = _new;

    }

}

注意,所有庫調用都是實際的 EVM 函數調用。這意味著如果傳遞內存或值類型,都將產生一個副本,即使是 self 變量。 使用存儲引用變量是唯一不會發生拷貝的情況。

到此,關于“Solidity語法的合約/抽象合約/接口/庫的定義是什么”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

西青区| 阳新县| 南召县| 黄平县| 宁河县| 昂仁县| 三门县| 中超| 佛冈县| 林口县| 肃北| 长顺县| 龙井市| 于都县| 罗田县| 景洪市| 灵武市| 平谷区| 浙江省| 光泽县| 十堰市| 德清县| 枞阳县| 济南市| 浦江县| 大连市| 澄城县| 乡城县| 东山县| 天津市| 宜宾市| 阿拉善右旗| 保定市| 万源市| 星子县| 武功县| 资溪县| 张家川| 通州市| 垫江县| 融水|