ブロックチェーン-solidityインテリジェント契約アーキテクチャの探索


solidity言語チュートリアル:http://www.tryblockchain.org/公式文書翻訳:https://solidity-cn.readthedocs.io/zh/develop/
説明:本明細書のファクトリモード、契約レジストリ、契約呼び出し外部契約のいくつかのモードにより、契約のデカップリング、契約呼び出し、契約アップグレードを実現し、javaのような大型dappを開発することができる.
工場モード
エンジニアリングモード:1つの契約で複数の契約を作成および管理できます.工場契約書にサブ契約書の住所を記録して保存し、工事契約書を通じてサブ契約書を呼び出す方法が原理です.区块链-solidity智能合约架构探索_第1张图片例を挙げます.
各呼び出し者に独自のカウンタがあり、アクセス回数を記録する簡単なカウンタ契約を作成します.もし1 W人のユーザーがいるならば、1 W個の契約を作成して、1 W個の住所を管理する必要があります.ここではこの1 W個の住所を人工的に管理する必要はありません.工場を通じて作成して管理します.
contract Counter {
 
    address owner;
    address factory;
    uint count = 0;
 
    function Counter(address _owner) { //     
        owner = _owner;
        factory = msg.sender;
    }
 
    modifier isOwner(address _caller) { //             
        require(msg.sender == factory);
        require(_caller == owner);
        _;
    }
     
    function increment(address caller) public isOwner(caller) { //        ,              
       count = SafeMath.add(count, 1);  //           
    }
 
    function getCount() constant returns (uint) { //      count  
       return count;
    }
 
}

Counterは、ユーザーごとに独自のカウンタ契約であり、1つも役に立たない.呼び出し回数の統計に使用します.ファクトリモードでは、まずmappingを使用してユーザーと契約アドレスの関係を記録します.mapping(address => address) counters;契約を作成するにはnewキーワードを使用します.counters[msg.sender] = new Counter(msg.sender);
アドレスを契約に変換し、契約を呼び出します.Counter(counters[msg.sender]).increment(msg.sender);
完全なコードは次のとおりです.
pragma solidity ^0.4.10;
  
/**
 * @title SafeMath
 * @dev Unsigned math operations with safety checks that revert on error
 */
library SafeMath {
    /**
    * @dev Multiplies two unsigned integers, reverts on overflow.
    */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
        if (a == 0) {
            return 0;
        }
 
        uint256 c = a * b;
        require(c / a == b);
 
        return c;
    }
 
    /**
    * @dev Integer division of two unsigned integers truncating the quotient, reverts on division by zero.
    */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        // Solidity only automatically asserts when dividing by 0
        require(b > 0);
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold
 
        return c;
    }
 
    /**
    * @dev Subtracts two unsigned integers, reverts on overflow (i.e. if subtrahend is greater than minuend).
    */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b <= a);
        uint256 c = a - b;
 
        return c;
    }
 
    /**
    * @dev Adds two unsigned integers, reverts on overflow.
    */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a);
 
        return c;
    }
 
    /**
    * @dev Divides two unsigned integers and returns the remainder (unsigned integer modulo),
    * reverts when dividing by zero.
    */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b != 0);
        return a % b;
    }
}
 
contract Counter {
 
    address owner;
    address factory;
    uint count = 0;
 
    function Counter(address _owner) { //     
        owner = _owner;
        factory = msg.sender;
    }
 
    modifier isOwner(address _caller) { //             
        require(msg.sender == factory);
        require(_caller == owner);
        _;
    }
     
    function increment(address caller) public isOwner(caller) { //        ,              
       count = SafeMath.add(count, 1);  //           
    }
 
    function getCount() constant returns (uint) { //      count  
       return count;
    }
}
 
contract CounterFactory {
  
    mapping(address => address) counters; //            map 
 
    function createCounter() public {
        if (counters[msg.sender] == 0) {//             ,    Counter     
            counters[msg.sender] = new Counter(msg.sender);
        }
    }
     
    function increment() public {
        require (counters[msg.sender] != 0); //            Counter  
        Counter(counters[msg.sender]).increment(msg.sender);//              
    }
     
    function getCount(address account) public constant returns (uint) {
        if (counters[account] != 0) {
            return (Counter(counters[account]).getCount());//  Counter   count  
        }
    }
 
  
}

