Ejemplo n.º 1
0
def create_risky_cdp(mcd: DssDeployment,
                     c: Collateral,
                     collateral_amount: Wad,
                     gal_address: Address,
                     draw_dai=True) -> Urn:
    assert isinstance(mcd, DssDeployment)
    assert isinstance(c, Collateral)
    assert isinstance(gal_address, Address)

    # Ensure vault isn't already unsafe (if so, this shouldn't be called)
    urn = mcd.vat.urn(c.ilk, gal_address)
    assert is_cdp_safe(mcd.vat.ilk(c.ilk.name), urn)

    # Add collateral to gal vault if necessary
    c.approve(gal_address)
    token = Token(c.ilk.name, c.gem.address, c.adapter.dec())
    print(f"collateral_amount={collateral_amount} ink={urn.ink}")
    dink = collateral_amount - urn.ink
    if dink > Wad(0):
        vat_balance = mcd.vat.gem(c.ilk, gal_address)
        balance = token.normalize_amount(c.gem.balance_of(gal_address))
        print(
            f"before join: dink={dink} vat_balance={vat_balance} balance={balance} vat_gap={dink - vat_balance}"
        )
        if vat_balance < dink:
            # handle dusty balances with non-18-decimal tokens
            vat_gap = dink - vat_balance + token.min_amount
            if balance < vat_gap:
                if c.ilk.name.startswith("ETH"):
                    wrap_eth(mcd, gal_address, vat_gap)
                else:
                    raise RuntimeError("Insufficient collateral balance")
            assert c.adapter.join(gal_address,
                                  token.unnormalize_amount(vat_gap)).transact(
                                      from_address=gal_address)
        vat_balance = mcd.vat.gem(c.ilk, gal_address)
        balance = token.normalize_amount(c.gem.balance_of(gal_address))
        print(
            f"after join: dink={dink} vat_balance={vat_balance} balance={balance} vat_gap={dink - vat_balance}"
        )
        assert vat_balance >= dink
        assert mcd.vat.frob(c.ilk, gal_address, dink,
                            Wad(0)).transact(from_address=gal_address)
        urn = mcd.vat.urn(c.ilk, gal_address)

    # Put gal CDP at max possible debt
    dart = max_dart(mcd, c, gal_address) - Wad(1)
    if dart > Wad(0):
        print(f"Frobbing {c.ilk.name} with ink={urn.ink} and dart={dart}")
        assert mcd.vat.frob(c.ilk, gal_address, Wad(0),
                            dart).transact(from_address=gal_address)

    # Draw our Dai, simulating the usual behavior
    urn = mcd.vat.urn(c.ilk, gal_address)
    if draw_dai and urn.art > Wad(0):
        mcd.approve_dai(gal_address)
        assert mcd.dai_adapter.exit(gal_address,
                                    urn.art).transact(from_address=gal_address)
        print(f"Exited {urn.art} Dai from urn")
    return urn
Ejemplo n.º 2
0
    def __init__(self, args: list, **kwargs):
        parser = argparse.ArgumentParser(prog='Mooniswap-market-maker-keeper')
        self.add_arguments(parser=parser)
        self.arguments = parser.parse_args(args)
        setup_logging(self.arguments)

        provider = HTTPProvider(
            endpoint_uri=self.arguments.rpc_host,
            request_kwargs={'timeout': self.arguments.rpc_timeout})
        self.web3: Web3 = kwargs['web3'] if 'web3' in kwargs else Web3(
            provider)

        self.web3.eth.defaultAccount = self.arguments.eth_from
        register_keys(self.web3, self.arguments.eth_key)
        self.our_address = Address(self.arguments.eth_from)

        self.mooniswap_factory = MooniFactory(
            web3=self.web3,
            factory_address=Address(self.arguments.mooniswap_factory_address))
        first_token_address = Address(self.arguments.first_token_address)
        second_token_address = Address(self.arguments.second_token_address)
        if first_token_address == Address(
                "0x0000000000000000000000000000000000000000"):
            self.first_token = EthToken(web3=self.web3,
                                        address=first_token_address)
        else:
            self.first_token = ERC20Token(
                web3=self.web3,
                address=Address(self.arguments.first_token_address))
        if second_token_address == Address(
                "0x0000000000000000000000000000000000000000"):
            self.second_token = EthToken(web3=self.web3,
                                         address=second_token_address)
        else:
            self.second_token = ERC20Token(
                web3=self.web3,
                address=Address(self.arguments.second_token_address))

        self.referral = Address(self.arguments.mooniswap_referral_address)

        self.token_first = Token(name=self.arguments.first_token_name,
                                 address=Address(
                                     self.arguments.first_token_address),
                                 decimals=self.arguments.first_token_decimals)

        self.token_second = Token(
            name=self.arguments.second_token_name,
            address=Address(self.arguments.second_token_address),
            decimals=self.arguments.second_token_decimals)

        self.mooniswap = self.mooniswap_factory.get_pair(
            first_token=self.token_first.address,
            second_token=self.token_second.address)

        self.min_eth_balance = Wad.from_number(self.arguments.min_eth_balance)

        self.gas_price = GasPriceFactory().create_gas_price(self.arguments)
        self.max_delta_on_percent = self.arguments.max_delta_on_percent

        self.price_feed = PriceFeedFactory().create_price_feed(self.arguments)
Ejemplo n.º 3
0
    def setup_method(self):

        # Use Ganache docker container
        self.web3 = Web3(HTTPProvider("http://0.0.0.0:8555"))
        self.web3.eth.defaultAccount = Web3.toChecksumAddress("0x9596C16D7bF9323265C2F2E22f43e6c80eB3d943")
        register_private_key(self.web3, "0x91cf2cc3671a365fcbf38010ff97ee31a5b7e674842663c56769e41600696ead")

        self.our_address = Address(self.web3.eth.defaultAccount)

        self.weth_address = self._deploy(self.web3, self.weth_abi, self.weth_bin, [])
        self.factory_address = self._deploy(self.web3, self.factory_abi, self.factory_bin, [self.our_address.address])
        self.router_address = self._deploy(self.web3, self.router_abi, self.router_bin, [self.factory_address.address, self.weth_address.address])
        self._weth_contract = self._get_contract(self.web3, self.weth_abi, self.weth_address)

        self.ds_dai = DSToken.deploy(self.web3, 'DAI')
        self.ds_usdc = DSToken.deploy(self.web3, 'USDC')
        self.token_dai = Token("DAI", self.ds_dai.address, 18)
        self.token_usdc = Token("USDC", self.ds_usdc.address, 6)
        self.token_weth = Token("WETH", self.weth_address, 18)

        self.dai_usdc_uniswap = UniswapV2(self.web3, self.token_dai, self.token_usdc, self.our_address, self.router_address, self.factory_address)
        self.dai_eth_uniswap = UniswapV2(self.web3, self.token_dai, self.token_weth, self.our_address, self.router_address, self.factory_address)
        
        ## Useful for debugging failing transactions
        logger = logging.getLogger('eth')
        logger.setLevel(8)
Ejemplo n.º 4
0
    def exit_gem(self):
        if not self.collateral:
            return

        token = Token(self.collateral.ilk.name.split('-')[0], self.collateral.gem.address, self.collateral.adapter.dec())
        vat_balance = self.vat.gem(self.ilk, self.our_address)
        if vat_balance > token.min_amount:
            self.logger.info(f"Exiting {str(vat_balance)} {self.ilk.name} from the Vat")
            assert self.gem_join.exit(self.our_address, token.unnormalize_amount(vat_balance)).transact(gas_price=self.gas_price)
Ejemplo n.º 5
0
    def setup_class(self):
        self.web3 = Web3(HTTPProvider("http://0.0.0.0:8555", request_kwargs={'timeout': 10}))
        self.web3.eth.defaultAccount = Web3.toChecksumAddress("0x9596C16D7bF9323265C2F2E22f43e6c80eB3d943")
        register_private_key(self.web3, "0x91cf2cc3671a365fcbf38010ff97ee31a5b7e674842663c56769e41600696ead")

        self.our_address = Address(self.web3.eth.defaultAccount)

        # reset ganache EVM state to genesis
        session = requests.Session()
        method = 'evm_revert'
        params = [1]
        payload = {"jsonrpc": "2.0",
                   "method": method,
                   "params": params,
                   "id": 1}
        headers = {'Content-type': 'application/json'}
        response = session.post('http://0.0.0.0:8555', json=payload, headers=headers)
        print("revert response: ", response)

        # constructor args for SwapRouter
        self.factory_address: Address = self._deploy(self.web3, self.UniswapV3Factory_abi, self.UniswapV3Factory_bin, [])
        self.weth_address: Address = self._deploy(self.web3, self.weth_abi, self.weth_bin, [])
        self.token_descriptor_address: Address = self._deploy(self.web3, self.NFTDescriptor_abi, self.NFTDescriptor_bin, [])

        self.swap_router_address = self._deploy(self.web3, self.SwapRouter_abi, self.SwapRouter_bin, [self.factory_address.address, self.weth_address.address])
        self.nonfungiblePositionManager_address = self._deploy(self.web3, self.NonfungiblePositionManager_abi, self.NonfungiblePositionManager_bin, [self.factory_address.address, self.weth_address.address, self.token_descriptor_address.address])
        self.tick_lens_address = self._deploy(self.web3, self.UniswapV3TickLens_abi, self.UniswapV3TickLens_bin, [])
        self.quoter_address = self._deploy(self.web3, self.Quoter_abi, self.Quoter_bin, [self.factory_address.address, self.weth_address.address])

        self.position_manager = PositionManager(self.web3, self.nonfungiblePositionManager_address, self.factory_address, self.tick_lens_address, self.weth_address)
        self.swap_router = SwapRouter(self.web3, self.swap_router_address, self.quoter_address)

        ## Useful for debugging failing transactions
        logger = logging.getLogger('eth')
        logger.setLevel(8)
        # Transact.gas_estimate_for_bad_txs = 210000

        self.ds_dai = DSToken.deploy(self.web3, 'DAI')
        self.ds_usdc = DSToken.deploy(self.web3, 'USDC')
        self.token_dai = Token("DAI", self.ds_dai.address, 18)
        self.token_usdc = Token("USDC", self.ds_usdc.address, 6)
        self.token_weth = Token("WETH", self.weth_address, 18)

        self.position_manager.approve(self.token_dai)
        self.position_manager.approve(self.token_usdc)
        self.position_manager.approve(self.token_weth)
        self.swap_router.approve(self.token_dai)
        self.swap_router.approve(self.token_usdc)
        self.swap_router.approve(self.token_weth)

        # TODO: normalize amounts for decimals
        dai_balance = Wad.from_number(9000000000000000)
        usdc_balance = Wad.from_number(900000000000000)

        self.ds_dai.mint(dai_balance).transact(from_address=self.our_address)
        self.ds_usdc.mint(self.token_usdc.unnormalize_amount(usdc_balance)).transact(from_address=self.our_address)
