728x90
안녕하세요, 오늘은 제가 BNB 테스트넷에서 작동하는 DApp 프로젝트 BNB.Fun을 개발하며 겪은 과정을 공유하려고 합니다. 이 프로젝트는 토큰 발행부터 프리세일(구매/환불) 기능까지 구현하는 것을 목표로 했고, 여러 시행착오 끝에 성공적으로 완성했습니다. 그 여정을 정리해 보겠습니다.
 
지난번까지 개발한 내용은 React(프론트앤드) <ㅡ> 백앤드(컨트랙트) 연동하여
웹 사이트(localhost)에서 토큰을 발행하고 웹사이트에 발행한 토큰들이 웹에 표시되는것까지
구현 완료했습니다.
 

Step 1: 컨트랙트 코드(Solidity)

먼저, TokenFactory.sol, CustomToken.sol, Presale.sol 세 개의 컨트랙트를 작성하고 배포했습니다.
  • TokenFactory:
    • createToken(name, symbol): 0.01 BNB를 받고 CustomTokenPresale 인스턴스를 생성.
    • withdraw(): 컨트랙트에 쌓인 BNB를 소유자에게 전송가능하도록 구현 
    • 배포 주소: 0x7E5b012c635d9bB9c19aAD3E37F059C4410E3AA1. (bsc testnet)
     
  • CustomToken:
    • ERC-20 기반 토큰, 초기 공급량은 Presale에 할당.
    • 예: 0x173ACc12fd867f2bee4bECB1e05a2e2fB106f1Ee.
  • Presale:
    • buyTokens(): BNB로 토큰 구매.
    • refund(): 투자한 BNB 환불 및 토큰 반환.
    • 예: 0x790fc6bB52B34488D9b6a9630e7DcC516d47c8a6.

 

Step 2: 프론트엔드 구현

 
React 프로젝트를 생성하고, 주요 컴포넌트를 구현했습니다.
  • App.js:
    • MetaMask 연결 및 tokens 상태 관리.
    • 로컬 스토리지로 tokens 유지 (새로고침 시 초기화 방지).
  • CreateTokenModal.js:
    • 토큰 이름, 심볼 입력 후 TokenFactory.createToken 호출.
    • 발행된 tokenAddresspresaleAddresstokens에 추가.
  • PresalePage.js:
    • URL 파라미터(tokenAddress)로 토큰 식별.
    • tokens에서 presaleAddress 찾아 프리세일 데이터 로드.
    • "Buy Now"와 "Refund" 버튼으로 구매/환불 기능 제공.

 

 


Step 3: 디버깅과 해결 과정

개발 중 여러 문제를 만났고, 하나씩 해결해 나갔습니다.
  1. "Transaction execution reverted":
    • 원인1 : 가스 한도 부족 또는 잔액 문제 / 
    • 해결: gasLimit: 3000000으로 증가, 계정에 0.1 BNB 충전.
    • 원인2. :  CustomTokenPresale 주소를 필요로 하는데, PresaleCustomToken 주소를 필요로 함 → 순서 문제로 인해 CustomToken의 초기 공급량이 잘못된 주소(address(0))로 민팅되거나, 이후 Presale에서 토큰을 다룰 때 오류 발생.
    • 해결: Presale을 먼저 만들어 presaleAddress를 확보한 뒤, 이를 CustomToken에 전달 → CustomToken이 올바른 Presale 주소에 민팅하고, Presale은 이후 업데이트로 토큰을 인식.
      *이 문제로 진짜 몇일동안 뭐가 문제인지 고민함...(백업하고 구현하고 문제발생되고 다시 백업하고 구현하고 문제 생기고.. 반복)
  2. "Presale address not found in local token list":
    • 원인: PresalePagetokens에서 데이터를 찾지 못함.
    • 해결: URL을 최신 토큰 주소로 수정, tokens 상태를 로컬 스토리지로 유지.
  3. "limit exceeded" (eth_getLogs):
    • 원인: TokenFactory 이벤트 조회 시 블록 범위 초과.
    • 해결: eth_getLogs 대신 tokens 상태만 사용.

