区块链技术:智能合约入门
什么是智能合约
一个智能合约是一套以数字形式定义的承诺(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.js
1. 编译合约
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. 引用其它源文件
import “filename”;//全局引入
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 Data {uint deadline;uint amount;}Data data;function set(uint id, uint deadline, uint amount) {data.deadline = deadline;data.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属性,表示当前的数组长度。
- bytes:类似于byte[], 动态长度的字节数组
- string:类似于bytes,动态长度的UTF-8编码的字符类型
- bytes1~bytes32
一般使用定长的 bytes1~bytes32。在知道字符串长度的情况下,指定长度时,更加节省空间。
6.4.1 创建数组
- 字面量 uint[] memory a = []
new uint[] memory a = new uint[](7);
例子pragma solidity ^0.4.0;contract SimpleStartDemo{uint[] stateVar;function f(){//定义一个变长数组uint[] memory memVar;//不能在使用new初始化以前使用//VM Exception: invalid opcode//memVar [0] = 100;//通过new初始化一个memory的变长数组memVar = new uint[](2);//不能在使用new初始化以前使用//VM Exception: invalid opcode//stateVar[0] = 1;//通过new初始化一个storage的变长数组stateVar = new uint[](2);stateVar[0] = 1;}}
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数组作为函数的参数传递,只能支持ABI能支持的类型类型。
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的限制,不能通过外部函数直接返回动态数组和多维数组
- 将stroage数组不能直接返回,需要转换成memory类型的返回
//Data层数据struct Rate {int key1;int unit;uint[3] exDataArr;bytes32[3] exDataStr;}mapping(int =>Rate) Rates;function getRate(int key1) public constant returns(int,uint[3],bytes32[3]) {uint[3] memory exDataInt = Rates[key1].exDataArr;bytes32[3] memory exDataStr = Rates[key1].exDataStr;return (Rates[key1].unit,exDataInt,exDataStr);}
业务场景
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;import "Meta.sol";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. 语言本身存在的痛点
- ABI支持的类型有限,难以返回复杂的结构体类型。
- Deep Stack的问题
- 难以调试,只能靠event log ,进行合约的调试
- 合约调用合约只能使用定长数组
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.相关名词解释:
- 以太坊合约的代码是使用低级的基于堆栈的字节码的语言写成的,被称为“以太坊虚拟机代码”或者“EVM代码”。代码由一系列字节构成,每一个字节代表一种操作。
- UTXO:比特币系统的“状态”是所有已经被挖出的、没有花费的比特币(技术上称为“未花费的交易输出,unspent transaction outputs 或UTXO”)的集合。每个UTXO都有一个面值和所有者(由20个字节的本质上是密码学公钥的地址所定义[1])。一笔交易包括一个或多个输入和一个或多个输出。每个输入包含一个对现有UTXO的引用和由与所有者地址相对应的私钥创建的密码学签名。每个输出包含一个新的加入到状态中的UTXO。
- 区块链:区块链起源于中本聪的比特币,作为比特币的底层技术,本质上是一个去中心化的数据库。是指通过去中心化和去信任的方式集体维护一个可靠数据库的技术方案。
一起来学习区块链技术吧
https://segmentfault.com/a/1190000012996636
区块链技术:智能合约入门相关推荐
- 区块链开发(十)区块链技术-智能合约Solidity编程语言
[文档翻译系列]Solidity语言 Solidity是一种智能合约高级语言,运行在Ethereum虚拟机(EVM)之上. Solidity与其它语言相关的特点? 它的语法接近于Javascript, ...
- 详解区块链,智能合约,去中心化应用
文章目录 一.区块链是什么? 二.智能合约 三.去中心化应用 一.区块链是什么? 区块链本质上是一个去中心化的分布式账本数据库,目的是解决交易信任问题.广义来看,区块链技术是利用块链式数据结构验证与存 ...
- 什么是智能合约,区块链和智能合约的关系及原理
智能合约(smart contract)这个术语至少可以追溯到 1995 年,是由多产的跨领域法 律学者尼克·萨博(Nick Szabo)提出来的.他的定义是"一个智能合约是一套以数字形式 ...
- 论文笔记:使用区块链和智能合约打击深度假冒视频
摘要 随着人工智能(AI)和深度学习技术的兴起,近年来虚假数字内容激增.假镜头.假图像.假音频和假视频(被称为深度假冒,Deepfake)可能是一种危险的现象,有可能改变真相,并通过伪造事实来侵蚀信任 ...
- [区块链安全-Damn-Vulnerable-DeFi]区块链DeFi智能合约安全实战-连载中
[区块链安全-Damn-Vulnerable-DeFi]区块链DeFi智能合约安全实战-连载中 前言 环境准备 1. unstoppable 任务分析 发起攻击 总结 2. Naive receive ...
- 区块链和智能合约的关系
链客,专为开发者而生,有问必答! 此文章来自区块链技术社区,未经允许拒绝转载. 尽管比特币(Bitcoin)和以太坊(Ethereum)是经常被一起提及的两个词,但实际上,比特币与以太坊有着很大的区别 ...
- 区块链上智能合约的讲解
智能合约 智能合约是在区块链上运行的应用或者程序.通常情况下,智能合约是一组具有特定规则的数字化协议,且这些协议能够强制化执行.这些规则由计算机代码预先定义,所有节点会复制和执行这些计算机源码. 智能 ...
- 每日区块链:智能合约是否是区块链的最佳创新?
1.盖茨基金会将使用Ripple跨账本技术推动移动支付 导读:盖茨基金会正在积极探索新技术来开展一些项目帮助没有银行账户的穷人.目前,它宣布使用Ripple的跨账本技术开发了一种开源软件,能够为金融机 ...
- 区块链学习-智能合约与ICO
什么是"以太坊"? 以太坊是由程序员Vitalik Buterin创立的,是比特币交易的替代品.比特币被严格用作加密电子货币,或被视作一种转移货币价值的手段.然而,相同的区块链技术 ...
- 众说区块链:智能合约在建材链上的落地实践
上期<众说区块链>讨论的主题是"数字货币交易所",围绕这个的相关主题,大家对这方面的相关话题展开了进一步的谈论. 本期<众说区块链>讨论的主题是" ...
最新文章
- 2010 年 360 盗取用户密码事件始末
- Jmail的邮件发送
- 【控制】《多智能体系统的动力学分析与设计》徐光辉老师-第2章-带时延位置的离散多智能体系统的一致性
- Java有线程安全的set吗?
- RTX5 | 内存池03 - 共享内存用于线程之间的通讯(轮询方式)
- CCS 软件仿真 支持6678
- 创建user01-user20 随机六位数密码 a-z A-Z 0-9
- 神经网络机器翻译技术
- Unity知识点整理
- 6500元都不到?战神Z8-DA5NP配置强悍,神船的性价比又回来了
- 上网操作1000问(供初學者)
- Licheepi_nano开发板:根文件系统编译
- PLSQL-Initialization error
- (九五至尊)九大管理体系,五大过程组:软考高级信息系统项目管理师
- 激光制导炸弹导引头半实物仿真系统ETest方案设计
- YOLOv4团队最新开源!YOLOv4改进版!!!Scaled-YOLOv4解读
- websphere mq_使用WebSphere MQ应用程序活动跟踪提高消息的可见性
- 用小程序商城模板搭建商城【商城小程序】
- 将目录专为源码html,LiteOS移植笔记
- Windows操作系统中消除汉字乱码有绝招