Ejemplo n.º 6
0
    def test_should_error_when_pools_on_different_networks(self):
        """ test that both pools have matching chain_id value """
        test_token_1 = Token("test_1", Address("0x0000000000000000000000000000000000000001"), 18)
        test_token_2 = Token("test_2", Address("0x0000000000000000000000000000000000000002"), 18)
        test_token_3 = Token("test_3", Address("0x0000000000000000000000000000000000000003"), 18)

        test_pool_1_medium = Pool(test_token_1, test_token_2, 3000, self.get_starting_sqrt_ratio(1, 1), 0, 0, [], 1)
        test_pool_2_low = Pool(test_token_2, test_token_3, 500, self.get_starting_sqrt_ratio(1, 1), 0, 0, [], 2)
        path = [test_pool_1_medium, test_pool_2_low]

        with pytest.raises(Exception):
            route = Route(path, test_token_1, test_token_3)
Ejemplo n.º 7
0
class TestMatchingMarketDecimal:
    def setup_method(self):
        self.web3 = Web3(HTTPProvider("http://localhost:8555"))
        self.web3.eth.defaultAccount = self.web3.eth.accounts[0]
        self.our_address = Address(self.web3.eth.defaultAccount)
        self.token1 = DSToken.deploy(self.web3, 'AAA')
        self.token1_tokenclass = Token('AAA', self.token1.address, 18)
        self.token1.mint(Wad.from_number(10000)).transact()
        self.token2 = DSToken.deploy(self.web3, 'BBB')
        self.token2_tokenclass = Token('BBB', self.token2.address, 6)
        self.token2.mint(Wad.from_number(10000)).transact()

        support_abi = Contract._load_abi(
            __name__, '../pymaker/abi/MakerOtcSupportMethods.abi')
        support_bin = Contract._load_bin(
            __name__, '../pymaker/abi/MakerOtcSupportMethods.bin')
        support_address = Contract._deploy(self.web3, support_abi, support_bin,
                                           [])

        self.otc = MatchingMarket.deploy(self.web3, 2500000000,
                                         support_address)
        self.otc.add_token_pair_whitelist(self.token1.address,
                                          self.token2.address).transact()
        self.otc.approve([self.token1, self.token2], directly())

    def test_get_orders(self):
        buy_amount_order1 = Wad.from_number(5.124988526145090209)
        pay_amount_order1 = Wad.from_number(5.024999999999999500)

        buy_amount_order2 = Wad.from_number(5.102550000000000000)
        pay_amount_order2 = Wad.from_number(5.000000000000000000)

        # given
        self.otc.make(p_token=self.token2_tokenclass,
                      pay_amount=self.token2_tokenclass.unnormalize_amount(
                          pay_amount_order1),
                      b_token=self.token1_tokenclass,
                      buy_amount=buy_amount_order1).transact()

        self.otc.make(p_token=self.token1_tokenclass,
                      pay_amount=pay_amount_order2,
                      b_token=self.token2_tokenclass,
                      buy_amount=self.token2_tokenclass.unnormalize_amount(
                          buy_amount_order2)).transact()

        # then
        assert self.otc.get_orders(
            self.token1_tokenclass,
            self.token2_tokenclass)[0].buy_amount == buy_amount_order2
        assert self.token2_tokenclass.unnormalize_amount(
            self.otc.get_orders(self.token2_tokenclass,
                                self.token1_tokenclass)[0].pay_amount
        ) == self.token2_tokenclass.unnormalize_amount(pay_amount_order1)
Ejemplo n.º 8
0
    def test_encode_route_to_path_multihop_output(self):
        """ Create 3 tokens, and two pools with one shared token between them. Encode the exact output path across these pools."""
        test_token_1 = Token("test_1", Address("0x0000000000000000000000000000000000000001"), 18)
        test_token_2 = Token("test_2", Address("0x0000000000000000000000000000000000000002"), 18)
        test_token_3 = Token("test_3", Address("0x0000000000000000000000000000000000000003"), 18)

        test_pool_1_medium = Pool(test_token_1, test_token_2, 3000, self.get_starting_sqrt_ratio(1, 1), 0, 0, [])
        test_pool_2_low = Pool(test_token_2, test_token_3, 500, self.get_starting_sqrt_ratio(1, 1), 0, 0, [])
        path = [test_pool_1_medium, test_pool_2_low]
        route = Route(path, test_token_1, test_token_3)

        encoded_path = self.swap_router.encode_route_to_path(route, True)
        assert encoded_path == '0x00000000000000000000000000000000000000030001f40000000000000000000000000000000000000002000bb80000000000000000000000000000000000000001'
Ejemplo n.º 9
0
 def setup_method(self):
     self.web3 = Web3(HTTPProvider("http://localhost:8555"))
     self.web3.eth.defaultAccount = self.web3.eth.accounts[0]
     self.our_address = Address(self.web3.eth.defaultAccount)
     self.token1 = DSToken.deploy(self.web3, 'AAA')
     self.token1_tokenclass = Token('AAA', self.token1.address, 18)
     self.token1.mint(Wad.from_number(10000)).transact()
     self.token2 = DSToken.deploy(self.web3, 'BBB')
     self.token2_tokenclass = Token('BBB', self.token2.address, 18)
     self.token2.mint(Wad.from_number(10000)).transact()
     self.token3 = DSToken.deploy(self.web3, 'CCC')
     self.token3_tokenclass = Token('CCC', self.token3.address, 18)
     self.token3.mint(Wad.from_number(10000)).transact()
     self.otc = None
Ejemplo n.º 10
0
class TestToken:
    def setup_class(self):
        self.token = Token(
            "COW", Address('0xbeef00000000000000000000000000000000BEEF'), 4)

    def test_convert(self):
        # two
        chain_amount = Wad(20000)
        assert self.token.normalize_amount(chain_amount) == Wad.from_number(2)

        # three
        normalized_amount = Wad.from_number(3)
        assert self.token.unnormalize_amount(normalized_amount) == Wad(30000)

    def test_min_amount(self):
        assert self.token.min_amount == Wad.from_number(0.0001)
        assert float(self.token.min_amount) == 0.0001
        assert self.token.unnormalize_amount(self.token.min_amount) == Wad(1)

        assert Wad.from_number(0.0004) > self.token.min_amount
        assert Wad.from_number(0.00005) < self.token.min_amount

        assert self.token.unnormalize_amount(
            Wad.from_number(0.0006)) > self.token.unnormalize_amount(
                self.token.min_amount)
        assert self.token.unnormalize_amount(
            Wad.from_number(0.00007)) < self.token.unnormalize_amount(
                self.token.min_amount)
        assert self.token.unnormalize_amount(
            Wad.from_number(0.00008)) == Wad(0)
Ejemplo n.º 11
0
    def instantiate_tokens(self, pair: str) -> Tuple[Token, Token]:
        assert (isinstance(pair, str))

        def get_address(value) -> Address:
            return Address(value['tokenAddress']) if 'tokenAddress' in value else None

        def get_decimals(value) -> int:
            return value['tokenDecimals'] if 'tokenDecimals' in value else 18

        token_a_name = 'WETH' if pair.split('-')[0] == 'ETH' or pair.split('-')[0] == 'WETH' else pair.split('-')[0]
        token_b_name = 'WETH' if pair.split('-')[1] == 'ETH' or pair.split('-')[1] == 'WETH' else pair.split('-')[1]
        token_a = Token(token_a_name, get_address(self.token_config[token_a_name]), get_decimals(self.token_config[token_a_name]))
        token_b = Token(token_b_name, get_address(self.token_config[token_b_name]), get_decimals(self.token_config[token_b_name]))

        return token_a, token_b