Step 4: 최종 테스트

최신 토큰 발행 후 프리세일 페이지 접속
  • URL: http://localhost:3000/presale/0x173ACc12fd867f2bee4bECB1e05a2e2fB106f1Ee.
  • 결과:
    • "Presale Details"에 현재 비율, 모금액 표시.
    • "Buy Now"로 0.01 BNB 구매 성공.
    • "Refund"로 환불 성공.
    •  
 
 
 

주요코드(Solidity)

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "./customToken.sol";
import "./Presale.sol";

contract TokenFactory is Ownable {
    uint256 public constant CREATE_FEE = 0.01 ether;

    event TokenCreated(address tokenAddress, address presaleAddress);

    constructor() Ownable(msg.sender) {}

    function createToken(string memory name, string memory symbol) external payable returns (address, address) {
        require(msg.value == CREATE_FEE, "Must send exactly 0.01 BNB");

        // Presale 먼저 생성
        Presale newPresale = new Presale(IERC20(address(0)));
        address presaleAddress = address(newPresale);

        // CustomToken 생성 및 Presale로 토큰 전송
        CustomToken newToken = new CustomToken(name, symbol, presaleAddress);
        address tokenAddress = address(newToken);

        // Presale에 토큰 설정 및 소유권 이전
        newPresale.updateToken(IERC20(tokenAddress));
        newPresale.transferOwnership(msg.sender);

        emit TokenCreated(tokenAddress, presaleAddress);
        return (tokenAddress, presaleAddress);
    }

    function withdraw() external onlyOwner {
        payable(owner()).transfer(address(this).balance);
    }
}

 

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

interface IPancakeSwapRouter {
    function addLiquidityETH(
        address token,
        uint256 amountTokenDesired,
        uint256 amountTokenMin,
        uint256 amountETHMin,
        address to,
        uint256 deadline
    ) external payable returns (uint256 amountToken, uint256 amountETH, uint256 liquidity);
}

