DEV Community

hideckies
hideckies

Posted on • Originally published at blog.hdks.org

ERC20 Token with Vyper and Brownie

When implementing smart contracts, I've used Solidity and OpenZeppelin.

I knew about Vyper, so I wanted to use it someday.

And now is the time.

As I read through the official documentation, it seems that it's easier to develop and test using a framework called Brownie so I use it.

0. Requirements

  • Python3.6 and higher

1. Install Brownie

Before installing Brownie, we need ganache-cli or we should face an error when running test.

npm i -g ganache-cli 
Enter fullscreen mode Exit fullscreen mode

Then, install it with pipx.

python3 -m pip install pipx pipx install eth-brownie 
Enter fullscreen mode Exit fullscreen mode

2. Create a new project with Brownie

mkdir sample cd sample brownie init 
Enter fullscreen mode Exit fullscreen mode

After that, you should see that some directories such as contracts/, tests/ are created in the sample/ directory.

Next, we need to prepare a Python virtual environment in sample directory to install Vyper.

# in sample/ python3 -m venv venv source venv/bin/activate 
Enter fullscreen mode Exit fullscreen mode

3. Install Vyper

Make sure you are in a virtual environment, then do the following:

pip install vyper 
Enter fullscreen mode Exit fullscreen mode

4. Create a ERC20 smart contract using Vyper

Create a new file named SampleToken.vy in contracts/ directory.

Then implement a smart contract while referring to the Vyper ERC20 example.

sample/contracts/SampleToken.vy

# @version ^0.3.0  from vyper.interfaces import ERC20 from vyper.interfaces import ERC20Detailed implements: ERC20 implements: ERC20Detailed event Transfer: sender: indexed(address) receiver: indexed(address) value: uint256 event Approval: owner: indexed(address) spender: indexed(address) value: uint256 name: public(String[64]) symbol: public(String[32]) decimals: public(uint8) balanceOf: public(HashMap[address, uint256]) allowance: public(HashMap[address, HashMap[address, uint256]]) totalSupply: public(uint256) minter: address @external def __init__(_name: String[64], _symbol: String[32], _decimals: uint8, _supply: uint256): init_supply: uint256 = _supply * 10 ** convert(_decimals, uint256) self.name = _name self.symbol = _symbol self.decimals = _decimals self.balanceOf[msg.sender] = init_supply self.totalSupply = init_supply self.minter = msg.sender log Transfer(ZERO_ADDRESS, msg.sender, init_supply) @external def transfer(_to : address, _value : uint256) -> bool: self.balanceOf[msg.sender] -= _value self.balanceOf[_to] += _value log Transfer(msg.sender, _to, _value) return True @external def transferFrom(_from : address, _to : address, _value : uint256) -> bool: self.balanceOf[_from] -= _value self.balanceOf[_to] += _value self.allowance[_from][msg.sender] -= _value log Transfer(_from, _to, _value) return True @external def approve(_spender : address, _value : uint256) -> bool: self.allowance[msg.sender][_spender] = _value log Approval(msg.sender, _spender, _value) return True @external def mint(_to: address, _value: uint256): assert msg.sender == self.minter assert _to != ZERO_ADDRESS self.totalSupply += _value self.balanceOf[_to] += _value log Transfer(ZERO_ADDRESS, _to, _value) @internal def _burn(_address: address, _value: uint256): assert _address != ZERO_ADDRESS self.totalSupply -= _value self.balanceOf[_address] -= _value log Transfer(_address, ZERO_ADDRESS, _value) @external def burn(_value: uint256): self._burn(msg.sender, _value) @external def burnFrom(_address: address, _value: uint256): self.allowance[_address][msg.sender] -= _value self._burn(_address, _value) 
Enter fullscreen mode Exit fullscreen mode

5. Create Unit Tests with Brownie

Create a new file named test_sampletoken.py in tests/ directory.

The filename prefix/postfix must be "test_*.py" or "*_test.py".

In addition, please note that this is a .py file, not .vy.

sample/tests/test_sampletoken.py

import brownie import pytest INIT_NAME = "SampleToken" INIT_SYMBOL = "ST" INIT_DECIMALS = 18 INIT_SUPPLY = 1000 @pytest.fixture def sampletoken_contract(SampleToken, accounts): yield SampleToken.deploy(INIT_NAME, INIT_SYMBOL, INIT_DECIMALS, INIT_SUPPLY, {'from': accounts[0]}) def test_initial_state(sampletoken_contract): assert sampletoken_contract.name() == INIT_NAME assert sampletoken_contract.symbol() == INIT_SYMBOL assert sampletoken_contract.decimals() == INIT_DECIMALS assert sampletoken_contract.totalSupply() == INIT_SUPPLY * 10 ** INIT_DECIMALS def test_transfer(sampletoken_contract, accounts): values = 1000 sampletoken_contract.transfer(accounts[1], values, {'from': accounts[0]}) assert sampletoken_contract.balanceOf(accounts[1]) == values def test_transferFrom(sampletoken_contract, accounts): values1 = 1000 sampletoken_contract.transfer(accounts[1], values1, {'from': accounts[0]}) values2 = 500 sampletoken_contract.approve(accounts[0], values2, {'from': accounts[1]}) sampletoken_contract.transferFrom(accounts[1], accounts[2], values2, {'from': accounts[0]}) assert sampletoken_contract.balanceOf(accounts[2]) == values2 def test_mint(sampletoken_contract, accounts): with brownie.reverts(): sampletoken_contract.mint(accounts[2], 1000, {'from': accounts[1]}) sampletoken_contract.mint(accounts[1], 1000, {'from': accounts[0]}) assert sampletoken_contract.balanceOf(accounts[1]) == 1000 def test_burn(sampletoken_contract, accounts): burned_value = 1000 sampletoken_contract.burn(burned_value, {'from': accounts[0]}) assert sampletoken_contract.totalSupply() < INIT_SUPPLY * 10 ** INIT_DECIMALS def test_burnFrom(sampletoken_contract, accounts): sampletoken_contract.transfer(accounts[1], 1000, {'from': accounts[0]}) burned_value = 500 sampletoken_contract.approve(accounts[0], burned_value, {'from': accounts[1]}) sampletoken_contract.burnFrom(accounts[1], burned_value, {'from': accounts[0]}) 
Enter fullscreen mode Exit fullscreen mode

6. Test

Finally, you can test it.

brownie test 
Enter fullscreen mode Exit fullscreen mode

If there are no errors, this ERC20 token is fine. Maybe.

Top comments (0)