Lanzamiento de Tokens: 니벨 디오스

67065 단어 defiweb3tokens
El ecosistema web3 sigue creciendo así como las herramientas que usamos para crear y lanzar 스마트 계약. En este video exploramos las herramientas nuevas que he estado para lanzar proyectos tokens y cualquier tipo de smart contracts.

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 안전모



Solidity0.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 ejemplotest/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.

좋은 웹페이지 즐겨찾기