Пример #1
0
 def get_balance(self, address, asset=Asset("THOR.RUNE")):
     """
     Get THOR balance for an address
     """
     if "VAULT" == get_alias("THOR", address):
         balance = self.fetch("/thorchain/balance/module/asgard")
         for coin in balance:
             if coin["denom"] == asset.get_symbol().lower():
                 return int(coin["amount"])
     else:
         balance = self.fetch("/auth/accounts/" + address)
         for coin in balance["result"]["value"]["coins"]:
             if coin["denom"] == asset.get_symbol().lower():
                 return int(coin["amount"])
     return 0
Пример #2
0
 def check_chain(self, chain, mock, reorg):
     # compare simulation bitcoin vs mock bitcoin
     for addr, sim_acct in chain.accounts.items():
         name = get_alias(chain.chain, addr)
         if name == "MASTER":
             continue  # don't care to compare MASTER account
         if name == "VAULT" and chain.chain == "THOR":
             continue  # don't care about vault for thorchain
         mock_coin = Coin(chain.coin, mock.get_balance(addr))
         sim_coin = Coin(chain.coin, sim_acct.get(chain.coin))
         # dont raise error on reorg balance being invalidated
         # sim is not smart enough to subtract funds on reorg
         if mock_coin.amount == 0 and reorg:
             return
         if sim_coin != mock_coin:
             self.error(
                 f"Bad {chain.name} balance: {name} {mock_coin} != {sim_coin}"
             )
Пример #3
0
    def handle_swap(self, txn):
        """
        Does a swap (or double swap)
        MEMO: SWAP:<asset(req)>:<address(op)>:<target_trade(op)>
        """
        # parse memo
        parts = txn.memo.split(":")
        if len(parts) < 2:
            if txn.memo == "":
                return self.refund(txn, 105, "memo can't be empty")
            return self.refund(txn, 105, f"invalid tx type: {txn.memo}")

        address = txn.from_address
        # check address to send to from memo
        if len(parts) > 2:
            address = parts[2]
            # checking if address is for mainnet, not testnet
            if address.lower().startswith("bnb"):
                reason = f"address format not supported: {address}"
                return self.refund(txn, 105, reason)

        # get trade target, if exists
        target_trade = 0
        if len(parts) > 3:
            target_trade = int(parts[3] or "0")

        asset = Asset(parts[1])

        # check that we have one coin
        if len(txn.coins) != 1:
            reason = "unknown request: not expecting multiple coins in a swap"
            return self.refund(txn, 105, reason)

        source = txn.coins[0].asset
        target = asset

        # refund if we're trying to swap with the coin we given ie swapping bnb
        # with bnb
        if source == asset:
            reason = "unknown request: swap Source and Target cannot be the same."
            return self.refund(txn, 105, reason)

        pools = []

        in_txn = txn

        if not txn.coins[0].is_rune() and not asset.is_rune():
            # its a double swap
            pool = self.get_pool(source)
            if pool.is_zero():
                # FIXME real world message
                return self.refund(txn, 105, "refund reason message")

            emit, liquidity_fee, liquidity_fee_in_rune, trade_slip, pool = self.swap(
                txn.coins[0], RUNE)
            if str(pool.asset) not in self.liquidity:
                self.liquidity[str(pool.asset)] = 0
            self.liquidity[str(pool.asset)] += liquidity_fee_in_rune

            # here we copy the txn to break references cause
            # the tx is split in 2 events and gas is handled only once
            in_txn = deepcopy(txn)

            # generate first swap "fake" outbound event
            out_txn = Transaction(
                emit.asset.get_chain(),
                txn.from_address,
                txn.to_address,
                [emit],
                txn.memo,
                id=Transaction.empty_id,
            )

            event = Event("outbound", [{
                "in_tx_id": in_txn.id
            }, *out_txn.get_attributes()])
            self.events.append(event)

            # generate event for SWAP transaction
            event = Event(
                "swap",
                [
                    {
                        "pool": pool.asset
                    },
                    {
                        "price_target": 0
                    },
                    {
                        "trade_slip": trade_slip
                    },
                    {
                        "liquidity_fee": liquidity_fee
                    },
                    {
                        "liquidity_fee_in_rune": liquidity_fee_in_rune
                    },
                    *in_txn.get_attributes(),
                ],
            )
            self.events.append(event)

            # and we remove the gas on in_txn for the next event so we don't
            # have it twice
            in_txn.gas = None

            pools.append(pool)
            in_txn.coins[0] = emit
            source = RUNE
            target = asset

        # set asset to non-rune asset
        asset = source
        if asset.is_rune():
            asset = target

        pool = self.get_pool(asset)
        if pool.is_zero():
            # FIXME real world message
            return self.refund(in_txn, 105,
                               "refund reason message: pool is zero")

        emit, liquidity_fee, liquidity_fee_in_rune, trade_slip, pool = self.swap(
            in_txn.coins[0], asset)
        pools.append(pool)

        # check emit is non-zero and is not less than the target trade
        if emit.is_zero() or (emit.amount < target_trade):
            reason = f"emit asset {emit.amount} less than price limit {target_trade}"
            return self.refund(in_txn, 108, reason)

        if str(pool.asset) not in self.liquidity:
            self.liquidity[str(pool.asset)] = 0
        self.liquidity[str(pool.asset)] += liquidity_fee_in_rune

        # save pools
        for pool in pools:
            self.set_pool(pool)

        # get from address VAULT cross chain
        from_address = in_txn.to_address
        if from_address != "VAULT":  # don't replace for unit tests
            from_alias = get_alias(in_txn.chain, from_address)
            from_address = get_alias_address(target.get_chain(), from_alias)

        gas = None

        # calculate gas if BTC
        if target.get_chain() == "BTC":
            gas = [Bitcoin._calculate_gas(pool, txn)]

        # calculate gas if ETH
        if target.get_chain() == "ETH":
            gas = [Ethereum._calculate_gas(pool, txn)]

        out_txns = [
            Transaction(
                target.get_chain(),
                from_address,
                address,
                [emit],
                f"OUTBOUND:{txn.id.upper()}",
                gas=gas,
            )
        ]

        # generate event for SWAP transaction
        event = Event(
            "swap",
            [
                {
                    "pool": pool.asset
                },
                {
                    "price_target": target_trade
                },
                {
                    "trade_slip": trade_slip
                },
                {
                    "liquidity_fee": liquidity_fee
                },
                {
                    "liquidity_fee_in_rune": liquidity_fee_in_rune
                },
                *in_txn.get_attributes(),
            ],
        )
        self.events.append(event)

        return out_txns
