使用 openzeppelin 开发第一个可升级智能合约-基于Openzeppelin/cli(已经停止维护推荐用truffle)

 

什么是 OpenZeppelin

OpenZeppelin 是一套命令行工具,可在以太坊以及所有其他由 EVM 和 eWASM 支持的区块链上开发,部署和运营智能合约项目。包含一些已经写好的经过安全验证的智能合约, 以及提供了编写可升级智能合约的方案。

简单点就是类似脚手架。

npm install --global @openzeppelin/cli

 

官方网站: https://openzeppelin.com/

官方 Github: https://github.com/OpenZeppelin/openzeppelin-sdk

官方文档: https://docs.openzeppelin.com/

初始化

新建一个空目录

mkdir start
cd start

 

初始化

# 下面这一步自己去一个包名, 其他默认直接回车
npm init
oz init

 

返回:

? Welcome to the OpenZeppelin SDK! Choose a name for your project first-smart-contract
? Initial project version 1.0.0
Project initialized. Write a new contract in the contracts folder and run 'openzeppelin deploy' to deploy it.

 

openzeppelin 命令等同于 oz

openzeppelin init
# 等同于
oz init
# 如果提示找不到命令使用
npx oz init

 

start 目录下出现下列文件

image-20200415173705130

contracts 目录是存放合约的文件夹, 此时还没有文件

编写第一个合约

contracts 目录下创建文件: Box.sol 文件内容如下

// contracts/Box.sol
pragma solidity ^0.5.0;

contract Box {
    uint256 private value;

    // Emitted when the stored value changes
    event ValueChanged(uint256 newValue);

    // Stores a new value in the contract
    function store(uint256 newValue) public {
        value = newValue;
        emit ValueChanged(newValue);
    }

    // Reads the last stored value
    function retrieve() public view returns (uint256) {
        return value;
    }
}

 

编译

oz compile

 

如果不报错会返回:

 oz compile
✓ Compiled contracts with solc 0.5.17 (commit.d19bba13)

 

start 目录下会多出一个 build 目录, 是编译好的文件, 其中 Box.json就是合约编译后的信息, 包含源代码, 源代码信息,ABI, 字节码 bytecode, 以及编译器使用的版本

│  networks.js      
│  package.json     
│                   
├─.openzeppelin     
│      project.json 
│                   
├─build             
│  └─contracts      
│          Box.json 
│                   
└─contracts         
        .gitkeep    
        Box.sol     

 

您还可以通过将参数传递给 compile 命令来配置编译,包括选择编译器版本和启用优化:

$ npx oz compile --solc-version=0.5.12 --optimizer on

 

有关这些选项的详细信息,请参阅《使用 CLI 编译》。

完善

# 安装几个 npm 包
# 包含 openzeppelin 提前写好的合同
npm install --save-dev @openzeppelin/contracts
# Ganache 能快速运行一个本地测试区块链 
npm install --save-dev ganache-cli

 

启动 Ganache,Ganache 将创建随机的一组解锁帐户,并将其分配给以太币。为了获得与本指南中将使用的地址相同的地址,可以在确定性模式下启动 Ganache:

# --deterministic 参数是使用当前目录下的 networks.js 为配置启动本地测试区块链
$ npx ganache-cli --deterministic

 

Ganache 将打印出可用帐户及其私钥的列表,以及一些区块链配置值。最重要的是,它将显示其地址,我们将使用它来连接到它。默认情况下,它将为127.0.0.1:8545

请记住,每次运行 Ganache 时,它将创建一个全新的本地区块链 - 不会 保留先前运行的状态。

如果要持久化数据, 可以使用 --db 选项, 指定一个目录存储区块链生成数据

# 家目录下的 data 存储数据
npx ganache-cli --deterministic --db ~/data

 

返回(生成了 10 个地址和私钥, 每个地址里面有 100ETH, 以及 gas 的价格和限制,HTTP 的 RPC 端口是监听在 8545 端口):

