Token Vendor

김현학·2024년 6월 25일
0

speedrun-ethereum

목록 보기
4/5
post-thumbnail

챌린지를 수행하며 기억에 남은 내용만 간단히 회고한다.

Contract

계약 자체는 @openzeppelin 라이브러리를 임포트하여 간편하게 작성했다.

  1. ERC20 스펙에 맞춰 토큰을 생성
    • decimals()는 소수점 거래를 위한 유효숫자 정의: 기본 18 (e.g. wei)
pragma solidity 0.8.4; //Do not change the solidity version as it negativly impacts submission grading
// SPDX-License-Identifier: MIT

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

contract YourToken is ERC20 {
	uint256 private ONE_ETHER_IN_WEI = 10 ** decimals();

	constructor() ERC20("Gold", "GLD") {
		_mint(msg.sender, 1000 * ONE_ETHER_IN_WEI);
	}
}
  1. 제한된 수량의 토큰을 매매할 수 있는 Vendor 계약 생성
    • ETH:GLD 비율 1:100으로 매매
pragma solidity 0.8.4; //Do not change the solidity version as it negativly impacts submission grading
// SPDX-License-Identifier: MIT

import "@openzeppelin/contracts/access/Ownable.sol";
import "./YourToken.sol";

interface IVendor {
	function buyTokens() external payable;

	function withdraw() external;

	function sellTokens(uint256 _amount) external;
}

contract Vendor is IVendor, Ownable {
	event BuyTokens(address buyer, uint256 amountOfETH, uint256 amountOfTokens);
	event SellTokens(
		address seller,
		uint256 amountOfTokens,
		uint256 amountOfETH
	);

	YourToken public yourToken;

	constructor(address tokenAddress) {
		yourToken = YourToken(tokenAddress);
	}

	function buyTokens() external payable override {
		uint256 amountOfTokens = msg.value * tokensPerEth();
		yourToken.transfer(msg.sender, amountOfTokens);
		emit BuyTokens(msg.sender, msg.value, amountOfTokens);
	}

	function withdraw() external override {
		require(msg.sender == owner(), "Only the owner can withdraw");
		payable(owner()).transfer(address(this).balance);
	}

	function sellTokens(uint256 _amount) external override {
		yourToken.transferFrom(msg.sender, address(this), _amount);
		uint256 amountOfTokens = _amount / tokensPerEth();
		payable(msg.sender).transfer(amountOfTokens);
		emit SellTokens(msg.sender, _amount, amountOfTokens);
	}

	function tokensPerEth() public pure returns (uint256) {
		return 100;
	}
}
  1. 계약 배포
    YourToken : 0x41f50A95Ac5b173FF0d100DcfC7Ff3338b3478b3
    Vendor : 0x7b14ccc3719084D55A3435B947184ac084a14a8d
❯ yarn deploy
Nothing to compile
No need to generate any newer typings.
deploying "YourToken" (tx: 0xf650b921837e178c72495168230d6977926e5f2405258b11b8dbd78d15c7a5af)...: deployed at 0x41f50A95Ac5b173FF0d100DcfC7Ff3338b3478b3 with 642328 gas
deploying "Vendor" (tx: 0xd2757cfcad443b7c0547bb19096849dfc5ae15634f6dd22d8e598b9839e1991a)...: deployed at 0x7b14ccc3719084D55A3435B947184ac084a14a8d with 435777 gas
📝 Updated TypeScript contract definition file on ../nextjs/contracts/deployedContracts.ts
  1. 배포된 YourTokenVendor 계약의 주소를 파악하고, yarn hardhat-verify <Vendor-Address> "<YourToken-Address>"로 검증
yarn hardhat-verify 0x7b14ccc3719084D55A3435B947184ac084a14a8d "0x41f50A95Ac5b173FF0d100DcfC7Ff3338b3478b3"
  1. Verified Contracts

Frontend: React

이번에는 프론트엔드 코드에 대한 내용도 다뤘다.

ethers 라이브러리를 통해, 계약에는 명시되지 않지만, 배포 이후에 수행할 작업을 수정했다.

import { HardhatRuntimeEnvironment } from "hardhat/types";
import { DeployFunction } from "hardhat-deploy/types";
import { Contract } from "ethers";
import { Vendor } from "../typechain-types";

/**
 * Deploys a contract named "Vendor" using the deployer account and
 * constructor arguments set to the deployer address
 *
 * @param hre HardhatRuntimeEnvironment object.
 */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const deployVendor: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {

  // Deploy Vendor
  const { deployer } = await hre.getNamedAccounts();
  const { deploy } = hre.deployments;
  const yourToken = await hre.ethers.getContract<Contract>("YourToken", deployer);
  const yourTokenAddress = await yourToken.getAddress();
  await deploy("Vendor", {
    from: deployer,
    // Contract constructor arguments
    args: [yourTokenAddress],
    log: true,
    // autoMine: can be passed to the deploy function to make the deployment process faster on local networks by
    // automatically mining the contract deployment transaction. There is no effect on live networks.
    autoMine: true,
  });
  const vendor = await hre.ethers.getContract<Contract>("Vendor", deployer);
  const vendorAddress = await vendor.getAddress();
  // Transfer tokens to Vendor
  await yourToken.transfer(vendorAddress, hre.ethers.parseEther("1000"));
  // Transfer contract ownership to your frontend address
  await vendor.transferOwnership(process.env.ADMIN_ADDRESS!);
};

export default deployVendor;

마지막 부분의 두 줄이 핵심인데, 생성해둔 1000개의 토큰은 Vendor로 옮기고, Vendor의 소유권을 임의의 운영자 계정으로 옮겨서 eth 인출이 가능하도록 한다.

await yourToken.transfer(vendorAddress, hre.ethers.parseEther("1000"));
await vendor.transferOwnership(process.env.ADMIN_ADDRESS!);

0개의 댓글