12-1. MyToken.vy 완성

동동주·2025년 11월 17일


Todo :
1. Mint 함수 구현
2. 이벤트 구현
3. modifier 기능 구현




1. Mint 함수 구현

test/MyToken.ts

describe("Mint", () => {
    // 1MT = 1*(10^18) = 1n*10n**18n = BigInt(1*10**18)
    it("should retrun initial supply + 1MT balance for signer 0", async () => {
      const signer0 = signers[0];
      const oneMt = hre.ethers.parseUnits("1", DECIMALS);
      await myTokenC.mint(oneMt, signer0.address);
      expect(await myTokenC.balanceOf(signer0.address)).equal(
        MINTING_AMOUNT * 10n ** DECIMALS + oneMt,
      );
    });
  ...
});

기존의 "should retrun 100 totalSupply"와 동일하던 테스트코드를
"should retrun initial supply + 1MT balance for signer 0"로 변경하여 mint함수의 작동을 확인하는 테스트코드 작성

contracts/MyToken.vy

@internal
def _mint(_amount: uint256, _to: address):
    self.balanceOf[_to] += _amount
    self.totalSupply += _amount

@external
def mint(_amount: uint256, _to: address):
    self._mint(_amount, _to)

mint함수 추가.

+@) vyper의 typechain

vyper는 typechain이 자동으로 생기지 않음

❯ npx hardhat compile 
// → artifacts 생성됨
❯ npx hardhat typechain 
// → typechain-types 생성됨



2. 이벤트 구현

contracts/MyToken.vy

# @version ^0.3.0
# @license MIT

event Transfer:
    owner: indexed(address) #from은 예약어
    to: indexed(address)
    amount: uint256

event Approval:
    # 원래는 owner: address 까지 적어주는 것 권장 (msg.sender로 사용할 수 있지만)
    spender: indexed(address)
    amount: uint256



name: public(String[64])
symbol: public(String[32])
decimals: public(uint256)
totalSupply: public(uint256)
#바이퍼만 스네이크 대신 낙타 사용..

balanceOf: public(HashMap[address, uint256])
allowances: public(HashMap[address, HashMap[address, uint256]])
 
@external
def __init__(_name: String[64], _symbol: String[32], _decimals: uint256, _initialSupply: uint256):
    self.name = _name
    self.symbol = _symbol
    self.decimals = _decimals
    self.totalSupply = _initialSupply * 10 ** 18
    self.balanceOf[msg.sender] += _initialSupply * 10 ** 18

  
@external
def transfer(_amount:uint256, _to:address):
    assert self.balanceOf[msg.sender] >= _amount, "insufficient balance" #require 역할
    self.balanceOf[msg.sender] -= _amount
    self.balanceOf[_to] += _amount
    #emit이던게 log로 변함
    log Transfer(msg.sender, _to, _amount)

@external
def approve( _spender:address, _amount:uint256):
    # assert self.balanceOf[_owner] >= _amount, "insuffient balance"
    self.allowances[msg.sender][_spender] += _amount

    log Approval(_spender, _amount)

@external
def transferFrom(_owner:address, _to:address, _amount:uint256):
    assert self.allowances[_owner][msg.sender] >= _amount, "insufficient allowance"
    assert self.balanceOf[_owner] >= _amount, "insufficient balance"
    self.balanceOf[_owner] -= _amount
    self.balanceOf[_to] += _amount
    self.allowances[_owner][msg.sender] -= _amount

    log Transfer(_owner, _to, _amount)

@internal
def _mint(_amount: uint256, _to: address):
    self.balanceOf[_to] += _amount
    self.totalSupply += _amount
    #zero address in solidity : address(0)
    log Transfer(ZERO_ADDRESS, _to, _amount)

@external
def mint(_amount: uint256, _to: address):
    self._mint(_amount, _to)

추가한 내용

event 정의

event EventName:
    name: type
    name: indexed(type) //인덱스를 할 경우

형태로 사용한다.
+ from이 import할 때 사용되는 예약어라서 owner로 바꿔서 사용했다.

event 발생

solidity 에서 사용하던 emit 대신
vyper에서는 log를 사용하여

log Transfer(ZERO_ADDRESS, _to, _amount)

형태로 사용한다.

+ "zero address" 사용은
solidity에서 address(0)로 사용한 것과 달리
vyper에서는 ZERO_ADDRESS를 사용한다.

zero address란? 참고 :
- stackoverflow - What is address(0) in Solidity
- metaschool - What is Address(0) in Solidity