Ganache CLI v6.9.1 (ganache-core: 2.10.2)

Available Accounts
==================
(0) 0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1 (100 ETH)
(1) 0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0 (100 ETH)
(2) 0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b (100 ETH)
(3) 0xE11BA2b4D45Eaed5996Cd0823791E0C93114882d (100 ETH)
(4) 0xd03ea8624C8C5987235048901fB614fDcA89b117 (100 ETH)
(5) 0x95cED938F7991cd0dFcb48F0a06a40FA1aF46EBC (100 ETH)
(6) 0x3E5e9111Ae8eB78Fe1CC3bb8915d5D461F3Ef9A9 (100 ETH)
(7) 0x28a8746e75304c0780E011BEd21C72cD78cd535E (100 ETH)
(8) 0xACa94ef8bD5ffEE41947b4585a84BdA5a3d3DA6E (100 ETH)
(9) 0x1dF62f291b2E969fB0849d99D9Ce41e2F137006e (100 ETH)

Private Keys
==================
(0) 0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d
(1) 0x6cbed15c793ce57650b9877cf6fa156fbef513c4e6134f022a85b1ffdd59b2a1
(2) 0x6370fd033278c143179d81c5526140625662b8daa446c22ee2d73db3707e620c
(3) 0x646f1ce2fdad0e6deeeb5c7e8e5543bdde65e86029e2fd9fc169899c440a7913
(4) 0xadd53f9a7e588d003326d1cbf9e4a43c061aadd9bc938c843a79e7b4fd2ad743
(5) 0x395df67f0c2d2d9fe1ad08d1bc8b6627011959b79c53d7dd6a3536a33ab8a4fd
(6) 0xe485d098507f54e7733a205420dfddbe58db035fa577fc294ebd14db90767a52
(7) 0xa453611d9419d0e56f499079478fd72c37b251a94bfde4d19872c44cf65386e3
(8) 0x829e924fdf021ba3dbbc4225edfece9aca04b929d6e75613329ca6f1d31c0bb4
(9) 0xb0057716d5917badaf911b193b12b910811c1497b5bada8d7711f758981c3773

HD Wallet
==================
Mnemonic:      myth like bonus scare over problem client lizard pioneer submit female collect
Base HD Path:  m/44'/60'/0'/0/{account_index}

Gas Price
==================
20000000000

Gas Limit
==================
6721975

Call Gas Limit
==================
9007199254740991

Listening on 127.0.0.1:8545

 

由于本地测试链已经启动, 这个不能关闭, 需要重新开一个终端命令行

使用 openzeppelin CLI 和链交互

要求区块链是在运行汇总,openzeppelin CLI 使用 networks.js 配置和链交互, 实际就是使用的链提供的 RPC 接口

# 查询有哪些地址
oz accounts

 

返回(结果和 ganache 启动时输出一致)

? Pick a network development
Accounts for dev-1586945927338:
Default: 0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1
All:
- 0: 0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1
- 1: 0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0
- 2: 0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b
- 3: 0xE11BA2b4D45Eaed5996Cd0823791E0C93114882d
- 4: 0xd03ea8624C8C5987235048901fB614fDcA89b117
- 5: 0x95cED938F7991cd0dFcb48F0a06a40FA1aF46EBC
- 6: 0x3E5e9111Ae8eB78Fe1CC3bb8915d5D461F3Ef9A9
- 7: 0x28a8746e75304c0780E011BEd21C72cD78cd535E
- 8: 0xACa94ef8bD5ffEE41947b4585a84BdA5a3d3DA6E
- 9: 0x1dF62f291b2E969fB0849d99D9Ce41e2F137006e

 

查询余额

oz balance

 

返回(需要手动输入一个地址):

? Enter an address to query its balance 0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1
? Pick a network development
Balance: 100 ETH
100000000000000000000

 

部署合约

# 建议使用 deploy
oz deploy
# create 已经废弃, 不建议使用
oz create

 

