Solidity入門知識點集

Solidity 的代碼都包裹在合約裏面. 一份合約就是以太應幣應用的基本模塊, 全部的變量和函數都屬於一份合約, 它是你全部應用的起點.前端

最基本的合約 — 每次創建一個新的項目時的第一段代碼(一份名爲 HelloWorld 的空合約以下:):

pragma solidity ^0.4.19;

contract HelloWorld {

}

版本指令

pragma solidity ^0.4.19;

狀態變量

狀態變量是被永久地保存在合約中。也就是說它們被寫入以太幣區塊鏈中,你能夠想象成寫入一個數據庫。web

contract Example {
  // 這個無符號整數將會永久的被保存在區塊鏈中
  uint myUnsignedInteger = 100;
}

無符號整數: uint

uint 無符號數據類型, 指其值不能是負數,對於有符號的整數存在名爲 int 的數據類型。數據庫

注: Solidity中, uint 其實是 uint256代名詞。你也能夠定義位數少的uints — uint8, uint16,
uint32, 等…… 但通常來說你願意使用簡單的 uint, 除非在某些特殊狀況下。

數學運算

  • 加法: x + y
  • 減法: x - y,
  • 乘法: x * y
  • 除法: x / y
  • 取模 / 求餘: x % y (例如, 13 % 5 餘 3, 由於13除以5,餘3)
  • 乘方: x ** y (x^y)

結構體

struct Person {
  uint age;
  string name;
}
結構體容許你生成一個更復雜的數據類型,它有多個屬性。

建立新的結構體

Person[] public people;
// 建立一個新的Person:
Person satoshi = Person(172, "Satoshi");

// 將新建立的satoshi添加進people數組:
people.push(satoshi);

數組

Solidity 支持兩種數組: 靜態 數組和動態 數組編程

// 固定長度爲2的靜態數組:
uint[2] fixedArray;
// 固定長度爲5的string類型的靜態數組:
string[5] stringArray;
// 動態數組,長度不固定,能夠動態添加元素:
uint[] dynamicArray;
// 結構體類型的數組
Person[] people;
記住:狀態變量被永久保存在區塊鏈中。因此在你的合約中建立動態數組來保存成結構的數據是很是有意義的。

公共數組

Person[] public people;
定義 public 數組, Solidity 會自動建立 getter 方法.
其它的合約能夠從這個數組讀取數據(但不能寫入數據),因此這在合約中是一個有用的保存公共數據的模式。

定義函數

function eatHamburgers(string _name, uint _amount) {

}

eatHamburgers("vitalik", 100);
注:習慣上函數裏的變量都是以(_)開頭 (但不是硬性規定) 以區別全局變量。

私有 / 公共函數

Solidity 定義的函數的屬性默認爲公共。 這就意味着任何一方 (或其它合約) 均可以調用你合約裏的函數。數組

顯然,不是何時都須要這樣,並且這樣的合約易於受到攻擊。 因此將本身的函數定義爲私有是一個好的編程習慣,只有當你須要外部世界調用它時纔將它設置爲公共。安全

// 定義一個私有的函數, 私有函數的名字用(_)起始
uint[] numbers;

function _addToArray(uint _number) private {
  numbers.push(_number);
}

只有咱們合約中的其它函數纔可以調用這個函數,給 numbers 數組添加新成員。app

函數的返回值

string greeting = "What's up dog";

function sayHello() public returns (string) {
  return greeting;
}

函數的修飾符: view / pure

view: 意味着它只能讀取數據不能更改數據
pure: 不讀取區塊鏈上的數據

Keccak256生成僞隨機數

keccak256("aaaab");
//6e91ec6b618bb462a4a6ee5aa2cb0e9cf30f7a052bb467b0ba58b8748c00d2e5
keccak256("aaaac");
//b1f078126895a1424524de5321b339ab00408010b7cf0e6ed451514981e58aa9
注: 在區塊鏈中安全地產生一個隨機數是一個很難的問題

類型轉換

