class StateMachine(BaseStateMachine): st_amount = strategy("uint256", max_value=1000000) st_sender = strategy("address") st_receiver = strategy("address") def setup(self): self.balances = {i: 0 for i in self.accounts} self.balances[self.accounts[0]] = self.total_supply # transfers an arbitrary amount def rule_transfer(self, st_sender, st_receiver, st_amount): self._transfer(st_sender, st_receiver, st_amount) # transfers the entire balance of an account def rule_transfer_all(self, st_sender, st_receiver): self._transfer(st_sender, st_receiver, self.balances[st_sender]) # transfers a single token def rule_transfer_one(self, st_sender, st_receiver): self._transfer(st_sender, st_receiver, 1) # internal shared transfer logic def _transfer(self, sender, receiver, amount): if amount <= self.balances[sender]: self.nft.transfer(receiver, amount, {"from": sender}) self.balances[sender] -= amount self.balances[receiver] += amount else: with brownie.reverts("dev: underflow"): self.nft.transfer(receiver, amount, {"from": sender})
class StateMachine(BaseStateMachine): st_amount = strategy("uint256", max_value=1000000) st_sender = strategy("address") st_receiver = strategy("address") def __init__(cls, NFToken, accounts): super().__init__(cls, NFToken, accounts, len(accounts)) for account in accounts[1:]: cls.nft.transfer(account, 1, {"from": accounts[0]}) def setup(self): self.balances = {i: 1 for i in self.accounts} # transfers a single token def rule_transfer_one(self, st_sender, st_receiver): if self.balances[st_sender]: self.nft.transfer(st_receiver, 1, {"from": st_sender}) self.balances[st_sender] -= 1 self.balances[st_receiver] += 1 else: with brownie.reverts("dev: underflow"): self.nft.transfer(st_receiver, 1, {"from": st_sender}) # transfers a single token using transferRange def rule_transfer_range_one(self, st_sender, st_receiver): if self.balances[st_sender]: start = self.nft.rangesOf(st_sender)[-1][0] self.nft.transferRange(st_receiver, start, start + 1, {"from": st_sender}) self.balances[st_sender] -= 1 self.balances[st_receiver] += 1 else: with brownie.reverts("dev: underflow"): self.nft.transfer(st_receiver, 1, {"from": st_sender})
class StateMachine(BaseStateMachine): st_idx = strategy("decimal", min_value=0, max_value="0.9999999999") st_amount = strategy("decimal", min_value=0, max_value="0.5") st_sender = strategy("address") st_receiver = strategy("address") def __init__(cls, NFToken, accounts): super().__init__(cls, NFToken, accounts, 1000) for account in accounts[1:]: cls.nft.transfer(account, 1000 // len(accounts), {"from": accounts[0]}) def setup(self): initial = self.total_supply // len(self.accounts) self.balances = {i: initial for i in self.accounts} # transfers a portion of a range starting from the first token in the range def rule_from_start(self, st_sender, st_receiver, st_idx, st_amount): start, stop, length = self._get_range(st_sender, st_idx) stop = start + int(length * st_amount) self._transfer(st_sender, st_receiver, start, stop) # transfers a portion of a range, ending with the last token in the range def rule_from_end(self, st_sender, st_receiver, st_idx, st_amount): start, stop, length = self._get_range(st_sender, st_idx) start = stop - int(length * st_amount) self._transfer(st_sender, st_receiver, start, stop) # transfers a portion of a range, from the middle of an existing range def rule_from_middle(self, st_sender, st_receiver, st_idx, st_amount): start, stop, length = self._get_range(st_sender, st_idx) offset = (length - int(length * st_amount)) // 2 self._transfer(st_sender, st_receiver, start + offset, stop - offset) # transfers an entire range def rule_full_range(self, st_sender, st_receiver, st_idx): start, stop, length = self._get_range(st_sender, st_idx) self._transfer(st_sender, st_receiver, start, stop) def _get_range(self, address, pct): ranges = self.nft.rangesOf(address) if not ranges: return 0, 0, 0 range_ = ranges[int(len(ranges) * pct)] return range_[0], range_[1], range_[1] - range_[0] def _transfer(self, sender, receiver, start, stop): if stop <= start: with brownie.reverts(): self.nft.transferRange(receiver, start, stop, {"from": sender}) else: self.nft.transferRange(receiver, start, stop, {"from": sender}) self.balances[sender] -= stop - start self.balances[receiver] += stop - start
class StateMachine: st_eth = strategy('uint256', max_value="10 ether") st_usd = strategy('uint256', max_value="1000000 ether") st_sleep = strategy('uint256', max_value=60000) st_address = strategy('address') def __init__(cls, accounts, contract): # deploy the contract at the start of the test cls.accounts = accounts cls.contract = contract def setup(self): # zero the deposit amounts at the start of each test run self.contributed_usd = 0 self.hardcap_usd = 3000000 * 10 ** 18 # in USD with 18 decimals self.frame_usd = self.contract.frameUsd({'from': accounts[1]}) def rule_buy_with_eth(self, st_address, st_eth): # make a deposit and adjust the local record tx = self.contract.calculateEthFrames(st_eth, st_address, {'from': st_address}) frames = tx[0] if frames > 0 : self.contract.buyFramesEth({'from': st_address, 'value': st_eth}) self.contributed_usd += frames * self.contract.frameUsdWithBonus(st_address, {'from': st_address}) else: with brownie.reverts(): self.contract.buyFramesEth({'from': st_address, 'value': st_eth}) def rule_buy_offchain(self, st_address, st_usd): # convert dollars into number of frames available to purchase tx = self.contract.calculateUsdFrames(st_usd, st_address, {'from': accounts[1]}) frames = tx[0] usd_to_contribute = tx[1] if frames > 0: self.contract.offlineFramesPurchase(st_address,frames, {'from': accounts[1]}) self.contributed_usd += usd_to_contribute else: with brownie.reverts(): self.contract.offlineFramesPurchase(st_address,frames, {'from': accounts[1]}) def rule_sleep(self, st_sleep): rpc.sleep(st_sleep) def rule_finalise(self): finalised = self.contract.finalised( {'from': accounts[1]}) if (not finalised and self.contributed_usd + self.frame_usd > self.hardcap_usd): self.contract.finalise(accounts[9],{'from': accounts[1]}) def invariant(self): # compare the contract deposit amounts with the local record assert (self.contract.contributedUsd() - self.contributed_usd) *2 < 1 * 10 ** 10 # err^2 < 10-8 dp
class StateMachine(SMTestBase): st_int = strategy("uint8") st_bool = strategy("bool") foobar = strategy("bytes4") def rule_one(self, st_int): assert type(st_int) is int assert 0 <= st_int <= 255 def rule_two(self, st_bool, foobar): assert type(st_bool) is bool assert type(foobar) is bytes def rule_three(self, foo="st_bool"): assert type(foo) is bool
class StateMachine: st_addr = strategy("address") st_random = strategy("uint256") def __init__(cls, accounts: Accounts, CallProxy: ContractContainer, Xhibit: ContractContainer): cls.alice = accounts[0] call_proxy = cls.alice.deploy(CallProxy) cls.accounts = accounts cls.xhibit: Contract = cls.alice.deploy(Xhibit, call_proxy) def setup(self): self.total_supply = 0 self.ownership = defaultdict(set) def rule_mint(self, st_addr): to = str(st_addr) self.xhibit.mint(to, {"from": self.alice}) self.ownership[to].add(self.total_supply) self.total_supply += 1 def rule_transfer(self, st_addr, st_random): if self.total_supply == 0: return to = str(st_addr) token_id = st_random % self.total_supply _from = str(self.xhibit.ownerOf(token_id)) self.xhibit.transferFrom(_from, to, token_id, {"from": _from}) self.ownership[_from].remove(token_id) self.ownership[to].add(token_id) def invariant_balanceOf(self): for acct in self.ownership.keys(): assert self.xhibit.balanceOf(acct) == len(self.ownership[acct]) def invariant_tokenOfOwnerByIndex(self): for acct in self.ownership.keys(): tokens = { self.xhibit.tokenOfOwnerByIndex(acct, i) for i in range(len(self.ownership[acct])) } assert tokens == self.ownership[acct]
class StateMachine(SMTestBase): @sf.rule(st_int=strategy("uint8")) def rule_one(self, st_int): pass @sf.rule() def invariant_horrible_name_for_a_rule(self): pass
def test_invalid_bits(type_str): with pytest.raises(ValueError): strategy(f"{type_str}1") with pytest.raises(ValueError): strategy(f"{type_str}264") with pytest.raises(ValueError): strategy(f"{type_str}69")
class StateMachine: value = strategy('uint256', max_value="1 ether") address = strategy('address') def __init__(cls, accounts, contract): # deploy the contract at the start of the test cls.accounts = accounts cls.contract = contract def setup(self): # zero the deposit amounts at the start of each test run self.deposits = {i: 0 for i in self.accounts} self.total = 0 def rule_deposit(self, address, value): # make a deposit and adjust the local record self.contract.depositDividends({'from': address, 'value': value}) self.deposits[address] += value self.total += value def rule_withdraw(self, address, value): if self.deposits[address] >= value: # make a withdrawal and adjust the local record self.contract.withdrawDividends({'from': address}) self.deposits[address] -= value self.total -= value else: # attempting to withdraw beyond your balance should revert with brownie.reverts(): self.contract.withdrawDividends({'from': address}) def invariant(self): # compare the contract deposit amounts with the local record for address, amount in self.deposits.items(): assert self.contract.dividendsOwing(address) == amount
class StateMachine: st_eth = strategy('uint256', min_value="1 ether", max_value="100 ether") st_owner = strategy('address') st_alice = strategy('address') st_bob = strategy('address') st_charlie = strategy('address') def __init__(cls, accounts, contract): # deploy the contract at the start of the test cls.accounts = accounts cls.contract = contract def setup(self): # zero the deposit amounts at the start of each test run self.total_supply = SUPPLY def rule_transfer(self, st_owner, st_alice, st_eth): balance = self.contract.balanceOf(st_owner, {'from': st_owner}) if balance >= st_eth: self.contract.transfer(st_alice, st_eth, {'from': st_owner}) def invariant(self): # compare the contract deposit amounts with the local record assert self.contract.totalSupply() == self.total_supply
def test_invalid_min_max(): # min too low with pytest.raises(ValueError): strategy("decimal", min_value=-(2**128)) # max too high with pytest.raises(ValueError): strategy("decimal", max_value=2**128) # min > max with pytest.raises(ValueError): strategy("decimal", min_value=42, max_value=12)
class TokenSupply: st_minter = strategy("address") def __init__(self): pass def rule_mint(self, st_receiver, st_minter, st_amount): print(st_amount) if self.contract.owner() == st_minter: self.contract.mintToken(st_receiver, st_amount, {"from": st_minter}) self.totalSupply += st_amount self.balances[st_receiver] += st_amount else: with brownie.reverts(): self.contract.mintToken(st_receiver, st_amount, {"from": st_minter}) """
class Edgecases: st_minter = strategy("address") def __init__(self): pass def rule_totalSupply_overflows(self, st_minter, st_receiver): uint256max = 2**256 - 1 if self.contract.owner == st_minter: tx = self.contract.mintToken(st_receiver, uint256max, {"from": st_minter}) self.totalSupply += uint256max self.balances[st_receiver] += uint256max else: with brownie.reverts(): self.contract.mintToken(st_receiver, uint256max, {"from": st_minter}) """
strategy("int", min_value=-(2**255) - 1) # max too high with pytest.raises(ValueError): strategy("uint", max_value=2**256) # min > max with pytest.raises(ValueError): strategy("int", min_value=42, max_value=12) with pytest.raises(ValueError): strategy("uint8", min_value=1024) with pytest.raises(ValueError): strategy("int8", max_value=-129) @given(value=strategy("uint")) def test_uint_given(value): assert type(value) is int assert 0 <= value <= 2**256 - 1 @given(value=strategy("uint8")) def test_uint8_given(value): assert type(value) is int assert 0 <= value <= 255 @given(value=strategy("int")) def test_int_given(value): assert -(2**255) <= value <= 2**255 - 1
def test_strategy(bits): type_str = f"uint{bits}" assert isinstance(strategy(type_str), SearchStrategy) type_str = f"int{bits}" assert isinstance(strategy(type_str), SearchStrategy)
# "rtol": 1e-4, # } # }, { "entry": { "timestamp": 1633554032, "micro_price": 326752400804053, "macro_price": 326749496496389, "rtol": 1e-4, } }, ] @given( collateral=strategy('uint256', min_value=1e18, max_value=(OI_CAP-1e4)/100), leverage=strategy('uint8', min_value=1, max_value=100), is_long=strategy('bool')) def test_build_success_zero_impact(ovl_collateral, token, mothership, market, bob, start_time, collateral, leverage, is_long): brownie.chain.mine(timestamp=start_time) oi = collateral * leverage trade_fee = oi * mothership.fee() / FEE_RESOLUTION # Get prior state of collateral manager fee_bucket = ovl_collateral.fees()
class StateMachine: """ Validate gauge weights and gauge weight sum. Strategies ---------- st_type : Decimal Gauge type, multiplied by `len(self.gauges)` to choose a value st_gauge_weight : int Gauge weight st_type_wiehgt : int Type weight """ st_type = strategy("decimal", min_value=0, max_value="0.99999999") st_gauge_weight = strategy("uint", min_value=10**17, max_value=10**19) st_type_weight = strategy("uint", min_value=10**17, max_value=10**19) def __init__(self, LiquidityGauge, accounts, gauge_controller, mock_lp_token, minter): self.LiquidityGauge = LiquidityGauge self.accounts = accounts self.lp_token = mock_lp_token self.minter = minter self.controller = gauge_controller def setup(self): self.type_weights = [] self.gauges = [] def initialize_add_type(self, st_type_weight): """ Add a new gauge type. This is also included as an intialize to increase the number of types early in the test. """ self.rule_add_type(st_type_weight) def rule_add_type(self, st_type_weight): """ Add a new gauge type. """ self.controller.add_type(b"Type!", st_type_weight, {"from": self.accounts[0]}) self.type_weights.append(st_type_weight) def rule_add_gauge(self, st_type, st_gauge_weight): """ Add a new gauge. If no types have been added, this rule has not effect. """ if not self.type_weights: return gauge_type = int(st_type * (len(self.type_weights))) gauge = self.LiquidityGauge.deploy(self.lp_token, self.minter, {"from": self.accounts[0]}) self.controller.add_gauge(gauge, gauge_type, st_gauge_weight, {"from": self.accounts[0]}) self.gauges.append({ "contract": gauge, "type": gauge_type, "weight": st_gauge_weight }) def _gauge_weight(self, idx): return sum(i["weight"] for i in self.gauges if i["type"] == idx) def invariant_gauge_weight_sums(self): """ Validate the gauge weight sums per type. """ for idx in range(len(self.type_weights)): gauge_weight_sum = self._gauge_weight(idx) assert self.controller.weight_sums_per_type( idx) == gauge_weight_sum def invariant_total_type_weight(self): """ Validate the total weight. """ total_weight = sum( self._gauge_weight(idx) * weight for idx, weight in enumerate(self.type_weights)) assert self.controller.get_total_weight() == total_weight def invariant_relative_gauge_weight(self): """ Validate the relative gauge weights. """ total_weight = sum( self._gauge_weight(idx) * weight for idx, weight in enumerate(self.type_weights)) for gauge, weight, idx in [(i["contract"], i["weight"], i["type"]) for i in self.gauges]: expected = 10**18 * self.type_weights[idx] * weight // total_weight assert self.controller.gauge_relative_weight(gauge) == expected
@pytest.fixture(scope="module") def airdrop_tokens(ERC20_Revert, accounts, airdrop_token, escrow): token_list = [airdrop_token] for i in range(2): token = ERC20_Revert.deploy("Airdrop", "AD", 18, {'from': accounts[0]}) token._mint_for_testing(10**24, {'from': accounts[3]}) escrow.add_token(token, {'from': accounts[0]}) token_list.append(token) yield token_list @given(st_deposits=strategy("uint256[3]", min_value=10**17, max_value=10**21, unique=True), st_airdrops=strategy("uint256[10]", min_value=10**10, max_value=10**18, unique=True)) @settings(max_examples=10, phases=[Phase.generate, Phase.target]) def test_many_airdrops_single_claim(accounts, rpc, escrow, airdrop_token, st_deposits, st_airdrops): """ Verify correct claim amount from a single claim of many airdrops. """ # initial deposits of `lp_token` total_deposited = sum(st_deposits) for i in range(3):
class StateMachine: # account to perform a deposit / withdrawal from st_account = strategy("address", length=10) # amount to deposit / withdraw st_value = strategy("uint64") # number of weeks to lock a deposit st_lock_duration = strategy("uint8") # number of weeks to advance the clock st_sleep_duration = strategy("uint", min_value=1, max_value=4) def __init__(self, accounts, token, voting_escrow): self.accounts = accounts self.token = token self.voting_escrow = voting_escrow for acct in accounts: token._mint_for_testing(acct, 10 ** 40) token.approve(voting_escrow, 2 ** 256 - 1, {"from": acct}) def setup(self): self.token_balances = {i: 10 ** 40 for i in self.accounts} self.voting_balances = {i: {"value": 0, "unlock_time": 0} for i in self.accounts} def rule_create_lock(self, st_account, st_value, st_lock_duration): unlock_time = (chain.time() + st_lock_duration * WEEK) // WEEK * WEEK if st_value == 0: with brownie.reverts("dev: need non-zero value"): self.voting_escrow.create_lock( st_value, unlock_time, {"from": st_account, "gas": GAS_LIMIT} ) elif self.voting_balances[st_account]["value"] > 0: with brownie.reverts("Withdraw old tokens first"): self.voting_escrow.create_lock( st_value, unlock_time, {"from": st_account, "gas": GAS_LIMIT} ) elif unlock_time <= chain.time(): with brownie.reverts("Can only lock until time in the future"): self.voting_escrow.create_lock( st_value, unlock_time, {"from": st_account, "gas": GAS_LIMIT} ) elif unlock_time > chain.time() + 86400 * 365 * 4: with brownie.reverts("Voting lock can be 4 years max"): self.voting_escrow.create_lock( st_value, unlock_time, {"from": st_account, "gas": GAS_LIMIT} ) else: tx = self.voting_escrow.create_lock( st_value, unlock_time, {"from": st_account, "gas": GAS_LIMIT} ) self.voting_balances[st_account] = { "value": st_value, "unlock_time": tx.events["Deposit"]["locktime"], } def rule_increase_amount(self, st_account, st_value): if st_value == 0: with brownie.reverts("dev: need non-zero value"): self.voting_escrow.increase_amount(st_value, {"from": st_account, "gas": GAS_LIMIT}) elif self.voting_balances[st_account]["value"] == 0: with brownie.reverts("No existing lock found"): self.voting_escrow.increase_amount(st_value, {"from": st_account, "gas": GAS_LIMIT}) elif self.voting_balances[st_account]["unlock_time"] <= chain.time(): with brownie.reverts("Cannot add to expired lock. Withdraw"): self.voting_escrow.increase_amount(st_value, {"from": st_account, "gas": GAS_LIMIT}) else: self.voting_escrow.increase_amount(st_value, {"from": st_account, "gas": GAS_LIMIT}) self.voting_balances[st_account]["value"] += st_value def rule_increase_unlock_time(self, st_account, st_lock_duration): unlock_time = (chain.time() + st_lock_duration * WEEK) // WEEK * WEEK if self.voting_balances[st_account]["unlock_time"] <= chain.time(): with brownie.reverts("Lock expired"): self.voting_escrow.increase_unlock_time( unlock_time, {"from": st_account, "gas": GAS_LIMIT} ) elif self.voting_balances[st_account]["value"] == 0: with brownie.reverts("Nothing is locked"): self.voting_escrow.increase_unlock_time( unlock_time, {"from": st_account, "gas": GAS_LIMIT} ) elif unlock_time <= self.voting_balances[st_account]["unlock_time"]: with brownie.reverts("Can only increase lock duration"): self.voting_escrow.increase_unlock_time( unlock_time, {"from": st_account, "gas": GAS_LIMIT} ) elif unlock_time > chain.time() + 86400 * 365 * 4: with brownie.reverts("Voting lock can be 4 years max"): self.voting_escrow.increase_unlock_time( unlock_time, {"from": st_account, "gas": GAS_LIMIT} ) else: tx = self.voting_escrow.increase_unlock_time( unlock_time, {"from": st_account, "gas": GAS_LIMIT} ) self.voting_balances[st_account]["unlock_time"] = tx.events["Deposit"]["locktime"] def rule_withdraw(self, st_account): """ Withdraw tokens from the voting escrow. """ if self.voting_balances[st_account]["unlock_time"] > chain.time(): # fail path - before unlock time with brownie.reverts("The lock didn't expire"): self.voting_escrow.withdraw({"from": st_account, "gas": GAS_LIMIT}) else: # success path - specific amount self.voting_escrow.withdraw({"from": st_account, "gas": GAS_LIMIT}) self.voting_balances[st_account]["value"] = 0 def rule_checkpoint(self, st_account): self.voting_escrow.checkpoint({"from": st_account, "gas": GAS_LIMIT}) def rule_advance_time(self, st_sleep_duration): """ Advance the clock. """ chain.sleep(st_sleep_duration * WEEK) # check the balance as a transaction, to ensure a block is mined after time travel self.token.balanceOf.transact(self.accounts[0], {"from": self.accounts[0]}) def invariant_token_balances(self): """ Verify that token balances are correct. """ for acct in self.accounts: assert self.token.balanceOf(acct) == 10 ** 40 - self.voting_balances[acct]["value"] def invariant_escrow_current_balances(self): """ Verify the sum of all escrow balances is equal to the escrow totalSupply. """ total_supply = 0 timestamp = chain[-1].timestamp for acct in self.accounts: data = self.voting_balances[acct] balance = self.voting_escrow.balanceOf(acct) total_supply += balance if data["unlock_time"] > timestamp and data["value"] // MAX_TIME > 0: assert balance elif not data["value"] or data["unlock_time"] <= timestamp: assert not balance assert self.voting_escrow.totalSupply() == total_supply def invariant_historic_balances(self): """ Verify the sum of historic escrow balances is equal to the historic totalSupply. """ total_supply = 0 block_number = history[-4].block_number for acct in self.accounts: total_supply += self.voting_escrow.balanceOfAt(acct, block_number) assert self.voting_escrow.totalSupplyAt(block_number) == total_supply
def test_exclude_repr(): st = strategy("uint8", exclude=42) assert repr(st) == "integers(min_value=0, max_value=255, exclude=(42,))"
import pytest from brownie.test import given, strategy from collections import deque from hypothesis import settings from itertools import permutations from simulation import Curve pytestmark = pytest.mark.skip_meta @given(st_pct=strategy('decimal[50]', min_value="0.001", max_value=1, unique=True, places=3), st_seed_amount=strategy("decimal", min_value=5, max_value=12, places=1)) @settings(max_examples=5) def test_curve_in_contract( alice, swap, wrapped_coins, wrapped_decimals, underlying_decimals, n_coins, approx, st_seed_amount, st_pct, ): st_seed_amount = int(10**st_seed_amount)
class ReleaseTest: st_bool = strategy("bool") def __init__(self, gov, registry, create_token, create_vault): self.gov = gov self.registry = registry self.create_token = lambda s: create_token() def create_vault_adaptor(self, *args, **kwargs): return create_vault(*args, **kwargs) self.create_vault = create_vault_adaptor def setup(self): self.latest_version = Version("1.0.0") token = self.create_token() vault = self.create_vault(token, version=str(self.latest_version)) self.vaults = {token: [vault]} self.registry.newRelease(vault, {"from": self.gov}) self.experiments = {} def rule_new_release(self, new_token="st_bool"): if new_token or len(self.vaults.keys()) == 0: token = self.create_token() else: token = list(self.vaults.keys())[-1] self.latest_version = self.latest_version.next_patch() vault = self.create_vault(token, version=str(self.latest_version)) print(f"Registry.newRelease({token}, {self.latest_version})") self.registry.newRelease(vault, {"from": self.gov}) if token in self.vaults: self.vaults[token].append(vault) else: self.vaults[token] = [vault] def rule_new_deployment(self, new_token="st_bool"): tokens_with_stale_deployments = [ token for token, deployments in self.vaults.items() if Version(deployments[-1].apiVersion()) < self.latest_version ] if new_token or len(tokens_with_stale_deployments) == 0: token = self.create_token() else: token = tokens_with_stale_deployments[-1] print(f"Registry.newVault({token}, {self.latest_version})") vault = Vault.at( self.registry.newVault(token, self.gov, self.gov, "", "").return_value) if token in self.vaults: self.vaults[token].append(vault) else: self.vaults[token] = [vault] def rule_new_experiment(self): token = self.create_token() print(f"Registry.newExperimentalVault({token}, {self.latest_version})") vault = Vault.at( self.registry.newExperimentalVault(token, self.gov, self.gov, self.gov, "", "").return_value) self.experiments[token] = [vault] def rule_endorse_experiment(self): experiments_with_latest_api = [ (token, deployments[-1]) for token, deployments in self.experiments.items() if (Version(deployments[-1].apiVersion()) == self.latest_version and (token not in self.vaults or Version(self.vaults[token][-1].apiVersion()) < Version( deployments[-1].apiVersion()))) ] if len(experiments_with_latest_api) > 0: token, vault = experiments_with_latest_api[-1] print(f"Registry.endorseVault({token}, {self.latest_version})") self.registry.endorseVault(vault, {"from": self.gov}) if token in self.vaults: self.vaults[token].append(vault) else: self.vaults[token] = [vault] def invariant(self): for token, deployments in self.vaults.items(): # Check that token matches up assert deployments[0].token() == token # Strictly linearly increasing versions last_version = Version(deployments[0].apiVersion()) assert last_version <= self.latest_version for vault in deployments[1:]: # Check that token matches up assert vault.token() == token # Strictly linearly increasing versions assert last_version < Version( vault.apiVersion()) <= self.latest_version
def test_invalid_min_max(): # min too low with pytest.raises(ValueError): strategy("uint", min_value=-1) with pytest.raises(ValueError): strategy("int", min_value=-(2**255) - 1) # max too high with pytest.raises(ValueError): strategy("uint", max_value=2**256) # min > max with pytest.raises(ValueError): strategy("int", min_value=42, max_value=12) with pytest.raises(ValueError): strategy("uint8", min_value=1024) with pytest.raises(ValueError): strategy("int8", max_value=-129)
class StateMachine: """ Stateful test that performs a series of deposits, swaps and withdrawals and confirms that the virtual price only goes up. """ st_pct = strategy("decimal", min_value="0.5", max_value="1", places=2) st_rates = strategy("decimal[8]", min_value="1.001", max_value="1.004", places=4, unique=True) def __init__(cls, alice, swap, wrapped_coins, wrapped_decimals): cls.alice = alice cls.swap = swap cls.coins = wrapped_coins cls.decimals = wrapped_decimals cls.n_coins = len(wrapped_coins) def setup(self): # reset the virtual price between each test run self.virtual_price = self.swap.get_virtual_price() def _min_max(self): # get index values for the coins with the smallest and largest balances in the pool balances = [ self.swap.balances(i) / (10**self.decimals[i]) for i in range(self.n_coins) ] min_idx = balances.index(min(balances)) max_idx = balances.index(max(balances)) if min_idx == max_idx: min_idx = abs(min_idx - 1) return min_idx, max_idx def rule_ramp_A(self, st_pct): """ Increase the amplification coefficient. This action happens at most once per test. If A has already been ramped, a swap is performed instead. """ if not hasattr(self.swap, "ramp_A") or self.swap.future_A_time(): return self.rule_exchange_underlying(st_pct) new_A = int(self.swap.A() * (1 + st_pct)) self.swap.ramp_A(new_A, chain.time() + 86410, {'from': self.alice}) def rule_increase_rates(self, st_rates): """ Increase the stored rate for each wrapped coin. """ for rate, coin in zip(self.coins, st_rates): if hasattr(coin, "set_exchange_rate"): coin.set_exchange_rate(int(coin.get_rate() * rate), {'from': self.alice}) def rule_exchange(self, st_pct): """ Perform a swap using wrapped coins. """ send, recv = self._min_max() amount = int(10**self.decimals[send] * st_pct) value = amount if self.coins[send] == ETH_ADDRESS else 0 self.swap.exchange(send, recv, amount, 0, { 'from': self.alice, 'value': value }) def rule_exchange_underlying(self, st_pct): """ Perform a swap using underlying coins. """ if not hasattr(self.swap, "exchange_underlying"): # if underlying coins aren't available, use wrapped instead return self.rule_exchange(st_pct) send, recv = self._min_max() amount = int(10**self.decimals[send] * st_pct) value = amount if self.coins[send] == ETH_ADDRESS else 0 self.swap.exchange_underlying(send, recv, amount, 0, { 'from': self.alice, 'value': value }) def rule_remove_one_coin(self, st_pct): """ Remove liquidity from the pool in only one coin. """ if not hasattr(self.swap, "remove_liquidity_one_coin"): # not all pools include `remove_liquidity_one_coin` return self.rule_remove_imbalance(st_pct) idx = self._min_max()[1] amount = int(10**self.decimals[idx] * st_pct) self.swap.remove_liquidity_one_coin(amount, idx, 0, {'from': self.alice}) def rule_remove_imbalance(self, st_pct): """ Remove liquidity from the pool in an imbalanced manner. """ idx = self._min_max()[1] amounts = [0] * self.n_coins amounts[idx] = 10**self.decimals[idx] * st_pct self.swap.remove_liquidity_imbalance(amounts, 2**256 - 1, {'from': self.alice}) def rule_remove(self, st_pct): """ Remove liquidity from the pool. """ amount = int(10**18 * st_pct) self.swap.remove_liquidity(amount, [0] * self.n_coins, {'from': self.alice}) def invariant_check_virtual_price(self): """ Verify that the pool's virtual price has either increased or stayed the same. """ virtual_price = self.swap.get_virtual_price() assert virtual_price >= self.virtual_price self.virtual_price = virtual_price def invariant_advance_time(self): """ Advance the clock by 1 hour between each action. """ chain.sleep(3600)
class StateMachine: st_acct = strategy("address", length=5) st_acct2 = strategy("address", length=5) st_token = strategy("uint", max_value=2) st_synth = strategy("uint", max_value=2) st_idx = strategy("decimal", min_value=0, max_value="0.99", places=2) st_amount = strategy("decimal", min_value=1, max_value=10, places=3) def __init__(cls, swap): cls.swap = swap def setup(self): self.settlers = [i["addr"] for i in self.swap.tx.events["NewSettler"]] self.used_token_ids = [] self.active_token_ids = {} # "Marty - you gotta come back with me!" # we're doing this because SNX oracle rates expire in 25 hours # it's weird and hacky but it works ¯\_(ツ)_/¯ chain.mine(timestamp=1600000000) def _mint(self, acct, token, amount): token = MintableForkToken(token) amount = int(amount * 10**token.decimals()) if not token.allowance(acct, self.swap): token.approve(self.swap, 2**256 - 1, {"from": acct}) balance = token.balanceOf(acct) if balance < amount: token._mint_for_testing(acct, amount - balance) return amount def _all_token_ids(self): return ([x for v in self.active_token_ids.values() for x in v] + self.used_token_ids + [int(i, 16) for i in self.settlers]) def rule_swap_into(self, st_acct, st_token, st_synth, st_idx, st_amount): """ Generate a new NFT via a cross-asset swap. """ idx = int(st_idx * len(TOKENS[st_token])) initial = TOKENS[st_token][idx] synth = TOKENS[st_synth][0] amount = self._mint(st_acct, initial, st_amount) if st_token == st_synth: # initial token and target synth come from the same asset class # no cross-asset swap is possible with brownie.reverts(): self.swap.swap_into_synth(initial, synth, amount, 0, {"from": st_acct}) else: tx = self.swap.swap_into_synth(initial, synth, amount, 0, {"from": st_acct}) token_id = tx.events["Transfer"][-1]["token_id"] assert token_id != 0 if "NewSettler" in tx.events: settler = tx.events["NewSettler"]["addr"] assert settler not in self.settlers self.settlers.append(settler) # make sure `token_id` isn't previously assigned assert (token_id not in list(self.active_token_ids.values()) + self.used_token_ids) self.active_token_ids.setdefault(st_acct, []).append(token_id) chain.mine(timedelta=600) def rule_swap_into_existing(self, st_acct, st_token, st_amount, st_idx): """ Increase the underyling balance of an existing NFT via a cross-asset swap. """ if self.active_token_ids.get(st_acct): idx = int(st_idx * len(self.active_token_ids[st_acct])) token_id = self.active_token_ids[st_acct][idx] else: token_ids = self._all_token_ids() idx = int(st_idx * len(token_ids)) token_id = token_ids[idx] synth = Settler.at(hex(token_id % 2**160)).synth() idx = int(st_idx * len(TOKENS[st_token])) initial = TOKENS[st_token][idx] amount = self._mint(st_acct, initial, st_amount) if self.active_token_ids.get(st_acct) and TOKENS[st_token][0] != synth: self.swap.swap_into_synth(initial, synth, amount, 0, st_acct, token_id, {"from": st_acct}) chain.mine(timedelta=600) else: with brownie.reverts(): self.swap.swap_into_synth(initial, synth, amount, 0, st_acct, token_id, {"from": st_acct}) def rule_transfer(self, st_acct, st_acct2, st_idx): """ Transfer ownership of an NFT. """ if self.active_token_ids.get(st_acct): # choose from the caller's valid NFT token IDs, if there are any idx = int(st_idx * len(self.active_token_ids[st_acct])) token_id = self.active_token_ids[st_acct][idx] self.swap.transferFrom(st_acct, st_acct2, token_id, {"from": st_acct}) self.active_token_ids[st_acct].remove(token_id) self.active_token_ids.setdefault(st_acct2, []).append(token_id) else: # if the caller does not own any NFTs, choose from any token ID token_ids = self._all_token_ids() idx = int(st_idx * len(token_ids)) token_id = token_ids[idx] with brownie.reverts(): self.swap.transferFrom(st_acct, st_acct2, token_id, {"from": st_acct}) def rule_withdraw(self, st_acct, st_amount, st_idx): """ Withdraw a synth from an NFT. """ if self.active_token_ids.get(st_acct): # choose from the caller's valid NFT token IDs, if there are any idx = int(st_idx * len(self.active_token_ids[st_acct])) token_id = self.active_token_ids[st_acct][idx] else: # if the caller does not own any NFTs, choose from any token ID token_ids = self._all_token_ids() idx = int(st_idx * len(token_ids)) token_id = token_ids[idx] amount = int(st_amount * 10**18) if self.active_token_ids.get(st_acct): # when the action is possible, don't exceed the max underlying balance balance = self.swap.token_info(token_id)["underlying_balance"] amount = min(amount, balance) if self.active_token_ids.get(st_acct): self.swap.withdraw(token_id, amount, {"from": st_acct}) if balance == amount: self.active_token_ids[st_acct].remove(token_id) self.used_token_ids.append(token_id) else: with brownie.reverts(): self.swap.withdraw(token_id, amount, {"from": st_acct}) def rule_swap_from(self, st_acct, st_token, st_amount, st_idx): """ Swap a synth out of an NFT. """ if self.active_token_ids.get(st_acct): # choose from the caller's valid NFT token IDs, if there are any idx = int(st_idx * len(self.active_token_ids[st_acct])) token_id = self.active_token_ids[st_acct][idx] else: # if the caller does not own any NFTs, choose from any token ID token_ids = self._all_token_ids() idx = int(st_idx * len(token_ids)) token_id = token_ids[idx] # choose a target coin for the swap synth = Settler.at(hex(token_id % 2**160)).synth() if synth == ZERO_ADDRESS: # if the token ID is not active, choose from any possible token - all should fail token_list = [x for v in TOKENS for x in v] else: # if the token ID is active, choose from the list of possible targets token_list = next(i for i in TOKENS if i[0] == synth) idx = int(st_idx * len(token_list)) target = token_list[idx] amount = int(st_amount * 10**18) if self.active_token_ids.get(st_acct): # when the action is possible, don't exceed the max underlying balance balance = self.swap.token_info(token_id)["underlying_balance"] amount = min(amount, balance) if self.active_token_ids.get(st_acct) and synth != target: # sender own the NFT, target is not the same as the underlying synth self.swap.swap_from_synth(token_id, target, amount, 0, {"from": st_acct}) if balance == amount: self.active_token_ids[st_acct].remove(token_id) self.used_token_ids.append(token_id) else: with brownie.reverts(): self.swap.swap_from_synth(token_id, target, amount, 0, {"from": st_acct}) def teardown(self): """ Verify balances and ownership of active and burned NFTs. """ for acct, token_id in [(k, x) for k, v in self.active_token_ids.items() for x in v]: token_info = self.swap.token_info(token_id) synth = Contract(token_info["synth"]) settler = hex(token_id % 2**160) assert self.swap.ownerOf(token_id) == acct == token_info["owner"] assert synth.balanceOf(settler) == token_info["underlying_balance"] assert len(self.used_token_ids) == len(set(self.used_token_ids)) for token_id in self.used_token_ids: with brownie.reverts(): self.swap.ownerOf(token_id) for acct in accounts[:5]: assert self.swap.balanceOf(acct) == len( self.active_token_ids.get(acct, []))
from brownie.test import given, strategy @given(account=strategy("address")) def test_balanceOf_queries_account_balances(account, xhibit): assert xhibit.balanceOf(account) == 0
import pytest from brownie.test import strategy, given from tests.conftest import YEAR @pytest.fixture(scope="module", autouse=True) def initial_setup(chain, token): chain.sleep(86401) token.update_boost_mining_parameters({'from': accounts[0]}) chain.mine() chain.sleep(86401 * 7) token.update_mining_parameters({'from': accounts[0]}) @given(duration=strategy('uint', min_value=86500, max_value=YEAR)) def test_mint(accounts, chain, token, duration): token.set_minter(accounts[0], {'from': accounts[0]}) creation_time = token.start_epoch_time() initial_supply = token.totalSupply() rate = token.rate() chain.sleep(duration) amount = (chain.time()-creation_time) * rate token.mint(accounts[1], amount, {'from': accounts[0]}) assert token.balanceOf(accounts[1]) == amount assert token.totalSupply() == initial_supply + amount @given(duration=strategy('uint', min_value=86500, max_value=YEAR))
import pytest from brownie.test import given, strategy ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" @pytest.fixture(scope="module", autouse=True) def initial_funding(vesting, accounts): vesting.add_tokens(10**21, {"from": accounts[0]}) recipients = [accounts[1]] + [ZERO_ADDRESS] * 99 vesting.fund(recipients, [10**20] + [0] * 99, {"from": accounts[0]}) @given(sleep_time=strategy("uint", max_value=100000)) def test_claim_partial(vesting, coin_a, accounts, chain, start_time, sleep_time, end_time): chain.sleep(start_time - chain.time() + sleep_time) tx = vesting.claim({"from": accounts[1]}) expected_amount = 10**20 * (tx.timestamp - start_time) // (end_time - start_time) assert coin_a.balanceOf(accounts[1]) == expected_amount
from brownie.test import strategy, given from tests.conftest import approx from tests.conftest import YEAR, YEAR_1_SUPPLY, INITIAL_SUPPLY @given(time=strategy("decimal", min_value=1, max_value=7)) def test_mintable_in_timeframe(accounts, token, block_timestamp, theoretical_supply, time, rpc): t0 = token.start_epoch_time() rpc.sleep(int(10**time)) rpc.mine() t1 = block_timestamp() if t1 - t0 >= YEAR: token.update_mining_parameters({'from': accounts[0]}) t1 = block_timestamp() available_supply = token.available_supply() mintable = token.mintable_in_timeframe(t0, t1) assert (available_supply - (INITIAL_SUPPLY * 10**18) ) >= mintable # Should only round down, not up if t1 == t0: assert mintable == 0 else: assert (available_supply - (INITIAL_SUPPLY * 10**18)) / mintable - 1 < 1e-7 assert approx(theoretical_supply(), available_supply, 1e-16) @given(time1=strategy('uint', max_value=YEAR),
@pytest.fixture(scope="module", autouse=True) def initial_setup(accounts, reward_contract, mock_lp_token, liquidity_gauge_reward): mock_lp_token.approve(liquidity_gauge_reward, 2**256 - 1, {'from': accounts[0]}) liquidity_gauge_reward.deposit(100000, {'from': accounts[0]}) for i in range(1, 11): mock_lp_token.transfer(accounts[i], 10**18, {'from': accounts[0]}) mock_lp_token.approve(liquidity_gauge_reward, 10**18, {'from': accounts[i]}) @given( amounts=strategy("uint256[10]", min_value=10**17, max_value=10**18, unique=True), durations=strategy("uint256[10]", max_value=86400 * 30, unique=True), ) @settings(max_examples=10) def test_withdraw_borked_rewards(accounts, chain, reward_contract, mock_lp_token, liquidity_gauge_reward, amounts, durations): for i, (amount, duration) in enumerate(zip(amounts, durations), start=1): liquidity_gauge_reward.deposit(amount, {'from': accounts[i]}) chain.sleep(duration) # Calling this method without transfering tokens breaks `CurveRewards.getReward` # however, it should still be possible to withdraw if `claim_rewards = False` reward_contract.notifyRewardAmount(10**20, {'from': accounts[0]})