contract Presale is Ownable, ReentrancyGuard {
    IERC20 public token;
    uint256 public constant INITIAL_RATE = 10_000_000 * 10**18; // 1 BNB = 1,000만 토큰
    uint256 public constant PRICE_INCREASE = 10; // 10% 증가
    uint256 public constant INCREASE_INTERVAL = 1 hours; // 1시간마다
    uint256 public constant TARGET_BNB = 5 ether; // 5 BNB 목표
    uint256 public constant LIQUIDITY_TOKENS = 50_000_000 * 10**18; // 5천만 토큰

    uint256 public startTime;
    uint256 public totalBNB;
    bool public liquidityAdded;

    mapping(address => uint256) public contributions; // 투자 BNB
    mapping(address => uint256) public tokenBalances; // 구매 토큰

    IPancakeSwapRouter public pancakeSwapRouter;
    address public constant PANCAKESWAP_ROUTER = 0xD99D1c33F9fC3444f8101754aBC46c52416550D1; // BSC Testnet 라우터

    event TokensPurchased(address buyer, uint256 bnbAmount, uint256 tokenAmount);
    event Refunded(address user, uint256 bnbAmount);
    event LiquidityAdded(uint256 tokenAmount, uint256 bnbAmount);

    constructor(IERC20 _token) Ownable(msg.sender) {
        token = _token;
        pancakeSwapRouter = IPancakeSwapRouter(PANCAKESWAP_ROUTER);
        startTime = block.timestamp;
    }

    function getCurrentRate() public view returns (uint256) {
        uint256 intervals = (block.timestamp - startTime) / INCREASE_INTERVAL;
        return INITIAL_RATE - (INITIAL_RATE * PRICE_INCREASE * intervals) / 100; // 토큰 수 감소
    }

    function buyTokens() external payable nonReentrant {
        require(!liquidityAdded, "Presale ended");
        require(msg.value > 0, "Must send BNB");

        uint256 rate = getCurrentRate();
        uint256 tokenAmount = (msg.value * rate) / 1 ether;
        require(token.balanceOf(address(this)) >= tokenAmount, "Not enough tokens");

        contributions[msg.sender] += msg.value;
        tokenBalances[msg.sender] += tokenAmount;
        totalBNB += msg.value;

        token.transfer(msg.sender, tokenAmount);
        emit TokensPurchased(msg.sender, msg.value, tokenAmount);

        if (totalBNB >= TARGET_BNB) {
            addLiquidity();
        }
    }

    function refund() external nonReentrant {
        require(!liquidityAdded, "Presale ended");
        uint256 bnbAmount = contributions[msg.sender];
        require(bnbAmount > 0, "No contribution");

        uint256 refundAmount = (bnbAmount * 90) / 100;
        uint256 tokenAmount = tokenBalances[msg.sender];

        contributions[msg.sender] = 0;
        tokenBalances[msg.sender] = 0;
        totalBNB -= bnbAmount;

        require(token.transferFrom(msg.sender, address(this), tokenAmount), "Token transfer failed");
        payable(msg.sender).transfer(refundAmount);
        emit Refunded(msg.sender, refundAmount);
    }

    function addLiquidity() internal {
        require(!liquidityAdded, "Liquidity already added");
        liquidityAdded = true;

        token.approve(address(pancakeSwapRouter), LIQUIDITY_TOKENS);
        pancakeSwapRouter.addLiquidityETH{value: TARGET_BNB}(
            address(token),
            LIQUIDITY_TOKENS,
            LIQUIDITY_TOKENS * 90 / 100, // 10% 슬리피지 허용
            TARGET_BNB * 90 / 100,
            address(this),
            block.timestamp + 15 minutes
        );

        emit LiquidityAdded(LIQUIDITY_TOKENS, TARGET_BNB);

        uint256 remainingTokens = token.balanceOf(address(this));
        if (remainingTokens > 0) {
            token.transfer(owner(), remainingTokens);
        }
    }

    function withdrawBNB() external onlyOwner {
        require(liquidityAdded, "Presale not ended");
        payable(owner()).transfer(address(this).balance);
    }

    function updateToken(IERC20 _token) external onlyOwner {
        require(address(token) == address(0), "Token already set");
        token = _token;
    }
}
 
 
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract CustomToken is ERC20, Ownable {
    constructor(
        string memory name,
        string memory symbol,
        address presaleAddress
    ) ERC20(name, symbol) Ownable(msg.sender) {
        uint256 initialSupply = 100_000_000 * 10**18; // 1억 토큰
        _mint(address(this), initialSupply);
        if (presaleAddress != address(0)) {
            _transfer(address(this), presaleAddress, initialSupply);
        }
    }
}
 
 

 
<마무리>
 
BSC 테스트넷의 JSON-RPC 제한 문제를 해결하면서 많은 걸 배웠습니다. 이 DApp은 토큰 발행과 프리세일을 간단히 경험할 수 있는 좋은 시작점이 될 거라 생각합니다.
다음 목표는 차트 데이터를 실시간으로 반영하고, 프리세일이 5BNB(테스트목적으로 0.5BNB)가 프리세일로 모금이 되면 팬케이크 스왑에 자동으로 유동성이 공급되는것을 구현 및 테스트 할 예정입니다. 
 
 
<잡담>
.env파일에 자신의 메타마스크 개인key를 입력해놓는데, 실수로 github에 .env도 같이올렷다가 1만원(bnb) 털렸습니다 ㅎㅎ 신기해서 
 
다른지갑에 5000원(bnb)입금하고 깃허브에 올렸더니 10초만에 털렸습니다. 아무튼 개인key는 절대 어디에 올리지 마세요

 

