Solidity

SunSeekerX ... 2021-12-23 大约 9 分钟

# Solidity

# 📌 学习

# 加密僵尸学习文档

https://cryptozombies.io/zh/course (opens new window)

# 📌 openzeppelin

标准合约库。使用 npm 进行分发。

官网:https://openzeppelin.com/ (opens new window)

合约库官网:https://openzeppelin.com/contracts/ (opens new window)

合约库文档:https://docs.openzeppelin.com/contracts/4.x/ (opens new window)

Github:https://github.com/OpenZeppelin (opens new window)

# @openzeppelin/contracts

知识相关

安装

需要和 truffle 配合使用

npm install @openzeppelin/contracts
1

使用

直接继承库合约就行

// contracts/MyNFT.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";

contract MyNFT is ERC721 {
    constructor() ERC721("MyNFT", "MNFT") {
    }
}
1
2
3
4
5
6
7
8
9
10

# 📌 Metamask

小狐狸钱包。

文档地址:https://docs.metamask.io/guide/ (opens new window)

# 📌 NFT

# hashlips_art_engine

HashLips 艺术引擎是一个用于根据提供的图层创建多个不同实例的艺术作品的工具。

Github:https://github.com/HashLips/hashlips_art_engine (opens new window)

# Pinata

管理上传到 ipfs 的文件。

官网:https://www.pinata.cloud/ (opens new window)

# 📌 Truffle

开发,测试,部署框架。

可以让你使用外部包,迁移,和测试。

官网:https://trufflesuite.com/ (opens new window)

中文文档:https://learnblockchain.cn/docs/truffle/index.html (opens new window)

# 1. 安装

npm install -g truffle
# 安装到项目
npm install --save-dev truffle
1
2
3

# 2. 新建合约

contracts/Box.sol

// contracts/Box.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