使用 oz create 返回(Call a function to initialize the instance after creating it 时选择否)

The create command is deprecated. Use deploy instead.
Nothing to compile, all contracts are up to date.
? Pick a contract to instantiate Box
? Pick a network development
✓ Added contract Box
✓ Contract Box deployed
All implementations have been deployed
? Call a function to initialize the instance after creating it? No
✓ Setting everything up to create contract instances
✓ Instance created at 0xCfEB869F69431e42cdB54A4F4f105C19C080A601
To upgrade this instance run 'oz upgrade'
0xCfEB869F69431e42cdB54A4F4f105C19C080A601

 

使用 oz deploy 返回:

这个会让你选择哪一种部署方式

? Choose the kind of deployment (Use arrow keys)
❯ regular standard non-upgradeable contract (常规不能升级的合约)
upgradeable upgradeable instance using a delegating proxy (EIP1967) (使用委托代理的可升级合约, 遵循 EIP1967)
minimal non-upgradeable minimal proxy instance (EIP1167) (不能升级的最小代理. 遵循 EIP1167)

我这里选择upgradeable 方便后面测试合约升级

Nothing to compile, all contracts are up to date.
? Choose the kind of deployment upgradeable
? Pick a network development
? Pick a contract to deploy Box
All implementations are up to date
? Call a function to initialize the instance after creating it? No
✓ Instance created at 0x254dffcd3277C0b1660F6d42EFbB754edaBAbC2B
To upgrade this instance run 'oz upgrade'
0x254dffcd3277C0b1660F6d42EFbB754edaBAbC2B

 

后面我们将使用这个合约地址:0x254dffcd3277C0b1660F6d42EFbB754edaBAbC2B

使用 openzeppelin CLI 和合约交互

合约中两个函数,store设置值,retrieve 查询值

调用函数修改值

# 使用 oz send-tx 去发送交易
oz send-tx

 

返回(调用合约中的 store 函数, 传递一个新的值,100 进去)

? Pick a network development
? Pick an instance Box at 0x254dffcd3277C0b1660F6d42EFbB754edaBAbC2B
? Select which function store(newValue: uint256)
? newValue: uint256: 100
✓ Transaction successful. Transaction hash: 0x11076136bab3a71551099b76626a9d561e72badba6e2b0d2f1c83d97f0c6d254
Events emitted:
 - ValueChanged(100)

 

查询值

retrieve 函数使用 view 修饰, 表示是只读函数, 只查询区块链不会去修改, 不消耗 gas, 此类函数使用 call 方式调用

oz call

 

返回:

? Pick a network development
? Pick an instance Box at 0x254dffcd3277C0b1660F6d42EFbB754edaBAbC2B
? Select which function retrieve()
✓ Method 'retrieve()' returned: 100
100

 

使用编程的方式和合约交互

安装 web3.js 和 OpenZeppelin Contract Loader

npm install web3 @openzeppelin/contract-loader

 

在项目目录新建目录 src 并创建文件index.js, 编写测试代码

// src/index.js
const Web3 = require('web3');
const { setupLoader } = require('@openzeppelin/contract-loader');

async function main() {
    // 连接 RPC 接口
    const web3 = new Web3('http://localhost:8545');

    // 测试是否能连通, 查询账户列表
    const accounts = await web3.eth.getAccounts();
    console.log(accounts);
}

main();

 

运行node src/index.js, 如果能打印出账户列表, 表示连通没问题

[
  '0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1',
  '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0',
  '0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b',
  '0xE11BA2b4D45Eaed5996Cd0823791E0C93114882d',
  '0xd03ea8624C8C5987235048901fB614fDcA89b117',
  '0x95cED938F7991cd0dFcb48F0a06a40FA1aF46EBC',
  '0x3E5e9111Ae8eB78Fe1CC3bb8915d5D461F3Ef9A9',
  '0x28a8746e75304c0780E011BEd21C72cD78cd535E',
  '0xACa94ef8bD5ffEE41947b4585a84BdA5a3d3DA6E',
  '0x1dF62f291b2E969fB0849d99D9Ce41e2F137006e'
]

 

