4-3. Token Contract 만들어보기

동동주·2025년 10월 4일

* smart contract 기반의 token 기능을 하는 contract를 만들어볼 예정
1. Token contract 작성
2. Token contract 배포
3. Token contract 테스트


Token (smart contract based)
--> native token과는 다르지만 비슷하게 기능하는 것을 구현하기..?

native token 이란?
ex) BTC, ETH.. 등등
해당 네트워크에서 발생한 수수료는 native token으로만 받는다.



1. Token contract 작성

contract 폴더 > MyToken.sol 파일 생성

코드1

// SPDX-License-Identifer: MIT 
// 라이센스 정책 기입 (위에는 주석상태 그대로 기입함!)

// pragma = 컴파일러에게 알려주는 내용
pragma solidity ^0.8.28;

//class
contract MyToken {
    string public name;
    string public symbol;
    uint8 public decimals; // 1wei --> 1 * 10^-18 ETH 소수점 자리 지원하는 범위 알려줌
    //uint = 음수x 8= 8bit ==> unsigned 8bit int. uint16, uint256....

    //생성자
    // uint8 의 경우 길이 알고있음 (지정되어있음)
    // string은 길이 제한x ==> memory를 사용해서 string을 복사해라 지정? (+@) 
    constructor(string memory _name, string memory _symbol, uint8 _decimals) {
        name = _name;
        symbol = _symbol;
        decimals = _decimals;
        // 변수 이름이 같으면 contract의 필드를 우선 인식한다고 함
        // 혼동 방지를 위해 언더바 사용
    }
}
  • memory가 뭐하는 걸까
    Solidity의 메모리 구조 (추후첨부)

  • name, symbol, decimals
    name : 토큰 이름 (ex. Ether)
    symbol : 화폐 기호 (ex. ETH --> 6ETH, 16ETH...)
    decimal : 값으로 유효한 소수점 n째 자리 (ex. ETH는 18)
    (ex. decimal이 1인 경우 --> 0.1까지만 유효, 0.01은 무의미)

변수 이름 인식 우선순위는 다른 프로그래밍 언어랑 좀 다른 것 같다...
보통은 매개변수 우선이라 this 같은 걸로 멤버변수(필드에 위치함)는 따로 지정해줘야 하지 않나..?

하여튼 작성 후

npx hardhat compile  

터미널에 실행

Warning: SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing "SPDX-License-Identifier: \" to each source file. Use "SPDX-License-Identifier: UNLICENSED" for non-open-source code. Please see https://spdx.org for more information.
--> contracts/MyToken.sol


Generating typings for: 1 artifacts in dir: typechain-types for target: ethers-v6 Successfully generated 6 typings! Compiled 1 Solidity file successfully (evm target: paris).

경고가 뜨지만 괜찮다고 하고..

컴파일 이후 변화 이미지
이렇게 뭔가 더 생긴 것을 볼 수 있다.
(typechain-types, bulid-info, contracts/MyToken.sol폴더 등등...)

  • contracts/MyToken.sol/MyToken.json
    여기에 가면 컴파일 결과가 bytecode로 나온다
    (자바도 컴파일하면 bytecode로 나옴)

  • typechain-types/factories
    배포, 테스트에 관련된 코드들이 자동으로 생성된다 (이거 사용해서 배포,테스트 할 예정)


2. Token contract 배포

ignition/modules 폴더 > MyToken.ts 파일 생성.

코드2

import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
// 빌드 모듈 가져오기 --> nomic...에서

// buildModule (모듈id, 모듈 정의 함수 입력받음)
export default buildModule("MyTokenDeploy", (m) => {
   const MyTokenC = m.contract("MyToken", ["MyToken", "MT" , 18])
   return { MyTokenC };
   // 오브젝트로 감싸서 리턴해줘야 한다?
})

+@.. 오브젝트로 감싼다는게...? 뭔지 모르겠다..

npx hardhat ignition deploy ignition/modules/MyToken.ts 

You are running Hardhat Ignition against an in-process instance of Hardhat Network.
This will execute the deployment, but the results will be lost.
You can use --network \ to deploy to a different network.

Hardhat Ignition 🚀

Deploying [ MyTokenDeploy ]

Batch #1
Executed MyTokenDeploy#MyToken

[ MyTokenDeploy ] successfully deployed 🚀

