区块链技术:智能合约入门
什么是智能合約
一個智能合約是一套以數字形式定義的承諾(promises) ,包括合約參與方可以在上面執行這些承諾的協議。一個合約由一組代碼(合約的函數)和數據(合約的狀態)組成,并且運行在以太坊虛擬機上.以太坊虛擬機(EVM)使用了256比特長度的機器碼,是一種基于堆棧的虛擬機,用于執行以太坊智能合約?。由于EVM是針對以太坊體系設計的,因此使用了以太坊賬戶模型(Account Model)進行價值傳輸。
合約的代碼具有什么能力:
讀取交易數據。 讀取或寫入合約自己的存儲空間。 讀取環境變量【塊高,哈希值,gas】 向另一個合約發送一個“內部交易”。在區塊鏈平臺的架構
區塊鏈平臺的架構
1. 什么是solidity
Solidity是一種智能合約高級語言,運行在Ethereum虛擬機(EVM)之上。
solidity 語言特點
它的語法接近于Javascript,是一種面向對象的語言。但作為一種真正意義上運行在網絡上的去中心合約,它有很多的不同點:
- 異常機制,類似于事務的原子性。一旦出現異常,所有的執行都將會被回撤,這主要是為了保證合約執行的原子性,以避免中間狀態出現的數據不一致。
- 運行環境是在去中心化的網絡上,會比較強調合約或函數執行的調用的方式。因為原來一個簡單的函數調用變為了一個網絡上的節點中的代碼執行
- 存儲是使用網絡上的區塊鏈,數據的每一個狀態都可以永久存儲。
2. 開發的工具
- 在線編譯器Remix
- Visual Studio Code + soliidty 插件
3 快速入門
準備工作:搭建區塊鏈
一鍵部署區塊鏈平臺
3.1 舉個例子
完整的步驟:
1. 寫合約 2. 編譯合約 3. 部署合約 4. 測試合約獲取例子get demo
參考操作步驟
$ git clone "https://github.com/cristicmf/bcos-qucik-start-demo" $ cd startDemo $ npm install $ babel-node index.js文件結構說明
startDemo ├── README.md ├── SimpleStartDemo.sol # 合約代碼 ├── codeUtils.js ├── config.js # 配置文件 ├── index.js # 部署合約和測試合約 ├── output # abi/bin/address的輸出 │?? ├── StartDemo.abi │?? ├── StartDemo.address │?? └── StartDemo.bin ├── package.json ├── sha3.js └── web3sync.js獲取例子
pragma solidity ^0.4.2;contract SimpleStartDemo {int256 storedData;event AddMsg(address indexed sender, bytes32 msg);modifier only_with_at_least(int x) {if (x >= 5) {x = x+10;_;}} function SimpleStartDemo() {storedData = 2;}function setData(int256 x) public only_with_at_least(x){storedData = x;AddMsg(msg.sender, "[in the set() method]");}function getData() constant public returns (int256 _ret) {AddMsg(msg.sender, "[in the get() method]");return _ret = storedData;}}3.2 部署合約
舉個例子get demo
$ babel-node index.js1. 編譯合約
execSync("solc --abi --bin --overwrite -o " + config.Ouputpath + " " + filename + ".sol");2. 部署合約到區塊鏈上
var Contract = await web3sync.rawDeploy(config.account,?config.privKey, filename);3. 對合約進行讀寫
var address = fs.readFileSync(config.Ouputpath + filename + '.address', 'utf-8');var abi = JSON.parse(fs.readFileSync(config.Ouputpath /*+filename+".sol:"*/ + filename + '.abi', 'utf-8'));var contract = web3.eth.contract(abi);var instance = contract.at(address);//獲取鏈上數據var data = instance.getData();//修改鏈上數據var func = "setData(int256)";var params = [10];var receipt = await web3sync.sendRawTransaction(config.account, config.privKey, address, func, params);3.2.1 引入概念:
address:以太坊地址的長度,大小20個字節,160位,所以可以用一個uint160編碼。地址是所有合約的基礎,所有的合約都會繼承地址對象,也可以隨時將一個地址串,得到對應的代碼進行調用。合約的地址是基于賬號隨機數和交易數據的哈希計算出來的
ABI:是以太坊的一種合約間調用時或消息發送時的一個消息格式。就是定義操作函數簽名,參數編碼,返回結果編碼等。
交易:以太坊中“交易”是指存儲從外部賬戶發出的消息的簽名數據包。
簡單理解是:只要對區塊鏈進行寫操作,一定會發生交易。
交易回執:
發生交易后的返回值
3.2.2 擴展閱讀:
- Ethereum-Contract-ABI
- Solidity-Features
- 以太坊白皮書
3.3 合約文件結構簡介
1. 版本聲明
pragma solidity ^0.4.10;1. 引用其它源文件
//全局引入1. 狀態變量(State Variables)
int256 storedData;詳細說明見下文
2. 函數(Functions)
function setData(int256 x) public {storedData = x;AddMsg(msg.sender, "[in the set() method]");}function getData() constant public returns (int256 _ret) {return _ret = storedData;}3. 事件(Events)
//事件的聲明event AddMsg(address indexed sender, bytes32 msg);//事件的使用function setData(int256 x) public {storedData = x;AddMsg(msg.sender, "in the set() method");}4. 結構類型(Structs Types)
contract Contract {struct {uint deadline;uint amount;} ; set(uint id, uint deadline, uint amount) {.deadline = deadline;.amount = amount;}}5. 函數修飾符(Function Modifiers)
類似于hook
modifier only_with_at_least(int x) {if (x >= 5) {x = x+10; _; }}4. 合約編程模式COP
面向條件的編程(COP)是面向合約編程的一個子域,作為一種面向函數和命令式編程的混合模式。COP解決了這個問題,通過需要程序員顯示地枚舉所有的條件。邏輯變得扁平,沒有條件的狀態變化。條件片段可以被正確的文檔化,復用,可以根據需求和實現來推斷。重要的是,COP在編程中把預先條件當作為一等公民。這樣的模式規范能保證合約的安全。
4.1 FEATURES
- 函數主體沒有條件判斷
例子:
contract Token {// The balance of everyonemapping (address => uint) public balances;// Constructor - we're a millionaire!function Token() {balances[msg.sender] = 1000000;}// Transfer `_amount` tokens of ours to `_dest`.function transfer(uint _amount, address _dest) {balances[msg.sender] -= _amount;balances[_dest] += _amount;} }改進后:
function transfer(uint _amount, address _dest) {if (balances[msg.sender] < _amount)return;balances[msg.sender] -= _amount;balances[_dest] += _amount; }COP的風格
modifier only_with_at_least(uint x) {if (balances[msg.sender] >= x) _; }function transfer(uint _amount, address _dest) only_with_at_least(_amount) {balances[msg.sender] -= _amount;balances[_dest] += _amount; }擴展閱讀:
- Condition-Orientated Programming
- Paper
5. 語法介紹
基礎語法見官方API
5.2 引用類型(Reference Types)
- 不定長字節數組(bytes)
- 字符串(string)
bytes3 a = "123"; - 數組(Array)
- 結構體(Struts)
6. 重要概念
6.1 Solidity的數據位置
數據位置的類型
變量的存儲位置屬性。有三種類型,memory,storage和calldata。
- memory存儲位置同我們普通程序的內存類似。即分配,即使用,越過作用域即不可被訪問,等待被回收-
- storage的變量,數據將永遠存在于區塊鏈上。
- calldata 數據位置比較特殊,一般只有外部函數的參數(不包括返回參數)被強制指定為calldata
Storage - 狀態變量的存儲模型
大小固定的變量(除了映射,變長數組以外的所有類型)在存儲(storage)中是依次連續從位置0開始排列的。如果多個變量占用的大小少于32字節,會盡可能的打包到單個storage槽位里,具體規則如下:
- 在storage槽中第一項是按低位對齊存儲(lower-order aligned)
- 基本類型存儲時僅占用其實際需要的字節。
- 如果基本類型不能放入某個槽位余下的空間,它將被放入下一個槽位。
- 結構體和數組總是使用一個全新的槽位,并占用整個槽(但在結構體內或數組內的每個項仍遵從上述規則)
優化建議:
為了方便EVM進行優化,嘗試有意識排序storage的變量和結構體的成員,從而讓他們能打包得更緊密。比如,按這樣的順序定義,uint128, uint128, uint256,而不是uint128, uint256, uint128。因為后一種會占用三個槽位。
Memory - 內存變量的布局(Layout in Memory)
Solidity預留了3個32字節大小的槽位:
0-64:哈希方法的暫存空間(scratch space)
64-96:當前已分配內存大小(也稱空閑內存指針(free memory pointer))
暫存空間可在語句之間使用(如在內聯編譯時使用)
Solidity總是在空閑內存指針所在位置創建一個新對象,且對應的內存永遠不會被釋放(也許未來會改變這種做法)。
有一些在Solidity中的操作需要超過64字節的臨時空間,這樣就會超過預留的暫存空間。他們就將會分配到空閑內存指針所在的地方,但由于他們自身的特點,生命周期相對較短,且指針本身不能更新,內存也許會,也許不會被清零(zerod out)。因此,大家不應該認為空閑的內存一定已經是清零(zeroed out)的。
例子
6.2 address
以太坊地址的長度,大小20個字節,160位,所以可以用一個uint160編碼。地址是所有合約的基礎,所有的合約都會繼承地址對象,也可以隨時將一個地址串,得到對應的代碼進行調用
6.3 event
event AddMsg(address indexed sender, bytes32 msg);
- 這行代碼聲明了一個“事件”。客戶端(服務端應用也適用)可以以很低的開銷來監聽這些由區塊鏈觸發的事件
事件是使用EVM日志內置功能的方便工具,在DAPP的接口中,它可以反過來調用Javascript的監聽事件的回調。
var event = instance.AddMsg({}, function(error, result) {if (!error) {var msg = "AddMsg: " + utils.hex2a(result.args.msg) + " from "console.log(msg); return;} else {console.log('it error')}});- 事件在合約中可被繼承。當被調用時,會觸發參數存儲到交易的日志中(一種區塊鏈上的特殊數據結構)。這些日志與合約的地址關聯,并合并到區塊鏈中,只要區塊可以訪問就一直存在(至少Frontier,Homestead是這樣,但Serenity也許也是這樣)。日志和事件在合約內不可直接被訪問,即使是創建日志的合約。
- 日志位置在nodedir0/log 里面,可以打出特殊的類型進行驗證
6.4 數組
數組是定長或者是變長數組。有length屬性,表示當前的數組長度。
一般使用定長的 bytes1~bytes32。在知道字符串長度的情況下,指定長度時,更加節省空間。
6.4.1 創建數組
new uint[] memory a = new uint[](7);
例子
6.4.2 數組的屬性和方法
length屬性
storage變長數組是可以修改length
memory變長數組是不可以修改length
push方法
storage變長數組可以使用push方法
bytes可以使用push方法
例子
pragma solidity ^0.4.2;contract SimpleStartDemo {uint[] stateVar;function f() returns (uint){//在元素初始化前使用stateVar.push(1);stateVar = new uint[](1);stateVar[0] = 0;//自動擴充長度uint pusharr = stateVar.push(1);uint len = stateVar.length;//不支持memory//Member "push" is not available in uint256[] memory outside of storage.//uint[] memory memVar = new uint[](1);//memVar.push(1);return len;} }下標:和其他語言類似
6.4.3 Memory數組
Memory數組是不能修改修改數組大小的屬性
例子
pragma solidity ^0.4.2;
contract SimpleStartDemo {
function f() {//創建一個memory的數組uint[] memory a = new uint[](7);//不能修改長度//Error: Expression has to be an lvalue.//a.length = 100;}//storageuint[] b;function g(){b = new uint[](7);//可以修改storage的數組b.length = 10;b[9] = 100;}}
EVM的限制
由于EVM的限制,不能通過外部函數直接返回動態數組和多維數組
業務場景
6.5 函數
function (<parameter types>) {internal(默認)|external} constant [returns (<return types>)]
6.5.1 函數的internal與external
例子
pragma solidity ^0.4.5;contract FuntionTest{function internalFunc() internal{}function externalFunc() external{}function callFunc(){//直接使用內部的方式調用internalFunc();//不能在內部調用一個外部函數,會報編譯錯誤。//Error: Undeclared identifier.//externalFunc();//不能通過`external`的方式調用一個`internal`//Member "internalFunc" not found or not visible after argument-dependent lookup in contract FuntionTest//this.internalFunc();//使用`this`以`external`的方式調用一個外部函數this.externalFunc();} } contract FunctionTest1{function externalCall(FuntionTest ft){//調用另一個合約的外部函數ft.externalFunc();//不能調用另一個合約的內部函數//Error: Member "internalFunc" not found or not visible after argument-dependent lookup in contract FuntionTest//ft.internalFunc();} }訪問函數有外部(external)可見性。如果通過內部(internal)的方式訪問,比如直接訪問,你可以直接把它當一個變量進行使用,但如果使用外部(external)的方式來訪問,如通過this.,那么它必須通過函數的方式來調用。
例子
pragma solidity ^0.4.2;contract SimpleStartDemo {uint public c = 10;function accessInternal() returns (uint){return c;}function accessExternal() returns (uint){return this.c();}}6.5.2 函數調用
- 內部調用,不會創建一個EVM調用,也叫消息調用
- 外部調用,創建EVM調用,會發起消息調用
6.5.3 函數修改器(Function Modifiers)
修改器(Modifiers)可以用來輕易的改變一個函數的行為。比如用于在函數執行前檢查某種前置條件。修改器是一種合約屬性,可被繼承,同時還可被派生的合約重寫(override)
例子
pragma solidity ^0.4.2;contract SimpleStartDemo {int256 storedData;event AddMsg(address indexed sender, bytes32 msg);modifier only_with_at_least(int x) {if (x >= 5) {x = x+10;_;}} function setData(int256 x) public only_with_at_least(x){storedData = x;AddMsg(msg.sender, "[in the set() method]");}}6.5.4合約構造函數 同名函數
- 可選
- 僅能有一個構造器
- 不支持重載
6.6 Constant
函數也可被聲明為常量,這類函數將承諾自己不修改區塊鏈上任何狀態。
一般從鏈上獲取數據時,get函數都會加上constant
6.7 繼承(Inheritance)
Solidity通過復制包括多態的代碼來支持多重繼承。
父類
pragma solidity ^0.4.4;contract Meta {string public name;string public abi;address metaAddress;function Meta(string n,string a){name=n;abi=a;}function getMeta()public constant returns(string,string,address){return (name,abi,metaAddress);}function setMetaAddress(address meta) public {metaAddress=meta;} }子類
pragma solidity ^0.4.4;contract Demo is Meta{bytes32 public orgID; function Demo (string n,string abi,bytes32 id) Meta(n,abi){orgID = id;}}最簡單的合約架構
1:1合約架構圖
7. 限制
基于EVM的限制,不能通過外部函數返回動態的內容please keep in mind
- Fail as early and loudly as possible
- Favor pull over push payments
- Order your function code: conditions, actions, interactions
- Be aware of platform limits
- Write tests
- Fault tolerance and Automatic bug bounties
- Limit the amount of funds deposited
- Write simple and modular code
- Don’t write all your code from scratch
- Timestamp dependency: Do not use timestamps in critical parts of the code, because miners can manipulate them
- Call stack depth limit: Don’t use recursion, and be aware that any call can fail if stack depth limit is reached
- Reentrancy: Do not perform external calls in contracts. If you do, ensure that they are the very last thing you do
8. 語言本身存在的痛點
9. 合約架構
合約架構分層
最簡單的架構
合約的架構分兩層數據合約和邏輯合約
數據合約 【model】
邏輯合約 【controller】
這樣分層的原因,是方便后期合約的升級。
bcos
truffle
- trufflesuite
優勢
大家都用它,簡單易用,生態相對于其他合約框架更加全面
功能
- 一鍵初始化開發合約的項目(包含配置)
- 合約編譯
- 合約部署
- 合約測試
- 合約debug【可借鑒】
upgrade smart contract
- upgradable點擊預覽
- solidity-proxy
- solution flow looks
- ContractFactory
- ether-router
- bcos
10. 參考資料
- blog.zeppelin.solutions
- solidity-workshop
- condition-orientated-programming
- 區塊鏈技術
- ABI
- 術語表
- fisco-bcos
11.相關名詞解釋:
一起來學習區塊鏈技術吧
https://segmentfault.com/a/1190000012996636
總結
以上是生活随笔為你收集整理的区块链技术:智能合约入门的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: (整理)用户空间_内核空间以及内存映射
- 下一篇: 跨链资产原子转移工具包 Decred a