重新编写代码:

// src/index.js
const Web3 = require('web3');
const { setupLoader } = require('@openzeppelin/contract-loader');

async function main() {
    // 连接 RPC
    const web3 = new Web3('http://localhost:8545');
    const loader = setupLoader({ provider: web3 }).web3;

    // 合约地址是之前使用 oz deploy 部署的那一个
    const address = '0x254dffcd3277C0b1660F6d42EFbB754edaBAbC2B';
    const box = loader.fromArtifact('Box', address);

    // 调用 retrieve 函数, 使用 call 的方式
    const value = await box.methods.retrieve().call();
    console.log("Box value is", value);
}

main();

 

运行node src/index.js,

$ node src/index.js
Box value is 100

 

发送交易, 调用 store 函数, 将值设置为 20:

// src/index.js
const Web3 = require('web3');
const {setupLoader} = require('@openzeppelin/contract-loader');

async function main() {
    // 连接 RPC
    const web3 = new Web3('http://localhost:8545');
    const loader = setupLoader({provider: web3}).web3;

    // 合约地址是之前使用 oz deploy 部署的那一个
    const address = '0x254dffcd3277C0b1660F6d42EFbB754edaBAbC2B';
    const box = loader.fromArtifact('Box', address);

    // 调用 retrieve 函数, 使用 call 的方式
    let value = await box.methods.retrieve().call();
    console.log("Box value Before is", value);

    // 获取账户列表
    const accounts = await web3.eth.getAccounts();

    // 使用第一个账户 accounts[0]来发送交易, 调用 store 函数, 将值设为 20, 指定 gas 为 50000,gasPrice 为 1e6
    await box.methods.store(20)
        .send({from: accounts[0], gas: 50000, gasPrice: 1e6});

    // 再次调用 retrieve 函数, 查看值是否变化
    value = await box.methods.retrieve().call();
    console.log("Box value After is", value);
}

main();

 

运行node src/index.js,

$ node src/index.js
Box value Before is 100
Box value After is 20

 

编写自动化测试

先忽略

使用公共测试网路测试

先忽略

升级智能合约

此时合约 0x254dffcd3277C0b1660F6d42EFbB754edaBAbC2B 中的 value 值为 20

升级合约需要关注的点:

1. 与合约交互的地址要发生变化吗? 如果变化, 那么要通知所有调用该合约的人, 更新新的地址

2. 老合约的数据怎么办?

假设现在为合约新添加了一个新函数 increment, 调用一次就使value 值 +1, 得到新合约代码

// contracts/Box.sol
pragma solidity ^0.5.0;

// 引入 OpenZeppelin 已经写好的权限合约
import "@openzeppelin/contracts/ownership/Ownable.sol";

contract Box is Ownable {
    uint256 private value;

    event ValueChanged(uint256 newValue);


    function store(uint256 newValue) public onlyOwner {
        value = newValue;
        emit ValueChanged(newValue);
    }

    function retrieve() public view returns (uint256) {return value;}

    // 新增一个函数, 每次使 value 值 +1
    function increment() public {
        value = value + 1;
        emit ValueChanged(value);
    }
}

 

使用 oz upgrade 去升级函数

oz upgrade

 

返回:

