Hardhat 설치 및 ERC20 테스트

Hardhat 설치 및 ERC20 테스트

in

Solidity 기본 예제

pragma solidity ^0.8.13;

contract SimpleStorage {
    //State variable to store a number
    uint public num;
    function set(uint _num) public {
        num = _num;
    }
    function get() public view returns (uint) {
        return num;
    }
}

contract X {
    string public name;
    constructor(string memory _name) {
        name = _name;
    }

contract Error {
    function testRequire(uint _i) public pure {
        require(_i > 10, "Input must be grater than 10");
    }}
}

취약점의 경우 require 설정을 잘못하여 발생하는 경우가 많음

  • 기본 타입 : unit, int, address, boolean
  • function : 호출의 대상이 되며, 상태 변경 가능하지만 view function 의 경우 상태변경 불가능
  • constructor() : 컨트랙트 배포시 실행되는 함수
  • require(조건,메시지) : 조건이 참이 아닌 경우 에러 발생

ERC20 토큰

ERC20 토큰 이란 대체 가능 토큰의 표준으로 2015년에 처음 구현된 ERC20은 개발자가 이더리움 기반 애플리케이션이나 프로토콜에 적합한 대체 가능 토큰을 만들 수 있도록 하는 토큰 표준

스크린샷 2022-11-01 오후 8 48 26 이미지 출처 : Introduction to Web3 and Smart Contract (수호아이오)

Solidity 로 작성된 스마트 컨트랙트 테스트

Smart Contract의 경우 한번 배포되어 블록체인에 기록되면 업그레이드가 어렵기 떄문에 코드가 잘 작성되었는지 테스트 필요
Ethereum Smart Contract는 테스트와 배포를 위한 Hardhat 이라는 Framework 가 존재

Hardhat 설치

실습 저장소 코드 : https://github.com/astean1001/2022_seminar_tutorial

  • 하드햇 설치
    npm install --save-dev hardhat
    npx hardhat
    npm install --save-dev @nomicfoundation/hardhat-toolbox
    vim hardhat.config.js
    

    hardhat.config.js 파일에 추가 설치한 toolbox 명시 스크린샷 2022-11-01 오후 8 59 36 hardhat.config.js

잘못 구현된 Contract

  • 정상적인 Simple Swap.sol 코드
pragma solidity 0.8.9;

import "./IERC20.sol";

contract SimpleSwap {
    IERC20 public token1;
    address public owner1;
    uint public amount1;
    IERC20 public token2;
    address public owner2;
    uint public amount2;

    constructor(
        address _token1,
        address _owner1,
        uint _amount1,
        address _token2,
        address _owner2,
        uint _amount2
    ) {
        token1 = IERC20(_token1);
        owner1 = _owner1;
        amount1 = _amount1;
        token2 = IERC20(_token2);
        owner2 = _owner2;
        amount2 = _amount2;
    }

    function swap() public {
        require(msg.sender == owner1 || msg.sender == owner2, "Not authorized");
        require(
            token1.allowance(owner1, address(this)) >= amount1,
            "Token 1 allowance too low"
        );
        require(
            token2.allowance(owner2, address(this)) >= amount2,
            "Token 2 allowance too low"
        );

        _safeTransferFrom(token1, owner1, owner2, amount1);
        _safeTransferFrom(token2, owner2, owner1, amount2);
    }

    function _safeTransferFrom(
        IERC20 token,
        address sender,
        address recipient,
        uint amount
    ) private {
        bool sent = token.transferFrom(sender, recipient, amount);
        require(sent, "Token transfer failed");
    }
}
  • 잘못 구현된 Simple Swap.sol 코드
    function transferForm{
      address sender,
      address recipient,
      unit amount
    } external returns (bool) {
      return true;
      allowance[sender][msg.sender] -= amount;
      balanceOf[sender] -= amount;
      balanceOf[sender] += amount;
      emit Transfer(sender, recipient, amount);
    }
    

return true 가 밑이 아닌 맨 위에 있기에 아래의 코드에서 잔고가 추가됬는지 확인을 하지 않고 alice가 잔고가 존재하지 않아도 bob의 토큰을 10만개를 가져갈 수 있음

  it("swap() might not work properly if token is malicious", async function () {
    simpleSwap = await swapFactory.connect(bob).deploy(tokenM.address, mallory.address, 10, tokenB.address, bob.address, 100000);
    await tokenM.connect(mallory).approve(simpleSwap.address, 10);
    await tokenB.connect(bob).approve(simpleSwap.address, 100000);
    await simpleSwap.connect(mallory).swap();
    expect(await tokenB.balanceOf(mallory.address)).to.be.equal(100000);
    expect(await tokenM.balanceOf(bob.address)).to.be.equal(0);
  });

📃 References