728x90
728x90
잡담
최종 프로젝트 형태는 Pump.fun과 비슷한 Dapp을 배포하는것입니다! 저만의 차별성이 있기도하고 아이디어가 번뜩 떠올라서 구현해보려고합니다. 100개 프로젝트 만들다보면 한개쯤 대박 터지겠죠? 1달에 1개 프로젝트를 만들면 8년안에 pump.fun같은 대박서비스가 개발될수도.... 지금 저는 군대(육군장교)인데 진짜 주간에는 
작업하고, 훈련계획짜고, 용사들 챙기고 훈련뛰고나면 개발하기가 힘들지만... 다시 1일1코딩 도전합니다.!
 
 
 
 
 
 
 

프로젝트 개요

BNB Chain Testnet에서 스마트 컨트랙트를 배포하고, 프론트엔드와 연동해서 토큰 생성 및 프리세일 기능을 구현한 과정을 공유하려고 합니다. Hardhat, React, Ethers.js를 사용했으며, 단계별로 자세히 정리해봤습니다.

 
이 프로젝트(실습)는 유저가 0.01 BNB(test net bnb)를 지불하면 새로운 ERC-20 토큰을 생성하고, 생성된 토큰을 프리세일 컨트랙트로 전송한 뒤, 프리세일에서 유저가 토큰을 구매할 수 있는 DApp을 만드는 것을 목표로 했습니다. 주요 구성 요소는 다음과 같습니다:
  • TokenFactory 컨트랙트: 새로운 토큰을 생성하고 프리세일 컨트랙트로 전송.
  • Presale 컨트랙트: 프리세일 진행 및 유동성 풀 추가.
  • 프론트엔드: React로 구현, 유저가 토큰 생성 및 프리세일에 참여 가능.        * ui는 Tailwind Css사용해서 업+Pump.fun과 비슷하게 구현할겁니다! 

개발 환경

  • 블록체인 네트워크: BNB Chain Testnet
  • 스마트 컨트랙트 개발: Hardhat, Solidity
  • 프론트엔드: React, Ethers.js
  • 지갑: MetaMask

구현 단계

1. 스마트 컨트랙트 작성
1.1 TokenFactory 컨트랙트
TokenFactory 컨트랙트는 유저가 0.01 BNB를 지불하면 새로운 ERC-20 토큰을 생성하고, 발행된 토큰을 Presale 컨트랙트로 전송합니다.
contracts/TokenFactory.sol:solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract TokenFactory is Ownable {
    uint256 public constant CREATE_FEE = 0.01 ether;
    address public presaleContract;

    event TokenCreated(address tokenAddress, string name, string symbol);

    constructor(address _presaleContract) Ownable(msg.sender) {
        require(_presaleContract != address(0), "Presale contract address cannot be zero");
        presaleContract = _presaleContract;
    }

    function createToken(string memory name, string memory symbol) external payable {
        require(msg.value == CREATE_FEE, "Must send 0.01 BNB to create token");
        require(bytes(name).length > 0, "Token name cannot be empty");
        require(bytes(symbol).length > 0, "Token symbol cannot be empty");

        CustomToken newToken = new CustomToken(name, symbol, 100_000_000 * 10**18);
        require(newToken.transfer(presaleContract, 100_000_000 * 10**18), "Token transfer failed");

        emit TokenCreated(address(newToken), name, symbol);
    }

    function withdraw() external onlyOwner {
        payable(owner()).transfer(address(this).balance);
    }
}

contract CustomToken is ERC20 {
    constructor(string memory name, string memory symbol, uint256 initialSupply) 
        ERC20(name, symbol) 
    {
        _mint(msg.sender, initialSupply);
    }
}
1.2 Presale 컨트랙트
Presale 컨트랙트는 유저가 BNB를 보내서 토큰을 구매할 수 있도록 하고, 목표 BNB(5 BNB)에 도달하면 PancakeSwap에 유동성을 추가합니다.
contracts/Presale.sol (일부 생략):solidity
 
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract Presale is Ownable {
    IERC20 public token;
    uint256 public constant INITIAL_RATE = 10_000_000;
    uint256 public constant TARGET_BNB = 5 ether;

    uint256 public totalBNB;
    mapping(address => uint256) public contributions;
    mapping(address => uint256) public tokenBalances;

    constructor(IERC20 _token) Ownable(msg.sender) {
        token = _token;
    }

    function buyTokens() external payable {
        uint256 tokenAmount = msg.value * INITIAL_RATE;
        require(token.balanceOf(address(this)) >= tokenAmount, "Not enough tokens");

        contributions[msg.sender] += msg.value;
        tokenBalances[msg.sender] += tokenAmount;
        totalBNB += msg.value;
    }
}
2. 스마트 컨트랙트 배포
Hardhat을 사용해 BNB Chain Testnet에 컨트랙트를 배포했습니다.
scripts/deploy.js:javascript
 
