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
Then, install it with pipx.
python3 -m pip install pipx
pipx install eth-brownie
2. Create a new project with Brownie
mkdir sample
cd sample
brownie init
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
3. Install Vyper
Make sure you are in a virtual environment, then do the following:
pip install vyper
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)
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]})
6. Test
Finally, you can test it.
brownie test
If there are no errors, this ERC20 token is fine. Maybe.
Top comments (0)