├── requirements.txt ├── test ├── constants.py ├── unitests │ └── test_socks.py └── conftest.py ├── .gitignore ├── README.md └── contracts └── socks.vy /requirements.txt: -------------------------------------------------------------------------------- 1 | vyper==0.1.0b9 2 | web3==5.0.0b2 3 | eth-tester[py-evm]==0.1.0b39 4 | pytest>=3.6 5 | -------------------------------------------------------------------------------- /test/constants.py: -------------------------------------------------------------------------------- 1 | ZERO_ADDR = '0x0000000000000000000000000000000000000000' 2 | DECIMALS = 10**18 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | *.pyc 3 | *.pyo 4 | .DS_Store 5 | .cache 6 | .pytest_cache/ 7 | env/ 8 | vyper/ 9 | __init__.py 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Installation: 2 | 3 | #### Requires [Python 3](https://www.python.org/download/releases/3.0/) 4 | 5 | 1) Clone Uniswap 6 | ``` 7 | $ git clone https://github.com/Uniswap/unisocks-erc20 8 | $ cd unisocks-erc20 9 | ``` 10 | 11 | 2) Setup environment 12 | ``` 13 | $ python3 -m venv env 14 | $ source env/bin/activate 15 | ``` 16 | 17 | 3) Install dependencies 18 | ``` 19 | pip install -r requirements.txt 20 | ``` 21 | 22 | 4) Run tests 23 | ``` 24 | $ pytest -v test/ 25 | ``` 26 | -------------------------------------------------------------------------------- /test/unitests/test_socks.py: -------------------------------------------------------------------------------- 1 | from test.constants import ( 2 | DECIMALS, 3 | ) 4 | 5 | def test_init(w3, SOCKS): 6 | a0, a1 = w3.eth.accounts[:2] 7 | assert SOCKS.name() == 'Unisocks Edition 0' 8 | assert SOCKS.symbol() == 'SOCKS' 9 | assert SOCKS.decimals() == 18 10 | assert SOCKS.totalSupply() == 500*DECIMALS 11 | assert SOCKS.balanceOf(a0) == 500*DECIMALS 12 | assert SOCKS.balanceOf(a1) == 0 13 | 14 | def test_transfer(w3, SOCKS): 15 | a0, a1 = w3.eth.accounts[:2] 16 | SOCKS.transfer(a1, 1*10**18, transact={}) 17 | assert SOCKS.balanceOf(a0) == 500*DECIMALS - 1*DECIMALS 18 | assert SOCKS.balanceOf(a1) == 1*DECIMALS 19 | 20 | def test_transferFrom(w3, SOCKS): 21 | a0, a1, a2 = w3.eth.accounts[:3] 22 | assert SOCKS.allowance(a0, a1) == 0 23 | SOCKS.approve(a1, 1*DECIMALS, transact={}) 24 | assert SOCKS.allowance(a0, a1) == 1*DECIMALS 25 | SOCKS.transferFrom(a0, a2, 1*DECIMALS, transact={'from': a1}) 26 | assert SOCKS.allowance(a0, a1) == 0 27 | assert SOCKS.balanceOf(a0) == 500*DECIMALS - 1*DECIMALS 28 | assert SOCKS.balanceOf(a1) == 0 29 | assert SOCKS.balanceOf(a2) == 1*DECIMALS 30 | 31 | def test_burn(w3, SOCKS): 32 | a0, a1, a2 = w3.eth.accounts[:3] 33 | SOCKS.burn(1*10**18, transact={}) 34 | assert SOCKS.balanceOf(a0) == 500*DECIMALS - 1*DECIMALS 35 | assert SOCKS.totalSupply() == 500*DECIMALS - 1*DECIMALS 36 | 37 | def test_burnFrom(w3, SOCKS): 38 | a0, a1, a2 = w3.eth.accounts[:3] 39 | SOCKS.approve(a1, 1*DECIMALS, transact={}) 40 | SOCKS.burnFrom(a0, 1*10**18, transact={'from': a1}) 41 | assert SOCKS.balanceOf(a0) == 500*DECIMALS - 1*DECIMALS 42 | assert SOCKS.totalSupply() == 500*DECIMALS - 1*DECIMALS 43 | -------------------------------------------------------------------------------- /test/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pytest 3 | from pytest import raises 4 | 5 | from web3 import Web3 6 | from web3.contract import ConciseContract 7 | import eth_tester 8 | from eth_tester import EthereumTester, PyEVMBackend 9 | from eth_tester.exceptions import TransactionFailed 10 | from vyper import compiler 11 | 12 | ''' 13 | # run tests with: python -m pytest -v 14 | ''' 15 | 16 | setattr(eth_tester.backends.pyevm.main, 'GENESIS_GAS_LIMIT', 10**9) 17 | setattr(eth_tester.backends.pyevm.main, 'GENESIS_DIFFICULTY', 1) 18 | 19 | @pytest.fixture 20 | def tester(): 21 | return EthereumTester(backend=PyEVMBackend()) 22 | 23 | @pytest.fixture 24 | def w3(tester): 25 | w3 = Web3(Web3.EthereumTesterProvider(tester)) 26 | w3.eth.setGasPriceStrategy(lambda web3, params: 0) 27 | w3.eth.defaultAccount = w3.eth.accounts[0] 28 | return w3 29 | 30 | # @pytest.fixture 31 | def create_contract(w3, path, *args, **kwargs): 32 | wd = os.path.dirname(os.path.realpath(__file__)) 33 | with open(os.path.join(wd, os.pardir, path)) as f: 34 | source = f.read() 35 | out = compiler.compile_code(source, ['abi', 'bytecode'], interface_codes=None) 36 | return w3.eth.contract(abi=out['abi'], bytecode=out['bytecode']) 37 | 38 | @pytest.fixture 39 | def SOCKS(w3): 40 | deploy = create_contract(w3, 'contracts/socks.vy') 41 | tx_hash = deploy.constructor().transact() 42 | tx_receipt = w3.eth.getTransactionReceipt(tx_hash) 43 | return ConciseContract(w3.eth.contract( 44 | address=tx_receipt.contractAddress, 45 | abi=deploy.abi 46 | )) 47 | 48 | @pytest.fixture 49 | def assert_fail(): 50 | def assert_fail(func): 51 | with raises(Exception): 52 | func() 53 | return assert_fail 54 | -------------------------------------------------------------------------------- /contracts/socks.vy: -------------------------------------------------------------------------------- 1 | # ERC20 implementation adapted from https://github.com/ethereum/vyper/blob/master/examples/tokens/ERC20.vy 2 | 3 | Transfer: event({_from: indexed(address), _to: indexed(address), _value: uint256}) 4 | Approval: event({_owner: indexed(address), _spender: indexed(address), _value: uint256}) 5 | 6 | name: public(string[32]) 7 | symbol: public(string[32]) 8 | decimals: public(uint256) 9 | totalSupply: public(uint256) 10 | balanceOf: public(map(address, uint256)) 11 | allowances: map(address, map(address, uint256)) 12 | 13 | 14 | @public 15 | def __init__(): 16 | _supply: uint256 = 500*10**18 17 | self.name = 'Unisocks Edition 0' 18 | self.symbol = 'SOCKS' 19 | self.decimals = 18 20 | self.balanceOf[msg.sender] = _supply 21 | self.totalSupply = _supply 22 | log.Transfer(ZERO_ADDRESS, msg.sender, _supply) 23 | 24 | 25 | @public 26 | @constant 27 | def allowance(_owner : address, _spender : address) -> uint256: 28 | return self.allowances[_owner][_spender] 29 | 30 | 31 | @public 32 | def transfer(_to : address, _value : uint256) -> bool: 33 | self.balanceOf[msg.sender] -= _value 34 | self.balanceOf[_to] += _value 35 | log.Transfer(msg.sender, _to, _value) 36 | return True 37 | 38 | 39 | @public 40 | def transferFrom(_from : address, _to : address, _value : uint256) -> bool: 41 | self.balanceOf[_from] -= _value 42 | self.balanceOf[_to] += _value 43 | if self.allowances[_from][msg.sender] < MAX_UINT256: 44 | self.allowances[_from][msg.sender] -= _value 45 | log.Transfer(_from, _to, _value) 46 | return True 47 | 48 | 49 | @public 50 | def approve(_spender : address, _value : uint256) -> bool: 51 | self.allowances[msg.sender][_spender] = _value 52 | log.Approval(msg.sender, _spender, _value) 53 | return True 54 | 55 | 56 | @public 57 | def burn(_value: uint256) -> bool: 58 | self.totalSupply -= _value 59 | self.balanceOf[msg.sender] -= _value 60 | log.Transfer(msg.sender, ZERO_ADDRESS, _value) 61 | return True 62 | 63 | 64 | @public 65 | def burnFrom(_from: address, _value: uint256) -> bool: 66 | if self.allowances[_from][msg.sender] < MAX_UINT256: 67 | self.allowances[_from][msg.sender] -= _value 68 | self.totalSupply -= _value 69 | self.balanceOf[_from] -= _value 70 | log.Transfer(_from, ZERO_ADDRESS, _value) 71 | return True 72 | --------------------------------------------------------------------------------