const hre = require("hardhat");

async function main() {
  const [deployer] = await hre.ethers.getSigners();
  console.log("Deploying contracts with the account:", deployer.address);

  const CustomToken = await hre.ethers.getContractFactory("CustomToken");
  const testToken = await CustomToken.deploy("Test Token", "TST", ethers.parseEther("100000000"));
  await testToken.waitForDeployment();
  console.log("Test Token deployed to:", await testToken.getAddress());

  const Presale = await hre.ethers.getContractFactory("Presale");
  const presale = await Presale.deploy(await testToken.getAddress());
  await presale.waitForDeployment();
  console.log("Presale deployed to:", await presale.getAddress());

  const TokenFactory = await hre.ethers.getContractFactory("TokenFactory");
  const tokenFactory = await TokenFactory.deploy(await presale.getAddress());
  await tokenFactory.waitForDeployment();
  console.log("TokenFactory deployed to:", await tokenFactory.getAddress());
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});
배포 명령어:bash
npx hardhat run scripts/deploy.js --network bscTestnet
배포 결과:(테스트넷)
  • Test Token: 0x8721C944f7Eb09F81FA12527ee6f7eea4A121696
  • Presale: 0xDD95883d286150DB9781242239C2197E22305A80
  • TokenFactory: 0xd9d2231675C43a4751AE5d262Ee54bc565C50d4f
3. 프론트엔드 구현
React와 Ethers.js를 사용해 프론트엔드를 구현했습니다. 유저가 토큰을 생성할 수 있는 모달을 만들었어요.
3.1 토큰 생성 모달
frontend/src/components/CreateTokenModal.js (간략화된 버전):
javascript
 
import React, { useState } from "react";
import { ethers } from "ethers";
import TokenFactoryABI from "../abis/TokenFactory.json";

const TOKEN_FACTORY_ADDRESS = "0xd9d2231675C43a4751AE5d262Ee54bc565C50d4f";

const CreateTokenModal = ({ onClose, onCreate, signer }) => {
  const [name, setName] = useState("");
  const [symbol, setSymbol] = useState("");
  const [error, setError] = useState("");

  const handleSubmit = async () => {
    if (!signer) {
      setError("Please connect your wallet first.");
      return;
    }

    try {
      const tokenFactory = new ethers.Contract(
        TOKEN_FACTORY_ADDRESS,
        TokenFactoryABI,
        signer
      );
      const tx = await tokenFactory.createToken(name, symbol, {
        value: ethers.parseEther("0.01"),
        gasLimit: 1000000,
      });
      await tx.wait();
      onCreate({ name, symbol });
      onClose();
    } catch (err) {
      setError("Transaction failed: " + err.message);
    }
  };

  return (
    <div className="fixed inset-0 bg-black bg-opacity-70 flex items-center justify-center">
      <div className="bg-gray-800 p-6 rounded-lg w-full max-w-md shadow-xl">
        <h2 className="text-xl font-semibold mb-4">Create New Token</h2>
        {error && <div className="mb-4 p-2 bg-red-600 text-white rounded">{error}</div>}
        <div className="mb-4">
          <label className="block mb-1 text-gray-300">Token Name</label>
          <input
            type="text"
            value={name}
            onChange={(e) => setName(e.target.value)}
            className="w-full p-2 bg-gray-700 rounded border border-gray-600"
            placeholder="My Token"
          />
        </div>
        <div className="mb-4">
          <label className="block mb-1 text-gray-300">Token Symbol</label>
          <input
            type="text"
            value={symbol}
            onChange={(e) => setSymbol(e.target.value)}
            className="w-full p-2 bg-gray-700 rounded border border-gray-600"
            placeholder="MTK"
          />
        </div>
        <div className="flex justify-end space-x-3">
          <button onClick={onClose} className="bg-gray-600 px-4 py-2 rounded-lg">
            Cancel
          </button>
          <button onClick={handleSubmit} className="bg-blue-600 px-4 py-2 rounded-lg">
            Create (0.01 BNB)
          </button>
        </div>
      </div>
    </div>
  );
};

