智能合约
区块链是什么
区块链使用P2P技术,分布式技术,非对称加密技术等底层技术构建了一个非安全环境下的分布式安全数据库,它可以在没有中央管理机构的前提下为用户提供安全可靠的业务环境。
本质上来说,区块链就是一个非安全环境下,去中心化,去信任,共同维护的分布式记账数据库。
基本特点
- 去中心化
- 去信任
- 集体维护
- 可靠数据库
什么是智能合约?
区块链是一个承载交易的去中心化的分布式数据库,那么这些承载的交易是否需要遵循某一些特定的规则呢?
由于区块链是多方共同维护的一个分布式数据库,所以当有交易产生并需要记录在区块链上的时候,我们需要一套特定的规则来约束交易,无论是从安全角度来说,还是从效率角度来说,这都是必须的。而这套特定的规则,就是我们所说的智能合约。
定义
智能合约是区块链上运行的计算机程序,使用高级编程语言开发,通常情况下,它们为一组具有特定规则的数字化协议,且协议能够强制执行。这些规则由计算机程序预先定义,其内容反应了真实物理世界中经过多方协商达成一致的业务逻辑,所有网络节点会复制和执行这些计算机代码。
智能合约的意义
区块链天生出现在一个非安全的环境中,通过底层技术可以保障其相对安全性,但是交易环节还是有很多漏洞可以被人所利用,如交易溢出,DDOS攻击等。
而智能合约则完美地扮演了用户和区块链之间的中间人角色,它的出现,意味着契约和规则可以通过代码的形式进行锁定和传递,并且由代码直接干预分配。甚至有人给智能合约赋予了“代码即法律”的高度。我们可以畅想,随着技术的发展演进,传统法律合同会逐渐被智能合约所取代。
智能合约的出现,同时也让区块链技术的产业应用成为了可能。不难想象,在未来,传统的产业应用会逐渐被由智能合约开发的分布式应用所取代。
智能合约的优点
- 高度的定制化:可以通过不同方式进行设计,提供不同的服务和解决方案。
- 由于其去中心化和自动执行的特征,智能合约可以提高透明度,提高效率并降低商业运行成本
- 由于智能合约运行在基于多方共同维护的区块链上,所以可以有效的降低监督监管成本。
- 由于智能合约是无法篡改的,所以其具有非常高的准确性和安全性。
智能合约的主要特征
- 分布式
- 一致性
- 自动化
- 不可篡改
- 定制化
- 无需信任
- 透明性
智能合约的应用场景
加密钱包:DCEP(数字货币电子支付,由央行发行)
投票系统
分布式交易所
游戏领域
医保健康
软件和服务(SaaS):蚂蚁链
蚂蚁链智能合约
蚂蚁链简介
- 由蚂蚁集团开发(ANTCHAIN)
- 技术服务
- BaaS平台
- 开放联盟链
- 应用快速搭建平台
- 区块链融资租赁
- 技术优势
- 共识机制
- 智能合约
- 可信计算
- 隐私保护
- 跨链交互
- 应用场景
- 金融、保险行业
- 跨境汇款
- 相护宝
- 融资租赁
- 区块链合同
- 溯源
- 慈善公益善款溯源
- 正品溯源
- 政务民生
- 数字物流
- 医疗健康
- 金融、保险行业
蚂蚁链智能合约平台
开放联盟链
- 类公链设计(公有制许可)
- 多种智能合约模板
- 超强性能
- Cloud IDE
- 个人用户开通送1亿Gas
蚂蚁区块链合约平台通过引入P2P网络、共识算法、虚拟机、智能合约、密码学、数据存储等技术特性,构建一个稳定、高效、安全的图灵完备智能合约执行环境,提供账户的基本操作以及面向智能合约的功能调用。基于合约平台提供的能力和功能特性,应用开发者能够完成基本的账户创建、合约调用、结果查询、事件监听等。
功能特性
- 账户操作
- 合约体系
- EVM引擎
- WASM引擎
- 区块查询
- 事件监听
- 数据隔离
- 隐私保护
- SPV验证
系统架构
核心逻辑
蚂蚁链相关基础术语介绍
中文 | 英文 | 说明 |
---|---|---|
标识 | Identity | 在区块链中唯一标识一个账户或者智能合约,长度为256位。一般为一个唯一可读内容的哈希值。 |
共识算法 | Consensus algorithm | 一种分布式系统数据一致性保证的算法,通过一定的协议交互来确保分布式系统的多个参与方达成数据的一致性。常见的算法包括PBFT、RAFT、POW、POS等。 |
交易 | Transaction | 区块链上的一个事务请求,用来承载具体业务操作数据的结构。区块链上所有针对世界状态的变化操作均是基于交易来完成的。 |
区块 | Block | 区块链上存储打包交易数据以及交易执行结果数据的一种组织形式。区块彼此之间通过前向的应用彼此链接形成区块链。每个区块记录着上一个区块的哈希值、本区块中的交易集合、本区块的哈希等基础数据。 |
智能合约 | Smart Contract | 一种旨在以信息化方式传播、验证或执行合同的计算机协议。一个智能合约是一套以数字形式定义的承诺(promises),包括合约参与方可以在上面执行这些承诺的协议。智能合约允许在没有第三方的情况下进行可信交易,这些交易可追溯且不可逆转。 |
区块链技术 | Blockchain | 也被称之为分布式账本技术,是一种去中心化的分布式数据库技术,其特点是去中心化、公开透明、不可篡改、可信任。区块链的每笔数据,都会广播全网的区块链节点,每个节点都有全量的、一致的数据。 |
区块链应用 | Application | 基于区块链SDK开发的应用。 |
去中心化应用 | Decentralized Application(DApp) | 与传统中心化应用的主要区别是,DApp通过客户端直接连接区块链节点,通过智能合约计算和访问数据,没有中心化的后端服务。 |
燃料 | Gas | 智能合约在虚拟机中执行计算和存储的消耗度量,通过燃料看防止一些恶意攻击和计算、存储的浪费。 |
Solidity编程入门概览
Solidity语言
简介
- Solidity是一门面向合约的、为实现智能合约而创建的高级编程语言。
- Solidity是静态类型语言,支持继承、库和复杂的用户类型定义等。
- 静态类型语言
- 动态类型语言
- 受到了C++、Python和JavaScript语言的影响
- Solidity语言可以运行在蚂蚁链虚拟机上
- 蚂蚁链支持EVM虚拟机
特性
Solidity是一个面向合约的静态类型语言,受到了C++、Python和JavaScript语言的影响,有着高级语言共有的特性,如继承,接口,事件等。但除此之外,还有着不同于其他高级语言的独特特征:
- 蚂蚁链等区块链平台是基于账户的,所以Solidity语言有一个特殊的identity(蚂蚁链独有),用于标识用户、定位合约等,这个类型是其他所有语言所没有的。
- 存储使用网络上的区块链,所以需要确定变量是存储在内存(Memory)还是存储在区块链(storage)
- 一旦出现异常,所有的执行都将会被撤回,这是为了保证合约执行的原子性,以避免中间状态出现数据不一致的情况。这个是Solidity和其他语言的一个明显不同之处。
- 蚂蚁链支持的Solidity语言和原生Solidity有一些不同,但是绝大部分都是相同的
Solidity例子
pragam solidity >=0.4.0<0.6.0;
contract SimpleStorage{
unit storedData;
function set(unit x) public{
storedData = x;
}
function get() public view returns (unit){
return storedData;
}
}
- pragma
- 第一行的pragma指令,用于指定源代码的Solidity版本。
- 第一行是告诉大家源代码为Solidity0.4.0以上版本编写的,但是不包括0.6.0及以上版本。
- pragma指令仅仅该源文件有效,新建另一个文件,需要重新指定。
- pragma solidity ^0.4.0
- 上述代码的意思是该源文件为Solidity0.4.0及以上版本编写,但是不包括0.5.0及以上版本。
- 也就是说,该源文件是为solidity0.4.0~0.4.9版本编写的。
- 约定俗成,小版本的变化不会包含大规模的更改,更多的是bug修复等小规模的改动,所以我们可以使用^符号来对于源文件支持的版本号
- contract
- contract关键字用于声明一个合约
- 例子中我们用contract关键字声明了一个名字叫做“SimpleStorage”的合约
- unit
- unit是solidity语言支持的基本类型之一,叫做无符号整型(unsigned integer)
- 例子中用unit关键字声明了一个名字叫做“storedData”的无符号整型
- function
- function关键字用于声明一个函数
- 一个函数包含以下几个部分:
- 函数名
- 函数参数
- 函数可见性
- 函数返回值
- 函数体
-
例子中我们用function关键字声明了一个叫做“set”的函数
- “set”后面紧跟了一个小括号,里面的“unit x”表示调用该函数需要传递一个unit类型的参数
- public表示该函数的可见性,public表示该函数是一个公共函数,部署到蚂蚁链后可以被外界所访问
蚂蚁链Solidity编程环境
Cloud IDE
简介
- Cloud IDE是蚂蚁联盟链合约平台提供的在线合约开发工具;
- Cloud IDE本身就是一个典型的去中心化应用(DApp),可通过JavaScript SDK直接与区块链平台腾讯,进行合约部署与调用;
- 合约工程管理
- 合约编辑与编译
- 合约的部署与调用
- 区块链浏览器
使用Cloud IDE
Solc-js
本地编译环境
Solidity智能合约结构
版本标注
我们不仅可以使用^符合来规定版本号,还可以使用更为复杂的规则来指定版本号,Solidity指定版本号的表达方式遵循NPM版本语义:
- ^
- <,>,<=,>=,=
- 多数情况下,我们使用^就可以
蚂蚁链Solidity目前支持0.4.24和0.6.4这两个版本。
状态变量
- 永久的存储在合约存储中的值
- 状态变量的可见性默认为internal
函数
- 合约代码的可执行单元
- 函数调用可以发生在内部,也可以发生在外部
- 函数的可见性可以用关键字来控制
- Public
- External
- Internal
- Private
事件
- 事件是可以方便地调用蚂蚁链虚拟机日志功能的接口
- 使用event关键字(用法上类似于function关键字)来定义事件
- 使用emit关键字在函数中触发事件
注释
- 单行注释
- 使用“//”开头
- 多行注释
- 多行注释包裹在“/*”与“*/”中间
- 编译器会忽略注释内容
- 建议在实际开发项目过程中多加注释,方便以后调试
结构类型
- Solidity支持通过结构体来定义新的类型
- 使用struct关键字来定义结构体
function structDefinedFunction() public {
//定义一个结构体
struction Voter{
uint weight;
bool voted;
uint vote;
}
}
枚举类型
- 枚举将一个变量的取值限制为几个预先定义的值中的一个
- 使用enum关键字来定义枚举类型
pragma solidity ^0.4.20;
contract test{
enum FreshjuiceSize{SMALL,MEDIUM,LARGE}
FreshjuiceSize choice;
FreshjuiceSize constant defaultChoice = FreshjuiceSize.MEDIUM;
function setLarge() public {
choice = FreshjuiceSize.LARGE;
}
function getChoice() public view returns (FreshjuiceSize){
return choice;
}
}
内建合约
- 在合约内部,我们可以使用new关键字来创建另一个合约
- 注意:蚂蚁链Solidity语言不支持使用这种方式来内建合约,这是和原生Solidity语言的不同点之一
Solidity基本语法
- Solidity是一种高级的静态类型的智能合约编程语言
- Solc是solidity的命令行编译器;
- 使用pragma关键字来指定合约版本;
- 使用花括号来分隔代码块
- //表示单行注释
- /*...*/表示多行注释(块注释)
- 大小写敏感
- 每行必须以分号结尾
- /**...*/也表示多行注释,该注释常用于注释函数
- solidity会为所有基本数据类型赋初值,如整型的默认初始值为0,布尔类型的默认初始值为false。所以不同于Java等语言,solidity不会出现undefined或者null这样的错误
变量
solidity是一种静态类型语言,也就是说需要声明的时候指定变量类型,比如声明一个uint类型的变量,需要使用uint关键字来指定该变量的类型为uint类型
每个变量声明的时候都会有一个默认值,如uint类型的变量默认值为0
状态变量
- 变量值永久保存在合约存储空间中的变量
- 我们将定义在合约内部的,但是不是在函数内部的变量称之为状态变量
- 状态变量直接存储在区块链上,因此状态变量的相关操作都会消耗Gas
- 蚂蚁区块链上获取一个uint256类型的状态变量需要消耗23064 Gas
- 我们可以使用蚂蚁链提供的“Gas预估‘来预估操作需要消耗的Gas值
局部变量
- 变量值仅在函数执行过程中有效的变量,函数退出后,变量无效
- 局部变量定义在函数内部
- 局部变量只能在函数内部调用,在函数外部调用会报错
- 针对局部变量的操作不会消耗Gas值
全局变量
- 蚂蚁链智能合约平台为我们提供了丰富的平台函数,这些平台函数我们称之为全局变量
- 保存在全局命名空间,用于获取区块链相关信息的特殊变量
变量作用域
局部变量
仅限于函数内部使用,函数外部调用会报错
全局变量
可以在任意智能合约中直接使用
状态变量
- Public
- 公共状态变量可以在内部访问,也可以通过消息访问
- 对于公共状态变量,将生成一个自动getter函数
- Internal
内部状态变量只能从当前合约货其派生合约(继承)内访问
- Private
私有状态变量只能从当前合约内部访问,派生合约(继承)内不能访问
默认情况下,状态变量的可见性为internal
Solidity基本数据类型
值类型
值类型的变量将始终按照值来传递。也就是说,当这些变量被用作函数参数或者用在赋值语句中时,总会进行值拷贝
整型(有符号/无符号)
布尔类型
定长浮点型
地址类型(蚂蚁链地址类型Identity)
定长字节数组
枚举类型
引用类型
在处理复杂的类型(即占用空间超过256位的类型)时,直接拷贝这些类型变量的开销相当大,所以我们将其存储在一个特定的位置中(如内存堆中),传递的时候,传递的就是其引用。
数组
映射
字符串
结构体
值类型和引用类型区别
值类型的变量始终直接存储,传递的时候也始终直接拷贝值
引用类型的变量传递的时候其实传递的是该变量的引用,而不是将该变量拷贝一份
整型
整型分为有符号整型和无符号整型,分别用int和uint来进行表示
整型可以指定位数,如int8表示8位有符号整型,uint16表示16位无符号整型
整型最多可以指定256位,并以8位为步长进行递增
整型有默认值,且无论是有符号还是无符号整型,其默认值都为0
取值范围
以int16和uint16来举例
int16位有符号整型,也就是说,其二进制的第一位不能表示数值,只能用来表示符号位,0表示正数,1表示负数
uint16为无符号整型,其二进制的所有位置皆可用于表示数值,无需单独拿出一位来表示正负
int(integer) | uint(unsigned integer) |
---|---|
有符号整型 | 无符号整型 |
int8~int256,以8位为步长递增 | uint8~uint256,以8位为步长递增 |
int8取值范围为-128-127, int16取值范围为-32768-32767, 以此类推 | uint8取值范围为0-256, uint16取值范围为0-65535, 以此类推 |
-(2^(n-1))~+(2^(n-1)-1) | 0~(2*n-1_ |
int等同于int256 | uint等同于uint256 |
默认情况下,初始值为0 | 默认情况下,初始值为0 |
运算
算术运算符
- 四则运算
+,-,*,/为最基本的运算方式
需要格外注意,整型是有取值范围的,整型的四则运算同样具有取值范围,如果运算的结果超过了整型的取值范围,solidity会舍弃一部分值而降低精度,所以进行运算的时候要格外注意自己变量的取值范围
除非我们知道运算结果,否则,一般情况下,我们使用uint256来进行运算
除法不会保留小数位置,只保留整数位,且不会四舍五入,小数位会直接舍弃
不能除0
- %:取余运算符
不能%0
- **:幂运算
<<:左移运算符和>>:右移运算符
左移运算符和右移运算符的作用就是在二进制的基础上,进行左移或右移
左移表示将数字转换为二进制后,整体往左移动,新的位置由0填充
右移表示将数字转换为二进制后,整体往右移动,新的位置由0填充
例如4的二进制表达为0100,其左移结果为1000(十进制为8),右移结果为0010(十进制为2)
因为是二进制移动,所以左移相当于*2,右移相当于/2
布尔类型
布尔类型用bool来表示
布尔类型仅可取两个值,true和false
默认情况下,初始值为false
运算规则
- !逻辑非运算符,反转操作符数的逻辑状态。如果表达式为真,则逻辑非的结果为假,如!false为真,!true为假;
- ||逻辑或运算符,如果这两个操作数中有一个为真,则条件为真,
- &&逻辑与运算符,如果两个操作数都为真,则条件为真
- ==等于运算符,如果两侧相等,则返回真
- !=不等运算符,如果两侧不等,则返回真
短路规则(short-circuiting)
运算符||和&&都遵循同样的短路规则
A||B,如果A为真,则B会被短路,不执行
A&&B,如果A为假,则B会被短路,不执行
枚举类型
使用enum关键字来创建枚举类型
枚举主要用于创建用户自定义的类型
枚举将一个变量的取值限定为几个预设值中的一个
枚举类型应当至少拥有一名成员
枚举类型与整型之间的转换
- 枚举可以显示地与整型进行转化,无法进行隐式转换
- 显示转换会在运行时检查数值范围,如果不匹配,将会抛出异常
- 枚举元素默认为uint8
- 当枚举元素数量足够多时,会自动变为uint16,第一个元素默认为0,使用超出范围的数值时会报错
枚举变量的赋值方式
- 我们直接使用定义的枚举类型Size来声明Size枚举变量
- 枚举变量只能取其预设值中的一个
字符串
字符串类型用string表示
Solidity中,字符串使用双引号("")或单引号('')包裹
string类型为引用类型
solidity中string无结束符(不同于C等语言),也就是说“foo”相当于3个字符而不是4个字符
不同于Java等语言,solidity中的string类型没有级联操作(cancat方法)
string类型不能直接替换字符串中的某一节(replace方法)
string类型实际在存储的时候是使用bytes(变长字节数组),所以string类型可以和bytes之间进行转换,使用string()方法即可
string类型会消耗高额的gas
总之,solidity中的string类型没有其他高级语言中的一些使用的方法,使用起来多有不便
转义字符
当转义字符放在字符序列时,它将对它后续的字符进行替代并解释
转义字符 | 说明 |
---|---|
\n | 开始新的一行 |
\\ | 反斜杠 |
\' | 单引号 |
\" | 双引号 |
\b | 退格 |
\f | 换页 |
\r | 回车 |
\t | 制表符 |
\v | 重置制表符 |
\xNN | 表示十六进制值并插入适当的字节 |
\uNNNN | 表示Unicode值并插入UTF-8序列 |
定长字节数组
有固定的长度,且不可更改
bytes1,bytes2,bytes3,……,bytes32
byte是bytes1的别名
成员变量
.length用于获取定长字节数组的长度
定长字节数组与字符串之间的内置转换
可以将字符串赋给byte32类型变量
我们预先就已经知道数组大小的话,推荐使用定长字节数组,这样可以提高效率,节省空间
但是请注意,我们不能讲定长字节数组赋值给字符串,会报错
定长字节数组的存储表示
solidity会将变量转换为十六进制的ASCII码进行存储
ASCII对照表
举例
bytes1 test = 'a'
表示定义了长度为1的固定字节数组
'a'对应的ASCII码为97
97转换为十六进制为0x61
十六进制需要使用0x前缀表明后面是一个十六进制的数,默认情况下,十进制不需要加前缀
所以最终存储在区块链上的值为0x61
Solidity内部使用十六进制表示定长字节数组,如“bytes3 y = 3”在solidity内部会表示成:“0x000003”
地址类型
在区块链上,地址类型唯一的表示一个地址,即一个账户
在区块链上进行交易,消耗gas值都基于地址的
原生solidity语言地址类型使用Address表示
原生solidity语言地址类型为20个字节
蚂蚁链地址类型
蚂蚁链地址类型使用identity关键字来表示
蚂蚁链solidity语言地址类型为36个字节(不同于原生solidity语言的20个字节,蚂蚁链solidity语言可以支持更多的地址)
蚂蚁链的地址
- 用户链账户地址
蚂蚁链会为我们提供用户链账户托管服务,包括账户地址和密钥,所以我们无需在本地维护链账户
如何在程序中获取链账户
可以使用“msg.sender”来获取链账户地址
msf.sender是一个全局函数(蚂蚁链平台函数),可以在智能合约中直接调用,返回的是用户交易中的发送方,即发送方的链账户地址
msg.sender返回一个identity类型
地址类型的存储表示
之前我们介绍说,链账户是个32个字节的值,但我们实际发现,链账户地址实际上是一个64位的值,这是为什么呢?
正如二进制每个位置使用0~1来表示,十进制每个位置使用0~9来表示,十六进制每个位置使用0~F来表示;
一个4位的二进制数可以使用1位十六进制数来表示
一个8位的二进制数可以使用2位十六进制数来表示
一个字节的二进制表达为8位,如'a'的二进制表达为0110 0001,对应的十六进制为0x61
我们由此得出:一个字节可以通过2位的十六进制来表示
所以,为了方便表达以及记录方便,我们使用64位的十六进制来表示一个地址
- 交易哈希地址
交易哈希使用tx.txhash表示
交易哈希是蚂蚁联盟链上每笔交易的唯一标识
交易哈希也是一种identity类型
蚂蚁联盟链上,我们可以使用交易哈希在区块链浏览器来查询交易详情
数据位置
solidity语言在所有的复杂数据类型(即数组和结构类型)中,都允许指定一个额外属性,“数据位置”,说明数据是保存在内存中,还是存储中
Storage
该存储位置存储永久数据,这意味着该数据可以被合约中的所有函数访问。可以把它视为计算机的硬盘数据,所有数据都永久存储
保存在存储区(Storage)中的变量,以智能合约的状态存储,并且在函数调用之间保持持久性。与其他数据位置相比,存储区数据位置的成本较高
Memory
内存位置是临时数据,比存储位置便宜。它只能在函数中访问
通常,内存数据用于保存临时变量,以便在函数执行期间进行计算。一旦函数执行完毕,它的内容就会被丢弃。你可以把它想象成每个单独函数的内存(RAM)
Storage和memory补充说明
在蚂蚁链平台上,局部变量和函数参数,我们只能对数组或结构体指定Storage
对于public类型的函数的参数我们只能使用memory位置
storage是静态分配的,所以我们无法将一个动态的函数参数赋值给storage位置的变量
我们可以将函数参数声明为storage位置,这样storage位置的局部变量即可接收该值
Calldata
是不可修改的非持久性数据位置,所有传递给函数的值,都存储在这里。此外,Calldata是外部函数的参数(而不是返回参数)的默认位置
Stack
是由EVM(Ethereum虚拟机)维护的非持久性数据。EVM使用堆栈数据位置在执行期间加载变量。堆栈位置最多有1024个级别的限制
重要性
数据位置的指定非常重要,因为它们影响着赋值行为:
在存储storage和内存memory之间两两赋值,或者存储storage向状态变量(甚至是从其他状态变量)赋值都会创建一份独立的拷贝。然而状态变量向局部变量赋值时仅仅传递一个引用,而且这个引用总是指向状态变量,因此后者改变的同时前者也会发生改变。另一方面,从一个内存memory存储的引用类型向另一个内存memory存储的引用类型赋值并不会创建拷贝
要永久性存储,可以保存在存储区storage
要消耗更少,局部变量可以选择使用memory
强制指定的数据位置
- 外部函数的参数(不包括返回参数):calldata
- 状态变量:storage
默认数据位置
- 函数参数(包括返回参数):memory
- 所有其他局部变量:storage
数组
定义
数组是一种数据结构,它是存储同类元素的有序集合
数组中的特定元素由索引访问,索引值从0开始。例如,声明一个数组变量,如Numbers,可以使用numbers[0]、numbers[1]、……,numbers[99]表示每个变量
数组可以是固定大小的(即在声明时指定长度),也可以是动态长度的
如何声明
固定长度数组:一个元素类型为T,固定长度为k的数组可以声明为T[k];
动态长度数组:T[]
举例说明
一个长度为5,元素类型为uint的固定长度数组,应该声明为:uint[5]
一个动态长度的,元素类型为string的数组,应该声明为:string[]
使用new关键字来创建一个内存数组
特征
对于存储(storage)数组,元素类型可以是任意的(即元素也可以是数组类型,映射类型或者结构体)
对于内存(memory)数组,元素类型不能是映射类型
如果作为公共函数(public)的参数,那么元素类型必须是ABI类型
两个特殊的数组
- 变长字节数组bytes
- bytes类似于byte[];
- 实际上就是一个变长字节数组
- 字符串string
- 字符串string等价于bytes,但是目前不允许长度或索引访问
- 字符串可以和变长字节数组相互转换
- 使用string()方法可以将一个bytes转换为字符串
- 使用bytes()方法可以将一个字符串转换为bytes
成员变量
- Length
获取元素数量
- Push
- 数组和bytes都有一个叫做push的成员变量,可以使用它将新的元素附加到数组末尾
- 该函数返回新的数组长度
映射
定义
映射使用关键字mapping来声明
可以理解为其他语言中的字典或map,映射建立了一个键值对的对应关系(键=>值),一个键对应一个值,我们可以直接通过键来查询该键对应的值是多少
其中,键必须是唯一的,而值是可以重复的
映射是一种引用类型
映射的声明
形式为:mapping(_KeyType=>_ValueType)
其中_KeyType可以是除了映射、变长数组、合约、枚举及结构体(引用类型)以外的几乎所有类型
_ValueType可以是包括映射在内的任何类型,也就是说,_ValueType也可以是一个映射,我们可以创建一个映射的映射
可以将映射声明为public可见性,solidity会自动为其创建一个getter
如何操作映射
我们可以这样来向mapping中添加新值:MappingName["_Key"]=_Value
同样的,我们可以改变_Key为一个已存在的键值对的键来修改该键值对对应的值
只有状态变量可以使用映射
必须使用一个有效的key值来获取value
无法使用index来调用
为什么要使用映射
我们可能会使用数组,或者直接在合约中定义大量的状态变量来完成这件事情
但是,这并不是一个足够好的方法,更加简便和高效的方法就是使用solidity提供给我们的映射
映射在今后的编程中会经常使用,所以大家一定要掌握映射的相关知识
注意事项
映射是没有长度的
映射没有key的集合或者value的集合
结构体
定义
solidity允许我们使用结构体来创建自定义类型(复合型数据)
结构体使用struct关键字来定义
结构体中可以存放任意类型的值
结构体是引用类型
结构体的定义
struct struct_name{
type1 type_name_1;
type2 type_name_2;
type3 type_name_3;
}
结构体赋值
我们可以使用如下方式来给结构体直接赋值:
StructName_Variable_Name = StructName(_Value1, _Value2,_Value3);
_Value1, _Value2,_Value3必须和结构体中的类型一一对应
我们在定义结构体之后可以直接声明一个变量并对其赋值
可以直接访问结构体的成员
_Variable_Name.Type = _Value;
结构体补充说明
结构体可以作为元素用在映射和数组中
结构体可以包含映射和数组
时间单位
Unix时间戳
Unix时间戳(Unix timestamp),或称Unix时间(Unix time)、POSIX(POSIX time),是一种时间表示方式,定义为从格林威治时间1970年01月01日00时00分00秒起至现在的总秒数
上世纪70年代,那时候的计算机系统是32位,时间需要精确到秒,而32位有符号数来表示秒最多只能表示68年,32位无符号数则可以表示138年;
所以当时的人们就使用格林威治时间1970年01月01日00时00分00秒来作为纪元时间,使用从该纪元时间到现在的总秒数来代表时间戳
早期的计算机语言(如B语言,C语言)及操作系统(如Unix系统)都采用了这种方式,延续至今
Unix时间戳的重要性
时间戳的主要目的是通过一定的技术手段,对数据产生的时间进行认证,从而验证这段数据在产生后是否经过篡改
尤其是在交易环节,时间戳的重要性更加不言而喻
由于现在主流编程语言和主流操作系统都支持Unix时间戳,所以solidity语言也不例外
Unix时间戳转换及各主流编程语言对时间戳的支持
在solidity语言中,我们可以使用now关键字来获取当前的Unix时间戳(1970年1月1日以来经过的秒数)
时间单位
- 秒是缺省时间单位(默认时间单位)
- solidity支持的时间单位有
- seconds
- minutes
- hours
- days
- weeks
- years
- 时间单位的换算关系
- 1 == 1 seconds
- 1 minutes == 60 seconds
- 1 hours == 60 minutes
- 1 days == 24 hours
- 1 weeks == 7 days
- 1 years = 365 days
注意事项
years后缀已经不推荐使用了,因为从0.5.0版本开始将不再支持该后缀
时间单位后缀不能直接用在变量后面
如果想用时间单位来将输入变量换算为时间,可以采用如下方式
uint public oneWeekLater = now +1 weeks;
我们需要特别注意,Cloud IDE编译环境会为我们返回毫秒值,所以我们需要使用如下代码才正确:
uint public oneWeekLater = now +1 weeks * 1000;
蚂蚁链Solidity与原生Solidity语言的区别
蚂蚁链目前支持0.4.24和0.6.4版本
蚂蚁链平台中,solidity合约使用identity替代原生solidity的address关键字
identity表示的合约地址或账户地址,均为32字节,而官方solidity中address表示的地址是20字节
蚂蚁链合约不支持在合约内创建合约,因此不要在合约内使用new来创建合约
蚂蚁链合约不支持suicide,也不支持selfdestruct
在蚂蚁链平台上,如果尝试在合约内向一个不存在的地址转账,合约会异常终止,并返回错误码10303;而在以太坊官方solidity合约内向不存在的地址转账时,系统会自动为该地址创建账户
在蚂蚁链平台中,合约内的block.number、block.timestamp分别指最新已形成的区块(即本交易所在区块的上一个区块)的高度和时间戳(毫秒);而官方solidity合约中,这两个参数分别指的是本次交易所在的高度和时间戳
蚂蚁链平台中,如果solidity合约执行过程中异常终止,终止之前产生的Event Log依然会出现在交易回执中
Solidity函数
什么是函数?
函数原本是数学上的概念,有着非常精确的定义:
函数表示两不为空集的集合间的一种对应关系:输入值集合中的每项元素皆能对应唯一一项输出值集合中的元素
我们常用f(x)来表示一个函数,而f就是英文中function的简写
Solidity函数的概念
solidity函数是一组可重用代码的包装,是合约代码中的可执行单元,接受输入,返回输出
定义
solidity中,我们可以使用如下方式定义一个函数
语法
可以看到,solidity语言在设计上参考了这些主流的编程语言,关于其语法我们总结如下:
使用function关键字来声明一个函数
函数如果有返回值,必须在函数定义中显示申明函数的返回值类型
函数有可见性
函数参数需要用小括号括起来
函数参数
函数的入参定义与变量类似,可以省略未使用到的参数变量名
函数的返回参数有默认值,如整型会被初始化为0,如果没有被明确地设置值,那么它会一直保持0
函数调用
要进行函数调用,使用函数名加函数参数即可调用
函数调用分为内部函数调用和外部函数调用
内部函数调用:只能在当前合约内被调用,调用形式为:function_name();
外部函数调用:由地址和方法签名两部分组成,可作为外部函数调用的参数或返回值,调用形式为:this.function_name()或者contract_instance.function_name();
内部调用和外部调用的区别在于是否采用消息调用:
内部函数调用:EVM内部一个简单的指令跳转
外部函数调用:实际上是通过一个消息调用,而不是EVM内部指令跳转
要调用其他合约的函数必须通过外部调用方式来完成
不可以在构造函数中通过this调用函数
函数返回值
返回值的定义与参数类似,使用returns关键字来标识要返回值得类型
返回值类型可以省略变量名称
在函数体中,使用return关键字来返回值
返回值的类型必须和函数定义中的返回值类型一致
Solidity中可以返回任意数量的参数作为输出,有两种返回方式:
- 使用返回参数名来返回参数
- 使用return关键字来返回多个值,这种情况下,函数参数变量名可以省略
命名参数
函数调用可以使用命名参数
可以将如下键值对以任意顺序放进{}中:
{key:value,key:value}
传递的参数类型一定要与函数入参的定义类型一致
函数可见性
Public
public函数是合约接口的一部分,可以在内部或通过消息调用
External
外部函数作为合约接口的一部分,意味着我们可以从其他合约和交易中调用。一个外部函数f不能从内部调用(即f不起作用,但this.f()可以)。当收到大量数据的时候,外部函数有时候会更有效率。
Internal
这些函数和状态变量只能是内部访问(即从当前合约内部或从它派生的合约访问),不使用this调用。
private
private函数和状态变量仅在当前定义它们的合约中使用,并且不能被派生合约使用。
注意事项
我们知道,智能合约拥有“透明性”特征,也就是说,智能合约一旦部署到区块链上,其源码对所有人可见,所以我们设置“private”类型只能阻止其他合约访问和修改这些信息,但是对于区块链外的整个世界它仍然是可见的
可见性标识符的定义位置,对于状态变量来说是在类型后面,对于函数是在参数列表和返回关键字中间
view函数
可以将函数声明为view类型,这种情况下要保证不修改状态。
下面的语句被认为是修改状态:
- 修改状态变量
- 产生事件
- 创建其他合约
- 使用selfdestruct
- 调用任何没有标记为view的函数
- 使用低级调用
- 使用包含特定操作码的内联汇编
编译器没有强制view方法不能修改状态
Solidity控制结构
条件语句
solidity支持条件语句,让程序可以根据条件执行不同的语句
if语句应该出现在函数中,不应该单独出现在智能合约中
条件语句包括:
- if
- if...else
- if...else if
条件表达式
条件表达式经常使用比较操作符,常用的比较操作符如下:
- >
- <
- >=
- <=
- ==
- !=
可以使用逻辑运算符写出更加复杂的条件表达式
注意事项
用于表示条件的括号不可以被省略,单语句体两边的花括号可以被省略
solidity中非布尔类型数值不能转换为布尔类型,因此if(1){...}的写法在solidity中无效,属于编译时错误
当有多个if else嵌套时,要注意每个条件之间的关系,尽量避免出现存在交集的条件表达式
循环语句
定义
solidity语言为我们提供了三个循环结构
- while
- do ... while
- For
可以使用循环来完成迭代操作,也可以完成数组的遍历操作
我们可以使用循环控制语句break和continue来控制循环
- Break:跳出循环,直接跳出代码块,不会再执行任何关于该循环的操作
- Continue:跳出本次循环,继续执行接下来的循环
注意事项
由于do ...while循环会在不判断表达式的前提下直接执行一次代码块,所以如果不注意代码块的逻辑,会经常出现错误。因此,对于循环语句,我们推荐使用while循环和for循环
注意不要出现死循环,否则合约调用会出错
蚂蚁链平台常用接口函数
区块数据接口函数
block.blockhash(uint blockNumber) return(bytes32):传入blockNumber,返回块的哈希值
block.gaslimit(uint):系统中gas最大值
block.number(uint):最新已经形成的区块高度,注意不是当前正在形成的区块高度
block.timestamp(uint):最新已经形成的区块时间戳,注意不是当前正在形成的区块时间戳
now(uint):block.timestamp的别名
交易数据接口函数
注意:以下方法介绍中,括号内是返回的结果数据类型,并不是传入参数。
msg.data(bytes):用户的输入数据
msg.gas(uint):用户交易中的gas值,即该合约中剩余的gas值
msg.sender(identity):用户交易中的发送方
msg.sig(bytes4):用户交易输入数据的前四字节
msg.value(uint):用户交易中的gas值,即给该合约中转移的gas值
加密接口函数
- sha256加密
sha256(bytes data) returns(bytes32 result);
蚂蚁区块链合约平台对sha256加密函数进行了修改,采用OpenSSL加密库进行实现。
- erecover加密
- Ripemd160加密(蚂蚁链平台不支持)
Solidity编码规范
编码规范
编码规范就是程序编码所要遵循的规则,要注意代码的正确性、稳定性、可读性
就像工程建造中需要有工程建造标准一样,一个大型软件开发过程中,涉及到的人员数量是庞大的,这就需要有一套编码规范来对代码就像规约
不同的编程语言,不同的公司,都有自己的编码规范,虽然略有不同,但是设计理念上都是相同的
要避免使用不易理解的数字,用有意义的标识来替代,不要用难懂的技巧性很高的语句
编码规范的重要性
规范的编码有助于提高效率。
如果没有统一的编码规范,每个人的代码风格迥异,在进行代码审查和修改bug的过程中势必会浪费很多时间,效率低下
规范的代码可以减少bug的数量。
往往一个项目中会出现各种各样的因为没有规范的输入输出,没有规范的错误处理或者没有规范的日志处理而出现大量傻瓜的bug,而代码规范可以避免出现这样的问题
规范代码可以降低维护成本
规范代码可以大大提高程序的可读性,对于后来者维护代码大有裨益
规范代码也可以让大家轮岗很容易
Solidity编码规范
solidity为我们提供了一份代码编写规范,该规范是不断演进的,旧的、过时的编码规范会被淘汰,新的、有用的编码规范会被添加进来
我们知道,编码规范是为了提高团队的效率,减少项目的bug率而产生的,所以如果你们的团队有自己的一套编码规范,优先考虑使用你们团队的编码规范(项目中的一致性是最重要的)
但是万变不离其宗,编码规范的一些核心要领都是一致的
代码结构
缩进:使用四个空格
制表符和空格:优先使用空格,避免混用
空行:合约声明之间留出两行空行,函数声明之间留出一行空行
代码行最大长度:79(或99)字符内
表达式中的空格
- 除单行函数声明外,紧接着小括号,中括号或者大括号的内容应该避免使用空格;
- 赋值或其他操作符两边各一个空格
用大括号表示合约、库、函数和结构,应该
- 开括号与声明应在同一行
- 闭括号在与之前函数声明对应的开括号保持同一缩进级别上另起一行
- 开括号前应该有空格
命名规范
避免使用:1,l,O(1和l容易混淆,O和0容易混淆)
使用驼峰式命名且首字母大写:合约和库的名称、结构体名称、事件名称、枚举名称
使用驼峰式命名且首字母小写:函数名称、函数参数名称、局部变量和状态变量
命名不应该和关键字(或称保留字)相同
Solidity关键字
关键字是电脑语言里事先定义的,有特别意义的标识符,有时又叫做保留字,还有特别意义的变量
关键字对编程语言来说有特殊的意义,他们用来表示一种数据类型,或者表示程序的结构等
关键字不能用作变量名、方法名、类名、包名和参数
- 基本数据类型:uint、int、byte、bytes、string ...
- 智能合约结构:contract、constructor、pragma、this、super、function、event、struct、mapping...
- 函数权限相关:public、private、internal、external
- 函数修饰相关:modifier、constant、view、pure、Storage、Memory
- 其他保留字:abstract、after、alias、apply、auto、case、catch、copyof、default、in、left、type、switch、of、null、sizeof、try...
Cloud IDE使用和调试技巧
安全检测
Cloud IDE可以创建合约安全检测任务,对工程中的合约进行安全检测,检测结果中提供合约的相关统计信息和安全问题,辅助和指导开发者更好地编写安全规范的智能合约
限制策略:每个开发者每天只能有10次的检测任务配额,第二天0时配额恢复
TEE加密配置
在Cloud IDE中进行环境配置时,可选择连接TEE硬件隐私合约链环境
目前,TEE硬件隐私合约链的加密交易仅支持solidity语言
尚未正式对外发布