Lanzamiento de Tokens: 니벨 디오스
Antes de comenzar
Para este tutorial ocuparás NodeJs que recomiendo descargarlo en Linux via NVM , y también necesitarás Metamask u otra wallet compatible con fondos en Goerli que puedes obetener desde un faucet .
1. Hardhat 프로젝트의 Creamos
mkdir MyToken
cd MyToken
npm install --save hardhat
npx hardhat
2. 설치 라이브러리 adicionales
Instalamos
dotenv
para proteger nuestra llave privada, @nomiclabs/hardhat-etherscan
para auto verificar en etherscan y @openzeppelin/contracts
para hacer uso de los contratos de OpenZeppelin.npm install --save dotenv @nomiclabs/hardhat-etherscan @openzeppelin/contracts
3. Configuramos 안전모
Solidity
0.8.16
버전을 사용할 수 있는 Hardhat을 구성하고 메인넷 포크와 콘트라토스 자동 검증을 위한 작업을 수행합니다.hardhat.config.js
require("@nomicfoundation/hardhat-toolbox");
require("@nomiclabs/hardhat-etherscan");
require('dotenv').config()
module.exports = {
solidity: "0.8.16",
networks: {
hardhat: {
forking: {
url: process.env.MAINNET_RPC_URL,
},
},
goerli: {
url: process.env.GOERLI_RPC_URL,
accounts: [process.env.GOERLI_PRIVATE_KEY],
}
},
etherscan: {
apiKey: process.env.ETHERSCAN_API_KEY
}
};
También colocamos nuestras 변수 ocultas en el archivo
.env
. Las RPC_URL
las conseguimos en infura o alchemy , la llave privada en metamask y la API Key desde etherscan ..env
MAINNET_RPC_URL=https://mainnet.infura.io/v3/TULLAVE
GOERLI_RPC_URL=https://goerli.infura.io/v3/TULLAVE
GOERLI_PRIVATE_KEY=TULLAVE
ETHERSCAN_API_KEY=TULLAVE
4. Compilamos 누에스트로 토큰
Borramos el archivo de ejemplo que viene por defector
contracts/Lock.sol
y colocamos nuestro contrato de token en la misma carpeta.contracts/MyToken.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
interface IUniswapV2Factory {
function createPair(address tokenA, address tokenB) external returns (address pair);
}
interface IUniswapV2Router02 {
function factory() external pure returns (address);
function WETH() external pure returns (address);
function addLiquidityETH(
address token,
uint256 amountTokenDesired,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline
) external payable returns (uint256 amountToken, uint256 amountETH, uint256 liquidity);
function swapExactTokensForETHSupportingFeeOnTransferTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external;
function swapETHForExactTokens(
uint amountOut,
address[] calldata path,
address to,
uint deadline
) external payable returns (uint[] memory amounts);
}
contract MyToken is Context, IERC20, IERC20Metadata {
// Openzeppelin variables
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
// My variables
address public vaultAddress;
address public pair;
uint public feeDecimal = 2;
enum FeesIndex{ BUY, SELL, P2P }
uint[] public feePercentages;
mapping(address => bool) public is_taxless;
bool private isInFeeTransfer;
constructor(address swapRouter, address _vaultAddress)
{
IUniswapV2Router02 _uniswapV2Router = IUniswapV2Router02(swapRouter);
pair = IUniswapV2Factory(_uniswapV2Router.factory()).createPair(address(this), _uniswapV2Router.WETH());
// Edit here
_name = "My Token";
_symbol = "TKN";
vaultAddress = _vaultAddress;
feePercentages.push(1000); // Buy fee is 10.00%
feePercentages.push(1500); // Sell fee is 15.00%
feePercentages.push(500); // Buy fee is 5.00%
// End edit
is_taxless[msg.sender] = true;
is_taxless[vaultAddress] = true;
is_taxless[address(this)] = true;
is_taxless[address(0)] = true;
_mint(msg.sender, 1_000_000 ether);
}
function name() public view virtual override returns (string memory) {
return _name;
}
function symbol() public view virtual override returns (string memory) {
return _symbol;
}
function decimals() public view virtual override returns (uint8) {
return 18;
}
function totalSupply() public view virtual override returns (uint256) {
return _totalSupply;
}
function balanceOf(address account) public view virtual override returns (uint256) {
return _balances[account];
}
function transfer(address to, uint256 amount) public virtual override returns (bool) {
address owner = _msgSender();
_transfer(owner, to, amount);
return true;
}
function allowance(address owner, address spender) public view virtual override returns (uint256) {
return _allowances[owner][spender];
}
function approve(address spender, uint256 amount) public virtual override returns (bool) {
address owner = _msgSender();
_approve(owner, spender, amount);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) public virtual override returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, amount);
_transfer(from, to, amount);
return true;
}
function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, _allowances[owner][spender] + addedValue);
return true;
}
function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
address owner = _msgSender();
uint256 currentAllowance = _allowances[owner][spender];
require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
unchecked {
_approve(owner, spender, currentAllowance - subtractedValue);
}
return true;
}
function _transfer(
address from,
address to,
uint256 amount
) internal virtual {
require(from != address(0), "ERC20: transfer from the zero address");
require(to != address(0), "ERC20: transfer to the zero address");
// My implementation
uint feesCollected;
if (!is_taxless[from] && !is_taxless[to]) {
bool sell = to == pair;
bool p2p = from != pair && to != pair;
uint fee = calculateFee(p2p ? FeesIndex.P2P : sell ? FeesIndex.SELL : FeesIndex.BUY, amount);
feesCollected += fee;
}
amount -= feesCollected;
_balances[from] -= feesCollected;
_balances[vaultAddress] += feesCollected;
// End my implementation
uint256 fromBalance = _balances[from];
require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
unchecked {
_balances[from] = fromBalance - amount;
}
_balances[to] += amount;
emit Transfer(from, to, amount);
}
function _mint(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: mint to the zero address");
_totalSupply += amount;
_balances[account] += amount;
emit Transfer(address(0), account, amount);
}
function _burn(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: burn from the zero address");
uint256 accountBalance = _balances[account];
require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
unchecked {
_balances[account] = accountBalance - amount;
}
_totalSupply -= amount;
emit Transfer(account, address(0), amount);
}
function _approve(
address owner,
address spender,
uint256 amount
) internal virtual {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
function _spendAllowance(
address owner,
address spender,
uint256 amount
) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
require(currentAllowance >= amount, "ERC20: insufficient allowance");
unchecked {
_approve(owner, spender, currentAllowance - amount);
}
}
}
// My Functions
function calculateFee(FeesIndex fee_index, uint amount) internal view returns(uint) {
return (amount * feePercentages[uint(fee_index)]) / (10**(feeDecimal + 2));
}
}
아호라 로 컴필라모스.
npx hardhat compile
5. Pruebas unitarias forkeando 메인넷
Borramos el archivo ejemplo
test/MyToken.js
및 agregamos el nuestro propio. Las pruebas unitarias nos ayudan a aseguramos que todo esté funcionando bien.test/MyToken.js
const {
time,
loadFixture,
} = require("@nomicfoundation/hardhat-network-helpers");
const { anyValue } = require("@nomicfoundation/hardhat-chai-matchers/withArgs");
const { expect } = require("chai");
describe("Lock", function () {
// We define a fixture to reuse the same setup in every test.
// We use loadFixture to run this setup once, snapshot that state,
// and reset Hardhat Network to that snapshopt in every test.
async function launchAndFundWalletA() {
const blockNumBefore = await ethers.provider.getBlockNumber();
const blockBefore = await ethers.provider.getBlock(blockNumBefore);
deadline = blockBefore.timestamp + 500;
const [deployer, vaultWallet, walletA, walletB] = await ethers.getSigners();
const MyToken = await ethers.getContractFactory("MyToken");
const uniswapRouter = await ethers.getContractAt("IUniswapV2Router02", "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D");
const myToken = await MyToken.deploy(uniswapRouter.address, vaultWallet.address);
await myToken.approve(uniswapRouter.address, ethers.utils.parseEther("500000"))
await uniswapRouter.addLiquidityETH(
myToken.address,
ethers.utils.parseEther("500000"),
ethers.utils.parseEther("0"),
ethers.utils.parseEther("0"),
deployer.address,
deadline,
{ value: ethers.utils.parseEther("1") })
await myToken.transfer(walletA.address, ethers.utils.parseEther("1000"))
return { uniswapRouter, myToken, vaultWallet, walletA, walletB, deadline };
}
describe("Fee collection", function () {
it("Should collect fees on P2P", async function () {
const { uniswapRouter, myToken, vaultWallet, walletA, walletB } = await loadFixture(launchAndFundWalletA);
await myToken.connect(walletA).transfer(walletB.address, ethers.utils.parseEther("100"))
expect(
ethers.utils.parseEther("95.0")
).to.equal(
await myToken.balanceOf(walletB.address)
);
expect(
ethers.utils.parseEther("5.0")
).to.equal(
await myToken.balanceOf(vaultWallet.address)
);
});
it("Should collect fees on Buy", async function () {
const { uniswapRouter, myToken, vaultWallet, walletA, walletB, deadline } = await loadFixture(launchAndFundWalletA);
await myToken.connect(walletA).approve(uniswapRouter.address, ethers.utils.parseEther("100.0"))
await uniswapRouter.connect(walletA).swapETHForExactTokens(
ethers.utils.parseEther("100.0"),
[await uniswapRouter.WETH(), myToken.address],
walletA.address,
deadline,
{ value: ethers.utils.parseEther("0.1") })
expect(
ethers.utils.parseEther("10.0")
).to.equal(
await myToken.balanceOf(vaultWallet.address)
);
});
it("Should collect fees on Sell", async function () {
const { uniswapRouter, myToken, vaultWallet, walletA, walletB, deadline } = await loadFixture(launchAndFundWalletA);
await myToken.connect(walletA).approve(uniswapRouter.address, ethers.utils.parseEther("100.0"))
await uniswapRouter.connect(walletA).swapExactTokensForETHSupportingFeeOnTransferTokens(
ethers.utils.parseEther("100.0"),
ethers.utils.parseEther("0"),
[myToken.address, await uniswapRouter.WETH()],
walletA.address,
deadline)
expect(
ethers.utils.parseEther("15.0")
).to.equal(
await myToken.balanceOf(vaultWallet.address)
);
});
});
});
Ejecutamos los test y nos aseguramos que todo funcionará bien haciendo las pruebas en el estado actual de mainnet forkeando.
npx hardhat test
6. Lanzamiento y Auto Verificación
Creamos nuestro archivo de deploy donde configuramos la rutina de lanzamientos y definimos los parametros de los constructores.
scripts/deploy.js
const hre = require("hardhat");
async function main() {
const MyToken = await ethers.getContractFactory("MyToken");
uniswapRouterAddress = "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"
vaultAddress = "0x18747BE67c5886881075136eb678cEADaf808028"
const myToken = await MyToken.deploy(uniswapRouterAddress, vaultAddress);
await myToken.deployed();
console.log("myToken launched at:", myToken.address);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
아호라 란자모스. Enste caso lo hacemos en gerli pero en el archivo
hardhat.config.js
podemos configurar el destino.npx hardhat run scripts/deploy.js --network goerli
Y lo auto verificamos si no lo hizo automáticamente
npx hardhat verify --network goerli ADDRESSDELCONTRATO PARAM1 PARAM2
Por ejemplo debería quedar algo así
npx hardhat verify --network goerli 0xFA4A4d277A8F8FF6158615c8FaE2Abfe147fb64c 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D 0x730bF3B67090511A64ABA060FbD2F7903536321E
Bono: Github Actions에서 자동 테스트
Los는 automáticos nos van a permitir detector errores en cada cambio en el código를 테스트합니다. Nuestros test se ejecutarán automaticamente cada vez que se haga una push o pull request y nos avisará si algún test no pasó.
Primero debemos hacer un par de modificaciones en el archivo de configuración de hardhat para que pueda operator sin necesidad de tener todas las 변수.
hardhat.config.js
require("@nomicfoundation/hardhat-toolbox");
require("@nomiclabs/hardhat-etherscan");
require('dotenv').config()
module.exports = {
solidity: "0.8.16",
networks: {
hardhat: {
forking: {
url: process.env.MAINNET_RPC_URL,
},
}
}
};
if(process.env.GOERLI_RPC_URL && process.env.GOERLI_PRIVATE_KEY)
{
module.exports["networks"]["goerli"] = {
url: process.env.GOERLI_RPC_URL,
accounts: [process.env.GOERLI_PRIVATE_KEY],
}
}
if(process.env.ETHERSCAN_API_KEY)
{
module.exports["etherscan"] = {
apiKey: process.env.ETHERSCAN_API_KEY
}
}
.github/workflows/unit-tests.yml
name: Node.js CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x]
steps:
- uses: actions/checkout@v3
- name: Mainnet Forking Tests
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: export MAINNET_RPC_URL=${{ secrets.MAINNET_RPC_URL }}; npx hardhat test
워크플로에 대한 Readme에 대한 최종 집계 및 배지.
README.md
![workflow](https://github.com/TUUSUARIO/TUREPO/actions/workflows/unit-tests.yml/badge.svg)
Gracias por ver este 비디오!
Sígannos en dev.to y en para todo lo relacionado al desarrollo en Blockchain en Español.
Reference
이 문제에 관하여(Lanzamiento de Tokens: 니벨 디오스), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/turupawn/lanzamiento-de-tokens-nivel-dios-15ek텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)