编写智能合约
智能合约
流程:
如何写智能合约->solidity语言->部署到区块链->web3js连接到区块链的智能合约(不仅是获得区块链基本的余额等信息,而是连接到他上面的智能合约程序来做一些交互操作)->最终实现简易的DApp开发
1.Remix
Remix IDE是开发以太坊智能合约的在线IDE工具,部署简单的智能合约非常方便
Remix地址: https://remix.ethereum.org
2.Truffle
Truffle是一个世界级的智能合约开发框架,专门为智能合约而生
- 管理智能合约的生命周期
- 自动化合约测试
- 可编程,可部署,可发布合约
- 不用过多的关注网络管理
- 强大的交互式控制台
创建项目
使用Truffle创建项目znhy
npm i truffle -g
cd znhy
truffle init
目录结构:
- contracts/:核心的solidity智能合约脚本
.sol
- migrations/:控制合约的部署
.js
- test/:测试文件存放文件(javascript or solidity)
- truffle-config.js:配置文件
本地开发可以把配置文件中的network.development
放开,在部署到区块链时要耗费燃料,本地默认从第一个账户扣除
创建合约
solidity中文文档 https://learnblockchain.cn/docs/solidity/introduction-to-smart-contracts.html
VsCode高亮插件:solidity
文档存储合约示例:
把一个数据保存到链上
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
contract SimpleStorage {
uint storedData;
function set(uint x) public {
storedData = x;
}
function get() public view returns (uint) {
return storedData;
}
}
以上代码解释看文档
注意:将合约创建在contracts目录下,文件名与定义的contract保持一致,如这里用SimpleStorage.sol
合约细节
数据位置
solidity数据存储位置有以下三类:
- storage:合约里的状态变量默认都是storage,存储在链上
- memory:函数里的参数和临时变量一般用memory,存储在内存中,不上链
- calldata:和memory类似,存储在内存中,不上链。与memory的不同点在于calldata变量不能修改(immutable),一般用于函数的参数
总结:不同存储位置的gas成本不同,storage类型的数据存在链上,类似计算机的硬盘,消耗gas多; memory和calldata类型的临时存在内存里,消耗gas少。
作用域
Solidity中变量按作用域划分以下三种
- 状态变量(state variable):状态变量是数据存储在链上的变量,所有合约内函数都可以访问,gas消耗高。状态变量在合约内、函数外声明。可以在函数里更改状态变量的值
- 局部变量(local variable):局部变量是仅在函数执行过程中有效的变量,函数退出后,变量无效。局部变量的数据存储在内存里,不上链,gas低。局部变量在函数内声明
- 全局变量(global variable):全局变量是全局范围工作的变量,都是solidity预留关键字。 他们可以在函数内不声明直接使用(类似于msg.sender ,block.number)
作用域类型
状态变量可以有三种作用域类型:
- Public:公共状态变量可以在内部访问,也可以通过消息访问,对于公共状态变量,将生成一个自动getter函数
- Internal:内部状态变量只能从当前合约或其派生合约内访问
- Private:私有状态变量只能从当前合约内部访问,派生合约内不能访问
函数可以指定为external,public,internal或者private
- external:外部函数是合约接口的一部分,这意味着可以从其他合约或通过事务调用它们,但是内部无法调用
- public:外部调用和内部都可以调用
- internal:只能从当前合约或从当前合约派生的合约中访问,外部无法访问它们,由于它们没有通过合约的ABI向外部公开,所以它们可以接受内部类型的参数,比如映射或存储引用
- private:私有函数类似于内部函数,但它们在派生合约中不可见。
实践
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
contract SimpleStorage {
// 创建两个状态变量,默认storage数据类型,存在链上的
uint8 age;
string name;
function set(string memory _name,uint8 _age) public {
// string memory a;
name = _name;
age = _age;
}
// view(视图函数,只访问不修改)
function getData() public view returns (string memory,uint) {
return (name,age);
}
// pure(纯函数,不访问,不修改)
function test(uint x,uint y) public pure returns (uint){
return x+y;
}
}
编译部署
使用一些命令编译智能合约
truffle compile
生成了一个build目录
接下来我们需要在migrations里编写部署脚本,名称需要以数字开头,如1_deploy.js
const Contacts = artifacts.require("SimpleStorage.sol")
module.exports = function(deployer){
deployer.deploy(Contacts)
}
使用以下命令可以同时编译并部署,可以省略上面的编译过程
truffle migrate
看到以上信息就表示部署好了,同时花费了一些gas
控制台测试
使用以下命令进入truffle控制台
truffle console
obj
智能合约对象,obj.address
链上的地址
obj.set('dolphin',18)
执行函数set函数,往链上存数据花费gas值
obj.getData()
查询
脚本测试
- mocha测试
- truffle脚本
使用truffle编写测试脚本
const Contacts = artifacts.require("SimpleStorage.sol")
module.exports = async function(callback){
const obj = await Contacts.deployed()
await obj.set('dol',100)
const res = await obj.getData()
console.log(res,'==');
callback()
}
执行脚本,执行测试前需要编译部署
truffle exec ./scripts/test.js
使用数组类型写合约
定义智能合约
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
contract ListsStorage {
// 结构体
struct User {
uint id;
string name;
uint8 age;
}
// 动态数组
User[] public UserList; //使用public自动生成getter方法,'[]'内有数字表示限制长度,没有就是动态的
function addList(string memory _name,uint8 _age) public returns (uint) {
uint count = UserList.length;
uint index = count + 1;
UserList.push(User(index,_name,_age));
return UserList.length;
}
function getList() public view returns (User[] memory) {
return UserList;
}
}
定义部署脚本,可以分开写部署脚本,也可以写在一起
const Contacts = artifacts.require("ListsStorage.sol")
module.exports = function(deployer){
deployer.deploy(Contacts)
}
使用编译部署命令会把migrations里所有的都执行一遍,reset参数可以覆盖之前部署的
truffle migrate --reset
定义测试文件
const Contacts = artifacts.require("ListsStorage.sol")
module.exports = async function(callback){
const obj = await Contacts.deployed()
const len = await obj.addList('dol',100)
const res = await obj.getList()
console.log(res,'==',len);
callback()
}