? Pick a network development
? Which instances would you like to upgrade? Choose by address
? Pick an instance to upgrade Box at 0x254dffcd3277C0b1660F6d42EFbB754edaBAbC2B
? Call a function on the instance after upgrading it? No
✓ Compiled contracts with solc 0.5.17 (commit.d19bba13)
- Contract Box or an ancestor has a constructor. Change it to an initializer function. See https://docs.openzeppelin.com/upgrades/2.6//writing-upgradeable#initializers.
- New variable 'address _owner' was inserted in contract Ownable in @openzeppelin/contracts/ownership/Ownable.sol:1. You should only add new variables at the end of your contract.
See https://docs.openzeppelin.com/upgrades/2.6//writing-upgradeable#modifying-your-contracts for more info.
- Contract Box imports ownership/Ownable.sol, GSN/Context.sol from @openzeppelin/contracts. Use @openzeppelin/contracts-ethereum-package instead. See https://docs.openzeppelin.com/cli/2.6/dependencies#linking-the-contracts-ethereum-package.
One or more contracts have validation errors. Please review the items listed above and fix them, or run this command again with the --force option.

 

报了 3 个错:

# 1. 合约中有构造函数, 应当修改为 initializer 函数
- Contract Box or an ancestor has a constructor. Change it to an initializer function. See https://docs.openzeppelin.com/upgrades/2.6//writing-upgradeable#initializers.
# 2. 应该是附带问题
- New variable 'address _owner' was inserted in contract Ownable in @openzeppelin/contracts/ownership/Ownable.sol:1. You should only add new variables at the end of your contract.
See https://docs.openzeppelin.com/upgrades/2.6//writing-upgradeable#modifying-your-contracts for more info.
# 3. 导入的合约来自 @openzeppelin/contracts, 使用 @openzeppelin/contracts-ethereum-package 替换
- Contract Box imports ownership/Ownable.sol, GSN/Context.sol from @openzeppelin/contracts. Use @openzeppelin/contracts-ethereum-package instead. See https://docs.openzeppelin.com/cli/2.6/dependencies#linking-the-contracts-ethereum-package.

先解决第 3 个:

# 移除旧包, 做测试的话这个包先不要移除
npm remove @openzeppelin/contracts
# 安装新包
# @openzeppelin/upgrades 也要装, 不然会提示你 @openzeppelin/contracts-ethereum-package 依赖这个包
npm install --save-dev @openzeppelin/upgrades
npm install --save-dev @openzeppelin/contracts-ethereum-package

 

第 1 个问题:

https://docs.openzeppelin.com/upgrades/2.8/writing-upgradeable

您可以在 OpenZeppelin 升级中使用您的 Solidity 合同,无需对其进行任何修改(构造函数 除外)。由于基于代理的可升级性系统的要求,因此在可升级合同中不能使用构造函数。要了解此限制的原因,请访问 代理

这意味着,在将合同与 OpenZeppelin 升级配合使用时,您需要将其构造函数更改为常规函数(通常名为)initialize,在其中运行所有设置逻辑:

由于这种模式在编写可升级合同时非常普遍,因此 OpenZeppelin 升级提供了一个 Initializable 基本合同,该合同具有一个 initializer 修饰符来处理此问题:

使用 initializer 去替代构造函数, 并引入 OpenZeppelin 提供的 Initializable 来限制它只能运行一次(像构造函数一样工作)

重新编写代码:

// contracts/Box.sol
pragma solidity ^0.5.0;

import '@openzeppelin/upgrades/contracts/Initializable.sol';
// 引入合约拥有者权限
import "@openzeppelin/contracts-ethereum-package/contracts/ownership/Ownable.sol";

contract Box is Initializable, Ownable {

    uint256 private value;

    event ValueChanged(uint256 newValue);

    // 使用初始化函数代替构造函数
    function initialize() public initializer {
        // 要初始化 Ownable 合约
        Ownable.initialize(msg.sender);
    }

    function store(uint256 newValue) public onlyOwner {
        value = newValue;
        emit ValueChanged(newValue);
    }

    function retrieve() public view returns (uint256) {return value;}
}

 

部署:

Call a function to initialize the instance after creating it? 选择的是 Y, 调用 initialize 函数, 相当于执行了构造函数

