Tech

Ethereumの言語 Solidityの仕様

  • このエントリーをはてなブックマークに追加

Solidityの仕様の自分用メモ、まとめです。

以下のwikiより僕が必要なところだけ大雑把に引用、まとめ、訳。
訳は超適当なので、ちゃんと知りたい人は、本家ドキュメントを読みましょう。

コメント

  • // 一行コメント
  • /* */ 複数行コメント
  • /// NatSpecコメント

  • bool
  • uint/intは、uint256/int256とaddress(16bit)のエイリアス

整数リテラル

略)

Etherと時間ユニット

  • 2 ether == 2000 finney は、true

Control Structures

  • switchとgotoは実装予定なし
  • if, else, while, for, break, continue, return がある
  • 型変換はないので、「if (1) { … }」は、無効

関数呼び出し

以下のサンプルのように、現コントラクトの関数は

  • 直接呼べる
  • 再帰的に呼べる
contract c {
  function g(uint a) returns (uint ret) { return f(); }
  function f() returns (uint ret) { return g(7) + f(); }
}

特別な変数と関数

  • block.coinbase (address): current block miner’s address
  • block.difficulty (uint): current block difficulty
  • block.gaslimit (uint): current block gaslimit
  • block.number (uint): current block number
  • block.blockhash (function(uint) returns (bytes32)): hash of the given block
  • block.timestamp (uint): current block timestamp
  • msg.data (bytes): complete calldata
  • msg.gas (uint): remaining gas
  • msg.sender (address): sender of the message (current call)
  • msg.value (uint): number of wei sent with the message
  • now (uint): current block timestamp (alias for block.timestamp)
  • tx.gasprice (uint): gas price of the transaction
  • tx.origin (address): sender of the transaction (full call chai

暗号系の関数

  • sha3(…) returns (bytes32): compute the SHA3 hash of the (tightly packed) arguments
  • sha256(…) returns (bytes32): compute the SHA256 hash of the (tightly packed) arguments
  • ripemd160(…) returns (bytes20): compute RIPEMD of 256 the (tightly packed) arguments
  • ecrecover(bytes32, byte, bytes32, bytes32) returns (address): recover public key from elliptic curve signature

コントラクト関係

  • this (現コントラクトの型): 現コントラクト。明示的にアドレスに変換可能
  • suicide(address): 現コントラクトをsuicide。ファンドを与えられたアドレスに送る

アドレス上の関数

balanceプロパティとsend関数を使ってアドレスにEtherを送ることでアドレスの残高を要求することが可能??

address x = 0x123;
if (x.balance < 10 && address(this).balance >= 10) x.send(10);
  • ABI

Expressionの評価の順番

  • not specified?

配列

  • 動的・固定サイズの配列はサポートされている
contract ArrayContract {
  uint[2**20] m_aLotOfIntegers;
  bool[2][] m_pairsOfFlags;
  function setAllFlagPairs(bool[2][] newPairs) external {
    // assignment to array replaces the complete array
    m_pairsOfFlags = newPairs;
  }
  function setFlagPair(uint index, bool flagA, bool flagB) {
    // access to a non-existing index will stop execution
    m_pairsOfFlags[index][0] = flagA;
    m_pairsOfFlags[index][1] = flagB;
  }
  function changeFlagArraySize(uint newSize) {
    // if the new size is smaller, removed array elements will be cleared
    m_pairsOfFlags.length = newSize;
  }
  function clear() {
    // these clear the arrays completely
    delete m_pairsOfFlags;
    delete m_aLotOfIntegers;
    // identical effect here
    m_pairsOfFlags.length = 0;
  }
  bytes m_byteData;
  function byteArrays(bytes data) external {
    // byte arrays ("bytes") are different as they are stored without padding,
    // but can be treated identical to "uint8[]"
    m_byteData = data;
    m_byteData.length += 7;
    m_byteData[3] = 8;
    delete m_byteData[2];
  }
}

構造体(Structs)


例:

contract CrowdFunding {
  struct Funder {
    address addr;
    uint amount;
  }
  struct Campaign {
    address beneficiary;
    uint fundingGoal;
    uint numFunders;
    uint amount;
    mapping (uint => Funder) funders;
  }
  uint numCampaigns;
  mapping (uint => Campaign) campaigns;
  function newCampaign(address beneficiary, uint goal) returns (uint campaignID) {
    campaignID = numCampaigns++; // campaignID is return variable
    Campaign c = campaigns[campaignID];  // assigns reference
    c.beneficiary = beneficiary;
    c.fundingGoal = goal;
  }
  function contribute(uint campaignID) {
    Campaign c = campaigns[campaignID];
    Funder f = c.funders[c.numFunders++];
    f.addr = msg.sender;
    f.amount = msg.value;
    c.amount += f.amount;
  }
  function checkGoalReached(uint campaignID) returns (bool reached) {
    Campaign c = campaigns[campaignID];
    if (c.amount < c.fundingGoal)
      return false;
    c.beneficiary.send(c.amount);
    c.amount = 0;
    return true;
  }
}

Assignment

(略)

Enums

例のまま

contract test {
    enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
    ActionChoices choices;
    ActionChoices constant defaultChoice = ActionChoices.GoStraight;
    function setGoStraight()
    {
        choices = ActionChoices.GoStraight;
    }
    function getChoice() returns (uint)
    {
        return uint(choices);
    }
    function getDefaultChoice() returns (uint)
    {
        return uint(defaultChoice);
    }
}

他のコントラクトとやり取りする

2つの方法がある

  • アドレスがわかっているコントラクトのメソッドを呼ぶ
  • 新しいコントラクトを作成する

以下のサンプルに2つの方法が記載されている

contract OwnedToken {
  // TokenCreator is a contract type that is defined below. It is fine to reference it
  // as long as it is not used to create a new contract.
  TokenCreator creator;
  address owner;
  bytes32 name;
  function OwnedToken(bytes32 _name) {
    address nameReg = 0x72ba7d8e73fe8eb666ea66babc8116a41bfb10e2;
    nameReg.call("register", _name);
    owner = msg.sender;
    // We do an explicit type conversion from `address` to `TokenCreator` and assume that the type of
    // the calling contract is TokenCreator, there is no real way to check.
    creator = TokenCreator(msg.sender);
    name = _name;
  }
  function changeName(bytes32 newName) {
    // Only the creator can alter the name -- contracts are explicitly convertible to addresses.
    if (msg.sender == address(creator)) name = newName;
  }
  function transfer(address newOwner) {
    // Only the current owner can transfer the token.
    if (msg.sender != owner) return;
    // We also want to ask the creator if the transfer is fine.
    // Note that this calls a function of the contract defined below.
    // If the call fails (e.g. due to out-of-gas), the execution here stops
    // immediately (the ability to catch this will be added later).
    if (creator.isTokenTransferOK(owner, newOwner))
      owner = newOwner;
  }
}
contract TokenCreator {
  function createToken(bytes32 name) returns (address tokenAddress) {
    // Create a new Token contract and return its address.
    return address(new OwnedToken(name));
  }
  function changeName(address tokenAddress, bytes32 name) {
    // We need an explicit type conversion because contract types are not part of the ABI.
    OwnedToken token = OwnedToken(tokenAddress);
    token.changeName(name);
  }
  function isTokenTransferOK(address currentOwner, address newOwner) returns (bool ok) {
    // Check some arbitrary condition.
    address tokenAddress = msg.sender;
    return (sha3(newOwner) & 0xff) == (bytes20(tokenAddress) & 0xff);
  }
}

コンストラクタの引数

Solidiyのコントラクトは、コントラクト・データ自身の終わりの後にコンストラクタの引数を期待している。
つまり、通常のABI形式のコンパイラによって返されるような形で、コンパイルされたバイトの後に置くことでコントラクトに引数を渡す。

コントラクトの継承

Solidityは、ポリモーフィズムを含むコードをコピーすることで、多重継承をサポートする。

contract owned {
    function owned() { owner = msg.sender; }
    address owner;
}

// Use "is" to derive from another contract. Derived contracts can access all members
// including private functions and state variables.
contract mortal is owned {
    function kill() { if (msg.sender == owner) suicide(owner); }
}

// These are only provided to make the interface known to the compiler.
contract Config { function lookup(uint id) returns (address adr) {} }
contract NameReg { function register(bytes32 name) {} function unregister() {} }

// Multiple inheritance is possible. Note that "owned" is also a base class of
// "mortal", yet there is only a single instance of "owned" (as for virtual
// inheritance in C++).
contract named is owned, mortal {
    function named(bytes32 name) {
        address ConfigAddress = 0xd5f9d8d94886e70b06e474c3fb14fd43e2f23970;
        NameReg(Config(ConfigAddress).lookup(1)).register(name);
    }

// Functions can be overridden, both local and message-based function calls take
// these overrides into account.
    function kill() {
        if (msg.sender == owner) {
            address ConfigAddress = 0xd5f9d8d94886e70b06e474c3fb14fd43e2f23970;
            NameReg(Config(ConfigAddress).lookup(1)).unregister();
// It is still possible to call a specific overridden function. 
            mortal.kill();
        }
    }
}

// If a constructor takes an argument, it needs to be provided in the header.
contract PriceFeed is owned, mortal, named("GoldFeed") {
   function updateInfo(uint newInfo) {
      if (msg.sender == owner) info = newInfo;
   }

   function get() constant returns(uint r) { return info; }

   uint info;
}

上記で書いたように、destructionリクエストをフォワードするために、mortal.kill() を呼ぶ。

contract mortal is owned {
    function kill() { if (msg.sender == owner) suicide(owner); }
}
contract Base1 is mortal {
    function kill() { /* do cleanup 1 */ mortal.kill(); }
}
contract Base2 is mortal {
    function kill() { /* do cleanup 2 */ mortal.kill(); }
}
contract Final is Base1, Base2 {
}

(略)

多重継承と線形化

(WIP)

抽象コントラクト

(WIP)

Visibility Specifiers

public, internal, private

(WIP)

Accessor Functions

(WIP)

Fallback関数

contract Test {
  function() { x = 1; }
  uint x;
}

contract Caller {
  function callTest(address testAddress) {
    Test(testAddress).call(0xabcdefgh); // hash does not exist
    // results in Test(testAddress).x becoming == 1.
  }
}

関数修飾子

  • _
  • onlyowner
  • modifier

Modifiers can be used to easily change the behaviour of functions, for example to automatically check a condition prior to executing the function. They are inheritable properties of contracts and may be overridden by derived contracts.

contract owned {
  function owned() { owner = msg.sender; }
  address owner;

  // This contract only defines a modifier but does not use it - it will
  // be used in derived contracts.
  // The function body is inserted where the special symbol "_" in the
  // definition of a modifier appears.
  modifier onlyowner { if (msg.sender == owner) _ }
}
contract mortal is owned {
  // This contract inherits the "onlyowner"-modifier from "owned" and
  // applies it to the "kill"-function, which causes that calls to "kill"
  // only have an effect if they are made by the stored owner.
  function kill() onlyowner {
    suicide(owner);
  }
}
contract priced {
  // Modifiers can receive arguments:
  modifier costs(uint price) { if (msg.value >= price) _ }
}
contract Register is priced, owned {
  mapping (address => bool) registeredAddresses;
  uint price;
  function Register(uint initialPrice) { price = initialPrice; }
  function register() costs(price) {
    registeredAddresses[msg.sender] = true;
  }
  function changePrice(uint _price) onlyowner {
    price = _price;
  }
}

Multiple modifiers can be applied to a function by specifying them in a whitespace-separated list and will be evaluated in order. Explicit returns from a modifier or function body immediately leave the whole function, while control flow reaching the end of a function or modifier body continues after the “_” in the preceding modifier. Arbitrary expressions are allowed for modifier arguments and in this context, all symbols visible from the function are visible in the modifier. Symbols introduced in the modifier are not visible in the function (as they might change by overriding).

イベント

イベントは、EVMのロギング・ファシリティの容易な利用を許す。イベントは、コントラクトの継承可能なメンバー。イベントが呼ばれた時に、引数がトランザクションログに保存される。最大3つのパラメータが、indexed属性を受け付けることができる。

(WIP)

contract ClientReceipt {
  event Deposit(address indexed _from, bytes32 indexed _id, uint _value);
  function deposit(bytes32 _id) {
    Deposit(msg.sender, _id, msg.value);
  }
}

Depositを呼ぶのは、log3(msg.value, 0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20, sha3(msg.sender), _id); と同じ。

Layout of State Variables in Storage

(WIP)

Statically-sized variables (everything except mapping and dynamically-sized array types) are laid out contiguously in storage starting from position 0. Multiple items that need less than 32 bytes are packed into a single storage slot if possible, according to the following rules:

The first item in a storage slot is stored lower-order aligned.
Elementary types use only that many bytes that are necessary to store them.
If an elementary type does not fit the remaining part of a storage slot, it is moved to the next storage slot.
Structs and array data always start a new slot and occupy whole slots (but items inside a struct or array are packed tightly according to these rules).
The elements of structs and arrays are stored after each other, just as if they were given explicitly.

Due to their unpredictable size, mapping and dynamically-sized array types use a sha3 computation to find the starting position of the value or the array data. These starting positions are always full stack slots.

The mapping or the dynamic array itself occupies an (unfilled) slot in storage at some position p according to the above rule (or by recursively applying this rule for mappings to mappings or arrays of arrays). For a dynamic array, this slot stores the number of elements in the array. For a mapping, the slot is unused (but it is needed so that two equal mappings after each other will use a different hash distribution). Array data is located at sha3(p) and the value corresponding to a mapping key k is located at sha3(k . p) where . is concatenation. If the value is again a non-elementary type, the positions are found by adding an offset of sha3(k . p).

So for the following contract snippet:

contract c {
  struct S { uint a; uint b; }
  uint x;
  mapping(uint => mapping(uint => S)) data;
}

The position of data[4][9].b is at sha3(uint256(9) . sha3(uint256(4) . uint(256(1))) + 1.

Esoteric Features

(WIP)

There are some types in Solidity’s type system that have no counterpart in the syntax. One of these types are the types of functions. But still, using var it is possible to have local variables of these types:

contract FunctionSelector {
  function select(bool useB, uint x) returns (uint z) {
    var f = a;
    if (useB) f = b;
    return f(x);
  }
  function a(uint x) returns (uint z) {
    return x * x;
  }
  function b(uint x) returns (uint z) {
    return 2 * x;
  }
}
Calling select(false, x) will compute x * x and select(true, x) will compute 2 * x.

Internals – the Optimizer

(WIP)

チートシート

(WIP)

以下、別のドキュメントから

Simple Sample

contract Coin {
    address minter;
    mapping (address => uint) balances;

    event Send(address from, address to, uint value);

    function Coin() {
        minter = msg.sender;
    }

    function mint(address owner, uint amount) {
        if (msg.sender != minter) return;
        balances[owner] += amount;
    }
    function send(address receiver, uint amount) {
        if (balances[msg.sender] < amount) return;
        balances[msg.sender] -= amount;
        balances[receiver] += amount;
        Send(msg.sender, receiver, amount);
    }
    function queryBalance(address addr) constant returns (uint balance) {
        return balances[addr];
    }
}
  • uint: unsigned integer of 256 bits
  • address: a 160 bit value that does not allow any arithmetic operations
  • the state variable balances is of a complex datatype that maps addresses to unsigned integers
  • Mappings can be seen as hashtables which are virtually initialized such that every possible key exists and is mapped to a value whose byte-representation is all zeros
  • このエントリーをはてなブックマークに追加