def test_does_not_exist(): with pytest.raises(NameError): contract_strategy("Foo").validate()
class StateMachine: # stateful test information pool_info = {} added_pools = set() # state machine rules st_pool = contract_strategy("PoolMock") st_coin = contract_strategy("yERC20") st_coin2 = contract_strategy("yERC20") st_underlying = contract_strategy("ERC20") st_underlying2 = contract_strategy("ERC20") st_decimals = strategy('uint8[8]', min_value=1, max_value=42) st_amount = strategy('uint256', max_value=1e18) def __init__(cls, PoolMock, accounts, registry, coins, underlying, USDT): """ Initial state machine setup. This method only runs once, prior to the initial snapshot. """ cls.accounts = accounts cls.registry = registry # fund test account with initial balances for coin in coins + underlying: coin._mint_for_testing(1e18, {'from': accounts[0]}) coin.approve(registry, 2**256 - 1, {'from': accounts[0]}) # create pools for i in range(3): cls._create_pool(PoolMock, 2, coins[:2], underlying[:2], USDT) coins.insert(0, coins.pop()) underlying.insert(0, underlying.pop()) for i in range(2): cls._create_pool(PoolMock, 3, coins[:3], underlying[:3], USDT) coins.insert(0, coins.pop()) underlying.insert(0, underlying.pop()) cls._create_pool(PoolMock, 4, coins, underlying, USDT) @classmethod def _create_pool(cls, PoolMock, n_coins, coins, underlying, USDT): # Create a pool and add it to `cls.pool_info` coins = coins + ([ZERO_ADDRESS] * (8 - n_coins)) underlying = underlying + ([ZERO_ADDRESS] * (8 - n_coins)) pool = PoolMock.deploy(n_coins, coins[:4], underlying[:4], 70, 4000000, {'from': cls.accounts[0]}) cls.pool_info[pool] = {'coins': coins, 'underlying': underlying} def setup(self): """ Reset test conditions between each run. """ self.added_pools.clear() def rule_add_pool(self, st_pool, st_decimals): """ Attempt to add a pool to the registry. Decimal values are randomized with `st_decimals` - this has no effect on other rules, and helps to verify `Registry.get_pool_coins` Revert Paths ------------ * The pool has already been added """ n_coins = st_pool.n_coins() if st_pool in self.added_pools: with brownie.reverts("dev: pool exists"): self.registry.add_pool(st_pool, n_coins, ZERO_ADDRESS, ZERO_ADDRESS, "0x00", "0x00", "0x00", True, True, {'from': self.accounts[0]}) else: decimals = st_decimals[:n_coins] + [0] * (8 - n_coins) udecimals = st_decimals[-n_coins:] + [0] * (8 - n_coins) self.registry.add_pool(st_pool, n_coins, ZERO_ADDRESS, ZERO_ADDRESS, "0x00", pack_values(decimals), pack_values(udecimals), True, True, {'from': self.accounts[0]}) self.added_pools.add(st_pool) self.pool_info[st_pool]['decimals'] = decimals self.pool_info[st_pool]['underlying_decimals'] = udecimals def rule_remove_pool(self, st_pool): """ Attempt to remove a pool from the registry. Revert Paths ------------ * The pool has not been added, or was already removed """ if st_pool in self.added_pools: self.registry.remove_pool(st_pool, {'from': self.accounts[0]}) self.added_pools.discard(st_pool) else: with brownie.reverts("dev: pool does not exist"): self.registry.remove_pool(st_pool, {'from': self.accounts[0]}) def _exchange(self, pool, coin, coin2, amount): # attempt to perform an exchange if pool in self.added_pools and coin != coin2: from_balance = coin.balanceOf(self.accounts[0]) to_balance = coin2.balanceOf(self.accounts[0]) if amount <= from_balance: expected = self.registry.get_exchange_amount( pool, coin, coin2, amount) self.registry.exchange(pool, coin, coin2, amount, 0) assert coin.balanceOf( self.accounts[0]) == from_balance - amount assert coin2.balanceOf( self.accounts[0]) == to_balance + expected else: with brownie.reverts(): self.registry.exchange(pool, coin, coin2, amount, 0) else: with brownie.reverts(): self.registry.exchange(pool, coin, coin2, amount, 0) def rule_exchange(self, st_coin, st_coin2, st_amount): """ Attempt to perform a token exchange. The exchange is attempted with the first pool in `cls.pool_info` that provides a market for `st_coin` and `st_coin2`. Revert Paths ------------ * The pool has not been added * `st_coin` and `st_coin2` are the same coin * `accounts[0]` has an insufficient balance of `st_coin` """ pool = next(k for k, v in self.pool_info.items() if st_coin in v['coins'] and st_coin2 in v['coins']) self._exchange(pool, st_coin, st_coin2, st_amount) def rule_exchange_underlying(self, st_underlying, st_underlying2, st_amount): """ Attempt to perform a token exchange with underlying tokens. The exchange is attempted with the first pool in `cls.pool_info` that provides a market for `st_underlying` and `st_underlying2`. Revert Paths ------------ * The pool has not been added * `st_underlying` and `st_underlying` are the same coin * `accounts[0]` has an insufficient balance of `st_underlying` """ pool = next(k for k, v in self.pool_info.items() if st_underlying in v['underlying'] and st_underlying2 in v['underlying']) self._exchange(pool, st_underlying, st_underlying2, st_amount) def invariant_coins(self): """ Invariant for `get_pool_coins` Checks ------ * Added pools should return the correct coins, underlying coins, and decimals * Pools that were not added, or were removed, should return zero values """ for pool in self.pool_info: coins = self.registry.get_pool_coins(pool) if pool in self.added_pools: coins = self.registry.get_pool_coins(pool) assert coins['coins'] == self.pool_info[pool]['coins'] assert coins['underlying_coins'] == self.pool_info[pool][ 'underlying'] assert coins['decimals'] == self.pool_info[pool]['decimals'] assert coins['underlying_decimals'] == self.pool_info[pool][ 'underlying_decimals'] else: assert coins['coins'] == [ZERO_ADDRESS] * 8 assert coins['underlying_coins'] == [ZERO_ADDRESS] * 8 assert coins['decimals'] == [0] * 8 assert coins['underlying_decimals'] == [0] * 8
def test_repr(): assert repr(contract_strategy("Token")) == "sampled_from(Token)"
def test_strategy(): assert isinstance(contract_strategy("ERC20"), DeferredStrategy)
#!/usr/bin/python3 import pytest from hypothesis import HealthCheck, given, settings from hypothesis.strategies._internal.deferred import DeferredStrategy from brownie.network.contract import ProjectContract from brownie.test import contract_strategy def test_strategy(): assert isinstance(contract_strategy("ERC20"), DeferredStrategy) def test_repr(): assert repr(contract_strategy("Token")) == "sampled_from(Token)" def test_does_not_exist(): with pytest.raises(NameError): contract_strategy("Foo").validate() @given(contract=contract_strategy("BrownieTester")) @settings(suppress_health_check=[HealthCheck.function_scoped_fixture]) def test_given(tester, contract): assert isinstance(contract, ProjectContract)
class StateMachine: st_contract = contract_strategy("ERC20") st_token = strategy("uint256", max_value=9) st_amount = strategy("uint256", max_value=2**16 * 10**18) def __init__(cls, alice: Account, ERC20: ContractContainer, xhibit: Contract): cls.alice = alice cls.xhibit = xhibit # create 10 mock ERC20 contracts for i in range(10): instance = alice.deploy(ERC20, f"Test Token {i}", f"TST{i}", 18) # mint a s#!t ton of tokens :) instance._mint_for_testing(alice, 2**256 - 1, {"from": alice}) # approve ahead of time instance.approve(xhibit, 2**256 - 1, {"from": alice}) # create 10 xhibit NFTs as well xhibit.mint(alice, {"from": alice}) def setup(self): self.state = State() def rule_receive_erc20( self, st_contract: Contract, st_token: int, st_amount: int, ): if st_contract.balanceOf(self.alice) >= st_amount: st_contract.approve(self.xhibit, 2**256 - 1, {"from": self.alice}) self.xhibit.getERC20(self.alice, st_token, st_contract, st_amount) self.state.receive_erc20(st_token, str(st_contract), st_amount) def rule_remove_erc20(self, st_contract: Contract, st_token: int, st_amount: int): if self.xhibit.balanceOfERC20(st_token, st_contract) >= st_amount: self.xhibit.transferERC20(st_token, self.alice, st_contract, st_amount) self.state.remove_erc20(st_token, str(st_contract), st_amount) def rule_clear_erc20(self, st_contract: Contract, st_token: int): balance = self.xhibit.balanceOfERC20(st_token, st_contract) if balance > 0: self.xhibit.transferERC20(st_token, self.alice, st_contract, balance) self.state.remove_erc20(st_token, str(st_contract), balance) def invariant_totalERC20Contracts(self): # for each xhibit NFT verify the total # of child contracts for i in range(10): assert self.xhibit.totalERC20Contracts( i) == self.state.total_erc20_contracts(i) def invariant_erc20ContractByIndex(self): # for each xhibit NFT for i in range(10): erc20_contracts = { self.xhibit.erc20ContractByIndex(i, j) for j in range(self.state.total_erc20_contracts(i)) } assert erc20_contracts == self.state.erc20_contracts(i) def invariant_erc20_balance(self): # for each xhibit NFT for i in range(10): erc20_contracts = { self.xhibit.erc20ContractByIndex(i, j) for j in range(self.state.total_erc20_contracts(i)) } for contract in erc20_contracts: assert (self.xhibit.balanceOfERC20( i, contract) == self.state.tokens[i][contract])
class StateMachine: st_contract = contract_strategy("ERC721") st_token = strategy("uint", max_value=9) def __init__(cls, alice: Account, ERC721: ContractContainer, xhibit: Contract): cls.alice = alice cls.xhibit = xhibit # create 10 mock ERC721 contracts for _ in range(10): instance = alice.deploy(ERC721) # create 10 xhibit NFTs as well xhibit.mint(alice, {"from": alice}) # create 10 NFTs in this contract instance for _ in range(10): instance._mint_for_testing(alice, {"from": alice}) def setup(self): self.state = State() def rule_receive_child( self, st_contract: Contract, token_id: int = "st_token", receiving_token: int = "st_token", ): if st_contract.ownerOf(token_id) == self.alice: st_contract.safeTransferFrom(self.alice, self.xhibit, token_id, receiving_token, {"from": self.alice}) self.state.receive_child(receiving_token, str(st_contract), token_id) def rule_remove_child( self, st_contract: Contract, child_token: int = "st_token", ): if st_contract.ownerOf(child_token) == self.xhibit: from_token = self.xhibit.ownerOfChild(st_contract, child_token)[1] self.xhibit.transferChild(from_token, self.alice, st_contract, child_token, {"from": self.alice}) self.state.remove_child(from_token, str(st_contract), child_token) def invariant_totalChildContracts(self): # for each xhibit NFT verify the total # of child contracts for i in range(10): assert self.xhibit.totalChildContracts( i) == self.state.total_child_contracts(i) def invariant_childContracts(self): # for each xhibit NFT for i in range(10): child_contracts = { self.xhibit.childContractByIndex(i, j) for j in range(self.state.total_child_contracts(i)) } assert child_contracts == self.state.child_contracts(i) def invariant_totalChildTokens(self): # for each xhibit NFT for i in range(10): for child_contract in self.state.child_contracts(i): assert self.xhibit.totalChildTokens( i, child_contract) == self.state.total_child_tokens( i, child_contract) def invariant_childTokens(self): for i in range(10): for child_contract in self.state.child_contracts(i): child_tokens = { self.xhibit.childTokenByIndex(i, child_contract, j) for j in range( self.state.total_child_tokens(i, child_contract)) } assert child_tokens == self.state.child_tokens( i, child_contract)