契約の導入にあたっては、主にCounterFactory:区块链-solidity智能合约架构探索_第2张图片を導入し、契約を導入した後、異なるテストアカウントを切り替えてテストすればよい.
契約レジストリ
多くの場合、私たちが契約を書くときは1つの契約だけではなく、通常は複数の契約が必要です.いずれかの契約がアップグレードされた場合、他の契約は、変更なしに最新の契約を呼び出すことができます.この場合、契約レジストリを使用する必要があります.
契約レジストリは、契約名=>契約アドレスのマッピングテーブルです.これにより、契約を取得し、関連契約内のメソッドを呼び出すことができます.
金鎖盟のようなCNSドメイン名サービス
完全契約の例
pragma solidity ^0.4.10;
 
contract NameRegistry {
   //       
   struct ContractDetails {
      address owner;
      address contractAddress;
      uint16 version;
   }
    
   mapping(string => ContractDetails) registry;//          map: 
    
   function registerName(string name, address addr, uint16 ver) constant returns (bool) { //  
      require(ver >= 1);//       version  >=1
       
      ContractDetails memory info = registry[name];
       
      if (info.contractAddress == address(0)) {//     
          info = ContractDetails({
             owner: msg.sender,
             contractAddress: addr,
             version: ver
          });
       } else {//       ,         
          require(info.owner == msg.sender);
          info.version = ver;
          info.contractAddress = addr;
       }
        
       registry[name] = info;//  map
       return true;
   }
    
    
    function getContractDetails(string name) constant returns(address, uint16) { //           ,       version
      return (registry[name].contractAddress, registry[name].version);
   }
}

契約は別の契約を呼び出す
ここでは、契約が別々に配置され、契約アドレスを介して呼び出されることを示します.
最初の契約:
pragma solidity ^0.4.0;

contract MyContract1 {

function f(uint data) constant returns (uint){
return data + 2;
}
}

最初の契約を導入し、契約住所0x6bc7ea1c744185d0ded0141d08a4dbc2978b5991を取得します.
2番目の契約MyContract2が作成され、上記MyContract 1の契約アドレスに基づいてMyContract 1が初期化される.これで最初の契約のメソッドを呼び出すことができます.
pragma solidity ^0.4.0;

contract MyContract1{
function f(uint data) constant returns (uint);
}

contract MyContract2{

MyContract1 myContract1 = MyContract1(0x6bc7ea1c744185d0ded0141d08a4dbc2978b5991);

function AddData(uint value) constant returns (uint){
return myContract1.f(value);
}
}

2番目の契約を配置します.テストdemoMyContract 2を作成する.jsコード(コアコードのみをリスト):
  name=instance.AddData(31);
  console.log("=== " + name.toString());

実行コード:出力結果は31+2=33レジストリと組み合わせて分散プログラミングを実現
これにより、上記の契約レジストリに合わせてjavaのような分散プログラミングを実現できます.
MyContract 1を配備し、addressを取得し、このaddressを契約レジストリNameRegistryに登録し、契約MyContract 2で契約MyContract 1を呼び出すたびに、契約レジストリNameRegistryに最新のMyContract 1のアドレスを問い合わせることで、契約のアップグレードを実現します.
契約間はデカップリングされ,柔軟にアップグレードされ,このモデルは大型DAPPを開発することができる.
完全な操作プロセス:
1、まずNameRegistry契約を配置し、契約住所を得る:0 x 2 ad 1 ecffff 78 caa 5 f 5 a 61398 d 077 ec 1856 b 54567 2、それからMyContract 2契約を修正して配置する:
pragma solidity ^0.4.0;

contract MyContract1{
function f(uint data) constant returns (uint);
}
contract NameRegistry {
function getContractDetails(string name) constant returns(address);
}

contract MyContract2{

//          (  address NameRegistry     )

NameRegistry nameRegistry = NameRegistry(0x2ad1ecffff78caa5f5a61398d077ec1856b54567);

function AddData(uint value) constant returns (uint){

//          MyContract1      MyContract1
MyContract1 myContract1 = MyContract1(nameRegistry.getContractDetails("myContract1"));
return myContract1.f(value);
}
}

3、MyContract 1を配備し、契約住所0 xe 7 a 58975 b 465 b 96658 f 5 db 80 f 057 f 2 e 25 c 80 f 594を得る.
4、nodejsを使用してレジストリに登録する.
var func = "registerName(string,address,uint16)";
var params = ["myContract1", "0xe7a58975b465b96658f5db80f057f2e25c80f594", 2];
var receipt = await web3sync.sendRawTransaction(config.account, config.privKey, address, func, params);

name=instance.getContractDetails("myContract1");
console.log("=== " + name.toString());

5、MyContract 2を呼び出すと、出力33が見える.6、テスト個別修正アップグレード契約MyContract 1ここ2は8に変更し、MyContract 2は変更する必要はありません.
pragma solidity ^0.4.0;

contract MyContract1 {

function f(uint data) constant returns (uint){
return data + 8; //   2   8
}
}