3. modifier 기능 구현

>> vyper에는 modifier가 없다....! <<

그래서 직접 함수로 구현해줘야 한다...!

contracts/MyToken.vy

# @version ^0.3.0
# @license MIT

event Transfer:
    owner: indexed(address) #from은 예약어
    to: indexed(address)
    amount: uint256

event Approval:
    # 원래는 owner: address 까지 적어주는 것 권장 (msg.sender로 사용할 수 있지만)
    spender: indexed(address)
    amount: uint256

#추가
owner: address
manager: address

name: public(String[64])
symbol: public(String[32])
decimals: public(uint256)
totalSupply: public(uint256)
#바이퍼만 스네이크 대신 낙타 사용..

balanceOf: public(HashMap[address, uint256])
allowances: public(HashMap[address, HashMap[address, uint256]])
 
@external
def __init__(_name: String[64], _symbol: String[32], _decimals: uint256, _initialSupply: uint256):
    self.name = _name
    self.symbol = _symbol
    self.decimals = _decimals
    self.totalSupply = _initialSupply * 10 ** 18
    self.balanceOf[msg.sender] += _initialSupply * 10 ** 18
    #추가
    self.owner = msg.sender
    self.manager = msg.sender

# 함수 호출 시 함수 정의 순서 중요 
@internal
def onlyOwner(_owner: address):
    assert self.owner == _owner, "You are not authorized"
    
@internal
def onlyManager(_manager: address):
    assert self.manager == _manager, "You are not authorized to manage this contract"

  
@external
def transfer(_amount:uint256, _to:address):
    assert self.balanceOf[msg.sender] >= _amount, "insufficient balance" #require 역할
    self.balanceOf[msg.sender] -= _amount
    self.balanceOf[_to] += _amount
    #emit이던게 log로 변함
    log Transfer(msg.sender, _to, _amount)

@external
def approve( _spender:address, _amount:uint256):
    # assert self.balanceOf[_owner] >= _amount, "insuffient balance"
    self.allowances[msg.sender][_spender] += _amount

    log Approval(_spender, _amount)

@external
def transferFrom(_owner:address, _to:address, _amount:uint256):
    assert self.allowances[_owner][msg.sender] >= _amount, "insufficient allowance"
    assert self.balanceOf[_owner] >= _amount, "insufficient balance"
    self.balanceOf[_owner] -= _amount
    self.balanceOf[_to] += _amount
    self.allowances[_owner][msg.sender] -= _amount

    log Transfer(_owner, _to, _amount)

@internal
def _mint(_amount: uint256, _to: address):
    self.balanceOf[_to] += _amount
    self.totalSupply += _amount
    #zero address in solidity : address(0)
    log Transfer(ZERO_ADDRESS, _to, _amount)

@external
def mint(_amount: uint256, _to: address):
    self.onlyManager(msg.sender)
    self._mint(_amount, _to)



@external
def setManager(_manager: address):
    self.onlyOwner(msg.sender)
    self.manager = _manager
    

# msg.sender is not allowed in internal functions
# Function does not exist or has not been declared

코드 흐름

  • 일단 ownermanager 주소를 선언하고, 생성자에서 msg.sender로 할당해준다.

  • onlyOwner, onlyManager 함수를 만들어준다
    내부에는 assert (require)로 호출자가 owner인지 확인해준다.

* 이 때,

assert self.owner == msg.sender, "~~"

와 같이 msg.sender 를 바로 함수 내부에 사용하면
msg.sender is not allowed in internal functions
오류가 난다.
internal 함수에서는 msg.sender에 접근할 수 없기에 (vyper 특성)
msg.sender를 매개변수로 받는다.
( 이후 사용 시 self.onlyManager(msg.sender) 처럼 사용)

* 또한, vyper는 함수의 선언 위치에 따라 사용에 제한이 있다.
함수가 사용되는 곳보다 나중에 함수가 선언되면 사용이 불가능하다.
(아래와 같은 오류가 남)
Function does not exist or has not been declared
따라서 onlyOwner, onlyManager 함수를 생성자 밑으로 가져와줬다.

  • mint()와 setManager() 함수에 각각 self.onlyManager(msg.sender)self.onlyOwner(msg.sender)를 추가해준다.



이렇게 수정을 마치면
MyToken.ts의 테스트는 모두 통과하는 것을 볼 수 있다.

MyToken.ts 테스트결과(통과)




다음에 할 내용 :

TinyBank를 vyper로 만들어본다.

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

0개의 댓글