본문 바로가기
IT/JavaScript(TypeScript)

Stable Coin 실습 - Oracle 과 담보 이더로 코인 발행

by 가능성1g 2025. 9. 23.
반응형

# 계약 작성  contracts/MyStablecoin.sol

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

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

// ReentrancyGuard는 재진입 공격이라는 해킹을 방지하는 보안 장치입니다.
contract MyStablecoin is ERC20, Ownable, ReentrancyGuard {

    // --- 상태 변수 ---
    uint256 public constant COLLATERAL_RATIO = 150; // 담보 비율 150%
    uint256 public ethUsdPrice; // 1 ETH가 몇 USD인지 (오라클이 업데이트)

    // 각 사용자가 담보로 맡긴 이더리움(ETH)의 양을 기록하는 장부
    mapping(address => uint256) public collateral;

    // --- 이벤트 ---
    event PriceUpdated(uint256 newPrice);
    event Minted(address indexed user, uint256 collateralAmount, uint256 mintAmount);
    event Burned(address indexed user, uint256 collateralAmount, uint256 burnAmount);

    // --- 함수 ---
    constructor(
        address initialOwner,
        uint256 _initialPrice
    ) ERC20("My Stablecoin", "MSC") Ownable(initialOwner) {
        ethUsdPrice = _initialPrice; // 계약 생성 시 초기 ETH 가격 설정
    }

    /**
     * @dev [소유자 전용] 오라클 역할을 하여 ETH 가격을 업데이트합니다.
     */
    function updatePrice(uint256 _newPrice) public onlyOwner {
        ethUsdPrice = _newPrice;
        emit PriceUpdated(_newPrice);
    }

    /**
     * @dev ETH를 담보로 맡기고 MSC를 발행(mint)합니다.
     * nonReentrant는 재진입 공격 방지 장치입니다.
     */
    function depositAndMint(uint256 _mintAmount) public payable nonReentrant {
        require(_mintAmount > 0, "Must mint at least some tokens");
        
        // 사용자가 맡긴 ETH의 총 가치 계산
        uint256 collateralValue = (collateral[msg.sender] + msg.value) * ethUsdPrice / 1e18;
        // 사용자가 발행한 MSC의 총 가치 계산
        uint256 mintedValue = (balanceOf(msg.sender) + _mintAmount) / 1e18;

        // 요구되는 담보 비율을 충족하는지 확인
        require(collateralValue * 100 / mintedValue >= COLLATERAL_RATIO, "Collateral ratio not met");

        collateral[msg.sender] += msg.value; // 담보 기록
        _mint(msg.sender, _mintAmount); // MSC 발행
        emit Minted(msg.sender, msg.value, _mintAmount);
    }

/**
     * @dev MSC를 소각하고 담보로 맡긴 ETH를 돌려받습니다. (수정된 버전)
     */
    function burnAndWithdraw(uint256 _burnAmount) public nonReentrant {
        require(_burnAmount > 0, "Must burn at least some tokens");
        require(balanceOf(msg.sender) >= _burnAmount, "Insufficient MSC balance");

        // --- BUG FIX ---
        // 돌려줄 담보(ETH) 양을 올바르게 계산합니다.
        // (소각할 MSC 가치 * 담보비율) / ETH 현재 가격
        uint256 withdrawAmount = (_burnAmount * COLLATERAL_RATIO) / (ethUsdPrice * 100);
        // --- END FIX ---
        
        require(collateral[msg.sender] >= withdrawAmount, "Insufficient collateral to withdraw");

        // 소각 후에도 남은 MSC가 담보 비율을 충족하는지 확인
        uint256 remainingMsc = balanceOf(msg.sender) - _burnAmount;
        if (remainingMsc > 0) {
            uint256 remainingCollateral = collateral[msg.sender] - withdrawAmount;
            uint256 collateralValue = remainingCollateral * ethUsdPrice / 1e18;
            uint256 remainingMscValue = remainingMsc / 1e18;
            require(collateralValue * 100 / remainingMscValue >= COLLATERAL_RATIO, "Collateral ratio not met after burn");
        }

        _burn(msg.sender, _burnAmount); // MSC 소각
        collateral[msg.sender] -= withdrawAmount; // 담보 기록 차감
        payable(msg.sender).transfer(withdrawAmount); // ETH 전송

        emit Burned(msg.sender, withdrawAmount, _burnAmount);
    }
}

 

바뀐 계약에 따라 deploy.ts 수정

import { ethers } from "hardhat";

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

  // 1 ETH = $3000 로 초기 가격 설정
  const initialPrice = 3000n; 

  const myStablecoinFactory = await ethers.getContractFactory("MyStablecoin");
  
  // 배포 시 소유자와 초기 가격을 전달합니다.
  const myStablecoin = await myStablecoinFactory.deploy(
    deployer.address,
    initialPrice
  );

  await myStablecoin.waitForDeployment();

  const contractAddress = await myStablecoin.getAddress();
  console.log(`MyStablecoin deployed to: ${contractAddress}`);
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});
# 노드실행
npx hardhat node

# 다른터미널에서.. 컴파일 및 배포 그리고 콘솔 진입
# 배포시 계약 주소는 메모해 둘것
npx hardhat compile
npx hardhat run scripts/deploy.ts --network localhost
npx hardhat console --network localhost

 

# 콘솔에서 진행!
// 테스트에 사용할 계정들을 가져옵니다. owner는 소유자, user1은 일반 사용자입니다.
const [owner, user1] = await ethers.getSigners()

// 스마트 계약과 연결합니다.
const MyStablecoin = await ethers.getContractFactory("MyStablecoin")
const stablecoin = await MyStablecoin.attach("새로운_계약_주소")
const stablecoin = await MyStablecoin.attach("0x5FbDB2315678afecb367f032d93F642f64180aa3")


// 원래 이더 지갑 확인
await ethers.provider.getBalance(user1.address)

// stablecoin 계약을 user1이 호출하도록 연결하고, 1 ETH를 보내면서 1000 MSC를 민팅합니다.
await stablecoin.connect(user1).depositAndMint(ethers.parseUnits("1000", 18), { value: ethers.parseEther("1.0") })

//잔액 확인
ethers.formatUnits(await stablecoin.balanceOf(user1.address), 18)

//담보잔액
ethers.formatEther(await stablecoin.collateral(user1.address))

//반환후 다시 담보 받기
await stablecoin.connect(user1).burnAndWithdraw(ethers.parseUnits("500", 18))

// 잔액확인
ethers.formatEther(await stablecoin.collateral(user1.address))

// 원래 이더 지갑도 확인
await ethers.provider.getBalance(user1.address)
반응형