标签归档:Solidity

使用Truffle部署智能合约

简介

Truffle是一个世界级的开发环境,测试框架,以太坊的资源管理通道,致力于让以太坊上的开发变得简单,Truffle有以下:

  • 内置的智能合约编译,链接,部署和二进制文件的管理。

  • 快速开发下的自动合约测试。

  • 脚本化的,可扩展的部署与发布框架。

  • 部署到不管多少的公网或私网的网络环境管理功能

  • 使用EthPM&NPM提供的包管理,使用ERC190标准。

  • 与合约直接通信的直接交互控制台(写完合约就可以命令行里验证了)。

  • 可配的构建流程,支持紧密集成。

  • 在Truffle环境里支持执行外部的脚本。

安装Truffle

nodejs版本5.0以上。如果中断报错就多试几次吧。

构建一个Truffle项目

安装完truffle之后,我们使用官方的案例来走一遍流程。

创建项目目录:

使用官方安装官方metacoin示例,

安装完成后目录:

测试项目

编译智能合约

编译成功后项目目录下会多出一个build的目录,保存编译文件。

发布合约到本地环境

首先启动geth

启动成功后,我们开启开启挖矿。因为发布合约需要旷工挖矿才能生效。

truffle迁移合约

在迁移之前我们需要先编写我们的配置文件,主要设置网络配置。

设置网络环境为development,并且配置对应的ip:port

设置好配置之后,我们尽心迁移

此时我们已经将合约发布到我们本地的私有链上去了。

与智能合约交互

使用控制台通过下面的方式进行交互:

  • 查看账户余额

  • 看看有多少以太合适(并注意合约定义1个metacoin价值2以太)

  • 从一个账号转账到另一个账号

  • 检查是否收到metacoin

  • 检查给别人转账的账户

注意:

  1. MetaCoin 的合约地址如下:

  1. 配置文件中的network_id: "*"表示匹配所有网络。

  2. 配置文件中需要填写gasgasPrice的设置,否者迁移的时候会报错。

报错信息如下:

  1. 迁移的时候主账户必须解锁,否则会报错帐号未解锁。

搭建自己的Hello World

创建项目目录,

初始化项目

编写合约hello world

编写迁移文件

编译合约

设置配置文件

迁移合约

合约地址为

进入truffle的控制台,并且调用方法输出hello world

Solidity 基础知识

一个简单的例子

创建和编译智能合约

这个例子主要讲解如何发布、执行智能合约

vim testContract.sol编辑一个sol文件,主要功能是取两个值的乘积。

首先获取EVM的二进制编码

再获取智能合约的 JSON ABI(Application Binary Interface),其中指定了合约接口,包括可调用的合约方法、变量、事件等:

启动区块链,然后这2个值保存在变量中。

需要注意的是,二进制编码前面需要添加0x前缀:

具体操作如下,

部署智能合约

首先需要解锁自己的账户

发送部署合约的交易:

此时如果没有挖矿,用 txpool.status 命令可以看到本地交易池中有一个待确认的交易。使用下面的命令查看当前待确认的交易:

智能合约调用

使用以下命令发送交易,sendTransaction 方法的前几个参数应该与合约中 multiply 方法的输入参数对应。这种情况下,交易会通过挖矿记录到区块链中:

如果只是本地运行该方法查看返回结果,可以采用如下方式:

搭建remix浏览器

安装

推荐使用 npm 或者 cnpm 直接安装。使用 git clone 源代码然后npm install会有很多报错情况。

启动

启动后,会监听127.0.0.1:8080,访问该网址就可以了。如果使用虚拟机无法访问改地址,使用nginx转发请求就可以了。

或者直接修改/usr/local/bin/remix-ide 文件

共享目录

启动的时候添加参数路径即可,如下

如果说需要非本地访问需要修改源码2处:

1、修改websocket监听ip: 127.0.0.1 => 0.0.0.0

2、修改访问websocket地址,修改成外网本地ip地址。

这两处修改后,启动服务。点击共享图标,就可以看到文件目录下有localhost文件夹,为共享目录下的文件。

Solidity语法

基础类型

Solidity是一种静态类型语言,Solidity类型分为两类:

  • 值类型(Value Type) – 变量在赋值或传参时,总是进行值拷贝。
  • 引用类型(Reference Types)

值类型包含:

  • 布尔 booleans
  • 整形 intergers
  • 定长浮点 fixed point numbers
  • 定长字节数组 fixed-size byte arrays
  • 有理数和整型常量 retional and integer literals
  • 字符串常量 string literals
  • 十六进制常量 hexadecimal literals
  • 枚举 enums
  • 函数类型 function types
  • 地址类型 address
  • 地址常量 address literals

引用类型包含:

  • 数组 arrays
  • 结构体 struct
整型

int/uint: 表示有符号和无符号不同为数整数。支持关键字uint8 到 uint256 (以8步进),uint 和 int 默认对应的是 uint256 和 int256。