Пример #4
0
    def handle_unstake(self, txn):
        """
        handles a unstaking transaction
        MEMO: WITHDRAW:<asset(req)>:<address(op)>:<basis_points(op)>
        """
        withdraw_basis_points = 10000

        # parse memo
        parts = txn.memo.split(":")
        if len(parts) < 2:
            if txn.memo == "":
                return self.refund(txn, 105, "memo can't be empty")
            return self.refund(txn, 105, f"invalid tx type: {txn.memo}")

        # get withdrawal basis points, if it exists in the memo
        if len(parts) >= 3:
            withdraw_basis_points = int(parts[2])

        # empty asset
        if parts[1] == "":
            return self.refund(txn, 105, "Invalid symbol")

        asset = Asset(parts[1])

        # add any rune to the reserve
        for coin in txn.coins:
            if coin.asset.is_rune():
                self.reserve += coin.amount
            else:
                coin.amount = 0

        pool = self.get_pool(asset)
        staker = pool.get_staker(txn.from_address)
        if staker.is_zero():
            # FIXME real world message
            return self.refund(txn, 105, "refund reason message")

        # calculate gas prior to update pool in case we empty the pool
        # and need to subtract
        gas = None
        if asset.get_chain() == "BTC":
            gas = [Bitcoin._calculate_gas(pool, txn)]

        if asset.get_chain() == "ETH":
            gas = [Ethereum._calculate_gas(pool, txn)]

        unstake_units, rune_amt, asset_amt = pool.unstake(
            txn.from_address, withdraw_basis_points)

        # if this is our last staker of bnb, subtract a little BNB for gas.
        if pool.total_units == 0:
            if pool.asset.is_bnb():
                fee_amt = 37500
                if RUNE.get_chain() == "BNB":
                    fee_amt *= 2
                asset_amt -= fee_amt
                pool.asset_balance += fee_amt
            elif pool.asset.is_btc() or pool.asset.is_eth():
                asset_amt -= gas[0].amount
                pool.asset_balance += gas[0].amount

        self.set_pool(pool)

        # get from address VAULT cross chain
        from_address = txn.to_address
        if from_address != "VAULT":  # don't replace for unit tests
            from_alias = get_alias(txn.chain, from_address)
            from_address = get_alias_address(asset.get_chain(), from_alias)

        # get to address cross chain
        to_address = txn.from_address
        if to_address not in get_aliases():  # don't replace for unit tests
            to_alias = get_alias(txn.chain, to_address)
            to_address = get_alias_address(asset.get_chain(), to_alias)

        out_txns = [
            Transaction(
                RUNE.get_chain(),
                txn.to_address,
                txn.from_address,
                [Coin(RUNE, rune_amt)],
                f"OUTBOUND:{txn.id.upper()}",
            ),
            Transaction(
                asset.get_chain(),
                from_address,
                to_address,
                [Coin(asset, asset_amt)],
                f"OUTBOUND:{txn.id.upper()}",
                gas=gas,
            ),
        ]

        # generate event for UNSTAKE transaction
        event = Event(
            "unstake",
            [
                {
                    "pool": pool.asset
                },
                {
                    "stake_units": unstake_units
                },
                {
                    "basis_points": withdraw_basis_points
                },
                {
                    "asymmetry": "0.000000000000000000"
                },
                *txn.get_attributes(),
            ],
        )
        self.events.append(event)

        return out_txns
