美文网首页Dapp开发
转载—DAPP实战:从0到1全过程(2)

转载—DAPP实战:从0到1全过程(2)

作者: 03e9c48218c1 | 来源:发表于2018-08-06 22:24 被阅读67次

转载微信订阅号(区块链之录)文章:

DAPP实战:从0到1全过程(2)

本章主要介绍DAPP合约开发基础,solidity基本语法、开发、编译和运行环境,以及合约调试,为forever bird合约开发做准备

Solidity 是一门面向合约的、为实现智能合约而创建的高级编程语言,运行在太坊虚拟机(EVM)上。下面我们对solidity相关内容进行讲解。

01

配置solidity开发工具

很多编辑器都支持solidity开发,例如:SublimeText、Atom、Visual Studio Code等。可参考如下路径:

http://solidity-cn.readthedocs.io/zh/develop/index.html

我们编写合约选择使用Visual Studio Code,安装Solidity插件可以提供语法高亮、关键字提示、合约编译等功能。

详细配置方法请参考一下路径:

http://juan.blanco.ws/solidity-contracts-in-visual-studio-code/

02

运行环境

智能合约是运行在以太链的虚拟机上面,合约编译通过之后首先部署到以太链,可以使用testrpc或者geth搭建以太坊私链,然后使用以太坊钱包(Ethereum Wallet)、trffule框架或者remix进行部署到私链。

Ethereum Wallet下载路径:

https://github.com/ethereum/mist/releases

trffule参考文档:

http://truffle.tryblockchain.org

Remix参考文档:

https://github.com/ethereum/remix

Ethereum Wallet提供了友好的界面,可以通过界面完成部署合约、执行合约、转账等操作。Trffule提供了nodejs的程序开发框架,内置的智能合约编译、链接、部署等,简化合约的开发流程。remix是基于浏览器的 IDE,集成了编译器和 Solidity运行时环境,可以使用内置的JavaScript VM作为运行环境,也使用当前浏览器的web3 Provider或自定义web3 Provider连接其他私链的node。

我们现在选择使用remix,使用其内置的JavaScript VM作为运行环境。remix除了提供运行环境,还集成了合约的调试功能,可以追溯合约执行逻辑、跟踪合约状态、查看内存数据等,可以方便检测和验证合约的正确性。

03

solidity语法简介

智能合约是存储于以太链的代码和数据,每个部署以太坊的合约都有一个特定的地址,可以通过合约地址调用代码函数更新合约中数据。关于solidity的详细介绍,请参考文档:

http://solidity-cn.readthedocs.io/zh/develop/index.html

源文件结构

版本标注

合约开头要有版本标注,如下所示:

pragmasolidity^0.4.11;

该标注说明源文件只允许编译器版本[0.4.11,0.5.0)

合约声明的关键字contract,例如:

contractSimple{...}

注释