$ oz deploy
✓ Compiled contracts with solc 0.5.17 (commit.d19bba13)
? Choose the kind of deployment upgradeable
? Pick a network development
? Pick a contract to deploy Box
✓ Contract Box deployed
All implementations have been deployed
? Call a function to initialize the instance after creating it? Yes
? Select which function * initialize()
✓ Setting everything up to create contract instances
✓ Instance created at 0x0290FB167208Af455bB137780163b7B7a9a10C16
To upgrade this instance run 'oz upgrade'
0x0290FB167208Af455bB137780163b7B7a9a10C16

 

再次使用 node src/index.js 去调用, 不过要修改一下地址

// src/index.js
const Web3 = require('web3');
const {setupLoader} = require('@openzeppelin/contract-loader');

async function main() {
    // 连接 RPC
    const web3 = new Web3('http://localhost:8545');
    const loader = setupLoader({provider: web3}).web3;

    // 改为新的地址
    const address = '0x0290FB167208Af455bB137780163b7B7a9a10C16';
    const box = loader.fromArtifact('Box', address);

    // 调用 retrieve 函数, 使用 call 的方式
    let value = await box.methods.retrieve().call();
    console.log("Box value Before is", value);

    // 获取账户列表
    const accounts = await web3.eth.getAccounts();

    // 使用第一个账户 accounts[0]来发送交易, 调用 store 函数, 将值设为 20, 指定 gas 为 50000,gasPrice 为 1e6
    await box.methods.store(20)
        .send({from: accounts[0], gas: 50000, gasPrice: 1e6});

    // 再次调用 retrieve 函数, 查看值是否变化
    value = await box.methods.retrieve().call();
    console.log("Box value After is", value);
}

main();

 

返回 (合约刚部署,value 没有值, 所以等于 0, 后面等于 20):

$ node src/index.js
Box value Before is 0
Box value After is 20

 

升级合约

此时value=20, 假设升级内容是增加一个函数increment, 没调用一次value+1

得到新的合约代码:

// contracts/Box.sol
pragma solidity ^0.5.0;

import '@openzeppelin/upgrades/contracts/Initializable.sol';
// 引入合约拥有者权限
import "@openzeppelin/contracts-ethereum-package/contracts/ownership/Ownable.sol";

contract Box is Initializable, Ownable {

    uint256 private value;

    event ValueChanged(uint256 newValue);

    // 使用初始化函数代替构造函数
    function initialize() public initializer {
        // 要初始化 Ownable 合约
        Ownable.initialize(msg.sender);
    }

    function store(uint256 newValue) public onlyOwner {
        value = newValue;
        emit ValueChanged(newValue);
    }

    function retrieve() public view returns (uint256) {return value;}

    // 新增一个函数, 每次使 value 值 +1
    function increment() public onlyOwner {
        value = value + 1;
        emit ValueChanged(value);
    }
}

 

使用 oz upgrade 再次升级

当 Call a function to initialize the instance after creating it? 再次选择 Y 时会报错

$ oz upgrade
? Pick a network development
? Which instances would you like to upgrade? Choose by address
? Pick an instance to upgrade Box at 0x0290FB167208Af455bB137780163b7B7a9a10C16
? Call a function on the instance after upgrading it? Yes
? Select which function * initialize()

✓ Compiled contracts with solc 0.5.17 (commit.d19bba13)
✓ Contract Box deployed
All implementations have been deployed
✖ Upgrading instance at 0x0290FB167208Af455bB137780163b7B7a9a10C16 and calling 'initialize' with no arguments
✖ Upgrading instance at 0x0290FB167208Af455bB137780163b7B7a9a10C16
Proxy first-smart-contract/Box at 0x0290FB167208Af455bB137780163b7B7a9a10C16 failed to upgrade with error: Returned error: VM Exception while processing transaction: revert

 

选择 N, 升级成功