Ejemplo n.º 12
0
 def set_pair_token(self, pair_address: Address):
     self.pair_address = pair_address
     self._pair_contract = self._get_contract(self.web3,
                                              self.pair_abi['abi'],
                                              self.pair_address)
     self.pair_token = Token('Liquidity', self.pair_address, 18)
     self.is_new_pool = False
    def instantiate_tokens(self, pair: str) -> Tuple[Token, Token]:
        assert (isinstance(pair, str))

        def get_address(value) -> Address:
            return Address(value['tokenAddress']) if 'tokenAddress' in value else None

        def get_decimals(value) -> int:
            return value['tokenDecimals'] if 'tokenDecimals' in value else 18

        token_a_name = 'WETH' if self.is_eth and self.eth_position == 0 else self.pair().split('-')[0]
        token_b_name = 'WETH' if self.is_eth and self.eth_position == 1 else self.pair().split('-')[1]

        token_a = Token(token_a_name, get_address(self.token_config[token_a_name]), get_decimals(self.token_config[token_a_name]))
        token_b = Token(token_b_name, get_address(self.token_config[token_b_name]), get_decimals(self.token_config[token_b_name]))

        return token_a, token_b
Ejemplo n.º 14
0
    def get_exchange_balance(self, token: Token, pair_address: Address) -> Wad:
        assert (isinstance(token, Token))
        assert (isinstance(pair_address, Address))

        return token.normalize_amount(
            ERC20Token(web3=self.web3,
                       address=token.address).balance_of(pair_address))
Ejemplo n.º 15
0
 def setup_method(self):
     self.web3 = Web3(HTTPProvider("http://localhost:8555"))
     self.web3.eth.defaultAccount = self.web3.eth.accounts[0]
     self.our_address = Address(self.web3.eth.defaultAccount)
     self.token1 = DSToken.deploy(self.web3, 'AAA')
     self.token1.mint(Wad.from_number(10000)).transact()
     self.token1_tokenclass = Token('AAA', self.token1.address, 18)
     self.token2 = DSToken.deploy(self.web3, 'BBB')
     self.token2.mint(Wad.from_number(10000)).transact()
     self.token2_tokenclass = Token('BBB', self.token2.address, 18)
     price_oracle = OasisMockPriceOracle.deploy(self.web3)
     self.otc = MatchingMarket.deploy(self.web3, self.token1.address, Wad(0), price_oracle.address)
     self.otc.add_token_pair_whitelist(self.token1.address, self.token2.address).transact()
     self.otc.approve([self.token1, self.token2], directly())
     for amount in [11,55,44,34,36,21,45,51,15]:
         self.otc.make(p_token=self.token1_tokenclass, pay_amount=Wad.from_number(1),
                       b_token=self.token2_tokenclass, buy_amount=Wad.from_number(amount)).transact()
Ejemplo n.º 16
0
    def test_mint_token_pool_low_price_and_slippage(self):
        """ Test minting a position for a pool that is a small fraction """
        test_token_1 = Token(
            "test_1", Address("0x0000000000000000000000000000000000000001"),
            18)
        test_token_2 = Token(
            "test_2", Address("0x0000000000000000000000000000000000000002"),
            18)

        token_1_balance = Wad.from_number(10)
        token_2_balance = Wad.from_number(100)

        # sqrt_price_ratio = self.get_starting_sqrt_ratio(3000, 1)
        sqrt_price_ratio = self.get_starting_sqrt_ratio(
            Wad.from_number(3000).value,
            Wad.from_number(1).value)
        current_tick = get_tick_at_sqrt_ratio(sqrt_price_ratio)
        ticks = []
        test_pool = Pool(test_token_1, test_token_2, FEES.MEDIUM.value,
                         sqrt_price_ratio, 0, current_tick, ticks)

        # set Position.from_amounts() params
        tick_lower = current_tick - TICK_SPACING.MEDIUM.value * 5
        tick_upper = current_tick + TICK_SPACING.MEDIUM.value * 7
        rounded_tick_lower = Tick.nearest_usable_tick(
            tick_lower, TICK_SPACING.MEDIUM.value)
        rounded_tick_upper = Tick.nearest_usable_tick(
            tick_upper, TICK_SPACING.MEDIUM.value)
        calculated_position = Position.from_amounts(
            test_pool, rounded_tick_lower, rounded_tick_upper,
            token_1_balance.value, token_2_balance.value, False)

        test_liquidity = calculated_position.liquidity

        test_position = Position(test_pool, rounded_tick_lower,
                                 rounded_tick_upper, test_liquidity)

        amount_0, amount_1 = test_position.mint_amounts()

        slippage_tolerance = Fraction(2, 100)
        amount_0_min, amount_1_min = test_position.mint_amounts_with_slippage(
            slippage_tolerance)

        # check that mint amounts will pass periphery contract assertions
        assert amount_0_min > 0 and amount_1_min > 0
        assert amount_0_min < amount_0 and amount_1_min < amount_1
Ejemplo n.º 17
0
    def get_exchange_balance_at_block(self, token: Token,
                                      pair_address: Address,
                                      block_number: int) -> Wad:
        assert (isinstance(token, Token))
        assert (isinstance(pair_address, Address))
        assert (isinstance(block_number, int))

        return token.normalize_amount(
            ERC20Token(web3=self.web3, address=token.address).balance_at_block(
                pair_address, block_number))
Ejemplo n.º 18
0
    def setup_method(self):
        self.web3 = Web3(HTTPProvider("http://localhost:8555"))
        self.web3.eth.defaultAccount = self.web3.eth.accounts[0]
        self.our_address = Address(self.web3.eth.defaultAccount)
        self.token1 = DSToken.deploy(self.web3, 'AAA')
        self.token1_tokenclass = Token('AAA', self.token1.address, 18)
        self.token1.mint(Wad.from_number(10000)).transact()
        self.token2 = DSToken.deploy(self.web3, 'BBB')
        self.token2_tokenclass = Token('BBB', self.token2.address, 6)
        self.token2.mint(Wad.from_number(10000)).transact()

        support_abi = Contract._load_abi(__name__, '../pymaker/abi/MakerOtcSupportMethods.abi')
        support_bin = Contract._load_bin(__name__, '../pymaker/abi/MakerOtcSupportMethods.bin')
        support_address = Contract._deploy(self.web3, support_abi, support_bin, [])

        price_oracle = OasisMockPriceOracle.deploy(self.web3)
        self.otc = MatchingMarket.deploy(self.web3, self.token1.address, Wad(0), price_oracle.address, support_address)
        self.otc.add_token_pair_whitelist(self.token1.address, self.token2.address).transact()
        self.otc.approve([self.token1, self.token2], directly())
Ejemplo n.º 19
0
    def __init__(self, data: dict):
        assert (isinstance(data, dict))

        self.tokens = [
            Token(name=key,
                  address=Address(value['tokenAddress'])
                  if 'tokenAddress' in value else None,
                  decimals=value['tokenDecimals']
                  if 'tokenDecimals' in value else 18)
            for key, value in data['tokens'].items()
        ]
Ejemplo n.º 20
0
    def deploy_tokens(self):
        self.ds_dai = DSToken.deploy(self.web3, 'DAI')
        self.ds_keep = DSToken.deploy(self.web3, 'KEEP')
        self.ds_lev = DSToken.deploy(self.web3, 'LEV')
        self.ds_usdc = DSToken.deploy(self.web3, 'USDC')
        self.ds_wbtc = DSToken.deploy(self.web3, 'WBTC')

        self.token_dai = Token("DAI", self.ds_dai.address, 18)
        self.token_keep = Token("KEEP", self.ds_keep.address, 18)
        self.token_lev = Token("LEV", self.ds_lev.address, 9)
        self.token_usdc = Token("USDC", self.ds_usdc.address, 6)
        self.token_wbtc = Token("WBTC", self.ds_wbtc.address, 8)
        self.token_weth = Token("WETH", self.weth_address, 18)
Ejemplo n.º 21
0
    def setup_method(self):
        self.ds_dai = DSToken.deploy(self.web3, 'DAI')
        self.ds_usdc = DSToken.deploy(self.web3, 'USDC')
        self.token_dai = Token("DAI", self.ds_dai.address, 18)
        self.token_usdc = Token("USDC", self.ds_usdc.address, 6)
        self.token_weth = Token("WETH", self.weth_address, 18)

        self.position_manager.approve(self.token_dai)
        self.position_manager.approve(self.token_usdc)
        self.position_manager.approve(self.token_weth)
        self.swap_router.approve(self.token_dai)
        self.swap_router.approve(self.token_usdc)
        self.swap_router.approve(self.token_weth)

        dai_balance = Wad.from_number(10000000)
        usdc_balance = Wad.from_number(10000000)

        self.ds_dai.mint(dai_balance).transact(from_address=self.our_address)
        self.ds_usdc.mint(
            self.token_usdc.unnormalize_amount(usdc_balance)).transact(
                from_address=self.our_address)
Ejemplo n.º 22
0
    def setup_method(self):
        # Use Ganache docker container
        self.web3 = Web3(HTTPProvider("http://0.0.0.0:8555"))
        self.web3.eth.defaultAccount = Web3.toChecksumAddress(
            "0x9596C16D7bF9323265C2F2E22f43e6c80eB3d943")
        self.our_address = Address(self.web3.eth.defaultAccount)

        self.private_key = "0x91cf2cc3671a365fcbf38010ff97ee31a5b7e674842663c56769e41600696ead"
        register_private_key(self.web3, self.private_key)

        self.weth_address = Contract._deploy(self.web3, self.weth_abi,
                                             self.weth_bin, [])
        self.factory_address = Contract._deploy(self.web3, self.factory_abi,
                                                self.factory_bin,
                                                [self.our_address.address])
        self.router_address = Contract._deploy(
            self.web3, self.router_abi, self.router_bin,
            [self.factory_address.address, self.weth_address.address])
        self._weth_contract = Contract._get_contract(self.web3, self.weth_abi,
                                                     self.weth_address)

        self.ds_dai = DSToken.deploy(self.web3, 'DAI')
        self.ds_dai.mint(
            Wad.from_number(500)).transact(from_address=self.our_address)
        self.token_dai = Token("DAI", self.ds_dai.address, 18)
        self.token_weth = Token("WETH", self.weth_address, 18)

        token_config = {
            "tokens": {
                "DAI": {
                    "tokenAddress": self.ds_dai.address.address
                },
                "WETH": {
                    "tokenAddress": self.weth_address.address
                }
            }
        }
        # write token config with locally deployed addresses to file
        with open("test-token-config.json", "w+") as outfile:
            outfile.write(json.dumps(token_config))
