def test_repr(self): coin = Coin("BNB.BNB") self.assertEqual(repr(coin), "<Coin 0_BNB.BNB>") coin = Coin(RUNE, 1000000) self.assertEqual(repr(coin), f"<Coin 1,000,000_{RUNE}>") coin = Coin("BNB.LOK-3C0", 1000000) self.assertEqual(repr(coin), "<Coin 1,000,000_BNB.LOK-3C0>")
def test_to_json(self): txn = Transaction( Binance.chain, "USER", "VAULT", Coin("BNB.BNB", 100), "STAKE:BNB", ) self.assertEqual( txn.to_json(), '{"id": "TODO", "chain": "BNB", "from_address": "USER", ' '"to_address": "VAULT", "memo": "STAKE:BNB", "coins": ' '[{"asset": "BNB.BNB", "amount": 100}], "gas": null}', ) txn.coins = [Coin("BNB.BNB", 1000000000), Coin(RUNE, 1000000000)] self.assertEqual( txn.to_json(), '{"id": "TODO", "chain": "BNB", "from_address": "USER", ' '"to_address": "VAULT", "memo": "STAKE:BNB", "coins": [' '{"asset": "BNB.BNB", "amount": 1000000000}, ' '{"asset": "' + RUNE + '", "amount": 1000000000}], "gas": null}', ) txn.coins = None self.assertEqual( txn.to_json(), '{"id": "TODO", "chain": "BNB", "from_address": "USER", ' '"to_address": "VAULT", "memo": "STAKE:BNB", "coins": null, "gas": null}', ) txn.gas = [Coin("BNB.BNB", 37500)] self.assertEqual( txn.to_json(), '{"id": "TODO", "chain": "BNB", "from_address": "USER", ' '"to_address": "VAULT", "memo": "STAKE:BNB", "coins": null,' ' "gas": [{"asset": "BNB.BNB", "amount": 37500}]}', )
def test_str(self): coin = Coin("BNB.BNB") self.assertEqual(str(coin), "0_BNB.BNB") coin = Coin(RUNE, 1000000) self.assertEqual(str(coin), "1,000,000_" + RUNE) coin = Coin("BNB.LOK-3C0", 1000000) self.assertEqual(str(coin), "1,000,000_BNB.LOK-3C0")
def _calculate_gas(cls, pool, txn): """ With given coin set, calculates the gas owed """ if not isinstance(txn.coins, list) or len(txn.coins) == 1: return Coin(cls.coin, 37500) return Coin(cls.coin, 30000 * len(txn.coins))
def test_custom_hash(self): txn = Transaction( Binance.chain, "USER", "tbnb1yxfyeda8pnlxlmx0z3cwx74w9xevspwdpzdxpj", Coin("BNB.BNB", 194765912), "REFUND:TODO", id="9999A5A08D8FCF942E1AAAA01AB1E521B699BA3A009FA0591C011DC1FFDC5E68", ) self.assertEqual( txn.custom_hash(""), "FE64709713A9F9D691CF2C5B144CA6DAA53E902800C1367C692FE7935BD029CE", ) txn.coins = None self.assertEqual( txn.custom_hash(""), "229BD31DB372A43FB71896BDE7512BFCA06731A4D825B4721A1D8DD800159DCD", ) txn.to_address = "tbnb189az9plcke2c00vns0zfmllfpfdw67dtv25kgx" txn.coins = [Coin(RUNE, 49900000000)] txn.memo = ( "REFUND:CA3A36052DC2FC30B91AD3996012E9EF2E69EEA70D5FBBBD9364F6F97A056D7C" ) pubkey = ( "thorpub1addwnpepqv7kdf473gc4jyls7hlx4rg" "t2lqxm9qkfh5m3ua7wnzzzfhlpz49u4slu4g" ) if DEFAULT_RUNE_ASSET == RUNE: self.assertEqual( txn.custom_hash(pubkey), "158D75777A5C23A5C8A39B55C0812252C0ABA9A87816D5E74BB7166EB95EDB73", )
def check_pools(self): """Check pools state between Midgard and Thorchain APIs. """ for tpool in self.thorchain_pools: asset = tpool["asset"] mpool = self.get_midgard_pool(asset) # Thorchain Coins trune_coin = Coin(RUNE, tpool["balance_rune"]) tasset_coin = Coin(asset, tpool["balance_asset"]) # Midgard Coins mrune_coin = Coin(RUNE, mpool["runeDepth"]) masset_coin = Coin(asset, mpool["assetDepth"]) # Check balances if trune_coin != mrune_coin: self.error(f"Bad Midgard Pool-{asset} balance: RUNE " f"{mrune_coin} != {trune_coin}") if tasset_coin != masset_coin: self.error(f"Bad Midgard Pool-{asset} balance: ASSET " f"{masset_coin} != {tasset_coin}") # Check pool units mpool_units = int(mpool["poolUnits"]) tpool_units = int(tpool["pool_units"]) if mpool_units != tpool_units: self.error(f"Bad Midgard Pool-{asset} units: " f"{mpool_units} != {tpool_units}")
def _calculate_gas(cls, pool, txn): """ Calculate gas according to RUNE thorchain fee 1 RUNE / 2 in BTC value """ if pool is None: return Coin(cls.coin, MockBitcoin.default_gas) btc_amount = pool.get_rune_in_asset(int(cls.rune_fee / 2)) return Coin(cls.coin, btc_amount)
def test_to_json(self): coin = Coin("BNB.BNB") self.assertEqual(coin.to_json(), '{"asset": "BNB.BNB", "amount": 0}') coin = Coin(RUNE, 1000000) self.assertEqual(coin.to_json(), '{"asset": "' + RUNE + '", "amount": 1000000}') coin = Coin("BNB.LOK-3C0", 1000000) self.assertEqual(coin.to_json(), '{"asset": "BNB.LOK-3C0", "amount": 1000000}')
def test_to_binance_fmt(self): coin = Coin("BNB.BNB") self.assertEqual(coin.to_binance_fmt(), {"denom": "BNB", "amount": 0}) coin = Coin("RUNE", 1000000) self.assertEqual(coin.to_binance_fmt(), {"denom": "RUNE", "amount": 1000000}) coin = Coin("LOK-3C0", 1000000) self.assertEqual(coin.to_binance_fmt(), {"denom": "LOK-3C0", "amount": 1000000})
def test_is_zero(self): coin = Coin("BNB.BNB", 100) self.assertEqual(coin.is_zero(), False) coin = Coin("BNB.BNB") self.assertEqual(coin.is_zero(), True) coin = Coin(RUNE, 0) self.assertEqual(coin.is_zero(), True)
def test_addsub(self): acct = Account("tbnbA") acct.add(Coin("BNB.BNB", 25)) self.assertEqual(acct.get("BNB.BNB"), 25) acct.add([Coin("BNB.BNB", 20), Coin(RUNE, 100)]) self.assertEqual(acct.get("BNB.BNB"), 45) self.assertEqual(acct.get(RUNE), 100) acct.sub([Coin("BNB.BNB", 20), Coin(RUNE, 100)]) self.assertEqual(acct.get("BNB.BNB"), 25) self.assertEqual(acct.get(RUNE), 0)
def swap(self, coin, asset): """ Does a swap returning amount of coins emitted and new pool :param Coin coin: coin sent to swap :param Asset asset: target asset :returns: list of events - emit (int) - number of coins to be emitted for the swap - liquidity_fee (int) - liquidity fee - liquidity_fee_in_rune (int) - liquidity fee in rune - trade_slip (int) - trade slip - pool (Pool) - pool with new values """ if not coin.is_rune(): asset = coin.asset pool = self.get_pool(asset) if coin.is_rune(): X = pool.rune_balance Y = pool.asset_balance else: X = pool.asset_balance Y = pool.rune_balance x = coin.amount emit = self._calc_asset_emission(X, x, Y) # calculate the liquidity fee (in rune) liquidity_fee = self._calc_liquidity_fee(X, x, Y) liquidity_fee_in_rune = liquidity_fee if coin.is_rune(): liquidity_fee_in_rune = pool.get_asset_in_rune(liquidity_fee) # calculate trade slip trade_slip = self._calc_trade_slip(X, x) # if we emit zero, return immediately if emit == 0: return Coin(asset, emit), 0, 0, 0, pool newPool = deepcopy(pool) # copy of pool if coin.is_rune(): newPool.add(x, 0) newPool.sub(0, emit) emit = Coin(asset, emit) else: newPool.add(0, x) newPool.sub(emit, 0) emit = Coin(RUNE, emit) return emit, liquidity_fee, liquidity_fee_in_rune, trade_slip, newPool
def test_constructor(self): txn = Transaction(Binance.chain, "USER", "VAULT", Coin("BNB.BNB", 100), "MEMO",) self.assertEqual(txn.chain, "BNB") self.assertEqual(txn.from_address, "USER") self.assertEqual(txn.to_address, "VAULT") self.assertEqual(txn.coins[0].asset, "BNB.BNB") self.assertEqual(txn.coins[0].amount, 100) self.assertEqual(txn.memo, "MEMO") txn.coins = [Coin("BNB.BNB", 1000000000), Coin(RUNE, 1000000000)] self.assertEqual(txn.coins[0].asset, "BNB.BNB") self.assertEqual(txn.coins[0].amount, 1000000000) self.assertEqual(txn.coins[1].asset, RUNE) self.assertEqual(txn.coins[1].amount, 1000000000)
def test_from_dict(self): value = { "asset": "BNB.BNB", "amount": 1000, } coin = Coin.from_dict(value) self.assertEqual(coin.asset, "BNB.BNB") self.assertEqual(coin.amount, 1000) value = { "asset": RUNE, "amount": "1000", } coin = Coin.from_dict(value) self.assertEqual(coin.asset, RUNE) self.assertEqual(coin.amount, 1000)
def test_is_cross_chain_stake(self): tx = Transaction( Binance.chain, "USER", "VAULT", Coin("BNB.BNB", 100), "STAKE:BNB.BNB:STAKER-1", ) self.assertEqual(tx.is_cross_chain_stake(), False) tx = Transaction( "THOR", "USER", "VAULT", Coin("THOR.RUNE", 100), "STAKE:BNB.BNB:STAKER-1", ) self.assertEqual(tx.is_cross_chain_stake(), True) tx = Transaction("THOR", "USER", "VAULT", Coin("THOR.RUNE", 100), "STAKE:",) self.assertEqual(tx.is_cross_chain_stake(), False)
def test_transfer(self): bnb = Binance() from_acct = bnb.get_account("tbnbA") from_acct.add(Coin("BNB.BNB", 300000000)) bnb.set_account(from_acct) txn = Transaction( bnb.chain, "tbnbA", "tbnbB", Coin("BNB.BNB", 200000000), "test transfer" ) bnb.transfer(txn) to_acct = bnb.get_account("tbnbB") self.assertEqual(to_acct.get("BNB.BNB"), 200000000) self.assertEqual(from_acct.get("BNB.BNB"), 99962500)
def __init__(self): super().__init__() # seeding the users, these seeds are established in build/scripts/genesis.sh acct = Account("tthor1j08ys4ct2hzzc2hcz6h2hgrvlmsjynawtaa5zs") acct.add(Coin(self.coin, 5000000000000)) self.set_account(acct) acct = Account("tthor1zupk5lmc84r2dh738a9g3zscavannjy3nzplwt") acct.add(Coin(self.coin, 25000000000100)) self.set_account(acct) acct = Account("tthor1qqnde7kqe5sf96j6zf8jpzwr44dh4gkddurry6") acct.add(Coin(self.coin, 5090000000000)) self.set_account(acct)
def test_constructor(self): coin = Coin("BNB.BNB", 100) self.assertEqual(coin.asset, "BNB.BNB") self.assertEqual(coin.amount, 100) coin = Coin("BNB.BNB") self.assertEqual(coin.asset, "BNB.BNB") self.assertEqual(coin.amount, 0) coin = Coin(RUNE, 1000000) self.assertEqual(coin.amount, 1000000) self.assertEqual(coin.asset, RUNE) coin = Coin(RUNE, 400_000 * 100000000) c = coin.__dict__ self.assertEqual(c["amount"], 400_000 * 100000000) self.assertEqual(c["asset"], RUNE)
def transfer(self, txn): """ Make a transaction/transfer on regtest bitcoin """ self.wait_for_node() if not isinstance(txn.coins, list): txn.coins = [txn.coins] if txn.to_address in get_aliases(): txn.to_address = get_alias_address(txn.chain, txn.to_address) if txn.from_address in get_aliases(): txn.from_address = get_alias_address(txn.chain, txn.from_address) # update memo with actual address (over alias name) for alias in get_aliases(): chain = txn.chain asset = txn.get_asset_from_memo() if asset: chain = asset.get_chain() # we use RUNE BNB address to identify a cross chain stake if txn.memo.startswith("STAKE"): chain = RUNE.get_chain() addr = get_alias_address(chain, alias) txn.memo = txn.memo.replace(alias, addr) # create transaction amount = float(txn.coins[0].amount / Coin.ONE) tx_out_dest = {txn.to_address: amount} tx_out_op_return = {"data": txn.memo.encode().hex()} # get unspents UTXOs address = txn.from_address min_amount = float(amount + (self.default_gas / Coin.ONE)) # add more for fee unspents = self.call("listunspent", 1, 9999, [str(address)], True, {"minimumAmount": min_amount}) if len(unspents) == 0: raise Exception( f"Cannot transfer. No BTC UTXO available for {address}") # choose the first UTXO unspent = unspents[0] tx_in = [{"txid": unspent["txid"], "vout": unspent["vout"]}] tx_out = [tx_out_dest] # create change output if needed amount_utxo = float(unspent["amount"]) amount_change = Decimal(amount_utxo) - Decimal(min_amount) if amount_change > 0: tx_out.append({txn.from_address: float(amount_change)}) tx_out.append(tx_out_op_return) tx = self.call("createrawtransaction", tx_in, tx_out) tx = self.call("signrawtransactionwithwallet", tx) txn.id = self.call("sendrawtransaction", tx["hex"]).upper() txn.gas = [Coin("BTC.BTC", self.default_gas)]
def handle_gas(self, txns): """ Subtracts gas from pool :param list Transaction: list outbound transaction updated with gas """ gas_coins = {} gas_coin_count = {} for txn in txns: if txn.gas: for gas in txn.gas: if gas.asset not in gas_coins: gas_coins[gas.asset] = Coin(gas.asset) gas_coin_count[gas.asset] = 0 gas_coins[gas.asset].amount += gas.amount gas_coin_count[gas.asset] += 1 if not len(gas_coins.items()): return for asset, gas in gas_coins.items(): pool = self.get_pool(gas.asset) # TODO: this is a hacky way to avoid # the problem of gas overdrawing a # balance. clean this up later if pool.asset_balance <= gas.amount: pool.asset_balance = 0 rune_amt = 0 else: # figure out how much rune is an equal amount to gas.amount rune_amt = pool.get_asset_in_rune(gas.amount) self.reserve -= rune_amt # take rune from the reserve pool.add(rune_amt, 0) # replenish gas costs with rune pool.sub(0, gas.amount) # subtract gas from pool self.set_pool(pool) # add gas event event = Event( "gas", [ { "asset": asset }, { "asset_amt": gas.amount }, { "rune_amt": rune_amt }, { "transaction_count": gas_coin_count[asset] }, ], ) self.events.append(event)
def check_binance(self): # compare simulation binance vs mock binance mock_accounts = self.mock_binance.accounts() for macct in mock_accounts: for name, address in aliases_bnb.items(): if name == "MASTER": continue # don't care to compare MASTER account if address == macct["address"]: sacct = self.binance.get_account(address) for bal in macct["balances"]: sim_coin = Coin(f"BNB.{bal['denom']}", sacct.get(f"BNB.{bal['denom']}")) bnb_coin = Coin(f"BNB.{bal['denom']}", bal["amount"]) if sim_coin != bnb_coin: self.error( f"Bad binance balance: {name} {bnb_coin} != {sim_coin}" )
def test_str(self): txn = Transaction(Binance.chain, "USER", "VAULT", Coin("BNB.BNB", 100), "MEMO",) self.assertEqual(str(txn), "Tx USER ==> VAULT | MEMO | 100_BNB.BNB") txn.coins = [Coin("BNB.BNB", 1000000000), Coin(RUNE, 1000000000)] self.assertEqual( str(txn), "Tx USER ==> VAULT | MEMO | 1,000,000,000_BNB.BNB" f", 1,000,000,000_{RUNE}", ) txn.coins = None self.assertEqual( str(txn), "Tx USER ==> VAULT | MEMO | No Coins", ) txn.gas = [Coin("BNB.BNB", 37500)] self.assertEqual( str(txn), "Tx USER ==> VAULT | MEMO | No Coins | Gas 37,500_BNB.BNB", )
def test_gas(self): bnb = Binance() txn = Transaction( Binance.chain, "USER", "VAULT", Coin("BNB.BNB", 5757575), "MEMO", ) self.assertEqual( bnb._calculate_gas(None, txn), Coin("BNB.BNB", 37500), ) txn = Transaction( Binance.chain, "USER", "VAULT", [Coin("BNB.BNB", 0), Coin("RUNE", 0)], "MEMO", ) self.assertEqual( bnb._calculate_gas(None, txn), Coin("BNB.BNB", 60000), )
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}" )
def test_repr(self): txn = Transaction(Binance.chain, "USER", "VAULT", Coin("BNB.BNB", 100), "MEMO",) self.assertEqual( repr(txn), "<Tx USER ==> VAULT | MEMO | [<Coin 100_BNB.BNB>]>" ) txn.coins = [Coin("BNB.BNB", 1000000000), Coin(RUNE, 1000000000)] self.assertEqual( repr(txn), "<Tx USER ==> VAULT | MEMO | [<Coin 1,000,000,000_BNB.BNB>," f" <Coin 1,000,000,000_{RUNE}>]>", ) txn.coins = None self.assertEqual( repr(txn), "<Tx USER ==> VAULT | MEMO | No Coins>", ) txn.gas = [Coin("BNB.BNB", 37500)] self.assertEqual( repr(txn), "<Tx USER ==> VAULT | MEMO | No Coins |" " Gas [<Coin 37,500_BNB.BNB>]>", )
def retrieve_data(self): """Retrieve data from APIs needed to run health checks. """ self.thorchain_asgard_vaults = self.thorchain_client.get_asgard_vaults( ) for vault in self.thorchain_asgard_vaults: if vault["coins"]: vault["coins"] = [Coin.from_dict(c) for c in vault["coins"]] self.binance_accounts = [] accounts = self.binance_client.accounts() for acct in accounts: account = Account(acct["address"]) if acct["balances"]: account.balances = [ Coin(b["denom"], b["amount"]) for b in acct["balances"] ] self.binance_accounts.append(account) self.thorchain_pools = self.thorchain_client.get_pools() if len(self.thorchain_pools) == 0: return pool_assets = [p["asset"] for p in self.thorchain_pools] self.midgard_pools = self.midgard_client.get_pool(pool_assets)
def transfer(self, txns): if not isinstance(txns, list): txns = [txns] for txn in txns: if not isinstance(txn.coins, list): txn.coins = [txn.coins] name = txn.from_address txn.gas = [Coin("THOR.RUNE", 100000000)] if txn.from_address in get_aliases(): txn.from_address = get_alias_address(txn.chain, txn.from_address) if txn.to_address in get_aliases(): txn.to_address = get_alias_address(txn.chain, txn.to_address) # update memo with actual address (over alias name) for alias in get_aliases(): chain = txn.chain asset = txn.get_asset_from_memo() if asset: chain = asset.get_chain() addr = get_alias_address(chain, alias) txn.memo = txn.memo.replace(alias, addr) acct = self._get_account(txn.from_address) payload = { "coins": [coin.to_thorchain_fmt() for coin in txn.coins], "memo": txn.memo, "base_req": { "chain_id": "thorchain", "from": txn.from_address }, } payload = self.post("/thorchain/native/tx", payload) msgs = payload["value"]["msg"] fee = payload["value"]["fee"] acct_num = acct["result"]["value"]["account_number"] seq = acct["result"]["value"]["sequence"] sig = self._sign( name, self._get_sign_message("thorchain", acct_num, fee, seq, msgs)) pushable = self.get_pushable(name, msgs, sig, fee, acct_num, seq) result = self.send(pushable) txn.id = result["txhash"]
def transfer(self, txn): """ Make a transaction/transfer on localnet Ethereum """ if not isinstance(txn.coins, list): txn.coins = [txn.coins] if txn.to_address in get_aliases(): txn.to_address = get_alias_address(txn.chain, txn.to_address) if txn.from_address in get_aliases(): txn.from_address = get_alias_address(txn.chain, txn.from_address) # update memo with actual address (over alias name) for alias in get_aliases(): chain = txn.chain asset = txn.get_asset_from_memo() if asset: chain = asset.get_chain() # we use RUNE BNB address to identify a cross chain stake if txn.memo.startswith("STAKE"): chain = RUNE.get_chain() addr = get_alias_address(chain, alias) txn.memo = txn.memo.replace(alias, addr) # create and send transaction tx = { "from": Web3.toChecksumAddress(txn.from_address), "to": Web3.toChecksumAddress(txn.to_address), "value": txn.coins[0].amount, "data": "0x" + txn.memo.encode().hex(), "gas": calculate_gas(txn.memo), } tx_hash = self.web3.geth.personal.send_transaction(tx, self.passphrase) receipt = self.web3.eth.waitForTransactionReceipt(tx_hash) txn.id = receipt["transactionHash"].hex()[2:].upper() txn.gas = [Coin("ETH.ETH", receipt["cumulativeGasUsed"] * self.gas_price)]
def _calculate_gas(cls, pool, txn): """ Calculate gas according to RUNE thorchain fee 1 RUNE / 2 in ETH value """ return Coin(cls.coin, calculate_gas("") * MockEthereum.gas_price)
def _calculate_gas(cls, pool, txn): """ With given coin set, calculates the gas owed """ return Coin(cls.coin, 100000000)