说明:

  • 整数除法总是截断的,但如果运算符是字面量(字面量稍后讲),则不会截断。
  • 整数除0会抛异常。
  • 移位运算的结果的正负取决于操作符左边的数。x << y 和 x^2y 是相等, x >> y 和 x / 2y 是相等的。
  • 不能进行负移位,即操作符右边的数不可以为负数,否则会抛出运行时异常。

注意:Solidity中,右移位是和除等价的,因此右移位一个负数,向下取整时会为0,而不像其他语言里为无限负小数。

定长字节数组(Fixed-size byte arrays)

关键字有:bytes1, bytes2, bytes3, …, bytes32。(以步长1递增)
byte代表bytes1。

枚举(Enums)

在Solidity中,枚举可以用来自定义类型。它可以显示的转换与整数进行转换,但不能进行隐式转换。显示的转换会在运行时检查数值范围,如果不匹配,将会引起异常。枚举类型应至少有一名成员。

地址类型

地址类型address是一个值类型,

地址: 20字节(一个以太坊地址的长度),地址类型也有成员,地址是所有合约的基础

地址类型的成员

  • balance 属性及transfer() 函数

balance用来查询账户余额,transfer()用来发送以太币(以wei为单位)。

注解:如果x是合约地址,合约的回退函数(fallback 函数)会随transfer调用一起执行(这个是EVM特性),如果因gas耗光或其他原因失败,转移交易会还原并且合约会抛异常停止。

关于回退函数(fallback 函数),简单来说它是合约中无函数名函数,下面代码事例中,进进一步讲解回退函数(fallback) 的使用。

  • send() 函数

send 与transfer对应,但更底层。如果执行失败,transfer不会因异常停止,而send会返回false。

警告:send() 执行有一些风险:如果调用栈的深度超过1024或gas耗光,交易都会失败。因此,为了保证安全,必须检查send的返回值,如果交易失败,会回退以太币。如果用transfer会更好。

  • call(), callcode() 和 delegatecall() 函数

为了和非ABI协议的合约进行交互,可以使用call() 函数, 它用来向另一个合约发送原始数据,支持任何类型任意数量的参数,每个参数会按规则(ABI协议)打包成32字节并一一拼接到一起。一个例外是:如果第一个参数恰好4个字节,在这种情况下,会被认为根据ABI协议定义的函数器指定的函数签名而直接使用。如果仅想发送消息体,需要避免第一个参数是4个字节。

地址常量(Address Literals)

一个能通过地址合法性检查(address checksum test)十六进制常量就会被认为是地址,如0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF。而不能通过地址合法性检查的39到41位长的十六进制常量,会提示一个警告,被视为普通的有理数常量。

函数类型

函数类型定义如下:

  • 如果函数不需要返回,则省去returns ()
  • internal函数只能在当前合约内被调用(在当前的代码块内,包括内部库函数,和继承的函数中)。
  • external函数由地址和函数方法签名两部分组成,可作为外部函数调用的参数,或返回值。
  • public函数任何用户或者合约都能调用和访问。与external区别,external不能被内部调用。
  • private函数,只能在其所在的合约中调用和访问,即使是其子合约也没有权限访问。与internal区别,不能再集成合约中调用。
  • 函数类型默认是internal, 因此internal可以省去。合约中函数本身默认是public的。
  • 有两个方式访问函数,一种是直接用函数名f, 一种是this.f, 前者用于内部函数,后者用于外部函数。

成员: 属性 selector

public (或 external) 函数有一个特殊的成员selector, 它对应一个ABI 函数选择器。

函数可见性分析

  • public – 任意访问
  • private – 仅当前合约内
  • internal – 仅当前合约及所继承的合约
  • external – 仅外部访问(在内部也只能用外部访问方式访问)

数据位置

引用类型是一个复杂类型,占用的空间通常超过256位, 拷贝时开销很大,因此我们需要考虑将它们存储在什么位置,

  • 是memory(内存中,数据不是永久存在)
  • 还是storage(永久存储在区块链中)
  • 还有一个存储位置是:calldata,效果与memory差不多。
memory

函数参数(包含返回的参数)默认是memory。

storage

局部复杂类型变量(local variables)和 状态变量(state variables) 默认是storage。

calldata

用来存储函数参数,是只读的,不会永久存储的一个数据位置。外部函数的参数(不包括返回参数)被强制指定为calldata。效果与memory差不多。

  • 在memory和storage之间或与状态变量之间相互赋值,总是会创建一个完全独立的拷贝。
  • 将一个storage的状态变量,赋值给一个storage的局部变量,是通过引用传递。所以对于局部变量的修改,同时修改关联的状态变量。
  • 将一个memory的引用类型赋值给另一个memory的引用,不会创建拷贝(即:memory之间是引用传递)。

注意:

  • 不能将memory赋值给局部变量。
  • 对于值类型,总是会进行拷贝。

总结
强制的数据位置(Forced data location)

  • 外部函数(External function)的参数(不包括返回参数)强制为:calldata
  • 状态变量(State variables)强制为: storage

默认数据位置(Default data location)

  • 函数参数及返回参数:memory
  • 复杂类型的局部变量:storage

关于栈(stack)

