前言
本文主要实现标准代币的空投合约和代币水龙头相关业务场景的实现,包含合约的编写,测试,部署全流程。
概念以及相关场景说明
空头合约:币圈中一种营销策略,项目方将代币免费发放给特定用户群体;
代币水龙头:一种为用户提供小额加密货币的机制;
标准代币
说明:本合约基于openzeppelin库实现,本代币具备转账,授权,铸造,销毁和代币相关查询等功能;
代币合约
// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.0.0
pragma solidity ^0.8.22;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {ERC20Burnable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
contract MyToken is ERC20, ERC20Burnable, Ownable {
constructor(string memory name_,string memory symbol_,address initialOwner)//参数说明1.代币name2.代币symbol3.代币的初始所有者账号或者4 unit tokenValue
ERC20(name_, symbol_)
Ownable(initialOwner)
{
_mint(msg.sender, 100 * 10 ** decimals());//部署合约时自己铸造100个代币,或者也可以通过构造函数传铸造数
}
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
}
# 合约说明
# 参数说明:1.代币name 2.代币symbol 3.代币的初始所有者账号 或者4 unit tokenValue,也可以部署时自己设置代币数
# 自定代币name和symbol_
# 部署时铸造100个代币
代币测试
const {ethers,getNamedAccounts,deployments} = require("hardhat");
const { assert,expect } = require("chai");
describe("Token",function(){
let token;//代币合约
let firstAccount//第一个账户
let secondAccount//第二个账户
//主要作用:测试之前,先部署合约拿到合约实例
beforeEach(async function(){
await deployments.fixture(["token"]);//和部署合约的脚本module.exports.tags对应可以选择all或token
firstAccount=(await getNamedAccounts()).firstAccount;//第一个账号
secondAccount=(await getNamedAccounts()).secondAccount;//第二个账号
const tokenDeployment = await deployments.get("MyToken");//获取合约的实例获取合约的地址
console.log('------',tokenDeployment.address)
token = await ethers.getContractAt("MyToken",tokenDeployment.address);//已经部署的合约交互
});
//用来写测试用例
describe("代币基本信息",function(){
it("读取合约基本信息以及查看账户余额",async function(){
console.log('总账账户余额',await token.totalSupply())
console.log('查看账户余额',await token.balanceOf(firstAccount))
console.log('代币精度',await token.decimals())
console.log('代币名',await token.name())
console.log('代币符号',await token.symbol())
// expect(await token.balanceOf(firstAccount.address)).to.equal(100);
});
it("transfer转账",async function(){
await token.transfer(secondAccount,10);//代币所有者向secondAccount的账号转10个代币
console.log(await token.balanceOf(firstAccount));//查看firstAccount的余额
console.log(await token.balanceOf(secondAccount));//查看secondAccount的余额
});
it("transferFrom转账",async function(){
//transferFrom和transfer的区别,执行次方法时需要先授权
await token.approve(firstAccount,100);//代币所有者向firstAccount授权100个代币
await token.transferFrom(firstAccount,secondAccount,10);//把firstAccount账号授权的代币转给secondAccount账号10
console.log(await token.balanceOf(firstAccount));//查看firstAccount余额
console.log(await token.balanceOf(secondAccount));//查看secondAccount余额
});
it("allowance",async function(){
await token.approve(secondAccount,10);//授权给secondAccount
console.log(await token.allowance(firstAccount,secondAccount));//查看授权额度
});
it("mint",async function(){
await token.mint(firstAccount,100);//铸造100个代币
console.log(await token.balanceOf(firstAccount));//查看额度
});
it("burn",async function(){
await token.burn(100);//销毁100个代币
console.log(await token.balanceOf(firstAccount));//查看额度
});
});
});
#说明
# 测试代币的基本信息读取、转账、铸造、销毁、授权相关用例
# npx hardhat test ./test/xxx.js
代币部署
module.exports = async ({getNamedAccounts,deployments})=>{
const getNamedAccount = (await getNamedAccounts()).firstAccount;//获取第一个账号
const TokenName = "BoykayuriToken";//代币name
const TokenSymbol = "BTK";//代币Symbol
const {deploy,log} = deployments;
const TokenC=await deploy("MyToken",{
from:getNamedAccount,
args: [TokenName,TokenSymbol,getNamedAccount],//参数说明1.代币name2.代币symbol3.代币的初始所有者账号和合约的constructor的参数一一对应
log: true,
})
console.log('合约地址',TokenC.address)
}
module.exports.tags = ["all", "token"];
# 部署一个 name是BoykayuriToken Symbol是BTK,代币100的测试代币
# npx hardhat deploy
代币水龙头
代币水龙头合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract Faucet {
uint256 public amountAllowed = 100; // 每次领 100 单位代币
address public tokenContract; // token合约地址
mapping(address => bool) public requestedAddress; // 记录领取过代币的地址
// SendToken事件
event SendToken(address indexed Receiver, uint256 indexed Amount);
constructor(address _tokenContract) {
tokenContract = _tokenContract; // set token contract
}
// 用户领取代币函数
function requestTokens() external {
require(!requestedAddress[msg.sender], "Can't Request Multiple Times!"); // 每个地址只能领一次
IERC20 token = IERC20(tokenContract); // 创建IERC20合约对象
require(token.balanceOf(address(this)) >= amountAllowed, "Faucet Empty!"); // 水龙头空了
token.transfer(msg.sender, amountAllowed); // 发送token
requestedAddress[msg.sender] = true; // 记录领取地址
emit SendToken(msg.sender, amountAllowed); // 释放SendToken事件
}
}
代币水龙头合约测试
const {ethers,getNamedAccounts,deployments} = require("hardhat");
const { assert,expect } = require("chai");
describe("Faucet",function(){
let token;//合约地址
let faucet;//合约地址
let owner;
let addr1;
let addr2;
beforeEach(async function(){
await deployments.fixture(["all"]);
[owner, addr1,addr2] = await ethers.getSigners();
console.log(owner.address, addr1.address,addr2.address)//获取账户1,账户2,账户3,
const tokenDeployment = await deployments.get("MyToken");
token = await ethers.getContractAt("MyToken",tokenDeployment.address);//已经部署的合约交互
const faucetDeployment = await deployments.get("Faucet");
faucet = await ethers.getContractAt("Faucet",faucetDeployment.address);//已经部署的合约交互
})
describe("水龙头测试合约",function(){
it("一天之内智能领取1次",async function(){
await token.transfer(faucet.target,1000);//给faucet合约转账1000个代币 1.代币额度足够 10002.不足50
//切换账号到addr1
await faucet.connect(addr1).requestTokens();//Addr1领取100个代币
let balance = await token.balanceOf(addr1);
console.log(balance)
await faucet.connect(addr1).requestTokens();
balance = await token.balanceOf(addr1);
console.log(balance1)
})
})
})
# 指令
# npx hardhat test ./test/xxx.js
代币水龙头合约部署
module.exports = async ({getNamedAccounts,deployments})=>{
const firstAccount= (await getNamedAccounts()).firstAccount;
const MyToken=await deployments.get("MyToken");
const TokenAddress = MyToken.address;
const {deploy,log} = deployments;
const Faucet=await deploy("Faucet",{
from:firstAccount,
args: [TokenAddress],//参数token合约地址,把代币地址执行到水龙头代币中
log: true,
})
console.log('水龙头合约',Faucet.address)
}
module.exports.tags = ["all", "faucet"];
# 指令
# npx hardhat deploy
空投合约
空投合约
// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.0.0
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
pragma solidity ^0.8.22;
contract Airdrop {
mapping(address => uint) failTransferList;
/// @notice 向多个地址转账ERC20代币,使用前需要先授权
/// @param _token 转账的ERC20代币地址
/// @param _addresses 空投地址数组
/// @param _amounts 代币数量数组(每个地址的空投数量)
function multiTransferToken(
address _token,
address[] calldata _addresses,
uint256[] calldata _amounts
) external {
// 检查:_addresses和_amounts数组的长度相等
require(
_addresses.length == _amounts.length,
"Lengths of Addresses and Amounts NOT EQUAL"
);
IERC20 token = IERC20(_token); // 声明IERC合约变量
uint _amountSum = getSum(_amounts); // 计算空投代币总量
// 检查:授权代币数量 > 空投代币总量
require(
token.allowance(msg.sender, address(this)) > _amountSum,
"Need Approve ERC20 token"
);
// for循环,利用transferFrom函数发送空投
for (uint256 i; i < _addresses.length; i++) {
token.transferFrom(msg.sender, _addresses[i], _amounts[i]);
}
}
/// 向多个地址转账ETH
function multiTransferETH(
address payable[] calldata _addresses,
uint256[] calldata _amounts
) public payable {
// 检查:_addresses和_amounts数组的长度相等
require(
_addresses.length == _amounts.length,
"Lengths of Addresses and Amounts NOT EQUAL"
);
uint _amountSum = getSum(_amounts); // 计算空投ETH总量
// 检查转入ETH等于空投总量
require(msg.value == _amountSum, "Transfer amount error");
// for循环,利用transfer函数发送ETH
for (uint256 i = 0; i < _addresses.length; i++) {
// 注释代码有Dos攻击风险, 并且transfer 也是不推荐写法
// Dos攻击 具体参考 https://github.com/AmazingAng/WTF-Solidity/blob/main/S09_DoS/readme.md
// _addresses[i].transfer(_amounts[i]);
(bool success, ) = _addresses[i].call{value: _amounts[i]}("");
if (!success) {
failTransferList[_addresses[i]] = _amounts[i];
}
}
}
// 给空投失败提供主动操作机会
function withdrawFromFailList(address _to) public {
uint failAmount = failTransferList[msg.sender];
require(failAmount > 0, "You are not in failed list");
failTransferList[msg.sender] = 0;
(bool success, ) = _to.call{value: failAmount}("");
require(success, "Fail withdraw");
}
// 数组求和函数
function getSum(uint256[] calldata _arr) public pure returns (uint sum) {
for (uint i = 0; i < _arr.length; i++) sum = sum + _arr[i];
}
}
空投合约测试
const {ethers,getNamedAccounts,deployments} = require("hardhat");
const { assert,expect } = require("chai");
describe("Airdrop",function(){
let token;//合约代币
let airdrop;//空头合约
let owner;
let addr1;
let addr2;
beforeEach(async function(){
await deployments.fixture(["all"]);
[owner, addr1,addr2] = await ethers.getSigners();
const tokenDeployment = await deployments.get("MyToken");
token = await ethers.getContractAt("MyToken",tokenDeployment.address);//已经部署的合约交互
const AirdropDeployment = await deployments.get("Airdrop");
airdrop = await ethers.getContractAt("Airdrop",AirdropDeployment.address);//已经部署的合约交互
})
describe("空头测试",function(){
it("空投",async function(){
console.log(airdrop.target)
await token.approve(airdrop.target,1000);//代币授权给空头合约
await airdrop.multiTransferToken(token.target,[addr1.address,addr2.address],[100,200]);//空投
console.log('账号1领取空头的额度',await token.balanceOf(addr1.address))
console.log('账号1领取空头的额度',await token.balanceOf(addr2.address))
})
})
})
# 指令
# npx hardhat test ./test/xxx.js
空投合约部署
module.exports = async ({getNamedAccounts,deployments})=>{
const firstAccount= (await getNamedAccounts()).firstAccount;
const {deploy,log} = deployments;
const Airdrop=await deploy("Airdrop",{
from:firstAccount,
args: [],//参数
log: true,
})
console.log("空投合约",Airdrop.address)
};
module.exports.tags = ["all", "airdrop"];
# 指令
# npx hardhat deploy
关于测试和部署脚本使用
- 关于测试脚本说明:
const {ethers,getNamedAccounts,deployments} = require("hardhat");
const { assert,expect } = require("chai");
describe("测试合约",function(){
let outxxx;//外部合约
let myxxx;//当前合约
let owner;//账号1
let addr1;//账号2
let addr2;//账号3
//此构造的作用用于部署合约
beforeEach(async function(){
await deployments.fixture(["all"]);//部署指定的合约
[owner, addr1,addr2] = await ethers.getSigners();//获取账号数组
console.log(owner.address, addr1.address,addr2.address)//获取账户1,账户2,账户3,
const outDeployment = await deployments.get("outxxx");//外部合约
outxxx = await ethers.getContractAt("outxxx",outDeployment.address);//已经部署的合约交互
const myDeployment = await deployments.get("myxxx");//当前合约
myxxx = await ethers.getContractAt("myxxx",myDeployment.address);//已经部署的合约交互
})
//主要编写测试用例
describe("关于测试的用例的描述",function(){
it("当前测试的用例的描述",async function(){
//相关测试的逻辑
//*******
})
})
})
- 关于部署脚本说明:
module.exports = async ({getNamedAccounts,deployments})=>{
const {deploy,log} = deployments;
//获取外部合约实例
const Otherxxx=await deployments.get("Otherxxx");//获取外部合约的实例
console.log(Otherxxx.address)//获取外部部署合约的地址
//部署当前合约
const Myxxx=await deploy("Myxxx",{
from:firstAccount,
args: [],//参数 与合约的构造函数中的参数一一对应
log: true,
})
console.log("当前合约的部署地址",Myxxx.address)
//可以通verify 验证合于是否部署到链上
await hre.run("verify:verify", {
address: Myxxx.address,
constructorArguments: [],//参数 与合约的构造函数中的参数一一对应
});
};
总结
以上就是关于发行代币和代币水龙头和空头合约使用场景的的简单的实现,也汇总了智能合约开发、测试、部署的详细的步骤。