$ oz upgrade
? Pick a network development
? Which instances would you like to upgrade? Choose by address
? Pick an instance to upgrade Box at 0x0290FB167208Af455bB137780163b7B7a9a10C16
? Call a function on the instance after upgrading it? No
Nothing to compile, all contracts are up to date.
All implementations are up to date
✓ Instance upgraded at 0x0290FB167208Af455bB137780163b7B7a9a10C16. Transaction receipt: 0x2b84fba55b975b97c511798eb772156aa14e7f5002cbc25d345b895d4d3dd159
✓ Instance at 0x0290FB167208Af455bB137780163b7B7a9a10C16 upgraded

 

看到地址没有变化, 现在要验证:

  1. value是否等于 20?, 如果等于表示合约状态 (数据) 还在
  2. 新增的 increment 是否存在?

修改一下 src/index.js

// src/index.js
const Web3 = require('web3');
const {setupLoader} = require('@openzeppelin/contract-loader');

async function main() {
    // 连接 RPC
    const web3 = new Web3('http://localhost:8545');
    const loader = setupLoader({provider: web3}).web3;

    // 改为新的地址
    const address = '0x0290FB167208Af455bB137780163b7B7a9a10C16';
    const box = loader.fromArtifact('Box', address);

    // 调用 retrieve 函数, 使用 call 的方式
    let value = await box.methods.retrieve().call();
    console.log("Box value Before is", value);

    // 获取账户列表
    const accounts = await web3.eth.getAccounts();

    // 使用第一个账户 accounts[0]来发送交易, 调用 store 函数, 将值设为 30, 指定 gas 为 50000,gasPrice 为 1e6
    await box.methods.store(30)
        .send({from: accounts[0], gas: 50000, gasPrice: 1e6});

    // 再次调用 retrieve 函数, 查看值是否变化
    value = await box.methods.retrieve().call();
    console.log("Box value After is", value);

    // 调用 increment 函数
    await box.methods.increment()
        .send({from: accounts[0], gas: 50000, gasPrice: 1e6});

    // 再次查看 value 值, 此时 value 应该等于 31
    value = await box.methods.retrieve().call();
    console.log("Box value Increment is", value);

}

main();

 

返回(结果符合预期):

$ node src/index.js
Box value Before is 20
Box value After is 30
Box value Increment is 31

 

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Truffle是以太坊智能合约开发的常用框架之一。它提供了一系列工具和库,可以帮助我们更加高效地开发、测试和部署智能合约。下面,我将简单介绍一下Truffle的基本用法。 Truffle的安装 首先,我们需要在本地安装Truffle。在命令行窗口中输入以下命令: ``` npm install -g truffle ``` 如果你在国内使用npm速度很慢,可以考虑使用cnpm(淘宝镜像): ``` npm install -g cnpm --registry=https://registry.npm.taobao.org cnpm install -g truffle ``` Truffle使用 Truffle提供了一系列命令行工具,可以帮助我们进行合约的编译、测试和部署等操作。下面,我列举几个常用的命令。 1. 初始化项目 ``` truffle init ``` 这个命令会在当前目录下创建一个新的Truffle项目。Truffle项目结构如下: ``` contracts/ // 合约文件目录 migrations/ // 部署脚本目录 test/ // 测试文件目录 truffle-config.js // Truffle配置文件 ``` 2. 编译合约 ``` truffle compile ``` 这个命令会编译contracts目录下的所有合约文件,并将编译结果保存在build/contracts目录下。 3. 部署合约 ``` truffle migrate ``` 这个命令会执行migrations目录下的所有部署脚本。部署脚本是以数字命名的JavaScript文件,Truffle会按照数字顺序执行所有脚本。每个脚本都需要定义一个合约对象,并使用module.exports导出。 4. 运行测试 ``` truffle test ``` 这个命令会运行test目录下的所有测试文件,并输出测试结果。 除了以上命令,Truffle还提供了很多其他的功能,比如调试工具、交互式控制台等。在实际开发中,你可以根据需要选择使用。 总结 Truffle是一个强大的以太坊智能合约开发框架,可以帮助我们更加高效地开发、测试和部署智能合约。在使用Truffle时,我们需要熟悉它的基本用法,并根据实际需求选择使用不同的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值