可以使用单行注释(//)和多行注释(/*...*/)

状态变量

永久存储在合约存储中的值。

contractSimple{ uintstoredData;// 状态变量   ... }

Solidity常见类型:

bool:可能的取值为字面常数值 true和 false。

int / uint :分别表示有符号和无符号的不同位数的整型变量。支持关键字 uint8 到 uint256(无符号,从 8 位到 256 位)以及 int8 到 int256,以 8 位为步长递增。 uint 和 int 分别是 uint256 和 int256 的别名。

address:地址类型存储一个 20 字节的值(以太坊地址的大小)。地址类型也有成员变量,并作为所有合约的基础。

地址类型成员变量(如 balance、transfer)请参考如下文档:

http://solidity-cn.readthedocs.io/zh/develop/units-and-global-variables.html#address-related 

bytes1, bytes2, bytes3, ..., bytes32,标识定长字节数组。

bytes 标识变长数组

string 标识变长UTF-8编码字符串类型。

enum 枚举类型

enum ActionChoices { Red, Blue}

函数类型

function()

{internal|external}

[pure|constant|view|payable]

[returns ()]

数据位置

所有的复杂类型,即数组结构类型,都有一个额外属性:数据位置,说明数据是保存在内存memory中还是存储storage 中。

强制指定的数据位置:

外部函数的参数(不包括返回参数):calldata

状态变量:storage

默认数据位置:

函数参数(包括返回参数):memory

所有其它局部变量: storage

存储和内存之间两两赋值,或存储向状态变量赋值都会创建一份独立的拷贝。 状态变量向局部变量赋值时仅传递一个引用,而且这个引用总是指向状态变量,因此后者改变的同时前者也会发生改变。

关于存储的详细内容请参考一下文档:

http://solidity-cn.readthedocs.io/zh/develop/introduction-to-smart-contracts.html#index-10

结构体 形式如下:

structFunder{addressaddr;uintamount;}

映射

映射类型在声明时的形式为 mapping(key=>value)。 其中key可以是除了映射、变长数组、合约、枚举以及结构体以外的几乎所有类型。value可以是包括映射类型在内的任何类型。

以上是对solidity的简介,关于solidity深入理解,请参考以下文档:

http://solidity-cn.readthedocs.io/zh/develop/solidity-in-depth.html

04

solidity编写和运行

我们首先编写一个简单的合约,用来记录用户账本,创建一个结构体User,User.addr标识用户地址,User.count标识用户存款次数,User.amount标识用户当前金额。add函数用户存款,get函数用户查看存款次数和金额。合约代码如下:

pragma solidity ^0.4.11;

contract Sample{

    struct User {

        address addr;

        int64 count;

        int64 amount;

    }

    mapping(address=>User) users;

    function add(int64 amount) external

    returns (bool) {

        if(users[msg.sender].addr == 0) {

            users[msg.sender] = User({

                addr:msg.sender,

                count:0,

                amount:0

});

        }

        users[msg.sender].count += 1;

        if(amount < 0) {

            return false;

        }

        users[msg.sender].amount += amount;

        return true;

    }

    function get() public view

    returns (int64, int64) {

        return (users[msg.sender].count,users[msg.sender].amount);

    }

}

我们把智能合约代码贴到remix代码区,如下图:

勾选"Auto compile",如果内容发生改变编译器会立刻编译,如下图,我们去掉一个分号,右边会立刻显示合约错误。

点击“Details”按钮,我们可以查看合约的详细内容,比如ABI、汇编代码等。我们查看编译没有问题,这时候就可以部署运行,切换Tab "Run",如下图所示。

运行环境默认选择当前浏览器提供的web3 provider,因为我的浏览器安装了Metamask钱包,并且连接了rinkeby测试网络,所以图中Environment显示的是Metamask提供的web3 provider。我们现在切换环境为remix内置的JS VM,会创建一些测试地址,每个地址分配100eth,切换之后如下图所示:

切换之后我们就点击按钮"deploy"发布合约,发布合约会消耗gas,所以我们的账户ether会变化,合约发布成功之后会展示到“Depolyed Contracts” 区域,合约源码下方的console区域会展示交易凭证(transaction receipt)信息,还可以点击“Debug”调试追溯本次交易,如下图:

展开合约函数,我们依次调用add和get函数,如下图所示。

执行add函数,就会给本地址账户添加金额111,交易执行打包完成之后,合约的状态存储区数据就发生了改变,我们点击get的debug,查看当前状态数据,如下图:

这时我们的get函数只能查看自己的金额,如果想要查看其他用户金额,我们再添加一个函数getAmount函数,如下:

function getAmount(address addr) public view 

    returns int64

{

        return users[addr].count;

    }

我们就可以调用getAmount获取其他用户的账户金额。

添加该函数之后就存在一个问题,所有人都可以查看其他账户的金额,显然这是不合理的,需要添加一个限制:必须是合约的创建者才可以调用该函数,该如何实现?

方法一:我们可以在每个需要限制的函数开头加入地址判断,如果调用者不是合约创建者则退出。

方法二:我们可以利用函数修饰器,在每个函数执行前检查是否符合执行条件。函数修饰器的参考资料如下:

http://solidity-cn.readthedocs.io/zh/develop/contracts.html#modifiers

我们使用函数修饰器,首先定义一个contract owned,智能合约的部署会调用合约的构造函数constructor,构造函数的调用者就是合约的拥有者,函数使用中使用了require,如果不符合条件该函数会抛出异常,还原合约状态,owned代码如下所示:

contract owned {

    constructor() public { owner = msg.sender; }

    address owner;

    modifier onlyOwner {

        require(msg.sender == owner);

        _;

    }

}

其次,Sample继承owned,如下所示:

contract Sample is owned{...}

最后,在要限制的函数定义上面加上修饰器,如下所示:

function getAmount(address addr) public view onlyOwner

    returns (int64, int64) {

        return (users[addr].count,users[msg.sender].amount);

}

修改完成之后我们重新部署合约进行测试,

(1)我们选择0xca3...账户进行部署,然后对本账号执行add函数,如下图:

(2)利用debug查看当前合约状态,如下图:

(3)切换账户0x147...,如下图:

(3)调用getAmount函数获取0xca3...的金额。如下图:

可以通过console区看到,执行getAmount的时候抛出了异常。这是因为当前的账户非owner,所以require抛出了异常,关于异常的相关文档请参考:

http://solidity-cn.readthedocs.io/zh/develop/control-structures.html#assert-require-revert-and-exceptions

(4)使用0x147...账户,调用add函数,添加金额123。如图:

(5)我们切换到owner账户0xca3...,然后查看0x147...的金额。如图:

以上步骤验证了函数修饰器的作用,我们利用这个简单的账户存款合约,给大家演示了合约语法、编写、编译、部署和运行过程,本章就先简单介绍这里,如果有什么疑问请留言。

下一章讲解智能合约常见问题和调试,敬请关注!

喜欢区块链之录,请长按二维码加入我们

一起奋斗成长

相关文章

网友评论

    本文标题:转载—DAPP实战:从0到1全过程(2)

    本文链接:https://www.haomeiwen.com/subject/idwwvftx.html