Пример #5
0
    def run(self):
        logging.info(f">>> Starting benchmark... ({self.tx_type}: {self.num})")
        logging.info(">>> setting up...")
        # seed staker
        self.mock_binance.transfer(
            Transaction(
                "BNB",
                get_alias("BNB", "MASTER"),
                get_alias("BNB", "STAKER-1"),
                [
                    Coin("BNB.BNB", self.num * 100 * Coin.ONE),
                    Coin(RUNE, self.num * 100 * Coin.ONE),
                ],
            )
        )

        # seed swapper
        self.mock_binance.transfer(
            Transaction(
                "BNB",
                get_alias("BNB", "MASTER"),
                get_alias("BNB", "USER-1"),
                [
                    Coin("BNB.BNB", self.num * 100 * Coin.ONE),
                    Coin(RUNE, self.num * 100 * Coin.ONE),
                ],
            )
        )

        if self.tx_type == "swap":
            # stake BNB
            self.mock_binance.transfer(
                Transaction(
                    "BNB",
                    get_alias("BNB", "STAKER-1"),
                    get_alias("BNB", "VAULT"),
                    [
                        Coin("BNB.BNB", self.num * 100 * Coin.ONE),
                        Coin(RUNE, self.num * 100 * Coin.ONE),
                    ],
                    memo="STAKE:BNB.BNB",
                )
            )

        time.sleep(5)  # give thorchain extra time to start the blockchain

        logging.info("<<< done.")
        logging.info(">>> compiling transactions...")
        txns = []
        memo = f"{self.tx_type}:BNB.BNB"
        for x in range(0, self.num):
            if self.tx_type == "stake":
                coins = [
                    Coin(RUNE, 10 * Coin.ONE),
                    Coin("BNB.BNB", 10 * Coin.ONE),
                ]
            elif self.tx_type == "swap":
                coins = [
                    Coin(RUNE, 10 * Coin.ONE),
                ]
            txns.append(
                Transaction(
                    "BNB",
                    get_alias("BNB", "USER-1"),
                    get_alias("BNB", "VAULT"),
                    coins,
                    memo=memo,
                )
            )

        logging.info("<<< done.")
        logging.info(">>> broadcasting transactions...")
        self.mock_binance.transfer(txns)
        logging.info("<<< done.")

        logging.info(">>> timing for thorchain...")
        start_block_height = self.thorchain_client.get_block_height()
        t1 = time.time()
        completed = 0

        pbar = tqdm(total=self.num)
        while completed < self.num:
            events = self.thorchain_client.events
            if len(events) == 0:
                time.sleep(1)
                continue
            completed = len([e for e in events if e.type == self.tx_type.lower()])
            pbar.update(completed)
            time.sleep(1)
        pbar.close()

        t2 = time.time()
        end_block_height = self.thorchain_client.get_block_height()
        total_time = t2 - t1
        total_blocks = end_block_height - start_block_height
        logging.info("<<< done.")
        logging.info(f"({self.tx_type}: {completed}")
        logging.info(f"Blocks: {total_blocks}, {total_time} seconds)")