7、MyContract 1を配備し、レジストリに登録する.8、契約MyContract 2を呼び出すと、結果は31+8 = 39になった.
マッピングテーブルmapping反復器
solidityはmappingを提供していますが、友好的なアクセスは提供されていません.
ここでは,mapping外に追加メタデータ情報を記録することによって反復を実現する.
インスタンス(デモ用としてdemoのみで、セキュリティが不足しています):
pragma solidity ^0.4.10;
 
contract MappingIterator {
     
   mapping(string => string) elements;//map,      
   string[] keys;//  map key
    
   // mapping          keys
   function put(string key, string addr) returns (bool) {
      keys.push(key);
      elements[key] = addr;
      return true;
    }
    //  keys    ,keys    mapping  
    function getKeyCount() constant returns (uint) {
       return keys.length;
    }
     
    function getElementAtIndex(uint index) constant returns (string) {
       return elements[keys[index]];//            , keys          mapping key 。      key mapping  ,     mapping    
    }
     
    function getElement(string name) constant returns (string) {
       return elements[name];
    }
}

修飾子の使用
通常、権限やパラメータなど、メソッドを呼び出すときに検証されます.いずれもメソッドに書かれていると、多くのコード量が増加し、読みにくくなります.
function increment() public {
 if (owner == msg.sender) { 
 count = count + 1;
 }
}

modifier定義修飾子を使用して、プログラムの可読性とメンテナンス性を向上させます.
modifier onlyBy(address _account) {
 require(msg.sender == _account);
 _;
}

function increment() public onlyBy(owner) {
 count = count + 1;
}

修飾子関数を抽象化して、任意の条件判断をサポートすることもできます.
modifier onlyIf(bool _condition) {
 require(_condition);
 _;
}
 
function increment() public onlyIf(msg.sender == owner) {
 count = count + 1;
}

上記onlyIfは、任意の判断条件を受信し、具体的な条件は呼び出し元で提供される.
スペースで区切られたリストに複数の修飾子を指定することで、複数の修飾子を関数に適用し、表示される順序で評価します.
pragma solidity ^0.4.10;
 
contract MappingIterator {
      
   address owner;
   uint _count;
     
   function MappingIterator() {//    
      owner = msg.sender;
   }
     
   modifier onlyIf(bool _condition) {//        
        require(_condition);
        _;
    }
       
    function increment(uint count) public onlyIf(msg.sender == owner) onlyIf(count < 20) {//    
        _count = count + 1;
    }
      
      
    function get() constant returns (uint)  {
        return _count;
    }
}

修飾子を使用すると、プログラムの可読性が向上するだけでなく、権限の検証、データの書き込み制限、データ検証などの機能も可能になります.
契約を破棄する
契約自己破壊モードは、契約を終了するために使用され、これは、ブロックチェーンからこの契約を永続的に削除することを意味する.破棄されると、契約の機能を呼び出すこともできず、帳簿に取引を記録することもできません.
破棄された契約を処理する際に注意すべき問題があります.
  • 契約破棄後、その契約に送信された取引は破棄された契約に送信された資金に失敗し、永遠に失われる
  • 資金損失を回避するため、資金を送信する前に目標契約が依然として存在することを確保し、破棄された契約に対するすべての参照
  • を除去しなければならない.
    pragma solidity ^0.4.10;
     
    contract SelfDesctructionContract {
         
       address owner;
        
       string someValue;
        
       modifier ownerRestricted {
          require(owner == msg.sender);
          _;
       }
       //     
       function SelfDesctructionContract() {
          owner = msg.sender;
       }
        
       function setSomeValue(string value){
          someValue = value;
       }
       //      
       function destroyContract() ownerRestricted {
         suicide(owner);
       }
    }
    

    契約を導入すると、契約のメソッドを呼び出すことができます.suicide()を呼び出して契約の破棄を行うと、ここで契約メソッドを呼び出すのは失敗し、内部に資産がある場合、資産は永遠に消えます.その意味をよく知らない限り、慎重に使います.
    モジュール化および契約のアップグレード
    Javaアプリケーションのように、契約の作成にもモジュール化が必要であり、異なる機能で異なる契約を作成する必要があります.契約間はインタフェースを介して相互に呼び出される.
    契約をアップグレードする場合も、すべての契約を再配置する必要はありません.変更された契約だけを配置すればいいです.
    データ契約と操作契約の分離
    モジュール化と契約のアップグレードがあるため、データストレージ契約とデータ操作契約を分離する必要があります.ストレージ契約が再配置されると、古い契約に格納されているデータは使用できません.
    コード作成仕様
    このコード仕様については、以下を参照してください.https://www.jianshu.com/p/d616f387d811