Ejemplo n.º 23
0
    def setup_method(self):
        # reduce logspew
        logging.getLogger("web3").setLevel(logging.INFO)
        logging.getLogger("urllib3").setLevel(logging.INFO)
        logging.getLogger("asyncio").setLevel(logging.INFO)

        self.web3 = Web3(HTTPProvider("http://localhost:8555"))
        self.web3.eth.defaultAccount = self.web3.eth.accounts[0]
        self.our_address = Address(self.web3.eth.defaultAccount)

        self.price_oracle = OasisMockPriceOracle.deploy(self.web3)
        self.price_oracle.set_price(Wad.from_number(10))

        self.token1 = DSToken.deploy(self.web3, 'AAA')
        self.token1_tokenclass = Token('AAA', self.token1.address, 18)
        self.token1.mint(Wad.from_number(10000)).transact()
        self.token2 = DSToken.deploy(self.web3, 'BBB')
        self.token2_tokenclass = Token('BBB', self.token2.address, 18)
        self.token2.mint(Wad.from_number(10000)).transact()
        self.token3 = DSToken.deploy(self.web3, 'CCC')
        self.token3_tokenclass = Token('CCC', self.token3.address, 18)
        self.token3.mint(Wad.from_number(10000)).transact()
        self.otc = None
Ejemplo n.º 24
0
    def deploy_liquidity_token(self) -> Token:

        # deploy uniswap contracts
        self.weth_address = self._deploy(self.web3, self.weth_abi, self.weth_bin, [])
        self.factory_address = self._deploy(self.web3, self.factory_abi, self.factory_bin, [self.our_address.address])
        self.router_address = self._deploy(self.web3, self.router_abi, self.router_bin, [self.factory_address.address, self.weth_address.address])
        self._weth_contract = self._get_contract(self.web3, self.weth_abi, self.weth_address)

        # deploy dai contract and instantiate DAI token
        self.ds_dai = DSToken.deploy(self.web3, 'DAI')
        self.token_dai = Token("DAI", self.ds_dai.address, 18)
        self.token_weth = Token("WETH", self.weth_address, 18)

        # self.dai_usdc_uniswap = UniswapV2(self.web3, self.token_dai, self.token_usdc, self.our_address, self.router_address, self.factory_address)
        self.dai_eth_uniswap = UniswapV2(self.web3, self.token_dai, self.token_weth, self.our_address, self.router_address, self.factory_address)

        # add liquidity
        self.add_liquidity_eth()

        # set liquidity token
        self.dai_eth_uniswap.set_pair_token(self.dai_eth_uniswap.get_pair_address(self.token_dai.address, self.token_weth.address))
        liquidity_token = self.dai_eth_uniswap.pair_token

        return liquidity_token
Ejemplo n.º 25
0
    def position(self, p_token: Token, pay_amount: Wad, b_token: Token,
                 buy_amount: Wad) -> int:
        """Calculate the position (`pos`) new order should be inserted at to minimize gas costs.

        The `MatchingMarket` contract maintains an internal ordered linked list of orders, which allows the contract
        to do automated matching. Client placing a new order can either let the contract find the correct
        position in the linked list (by passing `0` as the `pos` argument of `make`) or calculate the position
        itself and just pass the right value to the contract (this will happen if you omit the `pos`
        argument of `make`). The latter should always use less gas. If the client decides not to calculate the
        position or it does get it wrong and the number of open orders is high at the same time, the new order
        may not even be placed at all as the attempt to calculate the position by the contract will likely fail
        due to high gas usage.

        This method is responsible for calculating the correct insertion position. It is used internally
        by `make` when `pos` argument is omitted (or is `None`).

        Args:
            p_token: Token object (see `model.py`) of the token you want to put on sale.
            pay_amount: Amount of the `pay_token` token you want to put on sale.
            b_token: Token object (see `model.py`) of the token you want to be paid with.
            buy_amount: Amount of the `buy_token` you want to receive.

        Returns:
            The position (`pos`) new order should be inserted at.
        """
        assert (isinstance(p_token, Token))
        assert (isinstance(pay_amount, Wad))
        assert (isinstance(b_token, Token))
        assert (isinstance(buy_amount, Wad))

        pay_token = p_token.address
        buy_token = b_token.address

        self.logger.debug("Enumerating orders for position calculation...")

        orders = filter(
            lambda order: order.pay_amount / order.buy_amount >= p_token.
            normalize_amount(pay_amount) / b_token.normalize_amount(buy_amount
                                                                    ),
            self.get_orders(p_token, b_token))

        self.logger.debug(
            "Enumerating orders for position calculation finished")

        sorted_orders = sorted(orders,
                               key=lambda o: o.pay_amount / o.buy_amount)
        return sorted_orders[0].order_id if len(sorted_orders) > 0 else 0
Ejemplo n.º 26
0
    def setup_method(self):

        # Use Ganache docker container
        self.web3 = Web3(HTTPProvider("http://0.0.0.0:8555"))
        self.web3.eth.defaultAccount = Web3.toChecksumAddress("0x9596C16D7bF9323265C2F2E22f43e6c80eB3d943")
        register_private_key(self.web3, "0x91cf2cc3671a365fcbf38010ff97ee31a5b7e674842663c56769e41600696ead")
        self.our_address = Address(self.web3.eth.defaultAccount)

        self.ds_reward_token = DSToken.deploy(self.web3, 'REWARD')
        self.reward_token = Token("REWARD", self.ds_reward_token.address, 18)

        # Deploy UniswapV2 contracts and set liquidity token
        self.liquidity_token = self.deploy_liquidity_token()

        self.staking_rewards_address = self._deploy(self.web3, self.staking_rewards_abi, self.staking_rewards_bin, [self.our_address.address, self.reward_token.address.address, self.liquidity_token.address.address])

        self.uniswap_staking_rewards = UniswapStakingRewards(self.web3, self.our_address, Address(self.staking_rewards_address), "UniswapStakingRewards")

        ## Useful for debugging failing transactions
        logger = logging.getLogger('eth')
        logger.setLevel(8)
Ejemplo n.º 27
0
    def test_liquidity_given_balance(self):
        """ Test liquidity and mint amount calculations """
        test_token_1 = Token(
            "test_1", Address("0x0000000000000000000000000000000000000001"),
            18)
        test_token_2 = Token(
            "test_2", Address("0x0000000000000000000000000000000000000002"), 6)

        token_1_balance = test_token_1.unnormalize_amount(Wad.from_number(10))
        token_2_balance = test_token_2.unnormalize_amount(Wad.from_number(500))

        sqrt_price_ratio = self.get_starting_sqrt_ratio(
            Wad.from_number(1).value,
            test_token_2.unnormalize_amount(Wad.from_number(3000)).value)
        current_tick = get_tick_at_sqrt_ratio(sqrt_price_ratio)
        ticks = []
        test_pool = Pool(test_token_1, test_token_2, FEES.MEDIUM.value,
                         sqrt_price_ratio, 0, current_tick, ticks)

        tick_lower = current_tick - TICK_SPACING.MEDIUM.value * 5
        tick_upper = current_tick + TICK_SPACING.MEDIUM.value * 7
        rounded_tick_lower = Tick.nearest_usable_tick(
            tick_lower, TICK_SPACING.MEDIUM.value)
        rounded_tick_upper = Tick.nearest_usable_tick(
            tick_upper, TICK_SPACING.MEDIUM.value)
        calculated_position = Position.from_amounts(
            test_pool, rounded_tick_lower, rounded_tick_upper,
            token_1_balance.value, token_2_balance.value, False)

        test_liquidity = calculated_position.liquidity
        assert test_liquidity == 252860870269028

        test_position = Position(test_pool, rounded_tick_lower,
                                 rounded_tick_upper, test_liquidity)

        amount_0, amount_1 = test_position.mint_amounts()
        assert amount_0 == 95107120950731527
        assert amount_1 == 208677042