EVM是一个基于栈的语言,栈实际是在内存(memory)的一个数据结构,每个栈元素占为256位,栈最大长度为1024。
值类型的局部变量是存储在栈上。

不同存储的消耗(gas消耗)
– storage 会永久保存合约状态变量,开销最大
– memory 仅保存临时变量,函数调用之后释放,开销很小
– stack 保存很小的局部变量,几乎免费使用,但有数量限制。

数组

一个元素类型为T,固定长度为k的数组,可以声明为T[k],而一个动态大小(变长)的数组则声明为T[]。

bytes和string是一种特殊的数组。bytes类似byte[],但在外部函数作为参数调用中,bytes会进行压缩打包。string类似bytes,但不提供长度和按序号的访问方式(目前)。
所以应该尽量使用bytes而不是byte[]。

创建内存数组

可使用new关键字创建一个memory的数组。与stroage数组不同的是,你不能通过.length的长度来修改数组大小属性。我们来看看下面的例子:

成员

ength属性

数组有一个.length属性,表示当前的数组长度。storage的变长数组,可以通过给.length赋值调整数组长度。memory的变长数组不支持。
不能通过访问超出当前数组的长度的方式,来自动实现改变数组长度。memory数组虽然可以通过参数,灵活指定大小,但一旦创建,大小不可调整。

**push方法*

storage的变长数组和bytes都有一个push方法(string没有),用于附加新元素到数据末端,返回值为新的长度。

限制情况

当前在external函数中,不能使用多维数组。

另外,基于EVM的限制,不能通过外部函数返回动态的内容。

结构体

Solidity提供struct来定义自定义类型,自定义的类型是引用类型。

不能声明一个struct同时将自身struct作为成员,这个限制是基于结构体的大小必须是有限的。

映射

映射类型,一种键值对的映射关系存储结构。定义方式为mapping(_KeyType => _KeyValue)。键类型允许除映射、变长数组、合约、枚举、结构体外的几乎所有类型()。值类型没有任何限制,可以为任何类型包括映射类型。

映射并未提供迭代输出的方法,可以自行实现一个这样的数据结构。

货币单位(Ether Units)

一个数字常量(字面量)后面跟随一个后缀wei, finney,szabo或ether,这个后缀就是货币单位。不同的单位可以转换。不含任何后缀的默认单位是wei。

插曲:以太币单位其实是密码学家的名字,是以太坊创始人为了纪念他们在数字货币的领域的贡献。他们分别是:
wei: Wei Dai 戴伟 密码学家 ,发表 B-money
finney: Hal Finney 芬尼 密码学家、工作量证明机制(POW)提出
szabo: Nick Szabo 尼克萨博 密码学家、智能合约的提出者

时间单位(Time Units)

时间单位: seconds, minutes, hours, days, weeks, years均可做为后缀,并进行相互转换,规则如下:

使用这些单位进行日期计算需要特别小心,因为不是每年都是365天,且并不是每天都有24小时,因为还有闰秒。由于无法预测闰秒,必须由外部的预言(oracle)来更新从而得到一个精确的日历库。

错误处理

Solidity处理错误和我们常见的语言不一样,Solidity是通过回退状态的方式来处理错误。发生异常时会撤消当前调用(及其所有子调用)所改变的状态,同时给调用者返回一个错误标识。注意捕捉异常是不可能的,因此没有try … catch…

为什么Solidity处理错误要这样设计呢?

我们可以把区块链理解为是全球共享的分布式事务性数据库。全球共享意味着参与这个网络的每一个人都可以读写其中的记录。如果想修改这个数据库中的内容,就必须创建一个事务,事务意味着要做的修改(假如我们想同时修改两个值)只能被完全的应用或者一点都没有进行。
学习过数据库的同学,应该理解事务的含义,如果你对事务一词不是很理解,建议你搜索一下“数据库事务“。
Solidity错误处理就是要保证每次调用都是事务性的。

Solidity提供了两个函数assert和require来进行条件检查,如果条件不满足则抛出异常。assert函数通常用来检查(测试)内部错误,而require函数来检查输入变量或合同状态变量是否满足条件以及验证调用外部合约返回值。
另外,如果我们正确使用assert,有一个Solidity分析工具就可以帮我们分析出智能合约中的错误,帮助我们发现合约中有逻辑错误的bug。

  • revert函数可以用来标记错误并回退当前调用
  • 使用throw关键字抛出异常(从0.4.13版本,throw关键字已被弃用,将来会被淘汰。)

当子调用中发生异常时,异常会自动向上“冒泡”。 不过也有一些例外:send,和底层的函数调用call, delegatecall,callcode,当发生异常时,这些函数返回false。

注意:在一个不存在的地址上调用底层的函数call,delegatecall,callcode 也会返回成功,所以我们在进行调用时,应该总是优先进行函数存在性检查。

参考链接:

https://learnblockchain.cn/2018/06/07/remix-ide/https://blog.csdn.net/piqianming/article/details/79756900https://hiblock.net/topics/212https://blog.csdn.net/jerry81333/article/details/78118972https://learnblockchain.cn/categories/ethereum/Solidity/page/2/