uint8 a = 5;
uint b = 6;
// 將會拋出錯誤,由於 a * b 返回 uint, 而不是 uint8:
uint8 c = a * b;
// 咱們須要將 b 轉換爲 uint8:
uint8 c = a * uint8(b);

事件

事件 是合約和區塊鏈通信的一種機制。你的前端應用「監聽」某些事件,並作出反應。函數

// 這裏創建事件
event IntegersAdded(uint x, uint y, uint result);

function add(uint _x, uint _y) public {
  uint result = _x + _y;
  //觸發事件,通知app
  IntegersAdded(_x, _y, result);
  return result;
}

你的 app 前端能夠監聽這個事件。JavaScript 實現以下:區塊鏈

web3.eth.contract(abi).at('0x78e97bcc5b5dd9ed228fed7a4887c0d7287344a9').IntegersAdded(function(error, result) { 
  // 幹些事
}

Addresses (地址)

以太坊區塊鏈由 account (帳戶)組成,你能夠把它想象成銀行帳戶。一個賬戶的餘額是 以太幣 eth,你能夠和其餘賬戶之間支付和接受以太幣,就像你的銀行賬戶能夠電匯資金到其餘銀行賬戶同樣。ui

每一個賬戶都有一個「地址」,你能夠把它想象成銀行帳號。這是帳戶惟一的標識符,它看起來長這樣:

0x0cE446255506E92DF41614C46F1d6df9Cc969183

地址屬於特定用戶(或智能合約)的。

映射(Mapping)

映射 是另外一種在 Solidity 中存儲有組織數據的方法。

//對於金融應用程序,將用戶的餘額保存在一個 uint類型的變量中:
mapping (address => uint) public accountBalance;
//或者能夠用來經過userId 存儲/查找的用戶名
mapping (uint => string) userIdToName;
映射本質上是存儲和查找數據所用的鍵-值對。在第一個例子中,鍵是一個 address,值是一個
uint,在第二個例子中,鍵是一個uint,值是一個 string。

Msg.sender

在 Solidity 中,有一些全局變量能夠被全部函數調用。 其中一個就是 msg.sender,它指的是當前調用者(或智能合約)的 address。

注意:在 Solidity 中,功能執行始終須要從外部調用者開始。 一個合約只會在區塊鏈上什麼也不作,除非有人調用其中的函數。因此
msg.sender老是存在的。

如下是使用 msg.sender 來更新 mapping 的例子:

mapping (address => uint) favoriteNumber;

function setMyNumber(uint _myNumber) public {
  // 更新咱們的 `favoriteNumber` 映射來將 `_myNumber`存儲在 `msg.sender`名下
  favoriteNumber[msg.sender] = _myNumber;
  // 存儲數據至映射的方法和將數據存儲在數組類似
}

function whatIsMyNumber() public view returns (uint) {
  // 拿到存儲在調用者地址名下的值
  // 若調用者還沒調用 setMyNumber, 則值爲 `0`
  return favoriteNumber[msg.sender];
}

在這個例子中,任何人均可以調用 setMyNumber 在咱們的合約中存下一個 uint 而且與他們的地址相綁定。 而後,他們調用 whatIsMyNumber 就會返回他們存儲的 uint。

使用 msg.sender 很安全,由於它具備以太坊區塊鏈的安全保障 —— 除非竊取與以太坊地址相關聯的私鑰,不然是沒有辦法修改其餘人的數據的。

Require

require使得函數在執行過程當中,當不知足某些條件時拋出錯誤,並中止執行。

function sayHiToVitalik(string _name) public returns (string) {
  // 比較 _name 是否等於 "Vitalik". 若是不成立,拋出異常並終止程序
  // (敲黑板: Solidity 並不支持原生的字符串比較, 咱們只能經過比較
  // 兩字符串的 keccak256 哈希值來進行判斷)
  require(keccak256(_name) == keccak256("Vitalik"));
  // 若是返回 true, 運行以下語句
  return "Hi!";
}

繼承(Inheritance)

當代碼過於冗長的時候,最好將代碼和邏輯分拆到多個不一樣的合約中,以便於管理。合約繼承用is關鍵字。

contract Doge {
  function catchphrase() public returns (string) {
    return "So Wow CryptoDoge";
  }
}

contract BabyDoge is Doge {
  function anotherCatchphrase() public returns (string) {
    return "Such Moon BabyDoge";
  }
}

因爲 BabyDoge 是從 Doge 那裏 inherits (繼承)過來的。 這意味着當你編譯和部署了 BabyDoge,它將能夠訪問 catchphrase() 和 anotherCatchphrase()和其餘咱們在 Doge 中定義的其餘公共函數。

引入(Import)

上面繼承的例子中,若是分兩個文件,就須要引入。

import "./someothercontract.sol";

contract newContract is SomeOtherContract {

}

Storage與Memory

在 Solidity 中,有兩個地方能夠存儲變量 —— storage 或 memory

Storage 變量是指永久存儲在區塊鏈中的變量。 Memory 變量則是臨時的,當外部函數對某合約調用完成時,內存型變量即被移除。
你能夠把它想象成存儲在你電腦的硬盤或是RAM中數據的關係。

大多數時候你都用不到這些關鍵字,默認狀況下 Solidity 會自動處理它們。 狀態變量(在函數以外聲明的變量)默認爲「存儲」形式,並永久寫入區塊鏈;而在函數內部聲明的變量是「內存」型的,它們函數調用結束後消失。

然而也有一些狀況下,你須要手動聲明存儲類型,主要用於處理函數內的 結構體 和 數組 時:

contract SandwichFactory {
  struct Sandwich {
    string name;
    string status;
  }

  Sandwich[] sandwiches;

  function eatSandwich(uint _index) public {
    // Sandwich mySandwich = sandwiches[_index];

    // ^ 看上去很直接,不過 Solidity 將會給出警告
    // 告訴你應該明確在這裏定義 `storage` 或者 `memory`。

    // 因此你應該明肯定義 `storage`:
    Sandwich storage mySandwich = sandwiches[_index];
    // ...這樣 `mySandwich` 是指向 `sandwiches[_index]`的指針
    // 在存儲裏,另外...
    mySandwich.status = "Eaten!";
    // ...這將永久把 `sandwiches[_index]` 變爲區塊鏈上的存儲

    // 若是你只想要一個副本,可使用`memory`:
    Sandwich memory anotherSandwich = sandwiches[_index + 1];
    // ...這樣 `anotherSandwich` 就僅僅是一個內存裏的副本了
    // 另外
    anotherSandwich.status = "Eaten!";
    // ...將僅僅修改臨時變量,對 `sandwiches[_index + 1]` 沒有任何影響
    // 不過你能夠這樣作:
    sandwiches[_index + 1] = anotherSandwich;
    // ...若是你想把副本的改動保存回區塊鏈存儲
  }
}

函數可見性: internal 和 external

internal 和 private 相似,不過, 若是某個合約繼承自其父合約,這個合約便可以訪問父合約中定義的「內部」函數。

external 與 public 相似,只不過這些函數只能在合約以外調用 - 它們不能被合約內的其餘函數調用。

contract Sandwich {
  uint private sandwichesEaten = 0;

  function eat() internal {
    sandwichesEaten++;
  }
}

contract BLT is Sandwich {
  uint private baconSandwichesEaten = 0;

  function eatWithBacon() public returns (string) {
    baconSandwichesEaten++;
    // 由於eat() 是internal 的,因此咱們能在這裏調用
    eat();
  }
}

與其餘合約的交互

若是咱們的合約須要和區塊鏈上的其餘的合約會話,則需先定義一個 interface (接口)。

假設在區塊鏈上有這麼一個合約:

contract LuckyNumber {
  mapping(address => uint) numbers;

  function setNum(uint _num) public {
    numbers[msg.sender] = _num;
  }

  function getNum(address _myAddress) public view returns (uint) {
    return numbers[_myAddress];
  }
}

如今假設咱們有一個外部合約,使用 getNum 函數可讀取其中的數據。

首先,咱們定義 LuckyNumber 合約的 interface :

contract NumberInterface {
  function getNum(address _myAddress) public view returns (uint);
}
在咱們的 app 代碼中使用這個接口,合約就知道其餘合約的函數是怎樣的,應該如何調用,以及可期待什麼類型的返回值。

使用接口

上面的接口,咱們能夠在合約中這樣使用:

contract MyContract {
  address NumberInterfaceAddress = 0xab38...;
  // ^ 這是FavoriteNumber合約在以太坊上的地址
  NumberInterface numberContract = NumberInterface(NumberInterfaceAddress);
  // 如今變量 `numberContract` 指向另外一個合約對象

  function someFunction() public {
    // 如今咱們能夠調用在那個合約中聲明的 `getNum`函數:
    uint num = numberContract.getNum(msg.sender);
    // ...在這兒使用 `num`變量作些什麼
  }
}

經過這種方式,只要將您合約的可見性設置爲public(公共)或external(外部),它們就能夠與以太坊區塊鏈上的任何其餘合約進行交互。

處理多返回值

function multipleReturns() internal returns(uint a, uint b, uint c) {
  return (1, 2, 3);
}

function processMultipleReturns() external {
  uint a;
  uint b;
  uint c;
  // 這樣來作批量賦值:
  (a, b, c) = multipleReturns();
}

// 或者若是咱們只想返回其中一個變量:
function getLastReturnValue() external {
  uint c;
  // 能夠對其餘字段留空:
  (,,c) = multipleReturns();
}

if 語句

function eatBLT(string sandwich) public {
  // 看清楚了,當咱們比較字符串的時候,須要比較他們的 keccak256 哈希碼
  if (keccak256(sandwich) == keccak256("BLT")) {
    eat();
  }
}

payable 修飾符

payable 方法是讓 Solidity 和以太坊變得如此酷的一部分 —— 它們是一種能夠接收以太的特殊函數。

在以太坊中, 由於錢 (以太), 數據 (事務負載), 以及合約代碼自己都存在於以太坊。你能夠在同時調用函數 並付錢給另一個合約。

contract OnlineStore {
  function buySomething() external payable {
    // 檢查以肯定0.001以太發送出去來運行函數:
    require(msg.value == 0.001 ether);
    // 若是爲真,一些用來向函數調用者發送數字內容的邏輯
    transferThing(msg.sender);
  }
}

在這裏,msg.value 是一種能夠查看向合約發送了多少以太的方法,另外 ether 是一個內建單元。

這裏發生的事是,一些人會從 web3.js 調用這個函數 (從DApp的前端), 像這樣 :

// 假設 OnlineStore 在以太坊上指向你的合約:

OnlineStore.buySomething().send(from: web3.eth.defaultAccount, value: web3.utils.toWei(0.001))
注意這個 value 字段, JavaScript
調用來指定發送多少(0.001)以太。若是把事務想象成一個信封,你發送到函數的參數就是信的內容。 添加一個 value 很像在信封裏面放錢
—— 信件內容和錢同時發送給了接收者。

提現

在你發送以太以後,它將被存儲進以合約的以太坊帳戶中, 並凍結在哪裏 —— 除非你添加一個函數來從合約中把以太提現。

你能夠寫一個函數來從合約中提現以太,相似這樣:

contract GetPaid is Ownable {
  function withdraw() external onlyOwner {
    owner.transfer(this.balance);
  }
}

你能夠經過 transfer 函數向一個地址發送以太, 而後 this.balance 將返回當前合約存儲了多少以太。 因此若是100個用戶每人向咱們支付1以太, this.balance 將是100以太。

你能夠經過 transfer 向任何以太坊地址付錢。 好比,你能夠有一個函數在 msg.sender 超額付款的時候給他們退錢:

uint itemFee = 0.001 ether;
msg.sender.transfer(msg.value - itemFee);
相關文章
相關標籤/搜索