Ejemplo n.º 28
0
class TestUniswapV3SwapRouter(Contract):
    """ Deployment docs available here: https://github.com/Uniswap/uniswap-v3-periphery/blob/main/deploys.md """

    UniswapV3Factory_abi = Contract._load_abi(__name__, '../pyexchange/abi/UniswapV3Factory.abi')['abi']
    UniswapV3Factory_bin = Contract._load_bin(__name__, '../pyexchange/abi/UniswapV3Factory.bin')
    NFTDescriptor_abi = Contract._load_abi(__name__, '../pyexchange/abi/NFTDescriptor.abi')['abi']
    NFTDescriptor_bin = Contract._load_bin(__name__, '../pyexchange/abi/NFTDescriptor.bin')
    weth_abi = Contract._load_abi(__name__, '../pyexchange/abi/WETH.abi')
    weth_bin = Contract._load_bin(__name__, '../pyexchange/abi/WETH.bin')
    NonfungiblePositionManager_abi = Contract._load_abi(__name__, '../pyexchange/abi/NonfungiblePositionManager.abi')['abi']
    NonfungiblePositionManager_bin = Contract._load_bin(__name__, '../pyexchange/abi/NonfungiblePositionManager.bin')
    SwapRouter_abi = Contract._load_abi(__name__, '../pyexchange/abi/SwapRouter.abi')['abi']
    SwapRouter_bin = Contract._load_bin(__name__, '../pyexchange/abi/SwapRouter.bin')
    UniswapV3TickLens_abi = Contract._load_abi(__name__, '../pyexchange/abi/UniswapV3TickLens.abi')['abi']
    UniswapV3TickLens_bin = Contract._load_bin(__name__, '../pyexchange/abi/UniswapV3TickLens.bin')
    Quoter_abi = Contract._load_abi(__name__, '../pyexchange/abi/Quoter.abi')['abi']
    Quoter_bin = Contract._load_bin(__name__, '../pyexchange/abi/Quoter.bin')

    def setup_class(self):
        self.web3 = Web3(HTTPProvider("http://0.0.0.0:8555", request_kwargs={'timeout': 10}))
        self.web3.eth.defaultAccount = Web3.toChecksumAddress("0x9596C16D7bF9323265C2F2E22f43e6c80eB3d943")
        register_private_key(self.web3, "0x91cf2cc3671a365fcbf38010ff97ee31a5b7e674842663c56769e41600696ead")

        self.our_address = Address(self.web3.eth.defaultAccount)

        # reset ganache EVM state to genesis
        session = requests.Session()
        method = 'evm_revert'
        params = [1]
        payload = {"jsonrpc": "2.0",
                   "method": method,
                   "params": params,
                   "id": 1}
        headers = {'Content-type': 'application/json'}
        response = session.post('http://0.0.0.0:8555', json=payload, headers=headers)
        print("revert response: ", response)

        # constructor args for SwapRouter
        self.factory_address: Address = self._deploy(self.web3, self.UniswapV3Factory_abi, self.UniswapV3Factory_bin, [])
        self.weth_address: Address = self._deploy(self.web3, self.weth_abi, self.weth_bin, [])
        self.token_descriptor_address: Address = self._deploy(self.web3, self.NFTDescriptor_abi, self.NFTDescriptor_bin, [])

        self.swap_router_address = self._deploy(self.web3, self.SwapRouter_abi, self.SwapRouter_bin, [self.factory_address.address, self.weth_address.address])
        self.nonfungiblePositionManager_address = self._deploy(self.web3, self.NonfungiblePositionManager_abi, self.NonfungiblePositionManager_bin, [self.factory_address.address, self.weth_address.address, self.token_descriptor_address.address])
        self.tick_lens_address = self._deploy(self.web3, self.UniswapV3TickLens_abi, self.UniswapV3TickLens_bin, [])
        self.quoter_address = self._deploy(self.web3, self.Quoter_abi, self.Quoter_bin, [self.factory_address.address, self.weth_address.address])

        self.position_manager = PositionManager(self.web3, self.nonfungiblePositionManager_address, self.factory_address, self.tick_lens_address, self.weth_address)
        self.swap_router = SwapRouter(self.web3, self.swap_router_address, self.quoter_address)

        ## Useful for debugging failing transactions
        logger = logging.getLogger('eth')
        logger.setLevel(8)
        # Transact.gas_estimate_for_bad_txs = 210000

        self.ds_dai = DSToken.deploy(self.web3, 'DAI')
        self.ds_usdc = DSToken.deploy(self.web3, 'USDC')
        self.token_dai = Token("DAI", self.ds_dai.address, 18)
        self.token_usdc = Token("USDC", self.ds_usdc.address, 6)
        self.token_weth = Token("WETH", self.weth_address, 18)

        self.position_manager.approve(self.token_dai)
        self.position_manager.approve(self.token_usdc)
        self.position_manager.approve(self.token_weth)
        self.swap_router.approve(self.token_dai)
        self.swap_router.approve(self.token_usdc)
        self.swap_router.approve(self.token_weth)

        # TODO: normalize amounts for decimals
        dai_balance = Wad.from_number(9000000000000000)
        usdc_balance = Wad.from_number(900000000000000)

        self.ds_dai.mint(dai_balance).transact(from_address=self.our_address)
        self.ds_usdc.mint(self.token_usdc.unnormalize_amount(usdc_balance)).transact(from_address=self.our_address)

    def get_starting_sqrt_ratio(self, amount_0, amount_1) -> int:
        return encodeSqrtRatioX96(amount_1, amount_0)

    def deploy_and_mint_weth_dai(self, position_manager_helpers) -> Pool:
        # deploy weth_dai pool and mint initial liquidity to swap against
        position_manager_helper_wethdai = position_manager_helpers(self.web3, self.position_manager,
                                                                   self.NonfungiblePositionManager_abi, self.token_weth,
                                                                   self.token_dai)

        starting_price = self.get_starting_sqrt_ratio(1, 1900)
        weth_dai_pool = position_manager_helper_wethdai.create_and_initialize_pool(
            starting_price, FEES.MEDIUM.value)

        # wrap weth for testing (univ3 only uses weth)
        position_manager_helper_wethdai.wrap_eth(Wad.from_number(25), self.our_address)



        tick_lower = weth_dai_pool.tick_current - TICK_SPACING.MEDIUM.value * 3
        tick_upper = weth_dai_pool.tick_current + TICK_SPACING.MEDIUM.value * 5
        rounded_tick_lower = Tick.nearest_usable_tick(tick_lower, TICK_SPACING.MEDIUM.value)
        rounded_tick_upper = Tick.nearest_usable_tick(tick_upper, TICK_SPACING.MEDIUM.value)

        # TODO: calculate liquidity at a given price, with available balances
        # current liquidity levels result in too small of a balance being minted - need to determine why higher liquidity fails price slippage check
        liquidity_to_mint = 900000000000000

        weth_dai_mint_params = position_manager_helper_wethdai.generate_mint_params(weth_dai_pool,
                                                                                    Position(weth_dai_pool, rounded_tick_lower, rounded_tick_upper,
                                                                                             9000000000000000000), self.our_address,
                                                                                    Fraction(20, 100))
        weth_dai_mint_receipt = self.position_manager.mint(weth_dai_mint_params).transact()
        assert weth_dai_mint_receipt is not None and weth_dai_mint_receipt.successful

        # mint_result = weth_dai_mint_receipt.result[0]
        # print("mint result", mint_result, mint_result.liquidity, mint_result.tick_lower, mint_result.tick_upper)
        token_id = weth_dai_mint_receipt.result[0].token_id
        print("minted_pool token_id", token_id)
        minted_position = self.position_manager.positions(token_id, weth_dai_pool.token_0, weth_dai_pool.token_1)
        print("minted weth_dai value", self.position_manager.price_position(token_id, 1900))
        return minted_position.pool

    def deploy_and_mint_dai_usdc(self, position_manager_helpers) -> Pool:
        # deploy dai_usdc pool and mint initial liquidity to swap against
        position_manager_helper_daiusdc = position_manager_helpers(self.web3, self.position_manager,
                                                                   self.NonfungiblePositionManager_abi, self.token_dai,
                                                                   self.token_usdc)

        dai_usdc_pool = position_manager_helper_daiusdc.create_and_initialize_pool(self.get_starting_sqrt_ratio(1, 1),
                                                                                        FEES.LOW.value)

        liquidity_to_mint = 1000000000
        dai_usdc_mint_params = position_manager_helper_daiusdc.generate_mint_params(dai_usdc_pool,
                                                                                    Position(dai_usdc_pool, -10, 10,
                                                                                             900000000000000000), self.our_address,
                                                                                    Fraction(10, 100))
        dai_usdc_mint_receipt = self.position_manager.mint(dai_usdc_mint_params).transact()
        assert dai_usdc_mint_receipt is not None and dai_usdc_mint_receipt.successful


        token_id = dai_usdc_mint_receipt.result[0].token_id
        print("minted_pool token_id", token_id)
        minted_position = self.position_manager.positions(token_id, dai_usdc_pool.token_0, dai_usdc_pool.token_1)
        print("minted dai_usdc value", self.position_manager.price_position(token_id, 1))
        return minted_position.pool

    def test_encode_route_to_path_multihop_input(self):
        """ Create 3 tokens, and two pools with one shared token between them. Encode the exact input path across these pools."""
        test_token_1 = Token("test_1", Address("0x0000000000000000000000000000000000000001"), 18)
        test_token_2 = Token("test_2", Address("0x0000000000000000000000000000000000000002"), 18)
        test_token_3 = Token("test_3", Address("0x0000000000000000000000000000000000000003"), 18)

        test_pool_1_medium = Pool(test_token_1, test_token_2, 3000, self.get_starting_sqrt_ratio(1, 1), 0, 0, [])
        test_pool_2_low = Pool(test_token_2, test_token_3, 500, self.get_starting_sqrt_ratio(1, 1), 0, 0, [])
        path = [test_pool_1_medium, test_pool_2_low]
        route = Route(path, test_token_1, test_token_3)

        encoded_path = self.swap_router.encode_route_to_path(route, False)
        assert encoded_path == '0x0000000000000000000000000000000000000001000bb800000000000000000000000000000000000000020001f40000000000000000000000000000000000000003'

    def test_encode_route_to_path_multihop_output(self):
        """ Create 3 tokens, and two pools with one shared token between them. Encode the exact output path across these pools."""
        test_token_1 = Token("test_1", Address("0x0000000000000000000000000000000000000001"), 18)
        test_token_2 = Token("test_2", Address("0x0000000000000000000000000000000000000002"), 18)
        test_token_3 = Token("test_3", Address("0x0000000000000000000000000000000000000003"), 18)

        test_pool_1_medium = Pool(test_token_1, test_token_2, 3000, self.get_starting_sqrt_ratio(1, 1), 0, 0, [])
        test_pool_2_low = Pool(test_token_2, test_token_3, 500, self.get_starting_sqrt_ratio(1, 1), 0, 0, [])
        path = [test_pool_1_medium, test_pool_2_low]
        route = Route(path, test_token_1, test_token_3)

        encoded_path = self.swap_router.encode_route_to_path(route, True)
        assert encoded_path == '0x00000000000000000000000000000000000000030001f40000000000000000000000000000000000000002000bb80000000000000000000000000000000000000001'

    def test_should_find_swap_path_across_multiple_pools_exact_input(self, position_manager_helpers):
        # deploy both pools
        weth_dai_pool = self.deploy_and_mint_weth_dai(position_manager_helpers)
        dai_usdc_pool = self.deploy_and_mint_dai_usdc(position_manager_helpers)

        # set trade params
        weth_in = Wad.from_number(.000000000001)
        # weth_in = Wad.from_number(1)
        # weth_in = 1
        print(Wad.from_number(1).value)
        recipient = self.our_address
        slippage_tolerance = Fraction(20, 100)
        deadline = int(time.time() + 1000)

        # define route from weth to usdc via dai
        path = [weth_dai_pool, dai_usdc_pool]
        route = Route(path, self.token_weth, self.token_usdc)

        encoded_path = self.swap_router.encode_route_to_path(route, False)

        trade = Trade.from_route(route, CurrencyAmount.from_raw_amount(self.token_weth, weth_in.value), TRADE_TYPE.EXACT_INPUT.value)
        usdc_out = trade.minimum_amount_out(slippage_tolerance).quotient()
        print("usdc out trade", usdc_out)

        assert usdc_out > 0

        usdc_out_quoter = self.swap_router.quote_exact_input(encoded_path, weth_in)
        print("usdc_out quoter", usdc_out_quoter)

        exact_input_params = ExactInputParams(self.web3, self.SwapRouter_abi, encoded_path, recipient, deadline, weth_in.value, usdc_out)

        swap = self.swap_router.swap_exact_input(exact_input_params).transact()
        assert swap is not None and swap.successful

    def test_should_find_swap_path_across_multiple_pools_exact_output(self, position_manager_helpers):
        # check if pool is already deployed; else retrieve existing pool infromation from the address
        weth_dai_pool_address = self.position_manager.get_pool_address(self.token_weth, self.token_dai, FEES.MEDIUM.value)
        dai_usdc_pool_address = self.position_manager.get_pool_address(self.token_dai, self.token_usdc, FEES.LOW.value)

        if isinstance(weth_dai_pool_address, Address) and weth_dai_pool_address != Address("0x0000000000000000000000000000000000000000"):
            weth_dai_pool = self.position_manager.get_pool(weth_dai_pool_address, self.token_weth, self.token_dai, 1)
        else:
            weth_dai_pool = self.deploy_and_mint_weth_dai(position_manager_helpers)

        if isinstance(dai_usdc_pool_address, Address) and dai_usdc_pool_address != Address("0x0000000000000000000000000000000000000000"):
            dai_usdc_pool = self.position_manager.get_pool(dai_usdc_pool_address, self.token_dai, self.token_usdc, 1)
        else:
            dai_usdc_pool = self.deploy_and_mint_dai_usdc(position_manager_helpers)

        # set trade params
        weth_out = Wad.from_number(.000000000001)
        # weth_out = Wad.from_number(1)
        recipient = self.our_address
        slippage_tolerance = Fraction(20, 100)
        deadline = int(time.time() + 1000)

        # define route from weth to usdc via dai
        path = [weth_dai_pool, dai_usdc_pool]
        route = Route(path, self.token_weth, self.token_usdc)
        encoded_path = self.swap_router.encode_route_to_path(route, True)

        trade = Trade.from_route(route, CurrencyAmount.from_raw_amount(self.token_weth, weth_out.value), TRADE_TYPE.EXACT_INPUT.value)
        usdc_in = trade.minimum_amount_out(slippage_tolerance).quotient()
        print("usdc_in trade: ", usdc_in)

        assert usdc_in > 0

        # usdc_in_quoter = self.swap_router.quote_exact_output(encoded_path, weth_out)
        # print("usdc_in quoter", usdc_in_quoter)

        exact_output_params = ExactOutputParams(self.web3, self.SwapRouter_abi, encoded_path, recipient, deadline, weth_out.value,
                                              usdc_in)

        swap = self.swap_router.swap_exact_output(exact_output_params).transact()
        assert swap is not None and swap.successful

    def test_should_error_when_pools_on_different_networks(self):
        """ test that both pools have matching chain_id value """
        test_token_1 = Token("test_1", Address("0x0000000000000000000000000000000000000001"), 18)
        test_token_2 = Token("test_2", Address("0x0000000000000000000000000000000000000002"), 18)
        test_token_3 = Token("test_3", Address("0x0000000000000000000000000000000000000003"), 18)

        test_pool_1_medium = Pool(test_token_1, test_token_2, 3000, self.get_starting_sqrt_ratio(1, 1), 0, 0, [], 1)
        test_pool_2_low = Pool(test_token_2, test_token_3, 500, self.get_starting_sqrt_ratio(1, 1), 0, 0, [], 2)
        path = [test_pool_1_medium, test_pool_2_low]

        with pytest.raises(Exception):
            route = Route(path, test_token_1, test_token_3)