contract Box {
  uint256 private _value;

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

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

  // Reads the last stored value
  function retrieve() public view returns (uint256) {
    return _value;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 3. 编译合约

新建配置文件

truffle-config.js

module.exports = {
  compilers: {
    solc: {
      version: '^0.8.0',
    },
  },
}
1
2
3
4
5
6
7

编译合约

npx truffle compile
1

# 4. 添加更多合约

contracts/Auth.sol

// contracts/access-control/Auth.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

contract Auth {
  address private _administrator;

  constructor(address deployer) {
    // Make the deployer of the contract the administrator
    _administrator = deployer;
  }

  function isAdministrator(address user) public view returns (bool) {
    return user == _administrator;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

导入

contracts/Box.sol

// contracts/Box.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

// Import Auth from the access-control subdirectory
import './access-control/Auth.sol';

contract Box {
  uint256 private _value;
  Auth private _auth;

  event ValueChanged(uint256 value);

  constructor() {
    _auth = new Auth(msg.sender);
  }

  function store(uint256 value) public {
    // Require that the caller is registered as an administrator in Auth
    require(_auth.isAdministrator(msg.sender), 'Unauthorized');

    _value = value;
    emit ValueChanged(value);
  }

  function retrieve() public view returns (uint256) {
    return _value;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 5. 使用 OpenZeppelin 合约

导入 OpenZeppelin 合约

npm install --save-dev @openzeppelin/contracts
1

Box.sol

// contracts/Box.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

// Import Ownable from the OpenZeppelin Contracts library
import '@openzeppelin/contracts/access/Ownable.sol';

// Make Box inherit from the Ownable contract
contract Box is Ownable {
  uint256 private _value;

  event ValueChanged(uint256 value);

  // The onlyOwner modifier restricts who can call the store function
  function store(uint256 value) public onlyOwner {
    _value = value;
    emit ValueChanged(value);
  }

  function retrieve() public view returns (uint256) {
    return _value;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 6. 部署测试准备

启动本地区块链

最受欢迎的本地区块链是Ganache (opens new window)。要将其安装到您的项目中,请运行:

npm install --save-dev ganache-cli
1

启动时,Ganache 将随机创建一组未锁定的帐户并为它们提供以太币。为了获得将在本指南中使用的相同地址,您可以在确定性模式下启动 Ganache:

npx ganache-cli --deterministic
1

新建部署脚本

Truffle 使用迁移 (opens new window)来部署合约。迁移由 JavaScript 文件和一个特殊的迁移合约组成,用于跟踪链上的迁移。

我们将创建一个 JavaScript 迁移来部署我们的 Box 合约。我们将此文件另存为migrations/2_deploy.js.

migrations/2_deploy.js

// migrations/2_deploy.js
const Box = artifacts.require('Box')

module.exports = async function (deployer) {
  await deployer.deploy(Box)
}
1
2
3
4
5
6

在我们部署之前,我们需要配置到 ganache 的连接。我们需要为 localhost 和端口 8545 添加一个开发网络,这是我们本地区块链正在使用的。

truffle-config.js

module.exports = {
  networks: {
    development: {
      host: '127.0.0.1', // Localhost (default: none)
      port: 8545, // Standard Ethereum port (default: none)
      network_id: '*', // Any network (default: none)
    },
  },
  compilers: {
    solc: {
      version: '0.8.4',
    },
  },
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 7. 部署

注意需要在另外的命令行面板启动一个本地界面才能进行部署测试!

npx truffle migrate --network development
1

# 8. 测试交互 - 控制台

npx truffle console --network development
1

执行以上命令行就进入了 nodejs 的命令交互面板

获取需要操作的合约对象

const box = await Box.deployed()
1

发送交易

await box.store(42)
1

查询状态

Box的另一个函数被调用retrieve,它返回存储在合约中的整数值。这是区块链状态的查询,所以我们不需要发送交易:

await box.retrieve()
1

因为查询只读取状态而不发送事务,所以没有要报告的事务哈希。这也意味着使用查询不需要任何以太币,并且可以在任何网络上免费使用。

我们的Box合约返回uint256的数字对于 JavaScript 来说太大了,所以我们返回的是一个大数字对象。我们可以使用 将大数显示为字符串(await box.retrieve()).toString()

;(await box.retrieve()).toString()
1

# 9. 测试交互 - 编程

新建一个 scripts/index.js 文件,里面写上需要测试的代码

我们的代码都写入到 main 函数内

scripts/index.js

// scripts/index.js
module.exports = async function main(callback) {
  try {
    // Our code will go here
    // Retrieve accounts from the local node
    const accounts = await web3.eth.getAccounts()
    console.log(accounts)
    callback(0)
  } catch (error) {
    console.error(error)
    callback(1)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

运行测试

npx truffle exec --network development ./scripts/index.js
1

获取合约实例

为了与Box (opens new window)我们部署的合约进行交互,我们将使用 Truffle 合约抽象,这是一个 JavaScript 对象,代表我们在区块链上的合约。

// Set up a Truffle contract, representing our deployed Box instance
const Box = artifacts.require('Box')
const box = await Box.deployed()

// 获取盒子里的值
const value1 = await box.retrieve()
console.log('Box value is', value1.toString())

// 存入一个新的值
await box.store(23)

// 获取存入的值
const value2 = await box.retrieve()
console.log('Box value is', value2.toString())
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 10. 编写单元测试

# ethpm 包管理

ERC190 规范 (opens new window) 下的包管理,但是浏览包的时候出现问题,查看 git 项目,一般的项目都是用的 npm 来分发 sol 库。

官网:https://www.ethpm.com/ (opens new window)

# Ganache

个人便携式的区块链客户端。可以用来开发测试合约。支持 windows,linux 和 mac。

官网文档:https://trufflesuite.com/docs/ganache/index.html (opens new window)

# 📌 hardhat

另外的开发框架。

官网:https://hardhat.org/ (opens new window)

# 1. 安装

npm install --save-dev hardhat
1

# 2. 新建合约

contracts/Box.sol

// contracts/Box.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

contract Box {
  uint256 private _value;

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

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

  // Reads the last stored value
  function retrieve() public view returns (uint256) {
    return _value;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 3. 编译合约

生成配置文件

npx hardhat
1

选择 Create an empty hardhat.config.js

可以在 hardhat.config 配置编译器版本

/**
 * @type import('hardhat/config').HardhatUserConfig
 */
module.exports = {
  solidity: '0.8.4',
}
1
2
3
4
5
6

编译合约代码

以太坊虚拟机 (EVM) 不能直接执行 Solidity 代码:我们首先需要将其编译为 EVM 字节码。

npx hardhat compile
1

# 4. 添加更多合约

contracts/Auth.sol

// contracts/access-control/Auth.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

contract Auth {
  address private _administrator;

  constructor(address deployer) {
    // Make the deployer of the contract the administrator
    _administrator = deployer;
  }

  function isAdministrator(address user) public view returns (bool) {
    return user == _administrator;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

导入

contracts/Box.sol

// contracts/Box.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

// Import Auth from the access-control subdirectory
import './access-control/Auth.sol';

contract Box {
  uint256 private _value;
  Auth private _auth;

  event ValueChanged(uint256 value);

  constructor() {
    _auth = new Auth(msg.sender);
  }

  function store(uint256 value) public {
    // Require that the caller is registered as an administrator in Auth
    require(_auth.isAdministrator(msg.sender), 'Unauthorized');

    _value = value;
    emit ValueChanged(value);
  }

  function retrieve() public view returns (uint256) {
    return _value;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 5. 使用 OpenZeppelin 合约

导入 OpenZeppelin 合约

npm install --save-dev @openzeppelin/contracts
1

Box.sol

// contracts/Box.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

// Import Ownable from the OpenZeppelin Contracts library
import '@openzeppelin/contracts/access/Ownable.sol';

// Make Box inherit from the Ownable contract
contract Box is Ownable {
  uint256 private _value;

  event ValueChanged(uint256 value);

  // The onlyOwner modifier restricts who can call the store function
  function store(uint256 value) public onlyOwner {
    _value = value;
    emit ValueChanged(value);
  }

  function retrieve() public view returns (uint256) {
    return _value;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 6. 部署测试准备

hardhat 自带了一个本地测试网络,每次启动都会创建一个新的本地区块节点。

npx hardhat node
1

安装需要用到的依赖

npm install --save-dev @nomiclabs/hardhat-ethers ethers
1

添加插件到 hardhat.config.js

// hardhat.config.js
require('@nomiclabs/hardhat-ethers');
...
module.exports = {
...
};
1
2
3
4
5
6

新建部署脚本

scripts\deploy.js

// scripts/deploy.js
async function main() {
  // We get the contract to deploy
  const Box = await ethers.getContractFactory('Box')
  console.log('Deploying Box...')
  const box = await Box.deploy()
  await box.deployed()
  console.log('Box deployed to:', box.address)
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error)
    process.exit(1)
  })
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 7. 部署

注意需要在另外的命令行面板启动一个本地界面才能进行部署测试!

npx hardhat run --network localhost scripts/deploy.js
1

# 8. 测试交互 - 控制台

npx hardhat console --network localhost
1

执行以上命令行就进入了 nodejs 的命令交互面板

获取需要操作的合约对象

const Box = await ethers.getContractFactory('Box')
const box = await Box.attach('0x5FbDB2315678afecb367f032d93F642f64180aa3')
1
2

发送交易

Box的第一个函数,store接收一个整数值并将其存储在合约存储中。因为这个函数修改了区块链状态,所以我们需要向合约发送一个交易来执行它。

我们将发送一个交易来调用store带有数值的函数:

await box.store(42)
/*
{
  hash: '0x3d86c5c2c8a9f31bedb5859efa22d2d39a5ea049255628727207bc2856cce0d3',
...
*/
1
2
3
4
5
6

查询状态

Box的另一个函数被调用retrieve,它返回存储在合约中的整数值。这是区块链状态的查询,所以我们不需要发送交易:

await box.retrieve()
// BigNumber { _hex: '0x2a', _isBigNumber: true }
1
2

因为查询只读取状态而不发送事务,所以没有要报告的事务哈希。这也意味着使用查询不需要任何以太币,并且可以在任何网络上免费使用。

我们的Box合约返回uint256的数字对于 JavaScript 来说太大了,所以我们返回的是一个大数字对象。我们可以使用 将大数显示为字符串(await box.retrieve()).toString()

;(await box.retrieve()).toString()
// '42'
1
2

# 9. 测试交互 - 编程

新建一个 scripts/index.js 文件,里面写上需要测试的代码

我们的代码都写入到 main 函数内

scripts/index.js

// scripts/index.js
async function main() {
  // 我们的代码写到这里
  // 获取本地节点启动的账户
  const accounts = await ethers.provider.listAccounts()
  console.log(accounts)
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error)
    process.exit(1)
  })
1
2
3
4
5
6
7
8
9
10
11
12
13
14

运行测试

使用 hardhat 运行脚本会注入一些全局变量,例如 ethers

npx hardhat run --network localhost ./scripts/index.js
1

获取合约实例

为了与Box (opens new window)我们部署的合约进行交互,我们将使用一个ethers 合约实例 (opens new window)

ethers 合约实例是一个 JavaScript 对象,它代表我们在区块链上的合约,我们可以使用它来与我们的合约进行交互。要将其附加到我们部署的合约中,我们需要提供合约地址。

// 这里替换为控制台部署输出的合约地址
const address = '0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0'
const Box = await ethers.getContractFactory('Box')
const box = await Box.attach(address)

// 获取盒子里的值
const value1 = await box.retrieve()
console.log('Box value is', value1.toString())

// 存入一个新的值
await box.store(23)

// 获取存入的值
const value2 = await box.retrieve()
console.log('Box value is', value2.toString())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 10. 编写单元测试

# 📌 EIP

# erc20

标准 erc20 接口

interface ERC20 {
  function totalSupply() external view returns (uint256);
  function balanceOf(address who) external view returns (uint256);
  function transfer(address to, uint256 value) external returns (bool);
  function allowance(address owner, address spender) external view returns (uint256);
  function transferFrom(address from, address to, uint256 value) external returns (bool);
  function approve(address spender, uint256 value) external returns (bool);

  event Approval(address indexed owner, address indexed spender, uint256 value);
  event Transfer(address indexed from, address indexed to, uint256 value);
}
1
2
3
4
5
6
7
8
9
10
11
上次编辑于: 2022年1月14日 10:11
贡献者: SunSeekerX