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)