class UniswapV2MarketMakerKeeper:
    """Keeper acting as a market maker on Uniswap v2."""

    logger = logging.getLogger()
    send_transaction: bool = False

    def add_arguments(self, parser):
        """Provider info"""
        parser.add_argument(
            "--rpc-host",
            type=str,
            default="http://localhost:8545",
            help="JSON-RPC host (default: `http://localhost:8545`)")

        parser.add_argument("--rpc-timeout",
                            type=int,
                            default=10,
                            help="JSON-RPC timeout (in seconds, default: 10)")

        parser.add_argument(
            "--eth-from",
            type=str,
            required=True,
            help="Ethereum account from which to send transactions")

        parser.add_argument(
            "--eth-key",
            type=str,
            nargs='*',
            help=
            "Ethereum private key(s) to use (e.g. 'key_file=aaa.json,pass_file=aaa.pass')"
        )
        """Exchange info"""
        parser.add_argument(
            "--uniswap-router-address",
            type=str,
            required=True,
            help="Ethereum address of the Uniswap Router v2 contract")
        """Tokens info"""
        parser.add_argument("--first-token-address",
                            type=str,
                            required=True,
                            help="Ethereum address of the first token")

        parser.add_argument("--first-token-name",
                            type=str,
                            required=True,
                            help="name of the first token")

        parser.add_argument("--first-token-decimals",
                            type=int,
                            required=True,
                            help="decimal of the first token")

        parser.add_argument("--second-token-address",
                            type=str,
                            required=True,
                            help="Ethereum address of the second token")

        parser.add_argument("--second-token-name",
                            type=str,
                            required=True,
                            help="name of the second token")

        parser.add_argument("--second-token-decimals",
                            type=int,
                            required=True,
                            help="decimal of the second token")
        """settings"""
        parser.add_argument("--price-feed",
                            type=str,
                            required=True,
                            help="Source of price feed")

        parser.add_argument(
            "--price-feed-expiry",
            type=int,
            default=120,
            help="Maximum age of the price feed (in seconds, default: 120)")

        parser.add_argument("--max-delta-on-percent",
                            type=float,
                            default=3,
                            help="Delta permissible margin")

        parser.add_argument(
            "--max-first-token-amount-input",
            type=float,
            default=10000,
            help=
            "The maximum allowed number of first tokens that can be exchanged for installation."
        )

        parser.add_argument(
            "--max-second-token-amount-input",
            type=float,
            default=10000,
            help=
            "The maximum allowed number of second tokens that can be exchanged for installation."
        )

        parser.add_argument(
            "--min-eth-balance",
            type=float,
            default=0,
            help="Minimum ETH balance below which keeper will cease operation")

        parser.add_argument("--min-first-token-balance",
                            type=float,
                            default=0,
                            help="Minimum first token balance")

        parser.add_argument("--min-second-token-balance",
                            type=float,
                            default=0,
                            help="Minimum second token balance")

        parser.add_argument("--gas-price",
                            type=int,
                            default=50000000000,
                            help="Gas price (in Wei)")

        parser.add_argument(
            "--smart-gas-price",
            dest='smart_gas_price',
            action='store_true',
            help=
            "Use smart gas pricing strategy, based on the ethgasstation.info feed"
        )

        parser.add_argument("--ethgasstation-api-key",
                            type=str,
                            default=None,
                            help="ethgasstation API key")

        parser.add_argument(
            "--refresh-frequency",
            type=int,
            default=10,
            help="Order book refresh frequency (in seconds, default: 10)")

        parser.add_argument("--debug",
                            dest='debug',
                            action='store_true',
                            help="Enable debug output")

        parser.add_argument(
            "--telegram-log-config-file",
            type=str,
            required=False,
            help=
            "config file for send logs to telegram chat (e.g. 'telegram_conf.json')",
            default=None)

        parser.add_argument(
            "--keeper-name",
            type=str,
            required=False,
            help="market maker keeper name (e.g. 'Uniswap_V2_MDTETH')",
            default="Uniswap_V2")

    def __init__(self, args: list, **kwargs):
        parser = argparse.ArgumentParser(prog='uniswap-market-maker-keeper')
        self.add_arguments(parser=parser)
        self.arguments = parser.parse_args(args)
        setup_logging(self.arguments)

        provider = HTTPProvider(
            endpoint_uri=self.arguments.rpc_host,
            request_kwargs={'timeout': self.arguments.rpc_timeout})
        self.web3: Web3 = kwargs['web3'] if 'web3' in kwargs else Web3(
            provider)

        self.web3.eth.defaultAccount = self.arguments.eth_from
        register_keys(self.web3, self.arguments.eth_key)
        self.our_address = Address(self.arguments.eth_from)

        self.uniswap_router = UniswapRouter(
            web3=self.web3,
            router=Address(self.arguments.uniswap_router_address))

        self.first_token = ERC20Token(web3=self.web3,
                                      address=Address(
                                          self.arguments.first_token_address))
        self.second_token = ERC20Token(
            web3=self.web3,
            address=Address(self.arguments.second_token_address))

        self.token_first = Token(name=self.arguments.first_token_name,
                                 address=Address(
                                     self.arguments.first_token_address),
                                 decimals=self.arguments.first_token_decimals)

        self.token_second = Token(
            name=self.arguments.second_token_name,
            address=Address(self.arguments.second_token_address),
            decimals=self.arguments.second_token_decimals)

        self.min_eth_balance = Wad.from_number(self.arguments.min_eth_balance)

        self.gas_price = GasPriceFactory().create_gas_price(self.arguments)
        self.max_delta_on_percent = self.arguments.max_delta_on_percent

        self.price_feed = PriceFeedFactory().create_price_feed(self.arguments)

    def main(self):
        with Lifecycle(self.web3) as lifecycle:
            lifecycle.initial_delay(10)
            lifecycle.on_startup(self.startup)
            lifecycle.every(1, self.synchronize_price)

    def startup(self):
        self.approve()

    def approve(self):
        """Approve Uniswap to access our balances, so we can place orders."""
        self.uniswap_router.approve([self.first_token, self.second_token],
                                    directly(gas_price=self.gas_price))

    def our_available_balance(self, token: ERC20Token) -> Wad:
        if token.symbol() == self.token_first.name:
            return self.token_first.normalize_amount(
                token.balance_of(self.our_address))
        else:
            return self.token_second.normalize_amount(
                token.balance_of(self.our_address))

    @staticmethod
    def _get_amounts(market_price: Wad, first_token_liquidity_pool_amount: Wad,
                     second_token_liquidity_pool_amount: Wad):
        liquidity_pool_constant = first_token_liquidity_pool_amount * second_token_liquidity_pool_amount

        new_first_token_liquidity_pool_amount = sqrt(liquidity_pool_constant *
                                                     market_price)
        new_second_token_liquidity_pool_amount = sqrt(liquidity_pool_constant /
                                                      market_price)

        return AttrDict({
            'exact_value':
            first_token_liquidity_pool_amount -
            Wad.from_number(new_first_token_liquidity_pool_amount),
            'limit':
            Wad.from_number(new_second_token_liquidity_pool_amount) -
            second_token_liquidity_pool_amount,
        })

    def set_price(self, market_price: Wad, first_token: Address,
                  second_token: Address,
                  max_delta_on_percent: int) -> Transact:
        pair = self.uniswap_router.get_pair(first_token=first_token,
                                            second_token=second_token)

        reserves = pair.reserves.map()

        uniswap_price = reserves[first_token] / reserves[second_token]
        delta = (market_price.value * 100 / uniswap_price.value) - 100

        self.logger.debug(f"market price = {market_price}")
        self.logger.debug(f"uniswap price = {uniswap_price}")
        self.logger.debug(
            f"the percentage difference between the market price and the uniswap price = {delta}"
        )

        if delta > max_delta_on_percent:
            self.logger.debug(
                "the price for uniswap is higher than the market price")
            input_data = self._get_amounts(
                market_price=market_price,
                first_token_liquidity_pool_amount=reserves[first_token],
                second_token_liquidity_pool_amount=reserves[second_token])

            calculate_amount = self.uniswap_router.get_amounts_out(
                amount_in=abs(input_data.exact_value),
                path=[first_token, second_token])
            calulate_price = abs(input_data.exact_value) / calculate_amount[-1]

            if abs(input_data.exact_value) > Wad.from_number(
                    self.arguments.max_first_token_amount_input):
                self.logger.info(
                    f"Amount to send first_token > maximum allowed ({abs(input_data.exact_value)} > {self.arguments.max_first_token_amount_input})"
                )
            elif abs(input_data.exact_value) > self.first_token.balance_of(
                    self.our_address):
                self.logger.warning(
                    f"There is not enough balance to change the price "
                    f"(required: {abs(input_data.exact_value)}), "
                    f"balance: {self.first_token.balance_of(self.our_address)}, "
                    f"token={self.first_token.address.address}")
            elif calulate_price > market_price:
                self.logger.info(
                    f"new calulate price > market price ({calulate_price} > {market_price}). The price will not be changed"
                )
            else:
                self.logger.info(
                    f"To change the price, you must perform an exchange ({abs(input_data.exact_value)} {first_token.address} -> {calculate_amount[-1]} {second_token.address})"
                )
                return self.uniswap_router.swap_from_exact_amount(
                    amount_in=abs(input_data.exact_value),
                    min_amount_out=calculate_amount[-1],
                    path=[first_token, second_token])

        elif delta < 0 and abs(delta) > max_delta_on_percent:
            self.logger.debug(
                "the market price is higher than the uniswap price")
            input_data = self._get_amounts(
                market_price=market_price,
                first_token_liquidity_pool_amount=reserves[first_token],
                second_token_liquidity_pool_amount=reserves[second_token])

            calculate_amount = self.uniswap_router.get_amounts_in(
                amount_out=abs(input_data.exact_value),
                path=[second_token, first_token])
            calulate_price = abs(input_data.exact_value) / calculate_amount[0]

            if calculate_amount[0] > Wad.from_number(
                    self.arguments.max_second_token_amount_input):
                self.logger.info(
                    f"Amount to send second_token > maximum allowed ({calculate_amount[0]} > {self.arguments.max_second_token_amount_input})"
                )
            elif calculate_amount[0] > self.second_token.balance_of(
                    self.our_address):
                self.logger.warning(
                    f"There is not enough balance to change the price "
                    f"(required: {calculate_amount[0]}), "
                    f"balance: {self.second_token.balance_of(self.our_address)}, "
                    f"token={self.second_token.address.address}")
            elif calulate_price < market_price:
                self.logger.info(
                    f"new calulate price < market price ({calulate_price} < {market_price}). The price will not be changed"
                )

            else:
                self.logger.info(
                    f"To change the price, you must perform an exchange ({calculate_amount[0]} {second_token.address} -> {abs(input_data.exact_value)} {first_token.address})"
                )
                return self.uniswap_router.swap_to_exact_amount(
                    amount_out=abs(input_data.exact_value),
                    max_amount_in=calculate_amount[0],
                    path=[second_token, first_token])
        else:
            self.logger.debug(
                "the price for uniswap satisfies the input accuracy. The price will not be changed"
            )

    def synchronize_price(self):
        # market_maker = MarketMaker(self.uniswap_router)

        # If keeper balance is below `--min-eth-balance`, cancel all orders but do not terminate
        # the keeper, keep processing blocks as the moment the keeper gets a top-up it should
        # resume activity straight away, without the need to restart it.
        if eth_balance(self.web3, self.our_address) < self.min_eth_balance:
            self.logger.warning("Keeper ETH balance below minimum.")
            return

        if self.first_token.balance_of(self.our_address) < Wad.from_number(
                self.arguments.min_first_token_balance):
            self.logger.warning(
                f"Keeper {self.token_first.name} balance below minimum.")
            return

        if self.second_token.balance_of(self.our_address) < Wad.from_number(
                self.arguments.min_second_token_balance):
            self.logger.warning(
                f"Keeper {self.token_second.name} balance below minimum.")
            return

        target_price = self.price_feed.get_price()

        transaction = self.set_price(
            market_price=target_price.buy_price,
            first_token=self.first_token.address,
            second_token=self.second_token.address,
            max_delta_on_percent=self.max_delta_on_percent)

        if transaction is not None:
            transact = transaction.transact()
            if transact is not None and transact.successful:
                self.logger.info("The price was set successfully")
