区块链技术:智能合约入门
什么是智能合約
一個(gè)智能合約是一套以數(shù)字形式定義的承諾(promises) ,包括合約參與方可以在上面執(zhí)行這些承諾的協(xié)議。一個(gè)合約由一組代碼(合約的函數(shù))和數(shù)據(jù)(合約的狀態(tài))組成,并且運(yùn)行在以太坊虛擬機(jī)上.以太坊虛擬機(jī)(EVM)使用了256比特長(zhǎng)度的機(jī)器碼,是一種基于堆棧的虛擬機(jī),用于執(zhí)行以太坊智能合約?。由于EVM是針對(duì)以太坊體系設(shè)計(jì)的,因此使用了以太坊賬戶模型(Account Model)進(jìn)行價(jià)值傳輸。
合約的代碼具有什么能力:
讀取交易數(shù)據(jù)。 讀取或?qū)懭牒霞s自己的存儲(chǔ)空間。 讀取環(huán)境變量【塊高,哈希值,gas】 向另一個(gè)合約發(fā)送一個(gè)“內(nèi)部交易”。在區(qū)塊鏈平臺(tái)的架構(gòu)
區(qū)塊鏈平臺(tái)的架構(gòu)
1. 什么是solidity
Solidity是一種智能合約高級(jí)語(yǔ)言,運(yùn)行在Ethereum虛擬機(jī)(EVM)之上。
solidity 語(yǔ)言特點(diǎn)
它的語(yǔ)法接近于Javascript,是一種面向?qū)ο蟮恼Z(yǔ)言。但作為一種真正意義上運(yùn)行在網(wǎng)絡(luò)上的去中心合約,它有很多的不同點(diǎn):
- 異常機(jī)制,類似于事務(wù)的原子性。一旦出現(xiàn)異常,所有的執(zhí)行都將會(huì)被回撤,這主要是為了保證合約執(zhí)行的原子性,以避免中間狀態(tài)出現(xiàn)的數(shù)據(jù)不一致。
- 運(yùn)行環(huán)境是在去中心化的網(wǎng)絡(luò)上,會(huì)比較強(qiáng)調(diào)合約或函數(shù)執(zhí)行的調(diào)用的方式。因?yàn)樵瓉?lái)一個(gè)簡(jiǎn)單的函數(shù)調(diào)用變?yōu)榱艘粋€(gè)網(wǎng)絡(luò)上的節(jié)點(diǎn)中的代碼執(zhí)行
- 存儲(chǔ)是使用網(wǎng)絡(luò)上的區(qū)塊鏈,數(shù)據(jù)的每一個(gè)狀態(tài)都可以永久存儲(chǔ)。
2. 開(kāi)發(fā)的工具
- 在線編譯器Remix
- Visual Studio Code + soliidty 插件
3 快速入門
準(zhǔn)備工作:搭建區(qū)塊鏈
一鍵部署區(qū)塊鏈平臺(tái)
3.1 舉個(gè)例子
完整的步驟:
1. 寫合約 2. 編譯合約 3. 部署合約 4. 測(cè)試合約獲取例子get demo
參考操作步驟
$ git clone "https://github.com/cristicmf/bcos-qucik-start-demo" $ cd startDemo $ npm install $ babel-node index.js文件結(jié)構(gòu)說(shuō)明
startDemo ├── README.md ├── SimpleStartDemo.sol # 合約代碼 ├── codeUtils.js ├── config.js # 配置文件 ├── index.js # 部署合約和測(cè)試合約 ├── 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 部署合約
舉個(gè)例子get demo
$ babel-node index.js1. 編譯合約
execSync("solc --abi --bin --overwrite -o " + config.Ouputpath + " " + filename + ".sol");2. 部署合約到區(qū)塊鏈上
var Contract = await web3sync.rawDeploy(config.account,?config.privKey, filename);3. 對(duì)合約進(jìn)行讀寫
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);//獲取鏈上數(shù)據(jù)var data = instance.getData();//修改鏈上數(shù)據(jù)var func = "setData(int256)";var params = [10];var receipt = await web3sync.sendRawTransaction(config.account, config.privKey, address, func, params);3.2.1 引入概念:
address:以太坊地址的長(zhǎng)度,大小20個(gè)字節(jié),160位,所以可以用一個(gè)uint160編碼。地址是所有合約的基礎(chǔ),所有的合約都會(huì)繼承地址對(duì)象,也可以隨時(shí)將一個(gè)地址串,得到對(duì)應(yīng)的代碼進(jìn)行調(diào)用。合約的地址是基于賬號(hào)隨機(jī)數(shù)和交易數(shù)據(jù)的哈希計(jì)算出來(lái)的
ABI:是以太坊的一種合約間調(diào)用時(shí)或消息發(fā)送時(shí)的一個(gè)消息格式。就是定義操作函數(shù)簽名,參數(shù)編碼,返回結(jié)果編碼等。
交易:以太坊中“交易”是指存儲(chǔ)從外部賬戶發(fā)出的消息的簽名數(shù)據(jù)包。
簡(jiǎn)單理解是:只要對(duì)區(qū)塊鏈進(jìn)行寫操作,一定會(huì)發(fā)生交易。
交易回執(zhí):
發(fā)生交易后的返回值
3.2.2 擴(kuò)展閱讀:
- Ethereum-Contract-ABI
- Solidity-Features
- 以太坊白皮書
3.3 合約文件結(jié)構(gòu)簡(jiǎn)介
1. 版本聲明
pragma solidity ^0.4.10;1. 引用其它源文件
//全局引入1. 狀態(tài)變量(State Variables)
int256 storedData;詳細(xì)說(shuō)明見(jiàn)下文
2. 函數(shù)(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. 結(jié)構(gòu)類型(Structs Types)
contract Contract {struct {uint deadline;uint amount;} ; set(uint id, uint deadline, uint amount) {.deadline = deadline;.amount = amount;}}5. 函數(shù)修飾符(Function Modifiers)
類似于hook
modifier only_with_at_least(int x) {if (x >= 5) {x = x+10; _; }}4. 合約編程模式COP
面向條件的編程(COP)是面向合約編程的一個(gè)子域,作為一種面向函數(shù)和命令式編程的混合模式。COP解決了這個(gè)問(wèn)題,通過(guò)需要程序員顯示地枚舉所有的條件。邏輯變得扁平,沒(méi)有條件的狀態(tài)變化。條件片段可以被正確的文檔化,復(fù)用,可以根據(jù)需求和實(shí)現(xiàn)來(lái)推斷。重要的是,COP在編程中把預(yù)先條件當(dāng)作為一等公民。這樣的模式規(guī)范能保證合約的安全。
4.1 FEATURES
- 函數(shù)主體沒(méi)有條件判斷
例子:
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;} }改進(jìn)后:
function transfer(uint _amount, address _dest) {if (balances[msg.sender] < _amount)return;balances[msg.sender] -= _amount;balances[_dest] += _amount; }COP的風(fēng)格
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; }擴(kuò)展閱讀:
- Condition-Orientated Programming
- Paper
5. 語(yǔ)法介紹
基礎(chǔ)語(yǔ)法見(jiàn)官方API
5.2 引用類型(Reference Types)
- 不定長(zhǎng)字節(jié)數(shù)組(bytes)
- 字符串(string)
bytes3 a = "123"; - 數(shù)組(Array)
- 結(jié)構(gòu)體(Struts)
6. 重要概念
6.1 Solidity的數(shù)據(jù)位置
數(shù)據(jù)位置的類型
變量的存儲(chǔ)位置屬性。有三種類型,memory,storage和calldata。
- memory存儲(chǔ)位置同我們普通程序的內(nèi)存類似。即分配,即使用,越過(guò)作用域即不可被訪問(wèn),等待被回收-
- storage的變量,數(shù)據(jù)將永遠(yuǎn)存在于區(qū)塊鏈上。
- calldata 數(shù)據(jù)位置比較特殊,一般只有外部函數(shù)的參數(shù)(不包括返回參數(shù))被強(qiáng)制指定為calldata
Storage - 狀態(tài)變量的存儲(chǔ)模型
大小固定的變量(除了映射,變長(zhǎng)數(shù)組以外的所有類型)在存儲(chǔ)(storage)中是依次連續(xù)從位置0開(kāi)始排列的。如果多個(gè)變量占用的大小少于32字節(jié),會(huì)盡可能的打包到單個(gè)storage槽位里,具體規(guī)則如下:
- 在storage槽中第一項(xiàng)是按低位對(duì)齊存儲(chǔ)(lower-order aligned)
- 基本類型存儲(chǔ)時(shí)僅占用其實(shí)際需要的字節(jié)。
- 如果基本類型不能放入某個(gè)槽位余下的空間,它將被放入下一個(gè)槽位。
- 結(jié)構(gòu)體和數(shù)組總是使用一個(gè)全新的槽位,并占用整個(gè)槽(但在結(jié)構(gòu)體內(nèi)或數(shù)組內(nèi)的每個(gè)項(xiàng)仍遵從上述規(guī)則)
優(yōu)化建議:
為了方便EVM進(jìn)行優(yōu)化,嘗試有意識(shí)排序storage的變量和結(jié)構(gòu)體的成員,從而讓他們能打包得更緊密。比如,按這樣的順序定義,uint128, uint128, uint256,而不是uint128, uint256, uint128。因?yàn)楹笠环N會(huì)占用三個(gè)槽位。
Memory - 內(nèi)存變量的布局(Layout in Memory)
Solidity預(yù)留了3個(gè)32字節(jié)大小的槽位:
0-64:哈希方法的暫存空間(scratch space)
64-96:當(dāng)前已分配內(nèi)存大小(也稱空閑內(nèi)存指針(free memory pointer))
暫存空間可在語(yǔ)句之間使用(如在內(nèi)聯(lián)編譯時(shí)使用)
Solidity總是在空閑內(nèi)存指針?biāo)谖恢脛?chuàng)建一個(gè)新對(duì)象,且對(duì)應(yīng)的內(nèi)存永遠(yuǎn)不會(huì)被釋放(也許未來(lái)會(huì)改變這種做法)。
有一些在Solidity中的操作需要超過(guò)64字節(jié)的臨時(shí)空間,這樣就會(huì)超過(guò)預(yù)留的暫存空間。他們就將會(huì)分配到空閑內(nèi)存指針?biāo)诘牡胤?#xff0c;但由于他們自身的特點(diǎn),生命周期相對(duì)較短,且指針本身不能更新,內(nèi)存也許會(huì),也許不會(huì)被清零(zerod out)。因此,大家不應(yīng)該認(rèn)為空閑的內(nèi)存一定已經(jīng)是清零(zeroed out)的。
例子
6.2 address
以太坊地址的長(zhǎng)度,大小20個(gè)字節(jié),160位,所以可以用一個(gè)uint160編碼。地址是所有合約的基礎(chǔ),所有的合約都會(huì)繼承地址對(duì)象,也可以隨時(shí)將一個(gè)地址串,得到對(duì)應(yīng)的代碼進(jìn)行調(diào)用
6.3 event
event AddMsg(address indexed sender, bytes32 msg);
- 這行代碼聲明了一個(gè)“事件”。客戶端(服務(wù)端應(yīng)用也適用)可以以很低的開(kāi)銷來(lái)監(jiān)聽(tīng)這些由區(qū)塊鏈觸發(fā)的事件
事件是使用EVM日志內(nèi)置功能的方便工具,在DAPP的接口中,它可以反過(guò)來(lái)調(diào)用Javascript的監(jiān)聽(tīng)事件的回調(diào)。
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')}});- 事件在合約中可被繼承。當(dāng)被調(diào)用時(shí),會(huì)觸發(fā)參數(shù)存儲(chǔ)到交易的日志中(一種區(qū)塊鏈上的特殊數(shù)據(jù)結(jié)構(gòu))。這些日志與合約的地址關(guān)聯(lián),并合并到區(qū)塊鏈中,只要區(qū)塊可以訪問(wèn)就一直存在(至少Frontier,Homestead是這樣,但Serenity也許也是這樣)。日志和事件在合約內(nèi)不可直接被訪問(wèn),即使是創(chuàng)建日志的合約。
- 日志位置在nodedir0/log 里面,可以打出特殊的類型進(jìn)行驗(yàn)證
6.4 數(shù)組
數(shù)組是定長(zhǎng)或者是變長(zhǎng)數(shù)組。有l(wèi)ength屬性,表示當(dāng)前的數(shù)組長(zhǎng)度。
一般使用定長(zhǎng)的 bytes1~bytes32。在知道字符串長(zhǎng)度的情況下,指定長(zhǎng)度時(shí),更加節(jié)省空間。
6.4.1 創(chuàng)建數(shù)組
new uint[] memory a = new uint[](7);
例子
6.4.2 數(shù)組的屬性和方法
length屬性
storage變長(zhǎng)數(shù)組是可以修改length
memory變長(zhǎng)數(shù)組是不可以修改length
push方法
storage變長(zhǎng)數(shù)組可以使用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;//自動(dòng)擴(kuò)充長(zhǎng)度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;} }下標(biāo):和其他語(yǔ)言類似
6.4.3 Memory數(shù)組
Memory數(shù)組是不能修改修改數(shù)組大小的屬性
例子
pragma solidity ^0.4.2;
contract SimpleStartDemo {
function f() {//創(chuàng)建一個(gè)memory的數(shù)組uint[] memory a = new uint[](7);//不能修改長(zhǎng)度//Error: Expression has to be an lvalue.//a.length = 100;}//storageuint[] b;function g(){b = new uint[](7);//可以修改storage的數(shù)組b.length = 10;b[9] = 100;}}
EVM的限制
由于EVM的限制,不能通過(guò)外部函數(shù)直接返回動(dòng)態(tài)數(shù)組和多維數(shù)組
業(yè)務(wù)場(chǎng)景
6.5 函數(shù)
function (<parameter types>) {internal(默認(rèn))|external} constant [returns (<return types>)]
6.5.1 函數(shù)的internal與external
例子
pragma solidity ^0.4.5;contract FuntionTest{function internalFunc() internal{}function externalFunc() external{}function callFunc(){//直接使用內(nèi)部的方式調(diào)用internalFunc();//不能在內(nèi)部調(diào)用一個(gè)外部函數(shù),會(huì)報(bào)編譯錯(cuò)誤。//Error: Undeclared identifier.//externalFunc();//不能通過(guò)`external`的方式調(diào)用一個(gè)`internal`//Member "internalFunc" not found or not visible after argument-dependent lookup in contract FuntionTest//this.internalFunc();//使用`this`以`external`的方式調(diào)用一個(gè)外部函數(shù)this.externalFunc();} } contract FunctionTest1{function externalCall(FuntionTest ft){//調(diào)用另一個(gè)合約的外部函數(shù)ft.externalFunc();//不能調(diào)用另一個(gè)合約的內(nèi)部函數(shù)//Error: Member "internalFunc" not found or not visible after argument-dependent lookup in contract FuntionTest//ft.internalFunc();} }訪問(wèn)函數(shù)有外部(external)可見(jiàn)性。如果通過(guò)內(nèi)部(internal)的方式訪問(wèn),比如直接訪問(wèn),你可以直接把它當(dāng)一個(gè)變量進(jìn)行使用,但如果使用外部(external)的方式來(lái)訪問(wèn),如通過(guò)this.,那么它必須通過(guò)函數(shù)的方式來(lái)調(diào)用。
例子
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 函數(shù)調(diào)用
- 內(nèi)部調(diào)用,不會(huì)創(chuàng)建一個(gè)EVM調(diào)用,也叫消息調(diào)用
- 外部調(diào)用,創(chuàng)建EVM調(diào)用,會(huì)發(fā)起消息調(diào)用
6.5.3 函數(shù)修改器(Function Modifiers)
修改器(Modifiers)可以用來(lái)輕易的改變一個(gè)函數(shù)的行為。比如用于在函數(shù)執(zhí)行前檢查某種前置條件。修改器是一種合約屬性,可被繼承,同時(shí)還可被派生的合約重寫(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合約構(gòu)造函數(shù) 同名函數(shù)
- 可選
- 僅能有一個(gè)構(gòu)造器
- 不支持重載
6.6 Constant
函數(shù)也可被聲明為常量,這類函數(shù)將承諾自己不修改區(qū)塊鏈上任何狀態(tài)。
一般從鏈上獲取數(shù)據(jù)時(shí),get函數(shù)都會(huì)加上constant
6.7 繼承(Inheritance)
Solidity通過(guò)復(fù)制包括多態(tài)的代碼來(lái)支持多重繼承。
父類
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;}}最簡(jiǎn)單的合約架構(gòu)
1:1合約架構(gòu)圖
7. 限制
基于EVM的限制,不能通過(guò)外部函數(shù)返回動(dòng)態(tài)的內(nèi)容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. 語(yǔ)言本身存在的痛點(diǎn)
9. 合約架構(gòu)
合約架構(gòu)分層
最簡(jiǎn)單的架構(gòu)
合約的架構(gòu)分兩層數(shù)據(jù)合約和邏輯合約
數(shù)據(jù)合約 【model】
邏輯合約 【controller】
這樣分層的原因,是方便后期合約的升級(jí)。
bcos
truffle
- trufflesuite
優(yōu)勢(shì)
大家都用它,簡(jiǎn)單易用,生態(tài)相對(duì)于其他合約框架更加全面
功能
- 一鍵初始化開(kāi)發(fā)合約的項(xiàng)目(包含配置)
- 合約編譯
- 合約部署
- 合約測(cè)試
- 合約debug【可借鑒】
upgrade smart contract
- upgradable點(diǎn)擊預(yù)覽
- solidity-proxy
- solution flow looks
- ContractFactory
- ether-router
- bcos
10. 參考資料
- blog.zeppelin.solutions
- solidity-workshop
- condition-orientated-programming
- 區(qū)塊鏈技術(shù)
- ABI
- 術(shù)語(yǔ)表
- fisco-bcos
11.相關(guān)名詞解釋:
一起來(lái)學(xué)習(xí)區(qū)塊鏈技術(shù)吧
https://segmentfault.com/a/1190000012996636
總結(jié)
以上是生活随笔為你收集整理的区块链技术:智能合约入门的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: (整理)用户空间_内核空间以及内存映射
- 下一篇: 浅谈以太坊智能合约的设计模式与升级方法