export default CreateTokenModal;
3.2 테스트
프론트엔드를 실행하고, MetaMask를 연결한 뒤 토큰 생성을 테스트했습니다.
명령어:
bash
cd frontend
npm start
  • "Create Token" 버튼 클릭 → 이름: "MyToken", 심볼: "MTK" 입력.
  • 0.01 BNB 지불 → 트랜잭션 성공 → 토큰 생성 완료.
4. 토큰 소유자 확인
TokenFactory에서 생성된 토큰은 Presale 컨트랙트로 전송됩니다. 따라서 현재 1억 토큰은 Presale 컨트랙트(0xDD95883d286150DB9781242239C2197E22305A80)가 소유하고 있습니다.
마무리
오늘은 BNB Chain Testnet에서 토큰 생성 및 프리세일 DApp의 기본 기능을 구현해봤습니다. 몇 가지 에러(예: 트랜잭션 리버트, 가스 한도 문제)를 해결하면서 많은 것을 배웠어요. 다음 단계로는 프리세일 페이지에서 유저가 토큰을 구매할 수 있도록 기능을 추가할 예정입니다.
728x90
728x90

버전이 4.x.x일때는 잘 작동되던 코드가 truffle 버전이 5.x.x 되고나서 나타나는 에러이다.

 

해결방법 1. require -> import 로 바꾸면 해결된다.

 

해결방법 2. Truffle 다운그레이드

npm uninstall -g truffle

npm install -g truffle@4.x.x

 

 

