主页 > imtoken官方下载 > 智能合约的核心思想、语法重点、编程模式、示例、规范和架构
智能合约的核心思想、语法重点、编程模式、示例、规范和架构
目录[ ](
demo#%E4%BB%80%E4%B9%88%E6%98%AF%E6%99%BA%E8%83%BD%E5%90%88%E7%BA%A6) 什么是智能合约
智能合约是以数字形式定义的一组承诺,包括合约参与者可以实现这些承诺的协议。 合约由一组代码(合约的函数)和数据(合约的状态)组成,并在以太坊虚拟机上运行。
以太坊虚拟机(EVM)使用 256 位机器代码,是一个基于堆栈的虚拟机,用于执行以太坊智能合约。 由于 EVM 是为以太坊系统设计的,因此使用以太坊账户模型(Account Model)进行价值传递。
[ ](
演示#%E5%90%88%E7%BA%A6%E7%9A%84%E4%BB%A3%E7%A0%81%E5%85%B7%E6%9C%89%E4%BB%80 %E4%B9%88%E8%83%BD%E5%8A%9B%EF%BC%9A) 合约代码有哪些能力:
读取交易数据。读取或写入合约自己的存储空间。读取环境变量【块高,哈希值,gas】向另一个合约发送一个“内部交易”。
[ ](
演示#%E5%9C%A8%E5%8C%BA%E5%9D%97%E9%93%BE%E5%B9%B3%E5%8F%B0%E7%9A%84%E6%9E%B6 %E6%9E%84) 关于区块链平台的架构
区块链平台架构
[ ](
demo#1-%E4%BB%80%E4%B9%88%E6%98%AFsolidity) 1.什么是solidity
Solidity 是一种运行在以太坊虚拟机 (EVM) 之上的智能合约高级语言。
solidity 语言特性
它的语法接近Javascript,是一种面向对象的语言。 但作为真正运行在网络上的去中心化合约,它有很多不同之处:
[ ](
demo#2-%E5%BC%80%E5%8F%91%E7%9A%84%E5%B7%A5%E5%85%B7) 2.开发工具
[ ](
演示#3-%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8) 3 快速入门
[ ](
demo#31-%E4%B8%BE%E4%B8%AA%E4%BE%8B%E5%AD%90) 3.1 举个例子
完成步骤:
编写合约 编译合约 部署合约 测试合约
一个例子获取演示
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; } }
[ ](
demo#32-%E9%83%A8%E7%BD%B2%E5%90%88%E7%BA%A6) 3.2 部署合约
例如获取演示
$ babel-node index.js
[ ](
demo#1-%E7%BC%96%E8%AF%91%E5%90%88%E7%BA%A6) 1. 编译合约
execSync("solc --abi --bin --overwrite -o " + config.Ouputpath + " " + filename + ".sol");
[ ](
演示#2-%E9%83%A8%E7%BD%B2%E5%90%88%E7%BA%A6%E5%88%B0%E5%8C%BA%E5%9D%97%E9%93 %BE%E4%B8%8A)2。 将合约部署到区块链
var Contract = await web3sync.rawDeploy(config.account, config.privKey, filename);
[ ](
演示#3-%E5%AF%B9%E5%90%88%E7%BA%A6%E8%BF%9B%E8%A1%8C%E8%AF%BB%E5%86%99) 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);
[ ](
demo#321-%E5%BC%95%E5%85%A5%E6%A6%82%E5%BF%B5%EF%BC%9A) 3.2.1 概念介绍:
address:以太坊地址的长度比特币合约例子,大小为20字节,160位,所以可以用uint160编码。 地址是所有合约的基础,所有合约都会继承地址对象,随时可以使用一个地址字符串来获取对应的代码来调用。合约的地址是根据账户随机的哈希值计算出来的数量和交易数据
ABI:是以太坊中合约之间调用或发送消息时的一种消息格式。 就是定义操作函数签名、参数编码、返回结果编码等。
交易:以太坊中的“交易”是指存储从外部账户发送的消息的签名数据包。
简单的理解就是:只要写了区块链,就一定会发生交易。
交易收据:
交易发生后的返回值
[ ](
demo#322-%E6%89%A9%E5%B1%95%E9%98%85%E8%AF%BB%EF%BC%9A) 3.2.2 延伸阅读与学习:
3.3 合约文件结构介绍
[ ](
demo#1-%E7%89%88%E6%9C%AC%E5%A3%B0%E6%98%8E) 1.版本说明
pragma solidity ^0.4.10;
[ ](
演示#1-%E5%BC%95%E7%94%A8%E5%85%B6%E5%AE%83%E6%BA%90%E6%96%87%E4%BB%B6) 1. 报价其他源文件
import “filename”;//全局引入
[ ](
demo#1-%E7%8A%B6%E6%80%81%E5%8F%98%E9%87%8Fstate-variables) 1.状态变量(StateVariables)
int256 storedData;
详情见下文
[ ](
demo#2-%E5%87%BD%E6%95%B0functions) 2. 函数
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-%E4%BA%8B%E4%BB%B6%EF%BC%88events%EF%BC%89) 3. 事件
//事件的声明 event AddMsg(address indexed sender, bytes32 msg); //事件的使用 function setData(int256 x) public { storedData = x; AddMsg(msg.sender, "in the set() method"); }
[ ](
demo#4-%E7%BB%93%E6%9E%84%E7%B1%BB%E5%9E%8Bstructs-types) 4.结构类型(StructsTypes)
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-%E5%87%BD%E6%95%B0%E4%BF%AE%E9%A5%B0%E7%AC%A6%EF%BC%88函数修饰符%EF%BC%89) 5 . 函数修饰符
类似于钩子修饰符 only_with_at_least(int x) { if (x >= 5) { x = x+10; _; } }
[ ](
demo#4-%E5%90%88%E7%BA%A6%E7%BC%96%E7%A8%8B%E6%A8%A1%E5%BC%8Fcop) 4.合约编程模式COP
面向条件编程 (COP) 是面向契约编程的一个子领域,是面向函数和命令式编程的混合体。 COP 通过要求程序员显式枚举所有条件来解决这个问题。 逻辑变得平坦,没有条件状态变化。 可以从需求和实现中正确记录、重用和推断条件片段。 重要的是,COP 将前提条件视为编程中的一等公民。 这样的模型规范可以保证合约的安全性。
4.1
特征
例子:
contract Token { // The balance of everyone mapping (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;}
缔约方会议风格
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;}
[ ](
demo#%E6%89%A9%E5%B1%95%E9%98%85%E8%AF%BB%EF%BC%9A) 扩展阅读和学习:
[ ](
demo#5-%E8%AF%AD%E6%B3%95%E4%BB%8B%E7%BB%8D) 五、语法介绍
基本语法见官方API
[ ](
demo#51-%E5%80%BC%E7%B1%BB%E5%9E%8B) 5.1 值类型
! 逻辑非 && 逻辑与 || 逻辑或 == 等于! = 不等于
[ ](
demo#52-%E5%BC%95%E7%94%A8%E7%B1%BB%E5%9E%8Breference-types) 5.2 引用类型
[ ](
demo#6-%E9%87%8D%E8%A6%81%E6%A6%82%E5%BF%B5) 6. 重要概念
[ ](
demo#61-solidity%E7%9A%84%E6%95%B0%E6%8D%AE%E4%BD%8D%E7%BD%AE) 6.1 Solidity数据位置
[ ](
demo#%E6%95%B0%E6%8D%AE%E4%BD%8D%E7%BD%AE%E7%9A%84%E7%B1%BB%E5%9E%8B) 数据位置类型
变量的存储位置属性。 分为三种,memory,storage,calldata。
存储 - 状态变量的存储模型
固定大小的变量(除map和变长数组外的所有类型)在存储中从位置0开始顺序排列。 如果多个变量占用小于 32 字节比特币合约例子,则尽可能将它们打包到一个存储槽中。 具体规则如下:
[ ](
demo#%E4%BC%98%E5%8C%96%E5%BB%BA%E8%AE%AE%EF%BC%9A)优化建议:
为了便于EVM优化,尽量有意识地对存储变量和结构体成员进行排序,这样可以将它们打包得更紧。 比如按照这个顺序定义,uint128,uint128,uint256,不是uint128,uint256,uint128。 因为后者会占用三个槽位。
内存 - 内存中的布局
Solidity 保留三个 32 字节的槽:
0-64:哈希方法的暂存空间
64-96:当前分配的内存大小(也叫空闲内存指针)
语句之间有可用的暂存空间(例如内联编译时)
Solidity 总是在空闲内存指针所在的位置创建一个新对象,并且相应的内存永远不会被释放(也许这在未来会改变)。
Solidity 中的某些操作需要超过 64 字节的暂存空间,这将超过保留的暂存空间。 它们会被分配到空闲内存指针所在的地方,但是由于它们自身的特性,生命周期比较短,而且指针本身不能更新,内存可能清零也可能不清零。 因此,您不应认为空闲内存一定已清零。
例子
6.2
地址
以太坊地址的长度是20字节160位,所以可以用uint160编码。地址是所有合约的基础,所有合约都会继承地址对象,随时可以得到一个地址串来获取对应的代码调用
6.3
事件
事件 AddMsg(地址索引发件人,bytes32 消息);
Event是使用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') } });
[ ](
演示#64-%E6%95%B0%E7%BB%84) 6.4 数组
数组是固定长度或可变长度的数组。 有一个length属性,表示当前数组长度。
bytes:类似于byte[],一个动态长度的字节数组
string:类似于bytes,UTF-8编码的动态长度的字符类型
bytes1~bytes32
一般使用定长bytes1~bytes32。 当字符串的长度已知时,在指定长度时更节省空间。
[ ](
demo#641-%E5%88%9B%E5%BB%BA%E6%95%B0%E7%BB%84) 6.4.1 创建数组
文字 uint[] 内存 a = []
新的 uint[] 内存 a = 新的 uint ;
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; }}
[ ](
演示#642-%E6%95%B0%E7%BB%84%E7%9A%84%E5%B1%9E%E6%80%A7%E5%92%8C%E6%96%B9%E6%B3 %95)6.4.2 数组的属性和方法
长度属性
storage变长数组是可以修改lengthmemory变长数组是不可以修改length
推送方法
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; }}
下标:与其他语言类似
[ ](
demo#643-memory%E6%95%B0%E7%BB%84) 6.4.3 内存阵列
如果将 Memory 数组作为函数参数传递,则只能支持 ABI 支持的类型。
内存数组是一个属性,不能修改修改数组的大小
pragma solidity ^0.4.2;
contract SimpleStartDemo { function f() { //创建内存数组 uint[] memory a =new uint;
//不能修改长度 //Error: Expression has to be an lvalue. //a.length = 100; } //storage uint[] b; function g(){ b = new uint[](7); //可以修改storage的数组 b.length = 10; b[9] = 100; }
}
EVM 的局限性
由于EVM的限制,外部函数不能直接返回动态数组和多维数组
stroage数组不能直接返回,需要转成内存类型返回
//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); }
商业场景