跳到主要内容

编写智能合约

智能合约

流程:

如何写智能合约->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目录

image-20230309232504715

接下来我们需要在migrations里编写部署脚本,名称需要以数字开头,如1_deploy.js

const Contacts = artifacts.require("SimpleStorage.sol")
module.exports = function(deployer){
deployer.deploy(Contacts)
}

使用以下命令可以同时编译并部署,可以省略上面的编译过程

truffle migrate

image-20230309232925399

看到以上信息就表示部署好了,同时花费了一些gas

控制台测试

使用以下命令进入truffle控制台

truffle console

obj智能合约对象,obj.address链上的地址

image-20230309233909177

obj.set('dolphin',18)执行函数set函数,往链上存数据花费gas值

image-20230309233830852

obj.getData()查询

image-20230309234435517

脚本测试

  • 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

image-20230310100012919

使用数组类型写合约

定义智能合约

ListsStorage.sol
// 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;
}
}

定义部署脚本,可以分开写部署脚本,也可以写在一起

2_deploy.js
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()
}