반응형
# 계약 작성 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)반응형