It is esm only in 5.x
You can still use a dynamic import to import it in node at least afaik, but should probably just stick to 4.x until you can migrate to esm (feature set shouldn't be much different)

 

https://github.com/chaijs/chai/issues/1561

 

ERR_REQUIRE_ESM in v5 · Issue #1561 · chaijs/chai

Hi there, I don't have all the details, as I don't have access to my workstation right now, but our CI is failing with this error for all our projects using chai when trying an upgrade to chai@5. T...

github.com

 

728x90
728x90

Solidity Docs를 보면서 공부하는데 생소한 문법들이 많이보인다. 아래 2가지정도를 위주로 공부하였다

  • 함수제어자(접근제어,상태제어,사용자 정의제어,payable제어자)
  • 예외처리

 


함수제어자(function modifier) 

위 test함수 뒤에 있는

external , view , customModifier payable 은 모두 함수제어자이다.

 

1.접근제어자 (visibility modifier)

해당 함수가 언제,어디서 호출될 수 있는지 제어한다.

 

접근제어자는 함수를 정의할때 (param) 뒤에 쓰이기도 하고 변수 앞에 쓰이기도 하는데

보통은 (param)뒤에 쓴다.

이 접근제어자는 함수나 변수에 대한 접근을 제한하는 역할을 한다.

접근제어자는 기본적으로 public , private, internal, external 등이 있다. 

 

  • public : 변수나 함수가 어디에서든 접근 가능
  • private : 오직 private이 정의된 Contract 에서만 사용이 가능(private로 정의된 Contract에 get , set함수를 만들어야 접근가능 )
  • internal : 오직 internal이 정의된 contract 안에서, 상속받은 자식 smart contract 에서 접근 가능하다. private과 유사하나 상속받은 자식 contract에서 접근이 가능하다는것이 추가되었다고 보면 된다.
  • external : 오직밖에서만 접근이 가능하다. public과 유사하나 , external이 정의된 contract내에서는 사용이 불가능 

 

2. 상태 제어자(state modifier)

블록체인과 상호작용 하는 방법에 대한 것이다.

 

  • view, constant : 해당 함수를 실행해도 어떤 데이터도 저장/변경되지 않음
  • pure : 해당함수가 어떤 데이터도 블록체인에 저장하지 않을뿐만아니라, 블록체인으로부터 어떤 데이터도 읽지않는다.

 

3. 사용자 정의 제어자

직접 원하는 논리대로 제어자를 정의하여 함수에 적용할 수 있다.

 

// 메시지를 보낸 사람이 owner와 같은지 확인하는 제어자 정의

modifier onlyOwner () {
	require(msg.sender == owner);
  	_; // _;와 항상 함께 쓰인다.
}

 

require 는 예외처리이다. 예외처리는 아래와 같다. 

  • require : 조건이 false 에러를 발생하고 gas를 환불해줌
  • revert : 조건없이 에러를 발생시키고 gas를 환불
  • assert : gas를 다소비한 후 조건이 false일때 에러를 발생 

4. payable 제어자 

함수 호출을 통해 이더리움을 받을 수있는 특별한 함수 유형을 정의한다.

함수가 실행되는 동시에 컨트랙트 계정(CA)에 돈을 지불할 수 있다. payable을 작성하지 않은 함수에서는 이더를 보낼 수 없다.

 

contract OnlineStore {
  function buySomething() external payable {
    require(msg.value == 0.001 ether);
    transferThing(msg.sender);
  }
}

 

위 코드를 정리해 보면 

1. OnlineStore라는 Contract를 정의하고

2. buySomething()라는 함수를 정의하고 함수제어자로 external(external로 정의된 함수 밖에서만 접근가능)이고 

payable라는 함수제어자가 있다.

3. buySomething()는 msg.value(이 함수를 호출할 떄 보낸 wei(이더)의 양을 나타낸다) 가 0.001이더가 아닐 경우 종료한다. 참일경우(value == 0.001eth) 이더를 전송한다.


 

msg 는 솔리디티에서 제공하는 전역변수로서 현재 함수호출에 대한 정보를 담고있다. 

msg.sender 현재 함수를 호출한 주소를 나타냅니다.
msg.value 이더리움 네트워크에서, 이 함수를 호출할 때 보낸 wei의 양을 나타냅니다.
msg.data 함수를 호출할 때 보낸 데이터를 나타냅니다.
msg.gas 함수를 호출할 때 사용할 수 있는 가스의 양을 나타냅니다. (v0.4.21 이후로는 더 이상 사용되지 않습니다.)
msg.sig 함수를 호출할 때 사용한 함수 서명을 나타냅니다.
728x90
728x90

 

Solidity는 이더리움 가상머신을 대상으로 설계된 언어이며 Smart Contrat  을 구현하기 위한 언어이다.

Solidity을 이해하려면 Smart Contrat 이 무엇인지 먼저 알아야되고, Smart Contrat  을 이해하기 위해서는 블록체인이 뭔지 알아야된다. (추가로 EVM 까지...)

 

도움받은 자료(블록체인과 스마트컨트랙을 이해하는데 도움이 됨. 본인은 사실 코인투자를 해왔기에 이미 두루뭉실하게 알고있었음) 

1.  https://www.dbpia.co.kr/journal/articleDetail?nodeId=NODE10531916

 

블록체인 기술과 활용 연구 | DBpia

이새봄, 박아름, 송재민 | 한국컴퓨터정보학회논문지 | 2021.2

www.dbpia.co.kr

2. https://goldsystem.tistory.com/347

 

스마트컨트랙 제대로 이해하기

정의 스마트 계약 또는 스마트 컨트랙이란 블록체인 기반으로 금융거래, 부동산 계약, 공증 등 다양한 형태의 계약을 체결하고 이행하는 것을 말합니다. 블록체인 2.0이라고도 합니다. Hashgraph에

goldsystem.tistory.com


Solidity 언어 특징

  • 절차적 프로그래밍 언어(C++ , 자바스크립트, 자바)
  • 정적타입 (자바와 비슷) : 컴파일 시에 타입이 결정, 오류가 있다면 컴파일 에러 발생
  • 객체 지향언어
  • 확장자 : .sol 

예제 코드 

실습환경 : https://remix.ethereum.org/ 

Compiler : 0.8.24

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;

contract SimpleStorage {
    uint storedData;

    function set(uint x) public {
        storedData = x;
    }

    function get() public view returns (uint) {
        return storedData;
    }
}

SPDX-LIcense-Identifier

스마트 컨트랙트에 대한 신뢰를 높이고, 저작권 문제를 해소하기 위해 코드 최상단에 꼭 SPDX라이센스를 주석으로

명시해야된다. 

https://spdx.org/licenses/

 

SPDX License List | Software Package Data Exchange (SPDX)

SPDX License List The SPDX License List is an integral part of the SPDX Specification. The SPDX License List itself is a list of commonly found licenses and exceptions used in free and open or collaborative software, data, hardware, or documentation. The S

spdx.org

 

SPDX(Software Packgae Data Exchange)는 소프트웨어 명세서에 대한 개방형 표준

SPDX는 소프트웨어와 관련된 구성요소,라이센스,저작권,보안 참조 및 기타 메타데이터의 표현을 허용하며

원래목적은 라이센스 준수를 개선하는 것이었고 이후 공급망 투명성과 같은 추가 사용사례를 촉진하기 위해 확장됨


Pragma

컴파일러의 특정 기능을 활성화 하는데 사용됨

위 예제코드에서는 solidity 버전을 0.4.16 ~ 0.9.0을 사용한다고 설정 


Contrat

객체지향언어(Java)에서 class와 유사 위 예제코드에서는 Contrat SimpleStorage를 만들고, 이 Contract 안에는

storedData라는 상태변수(storage에 저장되어있는 데이터)와  set과 get의 함수들이 정의되어있다.


상태변수(State Variables)

Contract 내부에서 선언된 변수 중 function 외부에서 선언된 변수를 상태변수(state Variables)라고 함

contract storage에 저장됨 -> 모든 function이 접근가능, function이 변경한 값은 계속해서 저장됨,,

 

1. Storage : 블록체인에 기록되는 영구적으로 값이 유지되는 데이터 영역

2. Memory : 프로그램이 동작되는 동안에만 값이 저장되고, 종료되는 값이 사라짐


값형 데이터 타입 (Value types)

  • Boolean
    true(default)/false
  • 정수(int, uint)
    signed integers: 부호 있음
    unsigned integer: 부호 없음
    8bit ~ 256bit까지 지원 (예: int8: -128 ~ 127, uint16: ~65,535)
  • Byte
    데이터를 바이너리 형태로 저장하기 위해 byte타입을 지원함.
    bytes1 ~ bytes32까지 정해져 있음.
    크기가 정해져 있기 때문에 fixed-sized byte array라고도 불림.
    정해진 바이트 크기와 다르면 에러가 남.
  • cf.) bytes 타입: dynamically sized byte array - 참조형(reference) 타입임
  • 주소(Address)
    20byte 크기의 이더리움 어드레스
    balance property를 갖고 있어 이더 잔액을 확인할 수 있음.
    이더를 옮기는 transfer, send 메소드 지원(address.transfer(amount), address.send(amount))
  • 열거형 (Enumerations)
    내부적으로는 정수 값으로 구현됨.

 

참조

.https://velog.io/@mae-zung/Solidity-%EA%B8%B0%EB%B3%B8-%EB%AC%B8%EB%B2%95

 

.https://docs.soliditylang.org/en/v0.8.24/introduction-to-smart-contracts.html#simple-smart-contract

 

.https://goldsystem.tistory.com/347

 

728x90

+ Recent posts