Ejemplo n.º 30
0
class TestUniswapV2(Contract):
    """
    In order to run automated tests locally, all dependent contracts and deployable bytecode need to be available for deploying contract to local network. 
    Deployable bytecode differs from the runtime bytecode you would see on Etherscan.

    """
    pair_abi = Contract._load_abi(__name__, '../pyexchange/abi/IUniswapV2Pair.abi')
    Irouter_abi = Contract._load_abi(__name__, '../pyexchange/abi/IUniswapV2Router02.abi')['abi']
    router_abi = Contract._load_abi(__name__, '../pyexchange/abi/UniswapV2Router02.abi')
    router_bin = Contract._load_bin(__name__, '../pyexchange/abi/UniswapV2Router02.bin')
    factory_abi = Contract._load_abi(__name__, '../pyexchange/abi/UniswapV2Factory.abi')
    factory_bin = Contract._load_bin(__name__, '../pyexchange/abi/UniswapV2Factory.bin')
    weth_abi = Contract._load_abi(__name__, '../pyexchange/abi/WETH.abi')
    weth_bin = Contract._load_bin(__name__, '../pyexchange/abi/WETH.bin')

    def setup_method(self):

        # Use Ganache docker container
        self.web3 = Web3(HTTPProvider("http://0.0.0.0:8555"))
        self.web3.eth.defaultAccount = Web3.toChecksumAddress("0x9596C16D7bF9323265C2F2E22f43e6c80eB3d943")
        register_private_key(self.web3, "0x91cf2cc3671a365fcbf38010ff97ee31a5b7e674842663c56769e41600696ead")

        self.our_address = Address(self.web3.eth.defaultAccount)

        self.weth_address = self._deploy(self.web3, self.weth_abi, self.weth_bin, [])
        self.factory_address = self._deploy(self.web3, self.factory_abi, self.factory_bin, [self.our_address.address])
        self.router_address = self._deploy(self.web3, self.router_abi, self.router_bin, [self.factory_address.address, self.weth_address.address])
        self._weth_contract = self._get_contract(self.web3, self.weth_abi, self.weth_address)

        self.ds_dai = DSToken.deploy(self.web3, 'DAI')
        self.ds_usdc = DSToken.deploy(self.web3, 'USDC')
        self.token_dai = Token("DAI", self.ds_dai.address, 18)
        self.token_usdc = Token("USDC", self.ds_usdc.address, 6)
        self.token_weth = Token("WETH", self.weth_address, 18)

        self.dai_usdc_uniswap = UniswapV2(self.web3, self.token_dai, self.token_usdc, self.our_address, self.router_address, self.factory_address)
        self.dai_eth_uniswap = UniswapV2(self.web3, self.token_dai, self.token_weth, self.our_address, self.router_address, self.factory_address)
        
        ## Useful for debugging failing transactions
        logger = logging.getLogger('eth')
        logger.setLevel(8)
        # Transact.gas_estimate_for_bad_txs = 210000

    def add_liquidity_tokens(self) -> Receipt:
        self.ds_dai.mint(Wad(17 * 10**18)).transact(from_address=self.our_address)
        self.ds_usdc.mint(self.token_usdc.unnormalize_amount(Wad.from_number(9))).transact(from_address=self.our_address)
        self.dai_usdc_uniswap.approve(self.token_dai)
        self.dai_usdc_uniswap.approve(self.token_usdc)

        add_liquidity_tokens_args = {
            "amount_a_desired": Wad.from_number(1.9),
            "amount_b_desired": self.token_usdc.unnormalize_amount(Wad.from_number(2.0)),
            "amount_a_min": Wad.from_number(1.8),
            "amount_b_min": self.token_usdc.unnormalize_amount(Wad.from_number(1.9))
        }

        return self.dai_usdc_uniswap.add_liquidity(add_liquidity_tokens_args, self.token_dai, self.token_usdc).transact(from_address=self.our_address)

    def add_liquidity_eth(self) -> Receipt:
        self.ds_dai.mint(Wad(300 * 10**18)).transact(from_address=self.our_address)
        self.dai_eth_uniswap.approve(self.token_dai)
        self.dai_eth_uniswap.approve(self.token_weth)

        add_liquidity_eth_args = {
            "amount_b_desired": Wad.from_number(28),
            "amount_a_desired": Wad.from_number(.1),
            "amount_b_min":  Wad.from_number(25),
            "amount_a_min": Wad.from_number(0.01)
        }

        return self.dai_eth_uniswap.add_liquidity_eth(add_liquidity_eth_args, self.token_dai, 0).transact(from_address=self.our_address)

    def test_approval(self):
        # given
        assert self.ds_dai.allowance_of(self.our_address, self.router_address) == Wad(0)

        # when
        self.dai_usdc_uniswap.approve(self.token_dai)

        # then
        assert self.ds_dai.allowance_of(self.our_address, self.router_address) > Wad(0)

    def test_getting_token_balances(self):
        # given
        self.ds_dai.mint(Wad(17 * 10**18)).transact()
        self.ds_usdc.mint(self.token_usdc.unnormalize_amount(Wad.from_number(9))).transact()

        # when
        balance_dai = self.dai_usdc_uniswap.get_account_token_balance(self.token_dai)
        balance_usdc = self.dai_usdc_uniswap.get_account_token_balance(self.token_usdc)

        # then
        assert balance_dai == Wad.from_number(17)
        assert balance_usdc == Wad.from_number(9)

    def test_add_liquidity_tokens(self):
        # when
        add_liquidity = self.add_liquidity_tokens()

        # then
        assert add_liquidity.successful == True

        # when
        self.dai_usdc_uniswap.set_pair_token(self.dai_usdc_uniswap.get_pair_address(self.token_dai.address, self.token_usdc.address))

        # then
        assert self.dai_usdc_uniswap.get_current_liquidity() > Wad.from_number(0)

    def test_add_liquidity_eth(self):
        # when
        add_liquidity_eth = self.add_liquidity_eth()

        # then
        assert add_liquidity_eth.successful == True

        # when
        self.dai_eth_uniswap.set_pair_token(self.dai_eth_uniswap.get_pair_address(self.token_dai.address, self.token_weth.address))

        # then
        assert self.dai_eth_uniswap.get_current_liquidity() > Wad.from_number(0)

    def test_remove_liquidity_tokens(self):
        # given
        add_liquidity = self.add_liquidity_tokens()
        self.dai_usdc_uniswap.set_pair_token(self.dai_usdc_uniswap.get_pair_address(self.token_dai.address, self.token_usdc.address))

        current_liquidity = self.dai_usdc_uniswap.get_current_liquidity()
        total_liquidity = self.dai_usdc_uniswap.get_total_liquidity()
        dai_exchange_balance = self.dai_usdc_uniswap.get_exchange_balance(self.token_dai, self.dai_usdc_uniswap.pair_address)
        usdc_exchange_balance = self.token_usdc.unnormalize_amount(self.dai_usdc_uniswap.get_exchange_balance(self.token_usdc, self.dai_usdc_uniswap.pair_address))

        # then
        assert current_liquidity > Wad.from_number(0)
        assert total_liquidity > Wad.from_number(0)
        assert total_liquidity > current_liquidity

        # given
        amount_a_min = current_liquidity * dai_exchange_balance / total_liquidity
        amount_b_min = current_liquidity * usdc_exchange_balance / total_liquidity
        remove_liquidity_tokens_args = {
            "liquidity": current_liquidity,
            "amountAMin": amount_a_min,
            "amountBMin": amount_b_min
        }

        # when
        remove_liquidity = self.dai_usdc_uniswap.remove_liquidity(remove_liquidity_tokens_args, self.token_dai, self.token_usdc).transact(from_address=self.our_address)

        # then
        assert remove_liquidity.successful == True
        assert self.dai_usdc_uniswap.get_current_liquidity() == Wad.from_number(0)

    def test_remove_liquidity_eth(self):
        # given
        add_liquidity_eth = self.add_liquidity_eth()
        self.dai_eth_uniswap.set_pair_token(self.dai_eth_uniswap.get_pair_address(self.token_dai.address, self.token_weth.address))

        current_liquidity = self.dai_eth_uniswap.get_current_liquidity()
        total_liquidity = self.dai_eth_uniswap.get_total_liquidity()
        dai_exchange_balance = self.dai_eth_uniswap.get_exchange_balance(self.token_dai, self.dai_eth_uniswap.pair_address)
        weth_exchange_balance = self.dai_eth_uniswap.get_exchange_balance(self.token_weth, self.dai_eth_uniswap.pair_address)

        # then
        assert current_liquidity > Wad.from_number(0)
        assert total_liquidity > Wad.from_number(0)
        assert total_liquidity > current_liquidity
        
        # given
        amount_a_min = current_liquidity * weth_exchange_balance / total_liquidity
        amount_b_min = current_liquidity * dai_exchange_balance / total_liquidity
        remove_liquidity_eth_args = {
            "liquidity": current_liquidity,
            "amountBMin": amount_b_min,
            "amountAMin": amount_a_min
        }

        # when
        remove_liquidity = self.dai_eth_uniswap.remove_liquidity_eth(remove_liquidity_eth_args, self.token_dai, 0).transact(from_address=self.our_address)

        # then
        assert remove_liquidity.successful == True
        assert self.dai_eth_uniswap.get_current_liquidity() == Wad.from_number(0)

    def test_tokens_swap(self):
        # given
        add_liquidity = self.add_liquidity_tokens()

        balance_dai_before_swap = self.dai_usdc_uniswap.get_account_token_balance(self.token_dai)
        balance_usdc_before_swap = self.dai_usdc_uniswap.get_account_token_balance(self.token_usdc)

        # when
        swap = self.dai_usdc_uniswap.swap_exact_tokens_for_tokens(Wad.from_number(.4), self.token_usdc.unnormalize_amount(Wad.from_number(.3)), [self.ds_dai.address.address, self.ds_usdc.address.address]).transact(from_address=self.our_address)
        
        # then
        assert swap.successful == True

        balance_dai_after_swap = self.dai_usdc_uniswap.get_account_token_balance(self.token_dai)
        balance_usdc_after_swap = self.dai_usdc_uniswap.get_account_token_balance(self.token_usdc)

        assert balance_dai_after_swap < balance_dai_before_swap
        assert balance_usdc_before_swap < balance_usdc_after_swap