Deployed Addresses

MyTokenDeploy#MyToken - 0x5FbDB2315678afecb367f032d93F642f64180aa3

마지막이 smartcontract가 배포된 주소 (찾아가는데 필요)

  • hardhat 네트워크를 일시적으로 띄워서 배포함
    --> 다시 hardhat 네트워크를 띄우면 없음 (초기화됨)
npx hardhat node
nvm use v20.11.0 하고
npx hardhat ignition deploy ignition/modules/MyToken.ts --networkd localhost

로 로컬호스트로 해도 동일하게 배포되고
어떤 상호작용으로 배포했는지 node에 뜨는 걸로.. 볼 수 있다고..

  • contract 배포도 transaction으로 하는 걸 볼 수 있다



3. Token contract 테스트

test 폴더 > MyToken.ts 파일 생성.

보통 contract와 test 파일 이름은 같게 한다 (배포는 케바케)

코드3-1(성공x)

import hre from "hardhat";
//hardhat runtime environment ..!

describe("mytoken deploy", () => {
  it("should deploy", async () => {
    const myTokenC = (await) hre.ethers.deployContract("MyToken", [
      "MyToken",
      "MT",
      18,
    ]);
    // 일반 hardhat에 없고 hardhat-ethers에 있는 기능임
    // 알아서 컴파일 결과에서 배포할 contract를 받아온다 (hardhat ethers가)
    console.log(await myTokenC.name());
  });
});

해당 코드에는 await 부분에 괄호가 쳐져있다 (안넣고 실행시켰음)

npx hardhat test

그래서 위의 코드로 실행을 해도

mytoken deploy
Promise { <pending> } <-- promise만 나옴
✔ should deploy (2471ms)

1 passing (2s)

이렇게 promise가 나온다
저 deployContract가 비동기함수(시간걸리니까 일단 promise 주고 다음거 먼저 넘어가는 함수)라서, 기다리지 않고 (await없이) 그냥 받으면 promise가 나온다

비동기함수에 대해 좀 더 알아보고
블록체인 기본 개념 정리... 는 따로 해둬야겠다 (추후첨부)


코드3-2(성공o)

import hre from "hardhat";
//hardhat runtime environment ..!

describe("mytoken deploy", () => {
  it("should deploy", async () => {
    const myTokenC = await hre.ethers.deployContract("MyToken", [
      "MyToken",
      "MT",
      18,
    ]);
    // 일반 hardhat에 없고 hardhat-ethers에 있는 기능임
    // 알아서 컴파일 결과에서 배포할 contract를 받아온다 (hardhat ethers가)
    console.log(await myTokenC.name());
  });
});

await 잘 써주면~

mytoken deploy
MyToken
✔ should deploy (6524ms)

1 passing (7s)

잘 나와준다


코드 3-3.

각각 목표한 게 잘 나오는지 테스트..? 하는 코드

import hre from "hardhat";
import { expect } from "chai";
import { MyToken } from "../typechain-types";
// hardhat에서 컴파일 할 때 필요한 타입 정의 파일을 여기에 보관함
// 타입 작성하면 요 코드는 알아서 채워줌

describe("mytoken deploy", () => {
  let myTokenC: MyToken; //변수로 선언
  // 타입 지정이 되어있지 않을 시 --> 타입스크립트에서 에러로 표시함

  // 이 그룹(describe)을 실행하기 전에 실행시킨다
  // 미리 실행 --> let myTokenC에 의해 변수로 저장
  // 그러면 아래 테스트 코드에서도 myTokenC를 사용 가능함
  before("should deploy", async () => {
    myTokenC = await hre.ethers.deployContract("MyToken", [
      "MyToken",
      "MT",
      18,
    ]);
  });

  it("should return name", async () => {
    expect(await myTokenC.name()).equal("MyToken");
  });
  it("should return symbol", async () => {
    expect(await myTokenC.symbol()).equal("MT");
  });
  it("should return decimals", async () => {
    expect(await myTokenC.decimals()).equal(18);
  });
});

아까와 똑같이 npx~~ 치면

mytoken deploy
✔ should return name (50ms)
✔ should return symbol (45ms)
✔ should return decimals

3 passing (3s)

역시 잘 나온다~!

profile
배운 내용 정리&기록, 스크랩

0개의 댓글